diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-02-18 01:07:50 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-02-18 01:07:50 +0100 |
| commit | 05ec0f993b4a1b8481e494ee16285d23f000872f (patch) | |
| tree | bad1ea092025213173e66a3a88cf2c8d3f4ca3a3 /src | |
| parent | acae6e2a54f11b27bae343a15d9eff952323fe28 (diff) | |
Headers and footers
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/func.rs | 15 | ||||
| -rw-r--r-- | src/eval/template.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/library/heading.rs | 9 | ||||
| -rw-r--r-- | src/library/list.rs | 9 | ||||
| -rw-r--r-- | src/library/pad.rs | 18 | ||||
| -rw-r--r-- | src/library/page.rs | 97 |
7 files changed, 116 insertions, 40 deletions
diff --git a/src/eval/func.rs b/src/eval/func.rs index 9a8a9c94..128509f8 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -167,6 +167,21 @@ pub struct Arg { } impl Args { + /// Create positional arguments from a span and values. + pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> Self { + Self { + span, + items: values + .into_iter() + .map(|value| Arg { + span, + name: None, + value: Spanned::new(value, span), + }) + .collect(), + } + } + /// Consume and cast the first positional argument. /// /// Returns a `missing argument: {what}` error if no positional argument is diff --git a/src/eval/template.rs b/src/eval/template.rs index 68974452..1f1544e6 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -169,7 +169,7 @@ impl Template { } /// Layout this template into a collection of pages. - pub fn layout(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> { + pub fn layout_pages(&self, vm: &mut Vm) -> TypResult<Vec<Arc<Frame>>> { let sya = Arena::new(); let tpa = Arena::new(); @@ -180,8 +180,10 @@ impl Template { let mut frames = vec![]; let (pages, shared) = builder.pages.unwrap().finish(); + for (page, map) in pages.iter() { - frames.extend(page.layout(vm, map.chain(&shared))?); + let number = 1 + frames.len(); + frames.extend(page.layout(vm, number, map.chain(&shared))?); } Ok(frames) @@ -283,7 +283,7 @@ impl<'a> Vm<'a> { /// diagnostics in the form of a vector of error message with file and span /// information. pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Arc<Frame>>> { - self.evaluate(id)?.template.layout(self) + self.evaluate(id)?.template.layout_pages(self) } /// Resolve a user-entered path (relative to the source file) to be diff --git a/src/library/heading.rs b/src/library/heading.rs index 3438c7b7..1b8fcef9 100644 --- a/src/library/heading.rs +++ b/src/library/heading.rs @@ -117,14 +117,7 @@ impl<T: Cast> Leveled<T> { Self::Value(value) => value, Self::Mapping(mapping) => mapping(level), Self::Func(func, span) => { - let args = Args { - span, - items: vec![Arg { - span, - name: None, - value: Spanned::new(Value::Int(level as i64), span), - }], - }; + let args = Args::from_values(span, [Value::Int(level as i64)]); func.call(vm, args)?.cast().at(span)? } }) diff --git a/src/library/list.rs b/src/library/list.rs index fe499cb1..13f21a04 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -146,14 +146,7 @@ impl Label { Self::Template(template) => template.clone(), Self::Mapping(mapping) => mapping(number), &Self::Func(ref func, span) => { - let args = Args { - span, - items: vec![Arg { - span, - name: None, - value: Spanned::new(Value::Int(number as i64), span), - }], - }; + let args = Args::from_values(span, [Value::Int(number as i64)]); func.call(vm, args)?.cast().at(span)? } }) diff --git a/src/library/pad.rs b/src/library/pad.rs index 76647645..ca45a1ca 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -15,18 +15,14 @@ pub struct PadNode { impl PadNode { fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { let all = args.find()?; - let left = args.named("left")?; - let top = args.named("top")?; - let right = args.named("right")?; - let bottom = args.named("bottom")?; + let hor = args.named("horizontal")?; + let ver = args.named("vertical")?; + let left = args.named("left")?.or(hor).or(all).unwrap_or_default(); + let top = args.named("top")?.or(ver).or(all).unwrap_or_default(); + let right = args.named("right")?.or(hor).or(all).unwrap_or_default(); + let bottom = args.named("bottom")?.or(ver).or(all).unwrap_or_default(); let body: LayoutNode = args.expect("body")?; - let padding = Sides::new( - left.or(all).unwrap_or_default(), - top.or(all).unwrap_or_default(), - right.or(all).unwrap_or_default(), - bottom.or(all).unwrap_or_default(), - ); - + let padding = Sides::new(left, top, right, bottom); Ok(Template::block(body.padded(padding))) } } diff --git a/src/library/page.rs b/src/library/page.rs index 160ac1ed..07266ec6 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -30,6 +30,10 @@ impl PageNode { pub const FILL: Option<Paint> = None; /// How many columns the page has. pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); + /// The page's header. + pub const HEADER: Marginal = Marginal::None; + /// The page's footer. + pub const FOOTER: Marginal = Marginal::None; fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { Ok(Template::Page(Self(args.expect("body")?))) @@ -44,15 +48,19 @@ impl PageNode { styles.set_opt(Self::WIDTH, args.named("width")?); styles.set_opt(Self::HEIGHT, args.named("height")?); - let margins = args.named("margins")?; - styles.set_opt(Self::LEFT, args.named("left")?.or(margins)); - styles.set_opt(Self::TOP, args.named("top")?.or(margins)); - styles.set_opt(Self::RIGHT, args.named("right")?.or(margins)); - styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(margins)); + let all = args.named("margins")?; + let hor = args.named("horizontal")?; + let ver = args.named("vertical")?; + styles.set_opt(Self::LEFT, args.named("left")?.or(hor).or(all)); + styles.set_opt(Self::TOP, args.named("top")?.or(ver).or(all)); + styles.set_opt(Self::RIGHT, args.named("right")?.or(hor).or(all)); + styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(ver).or(all)); styles.set_opt(Self::FLIPPED, args.named("flipped")?); styles.set_opt(Self::FILL, args.named("fill")?); styles.set_opt(Self::COLUMNS, args.named("columns")?); + styles.set_opt(Self::HEADER, args.named("header")?); + styles.set_opt(Self::FOOTER, args.named("footer")?); Ok(()) } @@ -60,7 +68,12 @@ impl PageNode { impl PageNode { /// Layout the page run into a sequence of frames, one per page. - pub fn layout(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Vec<Arc<Frame>>> { + pub fn layout( + &self, + vm: &mut Vm, + mut page: usize, + styles: StyleChain, + ) -> TypResult<Vec<Arc<Frame>>> { // When one of the lengths is infinite the page fits its content along // that axis. let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); @@ -101,13 +114,37 @@ impl PageNode { } // Layout the child. - let expand = size.map(Length::is_finite); - let regions = Regions::repeat(size, size, expand); - Ok(child + let regions = Regions::repeat(size, size, size.map(Length::is_finite)); + let mut frames: Vec<_> = child .layout(vm, ®ions, styles)? .into_iter() .map(|c| c.item) - .collect()) + .collect(); + + let header = styles.get_ref(Self::HEADER); + let footer = styles.get_ref(Self::FOOTER); + + for frame in &mut frames { + let size = frame.size; + let padding = padding.resolve(size); + for (y, h, marginal) in [ + (Length::zero(), padding.top, header), + (size.y - padding.bottom, padding.bottom, footer), + ] { + if let Some(template) = marginal.resolve(vm, page)? { + let pos = Point::new(padding.left, y); + let w = size.x - padding.left - padding.right; + let area = Size::new(w, h); + let pod = Regions::one(area, area, area.map(Length::is_finite)); + let sub = template.layout(vm, &pod, styles)?.remove(0).item; + Arc::make_mut(frame).push_frame(pos, sub); + } + } + + page += 1; + } + + Ok(frames) } } @@ -129,6 +166,46 @@ impl PagebreakNode { } } +/// A header or footer definition. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum Marginal { + /// Nothing, + None, + /// A bare template. + Template(Template), + /// A closure mapping from a page number to a template. + Func(Func, Span), +} + +impl Marginal { + /// Resolve the marginal based on the page number. + pub fn resolve(&self, vm: &mut Vm, page: usize) -> TypResult<Option<Template>> { + Ok(match self { + Self::None => None, + Self::Template(template) => Some(template.clone()), + Self::Func(func, span) => { + let args = Args::from_values(*span, [Value::Int(page as i64)]); + func.call(vm, args)?.cast().at(*span)? + } + }) + } +} + +impl Cast<Spanned<Value>> for Marginal { + fn is(value: &Spanned<Value>) -> bool { + matches!(&value.v, Value::Template(_) | Value::Func(_)) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + match value.v { + Value::None => Ok(Self::None), + Value::Template(v) => Ok(Self::Template(v)), + Value::Func(v) => Ok(Self::Func(v, value.span)), + _ => Err("expected none, template or function")?, + } + } +} + /// Specification of a paper. #[derive(Debug, Copy, Clone)] pub struct Paper { |
