Dokumentation
PDFs erstellen
PDFs in Vue
PDFs in Nuxt
PDFs in React
PDFs in Angular
Positionierung
Das meiste Layout fließt von oben nach unten. Manchmal soll etwas ÜBER dem Flow sitzen: ein Badge auf
einer Karte, ein Ribbon, ein Titel, der in eine Box hineinragt, ein Watermark. Genau das leisten
relative-Frames und Positioned-Kinder. Die Idee ist die Positionierung aus CSS; Positioned ist die
Komponente, die platziert.
Ein Badge auf einer Karte
Machen Sie eine Box mit relative: true zum Frame. Darin umschließen Sie beliebigen Inhalt mit
Positioned und geben ihm Abstände von den Kanten des Frames. Ein Positioned-Kind ist OUT OF
FLOW: Es reserviert keinen Platz und schiebt nichts weg, es zeichnet einfach dort, wo Sie es hinsetzen.
Ein negativer Abstand lässt es über den Frame hinausragen - der „eingeschnittene“ Look.
import { writeFileSync } from "node:fs";
import { Document, Page, Box, Text, Positioned, renderToBytes } from "@jasy/pdf";
const tag = (bg: string, fg: string, t: string) =>
Box({ bg, radius: 6, padding: { x: 11, y: 6 } }, [Text(t, { size: 11, bold: true, color: fg })]);
async function build() {
const doc = Document([
Page({ size: "A4", margin: 56 }, [
Box({ relative: true, bg: "#eef3fb", border: "#1450aa", radius: 12, padding: 24 }, [
Text("Project Aurora", { size: 18, bold: true, color: "#0a2348" }),
Text("Badges anchor to the card's corners.", { size: 12, color: "gray" }),
Positioned({ top: -12, left: -12 }, tag("#f3dc29", "#0a2348", "NEW")), // pokes out
Positioned({ top: 14, right: 14 }, tag("#1450aa", "#ffffff", "v2")), // inside the corner
]),
]),
]);
writeFileSync("badge.pdf", await renderToBytes(doc));
}
build();
Anchoring
Es gibt zwei Arten, ein Kind zu platzieren - nehmen Sie, was sich am besten liest, auch eine pro Achse.
An einer Kante festmachen. top / left / right / bottom sind Abstände von den Kanten des
Frames. Setzen Sie eine pro Achse; das Kind passt sich seinem Inhalt an, und ein negativer Wert lässt es
hinausragen.
Positioned({ top: 14, right: 14 }, badge); // inside the top-right corner
Positioned({ bottom: -10, left: -10 }, badge); // pokes out the bottom-left
Setzen Sie beide Kanten einer Achse, spannt sich das Kind dazwischen: left und right geben ihm die
Breite dazwischen, top und bottom die Höhe.
Positioned platziert nur; das Kind behält seine eigene Größe. Mit einer Kante bleibt ein langer Text
einzeilig und läuft über den Frame hinaus. Damit er umbricht, geben Sie ihm eine Breite - spannen Sie
ihn, oder geben Sie dem Kind eine Größe:
Positioned({ top: 0, left: 20, right: 20 }, Text(note)); // wraps between the two edges
Positioned({ top: 0, left: 20 }, Box({ width: 160 }, [Text(note)])); // wraps at 160
Anchor setzen. h und v setzen das Kind an einen Punkt - start, center oder end auf jeder Achse
-, und x / y verschieben es von dort aus. „Genau in der Mitte, zehn Punkt höher“ ist also
{ h: "center", v: "center", y: -10 }. Das ist, was Kanten nicht sauber können: zentrieren.
Positioned({ h: "center", v: "center" }, watermark); // dead center of the frame
Positioned({ h: "end", v: "end", x: -8, y: -8 }, stamp); // bottom-right, inset 8
Positioned({ h: "center", v: "start", y: 12 }, ribbon); // top-center, nudged 12 down
Eine Kante gewinnt auf derselben Achse gegen einen Anchor. Ohne relative-Vorfahren richtet sich ein
Positioned an der Content-Box der Seite aus.
Stacking mit zIndex seit beta.5
Ohne zIndex liegt oben, was später geschrieben steht - ein Positioned, das vor dem Text steht, liegt
darunter, eines danach darüber. zIndex legt die Reihenfolge stattdessen fest: Höher liegt über
niedriger, ein positiver Wert über dem übrigen Inhalt des Frames, ein negativer darunter (aber über dem
eigenen Hintergrund des Frames). Gleiche Werte behalten die geschriebene Reihenfolge; 0 ist dasselbe wie
kein Wert.
Box({ relative: true, height: 130 }, [
Positioned({ top: 30, left: 60, zIndex: 2 }, blueCard),
Positioned({ top: 50, left: 120, zIndex: 3 }, yellowCard), // on top
Positioned({ top: 10, left: 0, zIndex: 1 }, navyCard), // at the bottom, though written last
]);
Die Reihenfolge gilt im ganzen Frame: Ein Positioned in einem anderen wird mit allen übrigen sortiert,
ein Badge tief in einer Karte kann also über einer Karte liegen, die danach geschrieben steht. Zwei Stellen
haben ihre eigene Reihenfolge: eine Box mit overflow: "hidden" und ein Rotated. Ein Positioned darin
wird dort sortiert und verlässt nie den Clip oder die Rotation, in der es geschrieben wurde.
Cropping mit overflow
Das overflow eines Frames entscheidet, was mit einem Kind geschieht, das über seine Kante läuft:
"visible" (der Standard) lässt es überstehen, "hidden" schneidet es am Frame ab, samt abgerundeter
Ecken. Hier ist dieselbe Karte zweimal nebeneinander, nur overflow ist anders.
import { writeFileSync } from "node:fs";
import { Document, Page, Row, Box, Text, Positioned, Expanded, renderToBytes } from "@jasy/pdf";
// a blue box pinned inside the card, extending past its bottom edge
const card = (overflow: "visible" | "hidden") =>
Box(
{
relative: true,
overflow,
bg: "#eef3fb",
border: "#1450aa",
radius: 10,
height: 70,
padding: 14,
},
[
Text(`overflow: "${overflow}"`, { size: 12, bold: true, color: "#0a2348" }),
Positioned(
{ top: 34, left: 14 },
Box({ bg: "#1450aa", radius: 8, width: 170, height: 100, padding: 12 }, [
Text("Pinned inside the card, it extends past the bottom edge.", {
size: 10,
color: "#ffffff",
}),
]),
),
],
);
async function build() {
const doc = Document([
Page({ size: "A4", margin: 56 }, [
Row({ gap: 24 }, [Expanded(card("visible")), Expanded(card("hidden"))]),
]),
]);
writeFileSync("overflow.pdf", await renderToBytes(doc));
}
build();
Links hängt die blaue Box über die Karte hinaus. Rechts wird die identische Box bündig an der Kante der
Karte abgeschnitten. overflow: "hidden" schneidet auch ein Image auf eine abgerundete Box zu, ein
sauberer Weg zu einem abgerundeten oder runden Foto.
Eine wiederverwendbare Komponente
Eine Komponente ist einfach eine Funktion, die einen Baum zurückgibt, „eine Karte mit eingeschnittenem Titel“ sind also ein paar Zeilen, die Sie überall wiederverwenden.
import { Box, Padding, Positioned, Text, type PDFElement } from "@jasy/pdf";
const TitledCard = (title: string, body: PDFElement) =>
Box({ relative: true, overflow: "hidden", bg: "#eef3fb", radius: 10 }, [
Padding(20, body),
Positioned(
{ top: -10, left: -10 },
Box({ bg: "#1450aa", radius: 6, padding: { x: 12, y: 6 } }, [
Text(title, { color: "white", bold: true }),
]),
),
]);