diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-01-17 21:50:35 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2024-01-17 21:53:20 +0100 |
| commit | 6ac71eeaf7b68dab07f75bd1a480810481fa9b73 (patch) | |
| tree | d452e7323200fe56d61a34b91c8b98826d60978c /crates/typst-pdf | |
| parent | 50741209a8f4c5e91d35281eb44b7425b3d022b2 (diff) | |
Add `Page` struct
To get rid of the Meta hack where numbering and things like that are stored in the frame.
Diffstat (limited to 'crates/typst-pdf')
| -rw-r--r-- | crates/typst-pdf/src/lib.rs | 4 | ||||
| -rw-r--r-- | crates/typst-pdf/src/page.rs | 119 |
2 files changed, 99 insertions, 24 deletions
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()), }, } } |
