1#![forbid(unsafe_code)]
25
26#[cfg(feature = "bundled-faces")]
27pub mod bundled;
28pub mod cff;
29mod gvar;
30pub mod outline;
31pub mod shaping;
32mod subset;
33pub use subset::{EmbeddingFormat, Subset, SubsetError, SubsetErrorKind};
34
35#[must_use]
39pub fn variable_triangle_fixture() -> Vec<u8> {
40 gvar::variable_triangle_fixture()
41}
42
43const MAX_LAYOUT_GLYPHS: usize = 65_536;
50
51const MAX_COVERAGE_GLYPHS: usize = MAX_LAYOUT_GLYPHS;
53pub const MISSING_GLYPH_REMAP: u16 = u16::MAX;
58
59const MAX_VARIATION_AXES: usize = 64;
63const MAX_NAMED_INSTANCES: usize = 256;
66const MAX_AVAR_MAPS: usize = 64;
68type AvarAxisMaps = Vec<Option<Vec<(f32, f32)>>>;
70
71#[derive(Debug, Clone)]
72struct Cmap4Segment {
73 start: u16,
74 end: u16,
75 id_delta: u16,
76 id_range_offset: u16,
77 id_range_offset_pos: usize,
78}
79
80#[derive(Debug, Clone)]
81struct Cmap4Cache {
82 segments: Vec<Cmap4Segment>,
83 sorted_by_end: bool,
84}
85
86#[derive(Debug, Clone)]
88pub struct Font {
89 data: Vec<u8>,
90 pub units_per_em: u16,
92 pub num_glyphs: u16,
94 pub ascent: i16,
96 pub descent: i16,
98 pub line_gap: i16,
100 num_h_metrics: u16,
101 hmtx_off: usize,
102 cmap_off: usize,
103 cmap_format: u16,
104 cmap4_cache: Option<Cmap4Cache>,
105 glyf: Option<(usize, usize)>,
108 loca_off: Option<usize>,
110 loca_long: bool,
112 kern0: Option<(usize, u16)>,
114 variation: Option<FontVariation>,
117 latin1_glyphs: [u16; 256],
118 latin1_advances_1000: [u32; 256],
119}
120
121#[derive(Debug, Clone, Copy, PartialEq)]
127pub struct VariationAxis {
128 pub tag: [u8; 4],
130 pub min: f32,
132 pub default: f32,
134 pub max: f32,
136 pub flags: u16,
138 pub name_id: u16,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq)]
144pub struct AxisBounds {
145 pub min: f32,
147 pub default: f32,
149 pub max: f32,
151}
152
153#[derive(Debug, Clone, PartialEq)]
155pub struct NamedInstance {
156 pub subfamily_name_id: u16,
158 pub flags: u16,
160 pub coordinates: Vec<f32>,
162 pub postscript_name_id: Option<u16>,
164}
165
166#[derive(Debug, Clone)]
167struct FontVariation {
168 axes: Vec<VariationAxis>,
169 instances: Vec<NamedInstance>,
170 avar: AvarAxisMaps,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
177pub enum FontError {
178 BadMagic,
180 MissingTable(&'static str),
182 Truncated,
184 NoUnicodeCmap,
186}
187
188impl core::fmt::Display for FontError {
189 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190 match self {
191 Self::BadMagic => write!(f, "not a TrueType/OpenType font"),
192 Self::MissingTable(t) => write!(f, "missing required font table: {t}"),
193 Self::Truncated => write!(f, "font data is truncated"),
194 Self::NoUnicodeCmap => write!(f, "no usable Unicode cmap (format 4/12)"),
195 }
196 }
197}
198
199impl std::error::Error for FontError {}
200
201pub(crate) fn be_u16(d: &[u8], o: usize) -> Option<u16> {
202 let bytes = d.get(o..o.checked_add(2)?)?;
203 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
204}
205pub(crate) fn be_i16(d: &[u8], o: usize) -> Option<i16> {
206 be_u16(d, o).map(|v| v as i16)
207}
208pub(crate) fn be_u32(d: &[u8], o: usize) -> Option<u32> {
209 let bytes = d.get(o..o.checked_add(4)?)?;
210 Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
211}
212
213fn be_i32(d: &[u8], o: usize) -> Option<i32> {
214 be_u32(d, o).map(|v| v as i32)
215}
216
217fn fixed_16_16(v: i32) -> f32 {
219 (f64::from(v) / 65536.0) as f32
220}
221
222fn f2dot14(v: i16) -> f32 {
224 f32::from(v) / 16384.0
225}
226
227pub(crate) fn off(base: usize, delta: usize) -> Option<usize> {
228 base.checked_add(delta)
229}
230
231pub(crate) fn off_mul(base: usize, index: usize, stride: usize) -> Option<usize> {
232 base.checked_add(index.checked_mul(stride)?)
233}
234
235fn be_u16_at(d: &[u8], base: usize, delta: usize) -> Option<u16> {
236 be_u16(d, off(base, delta)?)
237}
238
239fn be_u32_at(d: &[u8], base: usize, delta: usize) -> Option<u32> {
240 be_u32(d, off(base, delta)?)
241}
242
243fn bytes_at(d: &[u8], base: usize, len: usize) -> Option<&[u8]> {
244 d.get(base..off(base, len)?)
245}
246
247fn write_u16(d: &mut [u8], off: usize, v: u16) -> Option<()> {
249 let b = v.to_be_bytes();
250 let dst = d.get_mut(off..off.checked_add(2)?)?;
251 dst.copy_from_slice(&b);
252 Some(())
253}
254
255pub(crate) fn write_u32(d: &mut [u8], off: usize, v: u32) -> Option<()> {
257 let b = v.to_be_bytes();
258 let dst = d.get_mut(off..off.checked_add(4)?)?;
259 dst.copy_from_slice(&b);
260 Some(())
261}
262
263pub(crate) fn table_checksum(d: &[u8]) -> u32 {
264 let mut sum: u32 = 0;
265 let mut chunks16 = d.chunks_exact(16);
266 for c in &mut chunks16 {
267 let w0 = u32::from_be_bytes([c[0], c[1], c[2], c[3]]);
268 let w1 = u32::from_be_bytes([c[4], c[5], c[6], c[7]]);
269 let w2 = u32::from_be_bytes([c[8], c[9], c[10], c[11]]);
270 let w3 = u32::from_be_bytes([c[12], c[13], c[14], c[15]]);
271 sum = sum
272 .wrapping_add(w0)
273 .wrapping_add(w1)
274 .wrapping_add(w2)
275 .wrapping_add(w3);
276 }
277 let mut chunks4 = chunks16.remainder().chunks_exact(4);
278 for c in &mut chunks4 {
279 sum = sum.wrapping_add(u32::from_be_bytes([c[0], c[1], c[2], c[3]]));
280 }
281 let rem = chunks4.remainder();
282 if !rem.is_empty() {
283 let mut buf = [0u8; 4];
284 buf[..rem.len()].copy_from_slice(rem);
285 sum = sum.wrapping_add(u32::from_be_bytes(buf));
286 }
287 sum
288}
289
290fn find_table(d: &[u8], tag: &[u8; 4]) -> Option<usize> {
291 find_table_full(d, tag).map(|(off, _)| off)
292}
293
294pub(crate) fn find_table_full(d: &[u8], tag: &[u8; 4]) -> Option<(usize, usize)> {
296 let num_tables = be_u16(d, 4)? as usize;
297 for i in 0..num_tables {
298 let rec = off_mul(12, i, 16)?;
299 if bytes_at(d, rec, 4)? == tag {
300 return Some((
301 be_u32_at(d, rec, 8)? as usize,
302 be_u32_at(d, rec, 12)? as usize,
303 ));
304 }
305 }
306 None
307}
308
309fn find_kern0(d: &[u8]) -> Option<(usize, u16)> {
311 let (kern, kern_len) = find_table_full(d, b"kern")?;
312 let table_end = kern.checked_add(kern_len)?;
313 let version = be_u16(d, kern)?;
314 let n_tables = be_u16_at(d, kern, 2)? as usize;
315 if version != 0 {
316 return None;
317 }
318
319 let mut sub = off(kern, 4)?;
320 for _ in 0..n_tables {
321 if sub.checked_add(6)? > table_end {
322 return None;
323 }
324 let length = be_u16_at(d, sub, 2)? as usize;
325 let coverage = be_u16_at(d, sub, 4)?;
326 let format = coverage >> 8;
327 let horizontal = coverage & 0x0001 != 0;
328 let minimum = coverage & 0x0002 != 0;
329 let pairs = off(sub, 14)?;
330 if format == 0 && horizontal && !minimum && length >= 14 {
331 let sub_end = sub.checked_add(length)?;
332 if sub_end > table_end {
333 return None;
334 }
335 let n_pairs = be_u16_at(d, sub, 6)?;
336 let bytes_needed = (n_pairs as usize).checked_mul(6)?;
337 if pairs.checked_add(bytes_needed)? <= sub_end {
338 return Some((pairs, n_pairs));
339 }
340 return None;
341 }
342 if length == 0 {
343 return None;
344 }
345 sub = sub.checked_add(length)?;
346 }
347 None
348}
349
350fn parse_fvar(d: &[u8], table_off: usize, table_len: usize) -> Option<FontVariation> {
354 let table_end = table_off.checked_add(table_len)?;
355 if table_off.checked_add(16)? > table_end {
356 return None;
357 }
358 let major = be_u16(d, table_off)?;
359 if major != 1 {
360 return None;
361 }
362 let axes_array_offset = be_u16_at(d, table_off, 4)? as usize;
363 let axis_count = be_u16_at(d, table_off, 8)? as usize;
364 let axis_size = be_u16_at(d, table_off, 10)? as usize;
365 let instance_count = be_u16_at(d, table_off, 12)? as usize;
366 let instance_size = be_u16_at(d, table_off, 14)? as usize;
367 if axis_size < 20 {
368 return None;
369 }
370 let n_axes = axis_count.min(MAX_VARIATION_AXES);
371 let axes_off = off(table_off, axes_array_offset)?;
372 let axes_bytes = n_axes.checked_mul(axis_size)?;
373 if axes_off.checked_add(axes_bytes)? > table_end {
374 return None;
375 }
376
377 let mut axes = Vec::with_capacity(n_axes);
378 for i in 0..n_axes {
379 let rec = off_mul(axes_off, i, axis_size)?;
380 let tag_bytes = bytes_at(d, rec, 4)?;
381 let mut tag = [0u8; 4];
382 tag.copy_from_slice(tag_bytes);
383 let min = fixed_16_16(be_i32(d, off(rec, 4)?)?);
384 let default = fixed_16_16(be_i32(d, off(rec, 8)?)?);
385 let max = fixed_16_16(be_i32(d, off(rec, 12)?)?);
386 let flags = be_u16_at(d, rec, 16)?;
387 let name_id = be_u16_at(d, rec, 18)?;
388 axes.push(VariationAxis {
389 tag,
390 min,
391 default,
392 max,
393 flags,
394 name_id,
395 });
396 }
397
398 let n_inst = instance_count.min(MAX_NAMED_INSTANCES);
399 let coord_bytes = n_axes.checked_mul(4)?;
400 let min_inst_size = 4usize.checked_add(coord_bytes)?;
401 let mut instances = Vec::new();
402 if instance_size >= min_inst_size {
403 let inst_off = off(axes_off, axes_bytes)?;
404 let inst_bytes = n_inst.checked_mul(instance_size)?;
405 if inst_off.checked_add(inst_bytes)? <= table_end {
406 let has_ps_name = instance_size >= min_inst_size.saturating_add(2);
407 for i in 0..n_inst {
408 let rec = off_mul(inst_off, i, instance_size)?;
409 let subfamily_name_id = be_u16(d, rec)?;
410 let flags = be_u16_at(d, rec, 2)?;
411 let mut coordinates = Vec::with_capacity(n_axes);
412 let mut ok = true;
413 for a in 0..n_axes {
414 let Some(coord) = off(rec, 4)
415 .and_then(|base| off_mul(base, a, 4))
416 .and_then(|o| be_i32(d, o))
417 else {
418 ok = false;
419 break;
420 };
421 coordinates.push(fixed_16_16(coord));
422 }
423 if !ok {
424 continue;
425 }
426 let postscript_name_id = if has_ps_name {
427 be_u16_at(d, rec, min_inst_size)
428 } else {
429 None
430 };
431 instances.push(NamedInstance {
432 subfamily_name_id,
433 flags,
434 coordinates,
435 postscript_name_id,
436 });
437 }
438 }
439 }
440
441 let avar = vec![None; axes.len()];
442 Some(FontVariation {
443 axes,
444 instances,
445 avar,
446 })
447}
448
449fn parse_avar(d: &[u8], table_off: usize, table_len: usize, n_axes: usize) -> Option<AvarAxisMaps> {
452 let table_end = table_off.checked_add(table_len)?;
453 if table_off.checked_add(8)? > table_end {
454 return None;
455 }
456 let major = be_u16(d, table_off)?;
457 if major != 1 {
458 return None;
459 }
460 let axis_count = be_u16_at(d, table_off, 6)? as usize;
461 if axis_count != n_axes {
462 return None;
463 }
464 let mut maps = Vec::with_capacity(n_axes);
465 let mut cursor = off(table_off, 8)?;
466 for _ in 0..n_axes {
467 if cursor.checked_add(2)? > table_end {
468 return None;
469 }
470 let count = be_u16(d, cursor)? as usize;
471 cursor = off(cursor, 2)?;
472 let n = count.min(MAX_AVAR_MAPS);
473 let bytes = n.checked_mul(4)?;
474 if cursor.checked_add(bytes)? > table_end {
475 return None;
476 }
477 let mut segs = Vec::with_capacity(n);
478 for i in 0..n {
479 let rec = off_mul(cursor, i, 4)?;
480 let from = f2dot14(be_i16(d, rec)?);
481 let to = f2dot14(be_i16_at(d, rec, 2)?);
482 segs.push((from, to));
483 }
484 let claimed = count.checked_mul(4)?;
486 cursor = off(cursor, claimed)?;
487 if segs.len() < 2 {
488 maps.push(None);
489 continue;
490 }
491 let sorted = segs.windows(2).all(|w| w[0].0 <= w[1].0);
494 maps.push(if sorted { Some(segs) } else { None });
495 }
496 Some(maps)
497}
498
499fn be_i16_at(d: &[u8], base: usize, delta: usize) -> Option<i16> {
500 be_i16(d, off(base, delta)?)
501}
502
503fn avar_map(maps: &[(f32, f32)], x: f32) -> f32 {
505 let Some(first) = maps.first() else {
506 return x;
507 };
508 if x <= first.0 {
509 return first.1;
510 }
511 let Some(last) = maps.last() else {
512 return x;
513 };
514 if x >= last.0 {
515 return last.1;
516 }
517 for pair in maps.windows(2) {
518 let (from0, to0) = pair[0];
519 let (from1, to1) = pair[1];
520 if x <= from1 {
521 let span = from1 - from0;
522 if span == 0.0 {
523 return to0;
524 }
525 let t = (x - from0) / span;
526 return to0 + t * (to1 - to0);
527 }
528 }
529 last.1
530}
531
532fn normalize_user(user: f32, axis: &VariationAxis) -> f32 {
536 let lo = axis.min.min(axis.max);
537 let hi = axis.min.max(axis.max);
538 let user = if user < lo {
539 lo
540 } else if user > hi {
541 hi
542 } else {
543 user
544 };
545 let default = if axis.default < lo {
546 lo
547 } else if axis.default > hi {
548 hi
549 } else {
550 axis.default
551 };
552 if user < default {
553 let span = default - lo;
554 if span == 0.0 {
555 0.0
556 } else {
557 (user - default) / span
558 }
559 } else if user > default {
560 let span = hi - default;
561 if span == 0.0 {
562 0.0
563 } else {
564 (user - default) / span
565 }
566 } else {
567 0.0
568 }
569}
570
571impl Font {
572 pub fn parse(data: Vec<u8>) -> Result<Self, FontError> {
578 let d = data.as_slice();
579 let magic = be_u32(d, 0).ok_or(FontError::Truncated)?;
580 if magic != 0x0001_0000 && magic != 0x7472_7565 && magic != 0x4F54_544F {
582 return Err(FontError::BadMagic);
583 }
584
585 let head = find_table(d, b"head").ok_or(FontError::MissingTable("head"))?;
586 let maxp = find_table(d, b"maxp").ok_or(FontError::MissingTable("maxp"))?;
587 let hhea = find_table(d, b"hhea").ok_or(FontError::MissingTable("hhea"))?;
588 let hmtx = find_table(d, b"hmtx").ok_or(FontError::MissingTable("hmtx"))?;
589 let cmap = find_table(d, b"cmap").ok_or(FontError::MissingTable("cmap"))?;
590
591 let units_per_em =
592 be_u16(d, off(head, 18).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
593 let num_glyphs =
594 be_u16(d, off(maxp, 4).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
595 let ascent =
596 be_i16(d, off(hhea, 4).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
597 let descent =
598 be_i16(d, off(hhea, 6).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
599 let line_gap =
600 be_i16(d, off(hhea, 8).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
601 let num_h_metrics =
602 be_u16(d, off(hhea, 34).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
603
604 let (cmap_off, cmap_format) = select_cmap(d, cmap).ok_or(FontError::NoUnicodeCmap)?;
605 let cmap4_cache = if cmap_format == 4 {
606 parse_cmap4_cache(d, cmap_off)
607 } else {
608 None
609 };
610
611 let loca_long = off(head, 50)
614 .and_then(|offset| be_i16(d, offset))
615 .unwrap_or(0)
616 != 0;
617 let loca_off = find_table(d, b"loca");
618 let glyf = find_table_full(d, b"glyf");
619 let kern0 = find_kern0(d);
620 let mut variation =
621 find_table_full(d, b"fvar").and_then(|(off, len)| parse_fvar(d, off, len));
622 if let Some(var) = variation.as_mut() {
623 if let Some(avar) = find_table_full(d, b"avar")
624 .and_then(|(off, len)| parse_avar(d, off, len, var.axes.len()))
625 {
626 var.avar = avar;
627 }
628 }
629
630 let mut font = Self {
631 data,
632 units_per_em,
633 num_glyphs,
634 ascent,
635 descent,
636 line_gap,
637 num_h_metrics,
638 hmtx_off: hmtx,
639 cmap_off,
640 cmap_format,
641 cmap4_cache,
642 glyf,
643 loca_off,
644 loca_long,
645 kern0,
646 variation,
647 latin1_glyphs: [0; 256],
648 latin1_advances_1000: [0; 256],
649 };
650 for b in 0..256 {
651 let gid = match font.cmap_format {
652 4 => font.cmap4_lookup(b as u32).unwrap_or(0),
653 12 => font.cmap12_lookup(b as u32).unwrap_or(0),
654 _ => 0,
655 };
656 font.latin1_glyphs[b] = gid;
657 }
658 if font.units_per_em > 0 {
659 for b in 0..256 {
660 let gid = font.latin1_glyphs[b];
661 let aw = font.advance_width(gid) as u32;
662 font.latin1_advances_1000[b] = aw * 1000 / font.units_per_em as u32;
663 }
664 }
665 Ok(font)
666 }
667
668 #[must_use]
670 pub fn has_glyf_outlines(&self) -> bool {
671 self.glyf.is_some() && self.loca_off.is_some()
672 }
673
674 #[must_use]
676 pub fn axes(&self) -> &[VariationAxis] {
677 self.variation
678 .as_ref()
679 .map(|v| v.axes.as_slice())
680 .unwrap_or(&[])
681 }
682
683 #[must_use]
685 pub fn named_instances(&self) -> &[NamedInstance] {
686 self.variation
687 .as_ref()
688 .map(|v| v.instances.as_slice())
689 .unwrap_or(&[])
690 }
691
692 #[must_use]
694 pub fn instance_bounds(&self, tag: [u8; 4]) -> Option<AxisBounds> {
695 self.axes()
696 .iter()
697 .find(|a| a.tag == tag)
698 .map(|a| AxisBounds {
699 min: a.min,
700 default: a.default,
701 max: a.max,
702 })
703 }
704
705 #[must_use]
711 pub fn normalized_axis(&self, tag: [u8; 4], user: f32) -> Option<f32> {
712 let var = self.variation.as_ref()?;
713 let (idx, axis) = var.axes.iter().enumerate().find(|(_, a)| a.tag == tag)?;
714 let mut n = normalize_user(user, axis);
715 if let Some(maps) = var.avar.get(idx).and_then(|m| m.as_ref()) {
716 n = avar_map(maps, n);
717 }
718 Some(n)
719 }
720
721 #[must_use]
730 pub fn instance(&self, weight: f32) -> Option<Font> {
731 crate::gvar::instance_font(self, weight)
732 }
733
734 #[must_use]
737 pub fn as_sfnt(&self) -> &[u8] {
738 &self.data
739 }
740
741 pub(crate) fn raw_bytes(&self) -> &[u8] {
742 self.as_sfnt()
743 }
744
745 fn glyph_range(&self, gid: u16) -> Option<(usize, usize)> {
749 let loca = self.loca_off?;
750 let (glyf_off, glyf_len) = self.glyf?;
751 let i = gid as usize;
752 let (start, end) = if self.loca_long {
753 (
754 be_u32(&self.data, off_mul(loca, i, 4)?)? as usize,
755 be_u32(&self.data, off_mul(loca, i.checked_add(1)?, 4)?)? as usize,
756 )
757 } else {
758 (
760 be_u16(&self.data, off_mul(loca, i, 2)?)? as usize * 2,
761 be_u16(&self.data, off_mul(loca, i.checked_add(1)?, 2)?)? as usize * 2,
762 )
763 };
764 if end < start || end > glyf_len {
765 return None;
766 }
767 Some((off(glyf_off, start)?, off(glyf_off, end)?))
768 }
769
770 #[must_use]
773 pub fn glyph_data(&self, gid: u16) -> Option<&[u8]> {
774 let (s, e) = self.glyph_range(gid)?;
775 self.data.get(s..e)
776 }
777
778 #[must_use]
781 pub fn glyph_bbox(&self, gid: u16) -> Option<[i16; 4]> {
782 let (s, e) = self.glyph_range(gid)?;
783 if e <= s {
784 return None; }
786 Some([
787 be_i16(&self.data, off(s, 2)?)?,
788 be_i16(&self.data, off(s, 4)?)?,
789 be_i16(&self.data, off(s, 6)?)?,
790 be_i16(&self.data, off(s, 8)?)?,
791 ])
792 }
793
794 #[must_use]
796 pub fn is_composite(&self, gid: u16) -> bool {
797 match self.glyph_range(gid) {
798 Some((s, e)) if e > s => be_i16(&self.data, s).is_some_and(|n| n < 0),
799 _ => false,
800 }
801 }
802
803 #[must_use]
806 pub fn glyph_components(&self, gid: u16) -> Vec<u16> {
807 const ARG_WORDS: u16 = 0x0001;
808 const WE_HAVE_SCALE: u16 = 0x0008;
809 const MORE: u16 = 0x0020;
810 const X_Y_SCALE: u16 = 0x0040;
811 const TWO_BY_TWO: u16 = 0x0080;
812
813 let mut out = Vec::new();
814 let Some((s, e)) = self.glyph_range(gid) else {
815 return out;
816 };
817 if e <= s || be_i16(&self.data, s).is_none_or(|n| n >= 0) {
818 return out;
819 }
820 let Some(mut p) = off(s, 10) else {
821 return out;
822 };
823 while let Some(component_record_end) = off(p, 4) {
824 if component_record_end > e {
825 break;
826 }
827 let Some(flags) = be_u16(&self.data, p) else {
828 break;
829 };
830 let Some(comp) = off(p, 2).and_then(|offset| be_u16(&self.data, offset)) else {
831 break;
832 };
833 let mut step = 4usize + if flags & ARG_WORDS != 0 { 4 } else { 2 };
834 step += if flags & WE_HAVE_SCALE != 0 {
835 2
836 } else if flags & X_Y_SCALE != 0 {
837 4
838 } else if flags & TWO_BY_TWO != 0 {
839 8
840 } else {
841 0
842 };
843 let Some(next) = off(p, step) else {
844 break;
845 };
846 if next > e {
847 break;
848 }
849 out.push(comp);
850 p = next;
851 if flags & MORE == 0 || p >= e {
852 break;
853 }
854 }
855 out
856 }
857
858 #[must_use]
861 pub fn advance_width(&self, gid: u16) -> u16 {
862 let last = self.num_h_metrics.saturating_sub(1);
863 let idx = gid.min(last) as usize;
864 off_mul(self.hmtx_off, idx, 4)
865 .and_then(|offset| be_u16(&self.data, offset))
866 .unwrap_or(0)
867 }
868
869 #[must_use]
872 pub fn left_side_bearing(&self, gid: u16) -> i16 {
873 if self.num_h_metrics == 0 {
874 return 0;
875 }
876 let gid = gid as usize;
877 let num_h_metrics = self.num_h_metrics as usize;
878 let offset = if gid < num_h_metrics {
879 off_mul(self.hmtx_off, gid, 4).and_then(|base| off(base, 2))
880 } else {
881 off_mul(self.hmtx_off, num_h_metrics, 4)
882 .and_then(|base| off_mul(base, gid - num_h_metrics, 2))
883 };
884 offset
885 .and_then(|offset| be_i16(&self.data, offset))
886 .unwrap_or(0)
887 }
888
889 #[must_use]
891 #[inline(always)]
892 pub fn glyph_index(&self, ch: char) -> u16 {
893 let cp = ch as u32;
894 if cp < 256 {
895 return self.latin1_glyphs[cp as usize];
896 }
897 match self.cmap_format {
898 4 => self.cmap4_lookup(cp).unwrap_or(0),
899 12 => self.cmap12_lookup(cp).unwrap_or(0),
900 _ => 0,
901 }
902 }
903
904 #[must_use]
909 #[inline(always)]
910 pub fn advance_1000(&self, ch: char) -> u32 {
911 let cp = ch as u32;
912 if cp < 256 {
913 return self.latin1_advances_1000[cp as usize];
914 }
915 if self.units_per_em == 0 {
916 return 0;
917 }
918 let aw = self.advance_width(self.glyph_index(ch)) as u32;
919 aw * 1000 / self.units_per_em as u32
920 }
921
922 #[must_use]
928 pub fn kerning_between_glyphs(&self, left: u16, right: u16) -> i16 {
929 let Some((pairs, n_pairs)) = self.kern0 else {
930 return 0;
931 };
932 let target = ((left as u32) << 16) | right as u32;
933 let mut lo = 0usize;
934 let mut hi = n_pairs as usize;
935 while lo < hi {
936 let mid = lo + (hi - lo) / 2;
937 let Some(rec) = off_mul(pairs, mid, 6) else {
938 return 0;
939 };
940 let Some(l) = be_u16(&self.data, rec) else {
941 return 0;
942 };
943 let Some(r) = off(rec, 2).and_then(|offset| be_u16(&self.data, offset)) else {
944 return 0;
945 };
946 let key = ((l as u32) << 16) | r as u32;
947 if key == target {
948 return off(rec, 4)
949 .and_then(|offset| be_i16(&self.data, offset))
950 .unwrap_or(0);
951 }
952 if key < target {
953 lo = mid + 1;
954 } else {
955 hi = mid;
956 }
957 }
958 0
959 }
960
961 #[must_use]
963 pub fn kerning(&self, left: char, right: char) -> i16 {
964 self.kerning_between_glyphs(self.glyph_index(left), self.glyph_index(right))
965 }
966
967 #[must_use]
969 pub fn kerning_1000(&self, left: char, right: char) -> i32 {
970 if self.units_per_em == 0 {
971 return 0;
972 }
973 self.kerning(left, right) as i32 * 1000 / self.units_per_em as i32
974 }
975
976 fn cmap4_lookup(&self, cp: u32) -> Option<u16> {
977 if cp > 0xFFFF {
978 return Some(0);
979 }
980 let c = cp as u16;
981 if let Some(cache) = &self.cmap4_cache {
982 return self.cmap4_cached_lookup(c, cache);
983 }
984 self.cmap4_uncached_lookup(c)
985 }
986
987 fn cmap4_cached_lookup(&self, c: u16, cache: &Cmap4Cache) -> Option<u16> {
988 let segment = if cache.sorted_by_end {
989 let idx = cache.segments.partition_point(|seg| seg.end < c);
990 cache.segments.get(idx)
991 } else {
992 cache.segments.iter().find(|seg| c <= seg.end)
993 }?;
994
995 if c < segment.start {
996 return Some(0);
997 }
998 if segment.id_range_offset == 0 {
999 return Some(c.wrapping_add(segment.id_delta));
1000 }
1001 let gi_addr = off(
1002 off(
1003 segment.id_range_offset_pos,
1004 segment.id_range_offset as usize,
1005 )?,
1006 2usize.checked_mul((c - segment.start) as usize)?,
1007 )?;
1008 let g = be_u16(&self.data, gi_addr)?;
1009 Some(if g == 0 {
1010 0
1011 } else {
1012 g.wrapping_add(segment.id_delta)
1013 })
1014 }
1015
1016 fn cmap4_uncached_lookup(&self, c: u16) -> Option<u16> {
1017 let d = &self.data;
1018 let base = self.cmap_off;
1019 let seg_x2 = be_u16(d, off(base, 6)?)? as usize;
1020 let seg_count = seg_x2 / 2;
1021 let end_codes = off(base, 14)?;
1022 let start_codes = off(off(end_codes, seg_x2)?, 2)?; let id_deltas = off(start_codes, seg_x2)?;
1024 let id_range_offsets = off(id_deltas, seg_x2)?;
1025 for i in 0..seg_count {
1026 let end = be_u16(d, off_mul(end_codes, i, 2)?)?;
1027 if c > end {
1028 continue;
1029 }
1030 let start = be_u16(d, off_mul(start_codes, i, 2)?)?;
1031 if c < start {
1032 return Some(0);
1033 }
1034 let id_delta = be_u16(d, off_mul(id_deltas, i, 2)?)?;
1035 let iro_pos = off_mul(id_range_offsets, i, 2)?;
1036 let id_range_offset = be_u16(d, iro_pos)?;
1037 if id_range_offset == 0 {
1038 return Some(c.wrapping_add(id_delta));
1039 }
1040 let gi_addr = off(
1041 off(iro_pos, id_range_offset as usize)?,
1042 2usize.checked_mul((c - start) as usize)?,
1043 )?;
1044 let g = be_u16(d, gi_addr)?;
1045 return Some(if g == 0 { 0 } else { g.wrapping_add(id_delta) });
1046 }
1047 Some(0)
1048 }
1049
1050 #[must_use]
1057 pub fn subset(&self, keep: &[char]) -> Option<Vec<u8>> {
1058 let seed: Vec<u16> = keep.iter().map(|&c| self.glyph_index(c)).collect();
1059 self.subset_core(&seed, keep, true, false)
1063 .ok()
1064 .map(|(bytes, _)| bytes)
1065 }
1066
1067 pub fn subset_glyphs(
1080 &self,
1081 glyphs: &[u16],
1082 cmap_chars: &[char],
1083 ) -> Option<(Vec<u8>, std::collections::BTreeMap<u16, u16>)> {
1084 let (bytes, lookup) = self.subset_glyphs_with_lookup(glyphs, cmap_chars)?;
1085 let mut new_of = std::collections::BTreeMap::new();
1086 for (old, new) in lookup.into_iter().enumerate() {
1087 if new != MISSING_GLYPH_REMAP {
1088 new_of.insert(u16::try_from(old).ok()?, new);
1089 }
1090 }
1091 Some((bytes, new_of))
1092 }
1093
1094 pub fn subset_glyphs_with_lookup(
1107 &self,
1108 glyphs: &[u16],
1109 cmap_chars: &[char],
1110 ) -> Option<(Vec<u8>, Vec<u16>)> {
1111 self.subset_core(glyphs, cmap_chars, false, false).ok()
1114 }
1115
1116 fn subset_core(
1117 &self,
1118 seed_glyphs: &[u16],
1119 cmap_chars: &[char],
1120 include_os2: bool,
1121 strict: bool,
1122 ) -> Result<(Vec<u8>, Vec<u16>), SubsetError> {
1123 let mut error = SubsetError::new(SubsetErrorKind::Malformed, *b"sfnt", None, None);
1124 if !self.has_glyf_outlines() {
1127 return Err(SubsetError::new(
1128 SubsetErrorKind::UnsupportedFormat,
1129 *b"sfnt",
1130 None,
1131 None,
1132 ));
1133 }
1134 let mut set: std::collections::BTreeSet<u16> = std::collections::BTreeSet::new();
1135 set.insert(0);
1136 for &gid in seed_glyphs {
1137 if strict && gid >= self.num_glyphs {
1138 return Err(SubsetError::new(
1139 SubsetErrorKind::InvalidGlyph,
1140 *b"maxp",
1141 Some(gid),
1142 None,
1143 ));
1144 }
1145 if gid != 0 && gid < self.num_glyphs {
1146 set.insert(gid);
1147 }
1148 }
1149 let mut worklist: Vec<u16> = set.iter().copied().collect();
1157 while let Some(gid) = worklist.pop() {
1158 if strict {
1159 self.validate_subset_glyph(gid)?;
1160 }
1161 if self.is_composite(gid) {
1162 for c in self.glyph_components(gid) {
1163 if c < self.num_glyphs && set.insert(c) {
1164 worklist.push(c);
1165 }
1166 }
1167 }
1168 }
1169 let old_gids: Vec<u16> = set.into_iter().collect(); let mut new_of_lookup = vec![MISSING_GLYPH_REMAP; usize::from(self.num_glyphs).max(1)];
1175 for (i, &g) in old_gids.iter().enumerate() {
1176 let new_gid = u16::try_from(i).ok().ok_or(error)?;
1177 *new_of_lookup.get_mut(usize::from(g)).ok_or(error)? = new_gid;
1178 }
1179 let n = old_gids.len();
1180 let n_u16 = u16::try_from(n).ok().ok_or(error)?;
1181
1182 let mut glyf_bytes: Vec<u8> = Vec::with_capacity(n.saturating_mul(64));
1184 let mut loca_bytes: Vec<u8> =
1185 Vec::with_capacity(n.checked_add(1).ok_or(error)?.checked_mul(4).ok_or(error)?);
1186 for &old in &old_gids {
1187 error = SubsetError::new(SubsetErrorKind::Malformed, *b"glyf", Some(old), None);
1188 let offset = u32::try_from(glyf_bytes.len()).ok().ok_or(error)?;
1189 loca_bytes.extend_from_slice(&offset.to_be_bytes());
1190 let gb = self.subset_glyph_bytes(old, &new_of_lookup).ok_or(error)?;
1191 glyf_bytes.extend_from_slice(&gb);
1192 let rem = glyf_bytes.len() % 4;
1195 if rem != 0 {
1196 glyf_bytes.resize(glyf_bytes.len() + (4 - rem), 0);
1197 }
1198 }
1199 let final_offset = u32::try_from(glyf_bytes.len()).ok().ok_or(error)?;
1200 loca_bytes.extend_from_slice(&final_offset.to_be_bytes());
1201
1202 error = SubsetError::new(SubsetErrorKind::Malformed, *b"maxp", None, None);
1205 let (maxp_off, maxp_len) = find_table_full(&self.data, b"maxp").ok_or(error)?;
1206 let mut maxp = self
1207 .data
1208 .get(maxp_off..off(maxp_off, maxp_len).ok_or(error)?)
1209 .ok_or(error)?
1210 .to_vec();
1211 write_u16(&mut maxp, 4, n_u16).ok_or(error)?;
1212
1213 error = SubsetError::new(SubsetErrorKind::Malformed, *b"hhea", None, None);
1215 let (hhea_off, hhea_len) = find_table_full(&self.data, b"hhea").ok_or(error)?;
1216 let mut hhea = self
1217 .data
1218 .get(hhea_off..off(hhea_off, hhea_len).ok_or(error)?)
1219 .ok_or(error)?
1220 .to_vec();
1221 write_u16(&mut hhea, 34, n_u16).ok_or(error)?;
1222
1223 let mut hmtx: Vec<u8> = Vec::with_capacity(n.checked_mul(4).ok_or(error)?);
1225 for &old in &old_gids {
1226 let [a0, a1] = self.advance_width(old).to_be_bytes();
1227 let [l0, l1] = self.left_side_bearing(old).to_be_bytes();
1228 hmtx.extend_from_slice(&[a0, a1, l0, l1]);
1229 }
1230
1231 error = SubsetError::new(SubsetErrorKind::Malformed, *b"head", None, None);
1233 let (head_off, head_len) = find_table_full(&self.data, b"head").ok_or(error)?;
1234 let mut head = self
1235 .data
1236 .get(head_off..off(head_off, head_len).ok_or(error)?)
1237 .ok_or(error)?
1238 .to_vec();
1239 write_u32(&mut head, 8, 0).ok_or(error)?;
1240 write_u16(&mut head, 50, 1).ok_or(error)?; error = SubsetError::new(SubsetErrorKind::Capacity, *b"cmap", None, None);
1248 let cmap = if self.subset_reaches_supplementary_plane(cmap_chars, &new_of_lookup) {
1249 self.build_cmap12(cmap_chars, &new_of_lookup).ok_or(error)?
1250 } else {
1251 self.build_cmap4(cmap_chars, &new_of_lookup).ok_or(error)?
1252 };
1253
1254 let mut name: Vec<u8> = Vec::with_capacity(6);
1256 name.extend_from_slice(&0u16.to_be_bytes());
1257 name.extend_from_slice(&0u16.to_be_bytes());
1258 name.extend_from_slice(&6u16.to_be_bytes());
1259
1260 let mut post: Vec<u8> = Vec::with_capacity(32);
1262 post.extend_from_slice(&0x0003_0000u32.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); post.extend_from_slice(&0u16.to_be_bytes()); post.extend_from_slice(&0u16.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); post.extend_from_slice(&0u32.to_be_bytes()); let os2: Option<Vec<u8>> = if include_os2 {
1280 find_table_full(&self.data, b"OS/2")
1281 .and_then(|(o, l)| Some(self.data.get(o..off(o, l)?)?.to_vec()))
1282 } else {
1283 None
1284 };
1285
1286 let mut tables: Vec<(&[u8; 4], Vec<u8>)> = vec![
1288 (b"head", head),
1289 (b"hhea", hhea),
1290 (b"maxp", maxp),
1291 (b"hmtx", hmtx),
1292 (b"loca", loca_bytes),
1293 (b"glyf", glyf_bytes),
1294 (b"cmap", cmap),
1295 (b"name", name),
1296 (b"post", post),
1297 ];
1298 if let Some(os2) = os2 {
1299 tables.push((b"OS/2", os2));
1300 }
1301 tables.sort_by(|a, b| a.0.cmp(b.0)); error = SubsetError::new(SubsetErrorKind::Capacity, *b"sfnt", None, None);
1304 let num_tables = tables.len();
1305 let mut pw: usize = 1;
1307 let mut es: u16 = 0;
1308 while pw * 2 <= num_tables {
1309 pw *= 2;
1310 es += 1;
1311 }
1312 let search_range = (pw as u16).wrapping_mul(16);
1313 let entry_selector = es;
1314 let range_shift = (num_tables as u16)
1315 .wrapping_mul(16)
1316 .wrapping_sub(search_range);
1317
1318 let dir_size = 12 + num_tables * 16;
1319 let mut body: Vec<u8> = Vec::new();
1320 let mut records: Vec<([u8; 4], u32, u32, u32)> = Vec::with_capacity(num_tables);
1322 let mut head_offset: usize = 0;
1323 for (tag, bytes) in &tables {
1324 while (dir_size + body.len()) % 4 != 0 {
1326 body.push(0);
1327 }
1328 let table_offset = dir_size + body.len();
1329 if *tag == b"head" {
1330 head_offset = table_offset;
1331 }
1332 let checksum = table_checksum(bytes);
1333 records.push((
1334 **tag,
1335 checksum,
1336 u32::try_from(table_offset).ok().ok_or(error)?,
1337 u32::try_from(bytes.len()).ok().ok_or(error)?,
1338 ));
1339 body.extend_from_slice(bytes);
1340 }
1341 while body.len() % 4 != 0 {
1342 body.push(0);
1343 }
1344
1345 let mut out: Vec<u8> = Vec::with_capacity(dir_size + body.len());
1346 out.extend_from_slice(&0x0001_0000u32.to_be_bytes()); out.extend_from_slice(&(num_tables as u16).to_be_bytes());
1348 out.extend_from_slice(&search_range.to_be_bytes());
1349 out.extend_from_slice(&entry_selector.to_be_bytes());
1350 out.extend_from_slice(&range_shift.to_be_bytes());
1351 for (tag, checksum, toff, tlen) in &records {
1352 out.extend_from_slice(tag);
1353 out.extend_from_slice(&checksum.to_be_bytes());
1354 out.extend_from_slice(&toff.to_be_bytes());
1355 out.extend_from_slice(&tlen.to_be_bytes());
1356 }
1357 out.extend_from_slice(&body);
1358
1359 let file_checksum = table_checksum(&out);
1361 let adj = 0xB1B0_AFBAu32.wrapping_sub(file_checksum);
1362 write_u32(&mut out, off(head_offset, 8).ok_or(error)?, adj).ok_or(error)?;
1363
1364 Ok((out, new_of_lookup))
1365 }
1366
1367 fn subset_glyph_bytes(&self, old: u16, new_of: &[u16]) -> Option<Vec<u8>> {
1372 const ARG_WORDS: u16 = 0x0001;
1373 const WE_HAVE_SCALE: u16 = 0x0008;
1374 const MORE: u16 = 0x0020;
1375 const X_Y_SCALE: u16 = 0x0040;
1376 const TWO_BY_TWO: u16 = 0x0080;
1377 const WE_HAVE_INSTRUCTIONS: u16 = 0x0100;
1378
1379 let data = self.glyph_data(old).unwrap_or(&[]);
1380 if data.is_empty() {
1381 return Some(Vec::new());
1382 }
1383 let num_contours = be_i16(data, 0)?;
1384 if num_contours >= 0 {
1385 return strip_simple_glyph_instructions(data, num_contours as usize);
1386 }
1387 let mut out = data.to_vec();
1389 let mut p = 10usize; let mut instruction_flags_positions = Vec::new();
1391 let mut dangling_more = false;
1392 loop {
1393 let last_flags_pos = p;
1394 let flags = be_u16(&out, p)?;
1395
1396 if flags & WE_HAVE_INSTRUCTIONS != 0 {
1397 instruction_flags_positions.push(p);
1398 }
1399 let comp_old = be_u16_at(&out, p, 2)?;
1400 let comp_new = remapped_gid(new_of, comp_old).unwrap_or(0);
1406 let nb = comp_new.to_be_bytes();
1407 *out.get_mut(off(p, 2)?)? = nb[0];
1408 *out.get_mut(off(p, 3)?)? = nb[1];
1409 p = off(p, 4)?;
1410 p = off(p, if flags & ARG_WORDS != 0 { 4 } else { 2 })?;
1411 if flags & WE_HAVE_SCALE != 0 {
1412 p = off(p, 2)?;
1413 } else if flags & X_Y_SCALE != 0 {
1414 p = off(p, 4)?;
1415 } else if flags & TWO_BY_TWO != 0 {
1416 p = off(p, 8)?;
1417 }
1418 if flags & MORE == 0 {
1419 break;
1420 }
1421
1422 if off(p, 4).is_none_or(|end| end > out.len()) {
1431 write_u16(
1432 &mut out,
1433 last_flags_pos,
1434 flags & !(MORE | WE_HAVE_INSTRUCTIONS),
1435 )?;
1436 dangling_more = true;
1437 break;
1438 }
1439 }
1440 if dangling_more {
1441 for flags_pos in &instruction_flags_positions {
1442 let flags = be_u16(&out, *flags_pos)?;
1443 write_u16(&mut out, *flags_pos, flags & !WE_HAVE_INSTRUCTIONS)?;
1444 }
1445 } else if !instruction_flags_positions.is_empty() {
1446 for flags_pos in instruction_flags_positions {
1447 let flags = be_u16(&out, flags_pos)?;
1448 write_u16(&mut out, flags_pos, flags & !WE_HAVE_INSTRUCTIONS)?;
1449 }
1450 let instruction_len = be_u16(&out, p)? as usize;
1451 let instruction_start = off(p, 2)?;
1452 let instruction_end = off(instruction_start, instruction_len)?;
1453 if instruction_end > out.len() {
1454 return None;
1455 }
1456 out.drain(p..instruction_end);
1457 }
1458 Some(out)
1459 }
1460
1461 fn build_cmap4(&self, keep: &[char], new_of: &[u16]) -> Option<Vec<u8>> {
1465 let mut codes: std::collections::BTreeMap<u16, u16> = std::collections::BTreeMap::new();
1467 for &ch in keep {
1468 let cp = ch as u32;
1469 if cp >= 0xFFFF {
1470 continue;
1471 }
1472 let old = self.glyph_index(ch);
1473 let Some(ng) = remapped_gid(new_of, old) else {
1477 continue;
1478 };
1479 codes.insert(cp as u16, ng);
1480 }
1481 let entries: Vec<(u16, u16)> = codes.into_iter().collect();
1482 let seg_count = entries.len().checked_add(1)?; let sub_len = 16usize.checked_add(seg_count.checked_mul(8)?)?;
1484 let sub_len_u16 = u16::try_from(sub_len).ok()?;
1485 let seg_count_x2 = u16::try_from(seg_count.checked_mul(2)?).ok()?;
1486
1487 let mut pw: usize = 1;
1488 let mut es: u16 = 0;
1489 while pw * 2 <= seg_count {
1490 pw *= 2;
1491 es += 1;
1492 }
1493 let search_range = u16::try_from(pw.checked_mul(2)?).ok()?;
1494 let entry_selector = es;
1495 let range_shift = seg_count_x2.checked_sub(search_range)?;
1496
1497 let mut sub: Vec<u8> = Vec::with_capacity(sub_len);
1498 sub.extend_from_slice(&4u16.to_be_bytes()); sub.extend_from_slice(&sub_len_u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&seg_count_x2.to_be_bytes()); sub.extend_from_slice(&search_range.to_be_bytes());
1503 sub.extend_from_slice(&entry_selector.to_be_bytes());
1504 sub.extend_from_slice(&range_shift.to_be_bytes());
1505 for &(code, _) in &entries {
1507 sub.extend_from_slice(&code.to_be_bytes());
1508 }
1509 sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
1510 sub.extend_from_slice(&0u16.to_be_bytes());
1512 for &(code, _) in &entries {
1514 sub.extend_from_slice(&code.to_be_bytes());
1515 }
1516 sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
1517 for &(code, ng) in &entries {
1519 sub.extend_from_slice(&ng.wrapping_sub(code).to_be_bytes());
1520 }
1521 sub.extend_from_slice(&1u16.to_be_bytes());
1523 for _ in &entries {
1525 sub.extend_from_slice(&0u16.to_be_bytes());
1526 }
1527 sub.extend_from_slice(&0u16.to_be_bytes());
1528
1529 let mut cmap: Vec<u8> = Vec::with_capacity(12 + sub.len());
1530 cmap.extend_from_slice(&0u16.to_be_bytes()); cmap.extend_from_slice(&1u16.to_be_bytes()); cmap.extend_from_slice(&3u16.to_be_bytes()); cmap.extend_from_slice(&1u16.to_be_bytes()); cmap.extend_from_slice(&12u32.to_be_bytes()); cmap.extend_from_slice(&sub);
1536 Some(cmap)
1537 }
1538
1539 fn subset_reaches_supplementary_plane(&self, keep: &[char], new_of: &[u16]) -> bool {
1542 keep.iter().any(|&ch| {
1543 (ch as u32) >= 0x1_0000 && remapped_gid(new_of, self.glyph_index(ch)).is_some()
1544 })
1545 }
1546
1547 fn build_cmap12(&self, keep: &[char], new_of: &[u16]) -> Option<Vec<u8>> {
1554 let mut codes: std::collections::BTreeMap<u32, u16> = std::collections::BTreeMap::new();
1556 for &ch in keep {
1557 let old = self.glyph_index(ch);
1558 let Some(ng) = remapped_gid(new_of, old) else {
1559 continue;
1560 };
1561 codes.insert(u32::from(ch), ng);
1562 }
1563 struct Group {
1564 start_cp: u32,
1565 end_cp: u32,
1566 start_gid: u16,
1567 }
1568 let mut groups: Vec<Group> = Vec::with_capacity(codes.len());
1569 for (&cp, &ng) in &codes {
1570 match groups.last_mut() {
1571 Some(g)
1572 if g.end_cp.checked_add(1) == Some(cp)
1573 && u64::from(g.start_gid) + (g.end_cp - g.start_cp) as u64 + 1
1574 == u64::from(ng) =>
1575 {
1576 g.end_cp = cp;
1577 }
1578 _ => groups.push(Group {
1579 start_cp: cp,
1580 end_cp: cp,
1581 start_gid: ng,
1582 }),
1583 }
1584 }
1585
1586 let sub_len = 16usize.checked_add(groups.len().checked_mul(12)?)?;
1587 if sub_len > u32::MAX as usize {
1588 return None;
1589 }
1590 let mut sub: Vec<u8> = Vec::with_capacity(sub_len);
1591 sub.extend_from_slice(&12u16.to_be_bytes()); sub.extend_from_slice(&0u16.to_be_bytes()); sub.extend_from_slice(&(sub_len as u32).to_be_bytes()); sub.extend_from_slice(&0u32.to_be_bytes()); sub.extend_from_slice(&(groups.len() as u32).to_be_bytes()); for g in &groups {
1597 sub.extend_from_slice(&g.start_cp.to_be_bytes());
1598 sub.extend_from_slice(&g.end_cp.to_be_bytes());
1599 sub.extend_from_slice(&u32::from(g.start_gid).to_be_bytes());
1600 }
1601
1602 let mut cmap: Vec<u8> = Vec::with_capacity(12 + sub.len());
1603 cmap.extend_from_slice(&0u16.to_be_bytes()); cmap.extend_from_slice(&1u16.to_be_bytes()); cmap.extend_from_slice(&3u16.to_be_bytes()); cmap.extend_from_slice(&10u16.to_be_bytes()); cmap.extend_from_slice(&12u32.to_be_bytes()); cmap.extend_from_slice(&sub);
1609 Some(cmap)
1610 }
1611
1612 fn cmap12_lookup(&self, cp: u32) -> Option<u16> {
1613 let d = &self.data;
1614 let base = self.cmap_off;
1615 let num_groups = be_u32(d, off(base, 12)?)? as usize;
1616 for i in 0..num_groups {
1617 let g = off_mul(off(base, 16)?, i, 12)?;
1618 let start = be_u32(d, g)?;
1619 let end = be_u32(d, off(g, 4)?)?;
1620 if cp >= start && cp <= end {
1621 let start_gid = be_u32(d, off(g, 8)?)?;
1622 let gid = start_gid.checked_add(cp - start)?;
1623 return Some((gid & 0xFFFF) as u16);
1624 }
1625 }
1626 Some(0)
1627 }
1628}
1629
1630fn remapped_gid(new_of: &[u16], old: u16) -> Option<u16> {
1631 match new_of.get(usize::from(old)).copied()? {
1632 MISSING_GLYPH_REMAP => None,
1633 gid => Some(gid),
1634 }
1635}
1636
1637fn strip_simple_glyph_instructions(data: &[u8], contour_count: usize) -> Option<Vec<u8>> {
1638 let instruction_len_offset = off(10, contour_count.checked_mul(2)?)?;
1639 let instruction_len = be_u16(data, instruction_len_offset)? as usize;
1640 let instruction_start = off(instruction_len_offset, 2)?;
1641 let instruction_end = off(instruction_start, instruction_len)?;
1642 if instruction_end > data.len() {
1643 return None;
1644 }
1645
1646 let mut out = Vec::with_capacity(data.len().saturating_sub(instruction_len));
1647 out.extend_from_slice(data.get(..instruction_len_offset)?);
1648 out.extend_from_slice(&0u16.to_be_bytes());
1649 out.extend_from_slice(data.get(instruction_end..)?);
1650 Some(out)
1651}
1652
1653fn parse_cmap4_cache(d: &[u8], base: usize) -> Option<Cmap4Cache> {
1654 let seg_x2 = be_u16(d, off(base, 6)?)? as usize;
1655 let seg_count = seg_x2 / 2;
1656 let end_codes = off(base, 14)?;
1657 let start_codes = off(off(end_codes, seg_x2)?, 2)?;
1658 let id_deltas = off(start_codes, seg_x2)?;
1659 let id_range_offsets = off(id_deltas, seg_x2)?;
1660
1661 let mut segments = Vec::with_capacity(seg_count);
1662 let mut sorted_by_end = true;
1663 let mut prev_end: Option<u16> = None;
1664 for i in 0..seg_count {
1665 let end = be_u16(d, off_mul(end_codes, i, 2)?)?;
1666 let start = be_u16(d, off_mul(start_codes, i, 2)?)?;
1667 let id_delta = be_u16(d, off_mul(id_deltas, i, 2)?)?;
1668 let id_range_offset_pos = off_mul(id_range_offsets, i, 2)?;
1669 let id_range_offset = be_u16(d, id_range_offset_pos)?;
1670 if prev_end.is_some_and(|prev| end < prev) {
1671 sorted_by_end = false;
1672 }
1673 prev_end = Some(end);
1674 segments.push(Cmap4Segment {
1675 start,
1676 end,
1677 id_delta,
1678 id_range_offset,
1679 id_range_offset_pos,
1680 });
1681 }
1682
1683 Some(Cmap4Cache {
1684 segments,
1685 sorted_by_end,
1686 })
1687}
1688
1689fn select_cmap(d: &[u8], cmap: usize) -> Option<(usize, u16)> {
1693 let num = be_u16(d, off(cmap, 2)?)? as usize;
1694 let mut best: Option<(usize, u16, u8)> = None; for i in 0..num {
1696 let rec = off_mul(off(cmap, 4)?, i, 8)?;
1697 let platform = be_u16(d, rec)?;
1698 let encoding = be_u16(d, off(rec, 2)?)?;
1699 let sub = off(cmap, be_u32(d, off(rec, 4)?)? as usize)?;
1700 let format = be_u16(d, sub)?;
1701 let unicode = matches!((platform, encoding), (0, _) | (3, 1) | (3, 10));
1702 if !unicode {
1703 continue;
1704 }
1705 let rank = match format {
1706 12 => 3,
1707 4 => {
1708 if (platform, encoding) == (3, 1) || platform == 0 {
1709 2
1710 } else {
1711 1
1712 }
1713 }
1714 _ => continue,
1715 };
1716 if best.is_none_or(|(_, _, r)| rank > r) {
1717 best = Some((sub, format, rank));
1718 }
1719 }
1720 best.map(|(off, fmt, _)| (off, fmt))
1721}
1722
1723#[derive(Default)]
1735struct PairKeyHasher(u64);
1736
1737impl std::hash::Hasher for PairKeyHasher {
1738 fn write_u32(&mut self, v: u32) {
1739 self.0 = u64::from(v).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1740 }
1741
1742 fn write(&mut self, _bytes: &[u8]) {
1743 }
1745
1746 fn finish(&self) -> u64 {
1747 let mut x = self.0;
1748 x ^= x >> 29;
1749 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1750 x ^= x >> 32;
1751 x
1752 }
1753}
1754
1755type PairMap = std::collections::HashMap<u32, i16, std::hash::BuildHasherDefault<PairKeyHasher>>;
1756
1757fn pair_key(left: u16, right: u16) -> u32 {
1759 (u32::from(left) << 16) | u32::from(right)
1760}
1761
1762#[derive(Clone, Debug)]
1764enum ClassDef {
1765 Format1 { start: u16, classes: Vec<u16> },
1767 Format2 {
1771 ranges: Vec<(u16, u16, u16)>,
1772 dense: bool,
1773 },
1774}
1775
1776impl ClassDef {
1777 fn class(&self, g: u16) -> u16 {
1779 match self {
1780 ClassDef::Format1 { start, classes } => {
1781 if g >= *start {
1782 let i = (g - *start) as usize;
1783 if i < classes.len() {
1784 return classes[i];
1785 }
1786 }
1787 0
1788 }
1789 ClassDef::Format2 { ranges, dense } => {
1790 if *dense {
1791 let idx = ranges.partition_point(|&(s, _, _)| s <= g);
1794 if idx > 0 {
1795 let (_, e, c) = ranges[idx - 1];
1796 if g <= e {
1797 return c;
1798 }
1799 }
1800 0
1801 } else {
1802 for &(s, e, c) in ranges {
1803 if g >= s && g <= e {
1804 return c;
1805 }
1806 }
1807 0
1808 }
1809 }
1810 }
1811 }
1812}
1813
1814#[derive(Clone, Debug)]
1817enum KernSubtable {
1818 Format1 { pairs: PairMap },
1820 Format2 {
1822 coverage: Vec<u16>,
1824 class1: ClassDef,
1825 class2: ClassDef,
1826 class1_count: u16,
1829 class2_count: u16,
1830 matrix: Vec<i16>,
1833 },
1834}
1835
1836impl KernSubtable {
1837 fn lookup(&self, left: u16, right: u16) -> Option<i16> {
1843 match self {
1844 KernSubtable::Format1 { pairs } => pairs.get(&pair_key(left, right)).copied(),
1845 KernSubtable::Format2 {
1846 coverage,
1847 class1,
1848 class2,
1849 class1_count,
1850 class2_count,
1851 matrix,
1852 } => {
1853 if coverage.binary_search(&left).is_err() {
1855 return None;
1856 }
1857 let c1 = class1.class(left) as usize;
1858 let c2 = class2.class(right) as usize;
1859 let c1_count = *class1_count as usize;
1860 let c2_count = *class2_count as usize;
1861 if c1 >= c1_count || c2 >= c2_count {
1863 return Some(0);
1864 }
1865 if matrix.is_empty() {
1867 return Some(0);
1868 }
1869 let idx = c1.checked_mul(c2_count)?.checked_add(c2)?;
1870 Some(matrix.get(idx).copied().unwrap_or(0))
1873 }
1874 }
1875 }
1876}
1877
1878#[derive(Clone, Debug, Default)]
1884pub struct Kerning {
1885 subtables: Vec<KernSubtable>,
1886}
1887
1888impl Kerning {
1889 #[must_use]
1893 pub fn pair(&self, left: u16, right: u16) -> i16 {
1894 for st in &self.subtables {
1895 if let Some(v) = st.lookup(left, right) {
1896 return v;
1897 }
1898 }
1899 0
1900 }
1901
1902 pub fn for_each_ascii_pair(
1919 &self,
1920 glyph_of: impl Fn(u8) -> u16,
1921 mut emit: impl FnMut(u8, u8, i16),
1922 ) {
1923 let glyphs: [u16; 128] = std::array::from_fn(|b| glyph_of(b as u8));
1924 let mut by_glyph: Vec<(u16, u8)> = glyphs
1927 .iter()
1928 .enumerate()
1929 .map(|(b, &g)| (g, b as u8))
1930 .collect();
1931 by_glyph.sort_unstable();
1932
1933 let mut defined = [[false; 128]; 128];
1936
1937 for st in &self.subtables {
1938 match st {
1939 KernSubtable::Format1 { pairs } => {
1940 for (&key, &v) in pairs {
1941 let left = (key >> 16) as u16;
1942 let right = (key & 0xFFFF) as u16;
1943 let ls = by_glyph.partition_point(|&(g, _)| g < left);
1944 let le = by_glyph.partition_point(|&(g, _)| g <= left);
1945 let rs = by_glyph.partition_point(|&(g, _)| g < right);
1946 let re = by_glyph.partition_point(|&(g, _)| g <= right);
1947 for &(_, l) in &by_glyph[ls..le] {
1948 for &(_, r) in &by_glyph[rs..re] {
1949 let row = &mut defined[usize::from(l)];
1950 if row[usize::from(r)] {
1951 continue;
1952 }
1953 row[usize::from(r)] = true;
1954 if v != 0 {
1955 emit(l, r, v);
1956 }
1957 }
1958 }
1959 }
1960 }
1961 KernSubtable::Format2 {
1962 coverage,
1963 class1,
1964 class2,
1965 class1_count,
1966 class2_count,
1967 matrix,
1968 } => {
1969 let c2_of: [u16; 128] = std::array::from_fn(|b| class2.class(glyphs[b]));
1973 let c1_count = usize::from(*class1_count);
1974 let c2_count = usize::from(*class2_count);
1975 for l in 0..128u8 {
1976 if coverage.binary_search(&glyphs[usize::from(l)]).is_err() {
1977 continue;
1978 }
1979 let c1 = usize::from(class1.class(glyphs[usize::from(l)]));
1980 for r in 0..128u8 {
1981 let row = &mut defined[usize::from(l)];
1982 if row[usize::from(r)] {
1983 continue;
1984 }
1985 let value = if c1 >= c1_count
1990 || usize::from(c2_of[usize::from(r)]) >= c2_count
1991 || matrix.is_empty()
1992 {
1993 Some(0)
1994 } else {
1995 c1.checked_mul(c2_count)
1996 .and_then(|m| m.checked_add(usize::from(c2_of[usize::from(r)])))
1997 .map(|idx| matrix.get(idx).copied().unwrap_or(0))
1998 };
1999 if let Some(v) = value {
2000 row[usize::from(r)] = true;
2001 if v != 0 {
2002 emit(l, r, v);
2003 }
2004 }
2005 }
2006 }
2007 }
2008 }
2009 }
2010 }
2011}
2012
2013fn value_record_size(value_format: u16) -> usize {
2015 value_format.count_ones() as usize * 2
2016}
2017
2018fn value_record_x_advance(d: &[u8], off: usize, value_format: u16) -> Option<i16> {
2024 const X_ADVANCE: u16 = 0x0004;
2025 if value_format & X_ADVANCE == 0 {
2026 return Some(0);
2027 }
2028 let skip = (value_format & 0x0003).count_ones() as usize * 2;
2029 be_i16(d, off.checked_add(skip)?)
2030}
2031
2032fn parse_coverage_glyphs(d: &[u8], cov: usize) -> Option<Vec<u16>> {
2035 let format = be_u16(d, cov)?;
2036 match format {
2037 1 => {
2038 let count = be_u16_at(d, cov, 2)? as usize;
2039 let mut v = Vec::with_capacity(count.min(d.len() / 2 + 1));
2040 for i in 0..count {
2041 v.push(be_u16(d, off_mul(off(cov, 4)?, i, 2)?)?);
2042 }
2043 Some(v)
2044 }
2045 2 => {
2046 let range_count = be_u16_at(d, cov, 2)? as usize;
2047 let mut by_index: std::collections::BTreeMap<u32, u16> =
2050 std::collections::BTreeMap::new();
2051 let mut total: usize = 0;
2057 for i in 0..range_count {
2058 let rec = off_mul(off(cov, 4)?, i, 6)?;
2059 let start = be_u16(d, rec)? as u32;
2060 let end = be_u16_at(d, rec, 2)? as u32;
2061 let start_idx = be_u16_at(d, rec, 4)? as u32;
2062 if end < start {
2063 continue;
2064 }
2065 total = total.checked_add((end - start + 1) as usize)?;
2066 if total > MAX_COVERAGE_GLYPHS {
2067 return None;
2068 }
2069 let mut g = start;
2070 let mut idx = start_idx;
2071 while g <= end {
2072 by_index.insert(idx, g as u16);
2073 g += 1;
2074 idx += 1;
2075 }
2076 }
2077 Some(by_index.into_values().collect())
2078 }
2079 _ => None,
2080 }
2081}
2082
2083fn parse_class_def(d: &[u8], cd: usize) -> Option<ClassDef> {
2085 let format = be_u16(d, cd)?;
2086 match format {
2087 1 => {
2088 let start = be_u16_at(d, cd, 2)?;
2089 let count = be_u16_at(d, cd, 4)? as usize;
2090 let mut classes = Vec::with_capacity(count.min(d.len() / 2 + 1));
2091 for i in 0..count {
2092 classes.push(be_u16(d, off_mul(off(cd, 6)?, i, 2)?)?);
2093 }
2094 Some(ClassDef::Format1 { start, classes })
2095 }
2096 2 => {
2097 let range_count = be_u16_at(d, cd, 2)? as usize;
2098 let mut ranges = Vec::with_capacity(range_count.min(d.len() / 6 + 1));
2099 for i in 0..range_count {
2100 let rec = off_mul(off(cd, 4)?, i, 6)?;
2101 let s = be_u16(d, rec)?;
2102 let e = be_u16_at(d, rec, 2)?;
2103 let c = be_u16_at(d, rec, 4)?;
2104 ranges.push((s, e, c));
2105 }
2106 ranges.sort_by_key(|&(s, _, _)| s);
2112 let dense = ranges
2113 .windows(2)
2114 .all(|w| w[0].1 < w[1].0 || (w[0].1 == w[1].0 && w[0].2 == w[1].2));
2115 Some(ClassDef::Format2 { ranges, dense })
2116 }
2117 _ => None,
2118 }
2119}
2120
2121fn parse_pair_subtable(d: &[u8], sub: usize) -> Option<KernSubtable> {
2123 let pos_format = be_u16(d, sub)?;
2124 match pos_format {
2125 1 => parse_pair_format1(d, sub),
2126 2 => parse_pair_format2(d, sub),
2127 _ => None,
2128 }
2129}
2130
2131fn parse_pair_format1(d: &[u8], sub: usize) -> Option<KernSubtable> {
2133 let cov_off = be_u16_at(d, sub, 2)? as usize;
2134 let vf1 = be_u16_at(d, sub, 4)?;
2135 let vf2 = be_u16_at(d, sub, 6)?;
2136 let pair_set_count = be_u16_at(d, sub, 8)? as usize;
2137
2138 let rec1_size = value_record_size(vf1);
2139 let rec2_size = value_record_size(vf2);
2140 let pair_rec_size = off(2, off(rec1_size, rec2_size)?)?;
2142
2143 let coverage = parse_coverage_glyphs(d, off(sub, cov_off)?)?;
2144
2145 let mut pairs: PairMap = std::collections::HashMap::default();
2146
2147 let mut work: usize = 0;
2151 for i in 0..pair_set_count {
2152 work += 1;
2153 if work > MAX_LAYOUT_GLYPHS {
2154 break;
2155 }
2156 let Some(left_glyph) = coverage.get(i).copied() else {
2158 continue;
2159 };
2160 let Some(ps_off) = off_mul(off(sub, 10)?, i, 2).and_then(|slot| be_u16(d, slot)) else {
2161 continue;
2162 };
2163 let Some(ps) = off(sub, ps_off as usize) else {
2164 continue;
2165 };
2166 let Some(pair_value_count) = be_u16(d, ps) else {
2167 continue;
2168 };
2169 let Some(mut p) = off(ps, 2) else {
2170 continue;
2171 };
2172 for _ in 0..pair_value_count {
2173 work += 1;
2174 if work > MAX_LAYOUT_GLYPHS {
2175 break;
2176 }
2177 let Some(second) = be_u16(d, p) else {
2178 break;
2179 };
2180 let x_adv = off(p, 2)
2181 .and_then(|value_off| value_record_x_advance(d, value_off, vf1))
2182 .unwrap_or(0);
2183 pairs.entry(pair_key(left_glyph, second)).or_insert(x_adv);
2185 let Some(np) = p.checked_add(pair_rec_size) else {
2186 break;
2187 };
2188 p = np;
2189 }
2190 }
2191
2192 Some(KernSubtable::Format1 { pairs })
2193}
2194
2195fn parse_pair_format2(d: &[u8], sub: usize) -> Option<KernSubtable> {
2197 let cov_off = be_u16_at(d, sub, 2)? as usize;
2198 let vf1 = be_u16_at(d, sub, 4)?;
2199 let vf2 = be_u16_at(d, sub, 6)?;
2200 let class_def1_off = be_u16_at(d, sub, 8)? as usize;
2201 let class_def2_off = be_u16_at(d, sub, 10)? as usize;
2202 let class1_count = be_u16_at(d, sub, 12)? as usize;
2203 let class2_count = be_u16_at(d, sub, 14)? as usize;
2204
2205 let rec1_size = value_record_size(vf1);
2206 let rec2_size = value_record_size(vf2);
2207 let class_rec_size = off(rec1_size, rec2_size)?;
2208
2209 let matrix_base = off(sub, 16)?;
2211 let cell_count = class1_count.checked_mul(class2_count)?;
2212
2213 let matrix: Vec<i16> = if class_rec_size == 0 {
2216 Vec::new()
2218 } else {
2219 let needed = cell_count.checked_mul(class_rec_size)?;
2220 let end = matrix_base.checked_add(needed)?;
2221 if end > d.len() {
2222 return None;
2224 }
2225 let mut m = Vec::with_capacity(cell_count);
2226 for idx in 0..cell_count {
2227 let cell = off_mul(matrix_base, idx, class_rec_size)?;
2228 let x_adv = value_record_x_advance(d, cell, vf1).unwrap_or(0);
2230 m.push(x_adv);
2231 }
2232 m
2233 };
2234
2235 let mut coverage = parse_coverage_glyphs(d, off(sub, cov_off)?)?;
2236 coverage.sort_unstable();
2237
2238 let class1 = parse_class_def(d, off(sub, class_def1_off)?)?;
2239 let class2 = parse_class_def(d, off(sub, class_def2_off)?)?;
2240
2241 Some(KernSubtable::Format2 {
2242 coverage,
2243 class1,
2244 class2,
2245 class1_count: class1_count as u16,
2246 class2_count: class2_count as u16,
2247 matrix,
2248 })
2249}
2250
2251fn resolve_extension(d: &[u8], sub: usize) -> Option<(u16, usize)> {
2254 let pos_format = be_u16(d, sub)?;
2255 if pos_format != 1 {
2256 return None;
2257 }
2258 let ext_type = be_u16_at(d, sub, 2)?;
2259 let ext_off = be_u32_at(d, sub, 4)? as usize;
2260 Some((ext_type, sub.checked_add(ext_off)?))
2261}
2262
2263impl Font {
2264 #[must_use]
2269 pub fn gpos_kerning(&self) -> Kerning {
2270 self.parse_gpos_kerning().unwrap_or_default()
2271 }
2272
2273 fn parse_gpos_kerning(&self) -> Option<Kerning> {
2274 let d = &self.data;
2275 let (gpos, _gpos_len) = find_table_full(d, b"GPOS")?;
2276
2277 let feature_list_off = be_u16_at(d, gpos, 6)? as usize;
2279 let lookup_list_off = be_u16_at(d, gpos, 8)? as usize;
2280 let feature_list = off(gpos, feature_list_off)?;
2281 let lookup_list = off(gpos, lookup_list_off)?;
2282
2283 let feature_count = be_u16(d, feature_list)? as usize;
2285 let mut lookup_indices: Vec<u16> = Vec::new();
2286 for i in 0..feature_count {
2287 let rec = off_mul(off(feature_list, 2)?, i, 6)?;
2289 let Some(tag) = bytes_at(d, rec, 4) else {
2290 break;
2291 };
2292 if tag != b"kern" {
2293 continue;
2294 }
2295 let Some(feat_off) = be_u16_at(d, rec, 4) else {
2296 continue;
2297 };
2298 let Some(feat) = off(feature_list, feat_off as usize) else {
2299 continue;
2300 };
2301 let Some(lookup_index_count) = be_u16_at(d, feat, 2) else {
2303 continue;
2304 };
2305 for j in 0..lookup_index_count as usize {
2306 if let Some(idx) = off_mul(off(feat, 4)?, j, 2).and_then(|slot| be_u16(d, slot)) {
2307 if !lookup_indices.contains(&idx) {
2308 lookup_indices.push(idx);
2309 }
2310 }
2311 }
2312 }
2313
2314 let lookup_count = be_u16(d, lookup_list)? as usize;
2316 let mut subtables: Vec<KernSubtable> = Vec::new();
2317
2318 for &li in &lookup_indices {
2319 let li = li as usize;
2320 if li >= lookup_count {
2321 continue;
2322 }
2323 let Some(lookup_off) =
2324 off_mul(off(lookup_list, 2)?, li, 2).and_then(|slot| be_u16(d, slot))
2325 else {
2326 continue;
2327 };
2328 let Some(lookup) = off(lookup_list, lookup_off as usize) else {
2329 continue;
2330 };
2331 let Some(lookup_type) = be_u16(d, lookup) else {
2333 continue;
2334 };
2335 let Some(sub_count) = be_u16_at(d, lookup, 4) else {
2336 continue;
2337 };
2338
2339 for s in 0..sub_count as usize {
2340 let Some(sub_off) = off_mul(off(lookup, 6)?, s, 2).and_then(|slot| be_u16(d, slot))
2341 else {
2342 continue;
2343 };
2344 let Some(sub) = off(lookup, sub_off as usize) else {
2345 continue;
2346 };
2347
2348 match lookup_type {
2349 2 => {
2350 if let Some(st) = parse_pair_subtable(d, sub) {
2351 subtables.push(st);
2352 }
2353 }
2354 9 => {
2355 if let Some((ext_type, real_sub)) = resolve_extension(d, sub) {
2357 if ext_type == 2 {
2358 if let Some(st) = parse_pair_subtable(d, real_sub) {
2359 subtables.push(st);
2360 }
2361 }
2362 }
2363 }
2364 _ => {}
2365 }
2366 }
2367 }
2368
2369 Some(Kerning { subtables })
2370 }
2371}
2372
2373#[derive(Clone, Debug)]
2381struct LigRule {
2382 components: Vec<u16>,
2383 ligature: u16,
2384}
2385
2386#[derive(Clone, Debug, Default)]
2389pub struct Ligatures {
2390 rules: std::collections::BTreeMap<u16, Vec<LigRule>>,
2392}
2393
2394impl Ligatures {
2395 #[must_use]
2397 pub fn is_empty(&self) -> bool {
2398 self.rules.is_empty()
2399 }
2400
2401 pub fn rule_start_glyphs(&self) -> impl Iterator<Item = &u16> {
2406 self.rules.keys()
2407 }
2408
2409 #[must_use]
2414 pub fn max_rule_len(&self) -> usize {
2415 self.rules
2416 .values()
2417 .flat_map(|rules| rules.iter().map(|r| r.components.len() + 1))
2418 .max()
2419 .unwrap_or(1)
2420 }
2421
2422 #[must_use]
2426 pub fn substitute(&self, gids: &[u16]) -> Vec<u16> {
2427 self.substitute_with_spans(gids)
2428 .into_iter()
2429 .map(|(g, _)| g)
2430 .collect()
2431 }
2432
2433 #[must_use]
2438 pub fn substitute_with_spans(&self, gids: &[u16]) -> Vec<(u16, usize)> {
2439 let mut out = Vec::with_capacity(gids.len());
2440 self.substitute_with_spans_into(gids, &mut out);
2441 out
2442 }
2443
2444 pub fn substitute_with_spans_into(&self, gids: &[u16], out: &mut Vec<(u16, usize)>) {
2449 out.clear();
2450 out.reserve(gids.len());
2451 let mut i = 0;
2452 while i < gids.len() {
2453 let mut applied = false;
2454 if let Some(rules) = self.rules.get(&gids[i]) {
2455 for r in rules {
2456 let n = r.components.len();
2457 if i + 1 + n <= gids.len() && gids[i + 1..i + 1 + n] == r.components[..] {
2458 out.push((r.ligature, n + 1));
2459 i += n + 1;
2460 applied = true;
2461 break;
2462 }
2463 }
2464 }
2465 if !applied {
2466 out.push((gids[i], 1));
2467 i += 1;
2468 }
2469 }
2470 }
2471}
2472
2473impl Font {
2474 #[must_use]
2479 pub fn gsub_ligatures(&self) -> Ligatures {
2480 self.parse_gsub_ligatures().unwrap_or_default()
2481 }
2482
2483 fn parse_gsub_ligatures(&self) -> Option<Ligatures> {
2484 let d = &self.data;
2485 let (gsub, _) = find_table_full(d, b"GSUB")?;
2486 let feature_list = off(gsub, be_u16_at(d, gsub, 6)? as usize)?;
2487 let lookup_list = off(gsub, be_u16_at(d, gsub, 8)? as usize)?;
2488
2489 let feature_count = be_u16(d, feature_list)? as usize;
2491 let mut lookup_indices: Vec<u16> = Vec::new();
2492 for i in 0..feature_count {
2493 let rec = off_mul(off(feature_list, 2)?, i, 6)?;
2494 let Some(tag) = bytes_at(d, rec, 4) else {
2495 break;
2496 };
2497 if tag != b"liga" {
2498 continue;
2499 }
2500 let Some(feat_off) = be_u16_at(d, rec, 4) else {
2501 continue;
2502 };
2503 let Some(feat) = off(feature_list, feat_off as usize) else {
2504 continue;
2505 };
2506 let Some(n) = be_u16_at(d, feat, 2) else {
2507 continue;
2508 };
2509 for j in 0..n as usize {
2510 if let Some(idx) = off_mul(off(feat, 4)?, j, 2).and_then(|slot| be_u16(d, slot)) {
2511 if !lookup_indices.contains(&idx) {
2512 lookup_indices.push(idx);
2513 }
2514 }
2515 }
2516 }
2517
2518 let lookup_count = be_u16(d, lookup_list)? as usize;
2519 let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
2520 std::collections::BTreeMap::new();
2521 for &li in &lookup_indices {
2522 let li = li as usize;
2523 if li >= lookup_count {
2524 continue;
2525 }
2526 let Some(lookup_off) =
2527 off_mul(off(lookup_list, 2)?, li, 2).and_then(|slot| be_u16(d, slot))
2528 else {
2529 continue;
2530 };
2531 let Some(lookup) = off(lookup_list, lookup_off as usize) else {
2532 continue;
2533 };
2534 let Some(lookup_type) = be_u16(d, lookup) else {
2535 continue;
2536 };
2537 let Some(sub_count) = be_u16_at(d, lookup, 4) else {
2538 continue;
2539 };
2540 for s in 0..sub_count as usize {
2541 let Some(sub_off) = off_mul(off(lookup, 6)?, s, 2).and_then(|slot| be_u16(d, slot))
2542 else {
2543 continue;
2544 };
2545 let Some(sub) = off(lookup, sub_off as usize) else {
2546 continue;
2547 };
2548 match lookup_type {
2549 4 => parse_ligature_subst(d, sub, &mut rules),
2550 7 => {
2552 if let Some((ext_type, real)) = resolve_extension(d, sub) {
2553 if ext_type == 4 {
2554 parse_ligature_subst(d, real, &mut rules);
2555 }
2556 }
2557 }
2558 _ => {}
2559 }
2560 }
2561 }
2562 for v in rules.values_mut() {
2564 v.sort_by_key(|r| std::cmp::Reverse(r.components.len()));
2565 }
2566 Some(Ligatures { rules })
2567 }
2568}
2569
2570fn parse_ligature_subst(
2572 d: &[u8],
2573 sub: usize,
2574 rules: &mut std::collections::BTreeMap<u16, Vec<LigRule>>,
2575) {
2576 let Some(format) = be_u16(d, sub) else {
2577 return;
2578 };
2579 if format != 1 {
2580 return;
2581 }
2582 let Some(cov_off) = be_u16_at(d, sub, 2) else {
2583 return;
2584 };
2585 let Some(set_count) = be_u16_at(d, sub, 4) else {
2586 return;
2587 };
2588 let Some(coverage) = off(sub, cov_off as usize).and_then(|cov| parse_coverage_glyphs(d, cov))
2589 else {
2590 return;
2591 };
2592 let Some(set_offsets) = off(sub, 6) else {
2593 return;
2594 };
2595 let mut work: usize = 0;
2600 for i in 0..set_count as usize {
2601 work += 1;
2602 if work > MAX_LAYOUT_GLYPHS {
2603 return;
2604 }
2605 let Some(first) = coverage.get(i).copied() else {
2607 continue;
2608 };
2609 let Some(set_off) = off_mul(set_offsets, i, 2).and_then(|slot| be_u16(d, slot)) else {
2610 continue;
2611 };
2612 let Some(lig_set) = off(sub, set_off as usize) else {
2613 continue;
2614 };
2615 let Some(lig_count) = be_u16(d, lig_set) else {
2616 continue;
2617 };
2618 let Some(lig_offsets) = off(lig_set, 2) else {
2619 continue;
2620 };
2621 for j in 0..lig_count as usize {
2622 work += 1;
2623 if work > MAX_LAYOUT_GLYPHS {
2624 return;
2625 }
2626 let Some(lig_off) = off_mul(lig_offsets, j, 2).and_then(|slot| be_u16(d, slot)) else {
2627 continue;
2628 };
2629 let Some(lig) = off(lig_set, lig_off as usize) else {
2630 continue;
2631 };
2632 let Some(lig_glyph) = be_u16(d, lig) else {
2633 continue;
2634 };
2635 let Some(comp_count) = be_u16_at(d, lig, 2) else {
2636 continue;
2637 };
2638 if comp_count == 0 {
2639 continue;
2640 }
2641 let mut components = Vec::with_capacity(comp_count as usize - 1);
2643 let mut ok = true;
2644 let Some(component_base) = off(lig, 4) else {
2645 continue;
2646 };
2647 for k in 0..(comp_count as usize - 1) {
2648 match off_mul(component_base, k, 2).and_then(|slot| be_u16(d, slot)) {
2649 Some(g) => components.push(g),
2650 None => {
2651 ok = false;
2652 break;
2653 }
2654 }
2655 }
2656 if ok {
2657 rules.entry(first).or_default().push(LigRule {
2658 components,
2659 ligature: lig_glyph,
2660 });
2661 }
2662 }
2663 }
2664}
2665
2666#[cfg(test)]
2667#[cfg_attr(coverage_nightly, coverage(off))]
2668#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
2669mod dos_tests {
2670 use super::{MAX_COVERAGE_GLYPHS, parse_coverage_glyphs};
2671
2672 fn be(v: u16) -> [u8; 2] {
2673 v.to_be_bytes()
2674 }
2675
2676 #[test]
2677 fn coverage_format2_valid_range_expands() {
2678 let mut d = Vec::new();
2680 d.extend_from_slice(&be(2));
2681 d.extend_from_slice(&be(1));
2682 d.extend_from_slice(&be(10)); d.extend_from_slice(&be(20)); d.extend_from_slice(&be(0)); let got = parse_coverage_glyphs(&d, 0).unwrap();
2686 assert_eq!(got, (10u16..=20).collect::<Vec<_>>());
2687 }
2688
2689 #[test]
2690 fn coverage_format2_overclaiming_table_is_rejected_not_expanded() {
2691 let mut d = Vec::new();
2694 d.extend_from_slice(&be(2));
2695 d.extend_from_slice(&be(2)); for _ in 0..2 {
2697 d.extend_from_slice(&be(0)); d.extend_from_slice(&be(0xFFFF)); d.extend_from_slice(&be(0)); }
2701 assert!(parse_coverage_glyphs(&d, 0).is_none());
2702 assert_eq!(MAX_COVERAGE_GLYPHS, 65_536);
2704 }
2705}
2706
2707#[cfg(test)]
2708#[cfg_attr(coverage_nightly, coverage(off))]
2709#[allow(clippy::unwrap_used, clippy::expect_used)]
2710mod subset_degradation_tests {
2711 use super::{
2712 Font, MISSING_GLYPH_REMAP, be_i16, be_u16, find_table_full, strip_simple_glyph_instructions,
2713 };
2714
2715 fn cm_regular() -> Font {
2717 let bytes = std::fs::read(concat!(
2718 env!("CARGO_MANIFEST_DIR"),
2719 "/fonts/computer-modern/cmunrm.ttf"
2720 ))
2721 .expect("read bundled font");
2722 Font::parse(bytes).expect("parse bundled font")
2723 }
2724
2725 fn all_faces() -> Vec<Font> {
2726 let base = env!("CARGO_MANIFEST_DIR");
2727 [
2728 "/fonts/computer-modern/cmunrm.ttf",
2729 "/fonts/computer-modern/cmunbx.ttf",
2730 "/fonts/computer-modern/cmunti.ttf",
2731 "/fonts/computer-modern/cmunbi.ttf",
2732 "/fonts/computer-modern/cmuntt.ttf",
2733 "/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf",
2734 "/fonts/ibm-plex-sans/IBMPlexSans-Bold.ttf",
2735 "/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf",
2736 "/fonts/ibm-plex-sans/IBMPlexSans-BoldItalic.ttf",
2737 ]
2738 .iter()
2739 .filter_map(|p| Font::parse(std::fs::read(format!("{base}{p}")).ok()?).ok())
2740 .collect()
2741 }
2742
2743 fn test_remap(font: &Font, pairs: &[(u16, u16)]) -> Vec<u16> {
2744 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs).max(1)];
2745 for &(old, new) in pairs {
2746 if let Some(slot) = new_of.get_mut(usize::from(old)) {
2747 *slot = new;
2748 }
2749 }
2750 new_of
2751 }
2752
2753 #[test]
2759 fn subset_glyphs_with_lookup_matches_btreemap_remap() {
2760 fn assert_agree(font: &Font, glyphs: &[u16], cmap_chars: &[char]) {
2761 let (map_bytes, remap) = font
2762 .subset_glyphs(glyphs, cmap_chars)
2763 .expect("map-path subset");
2764 let (dense_bytes, lookup) = font
2765 .subset_glyphs_with_lookup(glyphs, cmap_chars)
2766 .expect("dense-path subset");
2767 assert_eq!(map_bytes, dense_bytes, "font bytes must be identical");
2768 assert_eq!(
2769 lookup.len(),
2770 usize::from(font.num_glyphs).max(1),
2771 "dense lookup covers every source glyph"
2772 );
2773 let mut mapped = 0usize;
2774 for (old, new) in lookup.iter().enumerate() {
2775 if *new == MISSING_GLYPH_REMAP {
2776 assert!(
2777 !remap.contains_key(&(old as u16)),
2778 "dense sentinel at {old} must be absent from the map"
2779 );
2780 } else {
2781 mapped += 1;
2782 assert_eq!(
2783 remap.get(&(old as u16)).copied(),
2784 Some(*new),
2785 "dense entry {old} -> {new} must match the map"
2786 );
2787 }
2788 }
2789 assert_eq!(
2790 remap.len(),
2791 mapped,
2792 "map and dense table cover the same glyphs"
2793 );
2794 let dense_pairs: Vec<(u16, u16)> = lookup
2798 .iter()
2799 .enumerate()
2800 .filter(|&(_, &v)| v != MISSING_GLYPH_REMAP)
2801 .map(|(old, &v)| (old as u16, v))
2802 .collect();
2803 let map_pairs: Vec<(u16, u16)> = remap.iter().map(|(&k, &v)| (k, v)).collect();
2804 assert_eq!(dense_pairs, map_pairs);
2805 }
2806
2807 for font in all_faces() {
2808 let a = font.glyph_index('A');
2809 let b = font.glyph_index('B');
2810 let q = font.glyph_index('Q');
2811 let composite =
2814 (0..font.num_glyphs).find(|&g| font.is_composite(g) && g != a && g != b && g != q);
2815 assert_agree(&font, &[], &[]);
2817 assert_agree(&font, &[], &['A', '\u{1D49C}']);
2819 assert_agree(&font, &[0], &[]);
2821 assert_agree(&font, &[a, b, q], &['A', 'B', 'Q']);
2823 let over = font.num_glyphs.saturating_add(3);
2826 assert_agree(
2827 &font,
2828 &[over, q, 0, u16::MAX, b, a, a, over],
2829 &['A', 'B', 'Q'],
2830 );
2831 if let Some(comp) = composite {
2832 assert_agree(&font, &[comp], &[]);
2833 let (bytes, _) = font.subset_glyphs(&[comp], &[]).expect("composite subset");
2834 let sub = Font::parse(bytes).expect("composite subset re-parses");
2835 assert!(
2836 sub.num_glyphs > 2,
2837 "closure must have pulled components beyond .notdef + the composite"
2838 );
2839 }
2840 }
2841 }
2842
2843 fn simple_instruction_len(data: &[u8]) -> Option<usize> {
2844 let contours = be_i16(data, 0)?;
2845 if contours < 0 {
2846 return None;
2847 }
2848 let instruction_len_offset = 10usize.checked_add((contours as usize).checked_mul(2)?)?;
2849 be_u16(data, instruction_len_offset).map(usize::from)
2850 }
2851
2852 #[test]
2853 fn simple_glyph_instruction_stripper_zeroes_length_and_removes_bytes() {
2854 let mut glyph = Vec::new();
2855 glyph.extend_from_slice(&1i16.to_be_bytes()); glyph.extend_from_slice(&[0u8; 8]); glyph.extend_from_slice(&0u16.to_be_bytes()); glyph.extend_from_slice(&3u16.to_be_bytes()); glyph.extend_from_slice(&[0xAA, 0xBB, 0xCC]); glyph.extend_from_slice(&[0x11, 0x22, 0x33]); let stripped = strip_simple_glyph_instructions(&glyph, 1).expect("valid simple glyph");
2863 assert_eq!(simple_instruction_len(&stripped), Some(0));
2864 assert_eq!(stripped.len(), glyph.len() - 3);
2865 assert_eq!(&stripped[stripped.len() - 3..], &[0x11, 0x22, 0x33]);
2866 }
2867
2868 #[test]
2869 fn subset_glyph_bytes_strips_simple_instructions_when_present() {
2870 let Some((font, gid, original_len)) = all_faces().into_iter().find_map(|font| {
2871 (1..font.num_glyphs).find_map(|gid| {
2872 let data = font.glyph_data(gid)?;
2873 let len = simple_instruction_len(data)?;
2874 (len > 0).then_some((font.clone(), gid, len))
2875 })
2876 }) else {
2877 eprintln!("skipping: bundled fonts have no hinted simple glyphs");
2878 return;
2879 };
2880
2881 let new_of = test_remap(&font, &[(0u16, 0u16), (gid, 1u16)]);
2882
2883 let stripped = font
2884 .subset_glyph_bytes(gid, &new_of)
2885 .expect("hinted simple glyph should subset");
2886 assert_eq!(simple_instruction_len(&stripped), Some(0));
2887 assert_eq!(
2888 stripped.len(),
2889 font.glyph_data(gid).expect("original glyph").len() - original_len
2890 );
2891 }
2892
2893 #[test]
2894 fn subset_hmtx_preserves_true_left_side_bearings() {
2895 let (font, ch, old_gid, old_lsb) = all_faces()
2896 .into_iter()
2897 .find_map(|font| {
2898 (33u8..=126).find_map(|byte| {
2899 let ch = char::from(byte);
2900 let gid = font.glyph_index(ch);
2901 let lsb = font.left_side_bearing(gid);
2902 (gid != 0 && lsb != 0).then_some((font.clone(), ch, gid, lsb))
2903 })
2904 })
2905 .expect("at least one bundled printable glyph has a nonzero lsb");
2906
2907 let (bytes, remap) = font
2908 .subset_glyphs(&[old_gid], &[ch])
2909 .expect("subset with nonzero-lsb glyph");
2910 let subset = Font::parse(bytes).expect("subset re-parses");
2911 let new_gid = remap[&old_gid];
2912 assert_eq!(subset.left_side_bearing(new_gid), old_lsb);
2913 }
2914
2915 #[test]
2916 fn html_subset_carries_verbatim_os2_while_pdf_subset_stays_lean() {
2917 for font in all_faces() {
2923 let (src_off, src_len) = find_table_full(&font.data, b"OS/2")
2924 .expect("every bundled face carries an OS/2 table");
2925 let src_os2 = font.data[src_off..src_off + src_len].to_vec();
2926
2927 let html_bytes = font.subset(&['A', 'b']).expect("html subset");
2928 let html_font = Font::parse(html_bytes).expect("html subset re-parses");
2929 let (o, l) = find_table_full(&html_font.data, b"OS/2")
2930 .expect("html subset must keep OS/2 for browser sanitizers");
2931 assert_eq!(
2932 &html_font.data[o..o + l],
2933 &src_os2[..],
2934 "OS/2 must be copied verbatim"
2935 );
2936
2937 let gid = font.glyph_index('A');
2938 assert_ne!(gid, 0, "bundled faces must map 'A'");
2939 let (pdf_bytes, _) = font.subset_glyphs(&[gid], &['A']).expect("pdf subset");
2940 assert!(
2941 find_table_full(&pdf_bytes, b"OS/2").is_none(),
2942 "pdf subset must not grow an OS/2 table (golden bytes)"
2943 );
2944 assert!(Font::parse(pdf_bytes).is_ok());
2945 }
2946 }
2947
2948 #[test]
2949 fn subset_skips_cmap_char_whose_glyph_is_absent_from_the_set() {
2950 let font = cm_regular();
2954 let g_b = font.glyph_index('B');
2955 assert_ne!(g_b, 0, "test font must map 'B'");
2956 let out = font.subset_glyphs(&[g_b], &['A', 'B']);
2958 let (bytes, _) = out.expect("un-subsettable cmap char must be skipped, not abort");
2959 assert!(Font::parse(bytes).is_ok());
2961 }
2962
2963 #[test]
2964 fn simple_instruction_len_rejects_composite_data() {
2965 assert_eq!(simple_instruction_len(&(-1i16).to_be_bytes()), None);
2968 }
2969
2970 #[test]
2971 fn subset_glyph_bytes_substitutes_notdef_for_a_missing_component() {
2972 let (font, comp) = all_faces()
2975 .into_iter()
2976 .find_map(|f| {
2977 (1..f.num_glyphs)
2978 .find(|&g| f.is_composite(g))
2979 .map(|g| (f, g))
2980 })
2981 .expect("at least one bundled face has a composite glyph");
2982 let new_of = test_remap(&font, &[(0u16, 0u16), (comp, 1u16)]);
2985 let bytes = font
2986 .subset_glyph_bytes(comp, &new_of)
2987 .expect("missing component must be substituted, not abort");
2988 assert!(!bytes.is_empty(), "a composite glyph is non-empty");
2989 }
2990}
2991
2992#[cfg(test)]
2993#[cfg_attr(coverage_nightly, coverage(off))]
2994#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
2995mod synthetic_font_tests {
2996 use super::*;
2997
2998 fn push16(out: &mut Vec<u8>, v: u16) {
3001 out.extend_from_slice(&v.to_be_bytes());
3002 }
3003
3004 fn push_i16(out: &mut Vec<u8>, v: i16) {
3005 out.extend_from_slice(&v.to_be_bytes());
3006 }
3007
3008 fn push32(out: &mut Vec<u8>, v: u32) {
3009 out.extend_from_slice(&v.to_be_bytes());
3010 }
3011
3012 fn sfnt(magic: u32, tables: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
3017 let mut out = Vec::new();
3018 push32(&mut out, magic);
3019 push16(&mut out, u16::try_from(tables.len()).unwrap());
3020 out.extend_from_slice(&[0u8; 6]); let mut offset = 12 + tables.len() * 16;
3022 let mut body = Vec::new();
3023 for (tag, bytes) in tables {
3024 out.extend_from_slice(&tag[..]);
3025 push32(&mut out, 0); push32(&mut out, u32::try_from(offset).unwrap());
3027 push32(&mut out, u32::try_from(bytes.len()).unwrap());
3028 offset += bytes.len();
3029 body.extend_from_slice(bytes);
3030 }
3031 out.extend_from_slice(&body);
3032 out
3033 }
3034
3035 fn head_table(upem: u16, loca_long: bool) -> Vec<u8> {
3036 let mut t = vec![0u8; 54];
3037 t[18..20].copy_from_slice(&upem.to_be_bytes());
3038 t[50..52].copy_from_slice(&u16::from(loca_long).to_be_bytes());
3039 t
3040 }
3041
3042 fn maxp_table(num_glyphs: u16) -> Vec<u8> {
3043 let mut t = vec![0u8; 6];
3044 t[4..6].copy_from_slice(&num_glyphs.to_be_bytes());
3045 t
3046 }
3047
3048 fn hhea_table(num_h_metrics: u16) -> Vec<u8> {
3049 let mut t = vec![0u8; 36];
3050 t[4..6].copy_from_slice(&700i16.to_be_bytes());
3051 t[6..8].copy_from_slice(&(-200i16).to_be_bytes());
3052 t[8..10].copy_from_slice(&50i16.to_be_bytes());
3053 t[34..36].copy_from_slice(&num_h_metrics.to_be_bytes());
3054 t
3055 }
3056
3057 fn hmtx_long(metrics: &[(u16, i16)]) -> Vec<u8> {
3058 let mut t = Vec::new();
3059 for &(aw, lsb) in metrics {
3060 push16(&mut t, aw);
3061 push_i16(&mut t, lsb);
3062 }
3063 t
3064 }
3065
3066 fn cmap4_table(segs: &[(u16, u16, u16, u16)], glyph_id_array: &[u8]) -> Vec<u8> {
3070 let seg_count = segs.len();
3071 let mut t = Vec::new();
3072 push16(&mut t, 0); push16(&mut t, 1); push16(&mut t, 3); push16(&mut t, 1); push32(&mut t, 12); push16(&mut t, 4); push16(
3079 &mut t,
3080 u16::try_from(16 + seg_count * 8 + glyph_id_array.len()).unwrap(),
3081 );
3082 push16(&mut t, 0); push16(&mut t, u16::try_from(seg_count * 2).unwrap()); push16(&mut t, 0); push16(&mut t, 0); push16(&mut t, 0); for &(end, _, _, _) in segs {
3088 push16(&mut t, end);
3089 }
3090 push16(&mut t, 0); for &(_, start, _, _) in segs {
3092 push16(&mut t, start);
3093 }
3094 for &(_, _, delta, _) in segs {
3095 push16(&mut t, delta);
3096 }
3097 for &(_, _, _, iro) in segs {
3098 push16(&mut t, iro);
3099 }
3100 t.extend_from_slice(glyph_id_array);
3101 t
3102 }
3103
3104 fn cmap4_simple(map: &[(u16, u16)]) -> Vec<u8> {
3107 let mut segs: Vec<(u16, u16, u16, u16)> = map
3108 .iter()
3109 .map(|&(code, gid)| (code, code, gid.wrapping_sub(code), 0))
3110 .collect();
3111 segs.push((0xFFFF, 0xFFFF, 1, 0));
3112 cmap4_table(&segs, &[])
3113 }
3114
3115 fn cmap12_table(groups: &[(u32, u32, u32)]) -> Vec<u8> {
3117 let mut t = Vec::new();
3118 push16(&mut t, 0); push16(&mut t, 1); push16(&mut t, 3); push16(&mut t, 10); push32(&mut t, 12); push16(&mut t, 12); push16(&mut t, 0); push32(&mut t, u32::try_from(16 + groups.len() * 12).unwrap());
3126 push32(&mut t, 0); push32(&mut t, u32::try_from(groups.len()).unwrap());
3128 for &(start, end, gid) in groups {
3129 push32(&mut t, start);
3130 push32(&mut t, end);
3131 push32(&mut t, gid);
3132 }
3133 t
3134 }
3135
3136 fn base_tables(
3137 num_glyphs: u16,
3138 num_h_metrics: u16,
3139 upem: u16,
3140 hmtx: Vec<u8>,
3141 cmap: Vec<u8>,
3142 ) -> Vec<(&'static [u8; 4], Vec<u8>)> {
3143 vec![
3144 (b"head", head_table(upem, false)),
3145 (b"maxp", maxp_table(num_glyphs)),
3146 (b"hhea", hhea_table(num_h_metrics)),
3147 (b"hmtx", hmtx),
3148 (b"cmap", cmap),
3149 ]
3150 }
3151
3152 fn parse(tables: &[(&[u8; 4], Vec<u8>)]) -> Font {
3153 Font::parse(sfnt(0x0001_0000, tables)).expect("synthetic font parses")
3154 }
3155
3156 const ARGW: u16 = 0x0001; const WHS: u16 = 0x0008; const MORE: u16 = 0x0020; const XYS: u16 = 0x0040; const TWO: u16 = 0x0080; const INSTR: u16 = 0x0100; fn composite_glyph(bbox: [i16; 4], records: &[(u16, u16, &[u8])], trailer: &[u8]) -> Vec<u8> {
3168 let mut g = Vec::new();
3169 push_i16(&mut g, -1);
3170 for v in bbox {
3171 push_i16(&mut g, v);
3172 }
3173 for &(flags, gid, payload) in records {
3174 push16(&mut g, flags);
3175 push16(&mut g, gid);
3176 g.extend_from_slice(payload);
3177 }
3178 g.extend_from_slice(trailer);
3179 g
3180 }
3181
3182 fn simple_glyph16() -> Vec<u8> {
3184 let mut g = Vec::new();
3185 push_i16(&mut g, 1); g.extend_from_slice(&[0u8; 8]); push16(&mut g, 0); push16(&mut g, 0); g.extend_from_slice(&[0x01, 0x00]); g
3191 }
3192
3193 fn zoo_font() -> Font {
3200 let glyphs: Vec<Vec<u8>> = vec![
3201 Vec::new(),
3202 1i16.to_be_bytes().to_vec(),
3203 composite_glyph([1, 2, 3, 4], &[(WHS, 5, &[0, 0, 0x40, 0])], &[]),
3204 composite_glyph([0; 4], &[(XYS, 5, &[0, 0, 0x40, 0, 0x40, 0])], &[]),
3205 composite_glyph(
3206 [0; 4],
3207 &[(TWO, 5, &[0, 0, 0x40, 0, 0, 0, 0, 0, 0x40, 0])],
3208 &[],
3209 ),
3210 simple_glyph16(),
3211 composite_glyph(
3212 [0; 4],
3213 &[(ARGW | MORE, 2, &[0, 0, 0, 0]), (0, 3, &[0, 0])],
3214 &[],
3215 ),
3216 composite_glyph([0; 4], &[(MORE, 5, &[0, 0])], &[]),
3217 composite_glyph([0; 4], &[(INSTR, 5, &[0, 0])], &[0xFF, 0xFF]),
3218 composite_glyph([0; 4], &[(0, 900, &[0, 0])], &[]),
3219 composite_glyph([0; 4], &[(MORE, 5, &[0, 0])], &[0, 0]),
3220 composite_glyph([0; 4], &[(TWO, 5, &[0, 0, 0x40, 0])], &[]),
3221 composite_glyph([0; 4], &[(INSTR, 5, &[0, 0])], &[0x00, 0x02, 0xAA, 0xBB]),
3222 ];
3223 let mut glyf = Vec::new();
3224 let mut loca = Vec::new();
3225 push16(&mut loca, 0);
3226 for g in &glyphs {
3227 glyf.extend_from_slice(g);
3228 push16(&mut loca, u16::try_from(glyf.len() / 2).unwrap());
3229 }
3230 let metrics: Vec<(u16, i16)> = (0..13u16).map(|g| (500 + g, g as i16)).collect();
3231 let mut tables = base_tables(13, 13, 1000, hmtx_long(&metrics), cmap4_simple(&[]));
3232 tables.push((b"loca", loca));
3233 tables.push((b"glyf", glyf));
3234 parse(&tables)
3235 }
3236
3237 fn truncated_composite_font(keep: usize) -> Font {
3240 let glyph = composite_glyph([0; 4], &[(0, 5, &[0, 0])], &[]);
3241 assert_eq!(glyph.len(), 16);
3242 let mut loca = Vec::new();
3243 push16(&mut loca, 0);
3244 push16(&mut loca, 8);
3245 let mut tables = base_tables(1, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_simple(&[]));
3246 tables.push((b"loca", loca));
3247 tables.push((b"glyf", glyph));
3248 let mut bytes = sfnt(0x0001_0000, &tables);
3249 bytes.truncate(bytes.len() - (16 - keep));
3250 Font::parse(bytes).expect("glyf payload is lazily read")
3251 }
3252
3253 fn kern0_table(pairs: &[(u16, u16, i16)]) -> Vec<u8> {
3255 let mut t = Vec::new();
3256 push16(&mut t, 0); push16(&mut t, 1); push16(&mut t, 0); push16(&mut t, u16::try_from(14 + pairs.len() * 6).unwrap()); push16(&mut t, 0x0001); push16(&mut t, u16::try_from(pairs.len()).unwrap()); t.extend_from_slice(&[0u8; 6]); for &(l, r, v) in pairs {
3264 push16(&mut t, l);
3265 push16(&mut t, r);
3266 push_i16(&mut t, v);
3267 }
3268 t
3269 }
3270
3271 fn gpos_table(ext_format: u16, ext_type: u16, lookup_index: u16, pos_format: u16) -> Vec<u8> {
3274 let mut g = Vec::new();
3275 push32(&mut g, 0x0001_0000); push16(&mut g, 0); push16(&mut g, 10); push16(&mut g, 24); push16(&mut g, 1); g.extend_from_slice(b"kern");
3282 push16(&mut g, 8); push16(&mut g, 0); push16(&mut g, 1); push16(&mut g, lookup_index);
3287 push16(&mut g, 1); push16(&mut g, 4); push16(&mut g, 9); push16(&mut g, 0); push16(&mut g, 1); push16(&mut g, 8); push16(&mut g, ext_format);
3297 push16(&mut g, ext_type);
3298 push32(&mut g, 8); push16(&mut g, pos_format);
3301 push16(&mut g, 18); push16(&mut g, 0x0004); push16(&mut g, 0); push16(&mut g, 1); push16(&mut g, 12); push16(&mut g, 1); push16(&mut g, 6); push_i16(&mut g, -40); push16(&mut g, 1);
3312 push16(&mut g, 1);
3313 push16(&mut g, 5);
3314 assert_eq!(g.len(), 68);
3315 g
3316 }
3317
3318 fn gpos_kerning_of(table: Vec<u8>) -> Kerning {
3321 let mut tables = base_tables(
3322 2,
3323 2,
3324 1000,
3325 hmtx_long(&[(600, 0), (600, 0)]),
3326 cmap4_simple(&[]),
3327 );
3328 tables.push((b"GPOS", table));
3329 parse(&tables).gpos_kerning()
3330 }
3331
3332 fn gpos_font(ext_format: u16, ext_type: u16, lookup_index: u16, pos_format: u16) -> Kerning {
3333 gpos_kerning_of(gpos_table(ext_format, ext_type, lookup_index, pos_format))
3334 }
3335
3336 fn gsub_table(ext_format: u16, ext_type: u16, lookup_index: u16) -> Vec<u8> {
3339 let mut g = Vec::new();
3340 push32(&mut g, 0x0001_0000);
3341 push16(&mut g, 0); push16(&mut g, 10); push16(&mut g, 24); push16(&mut g, 1);
3346 g.extend_from_slice(b"liga");
3347 push16(&mut g, 8); push16(&mut g, 0);
3350 push16(&mut g, 1);
3351 push16(&mut g, lookup_index);
3352 push16(&mut g, 1);
3354 push16(&mut g, 4); push16(&mut g, 7);
3357 push16(&mut g, 0);
3358 push16(&mut g, 1);
3359 push16(&mut g, 8); push16(&mut g, ext_format);
3362 push16(&mut g, ext_type);
3363 push32(&mut g, 8); push16(&mut g, 1); push16(&mut g, 28); push16(&mut g, 1); push16(&mut g, 8); push16(&mut g, 2); push16(&mut g, 6); push16(&mut g, 14); push16(&mut g, 99);
3375 push16(&mut g, 3);
3376 push16(&mut g, 11);
3377 push16(&mut g, 12);
3378 push16(&mut g, 77);
3380 push16(&mut g, 2);
3381 push16(&mut g, 11);
3382 push16(&mut g, 1);
3384 push16(&mut g, 1);
3385 push16(&mut g, 10);
3386 assert_eq!(g.len(), 78);
3387 g
3388 }
3389
3390 fn gsub_ligatures_of(table: Vec<u8>) -> Ligatures {
3392 let mut tables = base_tables(
3393 2,
3394 2,
3395 1000,
3396 hmtx_long(&[(600, 0), (600, 0)]),
3397 cmap4_simple(&[]),
3398 );
3399 tables.push((b"GSUB", table));
3400 parse(&tables).gsub_ligatures()
3401 }
3402
3403 fn gsub_font(ext_format: u16, ext_type: u16, lookup_index: u16) -> Ligatures {
3404 gsub_ligatures_of(gsub_table(ext_format, ext_type, lookup_index))
3405 }
3406
3407 #[test]
3410 fn parse_error_variants_and_display_messages() {
3411 assert_eq!(Font::parse(Vec::new()).err(), Some(FontError::Truncated));
3412 assert_eq!(
3413 Font::parse(vec![0x00, 0x02, 0x00, 0x00]).err(),
3414 Some(FontError::BadMagic)
3415 );
3416
3417 let mut tables: Vec<(&[u8; 4], Vec<u8>)> = Vec::new();
3419 let steps: [(&'static [u8; 4], &'static str, Vec<u8>); 4] = [
3420 (b"head", "head", head_table(1000, false)),
3421 (b"maxp", "maxp", maxp_table(1)),
3422 (b"hhea", "hhea", hhea_table(1)),
3423 (b"hmtx", "hmtx", hmtx_long(&[(500, 0)])),
3424 ];
3425 for (tag, name, table) in steps {
3426 assert_eq!(
3427 Font::parse(sfnt(0x0001_0000, &tables)).err(),
3428 Some(FontError::MissingTable(name))
3429 );
3430 tables.push((tag, table));
3431 }
3432 assert_eq!(
3433 Font::parse(sfnt(0x0001_0000, &tables)).err(),
3434 Some(FontError::MissingTable("cmap"))
3435 );
3436
3437 let mut bad_cmap = Vec::new();
3440 push16(&mut bad_cmap, 0);
3441 push16(&mut bad_cmap, 2);
3442 push16(&mut bad_cmap, 1); push16(&mut bad_cmap, 0);
3444 push32(&mut bad_cmap, 20);
3445 push16(&mut bad_cmap, 3); push16(&mut bad_cmap, 1);
3447 push32(&mut bad_cmap, 20);
3448 push16(&mut bad_cmap, 6); tables.push((b"cmap", bad_cmap));
3450 assert_eq!(
3451 Font::parse(sfnt(0x0001_0000, &tables)).err(),
3452 Some(FontError::NoUnicodeCmap)
3453 );
3454
3455 let short_head: Vec<(&[u8; 4], Vec<u8>)> = vec![
3457 (b"maxp", maxp_table(1)),
3458 (b"hhea", hhea_table(1)),
3459 (b"hmtx", hmtx_long(&[(500, 0)])),
3460 (b"cmap", cmap4_simple(&[])),
3461 (b"head", vec![0u8; 10]),
3462 ];
3463 assert_eq!(
3464 Font::parse(sfnt(0x0001_0000, &short_head)).err(),
3465 Some(FontError::Truncated)
3466 );
3467
3468 assert_eq!(
3469 FontError::BadMagic.to_string(),
3470 "not a TrueType/OpenType font"
3471 );
3472 assert_eq!(
3473 FontError::MissingTable("hhea").to_string(),
3474 "missing required font table: hhea"
3475 );
3476 assert_eq!(FontError::Truncated.to_string(), "font data is truncated");
3477 assert_eq!(
3478 FontError::NoUnicodeCmap.to_string(),
3479 "no usable Unicode cmap (format 4/12)"
3480 );
3481 }
3482
3483 #[test]
3484 fn parse_accepts_true_and_otto_magics() {
3485 let tables = base_tables(
3486 3,
3487 3,
3488 2048,
3489 hmtx_long(&[(500, 1), (510, 2), (520, 3)]),
3490 cmap4_simple(&[(0x41, 1)]),
3491 );
3492 let t = Font::parse(sfnt(0x7472_7565, &tables)).expect("'true' magic parses");
3493 assert_eq!(t.units_per_em, 2048);
3494 assert_eq!(t.num_glyphs, 3);
3495 assert_eq!(t.ascent, 700);
3496 assert_eq!(t.descent, -200);
3497 assert_eq!(t.line_gap, 50);
3498 assert_eq!(t.glyph_index('A'), 1);
3499
3500 let o = Font::parse(sfnt(0x4F54_544F, &tables)).expect("'OTTO' magic parses");
3502 assert!(!o.has_glyf_outlines());
3503 assert_eq!(o.subset(&['A']), None);
3504 assert_eq!(o.glyph_bbox(1), None);
3505 assert_eq!(o.glyph_data(1), None);
3506 assert!(!o.is_composite(1));
3507 assert!(o.glyph_components(1).is_empty());
3508 }
3509
3510 #[test]
3513 fn left_side_bearing_reads_trailing_run_and_zero_metrics() {
3514 let mut hmtx = hmtx_long(&[(500, 50)]);
3517 push_i16(&mut hmtx, -7);
3518 push_i16(&mut hmtx, 33);
3519 let font = parse(&base_tables(3, 1, 1000, hmtx, cmap4_simple(&[])));
3520 assert_eq!(font.left_side_bearing(0), 50);
3521 assert_eq!(font.left_side_bearing(1), -7);
3522 assert_eq!(font.left_side_bearing(2), 33);
3523 assert_eq!(font.advance_width(0), 500);
3524 assert_eq!(font.advance_width(2), 500);
3525
3526 let font0 = parse(&base_tables(1, 0, 1000, Vec::new(), cmap4_simple(&[])));
3528 assert_eq!(font0.left_side_bearing(0), 0);
3529 }
3530
3531 #[test]
3534 fn legacy_kern_pair_and_char_kerning() {
3535 let pairs = [(1u16, 2u16, -30i16), (1, 3, 15), (4, 1, 7)];
3536 let metrics: Vec<(u16, i16)> = (0..8u16).map(|g| (600 + g, 0)).collect();
3537 let mut tables = base_tables(
3538 8,
3539 8,
3540 1000,
3541 hmtx_long(&metrics),
3542 cmap4_simple(&[(0x41, 1), (0x56, 2)]),
3543 );
3544 tables.push((b"kern", kern0_table(&pairs)));
3545 let font = parse(&tables);
3546 assert_eq!(font.kerning_between_glyphs(1, 2), -30);
3547 assert_eq!(font.kerning_between_glyphs(1, 3), 15);
3548 assert_eq!(font.kerning_between_glyphs(4, 1), 7);
3549 assert_eq!(font.kerning_between_glyphs(2, 1), 0);
3550 assert_eq!(font.kerning_between_glyphs(1, 4), 0);
3551 assert_eq!(font.kerning('A', 'V'), -30);
3552 assert_eq!(font.kerning_1000('A', 'V'), -30); assert_eq!(font.advance_1000('A'), 601);
3554
3555 let mut zero = base_tables(
3557 8,
3558 8,
3559 0,
3560 hmtx_long(&metrics),
3561 cmap4_simple(&[(0x41, 1), (0x56, 2)]),
3562 );
3563 zero.push((b"kern", kern0_table(&pairs)));
3564 let z = parse(&zero);
3565 assert_eq!(z.units_per_em, 0);
3566 assert_eq!(z.advance_1000('A'), 0);
3567 assert_eq!(z.kerning_1000('A', 'V'), 0);
3568 }
3569
3570 #[test]
3571 fn legacy_kern_skips_short_vertical_minimum_and_format2_subtables() {
3572 let mut k = Vec::new();
3573 push16(&mut k, 0); push16(&mut k, 5); push16(&mut k, 0);
3577 push16(&mut k, 10);
3578 push16(&mut k, 0x0001);
3579 k.extend_from_slice(&[0u8; 4]);
3580 push16(&mut k, 0);
3582 push16(&mut k, 14);
3583 push16(&mut k, 0x0000);
3584 k.extend_from_slice(&[0u8; 8]);
3585 push16(&mut k, 0);
3587 push16(&mut k, 14);
3588 push16(&mut k, 0x0003);
3589 k.extend_from_slice(&[0u8; 8]);
3590 push16(&mut k, 0);
3592 push16(&mut k, 14);
3593 push16(&mut k, 0x0201);
3594 k.extend_from_slice(&[0u8; 8]);
3595 push16(&mut k, 0);
3597 push16(&mut k, 20);
3598 push16(&mut k, 0x0001);
3599 push16(&mut k, 1); k.extend_from_slice(&[0u8; 6]);
3601 push16(&mut k, 3);
3602 push16(&mut k, 4);
3603 push_i16(&mut k, -11);
3604
3605 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
3606 tables.push((b"kern", k));
3607 let font = parse(&tables);
3608 assert_eq!(font.kerning_between_glyphs(3, 4), -11);
3609 assert_eq!(font.kerning_between_glyphs(3, 5), 0);
3610 }
3611
3612 #[test]
3613 fn legacy_kern_rejects_malformed_table_headers() {
3614 fn kern_font(kern: Vec<u8>) -> Font {
3615 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
3616 tables.push((b"kern", kern));
3617 parse(&tables)
3618 }
3619 let mut v1 = kern0_table(&[(1, 2, -30)]);
3621 v1[0..2].copy_from_slice(&1u16.to_be_bytes());
3622 assert_eq!(kern_font(v1).kerning_between_glyphs(1, 2), 0);
3623
3624 let mut zero_len = Vec::new();
3626 push16(&mut zero_len, 0);
3627 push16(&mut zero_len, 2);
3628 push16(&mut zero_len, 0); push16(&mut zero_len, 0); push16(&mut zero_len, 0x0000); zero_len.extend_from_slice(&[0u8; 8]);
3632 assert_eq!(kern_font(zero_len).kerning_between_glyphs(1, 2), 0);
3633
3634 let mut walk_off = Vec::new();
3636 push16(&mut walk_off, 0);
3637 push16(&mut walk_off, 2);
3638 push16(&mut walk_off, 0);
3639 push16(&mut walk_off, 14);
3640 push16(&mut walk_off, 0x0000); walk_off.extend_from_slice(&[0u8; 8]);
3642 assert_eq!(kern_font(walk_off).kerning_between_glyphs(1, 2), 0);
3643
3644 let mut overlong = Vec::new();
3646 push16(&mut overlong, 0);
3647 push16(&mut overlong, 1);
3648 push16(&mut overlong, 0);
3649 push16(&mut overlong, 200); push16(&mut overlong, 0x0001);
3651 overlong.extend_from_slice(&[0u8; 8]);
3652 assert_eq!(kern_font(overlong).kerning_between_glyphs(1, 2), 0);
3653
3654 let mut hungry = Vec::new();
3656 push16(&mut hungry, 0);
3657 push16(&mut hungry, 1);
3658 push16(&mut hungry, 0);
3659 push16(&mut hungry, 20); push16(&mut hungry, 0x0001);
3661 push16(&mut hungry, 3); hungry.extend_from_slice(&[0u8; 12]);
3663 assert_eq!(kern_font(hungry).kerning_between_glyphs(1, 2), 0);
3664
3665 let mut vertical_only = Vec::new();
3667 push16(&mut vertical_only, 0);
3668 push16(&mut vertical_only, 1);
3669 push16(&mut vertical_only, 0);
3670 push16(&mut vertical_only, 14);
3671 push16(&mut vertical_only, 0x0000);
3672 vertical_only.extend_from_slice(&[0u8; 8]);
3673 assert_eq!(kern_font(vertical_only).kerning_between_glyphs(1, 2), 0);
3674 }
3675
3676 #[test]
3677 fn legacy_kern_truncated_pair_records_kern_to_zero() {
3678 let pairs = [(1u16, 2u16, -30i16), (1, 3, 15), (4, 1, 7), (5, 5, 9)];
3683 for chop in [10usize, 16] {
3684 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
3685 tables.push((b"kern", kern0_table(&pairs)));
3686 let mut bytes = sfnt(0x0001_0000, &tables);
3687 bytes.truncate(bytes.len() - chop);
3688 let font = Font::parse(bytes).expect("kern pair payload is lazily read");
3689 assert_eq!(font.kerning_between_glyphs(1, 2), 0, "chop={chop}");
3690 }
3691 }
3692
3693 #[test]
3696 fn glyph_range_rejects_inverted_and_overlong_loca_entries() {
3697 let mut loca = Vec::new();
3700 push16(&mut loca, 2);
3701 push16(&mut loca, 1);
3702 push16(&mut loca, 3);
3703 let mut tables = base_tables(
3704 2,
3705 2,
3706 1000,
3707 hmtx_long(&[(500, 0), (500, 0)]),
3708 cmap4_simple(&[]),
3709 );
3710 tables.push((b"loca", loca));
3711 tables.push((b"glyf", vec![0u8; 4]));
3712 let font = parse(&tables);
3713 assert!(font.has_glyf_outlines());
3714 assert_eq!(font.glyph_data(0), None);
3715 assert_eq!(font.glyph_data(1), None);
3716 assert_eq!(font.glyph_bbox(0), None);
3717 assert!(!font.is_composite(0));
3718 }
3719
3720 #[test]
3721 fn glyph_components_walk_all_transform_variants() {
3722 let font = zoo_font();
3723 assert!(font.glyph_components(0).is_empty()); assert!(font.glyph_components(1).is_empty()); assert_eq!(font.glyph_components(2), vec![5]); assert_eq!(font.glyph_components(3), vec![5]); assert_eq!(font.glyph_components(4), vec![5]); assert_eq!(font.glyph_components(6), vec![2, 3]); assert_eq!(font.glyph_components(7), vec![5]); assert_eq!(font.glyph_components(10), vec![5]); assert!(font.glyph_components(11).is_empty()); assert!(font.is_composite(2));
3733 assert!(!font.is_composite(1));
3734 assert!(!font.is_composite(0));
3735 assert_eq!(font.glyph_bbox(2), Some([1, 2, 3, 4]));
3736 assert_eq!(font.glyph_bbox(0), None);
3737 assert_eq!(font.glyph_data(0), Some(&[][..]));
3738 }
3739
3740 #[test]
3741 fn glyph_components_stop_at_truncated_component_records() {
3742 for keep in [1usize, 10, 12] {
3745 let font = truncated_composite_font(keep);
3746 assert!(font.glyph_components(0).is_empty(), "keep={keep}");
3747 assert_eq!(font.glyph_data(0), None, "keep={keep}");
3748 }
3749 }
3750
3751 #[test]
3754 fn subset_rewrites_component_ids_across_transform_variants() {
3755 let font = zoo_font();
3756 let (bytes, remap) = font.subset_glyphs(&[2, 3, 4], &[]).expect("subset");
3757 let remap: Vec<(u16, u16)> = remap.into_iter().collect();
3758 assert_eq!(remap, vec![(0, 0), (2, 1), (3, 2), (4, 3), (5, 4)]);
3759 let sub = Font::parse(bytes).expect("subset re-parses");
3760 assert_eq!(sub.num_glyphs, 5);
3761 assert_eq!(sub.glyph_components(1), vec![4]);
3762 assert_eq!(sub.glyph_components(2), vec![4]);
3763 assert_eq!(sub.glyph_components(3), vec![4]);
3764 assert_eq!(sub.glyph_bbox(1), Some([1, 2, 3, 4]));
3765 assert_eq!(sub.advance_width(1), 502);
3766 assert_eq!(sub.left_side_bearing(1), 2);
3767 assert_eq!(sub.advance_width(4), 505);
3768 assert_eq!(sub.left_side_bearing(4), 5);
3769 }
3770
3771 #[test]
3772 fn subset_closure_skips_component_ids_past_num_glyphs() {
3773 let font = zoo_font();
3774 let (bytes, remap) = font.subset_glyphs(&[9], &[]).expect("subset");
3775 assert_eq!(remap.get(&9).copied(), Some(1));
3776 assert_eq!(remap.len(), 2); let sub = Font::parse(bytes).expect("subset re-parses");
3778 assert_eq!(sub.num_glyphs, 2);
3779 assert_eq!(sub.glyph_components(1), vec![0]);
3781 }
3782
3783 #[test]
3784 fn subset_shares_a_component_between_two_composites() {
3785 let font = zoo_font();
3786 let (bytes, remap) = font.subset_glyphs(&[2, 4], &[]).expect("subset");
3787 let remap: Vec<(u16, u16)> = remap.into_iter().collect();
3788 assert_eq!(remap, vec![(0, 0), (2, 1), (4, 2), (5, 3)]);
3789 let sub = Font::parse(bytes).expect("subset re-parses");
3790 assert_eq!(sub.glyph_components(1), vec![3]);
3791 assert_eq!(sub.glyph_components(2), vec![3]);
3792 }
3793
3794 #[test]
3795 fn subset_tolerates_composite_whose_last_record_dangles_more() {
3796 let font = zoo_font();
3802 assert_eq!(font.glyph_components(7), vec![5]);
3803 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
3804 new_of[0] = 0;
3805 new_of[5] = 1;
3806 new_of[7] = 2;
3807 let out = font
3808 .subset_glyph_bytes(7, &new_of)
3809 .expect("dangling MORE bit is tolerated and stripped");
3810 assert_eq!(be_u16(&out, 10), Some(0));
3812 assert!(font.subset_glyphs(&[7], &[]).is_some());
3813 }
3814
3815 #[test]
3816 fn subset_tolerates_composite_whose_dangling_record_claims_instructions() {
3817 let glyph = composite_glyph([0; 4], &[(0x0100 | 0x0020, 0, &[0, 0])], &[]);
3822 let mut glyf = Vec::new();
3823 let mut loca = Vec::new();
3824 push16(&mut loca, 0); push16(&mut loca, 0);
3826 glyf.extend_from_slice(&glyph);
3827 push16(&mut loca, u16::try_from(glyf.len() / 2).unwrap());
3828 let mut tables = base_tables(
3829 2,
3830 1,
3831 1000,
3832 hmtx_long(&[(500, 0), (500, 1)]),
3833 cmap4_simple(&[]),
3834 );
3835 tables.push((b"loca", loca));
3836 tables.push((b"glyf", glyf));
3837 let font = parse(&tables);
3838
3839 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
3840 new_of[0] = 0;
3841 new_of[1] = 1;
3842 let out = font
3843 .subset_glyph_bytes(1, &new_of)
3844 .expect("dangling INSTRUCTIONS|MORE record is tolerated");
3845 let flags = be_u16(&out, 10).expect("record flags readable");
3848 assert_eq!(
3849 flags & (0x0100 | 0x0020),
3850 0,
3851 "MORE and INSTRUCTIONS cleared"
3852 );
3853 }
3854
3855 #[test]
3856 fn subset_emits_format12_cmap_when_supplementary_plane_is_kept() {
3857 let groups: &[(u32, u32, u32)] =
3861 &[(u32::from('A'), u32::from('B'), 1), (0x1D49C, 0x1D49D, 3)];
3862 let mut tables = base_tables(
3863 5,
3864 1,
3865 1000,
3866 hmtx_long(&[(500, 0), (505, 1), (510, 2), (515, 3)]),
3867 cmap12_table(groups),
3868 );
3869
3870 let mut glyf = Vec::new();
3871 let mut loca = Vec::new();
3872 push16(&mut loca, 0);
3873 for _ in 0..4 {
3874 let g = simple_glyph16();
3875 glyf.extend_from_slice(&g);
3876 push16(&mut loca, u16::try_from(glyf.len() / 2).unwrap());
3877 }
3878 while glyf.len() % 4 != 0 {
3879 glyf.push(0);
3880 }
3881 tables.push((b"loca", loca));
3882 tables.push((b"glyf", glyf));
3883 let font = parse(&tables);
3884 assert_ne!(font.glyph_index('A'), 0);
3885 assert_ne!(font.glyph_index('\u{1D49C}'), 0);
3886
3887 let subset = font
3888 .subset(&['A', 'B', '\u{1D49C}', '\u{1D49D}'])
3889 .expect("subset");
3890 let reparsed = Font::parse(subset).expect("subset re-parses");
3891 assert_ne!(reparsed.glyph_index('A'), 0, "BMP letter survives");
3892 assert_ne!(reparsed.glyph_index('B'), 0, "BMP letter survives");
3893 assert_ne!(
3894 reparsed.glyph_index('\u{1D49C}'),
3895 0,
3896 "script A must survive the subset"
3897 );
3898 assert_ne!(
3899 reparsed.glyph_index('\u{1D49D}'),
3900 0,
3901 "script B must survive the subset"
3902 );
3903 }
3904
3905 #[test]
3906 fn subset_strips_valid_composite_instructions_and_clears_the_flag() {
3907 let font = zoo_font();
3908 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
3909 new_of[0] = 0;
3910 new_of[5] = 1;
3911 new_of[12] = 2;
3912 let out = font
3913 .subset_glyph_bytes(12, &new_of)
3914 .expect("valid instructions strip");
3915 assert_eq!(out.len(), 16); assert_eq!(be_u16(&out, 10), Some(0)); assert_eq!(be_u16(&out, 12), Some(1)); let (bytes, remap) = font.subset_glyphs(&[12], &[]).expect("subset");
3919 assert_eq!(remap.get(&12).copied(), Some(2));
3920 let sub = Font::parse(bytes).expect("subset re-parses");
3921 assert_eq!(sub.glyph_components(2), vec![1]);
3922 }
3923
3924 #[test]
3925 fn subset_cmap_skips_supplementary_plane_chars() {
3926 let font = zoo_font();
3927 let (bytes, _) = font.subset_glyphs(&[2], &['😀']).expect("subset");
3928 let sub = Font::parse(bytes).expect("subset re-parses");
3929 assert_eq!(sub.glyph_index('😀'), 0); }
3931
3932 #[test]
3933 fn subset_rejects_composite_with_overlong_instruction_claim() {
3934 let font = zoo_font();
3935 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
3936 new_of[0] = 0;
3937 new_of[5] = 1;
3938 new_of[8] = 2;
3939 assert_eq!(font.subset_glyph_bytes(8, &new_of), None);
3940 assert!(font.subset_glyphs(&[8], &[]).is_none());
3941 }
3942
3943 #[test]
3944 fn strip_simple_glyph_instructions_rejects_overlong_length() {
3945 let mut glyph = Vec::new();
3946 push_i16(&mut glyph, 1);
3947 glyph.extend_from_slice(&[0u8; 8]); push16(&mut glyph, 0); push16(&mut glyph, 255); assert_eq!(strip_simple_glyph_instructions(&glyph, 1), None);
3951 }
3952
3953 #[test]
3956 fn cmap4_truncated_segment_arrays_fall_back_to_uncached_lookup() {
3957 let segs = [
3962 (0x5Au16, 0x41u16, 1u16.wrapping_sub(0x41), 0u16), (0x61, 0x61, 1, 6), (0x62, 0x62, 0, 2), (0x63, 0x63, 0, 0), (0x64, 0x64, 0, 7), (0x00FF, 0x00F0, 0, 0), ];
3969 let tables = base_tables(30, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_table(&segs, &[]));
3970 let mut bytes = sfnt(0x0001_0000, &tables);
3971 bytes.truncate(bytes.len() - 2); let font = Font::parse(bytes).expect("cmap payload is lazily read");
3973 assert!(font.cmap4_cache.is_none());
3974 assert_eq!(font.glyph_index('A'), 1);
3975 assert_eq!(font.glyph_index('Z'), 26);
3976 assert_eq!(font.glyph_index('@'), 0); assert_eq!(font.glyph_index('a'), 8); assert_eq!(font.glyph_index('b'), 0); assert_eq!(font.glyph_index('c'), 99); assert_eq!(font.glyph_index('d'), 0); assert_eq!(font.glyph_index('õ'), 0); assert_eq!(font.glyph_index('Ā'), 0); assert_eq!(font.glyph_index('😀'), 0); }
3985
3986 #[test]
3987 fn cmap4_cached_lookup_reads_glyph_id_array() {
3988 let segs = [(0x42u16, 0x41u16, 3u16, 4u16), (0xFFFF, 0xFFFF, 1, 0)];
3989 let mut array = Vec::new();
3990 push16(&mut array, 7);
3991 push16(&mut array, 0);
3992 let font = parse(&base_tables(
3993 20,
3994 1,
3995 1000,
3996 hmtx_long(&[(500, 0)]),
3997 cmap4_table(&segs, &array),
3998 ));
3999 let cache = font.cmap4_cache.as_ref().expect("valid table caches");
4000 assert!(cache.sorted_by_end);
4001 assert_eq!(font.glyph_index('A'), 10); assert_eq!(font.glyph_index('B'), 0); assert_eq!(font.glyph_index('C'), 0); }
4005
4006 #[test]
4007 fn cmap4_unsorted_segments_use_first_match_linear_scan() {
4008 let segs = [
4009 (0x61u16, 0x61u16, 2u16.wrapping_sub(0x61), 0u16),
4010 (0x5A, 0x41, 1u16.wrapping_sub(0x41), 0),
4011 (0xFFFF, 0xFFFF, 1, 0),
4012 ];
4013 let font = parse(&base_tables(
4014 30,
4015 1,
4016 1000,
4017 hmtx_long(&[(500, 0)]),
4018 cmap4_table(&segs, &[]),
4019 ));
4020 let cache = font
4021 .cmap4_cache
4022 .as_ref()
4023 .expect("caches even when unsorted");
4024 assert!(!cache.sorted_by_end);
4025 assert_eq!(font.glyph_index('a'), 2);
4026 assert_eq!(font.glyph_index('A'), 0);
4029 assert_eq!(font.glyph_index('p'), 0);
4030 }
4031
4032 #[test]
4033 fn cmap4_unsorted_lookup_misses_when_no_segment_covers_the_code() {
4034 let segs = [
4037 (0x61u16, 0x61u16, 2u16.wrapping_sub(0x61), 0u16),
4038 (0x5A, 0x41, 1u16.wrapping_sub(0x41), 0),
4039 ];
4040 let font = parse(&base_tables(
4041 30,
4042 1,
4043 1000,
4044 hmtx_long(&[(500, 0)]),
4045 cmap4_table(&segs, &[]),
4046 ));
4047 assert!(!font.cmap4_cache.as_ref().expect("caches").sorted_by_end);
4048 assert_eq!(font.glyph_index('a'), 2);
4049 assert_eq!(font.glyph_index('p'), 0); }
4051
4052 #[test]
4053 fn select_cmap_accepts_format4_under_a_non_bmp_encoding_record() {
4054 let mut cmap = cmap4_simple(&[(0x41, 1)]);
4057 cmap[6..8].copy_from_slice(&10u16.to_be_bytes()); let font = parse(&base_tables(5, 1, 1000, hmtx_long(&[(500, 0)]), cmap));
4059 assert_eq!(font.cmap_format, 4);
4060 assert_eq!(font.glyph_index('A'), 1);
4061 }
4062
4063 #[test]
4064 fn cmap12_groups_map_across_planes_and_truncate_gids() {
4065 let cmap = cmap12_table(&[
4066 (0x41, 0x5A, 100),
4067 (0x2000, 0x2000, 0x0001_2345),
4068 (0x1F600, 0x1F601, 7),
4069 ]);
4070 let font = parse(&base_tables(200, 1, 1000, hmtx_long(&[(500, 0)]), cmap));
4071 assert_eq!(font.cmap_format, 12);
4072 assert!(font.cmap4_cache.is_none());
4073 assert_eq!(font.glyph_index('A'), 100);
4074 assert_eq!(font.glyph_index('Z'), 125);
4075 assert_eq!(font.glyph_index('\u{2000}'), 0x2345); assert_eq!(font.glyph_index('😀'), 7);
4077 assert_eq!(font.glyph_index('😁'), 8);
4078 assert_eq!(font.glyph_index('0'), 0); }
4080
4081 #[test]
4084 fn gpos_extension_lookup_resolves_wrapped_pair_kerning() {
4085 let kern = gpos_font(1, 2, 0, 1);
4086 assert_eq!(kern.pair(5, 6), -40);
4087 assert_eq!(kern.pair(5, 7), 0);
4088 assert_eq!(kern.pair(6, 6), 0);
4089 }
4090
4091 #[test]
4092 fn gpos_skips_foreign_extensions_bad_formats_and_lookup_indices() {
4093 assert_eq!(gpos_font(1, 5, 0, 1).pair(5, 6), 0);
4095 assert_eq!(gpos_font(2, 2, 0, 1).pair(5, 6), 0);
4097 assert_eq!(gpos_font(1, 2, 0, 3).pair(5, 6), 0);
4099 assert_eq!(gpos_font(1, 2, 9, 1).pair(5, 6), 0);
4101 }
4102
4103 #[test]
4104 fn gpos_truncated_structures_yield_empty_kerning() {
4105 for end in [16usize, 20, 22, 26, 28, 32, 34] {
4110 let mut g = gpos_table(1, 2, 0, 1);
4111 g.truncate(end);
4112 assert_eq!(gpos_kerning_of(g).pair(5, 6), 0, "end={end}");
4113 }
4114 let mut over = gpos_table(1, 2, 0, 1);
4117 over[10..12].copy_from_slice(&12u16.to_be_bytes());
4118 assert_eq!(gpos_kerning_of(over).pair(5, 6), -40);
4119 let mut direct = gpos_table(1, 2, 0, 1);
4121 direct[28..30].copy_from_slice(&1u16.to_be_bytes());
4122 assert_eq!(gpos_kerning_of(direct).pair(5, 6), 0);
4123 }
4124
4125 #[test]
4126 fn resolve_extension_requires_format_1() {
4127 let mut d = Vec::new();
4128 push16(&mut d, 2);
4129 push16(&mut d, 2);
4130 push32(&mut d, 8);
4131 assert_eq!(resolve_extension(&d, 0), None);
4132 d[0..2].copy_from_slice(&1u16.to_be_bytes());
4133 assert_eq!(resolve_extension(&d, 0), Some((2, 8)));
4134 assert!(parse_pair_subtable(&[0, 3], 0).is_none());
4136 }
4137
4138 #[test]
4139 fn value_record_x_advance_field_extraction() {
4140 assert_eq!(value_record_x_advance(&[], 0, 0), Some(0));
4142 let rec = [0, 0, 0, 0, 0x12, 0x34];
4144 assert_eq!(value_record_x_advance(&rec, 0, 0x0007), Some(0x1234));
4145 assert_eq!(value_record_x_advance(&[0], 0, 0x0004), None);
4147 }
4148
4149 #[test]
4150 fn coverage_and_class_def_malformed_and_boundary_variants() {
4151 let mut cov = Vec::new();
4153 push16(&mut cov, 2);
4154 push16(&mut cov, 1);
4155 push16(&mut cov, 20); push16(&mut cov, 10); push16(&mut cov, 0);
4158 assert_eq!(parse_coverage_glyphs(&cov, 0), Some(Vec::new()));
4159 assert_eq!(parse_coverage_glyphs(&[0, 3, 0, 0], 0), None);
4161 assert!(parse_class_def(&[0, 3, 0, 0], 0).is_none());
4163
4164 let mut cd = Vec::new();
4166 push16(&mut cd, 1);
4167 push16(&mut cd, 5); push16(&mut cd, 2); push16(&mut cd, 7);
4170 push16(&mut cd, 9);
4171 let cd1 = parse_class_def(&cd, 0).expect("format 1 parses");
4172 assert_eq!(cd1.class(5), 7);
4173 assert_eq!(cd1.class(6), 9);
4174 assert_eq!(cd1.class(7), 0); assert_eq!(cd1.class(4), 0); let mut cd2b = Vec::new();
4179 push16(&mut cd2b, 2);
4180 push16(&mut cd2b, 1);
4181 push16(&mut cd2b, 10);
4182 push16(&mut cd2b, 20);
4183 push16(&mut cd2b, 3);
4184 let cd2 = parse_class_def(&cd2b, 0).expect("format 2 parses");
4185 assert_eq!(cd2.class(15), 3);
4186 assert_eq!(cd2.class(9), 0);
4187 assert_eq!(cd2.class(21), 0);
4188 }
4189
4190 #[test]
4191 fn kern_subtable_format2_guards_class_ranges_and_empty_matrix() {
4192 let st = KernSubtable::Format2 {
4193 coverage: vec![5, 9],
4194 class1: ClassDef::Format1 {
4195 start: 5,
4196 classes: vec![1, 0, 0, 0, 9],
4197 },
4198 class2: ClassDef::Format1 {
4199 start: 6,
4200 classes: vec![1, 7],
4201 },
4202 class1_count: 2,
4203 class2_count: 2,
4204 matrix: vec![0, 0, 0, -55],
4205 };
4206 assert_eq!(st.lookup(4, 6), None); assert_eq!(st.lookup(5, 6), Some(-55)); assert_eq!(st.lookup(9, 6), Some(0)); assert_eq!(st.lookup(5, 7), Some(0)); let empty = KernSubtable::Format2 {
4212 coverage: vec![5],
4213 class1: ClassDef::Format2 {
4214 ranges: Vec::new(),
4215 dense: true,
4216 },
4217 class2: ClassDef::Format2 {
4218 ranges: Vec::new(),
4219 dense: true,
4220 },
4221 class1_count: 1,
4222 class2_count: 1,
4223 matrix: Vec::new(),
4224 };
4225 assert_eq!(empty.lookup(5, 6), Some(0));
4226
4227 let kerning = Kerning {
4229 subtables: vec![empty, st],
4230 };
4231 assert_eq!(kerning.pair(5, 6), 0);
4232 assert_eq!(kerning.pair(4, 6), 0);
4233 }
4234
4235 #[test]
4236 fn for_each_ascii_pair_matches_brute_force_pair_on_bundled_faces() {
4237 let base = env!("CARGO_MANIFEST_DIR");
4242 let mut any_kerning_face = false;
4243 for path in [
4244 "/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf",
4245 "/fonts/ibm-plex-sans/IBMPlexSans-Bold.ttf",
4246 "/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf",
4247 "/fonts/computer-modern/cmunrm.ttf",
4248 "/fonts/computer-modern/cmuntt.ttf",
4249 "/fonts/noto-sans-math/NotoSansMathSymbols.ttf",
4250 ] {
4251 let Ok(bytes) = std::fs::read(format!("{base}{path}")) else {
4252 continue;
4253 };
4254 let Ok(font) = Font::parse(bytes) else {
4255 continue;
4256 };
4257 let kern = font.gpos_kerning();
4258 let glyphs: [u16; 128] = std::array::from_fn(|b| font.glyph_index(b as u8 as char));
4259 let nonzero_pairs = kern
4260 .subtables
4261 .iter()
4262 .map(|st| match st {
4263 KernSubtable::Format1 { pairs } => pairs.len(),
4264 KernSubtable::Format2 { coverage, .. } => coverage.len() * 128,
4265 })
4266 .sum::<usize>();
4267 if nonzero_pairs == 0 {
4268 continue;
4269 }
4270 any_kerning_face = true;
4271
4272 let mut enumerated = [0i16; 128 * 128];
4273 let mut emitted = 0usize;
4274 kern.for_each_ascii_pair(
4275 |b| glyphs[usize::from(b)],
4276 |l, r, v| {
4277 enumerated[usize::from(l) * 128 + usize::from(r)] = v;
4278 emitted += 1;
4279 },
4280 );
4281
4282 let mut brute = [0i16; 128 * 128];
4283 for l in 0..128usize {
4284 for r in 0..128usize {
4285 brute[l * 128 + r] = kern.pair(glyphs[l], glyphs[r]);
4286 }
4287 }
4288 assert_eq!(enumerated, brute, "enumeration != brute force for {path}");
4289 assert_eq!(
4290 emitted,
4291 brute.iter().filter(|&&v| v != 0).count(),
4292 "emission count != nonzero cells for {path}"
4293 );
4294 }
4295 assert!(
4296 any_kerning_face,
4297 "test is vacuous: no bundled face has GPOS kerning"
4298 );
4299 }
4300
4301 #[test]
4302 fn for_each_ascii_pair_first_match_duplicate_glyphs_and_zero_shadowing() {
4303 let glyph_of = |b: u8| -> u16 {
4306 match b {
4307 10..=12 => u16::from(b) - 5,
4308 13 | 14 => 9,
4309 other => u16::from(other),
4310 }
4311 };
4312
4313 let covered_all_rights = KernSubtable::Format2 {
4314 coverage: vec![5],
4315 class1: ClassDef::Format1 {
4316 start: 5,
4317 classes: vec![1],
4318 },
4319 class2: ClassDef::Format1 {
4320 start: 6,
4321 classes: vec![1, 1],
4322 },
4323 class1_count: 2,
4324 class2_count: 2,
4325 matrix: vec![0, 0, 30, -25],
4328 };
4329 let specific = KernSubtable::Format1 {
4330 pairs: PairMap::from_iter([(pair_key(5, 6), -99), (pair_key(9, 9), -12)]),
4331 };
4332 let out_of_range_class_zero = KernSubtable::Format2 {
4333 coverage: vec![9],
4334 class1: ClassDef::Format1 {
4335 start: 9,
4336 classes: vec![9], },
4338 class2: ClassDef::Format2 {
4339 ranges: Vec::new(),
4340 dense: true,
4341 },
4342 class1_count: 2,
4343 class2_count: 2,
4344 matrix: vec![0; 4],
4345 };
4346 let defined_zero = KernSubtable::Format1 {
4347 pairs: PairMap::from_iter([(pair_key(6, 6), 0)]),
4348 };
4349 let shadowed = KernSubtable::Format1 {
4350 pairs: PairMap::from_iter([(pair_key(6, 6), -77)]),
4351 };
4352
4353 let kerning = Kerning {
4354 subtables: vec![
4355 covered_all_rights,
4356 specific,
4357 out_of_range_class_zero,
4358 defined_zero,
4359 shadowed,
4360 ],
4361 };
4362
4363 let mut enumerated = [0i16; 128 * 128];
4364 kerning.for_each_ascii_pair(glyph_of, |l, r, v| {
4365 enumerated[usize::from(l) * 128 + usize::from(r)] = v;
4366 });
4367 let mut brute = [0i16; 128 * 128];
4368 for l in 0..128usize {
4369 for r in 0..128usize {
4370 brute[l * 128 + r] = kerning.pair(glyph_of(l as u8), glyph_of(r as u8));
4371 }
4372 }
4373 assert_eq!(enumerated, brute);
4374
4375 let cell = |l: u8, r: u8| enumerated[usize::from(l) * 128 + usize::from(r)];
4380 assert_eq!(cell(10, 11), -25); assert_eq!(cell(10, 12), -25); assert_eq!(cell(10, 13), 30); assert_eq!(cell(13, 13), -12); assert_eq!(cell(13, 14), -12);
4385 assert_eq!(cell(14, 13), -12);
4386 assert_eq!(cell(14, 14), -12);
4387 assert_eq!(cell(11, 11), 0); assert_eq!(cell(13, 11), 0); let mut calls = 0;
4392 Kerning::default().for_each_ascii_pair(glyph_of, |_, _, _| calls += 1);
4393 assert_eq!(calls, 0);
4394 }
4395
4396 #[test]
4397 fn pair_format1_skips_malformed_sets_and_truncated_records() {
4398 let mut d = Vec::new();
4401 push16(&mut d, 1); push16(&mut d, 20); push16(&mut d, 0x0004); push16(&mut d, 0); push16(&mut d, 2); push16(&mut d, 14); push16(&mut d, 14); push16(&mut d, 1); push16(&mut d, 6); push_i16(&mut d, -40);
4411 push16(&mut d, 1); push16(&mut d, 1);
4413 push16(&mut d, 5);
4414 let st = parse_pair_subtable(&d, 0).expect("format 1 parses");
4415 assert_eq!(st.lookup(5, 6), Some(-40));
4416 assert_eq!(st.lookup(5, 7), None);
4417
4418 let mut d2 = Vec::new();
4420 push16(&mut d2, 1);
4421 push16(&mut d2, 12); push16(&mut d2, 0x0004);
4423 push16(&mut d2, 0);
4424 push16(&mut d2, 1);
4425 push16(&mut d2, 0x4000); push16(&mut d2, 1);
4427 push16(&mut d2, 1);
4428 push16(&mut d2, 5);
4429 let st2 = parse_pair_subtable(&d2, 0).expect("parses to an empty set");
4430 assert_eq!(st2.lookup(5, 6), None);
4431
4432 let mut d3 = Vec::new();
4434 push16(&mut d3, 1);
4435 push16(&mut d3, 12); push16(&mut d3, 0x0004);
4437 push16(&mut d3, 0);
4438 push16(&mut d3, 1);
4439 push16(&mut d3, 18); push16(&mut d3, 1); push16(&mut d3, 1);
4442 push16(&mut d3, 5);
4443 push16(&mut d3, 2); push16(&mut d3, 6);
4445 push_i16(&mut d3, -40);
4446 let st3 = parse_pair_subtable(&d3, 0).expect("parses the readable record");
4447 assert_eq!(st3.lookup(5, 6), Some(-40));
4448 assert_eq!(st3.lookup(5, 0), None);
4449 }
4450
4451 #[test]
4452 fn pair_format1_work_cap_stops_aliased_pair_set_expansion() {
4453 for count in [65_535u16, 65_534] {
4456 let mut d = Vec::new();
4457 push16(&mut d, 1); push16(&mut d, 14); push16(&mut d, 0); push16(&mut d, 0); push16(&mut d, 2); push16(&mut d, 22); push16(&mut d, 22); push16(&mut d, 1); push16(&mut d, 2);
4466 push16(&mut d, 5);
4467 push16(&mut d, 6);
4468 push16(&mut d, count); d.resize(d.len() + usize::from(count) * 2, 0); let st = parse_pair_subtable(&d, 0).expect("parses under the work cap");
4471 assert_eq!(st.lookup(5, 0), Some(0), "count={count}");
4474 assert_eq!(st.lookup(6, 0), None, "count={count}");
4475 }
4476 }
4477
4478 #[test]
4479 fn pair_format2_empty_value_formats_and_oversized_matrix() {
4480 let mut d = Vec::new();
4483 push16(&mut d, 2); push16(&mut d, 16); push16(&mut d, 0); push16(&mut d, 0); push16(&mut d, 22); push16(&mut d, 22); push16(&mut d, 1); push16(&mut d, 1); push16(&mut d, 1); push16(&mut d, 1);
4493 push16(&mut d, 3);
4494 push16(&mut d, 1); push16(&mut d, 0);
4496 push16(&mut d, 0);
4497 let st = parse_pair_subtable(&d, 0).expect("empty-value format 2 parses");
4498 assert!(matches!(
4499 &st,
4500 KernSubtable::Format2 { matrix, .. } if matrix.is_empty()
4501 ));
4502 assert_eq!(st.lookup(3, 42), Some(0));
4503 assert_eq!(st.lookup(4, 42), None);
4504
4505 let mut big = Vec::new();
4507 push16(&mut big, 2);
4508 push16(&mut big, 16);
4509 push16(&mut big, 0x0004);
4510 push16(&mut big, 0);
4511 push16(&mut big, 22);
4512 push16(&mut big, 22);
4513 push16(&mut big, 0xFFFF);
4514 push16(&mut big, 0xFFFF);
4515 assert!(parse_pair_subtable(&big, 0).is_none());
4516 }
4517
4518 #[test]
4521 fn gsub_extension_lookup_parses_greedy_ligatures() {
4522 let ligs = gsub_font(1, 4, 0);
4523 assert!(!ligs.is_empty());
4524 assert!(Ligatures::default().is_empty());
4525 assert_eq!(ligs.substitute(&[10, 11, 12]), vec![99]);
4526 assert_eq!(ligs.substitute(&[10, 11, 7]), vec![77, 7]);
4527 assert_eq!(ligs.substitute(&[10, 7]), vec![10, 7]);
4528 assert_eq!(
4529 ligs.substitute_with_spans(&[10, 11, 12, 10, 11]),
4530 vec![(99, 3), (77, 2)]
4531 );
4532 }
4533
4534 #[test]
4535 fn substitute_with_spans_into_matches_allocating_variant() {
4536 let ligs = gsub_font(1, 4, 0);
4537 let corpora: [&[u16]; 7] = [
4538 &[],
4539 &[10, 11, 12, 10, 11],
4540 &[10, 11, 7],
4541 &[10, 7],
4542 &[99, 99, 99],
4543 &[10, 11, 12, 10, 11, 12, 10, 11],
4544 &[7, 8, 9, 10],
4545 ];
4546 let mut scratch = vec![(u16::MAX, usize::MAX); 4];
4549 for gids in corpora {
4550 let mut into = Vec::new();
4551 ligs.substitute_with_spans_into(gids, &mut into);
4552 assert_eq!(into, ligs.substitute_with_spans(gids));
4553 ligs.substitute_with_spans_into(gids, &mut scratch);
4554 assert_eq!(scratch, into);
4555 }
4556 }
4557
4558 #[test]
4559 fn max_rule_len_reports_longest_rule() {
4560 assert_eq!(gsub_font(1, 4, 0).max_rule_len(), 3);
4563 assert_eq!(Ligatures::default().max_rule_len(), 1);
4565 }
4566
4567 #[test]
4568 fn gsub_skips_foreign_extensions_and_bad_lookup_indices() {
4569 assert!(gsub_font(1, 2, 0).is_empty());
4571 assert!(gsub_font(2, 4, 0).is_empty());
4573 assert!(gsub_font(1, 4, 9).is_empty());
4575 }
4576
4577 #[test]
4578 fn gsub_truncated_structures_yield_no_ligatures() {
4579 for end in [16usize, 20, 22, 26, 28, 32, 34] {
4582 let mut g = gsub_table(1, 4, 0);
4583 g.truncate(end);
4584 assert!(gsub_ligatures_of(g).is_empty(), "end={end}");
4585 }
4586 let mut over = gsub_table(1, 4, 0);
4589 over[10..12].copy_from_slice(&12u16.to_be_bytes());
4590 assert_eq!(gsub_ligatures_of(over).substitute(&[10, 11]), vec![77]);
4591 let mut direct = gsub_table(1, 4, 0);
4593 direct[28..30].copy_from_slice(&1u16.to_be_bytes());
4594 assert!(gsub_ligatures_of(direct).is_empty());
4595 }
4596
4597 #[test]
4598 fn ligature_subst_skips_malformed_entries() {
4599 let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
4600 std::collections::BTreeMap::new();
4601
4602 parse_ligature_subst(&[0, 2, 0, 0], 0, &mut rules);
4604 assert!(rules.is_empty());
4605
4606 parse_ligature_subst(&[], 0, &mut rules); parse_ligature_subst(&[0, 1], 0, &mut rules); parse_ligature_subst(&[0, 1, 0, 8], 0, &mut rules); parse_ligature_subst(&[0, 1, 0, 6, 0, 1, 0, 3], 0, &mut rules); assert!(rules.is_empty());
4612
4613 let mut d = Vec::new();
4615 push16(&mut d, 1); push16(&mut d, 8); push16(&mut d, 1); push16(&mut d, 0x4000); push16(&mut d, 1); push16(&mut d, 1);
4621 push16(&mut d, 10);
4622 parse_ligature_subst(&d, 0, &mut rules);
4623 assert!(rules.is_empty());
4624
4625 let mut d2 = Vec::new();
4628 push16(&mut d2, 1); push16(&mut d2, 20); push16(&mut d2, 2); push16(&mut d2, 10); push16(&mut d2, 10); push16(&mut d2, 1); push16(&mut d2, 4); push16(&mut d2, 77); push16(&mut d2, 2); push16(&mut d2, 11); push16(&mut d2, 1); push16(&mut d2, 1);
4640 push16(&mut d2, 10);
4641 parse_ligature_subst(&d2, 0, &mut rules);
4642 assert_eq!(rules.len(), 1);
4643 assert_eq!(rules[&10].len(), 1);
4644 assert_eq!(rules[&10][0].components, vec![11]);
4645 assert_eq!(rules[&10][0].ligature, 77);
4646
4647 rules.clear();
4650 let mut d3 = Vec::new();
4651 push16(&mut d3, 1); push16(&mut d3, 8); push16(&mut d3, 1); push16(&mut d3, 14); push16(&mut d3, 1); push16(&mut d3, 1);
4657 push16(&mut d3, 10);
4658 push16(&mut d3, 4); push16(&mut d3, 10); push16(&mut d3, 20); push16(&mut d3, 0x4000); push16(&mut d3, 14); push16(&mut d3, 33); push16(&mut d3, 0); push16(&mut d3, 88); push16(&mut d3, 5); push16(&mut d3, 11);
4668 push16(&mut d3, 12);
4669 assert_eq!(d3.len(), 36);
4670 parse_ligature_subst(&d3, 0, &mut rules);
4671 assert!(rules.is_empty());
4672 }
4673
4674 #[test]
4675 fn ligature_subst_work_cap_stops_aliased_sets() {
4676 for lig_count in [65_535u16, 65_534] {
4680 let mut d = Vec::new();
4681 push16(&mut d, 1); push16(&mut d, 10); push16(&mut d, 2); push16(&mut d, 18); push16(&mut d, 18); push16(&mut d, 1); push16(&mut d, 2);
4688 push16(&mut d, 10);
4689 push16(&mut d, 11);
4690 push16(&mut d, lig_count); let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
4692 std::collections::BTreeMap::new();
4693 parse_ligature_subst(&d, 0, &mut rules);
4694 assert!(rules.is_empty(), "lig_count={lig_count}");
4695 }
4696 }
4697
4698 fn f32_to_fixed(v: f32) -> i32 {
4701 (f64::from(v) * 65536.0).round() as i32
4702 }
4703
4704 fn f32_to_f2dot14(v: f32) -> i16 {
4705 (v * 16384.0).round() as i16
4706 }
4707
4708 fn push_i32(out: &mut Vec<u8>, v: i32) {
4709 out.extend_from_slice(&v.to_be_bytes());
4710 }
4711
4712 fn fvar_table(
4715 axes: &[(&[u8; 4], f32, f32, f32, u16)],
4716 instances: &[(u16, &[f32])],
4717 with_ps_name: bool,
4718 ) -> Vec<u8> {
4719 let axis_count = u16::try_from(axes.len()).unwrap();
4720 let instance_count = u16::try_from(instances.len()).unwrap();
4721 let instance_size = 4 + 4 * axis_count + u16::from(with_ps_name) * 2;
4722 let mut t = Vec::new();
4723 push16(&mut t, 1); push16(&mut t, 0); push16(&mut t, 16); push16(&mut t, 0); push16(&mut t, axis_count);
4728 push16(&mut t, 20); push16(&mut t, instance_count);
4730 push16(&mut t, instance_size);
4731 for &(tag, min, default, max, name_id) in axes {
4732 t.extend_from_slice(&tag[..]);
4733 push_i32(&mut t, f32_to_fixed(min));
4734 push_i32(&mut t, f32_to_fixed(default));
4735 push_i32(&mut t, f32_to_fixed(max));
4736 push16(&mut t, 0); push16(&mut t, name_id);
4738 }
4739 for &(name_id, coords) in instances {
4740 push16(&mut t, name_id);
4741 push16(&mut t, 0); for &c in coords {
4743 push_i32(&mut t, f32_to_fixed(c));
4744 }
4745 if with_ps_name {
4746 push16(&mut t, name_id.saturating_add(1000));
4747 }
4748 }
4749 t
4750 }
4751
4752 fn avar_table(maps: &[&[(f32, f32)]]) -> Vec<u8> {
4754 let mut t = Vec::new();
4755 push16(&mut t, 1); push16(&mut t, 0); push16(&mut t, 0); push16(&mut t, u16::try_from(maps.len()).unwrap());
4759 for axis in maps {
4760 push16(&mut t, u16::try_from(axis.len()).unwrap());
4761 for &(from, to) in *axis {
4762 push_i16(&mut t, f32_to_f2dot14(from));
4763 push_i16(&mut t, f32_to_f2dot14(to));
4764 }
4765 }
4766 t
4767 }
4768
4769 fn variation_font(fvar: Vec<u8>, avar: Option<Vec<u8>>) -> Font {
4770 let mut tables = base_tables(1, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_simple(&[]));
4771 tables.push((b"fvar", fvar));
4772 if let Some(avar) = avar {
4773 tables.push((b"avar", avar));
4774 }
4775 parse(&tables)
4776 }
4777
4778 fn fmd_test_vf_bytes() -> Vec<u8> {
4781 let fvar = fvar_table(
4782 &[(b"wght", 100.0, 400.0, 900.0, 256)],
4783 &[(258, &[400.0]), (259, &[700.0])],
4784 true,
4785 );
4786 let avar = avar_table(&[&[(-1.0, -1.0), (0.0, 0.0), (1.0, 1.0)]]);
4787 let mut tables = base_tables(1, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_simple(&[]));
4788 tables.push((b"fvar", fvar));
4789 tables.push((b"avar", avar));
4790 sfnt(0x0001_0000, &tables)
4791 }
4792
4793 fn log_check(id: &str, subject: &str, ok: bool) {
4794 eprintln!(
4795 "check id={id} subject={subject} outcome={}",
4796 if ok { "PASS" } else { "FAIL" }
4797 );
4798 assert!(ok, "{id}: {subject}");
4799 }
4800
4801 #[test]
4802 fn fvar_axes_tags_and_named_instances() {
4803 let font = variation_font(
4804 fvar_table(
4805 &[
4806 (b"wght", 100.0, 400.0, 900.0, 256),
4807 (b"wdth", 75.0, 100.0, 125.0, 257),
4808 ],
4809 &[(258, &[400.0, 100.0]), (259, &[700.0, 100.0])],
4810 true,
4811 ),
4812 None,
4813 );
4814 let axes = font.axes();
4815 log_check("gk3v.1.axes.count", "two axes", axes.len() == 2);
4816 log_check(
4817 "gk3v.1.axes.wght",
4818 "first tag wght",
4819 axes[0].tag == *b"wght",
4820 );
4821 log_check(
4822 "gk3v.1.axes.wdth",
4823 "second tag wdth",
4824 axes[1].tag == *b"wdth",
4825 );
4826 let wght = font.instance_bounds(*b"wght").expect("wght present");
4827 log_check(
4828 "gk3v.1.bounds.wght",
4829 "wght 100/400/900",
4830 (wght.min - 100.0).abs() < 1e-4
4831 && (wght.default - 400.0).abs() < 1e-4
4832 && (wght.max - 900.0).abs() < 1e-4,
4833 );
4834 log_check(
4835 "gk3v.1.bounds.missing",
4836 "unknown tag is None",
4837 font.instance_bounds(*b"opsz").is_none(),
4838 );
4839 let inst = font.named_instances();
4840 log_check("gk3v.1.inst.count", "two named instances", inst.len() == 2);
4841 log_check(
4842 "gk3v.1.inst.regular",
4843 "Regular at wght=400",
4844 inst[0].subfamily_name_id == 258
4845 && (inst[0].coordinates[0] - 400.0).abs() < 1e-4
4846 && inst[0].postscript_name_id == Some(1258),
4847 );
4848 log_check(
4849 "gk3v.1.inst.bold",
4850 "Bold at wght=700",
4851 inst[1].subfamily_name_id == 259 && (inst[1].coordinates[0] - 700.0).abs() < 1e-4,
4852 );
4853 log_check(
4854 "gk3v.1.static",
4855 "static face has no axes",
4856 parse(&base_tables(
4857 1,
4858 1,
4859 1000,
4860 hmtx_long(&[(500, 0)]),
4861 cmap4_simple(&[]),
4862 ))
4863 .axes()
4864 .is_empty(),
4865 );
4866 }
4867
4868 #[test]
4869 fn avar_clamp_edges_map_to_endpoints() {
4870 let font = variation_font(
4872 fvar_table(&[(b"wght", 100.0, 400.0, 900.0, 256)], &[], false),
4873 Some(avar_table(&[&[
4874 (-1.0, -1.0),
4875 (0.0, 0.0),
4876 (0.5, 0.25),
4877 (1.0, 1.0),
4878 ]])),
4879 );
4880 let below = font.normalized_axis(*b"wght", 0.0);
4881 let at_min = font.normalized_axis(*b"wght", 100.0);
4882 let at_def = font.normalized_axis(*b"wght", 400.0);
4883 let at_max = font.normalized_axis(*b"wght", 900.0);
4884 let above = font.normalized_axis(*b"wght", 2000.0);
4885 log_check(
4886 "gk3v.1.avar.below",
4887 "below-min → -1",
4888 below.is_some_and(|v| (v + 1.0).abs() < 1e-4),
4889 );
4890 log_check(
4891 "gk3v.1.avar.min",
4892 "min → -1",
4893 at_min.is_some_and(|v| (v + 1.0).abs() < 1e-4),
4894 );
4895 log_check(
4896 "gk3v.1.avar.default",
4897 "default → 0",
4898 at_def.is_some_and(|v| v.abs() < 1e-4),
4899 );
4900 log_check(
4901 "gk3v.1.avar.max",
4902 "max → +1",
4903 at_max.is_some_and(|v| (v - 1.0).abs() < 1e-4),
4904 );
4905 log_check(
4906 "gk3v.1.avar.above",
4907 "above-max → +1",
4908 above.is_some_and(|v| (v - 1.0).abs() < 1e-4),
4909 );
4910 let mid = font.normalized_axis(*b"wght", 650.0);
4912 log_check(
4913 "gk3v.1.avar.mid",
4914 "650 → avar(0.5)=0.25",
4915 mid.is_some_and(|v| (v - 0.25).abs() < 1e-3),
4916 );
4917 }
4918
4919 #[test]
4920 fn fvar_avar_truncation_and_hostile_headers() {
4921 let good = fvar_table(&[(b"wght", 100.0, 400.0, 900.0, 256)], &[], false);
4922 let font = variation_font(good[..10].to_vec(), None);
4923 log_check(
4924 "gk3v.1.trunc.header",
4925 "truncated fvar header → no axes",
4926 font.axes().is_empty(),
4927 );
4928
4929 let mut bad_ver = good.clone();
4930 bad_ver[0..2].copy_from_slice(&2u16.to_be_bytes());
4931 log_check(
4932 "gk3v.1.trunc.version",
4933 "fvar major!=1 → no axes",
4934 variation_font(bad_ver, None).axes().is_empty(),
4935 );
4936
4937 let mut tiny_axis = good.clone();
4938 tiny_axis[10..12].copy_from_slice(&8u16.to_be_bytes());
4939 log_check(
4940 "gk3v.1.trunc.axisSize",
4941 "axisSize < 20 → no axes",
4942 variation_font(tiny_axis, None).axes().is_empty(),
4943 );
4944
4945 let mismatched = variation_font(
4947 fvar_table(&[(b"wght", 100.0, 400.0, 900.0, 256)], &[], false),
4948 Some(avar_table(&[
4949 &[(-1.0, -1.0), (1.0, 1.0)],
4950 &[(-1.0, -1.0), (1.0, 1.0)],
4951 ])),
4952 );
4953 log_check(
4954 "gk3v.1.avar.mismatch",
4955 "avar axisCount mismatch → identity",
4956 mismatched.axes().len() == 1
4957 && mismatched
4958 .normalized_axis(*b"wght", 100.0)
4959 .is_some_and(|v| (v + 1.0).abs() < 1e-4),
4960 );
4961 }
4962
4963 #[test]
4964 fn fvar_avar_lcg_mutation_never_panics() {
4965 let base = fmd_test_vf_bytes();
4966 let mut state = 0xC0FF_EE00u64;
4967 let mut lcg = move || {
4968 state = state
4969 .wrapping_mul(6_364_136_223_846_793_005)
4970 .wrapping_add(1);
4971 (state >> 33) as usize
4972 };
4973 for round in 0..128 {
4974 let mut mutated = base.clone();
4975 for _ in 0..8 {
4976 let pos = lcg() % mutated.len();
4977 let bit = 1u8 << (lcg() % 8);
4978 mutated[pos] ^= bit;
4979 }
4980 let outcome = std::panic::catch_unwind(move || {
4981 if let Ok(font) = Font::parse(mutated) {
4982 let _ = font.axes();
4983 let _ = font.named_instances();
4984 let _ = font.instance_bounds(*b"wght");
4985 let _ = font.normalized_axis(*b"wght", 0.0);
4986 let _ = font.normalized_axis(*b"wght", 400.0);
4987 let _ = font.normalized_axis(*b"wght", 9999.0);
4988 }
4989 });
4990 log_check(
4991 "gk3v.1.lcg",
4992 &format!("round {round} no panic"),
4993 outcome.is_ok(),
4994 );
4995 }
4996 for cut in (0..base.len()).step_by(7) {
4997 let truncated = base[..cut].to_vec();
4998 let outcome = std::panic::catch_unwind(move || {
4999 if let Ok(font) = Font::parse(truncated) {
5000 let _ = font.axes();
5001 let _ = font.normalized_axis(*b"wght", 100.0);
5002 }
5003 });
5004 log_check(
5005 "gk3v.1.trunc.sweep",
5006 &format!("cut {cut} no panic"),
5007 outcome.is_ok(),
5008 );
5009 }
5010 }
5011
5012 #[test]
5013 fn fmd_test_vf_fixture_round_trip() {
5014 let bytes = fmd_test_vf_bytes();
5015 let committed = include_bytes!("../fonts/test-variable/FmdTestVF.ttf");
5016 log_check(
5017 "gk3v.1.fixture.bytes",
5018 "committed TTF matches generator",
5019 bytes.as_slice() == committed,
5020 );
5021 let font = Font::parse(bytes).expect("test VF parses");
5022 log_check(
5023 "gk3v.1.fixture.axes",
5024 "committed-shape VF has wght",
5025 font.axes().len() == 1 && font.axes()[0].tag == *b"wght",
5026 );
5027 log_check(
5028 "gk3v.1.fixture.inst",
5029 "Regular + Bold instances",
5030 font.named_instances().len() == 2,
5031 );
5032 if std::env::var("FMD_DUMP_TEST_VF").ok().as_deref() == Some("1") {
5033 std::fs::write("/tmp/FmdTestVF.ttf", fmd_test_vf_bytes()).unwrap();
5034 eprintln!("check id=gk3v.1.dump subject=/tmp/FmdTestVF.ttf outcome=PASS");
5035 }
5036 }
5037}