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