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:
- ocr.rs MỚI: find_tesseract (sidecar như qpdf, env FOFREEXIT_TESSERACT_PATH);
ocr_page_words (render 300 DPI → tesseract tsv → lọc confidence ≥30 → quy
đổi pixel→điểm PDF); ocr_add_text_layer — thêm text object render-mode
INVISIBLE đúng khung từng từ lên CHÍNH trang gốc (searchable PDF chuẩn,
ảnh không đổi), font Unicode nhúng 1 lần, mặc định vie+eng
- convert.rs MỚI: export_images (PNG mỗi trang theo DPI), export_text,
export_docx tự viết (gom char boxes thành dòng thị giác → đoạn Word với cỡ
chữ xấp xỉ + ngắt trang; DOCX = zip STORE tự ghi kèm CRC-32, không thêm
dependency); find_soffice + office_to_pdf + pdf_to_docx_via_soffice
(LibreOffice headless, profile riêng -env:UserInstallation)
Tauri + UI (thanh 🔁 Chuyển đổi):
- Commands: ocr_run, convert_images, convert_txt, convert_docx (tự chọn
LibreOffice nếu có → fallback bộ cơ bản, báo engine đã dùng),
office_convert, convert_tools_status (UI disable nút + báo thiếu công cụ),
pick_office_file, pick_save_as
- Nút: OCR (select Việt+Anh/Việt/Anh), Xuất PNG 150 DPI, Xuất TXT, Xuất Word,
Office→PDF (mở luôn kết quả)
Test (6 mới → engine 73/73 xanh ngoài 2 fixture qpdf Linux):
- OCR tiếng Anh trên PDF-scan giả lập THẬT (xoá text chỉ còn ảnh render):
extract + search thấy từ, toạ độ lớp ẩn khớp vùng chữ
- OCR tiếng Việt đúng dấu ("Việt Nam đất nước")
- export PNG 3 trang / TXT / DOCX (unzip hệ thống giải được, có ngắt trang)
- LibreOffice round-trip 2 chiều (tự bỏ qua nếu máy không có soffice)
Docs: 19-phase7-summary (+ giới hạn v-sau), 20-phase7-user-tests (C1–C10 kèm
hướng dẫn cài Tesseract/LibreOffice trên Windows), README, roadmap (Phase 7
HOÀN TẤT lõi), 14 (kế tiếp: Phase 8 phát hành).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnbPwSoRGNH7LHsDUmRpk
Engine — chữ ký số (sign.rs MỚI):
- generate_self_signed_id: Digital ID tự ký RSA-2048 + X.509 (rcgen), ghi PEM
- sign_pdf: đúng cơ chế Adobe — lopdf dựng sig dict (/SubFilter
adbe.pkcs7.detached) + Widget field + AcroForm/SigFlags, ByteRange/Contents
placeholder; serialize → vá ByteRange theo offset thật → SHA-256 trên 2 đoạn
→ CMS SignedData detached (crate cms, signed attrs messageDigest+contentType,
RSA-SHA256) nhét DER vào Contents
- verify_signatures: parse ByteRange/Contents, băm lại, kiểm chữ ký RSA trên
signed attrs + messageDigest khớp + phủ toàn bộ file → phát hiện GIẢ MẠO
- Bài học: cắt DER về đúng độ dài (bỏ padding hex '0'); bỏ qua /Contents của
trang (tham chiếu), chỉ nhận /Contents <hex> của chữ ký
Engine — redact tỉa theo KÝ TỰ (redact.rs):
- Text chạm một phần: page_char_boxes xác định ký tự trong vùng, GIỮ các đoạn
ngoài vùng, dựng lại đúng vị trí/baseline/màu (font nhúng gốc → cùng họ →
mặc định). Guard: text xoay/không đọc được font → xoá nguyên (an toàn)
- qpdf::optimize_save: --object-streams=generate + nén, dọn object rác
Tauri + UI:
- Commands: sig_create_id, sig_sign, sig_verify, security_optimize,
pick_any_file, pick_save_pem
- Thanh Bảo mật thêm: Tạo Digital ID, Ký số (dialog + tự kiểm tra sau khi ký),
Kiểm tra chữ ký (modal bảng trạng thái + lý do), Lưu tối ưu
Test (7 mới → 62/62 engine xanh ngoài 2 fixture qpdf Linux):
- sign_then_verify_is_valid; tampering_after_signing_is_detected (phát hiện
sửa sau khi ký); verify_reports_none_for_unsigned
- redact tỉa ký tự: cụm mật biến mất, phần cùng dòng ngoài vùng vẫn còn
- optimize_save không phình + giữ nội dung
Deps: rsa, sha2, cms, x509-cert, rcgen, pkcs8, der, spki, const-oid.
Docs: 15 mục 6 (iter 2 + giới hạn), 16 (S13–S19), README, roadmap (Phase 5
HOÀN TẤT), 14 (kế tiếp: Phase 6 Form).
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
PDF viewer (render/search/text-select/thumbnails/outline) and annotation
engine (Highlight/Underline/Strikeout/Square/FreeText/Note) over PDFium +
lopdf, with Unicode font embedding for Vietnamese text and undo/redo.