
Un dossier d'images est peu pratique à transmettre à quelqu'un. Un PDF est un seul fichier, s'ouvre partout, s'imprime de manière prévisible et — la partie qui intéresse réellement les gens — conserve un ordre fixe. C'est pourquoi les pages numérisées, les ensembles de photos, les photos de reçus et les cadres de conception exportés sont si souvent assemblés dans un PDF avant d'être envoyés où que ce soit.
Construire ce PDF dans le navigateur est un problème différent du rendu d'un PDF en image. Vous ne décodez pas quelque chose qui existe déjà ; vous prenez des décisions qu'un format de document prendrait normalement pour vous : quelle taille fait la page, où l'image se place-t-elle dessus, ce qui se passe lorsqu'une image n'a pas la même forme que la page, et dans quel ordre les pages sont produites.
Spire.PDF pour JavaScript expose ces décisions via un canevas de page. Vous ajoutez une page, chargez une image, la dessinez sur cette page à une taille que vous calculez, puis enregistrez. Tout s'exécute côté client via WebAssembly, donc les images ne sont jamais téléversées.
Pourquoi les images se retrouvent dans les PDF
Les scénarios ont une même forme : plusieurs images qui doivent se comporter comme un seul document.
- Documents multipages numérisés ou photographiés — un contrat photographié page par page, réassemblé en un seul fichier pouvant être classé ou envoyé par e-mail.
- Ensembles de photos et portfolios — une image par page, dans un ordre choisi par quelqu'un.
- Reçus et notes de frais — une douzaine de photos prises au téléphone que la comptabilité veut en une seule pièce jointe.
- Exports de conception et de diagrammes — des cadres exportés depuis un outil, rassemblés dans un ensemble consultable.
Dans chaque cas, le PDF ne concerne pas vraiment le format PDF. Il s'agit d'obtenir un artefact stable, unique et ordonné à partir d'un tas d'images.
Prérequis
Ce didacticiel suppose un projet React avec Spire.PDF pour JavaScript installé et le module WASM initialisé. Pour la configuration, consultez Intégration de Spire.PDF pour JavaScript dans un projet React.
Vous aurez besoin de :
- Un ou plusieurs fichiers image chargés dans le VFS
- Le module WASM accessible à
window.wasmModule.spirepdf
Une image, une page
Le flux de base comporte quatre étapes : créer un document, ajouter une page, charger l'image, la dessiner. La partie intéressante est le dessin — vous devez décider de la taille que l'image doit avoir sur la page.
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;
Document PDF généré après le chargement d'une image via PdfImage.FromFile et son dessin avec Canvas.DrawImage

Extrayez le dimensionnement dans une fonction utilitaire, car c'est le seul élément de ce code que vous réutiliserez dans toutes les autres recettes ci-dessous. Les mathématiques : prenez page.Canvas.ClientSize — la zone dessinable de la page, en points — comme budget, comparez-la à la PhysicalDimension naturelle de l'image, et divisez les deux dimensions par un même ratio afin que le rapport d'aspect soit préservé. Enfouissez-le dans une fonction pour que la partie sujette aux bogues vive à un seul endroit :
// 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 };
}
Maintenant, l'appel de dessin dans l'exemple ci-dessus se réduit à trois lignes, et la décision « contenir ou couvrir » sort des mathématiques pour aller dans un nom de fonction :
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 est le choix « contain » — mettre à l'échelle selon l'axe le plus restrictif afin que toute l'image reste visible. Si vous voulez plutôt remplir la page et rogner le débordement, utilisez Math.min ; la section sur le dimensionnement vous donne l'équivalent fitCover et une variante avec marges.
Plusieurs images, un document
Une image par page signifie un appel à Pages.Add() et un DrawImage par image. Parcourez un tableau de noms de fichiers et l'ordre du tableau devient l'ordre des pages — exactement ce que vous voulez lorsque l'utilisateur vient de terminer de faire glisser des vignettes pour les mettre en séquence.
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);
};
Deux remarques pratiques. Comme chaque page est dimensionnée indépendamment, les images de dimensions différentes ne posent aucun problème — une photo en paysage et un scan en portrait peuvent figurer dans le même document sans traitement particulier. Et comme le document entier est construit en mémoire avant SaveToFile, le téléchargement n'a lieu qu'une fois à la fin, quel que soit le nombre d'images ajoutées.
Vous aimerez peut-être aussi : Assembler des images est l'inverse du rendu. Si vous avez déjà un PDF et voulez plutôt chacune de ses pages sous forme d'image, consultez Comment convertir des pages PDF en images en JavaScript (React).
Dimensionnement : adapter l'image à la page
Il existe deux manières raisonnables de placer une image sur une page, et celle que vous voulez dépend de si la perte d'une partie de l'image est acceptable.
Contenir (Math.max) |
Couvrir (Math.min) |
|
|---|---|---|
| Ce que cela fait | Met à l'échelle jusqu'à ce que l'image entière tienne | Met à l'échelle jusqu'à ce que la page soit remplie |
| Image entière visible | Oui | Non — le débordement est rogné |
| Espace vide | Possible, sur un axe | Aucun |
| Adapté à | Scans, documents, tout ce qui doit rester complet | Photos en fond perdu, pages de couverture, diapositives |
Le premier exemple utilise contenir — la fonction utilitaire fitContain. Passer à couvrir est le miroir de cette fonction : Math.min au lieu de Math.max, remplir la page et laisser le canevas rogner tout ce qui déborde, avec des décalages de centrage qui deviennent négatifs :
// 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
};
}
Si vous voulez une marge visible au lieu d'une sortie bord à bord, réduisez la zone utilisable plutôt que l'image — transmettez le budget ajusté en fonction de la marge à la même fonction utilitaire 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 });
Une chose à savoir sur PhysicalDimension : elle reflète la taille physique de l'image, qui n'est pas toujours sa taille en pixels. Une photo 4000 × 3000 enregistrée avec une balise DPI différente signalera des nombres différents de ce que vous pourriez attendre. C'est pourquoi l'approche basée sur le ratio ci-dessus est plus sûre que de coder en dur les dimensions en pixels — elle fonctionne indépendamment de la façon dont l'image a été balisée.
Charger des images depuis la mémoire
PdfImage.FromFile s'attend à ce que l'image soit déjà dans le VFS. Ce n'est pas toujours là que se trouvent vos images — une réponse d'API, un blob de base de données ou un export de canevas vous fournissent plutôt des octets en mémoire. PdfImage.FromStream prend directement ces octets.
// 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);
À partir de là, c'est comme pour n'importe quelle autre image — calculez la taille et dessinez-la :
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 même forme de bytes fonctionne quelle que soit sa provenance. Si vos images arrivent sous forme d'ArrayBuffer depuis fetch, encapsulez-le dans un Uint8Array avant de construire le flux :
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);
C'est le modèle à utiliser lorsque le PDF est assemblé à partir d'images déclenchées par le serveur, de téléversements utilisateur conservés dans l'état, ou de tout élément généré à l'exécution par un canevas — aucun aller-retour par le VFS n'est nécessaire.
Article recommandé : Spire.PDF peut également dessiner sur les pages d'un PDF que vous avez déjà ouvert, pas seulement sur les nouveaux documents ci-dessus. Pour placer des images dans un document existant, consultez Comment ajouter des images à un PDF en JavaScript (React).
Problèmes courants
L'image ressort étirée ou écrasée.
C'est presque toujours dû à deux facteurs d'échelle différents. Calculez un seul fitRate et divisez à la fois la largeur et la hauteur par celui-ci — ne mettez jamais les axes à l'échelle indépendamment.
L'image est minuscule au milieu d'une grande page vide. C'est attendu lorsque le rapport d'aspect de l'image est très éloigné de celui de la page. Une photo panoramique sur une page portrait laissera toujours des bandes en haut et en bas. Acceptez-le (correct pour les documents), passez à couvrir, ou utilisez la version ajustée avec marges pour au moins garder les espaces blancs symétriques.
L'image est coupée sur les bords.
Vous utilisez un comportement de couverture, intentionnellement ou non. Vérifiez si fitRate a utilisé Math.min ; passez à Math.max si toute l'image doit être visible.
Une photo haute résolution produit un PDF énorme.
L'image est intégrée à sa propre résolution. Si la taille du fichier compte, réduisez l'échelle avant de dessiner — dessinez-la sur un canevas à la taille cible, exportez, et utilisez ces octets avec PdfImage.FromStream.
Rien ne se passe au premier clic.
Le module WASM se charge de manière asynchrone. La garde if (!pdfModule) return; existe pour cette raison ; dans une vraie application, conditionnez le bouton à la disponibilité du module plutôt que d'afficher une alerte.
FAQ
Puis-je insérer des images dans un PDF existant au lieu d'en créer un nouveau ?
Oui. Les exemples ici créent un nouveau document, mais vous pouvez ouvrir un PDF existant et dessiner sur ses pages de la même manière. Consultez Comment ajouter des images à un PDF en JavaScript (React) pour ce flux de travail.
Quels formats d'image puis-je charger ?
Les formats bitmap courants — PNG, JPEG, BMP et similaires — sont pris en charge par PdfImage.FromFile et PdfImage.FromStream. Utilisez FromStream lorsque le format est inconnu au moment de la compilation ou que les octets proviennent d'une réponse réseau.
Puis-je contrôler l'ordre des pages ?
Oui. Les pages sont créées dans l'ordre dans lequel vous appelez Pages.Add(), donc trier votre tableau de noms de fichiers trie la sortie. C'est le mécanisme derrière les interfaces de glisser-déposer pour réorganiser : réorganisez le tableau, reconstruisez le PDF.
Cela nécessite-t-il un backend ?
Non. Le document est assemblé dans le navigateur par le module WebAssembly, et le PDF fini est renvoyé sous forme d'octets que vous transformez en Blob. Les images ne quittent jamais l'appareil.
Puis-je mélanger des images en portrait et en paysage dans un même PDF ?
Oui. Chaque page est dimensionnée et dessinée indépendamment, donc un scan en portrait et une photo en paysage peuvent se côtoyer. Si vous voulez une orientation de page uniforme, c'est une raison d'utiliser une taille de page fixe et de laisser les images s'y adapter.
J'ai un PDF et je veux ses pages sous forme d'images, et non l'inverse.
C'est l'opération inverse — un rendu plutôt qu'un assemblage. Consultez Comment convertir des pages PDF en images en JavaScript (React).
Ai-je besoin de l'image dans le VFS ?
Uniquement pour FromFile. FromStream accepte des octets de n'importe où — une réponse fetch, un export de canevas ou un état — et contourne entièrement le VFS.
Voir aussi
Les recettes d'assemblage ici construisent un tout nouveau document. Si vos images doivent aller dans un PDF existant — dessiner sur des pages que vous avez déjà — consultez Comment ajouter des images à un PDF en JavaScript (React). Les autres éléments utiles du pipeline image-PDF :