Articol tehnic

Exportă pagini PDF în SVG din Delphi cu HotPDF

HotPDF exportă o pagină din orice document PDF încărcat în marcaj SVG independent printr-un singur apel, BuildLoadedPageSVG, care returnează documentul SVG complet ca șir de caractere. Marcajul exportat conține geometria paginii, textul ca elemente SVG text reale, imaginile raster încorporate și starea de trasare pe care operatorii PDF au stabilit-o la fiecare operație de desenare

Tocmai acea ultimă parte este locul unde majoritatea convertoarelor construite intern cedează în tăcere. Transformarea unei pagini PDF în SVG pare o problemă de coordonate și se dovedește a fi o problemă de stare. PDF este o mașină cu stivă a cărei stare grafică se modifică pe măsură ce fluxul de conținut este interpretat; SVG este un arbore declarativ ale cărui elemente poartă fiecare propriile atribute de prezentare. Tot ce interpretorul nu reușește să surprindă în momentul în care un element este emis dispare pur și simplu din rezultat, iar eșecul este silențios: obții un SVG valid care randează o pagină ușor greșită

De ce o pagină PDF nu se convertește pur și simplu în SVG?

Trei nepotriviri fac conversia nebanală, iar toate trei produc un rezultat care pare plauzibil până îl compari, unul lângă altul, cu originalul. Prima este axa y. Spațiul utilizator PDF crește în sus de la colțul din stânga jos al paginii; SVG crește în jos de la colțul din stânga sus. O singură oglindire la nivel de pagină corectează coordonatele de desenare și apoi strică fiecare glif, deoarece oglindirea întregii pânze oglindește și formele literelor

A doua nepotrivire este moștenirea. În PDF, q și Q pun și scot dintr-o stivă o stare grafică ce include grosimea liniei, capătul liniei, îmbinarea liniei, limita de unghi (miter limit), matricea de linii întrerupte, faza liniilor întrerupte și alfa. În SVG, un element care nu numește un atribut îl moștenește de la un grup ascendent, ceea ce este o regulă de scop complet diferită. Un exportator care urmărește doar matricea de transformare curentă și uită starea de trasare permite ca starea restaurată după un Q să se scurgă în elementele care urmează

A treia este că PDF exprimă mai multe lucruri prin convenție, nu prin valoare. Capetele și îmbinările liniilor sunt numere întregi, o grosime de linie zero înseamnă o linie fină la nivel de dispozitiv, nu o linie invizibilă, iar variantele cu asterisc ale operatorilor de pictare schimbă regula de înfășurare în loc de culoare. Fiecare dintre acestea necesită o traducere, nu o copiere

Un singur apel pentru cazul obișnuit

Pentru sarcina obișnuită de export al paginilor pentru un vizualizator web, un instrument de comparare sau o predare de design, suprafața API-ului este o singură funcție. BuildLoadedPageSVG primește un index de pagină bazat pe zero pentru documentul încărcat curent și returnează documentul SVG ca AnsiString:

var
  Pdf: THotPDF;
  I: Integer;
  Svg: AnsiString;
  Output: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('statements.pdf', '') <= 0 then
      Exit;                     // LoadFromFile returnează numărul de pagini
    for I := 0 to Pdf.LoadedPageCount - 1 do
    begin
      Svg := Pdf.BuildLoadedPageSVG(I);
      if Length(Svg) = 0 then
        Continue;
      Output := TFileStream.Create(Format('page-%d.svg', [I + 1]), fmCreate);
      try
        Output.WriteBuffer(Svg[1], Length(Svg));
      finally
        Output.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Același export este expus de instrumentul de linie de comandă HotPDF ca și comanda sa export-svg, utilă în pipeline-uri de build și scripturi de regresie unde ai nevoie de o reprezentare a unei pagini comparabilă prin diff de text, fără să scrii niciun cod Pascal. Deoarece SVG este text, este un partener natural pentru calea raster descrisă în randarea unei pagini PDF într-un bitmap: bitmap-ul îți arată cum arată pagina, SVG-ul îți arată din ce este alcătuită

Cum este mapat textul PDF pe elementele de text SVG?

HotPDF compune lanțul matricei de text ca prefix înmulțit cu CTM înmulțit cu matricea de text înmulțit cu oglindirea glifului, unde oglindirea glifului este o înmulțire la dreapta cu matrix(1,0,0,-1,0,0). Acest factor din dreapta există exclusiv pentru a anula oglindirea verticală la nivel de pagină pentru formele glifelor, deoarece textul SVG desenat într-un cadru local oglindit ar apărea altfel cu susul în jos. Plasarea corecției în matrice, nu în cod tratat special, înseamnă că textul rotit, oglindit și înclinat iese corect fără ramificații suplimentare

Poziționarea orizontală folosește sintaxa multi-valoare x a elementului SVG text, o coordonată pentru fiecare caracter, acumulată din avansul fiecărui glif plus spațierea caracterelor Tc și spațierea cuvintelor Tw aflate în vigoare la acel moment. Scalarea orizontală Tz este încorporată în coloanele a și c ale matricei de text, nu emisă separat, astfel încât un vizualizator care ignoră atributele de text exotice tot plasează fiecare glif exact unde l-a pus PDF-ul. Textul produs prin formare complexă, acoperit în formarea textului pentru scripturi complexe, parcurge aceeași cale, deoarece formatorul a rezolvat deja clusterele în glife poziționate până în momentul în care fluxul de conținut este interpretat

Rotația și imaginile: două oglindiri ușor de inversat din greșeală

O pagină cu o intrare /Rotate diferită de zero are nevoie de o pre-transformare compusă dintr-o oglindire față de înălțimea pânzei rotite și o rotație exprimată în spațiul de afișare cu y în sus. Cele trei matrice de rotație sunt (0,-1,1,0,0,W) pentru 90 de grade, (-1,0,0,-1,W,H) pentru 180 și (0,1,-1,0,H,0) pentru 270, cu W și H dimensiunile paginii dinainte de rotație. Derivarea lor manuală invită erori de semn în exact trei locuri, așa că exportatorul le compune prin aceeași rutină de înmulțire matricială care gestionează orice altă transformare

Imaginile încorporate au nevoie de propria lor oglindire, deoarece spațiul imaginii PDF plasează primul rând de eșantioane la marginea de sus a pătratului unitate, în timp ce elementul SVG image poartă un cadru local cu y în jos. Transformarea emisă este așadar CTM înmulțit la dreapta cu matrix(1,0,0,-1,0,1). Greșirea acestui pas produce fotografii oglindite vertical pe o pagină altfel perfectă, exact genul de defect pe care un recenzent îl observă instantaneu și un test automat adesea nu

Ce păstrează efectiv dispozitivul de stare grafică?

HotPDF direcționează operatorii de stare a trasării w, J, j, M și d printr-o interfață de dispozitiv opțională separată, astfel încât fidelitatea trasării a fost adăugată fără să modifice vtable-ul dispozitivului de conținut existent și fără să strice compatibilitatea binară pentru codul construit pe versiuni anterioare. Concret, SVG-ul exportat primește cuvinte-cheie traduse, nu numere întregi PDF brute:

// Enumerările întregi PDF devin atribute SVG cu cuvinte-cheie
//   capăt linie  0, 1, 2  ->  butt, round, square
//   îmbinare linie 0, 1, 2  ->  miter, round, bevel
//
// O grosime de linie zero înseamnă o linie fină la nivel de dispozitiv în PDF, așa că
// exportatorul emite vector-effect="non-scaling-stroke" pentru a păstra
// trasarea vizibilă și lată de aproximativ un pixel de dispozitiv după CTM
//
// f* B* b* selectează regula even-odd și emit fill-rule="evenodd",
// în timp ce f B b păstrează implicita SVG de înfășurare nonzero

Restaurarea stării la un Q acoperă opacitatea, grosimea liniei, capătul, îmbinarea, limita de unghi, matricea de linii întrerupte și faza liniilor întrerupte împreună. XObjects Form imbricate surprind și restaurează același set complet la limitele lor, astfel încât un chenar întrerupt definit în interiorul unei ștampile nu-și poate scurge modelul în conținutul de pagină care urmează. Dacă deja urmărești comportamentul de decupare și CTM din alte motive, acesta este același model de stare care apare în importul vectorial EMF și WMF, rulând în direcție opusă

Limite bune de știut înainte de a trimite în producție

Exportatorul este onest cu privire la sfera sa, iar învățarea limitelor din timp e mai ieftină decât descoperirea lor în producție. Culoarea ajunge la dispozitivul SVG prin operatorii rg, RG, g și G. Umplerile stabilite printr-un spațiu de culoare plus scn, modul în care sunt pictate culorile Separation, DeviceN și ICCBased, nu ajung la dispozitiv ca triplet RGB rezolvat, așa că paginile care folosesc culori spot în acest fel își exportă geometria, dar nu și acele culori. Pentru surse orientate spre tipar, rasterizează în schimb sau aplatizează mai întâi culorile spot; modelul de pictare în sine este acoperit în randarea culorilor spot Separation și DeviceN

Două note mai mici economisesc timp de depanare. Literalii de culoare hexazecimali sunt emiși cu majuscule, așa că un test care verifică #ff0000 eșuează pentru un #FF0000 perfect corect. Iar dispozitivul SVG este numărat prin referință prin interfața sa, ceea ce înseamnă că eliberarea lui înseamnă lăsarea interfeței să iasă din domeniul de vizibilitate, nu apelarea Free pe obiect, o distincție care contează dacă extinzi dispozitivul pentru a emite propriul tău marcaj alături de conținutul paginii

Exportul SVG se potrivește natural cu comparația structurală atunci când ai nevoie să știi dacă un document generat s-a schimbat cu adevărat între două build-uri. Setul mai larg de instrumente din jurul documentelor încărcate, de la randare la editare la export, este documentat pe pagina componentei HotPDF PDF pentru Delphi