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
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:
@@ -1546,8 +1546,18 @@ fn rich_reflow_preserves_mixed_styles() {
|
||||
indices: vec![idx_a, idx_b],
|
||||
text: "Trang quan tri hien thi MOI windowsInitialPassword".into(),
|
||||
rich: Some(vec![vec![
|
||||
RichSeg { text: "Trang quan tri hien thi MOI ".into(), style: idx_a },
|
||||
RichSeg { text: "windowsInitialPassword".into(), style: idx_b },
|
||||
RichSeg {
|
||||
text: "Trang quan tri hien thi MOI ".into(),
|
||||
style: idx_a,
|
||||
bold: None,
|
||||
italic: None,
|
||||
},
|
||||
RichSeg {
|
||||
text: "windowsInitialPassword".into(),
|
||||
style: idx_b,
|
||||
bold: None,
|
||||
italic: None,
|
||||
},
|
||||
]]),
|
||||
}],
|
||||
&out,
|
||||
@@ -1593,3 +1603,135 @@ fn rich_reflow_preserves_mixed_styles() {
|
||||
plain.rect
|
||||
);
|
||||
}
|
||||
|
||||
/// Fixture kiểu file đã OCR: text render mode 3 (INVISIBLE — lớp chữ ẩn đè
|
||||
/// trên ảnh scan để tìm kiếm/copy).
|
||||
fn build_invisible_text_pdf(path: &std::path::Path) {
|
||||
use lopdf::{dictionary, Document, Object, Stream};
|
||||
let mut doc = Document::with_version("1.5");
|
||||
let pages_id = doc.new_object_id();
|
||||
let f1 = doc.add_object(dictionary! {
|
||||
"Type" => "Font",
|
||||
"Subtype" => "Type1",
|
||||
"BaseFont" => "Helvetica",
|
||||
});
|
||||
let content_id = doc.add_object(Stream::new(
|
||||
dictionary! {},
|
||||
b"BT /F1 14 Tf 3 Tr 72 700 Td (Van ban OCR nhan dien tu anh) Tj ET".to_vec(),
|
||||
));
|
||||
let page_id = doc.add_object(dictionary! {
|
||||
"Type" => "Page",
|
||||
"Parent" => pages_id,
|
||||
"MediaBox" => vec![0.into(), 0.into(), 612.into(), 792.into()],
|
||||
"Resources" => dictionary! { "Font" => dictionary! { "F1" => f1 } },
|
||||
"Contents" => content_id,
|
||||
});
|
||||
doc.objects.insert(
|
||||
pages_id,
|
||||
Object::Dictionary(dictionary! {
|
||||
"Type" => "Pages",
|
||||
"Kids" => vec![page_id.into()],
|
||||
"Count" => 1,
|
||||
}),
|
||||
);
|
||||
let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id });
|
||||
doc.trailer.set("Root", catalog_id);
|
||||
doc.save(path).expect("lưu fixture OCR");
|
||||
}
|
||||
|
||||
/// Sửa khối text ẨN (file scan đã OCR): engine phải CHE vùng chữ cũ bằng rect
|
||||
/// trắng và vẽ text mới HIỆN màu đen — như "edit scanned document" của Foxit.
|
||||
#[test]
|
||||
fn ocr_invisible_block_becomes_visible_with_cover() {
|
||||
let pdf = pdfium();
|
||||
let input = tmp("ff_edit_ocr_fx.pdf");
|
||||
build_invisible_text_pdf(&input);
|
||||
let out = tmp("ff_edit_ocr_out.pdf");
|
||||
let idx = find_text_index(&pdf, &input, "Van ban OCR");
|
||||
let paths_before = ff_engine::list_objects(&pdf, &input, 0, None)
|
||||
.expect("list in")
|
||||
.iter()
|
||||
.filter(|o| o.kind == ObjectKind::Path)
|
||||
.count();
|
||||
|
||||
ff_engine::apply_edits(
|
||||
&pdf,
|
||||
&input,
|
||||
0,
|
||||
&[EditOp::ReflowText {
|
||||
indices: vec![idx],
|
||||
text: "Van ban OCR DA SUA lai noi dung".into(),
|
||||
rich: None,
|
||||
}],
|
||||
&out,
|
||||
None,
|
||||
)
|
||||
.expect("reflow khối OCR");
|
||||
|
||||
let objs = ff_engine::list_objects(&pdf, &out, 0, None).expect("list out");
|
||||
let paths_after = objs.iter().filter(|o| o.kind == ObjectKind::Path).count();
|
||||
assert!(paths_after > paths_before, "phải có rect trắng che vùng chữ scan cũ");
|
||||
let t = objs
|
||||
.iter()
|
||||
.find(|o| o.text.as_deref().map(|t| t.contains("DA SUA")).unwrap_or(false))
|
||||
.unwrap_or_else(|| panic!("thiếu text mới: {objs:?}"));
|
||||
assert_eq!(t.color, Some([0, 0, 0, 255]), "chữ mới phải màu đen (hiện)");
|
||||
}
|
||||
|
||||
/// Format TỪNG CHỮ: bôi đen 1 từ bấm B → seg mang override bold=true; engine
|
||||
/// phải vẽ từ đó bằng BIẾN THỂ đậm thật (không phải giả lập), các từ quanh
|
||||
/// giữ nguyên; italic tương tự.
|
||||
#[test]
|
||||
fn rich_seg_bold_italic_override_uses_variant_font() {
|
||||
use ff_engine::RichSeg;
|
||||
let pdf = pdfium();
|
||||
let input = tmp("ff_edit_rich_override.pdf");
|
||||
build_mixed_style_pdf(&input);
|
||||
let out = tmp("ff_edit_rich_override_out.pdf");
|
||||
let idx_a = find_text_index(&pdf, &input, "Trang quan tri");
|
||||
|
||||
ff_engine::apply_edits(
|
||||
&pdf,
|
||||
&input,
|
||||
0,
|
||||
&[EditOp::ReflowText {
|
||||
indices: vec![idx_a],
|
||||
text: "Trang quan tri hien thi".into(),
|
||||
rich: Some(vec![vec![
|
||||
RichSeg { text: "Trang ".into(), style: idx_a, bold: None, italic: None },
|
||||
RichSeg { text: "quan tri ".into(), style: idx_a, bold: Some(true), italic: None },
|
||||
RichSeg { text: "hien ".into(), style: idx_a, bold: None, italic: Some(true) },
|
||||
RichSeg { text: "thi".into(), style: idx_a, bold: None, italic: None },
|
||||
]]),
|
||||
}],
|
||||
&out,
|
||||
None,
|
||||
)
|
||||
.expect("apply_edits rich override");
|
||||
|
||||
let objs = ff_engine::list_objects(&pdf, &out, 0, None).expect("list out");
|
||||
let find = |needle: &str| {
|
||||
objs.iter()
|
||||
.find(|o| o.text.as_deref().map(|t| t.contains(needle)).unwrap_or(false))
|
||||
.unwrap_or_else(|| panic!("thiếu run '{needle}': {objs:?}"))
|
||||
};
|
||||
let plain = find("Trang");
|
||||
let bolded = find("quan tri");
|
||||
let italicized = find("hien");
|
||||
assert_eq!(plain.font_bold, Some(false), "'Trang' giữ thường: {:?}", plain.font_name);
|
||||
assert_eq!(
|
||||
bolded.font_bold,
|
||||
Some(true),
|
||||
"'quan tri' phải thành ĐẬM thật: {:?}",
|
||||
bolded.font_name
|
||||
);
|
||||
assert_eq!(
|
||||
italicized.font_italic,
|
||||
Some(true),
|
||||
"'hien' phải thành NGHIÊNG thật: {:?}",
|
||||
italicized.font_name
|
||||
);
|
||||
// Cùng baseline, thứ tự trái→phải giữ nguyên.
|
||||
assert!((bolded.rect.bottom - plain.rect.bottom).abs() < 4.0);
|
||||
assert!(bolded.rect.left > plain.rect.left && italicized.rect.left > bolded.rect.left);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user