use std::io::Write;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use pdfboss_core::crypt::Sha256;
use pdfboss_core::object::decode_text_string;
use pdfboss_core::xref::{parse_section_at, startxref};
use pdfboss_core::{Dict, Document, FastMap, Name, ObjRef, Object, Page, Stream, XrefKind};
use crate::error::{Error, Result};
use crate::importer::{rect_array, Importer};
use crate::pdf::{text_string, Date, Metadata};
use crate::ser::{serialize_dict, serialize_object};
use crate::writer::{WriteOptions, Writer, XrefStyle};
const FORM_NAME: &str = "PdfbossWatermark";
fn free_form_name(base: &Document, pages: &[Page]) -> Result<String> {
let mut candidate = FORM_NAME.to_string();
let mut next = 2;
while xobject_name_taken(base, pages, &candidate)? {
candidate = format!("{FORM_NAME}{next}");
next += 1;
}
Ok(candidate)
}
fn xobject_name_taken(base: &Document, pages: &[Page], candidate: &str) -> Result<bool> {
for page in pages {
if page.object_ref().is_none() {
continue;
}
let Some(existing) = page.resources.get("XObject") else {
continue;
};
let existing = base.resolve(existing).map_err(core_error)?;
let Some(dict) = existing.as_dict() else {
continue;
};
if dict.get(candidate).is_some() {
return Ok(true);
}
}
Ok(false)
}
fn fetch_pages(base: &Document) -> Result<Vec<Page>> {
(0..base.page_count())
.map(|index| base.page(index).map_err(core_error))
.collect()
}
fn wrapper_streams(form_name: &str, under: bool) -> (Vec<u8>, Vec<u8>) {
if under {
return (
format!("q /{form_name} Do Q\nq\n").into_bytes(),
b"Q\n".to_vec(),
);
}
(
b"q\n".to_vec(),
format!("Q\nq /{form_name} Do Q\n").into_bytes(),
)
}
fn watermark_rewrite_placed(
base: &Document,
overlay: &Document,
options: WriteOptions,
under: bool,
) -> Result<Vec<u8>> {
let pages = fetch_pages(base)?;
let form_name = free_form_name(base, &pages)?;
let mut writer = Writer::new(options);
let (prefix_bytes, suffix_bytes) = wrapper_streams(&form_name, under);
let prefix = writer.put_stream_raw(Dict::new(), prefix_bytes);
let suffix = writer.put_stream_raw(Dict::new(), suffix_bytes);
let form = overlay_form(&mut writer, overlay)?;
let trailer = &base.xref().trailer;
let root = trailer.get_ref("Root").ok_or(Error::MissingRoot)?;
let mut importer = Importer::new(&mut writer, base)?;
let new_root = importer.reference(root);
let new_info = trailer.get_ref("Info").map(|info| importer.reference(info));
for page in &pages {
let Some(page_ref) = page.object_ref() else {
continue;
};
let dict = marked_page_dict(&mut importer, base, page, form, prefix, suffix, &form_name)?;
importer.substitute(page_ref, dict);
}
importer.finish()?;
if let Some(new_info) = new_info {
writer.set_info(new_info);
}
writer.finish(new_root)
}
pub fn watermark_with(
base: &Document,
overlay: &Document,
options: WriteOptions,
) -> Result<Vec<u8>> {
watermark_rewrite_placed(base, overlay, options, false)
}
pub fn watermark_under_with(
base: &Document,
overlay: &Document,
options: WriteOptions,
) -> Result<Vec<u8>> {
watermark_rewrite_placed(base, overlay, options, true)
}
fn marked_page_dict(
importer: &mut Importer,
base: &Document,
page: &Page,
form: ObjRef,
prefix: ObjRef,
suffix: ObjRef,
form_name: &str,
) -> Result<Object> {
let mut dict = importer.copy_dict(page.dict())?;
let mut resources = importer.copy_dict(&page.resources)?;
let mut xobjects = match page.resources.get("XObject") {
Some(existing) => {
let existing = base.resolve(existing).map_err(core_error)?;
match existing.as_dict() {
Some(d) => importer.copy_dict(d)?,
None => Dict::new(),
}
}
None => Dict::new(),
};
xobjects.insert(name(form_name), Object::Ref(form));
resources.insert(name("XObject"), Object::Dict(xobjects));
dict.insert(name("Resources"), Object::Dict(resources));
dict.insert(name("Type"), Object::Name(name("Page")));
let mut contents = vec![Object::Ref(prefix)];
match page.dict().get("Contents") {
Some(Object::Array(items)) => {
for item in items {
contents.push(importer.copy(item)?);
}
}
Some(Object::Ref(r)) => match base.get(*r).map_err(core_error)? {
Object::Array(items) => {
for item in &items {
contents.push(importer.copy(item)?);
}
}
_ => contents.push(Object::Ref(importer.reference(*r))),
},
_ => {}
}
contents.push(Object::Ref(suffix));
dict.insert(name("Contents"), Object::Array(contents));
Ok(Object::Dict(dict))
}
fn overlay_form(writer: &mut Writer, overlay: &Document) -> Result<ObjRef> {
let page = overlay.page(0).map_err(core_error)?;
let content = page.content(overlay).map_err(core_error)?;
let resources = {
let mut importer = Importer::new(writer, overlay)?;
let resources = importer.copy_dict(&page.resources)?;
importer.finish()?;
resources
};
let mut dict = Dict::new();
dict.insert(name("Type"), Object::Name(name("XObject")));
dict.insert(name("Subtype"), Object::Name(name("Form")));
dict.insert(name("FormType"), Object::Int(1));
dict.insert(name("BBox"), rect_array(page.media_box));
dict.insert(name("Resources"), Object::Dict(resources));
dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
let form = writer.reserve();
writer.fill(
form,
Object::Stream(Stream {
dict,
data: deflate(&content),
}),
)?;
Ok(form)
}
fn watermark_placed(base: &Document, overlay: &Document, under: bool) -> Result<Vec<u8>> {
let pages = fetch_pages(base)?;
let form_name = free_form_name(base, &pages)?;
let mut update = Update::new(base)?;
let form = update.overlay.import_form(overlay)?;
let (prefix_bytes, suffix_bytes) = wrapper_streams(&form_name, under);
let prefix = update
.overlay
.put(Object::Stream(plain_stream(prefix_bytes)));
let suffix = update
.overlay
.put(Object::Stream(plain_stream(suffix_bytes)));
for page in &pages {
let Some(page_ref) = page.object_ref() else {
continue;
};
let mut dict = page.dict().clone();
let mut resources = page.resources.clone();
let mut xobjects = match resources.get("XObject") {
Some(existing) => base
.resolve(existing)
.map_err(core_error)?
.as_dict()
.cloned()
.unwrap_or_default(),
None => Dict::new(),
};
xobjects.insert(name(&form_name), Object::Ref(form));
resources.insert(name("XObject"), Object::Dict(xobjects));
dict.insert(name("Resources"), Object::Dict(resources));
let mut contents = vec![Object::Ref(prefix)];
match dict.get("Contents").cloned() {
Some(Object::Array(items)) => contents.extend(items),
Some(Object::Ref(r)) => match base.get(r).map_err(core_error)? {
Object::Array(items) => contents.extend(items),
_ => contents.push(Object::Ref(r)),
},
_ => {}
}
contents.push(Object::Ref(suffix));
dict.insert(name("Contents"), Object::Array(contents));
update.set(page_ref, Object::Dict(dict));
}
update.bytes()
}
pub fn watermark(base: &Document, overlay: &Document) -> Result<Vec<u8>> {
watermark_placed(base, overlay, false)
}
pub fn watermark_under(base: &Document, overlay: &Document) -> Result<Vec<u8>> {
watermark_placed(base, overlay, true)
}
pub fn rotate_pages(update: &mut Update, pages: &[usize], by: i32) -> Result<()> {
if by % 90 != 0 {
return Err(Error::Other(
"rotation must be a multiple of 90 degrees".to_string(),
));
}
for &index in pages {
let page = update.doc.page(index).map_err(core_error)?;
let Some(page_ref) = page.object_ref() else {
return Err(Error::Other(format!(
"page {} is inlined into /Kids and cannot be edited in place",
index + 1
)));
};
let mut dict = page.dict().clone();
let rotate = (page.rotate + by).rem_euclid(360);
dict.insert(name("Rotate"), Object::Int(i64::from(rotate)));
update.set(page_ref, Object::Dict(dict));
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct OverlayBase {
pub prev: u64,
pub kind: XrefStyle,
pub size: u32,
pub root: ObjRef,
pub info: Option<ObjRef>,
pub id: Option<Object>,
}
impl OverlayBase {
pub fn from_document(doc: &Document) -> Result<OverlayBase> {
if doc.is_encrypted() {
return Err(Error::EncryptedBase);
}
let trailer = &doc.xref().trailer;
let root = trailer.get_ref("Root").ok_or(Error::MissingRoot)?;
let (prev, kind) = match doc.xref().newest_section() {
Some(section) => (section.offset, xref_style(section.kind)),
None => {
let offset = startxref(doc.bytes()).ok_or(Error::MissingStartxref)?;
let kind = xref_style(
parse_section_at(doc.bytes(), offset)
.map_err(core_error)?
.kind,
);
(offset as u64, kind)
}
};
let highest = doc.xref().iter().map(|(num, _)| num).max().unwrap_or(0);
let declared = trailer.get_int("Size").unwrap_or(0).max(0) as u32;
Ok(OverlayBase {
prev,
kind,
size: declared.max(highest + 1),
root,
info: trailer.get_ref("Info"),
id: trailer.get("ID").cloned(),
})
}
}
fn xref_style(kind: XrefKind) -> XrefStyle {
match kind {
XrefKind::Table => XrefStyle::Table,
XrefKind::Stream => XrefStyle::Stream,
}
}
#[derive(Debug, Clone)]
enum Change {
Set(Object),
Free,
}
#[derive(Debug, Clone)]
pub struct Overlay {
base: OverlayBase,
next: u32,
objects: Vec<(ObjRef, Change)>,
imported: FastMap<ObjRef, ObjRef>,
info: Option<ObjRef>,
}
impl Overlay {
pub fn new(base: OverlayBase) -> Overlay {
let next = base.size;
Overlay {
base,
next,
objects: Vec::new(),
imported: FastMap::default(),
info: None,
}
}
pub fn set(&mut self, r: ObjRef, obj: Object) {
if r.num == 0 {
return;
}
self.next = self.next.max(r.num.saturating_add(1));
self.objects.push((r, Change::Set(obj)));
}
pub fn remove(&mut self, r: ObjRef) {
if r.num == 0 {
return;
}
self.next = self.next.max(r.num.saturating_add(1));
let gen = r.gen.saturating_add(1);
self.objects
.push((ObjRef { num: r.num, gen }, Change::Free));
}
pub fn reserve(&mut self) -> ObjRef {
let r = ObjRef {
num: self.next,
gen: 0,
};
self.next += 1;
r
}
pub fn put(&mut self, obj: Object) -> ObjRef {
let r = self.reserve();
self.set(r, obj);
r
}
pub fn set_info(&mut self, r: ObjRef) {
self.info = Some(r);
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
pub fn section(&self, start: u64) -> Result<Vec<u8>> {
if self.is_empty() {
return Err(Error::EmptyUpdate);
}
let mut last: FastMap<u32, usize> = FastMap::default();
for (index, (r, _)) in self.objects.iter().enumerate() {
last.insert(r.num, index);
}
let mut winners: Vec<usize> = last.into_values().collect();
winners.sort_by_key(|&index| self.objects[index].0.num);
let mut out = Vec::new();
let mut rows: Vec<Row> = Vec::with_capacity(winners.len() + 1);
let mut freed: Vec<ObjRef> = Vec::new();
for index in winners {
let (r, change) = &self.objects[index];
match change {
Change::Set(obj) => {
rows.push(Row::InFile(*r, start as usize + out.len()));
write_indirect(&mut out, *r, obj)?;
}
Change::Free => freed.push(*r),
}
}
if !freed.is_empty() {
freed.sort_by_key(|r| r.num);
let head = freed.first().map_or(0, |r| r.num);
rows.push(Row::Free {
num: 0,
gen: 65535,
next: head,
});
for (index, r) in freed.iter().enumerate() {
let next = freed.get(index + 1).map_or(0, |n| n.num);
rows.push(Row::Free {
num: r.num,
gen: r.gen,
next,
});
}
}
rows.sort_by_key(Row::num);
let mut trailer = Dict::new();
trailer.insert(name("Root"), Object::Ref(self.base.root));
if let Some(info) = self.info.or(self.base.info) {
trailer.insert(name("Info"), Object::Ref(info));
}
if let Some(id) = rotated_id(&self.base, &out, &freed) {
trailer.insert(name("ID"), id);
}
trailer.insert(name("Prev"), Object::Int(self.base.prev as i64));
match self.base.kind {
XrefStyle::Stream => finish_stream(&mut out, start, rows, trailer, self.next)?,
XrefStyle::Table => finish_table(&mut out, start, &rows, trailer, self.next)?,
}
Ok(out)
}
pub(crate) fn import_form(&mut self, overlay: &Document) -> Result<ObjRef> {
if overlay.is_encrypted() {
return Err(Error::EncryptedBase);
}
let page = overlay.page(0).map_err(core_error)?;
let content = page.content(overlay).map_err(core_error)?;
let resources = self.import_object(overlay, &Object::Dict(page.resources.clone()))?;
let mut dict = Dict::new();
dict.insert(name("Type"), Object::Name(name("XObject")));
dict.insert(name("Subtype"), Object::Name(name("Form")));
dict.insert(name("FormType"), Object::Int(1));
dict.insert(name("BBox"), rect_array(page.media_box));
dict.insert(name("Resources"), resources);
dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
Ok(self.put(Object::Stream(Stream {
dict,
data: deflate(&content),
})))
}
pub(crate) fn import_object(&mut self, source: &Document, obj: &Object) -> Result<Object> {
Ok(match obj {
Object::Ref(r) => {
if let Some(copied) = self.imported.get(r) {
return Ok(Object::Ref(*copied));
}
let copied = self.reserve();
self.imported.insert(*r, copied);
let body = source.get(*r).map_err(core_error)?;
let body = self.import_object(source, &body)?;
self.objects.push((copied, Change::Set(body)));
Object::Ref(copied)
}
Object::Dict(d) => Object::Dict(self.import_dict(source, d)?),
Object::Array(items) => Object::Array(
items
.iter()
.map(|item| self.import_object(source, item))
.collect::<Result<Vec<Object>>>()?,
),
Object::Stream(s) => {
let mut dict = s.dict.clone();
dict.remove("Length");
Object::Stream(Stream {
dict: self.import_dict(source, &dict)?,
data: s.data.clone(),
})
}
other => other.clone(),
})
}
pub(crate) fn import_dict(&mut self, source: &Document, dict: &Dict) -> Result<Dict> {
let mut out = Dict::new();
for (key, value) in dict.iter() {
out.insert(key.clone(), self.import_object(source, value)?);
}
Ok(out)
}
}
pub fn set_metadata_with(
overlay: &mut Overlay,
existing_info: Option<(ObjRef, Dict)>,
xmp_ref: Option<ObjRef>,
meta: Metadata,
) -> Result<()> {
let (target, existing_dict) = match existing_info {
Some((r, dict)) => (r, Some(dict)),
None => (overlay.reserve(), None),
};
let (dict, merged) = merge_metadata(existing_dict, &meta);
overlay.set(target, Object::Dict(dict));
overlay.set_info(target);
let Some(xmp_ref) = xmp_ref else {
return Ok(());
};
overlay.set(xmp_ref, xmp_metadata_stream(&merged));
Ok(())
}
pub(crate) fn merge_metadata(existing: Option<Dict>, meta: &Metadata) -> (Dict, Metadata) {
let mut dict = existing.unwrap_or_default();
apply_metadata_fields(&mut dict, meta);
let merged = metadata_from_info(&dict);
(dict, merged)
}
pub(crate) fn xmp_metadata_stream(meta: &Metadata) -> Object {
let mut xmp_dict = Dict::new();
xmp_dict.insert(name("Type"), Object::Name(name("Metadata")));
xmp_dict.insert(name("Subtype"), Object::Name(name("XML")));
Object::Stream(Stream {
dict: xmp_dict,
data: crate::xmp::packet(meta),
})
}
pub(crate) fn resolve_dict(doc: &Document, dict: &Dict) -> Dict {
let mut out = Dict::new();
for (key, value) in dict.iter() {
let resolved = doc.resolve(value).unwrap_or_else(|_| value.clone());
out.insert(key.clone(), resolved);
}
out
}
pub(crate) fn catalog_metadata_ref(doc: &Document, root: ObjRef) -> Option<ObjRef> {
let catalog = doc.get(root).ok()?;
match catalog.as_dict()?.get("Metadata")? {
Object::Ref(r) => Some(*r),
_ => None,
}
}
fn apply_metadata_fields(dict: &mut Dict, meta: &Metadata) {
let texts = [
("Title", &meta.title),
("Author", &meta.author),
("Subject", &meta.subject),
("Keywords", &meta.keywords),
("Creator", &meta.creator),
("Producer", &meta.producer),
];
for (key, value) in texts {
if let Some(value) = value {
dict.insert(name(key), text_string(value));
}
}
let dates = [
("CreationDate", meta.creation_date),
("ModDate", meta.modification_date),
];
for (key, value) in dates {
if let Some(date) = value {
dict.insert(name(key), Object::String(date.to_pdf_string().into_bytes()));
}
}
}
fn metadata_from_info(dict: &Dict) -> Metadata {
Metadata {
title: info_text(dict, "Title"),
author: info_text(dict, "Author"),
subject: info_text(dict, "Subject"),
keywords: info_text(dict, "Keywords"),
creator: info_text(dict, "Creator"),
producer: info_text(dict, "Producer"),
creation_date: info_date(dict, "CreationDate"),
modification_date: info_date(dict, "ModDate"),
}
}
fn info_text(dict: &Dict, key: &str) -> Option<String> {
Some(decode_text_string(dict.get(key)?.as_str_bytes()?))
}
fn info_date(dict: &Dict, key: &str) -> Option<Date> {
Date::parse_pdf(&decode_text_string(dict.get(key)?.as_str_bytes()?))
}
pub fn start_offset(base: &[u8]) -> (u64, bool) {
let pad = !matches!(base.last(), Some(b'\n') | Some(b'\r'));
(base.len() as u64 + u64::from(pad), pad)
}
pub struct Update<'a> {
doc: &'a Document,
overlay: Overlay,
}
impl<'a> Update<'a> {
pub fn new(doc: &'a Document) -> Result<Update<'a>> {
let base = OverlayBase::from_document(doc)?;
Ok(Update {
doc,
overlay: Overlay::new(base),
})
}
pub fn set(&mut self, r: ObjRef, obj: Object) {
self.overlay.set(r, obj);
}
pub fn remove(&mut self, r: ObjRef) {
self.overlay.remove(r);
}
pub fn reserve(&mut self) -> ObjRef {
self.overlay.reserve()
}
pub fn overlay(&self) -> &Overlay {
&self.overlay
}
pub fn set_metadata(&mut self, meta: Metadata) -> Result<()> {
let info_ref = self.overlay.info.or(self.overlay.base.info);
let existing_info = info_ref.and_then(|r| {
let dict = self.doc.get(r).ok()?.as_dict()?.clone();
Some((r, resolve_dict(self.doc, &dict)))
});
let xmp_ref = catalog_metadata_ref(self.doc, self.overlay.base.root);
set_metadata_with(&mut self.overlay, existing_info, xmp_ref, meta)
}
fn parts(&self) -> Result<(&[u8], bool, Vec<u8>)> {
let base = self.doc.bytes();
let (start, pad) = start_offset(base);
let section = self.overlay.section(start)?;
Ok((base, pad, section))
}
pub fn append_into(&self, mut out: impl std::io::Write) -> Result<()> {
let (base, pad, section) = self.parts()?;
out.write_all(base)?;
if pad {
out.write_all(b"\n")?;
}
out.write_all(§ion)?;
Ok(())
}
pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
let (base, pad, section) = self.parts()?;
let mut file = std::fs::File::create(path)?;
file.write_all(base)?;
if pad {
file.write_all(b"\n")?;
}
file.write_all(§ion)?;
Ok(())
}
pub fn bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
self.append_into(&mut out)?;
Ok(out)
}
}
#[derive(Debug, Clone, Copy)]
enum Row {
InFile(ObjRef, usize),
Free { num: u32, gen: u16, next: u32 },
}
impl Row {
fn num(&self) -> u32 {
match self {
Row::InFile(r, _) => r.num,
Row::Free { num, .. } => *num,
}
}
}
fn contiguous_runs(rows: &[Row]) -> Vec<(usize, usize)> {
let mut runs = Vec::new();
let mut begin = 0;
while begin < rows.len() {
let mut end = begin + 1;
while end < rows.len() && rows[end].num() == rows[end - 1].num() + 1 {
end += 1;
}
runs.push((begin, end - begin));
begin = end;
}
runs
}
fn rotated_id(base: &OverlayBase, body: &[u8], freed: &[ObjRef]) -> Option<Object> {
let Some(Object::Array(halves)) = &base.id else {
return None;
};
let Some(Object::String(first)) = halves.first() else {
return None;
};
let mut hasher = Sha256::new();
hasher.update(first);
hasher.update(&base.prev.to_le_bytes());
hasher.update(body);
for r in freed {
hasher.update(&r.num.to_le_bytes());
hasher.update(&r.gen.to_le_bytes());
}
let digest = hasher.finalize();
Some(Object::Array(vec![
Object::String(first.clone()),
Object::String(digest[..16].to_vec()),
]))
}
fn finish_stream(
out: &mut Vec<u8>,
start: u64,
mut rows: Vec<Row>,
mut dict: Dict,
mut next: u32,
) -> Result<()> {
let xref_ref = ObjRef { num: next, gen: 0 };
next += 1;
let xref_offset = start as usize + out.len();
rows.push(Row::InFile(xref_ref, xref_offset));
rows.sort_by_key(Row::num);
let runs = contiguous_runs(&rows);
let mut index = Vec::with_capacity(runs.len() * 2);
for (begin, len) in runs {
index.push(Object::Int(i64::from(rows[begin].num())));
index.push(Object::Int(len as i64));
}
let mut data = Vec::with_capacity(rows.len() * 7);
for row in &rows {
match row {
Row::InFile(r, offset) => {
data.push(1);
data.extend_from_slice(&field_offset(*offset)?.to_be_bytes());
data.extend_from_slice(&r.gen.to_be_bytes());
}
Row::Free {
gen,
next: free_next,
..
} => {
data.push(0);
data.extend_from_slice(&free_next.to_be_bytes());
data.extend_from_slice(&gen.to_be_bytes());
}
}
}
dict.insert(name("Type"), Object::Name(name("XRef")));
dict.insert(name("Size"), Object::Int(i64::from(next)));
dict.insert(
name("W"),
Object::Array(vec![Object::Int(1), Object::Int(4), Object::Int(2)]),
);
dict.insert(name("Index"), Object::Array(index));
write_indirect(out, xref_ref, &Object::Stream(Stream { dict, data }))?;
out.extend_from_slice(format!("startxref\n{xref_offset}\n%%EOF\n").as_bytes());
Ok(())
}
fn finish_table(
out: &mut Vec<u8>,
start: u64,
rows: &[Row],
mut dict: Dict,
size: u32,
) -> Result<()> {
let xref_offset = start as usize + out.len();
out.extend_from_slice(b"xref\n");
for (begin, len) in contiguous_runs(rows) {
out.extend_from_slice(format!("{} {}\n", rows[begin].num(), len).as_bytes());
for row in &rows[begin..begin + len] {
match row {
Row::InFile(r, offset) => out.extend_from_slice(
format!("{:010} {:05} n \n", table_offset(*offset)?, r.gen).as_bytes(),
),
Row::Free { gen, next, .. } => {
out.extend_from_slice(format!("{next:010} {gen:05} f \n").as_bytes())
}
}
}
}
dict.insert(name("Size"), Object::Int(i64::from(size)));
out.extend_from_slice(b"trailer\n");
serialize_dict(&dict, out)?;
out.extend_from_slice(format!("\nstartxref\n{xref_offset}\n%%EOF\n").as_bytes());
Ok(())
}
fn write_indirect(out: &mut Vec<u8>, r: ObjRef, obj: &Object) -> Result<()> {
out.extend_from_slice(format!("{} {} obj\n", r.num, r.gen).as_bytes());
match obj {
Object::Stream(s) => {
let mut dict = s.dict.clone();
dict.insert(name("Length"), Object::Int(s.data.len() as i64));
serialize_dict(&dict, out)?;
out.extend_from_slice(b"\nstream\n");
out.extend_from_slice(&s.data);
out.extend_from_slice(b"\nendstream\nendobj\n");
}
direct => {
serialize_object(direct, out)?;
out.extend_from_slice(b"\nendobj\n");
}
}
Ok(())
}
fn plain_stream(data: Vec<u8>) -> Stream {
Stream {
dict: Dict::new(),
data,
}
}
pub(crate) fn deflate(data: &[u8]) -> Vec<u8> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(data)
.expect("writing into a Vec cannot fail");
encoder
.finish()
.expect("finishing an in-memory zlib stream cannot fail")
}
fn field_offset(position: usize) -> Result<u32> {
u32::try_from(position)
.map_err(|_| Error::Other("file offset exceeds the 4-byte xref field".to_string()))
}
fn table_offset(position: usize) -> Result<usize> {
if position as u64 <= 9_999_999_999 {
return Ok(position);
}
Err(Error::Other(
"file offset exceeds the 10-digit xref table field".to_string(),
))
}
fn name(text: &str) -> Name {
Name(text.to_string())
}
pub(crate) fn core_error(error: pdfboss_core::Error) -> Error {
Error::Other(error.to_string())
}