Giv den arabiske sætning يوضح ملف PDF til TextOut og åbn resultatet. Bogstaverne løber den forkerte vej, og hver især sidder i sin isolerede form med et synligt mellemrum før det næste, som om nogen skrev engelsk baglæns og trykkede på mellemrumstasten mellem hvert tegn. Ingen undtagelse (exception) blev udløst. Ingen advarsel blev udskrevet. Outputtet er simpelthen forkert, og det er forkert, fordi to separate transformationer, som arabisk er afhængig af, aldrig skete. At vide, hvad disse to transformationer er, og hvilket kald der udfører dem, er størstedelen af, hvad kompleks-script PDF-output handler om
HotPDF er en native VCL PDF-komponent til Delphi og C++Builder, og den gør højre-til-venstre-arbejdet for dig gennem et særskilt kald. Den stopper også op et par specifikke steder, som du gerne vil vide noget om, før du forpligter dig til en lokalitet (locale), så dette stykke kortlægger koncepterne og de ærlige grænser; den praktiske opsætning af selve kaldet findes i referenceartiklen for RtLTextOut
Hvorfor en korrekt streng stadig udskrives forkert
Unicode holder tekst i logisk rækkefølge, den rækkefølge du skriver den og læser den højt. En renderer skal lægge glyffer ned i visuel rækkefølge. For venstre-til-højre-scripts falder disse rækkefølger sammen, og ingen tænker over det. For arabisk og hebraisk gør de ikke, og når en enkelt linje blander retninger, f.eks. en arabisk sætning, der bærer det latinske token "PDF" eller en pris skrevet med tal, bestemmer Unicodes tovejs-algoritme (Bidirectional Algorithm, UAX #9) præcis, hvordan venstre-til-højre-fragmenterne integreres i højre-til-venstre-linjen. Det er den første transformation, omordning (reordering), og at springe den over er det, der vender linjen på hovedet
Den anden er kontekstuel formning (contextual shaping). Et arabisk bogstav tegnes forskelligt afhængigt af, hvor det falder i et ord: initial, medial, final eller stående alene. Kodepunktet (codepoint) forbliver det samme hele vejen igennem; kun glyffen ændres. En pipeline, der afleverer hvert kodepunkt direkte til dets standardglyf, producerer præcis det afbrudte, isolerede form-output fra det indledende afsnit. Hebraisk springer dette trin over, da dets bogstaver ikke samles (join), men det har stadig brug for omordningen. Arabisk har brug for begge dele, og det er derfor arabisk, ikke hebraisk, er den streng, du tester med
På skrivebordet (desktop) er intet af dette dit problem. Når en VCL-formular maler arabisk ind i en TEdit, omordner og former operativsystemets tekst-stack det stille og roligt, hvilket netop er grunden til, at den streng, der ser perfekt ud på skærmen, kommer ødelagt ud i en naiv PDF. En indholdsstrøm (content stream) gemmer ikke redigerbar tekst. Den gemmer positionerede glyffer, så den, der udsender strømmen, arver det formningsjob, som OS'et plejede at håndtere. RtLTextOut er det kald, der tager det job tilbage
Hvad RtLTextOut former for dig
HotPDF holder den latinske sti og den kompleks-script-sti som to forskellige metoder. TextOut udskriver, hvad du giver den, i den rækkefølge, du giver det. RtLTextOut udfører begge transformationer først — tovejs-omordning over hele linjen, kontekstuel analyse for sammensatte scripts (joining scripts) — og udskriver derefter. Hvilket scripts regler, der gælder, rejser ind via skrifttypens tegnsæt (charset) i stedet for gennem selve kaldet, så retning er et eksplicit valg på hvert opkaldssted i stedet for et gæt baseret på tegnene. Parameter-for-parameter opsætningen, tegnsætværdierne, skrifttyperegistreringstrinnene og et komplet kompilerbart eksempel findes alt sammen i referenceartiklen for RtLTextOut; dette stykke forbliver ved, hvad transformationerne betyder, hvor de stopper, og hvordan man beviser, at de virkede
Én brugsregel betyder noget selv i denne højde: inputtet skal være i logisk rækkefølge, fordi RtLTextOut selv udfører vendingen, og en streng, du allerede har vendt manuelt, kommer dobbelt-vendt ud — referenceartiklen gennemgår den fælde og dens oprydning. Det, der gør fælden værd at nævne her, er, hvorfor den overlever test. En dobbelt-vendt ren arabisk streng kan se perfekt ud og falder kun fra hinanden, når en linje bærer et latinsk ord eller et tal, fordi disse indlejrede forløb (runs) ikke længere integreres, som UAX #9 dikterer. Fejlen ligger ikke i renderingen; det ligger i at fodre algoritmen med tekst, der allerede var halvt behandlet
Den samme blandet-retning adfærd spænder oftere ben for anmeldere (reviewers), end den gør for koden. Inde i en højre-til-venstre-linje læses cifre og indlejrede latinske ord stadig venstre til højre. En person, der ikke har arbejdet med tovejs-layout (bidirectional layout), vil kigge på en renderet faktura, se kontonummeret læses den "forkerte" vej i forhold til det arabiske omkring det, og skrive det op som en fejl. Det er det specifikations-korrekte resultat. En kort note i dine acceptkriterier, skrevet før den første gennemgang af en indfødt taler, sparer den rundtur
Når omordning og sammensætning er nok, og når de ikke er
For arabisk og hebraisk løbende tekst — rapporter, fakturaer, kontrakter, breve — er omordning plus kontekstuel sammensætning (joining) hele jobbet, og RtLTextOut bærer det alene. Grænsen dukker op, når typografien beder om mere end sammensætning. HotPDF's svar på den arabiske side er en tilvalgs (opt-in) producer-side shaper: sæt AutoShapeArabic := True, og komponenten omskriver det logiske-rækkefølge-forløb til Unicode Præsentationsformer (Unicode Presentation Forms) før det tovejs-pass, så sammensætningsformer (joining forms) beregnes mod logiske naboer og ligatur-foldninger (ligature folds) bages ind i de kodepunkter (codepoints), PDF'en faktisk bærer, i stedet for at overlades til en fremviser (viewer) at løse. Skiftet er slået fra som standard, og outputtet er byte-stabilt, når det forbliver slået fra, så at slå det til er en bevidst beslutning pr. dokumentpipeline, ikke en global opgradering. Den samme tilvalgsmodel (opt-in model) udvides til de andre sammensatte (joining) højre-til-venstre-scripts, HotPDF former: Syrisk, N'Ko, Adlam og Hanifi Rohingya har hver deres eget auto-shape-flag, der afspejler det arabiske
Valgfri OpenType-funktioner er en anden mekanisme igen. Skønsmæssige (discretionary) ligaturer og lignende enkelt-udskiftnings-funktioner går gennem GetSingleSubstituteGlyph(GID, 'liga'), som løser én udskiftning ad gangen — input-glyf-id først, feature-tag derefter — og returnerer input-glyffen uændret, når funktionen ikke gælder. Det er nok til at drive en kendt, endelig ligaturliste, du vedligeholder selv. Det er ikke en fuld GSUB-motor (engine), og forskellen er præcis, hvor ambitiøse locale-planer går galt: en formningspipeline (shaping pipeline), der håndterer arabisk fejlfrit, har demonstreret omordning og sammensætning (joining), intet mere
Dækning på tværs af scripts
Arabisk udøver begge transformationer, hvilket er grunden til, at det er den streng, man skal teste med, og hvorfor et arabisk pass er det stærkeste enkelte bevis på, at pipelinen fungerer. Hebraisk har brug for omordningen, men ikke sammensætningen, da dets bogstaver står alene; hvis hebraisk renderes korrekt, men arabisk kommer ud afbrudt, er tovejs-halvdelen fin, og den kontekstuelle halvdel kørte aldrig. Persisk og urdu kører på det arabiske script og arver dets adfærd, selvom urdus præference for Nastaliq-stilen er en skrifttypebeslutning med læselighedskonsekvenser, som en indfødt læser bør bedømme
Thai sidder helt på den anden side af stregen. Det kører fra venstre til højre, så det behøver intet tovejs-arbejde (bidirectional work), og dets bogstaver samles (join) ikke, så det behøver ingen kontekstuel analyse; thailandske strenge går gennem den almindelige TextOut-sti ligesom latinsk. Hvad thai har, er stablede mærker (stacked marks) — vokaler og tonemærker over og under basiskonsonanten — og om de sidder korrekt, afhænger af, at skrifttypen bygger sine kombinerende mærker til at stable uden hjælp fra en shaper-motor. Det gør de fleste dedikerede thailandske skrifttyper. Test med den nøjagtige skrifttype, du vil indlejre (embed), ikke en lookalike
Devanagari og resten af den indiske familie er det ærlige, hårde stop. Deres vokaltegn omordnes (reorder) omkring konsonantklynger, og deres konjunkter formes gennem kæder af kontekstafhængige udskiftninger, hvilket er fuldt GSUB-territorium, ud over omordning og sammensætning. Hvis et indisk locale er på roadmap'en, så kør en rigtig pilot på ægte kundestrenge, før du lover det — at arabisk fungerer, er ikke bevis på, at devanagari vil. CJK-strenge, vietnamesisk med dets stablede diakritiske tegn og blandet europæisk tekst tager alle den almindelige sti uden tovejs-analyse (bidirectional analysis), og det betaler sig at holde de to stier fysisk adskilt i rapportkoden, én rutine til RTL-forløb og én til alt andet, så locale-logikken er synlig på opkaldsstedet i stedet for skjult bag et flag, nogen glemmer at sætte
Glyf-dækning besluttes, før formning overhovedet kører
Formning (shaping) vælger glyffer ud af en skrifttype. Hvis skrifttypen ikke bærer dem, er der intet at vælge, hvilket er grunden til, at den klassiske implementeringsfejl (deployment failure) — fejlfri på udviklerens maskine, tomme bokse på kundens server efter en stille skrifttypeudskiftning — er et dækningsproblem, ikke et formningsproblem. Den praktiske kur, at registrere en skrifttype, du sender (ship), i stedet for at stole på, hvad end en maskine har installeret, gennemgås trin for trin i referenceartiklen. Det konceptuelle punkt er, at dækning skal etableres, før ethvert formningsspørgsmål overhovedet er meningsfuldt, og at det kan etableres programmatisk i stedet for at vurdere outputtet med øjet
// After RegisterUnicodeTTF, audit coverage for the
// codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628); // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);
Registrering i sig selv bærer to begrænsninger — et PDF 1.5-gulv for indlejret Unicode-håndtering og skrifttypens tilladelsesbits til indlejring (embedding-permission bits) — begge dækket ved siden af opsætningstrinnene i referenceartiklen for RtLTextOut. Det, der hører til her, er revisionsvanen (audit habit): GetUnicodeGlyphForCodepoint er dit tidlige varslingssystem. Gennemgå de kodepunktområder, dine data faktisk bruger, når tjenesten starter, og log, hvilke glyf-id'er der kommer tilbage. Et dækningshul viser sig derefter som en linje i en opstartslog under udrulning (rollout), snarere end som manglende tegn i en faktura, der allerede har nået en kunde
Læserækkefølge tilhører dokumentet, ikke glyfferne
At få hver glyf rigtig efterlader stadig én ting ugjort. ISO 32000-1 §12.2 definerer en fremviserpræference (viewer preference) kaldet /Direction, der angiver dokumentets overordnede læserækkefølge. Den rører ingen glyffer. Det, den gør, er at fortælle en fremviser, hvordan to-op (two-up) opslag skal arrangeres, hvilken side et opslag (facing-page layout) skal starte fra, og hvilken vej læse-UI'en (reading UI) skal hælde. Intet af det vises på en enkelt side, hvilket er præcis derfor, det bliver glemt
// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft; // adds vpDirection to ViewerPreferences
At indstille Direction er hele jobbet: egenskabssætteren (property setter) tilføjer vpDirection til dokumentets ViewerPreferences, så én linje bærer præferencen ind i filen. Hvis teksten går ud gennem RtLTextOut, får du dette gratis, fordi kaldet vender dokumentretningen som en bivirkning — referenceartiklen dækker, hvornår et blandet dokument har brug for at få det omgjort. Det tilfælde, hvor du selv skal indstille det, er et højre-til-venstre-dokument produceret på enhver anden måde, for eksempel fra input du præ-formede upstream og tegnede gennem den almindelige sti. Udelader du det der, og den enkeltsides-prøve (proof), du stirrer på, ser identisk ud uanset hvad; så udskriver nogen en dupleks-brochure (duplex booklet), opslagene (spreads) kommer spejlvendte ud, og årsagen er en manglende one-liner fra uger tidligere
Verificering af formet output
Verificér ende-til-ende, for en side kan se korrekt ud og stadig være ubrugelig for alt downstream. Tre tjek finder de fleste problemer. Kopiér teksten tilbage ud af Acrobat, og sammenlign kodepunkterne (codepoints) med din kildestreng. Kør fremviserens i-dokument-søgning efter et ord, som du kan se på siden. Og åbn outputtet på en maskine, der ikke har dine udviklingsskrifttyper, den der højst sandsynligt vil afsløre en udskiftning (substitution). Intet af det erstatter en indfødt læser, der ser på ét rigtigt dokument, hvilket fanger ting, som intet syntetisk korpus vil. Få det review i kalenderen, før formatet udgives (ships)
Vælg teststrenge med vilje i stedet for at genbruge, hvad end en oversætter sendte sidste år. Et brugbart minimum pr. locale: en ren-script-sætning, en sætning med indlejrede latinske varemærker (brand names), en linje, der bærer cifre og valuta, og navne med diakritiske eller kombinerende mærker. Rigtige kundenavne bryder antagelser, som fyldtekst lader uberørt, så lad regressionssættet vokse med én streng, hver gang en support-sag afdækker et mønster, du ikke havde set
Skrifttyperegistrering, subsetting og den almindelige teksttegne-API er dækket i artiklen om rapportoutput, skrifttyper og billeder med HotPDF. Når de samme dokumenter også skal opfylde tilgængelighedsprofiler, sidder sprogtagging og strukturreglerne i PDF/A og PDF/UA valideringsartiklen oven på formningsarbejdet her
Højre-til-venstre- og Unicode-skrifttype-API'erne beskrevet ovenfor leveres med HotPDF-komponenten til Delphi og C++Builder; produktsiden linker den fulde tekst-output-reference