1use 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
17fn 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}