PDF, който изглежда перфектно на вашата машина и се рендира като ред от празни кутийки на нечия друга, е най-често срещаният дефект на шрифта в софтуера за документи и почти никога не означава, че текстът е грешен. Знаците са непокътнати, кодирането е наред, глифите просто ги няма. Това, което се е променило между двете машини, е кои шрифтове е инсталирала операционната система, а разликата между преносим файл и крехък файл е едно решение, взето при писането на страницата: дали шрифтът е пътувал вътре в PDF или се е предполагало, че присъства в далечния край
Разбирането защо това се случва и защо отделен провал произвежда текст, който изглежда подходящ за търсене, но се копира като безсмислици, означава да се разгледа как PDF съхранява текст. Той не съхранява изречения. Той съхранява кодове на глифи плюс програма за шрифтове плюс таблици, които съпоставят едното с другото, и всяка грешка при рендиране или извличане живее в пролука между тези трите. Това, което следва, е обиколка на този механизъм, основана на ISO 32000, с извикванията в Delphi, които го контролират, където имат значение
Знаци, кодове и глифи са три различни неща
Речникът спъва хората, защото ежедневната реч свива три отделни идеи в думата "буква". Знакът е абстрактна единица за писане, идеята за главно А, идентифицирана в Unicode като U+0041. Глифът е нарисувана форма, контурът от криви и стъбла, който даден шрифт използва, за да изобрази този знак. Между тях стои кодът: байтът или байтовете в потока от съдържание, които казват на програмата за преглед кой глиф в текущия шрифт да нарисува
PDF работи с кодове. Когато поток от съдържание показва низ, тези байтове са индекси към активния шрифт, а не Unicode. Кодирането на шрифта решава, че код 65 означава "нарисувай глифа, подаден под 65", и нищо в тази операция не знае, че резултатът изглежда като A за човека. Това е, което кара PDF да се рендира идентично навсякъде, където може да намери глифите, и също така е причината, поради която извличането е отделен проблем от показването: рисуването се нуждае само от код-към-глиф, четенето се нуждае от код-към-Unicode, а това са две различни таблици, които могат да не са съгласувани или да липсват независимо една от друга
Видовете шрифтове, които действително ще срещнете
ISO 32000 дефинира няколко типа речници на шрифтове и на практика документът, който получавате или генерирате, използва един от трите. Знанието кой от тях гледате обяснява по-голямата част от това, което може да се обърка
Type 1 е оригиналният контурен формат PostScript на Adobe, изграден от кубични криви на Безие. Четиринадесетте стандартни шрифта, които всяка съвместима програма за преглед трябва да предостави, фамилиите Helvetica, Times, Courier, Symbol и ZapfDingbats, са Type 1 и речник на шрифтове, който назовава един от тях, може законно да пропусне програмата за шрифтове. Това е единственият случай, когато оставянето на шрифт невграден е безопасно по спецификация, а не по късмет. За всяко друго лице от Type 1 програмата трябва да бъде вградена или програмата за преглед замества нещо, обикновено метрично подобен, но видимо различен шрифт
TrueType използва квадратни криви и идва от света на Apple и Microsoft. Това е нещото, което са повечето системни шрифтове, и това, което ще вграждате най-често. Един прост TrueType шрифт в PDF е ограничен до еднобайтови кодове, така че един такъв шрифт може да адресира най-много 256 глифа наведнъж. Този таван е структурната причина CJK и други големи скриптове да не могат да се возят на прост шрифт
Type 0, композитният или CID-ключов шрифт, е отговорът на това ограничение. Той използва многобайтови кодове и CMap, за да ги насочва през низходящ CIDFont, чиито контури самите са или TrueType, или CFF/Type 1. Това е единственият тип шрифт, който може да носи хиляди глифи, така че всеки PDF, съдържащ китайски, японски, корейски или широк многоезичен микс, използва Type 0, независимо дали авторът е мислил за това или не. Компромисът е сложността: повече движещи се части, повече от които трябва да са правилни както за рендиране, така и за извличане

Един детайл зад тази снимка управлява размера на файла. Шрифтът е библиотека от контури, а не растерни изображения с фиксиран размер, така че една и съща вградена програма обслужва всеки размер на точки на страницата. Мащабирането е трансформация, прилагана по време на рисуване, поради което заглавието и неговият основен текст споделят едно вградено лице и защо цената на вграждане е на шрифт, а не на размер
Вграждането е разликата между преносимо и крехко
Вграждането означава, че програмата за шрифтове, действителните данни за контурите, са записани в PDF като поток. Четец на машина, която никога не е чувала за вашия шрифт, чете тези контури направо от файла и чертае точни глифи. Пропуснете вграждането и залагате, че дестинацията има шрифт със същото име; когато няма, програмата за преглед прибягва до заместител. За стандартните четиринадесет тази замяна е дефинирана и доброкачествена. За всичко останало тя варира от близък пропуск в различен шрифт до резултат с празна кутийка, когато нито един заместител изобщо не покрива скрипта
При HotPDF контролът е едно-единствено свойство, зададено преди отварянето на документа. FontEmbedding казва на библиотеката да пакетира лицата, с които рисува, във файла:
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;
Подредбата не е козметична. BeginDoc е мястото, където HotPDF ангажира структурата на документа, така че FontEmbedding трябва да е true преди това извикване. Присвоете го след това и няма грешка, няма предупреждение, просто файл, който тихо е излязъл без своите шрифтове. Това е най-лошият вид грешка: тя минава всеки тест на машината на разработчика, където шрифтът случайно е инсталиран, и излиза на повърхността само на тази на клиента, където не е
Вграждането е и мястото, където лицензирането се среща с инженерството. Една програма за шрифтове носи флагове, описващи дали може да бъде вграждана свободно, само за предварителен преглед или изобщо не. Спазването на тези флагове е ваша отговорност, а не на рендиращия, и "работи" не е същото като "беше разрешено"
Subsetting (подмножество): вграждане само на глифите, които сте използвали
Пълното вграждане записва цялата програма за шрифтове във файла. Голямо CJK TrueType лице може да достигне до няколко мегабайта и вграждането му изцяло, за да се покажат дузина знаци, е разхищение по начин, който се наслагва в многостраничен документ. Подмножеството решава това, като записва само глифите, които документът реферира, след което преименува шрифта с шестбуквен етикет и знак плюс, формата ABCDEF+Calibri във всеки списък с шрифтове на PDF с подмножество, така че четецът никога да не бърка частичното лице с пълен системен шрифт със същото име
За повечето генерирани документи подмножеството е правилното решение по подразбиране. То поддържа размера на файла пропорционален на съдържанието, а не на изходния шрифт, което има най-голямо значение за големите многоезични шрифтове, които иначе биха доминирали файла. Единственото предупреждение е, че едно подмножество съдържа само това, което е използвано по време на създаването. Ако процес надолу по веригата се опита да добави текст към шрифт с подмножество по-късно, глифите, от които се нуждае, може да не са във файла, реално ограничение за инкременталното редактиране на чужд PDF
Unicode шрифтовете и проблемът с CJK кутийките
Когато текстът не е обикновена латиница, пътят на простия шрифт се изчерпва и решението е изрично да се регистрира шрифт с възможности за Unicode и да се позволи на HotPDF да изгради шрифт Type 0 от него. RegisterUnicodeTTF зарежда TrueType файл по път; след това регистрираното име може да се използва в SetFont като всяко друго:
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;
Две неща правят или провалят това. Шрифтът трябва да покрива скриптовете в низа: TrueType само на латиница няма да отгледа китайски глифи, защото сте го помолили, и резултатът отново са празни кутийки, този път защото глифът наистина не съществува в това лице. И вграждането трябва да остане включено, защото шрифт Type 0, сглобен от регистриран TTF, е безсмислен за четец, който не може да намери контурите. За смесено съдържание трайният избор е лице с широко покритие, като фамилиите Noto и Arial Unicode MS са обичайните отговори, вградени и като подмножество
Скриптовете отдясно наляво и сложните скриптове добавят слой за оформяне върху покритието. HotPDF излага RtLTextOut за арабски и иврит, което се справя с пренареждането на посоката, така че подавате логически ред и оставяте библиотеката да го подреди. Правилното изобразяване на арабски е покритие плюс оформяне плюс посока, три отделни неща, и една кутийка там може да означава, че някое от тях се е провалило
Таблицата ToUnicode: къде живее копирането и поставянето (copy-paste)
Всичко по-горе се отнася до рисуването. Извличането е огледалният образ и се проваля по свои собствени причини. Програмата за преглед рендира страница, като използва съпоставянето на код към глиф на шрифта, но когато потребителят избере текст и го копира, програмата за преглед трябва да превърне тези същите кодове обратно в Unicode. Това обратно съпоставяне е ToUnicode CMap, незадължителен поток, прикрепен към шрифта
Когато е налице и е правилен, копираният текст излиза като правилните знаци. Когато липсва или е грешен, или шрифтът е бил подмножество с персонализирани кодове на глифи и не е написан ToUnicode, страницата изглежда перфектно и клипбордът се пълни с боклук: кодовете на глифи се четат така, сякаш са Unicode, каквито те не са за подмножество с персонализирано кодиране. Това е причината сканиран документ с OCR текстов слой да може да се търси, докато роден цифров PDF от невнимателен генератор не може. Рендирането и извличането черпят от различни таблици, така че един файл може да удовлетвори едното и да се провали в другото. Ако извличането има значение за вашия изход, третирайте правилната карта ToUnicode като изискване и я проверете, като копирате текст от извадка, вместо да се доверявате, че е там
Как да диагностицирате грешка в шрифта бързо
Режимът на провал ви казва къде да търсите. Празни кутийки на друга машина почти винаги означават шрифт, който не е бил вграден, така че първо проверете вграждането и след това покритието на глифите. Кутийки, които се появяват дори на вашата собствена машина, сочат към покритието: шрифтът не съдържа този скрипт, независимо от вграждането. Текст, който се рендира правилно, но се копира като безсмислица, е проблем с ToUnicode, а не с рендиране, и бърникането с шрифтове или вграждане няма да го поправи, защото рисуването никога не е било счупено. За да прочетете готов файл, отворете го в Acrobat и погледнете Свойства на документа, Шрифтове: един здрав запис показва типа, казва Embedded (Вграден) или Embedded Subset (Вградено подмножество) и назовава кодиране. Шрифт, който трябва да бъде вграден, а не е, се обявява там, преди клиентът да го направи
Нищо от това не е екзотично, след като разделението между знак, код и глиф е ясно. Вградете шрифтовете, с които рисувате, направете подмножество от големите, посегнете към Unicode лице и RegisterUnicodeTTF в момента, в който текстът напусне латиницата, и поддържайте правилна карта ToUnicode, ако някой ще извлича текста. Направете ги правилно и кутийките спират да се появяват. За обкръжаващата механика, анатомията на минимален PDF показва къде седи речникът на шрифтовете в дървото на обектите, а ръководството за структурата на документа покрива как ресурсите се споделят между страниците
Извикванията SetFont, FontEmbedding и RegisterUnicodeTTF, показани тук, са част от HotPDF Component за Delphi и C++Builder