O sua dung FONT NHUNG cua PDF (FontFace): het "mat in dam" do may thieu font
Build Windows portable / build (push) Canceled after 0s
Build Windows portable / build (push) Canceled after 0s
User bao: chu mono von in dam (NotoSansMono-Bold), mo o sua thi "ve chu thuong". Chan doan bang CSS.getPlatformFontsForNode: span da co font-weight:700 dung, nhung may khong cai font cua tai lieu nen WebView thay the — mono roi ve Consolas-Bold (net manh hon han NotoSansMono-Bold, nhin nhu khong dam), chu thuong roi ve Arial. Fix WYSIWYG font that: - Engine: ff_engine::font_data(input, page, index) — bytes font nhung (sau giai nen) cua 1 text run, index PHANG nhu list_objects (vao duoc run trong Form XObject). - Tauri: command edit_font_data tra base64. - UI: nap luoi theo TEN font khi loadEditPage (dedup, 1 lan/font), dang ky FontFace voi weight/style theo run (khong synthesize chong), dat family nhung LEN DAU stack cua div/span trong o sua; glyph thieu trong subset tu roi xuong stack he thong; doi phien sua thi bo face cu (subset khac coverage). Loi (font khong nhung/khong parse) -> giu stack cu. Smoke test tren app that (CDP): FontFace tu ArrayBuffer + gan vao span mono => getPlatformFontsForNode tra isCustomFont:true. E2E day du sau build CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -703,6 +703,16 @@ fn edit_list_objects(path: String, page: u16, password: Option<String>) -> Resul
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Bytes font NHÚNG của 1 text run (base64) — UI đăng ký FontFace để ô sửa
|
||||
/// hiển thị đúng font tài liệu thay vì font hệ thống gần giống.
|
||||
#[tauri::command]
|
||||
fn edit_font_data(path: String, page: u16, index: u16, password: Option<String>) -> Result<String, String> {
|
||||
let pdfium = pdfium()?;
|
||||
let data = ff_engine::font_data(&pdfium, std::path::Path::new(&path), page, index, password.as_deref())
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(base64::engine::general_purpose::STANDARD.encode(&data))
|
||||
}
|
||||
|
||||
/// 1 thao tác sửa nội dung (tagged theo field `op`). Field thừa cho mỗi loại để None/mặc định.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -1424,6 +1434,7 @@ fn main() {
|
||||
preview_watermark,
|
||||
preview_header_footer,
|
||||
edit_list_objects,
|
||||
edit_font_data,
|
||||
edit_apply,
|
||||
edit_apply_to_temp,
|
||||
edit_preview,
|
||||
|
||||
+61
-2
@@ -2257,6 +2257,7 @@ function enterEditMode(pageIndex) {
|
||||
$("sidebar").classList.add("hidden");
|
||||
$("editModeBtn").classList.add("active");
|
||||
setTool(null);
|
||||
resetDocFonts(); // font nhúng nạp theo phiên sửa (subset mỗi file khác nhau)
|
||||
buildEditColumn();
|
||||
// Đứng ngay tại trang vào sửa (không smooth — vừa mở chế độ, chưa có ngữ cảnh cuộn).
|
||||
const slot0 = state.editSlots[state.editPage];
|
||||
@@ -2339,6 +2340,11 @@ async function loadEditPage() {
|
||||
img.src = url;
|
||||
img.style.width = stageW + "px";
|
||||
state.editObjects = objs;
|
||||
// Nạp trước font nhúng của mọi run text (dedup theo tên font) — đến lúc
|
||||
// đúp mở ô sửa thì face đã sẵn, đo đạc đúng ngay từ đầu.
|
||||
for (const o of objs) {
|
||||
if (o.kind === "text") ensureDocFont(o, state.editBase, state.editPage);
|
||||
}
|
||||
buildEditOverlay();
|
||||
$("pageInput").value = state.editPage + 1;
|
||||
updateZoomLabel();
|
||||
@@ -2583,6 +2589,59 @@ const FONT_CSS_ALIASES = {
|
||||
couriernewps: "Courier New",
|
||||
segoeui: "Segoe UI",
|
||||
};
|
||||
// ---------- Font NHÚNG của tài liệu cho ô sửa ----------
|
||||
// Máy người dùng thường KHÔNG cài font của PDF: WebView thay bằng font hệ
|
||||
// thống gần giống (NotoSansMono-Bold → Consolas-Bold nét mảnh — user thấy
|
||||
// như "mất in đậm"; NotoSans → Arial). Nạp bytes font nhúng từ engine vào
|
||||
// WebView (FontFace) rồi đặt family đó LÊN ĐẦU stack của ô sửa → chữ trong ô
|
||||
// đúng font thật của tài liệu. Glyph thiếu trong subset tự rơi xuống stack cũ.
|
||||
let docFontSeq = 0; // đổi phiên sửa = bỏ face cũ (subset khác coverage)
|
||||
const docFonts = new Map(); // fontName -> family đã nạp | null (lỗi) | Promise
|
||||
const docFontFaces = [];
|
||||
|
||||
function resetDocFonts() {
|
||||
docFontSeq++;
|
||||
for (const f of docFontFaces) { try { document.fonts.delete(f); } catch (_) {} }
|
||||
docFontFaces.length = 0;
|
||||
docFonts.clear();
|
||||
}
|
||||
|
||||
// Nạp lười theo TÊN font (NotoSansMono-Bold...): mỗi tên chỉ đọc bytes 1 lần,
|
||||
// dùng 1 run đại diện để lấy dữ liệu. Lỗi (font không nhúng, không parse
|
||||
// được) → đánh dấu null, ô sửa dùng stack hệ thống như cũ.
|
||||
function ensureDocFont(run, path, page) {
|
||||
const name = run && run.fontName;
|
||||
if (!name || docFonts.has(name)) return;
|
||||
const fam = "ff-doc-" + docFontSeq + "-" + name.replace(/[^A-Za-z0-9_-]/g, "");
|
||||
const p = (async () => {
|
||||
try {
|
||||
const b64 = await invoke("edit_font_data", { path, page, index: run.index, password: null });
|
||||
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
||||
const face = new FontFace(fam, bytes.buffer, {
|
||||
weight: run.fontBold ? "700" : "400",
|
||||
style: run.fontItalic ? "italic" : "normal",
|
||||
});
|
||||
await face.load();
|
||||
document.fonts.add(face);
|
||||
docFontFaces.push(face);
|
||||
docFonts.set(name, fam);
|
||||
return fam;
|
||||
} catch (_) {
|
||||
docFonts.set(name, null);
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
docFonts.set(name, p);
|
||||
}
|
||||
|
||||
// Stack font cho 1 run trong ô sửa: font nhúng (nếu đã nạp xong) đứng đầu,
|
||||
// stack hệ thống theo sau làm lưới an toàn.
|
||||
function editFontStack(run, fallbackFamily) {
|
||||
const base = cssFontStack(fallbackFamily);
|
||||
const fam = run && run.fontName ? docFonts.get(run.fontName) : null;
|
||||
return typeof fam === "string" && fam ? '"' + fam + '", ' + base : base;
|
||||
}
|
||||
|
||||
function cssFontStack(family) {
|
||||
if (!family) return "sans-serif";
|
||||
const k = family.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||
@@ -2932,7 +2991,7 @@ function startBlockTextEdit(o, lines, ev) {
|
||||
const divFs = Math.max(6, (l.fs || 12) * s);
|
||||
div.style.fontSize = divFs + "px";
|
||||
div.style.lineHeight = perLineAdvances[i] + "px";
|
||||
div.style.fontFamily = cssFontStack(rep.fontFamily || o.fontFamily);
|
||||
div.style.fontFamily = editFontStack(rep, rep.fontFamily || o.fontFamily);
|
||||
div.style.fontWeight = (rep.fontBold != null ? rep.fontBold : o.fontBold) ? "bold" : "normal";
|
||||
div.style.fontStyle = (rep.fontItalic != null ? rep.fontItalic : o.fontItalic) ? "italic" : "normal";
|
||||
if (rep.color || o.color) div.style.color = rgbCss(rep.color || o.color);
|
||||
@@ -2946,7 +3005,7 @@ function startBlockTextEdit(o, lines, ev) {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = g.text;
|
||||
span.dataset.styleRun = String(g.rep.index);
|
||||
span.style.fontFamily = cssFontStack(g.rep.fontFamily || o.fontFamily);
|
||||
span.style.fontFamily = editFontStack(g.rep, g.rep.fontFamily || o.fontFamily);
|
||||
span.style.fontWeight = g.rep.fontBold ? "bold" : "normal";
|
||||
span.style.fontStyle = g.rep.fontItalic ? "italic" : "normal";
|
||||
span.style.fontSize = Math.max(6, Math.abs(g.rep.fontSize || l.fs || 12) * s) + "px";
|
||||
|
||||
@@ -488,6 +488,43 @@ fn text_object_style(t: &PdfPageTextObject) -> (String, bool, bool) {
|
||||
}
|
||||
|
||||
/// Liệt kê các page object của 1 trang để UI vẽ overlay chỉnh sửa.
|
||||
/// Bytes font NHÚNG của 1 text object (sau giải nén). UI nạp vào WebView qua
|
||||
/// FontFace để ô sửa hiển thị ĐÚNG font tài liệu: máy người dùng thường thiếu
|
||||
/// font (NotoSansMono-Bold rơi về Consolas nét mảnh → trông như "mất in
|
||||
/// đậm"). `object_index` = index PHẲNG như list_objects (vào được cả run
|
||||
/// trong Form XObject).
|
||||
pub fn font_data(
|
||||
pdfium: &Pdfium,
|
||||
input: &Path,
|
||||
page_index: u16,
|
||||
object_index: u16,
|
||||
password: Option<&str>,
|
||||
) -> Result<Vec<u8>, EngineError> {
|
||||
let document = pdfium
|
||||
.load_pdf_from_file(input, password)
|
||||
.map_err(|e| EngineError::Pdfium(e.to_string()))?;
|
||||
let page = document
|
||||
.pages()
|
||||
.get(page_index)
|
||||
.map_err(|e| EngineError::Pdfium(format!("không lấy được trang {page_index}: {e}")))?;
|
||||
let entries = collect_flat(&page);
|
||||
let entry = entries
|
||||
.get(object_index as usize)
|
||||
.ok_or_else(|| EngineError::Pdfium(format!("object {object_index} ngoài phạm vi")))?;
|
||||
let obj = object_at_path(&page, &entry.path)?;
|
||||
let t = obj
|
||||
.as_text_object()
|
||||
.ok_or_else(|| EngineError::Pdfium("object không phải text".into()))?;
|
||||
let data = t
|
||||
.font()
|
||||
.data()
|
||||
.map_err(|e| EngineError::Pdfium(format!("không đọc được font nhúng: {e}")))?;
|
||||
if data.is_empty() {
|
||||
return Err(EngineError::Pdfium("font không nhúng trong file".into()));
|
||||
}
|
||||
Ok(data.to_vec())
|
||||
}
|
||||
|
||||
pub fn list_objects(
|
||||
pdfium: &Pdfium,
|
||||
input: &Path,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub mod watermark;
|
||||
pub use annot::{
|
||||
apply_annotations, count_annotations, list_annotations, AnnotInfo, AnnotKind, AnnotSpec,
|
||||
};
|
||||
pub use edit::{apply_edits, flatten_form_xobjects, list_objects, EditOp, ObjectInfo, ObjectKind, RichSeg};
|
||||
pub use edit::{apply_edits, flatten_form_xobjects, font_data, list_objects, EditOp, ObjectInfo, ObjectKind, RichSeg};
|
||||
pub use meta::{outline, page_dims, strip_metadata, OutlineItem, PageDim};
|
||||
pub use organize::{
|
||||
build_document, delete_pages, extract_pages, identity_plan, merge_files, rotate_pages,
|
||||
|
||||
Reference in New Issue
Block a user