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