A fully offline desktop application for batch-generating personalized certificate PDFs — with a visual drag-and-drop layout designer, QR verification codes, multithreaded rendering, and a CLI mode for automation pipelines.
This tool was built to solve a specific problem: generating hundreds of uniquely personalized certificates quickly, reliably, and entirely offline — without depending on external services or cloud APIs.
The workflow is simple:
- Design your layout visually by dragging bounding boxes onto your PDF template
- Map each box to a column in your metadata CSV
- Export the layout as a
layout.jsonfile - Hit Generate Batch — certificates are rendered in parallel and packaged into a ZIP archive
Each certificate gets a QR code that links to https://your-domain.com/verify/{certificate_id} for tamper-evident verification.
- Visual layout designer — drag, resize, and configure text/QR fields directly on top of your PDF template
- Inspector panel — control font, size, color, alignment, text case transform, and coordinate origin per field
- Auto-fit text — font size automatically scales down to prevent overflow inside the bounding box
- QR code generation — embeds a verification QR pointing to
{domain}/verify/{certificate_id} - Custom TTF fonts — drop
.ttffiles in thefonts/directory; falls back to Helvetica gracefully - Multithreaded batch rendering — configurable worker thread count (1–16)
- ZIP packaging — all generated PDFs bundled into a single archive automatically
- Layout import/export — save and reuse
layout.jsonfiles across runs - CLI mode — scriptable
generate.pyfor automation and CI pipelines - Inline validation — pre-flight checks on all inputs before any rendering starts
- Confirmation dialog — shows a full config summary before batch jobs run
| Layer | Library | Purpose |
|---|---|---|
| GUI | PySide6 | Main application window, widgets, canvas |
| PDF Overlay | ReportLab | Text and image drawing on a transparent canvas |
| PDF Merge | pypdf | Reading template pages and merging overlay |
| PDF Preview | PyMuPDF (fitz) |
Rendering PDF pages as pixmaps for canvas display |
| QR Codes | qrcode[pil] | Generating verification QR code images |
| Concurrency | concurrent.futures.ThreadPoolExecutor |
Parallel certificate rendering |
Python 3.10+ required.
certificate_generator/
│
├── gui.py # GUI entry point — launches the desktop app
├── generate.py # CLI entry point — batch generation from terminal
│
├── core/
│ ├── __init__.py
│ ├── renderer.py # PDF rendering: fonts, colors, text, QR, page merge
│ └── pipeline.py # Orchestration: CSV parsing, threading, ZIP packaging
│
├── gui/
│ ├── __init__.py
│ ├── app.py # Main QMainWindow — scaffold, sidebar, canvas, generation
│ ├── canvas.py # QGraphicsView canvas + draggable/resizable Box items
│ ├── styles.py # Design tokens and global QSS stylesheet
│ ├── widgets.py # Shared UI primitives: separators, ConfirmationDialog
│ └── worker.py # QThread worker + LogStream stdout redirect
│
├── fonts/ # Drop custom .ttf fonts here (git-ignored)
├── output/ # Generated certificates land here (git-ignored)
├── requirements.txt
└── .gitignore
git clone https://github.com/BIJJUDAMA/Certification-Generation-App.git
cd Certification-Generation-Apppython -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activatepip install -r requirements.txtpython gui.pyThe designer window opens. From there:
- Load a PDF Template — click the Template browse button in the sidebar
- Load a Metadata CSV — click the CSV browse button
- Add layout fields — use + Text Field or + QR Code buttons; drag boxes to position on canvas
- Configure each field — select a box to open the inspector; set font, size, color, alignment, text transform, and origin
- Set output folder and domain — in the Settings section of the sidebar
- Export the layout — use the Export Layout button in the header to save your
layout.jsonfor future reuse - Generate — click Generate Batch, review the confirmation dialog, then proceed
Progress is shown as a 3px bar at the top. Logs stream live into the Pipeline Logs panel.
python generate.py \
--metadata path/to/metadata.csv \
--template path/to/template.pdf \
--layout path/to/layout.json \
--domain https://your-domain.com \
--output ./output \
--workers 8 \
--zip-name certificates.zip| Argument | Required | Default | Description |
|---|---|---|---|
--metadata |
✅ | — | Path to the metadata CSV file |
--template |
✅ | — | Path to the background PDF template |
--layout |
✅ | — | Path to the layout JSON file |
--domain |
❌ | http://localhost:3000 |
Base domain for QR verification URLs |
--output |
❌ | output |
Output directory for generated PDFs and ZIP |
--workers |
❌ | 4 |
Number of concurrent render threads |
--zip-name |
❌ | certificates.zip |
Name of the output ZIP archive |
The CSV is the data source for all certificate fields. Each row produces one certificate.
Required columns:
| Column | Description |
|---|---|
certificate_id |
Unique identifier — used in the QR verification URL |
signature |
Cryptographic or display signature field |
Common optional columns (automatically recognised by the pipeline):
| Column | Aliases | Description |
|---|---|---|
recipient_name |
name, full_name, student_name |
Used to name the output file |
roll_number |
roll |
Appended to the output filename |
Any additional columns in the CSV can be mapped to layout fields in the designer. Column names are matched case-insensitively.
Example:
certificate_id,signature,recipient_name,roll_number,event_name,event_date,issued_at
CERT-001,abc123xyz,Arjun Sharma,22BCE1234,Hackathon 2026,2026-07-15,2026-07-30
CERT-002,def456uvw,Priya Mehta,22BCE5678,Hackathon 2026,2026-07-15,2026-07-30The layout file maps field keys to bounding box configurations on the PDF canvas. It is generated automatically by the GUI designer via Export Layout.
{
"recipient_name": {
"x": 180.0,
"y": 320.0,
"width": 280.0,
"height": 42.0,
"font": "Helvetica-Bold",
"size": 28,
"align": "center",
"color": "#1f2937",
"transform": "uppercase",
"origin": "top-left"
},
"event_name": {
"x": 180.0,
"y": 380.0,
"width": 280.0,
"height": 28.0,
"font": "Helvetica",
"size": 16,
"align": "center",
"color": "#6b7280",
"transform": "none",
"origin": "top-left"
},
"qr": {
"x": 460.0,
"y": 380.0,
"width": 90.0,
"height": 90.0,
"origin": "top-left"
}
}| Property | Type | Description |
|---|---|---|
x, y |
float |
Position in PDF points from the origin corner |
width, height |
float |
Bounding box dimensions in PDF points |
font |
string |
ReportLab built-in name or TTF filename (without .ttf) |
size |
int |
Maximum font size; auto-reduced to fit the bounding box |
align |
string |
left · center · right |
color |
string |
Hex color #rrggbb or RGB list [r, g, b] |
transform |
string |
none · uppercase · lowercase · capitalize |
origin |
string |
top-left (default) · bottom-left |
qrkey — reserved. Adding aqrfield renders the verification QR code. Onlyx,y,width,height, andoriginapply.
Drop any .ttf file into the fonts/ directory. Reference it in the layout JSON by filename without the extension:
"font": "Poppins-Bold"This loads fonts/Poppins-Bold.ttf. If the file is not found, the renderer falls back to Helvetica (or Helvetica-Bold / Helvetica-Oblique based on the font name hint) and prints a warning.
Bundled font: The repository ships with Geist (Geist-VariableFont_wght.ttf), Vercel's open-source variable font. Reference it in your layout as:
"font": "Geist-VariableFont_wght"Each certificate's QR code encodes:
https://your-domain.com/verify/{certificate_id}
This expects a verification endpoint at that route on your own backend that looks up the certificate_id and validates the signature from the CSV. The generator is fully offline — it only encodes the URL.
output/
├── Arjun_Sharma_22BCE1234.pdf
├── Priya_Mehta_22BCE5678.pdf
└── certificates.zip <- all PDFs bundled
Output filenames are sanitized as {recipient_name}_{roll_number}.pdf. Special characters and spaces are replaced with underscores.
Licensed under the Apache License, Version 2.0.
Copyright 2026 BIJJUDAMA
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.