Techninis straipsnis

Spalvoti emoji šriftai PDF: COLR v1, SVG ir bitmaps Delphi

HotPDF spalvotus emoji piešia į PDF per THotPDF.DrawRegisteredColorGlyph, kuris skaito RegisterUnicodeTTF užregistruoto šrifto spalvų duomenis ir išveda juos kaip natyvią PDF grafiką: COLR v0 sluoksnius kaip užpildytas glifų kontūras, COLR v1 paint grafas kaip clips, shadings ir blend režimus, SVG glifus kaip Form XObjects, o CBDT arba sbix bitmaps kaip paveikslėlius. Kas nesugebama atvaizduoti natyviai, keliauja į OnColorGlyphRasterize įvykį, vietoj to, kad tyliai pavirstų juoda forma

Ta paskutinė išlyga – visa priežastis, kodėl šis kodas egzistuoja. Įtaikykite emoji šriftą įprastu būdu, ir peržiūros programa gauna kontūrą iš glyf arba CFF, užpildytą tiesiogine dabartine užpildymo spalva. Šypsena atkeliauja kaip juoda dėmė, vėliava – kaip stačiakampis, ir niekas konvejeryje nesiskundžia

Kodėl spalvotas emoji PDF atspausdina kaip juodą siluetą?

PDF šrifto programa neturi supratimo apie spalvotus glifus. ISO 32000-1 glifą traktuoja kaip formą, nudažytą dabartine spalva, o spalvų lentelės, kurias OpenType pridėjo vėliau – COLR/CPAL, SVG , CBDT/CBLC ir sbix – nepriklauso PDF atvaizdavimo modeliui, tad nė viena peržiūros programa neįpareigota jų skaityti iš įtaikyto šrifto. Spalva turi būti išversta į puslapio turinį generavimo metu, kol generatorius dar turi šrifto baitus ir žino, kurio glifo nori. Tas vertimas skiriasi pagal formatą, ir laukiniai emoji šriftai naudoja visus: sluoksniotus vektorius, gradientinius paint grafus, įtaikytus SVG dokumentus ir PNG strikes. HotPDF rezultatą praneša kaip THPDFOpenTypeColorFormat, su reikšmėmis otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG ir otcfSBIX, o šriftą zonduoja fiksuotu prioritetu: pirmiausia COLR, paskui SVG, paskui CBDT, paskui sbix. Vektoriniai duomenys laimi prieš bitmaps, kai tik šriftas neša abu – būtent to norite dokumente, kurį gali tekti priartinti ar atspausdinti

HotPDF spalvoto glifo zondo diagrama: PDF šrifto programa glifo kontūrus nudažo dabartine spalva, tad OpenType spalvų lenteles COLR, SVG, CBDT ir sbix reikia išversti į puslapio turinį generavimo metu, o HotPDF užregistruotą šriftą zonduoja fiksuotu prioritetu – COLR, paskui SVG, paskui CBDT, paskui sbix, pranešdamas THPDFOpenTypeColorFormat nuo otcfCOLRv0 iki otcfSBIX
Vektoriniai duomenys laimi prieš bitmaps, kai tik šriftas neša abu – būtent to norite dokumente, kurį gali tekti priartinti ar atspausdinti, o glifas be spalvų kelio paliekamas jūsų atsarginei išėjai

Vienas iškvietimas, penki formatai: spalvoto glifo išsprendimas ir piešimas

THotPDF.GetRegisteredColorGlyphInfo atsako, kurį kelią ims kodo taškas, o DrawRegisteredColorGlyph juo eina. Abu kodo tašką ieško paskiausiai RegisterUnicodeTTF perduoto šrifto simbolių žemėlapyje, tad spalvotasis šriftas iškvietimo metu turi būti užregistruotuoju Unicode šriftu. Piešimo funkcija grąžina False, kai glifas neturi spalvų duomenų arba nė vienas kelias jo neatvaizduoja, ir atsarginę išėją palieka jums

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 paletė 0, bitmap strike arčiausiai 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
      // Nėra spalvų duomenų: grįžtama į vienspalvį kontūrą
      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;

Du parametrai verti dėmesio. PaletteIndex pasirenka CPAL paletę, tad šriftas, turintis tamsaus fono paletę, gali būti perjungtas neliečiant glifo. TargetPixelsPerEm svarbus tik bitmap šriftams; paliktas nulyje, jis pagal numatymą lygus Round(FontSize * 96 / 72) – ekrano raiškai, todėl pavyzdys spaudai prašo 300. Sąžininga riba slypi paraše: iškvietimas paima vieną kodo tašką ir jį susieja vien per cmap. ZWJ sekos, odos atspalvio modifikatoriai ir regional-indicator vėliavos yra GSUB ligatūros, tad jų sudėjimas yra shaping problema, kokios rūšį aprėpia OpenType GSUB alternatyvų straipsnis, o ne kas nors, ką šis įėjimo taškas padaro už jus

COLR v0: sukrauti glifų sluoksniai su paletės spalvomis

COLR v0 yra paprastas atvejis, ir HotPDF jį atvaizduoja tiesiai: kiekvienas bazinis glifas išvardija sluoksnių glifus su CPAL spalvos įrašu, o kiekvienas sluoksnis tampa viena įprasta teksto rodymo operacija su savo užpildymo spalva, sukrauta lentelės tvarka. Sluoksnis su alfa žemiau 255 gauna grafikos būsenos parametrų žodyną su atitinkamais /ca ir /CA (ISO 32000-1 §8.4.5), o kiekvienas sluoksnių glifas pažymimas kaip naudotas, kad subsetter išsaugotų jo kontūrą, nors nė vienas kodo taškas į jį tiesiogiai nesusieja. Viena detalė žmones nustebina: paletės įrašo indeksas 0xFFFF OpenType specifikacijoje reiškia „naudoti teksto priekinio plano spalvą“, o HotPDF jį išsprendžia į juodą, o ne į dabartinę puslapio užpildymo spalvą. Emoji šriftams tai retai svarbu; ikonų šriftams, kurie pasikliauja priekinio plano įrašu glifo atspalviui, patikrinkite išvestį, prieš manydami, kad ji seks jūsų teksto spalva

Kaip HotPDF COLR v1 paint grafą paverčia PDF operatoriais?

Išanalizavus paint lenteles į plokščią, ribotą grafą, ir tik tada susiejus kiekvieną mazgą su PDF konstruktu. COLR v1 glifas nėra sluoksnių sąrašas, o nukreiptasis aciklinis paint įrašų grafas, kuriame mazgais gali būti dalijamasi per PaintColrLayers ir PaintColrGlyph. Analizatorius jį riboja 4096 paint mazgų, 64 gylio lygių ir 1024 spalvų stotelių, ir kiekvieną mazgą seka kaip aktyvų arba baigtą, kad nuoroda atgal į aktyvų mazgą – ciklas, kurį kenkėjiškas šriftas gali sukurti iš sluoksnių pakartotinio naudojimo – būtų atmetama, o ne rekursuojama. Poslinkių bazės – vieta, kur pirma implementacija klysta. BaseGlyphPaintRecord poslinkiai yra santykūs su BaseGlyphList pradžia, LayerList paint poslinkiai – santykūs su LayerList, o kiekvienas Offset24 paint lentelės viduje – santykus su pačia ta paint lentele. Išsprendus visas tris prieš tą patį pagrindą, visiškai teisėti glifai žlunga ant ribų patikros, kuri atrodo lygiai kaip sugadintas šriftas. Grafas sukonstravus, susiejimas tiesmukas:

  • PaintGlyph nustato glifo kontūrą kaip clip su teksto atvaizdavimo režimu 7 (ISO 32000-1 §9.3.6), tada jame nupiešia savo vaiką
  • Solid paint užpildo nukirptą stačiakampį; tiesiniai gradientai tampa kelių stotelių aksialiais shadings, o spinduliniai gradientai – dviejų spalvų radialiais shadings (§8.7.4.5)
  • Sweep gradientai neturi PDF atitikmens, tad HotPDF juos aproksimuoja 96 vienspalviais sektoriais, kiekvieną ėmus iš spalvų linijos
  • Transformacijos išvedamos kaip cm, sujungtos aplink glifo baseline kilmės tašką, su per FontSize / UnitsPerEm perskaičiuotais postūmiais
  • PaintComposite režimai nuo 13 iki 27 susieja su atskiriamaisiais ir neatskiriamaisiais PDF blend režimais, tokiais kaip /Multiply, /Screen ir /Luminosity (§11.3.5), nustatomais per ExtGState /BM įrašą

Ribos iškeltos aiškiai. Porter-Duff režimai nuo 5 iki 12 (src_in, xor, plus ir kiti) neturi PDF blend režimų atitikmenų, repeat ir reflect išplėtimo režimai tiesiniams ir spinduliniams gradientams neišvedami, o gradientai, kurių stotelės neša skirtingas alfas, nėra apsimetamos viena opacity. Spinduliniai gradientai su daugiau nei dviem stotelėmis palieka tik pirmąją ir paskutinę spalvas. HotPDF visą grafą patikrina prieš šį palaikomą poaibį, dar nesurašęs nė vieno operatoriaus, tad nepalaikomas glifas palieka puslapį neliestą ir pereina prie raster atsarginės išėjos, vietoj to, kad paliktų pusiau piešinį

HotPDF COLR v1 konversijos diagrama: paint grafas išanalizuojamas į ribotą grafą, capped ties 4096 mazgų, 64 gylio lygiais ir 1024 spalvų stotelėmis su ciklų atmetimu, tada PaintGlyph tampa režimo 7 clip, tiesiniai ir spinduliniai gradientai tampa aksialiais ir radialiais shadings, sweep gradientai – 96 sektoriais, o PaintComposite režimai nuo 13 iki 27 – PDF blend režimais
Visas grafas patikrinamas prieš palaikomą poaibį, dar neparašius pirmojo operatoriaus, tad nepalaikomas glifas palieka puslapį neliestą ir pereina prie raster atsarginės išėjos, vietoj to, kad paliktų pusiau piešinį

SVG glifai ir bitmap strikes

SVG glifai eina pro tą patį ribotą builder, kurį HotPDF naudoja importuojamiems SVG failams, o rezultatas užregistruojamas kaip Form XObject (§8.10), tiksliai kaip aprašyta SVG į Form XObject straipsnyje. SVG lentelės dokumentas gali būti gzip suspaustas; išspaudimas bėga 8 KB gabalais ir sustoja, vos tik išplėstas dydis pasiektų 32 MB, vietoj to, kad pirmiausia išpūstų, o patikrintų paskui, ir pats suspaustas įvedimas ribojamas 8 MB. Profilis sąmoningai griežtas: scenarijai, įtaikyti paveikslėliai, išorinės URL, data: URI ir nelokalios nuorodos žlunga uždarydamosi. Forma masteliuojama taip, kad ilgesnioji jos pusė lygtų šrifto dydį, ir tvirtinama prie baseline – taip y-kryptimi žemyn einanti SVG koordinačių sistema susiejama su y-kryptimi aukštyn einančia PDF sistema. Turėkite omeny, kad builder gauna visą glifo SVG dokumentą, be glyphNNN elemento atrankos, tad šriftai, kurie sudeda daug glifų į vieną bendrą dokumentą, verti patikros dar prieš jais pasitikint

Bitmap šriftai yra strike pasirinkimo ir išdėstymo klausimas. CBDT atveju HotPDF pasirenka CBLC dydį, kurio vertikalus ppem arčiausiai TargetPixelsPerEm, priima paveikslėlių formatus 17, 18 ir 19, o formato 19 metrikas skaito iš CBLC index sublentelės, nes tas formatas savųjų nesaugoja. sbix atveju strike poslinkiai yra santykūs su lentele, o glifo poslinkiai – su strike, o dupe įrašas pakartotinai naudoja kito glifo grafiką, išlaikydamas savuosius kilmės poslinkius; leidus rekursijai perrašyti išorinę kilmę, paveikslėlis pasislenka. PNG ir JPEG turiniai iškoduojami viduje, masteliuojami FontSize / PixelsPerEmY, o ne ištempiami iki šrifto dydžio, ir rašomi su soft mask (§11.6.5.3), kai tik vienas pikselis nėra visiškai nepermatomas. sbix TIFF turiniai neiškoduojami ir keliauja į įvykį

Kas nutinka, kai glifo negalima piešti natyviai?

HotPDF pakelia OnColorGlyphRasterize ir patalpina tai, kokį RGBA bitmap grąžina jūsų apdorotojas; jei niekas nepriskirta arba apdorotojas palieka Handled false, DrawRegisteredColorGlyph grąžina False, o puslapis lieka nepakitęs. Įvykis suveikia COLR v1 grafui už palaikomo poaibio, SVG dokumentui, kurio atsisakė saugusis builder, ir bitmap turiniui, kurio neskaityo vidiniai dekoderiai. Apdorotojas gauna formatą, žalius šrifto baitus, ištrauktą turtą (SVG dokumentą, galbūt vis dar gzipped, arba bitmap baitus; COLR v1 – tuščią), glifo ID, paletę ir tikslinį pikselių dydį

HotPDF raster atsarginės išėjos diagrama: OnColorGlyphRasterize suveikia COLR v1 grafui už palaikomo poaibio, SVG dokumentui, kurio atsisakė saugusis builder, arba bitmap turiniui, kurio neskaityo dekoderiai, perduodamas formatą, šrifto baitus, turtą, GlyphID, PaletteIndex ir PixelSize, o grąžintas RGBA buferis priimamas tik tada, kai jo ilgis lygiai Width kart Height kart 4
Nuliniai dydžiai, netinkamas buferio ilgis arba per didelės dimensijos atmetamos dar nelietus puslapio, o be apdorotojo ar su Handled false iškvietimas grąžina False, ir puslapis lieka nepakitęs
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 yra jūsų rasterizatorius, o ne HotPDF API.
  // Jis turi grąžinti lygiai Width * Height * 4 RGBA baitus.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

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

HotPDF patikrina apdorotojo išvestį dar nelietęs puslapio: nuliniai dydžiai, buferis, kurio ilgis nėra lygiai Width * Height * 4, arba dimensijos, didelės persipildyti, atmetami, ir iškvietimas grąžina False. Raster atsarginė išėja vis tiek yra raster, tad taip atvaizduotas emoji praranda savo vektorinį aštrumą; prašykite PixelSize, atitinkančio jūsų išvesties raišką. Suporavus spalvų kelią su piešimo metu vykstančiomis aprėpties patikromis iš dingusių glifų sekimo straipsnio, konvejeris, apdorojantis bet kokį vartotojo tekstą, gali pranešti ir dingusius glifus, ir glifus, kurie prarado savo spalvą

Spalvotų glifų atvaizduoklis, OpenType shaping krūva ir saugusis SVG builder visi atkeliauja su HotPDF Delphi PDF komponentu, prieinamu Delphi ir C++Builder aplinkose