Skip to main content

pdf_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(pub SmallVec<[Number; OPERANDS_THRESHOLD]>, pub Option<Name>);
21
22fn scn_fn(stack: &Stack<'_>) -> Option<(SmallVec<[Number; OPERANDS_THRESHOLD]>, Option<Name>)> {
23    let mut nums = smallvec![];
24    let mut name = None;
25
26    for o in &stack.0 {
27        match o {
28            Object::Number(n) => nums.push(*n),
29            Object::Name(n) => name = Some(n.clone()),
30            _ => {
31                warn!("encountered unknown object {o:?} when parsing scn/SCN");
32
33                return None;
34            }
35        }
36    }
37
38    Some((nums, name))
39}
40
41op_impl!(
42    StrokeColorNamed,
43    "SCN",
44    u8::MAX as usize,
45    |stack: &Stack<'_>| {
46        let res = scn_fn(stack);
47        res.map(|r| StrokeColorNamed(r.0, r.1))
48    }
49);
50
51#[derive(Debug, PartialEq, Clone)]
52pub struct NonStrokeColorNamed(pub SmallVec<[Number; OPERANDS_THRESHOLD]>, pub Option<Name>);
53
54op_impl!(
55    NonStrokeColorNamed,
56    "scn",
57    u8::MAX as usize,
58    |stack: &Stack<'_>| {
59        let res = scn_fn(stack);
60        res.map(|r| NonStrokeColorNamed(r.0, r.1))
61    }
62);
63
64#[cfg(test)]
65mod tests {
66    use crate::content::TypedIter;
67    use crate::content::ops::{
68        BeginMarkedContentWithProperties, ClosePath, EndMarkedContent, FillPathNonZero, LineTo,
69        MarkedContentPointWithProperties, MoveTo, NonStrokeColorDeviceRgb, NonStrokeColorNamed,
70        SetGraphicsState, StrokeColorNamed, Transform, TypedInstruction,
71    };
72    use crate::object::Name;
73    use crate::object::Number;
74    use crate::object::Object;
75    use crate::object::{Dict, FromBytes};
76    use smallvec::smallvec;
77
78    fn n(num: i32) -> Number {
79        Number::from_i32(num)
80    }
81
82    #[test]
83    fn basic_ops_1() {
84        let input = b"
851 0 0 -1 0 200 cm
86/g0 gs
871 0 0 rg
88";
89
90        let expected = vec![
91            TypedInstruction::Transform(Transform(n(1), n(0), n(0), n(-1), n(0), n(200))),
92            TypedInstruction::SetGraphicsState(SetGraphicsState(Name::new(b"g0"))),
93            TypedInstruction::NonStrokeColorDeviceRgb(NonStrokeColorDeviceRgb(n(1), n(0), n(0))),
94        ];
95
96        let elements = TypedIter::new(input).collect::<Vec<_>>();
97        assert_eq!(elements, expected,);
98    }
99
100    #[test]
101    fn basic_ops_2() {
102        let input = b"
10320 20 m
104180 20 l
105180 180 l
10620 180 l
107h
108f
109";
110
111        let expected = vec![
112            TypedInstruction::MoveTo(MoveTo(n(20), n(20))),
113            TypedInstruction::LineTo(LineTo(n(180), n(20))),
114            TypedInstruction::LineTo(LineTo(n(180), n(180))),
115            TypedInstruction::LineTo(LineTo(n(20), n(180))),
116            TypedInstruction::ClosePath(ClosePath),
117            TypedInstruction::FillPathNonZero(FillPathNonZero),
118        ];
119
120        let elements = TypedIter::new(input).collect::<Vec<_>>();
121        assert_eq!(elements, expected,);
122    }
123
124    #[test]
125    fn scn() {
126        let input = b"
1270.0 scn
1281.0 1.0 1.0 /DeviceRgb SCN
129";
130
131        let expected = vec![
132            TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(
133                smallvec![Number::from_f32(0.0)],
134                None,
135            )),
136            TypedInstruction::StrokeColorNamed(StrokeColorNamed(
137                smallvec![
138                    Number::from_f32(1.0),
139                    Number::from_f32(1.0),
140                    Number::from_f32(1.0)
141                ],
142                Some(Name::new(b"DeviceRgb")),
143            )),
144        ];
145
146        let elements = TypedIter::new(input).collect::<Vec<_>>();
147
148        assert_eq!(elements, expected);
149    }
150
151    #[test]
152    fn dp() {
153        let input = b"/Attribute<</ShowCenterPoint false >> DP";
154
155        let expected = vec![TypedInstruction::MarkedContentPointWithProperties(
156            MarkedContentPointWithProperties(
157                Name::new(b"Attribute"),
158                Object::Dict(Dict::from_bytes(b"<</ShowCenterPoint false >>").unwrap()),
159            ),
160        )];
161
162        let elements = TypedIter::new(input).collect::<Vec<_>>();
163
164        assert_eq!(elements, expected);
165    }
166
167    #[test]
168    fn bdc_with_dict() {
169        let input = b"/Span << /MCID 0 /Alt (Alt)>> BDC EMC";
170
171        let expected = vec![
172            TypedInstruction::BeginMarkedContentWithProperties(BeginMarkedContentWithProperties(
173                Name::new(b"Span"),
174                Object::Dict(Dict::from_bytes(b"<< /MCID 0 /Alt (Alt)>>").unwrap()),
175            )),
176            TypedInstruction::EndMarkedContent(EndMarkedContent),
177        ];
178
179        let elements = TypedIter::new(input).collect::<Vec<_>>();
180
181        assert_eq!(elements, expected);
182    }
183
184    #[test]
185    fn bdc_with_name() {
186        let input = b"/Span /Name BDC EMC";
187
188        let expected = vec![
189            TypedInstruction::BeginMarkedContentWithProperties(BeginMarkedContentWithProperties(
190                Name::new(b"Span"),
191                Object::Name(Name::new(b"Name")),
192            )),
193            TypedInstruction::EndMarkedContent(EndMarkedContent),
194        ];
195
196        let elements = TypedIter::new(input).collect::<Vec<_>>();
197
198        assert_eq!(elements, expected);
199    }
200}