Dokumentation
PDFs erstellen
PDFs in Vue
PDFs in Nuxt
PDFs in React
PDFs in Angular
Layout
Jedes Layout entsteht aus einer Handvoll Primitives. Column und Row stapeln ihre Kinder, Spacer
und Expanded verteilen den Platz dazwischen, und Box und Padding rahmen Inhalt ein und rücken ihn ein.
Column und Row
Column stapelt seine Kinder von oben nach unten, Row von links nach rechts. Beide nehmen dieselben
Optionen.
Column({ gap: 12 }, [Text("First"), Text("Second")]);
Row({ gap: 8, justify: "between", align: "center" }, [
Text("Total", { size: 16, bold: true }),
Text("100.00 EUR"),
]);
| Prop | Typ | Was es tut |
|---|---|---|
gap | number | Abstand zwischen den Kindern, in Punkt |
justify | "start" | "center" | "end" | "between" | "around" | Verteilung entlang der Main-Axis (wie CSS justify-content) |
align | "start" | "center" | "end" | "stretch" | Position auf der Cross-Axis (wie CSS align-items) |
wrap | boolean | Kinder in weitere Zeilen umbrechen, wenn sie nicht passen |
alignContent | "start" | "center" | "end" | "between" | "around" | "stretch" | wie die UMGEBROCHENEN Zeilen als Block sitzen |
reverse | boolean | die Kinder in umgekehrter Reihenfolge anordnen |
width height | number | string | eine Abmessung festlegen, in Punkt oder als "50%" des Angebotenen |
justify: "between" schiebt das erste und das letzte Kind an die Ränder. align: "center" richtet die
Kinder auf der Cross-Axis mittig aus. Beide stehen standardmäßig auf start.
Wrapping und Steuerung pro Kind
Mit wrap beginnen Kinder, die nicht passen, eine neue Zeile, und alignContent setzt diese Zeilen als
Block. Einzelne Kinder haben drei eigene Props, an jedem Element:
Row({ wrap: true, gap: 8, alignContent: "between" }, [
Box({ width: "33%" }, [Text("one")]),
Box({ width: "33%" }, [Text("two")]),
Box({ width: "33%", order: -1 }, [Text("drawn first")]),
Box({ flexShrink: 0 }, [Text("keeps its width, and overflows")]),
Expanded({ flex: 1, flexBasis: 120 }, Box([Text("starts at 120pt, then grows")])),
Box({ alignSelf: "end" }, [Text("overrides the row's align")]),
]);
flexShrink ist 1, wie in CSS: Läuft eine Zeile über, geben ihre Kinder im Verhältnis zu ihrer
eigenen Breite Platz ab, bis die Zeile passt. Mit 0 nimmt sich ein Kind heraus, behält seine Breite und
läuft über den Rand. Ein Expanded mit flexBasis macht ebenfalls mit, gewichtet nach seiner Basis -
Expanded({ flexShrink: 0 }, ...) behält die Basis in jedem Fall. Ein Spacer hat keine Basis und gibt
nichts ab. seit beta.4
Nichts wird enger gedrückt, als es tatsächlich fassen kann. Die Untergrenze eines Kindes ist seine
min-content-Ausdehnung - bei einem Text sein längstes Wort -, dieselbe Regel, die ein Browser über
das automatische min-width eines Flex-Items anwendet. Sitzt ein Kind auf seiner Untergrenze, gibt es
nichts mehr ab, und der Anteil, den es nicht aufnehmen konnte, geht an die, die noch können.
Eine Prozentbreite in einer Flex-Zeile bezieht sich auf die Zeile abzüglich ihrer Abstände, also
passen N Spalten mit (100/N)% genau - in CSS müssen Sie die Abstände selbst abziehen.
Das
aligneines Layouts verwendet die Begriffe der Cross-Axis,start / center / end(es ist richtungsunabhängig - bei einerRowist die Cross-Axis senkrecht,left/rightergäbe dort keinen Sinn). Das unterscheidet sich vom eigenenaligneinesText, dasleft / center / rightlautet.
Spacer
Spacer ist dehnbarer leerer Raum. Zwischen zwei Elemente gesetzt, schiebt er sie auseinander.
Row([Text("Subtotal"), Spacer(), Text("100.00 EUR")]);
Mit einer Zahl gewichten Sie mehrere Spacer gegeneinander: Spacer(2) nimmt doppelt so viel Platz wie
Spacer(1).
Expanded
Expanded lässt ein Kind wachsen, bis es den restlichen Platz füllt. Geben Sie jedem ein flex-Gewicht,
um den Platz anteilig zu teilen.
Row({ gap: 12 }, [
Expanded({ flex: 1 }, Box({ border: "steelblue", padding: 12, radius: 6 }, [Text("flex 1")])),
Expanded(
{ flex: 2 },
Box({ bg: "#1450aa11", padding: 12, radius: 6 }, [Text("flex 2, twice as wide")]),
),
]);
| Prop | Typ | Was es tut |
|---|---|---|
flex | number | der Anteil des Kindes am restlichen Platz (Standard 1) |
Expanded macht sein Kind zum Flex-Item, wie CSS flex: 1 am Kind selbst
geändert in beta.5:
- die eigene
widthdes Kindes weicht seinem Anteil; - es wird nie schmaler, als sein Inhalt braucht - ein langes Wort hält es offen;
- ein
maxWidtham Kind stoppt sein Wachstum, und der frei bleibende Platz geht an die anderen; - das Kind reiht sich wie jedes Element in die Zeile ein: das
alignder Row, das eigenealignSelfdes Kindes undorder; - ein
%-Padding am Kind bezieht sich auf die Row.
Vor beta.5 behielt das Kind innerhalb seines Anteils seine eigene Breite.
Expanded nimmt ein einzelnes Kind, kein Array. In einer Row füllt es die Breite, in einer Column die
Höhe.
Box
Box ist der gestaltete Container: Hintergrund, Border, abgerundete Ecken, Padding und auf Wunsch
eine feste Größe. Standardmäßig füllt sie die angebotene Breite (wie ein Block-Element) und passt ihre
Höhe dem Inhalt an. Mit width oder height legen Sie eine Abmessung fest. (Als festes Kind einer Row
passt sie auch ihre Breite dem Inhalt an, denn dort wird ihr keine Breite zum Füllen angeboten.)
Box({ bg: "#1450aa11", border: "steelblue", borderWidth: 1, radius: 8, padding: 16 }, [
Text("A framed, padded, rounded box."),
]);
| Prop | Typ | Was es tut |
|---|---|---|
bg | ColorInput | Füllfarbe |
border | ColorInput | Border-Farbe (alle Seiten) |
borderTop borderRight borderBottom borderLeft | ColorInput | eine einzelne Seite einfärben |
borderWidth | number | Border-Stärke (Standard 1, sobald eine Border gesetzt ist) |
radius | number | Corners | Eckenradius - eine Zahl, oder pro Ecke |
padding | Insets | Padding (siehe unten) |
width height | number | string | eine feste Größe in Punkt, oder "50%" des angebotenen Platzes |
minWidth maxWidth minHeight maxHeight | number | string | eine Abmessung begrenzen |
aspectRatio | number | die nicht festgelegte Achse ableiten (16 / 9) |
overflow | "hidden" | die Kinder auf die Box zuschneiden, samt abgerundeter Ecken |
relative | boolean | diese Box zum Frame für Positioned-Kinder machen |
Größen, in der Reihenfolge, in der sie entstehen
Die Reihenfolge ist der Vertrag, und sie folgt CSS: relative Größen → die Aspect Ratio füllt die
offen gebliebene Achse → min/max begrenzt. Eine ausdrückliche Grenze schlägt also die Aspect Ratio,
so wie min-height im Browser aspect-ratio schlägt.
Box({ width: "50%", maxWidth: 100 }, []); // 50% of a 400pt region is 200, clamped to 100
Box({ width: 240, aspectRatio: 16 / 9 }, []); // height derived: 135
Prozentwerte bei padding und margin beziehen sich auf allen vier Seiten auf die Breite, oben und
unten eingeschlossen. Das ist die Eigenheit von CSS, absichtlich beibehalten, damit sich ein aus dem Web
übernommenes Layout genauso verhält.
Das Padding einer Box bezieht sich auf die Breite, die der Box angeboten wird - die
Content-Breite ihres Elternelements, so wie CSS den Containing Block nimmt. geändert in beta.4 Vor
beta.4 nahm es die eigene Breite der Box, Box({ width: 200, padding: "10%" }) in einer 400pt breiten
Spalte hat also jetzt 40pt Padding statt 20pt. Innerhalb einer Row bezieht es sich auf die Breite
der Row geändert in beta.5 - vor beta.5 nahm es dort die eigene.
Gradients
bg nimmt einen Gradient genauso wie eine Farbe. Er wird relativ zur Box geschrieben - ein Winkel und
Color-Stops - und beim Zeichnen gegen die Box aufgelöst:
import { linearGradient, radialGradient } from "@jasy/pdf";
Box({ bg: linearGradient({ angle: 135, stops: ["#1450aa", "#7aa5e8"] }), padding: 20 }, [...]);
Box({ bg: radialGradient({ stops: ["#ffffff", "#e2483d"] }), width: 80, height: 80 }, []);
Winkel folgen CSS: 0 zeigt nach oben, gedreht wird im Uhrzeigersinn.
Ecken, einzeln
radius nimmt auch ein Objekt pro Ecke. Zwei Radien, die sich eine Kante teilen und überlappen würden,
werden gemeinsam verkleinert, damit die Form glatt bleibt - react-pdf begrenzt jede Ecke für sich,
und das gibt Knicke.
Box({ radius: { topLeft: 16, bottomRight: 16 }, bg: "#eef1f5", padding: 16 }, [...]);
Padding
Padding rückt ein einzelnes Kind ein, ohne etwas zu zeichnen. Es ist der Verwandte von Box, der nur
Abstand macht.
Padding({ top: 16, bottom: 8 }, Text("Breathing room above and below."));
Überall, wo ein Abstandswert verlangt wird (padding an einer Box, das Element Padding), können Sie
ihn auf vier Arten schreiben:
16 // all four sides
{ x: 16, y: 8 } // horizontal / vertical
{ top: 16, left: 8 } // per side, any subset
[16, 8, 16, 8] // [top, right, bottom, left]
Divider
Divider zieht eine dünne waagerechte Linie über die volle Breite seines Elternelements - eine saubere
Trennung zwischen Abschnitten, Summen oder Listeneinträgen.
Column({ gap: 4 }, [Text("Subtotal"), Divider(), Text("Total", { bold: true })]);
| Prop | Typ | Was es tut |
|---|---|---|
color | ColorInput | Linienfarbe (Standard ein helles Grau) |
thickness | number | Linienstärke in Punkt (Standard 1) |
margin | Insets | Abstand über / unter der Linie (Standard ein kleiner Abstand) |
Divider({ color: "#1450aa", thickness: 2, margin: { y: 12 } });
Ein Element, zwei Stellen seit beta.5
Ein Element hat eine Position, ein zweites Platzieren desselben Elements wirft deshalb
ElementReusedError. Platzieren Sie eine Kopie: clone() kopiert ein Element mit allem, was darin
steckt, so wie es gebaut wurde.
const badge = Box({ bg: "#1450aa", padding: 4 }, [Text("PAID", { color: "white" })]);
Page([badge, Text("Thank you for your order."), badge.clone()]);
Eine Funktion, die das Element baut (const badge = () => Box(...)), funktioniert genauso.
Farben
Überall, wo eine Farbe verlangt wird (color, bg, border), nimmt jasy mehrere Formen an. rgb und
rgba sind kleine Helfer, die Sie aus dem Paket importieren.
import { rgb, rgba } from "@jasy/pdf";
// every entry below is a valid color value:
const colors = [
"steelblue", // any CSS color name
"#1450aa", // hex
"#1450aacc", // hex with alpha
0xff1450aa, // ARGB number, alpha first
rgb(20, 80, 170), // channels 0 to 255
rgba(20, 80, 170, 0.5), // ... with an alpha of 0 to 1
"rgb(20, 80, 170)", // the CSS function, as a string
"rgb(20 80 170 / 50%)", // the modern space form, with slash-alpha
"hsl(214, 79%, 37%)", // and hsl(), the same sRGB family
];
Eine Funktion für erweiterten Farbraum wie color(display-p3 …) oder light-dark() wird benannt und
abgelehnt, statt sie in sRGB zu quetschen - das Ergebnis wäre eine Farbe, die niemand gewählt hat.
Alles in einer Datei
Hier ist jedes Primitive dieser Seite in einem einzigen Dokument. Ein Document kann mehr als eine Page
enthalten, also zeigt jede Seite unten eine Idee. Kopieren Sie es, führen Sie es einmal aus, und blättern
Sie durch das Ergebnis.
import { writeFileSync } from "node:fs";
import {
Document,
Page,
Column,
Row,
Box,
Padding,
Text,
Spacer,
Expanded,
Divider,
renderToBytes,
} from "@jasy/pdf";
const heading = (t: string) => Text(t, { size: 22, bold: true, color: "#1450aa" });
const sub = (t: string) => Text(t, { size: 12, color: "gray" });
async function build() {
const doc = Document([
Page({ size: "A4", margin: 56 }, [
Column({ gap: 16 }, [
heading("Column and Row"),
sub("Column stacks top to bottom, Row stacks left to right."),
Divider({ color: "steelblue" }),
Row({ justify: "between", align: "center" }, [
Text("Left", { bold: true }),
Text("Right", { bold: true }),
]),
]),
]),
Page({ size: "A4", margin: 56 }, [
Column({ gap: 16 }, [
heading("Spacer"),
sub("Flexible empty space that pushes items apart."),
Divider({ color: "steelblue" }),
Row([Text("Subtotal"), Spacer(), Text("100.00 EUR")]),
]),
]),
Page({ size: "A4", margin: 56 }, [
Column({ gap: 16 }, [
heading("Expanded"),
sub("Children grow to share the remaining space by their flex weight."),
Divider({ color: "steelblue" }),
Row({ gap: 12 }, [
Expanded(
{ flex: 1 },
Box({ border: "steelblue", padding: 14, radius: 6 }, [Text("flex 1")]),
),
Expanded({ flex: 2 }, Box({ bg: "#1450aa11", padding: 14, radius: 6 }, [Text("flex 2")])),
]),
]),
]),
Page({ size: "A4", margin: 56 }, [
Column({ gap: 16 }, [
heading("Box"),
sub("A background, a border, rounded corners and padding."),
Divider({ color: "steelblue" }),
Box({ bg: "#1450aa11", border: "steelblue", borderWidth: 1, radius: 8, padding: 18 }, [
Text("A framed, padded, rounded box."),
]),
]),
]),
Page({ size: "A4", margin: 56 }, [
Column({ gap: 16 }, [
heading("Padding"),
sub("Insets a single child without drawing anything."),
Divider({ color: "steelblue" }),
Box({ border: "steelblue", radius: 6 }, [
Padding(
{ x: 20, y: 14 },
Text("This text sits inside 20pt horizontal, 14pt vertical padding."),
),
]),
]),
]),
]);
writeFileSync("layout-tour.pdf", await renderToBytes(doc));
}
build();
Führen Sie es aus, öffnen Sie layout-tour.pdf, und blättern Sie durch Column und Row, Spacer, Expanded,
Box und Padding.