name: pdf-generation
description: Generate 2-page ATS-compliant PDF CVs using WeasyPrint with multi-language support (EN/DE/PL) and visual regression testing. Use when generating final PDF, running visual tests, debugging layout issues, or iterating on CV template design. Trigger on: "generate PDF", "create CV PDF", "render PDF", "visual regression test", or after successful CV validation.
PDF Generation Skill
When to Apply
Trigger this skill when:
- User asks to generate final PDF
- After successful CV validation
- Running visual regression tests
- Debugging template layout issues
- Iterating on CV design changes
- Testing multi-language output (EN/DE/PL)
Prerequisites: CV data must pass validation first (use cv-validation skill)
Core PDF Generation Workflow
Step 1: Pre-Generation Validation
Always validate before generating PDF:
python .claude/skills/cv-validation/scripts/validate_schema.py <cv-json-file>
Only proceed if validation passes. Never generate PDF from invalid data.
Step 2: Call Generation API
Generate PDF via Azure Function:
curl -X POST http://localhost:7071/api/generate-cv-action \
-H "Content-Type: application/json" \
-d '{
"cv_data": {...},
"language": "en",
"source_docx_base64": null
}'
Parameters:
cv_data (required): Complete CV JSON object
language (optional): "en" (default), "de", or "pl"
source_docx_base64 (optional): Base64-encoded DOCX for photo extraction
Response:
{
"success": true,
"preview_html_url": "blob://sessions/.../preview.html",
"pdf_url": "blob://sessions/.../cv.pdf",
"session_id": "d03cf26e-0dc1-48f9-91c8-7def7a10ddca"
}
Step 3: Download and Display
Save generated files locally:
curl "<preview_html_url>" > tmp/preview_<session_id>.html
curl "<pdf_url>" > tmp/cv_<session_id>.pdf
Show to user:
✅ PDF Generated Successfully
Preview: [tmp/preview_d03cf26e.html](tmp/preview_d03cf26e.html)
PDF: [tmp/cv_d03cf26e.pdf](tmp/cv_d03cf26e.pdf)
Language: English (EN)
Pages: 2
Size: 245 KB
Next steps:
1. Review PDF visually
2. Run visual regression test (/visual-regression)
3. If satisfied, share with user
Step 4: Visual Regression Testing
Always test generated PDF against baseline:
npm test
Playwright will:
- Screenshot generated PDF
- Compare with approved baseline
- Report differences (threshold: 5%)
See: references/visual-regression-workflow.md
Extended Thinking Modes
Use Claude's extended thinking for complex PDF generation tasks:
Standard Generation ("think")
Use for: Regular CV generation with validated data
Example:
think: Generate PDF for validated CV data with English template
Layout Debugging ("think hard")
Use for: Content overflow, alignment issues, font problems
Example:
think hard: Why is the experience section overflowing to page 3?
Analyze template CSS, content estimation, and WeasyPrint rendering.
Multi-Language Edge Cases ("ultrathink")
Use for: Complex German/Polish layout issues, character encoding
Example:
ultrathink: German CV with long compound words breaks template layout.
Analyze word-break rules, hyphenation, character spacing, and CSS constraints.
Multi-Language Support
English (en)
Template: cv_template_2pages_2025_en.html
Characteristics:
- Shorter words (easier to fit)
- Standard sections (Experience, Education, Skills)
- Date format: MM/YYYY or YYYY-MM-DD
CSS considerations:
- Default line-height: 1.4
- Word-break: normal
German (de)
Template: cv_template_2pages_2025_de.html
Characteristics:
- Longer compound words (Projektmanagement, Softwareentwicklung)
- Umlauts (ä, ö, ü, ß) require UTF-8
- Section headers: Berufserfahrung, Ausbildung, Fähigkeiten
- Date format: MM.YYYY
CSS considerations:
- Line-height: 1.5 (15% more space)
- Word-break: break-word (allow mid-word breaks)
- Hyphens: auto (enable hyphenation)
WeasyPrint quirk: German hyphenation requires language tag:
<html lang="de">
Polish (pl)
Template: cv_template_2pages_2025_pl.html
Characteristics:
- Polish diacritics (ą, ć, ę, ł, ń, ó, ś, ź, ż)
- Moderately longer words than English
- Section headers: Doświadczenie, Wykształcenie, Umiejętności
- Date format: MM.YYYY
CSS considerations:
- Line-height: 1.45 (10% more space)
- Word-break: normal
- Character encoding: UTF-8 (critical)
Visual Iteration Workflow
For template design changes, use iterative screenshot comparison:
Iteration Pattern
- Make CSS change (e.g., increase heading font size)
- Generate preview (POST /api/generate-cv-action with
preview_only: true)
- Screenshot preview (use Playwright MCP)
- Compare with baseline (visual diff)
- Accept or iterate (update baseline if satisfied)
Example workflow:
User: "Increase section heading size and test"
Claude:
1. Edit templates/html/cv_template_2pages_2025.css:
h2 { font-size: 18px; } → h2 { font-size: 20px; }
2. Generate preview:
curl POST /api/generate-cv-action {"cv_data": {...}, "preview_only": true}
3. Use Playwright MCP to screenshot:
npx playwright screenshot tmp/preview_abc123.html tmp/preview_abc123.png
4. Compare with baseline:
compare tmp/preview_abc123.png test-results/cv-en-baseline.png
5. Display diff:
Diff: 3.2% (increased heading size visible, layout intact)
6. Ask: Accept new design? (yes/no)
See: references/visual-iteration-patterns.md
WeasyPrint CSS Compatibility
WeasyPrint renders HTML to PDF using a subset of CSS.
Supported Features
- ✅ Float layouts (main positioning method)
- ✅ Flexbox (basic support, not all properties)
- ✅ Absolute/relative positioning
- ✅ Margins, padding, borders
- ✅ Fonts (embedded via @font-face)
- ✅ Page breaks (page-break-before, page-break-after)
- ✅ Background colors/images
NOT Supported (or limited)
- ❌ CSS Grid (not supported)
- ❌ CSS Transforms (scale, rotate)
- ❌ CSS Animations
- ❌ CSS Variables (--custom-property)
- ⚠️ Flexbox gaps (use margins instead)
- ⚠️ Sticky positioning
Important: Always test template changes with actual PDF generation, not just browser preview.
See: references/weasyprint-quirks.md
Common PDF Generation Issues
Issue 1: Content Overflow (>2 Pages)
Symptom: PDF has 3+ pages
Root cause: Too much content or large fonts
Fix:
- Run layout estimation:
.claude/skills/cv-validation/scripts/count_template_space.py
- If >2.0 pages estimated, reduce content:
- Shorten experience bullets
- Limit experience entries (last 10 years)
- Remove less relevant skills
- Or reduce font sizes (last resort):
body { font-size: 10px; }
Issue 2: Photo Not Displaying
Symptom: Profile photo missing in PDF
Root cause: photo_url invalid or >32KB
Fix:
- Validate photo_url size:
python .claude/skills/cv-validation/scripts/validate_schema.py <cv-json> | grep photo_url
- If >32KB, compress:
python .claude/skills/cv-validation/scripts/compress_photo.py <image> --max-size=30KB
- Verify data URI format:
data:image/png;base64,iVBORw0KG...
Issue 3: Font Rendering Issues
Symptom: Fonts look wrong or fallback to default
Root cause: Font files not embedded correctly
Fix:
- Check font files exist:
templates/html/fonts/
- Verify @font-face in CSS:
@font-face {
font-family: 'Roboto';
src: url('fonts/Roboto-Regular.ttf');
}
- Use WeasyPrint font debugging:
from weasyprint import HTML, CSS
HTML(string=html).write_pdf('test.pdf', stylesheets=[CSS(string=css)])
Issue 4: Layout Shifts Between Languages
Symptom: German CV overflows but English fits
Root cause: Longer German words without proper word-breaking
Fix:
- Enable hyphenation in template:
p { hyphens: auto; }
- Set HTML lang attribute:
<html lang="de">
- Increase line-height for German:
body[lang="de"] { line-height: 1.5; }
Issue 5: Visual Regression Test Fails
Symptom: Playwright reports >5% diff
Root cause: Intentional change or unexpected regression
Fix:
- View diff image:
test-results/cv-german-diff.png
- If intentional change (e.g., font size update), accept new baseline:
npm test -- --update-snapshots
- If unintended regression, investigate:
- Check recent CSS changes
- Review template modifications
- Test WeasyPrint version consistency
Scripts
print_pdf_playwright.mjs
Location: scripts/print_pdf_playwright.mjs
Purpose: Headless PDF generation using Playwright
Usage:
node scripts/print_pdf_playwright.mjs <html-file> <output-pdf>
Features:
- Headless Chromium rendering
- Pixel-perfect PDF output
- Used by visual regression tests
Alternative to WeasyPrint: Use when debugging browser-specific rendering issues.
Progressive Disclosure
Level 1 (Metadata): Skill name + description (always loaded)
Level 2 (This file): SKILL.md body (loaded when skill triggers)
Level 3 (References):
Only load references when:
- Debugging complex layout issues
- Learning template structure
- Implementing new template features
Integration with Other Commands/Skills
cv-validation → pdf-generation
Flow:
/validate-cv → Pre-check + API validation
- If valid: Automatically offer PDF generation
- User confirms
- Generate PDF + preview
pdf-generation → visual-regression
Flow:
- Generate PDF
/visual-regression → Screenshot + compare
- If diff <5%: Accept PDF
- If diff ≥5%: Review changes, update baseline if intentional
Visual iteration loop
Flow:
- Edit template CSS
- Generate preview (no full PDF)
- Screenshot with Playwright MCP
- Compare with design mock (not baseline)
- Iterate until pixel-perfect
- Generate final PDF + update baseline
Performance Optimization
PDF generation timing:
- Validation: ~200ms (API call)
- HTML rendering: ~500ms (template + data injection)
- WeasyPrint PDF: ~1-2s (complex layout)
- Total: ~2-3s per PDF
Optimization tips:
- Cache validated data (don't re-validate on retry)
- Reuse template instances (don't re-parse CSS)
- Parallelize multi-language generation:
curl POST /api/generate-cv-action {"language": "en"} &
curl POST /api/generate-cv-action {"language": "de"} &
curl POST /api/generate-cv-action {"language": "pl"} &
wait
Headless CI/CD Integration (Phase 3)
GitHub Actions workflow:
name: Visual Regression Tests
on: [pull_request]
jobs:
test-cv-generation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/setup-python@v4
- name: Install dependencies
run: |
npm install
pip install -r requirements.txt
- name: Generate test PDFs
run: |
npm run pretest # Generate test artifacts
python scripts/generate_all_languages.py
- name: Run visual regression
run: npm test
- name: Upload diff images
if: failure()
uses: actions/upload-artifact@v3
with:
name: visual-diffs
path: test-results/**/*-diff.png
Claude Code headless mode:
claude -p "Generate CV PDF and run visual regression tests" \
--headless \
--output test-results/claude-report.md
References
Read these files for detailed information:
Project files:
Last updated: 2026-01-22
Skill version: 1.0.0