Кожен видимий символ у PDF містить посилання на шрифт, яким він був намальований, і PDFium Component дозволяє перейти за цим посиланням до об'єкта шрифту та прочитати те, що йому відомо. Одиницею доступу є символ, а не документ: ви вибираєте символ за його індексом у тексті сторінки та запитуєте назву сімейства, базову назву, насиченість, кут нахилу та те, чи фактично міститься базове накреслення всередині файлу. Ця остання властивість є тим, чого насправді прагнуть більшість аналізів, оскільки вбудований шрифт мандрує разом з документом, а невбудований є обіцянкою того, що на комп'ютері читача випадково встановлений такий самий шрифт
Компонент надає доступ до них через ті самі об'єкти TPdf та TPdfView, які ви використовуєте для рендерингу та вилучення тексту. Немає окремого об'єкта "таблиці шрифтів", який потрібно відкривати. Після того, як текст сторінки розібрано, властивості шрифту прив'язуються до індексу символу, і ви зчитуєте їх по одному гліфу за раз. Такий дизайн відповідає тому, як PDF зберігає інформацію від самого початку: одна сторінка може змінювати шрифти десятки разів, і єдина чесна відповідь на питання "яким шрифтом цей документ" звучить як "це залежить від того, який символ ви маєте на увазі"
Читання шрифту, що стоїть за одним символом
Найменша корисна операція полягає в тому, щоб взяти індекс символу і вивантажити все, що PDFium може розповісти про його шрифт. Кожна властивість шрифту в TPdf і TPdfView індексується за позицією символу, тому індекс проходить крізь них усіх. Сторінка також має бути поточною, щоб індекс розв'язувався відповідно до правильного тексту, що має значення, як тільки ви переходите далі першої сторінки
procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
Report: TStringList;
PtSize: Single;
begin
Report := TStringList.Create;
try
PtSize := Pdf.FontSize[CharIndex];
Report.Add('Character : ' + Pdf.Character[CharIndex]);
Report.Add('Family : ' + Pdf.FontFamilyName[CharIndex]);
Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
Report.Add('Weight : ' + IntToStr(Pdf.FontWeight[CharIndex]));
Report.Add('Italic : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
Report.Add('Size : ' + FormatFloat('0.0', PtSize) + ' pt');
Report.Add('Ascent : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
Report.Add('Descent : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
Report.Add('Embedded : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));
ShowMessage(Report.Text);
finally
Report.Free;
end;
end;
Кілька сигнатур дивують людей, які приходять з інших бібліотек. FontAscent та FontDescent приймають два аргументи: індекс символу та розмір у пунктах, оскільки PDFium повідомляє ці метрики в одиницях простору гліфів, які стають пікселями лише тоді, коли ви масштабуєте їх за розміром, у якому був встановлений текст. Передайте значення, яке ви вже прочитали з FontSize[CharIndex], і ви отримаєте підйом (ascent) та спуск (descent) у тих самих пунктах, що й решта макета. Спуск повертається від'ємним, оскільки він вимірюється нижче базової лінії. Назва сімейства та базова назва є окремими рядками з певною метою: базова назва є сирим записом /BaseFont з PDF, який часто містить префікс підмножини, як-от ABCDEF+, тоді як назва сімейства є очищеною назвою, в яку її розв'язує рендерер
Перетворення кліку в індекс символу
У засобі перегляду ви рідко знаєте індекс заздалегідь. Користувач клікає гліф, і вам потрібно перевести піксельні координати в символ під ним. CharacterIndexAtPos робить саме це, приймаючи позицію миші та допуск і повертаючи індекс найближчого символу, або від'ємне значення, коли клік потрапив на пробіл чи порожню сторінку
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// 4 px of slack in each direction so a near-miss still hits the glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // clicked between glyphs; leave the panel alone
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Допуск варто налаштувати. Якщо він занадто жорсткий, користувачі відчувають, що їм потрібно потрапити точно на штрих літери; якщо занадто вільний, клік на полі прив'язується до якогось віддаленого символу, що не має нічого спільного з тим, що вони мали на увазі. Три-п'ять пікселів пристрою є розумною відправною точкою для перегляду на екрані. Повернутий індекс відноситься до розібраного тексту поточної сторінки, того самого простору індексів, який очікує кожна властивість шрифту, тому ви можете передати його безпосередньо в процедуру вище. Збереження його в CurrentCharIndex є необов'язковим, але зручним: представлення зберігає його як своє поняття про сфокусований гліф, що зручно, якщо інші частини інтерфейсу хочуть прочитати виділення без його повторного виведення
Вбудовування є властивістю, що має значення
Для більшості реальної роботи єдине питання, на яке варто відповісти, полягає в тому, чи кожен шрифт вбудований. Документ, шрифти якого зберігаються всередині нього, відображається однаково на RIP друкарні, ноутбуці колеги та сервері взагалі без графічного інтерфейсу. Документ, який покладається на невбудований Helvetica, грає в рулетку з тим, що кожна з цих машин має відповідний шрифт, і коли ця ставка не спрацьовує, читач замінює щось близьке, метрики зсуваються, а ретельно скомпонована форма перекомпоновується рівно настільки, щоб зламатися. Обхід тексту сторінки та розподіл шрифтів за статусом вбудовування дешево дає вам цю відповідь
procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
Embedded, External: TStringList;
I: Integer;
Name: string;
begin
Embedded := TStringList.Create;
External := TStringList.Create;
try
Embedded.Sorted := True;
Embedded.Duplicates := dupIgnore;
External.Sorted := True;
External.Duplicates := dupIgnore;
for I := 0 to Pdf.CharacterCount - 1 do
begin
Name := Pdf.FontBaseName[I];
if Name = '' then
Continue; // generated spaces and the like have no font
if Pdf.FontIsEmbedded[I] then
Embedded.Add(Name)
else
External.Add(Name);
end;
if External.Count > 0 then
ShowMessage(IntToStr(External.Count) +
' non-embedded font(s):' + sLineBreak + External.Text)
else
ShowMessage('All ' + IntToStr(Embedded.Count) +
' font(s) on this page are embedded.');
finally
Embedded.Free;
External.Free;
end;
end;
Дві деталі зберігають цей підхід правильним. По-перше, CharacterCount застосовується до сторінки, тому аудит усього документа означає встановлення Pdf.PageNumber на кожну сторінку по черзі та повторне виконання циклу зі злиттям результатів. По-друге, текстовий шар містить згенеровані символи, такі як пробіли, які читач виводить між словами, і за ними немає об'єкта шрифту; перевірка на порожню базову назву пропускає їх замість реєстрації фантома. Базова назва є правильним ключем для дедуплікації тут, оскільки префікс підмножини, який вона несе, розрізняє дві різні підмножини одного сімейства, що, як правило, і є тим, що ви хочете дізнатися
Витягнення вбудованого накреслення
Коли шрифт вбудований, ви можете прочитати його байти напряму. FontData повертає сиру програму шрифту, ті самі дані TrueType або CFF, які містить PDF, чого достатньо, щоб записати окремий файл шрифту або зняти відбиток накреслення для порівняння з відомою бібліотекою. Він повертає порожній масив, коли шрифт не вбудований, тому перевірка вбудовування та перевірка довжини разом захищають запис
procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
const OutputFile: string);
var
Data: TBytes;
Stream: TFileStream;
begin
if not Pdf.FontIsEmbedded[CharIndex] then
begin
ShowMessage('That glyph''s font is not embedded; nothing to extract.');
Exit;
end;
Data := Pdf.FontData[CharIndex];
if Length(Data) = 0 then
Exit;
Stream := TFileStream.Create(OutputFile, fmCreate);
try
Stream.WriteBuffer(Data[0], Length(Data));
finally
Stream.Free;
end;
ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;
Ці байти є вбудованою підмножиною, а не оригінальним комерційним шрифтом, тому те, що ви отримуєте назад, зазвичай покриває лише ті гліфи, які фактично використовував документ. Це абсолютно правильно для криміналістики та верифікації і погано підходить для повторного використання; підмножина Times New Roman, що містить тридцять гліфів, не є шрифтом, який можна встановити та яким можна друкувати. Ставтеся до вилучення як до способу перевірки того, що було надіслано, а не як до інструменту відновлення шрифтів. Якщо вам потрібна відповідна базова назва для маркування виводу, прочитайте FontBaseName[CharIndex] разом із даними та приберіть початковий тег підмножини, якщо вам потрібне голе сімейство
Осмислення числа насиченості
FontWeight повертає числовий клас насиченості, ту саму шкалу від 100 до 900, яку використовує CSS, де 400 є звичайним, а 700 є жирним. PDFium повідомляє все, що оголошує шрифт, що не завжди є круглою сотнею; накреслення може оголошувати 350 або 650, і сприйняття будь-чого на рівні 600 або вище як "достатньо жирного, щоб мати значення" працює краще, ніж перевірка на рівно 700. Кут нахилу є супутнім сигналом: ненульове значення, зазвичай від'ємне, означає, що накреслення є похилим або справжнім курсивним дизайном, а нуль означає пряме. Разом вони дозволяють відрізнити жирно-курсивний текст від звичайного без рендерингу будь-чого, що є саме тим видом перевірки, яку додрукарська підготовка або аудит доступності хочуть здійснювати масово
Жодне з цих читань не вимагає відрендереного растрового зображення. Вони походять із розібраного текстового шару, тому відкритий документ на правильній сторінці є всім необхідним налаштуванням, що робить перевірку шрифтів дешевою для виконання в усьому архіві. Якщо ви поєднуєте це з вилученням тексту, ті самі індекси символів збігаються з текстом, який ви витягуєте, тому шрифт гліфа та його значення Unicode — це два читання щодо одного індексу. Супутня стаття про вилучення тексту з документів PDF за допомогою PDFium Component охоплює цей бік текстового шару більш глибоко
Властивості шрифту, показані тут, є частиною PDFium Delphi VCL Component