PHẪU THUẬT stream form: sửa/xoá text TRONG Form XObject không cần mở gói
Build Windows portable / build (push) Canceled after 0s
Build Windows portable / build (push) Canceled after 0s
Iteration 4b — giải pháp triệt để cho file Canva (thay cổng chặn của 4a): module formsurgery (lopdf) xoá đúng op vẽ chữ (Tj/TJ/'/") của run bị thay trong stream form — mọi byte khác giữ NGUYÊN (không đụng generator lossy của PDFium); chữ mới vẽ ở CẤP TRANG như thường. - Map PDFium ↔ stream: text-child thứ k của form ↔ show-op thứ k; form con thứ n ↔ Do-trỏ-form thứ n. Mỗi mức có BẤT BIẾN kiểm đếm (số op == số object PDFium thấy) — lệch là từ chối, không đoán mò. '/" thay bằng T*/Tw+Tc+T* để giữ hiệu ứng vị trí cho chữ sau. - apply_edits: run nested trong ReflowText/Delete → phẫu thuật ở pha A2 rồi MỞ LẠI document (index cấp trang không đổi); tầng-0 reflow (token gốc) tắt cho anchor nested (token vô hiệu sau mở lại) → đi tầng nhúng bytes/ cùng họ. SetText/Transform nested vẫn từ chối rõ (UI chặn với hint). - CỔNG AN TOÀN mới: page_render_mismatch_masked — mọi thay đổi pixel phải nằm TRONG vùng khối sửa (band reflow + rect run xoá); lệch ngoài vùng >0.5% → huỷ output, giữ nguyên file gốc. - UI: bỏ auto-flatten (không cần nữa), sửa nested trong suốt qua đúp chuột; hint 1 dòng cố định + ellipsis + title (hết cảnh trang nhảy lên xuống do thanh công cụ co giãn); chặn-với-thông-điệp cho đổi thuộc tính/kéo khối trong form. - Test: fixture form thêm dòng 2 + path đỏ; 2 test phẫu thuật (delete/reflow giữ nguyên phần khác, form còn nguyên); SetText nested vẫn bị từ chối. Flatten + cổng cũ giữ nguyên engine-side (có test riêng). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1038,7 +1038,8 @@ fn build_form_xobject_pdf(path: &std::path::Path) {
|
||||
"BBox" => vec![0.into(), 0.into(), 612.into(), 792.into()],
|
||||
"Resources" => dictionary! { "Font" => dictionary! { "F1" => font_id } },
|
||||
},
|
||||
b"BT /F1 24 Tf 72 700 Td (Hello inside form) Tj ET".to_vec(),
|
||||
// 1 path đỏ + 2 dòng text — đủ để kiểm phẫu thuật giữ nguyên phần khác.
|
||||
b"q 0.9 0.2 0.2 rg 40 40 100 20 re f Q BT /F1 24 Tf 72 700 Td (Hello inside form) Tj ET BT /F1 18 Tf 72 650 Td (Second line stays) Tj ET".to_vec(),
|
||||
));
|
||||
let content_id = doc.add_object(Stream::new(dictionary! {}, b"q /Fx1 Do Q".to_vec()));
|
||||
let page_id = doc.add_object(dictionary! {
|
||||
@@ -1207,18 +1208,89 @@ fn form_xobject_delete_after_flatten_clears_text() {
|
||||
assert!(!text.contains("Hello inside form"), "text phải biến mất sau xoá: {text:?}");
|
||||
}
|
||||
|
||||
// Op nhắm thẳng vào object trong form (chưa flatten) phải bị TỪ CHỐI rõ ràng
|
||||
// (không âm thầm ghi ra bản không đổi).
|
||||
// SetText tại chỗ vào object trong form vẫn bị TỪ CHỐI rõ ràng (toolbar đổi
|
||||
// thuộc tính) — chỉ ReflowText/Delete đi đường phẫu thuật.
|
||||
#[test]
|
||||
fn nested_target_without_flatten_is_rejected() {
|
||||
fn nested_settext_is_rejected() {
|
||||
let pdf = pdfium();
|
||||
let input = form_fixture("ff_edit_formx_reject.pdf");
|
||||
let out = tmp("ff_edit_formx_reject_out.pdf");
|
||||
let idx = find_text_index(&pdf, &input, "Hello inside form");
|
||||
|
||||
let err = ff_engine::apply_edits(&pdf, &input, 0, &[EditOp::Delete { index: idx }], &out, None)
|
||||
.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}");
|
||||
let err = ff_engine::apply_edits(
|
||||
&pdf,
|
||||
&input,
|
||||
0,
|
||||
&[EditOp::SetText {
|
||||
index: idx,
|
||||
text: "abc".into(),
|
||||
font_size: None,
|
||||
color: None,
|
||||
font_family: None,
|
||||
bold: None,
|
||||
italic: None,
|
||||
}],
|
||||
&out,
|
||||
None,
|
||||
)
|
||||
.expect_err("SetText vào object trong form phải bị từ chối");
|
||||
assert!(format!("{err}").contains("mở gói"), "thông điệp phải rõ: {err}");
|
||||
}
|
||||
|
||||
// PHẪU THUẬT: xoá 1 dòng TRONG form không cần mở gói — dòng kia + path đỏ
|
||||
// giữ nguyên, form còn nguyên là form, cổng an toàn (trong apply_edits) pass.
|
||||
#[test]
|
||||
fn form_xobject_surgical_delete() {
|
||||
let pdf = pdfium();
|
||||
let input = form_fixture("ff_edit_formx_surg_del.pdf");
|
||||
let out = tmp("ff_edit_formx_surg_del_out.pdf");
|
||||
let idx = find_text_index(&pdf, &input, "Hello inside form");
|
||||
|
||||
ff_engine::apply_edits(&pdf, &input, 0, &[EditOp::Delete { index: idx }], &out, None)
|
||||
.expect("phẫu thuật xoá trong form");
|
||||
|
||||
let text = ff_engine::extract_text(&pdf, &out, 0, None).expect("extract");
|
||||
assert!(!text.contains("Hello inside form"), "dòng xoá phải biến mất: {text:?}");
|
||||
assert!(text.contains("Second line stays"), "dòng còn lại phải nguyên: {text:?}");
|
||||
let objs = ff_engine::list_objects(&pdf, &out, 0, None).expect("list out");
|
||||
assert!(
|
||||
objs.iter().any(|o| o.kind == ObjectKind::XObjectForm),
|
||||
"form phải CÒN NGUYÊN (không mở gói): {objs:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// PHẪU THUẬT: sửa cả đoạn TRONG form (reflow) — chữ cũ mất, chữ mới hiện,
|
||||
// phần khác của form giữ nguyên.
|
||||
#[test]
|
||||
fn form_xobject_surgical_reflow() {
|
||||
let pdf = pdfium();
|
||||
let input = form_fixture("ff_edit_formx_surg_ref.pdf");
|
||||
let out = tmp("ff_edit_formx_surg_ref_out.pdf");
|
||||
let idx = find_text_index(&pdf, &input, "Hello inside form");
|
||||
|
||||
ff_engine::apply_edits(
|
||||
&pdf,
|
||||
&input,
|
||||
0,
|
||||
&[EditOp::ReflowText {
|
||||
indices: vec![idx],
|
||||
text: "Sửa trong form bằng phẫu thuật stream".into(),
|
||||
}],
|
||||
&out,
|
||||
None,
|
||||
)
|
||||
.expect("phẫu thuật reflow trong form");
|
||||
|
||||
let text = ff_engine::extract_text(&pdf, &out, 0, None).expect("extract");
|
||||
let norm = text.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
assert!(norm.contains("Sửa trong form"), "chữ mới phải có mặt: {text:?}");
|
||||
assert!(!norm.contains("Hello inside form"), "chữ cũ phải biến mất: {text:?}");
|
||||
assert!(norm.contains("Second line stays"), "dòng khác phải nguyên: {text:?}");
|
||||
let objs = ff_engine::list_objects(&pdf, &out, 0, None).expect("list out");
|
||||
assert!(
|
||||
objs.iter().any(|o| o.kind == ObjectKind::XObjectForm),
|
||||
"form phải còn nguyên: {objs:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// Cổng an toàn: mở gói fixture đơn giản phải giữ nguyên hiển thị (lệch ~0%).
|
||||
|
||||
Reference in New Issue
Block a user