Generación de archivos PDF en Node.js con PDFKit

En este tutorial veremos cómo generar archivos PDF en Node.js usando PDFKit, cómo personalizarlos, alinear texto, agregar imágenes, escalar imágenes, etc.

Introducción

El formato PDF es uno de los formatos de documentos más comunes para transferir información. En las aplicaciones web dinámicas, es posible que necesite exportar datos a un documento y el PDF suele ser una opción popular. En este artículo, discutiremos cómo generar archivos PDF en NodeJS usando el paquete NPM pdfkit.

PDFKit es una biblioteca de generación de PDF de JavaScript para Node.js que proporciona una manera fácil de crear documentos PDF imprimibles de varias páginas.

Primeros pasos con PDFKit

Vamos a crear un directorio de proyecto, cd en él e inicializar el proyecto Node con la configuración predeterminada:

1
2
3
$ mkdir pdfkit-project
$ cd pdfkit-project
$ npm init -y

Entonces, vamos a instalar pdfkit:

1
$ npm install pdfkit

Para usar el módulo en el proyecto, lo importaremos a través de require():

1
const PDFDocument = require('pdfkit');

Crear un documento PDF usando PDFKit

Para crear un documento PDF, también necesitaremos importar el módulo fs (sistema de archivos). Vamos a canalizar el contenido de nuestro archivo PDF en un flujo de escritura fs's para guardarlo. Echemos un vistazo a cómo hacerlo:

1
2
3
4
5
6
7
const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('SampleDocument.pdf'));
pdfDoc.text("My Sample PDF Document");
pdfDoc.end();

Primero, importamos los módulos requeridos, después de lo cual, instanciamos el PDFDocument. Esta instancia es una secuencia legible. Conectaremos esa transmisión a una transmisión grabable para guardar el archivo.

If you're not familiar with how streams work, check out our Introducción a las secuencias de Node.js.

Usamos la función pipe() para hacer esto y guardar el SampleDocument.pdf resultante en nuestro directorio raíz. Una vez creado, podemos agregarle contenido, a través de la función texto. Por supuesto, querremos finalizar() la secuencia al final.

Cuando ejecutamos el código, se crea un archivo PDF llamado SampleDocument.pdf en la carpeta raíz de nuestro proyecto:

1
$ node index.js

Nota: Antes de intentar sobrescribir un archivo PDF existente, debe estar libre. Es decir, todas las ventanas con ese archivo PDF deben cerrarse o el programa arrojará un error.

Formateo de texto en archivo PDF

Por supuesto, pdfkit nos permite hacer mucho más que simplemente agregar texto sin formato a un documento. Echemos un vistazo a algunas de las características que ofrece.

Texto de posicionamiento

De forma predeterminada, el módulo pdfkit realiza un seguimiento de dónde se debe agregar el texto al documento, esencialmente imprime cada llamada a la función text() en una nueva línea.

Puede cambiar dónde se imprime el texto dentro de la página actual, agregando las coordenadas x e y de la ubicación donde desea colocar el texto como argumentos a la función text().

Por ejemplo:

1
pdfDoc.text("Text positioned at (200,200)", 200, 200);

Esto es útil porque le permite ajustar la posición del texto, especialmente porque los documentos PDF tienen un aspecto universal, independientemente de la máquina o el sistema operativo en el que se abran. Esto también le permitiría, por ejemplo, imprimir texto sobre otro texto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('SampleDocument.pdf'));

pdfDoc.text("From Mon-Sat we will have a 10% discount on selected items!", 150, 150);
pdfDoc
    .fillColor('red')
    .fontSize(17)
    .text("20%", 305, 150);

pdfDoc.end();

Ejecutar este código nos daría:

text positioning

Ajuste y alineación de texto

El módulo pdfkit envuelve automáticamente las líneas para que encajen entre los márgenes, o en el ancho proporcionado (al escribir texto en columnas). En otras palabras, la opción lineBreak es true por defecto. Puedes cambiarlo a falso al llamar a la función text():

1
pdfDoc.text("very long text ".repeat(20), { lineBreak : false });

Las nuevas páginas también se agregan automáticamente según sea necesario, es decir, tan pronto como el contenido que desea agregar no cabe en la página actual en su totalidad. Sin embargo, también puede pasar a la siguiente página antes de completar la anterior simplemente llamando:

1
pdfDoc.addPage();

En cuanto a la alineación, pdfkit nos proporciona las opciones habituales: izquierda (predeterminado), derecha, centro y justificar. Tenga en cuenta que establecer una alineación específica con lineBreak establecido en false no funcionará, incluso si el texto puede caber en una línea.

Al igual que lineBreak, el parámetro align se establece pasando un objeto que contiene pares clave-valor a la función text(). Veamos algunos ejemplos de alineación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_alignment.pdf'));

pdfDoc.text("This text is left aligned", { align: 'left'})
pdfDoc.text("This text is at the center", { align: 'center'})
pdfDoc.text("This text is right aligned", { align: 'right'})
pdfDoc.text("This text needs to be slightly longer so that we can see that justification actually works as intended", { align: 'justify'})

pdfDoc.end();

Ejecutar el código anterior nos daría un PDF que se ve así:

alineación de texto

Dar estilo al texto

El módulo pdfkit también proporciona opciones que se pueden usar para diseñar texto en sus documentos PDF. Echaremos un vistazo a algunas de las opciones de estilo más importantes, puede encontrar la lista completa de opciones en la [Guía en PDF] (https://pdfkit.org/).

Podemos pasar diferentes opciones como pares clave-valor a la función text(), y también encadenar varias otras funciones antes de llamar a text().

Una cosa muy importante a tener en cuenta es que las funciones encadenadas, como fillColor() (y más tarde font(), fontSize(), etc.) afectarán todo el texto después de esa llamada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_styling.pdf'));

pdfDoc
    .fillColor('blue')
    .text("This is a link", { link: 'https://pdfkit.org/docs/guide.pdf', underline: true });
pdfDoc
    .fillColor('black')
    .text("This text is underlined", { underline: true });
pdfDoc.text("This text is italicized", { oblique: true });
pdfDoc.text("This text is striked-through", { strike: true });

pdfDoc.end();

Ejecutar este código generará un archivo PDF con los siguientes contenidos:

text styling

Cambiar estilos en medio de un párrafo es un poco más complicado, ya que encadenar múltiples funciones text() agrega una nueva línea después de cada una de forma predeterminada. Podemos evitar esto configurando la opción lineBreak de la primera llamada text() a false:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_styling2.pdf'));

pdfDoc
    .fillColor('blue')
    .text("This text is blue and italicized", {oblique : true, lineBreak : false})
    .fillColor('red')
    .text(" This text is red");

pdfDoc.end();

Lo que nos daría el resultado deseado:

estilo de texto en el mismo párrafo

Creación de listas

Para agregar una lista de elementos en su documento PDF, la instancia PDFDocument tiene una función list() que toma una matriz de elementos de cadena (o matrices anidadas de cadenas) y las muestra como una lista con viñetas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('lists.pdf'));

let myArrayOfItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

pdfDoc.list(myArrayOfItems);
// Move down a bit to provide space between lists
pdfDoc.moveDown(0.5);

let innerList = ['Nested Item 1', 'Nested Item 2'];
let nestedArrayOfItems = ['Example of a nested list', innerList];

pdfDoc.list(nestedArrayOfItems);

pdfDoc.end();

Lo que nos da:

listas con pdfkit

Fuentes

PDFKit viene con 14 fuentes estándar que se pueden usar en documentos PDF. Cualquiera de estas fuentes puede pasarse a la función font() de la clase PDFDocument y encadenarse con text():

1
pdfDoc.font('Times-Roman').text('A text in Times Roman')

También puede agregar fuentes adicionales pasando la ruta al archivo de fuente como argumento a la función font(), así como el nombre de la fuente específica que desea en caso de que el archivo tenga una colección de fuentes. Alternativamente, puede darle un nombre a la nueva fuente para que se pueda acceder a ella por ese nombre en lugar de la ruta del archivo:

1
pdfDoc.registerFont('Name of the font', '/file_path', 'specific_font_name_in_case_of_a_collection')

Las llamadas a font() se pueden encadenar con otras funciones, tal como en el ejemplo fillColor().

También puede establecer el tamaño de fuente utilizando la función fontSize(). Veamos algunos ejemplos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('fonts.pdf'));

pdfDoc.font('ZapfDingbats').text('This is a symbolic font.');
pdfDoc.font('Times-Roman').fontSize(25).fillColor('blue').text('You can set a color for any font');
pdfDoc.font('Courier').fontSize(5).fillColor('black').text('Some text to demonstrate.');

pdfDoc.end();

Ejecutar esto nos daría el siguiente PDF como salida:

fuentes con pdfkit

Adición de imágenes

Otra cosa común que quizás desee agregar a sus archivos PDF son las imágenes. Puede llamar a la función image() en la instancia del documento y pasar la ruta o el URI de la imagen que desea incluir.

También puede establecer opciones como el ancho, alto, alineación horizontal y vertical de la imagen pasando un objeto que contiene pares clave-valor como argumento a la función image(). De forma predeterminada, las imágenes se cargan en su tamaño original.

Si configura ancho y alto, la imagen se estirará para ajustarse a los parámetros especificados. Si se omite uno de estos, la imagen se escala proporcionalmente al parámetro proporcionado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('images.pdf'));

pdfDoc.text('By default, the image is loaded in its full size:')
pdfDoc.image('raspberries.jpg');

pdfDoc.moveDown(0.5)
pdfDoc.text('Scaled to fit width and height')
pdfDoc.image('raspberries.jpg', {width: 150, height: 150});

pdfDoc.moveDown(0.5)
pdfDoc.text('Scaled to fit width')
pdfDoc.image('raspberries.jpg', {width: 150});

pdfDoc.end();

Ejecutar este código nos daría:

imágenes predeterminadas y escaladas

También puede escalar la imagen dando un factor de escala. Además, puede proporcionar una matriz fit o cover, donde la imagen se escalará para ajustarse al rectángulo proporcionado o cubrirlo, respectivamente. Si proporciona una matriz fit o cover, también puede establecer la alineación horizontal (align) y la alineación vertical (valign):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('images.pdf'));

pdfDoc.text('Scaled by a factor, keeps the original proportions:')
pdfDoc.image('raspberries.jpg', {scale: 0.75});

pdfDoc.moveDown(0.5)
pdfDoc.text('Fit with horizontal alignment:')
pdfDoc.image('raspberries.jpg', {fit: [400, 150], align: 'center'});

pdfDoc.end();

Esto nos daría:

factor de escala y ajuste

Conclusión

En este artículo hemos visto cómo generar archivos PDF en Node.js usando PDFKit. Hemos explorado algunas de las opciones disponibles para dar formato al texto y cómo agregar imágenes a nuestros archivos. La biblioteca tiene [documentación] extensa (https://pdfkit.org/) que cubre mucho más sobre la creación de archivos PDF en aplicaciones Node.js.