From a2b5c6dc4aa3a108818bc08aecd5d20749b8cf14 Mon Sep 17 00:00:00 2001 From: Peter Adam Date: Tue, 5 May 2026 09:33:04 +0200 Subject: [PATCH] Add blanko build target, fix preamble duplication, update event config and margins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --blanko flag to generate_cards.py for blank cards (no CSV needed), 2-up layout - Fix preamble duplication bug affecting both blanko and multi-participant personalized builds - Add make build-blanko target; default make now builds personalized + blanko - Reduce page margins from 0.8cm to 0.4cm for Kyocera P6026 - Widen tikzpicture columns (6.9→7.2cm) and tabular columns (6.6→7.0cm) to fill page width - Update event.yml for BRM400 Bonn–Lüttich–Bastogne–Bonn, 9. Mai 2026, with 6 controls Co-Authored-By: Claude Sonnet 4.6 --- Makefile | 24 +++++++++++-- brevetkarte-rueckseite-template.tex | 54 ++++++++++++++--------------- brevetkarte-template.tex | 44 +++++++++++------------ generate_cards.py | 39 +++++++++++++++++++-- 4 files changed, 108 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index b0437e0..039b010 100644 --- a/Makefile +++ b/Makefile @@ -2,15 +2,17 @@ IMAGE_NAME := brevetcard-builder TEX_FILE_PERSONALIZED := brevetkarte-personalized.tex +TEX_FILE_BLANKO := brevetkarte-blanko.tex TEX_FILE_BACK := brevetkarte-rueckseite.tex PDF_FILE_PERSONALIZED := brevetkarte-personalized.pdf +PDF_FILE_BLANKO := brevetkarte-blanko.pdf PDF_FILE_BACK := brevetkarte-rueckseite.pdf CSV_FILE := export_brevetcard.csv -.PHONY: all build clean build-image build-back generate build-personalized run shell help +.PHONY: all build clean build-image build-back generate build-personalized build-blanko run shell help # Default target -all: build-personalized +all: build-personalized build-blanko # Build Docker image build-image: @@ -50,6 +52,23 @@ build-personalized: generate build-image pdflatex -interaction=nonstopmode $(TEX_FILE_BACK) @echo "PDF generated: $(PDF_FILE_BACK)" +# Build blank (blanko) front + event back side PDFs (no CSV required) +build-blanko: build-image + @echo "Generating blank card..." + python3 generate_cards.py --blanko + @echo "Compiling blank front side to PDF..." + docker run --rm \ + -v $(PWD):/workspace \ + $(IMAGE_NAME) \ + pdflatex -interaction=nonstopmode $(TEX_FILE_BLANKO) + @echo "PDF generated: $(PDF_FILE_BLANKO)" + @echo "Compiling back side to PDF..." + docker run --rm \ + -v $(PWD):/workspace \ + $(IMAGE_NAME) \ + pdflatex -interaction=nonstopmode $(TEX_FILE_BACK) + @echo "PDF generated: $(PDF_FILE_BACK)" + # Run container interactively run: docker run --rm -it \ @@ -86,6 +105,7 @@ help: @echo " make build-image - Build Docker image only" @echo " make generate - Generate tex files from CSV + event.yml" @echo " make build-personalized - Generate and compile front + back side PDFs" + @echo " make build-blanko - Generate and compile blank card (no CSV needed)" @echo " make build-back - Compile back side PDF only (after generate)" @echo " make shell - Open interactive shell in container" @echo " make clean - Remove generated files (aux, log, pdf)" diff --git a/brevetkarte-rueckseite-template.tex b/brevetkarte-rueckseite-template.tex index 9f7b8a5..b0ced48 100644 --- a/brevetkarte-rueckseite-template.tex +++ b/brevetkarte-rueckseite-template.tex @@ -1,7 +1,7 @@ \documentclass[a4paper,10pt,landscape]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} -\usepackage[landscape,top=0.8cm,bottom=0.8cm,left=0.8cm,right=0.8cm]{geometry} +\usepackage[landscape,top=0.4cm,bottom=0.4cm,left=0.4cm,right=0.4cm]{geometry} \usepackage{array} \usepackage{helvet} @@ -18,49 +18,49 @@ % Upper card table (rows 1-3) \noindent -\begin{tabular}{|p{6.6cm}|p{6.6cm}|p{6.6cm}|p{6.6cm}|} +\begin{tabular}{|p{7.0cm}|p{7.0cm}|p{7.0cm}|p{7.0cm}|} \hline % Row 1 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_4}}} \\ \hline % Row 2 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_4}}} \\ \hline % Row 3 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_4}}} \\ \hline @@ -70,49 +70,49 @@ % Lower card table (rows 1-3, identical) \noindent -\begin{tabular}{|p{6.6cm}|p{6.6cm}|p{6.6cm}|p{6.6cm}|} +\begin{tabular}{|p{7.0cm}|p{7.0cm}|p{7.0cm}|p{7.0cm}|} \hline % Row 1 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_1_4}}} \\ \hline % Row 2 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_2_4}}} \\ \hline % Row 3 -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_1}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_2}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_3}}} & -\parbox[c][\rowheight][t]{6.5cm}{% +\parbox[c][\rowheight][t]{6.9cm}{% {{CELL_3_4}}} \\ \hline diff --git a/brevetkarte-template.tex b/brevetkarte-template.tex index 3e7cd43..462a3b6 100644 --- a/brevetkarte-template.tex +++ b/brevetkarte-template.tex @@ -1,7 +1,7 @@ \documentclass[a4paper,10pt,landscape]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} -\usepackage[landscape,top=0.8cm,bottom=0.8cm,left=0.8cm,right=0.8cm]{geometry} +\usepackage[landscape,top=0.4cm,bottom=0.4cm,left=0.4cm,right=0.4cm]{geometry} \usepackage{graphicx} \usepackage{xcolor} \usepackage{tikz} @@ -32,31 +32,31 @@ \begin{tikzpicture}[x=1cm,y=1cm] % Black vertical separator lines (drawn first, extend through headers) -\draw[black,line width=0.5pt] (6.9,-7.2) -- (6.9,1.3); -\draw[black,line width=0.5pt] (13.8,-7.2) -- (13.8,1.3); -\draw[black,line width=0.5pt] (20.7,-7.2) -- (20.7,1.3); +\draw[black,line width=0.5pt] (7.2,-7.2) -- (7.2,1.3); +\draw[black,line width=0.5pt] (14.4,-7.2) -- (14.4,1.3); +\draw[black,line width=0.5pt] (21.6,-7.2) -- (21.6,1.3); % Black header boxes (drawn on top) -\fill[headerblack] (0,0) rectangle (6.9,1.3); -\node[white,align=center,font=\tiny,text width=6.6cm] at (3.45,0.65) { +\fill[headerblack] (0,0) rectangle (7.2,1.3); +\node[white,align=center,font=\tiny,text width=6.9cm] at (3.6,0.65) { Jeder Teilnehmer muss diese Brevetkarte zu jeder Zeit\\ mit sich führen und an den Kontrollen abstempeln lassen\\ bzw. Fotos erstellen.\\ \textbf{Ohne Kontrollzeiten und Zielzeit keine Wertung!} }; -\fill[headerblack] (6.9,0) rectangle (13.8,1.3); -\node[white,font=\Large] at (10.35,0.65) {HOMOLOGATION}; +\fill[headerblack] (7.2,0) rectangle (14.4,1.3); +\node[white,font=\Large] at (10.8,0.65) {HOMOLOGATION}; -\fill[headerblack] (13.8,0) rectangle (20.7,1.3); -\node[white,font=\Large] at (17.25,0.65) {TEILNEHMER/-IN}; +\fill[headerblack] (14.4,0) rectangle (21.6,1.3); +\node[white,font=\Large] at (18.0,0.65) {TEILNEHMER/-IN}; -\fill[headerblack] (20.7,0) rectangle (27.6,1.3); -\node[white,align=center,font=\normalsize] at (24.15,0.75) {BREVET DES RANDONNEURS}; -\node[white,font=\Large] at (24.15,0.35) {MONDIAUX}; +\fill[headerblack] (21.6,0) rectangle (28.8,1.3); +\node[white,align=center,font=\normalsize] at (25.2,0.75) {BREVET DES RANDONNEURS}; +\node[white,font=\Large] at (25.2,0.35) {MONDIAUX}; % Column 1 - Rules (left section) -\node[anchor=north west,text width=6.6cm,font=\small,align=left] at (0.2,-0.3) { +\node[anchor=north west,text width=6.9cm,font=\small,align=left] at (0.2,-0.3) { \textbf{Es gelten die Regeln von}\\ \textbf{Randonneur Mondiaux}\\ \textbf{insbesondere:} @@ -73,28 +73,28 @@ \hspace{0.3cm}$\Rightarrow$ \textbf{\underline{Bei Verstoß keine Wertung!}}\\[0.3cm] }; -\node[anchor=south west,text width=6.6cm,font=\small,align=left] at (0.2,-7.0) { +\node[anchor=south west,text width=6.9cm,font=\small,align=left] at (0.2,-7.0) { \textbf{AUDAX RANDONNEURS ALLEMAGNE E.V.}\\ \href{http://www.audax-randonneure.de}{www.audax-randonneure.de}\\ - gegründet 1992 in Hamburg - }; % Column 2 - Homologation (middle-left section) -\node[anchor=north,text width=6.6cm,font=\small,align=center] at (10.35,-0.5) { +\node[anchor=north,text width=6.9cm,font=\small,align=center] at (10.8,-0.5) { Der Randonnée wurde beendet in:\\[0.6cm] \makebox[2cm]{\dotfill}h\makebox[2cm]{\dotfill}min }; -\node[anchor=center,font=\Large] at (10.35,-4.0) { +\node[anchor=center,font=\Large] at (10.8,-4.0) { HOMOLOGATION }; -\node[anchor=south,text width=6.6cm,font=\small,align=center] at (10.35,-6.8) { +\node[anchor=south,text width=6.9cm,font=\small,align=center] at (10.8,-6.8) { Brevet N° \makebox[5cm]{\dotfill} }; % Column 3 - Participant Info (middle-right section) -\node[anchor=north west,text width=6.6cm,font=\small,align=left] at (14.0,-0.5) { +\node[anchor=north west,text width=6.9cm,font=\small,align=left] at (14.6,-0.5) { Name: {{NAME}}\\[0.4cm] Straße: {{STREET}}\\[0.4cm] PLZ/Ort: {{PLZ_ORT}}\\[0.4cm] @@ -104,11 +104,11 @@ }; % Column 4 - Event Info (right section) -\node[anchor=north,text width=6.6cm,align=center] at (24.15,-0.4) { +\node[anchor=north,text width=6.9cm,align=center] at (25.2,-0.4) { \includegraphics[width=5.5cm]{cyclist-logo.png} }; -\node[anchor=north,text width=6.6cm,font=\small,align=center] at (24.15,-2.2) { +\node[anchor=north,text width=6.9cm,font=\small,align=center] at (25.2,-2.2) { \textbf{{{EVENT_TITLE}}}\\ Randonnée über \textbf{{{EVENT_KM}}} km\\ am \textbf{{{EVENT_DATE}}}\\ @@ -117,7 +117,7 @@ N° ACP du Club \textbf{{{EVENT_CLUB_NR}}} }; -\node[anchor=south,text width=6.6cm,font=\scriptsize,align=center] at (24.15,-7.0) { +\node[anchor=south,text width=6.9cm,font=\scriptsize,align=center] at (25.2,-7.0) { CONTRÔLÉE ET HOMOLOGUÉE EXCLUSIVEMENT PAR\\ \href{http://www.audax-club-parisien.com}{www.audax-club-parisien.com}\\ - Société fondée en 1904 - diff --git a/generate_cards.py b/generate_cards.py index 1c2dd5b..2c7934e 100755 --- a/generate_cards.py +++ b/generate_cards.py @@ -2,6 +2,7 @@ """ Generate personalized brevet cards from CSV and event config. """ +import argparse import csv import sys from pathlib import Path @@ -92,7 +93,23 @@ def generate_card_from_template(template, data): return card +def generate_blanko_card(template): + """Generate a blank card with empty participant fields.""" + card = template.replace('{{NAME}}', '') + card = card.replace('{{STREET}}', '') + card = card.replace('{{PLZ_ORT}}', '') + card = card.replace('{{LAND}}', '') + card = card.replace('{{MEDAILLE}}', '') + card = card.replace('{{STARTNR}}', '') + return card + + def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--blanko', action='store_true', + help='Generate blank card without participant data') + args = parser.parse_args() + csv_file = Path("export_brevetcard.csv") template_file = Path("brevetkarte-template.tex") backside_template_file = Path("brevetkarte-rueckseite-template.tex") @@ -112,6 +129,24 @@ def main(): template = template_file.read_text(encoding='utf-8') template = apply_event_placeholders(template, event_config) + # Split preamble from body so \begin{document} appears only once per file + marker = '\\begin{document}' + doc_idx = template.find(marker) + preamble = template[:doc_idx + len(marker)] + body = template[doc_idx + len(marker):] + + if args.blanko: + blanko_output_file = Path("brevetkarte-blanko.tex") + blanko_body = generate_blanko_card(body) + blanko_output = preamble + blanko_body + "\n\\vspace{0.8cm}\n\n" + blanko_body + "\n\\end{document}\n" + blanko_output_file.write_text(blanko_output, encoding='utf-8') + print(f"Generated {blanko_output_file}") + backside_template = backside_template_file.read_text(encoding='utf-8') + backside_output = generate_backside(backside_template, event_config) + backside_output_file.write_text(backside_output, encoding='utf-8') + print(f"Generated {backside_output_file}") + return + print(f"Reading participant data from {csv_file}...") participants = [] with open(csv_file, 'r', encoding='utf-8-sig') as f: @@ -129,10 +164,10 @@ def main(): # Generate personalized front side cards = [] for participant in participants: - card = generate_card_from_template(template, participant) + card = generate_card_from_template(body, participant) cards.append(card) - document_parts = [] + document_parts = [preamble] for i, card in enumerate(cards): document_parts.append(card) if i % 2 == 0 and i < len(cards) - 1: