mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
This commit is contained in:
1 parent
d5b8343ac4
commit
3c15a18546
5 files changed
+102
-88
No files matched your search
+15
-49
@@ -1,5 +1,4 @@
|
||||
const MM_TO_PX = 96 / 25.4;
|
||||
const LOCAL_FONT_LOAD_LIMIT = 16;
|
||||
const FONT_DATABASE = "ofdgo";
|
||||
const fontChannel = typeof BroadcastChannel === "function" ? new BroadcastChannel(FONT_DATABASE) : null;
|
||||
const COMPACT_LAYOUT = window.matchMedia("(max-width: 900px)");
|
||||
@@ -765,7 +764,7 @@ async function queryLocalFonts() {
|
||||
}
|
||||
|
||||
async function autoLoadDocumentLocalFonts(openSeq) {
|
||||
if (!externalDocumentFontNames().length || !canReadLocalFonts() || state.systemFontPermission === "denied") {
|
||||
if (!state.doc?.fonts?.some((font) => !font.embedded) || !canReadLocalFonts() || state.systemFontPermission === "denied") {
|
||||
return false;
|
||||
}
|
||||
setProgress("正在匹配字体", 62, STATUS.fonts);
|
||||
@@ -787,7 +786,6 @@ async function autoLoadDocumentLocalFonts(openSeq) {
|
||||
|
||||
async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
const docFonts = state.doc?.fonts || [];
|
||||
const docNames = externalDocumentFontNames();
|
||||
if (!docFonts.length) {
|
||||
state.localFonts = [];
|
||||
setStatus("暂无字体");
|
||||
@@ -795,18 +793,14 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
renderFontList();
|
||||
return false;
|
||||
}
|
||||
if (!docNames.length) {
|
||||
if (docFonts.every((font) => font.embedded)) {
|
||||
state.localFonts = [];
|
||||
setStatus("文档字体均为内嵌");
|
||||
updateFontSummary();
|
||||
renderFontList();
|
||||
return false;
|
||||
}
|
||||
const docLoadLimit = Math.min(LOCAL_FONT_LOAD_LIMIT, Math.max(4, docNames.length * 3));
|
||||
let selected = await selectLocalFonts(available, docNames, docLoadLimit);
|
||||
if (!selected.length) {
|
||||
selected = await selectLocalFonts(available, [], 6);
|
||||
}
|
||||
const selected = await selectLocalFonts(available);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return false;
|
||||
}
|
||||
@@ -834,32 +828,22 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
return fonts.length > 0;
|
||||
}
|
||||
|
||||
function uniqueLocalFonts(fonts) {
|
||||
const seen = new Set();
|
||||
const selected = [];
|
||||
for (const font of fonts) {
|
||||
const key = normalizeFontName(localFontName(font));
|
||||
if (!key || seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
selected.push(font);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
async function selectLocalFonts(fonts, names, limit) {
|
||||
async function selectLocalFonts(fonts) {
|
||||
const available = new Map();
|
||||
for (const font of uniqueLocalFonts(fonts)) {
|
||||
available.set(localFontName(font), font);
|
||||
for (const name of [font.postscriptName, [font.family, font.style].filter(Boolean).join(" ")]) {
|
||||
if (name) {
|
||||
available.set(`${name}.ttf`, font);
|
||||
for (const font of fonts) {
|
||||
const names = [
|
||||
localFontName(font),
|
||||
font.postscriptName && `${font.postscriptName}.ttf`,
|
||||
font.family && `${[font.family, font.style].filter(Boolean).join(" ")}.ttf`,
|
||||
];
|
||||
for (const name of names) {
|
||||
if (name && !available.has(name)) {
|
||||
available.set(name, font);
|
||||
}
|
||||
}
|
||||
}
|
||||
const matched = await callWASM("ofdgoFontFileMatches", [...available.keys()], names);
|
||||
return uniqueLocalFonts(matched.map((name) => available.get(name))).slice(0, limit);
|
||||
const matched = await callWASM("ofdgoMatchFontFiles", [...available.keys()]);
|
||||
return [...new Set(matched.map((name) => available.get(name)))];
|
||||
}
|
||||
|
||||
async function fontData(fonts) {
|
||||
@@ -944,29 +928,11 @@ function fontRecords() {
|
||||
return [...state.localFonts, ...state.userFonts];
|
||||
}
|
||||
|
||||
function externalDocumentFontNames() {
|
||||
const names = [];
|
||||
for (const font of state.doc?.fonts || []) {
|
||||
if (font.embedded || font.status === "embedded") {
|
||||
continue;
|
||||
}
|
||||
names.push(font.fontName, font.familyName);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
function localFontName(font) {
|
||||
const name = font.fullName || font.family || font.postscriptName || "local-font";
|
||||
return `${name}.ttf`;
|
||||
}
|
||||
|
||||
function normalizeFontName(name = "") {
|
||||
return String(name)
|
||||
.toLowerCase()
|
||||
.replace(/\.[^.]+$/, "")
|
||||
.replace(/[\s_\-()()]/g, "");
|
||||
}
|
||||
|
||||
async function applyFontChange(options = {}) {
|
||||
updateFontSummary();
|
||||
renderFontList();
|
||||
|
||||
+11
-32
@@ -20,8 +20,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/xiaoqidun/ofdgo"
|
||||
)
|
||||
|
||||
// currentSession 当前WebUI文档会话
|
||||
@@ -43,7 +41,7 @@ func RunWASM() {
|
||||
registerCallback("ofdgoExportFormats", exportFormats)
|
||||
registerCallback("ofdgoExportPage", exportPage)
|
||||
registerCallback("ofdgoExportPDF", exportPDF)
|
||||
registerCallback("ofdgoFontFileMatches", fontFileMatches)
|
||||
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
|
||||
select {}
|
||||
}
|
||||
|
||||
@@ -224,13 +222,14 @@ func bytesToJS(data []byte) js.Value {
|
||||
return value
|
||||
}
|
||||
|
||||
// fontFileMatches 匹配浏览器字体文件名称
|
||||
// matchFontFiles 匹配文档所需的字体文件
|
||||
// 入参: args 浏览器参数
|
||||
// 返回: any 匹配的字体文件名称, error 错误信息
|
||||
func fontFileMatches(args []js.Value) (any, error) {
|
||||
files := stringsFromJS(jsArg(args, 0))
|
||||
names := stringsFromJS(jsArg(args, 1))
|
||||
return ofdgo.FontFileMatches(files, false, false, names...), nil
|
||||
func matchFontFiles(args []js.Value) (any, error) {
|
||||
if currentSession == nil {
|
||||
return nil, fmt.Errorf("ofd document is not opened")
|
||||
}
|
||||
return currentSession.Reader.MatchFontFiles(stringsFromJS(args[0]))
|
||||
}
|
||||
|
||||
// bytesFromJS 从浏览器值读取二进制数据
|
||||
@@ -279,31 +278,11 @@ func fontsFromJS(value js.Value) ([]FontFile, error) {
|
||||
// 入参: value 浏览器值
|
||||
// 返回: []string 字符串列表
|
||||
func stringsFromJS(value js.Value) []string {
|
||||
if value.IsUndefined() || value.IsNull() {
|
||||
return nil
|
||||
items := make([]string, value.Length())
|
||||
for i := range items {
|
||||
items[i] = value.Index(i).String()
|
||||
}
|
||||
if js.Global().Get("Array").Call("isArray", value).Bool() {
|
||||
length := value.Get("length").Int()
|
||||
items := make([]string, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
item := value.Index(i)
|
||||
if !item.IsUndefined() && !item.IsNull() {
|
||||
items = append(items, item.String())
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
return []string{value.String()}
|
||||
}
|
||||
|
||||
// jsArg 获取浏览器参数
|
||||
// 入参: args 浏览器参数列表, index 参数索引
|
||||
// 返回: js.Value 浏览器值
|
||||
func jsArg(args []js.Value, index int) js.Value {
|
||||
if index < 0 || index >= len(args) {
|
||||
return js.Undefined()
|
||||
}
|
||||
return args[index]
|
||||
return items
|
||||
}
|
||||
|
||||
// encodeResult 编码浏览器接口返回结果
|
||||
|
||||
@@ -147,6 +147,36 @@ func FontFileMatches(files []string, bold, italic bool, names ...string) []strin
|
||||
return fontFileMatchNames(fontFileMatches(fontFileCandidates(files, path.Base), names, bold, italic))
|
||||
}
|
||||
|
||||
// MatchFontFiles 按文档字体名称和样式选择首选文件,跳过内嵌字体
|
||||
// 入参: files 可用字体文件名
|
||||
// 返回: []string 所需字体文件名, error 错误信息
|
||||
func (r *Reader) MatchFontFiles(files []string) ([]string, error) {
|
||||
fonts, err := r.Fonts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candidates := fontFileCandidates(files, path.Base)
|
||||
names := make([]string, 0, len(fonts))
|
||||
seen := make(map[string]bool)
|
||||
for _, font := range fonts {
|
||||
if font.FontFile != "" {
|
||||
continue
|
||||
}
|
||||
matches := fontFileMatches(candidates, []string{font.FontName, font.FamilyName}, font.Bold, font.Italic)
|
||||
if len(matches) == 0 {
|
||||
matches = fontFileMatches(candidates, nil, font.Bold, font.Italic)
|
||||
}
|
||||
if len(matches) > 0 {
|
||||
name := matches[0].name
|
||||
if !seen[name] {
|
||||
names = append(names, name)
|
||||
seen[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// matchStyle 匹配指定样式的字体名称
|
||||
// 入参: names 字体名称列表, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []fontFileMatch 字体匹配列表
|
||||
|
||||
+11
-1
@@ -49,9 +49,19 @@ type FontInfo struct {
|
||||
// Fonts 获取OFD声明的字体列表
|
||||
// 返回: []Font 字体列表, error 错误信息
|
||||
func (r *Reader) Fonts() ([]Font, error) {
|
||||
if _, err := r.Doc(); err != nil {
|
||||
doc, err := r.Doc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !r.fontResourcesRead {
|
||||
for _, page := range doc.Pages.Page {
|
||||
r.loadPageResources(page)
|
||||
}
|
||||
for _, template := range doc.CommonData.TemplatePage {
|
||||
r.loadPageResources(Page{BaseLoc: template.BaseLoc})
|
||||
}
|
||||
r.fontResourcesRead = true
|
||||
}
|
||||
fonts := make([]Font, 0, len(r.fontCache))
|
||||
for _, font := range r.fontCache {
|
||||
if font != nil {
|
||||
|
||||
+35
-6
@@ -32,9 +32,11 @@ type Reader struct {
|
||||
RootDir string
|
||||
ResMap map[string]string
|
||||
fontCache map[string]*Font
|
||||
fontResourcesRead bool
|
||||
colorSpaceCache map[string]*ColorSpace
|
||||
drawParamCache map[string]*DrawParam
|
||||
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
|
||||
pageHeaderCache map[string]PageContent
|
||||
doc *Document
|
||||
Stamps map[string][]Stamp
|
||||
Annots map[string][]Annotation
|
||||
@@ -78,6 +80,7 @@ func (r *Reader) initRoot() error {
|
||||
r.colorSpaceCache = make(map[string]*ColorSpace)
|
||||
r.drawParamCache = make(map[string]*DrawParam)
|
||||
r.compositeGraphicUnitCache = make(map[string]*CompositeGraphicUnit)
|
||||
r.pageHeaderCache = make(map[string]PageContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -272,21 +275,38 @@ func (r *Reader) PageArea(page Page) (PageArea, error) {
|
||||
return r.resolvePageArea(content.Area, content.Template)
|
||||
}
|
||||
|
||||
// readPageHeader 读取页面区域和模板引用
|
||||
// loadPageResources 加载页面资源,不解析图元
|
||||
// 入参: page 页面引用
|
||||
func (r *Reader) loadPageResources(page Page) {
|
||||
header, err := r.readPageHeader(page)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, res := range header.PageRes {
|
||||
r.loadRes(resolveResourcePath(page.BaseLoc, "", res))
|
||||
}
|
||||
}
|
||||
|
||||
// readPageHeader 读取页面区域、模板引用和资源声明
|
||||
// 入参: page 页面对象
|
||||
// 返回: PageContent 页面头部, error 错误信息
|
||||
func (r *Reader) readPageHeader(page Page) (PageContent, error) {
|
||||
f, err := r.openFile(r.ResPath(page.BaseLoc))
|
||||
name := r.ResPath(page.BaseLoc)
|
||||
if content, ok := r.pageHeaderCache[name]; ok {
|
||||
return content, nil
|
||||
}
|
||||
f, err := r.openFile(name)
|
||||
if err != nil {
|
||||
return PageContent{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
var content PageContent
|
||||
d := xml.NewDecoder(f)
|
||||
scan:
|
||||
for {
|
||||
token, err := d.Token()
|
||||
if err == io.EOF {
|
||||
return content, nil
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return PageContent{}, err
|
||||
@@ -302,10 +322,17 @@ func (r *Reader) readPageHeader(page Page) (PageContent, error) {
|
||||
}
|
||||
content.Template = append(content.Template, template)
|
||||
case "Area":
|
||||
err := d.DecodeElement(&content.Area, &start)
|
||||
return content, err
|
||||
if err := d.DecodeElement(&content.Area, &start); err != nil {
|
||||
return PageContent{}, err
|
||||
}
|
||||
case "PageRes":
|
||||
var res string
|
||||
if err := d.DecodeElement(&res, &start); err != nil {
|
||||
return PageContent{}, err
|
||||
}
|
||||
content.PageRes = append(content.PageRes, res)
|
||||
case "Content":
|
||||
return content, nil
|
||||
break scan
|
||||
default:
|
||||
if err := d.Skip(); err != nil {
|
||||
return PageContent{}, err
|
||||
@@ -313,6 +340,8 @@ func (r *Reader) readPageHeader(page Page) (PageContent, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
r.pageHeaderCache[name] = content
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// resolvePageArea 解析页面区域的继承关系
|
||||
|
||||
Reference in new issue
Block a user