///|
/// Return a copy of the document after applying cpdf's current path redaction.
///
/// The upstream `cpdfredact.redact` implementation currently runs the page
/// processor but does not rewrite page content, so this validates the selected
/// page range and preserves the document.
pub fn PdfDocument::redact_path(
self : PdfDocument,
path : ArrayView[(Double, Double)],
page_numbers : ArrayView[Int],
) -> PdfDocument raise @core.PdfError {
ignore(path)
let pages = self.pages_of_pagetree()
pdf_page_box_validate_page_numbers(page_numbers, pages.length(), "redact")
self.copy()
}
///|
/// Compatibility wrapper for `PdfDocument::redact_path`.
pub fn pdf_redact(
document : PdfDocument,
path : ArrayView[(Double, Double)],
page_numbers : ArrayView[Int],
) -> PdfDocument raise @core.PdfError {
document.redact_path(path, page_numbers)
}
///|
/// Apply redaction annotations.
///
/// This mirrors the current cpdf stub, which intentionally performs no work.
pub fn PdfDocument::apply_redactions(
self : PdfDocument,
page_numbers : ArrayView[Int],
) -> Unit {
ignore(self)
ignore(page_numbers)
}
///|
/// Compatibility wrapper for `PdfDocument::apply_redactions`.
pub fn pdf_apply_redactions(
document : PdfDocument,
page_numbers : ArrayView[Int],
) -> Unit {
document.apply_redactions(page_numbers)
}
///|
/// Compatibility wrapper matching cpdfredact's `apply`.
pub fn pdf_apply(document : PdfDocument, page_numbers : ArrayView[Int]) -> Unit {
document.apply_redactions(page_numbers)
}
///|
/// Apply annotations of `annotation_type` as if they were redactions.
///
/// This mirrors the current cpdf stub, which intentionally performs no work.
pub fn PdfDocument::apply_redaction_type(
self : PdfDocument,
annotation_type : String,
page_numbers : ArrayView[Int],
) -> Unit {
ignore(self)
ignore(annotation_type)
ignore(page_numbers)
}
///|
/// Compatibility wrapper for `PdfDocument::apply_redaction_type`.
pub fn pdf_apply_redaction_type(
document : PdfDocument,
annotation_type : String,
page_numbers : ArrayView[Int],
) -> Unit {
document.apply_redaction_type(annotation_type, page_numbers)
}
///|
/// Compatibility wrapper matching cpdfredact's `apply_type`.
pub fn pdf_apply_type(
document : PdfDocument,
annotation_type : String,
page_numbers : ArrayView[Int],
) -> Unit {
document.apply_redaction_type(annotation_type, page_numbers)
}
///|
fn pdf_redact_annotation_box_ops(
rectangle : @geometry.PdfRectangle,
light : Bool,
) -> Array[@content.PdfContentOp] {
let stroke_colour : @content.PdfContentOp = if light {
Op_RG(1.0, 1.0, 0.5)
} else {
Op_RG(1.0, 1.0, 0.0)
}
[
stroke_colour,
Op_w(1.0),
Op_m(rectangle.min_x, rectangle.min_y),
Op_l(rectangle.min_x, rectangle.max_y),
Op_l(rectangle.max_x, rectangle.max_y),
Op_l(rectangle.max_x, rectangle.min_y),
Op_l(rectangle.min_x, rectangle.min_y),
Op_h,
Op_S,
]
}
///|
fn pdf_redact_annotation_page_box_ops(
annotations : ArrayView[@annotation.PdfAnnotation],
light : Bool,
) -> Array[@content.PdfContentOp] {
let output : Array[@content.PdfContentOp] = []
for annotation in annotations {
for op in pdf_redact_annotation_box_ops(annotation.rectangle, light) {
output.push(op)
}
}
output
}
///|
fn pdf_redact_content_box_colour_ops(
object : PdfContentObject,
light : Bool,
) -> Array[@content.PdfContentOp] {
match object {
PdfContentGlyphObject(_, _, _) => [Op_G(if light { 1.0 } else { 0.0 })]
PdfContentInlineImageObject =>
[if light { Op_RG(1.0, 0.5, 0.5) } else { Op_RG(1.0, 0.0, 0.0) }]
PdfContentImageObject(_) =>
[if light { Op_RG(0.5, 1.0, 0.5) } else { Op_RG(0.0, 1.0, 0.0) }]
PdfContentPathObject(_) =>
[if light { Op_RG(0.5, 0.5, 1.0) } else { Op_RG(0.0, 0.0, 1.0) }]
PdfContentShadingObject(_) =>
[if light { Op_RG(0.5, 1.0, 1.0) } else { Op_RG(0.0, 1.0, 1.0) }]
}
}
///|
fn pdf_redact_push_content_box_ops(
output : Array[@content.PdfContentOp],
entry : PdfContentJsonEntry,
light : Bool,
) -> Unit {
output.push(Op_q)
for op in pdf_redact_content_box_colour_ops(entry.object, light) {
output.push(op)
}
output.push(Op_w(0.5))
output.push(Op_m(entry.bounding_box.x0, entry.bounding_box.y0))
output.push(Op_l(entry.bounding_box.x1, entry.bounding_box.y1))
output.push(Op_l(entry.bounding_box.x2, entry.bounding_box.y2))
output.push(Op_l(entry.bounding_box.x3, entry.bounding_box.y3))
output.push(Op_h)
output.push(Op_S)
output.push(Op_Q)
}
///|
fn pdf_redact_content_entry_bounds(
entry : PdfContentJsonEntry,
) -> @geometry.PdfRectangle {
let bbox = entry.bounding_box
let min_x = double_min(
double_min(bbox.x0, bbox.x1),
double_min(bbox.x2, bbox.x3),
)
let max_x = double_max(
double_max(bbox.x0, bbox.x1),
double_max(bbox.x2, bbox.x3),
)
let min_y = double_min(
double_min(bbox.y0, bbox.y1),
double_min(bbox.y2, bbox.y3),
)
let max_y = double_max(
double_max(bbox.y0, bbox.y1),
double_max(bbox.y2, bbox.y3),
)
{ min_x, min_y, max_x, max_y, }
}
///|
fn pdf_redact_rectangles_overlap(
first : @geometry.PdfRectangle,
second : @geometry.PdfRectangle,
) -> Bool {
!(first.max_x < second.min_x ||
second.max_x < first.min_x ||
first.max_y < second.min_y ||
second.max_y < first.min_y)
}
///|
fn pdf_redact_content_box_matches_shape(
shape : @geometry.PdfRectangle,
entry : PdfContentJsonEntry,
) -> Bool {
let bounds = pdf_redact_content_entry_bounds(entry)
match entry.object {
PdfContentGlyphObject(_, _, _)
| PdfContentImageObject(_)
| PdfContentInlineImageObject =>
pdf_redact_rectangles_overlap(shape, bounds)
PdfContentPathObject(_) | PdfContentShadingObject(_) =>
bounds.min_x > shape.min_x &&
bounds.max_x < shape.max_x &&
bounds.min_y > shape.min_y &&
// Preserve cpdfredact.ml's source comparison, including its max-x check.
bounds.max_x < shape.max_y
}
}
///|
fn pdf_redact_content_box_selected(
shape : @geometry.PdfRectangle?,
entry : PdfContentJsonEntry,
) -> Bool {
match shape {
Some(shape) => pdf_redact_content_box_matches_shape(shape, entry)
None => true
}
}
///|
fn PdfDocument::pdf_redact_page_content_box_ops(
self : PdfDocument,
page : @page.PdfPage,
shape : @geometry.PdfRectangle?,
light : Bool,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
let output : Array[@content.PdfContentOp] = []
for entry in self.content_entries_of_page(page) {
if pdf_redact_content_box_selected(shape, entry) {
pdf_redact_push_content_box_ops(output, entry, light)
}
}
output
}
///|
/// Draw bounding boxes around annotations on selected one-based pages.
///
/// This ports the implemented annotation-overlay part of CamlPDF's redaction
/// helper. Each annotation rectangle is stroked in yellow, or light yellow when
/// `light` is set, and appended through the existing page-content rewrite path.
pub fn PdfDocument::show_annotation_bounding_boxes(
self : PdfDocument,
page_numbers : ArrayView[Int],
light? : Bool = false,
fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
let pages = self.pages_of_pagetree()
let selected_pages = @core.pdf_number_set(page_numbers)
let op_lists : Array[Array[@content.PdfContentOp]] = Array(
capacity=pages.length(),
)
for index in 0.. PdfDocument raise @core.PdfError {
document.show_annotation_bounding_boxes(page_numbers, light~, fast~)
}
///|
/// Draw bounding boxes around annotations and selected page-content objects.
pub fn PdfDocument::show_bounding_boxes(
self : PdfDocument,
page_numbers : ArrayView[Int],
shape? : @geometry.PdfRectangle? = None,
light? : Bool = false,
fast? : Bool = false,
) -> PdfDocument raise @core.PdfError {
let document = self.show_annotation_bounding_boxes(
page_numbers,
light~,
fast~,
)
let pages = document.pages_of_pagetree()
let selected_pages = @core.pdf_number_set(page_numbers)
let op_lists : Array[Array[@content.PdfContentOp]] = Array(
capacity=pages.length(),
)
for index in 0.. PdfDocument raise @core.PdfError {
document.show_bounding_boxes(page_numbers, shape~, light~, fast~)
}
///|
/// Compatibility wrapper matching cpdfredact's `show_bounding_boxes` order.
pub fn pdf_show_bounding_boxes_cpdf_order(
fast : Bool,
shape : @geometry.PdfRectangle?,
light : Bool,
document : PdfDocument,
page_numbers : ArrayView[Int],
) -> PdfDocument raise @core.PdfError {
document.show_bounding_boxes(page_numbers, shape~, light~, fast~)
}