Odborný článok

Color emoji fonty v PDF: COLR v1, SVG a bitmapy v Delphi

HotPDF kreslí color emoji do PDF cez THotPDF.DrawRegisteredColorGlyph, ktorý prečíta color dáta fontu zaregistrovaného cez RegisterUnicodeTTF a vypustí ich ako natívne PDF grafiku: vrstvy COLR v0 ako vyplnené obrysy glyfov, paint grafy COLR v1 ako clipy, shadingy a blend módy, SVG glyfy ako Form XObject a CBDT či sbix bitmapy ako obrázky. Čokoľvek nedokáže namapovať natívne, ide do eventu OnColorGlyphRasterize namiesto tichého premenenia na čierny tvar

Tá posledná klauzula je celý dôvod, prečo ten kód existuje. Vložte emoji font obyčajným spôsobom a prehliadač dostane obrys z glyf alebo CFF, vyplnený tým, aká aktuálna farba výplne sa náhodou nastavila. Usmiate tvár dorazí ako čierna škvrna, vlajka ako obdĺžnik a nič v pipeline si nesťažuje

Prečo sa color emoji vytlačí v PDF ako čierna silueta?

PDF font program nemá pojem color glyf. ISO 32000-1 berie glyf ako tvar natieraný aktuálnou farbou a color tabuľky, ktoré OpenType pridal neskôr, teda COLR/CPAL, SVG , CBDT/CBLC a sbix, nie sú súčasťou PDF imaging modelu, takže žiadny prehliadač nie je povinný ich čítať z vloženého fontu. Farba sa musí preložiť do obsahu strany už pri generovaní, kým producent ešte drží bajty fontu a vie, ktorý glyf chce. Tento preklad je pre každý formát iný a emoji fonty vo voľnej prírode používajú všetky: vrstvené vektory, gradient paint grafy, vložené SVG dokumenty a PNG striky. HotPDF hlási výsledok ako THPDFOpenTypeColorFormat s hodnotami otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG a otcfSBIX a skúma font v fixnej priorite: najprv COLR, potom SVG, potom CBDT, potom sbix. Vektorové dáta vyhrávajú nad bitmapami vždy, keď font nesie oboje, čo je presne to, čo chcete v dokumente, ktorý sa môže zväčšovať alebo tlačiť

Diagram color glyf probe v HotPDF: PDF font program natiera obrysy glyfov aktuálnou farbou, takže OpenType color tabuľky COLR, SVG, CBDT a sbix sa musia preložiť do obsahu strany pri generovaní a HotPDF skúma zaregistrovaný font v fixnej priorite COLR, potom SVG, potom CBDT, potom sbix, s hlásením THPDFOpenTypeColorFormat od otcfCOLRv0 po otcfSBIX
Vektorové dáta vyhrávajú nad bitmapami vždy, keď font nesie oboje, čo je presne to, čo chcete v dokumente, ktorý sa môže zväčšovať alebo tlačiť, a glyf bez color cesty sa nechá vášmu fallbacku

Jedno volanie, päť formátov: vyriešenie a nakreslenie color glyfu

THotPDF.GetRegisteredColorGlyphInfo odpovie, ktorou cestou pôjde kódový bod, a DrawRegisteredColorGlyph ju vezme. Oba hľadajú kódový bod v character mape fontu, ktorý sa naposledy podal do RegisterUnicodeTTF, takže color font musí byť v momente volania zaregistrovaným Unicode fontom. Draw funkcia vráti False, keď glyf nemá color dáta alebo žiadna cesta ho nedokázala vykresliť, a fallback necháva na vás

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 paleta 0, bitmap strike najbližšie k 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
      // Žiadne color dáta: spadnite na monochromatický obrys
      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;

Dva parametre zasluhujú pozornosť. PaletteIndex vyberá CPAL paletu, takže font, ktorý dodáva paletu pre tmavé pozadie, sa prepne bez dotyku glyfu. TargetPixelsPerEm má význam len pri bitmapových fontoch; na nule sa predvolí Round(FontSize * 96 / 72), rozlíšenie obrazovky, a preto si príklad žiada 300 pre tlačový výstup. Poctivý limit sedí v signatúre: volanie berie jeden kódový bod a mapuje ho len cez cmap. ZWJ sekvencie, modifikátory odtieňa pleti a vlajky z regional indicators sú GSUB ligatúry, takže ich skladanie je shaping problém druhu popísaného v článku o alternatívach OpenType GSUB, nie niečo, čo tento vstupný bod urobí za vás

COLR v0: vrstvené vrstvy glyfov s farbami palety

COLR v0 je jednoduchý prípad a HotPDF ho renderuje priamo: každý base glyf listuje layer glyfmi s farbovou položkou CPAL a každá vrstva sa stane jednou obyčajnou text-showing operáciou s vlastnou farbou výplne, vrstvenou v poradí tabuľky. Vrstva s alfa pod 255 dostane slovník parametrov grafického stavu so zodpovedajúcimi /ca a /CA (ISO 32000-1 §8.4.5) a každý layer glyf sa označí ako použitý, takže subsetter si nechá jeho obrys, aj keď sa naň priamo nemapuje žiadny kódový bod. Jeden detail prekvapuje: index položky palety 0xFFFF znamená v špecifikácii OpenType „použi farbu popredia textu" a HotPDF ho vyrieši na čiernu, nie na aktuálnu farbu výplne strany. Pre emoji fonty to zriedka hrá rolu; pri icon fontoch, ktoré sa spoliehajú na položku popredia pri tónovaní glyfu, skontrolujte výstup, skôr než predpokladáte, že bude nasledovať farbu vášho textu

Ako HotPDF premení paint graf COLR v1 na PDF operátory?

Tým, že najprv naparsuje paint tabuľky do plochého, ohraničeného grafu a až potom mapuje každý uzol na PDF konštrukciu. Glyf COLR v1 nie je zoznam vrstiev, ale orientovaný acyklický graf paint záznamov, kde sa uzly môžu zdieľať cez PaintColrLayers a PaintColrGlyph. Parser ho započítava na 4096 paint uzlov, 64 úrovní hĺbky a 1024 color stopov a vedie každý uzol ako aktívny alebo hotový, takže referencia späť na aktívny uzol, cyklus, ktorý zlomyselný font postaví z opätovného použitia vrstiev, sa zamietne namiesto rekurzie do neho. Offsetové bázy sú miesto, kde prvá implementácia zlyhá. Offsety BaseGlyphPaintRecord sú relatívne k začiatku BaseGlyphList, paint offsety LayerList sú relatívne k LayerList a každý Offset24 vnútri paint tabuľky je relatívny k samotnej paint tabuľke. Vyriešte všetky tri proti tej istej báze a dokonale legálne glyfy prepadnú na kontrole hraníc, čo vyzerá presne ako poškodený font. Keď je graf postavený, mapovanie je priame:

  • PaintGlyph nastaví obrys glyfu ako clip s text rendering režimom 7 (ISO 32000-1 §9.3.6) a potom vnútri neho natiera svoje dieťa
  • Solid painty vyplnia oříznutý obdĺžnik; lineárne gradienty sa stanú viacstopovými axiálnymi shadingmi a radiálne gradienty dvojfarebnými radiálnymi shadingmi (§8.7.4.5)
  • Sweep gradienty nemajú PDF ekvivalent, takže HotPDF ich aproximuje 96 plocho zafarbenými výsečami, každú vzorkovanú z color línie
  • Transformácie sa vypúšťajú ako cm, konjugované okolo pôvodu baseline glyfu, s posunmi škálovanými FontSize / UnitsPerEm
  • Režimy PaintComposite 13 až 27 sa mapujú na separovateľné a neseparovateľné PDF blend módy ako /Multiply, /Screen a /Luminosity (§11.3.5), nastavované cez položku /BM ExtGState

Hranica je explicitná. Porter-Duff režimy 5 až 12 (src_in, xor, plus a zvyšok) nemajú PDF counterpart v blend módoch, extend módy repeat a reflect pri lineárnych a radiálnych gradientoch sa nevypúšťajú a gradienty, ktorých stopy nesú rôzne alfa hodnoty, sa nefalšujú jednou nepriehľadnosťou. Radiálne gradienty s viac než dvomi stopmi si nechajú len prvé a posledné farby. HotPDF skontroluje celý graf proti tejto podporovanej podmnožine skôr, než zapíše jediný operátor, takže nepodporovaný glyf nechá stranu nedotknutú a prejde na rastrový fallback namiesto toho, aby nechal napoly nakreslený obrázok

Diagram konverzie COLR v1 v HotPDF: paint graf sa parsuje do ohraničeného grafu započítaného na 4096 uzlov, 64 úrovní hĺbky a 1024 color stopov so zamietnutím cyklov, potom PaintGlyph sa stane clipom režimu 7, lineárne a radiálne gradienty sa stanú axiálnymi a radiálnymi shadingmi, sweep gradienty sa stanú 96 výsečami a režimy PaintComposite 13 až 27 sa stanú PDF blend módmi
Celý graf sa skontroluje proti podporovanej podmnožine skôr, než sa zapíše prvý operátor, takže nepodporovaný glyf nechá stranu nedotknutú a prejde na rastrový fallback namiesto napoly nakresleného obrázku

SVG glyfy a bitmapové striky

SVG glyfy idú cez ten istý ohraničený builder, ktorý HotPDF používa pre importované SVG súbory, a výsledok sa registruje ako Form XObject (§8.10), presne ako popisuje článok o konverzii SVG do Form XObject. Dokument v tabuľke SVG môže byť gzip-komprimovaný; dekomprezia beží v 8 KB blokoch a zastane, akonáhle by rozbalená veľkosť prekročila 32 MB, namiesto nafúknutia napred a kontroly potom, a komprimovaný vstup je sám kapovaný na 8 MB. Profil je účelovo prísny: skripty, vložené obrázky, externé URL, URI data: a nelokálne referencie zlyhajú zatvorené. Form sa škáluje tak, aby jeho dlhšia strana sa rovnala veľkosti fontu, a kotví na baseline, čo mapuje y-dole súradnicový systém SVG na y-hore PDF. Počítajte s tým, že builder dostane celý SVG dokument glyfu, bez výberu elementu glyphNNN, takže fonty, ktoré balia mnoho glyfov do jedného zdieľaného dokumentu, stoja za otestovanie, skôr než sa na ne spoliehate

Bitmapové fonty sú otázka výberu striku a umiestnenia. Pri CBDT HotPDF vyberie veľkosť CBLC, ktorej vertikálne ppem je najbližšie k TargetPixelsPerEm, prijíma image formáty 17, 18 a 19 a číta metriky formátu 19 z indexovej podtabuľky CBLC, lebo ten formát žiadne vlastné neuchováva. Pri sbix sú offsety strikov relatívne k tabuľke a offsety glyfov k striku a záznam dupe znovu používa grafiku iného glyfu a drží si vlastné pôvodné offsety; ak necháte rekurziu prepísať vonkajší pôvod, posuniete obrázok. PNG a JPEG payloady sa dekódujú interne, škálujú sa FontSize / PixelsPerEmY namiesto natiahnutia na veľkosť fontu a zapisujú sa s soft maskou (§11.6.5.3), kedykoľvek niektorý pixel nie je úplne nepriehľadný. sbix TIFF payloady sa nedekódujú a idú do eventu

Čo sa stane, keď glyf nejde nakresliť natívne?

HotPDF spustí OnColorGlyphRasterize a umiestni ľubovoľnú RGBA bitmapu, ktorú váš handler vráti; ak nič nie je priradené, alebo handler nechá Handled false, DrawRegisteredColorGlyph vráti False a strana ostane nezmenená. Event sa spustí pre COLR v1 graf mimo podporovanej podmnožiny, pre SVG dokument, ktorý bezpečný builder odmietol, a pre bitmapový payload, ktorý interné dekodéry nečítajú. Handler dostane formát, surové bajty fontu, extrahovaný asset (SVG dokument, možno stále gzipovaný, alebo bitmapové bajty; prázdne pri COLR v1), ID glyfu, paletu a cieľovú pixelovú veľkosť

Diagram rastrového fallbacku HotPDF: OnColorGlyphRasterize sa spustí pre COLR v1 graf mimo podporovanej podmnožiny, pre SVG dokument, ktorý bezpečný builder odmietol, alebo bitmapový payload, ktorý dekodéry nečítajú, podá formát, bajty fontu, asset, GlyphID, PaletteIndex a PixelSize a vrátený RGBA buffer sa prijme, len keď jeho dĺžka je presne Width krát Height krát 4
Nulové veľkosti, zlá dĺžka bufferu alebo pretékajúce rozmery sa zamietnu skôr, než sa strany niekoho dotkne, a bez handlera alebo s Handled false vráti volanie False a strana ostane nezmenená
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 je váš rasterizer, nie API HotPDF.
  // Musí vrátiť presne Width * Height * 4 bajtov RGBA.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

// Zapojenie
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;

HotPDF validuje výstup handlera skôr, než sa dotkne strany: nulové veľkosti, buffer, ktorého dĺžka nie je presne Width * Height * 4, alebo rozmery dosť veľké na pretečenie sa zamietnu a volanie vráti False. Rastrový fallback je stále raster, takže emoji vykreslené týmto spôsobom stratí vektorovú ostrosť; žiadajte PixelSize zodpovedajúci vašej výstupnej rezolúcii. Spárujte color cestu s kontrolami pokrytia pri kreslení z článku o sledovaní chýbajúcich glyfov a pipeline spracúvajúca ľubovoľný text používateľa dokáže hlásiť jak chýbajúce glyfy, tak glyfy, ktoré stratili farbu

Color glyf renderer, OpenType shaping stack aj bezpečný SVG builder prichádzajú všetky v HotPDF Delphi PDF component, dostupnom pre Delphi a C++Builder