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