Artykuł techniczny

Funkcja TextOut w HotPDF (Delphi): Rozmiar, styl, obrót i odstępy

Każdy widoczny ciąg tekstu w dokumencie HotPDF trafia na stronę przez jedno wywołanie: TextOut(X, Y, angle, Text). Przykład Hello World używa go w najprostszej postaci, z czcionką ustawioną raz i czterema argumentami pozostawionymi na rozsądnych wartościach domyślnych. Poza tą pierwszą stroną te same cztery argumenty dźwigają cały ciężar układu strony. Trzeci argument obraca ciąg tekstu. Czcionka ustawiona tuż przed nim decyduje o rozmiarze i stylu. A para X, Y, mierzona od rogu strony w punktach, to jedyna rzecz stojąca między czystym raportem a tekstem, który się nakłada, obcina albo przesuwa o linię niżej na czyjejś drukarce. Właśnie tu TextOut pokazuje swoją wartość, i właśnie tu wartości domyślne przestają wystarczać

Sygnaturę warto zapamiętać, zanim przejdzie się do czegokolwiek innego: X i Y to Single w punktach, angle to Extended w stopniach, a Text to WideString, więc Unicode przechodzi bez osobnego wywołania. Druga przeciążona wersja przyjmuje PWORD plus długość, gdy dysponuje się już kodami glifów, ale dla zwykłych ciągów tekstu sięga się po formę WideString

Rozmiar i styl pochodzą z SetFont, nie z TextOut

TextOut nie ma parametru rozmiaru. Rozmiar, grubość, pochylenie, wszystko to mieszka w wywołaniu SetFont poprzedzającym dany ciąg tekstu i pozostaje w mocy aż do kolejnego wywołania SetFont, które je zastąpi. To jeden fakt, który tłumaczy większość zamieszania pierwszego dnia: linia wychodzi pogrubiona, ponieważ trzy wywołania wcześniej coś ustawiło [fsBold], a nikt tego nie wyczyścił

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report');        // 24pt zwykła

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue');                 // 12pt pogrubiona

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands');    // 11pt kursywa

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, '  +18.4% YoY');            // style się łączą

Drugi argument to zbiór TFontStyles, więc [fsBold, fsItalic] oznacza pogrubioną kursywę, a [] oznacza zwykły tekst. Rozmiar podaje się w punktach, w tej samej jednostce co współrzędne, co ułatwia rozumowanie o odstępach pionowych: linia o rozmiarze 12 punktów potrzebuje mniej więcej 14 do 16 punktów kroku pionowego, żeby oddychać, więc obniżenie Y o 14 na linię to rozsądny punkt startowy dla interlinii. Nie ma automatycznego przejścia do nowej linii. Każdą linię bazową oblicza się samodzielnie, co jest żmudne dla akapitu, ale dokładne dla formularza, gdzie każde pole siedzi na stałej współrzędnej

Dwie praktyczne uwagi dotyczące nazwy czcionki. Jest ona rozwiązywana względem czcionek zainstalowanych na maszynie budującej dokument, i to, co odda system operacyjny, zostaje osadzone, więc nazwa, która rozwiązuje się na komputerze programisty, i nazwa, która rozwiązuje się na serwerze budującym, wcale nie muszą wskazywać na ten sam krój. A czcionka musi obejmować skrypty użyte w ciągu tekstu. Fragment tekstu cyrylicą albo CJK pod czcionką wyłącznie łacińską renderuje się jako puste prostokąty brakujących glifów, bez żadnego błędu, i właśnie dlatego strona Hello World sięga po szeroką czcionkę Unicode, gdy miesza języki

Strona z przykładowym tekstem HotPDF TextOut pokazująca czcionki Arial, Times New Roman i Courier New w stylach normalnym, pogrubionym i kursywie przy użyciu różnych zestawów znaków

Argument kąta obraca wokół punktu zakotwiczenia

Trzeci argument to ten, który większość kodu zostawia na zerze na zawsze. Podaj wartość niezerową, a ciąg tekstu obraca się przeciwnie do ruchu wskazówek zegara wokół własnego punktu zakotwiczenia (X, Y), czyli lewego dolnego rogu tekstu, o tyle stopni. Sam punkt zakotwiczenia się nie przesuwa, więc ta sama współrzędna, która umieszczała poziomą etykietę, umieszcza jej obrócony bliźniak; zmienia się tylko kierunek, w którym maszerują glify

Diagram geometrii obrotu TextOut w HotPDF: przebiegi pod 0, 45 i 90 stopni obracające się przeciwnie do wskazówek zegara wokół stałej kotwicy lewego dolnego rogu, z przykładami wywołań Delphi TextOut dla etykiet grzbietów, znaków wodnych i pochylonych nagłówków kolumn
Niezerowy kąt obraca ciąg przeciwnie do ruchu wskazówek zegara wokół nieruchomej kotwicy w lewym dolnym rogu, a wspólne linie bazowe krokują X dokładnie tak, jak poziome wiersze krokują Y
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// Pionowa etykieta osi wzdłuż lewego marginesu: 90 stopni czyta się od dołu do góry.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// Ukośny znak wodny DRAFT w treści strony.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Nagłówki kolumn przechylone o 60 stopni, aby długie etykiety zmieściły się w wąskiej tabeli.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

Dziewięćdziesiąt stopni to najczęstszy przypadek, etykieta biegnąca w górę wzdłuż boku wykresu albo tytuł na grzbiecie. Czterdzieści pięć stopni obsługuje przechylone nagłówki kolumn, sztuczkę pozwalającą szerokiej etykiecie zmieścić się nad wąską kolumną bez wylewania się na sąsiednie. Obrót nie zmienia sposobu interpretacji punktu zakotwiczenia, co bywa mylące: ciąg obrócony o 90 stopni wciąż zaczyna się w (X, Y) i rośnie stamtąd w górę, więc żeby wyśrodkować obróconą etykietę, dostosowuje się punkt zakotwiczenia, a nie kąt. Gdy kilka obróconych ciągów dzieli tę samą linię bazową, nadaje się im to samo Y i krokuje X, dokładnie tak, jak krokuje się Y dla ułożonych jedna nad drugą linii poziomych

Umieszczanie współrzędnych bez zgadywania

Współrzędne to element, który albo przetrwa przegląd, albo po cichu go nie przejdzie. HotPDF mierzy od lewego dolnego rogu strony, z Y rosnącym w górę, w punktach po 72 na cal. Strona US Letter ma 612 na 792 punkty; A4 ma 595 na 842. Górny margines o szerokości jednego cala na formacie Letter umieszcza więc pierwszą linię bazową blisko Y = 792 minus 72 minus rozmiar czcionki, a nie przy jakiejś małej liczbie blisko góry. Każdy, kto przychodzi ze współrzędnych ekranowych, gdzie Y rośnie w dół od zera, pisze pierwszą linię poza dolną krawędzią i spędza dziesięć minut, zastanawiając się, gdzie ona zniknęła

HotPDF: Diagram strony US Letter pokazujący rachunek linii bazowej z LeftMargin 72, TopBaseline 720 i stałym Leading schodzącym ku podłodze dolnego marginesu na Y 72, obok pętli układu z nazwanymi stałymi z ręczną strażą AddPage i resetem SetFont
Nazwane kotwice zamieniają kolumnę magicznych liczb w arytmetykę: dekrementuj bieżącą linię bazową o Leading w każdym wierszu i sam doglądaj podłogi przez AddPage

Traktuj układ strony jak arytmetykę na nazwanych punktach odniesienia, a nie jak kolumnę magicznych liczb. Lewy margines, bieżąca linia bazowa zmniejszana z każdą linią, i stała interlinia zamieniają blok etykiet w krótką pętlę zamiast ściany literałów:

const
  LeftMargin = 72;        // 1 cal od brzegu
  TopBaseline = 720;       // pierwsza linia, ok. 1 cal od góry na formacie Letter
  Leading = 16;            // pionowy krok między liniami
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            // osiągnięto dolny margines
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // czcionka resetuje się na nowej stronie
      Y := TopBaseline;
    end;
  end;
end;

Zabezpieczenie przed przepełnieniem strony to linia, o której wszyscy zapominają najpierw i którą praktyka karze najsurowiej. Pod TextOut nie ma żadnego układu przepływowego. Zmniejsz Y poza dolny margines, a tekst dalej się rysuje, wchodząc w margines, poza stronę, w nicość, bez żadnego ostrzeżenia. Trzeba więc samodzielnie pilnować Y, wywoływać AddPage, gdy przekroczy dolną granicę, i resetować linię bazową. SetFont po AddPage to nie opcjonalny nadmiar: bieżąca czcionka nie przetrwa podziału strony, a pierwszy ciąg tekstu na nowej stronie wychodzi w domyślnej czcionce przeglądarki, jeśli się to wywołanie pominie

Odstępy między znakami i słowami dla dopasowania i wyrównania

Czasem ciąg tekstu jest poprawny, ale ma złą szerokość: nagłówek, który musi rozciągnąć się na całą linijkę, kod, który powinien czytać się z bardziej przewiewnymi cyframi, kolumna, której wartości trzeba delikatnie przesunąć, żeby się wyrównały. PDF niesie dwa operatory stanu tekstu do tego celu, odstęp między znakami (Tc, dodatkowa przestrzeń dodawana po każdym glifie) i odstęp między słowami (Tw, dodatkowa przestrzeń dodawana przy każdym znaku spacji), i oba wyrażane są w nieskalowanych jednostkach przestrzeni tekstu, czyli praktycznie w punktach przy bieżącym rozmiarze czcionki. Są to elementy stanu, nie argumenty TextOut, więc ustawia się je, rysuje, a potem ustawia z powrotem

HotPDF: Rozstrzelenie znaków Tc rozdzielające dodatkowy luz równo po każdym glifie SUMMARY kontra rozstrzelenie słów Tw gromadzące go tylko przy spacjach, wpięte w cykl ustaw, rysuj, reset, który trzyma stan rozstrzelenia z dala od przecieku do późniejszych akapitów
Tc rozprowadza swoją korektę na każdy glif, podczas gdy Tw działa tylko na znak spacji, a resetowanie w następnym wierszu utrzymuje stan rysowania w czystości
// Rozstrzelenie liter krótkiego nagłówka, aby rozciągnął się na całą linijkę.
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 przed zwykłym tekstem treści

// Poszerzenie odstępów między słowami w jednej szerokiej linii.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

Odstęp między słowami działa wyłącznie na znak spacji (kod 32), co ma konsekwencję wartą zapamiętania: nie robi niczego wewnątrz ciągu CJK, który nie ma spacji ASCII, i wchodzi w dziwną interakcję z tekstem kodowanym jako indeksy glifów zamiast bajtów. Dla łacińskiego wyniku tabelarycznego to tani sposób na poszerzenie odstępów bez przepisywania ciągu tekstu. Odstęp między znakami jest lepszym narzędziem dla nagłówka, który musi osiągnąć docelową szerokość, ponieważ rozkłada korektę równomiernie na każdy glif zamiast gromadzić ją przy spacjach

Reset to cała dyscyplina. Odstępy, tak jak czcionka, są częścią stanu rysowania strony, a stan trwa, dopóki się go nie zmieni. Rozstrzel litery jednego nagłówka i zapomnij wyzerować odstęp, a każdy akapit poniżej odziedziczy rozciągnięcie, co czyta się jako subtelna, trudna do zlokalizowania nieprawidłowość, która przetrwa pobieżną korektę i nie przetrwa dokładnej. Niezawodnym nawykiem jest ustawienie wartości odstępu, narysowanie ciągu, który go potrzebuje, i wyzerowanie go w kolejnej linii, tak aby żaden późniejszy kod nie musiał wiedzieć, co zrobiła wcześniejsza sekcja

Strona HotPDF TextOut porównująca poziome skalowanie tekstu, odstępy między znakami, odstępy między słowami oraz tryby renderowania wypełnienia i obrysu

Sprawdzanie wyniku tam, gdzie faktycznie się psuje

Układ tekstu zawodzi na drugiej maszynie, nie na pierwszej, więc kontrole, które mają znaczenie, dzieją się z dala od własnego biurka. Otwórz wygenerowany plik na systemie bez zainstalowanego zestawu czcionek programisty i potwierdź, że osadzone kroje wciąż się renderują, w tym akcentowana łacinka, wszelkie skrypty niełacińskie i interpunkcja, za jednym przejściem, a nie tylko wyrywkowo sprawdzając łatwe znaki. Zaznacz i skopiuj kilka linii, aby potwierdzić, że tekst jest prawdziwym tekstem, a nie konturami, co ma znaczenie w chwili, gdy w grę wchodzi wyszukiwanie lub ekstrakcja. Nakarm układ reprezentatywnymi danymi, najdłuższą niemiecką etykietą i najszerszą liczbą, a nie schludnym placeholderem, ponieważ ciąg tekstu, który przepełnia pole, zawsze jest tym, którego nikt nie wpisał ręcznie. A jeśli strona ma trafić na wydrukowany wcześniej formularz, wydrukuj lub zrasteryzuj jedną próbkę i zestaw ją z oryginałem; przesunięcie linii bazowej o ćwierć milimetra jest niewidoczne na ekranie i oczywiste na papierze

Jeśli nie napisano jeszcze ani jednej strony, warto zacząć od przykładu HotPDF Hello World, który ustawia dokument, czcionkę i system współrzędnych z lewym dolnym rogiem, od którego zależy wszystko powyżej. Wywołania TextOut, SetFont i odstępów pokazane tutaj są częścią komponentu HotPDF dla Delphi i C++Builder