feat: v4.2 - garant v editoru+PDF, footnote index v bloků, stránka s poznámkami
Some checks failed
Build & Push Docker / build (push) Has been cancelled

This commit is contained in:
2026-02-20 17:43:19 +01:00
parent 6c4ca5e9be
commit feb75219a7
6 changed files with 190 additions and 17 deletions

View File

@@ -295,6 +295,15 @@ def generate_pdf(doc) -> bytes:
set_fill(c, AXIS_TEXT)
c.drawCentredString(tx, table_top + (TIME_AXIS_H - time_axis_font) / 2, fmt_time(m))
# ── Footnote map: blocks with notes get sequential numbers ────────
footnotes = [] # [(num, block), ...]
footnote_map = {} # block.id → footnote number
for b in sorted(doc.blocks, key=lambda x: (x.date, x.start)):
if b.notes:
num = len(footnotes) + 1
footnotes.append((num, b))
footnote_map[b.id] = num
# ── Draw day rows ─────────────────────────────────────────────────
blocks_by_date = defaultdict(list)
for b in doc.blocks:
@@ -359,19 +368,43 @@ def generate_pdf(doc) -> bytes:
set_fill(c, text_rgb)
title_line = block.title + ('' if overnight else '')
if row_h > block_title_font * 2.2 and block.responsible:
# Two lines: title + responsible
c.setFont(_FONT_BOLD, block_title_font)
c.drawCentredString(bx + bw / 2, row_y + row_h / 2 + 1, title_line)
c.setFont(_FONT_REGULAR, block_time_font)
set_fill(c, (text_rgb[0] * 0.8, text_rgb[1] * 0.8, text_rgb[2] * 0.8)
if is_light(pt.color) else (0.85, 0.85, 0.85))
c.drawCentredString(bx + bw / 2, row_y + row_h / 2 - block_title_font + 1,
block.responsible)
fn_num = footnote_map.get(block.id)
title_text = block.title + ('' if overnight else '')
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)
resp_size = max(4.0, block_time_font)
if has_responsible and row_h >= block_title_font + resp_size + 3:
# Two-line: title (with superscript) + responsible
title_y = row_y + row_h * 0.55
resp_y = row_y + row_h * 0.55 - block_title_font - 1
# responsible
c.setFont(_FONT_ITALIC, resp_size)
set_fill(c, dim_rgb)
c.drawCentredString(bx + bw / 2, resp_y, block.responsible)
else:
c.setFont(_FONT_BOLD, block_title_font)
c.drawCentredString(bx + bw / 2, row_y + (row_h - block_title_font) / 2, title_line)
# Single line: title centred
title_y = row_y + (row_h - block_title_font) / 2
# Title
c.setFont(_FONT_BOLD, block_title_font)
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
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.restoreState()
@@ -402,11 +435,97 @@ def generate_pdf(doc) -> bytes:
ly - LEGEND_ITEM_H + 2 + (LEGEND_ITEM_H - 2 - 7) / 2,
pt.name)
# ── Footer ────────────────────────────────────────────────────────
# ── Footer (page 1) ───────────────────────────────────────────────
gen_date = datetime.now().strftime('%d.%m.%Y %H:%M')
c.setFont(_FONT_ITALIC, 6.5)
set_fill(c, FOOTER_TEXT)
c.drawCentredString(PAGE_W / 2, MARGIN - 2, f'Vygenerováno Scenár Creatorem v4 | {gen_date}')
footer_note = ' | Poznámky na str. 2' if footnotes else ''
c.drawCentredString(PAGE_W / 2, MARGIN - 2,
f'Vygenerováno Scenár Creatorem v4 | {gen_date}{footer_note}')
# ── Page 2: Poznámky ke scénáři ───────────────────────────────────
if footnotes:
c.showPage()
NOTE_MARGIN = 15 * mm
ny = PAGE_H - NOTE_MARGIN
# Page title
c.setFont(_FONT_BOLD, 14)
set_fill(c, HEADER_BG)
c.drawString(NOTE_MARGIN, ny - 14, 'Poznámky ke scénáři')
ny -= 14 + 4
# Subtitle: event title + date
ev_info = doc.event.title
date_display = doc.event.date_from or doc.event.date
date_to_display = doc.event.date_to
if date_display:
if date_to_display and date_to_display != date_display:
ev_info += f' | {date_display} {date_to_display}'
else:
ev_info += f' | {date_display}'
c.setFont(_FONT_REGULAR, 9)
set_fill(c, AXIS_TEXT)
c.drawString(NOTE_MARGIN, ny - 9, ev_info)
ny -= 9 + 8
# Separator line
set_stroke(c, GRID_HOUR)
c.setLineWidth(0.5)
c.line(NOTE_MARGIN, ny, PAGE_W - NOTE_MARGIN, ny)
ny -= 8
# Footnote entries
for fn_num, block in footnotes:
# Block header: number + title + day + time
day_label = format_date_cs(block.date)
time_str = f'{block.start}{block.end}'
resp_str = f' ({block.responsible})' if block.responsible else ''
header_text = f'{fn_num}. {block.title}{resp_str}{day_label}, {time_str}'
# Check space, add new page if needed
if ny < NOTE_MARGIN + 30:
c.showPage()
ny = PAGE_H - NOTE_MARGIN
c.setFont(_FONT_BOLD, 9)
set_fill(c, HEADER_BG)
c.drawString(NOTE_MARGIN, ny - 9, header_text)
ny -= 9 + 3
# Note text (wrapped manually)
note_text = block.notes or ''
words = note_text.split()
line_w = PAGE_W - 2 * NOTE_MARGIN - 10
c.setFont(_FONT_REGULAR, 8.5)
set_fill(c, (0.15, 0.15, 0.15))
line = ''
for word in words:
test_line = (line + ' ' + word).strip()
if c.stringWidth(test_line, _FONT_REGULAR, 8.5) > line_w:
if ny < NOTE_MARGIN + 15:
c.showPage()
ny = PAGE_H - NOTE_MARGIN
c.drawString(NOTE_MARGIN + 8, ny - 8.5, line)
ny -= 8.5 + 2
line = word
else:
line = test_line
if line:
if ny < NOTE_MARGIN + 15:
c.showPage()
ny = PAGE_H - NOTE_MARGIN
c.drawString(NOTE_MARGIN + 8, ny - 8.5, line)
ny -= 8.5 + 2
ny -= 5 # spacing between footnotes
# Footer on notes page
c.setFont(_FONT_ITALIC, 6.5)
set_fill(c, FOOTER_TEXT)
c.drawCentredString(PAGE_W / 2, MARGIN - 2,
f'Poznámky ke scénáři — {doc.event.title} | {gen_date}')
c.save()
return buf.getvalue()