
Ensamblar un PDF pulido a partir de archivos de origen dispersos es una tarea rutinaria pero complicada: una página de portada debe ir al principio de un resumen de proyecto, las páginas de precios pertenecen a su contrato, un resumen trimestral une gráficos de una docena de informes. Hacerlo a mano significa hacer malabares con varios lectores de PDF y esperar que el orden de las páginas salga bien, con tamaños de página que no coinciden y que agravan el problema.
Spire.PDF for JavaScript traslada toda la operación al navegador. Impulsado por WebAssembly, carga, manipula y guarda documentos PDF completamente en el lado del cliente a través de un sistema de archivos virtual (VFS), lo que significa que ningún archivo se sube a un servidor backend. Este artículo recorre cuatro técnicas distintas para copiar páginas PDF entre documentos — tres que reubican páginas completas y una que extrae el contenido de la página como una plantilla reutilizable — con ejemplos de código React completos para cada una.
Para obtener instrucciones de configuración e instalación del proyecto, consulta Integrar Spire.PDF for JavaScript en un proyecto React. Los ejemplos a continuación asumen que Spire.PDF está instalado y que el módulo WebAssembly se ha inicializado.
Cuatro maneras de copiar páginas PDF de un vistazo
Antes de examinar cada método individualmente, la siguiente tabla proporciona una comparación rápida. Las tres primeras técnicas mueven páginas intactas y transfieren automáticamente las dimensiones, la rotación y los márgenes de la página de origen. La cuarta desvincula el contenido de la geometría de la página, otorgándote control total sobre el tamaño de la página de destino y la posición de dibujo.
| Método | Llamada a la API | Qué se copia | Tamaño de página | Caso de uso típico |
|---|---|---|---|---|
| Insertar una sola página | InsertPage |
Una página en una posición que elijas | Hereda del origen | Agregar una portada o página de título al principio |
| Insertar un rango de páginas | InsertPageRange |
Un bloque consecutivo de páginas | Hereda del origen | Anexar una sección específica como tablas de precios |
| Anexar un documento completo | AppendPage |
Todas las páginas del documento de origen | Hereda del origen | Concatenar documentos completos de principio a fin |
| Dibujar el contenido de la página como una plantilla |
CreateTemplate + DrawTemplate
|
Solo el contenido de la página, dibujado en cualquier página | Tú decides el tamaño de destino | Reutilizar contenido en diferentes tamaños de página o repetirlo varias veces |
Los tres primeros métodos son movimientos de páginas sencillos: elige el origen, elige el destino y la biblioteca se encarga del resto. El enfoque de plantilla es más avanzado y abre posibilidades que la simple copia de páginas no puede abordar, como escalar contenido para ajustarlo a un tamaño de página diferente o estampar el mismo contenido en varias páginas. Cubriremos primero los tres métodos de movimiento de páginas y luego exploraremos la técnica de plantilla en profundidad.
Copiar una sola página a una posición específica
El más preciso de los cuatro métodos, PdfDocument.InsertPage, copia una página de un documento de origen y la coloca en un índice exacto del destino. El parámetro resultPageIndex controla dónde aterriza la copia: pasa 0 para anteponerla, pasa el recuento actual de páginas del destino para anexarla, o proporciona cualquier índice intermedio para insertarla en esa posición. Omite resultPageIndex por completo y la página se colocará al final de forma predeterminada.
function App() {
const copyPageAtPosition = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load both the source and the target document into the VFS
const sourceFileName = 'SourceDocument.pdf';
const targetFileName = 'TargetDocument.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the two documents
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// Copy page 1 of the source document to the front of the target document
// pageIndex comes from the source document, resultPageIndex is where the copy lands
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 0 });
// Save the result document
const outputFileName = 'CopyPageAtPosition.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// Read the generated file from the VFS and trigger the 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>Copy Page at Position</h1>
<button onClick={copyPageAtPosition}>
Start
</button>
</div>
);
}
export default App;
Entre los cuatro métodos de copia,
resultPageIndexes el único parámetro que te permite elegir el punto de inserción. Establecerlo en 0 coloca la página primero, 1 la coloca en segundo lugar, y pasar el recuento actual de páginas del documento de destino produce el mismo efecto que anexar.
El documento de destino pasa de dos páginas a tres, y la primera página del documento de origen ahora ocupa la posición inicial:

Copiar un rango de páginas al final
Cuando necesitas más de una página pero menos que un documento completo, PdfDocument.InsertPageRange copia un bloque contiguo de páginas definido por un índice de inicio y otro de fin. A diferencia de InsertPage, este método acepta argumentos posicionales en lugar de un objeto de opciones, y siempre anexa las páginas copiadas al final del destino — no hay parámetro para elegir la posición de inserción. El índice de fin es inclusivo, por lo que pasar (sourceDoc, 1, 2) copia las páginas 2 y 3 (con índice base cero).
function App() {
const appendPageRange = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load both the source and the target document into the VFS
const sourceFileName = 'SourceDocument.pdf';
const targetFileName = 'TargetDocument.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the two documents
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// Append pages 2 to 3 of the source document to the end of the target document
// Note: these are positional arguments, not an object; endIndex is inclusive
targetDoc.InsertPageRange(sourceDoc, 1, 2);
// Save the result document
const outputFileName = 'CopyPageRange.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// Read the generated file from the VFS and trigger the 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>Copy Page Range</h1>
<button onClick={appendPageRange}>
Copy pages 2-3
</button>
</div>
);
}
export default App;
El documento de destino incorpora dos páginas adicionales, pasando de dos a cuatro en total:

Anexar un documento completo
Para el caso más sencillo — mover todas las páginas de un documento a otro — PdfDocument.AppendPage elimina la necesidad de calcular índices por completo. Pasa el objeto del documento de origen y todas sus páginas se anexan al destino en su secuencia original. Para concatenar varios documentos, llama a AppendPage repetidamente con cada documento de origen a su vez.
function App() {
const appendWholeDocument = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load both the source and the target document into the VFS
const sourceFileName = 'SourceDocument.pdf';
const targetFileName = 'TargetDocument.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the two documents
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// Use AppendPage when the whole document has to be copied; all pages are appended in order
targetDoc.AppendPage({ doc: sourceDoc });
// Save the result document
const outputFileName = 'CopyAllPages.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// Read the generated file from the VFS and trigger the 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>Copy Whole Document</h1>
<button onClick={appendWholeDocument}>
Start
</button>
</div>
);
}
export default App;
Las cuatro páginas del documento de origen se unen al destino, expandiéndolo de dos páginas a seis:

Copiar el contenido de la página con una plantilla
Los tres métodos anteriores tratan una página como una unidad indivisible: se mueve conservando su tamaño, rotación y márgenes. Pero el ensamblaje de documentos del mundo real a menudo exige un control más preciso: colocar el contenido de una página en una página de tamaño diferente, escalarlo hacia arriba o hacia abajo, o estampar el mismo contenido en varias páginas. Aquí es donde entra en juego PdfPageBase.CreateTemplate.
CreateTemplate extrae el contenido visual de una página a un objeto PdfTemplate. Luego dibujas esa plantilla en cualquier página usando Canvas.DrawTemplate, especificando la posición y el tamaño del área de dibujo. La plantilla se desvincula de la geometría de la página original, por lo que puedes renderizarla a cualquier escala, en cualquier posición, en cualquier tamaño de página, y puedes dibujar la misma plantilla tantas veces como necesites.
Esto hace que las plantillas sean especialmente útiles para escenarios como:
- Colocar el contenido de una portada A5 centrado en una página A4 sin un borde blanco
- Crear una marca de agua o un patrón de fondo a partir de una página existente
- Duplicar el diseño de un formulario en varias páginas nuevas a diferentes escalas
function App() {
const copyPageWithTemplate = async () => {
// Get the Spire.PDF WASM module
const pdfModule = window.wasmModule?.spirepdf;
// Check whether the module is ready
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// Load the PDF file to work on into the VFS
const inputFileName = 'SourceDocument.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// Load the document
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// Take the page to be reused and turn it into a template: read the content once, draw it many times
const sourcePage = doc.Pages.get_Item(0);
const template = sourcePage.CreateTemplate();
// First placement: insert an A4 page at position 2, a different size from the source,
// and draw the content scaled to 297.6 x 421.6 at (80, 80)
const page1 = doc.Pages.Insert(1, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page1.Canvas.DrawTemplate(template, new pdfModule.PointF(80.0, 80.0), new pdfModule.SizeF(297.6, 421.6));
// Second placement: insert another A4 page, drawing the same template smaller in the lower right
const page2 = doc.Pages.Insert(2, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page2.Canvas.DrawTemplate(template, new pdfModule.PointF(320.0, 460.0), new pdfModule.SizeF(200.0, 283.3));
// Save the result document
const outputFileName = 'CopyPageWithTemplate.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// Read the generated file from the VFS and trigger the 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>Copy Page with Template</h1>
<button onClick={copyPageWithTemplate}>
Start
</button>
</div>
);
}
export default App;
Algunos detalles que vale la pena señalar sobre DrawTemplate:
- Argumento de tamaño: Cuando se omite el tercer argumento (tamaño de destino), la plantilla se renderiza en sus dimensiones originales sin escalar. En una página de destino más grande, el contenido ocupa solo una parte del espacio disponible.
-
Creación de página: Las dimensiones y los márgenes de la página de destino provienen de
Pages.Insert, no de la plantilla. En el ejemplo, los márgenes cero en todos los lados hacen que el origen del dibujo coincida con la esquina superior izquierda de la página. -
Dibujos múltiples: El mismo objeto
templatese dibuja dos veces en dos páginas separadas en diferentes posiciones y escalas, lo que demuestra la capacidad de reutilización.
El contenido de la página 1 ahora aparece en dos páginas A4 recién insertadas, a diferentes escalas y posiciones, aumentando el documento de cuatro páginas a seis:

Preguntas frecuentes
Crear una página con new PdfMargins(0.0) lanza Arg_NullReferenceException
Causa: El constructor de PdfMargins interpreta un argumento numérico sin más como un identificador interno en lugar de un valor de margen. Por lo tanto, llamar a new pdfModule.PdfMargins(0.0) produce un objeto que no representa márgenes válidos — acceder a su propiedad Left o Top desencadena Arg_NullReferenceException, y pasarlo a la creación de páginas genera resultados inesperados.
Solución: Pasa siempre los márgenes como un objeto de configuración. Para márgenes cero uniformes, usa { margin: 0.0 }; para valores individuales por lado, especifica cada lado explícitamente:
// Zero margins on all four sides
const margins = new pdfModule.PdfMargins({ margin: 0.0 });
// Or set each side separately
const custom = new pdfModule.PdfMargins({ left: 20.0, top: 20.0, right: 20.0, bottom: 20.0 });
Se produce un error de rango fuera de límites o de rango invertido al copiar páginas
Causa: Los índices de página se basan en cero y endIndex en InsertPageRange es inclusivo. Por lo tanto, el rango válido va de 0 a Pages.Count - 1. Proporcionar un índice fuera de este rango genera Index out of range, mientras que establecer startIndex mayor que endIndex genera The start index is greater then the end index.
Solución: Protege el límite superior limitándolo a Pages.Count antes de llamar al método:
// To copy pages 2 to 4: start = 1, end = 3, with the page count as the upper bound
const start = 1;
const end = Math.min(3, sourceDoc.Pages.Count - 1);
targetDoc.InsertPageRange(sourceDoc, start, end);
Una página rotada sale con la orientación incorrecta después de copiarla
Causa: CreateTemplate() captura el contenido dibujado de la página pero no su ángulo de rotación (la entrada /Rotate). Cuando la página de origen tiene una rotación, el sistema de coordenadas de la plantilla se desalinea con la página de destino — dibujarla directamente coloca el contenido fuera del área visible, y la copia resultante tiene una Rotation de 0.
Solución: Para páginas de origen rotadas, prefiere una copia de página completa para que el ángulo de rotación viaje con el contenido:
// Whole-page copy: the rotation angle comes with the page
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 1 });
Si el enfoque de plantilla es inevitable, borra temporalmente la rotación de la página de origen antes de extraer la plantilla, y luego restaura el ángulo original tanto en la página de origen como en la nueva:
const rotation = sourcePage.Rotation.value;
// Zero it temporarily so the template exports at the page's real coordinates
sourcePage.Rotation = 0;
const newPage = doc.Pages.Insert(1, sourcePage.Size, new pdfModule.PdfMargins({ margin: 0.0 }));
newPage.Canvas.DrawTemplate(sourcePage.CreateTemplate(), new pdfModule.PointF(0.0, 0.0));
// Restore the source page and give the copy the same angle
sourcePage.Rotation = rotation;
newPage.Rotation = rotation;
Para eliminar la marca de agua de evaluación de los documentos de salida o desbloquear el acceso completo a las funciones, contacta con ventas para obtener una licencia temporal de 30 días.