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: