1
//! Glyph outline generation for CFF.
2

            
3
// Portions of this file derived from ttf-parser, licenced under Apache-2.0.
4
// https://github.com/RazrFalcon/ttf-parser/tree/439aaaebd50eb8aed66302e3c1b51fae047f85b2
5

            
6
use pathfinder_geometry::line_segment::LineSegment2F;
7
use pathfinder_geometry::vector::vec2f;
8

            
9
use cff2::CFF2;
10
use charstring::CharStringParser;
11

            
12
use crate::cff;
13
use crate::error::ParseError;
14
use crate::outline::{BoundingBoxSink, OutlineBuilder, OutlineSink};
15
use crate::tables::glyf::BoundingBox;
16
use crate::tables::variable_fonts::OwnedTuple;
17

            
18
use super::charstring::{
19
    ArgumentsStack, CharStringVisitor, CharStringVisitorContext, SeacChar,
20
    VariableCharStringVisitorContext, VisitOp,
21
};
22
use super::{cff2, CFFError, CFFFont, CFFVariant, CFF};
23

            
24
mod charstring;
25

            
26
pub(crate) struct Builder<'a, B>
27
where
28
    B: OutlineSink,
29
{
30
    builder: &'a mut B,
31
    bbox: BoundingBoxSink,
32
}
33

            
34
pub struct CFFOutlines<'a, 'data> {
35
    pub table: &'a CFF<'data>,
36
}
37

            
38
pub struct CFF2Outlines<'a, 'data> {
39
    pub table: &'a CFF2<'data>,
40
}
41

            
42
impl<B> Builder<'_, B>
43
where
44
    B: OutlineSink,
45
{
46
    fn move_to(&mut self, x: f32, y: f32) {
47
        let point = vec2f(x, y);
48
        self.bbox.move_to(point);
49
        self.builder.move_to(point);
50
    }
51

            
52
    fn line_to(&mut self, x: f32, y: f32) {
53
        let point = vec2f(x, y);
54
        self.bbox.line_to(point);
55
        self.builder.line_to(point);
56
    }
57

            
58
    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
59
        let ctrl = LineSegment2F::new(vec2f(x1, y1), vec2f(x2, y2));
60
        let to = vec2f(x, y);
61
        self.bbox.cubic_curve_to(ctrl, to);
62
        self.builder.cubic_curve_to(ctrl, to);
63
    }
64

            
65
    fn close(&mut self) {
66
        self.bbox.close();
67
        self.builder.close();
68
    }
69
}
70

            
71
impl OutlineBuilder for CFFOutlines<'_, '_> {
72
    type Error = CFFError;
73
    type Output = Option<BoundingBox>;
74

            
75
    fn visit<S: OutlineSink>(
76
        &mut self,
77
        glyph_index: u16,
78
        _tuple: Option<&OwnedTuple>,
79
        sink: &mut S,
80
    ) -> Result<Self::Output, Self::Error> {
81
        let font = self.table.fonts.first().ok_or(ParseError::MissingValue)?;
82
        let local_subrs = match &font.data {
83
            CFFVariant::CID(_) => None, // local subrs will be resolved on request.
84
            CFFVariant::Type1(type1) => type1.local_subr_index.as_ref(),
85
        };
86

            
87
        let ctx = CharStringVisitorContext::new(
88
            glyph_index,
89
            &font.char_strings_index,
90
            local_subrs,
91
            &self.table.global_subr_index,
92
            None,
93
        );
94
        let mut stack = ArgumentsStack {
95
            data: &mut [0.0; cff::MAX_OPERANDS],
96
            len: 0,
97
            max_len: cff::MAX_OPERANDS,
98
        };
99

            
100
        parse_char_string(CFFFont::CFF(font), ctx, &mut stack, sink)
101
    }
102
}
103

            
104
impl OutlineBuilder for CFF2Outlines<'_, '_> {
105
    type Error = CFFError;
106
    type Output = Option<BoundingBox>;
107

            
108
    fn visit<S: OutlineSink>(
109
        &mut self,
110
        glyph_index: u16,
111
        tuple: Option<&OwnedTuple>,
112
        sink: &mut S,
113
    ) -> Result<Self::Output, Self::Error> {
114
        let font = self.table.fonts.first().ok_or(ParseError::MissingValue)?;
115

            
116
        let variable = tuple
117
            .map(|tuple| {
118
                let vstore = self
119
                    .table
120
                    .vstore
121
                    .as_ref()
122
                    .ok_or(CFFError::MissingVariationStore)?;
123
                Ok::<_, CFFError>(VariableCharStringVisitorContext {
124
                    vstore,
125
                    instance: tuple,
126
                })
127
            })
128
            .transpose()?;
129

            
130
        let ctx = CharStringVisitorContext::new(
131
            glyph_index,
132
            &self.table.char_strings_index,
133
            font.local_subr_index.as_ref(),
134
            &self.table.global_subr_index,
135
            variable,
136
        );
137

            
138
        let mut stack = ArgumentsStack {
139
            data: &mut [0.0; cff2::MAX_OPERANDS],
140
            len: 0,
141
            max_len: cff2::MAX_OPERANDS,
142
        };
143

            
144
        parse_char_string(CFFFont::CFF2(font), ctx, &mut stack, sink)
145
    }
146
}
147

            
148
fn parse_char_string<'a, 'data, B: OutlineSink>(
149
    font: CFFFont<'a, 'data>,
150
    mut context: CharStringVisitorContext<'a, 'data>,
151
    stack: &mut ArgumentsStack<'a, f32>,
152
    builder: &mut B,
153
) -> Result<Option<BoundingBox>, CFFError> {
154
    let mut inner_builder = Builder {
155
        builder,
156
        bbox: BoundingBoxSink::new(),
157
    };
158

            
159
    let mut parser = CharStringParser {
160
        builder: &mut inner_builder,
161
        x: 0.0,
162
        y: 0.0,
163
        has_move_to: false,
164
        is_first_move_to: true,
165
        temp: [0.0; cff::MAX_OPERANDS],
166
    };
167

            
168
    context.visit(font, stack, &mut parser)?;
169

            
170
    if font.is_cff2() {
171
        // > CFF2 CharStrings differ from Type 2 CharStrings in that there is no operator for
172
        // > finishing a CharString outline definition.The end of the CharString byte string
173
        // > implies the end of the last subpath, and serves the same purpose as the Type 2
174
        // > endchar operator.
175
        parser.builder.close();
176
    }
177

            
178
    let bbox = parser.builder.bbox.bbox();
179
    if bbox.is_default() {
180
        return Ok(None);
181
    }
182

            
183
    bbox.to_bounding_box()
184
        .ok_or(CFFError::BboxOverflow)
185
        .map(Some)
186
}
187

            
188
impl<B: OutlineSink> CharStringVisitor<f32, CFFError> for CharStringParser<'_, B> {
189
    fn visit(&mut self, op: VisitOp, stack: &ArgumentsStack<'_, f32>) -> Result<(), CFFError> {
190
        match op {
191
            VisitOp::HorizontalStem
192
            | VisitOp::VerticalStem
193
            | VisitOp::HorizontalStemHintMask
194
            | VisitOp::VerticalStemHintMask => {
195
                // We are ignoring the hint operators.
196
                Ok(())
197
            }
198
            VisitOp::VerticalMoveTo => self.parse_vertical_move_to(stack),
199
            VisitOp::LineTo => self.parse_line_to(stack),
200
            VisitOp::HorizontalLineTo => self.parse_horizontal_line_to(stack),
201
            VisitOp::VerticalLineTo => self.parse_vertical_line_to(stack),
202
            VisitOp::CurveTo => self.parse_curve_to(stack),
203
            VisitOp::Return => Ok(()),
204
            VisitOp::Endchar => {
205
                if !self.is_first_move_to {
206
                    self.is_first_move_to = true;
207
                    self.builder.close();
208
                }
209

            
210
                Ok(())
211
            }
212
            VisitOp::HintMask | VisitOp::CounterMask => Ok(()),
213
            VisitOp::MoveTo => self.parse_move_to(stack),
214
            VisitOp::HorizontalMoveTo => self.parse_horizontal_move_to(stack),
215
            VisitOp::CurveLine => self.parse_curve_line(stack),
216
            VisitOp::LineCurve => self.parse_line_curve(stack),
217
            VisitOp::VvCurveTo => self.parse_vv_curve_to(stack),
218
            VisitOp::HhCurveTo => self.parse_hh_curve_to(stack),
219
            VisitOp::VhCurveTo => self.parse_vh_curve_to(stack),
220
            VisitOp::HvCurveTo => self.parse_hv_curve_to(stack),
221
            VisitOp::VsIndex | VisitOp::Blend => {
222
                // Handled by the CharStringVisitor
223
                Ok(())
224
            }
225
            VisitOp::Hflex => self.parse_hflex(stack),
226
            VisitOp::Flex => self.parse_flex(stack),
227
            VisitOp::Hflex1 => self.parse_hflex1(stack),
228
            VisitOp::Flex1 => self.parse_flex1(stack),
229
        }
230
    }
231

            
232
    fn enter_seac(&mut self, seac: SeacChar, dx: f32, dy: f32) -> Result<(), CFFError> {
233
        if seac == SeacChar::Accent {
234
            self.x = dx;
235
            self.y = dy;
236
        }
237
        Ok(())
238
    }
239
}
240

            
241
#[cfg(test)]
242
mod tests {
243
    use crate::binary::read::ReadScope;
244
    use pathfinder_geometry::rect::RectI;
245
    use pathfinder_geometry::vector::{vec2i, Vector2F, Vector2I};
246
    use std::fmt::Write;
247
    use std::marker::PhantomData;
248

            
249
    use crate::binary::write::{WriteBinary, WriteBuffer};
250
    use crate::cff::charstring::operator;
251
    use crate::cff::{
252
        Charset, Encoding, Header, Index, MaybeOwnedIndex, Operand, Operator, PrivateDict, TopDict,
253
        Type1Data,
254
    };
255
    use crate::tests::writer::{self, TtfType::*};
256

            
257
    use super::*;
258

            
259
    struct Builder(String);
260

            
261
    impl OutlineSink for Builder {
262
        fn move_to(&mut self, to: Vector2F) {
263
            write!(&mut self.0, "M {} {} ", to.x(), to.y()).unwrap();
264
        }
265

            
266
        fn line_to(&mut self, to: Vector2F) {
267
            write!(&mut self.0, "L {} {} ", to.x(), to.y()).unwrap();
268
        }
269

            
270
        fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
271
            write!(
272
                &mut self.0,
273
                "Q {} {} {} {} ",
274
                ctrl.x(),
275
                ctrl.y(),
276
                to.x(),
277
                to.y()
278
            )
279
            .unwrap();
280
        }
281

            
282
        fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
283
            write!(
284
                &mut self.0,
285
                "C {} {} {} {} {} {} ",
286
                ctrl.from().x(),
287
                ctrl.from().y(),
288
                ctrl.to().x(),
289
                ctrl.to().y(),
290
                to.x(),
291
                to.y()
292
            )
293
            .unwrap();
294
        }
295

            
296
        fn close(&mut self) {
297
            write!(&mut self.0, "Z ").unwrap();
298
        }
299
    }
300

            
301
    fn gen_cff(
302
        global_subrs: &[&[writer::TtfType]],
303
        local_subrs: &[&[writer::TtfType]],
304
        chars: &[writer::TtfType],
305
    ) -> Vec<u8> {
306
        fn gen_subrs(subrs: &[&[writer::TtfType]]) -> Vec<u8> {
307
            let mut w = writer::Writer::new();
308
            for v1 in subrs {
309
                for v2 in v1.iter() {
310
                    w.write(*v2);
311
                }
312
            }
313
            w.data
314
        }
315

            
316
        // TODO: support multiple subrs
317
        assert!(global_subrs.len() <= 1);
318
        assert!(local_subrs.len() <= 1);
319

            
320
        let global_subrs_data = gen_subrs(global_subrs);
321
        let local_subrs_data = gen_subrs(local_subrs);
322
        let chars_data = writer::convert(chars);
323

            
324
        // FIXME: Explain xxx
325
        assert!(global_subrs_data.len() < 255);
326
        assert!(local_subrs_data.len() < 255);
327
        assert!(chars_data.len() < 255);
328

            
329
        // Header
330
        let header = Header {
331
            major: 1,
332
            minor: 0,
333
            hdr_size: 4,
334
            off_size: 1,
335
        };
336

            
337
        let font_name = b"Test Font";
338
        let name_index = Index {
339
            count: 1,
340
            off_size: 1,
341
            offset_array: &[1, font_name.len() as u8 + 1],
342
            data_array: font_name,
343
        };
344

            
345
        let top_dict = TopDict {
346
            dict: vec![
347
                (Operator::CharStrings, vec![Operand::Offset(0)]), // offset filled in when writing
348
                (
349
                    Operator::Private,
350
                    vec![Operand::Offset(0), Operand::Offset(0)],
351
                ), // offsets are filled in when writing
352
            ],
353
            default: PhantomData,
354
        };
355

            
356
        let string_index = MaybeOwnedIndex::Borrowed(Index {
357
            count: 0,
358
            off_size: 0,
359
            offset_array: &[],
360
            data_array: &[],
361
        });
362

            
363
        let global_subr_index = Index {
364
            count: if global_subrs_data.is_empty() { 0 } else { 1 },
365
            off_size: 1,
366
            offset_array: &[1, global_subrs_data.len() as u8 + 1],
367
            data_array: &global_subrs_data,
368
        };
369

            
370
        let char_strings_index = Index {
371
            count: 1,
372
            off_size: 1,
373
            offset_array: &[1, chars_data.len() as u8 + 1],
374
            data_array: &chars_data,
375
        };
376

            
377
        let local_subrs_index = Index {
378
            count: if local_subrs_data.is_empty() { 0 } else { 1 },
379
            off_size: 1,
380
            offset_array: &[1, local_subrs_data.len() as u8 + 1],
381
            data_array: &local_subrs_data,
382
        };
383

            
384
        let (private_dict_data, local_subr_index) = if !local_subrs_data.is_empty() {
385
            (
386
                vec![
387
                    (Operator::Subrs, vec![Operand::Offset(0)]), // offset filled in when writing
388
                ],
389
                Some(MaybeOwnedIndex::Borrowed(local_subrs_index)),
390
            )
391
        } else {
392
            (Vec::new(), None)
393
        };
394

            
395
        let private_dict = PrivateDict {
396
            dict: private_dict_data,
397
            default: PhantomData,
398
        };
399

            
400
        let cff = CFF {
401
            header,
402
            name_index: MaybeOwnedIndex::Borrowed(name_index),
403
            string_index,
404
            global_subr_index: MaybeOwnedIndex::Borrowed(global_subr_index),
405
            fonts: vec![cff::Font {
406
                top_dict,
407
                char_strings_index: MaybeOwnedIndex::Borrowed(char_strings_index),
408
                charset: Charset::ISOAdobe,
409
                data: CFFVariant::Type1(Type1Data {
410
                    encoding: Encoding::Standard,
411
                    private_dict,
412
                    local_subr_index,
413
                }),
414
            }],
415
        };
416

            
417
        let mut w = WriteBuffer::new();
418
        CFF::write(&mut w, &cff).unwrap();
419
        w.into_inner()
420
    }
421

            
422
    fn rect(x_min: i16, y_min: i16, x_max: i16, y_max: i16) -> RectI {
423
        RectI::from_points(
424
            vec2i(i32::from(x_min), i32::from(y_min)),
425
            vec2i(i32::from(x_max), i32::from(y_max)),
426
        )
427
    }
428

            
429
    // Helper for the test that parses the char string for glyph 0 and returns the result and
430
    // glyph path.
431
    fn parse_char_string0(data: &[u8]) -> (Result<RectI, CFFError>, String) {
432
        let glyph_id = 0;
433
        let metadata = ReadScope::new(data).read::<CFF<'_>>().unwrap();
434
        let mut builder = Builder(String::new());
435
        let font = metadata.fonts.first().unwrap();
436
        let local_subrs = match &font.data {
437
            CFFVariant::CID(_) => None, // local subrs will be resolved on request.
438
            CFFVariant::Type1(type1) => type1.local_subr_index.as_ref(),
439
        };
440

            
441
        let ctx = CharStringVisitorContext::new(
442
            glyph_id,
443
            &font.char_strings_index,
444
            local_subrs,
445
            &metadata.global_subr_index,
446
            None,
447
        );
448
        let mut stack = ArgumentsStack {
449
            data: &mut [0.0; cff::MAX_OPERANDS],
450
            len: 0,
451
            max_len: cff::MAX_OPERANDS,
452
        };
453

            
454
        let res = parse_char_string(CFFFont::CFF(font), ctx, &mut stack, &mut builder);
455
        (
456
            res.map(|opt_bbox| {
457
                opt_bbox
458
                    .map(|bbox| {
459
                        RectI::from_points(
460
                            vec2i(i32::from(bbox.x_min), i32::from(bbox.y_min)),
461
                            vec2i(i32::from(bbox.x_max), i32::from(bbox.y_max)),
462
                        )
463
                    })
464
                    .unwrap_or_else(|| RectI::from_points(Vector2I::zero(), Vector2I::zero()))
465
            }),
466
            builder.0,
467
        )
468
    }
469

            
470
    macro_rules! test_cs_with_subrs {
471
        ($name:ident, $glob:expr, $loc:expr, $values:expr, $path:expr, $rect_res:expr) => {
472
            #[test]
473
            fn $name() {
474
                let data = gen_cff($glob, $loc, $values);
475
                let (res, path) = parse_char_string0(&data);
476
                let rect = res.unwrap();
477

            
478
                assert_eq!(path, $path);
479
                assert_eq!(rect, $rect_res);
480
            }
481
        };
482
    }
483

            
484
    macro_rules! test_cs {
485
        ($name:ident, $values:expr, $path:expr, $rect_res:expr) => {
486
            test_cs_with_subrs!($name, &[], &[], $values, $path, $rect_res);
487
        };
488
    }
489

            
490
    macro_rules! test_cs_err {
491
        ($name:ident, $values:expr, $err:expr) => {
492
            #[test]
493
            fn $name() {
494
                let data = gen_cff(&[], &[], $values);
495
                let (res, _path) = parse_char_string0(&data);
496
                assert_eq!(res.unwrap_err(), $err);
497
            }
498
        };
499
    }
500

            
501
    test_cs!(
502
        move_to,
503
        &[
504
            CFFInt(10),
505
            CFFInt(20),
506
            UInt8(operator::MOVE_TO),
507
            UInt8(operator::ENDCHAR),
508
        ],
509
        "M 10 20 Z ",
510
        rect(10, 20, 10, 20)
511
    );
512

            
513
    test_cs!(
514
        move_to_with_width,
515
        &[
516
            CFFInt(5),
517
            CFFInt(10),
518
            CFFInt(20),
519
            UInt8(operator::MOVE_TO),
520
            UInt8(operator::ENDCHAR),
521
        ],
522
        "M 10 20 Z ",
523
        rect(10, 20, 10, 20)
524
    );
525

            
526
    test_cs!(
527
        hmove_to,
528
        &[
529
            CFFInt(10),
530
            UInt8(operator::HORIZONTAL_MOVE_TO),
531
            UInt8(operator::ENDCHAR),
532
        ],
533
        "M 10 0 Z ",
534
        rect(10, 0, 10, 0)
535
    );
536

            
537
    test_cs!(
538
        hmove_to_with_width,
539
        &[
540
            CFFInt(10),
541
            CFFInt(20),
542
            UInt8(operator::HORIZONTAL_MOVE_TO),
543
            UInt8(operator::ENDCHAR),
544
        ],
545
        "M 20 0 Z ",
546
        rect(20, 0, 20, 0)
547
    );
548

            
549
    test_cs!(
550
        vmove_to,
551
        &[
552
            CFFInt(10),
553
            UInt8(operator::VERTICAL_MOVE_TO),
554
            UInt8(operator::ENDCHAR),
555
        ],
556
        "M 0 10 Z ",
557
        rect(0, 10, 0, 10)
558
    );
559

            
560
    test_cs!(
561
        vmove_to_with_width,
562
        &[
563
            CFFInt(10),
564
            CFFInt(20),
565
            UInt8(operator::VERTICAL_MOVE_TO),
566
            UInt8(operator::ENDCHAR),
567
        ],
568
        "M 0 20 Z ",
569
        rect(0, 20, 0, 20)
570
    );
571

            
572
    test_cs!(
573
        line_to,
574
        &[
575
            CFFInt(10),
576
            CFFInt(20),
577
            UInt8(operator::MOVE_TO),
578
            CFFInt(30),
579
            CFFInt(40),
580
            UInt8(operator::LINE_TO),
581
            UInt8(operator::ENDCHAR),
582
        ],
583
        "M 10 20 L 40 60 Z ",
584
        rect(10, 20, 40, 60)
585
    );
586

            
587
    test_cs!(
588
        line_to_with_multiple_pairs,
589
        &[
590
            CFFInt(10),
591
            CFFInt(20),
592
            UInt8(operator::MOVE_TO),
593
            CFFInt(30),
594
            CFFInt(40),
595
            CFFInt(50),
596
            CFFInt(60),
597
            UInt8(operator::LINE_TO),
598
            UInt8(operator::ENDCHAR),
599
        ],
600
        "M 10 20 L 40 60 L 90 120 Z ",
601
        rect(10, 20, 90, 120)
602
    );
603

            
604
    test_cs!(
605
        hline_to,
606
        &[
607
            CFFInt(10),
608
            CFFInt(20),
609
            UInt8(operator::MOVE_TO),
610
            CFFInt(30),
611
            UInt8(operator::HORIZONTAL_LINE_TO),
612
            UInt8(operator::ENDCHAR),
613
        ],
614
        "M 10 20 L 40 20 Z ",
615
        rect(10, 20, 40, 20)
616
    );
617

            
618
    test_cs!(
619
        hline_to_with_two_coords,
620
        &[
621
            CFFInt(10),
622
            CFFInt(20),
623
            UInt8(operator::MOVE_TO),
624
            CFFInt(30),
625
            CFFInt(40),
626
            UInt8(operator::HORIZONTAL_LINE_TO),
627
            UInt8(operator::ENDCHAR),
628
        ],
629
        "M 10 20 L 40 20 L 40 60 Z ",
630
        rect(10, 20, 40, 60)
631
    );
632

            
633
    test_cs!(
634
        hline_to_with_three_coords,
635
        &[
636
            CFFInt(10),
637
            CFFInt(20),
638
            UInt8(operator::MOVE_TO),
639
            CFFInt(30),
640
            CFFInt(40),
641
            CFFInt(50),
642
            UInt8(operator::HORIZONTAL_LINE_TO),
643
            UInt8(operator::ENDCHAR),
644
        ],
645
        "M 10 20 L 40 20 L 40 60 L 90 60 Z ",
646
        rect(10, 20, 90, 60)
647
    );
648

            
649
    test_cs!(
650
        vline_to,
651
        &[
652
            CFFInt(10),
653
            CFFInt(20),
654
            UInt8(operator::MOVE_TO),
655
            CFFInt(30),
656
            UInt8(operator::VERTICAL_LINE_TO),
657
            UInt8(operator::ENDCHAR),
658
        ],
659
        "M 10 20 L 10 50 Z ",
660
        rect(10, 20, 10, 50)
661
    );
662

            
663
    test_cs!(
664
        vline_to_with_two_coords,
665
        &[
666
            CFFInt(10),
667
            CFFInt(20),
668
            UInt8(operator::MOVE_TO),
669
            CFFInt(30),
670
            CFFInt(40),
671
            UInt8(operator::VERTICAL_LINE_TO),
672
            UInt8(operator::ENDCHAR),
673
        ],
674
        "M 10 20 L 10 50 L 50 50 Z ",
675
        rect(10, 20, 50, 50)
676
    );
677

            
678
    test_cs!(
679
        vline_to_with_three_coords,
680
        &[
681
            CFFInt(10),
682
            CFFInt(20),
683
            UInt8(operator::MOVE_TO),
684
            CFFInt(30),
685
            CFFInt(40),
686
            CFFInt(50),
687
            UInt8(operator::VERTICAL_LINE_TO),
688
            UInt8(operator::ENDCHAR),
689
        ],
690
        "M 10 20 L 10 50 L 50 50 L 50 100 Z ",
691
        rect(10, 20, 50, 100)
692
    );
693

            
694
    test_cs!(
695
        curve_to,
696
        &[
697
            CFFInt(10),
698
            CFFInt(20),
699
            UInt8(operator::MOVE_TO),
700
            CFFInt(30),
701
            CFFInt(40),
702
            CFFInt(50),
703
            CFFInt(60),
704
            CFFInt(70),
705
            CFFInt(80),
706
            UInt8(operator::CURVE_TO),
707
            UInt8(operator::ENDCHAR),
708
        ],
709
        "M 10 20 C 40 60 90 120 160 200 Z ",
710
        rect(10, 20, 160, 200)
711
    );
712

            
713
    test_cs!(
714
        curve_to_with_two_sets_of_coords,
715
        &[
716
            CFFInt(10),
717
            CFFInt(20),
718
            UInt8(operator::MOVE_TO),
719
            CFFInt(30),
720
            CFFInt(40),
721
            CFFInt(50),
722
            CFFInt(60),
723
            CFFInt(70),
724
            CFFInt(80),
725
            CFFInt(90),
726
            CFFInt(100),
727
            CFFInt(110),
728
            CFFInt(120),
729
            CFFInt(130),
730
            CFFInt(140),
731
            UInt8(operator::CURVE_TO),
732
            UInt8(operator::ENDCHAR),
733
        ],
734
        "M 10 20 C 40 60 90 120 160 200 C 250 300 360 420 490 560 Z ",
735
        rect(10, 20, 490, 560)
736
    );
737

            
738
    test_cs!(
739
        hh_curve_to,
740
        &[
741
            CFFInt(10),
742
            CFFInt(20),
743
            UInt8(operator::MOVE_TO),
744
            CFFInt(30),
745
            CFFInt(40),
746
            CFFInt(50),
747
            CFFInt(60),
748
            UInt8(operator::HH_CURVE_TO),
749
            UInt8(operator::ENDCHAR),
750
        ],
751
        "M 10 20 C 40 20 80 70 140 70 Z ",
752
        rect(10, 20, 140, 70)
753
    );
754

            
755
    test_cs!(
756
        hh_curve_to_with_y,
757
        &[
758
            CFFInt(10),
759
            CFFInt(20),
760
            UInt8(operator::MOVE_TO),
761
            CFFInt(30),
762
            CFFInt(40),
763
            CFFInt(50),
764
            CFFInt(60),
765
            CFFInt(70),
766
            UInt8(operator::HH_CURVE_TO),
767
            UInt8(operator::ENDCHAR),
768
        ],
769
        "M 10 20 C 50 50 100 110 170 110 Z ",
770
        rect(10, 20, 170, 110)
771
    );
772

            
773
    test_cs!(
774
        vv_curve_to,
775
        &[
776
            CFFInt(10),
777
            CFFInt(20),
778
            UInt8(operator::MOVE_TO),
779
            CFFInt(30),
780
            CFFInt(40),
781
            CFFInt(50),
782
            CFFInt(60),
783
            UInt8(operator::VV_CURVE_TO),
784
            UInt8(operator::ENDCHAR),
785
        ],
786
        "M 10 20 C 10 50 50 100 50 160 Z ",
787
        rect(10, 20, 50, 160)
788
    );
789

            
790
    test_cs!(
791
        vv_curve_to_with_x,
792
        &[
793
            CFFInt(10),
794
            CFFInt(20),
795
            UInt8(operator::MOVE_TO),
796
            CFFInt(30),
797
            CFFInt(40),
798
            CFFInt(50),
799
            CFFInt(60),
800
            CFFInt(70),
801
            UInt8(operator::VV_CURVE_TO),
802
            UInt8(operator::ENDCHAR),
803
        ],
804
        "M 10 20 C 40 60 90 120 90 190 Z ",
805
        rect(10, 20, 90, 190)
806
    );
807

            
808
    // #[test]
809
    // fn only_endchar() {
810
    //     let data = gen_cff(&[], &[], &[UInt8(operator::ENDCHAR)]);
811
    //     let metadata = parse_metadata(&data).unwrap();
812
    //     let mut builder = Builder(String::new());
813
    //     let char_str = metadata.char_strings.get(0).unwrap();
814
    //     assert!(parse_char_string(char_str, &metadata, GlyphId(0), &mut builder).is_err());
815
    // }
816

            
817
    test_cs_with_subrs!(
818
        local_subr,
819
        &[],
820
        &[&[
821
            CFFInt(30),
822
            CFFInt(40),
823
            UInt8(operator::LINE_TO),
824
            UInt8(operator::RETURN),
825
        ]],
826
        &[
827
            CFFInt(10),
828
            UInt8(operator::HORIZONTAL_MOVE_TO),
829
            CFFInt(0 - 107), // subr index - subr bias
830
            UInt8(operator::CALL_LOCAL_SUBROUTINE),
831
            UInt8(operator::ENDCHAR),
832
        ],
833
        "M 10 0 L 40 40 Z ",
834
        rect(10, 0, 40, 40)
835
    );
836

            
837
    test_cs_with_subrs!(
838
        endchar_in_subr,
839
        &[],
840
        &[&[
841
            CFFInt(30),
842
            CFFInt(40),
843
            UInt8(operator::LINE_TO),
844
            UInt8(operator::ENDCHAR),
845
        ]],
846
        &[
847
            CFFInt(10),
848
            UInt8(operator::HORIZONTAL_MOVE_TO),
849
            CFFInt(0 - 107), // subr index - subr bias
850
            UInt8(operator::CALL_LOCAL_SUBROUTINE),
851
        ],
852
        "M 10 0 L 40 40 Z ",
853
        rect(10, 0, 40, 40)
854
    );
855

            
856
    test_cs_with_subrs!(
857
        global_subr,
858
        &[&[
859
            CFFInt(30),
860
            CFFInt(40),
861
            UInt8(operator::LINE_TO),
862
            UInt8(operator::RETURN),
863
        ]],
864
        &[],
865
        &[
866
            CFFInt(10),
867
            UInt8(operator::HORIZONTAL_MOVE_TO),
868
            CFFInt(0 - 107), // subr index - subr bias
869
            UInt8(operator::CALL_GLOBAL_SUBROUTINE),
870
            UInt8(operator::ENDCHAR),
871
        ],
872
        "M 10 0 L 40 40 Z ",
873
        rect(10, 0, 40, 40)
874
    );
875

            
876
    test_cs_err!(
877
        reserved_operator,
878
        &[CFFInt(10), UInt8(2), UInt8(operator::ENDCHAR),],
879
        CFFError::InvalidOperator
880
    );
881

            
882
    test_cs_err!(
883
        line_to_without_move_to,
884
        &[
885
            CFFInt(10),
886
            CFFInt(20),
887
            UInt8(operator::LINE_TO),
888
            UInt8(operator::ENDCHAR),
889
        ],
890
        CFFError::MissingMoveTo
891
    );
892

            
893
    // Width must be set only once.
894
    test_cs_err!(
895
        two_vmove_to_with_width,
896
        &[
897
            CFFInt(10),
898
            CFFInt(20),
899
            UInt8(operator::VERTICAL_MOVE_TO),
900
            CFFInt(10),
901
            CFFInt(20),
902
            UInt8(operator::VERTICAL_MOVE_TO),
903
            UInt8(operator::ENDCHAR),
904
        ],
905
        CFFError::InvalidArgumentsStackLength
906
    );
907

            
908
    test_cs_err!(
909
        move_to_with_too_many_coords,
910
        &[
911
            CFFInt(10),
912
            CFFInt(10),
913
            CFFInt(10),
914
            CFFInt(20),
915
            UInt8(operator::MOVE_TO),
916
            UInt8(operator::ENDCHAR),
917
        ],
918
        CFFError::InvalidArgumentsStackLength
919
    );
920

            
921
    test_cs_err!(
922
        move_to_with_not_enought_coords,
923
        &[
924
            CFFInt(10),
925
            UInt8(operator::MOVE_TO),
926
            UInt8(operator::ENDCHAR),
927
        ],
928
        CFFError::InvalidArgumentsStackLength
929
    );
930

            
931
    test_cs_err!(
932
        hmove_to_with_too_many_coords,
933
        &[
934
            CFFInt(10),
935
            CFFInt(10),
936
            CFFInt(10),
937
            UInt8(operator::HORIZONTAL_MOVE_TO),
938
            UInt8(operator::ENDCHAR),
939
        ],
940
        CFFError::InvalidArgumentsStackLength
941
    );
942

            
943
    test_cs_err!(
944
        hmove_to_with_not_enought_coords,
945
        &[
946
            UInt8(operator::HORIZONTAL_MOVE_TO),
947
            UInt8(operator::ENDCHAR),
948
        ],
949
        CFFError::InvalidArgumentsStackLength
950
    );
951

            
952
    test_cs_err!(
953
        vmove_to_with_too_many_coords,
954
        &[
955
            CFFInt(10),
956
            CFFInt(10),
957
            CFFInt(10),
958
            UInt8(operator::VERTICAL_MOVE_TO),
959
            UInt8(operator::ENDCHAR),
960
        ],
961
        CFFError::InvalidArgumentsStackLength
962
    );
963

            
964
    test_cs_err!(
965
        vmove_to_with_not_enought_coords,
966
        &[UInt8(operator::VERTICAL_MOVE_TO), UInt8(operator::ENDCHAR),],
967
        CFFError::InvalidArgumentsStackLength
968
    );
969

            
970
    test_cs_err!(
971
        line_to_with_single_coord,
972
        &[
973
            CFFInt(10),
974
            CFFInt(20),
975
            UInt8(operator::MOVE_TO),
976
            CFFInt(30),
977
            UInt8(operator::LINE_TO),
978
            UInt8(operator::ENDCHAR),
979
        ],
980
        CFFError::InvalidArgumentsStackLength
981
    );
982

            
983
    test_cs_err!(
984
        line_to_with_odd_number_of_coord,
985
        &[
986
            CFFInt(10),
987
            CFFInt(20),
988
            UInt8(operator::MOVE_TO),
989
            CFFInt(30),
990
            CFFInt(40),
991
            CFFInt(50),
992
            UInt8(operator::LINE_TO),
993
            UInt8(operator::ENDCHAR),
994
        ],
995
        CFFError::InvalidArgumentsStackLength
996
    );
997

            
998
    test_cs_err!(
999
        hline_to_without_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            UInt8(operator::HORIZONTAL_LINE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        vline_to_without_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            UInt8(operator::VERTICAL_LINE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        curve_to_with_invalid_num_of_coords_1,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            CFFInt(60),
            UInt8(operator::CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        curve_to_with_invalid_num_of_coords_2,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            CFFInt(60),
            CFFInt(70),
            CFFInt(80),
            CFFInt(90),
            UInt8(operator::CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        hh_curve_to_with_not_enought_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            UInt8(operator::HH_CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        hh_curve_to_with_too_many_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            UInt8(operator::HH_CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        vv_curve_to_with_not_enought_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            UInt8(operator::VV_CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        vv_curve_to_with_too_many_coords,
        &[
            CFFInt(10),
            CFFInt(20),
            UInt8(operator::MOVE_TO),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            CFFInt(30),
            CFFInt(40),
            CFFInt(50),
            UInt8(operator::VV_CURVE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::InvalidArgumentsStackLength
    );
    test_cs_err!(
        multiple_endchar,
        &[UInt8(operator::ENDCHAR), UInt8(operator::ENDCHAR),],
        CFFError::DataAfterEndChar
    );
    test_cs_err!(
        operands_overflow,
        &[
            CFFInt(0),
            CFFInt(1),
            CFFInt(2),
            CFFInt(3),
            CFFInt(4),
            CFFInt(5),
            CFFInt(6),
            CFFInt(7),
            CFFInt(8),
            CFFInt(9),
            CFFInt(0),
            CFFInt(1),
            CFFInt(2),
            CFFInt(3),
            CFFInt(4),
            CFFInt(5),
            CFFInt(6),
            CFFInt(7),
            CFFInt(8),
            CFFInt(9),
            CFFInt(0),
            CFFInt(1),
            CFFInt(2),
            CFFInt(3),
            CFFInt(4),
            CFFInt(5),
            CFFInt(6),
            CFFInt(7),
            CFFInt(8),
            CFFInt(9),
            CFFInt(0),
            CFFInt(1),
            CFFInt(2),
            CFFInt(3),
            CFFInt(4),
            CFFInt(5),
            CFFInt(6),
            CFFInt(7),
            CFFInt(8),
            CFFInt(9),
            CFFInt(0),
            CFFInt(1),
            CFFInt(2),
            CFFInt(3),
            CFFInt(4),
            CFFInt(5),
            CFFInt(6),
            CFFInt(7),
            CFFInt(8),
            CFFInt(9),
        ],
        CFFError::ArgumentsStackLimitReached
    );
    test_cs_err!(
        operands_overflow_with_4_byte_ints,
        &[
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
            CFFInt(30000),
        ],
        CFFError::ArgumentsStackLimitReached
    );
    test_cs_err!(
        bbox_overflow,
        &[
            CFFInt(32767),
            UInt8(operator::HORIZONTAL_MOVE_TO),
            CFFInt(32767),
            UInt8(operator::HORIZONTAL_LINE_TO),
            UInt8(operator::ENDCHAR),
        ],
        CFFError::BboxOverflow
    );
    #[test]
    fn endchar_in_subr_with_extra_data_1() {
        let data = gen_cff(
            &[],
            &[&[
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
                UInt8(operator::ENDCHAR),
            ]],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
    }
    #[test]
    fn endchar_in_subr_with_extra_data_2() {
        let data = gen_cff(
            &[],
            &[&[
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
                UInt8(operator::ENDCHAR),
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
            ]],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
    }
    #[test]
    fn subr_without_return() {
        let data = gen_cff(
            &[],
            &[&[
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
                UInt8(operator::ENDCHAR),
                CFFInt(30),
                CFFInt(40),
                UInt8(operator::LINE_TO),
            ]],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
    }
    #[test]
    fn recursive_local_subr() {
        let data = gen_cff(
            &[],
            &[&[
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
            ]],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
    }
    #[test]
    fn recursive_global_subr() {
        let data = gen_cff(
            &[&[
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_GLOBAL_SUBROUTINE),
            ]],
            &[],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_GLOBAL_SUBROUTINE),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
    }
    #[test]
    fn recursive_mixed_subr() {
        let data = gen_cff(
            &[&[
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_LOCAL_SUBROUTINE),
            ]],
            &[&[
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_GLOBAL_SUBROUTINE),
            ]],
            &[
                CFFInt(10),
                UInt8(operator::HORIZONTAL_MOVE_TO),
                CFFInt(0 - 107), // subr index - subr bias
                UInt8(operator::CALL_GLOBAL_SUBROUTINE),
            ],
        );
        let (res, _path) = parse_char_string0(&data);
        assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
    }
    // TODO: return from main
    // TODO: return without endchar
    // TODO: data after return
    // TODO: recursive subr
    // TODO: HORIZONTAL_STEM
    // TODO: VERTICAL_STEM
    // TODO: HORIZONTAL_STEM_HINT_MASK
    // TODO: HINT_MASK
    // TODO: COUNTER_MASK
    // TODO: VERTICAL_STEM_HINT_MASK
    // TODO: CURVE_LINE
    // TODO: LINE_CURVE
    // TODO: VH_CURVE_TO
    // TODO: HFLEX
    // TODO: FLEX
    // TODO: HFLEX1
    // TODO: FLEX1
}