otf_pdf_content/device.rs
1//! What the interpreter reports: the [`Device`] trait and what it receives,
2//! and the [`Font`] trait the interpreter needs to lay out text.
3
4use std::fmt;
5use std::sync::Arc;
6
7use otf_pdf_doc::Store;
8use otf_pdf_syntax::{Dict, Name, ObjId, Object};
9
10use crate::matrix::Matrix;
11
12/// A loaded font, as the interpreter needs it: how a string splits into
13/// character codes, how far each code advances, and (for extraction) what
14/// text it stands for. Implemented by `otf-pdf-font`.
15pub trait Font: Send + Sync + fmt::Debug {
16 /// Splits a shown string into `(code, byte length)` pairs.
17 fn codes(&self, bytes: &[u8]) -> Vec<(u32, usize)>;
18 /// Horizontal advance of `code`, in text space units per unit of font
19 /// size (glyph width / 1000 for most fonts).
20 fn width(&self, code: u32) -> f64;
21 /// Whether the font is written vertically (an `-V` CMap).
22 fn is_vertical(&self) -> bool {
23 false
24 }
25 /// Vertical advance of `code` in vertical writing (negative is down).
26 fn vertical_advance(&self, _code: u32) -> f64 {
27 -1.0
28 }
29 /// The Unicode text for `code`, if known.
30 fn unicode(&self, code: u32) -> Option<String>;
31 /// The font's name (`/BaseFont`), for reporting.
32 fn name(&self) -> &str;
33}
34
35/// Loads fonts from font dictionaries; implementations cache them.
36pub trait FontLoader {
37 /// The font for `font` (a reference to, or a, font dictionary).
38 fn load(&self, store: &Store, font: &Object) -> Option<Arc<dyn Font>>;
39}
40
41/// The text state (§9.3).
42#[derive(Debug, Clone)]
43pub struct TextState {
44 /// The current font.
45 pub font: Option<Arc<dyn Font>>,
46 /// Font size (`Tf`).
47 pub size: f64,
48 /// Character spacing (`Tc`).
49 pub char_spacing: f64,
50 /// Word spacing (`Tw`).
51 pub word_spacing: f64,
52 /// Horizontal scaling as a fraction (`Tz` / 100).
53 pub horizontal_scaling: f64,
54 /// Leading (`TL`).
55 pub leading: f64,
56 /// Rise (`Ts`).
57 pub rise: f64,
58 /// Rendering mode (`Tr`); 3 is invisible (OCR text layers).
59 pub render_mode: i64,
60}
61
62impl Default for TextState {
63 fn default() -> Self {
64 Self {
65 font: None,
66 size: 0.0,
67 char_spacing: 0.0,
68 word_spacing: 0.0,
69 horizontal_scaling: 1.0,
70 leading: 0.0,
71 rise: 0.0,
72 render_mode: 0,
73 }
74 }
75}
76
77/// The parts of the graphics state the interpreter tracks.
78#[derive(Debug, Clone)]
79pub struct GraphicsState {
80 /// Current transformation matrix: user space to page space.
81 pub ctm: Matrix,
82 /// Line width.
83 pub line_width: f64,
84 /// Text state.
85 pub text: TextState,
86 /// The clip, approximated by its bounding box in page space
87 /// (`[x0, y0, x1, y1]`); `None` is unclipped. Form and annotation
88 /// bounding boxes and `W`/`W*` paths narrow it.
89 pub clip: Option<[f64; 4]>,
90}
91
92impl GraphicsState {
93 /// Narrows the clip to the page-space bounding box of `points`
94 /// (user space).
95 pub fn clip_to(&mut self, points: impl IntoIterator<Item = (f64, f64)>) {
96 let mut b = [f64::MAX, f64::MAX, f64::MIN, f64::MIN];
97 for (x, y) in points {
98 let (px, py) = self.ctm.apply(x, y);
99 b = [b[0].min(px), b[1].min(py), b[2].max(px), b[3].max(py)];
100 }
101 if b[0] > b[2] {
102 return;
103 }
104 self.clip = Some(match self.clip {
105 Some(c) => [
106 c[0].max(b[0]),
107 c[1].max(b[1]),
108 c[2].min(b[2]),
109 c[3].min(b[3]),
110 ],
111 None => b,
112 });
113 }
114
115 /// Whether a page-space point is inside the clip (with a tolerance of
116 /// `slack` points).
117 pub fn in_clip(&self, (x, y): (f64, f64), slack: f64) -> bool {
118 self.clip.is_none_or(|c| {
119 x >= c[0] - slack && x <= c[2] + slack && y >= c[1] - slack && y <= c[3] + slack
120 })
121 }
122}
123
124impl Default for GraphicsState {
125 fn default() -> Self {
126 Self {
127 ctm: Matrix::IDENTITY,
128 line_width: 1.0,
129 text: TextState::default(),
130 clip: None,
131 }
132 }
133}
134
135/// One shown glyph.
136#[derive(Debug, Clone)]
137pub struct Glyph {
138 /// The character code.
139 pub code: u32,
140 /// Its Unicode text, if the font knows it.
141 pub unicode: Option<String>,
142 /// The font.
143 pub font: Arc<dyn Font>,
144 /// Text rendering matrix: glyph space (scaled by the font size) to page
145 /// space.
146 pub trm: Matrix,
147 /// Where the glyph starts on the baseline, in page space.
148 pub origin: (f64, f64),
149 /// Where the next glyph would start, in page space.
150 pub end: (f64, f64),
151 /// The font size in page space (the height of one em).
152 pub size: f64,
153 /// Text rendering mode.
154 pub render_mode: i64,
155}
156
157/// A path segment, in user space.
158#[derive(Debug, Clone, Copy, PartialEq)]
159pub enum PathSegment {
160 /// Start a subpath.
161 MoveTo(f64, f64),
162 /// Straight line.
163 LineTo(f64, f64),
164 /// Cubic Bézier curve: two control points and the end point.
165 CurveTo(f64, f64, f64, f64, f64, f64),
166 /// Close the subpath.
167 Close,
168}
169
170/// How a path's inside is decided.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum FillRule {
173 /// Nonzero winding.
174 NonZero,
175 /// Even-odd.
176 EvenOdd,
177}
178
179/// An image to draw: an XObject or an inline image.
180#[derive(Debug, Clone)]
181pub struct Image {
182 /// The image XObject's id, or `None` for an inline image.
183 pub id: Option<ObjId>,
184 /// The image dictionary.
185 pub dict: Dict,
186 /// The inline image's (still encoded) data; empty for an XObject.
187 pub inline_data: Vec<u8>,
188}
189
190/// Receives what a content stream draws. Every method has an empty default,
191/// so a device implements only what it needs: text extraction takes glyphs
192/// and marked content; a renderer takes everything.
193#[allow(unused_variables)]
194pub trait Device {
195 /// Fills `path` (user space) with the current fill colour.
196 fn fill_path(&mut self, path: &[PathSegment], rule: FillRule, state: &GraphicsState) {}
197 /// Strokes `path` (user space).
198 fn stroke_path(&mut self, path: &[PathSegment], state: &GraphicsState) {}
199 /// Intersects the clip with `path`.
200 fn clip(&mut self, path: &[PathSegment], rule: FillRule, state: &GraphicsState) {}
201 /// Draws one glyph.
202 fn draw_glyph(&mut self, glyph: &Glyph, state: &GraphicsState) {}
203 /// Draws an image into the unit square of `state.ctm`.
204 fn draw_image(&mut self, image: &Image, state: &GraphicsState) {}
205 /// Starts a marked-content sequence (`BMC`/`BDC`).
206 fn begin_marked_content(&mut self, tag: &Name, properties: Option<&Dict>) {}
207 /// Ends the innermost marked-content sequence (`EMC`).
208 fn end_marked_content(&mut self) {}
209 /// Starts a Form XObject (a group when it has `/Group`).
210 fn begin_group(&mut self, form: Option<ObjId>, dict: &Dict, state: &GraphicsState) {}
211 /// Ends the Form XObject started last.
212 fn end_group(&mut self) {}
213}