Technisch artikel

Arabische en RTL Tekstvorming in Delphi PDF's met HotPDF

Geef de Arabische zin يوضح ملف PDF door aan TextOut en open het resultaat. De letters lopen de verkeerde kant op, en elk teken staat in zijn geïsoleerde vorm met een zichtbare opening voor de volgende, alsof iemand Engels achterstevoren typte en na elk teken een spatie intikte. Er trad geen uitzondering op. Er werd geen waarschuwing afgedrukt. De uitvoer is simpelweg fout, en deze is fout omdat twee afzonderlijke transformaties waar het Arabisch van afhankelijk is, nooit hebben plaatsgevonden. Weten wat die twee transformaties zijn, en welke aanroep ze uitvoert, is grotendeels waar PDF-uitvoer van complexe scripts op neerkomt

HotPDF is een native VCL PDF component voor Delphi en C++Builder, en het doet het rechts-naar-links werk voor u via een aparte aanroep. Het stopt echter ook bij enkele specifieke punten waarvan u op de hoogte wilt zijn voordat u zich vastlegt op een locale, dus dit stuk brengt de concepten en de eerlijke grenzen in kaart; de praktische instelling voor de aanroep zelf staat in het RtLTextOut naslagartikel

Waarom een correcte string nog steeds verkeerd wordt geprint

Unicode bewaart tekst in logische volgorde, de volgorde waarin u het typt en hardop leest. Een renderer moet glyphs in visuele volgorde plaatsen. Voor links-naar-rechts scripts vallen die volgordes samen en denkt niemand erover na. Voor Arabisch en Hebreeuws is dat niet het geval, en wanneer een enkele regel richtingen combineert, bijvoorbeeld een Arabische zin die de Latijnse term "PDF" of een in cijfers geschreven prijs bevat, beslist het Unicode Bidirectional Algorithm (UAX #9) precies hoe de links-naar-rechts fragmenten in de rechts-naar-links regel worden genest. Dat is de eerste transformatie, herschikken, en het overslaan ervan is wat de regel omkeert

De tweede is contextuele tekstvorming. Een Arabische letter wordt anders getekend, afhankelijk van waar deze in een woord valt: aan het begin, in het midden, aan het eind, of op zichzelf staand. Het codepunt blijft overal hetzelfde; alleen de glyph verandert. Een pijplijn die elk codepunt rechtstreeks doorgeeft aan de standaardglyph ervan, produceert exact de losgekoppelde, geïsoleerde uitvoer uit de eerste alinea. Het Hebreeuws slaat deze stap over, aangezien de letters daarvan niet verbinden, maar het heeft nog steeds de herschikking nodig. Het Arabisch heeft beide nodig, en dat is waarom Arabisch, en niet Hebreeuws, de string is waarmee u test

Op het bureaublad is niets hiervan uw probleem. Wanneer een VCL-formulier Arabisch in een TEdit schildert, herschikt en vormt de tekst-stack van het besturingssysteem dit stilletjes, en dat is precies de reden waarom de string die er op het scherm perfect uitziet, in een naïeve PDF kapot overkomt. Een content stream slaat geen bewerkbare tekst op. Het slaat gepositioneerde glyphs op, dus degene die de stream uitzendt, erft de vormgevingstaak die het besturingssysteem vroeger afhandelde. RtLTextOut is de aanroep die die taak terugneemt

Wat RtLTextOut voor u vormt

HotPDF houdt het Latijnse pad en het pad van complexe scripts als twee verschillende methoden. TextOut print wat u het geeft, in de volgorde waarin u het geeft. RtLTextOut voert eerst beide transformaties uit — bidirectionele herschikking over de hele regel, contextuele analyse voor de verbindende scripts — en print daarna pas. De regels van welk script van toepassing zijn, worden doorgegeven via de tekenset (charset) van het lettertype in plaats van via de aanroep zelf, dus richting is een expliciete keuze bij elke aanroep in plaats van een gok die uit de tekens is gemaakt. De parameter-voor-parameter instelling, de charset waarden, de stappen voor fontregistratie, en een compleet compileerbaar voorbeeld staan allemaal in het RtLTextOut naslagartikel; dit stuk blijft bij wat de transformaties betekenen, waar ze stoppen, en hoe te bewijzen dat ze werkten

Eén gebruiksregel is zelfs op deze hoogte van belang: de invoer moet in logische volgorde staan, omdat RtLTextOut de omkering zelf uitvoert, en een string die u al handmatig heeft omgedraaid er dubbel omgekeerd uitkomt — het naslagartikel doorloopt die valstrik en de oplossing ervan. Wat deze valstrik een vermelding hier oplevert, is de reden waarom het testen doorstaat. Een dubbel omgekeerde puur Arabische string kan er volkomen correct uitzien, en valt pas uit elkaar wanneer een regel een Latijns woord of een getal bevat, omdat die ingebedde runs niet langer nesten op de manier die UAX #9 dicteert. De bug zit hem niet in het renderen; het zit hem in het voeden van de algoritmetekst die al halfverwerkt was

Datzelfde gedrag met gemengde richtingen zet beoordelaars meer op het verkeerde been dan code. Binnen een rechts-naar-links regel worden cijfers en ingebedde Latijnse woorden nog steeds van links naar rechts gelezen. Iemand die niet met bidirectionele lay-outs heeft gewerkt, zal naar een gerenderde factuur kijken, zien dat het rekeningnummer de "verkeerde" kant op leest ten opzichte van het Arabisch eromheen, en het als een bug noteren. Het is het correcte resultaat volgens de specificatie. Een korte notitie in uw acceptatiecriteria, geschreven vóór de eerste lezing door een native speaker, bespaart die heen-en-weer trip

Wanneer herschikken en verbinden voldoende zijn, en wanneer niet

Voor lopende tekst in Arabisch en Hebreeuws — rapporten, facturen, contracten, brieven — is herschikken plus contextuele verbinding de hele klus, en RtLTextOut voert dit alleen uit. De grens verschijnt wanneer de typografie om meer vraagt dan alleen verbinden. Het antwoord van HotPDF aan de Arabische kant is een opt-in shaper aan de producentenzijde: stel AutoShapeArabic := True in en de component herschrijft de logische-volgorde run naar Unicode Presentation Forms vóór de bidirectionele run, zodat verbindingsvormen worden berekend ten opzichte van logische buren en ligatuurvouwen worden ingebakken in de codepunten die de PDF daadwerkelijk meedraagt, in plaats van dit aan een viewer over te laten om op te lossen. De schakelaar staat standaard uit en de uitvoer is byte-stabiel zolang deze uitblijft, dus het aanzetten is een bewuste beslissing per documentpijplijn, niet een globale upgrade. Hetzelfde opt-in model strekt zich uit tot de andere verbindende rechts-naar-links scripts die HotPDF vormt: Syrisch, N'Ko, Adlam en Hanifi Rohingya hebben elk hun eigen auto-shape vlag die de Arabische weerspiegelt

Optionele OpenType-features zijn weer een ander mechanisme. Naar keuze in te zetten ligaturen en vergelijkbare enkelvoudige vervangingsfuncties gaan via GetSingleSubstituteGlyph(GID, 'liga'), wat één substitutie per keer oplost — invoer glyph-ID eerst, feature-tag als tweede — en retourneert de invoer-glyph ongewijzigd wanneer de feature niet van toepassing is. Dat is voldoende om een bekende, eindige ligatuurlijst aan te sturen die u zelf onderhoudt. Het is geen volledige GSUB-engine, en het verschil is exact waar ambitieuze locale plannen de mist in gaan: een pijplijn voor vormgeving die Arabisch vlekkeloos afhandelt, heeft laten zien dat het kan herschikken en verbinden, en niets meer dan dat

Dekking over scripts heen

Arabisch oefent beide transformaties uit, en dat is waarom het de string is om mee te testen, en waarom een succesvolle Arabische verwerking het sterkste bewijsstuk is dat de pijplijn werkt. Het Hebreeuws heeft de herschikking nodig, maar niet het verbinden, aangezien de letters daarvan op zichzelf staan; als Hebreeuws correct wordt weergegeven maar Arabisch losgekoppeld overkomt, is de bidirectionele helft in orde en is de contextuele helft nooit gedraaid. Perzisch en Urdu maken gebruik van het Arabische schrift en erven het gedrag daarvan, hoewel Urdu's voorkeur voor de Nastaliq-stijl een fontbeslissing is met gevolgen voor de leesbaarheid die door een native reader beoordeeld moet worden

Thai zit volledig aan de andere kant van de grens. Het loopt van links naar rechts, dus het heeft geen bidirectioneel werk nodig, en de letters verbinden niet, dus het heeft geen contextuele analyse nodig; Thaise strings gaan door het gewone TextOut pad net als het Latijns. Wat Thai echter wel heeft, zijn gestapelde markeringen — klinkers en toonteens boven en onder de basismedeklinker — en of die goed staan hangt af van het lettertype dat zijn combinerende markeringen opbouwt om te stapelen zonder hulp van een shaping-engine. De meeste toegewijde Thaise lettertypen doen dat. Test met het exacte lettertype dat u gaat insluiten, niet een dat erop lijkt

Devanagari en de rest van de Indische familie vormen de eerlijke absolute grens. Hun klinkertekens herschikken zich rond medeklinkerclusters en hun conjuncten (verbindingen) worden gevormd door ketens van contextafhankelijke substituties, wat volbloed GSUB-terrein is, en verder gaat dan herschikken en verbinden. Als er een Indische locale op de planning staat, draai dan een echte pilot op authentieke klantstrings voordat u dit belooft — dat Arabisch werkt, is geen bewijs dat Devanagari dat ook zal doen. CJK-strings, Vietnamees met zijn gestapelde diakritische tekens, en gemengde Europese tekst nemen allemaal het gewone pad zonder bidirectionele analyse, en het loont de moeite om de twee paden fysiek gescheiden te houden in rapportcode, één routine voor RTL-runs en één voor de rest, zodat de locale logica zichtbaar is bij de aanroep in plaats van verborgen achter een vlag die iemand vergeet in te stellen

Glyph-dekking wordt beslist voordat shaping überhaupt plaatsvindt

Shaping kiest glyphs uit een lettertype. Als het lettertype ze niet heeft, is er niets om te kiezen, wat verklaart waarom de klassieke implementatiefout — vlekkeloos op de machine van de ontwikkelaar, lege vakjes op de server van de klant na een stille fontsubstitutie — een dekkingsprobleem is, geen shaping-probleem. De praktische remedie, het registreren van een font dat u meelevert in plaats van te vertrouwen op wat een machine ook maar heeft geïnstalleerd, wordt stap voor stap doorgenomen in het naslagartikel. Het conceptuele punt is dat de dekking moet zijn vastgesteld voordat enige vraag over shaping ook maar zinvol is, en dat dit programmatisch kan worden vastgesteld in plaats van door de uitvoer op het oog te beoordelen

// After RegisterUnicodeTTF, audit coverage for the
// codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

Registratie zelf brengt twee beperkingen met zich mee — een ondergrens van PDF 1.5 voor geïntegreerde Unicode-afhandeling en de embedding-rechten van het lettertype — die beide naast de instellingsstappen in het RtLTextOut naslagartikel worden behandeld. Wat hier thuishoort, is de audit-gewoonte: GetUnicodeGlyphForCodepoint is uw vroege waarschuwingssysteem. Loop bij het opstarten van de service de reeksen codepunten door die uw gegevens daadwerkelijk gebruiken, en log welke glyph-ID's terugkomen. Een ontbrekende dekking verschijnt dan als een regel in een opstartlog tijdens de uitrol, in plaats van als ontbrekende tekens op een factuur die de klant al heeft bereikt

Leesvolgorde behoort tot het document, niet tot de glyphs

Als u elke glyph goed hebt, blijft er toch nog één ding ongedaan. ISO 32000-1 §12.2 definieert een viewer-voorkeur genaamd /Direction die de algemene leesvolgorde van het document aangeeft. Het raakt geen enkele glyph aan. Wat het doet, is een viewer vertellen hoe hij two-up spreads (naast elkaar liggende pagina's) moet ordenen, aan welke kant een layout voor tegenoverliggende pagina's moet beginnen, en in welke richting de lees-UI moet overhellen. Niets daarvan is zichtbaar op een enkele pagina, wat precies de reden is waarom het wordt vergeten

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

Het instellen van Direction is de hele klus: de property setter voegt vpDirection toe aan de ViewerPreferences van het document, dus één regel neemt de voorkeur op in het bestand. Als de tekst via RtLTextOut verloopt, krijgt u dit cadeau, omdat de aanroep de documentrichting als neveneffect omdraait — het naslagartikel behandelt wanneer een gemengd document vereist dat dit ongedaan wordt gemaakt. Het geval waarin u het zelf moet instellen, is een document van rechts-naar-links dat op een andere manier is geproduceerd, bijvoorbeeld vanuit input die u stroomopwaarts vooraf hebt gevormd en via het reguliere pad heeft getekend. Als u dit weglaat, ziet de afdruk van één pagina waar u naar kijkt er in beide gevallen hetzelfde uit; tot er vervolgens iemand een recto-verso boekje print en de spreads er gespiegeld uitkomen, en de oorzaak een ontbrekende regel van weken eerder is

Verifiëren van geshapete uitvoer

Verifieer van begin tot eind (end to end), want een pagina kan er correct uitzien en nog steeds onbruikbaar zijn voor alles verderop in de stroom. Drie controles vinden de meeste problemen. Kopieer de tekst weer uit Acrobat en vergelijk de codepunten met uw bronstring. Voer de zoekopdracht binnen het document van de viewer uit voor een woord dat u op de pagina kunt zien. En open de uitvoer op een machine die niet beschikt over uw ontwikkelingslettertypen, de machine die het meest waarschijnlijk een substitutie aan het licht zal brengen. Niets hiervan is een vervanging voor een native reader die naar één echt document kijkt, wat dingen ondervangt die geen enkel synthetisch corpus zal doen. Zet die review op de kalender voordat de opmaak wordt verzonden

Kies teststrings met opzet, in plaats van te recyclen wat een vertaler vorig jaar stuurde. Een werkbaar minimum per locale: een zin uitsluitend in het eigen script, een zin met ingebedde Latijnse merknamen, een regel met cijfers en valuta, en namen met diakritische tekens of combinerende markeringen. Echte klantnamen doorbreken aannames die door vultekst ongemoeid worden gelaten, dus laat de regressieset met één string groeien, elke keer dat een supportcasus een patroon oplevert dat u nog niet had gezien

Lettertyperegistratie, subsetting, en de dagelijkse API voor het tekenen van tekst komen aan bod in het artikel over rapportuitvoer, lettertypen en afbeeldingen met HotPDF. Wanneer dezelfde documenten ook moeten voldoen aan toegankelijkheidsprofielen, komen de taal-tagging en structuurregels uit het PDF/A en PDF/UA validatieartikel nog bovenop het shaping-werk dat hier wordt behandeld

De hierboven beschreven rechts-naar-links en Unicode font API's worden meegeleverd met de HotPDF Component voor Delphi en C++Builder; de productpagina bevat de link naar de volledige referentie voor tekstuitvoer