Skip to content

Commit

Permalink
Chunks and renumbering (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje authored Oct 4, 2023
1 parent c01bbc4 commit 441dc20
Show file tree
Hide file tree
Showing 22 changed files with 1,254 additions and 754 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ keywords = ["pdf", "writer"]
[dependencies]
bitflags = "1.1"
itoa = "1"
memchr = "2"
ryu = "1"

[dev-dependencies]
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ A step-by-step PDF writer.
pdf-writer = "0.8"
```

The entry point into the API is the main `PdfWriter`, which constructs the
document into one big internal buffer. The top-level writer has many methods to
create specialized writers for specific PDF objects. These all follow the same
general pattern: They borrow the main buffer mutably, expose a builder pattern
for writing individual fields in a strongly typed fashion and finish up the
object when dropped.
The entry point into the API is the main `Pdf`, which constructs the document
into one big internal buffer. The top-level writer has many methods to create
specialized writers for specific PDF objects. These all follow the same general
pattern: They borrow the main buffer mutably, expose a builder pattern for
writing individual fields in a strongly typed fashion and finish up the object
when dropped.

There are a few more top-level structs with internal buffers, like the builder
for `Content` streams, but wherever possible buffers are borrowed from parent
Expand All @@ -24,24 +24,24 @@ writers to minimize allocations.
The following example creates a PDF with a single, empty A4 page.

```rust
use pdf_writer::{PdfWriter, Rect, Ref};
use pdf_writer::{Pdf, Rect, Ref};

// Define some indirect reference ids we'll use.
let catalog_id = Ref::new(1);
let page_tree_id = Ref::new(2);
let page_id = Ref::new(3);

// Write a document catalog and a page tree with one A4 page that uses no resources.
let mut writer = PdfWriter::new();
writer.catalog(catalog_id).pages(page_tree_id);
writer.pages(page_tree_id).kids([page_id]).count(1);
writer.page(page_id)
let mut pdf = Pdf::new();
pdf.catalog(catalog_id).pages(page_tree_id);
pdf.pages(page_tree_id).kids([page_id]).count(1);
pdf.page(page_id)
.parent(page_tree_id)
.media_box(Rect::new(0.0, 0.0, 595.0, 842.0))
.resources();

// Finish with cross-reference table and trailer and write to file.
std::fs::write("target/empty.pdf", writer.finish())?;
std::fs::write("target/empty.pdf", pdf.finish())?;
```

For more examples, check out the [examples folder] in the repository.
Expand Down
8 changes: 4 additions & 4 deletions benches/oneshot.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::Write;

use pdf_writer::{Content, PdfWriter, Rect, Ref};
use pdf_writer::{Content, Pdf, Rect, Ref};

fn bench_alloc() -> Vec<u8> {
Vec::with_capacity(16)
Expand Down Expand Up @@ -50,12 +50,12 @@ fn bench_content() -> Vec<u8> {
c.finish()
}

fn bench_new() -> PdfWriter {
PdfWriter::new()
fn bench_new() -> Pdf {
Pdf::new()
}

fn bench_full() -> Vec<u8> {
let mut w = PdfWriter::new();
let mut w = Pdf::new();
w.catalog(Ref::new(1));
w.page(Ref::new(2)).media_box(Rect::new(0.0, 0.0, 595.0, 842.0));
w.stream(Ref::new(3), &b"ABCDEFG"[..]);
Expand Down
16 changes: 8 additions & 8 deletions examples/hello.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! This example gives you a first introduction on how to use pdf-writer.

use pdf_writer::types::{ActionType, AnnotationType, BorderType};
use pdf_writer::{Content, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str, TextStr};

fn main() -> std::io::Result<()> {
// Start writing.
let mut writer = PdfWriter::new();
let mut pdf = Pdf::new();

// Define some indirect reference ids we'll use.
let catalog_id = Ref::new(1);
Expand All @@ -16,13 +16,13 @@ fn main() -> std::io::Result<()> {
let font_name = Name(b"F1");

// Write the document catalog with a reference to the page tree.
writer.catalog(catalog_id).pages(page_tree_id);
pdf.catalog(catalog_id).pages(page_tree_id);

// Write the page tree with a single child page.
writer.pages(page_tree_id).kids([page_id]).count(1);
pdf.pages(page_tree_id).kids([page_id]).count(1);

// Write a page.
let mut page = writer.page(page_id);
let mut page = pdf.page(page_id);

// Set the size to A4 (measured in points) using `media_box` and set the
// text object we'll write later as the page's contents.
Expand Down Expand Up @@ -68,7 +68,7 @@ fn main() -> std::io::Result<()> {
// Specify the font we want to use. Because Helvetica is one of the 14 base
// fonts shipped with every PDF reader, we don't have to embed any font
// data.
writer.type1_font(font_id).base_font(Name(b"Helvetica"));
pdf.type1_font(font_id).base_font(Name(b"Helvetica"));

// Write a line of text, with the font specified in the resource list
// before, at a font size of 14.0, starting at coordinates (108.0, 734.0)
Expand All @@ -83,11 +83,11 @@ fn main() -> std::io::Result<()> {
content.next_line(108.0, 734.0);
content.show(Str(b"Hello World from Rust!"));
content.end_text();
writer.stream(content_id, &content.finish());
pdf.stream(content_id, &content.finish());

// Finish writing (this automatically creates the cross-reference table and
// file trailer) and retrieve the resulting byte buffer.
let buf: Vec<u8> = writer.finish();
let buf: Vec<u8> = pdf.finish();

// Write the thing to a file.
std::fs::write("target/hello.pdf", buf)
Expand Down
16 changes: 8 additions & 8 deletions examples/icc.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! This example shows how to use ICC-based color spaces.

use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Finish, Name, PdfWriter, Rect, Ref};
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref};

fn main() -> std::io::Result<()> {
// Start writing.
let mut writer = PdfWriter::new();
let mut pdf = Pdf::new();

// Define some indirect reference ids we'll use.
let catalog_id = Ref::new(1);
Expand All @@ -23,11 +23,11 @@ fn main() -> std::io::Result<()> {
let color_space_name = Name(b"sRGB");

// Set up the page tree. For more details see `hello.rs`.
writer.catalog(catalog_id).pages(page_tree_id);
writer.pages(page_tree_id).kids([page_id]).count(1);
pdf.catalog(catalog_id).pages(page_tree_id);
pdf.pages(page_tree_id).kids([page_id]).count(1);

// Write a page.
let mut page = writer.page(page_id);
let mut page = pdf.page(page_id);

// Create an A4 page.
page.media_box(Rect::new(0.0, 0.0, 595.0, 842.0));
Expand Down Expand Up @@ -79,14 +79,14 @@ fn main() -> std::io::Result<()> {
content.close_and_stroke();

// Write the content stream.
writer.stream(content_id, &content.finish());
pdf.stream(content_id, &content.finish());

// Read the ICC profile from a file.
let icc_data = std::fs::read("examples/sRGB_v4.icc")?;
// Start writing the ICC profile stream. In production use, you would
// compress the data stream with the `FlateDecode` filter. Check the
// `image.rs` example for details.
let mut icc_profile = writer.icc_profile(icc_id, &icc_data);
let mut icc_profile = pdf.icc_profile(icc_id, &icc_data);

// PDF requires metadata about the ICC profile. We provide it as entries in
// the stream dictionary. The `n` entry is required and specifies the number
Expand All @@ -104,5 +104,5 @@ fn main() -> std::io::Result<()> {
icc_profile.finish();

// Write the thing to a file.
std::fs::write("target/icc.pdf", writer.finish())
std::fs::write("target/icc.pdf", pdf.finish())
}
18 changes: 9 additions & 9 deletions examples/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

use image::{ColorType, GenericImageView, ImageFormat};
use miniz_oxide::deflate::{compress_to_vec_zlib, CompressionLevel};
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref};
use pdf_writer::{Content, Filter, Finish, Name, Pdf, Rect, Ref};

fn main() -> std::io::Result<()> {
// Start writing.
let mut writer = PdfWriter::new();
let mut pdf = Pdf::new();

// Define some indirect reference ids we'll use.
let catalog_id = Ref::new(1);
Expand All @@ -18,12 +18,12 @@ fn main() -> std::io::Result<()> {
let image_name = Name(b"Im1");

// Set up the page tree. For more details see `hello.rs`.
writer.catalog(catalog_id).pages(page_tree_id);
writer.pages(page_tree_id).kids([page_id]).count(1);
pdf.catalog(catalog_id).pages(page_tree_id);
pdf.pages(page_tree_id).kids([page_id]).count(1);

// Specify one A4 page and map the image name "Im1" to the id of the
// embedded image stream.
let mut page = writer.page(page_id);
let mut page = pdf.page(page_id);
let a4 = Rect::new(0.0, 0.0, 595.0, 842.0);
page.media_box(a4);
page.parent(page_tree_id);
Expand Down Expand Up @@ -74,7 +74,7 @@ fn main() -> std::io::Result<()> {
};

// Write the stream for the image we want to embed.
let mut image = writer.image_xobject(image_id, &encoded);
let mut image = pdf.image_xobject(image_id, &encoded);
image.filter(filter);
image.width(dynamic.width() as i32);
image.height(dynamic.height() as i32);
Expand All @@ -87,7 +87,7 @@ fn main() -> std::io::Result<()> {

// Add SMask if the image has transparency.
if let Some(encoded) = &mask {
let mut s_mask = writer.image_xobject(s_mask_id, encoded);
let mut s_mask = pdf.image_xobject(s_mask_id, encoded);
s_mask.filter(filter);
s_mask.width(dynamic.width() as i32);
s_mask.height(dynamic.height() as i32);
Expand Down Expand Up @@ -117,8 +117,8 @@ fn main() -> std::io::Result<()> {
content.transform([w, 0.0, 0.0, h, x, y]);
content.x_object(image_name);
content.restore_state();
writer.stream(content_id, &content.finish());
pdf.stream(content_id, &content.finish());

// Write the thing to a file.
std::fs::write("target/image.pdf", writer.finish())
std::fs::write("target/image.pdf", pdf.finish())
}
14 changes: 7 additions & 7 deletions examples/separations.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! This example shows how to use Separation color spaces.

use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Finish, Name, PdfWriter, Rect, Ref};
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref};

fn main() -> std::io::Result<()> {
// Start writing.
let mut writer = PdfWriter::new();
let mut pdf = Pdf::new();

// Define some indirect reference ids we'll use.
let catalog_id = Ref::new(1);
Expand All @@ -20,11 +20,11 @@ fn main() -> std::io::Result<()> {
let hot_pink_name = Name(b"AcmePink");

// Set up the page tree. For more details see `hello.rs`.
writer.catalog(catalog_id).pages(page_tree_id);
writer.pages(page_tree_id).kids([page_id]).count(1);
pdf.catalog(catalog_id).pages(page_tree_id);
pdf.pages(page_tree_id).kids([page_id]).count(1);

// Write a page.
let mut page = writer.page(page_id);
let mut page = pdf.page(page_id);

// Create an A4 page.
let width = 595.0;
Expand Down Expand Up @@ -172,8 +172,8 @@ fn main() -> std::io::Result<()> {
}

// Write the content stream.
writer.stream(content_id, &content.finish());
pdf.stream(content_id, &content.finish());

// Write the thing to a file.
std::fs::write("target/separations.pdf", writer.finish())
std::fs::write("target/separations.pdf", pdf.finish())
}
32 changes: 32 additions & 0 deletions src/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,35 @@ impl BorderType {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_annotations() {
test!(
crate::tests::slice(|w| {
let mut page = w.page(Ref::new(1));
let mut annots = page.annotations();
annots.push().rect(Rect::new(0.0, 0.0, 1.0, 1.0));
annots.push().rect(Rect::new(1.0, 1.0, 0.0, 0.0));
annots.finish();
page.bleed_box(Rect::new(-100.0, -100.0, 100.0, 100.0));
}),
b"1 0 obj",
b"<<",
b" /Type /Page",
b" /Annots [<<",
b" /Type /Annot",
b" /Rect [0 0 1 1]",
b" >> <<",
b" /Type /Annot",
b" /Rect [1 1 0 0]",
b" >>]",
b" /BleedBox [-100 -100 100 100]",
b">>",
b"endobj\n\n",
);
}
}
12 changes: 12 additions & 0 deletions src/buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub trait BufExt {
fn push_decimal(&mut self, value: f32);
fn push_hex(&mut self, value: u8);
fn push_hex_u16(&mut self, value: u16);
fn push_octal(&mut self, value: u8);
}

impl BufExt for Vec<u8> {
Expand Down Expand Up @@ -67,4 +68,15 @@ impl BufExt for Vec<u8> {
self.push_hex((value >> 8) as u8);
self.push_hex(value as u8);
}

#[inline]
fn push_octal(&mut self, value: u8) {
fn octal(b: u8) -> u8 {
b'0' + b
}

self.push(octal(value >> 6));
self.push(octal((value >> 3) & 63));
self.push(octal(value & 7));
}
}
Loading

0 comments on commit 441dc20

Please sign in to comment.