Το HotXLS, το εξάρτημα Excel για Delphi και C++Builder, μειώνει το μέγεθος ενσωματωμένων γραμματοσειρών PDF μέσω δημιουργίας υποσυνόλου γραμματοσειρών TrueType: κατά την εξαγωγή PDF καλεί τη συνάρτηση CreateFontPackage της βιβλιοθήκης συστήματος fontsub.dll των Windows για να ανακατασκευάσει μια ενσωματωμένη γραμματοσειρά TrueType γύρω μόνο από τα σημεία κώδικα Unicode που πράγματι χρησιμοποίησε ένα φύλλο εργασίας, αντί να αποστείλει ολόκληρο το αρχείο γραμματοσειράς. Μια αναφορά με διακόσιες γραμμές κινεζικών ονομάτων προϊόντων μπορεί να χρειάζεται μόνο μερικές εκατοντάδες ξεχωριστούς χαρακτήρες Han, ωστόσο οι γραμματοσειρές CJK που διανέμουν τα Windows τρέχουν συνήθως 5 έως 20 MB η καθεμία. Ενσωματώστε μία ολόκληρη, και η γραμματοσειρά μόνη της μπορεί να ξεπερνά σε βάρος κάθε άλλο αντικείμενο στο PDF μαζί
Το fontsub.dll δεν είναι μια βιβλιοθήκη που έχουν ξανακούσει οι περισσότεροι προγραμματιστές Delphi, και υπάρχει λόγος για αυτό: η Microsoft το διανέμει ως μια μικρή, αραιά τεκμηριωμένη βοηθητική DLL αντί για ένα κορυφαίο API Win32. Το HotXLS το αντιμετωπίζει ως προαιρετική δυνατότητα, όχι σκληρή εξάρτηση, οπότε το πώς ο εξαγωγέας τη φορτώνει, την καλεί, και επιστρέφει σε εναλλακτική λύση όταν λείπει λέει τόσα για τον αμυντικό προγραμματισμό Windows όσα και για τις μορφές γραμματοσειρών, και αξίζει να διατρέξουμε και τα δύο μισά αυτής της ιστορίας
Γιατί το κείμενο Unicode φουσκώνει μια εξαγωγή PDF του HotXLS;
Ο εξαγωγέας PDF του HotXLS καταφεύγει σε μια ενσωματωμένη γραμματοσειρά TrueType μόνο όταν το κείμενο του φύλλου εργασίας πέφτει έξω από το WinAnsi, και παραμένει στην ενσωματωμένη οικογένεια Helvetica τον υπόλοιπο χρόνο, την προεπιλεγμένη διαδρομή που καλύπτει σε βάθος το περιήγηση εξαγωγής φύλλου εργασίας σε PDF. Το WinAnsi καλύπτει το δυτικοευρωπαϊκό κείμενο αρκετά καλά ώστε πολλά βιβλία εργασίας να μην πυροδοτούν καθόλου ενσωμάτωση γραμματοσειράς: το PDF απλώς αναφέρεται στην Helvetica με το όνομά της και ο αναγνώστης την παρέχει τοπικά, οπότε το αρχείο παραμένει μικρό. Τη στιγμή που ένα κελί κρατά κάτι που το WinAnsi δεν μπορεί να αναπαραστήσει, ένα κινεζικό όνομα προϊόντος, μια κορεατική σημείωση, ένα περιπλανώμενο σύμβολο σε ένα σχόλιο, ο εξαγωγέας πρέπει να ενσωματώσει ένα πραγματικό πρόγραμμα γραμματοσειράς, επειδή ένας αναγνώστης PDF δεν έχει πηγή εναλλακτικού γλύφου για χαρακτήρες έξω από τις τυπικές 14 γραμματοσειρές
Το HotXLS εντοπίζει αυτή τη γραμματοσειρά αυτόματα, σαρώνοντας τον φάκελο Fonts των Windows για μια σύντομη λίστα εγκατεστημένων υποψηφίων, συμπεριλαμβανομένων των γραμματοσειρών ικανών για CJK που διανέμουν τα Windows για απόδοση κινεζικών και κορεατικών, εκτός αν η ιδιότητα UnicodeFontFile του εξαγωγέα ήδη δείχνει σε συγκεκριμένο αρχείο, και όποια γραμματοσειρά καταλήξει να επιλέξει ενσωματώνεται ολόκληρη προτού τρέξει καθόλου η δημιουργία υποσυνόλου. Αυτή η απαίτηση ενσωμάτωσης είναι ειδική για το PDF: οι διαδρομές εξαγωγής RTF και HTML του HotXLS διατηρούν το κείμενο Unicode ανέπαφο διαφεύγοντας σημεία κώδικα μέσα στη ροή byte αντί να αποστέλλουν πρόγραμμα γραμματοσειράς, γι' αυτό το πρόβλημα μεγέθους που καλύπτει αυτό το άρθρο δεν έχει αντίστοιχο σε αυτές τις δύο μορφές
uses
lxHandle, lxPDF;
var
Book: TXLSWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSWorkbook.Create;
try
Book.Open('catalog-cn.xlsx');
Exporter := TXLSPDFExport.Create;
try
// Optional: pin a specific CJK-capable font instead of the
// exporter's automatic Windows\Fonts scan.
Exporter.UnicodeFontFile := 'C:\Windows\Fonts\simhei.ttf';
Exporter.SaveAsPDF(Book.ActiveSheet, 'catalog-cn.pdf');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
Τι είναι το fontsub.dll, και γιατί να μη γραφτεί από την αρχή ένας δημιουργός υποσυνόλων;
Το fontsub.dll είναι μια μικρή βιβλιοθήκη συστήματος Windows, διανεμημένη από τα Windows XP, που εκθέτει μία μοναδική συνάρτηση σχετική εδώ: το CreateFontPackage. Δώστε της τα byte μιας γραμματοσειράς TrueType προέλευσης και μια λίστα σημείων κώδικα Unicode προς διατήρηση, και σας δίνει πίσω μια ελάχιστη γραμματοσειρά που εξακολουθεί να ικανοποιεί κάθε περιορισμό μορφής γραμματοσειράς: δείκτες γλύφων επαναριθμημένοι, glyf και loca ανακατασκευασμένα γύρω μόνο από τα διατηρημένα περιγράμματα, hmtx και cmap ξαναγραμμένα ώστε να ταιριάζουν. Το HotXLS δηλώνει τον τύπο δείκτη συνάρτησης απευθείας έναντι εκείνης της σύμβασης
const
TTFCFP_FLAGS_SUBSET = 1;
TTFMFP_SUBSET = 0;
TTFCFP_MS_PLATFORMID = 3;
TTFCFP_UNICODE_CHAR_SET = 1;
type
TCreateFontPackage = function(puchSrcBuffer: Pointer; ulSrcBufferSize: Cardinal;
var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: Cardinal;
var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: Word;
pusSubsetKeepList: PWordArray; usSubsetKeepListCount: Word;
lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: Pointer): Cardinal; cdecl;
Το να γράψετε τη δουλειά του CreateFontPackage με το χέρι αντί να το καλέσετε θα σήμαινε την υλοποίηση ενός σωστού δημιουργού υποσυνόλων TrueType: τη διάτρεξη σύνθετων γλύφων για να τραβήξετε κάθε γλύφο συστατικού που αναφέρει ένας διατηρημένος γλύφος, την ανακατασκευή μετατοπίσεων loca αφού αφαιρεθούν περιγράμματα, τον σεβασμό των bit άδειας ενσωμάτωσης στον πίνακα OS/2 μιας γραμματοσειράς, και το να τα κάνετε όλα αυτά σωστά σε όποιες παράξενες γραμματοσειρές τυχαίνει να έχει εγκαταστημένες το μηχάνημα ενός πελάτη. Η Microsoft έχει ήδη λύσει εκείνο το πρόβλημα και διανέμει τη λύση ως μέρος των ίδιων των Windows, οπότε η κλήση μιας DLL συστήματος που η ίδια συντηρεί, δοκιμάζει έναντι της δικής της στοίβας απόδοσης γραμματοσειρών, και διανέμει σε κάθε μηχάνημα δωρεάν κοστίζει στο HotXLS ένα δυναμικό φόρτωμα και έναν δείκτη συνάρτησης· η επαναϋλοποίηση της ίδιας λογικής θα σήμαινε την κατοχή ενός αναλυτή για μια δυαδική μορφή με δεκαετίες οριακών περιπτώσεων, για μια δυνατότητα που έχει σημασία μόνο όταν μια γραμματοσειρά τυχαίνει να είναι μεγάλη
Χτίζοντας τη λίστα διατήρησης από γλύφους που πράγματι αποδόθηκαν
Το HotXLS χτίζει τη λίστα διατήρησης δημιουργίας υποσυνόλου από έναν χάρτη που ήδη συντηρούσε για διαφορετικό λόγο, οπότε η λογιστική δεν κοστίζει τίποτα επιπλέον. Κάθε φορά που ο κώδικας απόδοσης σελίδας σχεδιάζει έναν χαρακτήρα που χρειάζεται την ενσωματωμένη γραμματοσειρά Unicode, αναζητά τον δείκτη γλύφου εκείνου του χαρακτήρα και καταγράφει το ζεύγος στο FUnicodeGlyphMap, έναν πίνακα γλύφου-σε-σημείο-κώδικα που επίσης οδηγεί το CMap ToUnicode του PDF ώστε η αντιγραφή-και-επικόλληση από το ολοκληρωμένο έγγραφο να επιστρέφει το αρχικό κείμενο αντί για ακατέργαστα αναγνωριστικά γλύφων. Μέχρι να ολοκληρωθούν οι ροές περιεχομένου σελίδας, εκείνος ο χάρτης ήδη απαριθμεί ακριβώς το σύνολο σημείων κώδικα Unicode που χρησιμοποίησε το έγγραφο, ούτε περισσότερο ούτε λιγότερο
var
keepList: array of Word;
keepCount, i: Integer;
codePoint: LongWord;
begin
SetLength(keepList, FUnicodeGlyphMap.Count);
keepCount := 0;
for i := 0 to FUnicodeGlyphMap.Count - 1 do
begin
codePoint := LongWord(StrToIntDef('$' + FUnicodeGlyphMap.ValueFromIndex[i], 0));
if codePoint > 0 then
begin
keepList[keepCount] := Word(codePoint);
Inc(keepCount);
end;
end;
end;
Κατά τον χρόνο οριστικοποίησης, το HotXLS διατρέχει τον ίδιο χάρτη μια δεύτερη φορά για να χτίσει τη λίστα διατήρησης που αναμένει το CreateFontPackage, έναν απλό πίνακα των σημείων κώδικα Unicode προς διατήρηση στη μορφή 16-bit που απαιτεί το όρισμα λίστας διατήρησης του API. Επειδή εκείνο το όρισμα είναι πίνακας λέξεων 16-bit, διευθυνσιοδοτεί καθαρά το Βασικό Πολυγλωσσικό Επίπεδο, που καλύπτει συνηθισμένο κείμενο CJK, κυριλλικό, ελληνικό, και αραβικό χωρίς περιπλοκή· ένα φύλλο εργασίας που βασίζεται σε χαρακτήρες συμπληρωματικού επιπέδου, ορισμένα emoji ή σπάνιες ιστορικές γραφές, βρίσκεται έξω από ό,τι μπορεί να ονομάσει απευθείας μία μοναδική καταχώριση λίστας διατήρησης, κάτι που αξίζει να το γνωρίζετε ως όριο παρά ως ελάττωμα, αφού η συντριπτική πλειονότητα των επιχειρηματικών υπολογιστικών φύλλων με πολύ Unicode ποτέ δεν πλησιάζει εκείνο το επίπεδο εξαρχής
Τι συμβαίνει όταν λείπει το fontsub.dll;
Το HotXLS ποτέ δεν υποθέτει ότι το fontsub.dll είναι παρόν, και η εξαγωγή PDF ποτέ δεν αποτυγχάνει επειδή δεν είναι. Η βιβλιοθήκη φορτώνεται δυναμικά τη στιγμή που χρειάζεται ένα υποσύνολο, με SafeLoadLibrary και GetProcAddress αντί για μια στατική εισαγωγή, ακριβώς επειδή το fontsub.dll δεν είναι ένα τεκμηριωμένο, εγγυημένα-παρόν δημόσιο API με τον τρόπο που είναι το kernel32.dll: είναι πακεταρισμένα εργαλεία ενσωμάτωσης γραμματοσειρών, και τίποτα στη σύμβαση της Microsoft δεν υπόσχεται ότι επιβιώνει σε κάθε SKU, κάθε κλάδο συντήρησης, ή κάθε επίπεδο συμβατότητας που προσπαθεί να προσομοιώσει τα Windows
var
hFontSub: HMODULE;
CreateFontPackage: TCreateFontPackage;
begin
hFontSub := SafeLoadLibrary('FontSub.dll');
if hFontSub = 0 then
Exit; // no subsetting available - keep the full embedded font
try
@CreateFontPackage := GetProcAddress(hFontSub, 'CreateFontPackage');
if not Assigned(CreateFontPackage) then
Exit;
// ... call CreateFontPackage, check its return code ...
finally
FreeLibrary(hFontSub);
end;
end;
Κάθε διαδρομή αποτυχίας διπλώνει πίσω στο ίδιο αποτέλεσμα. Μια απούσα DLL, μια απούσα εξαγωγή, ένας μη μηδενικός κωδικός επιστροφής, ή μια γραμματοσειρά της οποίας ο πίνακας OS/2 απαγορεύει τη δημιουργία υποσυνόλου μέσω των bit άδειας ενσωμάτωσής της, το HotXLS απλώς κρατά την πλήρη γραμματοσειρά που είχε ήδη ενσωματώσει και συνεχίζει. Τίποτα δεν εγείρει εξαίρεση, τίποτα δεν ματαιώνει την εξαγωγή, και ο καλών κώδικας ποτέ δεν χρειάζεται να τυλίξει μια βελτιστοποίηση γραμματοσειράς στον δικό του χειρισμό εξαιρέσεων· το εξαγόμενο PDF είναι έγκυρο και στις δύο περιπτώσεις, και η μόνη μεταβλητή είναι αν καταλήγει μικρό ή κάπως μεγαλύτερο
Πόσο μικρότερο γίνεται πράγματι το PDF;
Η δημιουργία υποσυνόλου γραμματοσειρών TrueType του HotXLS συνήθως συρρικνώνει το εξαγόμενο PDF ενός φύλλου εργασίας με πολύ Unicode σε κάπου μεταξύ ενός εικοστού και ενός όγδοου του μεγέθους του χωρίς υποσύνολο, μια μείωση 8 έως 20 φορές της οποίας η κλίμακα ακολουθεί το πόσο από μια πλήρη γραμματοσειρά αγγίζει πράγματι ένα δεδομένο έγγραφο: μια παραγγελία αγοράς χτισμένη γύρω από μερικές εκατοντάδες ξεχωριστούς κινεζικούς χαρακτήρες κρατά μόνο εκείνες τις λίγες εκατοντάδες γλύφους από τις δεκάδες χιλιάδες που διανέμει μια γραμματοσειρά CJK, ενώ ένα φύλλο που εκτείνεται σε ένα ευρύτερο μείγμα χαρακτήρων κρατά αναλογικά περισσότερους. Το HotXLS στρώνει ένα περαιτέρω πέρασμα συμπίεσης Flate πάνω από τα byte γραμματοσειράς υποσυνόλου προτού τα γράψει στη ροή /FontFile2 του PDF, την ίδια συμπίεση από την οποία ήδη περνά το υπόλοιπο των ροών περιεχομένου του εγγράφου, και τίποτα από αυτά δεν ζητά κάτι επιπλέον από τον καλούντα κώδικα: ένα φύλλο εργασίας που ποτέ δεν φεύγει από το WinAnsi ποτέ δεν αγγίζει αυτή τη διαδρομή και συνεχίζει να εξάγει μέσω απλής Helvetica, ενώ ένα φύλλο εργασίας που πράγματι πυροδοτεί τη διαδρομή γραμματοσειράς Unicode παίρνει δημιουργία υποσυνόλου αυτόματα, χωρίς ιδιότητα προς ρύθμιση και χωρίς ξεχωριστή κλήση να γίνει, και η μία ιδιότητα που εμπλέκεται, το UnicodeFontFile, απλώς επιλέγει ποια γραμματοσειρά ενσωματώνεται και υφίσταται δημιουργία υποσυνόλου, όχι αν συμβαίνει η δημιουργία υποσυνόλου
Η δημιουργία υποσυνόλου γραμματοσειρών είναι μια λεπτομέρεια μέσα στην ευρύτερη επιφάνεια εξαγωγής PDF του εξαρτήματος Excel HotXLS για Delphi, μαζί με τη σελιδοποίηση, τα μεταδεδομένα εκτύπωσης φύλλου εργασίας, και τις διαδρομές εξαγωγής CSV, HTML, και RTF με τις οποίες διατίθεται