HotPDF eksporterer en side fra ethvert indlæst PDF-dokument til selvstændig SVG-markup med ét enkelt kald, BuildLoadedPageSVG, som returnerer det komplette SVG-dokument som en streng. Den eksporterede markup indeholder sidegeometrien, tekst som ægte SVG-text-elementer, indlejrede rasterbilleder og den stregtilstand, som PDF-operatorerne havde etableret ved hver enkelt tegneoperation
Det er netop her, de fleste hjemmelavede konvertere stille og roligt bryder sammen. At omdanne en PDF-side til SVG ligner et koordinatproblem, men viser sig at være et tilstandsproblem. PDF er en stakmaskine, hvis grafiktilstand ændrer sig, mens indholdsstrømmen fortolkes; SVG er et deklarativt træ, hvor hvert element bærer sine egne præsentationsattributter. Alt, hvad fortolkeren ikke når at tage et øjebliksbillede af, når et element udskrives, forsvinder simpelthen fra outputtet, og fejlen er tavs: man får gyldig SVG, der gengiver en side, som er subtilt forkert
Hvorfor konverteres en PDF-side ikke bare direkte til SVG?
Tre uoverensstemmelser gør konverteringen ikke-triviel, og alle tre giver output, der ser plausibelt ud, indtil man sammenligner det side om side med originalen. Den første er y-aksen. PDF-brugerrummet vokser opad fra sidens nederste venstre hjørne; SVG vokser nedad fra det øverste venstre hjørne. En enkelt vending på sideniveau retter tegnekoordinaterne og ødelægger derefter hvert eneste glyf, fordi en vending af hele lærredet også spejlvender bogstavformerne
Den anden uoverensstemmelse er arv. I PDF skubber og fjerner q og Q en grafiktilstand, der omfatter linjebredde, linjeafslutning, linjesammenføjning, gæringsgrænse, stiplemønster, stiplefase og alfa. I SVG arver et element, der ikke selv angiver en attribut, den fra en forfadergruppe, hvilket er en helt anden scoping-regel. En eksportør, der kun sporer den aktuelle transformationsmatrix og glemmer stregtilstanden, lader den genoprettede tilstand efter en Q lække ind i de efterfølgende elementer
Den tredje er, at PDF udtrykker flere ting via konvention frem for via værdi. Linjeafslutninger og -sammenføjninger er heltal, en linjebredde på nul betyder en hårstreg i enhedsrummet frem for en usynlig linje, og stjernevarianterne af tegneoperatorerne ændrer omløbsreglen i stedet for farven. Hver af disse kræver en oversættelse, ikke en kopiering
Ét kald til det almindelige tilfælde
Til den almindelige opgave med at eksportere sider til en webviewer, et diff-værktøj eller en designoverdragelse er API-fladen én enkelt funktion. BuildLoadedPageSVG tager et nulbaseret sideindeks mod det aktuelt indlæste dokument og returnerer SVG-dokumentet som en 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 returnerer sideantallet
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;
Den samme eksport stilles til rådighed af HotPDFs kommandolinjeværktøj som kommandoen export-svg, hvilket er nyttigt i build-pipelines og regressionsscripts, hvor man ønsker en tekst-diffbar repræsentation af en side uden at skrive en eneste linje Pascal. Fordi SVG er tekst, er det en naturlig følgesvend til rasterstien beskrevet i rendering af en PDF-side til et bitmap: bitmappet fortæller, hvordan siden ser ud, SVG'en fortæller, hvad den er lavet af
Hvordan afbildes PDF-tekst på SVG-tekstelementer?
HotPDF sammensætter tekstmatrixkæden som præfiks gange CTM gange tekstmatrix gange glyfvending, hvor glyfvendingen er en højremultiplikation med matrix(1,0,0,-1,0,0). Denne højrehåndsfaktor findes udelukkende for at ophæve sidens lodrette vending for glyfformerne, da SVG-tekst tegnet i en vendt lokal ramme ellers ville fremstå på hovedet. Ved at placere korrektionen i matricen frem for i specialtilfældskode kommer roteret, spejlvendt og skævvredet tekst alt sammen ud korrekt uden ekstra forgreninger
Vandret positionering bruger SVG-text-elementets fler-værdi-syntaks for x, én koordinat pr. tegn, akkumuleret fra hver glyfforskydning plus den tegnafstand Tc og ordafstand Tw, der var gældende på det tidspunkt. Vandret skalering Tz foldes ind i a- og c-kolonnerne i tekstmatricen frem for at blive udskrevet separat, så en viewer, der ignorerer eksotiske tekstattributter, stadig placerer hvert glyf, hvor PDF'en satte det. Tekst produceret via kompleks formgivning, beskrevet i kompleks skript-tekstformgivning, følger samme vej, fordi formgiveren allerede har opløst klynger til positionerede glyffer, når indholdsstrømmen fortolkes
Rotation og billeder: to vendinger, det er let at få vendt forkert
En side med en /Rotate-værdi forskellig fra nul kræver en for-transformation sammensat af en vending mod det roterede lærreds højde og en rotation udtrykt i et y-op-visningsrum. De tre rotationsmatricer er (0,-1,1,0,0,W) for 90 grader, (-1,0,0,-1,W,H) for 180 grader og (0,1,-1,0,H,0) for 270 grader, hvor W og H er sidens dimensioner før rotation. At udlede disse i hånden indbyder til fortegnsfejl på præcis tre steder, så eksportøren sammensætter dem via den samme matrixmultiplikationsrutine, der håndterer alle andre transformationer
Indlejrede billeder kræver deres egen vending, fordi PDF-billedrummet placerer den første prøverække ved øverste kant af enhedskvadratet, mens SVG-image-elementet har en y-ned lokal ramme. Den udskrevne transformation er derfor CTM'en højremultipliceret med matrix(1,0,0,-1,0,1). At få dette galt giver lodret spejlvendte fotografier på en ellers perfekt side, hvilket er den slags fejl, en anmelder opdager øjeblikkeligt, mens en automatiseret test ofte ikke gør
Hvad bevarer grafiktilstandsenheden egentlig?
HotPDF sender stregtilstandsoperatorerne w, J, j, M og d videre gennem en separat, valgfri enhedsgrænseflade, så stregtroskab blev tilføjet uden at ændre vtable'en for den eksisterende indholdsenhed og uden at bryde binær kompatibilitet for kode bygget mod tidligere versioner. Konkret modtager den eksporterede SVG oversatte nøgleord frem for rå PDF-heltal:
// PDF-heltalsopregninger bliver til SVG-nøgleordsattributter
// linjeafslutning 0, 1, 2 -> butt, round, square
// linjesammenføjning 0, 1, 2 -> miter, round, bevel
//
// En linjebredde på nul betyder en hårstreg i enhedsrummet i PDF, så
// eksportøren udskriver vector-effect="non-scaling-stroke" for at holde
// stregen synlig og tæt på én enhedspixel bred efter CTM'en
//
// f* B* b* vælger lige-ulige-reglen og udskriver fill-rule="evenodd",
// mens f B b bevarer SVG's standard for nonzero-omløb
Genoprettelse af tilstand ved en Q dækker opacitet, linjebredde, afslutning, sammenføjning, gæringsgrænse, stiplemønster og stiplefase samlet. Indlejrede Form XObjects tager et øjebliksbillede af og genopretter det samme fulde sæt ved deres grænser, så en stiplet kant defineret inde i et stempel ikke kan lække sit mønster ind i det sideindhold, der følger efter. Hvis man allerede sporer klipning og CTM-adfærd af andre grunde, er dette den samme tilstandsmodel, der optræder i EMF- og WMF-vektorimport, blot kørende i den modsatte retning
Grænser, det er værd at kende, før man sender det i produktion
Eksportøren er ærlig omkring sit omfang, og at lære grænserne at kende på forhånd er billigere end at opdage dem i produktion. Farve når frem til SVG-enheden via operatorerne rg, RG, g og G. Fyldninger etableret via et farverum plus scn, som er den måde, Separation-, DeviceN- og ICCBased-farver males på, ankommer ikke til enheden som en opløst RGB-triplet, så sider, der bruger spotfarver på denne måde, eksporterer deres geometri, men ikke disse farver. Til trykorienterede kilder bør man i stedet rasterisere eller udjævne spotfarverne først; selve malemodellen er beskrevet i rendering af Separation- og DeviceN-spotfarver
To mindre bemærkninger sparer fejlfindingstid. Hexadecimale farveliteraler udskrives med store bogstaver, så en test, der forventer #ff0000, fejler mod et helt korrekt #FF0000. Og SVG-enheden er referencetalt via sin grænseflade, hvilket betyder, at frigivelse handler om at lade grænsefladen gå ud af scope frem for at kalde Free på objektet, en forskel der betyder noget, hvis man udvider enheden til at udskrive sin egen markup sammen med sideindholdet
SVG-eksport passer naturligt sammen med strukturel sammenligning, når man har brug for at vide, om et genereret dokument reelt ændrede sig mellem to builds. Det bredere værktøjssæt omkring indlæste dokumenter, fra rendering til redigering til eksport, er dokumenteret på HotPDF Delphi PDF-komponentsiden