Convertendo Imagens para PDF em JavaScript (React)

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

Uma pasta de imagens é incômoda de entregar a alguém. Um PDF é um único arquivo, abre em qualquer lugar, imprime de forma previsível e — a parte que realmente importa — mantém uma ordem fixa. É por isso que páginas digitalizadas, conjuntos de fotos, fotos de recibos e quadros de design exportados costumam ser reunidos em um PDF antes de serem enviados a qualquer lugar.

Construir esse PDF no navegador é um problema diferente de renderizar um PDF como imagem. Você não está decodificando algo que já existe; você está tomando decisões que um formato de documento normalmente tomaria por você: qual o tamanho da página, onde a imagem se posiciona nela, o que acontece quando uma imagem tem formato diferente da página e em que ordem as páginas saem.

Spire.PDF for JavaScript expõe essas decisões por meio de uma tela de página. Você adiciona uma página, carrega uma imagem, desenha-a nessa página em um tamanho que você calcula e salva. Tudo é executado no lado do cliente via WebAssembly, então as imagens nunca são enviadas.


Por que imagens acabam em PDFs

Os cenários compartilham uma mesma característica: várias imagens que precisam se comportar como um único documento.

  • Documentos de várias páginas digitalizados ou fotografados — um contrato fotografado página por página, remontado em um único arquivo que pode ser arquivado ou enviado por e-mail.
  • Conjuntos de fotos e portfólios — uma imagem por página, em uma ordem escolhida por alguém.
  • Recibos e relatórios de despesas — uma dúzia de fotos de celular que o departamento financeiro quer como um único anexo.
  • Exportações de design e diagramas — quadros exportados de uma ferramenta, reunidos em algo revisável.

Em cada caso, o PDF não é realmente sobre o formato PDF. É sobre obter um artefato estável, de arquivo único e ordenado a partir de uma pilha de imagens.


Pré-requisitos

Este tutorial pressupõe um projeto React com o Spire.PDF for JavaScript instalado e o módulo WASM inicializado. Para a configuração, consulte Integrating Spire.PDF for JavaScript in a React Project.

Você precisará de:

  • Um ou mais arquivos de imagem carregados no VFS
  • O módulo WASM acessível em window.wasmModule.spirepdf

Uma imagem, uma página

O fluxo básico tem quatro etapas: criar um documento, adicionar uma página, carregar a imagem e desenhá-la. A parte interessante é o desenho — você precisa decidir o quão grande a imagem deve ser na página.

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 gerado após carregar uma imagem via PdfImage.FromFile e desenhá-la com Canvas.DrawImage

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

Extraia o dimensionamento para um helper, pois é a única parte deste código que você reutilizará em todas as outras receitas abaixo. A matemática: pegue page.Canvas.ClientSize — a área desenhável da página, em pontos — como seu orçamento, compare-a com a PhysicalDimension natural da imagem e divida ambas as dimensões por uma única razão para que a proporção seja preservada. Esconda isso em uma função para que a parte propensa a bugs fique em exatamente um lugar:

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

Agora a chamada de desenho no exemplo acima se reduz a três linhas, e a decisão de "contain ou cover" sai da matemática e passa para o nome de uma função:

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 é a escolha "contain" — dimensiona pelo eixo mais restritivo para que a imagem inteira permaneça visível. Se, em vez disso, você quiser preencher a página e cortar o excesso, troque por Math.min; a seção sobre dimensionamento fornece a contraparte fitCover e uma variante com margem.


Muitas imagens, um documento

Uma imagem por página significa um Pages.Add() e um DrawImage por imagem. Percorra um array de nomes de arquivo e a ordem do array se torna a ordem das páginas — que é exatamente o que você quer quando o usuário acabou de arrastar miniaturas para colocá-las em sequência.

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

Duas observações práticas. Como cada página é dimensionada de forma independente, imagens de dimensões diferentes não são problema — uma foto em paisagem e uma digitalização em retrato podem ficar no mesmo documento sem nenhum tratamento especial. E como todo o documento é construído na memória antes de SaveToFile, o download acontece uma única vez no final, independentemente de quantas imagens foram incluídas.

Você também pode gostar: Montar imagens é o inverso da renderização. Se você já tem um PDF e quer cada uma de suas páginas como imagem, consulte How to Convert PDF Pages to Images in JavaScript (React).


Dimensionamento: ajustar a imagem à página

Há duas maneiras razoáveis de colocar uma imagem em uma página, e qual delas você quer depende de se perder parte da imagem é aceitável.

Contain (Math.max) Cover (Math.min)
O que faz Dimensiona até que a imagem inteira caiba Dimensiona até que a página seja preenchida
Imagem inteira visível Sim Não — o excesso é cortado
Espaço vazio Possível, em um dos eixos Nenhum
Ideal para Digitalizações, documentos, qualquer coisa que precise permanecer completa Fotos de página inteira, capas, slides

O primeiro exemplo usa contain — o helper fitContain. Mudar para cover é o espelho dessa função: Math.min em vez de Math.max, preenchendo a página e deixando a tela cortar o que transbordar, com deslocamentos de centralização que ficam negativos:

// 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 você quiser uma margem visível em vez de uma saída de borda a borda, reduza a área utilizável em vez da imagem — passe o orçamento ajustado pela margem para o mesmo helper 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 });

Uma coisa que vale saber sobre PhysicalDimension: ela reflete o tamanho físico da imagem, que nem sempre é o tamanho em pixels. Uma foto de 4000 × 3000 salva com uma tag de DPI diferente reportará números diferentes do que você poderia esperar. É por isso que a abordagem baseada em razão acima é mais segura do que codificar dimensões em pixels — ela funciona independentemente de como a imagem foi marcada.


Carregar imagens da memória

PdfImage.FromFile espera que a imagem já esteja no VFS. Nem sempre é aí que suas imagens estão — uma resposta de API, um blob de banco de dados ou uma exportação de canvas fornecem bytes na memória em vez disso. PdfImage.FromStream recebe esses bytes diretamente.

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

A partir daí, é o mesmo que qualquer outra imagem — calcule o tamanho e desenhe-a:

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

A mesma forma de bytes funciona independentemente de onde ela veio. Se suas imagens chegam como um ArrayBuffer de fetch, envolva-o em um Uint8Array antes de construir o 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);

Este é o padrão a usar quando o PDF é montado a partir de imagens acionadas pelo servidor, uploads de usuário mantidos em estado ou qualquer coisa gerada em tempo de execução por um canvas — sem necessidade de ida e volta pelo VFS.

Artigo recomendado: O Spire.PDF também pode desenhar nas páginas de um PDF que você já tem aberto, não apenas nos novos documentos acima. Para inserir imagens em um documento existente, consulte How to Add Images to a PDF in JavaScript (React).


Problemas comuns

A imagem sai esticada ou achatada. Isso quase sempre é o uso de dois fatores de escala diferentes. Calcule um único fitRate e divida tanto a largura quanto a altura por ele — nunca dimensione os eixos independentemente.

A imagem fica minúscula no meio de uma grande página vazia. Esperado, quando a proporção da imagem é muito diferente da proporção da página. Uma foto panorâmica em uma página retrato sempre deixará faixas acima e abaixo. Ou aceite isso (correto para documentos), mude para cover, ou use a versão ajustada com margem para pelo menos manter o espaço em branco simétrico.

A imagem fica cortada nas bordas. Você está usando o comportamento cover, intencionalmente ou não. Verifique se o fitRate usou Math.min; troque para Math.max se a imagem inteira precisar estar visível.

Uma foto de alta resolução produz um PDF enorme. A imagem é incorporada na sua própria resolução. Se o tamanho do arquivo importa, reduza a escala antes de desenhar — desenhe-a em um canvas no tamanho desejado, exporte e use esses bytes com PdfImage.FromStream.

Nada acontece no primeiro clique. O módulo WASM carrega de forma assíncrona. A verificação if (!pdfModule) return; existe por esse motivo; em um aplicativo real, condicione o botão à prontidão do módulo em vez de exibir um alerta.


Perguntas frequentes

Posso inserir imagens em um PDF existente em vez de criar um novo?

Sim. Os exemplos aqui criam um novo documento, mas você pode abrir um PDF existente e desenhar em suas páginas da mesma forma. Consulte How to Add Images to a PDF in JavaScript (React) para esse fluxo de trabalho.

Quais formatos de imagem posso carregar?

Formatos bitmap comuns — PNG, JPEG, BMP e similares — são suportados por PdfImage.FromFile e PdfImage.FromStream. Use FromStream quando o formato for desconhecido em tempo de compilação ou quando os bytes vierem de uma resposta de rede.

Posso controlar a ordem das páginas?

Sim. As páginas são criadas na ordem em que você chama Pages.Add(), então ordenar seu array de nomes de arquivo ordena a saída. Esse é o mecanismo por trás das interfaces de arrastar para reordenar: reordene o array, reconstrua o PDF.

Isso exige um backend?

Não. O documento é montado no navegador pelo módulo WebAssembly, e o PDF finalizado é retornado como bytes que você transforma em um Blob. As imagens nunca saem do dispositivo.

Posso misturar imagens em retrato e paisagem em um mesmo PDF?

Sim. Cada página é dimensionada e desenhada de forma independente, então uma digitalização em retrato e uma foto em paisagem podem ficar lado a lado. Se você quer orientação de página uniforme, esse é um motivo para usar um tamanho de página fixo e deixar as imagens se dimensionarem a ele.

Tenho um PDF e quero suas páginas como imagens, não o contrário.

Essa é a operação inversa — renderização em vez de montagem. Consulte How to Convert PDF Pages to Images in JavaScript (React).

Preciso da imagem no VFS?

Apenas para FromFile. FromStream aceita bytes de qualquer lugar — uma resposta de fetch, uma exportação de canvas ou estado — e ignora o VFS completamente.


Veja também

As receitas de montagem aqui criam um documento totalmente novo. Se suas imagens precisam ir para dentro de um PDF existente — desenhando em páginas que você já tem — consulte How to Add Images to a PDF in JavaScript (React). As outras peças úteis do pipeline de imagem-PDF: