Dot 1 "edit chuan moi PDF": CID/Identity-H font, format TUNG CHU, edit OCR, bu metrics, splash, Open-with
Build Windows portable / build (push) Canceled after 0s

1) fontfix v2 (PDF tu Word/InDesign): tong hop bang name/post khi subset
   thieu (OTS bat buoc); font CID/Identity-H KHONG co cmap -> dung cmap
   (3,1) fmt4 tu /ToUnicode x /CIDToGIDMap (Identity: GID=CID; stream 2B/CID
   parse tu descendant font); chan GID >= numGlyphs (maxp). tounicode.rs tra
   FontMapping {code_to_uni, is_cid, cid_to_gid}.

2) Format TUNG CHU trong o sua (chuan Word/Foxit): boi den -> B/I (nut
   toolbar khong cuop focus + Ctrl+B/I). UI tach span theo vung chon, dat
   data-bold/italic-override; commit gui override tung doan (RichSegDto
   bold/italic). Engine: bang style rich key theo (run nguon, override) -
   doi bien the thi lay font BIEN THE THAT cung ho (fontmatch -> builtin ->
   substitute), khong dung lai font goc. Test:
   rich_seg_bold_italic_override_uses_variant_font.

3) Edit file OCR (scan + text an): khoi toan run render-mode INVISIBLE ->
   ve rect TRANG che vung chu cu tren anh + ve text moi HIEN mau den (ca 4
   duong ve: rich chinh/fallback tu, 1-style chinh/fallback). Test:
   ocr_invisible_block_becomes_visible_with_cover.

4) Bu metrics kieu Adobe Sans MM trong o sua: span mang data-pdfw (be rong
   that tren trang); fitEditLinesToPdf them pass letter-spacing de moi nhom
   style chiem DUNG quang goc khi font thay the lech metrics (>2%); kep
   +/-18% co chu.

5) Khoi dong het "do": cua so tao AN (visible:false), UI show() ngay khi
   frontend san sang + man splash che phan nap dau; luoi an toan Rust hien
   cua so sau 4s neu JS chet som.

6) Open with FoFreeXit.exe: co che argv (initial_file) van hoat dong - loi
   nam o registry Applications tro exe CU (ban portable bi di chuyen/ghi
   de) lam Windows hien lai hop chon app. Fix tu chua: moi lan khoi dong
   dang ky lai HKCU (Applications command + ProgID FoFreeXit.pdf +
   OpenWithProgids .pdf) tro exe hien tai, chay nen qua reg.exe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 16:44:06 +07:00
co-authored by Claude Fable 5
parent a3b00da10d
commit 74f161a627
9 changed files with 839 additions and 64 deletions
+58 -10
View File
@@ -10,15 +10,26 @@ use lopdf::{Dictionary, Document as LoDoc, Object};
use crate::formsurgery::{deref, stream_bytes};
/// Map mã→Unicode của font có BaseFont khớp `font_name` (so phần sau prefix
/// subset "ABCDEF+") trên trang `page_index`; tìm cả trong resources của Form
/// Thông tin giải mã của 1 font trong PDF — đủ để fontfix dựng cmap Unicode.
pub(crate) struct FontMapping {
/// Mã trong content stream → Unicode (từ /ToUnicode).
pub code_to_uni: HashMap<u32, u32>,
/// Font CID (Type0/Identity-H): mã = CID. `cid_to_gid`: None = Identity
/// (GID = CID — trường hợp phổ biến của Word/InDesign), Some = bảng tra
/// từ stream /CIDToGIDMap (2 byte big-endian mỗi CID).
pub is_cid: bool,
pub cid_to_gid: Option<Vec<u16>>,
}
/// Mapping của font có BaseFont khớp `font_name` (so phần sau prefix subset
/// "ABCDEF+") trên trang `page_index`; tìm cả trong resources của Form
/// XObject (sâu tối đa 3). Không thấy/không parse được → None (fontfix rơi về
/// ASCII identity + MacRoman).
pub(crate) fn code_to_unicode(
pub(crate) fn font_mapping(
input: &Path,
page_index: u16,
font_name: &str,
) -> Option<HashMap<u32, u32>> {
) -> Option<FontMapping> {
let want = font_name.rsplit('+').next().unwrap_or(font_name).trim();
if want.is_empty() {
return None;
@@ -36,7 +47,7 @@ fn scan_resources(
res: &Dictionary,
want: &str,
depth: usize,
) -> Option<HashMap<u32, u32>> {
) -> Option<FontMapping> {
if depth > 3 {
return None;
}
@@ -54,14 +65,51 @@ fn scan_resources(
if suffix != want {
continue;
}
let Ok(tu) = fd.get(b"ToUnicode") else { continue };
if let Object::Stream(s) = deref(doc, tu) {
if let Some(m) = parse_cmap(&stream_bytes(s)) {
if !m.is_empty() {
return Some(m);
let mut code_to_uni = HashMap::new();
if let Ok(tu) = fd.get(b"ToUnicode") {
if let Object::Stream(s) = deref(doc, tu) {
if let Some(m) = parse_cmap(&stream_bytes(s)) {
code_to_uni = m;
}
}
}
// Type0 (Identity-H): mã = CID; GID tra qua /CIDToGIDMap của
// descendant font (Name "Identity" hoặc stream 2B/CID).
let subtype = fd
.get(b"Subtype")
.ok()
.and_then(|o| deref(doc, o).as_name().ok())
.map(|n| n.to_vec())
.unwrap_or_default();
let is_cid = subtype == b"Type0";
let mut cid_to_gid = None;
if is_cid {
if let Ok(desc) = fd.get(b"DescendantFonts") {
let desc = deref(doc, desc);
let first = match desc {
Object::Array(a) => a.first().map(|o| deref(doc, o)),
other => Some(other),
};
if let Some(Ok(dd)) = first.map(|o| o.as_dict()) {
if let Ok(map) = dd.get(b"CIDToGIDMap") {
if let Object::Stream(s) = deref(doc, map) {
let raw = stream_bytes(s);
cid_to_gid = Some(
raw.chunks(2)
.map(|c| {
u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])
})
.collect(),
);
}
}
}
}
}
if code_to_uni.is_empty() {
continue; // không có ToUnicode — thử font trùng tên khác
}
return Some(FontMapping { code_to_uni, is_cid, cid_to_gid });
}
}
}