HotPDF рисува цветни emoji в PDF чрез THotPDF.DrawRegisteredColorGlyph, който чете цветовите данни на шрифт, регистриран с RegisterUnicodeTTF, и ги излъчва като нативна PDF графика: COLR v0 слоеве като запълнени glyph контури, COLR v1 paint графи като клипове, shadings и blend режими, SVG глифове като Form XObjects, а CBDT или sbix битмапи като изображения. Каквото не може да се мапне нативно, отива на събитието OnColorGlyphRasterize, вместо тихо да се превърне в черна форма
Последната крачка е цялата причина този код да съществува. Вградете emoji шрифт по обичайния начин и viewer-ът получава контура от glyf или CFF, запълнен с какъвто и да е текущият fill цвят. Усмихнатото лице пристига като черно петно, знамето като правоъгълник, а нищо в линията не се оплаква
Защо цветно emoji излиза като черен силует в PDF?
PDF font програма няма понятие за цветни глифове. ISO 32000-1 третира глифа като форма, рисувана с текущия цвят, а цветовите таблици, които OpenType добави по-късно — COLR/CPAL, SVG , CBDT/CBLC и sbix — не са част от PDF imaging модела, така че нито един viewer не е длъжен да ги чете от вграден шрифт. Цветът трябва да се преведе в съдържание на страницата още при генерирането, докато producer-ът още държи байтовете на шрифта и знае кой глиф иска. Този превод е различен за всеки формат, а emoji шрифтовете в дивото ползват всички: наслоени вектори, paint графи с градиенти, вградени SVG документи и PNG strikes. HotPDF докладва резултата като THPDFOpenTypeColorFormat, със стойности otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG и otcfSBIX, и пробва шрифта в фиксиран приоритет: първо COLR, после SVG, после CBDT, после sbix. Векторните данни бият битмапите, щом шрифтът носи и двете, което е точно това, което искате в документ, който може да бъде увеличаван или печатан
Едно извикване, пет формата: разрешаване и рисуване на цветен глиф
THotPDF.GetRegisteredColorGlyphInfo отговаря кой път ще вземе една кодова точка, а DrawRegisteredColorGlyph го поема. И двете търсят кодовата точка в character map-а на шрифта, подаден най-скоро на RegisterUnicodeTTF, така че цветният шрифт трябва да е регистрираният Unicode шрифт в момента на извикването. Draw функцията връща False, когато глифът няма цветни данни или никой път не може да го изрендира, и оставя fallback-а на вас
const
FormatNames: array[THPDFOpenTypeColorFormat] of string =
('none', 'COLR v0', 'COLR v1', 'CBDT', 'SVG', 'sbix');
var
Pdf: THotPDF;
Info: THPDFOpenTypeColorGlyphInfo;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'emoji.pdf';
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\seguiemj.ttf');
// U+1F600, CPAL палитра 0, битмап strike най-близо до 300 ppem
if Pdf.GetRegisteredColorGlyphInfo($1F600, 0, 300, Info) then
Writeln(Format('GID %d via %s',
[Info.GlyphID, FormatNames[Info.Format]]));
if not Pdf.DrawRegisteredColorGlyph(Pdf.CurrentPage, $1F600,
72, 144, 'Segoe UI Emoji', 36, 0, 300) then
begin
// Няма цветни данни: връщане към монохромния контур
Pdf.CurrentPage.SetFont('Segoe UI Emoji', [], 36, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 144, 0, WideString(#$D83D#$DE00));
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Два параметъра заслужават внимание. PaletteIndex избира CPAL палитра, така че шрифт с палитра за тъмен фон може да се смени, без да се пипа глифът. TargetPixelsPerEm има значение само за битмап шрифтове; оставен на нула, подразбира Round(FontSize * 96 / 72) — екранна резолюция, което е причината примерът да иска 300 за печатен изход. Честната граница седи в сигнатурата: извикването приема една кодова точка и я мапва само през cmap. ZWJ последователности, skin-tone модификатори и regional-indicator знамена са GSUB лигатури, така че сглобяването им е shaping проблем от рода, покрит в статията за OpenType GSUB алтернативи, а не нещо, което този входен пункт върши вместо вас
COLR v0: наслагвани glyph слоеве с палитрени цветове
COLR v0 е простият случай и HotPDF го рендира директно: всеки базов глиф изброява layer глифове с CPAL цветови запис, а всеки слой става една обикновена операция за показване на текст със собствен fill цвят, наслагвана в реда на таблицата. Слой с алфа под 255 получава речник с параметри на graphics state със съответстващи /ca и /CA (ISO 32000-1 §8.4.5), а всеки layer глиф се маркира като ползван, така че subsetter-ът пази контура му, макар никоя кодова точка да не сочи него директно. Една подробност изненадва хората: палитреният индекс 0xFFFF значи „ползвай цвета на текста отпред" в OpenType спецификацията, а HotPDF го разрешава на черно, а не на текущия fill цвят на страницата. За emoji шрифтове това рядко има значение; за icon шрифтове, разчитащи на foreground записа, за да оцветят глиф, проверете изхода, преди да предполагате, че ще следва цвета на текста ви
Как HotPDF превръща COLR v1 paint графика в PDF оператори?
Като първо парсва paint таблиците в плоска, ограничена графика и чак после мапва всеки възел в PDF конструкция. COLR v1 глиф не е списък от слоеве, а насочен ацикличен граф от paint записи, при който възли могат да се споделят през PaintColrLayers и PaintColrGlyph. Parser-ът го капва на 4096 paint възела, 64 нива дълбочина и 1024 color stop-а, и следи всеки възел като активен или свършен, така че референция обратно към активен възел — цикъл, който злонамерен шрифт може да построи от преизползване на слоеве — се отхвърля, вместо да се рекурсира в него. Offset базите са мястото, където първата имплементация се проваля. BaseGlyphPaintRecord offset-ите са спрямо началото на BaseGlyphList, paint offset-ите на LayerList са спрямо LayerList, а всеки Offset24 вътре в paint таблица е спрямо самата тази paint таблица. Разрешите ли и трите срещу една и съща база, напълно легални глифове падат на bounds проверката, което изглежда точно като развален шрифт. Щом графиката е изградена, мапването е директно:
PaintGlyphзадава glyph контура като клип с text rendering режим 7 (ISO 32000-1 §9.3.6), после рисува своето дете вътре в него- Solid paint-ове запълват отрязан правоъгълник; линейните градиенти стават многостъпкови axial shadings, а радиалните градиенти — двуцветни радиални shadings (§8.7.4.5)
- Sweep градиентите нямат PDF еквивалент, затова HotPDF ги приближава с 96 клина с плътен цвят, всеки семплиран от цветовата линия
- Трансформациите се излъчват като
cm, конюгирани около baseline произхода на глифа, с транслации, мащабирани поFontSize / UnitsPerEm PaintCompositeрежими 13 до 27 се мапват към разделимите и неразделимите PDF blend режими като/Multiply,/Screenи/Luminosity(§11.3.5), задавани през ExtGState запис/BM
Границата е изрична. Porter-Duff режимите 5 до 12 (src_in, xor, plus и останалите) нямат PDF blend-mode съответствие, repeat и reflect extend режимите на линейни и радиални градиенти не се излъчват, а градиенти, чиито stop-ове носят различни алфа стойности, не се фалшифицират с единична плътност. Радиални градиенти с повече от два stop-а пазят само първия и последния си цвят. HotPDF проверява цялата графика срещу този поддържан поднабор, преди да напише един-единствен оператор, така че неподдържан глиф оставя страницата недокосната и минава към растерния fallback, вместо да остави половин рисунка след себе си
SVG глифове и битмап strikes
SVG глифовете минават през същия ограничен builder, който HotPDF ползва за импортирани SVG файлове, а резултатът се регистрира като Form XObject (§8.10), точно както е описано в статията за SVG към Form XObject. Документът в таблицата SVG може да е gzip-компресиран; декомпресията върви на парчета по 8 KB и спира веднага щом разширен размер би минал 32 MB, вместо първо да се надува и после да се проверява, а самият компресиран вход е капиран на 8 MB. Профилът е рестриктивен нарочно: скриптове, вградени изображения, външни URL-и, data: URI-и и нелокални референции провалят затворено. Формата се мащабира така, че по-дългата ѝ страна да равнява размера на шрифта, и се закотвя на baseline-а, което мапва y-надолу SVG координатната система върху y-нагоре PDF такава. Имайте предвид, че builder-ът получава целия SVG документ за глифа, без селекция на елемента glyphNNN, така че шрифтове, опаковащи много глифове в един споделен документ, си заслужават тестване, преди да им се доверите
Битмап шрифтовете са въпрос за избор на strike и поставяне. За CBDT HotPDF избира CBLC размера, чийто вертикален ppem е най-близо до TargetPixelsPerEm, приема image формати 17, 18 и 19, и чете метриките на формат 19 от CBLC index subtable-а, защото този формат не пази свои собствени. За sbix strike offset-ите са спрямо таблицата, а glyph offset-ите — спрямо strike-а, а запис dupe преизползва графиката на друг глиф, пазейки собствените си origin отмествания; ако рекурсията презапише външния origin, изображението се измества. PNG и JPEG payload-и се декодират вътрешно, мащабират се по FontSize / PixelsPerEmY, вместо да се разтегнат до размера на шрифта, и се записват със soft mask (§11.6.5.3), щом някой пиксел не е напълно непрозрачен. sbix TIFF payload-и не се декодират и отиват на събитието
Какво става, когато глиф не може да се нарисува нативно?
HotPDF вдига OnColorGlyphRasterize и поставя какъвто RGBA битмап върне вашият handler; ако нищо не е закачено, или handler-ът остави Handled false, DrawRegisteredColorGlyph връща False и страницата си остава непроменена. Събитието се включва за COLR v1 графика извън поддържания поднабор, за SVG документ, който safe builder-ът е отказал, и за битмап payload, който вътрешните декодери не четат. Handler-ът получава формата, суровите шрифтови байтове, извлечения asset (SVG документът, евентуално още gzipped, или битмап байтовете; празно за COLR v1), glyph ID-то, палитрата и целевия пикселен размер
type
TEmojiFallback = class
public
procedure Rasterize(Sender: TObject;
Format: THPDFOpenTypeColorFormat; const FontBytes: TBytes;
const AssetData: TBytes; GlyphID: Word;
PaletteIndex, PixelSize: Integer;
out Width, Height: Integer; out RGBA: TBytes;
out Handled: Boolean);
end;
procedure TEmojiFallback.Rasterize(Sender: TObject;
Format: THPDFOpenTypeColorFormat; const FontBytes: TBytes;
const AssetData: TBytes; GlyphID: Word;
PaletteIndex, PixelSize: Integer;
out Width, Height: Integer; out RGBA: TBytes;
out Handled: Boolean);
begin
Width := 0;
Height := 0;
RGBA := nil;
// RenderWithOwnEngine е вашият растеризатор, а не HotPDF API.
// Трябва да върне точно Width * Height * 4 байта RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Свързване
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF валидира изхода на handler-а, преди да пипне страницата: нулеви размери, буфер с дължина, различна от точно Width * Height * 4, или размери, достатъчно големи да прелеят, се отхвърлят и извикването връща False. Растерният fallback пак е растр, така че emoji, изрендериран така, губи векторната си острота; искайте PixelSize, съответстващ на изходната ви резолюция. Сдвоете цветния път с проверки за покритие при рисуване от статията за проследяване на липсващи глифове и линия, обработваща произволен потребителски текст, може да докладва и липсващи глифове, и глифове, изгубили цвета си
Рендерът за цветни глифове, OpenType shaping стекът и safe SVG builder-ът излизат всичките в HotPDF Delphi PDF компонента, наличен за Delphi и C++Builder