summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-18 01:07:50 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-18 01:07:50 +0100
commit05ec0f993b4a1b8481e494ee16285d23f000872f (patch)
treebad1ea092025213173e66a3a88cf2c8d3f4ca3a3
parentacae6e2a54f11b27bae343a15d9eff952323fe28 (diff)
Headers and footers
-rw-r--r--Cargo.lock2
-rw-r--r--benches/oneshot.rs2
-rw-r--r--src/eval/func.rs15
-rw-r--r--src/eval/template.rs6
-rw-r--r--src/lib.rs2
-rw-r--r--src/library/heading.rs9
-rw-r--r--src/library/list.rs9
-rw-r--r--src/library/pad.rs18
-rw-r--r--src/library/page.rs97
-rw-r--r--tests/ref/layout/page-marginals.pngbin0 -> 68057 bytes
-rw-r--r--tests/ref/utility/strings.pngbin10699 -> 10634 bytes
-rw-r--r--tests/typ/layout/page-marginals.typ22
-rw-r--r--tests/typeset.rs2
13 files changed, 141 insertions, 43 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a1024fd3..c7e8b009 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -537,7 +537,7 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "pixglyph"
version = "0.1.0"
-source = "git+https://github.com/typst/pixglyph#da648abb60d0e0f4353cd7602652c832132e6a39"
+source = "git+https://github.com/typst/pixglyph#8ee0d4517d887125e9184916780ac230e40a042a"
dependencies = [
"ttf-parser",
]
diff --git a/benches/oneshot.rs b/benches/oneshot.rs
index 92721013..5dbf993f 100644
--- a/benches/oneshot.rs
+++ b/benches/oneshot.rs
@@ -77,7 +77,7 @@ fn bench_layout(iai: &mut Iai) {
let (mut ctx, id) = context();
let mut vm = Vm::new(&mut ctx);
let module = vm.evaluate(id).unwrap();
- iai.run(|| module.template.layout(&mut vm));
+ iai.run(|| module.template.layout_pages(&mut vm));
}
fn bench_highlight(iai: &mut Iai) {
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)
diff --git a/src/lib.rs b/src/lib.rs
index 46bcad8d..6ad30d2f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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, &regions, 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 {
diff --git a/tests/ref/layout/page-marginals.png b/tests/ref/layout/page-marginals.png
new file mode 100644
index 00000000..734973b6
--- /dev/null
+++ b/tests/ref/layout/page-marginals.png
Binary files differ
diff --git a/tests/ref/utility/strings.png b/tests/ref/utility/strings.png
index c623aa00..d4d575d9 100644
--- a/tests/ref/utility/strings.png
+++ b/tests/ref/utility/strings.png
Binary files differ
diff --git a/tests/typ/layout/page-marginals.typ b/tests/typ/layout/page-marginals.typ
new file mode 100644
index 00000000..2d969647
--- /dev/null
+++ b/tests/typ/layout/page-marginals.typ
@@ -0,0 +1,22 @@
+#set page(
+ paper: "a8",
+ margins: 30pt,
+ horizontal: 15pt,
+ header: align(horizon, {
+ text(eastern)[*Typst*]
+ h(1fr)
+ text(80%)[_Chapter 1_]
+ }),
+ footer: page => v(5pt) + align(center)[\~ #page \~],
+)
+
+But, soft! what light through yonder window breaks? It is the east, and Juliet
+is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
+pale with grief, That thou her maid art far more fair than she: Be not her maid,
+since she is envious; Her vestal livery is but sick and green And none but fools
+do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
+were! She speaks yet she says nothing: what of that? Her eye discourses; I will
+answer it.
+
+#set page(header: none, height: auto, top: 15pt, bottom: 25pt)
+The END.
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 626fc113..dadcfed3 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -498,7 +498,7 @@ fn test_incremental(
ctx.layout_cache.turnaround();
- let cached = silenced(|| template.layout(&mut Vm::new(ctx)).unwrap());
+ let cached = silenced(|| template.layout_pages(&mut Vm::new(ctx)).unwrap());
let total = reference.levels() - 1;
let misses = ctx
.layout_cache