Technischer Artikel

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

Jeder sichtbare String in einem HotPDF-Dokument kommt über einen einzigen Aufruf zustande: TextOut(X, Y, angle, Text). Das Hello-World-Beispiel verwendet ihn in seiner einfachsten Form: Die Schriftart wird einmal festgelegt und die vier Argumente bleiben bei sinnvollen Standardwerten. Nach dieser ersten Seite tragen dieselben vier Argumente die gesamte Last des Layouts. Das dritte Argument rotiert den Lauf. Die unmittelbar davor festgelegte Schriftart bestimmt Größe und Stil. Und das X, Y-Paar, gemessen in Punkten von der Seitenecke aus, ist das Einzige, was zwischen einem sauberen Bericht und Text steht, der sich überlappt, abgeschnitten wird oder auf dem Drucker eines anderen eine Zeile tiefer rutscht. Hier verdient TextOut seinen Unterhalt, und hier reichen die Standardwerte nicht mehr aus

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 einen separaten Aufruf durchgereicht wird. Eine zweite Überladung nimmt ein PWORD plus eine Länge auf für den Fall, dass Sie bereits Glyphen-Codes haben, aber für gewöhnliche Strings ist die WideString-Form die richtige Wahl

Größe und Stil stammen aus SetFont, nicht aus TextOut

TextOut hat keinen Parameter für die Größe. Die Größe, die Strichstärke, die Neigung – all das steckt in dem SetFont-Aufruf, der dem Lauf vorangeht, und es bleibt so lange in Kraft, bis das nächste SetFont es ersetzt. Das ist die einzige Tatsache, die den Großteil der Verwirrung am ersten Tag erklärt: Eine Zeile wird fett gedruckt, weil drei Aufrufe zuvor etwas [fsBold] gesetzt hat und nichts es wieder 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 ein TFontStyles-Set. [fsBold, fsItalic] ist also fett kursiv und [] ist normal. Die Größe wird in Punkten angegeben, derselben Einheit wie die Koordinaten, was die Argumentation über vertikale Abstände erleichtert: Eine 12-Punkt-Zeile benötigt grob 14 bis 16 Punkte vertikalen Schritt, um "atmen" zu können, daher ist ein Absenken von Y um 14 pro Zeile ein vernünftiger Startwert für den Zeilenabstand (Leading). Es gibt keinen automatischen Zeilenvorschub. Sie berechnen jede Grundlinie selbst, was für einen Absatz mühsam, für ein Formular jedoch exakt ist, da dort jedes Feld an einer festen Koordinate sitzt

Zwei praktische Anmerkungen zum Namen der Schriftart. Sie wird anhand der auf dem Build-Rechner installierten Schriftarten aufgelöst, und was auch immer das Betriebssystem zurückgibt, wird eingebettet. Ein Name, der auf Ihrem Desktop aufgelöst wird, und ein Name, der auf einem Build-Server aufgelöst wird, bezeichnen also nicht zwangsläufig dieselbe Schriftart. Zudem muss die Schriftart die in der Zeichenfolge enthaltenen Skripte abdecken. Ein kyrillischer oder CJK-Text unter einer nur-lateinischen Schriftart wird ohne Fehlermeldung als Kästchen für fehlende Glyphen gerendert. Das ist der Grund, warum die Hello-World-Seite auf eine breite Unicode-Schriftart zurückgreift, wenn sie Sprachen mischt

HotPDF TextOut-Seite, die Arial, Times New Roman und Courier New mit normalen, fetten und kursiven Stilen über mehrere Zeichensätze hinweg gerendert zeigt

Das angle-Argument rotiert um den Ankerpunkt

Das dritte Argument ist dasjenige, das in den meisten Codes für immer bei null belassen wird. Übergeben Sie einen Wert ungleich null, und der Textlauf dreht sich gegen den Uhrzeigersinn um seinen eigenen (X, Y)-Anker, also die linke untere Ecke des Textes, um die angegebene Gradzahl. Der Anker selbst bewegt sich nicht. Dieselbe Koordinate, die eine horizontale Beschriftung platziert hat, platziert also auch 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 Standardfall, eine Beschriftung, die am Rand eines Diagramms nach oben verlä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. Rotation ändert nichts an der Interpretation des Ankers, was 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 mehrere rotierte Läufe sich eine Grundlinie teilen, geben Sie ihnen dasselbe Y und erhöhen Sie X schrittweise, genau wie Sie Y für gestapelte horizontale Linien schrittweise erhöhen würden

Koordinaten platzieren, ohne zu raten

Koordinaten sind der Teil, der eine Überprüfung übersteht oder stillschweigend daran scheitert. HotPDF misst von der unteren linken Ecke der Seite, wobei Y in Punkten bei 72 pro Zoll nach oben wächst. Eine US-Letter-Seite ist 612 mal 792 Punkte groß; A4 ist 595 mal 842. Ein oberer Rand von einem Zoll bei Letter setzt Ihre erste Grundlinie also in die Nähe von Y = 792 minus 72 minus der Schriftgröße, nicht auf eine kleine Zahl nahe der Oberkante. Wer von Bildschirmkoordinaten kommt, bei denen Y von null an nach unten wächst, schreibt die erste Zeile über den unteren Rand hinaus und wundert sich zehn Minuten lang, wo sie geblieben ist

Behandeln Sie das Layout als Arithmetik anhand benannter Anker und nicht als Spalte voller magischer Zahlen. Ein linker Rand, eine fortlaufende Grundlinie, die Sie pro Zeile dekrementieren, und ein fester Zeilenabstand (Leading) verwandeln einen Block von Beschriftungen in eine kurze Schleife anstelle einer Wand aus 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;

Die Seitenumbruchs-Sicherung ist die Codezeile, die jeder als Erstes vergisst und die in der Praxis am härtesten zuschlägt. Es gibt unter TextOut kein Flow-Layout. Wenn Sie über den unteren Rand hinaus dekrementieren, zeichnet der Text weiter in den Steg, von der Seite, ins Nichts, ohne Vorwarnung. Also behalten Sie Y selbst im Auge, rufen Sie AddPage auf, wenn es den Boden überschreitet, und setzen Sie die Grundlinie zurück. Das SetFont nach AddPage ist kein optionales Füllmaterial: Die aktuelle Schriftart übersteht einen Seitenumbruch nicht, und der erste Lauf auf der neuen Seite wird in der Standardschriftart des Viewers ausgegeben, wenn Sie ihn überspringen

Zeichen- und Wortabstände für Einpassung und Ausrichtung

Manchmal ist ein String korrekt, hat aber die falsche Breite: eine Überschrift, die eine feste Linie überspannen muss, ein Code, der mit luftigeren Ziffern lesbar sein sollte, eine Spalte, deren Werte zur Ausrichtung leicht verschoben werden müssen. PDF bietet dafür zwei Textstatus-Operatoren: Zeichenabstand (Tc, zusätzlicher Platz nach jeder Glyphe) und Wortabstand (Tw, zusätzlicher Platz bei jedem Leerzeichen). Beide werden in unskalierten Textbereich-Einheiten ausgedrückt, effektiv in Punkten bei der aktuellen Schriftgröße. Es handelt sich um Status, nicht um Argumente für TextOut. Sie setzen sie also, zeichnen und setzen sie wieder 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 Konsequenz hat, die man kennen sollte: Er bewirkt nichts innerhalb eines CJK-Laufs, der keine ASCII-Leerzeichen enthält, und er interagiert auf seltsame Weise mit Text, der als Glyphen-Indizes statt als Bytes kodiert ist. Für lateinische tabellarische Ausgaben ist dies der billige Weg, Lücken zu verbreitern, ohne den String neu einzutippen. Der Zeichenabstand ist das bessere Werkzeug für eine Überschrift, die eine Zielbreite erreichen muss, da er die Anpassung gleichmäßig über jede Glyphe verteilt, anstatt sie an den Leerzeichen zu bündeln

Das Zurücksetzen ist die eigentliche Disziplin. Der Abstand ist, genau wie die Schriftart, Teil des Zeichenstatus der Seite, und der Status bleibt bestehen, bis Sie ihn ändern. Setzen Sie den Zeichenabstand für eine Überschrift und vergessen Sie, ihn auf null zu setzen, und jeder nachfolgende Absatz erbt die Streckung. Das äußert sich in einer subtilen, schwer einzuordnenden Fehlerhaftigkeit, die ein beiläufiges Korrekturlesen übersteht und bei einer sorgfältigen Prüfung durchfällt. Die zuverlässige Angewohnheit besteht darin, einen Abstandswert festzulegen, den Textlauf zu zeichnen, der ihn benötigt, und ihn in der nächsten Zeile auf null zurückzusetzen, sodass kein späterer Code wissen muss, was ein früherer Abschnitt getan hat

HotPDF TextOut-Seite, die horizontale Textskalierung, Zeichenabstand, Wortabstand und die Rendermodi Füllen gegenüber Kontur vergleicht

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

Textlayouts scheitern auf dem zweiten Rechner, nicht auf dem ersten. Die Überprüfungen, die wirklich zählen, finden also abseits Ihres Schreibtisches statt. Öffnen Sie die generierte Datei auf einem System, auf dem Ihre Entwickler-Schriftart nicht installiert ist, und vergewissern Sie sich, dass die eingebetteten Schriftarten immer noch gerendert werden, einschließlich akzentuiertem Latein, aller nicht-lateinischen Skripte und Satzzeichen, in einem Durchgang, anstatt nur die einfachen Zeichen stichprobenartig zu prüfen. Markieren und kopieren Sie ein paar Zeilen, um zu bestätigen, dass der Text echter Text ist und keine Konturen, was wichtig wird, sobald Suchen oder Extrahieren im Spiel ist. Füttern Sie das Layout mit repräsentativen Daten, der längsten deutschen Bezeichnung und der breitesten Zahl, nicht mit einem aufgeräumten Platzhalter, denn der Lauf, der ein Feld überlaufen lässt, ist immer derjenige, den Sie nicht von Hand eingetippt haben. Und wenn die Seite auf einem vorgedruckten Formular landen muss, drucken oder rastern Sie ein Muster und legen Sie es auf das Original; ein Grundliniendrift von einem Viertelmillimeter ist auf dem Bildschirm unsichtbar und auf dem 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 von unten links einrichtet, von dem alles Obige abhängt. Die hier gezeigten Aufrufe für TextOut, SetFont und die Abstände sind Teil der HotPDF-Komponente für Delphi und C++Builder