// 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 ( "crypto/sha256" "fmt" ) // 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) } // ShapedGlyph 保存塑形后的字形、原文簇和基线定位 // Bidi模式下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.backends.Fonts == nil { return ResolvedFont{}, fmt.Errorf("fonts: %w", ErrBackendUnavailable) } key := resolvedFontKey{id: id, exact: exact, definition: r.Reader.fontCache[id]} if cached, ok := r.resolvedFonts[key]; ok { return cached.font, cached.err } resolved, err := r.backends.Fonts.ResolveFont(r, id, exact) key.definition = r.Reader.fontCache[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 cached := r.preparedFonts[id]; cached != nil { return cached, nil } resolved, err := r.ResolveFont(id, false) if err != nil { return nil, err } prepared := &PreparedFont{ResolvedFont: resolved} definition := r.Reader.fontCache[id] 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 = 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 == 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) 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() { r.fontSourcesCache = newFontSourceCache() r.resetFontBackendCache() } // resetFontBackendCache 清理后端字体对象,保留字体文件和图片缓存 func (r *Renderer) resetFontBackendCache() { r.sharedBackendStates = make(map[any]any) r.preparedFonts = make(map[string]*PreparedFont) r.fontDigests = &renderCache[fontDataKey, [32]byte]{limit: 4 << 20} r.fontPreparations = &renderCache[[32]byte, PreparedFont]{limit: 16 << 20} r.resolvedFonts = make(map[resolvedFontKey]resolvedFontResult) if r.fontSourcesCache != nil { r.fontSourcesCache.fallback = ResolvedFont{} r.fontSourcesCache.fallbackRead = false } r.fontMetrics = &renderCache[[32]byte, FontMetrics]{limit: 32 << 20} r.glyphOutlines = renderCache[glyphOutlineKey, GeometryPath]{limit: 16 << 20} for _, state := range r.backendStates { if fonts, ok := state.(interface{ resetFonts() }); ok { fonts.resetFonts() } } }