Skip to content

fix(render): stop one-word titles running off the label - #18

Merged
impuls42 merged 1 commit into
mainfrom
fix/one-word-title-overflow
Sep 25, 2026
Merged

impuls42 merged 1 commit into
mainfrom
fix/one-word-title-overflow

Conversation

@impuls42

Copy link
Copy Markdown
Member

A title of INV-SI1 on the stock item preset at 12mm prints as INV-SI. The 1 is past the right edge.

_wrap has if not current or font.getlength(trial) <= width, so the first word of each paragraph goes in without being measured. Multi-word titles get checked from the second word on, which is why this hasn't shown up before. A single word never is, so _fit's binary search only has height to satisfy and climbs to max_pt. _fit(120, 60) on INV-SI1 returned one line at 45px, which is exactly 16pt, in a 120px box.

Measuring the first word fixes that and exposes a second problem. _wrap splits an over-long word at the pixel boundary, so in a box tall enough for two lines, INV-S / I1 at a big size passes the fit and beats a smaller size that keeps the word whole. For codes that's worse than small text, since a split code reads as two codes. _fit now runs the search with word splitting off first, and only falls back to splitting when nothing fits otherwise. measure() also stops reporting a natural width narrower than the longest word.

The existing tests couldn't catch any of this. test_very_long_text_is_truncated_not_overflowed only asserts the image size, which is fixed by construction. test_text_stays_inside_the_canvas only checks the top row for ink. The new tests look at line widths and at the right edge.

Mutation check, same four tests:

  • code on main: all 4 fail
  • first-word fix only, without the whole-words-first search: test_a_word_is_kept_whole_when_a_smaller_size_fits fails
  • this PR: all pass, full suite 275 passed

Found while laying out labels for sengine-cloud/inventree-label-dispatch#10, which puts INV-SI1-style codes in the title. A part without an IPN gets a title that is just INV-PA12, a single word.

_wrap accepted the first word of every paragraph without measuring it. A title
with more than one word got checked from the second word on, but a single word
never was, so _fit's search only had height to satisfy and grew the font to max_pt.
"INV-SI1" printed as "INV-SI" with the 1 past the edge, and any one-word part name
could do the same.

Measure the first word like the rest. That exposes a second problem: with room for
two lines, splitting "INV-SI1" into "INV-S" and "I1" at a large size also passes
the fit. So _fit now searches with whole words first and only allows splitting a
word when nothing fits otherwise. measure() stops reporting a natural width
narrower than the longest word, which it now would have wrapped mid-word.

The existing overflow tests could not catch this: one asserts only the image size,
which is fixed, and the other only checks the top row for ink. The new ones fail
against the old code, and the whole-word test fails against the first half of the
fix alone.
@impuls42
impuls42 merged commit 5dd3a49 into main Sep 25, 2026
1 check passed
@impuls42
impuls42 deleted the fix/one-word-title-overflow branch September 25, 2026 15:12
impuls42 added a commit that referenced this pull request Sep 25, 2026
With `qr_base_url = "https://sngn.top/i/"` a phone camera offers to open
the label instead of showing `SNGN.TOP/I/PA39` as plain text, which is
the whole point of the redirector. But the scheme costs a QR version,
and the quiet zone is fixed at the spec's 4 modules inside the presets,
so on 12mm tape the code lands at 2px per module.

This adds `render.qr_quiet_zone` (default 4, range 1 to 8) and threads
it the same way `qr_base_url` goes: config section, worker
`_render_cfg`, `RenderConfig`, `PresetContext`, and both preset
`QrElement` sites. The render CLI gets `--qr-quiet-zone` to match.
Default behaviour doesn't change.

Measured on the `part` preset at 12mm, decoding the image rebuilt from
the packed device bytes, with the half-resolution round trip standing in
for a phone:

| base | 4 | 3 | 2 |
|---|:--:|:--:|:--:|
| `SNGN.TOP/I/` | ✅ | ✅ | ✅ |
| `https://sngn.top/i/` | ❌ | ❌ | ✅ |

3 buys nothing. In pixels, the prefixed code's dark area is 50px
(2px/module) at 4 and 3, and 75px (3px/module) at 2, which is bigger
than the bare code's 63px at 4. The QR box is 81 to 92px here rather
than the full 96, because the text takes part of the row.

That table is pinned as
`test_a_scheme_in_the_base_costs_two_quiet_zone_modules`. The other new
tests check that the value actually reaches the worker's `RenderConfig`
(a value that stopped at the config section would still print at 4) and
that 0 and 9 are rejected at load.

This has been running on the D30 here as a local patch since this
afternoon. iOS picks up the printed label as a link straight away, and
zxing decodes a photo of it with no preprocessing.

Unrelated, but found while testing layouts: a one-word title can run off
the label. That fix is
#18.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant