summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-01-17 21:50:35 +0100
committerLaurenz <laurmaedje@gmail.com>2024-01-17 21:53:20 +0100
commit6ac71eeaf7b68dab07f75bd1a480810481fa9b73 (patch)
treed452e7323200fe56d61a34b91c8b98826d60978c
parent50741209a8f4c5e91d35281eb44b7425b3d022b2 (diff)
Add `Page` struct
To get rid of the Meta hack where numbering and things like that are stored in the frame.
-rw-r--r--crates/typst-cli/src/compile.rs13
-rw-r--r--crates/typst-docs/src/html.rs12
-rw-r--r--crates/typst-docs/src/lib.rs7
-rw-r--r--crates/typst-ide/src/jump.rs4
-rw-r--r--crates/typst-pdf/src/lib.rs4
-rw-r--r--crates/typst-pdf/src/page.rs119
-rw-r--r--crates/typst-render/src/lib.rs12
-rw-r--r--crates/typst-svg/src/lib.rs30
-rw-r--r--crates/typst/src/introspection/introspector.rs16
-rw-r--r--crates/typst/src/introspection/mod.rs9
-rw-r--r--crates/typst/src/layout/frame.rs43
-rw-r--r--crates/typst/src/layout/page.rs41
-rw-r--r--crates/typst/src/model/document.rs11
-rw-r--r--crates/typst/src/model/numbering.rs45
-rw-r--r--tests/fuzz/src/compile.rs2
-rw-r--r--tests/src/benches.rs2
-rw-r--r--tests/src/tests.rs32
17 files changed, 203 insertions, 199 deletions
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index ea612630..2c80ce3f 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -219,7 +219,7 @@ fn export_image(
.pages
.par_iter()
.enumerate()
- .map(|(i, frame)| {
+ .map(|(i, page)| {
let storage;
let path = if numbered {
storage = string.replace("{n}", &format!("{:0width$}", i + 1));
@@ -231,20 +231,23 @@ fn export_image(
// If we are not watching, don't use the cache.
// If the frame is in the cache, skip it.
// If the file does not exist, always create it.
- if watching && cache.is_cached(i, frame) && path.exists() {
+ if watching && cache.is_cached(i, &page.frame) && path.exists() {
return Ok(());
}
match fmt {
ImageExportFormat::Png => {
- let pixmap =
- typst_render::render(frame, command.ppi / 72.0, Color::WHITE);
+ let pixmap = typst_render::render(
+ &page.frame,
+ command.ppi / 72.0,
+ Color::WHITE,
+ );
pixmap
.save_png(path)
.map_err(|err| eco_format!("failed to write PNG file ({err})"))?;
}
ImageExportFormat::Svg => {
- let svg = typst_svg::svg(frame);
+ let svg = typst_svg::svg(&page.frame);
fs::write(path, svg.as_bytes())
.map_err(|err| eco_format!("failed to write SVG file ({err})"))?;
}
diff --git a/crates/typst-docs/src/html.rs b/crates/typst-docs/src/html.rs
index 7481b050..8f6d1366 100644
--- a/crates/typst-docs/src/html.rs
+++ b/crates/typst-docs/src/html.rs
@@ -388,8 +388,8 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
let world = DocWorld(source);
let mut tracer = Tracer::new();
- let mut frames = match typst::compile(&world, &mut tracer) {
- Ok(doc) => doc.pages,
+ let mut document = match typst::compile(&world, &mut tracer) {
+ Ok(doc) => doc,
Err(err) => {
let msg = &err[0].message;
panic!("while trying to compile:\n{text}:\n\nerror: {msg}");
@@ -397,16 +397,16 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
};
if let Some([x, y, w, h]) = zoom {
- frames[0].translate(Point::new(-x, -y));
- *frames[0].size_mut() = Size::new(w, h);
+ document.pages[0].frame.translate(Point::new(-x, -y));
+ *document.pages[0].frame.size_mut() = Size::new(w, h);
}
if single {
- frames.truncate(1);
+ document.pages.truncate(1);
}
let hash = typst::util::hash128(text);
- resolver.example(hash, highlighted, &frames)
+ resolver.example(hash, highlighted, &document)
}
/// Extract an attribute value from an HTML element.
diff --git a/crates/typst-docs/src/lib.rs b/crates/typst-docs/src/lib.rs
index ad40b987..f2531462 100644
--- a/crates/typst-docs/src/lib.rs
+++ b/crates/typst-docs/src/lib.rs
@@ -25,9 +25,10 @@ use typst::foundations::{
FOUNDATIONS,
};
use typst::introspection::INTROSPECTION;
-use typst::layout::{Abs, Frame, Margin, PageElem, LAYOUT};
+use typst::layout::{Abs, Margin, PageElem, LAYOUT};
use typst::loading::DATA_LOADING;
use typst::math::MATH;
+use typst::model::Document;
use typst::model::MODEL;
use typst::symbols::SYMBOLS;
use typst::text::{Font, FontBook, TEXT};
@@ -97,7 +98,7 @@ pub trait Resolver {
fn image(&self, filename: &str, data: &[u8]) -> String;
/// Produce HTML for an example.
- fn example(&self, hash: u128, source: Option<Html>, frames: &[Frame]) -> Html;
+ fn example(&self, hash: u128, source: Option<Html>, document: &Document) -> Html;
/// Determine the commits between two tags.
fn commits(&self, from: &str, to: &str) -> Vec<Commit>;
@@ -789,7 +790,7 @@ mod tests {
None
}
- fn example(&self, _: u128, _: Option<Html>, _: &[Frame]) -> Html {
+ fn example(&self, _: u128, _: Option<Html>, _: &Document) -> Html {
Html::new(String::new())
}
diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs
index f1128878..c8b7343a 100644
--- a/crates/typst-ide/src/jump.rs
+++ b/crates/typst-ide/src/jump.rs
@@ -121,8 +121,8 @@ pub fn jump_from_cursor(
}
let span = node.span();
- for (i, frame) in document.pages.iter().enumerate() {
- if let Some(pos) = find_in_frame(frame, span) {
+ for (i, page) in document.pages.iter().enumerate() {
+ if let Some(pos) = find_in_frame(&page.frame, span) {
return Some(Position {
page: NonZeroUsize::new(i + 1).unwrap(),
point: pos,
diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs
index 63a9dd97..80d4d67f 100644
--- a/crates/typst-pdf/src/lib.rs
+++ b/crates/typst-pdf/src/lib.rs
@@ -30,7 +30,7 @@ use crate::color::ColorSpaces;
use crate::extg::ExtGState;
use crate::gradient::PdfGradient;
use crate::image::EncodedImage;
-use crate::page::Page;
+use crate::page::EncodedPage;
use crate::pattern::PdfPattern;
/// Export a document into a PDF file.
@@ -72,7 +72,7 @@ struct PdfContext<'a> {
/// The writer we are writing the PDF into.
pdf: Pdf,
/// Content of exported pages.
- pages: Vec<Page>,
+ pages: Vec<EncodedPage>,
/// For each font a mapping from used glyphs to their text representation.
/// May contain multiple chars in case of ligatures or similar things. The
/// same glyph can have a different text representation within one document,
diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs
index d3bbcb74..6f672848 100644
--- a/crates/typst-pdf/src/page.rs
+++ b/crates/typst-pdf/src/page.rs
@@ -10,11 +10,10 @@ use pdf_writer::writers::PageLabel;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
use typst::introspection::Meta;
use typst::layout::{
- Abs, Em, Frame, FrameItem, GroupItem, PdfPageLabel, PdfPageLabelStyle, Point, Ratio,
- Size, Transform,
+ Abs, Em, Frame, FrameItem, GroupItem, Page, Point, Ratio, Size, Transform,
};
-use typst::model::Destination;
-use typst::text::{Font, TextItem};
+use typst::model::{Destination, Numbering};
+use typst::text::{Case, Font, TextItem};
use typst::util::{Deferred, Numeric};
use typst::visualize::{
FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, Shape,
@@ -27,34 +26,36 @@ use crate::{deflate_deferred, AbsExt, EmExt, PdfContext};
/// Construct page objects.
#[typst_macros::time(name = "construct pages")]
-pub(crate) fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {
- for frame in frames {
- let (page_ref, page) = construct_page(ctx, frame);
+pub(crate) fn construct_pages(ctx: &mut PdfContext, pages: &[Page]) {
+ for page in pages {
+ let (page_ref, mut encoded) = construct_page(ctx, &page.frame);
+ encoded.label = page
+ .numbering
+ .as_ref()
+ .and_then(|num| PdfPageLabel::generate(num, page.number));
ctx.page_refs.push(page_ref);
- ctx.pages.push(page);
+ ctx.pages.push(encoded);
}
}
/// Construct a page object.
#[typst_macros::time(name = "construct page")]
-pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, Page) {
+pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, EncodedPage) {
let page_ref = ctx.alloc.bump();
+ let size = frame.size();
let mut ctx = PageContext {
parent: ctx,
page_ref,
- label: None,
uses_opacities: false,
content: Content::new(),
- state: State::new(frame.size()),
+ state: State::new(size),
saves: vec![],
bottom: 0.0,
links: vec![],
resources: HashMap::default(),
};
- let size = frame.size();
-
// Make the coordinate system start at the top-left.
ctx.bottom = size.y.to_f32();
ctx.transform(Transform {
@@ -69,13 +70,13 @@ pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, Page)
// Encode the page into the content stream.
write_frame(&mut ctx, frame);
- let page = Page {
+ let page = EncodedPage {
size,
content: deflate_deferred(ctx.content.finish()),
id: ctx.page_ref,
uses_opacities: ctx.uses_opacities,
links: ctx.links,
- label: ctx.label,
+ label: None,
resources: ctx.resources,
};
@@ -249,8 +250,86 @@ pub(crate) fn write_page_labels(ctx: &mut PdfContext) -> Vec<(NonZeroUsize, Ref)
result
}
+/// Specification for a PDF page label.
+#[derive(Debug, Clone, PartialEq, Hash, Default)]
+struct PdfPageLabel {
+ /// Can be any string or none. Will always be prepended to the numbering style.
+ prefix: Option<EcoString>,
+ /// Based on the numbering pattern.
+ ///
+ /// If `None` or numbering is a function, the field will be empty.
+ style: Option<PdfPageLabelStyle>,
+ /// Offset for the page label start.
+ ///
+ /// Describes where to start counting from when setting a style.
+ /// (Has to be greater or equal than 1)
+ offset: Option<NonZeroUsize>,
+}
+
+/// A PDF page label number style.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+enum PdfPageLabelStyle {
+ /// Decimal arabic numerals (1, 2, 3).
+ Arabic,
+ /// Lowercase roman numerals (i, ii, iii).
+ LowerRoman,
+ /// Uppercase roman numerals (I, II, III).
+ UpperRoman,
+ /// Lowercase letters (`a` to `z` for the first 26 pages,
+ /// `aa` to `zz` and so on for the next).
+ LowerAlpha,
+ /// Uppercase letters (`A` to `Z` for the first 26 pages,
+ /// `AA` to `ZZ` and so on for the next).
+ UpperAlpha,
+}
+
+impl PdfPageLabel {
+ /// Create a new `PdfNumbering` from a `Numbering` applied to a page
+ /// number.
+ fn generate(numbering: &Numbering, number: usize) -> Option<PdfPageLabel> {
+ let Numbering::Pattern(pat) = numbering else {
+ return None;
+ };
+
+ let Some((prefix, kind, case)) = pat.pieces.first() else {
+ return None;
+ };
+
+ // If there is a suffix, we cannot use the common style optimisation,
+ // since PDF does not provide a suffix field.
+ let mut style = None;
+ if pat.suffix.is_empty() {
+ use {typst::model::NumberingKind as Kind, PdfPageLabelStyle as Style};
+ match (kind, case) {
+ (Kind::Arabic, _) => style = Some(Style::Arabic),
+ (Kind::Roman, Case::Lower) => style = Some(Style::LowerRoman),
+ (Kind::Roman, Case::Upper) => style = Some(Style::UpperRoman),
+ (Kind::Letter, Case::Lower) if number <= 26 => {
+ style = Some(Style::LowerAlpha)
+ }
+ (Kind::Letter, Case::Upper) if number <= 26 => {
+ style = Some(Style::UpperAlpha)
+ }
+ _ => {}
+ }
+ }
+
+ // Prefix and offset depend on the style: If it is supported by the PDF
+ // spec, we use the given prefix and an offset. Otherwise, everything
+ // goes into prefix.
+ let prefix = if style.is_none() {
+ Some(pat.apply(&[number]))
+ } else {
+ (!prefix.is_empty()).then(|| prefix.clone())
+ };
+
+ let offset = style.and(NonZeroUsize::new(number));
+ Some(PdfPageLabel { prefix, style, offset })
+ }
+}
+
/// Data for an exported page.
-pub struct Page {
+pub struct EncodedPage {
/// The indirect object id of the page.
pub id: Ref,
/// The page's dimensions.
@@ -261,10 +340,10 @@ pub struct Page {
pub uses_opacities: bool,
/// Links in the PDF coordinate system.
pub links: Vec<(Destination, Rect)>,
- /// The page's PDF label.
- pub label: Option<PdfPageLabel>,
/// The page's used resources
pub resources: HashMap<PageResource, usize>,
+ /// The page's PDF label.
+ label: Option<PdfPageLabel>,
}
/// Represents a resource being used in a PDF page by its name.
@@ -326,7 +405,6 @@ impl PageResource {
pub struct PageContext<'a, 'b> {
pub(crate) parent: &'a mut PdfContext<'b>,
page_ref: Ref,
- label: Option<PdfPageLabel>,
pub content: Content,
state: State,
saves: Vec<State>,
@@ -557,7 +635,6 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
for &(pos, ref item) in frame.items() {
let x = pos.x.to_f32();
let y = pos.y.to_f32();
-
match item {
FrameItem::Group(group) => write_group(ctx, pos, group),
FrameItem::Text(text) => write_text(ctx, pos, text),
@@ -567,8 +644,6 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
Meta::Link(dest) => write_link(ctx, pos, dest, *size),
Meta::Elem(_) => {}
Meta::Hide => {}
- Meta::PageNumbering(_) => {}
- Meta::PdfPageLabel(label) => ctx.label = Some(label.clone()),
},
}
}
diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs
index 34e9b7b3..5718a7c0 100644
--- a/crates/typst-render/src/lib.rs
+++ b/crates/typst-render/src/lib.rs
@@ -13,6 +13,7 @@ use typst::introspection::Meta;
use typst::layout::{
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Ratio, Size, Transform,
};
+use typst::model::Document;
use typst::text::{Font, TextItem};
use typst::visualize::{
Color, DashPattern, FixedStroke, Geometry, Gradient, Image, ImageKind, LineCap,
@@ -39,19 +40,20 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
canvas
}
-/// Export multiple frames into a single raster image.
+/// Export a document with potentially multiple pages into a single raster image.
///
/// The padding will be added around and between the individual frames.
pub fn render_merged(
- frames: &[Frame],
+ document: &Document,
pixel_per_pt: f32,
frame_fill: Color,
padding: Abs,
padding_fill: Color,
) -> sk::Pixmap {
- let pixmaps: Vec<_> = frames
+ let pixmaps: Vec<_> = document
+ .pages
.iter()
- .map(|frame| render(frame, pixel_per_pt, frame_fill))
+ .map(|page| render(&page.frame, pixel_per_pt, frame_fill))
.collect();
let padding = (pixel_per_pt * padding.to_f32()).round() as u32;
@@ -165,8 +167,6 @@ fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) {
FrameItem::Meta(meta, _) => match meta {
Meta::Link(_) => {}
Meta::Elem(_) => {}
- Meta::PageNumbering(_) => {}
- Meta::PdfPageLabel(_) => {}
Meta::Hide => {}
},
}
diff --git a/crates/typst-svg/src/lib.rs b/crates/typst-svg/src/lib.rs
index bb451cbe..035ec330 100644
--- a/crates/typst-svg/src/lib.rs
+++ b/crates/typst-svg/src/lib.rs
@@ -11,6 +11,7 @@ use typst::layout::{
Abs, Angle, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Quadrant, Ratio,
Size, Transform,
};
+use typst::model::Document;
use typst::text::{Font, TextItem};
use typst::util::hash128;
use typst::visualize::{
@@ -35,24 +36,33 @@ pub fn svg(frame: &Frame) -> String {
renderer.finalize()
}
-/// Export multiple frames into a single SVG file.
+/// Export a document with potentially multiple pages into a single SVG file.
///
/// The padding will be added around and between the individual frames.
-pub fn svg_merged(frames: &[Frame], padding: Abs) -> String {
+pub fn svg_merged(document: &Document, padding: Abs) -> String {
let width = 2.0 * padding
- + frames.iter().map(|frame| frame.width()).max().unwrap_or_default();
- let height = padding + frames.iter().map(|page| page.height() + padding).sum::<Abs>();
- let size = Size::new(width, height);
+ + document
+ .pages
+ .iter()
+ .map(|page| page.frame.width())
+ .max()
+ .unwrap_or_default();
+ let height = padding
+ + document
+ .pages
+ .iter()
+ .map(|page| page.frame.height() + padding)
+ .sum::<Abs>();
let mut renderer = SVGRenderer::new();
- renderer.write_header(size);
+ renderer.write_header(Size::new(width, height));
let [x, mut y] = [padding; 2];
- for frame in frames {
+ for page in &document.pages {
let ts = Transform::translate(x, y);
- let state = State::new(frame.size(), Transform::identity());
- renderer.render_frame(state, ts, frame);
- y += frame.height() + padding;
+ let state = State::new(page.frame.size(), Transform::identity());
+ renderer.render_frame(state, ts, &page.frame);
+ y += page.frame.height() + padding;
}
renderer.finalize()
diff --git a/crates/typst/src/introspection/introspector.rs b/crates/typst/src/introspection/introspector.rs
index 9c3e73c6..75882edf 100644
--- a/crates/typst/src/introspection/introspector.rs
+++ b/crates/typst/src/introspection/introspector.rs
@@ -12,7 +12,7 @@ use smallvec::SmallVec;
use crate::diag::{bail, StrResult};
use crate::foundations::{Content, Label, Repr, Selector};
use crate::introspection::{Location, Meta};
-use crate::layout::{Frame, FrameItem, Point, Position, Transform};
+use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
use crate::model::Numbering;
use crate::util::NonZeroExt;
@@ -38,16 +38,17 @@ pub struct Introspector {
impl Introspector {
/// Applies new frames in-place, reusing the existing allocations.
#[typst_macros::time(name = "introspect")]
- pub fn rebuild(&mut self, frames: &[Frame]) {
- self.pages = frames.len();
+ pub fn rebuild(&mut self, pages: &[Page]) {
+ self.pages = pages.len();
self.elems.clear();
self.labels.clear();
self.page_numberings.clear();
self.queries.clear();
- for (i, frame) in frames.iter().enumerate() {
- let page = NonZeroUsize::new(1 + i).unwrap();
- self.extract(frame, page, Transform::identity());
+ for (i, page) in pages.iter().enumerate() {
+ let page_nr = NonZeroUsize::new(1 + i).unwrap();
+ self.extract(&page.frame, page_nr, Transform::identity());
+ self.page_numberings.push(page.numbering.clone());
}
}
@@ -77,9 +78,6 @@ impl Introspector {
self.labels.entry(label).or_default().push(self.elems.len() - 1);
}
}
- FrameItem::Meta(Meta::PageNumbering(numbering), _) => {
- self.page_numberings.push(numbering.clone());
- }
_ => {}
}
}
diff --git a/crates/typst/src/introspection/mod.rs b/crates/typst/src/introspection/mod.rs
index 25f67400..62512510 100644
--- a/crates/typst/src/introspection/mod.rs
+++ b/crates/typst/src/introspection/mod.rs
@@ -29,8 +29,7 @@ use crate::foundations::Packed;
use crate::foundations::{
category, elem, ty, Behave, Behaviour, Category, Content, Repr, Scope, Unlabellable,
};
-use crate::layout::PdfPageLabel;
-use crate::model::{Destination, Numbering};
+use crate::model::Destination;
/// Interactions between document parts.
///
@@ -88,10 +87,6 @@ pub enum Meta {
/// An identifiable element that produces something within the area this
/// metadata is attached to.
Elem(Content),
- /// The numbering of the current page.
- PageNumbering(Option<Numbering>),
- /// A PDF page label of the current page.
- PdfPageLabel(PdfPageLabel),
/// Indicates that content should be hidden. This variant doesn't appear
/// in the final frames as it is removed alongside the content that should
/// be hidden.
@@ -103,8 +98,6 @@ impl Debug for Meta {
match self {
Self::Link(dest) => write!(f, "Link({dest:?})"),
Self::Elem(content) => write!(f, "Elem({:?})", content.func()),
- Self::PageNumbering(value) => write!(f, "PageNumbering({value:?})"),
- Self::PdfPageLabel(label) => write!(f, "PdfPageLabel({label:?})"),
Self::Hide => f.pad("Hide"),
}
}
diff --git a/crates/typst/src/layout/frame.rs b/crates/typst/src/layout/frame.rs
index 3c6d88e7..3a21078e 100644
--- a/crates/typst/src/layout/frame.rs
+++ b/crates/typst/src/layout/frame.rs
@@ -4,9 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc;
-use ecow::{eco_format, EcoString};
-
-use crate::foundations::{cast, dict, Dict, Repr, StyleChain, Value};
+use crate::foundations::{cast, dict, Dict, StyleChain, Value};
use crate::introspection::{Meta, MetaElem};
use crate::layout::{
Abs, Axes, Corners, FixedAlignment, Length, Point, Rel, Sides, Size, Transform,
@@ -552,42 +550,3 @@ impl From<Position> for Dict {
}
}
}
-
-/// Specification for a PDF page label.
-#[derive(Debug, Clone, PartialEq, Hash, Default)]
-pub struct PdfPageLabel {
- /// Can be any string or none. Will always be prepended to the numbering style.
- pub prefix: Option<EcoString>,
- /// Based on the numbering pattern.
- ///
- /// If `None` or numbering is a function, the field will be empty.
- pub style: Option<PdfPageLabelStyle>,
- /// Offset for the page label start.
- ///
- /// Describes where to start counting from when setting a style.
- /// (Has to be greater or equal than 1)
- pub offset: Option<NonZeroUsize>,
-}
-
-impl Repr for PdfPageLabel {
- fn repr(&self) -> EcoString {
- eco_format!("{self:?}")
- }
-}
-
-/// A PDF page label number style.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum PdfPageLabelStyle {
- /// Decimal arabic numerals (1, 2, 3).
- Arabic,
- /// Lowercase roman numerals (i, ii, iii).
- LowerRoman,
- /// Uppercase roman numerals (I, II, III).
- UpperRoman,
- /// Lowercase letters (`a` to `z` for the first 26 pages,
- /// `aa` to `zz` and so on for the next).
- LowerAlpha,
- /// Uppercase letters (`A` to `Z` for the first 26 pages,
- /// `AA` to `ZZ` and so on for the next).
- UpperAlpha,
-}
diff --git a/crates/typst/src/layout/page.rs b/crates/typst/src/layout/page.rs
index c36be514..989e5ea2 100644
--- a/crates/typst/src/layout/page.rs
+++ b/crates/typst/src/layout/page.rs
@@ -9,10 +9,10 @@ use crate::foundations::{
cast, elem, AutoValue, Cast, Content, Dict, Fold, Func, NativeElement, Packed,
Resolve, Smart, StyleChain, Value,
};
-use crate::introspection::{Counter, CounterKey, ManualPageCounter, Meta};
+use crate::introspection::{Counter, CounterKey, ManualPageCounter};
use crate::layout::{
- Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Fragment, Frame, HAlignment,
- Layout, Length, Point, Ratio, Regions, Rel, Sides, Size, VAlignment,
+ Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Layout, Length,
+ Point, Ratio, Regions, Rel, Sides, Size, VAlignment,
};
use crate::model::Numbering;
@@ -347,7 +347,7 @@ impl Packed<PageElem> {
styles: StyleChain,
page_counter: &mut ManualPageCounter,
extend_to: Option<Parity>,
- ) -> SourceResult<Fragment> {
+ ) -> SourceResult<Vec<Page>> {
// When one of the lengths is infinite the page fits its content along
// that axis.
let width = self.width(styles).unwrap_or(Abs::inf());
@@ -413,7 +413,6 @@ impl Packed<PageElem> {
let header_ascent = self.header_ascent(styles);
let footer_descent = self.footer_descent(styles);
let numbering = self.numbering(styles);
- let numbering_meta = Meta::PageNumbering(numbering.clone());
let number_align = self.number_align(styles);
let mut header = Cow::Borrowed(self.header(styles));
let mut footer = Cow::Borrowed(self.footer(styles));
@@ -447,7 +446,8 @@ impl Packed<PageElem> {
}
// Post-process pages.
- for frame in frames.iter_mut() {
+ let mut pages = Vec::with_capacity(frames.len());
+ for mut frame in frames {
// The padded width of the page's content without margins.
let pw = frame.width();
@@ -462,7 +462,6 @@ impl Packed<PageElem> {
// Realize margins.
frame.set_size(frame.size() + margin.sum_by_axis());
frame.translate(Point::new(margin.left, margin.top));
- frame.push_positionless_meta(numbering_meta.clone());
// The page size with margins.
let size = frame.size();
@@ -506,22 +505,32 @@ impl Packed<PageElem> {
frame.fill(fill.clone());
}
- page_counter.visit(engine, frame)?;
-
- // Add a PDF page label if there is a numbering.
- if let Some(num) = numbering {
- if let Some(page_label) = num.apply_pdf(page_counter.logical()) {
- frame.push_positionless_meta(Meta::PdfPageLabel(page_label));
- }
- }
+ page_counter.visit(engine, &frame)?;
+ pages.push(Page {
+ frame,
+ numbering: numbering.clone(),
+ number: page_counter.logical(),
+ });
page_counter.step();
}
- Ok(Fragment::frames(frames))
+ Ok(pages)
}
}
+/// A finished page.
+#[derive(Debug, Default, Clone)]
+pub struct Page {
+ /// The frame that defines the page.
+ pub frame: Frame,
+ /// The page's numbering.
+ pub numbering: Option<Numbering>,
+ /// The logical page number (controlled by `counter(page)` and may thus not
+ /// match the physical number).
+ pub number: usize,
+}
+
/// Specification of the page's margins.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Margin {
diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs
index fedecc88..dc291b44 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst/src/model/document.rs
@@ -7,7 +7,7 @@ use crate::foundations::{
Value,
};
use crate::introspection::{Introspector, ManualPageCounter};
-use crate::layout::{Frame, LayoutRoot, PageElem};
+use crate::layout::{LayoutRoot, Page, PageElem};
/// The root element of a document and its metadata.
///
@@ -95,9 +95,8 @@ impl LayoutRoot for Packed<DocumentElem> {
.to_packed::<PageElem>()?
.clear_to(styles)
});
- let fragment =
- page.layout(engine, styles, &mut page_counter, extend_to)?;
- pages.extend(fragment);
+ let run = page.layout(engine, styles, &mut page_counter, extend_to)?;
+ pages.extend(run);
} else {
bail!(child.span(), "unexpected document child");
}
@@ -139,8 +138,8 @@ cast! {
/// A finished document with metadata and page frames.
#[derive(Debug, Default, Clone)]
pub struct Document {
- /// The page frames.
- pub pages: Vec<Frame>,
+ /// The document's finished pages.
+ pub pages: Vec<Page>,
/// The document's title.
pub title: Option<EcoString>,
/// The document's author.
diff --git a/crates/typst/src/model/numbering.rs b/crates/typst/src/model/numbering.rs
index b15bfc52..07495cbc 100644
--- a/crates/typst/src/model/numbering.rs
+++ b/crates/typst/src/model/numbering.rs
@@ -1,4 +1,3 @@
-use std::num::NonZeroUsize;
use std::str::FromStr;
use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
@@ -7,7 +6,6 @@ use ecow::{eco_format, EcoString, EcoVec};
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{cast, func, Func, Str, Value};
-use crate::layout::{PdfPageLabel, PdfPageLabelStyle};
use crate::text::Case;
/// Applies a numbering to a sequence of numbers.
@@ -89,49 +87,6 @@ impl Numbering {
})
}
- /// Create a new `PdfNumbering` from a `Numbering` applied to a page
- /// number.
- pub fn apply_pdf(&self, number: usize) -> Option<PdfPageLabel> {
- let Numbering::Pattern(pat) = self else {
- return None;
- };
-
- let Some((prefix, kind, case)) = pat.pieces.first() else {
- return None;
- };
-
- // If there is a suffix, we cannot use the common style optimisation,
- // since PDF does not provide a suffix field.
- let mut style = None;
- if pat.suffix.is_empty() {
- use {NumberingKind as Kind, PdfPageLabelStyle as Style};
- match (kind, case) {
- (Kind::Arabic, _) => style = Some(Style::Arabic),
- (Kind::Roman, Case::Lower) => style = Some(Style::LowerRoman),
- (Kind::Roman, Case::Upper) => style = Some(Style::UpperRoman),
- (Kind::Letter, Case::Lower) if number <= 26 => {
- style = Some(Style::LowerAlpha)
- }
- (Kind::Letter, Case::Upper) if number <= 26 => {
- style = Some(Style::UpperAlpha)
- }
- _ => {}
- }
- }
-
- // Prefix and offset depend on the style: If it is supported by the PDF
- // spec, we use the given prefix and an offset. Otherwise, everything
- // goes into prefix.
- let prefix = if style.is_none() {
- Some(pat.apply(&[number]))
- } else {
- (!prefix.is_empty()).then(|| prefix.clone())
- };
-
- let offset = style.and(NonZeroUsize::new(number));
- Some(PdfPageLabel { prefix, style, offset })
- }
-
/// Trim the prefix suffix if this is a pattern.
pub fn trimmed(mut self) -> Self {
if let Self::Pattern(pattern) = &mut self {
diff --git a/tests/fuzz/src/compile.rs b/tests/fuzz/src/compile.rs
index c6345051..883b9ea3 100644
--- a/tests/fuzz/src/compile.rs
+++ b/tests/fuzz/src/compile.rs
@@ -67,7 +67,7 @@ fuzz_target!(|text: &str| {
let mut tracer = Tracer::new();
if let Ok(document) = typst::compile(&world, &mut tracer) {
if let Some(page) = document.pages.first() {
- std::hint::black_box(typst_render::render(page, 1.0, Color::WHITE));
+ std::hint::black_box(typst_render::render(&page.frame, 1.0, Color::WHITE));
}
}
comemo::evict(10);
diff --git a/tests/src/benches.rs b/tests/src/benches.rs
index 6d5f3133..8a521419 100644
--- a/tests/src/benches.rs
+++ b/tests/src/benches.rs
@@ -75,7 +75,7 @@ fn bench_render(iai: &mut Iai) {
let world = BenchWorld::new();
let mut tracer = Tracer::new();
let document = typst::compile(&world, &mut tracer).unwrap();
- iai.run(|| typst_render::render(&document.pages[0], 1.0, Color::WHITE))
+ iai.run(|| typst_render::render(&document.pages[0].frame, 1.0, Color::WHITE))
}
struct BenchWorld {
diff --git a/tests/src/tests.rs b/tests/src/tests.rs
index dd465a70..61a2f7d6 100644
--- a/tests/src/tests.rs
+++ b/tests/src/tests.rs
@@ -33,7 +33,7 @@ use typst::diag::{bail, FileError, FileResult, Severity, SourceDiagnostic, StrRe
use typst::eval::Tracer;
use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value};
use typst::introspection::Meta;
-use typst::layout::{Abs, Frame, FrameItem, Margin, PageElem, Transform};
+use typst::layout::{Abs, Frame, FrameItem, Margin, Page, PageElem, Transform};
use typst::model::Document;
use typst::syntax::{FileId, Source, SyntaxNode, VirtualPath};
use typst::text::{Font, FontBook, TextElem, TextSize};
@@ -423,7 +423,7 @@ fn test(
let mut output = String::new();
let mut ok = true;
let mut updated = false;
- let mut frames = vec![];
+ let mut pages = vec![];
let mut line = 0;
let mut header_configuration = None;
let mut compare_ever = false;
@@ -490,13 +490,13 @@ fn test(
ok &= part_ok;
compare_ever |= compare_here;
- frames.extend(part_frames);
+ pages.extend(part_frames);
}
line += part.lines().count() + 1;
}
- let document = Document { pages: frames, ..Default::default() };
+ let document = Document { pages, ..Default::default() };
if compare_ever {
if let Some(pdf_path) = pdf_path {
let pdf_data = typst_pdf::pdf(
@@ -514,11 +514,12 @@ fn test(
}
}
- let canvas = render(&document.pages);
+ let canvas = render(&document);
fs::create_dir_all(png_path.parent().unwrap()).unwrap();
canvas.save_png(png_path).unwrap();
- let svg = typst_svg::svg_merged(&document.pages, Abs::pt(5.0));
+ let svg = typst_svg::svg_merged(&document, Abs::pt(5.0));
+
fs::create_dir_all(svg_path.parent().unwrap()).unwrap();
std::fs::write(svg_path, svg.as_bytes()).unwrap();
@@ -595,7 +596,7 @@ fn test_part(
header_configuration: &TestConfig,
rng: &mut LinearShift,
verbose: bool,
-) -> (bool, bool, Vec<Frame>) {
+) -> (bool, bool, Vec<Page>) {
let source = world.set(src_path, text);
if world.print.syntax {
writeln!(output, "Syntax Tree:\n{:#?}\n", source.root()).unwrap();
@@ -1038,19 +1039,19 @@ fn test_spans_impl(output: &mut String, node: &SyntaxNode, within: Range<u64>) -
}
/// Draw all frames into one image with padding in between.
-fn render(frames: &[Frame]) -> sk::Pixmap {
+fn render(document: &Document) -> sk::Pixmap {
let pixel_per_pt = 2.0;
let padding = Abs::pt(5.0);
- for frame in frames {
+ for page in &document.pages {
let limit = Abs::cm(100.0);
- if frame.width() > limit || frame.height() > limit {
- panic!("overlarge frame: {:?}", frame.size());
+ if page.frame.width() > limit || page.frame.height() > limit {
+ panic!("overlarge frame: {:?}", page.frame.size());
}
}
let mut pixmap = typst_render::render_merged(
- frames,
+ document,
pixel_per_pt,
Color::WHITE,
padding,
@@ -1059,11 +1060,12 @@ fn render(frames: &[Frame]) -> sk::Pixmap {
let padding = (pixel_per_pt * padding.to_pt() as f32).round();
let [x, mut y] = [padding; 2];
- for frame in frames {
+ for page in &document.pages {
let ts =
sk::Transform::from_scale(pixel_per_pt, pixel_per_pt).post_translate(x, y);
- render_links(&mut pixmap, ts, frame);
- y += (pixel_per_pt * frame.height().to_pt() as f32).round().max(1.0) + padding;
+ render_links(&mut pixmap, ts, &page.frame);
+ y += (pixel_per_pt * page.frame.height().to_pt() as f32).round().max(1.0)
+ + padding;
}
pixmap