mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
This commit is contained in:
1 parent
f361c61dcc
commit
a3435d5d8d
12 files changed
+339
-58
No files matched your search
+10
-2
@@ -861,9 +861,17 @@ function uniqueLocalFonts(fonts) {
|
||||
}
|
||||
|
||||
function selectLocalFonts(fonts, names, limit) {
|
||||
const available = new Map(uniqueLocalFonts(fonts).map((font) => [localFontName(font), font]));
|
||||
const available = new Map();
|
||||
for (const font of uniqueLocalFonts(fonts)) {
|
||||
available.set(localFontName(font), font);
|
||||
for (const name of [font.postscriptName, [font.family, font.style].filter(Boolean).join(" ")]) {
|
||||
if (name) {
|
||||
available.set(`${name}.ttf`, font);
|
||||
}
|
||||
}
|
||||
}
|
||||
const matched = callWASM("ofdgoFontFileMatches", [...available.keys()], names);
|
||||
return matched.slice(0, limit).map((name) => available.get(name));
|
||||
return uniqueLocalFonts(matched.map((name) => available.get(name))).slice(0, limit);
|
||||
}
|
||||
|
||||
async function fontData(fonts) {
|
||||
|
||||
@@ -78,6 +78,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
|
||||
fontSourceCache: make(map[string][]fontSource),
|
||||
fontSourceUsed: make(map[string]fontSource),
|
||||
fontDirCandidates: make(map[string][]fontFileCandidate),
|
||||
fontFSCandidates: make(map[int][]fontFileCandidate),
|
||||
textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue),
|
||||
templatePageCache: make(map[string]*PageContent),
|
||||
imageCache: make(map[string]image.Image),
|
||||
|
||||
@@ -787,6 +787,7 @@ func readCFFLocalSubrs(data []byte, fdArrOff int, fdCount int) ([][][]byte, []cf
|
||||
return localSubrs, privateDicts
|
||||
}
|
||||
|
||||
// type2Operand Type2操作数
|
||||
type type2Operand struct {
|
||||
value int
|
||||
outStart int
|
||||
|
||||
+42
-18
@@ -56,6 +56,9 @@ func NewFontFS(fonts []FontFile) *FontFS {
|
||||
}
|
||||
sort.Strings(fsys.names)
|
||||
fsys.candidates = fontFileCandidates(fsys.names, path.Base)
|
||||
for _, file := range fsys.candidates {
|
||||
fsys.candidates = appendFontFileNames(fsys.candidates, file, fontFileNames(bytes.NewReader(fsys.files[file.name])))
|
||||
}
|
||||
return fsys
|
||||
}
|
||||
|
||||
@@ -126,8 +129,10 @@ func (fsys *FontFS) Match(names ...string) (string, bool) {
|
||||
// 入参: bold 是否粗体, italic 是否斜体, names 字体名称列表
|
||||
// 返回: string 匹配字体文件, bool 是否为名称匹配
|
||||
func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool) {
|
||||
if matches := fsys.matchPatternsStyle(fontFilePatterns(names...), bold, italic); len(matches) > 0 {
|
||||
return matches[0], true
|
||||
if len(names) > 0 {
|
||||
if matches := fsys.matchStyle(names, bold, italic); len(matches) > 0 {
|
||||
return matches[0].name, true
|
||||
}
|
||||
}
|
||||
if matches := fsys.fallbackFonts(bold, italic); len(matches) > 0 {
|
||||
return matches[0], false
|
||||
@@ -139,41 +144,57 @@ func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool
|
||||
// 入参: files 可用字体文件名, bold 是否粗体, italic 是否斜体, names 字体名称,留空匹配默认回退字体
|
||||
// 返回: []string 按优先级排列的匹配文件名
|
||||
func FontFileMatches(files []string, bold, italic bool, names ...string) []string {
|
||||
if len(names) == 0 {
|
||||
names = fontDefaultSystemNames()
|
||||
}
|
||||
return fontFileMatches(fontFileCandidates(files, path.Base), fontFilePatterns(names...), bold, italic)
|
||||
return fontFileMatchNames(fontFileMatches(fontFileCandidates(files, path.Base), names, bold, italic))
|
||||
}
|
||||
|
||||
// matchPatternsStyle 匹配指定样式的字体文件模式
|
||||
// 入参: patterns 匹配模式列表, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []string 字体文件列表
|
||||
func (fsys *FontFS) matchPatternsStyle(patterns []string, bold, italic bool) []string {
|
||||
return fontFileMatches(fsys.candidates, patterns, bold, italic)
|
||||
// matchStyle 匹配指定样式的字体名称
|
||||
// 入参: names 字体名称列表, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []fontFileMatch 字体匹配列表
|
||||
func (fsys *FontFS) matchStyle(names []string, bold, italic bool) []fontFileMatch {
|
||||
return fontFileMatches(fsys.candidates, names, bold, italic)
|
||||
}
|
||||
|
||||
// fontFileMatches 匹配字体文件候选
|
||||
// 入参: candidates 字体候选, patterns 匹配模式, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []string 按优先级排列的匹配文件名
|
||||
func fontFileMatches(candidates []fontFileCandidate, patterns []string, bold, italic bool) []string {
|
||||
// 入参: candidates 字体候选, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []fontFileMatch 按优先级排列的字体匹配
|
||||
func fontFileMatches(candidates []fontFileCandidate, names []string, bold, italic bool) []fontFileMatch {
|
||||
matches := make([]fontFileMatch, 0, len(candidates))
|
||||
seen := make(map[string]int, len(candidates))
|
||||
for _, matcher := range newFontPatternMatchers(patterns) {
|
||||
for i, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
for j, alias := range append([]string{name}, fontQualifiedNames(name)...) {
|
||||
matcher := fontPatternMatcher{stem: fontNormalizeName(alias), priority: (i-len(names))*2 + min(j, 1)}
|
||||
if matcher.stem == "" {
|
||||
continue
|
||||
}
|
||||
for _, file := range candidates {
|
||||
if rank := matcher.rankCandidate(file); rank == fontMatchExact {
|
||||
appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(names) == 0 {
|
||||
names = fontDefaultSystemNames()
|
||||
}
|
||||
for _, matcher := range newFontPatternMatchers(fontFilePatterns(names...)) {
|
||||
for _, file := range candidates {
|
||||
rank := matcher.rankCandidate(file)
|
||||
appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic)
|
||||
}
|
||||
}
|
||||
sortFontFileMatches(matches)
|
||||
return fontFileMatchNames(matches)
|
||||
return matches
|
||||
}
|
||||
|
||||
// fallbackFonts 获取回退字体文件
|
||||
// 入参: bold 是否粗体, italic 是否斜体
|
||||
// 返回: []string 字体文件列表
|
||||
func (fsys *FontFS) fallbackFonts(bold, italic bool) []string {
|
||||
if matches := fsys.matchPatternsStyle(fontFilePatterns(fontDefaultSystemNames()...), bold, italic); len(matches) > 0 {
|
||||
return matches
|
||||
if matches := fsys.matchStyle(nil, bold, italic); len(matches) > 0 {
|
||||
return fontFileMatchNames(matches)
|
||||
}
|
||||
if len(fsys.names) > 0 {
|
||||
return []string{fsys.names[0]}
|
||||
@@ -210,6 +231,7 @@ func cleanFontName(name string) string {
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
||||
// fontFileCandidate 字体文件候选
|
||||
type fontFileCandidate struct {
|
||||
name string
|
||||
base string
|
||||
@@ -238,6 +260,7 @@ func fontFileCandidates(names []string, base func(string) string) []fontFileCand
|
||||
return files
|
||||
}
|
||||
|
||||
// fontPatternMatcher 字体文件匹配器
|
||||
type fontPatternMatcher struct {
|
||||
pattern string
|
||||
lowerPattern string
|
||||
@@ -351,6 +374,7 @@ func (m fontPatternMatcher) styleSuffixNormalized(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// fontFileMatch 字体文件匹配结果
|
||||
type fontFileMatch struct {
|
||||
name string
|
||||
priority int
|
||||
|
||||
@@ -16,7 +16,6 @@ package ofdgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"sort"
|
||||
)
|
||||
@@ -184,14 +183,6 @@ func (r *Renderer) fontInfo(font Font) FontInfo {
|
||||
return info
|
||||
}
|
||||
|
||||
// fontFSMatchesStyle 匹配字体文件系统中的指定样式字体文件
|
||||
// 入参: fsys 字体文件系统, patterns 匹配模式列表, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []string 字体文件列表
|
||||
func fontFSMatchesStyle(fsys fs.FS, patterns []string, bold, italic bool) []string {
|
||||
names, _ := fs.Glob(fsys, "*")
|
||||
return fontFileMatches(fontFileCandidates(names, path.Base), patterns, bold, italic)
|
||||
}
|
||||
|
||||
// fontUsage 统计文档字体使用次数
|
||||
// 入参: doc 文档结构, pages 页面内容列表, nil表示读取文档页面
|
||||
// 返回: map[string]int 字体使用次数
|
||||
|
||||
+85
-27
@@ -15,9 +15,14 @@
|
||||
package ofdgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
||||
"github.com/tdewolff/canvas"
|
||||
)
|
||||
@@ -33,10 +38,11 @@ const (
|
||||
|
||||
// fontSource 字体来源
|
||||
type fontSource struct {
|
||||
kind fontSourceKind
|
||||
index int
|
||||
name string
|
||||
exact bool
|
||||
kind fontSourceKind
|
||||
index int
|
||||
name string
|
||||
exact bool
|
||||
priority int
|
||||
}
|
||||
|
||||
// fontSourceKey 字体来源去重键
|
||||
@@ -139,36 +145,42 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
||||
}
|
||||
bold := style&canvas.FontBold != 0
|
||||
italic := style&canvas.FontItalic != 0
|
||||
patterns := fontFilePatterns(font.FontName, font.FamilyName)
|
||||
names := []string{font.FontName, font.FamilyName}
|
||||
sources := make([]fontSource, 0)
|
||||
seen := make(map[fontSourceKey]bool)
|
||||
for _, dir := range r.fontDirs {
|
||||
for _, name := range r.matchFontFiles(dir, patterns, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: name, exact: true})
|
||||
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||
}
|
||||
}
|
||||
for index, fsys := range r.fontFS {
|
||||
for _, name := range fontFSMatchesStyle(fsys, patterns, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name, exact: true})
|
||||
for index := range r.fontFS {
|
||||
for _, match := range r.matchFontFS(index, names, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||
}
|
||||
}
|
||||
sortFontSources(sources)
|
||||
systemStart := len(sources)
|
||||
if canLoadSystemFonts() {
|
||||
systemDirs := systemFontDirs()
|
||||
for _, name := range []string{font.FamilyName, font.FontName} {
|
||||
for _, systemName := range fontSystemNames(name) {
|
||||
systemPatterns := fontFilePatterns(systemName)
|
||||
for _, dir := range systemDirs {
|
||||
for _, match := range r.matchFontFiles(dir, systemPatterns, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match, exact: true})
|
||||
}
|
||||
}
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceSystem, name: systemName, exact: true})
|
||||
for _, dir := range systemFontDirs() {
|
||||
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||
}
|
||||
}
|
||||
for _, name := range fontSystemNames(names...) {
|
||||
source := fontSource{kind: fontSourceSystem, name: name, exact: true}
|
||||
for i, requested := range names {
|
||||
if fontNormalizeName(name) == fontNormalizeName(requested) {
|
||||
source.priority = (i - len(names)) * 2
|
||||
break
|
||||
}
|
||||
}
|
||||
sources = appendFontSource(sources, seen, source)
|
||||
}
|
||||
}
|
||||
for index, fsys := range r.fontFS {
|
||||
for _, name := range fontFSMatchesStyle(fsys, fontFilePatterns(fontDefaultSystemNames()...), bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name})
|
||||
sortFontSources(sources[systemStart:])
|
||||
for index := range r.fontFS {
|
||||
for _, match := range r.matchFontFS(index, nil, bold, italic) {
|
||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name})
|
||||
}
|
||||
}
|
||||
for index, fsys := range r.fontFS {
|
||||
@@ -181,6 +193,14 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
||||
return sources
|
||||
}
|
||||
|
||||
// sortFontSources 将完整名称匹配排在回退候选之前
|
||||
// 入参: sources 字体来源列表
|
||||
func sortFontSources(sources []fontSource) {
|
||||
sort.SliceStable(sources, func(i, j int) bool {
|
||||
return sources[i].priority < sources[j].priority
|
||||
})
|
||||
}
|
||||
|
||||
// appendFontSource 追加字体来源
|
||||
// 入参: sources 字体来源列表, seen 去重映射, source 字体来源
|
||||
// 返回: []fontSource 字体来源列表
|
||||
@@ -250,16 +270,54 @@ func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource,
|
||||
}
|
||||
|
||||
// matchFontFiles 查找字体文件
|
||||
// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []string 文件列表
|
||||
func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string {
|
||||
// 入参: dir 字体目录, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []fontFileMatch 字体匹配列表
|
||||
func (r *Renderer) matchFontFiles(dir string, names []string, bold, italic bool) []fontFileMatch {
|
||||
candidates, ok := r.fontDirCandidates[dir]
|
||||
if !ok {
|
||||
files, _ := filepath.Glob(filepath.Join(dir, "*"))
|
||||
candidates = fontFileCandidates(files, filepath.Base)
|
||||
for _, candidate := range candidates {
|
||||
file, err := os.Open(candidate.name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
candidates = appendFontFileNames(candidates, candidate, fontFileNames(file))
|
||||
file.Close()
|
||||
}
|
||||
r.fontDirCandidates[dir] = candidates
|
||||
}
|
||||
return fontFileMatches(candidates, patterns, bold, italic)
|
||||
return fontFileMatches(candidates, names, bold, italic)
|
||||
}
|
||||
|
||||
// matchFontFS 匹配字体文件系统中的字体名称
|
||||
// 入参: index 字体文件系统索引, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||
// 返回: []fontFileMatch 字体匹配列表
|
||||
func (r *Renderer) matchFontFS(index int, names []string, bold, italic bool) []fontFileMatch {
|
||||
fsys := r.fontFS[index]
|
||||
if fonts, ok := fsys.(*FontFS); ok {
|
||||
return fonts.matchStyle(names, bold, italic)
|
||||
}
|
||||
candidates, ok := r.fontFSCandidates[index]
|
||||
if !ok {
|
||||
files, _ := fs.Glob(fsys, "*")
|
||||
candidates = fontFileCandidates(files, path.Base)
|
||||
for _, candidate := range candidates {
|
||||
file, err := fsys.Open(candidate.name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
reader, ok := file.(io.ReaderAt)
|
||||
if !ok {
|
||||
data, _ := io.ReadAll(file)
|
||||
reader = bytes.NewReader(data)
|
||||
}
|
||||
candidates = appendFontFileNames(candidates, candidate, fontFileNames(reader))
|
||||
file.Close()
|
||||
}
|
||||
r.fontFSCandidates[index] = candidates
|
||||
}
|
||||
return fontFileMatches(candidates, names, bold, italic)
|
||||
}
|
||||
|
||||
// systemFontDirs 获取系统字体目录
|
||||
|
||||
+36
-2
@@ -19,6 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fontMatchRule 字体匹配规则
|
||||
type fontMatchRule struct {
|
||||
Keys []string
|
||||
Names []string
|
||||
@@ -145,7 +146,10 @@ func FontSystemNames(names ...string) []string {
|
||||
// 返回: string 规范化后的字体名称
|
||||
func fontNormalizeName(name string) string {
|
||||
name = strings.ReplaceAll(name, "\\", "/")
|
||||
name = strings.TrimSuffix(path.Base(name), path.Ext(name))
|
||||
name = path.Base(strings.TrimSpace(name))
|
||||
if isFontFileName(name) {
|
||||
name = strings.TrimSuffix(name, path.Ext(name))
|
||||
}
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
return fontNameReplacer.Replace(name)
|
||||
}
|
||||
@@ -164,6 +168,34 @@ func fontExactCandidateNames(names ...string) []string {
|
||||
return fontCandidateNamesByLevel(fontMatchExact, names...)
|
||||
}
|
||||
|
||||
// fontQualifiedNames 获取保留名称后缀的字体候选
|
||||
// 入参: name 字体名称
|
||||
// 返回: []string 保留后缀的候选名称
|
||||
func fontQualifiedNames(name string) []string {
|
||||
name = fontNormalizeName(name)
|
||||
var names []string
|
||||
seen := make(map[string]bool)
|
||||
for _, rule := range fontMatchRules {
|
||||
if fontRuleMatchLevel(rule, name) != fontMatchPartial {
|
||||
continue
|
||||
}
|
||||
prefix := ""
|
||||
for _, group := range [][]string{rule.Keys, rule.Names, {rule.System}} {
|
||||
for _, alias := range group {
|
||||
alias = fontNormalizeName(alias)
|
||||
if len(alias) > len(prefix) && strings.HasPrefix(name, alias) {
|
||||
prefix = alias
|
||||
}
|
||||
}
|
||||
}
|
||||
suffix := strings.TrimPrefix(name, prefix)
|
||||
for _, alias := range append([]string{rule.System}, rule.Names...) {
|
||||
names = appendFontName(names, seen, fontNormalizeName(alias)+suffix)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// fontCandidateNamesByLevel 获取指定等级内的字体候选名称
|
||||
// 入参: maxLevel 最大匹配等级, names 字体名称列表
|
||||
// 返回: []string 字体候选名称
|
||||
@@ -199,7 +231,9 @@ func fontCandidateNamesByLevel(maxLevel int, names ...string) []string {
|
||||
// 返回: string 字体匹配模式主干
|
||||
func fontPatternStem(pattern string) string {
|
||||
stem := strings.TrimSuffix(pattern, "*")
|
||||
stem = strings.TrimSuffix(stem, path.Ext(stem))
|
||||
if isFontFileName(stem) {
|
||||
stem = strings.TrimSuffix(stem, path.Ext(stem))
|
||||
}
|
||||
if index := strings.IndexAny(stem, "*?["); index >= 0 {
|
||||
stem = stem[:index]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
)
|
||||
|
||||
// fontNameRecord 字体名称表记录
|
||||
type fontNameRecord struct {
|
||||
Platform uint16
|
||||
Encoding uint16
|
||||
Language uint16
|
||||
Name uint16
|
||||
Length uint16
|
||||
Offset uint16
|
||||
}
|
||||
|
||||
// appendFontFileNames 追加字体内部名称对应的候选
|
||||
// 入参: candidates 字体候选, file 字体文件, names 字体内部名称
|
||||
// 返回: []fontFileCandidate 字体候选列表
|
||||
func appendFontFileNames(candidates []fontFileCandidate, file fontFileCandidate, names []string) []fontFileCandidate {
|
||||
base := file.normalized
|
||||
for _, name := range names {
|
||||
file.normalized = fontNormalizeName(name)
|
||||
if file.normalized != base {
|
||||
candidates = append(candidates, file)
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
// fontFileNames 读取字体或集合首项的名称
|
||||
// 入参: file 字体数据
|
||||
// 返回: []string 字体名称及带样式的字体族名称
|
||||
func fontFileNames(file io.ReaderAt) []string {
|
||||
var header [12]byte
|
||||
if _, err := file.ReadAt(header[:], 0); err != nil {
|
||||
return nil
|
||||
}
|
||||
var offset int64
|
||||
if string(header[:4]) == "ttcf" {
|
||||
if binary.BigEndian.Uint32(header[8:]) == 0 {
|
||||
return nil
|
||||
}
|
||||
var entry [4]byte
|
||||
if _, err := file.ReadAt(entry[:], 12); err != nil {
|
||||
return nil
|
||||
}
|
||||
offset = int64(binary.BigEndian.Uint32(entry[:]))
|
||||
if _, err := file.ReadAt(header[:], offset); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
switch string(header[:4]) {
|
||||
case "\x00\x01\x00\x00", "OTTO", "true":
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
entries := make([]byte, int(binary.BigEndian.Uint16(header[4:]))*16)
|
||||
if _, err := file.ReadAt(entries, offset+12); err != nil {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < len(entries); i += 16 {
|
||||
if string(entries[i:i+4]) != "name" {
|
||||
continue
|
||||
}
|
||||
offset := int64(binary.BigEndian.Uint32(entries[i+8:]))
|
||||
length := int64(binary.BigEndian.Uint32(entries[i+12:]))
|
||||
data, err := io.ReadAll(io.NewSectionReader(file, offset, length))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fontNamesFromTable(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fontNamesFromTable 解析字体名称表
|
||||
// 入参: data 名称表数据
|
||||
// 返回: []string 字体名称列表
|
||||
func fontNamesFromTable(data []byte) []string {
|
||||
if len(data) < 6 || binary.BigEndian.Uint16(data) > 1 {
|
||||
return nil
|
||||
}
|
||||
count := int(binary.BigEndian.Uint16(data[2:]))
|
||||
storage := int(binary.BigEndian.Uint16(data[4:]))
|
||||
if 6+count*12 > storage || storage > len(data) {
|
||||
return nil
|
||||
}
|
||||
records := make([]fontNameRecord, count)
|
||||
if err := binary.Read(bytes.NewReader(data[6:]), binary.BigEndian, records); err != nil {
|
||||
return nil
|
||||
}
|
||||
values := make(map[[4]uint16]string)
|
||||
for _, record := range records {
|
||||
switch record.Name {
|
||||
case 1, 2, 4, 6, 16, 17:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
start := storage + int(record.Offset)
|
||||
end := start + int(record.Length)
|
||||
if end > len(data) {
|
||||
continue
|
||||
}
|
||||
var name string
|
||||
switch {
|
||||
case record.Platform == 0 || record.Platform == 3 && (record.Encoding == 0 || record.Encoding == 1 || record.Encoding == 10):
|
||||
if record.Length%2 != 0 {
|
||||
continue
|
||||
}
|
||||
chars := make([]uint16, int(record.Length)/2)
|
||||
for i := range chars {
|
||||
chars[i] = binary.BigEndian.Uint16(data[start+i*2:])
|
||||
}
|
||||
name = string(utf16.Decode(chars))
|
||||
case record.Platform == 1 && record.Encoding == 0:
|
||||
name, _ = charmap.Macintosh.NewDecoder().String(string(data[start:end]))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
values[[4]uint16{record.Platform, record.Encoding, record.Language, record.Name}] = name
|
||||
}
|
||||
var names []string
|
||||
seen := make(map[string]bool)
|
||||
for _, record := range records {
|
||||
key := [4]uint16{record.Platform, record.Encoding, record.Language, record.Name}
|
||||
name := values[key]
|
||||
switch record.Name {
|
||||
case 1, 16:
|
||||
key[3]++
|
||||
if style := values[key]; style != "" && name != "" {
|
||||
name += " " + style
|
||||
}
|
||||
case 4, 6:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
names = appendFontName(names, seen, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
@@ -213,6 +213,7 @@ func (r *Renderer) parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float6
|
||||
return r.parseFillPaint((*FillColor)(strokeColor), x, y, pageH)
|
||||
}
|
||||
|
||||
// patternPaint 底纹画刷
|
||||
type patternPaint struct {
|
||||
*Pattern
|
||||
color FillColor
|
||||
|
||||
@@ -38,6 +38,7 @@ type Renderer struct {
|
||||
fontSourceCache map[string][]fontSource
|
||||
fontSourceUsed map[string]fontSource
|
||||
fontDirCandidates map[string][]fontFileCandidate
|
||||
fontFSCandidates map[int][]fontFileCandidate
|
||||
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
|
||||
templatePageCache map[string]*PageContent
|
||||
imageCache map[string]image.Image
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
const defaultPathLineWidth = 0.353
|
||||
const defaultMiterLimit = 3.528
|
||||
|
||||
// pathStyle 路径绘制样式
|
||||
type pathStyle struct {
|
||||
fillColor color.Color
|
||||
strokeColor color.Color
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/tdewolff/canvas"
|
||||
)
|
||||
|
||||
// shdPaint 渐变画刷
|
||||
type shdPaint struct {
|
||||
gradient canvas.Gradient
|
||||
view canvas.Matrix
|
||||
|
||||
Reference in new issue
Block a user