///|
fn pdf_attach_embedded_file_stream(data : @core.PdfBytes) -> @syntax.PdfObject {
@syntax.pdf_stream(
PdfDictionary([
(pdf_attach_length_key(), PdfInteger(data.length())),
(pdf_attach_type_key(), PdfNameObject(pdf_attach_embedded_file())),
(
pdf_attach_params_key(),
PdfDictionary([
(pdf_attach_size_key(), PdfInteger(data.length())),
(
pdf_attach_checksum_key(),
PdfString(@crypt_core.pdf_md5_digest(data)),
),
]),
),
]),
StreamGot(data),
)
}
///|
fn PdfDocument::pdf_attach_file_spec(
self : PdfDocument,
basename : String,
data : BytesView,
relationship : String?,
description : String?,
) -> @syntax.PdfObject raise @core.PdfError {
let payload = data.to_owned()
let stream_number = self.add_object(pdf_attach_embedded_file_stream(payload))
let basename_bytes = pdf_attach_pdfdoc_string(basename)
let entries : Array[(@core.PdfName, @syntax.PdfObject)] = [
(
pdf_attach_ef_key(),
PdfDictionary([(pdf_attach_f_key(), PdfIndirect(stream_number))]),
),
(pdf_attach_f_key(), PdfString(basename_bytes)),
(pdf_attach_type_key(), PdfNameObject(pdf_attach_filespec())),
(pdf_attach_uf_key(), PdfString(basename_bytes)),
]
match description {
Some(text) =>
entries.push(
(pdf_attach_desc_key(), PdfString(pdf_attach_pdfdoc_string(text))),
)
None => ()
}
match relationship {
Some(text) =>
entries.push(
(
pdf_attach_af_relationship_key(),
PdfNameObject(pdf_attach_relationship_name(text)),
),
)
None => ()
}
PdfDictionary(entries)
}
///|
fn PdfDocument::pdf_attach_existing_document_entries(
self : PdfDocument,
) -> Array[(@core.PdfBytes, @syntax.PdfObject)] raise @core.PdfError {
let catalog = self.catalog()
match self.lookup_direct(pdf_attach_names_key(), catalog) {
Some(names) =>
match self.lookup_direct(pdf_attach_embedded_files_key(), names) {
Some(tree) => self.read_name_tree(tree)
None => []
}
None => []
}
}
///|
fn PdfDocument::pdf_attach_add_document_file(
self : PdfDocument,
basename_bytes : @core.PdfBytes,
file_spec_number : Int,
) -> Unit raise @core.PdfError {
let entries = self.pdf_attach_existing_document_entries()
entries.push((basename_bytes, PdfIndirect(file_spec_number)))
let tree_number = self.add_object(self.build_name_tree(entries))
self.replace_chain(
[
pdf_attach_root_key(),
pdf_attach_names_key(),
pdf_attach_embedded_files_key(),
],
PdfIndirect(tree_number),
)
}
///|
fn pdf_attach_annotation_rect(
bounds : @geometry.PdfRectangle,
) -> @syntax.PdfObject {
PdfArray([
PdfReal(18.0),
PdfReal(bounds.max_y - 45.0),
PdfReal(45.0),
PdfReal(bounds.max_y - 18.0),
])
}
///|
fn PdfDocument::pdf_attach_add_page_file(
self : PdfDocument,
page_number : Int,
basename_bytes : @core.PdfBytes,
file_spec_number : Int,
) -> PdfDocument raise @core.PdfError {
let pages = self.pages_of_pagetree()
if page_number < 1 || page_number > pages.length() {
raise BadPageSpecification("attach_file: Page not found")
}
let index = page_number - 1
let page = pages[index]
let annots = match self.lookup_direct(pdf_attach_annots_key(), page.rest) {
Some(PdfArray(existing)) => existing
Some(_) => raise DictionaryExpected
None => []
}
let bounds = self.parse_rectangle(page.mediabox)
let annotation = @syntax.PdfDictionary([
(pdf_attach_fs_key(), PdfIndirect(file_spec_number)),
(pdf_attach_subtype_key(), PdfNameObject(pdf_attach_file_attachment())),
(pdf_attach_contents_key(), PdfString(basename_bytes)),
(pdf_attach_rect_key(), pdf_attach_annotation_rect(bounds)),
])
pages[index] = {
..page,
rest: page.rest.add_dict_entry(
pdf_attach_annots_key(),
PdfArray([annotation, ..annots]),
),
}
self.change_pages(pages, change_references=true)
}
///|
fn PdfDocument::pdf_attach_bump_version(
self : PdfDocument,
keep_version : Bool,
) -> Unit {
let (major, minor) = self.version()
if !keep_version && major <= 1 && minor < 4 {
self.set_version(major, 4)
}
}
///|
/// Attach an in-memory file payload to the document or to a one-based page.
///
/// The original document is left unchanged. `filename` supplies the attachment
/// basename; the payload bytes are copied. When `keep_version` is false, PDF
/// versions below 1.4 are bumped to 1.4 as in cpdf.
pub fn PdfDocument::attach_file(
self : PdfDocument,
filename : String,
data : BytesView,
keep_version? : Bool = false,
page_number? : Int? = None,
relationship? : String? = None,
description? : String? = None,
) -> PdfDocument raise @core.PdfError {
ignore(self.catalog())
let document = self.copy()
let basename = pdf_attach_basename(filename)
let basename_bytes = pdf_attach_pdfdoc_string(basename)
let file_spec_number = document.add_object(
document.pdf_attach_file_spec(basename, data, relationship, description),
)
let attached = match page_number {
Some(number) =>
document.pdf_attach_add_page_file(
number, basename_bytes, file_spec_number,
)
None => {
document.pdf_attach_add_document_file(basename_bytes, file_spec_number)
document
}
}
attached.pdf_attach_bump_version(keep_version)
attached
}
///|
/// Compatibility wrapper for `PdfDocument::attach_file`.
pub fn pdf_attach_file(
document : PdfDocument,
filename : String,
data : BytesView,
keep_version? : Bool = false,
page_number? : Int? = None,
relationship? : String? = None,
description? : String? = None,
) -> PdfDocument raise @core.PdfError {
document.attach_file(
filename,
data,
keep_version~,
page_number~,
relationship~,
description~,
)
}
///|
/// Source-order compatibility wrapper for cpdfattach's in-memory attach path.
///
/// The OCaml source also has a filesystem path branch; native filesystem reads
/// live in `async_io/pdf_attach_file_from_path`.
pub fn pdf_attach_file_cpdf_order(
keep_version : Bool,
page_number : Int?,
document : PdfDocument,
relationship : String?,
description : String?,
filename : String,
data : BytesView,
) -> PdfDocument raise @core.PdfError {
document.attach_file(
filename,
data,
keep_version~,
page_number~,
relationship~,
description~,
)
}