1use crate::StrokeProps;
2use crate::color::{AlphaColor, ColorComponents, ColorSpace};
3use crate::context::Context;
4use crate::convert::{convert_line_cap, convert_line_join};
5use crate::font::{Font, UNITS_PER_EM};
6use crate::function::Function;
7use crate::interpret::text::TextRenderingMode;
8use crate::pattern::Pattern;
9use crate::soft_mask::SoftMask;
10use crate::types::BlendMode;
11use crate::util::OptionLog;
12use kurbo::{Affine, BezPath, Vec2};
13use log::warn;
14use pdf_syntax::content::ops::{LineCap, LineJoin};
15use pdf_syntax::object::dict::keys::{FONT, SMASK, TR, TR2};
16use pdf_syntax::object::{Array, Dict, Name, Number, Object};
17use pdf_syntax::page::Resources;
18use smallvec::smallvec;
19use std::ops::Deref;
20
21#[derive(Clone, Debug)]
23pub enum ActiveTransferFunction {
24 Single(Function),
26 Four([Function; 4]),
28}
29
30impl ActiveTransferFunction {
31 pub fn apply(&self, color: &AlphaColor) -> AlphaColor {
34 let mut rgba = color.components();
35
36 match self {
37 Self::Single(f) => {
38 for c in &mut rgba[..3] {
39 if let Some(out) = f.eval(smallvec![*c]) {
40 *c = out[0];
41 }
42 }
43 }
44 Self::Four(functions) => {
45 for (i, f) in functions[..3].iter().enumerate() {
46 if let Some(out) = f.eval(smallvec![rgba[i]]) {
47 rgba[i] = out[0];
48 }
49 }
50 }
51 }
52
53 AlphaColor::new(rgba)
54 }
55}
56
57#[derive(Clone, Debug)]
58pub(crate) enum ClipType {
59 Dummy,
60 Real,
61}
62
63#[derive(Clone, Debug)]
64pub(crate) struct State<'a> {
65 pub(crate) graphics_state: GraphicsState<'a>,
68 pub(crate) text_state: TextState<'a>,
69 pub(crate) ctm: Affine,
70 pub(crate) clips: Vec<ClipType>,
73}
74
75impl Default for State<'_> {
76 fn default() -> Self {
77 State {
78 ctm: Affine::IDENTITY,
79 clips: vec![],
80 text_state: TextState::default(),
81 graphics_state: GraphicsState::default(),
82 }
83 }
84}
85
86impl<'a> State<'a> {
87 pub(crate) fn new(initial_transform: Affine) -> Self {
88 Self {
89 ctm: initial_transform,
90 ..Self::default()
91 }
92 }
93}
94
95#[derive(Clone, Debug)]
96pub(crate) enum TextStateFont<'a> {
97 Font(Font<'a>),
100 Fallback(Font<'a>),
102}
103
104impl<'a> Deref for TextStateFont<'a> {
105 type Target = Font<'a>;
106
107 fn deref(&self) -> &Self::Target {
108 match self {
109 TextStateFont::Font(f) => f,
110 TextStateFont::Fallback(f) => f,
111 }
112 }
113}
114
115#[derive(Clone, Debug)]
116pub(crate) struct TextState<'a> {
117 pub(crate) char_space: f32,
118 pub(crate) word_space: f32,
119 pub(crate) horizontal_scaling: f32,
121 pub(crate) leading: f32,
122 pub(crate) font: Option<TextStateFont<'a>>,
123 pub(crate) font_size: f32,
124 pub(crate) rise: f32,
125 pub(crate) render_mode: TextRenderingMode,
126
127 pub(crate) text_matrix: Affine,
128 pub(crate) text_line_matrix: Affine,
129
130 pub(crate) clip_paths: BezPath,
133}
134
135impl<'a> TextState<'a> {
136 fn temp_transform(&self) -> Affine {
137 Affine::new([
138 self.font_size as f64 * self.horizontal_scaling() as f64,
139 0.0,
140 0.0,
141 self.font_size as f64,
142 0.0,
143 self.rise as f64,
144 ])
145 }
146
147 fn horizontal_scaling(&self) -> f32 {
148 self.horizontal_scaling / 100.0
149 }
150
151 fn font_horizontal(&self) -> bool {
152 self.font
153 .as_ref()
154 .map(|f| f.is_horizontal())
155 .unwrap_or(false)
156 }
157
158 pub(crate) fn apply_adjustment(&mut self, adjustment: f32) {
159 let horizontal = self.font_horizontal();
160
161 let horizontal_scaling = if horizontal {
162 self.horizontal_scaling()
163 } else {
164 1.0
165 };
166
167 let scaled_adjustment = -adjustment / UNITS_PER_EM * self.font_size * horizontal_scaling;
168 let (tx, ty) = if horizontal {
169 (scaled_adjustment, 0.0)
170 } else {
171 (0.0, scaled_adjustment)
172 };
173
174 self.text_matrix *= Affine::new([1.0, 0.0, 0.0, 1.0, tx as f64, ty as f64]);
175 }
176
177 pub(crate) fn apply_code_advance(&mut self, char_code: u32, code_len: usize) {
178 let glyph_advance = self
179 .font
180 .as_ref()
181 .map(|f| f.code_advance(char_code))
182 .unwrap_or(Vec2::ZERO);
183 let horizontal = self.font_horizontal();
184
185 let word_space = if char_code == 32 && code_len == 1 {
186 self.word_space
187 } else {
188 0.0
189 };
190
191 let base_advance =
192 |advance: f32| advance / UNITS_PER_EM * self.font_size + self.char_space + word_space;
193
194 let tx = if horizontal {
195 base_advance(glyph_advance.x as f32) * self.horizontal_scaling()
196 } else {
197 0.0
198 };
199
200 let ty = if !horizontal {
201 base_advance(glyph_advance.y as f32)
202 } else {
203 0.0
204 };
205
206 self.text_matrix *= Affine::new([1.0, 0.0, 0.0, 1.0, tx as f64, ty as f64]);
207 }
208
209 pub(crate) fn full_transform(&self) -> Affine {
210 self.text_matrix * self.temp_transform()
211 }
212}
213
214impl Default for TextState<'_> {
215 fn default() -> Self {
216 Self {
217 char_space: 0.0,
218 word_space: 0.0,
219 horizontal_scaling: 100.0,
220 leading: 0.0,
221 font: None,
222 font_size: 1.0,
224 render_mode: TextRenderingMode::default(),
225 text_matrix: Affine::IDENTITY,
226 text_line_matrix: Affine::IDENTITY,
227 rise: 0.0,
228 clip_paths: BezPath::default(),
229 }
230 }
231}
232
233#[derive(Clone, Debug)]
234pub(crate) struct GraphicsState<'a> {
235 pub(crate) stroke_props: StrokeProps,
237
238 pub(crate) stroke_color: ColorComponents,
240 pub(crate) stroke_pattern: Option<Pattern<'a>>,
241 pub(crate) stroke_cs: ColorSpace,
242 pub(crate) stroke_alpha: f32,
243
244 pub(crate) non_stroke_color: ColorComponents,
246 pub(crate) non_stroke_pattern: Option<Pattern<'a>>,
247 pub(crate) none_stroke_cs: ColorSpace,
248 pub(crate) non_stroke_alpha: f32,
249
250 pub(crate) soft_mask: Option<SoftMask<'a>>,
251 pub(crate) transfer_function: Option<ActiveTransferFunction>,
252 pub(crate) blend_mode: BlendMode,
253}
254
255impl Default for GraphicsState<'_> {
256 fn default() -> Self {
257 GraphicsState {
258 stroke_props: StrokeProps::default(),
259 non_stroke_alpha: 1.0,
260 stroke_cs: ColorSpace::device_gray(),
261 stroke_color: smallvec![0.0,],
262 none_stroke_cs: ColorSpace::device_gray(),
263 non_stroke_color: smallvec![0.0],
264 stroke_alpha: 1.0,
265 stroke_pattern: None,
266 non_stroke_pattern: None,
267 soft_mask: None,
268 transfer_function: None,
269 blend_mode: BlendMode::default(),
270 }
271 }
272}
273
274pub(crate) fn handle_gs<'a>(
275 dict: &Dict<'a>,
276 context: &mut Context<'a>,
277 parent_resources: &Resources<'a>,
278) {
279 for key in dict.keys() {
280 handle_gs_single(dict, key.clone(), context, parent_resources).warn_none(&format!(
281 "invalid value in graphics state for {}",
282 key.as_str()
283 ));
284 }
285}
286
287pub(crate) fn handle_gs_single<'a>(
288 dict: &Dict<'a>,
289 key: Name,
290 context: &mut Context<'a>,
291 parent_resources: &Resources<'a>,
292) -> Option<()> {
293 match key.as_str() {
295 "LW" => context.get_mut().graphics_state.stroke_props.line_width = dict.get::<f32>(key)?,
296 "LC" => {
297 context.get_mut().graphics_state.stroke_props.line_cap =
298 convert_line_cap(LineCap(dict.get::<Number>(key)?));
299 }
300 "LJ" => {
301 context.get_mut().graphics_state.stroke_props.line_join =
302 convert_line_join(LineJoin(dict.get::<Number>(key)?));
303 }
304 "ML" => context.get_mut().graphics_state.stroke_props.miter_limit = dict.get::<f32>(key)?,
305 "CA" => context.get_mut().graphics_state.stroke_alpha = dict.get::<f32>(key)?,
306 "ca" => context.get_mut().graphics_state.non_stroke_alpha = dict.get::<f32>(key)?,
307 "TR" | "TR2" => {
308 let function = match dict
309 .get::<Object<'_>>(TR2)
310 .or_else(|| dict.get::<Object<'_>>(TR))?
311 {
312 Object::Array(array) => {
313 let mut iter = array.iter::<Object<'_>>();
314 let functions = [
315 Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
316 Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
317 Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
318 Function::new_with_sink(&iter.next()?, &context.settings.warning_sink)?,
319 ];
320
321 Some(ActiveTransferFunction::Four(functions))
322 }
323 Object::Name(_) => None,
325 o => Some(ActiveTransferFunction::Single(Function::new_with_sink(
326 &o,
327 &context.settings.warning_sink,
328 )?)),
329 };
330
331 context.get_mut().graphics_state.transfer_function = function;
332 }
333 "SMask" => {
334 if let Some(name) = dict.get::<Name>(SMASK) {
335 if name.deref() == b"None" {
336 context.get_mut().graphics_state.soft_mask = None;
337 }
338 } else {
339 context.get_mut().graphics_state.soft_mask = dict
340 .get::<Dict<'_>>(SMASK)
341 .and_then(|d| SoftMask::new(&d, context, parent_resources.clone()));
342 }
343 }
344 "BM" => {
345 if let Some(name) = dict.get::<Name>(key.clone()) {
346 if let Some(bm) = convert_blend_mode(name.as_str()) {
347 context.get_mut().graphics_state.blend_mode = bm;
348
349 return Some(());
350 }
351 } else if let Some(arr) = dict.get::<Array<'_>>(key) {
352 for name in arr.iter::<Name>() {
353 if let Some(bm) = convert_blend_mode(name.as_str()) {
354 context.get_mut().graphics_state.blend_mode = bm;
355
356 return Some(());
357 }
358 }
359 }
360
361 warn!("unknown blend mode, defaulting to Normal");
362 context.get_mut().graphics_state.blend_mode = BlendMode::Normal;
363 }
364 "Font" => {
365 let arr = dict.get::<Array<'_>>(FONT)?;
366 let mut iter = arr.iter::<Object<'_>>();
367 let font_dict = iter.next()?.into_dict()?;
368 let size = iter.next()?.into_number()?.as_f32();
369
370 let font = context.resolve_font(&font_dict);
371 context.get_mut().text_state.font_size = size;
372 context.get_mut().text_state.font = font;
373 }
374 "D" => {
375 let arr = dict.get::<Array<'_>>(key)?;
376 let mut iter = arr.iter::<Object<'_>>();
377 let dash_array = iter.next()?.into_array()?;
378 let dash_phase = iter.next()?.into_number()?.as_f32();
379
380 context.get_mut().graphics_state.stroke_props.dash_offset = dash_phase;
381 context.get_mut().graphics_state.stroke_props.dash_array = dash_array
382 .iter::<f32>()
383 .map(|n| if n == 0.0 { 0.01 } else { n })
385 .collect();
386 }
387 "Type" => {}
388 _ => {}
389 }
390
391 Some(())
392}
393
394fn convert_blend_mode(name: &str) -> Option<BlendMode> {
395 let bm = match name {
396 "Normal" => BlendMode::Normal,
397 "Multiply" => BlendMode::Multiply,
398 "Screen" => BlendMode::Screen,
399 "Overlay" => BlendMode::Overlay,
400 "Darken" => BlendMode::Darken,
401 "Lighten" => BlendMode::Lighten,
402 "ColorDodge" => BlendMode::ColorDodge,
403 "ColorBurn" => BlendMode::ColorBurn,
404 "HardLight" => BlendMode::HardLight,
405 "SoftLight" => BlendMode::SoftLight,
406 "Difference" => BlendMode::Difference,
407 "Exclusion" => BlendMode::Exclusion,
408 "Hue" => BlendMode::Hue,
409 "Saturation" => BlendMode::Saturation,
410 "Color" => BlendMode::Color,
411 "Luminosity" => BlendMode::Luminosity,
412 _ => return None,
413 };
414
415 Some(bm)
416}