mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
This commit is contained in:
1 parent
3ba9317cf3
commit
81e6657c65
8 files changed
+323
-66
No files matched your search
@@ -5714,7 +5714,7 @@ function layoutThumbnails() {
|
||||
}
|
||||
view.offsets = offsets;
|
||||
view.gap = 12;
|
||||
view.total = Math.max(0, offsets.at(-1) - 12);
|
||||
view.total = Math.ceil(offsets.at(-1));
|
||||
for (const [index, button] of view.nodes) {
|
||||
layoutThumbnail(button, state.doc.pages[index]);
|
||||
button.style.height = `${offsets[index + 1] - offsets[index] - 12}px`;
|
||||
|
||||
+43
-12
@@ -20,6 +20,7 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrBackendUnavailable 表示未配置所需后端,不会自动切换其他实现
|
||||
@@ -105,6 +106,7 @@ type EPSBackend interface {
|
||||
// RenderBackends 按职责组合后端,nil表示明确禁用,不隐式回退
|
||||
// 实例及其Renderer需串行使用,第三方实现无需引用任何内置绘图库
|
||||
type RenderBackends struct {
|
||||
Resources FontResourceBackend
|
||||
Fonts FontBackend
|
||||
Geometry GeometryBackend
|
||||
Compiler PageCompiler
|
||||
@@ -116,6 +118,7 @@ type RenderBackends struct {
|
||||
|
||||
// BackendInfo 描述各项能力实际使用的后端,空标识表示未配置
|
||||
type BackendInfo struct {
|
||||
Resources string `json:"resources"`
|
||||
Fonts string `json:"fonts"`
|
||||
Geometry string `json:"geometry"`
|
||||
Compiler string `json:"compiler"`
|
||||
@@ -125,33 +128,61 @@ type BackendInfo struct {
|
||||
EPS string `json:"eps"`
|
||||
}
|
||||
|
||||
// NewRenderBackends 创建内置后端组合,各项能力通过Info报告实际提供者
|
||||
// 入参: name 内置后端标识
|
||||
// NewRenderBackends 创建已注册后端组合,各项能力通过Info报告实际提供者
|
||||
// 入参: name 已注册后端标识
|
||||
// 返回: RenderBackends 后端组合, error 未知后端错误
|
||||
func NewRenderBackends(name string) (RenderBackends, error) {
|
||||
if name != "canvas" {
|
||||
backendRegistry.RLock()
|
||||
factory := backendRegistry.factories[name]
|
||||
backendRegistry.RUnlock()
|
||||
if factory == nil {
|
||||
return RenderBackends{}, fmt.Errorf("unknown render backend %q", name)
|
||||
}
|
||||
return defaultRenderBackends(), nil
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
// RenderBackendInfos 列出内置组合及其实际能力,供原生程序和WASM共用
|
||||
// RenderBackendInfos 列出已注册组合及其实际能力,供原生程序和WASM共用
|
||||
// 返回: map[string]BackendInfo 后端能力
|
||||
func RenderBackendInfos() map[string]BackendInfo {
|
||||
return map[string]BackendInfo{"canvas": defaultRenderBackends().Info()}
|
||||
backendRegistry.RLock()
|
||||
factories := make(map[string]func() RenderBackends, len(backendRegistry.factories))
|
||||
for name, factory := range backendRegistry.factories {
|
||||
factories[name] = factory
|
||||
}
|
||||
backendRegistry.RUnlock()
|
||||
result := make(map[string]BackendInfo, len(factories))
|
||||
for name, factory := range factories {
|
||||
result[name] = factory().Info()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// defaultRenderBackends 创建独立的默认配置
|
||||
// 返回: RenderBackends 默认后端组合
|
||||
func defaultRenderBackends() RenderBackends {
|
||||
backend := CanvasBackend{}
|
||||
return RenderBackends{Fonts: backend, Geometry: backend, Compiler: backend, Raster: backend, SVG: backend, PDF: backend, EPS: backend}
|
||||
// backendRegistry 保存后端工厂,不共享有状态的后端实例
|
||||
var backendRegistry = struct {
|
||||
sync.RWMutex
|
||||
factories map[string]func() RenderBackends
|
||||
}{factories: make(map[string]func() RenderBackends)}
|
||||
|
||||
// RegisterRenderBackend 注册后端组合,工厂应返回独立实例,重复标识不会覆盖已有实现
|
||||
// 入参: name 后端标识, factory 后端工厂
|
||||
// 返回: error 无效或重复注册错误
|
||||
func RegisterRenderBackend(name string, factory func() RenderBackends) error {
|
||||
if name == "" || factory == nil {
|
||||
return fmt.Errorf("backend name and factory are required")
|
||||
}
|
||||
backendRegistry.Lock()
|
||||
defer backendRegistry.Unlock()
|
||||
if _, exists := backendRegistry.factories[name]; exists {
|
||||
return fmt.Errorf("render backend %q already registered", name)
|
||||
}
|
||||
backendRegistry.factories[name] = factory
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info 返回配置中各能力的提供者,不依赖WebUI推断
|
||||
// 返回: BackendInfo 后端能力
|
||||
func (b RenderBackends) Info() BackendInfo {
|
||||
return BackendInfo{Fonts: backendName(b.Fonts), Geometry: backendName(b.Geometry), Compiler: backendName(b.Compiler), Raster: backendName(b.Raster), SVG: backendName(b.SVG), PDF: backendName(b.PDF), EPS: backendName(b.EPS)}
|
||||
return BackendInfo{Resources: backendName(b.Resources), Fonts: backendName(b.Fonts), Geometry: backendName(b.Geometry), Compiler: backendName(b.Compiler), Raster: backendName(b.Raster), SVG: backendName(b.SVG), PDF: backendName(b.PDF), EPS: backendName(b.EPS)}
|
||||
}
|
||||
|
||||
// backendName 获取已配置的后端标识
|
||||
|
||||
+19
-45
@@ -31,7 +31,6 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tdewolff/font"
|
||||
"github.com/xiaoqidun/jbig2"
|
||||
)
|
||||
|
||||
@@ -45,7 +44,7 @@ type Editor struct {
|
||||
Info DocInfo
|
||||
pages []PageContent
|
||||
resources []editorResource
|
||||
fonts map[string]*font.SFNT
|
||||
fonts map[string]*FontResource
|
||||
fontFS []fs.FS
|
||||
fontRenderer *Renderer
|
||||
fontMetrics map[string]FontMetrics
|
||||
@@ -86,6 +85,12 @@ func (e *Editor) SetPageCompiler(compiler PageCompiler) {
|
||||
// 入参: backends 后端组合,未配置的能力不会沿用默认实现
|
||||
func (e *Editor) SetRenderBackends(backends RenderBackends) {
|
||||
fontsChanged := !sameBackend(e.backends.Fonts, backends.Fonts)
|
||||
if !sameBackend(e.backends.Resources, backends.Resources) {
|
||||
e.fonts = make(map[string]*FontResource)
|
||||
for i := range e.resources {
|
||||
e.resources[i].subset = nil
|
||||
}
|
||||
}
|
||||
e.backends = backends
|
||||
if e.fontRenderer != nil {
|
||||
WithRenderBackends(backends)(e.fontRenderer)
|
||||
@@ -150,7 +155,7 @@ func NewEditor() *Editor {
|
||||
DocID: hex.EncodeToString(id[:]),
|
||||
CreationDate: time.Now().Format("2006-01-02"),
|
||||
},
|
||||
fonts: make(map[string]*font.SFNT),
|
||||
fonts: make(map[string]*FontResource),
|
||||
images: make(map[string]image.Point),
|
||||
resourceID: make(map[editorResourceKey]string),
|
||||
fontMetrics: make(map[string]FontMetrics),
|
||||
@@ -564,67 +569,36 @@ func (e *Editor) nextID() string {
|
||||
// 入参: file 字体文件, index 集合内字体索引,单字体为0
|
||||
// 返回: string 字体资源标识, error 错误信息
|
||||
func (e *Editor) AddFont(file FontFile, index int) (string, error) {
|
||||
data, err := font.ToSFNT(file.Data)
|
||||
if e.backends.Resources == nil {
|
||||
return "", fmt.Errorf("font resources: %w", ErrBackendUnavailable)
|
||||
}
|
||||
parsed, err := e.backends.Resources.OpenFontResource(file, index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key := editorResourceKey{checksum: sha256.Sum256(data), index: index}
|
||||
data := parsed.Data
|
||||
key := editorResourceKey{checksum: sha256.Sum256(data)}
|
||||
if id, ok := e.resourceID[key]; ok {
|
||||
return id, nil
|
||||
}
|
||||
data, err = (FontFile{Data: data}).Face(index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sfnt, err := font.ParseSFNT(data, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := editorFontName(sfnt, font.NamePostScript, font.NameFull, font.NameFontFamily)
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("font has no name")
|
||||
}
|
||||
if err := e.prepareSourceIDs(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
id := e.nextID()
|
||||
extension := ".ttf"
|
||||
if sfnt.IsCFF {
|
||||
extension = ".otf"
|
||||
}
|
||||
definition := parsed.Font
|
||||
definition.ID = id
|
||||
resource := editorResource{
|
||||
name: e.packageName("Res/Fonts/Font_" + id + extension),
|
||||
name: e.packageName("Res/Fonts/Font_" + id + parsed.Extension),
|
||||
data: data,
|
||||
font: &Font{
|
||||
ID: id, FontName: name,
|
||||
FamilyName: editorFontName(sfnt, font.NamePreferredFamily, font.NameFontFamily),
|
||||
Charset: "unicode",
|
||||
Bold: sfnt.Head.MacStyle[0],
|
||||
Italic: sfnt.Head.MacStyle[1],
|
||||
FixedWidth: sfnt.Post.IsFixedPitch != 0,
|
||||
},
|
||||
font: &definition,
|
||||
}
|
||||
resource.font.FontFile = "/" + resource.name
|
||||
e.resources = append(e.resources, resource)
|
||||
e.fonts[id] = sfnt
|
||||
e.fonts[id] = parsed
|
||||
e.resourceID[key] = id
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// editorFontName 获取字体中的名称
|
||||
// 入参: sfnt 字体, names 名称类型,按顺序查找
|
||||
// 返回: string 字体名称
|
||||
func editorFontName(sfnt *font.SFNT, names ...font.NameID) string {
|
||||
for _, name := range names {
|
||||
for _, record := range sfnt.Name.Get(name) {
|
||||
if value := record.String(); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AddImage 注册PNG或JPEG图片,重复资源复用标识,引用后写入文档
|
||||
// 不透明纯黑白PNG仅在无损JBIG2编码更小时转换,其他图片保留原始编码
|
||||
// 入参: data 图片数据
|
||||
|
||||
+13
-2
@@ -187,6 +187,9 @@ type editorFontSubset struct {
|
||||
// 入参: progress 保存进度回调
|
||||
// 返回: map[string][]byte 包内字体子集, error 错误信息
|
||||
func (e *Editor) subsetFonts(progress editorProgress) (map[string][]byte, error) {
|
||||
if e.backends.Resources == nil {
|
||||
return nil, nil
|
||||
}
|
||||
used := make(map[string]map[uint16]bool)
|
||||
mapped := make(map[string]bool)
|
||||
for _, resource := range e.resources {
|
||||
@@ -194,7 +197,15 @@ func (e *Editor) subsetFonts(progress editorProgress) (map[string][]byte, error)
|
||||
continue
|
||||
}
|
||||
sfnt := e.fonts[resource.font.ID]
|
||||
if sfnt.IsTrueType && !slices.ContainsFunc([]string{"fvar", "COLR", "CBDT", "sbix", "SVG "}, func(tag string) bool { return sfnt.Tables[tag] != nil }) {
|
||||
if sfnt == nil {
|
||||
var err error
|
||||
sfnt, err = e.backends.Resources.OpenFontResource(FontFile{Data: resource.data}, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.fonts[resource.font.ID] = sfnt
|
||||
}
|
||||
if sfnt.CanSubset {
|
||||
used[resource.font.ID] = make(map[uint16]bool)
|
||||
}
|
||||
}
|
||||
@@ -309,7 +320,7 @@ func (e *Editor) subsetFonts(progress editorProgress) (map[string][]byte, error)
|
||||
}
|
||||
slices.Sort(ids)
|
||||
if resource.subset == nil || resource.subset.mapped != mapped[resource.font.ID] || !slices.Equal(resource.subset.glyphs, ids) {
|
||||
data, err := subsetEditorFont(resource.data, ids, mapped[resource.font.ID])
|
||||
data, err := e.backends.Resources.SubsetFont(resource.data, ids, mapped[resource.font.ID])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subset font %s: %w", resource.font.FontName, err)
|
||||
}
|
||||
|
||||
+95
-4
@@ -441,9 +441,6 @@ func (e *Editor) sourceInfoXML() ([]byte, error) {
|
||||
return nil, fmt.Errorf("document has no DocInfo")
|
||||
}
|
||||
before, after := e.source.info, e.Info
|
||||
if !reflect.DeepEqual(before.CustomDatas, after.CustomDatas) {
|
||||
return nil, fmt.Errorf("editing imported custom metadata is not supported")
|
||||
}
|
||||
var values [][2]string
|
||||
for _, field := range [][3]string{
|
||||
{"DocID", before.DocID, after.DocID}, {"Title", before.Title, after.Title}, {"Author", before.Author, after.Author},
|
||||
@@ -454,7 +451,101 @@ func (e *Editor) sourceInfoXML() ([]byte, error) {
|
||||
values = append(values, [2]string{field[0], field[2]})
|
||||
}
|
||||
}
|
||||
return editorXMLSetText(data, info, values), nil
|
||||
data = editorXMLSetText(data, info, values)
|
||||
if !reflect.DeepEqual(before.CustomDatas, after.CustomDatas) {
|
||||
root, err = parseEditorXML(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return editorCustomDatas(data, root.child("DocBody").child("DocInfo"), after.CustomDatas)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// editorCustomDatas 更新自定义字段,保留容器扩展与同名字段的未知属性和子节点
|
||||
// 入参: data 原XML, info 文档信息节点, values 新字段
|
||||
// 返回: []byte 修改后的XML, error 编码错误
|
||||
func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byte, error) {
|
||||
container := info.child("CustomDatas")
|
||||
var items []CustomData
|
||||
if values != nil {
|
||||
items = values.CustomData
|
||||
}
|
||||
byName := make(map[string][]*editorXML)
|
||||
var nodes []*editorXML
|
||||
if container != nil {
|
||||
for _, node := range container.children {
|
||||
if node.name.Local == "CustomData" && (node.name.Space == ofdNamespace || node.name.Space == container.name.Space) {
|
||||
nodes = append(nodes, node)
|
||||
byName[node.attr("Name")] = append(byName[node.attr("Name")], node)
|
||||
}
|
||||
}
|
||||
}
|
||||
var encoded [][]byte
|
||||
for _, item := range items {
|
||||
matches := byName[item.Name]
|
||||
if len(matches) == 0 {
|
||||
entry, err := xml.Marshal(struct {
|
||||
XMLName xml.Name
|
||||
CustomData
|
||||
}{xml.Name{Space: ofdNamespace, Local: "CustomData"}, item})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded = append(encoded, entry)
|
||||
continue
|
||||
}
|
||||
node := matches[0]
|
||||
byName[item.Name] = matches[1:]
|
||||
var original CustomData
|
||||
if err := xml.Unmarshal(data[node.start:node.end], &original); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if original.Value == item.Value {
|
||||
encoded = append(encoded, data[node.start:node.end])
|
||||
continue
|
||||
}
|
||||
var content bytes.Buffer
|
||||
if err := xml.EscapeText(&content, []byte(item.Value)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, child := range node.children {
|
||||
content.Write(data[child.start:child.end])
|
||||
}
|
||||
patch := editorXMLContent(data, node, content.Bytes())
|
||||
entry := append(bytes.Clone(data[node.start:patch.start]), patch.data...)
|
||||
entry = append(entry, data[patch.end:node.end]...)
|
||||
encoded = append(encoded, entry)
|
||||
}
|
||||
if container == nil {
|
||||
if len(encoded) == 0 {
|
||||
return data, nil
|
||||
}
|
||||
content := []byte("<ofd:CustomDatas xmlns:ofd=\"" + ofdNamespace + "\">")
|
||||
content = append(content, bytes.Join(encoded, nil)...)
|
||||
content = append(content, []byte("</ofd:CustomDatas>")...)
|
||||
if info.open == info.end {
|
||||
return editorPatchXML(data, []editorXMLPatch{editorXMLContent(data, info, content)}), nil
|
||||
}
|
||||
return editorPatchXML(data, []editorXMLPatch{{info.close, info.close, content}}), nil
|
||||
}
|
||||
var patches []editorXMLPatch
|
||||
for i, node := range nodes {
|
||||
var entry []byte
|
||||
if i < len(encoded) {
|
||||
entry = encoded[i]
|
||||
}
|
||||
patches = append(patches, editorXMLPatch{node.start, node.end, entry})
|
||||
}
|
||||
if len(encoded) > len(nodes) {
|
||||
added := bytes.Join(encoded[len(nodes):], nil)
|
||||
if container.open == container.end {
|
||||
patches = append(patches, editorXMLContent(data, container, added))
|
||||
} else {
|
||||
patches = append(patches, editorXMLPatch{container.close, container.close, added})
|
||||
}
|
||||
}
|
||||
return editorPatchXML(data, patches), nil
|
||||
}
|
||||
|
||||
// writeSource 将未修改ZIP条目直接复制到新包,逐项写入改动,不持有原资源解压副本
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"path"
|
||||
@@ -172,14 +173,18 @@ func (e *Editor) compactSourceResources(parts map[string][]byte, progress editor
|
||||
}
|
||||
key := strings.ToLower(cleanPackagePath(name))
|
||||
usage := fontFiles[key]
|
||||
if usage == nil || usage.unsafe || refs.files[key] || removed[key] || parts[name] != nil {
|
||||
if e.backends.Resources == nil || usage == nil || usage.unsafe || refs.files[key] || removed[key] || parts[name] != nil {
|
||||
continue
|
||||
}
|
||||
data, err := reader.readFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if subset := subsetSourceFont(data, usage); subset != nil {
|
||||
subset, err := e.backends.Resources.SubsetSourceFont(data, FontUsage{Characters: slices.Sorted(maps.Keys(usage.chars)), Glyphs: slices.Sorted(maps.Keys(usage.glyphs)), Unsafe: usage.unsafe})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subset font %s: %w", name, err)
|
||||
}
|
||||
if subset != nil {
|
||||
updates[name] = subset
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// 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"
|
||||
|
||||
"github.com/tdewolff/font"
|
||||
)
|
||||
|
||||
// FontResource 保存独立字体数据、标准元信息与只读度量,不包含绘图库类型
|
||||
type FontResource struct {
|
||||
FontMetrics
|
||||
Data []byte
|
||||
Font Font
|
||||
Extension string
|
||||
CanSubset bool
|
||||
}
|
||||
|
||||
// FontUsage 描述全包已确认的字体引用,Unsafe禁止裁剪
|
||||
type FontUsage struct {
|
||||
Characters []rune
|
||||
Glyphs []uint16
|
||||
Unsafe bool
|
||||
}
|
||||
|
||||
// FontResourceBackend 解析和裁剪保存资源,独立于显示字体后端
|
||||
// SubsetFont的preserve为true时不得改变字形编号,SubsetSourceFont无安全收益时返回nil
|
||||
type FontResourceBackend interface {
|
||||
Backend
|
||||
OpenFontResource(file FontFile, index int) (*FontResource, error)
|
||||
SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error)
|
||||
SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error)
|
||||
}
|
||||
|
||||
// SFNTBackend 提供默认字体资源处理,不依赖Canvas绘图库
|
||||
type SFNTBackend struct{}
|
||||
|
||||
// Name 返回资源后端标识
|
||||
// 返回: string 后端标识
|
||||
func (SFNTBackend) Name() string { return "sfnt" }
|
||||
|
||||
// OpenFontResource 提取独立字体并读取名称、样式和裁剪能力
|
||||
// 入参: file 字体文件, index 集合索引
|
||||
// 返回: *FontResource 字体资源, error 解析错误
|
||||
func (SFNTBackend) OpenFontResource(file FontFile, index int) (*FontResource, error) {
|
||||
data, err := file.Face(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sfnt, err := font.ParseSFNT(data, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := editorFontName(sfnt, font.NamePostScript, font.NameFull, font.NameFontFamily)
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("font has no name")
|
||||
}
|
||||
extension := ".ttf"
|
||||
if sfnt.IsCFF {
|
||||
extension = ".otf"
|
||||
}
|
||||
return &FontResource{
|
||||
FontMetrics: sfntResourceMetrics{sfnt}, Data: data, Extension: extension,
|
||||
Font: Font{FontName: name, FamilyName: editorFontName(sfnt, font.NamePreferredFamily, font.NameFontFamily), Charset: "unicode", Bold: sfnt.Head.MacStyle[0], Italic: sfnt.Head.MacStyle[1], FixedWidth: sfnt.Post.IsFixedPitch != 0},
|
||||
CanSubset: sfntSubsettable(sfnt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sfntSubsettable 判断默认资源后端能否无损裁剪该字体
|
||||
// 入参: sfnt 已解析字体
|
||||
// 返回: bool 是否支持裁剪
|
||||
func sfntSubsettable(sfnt *font.SFNT) bool {
|
||||
return sfnt.IsTrueType && !slices.ContainsFunc([]string{"fvar", "COLR", "CBDT", "sbix", "SVG "}, func(tag string) bool { return sfnt.Tables[tag] != nil })
|
||||
}
|
||||
|
||||
// sfntResourceMetrics 隔离资源解析器的度量实现
|
||||
type sfntResourceMetrics struct{ *font.SFNT }
|
||||
|
||||
// Write 返回未改动元信息的字体数据
|
||||
// 返回: []byte 字体数据
|
||||
func (f sfntResourceMetrics) Write() []byte { return fontSFNTData(f.SFNT) }
|
||||
|
||||
// SubsetFont 裁剪新建字体并按要求保留显式字形编号
|
||||
// 入参: data 字体数据, glyphs 用字, preserve 是否保留编号
|
||||
// 返回: []byte 子集, error 裁剪错误
|
||||
func (SFNTBackend) SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error) {
|
||||
return subsetEditorFont(data, glyphs, preserve)
|
||||
}
|
||||
|
||||
// SubsetSourceFont 按全包引用裁剪原字体,不确定时保留原资源
|
||||
// 入参: data 字体数据, usage 用字信息
|
||||
// 返回: []byte 子集, error 裁剪错误
|
||||
func (SFNTBackend) SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error) {
|
||||
u := newEditorFontUsage()
|
||||
u.unsafe = usage.Unsafe
|
||||
for _, char := range usage.Characters {
|
||||
u.chars[char] = true
|
||||
}
|
||||
for _, glyph := range usage.Glyphs {
|
||||
u.glyphs[glyph] = true
|
||||
}
|
||||
return subsetSourceFont(data, u), nil
|
||||
}
|
||||
|
||||
// editorFontName 获取字体中的名称
|
||||
// 入参: sfnt 字体, names 名称类型,按顺序查找
|
||||
// 返回: string 字体名称
|
||||
func editorFontName(sfnt *font.SFNT, names ...font.NameID) string {
|
||||
for _, name := range names {
|
||||
for _, record := range sfnt.Name.Get(name) {
|
||||
if value := record.String(); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -29,6 +29,20 @@ type CanvasBackend struct {
|
||||
ColorSpace canvas.ColorSpace
|
||||
}
|
||||
|
||||
// init 注册Canvas适配器和默认组合
|
||||
func init() {
|
||||
if err := RegisterRenderBackend("canvas", defaultRenderBackends); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultRenderBackends 创建默认组合,调用方可按职责替换任意后端
|
||||
// 返回: RenderBackends 默认后端组合
|
||||
func defaultRenderBackends() RenderBackends {
|
||||
backend := CanvasBackend{}
|
||||
return RenderBackends{Resources: SFNTBackend{}, Fonts: backend, Geometry: backend, Compiler: backend, Raster: backend, SVG: backend, PDF: backend, EPS: backend}
|
||||
}
|
||||
|
||||
// Name 返回后端标识
|
||||
// 返回: string 后端标识
|
||||
func (CanvasBackend) Name() string { return "canvas" }
|
||||
|
||||
Reference in new issue
Block a user