Skip to main content

cff_parser/
cff.rs

1// Useful links:
2// http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
3// http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf
4// https://github.com/opentypejs/opentype.js/blob/master/src/tables/cff.js
5
6use core::convert::TryFrom;
7use core::num::NonZeroU16;
8use core::ops::Range;
9
10use super::argstack::ArgumentsStack;
11use super::charset::{parse_charset, Charset};
12use super::charstring::CharStringParser;
13use super::dict::DictionaryParser;
14use super::encoding::{parse_encoding, Encoding, STANDARD_ENCODING};
15use super::index::{parse_index, skip_index, Index};
16use super::std_names::STANDARD_NAMES;
17use super::{calc_subroutine_bias, conv_subroutine_index, Builder, CFFError, IsEven, StringId};
18use crate::parser::{LazyArray16, NumFrom, Stream, TryNumFrom};
19use crate::{DummyOutline, GlyphId, OutlineBuilder, Rect, RectF};
20
21// Limits according to the Adobe Technical Note #5176, chapter 4 DICT Data.
22const MAX_OPERANDS_LEN: usize = 48;
23
24// Limits according to the Adobe Technical Note #5177 Appendix B.
25const STACK_LIMIT: u8 = 10;
26const MAX_ARGUMENTS_STACK_LEN: usize = 48;
27
28const TWO_BYTE_OPERATOR_MARK: u8 = 12;
29
30/// Enumerates some operators defined in the Adobe Technical Note #5177.
31mod operator {
32    pub const HORIZONTAL_STEM: u8 = 1;
33    pub const VERTICAL_STEM: u8 = 3;
34    pub const VERTICAL_MOVE_TO: u8 = 4;
35    pub const LINE_TO: u8 = 5;
36    pub const HORIZONTAL_LINE_TO: u8 = 6;
37    pub const VERTICAL_LINE_TO: u8 = 7;
38    pub const CURVE_TO: u8 = 8;
39    pub const CALL_LOCAL_SUBROUTINE: u8 = 10;
40    pub const RETURN: u8 = 11;
41    pub const ENDCHAR: u8 = 14;
42    pub const HORIZONTAL_STEM_HINT_MASK: u8 = 18;
43    pub const HINT_MASK: u8 = 19;
44    pub const COUNTER_MASK: u8 = 20;
45    pub const MOVE_TO: u8 = 21;
46    pub const HORIZONTAL_MOVE_TO: u8 = 22;
47    pub const VERTICAL_STEM_HINT_MASK: u8 = 23;
48    pub const CURVE_LINE: u8 = 24;
49    pub const LINE_CURVE: u8 = 25;
50    pub const VV_CURVE_TO: u8 = 26;
51    pub const HH_CURVE_TO: u8 = 27;
52    pub const SHORT_INT: u8 = 28;
53    pub const CALL_GLOBAL_SUBROUTINE: u8 = 29;
54    pub const VH_CURVE_TO: u8 = 30;
55    pub const HV_CURVE_TO: u8 = 31;
56    pub const HFLEX: u8 = 34;
57    pub const FLEX: u8 = 35;
58    pub const HFLEX1: u8 = 36;
59    pub const FLEX1: u8 = 37;
60    pub const FIXED_16_16: u8 = 255;
61}
62
63/// Enumerates some operators defined in the Adobe Technical Note #5176,
64/// Table 9 Top DICT Operator Entries
65mod top_dict_operator {
66    pub const VERSION: u16 = 0;
67    pub const NOTICE: u16 = 1;
68    pub const FULL_NAME: u16 = 2;
69    pub const FAMILY_NAME: u16 = 3;
70    pub const CHARSET_OFFSET: u16 = 15;
71    pub const ENCODING_OFFSET: u16 = 16;
72    pub const CHAR_STRINGS_OFFSET: u16 = 17;
73    pub const PRIVATE_DICT_SIZE_AND_OFFSET: u16 = 18;
74    pub const FONT_MATRIX: u16 = 1207;
75    pub const ROS: u16 = 1230;
76    pub const FD_ARRAY: u16 = 1236;
77    pub const FD_SELECT: u16 = 1237;
78}
79
80/// Enumerates some operators defined in the Adobe Technical Note #5176,
81/// Table 23 Private DICT Operators
82mod private_dict_operator {
83    pub const LOCAL_SUBROUTINES_OFFSET: u16 = 19;
84    pub const DEFAULT_WIDTH: u16 = 20;
85    pub const NOMINAL_WIDTH: u16 = 21;
86}
87
88/// Enumerates Charset IDs defined in the Adobe Technical Note #5176, Table 22
89mod charset_id {
90    pub const ISO_ADOBE: usize = 0;
91    pub const EXPERT: usize = 1;
92    pub const EXPERT_SUBSET: usize = 2;
93}
94
95/// Enumerates Charset IDs defined in the Adobe Technical Note #5176, Table 16
96mod encoding_id {
97    pub const STANDARD: usize = 0;
98    pub const EXPERT: usize = 1;
99}
100
101#[derive(Clone, Copy, Debug)]
102pub(crate) enum FontKind<'a> {
103    SID(SIDMetadata<'a>),
104    CID(CIDMetadata<'a>),
105}
106
107#[derive(Clone, Copy, Default, Debug)]
108pub(crate) struct SIDMetadata<'a> {
109    local_subrs: Index<'a>,
110    /// Can be zero.
111    default_width: f64,
112    /// Can be zero.
113    nominal_width: f64,
114    encoding: Encoding<'a>,
115}
116
117#[derive(Clone, Copy, Default, Debug)]
118pub(crate) struct CIDMetadata<'a> {
119    fd_array: Index<'a>,
120    fd_select: FDSelect<'a>,
121}
122
123/// An affine transformation matrix.
124#[allow(missing_docs)]
125#[derive(Clone, Copy, Debug)]
126pub struct Matrix {
127    pub sx: f64,
128    pub ky: f64,
129    pub kx: f64,
130    pub sy: f64,
131    pub tx: f64,
132    pub ty: f64,
133}
134
135impl Default for Matrix {
136    fn default() -> Self {
137        Self {
138            sx: 0.001,
139            ky: 0.0,
140            kx: 0.0,
141            sy: 0.001,
142            tx: 0.0,
143            ty: 0.0,
144        }
145    }
146}
147
148#[derive(Default)]
149struct TopDict {
150    version: Option<StringId>,
151    notice: Option<StringId>,
152    full_name: Option<StringId>,
153    family_name: Option<StringId>,
154    charset_offset: Option<usize>,
155    encoding_offset: Option<usize>,
156    char_strings_offset: usize,
157    private_dict_range: Option<Range<usize>>,
158    matrix: Matrix,
159    has_ros: bool,
160    fd_array_offset: Option<usize>,
161    fd_select_offset: Option<usize>,
162}
163
164fn parse_top_dict(s: &mut Stream) -> Option<TopDict> {
165    let mut top_dict = TopDict::default();
166
167    let index = parse_index::<u16>(s)?;
168
169    // The Top DICT INDEX should have only one dictionary.
170    let data = index.get(0)?;
171
172    let mut operands_buffer = [0.0; MAX_OPERANDS_LEN];
173    let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer);
174    while let Some(operator) = dict_parser.parse_next() {
175        match operator.get() {
176            top_dict_operator::VERSION => {
177                top_dict.version = dict_parser.parse_sid();
178            }
179            top_dict_operator::NOTICE => {
180                top_dict.notice = dict_parser.parse_sid();
181            }
182            top_dict_operator::FULL_NAME => {
183                top_dict.full_name = dict_parser.parse_sid();
184            }
185            top_dict_operator::FAMILY_NAME => {
186                top_dict.family_name = dict_parser.parse_sid();
187            }
188            top_dict_operator::CHARSET_OFFSET => {
189                top_dict.charset_offset = dict_parser.parse_offset();
190            }
191            top_dict_operator::ENCODING_OFFSET => {
192                top_dict.encoding_offset = dict_parser.parse_offset();
193            }
194            top_dict_operator::CHAR_STRINGS_OFFSET => {
195                top_dict.char_strings_offset = dict_parser.parse_offset()?;
196            }
197            top_dict_operator::PRIVATE_DICT_SIZE_AND_OFFSET => {
198                top_dict.private_dict_range = dict_parser.parse_range();
199            }
200            top_dict_operator::FONT_MATRIX => {
201                dict_parser.parse_operands()?;
202                let operands = dict_parser.operands();
203                if operands.len() == 6 {
204                    top_dict.matrix = Matrix {
205                        sx: operands[0],
206                        ky: operands[1],
207                        kx: operands[2],
208                        sy: operands[3],
209                        tx: operands[4],
210                        ty: operands[5],
211                    };
212                }
213            }
214            top_dict_operator::ROS => {
215                top_dict.has_ros = true;
216            }
217            top_dict_operator::FD_ARRAY => {
218                top_dict.fd_array_offset = dict_parser.parse_offset();
219            }
220            top_dict_operator::FD_SELECT => {
221                top_dict.fd_select_offset = dict_parser.parse_offset();
222            }
223            _ => {}
224        }
225    }
226
227    Some(top_dict)
228}
229
230// TODO: move to integration
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn private_dict_size_overflow() {
237        let data = &[
238            0x00, 0x01, // count: 1
239            0x01, // offset size: 1
240            0x01, // index [0]: 1
241            0x0C, // index [1]: 14
242            0x1D, 0x7F, 0xFF, 0xFF, 0xFF, // length: i32::MAX
243            0x1D, 0x7F, 0xFF, 0xFF, 0xFF, // offset: i32::MAX
244            0x12, // operator: 18 (private)
245        ];
246
247        let top_dict = parse_top_dict(&mut Stream::new(data)).unwrap();
248        assert_eq!(top_dict.private_dict_range, Some(2147483647..4294967294));
249    }
250
251    #[test]
252    fn private_dict_negative_char_strings_offset() {
253        let data = &[
254            0x00, 0x01, // count: 1
255            0x01, // offset size: 1
256            0x01, // index [0]: 1
257            0x03, // index [1]: 3
258            // Item 0
259            0x8A, // offset: -1
260            0x11, // operator: 17 (char_string)
261        ];
262
263        assert!(parse_top_dict(&mut Stream::new(data)).is_none());
264    }
265
266    #[test]
267    fn private_dict_no_char_strings_offset_operand() {
268        let data = &[
269            0x00, 0x01, // count: 1
270            0x01, // offset size: 1
271            0x01, // index [0]: 1
272            0x02, // index [1]: 2
273            // Item 0
274            // <-- No number here.
275            0x11, // operator: 17 (char_string)
276        ];
277
278        assert!(parse_top_dict(&mut Stream::new(data)).is_none());
279    }
280
281    #[test]
282    fn negative_private_dict_offset_and_size() {
283        let data = &[
284            0x00, 0x01, // count: 1
285            0x01, // offset size: 1
286            0x01, // index [0]: 1
287            0x04, // index [1]: 4
288            // Item 0
289            0x8A, // length: -1
290            0x8A, // offset: -1
291            0x12, // operator: 18 (private)
292        ];
293
294        let top_dict = parse_top_dict(&mut Stream::new(data)).unwrap();
295        assert!(top_dict.private_dict_range.is_none());
296    }
297}
298
299#[derive(Default, Debug)]
300struct PrivateDict {
301    local_subroutines_offset: Option<usize>,
302    default_width: Option<f64>,
303    nominal_width: Option<f64>,
304}
305
306fn parse_private_dict(data: &[u8]) -> PrivateDict {
307    let mut dict = PrivateDict::default();
308    let mut operands_buffer = [0.0; MAX_OPERANDS_LEN];
309    let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer);
310    while let Some(operator) = dict_parser.parse_next() {
311        if operator.get() == private_dict_operator::LOCAL_SUBROUTINES_OFFSET {
312            dict.local_subroutines_offset = dict_parser.parse_offset();
313        } else if operator.get() == private_dict_operator::DEFAULT_WIDTH {
314            dict.default_width = dict_parser.parse_number();
315        } else if operator.get() == private_dict_operator::NOMINAL_WIDTH {
316            dict.nominal_width = dict_parser.parse_number();
317        }
318    }
319
320    dict
321}
322
323fn parse_font_dict(data: &[u8]) -> Option<Range<usize>> {
324    let mut operands_buffer = [0.0; MAX_OPERANDS_LEN];
325    let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer);
326    while let Some(operator) = dict_parser.parse_next() {
327        if operator.get() == top_dict_operator::PRIVATE_DICT_SIZE_AND_OFFSET {
328            return dict_parser.parse_range();
329        }
330    }
331
332    None
333}
334
335/// Parse a CID Font DICT for the per-FD FontMatrix (op 12 7).
336///
337/// CID-keyed CFF fonts can define a different FontMatrix for each FD entry in
338/// the FDArray. If absent, the top-level (Table) matrix applies instead.
339/// Returns `None` if the FD dict has no explicit FontMatrix.
340fn parse_font_dict_matrix(data: &[u8]) -> Option<Matrix> {
341    let mut operands_buffer = [0.0; MAX_OPERANDS_LEN];
342    let mut dict_parser = DictionaryParser::new(data, &mut operands_buffer);
343    while let Some(operator) = dict_parser.parse_next() {
344        if operator.get() == top_dict_operator::FONT_MATRIX {
345            dict_parser.parse_operands()?;
346            let operands = dict_parser.operands();
347            if operands.len() == 6 {
348                return Some(Matrix {
349                    sx: operands[0],
350                    ky: operands[1],
351                    kx: operands[2],
352                    sy: operands[3],
353                    tx: operands[4],
354                    ty: operands[5],
355                });
356            }
357        }
358    }
359    None
360}
361
362/// In CID fonts, to get local subroutines we have to:
363///   1. Find Font DICT index via FDSelect by GID.
364///   2. Get Font DICT data from FDArray using this index.
365///   3. Get a Private DICT offset from a Font DICT.
366///   4. Get a local subroutine offset from Private DICT.
367///   5. Parse a local subroutine at offset.
368fn parse_cid_local_subrs<'a>(
369    data: &'a [u8],
370    glyph_id: GlyphId,
371    cid: &CIDMetadata,
372) -> Option<Index<'a>> {
373    let font_dict_index = cid.fd_select.font_dict_index(glyph_id)?;
374    let font_dict_data = cid.fd_array.get(u32::from(font_dict_index))?;
375    let private_dict_range = parse_font_dict(font_dict_data)?;
376    let private_dict_data = data.get(private_dict_range.clone())?;
377    let private_dict = parse_private_dict(private_dict_data);
378    let subroutines_offset = private_dict.local_subroutines_offset?;
379
380    // 'The local subroutines offset is relative to the beginning
381    // of the Private DICT data.'
382    let start = private_dict_range.start.checked_add(subroutines_offset)?;
383    let subrs_data = data.get(start..)?;
384    let mut s = Stream::new(subrs_data);
385    parse_index::<u16>(&mut s)
386}
387
388pub fn string_by_id<'a>(metadata: &'a Table, sid: StringId) -> Option<&'a str> {
389    let sid = usize::from(sid.0);
390    match STANDARD_NAMES.get(sid) {
391        Some(name) => Some(name),
392        None => {
393            let idx = u32::try_from(sid - STANDARD_NAMES.len()).ok()?;
394            let name = metadata.strings.get(idx)?;
395            core::str::from_utf8(name).ok()
396        }
397    }
398}
399
400struct CharStringParserContext<'a> {
401    metadata: &'a Table<'a>,
402    width: Option<f64>,
403    stems_len: u32,
404    has_endchar: bool,
405    has_seac: bool,
406    glyph_id: GlyphId, // Required to parse local subroutine in CID fonts.
407    local_subrs: Option<Index<'a>>,
408}
409
410fn parse_char_string(
411    data: &[u8],
412    metadata: &Table,
413    glyph_id: GlyphId,
414    width_only: bool,
415    builder: &mut dyn OutlineBuilder,
416) -> Result<(Rect, Option<f64>), CFFError> {
417    let local_subrs = match metadata.kind {
418        FontKind::SID(ref sid) => Some(sid.local_subrs),
419        FontKind::CID(_) => None, // Will be resolved on request.
420    };
421
422    let mut ctx = CharStringParserContext {
423        metadata,
424        width: None,
425        stems_len: 0,
426        has_endchar: false,
427        has_seac: false,
428        glyph_id,
429        local_subrs,
430    };
431
432    let mut inner_builder = Builder {
433        builder,
434        bbox: RectF::new(),
435    };
436
437    let stack = ArgumentsStack {
438        data: &mut [0.0; MAX_ARGUMENTS_STACK_LEN], // 384B
439        len: 0,
440        max_len: MAX_ARGUMENTS_STACK_LEN,
441    };
442    let mut parser = CharStringParser {
443        stack,
444        builder: &mut inner_builder,
445        x: 0.0,
446        y: 0.0,
447        has_move_to: false,
448        is_first_move_to: true,
449        width_only,
450    };
451    _parse_char_string(&mut ctx, data, 0, &mut parser)?;
452
453    if width_only {
454        return Ok((Rect::zero(), ctx.width));
455    }
456
457    if !ctx.has_endchar {
458        return Err(CFFError::MissingEndChar);
459    }
460
461    let bbox = parser.builder.bbox;
462
463    // Check that bbox was changed.
464    if bbox.is_default() {
465        return Err(CFFError::ZeroBBox);
466    }
467
468    let rect = bbox.to_rect().ok_or(CFFError::BboxOverflow)?;
469    Ok((rect, ctx.width))
470}
471
472fn _parse_char_string(
473    ctx: &mut CharStringParserContext,
474    char_string: &[u8],
475    depth: u8,
476    p: &mut CharStringParser,
477) -> Result<(), CFFError> {
478    let mut s = Stream::new(char_string);
479    while !s.at_end() {
480        let op = s.read::<u8>().ok_or(CFFError::ReadOutOfBounds)?;
481        match op {
482            0 | 2 | 9 | 13 | 15 | 16 | 17 => {
483                // Reserved.
484                return Err(CFFError::InvalidOperator);
485            }
486            operator::HORIZONTAL_STEM
487            | operator::VERTICAL_STEM
488            | operator::HORIZONTAL_STEM_HINT_MASK
489            | operator::VERTICAL_STEM_HINT_MASK => {
490                // y dy {dya dyb}* hstem
491                // x dx {dxa dxb}* vstem
492                // y dy {dya dyb}* hstemhm
493                // x dx {dxa dxb}* vstemhm
494
495                // If the stack length is uneven, than the first value is a `width`.
496                let len = if p.stack.len().is_odd() && ctx.width.is_none() {
497                    ctx.width = Some(p.stack.at(0));
498                    p.stack.len() - 1
499                } else {
500                    p.stack.len()
501                };
502
503                ctx.stems_len += len as u32 >> 1;
504
505                // We are ignoring the hint operators.
506                p.stack.clear();
507            }
508            operator::VERTICAL_MOVE_TO => {
509                let mut i = 0;
510                if p.stack.len() == 2 {
511                    i += 1;
512                    if ctx.width.is_none() {
513                        ctx.width = Some(p.stack.at(0));
514                    }
515                }
516
517                p.parse_vertical_move_to(i)?;
518            }
519            operator::LINE_TO => {
520                p.parse_line_to()?;
521            }
522            operator::HORIZONTAL_LINE_TO => {
523                p.parse_horizontal_line_to()?;
524            }
525            operator::VERTICAL_LINE_TO => {
526                p.parse_vertical_line_to()?;
527            }
528            operator::CURVE_TO => {
529                p.parse_curve_to()?;
530            }
531            operator::CALL_LOCAL_SUBROUTINE => {
532                if p.stack.is_empty() {
533                    return Err(CFFError::InvalidArgumentsStackLength);
534                }
535
536                if depth == STACK_LIMIT {
537                    return Err(CFFError::NestingLimitReached);
538                }
539
540                // Parse and remember the local subroutine for the current glyph.
541                // Since it's a pretty complex task, we're doing it only when
542                // a local subroutine is actually requested by the glyphs charstring.
543                if ctx.local_subrs.is_none() {
544                    if let FontKind::CID(ref cid) = ctx.metadata.kind {
545                        ctx.local_subrs =
546                            parse_cid_local_subrs(ctx.metadata.table_data, ctx.glyph_id, cid);
547                    }
548                }
549
550                if let Some(local_subrs) = ctx.local_subrs {
551                    let subroutine_bias = calc_subroutine_bias(local_subrs.len());
552                    let index = conv_subroutine_index(p.stack.pop()?, subroutine_bias)?;
553                    let char_string = local_subrs
554                        .get(index)
555                        .ok_or(CFFError::InvalidSubroutineIndex)?;
556                    _parse_char_string(ctx, char_string, depth + 1, p)?;
557                } else {
558                    return Err(CFFError::NoLocalSubroutines);
559                }
560
561                if ctx.has_endchar && !ctx.has_seac {
562                    if !s.at_end() {
563                        return Err(CFFError::DataAfterEndChar);
564                    }
565
566                    break;
567                }
568            }
569            operator::RETURN => {
570                break;
571            }
572            TWO_BYTE_OPERATOR_MARK => {
573                // flex
574                let op2 = s.read::<u8>().ok_or(CFFError::ReadOutOfBounds)?;
575                match op2 {
576                    operator::HFLEX => p.parse_hflex()?,
577                    operator::FLEX => p.parse_flex()?,
578                    operator::HFLEX1 => p.parse_hflex1()?,
579                    operator::FLEX1 => p.parse_flex1()?,
580                    _ => return Err(CFFError::UnsupportedOperator),
581                }
582            }
583            operator::ENDCHAR => {
584                if p.stack.len() == 4 || (ctx.width.is_none() && p.stack.len() == 5) {
585                    // Process 'seac'.
586                    let accent_char = seac_code_to_glyph_id(&ctx.metadata.charset, p.stack.pop()?)
587                        .ok_or(CFFError::InvalidSeacCode)?;
588                    let base_char = seac_code_to_glyph_id(&ctx.metadata.charset, p.stack.pop()?)
589                        .ok_or(CFFError::InvalidSeacCode)?;
590                    let dy = p.stack.pop()?;
591                    let dx = p.stack.pop()?;
592
593                    // If a 5th argument remains, it is the Type 1-compatible 'asb'
594                    // (accent sidebearing from Type 1 seac), NOT an explicit advance
595                    // width.  veraPDF's §6.2.11.5 algorithm treats this 5-arg form as
596                    // "no explicit width" and uses defaultWidthX from the Private DICT.
597                    // Discard it so ctx.width stays None → glyph_width() returns
598                    // defaultWidthX, matching veraPDF.  Fixes #507.
599                    p.stack.clear();
600
601                    ctx.has_seac = true;
602
603                    if depth == STACK_LIMIT {
604                        return Err(CFFError::NestingLimitReached);
605                    }
606
607                    let base_char_string = ctx
608                        .metadata
609                        .char_strings
610                        .get(u32::from(base_char.0))
611                        .ok_or(CFFError::InvalidSeacCode)?;
612
613                    if p.width_only {
614                        // Width-only mode: Type 1 seac advance width = base char's
615                        // advance width.  Parse only the base char; ignore failures
616                        // (failed base → ctx.width stays None → defaultWidthX).
617                        // Skip the accent entirely — it must not affect the composite
618                        // width (e.g. "grave" w=263 must NOT overwrite "igrave" →
619                        // defaultWidthX=220).  Fixes #507.
620                        let _ = _parse_char_string(ctx, base_char_string, depth + 1, p);
621                    } else {
622                        _parse_char_string(ctx, base_char_string, depth + 1, p)?;
623                        p.x = dx;
624                        p.y = dy;
625
626                        let accent_char_string = ctx
627                            .metadata
628                            .char_strings
629                            .get(u32::from(accent_char.0))
630                            .ok_or(CFFError::InvalidSeacCode)?;
631                        _parse_char_string(ctx, accent_char_string, depth + 1, p)?;
632                    }
633                } else if p.stack.len() == 1 && ctx.width.is_none() {
634                    ctx.width = Some(p.stack.pop()?);
635                }
636
637                if !p.is_first_move_to {
638                    p.is_first_move_to = true;
639                    p.builder.close();
640                }
641
642                if !s.at_end() {
643                    return Err(CFFError::DataAfterEndChar);
644                }
645
646                ctx.has_endchar = true;
647
648                break;
649            }
650            operator::HINT_MASK | operator::COUNTER_MASK => {
651                let mut len = p.stack.len();
652
653                // We are ignoring the hint operators.
654                p.stack.clear();
655
656                // If the stack length is uneven, than the first value is a `width`.
657                if len.is_odd() {
658                    len -= 1;
659                    if ctx.width.is_none() {
660                        ctx.width = Some(p.stack.at(0));
661                    }
662                }
663
664                ctx.stems_len += len as u32 >> 1;
665
666                s.advance(usize::num_from((ctx.stems_len + 7) >> 3));
667            }
668            operator::MOVE_TO => {
669                let mut i = 0;
670                if p.stack.len() == 3 {
671                    i += 1;
672                    if ctx.width.is_none() {
673                        ctx.width = Some(p.stack.at(0));
674                    }
675                }
676
677                p.parse_move_to(i)?;
678            }
679            operator::HORIZONTAL_MOVE_TO => {
680                let mut i = 0;
681                if p.stack.len() == 2 {
682                    i += 1;
683                    if ctx.width.is_none() {
684                        ctx.width = Some(p.stack.at(0));
685                    }
686                }
687
688                p.parse_horizontal_move_to(i)?;
689            }
690            operator::CURVE_LINE => {
691                p.parse_curve_line()?;
692            }
693            operator::LINE_CURVE => {
694                p.parse_line_curve()?;
695            }
696            operator::VV_CURVE_TO => {
697                p.parse_vv_curve_to()?;
698            }
699            operator::HH_CURVE_TO => {
700                p.parse_hh_curve_to()?;
701            }
702            operator::SHORT_INT => {
703                let n = s.read::<i16>().ok_or(CFFError::ReadOutOfBounds)?;
704                p.stack.push(f64::from(n))?;
705            }
706            operator::CALL_GLOBAL_SUBROUTINE => {
707                if p.stack.is_empty() {
708                    return Err(CFFError::InvalidArgumentsStackLength);
709                }
710
711                if depth == STACK_LIMIT {
712                    return Err(CFFError::NestingLimitReached);
713                }
714
715                let subroutine_bias = calc_subroutine_bias(ctx.metadata.global_subrs.len());
716                let index = conv_subroutine_index(p.stack.pop()?, subroutine_bias)?;
717                let char_string = ctx
718                    .metadata
719                    .global_subrs
720                    .get(index)
721                    .ok_or(CFFError::InvalidSubroutineIndex)?;
722                _parse_char_string(ctx, char_string, depth + 1, p)?;
723
724                if ctx.has_endchar && !ctx.has_seac {
725                    if !s.at_end() {
726                        return Err(CFFError::DataAfterEndChar);
727                    }
728
729                    break;
730                }
731            }
732            operator::VH_CURVE_TO => {
733                p.parse_vh_curve_to()?;
734            }
735            operator::HV_CURVE_TO => {
736                p.parse_hv_curve_to()?;
737            }
738            32..=246 => {
739                p.parse_int1(op)?;
740            }
741            247..=250 => {
742                p.parse_int2(op, &mut s)?;
743            }
744            251..=254 => {
745                p.parse_int3(op, &mut s)?;
746            }
747            operator::FIXED_16_16 => {
748                p.parse_fixed(&mut s)?;
749            }
750        }
751
752        if p.width_only && ctx.width.is_some() {
753            break;
754        }
755    }
756
757    // TODO: 'A charstring subroutine must end with either an endchar or a return operator.'
758
759    Ok(())
760}
761
762fn seac_code_to_glyph_id(charset: &Charset, n: f64) -> Option<GlyphId> {
763    let code = u8::try_num_from(n)?;
764
765    let sid = STANDARD_ENCODING[usize::from(code)];
766    let sid = StringId(u16::from(sid));
767
768    match charset {
769        Charset::ISOAdobe => {
770            // ISO Adobe charset only defines string ids up to 228 (zcaron)
771            if code <= 228 {
772                Some(GlyphId(sid.0))
773            } else {
774                None
775            }
776        }
777        Charset::Expert | Charset::ExpertSubset => None,
778        _ => charset.sid_to_gid(sid),
779    }
780}
781
782#[derive(Clone, Copy, Debug)]
783enum FDSelect<'a> {
784    Format0(LazyArray16<'a, u8>),
785    Format3(&'a [u8]), // It's easier to parse it in-place.
786}
787
788impl Default for FDSelect<'_> {
789    fn default() -> Self {
790        FDSelect::Format0(LazyArray16::default())
791    }
792}
793
794impl FDSelect<'_> {
795    fn font_dict_index(&self, glyph_id: GlyphId) -> Option<u8> {
796        match self {
797            FDSelect::Format0(ref array) => array.get(glyph_id.0),
798            FDSelect::Format3(data) => {
799                let mut s = Stream::new(data);
800                let number_of_ranges = s.read::<u16>()?;
801                if number_of_ranges == 0 {
802                    return None;
803                }
804
805                // 'A sentinel GID follows the last range element and serves
806                // to delimit the last range in the array.'
807                // So we can simply increase the number of ranges by one.
808                let number_of_ranges = number_of_ranges.checked_add(1)?;
809
810                // Range is: GlyphId + u8
811                let mut prev_first_glyph = s.read::<GlyphId>()?;
812                let mut prev_index = s.read::<u8>()?;
813                for _ in 1..number_of_ranges {
814                    let curr_first_glyph = s.read::<GlyphId>()?;
815                    if (prev_first_glyph..curr_first_glyph).contains(&glyph_id) {
816                        return Some(prev_index);
817                    } else {
818                        prev_index = s.read::<u8>()?;
819                    }
820
821                    prev_first_glyph = curr_first_glyph;
822                }
823
824                None
825            }
826        }
827    }
828}
829
830fn parse_fd_select<'a>(number_of_glyphs: u16, s: &mut Stream<'a>) -> Option<FDSelect<'a>> {
831    let format = s.read::<u8>()?;
832    match format {
833        0 => Some(FDSelect::Format0(s.read_array16::<u8>(number_of_glyphs)?)),
834        3 => Some(FDSelect::Format3(s.tail()?)),
835        _ => None,
836    }
837}
838
839fn parse_sid_metadata<'a>(
840    data: &'a [u8],
841    top_dict: TopDict,
842    encoding: Encoding<'a>,
843) -> Option<FontKind<'a>> {
844    let mut metadata = SIDMetadata::default();
845    metadata.encoding = encoding;
846
847    let private_dict = if let Some(range) = top_dict.private_dict_range.clone() {
848        parse_private_dict(data.get(range)?)
849    } else {
850        return Some(FontKind::SID(metadata));
851    };
852
853    metadata.default_width = private_dict.default_width.unwrap_or(0.0);
854    metadata.nominal_width = private_dict.nominal_width.unwrap_or(0.0);
855
856    if let (Some(private_dict_range), Some(subroutines_offset)) = (
857        top_dict.private_dict_range,
858        private_dict.local_subroutines_offset,
859    ) {
860        // 'The local subroutines offset is relative to the beginning
861        // of the Private DICT data.'
862        if let Some(start) = private_dict_range.start.checked_add(subroutines_offset) {
863            let data = data.get(start..data.len())?;
864            let mut s = Stream::new(data);
865            metadata.local_subrs = parse_index::<u16>(&mut s)?;
866        }
867    }
868
869    Some(FontKind::SID(metadata))
870}
871
872fn parse_cid_metadata(data: &[u8], top_dict: TopDict, number_of_glyphs: u16) -> Option<FontKind> {
873    let (charset_offset, fd_array_offset, fd_select_offset) = match (
874        top_dict.charset_offset,
875        top_dict.fd_array_offset,
876        top_dict.fd_select_offset,
877    ) {
878        (Some(a), Some(b), Some(c)) => (a, b, c),
879        _ => return None, // charset, FDArray and FDSelect must be set.
880    };
881
882    if charset_offset <= charset_id::EXPERT_SUBSET {
883        // 'There are no predefined charsets for CID fonts.'
884        // Adobe Technical Note #5176, chapter 18 CID-keyed Fonts
885        return None;
886    }
887
888    let mut metadata = CIDMetadata::default();
889
890    metadata.fd_array = {
891        let mut s = Stream::new_at(data, fd_array_offset)?;
892        parse_index::<u16>(&mut s)?
893    };
894
895    metadata.fd_select = {
896        let mut s = Stream::new_at(data, fd_select_offset)?;
897        parse_fd_select(number_of_glyphs, &mut s)?
898    };
899
900    Some(FontKind::CID(metadata))
901}
902
903/// A [Compact Font Format Table](
904/// https://docs.microsoft.com/en-us/typography/opentype/spec/cff).
905#[derive(Clone, Copy)]
906pub struct Table<'a> {
907    // The whole CFF table.
908    // Used to resolve a local subroutine in a CID font.
909    table_data: &'a [u8],
910
911    #[allow(dead_code)]
912    strings: Index<'a>,
913    global_subrs: Index<'a>,
914    pub encoding: Encoding<'a>,
915    pub charset: Charset<'a>,
916    number_of_glyphs: NonZeroU16,
917    matrix: Matrix,
918    char_strings: Index<'a>,
919    kind: FontKind<'a>,
920    version: Option<StringId>,
921    notice: Option<StringId>,
922    full_name: Option<StringId>,
923    family_name: Option<StringId>,
924}
925
926impl<'a> Table<'a> {
927    /// Parses a table from raw data.
928    pub fn parse(data: &'a [u8]) -> Option<Self> {
929        let mut s = Stream::new(data);
930
931        // Parse Header.
932        let major = s.read::<u8>()?;
933        s.skip::<u8>(); // minor
934        let header_size = s.read::<u8>()?;
935        s.skip::<u8>(); // Absolute offset
936
937        if major != 1 {
938            return None;
939        }
940
941        // Jump to Name INDEX. It's not necessarily right after the header.
942        if header_size > 4 {
943            s.advance(usize::from(header_size) - 4);
944        }
945
946        // Skip Name INDEX.
947        skip_index::<u16>(&mut s)?;
948
949        let top_dict = parse_top_dict(&mut s)?;
950
951        // Must be set, otherwise there are nothing to parse.
952        if top_dict.char_strings_offset == 0 {
953            return None;
954        }
955
956        // String INDEX.
957        let strings = parse_index::<u16>(&mut s)?;
958
959        // Parse Global Subroutines INDEX.
960        let global_subrs = parse_index::<u16>(&mut s)?;
961
962        let char_strings = {
963            let mut s = Stream::new_at(data, top_dict.char_strings_offset)?;
964            parse_index::<u16>(&mut s)?
965        };
966
967        // 'The number of glyphs is the value of the count field in the CharStrings INDEX.'
968        let number_of_glyphs = u16::try_from(char_strings.len())
969            .ok()
970            .and_then(NonZeroU16::new)?;
971
972        let charset = match top_dict.charset_offset {
973            Some(charset_id::ISO_ADOBE) => Charset::ISOAdobe,
974            Some(charset_id::EXPERT) => Charset::Expert,
975            Some(charset_id::EXPERT_SUBSET) => Charset::ExpertSubset,
976            Some(offset) => {
977                let mut s = Stream::new_at(data, offset)?;
978                parse_charset(number_of_glyphs, &mut s)?
979            }
980            None => Charset::ISOAdobe, // default
981        };
982
983        let matrix = top_dict.matrix;
984        let version = top_dict.version;
985        let notice = top_dict.notice;
986        let full_name = top_dict.full_name;
987        let family_name = top_dict.family_name;
988
989        // Only SID fonts are allowed to have an Encoding.
990        let encoding = match top_dict.encoding_offset {
991            Some(encoding_id::STANDARD) => Encoding::new_standard(),
992            Some(encoding_id::EXPERT) => Encoding::new_expert(),
993            Some(offset) => parse_encoding(&mut Stream::new_at(data, offset)?)?,
994            None => Encoding::new_standard(), // default
995        };
996
997        let kind = if top_dict.has_ros {
998            parse_cid_metadata(data, top_dict, number_of_glyphs.get())?
999        } else {
1000            parse_sid_metadata(data, top_dict, encoding.clone())?
1001        };
1002
1003        Some(Self {
1004            table_data: data,
1005            strings,
1006            global_subrs,
1007            encoding,
1008            charset,
1009            number_of_glyphs,
1010            matrix,
1011            char_strings,
1012            kind,
1013            version,
1014            notice,
1015            full_name,
1016            family_name,
1017        })
1018    }
1019
1020    /// Returns a total number of glyphs in the font.
1021    ///
1022    /// Never zero.
1023    #[inline]
1024    pub fn number_of_glyphs(&self) -> u16 {
1025        self.number_of_glyphs.get()
1026    }
1027
1028    /// Returns a font transformation matrix.
1029    #[inline]
1030    pub fn matrix(&self) -> Matrix {
1031        self.matrix
1032    }
1033
1034    /// Returns the FontMatrix for the FD that owns `glyph_id`.
1035    ///
1036    /// CID-keyed CFF fonts can define a per-FD FontMatrix in each entry of
1037    /// the FDArray (op 12 7). If the FD has one it overrides the top-level
1038    /// matrix. For SID fonts (no FDArray) or when the FD has no explicit
1039    /// matrix, falls back to the top-level matrix.
1040    ///
1041    /// Use this instead of `matrix()` when converting CFF charstring advances
1042    /// to PDF text-space widths for CIDFontType0 width repair (6.2.11.5:1).
1043    pub fn glyph_fd_matrix(&self, glyph_id: GlyphId) -> Matrix {
1044        if let FontKind::CID(ref cid) = self.kind {
1045            if let Some(fd_index) = cid.fd_select.font_dict_index(glyph_id) {
1046                if let Some(fd_data) = cid.fd_array.get(u32::from(fd_index)) {
1047                    if let Some(fd_matrix) = parse_font_dict_matrix(fd_data) {
1048                        return fd_matrix;
1049                    }
1050                }
1051            }
1052        }
1053        self.matrix
1054    }
1055
1056    /// Outlines a glyph.
1057    pub fn outline(
1058        &self,
1059        glyph_id: GlyphId,
1060        builder: &mut dyn OutlineBuilder,
1061    ) -> Result<Rect, CFFError> {
1062        let data = self
1063            .char_strings
1064            .get(u32::from(glyph_id.0))
1065            .ok_or(CFFError::NoGlyph)?;
1066        parse_char_string(data, self, glyph_id, false, builder).map(|v| v.0)
1067    }
1068
1069    /// Resolves a Glyph ID for a code point.
1070    ///
1071    /// Similar to [`Face::glyph_index`](crate::Face::glyph_index) but 8bit
1072    /// and uses CFF encoding and charset tables instead of TrueType `cmap`.
1073    pub fn glyph_index(&self, code_point: u8) -> Option<GlyphId> {
1074        match self.kind {
1075            FontKind::SID(ref sid_meta) => {
1076                match sid_meta.encoding.code_to_gid(&self.charset, code_point) {
1077                    Some(id) => Some(id),
1078                    None => {
1079                        // Try using the Standard encoding otherwise.
1080                        // Custom Encodings does not guarantee to include all glyphs.
1081                        Encoding::new_standard().code_to_gid(&self.charset, code_point)
1082                    }
1083                }
1084            }
1085            FontKind::CID(_) => None,
1086        }
1087    }
1088
1089    /// Returns a glyph width.
1090    ///
1091    /// This value is different from outline bbox width and is stored separately.
1092    ///
1093    /// Technically similar to [`Face::glyph_hor_advance`](crate::Face::glyph_hor_advance).
1094    pub fn glyph_width(&self, glyph_id: GlyphId) -> Option<u16> {
1095        match self.kind {
1096            FontKind::SID(ref sid) => {
1097                let data = self.char_strings.get(u32::from(glyph_id.0))?;
1098                let (_, width) =
1099                    parse_char_string(data, self, glyph_id, true, &mut DummyOutline).ok()?;
1100                let width = width
1101                    .map(|w| sid.nominal_width + w)
1102                    .unwrap_or(sid.default_width);
1103                u16::try_from(width as i32).ok()
1104            }
1105            FontKind::CID(ref cid) => {
1106                // For CID-keyed CFF fonts, each glyph belongs to a Font DICT (FD)
1107                // identified by FDSelect. Each FD has its own Private DICT with
1108                // DefaultWidth and NominalWidth. The charstring width operand (if
1109                // present) is added to the NominalWidth of the glyph's own FD.
1110                let cs_data = self.char_strings.get(u32::from(glyph_id.0))?;
1111                let (_, width) =
1112                    parse_char_string(cs_data, self, glyph_id, true, &mut DummyOutline).ok()?;
1113                let font_dict_index = cid.fd_select.font_dict_index(glyph_id)?;
1114                let font_dict_data = cid.fd_array.get(u32::from(font_dict_index))?;
1115                let private_dict_range = parse_font_dict(font_dict_data)?;
1116                let private_dict_data = self.table_data.get(private_dict_range)?;
1117                let private_dict = parse_private_dict(private_dict_data);
1118                let nominal_width = private_dict.nominal_width.unwrap_or(0.0);
1119                let default_width = private_dict.default_width.unwrap_or(0.0);
1120                let width = width.map(|w| nominal_width + w).unwrap_or(default_width);
1121                u16::try_from(width as i32).ok()
1122            }
1123        }
1124    }
1125
1126    /// Returns the glyph width as a signed `f64` value.
1127    ///
1128    /// Unlike [`glyph_width`] which returns `u16` (and `None` for negative
1129    /// widths), this preserves the full CFF charstring advance including
1130    /// negative values that can arise from nominalWidthX offsets.
1131    pub fn glyph_width_f64(&self, glyph_id: GlyphId) -> Option<f64> {
1132        match self.kind {
1133            FontKind::SID(ref sid) => {
1134                let data = self.char_strings.get(u32::from(glyph_id.0))?;
1135                let (_, width) =
1136                    parse_char_string(data, self, glyph_id, true, &mut DummyOutline).ok()?;
1137                let width = width
1138                    .map(|w| sid.nominal_width + w)
1139                    .unwrap_or(sid.default_width);
1140                Some(width)
1141            }
1142            FontKind::CID(ref cid) => {
1143                let cs_data = self.char_strings.get(u32::from(glyph_id.0))?;
1144                let (_, width) =
1145                    parse_char_string(cs_data, self, glyph_id, true, &mut DummyOutline).ok()?;
1146                let font_dict_index = cid.fd_select.font_dict_index(glyph_id)?;
1147                let font_dict_data = cid.fd_array.get(u32::from(font_dict_index))?;
1148                let private_dict_range = parse_font_dict(font_dict_data)?;
1149                let private_dict_data = self.table_data.get(private_dict_range)?;
1150                let private_dict = parse_private_dict(private_dict_data);
1151                let nominal_width = private_dict.nominal_width.unwrap_or(0.0);
1152                let default_width = private_dict.default_width.unwrap_or(0.0);
1153                let width = width.map(|w| nominal_width + w).unwrap_or(default_width);
1154                Some(width)
1155            }
1156        }
1157    }
1158
1159    /// Returns the glyph width as a signed `f32` value.
1160    ///
1161    /// Prefer [`glyph_width_f64`] when exact PDF width reconstruction matters.
1162    pub fn glyph_width_f32(&self, glyph_id: GlyphId) -> Option<f32> {
1163        self.glyph_width_f64(glyph_id).map(|w| w as f32)
1164    }
1165
1166    /// The advance as veraPDF 1.28.2 computes it for a Type2 charstring.
1167    ///
1168    /// veraPDF truncates a fractional `nominalWidthX` to an integer when it
1169    /// applies it (measured on TeX subset CFFs: 384.77777 is applied as 384,
1170    /// 501.4375 as 501, so its reported advance is the true advance minus the
1171    /// fractional part). The CFF specification adds the full value, which is
1172    /// what [`glyph_width_f64`] returns. Both values sit well within the
1173    /// §6.2.11.5 tolerance of ±1 of each other, but a dictionary width that
1174    /// matches the *spec* value can exceed the tolerance against the
1175    /// *validator's* value — so dictionary widths written to satisfy veraPDF
1176    /// must use the same truncated arithmetic.
1177    pub fn glyph_width_f64_verapdf(&self, glyph_id: GlyphId) -> Option<f64> {
1178        match self.kind {
1179            FontKind::SID(ref sid) => {
1180                let data = self.char_strings.get(u32::from(glyph_id.0))?;
1181                let (_, width) =
1182                    parse_char_string(data, self, glyph_id, true, &mut DummyOutline).ok()?;
1183                let width = width
1184                    .map(|w| sid.nominal_width.trunc() + w)
1185                    .unwrap_or(sid.default_width.trunc());
1186                Some(width)
1187            }
1188            FontKind::CID(ref cid) => {
1189                let cs_data = self.char_strings.get(u32::from(glyph_id.0))?;
1190                let (_, width) =
1191                    parse_char_string(cs_data, self, glyph_id, true, &mut DummyOutline).ok()?;
1192                let font_dict_index = cid.fd_select.font_dict_index(glyph_id)?;
1193                let font_dict_data = cid.fd_array.get(u32::from(font_dict_index))?;
1194                let private_dict_range = parse_font_dict(font_dict_data)?;
1195                let private_dict_data = self.table_data.get(private_dict_range)?;
1196                let private_dict = parse_private_dict(private_dict_data);
1197                let nominal_width = private_dict.nominal_width.unwrap_or(0.0);
1198                let default_width = private_dict.default_width.unwrap_or(0.0);
1199                let width = width
1200                    .map(|w| nominal_width.trunc() + w)
1201                    .unwrap_or(default_width.trunc());
1202                Some(width)
1203            }
1204        }
1205    }
1206
1207    /// Returns a glyph ID by a name.
1208    pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> {
1209        match self.kind {
1210            FontKind::SID(_) => {
1211                let sid = if let Some(index) = STANDARD_NAMES.iter().position(|n| *n == name) {
1212                    StringId(index as u16)
1213                } else {
1214                    let index = self
1215                        .strings
1216                        .into_iter()
1217                        .position(|n| n == name.as_bytes())?;
1218                    StringId((STANDARD_NAMES.len() + index) as u16)
1219                };
1220
1221                self.charset.sid_to_gid(sid)
1222            }
1223            FontKind::CID(_) => None,
1224        }
1225    }
1226
1227    /// Returns the DefaultWidthX value from the Private DICT (SID fonts only).
1228    ///
1229    /// veraPDF uses this value as the font program width for character codes
1230    /// that are absent from the CFF encoding (i.e., `glyph_index` returns GID 0).
1231    pub fn default_width_x(&self) -> Option<u16> {
1232        match self.kind {
1233            FontKind::SID(ref sid) => u16::try_from(sid.default_width as i32).ok(),
1234            FontKind::CID(_) => None,
1235        }
1236    }
1237
1238    /// Returns the DefaultWidthX value from the Private DICT as `f64`.
1239    pub fn default_width_x_f64(&self) -> Option<f64> {
1240        match self.kind {
1241            FontKind::SID(ref sid) => Some(sid.default_width),
1242            FontKind::CID(_) => None,
1243        }
1244    }
1245
1246    /// Returns a glyph name.
1247    pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&'a str> {
1248        match self.kind {
1249            FontKind::SID(_) => {
1250                let sid = self.charset.gid_to_sid(glyph_id)?;
1251                let sid = usize::from(sid.0);
1252                match STANDARD_NAMES.get(sid) {
1253                    Some(name) => Some(name),
1254                    None => {
1255                        let idx = u32::try_from(sid - STANDARD_NAMES.len()).ok()?;
1256                        let name = self.strings.get(idx)?;
1257                        core::str::from_utf8(name).ok()
1258                    }
1259                }
1260            }
1261            FontKind::CID(_) => None,
1262        }
1263    }
1264
1265    /// Returns the CID corresponding to a glyph ID.
1266    ///
1267    /// Returns `None` if this is not a CIDFont.
1268    pub fn glyph_cid(&self, glyph_id: GlyphId) -> Option<u16> {
1269        match self.kind {
1270            FontKind::SID(_) => None,
1271            FontKind::CID(_) => self.charset.gid_to_sid(glyph_id).map(|id| id.0),
1272        }
1273    }
1274
1275    pub fn version(&self) -> Option<&str> {
1276        self.version.and_then(|sid| string_by_id(&self, sid))
1277    }
1278
1279    pub fn notice(&self) -> Option<&str> {
1280        self.notice.and_then(|sid| string_by_id(&self, sid))
1281    }
1282
1283    pub fn full_name(&self) -> Option<&str> {
1284        self.full_name.and_then(|sid| string_by_id(&self, sid))
1285    }
1286
1287    pub fn family_name(&self) -> Option<&str> {
1288        self.family_name.and_then(|sid| string_by_id(&self, sid))
1289    }
1290}
1291
1292impl core::fmt::Debug for Table<'_> {
1293    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1294        write!(f, "Table {{ ... }}")
1295    }
1296}
1297
1298#[cfg(test)]
1299mod width_tests {
1300    use super::*;
1301
1302    /// Minimal CID-keyed CFF binary with 2 glyphs and 1 Font DICT:
1303    ///
1304    /// - GID 0: endchar without width operand → DefaultWidth = 500
1305    /// - GID 1: push 650, endchar → NominalWidth(0) + 650 = 650
1306    ///
1307    /// FD Private DICT: DefaultWidth=500, NominalWidth=0
1308    ///
1309    /// Layout (64 bytes total):
1310    ///   [0]  Header
1311    ///   [4]  Name INDEX ("F")
1312    ///   [10] Top DICT INDEX (ROS, charset=35, FDSelect=38, FDArray=51, CharStrings=41)
1313    ///   [31] String INDEX (empty)
1314    ///   [33] GlobalSubr INDEX (empty)
1315    ///   [35] Charset Format 0 (GID1 → CID 1)
1316    ///   [38] FDSelect Format 0 (GID0,GID1 → FD 0)
1317    ///   [41] CharStrings INDEX (2 glyphs)
1318    ///   [51] FDArray INDEX (1 Font DICT: Private 6 @ 59)
1319    ///   [59] Private DICT (DefaultWidth=500, NominalWidth=0)
1320    #[rustfmt::skip]
1321    const MINIMAL_CID_CFF: &[u8] = &[
1322        // [0] Header: major=1, minor=0, hdrSize=4, offSize=1
1323        0x01, 0x00, 0x04, 0x01,
1324
1325        // [4] Name INDEX: count=1, offSize=1, offsets=[1,2], data="F"
1326        0x00, 0x01, 0x01, 0x01, 0x02, 0x46,
1327
1328        // [10] Top DICT INDEX: count=1, offSize=1, offsets=[1,17]
1329        0x00, 0x01, 0x01, 0x01, 0x11,
1330        // Top DICT data (16 bytes):
1331        // ROS: Adobe(SID 66) Identity(SID 228) 0 — operator 1230 (12 30)
1332        0xCD, 0xF7, 0x78, 0x8B, 0x0C, 0x1E,
1333        // charset offset 35 — operator 15
1334        0xAE, 0x0F,
1335        // FDSelect offset 38 — operator 1237 (12 37)
1336        0xB1, 0x0C, 0x25,
1337        // FDArray offset 51 — operator 1236 (12 36)
1338        0xBE, 0x0C, 0x24,
1339        // CharStrings offset 41 — operator 17
1340        0xB4, 0x11,
1341
1342        // [31] String INDEX: empty (count=0)
1343        0x00, 0x00,
1344
1345        // [33] GlobalSubr INDEX: empty (count=0)
1346        0x00, 0x00,
1347
1348        // [35] Charset Format 0: GID 1 → CID 1
1349        0x00, 0x00, 0x01,
1350
1351        // [38] FDSelect Format 0: GID 0 → FD 0, GID 1 → FD 0
1352        0x00, 0x00, 0x00,
1353
1354        // [41] CharStrings INDEX: count=2, offSize=1, offsets=[1,2,5]
1355        0x00, 0x02, 0x01, 0x01, 0x02, 0x05,
1356        // GID 0: endchar (no width operand → DefaultWidth)
1357        0x0E,
1358        // GID 1: push 650, endchar (NominalWidth + 650 = 650)
1359        0xF9, 0x1E, 0x0E,
1360
1361        // [51] FDArray INDEX: count=1, offSize=1, offsets=[1,4]
1362        0x00, 0x01, 0x01, 0x01, 0x04,
1363        // Font DICT: Private size=5 @ offset=59 — operator 18
1364        // encode(5)=0x90, encode(59)=0xC6, op18=0x12
1365        0x90, 0xC6, 0x12,
1366
1367        // [59] Private DICT: DefaultWidth=500 (op 20), NominalWidth=0 (op 21)
1368        0xF8, 0x88, 0x14,   // 500 op20
1369        0x8B, 0x15,         // 0   op21
1370    ];
1371
1372    #[test]
1373    fn cid_glyph_width_default() {
1374        // GID 0 has no width operand in its charstring → should return DefaultWidth=500.
1375        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1376        assert_eq!(
1377            table.glyph_width(GlyphId(0)),
1378            Some(500),
1379            "GID 0 must return DefaultWidth=500"
1380        );
1381    }
1382
1383    #[test]
1384    fn cid_glyph_width_nominal_plus_delta() {
1385        // GID 1 charstring: push 650, endchar → NominalWidth(0) + 650 = 650.
1386        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1387        assert_eq!(
1388            table.glyph_width(GlyphId(1)),
1389            Some(650),
1390            "GID 1 must return NominalWidth(0) + 650 = 650"
1391        );
1392    }
1393
1394    #[test]
1395    fn cid_glyph_width_out_of_range() {
1396        // GID beyond the number of glyphs must return None.
1397        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1398        assert_eq!(
1399            table.glyph_width(GlyphId(2)),
1400            None,
1401            "GID 2 is out of range and must return None"
1402        );
1403    }
1404
1405    #[test]
1406    fn number_of_glyphs_matches_charstrings_count() {
1407        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1408        assert_eq!(table.number_of_glyphs(), 2);
1409    }
1410
1411    #[test]
1412    fn glyph_cid_notdef() {
1413        // GID 0 is always .notdef with CID 0 in a CID-keyed font.
1414        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1415        assert_eq!(table.glyph_cid(GlyphId(0)), Some(0));
1416    }
1417
1418    #[test]
1419    fn glyph_cid_gid1() {
1420        // Charset Format 0 maps GID 1 → SID/CID 1.
1421        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1422        assert_eq!(table.glyph_cid(GlyphId(1)), Some(1));
1423    }
1424
1425    #[test]
1426    fn glyph_cid_out_of_range() {
1427        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1428        assert_eq!(table.glyph_cid(GlyphId(2)), None);
1429    }
1430
1431    #[test]
1432    fn glyph_name_returns_none_for_cid_font() {
1433        // CID-keyed fonts have no glyph names.
1434        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1435        assert_eq!(table.glyph_name(GlyphId(0)), None);
1436        assert_eq!(table.glyph_name(GlyphId(1)), None);
1437    }
1438
1439    #[test]
1440    fn glyph_index_returns_none_for_cid_font() {
1441        // CID-keyed fonts use FDSelect, not encoding-based lookup.
1442        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1443        assert_eq!(table.glyph_index(0x41), None); // 'A'
1444    }
1445
1446    #[test]
1447    fn matrix_is_default_when_absent() {
1448        // MINIMAL_CID_CFF has no Matrix entry → default (0.001 identity).
1449        let table = Table::parse(MINIMAL_CID_CFF).expect("CID CFF should parse");
1450        let m = table.matrix();
1451        assert!((m.sx - 0.001).abs() < f64::EPSILON);
1452        assert!((m.sy - 0.001).abs() < f64::EPSILON);
1453        assert_eq!(m.kx, 0.0);
1454        assert_eq!(m.ky, 0.0);
1455        assert_eq!(m.tx, 0.0);
1456        assert_eq!(m.ty, 0.0);
1457    }
1458
1459    #[test]
1460    fn parse_empty_data_returns_none() {
1461        assert!(Table::parse(&[]).is_none());
1462    }
1463
1464    #[test]
1465    fn parse_truncated_header_returns_none() {
1466        // Header is 4 bytes; 3 bytes is not enough.
1467        assert!(Table::parse(&[0x01, 0x00, 0x04]).is_none());
1468    }
1469
1470    #[test]
1471    fn malformed_charstring_returns_error() {
1472        let mut data = MINIMAL_CID_CFF.to_vec();
1473        data[48] = operator::CALL_GLOBAL_SUBROUTINE;
1474        data[49] = operator::ENDCHAR;
1475        data[50] = 0;
1476
1477        let table = Table::parse(&data).expect("CID CFF should parse");
1478        assert_eq!(
1479            table.outline(GlyphId(1), &mut DummyOutline),
1480            Err(CFFError::InvalidArgumentsStackLength)
1481        );
1482    }
1483}