This is why printed labels were illegible. Pillow uses a built-in bitmap
font when it cannot load a TrueType one, and that font ignores the size it
is asked for - at 300dpi it draws letters under a millimetre tall, which
no Text size setting could fix. python:3.12-slim ships no fonts at all.
load_font now tries LABEL_FONT, a wider list of system paths, a scan of
the font directories, matplotlib's bundled DejaVu, and finally Pillow's
scalable default. font_report() says which one won, so the pages can warn
instead of quietly printing micro-text.
render_fixed also takes a dpi, so the Dymo path draws at 300 rather than
being resampled from 313.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
Two things were costing sharpness on the Dymo. Labels were rendered at
the tape printer's 313dpi and resampled to 300, softening every letter;
they are now drawn at the target resolution. And the PDF embedded a JPEG,
whose ringing around 2mm text the thermal printer turned to mush - the
pages are now bitonal, which Pillow writes as lossless CCITT G4 at about
3KB a page instead of 45KB.
The new Ink setting decides how the page is reduced: threshold (crisp
text), Floyd-Steinberg (photos) or leave it in colour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
Measured it: on a 40mm label most names already span the width by about
1.4x, and everything above that did nothing at all. Cap the control at 2x
with Normal / Large / Largest, and say plainly what the limit is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
Stock presets, column packing, 300dpi rendering and a PDF whose page size
is exactly the sticker size. Labels are drawn by tape_printer.render_fixed
so they match the tape-printed ones.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
Tick labels under the preview to put them on one label; the grouping is
rebuilt from the whole plan, not just the twelve previewed. Text size
gains a slider and Normal/Large/Huge/Max presets. The print button
disables itself after a click so a double-click can't spend the tape
twice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
parse_group_spec / spec_from_groups let the page combine any parts onto
one label, and plan_groups never breaks a hand-made group up.
Text size now goes to 3x and means it: the name is sized first, the
image shrinks out of its way, and the floor no longer rises with the
setting - which is what truncated long names at large sizes. The meta
line takes the leftover room instead of pushing itself off the label.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
The VC-500W accepts one job at a time; anything sent while the tape is
moving comes back server-error-busy. Add IPPBusy, wait_until_idle and
print_job_paced so a batch goes through instead of failing from the
second label on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
A drawer with two part types is one label, so the page, the preview, the
print loop and the PDF all work from plan_groups rather than iterating
the selection. The preview takes the parts that share a label, and the
page reports how much tape the run will use.
Drawer labels are now a set physical size rather than however long the
text happens to be:
one part type in the drawer 40 x 15 mm
two part types 40 x 20 mm
They lie across the tape, so the 40mm spans the width and each label only
costs its height in tape length - 1.5m per 100 standard labels instead of
4m. That needs 50mm tape, which is now the default; a narrower tape is
detected and warned about rather than silently clipping.
Two part types in one drawer are matched on the location field and
combined onto a single taller label. Nothing is ever dropped: in
"always 40 x 15" a shared drawer simply yields two labels, and a third
part type grows the label by 5mm rather than vanishing.
Part names are wrapped to two lines and the type auto-sized to fit, so
"Plate, Modified with Clip" prints in full instead of truncating.
"Fit the tape width" keeps the previous content-sized behaviour.
Printing redirected back to a bare tape page, so every option - rotation
most visibly - reverted to the default the moment you pressed Print.
Carry the chosen options through the redirect instead.
Also read tape_rotate_default so an untick can be made permanent in
Settings; it only applies to a fresh arrival, never over an explicit
choice on the page.
Same absent-checkbox bug as the tape page, caught alongside it. The
hidden form marker lets an empty field list be honoured as a real
choice instead of being treated as "nothing was submitted".
An unticked checkbox is absent from a form submission entirely, so
"rotate unticked" and "rotate never mentioned" arrived at the parser
looking identical - and rotate defaults to on, so it snapped straight
back every time. Add a hidden marker the options form always sends, so
absent-but-submitted can be read as unticked.
Same bug silently reset the field list when every field was unticked;
that is fixed by the same marker. rotate_default lets a saved site-wide
preference apply on a fresh arrival without overriding an explicit
untick on the page.