printpdf/extgstate.rs
1#![allow(unused_variables)]
2
3//! Extended graphics state, for advanced graphical operation (overprint, black point control, etc.)
4//!
5//! Some of the operations can be done on the layer directly, but for advanced graphics,
6//! you need to set the graphics state. A PDF has an internal default graphics state,
7//! which can be reset to by setting `ExtendedGraphicsState::default()` as the active gs
8//! dictionary. Setting a new graphics state overwrites the old one, there is no "undo".
9//!
10//! In order to use a graphics state, it must be added to the Pages resource dicitionary.
11//! This is done by the `layer.set_graphics_state()` function, which returns a reference with the name of
12//! the newly added dictionary. From inside a stream, the graphics state parameter is invoked
13//! with the "gs" command using the name of the graphics state as a operator.
14//! This is done using the `layer.use_graphics_state()`.
15//!
16//! A full graphics state change is done like this:
17//!
18//! ```rust,ignore
19//! let mut new_state = ExtendedGraphicsState::default();
20//! new_state.overprint_stroke = true;
21//!
22//! // it is best to put the next lines in a seperate function
23//! // A PdfLayerReferences contains the indices of the page and the layer
24//! // as well as a `std::sync::Weak` reference to the document.
25//! // This is why you need the braces, otherwise, you'll trigger a deadlock
26//! {
27//! // supposing mylayer is a PdfLayerReference
28//! let doc = mylayer.document.upgrade().unwrap();
29//! let mut doc = doc.lock().unwrap();
30//! let mut page = doc.pages.get_mut(self.page.0).unwrap();
31//!
32//! // see the documentation for add_graphics_state
33//! page.add_graphics_state(new_state);
34//! }
35//! ```
36
37use crate::indices::FontIndex;
38use lopdf;
39use lopdf::content::Operation;
40use lopdf::Object::*;
41use std::collections::HashMap;
42use std::collections::HashSet;
43use std::string::String;
44
45// identifiers for tracking the changed fields
46pub(crate) const LINE_WIDTH: &str = "line_width";
47pub(crate) const LINE_CAP: &str = "line_cap";
48pub(crate) const LINE_JOIN: &str = "line_join";
49pub(crate) const MITER_LIMIT: &str = "miter_limit";
50pub(crate) const LINE_DASH_PATTERN: &str = "line_dash_pattern";
51pub(crate) const RENDERING_INTENT: &str = "rendering_intent";
52pub(crate) const OVERPRINT_STROKE: &str = "overprint_stroke";
53pub(crate) const OVERPRINT_FILL: &str = "overprint_fill";
54pub(crate) const OVERPRINT_MODE: &str = "overprint_mode";
55pub(crate) const FONT: &str = "font";
56pub(crate) const BLACK_GENERATION: &str = "black_generation";
57pub(crate) const BLACK_GENERATION_EXTRA: &str = "black_generation_extra";
58pub(crate) const UNDERCOLOR_REMOVAL: &str = "under_color_removal";
59pub(crate) const UNDERCOLOR_REMOVAL_EXTRA: &str = "undercolor_removal_extra";
60pub(crate) const TRANSFER_FUNCTION: &str = "transfer_function";
61pub(crate) const TRANSFER_FUNCTION_EXTRA: &str = "transfer_function_extra";
62pub(crate) const HALFTONE_DICTIONARY: &str = "halftone_dictionary";
63pub(crate) const FLATNESS_TOLERANCE: &str = "flatness_tolerance";
64pub(crate) const SMOOTHNESS_TOLERANCE: &str = "smoothness_tolerance";
65pub(crate) const STROKE_ADJUSTMENT: &str = "stroke_adjustment";
66pub(crate) const BLEND_MODE: &str = "blend_mode";
67pub(crate) const SOFT_MASK: &str = "soft_mask";
68pub(crate) const CURRENT_STROKE_ALPHA: &str = "current_stroke_alpha";
69pub(crate) const CURRENT_FILL_ALPHA: &str = "current_fill_alpha";
70pub(crate) const ALPHA_IS_SHAPE: &str = "alpha_is_shape";
71pub(crate) const TEXT_KNOCKOUT: &str = "text_knockout";
72
73/// List of many `ExtendedGraphicsState`
74#[derive(Debug, Clone, Default)]
75pub struct ExtendedGraphicsStateList {
76 /// Current indent level + current graphics state
77 pub(crate) latest_graphics_state: (usize, ExtendedGraphicsState),
78 /// All graphics states needed for this layer, collected together with a name for each one
79 /// The name should be: "GS[index of the graphics state]", so `/GS0` for the first graphics state.
80 pub(crate) all_graphics_states: HashMap<String, (usize, ExtendedGraphicsState)>,
81}
82
83impl ExtendedGraphicsStateList {
84 /// Creates a new ExtendedGraphicsStateList
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 /// Adds a graphics state
90 pub fn add_graphics_state(
91 &mut self,
92 added_state: ExtendedGraphicsState,
93 ) -> ExtendedGraphicsStateRef {
94 let gs_ref = ExtendedGraphicsStateRef::new(self.all_graphics_states.len());
95 self.all_graphics_states.insert(
96 gs_ref.gs_name.clone(),
97 (self.latest_graphics_state.0, added_state.clone()),
98 );
99 self.latest_graphics_state = (self.latest_graphics_state.0, added_state);
100 gs_ref
101 }
102}
103
104impl From<ExtendedGraphicsStateList> for lopdf::Dictionary {
105 fn from(val: ExtendedGraphicsStateList) -> Self {
106 let mut ext_g_state_resources = lopdf::Dictionary::new();
107
108 for (name, (_, graphics_state)) in val.all_graphics_states {
109 let gs: lopdf::Object = graphics_state.into();
110 ext_g_state_resources.set(name.to_string(), gs);
111 }
112
113 ext_g_state_resources
114 }
115}
116
117/// `ExtGState` dictionary
118#[derive(Debug, PartialEq, Clone)]
119pub struct ExtendedGraphicsState {
120 /* /Type ExtGState */
121 /// NOTE: We need to track which fields have changed in relation to the default() method.
122 /// This is because we want to optimize out the fields that haven't changed in relation
123 /// to the last graphics state. Please use only the constants defined in this module for
124 /// declaring the changed fields. The way to go about this is to first convert the ExtGState
125 /// into a vector of operations and then remove all operations that are unnecessary
126 /// before writing the document.
127 ///
128 /// If you are unsure about this, please use the `.with_[field name]` method. These methods
129 /// will set the `changed_fields` to the correct values. If you want to take care of this field
130 /// manually: Every time you change a field on the ExtGState dicitionary, you have to add the
131 /// string identifier of that field into the `changed_fields` vector.
132 pub(crate) changed_fields: HashSet<&'static str>,
133
134 /* LW float */
135 /// __(Optional; PDF 1.3)__ The current line width
136 pub(crate) line_width: f32,
137
138 /* LC integer */
139 /// __(Optional; PDF 1.3)__ The current line cap style
140 pub(crate) line_cap: LineCapStyle,
141
142 /* LJ integer */
143 /// __(Optional; PDF 1.3)__ The current line join style
144 pub(crate) line_join: LineJoinStyle,
145
146 /* ML float */
147 /// __(Optional; PDF 1.3)__ The miter limit (see “Miter Limit” on page 217).
148 pub(crate) miter_limit: f32,
149
150 /* D array */
151 /// __(Optional; PDF 1.3)__ The line dash pattern, expressed as an array of the form
152 /// [ dashArray dashPhase ] , where dashArray is itself an array and dashPhase is an
153 /// integer (see “Line Dash Pattern” on page 217).
154 pub(crate) line_dash_pattern: Option<LineDashPattern>,
155
156 /* RI name (or ri inside a stream)*/
157 /// __(Optional; PDF 1.3)__ The name of the rendering intent (see “Rendering
158 /// Intents” on page 260).
159 pub(crate) rendering_intent: RenderingIntent,
160
161 /* OP boolean */
162 /// __(Optional)__ A flag specifying whether to apply overprint (see Section 4.5.6,
163 /// “Overprint Control”). In PDF 1.2 and earlier, there is a single overprint
164 /// parameter that applies to all painting operations. Beginning with PDF 1.3,
165 /// there are two separate overprint parameters: one for stroking and one for all
166 /// other painting operations. Specifying an OP entry sets both parameters un-
167 /// less there is also an op entry in the same graphics state parameter dictionary,
168 /// in which case the OP entry sets only the overprint parameter for stroking.
169 pub(crate) overprint_stroke: bool,
170
171 /* op boolean */
172 /// __(Optional; PDF 1.3)__ A flag specifying whether to apply overprint (see Section
173 /// 4.5.6, “Overprint Control”) for painting operations other than stroking. If
174 /// this entry is absent, the OP entry, if any, sets this parameter.
175 pub(crate) overprint_fill: bool,
176
177 /* OPM integer */
178 /// __(Optional; PDF 1.3)__ The overprint mode (see Section 4.5.6, “Overprint Control”)
179 /// Initial value: `EraseUnderlying`
180 pub(crate) overprint_mode: OverprintMode,
181
182 /* Font array */
183 /// Font structure, expects a dictionary,
184 pub(crate) font: Option<FontIndex>,
185
186 /* BG function */
187 /// __(Optional)__ The black-generation function, which maps the interval [ 0.0 1.0 ]
188 /// to the interval [ 0.0 1.0 ] (see Section 6.2.3, “Conversion from DeviceRGB to
189 /// DeviceCMYK”)
190 pub(crate) black_generation: Option<BlackGenerationFunction>,
191
192 /* BG2 function or name */
193 /// __(Optional; PDF 1.3)__ Same as BG except that the value may also be the name
194 /// Default , denoting the black-generation function that was in effect at the start
195 /// of the page. If both BG and BG2 are present in the same graphics state param-
196 /// eter dictionary, BG2 takes precedence.
197 pub(crate) black_generation_extra: Option<BlackGenerationExtraFunction>,
198
199 /* UCR function */
200 /// __(Optional)__ The undercolor-removal function, which maps the interval
201 /// [ 0.0 1.0 ] to the interval [ −1.0 1.0 ] (see Section 6.2.3, “Conversion from
202 /// DeviceRGB to DeviceCMYK”).
203 pub(crate) under_color_removal: Option<UnderColorRemovalFunction>,
204
205 /* UCR2 function */
206 /// __(Optional; PDF 1.3)__ Same as UCR except that the value may also be the name
207 /// Default , denoting the undercolor-removal function that was in effect at the
208 /// start of the page. If both UCR and UCR2 are present in the same graphics state
209 /// parameter dictionary, UCR2 takes precedence.
210 pub(crate) under_color_removal_extra: Option<UnderColorRemovalExtraFunction>,
211
212 /* TR function */
213 /// __(Optional)__ The transfer function, which maps the interval [ 0.0 1.0 ] to the in-
214 /// terval [ 0.0 1.0 ] (see Section 6.3, “Transfer Functions”). The value is either a
215 /// single function (which applies to all process colorants) or an array of four
216 /// functions (which apply to the process colorants individually). The name
217 /// Identity may be used to represent the identity function.
218 pub(crate) transfer_function: Option<TransferFunction>,
219
220 /* TR2 function */
221 /// __(Optional; PDF 1.3)__ Same as TR except that the value may also be the name
222 /// Default , denoting the transfer function that was in effect at the start of the
223 /// page. If both TR and TR2 are present in the same graphics state parameter dic-
224 /// tionary, TR2 takes precedence.
225 pub(crate) transfer_extra_function: Option<TransferExtraFunction>,
226
227 /* HT [dictionary, stream or name] */
228 /// __(Optional)__ The halftone dictionary or stream (see Section 6.4, “Halftones”) or
229 /// the name Default , denoting the halftone that was in effect at the start of the
230 /// page.
231 pub(crate) halftone_dictionary: Option<HalftoneType>,
232
233 /* FL integer */
234 /// __(Optional; PDF 1.3)__ The flatness tolerance (see Section 6.5.1, “Flatness Toler-
235 /// ance”).
236 pub(crate) flatness_tolerance: f32,
237
238 /* SM integer */
239 /// __(Optional; PDF 1.3)__ The smoothness tolerance (see Section 6.5.2, “Smooth-
240 /// ness Tolerance”).
241 pub(crate) smoothness_tolerance: f32,
242
243 /* SA integer */
244 /// (Optional) A flag specifying whether to apply automatic stroke adjustment
245 /// (see Section 6.5.4, “Automatic Stroke Adjustment”).
246 pub(crate) stroke_adjustment: bool,
247
248 /* BM name or array */
249 /// __(Optional; PDF 1.4)__ The current blend mode to be used in the transparent
250 /// imaging model (see Sections 7.2.4, “Blend Mode,” and 7.5.2, “Specifying
251 /// Blending Color Space and Blend Mode”).
252 pub(crate) blend_mode: BlendMode,
253
254 /* SM dictionary or name */
255 /// __(Optional; PDF 1.4)__ The current soft mask, specifying the mask shape or
256 /// mask opacity values to be used in the transparent imaging model (see
257 /// “Source Shape and Opacity” on page 526 and “Mask Shape and Opacity” on
258 /// page 550).
259 ///
260 /// *Note:* Although the current soft mask is sometimes referred to as a “soft clip,”
261 /// altering it with the gs operator completely replaces the old value with the new
262 /// one, rather than intersecting the two as is done with the current clipping path
263 /// parameter (see Section 4.4.3, “Clipping Path Operators”).
264 pub(crate) soft_mask: Option<SoftMask>,
265
266 /* CA integer */
267 /// __(Optional; PDF 1.4)__ The current stroking alpha constant, specifying the con-
268 /// stant shape or constant opacity value to be used for stroking operations in the
269 /// transparent imaging model (see “Source Shape and Opacity” on page 526 and
270 /// “Constant Shape and Opacity” on page 551).
271 pub(crate) current_stroke_alpha: f32,
272
273 /* ca integer */
274 /// __(Optional; PDF 1.4)__ Same as CA , but for nonstroking operations.
275 pub(crate) current_fill_alpha: f32,
276
277 /* AIS boolean */
278 /// __(Optional; PDF 1.4)__ The alpha source flag (“alpha is shape”), specifying
279 /// whether the current soft mask and alpha constant are to be interpreted as
280 /// shape values ( true ) or opacity values ( false )
281 /// true if the soft mask contains shape values, false for opacity
282 pub(crate) alpha_is_shape: bool,
283
284 /* TK boolean */
285 /// __(Optional; PDF 1.4)__ The text knockout flag, which determines the behavior of
286 /// overlapping glyphs within a text object in the transparent imaging model (see
287 /// Section 5.2.7, “Text Knockout”).
288 pub(crate) text_knockout: bool,
289}
290
291#[derive(Debug, Clone, Default)]
292pub struct ExtendedGraphicsStateBuilder {
293 /// Private field so we can control the `changed_fields` parameter
294 gs: ExtendedGraphicsState,
295}
296
297impl ExtendedGraphicsStateBuilder {
298 /// Creates a new graphics state builder
299 pub fn new() -> Self {
300 Self::default()
301 }
302
303 /// Sets the line width
304 #[inline]
305 pub fn with_line_width(mut self, line_width: f32) -> Self {
306 self.gs.line_width = line_width;
307 self.gs.changed_fields.insert(LINE_WIDTH);
308 self
309 }
310
311 /// Sets the line cap
312 #[inline]
313 pub fn with_line_cap(mut self, line_cap: LineCapStyle) -> Self {
314 self.gs.line_cap = line_cap;
315 self.gs.changed_fields.insert(LINE_CAP);
316 self
317 }
318
319 /// Sets the line join
320 #[inline]
321 pub fn with_line_join(mut self, line_join: LineJoinStyle) -> Self {
322 self.gs.line_join = line_join;
323 self.gs.changed_fields.insert(LINE_JOIN);
324 self
325 }
326
327 /// Sets the miter limit
328 #[inline]
329 pub fn with_miter_limit(mut self, miter_limit: f32) -> Self {
330 self.gs.miter_limit = miter_limit;
331 self.gs.changed_fields.insert(MITER_LIMIT);
332 self
333 }
334
335 /// Sets the rendering intent
336 #[inline]
337 pub fn with_rendering_intent(mut self, rendering_intent: RenderingIntent) -> Self {
338 self.gs.rendering_intent = rendering_intent;
339 self.gs.changed_fields.insert(RENDERING_INTENT);
340 self
341 }
342
343 /// Sets the stroke overprint
344 #[inline]
345 pub fn with_overprint_stroke(mut self, overprint_stroke: bool) -> Self {
346 self.gs.overprint_stroke = overprint_stroke;
347 self.gs.changed_fields.insert(OVERPRINT_STROKE);
348 self
349 }
350
351 /// Sets the fill overprint
352 #[inline]
353 pub fn with_overprint_fill(mut self, overprint_fill: bool) -> Self {
354 self.gs.overprint_fill = overprint_fill;
355 self.gs.changed_fields.insert(OVERPRINT_FILL);
356 self
357 }
358
359 /// Sets the overprint mode
360 #[inline]
361 pub fn with_overprint_mode(mut self, overprint_mode: OverprintMode) -> Self {
362 self.gs.overprint_mode = overprint_mode;
363 self.gs.changed_fields.insert(OVERPRINT_MODE);
364 self
365 }
366
367 /// Sets the font
368 /// __WARNING:__ Use `layer.add_font()` instead if you are not absolutely sure.
369 #[inline]
370 pub fn with_font(mut self, font: Option<FontIndex>) -> Self {
371 self.gs.font = font;
372 self.gs.changed_fields.insert(FONT);
373 self
374 }
375
376 /// Sets the black generation
377 #[inline]
378 pub fn with_black_generation(
379 mut self,
380 black_generation: Option<BlackGenerationFunction>,
381 ) -> Self {
382 self.gs.black_generation = black_generation;
383 self.gs.changed_fields.insert(BLACK_GENERATION);
384 self
385 }
386
387 /// Sets the black generation extra function
388 #[inline]
389 pub fn with_black_generation_extra(
390 mut self,
391 black_generation_extra: Option<BlackGenerationExtraFunction>,
392 ) -> Self {
393 self.gs.black_generation_extra = black_generation_extra;
394 self.gs.changed_fields.insert(BLACK_GENERATION_EXTRA);
395 self
396 }
397
398 /// Sets the undercolor removal function
399 #[inline]
400 pub fn with_undercolor_removal(
401 mut self,
402 under_color_removal: Option<UnderColorRemovalFunction>,
403 ) -> Self {
404 self.gs.under_color_removal = under_color_removal;
405 self.gs.changed_fields.insert(UNDERCOLOR_REMOVAL);
406 self
407 }
408
409 /// Sets the undercolor removal extra function
410 #[inline]
411 pub fn with_undercolor_removal_extra(
412 mut self,
413 under_color_removal_extra: Option<UnderColorRemovalExtraFunction>,
414 ) -> Self {
415 self.gs.under_color_removal_extra = under_color_removal_extra;
416 self.gs.changed_fields.insert(UNDERCOLOR_REMOVAL_EXTRA);
417 self
418 }
419
420 /// Sets the transfer function
421 #[inline]
422 pub fn with_transfer(mut self, transfer_function: Option<TransferFunction>) -> Self {
423 self.gs.transfer_function = transfer_function;
424 self.gs.changed_fields.insert(TRANSFER_FUNCTION);
425 self
426 }
427
428 /// Sets the transfer extra function
429 #[inline]
430 pub fn with_transfer_extra(
431 mut self,
432 transfer_extra_function: Option<TransferExtraFunction>,
433 ) -> Self {
434 self.gs.transfer_extra_function = transfer_extra_function;
435 self.gs.changed_fields.insert(TRANSFER_FUNCTION_EXTRA);
436 self
437 }
438
439 /// Sets the halftone dictionary
440 #[inline]
441 pub fn with_halftone(mut self, halftone_type: Option<HalftoneType>) -> Self {
442 self.gs.halftone_dictionary = halftone_type;
443 self.gs.changed_fields.insert(HALFTONE_DICTIONARY);
444 self
445 }
446
447 /// Sets the flatness tolerance
448 #[inline]
449 pub fn with_flatness_tolerance(mut self, flatness_tolerance: f32) -> Self {
450 self.gs.flatness_tolerance = flatness_tolerance;
451 self.gs.changed_fields.insert(FLATNESS_TOLERANCE);
452 self
453 }
454
455 /// Sets the smoothness tolerance
456 #[inline]
457 pub fn with_smoothness_tolerance(mut self, smoothness_tolerance: f32) -> Self {
458 self.gs.smoothness_tolerance = smoothness_tolerance;
459 self.gs.changed_fields.insert(SMOOTHNESS_TOLERANCE);
460 self
461 }
462
463 /// Sets the stroke adjustment
464 #[inline]
465 pub fn with_stroke_adjustment(mut self, stroke_adjustment: bool) -> Self {
466 self.gs.stroke_adjustment = stroke_adjustment;
467 self.gs.changed_fields.insert(STROKE_ADJUSTMENT);
468 self
469 }
470
471 /// Sets the blend mode
472 #[inline]
473 pub fn with_blend_mode(mut self, blend_mode: BlendMode) -> Self {
474 self.gs.blend_mode = blend_mode;
475 self.gs.changed_fields.insert(BLEND_MODE);
476 self
477 }
478
479 /// Sets the soft mask
480 #[inline]
481 pub fn with_soft_mask(mut self, soft_mask: Option<SoftMask>) -> Self {
482 self.gs.soft_mask = soft_mask;
483 self.gs.changed_fields.insert(SOFT_MASK);
484 self
485 }
486
487 /// Sets the current alpha for strokes
488 #[inline]
489 pub fn with_current_stroke_alpha(mut self, current_stroke_alpha: f32) -> Self {
490 self.gs.current_stroke_alpha = current_stroke_alpha;
491 self.gs.changed_fields.insert(CURRENT_STROKE_ALPHA);
492 self
493 }
494
495 /// Sets the current alpha for fills
496 #[inline]
497 pub fn with_current_fill_alpha(mut self, current_fill_alpha: f32) -> Self {
498 self.gs.current_fill_alpha = current_fill_alpha;
499 self.gs.changed_fields.insert(CURRENT_FILL_ALPHA);
500 self
501 }
502
503 /// Sets the current "alpha is shape"
504 #[inline]
505 pub fn with_alpha_is_shape(mut self, alpha_is_shape: bool) -> Self {
506 self.gs.alpha_is_shape = alpha_is_shape;
507 self.gs.changed_fields.insert(ALPHA_IS_SHAPE);
508 self
509 }
510
511 /// Sets the current text knockout
512 #[inline]
513 pub fn with_text_knockout(mut self, text_knockout: bool) -> Self {
514 self.gs.text_knockout = text_knockout;
515 self.gs.changed_fields.insert(TEXT_KNOCKOUT);
516 self
517 }
518
519 /// Consumes the builder and returns an actual ExtendedGraphicsState
520 #[inline]
521
522 pub fn build(self) -> ExtendedGraphicsState {
523 self.gs
524 }
525}
526
527impl Default for ExtendedGraphicsState {
528 /// Creates a default ExtGState dictionary. Useful for resetting
529 fn default() -> Self {
530 Self {
531 changed_fields: HashSet::new(),
532 line_width: 1.0,
533 line_cap: LineCapStyle::Butt,
534 line_join: LineJoinStyle::Miter,
535 miter_limit: 0.0,
536 line_dash_pattern: None,
537 rendering_intent: RenderingIntent::RelativeColorimetric,
538 overprint_stroke: false,
539 overprint_fill: false,
540 overprint_mode: OverprintMode::EraseUnderlying,
541 font: None,
542 black_generation: None,
543 black_generation_extra: None,
544 under_color_removal: None,
545 under_color_removal_extra: None,
546 transfer_function: None,
547 transfer_extra_function: None,
548 halftone_dictionary: None,
549 flatness_tolerance: 0.0,
550 smoothness_tolerance: 0.0,
551 stroke_adjustment: true,
552 blend_mode: BlendMode::Seperable(SeperableBlendMode::Normal),
553 soft_mask: None,
554 current_stroke_alpha: 1.0, /* 1.0 = opaque, not transparent*/
555 current_fill_alpha: 1.0,
556 alpha_is_shape: false,
557 text_knockout: false,
558 }
559 }
560}
561
562impl From<ExtendedGraphicsState> for lopdf::Object {
563 /// Compares the current graphics state with the previous one and returns an
564 /// "optimized" graphics state, meaning only the fields that have changed in
565 /// comparison to the previous one are returned.
566
567 fn from(val: ExtendedGraphicsState) -> Self {
568 let mut gs_operations = Vec::<(String, lopdf::Object)>::new();
569
570 // for each field, look if it was contained in the "changed fields"
571 if val.changed_fields.contains(LINE_WIDTH) {
572 gs_operations.push(("LW".to_string(), val.line_width.into()));
573 }
574
575 if val.changed_fields.contains(LINE_CAP) {
576 gs_operations.push(("LC".to_string(), val.line_cap.into()));
577 }
578
579 if val.changed_fields.contains(LINE_JOIN) {
580 gs_operations.push(("LJ".to_string(), val.line_join.into()));
581 }
582
583 if val.changed_fields.contains(MITER_LIMIT) {
584 gs_operations.push(("ML".to_string(), val.miter_limit.into()));
585 }
586
587 if val.changed_fields.contains(FLATNESS_TOLERANCE) {
588 gs_operations.push(("FL".to_string(), val.flatness_tolerance.into()));
589 }
590
591 if val.changed_fields.contains(RENDERING_INTENT) {
592 gs_operations.push(("RI".to_string(), val.rendering_intent.into()));
593 }
594
595 if val.changed_fields.contains(STROKE_ADJUSTMENT) {
596 gs_operations.push(("SA".to_string(), val.stroke_adjustment.into()));
597 }
598
599 if val.changed_fields.contains(OVERPRINT_FILL) {
600 gs_operations.push(("OP".to_string(), val.overprint_fill.into()));
601 }
602
603 if val.changed_fields.contains(OVERPRINT_STROKE) {
604 gs_operations.push(("op".to_string(), val.overprint_stroke.into()));
605 }
606
607 if val.changed_fields.contains(OVERPRINT_MODE) {
608 gs_operations.push(("OPM".to_string(), val.overprint_mode.into()));
609 }
610
611 if val.changed_fields.contains(CURRENT_FILL_ALPHA) {
612 gs_operations.push(("CA".to_string(), val.current_fill_alpha.into()));
613 }
614
615 if val.changed_fields.contains(CURRENT_STROKE_ALPHA) {
616 gs_operations.push(("ca".to_string(), val.current_stroke_alpha.into()));
617 }
618
619 if val.changed_fields.contains(BLEND_MODE) {
620 gs_operations.push(("BM".to_string(), val.blend_mode.into()));
621 }
622
623 if val.changed_fields.contains(ALPHA_IS_SHAPE) {
624 gs_operations.push(("AIS".to_string(), val.alpha_is_shape.into()));
625 }
626
627 if val.changed_fields.contains(TEXT_KNOCKOUT) {
628 gs_operations.push(("TK".to_string(), val.text_knockout.into()));
629 }
630
631 // set optional parameters
632 if let Some(ldp) = val.line_dash_pattern {
633 if val.changed_fields.contains(LINE_DASH_PATTERN) {
634 let pattern: lopdf::Object = ldp.into();
635 gs_operations.push(("D".to_string(), pattern));
636 }
637 }
638
639 if let Some(ref font) = val.font {
640 if val.changed_fields.contains(FONT) {
641 // let font_ref: lopdf::Object = font.into(); /* should be a reference to a font dictionary later on*/
642 // gs_operations.push(("Font".to_string(), font_ref));
643 }
644 }
645
646 // todo: transfer functions, halftone functions,
647 // black generation, undercolor removal
648 // these types cannot yet be converted into lopdf::Objects,
649 // need to implement Into<Object> for them
650
651 if val.changed_fields.contains(BLACK_GENERATION) {
652 if let Some(ref black_generation) = val.black_generation {}
653 }
654
655 if val.changed_fields.contains(BLACK_GENERATION_EXTRA) {
656 if let Some(ref black_generation_extra) = val.black_generation_extra {}
657 }
658
659 if val.changed_fields.contains(UNDERCOLOR_REMOVAL) {
660 if let Some(ref under_color_removal) = val.under_color_removal {}
661 }
662
663 if val.changed_fields.contains(UNDERCOLOR_REMOVAL_EXTRA) {
664 if let Some(ref under_color_removal_extra) = val.under_color_removal_extra {}
665 }
666
667 if val.changed_fields.contains(TRANSFER_FUNCTION) {
668 if let Some(ref transfer_function) = val.transfer_function {}
669 }
670
671 if val.changed_fields.contains(TRANSFER_FUNCTION_EXTRA) {
672 if let Some(ref transfer_extra_function) = val.transfer_extra_function {}
673 }
674
675 if val.changed_fields.contains(HALFTONE_DICTIONARY) {
676 if let Some(ref halftone_dictionary) = val.halftone_dictionary {}
677 }
678
679 if val.changed_fields.contains(SOFT_MASK) {
680 if let Some(ref soft_mask) = val.soft_mask {
681 } else {
682 gs_operations.push(("SM".to_string(), Name("None".as_bytes().to_vec())));
683 }
684 }
685
686 // if there are operations, push the "Type > ExtGState"
687 // otherwise, just return an empty dictionary
688 if !gs_operations.is_empty() {
689 gs_operations.push(("Type".to_string(), "ExtGState".into()));
690 }
691
692 let graphics_state = lopdf::Dictionary::from_iter(gs_operations);
693
694 Dictionary(graphics_state)
695 }
696}
697
698/// A reference to the graphics state, for reusing the
699/// graphics state during a stream without adding new graphics states all the time
700pub struct ExtendedGraphicsStateRef {
701 /// The name / hash of the graphics state
702 pub(crate) gs_name: String,
703}
704
705impl ExtendedGraphicsStateRef {
706 /// Creates a new graphics state reference (in order to be unique inside a page)
707 #[inline]
708 pub fn new(index: usize) -> Self {
709 Self {
710 gs_name: format!("GS{index:?}"),
711 }
712 }
713}
714
715/// __(PDF 1.3)__ A code specifying whether a color component value of 0
716/// in a `DeviceCMYK` color space should erase that component (`EraseUnderlying`) or
717/// leave it unchanged (`KeepUnderlying`) when overprinting (see Section 4.5.6, “Over-
718/// print Control”). Initial value: `EraseUnderlying`
719#[derive(Debug, PartialEq, Copy, Clone)]
720pub enum OverprintMode {
721 /// Erase underlying color when overprinting
722 EraseUnderlying, /* 0, default */
723 /// Keep underlying color when overprinting
724 KeepUnderlying, /* 1 */
725}
726
727impl From<OverprintMode> for lopdf::Object {
728 fn from(val: OverprintMode) -> Self {
729 use self::OverprintMode::*;
730 match val {
731 EraseUnderlying => Integer(0),
732 KeepUnderlying => Integer(1),
733 }
734 }
735}
736
737/// Black generation calculates the amount of black to be used when trying to
738/// reproduce a particular color.
739#[derive(Debug, PartialEq, Copy, Clone)]
740pub enum BlackGenerationFunction {
741 /// Regular black generation function
742 ///
743 /// ```rust,ignore
744 /// let cyan = 1.0 - red;
745 /// let magenta = 1.0 - green;
746 /// let yellow = 1.0 - blue;
747 /// let black = min(cyan, magenta, yellow);
748 /// ```
749 Default,
750 /// Expects an UnderColorRemoval to be set. This will compensate
751 /// the color for the added black
752 ///
753 /// ```rust,ignore
754 /// let cyan = 1.0 - red;
755 /// let magenta = 1.0 - green;
756 /// let yellow = 1.0 - blue;
757 /// let black = min(cyan, magenta, yellow);
758 /// ```
759 WithUnderColorRemoval,
760}
761
762#[derive(Debug, PartialEq, Copy, Clone)]
763pub enum BlackGenerationExtraFunction {}
764
765/// See `BlackGenerationFunction`, too. Undercolor removal reduces the amounts
766/// of the cyan, magenta, and yellow components to compensate for the amount of
767/// black that was added by black generation.
768///
769/// The undercolor-removal function computes the amount to subtract from each of
770/// the intermediate c, m, and y values to produce the final cyan, magenta, and yellow
771/// components. It can simply return its k operand unchanged, or it can return 0.0
772/// (so that no color is removed), some fraction of the black amount, or even a
773/// negative amount, thereby adding to the total amount of colorant.
774#[derive(Debug, PartialEq, Copy, Clone)]
775pub enum UnderColorRemovalFunction {
776 Default,
777}
778
779#[derive(Debug, PartialEq, Copy, Clone)]
780pub enum UnderColorRemovalExtraFunction {}
781
782#[derive(Debug, PartialEq, Copy, Clone)]
783pub enum TransferFunction {}
784
785#[derive(Debug, PartialEq, Copy, Clone)]
786pub enum TransferExtraFunction {}
787
788/// In PDF 1.2, the graphics state includes a current halftone parameter,
789/// which determines the halftoning process to be used by the painting operators.
790/// It may be defined by either a dictionary or a stream, depending on the
791/// type of halftone; the term halftone dictionary is used generically
792/// throughout this section to refer to either a dictionary object or the
793/// dictionary portion of a stream object. (The halftones that are defined
794/// by streams are specifically identified as such in the descriptions
795/// of particular halftone types; unless otherwise stated, they are
796/// understood to be defined by simple dictionaries instead.)
797
798/*
799 <<
800 /Type /Halftone
801 /HalftoneType 1
802 /Frequency 120
803 /Angle 30
804 /SpotFunction /CosineDot
805 /TransferFunction /Identity
806 >>
807*/
808
809/// Deserialized into Integer: 1, 5, 6, 10 or 16
810#[derive(Debug, PartialEq, Clone)]
811pub enum HalftoneType {
812 /// 1: Defines a single halftone screen by a frequency, angle, and spot function
813 Type1(f32, f32, SpotFunction),
814 /// 5: Defines an arbitrary number of halftone screens, one for each colorant or
815 /// color component (including both primary and spot colorants).
816 /// The keys in this dictionary are names of colorants; the values are halftone
817 /// dictionaries of other types, each defining the halftone screen for a single colorant.
818 Type5(Vec<HalftoneType>),
819 /// 6: Defines a single halftone screen by a threshold array containing 8-bit sample values.
820 Type6(Vec<u8>),
821 /// 10: Defines a single halftone screen by a threshold array containing 8-bit sample values,
822 /// representing a halftone cell that may have a nonzero screen angle.
823 Type10(Vec<u8>),
824 /// 16: __(PDF 1.3)__ Defines a single halftone screen by a threshold array containing 16-bit
825 /// sample values, representing a halftone cell that may have a nonzero screen angle.
826 Type16(Vec<u16>),
827}
828
829impl HalftoneType {
830 /// Get the identifer integer of the HalftoneType
831 pub fn get_type(&self) -> i64 {
832 use self::HalftoneType::*;
833 match *self {
834 Type1(_, _, _) => 1,
835 Type5(_) => 5, /* this type does not actually exist, todo */
836 Type6(_) => 6,
837 Type10(_) => 10,
838 Type16(_) => 16,
839 }
840 }
841
842 pub fn into_obj(self) -> Vec<lopdf::Object> {
843 vec![Dictionary(lopdf::Dictionary::from_iter(vec![
844 ("Type", "Halftone".into()),
845 ("HalftoneType", self.get_type().into()),
846 ]))]
847 }
848}
849
850/// Spot functions, Table 6.1, Page 489 in Pdf Reference v1.7
851/// The code is pseudo code, returning the grey component at (x, y).
852#[derive(Debug, PartialEq, Copy, Clone)]
853pub enum SpotFunction {
854 /// `1 - (pow(x, 2) + pow(y, 2))`
855 SimpleDot,
856 /// `pow(x, 2) + pow(y, 2) - 1`
857 InvertedSimpleDot,
858 /// `(sin(360 * x) / 2) + (sin(360 * y) / 2)`
859 DoubleDot,
860 /// `- ((sin(360 * x) / 2) + (sin(360 * y) / 2))`
861 InvertedDoubleDot,
862 /// `(cos(180 * x) / 2) + (cos(180 * y) / 2)`
863 CosineDot,
864 /// `(sin(360 x (x / 2)) / 2) + (sin(360 * y) / 2)`
865 Double,
866 /// `- ((sin(360 x (x / 2)) / 2) + (sin(360 * y) / 2))`
867 InvertedDouble,
868 /// `- abs(y)`
869 Line,
870 /// `x`
871 LineX,
872 /// `y`
873 LineY,
874 /// ```rust,ignore
875 /// if abs(x) + abs(y) <= 1 {
876 /// 1 - (pow(x, 2) + pow(y, 2))
877 /// } else {
878 /// pow((abs(x) - 1), 2) + pow((abs(y) - 1), 2) - 1
879 /// }
880 /// ```
881 Round,
882 /// ```rust,ignore
883 /// let w = (3 * abs(x)) + (4 * abs(y)) - 3;
884 ///
885 /// if w < 0 {
886 /// 1 - ((pow(x, 2) + pow((abs(y) / 0.75), 2)) / 4)
887 /// } else if w > 1 {
888 /// pow((pow((1 - abs(x), 2) + (1 - abs(y)) / 0.75), 2) / 4) - 1
889 /// } else {
890 /// 0.5 - w
891 /// }
892 /// ```
893 Ellipse,
894 /// `1 - (pow(x, 2) + 0.9 * pow(y, 2))`
895 EllipseA,
896 /// `pow(x, 2) + 0.9 * pow(y, 2) - 1`
897 InvertedEllipseA,
898 /// `1 - sqrt(pow(x, 2) + (5 / 8) * pow(y, 2))`
899 EllipseB,
900 /// `1 - (0.9 * pow(x, 2) + pow(y, 2))`
901 EllipseC,
902 /// `0.9 * pow(x, 2) + pow(y, 2) - 1`
903 InvertedEllipseC,
904 /// `- max(abs(x), abs(y))`
905 Square,
906 /// `- min(abs(x), abs(y))`
907 Cross,
908 /// `(0.9 * abs(x) + abs(y)) / 2`
909 Rhomboid,
910 /// ```rust,ignore
911 /// let t = abs(x) + abs(y);
912 /// if t <= 0.75 {
913 /// 1 - (pow(x, 2) + pow(y, 2))
914 /// } else if t < 1.23 {
915 /// 1 - (0.85 * abs(x) + abs(y))
916 /// } else {
917 /// pow((abs(x) - 1), 2) + pow((abs(y) - 1), 2) - 1
918 /// }
919 /// ```
920 Diamond,
921}
922
923#[derive(Debug, PartialEq, Copy, Clone)]
924pub enum BlendMode {
925 Seperable(SeperableBlendMode),
926 NonSeperable(NonSeperableBlendMode),
927}
928
929impl From<BlendMode> for lopdf::Object {
930 fn from(val: BlendMode) -> Self {
931 use self::BlendMode::*;
932 use self::NonSeperableBlendMode::*;
933 use self::SeperableBlendMode::*;
934
935 let blend_mode_str = match val {
936 Seperable(s) => match s {
937 Normal => "Normal",
938 Multiply => "Multiply",
939 Screen => "Screen",
940 Overlay => "Overlay",
941 Darken => "Darken",
942 Lighten => "Lighten",
943 ColorDodge => "ColorDodge",
944 ColorBurn => "ColorBurn",
945 HardLight => "HardLight",
946 SoftLight => "SoftLight",
947 Difference => "Difference",
948 Exclusion => "Exclusion",
949 },
950 NonSeperable(n) => match n {
951 Hue => "Hue",
952 Saturation => "Saturation",
953 Color => "Color",
954 Luminosity => "Luminosity",
955 },
956 };
957
958 Name(blend_mode_str.as_bytes().to_vec())
959 }
960}
961
962/// PDF Reference 1.7, Page 520, Table 7.2
963/// Blending modes for objects
964/// In the following reference, each function gets one new color (the thing to paint on top)
965/// and an old color (the color that was already present before the object gets painted)
966///
967/// The function simply notes the formula that has to be applied to (`color_new`, `color_old`) in order
968/// to get the desired effect. You have to run each formula once for each color channel.
969#[derive(Debug, PartialEq, Copy, Clone)]
970pub enum SeperableBlendMode {
971 /// Selects the source color, ignoring the old color. Default mode.
972 ///
973 /// `color_new`
974 Normal,
975 /// Multiplies the old color and source color values
976 /// Note that these values have to be in the range [0.0 to 1.0] to work.
977 /// The result color is always at least as dark as either of the two constituent
978 /// colors. Multiplying any color with black produces black; multiplying with white
979 /// leaves the original color unchanged.Painting successive overlapping objects with
980 /// a color other than black or white produces progressively darker colors.
981 ///
982 /// `color_old * color_new`
983 Multiply,
984 /// Multiplies the complements of the old color and new color values, then
985 /// complements the result
986 /// The result color is always at least as light as either of the two constituent colors.
987 /// Screening any color with white produces white; screening with black leaves the original
988 /// color unchanged. The effect is similar to projecting multiple photographic slides
989 /// simultaneously onto a single screen.
990 ///
991 /// `color_old + color_new - (color_old * color_new)`
992 Screen,
993 /// Multiplies or screens the colors, depending on the old color value. Source colors
994 /// overlay the old color while preserving its highlights and shadows. The old color is
995 /// not replaced but is mixed with the source color to reflect the lightness or darkness
996 /// of the old color.
997 ///
998 /// TLDR: It's the inverse of HardLight
999 ///
1000 /// ```rust,ignore
1001 /// if color_old <= 0.5 {
1002 /// Multiply(color_new, 2 x color_old)
1003 /// } else {
1004 /// Screen(color_new, 2 * color_old - 1)
1005 /// }
1006 /// ```
1007 Overlay,
1008 /// Selects the darker one of two colors.The old color is replaced with the
1009 /// new color where the new color is darker; otherwise, it is left unchanged.
1010 ///
1011 /// `min(color_old, color_new)`
1012 Darken,
1013 /// Selects the lighter one of two colors. The old color is replaced with the
1014 /// new color where the new color is lighter; otherwise, it is left unchanged.
1015 ///
1016 /// `max(color_old, color_new)`
1017 Lighten,
1018 /// Brightens the backdrop color to reflect the source color. Painting with
1019 /// black produces no changes.
1020 ///
1021 /// ```rust,ignore
1022 /// if color_new < 1 {
1023 /// min(1, color_old / (1 - color_new))
1024 /// } else {
1025 /// 1
1026 /// }
1027 /// ```
1028 ColorDodge,
1029 /// Darkens the backdrop color to reflect the source color. Painting with
1030 /// white produces no change.
1031 ///
1032 /// ```rust,ignore
1033 /// if color_new > 0 {
1034 /// 1 - min(1, (1 - color_old) / color_new)
1035 /// } else {
1036 /// 0
1037 /// }
1038 /// ```
1039 ColorBurn,
1040 /// Multiplies or screens the colors, depending on the source color value. The effect is
1041 /// similar to shining a harsh spotlight on the old color. It's the inverse of Screen.
1042 ///
1043 /// ```rust,ignore
1044 /// if color_new <= 0.5 {
1045 /// Multiply(color_old, 2 x color_new)
1046 /// } else {
1047 /// Screen(color_old, 2 * color_new - 1)
1048 /// }
1049 /// ```
1050 HardLight,
1051 /// Darkens or lightens the colors, depending on the source color value.
1052 /// The effect is similar to shining a diffused spotlight on the backdrop.
1053 ///
1054 /// ```rust,ignore
1055 /// if color_new <= 0.5 {
1056 /// color_old - ((1 - (2 * color_new)) * color_old * (1 - color_old))
1057 /// } else {
1058 /// let mut dx_factor = color_old.sqrt();
1059 /// if color_old <= 0.25 {
1060 /// dx_factor = (((16 * color_old - 12) * color_old) + 4) * color_old;
1061 /// }
1062 /// color_old + ((2 * color_new) - 1) * (dx_factor - color_old)
1063 /// }
1064 /// ```
1065 SoftLight,
1066 /// Subtracts the darker of the two constituent colors from the lighter color
1067 /// Painting with white inverts the backdrop color; painting with black produces no change.
1068 ///
1069 /// `abs(color_old - color_new)`
1070 Difference,
1071 /// Produces an effect similar to that of the Difference mode but lower in contrast.
1072 /// Painting with white inverts the backdrop color; painting with black produces no change.
1073 ///
1074 /// `color_old + color_new - (2 * color_old * color_new)`
1075 Exclusion,
1076}
1077
1078/// Since the nonseparable blend modes consider all color components in combination, their
1079/// computation depends on the blending color space in which the components are interpreted.
1080/// They may be applied to all multiple-component color spaces that are allowed as blending
1081/// color spaces (see Section 7.2.3, “Blending Color Space”).
1082///
1083/// All of these blend modes conceptually entail the following steps:
1084///
1085/// 1. Convert the backdrop and source colors from the blending color space to an intermediate
1086/// HSL (hue-saturation-luminosity) representation.
1087/// 2. Create a new color from some combination of hue, saturation, and luminosity components
1088/// selected from the backdrop and source colors.
1089/// 3. Convert the result back to the original (blending) color space.
1090///
1091/// However, the formulas given below do not actually perform these conversions. Instead,
1092/// they start with whichever color (backdrop or source) is providing the hue for the result;
1093/// then they adjust this color to have the proper saturation and luminosity.
1094///
1095/// ### For RGB color spaces
1096///
1097/// The nonseparable blend mode formulas make use of several auxiliary functions. These
1098/// functions operate on colors that are assumed to have red, green, and blue components.
1099///
1100/// ```rust,ignore
1101/// # #[macro_use] extern crate printpdf;
1102/// # use printpdf::Rgb;
1103/// # use printpdf::glob_macros::*;
1104/// # fn main() { /* needed for testing*/ }
1105/// fn luminosity(input: Rgb) -> f32 {
1106/// 0.3 * input.r + 0.59 * input.g + 0.11 * input.b
1107/// }
1108///
1109/// fn set_luminosity(input: Rgb, target_luminosity: f32) -> Rgb {
1110/// let d = target_luminosity - luminosity(input);
1111/// Rgb {
1112/// r: input.r + d,
1113/// g: input.g + d,
1114/// b: input.b + d,
1115/// icc_profile: input.icc_profile,
1116/// }
1117/// }
1118///
1119/// fn clip_color(mut input: Rgb) -> Rgb {
1120///
1121/// let lum = luminosity(input);
1122///
1123/// let mut cur_r = (input.r * 1000.0) as i64;
1124/// let mut cur_g = (input.g * 1000.0) as i64;
1125/// let mut cur_b = (input.b * 1000.0) as i64;
1126///
1127/// /// min! and max! is defined in printpdf/src/glob_macros.rs
1128/// let mut min = min!(cur_r, cur_g, cur_b);
1129/// let mut max = max!(cur_r, cur_g, cur_b);
1130///
1131/// let new_min = (min as f32) / 1000.0;
1132/// let new_max = (max as f32) / 1000.0;
1133///
1134/// if new_min < 0.0 {
1135/// input.r = lum + (((input.r - lum) * lum) / (lum - new_min));
1136/// input.g = lum + (((input.g - lum) * lum) / (lum - new_min));
1137/// input.b = lum + (((input.b - lum) * lum) / (lum - new_min));
1138/// } else if new_max > 1.0 {
1139/// input.r = lum + ((input.r - lum) * (1.0 - lum) / (new_max - lum));
1140/// input.g = lum + ((input.g - lum) * (1.0 - lum) / (new_max - lum));
1141/// input.b = lum + ((input.b - lum) * (1.0 - lum) / (new_max - lum));
1142/// }
1143///
1144/// return input;
1145/// }
1146///
1147/// fn saturation(input: Rgb) -> f32 {
1148/// let mut cur_r = (input.r * 1000.0) as i64;
1149/// let mut cur_g = (input.g * 1000.0) as i64;
1150/// let mut cur_b = (input.b * 1000.0) as i64;
1151///
1152/// /// min! and max! is defined in printpdf/src/glob_macros.rs
1153/// let mut min = min!(cur_r, cur_g, cur_b);
1154/// let mut max = max!(cur_r, cur_g, cur_b);
1155///
1156/// let new_min = (min as f32) / 1000.0;
1157/// let new_max = (max as f32) / 1000.0;
1158/// new_max - new_min
1159/// }
1160/// ```
1161///
1162/// ### For CMYK color spaces
1163///
1164/// The C, M, and Y components are converted to their complementary R, G, and B components
1165/// in the usual way. The formulas above are applied to the RGB color values. The results
1166/// are converted back to C, M, and Y.
1167///
1168/// For the K component, the result is the K component of Cb for the Hue, Saturation, and
1169/// Color blend modes; it is the K component of Cs for the Luminosity blend mode.
1170#[derive(Debug, PartialEq, Copy, Clone)]
1171pub enum NonSeperableBlendMode {
1172 Hue,
1173 Saturation,
1174 Color,
1175 Luminosity,
1176}
1177
1178/* RI name (or ri inside a stream)*/
1179/// Although CIE-based color specifications are theoretically device-independent,
1180/// they are subject to practical limitations in the color reproduction capabilities of
1181/// the output device. Such limitations may sometimes require compromises to be
1182/// made among various properties of a color specification when rendering colors for
1183/// a given device. Specifying a rendering intent (PDF 1.1) allows a PDF file to set priorities
1184/// regarding which of these properties to preserve and which to sacrifice.
1185#[derive(Debug, PartialEq, Copy, Clone)]
1186pub enum RenderingIntent {
1187 /// Colors are represented solely with respect to the light source; no
1188 /// correction is made for the output medium’s white point (such as
1189 /// the color of unprinted paper). Thus, for example, a monitor’s
1190 /// white point, which is bluish compared to that of a printer’s paper,
1191 /// would be reproduced with a blue cast. In-gamut colors are
1192 /// reproduced exactly; out-of-gamut colors are mapped to the
1193 /// nearest value within the reproducible gamut. This style of reproduction
1194 /// has the advantage of providing exact color matches
1195 /// from one output medium to another. It has the disadvantage of
1196 /// causing colors with Y values between the medium’s white point
1197 /// and 1.0 to be out of gamut. A typical use might be for logos and
1198 /// solid colors that require exact reproduction across different media.
1199 AbsoluteColorimetric,
1200 /// Colors are represented with respect to the combination of the
1201 /// light source and the output medium’s white point (such as the
1202 /// color of unprinted paper). Thus, for example, a monitor’s white
1203 /// point would be reproduced on a printer by simply leaving the
1204 /// paper unmarked, ignoring color differences between the two
1205 /// media. In-gamut colors are reproduced exactly; out-of-gamut
1206 /// colors are mapped to the nearest value within the reproducible
1207 /// gamut. This style of reproduction has the advantage of adapting
1208 /// for the varying white points of different output media. It has the
1209 /// disadvantage of not providing exact color matches from one me-
1210 /// dium to another. A typical use might be for vector graphics.
1211 RelativeColorimetric,
1212 /// Colors are represented in a manner that preserves or emphasizes
1213 /// saturation. Reproduction of in-gamut colors may or may not be
1214 /// colorimetrically accurate. A typical use might be for business
1215 /// graphics, where saturation is the most important attribute of the
1216 /// color.
1217 Saturation,
1218 /// Colors are represented in a manner that provides a pleasing perceptual
1219 /// appearance. To preserve color relationships, both in-gamut
1220 /// and out-of-gamut colors are generally modified from
1221 /// their precise colorimetric values. A typical use might be for scanned images.
1222 Perceptual,
1223}
1224
1225/* ri name */
1226impl RenderingIntent {
1227 pub fn into_stream_op(self) -> Vec<Operation> {
1228 use self::RenderingIntent::*;
1229 let rendering_intent_string = match self {
1230 AbsoluteColorimetric => "AbsoluteColorimetric",
1231 RelativeColorimetric => "RelativeColorimetric",
1232 Saturation => "Saturation",
1233 Perceptual => "Perceptual",
1234 };
1235
1236 vec![Operation::new(
1237 "ri",
1238 vec![Name(rendering_intent_string.as_bytes().to_vec())],
1239 )]
1240 }
1241}
1242
1243/* RI name , only to be used in graphics state dictionary */
1244impl From<RenderingIntent> for lopdf::Object {
1245 /// Consumes the object and converts it to an PDF object
1246 fn from(val: RenderingIntent) -> Self {
1247 use self::RenderingIntent::*;
1248 let rendering_intent_string = match val {
1249 AbsoluteColorimetric => "AbsoluteColorimetric",
1250 RelativeColorimetric => "RelativeColorimetric",
1251 Saturation => "Saturation",
1252 Perceptual => "Perceptual",
1253 };
1254
1255 Name(rendering_intent_string.as_bytes().to_vec())
1256 }
1257}
1258
1259/// A soft mask is used for transparent images such as PNG with an alpha component
1260/// The bytes range from 0xFF (opaque) to 0x00 (transparent). The alpha channel of a
1261/// PNG image have to be sorted out.
1262/// Can also be used for Vignettes, etc.
1263/// Beware of color spaces!
1264/// __See PDF Reference Page 545__ - Soft masks
1265#[derive(Debug, PartialEq, Clone)]
1266pub struct SoftMask {
1267 /// The data to be used as a soft mask
1268 data: Vec<u8>,
1269 /// Bits per component (1 for black / white, 8 for greyscale, up to 16)
1270 bits_per_component: u8,
1271}
1272
1273#[derive(Debug, PartialEq, Copy, Clone)]
1274pub enum SoftMaskFunction {
1275 // (Color, Shape, Alpha) = Composite(Color0, Alpha0, Group)
1276 /// In this function, the old (backdrop) color does not contribute to the result.
1277 /// This is the easies function, but may look bad at edges.
1278 GroupAlpha,
1279 //
1280 GroupLuminosity,
1281}
1282/// __See PDF Reference Page 216__ - Line join style
1283#[derive(Debug, PartialEq, Copy, Clone)]
1284pub enum LineJoinStyle {
1285 /// Miter join. The outer edges of the strokes for the two segments are extended
1286 /// until they meet at an angle, as in a picture frame. If the segments meet at too
1287 /// sharp an angle (as defined by the miter limit parameter—see “Miter Limit,”
1288 /// above), a bevel join is used instead.
1289 Miter,
1290 /// Round join. An arc of a circle with a diameter equal to the line width is drawn
1291 /// around the point where the two segments meet, connecting the outer edges of
1292 /// the strokes for the two segments. This pieslice-shaped figure is filled in, pro-
1293 /// ducing a rounded corner.
1294 Round,
1295 /// Bevel join. The two segments are finished with butt caps (see “Line Cap Style”
1296 /// on page 216) and the resulting notch beyond the ends of the segments is filled
1297 /// with a triangle.
1298 Limit,
1299}
1300
1301impl From<LineJoinStyle> for i64 {
1302 fn from(val: LineJoinStyle) -> Self {
1303 use self::LineJoinStyle::*;
1304 match val {
1305 Miter => 0,
1306 Round => 1,
1307 Limit => 2,
1308 }
1309 }
1310}
1311
1312impl From<LineJoinStyle> for Operation {
1313 fn from(val: LineJoinStyle) -> Self {
1314 let line_join_num: i64 = val.into();
1315 Operation::new("j", vec![Integer(line_join_num)])
1316 }
1317}
1318
1319impl From<LineJoinStyle> for lopdf::Object {
1320 fn from(val: LineJoinStyle) -> Self {
1321 Integer(val.into())
1322 }
1323}
1324
1325/// __See PDF Reference (Page 216)__ - Line cap (ending) style
1326#[derive(Debug, PartialEq, Copy, Clone)]
1327pub enum LineCapStyle {
1328 /// Butt cap. The stroke is squared off at the endpoint of the path. There is no
1329 /// projection beyond the end of the path.
1330 Butt,
1331 /// Round cap. A semicircular arc with a diameter equal to the line width is
1332 /// drawn around the endpoint and filled in.
1333 Round,
1334 /// Projecting square cap. The stroke continues beyond the endpoint of the path
1335 /// for a distance equal to half the line width and is squared off.
1336 ProjectingSquare,
1337}
1338
1339impl From<LineCapStyle> for i64 {
1340 fn from(val: LineCapStyle) -> Self {
1341 use self::LineCapStyle::*;
1342 match val {
1343 Butt => 0,
1344 Round => 1,
1345 ProjectingSquare => 2,
1346 }
1347 }
1348}
1349
1350impl From<LineCapStyle> for Operation {
1351 fn from(val: LineCapStyle) -> Self {
1352 Operation::new("J", vec![Integer(val.into())])
1353 }
1354}
1355
1356impl From<LineCapStyle> for lopdf::Object {
1357 fn from(val: LineCapStyle) -> Self {
1358 Integer(val.into())
1359 }
1360}
1361
1362/// Line dash pattern is made up of a total width
1363#[derive(Debug, PartialEq, Copy, Clone, Default)]
1364pub struct LineDashPattern {
1365 /// Offset at which the dashing pattern should start, measured from the beginning ot the line
1366 /// Default: 0 (start directly where the line starts)
1367 pub offset: i64,
1368 /// Length of the first dash in the dash pattern. If `None`, the line will be solid (good for resetting the dash pattern)
1369 pub dash_1: Option<i64>,
1370 /// Whitespace after the first dash. If `None`, whitespace will be the same as length_1st,
1371 /// meaning that the line will have dash - whitespace - dash - whitespace in even offsets
1372 pub gap_1: Option<i64>,
1373 /// Length of the second dash in the dash pattern. If None, will be equal to length_1st
1374 pub dash_2: Option<i64>,
1375 /// Same as whitespace_1st, but for length_2nd
1376 pub gap_2: Option<i64>,
1377 /// Length of the second dash in the dash pattern. If None, will be equal to length_1st
1378 pub dash_3: Option<i64>,
1379 /// Same as whitespace_1st, but for length_3rd
1380 pub gap_3: Option<i64>,
1381}
1382
1383// impl LineDashPattern {
1384// /// Creates a new dash pattern
1385// pub fn new(
1386// offset: i64,
1387// dash_1: Option<i64>,
1388// gap_1: Option<i64>,
1389// dash_2: Option<i64>,
1390// gap_2: Option<i64>,
1391// dash_3: Option<i64>,
1392// gap_3: Option<i64>,
1393// ) -> Self {
1394// Self {
1395// offset,
1396// dash_1,
1397// gap_1,
1398// dash_2,
1399// gap_2,
1400// dash_3,
1401// gap_3,
1402// }
1403// }
1404// }
1405
1406// conversion into a dash array for reuse in operation / gs dictionary
1407impl From<LineDashPattern> for (Vec<i64>, i64) {
1408 fn from(val: LineDashPattern) -> Self {
1409 (
1410 [
1411 val.dash_1, val.gap_1, val.dash_2, val.gap_2, val.dash_3, val.gap_3,
1412 ]
1413 .iter()
1414 .copied()
1415 .take_while(Option::is_some)
1416 .flatten()
1417 .collect(),
1418 val.offset,
1419 )
1420 }
1421}
1422
1423impl From<LineDashPattern> for Operation {
1424 fn from(val: LineDashPattern) -> Self {
1425 let (dash_array, offset) = val.into();
1426 let dash_array_ints = dash_array.into_iter().map(Integer).collect();
1427 Operation::new("d", vec![Array(dash_array_ints), Integer(offset)])
1428 }
1429}
1430
1431impl From<LineDashPattern> for lopdf::Object {
1432 fn from(val: LineDashPattern) -> Self {
1433 use lopdf::Object::*;
1434 let (dash_array, offset) = val.into();
1435 let mut dash_array_ints: Vec<lopdf::Object> = dash_array.into_iter().map(Integer).collect();
1436 dash_array_ints.push(Integer(offset));
1437 Array(dash_array_ints)
1438 }
1439}