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