mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-02 01:08:05 +00:00
feat(layout): fit_text_proportional gains an axis-specific scale override
self.scale (min(width_ratio, height_ratio)) is the right conservative default for anything whose aspect ratio matters, but a caller whose surrounding composition already scales along a single axis — e.g. football-scoreboard's logo_slot = min(height, width // 2), which tracks height alone — needs text sized the same way, or it reads as under-scaled next to logos that grew on a panel that only got taller (128x32 -> 128x64: self.scale stays 1.0 since width didn't grow, but logos still double). fit_text_proportional(..., scale=None) now accepts an explicit override; None keeps the existing self.scale default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,7 +87,7 @@ Built per (width, height); exposes facts and fit queries:
|
|||||||
at-or-below the panel's tier.
|
at-or-below the panel's tier.
|
||||||
- `fit_text(text, box, ladder, ellipsis=True)` → `FitResult` — largest rung
|
- `fit_text(text, box, ladder, ellipsis=True)` → `FitResult` — largest rung
|
||||||
that fits; ellipsizes as a last resort. Cached per (text, box, ladder).
|
that fits; ellipsizes as a last resort. Cached per (text, box, ladder).
|
||||||
- `fit_text_proportional(text, box, base_size_px, ladder, ellipsis=True)` —
|
- `fit_text_proportional(text, box, base_size_px, ladder, ellipsis=True, scale=None)` —
|
||||||
rung closest to (not exceeding) `base_size_px * scale`, still capped to
|
rung closest to (not exceeding) `base_size_px * scale`, still capped to
|
||||||
what fits the box. Use this instead of `fit_text` when several
|
what fits the box. Use this instead of `fit_text` when several
|
||||||
independently-fitted elements need to stay visually harmonious as the
|
independently-fitted elements need to stay visually harmonious as the
|
||||||
@@ -96,7 +96,12 @@ Built per (width, height); exposes facts and fit queries:
|
|||||||
out of proportion to a neighbor that scales by geometry (e.g. logos
|
out of proportion to a neighbor that scales by geometry (e.g. logos
|
||||||
sized via `px()`), even though each individual pick is "correct" in
|
sized via `px()`), even though each individual pick is "correct" in
|
||||||
isolation. `base_size_px` is normally the element's existing classic/
|
isolation. `base_size_px` is normally the element's existing classic/
|
||||||
fixed font size, so it scales in step with everything else.
|
fixed font size. `scale` defaults to `self.scale` (the conservative
|
||||||
|
min-of-both-axes factor `px()` uses); pass an axis-specific value when
|
||||||
|
the surrounding composition already scales that way — e.g. a scoreboard
|
||||||
|
whose logo slots track height alone (`min(height, width // 2)`) should
|
||||||
|
size its text by `height / design_height` too, or the text reads as
|
||||||
|
under-scaled next to bigger logos on a panel that only grew taller.
|
||||||
- `fit_lines(lines, box, ladder, spacing)` — every line fits the width and
|
- `fit_lines(lines, box, ladder, spacing)` — every line fits the width and
|
||||||
the stack fits the height (measures the actual strings).
|
the stack fits the height (measures the actual strings).
|
||||||
- `font_for_rows(rows, box_h, ladder)` — largest rung whose line height
|
- `font_for_rows(rows, box_h, ladder)` — largest rung whose line height
|
||||||
|
|||||||
+16
-6
@@ -382,8 +382,9 @@ class LayoutContext:
|
|||||||
|
|
||||||
def fit_text_proportional(self, text: str, box: Union[Region, Tuple[int, int]],
|
def fit_text_proportional(self, text: str, box: Union[Region, Tuple[int, int]],
|
||||||
base_size_px: int, ladder: FontLadder = LADDER_DEFAULT,
|
base_size_px: int, ladder: FontLadder = LADDER_DEFAULT,
|
||||||
ellipsis: bool = True) -> FitResult:
|
ellipsis: bool = True,
|
||||||
"""Ladder rung closest to (but not exceeding) ``base_size_px * self.scale``
|
scale: Optional[float] = None) -> FitResult:
|
||||||
|
"""Ladder rung closest to (but not exceeding) ``base_size_px * scale``
|
||||||
that still fits the box — proportional sizing instead of ``fit_text``'s
|
that still fits the box — proportional sizing instead of ``fit_text``'s
|
||||||
"always maximize" behavior.
|
"always maximize" behavior.
|
||||||
|
|
||||||
@@ -396,8 +397,16 @@ class LayoutContext:
|
|||||||
independently "correct". ``base_size_px`` is the size that element
|
independently "correct". ``base_size_px`` is the size that element
|
||||||
renders at on the design size (``design_size``, typically 128x32)
|
renders at on the design size (``design_size``, typically 128x32)
|
||||||
— commonly a plugin's existing classic/fixed font size for that
|
— commonly a plugin's existing classic/fixed font size for that
|
||||||
element, so proportional sizing tracks the same geometry scale as
|
element.
|
||||||
everything else in ``px()``.
|
|
||||||
|
``scale`` defaults to ``self.scale`` (the same conservative
|
||||||
|
min(width_ratio, height_ratio) factor ``px()`` uses — safe for
|
||||||
|
content whose aspect ratio matters). Pass an explicit axis-specific
|
||||||
|
value when the surrounding composition already scales that way —
|
||||||
|
e.g. a scoreboard whose logos scale with height alone
|
||||||
|
(``logo_slot = min(height, width // 2)``) should size its score
|
||||||
|
text by ``height / design_height`` too, or its text will look
|
||||||
|
under-scaled next to bigger logos on a panel that only grew taller.
|
||||||
|
|
||||||
Falls back to the smallest rung when even that exceeds the target
|
Falls back to the smallest rung when even that exceeds the target
|
||||||
(a tiny scale factor), and to fit_text's ordinary smaller-rung
|
(a tiny scale factor), and to fit_text's ordinary smaller-rung
|
||||||
@@ -405,11 +414,12 @@ class LayoutContext:
|
|||||||
box.
|
box.
|
||||||
"""
|
"""
|
||||||
box_w, box_h = _box_dims(box)
|
box_w, box_h = _box_dims(box)
|
||||||
key = ("text_prop", text, box_w, box_h, ladder, base_size_px, ellipsis)
|
effective_scale = self.scale if scale is None else scale
|
||||||
|
key = ("text_prop", text, box_w, box_h, ladder, base_size_px, ellipsis, effective_scale)
|
||||||
cached = self._fit_cache.get(key)
|
cached = self._fit_cache.get(key)
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
return cached
|
return cached
|
||||||
target = base_size_px * self.scale
|
target = base_size_px * effective_scale
|
||||||
eligible = [step for step in ladder if step.size_px <= target]
|
eligible = [step for step in ladder if step.size_px <= target]
|
||||||
candidates = eligible if eligible else (min(ladder, key=lambda s: s.size_px),)
|
candidates = eligible if eligible else (min(ladder, key=lambda s: s.size_px),)
|
||||||
result = self._walk_ladder(text, candidates, box_w, box_h, ellipsis)
|
result = self._walk_ladder(text, candidates, box_w, box_h, ellipsis)
|
||||||
|
|||||||
@@ -274,6 +274,20 @@ class TestFontFitting:
|
|||||||
second = ctx.fit_text_proportional("X", ctx.bounds, base_size_px=10)
|
second = ctx.fit_text_proportional("X", ctx.bounds, base_size_px=10)
|
||||||
assert first is second
|
assert first is second
|
||||||
|
|
||||||
|
def test_fit_text_proportional_scale_override(self, font_manager):
|
||||||
|
# 128x64 vs design 128x32: self.scale (min of both axes) is 1.0
|
||||||
|
# since width didn't grow, but a caller whose composition scales by
|
||||||
|
# HEIGHT alone (e.g. logo_slot = min(h, w//2)) should be able to
|
||||||
|
# override the reference scale so text grows with it too.
|
||||||
|
ctx = LayoutContext(128, 64, font_manager)
|
||||||
|
assert ctx.scale == 1.0
|
||||||
|
default_fit = ctx.fit_text_proportional("17-21", ctx.bounds, base_size_px=10,
|
||||||
|
ladder=LADDER_ARCADE)
|
||||||
|
height_scale = 64 / 32 # matches design height
|
||||||
|
scaled_fit = ctx.fit_text_proportional("17-21", ctx.bounds, base_size_px=10,
|
||||||
|
ladder=LADDER_ARCADE, scale=height_scale)
|
||||||
|
assert scaled_fit.size_px > default_fit.size_px
|
||||||
|
|
||||||
def test_fit_lines_stacks_within_height(self, ctx):
|
def test_fit_lines_stacks_within_height(self, ctx):
|
||||||
box = ctx.bounds
|
box = ctx.bounds
|
||||||
lines = ["LINE ONE", "LINE TWO", "LINE THREE"]
|
lines = ["LINE ONE", "LINE TWO", "LINE THREE"]
|
||||||
|
|||||||
Reference in New Issue
Block a user