Convertire immagini in PDF in JavaScript (React)

2026-09-10 05:33:37 Allen Yang
AI Summarize:
ChatGPT
ChatGPT ✓
Claude ✓
Grok ✓
Perplexity ✓
Quick
Quick
Concise overview
Highlights
Key takeaways
Detailed
Structured explanation
Brief
One sentence summary
Summarize |

Multiple images assembled into a single multi-page PDF using Spire.PDF for JavaScript in React

Una cartella di immagini è scomoda da consegnare a qualcuno. Un PDF è un unico file, si apre ovunque, si stampa in modo prevedibile e — la parte che interessa davvero alle persone — mantiene un ordine fisso. Ecco perché pagine scansionate, set di foto, foto di ricevute e fotogrammi di progettazione esportati vengono così spesso assemblati in un PDF prima di essere inviati da qualche parte.

Costruire quel PDF nel browser è un problema diverso dal rendering di un PDF in un'immagine. Non stai decodificando qualcosa che esiste già; stai prendendo decisioni che un formato di documento normalmente prenderebbe per te: quanto è grande la pagina, dove si trova l'immagine su di essa, cosa succede quando un'immagine ha una forma diversa dalla pagina e in quale ordine vengono generate le pagine.

Spire.PDF for JavaScript espone queste decisioni tramite un canvas di pagina. Aggiungi una pagina, carica un'immagine, disegnala su quella pagina a una dimensione che calcoli e salva. Tutto viene eseguito lato client tramite WebAssembly, quindi le immagini non vengono mai caricate.


Perché le immagini finiscono nei PDF

Gli scenari condividono un unico schema: diverse immagini che devono comportarsi come un unico documento.

  • Documenti multipagina scansionati o fotografati — un contratto fotografato pagina per pagina, riassemblato in un unico file che può essere archiviato o inviato via email.
  • Set di foto e portfolio — un'immagine per pagina, in un ordine scelto da qualcuno.
  • Ricevute e note spese — una dozzina di foto scattate con il telefono che la contabilità vuole come un unico allegato.
  • Esportazioni di progetti e diagrammi — fotogrammi esportati da uno strumento, raccolti in qualcosa di revisionabile.

In ogni caso il PDF non riguarda davvero il formato PDF. Si tratta di ottenere un artefatto stabile, in un unico file e ordinato da un mucchio di immagini.


Prerequisiti

Questa guida presuppone un progetto React con Spire.PDF for JavaScript installato e il modulo WASM inizializzato. Per la configurazione, vedere Integrazione di Spire.PDF for JavaScript in un progetto React.

Ti serviranno:

  • Uno o più file immagine caricati nel VFS
  • Il modulo WASM raggiungibile all'indirizzo window.wasmModule.spirepdf

Un'immagine, una pagina

Il flusso di base prevede quattro passaggi: creare un documento, aggiungere una pagina, caricare l'immagine, disegnarla. La parte interessante è il disegno — devi decidere quanto grande debba essere l'immagine sulla pagina.

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

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

    // Load the image file into VFS
    const inputFileName = 'Scenery.png';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object
    let doc = new pdfModule.PdfDocument();

    // Add a page
    let page = doc.Pages.Add();

    // Load the image
    let image = pdfModule.PdfImage.FromFile(inputFileName);

    // Calculate the scale ratio so the image fits the page completely
    let widthFitRate = image.PhysicalDimension.Width / page.Canvas.ClientSize.Width;
    let heightFitRate = image.PhysicalDimension.Height / page.Canvas.ClientSize.Height;
    let fitRate = Math.max(widthFitRate, heightFitRate);

    // Calculate the scaled dimensions of the image
    let fitWidth = image.PhysicalDimension.Width / fitRate;
    let fitHeight = image.PhysicalDimension.Height / fitRate;

    // Center the image on the page
    let x = (page.Canvas.ClientSize.Width - fitWidth) / 2;
    let y = (page.Canvas.ClientSize.Height - fitHeight) / 2;

    // Draw the image onto the page
    page.Canvas.DrawImage({ image: image, x: x, y: y, width: fitWidth, height: fitHeight });

    const outputFileName = 'ImageToPDF.pdf';

    // Save as PDF format
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
    doc.Close();

    // Read the converted file from VFS and trigger 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>Convert Image To PDF</h1>
      <button onClick={convertImageToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

Documento PDF generato dopo aver caricato un'immagine tramite PdfImage.FromFile e averla disegnata con Canvas.DrawImage

PDF document generated after loading an image via PdfImage.FromFile and drawing it with Canvas.DrawImage

Estrai il dimensionamento in una funzione di supporto, perché è l'unico pezzo di questo codice che riutilizzerai in ogni altra ricetta di seguito. La matematica: prendi page.Canvas.ClientSize — l'area disegnabile della pagina, in punti — come budget, confrontala con la PhysicalDimension naturale dell'immagine e dividi entrambe le dimensioni per un unico rapporto, così le proporzioni si mantengono. Inseriscila in una funzione, in modo che la parte soggetta a errori viva in un solo posto:

// Contain: scale until the whole image fits inside the page
function fitContain(imgW, imgH, pageW, pageH) {
  const rate = Math.max(imgW / pageW, imgH / pageH);
  const width = imgW / rate;
  const height = imgH / rate;
  return { width, height, x: (pageW - width) / 2, y: (pageH - height) / 2 };
}

Ora la chiamata di disegno nell'esempio sopra si riduce a tre righe, e la decisione "contain o cover" esce dalla matematica ed entra nel nome di una funzione:

let page = doc.Pages.Add();
let box = fitContain(
  image.PhysicalDimension.Width, image.PhysicalDimension.Height,
  page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height
);
page.Canvas.DrawImage({ image: image, x: box.x, y: box.y, width: box.width, height: box.height });

Math.max è la scelta "contain" — scala sull'asse più restrittivo in modo che l'intera immagine rimanga visibile. Se invece vuoi riempire la pagina e ritagliare l'eccesso, sostituisci con Math.min; la sezione sul dimensionamento fornisce la controparte fitCover e una variante con margine.


Molte immagini, un documento

Un'immagine per pagina significa una chiamata a Pages.Add() e una a DrawImage per ogni immagine. Itera su un array di nomi di file e l'ordine dell'array diventa l'ordine delle pagine — esattamente ciò che vuoi quando l'utente ha appena finito di trascinare le miniature in sequenza.

const combineImagesToPdf = async () => {
  const pdfModule = window.wasmModule?.spirepdf;
  if (!pdfModule) return;

  // The order of this array is the order of pages in the PDF
  const imageFiles = ['scan_01.png', 'scan_02.png', 'scan_03.png', 'scan_04.png'];

  for (const fileName of imageFiles) {
    await window.spire.FetchFileToVFS(fileName, "", `${process.env.PUBLIC_URL}/data/`);
  }

  let doc = new pdfModule.PdfDocument();

  for (const fileName of imageFiles) {
    let page = doc.Pages.Add();
    let image = pdfModule.PdfImage.FromFile(fileName);

    let box = fitContain(
      image.PhysicalDimension.Width, image.PhysicalDimension.Height,
      page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height
    );

    page.Canvas.DrawImage({ image: image, x: box.x, y: box.y, width: box.width, height: box.height });
  }

  const outputFileName = 'ScannedDocument.pdf';
  doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
  doc.Close();

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

Due note pratiche. Poiché ogni pagina viene dimensionata in modo indipendente, immagini di dimensioni diverse vanno bene — una foto orizzontale e una scansione verticale possono stare nello stesso documento senza alcuna gestione speciale. E poiché l'intero documento viene costruito in memoria prima di SaveToFile, il download avviene una sola volta alla fine, indipendentemente da quante immagini sono state inserite.

Potrebbe interessarti anche: assemblare immagini è l'operazione inversa del rendering. Se hai già un PDF e vuoi invece ogni sua pagina come immagine, vedi Come convertire le pagine PDF in immagini in JavaScript (React).


Dimensionamento: adattare l'immagine alla pagina

Ci sono due modi ragionevoli per inserire un'immagine in una pagina, e quale scegliere dipende dal fatto che perdere parte dell'immagine sia accettabile.

Contain (Math.max) Cover (Math.min)
Cosa fa Ridimensiona finché l'intera immagine non entra Ridimensiona finché la pagina non è riempita
Immagine intera visibile Sì No — l'eccesso viene ritagliato
Spazio vuoto Possibile, su un asse Nessuno
Adatto per Scansioni, documenti, qualsiasi cosa che debba rimanere completa Foto a tutta pagina, copertine, diapositive

Il primo esempio usa contain — la funzione di supporto fitContain. Passare a cover è lo specchio di quella funzione: Math.min invece di Math.max, riempiendo la pagina e lasciando che il canvas ritagli ciò che eccede, con offset di centratura che diventano negativi:

// Cover: fill the page, clipping whatever overflows
function fitCover(imgW, imgH, pageW, pageH) {
  const rate = Math.min(imgW / pageW, imgH / pageH);
  const width = imgW / rate;
  const height = imgH / rate;
  return {
    width, height,
    x: (pageW - width) / 2,   // negative when the image is wider than the page
    y: (pageH - height) / 2   // negative when it is taller
  };
}

Se desideri un margine visibile invece di un output a filo pagina, riduci l'area utilizzabile anziché l'immagine — passa il budget corretto per il margine alla stessa funzione di supporto fitContain:

const margin = 36; // 36 points = 0.5 inch
let usableWidth = page.Canvas.ClientSize.Width - margin * 2;
let usableHeight = page.Canvas.ClientSize.Height - margin * 2;

let box = fitContain(
  image.PhysicalDimension.Width, image.PhysicalDimension.Height,
  usableWidth, usableHeight
);
page.Canvas.DrawImage({ image: image, x: box.x, y: box.y, width: box.width, height: box.height });

Una cosa da sapere su PhysicalDimension: riflette la dimensione fisica dell'immagine, che non sempre corrisponde alla sua dimensione in pixel. Una foto 4000 × 3000 salvata con un tag DPI diverso riporterà numeri diversi da quelli che potresti aspettarti. Ecco perché l'approccio basato sul rapporto descritto sopra è più sicuro che codificare a mano le dimensioni in pixel — funziona indipendentemente da come è stata taggata l'immagine.


Caricare immagini dalla memoria

PdfImage.FromFile si aspetta che l'immagine sia già nel VFS. Non è sempre lì che risiedono le tue immagini — una risposta API, un blob di database o un'esportazione canvas ti forniscono invece byte in memoria. PdfImage.FromStream prende direttamente quei byte.

// Read image bytes from VFS and build a memory stream
let bytes = window.dotnetRuntime.Module.FS.readFile(inputFileName);
let stream = new pdfModule.Stream(bytes);

// Load the image from the memory stream
let image = pdfModule.PdfImage.FromStream(stream);

Da lì in poi è lo stesso di qualsiasi altra immagine — calcola la dimensione e disegnala:

let page = doc.Pages.Add();
let box = fitContain(
  image.PhysicalDimension.Width, image.PhysicalDimension.Height,
  page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height
);
page.Canvas.DrawImage({ image: image, x: box.x, y: box.y, width: box.width, height: box.height });

La stessa forma di bytes funziona indipendentemente da dove proviene. Se le tue immagini arrivano come ArrayBuffer da fetch, avvolgilo in un Uint8Array prima di costruire lo stream:

const response = await fetch('/api/images/invoice-001');
const bytes = new Uint8Array(await response.arrayBuffer());
let stream = new pdfModule.Stream(bytes);
let image = pdfModule.PdfImage.FromStream(stream);

Questo è il pattern da usare quando il PDF viene assemblato da immagini attivate dal server, da caricamenti utente mantenuti nello stato o da qualsiasi cosa generata a runtime da un canvas — non è richiesto alcun passaggio attraverso il VFS.

Articolo consigliato: Spire.PDF può anche disegnare sulle pagine di un PDF che hai già aperto, non solo sui nuovi documenti di cui sopra. Per inserire immagini in un documento esistente, vedi Come aggiungere immagini a un PDF in JavaScript (React).


Problemi comuni

L'immagine risulta allungata o schiacciata. Quasi sempre dipende da due fattori di scala diversi. Calcola un unico fitRate e dividi sia la larghezza sia l'altezza per esso — non scalare mai gli assi in modo indipendente.

L'immagine è minuscola al centro di una grande pagina vuota. È previsto, quando le proporzioni dell'immagine sono molto diverse da quelle della pagina. Una foto panoramica su una pagina verticale lascerà sempre bande sopra e sotto. O lo accetti (corretto per i documenti), o passi a cover, oppure usi la versione con margine regolato per mantenere almeno simmetrico lo spazio bianco.

L'immagine viene tagliata ai bordi. Stai usando il comportamento cover, intenzionalmente o no. Controlla se fitRate ha usato Math.min; passa a Math.max se l'intera immagine deve essere visibile.

Una foto ad alta risoluzione produce un PDF enorme. L'immagine viene incorporata alla sua risoluzione. Se la dimensione del file è importante, ridimensionala prima di disegnarla — disegnala su un canvas alla dimensione target, esporta e usa quei byte con PdfImage.FromStream.

Non succede nulla al primo clic. Il modulo WASM si carica in modo asincrono. Il controllo if (!pdfModule) return; esiste per questo motivo; in un'app reale, abilita il pulsante solo quando il modulo è pronto invece di mostrare un avviso.


Domande frequenti

Posso inserire immagini in un PDF esistente invece di crearne uno nuovo?

Sì. Gli esempi qui creano un nuovo documento, ma puoi aprire un PDF esistente e disegnare sulle sue pagine allo stesso modo. Vedi Come aggiungere immagini a un PDF in JavaScript (React) per quel flusso di lavoro.

Quali formati di immagine posso caricare?

I formati bitmap comuni — PNG, JPEG, BMP e simili — sono supportati da PdfImage.FromFile e PdfImage.FromStream. Usa FromStream quando il formato è sconosciuto in fase di build o i byte provengono da una risposta di rete.

Posso controllare l'ordine delle pagine?

Sì. Le pagine vengono create nell'ordine in cui chiami Pages.Add(), quindi ordinare l'array dei nomi di file ordina l'output. Questo è il meccanismo alla base delle interfacce con trascinamento per riordinare: riordina l'array, ricostruisci il PDF.

Questo richiede un backend?

No. Il documento viene assemblato nel browser dal modulo WebAssembly e il PDF finito viene restituito come byte che trasformi in un Blob. Le immagini non lasciano mai il dispositivo.

Posso mescolare immagini verticali e orizzontali in un unico PDF?

Sì. Ogni pagina viene dimensionata e disegnata in modo indipendente, quindi una scansione verticale e una foto orizzontale possono stare una accanto all'altra. Se vuoi un orientamento uniforme delle pagine, questo è un motivo per usare una dimensione di pagina fissa e lasciare che le immagini si adattino a essa.

Ho un PDF e voglio le sue pagine come immagini, non il contrario.

Questa è l'operazione inversa — rendering anziché assemblaggio. Vedi Come convertire le pagine PDF in immagini in JavaScript (React).

Devo avere l'immagine nel VFS?

Solo per FromFile. FromStream accetta byte da qualsiasi origine — una risposta fetch, un'esportazione canvas o lo stato — e salta completamente il VFS.


Vedi anche

Le ricette di assemblaggio qui costruiscono un documento nuovo di zecca. Se le tue immagini devono andare in un PDF esistente — disegnando su pagine che hai già — vedi Come aggiungere immagini a un PDF in JavaScript (React). Gli altri pezzi utili della pipeline immagini-PDF: