Odborný článok

Export stránok PDF do SVG v Delphi pomocou HotPDF

HotPDF exportuje stránku ľubovoľného načítaného PDF dokumentu do samostatného SVG markupu jediným volaním, BuildLoadedPageSVG, ktoré vráti kompletný SVG dokument ako reťazec. Exportovaný markup obsahuje geometriu stránky, text ako skutočné SVG elementy text, vložené rastrové obrázky a stav ťahu, ktorý PDF operátory nastavili pri každej kresliacej operácii

Práve v tejto poslednej časti väčšina domácich konvertorov potichu zlyháva. Prevod stránky PDF na SVG vyzerá ako problém súradníc, no v skutočnosti ide o problém stavu. PDF je zásobníkový stroj, ktorého grafický stav sa mení počas interpretácie obsahového streamu; SVG je deklaratívny strom, v ktorom každý element nesie vlastné prezentačné atribúty. Čokoľvek, čo interpreter v okamihu vytvorenia elementu nezachytí, sa jednoducho stratí z výstupu, a toto zlyhanie je tiché: dostanete platné SVG, ktoré vykresľuje jemne nesprávnu stránku

Prečo sa stránka PDF jednoducho neprevedie na SVG?

Konverziu komplikujú tri nesúlady a všetky tri produkujú výstup, ktorý pôsobí vierohodne, kým ho neporovnáte bok po boku s originálom. Prvým je os y. Používateľský priestor PDF rastie smerom nahor od ľavého dolného rohu stránky; SVG rastie smerom nadol od ľavého horného rohu. Jediné prevrátenie na úrovni stránky opraví súradnice kresby, no následne pokazí každý glyf, pretože prevrátenie celého plátna zrkadlovo obráti aj tvary písmen

Druhým nesúladom je dedičnosť. V PDF operátory q a Q ukladajú na zásobník a z neho vyberajú grafický stav, ktorý zahŕňa šírku čiary, zakončenie čiary, spojenie čiar, limit skosenia, pole čiarkovania, fázu čiarkovania a alfu. V SVG element, ktorý atribút nepomenuje, ho zdedí od nadradenej skupiny, čo je úplne iné pravidlo platnosti. Exportér, ktorý sleduje iba aktuálnu transformačnú maticu a zabúda na stav ťahu, umožní, aby obnovený stav po Q unikol do nasledujúcich elementov

Tretím je, že PDF vyjadruje viaceré veci konvenciou, nie hodnotou. Zakončenia a spojenia čiar sú celé čísla, nulová šírka čiary znamená vlasovú čiaru v priestore zariadenia, nie neviditeľnú čiaru, a hviezdičkové varianty kresliacich operátorov menia pravidlo vinutia namiesto farby. Každá z týchto vecí si vyžaduje preklad, nie kópiu

Jedno volanie pre bežný prípad

Pre bežnú úlohu exportu stránok pre webový prehliadač, porovnávací nástroj alebo odovzdanie dizajnu stačí jediná funkcia API. BuildLoadedPageSVG prijíma index stránky od nuly voči aktuálne načítanému dokumentu a vracia SVG dokument ako 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 vráti počet stránok
    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;

Rovnaký export sprístupňuje aj nástroj príkazového riadka HotPDF ako príkaz export-svg, ktorý je užitočný v build pipelinách a regresných skriptoch, kde potrebujete textovo porovnateľnú reprezentáciu stránky bez písania akéhokoľvek Pascal kódu. Keďže SVG je text, prirodzene dopĺňa rastrovú cestu opísanú v časti vykresľovanie stránky PDF do bitmapy: bitmapa vám ukáže, ako stránka vyzerá, SVG vám ukáže, z čoho je zložená

Ako sa text PDF mapuje na elementy textu SVG?

HotPDF skladá reťazec textovej matice ako predponu krát CTM krát textová matica krát prevrátenie glyfu, pričom prevrátenie glyfu je pravostranné násobenie maticou matrix(1,0,0,-1,0,0). Tento pravostranný faktor existuje výlučne na zrušenie zvislého prevrátenia na úrovni stránky pre tvary glyfov, pretože text SVG vykreslený v prevrátenom lokálnom rámci by inak vyzeral hore nohami. Umiestnenie tejto korekcie do matice namiesto do špeciálne ošetreného kódu znamená, že otočený, zrkadlený aj skosený text vždy vyjde správne bez ďalších vetvení

Horizontálne polohovanie využíva viachodnotovú syntax x elementu SVG text, jednu súradnicu na znak, akumulovanú z posunu každého glyfu plus medzery znakov Tc a medzery slov Tw platné v danom okamihu. Horizontálne škálovanie Tz sa premieta do stĺpcov a a c textovej matice namiesto toho, aby sa vypisovalo samostatne, takže prehliadač, ktorý ignoruje exotické textové atribúty, aj tak umiestni každý glyf presne tam, kam ho umiestnilo PDF. Rovnakou cestou prechádza aj text vytvorený komplexným tvarovaním, opísaný v časti tvarovanie textu v komplexných písmach, pretože tvarovač už v čase interpretácie obsahového streamu vyriešil zhluky na umiestnené glyfy

Otočenie a obrázky: dve prevrátenia, ktoré sa dajú ľahko zameniť

Stránka s nenulovým záznamom /Rotate potrebuje predtransformáciu zloženú z prevrátenia voči výške otočeného plátna a otočenia vyjadreného v zobrazovacom priestore s osou y smerom nahor. Tri rotačné matice sú (0,-1,1,0,0,W) pre 90 stupňov, (-1,0,0,-1,W,H) pre 180 a (0,1,-1,0,H,0) pre 270, pričom W a H sú rozmery stránky pred otočením. Ich ručné odvodenie zvádza k chybám v znamienkach presne na troch miestach, takže exportér ich skladá cez rovnakú rutinu násobenia matíc, ktorá spracúva všetky ostatné transformácie

Vložené obrázky potrebujú vlastné prevrátenie, pretože priestor obrázka PDF umiestňuje prvý riadok vzoriek na horný okraj jednotkového štvorca, zatiaľ čo element SVG image nesie lokálny rámec s osou y smerom nadol. Vypísaná transformácia je teda CTM pravostranne vynásobená maticou matrix(1,0,0,-1,0,1). Chyba na tomto mieste spôsobí zvislo zrkadlené fotografie na inak dokonalej stránke, čo je typ chyby, ktorú recenzent postrehne okamžite, no automatizovaný test často nie

Čo presne zachováva zariadenie grafického stavu?

HotPDF odosiela operátory stavu ťahu w, J, j, M a d cez samostatné voliteľné rozhranie zariadenia, takže vernosť ťahu bola pridaná bez zmeny vtable existujúceho obsahového zariadenia a bez porušenia binárnej kompatibility pre kód zostavený voči starším verziám. Konkrétne exportované SVG dostáva preložené kľúčové slová namiesto surových celých čísel PDF:

// Celočíselné enumerácie PDF sa menia na kľúčové slová atribútov SVG
//   zakončenie čiary  0, 1, 2  ->  butt, round, square
//   spojenie čiar     0, 1, 2  ->  miter, round, bevel
//
// Nulová šírka čiary v PDF znamená vlasovú čiaru v priestore zariadenia,
// preto exportér vypisuje vector-effect="non-scaling-stroke", aby ťah
// zostal viditeľný a po CTM mal šírku blízku jednému pixelu zariadenia
//
// f* B* b* volia pravidlo párnosti-nepárnosti a vypisujú fill-rule="evenodd",
// zatiaľ čo f B b zachovávajú predvolené nenulové vinutie SVG

Obnovenie stavu pri Q pokrýva naraz priehľadnosť, šírku čiary, zakončenie, spojenie, limit skosenia, pole čiarkovania aj fázu čiarkovania. Vnorené Form XObjects zachytávajú a obnovujú rovnakú úplnú sadu na svojich hraniciach, takže čiarkovaný okraj definovaný vo vnútri pečiatky nemôže presiaknuť svoj vzor do nasledujúceho obsahu stránky. Ak už z iných dôvodov sledujete správanie orezávania a CTM, ide o rovnaký model stavu, aký sa objavuje pri importe vektorov EMF a WMF, len bežiaci opačným smerom

Hranice, ktoré je dobré poznať pred nasadením

Exportér je úprimný ohľadom svojho rozsahu a spoznanie jeho hraníc vopred je lacnejšie než ich objavenie až v produkcii. Farba sa dostáva do zariadenia SVG cez operátory rg, RG, g a G. Výplne nastavené cez farebný priestor plus scn, čím sa maľujú farby Separation, DeviceN a ICCBased, sa do zariadenia nedostanú ako vyriešená RGB trojica, takže stránky, ktoré takto používajú priame farby, exportujú svoju geometriu, ale nie tieto farby. Pri zdrojoch určených na tlač namiesto toho radšej rastrizujte alebo priame farby najprv sploštite; samotný model maľovania opisuje časť vykresľovanie priamych farieb Separation a DeviceN

Dve menšie poznámky ušetria čas pri ladení. Hexadecimálne farebné literály sa vypisujú veľkými písmenami, takže test overujúci #ff0000 zlyhá voči úplne správnemu #FF0000. A zariadenie SVG má počítanie referencií cez svoje rozhranie, čo znamená, že jeho uvoľnenie je záležitosťou toho, že rozhranie opustí platnosť, a nie volania Free na objekte — rozdiel, na ktorom záleží, ak zariadenie rozšírite tak, aby popri obsahu stránky vypisovalo aj vlastný markup

Export do SVG sa prirodzene dopĺňa so štrukturálnym porovnávaním, keď potrebujete zistiť, či sa vygenerovaný dokument medzi dvoma zostaveniami skutočne zmenil. Širšia sada nástrojov okolo načítaných dokumentov, od vykresľovania cez úpravy až po export, je zdokumentovaná na stránke HotPDF Delphi PDF component