Articol Tehnic

Justificarea completă a textului PDF în Delphi cu HotPDF

Justificarea completă este aspectul care aliniază o coloană de text atât pe marginea stângă cât și pe cea dreaptă, aspectul pe care îl așteptați de la o carte tipărită sau un raport formal. Este ușor de descris și surprinzător de ușor de greșit, deoarece răspunsul la întrebarea "unde merge spațiul suplimentar" nu este același pentru engleză ca pentru japoneză, și deoarece modul naiv de a măsura fiecare linie transformă o pagină rapidă într-una lentă. HotPDF vă oferă justificare conștientă de script printr-un singur apel de aspect în cutie, iar sub acel apel se află o corectare de performanță de manual care merită înțeleasă independent

Acest articol parcurge ambele aspecte. În primul rând, regula tipografică care decide cum este distribuită marja pentru scripturile cu spații între cuvinte față de scripturile fără ele. În al doilea rând, modificarea de măsurare care a redus costul per-pagină al justificării de aproximativ optzeci de ori fără nicio diferență vizibilă în ieșire. Ambele contează dacă generați documente în volum și doriți ca acestea să citesc ca o culegere reală mai degrabă decât ieșire monospațiată întinsă să se potrivească

Ce necesită de fapt justificarea completă

O linie de text desenată la lățimea sa naturală aproape niciodată nu ajunge la marginea dreaptă a coloanei sale. Există întotdeauna un rest, marja, între locul unde se termină ultimul glif și locul unde se află limita coloanei. Alinierea stângă lasă acea marjă la dreapta. Alinierea dreaptă o mută la stânga. Centrarea o împarte. Justificarea completă o elimină lărgind linia în sine până când ambele margini se întâlnesc cu cutia, și singurul mod onest de a face asta este să împingă glifele unul de altul din interior

Regula care separă justificarea bună de cea rea este unde puneți marja. Un script care scrie cuvinte cu spații între ele, cum ar fi engleza și restul familiei latine, are îmbinări naturale la fiecare spațiu inter-cuvânt. Lărgirea acelor spații este invizibilă pentru ochi deoarece cititorii acceptă deja că spațiile dintre cuvinte variază. Un script care scrie fără spații între cuvinte, cum ar fi caracterele Han chinezești, kana japoneze, sau Hangul coreean, nu are astfel de îmbinări. Acolo marja trebuie să fie distribuită uniform între glifele adiacente, care este principiul pe care zeții tipografi japonezi îl numesc kintou-waritsuke, spațiere uniformă. Aplicarea întinderii spațiului de cuvânt în stil latin pe o linie CJK, sau înghesuirea întregii marje în singurul loc unde o linie CJK se întâmplă să conțină un spațiu, produce râurile și golurile care marchează ieșirile amatore

Cum decide HotPDF unde merge spațiul

HotPDF ia acea decizie per interval, nu per linie. Când justifică o linie parcurge fiecare pereche adiacentă de glif și întreabă dacă o limită care poate fi extinsă se află între ele. O limită poate fi extinsă când oricare parte este un spațiu sau tab, cazul latin, sau când ambele părți sunt caractere care pot fi întrerupte CJK, cazul de spațiere uniformă. Numără acele limite, împarte marja liniei în mod egal între ele, și adaugă acea cotă la fiecare interval calificant

Consecința apare în mod natural. O linie engleză are limite extensibile numai la spațiile sale de cuvinte, deci toată marja aterizează acolo și cuvintele se separă în timp ce literele din interiorul fiecărui cuvânt își păstrează spațierea naturală. O linie Han sau kana are o limită extensibilă între aproape fiecare pereche de glif, deci marja se distribuie uniform pe întreaga linie, exact spațierea uniformă inter-glif pe care o cer acele scripturi. O linie care este un singur cuvânt latin lung fără spațiu intern nu are nicio limită extensibilă, deci HotPDF o lasă la lățimea sa naturală mai degrabă decât să rupă cuvântul literă cu literă. Aceeași logică gestionează run-urile mixte latine și CJK într-o singură linie fără tratament special, deoarece decizia este locală la fiecare limită

O limită este exclusă deliberat pretutindeni. Poziția după ultimul glif al unei linii nu este niciodată tratată ca un interval, deoarece extinderea acolo ar reintroduce pur și simplu un rest de dreapta, care este opusul justificării

De ce ultima linie este lăsată singură

Ultima linie a unui paragraf este specială, iar greșirea ei este cel mai comun bug de justificare. Ultima linie a unui paragraf este de obicei scurtă, adesea numai câteva cuvinte, iar întinderea ei la lățimea completă a coloanei trage acele cuvinte pe toată pagina într-un rând rar, rupt. Tipografia corectă lasă ultima linie la lățimea sa naturală, aliniată la stânga

HotPDF detectează linia finală după poziție. Pe măsură ce împachetează textul în linii, știe când linia pe care tocmai a separat-o ajunge la sfârșitul șirului furnizat. Acea linie finală este emisă cu aliniere simplă la stânga și își păstrează lățimea naturală. Fiecare linie înaintea ei este justificată la ambele margini. Întreruperile de linie hard pe care le scrieți în text sunt respectate ca atare, deci o linie scurtă intenționată nu este niciodată extinsă. Cititorul vede un bloc dreptunghiular curat de text a cărui ultimă linie se termină natural, ceea ce se așteaptă ochiul

Costul de măsurare care a făcut justificarea lentă

Pentru a justifica o linie trebuie să știți lățimea sa exactă, și trebuie să știți avansul fiecărui glif astfel încât să puteți plasa spațiul suplimentar cu precizie. Prima implementare a obținut acele numere în mod evident. A măsurat întreaga linie cu o interogare completă de lățime Unicode, apoi a măsurat prefix după prefix pentru a recupera avansul fiecărui glif prin diferențiere. Pentru o linie de N glif, aceasta înseamnă N+1 apeluri în motorul de măsurare, și fiecare apel este un round-trip GDI complet, solicitând sistemului de operare să formeze și măsoare text și să trimită răspunsul înapoi

Per linie aceasta pare ieftină. Pe o pagină nu este. Luați o pagină A4 densă de text de corp, aproximativ patruzeci și cinci de linii de aproximativ optzeci de caractere fiecare. La N+1 round-trip-uri per linie aceasta înseamnă aproximativ 81 de round-trip-uri pentru fiecare linie și aproximativ 3.645 pentru pagină, aproape toate cheltuite pentru remăsurarea textului pe care motorul îl analizase deja cu momente înainte. Pe un job de lot care produce mii de pagini, acel overhead domină timpul de aspect, și fiecare round-trip traversează limita dintre procesul dvs. și subsistemul grafic

Un apel în loc de N plus unu

Remedierea este tipul de modificare care pare mică și plătește mare. GDI poate deja raporta lățimea totală a unui șir și poziția fiecărui glif într-o singură interogare. HotPDF expune aceasta prin GetWideCharAdvances, care umple o matrice cu avansul natural al fiecărui glif, inclusiv kerning, și returnează lățimea totală, într-un singur apel mai degrabă decât N+1. Rutina de justificare, _HPDFEmitJustifiedWideLine intern, solicită toate avansurile o dată, calculează marja, o distribuie pe limitele extensibile și emite linia

Pentru aceeași pagină A4 măsurarea per-linie scade de la aproximativ 81 de round-trip-uri la unul, deci pagina scade de la aproximativ 3.645 de round-trip-uri la aproximativ 45, aproape o reducere de optzeci de ori. Ieșirea este identică octet cu octet, deoarece nimic din măsurare nu s-a schimbat cu excepția de câte ori este solicitată. Același motor GDI, aceleași metrici de font, același kerning alimentează aceleași numere. Numai numărul de round-trip-uri a scăzut. Când o măsurare este deja corectă, optimizarea potrivită este să opriți să o solicitați în mod repetat, nu să o aproximați

Cum ajunge linia pe pagină

Odată ce marja este alocată, HotPDF emite linia cu ExtTextOut și o matrice de avans per-glif, matricea Dx. Fiecare intrare este distanța de la originea unui glif la cea ulterioară, care este avansul natural al acelui glif plus cota sa din marjă când o limită extensibilă urmează după acesta. Aceasta se mapează direct pe modelul de imagine PDF. Textul poziționat este scris cu operatorul TJ, o matrice care intercalează run-urile de glif cu ajustări orizontale explicite, iar valorile Dx devin exact acele ajustări. Acesta este motivul pentru care spațiul suplimentar aterizează între glif la poziții precise sub-punct mai degrabă decât să fie simulat cu caractere de padding, și de ce o linie justificată HotPDF se măsoară corect dacă un instrument din aval o citește înapoi

Nu apelați ExtTextOut dvs. pentru paragrafele justificate. Punctul de intrare este WideTextOutBox, care împachetează un șir Unicode într-o cutie și aplică alinierea pe care o solicitați. Împarte textul în linii care se potrivesc cu lățimea cutiei, plasează fiecare linie de-a lungul înălțimii cutiei, și returnează numărul de caractere pe care a reușit să le plaseze înainte de a rămâne fără spațiu vertical. Alinierea este aleasă prin enumerarea de justificare

type
  THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);

Primele trei sunt auto-explicative: aliniere stângă, centrată și dreaptă. Al patrulea, jtJustify, este justificarea completă pe ambele margini descrisă aici, și este valoarea pe care WideTextOutBox o citește pentru a activa spațierea conștientă de script

Justificarea unui paragraf în practică

Un exemplu complet creează un document, setează un font și toarnă un paragraf într-o cutie cu justificare completă. Același cod justifică textul latin și CJK fără o schimbare de indicator, deoarece conștientizarea scriptului trăiește sub API

uses
  HPDFDoc;

procedure JustifyParagraph;
var
  Pdf: THotPDF;
  Body: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Justified.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', 11);

    Body :=
      'Full justification spreads the slack on each filled line so both ' +
      'edges meet the column, while the last line keeps its natural width. ' +
      'For scripts with word gaps the space lands between words; for ' +
      'scripts without them it spreads evenly between glyphs.';

    // X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
    Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Pentru a desena același bloc aliniat la stânga, centrat sau aliniat la dreapta, schimbați numai argumentul final la jtLeft, jtCenter, sau jtRight. Împachetarea, plasarea liniei și valoarea returnată rămân aceleași. Lățimea măsurată care conduce toate cele patru căi provine din GetWideTextWidth, interogarea de lățime conștientă de Unicode care măsoară corect un WideString acolo unde măsurarea mai veche bazată pe octeți ar dimensiona greșit orice dincolo de Latin-1, ceea ce face ca cutia să împacheteze textul CJK și perechile de substitute la locul potrivit de la bun început

Justificarea este un strat dintr-un stack mai larg de formare a textului. Când o linie conține scripturi care reordonează sau își unesc glifele, deciziile de spațiere de aici se află deasupra lucrului descris în articolul nostru despre formarea textului cu scripturi complexe, și când un font poartă variante tipografice pe care doriți să le selectați, consultați cum să conduceți alternativele stilistice OpenType GSUB. Totul este livrat în HotPDF Component pentru Delphi și C++Builder, alături de API-urile mai largi de text, aspect și document acoperite pe acest blog