pdf_writer/
color.rs

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