Open jilieryuyi opened 6 months ago
Thanks for raising this issue. Could you be more explicit? I believe the current implementation only looks for English names, and perhaps you're looking for a name in another language (Chinese)? This is more work to implement than it looks like, but I'll verify what would be required to do that.
Regarding the "matching method is not sound". Why? What problem are you having apart from the above?
Thanks for raising this issue. Could you be more explicit? I believe the current implementation only looks for English names, and perhaps you're looking for a name in another language (Chinese)? This is more work to implement than it looks like, but I'll verify what would be required to do that.
Regarding the "matching method is not sound". Why? What problem are you having apart from the above?
What I want to express is that a font contains multiple names, and they should all support matching these names
I have made the following attempts,but it runs slowly, try to write a simple font.ParseFont api for metadata ?
func FindSystemFonts(dirs []string) (*SystemFonts, error) {
// TODO: use concurrency
fonts := &SystemFonts{
Fonts: map[string]map[Style]FontMetadata{},
}
walkedDirs := map[string]bool{}
walkDir := func(dir string) error {
return fs.WalkDir(os.DirFS(dir), ".", func(path string, d fs.DirEntry, err error) error {
path = filepath.Join(dir, path)
if err != nil {
return err
} else if d.IsDir() {
if walkedDirs[path] {
return filepath.SkipDir
}
walkedDirs[path] = true
return nil
} else if !d.Type().IsRegular() {
return nil
}
b, err := os.ReadFile(path)
if err != nil {
return nil
}
SFNT, err := font.ParseFont(b, 0)
if err != nil {
return nil
}
var names []string
var style string
for id := 0; id < 25; id++ {
for _, record := range SFNT.Name.Get(font.NameID(id)) {
if id == 1 || id == 4 || id == 6 {
names = append(names, record.String())
}
if id == int(NameFontSubfamily) || id == int(NamePreferredSubfamily) {
style = record.String()
}
}
}
var metadata FontMetadata //, err := getMetadata(f)
metadata.Filename = path
metadata.Families = names
metadata.Style = ParseStyle(style)
fonts.Add(metadata)
return nil
})
}
var Err error
for _, dir := range dirs {
if info, err := os.Stat(dir); os.IsNotExist(err) {
continue
} else if !info.IsDir() {
continue
}
if err := walkDir(dir); err != nil && Err == nil {
Err = err
}
}
if Err != nil {
return nil, Err
}
fonts.Generics = DefaultGenericFonts()
return fonts, nil
}
type FontMetadata struct {
Filename string
Families []string
Style Style
}
func (s *SystemFonts) Add(metadata FontMetadata) {
for _, Family := range metadata.Families {
if _, ok := s.Fonts[Family]; !ok {
s.Fonts[Family] = map[Style]FontMetadata{}
}
s.Fonts[Family][metadata.Style] = metadata
}
}
func (s *SystemFonts) Match(name string, style Style) (FontMetadata, bool) {
// expand generic font names
families := strings.Split(name, ",")
for i := 0; i < len(families); i++ {
families[i] = strings.TrimSpace(families[i])
if names, ok := s.Generics[families[i]]; ok {
families = append(families[:i], append(names, families[i+1:]...)...)
i += len(names) - 1
}
}
families = append(families, name+" "+style.String())
// find the first font name that exists
var metadatas []map[Style]FontMetadata
for _, family := range families {
metadata, _ := s.Fonts[family]
if metadata != nil {
metadatas = append(metadatas, metadata)
}
}
if metadatas == nil {
return FontMetadata{}, false
}
// exact style match
for _, m := range metadatas {
if metadata, ok := m[style]; ok {
return metadata, true
}
}
styles := []Style{}
weight := style.Weight()
if weight == Regular {
styles = append(styles, Medium)
} else if weight == Medium {
styles = append(styles, Regular)
}
if weight == SemiBold || weight == Bold || weight == ExtraBold || weight == Black {
for s := weight + 1; s <= Black; s++ {
styles = append(styles, s)
}
for s := weight - 1; Thin <= s; s-- {
styles = append(styles, s)
}
} else {
for s := weight - 1; Thin <= s; s-- {
styles = append(styles, s)
}
for s := weight + 1; s <= Black; s++ {
styles = append(styles, s)
}
}
for _, s := range styles {
for _, m := range metadatas {
if metadata, ok := m[style&Italic|s]; ok {
return metadata, true
}
}
}
return FontMetadata{}, false
}
try to cache system fonts, run faster
// FindSystemFont finds the path to a font from the system's fonts.
func FindSystemFont(name string, style FontStyle) (string, bool) {
systemFonts.Lock()
if systemFonts.SystemFonts == nil {
systemFonts.SystemFonts, _ = loadSystemFontsCache()
if systemFonts.SystemFonts == nil {
systemFonts.SystemFonts, _ = font.FindSystemFonts(font.DefaultFontDirs())
saveSystemFontsCache()
}
}
systemFonts.Unlock()
font, ok := systemFonts.Match(name, font.ParseStyleCSS(style.CSS(), style.Italic()))
return font.Filename, ok
}
func loadSystemFontsCache() (*font.SystemFonts, error) {
cacheDir := os.TempDir()
cacheFile := "system_fonts.json"
c := filepath.Join(cacheDir, cacheFile)
data, err := os.ReadFile(c)
if err != nil {
return nil, err
}
var fonts font.SystemFonts
err = json.Unmarshal(data, &fonts)
if err != nil {
return nil, err
}
return &fonts, err
}
func saveSystemFontsCache() {
js, err := json.Marshal(systemFonts.SystemFonts)
if err != nil {
return
}
cacheDir := os.TempDir()
cacheFile := "system_fonts.json"
c := filepath.Join(cacheDir, cacheFile)
os.WriteFile(c, []byte(js), 0777)
}
It gets cached in https://github.com/tdewolff/canvas/blob/master/font.go#L192, but perhaps that should move to the font/
subpackage...
There is a bug in getSFNTMetadata the font loading on Windows is incomplete only the first name is used, and the matching method is not sound
msya.ttf names like:![image](https://github.com/tdewolff/canvas/assets/2398119/977e8ebd-038d-4c73-abeb-7439752e6df5)