summaryrefslogtreecommitdiff
path: root/library/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-17 11:32:15 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-17 11:45:57 +0100
commit312197b276748e1a17258ad21837850f582a467c (patch)
tree3fd0c078a2673a98b74bc12b4d654a4c143b4e1f /library/src/layout
parente8435df5ec718e8ecc8a2ad48e4eb3ddd1f92a72 (diff)
Counters
Diffstat (limited to 'library/src/layout')
-rw-r--r--library/src/layout/container.rs6
-rw-r--r--library/src/layout/enum.rs6
-rw-r--r--library/src/layout/flow.rs16
-rw-r--r--library/src/layout/grid.rs4
-rw-r--r--library/src/layout/list.rs2
-rw-r--r--library/src/layout/mod.rs33
-rw-r--r--library/src/layout/page.rs175
-rw-r--r--library/src/layout/par.rs12
-rw-r--r--library/src/layout/table.rs2
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())?
}
})
}