Jede sichtbare Zeichenkette in einem HotPDF-Dokument entsteht durch einen einzigen Aufruf: TextOut(X, Y, angle, Text). Das Hello-World-Beispiel nutzt ihn in seiner einfachsten Form, die Schrift wird einmal gesetzt und vier Argumente bleiben auf sinnvollen Standardwerten. Über diese erste Seite hinaus tragen dieselben vier Argumente die gesamte Last des Layouts. Das dritte Argument dreht den Textlauf. Die kurz zuvor gesetzte Schrift bestimmt Größe und Stil. Und das X-Y-Paar, gemessen vom Seitenrand in Punkten, ist das Einzige, was einen sauberen Bericht von Text trennt, der überlappt, abgeschnitten wird oder auf einem fremden Drucker eine Zeile zu tief landet. Hier zeigt TextOut, was er kann, und hier reichen die Standardwerte nicht mehr aus.
Die Signatur sollte man sich gut einprägen, bevor man weitermacht: X und Y sind Single in Punkten, angle ist ein Extended in Grad, und Text ist ein WideString, sodass Unicode ohne zusätzlichen Aufruf direkt übergeben werden kann. Eine zweite Überladung nimmt einen PWORD plus eine Länge für den Fall, dass man bereits Glyph-Codes vorliegen hat; für gewöhnliche Zeichenketten greift man aber zur WideString-Variante.
Größe und Stil kommen von SetFont, nicht von TextOut
TextOut hat keinen Größenparameter. Größe, Stärke und Neigung liegen alle im SetFont-Aufruf, der dem Textlauf vorangeht, und bleiben in Kraft, bis das nächste SetFont sie ablöst. Das ist die eine Tatsache, die den meisten ersten Verwirrtheitsmomenten zugrunde liegt: Eine Zeile erscheint fett, weil drei Aufrufe früher [fsBold] gesetzt und nichts es wieder zurückgesetzt hat.
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report'); // 24pt regular
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue'); // 12pt bold
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands'); // 11pt italic
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, ' +18.4% YoY'); // styles combine
Das zweite Argument ist eine TFontStyles-Menge, also ist [fsBold, fsItalic] fett-kursiv und [] ist normal. Die Größe wird in Punkten angegeben, derselben Einheit wie die Koordinaten, was den vertikalen Abstand leicht zu berechnen macht: Eine 12-Punkt-Zeile braucht etwa 14 bis 16 Punkte vertikalen Abstand zum Atmen, daher ist ein Y-Dekrement von 14 pro Zeile ein vernünftiger Ausgangspunkt für den Zeilenabstand. Es gibt keinen automatischen Zeilenvorschub. Jede Grundlinie muss selbst berechnet werden, was für einen Fließtext mühsam, für ein Formular, bei dem jedes Feld an einer festen Koordinate sitzt, aber exakt ist.
Zwei praktische Hinweise zum Schriftnamen: Er wird gegen die auf dem Build-Rechner installierten Schriften aufgelöst, und was das Betriebssystem zurückgibt, wird eingebettet. Ein Name, der auf dem Entwicklungs-Desktop aufgelöst wird, muss nicht derselbe Schriftschnitt sein wie auf einem Build-Server. Außerdem muss die Schrift die Skripte der Zeichenkette abdecken. Ein Lauf mit kyrillischen oder CJK-Zeichen unter einer rein lateinischen Schrift erzeugt Fehlglyph-Kästchen ohne jede Fehlermeldung, weshalb das Hello-World-Beispiel beim Mischen von Sprachen eine breite Unicode-Schrift wählt.
Das Winkelargument dreht um den Ankerpunkt
Das dritte Argument ist dasjenige, das die meisten Programme für immer auf null belassen. Übergibt man einen Wert ungleich null, dreht sich der Textlauf gegen den Uhrzeigersinn um seinen eigenen Ankerpunkt (X, Y), also die untere linke Ecke des Textes, um die angegebene Gradzahl. Der Ankerpunkt selbst bewegt sich nicht, sodass dieselbe Koordinate, die ein horizontales Label platziert, auch seine gedrehte Version platziert; nur die Richtung, in die die Glyphen marschieren, ändert sich.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
// Eine vertikale Achsenbeschriftung am linken Rand: 90 Grad liest von unten nach oben.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');
// Ein diagonales DRAFT-Wasserzeichen über dem Seiteninhalt.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// Spaltenköpfe um 60 Grad gekippt, damit lange Beschriftungen in schmale Tabellen passen.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
Neunzig Grad ist der häufigste Fall: eine Beschriftung, die an der Seite eines Diagramms oder als Buchrückentitel verläuft. Fünfundvierzig Grad eignet sich für geneigte Spaltenköpfe, den Trick, der eine breite Beschriftung über eine schmale Spalte setzt, ohne in die Nachbarspalte zu ragen. Rotation ändert nichts an der Interpretation des Ankerpunkts, was viele überrascht: Ein 90-Grad-Lauf beginnt immer noch bei (X, Y) und wächst von dort nach oben, sodass man zum Zentrieren einer gedrehten Beschriftung den Ankerpunkt anpasst, nicht den Winkel. Wenn mehrere gedrehte Läufe eine gemeinsame Grundlinie teilen, gibt man ihnen dasselbe Y und variiert X, genau wie man bei gestapelten horizontalen Zeilen Y variieren würde.
Koordinaten ohne Raten platzieren
Koordinaten sind der Teil, der im Review besteht oder stillschweigend scheitert. HotPDF misst von der unteren linken Ecke der Seite, Y wächst nach oben, in Punkten bei 72 pro Zoll. Eine US-Letter-Seite ist 612 mal 792 Punkte; A4 ist 595 mal 842. Ein Rand von einem Zoll oben auf Letter platziert die erste Grundlinie also bei etwa Y = 792 minus 72 minus der Schriftgröße, nicht bei einer kleinen Zahl nahe der Oberseite. Wer von Bildschirmkoordinaten kommt, bei denen Y von null nach unten wächst, schreibt die erste Zeile unterhalb des Seitenbodens und fragt sich zehn Minuten lang, wo sie geblieben ist.
Layout als Arithmetik gegen benannte Ankerpunkte zu behandeln statt als eine Spalte magischer Zahlen ist der richtige Weg. Ein linker Rand, eine laufende Grundlinie, die man pro Zeile dekrementiert, und ein fester Zeilenabstand verwandeln einen Block aus Beschriftungen in eine kurze Schleife statt in eine Wand aus Literalen:
const
LeftMargin = 72; // 1 Zoll vom Rand
TopBaseline = 720; // erste Zeile, ~1 Zoll nach unten auf Letter
Leading = 16; // vertikaler Schritt zwischen Zeilen
var
Y: Single;
Line: string;
begin
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := TopBaseline;
for Line in ReportLines do
begin
Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
Y := Y - Leading;
if Y < 72 then // unterer Rand erreicht
begin
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 11); // Schrift setzt sich bei neuer Seite zurück
Y := TopBaseline;
end;
end;
end;
Der Seitenumbruch-Schutz ist die Zeile, die alle zuerst vergessen und die in der Praxis am härtesten trifft. Unter TextOut gibt es kein Flusslayout. Unterschreitet man den unteren Rand, zeichnet der Text weiter in den Bundsteg, aus der Seite heraus, ins Nichts, ohne jede Warnung. Also überwacht man Y selbst, ruft bei Unterschreiten der Untergrenze AddPage auf und setzt die Grundlinie zurück. Das SetFont nach AddPage ist kein optionaler Füller: Die aktuelle Schrift übersteht einen Seitenumbruch nicht, und der erste Lauf auf der neuen Seite erscheint in der Standardschrift des Betrachters, wenn man es weglässt.
Zeichen- und Wortabstand für Passform und Ausrichtung
Manchmal ist eine Zeichenkette korrekt, aber hat die falsche Breite: Ein Kopf, der eine feste Linie überspannen muss, ein Code, der mit luftigerem Ziffernabstand lesbar sein soll, eine Spalte, deren Werte zum Ausrichten leicht verschoben werden müssen. PDF kennt zwei Textzustandsoperatoren dafür: Zeichenabstand (Tc, zusätzlicher Abstand nach jeder Glyphe) und Wortabstand (Tw, zusätzlicher Abstand an jedem Leerzeichen). Beide werden in unskallierten Textraumeinheiten angegeben, also effektiv in Punkten bei der aktuellen Schriftgröße. Sie sind ein Zustand, keine Argumente von TextOut; man setzt sie, zeichnet und setzt sie zurück.
// Eine kurze Überschrift mit Laufweite so strecken, dass sie eine Linie überspannt.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0); // vor normalem Fließtext zurücksetzen
// Die Abstände zwischen Wörtern in einer einzigen breiten Zeile vergrößern.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
Wortabstand wirkt nur auf das Leerzeichen (Code 32), was eine wichtige Konsequenz hat: In einem CJK-Lauf ohne ASCII-Leerzeichen bewirkt er nichts, und er verhält sich seltsam bei Text, der als Glyph-Indizes statt als Bytes kodiert ist. Für tabellarische lateinische Ausgaben ist er die günstige Methode, Abstände zu vergrößern, ohne die Zeichenkette neu zu tippen. Zeichenabstand ist das bessere Werkzeug für eine Überschrift, die eine Zielbreite erreichen muss, da er die Anpassung gleichmäßig über alle Glyphen verteilt statt sie an den Leerzeichen zu bündeln.
Das Zurücksetzen ist die ganze Disziplin. Abstände sind wie die Schrift Teil des Zeichenzustands der Seite, und der Zustand bleibt, bis man ihn ändert. Laufweite für eine Überschrift setzen und vergessen, sie auf null zu stellen, und jeder Absatz darunter erbt die Dehnung, was sich als subtile, schwer einzuordnende Falschheit liest, die ein flüchtiges Lektorat übersteht und ein sorgfältiges nicht. Die verlässliche Gewohnheit ist: Abstandswert setzen, den Lauf zeichnen, der ihn braucht, und im nächsten Schritt auf null zurücksetzen, sodass kein späterer Code wissen muss, was ein früherer Abschnitt getan hat.
Die Ausgabe dort prüfen, wo sie tatsächlich bricht
Textlayout schlägt auf dem zweiten Rechner fehl, nicht auf dem ersten, daher finden die relevanten Prüfungen abseits des eigenen Schreibtisches statt. Die erzeugte Datei auf einem System ohne installiertes Entwickler-Schriftpaket öffnen und bestätigen, dass die eingebetteten Schriften korrekt darstellen, einschließlich akzentuierter Lateinbuchstaben, aller nicht-lateinischen Skripte und Satzzeichen, in einem Durchgang statt durch punktuelles Prüfen der einfachen Zeichen. Einige Zeilen auswählen und kopieren, um sicherzustellen, dass der Text echter Text und keine Umrisse ist, was in dem Moment wichtig wird, sobald Suchen oder Extraktion im Spiel ist. Das Layout mit repräsentativen Daten füttern, dem längsten deutschen Label und der breitesten Zahl, nicht mit einem ordentlichen Platzhalter, denn der Lauf, der ein Feld überschreitet, ist immer der, den man nicht selbst getippt hat. Und wenn die Seite auf einem vorgedruckten Formular landen muss, ein Muster ausdrucken oder rastern und es gegen das Original legen; ein Grundlinienversatz von einem Viertelmillimeter ist auf dem Bildschirm unsichtbar und auf Papier offensichtlich.
Wer noch keine einzige Seite geschrieben hat, beginnt mit dem HotPDF Hello-World-Beispiel, das das Dokument, die Schrift und das unten-links-Koordinatensystem einrichtet, auf dem alles oben Genannte aufbaut. Die hier gezeigten Aufrufe TextOut, SetFont und Abstände sind Teil der HotPDF Component für Delphi und C++Builder.