diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index ea6126308..2c80ce3f6 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 7481b050c..8f6d1366d 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 ad40b9879..f25314629 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, frames: &[Frame]) -> Html; + fn example(&self, hash: u128, source: Option, document: &Document) -> Html; /// Determine the commits between two tags. fn commits(&self, from: &str, to: &str) -> Vec; @@ -789,7 +790,7 @@ mod tests { None } - fn example(&self, _: u128, _: Option, _: &[Frame]) -> Html { + fn example(&self, _: u128, _: Option, _: &Document) -> Html { Html::new(String::new()) } diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index f11288781..c8b7343aa 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 63a9dd978..80d4d67f7 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, + pages: Vec, /// 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 d3bbcb74d..6f6728482 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, + /// Based on the numbering pattern. + /// + /// If `None` or numbering is a function, the field will be empty. + style: Option, + /// 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, +} + +/// 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 { + 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, /// The page's used resources pub resources: HashMap, + /// The page's PDF label. + label: Option, } /// 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, pub content: Content, state: State, saves: Vec, @@ -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 34e9b7b3d..5718a7c0c 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 bb451cbe9..035ec330e 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::(); - 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::(); 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 9c3e73c60..75882edf0 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 25f67400e..625125106 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), - /// 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 3c6d88e7b..3a21078e4 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 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, - /// Based on the numbering pattern. - /// - /// If `None` or numbering is a function, the field will be empty. - pub style: Option, - /// 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, -} - -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 c36be5149..989e5ea22 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 { styles: StyleChain, page_counter: &mut ManualPageCounter, extend_to: Option, - ) -> SourceResult { + ) -> SourceResult> { // 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 { 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 { } // 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 { // 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 { 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, + /// 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 fedecc889..dc291b44d 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 { .to_packed::()? .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, + /// The document's finished pages. + pub pages: Vec, /// The document's title. pub title: Option, /// The document's author. diff --git a/crates/typst/src/model/numbering.rs b/crates/typst/src/model/numbering.rs index b15bfc523..07495cbc5 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 { - 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 c6345051a..883b9ea32 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 6d5f31335..8a521419d 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 dd465a70e..61a2f7d68 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) { +) -> (bool, bool, Vec) { 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) - } /// 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