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