///|
fn pdf_draft_name(text : String) -> @core.PdfName {
@core.pdf_name_of_bytes(@ascii.encode(text))
}
///|
let pdf_draft_subtype_key_name : @core.PdfName = pdf_draft_name("/Subtype")
///|
fn pdf_draft_subtype_key() -> @core.PdfName {
pdf_draft_subtype_key_name
}
///|
let pdf_draft_image_name : @core.PdfName = pdf_draft_name("/Image")
///|
fn pdf_draft_image() -> @core.PdfName {
pdf_draft_image_name
}
///|
let pdf_draft_form_name : @core.PdfName = pdf_draft_name("/Form")
///|
fn pdf_draft_form() -> @core.PdfName {
pdf_draft_form_name
}
///|
let pdf_draft_xobject_key_name : @core.PdfName = pdf_draft_name("/XObject")
///|
fn pdf_draft_xobject_key() -> @core.PdfName {
pdf_draft_xobject_key_name
}
///|
let pdf_draft_resources_key_name : @core.PdfName = pdf_draft_name("/Resources")
///|
fn pdf_draft_resources_key() -> @core.PdfName {
pdf_draft_resources_key_name
}
///|
let pdf_draft_length_key_name : @core.PdfName = pdf_draft_name("/Length")
///|
fn pdf_draft_length_key() -> @core.PdfName {
pdf_draft_length_key_name
}
///|
let pdf_draft_filter_key_name : @core.PdfName = pdf_draft_name("/Filter")
///|
fn pdf_draft_filter_key() -> @core.PdfName {
pdf_draft_filter_key_name
}
///|
let pdf_draft_decode_parms_key_name : @core.PdfName = pdf_draft_name(
"/DecodeParms",
)
///|
fn pdf_draft_decode_parms_key() -> @core.PdfName {
pdf_draft_decode_parms_key_name
}
///|
let pdf_draft_flate_decode_name : @core.PdfName = pdf_draft_name("/FlateDecode")
///|
fn pdf_draft_flate_decode() -> @core.PdfName {
pdf_draft_flate_decode_name
}
///|
fn pdf_draft_substitute_ops(boxes : Bool) -> Array[@content.PdfContentOp] {
if boxes {
[
Op_q,
Op_w(0.0),
Op_G(0.0),
Op_re(0.0, 0.0, 1.0, 1.0),
Op_m(0.0, 0.0),
Op_l(1.0, 1.0),
Op_m(0.0, 1.0),
Op_l(1.0, 0.0),
Op_S,
Op_Q,
]
} else {
[]
}
}
///|
fn pdf_draft_name_matches(
target : @core.PdfName?,
name : @core.PdfName,
) -> Bool {
match target {
Some(only_remove) => only_remove == name
None => true
}
}
///|
fn PdfDocument::pdf_draft_xobject_has_subtype(
self : PdfDocument,
object : @syntax.PdfObject,
subtype : @core.PdfName,
) -> Bool {
match self.lookup_direct(pdf_draft_subtype_key(), object) {
Some(PdfNameObject(found)) => found == subtype
_ => false
}
}
///|
fn PdfDocument::pdf_draft_xobject_is_image(
self : PdfDocument,
resources : @syntax.PdfObject,
name : @core.PdfName,
) -> Bool {
match self.lookup_direct(pdf_draft_xobject_key(), resources) {
Some(xobjects) =>
match self.lookup_direct(name, xobjects) {
Some(xobject) =>
self.pdf_draft_xobject_has_subtype(xobject, pdf_draft_image())
None => false
}
None => false
}
}
///|
fn PdfDocument::pdf_draft_rewrite_ops(
self : PdfDocument,
resources : @syntax.PdfObject,
ops : ArrayView[@content.PdfContentOp],
only_remove : @core.PdfName?,
boxes : Bool,
) -> Array[@content.PdfContentOp] {
let output : Array[@content.PdfContentOp] = []
let substitute = pdf_draft_substitute_ops(boxes)
for op in ops {
match op {
Op_Do(name) if self.pdf_draft_xobject_is_image(resources, name) &&
pdf_draft_name_matches(only_remove, name) =>
for replacement in substitute {
output.push(replacement)
}
Op_InlineImage(_, _, _) if only_remove is None =>
for replacement in substitute {
output.push(replacement)
}
_ => output.push(op)
}
}
output
}
///|
fn pdf_draft_stream_dictionary(
original : @syntax.PdfObject,
resources : @syntax.PdfObject,
length : Int,
) -> @syntax.PdfObject raise @core.PdfError {
original
.remove_dict_entry(pdf_draft_filter_key())
.remove_dict_entry(pdf_draft_decode_parms_key())
.add_dict_entry(pdf_draft_resources_key(), resources)
.add_dict_entry(pdf_draft_length_key(), PdfInteger(length))
}
///|
fn PdfDocument::pdf_draft_form_xobject(
self : PdfDocument,
object : @syntax.PdfObject,
only_remove : @core.PdfName?,
boxes : Bool,
) -> @syntax.PdfObject raise @core.PdfError {
let direct = self.direct(object)
match direct {
PdfStreamObject(original_stream) => {
let form_resources = match
self.lookup_direct(pdf_draft_resources_key(), direct) {
Some(resources) => resources
None => PdfDictionary([])
}
let rewritten_resources = self.pdf_draft_rewrite_resources(
form_resources, only_remove, boxes,
)
let ops = self.parse_content_ops_with_resources(form_resources, [direct])
let rewritten_stream = pdf_content_stream_of_ops(
self.pdf_draft_rewrite_ops(form_resources, ops, only_remove, boxes),
)
match rewritten_stream {
PdfStreamObject(stream) => {
let data = @syntax.PdfStreamObject(stream).stream_bytes()
PdfStreamObject({
dictionary: pdf_draft_stream_dictionary(
original_stream.dictionary,
rewritten_resources,
data.length(),
),
data: StreamGot(data),
})
}
_ => raise ParseStreamExpected
}
}
_ => raise ParseStreamExpected
}
}
///|
fn PdfDocument::pdf_draft_rewrite_xobject_entry(
self : PdfDocument,
name : @core.PdfName,
object : @syntax.PdfObject,
only_remove : @core.PdfName?,
boxes : Bool,
) -> (@core.PdfName, @syntax.PdfObject)? raise @core.PdfError {
let direct = self.direct(object)
if self.pdf_draft_xobject_has_subtype(direct, pdf_draft_image()) {
if pdf_draft_name_matches(only_remove, name) {
None
} else {
Some((name, object))
}
} else if self.pdf_draft_xobject_has_subtype(direct, pdf_draft_form()) {
let rewritten = self.pdf_draft_form_xobject(direct, only_remove, boxes)
Some((name, PdfIndirect(self.add_object(rewritten))))
} else {
Some((name, object))
}
}
///|
fn PdfDocument::pdf_draft_rewrite_xobjects(
self : PdfDocument,
xobjects : @syntax.PdfObject,
only_remove : @core.PdfName?,
boxes : Bool,
) -> @syntax.PdfObject raise @core.PdfError {
match self.direct(xobjects) {
PdfDictionary(entries) => {
let output : Array[(@core.PdfName, @syntax.PdfObject)] = []
for entry in entries {
let (name, object) = entry
match
self.pdf_draft_rewrite_xobject_entry(name, object, only_remove, boxes) {
Some(rewritten) => output.push(rewritten)
None => ()
}
}
PdfDictionary(output)
}
_ => raise DictionaryExpected
}
}
///|
fn PdfDocument::pdf_draft_rewrite_resources(
self : PdfDocument,
resources : @syntax.PdfObject,
only_remove : @core.PdfName?,
boxes : Bool,
) -> @syntax.PdfObject raise @core.PdfError {
match resources {
PdfDictionary(entries) => {
let output : Array[(@core.PdfName, @syntax.PdfObject)] = []
for entry in entries {
let (key, value) = entry
if key == pdf_draft_xobject_key() {
output.push(
(key, self.pdf_draft_rewrite_xobjects(value, only_remove, boxes)),
)
} else {
output.push(entry)
}
}
PdfDictionary(output)
}
PdfNull => resources
_ => raise DictionaryExpected
}
}
///|
fn PdfDocument::pdf_draft_page(
self : PdfDocument,
page : @page.PdfPage,
only_remove : @core.PdfName?,
boxes : Bool,
) -> @page.PdfPage raise @core.PdfError {
let rewritten_resources = self.pdf_draft_rewrite_resources(
page.resources,
only_remove,
boxes,
)
let ops = self.parse_content_ops_with_resources(page.resources, page.content)
let rewritten_stream = pdf_content_stream_of_ops(
self.pdf_draft_rewrite_ops(page.resources, ops, only_remove, boxes),
)
let encoded = pdf_encode_stream_with_filter(
pdf_draft_flate_decode(),
rewritten_stream,
only_if_smaller=true,
)
{ ..page, content: [encoded], resources: rewritten_resources, }
}
///|
fn pdf_draft_validate_page_numbers(
page_numbers : ArrayView[Int],
end_page : Int,
) -> Unit raise @core.PdfError {
for page_number in page_numbers {
if page_number < 1 || page_number > end_page {
raise BadPageSpecification(
"draft: range contains pages not present in pdf",
)
}
}
}
///|
/// Remove image XObjects and inline images from selected pages.
///
/// When `only_remove` is present, only matching image XObject invocations are
/// removed and inline images are retained. When `boxes` is true, removed images
/// are replaced by a crossed unit box, matching cpdf's draft mode.
pub fn PdfDocument::draft(
self : PdfDocument,
only_remove : @core.PdfName?,
boxes : Bool,
page_numbers : ArrayView[Int],
) -> PdfDocument raise @core.PdfError {
let end_page = self.endpage()
pdf_draft_validate_page_numbers(page_numbers, end_page)
let document = self.copy()
let pages = document.pages_of_pagetree()
let selected_pages = @core.pdf_number_set(page_numbers)
let rewritten : Array[@page.PdfPage] = Array(capacity=pages.length())
for index in 0.. PdfDocument raise @core.PdfError {
document.draft(only_remove, boxes, page_numbers)
}
///|
/// Compatibility wrapper matching cpdfdraft's string `only_remove` argument.
pub fn pdf_draft_string(
only_remove : String?,
boxes : Bool,
page_numbers : ArrayView[Int],
document : PdfDocument,
) -> PdfDocument raise @core.PdfError {
let only_remove_name = match only_remove {
Some(name) => Some(pdf_draft_name(name))
None => None
}
document.draft(only_remove_name, boxes, page_numbers)
}