Form XObject vòng 4 — kiến trúc cuối: MỞ GÓI form ra cấp trang trước khi sửa
Build Windows portable / build (push) Canceled after 0s

Kết luận sau 3 vòng CI + đọc nguyên văn source PDFium: PDFium hiện KHÔNG ghi
lại được stream của Form XObject — CPDF_PageContentManager chỉ biết key
/Contents (trang), form là stream tự thân nên nội dung regenerate rơi vào
key /Contents vô nghĩa trên dict form, stream thật giữ nguyên. Mọi cách sửa
"trong form" đều không thể lưu.

- Engine mới: flatten_form_xobjects — rút từng con khỏi form
  (FPDFFormObj_RemoveObject), nhân ma trận form vào con, chèn lại vào TRANG,
  xoá form rỗng; lặp <=4 vòng cho form lồng form. Hiển thị y hệt, mọi object
  thành cấp trang, sửa bằng đường page-level đã test kỹ.
- Tauri: command edit_flatten_to_temp; UI loadEditPage thấy object nested →
  tự mở gói một lần rồi nạp lại (bản mở gói không tính là "có thay đổi").
- apply_edits: gỡ toàn bộ đường sửa-trong-form (NestedRecreate, rút con,
  đánh thức trang — đã chứng minh không thể lưu); op nhắm vào object trong
  form khi chưa flatten → lỗi rõ "trang chưa được mở gói" (có test).
- Test viết lại theo flow flatten: 6 test (liệt kê nested, flatten giữ vị
  trí/nội dung, settext/reflow/delete sau flatten, từ chối op nested).
Docs 12-phase4-summary.md cập nhật kết luận + giới hạn (mất clip/ExtGState
mức form khi flatten; cấu trúc file đổi khi lưu).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 02:26:30 +07:00
co-authored by Claude Fable 5
parent 1b3cf15664
commit d4b09e0ed8
6 changed files with 306 additions and 224 deletions
+23
View File
@@ -1302,6 +1302,28 @@ fn pick_save_pem(app: tauri::AppHandle) -> Option<String> {
.map(|fp| fp.to_string())
}
/// "Mở gói" mọi Form XObject của trang ra cấp trang (file Canva/Illustrator
/// gói cả trang vào form — PDFium không ghi lại được stream form nên phải
/// flatten trước khi sửa). Trả None nếu trang không có form (không tạo file).
#[tauri::command]
fn edit_flatten_to_temp(input: String, page: u16, password: Option<String>) -> Result<Option<String>, String> {
let pdfium = pdfium()?;
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let out = std::env::temp_dir().join(format!("ff_edit_flat_{}_{}.pdf", std::process::id(), nanos));
let n = ff_engine::flatten_form_xobjects(
&pdfium,
std::path::Path::new(&input),
page,
&out,
password.as_deref(),
)
.map_err(|e| e.to_string())?;
Ok(if n > 0 { Some(out.to_string_lossy().into_owned()) } else { None })
}
/// Dọn các file làm việc tạm của chế độ sửa (undo stack). Chỉ xoá file có tên
/// `ff_edit_*` nằm đúng trong thư mục temp — không bao giờ đụng file người dùng.
#[tauri::command]
@@ -1362,6 +1384,7 @@ fn main() {
edit_apply_to_temp,
edit_preview,
edit_cleanup,
edit_flatten_to_temp,
redact_apply,
security_encrypt,
security_decrypt,