Tekninen artikkeli

Värilliset emoji-fontit PDF:ssä: COLR v1, SVG ja bittikartat

HotPDF piirtää värilliset emojit PDF:ään THotPDF.DrawRegisteredColorGlyph-kutsun kautta, joka lukee RegisterUnicodeTTFilla rekisteröidyn fontin väridatan ja tuo sen esiin natiivina PDF-grafiikkana: COLR v0 -kerrokset täytettyinä glyyfien ääriviivoina, COLR v1:n maalausgraafit clippeinä, varjostuksina ja sekoitustiloina, SVG-glyyfit Form XObjecteina ja CBDT- tai sbix-bittikartat kuvina. Kaikki, mitä se ei voi kuvata natiivisti, menee OnColorGlyphRasterize-tapahtumaan sen sijaan että muuttuisi hiljaa mustaksi muodoksi

Se viimeinen lause on koko syy sille, että tämä koodi on olemassa. Upota emoji-fontti tavallisella tavalla, niin katselin saa ääriviivan glyfistä tai CFFistä, täytettynä sillä, mikä nykyinen täyttöväri sattuu olemaan. Hymynaama saapuu mustana läiskänä, lippu suorakulmiona, eikä mikään putkessa valita

Miksi värillinen emoji tulostuu mustana siluettina PDF:ssä?

PDF-fonttiohjelmalla ei ole käsitystä väriglyyfeistä. ISO 32000-1 käsittelee glyyfin muotona, joka maalataan nykyisellä värillä, ja OpenType:n myöhemmin lisäämät väritaaulukot, nimittäin COLR/CPAL, SVG , CBDT/CBLC ja sbix, eivät kuulu PDF:n kuvausmalliin, joten mikään katselin ei ole velvollinen lukemaan niitä upotetusta fontista. Väri on käännettävä sivusisällöksi tuotantohetkellä, sillä silloin tuottajalla on vielä fontin tavut ja tieto siitä, mitä glyyfiä se haluaa. Käännös eroaa muotoa kohden, ja luonnonvaraiset emoji-fontit käyttävät niitä kaikkia: kerroksellisia vektoreita, liukuvärinmaalausgraafeja, upotettuja SVG-asiakirjoja ja PNG-strikeja. HotPDF raportoi tuloksen THPDFOpenTypeColorFormatina, arvoilla otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG ja otcfSBIX, ja sondoi fontin kiinteässä tärkeysjärjestyksessä: ensin COLR, sitten SVG, sitten CBDT, sitten sbix. Vektordata voittaa bittikartat aina, kun fontti kantaa molempia, mikä on juuri se, mitä haluat asiakirjassa, jota saatetaan zoomata tai tulostaa

HotPDF:n väriglyyfisondauksen kaavio: PDF-fonttiohjelma maalaa glyyfien ääriviivat nykyisellä värillä, joten OpenType:n väritaaulukot COLR, SVG, CBDT ja sbix on käännettävä sivusisällöksi tuotantohetkellä, ja HotPDF sondoi rekisteröityä fonttia kiinteässä tärkeysjärjestyksessä ensin COLR, sitten SVG, sitten CBDT, sitten sbix, raportoiden THPDFOpenTypeColorFormatin arvosta otcfCOLRv0 arvoon otcfSBIX
Vektordata voittaa bittikartat aina, kun fontti kantaa molempia, mikä on juuri se, mitä haluat asiakirjassa, jota saatetaan zoomata tai tulostaa, ja glyyfi ilman väripolkua jää varamerkintäsi varaan

Yksi kutsu, viisi muotoa: väriglyyfin ratkaiseminen ja piirtäminen

THotPDF.GetRegisteredColorGlyphInfo vastaa siihen, minkä polun koodipiste tulee ottamaan, ja DrawRegisteredColorGlyph ottaa sen. Molemmat etsivät koodipisteen siihen fonttiin, joka viimeisimpänä välitettiin RegisterUnicodeTTFille, joten värifontin on oltava rekisteröity Unicode-fontti kutsun hetkellä. Piirtofunktio palauttaa Falsein, kun glyyfillä ei ole väridataa tai mikään polku ei pystynyt renderöimään sitä, ja jättää varamerkinnän sinun hoidettavaksesi

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-paletti 0, bittikartta-strike lähimpänä 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
      // Ei väridataa: palataan yksivärisen ääriviivan varaan
      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;

Kaksi parametria ansaitsee huomion. PaletteIndex valitsee CPAL-paletin, joten fontti, joka toimittaa tummataustaisen paletin, voidaan vaihtaa koskematta glyyfiin. TargetPixelsPerEm on merkitsevä vain bittikarttafonteille; nollaksi jätettynä se olettaa Round(FontSize * 96 / 72)in, näytön resoluution, minkä takia esimerkki pyytää 300:aa tulostustuotosta varten. Rehellinen raja istuu allekirjoituksessa: kutsu ottaa yhden koodipisteen ja kuvaa sen pelkän cmapin kautta. ZWJ-sekvenssit, ihonvärimuokkaimet ja alueellisten osoittimien liput ovat GSUB-ligatuureja, joten niiden kokoaminen on muotoiluongelma, jonka kaltaisia käsittelee artikkeli OpenType GSUB -vaihtoehtoisista muodoista, ei jotain, mitä tämä sisääntulopiste tekee puolestasi

COLR v0: päällekkäiset glyyfikerrokset palettiväreillä

COLR v0 on yksinkertainen tapaus, ja HotPDF renderöi sen suoraan: jokainen perusglyyfi luettelee kerrosglyyfit CPAL-värimerkinnällä, ja jokainen kerros muuttuu yhdeksi tavalliseksi tekstinäyttöoperaatioksi omalla täyttövärillään, pinottuna taulukkojärjestykseen. Kerros, jonka alfa on alle 255, saa grafiikkatilan parametrisanakirjan, jossa on vastaavat /ca ja /CA (ISO 32000-1 §8.4.5), ja jokainen kerrosglyyfi merkitään käytetyksi, jotta subsetoija pitää sen ääriviivan, vaikka mikään koodipiste ei kuvaisi siihen suoraan. Yksi yksityiskohta yllättää ihmiset: palettimerkinnän indeksi 0xFFFF tarkoittaa OpenType-spesifikaatiossa ”tekstin etualan värin käyttämistä”, ja HotPDF ratkaisee sen mustaksi eikä sivun nykyiseksi täyttöväriksi. Emoji-fonteilla tämä harvoin on merkitsevää; ikonifonteille, jotka nojaavat etualamerkintään glyyfin värjäämisessä, tarkista tuotos ennen kuin oletat sen seuraavan tekstiväriäsi

Miten HotPDF muuttaa COLR v1 -maalausgraafin PDF-operaattoreiksi?

Jäsentämällä maalaustaulukot ensin litteäksi, rajatuksi graafiksi ja vasta sitten kuvaamalla jokainen solmu PDF-rakenteeksi. COLR v1 -glyyfi ei ole kerrosten luettelo vaan maalaustietueiden suunnattu syklitön graafi, jossa solmuja voi jakaa PaintColrLayersilla ja PaintColrGlyphilla. Jäsennin kattoo sen 4096 maalaussolmuun, 64 syvyyteen ja 1024 väripysäkkiin, ja seuraa jokaista solmua aktiivisena tai valmiina, jolloin viittaus takaisin aktiiviseen solmuun — sykli, jonka pahantahtoinen fontti voi rakentaa kerrosten uudelleenkäytöstä — hylätään sen sijaan että siihen rekursioitaisiin. Siirtymäkohdat ovat se, jossa ensimmäinen toteutus menee pieleen. BaseGlyphPaintRecord-siirtymät ovat suhteessa BaseGlyphListin alkuun, LayerListin maalaussiirtymät ovat suhteessa LayerListin, ja jokainen maalaustaulukon sisäinen Offset24 on suhteessa kyseiseen maalaustaulukkoon itseensä. Ratkaise kaikki kolme samaa perustaa vasten, niin täysin lailliset glyyfit kaatuvat rajojen tarkistukseen, mikä näyttää täsmälleen rikkinäiseltä fontilta. Kun graafi on rakennettu, kuvaus on suoraviivainen:

  • PaintGlyph asettaa glyyfin ääriviivan clipiksi tekstirenderöintitilalla 7 (ISO 32000-1 §9.3.6) ja maalaa sitten lapsensa sen sisään
  • Yhtenäiset maalaukset täyttävät clipatun suorakulmion; lineaariset liukuvärit muuttuvat monipysäkkäisiksi aksiaalisiksi varjostuksiksi ja säteittäiset liukuvärit kaksivärisiksi säteittäisiksi varjostuksiksi (§8.7.4.5)
  • Kiertoliukuväreillä ei ole PDF-vastinetta, joten HotPDF approksimoi ne 96 tasavärisellä kielolla, joista kukin näytteistetään värialalta
  • Muunnokset tuotetaan cminä, konjugoituna glyyfin perusviivan alkuperän ympärille, siirtymien skaalautuessa kaavalla FontSize / UnitsPerEm
  • PaintComposite-tilat 13–27 kuvautuvat eroteltaviin ja ei-eroteltaviin PDF-sekoitustiloihin kuten /Multiply, /Screen ja /Luminosity (§11.3.5), asetettuna ExtGState:n /BM-merkinnällä

Raja on eksplisiittinen. Porter-Duff-tiloilla 5–12 (src_in, xor, plus ja loput) ei ole PDF-sekoitustilavastinetta, toisto- ja heijastustilalaajennuksia ei tuoteta lineaarisille ja säteittäisille liukuväreille, eikä liukuvärejä, joiden pysäkit kantavat eri alfa-arvoja, väärennetään yhdellä läpinäkyvyydellä. Yli kahden pysäkin säteittäiset liukuvärit pitävät vain ensimmäisen ja viimeisen värinsä. HotPDF tarkistaa koko graafin tätä tuettua osajoukkoa vasten ennen kuin kirjoittaa yhtään operaattoria, joten tukematon glyyfi jättää sivun koskemattomaksi ja siirtyy rasterivaramerkintään sen sijaan että jättäisi puolet piirroksesta taakseen

HotPDF:n COLR v1 -muunnoksen kaavio: maalausgraafi jäsennetään rajatuksi graafiksi, jonka katot ovat 4096 solmua, 64 syvyystasoa ja 1024 väripysäkkiä syklien hylkäisyllä, sitten PaintGlyph muuttuu tilan 7 clipiksi, lineaariset ja säteittäiset liukuvärit aksiaalisiksi ja säteittäisiksi varjostuksiksi, kiertoliukuvärit 96 kieloksi ja PaintComposite-tilat 13–27 PDF-sekoitustiloiksi
Koko graafi tarkistetaan tuettua osajoukkoa vasten ennen kuin ensimmäinen operaattori kirjoitetaan, joten tukematon glyyfi jättää sivun koskemattomaksi ja siirtyy rasterivaramerkintään sen sijaan että jättäisi puolet piirroksesta

SVG-glyyfit ja bittikartta-striket

SVG-glyyfit kulkevat saman rajatun rakentajan läpi, jota HotPDF käyttää tuoduille SVG-tiedostoille, ja tulos rekisteröidään Form XObjectina (§8.10), täsmälleen niin kuin artikkeli SVG:stä Form XObjectiksi kertoo. SVG -taulukon asiakirja voi olla gzip-pakattu; purku ajaa 8 kilotavun paloissa ja pysähtyy heti, kun laajentunut koko ylittäisi 32 Mt, sen sijaan että purkaisi ensin ja tarkistaisi jälkeenpäin, ja pakatun syötteen koko on kattoluku 8 Mt. Profiili on rajoittava tahallaan: skriptit, upotetut kuvat, ulkoiset URLit, data:-URIit ja ei-paikalliset viittaukset kaatuvat kiinni. Form skaalataan niin, että sen pidempi sivu on fontin kokoinen, ja ankkoroidaan perusviivaan, mikä kuvaa SVG:n y-alas koordinaatiston PDF:n y-ylös koordinaatistoon. Huomaa, että rakentaja vastaanottaa koko SVG-asiakirjan glyyfiä varten ilman glyphNNN-elementin valintaa, joten fontit, jotka pakkaavat monta glyyfiä yhteen jaettuun asiakirjaan, kannattaa testata ennen kuin nojaat niihin

Bittikarttafontit ovat strike-valinnan ja sijoittelun kysymys. CBDT:lle HotPDF valitsee CBLC-koon, jonka pystyppem on lähimpänä TargetPixelsPerEmia, hyväksyy kuvaformaatit 17, 18 ja 19 ja lukee formaatin 19 metriikat CBLC-indeksialitaulukosta, koska kyseinen formaatti ei tallenna niitä itse. sbixille strike-siirtymät ovat suhteessa taulukkoon ja glyyfisiirtymät strikeen, ja dupe-tietue käyttää uudelleen toisen glyyfin grafiikkaa pitäen omat alkuperäsiirtymänsä; antamalla rekursion ylikirjoittaa ulkoisen alkuperän siirtää kuvan. PNG- ja JPEG-hyötykuormat dekoodataan sisäisesti, skaalataan kertoimella FontSize / PixelsPerEmY sen sijaan että venytettäisiin fonttikokoon, ja kirjoitetaan pehmeällä maskilla (§11.6.5.3) aina, kun yksikin pikseli ei ole täysin peittävä. sbixin TIFF-hyötykuormia ei dekoodata, vaan ne menevät tapahtumaan

Mitä tapahtuu, kun glyyfiä ei voi piirtää natiivisti?

HotPDF nostaa OnColorGlyphRasterizein ja sijoittaa sen RGBA-bittikartan, jonka käsittelijäsi palauttaa; jos mitään ei ole sijoitettu tai käsittelijä jättää Handledin epätodeksi, DrawRegisteredColorGlyph palauttaa Falsein ja sivu pysyy muuttumattomana. Tapahtuma lauhtuu tuetun osajoukon ulkopuoliselle COLR v1 -graafille, turvallisen rakentajan hylkäämälle SVG-asiakirjalle ja bittikarttahyötykuormalle, jota sisäiset dekooderit eivät lue. Käsittelijä saa formaatin, fontin raakatavut, poimitun hyödykkeen (SVG-asiakirja, mahdollisesti yhä gzipattu, tai bittikarttatavut; tyhjä COLR v1:lle), glyyfi-ID:n, paletin ja kohdepikselikoon

HotPDF:n rasterivaramerkinnän kaavio: OnColorGlyphRasterize lauhtuu tuetun osajoukon ulkopuoliselle COLR v1 -graafille, turvallisen rakentajan hylkäämälle SVG-asiakirjalle tai bittikarttahyötykuormalle, jota dekooderit eivät lue, välittäen formaatin, fonttitavut, hyödykkeen, GlyphID:n, PaletteIndexin ja PixelSizen, ja palautettu RGBA-puskuri hyväksytään vain, kun sen pituus on täsmälleen Width kertaa Height kertaa 4
Nollakoot, väärä puskurin pituus tai ylivuottavat ulottuvuudet hylätään ennen kuin sivuun kosketaan, ja ilman käsittelijää tai Handled false -tilassa kutsu palauttaa False:n ja sivu pysyy muuttumattomana
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 on sinun rasteroijasi, ei HotPDF:n API.
  // Sen on palautettava täsmälleen Width * Height * 4 tavua RGBA:ta.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

// Kytkentä
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;

HotPDF valido käsittelijän tuotoksen ennen kuin koskee sivuun: nollakoot, puskuri, jonka pituus ei ole täsmälleen Width * Height * 4, tai riittävän suuret ylivuottavat ulottuvuudet hylätään ja kutsu palauttaa Falsein. Rasterivaramerkintä on silti rasteria, joten tällä tavalla renderöityvä emoji menettää vektoriterävyytensä; pyydä PixelSizeia, joka vastaa tuotontarkuuttasi. Yhdistä väripolku piirtohetkellisiin kattavuustarkistuksiin artikkelista puuttuvien glyyfien seurannasta, niin mielivaltaista käyttäjätekstiä käsittelevä putki voi raportoida sekä puuttuvat glyyfit että värinsä menettäneet glyyfit

Väriglyyfirenderoija, OpenType-muotoilupino ja turvallinen SVG-rakentaja toimitetaan kaikki HotPDF Delphi PDF -komponentissa, saatavilla Delphille ja C++Builderille