Transformez des documents en présentations PowerPoint avec l'IA en C#
Table des matières

Transformer des documents en présentations PowerPoint avec l'IA en C# est plus qu'une conversion de format. Un rapport de 30 pages qui devient une présentation de 10 diapositives signifie que quelqu'un a lu le document complet, a décidé des 90 % à omettre, a réorganisé ce qui restait en un récit et a appliqué une conception visuelle. C'est de la synthèse éditoriale — la partie que les bibliothèques PowerPoint traditionnelles ne font pas.
La partie intéressante est ce qui est synthétisé. Dans les flux de travail réels, la source est rarement un seul fichier. Une revue trimestrielle d'activité tire des chiffres d'un classeur Excel, du récit d'un rapport Word et une facture signée d'un PDF. La valeur d'un agent documentaire IA n'est pas de « convertir un .docx en .pptx » — mais de « lire les trois, et me donner une présentation qui raconte l'histoire à travers eux. » Cet article montre la façon la plus simple de faire exactement cela avec Spire.Agent.Office en C# : transmettez à l'agent chaque document plus une instruction, et laissez-le générer la présentation en un seul appel.
1. Une instruction, plusieurs sources
Le code Spire.Office traditionnel traite chaque format comme une île. Pour construire une présentation à partir d'un rapport Word et d'une feuille Excel, vous devriez :
- Utiliser
Spire.Docpour ouvrir le.docxet extraire le texte souhaité. - Utiliser
Spire.XLSpour lire le classeur et calculer les chiffres. - Utiliser
Spire.Pdfsi une source est un PDF. - Utiliser
Spire.Presentationpour créer les diapositives une par une, positionner chaque zone de texte et graphique, et calculer les mises en page à la main.
Cela représente quatre surfaces d'API, un analyseur par format et un moteur de mise en page manuel. Dès que la structure d'un document change, le code casse.
Spire.Agent.Office réduit cela à une seule instruction. Vous transmettez à l'agent tous vos fichiers sources et lui indiquez quelle histoire la présentation doit raconter. L'agent lit chaque format, décide ce qui compte à travers eux et produit les diapositives — sans code de mise en page manuel.
Le modèle mental est une seule étape :

Au lieu d'écrire un analyseur par format, vous laissez l'agent lire chaque fichier et générer la présentation directement. Un appel, une chaîne d'instruction, un fichier .pptx.
2. Ce dont vous avez besoin
- .NET 6+ (l'exemple cible
net10.0). - Le package NuGet
Spire.Agent.Office. Il inclut de manière transitiveSpire.Doc,Spire.Pdf,Spire.XLSetSpire.Presentation, vous n'avez donc pas à les installer séparément. - Un
SpireToken(clé API Spire.Agent.Office). L'agent communique avec le service d'IA, une clé valide est donc requise pour la génération réelle. - Imports d'espaces de noms :
using Spire.Agent.Office.AI; // AIOptions, AIDocumentProcessor, AIResult
using Spire.Agent.Office.Extensions;// presentation.AI(...) extension
using Spire.Presentation; // Presentation
3. La recette simple : un appel, une présentation
L'idée centrale est d'une simplicité radicale : transmettez chaque document source comme pièce jointe, écrivez une instruction en langage naturel et laissez l'agent tout faire — lecture, compréhension, synthèse, mise en page — en un seul appel ExecuteInstruction.
string[] sources = new[]
{
Path.Combine(sampleDir, "AI-Powered Document Processing Report.docx"),
Path.Combine(sampleDir, "purchase-orders.xlsx"),
Path.Combine(sampleDir, "invoice_INV-2026-0815.pdf")
};
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIResult result = processor.ExecuteInstruction(
ppt, instruction, savePath, sources,
autonomousOutput: true, maxTurns: null);
C'est toute la recette : un appel de méthode, une chaîne d'instruction. Pas d'analyseurs par format, pas de fichiers intermédiaires, pas de code de mise en page manuel. L'agent lit chaque format, décide ce qui compte à travers eux et écrit la présentation directement dans savePath.
L'indicateur autonomousOutput: true est ce qui rend cela possible — il indique à l'agent qu'il peut écrire des fichiers (y compris le .pptx final) sans demander de confirmation à chaque étape. Définissez maxTurns sur null (ou un grand nombre) pour que l'agent dispose de suffisamment de tours pour terminer.
4. Exemple complet : une présentation à partir de Word + PDF + Excel
Voici le programme complet. Pointez-le vers un fichier ou un dossier, et il construit la présentation en un seul appel IA :
using System;
using System.IO;
using System.Linq;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;
class Program
{
static void Main()
{
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIOptions options = new AIOptions { SpireToken = "sk-YourSpireToken", TimeoutMs = 1000000 };
using (Presentation ppt = new Presentation())
{
AIResult result = ppt.AI(options).ExecuteInstruction(
ppt, instruction, @"C:\Samples\Q3_Review.pptx", ResolveInputs(@"C:\Samples"),
autonomousOutput: true, maxTurns: null);
Console.WriteLine(result.Success ? "Success" : $"Error={result.ErrorMessage}");
}
}
// Point at a file or a folder; a folder is filtered to supported document formats.
static string[] ResolveInputs(string path) =>
File.Exists(path) ? new[] { path }
: Directory.GetFiles(path).Where(IsSupported).OrderBy(f => f).ToArray();
static bool IsSupported(string file) =>
new[] { ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md" }
.Contains(Path.GetExtension(file).ToLowerInvariant());
}
C'est tout le programme. Hormis l'appel à l'Agent lui-même, le seul code est ResolveInputs, qui décide simplement quels fichiers joindre : il ne lit ni n'analyse jamais un document. L'instruction est la seule partie qui décrit la conception (thème, nombre de diapositives, focus) ; la lecture, la décision et la mise en page sont le travail de l'agent.
Un appel, cinq diapositives — la présentation que produit le programme ci-dessus :

5. Comment cela fonctionne sous le capot
AIDocumentProcessorest obtenu à partir d'unPresentationviappt.AI(options). L'objetPresentationest le canevas sur lequel l'agent dessine les diapositives.AIOptionsporte la configuration :SpireToken(votre clé) etTimeoutMs(définissez-le grand pour de vrais documents — l'analyse IA de gros documents prend du temps).ExecuteInstructionexécute l'agent. Vous passez lePresentation, uneinstructionen langage naturel, lesavePathpour le.pptxde sortie, et unstring[]de chemins de fichiers sources (tout mélange de Word, PDF, Excel, Markdown). AvecautonomousOutput: true, l'agent peut écrire des fichiers — y compris la présentation finale — sans demander de confirmation. Il renvoie unAIResultavecSuccess,ErrorMessageetOutputFiles(tout ce que l'agent a créé).maxTurnscontrôle le nombre d'étapes d'agent autorisées. Définissez-le surnullpour aucune limite, ou un grand nombre (par ex.80) pour de grands ensembles de documents.GeneratePresentation(sourcePath, instruction, savePath)est le raccourci dédié lorsque vous avez exactement un document source. Il renvoie unPPTGenerationResult(Success,GeneratedPages,TotalPages,OutputPath,ErrorMessage) au lieu d'unAIResult. SonsourcePathest un seul fichier, ce qui explique exactement pourquoi cet article utilise le chemin multi-pièces jointesExecuteInstruction: c'est ce qui permet à une présentation de s'appuyer sur plusieurs sources en un seul appel.
Pourquoi ne pas d'abord fusionner en Markdown ? Vous pouvez — voir la variante B à la section 6. Mais pour la plupart des cas d'utilisation, l'approche en un seul appel est plus simple, plus rapide et produit le même résultat. Le Markdown intermédiaire n'est utile que lorsque vous souhaitez inspecter ou modifier le contenu consolidé avant de dépenser des tokens pour la présentation.
6. Contrôle précis
La recette ci-dessus est délibérément minimale. Lorsque vous avez besoin de plus de contrôle, ces variantes s'appuient sur le même appel ExecuteInstruction.
Ajustez l'instruction. La structure, la longueur, le thème et les graphiques de la présentation sont tous contrôlés par la chaîne d'instruction. Soyez explicite :
// e.g. ask for a specific page count, theme, and a chart on a given slide
"Create a 12-slide deck. Light minimalist theme, accent colour #2E5AAC. " +
"Include a bar chart of monthly revenue on slide 5. Keep each slide to one key message.";
Variante A — résumez vous-même chaque source (plus prévisible). Si vous préférez interroger chaque document individuellement plutôt que de faire confiance à une instruction inter-documents, bouclez sur SummarizeDocument, qui renvoie le résumé par l'agent d'un seul fichier sous forme de string :
var sb = new StringBuilder();
foreach (var file in sources)
sb.AppendLine($"## {Path.GetFileName(file)}\n\n{processor.SummarizeDocument(file)}");
File.WriteAllText(Path.Combine(workDir, "consolidated.md"), sb.ToString());
Passez ensuite le Markdown consolidé à ExecuteInstruction comme source unique — vous obtenez un résumé séparé par document que vous pouvez lire et modifier avant la génération.
Variante B — consolidez en Markdown, puis générez (en deux étapes). Pour les cas où vous voulez un fichier intermédiaire inspectable, divisez le travail en deux appels. L'étape 1 utilise ExecuteInstruction en mode autonome pour fusionner toutes les sources dans un seul fichier Markdown ; l'étape 2 fournit ce Markdown à ExecuteInstruction à nouveau pour construire la présentation :
// Step 1 — merge all documents into one Markdown file
string consolidateInstruction =
"Read all the attached files. Merge their content into a SINGLE Markdown " +
"document named 'output_consolidated.md'. Use one top-level heading per source, " +
"preserve key facts / figures / tables, and drop boilerplate. Output ONLY that file.";
processor.ExecuteInstruction(ppt, consolidateInstruction,
Path.Combine(workDir, "output_consolidated.pptx"), sources,
autonomousOutput: true, maxTurns: 40);
string consolidatedMd = Path.Combine(workDir, "output_consolidated.md");
// Step 2 — generate the deck from the consolidated Markdown
string genInstruction =
"Create a 9-slide quarterly business review deck from the consolidated material. " +
"Professional corporate blue, clear hierarchy, speaker notes per slide.";
processor.ExecuteInstruction(ppt, genInstruction,
Path.Combine(workDir, "QBR_Deck.pptx"), new[] { consolidatedMd },
autonomousOutput: true, maxTurns: 40);
Le compromis : la variante B vous donne un fichier Markdown inspectable entre les deux appels, ce qui est utile pour le débogage ou lorsque la consolidation doit être revue par un humain. La recette en un seul appel de la section 3 est la valeur par défaut recommandée car elle est plus simple et plus rapide.
Vous aimerez peut-être aussi : Automatiser le traitement des factures avec un agent IA en .NET — un autre flux de travail très documentaire qui remet à l'agent un dossier de fichiers et récupère un document fini.
7. Relire et inspecter le résultat
Après la génération, rouvrez la présentation et vérifiez-la par programmation :
using (Presentation deck = new Presentation())
{
deck.LoadFromFile(savePath);
Console.WriteLine($"Total slides: {deck.Slides.Count}");
for (int i = 0; i < deck.Slides.Count; i++)
{
ISlide slide = deck.Slides[i];
string title = slide.Title ?? "(no title)";
Console.WriteLine($"Slide {i + 1}: {title}");
}
}
C'est aussi là qu'intervient une étape de revue humaine : chargez la présentation générée, vérifiez les titres et affinez l'instruction si quelque chose ne va pas.
8. FAQ
La présentation générée n'a pas le bon nombre de diapositives.
Si une source est volumineuse, l'analyse peut prendre plus de temps que le délai d'attente par défaut et l'agent s'arrête prématurément. Définissez AIOptions.TimeoutMs sur une grande valeur (par ex. 1000000 pour ~17 minutes) et indiquez une plage de pages dans l'instruction, par ex. « Limitez la présentation finale à 8–12 diapositives. »
L'agent n'inclut pas le contenu de l'un des documents. Les documents complexes peuvent perturber une seule passe. Nommez explicitement le contenu que vous voulez dans l'instruction (« inclure les conclusions du rapport Word », « préserver le tableau de la feuille Excel »). Si le problème persiste, essayez l'approche en deux étapes (variante B à la section 6) pour inspecter le Markdown consolidé avant la génération.
L'appel échoue avec « 401 Invalid token ».
Le SpireToken est rejeté — confirmez qu'il est valide, non expiré et correctement copié dans AIOptions.SpireToken.
Comment passer plusieurs documents ?
ExecuteInstruction accepte un string[] de chemins de fichiers comme quatrième argument. Passez chaque fichier source — tout mélange de .docx, .pdf, .xlsx, .md, et plus encore — et l'agent les lit tous en un seul appel. Avec un seul document source, le raccourci dédié est GeneratePresentation(sourcePath, instruction, savePath) ; l'appel multi-pièces jointes ci-dessus est ce qui permet de créer une présentation à partir de plusieurs sources.
Obtenez votre clé SpireToken Contactez-nous pour demander une clé API d'essai ou commerciale, ou demandez une licence temporaire. Configurez-la dans le code :
AIOptions options = new AIOptions();
options.SpireToken = "sk-YourSpireToken";
options.TimeoutMs = 1000000;
Voir aussi
- Présentation du produit Spire.Agent.Office — le SDK d'agent IA pour Word, Excel, PowerPoint et PDF
- Bien démarrer avec Spire.Agent.Office — installez le SDK et exécutez votre première instruction
- Générer un PPT à partir de documents — le tutoriel officiel de l'API de présentation utilisée dans cet article
- Génération par lots de contrats avec Spire.Agent.Office — le même modèle piloté par instruction appliqué à la génération de documents en masse
Convierte documentos en presentaciones de PowerPoint con IA en C#
Tabla de Contenidos

Convertir documentos en presentaciones de PowerPoint con IA en C# es más que una conversión de formato. Que un informe de 30 páginas se convierta en una presentación de 10 diapositivas significa que alguien leyó el documento completo, decidió qué 90% dejar fuera, reorganizó lo restante en una narrativa y aplicó un diseño visual. Eso es síntesis editorial: la parte que las bibliotecas tradicionales de PowerPoint no hacen.
La parte interesante es qué se sintetiza. En los flujos de trabajo reales, la fuente rara vez es un único archivo. Una revisión comercial trimestral extrae cifras de un libro de Excel, la narrativa de un informe de Word y una factura firmada de un PDF. El valor de un agente de documentos con IA no es «convertir un .docx a .pptx», sino «lee los tres y dame una presentación que cuente la historia a través de ellos». Este artículo muestra la forma más sencilla de hacer exactamente eso con Spire.Agent.Office en C#: entrega al agente todos los documentos más una instrucción, y deja que genere la presentación en una sola llamada.
1. Una Instrucción, Muchas Fuentes
El código tradicional de Spire.Office trata cada formato como una isla. Para construir una presentación a partir de un informe de Word más una hoja de Excel tendrías que:
- Usar
Spire.Docpara abrir el.docxy extraer el texto que quieres. - Usar
Spire.XLSpara leer el libro y calcular las cifras. - Usar
Spire.Pdfsi una fuente es un PDF. - Usar
Spire.Presentationpara crear diapositivas una por una, posicionar cada cuadro de texto y gráfico, y calcular los diseños a mano.
Eso son cuatro superficies de API, un analizador por formato y un motor de diseño manual. En el momento en que la estructura de un documento cambia, el código se rompe.
Spire.Agent.Office reduce todo eso a una sola instrucción. Entregas al agente todos tus archivos fuente y le dices qué historia debe contar la presentación. El agente lee cada formato, decide qué importa entre ellos y produce las diapositivas, sin código de diseño manual.
El modelo mental es un solo paso:

En lugar de escribir un analizador por formato, dejas que el agente lea cada archivo y genere la presentación directamente. Una llamada, una cadena de instrucción, un archivo .pptx.
2. Lo Que Necesitas
- .NET 6+ (el ejemplo apunta a
net10.0). - El paquete NuGet
Spire.Agent.Office. Trae transitivamenteSpire.Doc,Spire.Pdf,Spire.XLSySpire.Presentation, así que no los instalas por separado. - Un
SpireToken(clave de API de Spire.Agent.Office). El agente se comunica con el servicio de IA, por lo que se requiere un token válido para la generación real. - Importaciones de espacios de nombres:
using Spire.Agent.Office.AI; // AIOptions, AIDocumentProcessor, AIResult
using Spire.Agent.Office.Extensions;// presentation.AI(...) extension
using Spire.Presentation; // Presentation
3. La Receta Simple: Una Llamada, Una Presentación
La idea central es de una simplicidad radical: pasa cada documento fuente como adjunto, escribe una instrucción en lenguaje natural y deja que el agente haga todo —leer, comprender, sintetizar, diseñar— en una sola llamada a ExecuteInstruction.
string[] sources = new[]
{
Path.Combine(sampleDir, "AI-Powered Document Processing Report.docx"),
Path.Combine(sampleDir, "purchase-orders.xlsx"),
Path.Combine(sampleDir, "invoice_INV-2026-0815.pdf")
};
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIResult result = processor.ExecuteInstruction(
ppt, instruction, savePath, sources,
autonomousOutput: true, maxTurns: null);
Esa es toda la receta: una llamada a método, una cadena de instrucción. Sin analizadores por formato, sin archivos intermedios, sin código de diseño manual. El agente lee cada formato, decide qué importa entre ellos y escribe la presentación directamente en savePath.
El indicador autonomousOutput: true es lo que hace que esto funcione: le dice al agente que puede escribir archivos (incluido el .pptx final) sin pedir confirmación en cada paso. Establece maxTurns en null (o un número grande) para que el agente tenga suficientes turnos para terminar.
4. Ejemplo Completo: Una Presentación desde Word + PDF + Excel
Aquí está el programa completo. Apúntalo a un archivo o a una carpeta, y construye la presentación en una sola llamada a la IA:
using System;
using System.IO;
using System.Linq;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;
class Program
{
static void Main()
{
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIOptions options = new AIOptions { SpireToken = "sk-YourSpireToken", TimeoutMs = 1000000 };
using (Presentation ppt = new Presentation())
{
AIResult result = ppt.AI(options).ExecuteInstruction(
ppt, instruction, @"C:\Samples\Q3_Review.pptx", ResolveInputs(@"C:\Samples"),
autonomousOutput: true, maxTurns: null);
Console.WriteLine(result.Success ? "Success" : $"Error={result.ErrorMessage}");
}
}
// Point at a file or a folder; a folder is filtered to supported document formats.
static string[] ResolveInputs(string path) =>
File.Exists(path) ? new[] { path }
: Directory.GetFiles(path).Where(IsSupported).OrderBy(f => f).ToArray();
static bool IsSupported(string file) =>
new[] { ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md" }
.Contains(Path.GetExtension(file).ToLowerInvariant());
}
Ese es todo el programa. Aparte de la llamada al agente en sí, el único código es ResolveInputs, que solo decide qué archivos adjuntar: nunca lee ni analiza un documento. La instrucción es la única parte que describe el diseño (tema, número de diapositivas, enfoque); la lectura, la decisión y el diseño son tarea del agente.
Una llamada, cinco diapositivas: la presentación que produce el programa anterior:

5. Cómo Funciona Internamente
AIDocumentProcessorse obtiene de unPresentationmedianteppt.AI(options). El objetoPresentationes el lienzo sobre el que el agente dibuja las diapositivas.AIOptionscontiene la configuración:SpireToken(tu clave) yTimeoutMs(establécelo en un valor grande para documentos reales: el análisis de IA de documentos grandes lleva tiempo).ExecuteInstructionejecuta el agente. Le pasas elPresentation, unainstructionen lenguaje natural, lasavePathpara el.pptxde salida y unstring[]de rutas de archivos fuente (cualquier combinación de Word, PDF, Excel, Markdown). ConautonomousOutput: trueel agente puede escribir archivos —incluida la presentación final— sin pedir confirmación. Devuelve unAIResultconSuccess,ErrorMessageyOutputFiles(todo lo que el agente creó).maxTurnscontrola cuántos pasos del agente se permiten. Establécelo ennullpara no tener límite, o en un número grande (por ejemplo,80) para conjuntos grandes de documentos.GeneratePresentation(sourcePath, instruction, savePath)es el atajo dedicado cuando tienes exactamente un documento fuente. Devuelve unPPTGenerationResult(Success,GeneratedPages,TotalPages,OutputPath,ErrorMessage) en lugar de unAIResult. SusourcePathes un único archivo, que es exactamente por lo que este artículo usa la ruta deExecuteInstructioncon múltiples adjuntos: eso es lo que permite que una presentación se base en varias fuentes en una sola llamada.
¿Por qué no fusionar primero a Markdown? Puedes hacerlo; consulta la Variante B en la Sección 6. Pero para la mayoría de los casos de uso, el enfoque de una sola llamada es más simple, más rápido y produce el mismo resultado. El Markdown intermedio solo es útil cuando quieres inspeccionar o editar el contenido consolidado antes de gastar tokens en la presentación.
6. Control Preciso
La receta anterior es deliberadamente mínima. Cuando necesitas más control, estas variaciones se basan en la misma llamada a ExecuteInstruction.
Ajusta la instrucción. La estructura, la longitud, el tema y los gráficos de la presentación se controlan mediante la cadena de instrucción. Sé explícito:
// e.g. ask for a specific page count, theme, and a chart on a given slide
"Create a 12-slide deck. Light minimalist theme, accent colour #2E5AAC. " +
"Include a bar chart of monthly revenue on slide 5. Keep each slide to one key message.";
Variante A — resume cada fuente tú mismo (más predecible). Si prefieres solicitar cada documento individualmente en lugar de confiar en una única instrucción entre documentos, recorre con un bucle SummarizeDocument, que devuelve el resumen del agente de un solo archivo como un string:
var sb = new StringBuilder();
foreach (var file in sources)
sb.AppendLine($"## {Path.GetFileName(file)}\n\n{processor.SummarizeDocument(file)}");
File.WriteAllText(Path.Combine(workDir, "consolidated.md"), sb.ToString());
Luego pasa el Markdown consolidado a ExecuteInstruction como única fuente: obtienes un resumen separado por documento que puedes leer y editar antes de la generación.
Variante B — consolida en Markdown, luego genera (dos pasos). Para casos en los que quieres un archivo intermedio inspeccionable, divide el trabajo en dos llamadas. El Paso 1 usa ExecuteInstruction en modo autónomo para fusionar todas las fuentes en un único archivo Markdown; el Paso 2 alimenta ese Markdown a ExecuteInstruction de nuevo para construir la presentación:
// Step 1 — merge all documents into one Markdown file
string consolidateInstruction =
"Read all the attached files. Merge their content into a SINGLE Markdown " +
"document named 'output_consolidated.md'. Use one top-level heading per source, " +
"preserve key facts / figures / tables, and drop boilerplate. Output ONLY that file.";
processor.ExecuteInstruction(ppt, consolidateInstruction,
Path.Combine(workDir, "output_consolidated.pptx"), sources,
autonomousOutput: true, maxTurns: 40);
string consolidatedMd = Path.Combine(workDir, "output_consolidated.md");
// Step 2 — generate the deck from the consolidated Markdown
string genInstruction =
"Create a 9-slide quarterly business review deck from the consolidated material. " +
"Professional corporate blue, clear hierarchy, speaker notes per slide.";
processor.ExecuteInstruction(ppt, genInstruction,
Path.Combine(workDir, "QBR_Deck.pptx"), new[] { consolidatedMd },
autonomousOutput: true, maxTurns: 40);
La contrapartida: la Variante B te da un archivo Markdown inspeccionable entre las dos llamadas, lo cual es útil para depurar o cuando la consolidación necesita revisión humana. La receta de una sola llamada de la Sección 3 es el valor predeterminado recomendado porque es más simple y más rápida.
También te puede interesar: Automatizar el Procesamiento de Facturas con un Agente de IA en .NET: otro flujo de trabajo con muchos documentos que entrega al agente una carpeta de archivos y devuelve un documento terminado.
7. Leer y Verificar el Resultado
Después de la generación, vuelve a abrir la presentación y verifícala programáticamente:
using (Presentation deck = new Presentation())
{
deck.LoadFromFile(savePath);
Console.WriteLine($"Total slides: {deck.Slides.Count}");
for (int i = 0; i < deck.Slides.Count; i++)
{
ISlide slide = deck.Slides[i];
string title = slide.Title ?? "(no title)";
Console.WriteLine($"Slide {i + 1}: {title}");
}
}
Aquí también es donde encaja un paso de revisión humana: carga la presentación generada, revisa los títulos y refina la instrucción si algo no está bien.
8. Preguntas Frecuentes
La presentación generada tiene un número incorrecto de diapositivas.
Si una fuente es grande, el análisis puede tardar más que el tiempo de espera predeterminado y el agente se detiene antes de tiempo. Establece AIOptions.TimeoutMs en un valor grande (por ejemplo, 1000000 para unos 17 minutos) e indica un rango de páginas en la instrucción, por ejemplo: «Mantén la presentación final en 8–12 diapositivas».
El agente no incluye contenido de uno de los documentos. Los documentos complejos pueden confundir una sola pasada. Nombra explícitamente en la instrucción el contenido que quieres («incluye los hallazgos del informe de Word», «conserva la tabla de la hoja de Excel»). Si el problema persiste, prueba el enfoque de dos pasos (Variante B en la Sección 6) para inspeccionar el Markdown consolidado antes de la generación.
La llamada falla con «401 Invalid token».
Se rechaza el SpireToken: confirma que es válido, que no ha expirado y que se copió correctamente en AIOptions.SpireToken.
¿Cómo paso varios documentos?
ExecuteInstruction acepta un string[] de rutas de archivos como su cuarto argumento. Pasa cada archivo fuente —cualquier combinación de .docx, .pdf, .xlsx, .md y más— y el agente los lee todos en una sola llamada. Con un único documento fuente, el atajo dedicado es GeneratePresentation(sourcePath, instruction, savePath); la llamada con múltiples adjuntos anterior es lo que hace posible una presentación a partir de varias fuentes.
Obtén tu clave SpireToken Contáctanos para solicitar una clave de API de prueba o comercial, o solicita una licencia temporal. Configúrala en el código:
AIOptions options = new AIOptions();
options.SpireToken = "sk-YourSpireToken";
options.TimeoutMs = 1000000;
Ver También
- Descripción general del producto Spire.Agent.Office: el SDK de agente de IA para Word, Excel, PowerPoint y PDF
- Introducción a Spire.Agent.Office: instala el SDK y ejecuta tu primera instrucción
- Generar PPT a partir de Documentos: el tutorial oficial de la API de presentaciones utilizada en este artículo
- Generación por Lotes de Contratos con Spire.Agent.Office: el mismo patrón basado en instrucciones aplicado a la generación masiva de documentos
Dokumente mit KI in C# in PowerPoint-Präsentationen umwandeln
Inhaltsverzeichnis

Dokumente mit KI in C# in PowerPoint-Präsentationen zu verwandeln ist mehr als eine Formatkonvertierung. Wenn ein 30-seitiger Bericht zu einem 10-Folien-Deck wird, bedeutet das, dass jemand das vollständige Dokument gelesen, entschieden hat, welche 90 % weggelassen werden, das Verbliebene zu einer Erzählung neu geordnet und visuelles Design angewendet hat. Das ist redaktionelle Synthese — der Teil, den traditionelle PowerPoint-Bibliotheken nicht leisten.
Das Interessante ist, was synthetisiert wird. In realen Arbeitsabläufen ist die Quelle selten eine einzelne Datei. Ein vierteljährliches Business Review zieht Zahlen aus einer Excel-Arbeitsmappe, Erzähltext aus einem Word-Bericht und eine signierte Rechnung aus einem PDF. Der Wert eines KI-Dokumentenagenten ist nicht „konvertiere eine .docx in .pptx“ — sondern „lies alle drei und gib mir ein Deck, das die Geschichte über sie hinweg erzählt.“ Dieser Artikel zeigt den einfachsten Weg, genau das mit Spire.Agent.Office in C# zu tun: Übergeben Sie dem Agenten jedes Dokument plus eine Anweisung, und lassen Sie ihn das Deck in einem einzigen Aufruf generieren.
1. Eine Anweisung, viele Quellen
Traditioneller Spire.Office-Code behandelt jedes Format als eine Insel. Um ein Deck aus einem Word-Bericht plus einem Excel-Blatt zu erstellen, würden Sie:
Spire.Docverwenden, um die.docxzu öffnen und den gewünschten Text herauszuziehen.Spire.XLSverwenden, um die Arbeitsmappe zu lesen und die Zahlen zu berechnen.Spire.Pdfverwenden, wenn eine Quelle ein PDF ist.Spire.Presentationverwenden, um Folien einzeln zu erstellen, jedes Textfeld und Diagramm zu positionieren und Layouts von Hand zu berechnen.
Das sind vier API-Oberflächen, ein Parser pro Format und eine manuelle Layout-Engine. In dem Moment, in dem sich die Struktur eines Dokuments ändert, bricht der Code.
Spire.Agent.Office reduziert das auf eine Anweisung. Sie übergeben dem Agenten alle Ihre Quelldateien und sagen ihm, welche Geschichte das Deck erzählen soll. Der Agent liest jedes Format, entscheidet, was über sie hinweg wichtig ist, und erzeugt die Folien — ohne manuellen Layout-Code.
Das mentale Modell ist ein einziger Schritt:

Anstatt einen Parser pro Format zu schreiben, lassen Sie den Agenten jede Datei lesen und das Deck direkt generieren. Ein Aufruf, eine Anweisungszeichenfolge, eine .pptx-Datei.
2. Was Sie benötigen
- .NET 6+ (das Beispiel zielt auf
net10.0). - Das NuGet-Paket
Spire.Agent.Office. Es bringt transitivSpire.Doc,Spire.Pdf,Spire.XLSundSpire.Presentationmit, sodass Sie diese nicht separat installieren müssen. - Ein
SpireToken(Spire.Agent.Office-API-Schlüssel). Der Agent kommuniziert mit dem KI-Dienst, daher ist für die eigentliche Generierung ein gültiges Token erforderlich. - Namespace-Imports:
using Spire.Agent.Office.AI; // AIOptions, AIDocumentProcessor, AIResult
using Spire.Agent.Office.Extensions;// presentation.AI(...) extension
using Spire.Presentation; // Presentation
3. Das einfache Rezept: Ein Aufruf, ein Deck
Die Kernidee ist radikal einfach: Übergeben Sie jedes Quelldokument als Anhang, schreiben Sie eine natürlichsprachliche Anweisung und lassen Sie den Agenten alles erledigen — Lesen, Verstehen, Synthese, Layout — in einem einzigen ExecuteInstruction-Aufruf.
string[] sources = new[]
{
Path.Combine(sampleDir, "AI-Powered Document Processing Report.docx"),
Path.Combine(sampleDir, "purchase-orders.xlsx"),
Path.Combine(sampleDir, "invoice_INV-2026-0815.pdf")
};
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIResult result = processor.ExecuteInstruction(
ppt, instruction, savePath, sources,
autonomousOutput: true, maxTurns: null);
Das ist das ganze Rezept: ein Methodenaufruf, eine Anweisungszeichenfolge. Keine Parser pro Format, keine Zwischendateien, kein manueller Layout-Code. Der Agent liest jedes Format, entscheidet, was über sie hinweg wichtig ist, und schreibt das Deck direkt nach savePath.
Das Flag autonomousOutput: true ist es, was dies ermöglicht — es teilt dem Agenten mit, dass er Dateien schreiben darf (einschließlich der endgültigen .pptx), ohne bei jedem Schritt eine Bestätigung anzufordern. Setzen Sie maxTurns auf null (oder eine große Zahl), damit der Agent genügend Durchläufe hat, um fertig zu werden.
4. Vollständiges Beispiel: Ein Deck aus Word + PDF + Excel
Hier ist das vollständige Programm. Richten Sie es auf eine Datei oder einen Ordner, und es erstellt das Deck in einem einzigen KI-Aufruf:
using System;
using System.IO;
using System.Linq;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;
class Program
{
static void Main()
{
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIOptions options = new AIOptions { SpireToken = "sk-YourSpireToken", TimeoutMs = 1000000 };
using (Presentation ppt = new Presentation())
{
AIResult result = ppt.AI(options).ExecuteInstruction(
ppt, instruction, @"C:\Samples\Q3_Review.pptx", ResolveInputs(@"C:\Samples"),
autonomousOutput: true, maxTurns: null);
Console.WriteLine(result.Success ? "Success" : $"Error={result.ErrorMessage}");
}
}
// Point at a file or a folder; a folder is filtered to supported document formats.
static string[] ResolveInputs(string path) =>
File.Exists(path) ? new[] { path }
: Directory.GetFiles(path).Where(IsSupported).OrderBy(f => f).ToArray();
static bool IsSupported(string file) =>
new[] { ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md" }
.Contains(Path.GetExtension(file).ToLowerInvariant());
}
Das ist das ganze Programm. Abgesehen vom Agent-Aufruf selbst ist der einzige Code ResolveInputs, das lediglich entscheidet, welche Dateien angehängt werden: Es liest oder parst niemals ein Dokument. Die Anweisung ist der einzige Teil, der das Design beschreibt (Thema, Folienanzahl, Fokus); das Lesen, Entscheiden und Layout sind die Aufgabe des Agenten.
Ein Aufruf, fünf Folien — das Deck, das das obige Programm erzeugt:

5. Wie es unter der Haube funktioniert
AIDocumentProcessorwird überppt.AI(options)aus einerPresentationabgerufen. DasPresentation-Objekt ist die Leinwand, auf die der Agent Folien zeichnet.AIOptionsträgt die Konfiguration:SpireToken(Ihr Schlüssel) undTimeoutMs(setzen Sie ihn für echte Dokumente groß — die KI-Analyse großer Dokumente braucht Zeit).ExecuteInstructionführt den Agenten aus. Sie übergeben diePresentation, eine natürlichsprachlicheinstruction, densavePathfür die Ausgabe-.pptxund einstring[]mit Quell-dateipfaden (jede Mischung aus Word, PDF, Excel, Markdown). MitautonomousOutput: truedarf der Agent Dateien schreiben — einschließlich des endgültigen Decks — ohne Bestätigung. Er gibt einAIResultmitSuccess,ErrorMessageundOutputFileszurück (alles, was der Agent erstellt hat).maxTurnssteuert, wie viele Agent-Schritte erlaubt sind. Setzen Sie ihn aufnullfür kein Limit oder auf eine große Zahl (z. B.80) für große Dokumentensätze.GeneratePresentation(sourcePath, instruction, savePath)ist die dedizierte Abkürzung, wenn Sie genau ein Quelldokument haben. Sie gibt einPPTGenerationResultzurück (Success,GeneratedPages,TotalPages,OutputPath,ErrorMessage) statt einesAIResult. IhrsourcePathist eine einzelne Datei, weshalb dieser Artikel genau den Mehrfach-Anhang-PfadExecuteInstructionverwendet: Dieser ermöglicht es, dass ein Deck in einem einzigen Aufruf auf mehrere Quellen zurückgreift.
Warum nicht zuerst in Markdown zusammenführen? Sie können es — siehe Variante B in Abschnitt 6. Aber für die meisten Anwendungsfälle ist der Einzelaufruf-Ansatz einfacher, schneller und liefert dasselbe Ergebnis. Das Zwischen-Markdown ist nur dann nützlich, wenn Sie den zusammengeführten Inhalt überprüfen oder bearbeiten möchten, bevor Sie Token für das Deck ausgeben.
6. Präzise Steuerung
Das obige Rezept ist bewusst minimal gehalten. Wenn Sie mehr Kontrolle benötigen, bauen diese Variationen auf demselben ExecuteInstruction-Aufruf auf.
Stimmen Sie die Anweisung ab. Struktur, Länge, Thema und Diagramme des Decks werden alle durch die Anweisungszeichenfolge gesteuert. Seien Sie explizit:
// e.g. ask for a specific page count, theme, and a chart on a given slide
"Create a 12-slide deck. Light minimalist theme, accent colour #2E5AAC. " +
"Include a bar chart of monthly revenue on slide 5. Keep each slide to one key message.";
Variante A — fassen Sie jede Quelle selbst zusammen (vorhersehbarer). Wenn Sie lieber jedes Dokument einzeln ansprechen möchten, statt einer quellenübergreifenden Anweisung zu vertrauen, durchlaufen Sie SummarizeDocument in einer Schleife, was die Zusammenfassung einer einzelnen Datei durch den Agenten als string zurückgibt:
var sb = new StringBuilder();
foreach (var file in sources)
sb.AppendLine($"## {Path.GetFileName(file)}\n\n{processor.SummarizeDocument(file)}");
File.WriteAllText(Path.Combine(workDir, "consolidated.md"), sb.ToString());
Übergeben Sie dann das zusammengeführte Markdown als einzige Quelle an ExecuteInstruction — Sie erhalten eine separate Zusammenfassung pro Dokument, die Sie vor der Generierung lesen und bearbeiten können.
Variante B — in Markdown zusammenführen und dann generieren (zweistufig). Für Fälle, in denen Sie eine überprüfbare Zwischendatei wünschen, teilen Sie die Arbeit in zwei Aufrufe auf. Schritt 1 verwendet ExecuteInstruction im autonomen Modus, um alle Quellen in eine Markdown-Datei zusammenzuführen; Schritt 2 führt dieses Markdown erneut ExecuteInstruction zu, um das Deck zu erstellen:
// Step 1 — merge all documents into one Markdown file
string consolidateInstruction =
"Read all the attached files. Merge their content into a SINGLE Markdown " +
"document named 'output_consolidated.md'. Use one top-level heading per source, " +
"preserve key facts / figures / tables, and drop boilerplate. Output ONLY that file.";
processor.ExecuteInstruction(ppt, consolidateInstruction,
Path.Combine(workDir, "output_consolidated.pptx"), sources,
autonomousOutput: true, maxTurns: 40);
string consolidatedMd = Path.Combine(workDir, "output_consolidated.md");
// Step 2 — generate the deck from the consolidated Markdown
string genInstruction =
"Create a 9-slide quarterly business review deck from the consolidated material. " +
"Professional corporate blue, clear hierarchy, speaker notes per slide.";
processor.ExecuteInstruction(ppt, genInstruction,
Path.Combine(workDir, "QBR_Deck.pptx"), new[] { consolidatedMd },
autonomousOutput: true, maxTurns: 40);
Der Kompromiss: Variante B gibt Ihnen eine überprüfbare Markdown-Datei zwischen den beiden Aufrufen, was für das Debugging oder wenn die Zusammenführung eine menschliche Überprüfung erfordert, nützlich ist. Das Einzelaufruf-Rezept in Abschnitt 3 ist die empfohlene Standardeinstellung, weil es einfacher und schneller ist.
Das könnte Sie auch interessieren: Rechnungsverarbeitung mit einem KI-Agenten in .NET automatisieren — ein weiterer dokumentenlastiger Workflow, der dem Agenten einen Ordner mit Dateien übergibt und ein fertiges Dokument zurückbekommt.
7. Ergebnis zurücklesen und überprüfen
Öffnen Sie das Deck nach der Generierung erneut und überprüfen Sie es programmatisch:
using (Presentation deck = new Presentation())
{
deck.LoadFromFile(savePath);
Console.WriteLine($"Total slides: {deck.Slides.Count}");
for (int i = 0; i < deck.Slides.Count; i++)
{
ISlide slide = deck.Slides[i];
string title = slide.Title ?? "(no title)";
Console.WriteLine($"Slide {i + 1}: {title}");
}
}
Hier passt auch ein menschlicher Überprüfungsschritt hinein: Laden Sie das generierte Deck, prüfen Sie die Titel und verfeinern Sie die Anweisung, wenn etwas nicht stimmt.
8. FAQ
Das generierte Deck hat die falsche Anzahl von Folien.
Wenn eine Quelle groß ist, kann die Analyse länger dauern als das Standard-Timeout und der Agent stoppt vorzeitig. Setzen Sie AIOptions.TimeoutMs auf einen großen Wert (z. B. 1000000 für ~17 Minuten) und geben Sie in der Anweisung einen Seitenbereich an, z. B. „Halten Sie das endgültige Deck auf 8–12 Folien.“
Der Agent nimmt keinen Inhalt aus einem der Dokumente auf. Komplexe Dokumente können einen einzelnen Durchlauf verwirren. Benennen Sie den gewünschten Inhalt explizit in der Anweisung („beziehen Sie die Erkenntnisse aus dem Word-Bericht ein“, „bewahren Sie die Tabelle aus dem Excel-Blatt“). Wenn das Problem weiterhin besteht, versuchen Sie den zweistufigen Ansatz (Variante B in Abschnitt 6), um das zusammengeführte Markdown vor der Generierung zu überprüfen.
Der Aufruf schlägt mit "401 Invalid token" fehl.
Das SpireToken wird abgelehnt — stellen Sie sicher, dass es gültig, nicht abgelaufen und korrekt in AIOptions.SpireToken kopiert ist.
Wie übergebe ich mehrere Dokumente?
ExecuteInstruction akzeptiert ein string[] mit Dateipfaden als viertes Argument. Übergeben Sie jede Quelldatei — jede Mischung aus .docx, .pdf, .xlsx, .md und mehr — und der Agent liest sie alle in einem Aufruf. Bei einem einzigen Quelldokument ist die dedizierte Abkürzung GeneratePresentation(sourcePath, instruction, savePath); der obige Mehrfach-Anhang-Aufruf ist es, was ein Deck aus mehreren Quellen möglich macht.
Holen Sie sich Ihren SpireToken-Schlüssel Kontaktieren Sie uns, um einen Test- oder kommerziellen API-Schlüssel anzufordern, oder beantragen Sie eine temporäre Lizenz. Konfigurieren Sie ihn im Code:
AIOptions options = new AIOptions();
options.SpireToken = "sk-YourSpireToken";
options.TimeoutMs = 1000000;
Siehe auch
- Spire.Agent.Office Produktübersicht — das KI-Agent-SDK für Word, Excel, PowerPoint und PDF
- Erste Schritte mit Spire.Agent.Office — installieren Sie das SDK und führen Sie Ihre erste Anweisung aus
- PPT aus Dokumenten generieren — das offizielle Tutorial für die in diesem Artikel verwendete Präsentations-API
- Batch-Vertragsgenerierung mit Spire.Agent.Office — dasselbe anweisungsgesteuerte Muster, angewendet auf die Massendokumentengenerierung
Превратите документы в презентации PowerPoint с помощью ИИ на C#
Содержание

Превращение документов в презентации PowerPoint с помощью ИИ на C# — это больше, чем просто конвертация форматов. Когда отчёт на 30 страниц становится презентацией из 10 слайдов, это означает, что кто-то прочитал весь документ, решил, какие 90% опустить, реорганизовал оставшееся в связное повествование и применил визуальный дизайн. Это редакторский синтез — то, чего традиционные библиотеки для работы с PowerPoint не делают.
Интересно то, что именно синтезируется. В реальных рабочих процессах источником редко бывает один файл. Квартальный обзор бизнеса берёт цифры из книги Excel, повествование из отчёта Word и подписанный счёт из PDF. Ценность ИИ-агента для работы с документами не в том, чтобы «конвертировать один .docx в .pptx», а в том, чтобы «прочитать все три и дать мне одну презентацию, которая рассказывает историю по ним всем». В этой статье показан самый простой способ сделать именно это с помощью Spire.Agent.Office на C#: передайте агенту все документы плюс одну инструкцию — и пусть он сгенерирует презентацию за один вызов.
1. Одна инструкция, множество источников
Традиционный код Spire.Office рассматривает каждый формат как отдельный остров. Чтобы построить презентацию из отчёта Word вместе с листом Excel, вам пришлось бы:
- Использовать
Spire.Doc, чтобы открыть.docxи извлечь нужный текст. - Использовать
Spire.XLS, чтобы прочитать книгу и вычислить показатели. - Использовать
Spire.Pdf, если источник — это PDF. - Использовать
Spire.Presentation, чтобы создавать слайды один за другим, размещать каждый текстовый блок и диаграмму и рассчитывать макеты вручную.
Это четыре программных интерфейса, парсер для каждого формата и ручной движок вёрстки. Как только структура документа меняется, код ломается.
Spire.Agent.Office сводит всё это к одной инструкции. Вы передаёте агенту все исходные файлы и говорите, какую историю должна рассказать презентация. Агент читает каждый формат, решает, что важно в них всех, и создаёт слайды — без ручного кода вёрстки.
Ментальная модель — один шаг:

Вместо написания парсера для каждого формата вы позволяете агенту прочитать каждый файл и напрямую сгенерировать презентацию. Один вызов, одна строка инструкции, один файл .pptx.
2. Что вам понадобится
- .NET 6+ (в примере используется
net10.0). - NuGet-пакет
Spire.Agent.Office. Он транзитивно подключаетSpire.Doc,Spire.Pdf,Spire.XLSиSpire.Presentation, поэтому отдельно устанавливать их не нужно. SpireToken(ключ API Spire.Agent.Office). Агент взаимодействует со службой ИИ, поэтому для фактической генерации требуется действительный токен.- Импорты пространств имён:
using Spire.Agent.Office.AI; // AIOptions, AIDocumentProcessor, AIResult
using Spire.Agent.Office.Extensions;// presentation.AI(...) extension
using Spire.Presentation; // Presentation
3. Простой рецепт: один вызов, одна презентация
Основная идея — радикальная простота: передайте каждый исходный документ как вложение, напишите одну инструкцию на естественном языке и позвольте агенту сделать всё — чтение, понимание, синтез, вёрстку — за один вызов ExecuteInstruction.
string[] sources = new[]
{
Path.Combine(sampleDir, "AI-Powered Document Processing Report.docx"),
Path.Combine(sampleDir, "purchase-orders.xlsx"),
Path.Combine(sampleDir, "invoice_INV-2026-0815.pdf")
};
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIResult result = processor.ExecuteInstruction(
ppt, instruction, savePath, sources,
autonomousOutput: true, maxTurns: null);
Вот и весь рецепт: один вызов метода, одна строка инструкции. Никаких парсеров для отдельных форматов, никаких промежуточных файлов, никакого ручного кода вёрстки. Агент читает каждый формат, решает, что важно в них всех, и записывает презентацию напрямую в savePath.
Флаг autonomousOutput: true — то, что делает это возможным: он говорит агенту, что тот может записывать файлы (включая итоговый .pptx), не запрашивая подтверждения на каждом шаге. Установите maxTurns в null (или большое число), чтобы у агента было достаточно шагов для завершения.
4. Полный пример: одна презентация из Word + PDF + Excel
Вот полная программа. Укажите ей файл или папку — и она построит презентацию за один вызов ИИ:
using System;
using System.IO;
using System.Linq;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;
class Program
{
static void Main()
{
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIOptions options = new AIOptions { SpireToken = "sk-YourSpireToken", TimeoutMs = 1000000 };
using (Presentation ppt = new Presentation())
{
AIResult result = ppt.AI(options).ExecuteInstruction(
ppt, instruction, @"C:\Samples\Q3_Review.pptx", ResolveInputs(@"C:\Samples"),
autonomousOutput: true, maxTurns: null);
Console.WriteLine(result.Success ? "Success" : $"Error={result.ErrorMessage}");
}
}
// Point at a file or a folder; a folder is filtered to supported document formats.
static string[] ResolveInputs(string path) =>
File.Exists(path) ? new[] { path }
: Directory.GetFiles(path).Where(IsSupported).OrderBy(f => f).ToArray();
static bool IsSupported(string file) =>
new[] { ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md" }
.Contains(Path.GetExtension(file).ToLowerInvariant());
}
Вот и вся программа. Помимо самого вызова агента, единственный код — это ResolveInputs, который лишь решает, какие файлы прикрепить: он никогда не читает и не разбирает документ. Инструкция — единственная часть, описывающая дизайн (тема, количество слайдов, акцент); чтение, принятие решений и вёрстка — задача агента.
Один вызов, пять слайдов — презентация, которую создаёт приведённая выше программа:

5. Как это работает изнутри
AIDocumentProcessorполучается изPresentationчерезppt.AI(options). ОбъектPresentation— это холст, на который агент рисует слайды.AIOptionsсодержит конфигурацию:SpireToken(ваш ключ) иTimeoutMs(задайте большое значение для реальных документов — анализ больших документов с помощью ИИ занимает время).ExecuteInstructionзапускает агента. Вы передаётеPresentation, инструкцию на естественном языкеinstruction,savePathдля выходного.pptxиstring[]путей к исходным файлам (любая комбинация Word, PDF, Excel, Markdown). ПриautonomousOutput: trueагент может записывать файлы — включая итоговую презентацию — не запрашивая подтверждения. Он возвращаетAIResultс полямиSuccess,ErrorMessageиOutputFiles(всё, что создал агент).maxTurnsуправляет количеством разрешённых шагов агента. Установите его вnullдля отсутствия ограничения или задайте большое число (например,80) для больших наборов документов.GeneratePresentation(sourcePath, instruction, savePath)— это специальный короткий путь, когда у вас ровно один исходный документ. Он возвращаетPPTGenerationResult(Success,GeneratedPages,TotalPages,OutputPath,ErrorMessage) вместоAIResult. ЕгоsourcePath— это один файл, и именно поэтому в этой статье используется путьExecuteInstructionс несколькими вложениями: именно он позволяет одной презентации опираться на несколько источников за один вызов.
Почему бы сначала не объединить всё в Markdown? Вы можете — см. Вариант B в разделе 6. Но для большинства случаев подход с одним вызовом проще, быстрее и даёт тот же результат. Промежуточный Markdown полезен только тогда, когда вы хотите просмотреть или отредактировать объединённое содержимое перед тем, как тратить токены на презентацию.
6. Точный контроль
Приведённый выше рецепт намеренно минимален. Когда нужен больший контроль, эти вариации строятся на том же вызове ExecuteInstruction.
Настройте инструкцию. Структура, длина, тема и диаграммы презентации управляются строкой инструкции. Будьте конкретны:
// e.g. ask for a specific page count, theme, and a chart on a given slide
"Create a 12-slide deck. Light minimalist theme, accent colour #2E5AAC. " +
"Include a bar chart of monthly revenue on slide 5. Keep each slide to one key message.";
Вариант A — резюмируйте каждый источник самостоятельно (более предсказуемо). Если вы предпочитаете давать запрос по каждому документу по отдельности, а не доверять одной междокументной инструкции, используйте в цикле SummarizeDocument, который возвращает резюме агента по одному файлу в виде string:
var sb = new StringBuilder();
foreach (var file in sources)
sb.AppendLine($"## {Path.GetFileName(file)}\n\n{processor.SummarizeDocument(file)}");
File.WriteAllText(Path.Combine(workDir, "consolidated.md"), sb.ToString());
Затем передайте объединённый Markdown в ExecuteInstruction как единственный источник — вы получите отдельное резюме по каждому документу, которое можно прочитать и отредактировать перед генерацией.
Вариант B — объединить в Markdown, затем сгенерировать (два шага). В случаях, когда нужен промежуточный файл для просмотра, разделите работу на два вызова. Шаг 1 использует ExecuteInstruction в автономном режиме, чтобы объединить все источники в один файл Markdown; Шаг 2 подаёт этот Markdown снова в ExecuteInstruction для построения презентации:
// Step 1 — merge all documents into one Markdown file
string consolidateInstruction =
"Read all the attached files. Merge their content into a SINGLE Markdown " +
"document named 'output_consolidated.md'. Use one top-level heading per source, " +
"preserve key facts / figures / tables, and drop boilerplate. Output ONLY that file.";
processor.ExecuteInstruction(ppt, consolidateInstruction,
Path.Combine(workDir, "output_consolidated.pptx"), sources,
autonomousOutput: true, maxTurns: 40);
string consolidatedMd = Path.Combine(workDir, "output_consolidated.md");
// Step 2 — generate the deck from the consolidated Markdown
string genInstruction =
"Create a 9-slide quarterly business review deck from the consolidated material. " +
"Professional corporate blue, clear hierarchy, speaker notes per slide.";
processor.ExecuteInstruction(ppt, genInstruction,
Path.Combine(workDir, "QBR_Deck.pptx"), new[] { consolidatedMd },
autonomousOutput: true, maxTurns: 40);
Компромисс: Вариант B даёт вам просматриваемый файл Markdown между двумя вызовами, что полезно для отладки или когда объединение требует проверки человеком. Рецепт с одним вызовом из раздела 3 — рекомендуемый вариант по умолчанию, потому что он проще и быстрее.
Вам также может понравиться: Автоматизация обработки счетов с помощью ИИ-агента в .NET — ещё один рабочий процесс с большим объёмом документов, в котором агенту передаётся папка с файлами, а на выходе получается готовый документ.
7. Чтение и проверка результата
После генерации снова откройте презентацию и проверьте её программно:
using (Presentation deck = new Presentation())
{
deck.LoadFromFile(savePath);
Console.WriteLine($"Total slides: {deck.Slides.Count}");
for (int i = 0; i < deck.Slides.Count; i++)
{
ISlide slide = deck.Slides[i];
string title = slide.Title ?? "(no title)";
Console.WriteLine($"Slide {i + 1}: {title}");
}
}
Здесь же уместен и этап проверки человеком: загрузите сгенерированную презентацию, проверьте заголовки и уточните инструкцию, если что-то не так.
8. Часто задаваемые вопросы
В сгенерированной презентации неправильное количество слайдов.
Если источник большой, анализ может занять больше времени, чем тайм-аут по умолчанию, и агент завершится досрочно. Установите AIOptions.TimeoutMs в большое значение (например, 1000000 — примерно 17 минут) и укажите диапазон страниц в инструкции, например: «Оставьте итоговую презентацию в пределах 8–12 слайдов».
Агент не включает содержимое одного из документов. Сложные документы могут запутать один проход. Явно назовите нужное содержимое в инструкции («включите выводы из отчёта Word», «сохраните таблицу из листа Excel»). Если проблема сохраняется, попробуйте двухшаговый подход (Вариант B в разделе 6), чтобы просмотреть объединённый Markdown перед генерацией.
Вызов завершается ошибкой «401 Invalid token».
SpireToken отклонён — убедитесь, что он действителен, не истёк и правильно скопирован в AIOptions.SpireToken.
Как передать несколько документов?
ExecuteInstruction принимает string[] путей к файлам в качестве четвёртого аргумента. Передайте все исходные файлы — любую комбинацию .docx, .pdf, .xlsx, .md и других — и агент прочитает их все за один вызов. При одном исходном документе специальный короткий путь — GeneratePresentation(sourcePath, instruction, savePath); именно вызов с несколькими вложениями, приведённый выше, позволяет создать одну презентацию из нескольких источников.
Получите свой ключ SpireToken Свяжитесь с нами, чтобы запросить пробный или коммерческий ключ API, либо подайте заявку на временную лицензию. Настройте его в коде:
AIOptions options = new AIOptions();
options.SpireToken = "sk-YourSpireToken";
options.TimeoutMs = 1000000;
См. также
- Обзор продукта Spire.Agent.Office — SDK ИИ-агента для Word, Excel, PowerPoint и PDF
- Начало работы со Spire.Agent.Office — установите SDK и выполните свою первую инструкцию
- Генерация PPT из документов — официальное руководство по API презентаций, использованному в этой статье
- Пакетная генерация договоров со Spire.Agent.Office — тот же подход на основе инструкций, применённый к массовой генерации документов
Turn Documents into PowerPoint Presentations with AI in C#

Turning documents into PowerPoint presentations with AI in C# is more than format conversion. A 30-page report becoming a 10-slide deck means someone read the full document, decided which 90% to leave out, reorganized what remained into a narrative, and applied visual design. That is editorial synthesis — the part traditional PowerPoint libraries do not do.
The interesting part is what gets synthesized. In real workflows the source is rarely a single file. A quarterly business review pulls numbers from an Excel workbook, narrative from a Word report, and a signature invoice from a PDF. The value of an AI document agent is not "convert one .docx to .pptx" — it is "read all three, and give me one deck that tells the story across them." This article shows the simplest way to do exactly that with Spire.Agent.Office in C#: hand the agent every document plus one instruction, and let it generate the deck in a single call.
1. One Instruction, Many Sources
Traditional Spire.Office code treats each format as an island. To build a deck from a Word report plus an Excel sheet you would:
- Use
Spire.Docto open the.docxand pull the text you want. - Use
Spire.XLSto read the workbook and compute the figures. - Use
Spire.Pdfif a source is a PDF. - Use
Spire.Presentationto create slides one by one, position every text box and chart, and calculate layouts by hand.
That is four API surfaces, a parser per format, and a manual layout engine. The moment a document's structure changes, the code breaks.
Spire.Agent.Office collapses that into one instruction. You hand the agent all your source files and tell it what story the deck should tell. The agent reads each format, decides what matters across them, and produces the slides — no manual layout code.
The mental model is a single step:

Instead of writing a parser per format, you let the agent read every file and generate the deck directly. One call, one instruction string, one .pptx file.
2. What You Need
- .NET 6+ (the sample targets
net10.0). - The
Spire.Agent.OfficeNuGet package. It transitively brings inSpire.Doc,Spire.Pdf,Spire.XLS, andSpire.Presentation, so you do not install them separately. - A
SpireToken(Spire.Agent.Office API key). The agent talks to the AI service, so a valid token is required for the actual generation. - Namespace imports:
using Spire.Agent.Office.AI; // AIOptions, AIDocumentProcessor, AIResult
using Spire.Agent.Office.Extensions;// presentation.AI(...) extension
using Spire.Presentation; // Presentation
3. The Simple Recipe: One Call, One Deck
The core idea is radical simplicity: pass every source document as attachments, write one natural-language instruction, and let the agent do everything — reading, understanding, synthesis, layout — in a single ExecuteInstruction call.
string[] sources = new[]
{
Path.Combine(sampleDir, "AI-Powered Document Processing Report.docx"),
Path.Combine(sampleDir, "purchase-orders.xlsx"),
Path.Combine(sampleDir, "invoice_INV-2026-0815.pdf")
};
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIResult result = processor.ExecuteInstruction(
ppt, instruction, savePath, sources,
autonomousOutput: true, maxTurns: null);
That is the whole recipe: one method call, one instruction string. No per-format parsers, no intermediate files, no manual layout code. The agent reads each format, decides what matters across them, and writes the deck directly to savePath.
The autonomousOutput: true flag is what makes this work — it tells the agent it may write files (including the final .pptx) without asking for confirmation at each step. Set maxTurns to null (or a large number) so the agent has enough turns to finish.
4. Full Example: One Deck from Word + PDF + Excel
Here is the complete program. Point it at a file or a folder, and it builds the deck in a single AI call:
using System;
using System.IO;
using System.Linq;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;
class Program
{
static void Main()
{
string instruction =
"Create a concise Q3 business review summary from the provided documents. " +
"1. Use a clean blue theme; " +
"2. Summarize business review, project status, customer pilot research, and platform performance; " +
"3. Keep each slide focused on one key finding or metric; " +
"4. Generate 5 slides.";
AIOptions options = new AIOptions { SpireToken = "sk-YourSpireToken", TimeoutMs = 1000000 };
using (Presentation ppt = new Presentation())
{
AIResult result = ppt.AI(options).ExecuteInstruction(
ppt, instruction, @"C:\Samples\Q3_Review.pptx", ResolveInputs(@"C:\Samples"),
autonomousOutput: true, maxTurns: null);
Console.WriteLine(result.Success ? "Success" : $"Error={result.ErrorMessage}");
}
}
// Point at a file or a folder; a folder is filtered to supported document formats.
static string[] ResolveInputs(string path) =>
File.Exists(path) ? new[] { path }
: Directory.GetFiles(path).Where(IsSupported).OrderBy(f => f).ToArray();
static bool IsSupported(string file) =>
new[] { ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md" }
.Contains(Path.GetExtension(file).ToLowerInvariant());
}
That is the whole program. Apart from the Agent call itself, the only code is ResolveInputs, which just decides which files to attach: it never reads or parses a document. The instruction is the only part that describes the design (theme, slide count, focus); the reading, deciding, and layout are the agent's job.
One call, five slides — the deck the program above produces:

5. How It Works Under the Hood
AIDocumentProcessoris obtained from aPresentationviappt.AI(options). ThePresentationobject is the canvas the agent draws slides onto.AIOptionscarries the configuration:SpireToken(your key) andTimeoutMs(set it large for real documents — AI analysis of big docs takes time).ExecuteInstructionruns the agent. You pass thePresentation, a natural-languageinstruction, thesavePathfor the output.pptx, and astring[]of source file paths (any mix of Word, PDF, Excel, Markdown). WithautonomousOutput: truethe agent may write files — including the final deck — without asking for confirmation. It returns anAIResultwithSuccess,ErrorMessage, andOutputFiles(everything the agent created).maxTurnscontrols how many agent steps are allowed. Set it tonullfor no limit, or a large number (e.g.80) for big document sets.GeneratePresentation(sourcePath, instruction, savePath)is the dedicated shortcut when you have exactly one source document. It returns aPPTGenerationResult(Success,GeneratedPages,TotalPages,OutputPath,ErrorMessage) instead of anAIResult. ItssourcePathis a single file, which is exactly why this article uses the multi-attachmentExecuteInstructionpath: that is what lets one deck draw on several sources in a single call.
Why not merge into Markdown first? You can — see Variant B in Section 6. But for most use cases the single-call approach is simpler, faster, and produces the same result. The intermediate Markdown is useful only when you want to inspect or edit the consolidated content before spending tokens on the deck.
6. Precise Control
The recipe above is deliberately minimal. When you need more control, these variations build on the same ExecuteInstruction call.
Tune the instruction. The deck's structure, length, theme, and charts are all controlled by the instruction string. Be explicit:
// e.g. ask for a specific page count, theme, and a chart on a given slide
"Create a 12-slide deck. Light minimalist theme, accent colour #2E5AAC. " +
"Include a bar chart of monthly revenue on slide 5. Keep each slide to one key message.";
Variant A — summarize each source yourself (more predictable). If you would rather prompt each document individually than trust one cross-document instruction, loop SummarizeDocument, which returns the agent's summary of a single file as a string:
var sb = new StringBuilder();
foreach (var file in sources)
sb.AppendLine($"## {Path.GetFileName(file)}\n\n{processor.SummarizeDocument(file)}");
File.WriteAllText(Path.Combine(workDir, "consolidated.md"), sb.ToString());
Then pass the consolidated Markdown to ExecuteInstruction as the sole source — you get a separate, per-document summary you can read and edit before generation.
Variant B — consolidate into Markdown, then generate (two-step). For cases where you want an inspectable intermediate file, split the work into two calls. Step 1 uses ExecuteInstruction in autonomous mode to merge all sources into one Markdown file; Step 2 feeds that Markdown to ExecuteInstruction again to build the deck:
// Step 1 — merge all documents into one Markdown file
string consolidateInstruction =
"Read all the attached files. Merge their content into a SINGLE Markdown " +
"document named 'output_consolidated.md'. Use one top-level heading per source, " +
"preserve key facts / figures / tables, and drop boilerplate. Output ONLY that file.";
processor.ExecuteInstruction(ppt, consolidateInstruction,
Path.Combine(workDir, "output_consolidated.pptx"), sources,
autonomousOutput: true, maxTurns: 40);
string consolidatedMd = Path.Combine(workDir, "output_consolidated.md");
// Step 2 — generate the deck from the consolidated Markdown
string genInstruction =
"Create a 9-slide quarterly business review deck from the consolidated material. " +
"Professional corporate blue, clear hierarchy, speaker notes per slide.";
processor.ExecuteInstruction(ppt, genInstruction,
Path.Combine(workDir, "QBR_Deck.pptx"), new[] { consolidatedMd },
autonomousOutput: true, maxTurns: 40);
The trade-off: Variant B gives you an inspectable Markdown file between the two calls, which is useful for debugging or when the consolidation needs human review. The single-call recipe in Section 3 is the recommended default because it is simpler and faster.
You may also like: Automate Invoice Processing with an AI Agent in .NET — another document-heavy workflow that hands the agent a folder of files and gets a finished document back.
7. Read Back and Inspect the Result
After generation, reopen the deck and verify it programmatically:
using (Presentation deck = new Presentation())
{
deck.LoadFromFile(savePath);
Console.WriteLine($"Total slides: {deck.Slides.Count}");
for (int i = 0; i < deck.Slides.Count; i++)
{
ISlide slide = deck.Slides[i];
string title = slide.Title ?? "(no title)";
Console.WriteLine($"Slide {i + 1}: {title}");
}
}
This is also where a human review step fits: load the generated deck, check the titles, and refine the instruction if something is off.
8. FAQ
The generated deck has the wrong number of slides.
If a source is large, analysis can take longer than the default timeout and the agent stops early. Set AIOptions.TimeoutMs to a large value (e.g. 1000000 for ~17 minutes) and state a page range in the instruction, e.g. "Keep the final deck to 8–12 slides."
The agent does not include content from one of the documents. Complex documents can confuse a single pass. Name the content you want explicitly in the instruction ("include the findings from the Word report", "preserve the table in the Excel sheet"). If the problem persists, try the two-step approach (Variant B in Section 6) to inspect the consolidated Markdown before generation.
The call fails with "401 Invalid token".
The SpireToken is rejected — confirm it is valid, not expired, and copied correctly into AIOptions.SpireToken.
How do I pass multiple documents?
ExecuteInstruction accepts a string[] of file paths as its fourth argument. Pass every source file — any mix of .docx, .pdf, .xlsx, .md, and more — and the agent reads them all in one call. With a single source document, the dedicated shortcut is GeneratePresentation(sourcePath, instruction, savePath); the multi-attachment call above is what makes one deck from several sources possible.
Get Your SpireToken Key Contact us to request a trial or commercial API key, or apply for a temporary license. Configure it in code:
AIOptions options = new AIOptions();
options.SpireToken = "sk-YourSpireToken";
options.TimeoutMs = 1000000;
See Also
- Spire.Agent.Office product overview — the AI agent SDK for Word, Excel, PowerPoint, and PDF
- Getting Started with Spire.Agent.Office — install the SDK and run your first instruction
- Generate PPT from Documents — the official tutorial for the presentation API used in this article
- Batch Contract Generation with Spire.Agent.Office — the same instruction-driven pattern applied to bulk document generation
Convertendo Imagens para PDF em JavaScript (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

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:
JavaScript(React)에서 이미지를 PDF로 변환하기

폴더에 있는 이미지들은 다른 사람에게 건네기 번거롭습니다. PDF는 파일 하나로 어디서든 열리고, 예측 가능하게 인쇄되며 — 사람들이 실제로 신경 쓰는 부분인 — 고정된 순서를 유지합니다. 그래서 스캔한 페이지, 사진 세트, 영수증 사진, 내보낸 디자인 프레임은 어디로 보내기 전에 PDF로 조립되는 경우가 많습니다.
브라우저에서 그 PDF를 만드는 것은 PDF를 이미지로 렌더링하는 것과는 다른 문제입니다. 이미 존재하는 것을 디코딩하는 것이 아니라, 일반적으로 문서 형식이 대신 내려 주는 결정을 직접 내려야 합니다: 페이지는 얼마나 커야 하는지, 이미지는 페이지 어디에 놓이는지, 이미지가 페이지와 다른 모양일 때 어떻게 되는지, 페이지는 어떤 순서로 나오는지.
Spire.PDF for JavaScript는 페이지 캔버스를 통해 이러한 결정을 드러냅니다. 페이지를 추가하고, 이미지를 로드한 뒤, 계산한 크기로 해당 페이지에 그린 다음, 저장합니다. 모든 것이 WebAssembly를 통해 클라이언트 측에서 실행되므로 이미지는 업로드되지 않습니다.
PDF에 이미지가 들어가는 이유
이 시나리오들은 한 가지 형태를 공유합니다: 하나의 문서처럼 동작해야 하는 여러 이미지입니다.
- 스캔하거나 촬영한 여러 페이지 문서 — 계약서를 페이지별로 촬영해 파일로 보관하거나 이메일로 보낼 수 있는 단일 파일로 다시 조립한 경우.
- 사진 세트와 포트폴리오 — 누군가 선택한 순서대로 페이지당 이미지 하나.
- 영수증 및 경비 보고서 — 회계에서 하나의 첨부 파일로 원하는 휴대폰 사진 여러 장.
- 디자인 및 다이어그램 내보내기 — 도구에서 내보낸 프레임을 검토 가능한 무언가로 모은 경우.
각 경우에서 PDF는 사실 PDF 형식 자체에 관한 것이 아닙니다. 이미지 더미에서 안정적이고 단일 파일이며 순서가 있는 결과물을 얻는 것이 핵심입니다.
사전 요구 사항
이 안내는 Spire.PDF for JavaScript가 설치되고 WASM 모듈이 초기화된 React 프로젝트를 가정합니다. 설정 방법은 React 프로젝트에 Spire.PDF for JavaScript 통합하기를 참조하세요.
필요한 항목:
- VFS에 로드된 하나 이상의 이미지 파일
window.wasmModule.spirepdf에서 접근 가능한 WASM 모듈
이미지 하나, 페이지 하나
기본 흐름은 네 단계입니다: 문서를 만들고, 페이지를 추가하고, 이미지를 로드하고, 그립니다. 흥미로운 부분은 그리기입니다 — 페이지에서 이미지가 얼마나 커야 하는지 결정해야 합니다.
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;
PdfImage.FromFile로 이미지를 로드하고 Canvas.DrawImage로 그린 후 생성된 PDF 문서

크기 계산을 헬퍼로 분리하세요. 아래의 다른 모든 레시피에서 재사용하게 될 코드 조각이기 때문입니다. 계산 방식: page.Canvas.ClientSize — 페이지에서 그릴 수 있는 영역(포인트 단위) — 를 예산으로 삼고, 이미지의 자연스러운 PhysicalDimension과 비교한 뒤, 가로세로 비율이 유지되도록 두 치수를 하나의 비율로 나눕니다. 버그가 생기기 쉬운 부분이 정확히 한 곳에 있도록 함수로 감싸 두세요:
// 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 };
}
이제 위 예제의 그리기 호출은 세 줄로 줄어들고, "contain 또는 cover" 결정은 수학에서 빠져나와 함수 이름으로 이동합니다:
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는 "contain" 선택입니다 — 더 제한적인 축을 기준으로 크기를 조정하여 전체 이미지가 보이게 유지합니다. 대신 페이지를 채우고 넘치는 부분을 잘라내고 싶다면 Math.min으로 바꾸세요. 크기 조정 섹션에서 fitCover 대응 버전과 여백 변형을 확인할 수 있습니다.
여러 이미지, 하나의 문서
페이지당 이미지 하나는 이미지마다 Pages.Add() 하나와 DrawImage 하나를 의미합니다. 파일 이름 배열을 반복하면 배열 순서가 페이지 순서가 됩니다 — 사용자가 방금 썸네일을 순서대로 드래그해 놓았을 때 정확히 원하는 동작입니다.
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);
};
실용적인 참고 사항 두 가지. 각 페이지의 크기가 독립적으로 조정되므로 서로 다른 크기의 이미지도 문제없습니다 — 가로 사진과 세로 스캔이 특별한 처리 없이 같은 문서에 들어갈 수 있습니다. 또한 전체 문서가 SaveToFile 전에 메모리에서 만들어지므로 이미지가 몇 장 들어갔든 다운로드는 마지막에 한 번만 발생합니다.
이런 글도 좋아할 수 있습니다: 이미지를 조립하는 것은 렌더링의 반대입니다. 이미 PDF가 있고 각 페이지를 그림으로 원한다면, JavaScript(React)에서 PDF 페이지를 이미지로 변환하는 방법을 참조하세요.
크기 조정: 페이지에 이미지 맞추기
페이지에 이미지를 넣는 합리적인 방법은 두 가지이며, 어느 쪽을 원할지는 이미지의 일부가 잘려도 괜찮은지에 달려 있습니다.
Contain (Math.max) |
Cover (Math.min) |
|
|---|---|---|
| 수행하는 작업 | 전체 이미지가 들어갈 때까지 크기를 조정합니다 | 페이지가 채워질 때까지 크기를 조정합니다 |
| 전체 이미지 표시 | 예 | 아니요 — 넘치는 부분이 잘립니다 |
| 빈 공간 | 한 축에 생길 수 있음 | 없음 |
| 적합한 용도 | 스캔, 문서, 완전하게 유지되어야 하는 모든 것 | 전면 인쇄 사진, 표지 페이지, 슬라이드 |
첫 번째 예제는 contain — fitContain 헬퍼 — 를 사용합니다. cover로 전환하는 것은 해당 함수의 거울상입니다: Math.max 대신 Math.min을 사용하고, 페이지를 채우며 넘치는 부분은 캔버스가 잘라내도록 하고, 중앙 정렬 오프셋은 음수가 됩니다:
// 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
};
}
가장자리까지 꽉 찬 출력 대신 눈에 보이는 여백을 원한다면 이미지가 아니라 사용 가능한 영역을 줄이세요 — 여백을 조정한 예산을 같은 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 });
PhysicalDimension에 대해 알아 둘 만한 점: 이 값은 이미지의 물리적 크기를 반영하며, 항상 픽셀 크기와 같지는 않습니다. 다른 DPI 태그로 저장된 4000 × 3000 사진은 예상과 다른 숫자를 보고할 수 있습니다. 그래서 위의 비율 기반 접근 방식이 픽셀 치수를 하드코딩하는 것보다 더 안전합니다 — 이미지에 어떤 태그가 붙었든 작동합니다.
메모리에서 이미지 로드
PdfImage.FromFile은 이미지가 이미 VFS에 있기를 기대합니다. 이미지가 항상 그곳에 있는 것은 아닙니다 — API 응답, 데이터베이스 blob, 캔버스 내보내기는 모두 대신 메모리에 바이트를 제공합니다. PdfImage.FromStream은 그 바이트를 직접 받습니다.
// 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);
그다음부터는 다른 이미지와 동일합니다 — 크기를 계산하고 그리면 됩니다:
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 });
동일한 bytes 형태는 어디에서 왔든 작동합니다. 이미지가 fetch에서 ArrayBuffer로 도착한다면 스트림을 만들기 전에 Uint8Array로 감싸세요:
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);
PDF가 서버에서 트리거된 이미지, 상태에 보관된 사용자 업로드, 또는 캔버스가 런타임에 생성한 모든 것으로 조립될 때 사용할 패턴입니다 — VFS를 거치는 왕복이 필요하지 않습니다.
추천 문서: Spire.PDF는 위의 새 문서뿐만 아니라 이미 열어 둔 PDF의 페이지에도 그릴 수 있습니다. 기존 문서에 이미지를 배치하려면 JavaScript(React)에서 PDF에 이미지를 추가하는 방법을 참조하세요.
일반적인 문제
이미지가 늘어나거나 찌그러져 나옵니다.
거의 항상 두 가지 서로 다른 배율 인자 때문입니다. 하나의 fitRate를 계산하고 너비와 높이를 모두 그 값으로 나누세요 — 축을 독립적으로 조정하지 마세요.
이미지가 크고 빈 페이지 중앙에 아주 작게 나타납니다. 이미지의 가로세로 비율이 페이지와 크게 다를 때 예상되는 결과입니다. 세로 페이지에 파노라마 사진을 넣으면 항상 위아래에 띠가 생깁니다. 이를 받아들이거나(문서에는 올바름), cover로 전환하거나, 최소한 여백이 대칭을 이루도록 여백 조정 버전을 사용하세요.
이미지 가장자리가 잘립니다.
의도했든 아니든 cover 동작을 사용 중입니다. fitRate가 Math.min을 사용했는지 확인하고, 전체 이미지가 보여야 한다면 Math.max로 바꾸세요.
고해상도 사진이 거대한 PDF를 만듭니다.
이미지는 자체 해상도로 삽입됩니다. 파일 크기가 중요하다면 그리기 전에 축소하세요 — 대상 크기의 캔버스에 그린 뒤 내보내고, 그 바이트를 PdfImage.FromStream과 함께 사용하세요.
첫 클릭에 아무 일도 일어나지 않습니다.
WASM 모듈은 비동기로 로드됩니다. if (!pdfModule) return; 가드는 그 때문에 존재합니다. 실제 앱에서는 경고를 띄우기보다 모듈 준비 상태에 따라 버튼을 활성화하세요.
자주 묻는 질문
새 PDF를 만드는 대신 기존 PDF에 이미지를 삽입할 수 있나요?
예. 여기의 예제는 새 문서를 만들지만, 기존 PDF를 열고 같은 방식으로 해당 페이지에 그릴 수 있습니다. 그 작업 흐름은 JavaScript(React)에서 PDF에 이미지를 추가하는 방법을 참조하세요.
어떤 이미지 형식을 로드할 수 있나요?
일반적인 비트맵 형식 — PNG, JPEG, BMP 및 유사 형식 — 은 PdfImage.FromFile과 PdfImage.FromStream에서 지원됩니다. 빌드 시점에 형식을 모르거나 바이트가 네트워크 응답에서 오는 경우 FromStream을 사용하세요.
페이지 순서를 제어할 수 있나요?
예. 페이지는 Pages.Add()를 호출한 순서대로 생성되므로 파일 이름 배열을 정렬하면 출력도 정렬됩니다. 이것이 드래그로 순서를 바꾸는 인터페이스의 메커니즘입니다: 배열을 다시 정렬하고 PDF를 다시 만드세요.
백엔드가 필요한가요?
아니요. 문서는 WebAssembly 모듈에 의해 브라우저에서 조립되고, 완성된 PDF는 Blob으로 변환할 바이트로 반환됩니다. 이미지는 기기를 떠나지 않습니다.
한 PDF에 세로 및 가로 이미지를 섞을 수 있나요?
예. 각 페이지는 독립적으로 크기가 조정되고 그려지므로 세로 스캔과 가로 사진이 나란히 있을 수 있습니다. 페이지 방향을 통일하고 싶다면 고정 페이지 크기를 사용하고 이미지가 그 크기에 맞게 조정되도록 하는 것이 좋습니다.
PDF가 있고 그 페이지들을 이미지로 원합니다. 반대 방향은 아닙니다.
그것은 반대 작업입니다 — 조립이 아니라 렌더링입니다. JavaScript(React)에서 PDF 페이지를 이미지로 변환하는 방법을 참조하세요.
이미지를 VFS에 넣어야 하나요?
FromFile에만 필요합니다. FromStream은 어디에서든 바이트를 받습니다 — fetch 응답, 캔버스 내보내기, 또는 상태 — 그리고 VFS를 완전히 건너뜁니다.
같이 보기
여기의 조립 레시피는 완전히 새로운 문서를 만듭니다. 이미지가 기존 PDF 안으로 들어가야 한다면 — 이미 가지고 있는 페이지에 그리는 경우 — JavaScript(React)에서 PDF에 이미지를 추가하는 방법을 참조하세요. 이미지-PDF 파이프라인의 다른 유용한 부분:
Convertire immagini in PDF in JavaScript (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

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:
Conversion d'images en PDF en JavaScript (React)
Table des matières

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 :
Convertir imágenes a PDF en JavaScript (React)
Tabla de contenidos

Una carpeta de imágenes es incómoda de entregar a alguien. Un PDF es un solo archivo, se abre en todas partes, se imprime de forma predecible y —la parte que realmente importa a la gente— mantiene un orden fijo. Por eso las páginas escaneadas, los conjuntos de fotos, las fotos de recibos y los fotogramas de diseño exportados tan a menudo se ensamblan en un PDF antes de enviarse a cualquier parte.
Construir ese PDF en el navegador es un problema distinto al de renderizar un PDF a una imagen. No estás decodificando algo que ya existe; estás tomando decisiones que un formato de documento normalmente tomaría por ti: qué tan grande es la página, dónde se sitúa la imagen en ella, qué sucede cuando una imagen tiene una forma distinta a la de la página y en qué orden salen las páginas.
Spire.PDF for JavaScript expone esas decisiones mediante un lienzo de página. Añades una página, cargas una imagen, la dibujas en esa página con un tamaño que calculas y guardas. Todo se ejecuta en el lado del cliente a través de WebAssembly, por lo que las imágenes nunca se suben.
Por qué las imágenes terminan en PDFs
Los escenarios comparten una misma forma: varias imágenes que necesitan comportarse como un solo documento.
- Documentos de varias páginas escaneados o fotografiados — un contrato fotografiado página por página, reensamblado en un solo archivo que se puede archivar o enviar por correo electrónico.
- Conjuntos de fotos y portafolios — una imagen por página, en un orden elegido por alguien.
- Recibos e informes de gastos — una docena de fotos del teléfono que contabilidad quiere como un solo archivo adjunto.
- Exportaciones de diseño y diagramas — fotogramas exportados de una herramienta, reunidos en algo revisable.
En cada caso, el PDF no se trata realmente del formato PDF. Se trata de obtener un artefacto estable, de un solo archivo y ordenado a partir de un montón de imágenes.
Requisitos previos
Este tutorial asume un proyecto de React con Spire.PDF for JavaScript instalado y el módulo WASM inicializado. Para la configuración, consulta Integrar Spire.PDF for JavaScript en un proyecto de React.
Necesitarás:
- Uno o más archivos de imagen cargados en el VFS
- El módulo WASM accesible en
window.wasmModule.spirepdf
Una imagen, una página
El flujo básico consta de cuatro pasos: crear un documento, añadir una página, cargar la imagen y dibujarla. La parte interesante es el dibujo: tienes que decidir qué tan grande debe ser la imagen en la 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 generado después de cargar una imagen mediante PdfImage.FromFile y dibujarla con Canvas.DrawImage

Extrae el cálculo de tamaño a una función auxiliar, porque es la única pieza de este código que reutilizarás en todas las demás recetas a continuación. La matemática: toma page.Canvas.ClientSize —el área dibujable de la página, en puntos— como tu presupuesto, compárala con la PhysicalDimension natural de la imagen y divide ambas dimensiones por una única proporción para que la relación de aspecto se conserve. Escóndelo en una función para que la parte propensa a errores viva exactamente en un solo 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 };
}
Ahora la llamada de dibujo del ejemplo anterior se reduce a tres líneas, y la decisión de "contener o cubrir" sale de las matemáticas y pasa al nombre de una función:
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 es la opción de "contener": escala por el eje más restrictivo para que toda la imagen permanezca visible. Si en cambio quieres llenar la página y recortar el desbordamiento, cambia a Math.min; la sección sobre ajuste de tamaño te da la contraparte fitCover y una variante con margen.
Muchas imágenes, un documento
Una imagen por página significa un Pages.Add() y un DrawImage por imagen. Recorre en bucle un arreglo de nombres de archivo y el orden del arreglo se convierte en el orden de las páginas, que es exactamente lo que quieres cuando el usuario acaba de terminar de arrastrar miniaturas para ponerlas en secuencia.
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);
};
Dos notas prácticas. Como cada página se dimensiona de forma independiente, las imágenes de distintas dimensiones no son problema: una foto horizontal y un escaneo vertical pueden estar en el mismo documento sin ningún manejo especial. Y como todo el documento se construye en memoria antes de SaveToFile, la descarga ocurre una sola vez al final, sin importar cuántas imágenes se incluyeron.
También te puede interesar: Ensamblar imágenes es lo inverso de renderizar. Si ya tienes un PDF y quieres cada una de sus páginas como imagen, consulta Cómo convertir páginas de PDF a imágenes en JavaScript (React).
Ajuste de tamaño: adaptar la imagen a la página
Hay dos formas razonables de poner una imagen en una página, y cuál quieres depende de si es aceptable perder parte de la imagen.
Contener (Math.max) |
Cubrir (Math.min) |
|
|---|---|---|
| Qué hace | Escala hasta que la imagen completa encaja | Escala hasta que la página se llena |
| Imagen completa visible | Sí | No: el desbordamiento se recorta |
| Espacio vacío | Posible, en un eje | Ninguno |
| Adecuado para | Escaneos, documentos, cualquier cosa que deba permanecer completa | Fotos a sangre, portadas, diapositivas |
El primer ejemplo usa contener: la función auxiliar fitContain. Cambiar a cubrir es el espejo de esa función: Math.min en lugar de Math.max, llenando la página y dejando que el lienzo recorte lo que se desborde, con desplazamientos de centrado que se vuelven 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
};
}
Si quieres un margen visible en lugar de una salida de borde a borde, reduce el área utilizable en vez de la imagen: introduce el presupuesto ajustado por margen en la misma función auxiliar 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 });
Algo que vale la pena saber sobre PhysicalDimension: refleja el tamaño físico de la imagen, que no siempre es su tamaño en píxeles. Una foto de 4000 × 3000 guardada con una etiqueta DPI diferente reportará números distintos de los que podrías esperar. Por eso el enfoque basado en proporciones anterior es más seguro que codificar las dimensiones en píxeles: funciona independientemente de cómo se etiquetó la imagen.
Cargar imágenes desde memoria
PdfImage.FromFile espera que la imagen ya esté en el VFS. No siempre es ahí donde residen tus imágenes: una respuesta de API, un blob de base de datos o una exportación de lienzo te dan bytes en memoria en su lugar. PdfImage.FromStream toma esos bytes directamente.
// 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 de ahí, es igual que cualquier otra imagen: calcula el tamaño y dibújala:
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 misma forma de bytes funciona independientemente de dónde provenga. Si tus imágenes llegan como un ArrayBuffer de fetch, envuélvelo en un Uint8Array antes de construir el flujo:
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 es el patrón al que recurrir cuando el PDF se ensambla con imágenes desencadenadas por el servidor, cargas de usuario mantenidas en el estado o cualquier cosa generada en tiempo de ejecución por un lienzo: no se requiere un viaje de ida y vuelta a través del VFS.
Artículo recomendado: Spire.PDF también puede dibujar en las páginas de un PDF que ya tengas abierto, no solo en los documentos nuevos anteriores. Para colocar imágenes en un documento existente, consulta Cómo añadir imágenes a un PDF en JavaScript (React).
Problemas comunes
La imagen sale estirada o aplastada.
Esto casi siempre se debe a dos factores de escala diferentes. Calcula un único fitRate y divide tanto el ancho como el alto por él; nunca escales los ejes de forma independiente.
La imagen es diminuta en el medio de una gran página vacía. Es de esperar cuando la relación de aspecto de la imagen difiere mucho de la de la página. Una foto panorámica en una página vertical siempre dejará franjas arriba y abajo. O lo aceptas (correcto para documentos), cambias a cubrir, o usas la versión ajustada por margen para al menos mantener simétrico el espacio en blanco.
La imagen se corta en los bordes.
Estás usando el comportamiento de cubrir, intencionalmente o no. Comprueba si fitRate usó Math.min; cambia a Math.max si la imagen completa debe ser visible.
Una foto de alta resolución produce un PDF enorme.
La imagen se incrusta con su propia resolución. Si el tamaño del archivo importa, reduce la escala antes de dibujar: dibújala en un lienzo al tamaño objetivo, expórtala y usa esos bytes con PdfImage.FromStream.
No ocurre nada en el primer clic.
El módulo WASM se carga de forma asíncrona. La guarda if (!pdfModule) return; existe por esa razón; en una aplicación real, habilita el botón según la preparación del módulo en lugar de mostrar una alerta.
Preguntas frecuentes
¿Puedo insertar imágenes en un PDF existente en lugar de crear uno nuevo?
Sí. Los ejemplos aquí crean un documento nuevo, pero puedes abrir un PDF existente y dibujar en sus páginas de la misma manera. Consulta Cómo añadir imágenes a un PDF en JavaScript (React) para ese flujo de trabajo.
¿Qué formatos de imagen puedo cargar?
Los formatos de mapa de bits comunes —PNG, JPEG, BMP y similares— son compatibles con PdfImage.FromFile y PdfImage.FromStream. Usa FromStream cuando el formato se desconoce en tiempo de compilación o los bytes provienen de una respuesta de red.
¿Puedo controlar el orden de las páginas?
Sí. Las páginas se crean en el orden en que llamas a Pages.Add(), por lo que ordenar tu arreglo de nombres de archivo ordena la salida. Ese es el mecanismo detrás de las interfaces de arrastrar para reordenar: reordena el arreglo, reconstruye el PDF.
¿Esto requiere un backend?
No. El documento se ensambla en el navegador mediante el módulo WebAssembly, y el PDF terminado se devuelve como bytes que conviertes en un Blob. Las imágenes nunca salen del dispositivo.
¿Puedo mezclar imágenes verticales y horizontales en un mismo PDF?
Sí. Cada página se dimensiona y se dibuja de forma independiente, por lo que un escaneo vertical y una foto horizontal pueden estar uno junto al otro. Si quieres una orientación de página uniforme, esa es una razón para usar un tamaño de página fijo y dejar que las imágenes se escalen a él.
Tengo un PDF y quiero sus páginas como imágenes, no al revés.
Esa es la operación inversa: renderizar en lugar de ensamblar. Consulta Cómo convertir páginas de PDF a imágenes en JavaScript (React).
¿Necesito la imagen en el VFS?
Solo para FromFile. FromStream acepta bytes de cualquier lugar —una respuesta de fetch, una exportación de lienzo o el estado— y omite el VFS por completo.
Ver también
Las recetas de ensamblaje aquí crean un documento completamente nuevo. Si tus imágenes necesitan ir a un PDF existente —dibujando en páginas que ya tienes—, consulta Cómo añadir imágenes a un PDF en JavaScript (React). Las otras piezas útiles del flujo de trabajo de imagen a PDF: