Skip to content

PDF aus HTML erstellen

Zwei Einstiegspunkte stehen zur Auswahl:

  1. Pdf::from_html(content) — grundlegendes strukturelles HTML (Überschriften, Absätze, Listen, Code, fett/kursiv). Ohne Styling. In jeder Sprachanbindung verfügbar.
  2. Pdf::from_html_css(html, css, font_bytes) — die vollständige, rein in Rust geschriebene HTML+CSS-Pipeline, eingeführt in v0.3.37. Handgeschriebene CSS-Engine (Teilmenge der L3- und L4-Selektoren, Kaskade, calc() / var(), @page / @media print), Taffy-basiertes Block-, Flex- und Grid-Layout, Zeilenumbruch nach UAX #14, RTL-Shaping über rustybuzz, ::before / ::after, page-break-*, <a href> → Link-Anmerkung, <img> als Data-URI → /XObject, Kaskade mehrerer Fonts. Ohne MPL-Abhängigkeiten. In jeder Sprachanbindung verfügbar.

Schnellbeispiel

Python

from pdf_oxide import Pdf

pdf = Pdf.from_html("<h1>Hello</h1><p>World</p>")
pdf.save("out.pdf")

WASM

import { WasmPdf } from "pdf-oxide-wasm";
import { writeFileSync } from "fs";

const pdf = WasmPdf.fromHtml("<h1>Hello</h1><p>World</p>");
writeFileSync("out.pdf", pdf.toBytes());

Rust

use pdf_oxide::api::Pdf;

let pdf = Pdf::from_html("<h1>Hello</h1><p>World</p>")?;
pdf.save("out.pdf")?;

Go

package main

import (
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    pdf, err := pdfoxide.FromHtml("<h1>Hello</h1><p>World</p>")
    if err != nil { log.Fatal(err) }
    defer pdf.Close()

    if err := pdf.Save("out.pdf"); err != nil { log.Fatal(err) }
}

C#

using PdfOxide;

using var pdf = Pdf.FromHtml("<h1>Hello</h1><p>World</p>");
pdf.Save("out.pdf");

HTML+CSS-Pipeline (v0.3.37)

Pdf::from_html_css(html, css, font_bytes) nimmt HTML, ein CSS-Stylesheet und TTF/OTF-Font-Bytes entgegen und gibt ein paginiertes PDF zurück. extract_text liefert byte-identische Ergebnisse — erzeugte PDFs laufen damit durch dieselbe Testinfrastruktur.

Rust:

use pdf_oxide::api::Pdf;

let font = std::fs::read("DejaVuSans.ttf")?;
let pdf = Pdf::from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt } p { line-height: 1.5 }",
    font,
)?;
pdf.save("out.pdf")?;

Python:

from pdf_oxide import Pdf

with open("DejaVuSans.ttf", "rb") as f:
    font = f.read()

pdf = Pdf.from_html_css(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font,
)
pdf.save("out.pdf")

Node / TypeScript:

import { Pdf } from "pdf-oxide";
import { readFileSync } from "fs";

const font = readFileSync("DejaVuSans.ttf");
const pdf = Pdf.fromHtmlCss(
  "<h1>Hello</h1><p>World</p>",
  "h1 { color: blue; font-size: 24pt }",
  font,
);
pdf.save("out.pdf");

Go:

font, _ := os.ReadFile("DejaVuSans.ttf")
pdf, err := pdfoxide.FromHtmlCss(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font,
)
if err != nil { log.Fatal(err) }
defer pdf.Close()
_ = pdf.Save("out.pdf")

C#:

var font = File.ReadAllBytes("DejaVuSans.ttf");
using var pdf = Pdf.FromHtmlCss(
    "<h1>Hello</h1><p>World</p>",
    "h1 { color: blue; font-size: 24pt }",
    font);
pdf.Save("out.pdf");

Kaskade mehrerer Fonts

Pdf::from_html_css_with_fonts(html, css, fonts) ist die richtige Wahl, wenn Ihr Dokument mehrere Font-Familien mischt. CSS-font-family auf einem beliebigen Element wird gegen die registrierten Familien aufgelöst (Groß-/Kleinschreibung egal, mit oder ohne Anführungszeichen, mehrteilige Namen auch unquotiert). Unbekannte Familien fallen auf den zuerst registrierten Font zurück.

from pdf_oxide import Pdf

fonts = [
    ("DejaVu Sans", open("DejaVuSans.ttf", "rb").read()),
    ("Noto Sans CJK", open("NotoSansCJKtc-Regular.otf", "rb").read()),
]

pdf = Pdf.from_html_css_with_fonts(
    '<h1 style="font-family: DejaVu Sans">English</h1>'
    '<p style="font-family: \'Noto Sans CJK\'">中文段落</p>',
    "h1 { font-size: 24pt }",
    fonts,
)
pdf.save("multilang.pdf")

CJK-Inhalte werden bei der Ausgabe automatisch als Subset eingebettet (v0.3.38 #385) — ein PDF mit 5 Zeichen aus einem ~17 MB großen CJK-Font bleibt typischerweise unter 100 KB.

Unterstützte CSS-Oberfläche

  • Selektoren — Teilmenge L3 + L4: :is / :where / :not / :has, strukturelle Pseudoklassen, Attribut-Matcher mit i- / s-Flag.
  • Kaskade — Sortierung nach Ursprung, Spezifität und Quellreihenfolge, Vererbung, Zusammenführung von Inline-Styles, Custom Properties (var() mit Zyklenerkennung).
  • Funktionencalc(), min(), max(), clamp().
  • At-Regeln@media print (immer wahr), (min/max-width), @page :first / :left / :right / :blank mit Margin-Boxen, @font-face, @import, @supports.
  • Typisierte Werte — Farbe (~150 benannte, Hex, rgb/rgba, hsl), Länge (alle Einheiten aus CSS Values L4), display, font-size / -weight / -style / -family, Kurzformen für margin / padding, line-height.
  • Zählercounter / counters, counter-reset / -increment / -set, römische, griechische und alphabetische Nummerierung.
  • Pseudoelemente::before / ::after mit Literalstrings, attr(name), open-quote / close-quote.
  • Layout — Block, Flex, Grid (alles via Taffy), Randzusammenführung, Mehrspalten (column-count / column-width / column-gap), Tabellen (Auto- und Fixed-Algorithmus).
  • Inline — Zeilenumbruch nach UAX #14, text-align, white-space-Modi, harte Umbrüche, atomare Inline-Boxen.
  • Effekteopacity, transform: translate*(), page-break-before: always, page-break-after: always.
  • HTML — HTML5-Tokenizer, Extraktion aus <style> / <link rel="stylesheet"> / Inline-style="", <img>-Data-URI-Dekodierung (/XObject), <a href>/Link-Anmerkung mit /URI, <ul>- / <ol>-Listenmarker.

Nicht im Funktionsumfang

CSS-Filter, 3D-Transforms, Animationen, SVG in HTML (jede nutzbare Rust-SVG-Crate steht unter MPL), MathML, hyphens: auto, shape-outside, JavaScript-Ausführung, vollständige Transformationsmatrix (Skalieren/Rotieren), Farbverläufe, box-shadow.

Lizenz

cargo deny check licenses meldet null transitive MPL-Abhängigkeiten. Der Mozilla-CSS-Stack (cssparser, selectors, html5ever, lightningcss, stylo) steht komplett unter MPL-2.0; v0.3.37 schreibt die Äquivalente von Hand, damit pdf_oxide vollständig unter MIT/Apache bleibt.

Unterstützte HTML-Elemente

Element Beschreibung
<h1> bis <h6> Überschriften (auf PDF-Überschriftsgrößen abgebildet)
<p> Absätze mit automatischem Abstand
<b>, <strong> Fetter Text
<i>, <em> Kursiver Text
<ul>, <ol>, <li> Ungeordnete und geordnete Listen
<pre>, <code> Vorformatierter Text und Inline-Code
<blockquote> Blockzitate
<br> Zeilenumbrüche
<hr> Horizontale Trennlinien

Vollständige API-Referenz

Pdf::from_html(content) (statische Methode)

Erzeugt ein PDF aus HTML-Inhalten mit Standardwerten (Letter-Seite, 72 pt Rand, 12 pt Helvetica).

Rust:

use pdf_oxide::api::Pdf;

let html = r#"
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
<h2>Requirements</h2>
<ul>
    <li>Operating temperature: -20C to 60C</li>
    <li>Power consumption: &lt;5W</li>
    <li>Weight: &lt;200g</li>
</ul>
"#;

let pdf = Pdf::from_html(html)?;
pdf.save("spec.pdf")?;

JavaScript:

import { WasmPdf } from "pdf-oxide-wasm";
import { writeFileSync } from "fs";

const html = `
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
`;

const pdf = WasmPdf.fromHtml(html);
writeFileSync("spec.pdf", pdf.toBytes());

Python:

from pdf_oxide import Pdf

html = """
<h1>Product Specification</h1>
<p>This document describes the <strong>technical requirements</strong>
for the new product line.</p>
"""

pdf = Pdf.from_html(html)
pdf.save("spec.pdf")

Python Signature:

Pdf.from_html(
    content: str,
    title: str | None = None,
    author: str | None = None
) -> Pdf

PdfBuilder::new().from_html(content) (Builder-Muster)

Mit PdfBuilder kontrollieren Sie Seitengröße, Ränder, Schriftgröße und Dokument-Metadaten.

Rust:

use pdf_oxide::api::PdfBuilder;
use pdf_oxide::writer::PageSize;

let pdf = PdfBuilder::new()
    .title("Technical Specification")
    .author("Engineering")
    .page_size(PageSize::A4)
    .margin(54.0)
    .font_size(11.0)
    .from_html("<h1>Spec</h1><p>Version 2.0</p>")?;

pdf.save("spec_a4.pdf")?;

Erweiterte Beispiele

Strukturierter Bericht

use pdf_oxide::api::Pdf;

let html = r#"
<h1>Incident Report</h1>
<h2>Summary</h2>
<p>On <em>2025-11-15</em>, a service disruption was detected in the
<strong>payment processing</strong> pipeline.</p>

<h2>Timeline</h2>
<ol>
    <li>14:32 UTC - Alert triggered for elevated error rates</li>
    <li>14:35 UTC - On-call engineer acknowledged</li>
    <li>14:48 UTC - Root cause identified: database connection pool exhaustion</li>
    <li>15:02 UTC - Fix deployed, services recovering</li>
    <li>15:15 UTC - Full recovery confirmed</li>
</ol>

<h2>Root Cause</h2>
<p>A configuration change deployed at 14:00 UTC reduced the maximum
connection pool size from 100 to 10.</p>

<h2>Code Reference</h2>
<pre><code>max_connections: 10  # Should be 100
timeout_seconds: 30
</code></pre>

<h2>Action Items</h2>
<ul>
    <li>Add validation for connection pool configuration</li>
    <li>Implement canary deployment for config changes</li>
    <li>Add alerting for connection pool utilization</li>
</ul>
"#;

let pdf = Pdf::from_html(html)?;
pdf.save("incident_report.pdf")?;

Python mit dynamisch erzeugtem HTML

from pdf_oxide import Pdf

rows = [
    ("Widget A", "$12.99", 150),
    ("Widget B", "$24.50", 89),
    ("Widget C", "$7.25", 312),
]

html = "<h1>Inventory Report</h1>"
html += "<p>Generated on 2025-11-20</p>"
html += "<h2>Current Stock</h2><ul>"
for name, price, qty in rows:
    html += f"<li><strong>{name}</strong> - {price} ({qty} units)</li>"
html += "</ul>"

pdf = Pdf.from_html(html, title="Inventory Report")
pdf.save("inventory.pdf")

HTML aus einer Datei lesen

from pdf_oxide import Pdf

with open("report.html") as f:
    html = f.read()

pdf = Pdf.from_html(html, title="Report")
pdf.save("report.pdf")
import { WasmPdf } from "pdf-oxide-wasm";
import { readFileSync, writeFileSync } from "fs";

const html = readFileSync("report.html", "utf-8");
const pdf = WasmPdf.fromHtml(html);
writeFileSync("report.pdf", pdf.toBytes());
use pdf_oxide::api::Pdf;

let html = std::fs::read_to_string("report.html")?;
let pdf = Pdf::from_html(&html)?;
pdf.save("report.pdf")?;

Verwandte Seiten