doctor: match font names the way qt lists them (#3972)

port 2.0
This commit is contained in:
Leonard authored and GitHub committed 2026-10-29 12:58:28 -04:00
1 parent 8e8442fa76
commit b18fc38693
2 files changed
+93 -34

No files matched your search

+47 -19
View File
@@ -1761,6 +1761,9 @@ var bundledFontRelPaths = map[string][]string{
"notable": {
"DCommon/assets/fonts/notable/Notable-Regular.ttf",
},
"gochi hand": {
"DCommon/assets/fonts/gochi-hand/GochiHand-Regular.ttf",
},
}
func resolveDoctorShellPath() string {
@@ -1795,22 +1798,47 @@ func isBundledDefaultFont(family string) bool {
return ok
}
func fontInFcList(name, cacheLower string) bool {
target := strings.ToLower(strings.TrimSpace(name))
if target == "" {
return false
const fcListQtFormat = "%{family}\t%{style}\t%{foundry}\n"
var qtGenericFamilies = map[string]bool{"sans serif": true, "serif": true, "monospace": true}
func qtFontFamilies() (map[string]bool, error) {
output, err := exec.Command("fc-list", "--format", fcListQtFormat).Output()
if err != nil {
return nil, err
}
for line := range strings.SplitSeq(cacheLower, "\n") {
for fam := range strings.SplitSeq(strings.TrimSpace(line), ",") {
if strings.TrimSpace(fam) == target {
return true
return parseQtFontFamilies(string(output)), nil
}
// parseQtFontFamilies retrieves, from the provided fcOutput list, a new map in the style of Qt.fontFamilies()
func parseQtFontFamilies(fcOutput string) map[string]bool {
families := map[string]bool{}
for line := range strings.SplitSeq(fcOutput, "\n") {
fields := strings.Split(line, "\t")
if len(fields) != 3 || fields[0] == "" {
continue
}
styleCount := len(strings.Split(fields[1], ","))
for i, fam := range strings.Split(fields[0], ",") {
if i > 0 && i >= styleCount {
break
}
families[strings.ToLower(fam)] = true
// Qt only lists "Family [Foundry]" for multi-foundry families but resolves it either way
if fields[2] != "" {
families[strings.ToLower(fam+" ["+fields[2]+"]")] = true
}
}
}
return false
return families
}
func checkConfiguredFont(label, family, shellPath, fcCache string, fcListAvailable bool, url string) checkResult {
func fontInFcList(name string, families map[string]bool) bool {
target := strings.ToLower(strings.TrimSpace(name))
return qtGenericFamilies[target] || families[target]
}
func checkConfiguredFont(label, family, shellPath string, fcFamilies map[string]bool, fcListAvailable bool, url string) checkResult {
if bundled := findBundledFontFile(shellPath, family); bundled != "" {
details := "Bundled (Qt FontLoader)"
if doctorVerbose {
@@ -1845,7 +1873,7 @@ func checkConfiguredFont(label, family, shellPath, fcCache string, fcListAvailab
}
}
if fcCache == "" {
if len(fcFamilies) == 0 {
return checkResult{
catFonts, label, statusWarn,
fmt.Sprintf("'%s' not found", family),
@@ -1854,7 +1882,7 @@ func checkConfiguredFont(label, family, shellPath, fcCache string, fcListAvailab
}
}
if fontInFcList(family, fcCache) {
if fontInFcList(family, fcFamilies) {
return checkResult{catFonts, label, statusOK, family, "Available via fontconfig", url}
}
@@ -1900,18 +1928,18 @@ func checkFonts() []checkResult {
needFontconfig := !isBundledDefaultFont(fontFamily) || !isBundledDefaultFont(monoFontFamily) || !isBundledDefaultFont(displayFontFamily)
fcListAvailable := utils.CommandExists("fc-list")
fcCache := ""
var fcFamilies map[string]bool
if needFontconfig {
if !fcListAvailable {
results = append(results, checkResult{catFonts, "Fontconfig Tools", statusWarn, "fc-list not installed", "Cannot verify custom fonts in fontconfig cache.", url})
} else {
output, err := exec.Command("fc-list", ":", "family").Output()
families, err := qtFontFamilies()
if err != nil {
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Failed to query font list", "Fontconfig cache query failed. Try running 'fc-cache -fv'.", url})
} else {
fcCache = strings.ToLower(string(output))
if len(strings.TrimSpace(fcCache)) == 0 {
fcFamilies = families
if len(fcFamilies) == 0 {
results = append(results, checkResult{catFonts, "Fontconfig Cache", statusError, "Cache is empty", "No fonts found in fontconfig cache. Try running 'fc-cache -fv'.", url})
}
}
@@ -1919,9 +1947,9 @@ func checkFonts() []checkResult {
}
results = append(results,
checkConfiguredFont("Normal Font", fontFamily, shellPath, fcCache, fcListAvailable, url),
checkConfiguredFont("Monospace Font", monoFontFamily, shellPath, fcCache, fcListAvailable, url),
checkConfiguredFont("Display Font", displayFontFamily, shellPath, fcCache, fcListAvailable, url),
checkConfiguredFont("Normal Font", fontFamily, shellPath, fcFamilies, fcListAvailable, url),
checkConfiguredFont("Monospace Font", monoFontFamily, shellPath, fcFamilies, fcListAvailable, url),
checkConfiguredFont("Display Font", displayFontFamily, shellPath, fcFamilies, fcListAvailable, url),
)
return results
+46 -15
View File
@@ -3,26 +3,57 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestCheckBundledGoogleSans(t *testing.T) {
shellPath := t.TempDir()
fontPath := filepath.Join(shellPath, "DCommon", "assets", "fonts", "google-sans-flex", "GoogleSansFlex.ttf")
missing := checkConfiguredFont("UI font", "Google Sans Flex", shellPath, "", false, "")
if missing.status != statusWarn {
t.Fatalf("missing bundled font status = %v, want %v", missing.status, statusWarn)
func TestCheckBundledFonts(t *testing.T) {
for family, relPaths := range bundledFontRelPaths {
t.Run(family, func(t *testing.T) {
shellPath := t.TempDir()
if missing := checkConfiguredFont("Font", family, shellPath, nil, false, ""); missing.status != statusWarn {
t.Fatalf("missing bundled font status = %v, want %v", missing.status, statusWarn)
}
fontPath := filepath.Join(shellPath, relPaths[0])
if err := os.MkdirAll(filepath.Dir(fontPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(fontPath, []byte("font"), 0o644); err != nil {
t.Fatal(err)
}
for _, name := range []string{family, " " + strings.ToUpper(family) + " "} {
if result := checkConfiguredFont("Font", name, shellPath, nil, false, ""); result.status != statusOK {
t.Fatalf("bundled font %q status = %v, want %v", name, result.status, statusOK)
}
}
})
}
if err := os.MkdirAll(filepath.Dir(fontPath), 0o755); err != nil {
t.Fatal(err)
}
func TestQtFontFamilies(t *testing.T) {
fcOutput := "RobotoMono Nerd Font\tBold\tGOOG\n" +
"RobotoMono Nerd Font\tBold Italic\tpyrs\n" +
"Nimbus Sans\tRegular\tURW \n" +
"Nimbus Sans\tBold\tUKWN\n" +
"Plex,Plex Lt\tLight,Regular\tIBM\n" +
"Mono,Mono NF,Mono NF Light\tLight,Regular\tNONE\n"
families := parseQtFontFamilies(fcOutput)
cases := map[string]bool{
"RobotoMono Nerd Font [GOOG]": true,
"robotomono nerd font [pyrs]": true,
"RobotoMono Nerd Font": true,
"Nimbus Sans [URW ]": true,
"Plex Lt": true,
"Plex [IBM]": true,
"Mono NF": true,
"Mono NF Light": false,
"Monospace": true,
"Missing Font [GOOG]": false,
}
if err := os.WriteFile(fontPath, []byte("font"), 0o644); err != nil {
t.Fatal(err)
}
for _, family := range []string{"Google Sans Flex", " google sans flex "} {
result := checkConfiguredFont("UI font", family, shellPath, "", false, "")
if result.status != statusOK {
t.Fatalf("bundled font %q status = %v, want %v", family, result.status, statusOK)
for name, want := range cases {
if got := fontInFcList(name, families); got != want {
t.Errorf("fontInFcList(%q) = %v, want %v", name, got, want)
}
}
}