Skip to content

Commit

Permalink
First prototype implementation of page labels
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenzV committed Aug 18, 2024
1 parent 75a4f01 commit 6182c75
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 33 deletions.
101 changes: 87 additions & 14 deletions src/object/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::serialize::{Object, RegisterableObject, SerializerContext};
use crate::stream::Stream;
use crate::util::RectExt;
use pdf_writer::types::NumberingStyle;
use pdf_writer::writers::NumberTree;
use pdf_writer::{Chunk, Finish, Ref, TextStr};
use std::num::{NonZeroI32, NonZeroU32};
use tiny_skia_path::{Rect, Size};
Expand All @@ -13,14 +14,17 @@ pub(crate) struct Page {
pub stream: Stream,
/// The media box of the page.
pub media_box: Rect,
/// The label of the page.
pub page_label: PageLabel,
}

impl Page {
/// Create a new page.
pub fn new(size: Size, stream: Stream) -> Self {
pub fn new(size: Size, stream: Stream, page_label: PageLabel) -> Self {
Self {
stream,
media_box: size.to_rect(0.0, 0.0).unwrap(),
page_label,
}
}
}
Expand Down Expand Up @@ -51,7 +55,7 @@ impl Object for Page {

stream.finish();

sc.add_page_ref(root_ref);
sc.add_page_info(root_ref, self.page_label);

chunk
}
Expand All @@ -62,24 +66,32 @@ impl RegisterableObject for Page {}
// TODO: Make sure that page 0 is always included.

/// A page label.
#[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) struct PageLabel {
#[derive(Debug, Hash, Eq, PartialEq, Default, Clone)]
pub struct PageLabel {
/// The numbering style of the page label.
style: NumberingStyle,
pub style: Option<NumberingStyle>,
/// The prefix of the page label.
prefix: Option<String>,
/// The start numeric value of the page label.
offset: NonZeroI32,
pub prefix: Option<String>,
/// The numeric value of the page label.
pub offset: Option<NonZeroU32>,
}

impl PageLabel {
pub fn new(style: NumberingStyle, prefix: Option<String>, offset: NonZeroI32) -> Self {
pub fn new(
style: Option<NumberingStyle>,
prefix: Option<String>,
offset: Option<NonZeroU32>,
) -> Self {
Self {
style,
prefix,
offset,
}
}

pub(crate) fn is_empty(&self) -> bool {
self.style.is_none() && self.prefix.is_none() && self.offset.is_none()
}
}

impl Object for PageLabel {
Expand All @@ -88,13 +100,17 @@ impl Object for PageLabel {
let mut label = chunk
.indirect(root_ref)
.start::<pdf_writer::writers::PageLabel>();
label.style(self.style);
if let Some(style) = self.style {
label.style(style);
}

if let Some(prefix) = &self.prefix {
label.prefix(TextStr(prefix));
}

label.offset(self.offset.get());
if let Some(offset) = self.offset {
label.offset(i32::try_from(offset.get()).unwrap());
}

label.finish();

Expand All @@ -104,6 +120,61 @@ impl Object for PageLabel {

impl RegisterableObject for PageLabel {}

#[derive(Hash)]
pub struct PageLabelContainer {
labels: Vec<PageLabel>,
}

impl PageLabelContainer {
pub fn new(labels: Vec<PageLabel>) -> Option<Self> {
return if labels.iter().all(|f| f.is_empty()) {
None
} else {
Some(PageLabelContainer { labels })
};
}
}

impl Object for PageLabelContainer {
fn serialize_into(self, sc: &mut SerializerContext, root_ref: Ref) -> Chunk {
// Will always contain at least one entry, since we ensured that a PageLabelContainer cannot
// be empty
let mut filtered_entries = vec![];
let mut prev: Option<PageLabel> = None;

for (i, label) in self.labels.into_iter().enumerate() {
if let Some(n_prev) = &prev {
if n_prev.style != label.style
|| n_prev.prefix != label.prefix
|| n_prev.offset.map(|n| n.get()) != label.offset.map(|n| n.get() + 1)
{
filtered_entries.push((i, label.clone()));
prev = Some(label);
}
} else {
filtered_entries.push((i, label.clone()));
prev = Some(label);
}
}

let mut chunk = Chunk::new();
let mut num_tree = chunk.indirect(root_ref).start::<NumberTree<Ref>>();
let mut nums = num_tree.nums();

for (page_num, label) in filtered_entries {
let label_ref = sc.add(label);
nums.insert(page_num as i32, label_ref);
}

nums.finish();
num_tree.finish();

chunk
}
}

impl RegisterableObject for PageLabelContainer {}

#[cfg(test)]
mod tests {
use crate::object::page::{Page, PageLabel};
Expand All @@ -113,7 +184,7 @@ mod tests {
use crate::test_utils::check_snapshot;
use crate::Fill;
use pdf_writer::types::NumberingStyle;
use std::num::NonZeroI32;
use std::num::{NonZeroI32, NonZeroU32};
use tiny_skia_path::{PathBuilder, Rect, Size};

#[test]
Expand All @@ -132,6 +203,7 @@ mod tests {
let page = Page::new(
Size::from_wh(200.0, 200.0).unwrap(),
stream_builder.finish(),
PageLabel::default(),
);
sc.add(page);

Expand All @@ -157,6 +229,7 @@ mod tests {
let page = Page::new(
Size::from_wh(200.0, 200.0).unwrap(),
stream_builder.finish(),
PageLabel::default(),
);
sc.add(page);

Expand All @@ -168,9 +241,9 @@ mod tests {
let mut sc = SerializerContext::new(SerializeSettings::default_test());

let page_label = PageLabel::new(
NumberingStyle::Arabic,
Some(NumberingStyle::Arabic),
Some("P".to_string()),
NonZeroI32::new(2).unwrap(),
NonZeroU32::new(2),
);

sc.add(page_label);
Expand Down
38 changes: 22 additions & 16 deletions src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use crate::object::cid_font::CIDFont;
use crate::object::color_space::luma::SGray;
use crate::object::color_space::rgb::Srgb;
use crate::object::color_space::{DEVICE_GRAY, DEVICE_RGB};
use crate::object::page::{PageLabel, PageLabelContainer};
use crate::object::type3_font::Type3Font;
use crate::resource::{ColorSpaceEnum, FontResource};
use crate::stream::PdfFont;
use crate::util::NameExt;
use fontdb::{Database, ID};
use pdf_writer::{Chunk, Filter, Pdf, Ref};
use pdf_writer::{Chunk, Filter, Finish, Pdf, Ref};
use siphasher::sip128::{Hasher128, SipHasher13};
use skrifa::instance::Location;
use std::borrow::Cow;
Expand Down Expand Up @@ -80,6 +81,7 @@ pub struct SerializerContext {
catalog_ref: Ref,
page_tree_ref: Ref,
page_refs: Vec<Ref>,
page_labels: Vec<PageLabel>,
cached_mappings: HashMap<u128, Ref>,
chunks: Vec<Chunk>,
chunks_len: usize,
Expand Down Expand Up @@ -114,6 +116,7 @@ impl SerializerContext {
page_tree_ref,
catalog_ref,
page_refs: vec![],
page_labels: vec![],
chunks_len: 0,
font_map: HashMap::new(),
serialize_settings,
Expand All @@ -124,8 +127,9 @@ impl SerializerContext {
self.page_tree_ref
}

pub fn add_page_ref(&mut self, ref_: Ref) {
pub fn add_page_info(&mut self, ref_: Ref, label: PageLabel) {
self.page_refs.push(ref_);
self.page_labels.push(label);
}

pub fn new_ref(&mut self) -> Ref {
Expand Down Expand Up @@ -272,6 +276,10 @@ impl SerializerContext {

// Always needs to be called.
pub fn finish(mut self) -> Pdf {
if let Some(container) = PageLabelContainer::new(self.page_labels.clone()) {
self.add(container);
}

// Write fonts
// TODO: Make more efficient
let fonts = std::mem::take(&mut self.font_map);
Expand All @@ -298,20 +306,20 @@ impl SerializerContext {
pdf.set_binary_marker(&[b'A', b'A', b'A', b'A']);
}

// This basically just exists so that for unit tests, we don't print the catalog
// and page tree.
if !self.page_refs.is_empty() {
let mut page_tree_chunk = Chunk::new();
let mut page_tree_chunk = Chunk::new();

page_tree_chunk
.pages(self.page_tree_ref)
.count(self.page_refs.len() as i32)
.kids(self.page_refs);
page_tree_chunk
.pages(self.page_tree_ref)
.count(self.page_refs.len() as i32)
.kids(self.page_refs);

self.chunks_len += page_tree_chunk.len();
pdf.catalog(self.catalog_ref).pages(self.page_tree_ref);
pdf.extend(&page_tree_chunk);
}
self.chunks_len += page_tree_chunk.len();

let mut catalog = pdf.catalog(self.catalog_ref);
catalog.pages(self.page_tree_ref);
catalog.finish();

pdf.extend(&page_tree_chunk);

for part_chunk in self.chunks.drain(..) {
pdf.extend(&part_chunk);
Expand Down Expand Up @@ -416,5 +424,3 @@ fn hex_encode(data: &[u8]) -> Vec<u8> {
.collect::<String>()
.into_bytes()
}

pub struct PageLabel {}
23 changes: 20 additions & 3 deletions src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::font::Font;
use crate::object::color_space::ColorSpace;
use crate::object::image::Image;
use crate::object::mask::Mask;
use crate::object::page::Page;
use crate::object::page::{Page, PageLabel};
use crate::object::shading_function::ShadingFunction;
use crate::serialize::SerializerContext;
use crate::stream::{ContentBuilder, Stream, TestGlyph};
Expand Down Expand Up @@ -198,11 +198,28 @@ impl<'a> Surface<'a> {
pub struct PageBuilder<'a> {
sc: &'a mut SerializerContext,
size: Size,
page_label: PageLabel,
}

impl<'a> PageBuilder<'a> {
pub(crate) fn new(sc: &'a mut SerializerContext, size: Size) -> Self {
Self { sc, size }
Self {
sc,
size,
page_label: PageLabel::default(),
}
}

pub(crate) fn new_with(
sc: &'a mut SerializerContext,
size: Size,
page_label: PageLabel,
) -> Self {
Self {
sc,
size,
page_label,
}
}

pub fn surface(&mut self) -> Surface {
Expand All @@ -218,7 +235,7 @@ impl<'a> PageBuilder<'a> {
));

let finish_fn = Box::new(|stream, sc: &mut SerializerContext| {
let page = Page::new(self.size, stream);
let page = Page::new(self.size, stream, self.page_label.clone());
sc.add(page);
});

Expand Down
15 changes: 15 additions & 0 deletions tests/snapshots/cid_font/latin_modern_four_glyphs.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
%PDF-1.7
%AAAA

1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj

2 0 obj
<<
/Type /Pages
/Count 0
/Kids []
>>
endobj

3 0 obj
<<
/Type /Font
Expand Down
15 changes: 15 additions & 0 deletions tests/snapshots/cid_font/noto_sans_two_glyphs.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
%PDF-1.7
%AAAA

1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj

2 0 obj
<<
/Type /Pages
/Count 0
/Kids []
>>
endobj

3 0 obj
<<
/Type /Font
Expand Down
Loading

0 comments on commit 6182c75

Please sign in to comment.