Skip to main content

hayro_syntax/content/
ops.rs

1//! Content stream operators.
2
3use crate::content::{INLINE_COLOR_COMPONENTS, Instruction, 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 core::fmt::{Display, Formatter};
11use smallvec::{SmallVec, smallvec};
12
13use crate::content::macros::{op_all, op_impl, op0, op1, op2, op3, op4, op6};
14
15include!("ops_generated.rs");
16
17// Need to special-case those because they have variable arguments.
18
19fn parse_named_color<'b, 'a>(
20    objects: &'b [Object<'a>],
21) -> Option<(
22    SmallVec<[Number; INLINE_COLOR_COMPONENTS]>,
23    Option<&'b Name<'a>>,
24)> {
25    let mut nums = smallvec![];
26    let mut name = None;
27
28    for o in objects {
29        match o {
30            Object::Number(n) => nums.push(*n),
31            Object::Name(n) => name = Some(n),
32            _ => {
33                return None;
34            }
35        }
36    }
37
38    Some((nums, name))
39}
40
41fn fmt_named_color(
42    f: &mut Formatter<'_>,
43    operator: &str,
44    numbers: &[Number],
45    name: Option<&Name<'_>>,
46) -> core::fmt::Result {
47    f.write_str(operator)?;
48    if !numbers.is_empty() || name.is_some() {
49        f.write_str("(")?;
50        for (index, number) in numbers.iter().enumerate() {
51            if index > 0 {
52                f.write_str(" ")?;
53            }
54            Display::fmt(number, f)?;
55        }
56        if let Some(name) = name {
57            if !numbers.is_empty() {
58                f.write_str(" ")?;
59            }
60            Display::fmt(name, f)?;
61        }
62        f.write_str(")")?;
63    }
64    Ok(())
65}
66
67#[derive(Debug, Clone, PartialEq)]
68pub struct StrokeColorNamed<'b, 'a>(
69    pub SmallVec<[Number; INLINE_COLOR_COMPONENTS]>,
70    pub Option<&'b Name<'a>>,
71);
72
73impl Display for StrokeColorNamed<'_, '_> {
74    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
75        fmt_named_color(f, "StrokeColorNamed", &self.0, self.1)
76    }
77}
78
79op_impl!(
80    StrokeColorNamed<'b, 'a>,
81    "SCN",
82    u8::MAX as usize,
83    |stack: &'b Stack<'a>| {
84        let (nums, name) = parse_named_color(stack.as_slice())?;
85        Some(StrokeColorNamed(nums, name))
86    }
87);
88
89#[derive(Debug, PartialEq, Clone)]
90pub struct NonStrokeColorNamed<'b, 'a>(
91    pub SmallVec<[Number; INLINE_COLOR_COMPONENTS]>,
92    pub Option<&'b Name<'a>>,
93);
94
95impl Display for NonStrokeColorNamed<'_, '_> {
96    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
97        fmt_named_color(f, "NonStrokeColorNamed", &self.0, self.1)
98    }
99}
100
101op_impl!(
102    NonStrokeColorNamed<'b, 'a>,
103    "scn",
104    u8::MAX as usize,
105    |stack: &'b Stack<'a>| {
106        let (nums, name) = parse_named_color(stack.as_slice())?;
107        Some(NonStrokeColorNamed(nums, name))
108    }
109);
110
111#[cfg(test)]
112mod tests {
113    use crate::content::TypedIter;
114    use crate::content::ops::{
115        BeginMarkedContentWithProperties, ClosePath, EndMarkedContent, FillPathNonZero, LineTo,
116        MarkedContentPointWithProperties, MoveTo, NonStrokeColorDeviceRgb, NonStrokeColorNamed,
117        SetGraphicsState, StrokeColorNamed, Transform, TypedInstruction,
118    };
119    use crate::object::Name;
120    use crate::object::Number;
121    use crate::object::Object;
122    use crate::object::{Dict, FromBytes};
123    fn n(num: i32) -> Number {
124        Number::from_i32(num)
125    }
126
127    #[test]
128    fn basic_ops_1() {
129        let input = b"
1301 0 0 -1 0 200 cm
131/g0 gs
1321 0 0 rg
133";
134
135        let mut iter = TypedIter::new(input);
136
137        assert!(matches!(
138            iter.next(),
139            Some(TypedInstruction::Transform(Transform(a, b, c, d, e, f)))
140                if [a, b, c, d, e, f] == [n(1), n(0), n(0), n(-1), n(0), n(200)]
141        ));
142        assert!(matches!(
143            iter.next(),
144            Some(TypedInstruction::SetGraphicsState(SetGraphicsState(name)))
145                if name.as_ref() == b"g0"
146        ));
147        assert!(matches!(
148            iter.next(),
149            Some(TypedInstruction::NonStrokeColorDeviceRgb(NonStrokeColorDeviceRgb(r, g, b)))
150                if [r, g, b] == [n(1), n(0), n(0)]
151        ));
152        assert!(iter.next().is_none());
153    }
154
155    #[test]
156    fn basic_ops_2() {
157        let input = b"
15820 20 m
159180 20 l
160180 180 l
16120 180 l
162h
163f
164";
165
166        let mut iter = TypedIter::new(input);
167
168        assert!(matches!(
169            iter.next(),
170            Some(TypedInstruction::MoveTo(MoveTo(x, y))) if [x, y] == [n(20), n(20)]
171        ));
172        assert!(matches!(
173            iter.next(),
174            Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(20)]
175        ));
176        assert!(matches!(
177            iter.next(),
178            Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(180)]
179        ));
180        assert!(matches!(
181            iter.next(),
182            Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(20), n(180)]
183        ));
184        assert!(matches!(
185            iter.next(),
186            Some(TypedInstruction::ClosePath(ClosePath))
187        ));
188        assert!(matches!(
189            iter.next(),
190            Some(TypedInstruction::FillPathNonZero(FillPathNonZero))
191        ));
192        assert!(iter.next().is_none());
193    }
194
195    #[test]
196    fn display_ops() {
197        let mut iter = TypedIter::new(
198            b"h 2 w 1 0 0 RG 0 0 10 20 re 10 20 m 30 20 60 40 90 80 c \
199              0.1 0.2 SC 0.0 scn 1.0 /Spot SCN (Hello) Tj <00FF> Tj foo",
200        );
201
202        assert_eq!(format!("{}", iter.next().unwrap()), "ClosePath");
203        assert_eq!(format!("{}", iter.next().unwrap()), "LineWidth(2)");
204        assert_eq!(
205            format!("{}", iter.next().unwrap()),
206            "StrokeColorDeviceRgb(1 0 0)"
207        );
208        assert_eq!(format!("{}", iter.next().unwrap()), "RectPath(0 0 10 20)");
209        assert_eq!(format!("{}", iter.next().unwrap()), "MoveTo(10 20)");
210        assert_eq!(
211            format!("{}", iter.next().unwrap()),
212            "CubicTo(30 20 60 40 90 80)"
213        );
214        assert_eq!(format!("{}", iter.next().unwrap()), "StrokeColor(0.1 0.2)");
215        assert_eq!(
216            format!("{}", iter.next().unwrap()),
217            "NonStrokeColorNamed(0.0)"
218        );
219        assert_eq!(
220            format!("{}", iter.next().unwrap()),
221            "StrokeColorNamed(1.0 /Spot)"
222        );
223        assert_eq!(format!("{}", iter.next().unwrap()), "ShowText(\"Hello\")");
224        assert_eq!(format!("{}", iter.next().unwrap()), "ShowText([0, 255])");
225        assert_eq!(format!("{}", iter.next().unwrap()), "Fallback(foo)");
226        assert!(iter.next().is_none());
227    }
228
229    #[test]
230    fn scn() {
231        let input = b"
2320.0 scn
2331.0 1.0 1.0 /DeviceRgb SCN
234";
235
236        let mut iter = TypedIter::new(input);
237
238        match iter.next() {
239            Some(TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(nums, None))) => {
240                assert_eq!(nums.as_slice(), &[Number::from_f32(0.0)]);
241            }
242            other => panic!("unexpected instruction: {other:?}"),
243        }
244
245        match iter.next() {
246            Some(TypedInstruction::StrokeColorNamed(StrokeColorNamed(nums, Some(name)))) => {
247                assert_eq!(
248                    nums.as_slice(),
249                    &[
250                        Number::from_f32(1.0),
251                        Number::from_f32(1.0),
252                        Number::from_f32(1.0)
253                    ]
254                );
255                assert_eq!(name.as_ref(), b"DeviceRgb");
256            }
257            other => panic!("unexpected instruction: {other:?}"),
258        }
259
260        assert!(iter.next().is_none());
261    }
262
263    #[test]
264    fn scn_device_n_pattern() {
265        let input = [b"0.5 ".repeat(32), b"/P0 scn 0 0 m".to_vec()].concat();
266
267        let mut iter = TypedIter::new(&input);
268
269        match iter.next() {
270            Some(TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(nums, Some(name)))) => {
271                assert_eq!(nums.as_slice(), &[Number::from_f32(0.5); 32]);
272                assert_eq!(name.as_ref(), b"P0");
273            }
274            other => panic!("unexpected instruction: {other:?}"),
275        }
276
277        assert!(matches!(iter.next(), Some(TypedInstruction::MoveTo(_))));
278        assert!(iter.next().is_none());
279    }
280
281    #[test]
282    fn dp() {
283        let input = b"/Attribute<</ShowCenterPoint false >> DP";
284
285        let mut iter = TypedIter::new(input);
286
287        match iter.next() {
288            Some(TypedInstruction::MarkedContentPointWithProperties(
289                MarkedContentPointWithProperties(name, object),
290            )) => {
291                assert_eq!(name.as_ref(), b"Attribute");
292                assert_eq!(
293                    object,
294                    &Object::Dict(Dict::from_bytes(b"<</ShowCenterPoint false >>").unwrap())
295                );
296            }
297            other => panic!("unexpected instruction: {other:?}"),
298        }
299
300        assert!(iter.next().is_none());
301    }
302
303    #[test]
304    fn bdc_with_dict() {
305        let input = b"/Span << /MCID 0 /Alt (Alt)>> BDC EMC";
306
307        let mut iter = TypedIter::new(input);
308
309        match iter.next() {
310            Some(TypedInstruction::BeginMarkedContentWithProperties(
311                BeginMarkedContentWithProperties(name, object),
312            )) => {
313                assert_eq!(name.as_ref(), b"Span");
314                assert_eq!(
315                    object,
316                    &Object::Dict(Dict::from_bytes(b"<< /MCID 0 /Alt (Alt)>>").unwrap())
317                );
318            }
319            other => panic!("unexpected instruction: {other:?}"),
320        }
321
322        assert!(matches!(
323            iter.next(),
324            Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
325        ));
326        assert!(iter.next().is_none());
327    }
328
329    #[test]
330    fn bdc_with_name() {
331        let input = b"/Span /Name BDC EMC";
332
333        let mut iter = TypedIter::new(input);
334
335        match iter.next() {
336            Some(TypedInstruction::BeginMarkedContentWithProperties(
337                BeginMarkedContentWithProperties(name, object),
338            )) => {
339                assert_eq!(name.as_ref(), b"Span");
340                assert_eq!(object, &Object::Name(Name::new_unescaped(b"Name")));
341            }
342            other => panic!("unexpected instruction: {other:?}"),
343        }
344
345        assert!(matches!(
346            iter.next(),
347            Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
348        ));
349        assert!(iter.next().is_none());
350    }
351}