siyuan/kernel/util/font.go

196 lines
4.6 KiB
Go

// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package util
import (
"os"
"sort"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/ConradIrwin/font/sfnt"
"github.com/flopp/go-findfont"
"github.com/siyuan-note/logging"
ttc "golang.org/x/image/font/sfnt"
textUnicode "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
var (
sysFonts []string
sysFontsLock = sync.Mutex{}
)
func LoadSysFonts() (ret []string) {
sysFontsLock.Lock()
defer sysFontsLock.Unlock()
if 0 < len(sysFonts) {
return sysFonts
}
start := time.Now()
fonts := loadFonts()
ret = []string{}
for _, font := range fonts {
ret = append(ret, font.Family)
}
ret = gulu.Str.RemoveDuplicatedElem(ret)
ret = removeUnusedFonts(ret)
sort.Strings(ret)
sysFonts = ret
logging.LogInfof("loaded system fonts [%d] in [%dms]", len(sysFonts), time.Since(start).Milliseconds())
return
}
func removeUnusedFonts(fonts []string) (ret []string) {
ret = []string{}
for _, font := range fonts {
if strings.HasPrefix(font, "Noto Sans") {
continue
}
ret = append(ret, font)
}
return
}
type Font struct {
Path string
Family string
}
func loadFonts() (ret []*Font) {
ret = []*Font{}
for _, fontPath := range findfont.List() {
if strings.HasSuffix(strings.ToLower(fontPath), ".ttc") {
families := parseTTCFontFamily(fontPath)
for _, family := range families {
if existFont(family, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
//LogInfof("[%s] [%s]", fontPath, family)
}
} else if strings.HasSuffix(strings.ToLower(fontPath), ".otf") || strings.HasSuffix(strings.ToLower(fontPath), ".ttf") {
family := parseTTFFontFamily(fontPath)
if "" != family {
if existFont(family, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
//logging.LogInfof("[%s] [%s]", fontPath, family)
}
}
}
return
}
func existFont(family string, fonts []*Font) bool {
for _, font := range fonts {
if strings.EqualFold(family, font.Family) {
return true
}
}
return false
}
func parseTTCFontFamily(fontPath string) (ret []string) {
defer logging.Recover()
data, err := os.ReadFile(fontPath)
if err != nil {
//logging.LogErrorf("read font file [%s] failed: %s", fontPath, err)
return
}
collection, err := ttc.ParseCollection(data)
if err != nil {
//LogErrorf("parse font collection [%s] failed: %s", fontPath, err)
return
}
for i := 0; i < collection.NumFonts(); i++ {
font, err := collection.Font(i)
if err != nil {
//LogErrorf("get font [%s] failed: %s", fontPath, err)
continue
}
family, _ := font.Name(nil, ttc.NameIDTypographicFamily)
if "" == family {
family, _ = font.Name(nil, ttc.NameIDFamily)
}
family = strings.TrimSpace(family)
if "" == family || strings.HasPrefix(family, ".") {
continue
}
ret = append(ret, family)
}
return
}
func parseTTFFontFamily(fontPath string) (ret string) {
defer logging.Recover()
fontFile, err := os.Open(fontPath)
defer fontFile.Close()
if err != nil {
//LogErrorf("open font file [%s] failed: %s", fontPath, err)
return
}
font, err := sfnt.Parse(fontFile)
if err != nil {
//LogErrorf("parse font [%s] failed: %s", fontPath, err)
return
}
t, err := font.NameTable()
if err != nil {
logging.LogErrorf("get font [%s] name table failed: %s", fontPath, err)
return
}
for _, e := range t.List() {
if sfnt.NameFontFamily != e.NameID && sfnt.NamePreferredFamily != e.NameID {
continue
}
if sfnt.PlatformLanguageID(1033) == e.LanguageID || sfnt.PlatformLanguageID(2052) == e.LanguageID {
v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value)
if err != nil {
return ""
}
val := string(v)
if sfnt.NameFontFamily == e.NameID && "" != val {
ret = val
}
if sfnt.NamePreferredFamily == e.NameID && "" != val {
ret = val
}
}
}
ret = strings.TrimSpace(ret)
if strings.HasPrefix(ret, ".") {
return ""
}
return
}