| 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 | |