Teknik Makale

PDF'te renkli emoji fontları: COLR v1, SVG ve bitmapler

Sıradan bir metin çağrısıyla, font olarak Segoe UI Emoji kullanılarak PDF'e yazılan sırıtan yüz emojisi düz siyah bir siluet çıkar: şekil doğru, sarı yok, gölgeleme yok. CBDT emoji fontuyla değiştirin ve aynı çağrı hiçbir şey çizmeyebilir; çünkü o font resimlerini PNG dosyaları olarak tutar ve çok az ya da hiç anahat verisi taşımaz. Siyah şekli görüntüleyiciden kopyalayın; panoda hâlâ U+1F600 vardır: karakter doğrudur, yalnızca renk tabloları hiç okunmamıştır

Renkli bir emoji PDF'te neden siyah bir siluet olarak basılır?

Çünkü bir PDF font programının renkli glif kavramı yoktur. ISO 32000-1 her glifi geçerli dolgu rengiyle boyanan bir şekil sayar ve OpenType'ın sonra eklediği renk tabloları (COLR/CPAL, SVG , CBDT/CBLC ve sbix) PDF görüntüleme modelinin parçası değildir; hiçbir görüntüleyicinin gömülü fonttan onları okumak için bir nedeni yoktur. Emojinin taşıdığı renk, PDF yazılırken — üreticinin hâlâ font baytlarını elinde tuttuğu ve tam olarak hangi glifi istediğini bildiği bir noktada — sayfa içeriğine dönüştürülmelidir. O çeviri her biçim için farklıdır ve gerçek emoji fontları dördünü de kullanır: istiflenmiş vektör katmanları, gradyan boya grafları, bütün SVG belgeleri ve PNG strike'ları

HotPDF o çeviriyi THotPDF.DrawRegisteredColorGlyph'de yapar. Bir glifin hangi biçime çözüldüğünü THPDFOpenTypeColorFormat olarak bildirir — değerleri otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG ve otcfSBIX — ve fontu sabit bir sırada yoklar: önce COLR, sonra SVG, sonra CBDT, sonra sbix. Hem vektör hem bitmap taşıyan bir font bu yüzden vektör yolunu alır. Yakınlaştırılacak ya da basılacak her şey için doğru sıra budur

HotPDF renkli glyph yoklama şeması: bir PDF font programı glyph konturlarını geçerli renkle boyar; OpenType renk tabloları COLR, SVG, CBDT ve sbix üretim anında sayfa içeriğine çevrilmek zorundadır ve HotPDF kayıtlı bir fontu sabit öncelikle sınar — önce COLR, sonra SVG, sonra CBDT, sonra sbix — otcfCOLRv0'dan otcfSBIX'e THPDFOpenTypeColorFormat bildirir
Font ikisini de taşıdığında vektör verisi bitmapleri yener; yakınlaştırılabilir ya da basılabilir bir belgede istediğiniz budur ve renk yolu olmayan bir glyph sizin yedeğinize bırakılır

Tek bir kod noktasını çözmek ve çizmek

THotPDF.GetRegisteredColorGlyphInfo, bir kod noktasının hangi yolu izleyeceğini söyler; DrawRegisteredColorGlyph o yolu izler. İkisi de kod noktasını, en son RegisterUnicodeTTF'e geçirilen fontun karakter haritasında arar; dolayısıyla emoji fontu, çağrı anında kayıtlı Unicode font olmak zorundadır. Çizim işlevi, glifin renk verisi yoksa ya da hiçbir şey onu render edemediyse False döndürür ve o durumda sayfaya dokunulmaz

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';
    // Palet girdisi 0xFFFF ("metin ön planı") bu renge çözülür
    Pdf.ColorGlyphForegroundColor := clNavy;
    Pdf.BeginDoc;
    Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\seguiemj.ttf');

    // U+1F600, CPAL paleti 0, 300 ppem'e en yakın 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
      // Renk yolu yok: tek renkli ana hatta geri dön
      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;

PaletteIndex bir CPAL paleti seçer; koyu zemin paleti taşıyan bir font, glife dokunmadan değiştirilebilir. 0xFFFF palet girdisi OpenType belirtiminde özeldir: "metin rengi neyse o" demektir ve HotPDF onu ColorGlyphForegroundColor ile ColorGlyphForegroundAlpha'dan doldurur (varsayılan siyah ve 255). Emoji fontları o girdiyi nadiren kullanır. İkon fontları kullanır; sizinki glifleri onunla renklendiriyorsa çizimden önce özelliği metin renginize ayarlayın

TargetPixelsPerEm yalnızca bitmap fontlar için önemlidir ve varsayılanı, ezilmesine en çok değer parametredir. Sıfır bırakılırsa Round(FontSize * 96 / 72)'ye, bir ekran çözünürlüğüne dönüşür: 36 punto emoji için 48 piksel. sbix strike'larını 20, 32, 40, 48, 64, 96 ve 160 ppem'de bildiren bir font için bu varsayılan 48 piksellik strike'ı seçer ve 600 dpi'lık yazıcı sonra onu kabaca altı kat büyütmek zorunda kalır. 300 isteyin — 600 dpi'da 36 puncton gereksinimi budur — ve aynı çağrı, fontun en büyüğü olan 160 strike'ı alır

İmza ayrıca kapsamı sabitler. Tek bir kod noktası girer ve cmap üzerinden eşlenir. ZWJ aile dizileri, ten tonu değiştiricileri ve bölgesel göstergeli bayraklar GSUB ligatürleridir; önce bir glife şekillendirilmek zorundadırlar — OpenType GSUB alternatifleri makalesinin işlediği iş türü

COLR katmanları ve boyu grafları PDF vektörleri olarak

COLR v0 basit biçimdir. Her temel glif, her biri bir CPAL rengiyle, bir katman glifi yığını listeler ve HotPDF her katmanın ana hattını tablo sırasına göre doldurur. CPAL renginin alfası 255'in altında olan bir katman kendi /ca'sını alır; yarı saydam katmanlar, font tasarımcısının kastettiği gibi altlarındaki katmanlarla karışır. Sıradan bir belgede yığının tamamı sonra tek bir yalıtılmış saydamlık grubuna yakalanır (ISO 32000-1 §11.4). Grup, glifin dışında olanla ilgilidir: emojinin çevresine koyduğunuz bir opaklık, blend modu ya da soft mask, bitmiş glife bir kez uygulanır; her katmanda yinelenip katmanların örtüştü yerlerde ikilenmek yerine

COLR v1 başka bir hayvandır: gradyanlar, dönüşümler, iç içe glif kırpmaaları ve bileşik modlar taşıyan, düğümlerin PaintColrLayers ve PaintColrGlyph ile paylaşılabildiği yönlendirilmiş asiklik bir boya kaydı grafı. HotPDF onu önce sınırlı bir grafa ayrıştırır — 4096 boya düğümü, 96 derinlik düzeyi ve 1024 renk durağıyla sınırlı — ve her düğümü ziyaret edilirken izler; böylece geçerli yola geri dönen bir referans (düşmanca bir fontun katman yeniden kullanımıyla kurabileceği bir döngü) özyinelemeye girmez, reddedilir

Ofsetler, elle yazılan COLR v1 ayrıştırıcılarının genelde kırıldığı yerdir. BaseGlyphPaintRecord ofsetleri BaseGlyphList'in başına, LayerList boya ofsetleri LayerList'e ve boya tablosunun içindeki her Offset24 o boya tablosunun kendisine göredir. Üçünü de tek bir tabana göre çözün ve kuskusuz yasal glifler sınır kontrollerini geçemez; tam da bozuk bir fonta benzer. Graf kurulunca PDF'e eşleme çoğunlukla doğrudandır:

  • PaintGlyph glif ana hattını bir kırpma yoluna çevirir ve alt boyası kırpılan alanı doldurur
  • Doğrusal gradyanlar çok duraklı eksenel gölgelemelere, radyal gradyanlar radyal gölgelemelere dönüşür (§8.7.4.5)
  • Sweep gradyanlarının PDF gölgeleme türü yoktur; merkez çevresinde, renk çizgisinden örneklenen 96 düz renkli kama olarak çizilirler
  • Dönüşümler cm operatörlerine dönüşür; ötelemeler FontSize / UnitsPerEm ile ölçeklenir
  • PaintComposite 13-27 modları, aynı adlı PDF blend modlarına — /Multiply, /Screen, /Luminosity ve gerisi (§11.3.5) — bir ExtGState /BM girdisi üzerinden eşlenir

Yarı saydam duraklı gradyanlar bir tuzağı daha saklar. COLR v1 renkleri alfa ile çarpılmış olarak enterpolasyon eder; PDF ise düz renk bekler ve alfa işlemini kendisi uygular. Çarpılmış değerleri PDF'e veren bir dönüştürücü, her yarı saydam bandı iki kez koyulaştırır. Renkler, enterpolasyondan sonra ve yazılmadan önce çarpımı geri alınarak düzeltilmelidir

HotPDF COLR v1 dönüşüm şeması: paint grafı döngü reddiyle 4096 düğüm, 64 derinlik düzeyi ve 1024 renk durağıyla sınırlı bir grafa ayrıştırılır; sonra PaintGlyph mod 7 clip olur, doğrusal ve radyal gradientler axial ile radial shadinglere, sweep gradientler 96 kama ve PaintComposite modları 13 ile 27 PDF blend mode'larına dönüşür
Tüm graf, ilk operatör yazılmadan önce desteklenen alt kümeye karşı sınanır; desteklenmeyen bir glyph sayfayı dokunulmamış bırakır ve yarım çizim bırakmak yerine raster yedeğine geçer

COLR v1 bileşik modlarına PDF/A-1 ya da PDF/X-1a altında ne olur?

PaintComposite'deki Porter-Duff modları (5-12: src_in, dest_out, src_atop, xor, plus ve kardeşleri) aynı adlı bir PDF blend moduna sahip değildir ve PDF belirtiminin bariz okuması onların ifade edilemeyeceğini söyler. O okuma fazla güçlüdür. İn/out modları bir operand üzerinde alfa soft maskesidir; atop, xor ve plus, yalıtılmış biçimlerden, grup alfasından, parlaklık maskelerinden ve blend aritmetiğinden kurulabilir. HotPDF onları öyle kurar; bu glifler vektör kalır

Bedeli şu ki o kurulumların çoğu PDF saydamlığı ister ve PDF/A-1, PDF/X-1a ile PDF/X-3 bunu yasaklar. PDFACompliance bir PDF/A-1 değeri ya da PDFXCompliance 'X-1a' ya da 'X-3' olduğunda HotPDF glifi çizimden önce sınar ve saydamlık olmadan çizilemeyecek durumları raster olayına yönlendirir: atop, xor ve plus bileşimleri; maskelenen operandı basit bir opak boyadan daha fazlası olan in/out bileşimleri; kısmi alfalı düz boyalar; yarı saydam duraklı gradyanlar; ve alfası 255'in altında COLR v0 katmanları. İn ve out modları, maskelenen taraf düz bir renk ya da gradyan olduğunda — isteğe bağlı bir glif kırpması ya da dönüşümle sarılmış — vektör kalır; çünkü o durum yalnızca opak bir gri tonlama maskesi ister. StrictVersionLock dosyayı PDF 1.4'ün altına sabitlediğinde aynı şey COLR v0 için de geçerlidir

Pdf.PDFACompliance := 'B';   // PDF/A-1b: saydamlığa izin yok
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF(EmojiFontPath);

if not Pdf.DrawRegisteredColorGlyph(Pdf.CurrentPage, $1F308,
  72, 200, EmojiFontName, 36, 0, 300) then
  // OnColorGlyphRasterize işleyicisi olmadan, yarı saydam
  // ya da bileşiklenmiş bir glif PDF/A-1 altında burada biter
  LogMissingColorGlyph($1F308);   // sizin kendi günlük rutininiz

Arşiv emojiyi renkli göstermek zorundaysa PDF/A-1b yerine PDF/A-2b'yi hedefleyin. Bölüm 2 saydamlığa izin verir; her COLR kurulumu vektör kalır ve yedek işleyici, her yarı saydam emoji için ana renderer olmaktan çıkıp güvenlik ağına döner. PDF/A-1'i adıyla zorunlu kılan iş akışlarında saklayın

Burada bir okuyucu tarafı ayarına daha yer var. HotPDF, COLR v1 gruplarını doğrusal ışık blend uzayında bileştirir; bazı render'lar bu gruplarda çok koyu kanalları kaybeder, bazıları grup blend uzaylarını büsbütün yok sayar. COLRv1DeviceSpaceGroupBlend (varsayılan False) grupların sıradan aygıt uzayında bileşenmesine izin verir. Takas, COLR v1 tekrar ve yansıma gradyanları makalesinde açıklanır; koyu yarı saydam tonlar kullanıcılarınızın gerçekten çalıştırdığı görüntüleyicide kayboluyorsa açın

SVG glyphleri ve bitmap strike'lar

SVG glifleri, HotPDF'in içe aktarılan SVG dosyaları için kullandığı güvenli kurucudan geçer ve sonuç bir Form XObject (§8.10) olarak kaydedilir; SVG'den Form XObject'e makalesinde anlatıldığı gibi. SVG tablosundaki belge gzip ile sıkıştırılmış olabilir. Sıkıştırılmış girdi 8 MB ile sınırlıdır, açma 8 KB'lık parçalarla yürür ve genişleyen boyut 32 MB'yi aşacaksa durur. Script'ler, foreignObject, javascript: URL'leri ve @import stil sayfaları kurucunun belgeyi büsbütün reddetmesine yol açar. Form, uzun kenarı font boyutuna eşit olacak şekilde ölçeklenir. Üretimden önce denemeye değer bir şey: bazı fontlar çok sayıda glifi tek bir paylaşılan SVG belgesine paketler ve kurucu o belgenin tamamını her glif için alır

Bitmap fontlar strike seçimi ve yerleşim meselesine indirgenir. CBDT için HotPDF, dikey ppem'i TargetPixelsPerEm'e en yakın olan CBLC boyutunu seçer, 17, 18 ve 19 görüntü biçimlerini kabul eder ve 19. biçimin metriklerini CBLC index alt tablosundan okur, çünkü o biçim kendi metriklerini hiç saklamaz. sbix için glifi gerçekten içeren en yakın strike kazanır; strike ofsetleri tabloya, glif ofsetleri strike'a göredir. Bir dupe kaydı başka bir glifin resmini yeniden kullanır ama kendi başlangıç ofsetlerini korur ve aramanın dıştaki başlangıcı ezmesine izin veren bir ayrıştırıcı resmi yana kaydırır. PNG ve JPEG verileri içeride çözülür, font boyutuna gerilmek yerine FontSize / PixelsPerEmY ile ölçeklenir ve herhangi bir piksel tümüyle opak değilken bir soft mask (§11.6.5.3) ile yazılır. sbix'teki TIFF verileri olaya gider

Raster yedek işleyicisini yazmak

Hiçbir yerli yol işlemediğinde HotPDF OnColorGlyphRasterize fırlatır ve işleyicinizin döndürdüğü RGBA bitmap'i yerleştirir. İşleyici yokken ya da Handled False bırakılmışken DrawRegisteredColorGlyph False döndürür ve hiçbir şey çizilmez. İşleyici biçimi, font baytlarını, çıkarılan varlığı (SVG belgesi — belki hâlâ gzip'li — ya da bitmap baytları; COLR için boş), glif kimliğini, paleti ve hedef piksel boyutunu alır

HotPDF raster yedeği şeması: OnColorGlyphRasterize, desteklenen alt kümenin dışında bir COLR v1 grafı, güvenli kurucunun reddettiği bir SVG belgesi ya da decodların okumadığı bir bitmap verisi için tetiklenir; formatı, font baytlarını, varlığı, GlyphID, PaletteIndex ve PixelSize geçirir ve döndürülen RGBA tamponu yalnızca uzunluğu tam olarak Width çarpı Height çarpı 4 olduğunda kabul edilir
Sıfır boyutlar, yanlış tampon uzunluğu ya da taşan boyutlar sayfaya dokunulmadan reddedilir; handler yoksa ya da Handled false ise çağrı False döndürür ve sayfa değişmeden kalır
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 sizin rasterizer'ınızdır; HotPDF API'si değildir.
  // Tam olarak Width * Height * 4 baytlık RGBA döndürmek zorundadır;
  // alfa düz olmalı (çarpılmış değil)
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

// Bağlama
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;

HotPDF sonucu, sayfa onu görmeden önce doğrular: sıfır boyutlar, uzunluğu tam olarak Width * Height * 4 olmayan bir tampon ya da taşacak kadar büyük boyutlar reddedilir ve çağrı False döndürür. Değişken renkli bir font için işleyici, kayıtlı eksen değerlerindeki statik örneği alır; bir ara özgün değişken fontu alırsa Sender üzerinde (THotPDF'e dönüştürerek) GetRegisteredVariableFontAxes çağrısı, render edilecek koordinatları verir. PixelSize, aynı TargetPixelsPerEm değeridir; yukarıdaki baskı çözünürlüğü öğüdü burada da geçerlidir

Rastgele kullanıcı metni basan bir hattın iki tür kaybı bilmesi gerekir: fontun hiç sahip olmadığı glifler ve renklerini yitiren glifler. İlkini eksik glif izleme makalesindeki çizim anı kapsama izleme bildirir; ikincisini DrawRegisteredColorGlyph'ten dönen bir False

COLR, SVG ve bitmap emoji render'ı, OpenType şekillendirme ve güvenli SVG kurucusu, Delphi ve C++Builder için HotPDF component'in parçalarının hepsidir