1#![forbid(unsafe_code)]
23
24#[cfg(feature = "bundled-faces")]
25pub mod bundled;
26pub mod outline;
27
28const MAX_LAYOUT_GLYPHS: usize = 65_536;
35
36const MAX_COVERAGE_GLYPHS: usize = MAX_LAYOUT_GLYPHS;
38const MISSING_GLYPH_REMAP: u16 = u16::MAX;
39
40#[derive(Debug, Clone)]
41struct Cmap4Segment {
42 start: u16,
43 end: u16,
44 id_delta: u16,
45 id_range_offset: u16,
46 id_range_offset_pos: usize,
47}
48
49#[derive(Debug, Clone)]
50struct Cmap4Cache {
51 segments: Vec<Cmap4Segment>,
52 sorted_by_end: bool,
53}
54
55#[derive(Debug, Clone)]
57pub struct Font {
58 data: Vec<u8>,
59 pub units_per_em: u16,
61 pub num_glyphs: u16,
63 pub ascent: i16,
65 pub descent: i16,
67 pub line_gap: i16,
69 num_h_metrics: u16,
70 hmtx_off: usize,
71 cmap_off: usize,
72 cmap_format: u16,
73 cmap4_cache: Option<Cmap4Cache>,
74 glyf: Option<(usize, usize)>,
77 loca_off: Option<usize>,
79 loca_long: bool,
81 kern0: Option<(usize, u16)>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum FontError {
88 BadMagic,
90 MissingTable(&'static str),
92 Truncated,
94 NoUnicodeCmap,
96}
97
98impl core::fmt::Display for FontError {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 match self {
101 Self::BadMagic => write!(f, "not a TrueType/OpenType font"),
102 Self::MissingTable(t) => write!(f, "missing required font table: {t}"),
103 Self::Truncated => write!(f, "font data is truncated"),
104 Self::NoUnicodeCmap => write!(f, "no usable Unicode cmap (format 4/12)"),
105 }
106 }
107}
108
109impl std::error::Error for FontError {}
110
111pub(crate) fn be_u16(d: &[u8], o: usize) -> Option<u16> {
112 let bytes = d.get(o..o.checked_add(2)?)?;
113 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
114}
115pub(crate) fn be_i16(d: &[u8], o: usize) -> Option<i16> {
116 be_u16(d, o).map(|v| v as i16)
117}
118fn be_u32(d: &[u8], o: usize) -> Option<u32> {
119 let bytes = d.get(o..o.checked_add(4)?)?;
120 Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
121}
122
123fn off(base: usize, delta: usize) -> Option<usize> {
124 base.checked_add(delta)
125}
126
127fn off_mul(base: usize, index: usize, stride: usize) -> Option<usize> {
128 base.checked_add(index.checked_mul(stride)?)
129}
130
131fn be_u16_at(d: &[u8], base: usize, delta: usize) -> Option<u16> {
132 be_u16(d, off(base, delta)?)
133}
134
135fn be_u32_at(d: &[u8], base: usize, delta: usize) -> Option<u32> {
136 be_u32(d, off(base, delta)?)
137}
138
139fn bytes_at(d: &[u8], base: usize, len: usize) -> Option<&[u8]> {
140 d.get(base..off(base, len)?)
141}
142
143fn write_u16(d: &mut [u8], off: usize, v: u16) -> Option<()> {
145 let b = v.to_be_bytes();
146 let dst = d.get_mut(off..off.checked_add(2)?)?;
147 dst.copy_from_slice(&b);
148 Some(())
149}
150
151fn write_u32(d: &mut [u8], off: usize, v: u32) -> Option<()> {
153 let b = v.to_be_bytes();
154 let dst = d.get_mut(off..off.checked_add(4)?)?;
155 dst.copy_from_slice(&b);
156 Some(())
157}
158
159fn table_checksum(d: &[u8]) -> u32 {
162 let mut sum: u32 = 0;
163 let mut chunks = d.chunks_exact(4);
164 for c in &mut chunks {
165 sum = sum.wrapping_add(u32::from_be_bytes([c[0], c[1], c[2], c[3]]));
166 }
167 let rem = chunks.remainder();
168 if !rem.is_empty() {
169 let mut buf = [0u8; 4];
170 buf[..rem.len()].copy_from_slice(rem);
171 sum = sum.wrapping_add(u32::from_be_bytes(buf));
172 }
173 sum
174}
175
176fn find_table(d: &[u8], tag: &[u8; 4]) -> Option<usize> {
177 find_table_full(d, tag).map(|(off, _)| off)
178}
179
180fn find_table_full(d: &[u8], tag: &[u8; 4]) -> Option<(usize, usize)> {
182 let num_tables = be_u16(d, 4)? as usize;
183 for i in 0..num_tables {
184 let rec = off_mul(12, i, 16)?;
185 if bytes_at(d, rec, 4)? == tag {
186 return Some((
187 be_u32_at(d, rec, 8)? as usize,
188 be_u32_at(d, rec, 12)? as usize,
189 ));
190 }
191 }
192 None
193}
194
195fn find_kern0(d: &[u8]) -> Option<(usize, u16)> {
197 let (kern, kern_len) = find_table_full(d, b"kern")?;
198 let table_end = kern.checked_add(kern_len)?;
199 let version = be_u16(d, kern)?;
200 let n_tables = be_u16_at(d, kern, 2)? as usize;
201 if version != 0 {
202 return None;
203 }
204
205 let mut sub = off(kern, 4)?;
206 for _ in 0..n_tables {
207 if sub.checked_add(6)? > table_end {
208 return None;
209 }
210 let length = be_u16_at(d, sub, 2)? as usize;
211 let coverage = be_u16_at(d, sub, 4)?;
212 let format = coverage >> 8;
213 let horizontal = coverage & 0x0001 != 0;
214 let minimum = coverage & 0x0002 != 0;
215 let pairs = off(sub, 14)?;
216 if format == 0 && horizontal && !minimum && length >= 14 {
217 let sub_end = sub.checked_add(length)?;
218 if sub_end > table_end {
219 return None;
220 }
221 let n_pairs = be_u16_at(d, sub, 6)?;
222 let bytes_needed = (n_pairs as usize).checked_mul(6)?;
223 if pairs.checked_add(bytes_needed)? <= sub_end {
224 return Some((pairs, n_pairs));
225 }
226 return None;
227 }
228 if length == 0 {
229 return None;
230 }
231 sub = sub.checked_add(length)?;
232 }
233 None
234}
235
236impl Font {
237 pub fn parse(data: Vec<u8>) -> Result<Self, FontError> {
243 let d = data.as_slice();
244 let magic = be_u32(d, 0).ok_or(FontError::Truncated)?;
245 if magic != 0x0001_0000 && magic != 0x7472_7565 && magic != 0x4F54_544F {
247 return Err(FontError::BadMagic);
248 }
249
250 let head = find_table(d, b"head").ok_or(FontError::MissingTable("head"))?;
251 let maxp = find_table(d, b"maxp").ok_or(FontError::MissingTable("maxp"))?;
252 let hhea = find_table(d, b"hhea").ok_or(FontError::MissingTable("hhea"))?;
253 let hmtx = find_table(d, b"hmtx").ok_or(FontError::MissingTable("hmtx"))?;
254 let cmap = find_table(d, b"cmap").ok_or(FontError::MissingTable("cmap"))?;
255
256 let units_per_em =
257 be_u16(d, off(head, 18).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
258 let num_glyphs =
259 be_u16(d, off(maxp, 4).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
260 let ascent =
261 be_i16(d, off(hhea, 4).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
262 let descent =
263 be_i16(d, off(hhea, 6).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
264 let line_gap =
265 be_i16(d, off(hhea, 8).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
266 let num_h_metrics =
267 be_u16(d, off(hhea, 34).ok_or(FontError::Truncated)?).ok_or(FontError::Truncated)?;
268
269 let (cmap_off, cmap_format) = select_cmap(d, cmap).ok_or(FontError::NoUnicodeCmap)?;
270 let cmap4_cache = if cmap_format == 4 {
271 parse_cmap4_cache(d, cmap_off)
272 } else {
273 None
274 };
275
276 let loca_long = off(head, 50)
279 .and_then(|offset| be_i16(d, offset))
280 .unwrap_or(0)
281 != 0;
282 let loca_off = find_table(d, b"loca");
283 let glyf = find_table_full(d, b"glyf");
284 let kern0 = find_kern0(d);
285
286 Ok(Self {
287 data,
288 units_per_em,
289 num_glyphs,
290 ascent,
291 descent,
292 line_gap,
293 num_h_metrics,
294 hmtx_off: hmtx,
295 cmap_off,
296 cmap_format,
297 cmap4_cache,
298 glyf,
299 loca_off,
300 loca_long,
301 kern0,
302 })
303 }
304
305 #[must_use]
307 pub fn has_glyf_outlines(&self) -> bool {
308 self.glyf.is_some() && self.loca_off.is_some()
309 }
310
311 fn glyph_range(&self, gid: u16) -> Option<(usize, usize)> {
315 let loca = self.loca_off?;
316 let (glyf_off, glyf_len) = self.glyf?;
317 let i = gid as usize;
318 let (start, end) = if self.loca_long {
319 (
320 be_u32(&self.data, off_mul(loca, i, 4)?)? as usize,
321 be_u32(&self.data, off_mul(loca, i.checked_add(1)?, 4)?)? as usize,
322 )
323 } else {
324 (
326 be_u16(&self.data, off_mul(loca, i, 2)?)? as usize * 2,
327 be_u16(&self.data, off_mul(loca, i.checked_add(1)?, 2)?)? as usize * 2,
328 )
329 };
330 if end < start || end > glyf_len {
331 return None;
332 }
333 Some((off(glyf_off, start)?, off(glyf_off, end)?))
334 }
335
336 #[must_use]
339 pub fn glyph_data(&self, gid: u16) -> Option<&[u8]> {
340 let (s, e) = self.glyph_range(gid)?;
341 self.data.get(s..e)
342 }
343
344 #[must_use]
347 pub fn glyph_bbox(&self, gid: u16) -> Option<[i16; 4]> {
348 let (s, e) = self.glyph_range(gid)?;
349 if e <= s {
350 return None; }
352 Some([
353 be_i16(&self.data, off(s, 2)?)?,
354 be_i16(&self.data, off(s, 4)?)?,
355 be_i16(&self.data, off(s, 6)?)?,
356 be_i16(&self.data, off(s, 8)?)?,
357 ])
358 }
359
360 #[must_use]
362 pub fn is_composite(&self, gid: u16) -> bool {
363 match self.glyph_range(gid) {
364 Some((s, e)) if e > s => be_i16(&self.data, s).is_some_and(|n| n < 0),
365 _ => false,
366 }
367 }
368
369 #[must_use]
372 pub fn glyph_components(&self, gid: u16) -> Vec<u16> {
373 const ARG_WORDS: u16 = 0x0001;
374 const WE_HAVE_SCALE: u16 = 0x0008;
375 const MORE: u16 = 0x0020;
376 const X_Y_SCALE: u16 = 0x0040;
377 const TWO_BY_TWO: u16 = 0x0080;
378
379 let mut out = Vec::new();
380 let Some((s, e)) = self.glyph_range(gid) else {
381 return out;
382 };
383 if e <= s || be_i16(&self.data, s).is_none_or(|n| n >= 0) {
384 return out;
385 }
386 let Some(mut p) = off(s, 10) else {
387 return out;
388 };
389 while let Some(component_record_end) = off(p, 4) {
390 if component_record_end > e {
391 break;
392 }
393 let Some(flags) = be_u16(&self.data, p) else {
394 break;
395 };
396 let Some(comp) = off(p, 2).and_then(|offset| be_u16(&self.data, offset)) else {
397 break;
398 };
399 let mut step = 4usize + if flags & ARG_WORDS != 0 { 4 } else { 2 };
400 step += if flags & WE_HAVE_SCALE != 0 {
401 2
402 } else if flags & X_Y_SCALE != 0 {
403 4
404 } else if flags & TWO_BY_TWO != 0 {
405 8
406 } else {
407 0
408 };
409 let Some(next) = off(p, step) else {
410 break;
411 };
412 if next > e {
413 break;
414 }
415 out.push(comp);
416 p = next;
417 if flags & MORE == 0 || p >= e {
418 break;
419 }
420 }
421 out
422 }
423
424 #[must_use]
427 pub fn advance_width(&self, gid: u16) -> u16 {
428 let last = self.num_h_metrics.saturating_sub(1);
429 let idx = gid.min(last) as usize;
430 off_mul(self.hmtx_off, idx, 4)
431 .and_then(|offset| be_u16(&self.data, offset))
432 .unwrap_or(0)
433 }
434
435 #[must_use]
438 pub fn left_side_bearing(&self, gid: u16) -> i16 {
439 if self.num_h_metrics == 0 {
440 return 0;
441 }
442 let gid = gid as usize;
443 let num_h_metrics = self.num_h_metrics as usize;
444 let offset = if gid < num_h_metrics {
445 off_mul(self.hmtx_off, gid, 4).and_then(|base| off(base, 2))
446 } else {
447 off_mul(self.hmtx_off, num_h_metrics, 4)
448 .and_then(|base| off_mul(base, gid - num_h_metrics, 2))
449 };
450 offset
451 .and_then(|offset| be_i16(&self.data, offset))
452 .unwrap_or(0)
453 }
454
455 #[must_use]
457 pub fn glyph_index(&self, ch: char) -> u16 {
458 let cp = ch as u32;
459 match self.cmap_format {
460 4 => self.cmap4_lookup(cp).unwrap_or(0),
461 12 => self.cmap12_lookup(cp).unwrap_or(0),
462 _ => 0,
463 }
464 }
465
466 #[must_use]
471 pub fn advance_1000(&self, ch: char) -> u32 {
472 if self.units_per_em == 0 {
473 return 0;
474 }
475 let aw = self.advance_width(self.glyph_index(ch)) as u32;
476 aw * 1000 / self.units_per_em as u32
477 }
478
479 #[must_use]
485 pub fn kerning_between_glyphs(&self, left: u16, right: u16) -> i16 {
486 let Some((pairs, n_pairs)) = self.kern0 else {
487 return 0;
488 };
489 let target = ((left as u32) << 16) | right as u32;
490 let mut lo = 0usize;
491 let mut hi = n_pairs as usize;
492 while lo < hi {
493 let mid = lo + (hi - lo) / 2;
494 let Some(rec) = off_mul(pairs, mid, 6) else {
495 return 0;
496 };
497 let Some(l) = be_u16(&self.data, rec) else {
498 return 0;
499 };
500 let Some(r) = off(rec, 2).and_then(|offset| be_u16(&self.data, offset)) else {
501 return 0;
502 };
503 let key = ((l as u32) << 16) | r as u32;
504 if key == target {
505 return off(rec, 4)
506 .and_then(|offset| be_i16(&self.data, offset))
507 .unwrap_or(0);
508 }
509 if key < target {
510 lo = mid + 1;
511 } else {
512 hi = mid;
513 }
514 }
515 0
516 }
517
518 #[must_use]
520 pub fn kerning(&self, left: char, right: char) -> i16 {
521 self.kerning_between_glyphs(self.glyph_index(left), self.glyph_index(right))
522 }
523
524 #[must_use]
526 pub fn kerning_1000(&self, left: char, right: char) -> i32 {
527 if self.units_per_em == 0 {
528 return 0;
529 }
530 self.kerning(left, right) as i32 * 1000 / self.units_per_em as i32
531 }
532
533 fn cmap4_lookup(&self, cp: u32) -> Option<u16> {
534 if cp > 0xFFFF {
535 return Some(0);
536 }
537 let c = cp as u16;
538 if let Some(cache) = &self.cmap4_cache {
539 return self.cmap4_cached_lookup(c, cache);
540 }
541 self.cmap4_uncached_lookup(c)
542 }
543
544 fn cmap4_cached_lookup(&self, c: u16, cache: &Cmap4Cache) -> Option<u16> {
545 let segment = if cache.sorted_by_end {
546 let idx = cache.segments.partition_point(|seg| seg.end < c);
547 cache.segments.get(idx)
548 } else {
549 cache.segments.iter().find(|seg| c <= seg.end)
550 }?;
551
552 if c < segment.start {
553 return Some(0);
554 }
555 if segment.id_range_offset == 0 {
556 return Some(c.wrapping_add(segment.id_delta));
557 }
558 let gi_addr = off(
559 off(
560 segment.id_range_offset_pos,
561 segment.id_range_offset as usize,
562 )?,
563 2usize.checked_mul((c - segment.start) as usize)?,
564 )?;
565 let g = be_u16(&self.data, gi_addr)?;
566 Some(if g == 0 {
567 0
568 } else {
569 g.wrapping_add(segment.id_delta)
570 })
571 }
572
573 fn cmap4_uncached_lookup(&self, c: u16) -> Option<u16> {
574 let d = &self.data;
575 let base = self.cmap_off;
576 let seg_x2 = be_u16(d, off(base, 6)?)? as usize;
577 let seg_count = seg_x2 / 2;
578 let end_codes = off(base, 14)?;
579 let start_codes = off(off(end_codes, seg_x2)?, 2)?; let id_deltas = off(start_codes, seg_x2)?;
581 let id_range_offsets = off(id_deltas, seg_x2)?;
582 for i in 0..seg_count {
583 let end = be_u16(d, off_mul(end_codes, i, 2)?)?;
584 if c > end {
585 continue;
586 }
587 let start = be_u16(d, off_mul(start_codes, i, 2)?)?;
588 if c < start {
589 return Some(0);
590 }
591 let id_delta = be_u16(d, off_mul(id_deltas, i, 2)?)?;
592 let iro_pos = off_mul(id_range_offsets, i, 2)?;
593 let id_range_offset = be_u16(d, iro_pos)?;
594 if id_range_offset == 0 {
595 return Some(c.wrapping_add(id_delta));
596 }
597 let gi_addr = off(
598 off(iro_pos, id_range_offset as usize)?,
599 2usize.checked_mul((c - start) as usize)?,
600 )?;
601 let g = be_u16(d, gi_addr)?;
602 return Some(if g == 0 { 0 } else { g.wrapping_add(id_delta) });
603 }
604 Some(0)
605 }
606
607 #[must_use]
614 pub fn subset(&self, keep: &[char]) -> Option<Vec<u8>> {
615 let seed: Vec<u16> = keep.iter().map(|&c| self.glyph_index(c)).collect();
616 self.subset_core(&seed, keep, true).map(|(bytes, _)| bytes)
620 }
621
622 pub fn subset_glyphs(
631 &self,
632 glyphs: &[u16],
633 cmap_chars: &[char],
634 ) -> Option<(Vec<u8>, std::collections::BTreeMap<u16, u16>)> {
635 self.subset_core(glyphs, cmap_chars, false)
638 }
639
640 fn subset_core(
641 &self,
642 seed_glyphs: &[u16],
643 cmap_chars: &[char],
644 include_os2: bool,
645 ) -> Option<(Vec<u8>, std::collections::BTreeMap<u16, u16>)> {
646 if !self.has_glyf_outlines() {
649 return None;
650 }
651 let mut set: std::collections::BTreeSet<u16> = std::collections::BTreeSet::new();
652 set.insert(0);
653 for &gid in seed_glyphs {
654 if gid != 0 && gid < self.num_glyphs {
655 set.insert(gid);
656 }
657 }
658 let mut worklist: Vec<u16> = set.iter().copied().collect();
666 while let Some(gid) = worklist.pop() {
667 if self.is_composite(gid) {
668 for c in self.glyph_components(gid) {
669 if c < self.num_glyphs && set.insert(c) {
670 worklist.push(c);
671 }
672 }
673 }
674 }
675 let old_gids: Vec<u16> = set.into_iter().collect(); let mut new_of: std::collections::BTreeMap<u16, u16> = std::collections::BTreeMap::new();
679 let mut new_of_lookup = vec![MISSING_GLYPH_REMAP; usize::from(self.num_glyphs).max(1)];
680 for (i, &g) in old_gids.iter().enumerate() {
681 let new_gid = u16::try_from(i).ok()?;
682 new_of.insert(g, new_gid);
683 *new_of_lookup.get_mut(usize::from(g))? = new_gid;
684 }
685 let n = old_gids.len();
686 let n_u16 = u16::try_from(n).ok()?;
687
688 let mut glyf_bytes: Vec<u8> = Vec::new();
690 let mut loca: Vec<u32> = Vec::with_capacity(n.checked_add(1)?);
691 for &old in &old_gids {
692 loca.push(u32::try_from(glyf_bytes.len()).ok()?);
693 let gb = self.subset_glyph_bytes(old, &new_of_lookup)?;
694 glyf_bytes.extend_from_slice(&gb);
695 while glyf_bytes.len() % 4 != 0 {
698 glyf_bytes.push(0);
699 }
700 }
701 loca.push(u32::try_from(glyf_bytes.len()).ok()?);
702 let mut loca_bytes: Vec<u8> = Vec::with_capacity(loca.len().checked_mul(4)?);
703 for o in &loca {
704 loca_bytes.extend_from_slice(&o.to_be_bytes());
705 }
706
707 let (maxp_off, maxp_len) = find_table_full(&self.data, b"maxp")?;
710 let mut maxp = self.data.get(maxp_off..off(maxp_off, maxp_len)?)?.to_vec();
711 write_u16(&mut maxp, 4, n_u16)?;
712
713 let (hhea_off, hhea_len) = find_table_full(&self.data, b"hhea")?;
715 let mut hhea = self.data.get(hhea_off..off(hhea_off, hhea_len)?)?.to_vec();
716 write_u16(&mut hhea, 34, n_u16)?;
717
718 let mut hmtx: Vec<u8> = Vec::with_capacity(n.checked_mul(4)?);
720 for &old in &old_gids {
721 hmtx.extend_from_slice(&self.advance_width(old).to_be_bytes());
722 hmtx.extend_from_slice(&self.left_side_bearing(old).to_be_bytes());
723 }
724
725 let (head_off, head_len) = find_table_full(&self.data, b"head")?;
727 let mut head = self.data.get(head_off..off(head_off, head_len)?)?.to_vec();
728 write_u32(&mut head, 8, 0)?;
729 write_u16(&mut head, 50, 1)?; let cmap = self.build_cmap4(cmap_chars, &new_of_lookup)?;
733
734 let mut name: Vec<u8> = Vec::with_capacity(6);
736 name.extend_from_slice(&0u16.to_be_bytes());
737 name.extend_from_slice(&0u16.to_be_bytes());
738 name.extend_from_slice(&6u16.to_be_bytes());
739
740 let mut post: Vec<u8> = Vec::with_capacity(32);
742 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 {
760 find_table_full(&self.data, b"OS/2")
761 .and_then(|(o, l)| Some(self.data.get(o..off(o, l)?)?.to_vec()))
762 } else {
763 None
764 };
765
766 let mut tables: Vec<(&[u8; 4], Vec<u8>)> = vec![
768 (b"head", head),
769 (b"hhea", hhea),
770 (b"maxp", maxp),
771 (b"hmtx", hmtx),
772 (b"loca", loca_bytes),
773 (b"glyf", glyf_bytes),
774 (b"cmap", cmap),
775 (b"name", name),
776 (b"post", post),
777 ];
778 if let Some(os2) = os2 {
779 tables.push((b"OS/2", os2));
780 }
781 tables.sort_by(|a, b| a.0.cmp(b.0)); let num_tables = tables.len();
784 let mut pw: usize = 1;
786 let mut es: u16 = 0;
787 while pw * 2 <= num_tables {
788 pw *= 2;
789 es += 1;
790 }
791 let search_range = (pw as u16).wrapping_mul(16);
792 let entry_selector = es;
793 let range_shift = (num_tables as u16)
794 .wrapping_mul(16)
795 .wrapping_sub(search_range);
796
797 let dir_size = 12 + num_tables * 16;
798 let mut body: Vec<u8> = Vec::new();
799 let mut records: Vec<([u8; 4], u32, u32, u32)> = Vec::with_capacity(num_tables);
801 let mut head_offset: usize = 0;
802 for (tag, bytes) in &tables {
803 while (dir_size + body.len()) % 4 != 0 {
805 body.push(0);
806 }
807 let table_offset = dir_size + body.len();
808 if *tag == b"head" {
809 head_offset = table_offset;
810 }
811 let checksum = table_checksum(bytes);
812 records.push((
813 **tag,
814 checksum,
815 u32::try_from(table_offset).ok()?,
816 u32::try_from(bytes.len()).ok()?,
817 ));
818 body.extend_from_slice(bytes);
819 }
820 while body.len() % 4 != 0 {
821 body.push(0);
822 }
823
824 let mut out: Vec<u8> = Vec::with_capacity(dir_size + body.len());
825 out.extend_from_slice(&0x0001_0000u32.to_be_bytes()); out.extend_from_slice(&(num_tables as u16).to_be_bytes());
827 out.extend_from_slice(&search_range.to_be_bytes());
828 out.extend_from_slice(&entry_selector.to_be_bytes());
829 out.extend_from_slice(&range_shift.to_be_bytes());
830 for (tag, checksum, toff, tlen) in &records {
831 out.extend_from_slice(tag);
832 out.extend_from_slice(&checksum.to_be_bytes());
833 out.extend_from_slice(&toff.to_be_bytes());
834 out.extend_from_slice(&tlen.to_be_bytes());
835 }
836 out.extend_from_slice(&body);
837
838 let file_checksum = table_checksum(&out);
840 let adj = 0xB1B0_AFBAu32.wrapping_sub(file_checksum);
841 write_u32(&mut out, off(head_offset, 8)?, adj)?;
842
843 Some((out, new_of))
844 }
845
846 fn subset_glyph_bytes(&self, old: u16, new_of: &[u16]) -> Option<Vec<u8>> {
851 const ARG_WORDS: u16 = 0x0001;
852 const WE_HAVE_SCALE: u16 = 0x0008;
853 const MORE: u16 = 0x0020;
854 const X_Y_SCALE: u16 = 0x0040;
855 const TWO_BY_TWO: u16 = 0x0080;
856 const WE_HAVE_INSTRUCTIONS: u16 = 0x0100;
857
858 let data = self.glyph_data(old).unwrap_or(&[]);
859 if data.is_empty() {
860 return Some(Vec::new());
861 }
862 let num_contours = be_i16(data, 0)?;
863 if num_contours >= 0 {
864 return strip_simple_glyph_instructions(data, num_contours as usize);
865 }
866 let mut out = data.to_vec();
868 let mut p = 10usize; let mut instruction_flags_positions = Vec::new();
870 loop {
871 let flags = be_u16(&out, p)?;
872 if flags & WE_HAVE_INSTRUCTIONS != 0 {
873 instruction_flags_positions.push(p);
874 }
875 let comp_old = be_u16_at(&out, p, 2)?;
876 let comp_new = remapped_gid(new_of, comp_old).unwrap_or(0);
882 let nb = comp_new.to_be_bytes();
883 *out.get_mut(off(p, 2)?)? = nb[0];
884 *out.get_mut(off(p, 3)?)? = nb[1];
885 p = off(p, 4)?;
886 p = off(p, if flags & ARG_WORDS != 0 { 4 } else { 2 })?;
887 if flags & WE_HAVE_SCALE != 0 {
888 p = off(p, 2)?;
889 } else if flags & X_Y_SCALE != 0 {
890 p = off(p, 4)?;
891 } else if flags & TWO_BY_TWO != 0 {
892 p = off(p, 8)?;
893 }
894 if flags & MORE == 0 {
895 break;
896 }
897 }
898 if !instruction_flags_positions.is_empty() {
899 for flags_pos in instruction_flags_positions {
900 let flags = be_u16(&out, flags_pos)?;
901 write_u16(&mut out, flags_pos, flags & !WE_HAVE_INSTRUCTIONS)?;
902 }
903 let instruction_len = be_u16(&out, p)? as usize;
904 let instruction_start = off(p, 2)?;
905 let instruction_end = off(instruction_start, instruction_len)?;
906 if instruction_end > out.len() {
907 return None;
908 }
909 out.drain(p..instruction_end);
910 }
911 Some(out)
912 }
913
914 fn build_cmap4(&self, keep: &[char], new_of: &[u16]) -> Option<Vec<u8>> {
918 let mut codes: std::collections::BTreeMap<u16, u16> = std::collections::BTreeMap::new();
920 for &ch in keep {
921 let cp = ch as u32;
922 if cp >= 0xFFFF {
923 continue;
924 }
925 let old = self.glyph_index(ch);
926 let Some(ng) = remapped_gid(new_of, old) else {
930 continue;
931 };
932 codes.insert(cp as u16, ng);
933 }
934 let entries: Vec<(u16, u16)> = codes.into_iter().collect();
935 let seg_count = entries.len().checked_add(1)?; let sub_len = 16usize.checked_add(seg_count.checked_mul(8)?)?;
937 let sub_len_u16 = u16::try_from(sub_len).ok()?;
938 let seg_count_x2 = u16::try_from(seg_count.checked_mul(2)?).ok()?;
939
940 let mut pw: usize = 1;
941 let mut es: u16 = 0;
942 while pw * 2 <= seg_count {
943 pw *= 2;
944 es += 1;
945 }
946 let search_range = u16::try_from(pw.checked_mul(2)?).ok()?;
947 let entry_selector = es;
948 let range_shift = seg_count_x2.checked_sub(search_range)?;
949
950 let mut sub: Vec<u8> = Vec::with_capacity(sub_len);
951 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());
956 sub.extend_from_slice(&entry_selector.to_be_bytes());
957 sub.extend_from_slice(&range_shift.to_be_bytes());
958 for &(code, _) in &entries {
960 sub.extend_from_slice(&code.to_be_bytes());
961 }
962 sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
963 sub.extend_from_slice(&0u16.to_be_bytes());
965 for &(code, _) in &entries {
967 sub.extend_from_slice(&code.to_be_bytes());
968 }
969 sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
970 for &(code, ng) in &entries {
972 sub.extend_from_slice(&ng.wrapping_sub(code).to_be_bytes());
973 }
974 sub.extend_from_slice(&1u16.to_be_bytes());
976 for _ in &entries {
978 sub.extend_from_slice(&0u16.to_be_bytes());
979 }
980 sub.extend_from_slice(&0u16.to_be_bytes());
981
982 let mut cmap: Vec<u8> = Vec::with_capacity(12 + sub.len());
983 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);
989 Some(cmap)
990 }
991
992 fn cmap12_lookup(&self, cp: u32) -> Option<u16> {
993 let d = &self.data;
994 let base = self.cmap_off;
995 let num_groups = be_u32(d, off(base, 12)?)? as usize;
996 for i in 0..num_groups {
997 let g = off_mul(off(base, 16)?, i, 12)?;
998 let start = be_u32(d, g)?;
999 let end = be_u32(d, off(g, 4)?)?;
1000 if cp >= start && cp <= end {
1001 let start_gid = be_u32(d, off(g, 8)?)?;
1002 let gid = start_gid.checked_add(cp - start)?;
1003 return Some((gid & 0xFFFF) as u16);
1004 }
1005 }
1006 Some(0)
1007 }
1008}
1009
1010fn remapped_gid(new_of: &[u16], old: u16) -> Option<u16> {
1011 match new_of.get(usize::from(old)).copied()? {
1012 MISSING_GLYPH_REMAP => None,
1013 gid => Some(gid),
1014 }
1015}
1016
1017fn strip_simple_glyph_instructions(data: &[u8], contour_count: usize) -> Option<Vec<u8>> {
1018 let instruction_len_offset = off(10, contour_count.checked_mul(2)?)?;
1019 let instruction_len = be_u16(data, instruction_len_offset)? as usize;
1020 let instruction_start = off(instruction_len_offset, 2)?;
1021 let instruction_end = off(instruction_start, instruction_len)?;
1022 if instruction_end > data.len() {
1023 return None;
1024 }
1025
1026 let mut out = Vec::with_capacity(data.len().saturating_sub(instruction_len));
1027 out.extend_from_slice(data.get(..instruction_len_offset)?);
1028 out.extend_from_slice(&0u16.to_be_bytes());
1029 out.extend_from_slice(data.get(instruction_end..)?);
1030 Some(out)
1031}
1032
1033fn parse_cmap4_cache(d: &[u8], base: usize) -> Option<Cmap4Cache> {
1034 let seg_x2 = be_u16(d, off(base, 6)?)? as usize;
1035 let seg_count = seg_x2 / 2;
1036 let end_codes = off(base, 14)?;
1037 let start_codes = off(off(end_codes, seg_x2)?, 2)?;
1038 let id_deltas = off(start_codes, seg_x2)?;
1039 let id_range_offsets = off(id_deltas, seg_x2)?;
1040
1041 let mut segments = Vec::with_capacity(seg_count);
1042 let mut sorted_by_end = true;
1043 let mut prev_end: Option<u16> = None;
1044 for i in 0..seg_count {
1045 let end = be_u16(d, off_mul(end_codes, i, 2)?)?;
1046 let start = be_u16(d, off_mul(start_codes, i, 2)?)?;
1047 let id_delta = be_u16(d, off_mul(id_deltas, i, 2)?)?;
1048 let id_range_offset_pos = off_mul(id_range_offsets, i, 2)?;
1049 let id_range_offset = be_u16(d, id_range_offset_pos)?;
1050 if prev_end.is_some_and(|prev| end < prev) {
1051 sorted_by_end = false;
1052 }
1053 prev_end = Some(end);
1054 segments.push(Cmap4Segment {
1055 start,
1056 end,
1057 id_delta,
1058 id_range_offset,
1059 id_range_offset_pos,
1060 });
1061 }
1062
1063 Some(Cmap4Cache {
1064 segments,
1065 sorted_by_end,
1066 })
1067}
1068
1069fn select_cmap(d: &[u8], cmap: usize) -> Option<(usize, u16)> {
1073 let num = be_u16(d, off(cmap, 2)?)? as usize;
1074 let mut best: Option<(usize, u16, u8)> = None; for i in 0..num {
1076 let rec = off_mul(off(cmap, 4)?, i, 8)?;
1077 let platform = be_u16(d, rec)?;
1078 let encoding = be_u16(d, off(rec, 2)?)?;
1079 let sub = off(cmap, be_u32(d, off(rec, 4)?)? as usize)?;
1080 let format = be_u16(d, sub)?;
1081 let unicode = matches!((platform, encoding), (0, _) | (3, 1) | (3, 10));
1082 if !unicode {
1083 continue;
1084 }
1085 let rank = match format {
1086 12 => 3,
1087 4 => {
1088 if (platform, encoding) == (3, 1) || platform == 0 {
1089 2
1090 } else {
1091 1
1092 }
1093 }
1094 _ => continue,
1095 };
1096 if best.is_none_or(|(_, _, r)| rank > r) {
1097 best = Some((sub, format, rank));
1098 }
1099 }
1100 best.map(|(off, fmt, _)| (off, fmt))
1101}
1102
1103#[derive(Clone, Debug)]
1113enum ClassDef {
1114 Format1 { start: u16, classes: Vec<u16> },
1116 Format2 { ranges: Vec<(u16, u16, u16)> },
1118}
1119
1120impl ClassDef {
1121 fn class(&self, g: u16) -> u16 {
1123 match self {
1124 ClassDef::Format1 { start, classes } => {
1125 if g >= *start {
1126 let i = (g - *start) as usize;
1127 if i < classes.len() {
1128 return classes[i];
1129 }
1130 }
1131 0
1132 }
1133 ClassDef::Format2 { ranges } => {
1134 for &(s, e, c) in ranges {
1135 if g >= s && g <= e {
1136 return c;
1137 }
1138 }
1139 0
1140 }
1141 }
1142 }
1143}
1144
1145#[derive(Clone, Debug)]
1148enum KernSubtable {
1149 Format1 {
1151 pairs: std::collections::BTreeMap<(u16, u16), i16>,
1152 },
1153 Format2 {
1155 coverage: Vec<u16>,
1157 class1: ClassDef,
1158 class2: ClassDef,
1159 class1_count: u16,
1162 class2_count: u16,
1163 matrix: Vec<i16>,
1166 },
1167}
1168
1169impl KernSubtable {
1170 fn lookup(&self, left: u16, right: u16) -> Option<i16> {
1176 match self {
1177 KernSubtable::Format1 { pairs } => pairs.get(&(left, right)).copied(),
1178 KernSubtable::Format2 {
1179 coverage,
1180 class1,
1181 class2,
1182 class1_count,
1183 class2_count,
1184 matrix,
1185 } => {
1186 if coverage.binary_search(&left).is_err() {
1188 return None;
1189 }
1190 let c1 = class1.class(left) as usize;
1191 let c2 = class2.class(right) as usize;
1192 let c1_count = *class1_count as usize;
1193 let c2_count = *class2_count as usize;
1194 if c1 >= c1_count || c2 >= c2_count {
1196 return Some(0);
1197 }
1198 if matrix.is_empty() {
1200 return Some(0);
1201 }
1202 let idx = c1.checked_mul(c2_count)?.checked_add(c2)?;
1203 Some(matrix.get(idx).copied().unwrap_or(0))
1206 }
1207 }
1208 }
1209}
1210
1211#[derive(Clone, Debug, Default)]
1217pub struct Kerning {
1218 subtables: Vec<KernSubtable>,
1219}
1220
1221impl Kerning {
1222 #[must_use]
1226 pub fn pair(&self, left: u16, right: u16) -> i16 {
1227 for st in &self.subtables {
1228 if let Some(v) = st.lookup(left, right) {
1229 return v;
1230 }
1231 }
1232 0
1233 }
1234}
1235
1236fn value_record_size(value_format: u16) -> usize {
1238 value_format.count_ones() as usize * 2
1239}
1240
1241fn value_record_x_advance(d: &[u8], off: usize, value_format: u16) -> Option<i16> {
1247 const X_ADVANCE: u16 = 0x0004;
1248 if value_format & X_ADVANCE == 0 {
1249 return Some(0);
1250 }
1251 let skip = (value_format & 0x0003).count_ones() as usize * 2;
1252 be_i16(d, off.checked_add(skip)?)
1253}
1254
1255fn parse_coverage_glyphs(d: &[u8], cov: usize) -> Option<Vec<u16>> {
1258 let format = be_u16(d, cov)?;
1259 match format {
1260 1 => {
1261 let count = be_u16_at(d, cov, 2)? as usize;
1262 let mut v = Vec::with_capacity(count.min(d.len() / 2 + 1));
1263 for i in 0..count {
1264 v.push(be_u16(d, off_mul(off(cov, 4)?, i, 2)?)?);
1265 }
1266 Some(v)
1267 }
1268 2 => {
1269 let range_count = be_u16_at(d, cov, 2)? as usize;
1270 let mut by_index: std::collections::BTreeMap<u32, u16> =
1273 std::collections::BTreeMap::new();
1274 let mut total: usize = 0;
1280 for i in 0..range_count {
1281 let rec = off_mul(off(cov, 4)?, i, 6)?;
1282 let start = be_u16(d, rec)? as u32;
1283 let end = be_u16_at(d, rec, 2)? as u32;
1284 let start_idx = be_u16_at(d, rec, 4)? as u32;
1285 if end < start {
1286 continue;
1287 }
1288 total = total.checked_add((end - start + 1) as usize)?;
1289 if total > MAX_COVERAGE_GLYPHS {
1290 return None;
1291 }
1292 let mut g = start;
1293 let mut idx = start_idx;
1294 while g <= end {
1295 by_index.insert(idx, g as u16);
1296 g += 1;
1297 idx += 1;
1298 }
1299 }
1300 Some(by_index.into_values().collect())
1301 }
1302 _ => None,
1303 }
1304}
1305
1306fn parse_class_def(d: &[u8], cd: usize) -> Option<ClassDef> {
1308 let format = be_u16(d, cd)?;
1309 match format {
1310 1 => {
1311 let start = be_u16_at(d, cd, 2)?;
1312 let count = be_u16_at(d, cd, 4)? as usize;
1313 let mut classes = Vec::with_capacity(count.min(d.len() / 2 + 1));
1314 for i in 0..count {
1315 classes.push(be_u16(d, off_mul(off(cd, 6)?, i, 2)?)?);
1316 }
1317 Some(ClassDef::Format1 { start, classes })
1318 }
1319 2 => {
1320 let range_count = be_u16_at(d, cd, 2)? as usize;
1321 let mut ranges = Vec::with_capacity(range_count.min(d.len() / 6 + 1));
1322 for i in 0..range_count {
1323 let rec = off_mul(off(cd, 4)?, i, 6)?;
1324 let s = be_u16(d, rec)?;
1325 let e = be_u16_at(d, rec, 2)?;
1326 let c = be_u16_at(d, rec, 4)?;
1327 ranges.push((s, e, c));
1328 }
1329 Some(ClassDef::Format2 { ranges })
1330 }
1331 _ => None,
1332 }
1333}
1334
1335fn parse_pair_subtable(d: &[u8], sub: usize) -> Option<KernSubtable> {
1337 let pos_format = be_u16(d, sub)?;
1338 match pos_format {
1339 1 => parse_pair_format1(d, sub),
1340 2 => parse_pair_format2(d, sub),
1341 _ => None,
1342 }
1343}
1344
1345fn parse_pair_format1(d: &[u8], sub: usize) -> Option<KernSubtable> {
1347 let cov_off = be_u16_at(d, sub, 2)? as usize;
1348 let vf1 = be_u16_at(d, sub, 4)?;
1349 let vf2 = be_u16_at(d, sub, 6)?;
1350 let pair_set_count = be_u16_at(d, sub, 8)? as usize;
1351
1352 let rec1_size = value_record_size(vf1);
1353 let rec2_size = value_record_size(vf2);
1354 let pair_rec_size = off(2, off(rec1_size, rec2_size)?)?;
1356
1357 let coverage = parse_coverage_glyphs(d, off(sub, cov_off)?)?;
1358
1359 let mut pairs: std::collections::BTreeMap<(u16, u16), i16> = std::collections::BTreeMap::new();
1360
1361 let mut work: usize = 0;
1365 for i in 0..pair_set_count {
1366 work += 1;
1367 if work > MAX_LAYOUT_GLYPHS {
1368 break;
1369 }
1370 let Some(left_glyph) = coverage.get(i).copied() else {
1372 continue;
1373 };
1374 let Some(ps_off) = off_mul(off(sub, 10)?, i, 2).and_then(|slot| be_u16(d, slot)) else {
1375 continue;
1376 };
1377 let Some(ps) = off(sub, ps_off as usize) else {
1378 continue;
1379 };
1380 let Some(pair_value_count) = be_u16(d, ps) else {
1381 continue;
1382 };
1383 let Some(mut p) = off(ps, 2) else {
1384 continue;
1385 };
1386 for _ in 0..pair_value_count {
1387 work += 1;
1388 if work > MAX_LAYOUT_GLYPHS {
1389 break;
1390 }
1391 let Some(second) = be_u16(d, p) else {
1392 break;
1393 };
1394 let x_adv = off(p, 2)
1395 .and_then(|value_off| value_record_x_advance(d, value_off, vf1))
1396 .unwrap_or(0);
1397 pairs.entry((left_glyph, second)).or_insert(x_adv);
1399 let Some(np) = p.checked_add(pair_rec_size) else {
1400 break;
1401 };
1402 p = np;
1403 }
1404 }
1405
1406 Some(KernSubtable::Format1 { pairs })
1407}
1408
1409fn parse_pair_format2(d: &[u8], sub: usize) -> Option<KernSubtable> {
1411 let cov_off = be_u16_at(d, sub, 2)? as usize;
1412 let vf1 = be_u16_at(d, sub, 4)?;
1413 let vf2 = be_u16_at(d, sub, 6)?;
1414 let class_def1_off = be_u16_at(d, sub, 8)? as usize;
1415 let class_def2_off = be_u16_at(d, sub, 10)? as usize;
1416 let class1_count = be_u16_at(d, sub, 12)? as usize;
1417 let class2_count = be_u16_at(d, sub, 14)? as usize;
1418
1419 let rec1_size = value_record_size(vf1);
1420 let rec2_size = value_record_size(vf2);
1421 let class_rec_size = off(rec1_size, rec2_size)?;
1422
1423 let matrix_base = off(sub, 16)?;
1425 let cell_count = class1_count.checked_mul(class2_count)?;
1426
1427 let matrix: Vec<i16> = if class_rec_size == 0 {
1430 Vec::new()
1432 } else {
1433 let needed = cell_count.checked_mul(class_rec_size)?;
1434 let end = matrix_base.checked_add(needed)?;
1435 if end > d.len() {
1436 return None;
1438 }
1439 let mut m = Vec::with_capacity(cell_count);
1440 for idx in 0..cell_count {
1441 let cell = off_mul(matrix_base, idx, class_rec_size)?;
1442 let x_adv = value_record_x_advance(d, cell, vf1).unwrap_or(0);
1444 m.push(x_adv);
1445 }
1446 m
1447 };
1448
1449 let mut coverage = parse_coverage_glyphs(d, off(sub, cov_off)?)?;
1450 coverage.sort_unstable();
1451
1452 let class1 = parse_class_def(d, off(sub, class_def1_off)?)?;
1453 let class2 = parse_class_def(d, off(sub, class_def2_off)?)?;
1454
1455 Some(KernSubtable::Format2 {
1456 coverage,
1457 class1,
1458 class2,
1459 class1_count: class1_count as u16,
1460 class2_count: class2_count as u16,
1461 matrix,
1462 })
1463}
1464
1465fn resolve_extension(d: &[u8], sub: usize) -> Option<(u16, usize)> {
1468 let pos_format = be_u16(d, sub)?;
1469 if pos_format != 1 {
1470 return None;
1471 }
1472 let ext_type = be_u16_at(d, sub, 2)?;
1473 let ext_off = be_u32_at(d, sub, 4)? as usize;
1474 Some((ext_type, sub.checked_add(ext_off)?))
1475}
1476
1477impl Font {
1478 #[must_use]
1483 pub fn gpos_kerning(&self) -> Kerning {
1484 self.parse_gpos_kerning().unwrap_or_default()
1485 }
1486
1487 fn parse_gpos_kerning(&self) -> Option<Kerning> {
1488 let d = &self.data;
1489 let (gpos, _gpos_len) = find_table_full(d, b"GPOS")?;
1490
1491 let feature_list_off = be_u16_at(d, gpos, 6)? as usize;
1493 let lookup_list_off = be_u16_at(d, gpos, 8)? as usize;
1494 let feature_list = off(gpos, feature_list_off)?;
1495 let lookup_list = off(gpos, lookup_list_off)?;
1496
1497 let feature_count = be_u16(d, feature_list)? as usize;
1499 let mut lookup_indices: Vec<u16> = Vec::new();
1500 for i in 0..feature_count {
1501 let rec = off_mul(off(feature_list, 2)?, i, 6)?;
1503 let Some(tag) = bytes_at(d, rec, 4) else {
1504 break;
1505 };
1506 if tag != b"kern" {
1507 continue;
1508 }
1509 let Some(feat_off) = be_u16_at(d, rec, 4) else {
1510 continue;
1511 };
1512 let Some(feat) = off(feature_list, feat_off as usize) else {
1513 continue;
1514 };
1515 let Some(lookup_index_count) = be_u16_at(d, feat, 2) else {
1517 continue;
1518 };
1519 for j in 0..lookup_index_count as usize {
1520 if let Some(idx) = off_mul(off(feat, 4)?, j, 2).and_then(|slot| be_u16(d, slot)) {
1521 if !lookup_indices.contains(&idx) {
1522 lookup_indices.push(idx);
1523 }
1524 }
1525 }
1526 }
1527
1528 let lookup_count = be_u16(d, lookup_list)? as usize;
1530 let mut subtables: Vec<KernSubtable> = Vec::new();
1531
1532 for &li in &lookup_indices {
1533 let li = li as usize;
1534 if li >= lookup_count {
1535 continue;
1536 }
1537 let Some(lookup_off) =
1538 off_mul(off(lookup_list, 2)?, li, 2).and_then(|slot| be_u16(d, slot))
1539 else {
1540 continue;
1541 };
1542 let Some(lookup) = off(lookup_list, lookup_off as usize) else {
1543 continue;
1544 };
1545 let Some(lookup_type) = be_u16(d, lookup) else {
1547 continue;
1548 };
1549 let Some(sub_count) = be_u16_at(d, lookup, 4) else {
1550 continue;
1551 };
1552
1553 for s in 0..sub_count as usize {
1554 let Some(sub_off) = off_mul(off(lookup, 6)?, s, 2).and_then(|slot| be_u16(d, slot))
1555 else {
1556 continue;
1557 };
1558 let Some(sub) = off(lookup, sub_off as usize) else {
1559 continue;
1560 };
1561
1562 match lookup_type {
1563 2 => {
1564 if let Some(st) = parse_pair_subtable(d, sub) {
1565 subtables.push(st);
1566 }
1567 }
1568 9 => {
1569 if let Some((ext_type, real_sub)) = resolve_extension(d, sub) {
1571 if ext_type == 2 {
1572 if let Some(st) = parse_pair_subtable(d, real_sub) {
1573 subtables.push(st);
1574 }
1575 }
1576 }
1577 }
1578 _ => {}
1579 }
1580 }
1581 }
1582
1583 Some(Kerning { subtables })
1584 }
1585}
1586
1587#[derive(Clone, Debug)]
1595struct LigRule {
1596 components: Vec<u16>,
1597 ligature: u16,
1598}
1599
1600#[derive(Clone, Debug, Default)]
1603pub struct Ligatures {
1604 rules: std::collections::BTreeMap<u16, Vec<LigRule>>,
1606}
1607
1608impl Ligatures {
1609 #[must_use]
1611 pub fn is_empty(&self) -> bool {
1612 self.rules.is_empty()
1613 }
1614
1615 #[must_use]
1619 pub fn substitute(&self, gids: &[u16]) -> Vec<u16> {
1620 self.substitute_with_spans(gids)
1621 .into_iter()
1622 .map(|(g, _)| g)
1623 .collect()
1624 }
1625
1626 #[must_use]
1631 pub fn substitute_with_spans(&self, gids: &[u16]) -> Vec<(u16, usize)> {
1632 let mut out = Vec::with_capacity(gids.len());
1633 let mut i = 0;
1634 while i < gids.len() {
1635 let mut applied = false;
1636 if let Some(rules) = self.rules.get(&gids[i]) {
1637 for r in rules {
1638 let n = r.components.len();
1639 if i + 1 + n <= gids.len() && gids[i + 1..i + 1 + n] == r.components[..] {
1640 out.push((r.ligature, n + 1));
1641 i += n + 1;
1642 applied = true;
1643 break;
1644 }
1645 }
1646 }
1647 if !applied {
1648 out.push((gids[i], 1));
1649 i += 1;
1650 }
1651 }
1652 out
1653 }
1654}
1655
1656impl Font {
1657 #[must_use]
1662 pub fn gsub_ligatures(&self) -> Ligatures {
1663 self.parse_gsub_ligatures().unwrap_or_default()
1664 }
1665
1666 fn parse_gsub_ligatures(&self) -> Option<Ligatures> {
1667 let d = &self.data;
1668 let (gsub, _) = find_table_full(d, b"GSUB")?;
1669 let feature_list = off(gsub, be_u16_at(d, gsub, 6)? as usize)?;
1670 let lookup_list = off(gsub, be_u16_at(d, gsub, 8)? as usize)?;
1671
1672 let feature_count = be_u16(d, feature_list)? as usize;
1674 let mut lookup_indices: Vec<u16> = Vec::new();
1675 for i in 0..feature_count {
1676 let rec = off_mul(off(feature_list, 2)?, i, 6)?;
1677 let Some(tag) = bytes_at(d, rec, 4) else {
1678 break;
1679 };
1680 if tag != b"liga" {
1681 continue;
1682 }
1683 let Some(feat_off) = be_u16_at(d, rec, 4) else {
1684 continue;
1685 };
1686 let Some(feat) = off(feature_list, feat_off as usize) else {
1687 continue;
1688 };
1689 let Some(n) = be_u16_at(d, feat, 2) else {
1690 continue;
1691 };
1692 for j in 0..n as usize {
1693 if let Some(idx) = off_mul(off(feat, 4)?, j, 2).and_then(|slot| be_u16(d, slot)) {
1694 if !lookup_indices.contains(&idx) {
1695 lookup_indices.push(idx);
1696 }
1697 }
1698 }
1699 }
1700
1701 let lookup_count = be_u16(d, lookup_list)? as usize;
1702 let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
1703 std::collections::BTreeMap::new();
1704 for &li in &lookup_indices {
1705 let li = li as usize;
1706 if li >= lookup_count {
1707 continue;
1708 }
1709 let Some(lookup_off) =
1710 off_mul(off(lookup_list, 2)?, li, 2).and_then(|slot| be_u16(d, slot))
1711 else {
1712 continue;
1713 };
1714 let Some(lookup) = off(lookup_list, lookup_off as usize) else {
1715 continue;
1716 };
1717 let Some(lookup_type) = be_u16(d, lookup) else {
1718 continue;
1719 };
1720 let Some(sub_count) = be_u16_at(d, lookup, 4) else {
1721 continue;
1722 };
1723 for s in 0..sub_count as usize {
1724 let Some(sub_off) = off_mul(off(lookup, 6)?, s, 2).and_then(|slot| be_u16(d, slot))
1725 else {
1726 continue;
1727 };
1728 let Some(sub) = off(lookup, sub_off as usize) else {
1729 continue;
1730 };
1731 match lookup_type {
1732 4 => parse_ligature_subst(d, sub, &mut rules),
1733 7 => {
1735 if let Some((ext_type, real)) = resolve_extension(d, sub) {
1736 if ext_type == 4 {
1737 parse_ligature_subst(d, real, &mut rules);
1738 }
1739 }
1740 }
1741 _ => {}
1742 }
1743 }
1744 }
1745 for v in rules.values_mut() {
1747 v.sort_by_key(|r| std::cmp::Reverse(r.components.len()));
1748 }
1749 Some(Ligatures { rules })
1750 }
1751}
1752
1753fn parse_ligature_subst(
1755 d: &[u8],
1756 sub: usize,
1757 rules: &mut std::collections::BTreeMap<u16, Vec<LigRule>>,
1758) {
1759 let Some(format) = be_u16(d, sub) else {
1760 return;
1761 };
1762 if format != 1 {
1763 return;
1764 }
1765 let Some(cov_off) = be_u16_at(d, sub, 2) else {
1766 return;
1767 };
1768 let Some(set_count) = be_u16_at(d, sub, 4) else {
1769 return;
1770 };
1771 let Some(coverage) = off(sub, cov_off as usize).and_then(|cov| parse_coverage_glyphs(d, cov))
1772 else {
1773 return;
1774 };
1775 let Some(set_offsets) = off(sub, 6) else {
1776 return;
1777 };
1778 let mut work: usize = 0;
1783 for i in 0..set_count as usize {
1784 work += 1;
1785 if work > MAX_LAYOUT_GLYPHS {
1786 return;
1787 }
1788 let Some(first) = coverage.get(i).copied() else {
1790 continue;
1791 };
1792 let Some(set_off) = off_mul(set_offsets, i, 2).and_then(|slot| be_u16(d, slot)) else {
1793 continue;
1794 };
1795 let Some(lig_set) = off(sub, set_off as usize) else {
1796 continue;
1797 };
1798 let Some(lig_count) = be_u16(d, lig_set) else {
1799 continue;
1800 };
1801 let Some(lig_offsets) = off(lig_set, 2) else {
1802 continue;
1803 };
1804 for j in 0..lig_count as usize {
1805 work += 1;
1806 if work > MAX_LAYOUT_GLYPHS {
1807 return;
1808 }
1809 let Some(lig_off) = off_mul(lig_offsets, j, 2).and_then(|slot| be_u16(d, slot)) else {
1810 continue;
1811 };
1812 let Some(lig) = off(lig_set, lig_off as usize) else {
1813 continue;
1814 };
1815 let Some(lig_glyph) = be_u16(d, lig) else {
1816 continue;
1817 };
1818 let Some(comp_count) = be_u16_at(d, lig, 2) else {
1819 continue;
1820 };
1821 if comp_count == 0 {
1822 continue;
1823 }
1824 let mut components = Vec::with_capacity(comp_count as usize - 1);
1826 let mut ok = true;
1827 let Some(component_base) = off(lig, 4) else {
1828 continue;
1829 };
1830 for k in 0..(comp_count as usize - 1) {
1831 match off_mul(component_base, k, 2).and_then(|slot| be_u16(d, slot)) {
1832 Some(g) => components.push(g),
1833 None => {
1834 ok = false;
1835 break;
1836 }
1837 }
1838 }
1839 if ok {
1840 rules.entry(first).or_default().push(LigRule {
1841 components,
1842 ligature: lig_glyph,
1843 });
1844 }
1845 }
1846 }
1847}
1848
1849#[cfg(test)]
1850#[cfg_attr(coverage_nightly, coverage(off))]
1851#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
1852mod dos_tests {
1853 use super::{MAX_COVERAGE_GLYPHS, parse_coverage_glyphs};
1854
1855 fn be(v: u16) -> [u8; 2] {
1856 v.to_be_bytes()
1857 }
1858
1859 #[test]
1860 fn coverage_format2_valid_range_expands() {
1861 let mut d = Vec::new();
1863 d.extend_from_slice(&be(2));
1864 d.extend_from_slice(&be(1));
1865 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();
1869 assert_eq!(got, (10u16..=20).collect::<Vec<_>>());
1870 }
1871
1872 #[test]
1873 fn coverage_format2_overclaiming_table_is_rejected_not_expanded() {
1874 let mut d = Vec::new();
1877 d.extend_from_slice(&be(2));
1878 d.extend_from_slice(&be(2)); for _ in 0..2 {
1880 d.extend_from_slice(&be(0)); d.extend_from_slice(&be(0xFFFF)); d.extend_from_slice(&be(0)); }
1884 assert!(parse_coverage_glyphs(&d, 0).is_none());
1885 assert_eq!(MAX_COVERAGE_GLYPHS, 65_536);
1887 }
1888}
1889
1890#[cfg(test)]
1891#[cfg_attr(coverage_nightly, coverage(off))]
1892#[allow(clippy::unwrap_used, clippy::expect_used)]
1893mod subset_degradation_tests {
1894 use super::{
1895 Font, MISSING_GLYPH_REMAP, be_i16, be_u16, find_table_full, strip_simple_glyph_instructions,
1896 };
1897
1898 fn cm_regular() -> Font {
1900 let bytes = std::fs::read(concat!(
1901 env!("CARGO_MANIFEST_DIR"),
1902 "/fonts/computer-modern/cmunrm.ttf"
1903 ))
1904 .expect("read bundled font");
1905 Font::parse(bytes).expect("parse bundled font")
1906 }
1907
1908 fn all_faces() -> Vec<Font> {
1909 let base = env!("CARGO_MANIFEST_DIR");
1910 [
1911 "/fonts/computer-modern/cmunrm.ttf",
1912 "/fonts/computer-modern/cmunbx.ttf",
1913 "/fonts/computer-modern/cmunti.ttf",
1914 "/fonts/computer-modern/cmunbi.ttf",
1915 "/fonts/computer-modern/cmuntt.ttf",
1916 "/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf",
1917 "/fonts/ibm-plex-sans/IBMPlexSans-Bold.ttf",
1918 "/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf",
1919 "/fonts/ibm-plex-sans/IBMPlexSans-BoldItalic.ttf",
1920 ]
1921 .iter()
1922 .filter_map(|p| Font::parse(std::fs::read(format!("{base}{p}")).ok()?).ok())
1923 .collect()
1924 }
1925
1926 fn test_remap(font: &Font, pairs: &[(u16, u16)]) -> Vec<u16> {
1927 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs).max(1)];
1928 for &(old, new) in pairs {
1929 if let Some(slot) = new_of.get_mut(usize::from(old)) {
1930 *slot = new;
1931 }
1932 }
1933 new_of
1934 }
1935
1936 fn simple_instruction_len(data: &[u8]) -> Option<usize> {
1937 let contours = be_i16(data, 0)?;
1938 if contours < 0 {
1939 return None;
1940 }
1941 let instruction_len_offset = 10usize.checked_add((contours as usize).checked_mul(2)?)?;
1942 be_u16(data, instruction_len_offset).map(usize::from)
1943 }
1944
1945 #[test]
1946 fn simple_glyph_instruction_stripper_zeroes_length_and_removes_bytes() {
1947 let mut glyph = Vec::new();
1948 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");
1956 assert_eq!(simple_instruction_len(&stripped), Some(0));
1957 assert_eq!(stripped.len(), glyph.len() - 3);
1958 assert_eq!(&stripped[stripped.len() - 3..], &[0x11, 0x22, 0x33]);
1959 }
1960
1961 #[test]
1962 fn subset_glyph_bytes_strips_simple_instructions_when_present() {
1963 let Some((font, gid, original_len)) = all_faces().into_iter().find_map(|font| {
1964 (1..font.num_glyphs).find_map(|gid| {
1965 let data = font.glyph_data(gid)?;
1966 let len = simple_instruction_len(data)?;
1967 (len > 0).then_some((font.clone(), gid, len))
1968 })
1969 }) else {
1970 eprintln!("skipping: bundled fonts have no hinted simple glyphs");
1971 return;
1972 };
1973
1974 let new_of = test_remap(&font, &[(0u16, 0u16), (gid, 1u16)]);
1975
1976 let stripped = font
1977 .subset_glyph_bytes(gid, &new_of)
1978 .expect("hinted simple glyph should subset");
1979 assert_eq!(simple_instruction_len(&stripped), Some(0));
1980 assert_eq!(
1981 stripped.len(),
1982 font.glyph_data(gid).expect("original glyph").len() - original_len
1983 );
1984 }
1985
1986 #[test]
1987 fn subset_hmtx_preserves_true_left_side_bearings() {
1988 let (font, ch, old_gid, old_lsb) = all_faces()
1989 .into_iter()
1990 .find_map(|font| {
1991 (33u8..=126).find_map(|byte| {
1992 let ch = char::from(byte);
1993 let gid = font.glyph_index(ch);
1994 let lsb = font.left_side_bearing(gid);
1995 (gid != 0 && lsb != 0).then_some((font.clone(), ch, gid, lsb))
1996 })
1997 })
1998 .expect("at least one bundled printable glyph has a nonzero lsb");
1999
2000 let (bytes, remap) = font
2001 .subset_glyphs(&[old_gid], &[ch])
2002 .expect("subset with nonzero-lsb glyph");
2003 let subset = Font::parse(bytes).expect("subset re-parses");
2004 let new_gid = remap[&old_gid];
2005 assert_eq!(subset.left_side_bearing(new_gid), old_lsb);
2006 }
2007
2008 #[test]
2009 fn html_subset_carries_verbatim_os2_while_pdf_subset_stays_lean() {
2010 for font in all_faces() {
2016 let (src_off, src_len) = find_table_full(&font.data, b"OS/2")
2017 .expect("every bundled face carries an OS/2 table");
2018 let src_os2 = font.data[src_off..src_off + src_len].to_vec();
2019
2020 let html_bytes = font.subset(&['A', 'b']).expect("html subset");
2021 let html_font = Font::parse(html_bytes).expect("html subset re-parses");
2022 let (o, l) = find_table_full(&html_font.data, b"OS/2")
2023 .expect("html subset must keep OS/2 for browser sanitizers");
2024 assert_eq!(
2025 &html_font.data[o..o + l],
2026 &src_os2[..],
2027 "OS/2 must be copied verbatim"
2028 );
2029
2030 let gid = font.glyph_index('A');
2031 assert_ne!(gid, 0, "bundled faces must map 'A'");
2032 let (pdf_bytes, _) = font.subset_glyphs(&[gid], &['A']).expect("pdf subset");
2033 assert!(
2034 find_table_full(&pdf_bytes, b"OS/2").is_none(),
2035 "pdf subset must not grow an OS/2 table (golden bytes)"
2036 );
2037 assert!(Font::parse(pdf_bytes).is_ok());
2038 }
2039 }
2040
2041 #[test]
2042 fn subset_skips_cmap_char_whose_glyph_is_absent_from_the_set() {
2043 let font = cm_regular();
2047 let g_b = font.glyph_index('B');
2048 assert_ne!(g_b, 0, "test font must map 'B'");
2049 let out = font.subset_glyphs(&[g_b], &['A', 'B']);
2051 let (bytes, _) = out.expect("un-subsettable cmap char must be skipped, not abort");
2052 assert!(Font::parse(bytes).is_ok());
2054 }
2055
2056 #[test]
2057 fn simple_instruction_len_rejects_composite_data() {
2058 assert_eq!(simple_instruction_len(&(-1i16).to_be_bytes()), None);
2061 }
2062
2063 #[test]
2064 fn subset_glyph_bytes_substitutes_notdef_for_a_missing_component() {
2065 let (font, comp) = all_faces()
2068 .into_iter()
2069 .find_map(|f| {
2070 (1..f.num_glyphs)
2071 .find(|&g| f.is_composite(g))
2072 .map(|g| (f, g))
2073 })
2074 .expect("at least one bundled face has a composite glyph");
2075 let new_of = test_remap(&font, &[(0u16, 0u16), (comp, 1u16)]);
2078 let bytes = font
2079 .subset_glyph_bytes(comp, &new_of)
2080 .expect("missing component must be substituted, not abort");
2081 assert!(!bytes.is_empty(), "a composite glyph is non-empty");
2082 }
2083}
2084
2085#[cfg(test)]
2086#[cfg_attr(coverage_nightly, coverage(off))]
2087#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
2088mod synthetic_font_tests {
2089 use super::*;
2090
2091 fn push16(out: &mut Vec<u8>, v: u16) {
2094 out.extend_from_slice(&v.to_be_bytes());
2095 }
2096
2097 fn push_i16(out: &mut Vec<u8>, v: i16) {
2098 out.extend_from_slice(&v.to_be_bytes());
2099 }
2100
2101 fn push32(out: &mut Vec<u8>, v: u32) {
2102 out.extend_from_slice(&v.to_be_bytes());
2103 }
2104
2105 fn sfnt(magic: u32, tables: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
2110 let mut out = Vec::new();
2111 push32(&mut out, magic);
2112 push16(&mut out, u16::try_from(tables.len()).unwrap());
2113 out.extend_from_slice(&[0u8; 6]); let mut offset = 12 + tables.len() * 16;
2115 let mut body = Vec::new();
2116 for (tag, bytes) in tables {
2117 out.extend_from_slice(&tag[..]);
2118 push32(&mut out, 0); push32(&mut out, u32::try_from(offset).unwrap());
2120 push32(&mut out, u32::try_from(bytes.len()).unwrap());
2121 offset += bytes.len();
2122 body.extend_from_slice(bytes);
2123 }
2124 out.extend_from_slice(&body);
2125 out
2126 }
2127
2128 fn head_table(upem: u16, loca_long: bool) -> Vec<u8> {
2129 let mut t = vec![0u8; 54];
2130 t[18..20].copy_from_slice(&upem.to_be_bytes());
2131 t[50..52].copy_from_slice(&u16::from(loca_long).to_be_bytes());
2132 t
2133 }
2134
2135 fn maxp_table(num_glyphs: u16) -> Vec<u8> {
2136 let mut t = vec![0u8; 6];
2137 t[4..6].copy_from_slice(&num_glyphs.to_be_bytes());
2138 t
2139 }
2140
2141 fn hhea_table(num_h_metrics: u16) -> Vec<u8> {
2142 let mut t = vec![0u8; 36];
2143 t[4..6].copy_from_slice(&700i16.to_be_bytes());
2144 t[6..8].copy_from_slice(&(-200i16).to_be_bytes());
2145 t[8..10].copy_from_slice(&50i16.to_be_bytes());
2146 t[34..36].copy_from_slice(&num_h_metrics.to_be_bytes());
2147 t
2148 }
2149
2150 fn hmtx_long(metrics: &[(u16, i16)]) -> Vec<u8> {
2151 let mut t = Vec::new();
2152 for &(aw, lsb) in metrics {
2153 push16(&mut t, aw);
2154 push_i16(&mut t, lsb);
2155 }
2156 t
2157 }
2158
2159 fn cmap4_table(segs: &[(u16, u16, u16, u16)], glyph_id_array: &[u8]) -> Vec<u8> {
2163 let seg_count = segs.len();
2164 let mut t = Vec::new();
2165 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(
2172 &mut t,
2173 u16::try_from(16 + seg_count * 8 + glyph_id_array.len()).unwrap(),
2174 );
2175 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 {
2181 push16(&mut t, end);
2182 }
2183 push16(&mut t, 0); for &(_, start, _, _) in segs {
2185 push16(&mut t, start);
2186 }
2187 for &(_, _, delta, _) in segs {
2188 push16(&mut t, delta);
2189 }
2190 for &(_, _, _, iro) in segs {
2191 push16(&mut t, iro);
2192 }
2193 t.extend_from_slice(glyph_id_array);
2194 t
2195 }
2196
2197 fn cmap4_simple(map: &[(u16, u16)]) -> Vec<u8> {
2200 let mut segs: Vec<(u16, u16, u16, u16)> = map
2201 .iter()
2202 .map(|&(code, gid)| (code, code, gid.wrapping_sub(code), 0))
2203 .collect();
2204 segs.push((0xFFFF, 0xFFFF, 1, 0));
2205 cmap4_table(&segs, &[])
2206 }
2207
2208 fn cmap12_table(groups: &[(u32, u32, u32)]) -> Vec<u8> {
2210 let mut t = Vec::new();
2211 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());
2219 push32(&mut t, 0); push32(&mut t, u32::try_from(groups.len()).unwrap());
2221 for &(start, end, gid) in groups {
2222 push32(&mut t, start);
2223 push32(&mut t, end);
2224 push32(&mut t, gid);
2225 }
2226 t
2227 }
2228
2229 fn base_tables(
2230 num_glyphs: u16,
2231 num_h_metrics: u16,
2232 upem: u16,
2233 hmtx: Vec<u8>,
2234 cmap: Vec<u8>,
2235 ) -> Vec<(&'static [u8; 4], Vec<u8>)> {
2236 vec![
2237 (b"head", head_table(upem, false)),
2238 (b"maxp", maxp_table(num_glyphs)),
2239 (b"hhea", hhea_table(num_h_metrics)),
2240 (b"hmtx", hmtx),
2241 (b"cmap", cmap),
2242 ]
2243 }
2244
2245 fn parse(tables: &[(&[u8; 4], Vec<u8>)]) -> Font {
2246 Font::parse(sfnt(0x0001_0000, tables)).expect("synthetic font parses")
2247 }
2248
2249 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> {
2261 let mut g = Vec::new();
2262 push_i16(&mut g, -1);
2263 for v in bbox {
2264 push_i16(&mut g, v);
2265 }
2266 for &(flags, gid, payload) in records {
2267 push16(&mut g, flags);
2268 push16(&mut g, gid);
2269 g.extend_from_slice(payload);
2270 }
2271 g.extend_from_slice(trailer);
2272 g
2273 }
2274
2275 fn simple_glyph16() -> Vec<u8> {
2277 let mut g = Vec::new();
2278 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
2284 }
2285
2286 fn zoo_font() -> Font {
2293 let glyphs: Vec<Vec<u8>> = vec![
2294 Vec::new(),
2295 1i16.to_be_bytes().to_vec(),
2296 composite_glyph([1, 2, 3, 4], &[(WHS, 5, &[0, 0, 0x40, 0])], &[]),
2297 composite_glyph([0; 4], &[(XYS, 5, &[0, 0, 0x40, 0, 0x40, 0])], &[]),
2298 composite_glyph(
2299 [0; 4],
2300 &[(TWO, 5, &[0, 0, 0x40, 0, 0, 0, 0, 0, 0x40, 0])],
2301 &[],
2302 ),
2303 simple_glyph16(),
2304 composite_glyph(
2305 [0; 4],
2306 &[(ARGW | MORE, 2, &[0, 0, 0, 0]), (0, 3, &[0, 0])],
2307 &[],
2308 ),
2309 composite_glyph([0; 4], &[(MORE, 5, &[0, 0])], &[]),
2310 composite_glyph([0; 4], &[(INSTR, 5, &[0, 0])], &[0xFF, 0xFF]),
2311 composite_glyph([0; 4], &[(0, 900, &[0, 0])], &[]),
2312 composite_glyph([0; 4], &[(MORE, 5, &[0, 0])], &[0, 0]),
2313 composite_glyph([0; 4], &[(TWO, 5, &[0, 0, 0x40, 0])], &[]),
2314 composite_glyph([0; 4], &[(INSTR, 5, &[0, 0])], &[0x00, 0x02, 0xAA, 0xBB]),
2315 ];
2316 let mut glyf = Vec::new();
2317 let mut loca = Vec::new();
2318 push16(&mut loca, 0);
2319 for g in &glyphs {
2320 glyf.extend_from_slice(g);
2321 push16(&mut loca, u16::try_from(glyf.len() / 2).unwrap());
2322 }
2323 let metrics: Vec<(u16, i16)> = (0..13u16).map(|g| (500 + g, g as i16)).collect();
2324 let mut tables = base_tables(13, 13, 1000, hmtx_long(&metrics), cmap4_simple(&[]));
2325 tables.push((b"loca", loca));
2326 tables.push((b"glyf", glyf));
2327 parse(&tables)
2328 }
2329
2330 fn truncated_composite_font(keep: usize) -> Font {
2333 let glyph = composite_glyph([0; 4], &[(0, 5, &[0, 0])], &[]);
2334 assert_eq!(glyph.len(), 16);
2335 let mut loca = Vec::new();
2336 push16(&mut loca, 0);
2337 push16(&mut loca, 8);
2338 let mut tables = base_tables(1, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_simple(&[]));
2339 tables.push((b"loca", loca));
2340 tables.push((b"glyf", glyph));
2341 let mut bytes = sfnt(0x0001_0000, &tables);
2342 bytes.truncate(bytes.len() - (16 - keep));
2343 Font::parse(bytes).expect("glyf payload is lazily read")
2344 }
2345
2346 fn kern0_table(pairs: &[(u16, u16, i16)]) -> Vec<u8> {
2348 let mut t = Vec::new();
2349 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 {
2357 push16(&mut t, l);
2358 push16(&mut t, r);
2359 push_i16(&mut t, v);
2360 }
2361 t
2362 }
2363
2364 fn gpos_table(ext_format: u16, ext_type: u16, lookup_index: u16, pos_format: u16) -> Vec<u8> {
2367 let mut g = Vec::new();
2368 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");
2375 push16(&mut g, 8); push16(&mut g, 0); push16(&mut g, 1); push16(&mut g, lookup_index);
2380 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);
2390 push16(&mut g, ext_type);
2391 push32(&mut g, 8); push16(&mut g, pos_format);
2394 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);
2405 push16(&mut g, 1);
2406 push16(&mut g, 5);
2407 assert_eq!(g.len(), 68);
2408 g
2409 }
2410
2411 fn gpos_kerning_of(table: Vec<u8>) -> Kerning {
2414 let mut tables = base_tables(
2415 2,
2416 2,
2417 1000,
2418 hmtx_long(&[(600, 0), (600, 0)]),
2419 cmap4_simple(&[]),
2420 );
2421 tables.push((b"GPOS", table));
2422 parse(&tables).gpos_kerning()
2423 }
2424
2425 fn gpos_font(ext_format: u16, ext_type: u16, lookup_index: u16, pos_format: u16) -> Kerning {
2426 gpos_kerning_of(gpos_table(ext_format, ext_type, lookup_index, pos_format))
2427 }
2428
2429 fn gsub_table(ext_format: u16, ext_type: u16, lookup_index: u16) -> Vec<u8> {
2432 let mut g = Vec::new();
2433 push32(&mut g, 0x0001_0000);
2434 push16(&mut g, 0); push16(&mut g, 10); push16(&mut g, 24); push16(&mut g, 1);
2439 g.extend_from_slice(b"liga");
2440 push16(&mut g, 8); push16(&mut g, 0);
2443 push16(&mut g, 1);
2444 push16(&mut g, lookup_index);
2445 push16(&mut g, 1);
2447 push16(&mut g, 4); push16(&mut g, 7);
2450 push16(&mut g, 0);
2451 push16(&mut g, 1);
2452 push16(&mut g, 8); push16(&mut g, ext_format);
2455 push16(&mut g, ext_type);
2456 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);
2468 push16(&mut g, 3);
2469 push16(&mut g, 11);
2470 push16(&mut g, 12);
2471 push16(&mut g, 77);
2473 push16(&mut g, 2);
2474 push16(&mut g, 11);
2475 push16(&mut g, 1);
2477 push16(&mut g, 1);
2478 push16(&mut g, 10);
2479 assert_eq!(g.len(), 78);
2480 g
2481 }
2482
2483 fn gsub_ligatures_of(table: Vec<u8>) -> Ligatures {
2485 let mut tables = base_tables(
2486 2,
2487 2,
2488 1000,
2489 hmtx_long(&[(600, 0), (600, 0)]),
2490 cmap4_simple(&[]),
2491 );
2492 tables.push((b"GSUB", table));
2493 parse(&tables).gsub_ligatures()
2494 }
2495
2496 fn gsub_font(ext_format: u16, ext_type: u16, lookup_index: u16) -> Ligatures {
2497 gsub_ligatures_of(gsub_table(ext_format, ext_type, lookup_index))
2498 }
2499
2500 #[test]
2503 fn parse_error_variants_and_display_messages() {
2504 assert_eq!(Font::parse(Vec::new()).err(), Some(FontError::Truncated));
2505 assert_eq!(
2506 Font::parse(vec![0x00, 0x02, 0x00, 0x00]).err(),
2507 Some(FontError::BadMagic)
2508 );
2509
2510 let mut tables: Vec<(&[u8; 4], Vec<u8>)> = Vec::new();
2512 let steps: [(&'static [u8; 4], &'static str, Vec<u8>); 4] = [
2513 (b"head", "head", head_table(1000, false)),
2514 (b"maxp", "maxp", maxp_table(1)),
2515 (b"hhea", "hhea", hhea_table(1)),
2516 (b"hmtx", "hmtx", hmtx_long(&[(500, 0)])),
2517 ];
2518 for (tag, name, table) in steps {
2519 assert_eq!(
2520 Font::parse(sfnt(0x0001_0000, &tables)).err(),
2521 Some(FontError::MissingTable(name))
2522 );
2523 tables.push((tag, table));
2524 }
2525 assert_eq!(
2526 Font::parse(sfnt(0x0001_0000, &tables)).err(),
2527 Some(FontError::MissingTable("cmap"))
2528 );
2529
2530 let mut bad_cmap = Vec::new();
2533 push16(&mut bad_cmap, 0);
2534 push16(&mut bad_cmap, 2);
2535 push16(&mut bad_cmap, 1); push16(&mut bad_cmap, 0);
2537 push32(&mut bad_cmap, 20);
2538 push16(&mut bad_cmap, 3); push16(&mut bad_cmap, 1);
2540 push32(&mut bad_cmap, 20);
2541 push16(&mut bad_cmap, 6); tables.push((b"cmap", bad_cmap));
2543 assert_eq!(
2544 Font::parse(sfnt(0x0001_0000, &tables)).err(),
2545 Some(FontError::NoUnicodeCmap)
2546 );
2547
2548 let short_head: Vec<(&[u8; 4], Vec<u8>)> = vec![
2550 (b"maxp", maxp_table(1)),
2551 (b"hhea", hhea_table(1)),
2552 (b"hmtx", hmtx_long(&[(500, 0)])),
2553 (b"cmap", cmap4_simple(&[])),
2554 (b"head", vec![0u8; 10]),
2555 ];
2556 assert_eq!(
2557 Font::parse(sfnt(0x0001_0000, &short_head)).err(),
2558 Some(FontError::Truncated)
2559 );
2560
2561 assert_eq!(
2562 FontError::BadMagic.to_string(),
2563 "not a TrueType/OpenType font"
2564 );
2565 assert_eq!(
2566 FontError::MissingTable("hhea").to_string(),
2567 "missing required font table: hhea"
2568 );
2569 assert_eq!(FontError::Truncated.to_string(), "font data is truncated");
2570 assert_eq!(
2571 FontError::NoUnicodeCmap.to_string(),
2572 "no usable Unicode cmap (format 4/12)"
2573 );
2574 }
2575
2576 #[test]
2577 fn parse_accepts_true_and_otto_magics() {
2578 let tables = base_tables(
2579 3,
2580 3,
2581 2048,
2582 hmtx_long(&[(500, 1), (510, 2), (520, 3)]),
2583 cmap4_simple(&[(0x41, 1)]),
2584 );
2585 let t = Font::parse(sfnt(0x7472_7565, &tables)).expect("'true' magic parses");
2586 assert_eq!(t.units_per_em, 2048);
2587 assert_eq!(t.num_glyphs, 3);
2588 assert_eq!(t.ascent, 700);
2589 assert_eq!(t.descent, -200);
2590 assert_eq!(t.line_gap, 50);
2591 assert_eq!(t.glyph_index('A'), 1);
2592
2593 let o = Font::parse(sfnt(0x4F54_544F, &tables)).expect("'OTTO' magic parses");
2595 assert!(!o.has_glyf_outlines());
2596 assert_eq!(o.subset(&['A']), None);
2597 assert_eq!(o.glyph_bbox(1), None);
2598 assert_eq!(o.glyph_data(1), None);
2599 assert!(!o.is_composite(1));
2600 assert!(o.glyph_components(1).is_empty());
2601 }
2602
2603 #[test]
2606 fn left_side_bearing_reads_trailing_run_and_zero_metrics() {
2607 let mut hmtx = hmtx_long(&[(500, 50)]);
2610 push_i16(&mut hmtx, -7);
2611 push_i16(&mut hmtx, 33);
2612 let font = parse(&base_tables(3, 1, 1000, hmtx, cmap4_simple(&[])));
2613 assert_eq!(font.left_side_bearing(0), 50);
2614 assert_eq!(font.left_side_bearing(1), -7);
2615 assert_eq!(font.left_side_bearing(2), 33);
2616 assert_eq!(font.advance_width(0), 500);
2617 assert_eq!(font.advance_width(2), 500);
2618
2619 let font0 = parse(&base_tables(1, 0, 1000, Vec::new(), cmap4_simple(&[])));
2621 assert_eq!(font0.left_side_bearing(0), 0);
2622 }
2623
2624 #[test]
2627 fn legacy_kern_pair_and_char_kerning() {
2628 let pairs = [(1u16, 2u16, -30i16), (1, 3, 15), (4, 1, 7)];
2629 let metrics: Vec<(u16, i16)> = (0..8u16).map(|g| (600 + g, 0)).collect();
2630 let mut tables = base_tables(
2631 8,
2632 8,
2633 1000,
2634 hmtx_long(&metrics),
2635 cmap4_simple(&[(0x41, 1), (0x56, 2)]),
2636 );
2637 tables.push((b"kern", kern0_table(&pairs)));
2638 let font = parse(&tables);
2639 assert_eq!(font.kerning_between_glyphs(1, 2), -30);
2640 assert_eq!(font.kerning_between_glyphs(1, 3), 15);
2641 assert_eq!(font.kerning_between_glyphs(4, 1), 7);
2642 assert_eq!(font.kerning_between_glyphs(2, 1), 0);
2643 assert_eq!(font.kerning_between_glyphs(1, 4), 0);
2644 assert_eq!(font.kerning('A', 'V'), -30);
2645 assert_eq!(font.kerning_1000('A', 'V'), -30); assert_eq!(font.advance_1000('A'), 601);
2647
2648 let mut zero = base_tables(
2650 8,
2651 8,
2652 0,
2653 hmtx_long(&metrics),
2654 cmap4_simple(&[(0x41, 1), (0x56, 2)]),
2655 );
2656 zero.push((b"kern", kern0_table(&pairs)));
2657 let z = parse(&zero);
2658 assert_eq!(z.units_per_em, 0);
2659 assert_eq!(z.advance_1000('A'), 0);
2660 assert_eq!(z.kerning_1000('A', 'V'), 0);
2661 }
2662
2663 #[test]
2664 fn legacy_kern_skips_short_vertical_minimum_and_format2_subtables() {
2665 let mut k = Vec::new();
2666 push16(&mut k, 0); push16(&mut k, 5); push16(&mut k, 0);
2670 push16(&mut k, 10);
2671 push16(&mut k, 0x0001);
2672 k.extend_from_slice(&[0u8; 4]);
2673 push16(&mut k, 0);
2675 push16(&mut k, 14);
2676 push16(&mut k, 0x0000);
2677 k.extend_from_slice(&[0u8; 8]);
2678 push16(&mut k, 0);
2680 push16(&mut k, 14);
2681 push16(&mut k, 0x0003);
2682 k.extend_from_slice(&[0u8; 8]);
2683 push16(&mut k, 0);
2685 push16(&mut k, 14);
2686 push16(&mut k, 0x0201);
2687 k.extend_from_slice(&[0u8; 8]);
2688 push16(&mut k, 0);
2690 push16(&mut k, 20);
2691 push16(&mut k, 0x0001);
2692 push16(&mut k, 1); k.extend_from_slice(&[0u8; 6]);
2694 push16(&mut k, 3);
2695 push16(&mut k, 4);
2696 push_i16(&mut k, -11);
2697
2698 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
2699 tables.push((b"kern", k));
2700 let font = parse(&tables);
2701 assert_eq!(font.kerning_between_glyphs(3, 4), -11);
2702 assert_eq!(font.kerning_between_glyphs(3, 5), 0);
2703 }
2704
2705 #[test]
2706 fn legacy_kern_rejects_malformed_table_headers() {
2707 fn kern_font(kern: Vec<u8>) -> Font {
2708 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
2709 tables.push((b"kern", kern));
2710 parse(&tables)
2711 }
2712 let mut v1 = kern0_table(&[(1, 2, -30)]);
2714 v1[0..2].copy_from_slice(&1u16.to_be_bytes());
2715 assert_eq!(kern_font(v1).kerning_between_glyphs(1, 2), 0);
2716
2717 let mut zero_len = Vec::new();
2719 push16(&mut zero_len, 0);
2720 push16(&mut zero_len, 2);
2721 push16(&mut zero_len, 0); push16(&mut zero_len, 0); push16(&mut zero_len, 0x0000); zero_len.extend_from_slice(&[0u8; 8]);
2725 assert_eq!(kern_font(zero_len).kerning_between_glyphs(1, 2), 0);
2726
2727 let mut walk_off = Vec::new();
2729 push16(&mut walk_off, 0);
2730 push16(&mut walk_off, 2);
2731 push16(&mut walk_off, 0);
2732 push16(&mut walk_off, 14);
2733 push16(&mut walk_off, 0x0000); walk_off.extend_from_slice(&[0u8; 8]);
2735 assert_eq!(kern_font(walk_off).kerning_between_glyphs(1, 2), 0);
2736
2737 let mut overlong = Vec::new();
2739 push16(&mut overlong, 0);
2740 push16(&mut overlong, 1);
2741 push16(&mut overlong, 0);
2742 push16(&mut overlong, 200); push16(&mut overlong, 0x0001);
2744 overlong.extend_from_slice(&[0u8; 8]);
2745 assert_eq!(kern_font(overlong).kerning_between_glyphs(1, 2), 0);
2746
2747 let mut hungry = Vec::new();
2749 push16(&mut hungry, 0);
2750 push16(&mut hungry, 1);
2751 push16(&mut hungry, 0);
2752 push16(&mut hungry, 20); push16(&mut hungry, 0x0001);
2754 push16(&mut hungry, 3); hungry.extend_from_slice(&[0u8; 12]);
2756 assert_eq!(kern_font(hungry).kerning_between_glyphs(1, 2), 0);
2757
2758 let mut vertical_only = Vec::new();
2760 push16(&mut vertical_only, 0);
2761 push16(&mut vertical_only, 1);
2762 push16(&mut vertical_only, 0);
2763 push16(&mut vertical_only, 14);
2764 push16(&mut vertical_only, 0x0000);
2765 vertical_only.extend_from_slice(&[0u8; 8]);
2766 assert_eq!(kern_font(vertical_only).kerning_between_glyphs(1, 2), 0);
2767 }
2768
2769 #[test]
2770 fn legacy_kern_truncated_pair_records_kern_to_zero() {
2771 let pairs = [(1u16, 2u16, -30i16), (1, 3, 15), (4, 1, 7), (5, 5, 9)];
2776 for chop in [10usize, 16] {
2777 let mut tables = base_tables(8, 1, 1000, hmtx_long(&[(600, 0)]), cmap4_simple(&[]));
2778 tables.push((b"kern", kern0_table(&pairs)));
2779 let mut bytes = sfnt(0x0001_0000, &tables);
2780 bytes.truncate(bytes.len() - chop);
2781 let font = Font::parse(bytes).expect("kern pair payload is lazily read");
2782 assert_eq!(font.kerning_between_glyphs(1, 2), 0, "chop={chop}");
2783 }
2784 }
2785
2786 #[test]
2789 fn glyph_range_rejects_inverted_and_overlong_loca_entries() {
2790 let mut loca = Vec::new();
2793 push16(&mut loca, 2);
2794 push16(&mut loca, 1);
2795 push16(&mut loca, 3);
2796 let mut tables = base_tables(
2797 2,
2798 2,
2799 1000,
2800 hmtx_long(&[(500, 0), (500, 0)]),
2801 cmap4_simple(&[]),
2802 );
2803 tables.push((b"loca", loca));
2804 tables.push((b"glyf", vec![0u8; 4]));
2805 let font = parse(&tables);
2806 assert!(font.has_glyf_outlines());
2807 assert_eq!(font.glyph_data(0), None);
2808 assert_eq!(font.glyph_data(1), None);
2809 assert_eq!(font.glyph_bbox(0), None);
2810 assert!(!font.is_composite(0));
2811 }
2812
2813 #[test]
2814 fn glyph_components_walk_all_transform_variants() {
2815 let font = zoo_font();
2816 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));
2826 assert!(!font.is_composite(1));
2827 assert!(!font.is_composite(0));
2828 assert_eq!(font.glyph_bbox(2), Some([1, 2, 3, 4]));
2829 assert_eq!(font.glyph_bbox(0), None);
2830 assert_eq!(font.glyph_data(0), Some(&[][..]));
2831 }
2832
2833 #[test]
2834 fn glyph_components_stop_at_truncated_component_records() {
2835 for keep in [1usize, 10, 12] {
2838 let font = truncated_composite_font(keep);
2839 assert!(font.glyph_components(0).is_empty(), "keep={keep}");
2840 assert_eq!(font.glyph_data(0), None, "keep={keep}");
2841 }
2842 }
2843
2844 #[test]
2847 fn subset_rewrites_component_ids_across_transform_variants() {
2848 let font = zoo_font();
2849 let (bytes, remap) = font.subset_glyphs(&[2, 3, 4], &[]).expect("subset");
2850 let remap: Vec<(u16, u16)> = remap.into_iter().collect();
2851 assert_eq!(remap, vec![(0, 0), (2, 1), (3, 2), (4, 3), (5, 4)]);
2852 let sub = Font::parse(bytes).expect("subset re-parses");
2853 assert_eq!(sub.num_glyphs, 5);
2854 assert_eq!(sub.glyph_components(1), vec![4]);
2855 assert_eq!(sub.glyph_components(2), vec![4]);
2856 assert_eq!(sub.glyph_components(3), vec![4]);
2857 assert_eq!(sub.glyph_bbox(1), Some([1, 2, 3, 4]));
2858 assert_eq!(sub.advance_width(1), 502);
2859 assert_eq!(sub.left_side_bearing(1), 2);
2860 assert_eq!(sub.advance_width(4), 505);
2861 assert_eq!(sub.left_side_bearing(4), 5);
2862 }
2863
2864 #[test]
2865 fn subset_closure_skips_component_ids_past_num_glyphs() {
2866 let font = zoo_font();
2867 let (bytes, remap) = font.subset_glyphs(&[9], &[]).expect("subset");
2868 assert_eq!(remap.get(&9).copied(), Some(1));
2869 assert_eq!(remap.len(), 2); let sub = Font::parse(bytes).expect("subset re-parses");
2871 assert_eq!(sub.num_glyphs, 2);
2872 assert_eq!(sub.glyph_components(1), vec![0]);
2874 }
2875
2876 #[test]
2877 fn subset_shares_a_component_between_two_composites() {
2878 let font = zoo_font();
2879 let (bytes, remap) = font.subset_glyphs(&[2, 4], &[]).expect("subset");
2880 let remap: Vec<(u16, u16)> = remap.into_iter().collect();
2881 assert_eq!(remap, vec![(0, 0), (2, 1), (4, 2), (5, 3)]);
2882 let sub = Font::parse(bytes).expect("subset re-parses");
2883 assert_eq!(sub.glyph_components(1), vec![3]);
2884 assert_eq!(sub.glyph_components(2), vec![3]);
2885 }
2886
2887 #[test]
2888 fn subset_aborts_on_composite_whose_last_record_dangles_more() {
2889 let font = zoo_font();
2894 assert_eq!(font.glyph_components(7), vec![5]);
2895 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
2896 new_of[0] = 0;
2897 new_of[5] = 1;
2898 new_of[7] = 2;
2899 assert_eq!(font.subset_glyph_bytes(7, &new_of), None);
2900 assert!(font.subset_glyphs(&[7], &[]).is_none());
2901 }
2902
2903 #[test]
2904 fn subset_strips_valid_composite_instructions_and_clears_the_flag() {
2905 let font = zoo_font();
2906 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
2907 new_of[0] = 0;
2908 new_of[5] = 1;
2909 new_of[12] = 2;
2910 let out = font
2911 .subset_glyph_bytes(12, &new_of)
2912 .expect("valid instructions strip");
2913 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");
2917 assert_eq!(remap.get(&12).copied(), Some(2));
2918 let sub = Font::parse(bytes).expect("subset re-parses");
2919 assert_eq!(sub.glyph_components(2), vec![1]);
2920 }
2921
2922 #[test]
2923 fn subset_cmap_skips_supplementary_plane_chars() {
2924 let font = zoo_font();
2925 let (bytes, _) = font.subset_glyphs(&[2], &['π']).expect("subset");
2926 let sub = Font::parse(bytes).expect("subset re-parses");
2927 assert_eq!(sub.glyph_index('π'), 0); }
2929
2930 #[test]
2931 fn subset_rejects_composite_with_overlong_instruction_claim() {
2932 let font = zoo_font();
2933 let mut new_of = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
2934 new_of[0] = 0;
2935 new_of[5] = 1;
2936 new_of[8] = 2;
2937 assert_eq!(font.subset_glyph_bytes(8, &new_of), None);
2938 assert!(font.subset_glyphs(&[8], &[]).is_none());
2939 }
2940
2941 #[test]
2942 fn strip_simple_glyph_instructions_rejects_overlong_length() {
2943 let mut glyph = Vec::new();
2944 push_i16(&mut glyph, 1);
2945 glyph.extend_from_slice(&[0u8; 8]); push16(&mut glyph, 0); push16(&mut glyph, 255); assert_eq!(strip_simple_glyph_instructions(&glyph, 1), None);
2949 }
2950
2951 #[test]
2954 fn cmap4_truncated_segment_arrays_fall_back_to_uncached_lookup() {
2955 let segs = [
2960 (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), ];
2967 let tables = base_tables(30, 1, 1000, hmtx_long(&[(500, 0)]), cmap4_table(&segs, &[]));
2968 let mut bytes = sfnt(0x0001_0000, &tables);
2969 bytes.truncate(bytes.len() - 2); let font = Font::parse(bytes).expect("cmap payload is lazily read");
2971 assert!(font.cmap4_cache.is_none());
2972 assert_eq!(font.glyph_index('A'), 1);
2973 assert_eq!(font.glyph_index('Z'), 26);
2974 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); }
2983
2984 #[test]
2985 fn cmap4_cached_lookup_reads_glyph_id_array() {
2986 let segs = [(0x42u16, 0x41u16, 3u16, 4u16), (0xFFFF, 0xFFFF, 1, 0)];
2987 let mut array = Vec::new();
2988 push16(&mut array, 7);
2989 push16(&mut array, 0);
2990 let font = parse(&base_tables(
2991 20,
2992 1,
2993 1000,
2994 hmtx_long(&[(500, 0)]),
2995 cmap4_table(&segs, &array),
2996 ));
2997 let cache = font.cmap4_cache.as_ref().expect("valid table caches");
2998 assert!(cache.sorted_by_end);
2999 assert_eq!(font.glyph_index('A'), 10); assert_eq!(font.glyph_index('B'), 0); assert_eq!(font.glyph_index('C'), 0); }
3003
3004 #[test]
3005 fn cmap4_unsorted_segments_use_first_match_linear_scan() {
3006 let segs = [
3007 (0x61u16, 0x61u16, 2u16.wrapping_sub(0x61), 0u16),
3008 (0x5A, 0x41, 1u16.wrapping_sub(0x41), 0),
3009 (0xFFFF, 0xFFFF, 1, 0),
3010 ];
3011 let font = parse(&base_tables(
3012 30,
3013 1,
3014 1000,
3015 hmtx_long(&[(500, 0)]),
3016 cmap4_table(&segs, &[]),
3017 ));
3018 let cache = font
3019 .cmap4_cache
3020 .as_ref()
3021 .expect("caches even when unsorted");
3022 assert!(!cache.sorted_by_end);
3023 assert_eq!(font.glyph_index('a'), 2);
3024 assert_eq!(font.glyph_index('A'), 0);
3027 assert_eq!(font.glyph_index('p'), 0);
3028 }
3029
3030 #[test]
3031 fn cmap4_unsorted_lookup_misses_when_no_segment_covers_the_code() {
3032 let segs = [
3035 (0x61u16, 0x61u16, 2u16.wrapping_sub(0x61), 0u16),
3036 (0x5A, 0x41, 1u16.wrapping_sub(0x41), 0),
3037 ];
3038 let font = parse(&base_tables(
3039 30,
3040 1,
3041 1000,
3042 hmtx_long(&[(500, 0)]),
3043 cmap4_table(&segs, &[]),
3044 ));
3045 assert!(!font.cmap4_cache.as_ref().expect("caches").sorted_by_end);
3046 assert_eq!(font.glyph_index('a'), 2);
3047 assert_eq!(font.glyph_index('p'), 0); }
3049
3050 #[test]
3051 fn select_cmap_accepts_format4_under_a_non_bmp_encoding_record() {
3052 let mut cmap = cmap4_simple(&[(0x41, 1)]);
3055 cmap[6..8].copy_from_slice(&10u16.to_be_bytes()); let font = parse(&base_tables(5, 1, 1000, hmtx_long(&[(500, 0)]), cmap));
3057 assert_eq!(font.cmap_format, 4);
3058 assert_eq!(font.glyph_index('A'), 1);
3059 }
3060
3061 #[test]
3062 fn cmap12_groups_map_across_planes_and_truncate_gids() {
3063 let cmap = cmap12_table(&[
3064 (0x41, 0x5A, 100),
3065 (0x2000, 0x2000, 0x0001_2345),
3066 (0x1F600, 0x1F601, 7),
3067 ]);
3068 let font = parse(&base_tables(200, 1, 1000, hmtx_long(&[(500, 0)]), cmap));
3069 assert_eq!(font.cmap_format, 12);
3070 assert!(font.cmap4_cache.is_none());
3071 assert_eq!(font.glyph_index('A'), 100);
3072 assert_eq!(font.glyph_index('Z'), 125);
3073 assert_eq!(font.glyph_index('\u{2000}'), 0x2345); assert_eq!(font.glyph_index('π'), 7);
3075 assert_eq!(font.glyph_index('π'), 8);
3076 assert_eq!(font.glyph_index('0'), 0); }
3078
3079 #[test]
3082 fn gpos_extension_lookup_resolves_wrapped_pair_kerning() {
3083 let kern = gpos_font(1, 2, 0, 1);
3084 assert_eq!(kern.pair(5, 6), -40);
3085 assert_eq!(kern.pair(5, 7), 0);
3086 assert_eq!(kern.pair(6, 6), 0);
3087 }
3088
3089 #[test]
3090 fn gpos_skips_foreign_extensions_bad_formats_and_lookup_indices() {
3091 assert_eq!(gpos_font(1, 5, 0, 1).pair(5, 6), 0);
3093 assert_eq!(gpos_font(2, 2, 0, 1).pair(5, 6), 0);
3095 assert_eq!(gpos_font(1, 2, 0, 3).pair(5, 6), 0);
3097 assert_eq!(gpos_font(1, 2, 9, 1).pair(5, 6), 0);
3099 }
3100
3101 #[test]
3102 fn gpos_truncated_structures_yield_empty_kerning() {
3103 for end in [16usize, 20, 22, 26, 28, 32, 34] {
3108 let mut g = gpos_table(1, 2, 0, 1);
3109 g.truncate(end);
3110 assert_eq!(gpos_kerning_of(g).pair(5, 6), 0, "end={end}");
3111 }
3112 let mut over = gpos_table(1, 2, 0, 1);
3115 over[10..12].copy_from_slice(&12u16.to_be_bytes());
3116 assert_eq!(gpos_kerning_of(over).pair(5, 6), -40);
3117 let mut direct = gpos_table(1, 2, 0, 1);
3119 direct[28..30].copy_from_slice(&1u16.to_be_bytes());
3120 assert_eq!(gpos_kerning_of(direct).pair(5, 6), 0);
3121 }
3122
3123 #[test]
3124 fn resolve_extension_requires_format_1() {
3125 let mut d = Vec::new();
3126 push16(&mut d, 2);
3127 push16(&mut d, 2);
3128 push32(&mut d, 8);
3129 assert_eq!(resolve_extension(&d, 0), None);
3130 d[0..2].copy_from_slice(&1u16.to_be_bytes());
3131 assert_eq!(resolve_extension(&d, 0), Some((2, 8)));
3132 assert!(parse_pair_subtable(&[0, 3], 0).is_none());
3134 }
3135
3136 #[test]
3137 fn value_record_x_advance_field_extraction() {
3138 assert_eq!(value_record_x_advance(&[], 0, 0), Some(0));
3140 let rec = [0, 0, 0, 0, 0x12, 0x34];
3142 assert_eq!(value_record_x_advance(&rec, 0, 0x0007), Some(0x1234));
3143 assert_eq!(value_record_x_advance(&[0], 0, 0x0004), None);
3145 }
3146
3147 #[test]
3148 fn coverage_and_class_def_malformed_and_boundary_variants() {
3149 let mut cov = Vec::new();
3151 push16(&mut cov, 2);
3152 push16(&mut cov, 1);
3153 push16(&mut cov, 20); push16(&mut cov, 10); push16(&mut cov, 0);
3156 assert_eq!(parse_coverage_glyphs(&cov, 0), Some(Vec::new()));
3157 assert_eq!(parse_coverage_glyphs(&[0, 3, 0, 0], 0), None);
3159 assert!(parse_class_def(&[0, 3, 0, 0], 0).is_none());
3161
3162 let mut cd = Vec::new();
3164 push16(&mut cd, 1);
3165 push16(&mut cd, 5); push16(&mut cd, 2); push16(&mut cd, 7);
3168 push16(&mut cd, 9);
3169 let cd1 = parse_class_def(&cd, 0).expect("format 1 parses");
3170 assert_eq!(cd1.class(5), 7);
3171 assert_eq!(cd1.class(6), 9);
3172 assert_eq!(cd1.class(7), 0); assert_eq!(cd1.class(4), 0); let mut cd2b = Vec::new();
3177 push16(&mut cd2b, 2);
3178 push16(&mut cd2b, 1);
3179 push16(&mut cd2b, 10);
3180 push16(&mut cd2b, 20);
3181 push16(&mut cd2b, 3);
3182 let cd2 = parse_class_def(&cd2b, 0).expect("format 2 parses");
3183 assert_eq!(cd2.class(15), 3);
3184 assert_eq!(cd2.class(9), 0);
3185 assert_eq!(cd2.class(21), 0);
3186 }
3187
3188 #[test]
3189 fn kern_subtable_format2_guards_class_ranges_and_empty_matrix() {
3190 let st = KernSubtable::Format2 {
3191 coverage: vec![5, 9],
3192 class1: ClassDef::Format1 {
3193 start: 5,
3194 classes: vec![1, 0, 0, 0, 9],
3195 },
3196 class2: ClassDef::Format1 {
3197 start: 6,
3198 classes: vec![1, 7],
3199 },
3200 class1_count: 2,
3201 class2_count: 2,
3202 matrix: vec![0, 0, 0, -55],
3203 };
3204 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 {
3210 coverage: vec![5],
3211 class1: ClassDef::Format2 { ranges: Vec::new() },
3212 class2: ClassDef::Format2 { ranges: Vec::new() },
3213 class1_count: 1,
3214 class2_count: 1,
3215 matrix: Vec::new(),
3216 };
3217 assert_eq!(empty.lookup(5, 6), Some(0));
3218
3219 let kerning = Kerning {
3221 subtables: vec![empty, st],
3222 };
3223 assert_eq!(kerning.pair(5, 6), 0);
3224 assert_eq!(kerning.pair(4, 6), 0);
3225 }
3226
3227 #[test]
3228 fn pair_format1_skips_malformed_sets_and_truncated_records() {
3229 let mut d = Vec::new();
3232 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);
3242 push16(&mut d, 1); push16(&mut d, 1);
3244 push16(&mut d, 5);
3245 let st = parse_pair_subtable(&d, 0).expect("format 1 parses");
3246 assert_eq!(st.lookup(5, 6), Some(-40));
3247 assert_eq!(st.lookup(5, 7), None);
3248
3249 let mut d2 = Vec::new();
3251 push16(&mut d2, 1);
3252 push16(&mut d2, 12); push16(&mut d2, 0x0004);
3254 push16(&mut d2, 0);
3255 push16(&mut d2, 1);
3256 push16(&mut d2, 0x4000); push16(&mut d2, 1);
3258 push16(&mut d2, 1);
3259 push16(&mut d2, 5);
3260 let st2 = parse_pair_subtable(&d2, 0).expect("parses to an empty set");
3261 assert_eq!(st2.lookup(5, 6), None);
3262
3263 let mut d3 = Vec::new();
3265 push16(&mut d3, 1);
3266 push16(&mut d3, 12); push16(&mut d3, 0x0004);
3268 push16(&mut d3, 0);
3269 push16(&mut d3, 1);
3270 push16(&mut d3, 18); push16(&mut d3, 1); push16(&mut d3, 1);
3273 push16(&mut d3, 5);
3274 push16(&mut d3, 2); push16(&mut d3, 6);
3276 push_i16(&mut d3, -40);
3277 let st3 = parse_pair_subtable(&d3, 0).expect("parses the readable record");
3278 assert_eq!(st3.lookup(5, 6), Some(-40));
3279 assert_eq!(st3.lookup(5, 0), None);
3280 }
3281
3282 #[test]
3283 fn pair_format1_work_cap_stops_aliased_pair_set_expansion() {
3284 for count in [65_535u16, 65_534] {
3287 let mut d = Vec::new();
3288 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);
3297 push16(&mut d, 5);
3298 push16(&mut d, 6);
3299 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");
3302 assert_eq!(st.lookup(5, 0), Some(0), "count={count}");
3305 assert_eq!(st.lookup(6, 0), None, "count={count}");
3306 }
3307 }
3308
3309 #[test]
3310 fn pair_format2_empty_value_formats_and_oversized_matrix() {
3311 let mut d = Vec::new();
3314 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);
3324 push16(&mut d, 3);
3325 push16(&mut d, 1); push16(&mut d, 0);
3327 push16(&mut d, 0);
3328 let st = parse_pair_subtable(&d, 0).expect("empty-value format 2 parses");
3329 assert!(matches!(
3330 &st,
3331 KernSubtable::Format2 { matrix, .. } if matrix.is_empty()
3332 ));
3333 assert_eq!(st.lookup(3, 42), Some(0));
3334 assert_eq!(st.lookup(4, 42), None);
3335
3336 let mut big = Vec::new();
3338 push16(&mut big, 2);
3339 push16(&mut big, 16);
3340 push16(&mut big, 0x0004);
3341 push16(&mut big, 0);
3342 push16(&mut big, 22);
3343 push16(&mut big, 22);
3344 push16(&mut big, 0xFFFF);
3345 push16(&mut big, 0xFFFF);
3346 assert!(parse_pair_subtable(&big, 0).is_none());
3347 }
3348
3349 #[test]
3352 fn gsub_extension_lookup_parses_greedy_ligatures() {
3353 let ligs = gsub_font(1, 4, 0);
3354 assert!(!ligs.is_empty());
3355 assert!(Ligatures::default().is_empty());
3356 assert_eq!(ligs.substitute(&[10, 11, 12]), vec![99]);
3357 assert_eq!(ligs.substitute(&[10, 11, 7]), vec![77, 7]);
3358 assert_eq!(ligs.substitute(&[10, 7]), vec![10, 7]);
3359 assert_eq!(
3360 ligs.substitute_with_spans(&[10, 11, 12, 10, 11]),
3361 vec![(99, 3), (77, 2)]
3362 );
3363 }
3364
3365 #[test]
3366 fn gsub_skips_foreign_extensions_and_bad_lookup_indices() {
3367 assert!(gsub_font(1, 2, 0).is_empty());
3369 assert!(gsub_font(2, 4, 0).is_empty());
3371 assert!(gsub_font(1, 4, 9).is_empty());
3373 }
3374
3375 #[test]
3376 fn gsub_truncated_structures_yield_no_ligatures() {
3377 for end in [16usize, 20, 22, 26, 28, 32, 34] {
3380 let mut g = gsub_table(1, 4, 0);
3381 g.truncate(end);
3382 assert!(gsub_ligatures_of(g).is_empty(), "end={end}");
3383 }
3384 let mut over = gsub_table(1, 4, 0);
3387 over[10..12].copy_from_slice(&12u16.to_be_bytes());
3388 assert_eq!(gsub_ligatures_of(over).substitute(&[10, 11]), vec![77]);
3389 let mut direct = gsub_table(1, 4, 0);
3391 direct[28..30].copy_from_slice(&1u16.to_be_bytes());
3392 assert!(gsub_ligatures_of(direct).is_empty());
3393 }
3394
3395 #[test]
3396 fn ligature_subst_skips_malformed_entries() {
3397 let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
3398 std::collections::BTreeMap::new();
3399
3400 parse_ligature_subst(&[0, 2, 0, 0], 0, &mut rules);
3402 assert!(rules.is_empty());
3403
3404 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());
3410
3411 let mut d = Vec::new();
3413 push16(&mut d, 1); push16(&mut d, 8); push16(&mut d, 1); push16(&mut d, 0x4000); push16(&mut d, 1); push16(&mut d, 1);
3419 push16(&mut d, 10);
3420 parse_ligature_subst(&d, 0, &mut rules);
3421 assert!(rules.is_empty());
3422
3423 let mut d2 = Vec::new();
3426 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);
3438 push16(&mut d2, 10);
3439 parse_ligature_subst(&d2, 0, &mut rules);
3440 assert_eq!(rules.len(), 1);
3441 assert_eq!(rules[&10].len(), 1);
3442 assert_eq!(rules[&10][0].components, vec![11]);
3443 assert_eq!(rules[&10][0].ligature, 77);
3444
3445 rules.clear();
3448 let mut d3 = Vec::new();
3449 push16(&mut d3, 1); push16(&mut d3, 8); push16(&mut d3, 1); push16(&mut d3, 14); push16(&mut d3, 1); push16(&mut d3, 1);
3455 push16(&mut d3, 10);
3456 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);
3466 push16(&mut d3, 12);
3467 assert_eq!(d3.len(), 36);
3468 parse_ligature_subst(&d3, 0, &mut rules);
3469 assert!(rules.is_empty());
3470 }
3471
3472 #[test]
3473 fn ligature_subst_work_cap_stops_aliased_sets() {
3474 for lig_count in [65_535u16, 65_534] {
3478 let mut d = Vec::new();
3479 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);
3486 push16(&mut d, 10);
3487 push16(&mut d, 11);
3488 push16(&mut d, lig_count); let mut rules: std::collections::BTreeMap<u16, Vec<LigRule>> =
3490 std::collections::BTreeMap::new();
3491 parse_ligature_subst(&d, 0, &mut rules);
3492 assert!(rules.is_empty(), "lig_count={lig_count}");
3493 }
3494 }
3495}