Inhaltsverzeichnis

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);

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);


// 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);


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


12. Weiterführend

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.