@@ -2,11 +2,77 @@ package main
22
33import (
44 "fmt"
5+ "math"
6+ "path/filepath"
7+ "strings"
58
69 "github.com/spf13/cobra"
710 "github.com/xaionaro-go/ndk/font"
811)
912
13+ // matchFontFromIterator iterates all system fonts and finds the best
14+ // match for the given family, weight, and italic style. This replaces
15+ // AFontMatcher_match which crashes from headless CLI binaries because
16+ // libminikin's FontCollection is not initialized without an app context.
17+ func matchFontFromIterator (
18+ family string ,
19+ weight uint16 ,
20+ italic bool ,
21+ ) (* font.Font , error ) {
22+ iter := font .ASystemFontIterator_open ()
23+ if iter == nil || iter .Pointer () == nil {
24+ return nil , fmt .Errorf ("ASystemFontIterator_open returned nil" )
25+ }
26+ defer iter .Close ()
27+
28+ var bestFont * font.Font
29+ bestScore := math .MaxInt32
30+
31+ for {
32+ f := iter .Next ()
33+ if f == nil || f .Pointer () == nil {
34+ break
35+ }
36+
37+ // Check family match via file path (font files are named after families).
38+ path := f .GetFontFilePath ()
39+ base := strings .TrimSuffix (filepath .Base (path ), filepath .Ext (path ))
40+ baseLower := strings .ToLower (base )
41+ familyLower := strings .ToLower (family )
42+
43+ // Skip fonts that don't match the family name.
44+ // Match by checking if the file name contains the family name.
45+ if ! strings .Contains (baseLower , familyLower ) &&
46+ ! strings .Contains (familyLower , baseLower ) {
47+ f .Close ()
48+ continue
49+ }
50+
51+ // Score: lower is better.
52+ // Weight distance + italic mismatch penalty.
53+ weightDist := int (weight ) - int (f .Weight ())
54+ if weightDist < 0 {
55+ weightDist = - weightDist
56+ }
57+ score := weightDist
58+ if f .IsItalic () != italic {
59+ score += 1000
60+ }
61+
62+ if score < bestScore {
63+ if bestFont != nil {
64+ bestFont .Close ()
65+ }
66+ bestFont = f
67+ bestScore = score
68+ } else {
69+ f .Close ()
70+ }
71+ }
72+
73+ return bestFont , nil
74+ }
75+
1076var fontMatchCmd = & cobra.Command {
1177 Use : "match" ,
1278 Short : "Match a font by family name, weight, and italic style" ,
@@ -15,36 +81,23 @@ var fontMatchCmd = &cobra.Command{
1581 weight , _ := cmd .Flags ().GetUint16 ("weight" )
1682 italic , _ := cmd .Flags ().GetBool ("italic" )
1783
18- matcher := font .NewMatcher ()
19- defer matcher .Close ()
20-
21- fmt .Println ("setting style..." )
22- matcher .SetStyle (weight , italic )
23- matcher .SetLocales ("en-US" )
24- fmt .Println ("style set OK" )
25-
26- // AFontMatcher_match requires the Android font system (libminikin).
27- // On headless CLI binaries (no app/Activity), the internal
28- // FontCollection is null, causing SIGSEGV in getFamilyForChar.
29- // This command only works within an Android app context.
30- fmt .Println ("WARNING: font matching requires an Android app context (Activity/Service)." )
31- fmt .Println ("On headless CLI binaries, AFontMatcher_match will crash with SIGSEGV" )
32- fmt .Println ("because the system FontCollection is not initialized." )
33- text := []uint16 {'A' }
34- var runLength uint32
3584 fmt .Printf ("matching family=%q weight=%d italic=%v...\n " , family , weight , italic )
3685
37- matched := matcher .Match (family , & text [0 ], uint32 (len (text )), & runLength )
38- fmt .Printf ("match returned, runLength=%d\n " , runLength )
39- if matched == nil || matched .Pointer () == nil {
86+ matched , err := matchFontFromIterator (family , weight , italic )
87+ if err != nil {
88+ return fmt .Errorf ("font matching: %w" , err )
89+ }
90+ if matched == nil {
4091 fmt .Println ("no matching font found" )
4192 return nil
4293 }
4394 defer matched .Close ()
4495
4596 fmt .Printf ("matched font:\n " )
97+ fmt .Printf (" path: %s\n " , matched .GetFontFilePath ())
4698 fmt .Printf (" weight: %d\n " , matched .Weight ())
47- fmt .Printf (" is italic: %v\n " , matched .IsItalic ())
99+ fmt .Printf (" italic: %v\n " , matched .IsItalic ())
100+ fmt .Printf (" locale: %s\n " , matched .GetLocale ())
48101
49102 return nil
50103 },
@@ -54,6 +107,8 @@ var fontListCmd = &cobra.Command{
54107 Use : "list" ,
55108 Short : "List system fonts using ASystemFontIterator" ,
56109 RunE : func (cmd * cobra.Command , args []string ) (_err error ) {
110+ limit , _ := cmd .Flags ().GetInt ("limit" )
111+
57112 iter := font .ASystemFontIterator_open ()
58113 if iter == nil || iter .Pointer () == nil {
59114 return fmt .Errorf ("ASystemFontIterator_open returned nil" )
@@ -66,11 +121,11 @@ var fontListCmd = &cobra.Command{
66121 if f == nil || f .Pointer () == nil {
67122 break
68123 }
69- fmt .Printf (" [%d] weight=%d italic=%v path= %s\n " ,
124+ fmt .Printf (" [%d] weight=%d italic=%-5v %s\n " ,
70125 count , f .Weight (), f .IsItalic (), f .GetFontFilePath ())
71126 f .Close ()
72127 count ++
73- if count >= 20 {
128+ if limit > 0 && count >= limit {
74129 fmt .Println (" ... (truncated)" )
75130 break
76131 }
@@ -81,10 +136,12 @@ var fontListCmd = &cobra.Command{
81136}
82137
83138func init () {
84- fontMatchCmd .Flags ().String ("family" , "sans-serif " , "font family name" )
85- fontMatchCmd .Flags ().Uint16 ("weight" , uint16 ( font . Normal ) , "font weight (100-900)" )
139+ fontMatchCmd .Flags ().String ("family" , "Roboto " , "font family name (matched against file name) " )
140+ fontMatchCmd .Flags ().Uint16 ("weight" , 400 , "font weight (100-900)" )
86141 fontMatchCmd .Flags ().Bool ("italic" , false , "request italic style" )
87142
143+ fontListCmd .Flags ().Int ("limit" , 0 , "max fonts to list (0=all)" )
144+
88145 fontCmd .AddCommand (fontMatchCmd )
89146 fontCmd .AddCommand (fontListCmd )
90147}
0 commit comments