Műszaki cikk

Emoji fontok PDF-ben: COLR v1, SVG és bitmap Delphiben

A HotPDF a THotPDF.DrawRegisteredColorGlyph-ön keresztül rajzol színes emojikat PDF-be, ami egy RegisterUnicodeTTF-fel regisztrált font színadatait olvassa, és natív PDF-grafikaként bocsátja ki: COLR v0 rétegek kitöltött glyph körvonalakként, COLR v1 paint gráfok clipként, shadingekként és blend módokként, SVG glyphok Form XObjectként, CBDT vagy sbix bitmapok pedig képként. Amit nem tud natívan leképezni, az a OnColorGlyphRasterize eseményre megy ahelyett, hogy csendben fekete foltot kapna

Ez az utolsó kitétel az egész oka annak, hogy ez a kód létezik. Egy emoji fontot a szokásos módon beágyazva a megjelenítő a körvonalat a glyf-ből vagy a CFF-ből kapja, kitöltve azzal, amivel épp az aktuális kitöltőszín szokott lenni. A mosolygó arc fekete foltként érkezik, a zászló téglalapként, és a pipeline semmi része nem panaszkodik

Miért nyomtatódik egy színes emoji fekete sziluettnek PDF-ben?

Egy PDF fontprogram nem tud a színes glyphokról. Az ISO 32000-1 a glyphot aktuális színnel festett alaknak tekinti, és az OpenType később hozzáadott színtáblái, nevezetesen a COLR/CPAL, SVG , CBDT/CBLC és sbix, nem részei a PDF képalkotási modelljének, így egyetlen megjelenítő sem köteles őket beolvasni egy beágyazott fontból. A színt generálási időben kell oldaltartalommá fordítani, amíg a producernek még megvannak a font bájtjai, és tudja, melyik glyphot akarja. Ez a fordítás formátumonként más, és a vadon élő emoji fontok mindet használják: rétegezett vektorok, gradiens paint gráfok, beágyazott SVG dokumentumok és PNG strike-ok. A HotPDF az eredményt THPDFOpenTypeColorFormat-ként jelenti, otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG és otcfSBIX értékekkel, és fix prioritás szerint vizsgálja a fontot: először COLR, aztán SVG, aztán CBDT, végül sbix. A vektoradat bitmaphoz képest nyer, valahányszor a font mindkettőt hordozza, ami pont az, amit egy nagyítható vagy nyomtatott dokumentumban akarsz

HotPDF színes glyph-vizsgálat ábra: egy PDF fontprogram a glyph körvonalait az aktuális színnel festi, így az OpenType színtáblákat, a COLR-t, az SVG-t, a CBDT-t és az sbix-et generálási időben kell oldaltartalommá fordítani, és a HotPDF rögzített prioritásban vizsgálja a regisztrált fontot, először COLR, aztán SVG, aztán CBDT, végül sbix sorrendben, THPDFOpenTypeColorFormat-ot jelentve az otcfCOLRv0-tól az otcfSBIX-ig
A vektoradat bitmaphoz képest nyer, valahányszor a font mindkettőt hordozza, ami pont az, amit egy nagyítható vagy nyomtatott dokumentumban akarsz, és egy színút nélküli glyph a te tartalékodra marad

Egy hívás, öt formátum: színes glyph feloldása és kirajzolása

A THotPDF.GetRegisteredColorGlyphInfo megválaszolja, melyik útvonalat veszi egy kódpont, a DrawRegisteredColorGlyph pedig be is váltja. Mindkettő a legutóbb a RegisterUnicodeTTF-nek átadott font karaktertérképében keresi a kódpontot, tehát a hívás pillanatában a színes fontnak kell a regisztrált Unicode fontnak lennie. A rajzoló függvény False-t ad vissza, amikor a glyphnak nincs színadata, vagy egyetlen útvonal sem tudta renderelni, és a tartalék rád marad

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, 0-s CPAL paletta, a 300 ppemhez legközelebbi bitmap strike
    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
      // Nincs színadat: térj vissza a monokróm körvonalra
      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;

Két paraméter érdemel figyelmet. A PaletteIndex CPAL palettát választ, így egy sötét háttérű palettát szállító font váltható anélkül, hogy a glyphhoz nyúlnál. A TargetPixelsPerEm csak bitmap fontoknál számít; nullán hagyva a Round(FontSize * 96 / 72) az alapértéke, egy képernyőfelbontás, ezért kéri a példa a 300-at nyomtatott kimenethez. Az őszinte korlát a szignatúrában ül: a hívás egyetlen kódpontot vesz fel, és egyedül a cmap-en át képezi le. A ZWJ szekvenciák, a bőrtónus-módosítók és a regionális indikátor zászlók GSUB ligatúrák, így az összerakásuk shaping probléma, olyan fajta, amit a OpenType GSUB alternatívákról szóló cikk tárgyal, nem pedig valami, amit ez a belépési pont elintéz helyetted

COLR v0: egymásra rakott glyph rétegek palettaszínekkel

A COLR v0 az egyszerű eset, és a HotPDF közvetlenül rendereli: minden bázis glyph kilistázza a réteg glyphokat egy CPAL színbejegyzéssel, és minden réteg egyetlen közönséges szövegmegjelenítő műveletté válik a saját kitöltőszínével, táblarendben egymásra rakva. Egy 255 alatti alfájú réteg graphics state paraméterszótárat kap egyező /ca és /CA értékkel (ISO 32000-1 §8.4.5), minden réteg glyph pedig használtként jelölődik, hogy a subsetter megtartsa a körvonalát, hiába nem képződik rá közvetlenül kódpont. Egy részlet meglep: a 0xFFFF palettabejegyzés-index „használd a szöveg előtérszínét" jelent az OpenType specifikációban, és a HotPDF feketére oldja fel, nem az aktuális oldal kitöltőszínére. Emoji fontoknál ez ritkán számít; olyan icon fontoknál, amik az előtér bejegyzésre támaszkodnak egy glyph színezéséhez, ellenőrizd a kimenetet, mielőtt azt feltételezed, hogy a szövegszínedet fogja követni

Hogyan fordítja le a HotPDF a COLR v1 paint gráfot PDF operátorokká?

Úgy, hogy előbb lapos, határolt gráfba parszolja a paint táblákat, és csak aztán képezi le az egyes csomópontokat PDF konstrukciókra. Egy COLR v1 glyph nem rétegek listája, hanem paint rekordok irányított aciklikus gráfja, ahol a csomópontok megoszthatók a PaintColrLayers és a PaintColrGlyph révén. A parser 4096 paint csomópontra, 64 mélységi szintre és 1024 színmegállóra szabja határát, és minden csomópontot aktívként vagy készteként követ, így egy aktív csomópontra visszautaló hivatkozás — egy ciklus, amit egy rosszindulatú font rétegekből építhet — elutasításra kerül rekurzió helyett. Az offszetbázisok azok, ahol egy első implementáció elront. A BaseGlyphPaintRecord offszetek a BaseGlyphList elejéhez viszonyulnak, a LayerList paint offszetek a LayerList-hez, és minden Offset24 a paint táblán belül magához a paint táblához viszonyul. A hármat ugyanarra a bázisra oldva fel a tökéletesen legális glyphok megbuknak a határellenőrzésen, ami pontúgy néz ki, mint egy sérült font. Amint a gráf felépült, a leképezés egyenes:

  • A PaintGlyph a glyph körvonalát clipként állítja 7-es szövegrenderelési módban (ISO 32000-1 §9.3.6), majd a gyerekét festi bele
  • A szilárd festések egy clipelt téglalapot töltenek ki; a lineáris gradiensek többszakaszos axiális shadingekké, a radiális gradiensek kétszínű radiális shadingekké válnak (§8.7.4.5)
  • A sweep gradienseknek nincs PDF megfelelője, így a HotPDF 96 simaszínű körcikkel közelíti őket, mindegyiket a színvonalról mintavételezve
  • A transzformációk cm-ként kerülnek ki, a glyph bázisvonal-origója körül konjugálva, a transzlációk a FontSize / UnitsPerEm-nel skálázva
  • A PaintComposite 13-tól 27-ig terjedő módjai a szétválasztható és szétválaszthatatlan PDF blend módokra képeződnek le, mint a /Multiply, /Screen és /Luminosity (§11.3.5), ExtGState /BM bejegyzésen át beállítva

A határ explicit. A Porter-Duff 5-től 12-ig terjedő módjainak (src_in, xor, plus és a többi) nincs PDF blend mód megfelelője, a repeat és reflect kiterjesztési módok lineáris és radiális gradienseken nem kerülnek kiadásra, és azok a gradiensek, amiknek megállói eltérő alfaértékeket hordoznak, nem lesznek egyetlen opacity-vel meghazudtolva. A kettőnél több megállós radiális gradiensek csak az első és az utolsó színüket tartják meg. A HotPDF az egész gráfot e támogatott részhalmazra ellenőrzi, mielőtt egyetlen operátort kiírna, így egy nem támogatott glyph érintetlenül hagyja az oldalt, és továbblép a raszteres tartalékra ahelyett, hogy fél rajzot hagyna maga után

HotPDF COLR v1 konverziós ábra: a paint gráf határolt gráfba parszolódik, 4096 csomópontra, 64 mélységi szintre és 1024 színmegállóra szabott határral, ciklusok elutasításával, majd a PaintGlyph 7-es módú clippé válik, a lineáris és radiális gradiensek axiális és radiális shadingekké, a sweep gradiensek 96 körcikké, a PaintComposite 13-tól 27-ig terjedő módjai pedig PDF blend módokká
Az egész gráfot a támogatott részhalmazra ellenőrzik, mielőtt az első operátor kiíródna, így egy nem támogatott glyph érintetlenül hagyja az oldalt, és továbblép a raszteres tartalékra ahelyett, hogy fél rajzot hagyna maga után

SVG glyphok és bitmap strike-ok

Az SVG glyphok ugyanazon a határolt builderen mennek át, amit a HotPDF az importált SVG fájlokhoz használ, és az eredmény Form XObjectként regisztrálódik (§8.10), pontosanúgy, ahogy azt a SVG-ből Form XObject cikk leírja. Az SVG táblában lévő dokumentum gzip-tömörített lehet; a kicsomagolás 8 KB-os darabokban fut, és abban a pillanatban megáll, amikor a kibontott méret átlépné a 32 MB-ot, ahelyett hogy előbb felfújná és aztán ellenőrizne, maga a tömörített bemenet pedig 8 MB-ra van szabva. A profil szándékosan szűkítő: szkriptek, beágyazott képek, külső URL-ek, data: URI-k és nem lokális hivatkozások elutasítással buknak. A forma úgy skálázódik, hogy a hosszabb oldala egyenlő a fontmérettel, és a bázisvonalon horgonyzik, ami az y-lefelé SVG koordinátarendszert az y-felfelé PDF-re képezi le. Tudd, hogy a builder megkapja a glyph teljes SVG dokumentumát, a glyphNNN elem kiválasztása nélkül, így azok a fontok, amik sok glyphot raknak egy megosztott dokumentumba, megérnek egy tesztet, mielőtt rájuk támaszkodnál

A bitmap fontok a strike-választás és az elhelyezés kérdése. CBDT-nél a HotPDF azt a CBLC méretet választja, aminek a vertikális ppem-je a legközelebb van a TargetPixelsPerEm-hez, elfogadja a 17-es, 18-as és 19-es képformátumokat, és a 19-es formátum metrikáit a CBLC index altáblájából olvassa, mert az a formátum semmit sem tárol a sajátjából. sbix-nél a strike offszetek a táblához viszonyulnak, a glyph offszetek a strike-hoz, és egy dupe rekord újrahasznosítja egy másik glyph grafikáját, miközben megtartja a saját origó offszetjeit; ha a rekurzió felülírja a külső origót, a kép eltolódik. A PNG és JPEG hasznos terhek belül dekódolódnak, a FontSize / PixelsPerEmY szerint skálázódnak, nem a fontméretre nyújtódnak, és soft maszkkal íródnak (§11.6.5.3), valahányszor egyetlen pixel sem teljesen fedetlen. Az sbix TIFF hasznos terhek nem dekódolódnak, és az eseményre mennek

Mi történik, amikor egy glyph nem rajzolható natívan?

A HotPDF OnColorGlyphRasterize-t vált, és elhelyezi, amit a te handered RGBA bitmapként visszaad; ha nincs hozzárendelve semmi, vagy a hander hamisan hagyja a Handled-t, a DrawRegisteredColorGlyph False-t ad vissza, és az oldal változatlan marad. Az esemény elsül egy nem támogatott részhalmazon kívüli COLR v1 gráfra, egy biztonságos builder által elutasított SVG dokumentumra és egy belső dekóder által olvasatlan bitmap hasznos teherre. A hander megkapja a formátumot, a nyers font bájtokat, a kinyert assetet (az SVG dokumentumot, esetleg még gzipelt állapotban, vagy a bitmap bájtokat; COLR v1-nél üresen), a glyph ID-t, a palettát és a cél pixelméretet

HotPDF raszteres tartalék ábra: az OnColorGlyphRasterize elsül egy nem támogatott részhalmazon kívüli COLR v1 gráfra, egy biztonságos builder által elutasított SVG dokumentumra vagy egy dekóderek által olvasatlan bitmap hasznos teherre, átadva a formátumot, a font bájtokat, az assetet, a GlyphID-t, a PaletteIndex-et és a PixelSize-t, és a visszaadott RGBA buffer csak akkor fogadódik el, ha a hossza pontosan Width szorozva Height szorozva 4
Nulla méretek, rossz bufferhossz vagy túlcsorduló dimenziók elutasításra kerülnek, mielőtt az oldalhoz nyúlnának, és handler nélkül vagy hamis Handled-del a hívás False-t ad vissza, az oldal változatlan marad
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;
  // A RenderWithOwnEngine a te raszterelőd, nem HotPDF API.
  // Pontosan Width * Height * 4 bájt RGBA-t kell visszaadnia.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

// Bekötés
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;

A HotPDF validálja a hander kimenetét, mielőtt az oldalhoz nyúlna: nulla méretek, olyan buffer, aminek a hossza nem pontosan Width * Height * 4, vagy túlcsordulásra elég nagy dimenziók elutasításra kerülnek, és a hívás False-t ad vissza. Egy raszteres tartalék még mindig raszter, így az így renderelt emoji elveszíti a vektoros élességét; olyan PixelSize-t kérj, ami egyezik a kimeneti felbontásoddal. Párosítsd a színutat a hiányzó glyph követéséről szóló cikk rajzolásidőbeli lefedettség-ellenőrzéseivel, és egy tetszőleges felhasználói szöveget kezelő pipeline egyszerre tud jelenteni hiányzó glyphokat és színüket vesztett glyphokat

A színes glyph renderelő, az OpenType shaping stack és a biztonságos SVG builder mind a HotPDF Delphi PDF komponensben érkezik, elérhető Delphihez és C++Builderhez