pols-pdf 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # Pols-Pdf
2
+
3
+ Clase en TypeScript/JavaScript que encapsula [`pdfkit`](https://pdfkit.org/) para agregar
4
+ primitivas de maquetado que `pdfkit` no ofrece de forma nativa: rectángulos con bordes y esquinas
5
+ redondeadas independientes por lado, tablas con paginación automática, celdas con texto
6
+ carácter-por-carácter (wrap, alineación, espaciado, fuentes mixtas) e imágenes con ajuste
7
+ `proportional`/`cover`/`stretch`.
8
+
9
+ ## Instalación
10
+
11
+ ```bash
12
+ npm install pols-pdf
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Inicio rápido
18
+
19
+ ```typescript
20
+ import fs from 'fs'
21
+ import { PPdf } from 'pols-pdf'
22
+
23
+ const pdf = new PPdf({ title: 'Reporte' })
24
+
25
+ pdf.cell({ text: 'Hola mundo', height: 20 })
26
+
27
+ pdf.build().pipe(fs.createWriteStream('output.pdf'))
28
+ ```
29
+
30
+ `build()` cierra el documento y devuelve el `PDFDocument` de `pdfkit` (que ya es un
31
+ `stream.Readable`), listo para ser canalizado (`pipe`) a un archivo, a la respuesta HTTP, etc.
32
+
33
+ ---
34
+
35
+ ## Constructor
36
+
37
+ ```typescript
38
+ new PPdf(params?: {
39
+ title?: string
40
+ page?: Partial<PPage>
41
+ template?: () => void
42
+ addFirstPage?: boolean
43
+ })
44
+ ```
45
+
46
+ - `title`: título del documento (metadato PDF). También accesible/editable luego vía `pdf.title`.
47
+ - `page`: configuración de página por defecto (`size`, `layout`, `margins`), ver [`PPage`](#ppage).
48
+ - `template`: función que se ejecuta automáticamente cada vez que se agrega una página
49
+ (`addPage`), útil para dibujar encabezados/pies de página repetidos.
50
+ - `addFirstPage`: si es `false`, no agrega la primera página automáticamente (por defecto `true`).
51
+
52
+ ---
53
+
54
+ ## El cursor
55
+
56
+ `PPdf` mantiene internamente un cursor `{ left, top }` que la mayoría de los métodos de dibujo
57
+ leen como posición por defecto y actualizan después de dibujar. Se puede leer y mover manualmente:
58
+
59
+ ```typescript
60
+ pdf.getCursorLeftPosition()
61
+ pdf.getCursorTopPosition()
62
+ pdf.getBottomSpaceQuantity() // espacio restante hasta el borde inferior de la página
63
+ pdf.getPageWidth()
64
+
65
+ pdf.setCursorLeftPosition('left') // vuelve al margen izquierdo
66
+ pdf.setCursorLeftPosition(50)
67
+ pdf.moveCursorLeft(20)
68
+
69
+ pdf.setCursorTopPosition('top') // vuelve al margen superior
70
+ pdf.setCursorTopPosition(100)
71
+ pdf.moveCursorTop(20) // si autoJump (por defecto true) y se pasa del margen inferior, agrega una página nueva
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Páginas
77
+
78
+ ```typescript
79
+ pdf.addPage({ size: 'LETTER', layout: 'landscape', margins: 30 })
80
+ ```
81
+
82
+ `margins` (como todos los parámetros de tipo `*BoundParams` en esta librería) acepta un único
83
+ número para los cuatro lados, o un objeto parcial `{ top?, right?, bottom?, left? }`.
84
+
85
+ ---
86
+
87
+ ## Rectángulos
88
+
89
+ ```typescript
90
+ pdf.rectangle({
91
+ left: 20,
92
+ top: 20,
93
+ width: 200,
94
+ height: 80,
95
+ fillColor: '#eeeeee',
96
+ border: true, // o { left, right, top, bottom } individuales
97
+ radius: 8, // o { leftTop, rightTop, rightBottom, leftBottom }
98
+ stroke: { width: 1, color: '#333333' }
99
+ })
100
+ ```
101
+
102
+ `rectangleTo(params)` dibuja un rectángulo desde la posición actual del cursor hasta el punto
103
+ `{ left, top }` indicado (ancho/alto se calculan como la diferencia con el cursor).
104
+
105
+ ---
106
+
107
+ ## Celdas de texto (`cell`)
108
+
109
+ ```typescript
110
+ pdf.cell({
111
+ text: 'Texto de la celda',
112
+ width: 200,
113
+ height: 40, // o autojump: true si no se conoce el alto de antemano
114
+ padding: 6,
115
+ hAlign: 'center',
116
+ vAlign: 'center',
117
+ font: { size: 12, bold: true, color: '#111111' },
118
+ border: true,
119
+ fillColor: '#f5f5f5'
120
+ })
121
+ ```
122
+
123
+ - `text` acepta `string`, `number`, `null`/`undefined`, un objeto `{ chars, font? }`, o un arreglo
124
+ de estos últimos para mezclar fuentes/estilos dentro de una misma celda.
125
+ - Una celda necesita **o bien** `height` (alto fijo) **o bien** `autojump: true` (el alto se
126
+ calcula según el contenido y, si no entra en la página actual, el texto restante continúa
127
+ automáticamente en una página nueva).
128
+ - Si el contenido no entra en la página, `cell()` agrega una página nueva y continúa dibujando el
129
+ resto automáticamente.
130
+
131
+ ---
132
+
133
+ ## Filas y tablas (`row` / `table`)
134
+
135
+ ```typescript
136
+ pdf.table({
137
+ width: 'full', // 'full' | 'auto' | número
138
+ border: true,
139
+ padding: 4,
140
+ rows: [
141
+ {
142
+ fillColor: '#dddddd',
143
+ cells: [
144
+ { text: 'Nombre', weight: 2 },
145
+ { text: 'Edad', weight: 1 },
146
+ ]
147
+ },
148
+ {
149
+ cells: [
150
+ { text: 'Ana', weight: 2 },
151
+ { text: '30', weight: 1 },
152
+ ]
153
+ }
154
+ ]
155
+ })
156
+ ```
157
+
158
+ - Cada celda de una fila define su ancho con `width` (número fijo) **o** `weight` (proporción del
159
+ ancho disponible restante), nunca ambos.
160
+ - `table()` va maquetando fila por fila; en cuanto una fila no entra en la página actual, dibuja
161
+ el buffer acumulado, agrega una página nueva y continúa con el resto — una tabla puede así
162
+ abarcar varias páginas sin que el llamador tenga que ocuparse de la paginación.
163
+ - `row()` expone el mismo mecanismo para dibujar una única fila suelta (fuera de una tabla).
164
+
165
+ ---
166
+
167
+ ## Imágenes
168
+
169
+ ```typescript
170
+ pdf.image({ filePath: 'logo.png', left: 20, top: 20, width: 120 })
171
+
172
+ pdf.image({
173
+ filePath: 'foto.jpg',
174
+ left: 20,
175
+ top: 20,
176
+ width: 200,
177
+ height: 120,
178
+ fill: 'cover', // 'stretch' | 'proportional' | 'cover'
179
+ hAlign: 'center',
180
+ vAlign: 'center'
181
+ })
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Fuentes
187
+
188
+ ```typescript
189
+ pdf.setFont({ name: 'Helvetica', bold: true, size: 14, color: '#000000' })
190
+ ```
191
+
192
+ Cambia la fuente activa del documento; los métodos de texto (`cell`, `row`, `table`) también
193
+ aceptan `font` por llamada (y por fragmento de texto individual dentro de `text`), sin necesidad
194
+ de llamar a `setFont` explícitamente.
195
+
196
+ ---
197
+
198
+ ## Tipos con forma abreviada (`*Params`)
199
+
200
+ Los parámetros de borde, radio y márgenes/padding aceptan una forma abreviada además del objeto
201
+ completo:
202
+
203
+ | Tipo | Forma corta | Forma completa |
204
+ |---|---|---|
205
+ | `PBoundParams` (`padding`, `margins`) | `number` (aplica a los 4 lados) | `{ top?, right?, bottom?, left? }` |
206
+ | `PRadiusParams` (`radius`) | `number` | `{ leftTop?, rightTop?, rightBottom?, leftBottom? }` |
207
+ | `PBorderParams` (`border`) | `boolean` | `{ leftTop?, top?, rightTop?, right?, rightBottom?, bottom?, leftBottom?, left? }` |
208
+ | `PBorderColorParams` (`stroke.color`) | `string` (color aplicado a los 4/8 lados) | `{ top?, rightTop?, right?, rightBottom?, bottom?, leftBottom?, left?, leftTop? }` |
209
+
210
+ ---
211
+
212
+ ## Convención de nombres
213
+
214
+ Todas las clases y tipos exportados llevan el prefijo `P` (`PPdf`, `PCell`, `PRowParams`, etc.),
215
+ siguiendo la convención usada en los paquetes hermanos de este ecosistema: `pols-date` (`PDate`)
216
+ y `pols-utils` (`PRecord`, `PUtilsNumber`).
@@ -0,0 +1,287 @@
1
+ export type PBoundParams = number | {
2
+ top?: number;
3
+ bottom?: number;
4
+ left?: number;
5
+ right?: number;
6
+ };
7
+ export type PBound = {
8
+ top: number;
9
+ bottom: number;
10
+ left: number;
11
+ right: number;
12
+ };
13
+ export type PRadiusParams = number | {
14
+ leftTop?: number;
15
+ rightTop?: number;
16
+ rightBottom?: number;
17
+ leftBottom?: number;
18
+ };
19
+ export type PRadius = {
20
+ leftTop: number;
21
+ rightTop: number;
22
+ rightBottom: number;
23
+ leftBottom: number;
24
+ };
25
+ export type PBorderParams = boolean | {
26
+ leftTop?: boolean;
27
+ top?: boolean;
28
+ rightTop?: boolean;
29
+ right?: boolean;
30
+ rightBottom?: boolean;
31
+ bottom?: boolean;
32
+ leftBottom?: boolean;
33
+ left?: boolean;
34
+ };
35
+ export type PBorder = {
36
+ leftTop: boolean;
37
+ top: boolean;
38
+ rightTop: boolean;
39
+ right: boolean;
40
+ rightBottom: boolean;
41
+ bottom: boolean;
42
+ leftBottom: boolean;
43
+ left: boolean;
44
+ };
45
+ export type PBorderColorParams = string | {
46
+ top?: string;
47
+ rightTop?: string;
48
+ right?: string;
49
+ rightBottom?: string;
50
+ bottom?: string;
51
+ leftBottom?: string;
52
+ left?: string;
53
+ leftTop?: string;
54
+ };
55
+ export type PBorderColor = {
56
+ top: string;
57
+ rightTop: string;
58
+ right: string;
59
+ rightBottom: string;
60
+ bottom: string;
61
+ leftBottom: string;
62
+ left: string;
63
+ leftTop: string;
64
+ };
65
+ export type PPage = {
66
+ size: 'A4' | 'LETTER';
67
+ layout: 'portrait' | 'landscape';
68
+ margins: PBoundParams;
69
+ };
70
+ export type PFont = {
71
+ name: string;
72
+ bold: boolean;
73
+ oblique: boolean;
74
+ size: number;
75
+ color: string;
76
+ };
77
+ export type PImage = {
78
+ filePath: string | Buffer;
79
+ left?: number;
80
+ top?: number;
81
+ } & ({
82
+ width?: number;
83
+ } | {
84
+ height?: number;
85
+ } | {
86
+ width?: number;
87
+ height?: number;
88
+ fill?: 'stretch';
89
+ } | {
90
+ width?: number;
91
+ height?: number;
92
+ fill?: 'proportional' | 'cover';
93
+ hAlign?: 'left' | 'center' | 'right';
94
+ vAlign?: 'top' | 'center' | 'bottom';
95
+ });
96
+ export type PRectangle = {
97
+ left: number;
98
+ top: number;
99
+ width: number;
100
+ height: number;
101
+ stroke?: {
102
+ width?: number;
103
+ color?: PBorderColorParams;
104
+ };
105
+ fillColor?: string;
106
+ border?: boolean | {
107
+ left?: boolean;
108
+ right?: boolean;
109
+ top?: boolean;
110
+ bottom?: boolean;
111
+ };
112
+ radius?: PRadiusParams;
113
+ };
114
+ export type PTextParams = {
115
+ chars: string;
116
+ font?: Partial<PFont>;
117
+ };
118
+ type PLetter = {
119
+ char: string;
120
+ left: number;
121
+ top: number;
122
+ width: number;
123
+ height: number;
124
+ font: PFont;
125
+ };
126
+ export type PCell = {
127
+ letters: PLetter[];
128
+ left: number;
129
+ top: number;
130
+ height: number;
131
+ width: number;
132
+ padding: PBound;
133
+ border: PBorder;
134
+ radius: PRadius;
135
+ stroke?: {
136
+ width?: number;
137
+ color?: PBorderColorParams;
138
+ };
139
+ fillColor?: string;
140
+ hAlign?: 'left' | 'center' | 'right';
141
+ vAlign?: 'top' | 'center' | 'bottom';
142
+ };
143
+ export type PCellParams = {
144
+ text: null | undefined | number | string | PTextParams | PTextParams[];
145
+ left?: number;
146
+ top?: number;
147
+ width?: 'auto' | number;
148
+ font?: Partial<PFont>;
149
+ fillColor?: string;
150
+ padding?: PBoundParams;
151
+ border?: PBorderParams;
152
+ radius?: PRadiusParams;
153
+ stroke?: {
154
+ width?: number;
155
+ color?: PBorderColorParams;
156
+ };
157
+ hAlign?: 'left' | 'center' | 'right';
158
+ vAlign?: 'top' | 'center' | 'bottom';
159
+ spacing?: number;
160
+ complete?: boolean;
161
+ } & ({
162
+ height: number;
163
+ } | {
164
+ autojump: true | undefined;
165
+ });
166
+ export type PRow = {
167
+ cells: Omit<PCell, 'autojump'>[];
168
+ gap?: number;
169
+ left: number;
170
+ top: number;
171
+ width: number;
172
+ height: number;
173
+ padding: PBound;
174
+ border: PBorder;
175
+ radius: PRadius;
176
+ stroke?: {
177
+ width?: number;
178
+ color?: PBorderColorParams;
179
+ };
180
+ fillColor?: string;
181
+ };
182
+ export type PRowParams = {
183
+ cells: (Omit<PCellParams, 'width' | 'left' | 'top' | 'height' | 'autojump'> & ({
184
+ width: number;
185
+ } | {
186
+ weight: number;
187
+ }))[];
188
+ gap?: number;
189
+ left?: number;
190
+ top?: number;
191
+ padding?: PBoundParams;
192
+ border?: PBorderParams;
193
+ radius?: PRadiusParams;
194
+ font?: Partial<PFont>;
195
+ stroke?: {
196
+ width?: number;
197
+ color?: PBorderColorParams;
198
+ };
199
+ fillColor?: string;
200
+ width?: 'full' | 'auto' | number;
201
+ autojump?: boolean;
202
+ complete?: boolean;
203
+ };
204
+ export type PTableParams = {
205
+ rows: Omit<PRowParams, 'left' | 'top' | 'width' | 'autojump'>[];
206
+ gap?: number;
207
+ left?: number;
208
+ top?: number;
209
+ padding?: PBoundParams;
210
+ border?: PBorderParams;
211
+ radius?: PRadiusParams;
212
+ font?: Partial<PFont>;
213
+ stroke?: {
214
+ width?: number;
215
+ color?: PBorderColorParams;
216
+ };
217
+ fillColor?: string;
218
+ width?: 'full' | 'auto' | number;
219
+ autojump?: boolean;
220
+ };
221
+ export type PTable = {
222
+ rows: PRow[];
223
+ gap?: number;
224
+ left: number;
225
+ top: number;
226
+ width: number;
227
+ height: number;
228
+ padding: PBound;
229
+ border: PBorder;
230
+ radius: PRadius;
231
+ stroke?: {
232
+ width?: number;
233
+ color?: PBorderColorParams;
234
+ };
235
+ fillColor?: string;
236
+ };
237
+ export declare class PPdf {
238
+ private cursor;
239
+ private configPage;
240
+ private engine;
241
+ template?: () => void;
242
+ private currentFont;
243
+ get title(): string;
244
+ set title(value: string);
245
+ get page(): {
246
+ readonly height: number;
247
+ readonly width: number;
248
+ };
249
+ private capturedFont?;
250
+ private captureFont;
251
+ private restoreFont;
252
+ setFont(font: Partial<PFont>): void;
253
+ constructor(params?: {
254
+ title?: string;
255
+ page?: Partial<PPage>;
256
+ template?: () => void;
257
+ addFirstPage?: boolean;
258
+ });
259
+ addPage(config?: Partial<PPage>): void;
260
+ rectangleTo(params: Omit<PRectangle, 'width' | 'height'>): void;
261
+ rectangle(params: PRectangle): void;
262
+ build(): PDFKit.PDFDocument;
263
+ table(params: PTableParams): void;
264
+ private drawTable;
265
+ private prepareRow;
266
+ private drawRow;
267
+ row(params: PRowParams): void;
268
+ setCursorLeftPosition(value: number | 'left'): this;
269
+ moveCursorLeft(value: number): this;
270
+ setCursorTopPosition(value: number | 'top'): this;
271
+ moveCursorTop(value: number, autoJump?: boolean): this;
272
+ getCursorLeftPosition(): number;
273
+ getCursorTopPosition(): number;
274
+ getBottomSpaceQuantity(): number;
275
+ getPageWidth(): number;
276
+ private verticalAlign;
277
+ private prepareCell;
278
+ private drawCell;
279
+ cell(params: PCellParams): void;
280
+ image(params: PImage): void;
281
+ private prepareBoundValues;
282
+ private prepareRadiusValues;
283
+ private prepareBorderValues;
284
+ private prepareBorderColorValues;
285
+ }
286
+ export {};
287
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,811 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PPdf = void 0;
7
+ const pdfkit_1 = __importDefault(require("pdfkit"));
8
+ const pols_utils_1 = require("pols-utils");
9
+ class PPdf {
10
+ cursor = {
11
+ left: 0,
12
+ top: 0
13
+ };
14
+ configPage;
15
+ engine;
16
+ template;
17
+ currentFont = {
18
+ name: 'Helvetica',
19
+ bold: false,
20
+ oblique: false,
21
+ size: 10,
22
+ color: '#000000'
23
+ };
24
+ get title() {
25
+ return this.engine.info.Title ?? '';
26
+ }
27
+ set title(value) {
28
+ this.engine.info.Title = value;
29
+ }
30
+ get page() {
31
+ return {
32
+ height: this.engine.page.height,
33
+ width: this.engine.page.width
34
+ };
35
+ }
36
+ capturedFont;
37
+ captureFont() {
38
+ this.capturedFont = this.currentFont;
39
+ }
40
+ restoreFont() {
41
+ if (this.capturedFont)
42
+ this.currentFont = this.capturedFont;
43
+ }
44
+ /* Establece en el engine la fuente especificada en currentFont */
45
+ setFont(font) {
46
+ const finalFont = {
47
+ name: font.name ?? this.currentFont.name,
48
+ bold: font.bold ?? this.currentFont.bold,
49
+ oblique: font.oblique ?? this.currentFont.oblique,
50
+ size: font.size ?? this.currentFont.size,
51
+ color: font.color ?? this.currentFont.color,
52
+ };
53
+ let fontName = this.currentFont.name;
54
+ if (finalFont.bold || finalFont.oblique) {
55
+ fontName += `-${finalFont.bold ? 'Bold' : ''}${finalFont.oblique ? 'Oblique' : ''}`;
56
+ }
57
+ this.engine.font(fontName, finalFont.size);
58
+ this.currentFont = finalFont;
59
+ }
60
+ constructor(params) {
61
+ this.engine = new pdfkit_1.default({ autoFirstPage: false });
62
+ this.engine.info.Title = params?.title ?? 'pdf';
63
+ this.setFont(this.currentFont);
64
+ this.template = params?.template?.bind(this);
65
+ this.configPage = {
66
+ size: 'A4',
67
+ layout: 'portrait',
68
+ ...(params?.page ?? {}),
69
+ margins: this.prepareBoundValues(params?.page?.margins, {
70
+ left: 20,
71
+ top: 20,
72
+ right: 20,
73
+ bottom: 20
74
+ })
75
+ };
76
+ if (params?.addFirstPage == null || params.addFirstPage)
77
+ this.addPage();
78
+ }
79
+ addPage(config) {
80
+ const finalConfig = {
81
+ ...this.configPage,
82
+ ...(config ?? {}),
83
+ margins: this.prepareBoundValues(config?.margins ?? this.configPage.margins, {
84
+ left: 20,
85
+ top: 20,
86
+ right: 20,
87
+ bottom: 20
88
+ })
89
+ };
90
+ this.engine.addPage(finalConfig);
91
+ this.configPage = finalConfig;
92
+ this.cursor.top = finalConfig.margins.top;
93
+ this.template?.();
94
+ }
95
+ rectangleTo(params) {
96
+ this.rectangle({
97
+ ...params,
98
+ left: this.cursor.left,
99
+ top: this.cursor.top,
100
+ width: params.left - this.cursor.left,
101
+ height: params.top - this.cursor.top,
102
+ });
103
+ }
104
+ rectangle(params) {
105
+ this.engine.lineWidth(params.stroke?.width ?? 0);
106
+ const border = this.prepareBorderValues(params.border);
107
+ const radius = this.prepareRadiusValues(params.radius);
108
+ radius.leftBottom = Math.min(radius.leftBottom, params.height / 2, params.width / 2);
109
+ radius.leftTop = Math.min(radius.leftTop, params.height / 2, params.width / 2);
110
+ radius.rightBottom = Math.min(radius.rightBottom, params.height / 2, params.width / 2);
111
+ radius.rightTop = Math.min(radius.rightTop, params.height / 2, params.width / 2);
112
+ if (params.fillColor) {
113
+ this.engine.path('M' + `${params.left + radius.leftTop},${params.top} `
114
+ + `L${params.left + params.width - radius.rightTop},${params.top} `
115
+ + (radius.rightTop ? `A${radius.rightTop},${radius.rightTop} 0 0 1 ${params.left + params.width},${params.top + radius.rightTop} ` : ' ')
116
+ + `L${params.left + params.width},${params.top + params.height - radius.rightBottom} `
117
+ + (radius.rightBottom ? `A${radius.rightBottom},${radius.rightBottom} 0 0 1 ${params.left + params.width - radius.rightBottom},${params.top + params.height} ` : ' ')
118
+ + `L${params.left + radius.leftBottom},${params.top + params.height} `
119
+ + (radius.leftBottom ? `A${radius.leftBottom},${radius.leftBottom} 0 0 1 ${params.left},${params.top + params.height - radius.leftBottom} ` : ' ')
120
+ + `L${params.left},${params.top + radius.leftTop} `
121
+ + (radius.leftTop ? `A${radius.leftTop},${radius.leftTop} 0 0 1 ${params.left + radius.leftTop},${params.top} ` : ' ')
122
+ + '').fill(params.fillColor);
123
+ }
124
+ const color = this.prepareBorderColorValues(params.stroke?.color);
125
+ if (border.top)
126
+ this.engine.path(`M${params.left + radius.leftTop},${params.top} L${params.left + params.width - radius.rightTop},${params.top}`).stroke(color.top);
127
+ if (border.rightTop && radius.rightTop)
128
+ this.engine.path(`M${params.left + params.width - radius.rightTop},${params.top} A${radius.rightTop},${radius.rightTop} 0 0 1 ${params.left + params.width},${params.top + radius.rightTop}`).stroke(color.rightTop);
129
+ if (border.right)
130
+ this.engine.path(`M${params.left + params.width},${params.top + radius.rightTop} L${params.left + params.width},${params.top + params.height - radius.rightBottom}`).stroke(color.right);
131
+ if (border.rightBottom && radius.rightBottom)
132
+ this.engine.path(`
133
+ M${params.left + params.width},${params.top + params.height - radius.rightBottom}
134
+ A${radius.rightBottom},${radius.rightBottom} 0 0 1 ${params.left + params.width - radius.rightBottom},${params.top + params.height}
135
+ `).stroke(color.rightBottom);
136
+ if (border.bottom)
137
+ this.engine.path(`M${params.left + params.width - radius.rightBottom},${params.top + params.height} L${params.left + radius.leftBottom},${params.top + params.height}`).stroke(color.bottom);
138
+ if (border.leftBottom && radius.leftBottom)
139
+ this.engine.path(`M${params.left + radius.leftBottom},${params.top + params.height} A${radius.leftBottom},${radius.leftBottom} 0 0 1 ${params.left},${params.top + params.height - radius.leftBottom}`).stroke(color.leftBottom);
140
+ if (border.left)
141
+ this.engine.path(`M${params.left},${params.top + params.height - radius.leftBottom} L${params.left},${params.top + radius.leftTop}`).stroke(color.left);
142
+ if (border.leftTop && radius.leftTop)
143
+ this.engine.path(`M${params.left},${params.top + radius.leftTop} A${radius.leftTop},${radius.leftTop} 0 0 1 ${params.left + radius.leftTop},${params.top}`).stroke(color.leftTop);
144
+ this.cursor.top = params.top + params.height;
145
+ this.cursor.left = params.left + params.width;
146
+ }
147
+ /* PDFDocument ya es un stream.Readable; "end" cierra la escritura y lo deja listo para ser consumido/canalizado por el llamador. */
148
+ build() {
149
+ this.engine.end();
150
+ return this.engine;
151
+ }
152
+ table(params) {
153
+ const width = (() => {
154
+ if (params.width == null || params.width == 'auto') {
155
+ return this.engine.page.width - this.engine.page.margins.right - (params.left ?? this.cursor.left);
156
+ }
157
+ else if (params.width == 'full') {
158
+ return this.engine.page.width - this.engine.page.margins.right - this.engine.page.margins.left;
159
+ }
160
+ else {
161
+ return params.width;
162
+ }
163
+ })();
164
+ const left = params.left ?? this.cursor.left;
165
+ const top = params.top ?? this.cursor.top;
166
+ const padding = this.prepareBoundValues(params.padding);
167
+ const gap = params.gap ?? 0;
168
+ let cursorTop = top + padding.top;
169
+ const usefulWidth = width - padding.left - padding.right;
170
+ const buffer = {
171
+ ...params,
172
+ rows: [],
173
+ left,
174
+ top,
175
+ padding,
176
+ border: this.prepareBorderValues(params.border),
177
+ radius: this.prepareRadiusValues(params.radius),
178
+ width,
179
+ height: 0 /* valor provisional */
180
+ };
181
+ let i = 0;
182
+ let rowToDraw = params.rows[i];
183
+ while (rowToDraw) {
184
+ const preparedRow = this.prepareRow({
185
+ ...rowToDraw,
186
+ left: left + padding.left,
187
+ top: cursorTop,
188
+ width: usefulWidth,
189
+ font: params.font,
190
+ autojump: (params.autojump ?? true) ? true : undefined
191
+ });
192
+ if (preparedRow.currentPage.cells.some(cell => cell.letters?.length)) {
193
+ buffer.rows.push(preparedRow.currentPage);
194
+ }
195
+ /* Si la fila actual se rompe, dibuja el buffer de la tabla */
196
+ if (preparedRow.nextPage.cells.some(cell => cell.text)) {
197
+ const lastRow = buffer.rows[buffer.rows.length - 1];
198
+ buffer.height = lastRow ? ((lastRow?.top ?? 0) + (lastRow?.height ?? 0) - buffer.top + padding.bottom) : 0;
199
+ this.drawTable(buffer);
200
+ this.addPage();
201
+ rowToDraw = preparedRow.nextPage;
202
+ buffer.rows = [];
203
+ buffer.top = this.cursor.top;
204
+ cursorTop = this.cursor.top + padding.top;
205
+ }
206
+ else {
207
+ i++;
208
+ rowToDraw = params.rows[i];
209
+ cursorTop += preparedRow.currentPage.height + gap;
210
+ }
211
+ }
212
+ if (buffer.rows.length) {
213
+ const lastRow = buffer.rows[buffer.rows.length - 1];
214
+ buffer.height = lastRow.top + lastRow.height - buffer.top + padding.bottom;
215
+ this.drawTable(buffer);
216
+ }
217
+ this.cursor.left = left;
218
+ }
219
+ drawTable(params) {
220
+ /* Dibuja el borde. Este bloque podría ignorarse si al final no se escribirá ningún caracter debido al salto de página */
221
+ if (params.border || params.fillColor) {
222
+ this.rectangle({
223
+ left: params.left,
224
+ top: params.top,
225
+ width: params.width,
226
+ height: params.height,
227
+ border: params.border,
228
+ radius: params.radius,
229
+ fillColor: params.fillColor,
230
+ stroke: params.stroke
231
+ });
232
+ }
233
+ /* Dibujo de las celdas */
234
+ for (const row of params.rows) {
235
+ this.drawRow(row);
236
+ }
237
+ this.setCursorTopPosition(params.top + params.height);
238
+ }
239
+ prepareRow(params) {
240
+ const width = (() => {
241
+ if (params.width == null || params.width == 'auto') {
242
+ return this.engine.page.width - this.engine.page.margins.right - (params.left ?? this.cursor.left);
243
+ }
244
+ else if (params.width == 'full') {
245
+ return this.engine.page.width - this.engine.page.margins.right - this.engine.page.margins.left;
246
+ }
247
+ else {
248
+ return params.width;
249
+ }
250
+ })();
251
+ const left = params.left ?? this.cursor.left;
252
+ const top = params.top ?? this.cursor.top;
253
+ const padding = this.prepareBoundValues(params.padding);
254
+ const response = {
255
+ currentPage: {
256
+ ...params,
257
+ cells: [],
258
+ left,
259
+ top,
260
+ padding,
261
+ border: this.prepareBorderValues(params.border),
262
+ radius: this.prepareRadiusValues(params.radius),
263
+ width,
264
+ height: 0 /* valor provisional */
265
+ },
266
+ nextPage: {
267
+ ...params,
268
+ cells: []
269
+ }
270
+ };
271
+ const gap = params.gap ?? 0;
272
+ let cursorLeft = left + padding.left;
273
+ const usedWidth = params.cells.reduce((ac, cell) => ac + (('width' in cell && cell.width != null) ? cell.width : 0), 0);
274
+ const usefulWidth = width - padding.left - padding.right - gap * (params.cells.length - 1) - usedWidth;
275
+ const totalWeight = params.cells.reduce((ac, cell) => ac + (('weight' in cell && cell.weight != null) ? cell.weight : 0), 0);
276
+ for (const cell of params.cells) {
277
+ let cellWidth = 0;
278
+ if ('width' in cell && cell.width != null) {
279
+ cellWidth = cell.width;
280
+ }
281
+ else if ('weight' in cell && cell.weight != null) {
282
+ cellWidth = usefulWidth * cell.weight / totalWeight;
283
+ }
284
+ const preparedCell = this.prepareCell({
285
+ ...cell,
286
+ left: cursorLeft,
287
+ top: top + padding.top,
288
+ width: cellWidth,
289
+ font: cell.font ?? params.font,
290
+ autojump: (params.autojump ?? true) ? true : undefined,
291
+ complete: params.complete
292
+ });
293
+ response.currentPage.cells.push(preparedCell.currentPage);
294
+ response.nextPage.cells.push({
295
+ ...cell,
296
+ text: undefined,
297
+ ...preparedCell.nextPage,
298
+ ...('width' in cell ? { width: cell.width } : { weight: cell.weight })
299
+ });
300
+ cursorLeft += preparedCell.currentPage.width + gap;
301
+ }
302
+ /* Efectúa la alineación vertical en cada celda */
303
+ const height = Math.max(...response.currentPage.cells.map(cell => cell.height));
304
+ for (const [i, cell] of response.currentPage.cells.entries()) {
305
+ if (params.cells[i].vAlign != null)
306
+ this.verticalAlign(cell.letters, params.cells[i].vAlign, height, cell.padding);
307
+ cell.height = height;
308
+ }
309
+ response.currentPage.height = height + padding.top + padding.bottom;
310
+ return response;
311
+ }
312
+ drawRow(params) {
313
+ /* Dibuja el borde. Este bloque podría ignorarse si al final no se escribirá ningún caracter debido al salto de página */
314
+ if (params.border || params.fillColor) {
315
+ this.rectangle({
316
+ left: params.left,
317
+ top: params.top,
318
+ width: params.width,
319
+ height: params.height,
320
+ border: params.border,
321
+ radius: params.radius,
322
+ fillColor: params.fillColor,
323
+ stroke: params.stroke
324
+ });
325
+ }
326
+ /* Dibujo de las celdas */
327
+ for (const cell of params.cells) {
328
+ this.drawCell(cell);
329
+ }
330
+ this.setCursorTopPosition(params.top + params.height);
331
+ }
332
+ row(params) {
333
+ let paramsToDraw = params;
334
+ while (paramsToDraw) {
335
+ const prepared = this.prepareRow(paramsToDraw);
336
+ this.drawRow(prepared.currentPage);
337
+ if (prepared.nextPage.cells.some(cell => cell.text)) {
338
+ this.addPage();
339
+ paramsToDraw = {
340
+ ...prepared.nextPage,
341
+ top: this.cursor.top
342
+ };
343
+ }
344
+ else {
345
+ paramsToDraw = null;
346
+ }
347
+ }
348
+ }
349
+ setCursorLeftPosition(value) {
350
+ let n;
351
+ if (value == 'left') {
352
+ n = this.engine.page.margins.left;
353
+ }
354
+ else {
355
+ n = value;
356
+ }
357
+ this.cursor.left = n;
358
+ return this;
359
+ }
360
+ moveCursorLeft(value) {
361
+ this.cursor.left += value;
362
+ return this;
363
+ }
364
+ setCursorTopPosition(value) {
365
+ let n;
366
+ if (value == 'top') {
367
+ n = this.engine.page.margins.top;
368
+ }
369
+ else {
370
+ n = value;
371
+ }
372
+ this.cursor.top = n;
373
+ return this;
374
+ }
375
+ moveCursorTop(value, autoJump = true) {
376
+ this.cursor.top += value;
377
+ if (autoJump) {
378
+ if (this.cursor.top >= this.engine.page.height - this.configPage.margins.bottom)
379
+ this.addPage();
380
+ }
381
+ return this;
382
+ }
383
+ getCursorLeftPosition() {
384
+ return this.cursor.left;
385
+ }
386
+ getCursorTopPosition() {
387
+ return this.cursor.top;
388
+ }
389
+ getBottomSpaceQuantity() {
390
+ return this.page.height - this.cursor.top;
391
+ }
392
+ getPageWidth() {
393
+ return this.engine.page.width;
394
+ }
395
+ verticalAlign(letters, vAlign, height, padding) {
396
+ if (vAlign != 'top') {
397
+ const charTop = Math.min(...letters.map(letter => letter.top));
398
+ const textHeight = Math.max(...letters.map(letter => letter.top + letter.height)) - charTop;
399
+ const delta = height - padding.top - padding.bottom - textHeight;
400
+ /* Observa la distancia entre el último caracter y el ancho */
401
+ for (const letter of letters) {
402
+ letter.top += vAlign == 'bottom' ? delta : (delta / 2);
403
+ }
404
+ }
405
+ }
406
+ prepareCell(params) {
407
+ const text = [];
408
+ if (typeof params.text == 'string') {
409
+ text.push({ chars: params.text });
410
+ }
411
+ else if (typeof params.text == 'number') {
412
+ text.push({ chars: params.text.toString() });
413
+ }
414
+ else if (params.text == null) {
415
+ text.push({ chars: '' });
416
+ }
417
+ else if (params.text instanceof Array) {
418
+ if (params.text.length) {
419
+ text.push(...params.text);
420
+ }
421
+ else {
422
+ text.push({ chars: '' });
423
+ }
424
+ }
425
+ const letters = [];
426
+ const spacesIndexes = [];
427
+ const width = params.width == 'auto' ? this.engine.page.width - this.engine.page.margins.right - (params.left ?? this.cursor.left) : params.width;
428
+ /* Captura las coordenadas de inicio de celda */
429
+ const left = params.left ?? this.cursor.left;
430
+ const top = params.top ?? this.cursor.top;
431
+ /* Calcula el padding */
432
+ const padding = this.prepareBoundValues(params.padding, {
433
+ left: 0,
434
+ top: 0,
435
+ right: 0,
436
+ bottom: 0
437
+ });
438
+ /* Separa cada caracter por un objeto independiente identificando sus dimensiones, posición, fuente y color */
439
+ let charLeftPosition = left + padding.left;
440
+ const charTopStartPosition = top + padding.top;
441
+ for (const t of text) {
442
+ /* Almacena el estado anterior de la fuente para restablcerla. */
443
+ this.captureFont();
444
+ const font = {
445
+ name: t.font?.name ?? params.font?.name ?? this.currentFont.name,
446
+ bold: t.font?.bold ?? params.font?.bold ?? this.currentFont.bold,
447
+ oblique: t.font?.oblique ?? params.font?.oblique ?? this.currentFont.oblique,
448
+ size: t.font?.size ?? params.font?.size ?? this.currentFont.size,
449
+ color: t.font?.color ?? params.font?.color ?? this.currentFont.color,
450
+ };
451
+ this.setFont(font);
452
+ if (t.chars == null)
453
+ t.chars = '';
454
+ /* Sustituye los tabs por espacios regulares. */
455
+ t.chars = t.chars.replace(/\t/g, ' ');
456
+ for (let i = 0; i < t.chars.length; i++) {
457
+ const char = t.chars[i];
458
+ const charWidth = pols_utils_1.PUtilsNumber.round(this.engine.widthOfString(char), 6);
459
+ const charHeight = pols_utils_1.PUtilsNumber.round(this.engine.heightOfString(char), 6);
460
+ letters.push({
461
+ left: charLeftPosition,
462
+ top: charTopStartPosition,
463
+ char,
464
+ width: charWidth,
465
+ height: charHeight,
466
+ font,
467
+ });
468
+ charLeftPosition += charWidth;
469
+ if (char === ' ')
470
+ spacesIndexes.push(letters.length - 1);
471
+ }
472
+ this.restoreFont();
473
+ }
474
+ /* Si se ha definido el ancho de un párrafo, se determina dónde se puede hacer salto de línea */
475
+ /* Reinicia el inicio del primer caracter */
476
+ charLeftPosition = left + padding.left;
477
+ let lastSpaceIndex = null;
478
+ let lastSpaceSet = null;
479
+ let i = 0;
480
+ while (i < letters.length) {
481
+ const letter = letters[i];
482
+ letter.left = charLeftPosition;
483
+ if (letter.char != ' ' && width != null && (letter.left + letter.width > width + left - padding.right)) {
484
+ /* Si la posición excede el ancho de la celda, se busca el último espacio insertado, si no se ha insertado ningún espacio, el script continua */
485
+ if (lastSpaceIndex == null && i > 0)
486
+ lastSpaceIndex = i - 1;
487
+ if (lastSpaceIndex != null) {
488
+ if (lastSpaceIndex !== lastSpaceSet) {
489
+ i = lastSpaceIndex;
490
+ lastSpaceSet = lastSpaceIndex;
491
+ }
492
+ }
493
+ else {
494
+ letter.left = left + padding.left;
495
+ }
496
+ charLeftPosition = left + padding.left;
497
+ }
498
+ else if (letter.char == '\n') {
499
+ charLeftPosition = left + padding.left;
500
+ }
501
+ else {
502
+ charLeftPosition += letter.width;
503
+ }
504
+ if ([' ', '-'].includes(letter.char))
505
+ lastSpaceIndex = i;
506
+ i++;
507
+ }
508
+ /* Inserta cada caracter en un arreglo diferente según la línea que ocupa */
509
+ const lines = [[]];
510
+ let currentLine = 0;
511
+ for (const [i, letter] of letters.entries()) {
512
+ if (i > 0 && letter.left == left + padding.left) {
513
+ /* Si la letra tiene como posición */
514
+ lines.push([]);
515
+ currentLine++;
516
+ }
517
+ lines[currentLine].push(letter);
518
+ }
519
+ /* Define la posición vertical de cada letra en su respectiva línea */
520
+ let charTopPosition = charTopStartPosition;
521
+ let isFirstLine = true;
522
+ for (const line of lines) {
523
+ const maxHeight = Math.max(...line.map(letter => letter.height));
524
+ for (const letter of line) {
525
+ letter.top = charTopPosition - 0.4 + (!isFirstLine ? (params.spacing ?? 0) : 0);
526
+ /* Corrige la alineación vertical de las letras */
527
+ if (letter.height < maxHeight)
528
+ letter.top += maxHeight - letter.height;
529
+ }
530
+ charTopPosition = Math.max(...line.map(letter => letter.height + letter.top));
531
+ isFirstLine = false;
532
+ }
533
+ /* Elimina los espacios finales de cada línea */
534
+ for (const line of lines) {
535
+ if (line[line.length - 1]?.char == ' ')
536
+ line.splice(line.length - 1, 1);
537
+ }
538
+ const paragraphs = {
539
+ currentPage: [],
540
+ nextPage: []
541
+ };
542
+ let currentParagraph = 'currentPage';
543
+ /* Identifica qué líneas se desbordan de la página y las marca con "nextPage" a true */
544
+ for (const line of lines) {
545
+ const firstLetterOfLine = line[0];
546
+ if (!firstLetterOfLine)
547
+ continue;
548
+ /* Si la letra se desborda, se crea un nuevo párrafo */
549
+ if ('autojump' in params && currentParagraph != 'nextPage' && firstLetterOfLine.top + firstLetterOfLine.height + padding.bottom > (this.engine.page.height - this.engine.page.margins.bottom)) {
550
+ if (params.complete) {
551
+ paragraphs.nextPage = paragraphs.currentPage;
552
+ paragraphs.currentPage = [];
553
+ }
554
+ currentParagraph = 'nextPage';
555
+ }
556
+ paragraphs[currentParagraph].push(line);
557
+ }
558
+ /* Realiza la alineación horizontal */
559
+ if (width != null && (params.hAlign ?? 'left') != 'left') {
560
+ /* Observa la distancia entre el último caracter y el ancho */
561
+ for (const line of lines) {
562
+ if (!line.length)
563
+ continue;
564
+ const letter = line[line.length - 1];
565
+ const delta = left + width - padding.right - (letter.left + letter.width);
566
+ for (const letter of line) {
567
+ letter.left += params.hAlign === 'right' ? delta : (delta / 2);
568
+ }
569
+ }
570
+ }
571
+ /* Realiza la alineación vertical sólo para el primer párrafo */
572
+ if ('height' in params && (params.vAlign ?? 'top') != 'top') {
573
+ this.verticalAlign(paragraphs.currentPage.flat(), params.vAlign ?? 'top', params.height, padding);
574
+ }
575
+ const letterForCurrentPage = paragraphs.currentPage.flat();
576
+ const lastLetterForCurrentPage = letterForCurrentPage[letterForCurrentPage.length - 1];
577
+ const finalWidth = width ?? (Math.max(...lines.map(line => line.reduce((ac, letter) => ac + letter.width, 0))) + padding.left + padding.right);
578
+ return {
579
+ currentPage: {
580
+ letters: letterForCurrentPage,
581
+ left,
582
+ top,
583
+ width: finalWidth,
584
+ height: 'height' in params ? params.height : (lastLetterForCurrentPage == null ? top : (lastLetterForCurrentPage.top + lastLetterForCurrentPage.height)) - top + padding.bottom,
585
+ padding,
586
+ border: this.prepareBorderValues(params.border),
587
+ radius: this.prepareRadiusValues(params.radius),
588
+ stroke: params.stroke,
589
+ fillColor: params.fillColor
590
+ },
591
+ nextPage: paragraphs.nextPage.length ? {
592
+ ...params,
593
+ text: (() => {
594
+ const letters = [];
595
+ for (const [i, line] of paragraphs.nextPage.entries()) {
596
+ letters.push(...line.map(letter => ({
597
+ chars: letter.char,
598
+ font: letter.font
599
+ })));
600
+ if (i < paragraphs.nextPage.length - 1 && letters[letters.length - 1].chars != '\n') {
601
+ letters.push({
602
+ chars: '\n',
603
+ font: letters[letters.length - 1].font
604
+ });
605
+ }
606
+ }
607
+ return letters;
608
+ })(),
609
+ width: finalWidth
610
+ } : undefined
611
+ };
612
+ }
613
+ drawCell(params) {
614
+ /* Dibuja el borde. Este bloque podría ignorarse si al final no se escribirá ningún caracter debido al salto de página */
615
+ if (params.border || params.fillColor) {
616
+ this.rectangle({
617
+ left: params.left,
618
+ top: params.top,
619
+ width: params.width,
620
+ height: params.height,
621
+ border: params.border,
622
+ radius: params.radius,
623
+ fillColor: params.fillColor,
624
+ stroke: params.stroke
625
+ });
626
+ }
627
+ /* Dibujo de las letras */
628
+ let moveCursorTop = 0;
629
+ for (const letter of params.letters) {
630
+ /* Almacena el estado anterior de la fuente para restablcerla. */
631
+ this.captureFont();
632
+ this.setFont(letter.font);
633
+ let deltaFix = 0;
634
+ if ((letter.font.name ?? this.currentFont.name) == 'Helvetica') {
635
+ deltaFix = (letter.font.size ?? this.currentFont.size) * 0.2;
636
+ }
637
+ this.engine.fillColor(letter.font.color ?? this.currentFont.color).text(letter.char, letter.left, letter.top + deltaFix + (letter.font.name == 'Symbol' ? (letter.height * 0.6) : 0), { lineBreak: false });
638
+ moveCursorTop = Math.max(moveCursorTop, letter.top + letter.height);
639
+ this.restoreFont();
640
+ }
641
+ this.setCursorTopPosition(params.top + params.height);
642
+ }
643
+ cell(params) {
644
+ let paramsToDraw = params;
645
+ while (paramsToDraw) {
646
+ const prepared = this.prepareCell(paramsToDraw);
647
+ this.drawCell(prepared.currentPage);
648
+ if (prepared.nextPage) {
649
+ this.addPage();
650
+ paramsToDraw = {
651
+ ...prepared.nextPage,
652
+ top: this.cursor.top
653
+ };
654
+ }
655
+ else {
656
+ paramsToDraw = null;
657
+ }
658
+ }
659
+ }
660
+ image(params) {
661
+ const options = {};
662
+ if ('fill' in params) {
663
+ if (params.fill == 'proportional') {
664
+ options.fit = [params.width, params.height];
665
+ }
666
+ else if (params.fill == 'cover') {
667
+ options.cover = [params.width, params.height];
668
+ }
669
+ if ('vAlign' in params)
670
+ options.valign = params.vAlign == 'top' ? undefined : params.vAlign;
671
+ if ('hAlign' in params)
672
+ options.align = params.hAlign == 'left' ? undefined : params.hAlign;
673
+ }
674
+ else {
675
+ if ('width' in params)
676
+ options.width = params.width;
677
+ if ('height' in params)
678
+ options.height = params.height;
679
+ }
680
+ this.engine.image(params.filePath, params.left, params.top, options);
681
+ this.cursor.left = this.engine.x;
682
+ this.cursor.top = this.engine.y;
683
+ }
684
+ prepareBoundValues(value, defa = {
685
+ left: 0,
686
+ top: 0,
687
+ bottom: 0,
688
+ right: 0
689
+ }) {
690
+ if (value != null) {
691
+ if (typeof value == 'number') {
692
+ return {
693
+ left: value,
694
+ right: value,
695
+ top: value,
696
+ bottom: value,
697
+ };
698
+ }
699
+ else {
700
+ return {
701
+ top: value.top ?? defa?.top ?? 0,
702
+ right: value.right ?? defa?.right ?? 0,
703
+ bottom: value.bottom ?? defa?.bottom ?? 0,
704
+ left: value.left ?? defa?.left ?? 0,
705
+ };
706
+ }
707
+ }
708
+ else {
709
+ return defa;
710
+ }
711
+ }
712
+ prepareRadiusValues(value, defa = {
713
+ leftTop: 0,
714
+ leftBottom: 0,
715
+ rightTop: 0,
716
+ rightBottom: 0,
717
+ }) {
718
+ if (value != null) {
719
+ if (typeof value == 'number') {
720
+ return {
721
+ leftTop: value,
722
+ rightTop: value,
723
+ leftBottom: value,
724
+ rightBottom: value,
725
+ };
726
+ }
727
+ else {
728
+ return {
729
+ leftTop: value.leftTop ?? defa?.leftTop ?? 0,
730
+ rightTop: value.rightTop ?? defa?.rightTop ?? 0,
731
+ leftBottom: value.leftBottom ?? defa?.leftBottom ?? 0,
732
+ rightBottom: value.rightBottom ?? defa?.rightBottom ?? 0,
733
+ };
734
+ }
735
+ }
736
+ else {
737
+ return defa;
738
+ }
739
+ }
740
+ prepareBorderValues(value, defa = {
741
+ left: false,
742
+ right: false,
743
+ bottom: false,
744
+ top: false,
745
+ leftBottom: false,
746
+ leftTop: false,
747
+ rightBottom: false,
748
+ rightTop: false
749
+ }) {
750
+ if (value != null) {
751
+ if (typeof value == 'boolean') {
752
+ return {
753
+ leftTop: value,
754
+ top: value,
755
+ rightTop: value,
756
+ right: value,
757
+ rightBottom: value,
758
+ bottom: value,
759
+ leftBottom: value,
760
+ left: value,
761
+ };
762
+ }
763
+ else {
764
+ return {
765
+ leftTop: value.leftTop ?? defa?.leftTop ?? false,
766
+ top: value.top ?? defa?.top ?? false,
767
+ rightTop: value.rightTop ?? defa?.rightTop ?? false,
768
+ right: value.right ?? defa?.right ?? false,
769
+ rightBottom: value.rightBottom ?? defa?.rightBottom ?? false,
770
+ bottom: value.bottom ?? defa?.bottom ?? false,
771
+ leftBottom: value.leftBottom ?? defa?.leftBottom ?? false,
772
+ left: value.left ?? defa?.left ?? false,
773
+ };
774
+ }
775
+ }
776
+ else {
777
+ return defa;
778
+ }
779
+ }
780
+ prepareBorderColorValues(value, defa = {
781
+ top: 'black',
782
+ rightTop: 'black',
783
+ right: 'black',
784
+ rightBottom: 'black',
785
+ bottom: 'black',
786
+ leftBottom: 'black',
787
+ left: 'black',
788
+ leftTop: 'black',
789
+ }) {
790
+ return typeof value == 'string' ? {
791
+ top: value,
792
+ rightTop: value,
793
+ right: value,
794
+ rightBottom: value,
795
+ bottom: value,
796
+ leftBottom: value,
797
+ left: value,
798
+ leftTop: value,
799
+ } : {
800
+ top: value?.top ?? defa.top,
801
+ rightTop: value?.rightTop ?? defa.rightTop,
802
+ right: value?.right ?? defa.right,
803
+ rightBottom: value?.rightBottom ?? defa.rightBottom,
804
+ bottom: value?.bottom ?? defa.bottom,
805
+ leftBottom: value?.leftBottom ?? defa.leftBottom,
806
+ left: value?.left ?? defa.left,
807
+ leftTop: value?.leftTop ?? defa.leftTop,
808
+ };
809
+ }
810
+ }
811
+ exports.PPdf = PPdf;
@@ -0,0 +1,19 @@
1
+ import globals from "globals";
2
+ import pluginJs from "@eslint/js";
3
+ import tseslint from "typescript-eslint";
4
+
5
+ export default [
6
+ {
7
+ files: ["**/*.{ts}"],
8
+ },
9
+ { languageOptions: { globals: globals.node } },
10
+ pluginJs.configs.recommended,
11
+ ...tseslint.configs.recommended,
12
+ {
13
+ rules: {
14
+ "@typescript-eslint/no-unused-vars": "warn",
15
+ "@typescript-eslint/no-explicit-any": "off",
16
+ "no-use-before-define": "off"
17
+ }
18
+ }
19
+ ];
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "pols-pdf",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "npx tsc",
8
+ "lint": "eslint src/index.ts",
9
+ "test": "npx ts-node-dev -r tsconfig-paths/register --project tsconfig.json",
10
+ "export": "npm run build && npm publish"
11
+ },
12
+ "author": "Jean Paul Sánchez mendoza",
13
+ "license": "ISC",
14
+ "description": "",
15
+ "devDependencies": {
16
+ "@eslint/js": "^9.13.0",
17
+ "@types/node": "^22.7.9",
18
+ "@types/pdfkit": "^0.17.6",
19
+ "eslint": "^9.13.0",
20
+ "globals": "^15.11.0",
21
+ "ts-node-dev": "^2.0.0",
22
+ "tsconfig-paths": "^4.2.0",
23
+ "typescript": "^5.6.3",
24
+ "typescript-eslint": "^8.11.0"
25
+ },
26
+ "dependencies": {
27
+ "pdfkit": "^0.19.1",
28
+ "pols-utils": "^6.0.1"
29
+ }
30
+ }