Headers and footers
This commit is contained in:
parent
acae6e2a54
commit
05ec0f993b
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)?
|
||||
}
|
||||
})
|
||||
|
@ -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)?
|
||||
}
|
||||
})
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
@ -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, ®ions, 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 {
|
||||
|
BIN
tests/ref/layout/page-marginals.png
Normal file
BIN
tests/ref/layout/page-marginals.png
Normal file
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 |
22
tests/typ/layout/page-marginals.typ
Normal file
22
tests/typ/layout/page-marginals.typ
Normal 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.
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user