Dokumentation

Daten und Assets

Ihre Daten

Ein Dokument ist eine Komponente, Ihre Daten kommen also als Props. map, Bedingungen, Ihre eigenen Komponenten, Context - alles, was einen Baum erzeugt, wird zu einem PDF.

import { Divider, Document, Page, Row, Text } from "@jasy/react";

type Line = { id: number; name: string; price: number };

export function Statement({ lines }: { lines: Line[] }) {
  const total = lines.reduce((sum, l) => sum + l.price, 0);
  return (
    <Document size={11}>
      <Page size="A4" margin={48} gap={8}>
        {lines.map((line) => (
          <Row key={line.id} justify="between">
            <Text>{line.name}</Text>
            <Text>{line.price} EUR</Text>
          </Row>
        ))}
        <Divider />
        <Row justify="between">
          <Text bold>Total</Text>
          <Text bold>{total} EUR</Text>
        </Row>
      </Page>
    </Document>
  );
}

Ein PDF wird einmal gerendert und nicht wie eine Seite am Leben gehalten: Laden Sie Ihre Daten vorrenderToPdf und übergeben Sie sie. Ein Effect, der nach dem ersten Render State setzt (useEffect, der Daten holt), kommt zu spät - das Dokument ist dann schon gelesen.

Eigene Fonts

Die Standard-PDF-Fonts (Helvetica, Times, Courier) brauchen nichts. Für einen eigenen registrieren Sie den Font unter einem Namen an <Document fonts> und verwenden diesen Namen dann als font. Im Browser holen Sie zuerst die Bytes:

const inter = new Uint8Array(await (await fetch("/fonts/Inter.woff2")).arrayBuffer());
const bytes = await renderToPdf(<Report font={inter} />);

function Report({ font }: { font: Uint8Array }) {
  return (
    <Document fonts={{ Inter: font }} font="Inter">
      <Page size="A4" margin={48}>
        <Text size={24} bold>
          Set in Inter
        </Text>
      </Page>
    </Document>
  );
}

Das Format spielt keine Rolle: .ttf, .woff und .woff2 sind hier dasselbe - jasy entpackt den Wrapper selbst. Der Font wird auf dem Weg ins PDF gesubsettet, die Datei bleibt also klein. Für mehrere Schriftstärken registrieren Sie eine Datei pro Stil: fonts={{ Inter: { normal, bold, italic } }}.

Auf einem Server funktioniert auch ein einfacher Pfad - fonts={{ Inter: "./fonts/Inter.woff2" }} ist schon alles. Im Browser gibt es kein Dateisystem, auf das man zeigen könnte, also sind es Bytes.

Bilder

<Image src> nimmt die Bild-Bytes als Uint8Array, eine URL oder - auf einem Server - einen Pfad. JPEG und PNG funktionieren beide, und die Transparenz eines PNG liegt über dem, was dahinter ist, nicht in einem weißen Kasten.

const logo = new Uint8Array(await (await fetch("/logo.png")).arrayBuffer());

<Image src={logo} width={120} height={40} fit="contain" />;

Eine URL wird geladen, während das Dokument vorbereitet wird, noch vor dem Layout, ihre Größe ist also bekannt, wenn die Seite angeordnet wird.

Render-Optionen

renderToPdf (und renderToPdfString) nehmen die Render-Optionen der Engine als zweites Argument:

const bytes = await renderToPdf(<Invoice {...data} />, {
  onOverflow: "warn",
  accessible: true,
  encrypt: { userPassword: "secret" },
  onDiagnostic: (d) => console.log(d.code, d.message),
});
  • onOverflow - was passiert, wenn ein einzelnes unteilbares Element höher als die Seite ist: "error" (Standard) wirft, "warn" platziert es abgeschnitten und protokolliert, "ignore" platziert es abgeschnitten.
  • accessible - ein getaggtes PDF mit Structure Tree für Screenreader, siehe Barrierefreiheit.
  • encrypt - Passwortschutz mit AES-256, siehe Verschlüsselung.
  • onDiagnostic - alles, was die Engine über das Dokument zu sagen hat, ein Aufruf pro Befund, siehe Ausgabeoptionen.

Ein Descriptor für einen Server

toDocumentDescriptor(element) hält vor dem PDF an und gibt den Descriptor der Engine zurück: einen einfachen Baum { type, props, children }. Um ihn als JSON zu senden, kodieren Sie Font- und Bild-Bytes selbst - JSON kennt kein Uint8Array - und dekodieren sie auf der anderen Seite. Funktionen überleben JSON gar nicht, ein Dokument mit Canvas, Flow oder PageBuilder geht also über renderToPdf.