Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/danfse/desenho.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
formatarPercentual,
formatarTelefone,
juntar,
limparTextoPdf,
reticencias,
ufDoCodigoMunicipio,
} from './formatadores.js';
Expand Down Expand Up @@ -117,7 +118,7 @@ export async function desenharDanfse(dados: NfseLegivel, opcoes: OpcoesGerarDanf
// nesses tamanhos de fonte.
const semLigadura = { liga: false } as unknown as PDFKit.Mixins.OpenTypeFeatures[];
function escrever(texto: string, x: number, y: number, opcoes: PDFKit.Mixins.TextOptions = {}): void {
documento.text(texto, x, y, { ...opcoes, features: semLigadura });
documento.text(limparTextoPdf(texto), x, y, { ...opcoes, features: semLigadura });
}
function larguraDoTexto(texto: string): number {
return documento.widthOfString(texto, { features: semLigadura });
Expand Down Expand Up @@ -417,7 +418,7 @@ export async function desenharDanfse(dados: NfseLegivel, opcoes: OpcoesGerarDanf
const alturaLinha = tamanhoFonte * 1.15;
const disponivel = margemSup + alturaUtil - alturaReservadaAposServico() - (y + 3.0 * MM);
const maxLinhas = Math.max(Math.floor(disponivel / alturaLinha), 1);
let descricao = s.descricao;
let descricao = limparTextoPdf(s.descricao);
const linhas = contarLinhas(descricao, bw - 1.6 * MM, tamanhoFonte);
if (linhas > maxLinhas) {
const aproximado = Math.floor((descricao.length * maxLinhas) / linhas) - 4;
Expand Down
23 changes: 23 additions & 0 deletions src/danfse/formatadores.teste.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { limparTextoPdf } from './formatadores.js';

test('limparTextoPdf troca CRLF e CR solto por LF', () => {
assert.equal(limparTextoPdf('linha1\r\nlinha2\rlinha3'), 'linha1\nlinha2\nlinha3');
});

test('limparTextoPdf troca tab por espaco', () => {
assert.equal(limparTextoPdf('coluna1\tcoluna2'), 'coluna1 coluna2');
});

test('limparTextoPdf remove outros caracteres de controle sem apagar o texto ao redor', () => {
assert.equal(limparTextoPdf('abc\x00\x01\x1fdef'), 'abcdef');
});

test('limparTextoPdf preserva LF (quebra de linha legitima)', () => {
assert.equal(limparTextoPdf('linha1\nlinha2'), 'linha1\nlinha2');
});

test('limparTextoPdf devolve string vazia para undefined', () => {
assert.equal(limparTextoPdf(undefined), '');
});
16 changes: 16 additions & 0 deletions src/danfse/formatadores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,19 @@ export function reticencias(texto: string | undefined, limite: number): string {
const ultimoEspaco = cortado.lastIndexOf(' ');
return (ultimoEspaco > 0 ? cortado.slice(0, ultimoEspaco) : cortado) + '...';
}

/**
* Normaliza texto vindo do XML antes de desenhar no PDF: CR/CRLF viram LF,
* tab vira espaço e outros caracteres de controle são removidos. Sem isso,
* um \r (retorno de carro) ou \t soltos no XML aparecem como glifo visível
* no DANFSe em vez de virar quebra de linha ou espaço - visto em descrições
* de serviço e informações complementares com texto colado de fora.
*/
export function limparTextoPdf(texto: string | undefined): string {
if (!texto) return '';
return texto
.replace(/\r\n?/g, '\n')
.replace(/\t/g, ' ')
// eslint-disable-next-line no-control-regex
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
}
Loading