mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
fix(release): raise width factors above every observed rendering
The narrow factors were themselves under-estimates. `r` was assigned 0.38 while measuring 0.389 em here and about 0.49 em in the environment that reported it, so a 54-`r` line estimated 1067px, rendered 1381px there, and was neither wrapped nor clamped. The guard test added in the previous commit did not catch it only because no sample held a long narrow run. Root cause: the SVG names `DM Sans` and nothing guarantees it is installed, so each host resolves the fallback chain differently and renders the same line at a different width. No estimate can be both tight and correct across environments, so the factors now sit well above the widest observation — every sample over-estimates by at least 1.25x locally, which covers the wider measurements reported in review. The guard test gains the cases that were failing (long `r` and `i` runs, punctuation, digits) and its failure message says to raise the factors rather than suggesting the test is wrong. The word-wrap test is rewritten as a property — whole words, in order, each line within budget — because pinning exact break points made every future calibration look like a regression. 190 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a32ab20b0b
commit
29499a0bae
3 files changed
+43
-19
No files matched your search
@@ -55,6 +55,12 @@ is not width. Card text wraps by *estimated rendered width*
|
||||
(`estimateTextWidth`), because 34 `W` at font-size 52 measures ~1948px where
|
||||
1072px are available — a character-capped line still ran off the canvas.
|
||||
|
||||
The SVG names `DM Sans`, but nothing guarantees it is installed: every host
|
||||
resolves the fallback chain differently, and the same line measures 0.389 em
|
||||
per `r` here against about 0.49 em elsewhere. No estimate can be both tight
|
||||
and correct across environments, so the factors sit well above the widest
|
||||
observation — locally the model over-estimates every sample by at least 1.25×.
|
||||
|
||||
That estimate is deliberately **inverted**: narrow characters are enumerated
|
||||
and everything else is assumed wide. Enumerating the wide ones instead cannot
|
||||
converge — successive review passes each found another under-estimated glyph
|
||||
|
||||
@@ -81,12 +81,19 @@ export function escapeXml(text) {
|
||||
* estimate can only ever run high, and running high costs an early line
|
||||
* break nobody sees.
|
||||
*/
|
||||
const WIDEST_FACTOR = 1.15;
|
||||
const WIDEST_FACTOR = 1.25;
|
||||
|
||||
/** ASCII advance factors, padded above the measured bold rendering. */
|
||||
/**
|
||||
* ASCII advance factors. The SVG names `DM Sans` but nothing guarantees it is
|
||||
* installed, so every host resolves the fallback chain differently and the
|
||||
* same line renders at different widths: `r` measures 0.389 em here and about
|
||||
* 0.49 em in the environment that reported this. These factors sit above the
|
||||
* widest of those observations with margin, because the failure that matters
|
||||
* is one-directional — an over-estimate wraps early, an under-estimate crops.
|
||||
*/
|
||||
const ASCII_FACTORS = new Map([
|
||||
[' ', 0.3],
|
||||
...[...".,;:'\"`!|()[]{}/\\-ilIjtfr"].map((character) => [character, 0.38]),
|
||||
[' ', 0.4],
|
||||
...[...".,;:'\"`!|()[]{}/\\-ilIjtfr"].map((character) => [character, 0.55]),
|
||||
...[...'MWmw@%&#'].map((character) => [character, WIDEST_FACTOR]),
|
||||
]);
|
||||
|
||||
@@ -102,15 +109,15 @@ function advanceFactor(character) {
|
||||
}
|
||||
|
||||
if (character >= 'a' && character <= 'z') {
|
||||
return 0.62;
|
||||
return 0.75;
|
||||
}
|
||||
|
||||
if (character >= 'A' && character <= 'Z') {
|
||||
return 0.82;
|
||||
return 0.92;
|
||||
}
|
||||
|
||||
if (character >= '0' && character <= '9') {
|
||||
return 0.62;
|
||||
return 0.75;
|
||||
}
|
||||
|
||||
// Everything else: accented Latin, ligatures, Cyrillic, Greek, CJK, kana,
|
||||
|
||||
@@ -80,17 +80,17 @@ describe('wrapText', () => {
|
||||
});
|
||||
|
||||
it('wraps on word boundaries within the width budget', () => {
|
||||
assert.deepEqual(wrapText('one two three four', budget(60, 10, 4)), [
|
||||
'one two',
|
||||
'three four',
|
||||
]);
|
||||
// A tighter budget breaks earlier, still only between words.
|
||||
assert.deepEqual(wrapText('one two three four', budget(30, 10, 4)), [
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
'four',
|
||||
]);
|
||||
// Asserted as a property, not an exact split: the break points move
|
||||
// whenever the advance factors are tuned, and pinning them would make
|
||||
// every future calibration look like a regression.
|
||||
const text = 'one two three four';
|
||||
const lines = wrapText(text, budget(60, 10, 4));
|
||||
|
||||
assert.ok(lines.length > 1, 'expected the text to wrap at all');
|
||||
assert.equal(lines.join(' '), text, 'words were lost or reordered');
|
||||
for (const line of lines) {
|
||||
assert.ok(estimateTextWidth(line, 10) <= 60, line);
|
||||
}
|
||||
});
|
||||
|
||||
it('breaks a word wider than the budget instead of overflowing the card', () => {
|
||||
@@ -655,6 +655,12 @@ describe('estimateTextWidth against real rendering', () => {
|
||||
const samples = [
|
||||
['Advanced subtitles with external files', 52],
|
||||
['Live TV recordings in the download manager', 52],
|
||||
// A long run of a "narrow" glyph: the case that proved the
|
||||
// narrow factors were themselves under-estimates.
|
||||
['r'.repeat(54), 52],
|
||||
['i'.repeat(54), 52],
|
||||
['....------', 52],
|
||||
['0123456789', 52],
|
||||
['W'.repeat(34), 52],
|
||||
['M'.repeat(34), 62],
|
||||
['æ'.repeat(34), 52],
|
||||
@@ -670,9 +676,14 @@ describe('estimateTextWidth against real rendering', () => {
|
||||
const measured = await measureRenderedWidth(text, fontSize);
|
||||
const estimate = estimateTextWidth(text, fontSize);
|
||||
|
||||
// Fonts resolve differently per host — the SVG names DM Sans and
|
||||
// nothing guarantees it is installed — so this asserts the model
|
||||
// stays conservative in whatever environment runs it. A failure
|
||||
// here means the factors in `advanceFactor` are too low for this
|
||||
// host's fallback and must be raised, not that the test is wrong.
|
||||
assert.ok(
|
||||
estimate >= measured,
|
||||
`"${text.slice(0, 30)}" at ${fontSize}px: estimate ${Math.round(estimate)}px < measured ${measured}px`
|
||||
`"${text.slice(0, 30)}" at ${fontSize}px: estimate ${Math.round(estimate)}px < measured ${measured}px — raise the advanceFactor values`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user