reduce label width
This commit is contained in:
parent
811e32e3da
commit
0e8c6ece0e
1 changed files with 98 additions and 28 deletions
126
app.py
126
app.py
|
|
@ -41,7 +41,7 @@ def mm_for_px(px, dpi=DPI):
|
|||
@lru_cache(maxsize=256)
|
||||
def make_code128_exact(
|
||||
grocycode: str,
|
||||
xdim_px: int = 4, # 4–6 px is a good, scannable range
|
||||
xdim_px: int = 3, # narrower bars reduce overall width; ~0.25mm at 300dpi
|
||||
dpi: int = DPI,
|
||||
) -> Image.Image:
|
||||
code = Code128(grocycode, writer=ImageWriter())
|
||||
|
|
@ -150,76 +150,146 @@ def _ellipsize(
|
|||
def make_text_panel(
|
||||
product: str, due_date: Optional[str], max_width: int = 360, padding: int = 4
|
||||
) -> Image.Image:
|
||||
"""Render up to two lines of text (product, due_date) into a fixed-height strip.
|
||||
"""Render product (wrapped to 2 lines) + due_date in a fixed-height strip.
|
||||
|
||||
- Keeps height = TARGET_H.
|
||||
- Ellipsizes to ensure the panel width <= max_width.
|
||||
- Chooses font sizes that fit vertically.
|
||||
- Wraps product to up to 2 lines; ellipsizes overflow.
|
||||
- Ellipsizes due_date; keeps panel width <= max_width.
|
||||
"""
|
||||
# Create a temporary image for measurement
|
||||
meas_img = Image.new("L", (max_width, TARGET_H), 255)
|
||||
draw = ImageDraw.Draw(meas_img)
|
||||
|
||||
# Try font size pairs, largest first, until both lines fit vertically
|
||||
product_sizes = [22, 20, 18, 16, 14, 12, 11, 10, 9, 8]
|
||||
# Try font size pairs, largest first, until content fits vertically
|
||||
product_sizes = [28, 26, 24, 22, 20, 18, 16, 14, 12, 11, 10, 9, 8]
|
||||
due_sizes = [14, 13, 12, 11, 10, 9, 8]
|
||||
chosen = None
|
||||
have_due = bool(due_date)
|
||||
line_gap = 3
|
||||
|
||||
def text_h(f: ImageFont.ImageFont) -> int:
|
||||
return f.getbbox("Ag")[3] if hasattr(f, "getbbox") else f.getsize("Ag")[1]
|
||||
|
||||
def wrap_ellipsize(
|
||||
text: str, font: ImageFont.ImageFont, max_w: int, max_lines: int
|
||||
) -> list[str]:
|
||||
if not text:
|
||||
return []
|
||||
words = text.split()
|
||||
lines: list[str] = []
|
||||
i = 0
|
||||
while len(lines) < max_lines:
|
||||
line = ""
|
||||
while i < len(words):
|
||||
cand = (line + " " + words[i]).strip()
|
||||
if draw.textlength(cand, font=font) <= max_w:
|
||||
line = cand
|
||||
i += 1
|
||||
else:
|
||||
if not line:
|
||||
# Hard-break a very long word
|
||||
low, high = 0, len(words[i])
|
||||
best = ""
|
||||
while low <= high:
|
||||
mid = (low + high) // 2
|
||||
seg = words[i][:mid]
|
||||
if draw.textlength(seg, font=font) <= max_w:
|
||||
best = seg
|
||||
low = mid + 1
|
||||
else:
|
||||
high = mid - 1
|
||||
line = best or words[i]
|
||||
if best:
|
||||
words[i] = words[i][len(best) :]
|
||||
else:
|
||||
i += 1
|
||||
break
|
||||
# If last allowed line and text remains, ellipsize tail into this line
|
||||
if len(lines) == max_lines - 1 and i < len(words):
|
||||
tail = (line + " " + " ".join(words[i:])).strip()
|
||||
line = _ellipsize(tail, font, max_w, draw)
|
||||
i = len(words)
|
||||
if line:
|
||||
lines.append(line)
|
||||
else:
|
||||
break
|
||||
if i >= len(words):
|
||||
break
|
||||
return lines
|
||||
|
||||
for ps in product_sizes:
|
||||
pf = _try_load_font(ps)
|
||||
ph = pf.getbbox("Ag")[3] if hasattr(pf, "getbbox") else pf.getsize("Ag")[1]
|
||||
ph = text_h(pf)
|
||||
prod_lines = wrap_ellipsize(
|
||||
product or "", pf, max_width - 2 * padding, max_lines=2
|
||||
)
|
||||
lines_count = max(1, len(prod_lines))
|
||||
prod_block_h = ph * lines_count + line_gap * (lines_count - 1)
|
||||
if have_due:
|
||||
for ds in due_sizes:
|
||||
df = _try_load_font(ds)
|
||||
dh = (
|
||||
df.getbbox("Ag")[3]
|
||||
if hasattr(df, "getbbox")
|
||||
else df.getsize("Ag")[1]
|
||||
)
|
||||
total_h = ph + dh + padding # small gap between lines
|
||||
dh = text_h(df)
|
||||
total_h = prod_block_h + line_gap + dh
|
||||
if total_h + 2 * padding <= TARGET_H:
|
||||
chosen = (pf, df)
|
||||
chosen = (pf, df, prod_lines)
|
||||
break
|
||||
if chosen:
|
||||
break
|
||||
else:
|
||||
if ph + 2 * padding <= TARGET_H:
|
||||
chosen = (pf, None)
|
||||
if prod_block_h + 2 * padding <= TARGET_H:
|
||||
chosen = (pf, None, prod_lines)
|
||||
break
|
||||
|
||||
if not chosen:
|
||||
# Fallback to default tiny font(s)
|
||||
chosen = (_try_load_font(8), _try_load_font(8) if have_due else None)
|
||||
pf = _try_load_font(8)
|
||||
df = _try_load_font(8) if have_due else None
|
||||
prod_lines = wrap_ellipsize(
|
||||
product or "", pf, max_width - 2 * padding, max_lines=2
|
||||
)
|
||||
chosen = (pf, df, prod_lines)
|
||||
|
||||
pf, df = chosen
|
||||
pf, df, prod_lines = chosen
|
||||
|
||||
# Ellipsize to max_width
|
||||
product_text = _ellipsize(product or "", pf, max_width - 2 * padding, draw)
|
||||
# Prepare due text (single line, ellipsized)
|
||||
due_text = (
|
||||
_ellipsize(due_date or "", df or pf, max_width - 2 * padding, draw)
|
||||
if have_due
|
||||
else None
|
||||
)
|
||||
|
||||
# Measure final sizes
|
||||
p_w = int(draw.textlength(product_text, font=pf))
|
||||
p_h = pf.getbbox("Ag")[3] if hasattr(pf, "getbbox") else pf.getsize("Ag")[1]
|
||||
d_w = int(draw.textlength(due_text, font=df or pf)) if have_due else 0
|
||||
d_h = (
|
||||
# Measure and compute panel width
|
||||
ph = pf.getbbox("Ag")[3] if hasattr(pf, "getbbox") else pf.getsize("Ag")[1]
|
||||
dh = (
|
||||
(df.getbbox("Ag")[3] if hasattr(df, "getbbox") else df.getsize("Ag")[1])
|
||||
if have_due
|
||||
else 0
|
||||
)
|
||||
p_w = max(
|
||||
(int(draw.textlength(line, font=pf)) for line in (prod_lines or [""])),
|
||||
default=0,
|
||||
)
|
||||
d_w = int(draw.textlength(due_text, font=df or pf)) if have_due else 0
|
||||
panel_w = min(max(p_w, d_w) + 2 * padding, max_width)
|
||||
|
||||
# Render the panel
|
||||
out = Image.new("L", (panel_w, TARGET_H), 255)
|
||||
odraw = ImageDraw.Draw(out)
|
||||
y = (TARGET_H - (p_h + (d_h + padding if have_due else 0))) // 2
|
||||
odraw.text((padding, y), product_text, fill=0, font=pf)
|
||||
content_h = ph * max(1, len(prod_lines)) + line_gap * (max(1, len(prod_lines)) - 1)
|
||||
if have_due and due_text:
|
||||
odraw.text((padding, y + p_h + padding), due_text, fill=0, font=df or pf)
|
||||
content_h += line_gap + dh
|
||||
y = (TARGET_H - content_h) // 2
|
||||
# Draw product lines
|
||||
for idx, line in enumerate(prod_lines or []):
|
||||
odraw.text((padding, y + idx * (ph + line_gap)), line, fill=0, font=pf)
|
||||
# Draw due date under product
|
||||
if have_due and due_text:
|
||||
prod_block_h = ph * max(1, len(prod_lines)) + line_gap * (
|
||||
max(1, len(prod_lines)) - 1
|
||||
)
|
||||
odraw.text(
|
||||
(padding, y + prod_block_h + line_gap), due_text, fill=0, font=df or pf
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue