pdf_writer/
color.rs

1use super::*;
2use crate::object::TextStrLike;
3
4/// CIE XYZ coordinates of the D65 noon daylight white.
5const CIE_D65: [f32; 3] = [0.9505, 1.0, 1.0888];
6
7/// CIE XYZ coordinates of the D50 horizon light white.
8const CIE_D50: [f32; 3] = [0.9642, 1.0, 0.82489];
9
10/// CIE XYZ coordinates of the E equal radiator white.
11const CIE_E: [f32; 3] = [1.000, 1.000, 1.000];
12
13/// CIE XYZ coordinates of the C north sky daylight white.
14const CIE_C: [f32; 3] = [0.9807, 1.0000, 1.1822];
15
16/// The type of a color space.
17#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
18#[allow(unused)]
19enum ColorSpaceType {
20    CalGray,
21    CalRgb,
22    Lab,
23    IccBased,
24    DeviceRgb,
25    DeviceCmyk,
26    DeviceGray,
27    Indexed,
28    Pattern,
29    Separation,
30    DeviceN,
31}
32
33impl ColorSpaceType {
34    pub(crate) fn to_name(self) -> Name<'static> {
35        match self {
36            Self::CalRgb => Name(b"CalRGB"),
37            Self::CalGray => Name(b"CalGray"),
38            Self::Lab => Name(b"Lab"),
39            Self::IccBased => Name(b"ICCBased"),
40            Self::DeviceRgb => Name(b"DeviceRGB"),
41            Self::DeviceCmyk => Name(b"DeviceCMYK"),
42            Self::DeviceGray => Name(b"DeviceGray"),
43            Self::Separation => Name(b"Separation"),
44            Self::DeviceN => Name(b"DeviceN"),
45            Self::Indexed => Name(b"Indexed"),
46            Self::Pattern => Name(b"Pattern"),
47        }
48    }
49}
50
51/// The type of a device color space.
52#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
53pub enum DeviceColorSpace {
54    /// Red, green and blue.
55    Rgb,
56    /// Cyan, magenta, yellow and black.
57    Cmyk,
58    /// Gray.
59    Gray,
60}
61
62impl DeviceColorSpace {
63    pub(crate) fn to_name(self) -> Name<'static> {
64        match self {
65            Self::Rgb => Name(b"DeviceRGB"),
66            Self::Cmyk => Name(b"DeviceCMYK"),
67            Self::Gray => Name(b"DeviceGray"),
68        }
69    }
70}
71
72/// Writer for a _color space_.
73///
74/// This struct is created by [`Chunk::color_space`],
75/// [`Chunk::color_space`], [`ImageXObject::color_space`],
76/// [`Separation::alternate_color_space`] and [`Group::color_space`].
77pub struct ColorSpace<'a> {
78    obj: Obj<'a>,
79}
80
81writer!(ColorSpace: |obj| Self { obj });
82
83/// CIE-based color spaces.
84impl ColorSpace<'_> {
85    /// Write a `CalRGB` color space.
86    pub fn cal_rgb(
87        self,
88        white_point: [f32; 3],
89        black_point: Option<[f32; 3]>,
90        gamma: Option<[f32; 3]>,
91        matrix: Option<[f32; 9]>,
92    ) {
93        let mut array = self.obj.array();
94        array.item(ColorSpaceType::CalRgb.to_name());
95
96        let mut dict = array.push().dict();
97        dict.insert(Name(b"WhitePoint")).array().items(white_point);
98
99        if let Some(black_point) = black_point {
100            dict.insert(Name(b"BlackPoint")).array().items(black_point);
101        }
102
103        if let Some(gamma) = gamma {
104            dict.insert(Name(b"Gamma")).array().items(gamma);
105        }
106
107        if let Some(matrix) = matrix {
108            dict.insert(Name(b"Matrix")).array().items(matrix);
109        }
110    }
111
112    /// Write a `CalGray` color space.
113    pub fn cal_gray(
114        self,
115        white_point: [f32; 3],
116        black_point: Option<[f32; 3]>,
117        gamma: Option<f32>,
118    ) {
119        let mut array = self.obj.array();
120        array.item(ColorSpaceType::CalGray.to_name());
121
122        let mut dict = array.push().dict();
123        dict.insert(Name(b"WhitePoint")).array().items(white_point);
124
125        if let Some(black_point) = black_point {
126            dict.insert(Name(b"BlackPoint")).array().items(black_point);
127        }
128
129        if let Some(gamma) = gamma {
130            dict.pair(Name(b"Gamma"), gamma);
131        }
132    }
133
134    /// Write a `Lab` color space.
135    pub fn lab(
136        self,
137        white_point: [f32; 3],
138        black_point: Option<[f32; 3]>,
139        range: Option<[f32; 4]>,
140    ) {
141        let mut array = self.obj.array();
142        array.item(ColorSpaceType::Lab.to_name());
143
144        let mut dict = array.push().dict();
145        dict.insert(Name(b"WhitePoint")).array().items(white_point);
146
147        if let Some(black_point) = black_point {
148            dict.insert(Name(b"BlackPoint")).array().items(black_point);
149        }
150
151        if let Some(range) = range {
152            dict.insert(Name(b"Range")).array().items(range);
153        }
154    }
155
156    /// Write an `ICCBased` color space.
157    ///
158    /// The `stream` argument should be an indirect reference to an [ICC
159    /// profile](IccProfile) stream.
160    pub fn icc_based(self, stream: Ref) {
161        let mut array = self.obj.array();
162        array.item(ColorSpaceType::IccBased.to_name());
163        array.item(stream);
164    }
165}
166
167/// Writer for an _ICC profile stream_.
168///
169/// This struct is created by [`Chunk::icc_profile`].
170pub struct IccProfile<'a> {
171    stream: Stream<'a>,
172}
173
174impl<'a> IccProfile<'a> {
175    /// Create a new ICC profile stream writer
176    pub(crate) fn start(stream: Stream<'a>) -> Self {
177        Self { stream }
178    }
179
180    /// Write the `/N` attribute. Required.
181    ///
182    /// The number of components in the color space.
183    /// Shall be 1, 3, or 4.
184    pub fn n(&mut self, n: i32) -> &mut Self {
185        assert!(n == 1 || n == 3 || n == 4, "n must be 1, 3, or 4, but is {n}");
186        self.pair(Name(b"N"), n);
187        self
188    }
189
190    /// Write the `/Alternate` attribute with a color space.
191    ///
192    /// The alternate color space to use when the ICC profile is not
193    /// supported. Must be a color space with the same number of
194    /// components as the ICC profile. Pattern color spaces are not
195    /// allowed.
196    pub fn alternate(&mut self) -> ColorSpace<'_> {
197        ColorSpace::start(self.insert(Name(b"Alternate")))
198    }
199
200    /// Write the `/Alternate` attribute with a name.
201    ///
202    /// The alternate color space referenced by name must be registered in the
203    /// current [resource dictionary.](crate::writers::Resources)
204    pub fn alternate_name(&mut self, name: Name<'_>) -> &mut Self {
205        self.pair(Name(b"Alternate"), name);
206        self
207    }
208
209    /// Write the `/Range` attribute.
210    ///
211    /// Specifies the permissible range of values for each component. The array
212    /// shall contain 2 × `N` numbers, where [`N`](Self::n) is the number of
213    /// components in the color space. The array is organized in pairs, where
214    /// the first value shall be the minimum value and the second shall be the
215    /// maximum value.
216    pub fn range(&mut self, range: impl IntoIterator<Item = f32>) -> &mut Self {
217        self.insert(Name(b"Range")).array().typed().items(range);
218        self
219    }
220
221    /// Write the `/Metadata` attribute.
222    ///
223    /// A reference to a [stream containing metadata](crate::writers::Metadata)
224    /// for the ICC profile.
225    pub fn metadata(&mut self, metadata: Ref) -> &mut Self {
226        self.pair(Name(b"Metadata"), metadata);
227        self
228    }
229}
230
231deref!('a, IccProfile<'a> => Stream<'a>, stream);
232
233/// Common calibrated color spaces.
234impl ColorSpace<'_> {
235    /// Write a `CalRGB` color space approximating sRGB.
236    ///
237    /// Use an ICC profile for more accurate results.
238    pub fn srgb(self) {
239        self.cal_rgb(
240            CIE_D65,
241            None,
242            Some([2.2, 2.2, 2.2]),
243            Some([0.4124, 0.2126, 0.0193, 0.3576, 0.715, 0.1192, 0.1805, 0.0722, 0.9505]),
244        )
245    }
246
247    /// Write a `CalRGB` color space approximating Adobe RGB.
248    ///
249    /// Use an ICC profile for more accurate results.
250    pub fn adobe_rgb(self) {
251        self.cal_rgb(
252            CIE_D65,
253            None,
254            Some([2.1992188, 2.1992188, 2.1992188]),
255            Some([
256                0.57667, 0.29734, 0.02703, 0.18556, 0.62736, 0.07069, 0.18823, 0.07529,
257                0.99134,
258            ]),
259        )
260    }
261
262    /// Write a `CalRGB` color space approximating Display P3.
263    ///
264    /// Use an ICC profile for more accurate results.
265    pub fn display_p3(self) {
266        self.cal_rgb(
267            CIE_D65,
268            None,
269            Some([2.6, 2.6, 2.6]),
270            Some([
271                0.48657, 0.2297, 0.0, 0.26567, 0.69174, 0.04511, 0.19822, 0.07929,
272                1.04394,
273            ]),
274        )
275    }
276
277    /// Write a `CalRGB` color space approximating ProPhoto.
278    ///
279    /// Use an ICC profile for more accurate results.
280    pub fn pro_photo(self) {
281        self.cal_rgb(
282            CIE_D50,
283            None,
284            Some([1.8, 1.8, 1.8]),
285            Some([
286                0.7976749, 0.2880402, 0.0, 0.1351917, 0.7118741, 0.0, 0.0313534,
287                0.0000857, 0.82521,
288            ]),
289        )
290    }
291
292    /// Write a `CalRGB` color space for ECI RGB v1.
293    pub fn eci_rgb(self) {
294        self.cal_rgb(
295            CIE_D50,
296            None,
297            Some([1.8, 1.8, 1.8]),
298            Some([
299                0.6502043, 0.3202499, 0.0, 0.1780774, 0.6020711, 0.0678390, 0.1359384,
300                0.0776791, 0.757371,
301            ]),
302        )
303    }
304
305    /// Write a `CalRGB` color space for NTSC RGB.
306    pub fn ntsc(self) {
307        self.cal_rgb(
308            CIE_C,
309            None,
310            Some([2.2, 2.2, 2.2]),
311            Some([
312                0.6068909, 0.2989164, 0.0, 0.1735011, 0.586599, 0.0660957, 0.200348,
313                0.1144845, 1.1162243,
314            ]),
315        )
316    }
317
318    /// Write a `CalRGB` color space for PAL/SECAM RGB.
319    pub fn pal(self) {
320        self.cal_rgb(
321            CIE_D65,
322            None,
323            Some([2.2, 2.2, 2.2]),
324            Some([
325                0.430619, 0.2220379, 0.0201853, 0.3415419, 0.7066384, 0.1295504,
326                0.1783091, 0.0713236, 0.9390944,
327            ]),
328        )
329    }
330
331    /// Write a `CalGray` color space for CIE D65 at a 2.2 gamma, equivalent to
332    /// sRGB, Adobe RGB, Display P3, PAL, ...
333    pub fn d65_gray(self) {
334        self.cal_gray(CIE_D65, None, Some(2.2))
335    }
336
337    /// Write a `CalGray` color space for CIE D50 (horizon light). Set a 1.8
338    /// gamma for ProPhoto or ECI RGB equivalency, 2.2 is another common value.
339    pub fn d50_gray(self, gamma: Option<f32>) {
340        self.cal_gray(CIE_D50, None, gamma)
341    }
342
343    /// Write a `CalGray` color space for CIE C (north sky daylight) at 2.2
344    /// gamma, equivalent to NTSC.
345    pub fn c_gray(self) {
346        self.cal_gray(CIE_C, None, Some(2.2))
347    }
348
349    /// Write a `CalGray` color space for CIE E (equal emission). Common gamma
350    /// values include 1.8 or 2.2.
351    pub fn e_gray(self, gamma: Option<f32>) {
352        self.cal_gray(CIE_E, None, gamma)
353    }
354}
355
356/// Device color spaces.
357///
358/// Please note that the use of the device color spaces is restricted by several
359/// PDF standards such as PDF/A, PDF/X, et cetera. Their appearance will be
360/// governed by any applicable [output intent](crate::writers::OutputIntent) and
361/// default color spaces.
362impl ColorSpace<'_> {
363    /// Write a `DeviceRGB` color space.
364    pub fn device_rgb(self) {
365        self.obj.primitive(ColorSpaceType::DeviceRgb.to_name());
366    }
367
368    /// Write a `DeviceCMYK` color space.
369    pub fn device_cmyk(self) {
370        self.obj.primitive(ColorSpaceType::DeviceCmyk.to_name());
371    }
372
373    /// Write a `DeviceGray` color space.
374    pub fn device_gray(self) {
375        self.obj.primitive(ColorSpaceType::DeviceGray.to_name());
376    }
377}
378
379/// Special color spaces.
380impl<'a> ColorSpace<'a> {
381    /// Start writing a `Separation` color space. PDF 1.2+.
382    ///
383    /// The `color_name` argument is the name of the colorant that will be
384    /// used by the printer.
385    pub fn separation(self, color_name: Name) -> Separation<'a> {
386        let mut array = self.obj.array();
387        array.item(ColorSpaceType::Separation.to_name());
388        array.item(color_name);
389        Separation::start(array)
390    }
391
392    /// Write a `DeviceN` color space. PDF 1.3+.
393    ///
394    /// The `names` argument contains the N names of the colorants for the
395    /// respective components.
396    pub fn device_n<'n>(self, names: impl IntoIterator<Item = Name<'n>>) -> DeviceN<'a> {
397        let mut array = self.obj.array();
398        array.item(ColorSpaceType::DeviceN.to_name());
399        array.push().array().items(names);
400        DeviceN::start(array)
401    }
402
403    /// Write an `Indexed` color space. PDF 1.2+.
404    ///
405    /// The length of the lookup slice must be the product of the dimensions of
406    /// the base color space and (`hival + 1`) and `hival` shall be at most 255.
407    pub fn indexed(self, base: Name, hival: i32, lookup: &[u8]) {
408        let mut array = self.obj.array();
409        array.item(ColorSpaceType::Indexed.to_name());
410        array.item(base);
411        array.item(hival);
412        array.item(Str(lookup));
413    }
414
415    /// Write a `Pattern` color space for uncolored patterns. PDF 1.2+.
416    ///
417    /// The `base` attribute is the color space in which the pattern's
418    /// [tint](Content::set_stroke_pattern) color is specified upon use.
419    pub fn pattern(self, base: Name) {
420        let mut array = self.obj.array();
421        array.item(ColorSpaceType::Pattern.to_name());
422        array.item(base);
423    }
424}
425
426/// Type of pattern.
427#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
428enum PatternType {
429    /// A tiling pattern.
430    Tiling,
431    /// A shading pattern.
432    Shading,
433}
434
435impl PatternType {
436    pub(crate) fn to_int(self) -> i32 {
437        match self {
438            Self::Tiling => 1,
439            Self::Shading => 2,
440        }
441    }
442}
443
444/// Writer for a _separation dictionary_. PDF 1.2+.
445///
446/// First, one of the `alternate_...` methods must be called to specify the
447/// alternate color space. Then, one of the `tint_...` methods must be called
448/// to specify the tint transform method. If the tint transform method is
449/// called before the alternate color space, the function panics. If multiple
450/// alternate color space functions are called, the function panics.
451///
452/// This struct is created by [`ColorSpace::separation`].
453pub struct Separation<'a> {
454    array: Array<'a>,
455    has_alternate: bool,
456}
457
458impl<'a> Separation<'a> {
459    /// Start the wrapper.
460    pub(crate) fn start(array: Array<'a>) -> Self {
461        Self { array, has_alternate: false }
462    }
463
464    /// Write the `alternateSpace` element as a device color space.
465    pub fn alternate_device(&mut self, device_space: DeviceColorSpace) -> &mut Self {
466        if self.has_alternate {
467            panic!("alternate space already specified");
468        }
469        self.array.item(device_space.to_name());
470        self.has_alternate = true;
471        self
472    }
473
474    /// Start writing the `alternateSpace` element as a color space array. The
475    /// color space must not be another `Pattern`, `Separation`, or `DeviceN`
476    /// color space.
477    pub fn alternate_color_space(&mut self) -> ColorSpace<'_> {
478        if self.has_alternate {
479            panic!("alternate space already specified");
480        }
481        self.has_alternate = true;
482        ColorSpace::start(self.array.push())
483    }
484
485    /// Write the `alternateSpace` element as an indirect reference. The color
486    /// space must not be another `Pattern`, `Separation`, or `DeviceN` color
487    /// space.
488    pub fn alternate_color_space_ref(&mut self, id: Ref) -> &mut Self {
489        if self.has_alternate {
490            panic!("alternate space already specified");
491        }
492        self.array.item(id);
493        self.has_alternate = true;
494        self
495    }
496
497    /// Start writing the `tintTransform` element as an exponential
498    /// interpolation function.
499    pub fn tint_exponential(&mut self) -> ExponentialFunction<'_> {
500        if !self.has_alternate {
501            panic!("alternate space must be specified before tint transform");
502        }
503        ExponentialFunction::start(self.array.push())
504    }
505
506    /// Start writing the `tintTransform` element as a stitching function.
507    pub fn tint_stitching(&mut self) -> StitchingFunction<'_> {
508        if !self.has_alternate {
509            panic!("alternate space must be specified before tint transform");
510        }
511        StitchingFunction::start(self.array.push())
512    }
513
514    /// Write the `tintTransform` element as an indirect reference to a
515    /// function. The function must take a single number as input and produce a
516    /// color in the alternate color space as output. This must be used if a
517    /// stream function like [`SampledFunction`] or [`PostScriptFunction`] is
518    /// used.
519    pub fn tint_ref(&mut self, id: Ref) -> &mut Self {
520        if !self.has_alternate {
521            panic!("alternate space must be specified before tint transform");
522        }
523        self.array.item(id);
524        self
525    }
526}
527
528/// Writer for a _DeviceN color space array with attributes_. PDF 1.6+.
529///
530/// First, one of the `alternate_...` methods must be called to specify the
531/// alternate color space. Then, one of the `tint_...` methods must be called to
532/// specify the tint transform method. Finally, the `attrs` function may
533/// optionally be called. If any function is called out of order, the function
534/// panics.
535///
536/// This struct is created by [`ColorSpace::device_n`].
537pub struct DeviceN<'a> {
538    array: Array<'a>,
539    has_alternate: bool,
540    has_tint: bool,
541}
542
543impl<'a> DeviceN<'a> {
544    /// Start the wrapper.
545    pub(crate) fn start(array: Array<'a>) -> Self {
546        Self { array, has_alternate: false, has_tint: false }
547    }
548
549    /// Write the `alternateSpace` element as a device color space.
550    pub fn alternate_device(&mut self, device_space: DeviceColorSpace) -> &mut Self {
551        if self.has_alternate {
552            panic!("alternate space already specified");
553        }
554        self.array.item(device_space.to_name());
555        self.has_alternate = true;
556        self
557    }
558
559    /// Start writing the `alternateSpace` element as a color space array. The
560    /// color space must not be another `Pattern`, `Separation`, or `DeviceN`
561    /// color space.
562    pub fn alternate_color_space(&mut self) -> ColorSpace<'_> {
563        if self.has_alternate {
564            panic!("alternate space already specified");
565        }
566        self.has_alternate = true;
567        ColorSpace::start(self.array.push())
568    }
569
570    /// Write the `alternateSpace` element as an indirect reference. The color
571    /// space must not be another `Pattern`, `Separation`, or `DeviceN` color
572    /// space.
573    pub fn alternate_color_space_ref(&mut self, id: Ref) -> &mut Self {
574        if self.has_alternate {
575            panic!("alternate space already specified");
576        }
577        self.array.item(id);
578        self.has_alternate = true;
579        self
580    }
581
582    /// Start writing the `tintTransform` element as an exponential
583    /// interpolation function.
584    pub fn tint_exponential(&mut self) -> ExponentialFunction<'_> {
585        if !self.has_alternate {
586            panic!("alternate space must be specified before tint transform");
587        } else if self.has_tint {
588            panic!("tint transform already specified");
589        }
590
591        self.has_tint = true;
592        ExponentialFunction::start(self.array.push())
593    }
594
595    /// Start writing the `tintTransform` element as a stitching function.
596    pub fn tint_stitching(&mut self) -> StitchingFunction<'_> {
597        if !self.has_alternate {
598            panic!("alternate space must be specified before tint transform");
599        } else if self.has_tint {
600            panic!("tint transform already specified");
601        }
602
603        self.has_tint = true;
604        StitchingFunction::start(self.array.push())
605    }
606
607    /// Write the `tintTransform` element as an indirect reference to a
608    /// function. The function must take n numbers as input and produce a color
609    /// in the alternate color space as output. This must be used if a stream
610    /// function like [`SampledFunction`] or [`PostScriptFunction`] is used.
611    pub fn tint_ref(&mut self, id: Ref) -> &mut Self {
612        if !self.has_alternate {
613            panic!("alternate space must be specified before tint transform");
614        } else if self.has_tint {
615            panic!("tint transform already specified");
616        }
617
618        self.array.item(id);
619        self.has_tint = true;
620        self
621    }
622
623    /// Start writing the `attrs` dictionary. PDF 1.6+.
624    pub fn attrs(&mut self) -> DeviceNAttrs<'_> {
625        if !self.has_alternate {
626            panic!(
627                "alternate space and tint transform must be specified before attributes"
628            );
629        } else if !self.has_tint {
630            panic!("tint transform must be specified before attributes");
631        }
632
633        DeviceNAttrs::start(self.array.push())
634    }
635}
636
637/// Writer for a _DeviceN attributes dictionary_. PDF 1.6+.
638///
639/// This struct is created by [`DeviceN::attrs`].
640pub struct DeviceNAttrs<'a> {
641    dict: Dict<'a>,
642}
643
644writer!(DeviceNAttrs: |obj| Self { dict: obj.dict() });
645
646impl DeviceNAttrs<'_> {
647    /// Write the `/Subtype` attribute.
648    pub fn subtype(&mut self, subtype: DeviceNSubtype) -> &mut Self {
649        self.dict.pair(Name(b"Subtype"), subtype.to_name());
650        self
651    }
652
653    /// Start writing the `/Colorants` dictionary. Its keys are the colorant
654    /// names and its values are separation color space arrays.
655    ///
656    /// Required if the `/Subtype` attribute is `NChannel`. Required for spot
657    /// colors in PDF/A-2, PDF/A-3, and PDF/A-4.
658    pub fn colorants(&mut self) -> TypedDict<'_, Dict<'_>> {
659        self.dict.insert(Name(b"Colorants")).dict().typed()
660    }
661
662    /// Start writing the `/Process` dictionary.
663    ///
664    /// Required if the `/Subtype` attribute is `Separation`.
665    pub fn process(&mut self) -> DeviceNProcess<'_> {
666        DeviceNProcess::start(self.dict.insert(Name(b"Process")))
667    }
668
669    /// Start writing the `/MixingHints` dictionary.
670    pub fn mixing_hints(&mut self) -> DeviceNMixingHints<'_> {
671        DeviceNMixingHints::start(self.dict.insert(Name(b"MixingHints")))
672    }
673}
674
675/// Writer for a _DeviceN process dictionary_. PDF 1.6+.
676///
677/// This struct is created by [`DeviceNAttrs::process`].
678pub struct DeviceNProcess<'a> {
679    dict: Dict<'a>,
680}
681
682writer!(DeviceNProcess: |obj| Self { dict: obj.dict() });
683
684impl DeviceNProcess<'_> {
685    /// Write the `/ColorSpace` attribute with a name. Required.
686    pub fn color_space(&mut self, color_space: Name) -> &mut Self {
687        self.dict.pair(Name(b"ColorSpace"), color_space);
688        self
689    }
690
691    /// Write the `/ColorSpace` attribute with an array. Required.
692    pub fn color_space_array(&mut self) -> ColorSpace<'_> {
693        ColorSpace::start(self.dict.insert(Name(b"ColorSpace")))
694    }
695
696    /// Write the `/Components` attribute. Required.
697    ///
698    /// Contains the names of the colorants in the order in which they appear in
699    /// the color space array.
700    pub fn components<'n>(
701        &mut self,
702        components: impl IntoIterator<Item = Name<'n>>,
703    ) -> &mut Self {
704        self.dict
705            .insert(Name(b"Components"))
706            .array()
707            .typed()
708            .items(components);
709        self
710    }
711}
712
713/// Type of n-dimensional color space.
714pub enum DeviceNSubtype {
715    /// A subtractive color space.
716    DeviceN,
717    /// An additive color space.
718    NChannel,
719}
720
721impl DeviceNSubtype {
722    pub(crate) fn to_name(self) -> Name<'static> {
723        match self {
724            Self::DeviceN => Name(b"DeviceN"),
725            Self::NChannel => Name(b"NChannel"),
726        }
727    }
728}
729
730/// Writer for a _DeviceN mixing hints dictionary_. PDF 1.6+.
731///
732/// This struct is created by [`DeviceNAttrs::mixing_hints`].
733pub struct DeviceNMixingHints<'a> {
734    dict: Dict<'a>,
735}
736
737writer!(DeviceNMixingHints: |obj| Self { dict: obj.dict() });
738
739impl DeviceNMixingHints<'_> {
740    /// Start writing the `/Solidities` dictionary.
741    ///
742    /// Each key in the dictionary is a colorant name and each value is a number
743    /// between 0 and 1 indicating the relative solidity of the colorant.
744    pub fn solidities(&mut self) -> TypedDict<'_, f32> {
745        self.dict.insert(Name(b"Solidities")).dict().typed()
746    }
747
748    /// Write the `/PrintingOrder` attribute.
749    ///
750    /// Required if `/Solidities` is present. An array of colorant names in the
751    /// order in which they should be printed.
752    pub fn printing_order<'n>(
753        &mut self,
754        order: impl IntoIterator<Item = Name<'n>>,
755    ) -> &mut Self {
756        self.dict.insert(Name(b"PrintingOrder")).array().typed().items(order);
757        self
758    }
759
760    /// Start writing the `/DotGain` dictionary.
761    ///
762    /// Each key in the dictionary is a colorant name and each value is a number
763    /// between 0 and 1 indicating the dot gain of the colorant.
764    pub fn dot_gain(&mut self) -> TypedDict<'_, f32> {
765        self.dict.insert(Name(b"DotGain")).dict().typed()
766    }
767}
768
769/// Writer for a _tiling pattern stream_.
770///
771/// This struct is created by [`Chunk::tiling_pattern`].
772pub struct TilingPattern<'a> {
773    stream: Stream<'a>,
774}
775
776impl<'a> TilingPattern<'a> {
777    pub(crate) fn start_with_stream(mut stream: Stream<'a>) -> Self {
778        stream.pair(Name(b"Type"), Name(b"Pattern"));
779        stream.pair(Name(b"PatternType"), PatternType::Tiling.to_int());
780        Self { stream }
781    }
782
783    /// Write the `/PaintType` attribute.
784    ///
785    /// Sets whether to use external or stream color. Required.
786    pub fn paint_type(&mut self, paint_type: PaintType) -> &mut Self {
787        self.stream.pair(Name(b"PaintType"), paint_type.to_int());
788        self
789    }
790
791    /// Write the `/TilingType` attribute.
792    ///
793    /// Sets how to stretch and space the pattern. Required.
794    pub fn tiling_type(&mut self, tiling_type: TilingType) -> &mut Self {
795        self.stream.pair(Name(b"TilingType"), tiling_type.to_int());
796        self
797    }
798
799    /// Write the `/BBox` attribute.
800    ///
801    /// Sets the bounding box of the pattern in the pattern's coordinate system.
802    /// Required.
803    pub fn bbox(&mut self, bbox: Rect) -> &mut Self {
804        self.stream.pair(Name(b"BBox"), bbox);
805        self
806    }
807
808    /// Write the `/XStep` attribute.
809    ///
810    /// Sets the horizontal spacing between pattern cells. Required.
811    ///
812    /// Panics if `x_step` is zero.
813    pub fn x_step(&mut self, x_step: f32) -> &mut Self {
814        assert!(x_step != 0.0, "x step must not be zero");
815        self.stream.pair(Name(b"XStep"), x_step);
816        self
817    }
818
819    /// Write the `/YStep` attribute.
820    ///
821    /// Sets the vertical spacing between pattern cells. Required.
822    ///
823    /// Panics if `y_step` is zero.
824    pub fn y_step(&mut self, y_step: f32) -> &mut Self {
825        assert!(y_step != 0.0, "y step must not be zero");
826        self.stream.pair(Name(b"YStep"), y_step);
827        self
828    }
829
830    /// Start writing the `/Resources` dictionary.
831    ///
832    /// Sets the resources used by the pattern. Required.
833    pub fn resources(&mut self) -> Resources<'_> {
834        self.insert(Name(b"Resources")).start()
835    }
836
837    /// Write the `/Matrix` attribute.
838    ///
839    /// Maps the pattern coordinate system to the parent content stream
840    /// coordinates. The default is the identity matrix.
841    pub fn matrix(&mut self, matrix: [f32; 6]) -> &mut Self {
842        self.stream.insert(Name(b"Matrix")).array().items(matrix);
843        self
844    }
845}
846
847deref!('a, TilingPattern<'a> => Stream<'a>, stream);
848
849/// Type of paint for a tiling pattern.
850#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
851pub enum PaintType {
852    /// Paint the pattern with the colors specified in the stream.
853    Colored,
854    /// Paint the pattern with the colors active when the pattern was painted.
855    Uncolored,
856}
857
858impl PaintType {
859    pub(crate) fn to_int(self) -> i32 {
860        match self {
861            Self::Colored => 1,
862            Self::Uncolored => 2,
863        }
864    }
865}
866
867/// How to adjust tile spacing.
868#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
869pub enum TilingType {
870    /// Constant space between each tile, tiles may be distorted by 1px.
871    ConstantSpacing,
872    /// Tile size is constant, spacing between may vary by 1px.
873    NoDistortion,
874    /// Constant space between each tile and faster drawing, tiles may be distorted.
875    FastConstantSpacing,
876}
877
878impl TilingType {
879    pub(crate) fn to_int(self) -> i32 {
880        match self {
881            Self::ConstantSpacing => 1,
882            Self::NoDistortion => 2,
883            Self::FastConstantSpacing => 3,
884        }
885    }
886}
887
888/// Writer for a _shading pattern dictionary_. PDF 1.3+.
889///
890/// This struct is created by [`Chunk::shading_pattern`].
891pub struct ShadingPattern<'a> {
892    dict: Dict<'a>,
893}
894
895writer!(ShadingPattern: |obj| {
896    let mut dict = obj.dict();
897    dict.pair(Name(b"Type"), Name(b"Pattern"));
898    dict.pair(Name(b"PatternType"), PatternType::Shading.to_int());
899    Self { dict }
900});
901
902impl ShadingPattern<'_> {
903    /// Start writing the `/Shading` dictionary for a type 1, 2, or 3 shading.
904    pub fn function_shading(&mut self) -> FunctionShading<'_> {
905        self.dict.insert(Name(b"Shading")).start()
906    }
907
908    /// Add an indirect reference to a `/Shading` dictionary or a shading stream.
909    pub fn shading_ref(&mut self, id: Ref) -> &mut Self {
910        self.dict.pair(Name(b"Shading"), id);
911        self
912    }
913
914    /// Write the `/Matrix` attribute.
915    ///
916    /// Sets the matrix to use for the pattern. Defaults to the identity matrix.
917    pub fn matrix(&mut self, matrix: [f32; 6]) -> &mut Self {
918        self.dict.insert(Name(b"Matrix")).array().items(matrix);
919        self
920    }
921
922    /// Start writing the `/ExtGState` attribute.
923    pub fn ext_graphics(&mut self) -> ExtGraphicsState<'_> {
924        self.dict.insert(Name(b"ExtGState")).start()
925    }
926}
927
928deref!('a, ShadingPattern<'a> => Dict< 'a>, dict);
929
930/// Writer for a _shading dictionary_. PDF 1.3+.
931///
932/// This struct is created by [`Chunk::function_shading`] and
933/// [`ShadingPattern::function_shading`].
934pub struct FunctionShading<'a> {
935    dict: Dict<'a>,
936}
937
938writer!(FunctionShading: |obj| Self { dict: obj.dict() });
939
940impl FunctionShading<'_> {
941    /// Write the `/ShadingType` attribute.
942    ///
943    /// Sets the type of shading. The available and required attributes change
944    /// depending on this. Required.
945    pub fn shading_type(&mut self, kind: FunctionShadingType) -> &mut Self {
946        self.dict.pair(Name(b"ShadingType"), kind.to_int());
947        self
948    }
949
950    /// Start writing the `/ColorSpace` attribute.
951    ///
952    /// Sets the color space of the shading function. May not be a `Pattern`
953    /// space. Required.
954    pub fn color_space(&mut self) -> ColorSpace<'_> {
955        self.dict.insert(Name(b"ColorSpace")).start()
956    }
957
958    /// Write the `/Background` attribute.
959    ///
960    /// Sets the background color of the area to be shaded. The `background`
961    /// iterator must contain exactly as many elements as the current
962    /// color space has dimensions.
963    pub fn background(&mut self, background: impl IntoIterator<Item = f32>) -> &mut Self {
964        self.dict.insert(Name(b"Background")).array().items(background);
965        self
966    }
967
968    /// Write the `/BBox` attribute.
969    ///
970    /// Sets the bounding box of the shading in the target coordinate system.
971    pub fn bbox(&mut self, bbox: Rect) -> &mut Self {
972        self.dict.pair(Name(b"BBox"), bbox);
973        self
974    }
975
976    /// Write the `/AntiAlias` attribute.
977    ///
978    /// Sets whether to anti-alias the shading.
979    pub fn anti_alias(&mut self, anti_alias: bool) -> &mut Self {
980        self.dict.pair(Name(b"AntiAlias"), anti_alias);
981        self
982    }
983
984    /// Write the `/Domain` attribute.
985    ///
986    /// Sets the domain of the shading function in a rectangle. Can be used for
987    /// function, axial, or radial shadings. Will otherwise default to
988    /// `[x_min = 0, x_max = 1, y_min = 0, y_max = 1]`
989    pub fn domain(&mut self, domain: [f32; 4]) -> &mut Self {
990        self.dict.insert(Name(b"Domain")).array().items(domain);
991        self
992    }
993
994    /// Write the `/Matrix` attribute.
995    ///
996    /// Maps the shading domain rectangle to the target coordinate system. Can
997    /// be used for function shadings. Will otherwise
998    /// default to the identity matrix.
999    pub fn matrix(&mut self, matrix: [f32; 6]) -> &mut Self {
1000        self.dict.insert(Name(b"Matrix")).array().items(matrix);
1001        self
1002    }
1003
1004    /// Write the `/Function` attribute.
1005    ///
1006    /// Sets the function to use for shading. Number of in- and outputs depends
1007    /// on shading type. Required for type 1, 2, and 3, optional otherwise.
1008    pub fn function(&mut self, function: Ref) -> &mut Self {
1009        self.dict.pair(Name(b"Function"), function);
1010        self
1011    }
1012
1013    /// Write the `/Coords` attribute.
1014    ///
1015    /// Sets the coordinates of the start and end of the axis in terms of the
1016    /// target coordinate system. Required for axial (4 items) and radial (6
1017    /// items; centers and radii) shadings.
1018    pub fn coords(&mut self, coords: impl IntoIterator<Item = f32>) -> &mut Self {
1019        self.dict.insert(Name(b"Coords")).array().items(coords);
1020        self
1021    }
1022
1023    /// Write the `/Extend` attribute.
1024    ///
1025    /// Set whether the shading should extend beyond either side of the axis /
1026    /// circles. Can be used for axial and radial shadings.
1027    pub fn extend(&mut self, extend: [bool; 2]) -> &mut Self {
1028        self.dict.insert(Name(b"Extend")).array().items(extend);
1029        self
1030    }
1031}
1032
1033deref!('a, FunctionShading<'a> => Dict<'a>, dict);
1034
1035/// Writer for a _embedded file stream_.
1036///
1037/// This struct is created by [`Chunk::stream_shading`].
1038pub struct StreamShading<'a> {
1039    stream: Stream<'a>,
1040}
1041
1042impl<'a> StreamShading<'a> {
1043    /// Create a new character map writer.
1044    pub(crate) fn start(stream: Stream<'a>) -> Self {
1045        Self { stream }
1046    }
1047
1048    /// Write the `/ShadingType` attribute.
1049    ///
1050    /// Sets the type of shading. The available and required attributes change
1051    /// depending on this. Required.
1052    pub fn shading_type(&mut self, kind: StreamShadingType) -> &mut Self {
1053        self.stream.pair(Name(b"ShadingType"), kind.to_int());
1054        self
1055    }
1056
1057    /// Start writing the `/ColorSpace` attribute.
1058    ///
1059    /// Sets the color space of the shading function. May not be a `Pattern`
1060    /// space. Required.
1061    pub fn color_space(&mut self) -> ColorSpace<'_> {
1062        self.stream.insert(Name(b"ColorSpace")).start()
1063    }
1064
1065    /// Write the `/Background` attribute.
1066    ///
1067    /// Sets the background color of the area to be shaded. The `background`
1068    /// iterator must contain exactly as many elements as the current
1069    /// color space has dimensions.
1070    pub fn background(&mut self, background: impl IntoIterator<Item = f32>) -> &mut Self {
1071        self.stream.insert(Name(b"Background")).array().items(background);
1072        self
1073    }
1074
1075    /// Write the `/BBox` attribute.
1076    ///
1077    /// Sets the bounding box of the shading in the target coordinate system.
1078    pub fn bbox(&mut self, bbox: Rect) -> &mut Self {
1079        self.stream.pair(Name(b"BBox"), bbox);
1080        self
1081    }
1082
1083    /// Write the `/AntiAlias` attribute.
1084    ///
1085    /// Sets whether to anti-alias the shading.
1086    pub fn anti_alias(&mut self, anti_alias: bool) -> &mut Self {
1087        self.stream.pair(Name(b"AntiAlias"), anti_alias);
1088        self
1089    }
1090
1091    /// Write the `/Function` attribute.
1092    ///
1093    /// Sets the function to use for shading. Number of in- and outputs depends
1094    /// on shading type. Optional.
1095    pub fn function(&mut self, function: Ref) -> &mut Self {
1096        self.stream.pair(Name(b"Function"), function);
1097        self
1098    }
1099
1100    /// Write the `/BitsPerCoordinate` attribute.
1101    ///
1102    /// Sets how many bits are used to represent each vertex coordinate. Can be
1103    /// any power of 2 between 1 and 32. Required.
1104    pub fn bits_per_coordinate(&mut self, bits: i32) -> &mut Self {
1105        self.stream.pair(Name(b"BitsPerCoordinate"), bits);
1106        self
1107    }
1108
1109    /// Write the `/BitsPerComponent` attribute.
1110    ///
1111    /// Sets how many bits are used to represent each color component. Can be
1112    /// any power of 2 between 1 and 16. Required.
1113    pub fn bits_per_component(&mut self, bits: i32) -> &mut Self {
1114        self.stream.pair(Name(b"BitsPerComponent"), bits);
1115        self
1116    }
1117
1118    /// Write the `/BitsPerFlag` attribute.
1119    ///
1120    /// Sets how many bits are used to represent the vertices' edge flags. Can
1121    /// be 0, 1, or 2. Required for type 4, 6, and 7.
1122    pub fn bits_per_flag(&mut self, bits: i32) -> &mut Self {
1123        self.stream.pair(Name(b"BitsPerFlag"), bits);
1124        self
1125    }
1126
1127    /// Write the `/Decode` attribute.
1128    ///
1129    /// Sets the ranges of the vertices' coordinates. Required.
1130    pub fn decode(&mut self, decode: impl IntoIterator<Item = f32>) -> &mut Self {
1131        self.stream.insert(Name(b"Decode")).array().items(decode);
1132        self
1133    }
1134
1135    /// Write the `/VerticesPerRow` attribute.
1136    ///
1137    /// Sets how many vertices are in each row of the lattice. Must be greater
1138    /// than 2. Required for type 5.
1139    pub fn vertices_per_row(&mut self, vertices: i32) -> &mut Self {
1140        self.stream.pair(Name(b"VerticesPerRow"), vertices);
1141        self
1142    }
1143}
1144
1145deref!('a, StreamShading<'a> => Stream<'a>, stream);
1146
1147/// What kind of shading to use for a function-based shading.
1148#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1149pub enum FunctionShadingType {
1150    /// The function specifies the color for each point in the domain.
1151    Function,
1152    /// The function specifies the color for each point on a line.
1153    Axial,
1154    /// The function specifies the color for each circle between two nested
1155    /// circles.
1156    Radial,
1157}
1158
1159impl FunctionShadingType {
1160    pub(crate) fn to_int(self) -> i32 {
1161        match self {
1162            Self::Function => 1,
1163            Self::Axial => 2,
1164            Self::Radial => 3,
1165        }
1166    }
1167}
1168
1169/// What kind of shading to use.
1170#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1171pub enum StreamShadingType {
1172    /// The function specifies which vertex has which color. The function is
1173    /// optional.
1174    FreeformGouraud,
1175    /// The function specifies which vertex has which color. The function is
1176    /// optional.
1177    LatticeGouraud,
1178    /// The function specifies which corner of the cell has which color. The
1179    /// function is optional.
1180    CoonsPatch,
1181    /// The function specifies which corner of the cell has which color. The
1182    /// function is optional.
1183    TensorProductPatch,
1184}
1185
1186impl StreamShadingType {
1187    pub(crate) fn to_int(self) -> i32 {
1188        match self {
1189            Self::FreeformGouraud => 4,
1190            Self::LatticeGouraud => 5,
1191            Self::CoonsPatch => 6,
1192            Self::TensorProductPatch => 7,
1193        }
1194    }
1195}
1196
1197/// Writer for the _separation information dictionary_. PDF 1.3+.
1198///
1199/// This struct is created by [`Catalog::separation_info`].
1200pub struct SeparationInfo<'a> {
1201    dict: Dict<'a>,
1202}
1203
1204writer!(SeparationInfo: |obj| Self { dict: obj.dict() });
1205
1206impl SeparationInfo<'_> {
1207    /// Write the `/Pages` attribute. Required.
1208    ///
1209    /// This indicates all page dictionaries in the document that represent
1210    /// separations of the same page and shall be rendered together.
1211    pub fn pages(&mut self, pages: impl IntoIterator<Item = Ref>) -> &mut Self {
1212        self.dict.insert(Name(b"Pages")).array().typed().items(pages);
1213        self
1214    }
1215
1216    /// Write the `/DeviceColorant` attribute as a name. Required.
1217    ///
1218    /// The name of the device colorant that corresponds to the separation.
1219    pub fn device_colorant(&mut self, colorant: Name) -> &mut Self {
1220        self.dict.pair(Name(b"DeviceColorant"), colorant);
1221        self
1222    }
1223
1224    /// Write the `/DeviceColorant` attribute as a string. Required.
1225    ///
1226    /// The name of the device colorant that corresponds to the separation.
1227    pub fn device_colorant_str(&mut self, colorant: &str) -> &mut Self {
1228        self.dict.pair(Name(b"DeviceColorant"), TextStr(colorant));
1229        self
1230    }
1231
1232    /// Start writing the `/ColorSpace` array.
1233    ///
1234    /// This shall be an Separation or DeviceN color space that further defines
1235    /// the separation color space.
1236    pub fn color_space(&mut self) -> ColorSpace<'_> {
1237        self.dict.insert(Name(b"ColorSpace")).start()
1238    }
1239}
1240
1241/// Writer for an _output intent dictionary_. PDF 1.4+.
1242///
1243/// This describes the output conditions under which the document may be
1244/// rendered. Encouraged by PDF/A.
1245pub struct OutputIntent<'a> {
1246    dict: Dict<'a>,
1247}
1248
1249writer!(OutputIntent: |obj| {
1250    let mut dict = obj.dict();
1251    dict.pair(Name(b"Type"), Name(b"OutputIntent"));
1252    Self { dict }
1253});
1254
1255impl OutputIntent<'_> {
1256    /// Write the `/S` attribute. Required.
1257    pub fn subtype(&mut self, subtype: OutputIntentSubtype) -> &mut Self {
1258        self.dict.pair(Name(b"S"), subtype.to_name());
1259        self
1260    }
1261
1262    /// Write the `/OutputCondition` attribute.
1263    ///
1264    /// A human-readable description of the output condition.
1265    pub fn output_condition(&mut self, condition: impl TextStrLike) -> &mut Self {
1266        self.dict.pair(Name(b"OutputCondition"), condition);
1267        self
1268    }
1269
1270    /// Write the `/OutputConditionIdentifier` attribute.
1271    ///
1272    /// A well-known identifier for the output condition.
1273    pub fn output_condition_identifier(
1274        &mut self,
1275        identifier: impl TextStrLike,
1276    ) -> &mut Self {
1277        self.dict.pair(Name(b"OutputConditionIdentifier"), identifier);
1278        self
1279    }
1280
1281    /// Write the `/RegistryName` attribute.
1282    ///
1283    /// The URI of the registry that contains the output condition.
1284    pub fn registry_name(&mut self, name: TextStr) -> &mut Self {
1285        self.dict.pair(Name(b"RegistryName"), name);
1286        self
1287    }
1288
1289    /// Write the `/Info` attribute.
1290    ///
1291    /// A human-readable string with additional info about the intended output device.
1292    pub fn info(&mut self, info: impl TextStrLike) -> &mut Self {
1293        self.dict.pair(Name(b"Info"), info);
1294        self
1295    }
1296
1297    /// Write the `/DestOutputProfile` attribute.
1298    ///
1299    /// Required if `/OutputConditionIdentifier` does not contain a well-known
1300    /// identifier for the output condition.
1301    /// Must reference an [ICC profile](IccProfile) stream.
1302    ///
1303    /// Required for PDF/A. The profile must have the `prtr` or `mntr` tag.
1304    pub fn dest_output_profile(&mut self, profile: Ref) -> &mut Self {
1305        self.dict.pair(Name(b"DestOutputProfile"), profile);
1306        self
1307    }
1308}
1309
1310/// The output intent subtype.
1311#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1312pub enum OutputIntentSubtype<'a> {
1313    /// `GTS_PDFX`
1314    PDFX,
1315    /// `GTS_PDFA1`
1316    ///
1317    /// This is the right value for PDF/A-1 through PDF/A-4.
1318    PDFA,
1319    /// `ISO_PDFE1`
1320    PDFE,
1321    /// Custom name defined in an ISO 32000 extension.
1322    Custom(Name<'a>),
1323}
1324
1325impl<'a> OutputIntentSubtype<'a> {
1326    pub(crate) fn to_name(self) -> Name<'a> {
1327        match self {
1328            Self::PDFX => Name(b"GTS_PDFX"),
1329            Self::PDFA => Name(b"GTS_PDFA1"),
1330            Self::PDFE => Name(b"ISO_PDFE1"),
1331            Self::Custom(name) => name,
1332        }
1333    }
1334}