Skip to main content

pdfrum_page/
ops_table.rs

1// The operator table. Included by `ops.rs`; not a module of its own so the
2// macro, the helper impls and the rows read as one file while keeping the
3// 73-row table separately greppable.
4//
5// Columns: spelling => variant(operand type : ring index, …) [guard].
6// Ring indices count back from the newest operand, so `1 2 m` reads x at 1
7// and y at 0. `Point` and `Affine` read several slots starting at the index
8// given. A trailing integer is the `param_count` an operator demands; without
9// one, absent operands read as zero/empty/null.
10
11/// The `SC`/`sc` operand set: at most four numbers, oldest first.
12///
13/// PDFium caps these two at four components while `SCN`/`scn` take everything
14/// in the ring — a six-component `DeviceN` colour set with `sc` silently
15/// loses two values and is then rejected wholesale by the component-count
16/// check in [`crate::color::ColorValue::set_components`].
17#[derive(Debug, Clone, PartialEq, Default)]
18pub struct Components(pub SmallVec<[f32; 4]>);
19
20impl FromOperands for Components {
21    fn extract(ring: &OperandRing, _index: usize) -> Self {
22        Self(ring.numbers(ring.len().min(4)))
23    }
24}
25
26/// The `SCN`/`scn` operand set: every number in the ring, oldest first, plus
27/// the trailing pattern name when the newest operand is one.
28#[derive(Debug, Clone, PartialEq, Default)]
29pub struct PatternComponents {
30    /// The numeric components, in source order.
31    pub values: SmallVec<[f32; 4]>,
32    /// The pattern named by the last operand, when there is one.
33    pub pattern: Option<Name>,
34}
35
36impl FromOperands for PatternComponents {
37    fn extract(ring: &OperandRing, _index: usize) -> Self {
38        // `GetColors` takes all operands; `GetNamedColors` drops the trailing
39        // name. A `scn` with *no* operands at all has no last operand, and
40        // PDFium's `GetObject(0)` then yields null, which is not a name — so
41        // it takes the numeric branch with an empty set.
42        if ring.is_name(0) {
43            Self {
44                values: ring.named_numbers(),
45                pattern: Some(Name::new(ring.string(0))),
46            }
47        } else {
48            Self {
49                values: ring.numbers(ring.len()),
50                pattern: None,
51            }
52        }
53    }
54}
55
56/// A `d` operand pair: the dash array and its phase.
57///
58/// Every array element is read as a float, so a non-numeric element becomes
59/// `0.0`; a non-array first operand leaves the array empty *and* marks the
60/// row invalid, which makes the operator a no-op.
61#[derive(Debug, Clone, PartialEq, Default)]
62pub struct DashPattern {
63    /// The on/off lengths, verbatim — normalization happens in
64    /// [`crate::state::StrokeParams`].
65    pub array: SmallVec<[f32; 4]>,
66    /// The distance into the pattern at which to start.
67    pub phase: f32,
68    /// Whether operand 1 really was an array. PDFium's handler returns
69    /// without touching the state otherwise.
70    pub valid: bool,
71}
72
73impl FromOperands for DashPattern {
74    fn extract(ring: &OperandRing, _index: usize) -> Self {
75        let phase = ring.number(0);
76        match ring.object(1) {
77            Object::Array(a) => Self {
78                array: a.iter().map(|o| o.number().unwrap_or(0.0)).collect(),
79                phase,
80                valid: true,
81            },
82            _ => Self {
83                array: SmallVec::new(),
84                phase,
85                valid: false,
86            },
87        }
88    }
89}
90
91/// A `TJ` operand: the array's elements, with non-strings that are not
92/// numbers dropped.
93#[derive(Debug, Clone, PartialEq, Default)]
94pub struct TextArray {
95    /// The elements in source order.
96    pub items: Box<[TextItem]>,
97    /// Whether the operand was an array at all; `TJ` is a no-op otherwise.
98    pub valid: bool,
99}
100
101impl FromOperands for TextArray {
102    fn extract(ring: &OperandRing, _index: usize) -> Self {
103        match ring.object(0) {
104            Object::Array(a) => {
105                let items = a
106                    .iter()
107                    .filter_map(|o| match o {
108                        Object::Str(s) => Some(TextItem::Show(s.bytes.clone())),
109                        Object::Int(_) | Object::Real(_) => {
110                            o.number().map(TextItem::Adjust)
111                        }
112                        _ => None,
113                    })
114                    .collect();
115                Self { items, valid: true }
116            }
117            _ => Self {
118                items: Box::default(),
119                valid: false,
120            },
121        }
122    }
123}
124
125/// A `BDC` property operand: a name, an inline dictionary, or neither.
126///
127/// PDFium pushes no mark at all when the operand is null or is anything but a
128/// name or a dictionary, so "neither" has to be representable.
129#[derive(Debug, Clone, PartialEq)]
130pub struct MarkProps(pub Option<MarkProperties>);
131
132impl FromOperands for MarkProps {
133    fn extract(ring: &OperandRing, _index: usize) -> Self {
134        Self(match ring.object(0) {
135            Object::Name(n) => Some(MarkProperties::Named(n)),
136            Object::Dict(d) => Some(MarkProperties::Inline(Box::new(d))),
137            _ => None,
138        })
139    }
140}
141
142ops! {
143    // ---- Graphics state (11) ----
144
145    /// `q` — push a copy of the graphics state.
146    b"q" => SaveState();
147    /// `Q` — pop the graphics state; a no-op on an empty stack.
148    b"Q" => RestoreState();
149    /// `cm` — pre-concatenate a matrix onto the CTM.
150    b"cm" => Concat(Affine: 0);
151    /// `w` — set the line width. Negative and zero widths are stored as-is.
152    b"w" => SetLineWidth(f32: 0);
153    /// `J` — set the line cap.
154    b"J" => SetLineCap(LineCap: 0);
155    /// `j` — set the line join.
156    b"j" => SetLineJoin(LineJoin: 0);
157    /// `M` — set the miter limit.
158    b"M" => SetMiterLimit(f32: 0);
159    /// `d` — set the dash pattern and phase.
160    b"d" => SetDash(DashPattern: 0);
161    /// `ri` — set the rendering intent. PDFium discards it outright; only
162    /// the `/RI` `ExtGState` key is stored.
163    b"ri" => SetRenderIntent(Name: 0);
164    /// `i` — set the flatness tolerance. Stored, never used.
165    b"i" => SetFlatness(f32: 0);
166    /// `gs` — apply a named `/ExtGState` dictionary.
167    b"gs" => SetExtGState(Name: 0);
168
169    // ---- Path construction (7) ----
170
171    /// `m` — begin a new subpath at a point.
172    b"m" => MoveTo(Point: 0) 2;
173    /// `l` — append a straight segment.
174    b"l" => LineTo(Point: 0) 2;
175    /// `c` — append a cubic Bézier with both control points given.
176    b"c" => CurveTo(Point: 4, Point: 2, Point: 0);
177    /// `v` — append a cubic Bézier whose first control point is the current
178    /// point.
179    b"v" => CurveToV(Point: 2, Point: 0);
180    /// `y` — append a cubic Bézier whose second control point is its
181    /// endpoint.
182    b"y" => CurveToY(Point: 2, Point: 0);
183    /// `h` — close the current subpath.
184    b"h" => ClosePath();
185    /// `re` — append a complete rectangle as a new subpath: x, y, width,
186    /// height, oldest first.
187    b"re" => Rectangle(f32: 3, f32: 2, f32: 1, f32: 0);
188
189    // ---- Path painting (10) ----
190
191    /// `S` — stroke the path.
192    b"S" => Stroke();
193    /// `s` — close and stroke.
194    b"s" => CloseStroke();
195    /// `f` — fill with the nonzero winding rule.
196    b"f" => Fill();
197    /// `F` — the obsolete spelling of `f`, handled identically.
198    b"F" => FillObsolete();
199    /// `f*` — fill with the even-odd rule.
200    b"f*" => FillEvenOdd();
201    /// `B` — fill then stroke, nonzero winding.
202    b"B" => FillStroke();
203    /// `B*` — fill then stroke, even-odd.
204    b"B*" => FillStrokeEvenOdd();
205    /// `b` — close, fill and stroke, nonzero winding.
206    b"b" => CloseFillStroke();
207    /// `b*` — close, fill and stroke, even-odd. Unlike `b`, this appends the
208    /// closing segment unconditionally.
209    b"b*" => CloseFillStrokeEvenOdd();
210    /// `n` — end the path without painting; only a pending clip survives.
211    b"n" => EndPath();
212
213    // ---- Clipping (2) ----
214
215    /// `W` — intersect the clip with the current path, nonzero winding.
216    b"W" => Clip();
217    /// `W*` — intersect the clip with the current path, even-odd.
218    b"W*" => ClipEvenOdd();
219
220    // ---- Text objects and positioning (9) ----
221
222    /// `BT` — begin a text object, resetting both text matrices.
223    b"BT" => BeginText();
224    /// `ET` — end a text object, flushing any pending text clip.
225    b"ET" => EndText();
226    /// `Td` — move to the start of the next line, offset from the current
227    /// line start.
228    b"Td" => TextMove(f32: 1, f32: 0);
229    /// `TD` — `Td` plus setting the leading to the negated y offset.
230    b"TD" => TextMoveSetLeading(f32: 1, f32: 0);
231    /// `Tm` — set the text matrix and the line matrix.
232    b"Tm" => SetTextMatrix(Affine: 0);
233    /// `T*` — move to the start of the next line.
234    b"T*" => TextNextLine();
235    /// `TL` — set the leading.
236    b"TL" => SetLeading(f32: 0);
237    /// `Ts` — set the text rise.
238    b"Ts" => SetTextRise(f32: 0);
239    /// `Tz` — set the horizontal scale, stored as the percentage over 100.
240    b"Tz" => SetHorzScale(f32: 0) 1;
241
242    // ---- Text state (4) ----
243
244    /// `Tc` — set the character spacing.
245    b"Tc" => SetCharSpace(f32: 0);
246    /// `Tw` — set the word spacing.
247    b"Tw" => SetWordSpace(f32: 0);
248    /// `Tf` — set the font and size. The size is always taken; the font only
249    /// when the name resolves.
250    b"Tf" => SetFont(Name: 1, f32: 0);
251    /// `Tr` — set the text rendering mode. Values outside 0..=7 leave the
252    /// mode unchanged, so the operand arrives raw.
253    b"Tr" => SetTextRenderMode(i64: 0);
254
255    // ---- Text showing (4) ----
256
257    /// `Tj` — show a string.
258    b"Tj" => ShowText(PdfString: 0);
259    /// `'` — move to the next line and show a string.
260    b"'" => NextLineShowText(PdfString: 0);
261    /// `"` — set word and character spacing, then `'`. The string is the
262    /// newest operand.
263    b"\"" => SetSpacingShowText(f32: 2, f32: 1, PdfString: 0);
264    /// `TJ` — show strings with individual position adjustments.
265    b"TJ" => ShowTextAdjusted(TextArray: 0);
266
267    // ---- Type 3 glyph metrics (2) ----
268
269    /// `d0` — declare a coloured Type 3 glyph's advance.
270    b"d0" => Type3Width(f32: 1, f32: 0);
271    /// `d1` — declare an uncoloured Type 3 glyph's advance and bounding box,
272    /// oldest first.
273    b"d1" => Type3WidthBBox(f32: 5, f32: 4, f32: 3, f32: 2, f32: 1, f32: 0);
274
275    // ---- Colour (12) ----
276
277    /// `CS` — set the stroking colorspace, resetting the colour to its
278    /// default.
279    b"CS" => SetStrokeColorSpace(Name: 0);
280    /// `cs` — set the non-stroking colorspace.
281    b"cs" => SetFillColorSpace(Name: 0);
282    /// `SC` — set stroking colour components, at most four.
283    b"SC" => SetStrokeColor(Components: 0);
284    /// `sc` — set non-stroking colour components, at most four.
285    b"sc" => SetFillColor(Components: 0);
286    /// `SCN` — set stroking colour components, optionally naming a pattern.
287    b"SCN" => SetStrokeColorN(PatternComponents: 0);
288    /// `scn` — set non-stroking colour components, optionally naming a
289    /// pattern.
290    b"scn" => SetFillColorN(PatternComponents: 0);
291    /// `G` — set the stroking colour to a `DeviceGray` level.
292    b"G" => SetStrokeGray(f32: 0);
293    /// `g` — set the non-stroking colour to a `DeviceGray` level.
294    b"g" => SetFillGray(f32: 0);
295    /// `RG` — set the stroking colour to a `DeviceRGB` triple.
296    b"RG" => SetStrokeRgb(f32: 2, f32: 1, f32: 0) 3;
297    /// `rg` — set the non-stroking colour to a `DeviceRGB` triple.
298    b"rg" => SetFillRgb(f32: 2, f32: 1, f32: 0) 3;
299    /// `K` — set the stroking colour to a `DeviceCMYK` quadruple.
300    b"K" => SetStrokeCmyk(f32: 3, f32: 2, f32: 1, f32: 0) 4;
301    /// `k` — set the non-stroking colour to a `DeviceCMYK` quadruple.
302    b"k" => SetFillCmyk(f32: 3, f32: 2, f32: 1, f32: 0) 4;
303
304    // ---- XObjects and shading (2) ----
305
306    /// `Do` — paint a named form or image `XObject`.
307    b"Do" => DoXObject(Name: 0);
308    /// `sh` — paint a named shading across the clip region.
309    b"sh" => ShadeFill(Name: 0);
310
311    // ---- Inline images (3) ----
312
313    /// `BI` — begin an inline image. Reaching dispatch means the tokenizer
314    /// abandoned the image, so the operator itself does nothing.
315    b"BI" => BeginInlineImage();
316    /// `ID` — a stray `ID` outside a `BI`. A no-op.
317    b"ID" => InlineImageData();
318    /// `EI` — a stray `EI` outside a `BI`. A no-op.
319    b"EI" => EndInlineImage();
320
321    // ---- Marked content (5) ----
322
323    /// `BMC` — begin a marked-content sequence with no properties.
324    b"BMC" => BeginMarkedContent(Name: 0);
325    /// `BDC` — begin a marked-content sequence with a property list.
326    b"BDC" => BeginMarkedContentDict(Name: 1, MarkProps: 0);
327    /// `EMC` — end a marked-content sequence; never pops past the sentinel.
328    b"EMC" => EndMarkedContent();
329    /// `MP` — a marked-content point with no properties. A no-op.
330    b"MP" => MarkPoint(Name: 0);
331    /// `DP` — a marked-content point with a property list. A no-op.
332    b"DP" => MarkPointDict(Name: 1, MarkProps: 0);
333
334    // ---- Compatibility (2) ----
335
336    /// `BX` — begin a compatibility section. PDFium never registers this, so
337    /// it falls through as an unknown keyword and is ignored; we name it so
338    /// the enum is complete.
339    b"BX" => BeginCompat();
340    /// `EX` — end a compatibility section. Ignored, like `BX`.
341    b"EX" => EndCompat();
342}