Teknik Makale

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

HotPDF, renkli emojiyi bir PDF'e THotPDF.DrawRegisteredColorGlyph üzerinden çizer; bu, RegisterUnicodeTTF ile kaydedilmiş bir fontun renk verisini okur ve yerli PDF grafikleri olarak çıkarır: COLR v0 katmanları dolgulu glyph konturları olarak, COLR v1 paint grafları clipler, shadingler ve blend mode'lar olarak, SVG glyphleri Form XObjectleri olarak, CBDT ya da sbix bitmapleri ise görüntüler olarak. Yerli olarak eşleyemediği her şey, sessizce siyah bir şekle dönüşmek yerine OnColorGlyphRasterize olayına gider

Bu son cümle, bu kodun var olma sebebinin bütünüdür. Bir emoji fontunu sıradan yolla gömerseniz görüntüleyici konturu glyf ya da CFFden alır ve o anki dolgu rengiyle doldurur. Gülücük yüz siyah bir leke olarak gelir, bayrak bir dikdörtgen olarak gelir ve hattın hiçbir yerinden ses çıkmaz

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

Bir PDF font programının renkli glyph kavramı yoktur. ISO 32000-1 bir glyphi geçerli renkle boyanmış bir şekil sayar ve OpenType'ın sonradan eklediği renk tabloları — COLR/CPAL, SVG , CBDT/CBLC ve sbix — PDF görüntü modelinin parçası değildir; hiçbir görüntüleyici bunları gömülü bir fonttan okumak zorunda değildir. Renk, üreticinin hâlâ font baytlarına sahip olduğu ve hangi glyphi istediğini bildiği üretim anında sayfa içeriğine çevrilmek zorundadır. O çeviri biçime göre değişir ve sahada emoji fontları hepsini kullanır: katmanlı vektörler, gradient paint grafları, gömülü SVG belgeleri ve PNG strike'lar. HotPDF sonucu THPDFOpenTypeColorFormat olarak bildirir — otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG ve otcfSBIX değerleriyle — ve fontu sabit bir öncelikle sınar: önce COLR, sonra SVG, sonra CBDT, sonra sbix. Vektör verisi, font ikisini de taşıdığında bitmapleri yener; yakınlaştırılabilir ya da basılabilir bir belgede istediğiniz de 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 çağrı, beş biçim: renkli bir glyphi çözmek ve çizmek

THotPDF.GetRegisteredColorGlyphInfo, bir kod noktasının hangi yolu izleyeceğini yanıtlar ve DrawRegisteredColorGlyph o yolu izler. İkisi de kod noktasını, en son RegisterUnicodeTTFe geçirilen fontun karakter haritasında arar; dolayısıyla renk fontu, çağrı anında kayıtlı Unicode font olmak zorundadır. Çizim fonksiyonu, glyphin renk verisi yoksa ya da hiçbir yol onu render edemediyse False döndürür ve yedeği size bırakır

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 paleti 0, 300 ppeme 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 verisi yok: tek renkli kontura 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;

İki parametre ilgiyi hak ediyor. PaletteIndex bir CPAL paleti seçer; koyu arka plan paleti getiren bir font, glyphe dokunmadan değiştirilebilir. TargetPixelsPerEm yalnızca bitmap fontlar için önemli; sıfır bırakılırsa Round(FontSize * 96 / 72) varsayılanına döner, bu bir ekran çözünürlüğüdür ve örneğin baskı çıktısı için 300 istemesinin sebebi budur. Dürüst sınır imzadadır: çağrı tek bir kod noktası alır ve onu yalnızca cmap üzerinden eşler. ZWJ dizileri, ten tonu değiştiricileri ve bölgesel gösterge bayrakları GSUB ligatürleridir; onları birleştirmek, OpenType GSUB alternatifleri yazısının kapsadığı türden bir shaping problemidir, bu giriş noktasının sizin için yaptığı bir şey değil

COLR v0: palet renkleriyle istiflenmiş glyph katmanları

COLR v0 basit vakadır ve HotPDF onu doğrudan render eder: her base glyph, bir CPAL renk girdisiyle katman glyphleri listeler ve her katman, kendi dolgu rengiyle sıradan tek bir metin gösterme işlemine dönüşür, tablo sırasında istiflenir. Alfası 255in altında olan bir katman, eşleşen /ca ile /CA taşıyan bir graphics state parameter sözlüğü alır (ISO 32000-1 §8.4.5) ve her katman glyphi kullanılmış işaretlenir, böylece subsetter ona doğrudan kod noktası eşleşmese bile konturunu korur. İnsanları şaşırtan bir ayrıntı var: palet girdisi indeksi 0xFFFF, OpenType spesifikasyonunda "metin ön plan rengini kullan" demektir ve HotPDF bunu geçerli sayfa dolgu rengine değil siyaha çözer. Emoji fontları için bu nadiren önemli; bir glyphi renklendirmek için ön plan girdisine güvenen icon fontları için, metin renginizi izleyeceğini varsaymadan önce çıktıyı kontrol edin

HotPDF bir COLR v1 paint grafi nasıl PDF operatörlerine çevirir?

Paint tablolarını önce düz, sınırlı bir grafa ayrıştırıp sonra her düğümü bir PDF yapısına eşleyerek. Bir COLR v1 glyph, katman listesi değildir; paint kayıtlarından oluşan yönlendirilmiş döngüsüz bir grafdır ve düğümler PaintColrLayers ile PaintColrGlyph üzerinden paylaşılabilir. Parser onu 4096 paint düğümünde, 64 derinlik düzeyinde ve 1024 renk durağında sınırlar ve her düğümü aktif ya da bitmiş olarak izler; aktif bir düğüme geri referans — kötü niyetli bir fontun katman yeniden kullanımından kurabileceği bir döngü — içine özyinelemek yerine reddedilir. Offset tabanları, ilk uygulamanın yanıldığı yerdir. BaseGlyphPaintRecord offsetleri BaseGlyphListin başına göredir, LayerList paint offsetleri LayerListe göredir ve paint tablosu içindeki her Offset24 o paint tablosunun kendisine göredir. Üçünü de aynı tabana karşı çözerseniz tümüyle meşru glyphler sınır kontrolünden geçemez; bu da tam olarak bozuk font görünümü verir. Graf kurulduktan sonra eşleme doğrudandır:

  • PaintGlyph, glyph konturunu metin render modu 7 ile bir clip olarak set eder (ISO 32000-1 §9.3.6) ve çocuğunu onun içinde boyar
  • Düz boyalar kırpılmış bir dikdörtgeni doldurur; doğrusal gradientler çok duraklı axial shadinglere, radyal gradientler iki renkli radial shadinglere dönüşür (§8.7.4.5)
  • Sweep gradientlerin PDF karşılığı yoktur; HotPDF onları renk çizgisinden örneklenmiş 96 düz renkli kama ile yaklaştırır
  • Dönüşümler cm olarak çıkar, glyph baseline başlangıcı çevresinde eşleniklenir ve ötelemeler FontSize / UnitsPerEm ile ölçeklenir
  • PaintComposite modları 13 ile 27, /Multiply, /Screen ve /Luminosity gibi ayrıştırılabilir ve ayrıştırılamaz PDF blend mode'larına eşlenir (§11.3.5); bir ExtGState /BM girdisi üzerinden set edilir

Sınır açıktır. Porter-Duff modları 5 ile 12 (src_in, xor, plus ve gerisi) PDF blend mode karşılığı bulamaz; doğrusal ve radyal gradientlerde repeat ile reflect extend modları çıkarılmaz ve durakları farklı alfa değerleri taşıyan gradientler tek bir opaklıkla taklit edilmez. İkiden fazla duraklı radyal gradientler yalnızca ilk ve son renklerini korur. HotPDF, tek bir operatör yazmadan önce tüm grafi bu desteklenen alt kümeye karşı sınar; desteklenmeyen bir glyph sayfayı dokunulmamış bırakır ve yarım bir çizim bırakmak yerine raster yedeğine geçer

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

SVG glyphleri ve bitmap strike'lar

SVG glyphleri, HotPDF'in içe aktarılan SVG dosyaları için kullandığı aynı sınırlı kurucudan geçer ve sonuç bir Form XObject olarak kaydedilir (§8.10); tam olarak SVG'den Form XObject'e yazısında anlatıldığı gibi. SVG tablosundaki belge gzip ile sıkıştırılmış olabilir; açma 8 KB'lik parçalarla yürür ve açılmış boyut 32 MB'ı geçecek gibi olur olmaz durur, önce açıp sonra bakmak yerine, ve sıkıştırılmış girdinin kendisi 8 MB ile sınırlıdır. Profil bilinçli olarak kısıtlayıcıdır: scriptler, gömülü görüntüler, harici URL'ler, data: URI'leri ve yerel olmayan referanslar kapalı kalmaya başarısız olur. Form, uzun kenarı font boyutuna eşit olacak şekilde ölçeklenir ve baseline'a sabitlenir; bu, y-aşağı SVG koordinat sistemini y-yukarı PDF sistemine eşler. Kurucunun glyph için tüm SVG belgesini aldığını, glyphNNN elemanının seçilmediğini bilin; bu yüzden çok sayıda glyphi tek bir paylaşılan belgeye paketleyen fontlara güvenmeden önce test etmeye değer

Bitmap fontlar strike seçimi ve yerleşim meselesidir. CBDT için HotPDF, dikey ppem'i TargetPixelsPerEme 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 strike offsetleri tabloya, glyph offsetleri strike'a göredir ve bir dupe kaydı, kendi başlangıç offsetlerini korurken başka bir glyphin grafiğini yeniden kullanır; özyinelemenin dıştaki başlangıcı ezmesine izin vermek görüntüyü 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 ile yazılır (§11.6.5.3). sbix TIFF verileri çözülmez ve olaya gider

Bir glyph yerli olarak çizilemediğinde ne olur?

HotPDF OnColorGlyphRasterize tetikler ve handlerınızın döndürdüğü RGBA bitmapini yerleştirir; hiçbir şey atanmamışsa ya da handler Handledı false bırakırsa DrawRegisteredColorGlyph False döndürür ve sayfa değişmeden kalır. Olay, desteklenen alt kümenin dışında bir COLR v1 grafı için, güvenli kurucunun reddettiği bir SVG belgesi için ve iç decodların okumadığı bir bitmap verisi için tetiklenir. Handler; formatı, ham font baytlarını, çıkarılan varlığı (SVG belgesi, hâlâ gzip'li olabilir, ya da bitmap baytları; COLR v1 için boş), glyph IDyi, 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, bir HotPDF API'si değil.
  // Tam olarak Width * Height * 4 bayt RGBA döndürmeli.
  Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
    GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;

// Bağlantı
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;

HotPDF, sayfaya dokunmadan önce handler çıktısını doğrular: sıfır boyutlar, uzunluğu tam olarak Width * Height * 4 olmayan bir tampon ya da taşmaya yetecek kadar büyük boyutlar reddedilir ve çağrı False döndürür. Raster yedeği yine de rasterdır; bu yolla render edilen bir emoji vektör keskinliğini kaybeder, çıktı çözünürlüğünüzle eşleşen bir PixelSize isteyin. Renk yolunu eksik glyph izleme yazısındaki çizim zamanı kapsam kontrolleriyle eşleştirin; keyfi kullanıcı metnini işleyen bir hat hem eksik glyphleri hem rengini kaybetmiş glyphleri bildirebilir

Renkli glyph renderer, OpenType shaping yığını ve güvenli SVG kurucusu, Delphi ve C++Builder için mevcut HotPDF Delphi PDF component ile gelir