perf(性能提升): 优化编辑性能与字体缓存
build.yaml / build (push) Successful in 50s

This commit is contained in:
xiaoqidun committed 2026-09-30 11:44:15 +08:00
1 parent e891600510
commit 7b9b961196
9 files changed
+300 -203

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/jbig2 v1.1.1
github.com/xiaoqidun/pdfgo v0.0.0-20260929170129-585f0de1a090
github.com/xiaoqidun/pdfgo v0.0.0-20260930033808-2918854e21c8
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/text v0.42.0
+2 -2
View File
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260929170129-585f0de1a090 h1:G3uFJQPEm+eHQEdKIlyrabcK/TnF7N3xNiOKe/Ib1EQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260929170129-585f0de1a090/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260930033808-2918854e21c8 h1:19c9Wr9PPJ/GEh4AlrDqiUgucq1bYSIvughgsPKvNis=
github.com/xiaoqidun/pdfgo v0.0.0-20260930033808-2918854e21c8/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+9 -156
View File
@@ -1725,169 +1725,22 @@ func changeSelection(args []js.Value) (any, error) {
// 入参: page 页面, ids 标识, operation 操作, direction 方向
// 返回: error 错误信息
func arrangeEditorSelection(page int, ids []string, operation, direction string) error {
if operation == "align" && !slices.Contains([]string{"left", "center", "right", "top", "middle", "bottom"}, direction) || operation == "distribute" && direction != "horizontal" && direction != "vertical" {
return fmt.Errorf("invalid arrangement direction")
objects, annotations := splitEditorSelection(ids)
switch operation {
case "align":
return currentEditor.AlignSelection(page, objects, annotations, direction)
case "distribute":
return currentEditor.DistributeSelection(page, objects, annotations, direction)
default:
return fmt.Errorf("invalid arrangement operation %q", operation)
}
boxes := make([]ofdgo.Box, len(ids))
var target ofdgo.Box
for i, id := range ids {
objects, annotations := splitEditorSelection([]string{id})
box, err := editorSelectionBounds(page, objects, annotations)
if err != nil {
return err
}
boxes[i] = box
if i == 0 {
target = box
} else {
x, y := math.Min(target.X, box.X), math.Min(target.Y, box.Y)
target = ofdgo.Box{X: x, Y: y, W: math.Max(target.X+target.W, box.X+box.W) - x, H: math.Max(target.Y+target.H, box.Y+box.H) - y}
}
}
if len(ids) == 1 {
content, err := currentSession.pageContent(page)
if err != nil {
return err
}
target, err = currentSession.pageBox(page, content)
if err != nil {
return err
}
}
translations := make([]ofdgo.Point, len(ids))
if operation == "align" {
for i, box := range boxes {
switch direction {
case "left":
translations[i].X = target.X - box.X
case "center":
translations[i].X = target.X + (target.W-box.W)/2 - box.X
case "right":
translations[i].X = target.X + target.W - box.X - box.W
case "top":
translations[i].Y = target.Y - box.Y
case "middle":
translations[i].Y = target.Y + (target.H-box.H)/2 - box.Y
case "bottom":
translations[i].Y = target.Y + target.H - box.Y - box.H
}
}
} else {
if len(ids) < 3 {
return nil
}
order := make([]int, len(ids))
position := func(i int) float64 {
if direction == "vertical" {
return boxes[i].Y
}
return boxes[i].X
}
size := func(i int) float64 {
if direction == "vertical" {
return boxes[i].H
}
return boxes[i].W
}
for i := range order {
order[i] = i
}
slices.SortStableFunc(order, func(a, b int) int {
if position(a) < position(b) {
return -1
}
if position(a) > position(b) {
return 1
}
return 0
})
total := 0.0
for _, i := range order {
total += size(i)
}
first, last := order[0], order[len(order)-1]
gap := (position(last) + size(last) - position(first) - total) / float64(len(order)-1)
next := position(first)
for _, i := range order {
if direction == "vertical" {
translations[i].Y = next - position(i)
} else {
translations[i].X = next - position(i)
}
next += size(i) + gap
}
}
for i, id := range ids {
objects, annotations := splitEditorSelection([]string{id})
matrix := ofdgo.TranslationMatrix(translations[i].X, translations[i].Y)
if err := currentEditor.TransformObjectsMatrix(page, objects, matrix); err != nil {
return err
}
if err := currentEditor.TransformAnnotations(page, annotations, matrix); err != nil {
return err
}
}
return nil
}
// editorSelectionBounds 计算混合选区的实际内容范围
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
// 返回: ofdgo.Box 页面毫米范围, error 错误信息
func editorSelectionBounds(page int, objects, annotations []string) (ofdgo.Box, error) {
content, err := currentEditor.Page(page)
if err != nil {
return ofdgo.Box{}, err
}
var bounds ofdgo.Box
add := func(box ofdgo.Box) {
if box.W <= 0 || box.H <= 0 {
return
}
if bounds.W <= 0 || bounds.H <= 0 {
bounds = box
return
}
x, y := math.Min(bounds.X, box.X), math.Min(bounds.Y, box.Y)
bounds = ofdgo.Box{X: x, Y: y, W: math.Max(bounds.X+bounds.W, box.X+box.W) - x, H: math.Max(bounds.Y+bounds.H, box.Y+box.H) - y}
}
for _, layer := range content.Content.Layer {
for _, object := range layer.Objects {
var id string
switch object.Type {
case "TextObject":
id = object.TextObject.ID
case "PathObject":
id = object.PathObject.ID
case "ImageObject":
id = object.ImageObject.ID
case "CompositeObject", "CompositeGraphicUnit":
id = object.CompositeGraphicUnit.ID
}
if slices.Contains(objects, id) {
box, err := currentSession.Renderer.ObjectBounds(object, layer.DrawParam)
if err != nil {
return bounds, err
}
add(box)
}
}
}
for _, annotation := range currentSession.Reader.Annots[content.ID] {
if slices.Contains(annotations, annotation.ID) {
box, _, err := currentSession.Renderer.AnnotationGeometry(annotation)
if err != nil {
return bounds, err
}
if box.W <= 0 || box.H <= 0 {
box, _ = ofdgo.ParseBox(annotation.Appearance.Boundary)
}
add(box)
}
}
if bounds.W <= 0 || bounds.H <= 0 {
return bounds, fmt.Errorf("selection has no bounds")
}
return bounds, nil
return currentEditor.SelectionBounds(page, objects, annotations)
}
// captureEditorSelection 捕获正文与注解的独立副本
+55 -42
View File
@@ -52,12 +52,10 @@ func (e *Editor) Objects(page int, ids []string) ([]GraphicObject, error) {
}
slices.SortFunc(indexes, compareEditorPosition)
objects := make([]GraphicObject, len(indexes))
orderable := make(map[*editorXML]bool)
for i, index := range indexes {
object := e.pages[page].Content.Layer[index.layer].Objects[index.index]
capability, err := e.ObjectCapabilities(page, editorObjectID(object))
if err != nil {
return nil, err
}
capability := e.objectCapabilities(object, orderable)
if !capability.Copy {
return nil, fmt.Errorf("object %q cannot be copied: %w", editorObjectID(object), capability.editError())
}
@@ -208,17 +206,15 @@ func (e *Editor) OrderObjects(page int, ids []string, order string) error {
if !slices.Contains([]string{"up", "down", "top", "bottom"}, order) {
return fmt.Errorf("invalid object order %q", order)
}
_, indexes, err := e.selectedObjects(page, ids)
selectedObjects, indexes, err := e.selectedObjects(page, ids)
if err != nil || len(indexes) == 0 {
return err
}
layer := &e.pages[page].Content.Layer[indexes[0].layer]
_, siblings := e.objectOrderIndexes(page, layer, ids[0])
for i, id := range ids {
capability, err := e.ObjectCapabilities(page, id)
if err != nil {
return err
}
orderable := make(map[*editorXML]bool)
for i, object := range selectedObjects {
capability := e.objectCapabilities(object, orderable)
if !capability.Order || indexes[i].layer != indexes[0].layer || !slices.Contains(siblings, indexes[i].index) {
return fmt.Errorf("ordering requires editable objects in the same container")
}
@@ -349,17 +345,15 @@ func (e *Editor) AlignObjects(page int, ids []string, alignment string) error {
// 入参: page 页面索引, ids 对象标识
// 返回: error 错误信息
func (e *Editor) DeleteObjects(page int, ids []string) error {
_, indexes, err := e.selectedObjects(page, ids)
objects, indexes, err := e.selectedObjects(page, ids)
if err != nil || len(indexes) == 0 {
return err
}
for _, id := range ids {
capability, err := e.ObjectCapabilities(page, id)
if err != nil {
return err
}
orderable := make(map[*editorXML]bool)
for _, object := range objects {
capability := e.objectCapabilities(object, orderable)
if !capability.Delete {
return fmt.Errorf("object %q cannot be deleted: %w", id, capability.editError())
return fmt.Errorf("object %q cannot be deleted: %w", editorObjectID(object), capability.editError())
}
}
layers := copyEditorPage(e.pages[page]).Content.Layer
@@ -549,15 +543,13 @@ func (e *Editor) updateObjectOrigins(page int, objects []GraphicObject, preserve
}
after := make([]GraphicObject, len(objects))
var beforeOrigins, afterOrigins map[string]*editorObjectOrigin
orderable := make(map[*editorXML]bool)
for i, object := range objects {
object.origin = nil
preserved := preserveContent
origin := e.objectOrigin(ids[i])
if origin != nil {
capability, err := e.ObjectCapabilities(page, ids[i])
if err != nil {
return err
}
capability := e.objectCapabilities(before[i], orderable)
if !capability.Transform {
return fmt.Errorf("object %q is read-only for this operation: %w", ids[i], capability.editError())
}
@@ -664,28 +656,39 @@ func editorAlignment(box, target Box, alignment string) Matrix {
// 入参: page 页面索引, ids 对象标识,不得重复
// 返回: []GraphicObject 所选对象, []editorObjectPosition 图层和对象索引, error 错误信息
func (e *Editor) selectedObjects(page int, ids []string) ([]GraphicObject, []editorObjectPosition, error) {
if _, err := e.page(page); err != nil {
content, err := e.page(page)
if err != nil {
return nil, nil, err
}
objects := make([]GraphicObject, len(ids))
indexes := make([]editorObjectPosition, len(ids))
seen := make(map[string]bool, len(ids))
if len(ids) == 0 {
return objects, indexes, nil
}
selected := make(map[string]int, len(ids))
for i, id := range ids {
if seen[id] {
if _, exists := selected[id]; exists {
return nil, nil, fmt.Errorf("duplicate object %q", id)
}
seen[id] = true
layer, index, err := e.findObject(page, id)
if err != nil {
return nil, nil, err
}
for j := range e.pages[page].Content.Layer {
if &e.pages[page].Content.Layer[j] == layer {
indexes[i] = editorObjectPosition{j, index}
break
selected[id] = i
}
for i, layer := range content.Content.Layer {
for j, object := range layer.Objects {
id := editorObjectID(object)
if index, ok := selected[id]; ok && id != "" {
objects[index] = object
indexes[index] = editorObjectPosition{i, j}
delete(selected, id)
if len(selected) == 0 {
return objects, indexes, nil
}
}
}
objects[i] = layer.Objects[index]
}
for _, id := range ids {
if _, missing := selected[id]; missing {
return nil, nil, fmt.Errorf("object %q not found on page %d", id, page)
}
}
return objects, indexes, nil
}
@@ -711,11 +714,9 @@ func editorObjectID(object GraphicObject) string {
// 入参: page 页面索引, objects 待度量的对象
// 返回: []Box 对象范围, error 错误信息
func (e *Editor) objectBounds(page int, objects []GraphicObject) ([]Box, error) {
orderable := make(map[*editorXML]bool)
for _, object := range objects {
capability, err := e.ObjectCapabilities(page, editorObjectID(object))
if err != nil {
return nil, err
}
capability := e.objectCapabilities(object, orderable)
if !capability.Arrange {
return nil, fmt.Errorf("object %q cannot be arranged: %w", editorObjectID(object), capability.editError())
}
@@ -729,13 +730,25 @@ func (e *Editor) objectBounds(page int, objects []GraphicObject) ([]Box, error)
return nil, err
}
renderer := e.newRenderer(reader)
objectLayers := make(map[string]int, len(objects))
for _, object := range objects {
objectLayers[editorObjectID(object)] = -1
}
for i, layer := range e.pages[page].Content.Layer {
for _, object := range layer.Objects {
id := editorObjectID(object)
if index, wanted := objectLayers[id]; wanted && index < 0 {
objectLayers[id] = i
}
}
}
boxes := make([]Box, len(objects))
for i, object := range objects {
layer, _, findErr := e.findObject(page, editorObjectID(object))
if findErr != nil {
return nil, findErr
layer := objectLayers[editorObjectID(object)]
if layer < 0 {
return nil, fmt.Errorf("object %q not found on page %d", editorObjectID(object), page)
}
boxes[i], err = renderer.ObjectBounds(object, layer.DrawParam)
boxes[i], err = renderer.ObjectBounds(object, e.pages[page].Content.Layer[layer].DrawParam)
if err != nil {
return nil, err
}
+179
View File
@@ -0,0 +1,179 @@
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"fmt"
"slices"
)
// SelectionBounds 获取正文与注解混合选区的实际范围,复用同一次度量的资源缓存
// 入参: page 页面索引, objects 正文标识, annotations 注解标识,各列表不得重复
// 返回: Box 页面毫米范围, error 标识或度量错误
func (e *Editor) SelectionBounds(page int, objects, annotations []string) (Box, error) {
boxes, _, _, err := e.selectionGeometry(page, objects, annotations)
if err != nil {
return Box{}, err
}
var bounds Box
for _, box := range boxes {
bounds = unionTextBox(bounds, box)
}
if bounds.W <= 0 || bounds.H <= 0 {
return Box{}, fmt.Errorf("selection has no visible bounds")
}
return bounds, nil
}
// AlignSelection 将正文与注解统一对齐,单选对齐页面,多选对齐选区,一次撤销恢复全部
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, alignment 为left、center、right、top、middle或bottom
// 返回: error 标识、度量或编辑错误
func (e *Editor) AlignSelection(page int, objects, annotations []string, alignment string) error {
if !slices.Contains([]string{"left", "center", "right", "top", "middle", "bottom"}, alignment) {
return fmt.Errorf("invalid alignment %q", alignment)
}
boxes, _, target, err := e.selectionGeometry(page, objects, annotations)
if err != nil || len(boxes) == 0 {
return err
}
if len(boxes) > 1 {
target = Box{}
for _, box := range boxes {
target = unionTextBox(target, box)
}
}
matrices := make([]Matrix, len(boxes))
for i, box := range boxes {
matrices[i] = editorAlignment(box, target, alignment)
}
return e.transformSelection(page, objects, annotations, matrices)
}
// DistributeSelection 按正文与注解实际范围等距分布,固定两端并校验位置顺序,一次撤销恢复全部
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, axis 为horizontal或vertical
// 返回: error 标识、度量、重叠或编辑错误
func (e *Editor) DistributeSelection(page int, objects, annotations []string, axis string) error {
if axis != "horizontal" && axis != "vertical" {
return fmt.Errorf("invalid distribution axis %q", axis)
}
boxes, positions, _, err := e.selectionGeometry(page, objects, annotations)
if err != nil || len(boxes) < 3 {
return err
}
matrices, err := editorDistribution(boxes, positions, axis)
if err != nil {
return err
}
return e.transformSelection(page, objects, annotations, matrices)
}
// selectionGeometry 一次读取页面与资源,按正文、注解标识顺序度量并保留绘制位置
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
// 返回: []Box 成员范围, []editorObjectPosition 绘制位置, Box 页面范围, error 标识或度量错误
func (e *Editor) selectionGeometry(page int, objects, annotations []string) ([]Box, []editorObjectPosition, Box, error) {
items, positions, err := e.selectedObjects(page, objects)
if err != nil {
return nil, nil, Box{}, err
}
reader, err := e.Reader()
if err != nil {
return nil, nil, Box{}, err
}
defer reader.Close()
content, err := reader.PageContentByIndex(page)
if err != nil {
return nil, nil, Box{}, err
}
renderer := e.newRenderer(reader)
paper, err := renderer.GetPageBox(content)
if err != nil {
return nil, nil, Box{}, err
}
boxes := make([]Box, 0, len(items)+len(annotations))
for i, item := range items {
box, err := renderer.ObjectBounds(item, e.pages[page].Content.Layer[positions[i].layer].DrawParam)
if err != nil {
return nil, nil, Box{}, err
}
boxes = append(boxes, box)
}
list := reader.Annots[content.ID]
lookup := make(map[string]int, len(list))
for i, annotation := range list {
lookup[annotation.ID] = i
}
seen := make(map[string]bool, len(annotations))
for _, id := range annotations {
index, found := lookup[id]
if seen[id] || !found {
return nil, nil, Box{}, fmt.Errorf("duplicate or missing annotation %q", id)
}
seen[id] = true
box, _, err := renderer.AnnotationGeometry(list[index])
if err != nil {
return nil, nil, Box{}, err
}
if box.W <= 0 || box.H <= 0 {
box, err = ParseBox(list[index].Appearance.Boundary)
if err != nil {
return nil, nil, Box{}, err
}
}
boxes = append(boxes, box)
positions = append(positions, editorObjectPosition{layer: len(content.Content.Layer), index: index})
}
for _, box := range boxes {
if !finite(box.X) || !finite(box.Y) || !finite(box.W) || !finite(box.H) || box.W <= 0 || box.H <= 0 {
return nil, nil, Box{}, fmt.Errorf("selection member has no visible bounds")
}
}
return boxes, positions, paper, nil
}
// transformSelection 原子应用成员平移,跳过未改变的成员及历史记录
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, matrices 按正文、注解排列的平移
// 返回: error 编辑错误
func (e *Editor) transformSelection(page int, objects, annotations []string, matrices []Matrix) error {
return e.Transaction(func(edit *Editor) error {
items, _, err := edit.selectedObjects(page, objects)
if err != nil {
return err
}
changes := make(map[string]Matrix, len(objects))
var updates []GraphicObject
for i, id := range objects {
if matrices[i] != IdentityMatrix {
changes[id] = matrices[i]
updates = append(updates, items[i])
}
}
if len(updates) != 0 {
if err := edit.transformObjects(page, updates, func(object GraphicObject) (GraphicObject, error) {
return edit.transformMatrix(object, changes[editorObjectID(object)])
}); err != nil {
return err
}
}
for i, id := range annotations {
matrix := matrices[len(objects)+i]
if matrix != IdentityMatrix {
if err := edit.MoveAnnotations(page, []string{id}, matrix.e, matrix.f); err != nil {
return err
}
}
}
return nil
})
}
+16 -1
View File
@@ -14,7 +14,10 @@
package ofdgo
import "fmt"
import (
"crypto/sha256"
"fmt"
)
// FontMetrics 提供不依赖绘图库的字体度量,所有整数尺寸使用字体设计单位
// VerticalMetrics依次返回非负的上升高度、下降高度和行间隙
@@ -141,6 +144,7 @@ func (r *Renderer) ResolveFont(id string, exact bool) (ResolvedFont, error) {
}
// PrepareFont 统一处理内嵌字体包装和索引映射,供各编译器复用
// 子渲染器按原始内容复用只读包装结果,各文档保留独立的来源信息
// 入参: id 字体资源标识
// 返回: *PreparedFont 绘制字体, error 字体解析错误
func (r *Renderer) PrepareFont(id string) (*PreparedFont, error) {
@@ -154,6 +158,12 @@ func (r *Renderer) PrepareFont(id string) (*PreparedFont, error) {
prepared := &PreparedFont{ResolvedFont: resolved}
definition := r.Reader.fontCache[id]
if definition != nil && definition.FontFile != "" {
key := sha256.Sum256(resolved.Data)
if content, ok := r.fontPreparations.get(key); ok {
prepared.Data, prepared.Glyphs, prepared.CIDs, prepared.digest = content.Data, content.Glyphs, content.CIDs, content.digest
r.preparedFonts[id] = prepared
return prepared, nil
}
prepared.CIDs = getCFFCIDRuneMap(resolved.Data)
if _, data, mapping, _, err := FixFontDataAggressive(resolved.Data, true, true); err == nil {
prepared.Data = data
@@ -171,6 +181,9 @@ func (r *Renderer) PrepareFont(id string) (*PreparedFont, error) {
}
}
}
prepared.digest = sha256.Sum256(prepared.Data)
cost := min(len(prepared.Data), r.fontPreparations.limit)*2 + (len(prepared.Glyphs)+len(prepared.CIDs))*48 + 256
r.fontPreparations.put(key, PreparedFont{ResolvedFont: ResolvedFont{Data: prepared.Data}, Glyphs: prepared.Glyphs, CIDs: prepared.CIDs, digest: prepared.digest}, cost)
}
r.preparedFonts[id] = prepared
return prepared, nil
@@ -184,7 +197,9 @@ func (r *Renderer) resetFontCache() {
// resetFontBackendCache 清理后端字体对象,保留字体文件和图片缓存
func (r *Renderer) resetFontBackendCache() {
r.sharedBackendStates = make(map[any]any)
r.preparedFonts = make(map[string]*PreparedFont)
r.fontPreparations = &renderCache[[32]byte, PreparedFont]{limit: 16 << 20}
r.resolvedFonts = make(map[resolvedFontKey]resolvedFontResult)
if r.fontSourcesCache != nil {
r.fontSourcesCache.fallback = ResolvedFont{}
+4
View File
@@ -46,8 +46,10 @@ type Renderer struct {
textOnly bool
backends RenderBackends
backendStates map[any]any
sharedBackendStates map[any]any
resolvedFonts map[resolvedFontKey]resolvedFontResult
preparedFonts map[string]*PreparedFont
fontPreparations *renderCache[[32]byte, PreparedFont]
fontSourcesCache *fontSourceCache
fontMetrics renderCache[[32]byte, FontMetrics]
glyphOutlines renderCache[glyphOutlineKey, GeometryPath]
@@ -96,6 +98,8 @@ func (r *Renderer) childRenderer(reader *Reader) *Renderer {
opts = append(opts, WithFontFS(r.fontFS...))
}
renderer := NewRenderer(reader, opts...)
renderer.sharedBackendStates = r.sharedBackendStates
renderer.fontPreparations = r.fontPreparations
renderer.decodeImages = r.decodeImages
renderer.TransparentBackground = r.TransparentBackground
return renderer
+15 -1
View File
@@ -14,7 +14,11 @@
package ofdgo
import "github.com/tdewolff/canvas"
import (
"crypto/sha256"
"github.com/tdewolff/canvas"
)
// loadFont 加载字体
// 入参: fontID 字体ID
@@ -37,12 +41,22 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
}
fontData := resolved.Data
fontStyle := canvasFontStyle(of)
if resolved.digest == ([32]byte{}) {
resolved.digest = sha256.Sum256(fontData)
}
key := canvasFontKey{digest: resolved.digest, name: of.FontName, style: fontStyle}
cache := r.canvasFontFamilies()
if ff, ok := cache.get(key); ok {
r.canvasState().fontMap[fontID] = ff
return ff
}
ff := canvas.NewFontFamily(of.FontName)
if err := ff.LoadFont(fontData, 0, fontStyle); err != nil {
r.renderError = err
return nil
}
r.canvasState().fontMap[fontID] = ff
cache.put(key, ff, min(len(fontData), cache.limit)*8+256)
return ff
}
+19
View File
@@ -20,6 +20,13 @@ import (
"github.com/tdewolff/canvas"
)
// canvasFontKey 按字体内容、名称及样式区分可复用的字体族,不依赖文档资源编号
type canvasFontKey struct {
digest [32]byte
name string
style canvas.FontStyle
}
// canvasBackendState 保存Canvas字体、字形和编码图片适配缓存
type canvasBackendState struct {
images renderCache[*EncodedImage, image.Image]
@@ -29,6 +36,18 @@ type canvasBackendState struct {
fontOutlines map[*canvas.Font]*sfntOutliner
}
// canvasFontFamilies 获取子渲染器共享的只读字体族缓存,限制解析数据保留量
// 返回: *renderCache[canvasFontKey, *canvas.FontFamily] 字体族缓存
func (r *Renderer) canvasFontFamilies() *renderCache[canvasFontKey, *canvas.FontFamily] {
key := CanvasBackend{}
if state, ok := r.sharedBackendStates[key]; ok {
return state.(*renderCache[canvasFontKey, *canvas.FontFamily])
}
cache := &renderCache[canvasFontKey, *canvas.FontFamily]{limit: 32 << 20}
r.sharedBackendStates[key] = cache
return cache
}
// canvasState 获取当前渲染器的Canvas私有缓存,按需创建且不向其他后端暴露
// 返回: *canvasBackendState Canvas缓存
func (r *Renderer) canvasState() *canvasBackendState {