Bilder in PDF in JavaScript (React) umwandeln

2026-09-10 05:33:31 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

Einen Ordner voller Bilder jemandem zu übergeben, ist umständlich. Ein PDF ist eine einzige Datei, öffnet sich überall, lässt sich vorhersehbar drucken und – der Teil, der wirklich zählt – behält eine feste Reihenfolge bei. Deshalb werden gescannte Seiten, Fotoserien, Belegfotos und exportierte Design-Frames so oft zu einem PDF zusammengefügt, bevor sie irgendwohin gehen.

Dieses PDF im Browser zu erstellen, ist ein anderes Problem als das Rendern eines PDFs in ein Bild. Du dekodierst nicht etwas, das bereits existiert; du triffst Entscheidungen, die ein Dokumentformat normalerweise für dich treffen würde: Wie groß ist die Seite, wo sitzt das Bild darauf, was passiert, wenn ein Bild eine andere Form hat als die Seite, und in welcher Reihenfolge erscheinen die Seiten.

Spire.PDF for JavaScript macht diese Entscheidungen über eine Seiten-Leinwand zugänglich. Du fügst eine Seite hinzu, lädst ein Bild, zeichnest es in einer von dir berechneten Größe auf diese Seite und speicherst. Alles läuft clientseitig über WebAssembly, sodass die Bilder niemals hochgeladen werden.


Warum Bilder in PDFs landen

Die Szenarien haben alle dieselbe Form: mehrere Bilder, die sich wie ein einziges Dokument verhalten sollen.

  • Gescannte oder fotografierte mehrseitige Dokumente — ein Vertrag, Seite für Seite fotografiert und zu einer einzigen Datei zusammengesetzt, die abgelegt oder per E-Mail versendet werden kann.
  • Fotoserien und Portfolios — ein Bild pro Seite, in einer selbst gewählten Reihenfolge.
  • Belege und Spesenabrechnungen — ein Dutzend Handyfotos, die die Buchhaltung als einen Anhang haben möchte.
  • Design- und Diagramm-Exporte — Frames, die aus einem Tool exportiert und zu etwas Überprüfbarem zusammengestellt werden.

In jedem Fall geht es beim PDF nicht wirklich um das PDF-Format. Es geht darum, aus einem Haufen Bilder ein stabiles, einteiliges, geordnetes Artefakt zu machen.


Voraussetzungen

Diese Anleitung setzt ein React-Projekt mit installiertem Spire.PDF for JavaScript und initialisiertem WASM-Modul voraus. Zur Einrichtung siehe Integrating Spire.PDF for JavaScript in a React Project.

Du benötigst:

  • Eine oder mehrere in das VFS geladene Bilddateien
  • Das WASM-Modul, erreichbar unter window.wasmModule.spirepdf

Ein Bild, eine Seite

Der grundlegende Ablauf besteht aus vier Schritten: ein Dokument erstellen, eine Seite hinzufügen, das Bild laden, es zeichnen. Der interessante Teil ist das Zeichnen — du musst entscheiden, wie groß das Bild auf der Seite sein soll.

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;

PDF-Dokument, das nach dem Laden eines Bildes über PdfImage.FromFile und dem Zeichnen mit Canvas.DrawImage erzeugt wurde

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

Lagere die Größenberechnung in einen Helfer aus, denn das ist der eine Teil dieses Codes, den du in jedem weiteren Rezept unten wiederverwenden wirst. Die Mathematik: Nimm page.Canvas.ClientSize — die zeichenbare Fläche der Seite, in Punkten — als dein Budget, vergleiche es mit der natürlichen PhysicalDimension des Bildes und teile beide Dimensionen durch einen einzigen Faktor, damit das Seitenverhältnis erhalten bleibt. Vergrabe es in einer Funktion, damit der fehleranfällige Teil an genau einer Stelle lebt:

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

Jetzt schrumpft der Zeichenaufruf im obigen Beispiel auf drei Zeilen, und die Entscheidung „contain oder cover“ wandert aus der Mathematik heraus und in einen Funktionsnamen:

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 ist die „Contain“-Wahl — skaliere anhand der restriktiveren Achse, damit das gesamte Bild sichtbar bleibt. Wenn du stattdessen die Seite füllen und den Überlauf abschneiden möchtest, ersetze es durch Math.min; der Abschnitt über Größenanpassung liefert dir das fitCover-Gegenstück und eine Variante mit Rand.


Viele Bilder, ein Dokument

Ein Bild pro Seite bedeutet ein Pages.Add() und ein DrawImage pro Bild. Durchlaufe ein Array von Dateinamen in einer Schleife, und die Array-Reihenfolge wird zur Seitenreihenfolge — genau das, was du willst, wenn der Benutzer gerade Miniaturansichten in eine Reihenfolge gezogen hat.

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

Zwei praktische Hinweise. Da jede Seite unabhängig dimensioniert wird, sind Bilder mit unterschiedlichen Abmessungen kein Problem — ein Querformatfoto und ein Hochformat-Scan können ohne spezielle Behandlung im selben Dokument stehen. Und da das gesamte Dokument vor SaveToFile im Speicher aufgebaut wird, erfolgt der Download am Ende nur einmal, unabhängig davon, wie viele Bilder hineingegangen sind.

Das könnte dir auch gefallen: Das Zusammensetzen von Bildern ist die Umkehrung des Renderns. Wenn du bereits ein PDF hast und jede seiner Seiten stattdessen als Bild möchtest, siehe How to Convert PDF Pages to Images in JavaScript (React).


Größenanpassung: Das Bild an die Seite anpassen

Es gibt zwei sinnvolle Möglichkeiten, ein Bild auf eine Seite zu setzen, und welche du willst, hängt davon ab, ob der Verlust eines Teils des Bildes akzeptabel ist.

Contain (Math.max) Cover (Math.min)
Was es tut Skaliert, bis das gesamte Bild passt Skaliert, bis die Seite gefüllt ist
Gesamtes Bild sichtbar Ja Nein — der Überlauf wird abgeschnitten
Leerraum Möglich, auf einer Achse Keiner
Geeignet für Scans, Dokumente, alles, was vollständig bleiben muss Randlose Fotos, Titelbilder, Folien

Das erste Beispiel verwendet Contain — den fitContain-Helfer. Der Wechsel zu Cover ist das Spiegelbild dieser Funktion: Math.min statt Math.max, die Seite füllend und die Leinwand alles abschneiden lassend, was überläuft, mit Zentrierungs-Offsets, die negativ werden:

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

Wenn du einen sichtbaren Rand anstelle einer randlosen Ausgabe möchtest, verkleinere den nutzbaren Bereich statt das Bild — führe das um den Rand reduzierte Budget in denselben fitContain-Helfer ein:

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

Eine Sache, die man über PhysicalDimension wissen sollte: Sie spiegelt die physische Größe des Bildes wider, die nicht immer seiner Pixelgröße entspricht. Ein Foto mit 4000 × 3000 Pixeln, das mit einem anderen DPI-Tag gespeichert wurde, meldet andere Zahlen als erwartet. Deshalb ist der oben beschriebene verhältnisbasierte Ansatz sicherer als fest codierte Pixelabmessungen — er funktioniert unabhängig davon, wie das Bild getaggt wurde.


Bilder aus dem Speicher laden

PdfImage.FromFile erwartet, dass sich das Bild bereits im VFS befindet. Dort liegen deine Bilder aber nicht immer — eine API-Antwort, ein Datenbank-Blob oder ein Canvas-Export liefern dir stattdessen Bytes im Speicher. PdfImage.FromStream nimmt diese Bytes direkt entgegen.

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

Von dort aus ist es wie bei jedem anderen Bild — die Größe berechnen und es zeichnen:

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

Dieselbe bytes-Form funktioniert unabhängig davon, woher sie stammt. Wenn deine Bilder als ArrayBuffer von fetch ankommen, packe ihn in ein Uint8Array, bevor du den Stream konstruierst:

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

Dies ist das Muster, zu dem du greifen solltest, wenn es sich um serverausgelöste Bilder, im State gehaltene Benutzer-Uploads oder etwas handelt, das zur Laufzeit von einem Canvas generiert wird — kein Umweg über das VFS erforderlich.

Empfohlener Artikel: Spire.PDF kann auch auf die Seiten eines PDFs zeichnen, das du bereits geöffnet hast, nicht nur auf die neuen Dokumente oben. Zum Platzieren von Bildern in einem bestehenden Dokument siehe How to Add Images to a PDF in JavaScript (React).


Häufige Probleme

Das Bild kommt gestreckt oder gestaucht heraus. Das sind fast immer zwei verschiedene Skalierungsfaktoren. Berechne einen einzigen fitRate und teile sowohl Breite als auch Höhe durch ihn — skaliere die Achsen niemals unabhängig voneinander.

Das Bild ist winzig in der Mitte einer großen leeren Seite. Das ist zu erwarten, wenn das Seitenverhältnis des Bildes stark von dem der Seite abweicht. Ein Panoramafoto auf einer Hochformatseite hinterlässt immer Streifen oben und unten. Akzeptiere es entweder (korrekt für Dokumente), wechsle zu Cover oder verwende die randangepasste Version, um wenigstens den Leerraum symmetrisch zu halten.

Das Bild ist an den Rändern abgeschnitten. Du verwendest absichtlich oder unabsichtlich das Cover-Verhalten. Prüfe, ob fitRate Math.min verwendet hat; wechsle zu Math.max, wenn das gesamte Bild sichtbar sein muss.

Ein hochauflösendes Foto erzeugt ein riesiges PDF. Das Bild wird in seiner eigenen Auflösung eingebettet. Wenn die Dateigröße wichtig ist, skaliere vor dem Zeichnen herunter — zeichne es in Zielgröße auf ein Canvas, exportiere es und verwende diese Bytes mit PdfImage.FromStream.

Beim ersten Klick passiert nichts. Das WASM-Modul lädt asynchron. Die Prüfung if (!pdfModule) return; existiert aus diesem Grund; in einer echten App solltest du den Button an die Bereitschaft des Moduls koppeln, statt eine Warnung anzuzeigen.


FAQ

Kann ich Bilder in ein bestehendes PDF einfügen, anstatt ein neues zu erstellen?

Ja. Die Beispiele hier erstellen ein neues Dokument, aber du kannst ein bestehendes PDF öffnen und auf dieselbe Weise auf seine Seiten zeichnen. Siehe How to Add Images to a PDF in JavaScript (React) für diesen Workflow.

Welche Bildformate kann ich laden?

Gängige Bitmap-Formate — PNG, JPEG, BMP und ähnliche — werden von PdfImage.FromFile und PdfImage.FromStream unterstützt. Verwende FromStream, wenn das Format zur Build-Zeit unbekannt ist oder die Bytes aus einer Netzwerkantwort stammen.

Kann ich die Seitenreihenfolge steuern?

Ja. Seiten werden in der Reihenfolge erstellt, in der du Pages.Add() aufrufst, also sortiert das Sortieren deines Dateinamen-Arrays die Ausgabe. Das ist der Mechanismus hinter Drag-and-Drop-Oberflächen zum Umsortieren: Ordne das Array um, baue das PDF neu.

Ist dafür ein Backend erforderlich?

Nein. Das Dokument wird im Browser durch das WebAssembly-Modul zusammengesetzt, und das fertige PDF wird als Bytes zurückgegeben, die du in einen Blob umwandelst. Die Bilder verlassen niemals das Gerät.

Kann ich Hoch- und Querformatbilder in einem PDF mischen?

Ja. Jede Seite wird unabhängig dimensioniert und gezeichnet, sodass ein Hochformat-Scan und ein Querformatfoto nebeneinander stehen können. Wenn du eine einheitliche Seitenausrichtung möchtest, ist das ein Grund, eine feste Seitengröße zu verwenden und die Bilder darauf skalieren zu lassen.

Ich habe ein PDF und möchte seine Seiten als Bilder, nicht umgekehrt.

Das ist die umgekehrte Operation — Rendern statt Zusammensetzen. Siehe How to Convert PDF Pages to Images in JavaScript (React).

Muss das Bild im VFS liegen?

Nur für FromFile. FromStream akzeptiert Bytes von überall — eine Fetch-Antwort, ein Canvas-Export oder ein State — und umgeht das VFS vollständig.


Siehe auch

Die Zusammenbau-Rezepte hier erstellen ein brandneues Dokument. Wenn deine Bilder in ein bestehendes PDF eingefügt werden sollen — auf Seiten zeichnen, die du bereits hast —, siehe How to Add Images to a PDF in JavaScript (React). Die anderen nützlichen Teile der Bild-zu-PDF-Pipeline: