summaryrefslogtreecommitdiff
path: root/src/library/page.rs
blob: 59cca1f0b8a4c74b8670c8c8d715b2347282b8b1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use super::*;
use crate::paper::{Paper, PaperClass};

/// `page`: Configure pages.
///
/// # Positional parameters
/// - Paper name: optional, of type `string`, see [here](crate::paper) for a
///   full list of all paper names.
/// - Body: optional, of type `template`.
///
/// # Named parameters
/// - Width of the page: `width`, of type `length`.
/// - Height of the page: `height`, of type `length`.
/// - Margins for all sides: `margins`, of type `linear` relative to sides.
/// - Left margin: `left`, of type `linear` relative to width.
/// - Right margin: `right`, of type `linear` relative to width.
/// - Top margin: `top`, of type `linear` relative to height.
/// - Bottom margin: `bottom`, of type `linear` relative to height.
/// - Flip width and height: `flip`, of type `bool`.
///
/// # Return value
/// A template that configures page properties. The effect is scoped to the body
/// if present.
pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
    let paper = args.eat::<Spanned<String>>(ctx).and_then(|name| {
        Paper::from_name(&name.v).or_else(|| {
            ctx.diag(error!(name.span, "invalid paper name"));
            None
        })
    });

    let width = args.eat_named(ctx, "width");
    let height = args.eat_named(ctx, "height");
    let margins = args.eat_named(ctx, "margins");
    let left = args.eat_named(ctx, "left");
    let top = args.eat_named(ctx, "top");
    let right = args.eat_named(ctx, "right");
    let bottom = args.eat_named(ctx, "bottom");
    let flip = args.eat_named(ctx, "flip");
    let body = args.eat::<TemplateValue>(ctx);
    let span = args.span;

    Value::template("page", move |ctx| {
        let snapshot = ctx.state.clone();

        if let Some(paper) = paper {
            ctx.state.page.class = paper.class;
            ctx.state.page.size = paper.size();
        }

        if let Some(width) = width {
            ctx.state.page.class = PaperClass::Custom;
            ctx.state.page.size.width = width;
        }

        if let Some(height) = height {
            ctx.state.page.class = PaperClass::Custom;
            ctx.state.page.size.height = height;
        }

        if let Some(margins) = margins {
            ctx.state.page.margins = Sides::splat(Some(margins));
        }

        if let Some(left) = left {
            ctx.state.page.margins.left = Some(left);
        }

        if let Some(top) = top {
            ctx.state.page.margins.top = Some(top);
        }

        if let Some(right) = right {
            ctx.state.page.margins.right = Some(right);
        }

        if let Some(bottom) = bottom {
            ctx.state.page.margins.bottom = Some(bottom);
        }

        if flip.unwrap_or(false) {
            let page = &mut ctx.state.page;
            std::mem::swap(&mut page.size.width, &mut page.size.height);
        }

        ctx.pagebreak(false, true, span);

        if let Some(body) = &body {
            // TODO: Restrict body to a single page?
            body.exec(ctx);
            ctx.state = snapshot;
            ctx.pagebreak(true, false, span);
        }
    })
}

/// `pagebreak`: Start a new page.
///
/// # Return value
/// A template that inserts a page break.
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
    let span = args.span;
    Value::template("pagebreak", move |ctx| {
        ctx.pagebreak(true, true, span);
    })
}