Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getSFNTMetadata bug #264

Open
jilieryuyi opened this issue Jan 8, 2024 · 4 comments
Open

getSFNTMetadata bug #264

jilieryuyi opened this issue Jan 8, 2024 · 4 comments

Comments

@jilieryuyi
Copy link
Contributor

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

@tdewolff
Copy link
Owner

tdewolff commented Jan 8, 2024

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?

@jilieryuyi
Copy link
Contributor Author

jilieryuyi commented Jan 9, 2024

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
}

@jilieryuyi
Copy link
Contributor Author

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)
}

@tdewolff
Copy link
Owner

It gets cached in https://github.com/tdewolff/canvas/blob/master/font.go#L192, but perhaps that should move to the font/ subpackage...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants