Skip to main content

fmd_font/
cff.rs

1//! Bounded CFF1 Type 2 decoding and deterministic, unhinted CFF subsetting.
2//! CID-keyed CFF, CFF2, seac and computational charstring operators are
3//! explicitly refused. Drawing, hints, flex and local/global subroutines are
4//! supported. Coordinates and advances are font design units.
5use crate::outline::Point;
6use crate::{
7    EmbeddingFormat, Font, MISSING_GLYPH_REMAP, Subset, SubsetError, SubsetErrorKind as Kind,
8    be_u16, be_u32, find_table_full, table_checksum,
9};
10use std::collections::{BTreeMap, BTreeSet};
11
12/// A CFF contour command. Cubics are retained without quadratic approximation.
13#[derive(Debug, Clone, PartialEq)]
14pub enum Command {
15    Move(Point),
16    Line(Point),
17    Curve(Point, Point, Point),
18    Close,
19}
20#[derive(Debug, Clone, PartialEq)]
21pub struct CffOutline {
22    pub commands: Vec<Command>,
23    pub advance: u16,
24    pub lsb: i16,
25}
26fn err(kind: Kind, offset: usize) -> SubsetError {
27    SubsetError::new(kind, *b"CFF ", None, Some(offset))
28}
29fn malformed(offset: usize) -> SubsetError {
30    err(Kind::Malformed, offset)
31}
32fn slice(d: &[u8], start: usize, len: usize) -> Result<&[u8], SubsetError> {
33    d.get(start..start.checked_add(len).ok_or(malformed(start))?)
34        .ok_or(malformed(start))
35}
36fn index(d: &[u8], start: usize) -> Result<(Vec<&[u8]>, usize), SubsetError> {
37    let count = usize::from(be_u16(d, start).ok_or(malformed(start))?);
38    if count == 0 {
39        return Ok((Vec::new(), start + 2));
40    }
41    let size = usize::from(*d.get(start + 2).ok_or(malformed(start + 2))?);
42    if !(1..=4).contains(&size) {
43        return Err(malformed(start + 2));
44    }
45    let base = start
46        .checked_add(3 + (count + 1) * size)
47        .ok_or(malformed(start))?;
48    let offset = |i: usize| -> Result<usize, SubsetError> {
49        let bytes = slice(d, start + 3 + i * size, size)?;
50        let v = bytes.iter().fold(0usize, |a, &b| (a << 8) | usize::from(b));
51        base.checked_add(v.checked_sub(1).ok_or(malformed(start))?)
52            .ok_or(malformed(start))
53    };
54    if offset(0)? != base {
55        return Err(malformed(start));
56    }
57    let mut objects = Vec::with_capacity(count);
58    for i in 0..count {
59        let a = offset(i)?;
60        let b = offset(i + 1)?;
61        objects.push(slice(d, a, b.checked_sub(a).ok_or(malformed(a))?)?);
62    }
63    Ok((objects, offset(count)?))
64}
65fn number(d: &[u8], p: &mut usize, dict: bool) -> Result<Option<f64>, SubsetError> {
66    let at = *p;
67    let b = *d.get(at).ok_or(malformed(at))?;
68    let (v, n) = match b {
69        32..=246 => (f64::from(b) - 139.0, 1),
70        247..=250 => (
71            f64::from(b - 247) * 256.0 + f64::from(*d.get(at + 1).ok_or(malformed(at))?) + 108.0,
72            2,
73        ),
74        251..=254 => (
75            -f64::from(b - 251) * 256.0 - f64::from(*d.get(at + 1).ok_or(malformed(at))?) - 108.0,
76            2,
77        ),
78        28 => (f64::from(be_u16(d, at + 1).ok_or(malformed(at))? as i16), 3),
79        29 if dict => (f64::from(be_u32(d, at + 1).ok_or(malformed(at))? as i32), 5),
80        255 if !dict => (
81            f64::from(be_u32(d, at + 1).ok_or(malformed(at))? as i32) / 65536.0,
82            5,
83        ),
84        30 if dict => {
85            let mut text = String::new();
86            let mut pos = at + 1;
87            'real: loop {
88                let v = *d.get(pos).ok_or(malformed(pos))?;
89                pos += 1;
90                for digit in [v >> 4, v & 15] {
91                    match digit {
92                        0..=9 => text.push(char::from(b'0' + digit)),
93                        10 => text.push('.'),
94                        11 => text.push('E'),
95                        12 => text.push_str("E-"),
96                        14 => text.push('-'),
97                        15 => break 'real,
98                        _ => return Err(malformed(pos - 1)),
99                    }
100                    if text.len() > 64 {
101                        return Err(err(Kind::BudgetExceeded, at));
102                    }
103                }
104            }
105            let v = text.parse::<f64>().map_err(|_| malformed(at))?;
106            if !v.is_finite() {
107                return Err(malformed(at));
108            }
109            (v, pos - at)
110        }
111        _ => return Ok(None),
112    };
113    *p += n;
114    Ok(Some(v))
115}
116fn dictionary(d: &[u8]) -> Result<BTreeMap<u16, Vec<f64>>, SubsetError> {
117    let mut out = BTreeMap::new();
118    let mut stack = Vec::new();
119    let mut p = 0;
120    while p < d.len() {
121        if let Some(v) = number(d, &mut p, true)? {
122            stack.push(v);
123            if stack.len() > 48 {
124                return Err(err(Kind::BudgetExceeded, p));
125            }
126            continue;
127        }
128        let b = d[p];
129        p += 1;
130        let op = if b == 12 {
131            let b = *d.get(p).ok_or(malformed(p))?;
132            p += 1;
133            0x0c00 + u16::from(b)
134        } else {
135            u16::from(b)
136        };
137        if out.insert(op, std::mem::take(&mut stack)).is_some() {
138            return Err(malformed(p));
139        }
140    }
141    if !stack.is_empty() {
142        return Err(malformed(p));
143    }
144    Ok(out)
145}
146fn integer(v: f64, p: usize) -> Result<usize, SubsetError> {
147    if v < 0.0 || v > u32::MAX as f64 || v.fract() != 0.0 {
148        Err(malformed(p))
149    } else {
150        Ok(v as usize)
151    }
152}
153struct Cff<'a> {
154    chars: Vec<&'a [u8]>,
155    local: Vec<&'a [u8]>,
156    global: Vec<&'a [u8]>,
157}
158impl<'a> Cff<'a> {
159    fn parse(font: &'a Font) -> Result<Self, SubsetError> {
160        font.validate_font_metrics()?;
161        let (o, n) = find_table_full(&font.data, b"CFF ").ok_or(SubsetError::new(
162            Kind::UnsupportedFormat,
163            *b"sfnt",
164            None,
165            None,
166        ))?;
167        let d = slice(&font.data, o, n)?;
168        if d.first() != Some(&1) {
169            return Err(err(Kind::UnsupportedFormat, 0));
170        }
171        let header = usize::from(*d.get(2).ok_or(malformed(2))?);
172        if header < 4 || !matches!(d.get(3), Some(1..=4)) {
173            return Err(malformed(2));
174        }
175        let (names, p) = index(d, header)?;
176        if names.len() != 1 {
177            return Err(err(Kind::UnsupportedFormat, header));
178        }
179        let (top, p) = index(d, p)?;
180        if top.len() != 1 {
181            return Err(malformed(p));
182        }
183        let (_, p) = index(d, p)?;
184        let (global, _) = index(d, p)?;
185        let dict = dictionary(top[0])?;
186        if dict.get(&0x0c05).is_some_and(|v| v.as_slice() != [0.0]) {
187            return Err(err(Kind::UnsupportedFormat, header));
188        }
189        if dict.contains_key(&0x0c1e) {
190            return Err(err(Kind::UnsupportedFormat, header));
191        }
192        if dict.get(&0x0c06).is_some_and(|v| v.as_slice() != [2.0]) {
193            return Err(err(Kind::UnsupportedFormat, header));
194        }
195        if let Some(matrix) = dict.get(&0x0c07) {
196            let unit = 1.0 / f64::from(font.units_per_em);
197            if matrix.len() != 6
198                || matrix
199                    .iter()
200                    .zip([unit, 0.0, 0.0, unit, 0.0, 0.0])
201                    .any(|(a, b)| (a - b).abs() > 1e-12)
202            {
203                return Err(err(Kind::UnsupportedFormat, header));
204            }
205        } else if font.units_per_em != 1000 {
206            return Err(err(Kind::UnsupportedFormat, header));
207        }
208        let cs = dict
209            .get(&17)
210            .filter(|v| v.len() == 1)
211            .ok_or(malformed(header))?;
212        let (chars, _) = index(d, integer(cs[0], header)?)?;
213        if chars.len() != usize::from(font.num_glyphs) {
214            return Err(malformed(header));
215        }
216        let mut local = Vec::new();
217        if let Some(private) = dict.get(&18) {
218            if private.len() != 2 {
219                return Err(malformed(header));
220            }
221            let size = integer(private[0], header)?;
222            let base = integer(private[1], header)?;
223            let pd = dictionary(slice(d, base, size)?)?;
224            if let Some(subrs) = pd.get(&19) {
225                if subrs.len() != 1 {
226                    return Err(malformed(base));
227                }
228                let sub = base
229                    .checked_add(integer(subrs[0], base)?)
230                    .ok_or(malformed(base))?;
231                local = index(d, sub)?.0;
232            }
233        }
234        Ok(Self {
235            chars,
236            local,
237            global,
238        })
239    }
240    fn outline(&self, font: &Font, gid: u16) -> Result<CffOutline, SubsetError> {
241        self.outline_budget(font, gid, &mut 100_000)
242    }
243    fn outline_budget(
244        &self,
245        font: &Font,
246        gid: u16,
247        budget: &mut usize,
248    ) -> Result<CffOutline, SubsetError> {
249        let initial = (*budget).min(100_000);
250        let program = self.chars.get(usize::from(gid)).ok_or(SubsetError::new(
251            Kind::InvalidGlyph,
252            *b"CFF ",
253            Some(gid),
254            None,
255        ))?;
256        let mut vm = Vm {
257            cff: self,
258            stack: Vec::new(),
259            out: Vec::new(),
260            x: 0.0,
261            y: 0.0,
262            open: false,
263            width: false,
264            stems: 0,
265            fuel: initial,
266        };
267        let ended = vm.run(program, 0).map_err(|mut e| {
268            e.glyph = Some(gid);
269            e
270        })?;
271        *budget -= initial - vm.fuel;
272        if !ended {
273            return Err(SubsetError::new(
274                Kind::Malformed,
275                *b"CFF ",
276                Some(gid),
277                Some(program.len()),
278            ));
279        }
280        Ok(CffOutline {
281            commands: vm.out,
282            advance: font.advance_width(gid),
283            lsb: font.left_side_bearing(gid),
284        })
285    }
286}
287struct Vm<'a, 'b> {
288    cff: &'a Cff<'b>,
289    stack: Vec<f64>,
290    out: Vec<Command>,
291    x: f64,
292    y: f64,
293    open: bool,
294    width: bool,
295    stems: usize,
296    fuel: usize,
297}
298impl Vm<'_, '_> {
299    fn point(&mut self, dx: f64, dy: f64) -> Result<Point, SubsetError> {
300        self.x += dx;
301        self.y += dy;
302        if !self.x.is_finite() || !self.y.is_finite() || self.x.abs() > 1e9 || self.y.abs() > 1e9 {
303            return Err(malformed(0));
304        }
305        Ok(Point {
306            x: self.x,
307            y: self.y,
308        })
309    }
310    fn close(&mut self) {
311        if self.open {
312            self.out.push(Command::Close);
313            self.open = false;
314        }
315    }
316    fn line(&mut self, dx: f64, dy: f64) -> Result<(), SubsetError> {
317        if !self.open {
318            return Err(malformed(0));
319        }
320        let p = self.point(dx, dy)?;
321        self.out.push(Command::Line(p));
322        Ok(())
323    }
324    fn curve(&mut self, v: &[f64]) -> Result<(), SubsetError> {
325        if !self.open || v.len() != 6 {
326            return Err(malformed(0));
327        }
328        let a = self.point(v[0], v[1])?;
329        let b = self.point(v[2], v[3])?;
330        let c = self.point(v[4], v[5])?;
331        self.out.push(Command::Curve(a, b, c));
332        Ok(())
333    }
334    fn run(&mut self, d: &[u8], depth: usize) -> Result<bool, SubsetError> {
335        if depth > 10 {
336            return Err(err(Kind::BudgetExceeded, 0));
337        }
338        let mut p = 0;
339        while p < d.len() {
340            if self.fuel == 0 || self.out.len() > 65_536 {
341                return Err(err(Kind::BudgetExceeded, p));
342            }
343            self.fuel -= 1;
344            if let Some(v) = number(d, &mut p, false)? {
345                self.stack.push(v);
346                if self.stack.len() > 48 {
347                    return Err(err(Kind::BudgetExceeded, p));
348                }
349                continue;
350            }
351            let at = p;
352            let op = d[p];
353            p += 1;
354            if op == 10 || op == 29 {
355                let v = self.stack.pop().ok_or(malformed(at))?;
356                let subrs = if op == 10 {
357                    &self.cff.local
358                } else {
359                    &self.cff.global
360                };
361                let bias = if subrs.len() < 1240 {
362                    107.0
363                } else if subrs.len() < 33900 {
364                    1131.0
365                } else {
366                    32768.0
367                };
368                let i = integer(v + bias, at)?;
369                let sub = subrs.get(i).ok_or(malformed(at))?;
370                if self.run(sub, depth + 1)? {
371                    return Ok(true);
372                }
373                continue;
374            }
375            if op == 11 {
376                if depth == 0 {
377                    return Err(malformed(at));
378                }
379                return Ok(false);
380            }
381            let mut s = std::mem::take(&mut self.stack);
382            match op {
383                1 | 3 | 18 | 23 | 19 | 20 => {
384                    if !self.width && s.len() % 2 == 1 {
385                        s.remove(0);
386                    }
387                    self.width = true;
388                    if s.len() % 2 != 0 {
389                        return Err(malformed(at));
390                    }
391                    self.stems += s.len() / 2;
392                    if self.stems > 96 {
393                        return Err(err(Kind::BudgetExceeded, at));
394                    }
395                    if op == 19 || op == 20 {
396                        let n = self.stems.div_ceil(8);
397                        slice(d, p, n)?;
398                        p += n;
399                    }
400                }
401                4 | 21 | 22 => {
402                    let n = if op == 21 { 2 } else { 1 };
403                    if !self.width && s.len() == n + 1 {
404                        s.remove(0);
405                    }
406                    self.width = true;
407                    if s.len() != n {
408                        return Err(malformed(at));
409                    }
410                    self.close();
411                    let (dx, dy) = match op {
412                        4 => (0.0, s[0]),
413                        22 => (s[0], 0.0),
414                        _ => (s[0], s[1]),
415                    };
416                    let pt = self.point(dx, dy)?;
417                    self.out.push(Command::Move(pt));
418                    self.open = true;
419                }
420                5 => {
421                    if s.is_empty() || s.len() % 2 != 0 {
422                        return Err(malformed(at));
423                    }
424                    for v in s.chunks_exact(2) {
425                        self.line(v[0], v[1])?;
426                    }
427                }
428                6 | 7 => {
429                    if s.is_empty() {
430                        return Err(malformed(at));
431                    }
432                    let mut horizontal = op == 6;
433                    for v in s {
434                        self.line(
435                            if horizontal { v } else { 0.0 },
436                            if horizontal { 0.0 } else { v },
437                        )?;
438                        horizontal = !horizontal;
439                    }
440                }
441                8 => {
442                    if s.is_empty() || s.len() % 6 != 0 {
443                        return Err(malformed(at));
444                    }
445                    for v in s.chunks_exact(6) {
446                        self.curve(v)?;
447                    }
448                }
449                24 => {
450                    if s.len() < 8 || (s.len() - 2) % 6 != 0 {
451                        return Err(malformed(at));
452                    }
453                    let n = s.len() - 2;
454                    for v in s[..n].chunks_exact(6) {
455                        self.curve(v)?;
456                    }
457                    self.line(s[n], s[n + 1])?;
458                }
459                25 => {
460                    if s.len() < 8 || (s.len() - 6) % 2 != 0 {
461                        return Err(malformed(at));
462                    }
463                    let n = s.len() - 6;
464                    for v in s[..n].chunks_exact(2) {
465                        self.line(v[0], v[1])?;
466                    }
467                    self.curve(&s[n..])?;
468                }
469                26 | 27 => {
470                    if s.len() < 4 || !matches!(s.len() % 4, 0 | 1) {
471                        return Err(malformed(at));
472                    }
473                    let mut extra = if s.len() % 4 == 1 { s.remove(0) } else { 0.0 };
474                    for v in s.chunks_exact(4) {
475                        if op == 26 {
476                            self.curve(&[extra, v[0], v[1], v[2], 0.0, v[3]])?;
477                        } else {
478                            self.curve(&[v[0], extra, v[1], v[2], v[3], 0.0])?;
479                        }
480                        extra = 0.0;
481                    }
482                }
483                30 | 31 => {
484                    if s.len() < 4 || !matches!(s.len() % 4, 0 | 1) {
485                        return Err(malformed(at));
486                    }
487                    let count = s.len() / 4;
488                    let mut horizontal = op == 31;
489                    for (i, v) in s[..count * 4].chunks_exact(4).enumerate() {
490                        let extra = if i + 1 == count && s.len() % 4 == 1 {
491                            s[s.len() - 1]
492                        } else {
493                            0.0
494                        };
495                        if horizontal {
496                            self.curve(&[v[0], 0.0, v[1], v[2], extra, v[3]])?;
497                        } else {
498                            self.curve(&[0.0, v[0], v[1], v[2], v[3], extra])?;
499                        }
500                        horizontal = !horizontal;
501                    }
502                }
503                12 => {
504                    let escape = *d.get(p).ok_or(malformed(p))?;
505                    p += 1;
506                    match escape {
507                        34 if s.len() == 7 => {
508                            self.curve(&[s[0], 0.0, s[1], s[2], s[3], 0.0])?;
509                            self.curve(&[s[4], 0.0, s[5], -s[2], s[6], 0.0])?;
510                        }
511                        35 if s.len() == 13 => {
512                            self.curve(&s[..6])?;
513                            self.curve(&s[6..12])?;
514                        }
515                        36 if s.len() == 9 => {
516                            self.curve(&[s[0], s[1], s[2], s[3], s[4], 0.0])?;
517                            self.curve(&[s[5], 0.0, s[6], s[7], s[8], -s[1] - s[3] - s[7]])?;
518                        }
519                        37 if s.len() == 11 => {
520                            let dx = s[0] + s[2] + s[4] + s[6] + s[8];
521                            let dy = s[1] + s[3] + s[5] + s[7] + s[9];
522                            self.curve(&s[..6])?;
523                            let (x, y) = if dx.abs() > dy.abs() {
524                                (s[10], -dy)
525                            } else {
526                                (-dx, s[10])
527                            };
528                            self.curve(&[s[6], s[7], s[8], s[9], x, y])?;
529                        }
530                        34..=37 => return Err(malformed(at)),
531                        _ => return Err(err(Kind::UnsupportedOperator, at)),
532                    }
533                }
534                14 => {
535                    if !self.width && s.len() % 2 == 1 {
536                        s.remove(0);
537                    }
538                    if s.len() == 4 {
539                        return Err(err(Kind::UnsupportedOperator, at));
540                    }
541                    if !s.is_empty() {
542                        return Err(malformed(at));
543                    }
544                    self.close();
545                    return Ok(true);
546                }
547                _ => return Err(err(Kind::UnsupportedOperator, at)),
548            }
549        }
550        Err(malformed(p))
551    }
552}
553impl Font {
554    /// Decode a CFF1 glyph, preserving exact cubic control points. No host APIs.
555    pub fn cff_outline(&self, gid: u16) -> Result<CffOutline, SubsetError> {
556        Cff::parse(self)?.outline(self, gid)
557    }
558}
559fn dict_int(out: &mut Vec<u8>, v: i32) {
560    out.push(29);
561    out.extend_from_slice(&v.to_be_bytes());
562}
563fn encode_index(objects: &[Vec<u8>]) -> Result<Vec<u8>, SubsetError> {
564    let count = u16::try_from(objects.len()).map_err(|_| err(Kind::Capacity, 0))?;
565    let mut out = count.to_be_bytes().to_vec();
566    if count == 0 {
567        return Ok(out);
568    }
569    out.push(4);
570    let mut offset = 1u32;
571    out.extend_from_slice(&offset.to_be_bytes());
572    for v in objects {
573        offset = offset
574            .checked_add(u32::try_from(v.len()).map_err(|_| err(Kind::Capacity, 0))?)
575            .ok_or(err(Kind::Capacity, 0))?;
576        out.extend_from_slice(&offset.to_be_bytes());
577    }
578    for v in objects {
579        out.extend_from_slice(v);
580    }
581    Ok(out)
582}
583fn cs_num(out: &mut Vec<u8>, v: f64) -> Result<(), SubsetError> {
584    if v.fract() == 0.0 && (-32768.0..=32767.0).contains(&v) {
585        out.push(28);
586        out.extend_from_slice(&(v as i16).to_be_bytes());
587    } else {
588        let fixed = (v * 65536.0).round();
589        if fixed < i32::MIN as f64 || fixed > i32::MAX as f64 {
590            return Err(err(Kind::Capacity, 0));
591        }
592        out.push(255);
593        out.extend_from_slice(&(fixed as i32).to_be_bytes());
594    }
595    Ok(())
596}
597fn encode_outline(o: &CffOutline) -> Result<Vec<u8>, SubsetError> {
598    let mut out = Vec::new();
599    cs_num(&mut out, f64::from(o.advance))?;
600    let mut current = Point { x: 0.0, y: 0.0 };
601    for c in &o.commands {
602        match c {
603            Command::Move(p) | Command::Line(p) => {
604                cs_num(&mut out, p.x - current.x)?;
605                cs_num(&mut out, p.y - current.y)?;
606                out.push(if matches!(c, Command::Move(_)) { 21 } else { 5 });
607                current = *p;
608            }
609            Command::Curve(a, b, c) => {
610                for p in [a, b, c] {
611                    cs_num(&mut out, p.x - current.x)?;
612                    cs_num(&mut out, p.y - current.y)?;
613                    current = *p;
614                }
615                out.push(8);
616            }
617            Command::Close => {}
618        }
619    }
620    out.push(14);
621    Ok(out)
622}
623fn cff_program(outlines: &[CffOutline], upem: u16) -> Result<Vec<u8>, SubsetError> {
624    let names = encode_index(&[b"FmdSubset".to_vec()])?;
625    let strings: Vec<_> = (1..outlines.len())
626        .map(|i| format!("g{i}").into_bytes())
627        .collect();
628    let strings = encode_index(&strings)?;
629    let chars = encode_index(
630        &outlines
631            .iter()
632            .map(encode_outline)
633            .collect::<Result<Vec<_>, _>>()?,
634    )?;
635    let mut charset = vec![0];
636    for i in 1..outlines.len() {
637        let sid = u16::try_from(390 + i).map_err(|_| err(Kind::Capacity, 0))?;
638        charset.extend_from_slice(&sid.to_be_bytes());
639    }
640    let mut bounds: Option<[f64; 4]> = None;
641    for outline in outlines {
642        for command in &outline.commands {
643            let points: &[Point] = match command {
644                Command::Move(p) | Command::Line(p) => std::slice::from_ref(p),
645                Command::Curve(a, b, c) => &[*a, *b, *c],
646                Command::Close => &[],
647            };
648            for p in points {
649                bounds = Some(match bounds {
650                    None => [p.x, p.y, p.x, p.y],
651                    Some([x0, y0, x1, y1]) => [x0.min(p.x), y0.min(p.y), x1.max(p.x), y1.max(p.y)],
652                });
653            }
654        }
655    }
656    let bounds = bounds.unwrap_or([0.0; 4]);
657    // Fixed-width DICT offsets make layout independent of offset magnitude.
658    let make_top = |charset_off: i32, chars_off: i32| -> Vec<u8> {
659        let mut d = Vec::new();
660        for (i, v) in bounds.iter().enumerate() {
661            dict_int(
662                &mut d,
663                if i < 2 {
664                    v.floor() as i32
665                } else {
666                    v.ceil() as i32
667                },
668            );
669        }
670        d.push(5);
671        dict_int(&mut d, 0);
672        dict_int(&mut d, chars_off + chars.len() as i32);
673        d.push(18);
674        dict_int(&mut d, charset_off);
675        d.push(15);
676        dict_int(&mut d, chars_off);
677        d.push(17);
678        // Real FontMatrix = 1/upem. Decimal round-trip is deterministic.
679        let value = format!("{:.16}", 1.0 / f64::from(upem));
680        for val in [value.as_str(), "0", "0", value.as_str(), "0", "0"] {
681            d.push(30);
682            let mut digits: Vec<u8> = val
683                .bytes()
684                .map(|b| if b == b'.' { 10 } else { b - b'0' })
685                .collect();
686            digits.push(15);
687            if digits.len() % 2 != 0 {
688                digits.push(15);
689            }
690            for pair in digits.chunks_exact(2) {
691                d.push(pair[0] * 16 + pair[1]);
692            }
693        }
694        d.extend_from_slice(&[12, 7]);
695        d
696    };
697    let top_size = encode_index(&[make_top(0, 0)])?.len();
698    let charset_off = 4 + names.len() + top_size + strings.len() + 2;
699    let mut out = vec![1, 0, 4, 4];
700    out.extend(names);
701    out.extend(encode_index(&[make_top(
702        charset_off as i32,
703        (charset_off + charset.len()) as i32,
704    )])?);
705    out.extend(strings);
706    out.extend([0, 0]);
707    out.extend(charset);
708    out.extend(chars);
709    Ok(out)
710}
711pub(crate) fn subset(font: &Font, glyphs: &[u16], chars: &[char]) -> Result<Subset, SubsetError> {
712    let cff = Cff::parse(font)?;
713    let mut keep = BTreeSet::from([0]);
714    for &g in glyphs {
715        if g >= font.num_glyphs {
716            return Err(SubsetError::new(
717                Kind::InvalidGlyph,
718                *b"maxp",
719                Some(g),
720                None,
721            ));
722        }
723        keep.insert(g);
724    }
725    let mut map = vec![MISSING_GLYPH_REMAP; usize::from(font.num_glyphs)];
726    let mut outlines = Vec::new();
727    let mut command_count = 0usize;
728    let mut instruction_budget = 5_000_000usize;
729    for (i, &g) in keep.iter().enumerate() {
730        map[usize::from(g)] = i as u16;
731        let outline = cff.outline_budget(font, g, &mut instruction_budget)?;
732        command_count += outline.commands.len();
733        if command_count > 1_000_000 {
734            return Err(err(Kind::BudgetExceeded, 0));
735        }
736        outlines.push(outline);
737    }
738    let cff = cff_program(&outlines, font.units_per_em)?;
739    let mut tables = BTreeMap::new();
740    for tag in [*b"head", *b"hhea", *b"OS/2", *b"name"] {
741        if let Some((o, n)) = find_table_full(&font.data, &tag) {
742            tables.insert(tag, slice(&font.data, o, n)?.to_vec());
743        }
744    }
745    // Avoid carrying reserved source font names into a derivative program.
746    tables.insert(*b"name", vec![0, 0, 0, 0, 0, 6]);
747    let n = keep.len() as u16;
748    let head = tables.get_mut(b"head").ok_or(malformed(0))?;
749    crate::write_u32(head, 8, 0).ok_or(malformed(8))?;
750    crate::write_u16(head, 50, 0).ok_or(malformed(50))?;
751    crate::write_u16(tables.get_mut(b"hhea").ok_or(malformed(0))?, 34, n).ok_or(malformed(34))?;
752    let mut maxp = 0x0000_5000u32.to_be_bytes().to_vec();
753    maxp.extend_from_slice(&n.to_be_bytes());
754    tables.insert(*b"maxp", maxp);
755    let mut metrics = Vec::new();
756    for o in &outlines {
757        metrics.extend_from_slice(&o.advance.to_be_bytes());
758        metrics.extend_from_slice(&o.lsb.to_be_bytes());
759    }
760    tables.insert(*b"hmtx", metrics);
761    let cmap = font.build_cmap12(chars, &map).ok_or(SubsetError::new(
762        Kind::Capacity,
763        *b"cmap",
764        None,
765        None,
766    ))?;
767    tables.insert(*b"cmap", cmap);
768    let mut post = vec![0; 32];
769    post[..4].copy_from_slice(&0x0003_0000u32.to_be_bytes());
770    tables.insert(*b"post", post);
771    tables.insert(*b"CFF ", cff);
772    Ok(Subset {
773        bytes: assemble(&tables)?,
774        glyph_map: map,
775        format: EmbeddingFormat::OpenTypeCff,
776    })
777}
778fn assemble(tables: &BTreeMap<[u8; 4], Vec<u8>>) -> Result<Vec<u8>, SubsetError> {
779    let n = tables.len();
780    let pow = 1usize << n.ilog2();
781    let mut out = vec![0; 12 + n * 16];
782    out[..4].copy_from_slice(b"OTTO");
783    for (off, v) in [
784        (4, n as u16),
785        (6, (pow * 16) as u16),
786        (8, n.ilog2() as u16),
787        (10, ((n - pow) * 16) as u16),
788    ] {
789        out[off..off + 2].copy_from_slice(&v.to_be_bytes());
790    }
791    let mut head = 0;
792    for (i, (tag, data)) in tables.iter().enumerate() {
793        while out.len() % 4 != 0 {
794            out.push(0);
795        }
796        let offset = out.len();
797        let p = 12 + i * 16;
798        out[p..p + 4].copy_from_slice(tag);
799        for (p, v) in [
800            (p + 4, table_checksum(data)),
801            (
802                p + 8,
803                u32::try_from(offset).map_err(|_| err(Kind::Capacity, 0))?,
804            ),
805            (p + 12, data.len() as u32),
806        ] {
807            out[p..p + 4].copy_from_slice(&v.to_be_bytes());
808        }
809        if tag == b"head" {
810            head = offset;
811        }
812        out.extend_from_slice(data);
813    }
814    while out.len() % 4 != 0 {
815        out.push(0);
816    }
817    let sum = 0xB1B0_AFBAu32.wrapping_sub(table_checksum(&out));
818    crate::write_u32(&mut out, head + 8, sum).ok_or(malformed(head))?;
819    Ok(out)
820}
821
822#[cfg(test)]
823#[allow(clippy::unwrap_used, clippy::expect_used)]
824mod tests {
825    use super::*;
826    const FONT: &[u8] = include_bytes!("../fonts/test-cff/Bravura.otf");
827    #[test]
828    fn charstring_failures_keep_distinct_typed_causes() {
829        let f = Font::parse(FONT.to_vec()).unwrap();
830        for (program, kind) in [
831            (vec![12, 23, 14], Kind::UnsupportedOperator),
832            (vec![28, 0], Kind::Malformed),
833            (vec![5, 14], Kind::Malformed),
834            (vec![11], Kind::Malformed),
835        ] {
836            let c = Cff {
837                chars: vec![&program],
838                local: vec![],
839                global: vec![],
840            };
841            let e = c.outline(&f, 0).unwrap_err();
842            assert_eq!(e.kind, kind);
843            assert_eq!(e.glyph, Some(0));
844            assert!(e.offset.is_some());
845        }
846        let recursive = [32, 10, 11];
847        let main = [32, 10, 14];
848        let c = Cff {
849            chars: vec![&main],
850            local: vec![&recursive],
851            global: vec![],
852        };
853        assert_eq!(c.outline(&f, 0).unwrap_err().kind, Kind::BudgetExceeded);
854    }
855    #[test]
856    fn zero_glyph_font_and_cff2_refuse_without_panics() {
857        let mut d = FONT.to_vec();
858        let (o, _) = find_table_full(&d, b"maxp").unwrap();
859        crate::write_u16(&mut d, o + 4, 0).unwrap();
860        let f = Font::parse(d).unwrap();
861        assert_eq!(
862            f.try_subset_glyphs(&[], &[]).unwrap_err().kind,
863            Kind::Malformed
864        );
865        let mut d = FONT.to_vec();
866        let (o, _) = find_table_full(&d, b"CFF ").unwrap();
867        d[o] = 2;
868        let f = Font::parse(d).unwrap();
869        assert_eq!(f.cff_outline(0).unwrap_err().kind, Kind::UnsupportedFormat);
870    }
871    #[test]
872    fn cff_subset_has_valid_sfnt_checksum() {
873        let f = Font::parse(FONT.to_vec()).unwrap();
874        let s = f.try_subset_glyphs(&[5, 1000], &[]).unwrap();
875        assert_eq!(table_checksum(&s.bytes), 0xB1B0_AFBA);
876    }
877}