summaryrefslogtreecommitdiff
path: root/library/src/layout/page.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/layout/page.rs')
-rw-r--r--library/src/layout/page.rs185
1 files changed, 180 insertions, 5 deletions
diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs
index 26ebbc53..a3f99d56 100644
--- a/library/src/layout/page.rs
+++ b/library/src/layout/page.rs
@@ -4,6 +4,7 @@ use std::str::FromStr;
use super::{AlignElem, ColumnsElem};
use crate::meta::{Counter, CounterKey, Numbering};
use crate::prelude::*;
+use crate::text::TextElem;
/// Layouts its child onto one or multiple pages.
///
@@ -97,11 +98,18 @@ pub struct PageElem {
/// - `right`: The right margin.
/// - `bottom`: The bottom margin.
/// - `left`: The left margin.
+ /// - `inside`: The margin at the inner side of the page (where the
+ /// [binding]($func/page.binding) is).
+ /// - `outside`: The margin at the outer side of the page (opposite to the
+ /// [binding]($func/page.binding)).
/// - `x`: The horizontal margins.
/// - `y`: The vertical margins.
/// - `rest`: The margins on all sides except those for which the
/// dictionary explicitly sets a size.
///
+ /// The values for `left` and `right` are mutually exclusive with
+ /// the values for `inside` and `outside`.
+ ///
/// ```example
/// #set page(
/// width: 3cm,
@@ -116,7 +124,18 @@ pub struct PageElem {
/// )
/// ```
#[fold]
- pub margin: Sides<Option<Smart<Rel<Length>>>>,
+ pub margin: Margin,
+
+ /// On which side the pages will be bound.
+ ///
+ /// - `{auto}`: Equivalent to `left` if the [text direction]($func/text.dir)
+ /// is left-to-right and `right` if it is right-to-left.
+ /// - `left`: Bound on the left side.
+ /// - `right`: Bound on the right side.
+ ///
+ /// This affects the meaning of the `inside` and `outside` options for
+ /// margins.
+ pub binding: Smart<Binding>,
/// How many columns the page has.
///
@@ -301,13 +320,23 @@ impl PageElem {
}
// Determine the margins.
- let default = Rel::from(0.1190 * min);
- let margin = self
- .margin(styles)
- .map(|side| side.unwrap_or(default))
+ let default = Rel::<Length>::from(0.1190 * min);
+ let margin = self.margin(styles);
+ let two_sided = margin.two_sided.unwrap_or(false);
+ let margin = margin
+ .sides
+ .map(|side| side.and_then(Smart::as_custom).unwrap_or(default))
.resolve(styles)
.relative_to(size);
+ // Determine the binding.
+ let binding =
+ self.binding(styles)
+ .unwrap_or_else(|| match TextElem::dir_in(styles) {
+ Dir::LTR => Binding::Left,
+ _ => Binding::Right,
+ });
+
// Realize columns.
let mut child = self.body();
let columns = self.columns(styles);
@@ -352,6 +381,14 @@ impl PageElem {
// The padded width of the page's content without margins.
let pw = frame.width();
+ // If two sided, left becomes inside and right becomes outside.
+ // Thus, for left-bound pages, we want to swap on even pages and
+ // for right-bound pages, we want to swap on odd pages.
+ let mut margin = margin;
+ if two_sided && binding.swap(number) {
+ std::mem::swap(&mut margin.left, &mut margin.right);
+ }
+
// Realize margins.
frame.set_size(frame.size() + margin.sum_by_axis());
frame.translate(Point::new(margin.left, margin.top));
@@ -437,6 +474,144 @@ pub struct PagebreakElem {
pub weak: bool,
}
+/// Specification of the page's margins.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct Margin {
+ /// The margins for each side.
+ pub sides: Sides<Option<Smart<Rel<Length>>>>,
+ /// Whether to swap `left` and `right` to make them `inside` and `outside`
+ /// (when to swap depends on the binding).
+ pub two_sided: Option<bool>,
+}
+
+impl Margin {
+ /// Create an instance with four equal components.
+ pub fn splat(value: Option<Smart<Rel<Length>>>) -> Self {
+ Self { sides: Sides::splat(value), two_sided: None }
+ }
+}
+
+impl Fold for Margin {
+ type Output = Margin;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ let sides =
+ self.sides
+ .zip(outer.sides)
+ .map(|(inner, outer)| match (inner, outer) {
+ (Some(value), Some(outer)) => Some(value.fold(outer)),
+ _ => inner.or(outer),
+ });
+ let two_sided = self.two_sided.or(outer.two_sided);
+ Margin { sides, two_sided }
+ }
+}
+
+cast! {
+ Margin,
+ self => {
+ let mut dict = Dict::new();
+ let mut handle = |key: &str, component: Value| {
+ let value = component.into_value();
+ if value != Value::None {
+ dict.insert(key.into(), value);
+ }
+ };
+
+ handle("top", self.sides.top.into_value());
+ handle("bottom", self.sides.bottom.into_value());
+ if self.two_sided.unwrap_or(false) {
+ handle("inside", self.sides.left.into_value());
+ handle("outside", self.sides.right.into_value());
+ } else {
+ handle("left", self.sides.left.into_value());
+ handle("right", self.sides.right.into_value());
+ }
+
+ Value::Dict(dict)
+ },
+ _: AutoValue => Self::splat(Some(Smart::Auto)),
+ v: Rel<Length> => Self::splat(Some(Smart::Custom(v))),
+ mut dict: Dict => {
+ let mut take = |key| dict.take(key).ok().map(Value::cast).transpose();
+
+ let rest = take("rest")?;
+ let x = take("x")?.or(rest);
+ let y = take("y")?.or(rest);
+ let top = take("top")?.or(y);
+ let bottom = take("bottom")?.or(y);
+ let outside = take("outside")?;
+ let inside = take("inside")?;
+ let left = take("left")?;
+ let right = take("right")?;
+
+ let implicitly_two_sided = outside.is_some() || inside.is_some();
+ let implicitly_not_two_sided = left.is_some() || right.is_some();
+ if implicitly_two_sided && implicitly_not_two_sided {
+ bail!("`inside` and `outside` are mutually exclusive with `left` and `right`");
+ }
+
+ // - If 'implicitly_two_sided' is false here, then
+ // 'implicitly_not_two_sided' will be guaranteed to be true
+ // due to the previous two 'if' conditions.
+ // - If both are false, this means that this margin change does not
+ // affect lateral margins, and thus shouldn't make a difference on
+ // the 'two_sided' attribute of this margin.
+ let two_sided = (implicitly_two_sided || implicitly_not_two_sided)
+ .then_some(implicitly_two_sided);
+
+ dict.finish(&[
+ "left", "top", "right", "bottom", "outside", "inside", "x", "y", "rest",
+ ])?;
+
+ Margin {
+ sides: Sides {
+ left: inside.or(left).or(x),
+ top,
+ right: outside.or(right).or(x),
+ bottom,
+ },
+ two_sided,
+ }
+ }
+}
+
+/// Specification of the page's binding.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Binding {
+ /// Bound on the left, as customary in LTR languages.
+ Left,
+ /// Bound on the right, as customary in RTL languages.
+ Right,
+}
+
+impl Binding {
+ /// Whether to swap left and right margin for the page with this number.
+ fn swap(self, number: NonZeroUsize) -> bool {
+ match self {
+ // Left-bound must swap on even pages
+ // (because it is correct on the first page).
+ Self::Left => number.get() % 2 == 0,
+ // Right-bound must swap on odd pages
+ // (because it is wrong on the first page).
+ Self::Right => number.get() % 2 == 1,
+ }
+ }
+}
+
+cast! {
+ Binding,
+ self => match self {
+ Self::Left => GenAlign::Specific(Align::Left).into_value(),
+ Self::Right => GenAlign::Specific(Align::Right).into_value(),
+ },
+ v: GenAlign => match v {
+ GenAlign::Specific(Align::Left) => Self::Left,
+ GenAlign::Specific(Align::Right) => Self::Right,
+ _ => Err("must be `left` or `right`")?,
+ },
+}
+
/// A header, footer, foreground or background definition.
#[derive(Debug, Clone, Hash)]
pub enum Marginal {