Zum Inhalt springen

Anleitung

HTML-Rechnungsvorlage mit funktionierendem PDF-Beispiel

Eine vollständige Rechnungsvorlage in HTML und CSS zum Herunterladen und Anpassen, das daraus gerenderte PDF, die Anfrage, die es rendert, und das Druck-CSS, das lange Rechnungen über mehrere Seiten hinweg sauber hält.

Wenn eine Rechnung bereits eine HTML-Webansicht hat, lassen sich dasselbe Markup und dieselben Styles für das PDF verwenden. So bleiben Browser- und PDF-Version deckungsgleich, ohne dass Sie ein separates Layout pflegen. Ironfang Render druckt die Seite in Chromium, wendet dabei Ihre Druck-Styles an und gibt das fertige PDF zurück.

Das funktionierende Beispiel herunterladen

  • invoice.html: eine einseitige A4-Rechnung: eigenständiges HTML und CSS, ohne externe Schriften oder Bilder
  • invoice-multipage.html: dasselbe Design mit 48 Positionen, das vier Seiten füllt
  • request.json: der genaue Body der Anfrage für invoice.html, erzeugt mit dem Befehl weiter unten
  • invoice.pdf: das PDF, das diese Anfrage erzeugt, eine Seite
  • invoice-multipage.pdf: die mehrseitige Vorlage, mit denselben Optionen gerendert, vier Seiten
Seite 1 des Beispiel-PDFs: Rechnung INV-1042 von Northwind Studio Ltd an Harbour Lane Retail Ltd, sechs Positionen, netto 11.175,00 Pfund, Umsatzsteuer zu 20 Prozent 2.235,00, fälliger Gesamtbetrag 13.410,00 und "Page 1 of 1" in der Fußzeile.
Die erste Seite von invoice.pdf.

Die Vorlage anpassen

  • Verkäufer und Käufer: Die drei Blöcke in section.parties enthalten Namen, Adressen, Umsatzsteuer-Identifikationsnummer und Rechnungsangaben. Ersetzen Sie den Text; das Layout hängt nicht davon ab.
  • Positionen: Jede tr in tbody ist eine Rechnungsposition, mit der Beschreibung und einer kurzen Detailzeile in td.desc. Fügen Sie beliebig Zeilen hinzu oder entfernen Sie welche; die mehrseitige Vorlage zeigt 48.
  • Summen: Die Vorlage berechnet nichts. Ermitteln Sie Positionsbeträge, Umsatzsteuer und Gesamtbetrag in Ihrem eigenen Code mit Dezimalarithmetik, und tragen Sie die Ergebnisse ein.
  • Branding: Die Farbe des Kopfbands ist #1e3a5f, das Logo ist ein Inline-SVG. Ersetzen Sie beides und lassen Sie das Logo inline oder als Data-URI, damit es nichts nachladen muss.

Ein solches PDF ist ein Abbild einer Rechnung, gemacht für Menschen. Wo ein Käufer oder ein Land eine strukturierte E-Rechnung verlangt, ist das ein separates XML-Dokument, etwa eine Rechnung nach Peppol BIS Billing 3: Ein Beispiel dafür, Feld für Feld erklärt, finden Sie im Peppol-XML-Rechnungsbeispiel.

Die Rechnung rendern

jq baut den Body der Anfrage aus der Vorlage und maskiert das HTML als JSON-String, sodass Sie das Markup nie von Hand escapen müssen. Senden Sie ihn dann mit Ihrem Schlüssel aus der Umgebungsvariablen IRONFANG_API_KEY.

export IRONFANG_API_KEY="if_live_..."

jq -n --rawfile html invoice.html '{
  html: $html,
  paper_format: "a4",
  print_background: true,
  margin: { top: 0.6, right: 0.5, bottom: 0.8, left: 0.5 },
  header_html: "<span></span>",
  footer_html: "<div style=\"font-size:9px;width:100%;text-align:center;color:#666;font-family:sans-serif\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}' > request.json

curl -sS https://api.ironfang.uk/render/v1/pdf \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @request.json \
  -o invoice.pdf \
  -w '%{http_code} %{content_type}\n'

Ein 200 mit application/pdf bedeutet, dass invoice.pdf das Dokument ist. Jeder andere Status ist ein JSON-Fehler, {"error": {"code", "message"}}, der in dieselbe Datei geschrieben wird: Lesen Sie ihn, statt ihn als PDF zu öffnen. Jedes PDF verbraucht zwei Credits, und ein fehlgeschlagenes Rendering wird erstattet.

Seitengröße, Ränder und Seitenzahlen

paper_format akzeptiert a3, a4, a5, letter, legal oder tabloid; ohne Angabe ist die Seite Letter. Ränder werden in Zoll angegeben, je Seite. Die Fußzeile wird innerhalb des unteren Rands gezeichnet, lassen Sie also Platz dafür: Mit 0,8 Zoll bleibt die Seitenzahl hier frei von der letzten Zeile.

Chromium füllt die Klassen pageNumber, totalPages, date, title und url in Kopf- und Fußzeilenvorlagen. Sobald Sie eine der beiden Vorlagen senden, sind beide aktiv; eine Anfrage mit nur einer Fußzeile braucht deshalb zusätzlich ein leeres header_html, wie oben. Sonst druckt Chromium sein Standarddatum und den Seitentitel oben auf jede Seite.

Lange Rechnungen über mehrere Seiten

Drei Regeln in der Vorlage tragen eine lange Rechnung über mehrere Seiten. Die Spaltenüberschriften wiederholen sich oben auf jeder Seite, eine Position wird nie auf zwei Seiten verteilt, und die Summen bleiben mit den Zahlungsangaben zusammen. In invoice-multipage.pdf stehen die Überschriften oben auf allen vier Seiten, die langen fünfzeiligen Beschreibungen bleiben ganz, und die Summen landen gemeinsam auf Seite vier.

Aus invoice-multipage.html.

table.items thead { display: table-header-group; } /* repeat the headings */
table.items tr { break-inside: avoid; }            /* keep each line whole */
.end, .totals { break-inside: avoid; }             /* totals with payment details */

Eine einzelne Zeile, die höher als eine Seite ist, muss trotzdem geteilt werden. Halten Sie Positionsbeschreibungen auf wenige Zeilen begrenzt, und setzen Sie lange Bedingungen in einen eigenen Abschnitt nach den Summen.

Hintergründe, Schriften und Bilder

  • Hintergründe: Browser lassen Hintergrundfarben und -bilder beim Drucken standardmäßig weg. Senden Sie print_background: true, und setzen Sie print-color-adjust: exact auf die Elemente, die sie tragen, etwa ein Kopfband in Markenfarbe oder einen schattierten Summenkasten.
  • Schriften: Der Renderer hat seine eigenen installierten Schriften, nicht Ihre. Für eine exakte Übereinstimmung laden Sie Ihre Schrift mit @font-face von einer öffentlichen URL oder als Data-URI, und setzen Sie wait_until auf networkidle, damit sie da ist, bevor die Seite gedruckt wird. Das Beispiel nutzt einen System-Font-Stack und muss deshalb überhaupt nichts nachladen.
  • Bilder: Ein Inline-SVG oder eine Data-URI wird ohne weitere Anfrage gedruckt. Ein Bild per URL muss aus dem öffentlichen Internet erreichbar sein.

Die Vorlage sicher befüllen

Kundennamen, Adressen und Positionsbeschreibungen stammen aus Ihren Daten, und eine einzige verirrte spitze Klammer kann das Layout zerstören oder Markup einschleusen. Maskieren Sie jeden Wert, wenn Sie ihn in das HTML einsetzen, genau wie bei einer Webseite.

Python, mit der Standardbibliothek.

from html import escape

row = (
    f"<tr><td>{escape(item.description)}</td>"
    f"<td>{item.quantity}</td><td>{item.unit_price:,.2f}</td></tr>"
)

Fehlerbehebung

  • Oben auf jeder Seite erscheinen ein Datum und der Seitentitel: Die Anfrage hat footer_html, aber kein header_html. Ergänzen Sie "header_html": "<span></span>".
  • Die Seitenzahl überdeckt die letzte Zeile: Der untere Rand ist zu klein für die Fußzeile. Erhöhen Sie margin.bottom.
  • Farben oder das Kopfband fehlen: Setzen Sie print_background auf true.
  • Der rechte Rand ist abgeschnitten: Ein Element hat eine feste Breite, die breiter als das Papier ist. Verwenden Sie Prozentangaben oder max-width: 100% für den Seitencontainer und die Tabellen.
  • Eine Position ist auf zwei Seiten verteilt: Ergänzen Sie break-inside: avoid für tr, und prüfen Sie, dass keine einzelne Zeile höher als eine Seite ist.
  • Das PDF öffnet sich als Fehler: Die Antwort war JSON, kein PDF. Prüfen Sie den Statuscode, den der curl-Befehl ausgibt.

Credit-Verbrauch

Jedes PDF verbraucht zwei Credits, fehlgeschlagene Renderings werden erstattet. PDFs werden nicht zwischengespeichert: Jede Anfrage rendert das Dokument neu. Bewahren Sie das ausgestellte PDF deshalb auf und liefern Sie wiederholte Downloads aus Ihrer Kopie, statt es erneut zu rendern.

Weiterlesen