mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
1 parent
f8c8259502
commit
9d6bf3e87f
4 files changed
+106
-13
No files matched your search
+37
-13
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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渲染
|
||||
|
||||
Reference in new issue
Block a user