Uniscribe gjør mer arbeid enn de fleste kallere innser. ScriptItemize utfører bidireksjonell analyse og skriptsegmentering i én gjennomkjøring, og ScriptLayout produserer den visuelle rekkefølgen av de resulterende runene. HarfBuzz, den portable erstatningen folk strekker seg etter, gjør ingen av delene: den shaper en enkelt run hvis retning og skript allerede er avgjort av noen andre. Så den vanskelige delen av å ta en Windows PDF-tekstpipeline til Linux eller macOS, er ikke å binde en shaping-motor. Det er å levere den bidireksjonelle algoritmen Uniscribe stille leverte, og i PDFium-komponenten er det det FPdfBidi er til for
Enheten implementerer UAX #9 direkte: regler P2 og P3 for avsnittsretning, X1 til X10 for eksplisitte innbygginger og isolater, W1 til W7 for svake typer, N0 til N2 for nøytrale og klammer, I1 og I2 for implisitte nivåer, og L1 og L2 for den endelige reordregeringen. To funksjoner bærer den: PdfResolveBidiLevels returnerer ett innbyggingsnivå per UTF-16 kodeenhet, og PdfBidiVisualOrder gjør de nivåene til permutasjonen som plasserer kodeenheter venstre til høyre
Hva algoritmen gir deg, og hva den ikke gjør
Den gir deg tall. Like nivåer er venstre-til-høyre, odde nivåer er høyre-til-venstre, og nivået til hvert tegn koder nestingen av de retningsmessige runene det tegnet sitter inne i. Fra de tallene utleder L2 en permutasjon. Det algoritmen bevisst ikke gjør, er å avgjøre hvilken font som skal brukes, danne ligaturer, eller reordrener glyfer innenfor en klynge; det er shapingbekymringer og hører til stadiet etter denne
uses
FPdfBidi;
var
Levels: TPdfBidiLevels;
Order: TPdfBidiOrder;
ParagraphLevel: Byte;
Text, Visual: WideString;
I: Integer;
begin
Text := SourceLine;
// pbdAuto anvender P2-P3: det første sterke tegnet avgjør
if PdfResolveBidiLevels(Text, pbdAuto, Levels, ParagraphLevel) then
begin
Order := PdfBidiVisualOrder(Text, Levels);
SetLength(Visual, Length(Order));
for I := 0 to High(Order) do
Visual[I + 1] := Text[Order[I] + 1];
// Visual leser nå venstre til høyre; Levels[] sier fortsatt hvilke
// runer som er RTL slik at en shaper kan få korrekte retninger
end;
end;
Tegnklassetabellen er generert, ikke skrevet
Hvert kodepunkt har en Bidi_Class-egenskap, og algoritmen konsulterer den stadig, så tabellen er fundamentet alt annet står på. Den genereres fra Unicode Character Database snarere enn vedlikeholdt for hånd: felt fem av UnicodeData.txt gir de tildelte klassene, og @missing-deklarasjonene i DerivedBidiClass.txt gir standardene for kodepunktene databasen ikke tildeler, noe som er hvordan utildelte blokker korrekt standarder til R, AL, ET eller BN snarere enn til L
Komprimeringstrikset er å sende ut bare områdene hvis klasse ikke er L. Alt som faller utenfor hvert område, er L, som både er Unicode-standarden og klassen til det overveldende flertallet av kodepunkter. Det tar en tabell som ellers ville løpe til tusenvis av oppføringer ned til 745 områder og omtrent 6,7 KB. Den operasjonelle konsekvensen er verdt å slå fast: når du flytter til en ny Unicode-versjon, kjør generatoren på nytt. Å håndredigere include-filen vil fungere, og den vil også lydløst drive fra databasen ved neste oppgradering
L2 må reordrener kodepunkter, ikke UTF-16 kodeenheter
Dette er feilen som produserer genuint korruptert output, og den første implementeringen gjorde den. L2 sier å reversere sammenhengende runer på hvert nivå fra det høyeste ned til det laveste odde nivået. Skrevet mot en UTF-16-streng betyr «reverser en run» naturlig å reversere kodeenhetene i den. For tegn i Basic Multilingual Plane er det greit. For et RTL-tegn i et astralt plan, slik som de i de kypriotiske eller gammel-sør-arabiske blokkene nær U+10800, er det ikke det: tegnet er et surrogatpar, å reversere runen legger det lave surrogatet før det høye, og strengen inneholder nå to uparede surrogater i stedet for ett tegn. Ingenting nedstrøms kan gjenopprette det
Fiksen er å gjøre L2 på kodepunktenheter. Implementeringen fletter kodeenheter inn i kodepunktenheter, utfører reverseringene på de enhetene, og ekspanderer resultatet tilbake til kodeenhetsindekser på slutten. Det er hvorfor PdfBidiVisualOrder tar teksten og ikke bare nivåarrayen: den kan ikke si hvor surrogatgrensene er fra nivåer alene. Den samme surrogatpardisiplinen går gjennom tekst-API-ene generelt, som beskrevet i artikkelen om emoji, CJK og surrogatpar
Nedstigningen gjennom nivåer må inkludere nivåer som ikke opptrer
Den andre feilen er subtilere og produserer intet krasj, bare tekst som ikke er reordnet. L2 sier å starte på det høyeste til stede værende nivået og arbeide ned til det laveste odde nivået. En naturlig optimalisering er å samle mengden av nivåer som faktisk opptrer og iterere over den mengden. Det er feil
Betragt en linje med latinsk tekst inne i en høyre-til-venstre-innbygging. Avsnittsnivået er 0, innbyggingen skyver de latinske tegnene til nivå 2, og intet tegn sitter på nivå 1. Å iterere over opptrødende nivåer finner bare 0 og 2, og det finnes ikke noe odde nivå i det hele tatt, så løkken utfører ingen reversering. Det svaret er korrekt, men av en grunn optimaliseringen ikke kjenner: en reversering på nivå 2 etterfulgt av en reversering på nivå 1 ville kansellere nøyaktig, så å utføre ingen av dem er det riktige utfallet. Endre inputen litt, slik at både nivå 1- og nivå 3-tegn finnes men nivå 2 ikke gjør det, og den mengdebaserte løkken hopper over nivå 2-reverseringen algoritmen krever
// Korrekt: gå hvert nivå fra maksimum ned til det laveste odde
// nivået, inkludert nivåer intet tegn faktisk har
Level := MaxLevel;
while Level >= LowestOddLevel do
begin
ReverseRunsAtOrAbove(Level); // ingen virkning når ingen run kvalifiserer
Dec(Level);
end;
Skrevet som en ren dekrementerende løkke faller atferden ut gratis, og ingenvirkning-iterasjonene koster ingenting målbart. Dette er et tilfelle der den opplagte optimaliseringen ikke er litt feil, den er feil på en inputavhengig måte et lite testkorpus aldri vil avdekke
Klammer: BD16 med en pragmatisk tabell
Regel N0 og BD16 klammeparalgoritmen finnes slik at en parentes i blandetretningstekst løses til retningen av det den omslutter, snarere enn til hva som tilfeldigvis er tilstøtende. Det trenger en tabell av klammepar. Implementeringen bærer parene i vanlig bruk snarere enn det fulle innholdet av Unicode-klammefilen: ASCII, CJK, fullbredde, matematiske og dekorative klammer
En uoppført klamme er ikke en feil. Den løses som en ordinær nøytral gjennom N1 og N2, noe som er nøyaktig atferden enhver implementering hadde før Unicode 6.3 innførte N0. Så grensen er «mindre finjustert for sjeldne klammer», ikke «inkorrekt». Én detalj trenger eksplisitt håndtering: den kanoniske ekvivalensen mellom vinkelklammene ved U+2329 og U+232A og de ved U+3008 og U+3009 må foldes ved matching av par, ellers vil en åpneklamme skrevet på én måte feile å pare med en lukkeklamme skrevet på den andre
Hvordan du tester tretti samvirkende regler
Ikke med et stort korpus, i det minste ikke først. Den produktive tilnærmingen var seksten håndverifiserte tilfeller, hvert valgt for å trene en spesifikk regel og hvert sjekket mot nivåene UAX #9 sier det skal produsere: avsnittsretningdeteksjon under P2 og P3, de svaketyperreglene W2, W3 og W7, de implisitte nivåreglene I1 og I2, eksplisitt innbygging via X2 og X7, isolater via X5a og X6a, L1-nullstillingen av etterfølgende whitespace og skilletegn, et N0-klammertilfelle, og ett tilfelle med et astralt tegn for å låse surrogathåndteringen
Seksten tilfeller med kjentriktige forventede nivåer fanger mer enn seksten hundre tilfeller med plausibel seende output, fordi feilmodusen til en bidireksjonell implementering er tekst som leses nesten riktig. Når de passerer, er et korpus nyttig for å finne tabellhull og ytelsesproblemer, noe som er forskjellige klasser av defekter
Inne i PDFium-komponenten mater nivåene to konsumenter. På skrivesiden forteller de shaping-backenden retningen til hver run, noe som er input HarfBuzz krever. På lesesiden informerer de utvalgsgeometri og leserrekkefølge, siden et klikk i RTL-tekst må mappe til en logisk posisjon snarere enn en visuell; den mappingen er dekket i artikkelen om visuell linjeutvalg og leserrekkefølgemodellen i strukturerte tekstblokker og leserrekkefølge. Plattformstøttedetaljer for komponenten står på produktsiden for PDFium Delphi component