fix: v4.5.0 - cross-day drag (ghost element, no overflow issue); adaptive block labels; PDF fit_text truncation
Some checks failed
Build & Push Docker / build (push) Has been cancelled
Some checks failed
Build & Push Docker / build (push) Has been cancelled
This commit is contained in:
@@ -153,6 +153,25 @@ def draw_clipped_text(c, text, x, y, w, h, font, size, rgb, align='center'):
|
||||
c.restoreState()
|
||||
|
||||
|
||||
def fit_text(c, text: str, font: str, size: float, max_w: float) -> str:
|
||||
"""Truncate text with ellipsis so it fits within max_w points."""
|
||||
if not text:
|
||||
return text
|
||||
if c.stringWidth(text, font, size) <= max_w:
|
||||
return text
|
||||
# Binary-search trim
|
||||
ellipsis = '…'
|
||||
ellipsis_w = c.stringWidth(ellipsis, font, size)
|
||||
lo, hi = 0, len(text)
|
||||
while lo < hi:
|
||||
mid = (lo + hi + 1) // 2
|
||||
if c.stringWidth(text[:mid], font, size) + ellipsis_w <= max_w:
|
||||
lo = mid
|
||||
else:
|
||||
hi = mid - 1
|
||||
return (text[:lo] + ellipsis) if lo < len(text) else text
|
||||
|
||||
|
||||
def generate_pdf(doc) -> bytes:
|
||||
if not doc.blocks:
|
||||
raise ScenarsError("No blocks provided")
|
||||
@@ -373,19 +392,29 @@ def generate_pdf(doc) -> bytes:
|
||||
dim_rgb = ((text_rgb[0] * 0.78, text_rgb[1] * 0.78, text_rgb[2] * 0.78)
|
||||
if is_light(pt.color) else (0.82, 0.82, 0.82))
|
||||
|
||||
# Determine vertical layout: how many lines fit?
|
||||
has_responsible = bool(block.responsible)
|
||||
sup_size = max(4.0, block_title_font * 0.65)
|
||||
# Available width for text (inset + 2pt padding each side)
|
||||
text_w_avail = max(1.0, bw - 2 * inset - 4)
|
||||
|
||||
sup_size = max(4.0, block_title_font * 0.65)
|
||||
resp_size = max(4.0, block_time_font)
|
||||
|
||||
# Truncate title to fit (leave room for superscript number)
|
||||
sup_reserve = (c.stringWidth(str(fn_num), _FONT_BOLD, sup_size) + 1.5
|
||||
if fn_num else 0)
|
||||
fitted_title = fit_text(c, title_text, _FONT_BOLD, block_title_font,
|
||||
text_w_avail - sup_reserve)
|
||||
|
||||
# Determine vertical layout: how many lines fit?
|
||||
has_responsible = bool(block.responsible)
|
||||
if has_responsible and row_h >= block_title_font + resp_size + 3:
|
||||
# Two-line: title (with superscript) + responsible
|
||||
# Two-line: title + responsible
|
||||
title_y = row_y + row_h * 0.55
|
||||
resp_y = row_y + row_h * 0.55 - block_title_font - 1
|
||||
# responsible
|
||||
fitted_resp = fit_text(c, block.responsible, _FONT_ITALIC, resp_size,
|
||||
text_w_avail)
|
||||
c.setFont(_FONT_ITALIC, resp_size)
|
||||
set_fill(c, dim_rgb)
|
||||
c.drawCentredString(bx + bw / 2, resp_y, block.responsible)
|
||||
c.drawCentredString(bx + bw / 2, resp_y, fitted_resp)
|
||||
else:
|
||||
# Single line: title centred
|
||||
title_y = row_y + (row_h - block_title_font) / 2
|
||||
@@ -395,16 +424,15 @@ def generate_pdf(doc) -> bytes:
|
||||
set_fill(c, text_rgb)
|
||||
if fn_num is not None:
|
||||
# Draw title then superscript footnote number
|
||||
title_w = c.stringWidth(title_text, _FONT_BOLD, block_title_font)
|
||||
tx = bx + bw / 2 - title_w / 2
|
||||
c.drawString(tx, title_y, title_text)
|
||||
# Superscript: small number raised by ~font_size * 0.5
|
||||
title_w = c.stringWidth(fitted_title, _FONT_BOLD, block_title_font)
|
||||
tx = bx + bw / 2 - (title_w + sup_reserve) / 2
|
||||
c.drawString(tx, title_y, fitted_title)
|
||||
c.setFont(_FONT_BOLD, sup_size)
|
||||
set_fill(c, dim_rgb)
|
||||
c.drawString(tx + title_w + 0.5, title_y + block_title_font * 0.45,
|
||||
str(fn_num))
|
||||
else:
|
||||
c.drawCentredString(bx + bw / 2, title_y, title_text)
|
||||
c.drawCentredString(bx + bw / 2, title_y, fitted_title)
|
||||
|
||||
c.restoreState()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user