Technický článek

Export stránek PDF do SVG v Delphi s HotPDF

HotPDF exportuje stránku libovolného načteného PDF dokumentu do samostatného SVG kódu jediným voláním, BuildLoadedPageSVG, které vrátí kompletní SVG dokument jako řetězec. Exportovaný kód nese geometrii stránky, text jako skutečné SVG prvky text, vložené rastrové obrázky a stav tahu, který operátory PDF nastavily při každé kreslicí operaci

Právě v této poslední části většina domácích konvertorů tiše selhává. Převod stránky PDF do SVG vypadá jako problém se souřadnicemi, ale ve skutečnosti jde o problém se stavem. PDF je zásobníkový stroj, jehož grafický stav se mění během interpretace obsahového proudu; SVG je deklarativní strom, jehož prvky nesou vlastní prezentační atributy. Cokoli, co interpret nezachytí ve chvíli, kdy je prvek vytvořen, se ve výstupu jednoduše ztratí, a selhání je tiché: dostanete platné SVG, které vykresluje jemně chybnou stránku

Proč se stránka PDF nedá jen tak převést do SVG?

Tři nesrovnalosti dělají převod netriviálním, a všechny tři produkují výstup, který vypadá věrohodně, dokud jej neporovnáte s originálem vedle sebe. První je osa y. Uživatelský prostor PDF roste nahoru od levého dolního rohu stránky; SVG roste dolů od levého horního rohu. Jediné převrácení na úrovni stránky opraví souřadnice kreslení, ale zároveň rozbije každý glyf, protože převrácení celého plátna zrcadlí i tvary písmen

Druhou nesrovnalostí je dědičnost. V PDF operátory q a Q ukládají a obnovují grafický stav, který zahrnuje šířku čáry, zakončení čáry, spojení čar, limit ostrého zakončení, vzor čárkování, fázi čárkování a alfu. V SVG prvek, který atribut neuvádí, jej dědí z nadřazené skupiny, což je zcela odlišné pravidlo platnosti. Exportér, který sleduje pouze aktuální transformační matici a zapomíná na stav tahu, nechá obnovený stav po Q prosáknout do prvků, které následují

Třetí je, že PDF vyjadřuje několik věcí konvencí, nikoli hodnotou. Zakončení a spojení čar jsou celá čísla, nulová šířka čáry znamená vlasovou linii v prostoru zařízení, nikoli neviditelnou čáru, a hvězdičkové varianty kreslicích operátorů mění pravidlo vinutí místo barvy. Každá z nich potřebuje překlad, nikoli kopii

Jedno volání pro běžný případ

Pro běžný úkol exportu stránek pro webový prohlížeč, nástroj pro porovnávání rozdílů nebo předání návrhu je rozhraní API jedinou funkcí. BuildLoadedPageSVG přijímá index stránky počítaný od nuly vůči aktuálně načtenému dokumentu a vrací SVG dokument jako 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 vrací počet stránek
    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;

Stejný export zpřístupňuje nástroj HotPDF pro příkazový řádek jako příkaz export-svg, který se hodí v buildovacích pipeline a regresních skriptech, kde chcete textově porovnatelnou reprezentaci stránky bez psaní jakéhokoli Pascalu. Protože SVG je text, tvoří přirozený doplněk k rastrové cestě popsané v článku vykreslování stránky PDF do bitmapy: bitmapa vám řekne, jak stránka vypadá, SVG vám řekne, z čeho se skládá

Jak se text PDF mapuje na prvky SVG text?

HotPDF skládá řetězec textové matice jako prefix krát CTM krát textová matice krát převrácení glyfu, kde převrácení glyfu je pravé násobení maticí matrix(1,0,0,-1,0,0). Tento pravostranný faktor existuje výhradně proto, aby zrušil svislé převrácení na úrovni stránky pro tvary glyfů, protože text SVG vykreslený v převráceném lokálním rámci by se jinak zobrazil vzhůru nohama. Umístění korekce do matice namísto zvláštního kódu pro speciální případy znamená, že otočený, zrcadlený a zkosený text vyjde správně bez dalších větvení

Vodorovné umístění používá vícehodnotovou syntaxi atributu x prvku SVG text, jednu souřadnici na znak, akumulovanou z posunu každého glyfu plus mezera mezi znaky Tc a mezera mezi slovy Tw platné v daném okamžiku. Vodorovné měřítko Tz je zapracováno do sloupců a a c textové matice namísto samostatného vydání, takže prohlížeč, který ignoruje exotické textové atributy, přesto umístí každý glyf tam, kam jej umístilo PDF. Text vytvořený složitým tvarováním, popsaný v článku tvarování textu ve složitých písmech, prochází stejnou cestou, protože tvarovač už v okamžiku interpretace obsahového proudu rozřešil shluky do umístěných glyfů

Otočení a obrázky: dvě převrácení, u kterých se snadno spletou strany

Stránka s nenulovou položkou /Rotate potřebuje předtransformaci složenou z převrácení vůči výšce otočeného plátna a otočení vyjádřeného v zobrazovacím prostoru s osou y nahoru. Tři rotační matice jsou (0,-1,1,0,0,W) pro 90 stupňů, (-1,0,0,-1,W,H) pro 180 a (0,1,-1,0,H,0) pro 270, kde W a H jsou rozměry stránky před otočením. Ruční odvození těchto matic svádí k chybám ve znaménku přesně na třech místech, takže exportér je skládá stejnou rutinou pro násobení matic, která zpracovává každou další transformaci

Vložené obrázky potřebují vlastní převrácení, protože obrazový prostor PDF umísťuje první řádek vzorků na horní hranu jednotkového čtverce, zatímco prvek SVG image nese lokální rámec s osou y dolů. Vydávaná transformace je tedy CTM pravostranně vynásobená maticí matrix(1,0,0,-1,0,1). Chyba na tomto místě způsobí svisle zrcadlené fotografie na jinak dokonalé stránce, což je typ vady, kterou recenzent postřehne okamžitě, zatímco automatizovaný test často ne

Co si zařízení grafického stavu skutečně zachovává?

HotPDF předává operátory stavu tahu w, J, j, M a d přes samostatné volitelné rozhraní zařízení, takže věrnost tahu byla přidána bez změny vtable stávajícího obsahového zařízení a bez narušení binární kompatibility pro kód sestavený proti dřívějším verzím. Konkrétně exportované SVG dostává přeložená klíčová slova namísto syrových celočíselných hodnot PDF:

// Celočíselné výčty PDF se stávají klíčovými slovy atributů SVG
//   zakončení čáry  0, 1, 2  ->  butt, round, square
//   spojení čar 0, 1, 2  ->  miter, round, bevel
//
// Nulová šířka čáry v PDF znamená vlasovou linii v prostoru zařízení, proto
// exportér vydává vector-effect="non-scaling-stroke", aby tah zůstal
// viditelný a po aplikaci CTM měl šířku blízkou jednomu pixelu zařízení
//
// f* B* b* volí pravidlo lichý-sudý a vydávají fill-rule="evenodd",
// zatímco f B b zachovávají výchozí nenulové vinutí SVG

Obnovení stavu při Q zahrnuje společně neprůhlednost, šířku čáry, zakončení, spojení, limit ostrého zakončení, vzor čárkování a fázi čárkování. Vnořené Form XObjects zachycují a obnovují stejnou úplnou sadu na svých hranicích, takže čárkovaný okraj definovaný uvnitř razítka nemůže prosáknout svým vzorem do následujícího obsahu stránky. Pokud už z jiných důvodů sledujete chování ořezu a CTM, jde o stejný model stavu, jaký se objevuje v článku import vektorů EMF a WMF, běžící opačným směrem

Hranice, které se vyplatí znát před nasazením

Exportér je upřímný ohledně svého rozsahu a poznat jeho hranice předem je levnější než je objevit až v produkčním nasazení. Barva se dostává do zařízení SVG přes operátory rg, RG, g a G. Výplně vytvořené přes barevný prostor plus scn, což je způsob, jakým se vykreslují barvy Separation, DeviceN a ICCBased, se do zařízení nedostávají jako rozřešená trojice RGB, takže stránky, které tímto způsobem používají přímé barvy, exportují svou geometrii, ale ne tyto barvy. U zdrojů určených k tisku raději rastrujte nebo přímé barvy nejprve zploštěte; samotný model vykreslování popisuje článek vykreslování přímých barev Separation a DeviceN

Dvě menší poznámky ušetří čas při ladění. Hexadecimální barevné literály se vydávají velkými písmeny, takže test ověřující #ff0000 selže proti naprosto správnému #FF0000. A zařízení SVG je počítáno referencemi přes své rozhraní, což znamená, že jeho uvolnění je otázkou toho, nechat rozhraní opustit rozsah platnosti, nikoli volat Free na objektu, což je rozdíl, na kterém záleží, pokud zařízení rozšíříte tak, aby vydávalo vlastní kód vedle obsahu stránky

Export do SVG se přirozeně doplňuje se strukturálním porovnáním, když potřebujete zjistit, zda se vygenerovaný dokument mezi dvěma sestaveními skutečně změnil. Širší sada nástrojů kolem načtených dokumentů, od vykreslování přes úpravy až po export, je zdokumentována na stránce komponenty HotPDF PDF pro Delphi