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}