Technisch artikel

SVG importeren in PDF als herbruikbare Form XObjects

HotPDF importeert een SVG-bestand via ImportSVGFormXObject als herbruikbaar Form XObject in een geladen of nieuw aangemaakt PDF-bestand; de methode retourneert de resourcenaam van het aangemaakte formulier. Het plaatsen gebeurt via een aparte aanroep, DrawSVGFormXObject, zodat één import op veel pagina's in verschillende groottes kan worden toegepast zonder ook maar één byte van de afbeelding te dupliceren

Dat onderscheid is belangrijker dan het klinkt. Een logo dat op 300 DPI is gerasterd en op 400 pagina's wordt geplaatst, levert 400 kopieën van een bitmap op. Hetzelfde logo als Form XObject is één object dat 400 keer wordt aangehaald, blijft scherp bij elke zoomfactor en wordt afgedrukt op apparaatresolutie in plaats van op de DPI die op het moment van bouwen voldoende leek

Diagram van één SVG-import die één PDF Form XObject creëert, dat DrawSVGFormXObject op veel pagina's in verschillende groottes plaatst, vergeleken met honderden rasterbitmap-kopieën
Eén import schrijft de afbeelding één keer weg als Form XObject met eigen resources, en elke plaatsing is een kleine aanroep die geen dubbele bytes toevoegt

Waarom een Form XObject in plaats van de SVG telkens opnieuw te tekenen?

Een Form XObject is PDF's eigen mechanisme voor herbruikbare inhoud: een op zichzelf staande content stream met een eigen bounding box en resource dictionary, aangeroepen via een naam. Het importeren van SVG hierin levert drie eigenschappen op die direct tekenen niet biedt. De afbeelding bestaat maar één keer in het bestand. Elke plaatsing is een kleine matrix en één aanroep. En de vormen blijven vectoren, zodat een viewer bij 800 procent zoom curven tekent, geen pixels

De subtiliteit zit in de resources. De lettertypen en grafische toestanden van een formulier moeten in het eigen /Resources-dictionary van het formulier staan, niet in de resources van de pagina die toevallig actief was op het moment van import. HotPDF schrijft de Base 14-lettertypen en de ExtGState-vermeldingen die een formulier nodig heeft rechtstreeks in het formulier zelf, want een formulier dat een naam ontleent aan zijn eerste hostpagina rendert daar correct maar produceert op elke andere pagina waarop het later wordt geplaatst een onoplosbare resourcenaam

Twee spiegelingen, één voor de geometrie en één voor de glyphs

Het coördinatensysteem van SVG groeit van de linkerbovenhoek naar beneden; een PDF Form XObject gebruikt de PDF-gebruikersruimte, die vanaf linksonder naar boven groeit. De root-viewBox-transformatie voert één spiegeling langs de Y-as uit zodat de geometrie correct terechtkomt. Tekst heeft daarna een tweede, lokale spiegeling nodig, want een glyph die in een al gespiegeld kader wordt getekend, zou anders ondersteboven staan

Diagram van de twee spiegelingen langs de Y-as tussen SVG- en PDF-coördinaten: de root-viewBox-transformatie corrigeert de geometrie en tekstglyphs hebben een tweede, lokale spiegeling nodig om rechtop te blijven staan
De root-viewBox-transformatie spiegelt de Y-as één keer zodat de geometrie in de PDF-ruimte terechtkomt, en tekst krijgt een tweede, lokale spiegeling, anders komt elke glyph ondersteboven aan

Dit is dezelfde correctie die in omgekeerde richting optreedt wanneer een PDF-pagina als SVG wordt weggeschreven, beschreven in het exporteren van PDF-pagina's naar SVG. Deze fout maken is gemakkelijk te zien in afbeeldingen met tekst en bijna onzichtbaar in afbeeldingen zonder tekst, wat de reden is waarom een logo met slogan een betere smoke test is dan een logo alleen

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');

    // Eén import, veel plaatsingen: de afbeeldingsbytes bestaan maar één keer
    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;

Hoe overleven hyperlinks de overtocht?

Ze kunnen niet overleven binnen het formulier, want een Form XObject is inhoud en PDF-linkdoelen zijn annotaties, die bij een pagina horen. Een <a>-element in de bron-SVG kan daarom niet zomaar naar de content stream van het formulier worden gekopieerd

HotPDF lost dit op door linkrechthoeken tijdens het parsen als formulierruimte-metadata te bewaren, en ze bij elke plaatsing te materialiseren als linkannotaties op paginaniveau. DrawSVGFormXObject koppelt die rechthoeken via de doelrechthoek die u meegeeft, zodat een formulier dat twee keer op verschillende groottes wordt geplaatst twee correct geschaalde sets klikgebieden krijgt. Linkrechthoeken worden alleen via de rootgrootte gekoppeld en komen nooit in de content stream terecht, wat ervoor zorgt dat de getekende afbeelding identiek blijft ongeacht hoeveel links ze bevat. Geef AddLinks = False mee wanneer u de afbeelding zonder interactiviteit wilt, bijvoorbeeld op een pagina die alleen voor afdrukken bedoeld is

Wat de importeerder weigert, en waarom dat precies de bedoeling is

SVG is een XML-dialect met scripting, externe verwijzingen en entity-expansie, wat een willekeurig SVG-bestand tot een vijandige input maakt op dezelfde manier als een willekeurig HTML-bestand dat is. Het importprofiel is fail-closed op alles wat buiten het document reikt: externe resourceverwijzingen, scripts, event handler-attributen, DTD's en elke url() die geen lokale fragmentverwijzing is

Fail-closed betekent dat de import mislukt in plaats van de aanstootgevende constructie stilzwijgend te laten vallen. Dat onderscheid is belangrijk voor een batchpijplijn, want een stille drop levert een document op waarin iets ontbreekt waar niemand van weet, terwijl een weigering een bestand oplevert dat u kunt quarantainen en inspecteren. Als u SVG-uploads van gebruikers accepteert, is dit het gedrag dat u wilt, en het gaat samen met de procesisolatie beschreven in procesisolatie voor codecs bij niet-vertrouwde afbeeldingen

Diagram van een fail-closed SVG-importprofiel dat lokale vectorfuncties accepteert en scripts, externe verwijzingen en DTD's weigert door de import te laten mislukken in plaats van ze stilzwijgend te laten vallen
Het importprofiel weigert scripts, externe verwijzingen en DTD's door regelrecht te mislukken, wat u een bestand oplevert dat u kunt quarantainen in plaats van een document dat stilzwijgend onvolledig is

Eén interne grens is het waard om te kennen als u ook kleurenlettertypen rendert. De strikte SVG-subset die wordt gebruikt voor OpenType SVG-kleurglyphs loopt via een apart toegangspunt en behoudt zijn oorspronkelijke, striktere foutgedrag. Algemene import van afbeeldingen is een apart traject, dus het toevoegen van boogondersteuning, transparantie en CSS-ondersteuning aan geïmporteerde afbeeldingen heeft niet verruimd wat een lettertype-glyph mag bevatten

Verlopen, maskers en de regel zonder rasterisatie

Verlopen worden geïmporteerd als begrensde vectorkleurbanden, beperkt door het clippingpad van de oorspronkelijke geometrie, niet als een gerasterde afbeelding. Maskers gebruiken dezelfde aanpak: de maskercontour wordt een vector-clippingpad. Het resultaat is dat een illustratie met veel verlopen resolutie-onafhankelijk blijft en klein blijft, terwijl een rasteriserende importeerder een groot afbeeldingsobject zou produceren waarvan de kwaliteit vastligt op het moment van import

De prijs die daarvoor betaald wordt is nauwkeurigheid in extreme gevallen. Een gebande benadering van een vloeiend verloop is niet wiskundig identiek aan het SVG-verloop, en afbeeldingen met tientallen overlappende zachte maskers zullen dichter bij het origineel liggen als u ze naar een afbeelding met hoge resolutie rendert. Voor de shadingmodellen die PDF native ondersteunt, en wanneer u die rechtstreeks moet gebruiken, zie het renderen van axiale en radiale shadings

// Het lezen van de importstatistieken vertelt u wat er daadwerkelijk is doorgekomen
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]));

// Hetzelfde formulier op twee groottes plaatsen levert twee correcte linksets op
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Plaatsing alleen voor afdrukken: afbeelding zonder klikgebieden
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

Waar past SVG-import naast de andere vectorroutes?

Er bestaan drie vectorimportroutes en ze zijn niet onderling verwisselbaar. SVG-import is geschikt voor afbeeldingen die door ontwerptools zijn geproduceerd en voor alles wat uit een webpijplijn komt. Metafile-import, behandeld in EMF- en WMF-vectorimport, is geschikt voor inhoud die door Windows-toepassingen en door grafiekcomponenten die naar een apparaatcontext tekenen wordt gegenereerd. Direct tekenen via de pagina-API is geschikt voor afbeeldingen die uw eigen code berekent, waar helemaal geen tussenformaat bestaat

Het herbruikbaarheidsargument is specifiek in het voordeel van SVG-import bij herhaalde elementen. Briefhoofden, logo's, watermerken, certificeringsmerken en decoratieve lijnen worden allemaal identiek op veel pagina's getekend, en elk daarvan is één formulierobject plus één aanroep per pagina. Meet het bestandsgrootteverschil op een echt document voordat u besluit dat het er niet toe doet: op een catalogus van 500 pagina's met een vectorlogo per pagina is het verschil tussen één formulier en 500 inline kopieën meestal enkele megabytes

SVG-import, metafile-import en direct vector tekenen delen dezelfde content stream-writer voor Delphi en C++Builder; de volledige functielijst staat op de HotPDF Delphi PDF component-pagina