Edit mode: guard editLoadSeq chong race nap trang (chuyen trang/zoom nhanh - luot cham khong duoc de luot moi) + docs vong 5
Build Windows portable / build (push) Canceled after 0s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 09:21:36 +07:00
co-authored by Claude Fable 5
parent f0b4de9af1
commit 1362efef63
2 changed files with 29 additions and 0 deletions
+7
View File
@@ -460,10 +460,12 @@ function scheduleEditSharpRender() {
clearTimeout(editSharpTimer); clearTimeout(editSharpTimer);
editSharpTimer = setTimeout(async () => { editSharpTimer = setTimeout(async () => {
try { try {
const seq = editLoadSeq;
const p = state.pages[state.editPage]; const p = state.pages[state.editPage];
const url = await invoke("render_page", { const url = await invoke("render_page", {
path: state.editBase, page: state.editPage, width: editStageWidth(p), path: state.editBase, page: state.editPage, width: editStageWidth(p),
}); });
if (seq !== editLoadSeq) return; // trang/file đã đổi trong lúc render
$("editImg").src = url; $("editImg").src = url;
} catch (_) { /* giữ ảnh cũ (đã co giãn CSS) */ } } catch (_) { /* giữ ảnh cũ (đã co giãn CSS) */ }
}, 220); }, 220);
@@ -2185,7 +2187,11 @@ async function onViewerDblClick(e) {
} }
// Đọc lại object + render ảnh trang hiện tại từ editBase, dựng overlay box. // Đọc lại object + render ảnh trang hiện tại từ editBase, dựng overlay box.
// Guard số thứ tự: chuyển trang/zoom nhanh tạo nhiều lượt nạp song song —
// chỉ lượt MỚI NHẤT được ghi vào DOM/state (lượt chậm về sau bị bỏ).
let editLoadSeq = 0;
async function loadEditPage() { async function loadEditPage() {
const mySeq = ++editLoadSeq;
const p = state.pages[state.editPage]; const p = state.pages[state.editPage];
const stageW = editStageWidth(p); const stageW = editStageWidth(p);
state.editScale = stageW / p.widthPt; state.editScale = stageW / p.widthPt;
@@ -2194,6 +2200,7 @@ async function loadEditPage() {
invoke("render_page", { path: state.editBase, page: state.editPage, width: stageW }), invoke("render_page", { path: state.editBase, page: state.editPage, width: stageW }),
invoke("edit_list_objects", { path: state.editBase, page: state.editPage, password: null }), invoke("edit_list_objects", { path: state.editBase, page: state.editPage, password: null }),
]); ]);
if (mySeq !== editLoadSeq) return; // đã có lượt nạp mới hơn — bỏ lượt này
// Object nằm TRONG Form XObject (file Canva/Illustrator): sửa/xoá text // Object nằm TRONG Form XObject (file Canva/Illustrator): sửa/xoá text
// được xử lý bằng PHẪU THUẬT stream ở engine (trong suốt với UI) — không // được xử lý bằng PHẪU THUẬT stream ở engine (trong suốt với UI) — không
// cần mở gói trang. // cần mở gói trang.
+22
View File
@@ -77,6 +77,28 @@ Máy dev không có Rust nên **CI là nơi duy nhất chạy test**: workflow t
bước `cargo test -p ff-engine` TRƯỚC khi build release (fail test = fail bước `cargo test -p ff-engine` TRƯỚC khi build release (fail test = fail
build, không publish bản hỏng); cache thêm `target/` gốc workspace. build, không publish bản hỏng); cache thêm `target/` gốc workspace.
### Vòng 5 — zoom/chuyển trang trong chế độ sửa + dòng lẫn nhiều style
Ba vấn đề user báo (kiểm chứng E2E build 29 trên file dịch 3 trang):
- **Zoom**: stage sửa hết cứng 820px — `editZoom` riêng (kế thừa zoom viewer),
Ctrl+wheel/nút ±/dropdown %/Vừa rộng/Vừa trang đều chạy; co giãn CSS ngay,
render nét sau 220ms; `margin:auto` fix flex-center overflow. Kiểm: 794→1191px.
- **Chuyển trang**: ▲▼/ô nhập trang nhận biết edit mode (`switchEditPage` —
giữ undo/thay đổi trang khác vì chung editBase); guard `editLoadSeq` cho
loadEditPage/sharp-render: lượt nạp chậm không đè lượt mới (race khi
chuyển trang/zoom nhanh). Kiểm: Trang 2 · 114 đối tượng.
- **Dòng lẫn style (thường + mono-đậm…)**: ô sửa dựng SPAN theo nhóm run
cùng style (styleKeyOf/styleGroupsOfLine) — WYSIWYG từng phần; commit đọc
đoạn-style (`readRich`, text node trần mượn span kề), ≥2 style gửi
`richLines`. Engine: `ReflowText.rich` — resolve thang giữ-font RIÊNG cho
từng style-run, token hoá theo từ (giữ glue giữa đoạn dính liền), bẻ dòng
chung, vẽ mỗi cụm cùng-style 1 object đúng font/cỡ/màu + tự kiểm/fallback
từng từ. Tầng-0 token gốc tắt khi KHỐI có run nested (token vô hiệu sau
mở lại document hậu phẫu thuật — trước chỉ xét anchor). Kiểm trên dòng
"Hiển thị `vmDetails.vm.windowsInitialPassword`.": ô sửa 3 span đúng
style; sau commit mono giữ NotoSansMono-Bold, "." giữ NotoSans-Regular
(token gốc), phần sửa sang Arial hợp lệ (subset gốc thiếu glyph Ể hoa).
Test engine: fixture Helvetica + Courier-Bold cùng dòng.
### CỔNG AN TOÀN + giới hạn ghi nhận (iteration 4 v1) ### CỔNG AN TOÀN + giới hạn ghi nhận (iteration 4 v1)
Kiểm chứng E2E trên file Canva THẬT (CV, 1149 object sau mở gói) phát hiện: Kiểm chứng E2E trên file Canva THẬT (CV, 1149 object sau mở gói) phát hiện:
**generator của PDFium ghi lại cả trang là LOSSY với file phức tạp**: **generator của PDFium ghi lại cả trang là LOSSY với file phức tạp**: