Cong an toan cho mo goi form: so render truoc/sau (page_render_mismatch), lech >0.5% pixel -> huy + chan sua voi thong diep ro. E2E tren CV Canva that phat hien generator PDFium lossy (font subset trung ten bi gop lam mat glyph, anh inline bi bo, z-order xao tron) - khong ship flatten mu quang; ke hoach iteration sau (phau thuat stream bang lopdf) ghi trong docs.
Build Windows portable / build (push) Canceled after 0s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 03:22:41 +07:00
co-authored by Claude Fable 5
parent 93d49a67df
commit 5c53c1f987
6 changed files with 110 additions and 10 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ pub use form::{
pub use ocr::{find_tesseract, ocr_add_text_layer, ocr_page_words, OcrWord};
pub use redact::redact_areas;
pub use sign::{generate_self_signed_id, sign_pdf, verify_signatures, SignatureCheck};
pub use render::{bind_pdfium, page_count, render_page_png, PageImage};
pub use render::{bind_pdfium, page_count, page_render_mismatch, render_page_png, PageImage};
pub use text::{extract_text, page_char_boxes, search, CharBox, Rect, SearchHit};
pub use watermark::{add_header_footer, add_watermark, Anchor, HeaderFooterSpec, WatermarkSpec};
+34
View File
@@ -93,6 +93,40 @@ pub fn render_page(
})
}
/// Tỉ lệ pixel KHÁC NHAU (0.0–1.0) giữa cùng 1 trang của 2 file, render cùng
/// bề rộng. Dùng làm CỔNG AN TOÀN cho các phép biến đổi phải giữ nguyên hiển
/// thị (mở gói Form XObject…): lệch quá ngưỡng → từ chối biến đổi thay vì âm
/// thầm làm hỏng file. Sai khác ≤12/255 mỗi kênh bỏ qua (nhiễu anti-alias).
pub fn page_render_mismatch(
pdfium: &Pdfium,
a: &Path,
b: &Path,
page_index: u16,
target_width: u32,
) -> Result<f32, EngineError> {
let ia = render_page(pdfium, a, page_index, target_width, None)?.image.to_rgba8();
let ib = render_page(pdfium, b, page_index, target_width, None)?.image.to_rgba8();
if ia.dimensions() != ib.dimensions() {
return Ok(1.0);
}
let (w, h) = ia.dimensions();
let total = (w as u64 * h as u64).max(1);
let mut diff = 0u64;
for (pa, pb) in ia.pixels().zip(ib.pixels()) {
let d = pa
.0
.iter()
.zip(pb.0.iter())
.map(|(x, y)| (*x as i16 - *y as i16).unsigned_abs())
.max()
.unwrap_or(0);
if d > 12 {
diff += 1;
}
}
Ok(diff as f32 / total as f32)
}
/// Render một trang và ghi ra file PNG.
pub fn render_page_png(
pdfium: &Pdfium,
+11
View File
@@ -1220,3 +1220,14 @@ fn nested_target_without_flatten_is_rejected() {
.expect_err("op vào object trong form phải bị từ chối");
assert!(format!("{err}").contains("mở gói"), "thông điệp phải nhắc flatten: {err}");
}
// Cổng an toàn: mở gói fixture đơn giản phải giữ nguyên hiển thị (lệch ~0%).
#[test]
fn flatten_is_visually_lossless_on_simple_form() {
let pdf = pdfium();
let input = form_fixture("ff_edit_formx_gate.pdf");
let out = tmp("ff_edit_formx_gate_out.pdf");
ff_engine::flatten_form_xobjects(&pdf, &input, 0, &out, None).expect("flatten");
let mm = ff_engine::page_render_mismatch(&pdf, &input, &out, 0, 500).expect("mismatch");
assert!(mm < 0.005, "mở gói phải giữ nguyên hiển thị, lệch {:.3}%", mm * 100.0);
}