feat: v4.1 - Czech diacritics in PDF (Liberation fonts), hour/min duration fields, day select in modal
Some checks failed
Build & Push Docker / build (push) Has been cancelled

This commit is contained in:
2026-02-20 17:39:38 +01:00
parent f0e7c3b093
commit 6c4ca5e9be
7 changed files with 175 additions and 37 deletions

View File

@@ -7,15 +7,62 @@ Always exactly one page, A4 landscape.
from io import BytesIO
from datetime import datetime
from collections import defaultdict
import os
import logging
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas as rl_canvas
import logging
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from .validator import ScenarsError
logger = logging.getLogger(__name__)
# ── Font registration ─────────────────────────────────────────────────────────
# LiberationSans supports Czech diacritics. Fallback to Helvetica if not found.
_FONT_REGULAR = 'Helvetica'
_FONT_BOLD = 'Helvetica-Bold'
_FONT_ITALIC = 'Helvetica-Oblique'
_LIBERATION_PATHS = [
'/usr/share/fonts/truetype/liberation',
'/usr/share/fonts/liberation',
'/usr/share/fonts/truetype',
]
def _find_font(filename: str):
for base in _LIBERATION_PATHS:
path = os.path.join(base, filename)
if os.path.isfile(path):
return path
return None
def _register_fonts():
global _FONT_REGULAR, _FONT_BOLD, _FONT_ITALIC
regular = _find_font('LiberationSans-Regular.ttf')
bold = _find_font('LiberationSans-Bold.ttf')
italic = _find_font('LiberationSans-Italic.ttf')
if regular and bold and italic:
try:
pdfmetrics.registerFont(TTFont('LiberationSans', regular))
pdfmetrics.registerFont(TTFont('LiberationSans-Bold', bold))
pdfmetrics.registerFont(TTFont('LiberationSans-Italic', italic))
_FONT_REGULAR = 'LiberationSans'
_FONT_BOLD = 'LiberationSans-Bold'
_FONT_ITALIC = 'LiberationSans-Italic'
logger.info('PDF: Using LiberationSans (Czech diacritics supported)')
except Exception as e:
logger.warning(f'PDF: Font registration failed: {e}')
else:
logger.warning('PDF: LiberationSans not found, Czech diacritics may be broken')
_register_fonts()
PAGE_W, PAGE_H = landscape(A4)
MARGIN = 10 * mm
@@ -186,13 +233,13 @@ def generate_pdf(doc) -> bytes:
# ── Draw header ───────────────────────────────────────────────────
y = y_top
c.setFont('Helvetica-Bold', TITLE_SIZE)
c.setFont(_FONT_BOLD, TITLE_SIZE)
set_fill(c, HEADER_BG)
c.drawString(x0, y - TITLE_SIZE, doc.event.title)
y -= TITLE_SIZE + 5
if doc.event.subtitle:
c.setFont('Helvetica', SUB_SIZE)
c.setFont(_FONT_REGULAR, SUB_SIZE)
set_fill(c, (0.4, 0.4, 0.4))
c.drawString(x0, y - SUB_SIZE, doc.event.subtitle)
y -= SUB_SIZE + 3
@@ -208,7 +255,7 @@ def generate_pdf(doc) -> bytes:
parts.append(f'Datum: {date_display}')
if doc.event.location:
parts.append(f'Místo: {doc.event.location}')
c.setFont('Helvetica', INFO_SIZE)
c.setFont(_FONT_REGULAR, INFO_SIZE)
set_fill(c, (0.5, 0.5, 0.5))
c.drawString(x0, y - INFO_SIZE, ' | '.join(parts))
y -= INFO_SIZE + 3
@@ -230,7 +277,7 @@ def generate_pdf(doc) -> bytes:
c.line(tx, table_top, tx, table_top + TIME_AXIS_H)
# label
label = fmt_time(m)
c.setFont('Helvetica', time_axis_font)
c.setFont(_FONT_REGULAR, time_axis_font)
set_fill(c, AXIS_TEXT)
c.drawCentredString(tx, table_top + (TIME_AXIS_H - time_axis_font) / 2, label)
@@ -244,7 +291,7 @@ def generate_pdf(doc) -> bytes:
for m in range(t_start, t_end + 1, 60):
slot_idx = (m - t_start) // 15
tx = x0 + DATE_COL_W + slot_idx * slot_w
c.setFont('Helvetica', time_axis_font)
c.setFont(_FONT_REGULAR, time_axis_font)
set_fill(c, AXIS_TEXT)
c.drawCentredString(tx, table_top + (TIME_AXIS_H - time_axis_font) / 2, fmt_time(m))
@@ -263,7 +310,7 @@ def generate_pdf(doc) -> bytes:
# Date label cell
fill_rect(c, x0, row_y, DATE_COL_W, row_h, AXIS_BG, BORDER, 0.4)
draw_clipped_text(c, format_date_cs(date_key), x0, row_y, DATE_COL_W, row_h,
'Helvetica-Bold', date_font, AXIS_TEXT, 'center')
_FONT_BOLD, date_font, AXIS_TEXT, 'center')
# Vertical grid lines (15-min slots, hour lines darker)
for slot_i in range(num_15min_slots + 1):
@@ -315,15 +362,15 @@ def generate_pdf(doc) -> bytes:
title_line = block.title + ('' if overnight else '')
if row_h > block_title_font * 2.2 and block.responsible:
# Two lines: title + responsible
c.setFont('Helvetica-Bold', block_title_font)
c.setFont(_FONT_BOLD, block_title_font)
c.drawCentredString(bx + bw / 2, row_y + row_h / 2 + 1, title_line)
c.setFont('Helvetica', block_time_font)
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)
else:
c.setFont('Helvetica-Bold', block_title_font)
c.setFont(_FONT_BOLD, block_title_font)
c.drawCentredString(bx + bw / 2, row_y + (row_h - block_title_font) / 2, title_line)
c.restoreState()
@@ -331,7 +378,7 @@ def generate_pdf(doc) -> bytes:
# ── Legend ────────────────────────────────────────────────────────
legend_y_top = table_top - num_days * row_h - 6
c.setFont('Helvetica-Bold', 7)
c.setFont(_FONT_BOLD, 7)
set_fill(c, HEADER_BG)
c.drawString(x0, legend_y_top, 'Legenda:')
legend_y_top -= LEGEND_ITEM_H
@@ -349,7 +396,7 @@ def generate_pdf(doc) -> bytes:
fill_rgb, BORDER, 0.3)
# Type name NEXT TO the square
c.setFont('Helvetica', 7)
c.setFont(_FONT_REGULAR, 7)
set_fill(c, (0.15, 0.15, 0.15))
c.drawString(lx + LEGEND_BOX_W + 3,
ly - LEGEND_ITEM_H + 2 + (LEGEND_ITEM_H - 2 - 7) / 2,
@@ -357,7 +404,7 @@ def generate_pdf(doc) -> bytes:
# ── Footer ────────────────────────────────────────────────────────
gen_date = datetime.now().strftime('%d.%m.%Y %H:%M')
c.setFont('Helvetica-Oblique', 6.5)
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}')