Στείλτε την αραβική πρόταση يوضح ملف PDF هذا στο απλό TextOut και η σελίδα που επιστρέφει είναι λάθος με δύο τρόπους ταυτόχρονα. Οι λέξεις πηγαίνουν από αριστερά προς τα δεξιά αντί από δεξιά προς τα αριστερά, και τα γράμματα είναι απομακρυσμένα στις απομονωμένες μορφές τους αντί να ενώνονται σε συνδεδεμένες λέξεις. Δεν προκύπτει κανένα σφάλμα. Το Delphi κάνει μεταγλώττιση (compile), το αρχείο ανοίγει, και ένας ελεγκτής που διαβάζει Αραβικά σας λέει ότι το αποτέλεσμα είναι άχρηστο. Η λύση είναι μία κλήση, όχι αλλαγή βιβλιοθήκης: το HotPDF δρομολογεί το κείμενο από δεξιά προς τα αριστερά μέσω μιας ξεχωριστής μεθόδου, της RtLTextOut, η οποία χειρίζεται την αναδιάταξη που το απλό TextOut δεν κάνει. Τέσσερα πράγματα σχετικά με αυτήν τη μέθοδο καθορίζουν αν το αποτέλεσμα θα είναι χρησιμοποιήσιμο: τι κάνει στη συμβολοσειρά (string), πώς το όρισμα charset της επιλέγει το σύστημα γραφής (script), η αλλαγή σε επίπεδο εγγράφου που επιφέρει ως παρενέργεια, και η εργασία με τη γραμματοσειρά (font) που πρέπει να προηγηθεί
Γιατί η γραφή από δεξιά προς τα αριστερά χρειάζεται τη δική της κλήση
Μια ροή περιεχομένου PDF δεν αποθηκεύει επεξεργάσιμο κείμενο. Αποθηκεύει γλύφους (glyphs) σε σταθερές θέσεις, το οποίο σημαίνει ότι ό,τι εκπέμπει τη ροή έχει την ευθύνη να αποφασίσει με ποια σειρά θα μπουν αυτοί οι γλύφοι. Στην οθόνη, το λειτουργικό σύστημα το έκανε αυτό για εσάς: ρίξτε Αραβικά σε ένα TEdit και η στοίβα κειμένου του λειτουργικού συστήματος τα αναδιατάσσει και τα ενώνει πριν καν δείτε ένα pixel. Αυτός είναι ακριβώς ο λόγος που η συμβολοσειρά φαίνεται τέλεια στη φόρμα σας και χαλάει στο PDF. Το περιβάλλον εργασίας (desktop) έκανε τη δουλειά σιωπηλά, και τη στιγμή που γράφετε τη δική σας ροή περιεχομένου, η δουλειά επιστρέφει σε εσάς
Το TextOut σας παίρνει τοις μετρητοίς. Σχεδιάζει τα σημεία κώδικα (codepoints) με τη σειρά που τα περνάτε, από αριστερά προς τα δεξιά, πράγμα που είναι σωστό για Λατινικά, Κυριλλικά και CJK και λάθος για Αραβικά και Εβραϊκά. Η RtLTextOut είναι η κλήση που αναδιατάσσει τη γραμμή σε οπτική σειρά από δεξιά προς τα αριστερά πρώτα, και μετά τη σχεδιάζει. Το HotPDF κρατά τις δύο μεθόδους εσκεμμένα ξεχωριστές αντί να μαντεύει την κατεύθυνση από τους χαρακτήρες, επομένως η επιλογή του ποια θα καλέσετε είναι και η επιλογή του ποια συμπεριφορά συστήματος γραφής (script) θα έχετε. Οι βαθύτεροι μηχανισμοί της αμφίδρομης αναδιάταξης (bidirectional reordering) και της αραβικής σύνδεσης με βάση τα συμφραζόμενα (contextual joining) είναι ξεχωριστό θέμα, που καλύπτεται στο άρθρο Διαμόρφωση αραβικού και RTL κειμένου με το HotPDF· εδώ το πρακτικό σημείο είναι πιο στενό. Χρησιμοποιήστε το RtLTextOut για εκτελέσεις (runs) από δεξιά προς τα αριστερά, χρησιμοποιήστε το TextOut για οτιδήποτε άλλο, και ποτέ μην δρομολογείτε το ένα μέσω του άλλου

Το όρισμα charset αποφασίζει το σύστημα γραφής (script)
Αυτό που λέει στο RtLTextOut αν διατάσσει Αραβικά ή Εβραϊκά δεν είναι η μέθοδος, είναι η γραμματοσειρά. Το SetFont παίρνει ένα Windows charset ως τέταρτο όρισμά του, και αυτή η τιμή μεταφέρει τους κανόνες γραφής στην κλήση από δεξιά προς τα αριστερά: το 178 επιλέγει Αραβικά, το 177 επιλέγει Εβραϊκά. Ορίστε το charset, μετά σχεδιάστε, και οι δύο παρακάτω γραμμές βγαίνουν στη σωστή σειρά ανάγνωσης χωρίς καμία περαιτέρω ρύθμιση
// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Δύο λεπτομέρειες σχετικά με αυτές τις συντεταγμένες είναι εύκολο να σας ξεφύγουν. Η θέση που περνάτε είναι ακόμα η αρχή της εκτέλεσης (run) στο δικό της σύστημα συντεταγμένων της σελίδας, μετρημένη από την κάτω αριστερή γωνία με το Y να αυξάνεται προς τα πάνω, την ίδια αρχή που χρησιμοποιεί κάθε TextOut· το RtLTextOut αλλάζει τη σειρά των γλύφων, όχι το από πού μετράει η σελίδα. Και όπως σε κάθε κλήση σχεδίασης, το SetFont πρέπει να προηγείται και πρέπει να επαναλαμβάνεται μετά από κάθε AddPage, επειδή η τρέχουσα γραμματοσειρά δεν επιβιώνει από μια αλλαγή σελίδας. Ξεχάστε την επανάληψη και η δεύτερη σελίδα επανέρχεται σε όποια γραμματοσειρά ήταν ενεργή, το οποίο για τα Αραβικά συνήθως σημαίνει άδεια κουτάκια
Δεν αντιστρέφει κείμενο που έχετε ήδη αντιστρέψει
Το μοναδικό λάθος που καταπίνει τον περισσότερο χρόνο αποσφαλμάτωσης (debugging) εδώ είναι να τροφοδοτήσετε το RtLTextOut με μια συμβολοσειρά (string) που έχετε ήδη αναστρέψει χειροκίνητα. Οι άνθρωποι φτάνουν σε αυτήν τη μέθοδο αφού μια πρώτη προσπάθεια με το απλό TextOut βγήκε ανάποδα, και μια κοινή προσωρινή λύση είναι να αντιστραφούν οι χαρακτήρες στον κώδικα πριν από τη σχεδίαση. Το RtLTextOut αναστρέφει εσωτερικά από μόνο του, οπότε μια προ-ανεστραμμένη συμβολοσειρά αναστρέφεται δεύτερη φορά και προσγειώνεται ακριβώς εκεί από όπου ξεκίνησε. Περάστε το κείμενο με λογική σειρά, με τη σειρά που θα το πληκτρολογούσατε και θα το διαβάζατε δυνατά, και αφήστε την κλήση να κάνει την αναδιάταξη
Η παγίδα είναι πιο δυσάρεστη από μια απλή αναστροφή, επειδή μια διπλά ανεστραμμένη συμβολοσειρά μπορεί να φαίνεται σωστή για μια καθαρά αραβική δοκιμαστική φράση και μετά να χαλάσει τη στιγμή που μια γραμμή φέρει μια λατινική λέξη ή έναν αριθμό. Μέσα σε μια γραμμή από δεξιά προς τα αριστερά, αυτές οι ενσωματωμένες εκτελέσεις υποτίθεται ότι διαβάζονται από αριστερά προς τα δεξιά, και η χειροκίνητη αναστροφή καταστρέφει αυτό το φώλιασμα ενώ η περίπτωση των καθαρά Αραβικών τυχαίνει να το επιβιώνει. Επομένως, το σφάλμα περνάει από το πρώτο σας smoke test και εμφανίζεται αργότερα σε ένα πραγματικό τιμολόγιο με έναν αριθμό λογαριασμού σε αυτό. Αφαιρέστε κάθε χειροκίνητη αναστροφή τη στιγμή που θα μεταβείτε στο RtLTextOut
Η παρενέργεια Direction που αξίζει να γνωρίζετε
Η κλήση της RtLTextOut αλλάζει κάτι περισσότερο από τη γραμμή που σχεδιάζετε. Αντιστρέφει επίσης την προτίμηση κατεύθυνσης ανάγνωσης του εγγράφου από δεξιά προς τα αριστερά, το ίδιο πράγμα που διαφορετικά θα ορίζατε μόνοι σας μέσω της ιδιότητας Direction. Αυτός ο setter προσθέτει το vpDirection στα ViewerPreferences του εγγράφου, το οποίο λέει σε ένα πρόγραμμα προβολής πώς να τακτοποιήσει τα σαλόνια δύο σελίδων (two-up spreads) και από ποια πλευρά ξεκινά μια διάταξη αντικριστών σελίδων (facing-page). Όταν ολόκληρο το έγγραφο είναι στα Αραβικά ή στα Εβραϊκά, αυτό είναι ακριβώς αυτό που θέλετε, και το λαμβάνετε δωρεάν
Αξίζει να το γνωρίζετε ακριβώς επειδή είναι αόρατο σε μια μεμονωμένη σελίδα. Εάν το έγγραφο είναι κυρίως από αριστερά προς τα δεξιά με ένα μπλοκ από δεξιά προς τα αριστερά, η πρώτη κλήση RtLTextOut θα εξακολουθεί να ανατρέπει την προτίμηση ολόκληρου του αρχείου, και τίποτα στο μονοσέλιδο αποδεικτικό σας δεν θα το δείξει. Το σύμπτωμα εμφανίζεται εβδομάδες αργότερα όταν κάποιος τυπώνει ένα φυλλάδιο διπλής όψης (duplex) και τα σαλόνια βγαίνουν καθρεφτισμένα. Εάν δεν είναι αυτό που θέλετε, ρυθμίστε ξανά ρητά το Direction μετά την εκτέλεση (run) από δεξιά προς τα αριστερά:
// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;
Για ένα έγγραφο που διαβάζεται πραγματικά από δεξιά προς τα αριστερά, αφήστε το ως έχει. Το θέμα είναι να γνωρίζετε ότι η κλήση έχει αποτέλεσμα σε όλο το έγγραφο, ώστε να μην συμβεί ποτέ η έκπληξη με το φυλλάδιο
Εγγράψτε τη γραμματοσειρά που παρέχετε, όχι αυτή που ελπίζετε ότι είναι εγκατεστημένη
Καμία από τις αναδιατάξεις δεν έχει σημασία εάν η γραμματοσειρά δεν έχει γλύφους για σχεδίαση. Η κλασική αποτυχία είναι μια αναφορά που αποδίδεται άψογα στο μηχάνημα του προγραμματιστή, όπου τυχαίνει να υπάρχει η Arial Unicode MS, και βγαίνει ως σειρές από άδεια κουτάκια στον διακομιστή ενός πελάτη όπου τα Windows αντικατέστησαν αθόρυβα μια γραμματοσειρά χωρίς καθόλου αραβική κάλυψη. Η θεραπεία είναι να σταματήσετε να εμπιστεύεστε τις εγκατεστημένες γραμματοσειρές του συστήματος και να εγγράψετε μία που παρέχετε μαζί με την εφαρμογή
// Ship a known Arabic font and register it before drawing
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Δύο όρια (boundaries) συνοδεύουν την εγγραφή. Μια γραμματοσειρά που εισάγεται μέσω του RegisterUnicodeTTF ενσωματώνεται, και ο χειρισμός του ενσωματωμένου Unicode του HotPDF χρειάζεται το έγγραφο σε PDF 1.5 ή νεότερο· αυτό προκαλεί πρόβλημα μόνο αν κάτι μεταγενέστερο επιμένει σε PDF 1.4, αλλά όταν συμβαίνει, η αποτυχία είναι σιωπηλή. Το άλλο είναι νομικό παρά τεχνικό: τα αρχεία TrueType φέρουν bit άδειας ενσωμάτωσης (embedding-permission), και μια όψη που φαίνεται ωραία στην οθόνη μπορεί να έχει άδεια χρήσης με τρόπο που απαγορεύει την αποστολή της μέσα σε έγγραφα πελατών. Επιβεβαιώστε την άδεια χρήσης πριν την ενσωματώσετε, όχι μετά από κάποιο παράπονο
Ένα πλήρες παράδειγμα κονσόλας
Ενώνοντας τα κομμάτια, εδώ είναι ένα αυτοτελές πρόγραμμα που γράφει μια σελίδα με μια αραβική γραμμή, μια εβραϊκή γραμμή και μια μικτή γραμμή που φέρει ένα λατινικό όνομα προϊόντος. Κάθε μπλοκ ορίζει το charset του και στη συνέχεια σχεδιάζει με λογική σειρά
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // HotPDF main unit
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// A Latin heading goes through the ordinary TextOut path
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabic: charset 178, logical order, RtLTextOut does the reordering
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebrew: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Mixed line: the embedded Latin word still reads left to right
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
Εκτελέστε το και ανοίξτε το αποτέλεσμα. Οι γραμμές στα Αραβικά και στα Εβραϊκά διαβάζονται από δεξιά προς τα αριστερά, τα γράμματα ενώνονται όπου τα ενώνει το σύστημα γραφής (script), και στην τελευταία γραμμή η λέξη HotPDF βρίσκεται από αριστερά προς τα δεξιά μέσα στην αραβική εκτέλεση, το οποίο είναι το σωστό αποτέλεσμα σύμφωνα με τις προδιαγραφές, παρόλο που εκπλήσσει όποιον βλέπει αμφίδρομη διάταξη (bidirectional layout) για πρώτη φορά. Αυτό το τελευταίο σημείο αξίζει να γραφτεί στα κριτήρια αποδοχής σας πριν ένας φυσικός ομιλητής ελέγξει το αποτέλεσμα, επειδή η ενσωματωμένη εκτέλεση που διαβάζεται με τον «λάθος» τρόπο σε σχέση με το περιβάλλον σύστημα γραφής, είναι το μόνο πράγμα που αναφέρεται συχνότερα ως σφάλμα (bug) ενώ δεν είναι
Επαλήθευση του αποτελέσματος
Μια σελίδα που φαίνεται σωστή δεν είναι το ίδιο με μια σελίδα που είναι σωστή, γι' αυτό ελέγξτε την με τον τρόπο που θα την ελέγξει ένα μεταγενέστερο σύστημα. Αντιγράψτε το κείμενο ξανά έξω από το πρόγραμμα προβολής και συγκρίνετε τα σημεία κώδικα (codepoints) με τη συμβολοσειρά προέλευσης· η σωστή οπτική σειρά με ανακατεμένη λογική σειρά είναι μια πραγματική λειτουργία αστοχίας. Εκτελέστε την αναζήτηση του προγράμματος προβολής μέσα στο έγγραφο για μια λέξη που μπορείτε να δείτε στη σελίδα. Στη συνέχεια, ανοίξτε το αρχείο σε ένα μηχάνημα που δεν διαθέτει τις γραμματοσειρές ανάπτυξής σας, αυτό που είναι πιο πιθανό να εκθέσει μια σιωπηλή αντικατάσταση. Τίποτα από αυτά δεν αντικαθιστά έναν φυσικό ομιλητή που διαβάζει ένα γνήσιο έγγραφο, κάτι που εντοπίζει προβλήματα που καμία συνθετική συμβολοσειρά δοκιμής δεν θα εντοπίσει, επομένως βάλτε αυτόν τον έλεγχο στο ημερολόγιο πριν αποσταλεί η μορφή
Το RtLTextOut χειρίζεται την αμφίδρομη αναδιάταξη και την αραβική σύνδεση με βάση τα συμφραζόμενα, κάτι που καλύπτει τη συντριπτική πλειοψηφία των εργασιών αναφορών και εγγράφων από δεξιά προς τα αριστερά. Το πού σταματά (συστήματα γραφής που χρειάζονται περισσότερα από την αναδιάταξη και τη σύνδεση, όπως οι Ινδικές οικογένειες (Indic), και τα προαιρετικά χαρακτηριστικά OpenType που περνούν μέσα από αντικατάσταση ενός γλύφου), χαρτογραφείται μαζί με τις λεπτομέρειες κάλυψης γλύφων και διαμόρφωσης (shaping) στο συνοδευτικό άρθρο Διαμόρφωση αραβικού και RTL κειμένου με το HotPDF
Οι κλήσεις RtLTextOut, SetFont, και RegisterUnicodeTTF που φαίνονται εδώ αποτελούν μέρος του Εξαρτήματος HotPDF (HotPDF Component) για Delphi και C++Builder