mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
This commit is contained in:
1 parent
1914b5329a
commit
f9646c70e9
8 files changed
+163
-68
No files matched your search
@@ -265,12 +265,26 @@ a:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.font-listbox [role="option"] {
|
.font-listbox [role="option"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-listbox [role="option"] > span:first-child {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-option-source {
|
||||||
|
flex: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.font-listbox [role="option"]:hover,
|
.font-listbox [role="option"]:hover,
|
||||||
.font-listbox [aria-selected="true"] {
|
.font-listbox [aria-selected="true"] {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ export class FontPicker {
|
|||||||
setFonts(fonts, selected) {
|
setFonts(fonts, selected) {
|
||||||
this.close();
|
this.close();
|
||||||
this.fonts = fonts;
|
this.fonts = fonts;
|
||||||
|
this.names = new Map();
|
||||||
|
for (const font of fonts) {
|
||||||
|
const name = (font.fullName || font.name).normalize("NFKC").toLocaleLowerCase();
|
||||||
|
this.names.set(name, (this.names.get(name) || 0) + 1);
|
||||||
|
}
|
||||||
const key = font => font?.id || font?.postscriptName;
|
const key = font => font?.id || font?.postscriptName;
|
||||||
this.value = fonts.length ? String(Math.max(0, fonts.findIndex(font => key(font) === key(selected)))) : "";
|
this.value = fonts.length ? String(Math.max(0, fonts.findIndex(font => key(font) === key(selected)))) : "";
|
||||||
this.restore();
|
this.restore();
|
||||||
@@ -39,10 +44,17 @@ export class FontPicker {
|
|||||||
restore() {
|
restore() {
|
||||||
const font = this.fonts[Number(this.value)];
|
const font = this.fonts[Number(this.value)];
|
||||||
this.input.value = font?.fullName || font?.name || "";
|
this.input.value = font?.fullName || font?.name || "";
|
||||||
|
this.input.title = font ? [this.input.value, this.sourceLabel(font)].filter(Boolean).join(" · ") : "";
|
||||||
this.input.setAttribute("aria-expanded", String(this.open));
|
this.input.setAttribute("aria-expanded", String(this.open));
|
||||||
this.input.removeAttribute("aria-activedescendant");
|
this.input.removeAttribute("aria-activedescendant");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceLabel(font) {
|
||||||
|
const name = (font.fullName || font.name).normalize("NFKC").toLocaleLowerCase();
|
||||||
|
if (this.names.get(name) < 2) return "";
|
||||||
|
return font.embedded ? "内嵌" : font.file || ["stored", "upload"].includes(font.source) ? "上传" : "系统";
|
||||||
|
}
|
||||||
|
|
||||||
show(query = "") {
|
show(query = "") {
|
||||||
if (this.input.disabled) return;
|
if (this.input.disabled) return;
|
||||||
const normalize = value => value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/g, " ").trim();
|
const normalize = value => value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/g, " ").trim();
|
||||||
@@ -58,7 +70,17 @@ export class FontPicker {
|
|||||||
option.dataset.index = String(index);
|
option.dataset.index = String(index);
|
||||||
option.setAttribute("role", "option");
|
option.setAttribute("role", "option");
|
||||||
option.setAttribute("aria-disabled", String(Boolean(font.disabled)));
|
option.setAttribute("aria-disabled", String(Boolean(font.disabled)));
|
||||||
option.textContent = font.fullName || font.name;
|
const name = document.createElement("span");
|
||||||
|
name.textContent = font.fullName || font.name;
|
||||||
|
option.append(name);
|
||||||
|
const source = this.sourceLabel(font);
|
||||||
|
if (source) {
|
||||||
|
const label = document.createElement("span");
|
||||||
|
label.className = "font-option-source";
|
||||||
|
label.textContent = source;
|
||||||
|
option.append(label);
|
||||||
|
}
|
||||||
|
option.title = font.name || font.fullName;
|
||||||
this.list.append(option);
|
this.list.append(option);
|
||||||
}
|
}
|
||||||
if (!this.matches.length) {
|
if (!this.matches.length) {
|
||||||
|
|||||||
+3
-12
@@ -758,27 +758,18 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) {
|
|||||||
return objects, nil
|
return objects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorFont 读取创作对象的完整内嵌字体,供画布输入使用
|
// editorFont 读取对象实际使用的内嵌字体,供画布输入使用
|
||||||
// 入参: args 字体资源标识
|
// 入参: args 字体资源标识
|
||||||
// 返回: any 字体数据, error 错误信息
|
// 返回: any 字体数据, error 错误信息
|
||||||
func editorFont(args []js.Value) (any, error) {
|
func editorFont(args []js.Value) (any, error) {
|
||||||
if currentEditor == nil {
|
if currentEditor == nil {
|
||||||
return nil, fmt.Errorf("no document is being edited")
|
return nil, fmt.Errorf("no document is being edited")
|
||||||
}
|
}
|
||||||
fonts, err := currentSession.Reader.Fonts()
|
data, err := currentSession.Reader.FontData(args[0].String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, font := range fonts {
|
return successResult(map[string]any{"bytes": bytesToJS(data)}), nil
|
||||||
if font.ID == args[0].String() {
|
|
||||||
data, err := currentSession.Reader.ResData(font.FontFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return successResult(map[string]any{"bytes": bytesToJS(data)}), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("font resource %q not found", args[0].String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaceImage 替换图片资源并更新预览
|
// replaceImage 替换图片资源并更新预览
|
||||||
|
|||||||
+9
-16
@@ -410,23 +410,16 @@ func (e *Editor) editorFont(id string) (*font.SFNT, error) {
|
|||||||
return sfnt, nil
|
return sfnt, nil
|
||||||
}
|
}
|
||||||
if e.source != nil {
|
if e.source != nil {
|
||||||
reader := e.source.reader
|
data, err := e.source.reader.FontData(id)
|
||||||
if f := reader.fontCache[id]; f != nil && f.FontFile != "" {
|
if err != nil {
|
||||||
data, err := reader.ResData(f.FontFile)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err = font.ToSFNT(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sfnt, err := font.ParseSFNT(data, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e.fonts[id] = sfnt
|
|
||||||
return sfnt, nil
|
|
||||||
}
|
}
|
||||||
|
sfnt, err := font.ParseSFNT(data, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.fonts[id] = sfnt
|
||||||
|
return sfnt, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("embedded font %q not found", id)
|
return nil, fmt.Errorf("embedded font %q not found", id)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-6
@@ -189,7 +189,7 @@ func (fsys *FontFS) matchStyle(names []string, bold, italic bool) []fontFileMatc
|
|||||||
// 返回: []fontFileMatch 按优先级排列的字体匹配
|
// 返回: []fontFileMatch 按优先级排列的字体匹配
|
||||||
func fontFileMatches(candidates []fontFileCandidate, names []string, bold, italic bool) []fontFileMatch {
|
func fontFileMatches(candidates []fontFileCandidate, names []string, bold, italic bool) []fontFileMatch {
|
||||||
matches := make([]fontFileMatch, 0, len(candidates))
|
matches := make([]fontFileMatch, 0, len(candidates))
|
||||||
seen := make(map[string]int, len(candidates))
|
seen := make(map[fontFileKey]int, len(candidates))
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
for _, name := range fontDefaultSystemNames() {
|
for _, name := range fontDefaultSystemNames() {
|
||||||
matches = append(matches, fontFileMatches(candidates, []string{name}, bold, italic)...)
|
matches = append(matches, fontFileMatches(candidates, []string{name}, bold, italic)...)
|
||||||
@@ -199,7 +199,7 @@ func fontFileMatches(candidates []fontFileCandidate, names []string, bold, itali
|
|||||||
})
|
})
|
||||||
unique := matches[:0]
|
unique := matches[:0]
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
key := strings.ToLower(match.name)
|
key := fontFileKey{strings.ToLower(match.name), match.face}
|
||||||
if _, ok := seen[key]; !ok {
|
if _, ok := seen[key]; !ok {
|
||||||
seen[key] = len(unique)
|
seen[key] = len(unique)
|
||||||
unique = append(unique, match)
|
unique = append(unique, match)
|
||||||
@@ -278,6 +278,7 @@ func cleanFontName(name string) string {
|
|||||||
// fontFileCandidate 字体文件候选
|
// fontFileCandidate 字体文件候选
|
||||||
type fontFileCandidate struct {
|
type fontFileCandidate struct {
|
||||||
name string
|
name string
|
||||||
|
face int
|
||||||
base string
|
base string
|
||||||
lowerBase string
|
lowerBase string
|
||||||
normalized string
|
normalized string
|
||||||
@@ -421,15 +422,22 @@ func (m fontPatternMatcher) styleSuffixNormalized(name string) string {
|
|||||||
// fontFileMatch 字体文件匹配结果
|
// fontFileMatch 字体文件匹配结果
|
||||||
type fontFileMatch struct {
|
type fontFileMatch struct {
|
||||||
name string
|
name string
|
||||||
|
face int
|
||||||
priority int
|
priority int
|
||||||
rank int
|
rank int
|
||||||
styleRank int
|
styleRank int
|
||||||
sortName string
|
sortName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontFileKey 字体文件与集合索引的去重键
|
||||||
|
type fontFileKey struct {
|
||||||
|
name string
|
||||||
|
face int
|
||||||
|
}
|
||||||
|
|
||||||
// appendFontFileMatch 追加字体文件匹配结果
|
// appendFontFileMatch 追加字体文件匹配结果
|
||||||
// 入参: matches 匹配结果, seen 已匹配文件, matcher 匹配器, file 字体文件候选, rank 匹配等级, bold 是否粗体, italic 是否斜体
|
// 入参: matches 匹配结果, seen 已匹配文件, matcher 匹配器, file 字体文件候选, rank 匹配等级, bold 是否粗体, italic 是否斜体
|
||||||
func appendFontFileMatch(matches *[]fontFileMatch, seen map[string]int, matcher fontPatternMatcher, file fontFileCandidate, rank int, bold, italic bool) {
|
func appendFontFileMatch(matches *[]fontFileMatch, seen map[fontFileKey]int, matcher fontPatternMatcher, file fontFileCandidate, rank int, bold, italic bool) {
|
||||||
if rank == fontMatchNone {
|
if rank == fontMatchNone {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -439,18 +447,20 @@ func appendFontFileMatch(matches *[]fontFileMatch, seen map[string]int, matcher
|
|||||||
}
|
}
|
||||||
next := fontFileMatch{
|
next := fontFileMatch{
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
face: file.face,
|
||||||
priority: matcher.priority,
|
priority: matcher.priority,
|
||||||
rank: rank,
|
rank: rank,
|
||||||
styleRank: fontFileStyleRank(suffix, bold, italic),
|
styleRank: fontFileStyleRank(suffix, bold, italic),
|
||||||
sortName: file.lowerBase,
|
sortName: file.lowerBase,
|
||||||
}
|
}
|
||||||
if index, ok := seen[file.fold]; ok {
|
key := fontFileKey{file.fold, file.face}
|
||||||
|
if index, ok := seen[key]; ok {
|
||||||
if fontFileMatchLess(next, (*matches)[index]) {
|
if fontFileMatchLess(next, (*matches)[index]) {
|
||||||
(*matches)[index] = next
|
(*matches)[index] = next
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen[file.fold] = len(*matches)
|
seen[key] = len(*matches)
|
||||||
*matches = append(*matches, next)
|
*matches = append(*matches, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,8 +493,13 @@ func fontFileMatchLess(left, right fontFileMatch) bool {
|
|||||||
// 返回: []string 字体文件列表
|
// 返回: []string 字体文件列表
|
||||||
func fontFileMatchNames(matches []fontFileMatch) []string {
|
func fontFileMatchNames(matches []fontFileMatch) []string {
|
||||||
names := make([]string, 0, len(matches))
|
names := make([]string, 0, len(matches))
|
||||||
|
seen := make(map[string]bool, len(matches))
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
names = append(names, match.name)
|
key := strings.ToLower(match.name)
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
names = append(names, match.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -75,6 +76,30 @@ func (r *Reader) Fonts() ([]Font, error) {
|
|||||||
return fonts, nil
|
return fonts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FontData 读取内嵌字体,集合按声明名称和样式提取为独立字体,其他格式保留原始字节
|
||||||
|
// 入参: id 字体资源标识
|
||||||
|
// 返回: []byte 字体数据, error 资源缺失或集合解析错误
|
||||||
|
func (r *Reader) FontData(id string) ([]byte, error) {
|
||||||
|
if r.fontCache[id] == nil {
|
||||||
|
if _, err := r.Fonts(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := r.fontCache[id]
|
||||||
|
if f == nil || f.FontFile == "" {
|
||||||
|
return nil, fmt.Errorf("embedded font %q not found", id)
|
||||||
|
}
|
||||||
|
data, err := r.ResData(f.FontFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if bytes.HasPrefix(data, []byte("ttcf")) {
|
||||||
|
index := fontCollectionIndex(data, []string{f.FontName, f.FamilyName}, f.Bold, f.Italic)
|
||||||
|
return extractCollectionFont(data, index)
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// FontInfos 获取OFD字体诊断信息
|
// FontInfos 获取OFD字体诊断信息
|
||||||
// 返回: []FontInfo 字体诊断列表, error 错误信息
|
// 返回: []FontInfo 字体诊断列表, error 错误信息
|
||||||
func (r *Renderer) FontInfos() ([]FontInfo, error) {
|
func (r *Renderer) FontInfos() ([]FontInfo, error) {
|
||||||
|
|||||||
+19
-12
@@ -40,6 +40,7 @@ const (
|
|||||||
type fontSource struct {
|
type fontSource struct {
|
||||||
kind fontSourceKind
|
kind fontSourceKind
|
||||||
index int
|
index int
|
||||||
|
face int
|
||||||
name string
|
name string
|
||||||
exact bool
|
exact bool
|
||||||
priority int
|
priority int
|
||||||
@@ -49,6 +50,7 @@ type fontSource struct {
|
|||||||
type fontSourceKey struct {
|
type fontSourceKey struct {
|
||||||
kind fontSourceKind
|
kind fontSourceKind
|
||||||
index int
|
index int
|
||||||
|
face int
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
|||||||
fontStyle := canvasFontStyle(of)
|
fontStyle := canvasFontStyle(of)
|
||||||
ff := canvas.NewFontFamily(of.FontName)
|
ff := canvas.NewFontFamily(of.FontName)
|
||||||
if of.FontFile != "" {
|
if of.FontFile != "" {
|
||||||
if fontData, err := r.Reader.ResData(of.FontFile); err == nil {
|
if fontData, err := r.Reader.FontData(fontID); err == nil {
|
||||||
if cidMap := getCFFCIDRuneMap(fontData); len(cidMap) > 0 {
|
if cidMap := getCFFCIDRuneMap(fontData); len(cidMap) > 0 {
|
||||||
r.fontCIDMap[fontID] = cidMap
|
r.fontCIDMap[fontID] = cidMap
|
||||||
}
|
}
|
||||||
@@ -144,12 +146,12 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
|||||||
seen := make(map[fontSourceKey]bool)
|
seen := make(map[fontSourceKey]bool)
|
||||||
for _, dir := range r.fontDirs {
|
for _, dir := range r.fontDirs {
|
||||||
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
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)})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, face: match.face, exact: true, priority: min(match.priority, 0)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index := range r.fontFS {
|
for index := range r.fontFS {
|
||||||
for _, match := range r.matchFontFS(index, names, bold, italic) {
|
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)})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name, face: match.face, exact: true, priority: min(match.priority, 0)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortFontSources(sources)
|
sortFontSources(sources)
|
||||||
@@ -157,7 +159,7 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
|||||||
if canLoadSystemFonts() {
|
if canLoadSystemFonts() {
|
||||||
for _, dir := range systemFontDirs() {
|
for _, dir := range systemFontDirs() {
|
||||||
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
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)})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, face: match.face, exact: true, priority: min(match.priority, 0)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, name := range fontSystemNames(names...) {
|
for _, name := range fontSystemNames(names...) {
|
||||||
@@ -174,7 +176,7 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
|||||||
sortFontSources(sources[systemStart:])
|
sortFontSources(sources[systemStart:])
|
||||||
for index := range r.fontFS {
|
for index := range r.fontFS {
|
||||||
for _, match := range r.matchFontFS(index, nil, bold, italic) {
|
for _, match := range r.matchFontFS(index, nil, bold, italic) {
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name, face: match.face})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, fsys := range r.fontFS {
|
for index, fsys := range r.fontFS {
|
||||||
@@ -202,7 +204,7 @@ func appendFontSource(sources []fontSource, seen map[fontSourceKey]bool, source
|
|||||||
if source.name == "" {
|
if source.name == "" {
|
||||||
return sources
|
return sources
|
||||||
}
|
}
|
||||||
key := fontSourceKey{kind: source.kind, index: source.index, name: source.name}
|
key := fontSourceKey{kind: source.kind, index: source.index, name: source.name, face: source.face}
|
||||||
if seen[key] {
|
if seen[key] {
|
||||||
return sources
|
return sources
|
||||||
}
|
}
|
||||||
@@ -219,7 +221,7 @@ func (r *Renderer) fontSourceMatch(fontID string, font *Font) (fontSource, bool)
|
|||||||
}
|
}
|
||||||
style := canvasFontStyle(font)
|
style := canvasFontStyle(font)
|
||||||
for _, source := range r.fontSources(fontID, font, style) {
|
for _, source := range r.fontSources(fontID, font, style) {
|
||||||
key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name}, style: style}
|
key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name, face: source.face}, style: style}
|
||||||
if cached, ok := r.fontCache[key]; ok {
|
if cached, ok := r.fontCache[key]; ok {
|
||||||
if cached != nil {
|
if cached != nil {
|
||||||
return source, true
|
return source, true
|
||||||
@@ -238,22 +240,27 @@ func (r *Renderer) fontSourceMatch(fontID string, font *Font) (fontSource, bool)
|
|||||||
// 入参: family 字体族, source 字体来源, style Canvas字体样式
|
// 入参: family 字体族, source 字体来源, style Canvas字体样式
|
||||||
// 返回: *canvas.FontFamily 字体族
|
// 返回: *canvas.FontFamily 字体族
|
||||||
func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource, style canvas.FontStyle) *canvas.FontFamily {
|
func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource, style canvas.FontStyle) *canvas.FontFamily {
|
||||||
key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name}, style: style}
|
key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name, face: source.face}, style: style}
|
||||||
if cached, ok := r.fontCache[key]; ok {
|
if cached, ok := r.fontCache[key]; ok {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
var data []byte
|
||||||
switch source.kind {
|
switch source.kind {
|
||||||
case fontSourceFile:
|
case fontSourceFile:
|
||||||
err = family.LoadFontFile(source.name, style)
|
data, err = os.ReadFile(source.name)
|
||||||
case fontSourceFS:
|
case fontSourceFS:
|
||||||
var data []byte
|
|
||||||
data, err = fs.ReadFile(r.fontFS[source.index], source.name)
|
data, err = fs.ReadFile(r.fontFS[source.index], source.name)
|
||||||
|
case fontSourceSystem:
|
||||||
|
err = family.LoadSystemFont(source.name, style)
|
||||||
|
}
|
||||||
|
if err == nil && source.kind != fontSourceSystem {
|
||||||
|
if bytes.HasPrefix(data, []byte("ttcf")) {
|
||||||
|
data, err = extractCollectionFont(data, source.face)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = family.LoadFont(data, 0, style)
|
err = family.LoadFont(data, 0, style)
|
||||||
}
|
}
|
||||||
case fontSourceSystem:
|
|
||||||
err = family.LoadSystemFont(source.name, style)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.fontCache[key] = nil
|
r.fontCache[key] = nil
|
||||||
|
|||||||
+49
-21
@@ -34,42 +34,70 @@ type fontNameRecord struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendFontFileNames 使用字体内部名称匹配,缺失时保留文件名
|
// appendFontFileNames 使用字体内部名称匹配,缺失时保留文件名
|
||||||
// 入参: candidates 字体候选, index 文件候选索引, names 字体内部名称
|
// 入参: candidates 字体候选, index 文件候选索引, faces 各字体内部名称
|
||||||
// 返回: []fontFileCandidate 字体候选列表
|
// 返回: []fontFileCandidate 字体候选列表
|
||||||
func appendFontFileNames(candidates []fontFileCandidate, index int, names []string) []fontFileCandidate {
|
func appendFontFileNames(candidates []fontFileCandidate, index int, faces [][]string) []fontFileCandidate {
|
||||||
file := candidates[index]
|
file := candidates[index]
|
||||||
for i, name := range names {
|
replaced := false
|
||||||
file.normalized = fontNormalizeName(name)
|
for face, names := range faces {
|
||||||
if i == 0 {
|
file.face = face
|
||||||
candidates[index] = file
|
for _, name := range names {
|
||||||
} else {
|
file.normalized = fontNormalizeName(name)
|
||||||
candidates = append(candidates, file)
|
if !replaced {
|
||||||
|
candidates[index] = file
|
||||||
|
replaced = true
|
||||||
|
} else {
|
||||||
|
candidates = append(candidates, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontFileNames 读取字体或集合首项的名称
|
// fontFileNames 按字体索引读取字体或集合中各项的名称
|
||||||
// 入参: file 字体数据
|
// 入参: file 字体数据
|
||||||
// 返回: []string 字体名称及带样式的字体族名称
|
// 返回: [][]string 各字体名称及带样式的字体族名称
|
||||||
func fontFileNames(file io.ReaderAt) []string {
|
func fontFileNames(file io.ReaderAt) [][]string {
|
||||||
var header [12]byte
|
var header [12]byte
|
||||||
if _, err := file.ReadAt(header[:], 0); err != nil {
|
if _, err := file.ReadAt(header[:], 0); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var offset int64
|
if string(header[:4]) != "ttcf" {
|
||||||
if string(header[:4]) == "ttcf" {
|
return [][]string{fontFaceNames(file, 0)}
|
||||||
if binary.BigEndian.Uint32(header[8:]) == 0 {
|
}
|
||||||
return nil
|
version := binary.BigEndian.Uint32(header[4:])
|
||||||
}
|
if version != 0x00010000 && version != 0x00020000 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var faces [][]string
|
||||||
|
for i, count := uint32(0), binary.BigEndian.Uint32(header[8:]); i < count; i++ {
|
||||||
var entry [4]byte
|
var entry [4]byte
|
||||||
if _, err := file.ReadAt(entry[:], 12); err != nil {
|
if _, err := file.ReadAt(entry[:], 12+int64(i)*4); err != nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
offset = int64(binary.BigEndian.Uint32(entry[:]))
|
|
||||||
if _, err := file.ReadAt(header[:], offset); err != nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
faces = append(faces, fontFaceNames(file, int64(binary.BigEndian.Uint32(entry[:]))))
|
||||||
|
}
|
||||||
|
return faces
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontCollectionIndex 按内部名称与样式选择集合项,未匹配时保留首项
|
||||||
|
// 入参: data 集合数据, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||||
|
// 返回: int 零起始字体索引
|
||||||
|
func fontCollectionIndex(data []byte, names []string, bold, italic bool) int {
|
||||||
|
candidates := appendFontFileNames([]fontFileCandidate{{}}, 0, fontFileNames(bytes.NewReader(data)))
|
||||||
|
if matches := fontFileMatches(candidates, names, bold, italic); len(matches) > 0 {
|
||||||
|
return matches[0].face
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontFaceNames 读取指定字体表目录中的名称
|
||||||
|
// 入参: file 字体数据, offset 字体表目录偏移
|
||||||
|
// 返回: []string 字体名称及带样式的字体族名称
|
||||||
|
func fontFaceNames(file io.ReaderAt, offset int64) []string {
|
||||||
|
var header [12]byte
|
||||||
|
if _, err := file.ReadAt(header[:], offset); err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
switch string(header[:4]) {
|
switch string(header[:4]) {
|
||||||
case "\x00\x01\x00\x00", "OTTO", "true":
|
case "\x00\x01\x00\x00", "OTTO", "true":
|
||||||
|
|||||||
Reference in new issue
Block a user