Tehnični članak

Pisave barvnih emoji v PDF: COLR v1, SVG in bitne slike

HotPDF riše barvne emoji v PDF prek THotPDF.DrawRegisteredColorGlyph, ki prebere barvne podatke pisave, registrirane s RegisterUnicodeTTF, in jih odda kot izvorno grafiko PDF: plasti COLR v0 kot zapolnjene glifne orise, grafe risanja COLR v1 kot odreze, senčenja in načine zlivanja, glife SVG kot Form XObjects, bitne slike CBDT ali sbix pa kot slike. Česar ne more preslikati izvorno, gre namesto tega na dogodek OnColorGlyphRasterize in se ne spremeni tiho v črno obliko

Zadnji stavek je celoten razlog, da ta koda sploh obstaja. Vgradite pisavo emoji na običajen način in pregledovalnik dobi oris iz glyf ali CFF, zapolnjen s tem, kar trenutna barva polnjenja slučajno je. Nasmehnjen obraz pride kot črna gmota, zastava kot pravokotnik in nič v cevovodu se ne pritoži

Zakaj se barvni emoji natisne kot črna silhueta v PDF?

Program pisave PDF nima pojma o barvnih glifih. ISO 32000-1 obravnava glifo kot obliko, pobarvano s trenutno barvo, barvne tabele, ki jih je OpenType dodal pozneje, torej COLR/CPAL, SVG , CBDT/CBLC in sbix, pa niso del modela slikanja PDF, zato noben pregledovalnik ni dolžan brati jih iz vgrajene pisave. Barvo je treba prevesti v vsebino strani ob ustvarjanju, ko producent še ima bajte pisave in ve, katero glifo želi. Ta prevod se razlikuje po formatu, pisave emoji v divjini pa uporabljajo vse: plastene vektorje, grafe risanja s prelivami, vgrajene dokumente SVG in bitne velikosti PNG. HotPDF poroča rezultat kot THPDFOpenTypeColorFormat, z vrednostmi otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG in otcfSBIX, pisavo pa preizkusi v fiksnem prednostnem vrstnem redu: najprej COLR, nato SVG, nato CBDT, nato sbix. Vektorski podatki zmagajo nad bitnimi slikami, kadar pisava nosi oboje, kar je točno tisto, kar hočete v dokumentu, ki ga je mogoče povečati ali natisniti

Diagram preizkusa barvnih glif v HotPDF: program pisave PDF pobarva glifne orise s trenutno barvo, zato morajo biti barvne tabele OpenType COLR, SVG, CBDT in sbix prevedene v vsebino strani ob ustvarjanju, HotPDF pa preizkusi registrirano pisavo v fiksnem prednostnem vrstnem redu COLR, nato SVG, nato CBDT, nato sbix, in poroča THPDFOpenTypeColorFormat od otcfCOLRv0 do otcfSBIX
Vektorski podatki zmagajo nad bitnimi slikami, kadar pisava nosi oboje, kar je točno to, kar hočete v dokumentu, ki ga je mogoče povečati ali natisniti, glifa brez barvne poti pa ostane vaši rezervi

En klic, pet formatov: razreševanje in risanje barvne glife

THotPDF.GetRegisteredColorGlyphInfo odgovori, katero pot bo vzela kodna točka, DrawRegisteredColorGlyph pa jo vzame. Oba poiščeta kodno točko v znakovnem zemljevidu pisave, ki je bila nazadnje podana RegisterUnicodeTTF, zato mora biti barvna pisava registrirana pisava Unicode v času klica. Funkcija risanja vrne False, ko glifa nima barvnih podatkov ali je nobena pot ne more upodobiti, rezervo pa prepusti vam

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, paleta CPAL 0, bitna velikost najbližja 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
      // Brez barvnih podatkov: padite nazaj na enobarvni oris
      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 parametra zaslužita pozornost. PaletteIndex izbere paleto CPAL, zato se pisava, ki dobavlja paleto za temno ozadje, lahko zamenja, brez da bi se dotaknili glife. TargetPixelsPerEm je pomemben le za bitne pisave; puščen na nič privzame Round(FontSize * 96 / 72), ločljivost zaslona, zato primer zahteva 300 za tiskovni izhod. Iskrena meja sedi v podpisu: klic vzame eno kodno točko in jo preslika skozi cmap samega. Zaporedja ZWJ, modifikatorji odtenka kože in zastave regionalnih kazalcev so ligature GSUB, zato je njihovo sestavljanje problem oblikovanja, take vrste, kot ga pokriva članek o alternativah OpenType GSUB, in ne nekaj, kar bi ta vstopna točka naredila za vas

COLR v0: naložene plasti glif z barvami palete

COLR v0 je preprost primer in HotPDF ga upodobi neposredno: vsaka osnovna glifa našteje plasti glif z barvnim vnosom CPAL, vsaka plast pa postane ena navadna operacija prikaza besedila z lastno barvo polnjenja, naložena v vrstnem redu tabele. Plast z alfo pod 255 dobi slovar parametrov grafičnega stanja z ujemajočima /ca in /CA (ISO 32000-1 §8.4.5), vsaka glifa plasti pa je označena kot uporabljena, tako da subsetter obdrži njen oris, čeprav se nanjo neposredno ne preslika nobena kodna točka. Ena podrobnost preseneča: kazalo vnosa palete 0xFFFF pomeni »uporabi barvo ospredja besedila« v specifikaciji OpenType, HotPDF pa jo razreši v črno in ne v trenutno barvo polnjenja strani. Za pisave emoji to redko pomaga; za ikonske pisave, ki se zanašajo na vnos ospredja, da obarvajo glifo, preverite izhod, preden domnevate, da bo sledil vaši barvi besedila

Kako HotPDF pretvori graf risanja COLR v1 v operatorje PDF?

Z razčlenjevanjem tabel risanja najprej v raven, omejen graf in šele nato s preslikavo vsakega vozlišča v konstrukcijo PDF. Glifa COLR v1 ni seznam plasti, temveč usmerjen aciklični graf zapisov risanja, kjer si lahko vozlišča delijo prek PaintColrLayers in PaintColrGlyph. Razčlenjevalnik ga omeji na 4096 vozlišč risanja, 64 ravni globine in 1024 barvnih postankov ter sledi vsakemu vozlišču kot aktivnemu ali opravljenemu, tako da je sklic nazaj na aktivno vozlišče, cikel, ki ga lahko zlonamerna pisava zgradi iz ponovne uporabe plasti, zavrnjen in ne ponavljan. Osnovi odmikov so mesto, kjer se prva implementacija zmoti. Odmiki BaseGlyphPaintRecord so relativni na začetek BaseGlyphList, odmiki risanja LayerList so relativni na LayerList, vsak Offset24 znotraj tabele risanja pa je relativen na to tabelo risanja samo. Razrešite vse tri proti isti osnovi in povsem zakonite glife odpovejo preizkusu meja, kar izgleda točno kot pokvarjena pisava. Ko je graf zgrajen, je preslikava neposredna:

  • PaintGlyph nastavi glifni oris kot odrez z načinom upodabljanja besedila 7 (ISO 32000-1 §9.3.6) in nato pobarva svojega otroka znotraj njega
  • Enobarvna risanja zapolnijo odrezan pravokotnik; linearni prelivi postanejo večpostanek aksialna senčenja in radialni prelivi dvobarvna radialna senčenja (§8.7.4.5)
  • Sweep prelivi nimajo ustreznika v PDF, zato jih HotPDF približa s 96 ravnimi barvnimi klini, vsak vzorčen iz barvne črte
  • Transformacije se oddajo kot cm, konjugirane okoli izhodišča glifne osnovne črte, s premiki, umerjenimi z FontSize / UnitsPerEm
  • Načini PaintComposite od 13 do 27 se preslikajo v ločljive in neločljive načine zlivanja PDF, kot so /Multiply, /Screen in /Luminosity (§11.3.5), nastavljene prek vnosa /BM ExtGState

Meja je izrecna. Načini Porter-Duff od 5 do 12 (src_in, xor, plus in preostali) nimajo ustreznika med načini zlivanja PDF, razširitveni način ponovi in odbij na linearnih in radialnih prelivih ni oddan, prelivi, katerih postanki nosijo različne vrednosti alfe, pa niso ponarejeni z eno samo prosojnostjo. Radialni prelivi z več kot dvema postankoma obdržita le svoji prvi in zadnji barvi. HotPDF preveri celoten graf proti tej podprti podmnožici, preden zapiše en samega operatorja, zato nepodprta glifa pusti stran nedotaknjeno in nadaljuje na rastersko rezervo, namesto da bi za sabo pustila pol risbe

Diagram pretvorbe COLR v1 v HotPDF: graf risanja se razčleni v omejen graf, omejen na 4096 vozlišč, 64 ravni globine in 1024 barvnih postankov z zavrnitvijo ciklov, nato PaintGlyph postane odrez načina 7, linearni in radialni prelivi postanejo aksialna in radialna senčenja, sweep prelivi 96 klinov, načini PaintComposite od 13 do 27 pa načini zlivanja PDF
Celoten graf se preveri proti podprti podmnožici, preden se zapiše prvi operator, zato nepodprta glifa pusti stran nedotaknjeno in nadaljuje na rastersko rezervo, namesto da bi pustila pol risbe

Glife SVG in bitne velikosti

Glife SVG gredo skozi isti omejen graditelj, ki ga HotPDF uporablja za uvožene datoteke SVG, rezultat pa je registriran kot Form XObject (§8.10), točno tako, kot je opisano v članku od SVG do Form XObject. Dokument v tabeli SVG je lahko stisnjen z gzip; dekompresija teče v koščkih po 8 KB in se ustavi, takoj ko bi razširjena velikost presegla 32 MB, namesto da bi najprej napihnili in šele potem preverili, stisnjeni vhod pa je sam omejen na 8 MB. Profil je omejen namenoma: skripti, vgrajene slike, zunanji URL-ji, URI-ji data: in nelokalne reference odpovejo zaprto. Forma se umeri tako, da njena daljša stran enača velikost pisave, in zasidra na osnovni črti, kar preslika koordinatni sistem SVG z y navzdol na tistega PDF z y navzgor. Vedite, da graditelj dobi celoten dokument SVG za glifo, brez izbire elementa glyphNNN, zato so pisave, ki spravijo veliko glif v en skupni dokument, vredne preizkusa, preden se nanje zanesete

Bitne pisave so vprašanje izbire velikosti in postavitve. Za CBDT HotPDF izbere velikost CBLC, katere navpični ppem je najbližje TargetPixelsPerEm, sprejme slikovne formate 17, 18 in 19 ter prebere meritve formata 19 iz indeksne podtabele CBLC, ker ta format ne shrani lastnih. Za sbix so odmiki velikosti relativni na tabelo in odmiki glif na velikost, zapis dupe pa ponovno uporabi grafiko druge glife, medtem ko obdrži svoje lastne odmike izhodišča; če pustite, da rekurzija prepiše zunanje izhodišče, se slika premakne. Paketi PNG in JPEG se dekodirajo interno, umerijo z FontSize / PixelsPerEmY, namesto da bi bili raztegnjeni na velikost pisave, in zapišejo z mehko masko (§11.6.5.3), kadarkoli kakšen piksel ni povsem moten. Paketi TIFF sbix se ne dekodirajo in gredo na dogodek

Kaj se zgodi, ko glife ni mogoče narisati izvorno?

HotPDF sproži OnColorGlyphRasterize in postavi katero koli bitno sliko RGBA, ki jo vrne vaš upravljalnik; če nič ni dodeljeno ali upravljalnik pusti Handled neresnično, DrawRegisteredColorGlyph vrne False in stran ostane nespremenjena. Dogodek se sproži za graf COLR v1 zunaj podprte podmnožice, za dokument SVG, ki ga varni graditelj odkloni, in za bitni paket, ki ga notranji dekoderji ne berejo. Upravljalnik dobi format, surove bajte pisave, izvlečeno sredstvo (dokument SVG, morda še vedno gzip, ali bitne bajte; prazno za COLR v1), ID glife, paleta in ciljno velikost pikslov

Diagram rasterske rezerve v HotPDF: OnColorGlyphRasterize se sproži za graf COLR v1 zunaj podprte podmnožice, za dokument SVG, ki ga varni graditelj odkloni, ali za bitni paket, ki ga dekoderji ne berejo, in poda format, bajte pisave, sredstvo, GlyphID, PaletteIndex in PixelSize, vrnjeni medpomnilnik RGBA pa je sprejet le, ko je njegova dolžina točno Width krat Height krat 4
Ničelne velikosti, napačna dolžina medpomnilnika ali prelivanje mer se zavrnejo, preden se stran dotakne, brez upravljalnika ali z Handled neresnično pa klic vrne False in stran ostane nespremenjena
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 vaš rasterizer, ni pa API HotPDF.
  // Vrniti mora točno Width * Height * 4 bajtov RGBA.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

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

HotPDF preveri izhod upravljalnika, preden se dotakne strani: ničelne velikosti, medpomnilnik, katerega dolžina ni točno Width * Height * 4, ali mere, dovolj velike za prelivanje, so zavrnjene in klic vrne False. Rasterska rezerva je še vedno raster, zato emoji, upodobljen na ta način, izgubi svojo vektorsko ostrost; zahtevajte PixelSize, ki se ujema z vašo izhodno ločljivostjo. Združite barvno pot s preizkusi pokritosti ob risanju iz članka o sledenju manjkajočih glif in cevovod, ki obravnava poljubno uporabniško besedilo, lahko poroča tako o manjkajočih glifah kot o glifah, ki so izgubile barvo

Upodobitelj barvnih glif, sklad oblikovanja OpenType in varni graditelj SVG vsi prihajajo v komponento HotPDF Delphi PDF, na voljo za Delphi in C++Builder