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
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
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
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
// 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
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