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
Some checks failed
Build & Push Docker / build (push) Has been cancelled
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Application configuration."""
|
||||
|
||||
VERSION = "4.1.0"
|
||||
VERSION = "4.2.0"
|
||||
MAX_FILE_SIZE_MB = 10
|
||||
DEFAULT_COLOR = "#ffffff"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -912,3 +912,14 @@ body {
|
||||
color: var(--text-light);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Responsible in block */
|
||||
.block-responsible {
|
||||
font-size: 9px;
|
||||
opacity: 0.75;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.1;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -233,14 +233,24 @@ const Canvas = {
|
||||
const inner = document.createElement('div');
|
||||
inner.className = 'block-inner';
|
||||
const timeLabel = `${block.start}–${block.end}`;
|
||||
|
||||
const nameEl = document.createElement('span');
|
||||
nameEl.className = 'block-title';
|
||||
nameEl.textContent = block.title;
|
||||
nameEl.textContent = block.title + (block.notes ? ' *' : '');
|
||||
|
||||
const timeEl = document.createElement('span');
|
||||
timeEl.className = 'block-time';
|
||||
timeEl.textContent = timeLabel + (isOvernight ? ' →' : '');
|
||||
|
||||
inner.appendChild(nameEl);
|
||||
inner.appendChild(timeEl);
|
||||
|
||||
if (block.responsible) {
|
||||
const respEl = document.createElement('span');
|
||||
respEl.className = 'block-responsible';
|
||||
respEl.textContent = block.responsible;
|
||||
inner.appendChild(respEl);
|
||||
}
|
||||
el.appendChild(inner);
|
||||
|
||||
// Click to edit
|
||||
|
||||
Reference in New Issue
Block a user