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]] [[package]]
name = "pixglyph" name = "pixglyph"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/typst/pixglyph#da648abb60d0e0f4353cd7602652c832132e6a39" source = "git+https://github.com/typst/pixglyph#8ee0d4517d887125e9184916780ac230e40a042a"
dependencies = [ dependencies = [
"ttf-parser", "ttf-parser",
] ]

View File

@ -77,7 +77,7 @@ fn bench_layout(iai: &mut Iai) {
let (mut ctx, id) = context(); let (mut ctx, id) = context();
let mut vm = Vm::new(&mut ctx); let mut vm = Vm::new(&mut ctx);
let module = vm.evaluate(id).unwrap(); 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) { fn bench_highlight(iai: &mut Iai) {

View File

@ -167,6 +167,21 @@ pub struct Arg {
} }
impl Args { 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. /// Consume and cast the first positional argument.
/// ///
/// Returns a `missing argument: {what}` error if no positional argument is /// 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. /// 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 sya = Arena::new();
let tpa = Arena::new(); let tpa = Arena::new();
@ -180,8 +180,10 @@ impl Template {
let mut frames = vec![]; let mut frames = vec![];
let (pages, shared) = builder.pages.unwrap().finish(); let (pages, shared) = builder.pages.unwrap().finish();
for (page, map) in pages.iter() { 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) 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 /// diagnostics in the form of a vector of error message with file and span
/// information. /// information.
pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Arc<Frame>>> { 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 /// 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::Value(value) => value,
Self::Mapping(mapping) => mapping(level), Self::Mapping(mapping) => mapping(level),
Self::Func(func, span) => { Self::Func(func, span) => {
let args = Args { let args = Args::from_values(span, [Value::Int(level as i64)]);
span,
items: vec![Arg {
span,
name: None,
value: Spanned::new(Value::Int(level as i64), span),
}],
};
func.call(vm, args)?.cast().at(span)? func.call(vm, args)?.cast().at(span)?
} }
}) })

View File

@ -146,14 +146,7 @@ impl Label {
Self::Template(template) => template.clone(), Self::Template(template) => template.clone(),
Self::Mapping(mapping) => mapping(number), Self::Mapping(mapping) => mapping(number),
&Self::Func(ref func, span) => { &Self::Func(ref func, span) => {
let args = Args { let args = Args::from_values(span, [Value::Int(number as i64)]);
span,
items: vec![Arg {
span,
name: None,
value: Spanned::new(Value::Int(number as i64), span),
}],
};
func.call(vm, args)?.cast().at(span)? func.call(vm, args)?.cast().at(span)?
} }
}) })

View File

@ -15,18 +15,14 @@ pub struct PadNode {
impl PadNode { impl PadNode {
fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
let all = args.find()?; let all = args.find()?;
let left = args.named("left")?; let hor = args.named("horizontal")?;
let top = args.named("top")?; let ver = args.named("vertical")?;
let right = args.named("right")?; let left = args.named("left")?.or(hor).or(all).unwrap_or_default();
let bottom = args.named("bottom")?; 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 body: LayoutNode = args.expect("body")?;
let padding = Sides::new( let padding = Sides::new(left, top, right, bottom);
left.or(all).unwrap_or_default(),
top.or(all).unwrap_or_default(),
right.or(all).unwrap_or_default(),
bottom.or(all).unwrap_or_default(),
);
Ok(Template::block(body.padded(padding))) Ok(Template::block(body.padded(padding)))
} }
} }

View File

@ -30,6 +30,10 @@ impl PageNode {
pub const FILL: Option<Paint> = None; pub const FILL: Option<Paint> = None;
/// How many columns the page has. /// How many columns the page has.
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); 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> { fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> {
Ok(Template::Page(Self(args.expect("body")?))) 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::WIDTH, args.named("width")?);
styles.set_opt(Self::HEIGHT, args.named("height")?); styles.set_opt(Self::HEIGHT, args.named("height")?);
let margins = args.named("margins")?; let all = args.named("margins")?;
styles.set_opt(Self::LEFT, args.named("left")?.or(margins)); let hor = args.named("horizontal")?;
styles.set_opt(Self::TOP, args.named("top")?.or(margins)); let ver = args.named("vertical")?;
styles.set_opt(Self::RIGHT, args.named("right")?.or(margins)); styles.set_opt(Self::LEFT, args.named("left")?.or(hor).or(all));
styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(margins)); 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::FLIPPED, args.named("flipped")?);
styles.set_opt(Self::FILL, args.named("fill")?); styles.set_opt(Self::FILL, args.named("fill")?);
styles.set_opt(Self::COLUMNS, args.named("columns")?); 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(()) Ok(())
} }
@ -60,7 +68,12 @@ impl PageNode {
impl PageNode { impl PageNode {
/// Layout the page run into a sequence of frames, one per page. /// 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 // When one of the lengths is infinite the page fits its content along
// that axis. // that axis.
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
@ -101,13 +114,37 @@ impl PageNode {
} }
// Layout the child. // Layout the child.
let expand = size.map(Length::is_finite); let regions = Regions::repeat(size, size, size.map(Length::is_finite));
let regions = Regions::repeat(size, size, expand); let mut frames: Vec<_> = child
Ok(child
.layout(vm, &regions, styles)? .layout(vm, &regions, styles)?
.into_iter() .into_iter()
.map(|c| c.item) .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. /// Specification of a paper.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Paper { 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(); 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 total = reference.levels() - 1;
let misses = ctx let misses = ctx
.layout_cache .layout_cache