Bug thu 2 cua "luu xong de len text khac" (bug 1 - de NGANG trong dong - da
fix bang mep muc that): vong no doc 8pt (sinh ra de bat dau tieng Viet ve
bang op roi) nuot ca run cao co chu cua DONG KE TREN khi gian dong sat
(~13-14pt): tam muc run dong tren lot vao bbox+8pt -> ca cau bi xoa,
first_baseline troi len 1 dong -> khoi ve lai de len vi tri dong tren, sot
manh mono dau dong (tam cao hon nguong chut).
Tai hien tren file that cua user (trang 2, khoi "Cac thuoc tinh HttpOnly..."
nuot dong "Path=/ khien refresh token..." ngay tren). Ban dau ket luan nham
la "file nhiem artifact" - DINH CHINH trong docs/12: file sach, Path=/ la
dau bullet hop le.
Fix: vung no 8pt chi duoc nuot op NHO (cao < 0.6 x median cao run trong
khoi - manh dau); op cao co chu phai co tam trong bbox LOI. Test hoi quy
reflow_tight_leading_does_not_swallow_line_above (gian dong 13pt, khoi 2
dong duoi, dong tren phai song sot + dong moi khong cham muc dong tren).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slot trang active cao theo anh that (CSS :has -> height auto), nhung luc vua
vao che do sua anh CHUA load nen slot cao ~0 -> scrollIntoView ban dau lech
ca cot va listener cuon (debounce 200ms) tu chuyen nham trang active (vao
trang 2 nhay sang trang 3). sizeEditSlot dat them min-height = chieu cao
uoc tinh nen slot khong bao gio sap ve 0.
Kem ket qua kiem chung build 31 tren app that (docs/12): dong lan style sau
commit tang dan tuyet doi (worst overlap 0pt) - fix mep muc dat; mảnh
"Path=/" con de la artifact NHIEM SAN trong file test cua user tu lan luu
bang build loi cu (reflow bao ton nhu marker) - khong phai bug moi.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Engine (edit.rs): sau khi ve tung manh rich, tien con tro theo MAX(do hmtx,
MEP MUC THAT tu PDFium bounds) - subset font ttf-parser khong parse duoc roi
ve fallback 0.5em/ky tu uoc luong thieu ("HttpOnly" do 36pt vs muc that
42.6pt) lam manh sau de len manh truoc ("auth-storagetu"). Ca duong ve ca
manh lan duong be theo tung tu.
- UI nhieu trang: #editPagesCol chua 1 .edit-slot/trang; trang active giu
#editPageWrap (img+overlay cu, chi DI CHUYEN giua cac slot), trang khac
render read-only luoi (IntersectionObserver, stamp editBase|zoom tu
invalidate sau op/undo/zoom). Dup vao trang read-only = chuyen active + mo
o sua ngay tai diem dup (tai dung editPendingPoint). Cuon: so trang bam
theo trang giua khung nhin, tu doi active khi khong co o sua mo. Zoom co
gian moi slot ngay, render net sau.
- Nut "Huy thay doi" canh nut Luu: dong o sua, xoa file tam, editBase ve file
goc, xoa undo/redo, moi trang tu render lai noi dung goc.
- E2E CDP tren app that (tiem patch vao build 29): cot 3 trang, dup trang 3
read-only mo dung dong, Huy tra ve goc (text thu "KIEM" bien mat), zoom
130% ca 3 slot. Docs/12 vong 6.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
CI vòng 1 bắt đúng lỗi thiết kế: FPDFText_SetText trên con trong form KHÔNG
đánh dấu dirty_streams của form (chỉ RemovePageObject/InsertPageObjectAtIndex
mới làm — xem cpdf_pageobjectholder.cpp) nên mọi sửa in-place trong form
không bao giờ được ghi ra file. Test canary fail chính xác ở điểm này.
- Đổi feature pdfium-render pdfium_latest(7543) -> pdfium_7350 (cả ff-engine
lẫn app): mở remove_object cho Form XObject qua blanket impl
PdfPageObjectsCommon (wrapper 0.8.37 sót gate 7543 dù binding có đủ);
API 7350 là subset của DLL bblanchon mới, đã quét 0 hàm 7543-only.
- Mọi sửa/xoá con trong form = RÚT THẬT khỏi form (FPDFFormObj_RemoveObject,
thứ tự path sâu trước + cùng cha con lớn trước) -> form dirty -> ProcessForm
regenerate stream khi lưu. SetText con trong form = SetTextMode mới
NestedRecreate: rút khỏi form + tạo lại ở cấp trang giữ font gốc qua token
(probe như tầng 0 reflow; thiếu glyph -> nhúng lại bytes/cùng họ), matrix
đã compose.
- CI test thêm --test-threads=1: PDFium serialize qua 1 mutex toàn cục,
1 test panic khi giữ lock poison mutex làm 17 test song song chết oan.
Docs cập nhật theo (12-phase4-summary.md Iteration 4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
File Canva gói toàn bộ nội dung trang vào 1 Form XObject nên chế độ sửa
không thấy gì để sửa. Engine giờ liệt kê PHẲNG đệ quy vào form
(FPDFFormObj_CountObjects/GetObject qua pdfium-render), mỗi mục có path
[i,j,...] + tích ma trận form để quy toạ độ/cỡ chữ về trang; index của
ObjectInfo/EditOp = vị trí trong danh sách phẳng (trang không form thì
trùng index trang — tương thích ngược, UI không đổi cách gửi op).
- SetText in-place/substitute + ReflowText + Delete hoạt động với run trong
form; xoá trong form = set_text chuỗi rỗng (FPDFFormObj_RemoveObject chưa
mở ở wrapper cho pdfium_7543), object mới vẽ ở cấp trang với matrix đã
compose.
- Đã xác minh trước: PDFium regenerate stream form khi con bị sửa
(ProcessForm / HasDirtyStreams) — canary test round-trip kèm theo.
- ObjectInfo thêm nested/expanded; UI bỏ khung form phủ trang + chặn kéo
run trong form (Transform chưa hỗ trợ).
- 4 test integration mới với fixture Form XObject tự dựng bằng lopdf
(dev-dependency mới); CI thêm bước cargo test -p ff-engine trước build
(máy dev không có Rust — CI là nơi duy nhất chạy test), cache thêm target/.
Chi tiết: docs/12-phase4-summary.md (Iteration 4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Debug trên app thật (WebView2 remote debugging + CDP) với file thật tìm ra
nguyên nhân gốc chuỗi lỗi font: DTO Tauri serialize camelCase (fontBold,
fontFamily, fontSize) nhưng code edit-mode đọc snake_case (font_bold...) →
mọi thuộc tính font trong UI luôn undefined (không đậm, sai family, cỡ mặc
định 12). Đổi hết sang camelCase nhất quán với app.
- Engine: fontmatch::style_from_font_bytes parse sfnt thủ công — OS/2
usWeightClass/fsSelection, fallback head.macStyle (LibreOffice subset vứt
OS/2 nhưng head luôn còn; kiểm bằng qpdf trích FontFile2 file thật). 2
unit test sfnt tự dựng.
- Kéo-thả .pdf từ Explorer vào cửa sổ → mở file (tauri://drag-drop).
- Ẩn khung dòng overlay khi ô sửa mở (nền trong suốt lộ khung gây nhiễu).
- Ignore /*.pdf ở root (file test thật của user không được commit).
Kiểm chứng E2E không cần rebuild: inject hàm mới vào app build-12 đang chạy
qua CDP, mở ô sửa trên file thật + chụp màn hình — tiêu đề 2 dòng bold đúng
cỡ/vị trí nền trong suốt; khối bullet 8 dòng sạch PUA, bullet gốc nguyên
trên trang. Test Node trên dữ liệu run thật từ engine: 6/6 PASS.
Chi tiết: docs/12-phase4-summary.md (Vá vòng 5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 lỗi user báo khi test build 11 trên file thật:
- Gom đoạn quá tham (tiêu đề + code + thân bài + nhãn đỏ 1 khối): đối chiếu
Foxit (mỗi đoạn = 1 text block riêng) — paragraphLines chỉ gom dòng kề
CÙNG format (family + đậm/nghiêng + màu, cỡ chênh <=1.25x).
- Bullet hiện ô vuông trong ô sửa: bullet là run font Symbol mã PUA, DOM
không render được — isMarkerRun/stripLineMarkers loại marker khỏi ô
sửa/commit, bullet giữ nguyên trên trang (chuẩn Foxit); đúp trúng bullet
vẫn mở khối chữ.
- Ô sửa nền trắng: render ngầm bản trang đã ẩn run đang sửa rồi chuyển nền
trong suốt — thấy nền gốc (dải màu/bullet) dưới chữ đang gõ; huỷ trả ảnh
gốc; commit vẫn tính trên editBase.
- Mất in đậm: engine thêm fallback đọc OS/2 usWeightClass + cờ italic từ
bytes font nhúng (file Word xuất không khai /FontWeight, tên không có
Bold); list_objects cache kiểu chữ theo tên font.
Test Node trích hàm thật từ main.js: 11 case tách khối/bullet đều PASS.
Chi tiết: docs/12-phase4-summary.md (Vá vòng 4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ba lỗi làm chữ trong ô sửa khác hẳn chữ gốc:
- Hồi quy: line-height từng dòng tính bằng khoảng hở giữa 2 bbox (~0/âm,
kẹp 8px) thay vì khoảng cách baseline → các dòng dồn cục lên đầu ô.
Fix: advance = hiệu mép dưới 2 bbox liền kề.
- Sai font: family PostScript CamelCase (TimesNewRoman, SegoeUI) không
khớp tên font Windows → CSS rơi về serif/sans mặc định. Fix: cssFontStack
tách CamelCase + bảng alias (Helvetica→Arial...).
- Sai cỡ với file engine báo cỡ lệch: thêm fitEditLinesToPdf — vòng ĐO-HIỆU
CHỈNH sau khi mount: đo chiều cao mực thật của đúng chuỗi đó (canvas
actualBoundingBox ở cỡ tham chiếu 100px, né hinting) so với bbox dòng PDF
rồi tự sửa cỡ (dead-band 12% giữ ổn định file chuẩn); đo vị trí render
thật (Range + font/ink ascent) rồi bù margin/indent từng dòng cho trùng
mép mực — khối căn giữa cũng bù ngang qua text-indent.
Kèm: giữ thụt lề từng dòng; đúp từ viewer thường đặt con trỏ đúng điểm đúp
(chờ img.decode trước khi đo). Kiểm chứng bằng harness canvas + Edge
headless: 3 kịch bản (cỡ báo sai 1/2, cỡ đúng, căn giữa) đều trùng khít
≤1.3px. Chi tiết: docs/12-phase4-summary.md (Vá vòng 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chẩn đoán □: PDFium FPDFText_LoadFont nạp LẠI font subset rồi SetText map
U+0020 ra .notdef (hộp vuông) dù cmap font CÓ glyph space (đã probe cả 2
subtable) — lỗi PDFium với font nạp động, không phải do font.
Engine (edit.rs):
- Tầng 0 mới của thang font reflow: DÙNG LẠI chính font object gốc trong
PDF (PdfFontToken của run neo, không nạp lại) — glyph + charcode map gốc
xử lý dấu cách chuẩn, file không phình; chỉ chọn sau khi PROBE ghi/đọc-lại
toàn bộ text mới thành công (tạo object nháp → so text → gỡ + mem::forget)
- Ghi từng dòng xong ĐỌC LẠI NGAY tự kiểm; PDFium vẫn vứt ký tự → gỡ và ghi
lại TỪNG TỪ tự đặt vị trí bằng advance hmtx (không bao giờ ghi glyph dấu
cách) — lưới an toàn cuối cho font lạ
- Dòng cứng (\n từ ô sửa) NỞ tới +35% bề rộng khối (chặn mép trang) trước
khi bẻ lại — hộp text tự nở như Foxit; khối căn giữa nở đều quanh tâm
- line_styles theo baseline gốc: dòng cứng thứ i giữ ĐÚNG CỠ dòng gốc thứ i
(tiêu đề 22.5/20pt không bị ép cả khối về 1 cỡ)
UI (main.js + styles.css):
- Ô sửa cả đoạn đổi textarea → contenteditable plaintext-only: MỖI DÒNG GỐC
= 1 div đúng font/cỡ/màu/đậm-nghiêng CỦA DÒNG ĐÓ, khối căn giữa hiện căn
giữa (nở đều 2 phía quanh tâm), khung bù lệch dọc line-box → chữ trong ô
TRÙNG vị trí chữ gốc khi mở; con trỏ đặt đúng điểm đúp (caretRangeFromPoint);
Enter xuống dòng, Ctrl+Enter/blur áp dụng, Esc huỷ, dán luôn chữ thuần
Kiểm chứng file thật: "HỢP"→"HỢPP" giữ đúng 2 dòng căn giữa cỡ 22.5/20,
font BAAAAA+TimesNewRomanPS-BoldMT gốc, dấu cách nguyên vẹn, render khớp
bản gốc. Test: +2 hồi quy (giữ cỡ từng dòng; dòng nở không bẻ) → edit 21/21,
các suite annot/organize/redact/form xanh. Docs 12 (mục vá vòng 2), 13
(E39–E42).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
Chẩn đoán trên file thật (Word xuất): tiêu đề 2 dòng bị cắt thành 41 run
per-word/per-char xen run rỗng kích thước 0, cỡ 22.5pt + 20pt trộn nhau,
bbox chữ có dấu (Ạ, Ợ) tụt thấp hơn hẳn chữ thường.
UI (main.js):
- clusterTextLines: gom run thành DÒNG theo GIAO DỌC ≥50% chiều cao nhỏ
(miễn nhiễm dấu tiếng Việt) + tách cột theo hở ngang; gắn run rỗng vào dòng
chứa nó. Overlay vẽ 1 khung/DÒNG (file thật: 231 ô vụn → 18 khung);
chọn/kéo/xoá/đổi thuộc tính tác động CẢ DÒNG (batch ops, editSelRuns)
- paragraphLines mới trên nền cluster: dòng kề nhau (hở ≤1.3× chiều cao),
giao ngang ≥30%, cỡ chữ ≤1.8× (tiêu đề 22.5/20pt vẫn 1 đoạn), khoá nhịp ±35%
- Đúp LUÔN mở ô sửa CẢ ĐOẠN, mỗi dòng gốc = 1 dòng textarea (giữ và sửa được
cả xuống dòng); đúp trúng KHE giữa các từ vẫn mở (hit-test theo điểm trên
overlay); click/chọn chữ trong ô sửa di chuyển con trỏ, KHÔNG thoát edit
(stopPropagation + guard .edit-inline ở onEditStageClick); ô che kín chữ cũ,
line-height đúng nhịp dòng, scrollIntoView
Engine (edit.rs ReflowText):
- TỰ NỞ danh sách run: mọi text object có tâm trong union bbox (+2pt) của các
run được đưa đều bị thay — không bao giờ sót run rỗng/lệch bbox → hết cảnh
chữ mới chồng chữ cũ
- Phát hiện khối CĂN GIỮA (tâm mọi dòng ≈ tâm khối, có dòng thụt trái) →
dòng mới đặt x = tâm khối − w/2 (đo hmtx) — tiêu đề căn giữa giữ căn giữa
Kiểm chứng với file thật (cục bộ): 39 run tiêu đề xoá sạch 0 mảnh sót, 2 dòng
mới căn giữa lệch tâm trang <5pt, extract sạch chữ cũ; grouping đúng khi đúp
mọi từ của cả 2 dòng lẫn header 8pt per-char.
Test: 2 test hồi quy mới (nở indices bắt run giữa bị sót; giữ căn giữa đo
bằng fixture tự căn) → edit 19/19, toàn bộ engine 75/75 ngoài qpdf-fixture.
Docs: 12 (mục vá theo phản hồi), 13 (E34–E38).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
Engine:
- EditOp::ReflowText { indices, text }: engine tự suy hình học từ các run
(lề trái/bề rộng từ bounds, baseline từ matrix.f gom cụm ±1pt, khoảng cách
dòng = median hiệu baseline); bẻ dòng greedy đo bằng hmtx của font thật
(fontmatch::wrap_lines + char_advance, ttf-parser); \n = ngắt dòng cứng,
từ quá dài cắt theo ký tự, dòng rỗng giữ nhịp baseline
- Giữ font theo thang 4 mức nhất quán với iteration 2: (1) font nhúng parse
được + phủ đủ glyph → nhúng lại chính bytes đó; (2) family base-14 + ASCII
→ font chuẩn PDF qua FPDFText_LoadStandardFont (BaseFont giữ tên chuẩn,
không nhúng, file không phình; đo bằng font metric-compatible); (3) font
hệ thống cùng họ; (4) fallback. FontLoad enum Bytes/Builtin cho pha nạp font
- Dòng mới giữ phần tuyến tính matrix của run neo (scale/nghiêng), Tf =
unscaled run neo → cỡ hiển thị không đổi
UI + Tauri:
- paragraphLines: gom đoạn nhiều dòng (baseline cách đều ±25%, cỡ chữ tương
đồng, giao ngang ≥30%); double-click đoạn ≥2 dòng → textarea sửa CẢ ĐOẠN
WYSIWYG (đúng font/cỡ/màu/kiểu + line-height đúng nhịp baseline)
- Enter = xuống dòng cứng · Ctrl+Enter/bấm ra ngoài = áp dụng · Esc = huỷ;
1 commit = 1 op reflowText = 1 nấc undo
- DTO reflowText (indices) trong Tauri
Test: 3 integration mới (wrap giữ font nhúng + tiếng Việt + baseline đều;
hard break; base-14 giữ BaseFont Helvetica chuẩn) + 3 unit wrap_lines.
Toàn bộ suite engine ngoài qpdf: 52/52 xanh.
Docs: 12 (Iteration 3 + giới hạn v1), 13 (E26–E30), 14 (mục 3.1 → đã giải,
cập nhật thứ tự việc tiếp theo), README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
Engine:
- fontmatch.rs mới: làm sạch tên PostScript/subset, kiểm coverage cmap bằng
ttf-parser, match font hệ thống CÙNG HỌ (bảng Windows/macOS, Liberation
metric-compatible + fc-match trên Linux, alias base-14 Helvetica→Arial...)
- edit.rs: SetText quyết định 3 tầng — (1) sửa TẠI CHỖ giữ nguyên font gốc
(charset-subset / base-14+ASCII / cmap phủ đủ, gồm font nhúng + tiếng Việt);
(2) thiếu glyph → thay font cùng họ đúng đậm/nghiêng; (3) mới đến fallback.
Đổi cỡ chữ = scale matrix neo baseline, không đụng font; fix bug phóng đại
kép khi text có matrix scale. ObjectInfo thêm font_family/bold/italic/embedded.
SetText/AddText nhận font_family + bold/italic dạng Option (None = giữ nguyên)
- annot.rs: fallback Linux có đủ biến thể đậm/nghiêng (DejaVu/Liberation)
UI (chuẩn trải nghiệm Foxit):
- Double-click sửa CẢ DÒNG (gộp run cùng baseline), commit 1 batch = 1 undo
- Ô sửa WYSIWYG: đúng font/cỡ/màu/đậm-nghiêng của run gốc khi gõ
- Toolbar Format: dropdown font "(giữ nguyên: X)", nút B/I theo kiểu thật,
đổi cỡ/màu/kiểu gửi đúng field đó (null = giữ nguyên → không đổi font)
- Kéo di chuyển/resize có phản hồi trực tiếp (live), resize neo góc trên-trái
- Dọn file tạm ff_edit_* khi thoát/lưu (command edit_cleanup)
Test: 14 test edit (7 mới: giữ font ASCII/base-14, giữ font NHÚNG tiếng Việt,
match cùng họ, cỡ chữ neo + hồi quy matrix, bold override, gộp dòng) + 3 unit
fontmatch. Toàn bộ suite engine ngoài qpdf: 50/50 xanh (chạy trên Linux).
Docs: 12 (iteration 2), 13 (E17–E25), 14 MỚI — phân tích định hướng/công nghệ/
tiến độ so với tham vọng thay Foxit, gap còn lại + giải pháp; README cập nhật.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk