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 -1
View File
@@ -39,6 +39,7 @@ const state = {
editArm: null, // 'text' | 'image' khi đang chờ click đặt; null = không
editPendingImage: null, // đường dẫn ảnh chờ đặt (Thêm ảnh)
editPendingPoint: null, // điểm PDF chờ mở ô sửa (đúp chuột từ viewer thường)
editFlatBase: null, // bản "mở gói" Form XObject — không tính là thay đổi
editColor: [0, 0, 0], // màu chữ áp khi sửa/thêm text
editTemps: [], // mọi file tạm đã materialize trong phiên sửa (để dọn)
secMode: false, // thanh Bảo mật (Phase 5) đang mở
@@ -2054,6 +2055,7 @@ function enterEditMode(pageIndex) {
state.editSel = null;
state.editArm = null;
state.editPendingImage = null;
state.editFlatBase = null;
$("annobar").classList.add("hidden");
$("editBar").classList.remove("hidden");
$("viewport").classList.add("hidden");
@@ -2125,6 +2127,25 @@ async function loadEditPage() {
invoke("render_page", { path: state.editBase, page: state.editPage, width: EDIT_STAGE_W }),
invoke("edit_list_objects", { path: state.editBase, page: state.editPage, password: null }),
]);
// Trang có object nằm TRONG Form XObject (file Canva/Illustrator gói cả
// trang vào form): PDFium không ghi lại được stream form → "mở gói" form
// ra cấp trang MỘT LẦN (hiển thị y hệt) rồi nạp lại — từ đó sửa như
// trang thường. Bản mở gói không tính là "có thay đổi" cho tới khi sửa.
if (objs.some((o) => o.nested)) {
try {
const flat = await invoke("edit_flatten_to_temp", {
input: state.editBase, page: state.editPage, password: null,
});
if (flat) {
state.editTemps.push(flat);
state.editBase = flat;
state.editFlatBase = flat;
return loadEditPage();
}
} catch (e) {
$("editHint").textContent = "Không mở gói được form: " + e;
}
}
$("editImg").src = url;
state.editObjects = objs;
buildEditOverlay();
@@ -2132,7 +2153,8 @@ async function loadEditPage() {
} catch (e) {
$("editHint").textContent = "Lỗi nạp trang sửa: " + e;
}
$("edSave").disabled = state.editBase === state.path; // chưa có thay đổi nào
// Chưa có thay đổi nào (bản mở gói form không tính).
$("edSave").disabled = state.editBase === state.path || state.editBase === state.editFlatBase;
updateEditUndoButtons();
// Đúp chuột từ viewer thường (kiểu Foxit): vào thẳng ô sửa tại điểm đã đúp.