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