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 { |
186 | if face.tables().vmtx.is_some() { |
187 | return face.glyph_ver_advance(glyph).unwrap_or(0) as u32; |
188 | } else { |
189 | // TODO: Original code calls `h_extents_with_fallback` |
190 | return (face.ascender() - face.descender()) as u32; |
191 | } |
192 | } else if !is_vertical && face.tables().hmtx.is_some() { |
193 | face.glyph_hor_advance(glyph).unwrap_or(0) as u32 |
194 | } else { |
195 | face.units_per_em() as u32 |
196 | } |
197 | } |
198 | |
199 | pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 { |
200 | self.glyph_h_advance(glyph) / 2 |
201 | } |
202 | |
203 | pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 { |
204 | match self.ttfp_face.glyph_y_origin(glyph) { |
205 | Some(y) => i32::from(y), |
206 | None => { |
207 | let mut extents = GlyphExtents::default(); |
208 | if self.glyph_extents(glyph, &mut extents) { |
209 | if self.ttfp_face.tables().vmtx.is_some() { |
210 | extents.y_bearing + self.glyph_side_bearing(glyph, true) |
211 | } else { |
212 | let advance = self.ttfp_face.ascender() - self.ttfp_face.descender(); |
213 | let diff = advance as i32 - -extents.height; |
214 | return extents.y_bearing + (diff >> 1); |
215 | } |
216 | } else { |
217 | // TODO: Original code calls `h_extents_with_fallback` |
218 | self.ttfp_face.ascender() as i32 |
219 | } |
220 | } |
221 | } |
222 | } |
223 | |
224 | pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 { |
225 | let face = &self.ttfp_face; |
226 | if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() { |
227 | return match face.glyph_bounding_box(glyph) { |
228 | Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32, |
229 | None => 0, |
230 | }; |
231 | } |
232 | |
233 | if is_vertical { |
234 | face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32 |
235 | } else { |
236 | face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32 |
237 | } |
238 | } |
239 | |
240 | pub(crate) fn glyph_extents(&self, glyph: GlyphId, glyph_extents: &mut GlyphExtents) -> bool { |
241 | let pixels_per_em = match self.pixels_per_em { |
242 | Some(ppem) => ppem.0, |
243 | None => core::u16::MAX, |
244 | }; |
245 | |
246 | if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) { |
247 | // HarfBuzz also supports only PNG. |
248 | if img.format == ttf_parser::RasterImageFormat::PNG { |
249 | let scale = self.units_per_em as f32 / img.pixels_per_em as f32; |
250 | glyph_extents.x_bearing = crate::round(f32::from(img.x) * scale) as i32; |
251 | glyph_extents.y_bearing = |
252 | crate::round((f32::from(img.y) + f32::from(img.height)) * scale) as i32; |
253 | glyph_extents.width = crate::round(f32::from(img.width) * scale) as i32; |
254 | glyph_extents.height = crate::round(-f32::from(img.height) * scale) as i32; |
255 | return true; |
256 | } |
257 | } |
258 | |
259 | let bbox = self.ttfp_face.glyph_bounding_box(glyph); |
260 | |
261 | // See https://github.com/RazrFalcon/rustybuzz/pull/98#issuecomment-1948430785 |
262 | if self.ttfp_face.tables().glyf.is_some() && bbox.is_none() { |
263 | // Empty glyph; zero extents. |
264 | return true; |
265 | } |
266 | |
267 | let Some(bbox) = bbox else { |
268 | return false; |
269 | }; |
270 | |
271 | glyph_extents.x_bearing = i32::from(bbox.x_min); |
272 | glyph_extents.y_bearing = i32::from(bbox.y_max); |
273 | glyph_extents.width = i32::from(bbox.width()); |
274 | glyph_extents.height = i32::from(bbox.y_min - bbox.y_max); |
275 | |
276 | return true; |
277 | } |
278 | |
279 | pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> { |
280 | self.ttfp_face.glyph_name(glyph) |
281 | } |
282 | |
283 | pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 { |
284 | let table = match self.tables().gdef { |
285 | Some(v) => v, |
286 | None => return 0, |
287 | }; |
288 | |
289 | match table.glyph_class(glyph) { |
290 | Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(), |
291 | Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(), |
292 | Some(GlyphClass::Mark) => { |
293 | let class = table.glyph_mark_attachment_class(glyph); |
294 | (class << 8) | GlyphPropsFlags::MARK.bits() |
295 | } |
296 | _ => 0, |
297 | } |
298 | } |
299 | |
300 | pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> { |
301 | match table_index { |
302 | TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner), |
303 | TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner), |
304 | } |
305 | } |
306 | |
307 | pub(crate) fn layout_tables( |
308 | &self, |
309 | ) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ { |
310 | TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table))) |
311 | } |
312 | } |
313 | |
314 | #[derive (Clone, Copy, Default)] |
315 | pub struct GlyphExtents { |
316 | pub x_bearing: i32, |
317 | pub y_bearing: i32, |
318 | pub width: i32, |
319 | pub height: i32, |
320 | } |
321 | |
322 | fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> { |
323 | use ttf_parser::PlatformId; |
324 | |
325 | // Symbol subtable. |
326 | // Prefer symbol if available. |
327 | // https://github.com/harfbuzz/harfbuzz/issues/1918 |
328 | find_cmap_subtableOption(face, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING) |
329 | // 32-bit subtables: |
330 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING)) |
331 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING)) |
332 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING)) |
333 | // 16-bit subtables: |
334 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING)) |
335 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING)) |
336 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING)) |
337 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING)) |
338 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING)) |
339 | } |
340 | |
341 | fn find_cmap_subtable( |
342 | face: &ttf_parser::Face, |
343 | platform_id: ttf_parser::PlatformId, |
344 | encoding_id: u16, |
345 | ) -> Option<u16> { |
346 | for (i: usize, subtable: Subtable<'_>) in face.tables().cmap?.subtables.into_iter().enumerate() { |
347 | if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id { |
348 | return Some(i as u16); |
349 | } |
350 | } |
351 | |
352 | None |
353 | } |
354 | |