summaryrefslogtreecommitdiff
path: root/crates/typst-pdf
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 /crates/typst-pdf
parent50741209a8f4c5e91d35281eb44b7425b3d022b2 (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.rs4
-rw-r--r--crates/typst-pdf/src/page.rs119
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()),
},
}
}