Dokumentation
PDFs erstellen
PDFs in Vue
PDFs in Nuxt
PDFs in React
PDFs in Angular
Ausgabeoptionen
renderToBytes(doc, options) macht aus einem Dokument Bytes. Alles auf dieser Seite ist optional -
renderToBytes(doc) allein erzeugt schon ein gültiges PDF.
import { renderToBytes } from "@jasy/pdf";
const bytes = await renderToBytes(doc, {
title: "Q3 report",
accessible: true,
});
Dateien im PDF
Ein PDF kann andere Dateien mitführen. So versteckt eine ZUGFeRD-Rechnung ihr XML in der Seite, die ein Mensch liest, und Sie können es für alles andere nutzen: die CSV, aus der eine Tabelle gebaut wurde, den Vertrag, auf den sich ein Angebot bezieht, die Quelle eines Diagramms.
const bytes = await renderToBytes(doc, {
attachments: [
{
name: "figures.csv",
data: new TextEncoder().encode("month,revenue\n2026-06,42000\n"),
mimeType: "text/csv",
relationship: "Source",
description: "The numbers behind the chart on page 2",
},
],
});
| Feld | Bedeutung |
|---|---|
name | der Dateiname, den ein Reader anzeigt, z. B. "figures.csv" |
data | die Bytes (Uint8Array) |
mimeType | z. B. "text/csv"; Standard "application/octet-stream" |
relationship | /AFRelationship: "Data", "Source", "Alternative", "Supplement" |
description | freier Text, der neben der Datei angezeigt wird |
relationship sagt, was die Datei für das Dokument IST, nicht wie sie gespeichert ist, und ein Validator
prüft es: ZUGFeRD verlangt "Data" für seine factur-x.xml, denn das XML ist die Rechnung und keine
Ergänzung dazu. Anhänge werden als Associated Files geschrieben, so wie es PDF/A-3
und PDF 2.0 verlangen - ein einfacher Eintrag in /EmbeddedFiles würde keinem von beiden genügen.
Zurücklesen seit beta.4
readAttachments listet jede Datei, die ein PDF mitführt - von jedem Erzeuger, nicht nur von jasy:
import { PdfDocument, readAttachments } from "@jasy/pdf/edit";
const doc = await PdfDocument.open(bytes, { password }); // password only if it is encrypted
for (const file of readAttachments(doc)) {
console.log(file.name, file.mimeType, file.relationship, file.locations);
const data = file.bytes(); // or file.text() for XML, CSV, ...
}
Es findet eine Datei überall, wo ein PDF eine ablegen kann - in der Anhangsliste des Dokuments, in den
Associated Files von PDF/A-3 und in File Attachment Annotations auf einer Seite -, und locations sagt, wo jede
hängt. Eine Datei, die an zwei Stellen aufgeführt ist, ist ein Eintrag; zwei Dateien mit demselben Namen
sind zwei, was mit einem doppelten Namen geschieht, entscheiden also Sie. bytes() gibt genau zurück, was
eingebettet wurde: entschlüsselt, jeder Standardfilter dekodiert und gegen die eigene Prüfsumme der Datei
geprüft. Daten, die nicht dazu passen, oder ein Filter, der sich nicht dekodieren lässt, werfen einen
AttachmentError, statt etwas Ähnliches zurückzugeben.
Ein PDF/A selbst bauen
@jasy/e-invoice erledigt all das für Sie, und bei einer Rechnung sollten Sie es ihm
überlassen. Diese Optionen gibt es, weil dieselbe Mechanik für jedes Archiv-PDF nützlich ist - einen
Vertrag, einen Bericht, der ein Jahrzehnt überstehen muss:
const bytes = await renderToBytes(doc, {
pdfVersion: "1.7", // PDF/A-3 needs it; default "1.4"
documentId: true, // a trailer /ID, which PDF/A requires
outputIntent: sRGBProfileBytes, // an ICC profile, so color is defined
xmp: xmpPacket, // the XMP that DECLARES the conformance level
standardFonts: false, // no Helvetica: PDF/A wants every font embedded
fonts: { Helvetica: myEmbeddedFace },
});
Zwei davon macht man leicht falsch:
standardFonts: falseist der Kern, kein Detail. PDF/A verbietet, sich auf die Schriften des Readers zu verlassen, und die 14 Standardschriften sind genau das. Schalten Sie sie ab, muss jeder Schriftname, den Sie verwenden, überfontskommen - deshalb liefert@jasy/e-invoicedie Liberation-Schriften unter den NamenHelvetica,TimesundCourieraus.xmpist das, was ein Validator liest. Das Conformance Level steht im XMP-Packet, nicht in der Struktur. Eine richtige Struktur mit falschem Packet ergibt eine Datei, die nichts behauptet.
documentId schreibt eine deterministische /ID: Sie ist ein Hash der Objekte, keine Zufallszahl und
nicht die Uhrzeit. Nur so rendert dasselbe Dokument zweimal Byte für Byte gleich, und eine archivierte
Datei bleibt auch Jahre später neu herleitbar.
Typografie und Größe
| Option | Standard | Was es tut |
|---|---|---|
fonts | - | eingebettete Schriften, nach dem Namen, der in Text({ font }) steht |
kerning | true | Glyph-Paare nach den eigenen Daten der Schrift ausgleichen; false ist die ältere Ausgabe |
compress | true | die Streams mit FlateDecode komprimieren; false ergibt ein lesbares, durchsuchbares PDF |
compress: false ist ein Werkzeug zur Fehlersuche, das man kennen sollte: Der Content Stream wird zu
Klartext, Sie können das PDF also in einem Editor öffnen und die Operatoren lesen, die jasy geschrieben
hat.
Vorab laden seit beta.7
Die Metriken der Standardschriften sind nicht im Bundle, mit dem Ihre App startet. Sie laden mit dem ersten Rendern (158 KB minifiziert, 12 KB mit gzip), und jedes weitere Rendern findet sie geladen vor. Um diese Wartezeit vom ersten PDF zu nehmen, laden Sie sie früher - sobald die App untätig ist, oder wenn der Zeiger einen Download-Button erreicht:
import { preloadStandardFonts } from "@jasy/pdf";
requestIdleCallback(() => void preloadStandardFonts());
Der Aufruf darf beliebig oft kommen, und ein fehlgeschlagenes Laden wird beim nächsten Aufruf wiederholt.
Was passiert ist
const bytes = await renderToBytes(doc, {
onOverflow: "warn",
onMissingGlyphs: (chars) => report(`not drawable: ${chars.join(" ")}`),
onDiagnostic: (d) => report(`${d.code} in ${d.element} "${d.text}" on page ${d.page}`),
});
onOverflow- was geschehen soll, wenn ein einzelner Block höher als eine ganze Seite ist und sich nicht teilen lässt."error"(Standard) wirft,"warn"protokolliert,"ignore"schweigt. Abgeschnitten wird in jedem Fall: Die Alternative wäre eine Pagination, die nie endet.onMissingGlyphs- wird einmal mit allen Zeichen aufgerufen, die keine Schrift zeichnen konnte. Sie werden entfernt, statt als.notdef-Kästchen der Schrift geschrieben zu werden, was PDF/A verbietet. Absichtlich ein Callback und keine Regel: Ein fehlendes Häkchen darf nie verhindern, dass eine Rechnung entsteht, aber Ihre Anwendung soll sagen können, welches Zeichen fehlte.onDiagnosticseit beta.4 - ein Kanal für alles, was jasy über ein Dokument zu sagen hat, das es trotzdem gerendert hat. Jeder Befund hat einen stabilencode, das Element so, wie Sie es geschrieben haben ("Box","Text"), seinen ersten Text und die Seite:codeBedeutung page-break-ignoredein PageBreakin einerRowoder einemPageBuilder, die nie auf Seiten umbrechencolumn-break-ignoredseit beta.5ein ColumnBreakaußerhalb der Spalten einesFlowbreak-ignoredbreakBefore/breakAfteran etwas in einerRowoder einer Tabellenzeilekeep-together-spliteine keepTogether-Gruppe enthält einen erzwungenen Umbruch; der Umbruch gewinntoverflow-clippedein Block, höher als eine Seite, der sich nicht teilen lässt (mit onOverflownicht"error")overflow-wideseit beta.5ein Wort, das sich nicht umbrechen lässt, oder eine Row, deren Kinder mehr Platz brauchen, über den Rand gezeichnetmissing-glyphsZeichen, die keine Schrift zeichnen konnte, pro Textimage-unsizedseit beta.5ein Bild, dessen eigene Größe beim Layout unbekannt war - eine URL, die erst in einem PageBuilderentsteht
Ohne Handler ändert sich nichts: Was vorher gewarnt hat, geht weiterhin auf die Konsole.
Metadaten und Zugriff
| Option | Was es tut |
|---|---|
title | der Titel des Dokuments - von Readern verwendet, und Pflicht für ein konformes getaggtes PDF |
lang | die Sprache des Dokuments, z. B. "de-DE" (Standard "en-US") |
accessible | einen getaggten Structure Tree erzeugen - siehe Barrierefreiheit |
encrypt | Passwortschutz mit AES-256 - siehe Verschlüsselung |
encrypt und PDF/A schließen sich gegenseitig aus, und jasy sagt das, statt eine Datei zu erzeugen, die
später an der Validierung scheitert: PDF/A verbietet Verschlüsselung, das Verschlüsseln einer
ZUGFeRD-Rechnung wirft also einen Fehler.