Articolo tecnico

Importare SVG in PDF come Form XObject riutilizzabili

HotPDF importa un file SVG in un PDF caricato o creato come Form XObject riutilizzabile tramite ImportSVGFormXObject, che restituisce il nome della risorsa del form creato. Il posizionamento è una chiamata separata, DrawSVGFormXObject, quindi una singola importazione può essere applicata su molte pagine a dimensioni diverse senza duplicare un solo byte della grafica

Questa separazione conta più di quanto sembri. Un logo rasterizzato a 300 DPI e inserito in 400 pagine equivale a 400 copie di un bitmap. Lo stesso logo come Form XObject è un unico oggetto referenziato 400 volte, resta nitido a qualsiasi zoom e viene stampato alla risoluzione del dispositivo anziché al DPI che sembrava sufficiente in fase di creazione

Diagramma di una singola importazione SVG che crea un unico Form XObject PDF che DrawSVGFormXObject posiziona su molte pagine a dimensioni diverse, a confronto con centinaia di copie bitmap rasterizzate
Un'importazione scrive la grafica una sola volta come Form XObject con proprie risorse, e ogni posizionamento è una piccola invocazione che non aggiunge byte duplicati

Perché un Form XObject invece di disegnare l'SVG ogni volta?

Un Form XObject è il meccanismo nativo del PDF per i contenuti riutilizzabili: un content stream autonomo con un proprio bounding box e un proprio dizionario delle risorse, invocato per nome. Importare un SVG al suo interno offre tre proprietà che il disegno diretto non garantisce. La grafica esiste una sola volta nel file. Ogni posizionamento è una piccola matrice e un'invocazione. E le forme restano vettoriali, quindi un visualizzatore a 800 percento di zoom disegna curve, non pixel

La sottigliezza sta nelle risorse. I font e gli stati grafici di un form devono risiedere nel dizionario /Resources proprio del form, non nelle risorse della pagina che risultava corrente al momento dell'importazione. HotPDF scrive i font Base 14 e le voci ExtGState di cui un form ha bisogno direttamente nel form stesso, perché un form che prende in prestito un nome dalla sua prima pagina ospite viene renderizzato correttamente lì, ma produce un nome di risorsa non risolto su ogni altra pagina su cui viene poi posizionato

Due ribaltamenti, uno per la geometria e uno per i glifi

Il sistema di coordinate SVG cresce verso il basso a partire dall'angolo in alto a sinistra; un Form XObject PDF usa lo spazio utente PDF, che cresce verso l'alto a partire dal basso a sinistra. La trasformazione del viewBox radice esegue un ribaltamento sull'asse Y affinché la geometria risulti corretta. Il testo richiede poi un secondo ribaltamento locale, perché un glifo disegnato in un frame già ribaltato risulterebbe capovolto

È la stessa correzione che compare in senso inverso quando una pagina PDF viene esportata come SVG, descritta in esportare pagine PDF in SVG. Un errore in questo punto è facile da notare in una grafica con testo e quasi invisibile in una senza, motivo per cui un logo con uno slogan è uno smoke test migliore di un logo da solo

Diagramma dei due ribaltamenti sull'asse Y tra le coordinate SVG e PDF: la trasformazione del viewBox radice corregge la geometria e i glifi del testo richiedono un secondo ribaltamento locale per restare dritti
La trasformazione del viewBox radice ribalta l'asse Y una sola volta in modo che la geometria arrivi nello spazio PDF corretto, mentre il testo richiede un secondo ribaltamento locale, altrimenti ogni glifo arriva capovolto
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');

    // Una sola importazione, molti posizionamenti: i byte della grafica esistono una sola volta
    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;

Come sopravvivono i collegamenti ipertestuali al passaggio?

Non possono sopravvivere all'interno del form, perché un Form XObject è contenuto, mentre le destinazioni dei link PDF sono annotazioni, che appartengono a una pagina. Un elemento <a> nell'SVG di origine non può quindi essere semplicemente copiato nel content stream del form

HotPDF risolve il problema mantenendo i rettangoli dei link come metadati nello spazio del form durante il parsing, e materializzandoli come annotazioni di link a livello di pagina a ogni posizionamento. DrawSVGFormXObject mappa quei rettangoli attraverso il rettangolo di destinazione che passate, quindi un form posizionato due volte a dimensioni diverse ottiene due set di aree cliccabili correttamente dimensionati. I rettangoli dei link vengono mappati solo tramite la dimensione radice e non entrano mai nel content stream, il che mantiene identica la grafica disegnata indipendentemente da quanti link porti. Passate AddLinks = False quando volete la grafica senza interattività, ad esempio su una pagina destinata solo alla stampa

Cosa rifiuta l'importatore, e perché è proprio questo il punto

SVG è un dialetto XML con supporto per script, riferimenti esterni ed espansione di entità, il che rende un file SVG arbitrario un input ostile allo stesso modo di un file HTML arbitrario. Il profilo di importazione adotta un comportamento fail-closed su tutto ciò che punta fuori dal documento: riferimenti a risorse esterne, script, attributi di gestori di eventi, DTD e qualsiasi url() che non sia un riferimento a frammento locale

Fail-closed significa che l'importazione fallisce anziché scartare silenziosamente il costrutto incriminato. Questa distinzione è importante per una pipeline batch, perché uno scarto silenzioso produce un documento a cui manca qualcosa che nessuno sa di dover cercare, mentre un rifiuto produce un file che potete mettere in quarantena e ispezionare. Se accettate upload SVG dagli utenti, questo è il comportamento che volete, e si abbina all'isolamento a livello di processo descritto in isolamento dei processi codec per immagini non attendibili

Diagramma di un profilo di importazione SVG fail-closed che accetta funzionalità vettoriali locali e rifiuta script, riferimenti esterni e DTD facendo fallire l'importazione anziché scartarli silenziosamente
Il profilo di importazione rifiuta script, riferimenti esterni e DTD facendo fallire l'operazione, il che vi offre un file da mettere in quarantena anziché un documento silenziosamente incompleto

Vale la pena conoscere un confine interno se renderizzate anche font a colori. Il sottoinsieme SVG rigoroso usato per i glifi a colori OpenType SVG passa attraverso un punto di ingresso separato e mantiene il suo comportamento di fallimento originale, più restrittivo. L'importazione di grafica generica è un percorso distinto, quindi l'aggiunta del supporto per archi, trasparenza e CSS alla grafica importata non ha ampliato ciò che un glifo di font può contenere

Gradienti, maschere e la regola del no-raster

I gradienti vengono importati come bande di colore vettoriali delimitate, vincolate dal percorso di ritaglio della geometria originale, non come immagine rasterizzata. Le maschere usano lo stesso approccio: il contorno della maschera diventa un percorso di ritaglio vettoriale. Il risultato è che un'illustrazione ricca di gradienti resta indipendente dalla risoluzione e resta di piccole dimensioni, mentre un importatore che rasterizza produrrebbe un grande oggetto immagine la cui qualità è fissata al momento dell'importazione

Il compromesso riguarda la fedeltà nei casi estremi. Un'approssimazione a bande di un gradiente uniforme non è matematicamente identica al gradiente SVG, e una grafica con decine di maschere morbide sovrapposte apparirà più vicina all'originale se invece la renderizzate come immagine ad alta risoluzione. Per i modelli di shading supportati nativamente dal PDF, e su quando ricorrervi direttamente, vedere il rendering di shading assiali e radiali

// Leggere le statistiche di importazione indica cosa è effettivamente passato
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]));

// Posizionare lo stesso form a due dimensioni produce due set di link corretti
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// Posizionamento solo stampa: grafica senza aree cliccabili
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

Come si colloca l'importazione SVG accanto alle altre vie vettoriali

Esistono tre vie di importazione vettoriale e non sono intercambiabili. L'importazione SVG è adatta per grafica prodotta da strumenti di design e per qualsiasi cosa provenga da una pipeline web. L'importazione di metafile, trattata in importazione vettoriale EMF e WMF, è adatta per contenuti generati da applicazioni Windows e da componenti di grafici che disegnano su un device context. Il disegno diretto tramite l'API di pagina è adatto per grafica calcolata dal vostro stesso codice, dove non esiste alcun formato intermedio

L'argomento della riutilizzabilità favorisce l'importazione SVG in particolare per gli elementi ripetuti. Carte intestate, loghi, filigrane, marchi di certificazione e linee decorative vengono tutti disegnati in modo identico su molte pagine, e ciascuno di essi è un solo oggetto form più un'invocazione per pagina. Misurate la differenza di dimensione del file su un documento reale prima di decidere che non conta: su un catalogo di 500 pagine con un logo vettoriale per pagina, il divario tra un form unico e 500 copie inline è di solito di diversi megabyte

L'importazione SVG, l'importazione di metafile e il disegno vettoriale diretto condividono lo stesso writer di content stream per Delphi e C++Builder; l'elenco completo delle funzionalità è disponibile sulla pagina del componente PDF HotPDF Delphi