Files
ofdgo/ofdgo_font_backend.go
T
2026-10-19 23:17:39 +08:00

307 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 (
"context"
"crypto/sha256"
"errors"
"fmt"
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
)
// FontMetrics 提供不依赖绘图库的字体度量,所有整数尺寸使用字体设计单位
// VerticalMetrics依次返回非负的上升高度、下降高度和行间隙
// Write返回独立的SFNT数据,调用方不得修改字体实例
type FontMetrics interface {
UnitsPerEm() uint16
NumGlyphs() uint16
GlyphIndex(rune) uint16
GlyphAdvance(uint16) uint16
VerticalMetrics() (uint16, uint16, uint16)
Write() []byte
}
// FontOutlines 提供基线原点、纵轴向下的字形轮廓,不绑定字体实现
// GlyphOutline返回只读路径,字号与路径单位为毫米
type FontOutlines interface {
FontMetrics
GlyphOutline(glyph uint16, size float64) (GeometryPath, error)
}
// FontOutlineBatch 批量提取指定字形,不经过字符映射或文字塑形
// 返回路径与输入编号一一对应,允许重复编号和空轮廓,路径只读
type FontOutlineBatch interface {
FontOutlines
GlyphOutlines(glyphs []uint16, size float64) ([]GeometryPath, error)
}
// FontGlyphBounds 提供与字形轮廓一致的边界,字号及边界单位为毫米
// 不使用字体全局边界替代单个字形,空轮廓返回零边界
type FontGlyphBounds interface {
FontOutlines
GlyphBounds(glyph uint16, size float64) (Box, error)
}
// FontGlyphDiagnostics 提供已提取字形的恢复提示,不将提示作为轮廓解析失败
// 字形不存在或不支持的指令仍由GlyphOutline返回错误
type FontGlyphDiagnostics interface {
FontMetrics
GlyphWarning(glyph uint16) error
}
// FontShaper 提供可选的横向文字塑形,仅用于显式新建或重排
// 字形按逻辑簇顺序排列,Cluster使用原文的符文索引,坐标为毫米且纵轴向下
type FontShaper interface {
FontMetrics
ShapeText(value string, size float64) ([]ShapedGlyph, error)
}
// TextShapeOptions 指定文字塑形参数,Bidi启用双向及脚本分段,不执行字体回退
// Direction为空或ltr时从左向右,rtl时从右向左;Script为四字母ISO15924代码,空值自动检测
// Bidi启用时Direction指定段落方向,各文字片段按双向算法排列
// Language为语言标签,Features为逗号分隔的全局OpenType特性,如kern=0,liga=1,不接受字符区间
// 零值保持原有FontShaper行为,非零值要求FontShaperOptions能力
type TextShapeOptions struct {
Bidi bool
Direction string
Script string
Language string
Features string
}
// FontShaperOptions 提供显式可配置塑形,簇与坐标遵循FontShaper约定
// 右向左文字仍按逻辑簇返回,视觉顺序由X和Y保留
type FontShaperOptions interface {
FontShaper
ShapeTextWithOptions(value string, size float64, options TextShapeOptions) ([]ShapedGlyph, error)
}
// FontShaperRun 保留完整单行原文的上下文,仅塑形指定区间,不执行字体回退
// start和end为原文的符文索引,区间左闭右开,返回Cluster仍使用原文索引
// 调用方负责方向和脚本分段,options.Bidi须为false;坐标相对当前区间的基线起点
// value为已校验的原文符文序列,调用期间不得修改,返回结果不引用该切片
type FontShaperRun interface {
FontShaperOptions
ShapeTextRun(value []rune, start, end int, size float64, options TextShapeOptions) ([]ShapedGlyph, error)
}
// ShapedGlyph 保存塑形后的字形、原文簇和基线定位
// VisualOrder为簇从左到右的零起始顺序,同簇字形共用顺序
type ShapedGlyph struct {
Glyph uint16
Cluster int
VisualOrder int
X, Y, Advance float64
}
// ResolvedFont 保存字体来源和只读字体数据,不将外部字体写入文档
type ResolvedFont struct {
Data []byte
Source string
Face *FontFace
Exact bool
}
// PreparedFont 保存绘制用字体及GID、CID到包装字符的映射,所有数据只读
// Data可与源编码不同,原始资源仍由ResolveFont和Reader保留
type PreparedFont struct {
ResolvedFont
Glyphs map[uint16]rune
CIDs map[uint16]rune
digest [32]byte
}
// FontBackend 统一字体来源与度量,exact禁止使用无关回退字体
// ResolveFont不得递归调用Renderer.ResolveFont,OpenFont不得修改输入数据
type FontBackend interface {
Backend
ResolveFont(renderer *Renderer, id string, exact bool) (ResolvedFont, error)
OpenFont(data []byte) (FontMetrics, error)
}
// resolvedFontKey 区分只读资源的字体标识与精确匹配要求
type resolvedFontKey struct {
id string
exact bool
definition *Font
}
// resolvedFontResult 缓存字体解析结果及不可用原因
type resolvedFontResult struct {
font ResolvedFont
err error
}
// fontDataKey 区分只读字体的起点和长度,指针保留源数据直到预算淘汰
type fontDataKey struct {
data *byte
size int
}
// ResolveFont 使用配置的字体后端解析资源,保留精确匹配与回退的区别
// 页面取消及后端取消、超时结果不缓存,后续调用可重试
// 入参: id 字体资源标识, exact 是否要求精确匹配
// 返回: ResolvedFont 字体与来源, error 解析错误
func (r *Renderer) ResolveFont(id string, exact bool) (ResolvedFont, error) {
if r.pageContext != nil {
if err := r.pageContext.Err(); err != nil {
return ResolvedFont{}, err
}
}
if r.backends.Fonts == nil {
return ResolvedFont{}, fmt.Errorf("fonts: %w", ErrBackendUnavailable)
}
key := resolvedFontKey{id: id, exact: exact, definition: r.Reader.fontDefinition(id)}
if key.definition != nil && key.definition.ID != "" {
key.id = key.definition.ID
}
if cached, ok := r.resolvedFonts[key]; ok {
return cached.font, cached.err
}
resolved, err := r.backends.Fonts.ResolveFont(r, key.id, exact)
if r.pageContext != nil {
if err := r.pageContext.Err(); err != nil {
return ResolvedFont{}, err
}
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return ResolvedFont{}, err
}
key.definition = r.Reader.fontDefinition(id)
if key.definition != nil && key.definition.ID != "" {
key.id = key.definition.ID
}
r.resolvedFonts[key] = resolvedFontResult{font: resolved, err: err}
return resolved, err
}
// PrepareFont 统一处理内嵌字体包装和索引映射,供各编译器复用
// 子渲染器按原始内容复用只读包装结果,各文档保留独立的来源信息
// 入参: id 字体资源标识
// 返回: *PreparedFont 绘制字体, error 字体解析错误
func (r *Renderer) PrepareFont(id string) (*PreparedFont, error) {
if r.pageContext != nil {
if err := r.pageContext.Err(); err != nil {
return nil, err
}
}
if definition := r.Reader.fontDefinition(id); definition != nil && definition.ID != "" {
id = definition.ID
}
if cached := r.preparedFonts[id]; cached != nil {
return cached, nil
}
resolved, err := r.ResolveFont(id, false)
if err != nil {
return nil, err
}
definition := r.Reader.fontDefinition(id)
if definition != nil && definition.ID != "" {
id = definition.ID
}
prepared := &PreparedFont{ResolvedFont: resolved}
if definition != nil && definition.FontFile != "" {
key := r.fontDigest(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 = basefont.GetCFFCIDRuneMap(resolved.Data)
if _, data, mapping, _, err := FixFontDataAggressive(resolved.Data, true, true); err == nil {
prepared.Data = data
if mapping != nil {
prepared.Glyphs = make(map[uint16]rune)
for character, glyph := range mapping {
if character == basefont.PackedGlyphRune(glyph) {
prepared.Glyphs[glyph] = character
}
}
for character, glyph := range mapping {
if _, ok := prepared.Glyphs[glyph]; !ok {
prepared.Glyphs[glyph] = character
}
}
}
}
prepared.digest = r.fontDigest(prepared.Data)
if r.pageContext != nil {
if err := r.pageContext.Err(); err != nil {
return nil, err
}
}
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
}
// fontDigest 在预算内复用只读字体摘要,内容副本仍按摘要复用解析结果
// 入参: data 不得修改的字体数据
// 返回: [32]byte 字体摘要
func (r *Renderer) fontDigest(data []byte) [32]byte {
if len(data) == 0 {
return sha256.Sum256(nil)
}
key := fontDataKey{data: &data[0], size: len(data)}
if digest, ok := r.fontDigests.get(key); ok {
return digest
}
digest := sha256.Sum256(data)
r.fontDigests.put(key, digest, min(cap(data), r.fontDigests.limit)+256)
return digest
}
// resetFontCache 重置字体解析及字形缓存,保留图片和非字体后端状态
func (r *Renderer) resetFontCache() {
if r.fontSourcesCache != nil {
r.fontSourcesCache.collections.clear()
}
r.fontSourcesCache = newFontSourceCache(r.Reader.resources)
r.resetFontBackendCache()
}
// resetFontBackendCache 清理后端字体对象,保留字体文件和图片缓存
func (r *Renderer) resetFontBackendCache() {
r.fontDigests.clear()
r.fontPreparations.clear()
r.fontMetrics.clear()
r.glyphOutlines.clear()
for _, state := range r.sharedBackendStates {
if cache, ok := state.(interface{ clear() }); ok {
cache.clear()
}
}
r.sharedBackendStates = make(map[any]any)
r.preparedFonts = make(map[string]*PreparedFont)
r.fontDigests = resourceCache[fontDataKey, [32]byte](r.Reader.resources, 4<<20)
r.fontPreparations = resourceCache[[32]byte, PreparedFont](r.Reader.resources, 16<<20)
r.resolvedFonts = make(map[resolvedFontKey]resolvedFontResult)
if r.fontSourcesCache != nil {
r.fontSourcesCache.fallback = ResolvedFont{}
r.fontSourcesCache.fallbackRead = false
}
r.fontMetrics = resourceCache[[32]byte, FontMetrics](r.Reader.resources, 32<<20)
r.glyphOutlines = resourceCache[glyphOutlineKey, GeometryPath](r.Reader.resources, 16<<20)
for _, state := range r.backendStates {
if fonts, ok := state.(interface{ resetFonts() }); ok {
fonts.resetFonts()
}
}
}