====== 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.