diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index a75b831..dcc7f29 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -2334,7 +2334,7 @@ async function refreshEditorPage(doc, index, openSeq, clearSelection = false) { updateControls(); loadDocumentDetails(openSeq); for (const page of doc.pages) { - observeFlowPage(pageShell(page.index), page.index); + observeFlowPage(pageShell(page.index)); const button = el.pageList.querySelector(`[data-page-index="${page.index}"]`); observeThumbnail(button, page.index, openSeq); } @@ -3343,15 +3343,17 @@ async function openDocument(options = {}) { } async function loadDocumentDetails(openSeq) { - await waitForPaint(); - if (openSeq !== state.openSeq) { - return; - } try { - const info = await callWASM("ofdgoDocumentInfo"); - if (openSeq === state.openSeq) { - Object.assign(state.doc, info, { detailsPending: false, detailsError: "" }); - renderMeta(); + while (openSeq === state.openSeq) { + await waitForPaint(); + if (openSeq !== state.openSeq) return; + const info = await callWASM("ofdgoDocumentInfo", true); + if (openSeq !== state.openSeq) return; + if (!info.detailsPending) { + Object.assign(state.doc, info, { detailsPending: false, detailsError: "" }); + renderMeta(); + return; + } } } catch (err) { if (openSeq === state.openSeq) { @@ -3607,12 +3609,12 @@ function renderPageFlow() { shell.append(surface, placeholder); fragment.append(shell); - targets.push([shell, page.index]); + targets.push(shell); } el.svgHost.append(fragment); layoutPages(); - for (const [shell, index] of targets) { - observeFlowPage(shell, index); + for (const shell of targets) { + observeFlowPage(shell); } el.emptyState.hidden = true; el.pageFrame.hidden = false; @@ -3642,11 +3644,7 @@ function resetPageLoading() { } } -function observeFlowPage(shell, index) { - const openSeq = state.openSeq; - if (index < 4 && index !== state.pageIndex) { - renderFlowPage(index, { openSeq, priority: 3 }); - } +function observeFlowPage(shell) { const observer = flowPageObserver(); if (observer) { observer.observe(shell); @@ -5152,14 +5150,7 @@ function observeThumbnail(button, index, openSeq = state.openSeq) { if (observer) { observer.observe(button); } - if (index === state.pageIndex || index < 6) { - renderThumbnail(index, openSeq); - return; - } - if (observer) { - return; - } - if (index < 8) { + if (index === state.pageIndex || !observer && index < 8) { renderThumbnail(index, openSeq); } } @@ -5271,7 +5262,7 @@ function renderMeta(keepDetails = false) { renderMetaContent(el.attachmentList, [doc.attachments, doc.attachmentError], renderAttachments); renderMetaContent(el.signatureList, [doc.signatures, doc.signatureError], renderSignatures); renderMetaContent(el.docFontList, doc.fonts || [], renderDocumentFonts); - updateTextFonts(canvasEditor.selected, true); + refreshEditorFonts(); updateLocalFontButton(); } diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index f3cfe94..6d1b794 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -293,13 +293,16 @@ func configureDocument(args []js.Value) (any, error) { return currentSession.Summary(), nil } -// documentInfo 获取首屏之后加载的字体统计和验签结果 +// documentInfo 获取字体统计和验签结果,首个参数为true时逐页扫描 // 入参: args 浏览器参数 // 返回: any 文档信息, error 错误信息 func documentInfo(args []js.Value) (any, error) { if currentSession == nil { return nil, fmt.Errorf("ofd document is not opened") } + if len(args) > 0 && args[0].Bool() && !currentSession.scanFontInfo() { + return map[string]bool{"detailsPending": true}, nil + } return currentSession.Info(), nil } @@ -2731,7 +2734,14 @@ func editDocument(args []js.Value) (any, error) { return nil, err } editor.SetHistoryLimit(100) - return previewEditor(editor, currentSession.Renderer.RenderAnnotations) + if currentSession.fontFS != nil { + editor.SetFontFS(currentSession.fontFS) + } + currentEditor = editor + currentSession.editing = true + copiedObjects = nil + copiedStyle = nil + return editorSummary(), nil } // previewEditor 通过内存快照更新预览,不生成中间压缩包 diff --git a/internal/webui/webui.go b/internal/webui/webui.go index 1f73eef..886d602 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -98,18 +98,22 @@ type SignatureStampInfo struct { // Session WebUI文档会话 type Session struct { - Reader *ofdgo.Reader - Renderer *ofdgo.Renderer - fontFS *ofdgo.FontFS - doc *ofdgo.Document - pageCache map[int]*ofdgo.PageContent - boxCache map[int]ofdgo.Box - textCache map[int]*ofdgo.PageText - svgFonts map[string][]byte - signatures []SignatureInfo - signatureError error - signaturesRead bool - editing bool + Reader *ofdgo.Reader + Renderer *ofdgo.Renderer + fontFS *ofdgo.FontFS + doc *ofdgo.Document + pageCache map[int]*ofdgo.PageContent + boxCache map[int]ofdgo.Box + textCache map[int]*ofdgo.PageText + svgFonts map[string][]byte + fontScan *ofdgo.FontInfoScanner + fontInfos []FontInfo + fontsRead bool + fontAnnotations bool + signatures []SignatureInfo + signatureError error + signaturesRead bool + editing bool } // DocumentInfo 文档信息 @@ -282,9 +286,44 @@ func (s *Session) SetFonts(fonts []FontFile) error { } clear(s.textCache) clear(s.svgFonts) + s.resetFontInfo() return nil } +// resetFontInfo 清除字体统计及扫描状态,供字体或注解配置变更后重算 +func (s *Session) resetFontInfo() { + s.fontScan = nil + s.fontInfos = nil + s.fontsRead = false +} + +// scanFontInfo 分步统计字体,每次最多处理一页,完成后保留当前会话的诊断 +// 返回: bool 是否完成 +func (s *Session) scanFontInfo() bool { + if s.fontAnnotations != s.Renderer.RenderAnnotations { + s.resetFontInfo() + s.fontAnnotations = s.Renderer.RenderAnnotations + } + if s.fontsRead { + return true + } + if s.fontScan == nil { + var err error + s.fontScan, err = s.Renderer.ScanFontInfos() + if err != nil { + s.fontsRead = true + return true + } + } + if s.fontScan.Next() { + return false + } + s.fontInfos, _ = s.fontScan.Infos() + s.fontsRead = true + s.fontScan = nil + return true +} + // PageText 获取指定页面文字,复用当前会话的文字索引 // 入参: index 页面索引 // 返回: *ofdgo.PageText 页面文字, error 错误信息 @@ -429,9 +468,9 @@ func (s *Session) Info() DocumentInfo { } info.Pages = append(info.Pages, pageInfo) } - if fonts, err := s.Renderer.FontInfos(); err == nil { - info.Fonts = fonts + for !s.scanFontInfo() { } + info.Fonts = s.fontInfos info.FontCount = len(info.Fonts) return info } diff --git a/ofdgo_font_info.go b/ofdgo_font_info.go index 7d6e0ce..7892bac 100644 --- a/ofdgo_font_info.go +++ b/ofdgo_font_info.go @@ -149,11 +149,61 @@ func (r *Reader) embeddedFontFace(of Font) (*FontFace, error) { // FontInfos 获取OFD字体诊断信息 // 返回: []FontInfo 字体诊断列表, error 错误信息 func (r *Renderer) FontInfos() ([]FontInfo, error) { + scanner, err := r.ScanFontInfos() + if err != nil { + return nil, err + } + for scanner.Next() { + } + return scanner.Infos() +} + +// FontInfoScanner 逐页统计字体用量,不保留页面图元,扫描期间不得修改阅读器或渲染器配置 +type FontInfoScanner struct { + renderer *Renderer + doc *Document + index int + usage map[string]int +} + +// ScanFontInfos 创建分步字体诊断,页面读取失败的处理与FontInfos一致 +// 返回: *FontInfoScanner 字体诊断扫描器, error 错误信息 +func (r *Renderer) ScanFontInfos() (*FontInfoScanner, error) { doc, err := r.fontInfoDocument() if err != nil { return nil, err } - return r.fontInfos(doc, nil) + return &FontInfoScanner{renderer: r, doc: doc, usage: make(map[string]int)}, nil +} + +// Next 统计下一页或模板的字体用量 +// 返回: bool 是否处理了页面,false表示扫描结束 +func (s *FontInfoScanner) Next() bool { + pages := len(s.doc.Pages.Page) + if s.index < pages { + if counts, err := s.renderer.readPageFontUsage(s.doc.Pages.Page[s.index]); err == nil { + for id, count := range counts { + s.usage[id] += count + } + } + } else if index := s.index - pages; index < len(s.doc.CommonData.TemplatePage) { + if page := s.renderer.loadTemplate(s.doc.CommonData.TemplatePage[index].ID); page != nil { + s.renderer.countPageFonts(page, s.usage) + } + } else { + return false + } + s.index++ + return true +} + +// Infos 获取扫描结束后的字体诊断,不返回未完成的用量统计 +// 返回: []FontInfo 字体诊断列表, error 错误信息 +func (s *FontInfoScanner) Infos() ([]FontInfo, error) { + if s.index < len(s.doc.Pages.Page)+len(s.doc.CommonData.TemplatePage) { + return nil, fmt.Errorf("font info scan is not complete") + } + return s.renderer.fontInfos(s.usage) } // FontInfosFromPages 从页面内容获取OFD字体诊断信息 @@ -164,10 +214,7 @@ func (r *Renderer) FontInfosFromPages(pages []*PageContent) ([]FontInfo, error) if err != nil { return nil, err } - if pages == nil { - pages = []*PageContent{} - } - return r.fontInfos(doc, pages) + return r.fontInfos(r.fontUsage(doc, pages)) } // fontInfoDocument 获取字体诊断文档结构 @@ -187,10 +234,9 @@ func (r *Renderer) fontInfoDocument() (*Document, error) { } // fontInfos 获取OFD字体诊断信息 -// 入参: doc 文档结构, pages 页面内容列表 +// 入参: usage 字体使用次数 // 返回: []FontInfo 字体诊断列表, error 错误信息 -func (r *Renderer) fontInfos(doc *Document, pages []*PageContent) ([]FontInfo, error) { - usage := r.fontUsage(doc, pages) +func (r *Renderer) fontInfos(usage map[string]int) ([]FontInfo, error) { fonts, err := r.Reader.Fonts() if err != nil { return nil, err @@ -271,23 +317,13 @@ func (r *Renderer) fontInfo(font Font) FontInfo { } // fontUsage 统计文档字体使用次数 -// 入参: doc 文档结构, pages 页面内容列表, nil表示读取文档页面 +// 入参: doc 文档结构, pages 页面内容列表 // 返回: map[string]int 字体使用次数 func (r *Renderer) fontUsage(doc *Document, pages []*PageContent) map[string]int { usage := make(map[string]int) - if pages == nil { - for _, pageRef := range doc.Pages.Page { - if counts, err := r.readPageFontUsage(pageRef); err == nil { - for id, count := range counts { - usage[id] += count - } - } - } - } else { - for _, page := range pages { - if page != nil { - r.countPageFonts(page, usage) - } + for _, page := range pages { + if page != nil { + r.countPageFonts(page, usage) } } for _, tpl := range doc.CommonData.TemplatePage {