1use std::sync::Arc;
32
33use ab_glyph::{CodepointIdIter, Font, FontArc, GlyphId, GlyphSvg, Outline, v2};
34use ttf_parser::{Face, NormalizedCoordinate, Tag};
35
36use crate::{
37 ascii_glyphs::AsciiGlyphs,
38 font_features::{FeatureVariants, GlyphSubstitutions},
39};
40
41const VALUE_FIELDS: [u16; 8] = [
42 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
43];
44const X_ADVANCE: u16 = 0x0004;
45const X_ADVANCE_DEVICE: u16 = 0x0040;
46
47const VARIATION_INDEX: u16 = 0x8000;
48
49#[derive(Debug, Clone)]
50struct PairLookup {
51 subtables: Vec<PairSubtable>,
52}
53
54#[derive(Debug, Clone)]
55enum PairSubtable {
56 Specific {
57 pairs: Vec<(u32, f32)>,
58 },
59 Class {
60 coverage: Vec<u16>,
61 first: ClassDef,
62 second: ClassDef,
63 second_count: u16,
64 matrix: Vec<f32>,
65 },
66}
67
68#[derive(Debug, Clone, Default)]
69struct ClassDef {
70 ranges: Vec<(u16, u16, u16)>,
71}
72
73impl ClassDef {
74 fn class_of(&self, glyph: u16) -> u16 {
75 match self.ranges.binary_search_by(|(start, end, _)| {
76 if glyph < *start {
77 std::cmp::Ordering::Greater
78 } else if glyph > *end {
79 std::cmp::Ordering::Less
80 } else {
81 std::cmp::Ordering::Equal
82 }
83 }) {
84 Ok(index) => self.ranges[index].2,
85 Err(_) => 0,
86 }
87 }
88
89 fn from_pairs(mut pairs: Vec<(u16, u16)>) -> Self {
90 pairs.sort_unstable();
91 let mut ranges: Vec<(u16, u16, u16)> = Vec::new();
92 for (glyph, class) in pairs {
93 if class == 0 {
94 continue;
95 }
96 match ranges.last_mut() {
97 Some((_, end, last_class))
98 if *last_class == class && glyph == end.saturating_add(1) =>
99 {
100 *end = glyph;
101 }
102 _ => ranges.push((glyph, glyph, class)),
103 }
104 }
105 Self { ranges }
106 }
107}
108
109#[derive(Debug, Clone, Default)]
118pub struct GposKerning {
119 lookups: Vec<PairLookup>,
120}
121
122impl GposKerning {
123 pub fn parse(face: &Face<'_>) -> Option<Self> {
143 let gpos = face.raw_face().table(Tag::from_bytes(b"GPOS"))?;
144 let coordinates = face.variation_coordinates();
145 let gdef = face.tables().gdef;
146
147 let script_list = u16_at(gpos, 4)? as usize;
148 let feature_list = u16_at(gpos, 6)? as usize;
149 let lookup_list = u16_at(gpos, 8)? as usize;
150
151 let feature_indices = kern_feature_indices(gpos, script_list, feature_list)?;
152 let mut lookup_indices = Vec::new();
153 for index in feature_indices {
154 for lookup in feature_lookups(gpos, feature_list, index)? {
155 if !lookup_indices.contains(&lookup) {
156 lookup_indices.push(lookup);
157 }
158 }
159 }
160 lookup_indices.sort_unstable();
161
162 let mut lookups = Vec::new();
163 for index in lookup_indices {
164 let Some(subtables) = pair_lookup(gpos, lookup_list, index, gdef, coordinates) else {
165 continue;
166 };
167 if !subtables.is_empty() {
168 lookups.push(PairLookup { subtables });
169 }
170 }
171
172 (!lookups.is_empty()).then_some(Self { lookups })
173 }
174
175 pub fn kern_unscaled(&self, first: u16, second: u16) -> f32 {
177 let mut total = 0.0;
178 for lookup in &self.lookups {
179 for subtable in &lookup.subtables {
180 if let Some(value) = subtable.lookup(first, second) {
181 total += value;
182 break;
183 }
184 }
185 }
186 total
187 }
188
189 pub fn is_empty(&self) -> bool {
190 self.lookups.is_empty()
191 }
192}
193
194impl PairSubtable {
195 fn lookup(&self, first: u16, second: u16) -> Option<f32> {
196 match self {
197 Self::Specific { pairs } => {
198 let key = (u32::from(first) << 16) | u32::from(second);
199 pairs
200 .binary_search_by_key(&key, |(pair, _)| *pair)
201 .ok()
202 .map(|index| pairs[index].1)
203 }
204 Self::Class {
205 coverage,
206 first: first_classes,
207 second: second_classes,
208 second_count,
209 matrix,
210 } => {
211 coverage.binary_search(&first).ok()?;
212 let row = first_classes.class_of(first);
213 let column = second_classes.class_of(second);
214 if column >= *second_count {
215 return None;
216 }
217 let index = usize::from(row) * usize::from(*second_count) + usize::from(column);
218 matrix.get(index).copied()
219 }
220 }
221 }
222}
223
224fn u16_at(data: &[u8], offset: usize) -> Option<u16> {
225 let bytes = data.get(offset..offset + 2)?;
226 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
227}
228
229fn i16_at(data: &[u8], offset: usize) -> Option<i16> {
230 u16_at(data, offset).map(|value| value as i16)
231}
232
233fn u32_at(data: &[u8], offset: usize) -> Option<u32> {
234 let bytes = data.get(offset..offset + 4)?;
235 Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
236}
237
238fn kern_feature_indices(gpos: &[u8], script_list: usize, feature_list: usize) -> Option<Vec<u16>> {
239 let script_count = u16_at(gpos, script_list)?;
240 let mut reachable = Vec::new();
241 for index in 0..script_count {
242 let record = script_list + 2 + usize::from(index) * 6;
243 let script = script_list + usize::from(u16_at(gpos, record + 4)?);
244 let default_lang_sys = u16_at(gpos, script)?;
245 if default_lang_sys == 0 {
246 continue;
247 }
248 let lang_sys = script + usize::from(default_lang_sys);
249 let count = u16_at(gpos, lang_sys + 4)?;
250 for slot in 0..count {
251 let feature = u16_at(gpos, lang_sys + 6 + usize::from(slot) * 2)?;
252 if !reachable.contains(&feature) {
253 reachable.push(feature);
254 }
255 }
256 }
257
258 let feature_count = u16_at(gpos, feature_list)?;
259 let mut kern = Vec::new();
260 for index in reachable {
261 if index >= feature_count {
262 continue;
263 }
264 let record = feature_list + 2 + usize::from(index) * 6;
265 let tag = gpos.get(record..record + 4)?;
266 if tag == b"kern" {
267 kern.push(index);
268 }
269 }
270 Some(kern)
271}
272
273fn feature_lookups(gpos: &[u8], feature_list: usize, index: u16) -> Option<Vec<u16>> {
274 let record = feature_list + 2 + usize::from(index) * 6;
275 let feature = feature_list + usize::from(u16_at(gpos, record + 4)?);
276 let count = u16_at(gpos, feature + 2)?;
277 let mut lookups = Vec::with_capacity(usize::from(count));
278 for slot in 0..count {
279 lookups.push(u16_at(gpos, feature + 4 + usize::from(slot) * 2)?);
280 }
281 Some(lookups)
282}
283
284fn pair_lookup(
285 gpos: &[u8],
286 lookup_list: usize,
287 index: u16,
288 gdef: Option<ttf_parser::gdef::Table<'_>>,
289 coordinates: &[NormalizedCoordinate],
290) -> Option<Vec<PairSubtable>> {
291 let count = u16_at(gpos, lookup_list)?;
292 if index >= count {
293 return None;
294 }
295 let lookup = lookup_list + usize::from(u16_at(gpos, lookup_list + 2 + usize::from(index) * 2)?);
296 let kind = u16_at(gpos, lookup)?;
297 let subtable_count = u16_at(gpos, lookup + 4)?;
298
299 let mut subtables = Vec::new();
300 for slot in 0..subtable_count {
301 let mut offset = lookup + usize::from(u16_at(gpos, lookup + 6 + usize::from(slot) * 2)?);
302 let mut resolved = kind;
303 if resolved == 9 {
304 resolved = u16_at(gpos, offset + 2)?;
305 offset += u32_at(gpos, offset + 4)? as usize;
306 }
307 if resolved != 2 {
308 continue;
309 }
310 if let Some(subtable) = pair_subtable(gpos, offset, gdef, coordinates) {
311 subtables.push(subtable);
312 }
313 }
314 Some(subtables)
315}
316
317fn pair_subtable(
318 gpos: &[u8],
319 offset: usize,
320 gdef: Option<ttf_parser::gdef::Table<'_>>,
321 coordinates: &[NormalizedCoordinate],
322) -> Option<PairSubtable> {
323 let format = u16_at(gpos, offset)?;
324 let coverage_offset = offset + usize::from(u16_at(gpos, offset + 2)?);
325 let value_format_1 = u16_at(gpos, offset + 4)?;
326 let value_format_2 = u16_at(gpos, offset + 6)?;
327 let coverage = coverage_glyphs(gpos, coverage_offset)?;
328
329 match format {
330 1 => {
331 let set_count = u16_at(gpos, offset + 8)?;
332 let record_len = 2 + value_size(value_format_1) + value_size(value_format_2);
333 let mut pairs = Vec::new();
334 for slot in 0..set_count {
335 let Some(&first) = coverage.get(usize::from(slot)) else {
336 continue;
337 };
338 let set = offset + usize::from(u16_at(gpos, offset + 10 + usize::from(slot) * 2)?);
339 let count = u16_at(gpos, set)?;
340 for record in 0..count {
341 let at = set + 2 + usize::from(record) * record_len;
342 let Some(second) = u16_at(gpos, at) else {
343 continue;
344 };
345 let value = x_advance(gpos, at + 2, value_format_1, set, gdef, coordinates);
346 pairs.push(((u32::from(first) << 16) | u32::from(second), value));
347 }
348 }
349 pairs.sort_by_key(|(key, _)| *key);
350 pairs.dedup_by_key(|(key, _)| *key);
351 (!pairs.is_empty()).then_some(PairSubtable::Specific { pairs })
352 }
353 2 => {
354 let first = class_def(gpos, offset + usize::from(u16_at(gpos, offset + 8)?))?;
355 let second = class_def(gpos, offset + usize::from(u16_at(gpos, offset + 10)?))?;
356 let first_count = u16_at(gpos, offset + 12)?;
357 let second_count = u16_at(gpos, offset + 14)?;
358 let record_len = value_size(value_format_1) + value_size(value_format_2);
359 let cells = usize::from(first_count) * usize::from(second_count);
360 let mut matrix = Vec::with_capacity(cells);
361 for cell in 0..cells {
362 let at = offset + 16 + cell * record_len;
363 matrix.push(x_advance(
364 gpos,
365 at,
366 value_format_1,
367 offset,
368 gdef,
369 coordinates,
370 ));
371 }
372 Some(PairSubtable::Class {
373 coverage,
374 first,
375 second,
376 second_count,
377 matrix,
378 })
379 }
380 _ => None,
381 }
382}
383
384fn value_size(format: u16) -> usize {
385 usize::try_from((format & 0xFF).count_ones()).unwrap_or(0) * 2
386}
387
388fn x_advance(
389 gpos: &[u8],
390 at: usize,
391 format: u16,
392 device_base: usize,
393 gdef: Option<ttf_parser::gdef::Table<'_>>,
394 coordinates: &[NormalizedCoordinate],
395) -> f32 {
396 let mut cursor = at;
397 let mut advance = 0.0f32;
398 for field in VALUE_FIELDS {
399 if format & field == 0 {
400 continue;
401 }
402 if field == X_ADVANCE {
403 advance += f32::from(i16_at(gpos, cursor).unwrap_or(0));
404 }
405 if field == X_ADVANCE_DEVICE {
406 let device = u16_at(gpos, cursor).unwrap_or(0);
407 if device != 0 && !coordinates.is_empty() {
408 let table = device_base + usize::from(device);
409 if u16_at(gpos, table + 4) == Some(VARIATION_INDEX) {
410 let outer = u16_at(gpos, table).unwrap_or(0);
411 let inner = u16_at(gpos, table + 2).unwrap_or(0);
412 advance += gdef
413 .and_then(|gdef| gdef.glyph_variation_delta(outer, inner, coordinates))
414 .unwrap_or(0.0);
415 }
416 }
417 }
418 cursor += 2;
419 }
420 advance
421}
422
423fn coverage_glyphs(gpos: &[u8], offset: usize) -> Option<Vec<u16>> {
424 match u16_at(gpos, offset)? {
425 1 => {
426 let count = u16_at(gpos, offset + 2)?;
427 let mut glyphs = Vec::with_capacity(usize::from(count));
428 for index in 0..count {
429 glyphs.push(u16_at(gpos, offset + 4 + usize::from(index) * 2)?);
430 }
431 Some(glyphs)
432 }
433 2 => {
434 let count = u16_at(gpos, offset + 2)?;
435 let mut glyphs = Vec::new();
436 for index in 0..count {
437 let record = offset + 4 + usize::from(index) * 6;
438 let start = u16_at(gpos, record)?;
439 let end = u16_at(gpos, record + 2)?;
440 for glyph in start..=end {
441 glyphs.push(glyph);
442 }
443 }
444 Some(glyphs)
445 }
446 _ => None,
447 }
448}
449
450fn class_def(gpos: &[u8], offset: usize) -> Option<ClassDef> {
451 let mut pairs: Vec<(u16, u16)> = Vec::new();
452 match u16_at(gpos, offset)? {
453 1 => {
454 let start = u16_at(gpos, offset + 2)?;
455 let count = u16_at(gpos, offset + 4)?;
456 for index in 0..count {
457 let class = u16_at(gpos, offset + 6 + usize::from(index) * 2)?;
458 pairs.push((start.saturating_add(index), class));
459 }
460 }
461 2 => {
462 let count = u16_at(gpos, offset + 2)?;
463 for index in 0..count {
464 let record = offset + 4 + usize::from(index) * 6;
465 let start = u16_at(gpos, record)?;
466 let end = u16_at(gpos, record + 2)?;
467 let class = u16_at(gpos, record + 4)?;
468 for glyph in start..=end {
469 pairs.push((glyph, class));
470 }
471 }
472 }
473 _ => return Some(ClassDef::default()),
474 }
475 Some(ClassDef::from_pairs(pairs))
476}
477
478#[derive(Clone)]
498pub struct KernedFont {
499 font: FontArc,
500 kerning: Option<Arc<GposKerning>>,
501 ascii: Arc<AsciiGlyphs>,
502 substitutions: Option<Arc<GlyphSubstitutions>>,
503 variants: Arc<FeatureVariants>,
504}
505
506impl KernedFont {
507 pub fn new(font: FontArc, kerning: Option<Arc<GposKerning>>) -> Self {
508 let ascii = Arc::new(AsciiGlyphs::new());
509 Self {
510 font,
511 kerning,
512 variants: Arc::new(FeatureVariants::new(Arc::clone(&ascii))),
513 ascii,
514 substitutions: None,
515 }
516 }
517
518 pub(crate) fn with_feature_settings(&self, settings: Option<&str>) -> Option<Self> {
519 let wanted =
520 settings.and_then(|settings| self.variants.get(settings, self.font.font_data()));
521 let (substitutions, ascii) = match wanted {
522 Some(wanted)
523 if self
524 .substitutions
525 .as_ref()
526 .is_some_and(|current| current.tags() == wanted.substitutions.tags()) =>
527 {
528 return None;
529 }
530 Some(wanted) => (Some(wanted.substitutions), wanted.ascii),
531 None if self.substitutions.is_none() => return None,
532 None => (None, Arc::clone(self.variants.base_ascii())),
533 };
534 Some(Self {
535 font: self.font.clone(),
536 kerning: self.kerning.clone(),
537 ascii,
538 substitutions,
539 variants: Arc::clone(&self.variants),
540 })
541 }
542
543 pub(crate) fn feature_key(&self) -> u64 {
544 self.substitutions
545 .as_ref()
546 .map_or(0, |substitutions| substitutions.key())
547 }
548
549 pub(crate) fn glyph_advance(&self, ch: char) -> (GlyphId, f32) {
551 self.ascii_glyph(ch).unwrap_or_else(|| {
552 let id = self.glyph_id(ch);
553 (id, self.h_advance_unscaled(id))
554 })
555 }
556
557 pub(crate) fn ascii_glyph(&self, ch: char) -> Option<(GlyphId, f32)> {
560 self.ascii.glyph(self, ch)
561 }
562
563 pub(crate) fn ascii_kern(
567 &self,
568 previous: (char, GlyphId),
569 next: (char, GlyphId),
570 ) -> Option<f32> {
571 self.ascii.kern(self, previous, next)
572 }
573
574 pub fn read_kerning(bytes: &[u8], variations: &[([u8; 4], f32)]) -> Option<Arc<GposKerning>> {
581 let mut face = Face::parse(bytes, 0).ok()?;
582 for (tag, value) in variations {
583 face.set_variation(Tag::from_bytes(tag), *value);
584 }
585 GposKerning::parse(&face).map(Arc::new)
586 }
587}
588
589impl Font for KernedFont {
590 fn units_per_em(&self) -> Option<f32> {
591 self.font.units_per_em()
592 }
593
594 fn ascent_unscaled(&self) -> f32 {
595 self.font.ascent_unscaled()
596 }
597
598 fn descent_unscaled(&self) -> f32 {
599 self.font.descent_unscaled()
600 }
601
602 fn line_gap_unscaled(&self) -> f32 {
603 self.font.line_gap_unscaled()
604 }
605
606 fn italic_angle(&self) -> f32 {
607 self.font.italic_angle()
608 }
609
610 fn glyph_id(&self, c: char) -> GlyphId {
611 let id = self.font.glyph_id(c);
612 match &self.substitutions {
613 Some(substitutions) => substitutions.apply(id),
614 None => id,
615 }
616 }
617
618 fn h_advance_unscaled(&self, id: GlyphId) -> f32 {
619 self.font.h_advance_unscaled(id)
620 }
621
622 fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
623 self.font.h_side_bearing_unscaled(id)
624 }
625
626 fn v_advance_unscaled(&self, id: GlyphId) -> f32 {
627 self.font.v_advance_unscaled(id)
628 }
629
630 fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
631 self.font.v_side_bearing_unscaled(id)
632 }
633
634 fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32 {
635 match &self.kerning {
636 Some(kerning) => kerning.kern_unscaled(first.0, second.0),
637 None => self.font.kern_unscaled(first, second),
638 }
639 }
640
641 fn outline(&self, id: GlyphId) -> Option<Outline> {
642 self.font.outline(id)
643 }
644
645 fn glyph_count(&self) -> usize {
646 self.font.glyph_count()
647 }
648
649 fn codepoint_ids(&self) -> CodepointIdIter<'_> {
650 self.font.codepoint_ids()
651 }
652
653 fn glyph_raster_image2(&self, id: GlyphId, pixel_size: u16) -> Option<v2::GlyphImage<'_>> {
654 self.font.glyph_raster_image2(id, pixel_size)
655 }
656
657 fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg<'_>> {
658 self.font.glyph_svg_image(id)
659 }
660
661 fn font_data(&self) -> &[u8] {
662 self.font.font_data()
663 }
664}
665
666#[cfg(test)]
667#[path = "tests/gpos_kerning_tests.rs"]
668mod tests;