Plik PDF, który wygląda idealnie na Twojej maszynie, a renderuje się jako rząd pustych prostokątów na innej, to najczęstszy defekt związany z czcionkami w oprogramowaniu do dokumentów i prawie nigdy nie oznacza, że tekst jest błędny. Znaki są nienaruszone, kodowanie jest w porządku, po prostu nie ma glifów. To, co zmieniło się między dwiema maszynami, to zainstalowane w systemie operacyjnym czcionki, a różnica między plikiem przenośnym a plikiem podatnym na błędy to jedna decyzja podjęta podczas tworzenia strony: czy czcionka podróżuje wewnątrz pliku PDF, czy też założono, że jest obecna na urządzeniu docelowym
Zrozumienie, dlaczego tak się dzieje i dlaczego osobny błąd generuje tekst wyglądający na możliwy do przeszukiwania, który po skopiowaniu staje się bełkotem, wymaga spojrzenia na to, jak PDF przechowuje tekst. Nie przechowuje zdań. Przechowuje kody glifów, a także program czcionki oraz tabele, które mapują jedno na drugie, i każdy błąd renderowania lub wyodrębniania tkwi w luce między tymi trzema elementami. Poniżej znajduje się przegląd tego mechanizmu, oparty na normie ISO 32000, wraz z wywołaniami w Delphi, które nim sterują tam, gdzie mają one znaczenie
Znaki, kody i glify to trzy różne rzeczy
Słownictwo myli ludzi, ponieważ potoczna mowa łączy trzy różne pojęcia w słowo „litera”. Znak to abstrakcyjna jednostka pisma, idea wielkiej litery A, zidentyfikowana w standardzie Unicode jako U+0041. Glif to narysowany kształt, kontur składający się z krzywych i kresek, którego określona czcionka używa do przedstawienia tego znaku. Pomiędzy nimi znajduje się kod: bajt lub bajty w strumieniu zawartości, które mówią przeglądarce, jaki glif z bieżącej czcionki należy narysować
PDF działa na kodach. Kiedy strumień zawartości pokazuje ciąg znaków, te bajty są indeksami aktywnej czcionki, a nie kodowaniem Unicode. Kodowanie czcionki decyduje, że kod 65 oznacza „narysuj glif zapisany pod numerem 65”, i nic w tej operacji nie wie, że wynik wygląda dla człowieka jak litera A. Właśnie to sprawia, że plik PDF renderuje się identycznie wszędzie tam, gdzie może znaleźć glify, i to jest również powód, dla którego wyodrębnianie jest problemem odrębnym od wyświetlania: rysowanie wymaga tylko mapowania kodu na glif, czytanie wymaga mapowania kodu na Unicode, a to są dwie różne tabele, które mogą być niezgodne lub niezależnie od siebie brakować
Typy czcionek, z którymi faktycznie się spotkasz
Norma ISO 32000 definiuje kilka typów słowników czcionek i w praktyce dokument, który otrzymujesz lub generujesz, używa jednego z trzech. Wiedza na temat tego, na który patrzysz, wyjaśnia większość problemów, które mogą wystąpić
Type 1 to oryginalny format konturów PostScript firmy Adobe, zbudowany z sześciennych krzywych Béziera. Czternaście standardowych czcionek, które każdy zgodny ze standardem czytnik musi dostarczać, z rodzin Helvetica, Times, Courier, Symbol i ZapfDingbats, to czcionki Type 1, i słownik czcionek, który wymienia jedną z nich, może legalnie pominąć program czcionki. Jest to jeden przypadek, w którym pozostawienie nieosadzonej czcionki jest bezpieczne z definicji specyfikacji, a nie przez przypadek. W przypadku każdego innego kroju Type 1 program musi być osadzony lub czytnik podstawia coś innego, zazwyczaj czcionkę podobną metrycznie, ale wyraźnie inną wizualnie
TrueType wykorzystuje krzywe kwadratowe i wywodzi się ze świata firm Apple i Microsoft. Są to najczęściej systemowe czcionki i te, które będziesz najczęściej osadzać. Prosta czcionka TrueType w pliku PDF jest ograniczona do jednobajtowych kodów, więc jedna taka czcionka może zaadresować najwyżej 256 glifów naraz. Ten limit to strukturalny powód, dla którego pismo CJK i inne obszerne systemy pisma nie mogą opierać się na prostej czcionce
Type 0, czcionka złożona lub CID-keyed, jest odpowiedzią na to ograniczenie. Wykorzystuje wielobajtowe kody i tablicę CMap, aby kierować je przez potomną czcionkę CIDFont, której kontury to TrueType lub CFF/Type 1. Jest to jedyny typ czcionki, który może przenosić tysiące glifów, więc każdy plik PDF zawierający pismo chińskie, japońskie, koreańskie lub obszerną mieszankę wielojęzyczną używa Type 0, bez względu na to, czy autor o tym pomyślał. Kosztem jest złożoność: więcej ruchomych części, z których więcej musi być poprawnych zarówno dla renderowania, jak i wyodrębniania

Jeden szczegół za tym obrazkiem wpływa na rozmiar pliku. Czcionka to biblioteka konturów, a nie bitmapy o stałym rozmiarze, więc ten sam osadzony program obsługuje każdy rozmiar punktowy na stronie. Skalowanie jest transformacją stosowaną w momencie rysowania, co wyjaśnia, dlaczego nagłówek i jego tekst główny współdzielą jeden osadzony krój i dlaczego koszt osadzania jest ponoszony na każdą czcionkę, a nie na rozmiar
Osadzanie to różnica między plikiem przenośnym a podatnym na błędy
Osadzanie oznacza, że program czcionki, czyli rzeczywiste dane konturów, jest zapisywany w pliku PDF jako strumień. Czytnik na maszynie, która nigdy nie słyszała o twojej czcionce, czyta te kontury prosto z pliku i rysuje dokładne glify. Pominiesz osadzanie i będziesz zgadywać, że na docelowym komputerze zainstalowano czcionkę o tej samej nazwie; jeśli tak nie jest, przeglądarka stosuje zamiennik. W przypadku czternastu standardowych czcionek podstawienie to jest zdefiniowane i łagodne. Dla wszystkich pozostałych czcionek, efektem może być od niezbyt dokładnego dopasowania w innym kroju pisma, do puste prostokąty, kiedy żaden zamiennik nie obsługuje danego systemu pisma
W HotPDF kontrolę sprawuje pojedyncza właściwość ustawiana przed otwarciem dokumentu. FontEmbedding nakazuje bibliotece spakować do pliku używane kroje pisma:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Compression := cmFlateDecode;
Pdf.FontEmbedding := True; // outlines travel inside the file
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Calibri', [], 11);
Pdf.CurrentPage.TextOut(72, 760, 0, 'This renders the same on a machine without Calibri.');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Kolejność nie jest tylko kosmetyczna. BeginDoc to moment, w którym HotPDF zatwierdza strukturę dokumentu, więc FontEmbedding musi mieć wartość true przed tym wywołaniem. Przypisanie tego później spowoduje, że nie pojawi się żaden błąd, żadne ostrzeżenie, a powstanie plik, który po cichu został wygenerowany bez swoich czcionek. To najgorszy rodzaj błędu: przechodzi wszystkie testy na maszynie dewelopera, gdzie czcionka jest zainstalowana, a ujawnia się dopiero u klienta, gdzie jej nie ma
Osadzanie to także moment, w którym licencjonowanie spotyka się z inżynierią. Program czcionki zawiera flagi opisujące, czy można ją swobodnie osadzać, czy tylko w celach podglądu, czy też w ogóle nie można tego robić. Przestrzeganie tych flag to twój obowiązek, a nie modułu renderującego, a „to zadziałało” nie jest tym samym, co „to było dozwolone”
Tworzenie podzbiorów: osadzaj tylko te glify, których użyłeś
Pełne osadzenie zapisuje cały program czcionki w pliku. Duży krój CJK TrueType może zajmować kilka megabajtów i osadzanie go w całości, by pokazać kilkanaście znaków, to marnotrawstwo, które kumuluje się w dokumencie wielostronicowym. Podzbiory rozwiązują ten problem, zapisując tylko te glify, do których odwołuje się dokument, po czym zmieniają nazwę czcionki, dodając sześcioliterowy tag i znak plus – w formie ABCDEF+Calibri na liście czcionek każdego pliku PDF z podzbiorem – aby czytnik nigdy nie pomylił częściowego kroju pisma z pełną czcionką systemową o tej samej nazwie
Dla większości wygenerowanych dokumentów domyślnie należy używać podzbiorów. Utrzymuje to rozmiar pliku proporcjonalny do treści, a nie do źródłowej czcionki, co ma największe znaczenie w przypadku dużych czcionek wielojęzycznych, które w przeciwnym razie zdominowałyby plik. Jedynym zastrzeżeniem jest to, że podzbiór zawiera tylko to, co zostało użyte w momencie tworzenia. Jeśli późniejszy proces będzie próbował dodać tekst do czcionki z podzbioru, może się okazać, że potrzebnych glifów nie ma w pliku, co stanowi realne ograniczenie przy edycji przyrostowej cudzego pliku PDF
Czcionki Unicode i problem pustych prostokątów w CJK
Gdy tekst nie używa zwykłej łaciny, rozwiązanie z prostą czcionką wyczerpuje się, a sposobem na to jest jawna rejestracja czcionki obsługującej Unicode i pozwolenie HotPDF na zbudowanie na jej podstawie czcionki Type 0. RegisterUnicodeTTF ładuje plik TrueType według ścieżki; następnie zarejestrowaną nazwę można używać w wywołaniu SetFont jak każdej innej:
Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;
Decydują o tym dwie rzeczy. Czcionka musi obejmować systemy pisma użyte w ciągu znaków: TrueType zawierający tylko łacinę nie wygeneruje chińskich glifów tylko dlatego, że tego zażądałeś, a w rezultacie znowu pojawią się puste prostokąty, tym razem dlatego, że dany glif faktycznie nie istnieje w tym kroju pisma. Osadzanie musi też pozostać włączone, ponieważ czcionka Type 0 złożona z zarejestrowanej czcionki TTF jest bez znaczenia dla czytnika, który nie może znaleźć konturów. W przypadku zróżnicowanej zawartości trwałym wyborem jest krój pisma o szerokim pokryciu, najczęstszym rozwiązaniem są rodziny Noto i Arial Unicode MS, osadzone jako podzbiory
Złożone systemy pisma i pisane od prawej do lewej dodają do pokrycia warstwę kształtowania (shaping). HotPDF udostępnia RtLTextOut dla języka arabskiego i hebrajskiego, który obsługuje zmianę kierunku, więc przekazujesz porządek logiczny i pozwalasz bibliotece się tym zająć. Poprawność w przypadku arabskiego to pokrycie glifami plus kształtowanie, plus kierunek, to trzy oddzielne rzeczy, a pusty prostokąt w tym przypadku może oznaczać, że zawiodła jakakolwiek z nich
Tabela ToUnicode: tu żyje operacja kopiuj-wklej
Wszystko powyższe dotyczy rysowania. Wyodrębnianie (ekstrakcja) to jego lustrzane odbicie i z innych przyczyn może się nie udać. Przeglądarka renderuje stronę za pomocą mapowania kod-glif czcionki, ale gdy użytkownik wybiera tekst i go kopiuje, przeglądarka musi przekształcić te same kody z powrotem na format Unicode. To odwrotne mapowanie to tablica ToUnicode CMap, będąca opcjonalnym strumieniem dołączonym do czcionki
Gdy jest ona obecna i poprawna, skopiowany tekst ujawnia się we właściwych znakach. Kiedy jej brakuje, jest błędna lub czcionka jest w postaci podzbioru z niestandardowymi kodami glifów i nie zapisano tablicy ToUnicode, strona wygląda idealnie, a schowek wypełnia się śmieciami: kody glifów odczytano tak, jakby były w formacie Unicode, czym dla podzbioru kodowanego w niestandardowy sposób nie są. Właśnie dlatego zeskanowany dokument z warstwą tekstu OCR może być możliwy do przeszukiwania, podczas gdy natywnie cyfrowy PDF od niestarannie napisanego generatora już nie. Renderowanie i ekstrakcja opierają się na różnych tabelach, więc plik może spełniać warunki jednej z nich i nie spełniać drugiej. Jeśli ekstrakcja jest ważna dla generowanych przez ciebie plików, traktuj prawidłową mapę ToUnicode jako wymóg i weryfikuj to, kopiując tekst z próbki, zamiast ufać, że mapa na pewno się tam znajduje
Jak szybko diagnozować błędy czcionek
Rodzaj błędu podpowiada, gdzie szukać. Puste prostokąty na innej maszynie prawie zawsze oznaczają czcionkę, która nie została osadzona, więc najpierw sprawdź osadzanie, a w drugiej kolejności pokrycie glifami. Puste prostokąty pojawiające się nawet na twojej własnej maszynie wskazują na problem z pokryciem: czcionka nie zawiera danego pisma, niezależnie od osadzenia. Tekst renderowany prawidłowo, po skopiowaniu wyglądający jak nonsens to problem z ToUnicode, a nie błąd renderowania, i zmiana ustawień dotyczących czcionek lub osadzania tego nie naprawi, bo rysowanie nigdy nie było zepsute. Aby sprawdzić gotowy plik, otwórz go w programie Acrobat i spójrz we Właściwości dokumentu i zakładkę Czcionki (Fonts): prawidłowy wpis pokazuje typ, mówi Osadzona (Embedded) lub Osadzony podzbiór (Embedded Subset) i określa kodowanie. Czcionka, która powinna zostać osadzona, a nie została, pojawia się w tym miejscu, zanim zdąży zauważyć to klient
Nic z tego nie jest dziwne, kiedy różnica między znakiem, kodem i glifem stanie się jasna. Osadzaj czcionki, którymi rysujesz, uciekaj się do podzbiorów dla największych z nich, sięgaj po krój Unicode i używaj metody RegisterUnicodeTTF w momencie, gdy tekst przestaje być po łacinie, i zachowuj prawidłową mapę ToUnicode, jeśli ktoś może wyodrębniać ten tekst. Jeśli wszystko to zrobisz dobrze, prostokąty przestaną się pojawiać. Jeśli chodzi o powiązaną mechanikę, anatomia minimalnego pliku PDF pokazuje, gdzie słownik czcionek znajduje się w drzewie obiektów, a przegląd struktury dokumentu obejmuje współdzielenie zasobów pomiędzy stronami
Przedstawione tutaj wywołania SetFont, FontEmbedding oraz RegisterUnicodeTTF są częścią komponentu HotPDF Component dla Delphi i C++Builder