Generating PDFs from HTML
Skill: databricks-unstructured-pdf-generation
What You Can Build
Section titled “What You Can Build”Production-looking documents — policy manuals, reports, reference guides — rendered from HTML your AI coding assistant writes, converted with PlutoPrint, and uploaded straight to a Unity Catalog Volume. Because the agent writes every line of HTML, you control structure, styling, and length completely. The tool’s only job is faithful HTML-to-PDF conversion and the upload, so the craft is all in the HTML.
In Action
Section titled “In Action”“Generate an employee leave policy PDF that looks like a real corporate document — themed headings, a highlighted notice, a footer — and upload it to hr_catalog.policies.”
generate_and_upload_pdf( html_content='''<!DOCTYPE html><html><head> <style> :root { --primary: #1a73e8; --text: #202124; --gray: #5f6368; } body { font-family: 'Segoe UI', Arial, sans-serif; margin: 50px; color: var(--text); line-height: 1.6; } h1 { color: var(--primary); border-bottom: 3px solid var(--primary); padding-bottom: 15px; } h2 { margin-top: 30px; } .highlight { background: #e8f0fe; padding: 15px; border-left: 4px solid var(--primary); margin: 20px 0; } .footer { margin-top: 50px; padding-top: 20px; border-top: 1px solid #dadce0; color: var(--gray); font-size: 0.9em; } </style></head><body> <h1>Employee Leave Policy</h1> <p><em>Effective: January 1, 2024</em></p> <h2>1. Annual Leave</h2> <p>All full-time employees are entitled to 20 days of paid annual leave per calendar year.</p> <div class="highlight"> <strong>Note:</strong> Leave requests must be submitted at least 2 weeks in advance. </div> <div class="footer">Generated on 2024-01-15 | Confidential</div></body></html>''', filename="leave_policy.pdf", catalog="hr_catalog", schema="policies")Key decisions:
- Full HTML5 skeleton, always —
<!DOCTYPE html>, a<head>with an inline<style>block, then<body>. The converter expects a complete document, not a fragment. - CSS variables for theming — define
--primaryand friends in:rootonce, reuse everywhere. Carrying the same variables across a corpus gives every document a consistent corporate identity. - System fonts by default — web fonts are supported, but ‘Segoe UI’, Arial, and Georgia render reliably without adding a remote dependency to every conversion.
- Everything inline — styles live in the
<head>, and images (if you need them) go in as base64 data URIs. The document must be self-contained.
More Patterns
Section titled “More Patterns”Scale to a batch with the four-step workflow
Section titled “Scale to a batch with the four-step workflow”“I need a 10-document onboarding pack in docs_catalog.onboarding — plan the titles, write each document, and upload them all.”
# 1. Plan: enumerate ten titles and a content outline for each# 2. Author: write complete HTML per document# 3. Convert: fire all ten calls in parallelgenerate_and_upload_pdf(html_content=welcome_html, filename="01_welcome.pdf", catalog="docs_catalog", schema="onboarding", folder="onboarding_pack")generate_and_upload_pdf(html_content=it_setup_html, filename="02_it_setup.pdf", catalog="docs_catalog", schema="onboarding", folder="onboarding_pack")generate_and_upload_pdf(html_content=security_html, filename="03_security_training.pdf", catalog="docs_catalog", schema="onboarding", folder="onboarding_pack")# ... seven more parallel calls ...# 4. Report: summarize volume_paths and any errorsThe workflow is plan, author, convert in parallel, report. At 2-5 seconds per document, parallelism is the difference between a ten-document batch finishing in about 5 seconds versus close to a minute. Author all the HTML before firing any call — interleaving writing with uploads serializes the batch by accident.
Use the CSS the converter actually supports
Section titled “Use the CSS the converter actually supports”“Make the summary page a two-column metrics layout with a styled data table.”
<style> .metrics { display: flex; gap: 24px; } /* flexbox: supported */ .grid { display: grid; grid-template-columns: 1fr 1fr; } /* grid: supported */ table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #dadce0; padding: 12px; text-align: left; } th { background: #f1f3f4; } /* Avoid: animations, transitions, hover states, form inputs — meaningless in a static PDF */</style>PlutoPrint handles modern CSS3 — flexbox, grid, CSS variables, full color/border/background control, and styled tables. What it cannot use is anything interactive or remote: animations and hover effects are dead weight, and external resources fetched by URL are on the avoid list. Design for print, not for a browser.
Verify every upload in the batch
Section titled “Verify every upload in the batch”“Confirm all the PDFs landed and show me anything that failed.”
# Each call returns a result worth reading:{"success": True, "volume_path": "/Volumes/docs_catalog/onboarding/raw_data/onboarding_pack/01_welcome.pdf", "error": None}{"success": True, "volume_path": "/Volumes/docs_catalog/onboarding/raw_data/onboarding_pack/02_it_setup.pdf", "error": None}{"success": False, "volume_path": None, "error": "Volume does not exist"}Don’t declare a batch done because the calls returned — read each result. A volume or schema error means missing infrastructure: create it and rerun that call. A rendering failure means one document’s HTML needs fixing. Either way, only the failed documents need retrying — another payoff of one call per document.
Watch Out For
Section titled “Watch Out For”- Fragments instead of full documents — skipping
<!DOCTYPE html>or the<head>is the most common cause of a PDF that “looks wrong.” If output is mangled, validate the HTML structure before suspecting the converter. - Web-app CSS habits — transitions, hover effects, and form controls do nothing in PDF output. Every interactive affordance is wasted bytes at best and broken layout at worst.
- Remote images — external resources referenced by URL are unsupported. Embed images as base64 data URIs, or design documents that don’t need them.
- Assuming
volumematches your Volume’s name — the default israw_data. If your Volume is namedlandingordocs, passvolume=explicitly or every call fails with “Volume does not exist.”