Fachartikel

HotPDF TextOut in Delphi: Größe, Stil, Drehung und Abstand

Jede sichtbare Zeichenfolge in einem HotPDF-Dokument kommt durch einen einzigen Aufruf an: TextOut(X, Y, angle, Text). Das Hello-World-Beispiel verwendet es in seiner einfachsten Form, die Schriftart einmal festgelegt und vier Argumente auf vernünftigen Standardwerten belassen. Nach dieser ersten Seite tragen dieselben vier Argumente das gesamte Gewicht des Layouts. Das dritte Argument dreht den Textlauf. Der direkt davor festgelegte Font-Set bestimmt Größe und Stil. Und das X, Y-Paar, gemessen von der Seitenecke in Punkten, ist das Einzige, was zwischen einem sauberen Bericht und Text steht, der sich überlappt, abschneidet oder auf dem Drucker eines anderen eine Zeile nach unten rutscht. Hier verdient TextOut seinen Unterhalt und wo die Standardwerte nicht mehr ausreichen.

Die Signatur sollte man sich vor allem anderen einprägen: X und Y sind Single in Punkten, angle ist ein Extended in Grad, und Text ist ein WideString, sodass Unicode ohne separaten Aufruf durchgereicht wird. Eine zweite Überladung nimmt ein PWORD plus einer Länge für den Fall, dass Sie bereits Glyph-Codes haben, aber für gewöhnliche Zeichenfolgen ist die WideString-Form diejenige, nach der Sie greifen.

Größe und Stil kommen von SetFont, nicht von TextOut

TextOut hat keinen Größenparameter. Die Größe, das Gewicht, die Neigung, all das lebt im SetFont-Aufruf, der dem Lauf vorausgeht, und es bleibt in Kraft, bis das nächste SetFont es ersetzt. Das ist die einzige Tatsache, die die meisten anfänglichen Verwirrungen erklärt: Eine Zeile wird fett ausgegeben, weil drei Aufrufe zuvor etwas [fsBold] gesetzt hat und nichts es gelöscht 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 ist in Punkten, dieselbe Einheit wie die Koordinaten, was den vertikalen Abstand leicht zu durchdenken macht: Eine 12-Punkt-Zeile braucht ungefähr 14 bis 16 Punkte vertikalen Schritt, um zu atmen, also ist das Verringern von Y um 14 pro Zeile ein vernünftiger Start-Zeilenabstand. Es gibt keinen automatischen Zeilenvorschub. Sie berechnen jede Grundlinie selbst, was für einen Absatz mühsam ist, aber exakt für ein Formular, bei dem jedes Feld an einer festen Koordinate sitzt.

Zwei praktische Anmerkungen zum Schriftnamen. Er wird gegen die auf der Build-Maschine installierten Schriftarten aufgelöst, und was auch immer das Betriebssystem zurückgibt, wird eingebettet. Ein Name, der sich auf Ihrem Desktop auflöst, und ein Name, der sich auf einem Build-Server auflöst, garantieren also nicht, dasselbe Schriftbild zu sein. Und die Schriftart muss die Skripte in der Zeichenfolge abdecken. Ein Lauf von kyrillischem oder CJK-Text unter einem rein lateinischen Schriftbild wird ohne Fehler als Kästchen für fehlende Glyphen gerendert. Das ist der Grund, warum die Hello World-Seite nach einer breiten Unicode-Schriftart greift, wenn sie Sprachen mischt.

HotPDF TextOut-Seite mit Arial, Times New Roman und Courier New, gerendert mit den Stilen normal, fett und kursiv über mehrere Zeichensätze

Das Winkelargument dreht sich um den Anker

Das dritte Argument ist dasjenige, das die meisten Codes für immer auf Null belassen. Übergeben Sie einen Wert ungleich Null, und der Lauf dreht sich gegen den Uhrzeigersinn um seinen eigenen (X, Y)-Anker, die untere linke Ecke des Textes, um diese Anzahl von Grad. Der Anker selbst bewegt sich nicht. Dieselbe Koordinate, die eine horizontale Beschriftung platziert hat, platziert also ihren gedrehten Zwilling; nur die Richtung, in die die Glyphen marschieren, ändert sich.

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
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 am Rand eines Diagramms hochläuft, oder ein Rückentitel. Fünfundvierzig Grad bewältigen geneigte Spaltenüberschriften, der Trick, der es einer breiten Beschriftung ermöglicht, über einer schmalen Spalte zu sitzen, ohne in ihre Nachbarn überzulaufen. Die Drehung ändert nichts daran, wie der Anker interpretiert wird, was die Leute stolpern lässt: Ein 90-Grad-Lauf beginnt immer noch bei (X, Y) und wächst von dort nach oben. Um eine gedrehte Beschriftung zu zentrieren, passen Sie also den Anker an, nicht den Winkel. Wenn sich mehrere gedrehte Läufe eine Grundlinie teilen, geben Sie ihnen dasselbe Y und schrittweises X, genau wie Sie Y für gestapelte horizontale Linien schrittweise anpassen würden.

Koordinaten platzieren ohne Raten

Koordinaten sind der Teil, der eine Überprüfung überlebt oder stillschweigend daran 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 groß; A4 ist 595 mal 842. Ein oberer Rand von einem Zoll platziert Ihre erste Grundlinie daher nahe Y = 792 minus 72 minus der Schriftgröße, nicht bei irgendeiner kleinen Zahl nahe am oberen Rand. Jeder, der von Bildschirmkoordinaten kommt, wo Y von Null abwärts wächst, schreibt die erste Zeile über den unteren Rand hinaus und fragt sich zehn Minuten lang, wo sie geblieben ist.

Behandeln Sie das Layout als Arithmetik gegen benannte Anker anstatt als eine Spalte magischer Zahlen. Ein linker Rand, eine fortlaufende Grundlinie, die Sie pro Zeile verringern, und ein fester Zeilenabstand verwandeln einen Block von Beschriftungen in eine kurze Schleife anstelle einer Wand von Literalen:

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
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            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

Der Seitenumbruchswächter ist die Zeile, die jeder zuerst vergisst und die in der Praxis am härtesten zuschlägt. Es gibt kein Flusslayout unter TextOut. Dekrementieren Sie über den unteren Rand hinaus, und der Text zeichnet einfach weiter in den Bundsteg, von der Seite herunter, ins Nichts, ohne Warnung. Sie müssen also Y selbst überwachen, AddPage aufrufen, wenn es den Boden überschreitet, und die Grundlinie zurücksetzen. Das SetFont nach AddPage ist kein optionales Füllmaterial: Die aktuelle Schriftart überlebt keinen Seitenumbruch, und der erste Lauf auf der neuen Seite wird in der Standardschriftart des Viewers ausgegeben, wenn Sie ihn überspringen.

Zeichen- und Wortabstand für Passform und Ausrichtung

Manchmal ist eine Zeichenfolge korrekt, aber die falsche Breite: eine Überschrift, die eine feste Linie überspannen muss, ein Code, der mit luftigeren Ziffern lesen sollte, eine Spalte, deren Werte zur Ausrichtung verschoben werden müssen. PDF bietet dafür zwei Textstatus-Operatoren: Zeichenabstand (Tc, zusätzlicher Raum, der nach jeder Glyphe hinzugefügt wird) und Wortabstand (Tw, zusätzlicher Raum, der bei jedem Leerzeichen hinzugefügt wird). Beide werden in unskalierten Textraumeinheiten ausgedrückt, effektiv in Punkten in der aktuellen Schriftgröße. Sie sind Zustand, keine Argumente für TextOut, also setzen Sie sie, zeichnen Sie und setzen Sie sie zurück.

// Letter-space a short heading so it stretches across a rule.
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);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

Der Wortabstand wirkt sich nur auf das Leerzeichen (Code 32) aus, was eine wissenswerte Konsequenz hat: Er bewirkt nichts innerhalb eines CJK-Laufs, der keine ASCII-Leerzeichen hat, und er interagiert seltsam mit Text, der als Glyph-Indizes anstelle von Bytes codiert ist. Für tabellarische Ausgaben in lateinischer Schrift ist es der billige Weg, Lücken zu verbreitern, ohne die Zeichenfolge neu einzutippen. Der Zeichenabstand ist das bessere Werkzeug für eine Überschrift, die eine Zielbreite erreichen muss, da er die Anpassung gleichmäßig auf jede Glyphe verteilt, anstatt sie an den Leerzeichen zu bündeln.

Das Zurücksetzen ist die ganze Disziplin. Der Abstand ist, wie die Schriftart, Teil des Zeichenzustands der Seite, und der Zustand bleibt bestehen, bis Sie ihn ändern. Setzen Sie den Zeichenabstand für eine Überschrift und vergessen Sie, ihn auf Null zu setzen, dann erbt jeder Absatz darunter die Streckung. Dies liest sich als eine subtile, schwer einzuordnende Falschheit, die ein flüchtiges Korrekturlesen überlebt und bei einem genauen durchfällt. Die verlässliche Gewohnheit ist, einen Abstandswert festzulegen, den Lauf zu zeichnen, der ihn benötigt, und ihn in der nächsten Zeile auf Null zurückzusetzen, damit kein späterer Code wissen muss, was ein früherer Abschnitt getan hat.

HotPDF TextOut-Seite zum Vergleich der horizontalen Textskalierung, des Zeichenabstands, des Wortabstands sowie der Fill- und Stroke-Rendering-Modi

Überprüfen der Ausgabe dort, wo sie tatsächlich bricht

Das Textlayout schlägt auf der zweiten Maschine fehl, nicht auf der ersten. Die Kontrollen, auf die es ankommt, finden also nicht an Ihrem Schreibtisch statt. Öffnen Sie die generierte Datei auf einem System ohne Ihre Entwickler-Schriftart und bestätigen Sie, dass die eingebetteten Schriften immer noch gerendert werden, einschließlich akzentuiertem Latein, aller nicht-lateinischen Skripte und Satzzeichen, in einem Durchgang, anstatt die einfachen Zeichen stichprobenartig zu überprüfen. Wählen Sie einige Zeilen aus und kopieren Sie sie, um zu bestätigen, dass der Text echter Text und keine Umrisse ist, was in dem Moment wichtig ist, in dem Suche oder Extraktion in Betracht kommen. Füttern Sie das Layout mit repräsentativen Daten, der längsten deutschen Beschriftung und der breitesten Zahl, keinem ordentlichen Platzhalter, denn der Lauf, der ein Feld überfüllt, ist immer der, den Sie nicht von Hand getippt haben. Und wenn die Seite auf einem vorgedruckten Formular landen muss, drucken oder rastern Sie ein Muster und legen Sie es gegen das Original; ein Viertelmillimeter Grundlinienverschiebung ist auf dem Bildschirm unsichtbar und auf Papier offensichtlich.

Wenn Sie noch keine einzige Seite geschrieben haben, beginnen Sie mit dem HotPDF Hello World-Beispiel, das das Dokument, die Schriftart und das Koordinatensystem unten links einrichtet, von dem alles oben abhängt. Die hier gezeigten Aufrufe für TextOut, SetFont und Abstände sind Teil der HotPDF-Komponente für Delphi und C++Builder.