1 | #[cfg (not(feature = "std" ))] |
2 | use core_maths::CoreFloat; |
3 | |
4 | use crate::hb::paint_extents::hb_paint_extents_context_t; |
5 | use ttf_parser::gdef::GlyphClass; |
6 | use ttf_parser::opentype_layout::LayoutTable; |
7 | use ttf_parser::{GlyphId, RgbaColor}; |
8 | |
9 | use super::buffer::GlyphPropsFlags; |
10 | use super::ot_layout::TableIndex; |
11 | use super::ot_layout_common::{PositioningTable, SubstitutionTable}; |
12 | use crate::Variation; |
13 | |
14 | // https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3 |
15 | const WINDOWS_SYMBOL_ENCODING: u16 = 0; |
16 | const WINDOWS_UNICODE_BMP_ENCODING: u16 = 1; |
17 | const WINDOWS_UNICODE_FULL_ENCODING: u16 = 10; |
18 | |
19 | // https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-specific-encoding-and-language-ids-unicode-platform-platform-id--0 |
20 | const UNICODE_1_0_ENCODING: u16 = 0; |
21 | const UNICODE_1_1_ENCODING: u16 = 1; |
22 | const UNICODE_ISO_ENCODING: u16 = 2; |
23 | const UNICODE_2_0_BMP_ENCODING: u16 = 3; |
24 | const UNICODE_2_0_FULL_ENCODING: u16 = 4; |
25 | //const UNICODE_VARIATION_ENCODING: u16 = 5; |
26 | const UNICODE_FULL_ENCODING: u16 = 6; |
27 | |
28 | /// A font face handle. |
29 | #[derive (Clone)] |
30 | pub struct hb_font_t<'a> { |
31 | pub(crate) ttfp_face: ttf_parser::Face<'a>, |
32 | pub(crate) units_per_em: u16, |
33 | pixels_per_em: Option<(u16, u16)>, |
34 | pub(crate) points_per_em: Option<f32>, |
35 | prefered_cmap_encoding_subtable: Option<u16>, |
36 | pub(crate) gsub: Option<SubstitutionTable<'a>>, |
37 | pub(crate) gpos: Option<PositioningTable<'a>>, |
38 | } |
39 | |
40 | impl<'a> AsRef<ttf_parser::Face<'a>> for hb_font_t<'a> { |
41 | #[inline ] |
42 | fn as_ref(&self) -> &ttf_parser::Face<'a> { |
43 | &self.ttfp_face |
44 | } |
45 | } |
46 | |
47 | impl<'a> AsMut<ttf_parser::Face<'a>> for hb_font_t<'a> { |
48 | #[inline ] |
49 | fn as_mut(&mut self) -> &mut ttf_parser::Face<'a> { |
50 | &mut self.ttfp_face |
51 | } |
52 | } |
53 | |
54 | impl<'a> core::ops::Deref for hb_font_t<'a> { |
55 | type Target = ttf_parser::Face<'a>; |
56 | |
57 | #[inline ] |
58 | fn deref(&self) -> &Self::Target { |
59 | &self.ttfp_face |
60 | } |
61 | } |
62 | |
63 | impl<'a> core::ops::DerefMut for hb_font_t<'a> { |
64 | #[inline ] |
65 | fn deref_mut(&mut self) -> &mut Self::Target { |
66 | &mut self.ttfp_face |
67 | } |
68 | } |
69 | |
70 | impl<'a> hb_font_t<'a> { |
71 | /// Creates a new `Face` from data. |
72 | /// |
73 | /// Data will be referenced, not owned. |
74 | pub fn from_slice(data: &'a [u8], face_index: u32) -> Option<Self> { |
75 | let face = ttf_parser::Face::parse(data, face_index).ok()?; |
76 | Some(Self::from_face(face)) |
77 | } |
78 | |
79 | /// Creates a new [`Face`] from [`ttf_parser::Face`]. |
80 | /// |
81 | /// Data will be referenced, not owned. |
82 | pub fn from_face(face: ttf_parser::Face<'a>) -> Self { |
83 | hb_font_t { |
84 | units_per_em: face.units_per_em(), |
85 | pixels_per_em: None, |
86 | points_per_em: None, |
87 | prefered_cmap_encoding_subtable: find_best_cmap_subtable(&face), |
88 | gsub: face.tables().gsub.map(SubstitutionTable::new), |
89 | gpos: face.tables().gpos.map(PositioningTable::new), |
90 | ttfp_face: face, |
91 | } |
92 | } |
93 | |
94 | // TODO: remove |
95 | /// Returns face’s units per EM. |
96 | #[inline ] |
97 | pub fn units_per_em(&self) -> i32 { |
98 | self.units_per_em as i32 |
99 | } |
100 | |
101 | #[inline ] |
102 | pub(crate) fn pixels_per_em(&self) -> Option<(u16, u16)> { |
103 | self.pixels_per_em |
104 | } |
105 | |
106 | /// Sets pixels per EM. |
107 | /// |
108 | /// Used during raster glyphs processing and hinting. |
109 | /// |
110 | /// `None` by default. |
111 | #[inline ] |
112 | pub fn set_pixels_per_em(&mut self, ppem: Option<(u16, u16)>) { |
113 | self.pixels_per_em = ppem; |
114 | } |
115 | |
116 | /// Sets point size per EM. |
117 | /// |
118 | /// Used for optical-sizing in Apple fonts. |
119 | /// |
120 | /// `None` by default. |
121 | #[inline ] |
122 | pub fn set_points_per_em(&mut self, ptem: Option<f32>) { |
123 | self.points_per_em = ptem; |
124 | } |
125 | |
126 | /// Sets font variations. |
127 | pub fn set_variations(&mut self, variations: &[Variation]) { |
128 | for variation in variations { |
129 | self.set_variation(variation.tag, variation.value); |
130 | } |
131 | } |
132 | |
133 | pub(crate) fn has_glyph(&self, c: u32) -> bool { |
134 | self.get_nominal_glyph(c).is_some() |
135 | } |
136 | |
137 | pub(crate) fn get_nominal_glyph(&self, mut c: u32) -> Option<GlyphId> { |
138 | let subtable_idx = self.prefered_cmap_encoding_subtable?; |
139 | let subtable = self.tables().cmap?.subtables.get(subtable_idx)?; |
140 | |
141 | if subtable.platform_id == ttf_parser::PlatformId::Macintosh && c > 0x7F { |
142 | c = unicode_to_macroman(c); |
143 | } |
144 | |
145 | match subtable.glyph_index(c) { |
146 | Some(gid) => Some(gid), |
147 | None => { |
148 | // Special case for Windows Symbol fonts. |
149 | // TODO: add tests |
150 | if subtable.platform_id == ttf_parser::PlatformId::Windows |
151 | && subtable.encoding_id == WINDOWS_SYMBOL_ENCODING |
152 | { |
153 | if c <= 0x00FF { |
154 | // For symbol-encoded OpenType fonts, we duplicate the |
155 | // U+F000..F0FF range at U+0000..U+00FF. That's what |
156 | // Windows seems to do, and that's hinted about at: |
157 | // https://docs.microsoft.com/en-us/typography/opentype/spec/recom |
158 | // under "Non-Standard (Symbol) Fonts". |
159 | return self.get_nominal_glyph(0xF000 + c); |
160 | } |
161 | } |
162 | |
163 | None |
164 | } |
165 | } |
166 | } |
167 | |
168 | pub(crate) fn glyph_h_advance(&self, glyph: GlyphId) -> i32 { |
169 | self.glyph_advance(glyph, false) as i32 |
170 | } |
171 | |
172 | pub(crate) fn glyph_v_advance(&self, glyph: GlyphId) -> i32 { |
173 | -(self.glyph_advance(glyph, true) as i32) |
174 | } |
175 | |
176 | fn glyph_advance(&self, glyph: GlyphId, is_vertical: bool) -> u32 { |
177 | let face = &self.ttfp_face; |
178 | if face.is_variable() |
179 | && face.has_non_default_variation_coordinates() |
180 | && face.tables().hvar.is_none() |
181 | && face.tables().vvar.is_none() |
182 | && face.glyph_phantom_points(glyph).is_none() |
183 | { |
184 | return match face.glyph_bounding_box(glyph) { |
185 | Some(bbox) => { |
186 | (if is_vertical { |
187 | bbox.y_max + bbox.y_min |
188 | } else { |
189 | bbox.x_max + bbox.x_min |
190 | }) as u32 |
191 | } |
192 | None => 0, |
193 | }; |
194 | } |
195 | |
196 | if is_vertical { |
197 | if face.tables().vmtx.is_some() { |
198 | face.glyph_ver_advance(glyph).unwrap_or(0) as u32 |
199 | } else { |
200 | // TODO: Original code calls `h_extents_with_fallback` |
201 | (face.ascender() - face.descender()) as u32 |
202 | } |
203 | } else if !is_vertical && face.tables().hmtx.is_some() { |
204 | face.glyph_hor_advance(glyph).unwrap_or(0) as u32 |
205 | } else { |
206 | face.units_per_em() as u32 |
207 | } |
208 | } |
209 | |
210 | pub(crate) fn glyph_h_origin(&self, glyph: GlyphId) -> i32 { |
211 | self.glyph_h_advance(glyph) / 2 |
212 | } |
213 | |
214 | pub(crate) fn glyph_v_origin(&self, glyph: GlyphId) -> i32 { |
215 | match self.ttfp_face.glyph_y_origin(glyph) { |
216 | Some(y) => i32::from(y), |
217 | None => { |
218 | let mut extents = hb_glyph_extents_t::default(); |
219 | if self.glyph_extents(glyph, &mut extents) { |
220 | if self.ttfp_face.tables().vmtx.is_some() { |
221 | extents.y_bearing + self.glyph_side_bearing(glyph, true) |
222 | } else { |
223 | let advance = self.ttfp_face.ascender() - self.ttfp_face.descender(); |
224 | let diff = advance as i32 - -extents.height; |
225 | extents.y_bearing + (diff >> 1) |
226 | } |
227 | } else { |
228 | // TODO: Original code calls `h_extents_with_fallback` |
229 | self.ttfp_face.ascender() as i32 |
230 | } |
231 | } |
232 | } |
233 | } |
234 | |
235 | pub(crate) fn glyph_side_bearing(&self, glyph: GlyphId, is_vertical: bool) -> i32 { |
236 | let face = &self.ttfp_face; |
237 | if face.is_variable() && face.tables().hvar.is_none() && face.tables().vvar.is_none() { |
238 | return match face.glyph_bounding_box(glyph) { |
239 | Some(bbox) => (if is_vertical { bbox.x_min } else { bbox.y_min }) as i32, |
240 | None => 0, |
241 | }; |
242 | } |
243 | |
244 | if is_vertical { |
245 | face.glyph_ver_side_bearing(glyph).unwrap_or(0) as i32 |
246 | } else { |
247 | face.glyph_hor_side_bearing(glyph).unwrap_or(0) as i32 |
248 | } |
249 | } |
250 | |
251 | pub(crate) fn glyph_extents( |
252 | &self, |
253 | glyph: GlyphId, |
254 | glyph_extents: &mut hb_glyph_extents_t, |
255 | ) -> bool { |
256 | let pixels_per_em = self.pixels_per_em.map_or(u16::MAX, |ppem| ppem.0); |
257 | |
258 | if let Some(img) = self.ttfp_face.glyph_raster_image(glyph, pixels_per_em) { |
259 | // HarfBuzz also supports only PNG. |
260 | if img.format == ttf_parser::RasterImageFormat::PNG { |
261 | let scale = self.units_per_em as f32 / img.pixels_per_em as f32; |
262 | glyph_extents.x_bearing = (f32::from(img.x) * scale).round() as i32; |
263 | glyph_extents.y_bearing = |
264 | ((f32::from(img.y) + f32::from(img.height)) * scale).round() as i32; |
265 | glyph_extents.width = (f32::from(img.width) * scale).round() as i32; |
266 | glyph_extents.height = (-f32::from(img.height) * scale).round() as i32; |
267 | return true; |
268 | } |
269 | // TODO: Add tests for this. We should use all glyphs from |
270 | // https://github.com/googlefonts/color-fonts/blob/main/fonts/test_glyphs-glyf_colr_1_no_cliplist.ttf |
271 | // and test their output against harfbuzz. |
272 | } else if let Some(colr) = self.ttfp_face.tables().colr { |
273 | if colr.is_simple() { |
274 | return false; |
275 | } |
276 | |
277 | if let Some(clip_box) = colr.clip_box(glyph, self.variation_coordinates()) { |
278 | // Floor |
279 | glyph_extents.x_bearing = (clip_box.x_min).round() as i32; |
280 | glyph_extents.y_bearing = (clip_box.y_max).round() as i32; |
281 | glyph_extents.width = (clip_box.x_max - clip_box.x_min).round() as i32; |
282 | glyph_extents.height = (clip_box.y_min - clip_box.y_max).round() as i32; |
283 | return true; |
284 | } |
285 | |
286 | let mut extents_data = hb_paint_extents_context_t::new(&self.ttfp_face); |
287 | let ret = colr |
288 | .paint( |
289 | glyph, |
290 | 0, |
291 | &mut extents_data, |
292 | self.variation_coordinates(), |
293 | RgbaColor::new(0, 0, 0, 0), |
294 | ) |
295 | .is_some(); |
296 | |
297 | let e = extents_data.get_extents(); |
298 | if e.is_void() { |
299 | glyph_extents.x_bearing = 0; |
300 | glyph_extents.y_bearing = 0; |
301 | glyph_extents.width = 0; |
302 | glyph_extents.height = 0; |
303 | } else { |
304 | glyph_extents.x_bearing = e.x_min as i32; |
305 | glyph_extents.y_bearing = e.y_max as i32; |
306 | glyph_extents.width = (e.x_max - e.x_min) as i32; |
307 | glyph_extents.height = (e.y_min - e.y_max) as i32; |
308 | } |
309 | |
310 | return ret; |
311 | } |
312 | |
313 | let mut bbox = None; |
314 | |
315 | if let Some(glyf) = self.ttfp_face.tables().glyf { |
316 | bbox = glyf.bbox(glyph); |
317 | } |
318 | |
319 | // See https://github.com/harfbuzz/rustybuzz/pull/98#issuecomment-1948430785 |
320 | if self.ttfp_face.tables().glyf.is_some() && bbox.is_none() { |
321 | // Empty glyph; zero extents. |
322 | return true; |
323 | } |
324 | |
325 | let Some(bbox) = bbox else { |
326 | return false; |
327 | }; |
328 | |
329 | glyph_extents.x_bearing = i32::from(bbox.x_min); |
330 | glyph_extents.y_bearing = i32::from(bbox.y_max); |
331 | glyph_extents.width = i32::from(bbox.width()); |
332 | glyph_extents.height = i32::from(bbox.y_min - bbox.y_max); |
333 | |
334 | true |
335 | } |
336 | |
337 | pub(crate) fn glyph_name(&self, glyph: GlyphId) -> Option<&str> { |
338 | self.ttfp_face.glyph_name(glyph) |
339 | } |
340 | |
341 | pub(crate) fn glyph_props(&self, glyph: GlyphId) -> u16 { |
342 | let table = match self.tables().gdef { |
343 | Some(v) => v, |
344 | None => return 0, |
345 | }; |
346 | |
347 | match table.glyph_class(glyph) { |
348 | Some(GlyphClass::Base) => GlyphPropsFlags::BASE_GLYPH.bits(), |
349 | Some(GlyphClass::Ligature) => GlyphPropsFlags::LIGATURE.bits(), |
350 | Some(GlyphClass::Mark) => { |
351 | let class = table.glyph_mark_attachment_class(glyph); |
352 | (class << 8) | GlyphPropsFlags::MARK.bits() |
353 | } |
354 | _ => 0, |
355 | } |
356 | } |
357 | |
358 | pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> { |
359 | match table_index { |
360 | TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner), |
361 | TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner), |
362 | } |
363 | } |
364 | |
365 | pub(crate) fn layout_tables( |
366 | &self, |
367 | ) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ { |
368 | TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table))) |
369 | } |
370 | } |
371 | |
372 | #[derive (Clone, Copy, Default)] |
373 | #[repr (C)] |
374 | pub struct hb_glyph_extents_t { |
375 | pub x_bearing: i32, |
376 | pub y_bearing: i32, |
377 | pub width: i32, |
378 | pub height: i32, |
379 | } |
380 | |
381 | unsafe impl bytemuck::Zeroable for hb_glyph_extents_t {} |
382 | unsafe impl bytemuck::Pod for hb_glyph_extents_t {} |
383 | |
384 | fn find_best_cmap_subtable(face: &ttf_parser::Face) -> Option<u16> { |
385 | use ttf_parser::PlatformId; |
386 | |
387 | // Symbol subtable. |
388 | // Prefer symbol if available. |
389 | // https://github.com/harfbuzz/harfbuzz/issues/1918 |
390 | find_cmap_subtableOption(face, PlatformId::Windows, WINDOWS_SYMBOL_ENCODING) |
391 | // 32-bit subtables: |
392 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_FULL_ENCODING)) |
393 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_FULL_ENCODING)) |
394 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_FULL_ENCODING)) |
395 | // 16-bit subtables: |
396 | .or_else(|| find_cmap_subtable(face, PlatformId::Windows, WINDOWS_UNICODE_BMP_ENCODING)) |
397 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_2_0_BMP_ENCODING)) |
398 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_ISO_ENCODING)) |
399 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_1_ENCODING)) |
400 | .or_else(|| find_cmap_subtable(face, PlatformId::Unicode, UNICODE_1_0_ENCODING)) |
401 | // MacRoman subtable: |
402 | .or_else(|| find_cmap_subtable(face, PlatformId::Macintosh, encoding_id:0)) |
403 | } |
404 | |
405 | fn find_cmap_subtable( |
406 | face: &ttf_parser::Face, |
407 | platform_id: ttf_parser::PlatformId, |
408 | encoding_id: u16, |
409 | ) -> Option<u16> { |
410 | for (i: usize, subtable: Subtable<'_>) in face.tables().cmap?.subtables.into_iter().enumerate() { |
411 | if subtable.platform_id == platform_id && subtable.encoding_id == encoding_id { |
412 | return Some(i as u16); |
413 | } |
414 | } |
415 | |
416 | None |
417 | } |
418 | |
419 | #[rustfmt::skip] |
420 | static UNICODE_TO_MACROMAN: &[u16] = &[ |
421 | 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, |
422 | 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, |
423 | 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, |
424 | 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, |
425 | 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, |
426 | 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, |
427 | 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, |
428 | 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, |
429 | 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, |
430 | 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, |
431 | 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, |
432 | 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, |
433 | 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, |
434 | 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, |
435 | 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, |
436 | 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, |
437 | ]; |
438 | |
439 | fn unicode_to_macroman(c: u32) -> u32 { |
440 | let u: u16 = c as u16; |
441 | let Some(index: usize) = UNICODE_TO_MACROMAN.iter().position(|m: &u16| *m == u) else { |
442 | return 0; |
443 | }; |
444 | (0x80 + index) as u32 |
445 | } |
446 | |