Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the Buf type #47

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benches/oneshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn bench_content() -> Vec<u8> {
c.save_state();
c.set_flatness(10);
c.restore_state();
c.finish()
c.finish().to_bytes()
}

fn bench_new() -> Pdf {
Expand Down
175 changes: 154 additions & 21 deletions src/buf.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,139 @@
use super::Primitive;
use std::ops::Deref;
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved

/// Additional methods for byte buffers.
pub trait BufExt {
fn push_val<T: Primitive>(&mut self, value: T);
fn push_int(&mut self, value: i32);
fn push_float(&mut self, value: f32);
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);
/// Track the limits of data types used in a buffer.
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, PartialEq, Debug, Default)]
pub struct Limits {
int: i32,
real: f32,
name_len: usize,
str_len: usize,
array_len: usize,
dict_entries: usize,
}

impl BufExt for Vec<u8> {
impl Limits {
/// Create a new `Limits` struct with all values initialized to zero.
pub fn new() -> Self {
Self::default()
}

pub(crate) fn register_int(&mut self, val: i32) {
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
self.int = self.int.max(val.abs());
}

pub(crate) fn register_real(&mut self, val: f32) {
self.real = self.real.max(val.abs());
}

pub(crate) fn register_name_len(&mut self, len: usize) {
self.name_len = self.name_len.max(len);
}

pub(crate) fn register_str_len(&mut self, len: usize) {
self.str_len = self.str_len.max(len);
}

pub(crate) fn register_array_len(&mut self, len: usize) {
self.array_len = self.array_len.max(len);
}

pub(crate) fn register_dict_entries(&mut self, len: usize) {
self.dict_entries = self.dict_entries.max(len);
}

/// Get the absolute value of the largest positive/negative integer number.
pub fn int(&self) -> i32 {
self.int
}

/// Get the absolute value of the largest positive/negative real number.
pub fn real(&self) -> f32 {
self.real
}

/// Get the maximum length of any used name.
pub fn name_len(&self) -> usize {
self.name_len
}

/// Get the maximum length of any used array.
pub fn array_len(&self) -> usize {
self.array_len
}

/// Get the maximum number of entries in any dictionary.
pub fn dict_entries(&self) -> usize {
self.dict_entries
}

/// Get the maximum length of any used string.
pub fn str_len(&self) -> usize {
self.str_len
}

/// Merge two `Limits` with each other, taking the maximum
/// of each field from both.
pub fn merge(&mut self, other: &Limits) {
self.register_int(other.int);
self.register_real(other.real);
self.register_name_len(other.name_len);
self.register_str_len(other.str_len);
self.register_array_len(other.array_len);
self.register_dict_entries(other.dict_entries);
}
}

/// A buffer of arbitrary PDF content.
#[derive(Clone, PartialEq, Debug)]
pub struct Buf {
pub(crate) inner: Vec<u8>,
pub(crate) limits: Limits,
}

impl Deref for Buf {
type Target = Vec<u8>;
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl Buf {
pub(crate) fn new() -> Self {
Self { inner: Vec::new(), limits: Limits::new() }
}

pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
inner: Vec::with_capacity(capacity),
limits: Limits::new(),
}
}

/// Get the underlying bytes of the buffer.
pub fn to_bytes(self) -> Vec<u8> {
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
self.inner
}

/// Return the limits of the buffer.
pub fn limits(&self) -> &Limits {
&self.limits
}

#[inline]
fn push_val<T: Primitive>(&mut self, value: T) {
pub(crate) fn push_val<T: Primitive>(&mut self, value: T) {
value.write(self);
}

#[inline]
fn push_int(&mut self, value: i32) {
self.extend(itoa::Buffer::new().format(value).as_bytes());
pub(crate) fn push_int(&mut self, value: i32) {
self.limits.register_int(value);
self.extend_slice(itoa::Buffer::new().format(value).as_bytes());
}

#[inline]
fn push_float(&mut self, value: f32) {
pub(crate) fn push_float(&mut self, value: f32) {
// Don't write the decimal point if we don't need it.
// Also, integer formatting is way faster.
if value as i32 as f32 == value {
Expand All @@ -35,22 +145,40 @@ impl BufExt for Vec<u8> {

/// Like `push_float`, but forces the decimal point.
#[inline]
fn push_decimal(&mut self, value: f32) {
pub(crate) fn push_decimal(&mut self, value: f32) {
self.limits.register_real(value);

if value == 0.0 || (value.abs() > 1e-6 && value.abs() < 1e12) {
self.extend(ryu::Buffer::new().format(value).as_bytes());
self.extend_slice(ryu::Buffer::new().format(value).as_bytes());
} else {
#[inline(never)]
fn write_extreme(buf: &mut Vec<u8>, value: f32) {
fn write_extreme(buf: &mut Buf, value: f32) {
use std::io::Write;
write!(buf, "{}", value).unwrap();
write!(buf.inner, "{}", value).unwrap();
}

write_extreme(self, value);
}
}

#[inline]
fn push_hex(&mut self, value: u8) {
pub(crate) fn extend_slice(&mut self, other: &[u8]) {
self.inner.extend(other);
}

#[inline]
pub(crate) fn extend(&mut self, other: &Buf) {
self.limits.merge(&other.limits);
self.inner.extend(&other.inner);
}

#[inline]
pub(crate) fn push(&mut self, b: u8) {
self.inner.push(b);
}

#[inline]
pub(crate) fn push_hex(&mut self, value: u8) {
fn hex(b: u8) -> u8 {
if b < 10 {
b'0' + b
Expand All @@ -64,13 +192,13 @@ impl BufExt for Vec<u8> {
}

#[inline]
fn push_hex_u16(&mut self, value: u16) {
pub(crate) fn push_hex_u16(&mut self, value: u16) {
self.push_hex((value >> 8) as u8);
self.push_hex(value as u8);
}

#[inline]
fn push_octal(&mut self, value: u8) {
pub(crate) fn push_octal(&mut self, value: u8) {
fn octal(b: u8) -> u8 {
b'0' + b
}
Expand All @@ -79,4 +207,9 @@ impl BufExt for Vec<u8> {
self.push(octal((value >> 3) & 7));
self.push(octal(value & 7));
}

#[inline]
pub(crate) fn reserve(&mut self, additional: usize) {
self.inner.reserve(additional)
}
}
16 changes: 11 additions & 5 deletions src/chunk.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use crate::buf::Buf;

/// A builder for a collection of indirect PDF objects.
///
Expand All @@ -12,7 +13,7 @@ use super::*;
/// it at a time).
#[derive(Clone)]
pub struct Chunk {
pub(crate) buf: Vec<u8>,
pub(crate) buf: Buf,
pub(crate) offsets: Vec<(Ref, usize)>,
}

Expand All @@ -25,7 +26,7 @@ impl Chunk {

/// Create a new chunk with the specified initial capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self { buf: Vec::with_capacity(capacity), offsets: vec![] }
Self { buf: Buf::with_capacity(capacity), offsets: vec![] }
}

/// The number of bytes that were written so far.
Expand All @@ -40,10 +41,15 @@ impl Chunk {
self.buf.as_slice()
}

/// Return the limits of the chunk.
pub fn limits(&self) -> &Limits {
self.buf.limits()
}

/// Add all objects from another chunk to this one.
pub fn extend(&mut self, other: &Chunk) {
let base = self.len();
self.buf.extend_from_slice(&other.buf);
self.buf.extend(&other.buf);
self.offsets
.extend(other.offsets.iter().map(|&(id, offset)| (id, base + offset)));
}
Expand Down Expand Up @@ -252,7 +258,7 @@ impl Chunk {
/// file.
///
/// You can create the content bytes using a [`Content`] builder.
pub fn form_xobject<'a>(&'a mut self, id: Ref, content: &'a [u8]) -> FormXObject<'a> {
pub fn form_xobject<'a>(&'a mut self, id: Ref, content: &'a [u8]) -> FormXObject {
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
FormXObject::start(self.stream(id, content))
}

Expand Down Expand Up @@ -314,7 +320,7 @@ impl Chunk {
pub fn stream_shading<'a>(
&'a mut self,
id: Ref,
content: &'a [u8],
content: &'a Buf,
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
) -> StreamShading<'a> {
StreamShading::start(self.stream(id, content))
}
Expand Down
22 changes: 13 additions & 9 deletions src/content.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use super::*;
use crate::buf::Buf;

/// A builder for a content stream.
pub struct Content {
buf: Vec<u8>,
buf: Buf,
q_depth: usize,
}

Expand All @@ -17,7 +18,7 @@ impl Content {

/// Create a new content stream with the specified initial buffer capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self { buf: Vec::with_capacity(capacity), q_depth: 0 }
Self { buf: Buf::with_capacity(capacity), q_depth: 0 }
}

/// Start writing an arbitrary operation.
Expand All @@ -27,9 +28,9 @@ impl Content {
}

/// Return the raw constructed byte stream.
pub fn finish(mut self) -> Vec<u8> {
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved
pub fn finish(mut self) -> Buf {
if self.buf.last() == Some(&b'\n') {
self.buf.pop();
self.buf.inner.pop();
}
self.buf
}
Expand All @@ -39,14 +40,14 @@ impl Content {
///
/// This struct is created by [`Content::op`].
pub struct Operation<'a> {
buf: &'a mut Vec<u8>,
buf: &'a mut Buf,
op: &'a str,
first: bool,
}

impl<'a> Operation<'a> {
#[inline]
pub(crate) fn start(buf: &'a mut Vec<u8>, op: &'a str) -> Self {
pub(crate) fn start(buf: &'a mut Buf, op: &'a str) -> Self {
Self { buf, op, first: true }
}

Expand Down Expand Up @@ -87,7 +88,7 @@ impl Drop for Operation<'_> {
if !self.first {
self.buf.push(b' ');
}
self.buf.extend(self.op.as_bytes());
self.buf.extend_slice(self.op.as_bytes());
self.buf.push(b'\n');
}
}
Expand Down Expand Up @@ -1655,7 +1656,7 @@ mod tests {
.restore_state();

assert_eq!(
content.finish(),
content.finish().to_bytes(),
b"q\n1 2 3 4 re\nf\n[7 2] 4 d\n/MyImage Do\n2 3.5 /MyPattern scn\nQ"
);
}
Expand All @@ -1675,6 +1676,9 @@ mod tests {
.show(Str(b"CD"));
content.end_text();

assert_eq!(content.finish(), b"/F1 12 Tf\nBT\n[] TJ\n[(AB) 2 (CD)] TJ\nET");
assert_eq!(
content.finish().to_bytes(),
b"/F1 12 Tf\nBT\n[] TJ\n[(AB) 2 (CD)] TJ\nET"
);
}
}
Loading