std.pdf erzeugt PDF-Dokumente nativ — ohne externe Bibliothek, ohne Rendering-Engine. 24 Units decken Text, Grafik, Bilder, Formulare, Lesezeichen, Anhänge, Verschlüsselung und das Lesen vorhandener Dateien ab. Diese Seite führt vom leeren Dokument bis zum verschlüsselten Formular.
→ std.pdf — Unit-Referenz · Bildverarbeitung · SVG
Jedes PDF-Programm hat dieselben fünf Schritte: Dokument anlegen, Seite hinzufügen, zeichnen, speichern, freigeben.
import std.io;
import std.pdf.builder;
import std.pdf.page;
import std.pdf.fonts;
import std.pdf.graphics;
import std.pdf.meta;
fn main(): int64 {
var doc: int64 := PdfNew();
if (doc == 0) { PrintLn("PdfNew fehlgeschlagen"); return 1; }
PdfSetTitle(doc, "Bericht"c);
PdfSetAuthor(doc, "Andreas"c);
var page: int64 := PdfAddPage(doc, PDF_A4_W, PDF_A4_H);
PdfSetFont(doc, page, PDF_FONT_HELVETICA_BOLD, 24.0);
PdfTextAt(doc, page, 60.0, 760.0, "Quartalsbericht"c);
PdfSetFont(doc, page, PDF_FONT_HELVETICA, 11.0);
PdfTextAt(doc, page, 60.0, 730.0, "Erstellt mit Lyx"c);
PdfSetStrokeColor(doc, page, 0.2, 0.2, 0.6);
PdfSetLineWidth(doc, page, 1.5);
PdfMoveTo(doc, page, 60.0, 720.0);
PdfLineTo(doc, page, 535.0, 720.0);
PdfStroke(doc, page);
if (PdfSave(doc, "bericht.pdf"c) != 0) { PrintLn("Speichern fehlgeschlagen"); return 1; }
PdfFree(doc);
return 0;
}
Ergebnis: eine gültige Datei (PDF document, version 1.4, 1 page(s), 924 Byte).
| Unit | Zuständig für |
|---|---|
std.pdf.builder | Dokument, Speichern, Kompression, Verschlüsselung |
std.pdf.page | Seiten anlegen, Seitenformate, Drehung |
std.pdf.fonts | die 14 Standardschriften |
std.pdf.graphics | Text setzen, Linien, Formen, Farben, Transformationen |
std.pdf.image | Rasterbilder einbetten |
std.pdf.meta | Titel, Autor, Schlagwörter |
std.pdf.annot | Links und Notizen |
std.pdf.outline | Lesezeichenbaum |
std.pdf.forms | Formularfelder |
std.pdf.reader | vorhandene PDFs lesen, Seiten importieren, zusammenführen |
std.pdf.attach | Dateianhänge |
std.pdf.shading, .layer, .xobject, .spot | Verläufe, Ebenen, wiederverwendbare Objekte, Sonderfarben |
std.pdf.pdfa, .xmp, .viewprefs, .pagelabels | Archivierung, Metadatenstrom, Anzeigeoptionen, Seitennummerierung |
std.pdf.ttfont | TrueType-Schriften einbetten |
Importiert wird nur, was gebraucht wird — die Units sind unabhängig voneinander.
Der Ursprung liegt unten links, die Y-Achse zeigt nach oben. Gemessen wird in Punkt (1/72 Zoll).
841.89 ┌──────────────────┐ ← oberer Seitenrand (A4)
│ │
760.0 │ Überschrift │ ← PdfTextAt(doc, page, 60.0, 760.0, …)
│ │
0.0 └──────────────────┘ ← Ursprung
0.0 595.28
| Format | Konstanten | Punkt |
|---|---|---|
| A4 | PDF_A4_W × PDF_A4_H | 595.28 × 841.89 |
| A3 | PDF_A3_W × PDF_A3_H | 841.89 × 1190.55 |
| A5 | PDF_A5_W × PDF_A5_H | 419.53 × 595.28 |
| Letter | PDF_LETTER_W × PDF_LETTER_H | 612 × 792 |
| Legal | PDF_LEGAL_W × PDF_LEGAL_H | 612 × 1008 |
| B5 | PDF_B5_W × PDF_B5_H | 498.90 × 708.66 |
Ein Querformat entsteht durch Vertauschen: PdfAddPage(doc, PDF_A4_H, PDF_A4_W).
Alle Koordinaten und Größen sind f64. PdfTextAt(doc, page, 60, 760, …) mit Ganzzahlen ist ein Typfehler — 60.0 schreiben.
PdfSetFont(doc, page, PDF_FONT_HELVETICA_BOLD, 24.0);
PdfSetTextColor(doc, page, 0.1, 0.1, 0.4);
PdfTextAt(doc, page, 60.0, 760.0, "Überschrift"c);
PdfSetFont(doc, page, PDF_FONT_TIMES, 12.0);
PdfSetLineSpacing(doc, page, 16.0);
PdfTextBlock(doc, page, 60.0, 700.0, 400.0,
"Ein laengerer Absatz, der automatisch umbrochen wird, wenn er breiter als die angegebene Breite ist."c);
PdfTextAt setzt eine Zeile an eine feste Stelle — kein Umbruch, kein Rand.PdfTextBlock bricht innerhalb der angegebenen Breite um; der Zeilenabstand kommt aus PdfSetLineSpacing.PdfSetTextColor(doc, page, 1.0, 0.0, 0.0) ist reines Rot.| Familie | Konstanten |
|---|---|
| Helvetica | PDF_FONT_HELVETICA, _BOLD, _OBLIQUE, _BOLDOBLIQUE |
| Times | PDF_FONT_TIMES, _BOLD, _ITALIC, _BOLDITALIC |
| Courier | PDF_FONT_COURIER, _BOLD, _OBLIQUE, _BOLDOBLIQUE |
| Symbol | PDF_FONT_SYMBOL |
| ZapfDingbats | PDF_FONT_ZAPFDINGBATS |
Diese Schriften sind in jedem Betrachter vorhanden und werden nicht eingebettet — das hält die Datei klein. Wer eine eigene Schrift oder Zeichen jenseits von Latin-1 braucht, bettet über std.pdf.ttfont eine TrueType-Datei ein.
PdfSetStrokeColor(doc, page, 0.2, 0.2, 0.6);
PdfSetLineWidth(doc, page, 1.5);
PdfMoveTo(doc, page, 60.0, 720.0);
PdfLineTo(doc, page, 535.0, 720.0);
PdfStroke(doc, page); // zeichnen — ohne diesen Aufruf bleibt der Pfad unsichtbar
PdfSetFillColor(doc, page, 0.9, 0.9, 1.0);
PdfRect(doc, page, 60.0, 640.0, 200.0, 50.0);
PdfFill(doc, page);
Ein Pfad wird erst durch PdfStroke, PdfFill oder PdfFillStroke sichtbar. Wer das vergisst, bekommt eine leere Seite und sucht den Fehler bei den Koordinaten.
| Aufgabe | Funktion |
|---|---|
| Pfad beginnen / fortsetzen | PdfMoveTo, PdfLineTo, PdfCurveTo, PdfClosePath |
| Fertige Formen | PdfRect, PdfCircle, PdfEllipse |
| Zeichnen | PdfStroke, PdfFill, PdfFillStroke |
| Beschneiden | PdfClip |
| Linienbild | PdfSetLineWidth, PdfSetLineCap, PdfSetLineJoin, PdfSetDash |
| Farbe | PdfSetStrokeColor, PdfSetFillColor, dazu die …CMYK-Varianten |
PdfSaveState(doc, page);
PdfTranslate(doc, page, 300.0, 400.0);
PdfRotate(doc, page, 45.0);
PdfRect(doc, page, -50.0, -10.0, 100.0, 20.0);
PdfFill(doc, page);
PdfRestoreState(doc, page); // Drehung gilt danach nicht mehr
Transformationen wirken auf alles Folgende. Ohne das Paar PdfSaveState/PdfRestoreState steht der Rest der Seite schief. Faustregel: jede Transformation gehört zwischen ein solches Paar.
Für den Druck ist CMYK die richtige Wahl: PdfSetFillColorCMYK(doc, page, 0.0, 0.9, 0.8, 0.0). RGB-Werte werden von der Druckerei umgerechnet — mit Farbabweichungen, die man dann nicht mehr kontrolliert.
PdfAddImage nimmt rohe Pixeldaten, keine PNG- oder JPEG-Datei: Breite, Höhe und Kanalzahl (3 = RGB, 1 = Graustufen).
var img: int64 := alloc(12); // 2×2 Pixel, RGB
poke8(img + 0, 255); poke8(img + 1, 0); poke8(img + 2, 0); // rot
poke8(img + 3, 0); poke8(img + 4, 255); poke8(img + 5, 0); // grün
poke8(img + 6, 0); poke8(img + 7, 0); poke8(img + 8, 255); // blau
poke8(img + 9, 255); poke8(img + 10, 255); poke8(img + 11, 0); // gelb
var imgIdx: int64 := PdfAddImage(doc, img, 2, 2, 3);
PdfDrawImage(doc, page, imgIdx, 60.0, 500.0, 100.0, 100.0);
PdfAddImage bettet die Daten einmal ein und liefert einen Index. Dasselbe Bild mehrfach auf verschiedenen Seiten zeichnen: einmal hinzufügen, mehrfach PdfDrawImage.PdfDrawImage ist in Punkt, nicht in Pixeln. Ein 2×2-Bild auf 100×100 Punkt wird entsprechend hochskaliert.std.image → Bildverarbeitungs-Guide.PdfSetCompression(doc, 6) vor dem Speichern schaltet die Zlib-Kompression der Streams ein.
// Externer Link auf einen rechteckigen Bereich
var link: int64 := PdfAddLink(doc, page, 60.0, 480.0, 200.0, 495.0);
PdfLinkSetURI(doc, link, "https://seolizer.de"c);
// Sprung innerhalb des Dokuments
var innen: int64 := PdfAddLinkGoto(doc, page, 60.0, 460.0, 200.0, 475.0);
PdfLinkSetGoto(doc, innen, 1); // Zielseite (0-basiert)
// Notiz
PdfAddNote(doc, page, 400.0, 700.0, "Hinweis"c, "Zahl noch pruefen"c);
// Lesezeichenbaum
var kapitel: int64 := PdfOutlineAdd(doc, -1, "Kapitel 1"c, 0);
PdfOutlineAdd(doc, kapitel, "Abschnitt 1.1"c, 1);
PdfOutlineAdd mit -1 als Elternindex legt einen Eintrag auf oberster Ebene an; der Rückgabewert ist der Elternindex für Untereinträge. Ein Link ist ein Rechteck, kein Text — die sichtbare Beschriftung setzt man separat mit PdfTextAt an dieselbe Stelle.
import std.pdf.forms;
import std.pdf.shading; // wegen PdfPackCoords
var pos: int64 := PdfPackCoords(12000, 69000); // x=120.00, y=690.00
var size: int64 := PdfPackCoords(20000, 2000); // 200.00 × 20.00
var feld: int64 := PdfAddTextField(doc, page, pos, size, "name"c, ""c);
Formularkoordinaten werden ×100 als Ganzzahl übergeben, nicht alsf64.PdfPackCoords(12000, 69000)meint 120.00 / 690.00. Die Funktion packt zwei 32-Bit-Werte in einenint64und steht instd.pdf.shading— nicht instd.pdf.forms, wo man sie vermuten würde. Fehlt der Import, meldet der Compilerundefined function 'PdfPackCoords'.
| Feldtyp | Funktion |
|---|---|
| Textfeld | PdfAddTextField(doc, page, pos, size, name, vorgabe) |
| Kontrollkästchen | PdfAddCheckbox(doc, page, pos, size100, name, checked) |
| Optionsfeld | PdfAddRadioButton(doc, page, pos, size100, gruppe, wert) |
| Auswahlliste | PdfAddComboBox + PdfComboAddItem |
PdfFlattenForms(doc) wandelt alle Felder in feste Seiteninhalte um — das Dokument ist danach nicht mehr ausfüllbar. Der richtige letzte Schritt für ein archiviertes, unterschriebenes Formular.
PdfSetUserPassword(doc, "geheim"c); // zum Öffnen nötig
PdfSetOwnerPassword(doc, "chef"c); // hebt die Berechtigungsgrenzen auf
PdfSetPermissions(doc, 0); // nichts erlaubt: kein Druck, kein Kopieren
PdfSetEncryption(doc, 1); // schaltet die Verschlüsselung ein
Die Reihenfolge zählt: PdfSetEncryption zuletzt, nach den Passwörtern und Berechtigungen. Das Ergebnis ist echt verschlüsselt — ein Betrachter ohne Passwort scheitert mit Incorrect password.
Ein Benutzerpasswort schützt den Inhalt. Berechtigungen ohne Benutzerpasswort sind dagegen eine Bitte, keine Sperre: Jeder Betrachter kann sie ignorieren.
import std.pdf.reader;
var rdr: int64 := PdfOpen("bericht.pdf"c);
if (rdr == 0) { PrintLn("Lesen fehlgeschlagen"); return 1; }
Print("Seiten: "); PrintLn(IntToStr(PdfRdrPageCount(rdr)));
var buf: int64 := alloc(512);
PdfRdrGetTitle(rdr, buf, 512);
var titel: pchar := buf as pchar;
PrintLn(titel); // Bericht
PdfRdrExtractText(rdr, 0, buf, 512);
var text: pchar := buf as pchar;
PrintLn(text); // QuartalsberichtErstellt mit Lyx
PdfClose(rdr);
PdfRdrExtractText liefert die Textstücke der Seite hintereinander, ohne Trennzeichen — Zeilen- und Wortgrenzen der Vorlage gehen verloren. Für eine Volltextsuche reicht das; für eine Rekonstruktion des Layouts nicht.PdfRdrGetTitle, …Author, …Subject, …Keywords, …Creator, …Producer — alle schreiben in einen bereitgestellten Puffer.PdfImportPage(ziel, quelle, index) übernimmt eine einzelne Seite, PdfMergeAll(ziel, quelle) das ganze Dokument. Damit lassen sich PDFs zusammenführen, ohne den Inhalt neu zu erzeugen.
import std.io;
import std.alloc;
import std.pdf.builder;
import std.pdf.page;
import std.pdf.fonts;
import std.pdf.graphics;
import std.pdf.image;
import std.pdf.annot;
import std.pdf.outline;
import std.pdf.meta;
fn main(): int64 {
var doc: int64 := PdfNew();
PdfSetCompression(doc, 6);
PdfSetTitle(doc, "Handbuch"c);
var p1: int64 := PdfAddPage(doc, PDF_A4_W, PDF_A4_H);
PdfSetFont(doc, p1, PDF_FONT_TIMES, 12.0);
PdfTextBlock(doc, p1, 60.0, 700.0, 400.0,
"Ein laengerer Absatz, der automatisch umbrochen wird."c);
var img: int64 := alloc(12);
poke8(img + 0, 255); poke8(img + 1, 0); poke8(img + 2, 0);
poke8(img + 3, 0); poke8(img + 4, 255); poke8(img + 5, 0);
poke8(img + 6, 0); poke8(img + 7, 0); poke8(img + 8, 255);
poke8(img + 9, 255); poke8(img + 10, 255); poke8(img + 11, 0);
var imgIdx: int64 := PdfAddImage(doc, img, 2, 2, 3);
PdfDrawImage(doc, p1, imgIdx, 60.0, 500.0, 100.0, 100.0);
var link: int64 := PdfAddLink(doc, p1, 60.0, 480.0, 200.0, 495.0);
PdfLinkSetURI(doc, link, "https://seolizer.de"c);
var p2: int64 := PdfAddPage(doc, PDF_A4_W, PDF_A4_H);
PdfSetFont(doc, p2, PDF_FONT_HELVETICA, 12.0);
PdfTextAt(doc, p2, 60.0, 760.0, "Seite 2"c);
var kapitel: int64 := PdfOutlineAdd(doc, -1, "Kapitel 1"c, 0);
PdfOutlineAdd(doc, kapitel, "Abschnitt 1.1"c, 1);
if (PdfSave(doc, "handbuch.pdf"c) != 0) { PrintLn("Speichern fehlgeschlagen"); return 1; }
PdfFree(doc);
return 0;
}
Ergebnis: 2 Seiten, 1938 Byte, mit Bild, Link und Lesezeichenbaum.
f64 — außer bei Formularfeldern, die PdfPackCoords mit ×100-Ganzzahlen erwarten.PdfStroke/PdfFill bleibt der Pfad unsichtbar.PdfSave gibt 0 bei Erfolg zurück — wie in der übrigen Standardbibliothek. Ein Wert ≠ 0 ist der Fehlerfall.PdfFree nach dem Speichern. Ein Dokument mit eingebetteten Bildern hält schnell Megabyte; in einem Dienst, der Dokumente am Fließband erzeugt, ist das der erste Speicherfresser.PdfSaveState ziehen sich durch den Rest der Seite.€, kyrillische oder asiatische Zeichen eine TrueType-Schrift über std.pdf.ttfont einbetten.PdfPackCoords steht in std.pdf.shading — der Import fehlt sonst.pchar-Feld gibt PrintLn als Adresse aus — über eine Variable gehen: var s: pchar := buf as pchar; PrintLn(s);. Die Umwandlung im Argument selbst arbeitet seit lyxc 1.0.16F korrekt.
Alle Codebeispiele dieser Seite sind mit lyxc 1.0.16F übersetzt und ausgeführt worden; die erzeugten Dateien wurden mit file und pdfinfo geprüft.