Skicka den arabiska frasen يوضح ملف PDF till TextOut och öppna resultatet. Bokstäverna går åt fel håll, och var och en sitter i sin isolerade form med en synlig lucka innan nästa, som om någon skrivit engelska baklänges och tryckt på mellanslag mellan varje tecken. Inget undantag löstes ut. Ingen varning skrevs ut. Utmatningen är helt enkelt fel, och den är fel för att två separata transformationer som arabiskan är beroende av aldrig inträffade. Att veta vad de två transformationerna är, och vilket anrop som utför dem, är det mesta av vad komplex skript-PDF-utmatning handlar om
HotPDF är en inbyggd VCL PDF-komponent för Delphi och C++Builder, och den gör höger-till-vänster-arbetet åt dig genom ett distinkt anrop. Den stannar också till på några specifika platser som du vill veta om innan du förbinder dig till en språkkonfiguration (locale), så den här artikeln kartlägger koncepten och de ärliga gränserna; den praktiska konfigurationen för själva anropet finns i RtLTextOut referensartikeln
Varför en korrekt sträng ändå skrivs ut fel
Unicode behåller text i logisk ordning, den ordning du skriver in den och läser den högt. En renderare måste placera ut glyfer i visuell ordning. För skript från vänster till höger sammanfaller dessa ordningar och ingen tänker på det. För arabiska och hebreiska gör de inte det, och när en enskild rad blandar riktningar, till exempel en arabisk mening som bär den latinska symbolen "PDF" eller ett pris skrivet med siffror, avgör Unicodes dubbelriktade algoritm (UAX #9) exakt hur vänster-till-höger-fragmenten nästlas inuti höger-till-vänster-raden. Det är den första transformationen, omordning, och att hoppa över den är det som vänder på raden
Den andra är kontextuell formning. En arabisk bokstav ritas annorlunda beroende på var den hamnar i ett ord: initial, medial, final eller fristående. Kodpunkten förblir densamma hela tiden; bara glyfen ändras. En pipeline som överlämnar varje kodpunkt rakt till sin standardglyf producerar exakt den frånkopplade utmatningen i isolerad form från inledningsstycket. Hebreiska hoppar över detta steg, eftersom dess bokstäver inte sammanfogas, men det behöver fortfarande omordningen. Arabiska behöver båda, och det är anledningen till att arabiska, inte hebreiska, är strängen du testar med
På skrivbordet är inget av detta ditt problem. När ett VCL-formulär målar arabiska i en TEdit, omordnar och formar operativsystemets textstack tyst den, vilket är precis varför strängen som ser perfekt ut på skärmen kommer ut trasig i en naiv PDF. En innehållsström lagrar inte redigerbar text. Den lagrar positionerade glyfer, så den som matar ut strömmen ärver det formningsjobb som operativsystemet brukade hantera. RtLTextOut är anropet som tar tillbaka det jobbet
Vad RtLTextOut formar åt dig
HotPDF håller den latinska sökvägen och den komplexa skriptsökvägen som två olika metoder. TextOut skriver ut vad du ger den i den ordning du ger den. RtLTextOut utför båda transformationerna först — dubbelriktad omordning över hela raden, kontextuell analys för sammanfogningsskripten — och skriver sedan ut. Vilka skripts regler som gäller färdas in genom teckensnittets teckenuppsättning (charset) i stället för genom själva anropet, så riktning är ett explicit val vid varje anropsplats istället för en gissning gjord utifrån tecknen. Parameter-för-parameter-inställningen, teckenuppsättningens värden, stegen för registrering av teckensnitt och ett komplett kompilerbart exempel finns alla i RtLTextOut referensartikeln; det här stycket stannar vid vad transformationerna betyder, var de stannar och hur man bevisar att de fungerade
En användningsregel spelar roll även på den här höjden: inmatningen måste vara i logisk ordning, eftersom RtLTextOut utför själva vändningen, och en sträng som du redan vänt för hand kommer ut dubbelvänd — referensartikeln går igenom den fällan och dess rensning. Det som gör fällan värd att nämna här är varför den överlever testning. En dubbelvänd ren-arabisk sträng kan se helt korrekt ut, och faller bara isär när en rad bär ett latinskt ord eller en siffra, eftersom dessa inbäddade körningar inte längre nästlas på det sätt som UAX #9 föreskriver. Felet ligger inte i renderingen; det ligger i att mata algoritmen med text som redan var halvt bearbetad
Samma blandade riktningsbeteende ställer till det för granskare mer än det ställer till det för kod. Inuti en höger-till-vänster-rad läses siffror och inbäddade latinska ord fortfarande från vänster till höger. Någon som inte har arbetat med dubbelriktad layout kommer att titta på en renderad faktura, se kontonumret läsas på "fel" håll i förhållande till det arabiska runtomkring, och skriva upp det som ett fel. Det är det spec-korrekta resultatet. En kort anteckning i dina acceptanskriterier, skriven innan det första modersmålstalarpasset, sparar den onödiga vändan
När omordning och sammanfogning är tillräckligt, och när de inte är det
För rullande arabisk och hebreisk text — rapporter, fakturor, kontrakt, brev — är omordning plus kontextuell sammanfogning hela jobbet, och RtLTextOut bär det ensamt. Gränsen uppträder när typografin ber om mer än sammanfogning. HotPDF:s svar på den arabiska sidan är en producent-sidans formare man väljer till (opt-in): ställ in AutoShapeArabic := True och komponenten skriver om logisk-ordning-körningen till Unicode Presentationsformulär (Presentation Forms) före det dubbelriktade passet, så sammanfogningsformer beräknas mot logiska grannar och ligaturveckning (ligature folds) bakas in i de kodpunkter PDF:en faktiskt bär, i stället för att lämnas till en visare att lösa. Bytet är som standard avstängt och utmatningen är byte-stabil när den förblir av, så att slå på det är ett medvetet beslut per dokument-pipeline, inte en global uppgradering. Samma opt-in-modell sträcker sig till de andra sammanfogande höger-till-vänster-skripten HotPDF formar: syriska, n'ko, adlam och hanifi rohingya har alla sin egen auto-formflagga (auto-shape flag) som speglar den arabiska
Valfria OpenType-funktioner är återigen en annan mekanism. Valfria ligaturer och liknande enkelsubstitutionsfunktioner går genom GetSingleSubstituteGlyph(GID, 'liga'), som löser en ersättning i taget — inmatnings-glyf-ID först, funktionstagg därefter — och returnerar inmatnings-glyfen oförändrad när funktionen inte är tillämplig. Det räcker för att driva en känd, ändlig ligaturlista som du underhåller själv. Det är inte en fullständig GSUB-motor, och skillnaden är exakt var ambitiösa språkplaner (locale plans) går fel: en formnings-pipeline som hanterar arabiska felfritt har demonstrerat omordning och sammanfogning, ingenting mer
Täckning över skript
Arabiskan övar båda transformationerna, vilket är anledningen till att det är strängen man ska testa med, och varför ett arabiskt testpass är det starkaste enskilda beviset på att pipelinen fungerar. Hebreiska behöver omordningen men inte sammanfogningen, eftersom dess bokstäver står ensamma; om hebreiskan renderas korrekt men arabiskan kommer ut oansluten, är den dubbelriktade halvan bra och den kontextuella halvan kördes aldrig. Persiska och urdu rider på det arabiska skriptet och ärver dess beteende, även om urdus preferens för nastaliq-stilen är ett teckensnittsbeslut med läslighetskonsekvenser som en infödd läsare bör bedöma
Thailändska sitter på den andra sidan av linjen helt och hållet. Det körs från vänster till höger, så det behöver inget dubbelriktat arbete, och dess bokstäver sammanfogas inte, så det behöver ingen kontextuell analys; thailändska strängar går igenom den vanliga TextOut-sökvägen precis som latinska. Vad thailändska däremot har är staplade markeringar — vokaler och tonmärken ovanför och under baskonsonanten — och huruvida dessa sitter korrekt beror på om teckensnittet bygger sina kombinerande markeringar för att staplas utan hjälp av en formningsmotor. De flesta dedikerade thailändska teckensnitt gör det. Testa med det exakta teckensnitt du kommer att bädda in, inte ett som ser likadant ut
Devanagari och resten av den indiska familjen är det ärliga, svåra stoppet. Deras vokaltecken ordnas om kring konsonantkluster och deras konjunkter formas genom kedjor av kontextberoende utbyten, vilket är ren GSUB-territorium, bortom omordning och sammanfogning. Om en indisk språkuppsättning (locale) finns på vägkartan, kör en riktig pilot på genuina kundsträngar innan du lovar det — att arabiska fungerar är inget bevis för att devanagari kommer att göra det. CJK-strängar, vietnamesiska med dess staplade diakritiska tecken och blandad europeisk text tar alla den vanliga vägen utan dubbelriktad analys, och det lönar sig att hålla de två vägarna fysiskt separerade i rapportkoden, en rutin för RTL-körningar och en för allt annat, så att språklogiken är synlig på anropsplatsen i stället för gömd bakom en flagga som någon glömmer att ställa in
Glyftäckning bestäms innan formning ens körs
Formning plockar glyfer ur ett teckensnitt. Om teckensnittet inte bär dem, finns det inget att plocka, vilket är anledningen till att det klassiska driftsättningsfelet — felfritt på utvecklarens maskin, tomma rutor på kundens server efter en tyst teckensnittsersättning — är ett täckningsproblem, inte ett formningsproblem. Det praktiska botemedlet, att registrera ett teckensnitt du skickar med i stället för att lita på vad en maskin har installerat, gås igenom steg för steg i referensartikeln. Den konceptuella punkten är att täckning måste fastställas innan någon formningsfråga ens är meningsfull, och att den kan fastställas programmatiskt i stället för genom att titta på resultatet
// After RegisterUnicodeTTF, audit coverage for the
// codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628); // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);
Registreringen i sig medför två begränsningar — ett PDF 1.5-golv för inbäddad Unicode-hantering och teckensnittets inbäddningsbehörighetsbitar — som båda täcks vid sidan av inställningsstegen i RtLTextOut referensartikeln. Det som hör hemma här är granskningsvanan: GetUnicodeGlyphForCodepoint är ditt tidiga varningssystem. Gå igenom de kodpunktsintervall din data faktiskt använder när tjänsten startar och logga vilka glyf-ID:n som kommer tillbaka. En lucka i täckningen dyker då upp som en rad i en startlogg under utrullningen, snarare än som saknade tecken i en faktura som redan nått en kund
Läsordning tillhör dokumentet, inte glyferna
Att få varje glyf rätt lämnar fortfarande en sak ogjord. ISO 32000-1 §12.2 definierar en visningspreferens kallad /Direction som anger dokumentets övergripande läsordning. Det rör inga glyfer. Vad det gör är att berätta för en visare (viewer) hur man ska arrangera uppslag (two-up spreads), från vilken sida en motstående sidlayout ska börja, och åt vilket håll läs-gränssnittet (reading UI) bör luta. Inget av det syns på en enda sida, vilket är exakt varför det glöms bort
// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft; // adds vpDirection to ViewerPreferences
Att ställa in Direction är hela jobbet: egenskapsväljaren lägger till vpDirection i dokumentets ViewerPreferences, så en enda kodrad bär in preferensen i filen. Om texten går ut via RtLTextOut får du detta gratis, eftersom anropet byter dokumentriktning som en sidoeffekt — referensartikeln täcker när ett blandat dokument behöver få det ogjort. Det fall där du måste ställa in det själv är ett höger-till-vänster-dokument som producerats på något annat sätt, till exempel från inmatning du för-format uppströms och ritat genom den vanliga sökvägen. Lämna det utanför och den ensidiga korrektur du stirrar på ser identisk ut i båda fallen; sedan skriver någon ut ett dubbelsidigt häfte, uppslagen kommer ut spegelvända, och orsaken är en saknad en-radare från veckor tidigare
Verifiera formaterad utmatning
Verifiera från början till slut, eftersom en sida kan se korrekt ut och fortfarande vara oanvändbar för allt nedströms. Tre kontroller hittar de flesta problem. Kopiera texten tillbaka ut från Acrobat och jämför kodpunkterna mot din källsträng. Kör visarens (viewerns) sökning i dokumentet efter ett ord som du kan se på sidan. Och öppna utmatningen på en maskin som inte har dina utvecklingsteckensnitt, den maskin som är mest sannolik att avslöja ett utbyte. Inget av det ersätter en infödd läsare som tittar på ett riktigt dokument, vilket fångar upp saker inget syntetiskt korpus kommer att göra. Få den granskningen inskriven i kalendern innan formatet levereras
Välj teststrängar med flit istället för att återanvända vad en översättare skickade förra året. Ett fungerande minimum per språkuppsättning (locale): en menig i rent skript, en mening med inbäddade latinska varumärken, en rad med siffror och valuta, samt namn med diakritiska tecken eller kombinerande markeringar. Verkliga kundnamn bryter mot antaganden som fylltext lämnar orörda, så låt regressionssetet växa med en sträng varje gång ett supportärende avslöjar ett mönster du inte sett tidigare
Teckensnittsregistrering, delmängdsskapande (subsetting) och det vardagliga gränssnittet för textritning täcks i artikeln om rapportutmatning, teckensnitt och bilder med HotPDF. När samma dokument också måste möta tillgänglighetsprofiler sitter språktaggningen och strukturreglerna i artikeln om PDF/A- och PDF/UA-validering ovanpå formningsarbetet här
De API:er för höger-till-vänster och Unicode-teckensnitt som beskrivs ovan levereras med HotPDF Component för Delphi och C++Builder; produktsidan länkar till den fullständiga referensen för textutmatning