mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
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]]
|
[[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",
|
||||||
]
|
]
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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)?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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, ®ions, styles)?
|
.layout(vm, ®ions, 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 {
|
||||||
|
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();
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user