Gdy PDF nie osadza fontu, komponent HotPDF renderuje ten tekst zainstalowanym fontem Windows wybranym przez HPDFMapBaseFontToSystem: dekoduje nazwę /BaseFont, obcina część stylową, próbuje kilku pisowni, dopóki GDI nie potwierdzi, że rodzina jest zainstalowana, mierzy brakujące szerokości standard 14 na fontach zgodnych metrycznie i konwertuje jednobajtowe kody na Unicode przed rysowaniem. Każdy z tych kroków istnieje, bo naiwna wersja wywalała się na prawdziwych plikach. Renderer stron RenderLoadedPageToBitmap radzi sobie dobrze z osadzonymi programami; to historia fontów, których w pliku w ogóle nie ma
Dlaczego GDI po cichu rysuje złą odmianę pisma dla nieosadzonego fontu?
GDI nigdy nie zgłasza brakującego fontu: podaj CreateFontIndirect nazwę odmiany, której nie zna, a po cichu wybierze zamiennik, często inny serif bez grubości bold. Wczesny renderer przekazywał nazwę PDF niemal dosłownie, więc TimesNewRoman,Bold, TimesNewRomanPS-BoldMT i SegoeUI-Semibold niczego nie łapały i wychodziły w tym, co GDI wybrało. Nazwy potrafią być gorsze. ISO 32000-1 §7.3.5 pozwala nazwie zapisać każdy bajt jako #xx, a producenci CJK rutynowo literują nazwy fontów jako escaped UTF-8 albo bajty starszych stron kodowych; przed v2.766.69 same ucieczki stawały się nazwą odmiany. HPDFMapBaseFontToSystem dekoduje teraz najpierw ucieczki, zwraca poprawną sekwencję bajtów UTF-8 jako swoje znaki i czyta pozostałe wysokie bajty w systemowej stronie kodowej
Obcinanie stylu to miejsce, gdzie heurystyki gryzą. Przecinek zawsze kończy rodzinę (Arial,Bold daje Arial), ale łącznik tylko wtedy, gdy słowo po nim jest stylem: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra albo Condensed. Ta reguła trzyma MS-Mincho w całości, zamieniając Calibri-Light w Calibri. HotPDF próbuje potem pisowni rodziny, a za nimi pisowni rodziny z obciętym sufiksem PSMT, MT albo PS. Od v2.768.18 te pisownie pokrywają każdy wybór spacji w miejscach, gdzie może zacząć się słowo: przed wielką literą po małej (MyriadPro staje się Myriad Pro), przy ostatniej wielkiej ciągu, po którym następuje mała (UIGothic), i po wiodącym MS (MSPGothic), od w pełni rozdzielanej postaci aż po nazwę tak, jak zapisano; ponad cztery takie miejsca ograniczają się do w pełni rozdzielanej i sklejonej nazwy. Spacji nie da się stosować na ślepo, bo Windows część słów trzyma sklejone: SimSun jest zainstalowany dokładnie pod tą pisownią, podczas gdy MicrosoftYaHei, MicrosoftJhengHei i MSPGothic należą do Microsoft YaHei, Microsoft JhengHei i MS PGothic. Przed v2.768.18 mapper stawiał spację przed każdą wewnętrzną wielką literą, więc MicrosoftYaHei było wyszukiwane jako Microsoft Ya Hei i nigdy nie było znajdowane. Kandydat liczy się jako zainstalowany, gdy CreateFontIndirect, a za nim GetTextFace, zwracają żądaną nazwę albo, od v2.768.18, gdy tabela name wybranego fontu wymienia go jako rodzinę, pełną albo typograficzną nazwę rodziny w dowolnym języku; odpowiedź jest cache'owana per nazwa, więc dokumenty z wieloma niezainstalowanymi fontami nie dopytują już Windows o każdą nazwę na każdej stronie
Ponieważ funkcja mapująca jest publiczna w jednostce HPDFRenderFontMetrics, raport wstępny może pokazać, z którą zainstalowaną rodziną wyrenderuje się każdy nieosadzony font, korzystając z wyliczania fontów, które THotPDF już wystawia dla wczytanych dokumentów:
uses
HPDFDoc, HPDFRenderFontMetrics;
procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
Page, I: Integer;
Info: THPDFLoadedFontInfo;
begin
for Page := 0 to Pdf.LoadedPageCount - 1 do
for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
Log.Add(Format('page %d /%s %s -> %s',
[Page + 1, string(Info.ResourceName), string(Info.FontName),
HPDFMapBaseFontToSystem(Info.FontName)]));
end;
Jak HotPDF mierzy standardowe 14 fontów bez /Widths?
HotPDF mierzy brakujące posuwy na zainstalowanym fontie o tych samych metrykach, bo ISO 32000-1 §9.6.2.2 pozwala standardowym 14 fontom pominąć /Widths, a biblioteka nie wozi ze sobą tabel AFM. Arial niesie metryki Helvetica, Times New Roman niesie Times, a Courier New niesie Courier, więc HPDFMeasureBaseFontWidths tworzy pasującą odmianę przy lfHeight = -1000 i woła GetCharWidth32W; przy tej wysokości wynik jest już w jednostkach 1/1000 em, którymi posługują się szerokości PDF. Renderer najpierw zamienia każdy kod na Unicode przez /Encoding, /BaseEncoding i /Differences, z domyślnym StandardEncoding. Eksport SVG i ekstrakcja tekstu wpadły w jeszcze jedną pułapkę: standardowy font Type 1 bez jakiegokolwiek /Encoding produkował dekoder bez informacji o kodowaniu, eksport SVG nigdy go nie rejestrował i żadna zmierzona szerokość nie była używana. Podanie implikowanego StandardEncoding naprawiło sprawę, pod warunkiem oznaczenia go jako kodowanie predefiniowane; skierowanie go ścieżką nazwy CMap dekoduje każdy kod jako 0 i każda szerokość za tym idzie
Bold, italic i przesunięcie o jeden w flagach deskryptora fontu
Wpis /Flags deskryptora fontu numeruje swoje bity od 1, nie od 0, więc ForceBold to bit 19 ($40000), a Italic to bit 7 ($40), zgodnie z tabelą 123 ISO 32000-1. Stary kod testował $20000, czyli bit 18, SmallCap. Pomyłka przeżyła od v2.345.0 do v2.766.53, bo /FontDescriptor jest niemal zawsze referencją pośrednią, a budowniczy fontów czytał tylko obiekty bezpośrednie, więc cała gałąź flag nigdy się nie uruchamiała, i ta sama ślepota ignorowała /Widths 12 0 R, składając tekst z zastępczym posuwem 500 jednostek. Gdy v2.766.53 zaczęło rozwiązywać referencje pośrednie przez renderer, bit trzeba było poprawić w tej samej zmianie, inaczej każda odmiana kapitalików nagle renderowałaby się jako bold:
const
// Tabela 123 ISO 32000-1 liczy pozycje bitów od 1
FD_ITALIC = $00040; // bit 7
FD_SMALLCAP = $20000; // bit 18, nie grubość
FD_FORCEBOLD = $40000; // bit 19
procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
if (Flags and FD_FORCEBOLD) <> 0 then
LF.lfWeight := FW_BOLD;
if (Flags and FD_ITALIC) <> 0 then
LF.lfItalic := 1;
end;
Dlaczego fonty CJK z CMap UCS2 dostają złe szerokości?
Tekst CJK z predefiniowanym CMap UCS2 rysuje właściwe glify, ale złe odstępy, gdy renderer traktuje kod jak CID, bo /W jest indeksowana CID-ami, a nie kodami znaków. Przy STSong-Light i UniGB-UCS2-H kod akurat równa się wartości Unicode, więc GDI rysuje właściwe znaki, a błąd kryje się w posuwach: małe litery przychodzą jako kody 97 i wyżej, wypadają poza wpis /W takim jak [1 95 500] i wszystkie dostają domyślną szerokość /DW równą 1000. Od v2.766.56 renderer HotPDF czyta kody przez zakresy codespace CMap (ISO 32000-1 §9.7.6.2) i mapuje je na CID-y, zanim zacznie szukać szerokości. Używane są tylko wbudowane tabele UCS2 i UTF16 oraz osadzone strumienie CMap; przybliżenie tożsamościowe dla czegoś takiego jak GBK-EUC-H jedynie udawałoby wsparcie, produkując zły wynik, więc renderer nie udaje
Dlaczego znaki z akcentami zmieniają się w znaki zapytania na chińskim Windows?
Jednobajtowe kody nie mogą nigdy dotrzeć do funkcji GDI ANSI („A"), bo GetGlyphOutlineA i GetGlyphIndicesA interpretują bajty w systemowej stronie kodowej, podczas gdy TextOutA używa zestawu znaków wybranego fontu. Na chińskim systemie bajt $A9 Ariala (znak copyright w Windows-1252) stawał się wiodącym bajtem GBK i renderował się jako „?", w co prosto wdepnęła ścieżka konturów bez hintingu dodana w v2.766.83. v2.767.3 dopytuje zrealizowany font o jego zestaw znaków przez GetTextCharset, przelicza to na stronę kodową przez TranslateCharsetInfo, przepuszcza bajt przez MultiByteToWideChar i woła funkcje W; fonty symboli używają zamiast tego U+F000 plus kod. Kodowania niezgodne z Windows-1252 — /Differences, StandardEncoding, MacRomanEncoding — są mapowane na Unicode, zanim zobaczy je jakikolwiek font systemowy
Jakie są granice rysowania fontami systemowymi?
Renderowanie fontami systemowymi to przybliżenie, i komponent HotPDF mówi szczerze, gdzie się kończy. Przed v2.768.18 sprawdzenie instalacji porównywało tylko nazwę, którą zwraca GetTextFace, a na zlokalizowanym Windows ta funkcja raportuje nazwę rodziny w języku systemu, więc Microsoft YaHei na chińskim Windows albo Yu Mincho na japońskim był uznawany za brakujący i rysowany zamiennikiem GDI; od v2.768.18 odmiana wracająca pod inną nazwą jest też wyszukiwana w tabeli name fontu i takie fonty są znajdowane. Zgodność metryk jest gwarantowana tylko dla rodzin Helvetica, Times i Courier; Symbol mapuje się na Symbol, a ZapfDingbats na Wingdings, co jest protezą, nie dopasowaniem. Preflight z góry widzi też tylko fonty w słowniku /Resources każdej strony, nie te wskazywane z wnętrza form XObjects. Gdy kodu nadal nie da się narysować, raportuje to śledzenie nierozwiązanych glifów w czasie rysowania, co jest lepszym sygnałem niż oglądanie miniatur
Trwała poprawka siedzi po stronie autorskiej. HotPDF sam pisze z FontEmbedding ustawionym domyślnie na True, podstawiając osadzony Arial nawet, gdy kod woła SetFont z Helvetica, a tekst osadzony przechodzi przez renderer glifów fontów osadzonych zamiast przez jakiekolwiek zgadywanie z góry. Tanią strażą dla przychodzących plików jest ostrzeżenie przed renderowaniem, gdy zmapowanej rodziny nie ma na liście fontów ekranu:
// VCL: Screen.Fonts wymienia zainstalowane nazwy rodzin (jednostka Forms)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;
Pełny komponent, łącznie z renderowaniem stron, ekstrakcją tekstu i subsetowaniem fontów po stronie zapisu, opisuje strona produktu HotPDF Delphi PDF component