Липсващ глиф в PDF не е грешка; Производителят иска символ, който избраният шрифт не може да съпостави, шрифтът връща глиф индекс нула и файлът, който излиза, е структурно валиден, отваря се навсякъде и показва празна кутия там, където трябва да е име или сума; Никой в генериращия конвейер не научава; Получателят научава; HotPDF затваря този цикъл с TrackUnresolvedGlyphs: включете го и пътят за рисуване на текст записва всеки кодов пункт, чието глиф търсене резолвира до индекс нула, изстрелвайки OnUnresolvedGlyph веднъж на уникална находка с кодовия пункт, шрифта, върху който се провали, скрипта, на който принадлежи, и предложение за шрифтове, които биха го покрили
Откриването е половината отговор; Другата половина е SetFontFallbackChain, която регистрира подреден списък от шрифтове за скрипт, така че честните случаи се разрешават сами и само истинските дупки достигат вашия handler; Заедно те превръщат клас дефекти, който преди се докладваше от клиенти, в проверка по време на build
Защо липсващ глиф не хвърля нищо?
Защото ISO 32000 не поставя задължение на производителя да верифицира покритието и глиф индекс нула е легитимен глиф; Той е .notdef, чиято контурна линия дизайнерът на шрифта избира: обикновено празен или кух правоъгълник, понякога нищо изобщо; Viewer, който го рисува, се държи правилно; Извличането на текст може дори да върне правилните символи, защото /ToUnicode съпоставянето е записано от source текста, а не от контурите, така че автоматична round-trip проверка с удоволствие ще мине документ, чийто видим текст има дупки
Практическата последица е, че покритието трябва да се провери в момента на рисуване, когато библиотеката все още знае кой кодов пункт е бил поиспан и кой глиф шрифтът действително е предложил; След това информацията е изгубена
Детекторът трябва да гледа subset състоянието, не device context-а
Това е мястото, където първата имплементация се обърка и причината си заслужава да се разбере, защото се отнася за всяка проверка на покритие, прикрепена към текстов конвейер; HotPDF има два текстови пътя; Единият излъчва през регистриран Unicode TrueType шрифт с in-memory символна карта, изградена в момента на регистрация; Другият е legacy GDI път, който създава пресен device context и font handle на символен run
Съдене на покритието от GDI пътя е безнадеждно; Неговото съпоставяне не е съпоставянето, което завършва в излъчения content поток и двете не са синхронизирани, така че детектор, четещ GDI резултати, докладва целия printable ASCII диапазон като неразрешен; Авторитетният отговор живее в регистрирания шрифт: символната карта, която RegisterUnicodeTTF парсва, запитвана през GetUnicodeGlyphForCodepoint; Детекторът следователно е ограден на subset-ready състоянието, не на каквото и да е GDI условие и той просто не тича върху документи, които никога не са регистрирали Unicode шрифт, което е правилно, защото тези документи и така са ограничени до стандартните кодировки
Втори капан седи до него; GDI фамилното име на шрифт и PostScript името, извлечено от шрифтовия двоичен файл при регистрация, са различни низове и не по начин, който можете да нормализирате: фамилия, казваща се Arial Unicode MS, носи PostScript името ArialMT; Всяка порта, написана като „дали текущо избраният шрифт е онзи, който регистрирахме“, сравнявана по име, е мъртъв код, който никога не изстрелва; Ограждайте по състояние, никога по шрифтови имена
Не тествайте глиф детектор с emoji
Очевидният тестов случай е усмихнато лице и той ще ви убеди, че детекторът е счупен; Честни emoji кодови пунктове в астралните равнини резолвират през private-use синтезен път, който ги съпоставя директно към глиф индекс, така че те никога не достигат общия клон за покритие; Детекторът се държи правилно и тестът измерва грешния път
Използвайте вместо това неназован кодов пункт; U+0378 е трайно неразпределен в Unicode, така че никой шрифт не може легитимно да го съпостави и той упражнява точно клона, който искате да верифицирате; Това разграничение между „функцията е счупена“ и „тестът избра вход, който заобикаля функцията“ струва реални часове и неназованите кодови пунктове са най-евтиният начин да го избегнете
type
TCoverageAudit = class
private
FFindings: TStringList;
public
procedure Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
property Findings: TStringList read FFindings;
end;
procedure TCoverageAudit.Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
begin
// Изстрелва веднъж на уникален кодов пункт, не веднъж на срещане
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Свързване в генерираща задача
Pdf := THotPDF.Create(nil);
try
Pdf.TrackUnresolvedGlyphs := True;
Pdf.OnUnresolvedGlyph := Audit.Handle;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
Pdf.EndDoc;
if Audit.Findings.Count > 0 then
// Провалете задачата, вместо да доставите страница с кутии върху нея
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Fallback веригите са за скрипт, не за шрифт
Причината fallback да е обхватен по скрипт, а не по source шрифт, е, че дупките в покритието се групират по писменост; Латински текстов шрифт липсва Devanagari, Thai, Han и emoji, всичко наведнъж и заместникът за всеки е различен шрифт; Декларирането на една верига за скрипт следователно описва реалното внедряване: един латински шрифт за основен текст, един CJK шрифт, един emoji шрифт, един catch-all
// THPDFFontScript covers hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji and hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);
Fallback и откриването са комплементарни, а не алтернативни; Веригите обработват покритието, което сте предвидили; детекторът докладва покритието, което не сте, което върху система, обработваща произволни клиентски данни, е интересната половина; Обърнете внимание, че замяната на шрифт променя метриките, така че абзац, който падне обратно, може да пренареди; ако оформлението има значение, поведението на затваряне и subsetting на замениящия шрифт си заслужава да прочетете в статията за font subset closure, а скриптове, които се нуждаят от пренареждане или свързване, се обработват от shaping етапа, описан в complex script текстово оформяне
Как да добавите поведение ретроспективно, без да рискувате съществуващия път
Същото издание добави legacy kern таблица fallback за двойно разстояние и начинът, по който беше обхватен, е образец, който си заслужава да се копира; Вместо добавяне на нова точка на решение към kerning логиката, fallback-ът виси от early-exit клона, който вече съществуваше за шрифтове без GPOS таблица; Модерен шрифт с GPOS никога не го достига, така че неговото поведение е непроменено по конструкция, а не по тестване; Пътища, които не регистрират Unicode шрифт, произвеждат две нулеви отмествания, така че те също са непроменени
Това е общата форма на нискорисков retrofit в зряла rendering библиотека: намерете клона, който в момента не произвежда нищо и сложете новото поведение там; Това превръща „вярваме, че това не регресира нищо“ в „това не може да е регресирало нищо“, което е много по-добро нещо за казване върху текстов engine, през който минават чужди фактури
Направете го порта, не лог
Находките за покритие са полезни само ако нещо се проваля върху тях; В услуга за генериране на документи продуктивната уредба е tracking-ът да остане включен в нощната regression задача срещу корпус от реални клиентски имена, адреси и описания на продукти и задачата да се провали при всяка находка; Понеже събитието изстрелва веднъж на уникален кодов пункт, а не веднъж на срещане, изходът остава достатъчно малък за четене, дори целият скрипт да липсва
В продукция същият handler е по-добре да се използва като telemetry: запишете кодовия пункт и шрифта, продължете да сервирате документа и нека агрегатът ви каже кой скрипт да добавите следващия към deployment шрифтовия набор; Rendering поведението за вградени и замени шрифтове е разгледано допълнително в изобразяване на вградени шрифтови глифи, а пълният списък със свойства, включително TrackUnresolvedGlyphs, е документиран на продуктовата страница HotPDF Delphi PDF component