fix(config): bound hint.size
- commit
- a1a57baddc8d5c37b10717b70eb78ec0d91fc8a2
- author
- brice · · 2026-10-19 15:32 UTC
- parent
- 1c416bd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +26 -4
M docs/config.md +1 -1
| @@ -58,7 +58,7 @@ See [examples/config](../examples/config) for every key with its default. | |||
| 58 | 58 | | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) | | |
| 59 | 59 | | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) | | |
| 60 | 60 | | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click | | |
| 61 | - | | `hint.size` | `auto` | Label text size in logical pixels (positive number), or `auto` for screen pixel density | | |
| 61 | + | | `hint.size` | `auto` | Label text size in logical pixels (6 to 72), or `auto` for screen pixel density | | |
| 62 | 62 | | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` | | |
| 63 | 63 | | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour | | |
| 64 | 64 | | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace | | |
| @@ -58,7 +58,7 @@ See [examples/config](../examples/config) for every key with its default. | |||||
| 58 | | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) | | 58 | | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) | | ||
| 59 | | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) | | 59 | | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) | | ||
| 60 | | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click | | 60 | | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click | | ||
| 61 | - | | `hint.size` | `auto` | Label text size in logical pixels (positive number), or `auto` for screen pixel density | | 61 | + | | `hint.size` | `auto` | Label text size in logical pixels (6 to 72), or `auto` for screen pixel density | |
| 62 | | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` | | 62 | | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` | | ||
| 63 | | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour | | 63 | | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour | | ||
| 64 | | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace | | 64 | | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace | | ||
M internal/adapters/config/config.go +2 -2
| @@ -863,8 +863,8 @@ func set(c *ports.Config, key, v string) error { | |||
| 863 | 863 | break | |
| 864 | 864 | } | |
| 865 | 865 | n, err := strconv.ParseFloat(v, 64) | |
| 866 | - | if err != nil || n <= 0 || math.IsInf(n, 0) || math.IsNaN(n) { | |
| 867 | - | return fmt.Errorf("must be auto or a finite positive number") | |
| 866 | + | if err != nil || math.IsNaN(n) || n < 6 || n > 72 { | |
| 867 | + | return fmt.Errorf("must be auto or a number from 6 to 72 (logical pixels)") | |
| 868 | 868 | } | |
| 869 | 869 | c.Hint.Size = n | |
| 870 | 870 | case "hint.background", "hint.foreground": | |
| @@ -863,8 +863,8 @@ func set(c *ports.Config, key, v string) error { | |||||
| 863 | break | 863 | break | ||
| 864 | } | 864 | } | ||
| 865 | n, err := strconv.ParseFloat(v, 64) | 865 | n, err := strconv.ParseFloat(v, 64) | ||
| 866 | - | if err != nil || n <= 0 || math.IsInf(n, 0) || math.IsNaN(n) { | 866 | + | if err != nil || math.IsNaN(n) || n < 6 || n > 72 { |
| 867 | - | return fmt.Errorf("must be auto or a finite positive number") | 867 | + | return fmt.Errorf("must be auto or a number from 6 to 72 (logical pixels)") |
| 868 | } | 868 | } | ||
| 869 | c.Hint.Size = n | 869 | c.Hint.Size = n | ||
| 870 | case "hint.background", "hint.foreground": | 870 | case "hint.background", "hint.foreground": | ||
M internal/adapters/config/config_test.go +10 -1
| @@ -986,7 +986,7 @@ func TestHintStyleConfig(t *testing.T) { | |||
| 986 | 986 | if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" { | |
| 987 | 987 | t.Fatalf("automatic: %+v %v", c.Hint, warnings) | |
| 988 | 988 | } | |
| 989 | - | for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.background = red", "hint.foreground = #fff"} { | |
| 989 | + | for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.size = 5.9", "hint.size = 72.1", "hint.size = 1e9", "hint.background = red", "hint.foreground = #fff"} { | |
| 990 | 990 | _, warnings := parseString(t, line+"\n") | |
| 991 | 991 | if len(warnings) == 0 { | |
| 992 | 992 | t.Errorf("expected validate-config warning for %s", line) | |
| @@ -994,6 +994,15 @@ func TestHintStyleConfig(t *testing.T) { | |||
| 994 | 994 | } | |
| 995 | 995 | } | |
| 996 | 996 | ||
| 997 | + | func TestHintSizeBounds(t *testing.T) { | |
| 998 | + | for _, v := range []string{"6", "72", "13"} { | |
| 999 | + | c, w := parseString(t, "hint.size = "+v+"\n") | |
| 1000 | + | if len(w) != 0 || c.Hint.Size == 0 { | |
| 1001 | + | t.Errorf("hint.size = %s: %+v %v", v, c.Hint, w) | |
| 1002 | + | } | |
| 1003 | + | } | |
| 1004 | + | } | |
| 1005 | + | ||
| 997 | 1006 | func TestHintTextClick(t *testing.T) { | |
| 998 | 1007 | if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift { | |
| 999 | 1008 | t.Fatalf("default %d", got) | |
| @@ -986,7 +986,7 @@ func TestHintStyleConfig(t *testing.T) { | |||||
| 986 | if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" { | 986 | if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" { | ||
| 987 | t.Fatalf("automatic: %+v %v", c.Hint, warnings) | 987 | t.Fatalf("automatic: %+v %v", c.Hint, warnings) | ||
| 988 | } | 988 | } | ||
| 989 | - | for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.background = red", "hint.foreground = #fff"} { | 989 | + | for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.size = 5.9", "hint.size = 72.1", "hint.size = 1e9", "hint.background = red", "hint.foreground = #fff"} { |
| 990 | _, warnings := parseString(t, line+"\n") | 990 | _, warnings := parseString(t, line+"\n") | ||
| 991 | if len(warnings) == 0 { | 991 | if len(warnings) == 0 { | ||
| 992 | t.Errorf("expected validate-config warning for %s", line) | 992 | t.Errorf("expected validate-config warning for %s", line) | ||
| @@ -994,6 +994,15 @@ func TestHintStyleConfig(t *testing.T) { | |||||
| 994 | } | 994 | } | ||
| 995 | } | 995 | } | ||
| 996 | 996 | ||||
| 997 | + | func TestHintSizeBounds(t *testing.T) { | |||
| 998 | + | for _, v := range []string{"6", "72", "13"} { | |||
| 999 | + | c, w := parseString(t, "hint.size = "+v+"\n") | |||
| 1000 | + | if len(w) != 0 || c.Hint.Size == 0 { | |||
| 1001 | + | t.Errorf("hint.size = %s: %+v %v", v, c.Hint, w) | |||
| 1002 | + | } | |||
| 1003 | + | } | |||
| 1004 | + | } | |||
| 1005 | + | ||||
| 997 | func TestHintTextClick(t *testing.T) { | 1006 | func TestHintTextClick(t *testing.T) { | ||
| 998 | if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift { | 1007 | if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift { | ||
| 999 | t.Fatalf("default %d", got) | 1008 | t.Fatalf("default %d", got) | ||
M internal/adapters/hintlabel/style.go +8 -0
| @@ -15,6 +15,14 @@ type Style struct { | |||
| 15 | 15 | // FontPixels resolves an explicit logical size or the output's pixel density. | |
| 16 | 16 | // Both millimetre dimensions must be plausible for the untransformed mode. | |
| 17 | 17 | func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 { | |
| 18 | + | return min(maxFontPx, fontPixels(width, height, mmWidth, mmHeight, scale, logical)) | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // maxFontPx bounds the physical font size, whatever the configuration or the | |
| 22 | + | // scale ask for. | |
| 23 | + | const maxFontPx = 256 | |
| 24 | + | ||
| 25 | + | func fontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 { | |
| 18 | 26 | if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) { | |
| 19 | 27 | scale = 1 | |
| 20 | 28 | } | |
| @@ -15,6 +15,14 @@ type Style struct { | |||||
| 15 | // FontPixels resolves an explicit logical size or the output's pixel density. | 15 | // FontPixels resolves an explicit logical size or the output's pixel density. | ||
| 16 | // Both millimetre dimensions must be plausible for the untransformed mode. | 16 | // Both millimetre dimensions must be plausible for the untransformed mode. | ||
| 17 | func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 { | 17 | func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 { | ||
| 18 | + | return min(maxFontPx, fontPixels(width, height, mmWidth, mmHeight, scale, logical)) | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // maxFontPx bounds the physical font size, whatever the configuration or the | |||
| 22 | + | // scale ask for. | |||
| 23 | + | const maxFontPx = 256 | |||
| 24 | + | ||||
| 25 | + | func fontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 { | |||
| 18 | if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) { | 26 | if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) { | ||
| 19 | scale = 1 | 27 | scale = 1 | ||
| 20 | } | 28 | } | ||
M internal/adapters/hintlabel/style_test.go +5 -0
| @@ -73,3 +73,8 @@ func TestStyledRasterSizeAndCache(t *testing.T) { | |||
| 73 | 73 | require.NotEqual(t, b, c) | |
| 74 | 74 | require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha") | |
| 75 | 75 | } | |
| 76 | + | ||
| 77 | + | func TestFontPixelsClamped(t *testing.T) { | |
| 78 | + | require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 3, 1e9), .00001) | |
| 79 | + | require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 4, 72), .00001) | |
| 80 | + | } | |
| @@ -73,3 +73,8 @@ func TestStyledRasterSizeAndCache(t *testing.T) { | |||||
| 73 | require.NotEqual(t, b, c) | 73 | require.NotEqual(t, b, c) | ||
| 74 | require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha") | 74 | require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha") | ||
| 75 | } | 75 | } | ||
| 76 | + | ||||
| 77 | + | func TestFontPixelsClamped(t *testing.T) { | |||
| 78 | + | require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 3, 1e9), .00001) | |||
| 79 | + | require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 4, 72), .00001) | |||
| 80 | + | } | |||