Skip to main content

typst_library/layout/
page.rs

1use std::num::NonZeroUsize;
2use std::ops::RangeInclusive;
3use std::str::FromStr;
4
5use typst_utils::{NonZeroExt, Scalar, singleton};
6
7use crate::diag::{HintedStrResult, SourceResult, bail};
8use crate::engine::Engine;
9use crate::foundations::{
10    Args, Cast, CastInfo, Construct, Content, Dict, Fold, FromValue, IntoValue,
11    NativeElement, Reflect, Set, Smart, Value, cast, elem,
12};
13use crate::layout::{
14    Abs, Alignment, FlushElem, HAlignment, Length, OuterVAlignment, Ratio, Rel, Sides,
15    SpecificAlignment,
16};
17use crate::model::Numbering;
18use crate::text::LocalName;
19use crate::visualize::Paint;
20
21/// Layouts its child onto one or multiple pages.
22///
23/// Although this function is primarily used in set rules to affect page
24/// properties, it can also be used to explicitly render its argument onto a set
25/// of pages of its own.
26///
27/// Pages can be set to use `{auto}` as their width or height. In this case, the
28/// pages will grow to fit their content on the respective axis.
29///
30/// The @guides:page-setup[Guide for Page Setup] explains how to use this and
31/// related functions to set up a document with many examples.
32///
33/// = Example <example>
34/// ```example
35/// >>> #set page(margin: auto)
36/// #set page("us-letter")
37///
38/// There you go, US friends!
39/// ```
40///
41/// = Accessibility <accessibility>
42/// The contents of the page's header, footer, foreground, and background are
43/// invisible to Assistive Technology (AT) like screen readers. Only the body of
44/// the page is read by AT. Do not include vital information not included
45/// elsewhere in the document in these areas.
46///
47/// = Styling <styling>
48/// Note that the @page element cannot be targeted by show rules; writing
49/// `{show page: ..}` has no effect. To repeat content on every page, you can
50/// instead configure the @page.header[`header`], @page.footer[`footer`],
51/// @page.background[`background`], and @page.foreground[`foreground`]
52/// properties with a set rule.
53#[elem(Construct)]
54pub struct PageElem {
55    /// A standard paper size to set width and height.
56    ///
57    /// This is just a shorthand for setting `width` and `height` and, as such,
58    /// cannot be retrieved in a context expression.
59    #[external]
60    #[default(Paper::A4)]
61    pub paper: Paper,
62
63    /// The width of the final page, after any trims have been applied.
64    ///
65    /// In professional printing setups, this may be smaller than the sheet size
66    /// fed into the printer.
67    ///
68    /// See the @page.bleed[`bleed`] parameter for details on how to set a trim
69    /// or bleed area.
70    ///
71    /// ```example
72    /// #set page(
73    ///   width: 3cm,
74    ///   margin: (x: 0cm),
75    /// )
76    ///
77    /// #for i in range(3) {
78    ///   box(square(width: 1cm))
79    /// }
80    /// ```
81    #[parse(
82        let paper = args.named_or_find::<Paper>("paper")?;
83        args.named("width")?
84            .or_else(|| paper.map(|paper| Smart::Custom(paper.width().into())))
85    )]
86    #[default(Smart::Custom(Paper::A4.width().into()))]
87    #[ghost]
88    pub width: Smart<Length>,
89
90    /// The height of the final page area, after any trims have been applied.
91    ///
92    /// If this is set to `{auto}`, page breaks can only be triggered manually
93    /// by inserting a @pagebreak[page break] or by adding another non-empty
94    /// page set rule. Most examples throughout this documentation use `{auto}`
95    /// for the height of the page to dynamically grow and shrink to fit their
96    /// content.
97    #[parse(
98        args.named("height")?
99            .or_else(|| paper.map(|paper| Smart::Custom(paper.height().into())))
100    )]
101    #[default(Smart::Custom(Paper::A4.height().into()))]
102    #[ghost]
103    pub height: Smart<Length>,
104
105    /// Whether the page is flipped into landscape orientation.
106    ///
107    /// ```example
108    /// #set page(
109    ///   "us-business-card",
110    ///   flipped: true,
111    ///   fill: rgb("f2e5dd"),
112    /// )
113    ///
114    /// #set align(bottom + end)
115    /// #text(14pt)[*Sam H. Richards*] \
116    /// _Procurement Manager_
117    ///
118    /// #set text(10pt)
119    /// 23 W 23rd Street \
120    /// New York, NY 10010 \
121    /// +1 (212) 555-0155
122    /// ```
123    #[default(false)]
124    #[ghost]
125    pub flipped: bool,
126
127    /// The page's margins.
128    ///
129    /// - `{auto}`: The margins are set automatically to 2.5/21 times the
130    ///   smaller dimension of the page. This results in 2.5 cm margins for an
131    ///   A4 page.
132    /// - A single length: The same margin on all sides.
133    /// - A dictionary: With a dictionary, the margins can be set individually.
134    ///   The dictionary can contain the following keys in order of precedence:
135    ///   - `top`: The top margin.
136    ///   - `right`: The right margin.
137    ///   - `bottom`: The bottom margin.
138    ///   - `left`: The left margin.
139    ///   - `inside`: The margin at the inner side of the page (where the
140    ///     @page.binding[binding] is).
141    ///   - `outside`: The margin at the outer side of the page (opposite to the
142    ///     @page.binding[binding]).
143    ///   - `x`: The horizontal margins.
144    ///   - `y`: The vertical margins.
145    ///   - `rest`: The margins on all sides except those for which the
146    ///     dictionary explicitly sets a size.
147    ///
148    /// All keys are optional; omitted keys will use their previously set value,
149    /// or the default margin if never set. In addition, the values for `left`
150    /// and `right` are mutually exclusive with the values for `inside` and
151    /// `outside`. The values should be relative lengths or `{auto}`.
152    ///
153    /// ```example
154    /// #set page(
155    ///  width: 3cm,
156    ///  height: 4cm,
157    ///  margin: (x: 8pt, y: 4pt),
158    /// )
159    ///
160    /// #rect(
161    ///   width: 100%,
162    ///   height: 100%,
163    ///   fill: aqua,
164    /// )
165    /// ```
166    #[fold]
167    #[ghost]
168    pub margin: Smart<Margin<Smart<Rel<Length>>>>,
169
170    /// The page's bleed margin.
171    ///
172    /// The bleed is the area of content that extends beyond the final trimmed
173    /// size of the page. It ensures that no unprinted edges appear in the final
174    /// product, even if minor trimming misalignments occur.
175    ///
176    /// Accepted values:
177    ///
178    /// - A single length: The same bleed on all sides.
179    /// - A dictionary: With a dictionary, the bleed margins can be set
180    ///   individually. The dictionary may include the following keys, listed in
181    ///   order of precedence:
182    ///   - `top`: The top bleed margin.
183    ///   - `right`: The right bleed margin.
184    ///   - `bottom`: The bottom bleed margin.
185    ///   - `left`: The left bleed margin.
186    ///   - `inside`: The bleed margin at the inner side of the page (where the
187    ///     @page.binding[binding] is).
188    ///   - `outside`: The bleed margin at the outer side of the page (opposite
189    ///     to the @page.binding[binding]).
190    ///   - `x`: The horizontal bleed margins.
191    ///   - `y`: The vertical bleed margins.
192    ///   - `rest`: The bleed margins on all sides except those for which the
193    ///     dictionary explicitly sets a size.
194    ///
195    /// All keys are optional; omitted keys will use their previously set value,
196    /// or `{0pt}` if never set. In addition, the values for `left` and `right`
197    /// are mutually exclusive with the values for `inside` and `outside`. The
198    /// values should be relative lengths.
199    ///
200    /// In PDF export, if the bleed is non-zero, a `TrimBox` is defined for the
201    /// page.
202    ///
203    /// ```example
204    /// #set page(
205    ///   width: 8cm,
206    ///   height: 5cm,
207    ///   margin: 1cm,
208    ///   // The bleed is not visible in the preview;
209    ///   // it exceeds beyond the page.
210    ///   bleed: 0.5cm,
211    ///   // Fills the entire bleed area, so there will
212    ///   // be no white strips after printing and trimming.
213    ///   background: rect(width: 100%, height: 100%, fill: aqua),
214    /// )
215    ///
216    /// #rect(width: 100%, height: 100%, fill: white)
217    /// ```
218    #[ghost]
219    pub bleed: Margin<Rel<Length>>,
220
221    /// On which side the pages will be bound.
222    ///
223    /// - `{auto}`: Equivalent to `left` if the @text.dir[text direction] is
224    ///   left-to-right and `right` if it is right-to-left.
225    /// - `left`: Bound on the left side.
226    /// - `right`: Bound on the right side.
227    ///
228    /// This affects the meaning of the `inside` and `outside` options for
229    /// margins.
230    #[ghost]
231    pub binding: Smart<Binding>,
232
233    /// How many columns the page has.
234    ///
235    /// If you need to insert columns into a page or other container, you can
236    /// also use the @columns[`columns` function].
237    ///
238    /// #example(
239    ///   single: true,
240    ///   ```
241    ///   #set page(columns: 2, height: 4.8cm)
242    ///   Climate change is one of the most
243    ///   pressing issues of our time, with
244    ///   the potential to devastate
245    ///   communities, ecosystems, and
246    ///   economies around the world. It's
247    ///   clear that we need to take urgent
248    ///   action to reduce our carbon
249    ///   emissions and mitigate the impacts
250    ///   of a rapidly changing climate.
251    ///   ```
252    /// )
253    #[default(NonZeroUsize::ONE)]
254    #[ghost]
255    pub columns: NonZeroUsize,
256
257    /// The page's background fill.
258    ///
259    /// Setting this to something non-transparent instructs the printer to color
260    /// the complete page. If you are considering larger production runs, it may
261    /// be more environmentally friendly and cost-effective to source pre-dyed
262    /// pages and not set this property.
263    ///
264    /// When set to `{none}`, the background becomes transparent. Note that PDF
265    /// pages will still appear with a (usually white) background in viewers,
266    /// but they are actually transparent. (If you print them, no color is used
267    /// for the background.)
268    ///
269    /// The default of `{auto}` results in `{none}` for PDF output, and
270    /// `{white}` for PNG and SVG.
271    ///
272    /// ```example
273    /// #set page(fill: rgb("444352"))
274    /// #set text(fill: rgb("fdfdfd"))
275    /// *Dark mode enabled.*
276    /// ```
277    #[ghost]
278    pub fill: Smart<Option<Paint>>,
279
280    /// How to number the pages. You can refer to the Page Setup Guide for
281    /// @guides:page-setup:page-numbers[customizing page numbers].
282    ///
283    /// Accepts a @numbering[numbering pattern or function] taking one or two
284    /// numbers:
285    /// + The first number is the current page number.
286    /// + The second number is the total number of pages. In a numbering
287    ///   pattern, the second number can be omitted. If a function is passed, it
288    ///   will receive one argument in the context of links or references, and
289    ///   two arguments when producing the visible page numbers.
290    ///
291    /// These are logical numbers controlled by the page counter, and may thus
292    /// not match the physical numbers. Specifically, they are the
293    /// @counter.get[current] and the @counter.final[final] value of
294    /// `{counter(page)}`. See the @counter:page-counter[`counter`]
295    /// documentation for more details.
296    ///
297    /// If an explicit @page.footer[`footer`] (or @page.header[`header`] for
298    /// @page.number-align[top-aligned] numbering) is given, the numbering is
299    /// ignored.
300    ///
301    /// ```example
302    /// #set page(
303    ///   height: 100pt,
304    ///   margin: (top: 16pt, bottom: 24pt),
305    ///   numbering: "1 / 1",
306    /// )
307    ///
308    /// #lorem(48)
309    /// ```
310    #[ghost]
311    pub numbering: Option<Numbering>,
312
313    /// A supplement for the pages.
314    ///
315    /// For page references, this is added before the page number.
316    ///
317    /// ```example
318    /// #set page(numbering: "1.", supplement: [p.])
319    ///
320    /// = Introduction <intro>
321    /// We are on #ref(<intro>, form: "page")!
322    /// ```
323    #[ghost]
324    pub supplement: Smart<Option<Content>>,
325
326    /// The alignment of the page numbering.
327    ///
328    /// If the vertical component is `top`, the numbering is placed into the
329    /// header and if it is `bottom`, it is placed in the footer. Horizon
330    /// alignment is forbidden. If an explicit matching `header` or `footer` is
331    /// given, the numbering is ignored.
332    ///
333    /// ```example
334    /// #set page(
335    ///   margin: (top: 16pt, bottom: 24pt),
336    ///   numbering: "1",
337    ///   number-align: right,
338    /// )
339    ///
340    /// #lorem(30)
341    /// ```
342    #[default(SpecificAlignment::Both(HAlignment::Center, OuterVAlignment::Bottom))]
343    #[ghost]
344    pub number_align: SpecificAlignment<HAlignment, OuterVAlignment>,
345
346    /// The page's header. Fills the top margin of each page.
347    ///
348    /// - Content: Shows the content as the header.
349    /// - `{auto}`: Shows the page number if a @page.numbering[`numbering`] is
350    ///   set and @page.number-align[`number-align`] is `top`.
351    /// - `{none}`: Suppresses the header.
352    ///
353    /// ```example
354    /// #set par(justify: true)
355    /// #set page(
356    ///   margin: (top: 32pt, bottom: 20pt),
357    ///   header: [
358    ///     #set text(8pt)
359    ///     #smallcaps[Typst Academy]
360    ///     #h(1fr) _Exercise Sheet 3_
361    ///   ],
362    /// )
363    ///
364    /// #lorem(19)
365    /// ```
366    #[ghost]
367    pub header: Smart<Option<Content>>,
368
369    /// The amount the header is raised into the top margin. Ratios are relative
370    /// to the height of the top margin.
371    #[default(Ratio::new(0.3).into())]
372    #[ghost]
373    pub header_ascent: Rel<Length>,
374
375    /// The page's footer. Fills the bottom margin of each page.
376    ///
377    /// - Content: Shows the content as the footer.
378    /// - `{auto}`: Shows the page number if a @page.numbering[`numbering`] is
379    ///   set and @page.number-align[`number-align`] is `bottom`.
380    /// - `{none}`: Suppresses the footer.
381    ///
382    /// For just a page number, the `numbering` property typically suffices. If
383    /// you want to create a custom footer but still display the page number,
384    /// you can directly access the @counter[page counter].
385    ///
386    /// ```example
387    /// #set par(justify: true)
388    /// #set page(
389    ///   height: 100pt,
390    ///   margin: 20pt,
391    ///   footer: context [
392    ///     #set align(right)
393    ///     #set text(8pt)
394    ///     #counter(page).display(
395    ///       "1 of I",
396    ///       both: true,
397    ///     )
398    ///   ]
399    /// )
400    ///
401    /// #lorem(48)
402    /// ```
403    #[ghost]
404    pub footer: Smart<Option<Content>>,
405
406    /// The amount the footer is lowered into the bottom margin. Ratios are
407    /// relative to the height of the bottom margin.
408    ///
409    /// ```preview
410    /// #set page(
411    ///   height: 126pt,
412    ///   width: 240pt,
413    ///   margin: (top: 0pt, x: 20pt, bottom: 50pt),
414    ///   numbering: (..nums) => box(
415    ///     outset: (x: 50%),
416    ///     fill: orange.lighten(50%),
417    ///   )[6 / 8],
418    ///   footer-descent: 47%,
419    ///   foreground: place(bottom, {
420    ///     let arrow(height) = math.stretch(
421    ///       text(1.4em, sym.arrow.t.b),
422    ///       size: height,
423    ///     )
424    ///     set text(
425    ///       1.2em,
426    ///       purple.darken(10%),
427    ///       bottom-edge: "bounds",
428    ///       top-edge: "bounds",
429    ///     )
430    ///     set par(leading: 0.5em)
431    ///     import grid: cell
432    ///     context grid(
433    ///       align: center + horizon,
434    ///       columns: (37%, 10%, 6%, 47%),
435    ///       cell(rowspan: 2, align: right)[bottom\ margin],
436    ///       cell(rowspan: 2, align: left, arrow(page.margin.bottom)),
437    ///       arrow(page.margin.bottom * page.footer-descent.ratio),
438    ///       cell(align: left)[footer descent],
439    ///     )
440    ///   }),
441    /// )
442    /// #block(width: 100%, height: 100%, fill: green.lighten(75%), {
443    ///   set par(justify: true)
444    ///   set text(luma(25%))
445    ///   place(bottom, lorem(42))
446    /// })
447    /// ```
448    #[default(Ratio::new(0.3).into())]
449    #[ghost]
450    pub footer_descent: Rel<Length>,
451
452    /// Content in the page's background.
453    ///
454    /// This content will be placed behind the page's body. It can be used to
455    /// place a background image or a watermark.
456    ///
457    /// For convenience, @relative[relative lengths] are resolved against the
458    /// page size including the @page.bleed[page `bleed`] when used in
459    /// background content. For example, on a page that is `{100mm}` wide with a
460    /// `{5mm}` bleed, a width of `{100%}` is computed as `{5mm + 100mm + 5mm}`.
461    ///
462    /// ```example
463    /// #set page(background: rotate(24deg,
464    ///   text(18pt, fill: rgb("FFCBC4"))[
465    ///     *CONFIDENTIAL*
466    ///   ]
467    /// ))
468    ///
469    /// = Typst's secret plans
470    /// In the year 2023, we plan to take
471    /// over the world (of typesetting).
472    /// ```
473    #[ghost]
474    pub background: Option<Content>,
475
476    /// Content in the page's foreground.
477    ///
478    /// This content will overlay the page's body.
479    ///
480    /// Relative lengths are resolved against the page size including
481    /// @page.bleed[`bleed`], following the same behavior as
482    /// @page.background[`background`].
483    ///
484    /// ```example
485    /// #set page(foreground: text(24pt)[🤓])
486    ///
487    /// Reviewer 2 has marked our paper
488    /// "Weak Reject" because they did
489    /// not understand our approach...
490    /// ```
491    #[ghost]
492    pub foreground: Option<Content>,
493
494    /// The contents of the page(s).
495    ///
496    /// Multiple pages will be created if the content does not fit on a single
497    /// page. A new page with the page properties prior to the function
498    /// invocation will be created after the body has been typeset.
499    #[external]
500    #[required]
501    pub body: Content,
502}
503
504impl Construct for PageElem {
505    fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content> {
506        // The page constructor is special: It doesn't create a page element.
507        // Instead, it just ensures that the passed content lives in a separate
508        // page and styles it. Because no element node is produced, `show`
509        // rules can't match `page`; use `set` rules instead.
510        let styles = Self::set(engine, args)?;
511        let body = args.expect::<Content>("body")?;
512        Ok(Content::sequence([
513            PagebreakElem::shared_weak().clone(),
514            // We put an effectless, invisible non-tag element on the page.
515            // This has two desirable consequences:
516            // - The page is kept even if the body is empty
517            // - The page doesn't inherit shared styles from the body
518            FlushElem::new().pack(),
519            body,
520            PagebreakElem::shared_boundary().clone(),
521        ])
522        .styled_with_map(styles))
523    }
524}
525
526impl LocalName for PageElem {
527    const KEY: &'static str = "page";
528}
529
530/// A manual page break.
531///
532/// Must not be used inside any containers.
533///
534/// = Example <example>
535/// ```example
536/// The next page contains
537/// more details on compound theory.
538/// #pagebreak()
539///
540/// == Compound Theory
541/// In 1984, the first ...
542/// ```
543///
544/// Even without manual page breaks, content will be automatically paginated
545/// based on the configured page size. You can set @page.height[the page height]
546/// to `{auto}` to let the page grow dynamically until a manual page break
547/// occurs.
548///
549/// Pagination tries to avoid single lines of text at the top or bottom of a
550/// page (these are called _widows_ and _orphans_). You can adjust the
551/// @text.costs parameter to disable this behavior.
552#[elem(title = "Page Break")]
553pub struct PagebreakElem {
554    /// If `{true}`, the page break is skipped if the current page is already
555    /// empty.
556    #[default(false)]
557    pub weak: bool,
558
559    /// If given, ensures that the next page will be an even/odd page, with an
560    /// empty page in between if necessary.
561    ///
562    /// ```example
563    /// #set page(height: 30pt)
564    ///
565    /// First.
566    /// #pagebreak(to: "odd")
567    /// Third.
568    /// ```
569    pub to: Option<Parity>,
570
571    /// Whether this pagebreak designates an end boundary of a page run. This is
572    /// an even weaker version of pagebreak `weak` because it not only doesn't
573    /// force an empty page, but also doesn't force its initial styles onto a
574    /// staged empty page.
575    #[internal]
576    #[parse(None)]
577    #[default(false)]
578    pub boundary: bool,
579}
580
581impl PagebreakElem {
582    /// Get the globally shared weak pagebreak element.
583    pub fn shared_weak() -> &'static Content {
584        singleton!(Content, PagebreakElem::new().with_weak(true).pack())
585    }
586
587    /// Get the globally shared boundary pagebreak element.
588    pub fn shared_boundary() -> &'static Content {
589        singleton!(
590            Content,
591            PagebreakElem::new().with_weak(true).with_boundary(true).pack()
592        )
593    }
594}
595
596/// Specification of the page's margins.
597#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
598pub struct Margin<T: PartialEq> {
599    /// The margins for each side.
600    pub sides: Sides<Option<T>>,
601    /// Whether to swap `left` and `right` to make them `inside` and `outside`
602    /// (when to swap depends on the binding).
603    pub two_sided: Option<bool>,
604}
605
606impl<T: Clone + PartialEq> Margin<T> {
607    /// Create an instance with four equal components.
608    pub fn splat(value: Option<T>) -> Self {
609        Self { sides: Sides::splat(value), two_sided: None }
610    }
611}
612
613impl<T: Fold + PartialEq> Fold for Margin<T> {
614    fn fold(self, outer: Self) -> Self {
615        Margin {
616            sides: self.sides.fold(outer.sides),
617            two_sided: self.two_sided.fold(outer.two_sided),
618        }
619    }
620}
621
622impl<T: Reflect + PartialEq> Reflect for Margin<T> {
623    fn input() -> CastInfo {
624        T::input() + Dict::input()
625    }
626
627    fn output() -> CastInfo {
628        Self::input()
629    }
630
631    fn castable(value: &Value) -> bool {
632        T::castable(value) || Dict::castable(value)
633    }
634}
635
636impl<T: IntoValue + PartialEq> IntoValue for Margin<T> {
637    fn into_value(self) -> Value {
638        let two_sided = self.two_sided.unwrap_or(false);
639        if !two_sided
640            && self.sides.is_uniform()
641            && let Some(left) = self.sides.left
642        {
643            return left.into_value();
644        }
645
646        let mut dict = Dict::new();
647        let mut handle = |key: &str, component: Option<T>| {
648            if let Some(c) = component {
649                dict.insert(key.into(), c.into_value());
650            }
651        };
652
653        handle("top", self.sides.top);
654        handle("bottom", self.sides.bottom);
655        if two_sided {
656            handle("inside", self.sides.left);
657            handle("outside", self.sides.right);
658        } else {
659            handle("left", self.sides.left);
660            handle("right", self.sides.right);
661        }
662
663        Value::Dict(dict)
664    }
665}
666
667impl<T: Reflect + FromValue + Copy + PartialEq> FromValue for Margin<T> {
668    fn from_value(value: Value) -> HintedStrResult<Self> {
669        if T::castable(&value) {
670            let v = T::from_value(value)?;
671            return Ok(Self::splat(Some(v)));
672        }
673
674        if Dict::castable(&value) {
675            let mut dict = Dict::from_value(value)?;
676            let mut take = |key| dict.take(key).ok().map(Value::cast).transpose();
677
678            let rest = take("rest")?;
679            let x = take("x")?.or(rest);
680            let y = take("y")?.or(rest);
681            let top = take("top")?.or(y);
682            let bottom = take("bottom")?.or(y);
683            let outside = take("outside")?;
684            let inside = take("inside")?;
685            let left = take("left")?;
686            let right = take("right")?;
687
688            let implicitly_two_sided = outside.is_some() || inside.is_some();
689            let implicitly_not_two_sided = left.is_some() || right.is_some();
690            if implicitly_two_sided && implicitly_not_two_sided {
691                bail!(
692                    "`inside` and `outside` are mutually exclusive with `left` and `right`"
693                );
694            }
695
696            // - If 'implicitly_two_sided' is false here, then
697            //   'implicitly_not_two_sided' will be guaranteed to be true
698            //    due to the previous two 'if' conditions.
699            // - If both are false, this means that this margin change does not
700            //   affect lateral margins, and thus shouldn't make a difference on
701            //   the 'two_sided' attribute of this margin.
702            let two_sided = (implicitly_two_sided || implicitly_not_two_sided)
703                .then_some(implicitly_two_sided);
704
705            dict.finish(&[
706                "left", "top", "right", "bottom", "outside", "inside", "x", "y", "rest",
707            ])?;
708
709            return Ok(Margin {
710                sides: Sides {
711                    left: inside.or(left).or(x),
712                    top,
713                    right: outside.or(right).or(x),
714                    bottom,
715                },
716                two_sided,
717            });
718        }
719
720        Err(Self::error(&value))
721    }
722}
723
724/// Specification of the page's binding.
725#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
726pub enum Binding {
727    /// Bound on the left, as customary in LTR languages.
728    Left,
729    /// Bound on the right, as customary in RTL languages.
730    Right,
731}
732
733impl Binding {
734    /// Whether to swap left and right margin for the page with this number.
735    pub fn swap(self, number: NonZeroUsize) -> bool {
736        match self {
737            // Left-bound must swap on even pages
738            // (because it is correct on the first page).
739            Self::Left => number.get() % 2 == 0,
740            // Right-bound must swap on odd pages
741            // (because it is wrong on the first page).
742            Self::Right => number.get() % 2 == 1,
743        }
744    }
745}
746
747cast! {
748    Binding,
749    self => match self {
750        Self::Left => Alignment::LEFT.into_value(),
751        Self::Right => Alignment::RIGHT.into_value(),
752    },
753    v: Alignment => match v {
754        Alignment::LEFT => Self::Left,
755        Alignment::RIGHT => Self::Right,
756        _ => bail!("must be `left` or `right`"),
757    },
758}
759
760/// A list of page ranges to be exported.
761#[derive(Debug, Clone, Hash)]
762pub struct PageRanges(Vec<PageRange>);
763
764/// A range of pages to export.
765///
766/// The range is one-indexed. For example, `1..=3` indicates the first, second
767/// and third pages should be exported.
768pub type PageRange = RangeInclusive<Option<NonZeroUsize>>;
769
770impl PageRanges {
771    /// Create new page ranges.
772    pub fn new(ranges: Vec<PageRange>) -> Self {
773        Self(ranges)
774    }
775
776    /// Check if a page, given its number, should be included when exporting the
777    /// document while restricting the exported pages to these page ranges.
778    /// This is the one-indexed version of 'includes_page_index'.
779    pub fn includes_page(&self, page: NonZeroUsize) -> bool {
780        self.includes_page_index(page.get() - 1)
781    }
782
783    /// Check if a page, given its index, should be included when exporting the
784    /// document while restricting the exported pages to these page ranges.
785    /// This is the zero-indexed version of 'includes_page'.
786    pub fn includes_page_index(&self, page: usize) -> bool {
787        let page = NonZeroUsize::try_from(page + 1).unwrap();
788        self.0.iter().any(|range| match (range.start(), range.end()) {
789            (Some(start), Some(end)) => (start..=end).contains(&&page),
790            (Some(start), None) => (start..).contains(&&page),
791            (None, Some(end)) => (..=end).contains(&&page),
792            (None, None) => true,
793        })
794    }
795}
796
797/// Whether something should be even or odd.
798#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
799pub enum Parity {
800    /// Next page will be an even page.
801    Even,
802    /// Next page will be an odd page.
803    Odd,
804}
805
806impl Parity {
807    /// Whether the given number matches the parity.
808    pub fn matches(self, number: usize) -> bool {
809        match self {
810            Self::Even => number % 2 == 0,
811            Self::Odd => number % 2 == 1,
812        }
813    }
814}
815
816/// Specification of a paper.
817#[derive(Debug, Copy, Clone, Hash)]
818pub struct Paper {
819    /// The name of the paper.
820    name: &'static str,
821    /// The width of the paper in millimeters.
822    width: Scalar,
823    /// The height of the paper in millimeters.
824    height: Scalar,
825}
826
827impl Paper {
828    /// The width of the paper.
829    pub fn width(self) -> Abs {
830        Abs::mm(self.width.get())
831    }
832
833    /// The height of the paper.
834    pub fn height(self) -> Abs {
835        Abs::mm(self.height.get())
836    }
837}
838
839/// Defines paper constants and a paper parsing implementation.
840macro_rules! papers {
841    ($(($var:ident: $width:expr, $height: expr, $name:literal))*) => {
842        /// Predefined papers.
843        ///
844        /// Each paper is parsable from its name in kebab-case.
845        impl Paper {
846            $(pub const $var: Self = Self {
847                name: $name,
848                width: Scalar::new($width),
849                height: Scalar::new($height),
850            };)*
851        }
852
853        impl FromStr for Paper {
854            type Err = &'static str;
855
856            fn from_str(name: &str) -> Result<Self, Self::Err> {
857                match name.to_lowercase().as_str() {
858                    $($name => Ok(Self::$var),)*
859                    _ => Err("unknown paper size"),
860                }
861            }
862        }
863
864        cast! {
865            Paper,
866            self => self.name.into_value(),
867            $(
868                /// A paper that is
869                #[doc = stringify!($width)]
870                /// ×
871                #[doc = stringify!($height)]
872                /// millimeters in size.
873                $name => Self::$var,
874            )*
875        }
876    };
877}
878
879// All paper sizes in mm.
880//
881// Resources:
882// - https://papersizes.io/
883// - https://en.wikipedia.org/wiki/Paper_size
884// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm
885// - https://vintagepaper.co/blogs/news/traditional-paper-sizes
886papers! {
887    // ---------------------------------------------------------------------- //
888    // ISO 216 A Series
889    (A0:  841.0, 1189.0, "a0")
890    (A1:  594.0,  841.0, "a1")
891    (A2:  420.0,  594.0, "a2")
892    (A3:  297.0,  420.0, "a3")
893    (A4:  210.0,  297.0, "a4")
894    (A5:  148.0,  210.0, "a5")
895    (A6:  105.0,  148.0, "a6")
896    (A7:   74.0,  105.0, "a7")
897    (A8:   52.0,   74.0, "a8")
898    (A9:   37.0,   52.0, "a9")
899    (A10:  26.0,   37.0, "a10")
900    (A11:  18.0,   26.0, "a11")
901
902    // ISO 216 B Series
903    (ISO_B1: 707.0, 1000.0, "iso-b1")
904    (ISO_B2: 500.0,  707.0,  "iso-b2")
905    (ISO_B3: 353.0,  500.0,  "iso-b3")
906    (ISO_B4: 250.0,  353.0,  "iso-b4")
907    (ISO_B5: 176.0,  250.0,  "iso-b5")
908    (ISO_B6: 125.0,  176.0,  "iso-b6")
909    (ISO_B7:  88.0,  125.0,  "iso-b7")
910    (ISO_B8:  62.0,   88.0,  "iso-b8")
911
912    // ISO 216 C Series
913    (ISO_C3: 324.0, 458.0, "iso-c3")
914    (ISO_C4: 229.0, 324.0, "iso-c4")
915    (ISO_C5: 162.0, 229.0, "iso-c5")
916    (ISO_C6: 114.0, 162.0, "iso-c6")
917    (ISO_C7:  81.0, 114.0, "iso-c7")
918    (ISO_C8:  57.0,  81.0, "iso-c8")
919
920    // DIN D Series (extension to ISO)
921    (DIN_D3: 272.0, 385.0, "din-d3")
922    (DIN_D4: 192.0, 272.0, "din-d4")
923    (DIN_D5: 136.0, 192.0, "din-d5")
924    (DIN_D6:  96.0, 136.0, "din-d6")
925    (DIN_D7:  68.0,  96.0, "din-d7")
926    (DIN_D8:  48.0,  68.0, "din-d8")
927
928    // SIS (used in academia)
929    (SIS_G5: 169.0, 239.0, "sis-g5")
930    (SIS_E5: 115.0, 220.0, "sis-e5")
931
932    // ANSI Extensions
933    (ANSI_A: 216.0,  279.0, "ansi-a")
934    (ANSI_B: 279.0,  432.0, "ansi-b")
935    (ANSI_C: 432.0,  559.0, "ansi-c")
936    (ANSI_D: 559.0,  864.0, "ansi-d")
937    (ANSI_E: 864.0, 1118.0, "ansi-e")
938
939    // ANSI Architectural Paper
940    (ARCH_A:  229.0,  305.0, "arch-a")
941    (ARCH_B:  305.0,  457.0, "arch-b")
942    (ARCH_C:  457.0,  610.0, "arch-c")
943    (ARCH_D:  610.0,  914.0, "arch-d")
944    (ARCH_E1: 762.0, 1067.0, "arch-e1")
945    (ARCH_E:  914.0, 1219.0, "arch-e")
946
947    // JIS B Series
948    (JIS_B0:  1030.0, 1456.0, "jis-b0")
949    (JIS_B1:   728.0, 1030.0, "jis-b1")
950    (JIS_B2:   515.0,  728.0, "jis-b2")
951    (JIS_B3:   364.0,  515.0, "jis-b3")
952    (JIS_B4:   257.0,  364.0, "jis-b4")
953    (JIS_B5:   182.0,  257.0, "jis-b5")
954    (JIS_B6:   128.0,  182.0, "jis-b6")
955    (JIS_B7:    91.0,  128.0, "jis-b7")
956    (JIS_B8:    64.0,   91.0, "jis-b8")
957    (JIS_B9:    45.0,   64.0, "jis-b9")
958    (JIS_B10:   32.0,   45.0, "jis-b10")
959    (JIS_B11:   22.0,   32.0, "jis-b11")
960
961    // SAC D Series
962    (SAC_D0: 764.0, 1064.0, "sac-d0")
963    (SAC_D1: 532.0,  760.0, "sac-d1")
964    (SAC_D2: 380.0,  528.0, "sac-d2")
965    (SAC_D3: 264.0,  376.0, "sac-d3")
966    (SAC_D4: 188.0,  260.0, "sac-d4")
967    (SAC_D5: 130.0,  184.0, "sac-d5")
968    (SAC_D6:  92.0,  126.0, "sac-d6")
969
970    // ISO 7810 ID
971    (ISO_ID_1: 85.6, 53.98, "iso-id-1")
972    (ISO_ID_2: 74.0, 105.0, "iso-id-2")
973    (ISO_ID_3: 88.0, 125.0, "iso-id-3")
974
975    // ---------------------------------------------------------------------- //
976    // Asia
977    (ASIA_F4: 210.0, 330.0, "asia-f4")
978
979    // Japan
980    (JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4")
981    (JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5")
982    (JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6")
983    (JP_KIKU_4:        227.0, 306.0, "jp-kiku-4")
984    (JP_KIKU_5:        151.0, 227.0, "jp-kiku-5")
985    (JP_BUSINESS_CARD:  91.0,  55.0, "jp-business-card")
986
987    // China
988    (CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card")
989
990    // Europe
991    (EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card")
992
993    // French Traditional (AFNOR)
994    (FR_TELLIERE:          340.0, 440.0, "fr-tellière")
995    (FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture")
996    (FR_COURONNE_EDITION:  370.0, 470.0, "fr-couronne-édition")
997    (FR_RAISIN:            500.0, 650.0, "fr-raisin")
998    (FR_CARRE:             450.0, 560.0, "fr-carré")
999    (FR_JESUS:             560.0, 760.0, "fr-jésus")
1000
1001    // United Kingdom Imperial
1002    (UK_BRIEF:    406.4, 342.9, "uk-brief")
1003    (UK_DRAFT:    254.0, 406.4, "uk-draft")
1004    (UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap")
1005    (UK_QUARTO:   203.2, 254.0, "uk-quarto")
1006    (UK_CROWN:    508.0, 381.0, "uk-crown")
1007    (UK_BOOK_A:   111.0, 178.0, "uk-book-a")
1008    (UK_BOOK_B:   129.0, 198.0, "uk-book-b")
1009
1010    // Unites States
1011    (US_LETTER:         215.9,  279.4, "us-letter")
1012    (US_LEGAL:          215.9,  355.6, "us-legal")
1013    (US_TABLOID:        279.4,  431.8, "us-tabloid")
1014    (US_EXECUTIVE:      184.15, 266.7, "us-executive")
1015    (US_FOOLSCAP_FOLIO: 215.9,  342.9, "us-foolscap-folio")
1016    (US_STATEMENT:      139.7,  215.9, "us-statement")
1017    (US_LEDGER:         431.8,  279.4, "us-ledger")
1018    (US_OFICIO:         215.9, 340.36, "us-oficio")
1019    (US_GOV_LETTER:     203.2,  266.7, "us-gov-letter")
1020    (US_GOV_LEGAL:      215.9,  330.2, "us-gov-legal")
1021    (US_BUSINESS_CARD:   88.9,   50.8, "us-business-card")
1022    (US_DIGEST:         139.7,  215.9, "us-digest")
1023    (US_TRADE:          152.4,  228.6, "us-trade")
1024
1025    // ---------------------------------------------------------------------- //
1026    // Other
1027    (NEWSPAPER_COMPACT:    280.0,    430.0, "newspaper-compact")
1028    (NEWSPAPER_BERLINER:   315.0,    470.0, "newspaper-berliner")
1029    (NEWSPAPER_BROADSHEET: 381.0,    578.0, "newspaper-broadsheet")
1030    (PRESENTATION_16_9:    297.0, 167.0625, "presentation-16-9")
1031    (PRESENTATION_4_3:     280.0,    210.0, "presentation-4-3")
1032}