Copia e riutilizza pagine PDF tra documenti con JavaScript

2026-09-28 08:36:20 Allen Yang
AI Summarize:
ChatGPT
ChatGPT ✓
Claude ✓
Grok ✓
Perplexity ✓
Quick
Quick
Concise overview
Highlights
Key takeaways
Detailed
Structured explanation
Brief
One sentence summary
Summarize |

Copiare e riutilizzare pagine PDF tra documenti con JavaScript

Assemblare un PDF rifinito a partire da file sorgente sparsi è un'operazione di routine ma delicata: una copertina deve stare in testa a una sintesi di progetto, le pagine dei prezzi appartengono al loro contratto, un riepilogo trimestrale mette insieme i grafici di una dozzina di report. Farlo a mano significa destreggiarsi tra più lettori PDF e sperare che l'ordine delle pagine risulti corretto, con dimensioni di pagina non omogenee che complicano ulteriormente il problema.

Spire.PDF for JavaScript sposta l'intera operazione nel browser. Basato su WebAssembly, carica, modifica e salva documenti PDF interamente lato client tramite un file system virtuale (VFS), il che significa che nessun file viene mai caricato su un server backend. Questo articolo illustra quattro tecniche distinte per copiare pagine PDF tra documenti — tre che spostano intere pagine e una che estrae il contenuto di una pagina come modello riutilizzabile — con esempi di codice React completi per ciascuna.

Per la configurazione del progetto e le istruzioni di installazione, consulta Integrare Spire.PDF for JavaScript in un progetto React. Gli esempi seguenti presuppongono che Spire.PDF sia installato e che il modulo WebAssembly sia stato inizializzato.


Quattro modi per copiare pagine PDF in sintesi

Prima di esaminare singolarmente ogni metodo, la tabella seguente offre un rapido confronto. Le prime tre tecniche spostano pagine intatte e trasferiscono automaticamente dimensioni, rotazione e margini della pagina di origine. La quarta svincola il contenuto dalla geometria della pagina, offrendoti il pieno controllo sulle dimensioni della pagina di destinazione e sulla posizione di disegno.

Metodo Chiamata API Cosa viene copiato Dimensioni pagina Caso d'uso tipico
Inserire una singola pagina InsertPage Una pagina in una posizione a tua scelta Ereditata dall'origine Aggiungere una copertina o un frontespizio in testa
Inserire un intervallo di pagine InsertPageRange Un blocco consecutivo di pagine Ereditata dall'origine Aggiungere in fondo una sezione specifica, come tabelle dei prezzi
Aggiungere un intero documento AppendPage Tutte le pagine del documento di origine Ereditata dall'origine Concatenare documenti completi uno dopo l'altro
Disegnare il contenuto della pagina come modello CreateTemplate + DrawTemplate Solo il contenuto della pagina, disegnato su qualsiasi pagina Sei tu a decidere le dimensioni di destinazione Riutilizzare il contenuto su dimensioni di pagina diverse o ripeterlo più volte

I primi tre metodi sono semplici spostamenti di pagine: scegli l'origine, scegli la destinazione e la libreria fa il resto. L'approccio con modello è più avanzato e apre possibilità che la semplice copia di pagine non può offrire, come ridimensionare il contenuto per adattarlo a una pagina di formato diverso o apporre lo stesso contenuto su più pagine. Tratteremo prima i tre metodi di spostamento delle pagine, poi approfondiremo la tecnica del modello.


Copiare una singola pagina in una posizione specifica

Il più preciso dei quattro metodi, PdfDocument.InsertPage, copia una pagina da un documento di origine e la colloca in un indice esatto del documento di destinazione. Il parametro resultPageIndex controlla dove finisce la copia: passa 0 per anteporla, passa il numero di pagine corrente del documento di destinazione per aggiungerla in fondo, oppure fornisci un indice qualsiasi intermedio per inserirla in quella posizione. Se ometti del tutto resultPageIndex, la pagina viene inserita per impostazione predefinita in fondo.

function App() {
  const copyPageAtPosition = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Copy page 1 of the source document to the front of the target document
    // pageIndex comes from the source document, resultPageIndex is where the copy lands
    targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 0 });

    // Save the result document
    const outputFileName = 'CopyPageAtPosition.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page at Position</h1>
      <button onClick={copyPageAtPosition}>
        Start
      </button>
    </div>
  );
}

export default App;

Tra tutti e quattro i metodi di copia, resultPageIndex è l'unico parametro che consente di scegliere il punto di inserimento. Impostandolo a 0 la pagina va in prima posizione, a 1 in seconda, mentre passando il numero di pagine corrente del documento di destinazione si ottiene lo stesso effetto di un'aggiunta in fondo.

Il documento di destinazione passa da due a tre pagine, con la prima pagina del documento di origine che ora occupa la posizione iniziale:

La pagina 1 del documento di origine ora si trova davanti al documento di destinazione, che passa da 2 a 3 pagine


Copiare un intervallo di pagine in fondo

Quando servono più pagine ma meno di un intero documento, PdfDocument.InsertPageRange copia un blocco contiguo di pagine definito da un indice iniziale e uno finale. A differenza di InsertPage, questo metodo accetta argomenti posizionali anziché un oggetto di opzioni e aggiunge sempre le pagine copiate in fondo al documento di destinazione: non esiste un parametro per scegliere la posizione di inserimento. L'indice finale è incluso, quindi passando (sourceDoc, 1, 2) si copiano le pagine 2 e 3 (con indice a base zero).

function App() {
  const appendPageRange = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Append pages 2 to 3 of the source document to the end of the target document
    // Note: these are positional arguments, not an object; endIndex is inclusive
    targetDoc.InsertPageRange(sourceDoc, 1, 2);

    // Save the result document
    const outputFileName = 'CopyPageRange.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page Range</h1>
      <button onClick={appendPageRange}>
        Copy pages 2-3
      </button>
    </div>
  );
}

export default App;

Il documento di destinazione acquisisce due pagine aggiuntive, passando da due a quattro pagine in totale:

Dopo l'aggiunta delle pagine 2 e 3 del documento di origine, il documento ha 4 pagine


Aggiungere un intero documento

Nel caso più semplice — spostare tutte le pagine di un documento in un altro — PdfDocument.AppendPage elimina del tutto la necessità di calcolare indici. Passa l'oggetto del documento di origine e tutte le sue pagine vengono aggiunte in fondo al documento di destinazione nella loro sequenza originale. Per concatenare più documenti tra loro, chiama AppendPage ripetutamente con ciascun documento di origine, uno alla volta.

function App() {
  const appendWholeDocument = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load both the source and the target document into the VFS
    const sourceFileName = 'SourceDocument.pdf';
    const targetFileName = 'TargetDocument.pdf';
    await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the two documents
    const sourceDoc = new pdfModule.PdfDocument();
    sourceDoc.LoadFromFile(sourceFileName);
    const targetDoc = new pdfModule.PdfDocument();
    targetDoc.LoadFromFile(targetFileName);

    // Use AppendPage when the whole document has to be copied; all pages are appended in order
    targetDoc.AppendPage({ doc: sourceDoc });

    // Save the result document
    const outputFileName = 'CopyAllPages.pdf';
    targetDoc.SaveToFile(outputFileName);
    sourceDoc.Close();
    targetDoc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Whole Document</h1>
      <button onClick={appendWholeDocument}>
        Start
      </button>
    </div>
  );
}

export default App;

Tutte e quattro le pagine del documento di origine si uniscono a quelle di destinazione, portando il documento da due a sei pagine:

Dopo l'aggiunta di tutte e 4 le pagine del documento di origine, il documento ha 6 pagine


Copiare il contenuto di una pagina con un modello

I tre metodi precedenti trattano la pagina come un'unità indivisibile: viene spostata conservando dimensioni, rotazione e margini. Ma l'assemblaggio di documenti nel mondo reale richiede spesso un controllo più fine: collocare il contenuto di una pagina su una pagina di formato diverso, ridimensionarlo verso l'alto o verso il basso, oppure apporre lo stesso contenuto su più pagine. È qui che entra in gioco PdfPageBase.CreateTemplate.

CreateTemplate estrae il contenuto visivo di una pagina in un oggetto PdfTemplate. Puoi poi disegnare quel modello su qualsiasi pagina usando Canvas.DrawTemplate, specificando la posizione e le dimensioni dell'area di disegno. Il modello è svincolato dalla geometria della pagina originale, quindi puoi renderizzarlo a qualsiasi scala, in qualsiasi posizione, su qualsiasi formato di pagina — e puoi disegnare lo stesso modello tutte le volte che ti servono.

Questo rende i modelli particolarmente utili in scenari come:

  • Posizionare il contenuto di una copertina A5 centrato su una pagina A4 senza bordi bianchi
  • Creare una filigrana o un motivo di sfondo a partire da una pagina esistente
  • Duplicare il layout di un modulo su più pagine nuove a scale diverse
function App() {
  const copyPageWithTemplate = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF file to work on into the VFS
    const inputFileName = 'SourceDocument.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Take the page to be reused and turn it into a template: read the content once, draw it many times
    const sourcePage = doc.Pages.get_Item(0);
    const template = sourcePage.CreateTemplate();

    // First placement: insert an A4 page at position 2, a different size from the source,
    // and draw the content scaled to 297.6 x 421.6 at (80, 80)
    const page1 = doc.Pages.Insert(1, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
    page1.Canvas.DrawTemplate(template, new pdfModule.PointF(80.0, 80.0), new pdfModule.SizeF(297.6, 421.6));

    // Second placement: insert another A4 page, drawing the same template smaller in the lower right
    const page2 = doc.Pages.Insert(2, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
    page2.Canvas.DrawTemplate(template, new pdfModule.PointF(320.0, 460.0), new pdfModule.SizeF(200.0, 283.3));

    // Save the result document
    const outputFileName = 'CopyPageWithTemplate.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Copy Page with Template</h1>
      <button onClick={copyPageWithTemplate}>
        Start
      </button>
    </div>
  );
}

export default App;

Alcuni dettagli da notare su DrawTemplate:

  • Argomento delle dimensioni: quando il terzo argomento (dimensioni di destinazione) viene omesso, il modello viene renderizzato alle sue dimensioni originali, senza ridimensionamento. Su una pagina di destinazione più grande, il contenuto occupa solo una parte dello spazio disponibile.
  • Creazione della pagina: le dimensioni e i margini della pagina di destinazione provengono da Pages.Insert, non dal modello. Nell'esempio, margini a zero su tutti i lati fanno coincidere l'origine del disegno con l'angolo in alto a sinistra della pagina.
  • Disegni multipli: lo stesso oggetto template viene disegnato due volte su due pagine separate, in posizioni e scale diverse, dimostrando la possibilità di riutilizzo.

Il contenuto della pagina 1 ora compare su due pagine A4 appena inserite, a scale e posizioni diverse, portando il documento da quattro a sei pagine:

Il contenuto della pagina 1 del documento di origine viene ridimensionato su due nuove pagine A4, portando il documento da 4 a 6 pagine


FAQ

La creazione di una pagina con new PdfMargins(0.0) genera Arg_NullReferenceException

Causa: il costruttore PdfMargins interpreta un argomento numerico nudo come handle interno anziché come valore di margine. Chiamare new pdfModule.PdfMargins(0.0) produce quindi un oggetto che non rappresenta margini validi: accedere alla sua proprietà Left o Top genera Arg_NullReferenceException e passarlo alla creazione di una pagina produce risultati inattesi.

Soluzione: passa sempre i margini come oggetto di configurazione. Per margini zero uniformi usa { margin: 0.0 }; per valori distinti per ogni lato, specifica esplicitamente ciascun lato:

// Zero margins on all four sides
const margins = new pdfModule.PdfMargins({ margin: 0.0 });

// Or set each side separately
const custom = new pdfModule.PdfMargins({ left: 20.0, top: 20.0, right: 20.0, bottom: 20.0 });

Durante la copia delle pagine viene generato un errore di indice fuori intervallo o di intervallo invertito

Causa: gli indici delle pagine partono da zero e endIndex in InsertPageRange è incluso. L'intervallo valido va quindi da 0 a Pages.Count - 1. Fornire un indice fuori da questo intervallo genera Index out of range, mentre impostare startIndex maggiore di endIndex genera The start index is greater then the end index.

Soluzione: limita il limite superiore verificandolo rispetto a Pages.Count prima di chiamare il metodo:

// To copy pages 2 to 4: start = 1, end = 3, with the page count as the upper bound
const start = 1;
const end = Math.min(3, sourceDoc.Pages.Count - 1);
targetDoc.InsertPageRange(sourceDoc, start, end);

Una pagina ruotata risulta con orientamento errato dopo la copia

Causa: CreateTemplate() cattura il contenuto disegnato della pagina ma non il suo angolo di rotazione (la voce /Rotate). Quando la pagina di origine presenta una rotazione, il sistema di coordinate del modello non è allineato con la pagina di destinazione: disegnandolo direttamente il contenuto finisce fuori dall'area visibile e la copia risultante ha una Rotation pari a 0.

Soluzione: per le pagine di origine ruotate, preferisci una copia dell'intera pagina, in modo che l'angolo di rotazione accompagni il contenuto:

// Whole-page copy: the rotation angle comes with the page
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 1 });

Se l'approccio con modello è inevitabile, azzera temporaneamente la rotazione della pagina di origine prima di estrarre il modello, poi ripristina l'angolo originale sia sulla pagina di origine sia sulla nuova pagina:

const rotation = sourcePage.Rotation.value;

// Zero it temporarily so the template exports at the page's real coordinates
sourcePage.Rotation = 0;
const newPage = doc.Pages.Insert(1, sourcePage.Size, new pdfModule.PdfMargins({ margin: 0.0 }));
newPage.Canvas.DrawTemplate(sourcePage.CreateTemplate(), new pdfModule.PointF(0.0, 0.0));

// Restore the source page and give the copy the same angle
sourcePage.Rotation = rotation;
newPage.Rotation = rotation;

Per rimuovere la filigrana di valutazione dai documenti di output o per sbloccare l'accesso completo alle funzionalità, contatta il reparto vendite per ottenere una licenza temporanea di 30 giorni.


Vedi anche