read_fonts/tables/cff.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
//! The [CFF](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table
include!("../../generated/generated_cff.rs");
use super::postscript::{Index1, Latin1String, StringId};
/// The [Compact Font Format](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table.
#[derive(Clone)]
pub struct Cff<'a> {
header: CffHeader<'a>,
names: Index1<'a>,
top_dicts: Index1<'a>,
strings: Index1<'a>,
global_subrs: Index1<'a>,
}
impl<'a> Cff<'a> {
pub fn offset_data(&self) -> FontData<'a> {
self.header.offset_data()
}
pub fn header(&self) -> CffHeader<'a> {
self.header.clone()
}
/// Returns the name index.
///
/// This contains the PostScript names of all fonts in the font set.
///
/// See "Name INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=13>
pub fn names(&self) -> Index1<'a> {
self.names.clone()
}
/// Returns the PostScript name for the font in the font set at the
/// given index.
pub fn name(&self, index: usize) -> Option<Latin1String<'a>> {
Some(Latin1String::new(self.names.get(index).ok()?))
}
/// Returns the top dict index.
///
/// This contains the top-level DICTs of all fonts in the font set. The
/// objects here correspond to those in the name index.
///
/// See "Top DICT INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=14>
pub fn top_dicts(&self) -> Index1<'a> {
self.top_dicts.clone()
}
/// Returns the string index.
///
/// This contains all of the strings used by fonts within the font set.
/// They are referenced by string identifiers represented by the
/// [`StringId`] type.
///
/// See "String INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=17>
pub fn strings(&self) -> Index1<'a> {
self.strings.clone()
}
/// Returns the associated string for the given identifier.
///
/// If the identifier does not represent a standard string, the result is
/// looked up in the string index.
pub fn string(&self, id: StringId) -> Option<Latin1String<'a>> {
match id.standard_string() {
Ok(name) => Some(name),
Err(ix) => self.strings.get(ix).ok().map(Latin1String::new),
}
}
/// Returns the global subroutine index.
///
/// This contains sub-programs that are referenced by one or more
/// charstrings in the font set.
///
/// See "Local/Global Subrs INDEXes" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=25>
pub fn global_subrs(&self) -> Index1<'a> {
self.global_subrs.clone()
}
}
impl TopLevelTable for Cff<'_> {
const TAG: Tag = Tag::new(b"CFF ");
}
impl<'a> FontRead<'a> for Cff<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let header = CffHeader::read(data)?;
let mut data = FontData::new(header.trailing_data());
let names = Index1::read(data)?;
data = data
.split_off(names.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let top_dicts = Index1::read(data)?;
data = data
.split_off(top_dicts.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let strings = Index1::read(data)?;
data = data
.split_off(strings.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let global_subrs = Index1::read(data)?;
Ok(Self {
header,
names,
top_dicts,
strings,
global_subrs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{tables::postscript::StringId, FontRef, TableProvider};
#[test]
fn read_noto_serif_display_cff() {
let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
let cff = font.cff().unwrap();
assert_eq!(cff.header().major(), 1);
assert_eq!(cff.header().minor(), 0);
assert_eq!(cff.top_dicts().count(), 1);
assert_eq!(cff.names().count(), 1);
assert_eq!(cff.global_subrs.count(), 17);
let name = Latin1String::new(cff.names().get(0).unwrap());
assert_eq!(name, "NotoSerifDisplay-Regular");
assert_eq!(cff.strings().count(), 5);
// Version
assert_eq!(cff.string(StringId::new(391)).unwrap(), "2.9");
// Notice
assert_eq!(
cff.string(StringId::new(392)).unwrap(),
"Noto is a trademark of Google LLC."
);
// Copyright
assert_eq!(
cff.string(StringId::new(393)).unwrap(),
"Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
);
// FullName
assert_eq!(
cff.string(StringId::new(394)).unwrap(),
"Noto Serif Display Regular"
);
// FamilyName
assert_eq!(
cff.string(StringId::new(395)).unwrap(),
"Noto Serif Display"
);
}
}