Dokumentation

SVG und Canvas

PDF ist ein Vektorformat. Ein als PNG eingefügtes Logo wirft das weg: Es wird unscharf, wenn jemand hineinzoomt, fett im Druck, und es trägt Pixel in ein Dokument, das archiviert werden soll. jasy zeichnet stattdessen Vektoren - entweder aus einer SVG-Datei, die Sie schon haben, oder mit einem Pen, den Sie selbst führen.

Image erkennt eine SVG-Quelle von selbst, ein Logo braucht also keine Sonderbehandlung:

import { Document, Page, Image, Svg, renderToBytes } from "@jasy/pdf";

Image("logo.svg", { width: 120 }); // a path (Node), bytes, or markup
Svg(markup, { width: 120, alt: "Acme" }); // the same thing, named explicitly

Ohne Größe zeichnet es in seiner eigenen Größe geändert in beta.5 - width/height aus der Datei, sonst ihre viewBox, sonst 300 x 150 px, wie im Browser. Eine User Unit ist ein CSS-Pixel, 1/96 Zoll, nach derselben Regel wie bei einem Raster-Image, und pt, pc, in, cm, mm und q werden umgerechnet, width="100mm" sind also 100 mm. Gibt die Datei eine Seite an, folgt die andere der Aspect Ratio der viewBox; eine Größe in % zählt als nicht angegeben. Vor beta.5 war eine User Unit ein Punkt, und Einheiten wurden ignoriert.

Legen Sie eine Achse fest, folgt die andere der Aspect Ratio, genau wie bei einem <img> ohne Größe; ist es breiter als der angebotene Platz, wird es verkleinert, nie vergrößert. Die Zeichnung wird gleichmäßig skaliert und in ihrer Box zentriert, das ist der eigene Standard von SVG für preserveAspectRatio.

Die Datei wird gelesen und geparst, wenn Sie sie nennen, nicht beim Rendern, ein falscher Pfad oder eine fehlerhafte Datei scheitert also in dieser Zeile und nicht drei Aufrufebenen tief in einem Rendervorgang.

Was es zeichnet

Wird gezeichnetWird mit Namen gemeldet, nie übersprungen
path (Bögen, Kurzformen, relative Koordinaten), rect, circle, ellipse, line, polyline, polygon<text> - wandeln Sie es in Pfade um
g, defs, viewBox, jede transform-Funktion<use>, <symbol>
Presentation Attributes, style="" und <style>-Blöcke mit flachen Selektorenein CSS-Combinator oder eine Pseudo-Class
fill, stroke (Width, Cap, Join, Dash), fill-rule, Opacityfilter, <mask> - Pixeloperationen
<clipPath>, <linearGradient>, <radialGradient>, href-Kettenein Gradient auf einem Stroke

SVG ist ein Dokumentformat, kein Grafikformat. Es bringt CSS, Text-Layout, Filter und Animation mit, und Filter und Masken sind Pixeloperationen, die kein Vektor-Backend ausdrücken kann - deshalb hat alles, was SVG vollständig rendert, einen Rasterizer, und deshalb liefert jede PDF-Bibliothek ein Subset. Unsere steht oben.

Der Unterschied liegt darin, was an ihrem Rand passiert: Ein Element, das jasy nicht zeichnen kann, ist ein benannter Fehler mit Lösung, nie ein stilles Überspringen. Ein weggelassenes Element hinterlässt ein Logo, das sich rendern lässt und unbemerkt falsch ist, und niemand merkt es. (Gemessen: Die <style>-Verarbeitung von react-pdf verliert jede Füllung eines Illustrator-Exports mit dessen Standardoption „Internes CSS“.)

@jasy/pdf: the SVG element <text> is not supported yet.
           Convert the text to outlines in your editor - a PDF cannot lay out SVG text.

Das Subset wurde an 10.819 echten SVG-Dateien von einem Arbeitsrechner geprüft: 96,4 % werden gerendert. Die Korrektheit wird gegen Headless-Chrome geprüft, nicht gegen unsere eigenen Überlegungen - so haben wir herausgefunden, dass das Root-<svg> auf sich selbst zuschneidet und dass sein eigenes fill="none" (der Standard beim Export aus Figma, in 778 dieser Dateien) gelesen werden muss, sonst wird jede Form ohne Füllung schwarz.

Selbst zeichnen

Canvas ist der Ausweg für alles, was keine Komponente abdeckt: eine Sparkline, ein Siegel, ein Diagramm, ein Schnörkel unter der Unterschrift. Sie bekommen eine Box im Layout und einen typisierten Pen.

import { Canvas } from "@jasy/pdf";

Canvas({ height: 46 }, (c, { width, height }) => {
  c.move(0, height).line(20, 12).line(60, 22).line(width, 4);
  c.stroke("#1450aa", { width: 1.5, cap: "round", join: "round" });

  c.circle(width - 4, 4, 2.5).fill("#1450aa");
});

Die Funktion bekommt die Größe, die das Layout ermittelt hat, eine Zeichnung, die gegen size.width geschrieben ist, funktioniert also in jeder Breite - setzen Sie dieselbe Funktion in eine Box mit 300pt und eine mit 160pt, und sie passt in beide.

Der Pen

Einen Pfad bauenIhn malen
move line curve quad closefill(paint?, { rule })
rect circle ellipse polygonstroke(color?, { width, cap, join, miterLimit, dash, dashOffset })
path(d) - SVG-Path-Daten, durch denselben Parser, den Svg() verwendetfillAndStroke(fill, stroke, opts)

Malen schließt den Pfad ab und beginnt einen neuen, eine Form kann also nie in die nächste hineinlaufen. fill nimmt jede Farbform, die auch der Rest der API nimmt - und einen Gradient, aufgelöst gegen die eigene Bounding Box der Form, so wie objectBoundingBox in SVG.

Scopes statt save/restore

Eine Transformation oder ein Clip ist ein Scope, der sich selbst schließt. Es gibt kein Paar save()/restore(), das man vergessen kann, und kein lineWidth(2), das ein späteres stroke() zufällig übernimmt:

Canvas({ width: 90, height: 90 }, (c, { width }) => {
  for (let i = 0; i < 12; i++) {
    c.group({ translate: [width / 2, width / 2], rotate: i * 30 }, (g) => {
      g.rect(-1, -width / 2 + 6, 2, i % 3 === 0 ? 10 : 5).fill("#0a2348");
    });
  }

  c.clipped(
    (clip) => clip.circle(width / 2, width / 2, 20),
    (draw) => draw.rect(0, 0, width, width).fill("#f3dc29"),
  );
});

Koordinaten beginnen oben links und sind in Punkt - dasselbe System wie bei jedem anderen Element und dasselbe wie in SVG. 0,0 ist die eigene Ecke der Canvas-Box.

Ein Block im Layout, kein Weg daran vorbei

Eine Canvas wird auf ihre eigene Box zugeschnitten, bricht als eine unteilbare Einheit um und kann mit alt in einem getaggten PDF zu einer Figure werden. Was sie absichtlich nicht hat, ist Textsatz: Beschriftungen gehören in Text, in den Baum, wo sie markierbar, durchsuchbar und getaggt bleiben.

Row({ gap: 8, align: "stretch" }, [
  Column(
    { justify: "between", width: 26 },
    ticks.map((t) => Text(String(t), { size: 7 })),
  ),
  Expanded(
    { flex: 1 },
    Column({ gap: 4 }, [
      Canvas({ height: 170, alt: "Monthly revenue" }, barChart),
      Row(
        { justify: "between" },
        months.map((m) => Text(m, { size: 7 })),
      ),
    ]),
  ),
]);

Das ist ein echtes Diagramm: Die Zeichnung ist eine Canvas, die Achsen sind Text im Layout.

Beides gibt es auch als Komponenten in Vue, React und Nuxt - siehe die Komponenten für Vue und React.