hayro_syntax/content/
ops.rs

1//! Content stream operators.
2
3use crate::content::{Instruction, OPERANDS_THRESHOLD, OperatorTrait, Stack};
4use crate::object;
5use crate::object::Array;
6use crate::object::Name;
7use crate::object::Number;
8use crate::object::Object;
9use crate::object::Stream;
10use smallvec::{SmallVec, smallvec};
11
12use crate::content::macros::{op_all, op_impl, op0, op1, op2, op3, op4, op6};
13use log::warn;
14
15include!("ops_generated.rs");
16
17// Need to special-case those because they have variable arguments.
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct StrokeColorNamed<'a>(
21    pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
22    pub Option<Name<'a>>,
23);
24
25fn scn_fn<'a>(
26    stack: &Stack<'a>,
27) -> Option<(SmallVec<[Number; OPERANDS_THRESHOLD]>, Option<Name<'a>>)> {
28    let mut nums = smallvec![];
29    let mut name = None;
30
31    for o in &stack.0 {
32        match o {
33            Object::Number(n) => nums.push(*n),
34            Object::Name(n) => name = Some(n.clone()),
35            _ => {
36                warn!("encountered unknown object {o:?} when parsing scn/SCN");
37
38                return None;
39            }
40        }
41    }
42
43    Some((nums, name))
44}
45
46op_impl!(
47    StrokeColorNamed<'a>,
48    "SCN",
49    u8::MAX as usize,
50    |stack: &Stack<'a>| {
51        let res = scn_fn(stack);
52        res.map(|r| StrokeColorNamed(r.0, r.1))
53    }
54);
55
56#[derive(Debug, PartialEq, Clone)]
57pub struct NonStrokeColorNamed<'a>(
58    pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
59    pub Option<Name<'a>>,
60);
61
62op_impl!(
63    NonStrokeColorNamed<'a>,
64    "scn",
65    u8::MAX as usize,
66    |stack: &Stack<'a>| {
67        let res = scn_fn(stack);
68        res.map(|r| NonStrokeColorNamed(r.0, r.1))
69    }
70);
71
72#[cfg(test)]
73mod tests {
74    use crate::content::TypedIter;
75    use crate::content::ops::{
76        BeginMarkedContentWithProperties, ClosePath, EndMarkedContent, FillPathNonZero, LineTo,
77        MarkedContentPointWithProperties, MoveTo, NonStrokeColorDeviceRgb, NonStrokeColorNamed,
78        SetGraphicsState, StrokeColorNamed, Transform, TypedInstruction,
79    };
80    use crate::object::Name;
81    use crate::object::Number;
82    use crate::object::Object;
83    use crate::object::{Dict, FromBytes};
84    use smallvec::smallvec;
85
86    fn n(num: i32) -> Number {
87        Number::from_i32(num)
88    }
89
90    #[test]
91    fn basic_ops_1() {
92        let input = b"
931 0 0 -1 0 200 cm
94/g0 gs
951 0 0 rg
96";
97
98        let expected = vec![
99            TypedInstruction::Transform(Transform(n(1), n(0), n(0), n(-1), n(0), n(200))),
100            TypedInstruction::SetGraphicsState(SetGraphicsState(Name::new(b"g0"))),
101            TypedInstruction::NonStrokeColorDeviceRgb(NonStrokeColorDeviceRgb(n(1), n(0), n(0))),
102        ];
103
104        let elements = TypedIter::new(input).collect::<Vec<_>>();
105        assert_eq!(elements, expected,)
106    }
107
108    #[test]
109    fn basic_ops_2() {
110        let input = b"
11120 20 m
112180 20 l
113180 180 l
11420 180 l
115h
116f
117";
118
119        let expected = vec![
120            TypedInstruction::MoveTo(MoveTo(n(20), n(20))),
121            TypedInstruction::LineTo(LineTo(n(180), n(20))),
122            TypedInstruction::LineTo(LineTo(n(180), n(180))),
123            TypedInstruction::LineTo(LineTo(n(20), n(180))),
124            TypedInstruction::ClosePath(ClosePath),
125            TypedInstruction::FillPathNonZero(FillPathNonZero),
126        ];
127
128        let elements = TypedIter::new(input).collect::<Vec<_>>();
129        assert_eq!(elements, expected,)
130    }
131
132    #[test]
133    fn scn() {
134        let input = b"
1350.0 scn
1361.0 1.0 1.0 /DeviceRgb SCN
137";
138
139        let expected = vec![
140            TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(
141                smallvec![Number::from_i32(0)],
142                None,
143            )),
144            TypedInstruction::StrokeColorNamed(StrokeColorNamed(
145                smallvec![
146                    Number::from_i32(1),
147                    Number::from_i32(1),
148                    Number::from_i32(1)
149                ],
150                Some(Name::new(b"DeviceRgb")),
151            )),
152        ];
153
154        let elements = TypedIter::new(input).collect::<Vec<_>>();
155
156        assert_eq!(elements, expected);
157    }
158
159    #[test]
160    fn dp() {
161        let input = b"/Attribute<</ShowCenterPoint false >> DP";
162
163        let expected = vec![TypedInstruction::MarkedContentPointWithProperties(
164            MarkedContentPointWithProperties(
165                Name::new(b"Attribute"),
166                Object::Dict(Dict::from_bytes(b"<</ShowCenterPoint false >>").unwrap()),
167            ),
168        )];
169
170        let elements = TypedIter::new(input).collect::<Vec<_>>();
171
172        assert_eq!(elements, expected);
173    }
174
175    #[test]
176    fn bdc_with_dict() {
177        let input = b"/Span << /MCID 0 /Alt (Alt)>> BDC EMC";
178
179        let expected = vec![
180            TypedInstruction::BeginMarkedContentWithProperties(BeginMarkedContentWithProperties(
181                Name::new(b"Span"),
182                Object::Dict(Dict::from_bytes(b"<< /MCID 0 /Alt (Alt)>>").unwrap()),
183            )),
184            TypedInstruction::EndMarkedContent(EndMarkedContent),
185        ];
186
187        let elements = TypedIter::new(input).collect::<Vec<_>>();
188
189        assert_eq!(elements, expected);
190    }
191
192    #[test]
193    fn bdc_with_name() {
194        let input = b"/Span /Name BDC EMC";
195
196        let expected = vec![
197            TypedInstruction::BeginMarkedContentWithProperties(BeginMarkedContentWithProperties(
198                Name::new(b"Span"),
199                Object::Name(Name::new(b"Name")),
200            )),
201            TypedInstruction::EndMarkedContent(EndMarkedContent),
202        ];
203
204        let elements = TypedIter::new(input).collect::<Vec<_>>();
205
206        assert_eq!(elements, expected);
207    }
208}