====== 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.
→ [[lyx_-_programmiersprache:units:pdf|std.pdf — Unit-Referenz]] · [[lyx_-_programmiersprache:guides:image|Bildverarbeitung]] · [[lyx_-_programmiersprache:guides:svg|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);
* ''PdfTextAt'' setzt **eine** Zeile an eine feste Stelle — kein Umbruch, kein Rand.
* ''PdfTextBlock'' bricht innerhalb der angegebenen Breite um; der Zeilenabstand kommt aus ''PdfSetLineSpacing''.
* 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);
* ''PdfAddImage'' bettet die Daten **einmal** ein und liefert einen Index. Dasselbe Bild mehrfach auf verschiedenen Seiten zeichnen: einmal hinzufügen, mehrfach ''PdfDrawImage''.
* Die Zielgröße in ''PdfDrawImage'' ist 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'' → [[lyx_-_programmiersprache:guides: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 als ''f64''.** ''PdfPackCoords(12000, 69000)'' meint 120.00 / 690.00. Die Funktion packt zwei 32-Bit-Werte in einen ''int64'' und steht in ''std.pdf.shading'' — nicht in ''std.pdf.forms'', wo man sie vermuten würde. Fehlt der Import, meldet der Compiler ''undefined 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);
* ''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.
* 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, die ''PdfPackCoords'' mit ×100-Ganzzahlen erwarten.
* **Ohne ''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.
* **Transformationen ohne ''PdfSaveState''** ziehen sich durch den Rest der Seite.
* **Standardschriften decken nur Latin-1 ab.** Für ''€'', kyrillische oder asiatische Zeichen eine TrueType-Schrift über ''std.pdf.ttfont'' einbetten.
* **''PdfPackCoords'' steht in ''std.pdf.shading''** — der Import fehlt sonst.
* **Ergebnisse ausgeben:** Ein ''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.
----
===== 12. Weiterführend =====
* [[lyx_-_programmiersprache:units:pdf|std.pdf — vollständige Unit-Referenz]] (24 Units)
* [[lyx_-_programmiersprache:guides:image|Bildverarbeitung (std.image)]] — PNG/JPEG zu Rohpixeln
* [[lyx_-_programmiersprache:guides:svg|SVG mit Lyx]] — Vektorgrafik für das Web
* [[lyx_-_programmiersprache:guides:kassensichv|KassenSichV / TSE]] — DSFinV-K-Export
* [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher: alloc, peek & poke]]
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.