PDF-Guide
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
1. Das Grundgerüst
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).
Die Units und wofür sie zuständig sind
| 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.
2. Das Koordinatensystem
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.
3. Text
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);
PdfTextAtsetzt eine Zeile an eine feste Stelle — kein Umbruch, kein Rand.PdfTextBlockbricht innerhalb der angegebenen Breite um; der Zeilenabstand kommt ausPdfSetLineSpacing.- Farben sind RGB im Bereich 0.0–1.0, nicht 0–255.
PdfSetTextColor(doc, page, 1.0, 0.0, 0.0)ist reines Rot.
Die 14 Standardschriften
| 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.
4. Grafik
Pfade
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 |
Zustand und Transformationen
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.
5. Bilder
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);
PdfAddImagebettet die Daten einmal ein und liefert einen Index. Dasselbe Bild mehrfach auf verschiedenen Seiten zeichnen: einmal hinzufügen, mehrfachPdfDrawImage.- Die Zielgröße in
PdfDrawImageist in Punkt, nicht in Pixeln. Ein 2×2-Bild auf 100×100 Punkt wird entsprechend hochskaliert. - Der Weg von einer PNG- oder JPEG-Datei zu Rohpixeln führt über
std.image→ Bildverarbeitungs-Guide. - Bilder dominieren die Dateigröße.
PdfSetCompression(doc, 6)vor dem Speichern schaltet die Zlib-Kompression der Streams ein.
6. Navigation: Links, Lesezeichen, Notizen
// 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.
7. Formulare
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.
8. Verschlüsselung und Berechtigungen
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.
9. Vorhandene PDFs lesen
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);
PdfRdrExtractTextliefert 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.- Metadaten kommen über
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.
10. Ein vollständiges Dokument
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.
11. Fallstricke
- Y wächst nach oben. Wer von HTML oder Canvas kommt, setzt seine erste Zeile sonst unter den Seitenrand.
- Koordinaten sind
f64— außer bei Formularfeldern, diePdfPackCoordsmit ×100-Ganzzahlen erwarten. - Ohne
PdfStroke/PdfFillbleibt der Pfad unsichtbar. PdfSavegibt 0 bei Erfolg zurück — wie in der übrigen Standardbibliothek. Ein Wert ≠ 0 ist der Fehlerfall.PdfFreenach 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.- Transformationen ohne
PdfSaveStateziehen sich durch den Rest der Seite. - Standardschriften decken nur Latin-1 ab. Für
€, kyrillische oder asiatische Zeichen eine TrueType-Schrift überstd.pdf.ttfonteinbetten. PdfPackCoordssteht instd.pdf.shading— der Import fehlt sonst.- Ergebnisse ausgeben: Ein
pchar-Feld gibtPrintLnals 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.
12. Weiterführend
- std.pdf — vollständige Unit-Referenz (24 Units)
- Bildverarbeitung (std.image) — PNG/JPEG zu Rohpixeln
- SVG mit Lyx — Vektorgrafik für das Web
- KassenSichV / TSE — DSFinV-K-Export
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.
