feat(原生桌面): 统一原生字体中文名称显示
build.yaml / build (push) Successful in 44s

This commit is contained in:
xiaoqidun committed 2026-10-18 03:36:27 +08:00
1 parent a4f979ed2f
commit 460701f4d9
7 files changed
+83 -68

No files matched your search

+28 -2
View File
@@ -36,6 +36,13 @@ type Face struct {
// 入参: data 名称表数据, index 零起始字体索引
// 返回: Face 字体信息
func FaceInfo(data []byte, index int) Face {
return faceInfoLanguage(data, index, 0xffff, 0xffff)
}
// faceInfoLanguage 优先读取指定平台和语言的显示名称,保留标识及全部别名
// 入参: data 名称表数据, index 字体索引, platform 平台标识, language 语言标识
// 返回: Face 字体信息
func faceInfoLanguage(data []byte, index int, platform, language uint16) Face {
records, values := fontNameValues(data)
name := func(ids ...uint16) string {
for _, id := range ids {
@@ -49,7 +56,19 @@ func FaceInfo(data []byte, index int) Face {
}
return ""
}
return Face{Index: index, FullName: name(4, 6, 16, 1), Family: name(16, 1), Style: name(17, 2), PostScriptName: name(6), Names: fontNamesFromValues(records, values)}
localized := func(ids ...uint16) string {
for _, id := range ids {
for _, record := range records {
if record.Platform == platform && record.Language == language && record.Name == id {
if value := strings.TrimSpace(values[[4]uint16{record.Platform, record.Encoding, record.Language, id}]); value != "" {
return value
}
}
}
}
return name(ids...)
}
return Face{Index: index, FullName: localized(4, 6, 16, 1), Family: localized(16, 1), Style: localized(17, 2), PostScriptName: name(6), Names: fontNamesFromValues(records, values)}
}
// FileCount 校验字体或集合头并读取字体数量
@@ -78,6 +97,13 @@ func FileCount(data []byte) (int, error) {
// 入参: data OpenType数据
// 返回: []Face 字体列表, error 格式或名称表错误
func ReadFaces(data []byte) ([]Face, error) {
return ReadLocalizedFaces(data, 0xffff, 0xffff)
}
// ReadLocalizedFaces 读取指定平台和语言的字体名称,缺失时沿用名称表顺序
// 入参: data OpenType数据, platform 名称表平台标识, language 名称表语言标识
// 返回: []Face 字体列表, error 格式或名称表错误
func ReadLocalizedFaces(data []byte, platform, language uint16) ([]Face, error) {
count, err := FileCount(data)
if err != nil {
return nil, err
@@ -88,7 +114,7 @@ func ReadFaces(data []byte) ([]Face, error) {
if err != nil {
return nil, err
}
face := FaceInfo(tables["name"], index)
face := faceInfoLanguage(tables["name"], index, platform, language)
if face.FullName == "" {
return nil, fmt.Errorf("font %d has no usable name", index)
}
+2 -1
View File
@@ -687,12 +687,13 @@ func (h *Host) cleanupOutput(ctx context.Context, target webui.NativeOutputTarge
// readFonts 建立系统字体目录并登记按需读取资源
func (h *Host) readFonts() {
platform, language := fontNameLanguage()
for _, name := range ofdgo.FontSystemFiles() {
data, err := os.ReadFile(name)
if err != nil {
continue
}
faces, err := (ofdgo.FontFile{Name: filepath.Base(name), Data: data}).Faces()
faces, err := (ofdgo.FontFile{Name: filepath.Base(name), Data: data}).LocalizedFaces(platform, language)
if err != nil {
continue
}
-20
View File
@@ -1,20 +0,0 @@
// 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.
//go:build !windows
package webview
// conversionMemory 未提供系统内存探测时仅按并行数调度
var conversionMemory func() int64
@@ -1,45 +0,0 @@
// 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 webview
import (
"syscall"
"unsafe"
)
// memoryStatusProc 查询系统物理内存和提交额度
var memoryStatusProc = syscall.NewLazyDLL("kernel32.dll").NewProc("GlobalMemoryStatusEx")
// conversionMemory 预留系统内存后返回可用于新增转换的额度
// 返回: int64 可用字节数,查询失败时暂停扩容
func conversionMemory() int64 {
status := struct {
length uint32
load uint32
total uint64
available uint64
totalPage uint64
availablePage uint64
totalVirtual uint64
availableVirtual uint64
extended uint64
}{}
status.length = uint32(unsafe.Sizeof(status))
ok, _, _ := memoryStatusProc.Call(uintptr(unsafe.Pointer(&status)))
if ok == 0 {
return 0
}
return max(0, int64(min(status.available, status.availablePage))-int64(status.total/8))
}
@@ -18,6 +18,9 @@ package webview
import "os"
// conversionMemory 未提供系统内存探测时仅按并行数调度
var conversionMemory func() int64
// openReadFile 打开独立读取句柄
func openReadFile(name string) (*os.File, error) {
return os.Open(name)
@@ -27,3 +30,9 @@ func openReadFile(name string) (*os.File, error) {
func replaceFile(source, target string) error {
return os.Rename(source, target)
}
// fontNameLanguage 未指定系统显示语言时沿用字体名称表顺序
// 返回: uint16 未指定平台标识, uint16 未指定语言标识
func fontNameLanguage() (uint16, uint16) {
return 0xffff, 0xffff
}
@@ -25,6 +25,9 @@ import (
// replaceFileProc 保留目标文件属性并替换文件内容
var replaceFileProc = syscall.NewLazyDLL("kernel32.dll").NewProc("ReplaceFileW")
// memoryStatusProc 查询系统物理内存和提交额度
var memoryStatusProc = syscall.NewLazyDLL("kernel32.dll").NewProc("GlobalMemoryStatusEx")
// openReadFile 允许原子替换路径,同时保持已有读取句柄有效
func openReadFile(name string) (*os.File, error) {
path, err := nativeFilePath(name)
@@ -73,3 +76,32 @@ func nativeFilePath(name string) (*uint16, error) {
}
return syscall.UTF16PtrFromString(path)
}
// fontNameLanguage 获取系统显示语言对应的OpenType名称表标识
// 返回: uint16 名称表平台标识, uint16 系统显示语言标识
func fontNameLanguage() (uint16, uint16) {
language, _, _ := syscall.NewLazyDLL("kernel32.dll").NewProc("GetUserDefaultUILanguage").Call()
return 3, uint16(language)
}
// conversionMemory 预留系统内存后返回可用于新增转换的额度
// 返回: int64 可用字节数,查询失败时暂停扩容
func conversionMemory() int64 {
status := struct {
length uint32
load uint32
total uint64
available uint64
totalPage uint64
availablePage uint64
totalVirtual uint64
availableVirtual uint64
extended uint64
}{}
status.length = uint32(unsafe.Sizeof(status))
ok, _, _ := memoryStatusProc.Call(uintptr(unsafe.Pointer(&status)))
if ok == 0 {
return 0
}
return max(0, int64(min(status.available, status.availablePage))-int64(status.total/8))
}
+12
View File
@@ -44,6 +44,18 @@ func (f FontFile) Faces() ([]FontFace, error) {
return basefont.ReadFaces(data)
}
// LocalizedFaces 读取指定语言的显示名称,保留PostScript名称、全部别名和集合索引
// 未找到对应名称时沿用Faces的名称选择规则,不修改字体数据
// 入参: platform OpenType名称表平台标识, language 该平台的语言标识
// 返回: []FontFace 字体列表, error 格式或名称表错误
func (f FontFile) LocalizedFaces(platform, language uint16) ([]FontFace, error) {
data, err := decodeFontContainer(f.Data)
if err != nil {
return nil, err
}
return basefont.ReadLocalizedFaces(data, platform, language)
}
// Face 提取指定字体,返回独立的OpenType数据,可用于预览或AddFont,且不修改原文件
// 入参: index 零起始字体索引,非集合文件只能为0
// 返回: []byte 独立字体数据, error 格式或索引错误