Το HotPDF ζωγραφίζει color emoji σε PDF μέσω του THotPDF.DrawRegisteredColorGlyph, που διαβάζει τα color δεδομένα γραμματοσειράς καταχωρισμένης με RegisterUnicodeTTF και τα βγάζει ως native PDF γραφικά: layers COLR v0 ως γεμάτα glyph outlines, paint graphs COLR v1 ως clips, shadings και blend modes, glyphs SVG ως Form XObjects, και bitmaps CBDT ή sbix ως εικόνες. Οτιδήποτε δεν μπορεί να αντιστοιχίσει natively πάει στο event OnColorGlyphRasterize αντί να γίνει σιωπηλά μαύρο σχήμα
Εκείνη η τελευταία ρήτρα είναι όλος ο λόγος που υπάρχει αυτός ο κώδικας. Ενσωματώστε emoji font με τον συνηθισμένο τρόπο και ο viewer παίρνει το outline από glyf ή CFF, γεμάτο με ό,τι τυχαίνει να είναι το τρέχον χρώμα γεμίσματος. Το χαμογελαστό πρόσωπο φτάνει ως μαύρη κηλίδα, η σημαία ως ορθογώνιο, και τίποτα στο pipeline δεν παραπονιέται
Γιατί ένα color emoji τυπώνεται ως μαύρη σιλουέτα σε PDF;
Ένα font program PDF δεν έχει καμία έννοια color glyphs. Το ISO 32000-1 μεταχειρίζεται glyph ως σχήμα ζωγραφισμένο με το τρέχον χρώμα, και οι πίνακες χρωμάτων που πρόσθεσε αργότερα το OpenType, δηλαδή COLR/CPAL, SVG , CBDT/CBLC και sbix, δεν είναι μέρος του imaging μοντέλου του PDF, οπότε κανένας viewer δεν υποχρεούται να τους διαβάσει από ενσωματωμένη γραμματοσειρά. Το χρώμα πρέπει να μεταφραστεί σε περιεχόμενο σελίδας τη στιγμή της παραγωγής, όσο ο παραγωγός κρατά ακόμα τα bytes της γραμματοσειράς και ξέρει ποιο glyph θέλει. Εκείνη η μετάφραση διαφέρει ανά μορφή, και τα emoji fonts στη φύση χρησιμοποιούν όλες: layered vectors, gradient paint graphs, ενσωματωμένα SVG έγγραφα και PNG strikes. Το HotPDF αναφέρει το αποτέλεσμα ως THPDFOpenTypeColorFormat, με τις τιμές otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG και otcfSBIX, και τεστάρει τη γραμματοσειρά σε σταθερή προτεραιότητα: πρώτα COLR, μετά SVG, μετά CBDT, μετά sbix. Τα vector δεδομένα κερδίζουν τα bitmaps όποτε μια γραμματοσειρά κουβαλά και τα δύο, που είναι ό,τι θέλετε σε έγγραφο που μπορεί να γίνει zoom ή να τυπωθεί
Μία κλήση, πέντε μορφές: λύση και ζωγραφική ενός color glyph
Το THotPDF.GetRegisteredColorGlyphInfo απαντά ποιο μονοπάτι θα πάρει ένα code point, και το DrawRegisteredColorGlyph το παίρνει. Και τα δύο ψάχνουν το code point στον character map της γραμματοσειράς που πέρασε πιο πρόσφατα στο RegisterUnicodeTTF, οπότε το color font πρέπει να είναι η καταχωρισμένη Unicode γραμματοσειρά τη στιγμή της κλήσης. Η συνάρτηση σχεδίασης επιστρέφει False όταν το glyph δεν έχει color δεδομένα ή κανένα μονοπάτι δεν μπορούσε να το αποδώσει, και αφήνει το fallback σε εσάς
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 0, bitmap strike πιο κοντά σε 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
// Κανένα color δεδομένο: πέστε πίσω στο μονόχρωμο outline
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 διαλέγει παλέτα CPAL, οπότε γραμματοσειρά που παραδίδει παλέτα σκούρου φόντου μπορεί να αλλάξει χωρίς να αγγίξετε το glyph. Το TargetPixelsPerEm έχει σημασία μόνο για bitmap γραμματοσειρές· στο μηδέν defaults στο Round(FontSize * 96 / 72), μια ανάλυση οθόνης, γι' αυτό το παράδειγμα ζητά 300 για εκτύπωση. Το ειλικρινές όριο κάθεται στην υπογραφή: η κλήση παίρνει ένα code point και το αντιστοιχίζει μόνο μέσω cmap. Αλληλουχίες ZWJ, modifiers τόνου δέρματος και σημαίες regional-indicator είναι ligatures GSUB, οπότε η σύνθεσή τους είναι πρόβλημα shaping του είδους που καλύπτει το άρθρο GSUB alternates του OpenType, όχι κάτι που κάνει αυτό το σημείο εισόδου για εσάς
COLR v0: στοιβαγμένα glyph layers με χρώματα παλέτας
Το COLR v0 είναι η απλή περίπτωση και το HotPDF το αποδίδει απευθείας: κάθε base glyph απαριθμεί layer glyphs με εγγραφή χρώματος CPAL, και κάθε layer γίνεται μία συνηθισμένη πράξη text-showing με δικό της χρώμα γεμίσματος, στοιβαγμένη σε σειρά πίνακα. Layer με alpha κάτω από 255 παίρνει dictionary παραμέτρων graphics state με αντίστοιχα /ca και /CA (ISO 32000-1 §8.4.5), και κάθε layer glyph σημειώνεται ως χρησιμοποιούμενο ώστε ο subsetter να κρατά το outline του παρόλο που κανένα code point δεν τον αντιστοιχίζει άμεσα. Μία λεπτομέρεια εκπλήσσει κόσμο: ο δείκτης εγγραφής παλέτας 0xFFFF σημαίνει «χρησιμοποίησε το χρώμα προσκηνίου του κειμένου» στην προδιαγραφή OpenType, και το HotPDF τον λύνει σε μαύρο αντί για το τρέχον χρώμα γεμίσματος σελίδας. Για emoji fonts αυτό σπάνια έχει σημασία· για icon fonts που βασίζονται στην εγγραφή προσκηνίου για να βάψουν glyph, ελέγξτε την έξοδο πριν υποθέσετε ότι θα ακολουθήσει το χρώμα κειμένου σας
Πώς μετατρέπει το HotPDF paint graph COLR v1 σε operators PDF;
Αναλύοντας τους paint πίνακες πρώτα σε flat, οριοθετημένο γράφο και μόνο μετά αντιστοιχίζοντας κάθε node σε PDF δομή. Ένα glyph COLR v1 δεν είναι λίστα layers αλλά directed acyclic γράφος paint εγγραφών, όπου nodes μπορούν να μοιράζονται μέσω PaintColrLayers και PaintColrGlyph. Ο parser το περικόπτει σε 4096 paint nodes, 64 επίπεδα βάθους και 1024 color stops, και ιχνηλατεί κάθε node ως ενεργό ή τελειωμένο ώστε αναφορά πίσω σε ενεργό node, κύκλος που μπορεί να χτίσει κακόβουλη γραμματοσειρά από επαναχρησιμοποίηση layers, να απορρίπτεται αντί να αναδύεται μέσα του. Οι βάσεις offsets είναι εκεί όπου μια πρώτη υλοποίηση πάει στραβά. Τα offsets BaseGlyphPaintRecord είναι σχετικά με την αρχή του BaseGlyphList, τα paint offsets του LayerList είναι σχετικά με το LayerList, και κάθε Offset24 μέσα σε paint πίνακα είναι σχετικό με τον ίδιο εκείνο τον paint πίνακα. Λύστε και τα τρία απέναντι στην ίδια βάση και απόλυτα νόμιμα glyphs αποτυγχάνουν τον έλεγχο ορίων, που μοιάζει ακριβώς με χαλασμένη γραμματοσειρά. Μόλις χτιστεί ο γράφος, η αντιστοίχιση είναι ευθεία:
- Το
PaintGlyphθέτει το glyph outline ως clip με text rendering mode 7 (ISO 32000-1 §9.3.6), μετά ζωγραφίζει το child του μέσα του - Συμπαγή paints γεμίζουν κομμένο ορθογώνιο· τα linear gradients γίνονται multi-stop axial shadings και τα radial gradients δυόχρωμες radial shadings (§8.7.4.5)
- Τα sweep gradients δεν έχουν PDF αντίστοιχο, οπότε το HotPDF τα προσεγγίζει με 96 σφήνες συμπαγούς χρώματος, καθεμία με δείγμα από τη color γραμμή
- Τα transforms βγαίνουν ως
cm, συζευγμένα γύρω από την αρχή baseline του glyph, με μετατοπίσεις κλιμακωμένες κατάFontSize / UnitsPerEm - Οι καταστάσεις 13 έως 27 του
PaintCompositeαντιστοιχίζονται στα separable και non-separable PDF blend modes όπως/Multiply,/Screenκαι/Luminosity(§11.3.5), set μέσω εγγραφής/BMσε ExtGState
Το όριο είναι ρητό. Οι καταστάσεις Porter-Duff 5 έως 12 (src_in, xor, plus και τα λοιπά) δεν έχουν PDF αντίστοιχο blend mode, οι καταστάσεις επέκτασης repeat και reflect σε linear και radial gradients δεν εκδίδονται, και gradients των οποίων τα stops κουβαλάνε διαφορετικές τιμές alpha δεν προσποιούνται με μία απλή opacity. Radial gradients με πάνω από δύο stops κρατούν μόνο τα πρώτα και τελευταία χρώματά τους. Το HotPDF ελέγχει ολόκληρο τον γράφο απέναντι σε αυτό το υποστηριζόμενο υποσύνολο πριν γράψει έναν μόνο operator, οπότε ένα μη υποστηριζόμενο glyph αφήνει τη σελίδα άθικτη και προχωρά στο raster fallback αντί να αφήσει πίσω μισό σχέδιο
Glyphs SVG και bitmap strikes
Τα glyphs SVG περνούν από τον ίδιο οριοθετημένο builder που χρησιμοποιεί το HotPDF για εισαγόμενα SVG αρχεία, και το αποτέλεσμα καταχωρίζεται ως Form XObject (§8.10), ακριβώς όπως περιγράφεται στο άρθρο SVG σε Form XObject. Το έγγραφο στον πίνακα SVG μπορεί να είναι gzip-συμπιεσμένο· η αποσυμπίεση τρέχει σε κομμάτια 8 KB και σταματά μόλις το διασταλμένο μέγεθος θα ξεπερνούσε 32 MB, αντί να φουσκώσει πρώτα και να ελέγξει μετά, και η ίδια η συμπιεσμένη είσοδος περικόπτεται στα 8 MB. Το profile είναι περιοριστικό επίτηδες: scripts, ενσωματωμένες εικόνες, εξωτερικά URLs, URIs data: και μη τοπικές αναφορές αποτυγχάνουν κλειστά. Το form κλιμακώνεται ώστε η μεγαλύτερη πλευρά του να ισούται με το μέγεθος γραμματοσειράς και αγκυρώνεται στη baseline, που αντιστοιχίζει το σύστημα συντεταγμένων y-κάτω του SVG στο y-πάνω του PDF. Γνωρίζετε ότι ο builder δέχεται ολόκληρο το SVG έγγραφο για το glyph, χωρίς επιλογή του στοιχείου glyphNNN, οπότε γραμματοσειρές που πακετάρουν πολλά glyphs σε ένα κοινό έγγραφο αξίζει να τεσταριστούν πριν επενδύσετε σε αυτές
Τα bitmap fonts είναι θέμα επιλογής strike και τοποθέτησης. Για CBDT, το HotPDF διαλέγει το μέγεθος CBLC του οποίου το κατακόρυφο ppem είναι πιο κοντά στο TargetPixelsPerEm, δέχεται μορφές εικόνας 17, 18 και 19, και διαβάζει metrics μορφής 19 από την υπο-πίνακα CBLC index επειδή εκείνη η μορφή δεν αποθηκεύει δικά της. Για sbix, τα offsets strikes είναι σχετικά με τον πίνακα και τα offsets glyphs με το strike, και εγγραφή dupe ξαναχρησιμοποιεί το graphic άλλου glyph κρατώντας τα δικά της origin offsets· το να αφήσετε την αναδρομή να ξαναγράψει το εξωτερικό origin μετατοπίζει την εικόνα. Τα payloads PNG και JPEG αποκωδικοποιούνται εσωτερικά, κλιμακώνονται κατά FontSize / PixelsPerEmY αντί να τεντωθούν στο μέγεθος γραμματοσειράς, και γράφονται με soft mask (§11.6.5.3) όποτε οποιοδήποτε pixel δεν είναι απόλυτα αδιαφανές. Τα payloads TIFF του sbix δεν αποκωδικοποιούνται και πάνε στο event
Τι συμβαίνει όταν ένα glyph δεν μπορεί να ζωγραφιστεί natively;
Το HotPDF σηκώνει OnColorGlyphRasterize και τοποθετεί ό,τι RGBA bitmap επιστρέψει ο handler σας· αν δεν έχει ανατεθεί τίποτα, ή ο handler αφήνει το Handled false, το DrawRegisteredColorGlyph επιστρέφει False και η σελίδα μένει αμετάβλητη. Το event πυροδοτείται για γράφο COLR v1 εκτός υποστηριζόμενου υποσυνόλου, για SVG έγγραφο που ο ασφαλής builder αρνήθηκε, και για bitmap payload που οι εσωτερικοί decoders δεν διαβάζουν. Ο handler παίρνει τη μορφή, τα raw bytes γραμματοσειράς, το εξαχθέν asset (το SVG έγγραφο, πιθανώς ακόμα gzipped, ή τα bitmap bytes· κενό για COLR v1), το glyph ID, την παλέτα και το μέγεθος pixel στόχου
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 είναι ο rasterizer σας, όχι API του HotPDF.
// Πρέπει να επιστρέφει ακριβώς Width * Height * 4 bytes RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Σύνδεση
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
Το HotPDF επικυρώνει την έξοδο του handler πριν αγγίξει τη σελίδα: μηδενικά μεγέθη, buffer του οποίου το μήκος δεν είναι ακριβώς Width * Height * 4, ή διαστάσεις αρκετά μεγάλες να ξεχειλίσουν απορρίπτονται και η κλήση επιστρέφει False. Ένα raster fallback εξακολουθεί να είναι raster, οπότε emoji αποδομένο έτσι χάνει τη vector ευκρίνειά του· ζητήστε PixelSize που ταιριάζει την ανάλυση εξόδου σας. Ζευγαρώστε το color μονοπάτι με ελέγχους κάλυψης τη στιγμή της σχεδίασης από το άρθρο ιχνηλάτησης χαμένων glyphs και ένα pipeline που χειρίζεται αυθαίρετο κείμενο χρήστη μπορεί να αναφέρει και χαμένα glyphs και glyphs που έχασαν το χρώμα τους
Ο renderer color glyphs, η στοίβα shaping OpenType και ο ασφαλής SVG builder παραδίδονται όλα στο HotPDF Delphi PDF component, διαθέσιμο για Delphi και C++Builder