From ca995a1c1fe9647d84ca661fdb760d9fe0b4374f Mon Sep 17 00:00:00 2001
From: Peter Adam
Date: Tue, 5 May 2026 16:16:20 +0200
Subject: [PATCH] Add duplex PDF build targets with pdftk page interleaving
- Add pdftk-java to Docker image for PDF page manipulation
- Extend generate_backside() to repeat back content num_pages times so
front and back PDFs have matching page counts for pdftk shuffle
- Add build-duplex and build-blanko-duplex Makefile targets that combine
front and back PDFs with alternating pages (front1, back1, front2, back2)
- Document duplex targets in README
Co-Authored-By: Claude Sonnet 4.6
---
Dockerfile | 1 +
Makefile | 26 ++++++++++++++++++++++++--
README.md | 9 +++++++++
generate_cards.py | 22 +++++++++++++++++-----
4 files changed, 51 insertions(+), 7 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 1427d3e..f913a00 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
make \
python3 \
python3-yaml \
+ pdftk-java \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 0ca5cc9..b5788bc 100644
--- a/Makefile
+++ b/Makefile
@@ -7,9 +7,11 @@ TEX_FILE_BACK := brevetkarte-rueckseite.tex
PDF_FILE_PERSONALIZED := brevetkarte-personalized.pdf
PDF_FILE_BLANKO := brevetkarte-blanko.pdf
PDF_FILE_BACK := brevetkarte-rueckseite.pdf
+PDF_FILE_DUPLEX := brevetkarte-duplex.pdf
+PDF_FILE_BLANKO_DUPLEX := brevetkarte-blanko-duplex.pdf
CSV_FILE := export_brevetcard.csv
-.PHONY: all build clean build-image build-back generate build-personalized build-blanko run shell help
+.PHONY: all build clean build-image build-back generate build-personalized build-blanko build-duplex build-blanko-duplex run shell help
# Default target
all: build-personalized build-blanko
@@ -75,6 +77,24 @@ build-blanko: build-image
pdflatex -interaction=nonstopmode $(TEX_FILE_BACK)
@echo "PDF generated: $(PDF_FILE_BACK)"
+# Build duplex PDF (personalized front + back interleaved for duplex printing)
+build-duplex: build-personalized
+ @echo "Combining front and back for duplex printing..."
+ docker run --rm \
+ -v $(PWD):/workspace \
+ $(IMAGE_NAME) \
+ pdftk A=$(PDF_FILE_PERSONALIZED) B=$(PDF_FILE_BACK) shuffle A B output $(PDF_FILE_DUPLEX)
+ @echo "PDF generated: $(PDF_FILE_DUPLEX)"
+
+# Build duplex PDF (blank front + back interleaved for duplex printing)
+build-blanko-duplex: build-blanko
+ @echo "Combining blank front and back for duplex printing..."
+ docker run --rm \
+ -v $(PWD):/workspace \
+ $(IMAGE_NAME) \
+ pdftk A=$(PDF_FILE_BLANKO) B=$(PDF_FILE_BACK) shuffle A B output $(PDF_FILE_BLANKO_DUPLEX)
+ @echo "PDF generated: $(PDF_FILE_BLANKO_DUPLEX)"
+
# Run container interactively
run:
docker run --rm -it \
@@ -92,7 +112,7 @@ shell:
# Clean generated files
clean:
@echo "Cleaning generated files..."
- rm -f *.aux *.log *.out *.toc *.pdf brevetkarte-personalized.tex brevetkarte-blanko.tex brevetkarte-rueckseite.tex
+ rm -f *.aux *.log *.out *.toc *.pdf brevetkarte-personalized.tex brevetkarte-blanko.tex brevetkarte-rueckseite.tex $(PDF_FILE_DUPLEX) $(PDF_FILE_BLANKO_DUPLEX)
# Clean everything including Docker image
clean-all: clean
@@ -112,6 +132,8 @@ help:
@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-duplex - Build duplex PDF (front+back interleaved)"
+ @echo " make build-blanko-duplex - Build blank duplex PDF"
@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/README.md b/README.md
index 6207783..17d2810 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,15 @@ Führt folgende Schritte aus:
3. Erzeugt `brevetkarte-rueckseite.tex` (Rückseite mit Kontrollpunkten aus event.yml)
4. Kompiliert beide .tex-Dateien zu PDFs
+### Duplex-PDF erzeugen
+
+```bash
+make build-duplex # personalisierte Karten
+make build-blanko-duplex # Blanko-Karten
+```
+
+Erzeugt ein einzelnes PDF mit abwechselnden Vorder- und Rückseiten für den direkten Duplexdruck: Seite 1 Vorderseite, Seite 2 Rückseite, Seite 3 nächste Vorderseite, usw. Erfordert kein manuelles Zusammenführen zweier Dateien.
+
### Blanko-Karten erzeugen und bauen
```bash
diff --git a/generate_cards.py b/generate_cards.py
index 47c1c5d..ea2a00e 100755
--- a/generate_cards.py
+++ b/generate_cards.py
@@ -59,7 +59,7 @@ def apply_event_placeholders(text, config):
return text
-def generate_backside(template, config):
+def generate_backside(template, config, num_pages=1):
"""Fill back side template with cell content from event config."""
cells = config.get('backside', {})
result = template
@@ -71,7 +71,18 @@ def generate_backside(template, config):
if content is None:
content = ""
result = result.replace(placeholder, content.strip())
- return result
+
+ if num_pages <= 1:
+ return result
+
+ # Repeat body between \begin{document} and \end{document} num_pages times
+ begin_marker = '\\begin{document}\n'
+ end_marker = '\\end{document}'
+ begin_idx = result.find(begin_marker) + len(begin_marker)
+ end_idx = result.rfind(end_marker)
+ preamble = result[:begin_idx]
+ body = result[begin_idx:end_idx].rstrip()
+ return preamble + ('\n\n\\newpage\n\n').join([body] * num_pages) + '\n' + result[end_idx:]
def generate_card_from_template(template, data):
@@ -142,7 +153,7 @@ def main():
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 = generate_backside(backside_template, event_config, num_pages=1)
backside_output_file.write_text(backside_output, encoding='utf-8')
print(f"Generated {backside_output_file}")
return
@@ -179,10 +190,11 @@ def main():
output_file.write_text(''.join(document_parts), encoding='utf-8')
print(f"Generated {output_file}")
- # Generate personalized back side
+ # Generate personalized back side (one page per front page for duplex)
+ num_pages = (len(participants) + 1) // 2
print(f"Reading back side template from {backside_template_file}...")
backside_template = backside_template_file.read_text(encoding='utf-8')
- backside_output = generate_backside(backside_template, event_config)
+ backside_output = generate_backside(backside_template, event_config, num_pages=num_pages)
backside_output_file.write_text(backside_output, encoding='utf-8')
print(f"Generated {backside_output_file}")