1 | use ttf_parser::gdef::GlyphClass; |
---|---|
2 | use ttf_parser::opentype_layout::LayoutTable; |
3 | use ttf_parser::GlyphId; |
4 | |
5 | use crate::buffer::GlyphPropsFlags; |
6 | use crate::ot::{PositioningTable, SubstitutionTable, TableIndex}; |
7 | use crate::Variation; |
8 | |
9 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3 |
10 | const WINDOWS_SYMBOL_ENCODING: u16 = 0; |
11 | const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1; |
12 | const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10; |
13 | |
14 | // https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0 |
15 | const UNICODE_1_0_ENCODING: u16 = 0; |
16 | const UNICODE_1_1_ENCODING: u16 = 1; |
17 | const UNICODE_ISO_ENCODING: u16 = 2; |
18 | const UNICODE_2_0_BMP_ENCODING: u16 = 3; |
19 | const UNICODE_2_0_FULL_ENCODING: u16 = 4; |
20 | //const UNICODE_VARIATION_ENCODING: u16 = 5; |
21 | const UNICODE_FULL_ENCODING: u16 = 6; |
22 | |
23 | /// A font face handle. |
24 | #[derive(Clone)] |
25 | pub struct Face<'a> { |
26 | pub(crate) ttfp_face: ttf_parser::Face<'a>, |
27 | pub(crate) units_per_em: u16, |
28 | pixels_per_em: Option<(u16, u16)>, |
29 | pub(crate) points_per_em: Option<f32>, |
30 | prefered_cmap_encoding_subtable: Option<u16>, |
31 | pub(crate) gsub: Option<SubstitutionTable<'a>>, |
32 | pub(crate) gpos: Option<PositioningTable<'a>>, |
33 | } |
34 | |
35 | impl<'a> AsRef<ttf_parser::Face<'a>> for Face<'a> { |
36 | #[inline] |
37 | fn as_ref(&self) -> &ttf_parser::Face<'a> { |
38 | &self.ttfp_face |
39 | } |
40 | } |
41 | |
42 | impl<'a> AsMut<ttf_parser::Face<'a>> for Face<'a> { |
43 | #[inline] |
44 | fn as_mut(&mut self) -> &mut ttf_parser::Face<'a> { |
45 | &mut self.ttfp_face |
46 | } |
47 | } |
48 | |
49 | impl<'a> core::ops::Deref for Face<'a> { |
50 | type Target = ttf_parser::Face<'a>; |
51 | |
52 | #[inline] |
53 | fn deref(&self) -> &Self::Target { |
54 | &self.ttfp_face |
55 | } |
56 | } |
57 | |
58 | impl<'a> core::ops::DerefMut for Face<'a> { |
59 | #[inline] |
60 | fn deref_mut(&mut self) -> &mut Self::Target { |
61 | &mut self.ttfp_face |
62 | } |
63 | } |
64 | |
65 | impl<'a> Face<'a> { |
66 | /// Creates a new `Face` from data. |
67 | /// |
68 | /// Data will be referenced, not owned. |
69 | pub fn from_slice(data: &'a [u8], face_index: u32) -> Option<Self> { |
70 | let face = ttf_parser::Face::parse(data, face_index).ok()?; |
71 | Some(Self::from_face(face)) |
72 | } |
73 | |
74 | /// Creates a new [`Face`] from [`ttf_parser::Face`]. |
75 | /// |
76 | /// Data will be referenced, not owned. |
77 | pub fn from_face(face: ttf_parser::Face<'a>) -> Self { |
78 | Face { |
79 | units_per_em: face.units_per_em(), |
80 | pixels_per_em: None, |
81 | points_per_em: None, |
82 | prefered_cmap_encoding_subtable: find_best_cmap_subtable(&face), |
83 | gsub: face.tables().gsub.map(SubstitutionTable::new), |
84 | gpos: face.tables().gpos.map(PositioningTable::new), |
85 | ttfp_face: face, |
86 | } |
87 | } |
88 | |
89 | // TODO: remove |
90 | /// Returns face’s units per EM. |
91 | #[inline] |
92 | pub fn units_per_em(&self) -> i32 { |
93 | self.units_per_em as i32 |
94 | } |
95 | |
96 | #[inline] |
97 | pub(crate) fn pixels_per_em(&self) -> Option<(u16, u16)> { |
98 | self.pixels_per_em |
99 | } |
100 | |
101 | /// Sets pixels per EM. |
102 | /// |
103 | /// Used during raster glyphs processing and hinting. |
104 | /// |
105 | /// `None` by default. |
106 | #[inline] |
107 | pub fn set_pixels_per_em(&mut self, ppem: Option<(u16, u16)>) { |
108 | self.pixels_per_em = ppem; |
109 | } |
110 | |
111 | /// Sets point size per EM. |
112 | /// |
113 | /// Used for optical-sizing in Apple fonts. |
114 | /// |
115 | /// `None` by default. |
116 | #[inline] |
117 | pub fn set_points_per_em(&mut self, ptem: Option<f32>) { |
118 | self.points_per_em = ptem; |
119 | } |
120 | |
121 | /// Sets font variations. |
122 | pub fn set_variations(&mut self, variations: &[Variation]) { |
123 | for variation in variations { |
124 | self.set_variation(variation.tag, variation.value); |
125 | } |
126 | } |
127 | |
128 | pub(crate) fn has_glyph(&self, c: u32) -> bool { |
129 | self.glyph_index(c).is_some() |
130 | } |
131 | |
132 | pub(crate) fn glyph_index(&self, c: u32) -> Option<GlyphId> { |
133 | let subtable_idx = self.prefered_cmap_encoding_subtable?; |
134 | let subtable = self.tables().cmap?.subtables.get(subtable_idx)?; |
135 | match subtable.glyph_index(c) { |
136 | Some(gid) => Some(gid), |
137 | None => { |
138 | // Special case for Windows Symbol fonts. |
139 | // TODO: add tests |
140 | if subtable.platform_id == ttf_parser::PlatformId::Windows |
141 | && subtable.encoding_id == WINDOWS_SYMBOL_ENCODING |
142 | { |
143 | if c <= 0x00FF { |
144 | // For symbol-encoded OpenType fonts, we duplicate the |
145 | // U+F000..F0FF range at U+0000..U+00FF. That's what |
146 | // Windows seems to do, and that's hinted about at: |
147 | // https://docs.microsoft.com/en-us/typography/opentype/spec/recom |
148 | // under "Non-Standard (Symbol) Fonts". |
149 | return self.glyph_index(0xF000 + c); |
150 | } |
151 | } |
152 | |
153 | None |
154 | } |
155 | } |
156 | } |
157 | |
158 | pub(crate) fn glyph_h_advance(&self, glyph: GlyphId) -> i32 { |
159 | self.glyph_advance(glyph, false) as i32 |
160 | } |
161 | |
162 | pub(crate) fn glyph_v_advance(&self, glyph: GlyphId) -> i32 { |
163 | -(self.glyph_advance(glyph, true) as i32) |
164 | } |
165 | |
166 | fn glyph_advance(&self, glyph: GlyphId, is_vertical: bool) -> u32 { |
167 | let face = &self.ttfp_face; |
168 | if face.is_variable() |
169 | && face.has_non_default_variation_coordinates() |
170 | && face.tables().hvar.is_none() |
171 | && face.tables().vvar.is_none() |
172 | { |
173 | return match face.glyph_bounding_box(glyph) { |
174 | Some(bbox) => { |
175 | (if is_vertical { |
176 | bbox.y_max + bbox.y_min |
177 | } else { |
178 | bbox.x_max + bbox.x_min |
179 | }) as u32 |
180 | } |
181 | None => 0, |
182 | }; |
183 | } |
184 | |
185 | if is_vertical && face.tables().vmtx.is_some() { |
186 | face.glyph_ver_advance(glyph).unwrap_or(0) as u32 |
187 | } else if !is_vertical && face.tables().hmtx.is_some() { |
188 | face.glyph_hor_advance(glyph).unwrap_or(0) as u32 |
189 | } else { |
190 | face.units_per_em() as u32 |
191 | } |
192 | } |
193 | |
194 | pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 { |
195 | self.glyph_h_advance(glyph) / 2 |
196 | } |
197 | |
198 | pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 { |
199 | match self.ttfp_face.glyph_y_origin(glyph) { |
200 | Some(y) => i32::from(y), |
201 | None => { |
202 | self.glyph_extents(glyph).map_or(0, |ext| ext.y_bearing) |
203 | + self.glyph_side_bearing(glyph, true) |
204 | } |
205 | } |
206 | } |
207 | |
208 | pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 { |
209 | let face = &self.ttfp_face; |
210 | if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() { |
211 | return match face.glyph_bounding_box(glyph) { |
212 | Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32, |
213 | None => 0, |
214 | }; |
215 | } |
216 | |
217 | if is_vertical { |
218 | face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32 |
219 | } else { |
220 | face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32 |
221 | } |
222 | } |
223 | |
224 | pub(crate) fn glyph_extents(&self, glyph: GlyphId) -> Option<GlyphExtents> { |
225 | let pixels_per_em = match self.pixels_per_em { |
226 | Some(ppem) => ppem.0, |
227 | None => core::u16::MAX, |
228 | }; |
229 | |
230 | if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) { |
231 | // HarfBuzz also supports only PNG. |
232 | if img.format == ttf_parser::RasterImageFormat::PNG { |
233 | let scale = self.units_per_em as f32 / img.pixels_per_em as f32; |
234 | return Some(GlyphExtents { |
235 | x_bearing: crate::round(f32::from(img.x) * scale) as i32, |
236 | y_bearing: crate::round((f32::from(img.y) + f32::from(img.height)) * scale) |
237 | as i32, |
238 | width: crate::round(f32::from(img.width) * scale) as i32, |
239 | height: crate::round(-f32::from(img.height) * scale) as i32, |
240 | }); |
241 | } |
242 | } |
243 | |
244 | let bbox = self.ttfp_face.glyph_bounding_box(glyph)?; |
245 | Some(GlyphExtents { |
246 | x_bearing: i32::from(bbox.x_min), |
247 | y_bearing: i32::from(bbox.y_max), |
248 | width: i32::from(bbox.width()), |
249 | height: i32::from(bbox.y_min - bbox.y_max), |
250 | }) |
251 | } |
252 | |
253 | pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> { |
254 | self.ttfp_face.glyph_name(glyph) |
255 | } |
256 | |
257 | pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 { |
258 | let table = match self.tables().gdef { |
259 | Some(v) => v, |
260 | None => return 0, |
261 | }; |
262 | |
263 | match table.glyph_class(glyph) { |
264 | Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(), |
265 | Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(), |
266 | Some(GlyphClass::Mark) => { |
267 | let class = table.glyph_mark_attachment_class(glyph); |
268 | (class << 8) | GlyphPropsFlags::MARK.bits() |
269 | } |
270 | _ => 0, |
271 | } |
272 | } |
273 | |
274 | pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> { |
275 | match table_index { |
276 | TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner), |
277 | TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner), |
278 | } |
279 | } |
280 | |
281 | pub(crate) fn layout_tables( |
282 | &self, |
283 | ) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ { |
284 | TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table))) |
285 | } |
286 | } |
287 | |
288 | #[derive(Clone, Copy, Default)] |
289 | pub struct GlyphExtents { |
290 | pub x_bearing: i32, |
291 | pub y_bearing: i32, |
292 | pub width: i32, |
293 | pub height: i32, |
294 | } |
295 | |
296 | fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> { |
297 | use ttf_parser::PlatformId; |
298 | |
299 | // Symbol subtable. |
300 | // Prefer symbol if available. |
301 | // https://github.com/harfbuzz/harfbuzz/issues/1918 |
302 | find_cmap_subtableOption |
303 | // 32-bit subtables: |
304 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING)) |
305 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING)) |
306 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING)) |
307 | // 16-bit subtables: |
308 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING)) |
309 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING)) |
310 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING)) |
311 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING)) |
312 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING)) |
313 | } |
314 | |
315 | fn find_cmap_subtable( |
316 | face: &ttf_parser::Face, |
317 | platform_id: ttf_parser::PlatformId, |
318 | encoding_id: u16, |
319 | ) -> Option<u16> { |
320 | for (i: usize, subtable: Subtable<'_>) in face.tables().cmap?.subtables.into_iter().enumerate() { |
321 | if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id { |
322 | return Some(i as u16); |
323 | } |
324 | } |
325 | |
326 | None |
327 | } |
328 |
Definitions
- Face
- ttfp_face
- units_per_em
- pixels_per_em
- points_per_em
- prefered_cmap_encoding_subtable
- gsub
- gpos
- as_ref
- as_mut
- Target
- deref
- deref_mut
- from_slice
- from_face
- units_per_em
- pixels_per_em
- set_pixels_per_em
- set_points_per_em
- set_variations
- has_glyph
- glyph_index
- glyph_h_advance
- glyph_v_advance
- glyph_advance
- glyph_h_origin
- glyph_v_origin
- glyph_side_bearing
- glyph_extents
- glyph_name
- glyph_props
- layout_table
- layout_tables
- GlyphExtents
- x_bearing
- y_bearing
- width
- height
- find_best_cmap_subtable
Learn Rust with the experts
Find out more