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}