Headers and footers

This commit is contained in:
Laurenz 2022-02-18 01:07:50 +01:00
parent acae6e2a54
commit 05ec0f993b
13 changed files with 141 additions and 43 deletions

2
Cargo.lock generated
View File

@ -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",
]

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)?
}
})

View File

@ -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)?
}
})

View File

@ -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)))
}
}

View File

@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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.

View File

@ -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