Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Vanessa 2026-04-20 19:44:27 +08:00
commit e9cc256964
4 changed files with 107 additions and 75 deletions

View file

@ -668,7 +668,15 @@ func setFollowSystemLockScreen(c *gin.Context) {
func getSysFonts(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = util.LoadSysFonts()
fonts := util.LoadSysFonts()
// TODO: 字重 https://github.com/siyuan-note/siyuan/issues/10313
var families []string
for _, font := range fonts {
families = append(families, font.Family)
}
families = gulu.Str.RemoveDuplicatedElem(families)
ret.Data = families
}
func version(c *gin.Context) {

View file

@ -11,7 +11,7 @@ require (
github.com/88250/lute v1.7.7-0.20260419134724-bb68012f231d
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
github.com/ConradIrwin/font v0.2.1
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22
github.com/Masterminds/sprig/v3 v3.3.0
github.com/PuerkitoBio/goquery v1.11.0
github.com/Xuanwo/go-locale v1.1.3
@ -78,7 +78,6 @@ require (
github.com/vmihailenco/msgpack/v5 v5.4.1
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342
github.com/xuri/excelize/v2 v2.10.1
golang.org/x/image v0.38.0
golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294
golang.org/x/mod v0.34.0
golang.org/x/net v0.53.0
@ -138,6 +137,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-sw/text-codec v0.0.1 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@ -194,6 +194,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/image v0.38.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View file

@ -24,8 +24,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 h1:r10k4+Lu1mDpiCKa1liAdGJUhB4BJHHJnMvtoli6Hts=
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4/go.mod h1:a3CzWIqeRxiODAscAIfZ4wbFRXxywBrdCwTENVAWB2g=
github.com/ConradIrwin/font v0.2.1 h1:D4tWi7zyRAdVKOtOys5960HnAAfUSRx/syaf+J9JqlI=
github.com/ConradIrwin/font v0.2.1/go.mod h1:krTLO7JWu6g8RMxG8sl+T1Hf8W93XQacBKJmqFZ2MFY=
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22 h1:xEDrMXxOJsMByKW9Uw2WvwuVhfRd0SN5sOWVR8rYSjc=
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22/go.mod h1:5iRYC36M+hBFrRcE25N9/kioASZaqIkAXbgOyfVDCXg=
github.com/JalfResi/justext v0.0.0-20221106200834-be571e3e3052 h1:8T2zMbhLBbH9514PIQVHdsGhypMrsB4CxwbldKA9sBA=
github.com/JalfResi/justext v0.0.0-20221106200834-be571e3e3052/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
@ -186,6 +186,8 @@ github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3
github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sw/text-codec v0.0.1 h1:H+wnKj5TuvqYlfhMtbc52m1Oe0YnxD7QPzZPBE8QAn8=
github.com/go-sw/text-codec v0.0.1/go.mod h1:RTuIwijiCSy7wYLZd3h4RmWHBCD1bERsEST7vqnOzmo=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
@ -529,7 +531,6 @@ golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20180302201248-b7ef84aaf62a/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=

View file

@ -19,25 +19,24 @@ package util
import (
"os"
"sort"
"strconv"
"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
sysFonts []*Font
sysFontsLock = sync.Mutex{}
)
func LoadSysFonts() (ret []string) {
func LoadSysFonts() []*Font {
sysFontsLock.Lock()
defer sysFontsLock.Unlock()
@ -46,21 +45,20 @@ func LoadSysFonts() (ret []string) {
}
start := time.Now()
fonts := loadFonts()
ret = []string{}
for _, font := range fonts {
ret = append(ret, font.Family)
}
ret = gulu.Str.RemoveDuplicatedElem(ret)
sort.Strings(ret)
sysFonts = ret
sysFonts = loadFonts()
sort.Slice(sysFonts, func(i, j int) bool {
return sysFonts[i].DisplayName < sysFonts[j].DisplayName
})
logging.LogInfof("loaded system fonts [%d] in [%dms]", len(sysFonts), time.Since(start).Milliseconds())
return
return sysFonts
}
type Font struct {
Path string
Family string
Family string `json:"family"` // 对应 CSS font-family
Weight int `json:"weight"` // 对应 CSS font-weight
DisplayName string `json:"displayName"` // 给人看的名称 (Family + Subfamily)
}
func loadFonts() (ret []*Font) {
@ -68,22 +66,22 @@ func loadFonts() (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) {
for _, f := range families {
if existFont(f, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
ret = append(ret, f)
//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) {
f := parseTTFFontFamily(fontPath)
if nil != f {
if existFont(f, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
ret = append(ret, f)
//logging.LogInfof("[%s] [%s]", fontPath, family)
}
}
@ -91,77 +89,63 @@ func loadFonts() (ret []*Font) {
return
}
func existFont(family string, fonts []*Font) bool {
func existFont(f *Font, fonts []*Font) bool {
for _, font := range fonts {
if strings.EqualFold(family, font.Family) {
if strings.EqualFold(f.Family, font.Family) && f.Weight == font.Weight {
return true
}
}
return false
}
func parseTTCFontFamily(fontPath string) (ret []string) {
func parseTTCFontFamily(fontPath string) (ret []*Font) {
defer logging.Recover()
data, err := os.ReadFile(fontPath)
fontFile, err := os.Open(fontPath)
if err != nil {
//logging.LogErrorf("read font file [%s] failed: %s", fontPath, err)
return
}
collection, err := ttc.ParseCollection(data)
defer fontFile.Close()
fonts, err := sfnt.ParseCollection(fontFile)
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.NameIDFull)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDTypographicFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
for _, f := range fonts {
font := parseFont(f)
if nil != font {
ret = append(ret, font)
}
}
ret = gulu.Str.RemoveDuplicatedElem(ret)
return
}
func parseTTFFontFamily(fontPath string) (ret string) {
func parseTTFFontFamily(fontPath string) *Font {
defer logging.Recover()
fontFile, err := os.Open(fontPath)
defer fontFile.Close()
if err != nil {
//LogErrorf("open font file [%s] failed: %s", fontPath, err)
return
return nil
}
defer fontFile.Close()
font, err := sfnt.Parse(fontFile)
if err != nil {
//LogErrorf("parse font [%s] failed: %s", fontPath, err)
return
//logging.LogErrorf("parse font [%s] failed: %s", fontFile.Name(), err)
return nil
}
return parseFont(font)
}
func parseFont(font *sfnt.Font) *Font {
t, err := font.NameTable()
if err != nil {
logging.LogErrorf("get font [%s] name table failed: %s", fontPath, err)
return
//logging.LogErrorf("parse font name table failed: %s", err)
return nil
}
var family, subfamily string
@ -192,15 +176,53 @@ func parseTTFFontFamily(fontPath string) (ret string) {
}
}
//if family != "" && !strings.HasPrefix(family, ".") {
// if subfamily != "" && !strings.Contains(subfamily, "<") && !strings.EqualFold(subfamily, "Regular") {
// ret = family + "(" + subfamily + ")"
// } else {
// ret = family
// }
//}
// TODO: 字重加载方案
_ = subfamily
ret = family
return
weight := 400
os2, err := font.OS2Table()
if nil == err {
weight = int(os2.USWeightClass)
}
if weight == 400 && subfamily != "" {
s := strings.ToLower(subfamily)
// 自动匹配 W01-W09
for i := 1; i <= 9; i++ {
wStr := "w0" + strconv.Itoa(i)
if strings.Contains(s, wStr) {
weight = i * 100
break
}
}
// 自动匹配标准关键词
if weight == 400 { // 如果 W 系列没匹配到
switch {
case strings.Contains(s, "thin"):
weight = 100
case strings.Contains(s, "light"):
weight = 300
case strings.Contains(s, "medium"):
weight = 500
case strings.Contains(s, "semibold") || strings.Contains(s, "demi"):
weight = 600
case strings.Contains(s, "bold"):
weight = 700
case strings.Contains(s, "black") || strings.Contains(s, "heavy"):
weight = 900
}
}
}
if family != "" && !strings.HasPrefix(family, ".") {
displayName := family
if subfamily != "" && !strings.EqualFold(subfamily, "Regular") {
displayName = family + " " + subfamily
}
return &Font{
Family: family,
Weight: weight,
DisplayName: displayName,
}
}
return nil
}