///|
/// Encoding policy used when reading PDF information dictionary metadata.
pub(all) enum PdfMetadataEncoding {
PdfMetadataRaw
PdfMetadataUTF8
PdfMetadataStripped
} derive(Debug, Eq, ToJson)
///|
fn pdf_metadata_name(text : String) -> PdfName {
pdf_name_of_bytes(@ascii.encode(text))
}
///|
fn pdf_metadata_source_key_name(key : String) -> PdfName {
pdf_name_of_bytes(@ascii.encode(key))
}
///|
let pdf_metadata_root_name : PdfName = pdf_metadata_name("/Root")
///|
fn pdf_metadata_root() -> PdfName {
pdf_metadata_root_name
}
///|
let pdf_metadata_info_name : PdfName = pdf_metadata_name("/Info")
///|
fn pdf_metadata_info() -> PdfName {
pdf_metadata_info_name
}
///|
let pdf_metadata_id_name : PdfName = pdf_metadata_name("/ID")
///|
fn pdf_metadata_id() -> PdfName {
pdf_metadata_id_name
}
///|
let pdf_metadata_metadata_name : PdfName = pdf_metadata_name("/Metadata")
///|
fn pdf_metadata_metadata() -> PdfName {
pdf_metadata_metadata_name
}
///|
let pdf_metadata_type_name : PdfName = pdf_metadata_name("/Type")
///|
fn pdf_metadata_type() -> PdfName {
pdf_metadata_type_name
}
///|
let pdf_metadata_subtype_name : PdfName = pdf_metadata_name("/Subtype")
///|
fn pdf_metadata_subtype() -> PdfName {
pdf_metadata_subtype_name
}
///|
let pdf_metadata_xml_name : PdfName = pdf_metadata_name("/XML")
///|
fn pdf_metadata_xml() -> PdfName {
pdf_metadata_xml_name
}
///|
let pdf_metadata_length_name : PdfName = pdf_metadata_name("/Length")
///|
fn pdf_metadata_length() -> PdfName {
pdf_metadata_length_name
}
///|
let pdf_metadata_viewer_preferences_name : PdfName = pdf_metadata_name(
"/ViewerPreferences",
)
///|
fn pdf_metadata_viewer_preferences() -> PdfName {
pdf_metadata_viewer_preferences_name
}
///|
let pdf_metadata_page_layout_name : PdfName = pdf_metadata_name("/PageLayout")
///|
fn pdf_metadata_page_layout() -> PdfName {
pdf_metadata_page_layout_name
}
///|
let pdf_metadata_page_mode_name : PdfName = pdf_metadata_name("/PageMode")
///|
fn pdf_metadata_page_mode() -> PdfName {
pdf_metadata_page_mode_name
}
///|
let pdf_metadata_non_full_screen_page_mode_name : PdfName = pdf_metadata_name(
"/NonFullScreenPageMode",
)
///|
fn pdf_metadata_non_full_screen_page_mode() -> PdfName {
pdf_metadata_non_full_screen_page_mode_name
}
///|
let pdf_metadata_open_action_name : PdfName = pdf_metadata_name("/OpenAction")
///|
fn pdf_metadata_open_action() -> PdfName {
pdf_metadata_open_action_name
}
///|
let pdf_metadata_d_name : PdfName = pdf_metadata_name("/D")
///|
fn pdf_metadata_d() -> PdfName {
pdf_metadata_d_name
}
///|
let pdf_metadata_s_name : PdfName = pdf_metadata_name("/S")
///|
fn pdf_metadata_s() -> PdfName {
pdf_metadata_s_name
}
///|
let pdf_metadata_goto_name : PdfName = pdf_metadata_name("/GoTo")
///|
fn pdf_metadata_goto() -> PdfName {
pdf_metadata_goto_name
}
///|
let pdf_metadata_fit_name : PdfName = pdf_metadata_name("/Fit")
///|
fn pdf_metadata_fit() -> PdfName {
pdf_metadata_fit_name
}
///|
let pdf_metadata_xyz_name : PdfName = pdf_metadata_name("/XYZ")
///|
fn pdf_metadata_xyz() -> PdfName {
pdf_metadata_xyz_name
}
///|
let pdf_metadata_lang_name : PdfName = pdf_metadata_name("/Lang")
///|
fn pdf_metadata_lang() -> PdfName {
pdf_metadata_lang_name
}
///|
let pdf_metadata_mark_info_name : PdfName = pdf_metadata_name("/MarkInfo")
///|
fn pdf_metadata_mark_info() -> PdfName {
pdf_metadata_mark_info_name
}
///|
let pdf_metadata_acroform_name : PdfName = pdf_metadata_name("/AcroForm")
///|
fn pdf_metadata_acroform() -> PdfName {
pdf_metadata_acroform_name
}
///|
let pdf_metadata_xfa_name : PdfName = pdf_metadata_name("/XFA")
///|
fn pdf_metadata_xfa() -> PdfName {
pdf_metadata_xfa_name
}
///|
let pdf_metadata_trapped_name : PdfName = pdf_metadata_name("/Trapped")
///|
fn pdf_metadata_trapped() -> PdfName {
pdf_metadata_trapped_name
}
///|
let pdf_metadata_cropbox_name : PdfName = pdf_metadata_name("/CropBox")
///|
fn pdf_metadata_cropbox() -> PdfName {
pdf_metadata_cropbox_name
}
///|
let pdf_metadata_bleedbox_name : PdfName = pdf_metadata_name("/BleedBox")
///|
fn pdf_metadata_bleedbox() -> PdfName {
pdf_metadata_bleedbox_name
}
///|
let pdf_metadata_trimbox_name : PdfName = pdf_metadata_name("/TrimBox")
///|
fn pdf_metadata_trimbox() -> PdfName {
pdf_metadata_trimbox_name
}
///|
let pdf_metadata_artbox_name : PdfName = pdf_metadata_name("/ArtBox")
///|
fn pdf_metadata_artbox() -> PdfName {
pdf_metadata_artbox_name
}
///|
/// Adobe PDF XMP namespace URI.
pub let pdf_metadata_adobe_namespace : String = "http://ns.adobe.com/pdf/1.3/"
///|
/// Adobe XMP namespace URI.
pub let pdf_metadata_xmp_namespace : String = "http://ns.adobe.com/xap/1.0/"
///|
/// Dublin Core metadata namespace URI.
pub let pdf_metadata_dc_namespace : String = "http://purl.org/dc/elements/1.1/"
///|
/// RDF syntax namespace URI used by XMP metadata.
pub let pdf_metadata_rdf_namespace : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
///|
/// PDF/A identification namespace URI.
pub let pdf_metadata_pdfaid_namespace : String = "http://www.aiim.org/pdfa/ns/id/"
///|
let pdf_metadata_pdfaid_wrong_iso_namespace : String = "http://www.aiim.org/pdfa/ns/id"
///|
let pdf_metadata_pdfaid_wrong_acrobat_namespace : String = "http://www.aiim.org/pdfa/ns/id.html"
///|
/// PDF/X identification namespace URI.
pub let pdf_metadata_pdfxid_namespace : String = "http://www.npes.org/pdfx/ns/id/"
///|
/// PDF/E identification namespace URI.
pub let pdf_metadata_pdfe_namespace : String = "http://www.aiim.org/pdfe/ns/id/"
///|
/// PDF/UA identification namespace URI.
pub let pdf_metadata_pdfuaid_namespace : String = "http://www.aiim.org/pdfua/ns/id/"
///|
/// PDF/VT identification namespace URI.
pub let pdf_metadata_pdfvtid_namespace : String = "http://www.npes.org/pdfvt/ns/id/"
///|
/// Alias matching cpdfmetadata's `adobe` namespace constant.
pub let pdf_metadata_adobe : String = pdf_metadata_adobe_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `adobe`.
pub let pdf_adobe : String = pdf_metadata_adobe_namespace
///|
/// Alias matching cpdfmetadata's `xmp` namespace constant.
pub let pdf_metadata_xmp : String = pdf_metadata_xmp_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `xmp`.
pub let pdf_xmp : String = pdf_metadata_xmp_namespace
///|
/// Alias matching cpdfmetadata's `dc` namespace constant.
pub let pdf_metadata_dc : String = pdf_metadata_dc_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `dc`.
pub let pdf_dc : String = pdf_metadata_dc_namespace
///|
/// Alias matching cpdfmetadata's `rdf` namespace constant.
pub let pdf_metadata_rdf : String = pdf_metadata_rdf_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `rdf`.
pub let pdf_rdf : String = pdf_metadata_rdf_namespace
///|
/// Alias matching cpdfmetadata's `pdfaid` namespace constant.
pub let pdf_metadata_pdfaid : String = pdf_metadata_pdfaid_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `pdfaid`.
pub let pdf_pdfaid : String = pdf_metadata_pdfaid_namespace
///|
/// Alias matching cpdfmetadata's `pdfxid` namespace constant.
pub let pdf_metadata_pdfxid : String = pdf_metadata_pdfxid_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `pdfxid`.
pub let pdf_pdfxid : String = pdf_metadata_pdfxid_namespace
///|
/// Alias matching cpdfmetadata's `pdfe` namespace constant.
pub let pdf_metadata_pdfe : String = pdf_metadata_pdfe_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `pdfe`.
pub let pdf_pdfe : String = pdf_metadata_pdfe_namespace
///|
/// Alias matching cpdfmetadata's `pdfuaid` namespace constant.
pub let pdf_metadata_pdfuaid : String = pdf_metadata_pdfuaid_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `pdfuaid`.
pub let pdf_pdfuaid : String = pdf_metadata_pdfuaid_namespace
///|
/// Alias matching cpdfmetadata's `pdfvtid` namespace constant.
pub let pdf_metadata_pdfvtid : String = pdf_metadata_pdfvtid_namespace
///|
/// Source-spelled compatibility alias for cpdfmetadata's `pdfvtid`.
pub let pdf_pdfvtid : String = pdf_metadata_pdfvtid_namespace
///|
/// Qualified XML name used by the metadata XML tree parser.
pub(all) struct PdfMetadataXmlName {
uri : String
local_name : String
prefix : String?
} derive(Debug, Eq, ToJson)
///|
/// XML attribute used by the metadata XML tree parser.
pub(all) struct PdfMetadataXmlAttribute {
name : PdfMetadataXmlName
value : String
} derive(Debug, Eq, ToJson)
///|
/// XML element tag name and attributes used by metadata trees.
pub(all) struct PdfMetadataXmlTag {
name : PdfMetadataXmlName
attributes : Array[PdfMetadataXmlAttribute]
} derive(Debug, Eq, ToJson)
///|
/// Minimal XML tree representation used for XMP metadata.
pub(all) enum PdfMetadataXmlTree {
PdfMetadataXmlElement(PdfMetadataXmlTag, Array[PdfMetadataXmlTree])
PdfMetadataXmlData(String)
} derive(Debug, Eq, ToJson)
///|
/// Parsed XML metadata document with an optional DTD fragment.
pub(all) struct PdfMetadataXmlDocument {
dtd : String?
tree : PdfMetadataXmlTree
} derive(Debug, Eq, ToJson)
///|
let pdf_metadata_xml_uri : String = "http://www.w3.org/XML/1998/namespace"
///|
let pdf_metadata_xmlns_uri : String = "http://www.w3.org/2000/xmlns/"
///|
let pdf_metadata_xmp_template : String =
#|
#|
#|
#|
#|
#|
#| CREATEDATE
#| MODDATE
#| PRODUCER
#| CREATOR
#| TITLE
#| SUBJECT
#| AUTHOR
#| KEYWORDS
#| TRAPPED
#|
#|
#|
#| CREATEDATE
#| CREATOR
#| MODDATE
#| METADATADATE
#|
#|
#|
#|
#|
#| TITLE
#|
#|
#|
#|
#|
#|
#|
#|
#|
///|
fn pdf_metadata_bool_string(value : Bool) -> String {
if value {
"True"
} else {
"False"
}
}
///|
fn pdf_metadata_ascii_codepoints(bytes : BytesView) -> PdfBytes raise PdfError {
let codepoints = pdf_codepoints_of_pdfdocstring(bytes)
let mut output_length = 0
for codepoint in codepoints {
if codepoint <= 127 {
output_length += 1
}
}
let output = Array::make(output_length, b'\x00')
let mut position = 0
for codepoint in codepoints {
if codepoint <= 127 {
output[position] = pdf_byte_of_int(codepoint)
position += 1
}
}
Bytes::from_array(output)
}
///|
/// Encode a PDF string for metadata/reporting output.
pub fn pdf_metadata_encode_output(
encoding : PdfMetadataEncoding,
bytes : BytesView,
) -> PdfBytes raise PdfError {
match encoding {
PdfMetadataRaw => bytes.to_owned()
PdfMetadataUTF8 => pdf_utf8_of_pdfdocstring(bytes)
PdfMetadataStripped => pdf_metadata_ascii_codepoints(bytes)
}
}
///|
/// Source-spelled compatibility alias for cpdfmetadata's `encode_output`.
pub fn pdf_encode_output(
encoding : PdfMetadataEncoding,
bytes : BytesView,
) -> PdfBytes raise PdfError {
pdf_metadata_encode_output(encoding, bytes)
}
///|
fn PdfDocument::pdf_metadata_bump_minor(
self : PdfDocument,
version : Int,
keep_version : Bool,
) -> Unit {
if self.major == 1 && !keep_version && self.minor < version {
self.minor = version
}
}
///|
fn PdfDocument::pdf_metadata_replace_catalog(
self : PdfDocument,
catalog : PdfObject,
) -> Unit raise PdfError {
let root_number = self.add_object(catalog)
self.set_root(root_number)
self.set_trailer_dict(
self
.trailer_dict()
.add_dict_entry(pdf_metadata_root(), PdfIndirect(root_number)),
)
}
///|
fn PdfDocument::pdf_metadata_update_catalog(
self : PdfDocument,
update : (PdfObject) -> PdfObject raise PdfError,
) -> Unit raise PdfError {
let catalog = self.catalog()
self.pdf_metadata_replace_catalog(update(catalog))
}
///|
fn pdf_metadata_name_string(name : PdfName) -> String raise PdfError {
let bytes = name.bytes()
let start = if bytes.length() > 0 && bytes[0].to_int() == 47 { 1 } else { 0 }
@ascii.decode(bytes[start:]) catch {
_ => raise InvalidUTF8
}
}
///|
fn pdf_metadata_known_name(
value : String,
allowed : ArrayView[String],
error_prefix : String,
) -> PdfName raise PdfError {
for allowed_value in allowed {
if value == allowed_value {
break pdf_metadata_name("/" + value)
}
} nobreak {
raise BadNumberArgument(error_prefix + value)
}
}
///|
/// Copy the trailer `/ID` from `source` onto this document.
///
/// The original document is left unchanged. When `keep_version` is false, PDF
/// 1.x outputs are bumped to at least 1.1, matching cpdf.
pub fn PdfDocument::copy_id_from(
self : PdfDocument,
source : PdfDocument,
keep_version? : Bool = false,
) -> PdfDocument raise PdfError {
let document = self.copy()
match source.lookup_direct(pdf_metadata_id(), source.trailer_dict()) {
Some(id) => {
document.set_trailer_dict(
document.trailer_dict().add_dict_entry(pdf_metadata_id(), id),
)
document.pdf_metadata_bump_minor(1, keep_version)
document
}
None => document
}
}
///|
/// Set or replace one `/Info` dictionary entry.
pub fn PdfDocument::set_info_entry(
self : PdfDocument,
key : PdfName,
value : PdfObject,
version : Int,
keep_version? : Bool = false,
xmp_also? : Bool = false,
xmp_just_set? : Bool = false,
) -> PdfDocument raise PdfError {
let document = if xmp_just_set {
self
} else {
let document = self.copy()
let info = match
document.lookup_direct(pdf_metadata_info(), document.trailer_dict()) {
Some(dictionary) => dictionary
None => PdfDictionary([])
}
let info_number = document.add_object(info.add_dict_entry(key, value))
document.set_trailer_dict(
document
.trailer_dict()
.add_dict_entry(pdf_metadata_info(), PdfIndirect(info_number)),
)
document.pdf_metadata_bump_minor(version, keep_version)
document
}
if !xmp_also {
document
} else {
match document.metadata() {
None => document
Some(metadata) => {
let (changes, replacement) = pdf_metadata_info_xmp_changes(key, value)
let mut updated = metadata
for change in changes {
updated = pdf_metadata_rewrite_xmp_field(
updated,
change.0,
pdf_metadata_namespace_fallback_prefix(change.0),
change.1,
replacement,
)
}
document.set_metadata_from_bytes(updated, keep_version=true)
}
}
}
}
///|
/// Set or replace one `/Info` entry from cpdf's string-key tuple form.
pub fn PdfDocument::set_info_entry_string(
self : PdfDocument,
entry : (String, PdfObject, Int),
keep_version? : Bool = false,
xmp_also? : Bool = false,
xmp_just_set? : Bool = false,
) -> PdfDocument raise PdfError {
self.set_info_entry(
pdf_metadata_source_key_name(entry.0),
entry.1,
entry.2,
keep_version~,
xmp_also~,
xmp_just_set~,
)
}
///|
/// Return one `/Info` dictionary entry as a UTF-8 string.
pub fn PdfDocument::info_utf8(self : PdfDocument, key : PdfName) -> String {
let info = match
self.lookup_direct(pdf_metadata_info(), self.trailer_dict()) {
Some(dictionary) => dictionary
None => PdfDictionary([])
}
match self.lookup_direct(key, info) {
Some(PdfString(bytes)) =>
@utf8.decode(pdf_utf8_of_pdfdocstring(bytes)) catch {
_ => ""
}
Some(PdfBoolean(value)) => pdf_metadata_bool_string(value)
_ => if key == pdf_metadata_trapped() { "False" } else { "" }
}
}
///|
/// Return one cpdf-style slash-prefixed string-key `/Info` entry as UTF-8.
pub fn PdfDocument::info_utf8_string(
self : PdfDocument,
key : String,
) -> String {
self.info_utf8(pdf_metadata_source_key_name(key))
}
///|
/// Set or replace one `/ViewerPreferences` dictionary entry.
pub fn PdfDocument::set_viewer_preference(
self : PdfDocument,
key : PdfName,
value : PdfObject,
version : Int,
keep_version? : Bool = false,
) -> PdfDocument raise PdfError {
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
let preferences = match
document.lookup_direct(pdf_metadata_viewer_preferences(), catalog) {
Some(dictionary) => dictionary
None => PdfDictionary([])
}
catalog.add_dict_entry(
pdf_metadata_viewer_preferences(),
preferences.add_dict_entry(key, value),
)
})
document.pdf_metadata_bump_minor(version, keep_version)
document
}
///|
/// Set one `/ViewerPreferences` entry from cpdf's string-key tuple form.
pub fn PdfDocument::set_viewer_preference_string(
self : PdfDocument,
entry : (String, PdfObject, Int),
) -> PdfDocument raise PdfError {
self.set_viewer_preference(
pdf_metadata_source_key_name(entry.0),
entry.1,
entry.2,
)
}
///|
/// Set the catalog `/PageLayout` value using a name without the leading slash.
pub fn PdfDocument::set_page_layout(
self : PdfDocument,
layout : String,
) -> PdfDocument raise PdfError {
let name = pdf_metadata_known_name(
layout,
[
"SinglePage", "OneColumn", "TwoColumnLeft", "TwoColumnRight", "TwoPageLeft",
"TwoPageRight",
],
"Unknown page layout: ",
)
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.add_dict_entry(pdf_metadata_page_layout(), PdfNameObject(name))
})
document
}
///|
/// Set the catalog `/PageMode` value using a name without the leading slash.
pub fn PdfDocument::set_page_mode(
self : PdfDocument,
mode : String,
) -> PdfDocument raise PdfError {
let name = pdf_metadata_known_name(
mode,
[
"UseNone", "UseOutlines", "UseThumbs", "FullScreen", "UseOC", "UseAttachments",
],
"Unknown page mode: ",
)
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.add_dict_entry(pdf_metadata_page_mode(), PdfNameObject(name))
})
document
}
///|
/// Set `/ViewerPreferences /NonFullScreenPageMode`.
pub fn PdfDocument::set_non_full_screen_page_mode(
self : PdfDocument,
mode : String,
) -> PdfDocument raise PdfError {
let name = pdf_metadata_known_name(
mode,
["UseNone", "UseOutlines", "UseThumbs", "UseOC", "UseAttachments"],
"Unknown non full screen page mode: ",
)
self.set_viewer_preference(
pdf_metadata_non_full_screen_page_mode(),
PdfNameObject(name),
2,
)
}
///|
fn PdfDocument::pdf_metadata_rewrite_destination_page(
self : PdfDocument,
destination : PdfObject,
) -> PdfObject raise PdfError {
match destination {
PdfArray(values) =>
match values {
[PdfInteger(page_number), .. rest] => {
let page_numbers = self.page_reference_numbers()
if page_number < 1 || page_number > page_numbers.length() {
raise BadPageSpecification(page_number.to_string())
}
PdfArray([PdfIndirect(page_numbers[page_number - 1]), ..rest])
}
_ => destination
}
_ => destination
}
}
///|
fn pdf_metadata_parse_open_action_destination(
destination : String,
) -> PdfObject raise PdfError {
let parsed : Result[PdfObject, Error] = try? pdf_parse_object_from_bytes(
@ascii.encode(destination),
)
match parsed {
Ok(object) =>
match object {
PdfArray([PdfInteger(_), ..]) => object
_ => raise BadNumberArgument("Bad destination syntax")
}
Err(_) => raise BadNumberArgument("Bad destination syntax")
}
}
///|
/// Set the catalog `/OpenAction` to a GoTo action.
///
/// `page_number` is one-based. When `destination` is supplied and starts with a
/// page number integer, that integer is rewritten to the corresponding page
/// indirect reference.
pub fn PdfDocument::set_open_action(
self : PdfDocument,
page_number : Int,
fit? : Bool = true,
destination? : PdfObject? = None,
) -> PdfDocument raise PdfError {
let page_numbers = self.page_reference_numbers()
if page_number < 1 || page_number > page_numbers.length() {
raise BadPageSpecification(page_number.to_string())
}
let destination = match destination {
Some(value) => self.pdf_metadata_rewrite_destination_page(value)
None =>
if fit {
PdfArray([
PdfIndirect(page_numbers[page_number - 1]),
PdfNameObject(pdf_metadata_fit()),
])
} else {
PdfArray([
PdfIndirect(page_numbers[page_number - 1]),
PdfNameObject(pdf_metadata_xyz()),
PdfNull,
PdfNull,
PdfNull,
])
}
}
let open_action = PdfDictionary([
(pdf_metadata_d(), destination),
(pdf_metadata_s(), PdfNameObject(pdf_metadata_goto())),
])
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.add_dict_entry(pdf_metadata_open_action(), open_action)
})
document
}
///|
/// Set the catalog `/OpenAction` from cpdf-style PDF destination syntax.
pub fn PdfDocument::set_open_action_string_destination(
self : PdfDocument,
page_number : Int,
fit? : Bool = true,
destination? : String? = None,
) -> PdfDocument raise PdfError {
let destination_object = match destination {
Some(value) => Some(pdf_metadata_parse_open_action_destination(value))
None => None
}
self.set_open_action(page_number, fit~, destination=destination_object)
}
///|
/// Return a catalog item in cpdf's text-reporting form.
pub fn PdfDocument::catalog_item(self : PdfDocument, key : PdfName) -> String {
try self.catalog() catch {
_ => ""
} noraise {
catalog =>
match self.lookup_direct(key, catalog) {
Some(PdfNameObject(name)) =>
pdf_metadata_name_string(name) catch {
_ => ""
}
Some(PdfDictionary(_)) => "True"
_ => ""
}
}
}
///|
/// Return a catalog item for a cpdf-style slash-prefixed string key.
pub fn PdfDocument::catalog_item_string(
self : PdfDocument,
key : String,
) -> String {
self.catalog_item(pdf_metadata_source_key_name(key))
}
///|
/// Return a viewer-preference item in cpdf's text-reporting form.
pub fn PdfDocument::viewer_preference_item(
self : PdfDocument,
key : PdfName,
) -> String {
try self.catalog() catch {
_ => ""
} noraise {
catalog =>
match self.lookup_direct(pdf_metadata_viewer_preferences(), catalog) {
Some(preferences) =>
match self.lookup_direct(key, preferences) {
Some(PdfNameObject(name)) =>
pdf_metadata_name_string(name) catch {
_ => ""
}
Some(PdfBoolean(value)) => if value { "true" } else { "false" }
_ => ""
}
None => ""
}
}
}
///|
/// Return a viewer-preference item for a cpdf-style slash-prefixed string key.
pub fn PdfDocument::viewer_preference_item_string(
self : PdfDocument,
key : String,
) -> String {
self.viewer_preference_item(pdf_metadata_source_key_name(key))
}
///|
/// Return a `/MarkInfo` boolean item, or false when absent/malformed.
pub fn PdfDocument::mark_info_item(self : PdfDocument, key : PdfName) -> Bool {
try self.catalog() catch {
_ => false
} noraise {
catalog =>
match self.lookup_direct(pdf_metadata_mark_info(), catalog) {
Some(mark_info) =>
match self.lookup_direct(key, mark_info) {
Some(PdfBoolean(value)) => value
_ => false
}
None => false
}
}
}
///|
/// Return true when the catalog contains an AcroForm XFA entry.
pub fn PdfDocument::is_xfa(self : PdfDocument) -> Bool {
self.lookup_chain(self.trailer_dict(), [
pdf_metadata_root(),
pdf_metadata_acroform(),
pdf_metadata_xfa(),
]) !=
None
}
///|
fn PdfDocument::pdf_metadata_page_number_for_reference(
self : PdfDocument,
reference : Int,
) -> Int? raise PdfError {
let page_numbers = self.page_reference_numbers()
for index in 0.. PdfObject raise PdfError {
match action {
PdfArray([PdfIndirect(reference), .. rest]) =>
match self.pdf_metadata_page_number_for_reference(reference) {
Some(page_number) => PdfArray([PdfInteger(page_number), ..rest])
None => action
}
_ => action
}
}
///|
/// Return the catalog `/OpenAction` in cpdf's reporting form.
///
/// Destination arrays beginning with a page indirect reference are rewritten
/// to one-based page numbers. For GoTo action dictionaries with `/D`, the
/// destination is returned; other action objects are returned unchanged.
pub fn PdfDocument::open_action(self : PdfDocument) -> PdfObject raise PdfError {
match
self.lookup_chain(self.trailer_dict(), [
pdf_metadata_root(),
pdf_metadata_open_action(),
]) {
Some(PdfArray(_) as action) =>
self.pdf_metadata_rewrite_open_action_page(action)
Some(PdfDictionary(_) as action) =>
match self.lookup_direct(pdf_metadata_d(), action) {
Some(PdfArray(_) as destination) =>
self.pdf_metadata_rewrite_open_action_page(destination)
_ => action
}
Some(action) => action
None => PdfNull
}
}
///|
/// Return the catalog `/OpenAction` as cpdf-style JSON bytes.
pub fn PdfDocument::open_action_json_blob(
self : PdfDocument,
) -> PdfBytes raise PdfError {
@utf8.encode(
self
.pdf_util_json_of_object(
self.open_action(),
true,
false,
false,
false,
false,
)
.stringify(),
)
}
///|
/// Return the catalog `/OpenAction` as cpdf-style PDF syntax.
pub fn PdfDocument::open_action_string(
self : PdfDocument,
) -> String raise PdfError {
match self.open_action() {
PdfNull => ""
action =>
@ascii.decode(pdf_write_object(action)) catch {
_ => raise InvalidUTF8
}
}
}
///|
fn pdf_metadata_is_xml_separator(byte : Byte) -> Bool {
match byte.to_int() {
9 | 10 | 13 | 32 | 60 => true
_ => false
}
}
///|
fn pdf_metadata_is_xml_space(byte : Byte) -> Bool {
match byte.to_int() {
9 | 10 | 13 | 32 => true
_ => false
}
}
///|
fn pdf_metadata_trim_xml_text(value : BytesView) -> BytesView {
let mut start = 0
let mut end = value.length()
while start < end && pdf_metadata_is_xml_space(value[start]) {
start += 1
}
while end > start && pdf_metadata_is_xml_space(value[end - 1]) {
end -= 1
}
value[start:end]
}
///|
fn pdf_metadata_xml_entity_digit(byte : Byte, base : Int) -> Int? {
let value = byte.to_int()
if value >= 48 && value <= 57 {
let digit = value - 48
if digit < base {
Some(digit)
} else {
None
}
} else if base == 16 && value >= 65 && value <= 70 {
Some(value - 55)
} else if base == 16 && value >= 97 && value <= 102 {
Some(value - 87)
} else {
None
}
}
///|
fn pdf_metadata_xml_entity_codepoint(entity : BytesView) -> Int? {
if entity.length() == 3 && pdf_metadata_view_matches_at(entity, 0, b"amp") {
Some(38)
} else if entity.length() == 2 &&
pdf_metadata_view_matches_at(entity, 0, b"lt") {
Some(60)
} else if entity.length() == 2 &&
pdf_metadata_view_matches_at(entity, 0, b"gt") {
Some(62)
} else if entity.length() == 4 &&
pdf_metadata_view_matches_at(entity, 0, b"quot") {
Some(34)
} else if entity.length() == 4 &&
pdf_metadata_view_matches_at(entity, 0, b"apos") {
Some(39)
} else if entity.length() >= 2 && entity[0].to_int() == 35 {
let mut index = 1
let mut base = 10
if index < entity.length() &&
(entity[index].to_int() == 120 || entity[index].to_int() == 88) {
base = 16
index += 1
}
if index >= entity.length() {
return None
}
let mut codepoint = 0
while index < entity.length() {
match pdf_metadata_xml_entity_digit(entity[index], base) {
Some(digit) => codepoint = codepoint * base + digit
None => return None
}
if codepoint > 0x10FFFF {
return None
}
index += 1
}
if codepoint >= 0xD800 && codepoint <= 0xDFFF {
None
} else {
Some(codepoint)
}
} else {
None
}
}
///|
fn pdf_metadata_write_xml_codepoint(
output : Array[Byte],
position : Int,
codepoint : Int,
) -> Int? {
let encoded = pdf_utf8_of_codepoints([codepoint]) catch { _ => return None }
let mut current = position
for byte in encoded {
output[current] = byte
current += 1
}
Some(current)
}
///|
fn pdf_metadata_xml_codepoint_is_space(codepoint : Int) -> Bool {
codepoint == 9 || codepoint == 10 || codepoint == 13 || codepoint == 32
}
///|
fn pdf_metadata_decode_xml_text(value : BytesView) -> String? {
let trimmed = pdf_metadata_trim_xml_text(value)
let output = Array::make(trimmed.length(), b'\x00')
let mut output_position = 0
let mut pending_space = false
let mut index = 0
while index < trimmed.length() {
if trimmed[index].to_int() == 38 {
let semicolon = pdf_metadata_find_byte(trimmed, index + 1, 59)
if semicolon > index {
match pdf_metadata_xml_entity_codepoint(trimmed[index + 1:semicolon]) {
Some(codepoint) =>
if pdf_metadata_xml_codepoint_is_space(codepoint) {
if output_position > 0 {
pending_space = true
}
index = semicolon + 1
} else {
if pending_space {
output[output_position] = 32
output_position += 1
pending_space = false
}
match
pdf_metadata_write_xml_codepoint(
output, output_position, codepoint,
) {
Some(position) => {
output_position = position
index = semicolon + 1
}
None => {
output[output_position] = trimmed[index]
output_position += 1
index += 1
}
}
}
_ => {
if pending_space {
output[output_position] = 32
output_position += 1
pending_space = false
}
output[output_position] = trimmed[index]
output_position += 1
index += 1
}
}
} else {
if pending_space {
output[output_position] = 32
output_position += 1
pending_space = false
}
output[output_position] = trimmed[index]
output_position += 1
index += 1
}
} else if pdf_metadata_is_xml_space(trimmed[index]) {
if output_position > 0 {
pending_space = true
}
index += 1
} else {
if pending_space {
output[output_position] = 32
output_position += 1
pending_space = false
}
output[output_position] = trimmed[index]
output_position += 1
index += 1
}
}
let text = @utf8.decode(Bytes::from_array(output[:output_position])) catch {
_ => return None
}
Some(text)
}
///|
fn pdf_metadata_xml_decode_name(value : BytesView) -> String raise PdfError {
@utf8.decode(value) catch {
_ => raise InvalidUTF8
}
}
///|
fn pdf_metadata_xml_is_name_boundary(byte : Byte) -> Bool {
let value = byte.to_int()
value == 9 ||
value == 10 ||
value == 13 ||
value == 32 ||
value == 47 ||
value == 61 ||
value == 62
}
///|
fn pdf_metadata_xml_skip_space(data : BytesView, start : Int) -> Int {
let mut index = start
while index < data.length() && pdf_metadata_is_xml_space(data[index]) {
index += 1
}
index
}
///|
fn pdf_metadata_xml_read_name(
data : BytesView,
start : Int,
) -> (Bytes, Int) raise PdfError {
let mut index = start
while index < data.length() && !pdf_metadata_xml_is_name_boundary(data[index]) {
index += 1
}
guard index > start else { raise BadText }
(data[start:index].to_owned(), index)
}
///|
fn pdf_metadata_xml_uri_lookup(
context : ArrayView[(String, String)],
prefix : String,
) -> String? {
for index = context.length() - 1; index >= 0; index = index - 1 {
if context[index].0 == prefix {
break Some(context[index].1)
}
} nobreak {
None
}
}
///|
fn pdf_metadata_xml_resolve_name(
qname : BytesView,
is_attribute : Bool,
context : ArrayView[(String, String)],
) -> PdfMetadataXmlName raise PdfError {
let colon = pdf_metadata_find_byte(qname, 0, 58)
if colon >= 0 {
let prefix = pdf_metadata_xml_decode_name(qname[:colon])
let local_name = pdf_metadata_xml_decode_name(qname[colon + 1:])
let uri = if prefix == "xml" {
pdf_metadata_xml_uri
} else if prefix == "xmlns" {
pdf_metadata_xmlns_uri
} else {
match pdf_metadata_xml_uri_lookup(context, prefix) {
Some(uri) => uri
None => raise BadText
}
}
{ uri, local_name, prefix: Some(prefix) }
} else {
let local_name = pdf_metadata_xml_decode_name(qname)
let uri = if is_attribute {
if local_name == "xmlns" {
pdf_metadata_xmlns_uri
} else {
""
}
} else {
match pdf_metadata_xml_uri_lookup(context, "") {
Some(uri) => uri
None => ""
}
}
{ uri, local_name, prefix: None }
}
}
///|
fn pdf_metadata_xml_parse_raw_attributes(
data : BytesView,
start : Int,
) -> Array[(Bytes, String)] raise PdfError {
let attributes : Array[(Bytes, String)] = []
let mut index = start
while index < data.length() {
index = pdf_metadata_xml_skip_space(data, index)
if index >= data.length() {
break
}
let (name, after_name) = pdf_metadata_xml_read_name(data, index)
index = pdf_metadata_xml_skip_space(data, after_name)
guard index < data.length() && data[index].to_int() == 61 else {
raise BadText
}
index = pdf_metadata_xml_skip_space(data, index + 1)
guard index < data.length() &&
(data[index].to_int() == 34 || data[index].to_int() == 39) else {
raise BadText
}
let quote = data[index].to_int()
let value_start = index + 1
let value_end = pdf_metadata_find_byte(data, value_start, quote)
guard value_end >= 0 else { raise BadText }
let value = match
pdf_metadata_decode_xml_text(data[value_start:value_end]) {
Some(value) => value
None => raise InvalidUTF8
}
attributes.push((name, value))
index = value_end + 1
}
attributes
}
///|
fn pdf_metadata_xml_context_with_attributes(
context : ArrayView[(String, String)],
attributes : ArrayView[(Bytes, String)],
) -> Array[(String, String)] raise PdfError {
let output = context.to_owned()
for attribute in attributes {
let name = attribute.0[:]
if name.length() == 5 && pdf_metadata_view_matches_at(name, 0, b"xmlns") {
output.push(("", attribute.1))
} else if name.length() > 6 &&
pdf_metadata_view_matches_at(name, 0, b"xmlns:") {
output.push((pdf_metadata_xml_decode_name(name[6:]), attribute.1))
}
}
output
}
///|
fn pdf_metadata_xml_tag_from_raw(
name : BytesView,
attributes : ArrayView[(Bytes, String)],
context : ArrayView[(String, String)],
) -> PdfMetadataXmlTag raise PdfError {
let resolved_attributes : Array[PdfMetadataXmlAttribute] = []
for attribute in attributes {
resolved_attributes.push({
name: pdf_metadata_xml_resolve_name(attribute.0, true, context),
value: attribute.1,
})
}
{
name: pdf_metadata_xml_resolve_name(name, false, context),
attributes: resolved_attributes,
}
}
///|
fn pdf_metadata_xml_find_tag_end(
data : BytesView,
start : Int,
) -> Int raise PdfError {
let mut index = start
let mut quote = 0
while index < data.length() {
let value = data[index].to_int()
if quote != 0 {
if value == quote {
quote = 0
}
} else if value == 34 || value == 39 {
quote = value
} else if value == 62 {
return index
}
index += 1
}
raise BadText
}
///|
fn pdf_metadata_xml_is_self_closing_tag(data : BytesView, end : Int) -> Bool {
let mut index = end - 1
while index >= 0 && pdf_metadata_is_xml_space(data[index]) {
index -= 1
}
index >= 0 && data[index].to_int() == 47
}
///|
fn pdf_metadata_xml_parse_start_tag(
data : BytesView,
start : Int,
context : ArrayView[(String, String)],
) -> (String, PdfMetadataXmlTag, Array[(String, String)], Bool, Int) raise PdfError {
guard start < data.length() && data[start].to_int() == 60 else {
raise BadText
}
let tag_end = pdf_metadata_xml_find_tag_end(data, start + 1)
let self_closing = pdf_metadata_xml_is_self_closing_tag(data, tag_end)
let body_end = if self_closing {
let mut index = tag_end - 1
while index >= start + 1 && pdf_metadata_is_xml_space(data[index]) {
index -= 1
}
index
} else {
tag_end
}
guard body_end > start else { raise BadText }
let body = data[start + 1:body_end]
let body_start = pdf_metadata_xml_skip_space(body, 0)
let (qname, after_name) = pdf_metadata_xml_read_name(body, body_start)
let raw_attributes = pdf_metadata_xml_parse_raw_attributes(body, after_name)
let next_context = pdf_metadata_xml_context_with_attributes(
context, raw_attributes,
)
let tag = pdf_metadata_xml_tag_from_raw(qname, raw_attributes, next_context)
(
pdf_metadata_xml_decode_name(qname),
tag,
next_context,
self_closing,
tag_end + 1,
)
}
///|
fn pdf_metadata_xml_parse_close_tag(
data : BytesView,
start : Int,
) -> (String, Int) raise PdfError {
guard start + 1 < data.length() &&
data[start].to_int() == 60 &&
data[start + 1].to_int() == 47 else {
raise BadText
}
let end = pdf_metadata_find_byte(data, start + 2, 62)
guard end >= 0 else { raise BadText }
let name = match pdf_metadata_decode_xml_text(data[start + 2:end]) {
Some(name) => name
None => raise InvalidUTF8
}
(name, end + 1)
}
///|
fn pdf_metadata_xml_skip_processing_instruction(
data : BytesView,
start : Int,
) -> Int raise PdfError {
let end = pdf_metadata_find_bytes_from(data, start + 2, b"?>")
guard end >= 0 else { raise BadText }
end + 2
}
///|
fn pdf_metadata_xml_skip_comment(
data : BytesView,
start : Int,
) -> Int raise PdfError {
let end = pdf_metadata_find_bytes_from(data, start + 4, b"-->")
guard end >= 0 else { raise BadText }
end + 3
}
///|
fn pdf_metadata_xml_skip_misc(
data : BytesView,
start : Int,
) -> Int raise PdfError {
let mut index = start
let mut done = false
while !done {
index = pdf_metadata_xml_skip_space(data, index)
if index + 1 < data.length() &&
pdf_metadata_view_matches_at(data, index, b"") {
index = pdf_metadata_xml_skip_processing_instruction(data, index)
} else if index + 3 < data.length() &&
pdf_metadata_view_matches_at(data, index, b"