1use 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};
13
14include!("ops_generated.rs");
15
16fn parse_named_color<'b, 'a>(
19 objects: &'b [Object<'a>],
20) -> Option<(SmallVec<[Number; OPERANDS_THRESHOLD]>, Option<&'b Name<'a>>)> {
21 let mut nums = smallvec![];
22 let mut name = None;
23
24 for o in objects {
25 match o {
26 Object::Number(n) => nums.push(*n),
27 Object::Name(n) => name = Some(n),
28 _ => {
29 warn!("encountered unknown object {o:?} when parsing scn/SCN");
30
31 return None;
32 }
33 }
34 }
35
36 Some((nums, name))
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct StrokeColorNamed<'b, 'a>(
41 pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
42 pub Option<&'b Name<'a>>,
43);
44
45op_impl!(
46 StrokeColorNamed<'b, 'a>,
47 "SCN",
48 u8::MAX as usize,
49 |stack: &'b Stack<'a>| {
50 let (nums, name) = parse_named_color(stack.as_slice())?;
51 Some(StrokeColorNamed(nums, name))
52 }
53);
54
55#[derive(Debug, PartialEq, Clone)]
56pub struct NonStrokeColorNamed<'b, 'a>(
57 pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
58 pub Option<&'b Name<'a>>,
59);
60
61op_impl!(
62 NonStrokeColorNamed<'b, 'a>,
63 "scn",
64 u8::MAX as usize,
65 |stack: &'b Stack<'a>| {
66 let (nums, name) = parse_named_color(stack.as_slice())?;
67 Some(NonStrokeColorNamed(nums, name))
68 }
69);
70
71#[cfg(test)]
72mod tests {
73 use crate::content::TypedIter;
74 use crate::content::ops::{
75 BeginMarkedContentWithProperties, ClosePath, EndMarkedContent, FillPathNonZero, LineTo,
76 MarkedContentPointWithProperties, MoveTo, NonStrokeColorDeviceRgb, NonStrokeColorNamed,
77 SetGraphicsState, StrokeColorNamed, Transform, TypedInstruction,
78 };
79 use crate::object::Name;
80 use crate::object::Number;
81 use crate::object::Object;
82 use crate::object::{Dict, FromBytes};
83 fn n(num: i32) -> Number {
84 Number::from_i32(num)
85 }
86
87 #[test]
88 fn basic_ops_1() {
89 let input = b"
901 0 0 -1 0 200 cm
91/g0 gs
921 0 0 rg
93";
94
95 let mut iter = TypedIter::new(input);
96
97 assert!(matches!(
98 iter.next(),
99 Some(TypedInstruction::Transform(Transform(a, b, c, d, e, f)))
100 if [a, b, c, d, e, f] == [n(1), n(0), n(0), n(-1), n(0), n(200)]
101 ));
102 assert!(matches!(
103 iter.next(),
104 Some(TypedInstruction::SetGraphicsState(SetGraphicsState(name)))
105 if name.as_ref() == b"g0"
106 ));
107 assert!(matches!(
108 iter.next(),
109 Some(TypedInstruction::NonStrokeColorDeviceRgb(NonStrokeColorDeviceRgb(r, g, b)))
110 if [r, g, b] == [n(1), n(0), n(0)]
111 ));
112 assert!(iter.next().is_none());
113 }
114
115 #[test]
116 fn basic_ops_2() {
117 let input = b"
11820 20 m
119180 20 l
120180 180 l
12120 180 l
122h
123f
124";
125
126 let mut iter = TypedIter::new(input);
127
128 assert!(matches!(
129 iter.next(),
130 Some(TypedInstruction::MoveTo(MoveTo(x, y))) if [x, y] == [n(20), n(20)]
131 ));
132 assert!(matches!(
133 iter.next(),
134 Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(20)]
135 ));
136 assert!(matches!(
137 iter.next(),
138 Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(180)]
139 ));
140 assert!(matches!(
141 iter.next(),
142 Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(20), n(180)]
143 ));
144 assert!(matches!(
145 iter.next(),
146 Some(TypedInstruction::ClosePath(ClosePath))
147 ));
148 assert!(matches!(
149 iter.next(),
150 Some(TypedInstruction::FillPathNonZero(FillPathNonZero))
151 ));
152 assert!(iter.next().is_none());
153 }
154
155 #[test]
156 fn scn() {
157 let input = b"
1580.0 scn
1591.0 1.0 1.0 /DeviceRgb SCN
160";
161
162 let mut iter = TypedIter::new(input);
163
164 match iter.next() {
165 Some(TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(nums, None))) => {
166 assert_eq!(nums.as_slice(), &[Number::from_f32(0.0)]);
167 }
168 other => panic!("unexpected instruction: {other:?}"),
169 }
170
171 match iter.next() {
172 Some(TypedInstruction::StrokeColorNamed(StrokeColorNamed(nums, Some(name)))) => {
173 assert_eq!(
174 nums.as_slice(),
175 &[
176 Number::from_f32(1.0),
177 Number::from_f32(1.0),
178 Number::from_f32(1.0)
179 ]
180 );
181 assert_eq!(name.as_ref(), b"DeviceRgb");
182 }
183 other => panic!("unexpected instruction: {other:?}"),
184 }
185
186 assert!(iter.next().is_none());
187 }
188
189 #[test]
190 fn dp() {
191 let input = b"/Attribute<</ShowCenterPoint false >> DP";
192
193 let mut iter = TypedIter::new(input);
194
195 match iter.next() {
196 Some(TypedInstruction::MarkedContentPointWithProperties(
197 MarkedContentPointWithProperties(name, object),
198 )) => {
199 assert_eq!(name.as_ref(), b"Attribute");
200 assert_eq!(
201 object,
202 &Object::Dict(Dict::from_bytes(b"<</ShowCenterPoint false >>").unwrap())
203 );
204 }
205 other => panic!("unexpected instruction: {other:?}"),
206 }
207
208 assert!(iter.next().is_none());
209 }
210
211 #[test]
212 fn bdc_with_dict() {
213 let input = b"/Span << /MCID 0 /Alt (Alt)>> BDC EMC";
214
215 let mut iter = TypedIter::new(input);
216
217 match iter.next() {
218 Some(TypedInstruction::BeginMarkedContentWithProperties(
219 BeginMarkedContentWithProperties(name, object),
220 )) => {
221 assert_eq!(name.as_ref(), b"Span");
222 assert_eq!(
223 object,
224 &Object::Dict(Dict::from_bytes(b"<< /MCID 0 /Alt (Alt)>>").unwrap())
225 );
226 }
227 other => panic!("unexpected instruction: {other:?}"),
228 }
229
230 assert!(matches!(
231 iter.next(),
232 Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
233 ));
234 assert!(iter.next().is_none());
235 }
236
237 #[test]
238 fn bdc_with_name() {
239 let input = b"/Span /Name BDC EMC";
240
241 let mut iter = TypedIter::new(input);
242
243 match iter.next() {
244 Some(TypedInstruction::BeginMarkedContentWithProperties(
245 BeginMarkedContentWithProperties(name, object),
246 )) => {
247 assert_eq!(name.as_ref(), b"Span");
248 assert_eq!(object, &Object::Name(Name::new_unescaped(b"Name")));
249 }
250 other => panic!("unexpected instruction: {other:?}"),
251 }
252
253 assert!(matches!(
254 iter.next(),
255 Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
256 ));
257 assert!(iter.next().is_none());
258 }
259}