///|
fn pad_int(value : Int, width : Int) -> String {
let s = value.to_string()
if s.length() >= width {
s
} else {
"0".repeat(width - s.length()) + s
}
}
///|
fn write_header(pdf : @pdf.Pdf, output : @pdfio.Output) -> Unit {
(output.output_string)("%PDF-\{pdf.major}.\{pdf.minor}\n%")
let bytes = [128, 129, 130, 131]
for b in bytes {
(output.output_byte)(b)
}
(output.output_char)('\n')
}
///|
fn bytes_required(value : Int) -> Int {
if value <= 0 {
return 1
}
let mut v = value
let mut bytes = 0
while v > 0 {
bytes = bytes + 1
v = v >> 8
}
bytes
}
///|
fn write_int_be(output : @pdfio.Output, value : Int, width : Int) -> Unit {
for pos in (width - 1)>=..0 {
let shift = pos * 8
let byte = (value >> shift) & 0xff
(output.output_byte)(byte)
}
}
///|
fn build_xref_stream(
pdf : @pdf.Pdf,
size : Int,
offsets : Array[Int],
gens : Array[Int],
used : Array[Bool],
object_stream_entries : Map[Int, (Int, Int)],
) -> @pdf.PdfObject raise {
let mut max_type = 1
let mut max_field2 = 0
let mut max_field3 = 0
for i in 0.. (2, stream_id, index)
None =>
if i == 0 || !used[i] {
(0, 0, 65535)
} else {
(1, offsets[i], gens[i])
}
}
if typ > max_type {
max_type = typ
}
if field2 > max_field2 {
max_field2 = field2
}
if field3 > max_field3 {
max_field3 = field3
}
}
let w1 = bytes_required(max_type)
let w2 = bytes_required(max_field2)
let w3 = bytes_required(max_field3)
let (output, buffer) = @pdfio.Output::of_bytes(256)
for i in 0.. (2, stream_id, index)
None =>
if i == 0 || !used[i] {
(0, 0, 65535)
} else {
(1, offsets[i], gens[i])
}
}
write_int_be(output, typ, w1)
write_int_be(output, field2, w2)
write_int_be(output, field3, w3)
}
let data = output.extract_bytes(buffer)
let dict_with_type = pdf.trailerdict.add_entry(
"/Type",
Name(PdfName(b"/XRef")),
)
let dict_with_size = dict_with_type.add_entry("/Size", Integer(size))
let dict_with_root = dict_with_size.add_entry("/Root", Indirect(pdf.root))
let dict_with_w = dict_with_root.add_entry(
"/W",
PdfArray([Integer(w1), Integer(w2), Integer(w3)]),
)
let dict_with_length = dict_with_w.add_entry(
"/Length",
Integer(@pdfio.bytes_size(data)),
)
let stream_obj = PdfObject::Stream(Ref((dict_with_length, Got(data))))
stream_obj
}
///|
/// Copy containers and stream `Ref`s of `obj`, sharing stream byte buffers
/// (no writer step modifies a buffer in place; they only replace them).
fn copy_object_structure(obj : PdfObject) -> PdfObject {
match obj {
PdfArray(values) => PdfArray(values.map(copy_object_structure))
Dictionary(entries) =>
Dictionary(entries.map(e => (e.0, copy_object_structure(e.1))))
Stream(r) => {
let (dict, data) = r.val
Stream(Ref((copy_object_structure(dict), data)))
}
_ => obj
}
}
///|
/// A version of `pdf` that the writer may mutate without the caller's
/// document being encrypted or losing objects.
///
/// The writer mutates the `Pdf` it works on: `build_new_id` replaces the
/// trailer, object streams remove the objects they absorb from the object
/// table (and rewrite the object-stream ids), and encryption / recryption
/// replace every string and stream `Ref` in place (stream `Ref`s are shared
/// with the caller).
///
/// The object table, object-stream ids and trailer are therefore always
/// copied; that is cheap (one entry per object). Only when `copy_objects`
/// (encryption) are the objects themselves copied, and even then stream byte
/// buffers are shared, so a write never duplicates the stream payload.
///
/// This is not full isolation. Without encryption, object cells and stream
/// `Ref`s are shared with the caller, so what writing does through them is
/// visible there: lazily read objects get parsed and cached, and
/// `getstream()` materializes deferred (`ToGet`) stream data and replaces the
/// stream's `/Length` with the byte count (an indirect `/Length` becomes an
/// integer). The decoded content of the caller's objects is unchanged.
fn private_copy(pdf : @pdf.Pdf, copy_objects : Bool) -> @pdf.Pdf {
let objects : @pdf.PdfObjMap = Map([], capacity=pdf.objects.objects.length())
for objnum, entry in pdf.objects.objects {
let (cell, gen) = entry
let cell = if copy_objects {
match cell.val {
Parsed(obj) => Ref(@pdf.ObjectData::Parsed(copy_object_structure(obj)))
ParsedAlreadyDecrypted(obj) =>
Ref(ParsedAlreadyDecrypted(copy_object_structure(obj)))
// Parsed on demand into the copy's own cell.
ToParse | ToParseFromObjectStream(_, _, _, _) => Ref(cell.val)
}
} else {
cell
}
objects.set(objnum, (cell, gen))
}
let object_stream_ids : Map[Int, Int] = Map(
[],
capacity=pdf.objects.object_stream_ids.length(),
)
for objnum, stream_id in pdf.objects.object_stream_ids {
object_stream_ids.set(objnum, stream_id)
}
{
..pdf,
objects: {
max_obj_num: pdf.objects.max_obj_num,
parse: pdf.objects.parse,
objects,
object_stream_ids,
},
trailerdict: copy_object_structure(pdf.trailerdict),
}
}
///|
/// Write a PDF to an output.
///
/// The caller's `pdf` is never encrypted and object streams do not remove
/// objects from it, but deferred streams may be read into memory and their
/// `/Length` normalized to the actual byte count.
pub fn PdfWrite::pdf_to_output(
self : PdfWrite,
preserve_objstm? : Bool = false,
generate_objstm? : Bool = false,
compress_objstm? : Bool = false,
recrypt? : String,
encryption : Encryption?,
build_new_id : Bool,
pdf : @pdf.Pdf,
output : @pdfio.Output,
) -> Unit raise {
let options : PdfWriteOptions = {
preserve_objstm,
generate_objstm,
compress_objstm,
recrypt,
encryption,
build_new_id,
}
self.pdf_to_output_with_options(options, pdf, output)
}
///|
/// Write a PDF to an output using an options record.
///
/// Leaves `pdf` as described for `pdf_to_output`.
pub fn PdfWrite::pdf_to_output_with_options(
self : PdfWrite,
options : PdfWriteOptions,
pdf : @pdf.Pdf,
output : @pdfio.Output,
) -> Unit raise {
let preserve_objstm = options.preserve_objstm
let generate_objstm = options.generate_objstm
let compress_objstm = options.compress_objstm
let recrypt = options.recrypt
let encryption = options.encryption
let build_new_id = options.build_new_id
// Keep the caller's objects unencrypted and its object table intact; see
// `private_copy` for what a write can still change (stream
// materialization, `/Length`).
let mut pdf = private_copy(pdf, recrypt is Some(_) || encryption is Some(_))
if build_new_id {
pdf.change_id("")
}
let recrypting = match recrypt {
Some(_) => true
None => false
}
let mut encryption_value = encryption
if recrypting {
encryption_value = Some(dummy_encryption)
}
let encrypting = match encryption_value {
Some(_) => true
None => false
}
if encrypting {
pdf.objects.object_stream_ids.remove(pdf.root)
}
let mut object_stream_entries : Map[Int, (Int, Int)] = Map([])
if preserve_objstm || generate_objstm {
if generate_objstm {
generate_object_stream_hints(pdf, preserve_objstm, encrypting)
}
let groups = build_object_stream_groups(pdf)
if groups.length() > 0 {
object_stream_entries = bake_object_streams(pdf, compress_objstm, groups)
}
}
if !preserve_objstm && !generate_objstm {
match encryption_value {
Some(enc) =>
match enc.encryption_method {
AlreadyEncrypted => ()
_ => pdf = pdf.renumber(pdf.changes())
}
None => ()
}
}
match recrypt {
Some(password) =>
pdf = @pdfcrypt.PdfCrypt::new().recrypt_pdf(
pdf,
password,
renumber=!(preserve_objstm || generate_objstm),
)
None => ()
}
pdf = crypt_if_necessary(pdf, encryption_value)
let use_xref_stream = preserve_objstm || generate_objstm
write_header(pdf, output)
let entries = @pdf.pdfobjmap_bindings(pdf.objects.objects)
let objnums = entries.map(pair => pair.0)
objnums.sort_by((a, b) => a - b)
let max_obj = pdf.objects.max_obj_num
let size = if use_xref_stream { max_obj + 2 } else { max_obj + 1 }
let xref_objnum = max_obj + 1
let offsets = Array::make(size, 0)
let gens = Array::make(size, 0)
let used = Array::make(size, false)
for objnum in objnums {
if objnum == 0 || (use_xref_stream && objnum == xref_objnum) {
continue
}
match pdf.objects.objects.get(objnum) {
None => ()
Some((_obj_ref, gen)) => {
let offset = (output.pos_out)()
offsets[objnum] = offset
gens[objnum] = gen
used[objnum] = true
(output.output_string)("\{objnum} \{gen} obj\n")
let obj = pdf.lookup_obj(objnum)
write_pdf_object_to_output(self, obj, output, true, false)
(output.output_string)("\nendobj\n")
}
}
}
let xref_start = (output.pos_out)()
if use_xref_stream {
offsets[xref_objnum] = xref_start
gens[xref_objnum] = 0
used[xref_objnum] = true
let xref_stream = build_xref_stream(
pdf, size, offsets, gens, used, object_stream_entries,
)
(output.output_string)("\{xref_objnum} 0 obj\n")
write_pdf_object_to_output(self, xref_stream, output, true, true)
(output.output_string)("\nendobj\n")
(output.output_string)("startxref\n")
(output.output_string)("\{xref_start}\n%%EOF\n")
} else {
(output.output_string)("xref\n")
(output.output_string)("0 \{size}\n")
for i in 0..