nfe-lib-node 1.7.0 → 1.8.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 +46 -151
- package/dist/Danfe.d.ts +24 -5
- package/dist/Danfe.d.ts.map +1 -1
- package/dist/Danfe.js +660 -183
- package/dist/Danfe.js.map +1 -1
- package/dist/NFe.d.ts +3 -1
- package/dist/NFe.d.ts.map +1 -1
- package/dist/NFe.js +104 -4
- package/dist/NFe.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +18 -16
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/Danfe.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inflateSync, deflateSync } from 'node:zlib';
|
|
1
2
|
import { DOMParser } from '@xmldom/xmldom';
|
|
2
3
|
const CODE128_PATTERNS = [
|
|
3
4
|
'212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213',
|
|
@@ -13,23 +14,30 @@ const CODE128_PATTERNS = [
|
|
|
13
14
|
'114131', '311141', '411131', '211412', '211214', '211232', '2331112',
|
|
14
15
|
];
|
|
15
16
|
const COLUNAS_ITENS = [
|
|
16
|
-
{ x: 20, w:
|
|
17
|
-
{ x:
|
|
18
|
-
{ x: 217, w: 36, label: 'NCM/SH' },
|
|
19
|
-
{ x: 253, w: 28, label: 'O/CSOSN' },
|
|
20
|
-
{ x: 281, w: 24, label: 'CFOP' },
|
|
21
|
-
{ x: 305, w: 18, label: 'UN' },
|
|
22
|
-
{ x: 323, w: 34, label: 'QUANT.' },
|
|
23
|
-
{ x: 357, w: 36, label: '
|
|
24
|
-
{ x: 393, w: 36, label: '
|
|
25
|
-
{ x: 429, w: 34, label: 'B.
|
|
26
|
-
{ x: 463, w: 32, label: '
|
|
27
|
-
{ x: 495, w: 28, label: '
|
|
28
|
-
{ x: 523, w: 26, label: '
|
|
29
|
-
{ x: 549, w: 26, label: '
|
|
17
|
+
{ x: 20, w: 44, label: 'CÓDIGO PROD.', align: 'left' },
|
|
18
|
+
{ x: 64, w: 153, label: 'DESCRIÇÃO DO PRODUTO / SERVIÇO', align: 'left' },
|
|
19
|
+
{ x: 217, w: 36, label: 'NCM/SH', align: 'center' },
|
|
20
|
+
{ x: 253, w: 28, label: 'O/CSOSN', align: 'center' },
|
|
21
|
+
{ x: 281, w: 24, label: 'CFOP', align: 'center' },
|
|
22
|
+
{ x: 305, w: 18, label: 'UN', align: 'center' },
|
|
23
|
+
{ x: 323, w: 34, label: 'QUANT.', align: 'right' },
|
|
24
|
+
{ x: 357, w: 36, label: 'VALOR UNIT.', align: 'right' },
|
|
25
|
+
{ x: 393, w: 36, label: 'VALOR TOTAL', align: 'right' },
|
|
26
|
+
{ x: 429, w: 34, label: 'B.CÁLC ICMS', align: 'right' },
|
|
27
|
+
{ x: 463, w: 32, label: 'VALOR ICMS', align: 'right' },
|
|
28
|
+
{ x: 495, w: 28, label: 'VALOR IPI', align: 'right' },
|
|
29
|
+
{ x: 523, w: 26, label: 'ALÍQ. ICMS', align: 'right' },
|
|
30
|
+
{ x: 549, w: 26, label: 'ALÍQ. IPI', align: 'right' },
|
|
30
31
|
];
|
|
31
32
|
export class Danfe {
|
|
32
|
-
static fromXml(xmlNFe,
|
|
33
|
+
static fromXml(xmlNFe, xmlRetornoSefazOuOpcoes, opcoes) {
|
|
34
|
+
const xmlRetornoSefaz = typeof xmlRetornoSefazOuOpcoes === 'string' ? xmlRetornoSefazOuOpcoes : undefined;
|
|
35
|
+
const opcoesResolvidas = typeof xmlRetornoSefazOuOpcoes === 'object' && xmlRetornoSefazOuOpcoes !== null
|
|
36
|
+
? xmlRetornoSefazOuOpcoes
|
|
37
|
+
: opcoes;
|
|
38
|
+
const logoEmbed = opcoesResolvidas?.logo
|
|
39
|
+
? this.parseLogoImage(opcoesResolvidas.logo)
|
|
40
|
+
: undefined;
|
|
33
41
|
const doc = new DOMParser().parseFromString(xmlNFe, 'text/xml');
|
|
34
42
|
const docRetorno = xmlRetornoSefaz
|
|
35
43
|
? new DOMParser().parseFromString(xmlRetornoSefaz, 'text/xml')
|
|
@@ -43,7 +51,11 @@ export class Danfe {
|
|
|
43
51
|
const icmsTot = doc.getElementsByTagName('ICMSTot')[0];
|
|
44
52
|
const transp = doc.getElementsByTagName('transp')[0];
|
|
45
53
|
const transporta = transp?.getElementsByTagName('transporta')[0];
|
|
54
|
+
const veicTransp = transp?.getElementsByTagName('veicTransp')[0];
|
|
46
55
|
const vol = transp?.getElementsByTagName('vol')[0];
|
|
56
|
+
const cobr = doc.getElementsByTagName('cobr')[0];
|
|
57
|
+
const fat = cobr?.getElementsByTagName('fat')[0];
|
|
58
|
+
const dupNodes = cobr?.getElementsByTagName('dup');
|
|
47
59
|
const detPag = doc.getElementsByTagName('detPag')[0];
|
|
48
60
|
const infAdic = doc.getElementsByTagName('infAdic')[0];
|
|
49
61
|
const infProt = docRetorno.getElementsByTagName('infProt')[0] ??
|
|
@@ -59,16 +71,17 @@ export class Danfe {
|
|
|
59
71
|
const dhEmi = this.formatDateTime(this.getTag(ide, 'dhEmi', ''));
|
|
60
72
|
const dhSaiEnt = this.formatDateTime(this.getTag(ide, 'dhSaiEnt', this.getTag(ide, 'dhEmi', '')));
|
|
61
73
|
const emitNome = this.getTag(emit, 'xNome', '');
|
|
74
|
+
const emitFant = this.getTag(emit, 'xFant', '');
|
|
62
75
|
const emitCnpj = this.formatDoc(this.getTag(emit, 'CNPJ', this.getTag(emit, 'CPF', '')));
|
|
63
76
|
const emitIe = this.getTag(emit, 'IE', '');
|
|
64
77
|
const emitIeSt = this.getTag(emit, 'IEST', '');
|
|
65
78
|
const emitEnd = `${this.getTag(enderEmit, 'xLgr', '')}, ${this.getTag(enderEmit, 'nro', '')} - ${this.getTag(enderEmit, 'xBairro', '')}`;
|
|
66
|
-
const emitMunUfCep = `${this.getTag(enderEmit, 'xMun', '')}/${this.getTag(enderEmit, 'UF', '')} - CEP ${this.getTag(enderEmit, 'CEP', '')} - Fone
|
|
79
|
+
const emitMunUfCep = `${this.getTag(enderEmit, 'xMun', '')}/${this.getTag(enderEmit, 'UF', '')} - CEP ${this.formatCep(this.getTag(enderEmit, 'CEP', ''))}${this.getTag(enderEmit, 'fone', '') ? ' - Fone: ' + this.getTag(enderEmit, 'fone', '') : ''}`;
|
|
67
80
|
const destNome = this.getTag(dest, 'xNome', '');
|
|
68
81
|
const destDoc = this.formatDoc(this.getTag(dest, 'CNPJ', this.getTag(dest, 'CPF', '')));
|
|
69
82
|
const destEnd = `${this.getTag(enderDest, 'xLgr', '')}, ${this.getTag(enderDest, 'nro', '')}`;
|
|
70
83
|
const destBairro = this.getTag(enderDest, 'xBairro', '');
|
|
71
|
-
const destCep = this.getTag(enderDest, 'CEP', '');
|
|
84
|
+
const destCep = this.formatCep(this.getTag(enderDest, 'CEP', ''));
|
|
72
85
|
const destMun = this.getTag(enderDest, 'xMun', '');
|
|
73
86
|
const destUf = this.getTag(enderDest, 'UF', '');
|
|
74
87
|
const destFone = this.getTag(enderDest, 'fone', '');
|
|
@@ -79,7 +92,9 @@ export class Danfe {
|
|
|
79
92
|
const dhRecbto = this.formatDateTime(this.getTag(infProt, 'dhRecbto', this.getTag(docRetorno, 'dhRecbto', '')));
|
|
80
93
|
const protocoloTexto = nProt
|
|
81
94
|
? `${nProt} - ${dhRecbto}`
|
|
82
|
-
:
|
|
95
|
+
: cStat
|
|
96
|
+
? `Status ${cStat}: ${xMotivo.slice(0, 36)}`
|
|
97
|
+
: 'NFe sem Autorização de Uso da SEFAZ';
|
|
83
98
|
const detNodes = doc.getElementsByTagName('det');
|
|
84
99
|
const itens = [];
|
|
85
100
|
for (let i = 0; i < detNodes.length; i++) {
|
|
@@ -108,142 +123,408 @@ export class Danfe {
|
|
|
108
123
|
pIPI: this.formatDecimal(this.getTag(ipi, 'pIPI', '0.00')),
|
|
109
124
|
});
|
|
110
125
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.drawBox(cmds, 470, 782, 105, 38, 'NF-e', `No. ${nNF.padStart(9, '0')} / Serie ${serie}`, 7.5, true);
|
|
117
|
-
cmds.push('[2 2] 0 d 0.4 w 20 776 m 575 776 l S [] 0 d 0.5 w');
|
|
118
|
-
this.drawBox(cmds, 20, 688, 220, 82, 'IDENTIFICACAO DO EMITENTE');
|
|
119
|
-
this.drawText(cmds, 24, 752, emitNome.slice(0, 38), 7.5, true);
|
|
120
|
-
this.drawText(cmds, 24, 739, emitEnd.slice(0, 45), 6.5, false);
|
|
121
|
-
this.drawText(cmds, 24, 728, emitMunUfCep.slice(0, 47), 6.5, false);
|
|
122
|
-
if (tpAmb === '2') {
|
|
123
|
-
this.drawText(cmds, 24, 695, 'AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL', 6.5, true);
|
|
126
|
+
const itensPagina1 = 20;
|
|
127
|
+
const itensPorPaginaExtra = 40;
|
|
128
|
+
const paginasItens = [];
|
|
129
|
+
if (itens.length <= itensPagina1) {
|
|
130
|
+
paginasItens.push(itens);
|
|
124
131
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.drawBox(cmds, 490, 609, 85, 22, 'DATA DA EMISSAO', dhEmi.slice(0, 10), 7.5, false);
|
|
147
|
-
this.drawBox(cmds, 20, 587, 270, 22, 'ENDERECO', destEnd.slice(0, 48), 7.5, false);
|
|
148
|
-
this.drawBox(cmds, 290, 587, 120, 22, 'BAIRRO / DISTRITO', destBairro.slice(0, 24), 7.5, false);
|
|
149
|
-
this.drawBox(cmds, 410, 587, 80, 22, 'CEP', destCep, 7.5, false);
|
|
150
|
-
this.drawBox(cmds, 490, 587, 85, 22, 'DATA SAIDA/ENTRADA', dhSaiEnt.slice(0, 10), 7.5, false);
|
|
151
|
-
this.drawBox(cmds, 20, 565, 220, 22, 'MUNICIPIO', destMun, 7.5, false);
|
|
152
|
-
this.drawBox(cmds, 240, 565, 95, 22, 'FONE / FAX', destFone, 7.5, false);
|
|
153
|
-
this.drawBox(cmds, 335, 565, 30, 22, 'UF', destUf, 7.5, false);
|
|
154
|
-
this.drawBox(cmds, 365, 565, 125, 22, 'INSCRICAO ESTADUAL', destIe, 7.5, false);
|
|
155
|
-
this.drawBox(cmds, 490, 565, 85, 22, 'HORA DA SAIDA', dhSaiEnt.slice(11, 19), 7.5, false);
|
|
156
|
-
this.drawSectionHeader(cmds, 20, 554, 'FATURA / PAGAMENTO');
|
|
157
|
-
const tPag = this.getTag(detPag, 'tPag', '01');
|
|
158
|
-
const vPag = this.formatDecimal(this.getTag(detPag, 'vPag', '0.00'));
|
|
159
|
-
this.drawBox(cmds, 20, 530, 185, 22, 'FORMA DE PAGAMENTO', this.getDescricaoPagamento(tPag), 7.5, false);
|
|
160
|
-
this.drawBox(cmds, 205, 530, 185, 22, 'VALOR DO PAGAMENTO (R$)', vPag, 7.5, true);
|
|
161
|
-
this.drawBox(cmds, 390, 530, 185, 22, 'TROCO (R$)', this.formatDecimal(this.getTag(doc.getElementsByTagName('pag')[0], 'vTroco', '0.00')), 7.5, false);
|
|
162
|
-
this.drawSectionHeader(cmds, 20, 519, 'CALCULO DO IMPOSTO');
|
|
163
|
-
this.drawBox(cmds, 20, 495, 70, 22, 'BASE CALC. ICMS', this.formatDecimal(this.getTag(icmsTot, 'vBC', '0.00')), 7.5, false);
|
|
164
|
-
this.drawBox(cmds, 90, 495, 68, 22, 'VALOR DO ICMS', this.formatDecimal(this.getTag(icmsTot, 'vICMS', '0.00')), 7.5, false);
|
|
165
|
-
this.drawBox(cmds, 158, 495, 68, 22, 'BASE CALC. ICMS ST', this.formatDecimal(this.getTag(icmsTot, 'vBCST', '0.00')), 7.5, false);
|
|
166
|
-
this.drawBox(cmds, 226, 495, 68, 22, 'VALOR ICMS SUBST.', this.formatDecimal(this.getTag(icmsTot, 'vST', '0.00')), 7.5, false);
|
|
167
|
-
this.drawBox(cmds, 294, 495, 65, 22, 'V. IMP. IMPORTACAO', this.formatDecimal(this.getTag(icmsTot, 'vII', '0.00')), 7.5, false);
|
|
168
|
-
this.drawBox(cmds, 359, 495, 65, 22, 'VALOR DO FCP', this.formatDecimal(this.getTag(icmsTot, 'vFCP', '0.00')), 7.5, false);
|
|
169
|
-
this.drawBox(cmds, 424, 495, 65, 22, 'VALOR DO PIS', this.formatDecimal(this.getTag(icmsTot, 'vPIS', '0.00')), 7.5, false);
|
|
170
|
-
this.drawBox(cmds, 489, 495, 86, 22, 'V. TOTAL PRODUTOS', this.formatDecimal(this.getTag(icmsTot, 'vProd', '0.00')), 7.5, true);
|
|
171
|
-
this.drawBox(cmds, 20, 473, 70, 22, 'VALOR DO FRETE', this.formatDecimal(this.getTag(icmsTot, 'vFrete', '0.00')), 7.5, false);
|
|
172
|
-
this.drawBox(cmds, 90, 473, 68, 22, 'VALOR DO SEGURO', this.formatDecimal(this.getTag(icmsTot, 'vSeg', '0.00')), 7.5, false);
|
|
173
|
-
this.drawBox(cmds, 158, 473, 68, 22, 'DESCONTO', this.formatDecimal(this.getTag(icmsTot, 'vDesc', '0.00')), 7.5, false);
|
|
174
|
-
this.drawBox(cmds, 226, 473, 68, 22, 'OUTRAS DESPESAS', this.formatDecimal(this.getTag(icmsTot, 'vOutro', '0.00')), 7.5, false);
|
|
175
|
-
this.drawBox(cmds, 294, 473, 65, 22, 'VALOR TOTAL IPI', this.formatDecimal(this.getTag(icmsTot, 'vIPI', '0.00')), 7.5, false);
|
|
176
|
-
this.drawBox(cmds, 359, 473, 65, 22, 'V. APROX. TRIBUTOS', this.formatDecimal(this.getTag(icmsTot, 'vTotTrib', '0.00')), 7.5, false);
|
|
177
|
-
this.drawBox(cmds, 424, 473, 65, 22, 'VALOR DA COFINS', this.formatDecimal(this.getTag(icmsTot, 'vCOFINS', '0.00')), 7.5, false);
|
|
178
|
-
this.drawBox(cmds, 489, 473, 86, 22, 'V. TOTAL DA NOTA', this.formatDecimal(this.getTag(icmsTot, 'vNF', '0.00')), 8.5, true);
|
|
179
|
-
this.drawSectionHeader(cmds, 20, 462, 'TRANSPORTADOR / VOLUMES TRANSPORTADOS');
|
|
180
|
-
const modFrete = this.getTag(transp, 'modFrete', '9');
|
|
181
|
-
this.drawBox(cmds, 20, 438, 210, 22, 'NOME / RAZAO SOCIAL', this.getTag(transporta, 'xNome', ''), 7.5, false);
|
|
182
|
-
this.drawBox(cmds, 230, 438, 95, 22, 'FRETE POR CONTA', this.getDescricaoFrete(modFrete), 7, false);
|
|
183
|
-
this.drawBox(cmds, 325, 438, 65, 22, 'CODIGO ANTT', '', 7.5, false);
|
|
184
|
-
this.drawBox(cmds, 390, 438, 65, 22, 'PLACA DO VEICULO', '', 7.5, false);
|
|
185
|
-
this.drawBox(cmds, 455, 438, 25, 22, 'UF', '', 7.5, false);
|
|
186
|
-
this.drawBox(cmds, 480, 438, 95, 22, 'CNPJ / CPF', this.formatDoc(this.getTag(transporta, 'CNPJ', '')), 7.5, false);
|
|
187
|
-
this.drawBox(cmds, 20, 416, 85, 22, 'QUANTIDADE', this.getTag(vol, 'qVol', ''), 7.5, false);
|
|
188
|
-
this.drawBox(cmds, 105, 416, 110, 22, 'ESPECIE', this.getTag(vol, 'esp', ''), 7.5, false);
|
|
189
|
-
this.drawBox(cmds, 215, 416, 100, 22, 'MARCA', this.getTag(vol, 'marca', ''), 7.5, false);
|
|
190
|
-
this.drawBox(cmds, 315, 416, 90, 22, 'NUMERACAO', this.getTag(vol, 'nVol', ''), 7.5, false);
|
|
191
|
-
this.drawBox(cmds, 405, 416, 85, 22, 'PESO BRUTO', this.getTag(vol, 'pesoB', ''), 7.5, false);
|
|
192
|
-
this.drawBox(cmds, 490, 416, 85, 22, 'PESO LIQUIDO', this.getTag(vol, 'pesoL', ''), 7.5, false);
|
|
193
|
-
this.drawSectionHeader(cmds, 20, 405, 'DADOS DOS PRODUTOS / SERVICOS');
|
|
194
|
-
const headerY = 388;
|
|
195
|
-
const headerH = 14;
|
|
196
|
-
const tableBottomY = 112;
|
|
197
|
-
const tableBodyH = headerY - tableBottomY;
|
|
198
|
-
for (const col of COLUNAS_ITENS) {
|
|
199
|
-
cmds.push(`${col.x} ${headerY} ${col.w} ${headerH} re S`);
|
|
200
|
-
this.drawText(cmds, col.x + 2, headerY + 4, col.label, 5, true);
|
|
201
|
-
cmds.push(`${col.x} ${tableBottomY} ${col.w} ${tableBodyH} re S`);
|
|
132
|
+
else {
|
|
133
|
+
paginasItens.push(itens.slice(0, itensPagina1));
|
|
134
|
+
for (let idx = itensPagina1; idx < itens.length; idx += itensPorPaginaExtra) {
|
|
135
|
+
paginasItens.push(itens.slice(idx, idx + itensPorPaginaExtra));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const totalPaginas = paginasItens.length;
|
|
139
|
+
const duplicatasResumo = [];
|
|
140
|
+
if (dupNodes) {
|
|
141
|
+
for (let i = 0; i < Math.min(dupNodes.length, 4); i++) {
|
|
142
|
+
const dup = dupNodes[i];
|
|
143
|
+
if (!dup)
|
|
144
|
+
continue;
|
|
145
|
+
const nDup = this.getTag(dup, 'nDup', String(i + 1).padStart(3, '0'));
|
|
146
|
+
const dVencRaw = this.getTag(dup, 'dVenc', '');
|
|
147
|
+
const dVenc = dVencRaw.length >= 10
|
|
148
|
+
? `${dVencRaw.slice(8, 10)}/${dVencRaw.slice(5, 7)}/${dVencRaw.slice(0, 4)}`
|
|
149
|
+
: dVencRaw;
|
|
150
|
+
const vDup = this.formatDecimal(this.getTag(dup, 'vDup', '0.00'));
|
|
151
|
+
duplicatasResumo.push(`${nDup} ${dVenc} R$ ${vDup}`);
|
|
152
|
+
}
|
|
202
153
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
154
|
+
const numeroFormatado = this.formatNumeroNF(nNF);
|
|
155
|
+
const now = new Date();
|
|
156
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
157
|
+
const agoraStr = `${pad2(now.getDate())}/${pad2(now.getMonth() + 1)}/${now.getFullYear()} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
|
|
158
|
+
const streamsPaginas = [];
|
|
159
|
+
for (let numPag = 1; numPag <= totalPaginas; numPag++) {
|
|
160
|
+
const cmds = [];
|
|
161
|
+
const itensDaPagina = paginasItens[numPag - 1] ?? [];
|
|
162
|
+
// Estilo NFePHP (sped-da): 100% monocromático (preto/branco), traço fino 0.4pt e cantos suavizados
|
|
163
|
+
cmds.push('1 j 1 J 0.4 w 0 G 0 g');
|
|
164
|
+
// =========================================================================
|
|
165
|
+
// 1. CANHOTO DESTACÁVEL (Apenas na 1ª página)
|
|
166
|
+
// =========================================================================
|
|
167
|
+
if (numPag === 1) {
|
|
168
|
+
this.drawFieldBox(cmds, 20, 801, 450, 19, `RECEBEMOS DE ${emitNome.slice(0, 62)} OS PRODUTOS E/OU SERVIÇOS CONSTANTES DA NOTA FISCAL ELETRÔNICA INDICADA ABAIXO`);
|
|
169
|
+
this.drawFieldBox(cmds, 20, 782, 110, 19, 'DATA DE RECEBIMENTO');
|
|
170
|
+
this.drawFieldBox(cmds, 130, 782, 340, 19, 'IDENTIFICAÇÃO E ASSINATURA DO RECEBEDOR');
|
|
171
|
+
this.drawFieldBox(cmds, 470, 782, 105, 38, '');
|
|
172
|
+
this.drawTextCentered(cmds, 470, 105, 808, 'NF-e', 9, true);
|
|
173
|
+
this.drawTextCentered(cmds, 470, 105, 796, `Nº. ${numeroFormatado}`, 7.5, true);
|
|
174
|
+
this.drawTextCentered(cmds, 470, 105, 786, `Série ${serie.padStart(3, '0')}`, 6.8, true);
|
|
175
|
+
// Linha pontilhada de destaque do canhoto
|
|
176
|
+
cmds.push('[2 2] 0 d 0.35 w 20 776 m 575 776 l S [] 0 d 0.4 w');
|
|
177
|
+
}
|
|
178
|
+
// =========================================================================
|
|
179
|
+
// 2. CABEÇALHO PRINCIPAL (Emitente + DANFE + Código de Barras / Chave)
|
|
180
|
+
// =========================================================================
|
|
181
|
+
this.drawFieldBox(cmds, 20, 688, 220, 82, 'IDENTIFICAÇÃO DO EMITENTE');
|
|
182
|
+
if (logoEmbed) {
|
|
183
|
+
const slotX = 25;
|
|
184
|
+
const slotY = 703;
|
|
185
|
+
const slotW = 54;
|
|
186
|
+
const slotH = 54;
|
|
187
|
+
const scale = Math.min(slotW / logoEmbed.width, slotH / logoEmbed.height);
|
|
188
|
+
const drawW = logoEmbed.width * scale;
|
|
189
|
+
const drawH = logoEmbed.height * scale;
|
|
190
|
+
const imgX = slotX + (slotW - drawW) / 2;
|
|
191
|
+
const imgY = slotY + (slotH - drawH) / 2;
|
|
192
|
+
cmds.push(`q ${drawW.toFixed(2)} 0 0 ${drawH.toFixed(2)} ${imgX.toFixed(2)} ${imgY.toFixed(2)} cm /Im1 Do Q`);
|
|
193
|
+
const textBoxX = 83;
|
|
194
|
+
const textBoxW = 154;
|
|
195
|
+
const linhasNomeEmit = this.wrapText(emitNome, 26);
|
|
196
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 749, linhasNomeEmit[0] ?? '', 7.4, true);
|
|
197
|
+
if (linhasNomeEmit[1]) {
|
|
198
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 739, linhasNomeEmit[1].slice(0, 28), 7, true);
|
|
199
|
+
}
|
|
200
|
+
else if (emitFant) {
|
|
201
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 739, emitFant.slice(0, 28), 6.6, false);
|
|
202
|
+
}
|
|
203
|
+
const linhasEndEmit = this.wrapText(emitEnd, 34);
|
|
204
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 726, linhasEndEmit[0] ?? '', 5.8, false);
|
|
205
|
+
if (linhasEndEmit[1]) {
|
|
206
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 718, linhasEndEmit[1].slice(0, 36), 5.8, false);
|
|
207
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 709, emitMunUfCep.slice(0, 50), 5.2, false);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.drawTextCentered(cmds, textBoxX, textBoxW, 716, emitMunUfCep.slice(0, 50), 5.3, false);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const linhasNomeEmit = this.wrapText(emitNome, 33);
|
|
215
|
+
this.drawTextCentered(cmds, 20, 220, 747, linhasNomeEmit[0] ?? '', 8, true);
|
|
216
|
+
if (linhasNomeEmit[1]) {
|
|
217
|
+
this.drawTextCentered(cmds, 20, 220, 737, linhasNomeEmit[1], 7.5, true);
|
|
218
|
+
}
|
|
219
|
+
else if (emitFant) {
|
|
220
|
+
this.drawTextCentered(cmds, 20, 220, 737, emitFant.slice(0, 36), 7, false);
|
|
221
|
+
}
|
|
222
|
+
this.drawTextCentered(cmds, 20, 220, 722, emitEnd.slice(0, 44), 6.3, false);
|
|
223
|
+
this.drawTextCentered(cmds, 20, 220, 712, emitMunUfCep.slice(0, 46), 6.2, false);
|
|
224
|
+
}
|
|
225
|
+
if (tpAmb === '2') {
|
|
226
|
+
this.drawTextCentered(cmds, 20, 220, 693, 'EMITIDA EM AMBIENTE DE HOMOLOGAÇÃO - SEM VALOR FISCAL', 5.8, true);
|
|
227
|
+
}
|
|
228
|
+
// Quadro Central DANFE (estilo sped-da)
|
|
229
|
+
this.drawFieldBox(cmds, 240, 688, 95, 82, '');
|
|
230
|
+
this.drawTextCentered(cmds, 240, 95, 755, 'DANFE', 12, true);
|
|
231
|
+
this.drawTextCentered(cmds, 240, 95, 745, 'Documento Auxiliar da', 6, false);
|
|
232
|
+
this.drawTextCentered(cmds, 240, 95, 737, 'Nota Fiscal Eletrônica', 6, false);
|
|
233
|
+
this.drawText(cmds, 246, 724, '0 - ENTRADA', 6, false);
|
|
234
|
+
this.drawText(cmds, 246, 715, '1 - SAÍDA', 6, false);
|
|
235
|
+
this.drawFieldBox(cmds, 310, 714, 16, 16, '');
|
|
236
|
+
this.drawTextCentered(cmds, 310, 16, 719, tpNF, 8.5, true);
|
|
237
|
+
this.drawTextCentered(cmds, 240, 95, 702, `Nº. ${numeroFormatado}`, 7.5, true);
|
|
238
|
+
this.drawTextCentered(cmds, 240, 95, 692, `Série ${serie.padStart(3, '0')} Folha ${numPag}/${totalPaginas}`, 6.5, true);
|
|
239
|
+
// Código de Barras e Chave de Acesso
|
|
240
|
+
this.drawFieldBox(cmds, 335, 688, 240, 82, '');
|
|
241
|
+
this.drawCode128C(cmds, chave, 348, 738, 214, 25);
|
|
242
|
+
this.drawHLine(cmds, 335, 575, 732);
|
|
243
|
+
this.drawLabel(cmds, 339, 725, 'CHAVE DE ACESSO');
|
|
244
|
+
this.drawTextCentered(cmds, 335, 240, 714, chaveFormatada, 6.8, true);
|
|
245
|
+
this.drawHLine(cmds, 335, 575, 709);
|
|
246
|
+
this.drawTextCentered(cmds, 335, 240, 699, 'Consulta de autenticidade no portal nacional da NF-e', 6, false);
|
|
247
|
+
this.drawTextCentered(cmds, 335, 240, 691, 'www.nfe.fazenda.gov.br/portal ou no site da Sefaz Autorizadora', 5.5, false);
|
|
248
|
+
// Natureza da Operação e Protocolo
|
|
249
|
+
this.drawFieldBox(cmds, 20, 666, 315, 22, 'NATUREZA DA OPERAÇÃO', natOp.slice(0, 56), 7.5, false);
|
|
250
|
+
this.drawFieldBox(cmds, 335, 666, 240, 22, 'PROTOCOLO DE AUTORIZAÇÃO DE USO', protocoloTexto, 6.5, true, 'center');
|
|
251
|
+
// IE / IE ST / CNPJ
|
|
252
|
+
this.drawFieldBox(cmds, 20, 644, 185, 22, 'INSCRIÇÃO ESTADUAL', emitIe, 7.5, false);
|
|
253
|
+
this.drawFieldBox(cmds, 205, 644, 185, 22, 'INSCRIÇÃO ESTADUAL DO SUBST. TRIBUT.', emitIeSt, 7.5, false);
|
|
254
|
+
this.drawFieldBox(cmds, 390, 644, 185, 22, 'CNPJ / CPF', emitCnpj, 7.5, false);
|
|
255
|
+
let headerY = 385;
|
|
256
|
+
let tableBottomY = 108;
|
|
257
|
+
if (numPag === 1) {
|
|
258
|
+
// =========================================================================
|
|
259
|
+
// 3. DESTINATÁRIO / REMETENTE
|
|
260
|
+
// =========================================================================
|
|
261
|
+
this.drawSectionTitle(cmds, 20, 634, 'DESTINATÁRIO / REMETENTE');
|
|
262
|
+
this.drawFieldBox(cmds, 20, 610, 340, 22, 'NOME / RAZÃO SOCIAL', destNome.slice(0, 64), 7.5, false);
|
|
263
|
+
this.drawFieldBox(cmds, 360, 610, 130, 22, 'CNPJ / CPF', destDoc, 7.5, true);
|
|
264
|
+
this.drawFieldBox(cmds, 490, 610, 85, 22, 'DATA DA EMISSÃO', dhEmi.slice(0, 10), 7.5, false, 'center');
|
|
265
|
+
this.drawFieldBox(cmds, 20, 588, 270, 22, 'ENDEREÇO', destEnd.slice(0, 48), 7.5, false);
|
|
266
|
+
this.drawFieldBox(cmds, 290, 588, 120, 22, 'BAIRRO / DISTRITO', destBairro.slice(0, 24), 7.5, false);
|
|
267
|
+
this.drawFieldBox(cmds, 410, 588, 80, 22, 'CEP', destCep, 7.5, false, 'center');
|
|
268
|
+
this.drawFieldBox(cmds, 490, 588, 85, 22, 'DATA DA SAÍDA/ENTRADA', dhSaiEnt.slice(0, 10), 7.5, false, 'center');
|
|
269
|
+
this.drawFieldBox(cmds, 20, 566, 220, 22, 'MUNICÍPIO', destMun, 7.5, false);
|
|
270
|
+
this.drawFieldBox(cmds, 240, 566, 95, 22, 'FONE / FAX', destFone, 7.5, false);
|
|
271
|
+
this.drawFieldBox(cmds, 335, 566, 30, 22, 'UF', destUf, 7.5, false, 'center');
|
|
272
|
+
this.drawFieldBox(cmds, 365, 566, 125, 22, 'INSCRIÇÃO ESTADUAL', destIe, 7.5, false);
|
|
273
|
+
this.drawFieldBox(cmds, 490, 566, 85, 22, 'HORA DA SAÍDA/ENTRADA', dhSaiEnt.slice(11, 19), 7.5, false, 'center');
|
|
274
|
+
// =========================================================================
|
|
275
|
+
// 4. FATURA / DUPLICATAS / PAGAMENTO
|
|
276
|
+
// =========================================================================
|
|
277
|
+
this.drawSectionTitle(cmds, 20, 556, 'FATURA / DUPLICATAS');
|
|
278
|
+
const tPag = this.getTag(detPag, 'tPag', '01');
|
|
279
|
+
const vPag = this.formatDecimal(this.getTag(detPag, 'vPag', '0.00'));
|
|
280
|
+
if (duplicatasResumo.length > 0 || fat) {
|
|
281
|
+
const nFat = this.getTag(fat, 'nFat', '');
|
|
282
|
+
const vLiq = this.getTag(fat, 'vLiq', '');
|
|
283
|
+
const textoFatura = nFat ? `Nº ${nFat} - Líq: R$ ${this.formatDecimal(vLiq)}` : this.getDescricaoPagamento(tPag);
|
|
284
|
+
this.drawFieldBox(cmds, 20, 532, 145, 22, 'FATURA / FORMA PAGTO', textoFatura.slice(0, 28), 6.8, false);
|
|
285
|
+
this.drawFieldBox(cmds, 165, 532, 295, 22, 'DUPLICATAS (NÚMERO / VENCIMENTO / VALOR)', duplicatasResumo.join(' | ').slice(0, 64), 6.3, false);
|
|
286
|
+
this.drawFieldBox(cmds, 460, 532, 115, 22, 'VALOR DO PAGAMENTO', vPag, 7.5, true, 'right');
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
this.drawFieldBox(cmds, 20, 532, 185, 22, 'FORMA DE PAGAMENTO', this.getDescricaoPagamento(tPag), 7.5, false);
|
|
290
|
+
this.drawFieldBox(cmds, 205, 532, 185, 22, 'VALOR DO PAGAMENTO', vPag, 7.5, true, 'right');
|
|
291
|
+
this.drawFieldBox(cmds, 390, 532, 185, 22, 'TROCO', this.formatDecimal(this.getTag(doc.getElementsByTagName('pag')[0], 'vTroco', '0.00')), 7.5, false, 'right');
|
|
292
|
+
}
|
|
293
|
+
// =========================================================================
|
|
294
|
+
// 5. CÁLCULO DO IMPOSTO
|
|
295
|
+
// =========================================================================
|
|
296
|
+
this.drawSectionTitle(cmds, 20, 522, 'CÁLCULO DO IMPOSTO');
|
|
297
|
+
this.drawFieldBox(cmds, 20, 498, 70, 22, 'BASE DE CÁLC. DO ICMS', this.formatDecimal(this.getTag(icmsTot, 'vBC', '0.00')), 7.2, false, 'right');
|
|
298
|
+
this.drawFieldBox(cmds, 90, 498, 68, 22, 'VALOR DO ICMS', this.formatDecimal(this.getTag(icmsTot, 'vICMS', '0.00')), 7.2, false, 'right');
|
|
299
|
+
this.drawFieldBox(cmds, 158, 498, 68, 22, 'BASE DE CÁLC. ICMS S.T.', this.formatDecimal(this.getTag(icmsTot, 'vBCST', '0.00')), 7.2, false, 'right');
|
|
300
|
+
this.drawFieldBox(cmds, 226, 498, 68, 22, 'VALOR DO ICMS SUBST.', this.formatDecimal(this.getTag(icmsTot, 'vST', '0.00')), 7.2, false, 'right');
|
|
301
|
+
this.drawFieldBox(cmds, 294, 498, 65, 22, 'V. IMP. IMPORTAÇÃO', this.formatDecimal(this.getTag(icmsTot, 'vII', '0.00')), 7.2, false, 'right');
|
|
302
|
+
this.drawFieldBox(cmds, 359, 498, 65, 22, 'VALOR DO FCP', this.formatDecimal(this.getTag(icmsTot, 'vFCP', '0.00')), 7.2, false, 'right');
|
|
303
|
+
this.drawFieldBox(cmds, 424, 498, 65, 22, 'VALOR DO PIS', this.formatDecimal(this.getTag(icmsTot, 'vPIS', '0.00')), 7.2, false, 'right');
|
|
304
|
+
this.drawFieldBox(cmds, 489, 498, 86, 22, 'V. TOTAL PRODUTOS', this.formatDecimal(this.getTag(icmsTot, 'vProd', '0.00')), 7.5, true, 'right');
|
|
305
|
+
this.drawFieldBox(cmds, 20, 476, 70, 22, 'VALOR DO FRETE', this.formatDecimal(this.getTag(icmsTot, 'vFrete', '0.00')), 7.2, false, 'right');
|
|
306
|
+
this.drawFieldBox(cmds, 90, 476, 68, 22, 'VALOR DO SEGURO', this.formatDecimal(this.getTag(icmsTot, 'vSeg', '0.00')), 7.2, false, 'right');
|
|
307
|
+
this.drawFieldBox(cmds, 158, 476, 68, 22, 'DESCONTO', this.formatDecimal(this.getTag(icmsTot, 'vDesc', '0.00')), 7.2, false, 'right');
|
|
308
|
+
this.drawFieldBox(cmds, 226, 476, 68, 22, 'OUTRAS DESPESAS', this.formatDecimal(this.getTag(icmsTot, 'vOutro', '0.00')), 7.2, false, 'right');
|
|
309
|
+
this.drawFieldBox(cmds, 294, 476, 65, 22, 'VALOR TOTAL IPI', this.formatDecimal(this.getTag(icmsTot, 'vIPI', '0.00')), 7.2, false, 'right');
|
|
310
|
+
this.drawFieldBox(cmds, 359, 476, 65, 22, 'V. APROX. TRIBUTOS', this.formatDecimal(this.getTag(icmsTot, 'vTotTrib', '0.00')), 7.2, false, 'right');
|
|
311
|
+
this.drawFieldBox(cmds, 424, 476, 65, 22, 'VALOR DA COFINS', this.formatDecimal(this.getTag(icmsTot, 'vCOFINS', '0.00')), 7.2, false, 'right');
|
|
312
|
+
this.drawFieldBox(cmds, 489, 476, 86, 22, 'V. TOTAL DA NOTA', this.formatDecimal(this.getTag(icmsTot, 'vNF', '0.00')), 8.2, true, 'right');
|
|
313
|
+
// =========================================================================
|
|
314
|
+
// 6. TRANSPORTADOR / VOLUMES TRANSPORTADOS
|
|
315
|
+
// =========================================================================
|
|
316
|
+
this.drawSectionTitle(cmds, 20, 466, 'TRANSPORTADOR / VOLUMES TRANSPORTADOS');
|
|
317
|
+
const modFrete = this.getTag(transp, 'modFrete', '9');
|
|
318
|
+
const rntc = this.getTag(veicTransp, 'RNTC', '');
|
|
319
|
+
const placa = this.getTag(veicTransp, 'placa', '');
|
|
320
|
+
const ufVeic = this.getTag(veicTransp, 'UF', this.getTag(transporta, 'UF', ''));
|
|
321
|
+
const docTransp = this.formatDoc(this.getTag(transporta, 'CNPJ', this.getTag(transporta, 'CPF', '')));
|
|
322
|
+
this.drawFieldBox(cmds, 20, 442, 210, 22, 'NOME / RAZÃO SOCIAL', this.getTag(transporta, 'xNome', ''), 7.2, false);
|
|
323
|
+
this.drawFieldBox(cmds, 230, 442, 95, 22, 'FRETE POR CONTA', this.getDescricaoFrete(modFrete), 6.8, false);
|
|
324
|
+
this.drawFieldBox(cmds, 325, 442, 65, 22, 'CÓDIGO ANTT', rntc, 7.2, false, 'center');
|
|
325
|
+
this.drawFieldBox(cmds, 390, 442, 65, 22, 'PLACA DO VEÍCULO', placa, 7.2, false, 'center');
|
|
326
|
+
this.drawFieldBox(cmds, 455, 442, 25, 22, 'UF', ufVeic, 7.2, false, 'center');
|
|
327
|
+
this.drawFieldBox(cmds, 480, 442, 95, 22, 'CNPJ / CPF', docTransp, 7.2, false);
|
|
328
|
+
this.drawFieldBox(cmds, 20, 420, 85, 22, 'QUANTIDADE', this.getTag(vol, 'qVol', ''), 7.2, false, 'right');
|
|
329
|
+
this.drawFieldBox(cmds, 105, 420, 110, 22, 'ESPÉCIE', this.getTag(vol, 'esp', ''), 7.2, false);
|
|
330
|
+
this.drawFieldBox(cmds, 215, 420, 100, 22, 'MARCA', this.getTag(vol, 'marca', ''), 7.2, false);
|
|
331
|
+
this.drawFieldBox(cmds, 315, 420, 90, 22, 'NUMERAÇÃO', this.getTag(vol, 'nVol', ''), 7.2, false);
|
|
332
|
+
this.drawFieldBox(cmds, 405, 420, 85, 22, 'PESO BRUTO', this.getTag(vol, 'pesoB', ''), 7.2, false, 'right');
|
|
333
|
+
this.drawFieldBox(cmds, 490, 420, 85, 22, 'PESO LÍQUIDO', this.getTag(vol, 'pesoL', ''), 7.2, false, 'right');
|
|
334
|
+
this.drawSectionTitle(cmds, 20, 410, 'DADOS DOS PRODUTOS / SERVIÇOS');
|
|
335
|
+
headerY = 407;
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this.drawSectionTitle(cmds, 20, 634, `DADOS DOS PRODUTOS / SERVIÇOS (CONTINUAÇÃO - FOLHA ${numPag}/${totalPaginas})`);
|
|
339
|
+
headerY = 631;
|
|
340
|
+
tableBottomY = 42;
|
|
341
|
+
}
|
|
342
|
+
// =========================================================================
|
|
343
|
+
// 7. TABELA DE ITENS E MARCA D'ÁGUA "SEM VALOR FISCAL" ESTILO NFEPHP
|
|
344
|
+
// =========================================================================
|
|
345
|
+
const headerH = 13;
|
|
346
|
+
const topTableY = headerY - headerH;
|
|
347
|
+
// Marca d'água em grande evidência no centro da tabela (exatamente como no NFePHP sped-da)
|
|
348
|
+
if (tpAmb === '2' || !nProt) {
|
|
349
|
+
const centerY = (topTableY + tableBottomY) / 2;
|
|
350
|
+
cmds.push('0.68 g');
|
|
351
|
+
this.drawTextCentered(cmds, 20, 555, centerY + 6, 'SEM VALOR FISCAL', 34, true);
|
|
352
|
+
if (tpAmb === '2') {
|
|
353
|
+
this.drawTextCentered(cmds, 20, 555, centerY - 14, 'EMITIDA EM AMBIENTE DE HOMOLOGAÇÃO', 13, true);
|
|
354
|
+
}
|
|
355
|
+
if (!nProt) {
|
|
356
|
+
this.drawTextCentered(cmds, 20, 555, centerY - 28, 'FALTA PROTOCOLO DE AUTORIZAÇÃO DA SEFAZ', 10, true);
|
|
357
|
+
}
|
|
358
|
+
cmds.push('0 g');
|
|
359
|
+
}
|
|
360
|
+
// Contorno arredondado da tabela e divisórias de colunas
|
|
361
|
+
this.drawRoundedRect(cmds, 20, tableBottomY, 555, headerY - tableBottomY, 2.5, 'S');
|
|
362
|
+
this.drawHLine(cmds, 20, 575, topTableY);
|
|
363
|
+
for (let i = 1; i < COLUNAS_ITENS.length; i++) {
|
|
364
|
+
const col = COLUNAS_ITENS[i];
|
|
365
|
+
this.drawVLine(cmds, col.x, tableBottomY, headerY);
|
|
366
|
+
}
|
|
367
|
+
// Títulos das colunas da tabela
|
|
368
|
+
for (const col of COLUNAS_ITENS) {
|
|
369
|
+
if (col.align === 'left') {
|
|
370
|
+
this.drawText(cmds, col.x + 2.5, topTableY + 4, col.label, 4.4, true);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.drawTextCentered(cmds, col.x, col.w, topTableY + 4, col.label, 4.3, true);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Linhas de itens
|
|
377
|
+
let rowY = topTableY - 10;
|
|
378
|
+
for (let idxItem = 0; idxItem < itensDaPagina.length; idxItem++) {
|
|
379
|
+
const item = itensDaPagina[idxItem];
|
|
380
|
+
if (!item || rowY < tableBottomY + 6)
|
|
381
|
+
break;
|
|
382
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[0], rowY, item.cProd.slice(0, 10));
|
|
383
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[1], rowY, item.xProd.slice(0, 37));
|
|
384
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[2], rowY, item.ncm);
|
|
385
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[3], rowY, item.origCsosnCst);
|
|
386
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[4], rowY, item.cfop);
|
|
387
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[5], rowY, item.uCom.slice(0, 4));
|
|
388
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[6], rowY, item.qCom);
|
|
389
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[7], rowY, item.vUnCom);
|
|
390
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[8], rowY, item.vProd);
|
|
391
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[9], rowY, item.vBC);
|
|
392
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[10], rowY, item.vICMS);
|
|
393
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[11], rowY, item.vIPI);
|
|
394
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[12], rowY, item.pICMS);
|
|
395
|
+
this.drawAlignedItemCell(cmds, COLUNAS_ITENS[13], rowY, item.pIPI);
|
|
396
|
+
cmds.push(`[1 2] 0 d 0.25 w 20 ${(rowY - 3.5).toFixed(2)} m 575 ${(rowY - 3.5).toFixed(2)} l S [] 0 d 0.4 w`);
|
|
397
|
+
rowY -= 13;
|
|
398
|
+
}
|
|
399
|
+
// =========================================================================
|
|
400
|
+
// 8. DADOS ADICIONAIS (Apenas na 1ª página)
|
|
401
|
+
// =========================================================================
|
|
402
|
+
if (numPag === 1) {
|
|
403
|
+
this.drawSectionTitle(cmds, 20, 98, 'DADOS ADICIONAIS');
|
|
404
|
+
this.drawFieldBox(cmds, 20, 42, 370, 53, 'INFORMAÇÕES COMPLEMENTARES');
|
|
405
|
+
this.drawFieldBox(cmds, 390, 42, 185, 53, 'RESERVADO AO FISCO');
|
|
406
|
+
const infCpl = this.getTag(infAdic, 'infCpl', '');
|
|
407
|
+
const linhaStatus = `Ambiente: ${tpAmb === '2' ? '2 - Homologação (Sem Valor Fiscal)' : '1 - Produção'}${cStat ? ` | Status SEFAZ: ${cStat} - ${xMotivo}` : ''}`;
|
|
408
|
+
const linhasCompl = infCpl
|
|
409
|
+
? [...this.wrapText(infCpl, 74).slice(0, 2), linhaStatus.slice(0, 78)]
|
|
410
|
+
: [linhaStatus.slice(0, 78)];
|
|
411
|
+
let yCompl = 76;
|
|
412
|
+
for (const linha of linhasCompl) {
|
|
413
|
+
this.drawText(cmds, 24, yCompl, linha, 6.3, false);
|
|
414
|
+
yCompl -= 10;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// =========================================================================
|
|
418
|
+
// 9. RODAPÉ ESTILO NFEPHP (Monocromático e nítido)
|
|
419
|
+
// =========================================================================
|
|
420
|
+
this.drawText(cmds, 20, 30, `Impresso em ${agoraStr.slice(0, 10)} às ${agoraStr.slice(11, 19)}`, 6.2, false);
|
|
421
|
+
this.drawTextRight(cmds, 575, 30, 'Powered by nfe-lib-node', 7.2, true);
|
|
422
|
+
streamsPaginas.push(cmds.join('\n'));
|
|
223
423
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
424
|
+
return this.buildPdfBuffer(streamsPaginas, logoEmbed);
|
|
425
|
+
}
|
|
426
|
+
static drawSectionTitle(cmds, x, y, title) {
|
|
427
|
+
this.drawText(cmds, x + 1, y, title, 5.8, true);
|
|
428
|
+
}
|
|
429
|
+
static drawFieldBox(cmds, x, y, w, h, label, value = '', valueSize = 7.5, boldValue = false, align = 'left') {
|
|
430
|
+
this.drawRoundedRect(cmds, x, y, w, h, 2, 'S');
|
|
431
|
+
if (label) {
|
|
432
|
+
this.drawLabel(cmds, x + 3, y + h - 6.2, label);
|
|
433
|
+
}
|
|
434
|
+
if (value) {
|
|
435
|
+
if (align === 'right') {
|
|
436
|
+
this.drawTextRight(cmds, x + w - 3.5, y + 4.2, value, valueSize, boldValue);
|
|
437
|
+
}
|
|
438
|
+
else if (align === 'center') {
|
|
439
|
+
this.drawTextCentered(cmds, x, w, y + 4.2, value, valueSize, boldValue);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
this.drawText(cmds, x + 3.5, y + 4.2, value, valueSize, boldValue);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
static drawRoundedRect(cmds, x, y, w, h, r, style) {
|
|
447
|
+
const radius = Math.min(r, w / 2, h / 2);
|
|
448
|
+
const k = 0.55228475 * radius;
|
|
449
|
+
cmds.push(`${(x + radius).toFixed(2)} ${y.toFixed(2)} m`, `${(x + w - radius).toFixed(2)} ${y.toFixed(2)} l`, `${(x + w - radius + k).toFixed(2)} ${y.toFixed(2)} ${(x + w).toFixed(2)} ${(y + radius - k).toFixed(2)} ${(x + w).toFixed(2)} ${(y + radius).toFixed(2)} c`, `${(x + w).toFixed(2)} ${(y + h - radius).toFixed(2)} l`, `${(x + w).toFixed(2)} ${(y + h - radius + k).toFixed(2)} ${(x + w - radius + k).toFixed(2)} ${(y + h).toFixed(2)} ${(x + w - radius).toFixed(2)} ${(y + h).toFixed(2)} c`, `${(x + radius).toFixed(2)} ${(y + h).toFixed(2)} l`, `${(x + radius - k).toFixed(2)} ${(y + h).toFixed(2)} ${x.toFixed(2)} ${(y + h - radius + k).toFixed(2)} ${x.toFixed(2)} ${(y + h - radius).toFixed(2)} c`, `${x.toFixed(2)} ${(y + radius).toFixed(2)} l`, `${x.toFixed(2)} ${(y + radius - k).toFixed(2)} ${(x + radius - k).toFixed(2)} ${y.toFixed(2)} ${(x + radius).toFixed(2)} ${y.toFixed(2)} c`, `h ${style}`);
|
|
450
|
+
}
|
|
451
|
+
static drawHLine(cmds, x1, x2, y) {
|
|
452
|
+
cmds.push(`${x1.toFixed(2)} ${y.toFixed(2)} m ${x2.toFixed(2)} ${y.toFixed(2)} l S`);
|
|
453
|
+
}
|
|
454
|
+
static drawVLine(cmds, x, y1, y2) {
|
|
455
|
+
cmds.push(`${x.toFixed(2)} ${y1.toFixed(2)} m ${x.toFixed(2)} ${y2.toFixed(2)} l S`);
|
|
456
|
+
}
|
|
457
|
+
static drawLabel(cmds, x, y, label) {
|
|
458
|
+
this.drawText(cmds, x, y, label, 4.8, false);
|
|
459
|
+
}
|
|
460
|
+
static drawAlignedItemCell(cmds, col, y, text) {
|
|
461
|
+
if (!text)
|
|
462
|
+
return;
|
|
463
|
+
if (col.align === 'right') {
|
|
464
|
+
this.drawTextRight(cmds, col.x + col.w - 2.5, y, text, 5.8, false);
|
|
465
|
+
}
|
|
466
|
+
else if (col.align === 'center') {
|
|
467
|
+
this.drawTextCentered(cmds, col.x, col.w, y, text, 5.8, false);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
this.drawText(cmds, col.x + 2.5, y, text, 5.8, false);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
static estimateTextWidth(text, size, bold) {
|
|
474
|
+
const factor = bold ? 0.54 : 0.50;
|
|
475
|
+
return text.length * size * factor;
|
|
476
|
+
}
|
|
477
|
+
static drawText(cmds, x, y, text, size, bold) {
|
|
478
|
+
const font = bold ? '/F2' : '/F1';
|
|
479
|
+
const escaped = this.escapePdfText(text);
|
|
480
|
+
cmds.push(`BT ${font} ${size} Tf ${x.toFixed(2)} ${y.toFixed(2)} Td (${escaped}) Tj ET`);
|
|
481
|
+
}
|
|
482
|
+
static drawTextRight(cmds, rightX, y, text, size, bold) {
|
|
483
|
+
const w = this.estimateTextWidth(text, size, bold);
|
|
484
|
+
this.drawText(cmds, rightX - w, y, text, size, bold);
|
|
485
|
+
}
|
|
486
|
+
static drawTextCentered(cmds, boxX, boxW, y, text, size, bold) {
|
|
487
|
+
const w = this.estimateTextWidth(text, size, bold);
|
|
488
|
+
const x = boxX + Math.max(2, (boxW - w) / 2);
|
|
489
|
+
this.drawText(cmds, x, y, text, size, bold);
|
|
490
|
+
}
|
|
491
|
+
static wrapText(text, maxCharsPerLine) {
|
|
492
|
+
const words = text.trim().split(/\s+/);
|
|
493
|
+
const lines = [];
|
|
494
|
+
let current = '';
|
|
495
|
+
for (const word of words) {
|
|
496
|
+
if (!current) {
|
|
497
|
+
current = word;
|
|
498
|
+
}
|
|
499
|
+
else if (current.length + 1 + word.length <= maxCharsPerLine) {
|
|
500
|
+
current += ' ' + word;
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
lines.push(current);
|
|
504
|
+
current = word;
|
|
505
|
+
}
|
|
228
506
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
507
|
+
if (current)
|
|
508
|
+
lines.push(current);
|
|
509
|
+
return lines;
|
|
510
|
+
}
|
|
511
|
+
static escapePdfText(text) {
|
|
512
|
+
return text
|
|
513
|
+
.replace(/•/g, '-')
|
|
514
|
+
.replace(/—/g, '-')
|
|
515
|
+
.replace(/[^\x20-\xFF]/g, '')
|
|
516
|
+
.replace(/\\/g, '\\\\')
|
|
517
|
+
.replace(/\(/g, '\\(')
|
|
518
|
+
.replace(/\)/g, '\\)');
|
|
238
519
|
}
|
|
239
520
|
static getDescricaoFrete(modFrete) {
|
|
240
521
|
const mapa = {
|
|
241
|
-
'0': '0-Remetente (CIF)',
|
|
242
|
-
'1': '1-
|
|
243
|
-
'2': '2-Terceiros',
|
|
244
|
-
'3': '3-
|
|
245
|
-
'4': '4-
|
|
246
|
-
'9': '9-Sem Transporte',
|
|
522
|
+
'0': '0 - Remetente (CIF)',
|
|
523
|
+
'1': '1 - Destinatário (FOB)',
|
|
524
|
+
'2': '2 - Terceiros',
|
|
525
|
+
'3': '3 - Próprio Remet.',
|
|
526
|
+
'4': '4 - Próprio Dest.',
|
|
527
|
+
'9': '9 - Sem Transporte',
|
|
247
528
|
};
|
|
248
529
|
return mapa[modFrete] ?? modFrete;
|
|
249
530
|
}
|
|
@@ -251,14 +532,14 @@ export class Danfe {
|
|
|
251
532
|
const mapa = {
|
|
252
533
|
'01': '01 - Dinheiro',
|
|
253
534
|
'02': '02 - Cheque',
|
|
254
|
-
'03': '03 -
|
|
255
|
-
'04': '04 -
|
|
256
|
-
'15': '15 - Boleto
|
|
535
|
+
'03': '03 - Cartão de Crédito',
|
|
536
|
+
'04': '04 - Cartão de Débito',
|
|
537
|
+
'15': '15 - Boleto Bancário',
|
|
257
538
|
'17': '17 - PIX',
|
|
258
539
|
'90': '90 - Sem Pagamento',
|
|
259
540
|
'99': '99 - Outros',
|
|
260
541
|
};
|
|
261
|
-
return mapa[tPag] ?? `
|
|
542
|
+
return mapa[tPag] ?? `Código ${tPag}`;
|
|
262
543
|
}
|
|
263
544
|
static formatDecimal(val) {
|
|
264
545
|
const num = Number(val.replace(',', '.'));
|
|
@@ -266,6 +547,17 @@ export class Danfe {
|
|
|
266
547
|
return val;
|
|
267
548
|
return num.toFixed(2).replace('.', ',');
|
|
268
549
|
}
|
|
550
|
+
static formatNumeroNF(nNF) {
|
|
551
|
+
const padded = nNF.replace(/\D/g, '').padStart(9, '0');
|
|
552
|
+
return `${padded.slice(0, 3)}.${padded.slice(3, 6)}.${padded.slice(6, 9)}`;
|
|
553
|
+
}
|
|
554
|
+
static formatCep(cep) {
|
|
555
|
+
const digits = cep.replace(/\D/g, '');
|
|
556
|
+
if (digits.length === 8) {
|
|
557
|
+
return `${digits.slice(0, 5)}-${digits.slice(5)}`;
|
|
558
|
+
}
|
|
559
|
+
return cep;
|
|
560
|
+
}
|
|
269
561
|
static getTag(parent, tagName, fallback) {
|
|
270
562
|
if (!parent || typeof parent.getElementsByTagName !== 'function') {
|
|
271
563
|
return fallback;
|
|
@@ -290,31 +582,6 @@ export class Danfe {
|
|
|
290
582
|
const timePart = iso.slice(11, 19);
|
|
291
583
|
return `${datePart} ${timePart}`;
|
|
292
584
|
}
|
|
293
|
-
static drawSectionHeader(cmds, x, y, title) {
|
|
294
|
-
this.drawText(cmds, x, y, title, 6, true);
|
|
295
|
-
}
|
|
296
|
-
static drawBox(cmds, x, y, w, h, label, value = '', valueSize = 7.5, boldValue = false) {
|
|
297
|
-
cmds.push(`${x} ${y} ${w} ${h} re S`);
|
|
298
|
-
if (label) {
|
|
299
|
-
this.drawText(cmds, x + 2.5, y + h - 6.5, label, 5, false);
|
|
300
|
-
}
|
|
301
|
-
if (value) {
|
|
302
|
-
this.drawText(cmds, x + 2.5, y + 4.5, value, valueSize, boldValue);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
static drawText(cmds, x, y, text, size, bold) {
|
|
306
|
-
const font = bold ? '/F2' : '/F1';
|
|
307
|
-
const escaped = this.escapePdfText(text);
|
|
308
|
-
cmds.push(`BT ${font} ${size} Tf ${x} ${y} Td (${escaped}) Tj ET`);
|
|
309
|
-
}
|
|
310
|
-
static escapePdfText(text) {
|
|
311
|
-
return text
|
|
312
|
-
.normalize('NFD')
|
|
313
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
314
|
-
.replace(/\\/g, '\\\\')
|
|
315
|
-
.replace(/\(/g, '\\(')
|
|
316
|
-
.replace(/\)/g, '\\)');
|
|
317
|
-
}
|
|
318
585
|
static drawCode128C(cmds, digits, x, y, width, height) {
|
|
319
586
|
const clean = digits.replace(/\D/g, '').padStart(44, '0').slice(0, 44);
|
|
320
587
|
const codes = [105];
|
|
@@ -341,16 +608,226 @@ export class Danfe {
|
|
|
341
608
|
}
|
|
342
609
|
}
|
|
343
610
|
}
|
|
344
|
-
static
|
|
345
|
-
const
|
|
611
|
+
static parseLogoImage(logo) {
|
|
612
|
+
const buf = Buffer.isBuffer(logo)
|
|
613
|
+
? logo
|
|
614
|
+
: Buffer.from(logo.replace(/^data:image\/[a-zA-Z0-9.+-]+;base64,/, '').trim(), 'base64');
|
|
615
|
+
if (buf.length < 16)
|
|
616
|
+
return undefined;
|
|
617
|
+
// 1. JPEG (FF D8)
|
|
618
|
+
if (buf[0] === 0xff && buf[1] === 0xd8) {
|
|
619
|
+
let offset = 2;
|
|
620
|
+
while (offset + 9 < buf.length) {
|
|
621
|
+
if (buf[offset] !== 0xff) {
|
|
622
|
+
offset++;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
let marker = buf[offset + 1] ?? 0;
|
|
626
|
+
while (marker === 0xff && offset + 2 < buf.length) {
|
|
627
|
+
offset++;
|
|
628
|
+
marker = buf[offset + 1] ?? 0;
|
|
629
|
+
}
|
|
630
|
+
if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01) {
|
|
631
|
+
offset += 2;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const segLen = buf.readUInt16BE(offset + 2);
|
|
635
|
+
if (marker === 0xc0 || marker === 0xc1 || marker === 0xc2) {
|
|
636
|
+
const bitsPerComponent = buf[offset + 4] ?? 8;
|
|
637
|
+
const height = buf.readUInt16BE(offset + 5);
|
|
638
|
+
const width = buf.readUInt16BE(offset + 7);
|
|
639
|
+
const numComponents = buf[offset + 9] ?? 3;
|
|
640
|
+
const colorSpace = numComponents === 1
|
|
641
|
+
? '/DeviceGray'
|
|
642
|
+
: numComponents === 4
|
|
643
|
+
? '/DeviceCMYK'
|
|
644
|
+
: '/DeviceRGB';
|
|
645
|
+
const hexStream = buf.toString('hex') + '>';
|
|
646
|
+
return {
|
|
647
|
+
width,
|
|
648
|
+
height,
|
|
649
|
+
colorSpace,
|
|
650
|
+
bitsPerComponent,
|
|
651
|
+
filter: '[/ASCIIHexDecode /DCTDecode]',
|
|
652
|
+
hexStream,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
offset += 2 + segLen;
|
|
656
|
+
}
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
// 2. PNG (89 50 4E 47 0D 0A 1A 0A)
|
|
660
|
+
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {
|
|
661
|
+
let offset = 8;
|
|
662
|
+
let width = 0;
|
|
663
|
+
let height = 0;
|
|
664
|
+
let bitDepth = 8;
|
|
665
|
+
let colorType = 2;
|
|
666
|
+
let palette;
|
|
667
|
+
let trns;
|
|
668
|
+
const idatChunks = [];
|
|
669
|
+
while (offset + 8 <= buf.length) {
|
|
670
|
+
const length = buf.readUInt32BE(offset);
|
|
671
|
+
const type = buf.toString('ascii', offset + 4, offset + 8);
|
|
672
|
+
const dataStart = offset + 8;
|
|
673
|
+
const dataEnd = dataStart + length;
|
|
674
|
+
if (dataEnd > buf.length)
|
|
675
|
+
break;
|
|
676
|
+
const chunkData = buf.subarray(dataStart, dataEnd);
|
|
677
|
+
if (type === 'IHDR' && length >= 13) {
|
|
678
|
+
width = chunkData.readUInt32BE(0);
|
|
679
|
+
height = chunkData.readUInt32BE(4);
|
|
680
|
+
bitDepth = chunkData[8] ?? 8;
|
|
681
|
+
colorType = chunkData[9] ?? 2;
|
|
682
|
+
}
|
|
683
|
+
else if (type === 'PLTE') {
|
|
684
|
+
palette = chunkData;
|
|
685
|
+
}
|
|
686
|
+
else if (type === 'tRNS') {
|
|
687
|
+
trns = chunkData;
|
|
688
|
+
}
|
|
689
|
+
else if (type === 'IDAT') {
|
|
690
|
+
idatChunks.push(chunkData);
|
|
691
|
+
}
|
|
692
|
+
else if (type === 'IEND') {
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
offset = dataEnd + 4;
|
|
696
|
+
}
|
|
697
|
+
if (width <= 0 || height <= 0 || bitDepth !== 8 || idatChunks.length === 0) {
|
|
698
|
+
return undefined;
|
|
699
|
+
}
|
|
700
|
+
const bpp = colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 4 ? 2 : 1;
|
|
701
|
+
const stride = width * bpp;
|
|
702
|
+
const rawData = inflateSync(Buffer.concat(idatChunks));
|
|
703
|
+
const unfiltered = Buffer.alloc(height * stride);
|
|
704
|
+
const paethPredictor = (a, b, c) => {
|
|
705
|
+
const p = a + b - c;
|
|
706
|
+
const pa = Math.abs(p - a);
|
|
707
|
+
const pb = Math.abs(p - b);
|
|
708
|
+
const pc = Math.abs(p - c);
|
|
709
|
+
if (pa <= pb && pa <= pc)
|
|
710
|
+
return a;
|
|
711
|
+
if (pb <= pc)
|
|
712
|
+
return b;
|
|
713
|
+
return c;
|
|
714
|
+
};
|
|
715
|
+
for (let y = 0; y < height; y++) {
|
|
716
|
+
const filterType = rawData[y * (stride + 1)] ?? 0;
|
|
717
|
+
const srcRowStart = y * (stride + 1) + 1;
|
|
718
|
+
const dstRowStart = y * stride;
|
|
719
|
+
for (let x = 0; x < stride; x++) {
|
|
720
|
+
const rawByte = rawData[srcRowStart + x] ?? 0;
|
|
721
|
+
const left = x >= bpp ? (unfiltered[dstRowStart + x - bpp] ?? 0) : 0;
|
|
722
|
+
const up = y > 0 ? (unfiltered[dstRowStart - stride + x] ?? 0) : 0;
|
|
723
|
+
const upLeft = y > 0 && x >= bpp ? (unfiltered[dstRowStart - stride + x - bpp] ?? 0) : 0;
|
|
724
|
+
let recon = rawByte;
|
|
725
|
+
if (filterType === 1) {
|
|
726
|
+
recon = (rawByte + left) & 0xff;
|
|
727
|
+
}
|
|
728
|
+
else if (filterType === 2) {
|
|
729
|
+
recon = (rawByte + up) & 0xff;
|
|
730
|
+
}
|
|
731
|
+
else if (filterType === 3) {
|
|
732
|
+
recon = (rawByte + Math.floor((left + up) / 2)) & 0xff;
|
|
733
|
+
}
|
|
734
|
+
else if (filterType === 4) {
|
|
735
|
+
recon = (rawByte + paethPredictor(left, up, upLeft)) & 0xff;
|
|
736
|
+
}
|
|
737
|
+
unfiltered[dstRowStart + x] = recon;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// Converte qualquer PNG (RGB, RGBA, Escala de Cinza, Paleta) em RGB de 24 bits sobre fundo branco (#FFFFFF)
|
|
741
|
+
const rgbOut = Buffer.alloc(width * height * 3);
|
|
742
|
+
for (let i = 0; i < width * height; i++) {
|
|
743
|
+
let r = 255;
|
|
744
|
+
let g = 255;
|
|
745
|
+
let b = 255;
|
|
746
|
+
let a = 255;
|
|
747
|
+
if (colorType === 6) {
|
|
748
|
+
r = unfiltered[i * 4] ?? 0;
|
|
749
|
+
g = unfiltered[i * 4 + 1] ?? 0;
|
|
750
|
+
b = unfiltered[i * 4 + 2] ?? 0;
|
|
751
|
+
a = unfiltered[i * 4 + 3] ?? 255;
|
|
752
|
+
}
|
|
753
|
+
else if (colorType === 2) {
|
|
754
|
+
r = unfiltered[i * 3] ?? 0;
|
|
755
|
+
g = unfiltered[i * 3 + 1] ?? 0;
|
|
756
|
+
b = unfiltered[i * 3 + 2] ?? 0;
|
|
757
|
+
}
|
|
758
|
+
else if (colorType === 4) {
|
|
759
|
+
const gray = unfiltered[i * 2] ?? 0;
|
|
760
|
+
r = gray;
|
|
761
|
+
g = gray;
|
|
762
|
+
b = gray;
|
|
763
|
+
a = unfiltered[i * 2 + 1] ?? 255;
|
|
764
|
+
}
|
|
765
|
+
else if (colorType === 0) {
|
|
766
|
+
const gray = unfiltered[i] ?? 0;
|
|
767
|
+
r = gray;
|
|
768
|
+
g = gray;
|
|
769
|
+
b = gray;
|
|
770
|
+
}
|
|
771
|
+
else if (colorType === 3 && palette) {
|
|
772
|
+
const idx = unfiltered[i] ?? 0;
|
|
773
|
+
r = palette[idx * 3] ?? 0;
|
|
774
|
+
g = palette[idx * 3 + 1] ?? 0;
|
|
775
|
+
b = palette[idx * 3 + 2] ?? 0;
|
|
776
|
+
if (trns && idx < trns.length) {
|
|
777
|
+
a = trns[idx] ?? 255;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (a < 255) {
|
|
781
|
+
const alpha = a / 255;
|
|
782
|
+
r = Math.round(r * alpha + 255 * (1 - alpha));
|
|
783
|
+
g = Math.round(g * alpha + 255 * (1 - alpha));
|
|
784
|
+
b = Math.round(b * alpha + 255 * (1 - alpha));
|
|
785
|
+
}
|
|
786
|
+
rgbOut[i * 3] = r;
|
|
787
|
+
rgbOut[i * 3 + 1] = g;
|
|
788
|
+
rgbOut[i * 3 + 2] = b;
|
|
789
|
+
}
|
|
790
|
+
const compressedRgb = deflateSync(rgbOut);
|
|
791
|
+
const hexStream = compressedRgb.toString('hex') + '>';
|
|
792
|
+
return {
|
|
793
|
+
width,
|
|
794
|
+
height,
|
|
795
|
+
colorSpace: '/DeviceRGB',
|
|
796
|
+
bitsPerComponent: 8,
|
|
797
|
+
filter: '[/ASCIIHexDecode /FlateDecode]',
|
|
798
|
+
hexStream,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
static buildPdfBuffer(contentStreams, logoEmbed) {
|
|
804
|
+
const pageObjNums = [];
|
|
805
|
+
const pageObjects = [];
|
|
806
|
+
// Objetos 1..4: Catalog (1), Pages (2), Font Regular (3), Font Bold (4)
|
|
807
|
+
// Objeto 5 (opcional): XObject Image /Im1 (quando logoEmbed estiver presente)
|
|
808
|
+
const imageObjNum = logoEmbed ? 5 : undefined;
|
|
809
|
+
let nextObjNum = logoEmbed ? 6 : 5;
|
|
810
|
+
for (const stream of contentStreams) {
|
|
811
|
+
const pageObjNum = nextObjNum++;
|
|
812
|
+
const contentObjNum = nextObjNum++;
|
|
813
|
+
pageObjNums.push(pageObjNum);
|
|
814
|
+
const xObjectResource = imageObjNum ? ` /XObject << /Im1 ${imageObjNum} 0 R >>` : '';
|
|
815
|
+
const streamBytes = Buffer.byteLength(stream, 'latin1');
|
|
816
|
+
pageObjects.push(`${pageObjNum} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595.28 841.89] /Resources << /Font << /F1 3 0 R /F2 4 0 R >>${xObjectResource} >> /Contents ${contentObjNum} 0 R >>\nendobj\n`);
|
|
817
|
+
pageObjects.push(`${contentObjNum} 0 obj\n<< /Length ${streamBytes} >>\nstream\n${stream}\nendstream\nendobj\n`);
|
|
818
|
+
}
|
|
819
|
+
const kidsRefs = pageObjNums.map((n) => `${n} 0 R`).join(' ');
|
|
346
820
|
const objects = [
|
|
347
821
|
'1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n',
|
|
348
|
-
|
|
349
|
-
'3 0 obj\n<< /Type /
|
|
350
|
-
'4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n',
|
|
351
|
-
'5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\nendobj\n',
|
|
352
|
-
`6 0 obj\n<< /Length ${streamBytes} >>\nstream\n${contentStream}\nendstream\nendobj\n`,
|
|
822
|
+
`2 0 obj\n<< /Type /Pages /Kids [${kidsRefs}] /Count ${pageObjNums.length} >>\nendobj\n`,
|
|
823
|
+
'3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n',
|
|
824
|
+
'4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\nendobj\n',
|
|
353
825
|
];
|
|
826
|
+
if (logoEmbed && imageObjNum) {
|
|
827
|
+
const imgStreamLen = Buffer.byteLength(logoEmbed.hexStream, 'latin1');
|
|
828
|
+
objects.push(`${imageObjNum} 0 obj\n<< /Type /XObject /Subtype /Image /Width ${logoEmbed.width} /Height ${logoEmbed.height} /ColorSpace ${logoEmbed.colorSpace} /BitsPerComponent ${logoEmbed.bitsPerComponent} /Filter ${logoEmbed.filter} /Length ${imgStreamLen} >>\nstream\n${logoEmbed.hexStream}\nendstream\nendobj\n`);
|
|
829
|
+
}
|
|
830
|
+
objects.push(...pageObjects);
|
|
354
831
|
let pdf = '%PDF-1.4\n';
|
|
355
832
|
const offsets = [0];
|
|
356
833
|
for (const obj of objects) {
|