PDF-документ, який виглядає ідеально на вашому комп'ютері, але відображається як ряд порожніх прямокутників на чиємусь іншому, є найпоширенішим дефектом шрифтів у програмному забезпеченні для роботи з документами, і це майже ніколи не означає, що текст пошкоджений. Символи цілі, кодування правильне, просто відсутні гліфи. Різниця між двома комп'ютерами полягає у встановлених в операційній системі шрифтах, а межа між портативним і крихким файлом визначається одним рішенням, прийнятим під час запису сторінки: чи був шрифт вбудований у PDF, чи очікувалося його наявність на кінцевому пристрої
Щоб зрозуміти, чому це відбувається, і чому через інший збій текст виглядає так, ніби його можна знайти, але копіюється як нісенітниця, потрібно розібратися, як PDF зберігає текст. Він не зберігає речення. Він зберігає коди гліфів разом із програмою шрифту та таблицями, які пов'язують їх одне з одним, і кожна помилка відтворення або вилучення ховається в розриві між цими трьома елементами. Далі наведено огляд цього механізму, що ґрунтується на ISO 32000, із викликами Delphi, які керують ним там, де це важливо
Символи, коди та гліфи: три різні поняття
Термінологія збиває людей з пантелику, оскільки в повсякденній мові три різні ідеї об'єднуються в слово "літера". Символ - це абстрактна одиниця письма, ідея великої літери А, ідентифікована в Unicode як U+0041. Гліф - це намальована фігура, контур із кривих і штрихів, який певний шрифт використовує для зображення цього символу. Між ними знаходиться код: байт або байти в потоці вмісту, які повідомляють програмі перегляду, який гліф у поточному шрифті потрібно намалювати
PDF працює з кодами. Коли потік вмісту показує рядок, ці байти є індексами в активному шрифті, а не Unicode. Кодування шрифту визначає, що код 65 означає "намалювати гліф під номером 65", і ніщо в цій операції не знає, що результат виглядає як А для людини. Саме це змушує 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 перед цим викликом. Призначте його після, і не буде жодної помилки чи попередження, лише файл, який тихо вийшов без своїх шрифтів. Це найгірший вид помилки: він проходить усі тести на комп'ютері розробника, де шрифт випадково встановлений, і виявляється лише у клієнта, де його немає
Вбудовування також є місцем, де ліцензування зустрічається з інженерією. Програма шрифту містить прапорці, що описують, чи можна її вбудовувати вільно, лише для попереднього перегляду, чи взагалі не можна. Дотримання цих прапорців є вашим обов'язком, а не засобу відтворення, і "воно спрацювало" не означає те саме, що "це було дозволено"
Субсетування: вбудовуйте лише ті гліфи, які ви використовували
Повне вбудовування записує у файл усю програму шрифту. Великий шрифт 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, вбудовані та субсетовані
Письмо справа наліво та складні системи письма додають шар формування (shaping) поверх охоплення. HotPDF пропонує RtLTextOut для арабської та івриту, який обробляє зміну напрямку, тому ви передаєте логічний порядок і дозволяєте бібліотеці скомпонувати його. Правильне відображення арабської мови - це охоплення, формування та напрямок, три окремі речі, і прямокутник у цьому випадку може означати збій будь-якої з них
Таблиця ToUnicode: місце, де живе копіювання і вставлення
Усе вищезазначене стосується малювання. Вилучення тексту є дзеркальним відображенням і зазнає невдачі з власних причин. Програма перегляду відтворює сторінку, використовуючи відображення коду в гліф шрифту, але коли користувач виділяє текст і копіює його, програмі перегляду потрібно перетворити ті самі коди назад у Unicode. Це зворотне відображення є CMap ToUnicode, додатковим потоком, прикріпленим до шрифту
Коли воно присутнє і правильне, скопійований текст виходить як правильні символи. Коли його немає або воно неправильне, або шрифт був субсетований із власними кодами гліфів і ToUnicode не було записано, сторінка виглядає ідеально, а буфер обміну заповнюється нісенітницею: коди гліфів читаються так, ніби вони є Unicode, хоча для спеціально закодованого субсету це не так. Саме тому відсканований документ із текстовим шаром OCR може бути придатним для пошуку, тоді як створений у цифровому вигляді PDF від недбалого генератора - ні. Відтворення та вилучення спираються на різні таблиці, тому файл може задовольняти одну і не працювати з іншою. Якщо вилучення тексту має значення для вашого результату, вважайте правильну карту ToUnicode вимогою та перевіряйте її, копіюючи текст зі зразка, а не довіряючи тому, що вона там є
Як швидко діагностувати помилку шрифту
Режим збою підказує, де шукати. Порожні прямокутники на іншому комп'ютері майже завжди означають невбудований шрифт, тому спершу перевіряйте вбудовування, а потім охоплення гліфів. Прямокутники, які з'являються навіть на вашому власному комп'ютері, вказують на охоплення: шрифт не містить цю систему письма, незалежно від вбудовування. Текст, який відтворюється правильно, але копіюється як нісенітниця, є проблемою ToUnicode, а не відтворення, і маніпуляції зі шрифтами або вбудовуванням не виправлять її, оскільки малювання ніколи не було зламано. Щоб прочитати готовий файл, відкрийте його в Acrobat і перегляньте властивості документа на вкладці шрифтів: здоровий запис показує тип, каже Embedded або Embedded Subset і називає кодування. Шрифт, який має бути вбудованим, але не є таким, заявляє про себе там ще до того, як це зробить клієнт
Ніщо з цього не є екзотикою, щойно поділ на символ, код і гліф стає зрозумілим. Вбудовуйте шрифти, якими ви малюєте, робіть субсети для великих шрифтів, використовуйте шрифт Unicode та RegisterUnicodeTTF, щойно текст перестає бути латиницею, і зберігайте правильну карту ToUnicode, якщо хтось буде вилучати текст. Зробіть це правильно, і прямокутники перестануть з'являтися. Щодо супутньої механіки, анатомія мінімального PDF показує, де в дереві об'єктів знаходиться словник шрифтів, а покроковий огляд структури документа охоплює те, як ресурси розподіляються між сторінками
Виклики SetFont, FontEmbedding та RegisterUnicodeTTF, показані тут, є частиною компонента HotPDF для Delphi та C++Builder