diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:32:15 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:45:57 +0100 |
| commit | 312197b276748e1a17258ad21837850f582a467c (patch) | |
| tree | 3fd0c078a2673a98b74bc12b4d654a4c143b4e1f /library/src/layout | |
| parent | e8435df5ec718e8ecc8a2ad48e4eb3ddd1f92a72 (diff) | |
Counters
Diffstat (limited to 'library/src/layout')
| -rw-r--r-- | library/src/layout/container.rs | 6 | ||||
| -rw-r--r-- | library/src/layout/enum.rs | 6 | ||||
| -rw-r--r-- | library/src/layout/flow.rs | 16 | ||||
| -rw-r--r-- | library/src/layout/grid.rs | 4 | ||||
| -rw-r--r-- | library/src/layout/list.rs | 2 | ||||
| -rw-r--r-- | library/src/layout/mod.rs | 33 | ||||
| -rw-r--r-- | library/src/layout/page.rs | 175 | ||||
| -rw-r--r-- | library/src/layout/par.rs | 12 | ||||
| -rw-r--r-- | library/src/layout/table.rs | 2 |
9 files changed, 171 insertions, 85 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index a48933a7..0b7f7eb1 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -145,7 +145,7 @@ impl Layout for BoxNode { } // Apply metadata. - frame.meta(styles); + frame.meta(styles, false); Ok(Fragment::frame(frame)) } @@ -336,7 +336,7 @@ impl Layout for BlockNode { // Measure to ensure frames for all regions have the same width. if sizing.x == Smart::Auto { let pod = Regions::one(size, Axes::splat(false)); - let frame = body.layout(vt, styles, pod)?.into_frame(); + let frame = body.measure(vt, styles, pod)?.into_frame(); size.x = frame.width(); expand.x = true; } @@ -389,7 +389,7 @@ impl Layout for BlockNode { // Apply metadata. for frame in &mut frames { - frame.meta(styles); + frame.meta(styles, false); } Ok(Fragment::frames(frames)) diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 33b297e7..1c08cd5f 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -100,7 +100,7 @@ pub struct EnumNode { /// [Ahead], /// ) /// ``` - #[default(NonZeroUsize::new(1).unwrap())] + #[default(NonZeroUsize::ONE)] pub start: NonZeroUsize, /// Whether to display the full numbering, including the numbers of @@ -180,7 +180,7 @@ impl Layout for EnumNode { let resolved = if full { parents.push(number); - let content = numbering.apply(vt.world(), &parents)?.display(); + let content = numbering.apply(vt.world, &parents)?.display(); parents.pop(); content } else { @@ -188,7 +188,7 @@ impl Layout for EnumNode { Numbering::Pattern(pattern) => { TextNode::packed(pattern.apply_kth(parents.len(), number)) } - other => other.apply(vt.world(), &[number])?.display(), + other => other.apply(vt.world, &[number])?.display(), } }; diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 2671b983..096c575e 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -47,7 +47,16 @@ impl Layout for FlowNode { || child.is::<CircleNode>() || child.is::<ImageNode>() { - layouter.layout_single(vt, &child, styles)?; + let layoutable = child.with::<dyn Layout>().unwrap(); + layouter.layout_single(vt, layoutable, styles)?; + } else if child.is::<MetaNode>() { + let mut frame = Frame::new(Size::zero()); + frame.meta(styles, true); + layouter.items.push(FlowItem::Frame( + frame, + Axes::new(Align::Top, Align::Left), + true, + )); } else if child.can::<dyn Layout>() { layouter.layout_multiple(vt, &child, styles)?; } else if child.is::<ColbreakNode>() { @@ -173,14 +182,13 @@ impl<'a> FlowLayouter<'a> { fn layout_single( &mut self, vt: &mut Vt, - content: &Content, + content: &dyn Layout, styles: StyleChain, ) -> SourceResult<()> { let aligns = AlignNode::alignment_in(styles).resolve(styles); let sticky = BlockNode::sticky_in(styles); let pod = Regions::one(self.regions.base(), Axes::splat(false)); - let layoutable = content.with::<dyn Layout>().unwrap(); - let frame = layoutable.layout(vt, styles, pod)?.into_frame(); + let frame = content.layout(vt, styles, pod)?.into_frame(); self.layout_item(FlowItem::Frame(frame, aligns, sticky)); self.last_was_par = false; Ok(()) diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index b6e86afd..47d3ab86 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -386,7 +386,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { let size = Size::new(available, height); let pod = Regions::one(size, Axes::splat(false)); - let frame = cell.layout(self.vt, self.styles, pod)?.into_frame(); + let frame = cell.measure(self.vt, self.styles, pod)?.into_frame(); resolved.set_max(frame.width()); } } @@ -457,7 +457,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { let mut pod = self.regions; pod.size.x = rcol; - let frames = cell.layout(self.vt, self.styles, pod)?.into_frames(); + let frames = cell.measure(self.vt, self.styles, pod)?.into_frames(); if let [first, rest @ ..] = frames.as_slice() { skip |= first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index 6d605868..c954ab67 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -128,7 +128,7 @@ impl Layout for ListNode { }; let depth = self.depth(styles); - let marker = self.marker(styles).resolve(vt.world(), depth)?; + let marker = self.marker(styles).resolve(vt.world, depth)?; let mut cells = vec![]; for item in self.children() { diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index eb440b7f..fc0279eb 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -47,10 +47,7 @@ use std::mem; use typed_arena::Arena; use typst::diag::SourceResult; -use typst::model::{ - applicable, realize, Content, Node, SequenceNode, StyleChain, StyleVecBuilder, - StyledNode, -}; +use typst::model::{applicable, realize, SequenceNode, StyleVecBuilder, StyledNode}; use crate::math::{FormulaNode, LayoutMath}; use crate::meta::DocumentNode; @@ -103,6 +100,22 @@ pub trait Layout { styles: StyleChain, regions: Regions, ) -> SourceResult<Fragment>; + + /// Layout without side effects. + /// + /// This node must be layouted again in the same order for the results to be + /// valid. + fn measure( + &self, + vt: &mut Vt, + styles: StyleChain, + regions: Regions, + ) -> SourceResult<Fragment> { + vt.provider.save(); + let result = self.layout(vt, styles, regions); + vt.provider.restore(); + result + } } impl Layout for Content { @@ -417,7 +430,10 @@ impl<'a> FlowBuilder<'a> { let last_was_parbreak = self.1; self.1 = false; - if content.is::<VNode>() || content.is::<ColbreakNode>() { + if content.is::<VNode>() + || content.is::<ColbreakNode>() + || content.is::<MetaNode>() + { self.0.push(content.clone(), styles); return true; } @@ -457,7 +473,12 @@ struct ParBuilder<'a>(BehavedBuilder<'a>); impl<'a> ParBuilder<'a> { fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool { - if content.is::<SpaceNode>() + if content.is::<MetaNode>() { + if !self.0.is_basically_empty() { + self.0.push(content.clone(), styles); + return true; + } + } else if content.is::<SpaceNode>() || content.is::<TextNode>() || content.is::<HNode>() || content.is::<LinebreakNode>() diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index e469bf10..8ad76387 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -1,6 +1,8 @@ +use std::ptr; use std::str::FromStr; -use super::ColumnsNode; +use super::{AlignNode, ColumnsNode}; +use crate::meta::{Counter, CounterAction, CounterNode, Numbering}; use crate::prelude::*; /// Layouts its child onto one or multiple pages. @@ -130,7 +132,7 @@ pub struct PageNode { /// emissions and mitigate the impacts /// of a rapidly changing climate. /// ``` - #[default(NonZeroUsize::new(1).unwrap())] + #[default(NonZeroUsize::ONE)] pub columns: NonZeroUsize, /// The page's background color. @@ -147,49 +149,84 @@ pub struct PageNode { /// ``` pub fill: Option<Paint>, - /// The page's header. + /// How to [number]($func/numbering) the pages. /// - /// The header is placed in the top margin of each page. + /// If an explicit `footer` is given, the numbering is ignored. /// - /// - Content: The content will be placed in the header. - /// - A function: The function will be called with the page number (starting - /// at one) as its only argument. The content it returns will be placed in - /// the header. - /// - `{none}`: The header will be empty. + /// ```example + /// #set page( + /// height: 100pt, + /// margin: (top: 16pt, bottom: 24pt), + /// numbering: "1 / 1", + /// ) + /// + /// #lorem(48) + /// ``` + pub numbering: Option<Numbering>, + + /// The alignment of the page numbering. /// /// ```example - /// #set par(justify: true) /// #set page( - /// margin: (x: 24pt, y: 32pt), - /// header: align(horizon + right, text(8pt)[_Exercise Sheet 3_]), + /// margin: (top: 16pt, bottom: 24pt), + /// numbering: "1", + /// number-align: right, /// ) /// - /// #lorem(18) + /// #lorem(30) /// ``` - pub header: Option<Marginal>, + #[default(Align::Center.into())] + pub number_align: Axes<Option<GenAlign>>, - /// The page's footer. + /// The page's header. Fills the top margin of each page. /// - /// The footer is placed in the bottom margin of each page. + /// ```example + /// #set par(justify: true) + /// #set page( + /// margin: (top: 32pt, bottom: 20pt), + /// header: [ + /// #set text(8pt) + /// #smallcaps[Typst Academcy] + /// #h(1fr) _Exercise Sheet 3_ + /// ], + /// ) /// - /// - Content: The content will be placed in the footer. - /// - A function: The function will be called with the page number (starting - /// at one) as its only argument. The content it returns will be placed in - /// the footer. - /// - `{none}`: The footer will be empty. + /// #lorem(19) + /// ``` + pub header: Option<Content>, + + /// The amount the header is raised into the top margin. + #[resolve] + #[default(Ratio::new(0.3).into())] + pub header_ascent: Rel<Length>, + + /// The page's footer. Fills the bottom margin of each page. + /// + /// For just a page number, the `numbering` property, typically suffices. If + /// you want to create a custom footer, but still display the page number, + /// you can directly access the [page counter]($func/counter). /// /// ```example /// #set par(justify: true) /// #set page( - /// margin: (x: 24pt, y: 32pt), - /// footer: i => align(horizon + right, - /// text(8pt, numbering("I", i)) - /// ) + /// height: 100pt, + /// margin: 20pt, + /// footer: [ + /// #set align(right) + /// #set text(8pt) + /// #counter(page).get("1") of + /// #counter(page).final("I") + /// ] /// ) /// - /// #lorem(18) + /// #lorem(48) /// ``` - pub footer: Option<Marginal>, + pub footer: Option<Content>, + + /// The amount the footer is lowered into the bottom margin. + #[resolve] + #[default(Ratio::new(0.3).into())] + pub footer_descent: Rel<Length>, /// Content in the page's background. /// @@ -197,35 +234,30 @@ pub struct PageNode { /// used to place a background image or a watermark. /// /// ```example - /// #set page(background: align( - /// center + horizon, - /// rotate(24deg, - /// text(18pt, fill: rgb("FFCBC4"))[*CONFIDENTIAL*] - /// ), + /// #set page(background: rotate(24deg, + /// text(18pt, fill: rgb("FFCBC4"))[ + /// *CONFIDENTIAL* + /// ] /// )) /// /// = Typst's secret plans - /// - /// In the year 2023, we plan to take over the world - /// (of typesetting). + /// In the year 2023, we plan to take + /// over the world (of typesetting). /// ``` - pub background: Option<Marginal>, + pub background: Option<Content>, /// Content in the page's foreground. /// /// This content will overlay the page's body. /// /// ```example - /// #set page(foreground: align( - /// center + horizon, - /// text(24pt)[🥸], - /// )) + /// #set page(foreground: text(24pt)[🥸]) /// /// Reviewer 2 has marked our paper /// "Weak Reject" because they did /// not understand our approach... /// ``` - pub foreground: Option<Marginal>, + pub foreground: Option<Content>, /// The contents of the page(s). /// @@ -238,12 +270,7 @@ pub struct PageNode { impl PageNode { /// Layout the page run into a sequence of frames, one per page. - pub fn layout( - &self, - vt: &mut Vt, - mut page: usize, - styles: StyleChain, - ) -> SourceResult<Fragment> { + pub fn layout(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Fragment> { // When one of the lengths is infinite the page fits its content along // that axis. let width = self.width(styles).unwrap_or(Abs::inf()); @@ -278,10 +305,18 @@ impl PageNode { let mut fragment = child.layout(vt, styles, regions)?; let fill = self.fill(styles); - let header = self.header(styles); - let footer = self.footer(styles); let foreground = self.foreground(styles); let background = self.background(styles); + let header = self.header(styles); + let header_ascent = self.header_ascent(styles); + let footer = self.footer(styles).or_else(|| { + self.numbering(styles).map(|numbering| { + CounterNode::new(Counter::Page, CounterAction::Both(numbering)) + .pack() + .aligned(self.number_align(styles)) + }) + }); + let footer_descent = self.footer_descent(styles); // Realize overlays. for frame in &mut fragment { @@ -292,26 +327,38 @@ impl PageNode { let size = frame.size(); let pad = padding.resolve(styles).relative_to(size); let pw = size.x - pad.left - pad.right; - let py = size.y - pad.bottom; - for (marginal, pos, area) in [ - (&header, Point::with_x(pad.left), Size::new(pw, pad.top)), - (&footer, Point::new(pad.left, py), Size::new(pw, pad.bottom)), - (&foreground, Point::zero(), size), - (&background, Point::zero(), size), - ] { - let in_background = std::ptr::eq(marginal, &background); - let Some(marginal) = marginal else { continue }; - let content = marginal.resolve(vt, page)?; + for marginal in [&header, &footer, &background, &foreground] { + let Some(content) = marginal else { continue }; + + let (pos, area, align); + if ptr::eq(marginal, &header) { + let ascent = header_ascent.relative_to(pad.top); + pos = Point::with_x(pad.left); + area = Size::new(pw, pad.top - ascent); + align = Align::Bottom.into(); + } else if ptr::eq(marginal, &footer) { + let descent = footer_descent.relative_to(pad.bottom); + pos = Point::new(pad.left, size.y - pad.bottom + descent); + area = Size::new(pw, pad.bottom - descent); + align = Align::Top.into(); + } else { + pos = Point::zero(); + area = size; + align = Align::CENTER_HORIZON.into(); + }; + let pod = Regions::one(area, Axes::splat(true)); - let sub = content.layout(vt, styles, pod)?.into_frame(); - if in_background { + let sub = content + .clone() + .styled(AlignNode::set_alignment(align)) + .layout(vt, styles, pod)? + .into_frame(); + if ptr::eq(marginal, &header) || ptr::eq(marginal, &background) { frame.prepend_frame(pos, sub); } else { frame.push_frame(pos, sub); } } - - page += 1; } Ok(fragment) @@ -358,7 +405,7 @@ impl Marginal { Self::Content(content) => content.clone(), Self::Func(func) => { let args = Args::new(func.span(), [Value::Int(page as i64)]); - func.call_detached(vt.world(), args)?.display() + func.call_detached(vt.world, args)?.display() } }) } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 244a61a9..1906dd7c 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -325,6 +325,8 @@ enum Segment<'a> { Formula(&'a FormulaNode), /// A box with arbitrary content. Box(&'a BoxNode, bool), + /// Metadata. + Meta, } impl Segment<'_> { @@ -334,7 +336,7 @@ impl Segment<'_> { Self::Text(len) => len, Self::Spacing(_) => SPACING_REPLACE.len_utf8(), Self::Box(_, true) => SPACING_REPLACE.len_utf8(), - Self::Formula(_) | Self::Box(_, _) => NODE_REPLACE.len_utf8(), + Self::Formula(_) | Self::Box(_, _) | Self::Meta => NODE_REPLACE.len_utf8(), } } } @@ -599,6 +601,9 @@ fn collect<'a>( let frac = node.width(styles).is_fractional(); full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE }); Segment::Box(node, frac) + } else if child.is::<MetaNode>() { + full.push(NODE_REPLACE); + Segment::Meta } else { bail!(child.span(), "unexpected paragraph child"); }; @@ -679,6 +684,11 @@ fn prepare<'a>( items.push(Item::Frame(frame)); } } + Segment::Meta => { + let mut frame = Frame::new(Size::zero()); + frame.meta(styles, true); + items.push(Item::Frame(frame)); + } } cursor = end; diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 6daafdbb..024bb0d6 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -233,7 +233,7 @@ impl<T: Cast + Clone> Celled<T> { Self::Func(func) => { let args = Args::new(func.span(), [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(vt.world(), args)?.cast().at(func.span())? + func.call_detached(vt.world, args)?.cast().at(func.span())? } }) } |
