Teknisk artikel

Importer SVG i PDF som genanvendelige Form XObjects

HotPDF importerer en SVG-fil i et indlæst eller oprettet PDF-dokument som et genanvendeligt Form XObject via ImportSVGFormXObject, som returnerer ressourcenavnet på den oprettede formular. At placere den er et separat kald, DrawSVGFormXObject, så én import kan stemples på mange sider i forskellige størrelser uden at duplikere en eneste byte af grafikken

Denne opdeling betyder mere, end det lyder som. Et logo rasteriseret ved 300 DPI og indsat på 400 sider er 400 kopier af et bitmap. Det samme logo som et Form XObject er ét objekt, der refereres 400 gange, det forbliver skarpt ved enhver zoom, og det udskrives ved enhedens opløsning i stedet for ved hvilken DPI, der virkede tilstrækkelig på opbygningstidspunktet

Diagram af én SVG-import, der opretter et enkelt PDF Form XObject, som DrawSVGFormXObject placerer på mange sider i forskellige størrelser, sammenlignet med hundredvis af rasterbitmap-kopier
Én import skriver grafikken én gang som et Form XObject med sine egne ressourcer, og hver placering er en lille påkaldelse, der ikke tilføjer duplikerede bytes

Hvorfor et Form XObject i stedet for at tegne SVG'en hver gang?

Et Form XObject er PDF's egen mekanisme til genanvendeligt indhold: en selvstændig content stream med sin egen bounding box og resource dictionary, der påkaldes ved navn. At importere SVG til et sådant giver dig tre egenskaber, som direkte tegning ikke giver. Grafikken findes kun én gang i filen. Hver placering er blot en matrix og en påkaldelse. Og formerne forbliver vektorer, så en fremviser ved 800 procent zoom tegner kurver, ikke pixels

Finessen ligger i ressourcerne. En formulars skrifttyper og grafiktilstande skal ligge i formularens egen /Resources dictionary, ikke i ressourcerne for den side, der tilfældigvis var aktiv på importtidspunktet. HotPDF skriver de Base 14-skrifttyper og de ExtGState-poster, en formular har brug for, ind i formularen selv, fordi en formular, der låner et navn fra sin første værtsside, gengives korrekt dér og giver et uløst ressourcenavn på enhver anden side, den senere placeres på

To spejlvendinger: én for geometri og én for glyffer

SVG's koordinatsystem vokser nedad fra øverste venstre hjørne; et PDF Form XObject bruger PDF-brugerrum, som vokser opad fra nederste venstre hjørne. Rodens viewBox-transformation udfører én spejlvending om Y-aksen, så geometrien lander korrekt. Tekst kræver derefter en anden, lokal spejlvending, fordi en glyf tegnet i en allerede spejlvendt ramme ville stå på hovedet

Dette er den samme korrektion, der optræder omvendt, når en PDF-side skrives ud som SVG, beskrevet i eksport af PDF-sider til SVG. At gøre det forkert er let at få øje på i grafik med tekst og næsten usynligt i grafik uden, hvilket er grunden til, at et logo med en tagline er en bedre smoketest end et logo alene

uses
  HPDFDoc;

var
  Pdf: THotPDF;
  SvgBytes: TBytes;
  FormName: AnsiString;
  Stats: THPDFSVGImportStatistics;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalogue.pdf';
    Pdf.BeginDoc;

    SvgBytes := LoadFileBytes('brand-logo.svg');
    FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
    if FormName = '' then
      raise Exception.Create('SVG rejected - see the import statistics');

    // Én import, mange placeringer: grafikbyttene findes kun én gang
    for I := 1 to 40 do
    begin
      if I > 1 then
        Pdf.AddPage;
      Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;
Diagram af de to spejlvendinger om Y-aksen mellem SVG- og PDF-koordinater: rodens viewBox-transformation korrigerer geometrien, og tekstglyffer kræver en anden, lokal spejlvending for at forblive opret
Rodens viewBox-transformation spejlvender Y-aksen én gang, så geometrien lander korrekt i PDF-rummet, og teksten får en anden, lokal spejlvending, ellers ankommer hver glyf på hovedet

Hvordan overlever hyperlinks turen?

De kan ikke overleve inde i formularen, fordi et Form XObject er indhold, og PDF-linkmål er annotationer, som hører til en side. Et <a>-element i kilde-SVG'en kan derfor ikke bare kopieres ind i formularens content stream

HotPDF løser dette ved at bevare linkrektangler som metadata i formularrum under parsing og materialisere dem som linkannotationer på sideniveau ved hver placering. DrawSVGFormXObject mapper disse rektangler gennem det målrektangel, du angiver, så en formular placeret to gange i forskellige størrelser får to korrekt dimensionerede sæt hit-områder. Linkrektangler mappes udelukkende gennem rodstørrelsen og kommer aldrig ind i content stream'en, hvilket holder den tegnede grafik identisk uanset hvor mange links den bærer. Angiv AddLinks = False, når du ønsker grafikken uden interaktivitet, for eksempel på en side kun til print

Hvad importeren afviser, og hvorfor det er pointen

SVG er en XML-dialekt med scripting, eksterne referencer og entity expansion, hvilket gør en vilkårlig SVG-fil til en fjendtlig input på samme måde som en vilkårlig HTML-fil er det. Importprofilen er fail-closed over for alt, der rækker uden for dokumentet: eksterne ressourcereferencer, scripts, event handler-attributter, DTD'er og enhver url(), der ikke er en lokal fragmentreference

Fail-closed betyder, at importen fejler i stedet for stiltiende at droppe den problematiske konstruktion. Den forskel betyder noget for en batch-pipeline, fordi et stiltiende drop giver et dokument, der mangler noget, som ingen kender til, mens en afvisning giver en fil, du kan sætte i karantæne og undersøge. Hvis du accepterer SVG-uploads fra brugere, er dette den adfærd, du ønsker, og den går hånd i hånd med den procesniveau-isolation, der er beskrevet i codec-procesisolation for utroværdige billeder

Diagram af en fail-closed SVG-importprofil, der accepterer lokale vektorfunktioner og afviser scripts, eksterne referencer og DTD'er ved at lade importen fejle i stedet for at droppe dem stiltiende
Importprofilen afviser scripts, eksterne referencer og DTD'er ved at fejle direkte, hvilket giver dig en fil, du kan sætte i karantæne, i stedet for et dokument, der stille mangler noget

Én intern grænse er værd at kende, hvis du også gengiver farveskrifttyper. Den strenge SVG-delmængde, der bruges til OpenType SVG-farveglyffer, kører gennem et separat indgangspunkt og bevarer sin oprindelige, snævrere fejladfærd. Generel grafikimport er en separat sti, så tilføjelse af understøttelse af buer, transparens og CSS til importeret grafik udvidede ikke, hvad en skrifttypeglyf må indeholde

Gradienter, masker og no-raster-reglen

Gradienter importeres som afgrænsede vektor-farvebånd, begrænset af den oprindelige geometris beskæringsvej, ikke som et rasteriseret billede. Masker bruger samme fremgangsmåde: maskens omrids bliver en vektor-beskæringsvej. Resultatet er, at en gradienttung illustration forbliver opløsningsuafhængig og forbliver lille, hvor en rasteriserende importer ville producere et stort billedobjekt, hvis kvalitet er fastlåst på importtidspunktet

Prisen er præcision i de ekstreme tilfælde. En båndinddelt tilnærmelse af en jævn gradient er ikke matematisk identisk med SVG-gradienten, og grafik med dusinvis af overlappende soft masks vil ligne originalen mere, hvis du i stedet gengiver den som et højopløsningsbillede. For de shading-modeller, PDF understøtter nativt, og hvornår man skal gribe direkte til dem, se gengivelse af aksiale og radiale shadings

// Læsning af importstatistikken viser dig, hvad der faktisk kom igennem
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
  [Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
   Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
  [Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));

// Placering af samme formular i to størrelser giver to korrekte linksæt
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Placering kun til print: grafik uden hit-områder
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

Hvor SVG-import passer ind blandt de andre vektorruter

Der findes tre vektor-importruter, og de er ikke indbyrdes udskiftelige. SVG-import er rigtig til grafik produceret af designværktøjer og til alt, der kommer fra en webpipeline. Metafil-import, som er dækket i EMF- og WMF-vektorimport, er rigtig til indhold genereret af Windows-applikationer og af diagramkomponenter, der tegner til en enhedskontekst. Direkte tegning via side-API'en er rigtig til grafik, din egen kode beregner, hvor der slet ikke findes et mellemformat

Genanvendelighedsargumentet taler specifikt for SVG-import ved gentagne elementer. Brevhoveder, logoer, vandmærker, certificeringsmærker og dekorative linjer tegnes alle identisk på mange sider, og hver af dem er ét formularobjekt plus én påkaldelse pr. side. Mål forskellen i filstørrelse på et rigtigt dokument, før du beslutter, at det ikke betyder noget: på et katalog på 500 sider med et vektorlogo pr. side er forskellen mellem én formular og 500 indlejrede kopier som regel adskillige megabyte

SVG-import, metafil-import og direkte vektortegning deler den samme content stream writer til Delphi og C++Builder; den komplette funktionsliste findes på HotPDF Delphi PDF component-siden