///|
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))
}
///|
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
}
///|
pub let pdf_metadata_adobe_namespace : String = "http://ns.adobe.com/pdf/1.3/"
///|
pub let pdf_metadata_xmp_namespace : String = "http://ns.adobe.com/xap/1.0/"
///|
pub let pdf_metadata_dc_namespace : String = "http://purl.org/dc/elements/1.1/"
///|
pub let pdf_metadata_rdf_namespace : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
///|
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"
///|
pub let pdf_metadata_pdfxid_namespace : String = "http://www.npes.org/pdfx/ns/id/"
///|
pub let pdf_metadata_pdfe_namespace : String = "http://www.aiim.org/pdfe/ns/id/"
///|
pub let pdf_metadata_pdfuaid_namespace : String = "http://www.aiim.org/pdfua/ns/id/"
///|
pub let pdf_metadata_pdfvtid_namespace : String = "http://www.npes.org/pdfvt/ns/id/"
///|
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 output : Array[Byte] = []
for codepoint in pdf_codepoints_of_pdfdocstring(bytes) {
if codepoint <= 127 {
output.push(pdf_byte_of_int(codepoint))
}
}
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)
}
}
///|
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)
}
}
}
}
///|
/// 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 { "" }
}
}
///|
/// 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 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
}
}
///|
/// 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
}
///|
/// 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 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 `/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_decode_xml_text(value : BytesView) -> String? {
let text = @utf8.decode(pdf_metadata_trim_xml_text(value)) catch {
_ => return None
}
Some(text)
}
///|
fn pdf_metadata_find_byte(data : BytesView, start : Int, byte : Int) -> Int {
for position = start; position < data.length(); position = position + 1 {
if data[position].to_int() == byte {
break position
}
} nobreak {
-1
}
}
///|
fn pdf_metadata_view_matches_at(
data : BytesView,
position : Int,
pattern : BytesView,
) -> Bool {
if position < 0 || position + pattern.length() > data.length() {
false
} else {
for index = 0; index < pattern.length(); index = index + 1 {
if data[position + index] != pattern[index] {
break false
}
} nobreak {
true
}
}
}
///|
fn pdf_metadata_find_bytes_from(
data : BytesView,
start : Int,
pattern : BytesView,
) -> Int {
if pattern.length() == 0 {
start
} else if data.length() < pattern.length() ||
start > data.length() - pattern.length() {
-1
} else {
for position = start
position <= data.length() - pattern.length()
position = position + 1 {
if pdf_metadata_view_matches_at(data, position, pattern) {
break position
}
} nobreak {
-1
}
}
}
///|
fn pdf_metadata_add_unique(values : Array[String], value : String) -> Unit {
if value != "" && !values.contains(value) {
values.push(value)
}
}
///|
fn pdf_metadata_namespace_prefixes(
data : BytesView,
xml_namespace : String,
fallback : String,
) -> Array[String] {
let prefixes : Array[String] = []
pdf_metadata_add_unique(prefixes, fallback)
let namespace_bytes = @ascii.encode(xml_namespace)
let xmlns = [120, 109, 108, 110, 115, 58]
let mut search_start = 0
while search_start < data.length() {
let namespace_start = pdf_metadata_find_bytes_from(
data, search_start, namespace_bytes,
)
if namespace_start < 0 {
search_start = data.length()
} else {
let mut attribute_start = namespace_start
while attribute_start > 0 &&
!pdf_metadata_is_xml_separator(data[attribute_start - 1]) {
attribute_start -= 1
}
if pdf_view_matches_ascii_at(data, attribute_start, xmlns) {
let prefix_start = attribute_start + xmlns.length()
let mut prefix_end = prefix_start
while prefix_end < namespace_start &&
data[prefix_end].to_int() != 61 &&
data[prefix_end].to_int() != 34 &&
data[prefix_end].to_int() != 39 {
prefix_end += 1
}
match pdf_metadata_decode_xml_text(data[prefix_start:prefix_end]) {
Some(prefix) => pdf_metadata_add_unique(prefixes, prefix)
None => ()
}
}
search_start = namespace_start + namespace_bytes.length()
}
}
prefixes
}
///|
fn pdf_metadata_extract_quoted_xml_value(
data : BytesView,
start : Int,
quote : Int,
) -> String? {
let end = pdf_metadata_find_byte(data, start, quote)
if end < 0 {
None
} else {
pdf_metadata_decode_xml_text(data[start:end])
}
}
///|
fn pdf_metadata_extract_xmp_attribute(
data : BytesView,
prefix : String,
name : String,
) -> String? {
let double_pattern = @ascii.encode(prefix + ":" + name + "=\"")
let double_start = pdf_metadata_find_bytes_from(data, 0, double_pattern)
if double_start >= 0 {
pdf_metadata_extract_quoted_xml_value(
data,
double_start + double_pattern.length(),
34,
)
} else {
let single_pattern = @ascii.encode(prefix + ":" + name + "='")
let single_start = pdf_metadata_find_bytes_from(data, 0, single_pattern)
if single_start >= 0 {
pdf_metadata_extract_quoted_xml_value(
data,
single_start + single_pattern.length(),
39,
)
} else {
None
}
}
}
///|
fn pdf_metadata_combine_list_items(values : ArrayView[String]) -> String {
let mut output = ""
for value in values {
if output == "" {
output = value
} else {
output = output + ", " + value
}
}
output
}
///|
fn pdf_metadata_extract_rdf_list_items_for_prefix(
value : BytesView,
prefix : String,
) -> Array[String] {
let items : Array[String] = []
let open_pattern = @ascii.encode("<" + prefix + ":li")
let close_pattern = @ascii.encode("" + prefix + ":li>")
let mut search_start = 0
while search_start < value.length() {
let item_start = pdf_metadata_find_bytes_from(
value, search_start, open_pattern,
)
if item_start < 0 {
search_start = value.length()
} else {
let name_end = item_start + open_pattern.length()
if name_end >= value.length() ||
!pdf_metadata_xml_tag_boundary(value[name_end]) {
search_start = name_end
} else {
let item_tag_end = pdf_metadata_find_byte(value, name_end, 62)
if item_tag_end < 0 {
search_start = value.length()
} else if value[item_tag_end - 1].to_int() == 47 {
search_start = item_tag_end + 1
} else {
let item_value_start = item_tag_end + 1
let item_value_end = pdf_metadata_find_bytes_from(
value, item_value_start, close_pattern,
)
if item_value_end < 0 {
search_start = value.length()
} else {
match
pdf_metadata_decode_xml_text(
value[item_value_start:item_value_end],
) {
Some(item) => items.push(item)
None => ()
}
search_start = item_value_end + close_pattern.length()
}
}
}
}
}
items
}
///|
fn pdf_metadata_extract_rdf_list_items(
data : BytesView,
value : BytesView,
) -> String? {
let items : Array[String] = []
let prefixes = pdf_metadata_namespace_prefixes(
data, pdf_metadata_rdf_namespace, "rdf",
)
for prefix in prefixes {
for item in pdf_metadata_extract_rdf_list_items_for_prefix(value, prefix) {
items.push(item)
}
}
if items.length() == 0 {
None
} else {
Some(pdf_metadata_combine_list_items(items))
}
}
///|
fn pdf_metadata_extract_xmp_element(
data : BytesView,
prefix : String,
name : String,
) -> String? {
let open_pattern = @ascii.encode("<" + prefix + ":" + name)
let tag_start = pdf_metadata_find_bytes_from(data, 0, open_pattern)
if tag_start < 0 {
None
} else {
let tag_end = pdf_metadata_find_byte(
data,
tag_start + open_pattern.length(),
62,
)
if tag_end < 0 || data[tag_end - 1].to_int() == 47 {
None
} else {
let close_pattern = @ascii.encode("" + prefix + ":" + name + ">")
let value_start = tag_end + 1
let value_end = pdf_metadata_find_bytes_from(
data, value_start, close_pattern,
)
if value_end < 0 {
None
} else {
let value = data[value_start:value_end]
match pdf_metadata_extract_rdf_list_items(data, value) {
Some(items) => Some(items)
None => pdf_metadata_decode_xml_text(value)
}
}
}
}
}
///|
fn pdf_metadata_get_xmp_data(
data : BytesView,
xml_namespace : String,
fallback_prefix : String,
name : String,
) -> String? {
let prefixes = pdf_metadata_namespace_prefixes(
data, xml_namespace, fallback_prefix,
)
for prefix in prefixes {
match pdf_metadata_extract_xmp_attribute(data, prefix, name) {
Some(value) => break Some(value)
None =>
match pdf_metadata_extract_xmp_element(data, prefix, name) {
Some(value) => break Some(value)
None => ()
}
}
} nobreak {
None
}
}
///|
fn pdf_metadata_decode_pdf_string(value : BytesView) -> String? {
let text = @utf8.decode(value) catch { _ => return None }
Some(text)
}
///|
fn pdf_metadata_json_string_array(values : ArrayView[String]) -> Json {
let items : Array[Json] = []
for value in values {
items.push(Json::string(value))
}
Json::array(items)
}
///|
fn pdf_metadata_unit_number(value : Double, unit : PdfUnit) -> Double {
match unit {
PdfPoint => value
Inch => unit_inches(value, PdfPoint)
Centimetre => unit_centimetres(value, PdfPoint)
Millimetre => unit_millimetres(value, PdfPoint)
}
}
///|
fn pdf_metadata_string_json(value : String) -> Json {
if value == "" {
Json::null()
} else {
Json::string(value)
}
}
///|
fn pdf_metadata_bool_json(value : String) -> Json {
if value == "" {
Json::null()
} else {
Json::boolean(value.to_lower() == "true")
}
}
///|
fn PdfDocument::pdf_metadata_info_encoded(
self : PdfDocument,
key : PdfName,
encoding : PdfMetadataEncoding,
) -> 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_lossy(
pdf_metadata_encode_output(encoding, bytes) catch {
_ => return ""
},
)
Some(PdfBoolean(value)) => pdf_metadata_bool_string(value)
_ => if key == pdf_metadata_trapped() { "False" } else { "" }
}
}
///|
fn PdfDocument::pdf_metadata_box_array_json(
self : PdfDocument,
box : PdfObject,
unit : PdfUnit,
) -> Json? {
match self.direct(box) {
PdfArray(values) if values.length() == 4 => {
let output : Array[Json] = []
for value in values {
let number = self.get_number(value) catch { _ => return None }
output.push(Json::number(pdf_metadata_unit_number(number, unit)))
}
Some(Json::array(output))
}
_ => None
}
}
///|
fn PdfDocument::pdf_metadata_page_box_json(
self : PdfDocument,
boxes : ArrayView[PdfObject?],
unit : PdfUnit,
) -> Json {
if boxes.length() == 0 {
return Json::null()
}
let first = boxes[0]
for index in 1..
match self.pdf_metadata_box_array_json(box, unit) {
Some(json) => json
None => Json::null()
}
None => Json::null()
}
}
///|
fn pdf_metadata_set_json_string(
output : Map[String, Json],
key : String,
value : String,
) -> Unit {
output.set(key, pdf_metadata_string_json(value))
}
///|
fn PdfDocument::pdf_metadata_info_json_boxes(
self : PdfDocument,
pages : ArrayView[PdfPage],
unit : PdfUnit,
output : Map[String, Json],
) -> Unit {
let mediaboxes : Array[PdfObject?] = []
let cropboxes : Array[PdfObject?] = []
let bleedboxes : Array[PdfObject?] = []
let trimboxes : Array[PdfObject?] = []
let artboxes : Array[PdfObject?] = []
for page in pages {
mediaboxes.push(Some(page.mediabox))
cropboxes.push(self.lookup_direct(pdf_metadata_cropbox(), page.rest))
bleedboxes.push(self.lookup_direct(pdf_metadata_bleedbox(), page.rest))
trimboxes.push(self.lookup_direct(pdf_metadata_trimbox(), page.rest))
artboxes.push(self.lookup_direct(pdf_metadata_artbox(), page.rest))
}
output.set("MediaBox", self.pdf_metadata_page_box_json(mediaboxes, unit))
output.set("CropBox", self.pdf_metadata_page_box_json(cropboxes, unit))
output.set("BleedBox", self.pdf_metadata_page_box_json(bleedboxes, unit))
output.set("TrimBox", self.pdf_metadata_page_box_json(trimboxes, unit))
output.set("ArtBox", self.pdf_metadata_page_box_json(artboxes, unit))
}
///|
let pdf_metadata_xmp_report_fields : Array[(String, String, String)] = [
("XMP pdf:Keywords", pdf_metadata_adobe_namespace, "Keywords"),
("XMP pdf:Producer", pdf_metadata_adobe_namespace, "Producer"),
("XMP pdf:Trapped", pdf_metadata_adobe_namespace, "Trapped"),
("XMP pdf:Title", pdf_metadata_adobe_namespace, "Title"),
("XMP pdf:Creator", pdf_metadata_adobe_namespace, "Creator"),
("XMP pdf:Subject", pdf_metadata_adobe_namespace, "Subject"),
("XMP pdf:Author", pdf_metadata_adobe_namespace, "Author"),
("XMP pdf:CreationDate", pdf_metadata_adobe_namespace, "CreationDate"),
("XMP pdf:ModDate", pdf_metadata_adobe_namespace, "ModDate"),
("XMP xmp:CreateDate", pdf_metadata_xmp_namespace, "CreateDate"),
("XMP xmp:CreatorTool", pdf_metadata_xmp_namespace, "CreatorTool"),
("XMP xmp:MetadataDate", pdf_metadata_xmp_namespace, "MetadataDate"),
("XMP xmp:ModifyDate", pdf_metadata_xmp_namespace, "ModifyDate"),
("XMP dc:title", pdf_metadata_dc_namespace, "title"),
("XMP dc:creator", pdf_metadata_dc_namespace, "creator"),
("XMP dc:subject", pdf_metadata_dc_namespace, "subject"),
("XMP dc:description", pdf_metadata_dc_namespace, "description"),
("PDF/UA pdfuaid:amd", pdf_metadata_pdfuaid_namespace, "amd"),
("PDF/UA pdfuaid:corr", pdf_metadata_pdfuaid_namespace, "corr"),
]
///|
fn pdf_metadata_add_xmp_report_field(
output : Map[String, Json],
metadata : BytesView,
field : (String, String, String),
) -> Unit {
match
pdf_metadata_get_xmp_data(
metadata,
field.1,
pdf_metadata_namespace_fallback_prefix(field.1),
field.2,
) {
Some(value) => output.set(field.0, Json::string(value))
None => ()
}
}
///|
fn pdf_metadata_push_view(output : Array[Byte], bytes : BytesView) -> Unit {
for byte in bytes {
output.push(byte)
}
}
///|
fn pdf_metadata_xml_text(value : String) -> PdfBytes {
@utf8.encode(
value
.replace(old="&", new="&")
.replace(old="<", new="<")
.replace(old=">", new=">"),
)
}
///|
fn pdf_metadata_xml_tag_boundary(byte : Byte) -> Bool {
match byte.to_int() {
9 | 10 | 13 | 32 | 47 | 62 => true
_ => false
}
}
///|
fn pdf_metadata_rewrite_xmp_element_prefix(
data : BytesView,
prefix : String,
field : String,
replacement : String,
) -> (PdfBytes, Bool) {
let open_pattern = @ascii.encode("<" + prefix + ":" + field)
let close_pattern = @ascii.encode("" + prefix + ":" + field + ">")
let replacement_bytes = pdf_metadata_xml_text(replacement)
let output : Array[Byte] = []
let mut changed = false
let mut search_start = 0
while search_start < data.length() {
let tag_start = pdf_metadata_find_bytes_from(
data, search_start, open_pattern,
)
if tag_start < 0 {
pdf_metadata_push_view(output, data[search_start:])
search_start = data.length()
} else {
let name_end = tag_start + open_pattern.length()
if name_end >= data.length() ||
!pdf_metadata_xml_tag_boundary(data[name_end]) {
pdf_metadata_push_view(output, data[search_start:name_end])
search_start = name_end
} else {
let tag_end = pdf_metadata_find_byte(data, name_end, 62)
if tag_end < 0 {
pdf_metadata_push_view(output, data[search_start:])
search_start = data.length()
} else if tag_end > tag_start && data[tag_end - 1].to_int() == 47 {
pdf_metadata_push_view(output, data[search_start:tag_end - 1])
output.push((62).to_byte())
pdf_metadata_push_view(output, replacement_bytes)
pdf_metadata_push_view(output, close_pattern)
search_start = tag_end + 1
changed = true
} else {
let value_start = tag_end + 1
let value_end = pdf_metadata_find_bytes_from(
data, value_start, close_pattern,
)
if value_end < 0 {
pdf_metadata_push_view(output, data[search_start:])
search_start = data.length()
} else {
pdf_metadata_push_view(output, data[search_start:value_start])
pdf_metadata_push_view(output, replacement_bytes)
pdf_metadata_push_view(
output,
data[value_end:value_end + close_pattern.length()],
)
search_start = value_end + close_pattern.length()
changed = true
}
}
}
}
}
if changed {
(Bytes::from_array(output), true)
} else {
(data.to_owned(), false)
}
}
///|
fn pdf_metadata_rewrite_xmp_field(
data : BytesView,
xml_namespace : String,
fallback_prefix : String,
field : String,
replacement : String,
) -> PdfBytes {
let mut output = data.to_owned()
let prefixes = pdf_metadata_namespace_prefixes(
data, xml_namespace, fallback_prefix,
)
for prefix in prefixes {
let (rewritten, _) = pdf_metadata_rewrite_xmp_element_prefix(
output, prefix, field, replacement,
)
output = rewritten
}
output
}
///|
fn pdf_metadata_pad2(value : Int) -> String {
let absolute = if value < 0 { -value } else { value }
if absolute < 10 {
"0" + absolute.to_string()
} else {
absolute.to_string()
}
}
///|
fn pdf_metadata_pad4(value : Int) -> String {
if value < 10 {
"000" + value.to_string()
} else if value < 100 {
"00" + value.to_string()
} else if value < 1000 {
"0" + value.to_string()
} else {
value.to_string()
}
}
///|
fn pdf_metadata_xmp_date(date : String) -> String raise PdfError {
if !date.has_prefix("D:") {
"0000-01-01T00:00:00Z"
} else {
let parsed = pdf_date_of_string(date)
let tzd = if parsed.hour_offset == 0 && parsed.minute_offset == 0 {
"Z"
} else {
let sign = if parsed.hour_offset < 0 || parsed.minute_offset < 0 {
"-"
} else {
"+"
}
sign +
pdf_metadata_pad2(parsed.hour_offset) +
":" +
pdf_metadata_pad2(parsed.minute_offset)
}
pdf_metadata_pad4(parsed.year) +
"-" +
pdf_metadata_pad2(parsed.month) +
"-" +
pdf_metadata_pad2(parsed.day) +
"T" +
pdf_metadata_pad2(parsed.hour) +
":" +
pdf_metadata_pad2(parsed.minute) +
":" +
pdf_metadata_pad2(parsed.second) +
tzd
}
}
///|
fn pdf_metadata_is_leap_year(year : Int) -> Bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
///|
fn pdf_metadata_days_in_month(year : Int, month : Int) -> Int {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
4 | 6 | 9 | 11 => 30
2 => if pdf_metadata_is_leap_year(year) { 29 } else { 28 }
_ => 30
}
}
///|
fn pdf_metadata_pdf_date_from_unix_millis(milliseconds : UInt64) -> String {
let total_seconds = (milliseconds / (1000 : UInt64)).reinterpret_as_int64()
let mut days = (total_seconds / 86400L).to_int()
let seconds_of_day = (total_seconds % 86400L).to_int()
let hour = seconds_of_day / 3600
let minute = seconds_of_day % 3600 / 60
let second = seconds_of_day % 60
let mut year = 1970
let mut year_days = if pdf_metadata_is_leap_year(year) { 366 } else { 365 }
while days >= year_days {
days -= year_days
year += 1
year_days = if pdf_metadata_is_leap_year(year) { 366 } else { 365 }
}
let mut month = 1
while days >= pdf_metadata_days_in_month(year, month) {
days -= pdf_metadata_days_in_month(year, month)
month += 1
}
let day = days + 1
"D:" +
pdf_metadata_pad4(year) +
pdf_metadata_pad2(month) +
pdf_metadata_pad2(day) +
pdf_metadata_pad2(hour) +
pdf_metadata_pad2(minute) +
pdf_metadata_pad2(second)
}
///|
fn pdf_metadata_expand_date(date : String) -> String {
if date == "now" {
match @env.get_env_var("CPDF_REPRODUCIBLE_DATES") {
Some("true") =>
pdf_strftime_with_time(pdf_strftime_dummy_time(), "D:%Y%m%d%H%M%S")
_ => pdf_metadata_pdf_date_from_unix_millis(@env.now())
}
} else {
date
}
}
///|
fn pdf_metadata_replace_all(
text : String,
old : String,
new : String,
) -> String {
let mut output = text
if old == "" {
output
} else {
while output.contains(old) {
output = output.replace(old~, new~)
}
output
}
}
///|
fn PdfDocument::pdf_metadata_template_replacements(
self : PdfDocument,
) -> Array[(String, String)] raise PdfError {
let info = fn(name : String) -> String {
self.info_utf8(pdf_metadata_name(name))
}
let creation_date = match info("/CreationDate") {
"" => pdf_metadata_expand_date("now")
value => value
}
let mod_date = match info("/ModDate") {
"" => pdf_metadata_expand_date("now")
value => value
}
[
("CREATEDATE", pdf_metadata_xmp_date(creation_date)),
("MODDATE", pdf_metadata_xmp_date(mod_date)),
("PRODUCER", info("/Producer")),
("CREATOR", info("/Creator")),
("TITLE", info("/Title")),
("SUBJECT", info("/Subject")),
("AUTHOR", info("/Author")),
("KEYWORDS", info("/Keywords")),
("TRAPPED", info("/Trapped")),
("METADATADATE", pdf_metadata_xmp_date(pdf_metadata_expand_date("now"))),
]
}
///|
fn pdf_metadata_xml_value_of_object(value : PdfObject) -> String raise PdfError {
match value {
PdfString(bytes) =>
@utf8.decode(pdf_utf8_of_pdfdocstring(bytes)) catch {
_ => raise InvalidUTF8
}
PdfBoolean(true) => "True"
PdfBoolean(false) => "False"
_ => raise BadNumberArgument("set_pdf_info_xml: not a string")
}
}
///|
fn pdf_metadata_xmp_date_of_object(value : PdfObject) -> String raise PdfError {
match value {
PdfString(bytes) =>
pdf_metadata_xmp_date(
@ascii.decode(bytes) catch {
_ => raise InvalidUTF8
},
)
_ => raise BadNumberArgument("xmp_date not a string")
}
}
///|
fn pdf_metadata_info_xmp_changes(
key : PdfName,
value : PdfObject,
) -> (Array[(String, String)], String) raise PdfError {
match pdf_metadata_name_with_slash(key) {
Some("/Producer") =>
(
[(pdf_metadata_adobe_namespace, "Producer")],
pdf_metadata_xml_value_of_object(value),
)
Some("/Creator") =>
(
[
(pdf_metadata_adobe_namespace, "Creator"),
(pdf_metadata_xmp_namespace, "CreatorTool"),
(pdf_metadata_dc_namespace, "creator"),
],
pdf_metadata_xml_value_of_object(value),
)
Some("/Author") =>
(
[(pdf_metadata_adobe_namespace, "Author")],
pdf_metadata_xml_value_of_object(value),
)
Some("/Title") =>
(
[
(pdf_metadata_adobe_namespace, "Title"),
(pdf_metadata_dc_namespace, "title"),
],
pdf_metadata_xml_value_of_object(value),
)
Some("/Subject") =>
(
[
(pdf_metadata_adobe_namespace, "Subject"),
(pdf_metadata_dc_namespace, "subject"),
],
pdf_metadata_xml_value_of_object(value),
)
Some("/Keywords") =>
(
[(pdf_metadata_adobe_namespace, "Keywords")],
pdf_metadata_xml_value_of_object(value),
)
Some("/CreationDate") =>
(
[
(pdf_metadata_adobe_namespace, "CreationDate"),
(pdf_metadata_xmp_namespace, "CreateDate"),
],
pdf_metadata_xmp_date_of_object(value),
)
Some("/ModDate") =>
(
[
(pdf_metadata_adobe_namespace, "ModDate"),
(pdf_metadata_xmp_namespace, "ModifyDate"),
],
pdf_metadata_xmp_date_of_object(value),
)
Some("/Trapped") =>
(
[(pdf_metadata_adobe_namespace, "Trapped")],
pdf_metadata_xml_value_of_object(value),
)
_ => raise BadNumberArgument("Unknown call to set_pdf_info")
}
}
///|
fn PdfDocument::pdf_metadata_fallback_pdfx(self : PdfDocument) -> String? {
match self.lookup_direct(pdf_metadata_info(), self.trailer_dict()) {
Some(info) =>
match self.lookup_direct(pdf_metadata_name("/GTS_PDFXVersion"), info) {
Some(PdfString(value)) => pdf_metadata_decode_pdf_string(value)
_ => None
}
None => None
}
}
///|
fn pdf_metadata_reverse_strings(values : ArrayView[String]) -> Array[String] {
let output : Array[String] = []
for index = values.length() - 1; index >= 0; index = index - 1 {
output.push(values[index])
}
output
}
///|
fn pdf_metadata_push_if_some(values : Array[String], value : String?) -> Unit {
match value {
Some(value) if value != "" => values.push(value)
_ => ()
}
}
///|
fn pdf_metadata_namespace_fallback_prefix(xml_namespace : String) -> String {
if xml_namespace == pdf_metadata_adobe_namespace {
"pdf"
} else if xml_namespace == pdf_metadata_xmp_namespace {
"xmp"
} else if xml_namespace == pdf_metadata_dc_namespace {
"dc"
} else if xml_namespace == pdf_metadata_pdfuaid_namespace {
"pdfuaid"
} else if xml_namespace == pdf_metadata_pdfe_namespace {
"pdfe"
} else if xml_namespace == pdf_metadata_pdfaid_namespace {
"pdfaid"
} else if xml_namespace == pdf_metadata_pdfxid_namespace {
"pdfxid"
} else if xml_namespace == pdf_metadata_pdfvtid_namespace {
"pdfvtid"
} else {
""
}
}
///|
fn pdf_metadata_name_with_slash(name : PdfName) -> String? {
let text = @ascii.decode(name.bytes()) catch { _ => return None }
Some(text)
}
///|
fn pdf_metadata_xmp_info_fields(key : PdfName) -> Array[(String, String)]? {
match pdf_metadata_name_with_slash(key) {
Some("/Title") =>
Some([
(pdf_metadata_adobe_namespace, "Title"),
(pdf_metadata_dc_namespace, "title"),
])
Some("/Author") =>
Some([
(pdf_metadata_adobe_namespace, "Author"),
(pdf_metadata_dc_namespace, "creator"),
])
Some("/Subject") =>
Some([
(pdf_metadata_adobe_namespace, "Subject"),
(pdf_metadata_dc_namespace, "subject"),
])
Some("/Keywords") => Some([(pdf_metadata_adobe_namespace, "Keywords")])
Some("/Creator") =>
Some([
(pdf_metadata_adobe_namespace, "Creator"),
(pdf_metadata_xmp_namespace, "CreatorTool"),
])
Some("/Producer") => Some([(pdf_metadata_adobe_namespace, "Producer")])
Some("/CreationDate") =>
Some([
(pdf_metadata_adobe_namespace, "CreationDate"),
(pdf_metadata_xmp_namespace, "CreateDate"),
])
Some("/ModDate") =>
Some([
(pdf_metadata_adobe_namespace, "ModificationDate"),
(pdf_metadata_xmp_namespace, "ModifyDate"),
])
_ => None
}
}
///|
/// Return XMP metadata corresponding to a legacy `/Info` key.
pub fn PdfDocument::xmp_info(self : PdfDocument, key : PdfName) -> String {
let fields = match pdf_metadata_xmp_info_fields(key) {
Some(fields) => fields
None => return ""
}
let metadata = try self.metadata() catch {
_ => return ""
} noraise {
Some(metadata) => metadata
None => return ""
}
for field in fields {
let value = pdf_metadata_get_xmp_data(
metadata,
field.0,
pdf_metadata_namespace_fallback_prefix(field.0),
field.1,
)
match value {
Some(value) if value != "" => break value
_ => ()
}
} nobreak {
""
}
}
///|
/// Determine PDF/A, PDF/E, PDF/UA, PDF/VT, and PDF/X subformat markers.
///
/// This ports cpdf's in-memory subformat detection. It recognizes simple XMP
/// element and attribute forms and falls back to `/Info /GTS_PDFXVersion`.
pub fn PdfDocument::determine_subformats(
self : PdfDocument,
) -> Array[String] raise PdfError {
let formats : Array[String] = []
match self.metadata() {
None =>
pdf_metadata_push_if_some(formats, self.pdf_metadata_fallback_pdfx())
Some(metadata) => {
pdf_metadata_push_if_some(
formats,
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfe_namespace, "pdfe", "ISO_PDFEVersion",
),
)
match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfuaid_namespace, "pdfuaid", "part",
) {
Some(part) if part != "" => formats.push("PDF/UA-" + part)
_ => ()
}
let pdfa_part = match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_namespace, "pdfaid", "part",
) {
Some(part) => Some(part)
None =>
match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_wrong_acrobat_namespace, "pdfaid", "part",
) {
Some(part) => Some(part)
None =>
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_wrong_iso_namespace, "pdfaid", "part",
)
}
}
match pdfa_part {
Some(part) if part != "" => {
let conformance = match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_namespace, "pdfaid", "conformance",
) {
Some(value) => value.to_lower()
None =>
match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_wrong_acrobat_namespace, "pdfaid",
"conformance",
) {
Some(value) => value.to_lower()
None =>
match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfaid_wrong_iso_namespace, "pdfaid",
"conformance",
) {
Some(value) => value.to_lower()
None => ""
}
}
}
formats.push("PDF/A-" + part + conformance)
}
_ => ()
}
match
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfxid_namespace, "pdfxid", "GTS_PDFXVersion",
) {
Some(value) if value != "" => formats.push(value)
_ =>
pdf_metadata_push_if_some(formats, self.pdf_metadata_fallback_pdfx())
}
pdf_metadata_push_if_some(
formats,
pdf_metadata_get_xmp_data(
metadata, pdf_metadata_pdfvtid_namespace, "pdfvtid", "GTS_PDFVTVersion",
),
)
}
}
pdf_metadata_reverse_strings(formats)
}
///|
/// Return cpdf-style document information report fields as JSON.
pub fn PdfDocument::info_json(
self : PdfDocument,
encoding? : PdfMetadataEncoding = PdfMetadataUTF8,
unit? : PdfUnit = PdfPoint,
) -> Json raise PdfError {
let output : Map[String, Json] = Map([])
let (major, minor) = self.version()
let pages = self.pages_of_pagetree()
output.set(
"Version",
Json::array([pdf_util_json_number(major), pdf_util_json_number(minor)]),
)
output.set("Pages", pdf_util_json_number(pages.length()))
let info_fields : Array[(String, PdfName)] = [
("Title", pdf_metadata_name("/Title")),
("Author", pdf_metadata_name("/Author")),
("Subject", pdf_metadata_name("/Subject")),
("Keywords", pdf_metadata_name("/Keywords")),
("Creator", pdf_metadata_name("/Creator")),
("Producer", pdf_metadata_name("/Producer")),
("Created", pdf_metadata_name("/CreationDate")),
("Modified", pdf_metadata_name("/ModDate")),
]
for field in info_fields {
pdf_metadata_set_json_string(
output,
field.0,
self.pdf_metadata_info_encoded(field.1, encoding),
)
}
output.set(
"Trapped",
pdf_metadata_bool_json(
self.pdf_metadata_info_encoded(pdf_metadata_trapped(), encoding),
),
)
pdf_metadata_set_json_string(
output,
"PageMode",
self.catalog_item(pdf_metadata_page_mode()),
)
pdf_metadata_set_json_string(
output,
"PageLayout",
self.catalog_item(pdf_metadata_page_layout()),
)
output.set(
"OpenAction",
self.pdf_util_json_of_object(
self.open_action(),
true,
false,
false,
false,
false,
),
)
let viewer_bool_fields : Array[(String, PdfName)] = [
("HideToolbar", pdf_metadata_name("/HideToolbar")),
("HideMenubar", pdf_metadata_name("/HideMenubar")),
("HideWindowUI", pdf_metadata_name("/HideWindowUI")),
("FitWindow", pdf_metadata_name("/FitWindow")),
("CenterWindow", pdf_metadata_name("/CenterWindow")),
("DisplayDocTitle", pdf_metadata_name("/DisplayDocTitle")),
]
for field in viewer_bool_fields {
output.set(
field.0,
pdf_metadata_bool_json(self.viewer_preference_item(field.1)),
)
}
pdf_metadata_set_json_string(
output,
"NonFullPageScreenMode",
self.viewer_preference_item(pdf_metadata_non_full_screen_page_mode()),
)
output.set(
"AcroForm",
Json::boolean(self.catalog_item(pdf_metadata_acroform()) != ""),
)
output.set("XFA", Json::boolean(self.is_xfa()))
output.set(
"Marked",
Json::boolean(self.mark_info_item(pdf_metadata_name("/Marked"))),
)
output.set(
"UserProperties",
Json::boolean(self.mark_info_item(pdf_metadata_name("/UserProperties"))),
)
output.set(
"Suspects",
Json::boolean(self.mark_info_item(pdf_metadata_name("/Suspects"))),
)
self.pdf_metadata_info_json_boxes(pages, unit, output)
Json::object(output)
}
///|
/// Return cpdf-style document information report fields as UTF-8 JSON bytes.
pub fn PdfDocument::info_json_blob(
self : PdfDocument,
encoding? : PdfMetadataEncoding = PdfMetadataUTF8,
unit? : PdfUnit = PdfPoint,
) -> PdfBytes raise PdfError {
@utf8.encode(self.info_json(encoding~, unit~).stringify())
}
///|
/// Return cpdf-style XMP metadata report fields as JSON.
pub fn PdfDocument::xmp_info_json(self : PdfDocument) -> Json raise PdfError {
let output : Map[String, Json] = Map([])
output.set(
"Subformats",
pdf_metadata_json_string_array(self.determine_subformats()),
)
match self.language() {
Some(language) =>
output.set("Language", Json::string(@utf8.decode_lossy(language)))
None => output.set("Language", Json::null())
}
match self.metadata() {
Some(metadata) =>
for field in pdf_metadata_xmp_report_fields {
pdf_metadata_add_xmp_report_field(output, metadata, field)
}
None => ()
}
Json::object(output)
}
///|
/// Return cpdf-style XMP metadata report fields as UTF-8 JSON bytes.
pub fn PdfDocument::xmp_info_json_blob(
self : PdfDocument,
) -> PdfBytes raise PdfError {
@utf8.encode(self.xmp_info_json().stringify())
}
///|
/// Return the catalog `/Lang` value, if present.
pub fn PdfDocument::language(self : PdfDocument) -> PdfBytes? {
match
self.lookup_chain(self.trailer_dict(), [
pdf_metadata_root(),
pdf_metadata_lang(),
]) {
Some(PdfString(value)) => Some(value)
_ => None
}
}
///|
/// Set the catalog `/Lang` value.
pub fn PdfDocument::set_language(
self : PdfDocument,
language : BytesView,
) -> PdfDocument raise PdfError {
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.add_dict_entry(pdf_metadata_lang(), PdfString(language.to_owned()))
})
document
}
///|
fn pdf_metadata_stream(data : BytesView) -> PdfObject {
pdf_stream(
PdfDictionary([
(pdf_metadata_length(), PdfInteger(data.length())),
(pdf_metadata_type(), PdfNameObject(pdf_metadata_metadata())),
(pdf_metadata_subtype(), PdfNameObject(pdf_metadata_xml())),
]),
StreamGot(data.to_owned()),
)
}
///|
/// Set the catalog metadata XML stream from in-memory bytes.
pub fn PdfDocument::set_metadata_from_bytes(
self : PdfDocument,
data : BytesView,
keep_version? : Bool = false,
) -> PdfDocument raise PdfError {
let document = self.copy()
let metadata_number = document.add_object(pdf_metadata_stream(data))
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.add_dict_entry(
pdf_metadata_metadata(),
PdfIndirect(metadata_number),
)
})
document.pdf_metadata_bump_minor(4, keep_version)
document
}
///|
/// Set the XMP `/Metadata` stream's `xmp:MetadataDate` field.
pub fn PdfDocument::set_metadata_date(
self : PdfDocument,
date : String,
) -> PdfDocument raise PdfError {
match self.metadata() {
None => self
Some(metadata) => {
let value = if date == "now" {
pdf_metadata_xmp_date(pdf_metadata_expand_date(date))
} else {
date
}
let updated = pdf_metadata_rewrite_xmp_field(
metadata, pdf_metadata_xmp_namespace, "xmp", "MetadataDate", value,
)
self.set_metadata_from_bytes(updated, keep_version=true)
}
}
}
///|
/// Create a fresh XMP metadata stream from the document `/Info` dictionary.
pub fn PdfDocument::create_metadata(
self : PdfDocument,
) -> PdfDocument raise PdfError {
let mut xmp = pdf_metadata_xmp_template
for replacement in self.pdf_metadata_template_replacements() {
xmp = pdf_metadata_replace_all(xmp, replacement.0, replacement.1)
}
self.set_metadata_from_bytes(@utf8.encode(xmp))
}
///|
/// Extract the catalog metadata XML stream, decoding supported stream filters.
pub fn PdfDocument::metadata(self : PdfDocument) -> PdfBytes? raise PdfError {
match
self.lookup_chain(self.trailer_dict(), [
pdf_metadata_root(),
pdf_metadata_metadata(),
]) {
Some(PdfStreamObject(_) as stream) => {
let decoded = self.pdf_decode_stream(stream)
Some(decoded.stream_bytes())
}
_ => None
}
}
///|
/// Remove the catalog `/Metadata` entry.
pub fn PdfDocument::remove_metadata(
self : PdfDocument,
) -> PdfDocument raise PdfError {
let document = self.copy()
document.pdf_metadata_update_catalog(fn(catalog) raise PdfError {
catalog.remove_dict_entry(pdf_metadata_metadata())
})
document
}
///|
/// Remove the catalog metadata entry and every parsed object whose `/Type` is
/// `/Metadata`.
pub fn PdfDocument::remove_all_metadata(
self : PdfDocument,
) -> PdfDocument raise PdfError {
let document = self.remove_metadata()
let to_remove : Array[Int] = []
for entry in document.parsed_object_entries() {
match document.lookup_direct(pdf_metadata_type(), entry.2) {
Some(PdfNameObject(name)) if name == pdf_metadata_metadata() =>
to_remove.push(entry.0)
_ => ()
}
}
for number in to_remove {
document.remove_object(number)
}
document
}
///|
/// Compatibility wrapper for `PdfDocument::copy_id_from`.
pub fn pdf_copy_id(
keep_version : Bool,
copy_from : PdfDocument,
copy_to : PdfDocument,
) -> PdfDocument raise PdfError {
copy_to.copy_id_from(copy_from, keep_version~)
}
///|
/// Compatibility wrapper for `PdfDocument::set_info_entry`.
pub fn pdf_set_pdf_info(
key : PdfName,
value : PdfObject,
version : Int,
document : PdfDocument,
keep_version? : Bool = false,
xmp_also? : Bool = false,
xmp_just_set? : Bool = false,
) -> PdfDocument raise PdfError {
document.set_info_entry(
key,
value,
version,
keep_version~,
xmp_also~,
xmp_just_set~,
)
}
///|
/// Compatibility wrapper for `PdfDocument::info_utf8`.
pub fn pdf_get_info_utf8(document : PdfDocument, key : PdfName) -> String {
document.info_utf8(key)
}
///|
/// Compatibility wrapper for `PdfDocument::xmp_info`.
pub fn pdf_get_xmp_info(document : PdfDocument, key : PdfName) -> String {
document.xmp_info(key)
}
///|
/// Compatibility wrapper for `PdfDocument::set_viewer_preference`.
pub fn pdf_set_viewer_preference(
key : PdfName,
value : PdfObject,
version : Int,
document : PdfDocument,
) -> PdfDocument raise PdfError {
document.set_viewer_preference(key, value, version)
}
///|
/// Compatibility wrapper for `PdfDocument::set_page_layout`.
pub fn pdf_set_page_layout(
document : PdfDocument,
layout : String,
) -> PdfDocument raise PdfError {
document.set_page_layout(layout)
}
///|
/// Compatibility wrapper for `PdfDocument::set_page_mode`.
pub fn pdf_set_page_mode(
document : PdfDocument,
mode : String,
) -> PdfDocument raise PdfError {
document.set_page_mode(mode)
}
///|
/// Compatibility wrapper for `PdfDocument::set_non_full_screen_page_mode`.
pub fn pdf_set_non_full_screen_page_mode(
document : PdfDocument,
mode : String,
) -> PdfDocument raise PdfError {
document.set_non_full_screen_page_mode(mode)
}
///|
/// Compatibility wrapper for `PdfDocument::set_open_action`.
pub fn pdf_set_open_action(
document : PdfDocument,
fit : Bool,
page_number : Int,
destination? : PdfObject? = None,
) -> PdfDocument raise PdfError {
document.set_open_action(page_number, fit~, destination~)
}
///|
/// Compatibility wrapper for `PdfDocument::catalog_item`.
pub fn pdf_get_catalog_item(key : PdfName, document : PdfDocument) -> String {
document.catalog_item(key)
}
///|
/// Compatibility wrapper for `PdfDocument::viewer_preference_item`.
pub fn pdf_get_viewer_pref_item(
key : PdfName,
document : PdfDocument,
) -> String {
document.viewer_preference_item(key)
}
///|
/// Compatibility wrapper for `PdfDocument::mark_info_item`.
pub fn pdf_get_mark_info_item(key : PdfName, document : PdfDocument) -> Bool {
document.mark_info_item(key)
}
///|
/// Compatibility wrapper for `PdfDocument::is_xfa`.
pub fn pdf_is_xfa(document : PdfDocument) -> Bool {
document.is_xfa()
}
///|
/// Compatibility wrapper for `PdfDocument::open_action`.
pub fn pdf_get_open_action(document : PdfDocument) -> PdfObject raise PdfError {
document.open_action()
}
///|
/// Compatibility wrapper for `PdfDocument::open_action_json_blob`.
pub fn pdf_get_open_action_json_blob(
document : PdfDocument,
) -> PdfBytes raise PdfError {
document.open_action_json_blob()
}
///|
/// Compatibility wrapper for `PdfDocument::open_action_string`.
pub fn pdf_get_open_action_string(
document : PdfDocument,
) -> String raise PdfError {
document.open_action_string()
}
///|
/// Compatibility wrapper for cpdf's minor-version setter.
pub fn pdf_set_version(minor : Int, document : PdfDocument) -> Unit {
let (major, _) = document.version()
document.set_version(major, minor)
}
///|
/// Compatibility wrapper for `PdfDocument::determine_subformats`.
pub fn pdf_determine_subformats(
document : PdfDocument,
) -> Array[String] raise PdfError {
document.determine_subformats()
}
///|
/// Compatibility wrapper for `PdfDocument::info_json_blob`.
pub fn pdf_output_info_json_blob(
document : PdfDocument,
encoding? : PdfMetadataEncoding = PdfMetadataUTF8,
unit? : PdfUnit = PdfPoint,
) -> PdfBytes raise PdfError {
document.info_json_blob(encoding~, unit~)
}
///|
/// Compatibility wrapper for `PdfDocument::xmp_info_json_blob`.
pub fn pdf_output_xmp_info_json_blob(
document : PdfDocument,
) -> PdfBytes raise PdfError {
document.xmp_info_json_blob()
}
///|
/// Compatibility wrapper for `PdfDocument::language`.
pub fn pdf_language(document : PdfDocument) -> PdfBytes? {
document.language()
}
///|
/// Compatibility wrapper for `PdfDocument::set_language`.
pub fn pdf_set_language(
document : PdfDocument,
language : BytesView,
) -> PdfDocument raise PdfError {
document.set_language(language)
}
///|
/// Compatibility wrapper for `PdfDocument::set_metadata_from_bytes`.
pub fn pdf_set_metadata_from_bytes(
keep_version : Bool,
data : BytesView,
document : PdfDocument,
) -> PdfDocument raise PdfError {
document.set_metadata_from_bytes(data, keep_version~)
}
///|
/// Compatibility wrapper for `PdfDocument::metadata`.
pub fn pdf_get_metadata(document : PdfDocument) -> PdfBytes? raise PdfError {
document.metadata()
}
///|
/// Compatibility wrapper for `PdfDocument::set_metadata_date`.
pub fn pdf_set_metadata_date(
document : PdfDocument,
date : String,
) -> PdfDocument raise PdfError {
document.set_metadata_date(date)
}
///|
/// Compatibility wrapper for `PdfDocument::create_metadata`.
pub fn pdf_create_metadata(
document : PdfDocument,
) -> PdfDocument raise PdfError {
document.create_metadata()
}
///|
/// Compatibility wrapper for `PdfDocument::remove_metadata`.
pub fn pdf_remove_metadata(
document : PdfDocument,
) -> PdfDocument raise PdfError {
document.remove_metadata()
}
///|
/// Compatibility wrapper for `PdfDocument::remove_all_metadata`.
pub fn pdf_remove_all_metadata(
document : PdfDocument,
) -> PdfDocument raise PdfError {
document.remove_all_metadata()
}