feat(字体选择): 默认字体优先中文
build.yaml / build (push) Successful in 47s

This commit is contained in:
xiaoqidun committed 2026-10-28 18:50:10 +08:00
1 parent f8c8259502
commit 9d6bf3e87f
4 files changed
+106 -13

No files matched your search

+37 -13
View File
@@ -113,6 +113,8 @@ function createDocumentState() {
outlineExpanded: new Map(),
textFonts: [],
textFontID: null,
textFontInitialized: false,
textFontInitializing: null,
textDefaults: { type: "TextObject", size: 16 * 25.4 / 72, color: "#000000", wrap: true, align: "left", paragraphHeight: 0, letterSpacing: 0 },
ofdBytes: null,
sourcePDF: false,
@@ -4363,13 +4365,12 @@ function editorFonts(item) {
return [...embedded.values(), ...fonts];
}
async function loadEditorFonts(requestPermission = true) {
const tab = documentTab(), target = tab.state;
const tasks = [];
if (!state.fontCatalogLoading && fontManager.canReadLocal() && !fontManager.catalogLoaded && fontManager.permission !== "denied"
async function loadEditorFonts(requestPermission = true, tab = documentTab(), waitPending = false) {
const target = tab.state;
const tasks = waitPending ? [target.fontCatalogLoading, target.fontFacesLoading].filter(Boolean) : [];
if (!target.fontCatalogLoading && fontManager.canReadLocal() && !fontManager.catalogLoaded && fontManager.permission !== "denied"
&& (requestPermission || fontManager.permission === "granted")) {
target.fontCatalogLoading = true;
tasks.push((async () => {
target.fontCatalogLoading = (async () => {
try {
await fontManager.queryLocal();
refreshEditorFonts();
@@ -4378,13 +4379,13 @@ async function loadEditorFonts(requestPermission = true) {
} finally {
target.fontCatalogLoading = false;
}
})());
})();
tasks.push(target.fontCatalogLoading);
}
if (!state.fontFacesLoading && fontManager.userFonts.some(font => font.enabled && !font.faces)) {
target.fontFacesLoading = true;
if (!target.fontFacesLoading && fontManager.userFonts.some(font => font.enabled && !font.faces)) {
const wasmSeq = target.wasmSeq;
const current = () => !tab.closed && !target.wasmExited && target.wasmSeq === wasmSeq;
tasks.push((async () => {
target.fontFacesLoading = (async () => {
try {
await fontManager.loadFaces(data => callTabWASM(tab, "ofdgoFontFaces", data), current);
refreshEditorFonts();
@@ -4392,11 +4393,33 @@ async function loadEditorFonts(requestPermission = true) {
target.fontFacesLoading = false;
if (!current() && state.ready) loadEditorFonts(false);
}
})());
})();
tasks.push(target.fontFacesLoading);
}
await Promise.all(tasks);
}
async function initializeTextFont(tab) {
const target = tab.state;
if (target.textFontInitialized) return;
if (target.textFontInitializing) return target.textFontInitializing;
const wasmSeq = target.wasmSeq;
const current = () => !tab.closed && !target.wasmExited && target.wasmSeq === wasmSeq;
target.textFontInitializing = (async () => {
await loadEditorFonts(false, tab, true);
if (!current()) return;
const font = await fontManager.defaultFont((faces, families) => callTabWASM(tab, "ofdgoFontPreferenceOrder", JSON.stringify({
faces: faces.map(({ family, fullName, postscriptName, style, names }) => ({ family, fullName, postscriptName, style, names })), families
})), async (font, text) => callTabWASM(tab, "ofdgoCheckTextFont", await readTextFont(font, tab), text), current);
if (!current()) return;
target.textDefaults.fontChoice = font || (fontManager.editorFonts().length ? null : undefined);
target.textFontInitialized = true;
if (isForegroundDocument(tab)) refreshEditorFonts();
})();
try { await target.textFontInitializing; }
finally { target.textFontInitializing = null; }
}
function refreshEditorFonts() {
updateTextFonts(canvasEditor.selected, true, true);
}
@@ -4446,9 +4469,9 @@ function updateTextFonts(item, refresh = false, preserve = false) {
state.textFonts.unshift(selected);
}
const mixed = (item?.items || item?.richText) && !id;
fontPicker.setFonts(state.textFonts, mixed ? null : selected || state.textFonts[0], preserve);
fontPicker.setFonts(state.textFonts, mixed ? null : selected || (state.textDefaults.fontChoice === null ? null : state.textFonts[0]), preserve);
el.textFont.placeholder = mixed ? "混合" : "";
if (!id && !item?.items && !item?.richText) state.textDefaults.fontChoice = state.textFonts[Number(fontPicker.value)];
if (!id && !item?.items && !item?.richText) state.textDefaults.fontChoice = state.textFonts[Number(fontPicker.value)] || (state.textDefaults.fontChoice === null ? null : undefined);
}
}
@@ -6567,6 +6590,7 @@ async function openDocument(options = {}) {
return;
}
const pageFonts = state.sourcePDF && !nativeHost && !state.editorInfo ? await fontManager.files(fontManager.records()) : fonts;
if (state.editing) await initializeTextFont(tab);
if (openSeq !== state.openSeq) return;
tab.worker.configurePages?.(!state.editorInfo && !doc.encrypted && state.ofdBytes ? {
file: state.ofdBytes, fonts: pageFonts, pdf: state.sourcePDF, fontNames: state.sourcePDF ? [...fontManager.localEntries().keys()] : [], annotations: state.renderAnnotations, document: doc.documentIndex, version: doc.documentVersion
+19
View File
@@ -335,6 +335,25 @@ export class FontManager {
}
}
async defaultFont(order, inspect, current = () => true) {
const fonts = this.editorFonts().filter(font => !font.disabled);
if (!fonts.length) return;
const families = [["DengXian", "等线"], ["Microsoft YaHei", "微软雅黑"], ["PingFang SC", "苹方-简", "苹方 简体"],
["Noto Sans SC", "Noto Sans CJK SC"], ["Source Han Sans SC", "Source Han Sans CN", "思源黑体", "思源黑体 CN"]];
const indices = await order(fonts, families);
let fallback = fonts.length;
for (const index of indices) {
if (!current()) return;
try {
await inspect(fonts[index], "中文汉字编辑水印,。!?");
if (current()) return fonts[index];
} catch (error) {
if (error.missingGlyphs) fallback = Math.min(fallback, index);
}
}
if (current()) return fonts[fallback];
}
async add(fonts) {
let saved = true;
try {
+15
View File
@@ -3175,6 +3175,20 @@ func (e *businessEngine) fontFaces(args []platformValue) (any, error) {
return (ofdgo.FontFile{Data: data}).Faces()
}
// fontPreferenceOrder 按字体族偏好排列编辑字体候选索引
// 入参: args 候选字体和字体族别名组的JSON数据
// 返回: any 候选索引列表, error 错误信息
func (e *businessEngine) fontPreferenceOrder(args []platformValue) (any, error) {
var request struct {
Faces []ofdgo.FontFace `json:"faces"`
Families [][]string `json:"families"`
}
if err := json.Unmarshal([]byte(args[0].String()), &request); err != nil {
return nil, err
}
return ofdgo.FontPreferenceOrder(request.Faces, request.Families), nil
}
// fontFace 提取编辑时选定的字体,供输入预览和保存共用
// 入参: args 字体数据和零起始索引
// 返回: any 独立字体数据, error 错误信息
@@ -8053,6 +8067,7 @@ func (e *businessEngine) businessCommands() map[string]func([]platformValue) (an
"ofdgoSaveRichText": e.saveRichText,
"ofdgoPreviewRichText": e.previewRichText,
"ofdgoCheckTextFont": e.checkTextFont,
"ofdgoFontPreferenceOrder": e.fontPreferenceOrder,
"ofdgoInsertImage": e.insertImage,
"ofdgoInsertShape": e.insertShape,
"ofdgoUpdatePathStyle": e.updatePathStyle,
+35
View File
@@ -17,6 +17,7 @@ package ofdgo
import (
"fmt"
"path"
"sort"
"unicode/utf8"
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
@@ -25,6 +26,40 @@ import (
// FontFace 字体文件中的字体名称与零起始索引,不依赖文件名
type FontFace = basefont.Face
// FontPreferenceOrder 按字体族偏好排列候选索引,同族优先常规样式,其余保持原顺序
// 入参: faces 候选字体, families 按优先级排列的字体族别名组
// 返回: []int 候选字体的零起始索引,不修改输入列表
func FontPreferenceOrder(faces []FontFace, families [][]string) []int {
priorities := make(map[string]int)
for priority, names := range families {
for _, name := range names {
key := FontNormalizeName(name)
if _, exists := priorities[key]; key != "" && !exists {
priorities[key] = priority
}
}
}
order, ranks := make([]int, len(faces)), make([]int, len(faces))
for i, face := range faces {
order[i], ranks[i] = i, len(families)*2
names := append([]string{face.Family, face.FullName, face.PostScriptName}, face.Names...)
for _, name := range names {
if priority, exists := priorities[FontNormalizeName(name)]; exists && priority*2 < ranks[i] {
ranks[i] = priority * 2
}
}
if ranks[i] < len(families)*2 {
switch FontNormalizeName(face.Style) {
case "", "regular", "normal", "roman", "常规", "正常", "标准":
default:
ranks[i]++
}
}
}
sort.SliceStable(order, func(i, j int) bool { return ranks[order[i]] < ranks[order[j]] })
return order
}
// FixFontDataAggressive 激进修复字体数据
// 尝试修复缺失表(OS/2, cmap等)的TrueType字体或包装CFF裸数据
// 对显式字形映射补充私有字符cmap,便于按glyph id渲染