Τεχνικό Άρθρο

Μεταβλητές γραμματοσειρές σε PDF: Στατική δημιουργία στιγμιοτύπων στη Delphi

Το PDF δεν έχει καμία έννοια μεταβλητής γραμματοσειράς. Μια γραμματοσειρά ενσωματωμένη σε ένα αρχείο PDF είναι ένα σταθερό σύνολο περιγραμμάτων με σταθερές μετρικές, οπότε μια μεταβλητή γραμματοσειρά πρέπει να αναχθεί σε ένα στατικό στιγμιότυπο πριν μπορέσει να μπει σε ένα έγγραφο. Το HotPDF εκτελεί αυτή τη δημιουργία στιγμιοτύπου εσωτερικά: επιθεωρείτε τους άξονες μιας μεταβλητής γραμματοσειράς, επιλέγετε συντεταγμένες όπως βάρος 620 ή πλάτος 87.5, και η βιβλιοθήκη ενσωματώνει αυτές τις τιμές σε ένα πλήρες, αυτόνομο πρόγραμμα γραμματοσειράς που μπορεί να αποδώσει οποιοσδήποτε συμμορφούμενος αναγνώστης PDF

Ο λόγος που αυτό έχει σημασία είναι πρακτικός και όχι θεωρητικός. Οι εταιρείες γραμματοσειρών όλο και περισσότερο διανέμουν ένα μεταβλητό αρχείο αντί για μια ντουζίνα στατικά βάρη, και οι ομάδες σχεδίασης επιλέγουν τιμές που δεν παρέχει κανένα επώνυμο στιγμιότυπο. Χωρίς δημιουργία στιγμιοτύπου, μια μηχανή αναφορών είτε επιστρέφει στο προεπιλεγμένο στιγμιότυπο, το οποίο απορρίπτει τη σχεδιαστική απόφαση, είτε ενσωματώνει ολόκληρη τη μεταβλητή γραμματοσειρά και ελπίζει ότι το πρόγραμμα προβολής θα τιμήσει συντεταγμένες αξόνων που δεν έχει τρόπο να γνωρίζει, κάτι που κανένας αναγνώστης δεν είναι υποχρεωμένος να κάνει

Τι πρέπει πραγματικά να ανακατασκευάσει η δημιουργία στιγμιοτύπου;

Μια μεταβλητή γραμματοσειρά OpenType αποθηκεύει ένα προεπιλεγμένο περίγραμμα ανά γλυφή, συν ένα σύνολο δέλτα ευρετηριασμένων κατά θέση στον χώρο σχεδίασης. Η εφαρμογή μιας συντεταγμένης άξονα δεν είναι θέμα εγγραφής ενός αριθμού σε μια κεφαλίδα· σημαίνει διάσχιση του πίνακα gvar, παρεμβολή δέλτα για τη ζητούμενη θέση, μετακίνηση σημείων, και έπειτα επανυπολογισμό όλων όσων προήλθαν από αυτά τα σημεία. Το HotPDF ανακατασκευάζει τα περιγράμματα γλυφών, τον μεγάλο πίνακα loca, τις πλήρεις οριζόντιες και κάθετες μετρικές, το γενικό πλαίσιο οριοθέτησης της γραμματοσειράς και την προσαρμογή αθροίσματος ελέγχου sfnt

Εξίσου σημαντικό είναι τι αφαιρείται. Ένα στατικό στιγμιότυπο δεν πρέπει να διατηρεί τα fvar, avar, gvar, HVAR, VVAR, MVAR, STAT ή cvar, και ένα ξεπερασμένο DSIG πρέπει επίσης να φύγει, αφού τα υπογεγραμμένα bytes δεν υπάρχουν πια. Η παράλειψη οποιουδήποτε από αυτά παράγει μια γραμματοσειρά που ισχυρίζεται ότι είναι μεταβλητή ενώ φέρει περιγράμματα που έχουν ήδη μετακινηθεί, και αναγνώστες που πράγματι εφαρμόζουν παραλλαγές θα τις εφαρμόσουν τότε για δεύτερη φορά

Φαντασματικά σημεία, και η παγίδα διπλής εφαρμογής

Ο πιο λεπτός κανόνας σε ολόκληρη τη διαδικασία αφορά τις μετρικές. Στο gvar, ο αριθμός σημείων για μια γλυφή καλύπτει τα σημεία περιγράμματος, ή τα σημεία στοιχείων για μια σύνθετη γλυφή, συν τέσσερα φαντασματικά σημεία που κωδικοποιούν το αριστερό πλευρικό περιθώριο, το πλάτος προχωρήματος και τα κάθετα ισοδύναμά τους. Αυτά τα φαντασματικά σημεία υπόκεινται και τα ίδια σε δέλτα

Έτσι, όταν μια γραμματοσειρά έχει πίνακα gvar, το HotPDF παράγει τις οριζόντιες και κάθετες μετρικές από τα παρεμβεβλημένα φαντασματικά σημεία και δεν εφαρμόζει επιπλέον τα HVAR ή VVAR. Η εφαρμογή και των δύο είναι το κλασικό σφάλμα: η ίδια παραλλαγή εφαρμόζεται δύο φορές και κάθε πλάτος προχωρήματος βγαίνει ελαφρώς πιο φαρδύ, κάτι που εμφανίζεται ως κείμενο που σταδιακά μετατοπίζεται προς τα δεξιά σε μια στοιχισμένη γραμμή. Μόνο όταν μια γραμματοσειρά δεν έχει gvar η βιβλιοθήκη ενσωματώνει την αποθήκη παραλλαγής μετρικών απευθείας στα hmtx ή vmtx

Δύο ακόμη λεπτομέρειες διατηρούν τη γεωμετρία ακριβή. Τα φαντασματικά σημεία ποτέ δεν συμμετέχουν στην παρεμβολή περιγράμματος, οπότε σημεία που δεν αναφέρονται ρητά για μια απλή γλυφή συνάγονται με IUP ανά περίγραμμα, με τα φαντασματικά σημεία αποκλεισμένα. Και οι σύνθετες γλυφές λαμβάνουν τα δέλτα τους εφαρμοσμένα στις μετατοπίσεις στοιχείων που χρησιμοποιούν παραμέτρους XY, μετά την οποία τα όρια των παιδιών επανυπολογίζονται αναδρομικά. Αυτή η αναδρομή έχει φραγμένο βάθος και ελέγχεται για κύκλους, επειδή ένα εχθρικό ή απλώς κατεστραμμένο γράφημα στοιχείων μπορεί διαφορετικά να αναδρομεί επ' αόριστον

Επιθεώρηση του χώρου σχεδίασης πριν την επιλογή

Η πρώτη κλήση σε κάθε ροή εργασίας δημιουργίας στιγμιοτύπου είναι η InspectVariableFont, η οποία αναφέρει τους άξονες και τα επώνυμα στιγμιότυπα που όρισε η εταιρεία γραμματοσειρών. Οι εγγραφές αξόνων φέρουν την ετικέτα τεσσάρων byte, την ελάχιστη, προεπιλεγμένη και μέγιστη τιμή, σημαίες και ένα αναγνωριστικό ονόματος· τα επώνυμα στιγμιότυπα φέρουν αναγνωριστικό ονόματος υποοικογένειας, σημαίες, προαιρετικό αναγνωριστικό ονόματος PostScript και μία συντεταγμένη ανά άξονα:

var
  Pdf: THotPDF;
  Axes: THPDFVariableFontAxisArray;
  Instances: THPDFVariableFontNamedInstanceArray;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
    begin
      for I := 0 to High(Axes) do
        Writeln(Format('%s  min=%.1f default=%.1f max=%.1f',
          [string(Axes[I].Tag), Axes[I].MinimumValue,
           Axes[I].DefaultValue, Axes[I].MaximumValue]));
      Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
    end
    else
      Writeln('not a variable font - embed it as an ordinary TrueType face');
  finally
    Pdf.Free;
  end;
end;

Η αναφορά του εύρους αξόνων έχει σημασία επειδή οι τιμές αξόνων περιορίζονται στο εύρος που δηλώνει η γραμματοσειρά, όχι στο εύρος που προσφέρει το περιβάλλον χρήστη σας. Ένα ρυθμιστικό που επιτρέπει σε έναν χρήστη να ζητήσει βάρος 1000 σε μια γραμματοσειρά της οποίας ο άξονας wght σταματά στο 900 πρέπει να διορθωθεί στη διεπαφή, όχι σιωπηλά στο επίπεδο γραμματοσειράς, αλλιώς η εκτυπωμένη έξοδος θα διαφωνεί με την προεπισκόπηση

Επιλογή συντεταγμένων και δημιουργία του εγγράφου

Η επιλογή άξονα είναι με κατάσταση και ισχύει για γραμματοσειρές που καταχωρούνται στη συνέχεια. Η SetVariableFontAxis δέχεται μια εκτυπώσιμη ετικέτα ASCII τεσσάρων byte και μια πεπερασμένη τιμή, και απορρίπτει οτιδήποτε άλλο με εξαίρεση αντί να το αγνοήσει σιωπηλά. Η ClearVariableFontAxes επαναφέρει την επιλογή, και η GetVariableFontAxisSelections αναφέρει τι εκκρεμεί αυτή τη στιγμή, κάτι που αξίζει να καταγράφεται σε μηχανές αναφορών όπου πολλαπλές διαδρομές κώδικα μπορεί να έχουν αγγίξει το ίδιο αντικείμενο εγγράφου. Η ίδια η οικογένεια επιλέγεται με όνομα μέσω της SetFont, ακριβώς όπως κάθε άλλη ενσωματωμένη γραμματοσειρά TrueType:

begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;

    Pdf.SetVariableFontAxis('wght', 620);   // ημιβαρύ, όχι επώνυμο στιγμιότυπο
    Pdf.SetVariableFontAxis('wdth', 87.5);  // ελαφρώς συμπυκνωμένο
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // επιστροφή στο προεπιλεγμένο στιγμιότυπο
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Το συμβάν OnVariableFontInstance ενεργοποιείται καθώς παράγεται κάθε στιγμιότυπο και αναφέρει τις τιμές αξόνων που χρησιμοποιήθηκαν, που είναι ο φθηνότερος τρόπος να αποδείξετε σε ένα αρχείο καταγραφής τι πραγματικά περιέχει ένα δεδομένο PDF. Επειδή κάθε ξεχωριστό σύνολο συντεταγμένων παράγει ξεχωριστό πρόγραμμα γραμματοσειράς, αντιμετωπίστε τις επιλογές αξόνων ως μέρος του κλειδιού προσωρινής μνήμης γραμματοσειράς σας· οι μηχανισμοί προσωρινής αποθήκευσης περιγράφονται στην μόνιμη προσωρινή μνήμη υποσυνόλων γραμματοσειράς

Πώς αλληλεπιδρά η δημιουργία στιγμιοτύπου με την υποσυνολοποίηση και τη διαμόρφωση;

Η δημιουργία στιγμιοτύπου εκτελείται πριν την υποσυνολοποίηση, και αυτή η σειρά είναι η σωστή. Η γραμματοσειρά στιγμιοτύπου είναι μια κανονική στατική γραμματοσειρά TrueType, οπότε ο συνηθισμένος υποσυνολοποιητής τη μεταχειρίζεται όπως κάθε άλλη: υπολογίζει το κλείσιμο γλυφών, κρατά τις γλυφές που πράγματι χρησιμοποιεί το έγγραφο και απορρίπτει τις υπόλοιπες. Η αλληλεπίδραση που πρέπει να γνωρίζετε είναι ότι δύο διαφορετικές επιλογές αξόνων της ίδιας οικογένειας είναι δύο διαφορετικά προγράμματα γραμματοσειράς, οπότε ένα έγγραφο που αναμειγνύει βάρος 400 και βάρος 620 ενσωματώνει δύο υποσύνολα, όχι μία κοινή γραμματοσειρά με δύο στιγμιότυπα

Η διαμόρφωση δεν επηρεάζεται καταρχήν και αξίζει να ελεγχθεί στην πράξη. Τα χαρακτηριστικά διάταξης βρίσκονται στα GSUB και GPOS, τα οποία η δημιουργία στιγμιοτύπου διατηρεί, οπότε οι συνδέσεις και οι στιλιστικές εναλλακτικές συνεχίζουν να λειτουργούν όπως περιγράφεται στις στιλιστικές εναλλακτικές OpenType GSUB. Αυτό που αλλάζει είναι η τοποθέτηση: ένα συμπυκνωμένο στιγμιότυπο έχει στενότερα προχωρήματα από το προεπιλεγμένο, οπότε οποιαδήποτε διάταξη μέτρησε κείμενο πριν τη δημιουργία στιγμιοτύπου μέτρησε λάθος πλάτη. Μετρήστε με την ίδια επιλογή άξονα με την οποία θα αποδώσετε, και η ασυμφωνία εξαφανίζεται

Μια τελευταία αμυντική σημείωση από την υλοποίηση, χρήσιμη για όποιον επεκτείνει αυτή τη διαδρομή. Γραμματοσειρές χωρίς κάθετες μετρικές εξακολουθούν να αξιολογούν δυναμικά ορίσματα πίνακα στο σημείο κλήσης της Delphi, οπότε οι πίνακες χρόνου ανάλυσης πάντα εκχωρούνται αντί να βασίζονται σε έναν έλεγχο HasVerticalMetrics για να παρακάμψουν ένα κενό ευρετήριο. Είναι το είδος λεπτομέρειας επιπέδου γλώσσας που μετατρέπει έναν φαινομενικά προστατευμένο κλάδο σε παραβίαση πρόσβασης ακριβώς στις γραμματοσειρές που δεν δοκιμάσατε

Η υποστήριξη μεταβλητών γραμματοσειρών εντάσσεται στον ίδιο αγωγό γραμματοσειρών με την ενσωμάτωση, την υποσυνολοποίηση και το κλείσιμο γλυφών, που περιγράφεται με περισσότερη λεπτομέρεια στο κλείσιμο υποσυνόλου γραμματοσειράς και διαμορφωμένες γλυφές. Το πλήρες σύνολο χαρακτηριστικών τυπογραφίας για Delphi και C++Builder αναφέρεται στη σελίδα του εξαρτήματος HotPDF PDF για Delphi