Skip to content

Generating PDFs from HTML

Skill: databricks-unstructured-pdf-generation

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.

“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 --primary and friends in :root once, 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.

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 parallel
generate_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 errors

The 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.

“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.

  • 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 volume matches your Volume’s name — the default is raw_data. If your Volume is named landing or docs, pass volume= explicitly or every call fails with “Volume does not exist.”