use std::convert::TryFrom;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::num::NonZeroI32;
use super::*;
pub trait Primitive {
fn write(self, buf: &mut Buf);
}
impl<T: Primitive> Primitive for &T
where
T: Copy,
{
#[inline]
fn write(self, buf: &mut Buf) {
(*self).write(buf);
}
}
impl Primitive for bool {
#[inline]
fn write(self, buf: &mut Buf) {
if self {
buf.extend(b"true");
} else {
buf.extend(b"false");
}
}
}
impl Primitive for i32 {
#[inline]
fn write(self, buf: &mut Buf) {
buf.push_int(self);
}
}
impl Primitive for f32 {
#[inline]
fn write(self, buf: &mut Buf) {
buf.push_float(self);
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Str<'a>(pub &'a [u8]);
impl Str<'_> {
fn is_balanced(self) -> bool {
let mut depth = 0;
for &byte in self.0 {
match byte {
b'(' => depth += 1,
b')' if depth > 0 => depth -= 1,
b')' => return false,
_ => {}
}
}
depth == 0
}
}
impl Primitive for Str<'_> {
fn write(self, buf: &mut Buf) {
buf.limits.register_str_len(self.0.len());
if self.0.iter().all(|b| b.is_ascii()) {
buf.reserve(self.0.len());
buf.inner.push(b'(');
let mut balanced = None;
for &byte in self.0 {
match byte {
b'(' | b')' => {
if !*balanced
.get_or_insert_with(|| byte != b')' && self.is_balanced())
{
buf.push(b'\\');
}
buf.push(byte);
}
b'\\' => buf.extend(br"\\"),
b' '..=b'~' => buf.push(byte),
b'\n' => buf.extend(br"\n"),
b'\r' => buf.extend(br"\r"),
b'\t' => buf.extend(br"\t"),
b'\x08' => buf.extend(br"\b"),
b'\x0c' => buf.extend(br"\f"),
_ => {
buf.push(b'\\');
buf.push_octal(byte);
}
}
}
buf.push(b')');
} else {
buf.reserve(2 + 2 * self.0.len());
buf.push(b'<');
for &byte in self.0 {
buf.push_hex(byte);
}
buf.push(b'>');
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TextStr<'a>(pub &'a str);
impl Primitive for TextStr<'_> {
fn write(self, buf: &mut Buf) {
buf.limits.register_str_len(self.0.len());
if self.0.bytes().all(|b| matches!(b, 32..=126)) {
Str(self.0.as_bytes()).write(buf);
} else {
buf.reserve(6 + 4 * self.0.len());
write_utf16be_text_str_header(buf);
for value in self.0.encode_utf16() {
buf.push_hex_u16(value);
}
write_utf16be_text_str_footer(buf);
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LanguageIdentifier {
lang: [u8; 2],
region: Option<[u8; 2]>,
}
impl LanguageIdentifier {
pub fn new(lang: [u8; 2], region: Option<[u8; 2]>) -> Self {
Self { lang, region }
}
pub fn from_lang(lang: &str) -> Option<Self> {
let lang = Self::str_to_code(lang)?;
Some(Self::new(lang, None))
}
pub fn from_lang_region(lang: &str, region: &str) -> Option<Self> {
let lang = Self::str_to_code(lang)?;
let region = Self::str_to_code(region)?;
Some(Self::new(lang, Some(region)))
}
fn len(self) -> usize {
if self.region.is_some() {
4
} else {
2
}
}
fn str_to_code(string: &str) -> Option<[u8; 2]> {
if string.chars().all(|c| c.is_ascii_alphanumeric()) {
string.as_bytes().try_into().ok()
} else {
None
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TextStrWithLang<'a, 'b>(pub &'b [(LanguageIdentifier, &'a str)]);
impl<'a, 'b> Primitive for TextStrWithLang<'a, 'b> {
fn write(self, buf: &mut Buf) {
let mut len = 0;
let mut buf_len = 6;
for (lang, text) in self.0 {
len += text.len() + lang.len() + 2;
buf_len += 4 * text.len() + lang.len() * 2 + 4;
}
buf.limits.register_str_len(len);
buf.reserve(buf_len);
write_utf16be_text_str_header(buf);
for (lang, text) in self.0 {
write_utf16be_lang_code(*lang, buf);
for value in text.encode_utf16() {
buf.push_hex_u16(value);
}
}
write_utf16be_text_str_footer(buf);
}
}
fn write_utf16be_text_str_header(buf: &mut Buf) {
buf.push(b'<');
buf.push_hex(254);
buf.push_hex(255);
}
fn write_utf16be_text_str_footer(buf: &mut Buf) {
buf.push(b'>');
}
fn write_utf16be_lang_code(lang: LanguageIdentifier, buf: &mut Buf) {
buf.push_hex_u16(0x001B);
buf.push_hex_u16(u16::from(lang.lang[0]));
buf.push_hex_u16(u16::from(lang.lang[1]));
if let Some(region) = lang.region {
buf.push_hex_u16(u16::from(region[0]));
buf.push_hex_u16(u16::from(region[1]));
}
buf.push_hex_u16(0x001B);
}
pub trait TextStrLike: Primitive {}
impl<'a> TextStrLike for TextStr<'a> {}
impl<'a, 'b> TextStrLike for TextStrWithLang<'a, 'b> {}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Name<'a>(pub &'a [u8]);
impl Primitive for Name<'_> {
fn write(self, buf: &mut Buf) {
buf.limits.register_name_len(self.0.len());
buf.reserve(1 + self.0.len());
buf.push(b'/');
for &byte in self.0 {
if byte != b'#' && matches!(byte, b'!'..=b'~') && is_regular_character(byte) {
buf.push(byte);
} else {
buf.push(b'#');
buf.push_hex(byte);
}
}
}
}
fn is_regular_character(byte: u8) -> bool {
!matches!(
byte,
b'\0'
| b'\t'
| b'\n'
| b'\x0C'
| b'\r'
| b' '
| b'('
| b')'
| b'<'
| b'>'
| b'['
| b']'
| b'{'
| b'}'
| b'/'
| b'%'
)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Null;
impl Primitive for Null {
#[inline]
fn write(self, buf: &mut Buf) {
buf.extend(b"null");
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Ref(NonZeroI32);
impl Ref {
#[inline]
#[track_caller]
pub const fn new(id: i32) -> Ref {
let option = if id > 0 { NonZeroI32::new(id) } else { None };
match option {
Some(val) => Self(val),
None => panic!("indirect reference out of valid range"),
}
}
#[inline]
pub const fn get(self) -> i32 {
self.0.get()
}
#[inline]
pub const fn next(self) -> Self {
Self::new(self.get() + 1)
}
#[inline]
pub fn bump(&mut self) -> Self {
let prev = *self;
*self = self.next();
prev
}
}
impl Primitive for Ref {
#[inline]
fn write(self, buf: &mut Buf) {
buf.push_int(self.0.get());
buf.extend(b" 0 R");
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Rect {
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
impl Rect {
#[inline]
pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
Self { x1, y1, x2, y2 }
}
#[inline]
pub fn to_quad_points(self) -> [f32; 8] {
[self.x1, self.y1, self.x2, self.y1, self.x2, self.y2, self.x1, self.y2]
}
}
impl Primitive for Rect {
#[inline]
fn write(self, buf: &mut Buf) {
buf.push(b'[');
buf.push_val(self.x1);
buf.push(b' ');
buf.push_val(self.y1);
buf.push(b' ');
buf.push_val(self.x2);
buf.push(b' ');
buf.push_val(self.y2);
buf.push(b']');
buf.limits.register_array_len(4);
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Date {
year: u16,
month: Option<u8>,
day: Option<u8>,
hour: Option<u8>,
minute: Option<u8>,
second: Option<u8>,
utc_offset_hour: Option<i8>,
utc_offset_minute: u8,
}
impl Date {
#[inline]
pub fn new(year: u16) -> Self {
Self {
year: year.min(9999),
month: None,
day: None,
hour: None,
minute: None,
second: None,
utc_offset_hour: None,
utc_offset_minute: 0,
}
}
#[inline]
pub fn month(mut self, month: u8) -> Self {
self.month = Some(month.clamp(1, 12));
self
}
#[inline]
pub fn day(mut self, day: u8) -> Self {
self.day = Some(day.clamp(1, 31));
self
}
#[inline]
pub fn hour(mut self, hour: u8) -> Self {
self.hour = Some(hour.min(23));
self
}
#[inline]
pub fn minute(mut self, minute: u8) -> Self {
self.minute = Some(minute.min(59));
self
}
#[inline]
pub fn second(mut self, second: u8) -> Self {
self.second = Some(second.min(59));
self
}
#[inline]
pub fn utc_offset_hour(mut self, hour: i8) -> Self {
self.utc_offset_hour = Some(hour.clamp(-23, 23));
self
}
#[inline]
pub fn utc_offset_minute(mut self, minute: u8) -> Self {
self.utc_offset_minute = minute.min(59);
self
}
}
impl Primitive for Date {
fn write(self, buf: &mut Buf) {
buf.extend(b"(D:");
(|| {
write!(buf.inner, "{:04}", self.year).unwrap();
write!(buf.inner, "{:02}", self.month?).unwrap();
write!(buf.inner, "{:02}", self.day?).unwrap();
write!(buf.inner, "{:02}", self.hour?).unwrap();
write!(buf.inner, "{:02}", self.minute?).unwrap();
write!(buf.inner, "{:02}", self.second?).unwrap();
let utc_offset_hour = self.utc_offset_hour?;
if utc_offset_hour == 0 && self.utc_offset_minute == 0 {
buf.push(b'Z');
} else {
write!(
buf.inner,
"{:+03}'{:02}",
utc_offset_hour, self.utc_offset_minute
)
.unwrap();
}
Some(())
})();
buf.push(b')');
}
}
#[must_use = "not consuming this leaves the writer in an inconsistent state"]
pub struct Obj<'a> {
buf: &'a mut Buf,
indirect: bool,
indent: u8,
}
impl<'a> Obj<'a> {
#[inline]
pub(crate) fn direct(buf: &'a mut Buf, indent: u8) -> Self {
Self { buf, indirect: false, indent }
}
#[inline]
pub(crate) fn indirect(buf: &'a mut Buf, id: Ref) -> Self {
buf.push_int(id.get());
buf.extend(b" 0 obj\n");
Self { buf, indirect: true, indent: 0 }
}
#[inline]
pub fn primitive<T: Primitive>(self, value: T) {
value.write(self.buf);
if self.indirect {
self.buf.extend(b"\nendobj\n\n");
}
}
#[inline]
pub fn array(self) -> Array<'a> {
self.start()
}
#[inline]
pub fn dict(self) -> Dict<'a> {
self.start()
}
#[inline]
pub fn start<W: Writer<'a>>(self) -> W {
W::start(self)
}
}
pub trait Writer<'a> {
fn start(obj: Obj<'a>) -> Self;
}
pub trait Rewrite<'a> {
type Output: Writer<'a>;
}
pub struct Array<'a> {
buf: &'a mut Buf,
indirect: bool,
indent: u8,
len: i32,
}
writer!(Array: |obj| {
obj.buf.push(b'[');
Self {
buf: obj.buf,
indirect: obj.indirect,
indent: obj.indent,
len: 0,
}
});
impl<'a> Array<'a> {
#[inline]
pub fn len(&self) -> i32 {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn push(&mut self) -> Obj<'_> {
if self.len != 0 {
self.buf.push(b' ');
}
self.len += 1;
Obj::direct(self.buf, self.indent)
}
#[inline]
pub fn item<T: Primitive>(&mut self, value: T) -> &mut Self {
self.push().primitive(value);
self
}
#[inline]
pub fn items<T: Primitive>(
&mut self,
values: impl IntoIterator<Item = T>,
) -> &mut Self {
for value in values {
self.item(value);
}
self
}
#[inline]
pub fn typed<T>(self) -> TypedArray<'a, T> {
TypedArray::wrap(self)
}
}
impl Drop for Array<'_> {
#[inline]
fn drop(&mut self) {
self.buf.limits.register_array_len(self.len() as usize);
self.buf.push(b']');
if self.indirect {
self.buf.extend(b"\nendobj\n\n");
}
}
}
pub struct TypedArray<'a, T> {
array: Array<'a>,
phantom: PhantomData<fn() -> T>,
}
impl<'a, T> Writer<'a> for TypedArray<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { array: obj.array(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for TypedArray<'_, T> {
type Output = TypedArray<'a, T>;
}
impl<'a, T> TypedArray<'a, T> {
#[inline]
pub fn wrap(array: Array<'a>) -> Self {
Self { array, phantom: PhantomData }
}
#[inline]
pub fn len(&self) -> i32 {
self.array.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn item(&mut self, value: T) -> &mut Self
where
T: Primitive,
{
self.array.item(value);
self
}
#[inline]
pub fn items(&mut self, values: impl IntoIterator<Item = T>) -> &mut Self
where
T: Primitive,
{
self.array.items(values);
self
}
#[inline]
pub fn push<'b>(&'b mut self) -> <T as Rewrite<'b>>::Output
where
T: Writer<'a> + Rewrite<'b>,
{
<T as Rewrite>::Output::start(self.array.push())
}
}
pub struct Dict<'a> {
buf: &'a mut Buf,
indirect: bool,
indent: u8,
len: i32,
}
writer!(Dict: |obj| {
obj.buf.extend(b"<<");
Self {
buf: obj.buf,
indirect: obj.indirect,
indent: obj.indent.saturating_add(2),
len: 0,
}
});
impl<'a> Dict<'a> {
#[inline]
pub fn len(&self) -> i32 {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn insert(&mut self, key: Name) -> Obj<'_> {
self.len += 1;
self.buf.push(b'\n');
for _ in 0..self.indent {
self.buf.push(b' ');
}
self.buf.push_val(key);
self.buf.push(b' ');
Obj::direct(self.buf, self.indent)
}
#[inline]
pub fn pair<T: Primitive>(&mut self, key: Name, value: T) -> &mut Self {
self.insert(key).primitive(value);
self
}
pub fn pairs<'n, T: Primitive>(
&mut self,
pairs: impl IntoIterator<Item = (Name<'n>, T)>,
) -> &mut Self {
for (key, value) in pairs {
self.pair(key, value);
}
self
}
#[inline]
pub fn typed<T>(self) -> TypedDict<'a, T> {
TypedDict::wrap(self)
}
}
impl Drop for Dict<'_> {
#[inline]
fn drop(&mut self) {
self.buf.limits.register_dict_entries(self.len as usize);
if self.len != 0 {
self.buf.push(b'\n');
for _ in 0..self.indent - 2 {
self.buf.push(b' ');
}
}
self.buf.extend(b">>");
if self.indirect {
self.buf.extend(b"\nendobj\n\n");
}
}
}
pub struct TypedDict<'a, T> {
dict: Dict<'a>,
phantom: PhantomData<fn() -> T>,
}
impl<'a, T> Writer<'a> for TypedDict<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { dict: obj.dict(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for TypedDict<'_, T> {
type Output = TypedDict<'a, T>;
}
impl<'a, T> TypedDict<'a, T> {
#[inline]
pub fn wrap(dict: Dict<'a>) -> Self {
Self { dict, phantom: PhantomData }
}
#[inline]
pub fn len(&self) -> i32 {
self.dict.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn pair(&mut self, key: Name, value: T) -> &mut Self
where
T: Primitive,
{
self.dict.pair(key, value);
self
}
#[inline]
pub fn pairs<'n>(
&mut self,
pairs: impl IntoIterator<Item = (Name<'n>, T)>,
) -> &mut Self
where
T: Primitive,
{
self.dict.pairs(pairs);
self
}
#[inline]
pub fn insert<'b>(&'b mut self, key: Name) -> <T as Rewrite<'b>>::Output
where
T: Writer<'a> + Rewrite<'b>,
{
<T as Rewrite>::Output::start(self.dict.insert(key))
}
}
pub struct Stream<'a> {
dict: ManuallyDrop<Dict<'a>>,
data: &'a [u8],
}
impl<'a> Stream<'a> {
pub(crate) fn start(obj: Obj<'a>, data: &'a [u8]) -> Self {
assert!(obj.indirect);
let mut dict = obj.dict();
dict.pair(
Name(b"Length"),
i32::try_from(data.len()).unwrap_or_else(|_| {
panic!("data length (is `{}`) must be <= i32::MAX", data.len());
}),
);
Self { dict: ManuallyDrop::new(dict), data }
}
pub fn filter(&mut self, filter: Filter) -> &mut Self {
self.pair(Name(b"Filter"), filter.to_name());
self
}
pub fn decode_parms(&mut self) -> DecodeParms<'_> {
self.insert(Name(b"DecodeParms")).start()
}
}
impl Drop for Stream<'_> {
fn drop(&mut self) {
let dict_len = self.dict.len as usize;
self.dict.buf.limits.register_dict_entries(dict_len);
self.dict.buf.extend(b"\n>>");
self.dict.buf.extend(b"\nstream\n");
self.dict.buf.extend(self.data.as_ref());
self.dict.buf.extend(b"\nendstream");
self.dict.buf.extend(b"\nendobj\n\n");
}
}
deref!('a, Stream<'a> => Dict<'a>, dict);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum Filter {
AsciiHexDecode,
Ascii85Decode,
LzwDecode,
FlateDecode,
RunLengthDecode,
CcittFaxDecode,
Jbig2Decode,
DctDecode,
JpxDecode,
Crypt,
}
impl Filter {
pub(crate) fn to_name(self) -> Name<'static> {
match self {
Self::AsciiHexDecode => Name(b"ASCIIHexDecode"),
Self::Ascii85Decode => Name(b"ASCII85Decode"),
Self::LzwDecode => Name(b"LZWDecode"),
Self::FlateDecode => Name(b"FlateDecode"),
Self::RunLengthDecode => Name(b"RunLengthDecode"),
Self::CcittFaxDecode => Name(b"CCITTFaxDecode"),
Self::Jbig2Decode => Name(b"JBIG2Decode"),
Self::DctDecode => Name(b"DCTDecode"),
Self::JpxDecode => Name(b"JPXDecode"),
Self::Crypt => Name(b"Crypt"),
}
}
}
pub struct DecodeParms<'a> {
dict: Dict<'a>,
}
writer!(DecodeParms: |obj| Self { dict: obj.dict() });
impl DecodeParms<'_> {
pub fn predictor(&mut self, predictor: Predictor) -> &mut Self {
self.pair(Name(b"Predictor"), predictor.to_i32());
self
}
pub fn colors(&mut self, colors: i32) -> &mut Self {
if colors <= 0 {
panic!("`Colors` must be greater than 0");
}
self.pair(Name(b"Colors"), colors);
self
}
pub fn bits_per_component(&mut self, bits: i32) -> &mut Self {
if ![1, 2, 4, 8, 16].contains(&bits) {
panic!("`BitsPerComponent` must be one of 1, 2, 4, 8, or 16");
}
self.pair(Name(b"BitsPerComponent"), bits);
self
}
pub fn columns(&mut self, columns: i32) -> &mut Self {
self.pair(Name(b"Columns"), columns);
self
}
pub fn early_change(&mut self, early_change: bool) -> &mut Self {
self.pair(Name(b"EarlyChange"), if early_change { 1 } else { 0 });
self
}
}
impl DecodeParms<'_> {
pub fn k(&mut self, k: i32) -> &mut Self {
self.pair(Name(b"K"), k);
self
}
pub fn end_of_line(&mut self, eol: bool) -> &mut Self {
self.pair(Name(b"EndOfLine"), eol);
self
}
pub fn encoded_byte_align(&mut self, encoded_byte_align: bool) -> &mut Self {
self.pair(Name(b"EncodedByteAlign"), encoded_byte_align);
self
}
pub fn rows(&mut self, rows: i32) -> &mut Self {
self.pair(Name(b"Rows"), rows);
self
}
pub fn end_of_block(&mut self, end_of_block: bool) -> &mut Self {
self.pair(Name(b"EndOfBlock"), end_of_block);
self
}
pub fn black_is_1(&mut self, black_is_1: bool) -> &mut Self {
self.pair(Name(b"BlackIs1"), black_is_1);
self
}
pub fn damaged_rows_before_error(&mut self, count: i32) -> &mut Self {
self.pair(Name(b"DamagedRowsBeforeError"), count);
self
}
}
impl DecodeParms<'_> {
pub fn jbig2_globals(&mut self, globals: Ref) -> &mut Self {
self.pair(Name(b"JBIG2Globals"), globals);
self
}
}
impl DecodeParms<'_> {
pub fn color_transform(&mut self, color_transform: bool) -> &mut Self {
self.pair(Name(b"ColorTransform"), if color_transform { 1 } else { 0 });
self
}
}
impl DecodeParms<'_> {
pub fn crypt_type(&mut self) -> &mut Self {
self.pair(Name(b"Type"), Name(b"CryptFilterDecodeParms"));
self
}
pub fn name(&mut self, name: Name) -> &mut Self {
self.pair(Name(b"Name"), name);
self
}
}
deref!('a, DecodeParms<'a> => Dict<'a>, dict);
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum Predictor {
#[default]
None,
Tiff,
PngNone,
PngSub,
PngUp,
PngAverage,
PngPaeth,
PngOptimum,
}
impl Predictor {
fn to_i32(self) -> i32 {
match self {
Self::None => 1,
Self::Tiff => 2,
Self::PngNone => 10,
Self::PngSub => 11,
Self::PngUp => 12,
Self::PngAverage => 13,
Self::PngPaeth => 14,
Self::PngOptimum => 15,
}
}
}
pub struct NameTree<'a, T> {
dict: Dict<'a>,
phantom: PhantomData<T>,
}
impl<'a, T> Writer<'a> for NameTree<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { dict: obj.dict(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for NameTree<'_, T> {
type Output = NameTree<'a, T>;
}
impl<T> NameTree<'_, T> {
pub fn kids(&mut self) -> TypedArray<'_, Ref> {
self.dict.insert(Name(b"Kids")).array().typed()
}
pub fn names(&mut self) -> NameTreeEntries<'_, T> {
self.dict.insert(Name(b"Names")).start()
}
pub fn limits(&mut self, min: Name, max: Name) -> &mut Self {
self.dict.insert(Name(b"Limits")).array().typed().items([min, max]);
self
}
}
pub struct NameTreeEntries<'a, T> {
arr: Array<'a>,
phantom: PhantomData<T>,
}
impl<'a, T> Writer<'a> for NameTreeEntries<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { arr: obj.array(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for NameTreeEntries<'_, T> {
type Output = NameTreeEntries<'a, T>;
}
impl<T> NameTreeEntries<'_, T>
where
T: Primitive,
{
pub fn insert(&mut self, key: Str, value: T) -> &mut Self {
self.arr.item(key);
self.arr.item(value);
self
}
}
pub struct NumberTree<'a, T> {
dict: Dict<'a>,
phantom: PhantomData<T>,
}
impl<'a, T> Writer<'a> for NumberTree<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { dict: obj.dict(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for NumberTree<'_, T> {
type Output = NumberTree<'a, T>;
}
impl<T> NumberTree<'_, T> {
pub fn kids(&mut self) -> TypedArray<'_, Ref> {
self.dict.insert(Name(b"Kids")).array().typed()
}
pub fn nums(&mut self) -> NumberTreeEntries<'_, T> {
self.dict.insert(Name(b"Nums")).start()
}
pub fn limits(&mut self, min: i32, max: i32) -> &mut Self {
self.dict.insert(Name(b"Limits")).array().typed().items([min, max]);
self
}
}
pub struct NumberTreeEntries<'a, T> {
arr: Array<'a>,
phantom: PhantomData<T>,
}
impl<'a, T> Writer<'a> for NumberTreeEntries<'a, T> {
fn start(obj: Obj<'a>) -> Self {
Self { arr: obj.array(), phantom: PhantomData }
}
}
impl<'a, T> Rewrite<'a> for NumberTreeEntries<'_, T> {
type Output = NumberTreeEntries<'a, T>;
}
impl<T> NumberTreeEntries<'_, T>
where
T: Primitive,
{
pub fn insert(&mut self, key: i32, value: T) -> &mut Self {
self.arr.item(key);
self.arr.item(value);
self
}
}
pub trait Finish: Sized {
#[inline]
fn finish(self) {}
}
impl<T> Finish for T {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primitive_objects() {
test_primitive!(true, b"true");
test_primitive!(false, b"false");
test_primitive!(78, b"78");
test_primitive!(4.22, b"4.22");
test_primitive!(1.184e-7, b"0.0000001184");
test_primitive!(4.2e13, b"42000000000000");
test_primitive!(Ref::new(7), b"7 0 R");
test_primitive!(Null, b"null");
test_primitive!(Str(b"Hello, World!"), b"(Hello, World!)");
test_primitive!(Str(b"()"), br"(())");
test_primitive!(Str(b")()"), br"(\)\(\))");
test_primitive!(Str(b"()(())"), br"(()(()))");
test_primitive!(Str(b"(()))"), br"(\(\(\)\)\))");
test_primitive!(Str(b"\\"), br"(\\)");
test_primitive!(Str(b"\n\ta"), br"(\n\ta)");
test_primitive!(Str(br"\n"), br"(\\n)");
test_primitive!(Str(b"a\x14b"), br"(a\024b)");
test_primitive!(Str(b"\xFF\xAA"), b"<FFAA>");
test_primitive!(Str(b"\x0A\x7F\x1F"), br"(\n\177\037)");
test_primitive!(TextStr("Hallo"), b"(Hallo)");
test_primitive!(TextStr("😀!"), b"<FEFFD83DDE000021>");
test_primitive!(Name(b"Filter"), b"/Filter");
test_primitive!(Name(b"A B"), br"/A#20B");
test_primitive!(Name(b"~+c"), br"/~+c");
test_primitive!(Name(b"/A-B"), br"/#2FA-B");
test_primitive!(Name(b"<A>"), br"/#3CA#3E");
test_primitive!(Name(b"#"), br"/#23");
test_primitive!(Name(b"\n"), br"/#0A");
}
#[test]
fn test_dates() {
test_primitive!(Date::new(2021), b"(D:2021)");
test_primitive!(Date::new(2021).month(30), b"(D:202112)");
let date = Date::new(2020).month(3).day(17).hour(1).minute(2).second(3);
test_primitive!(date, b"(D:20200317010203)");
test_primitive!(date.utc_offset_hour(0), b"(D:20200317010203Z)");
test_primitive!(date.utc_offset_hour(4), b"(D:20200317010203+04'00)");
test_primitive!(
date.utc_offset_hour(-17).utc_offset_minute(10),
b"(D:20200317010203-17'10)"
);
}
#[test]
fn test_arrays() {
test_obj!(|obj| obj.array(), b"[]");
test_obj!(|obj| obj.array().item(12).item(Null), b"[12 null]");
test_obj!(|obj| obj.array().typed().items(vec![1, 2, 3]), b"[1 2 3]");
test_obj!(
|obj| {
let mut array = obj.array();
array.push().array().typed().items(vec![1, 2]);
array.item(3);
},
b"[[1 2] 3]",
);
}
#[test]
fn test_dicts() {
test_obj!(|obj| obj.dict(), b"<<>>");
test_obj!(
|obj| obj.dict().pair(Name(b"Quality"), Name(b"Good")),
b"<<\n /Quality /Good\n>>",
);
test_obj!(
|obj| {
obj.dict().pair(Name(b"A"), 1).pair(Name(b"B"), 2);
},
b"<<\n /A 1\n /B 2\n>>",
);
}
#[test]
fn test_streams() {
let mut w = Pdf::new();
w.stream(Ref::new(1), &b"Hi there!"[..]).filter(Filter::Crypt);
test!(
w.finish(),
b"%PDF-1.7\n%\x80\x80\x80\x80\n",
b"1 0 obj",
b"<<\n /Length 9\n /Filter /Crypt\n>>",
b"stream",
b"Hi there!",
b"endstream",
b"endobj\n",
b"xref",
b"0 2",
b"0000000000 65535 f\r",
b"0000000016 00000 n\r",
b"trailer",
b"<<\n /Size 2\n>>",
b"startxref\n94\n%%EOF",
)
}
}