Skip to content

dacte: recorta textos longos e agrega componentes excedentes - #186

Closed
andmit10 wants to merge 1 commit into
Engenere:mainfrom
andmit10:fix/dacte-text-overflow
Closed

dacte: recorta textos longos e agrega componentes excedentes#186
andmit10 wants to merge 1 commit into
Engenere:mainfrom
andmit10:fix/dacte-text-overflow

Conversation

@andmit10

Copy link
Copy Markdown
Contributor

Problema

O layout do DACTE desenha em posição absoluta, e alguns campos não recortam o texto. Quando o conteúdo é mais largo que a coluna, ele é escrito por cima do campo vizinho — o PDF fica ilegível naquele ponto.

Três situações, todas em CT-e válido (nenhuma depende de XML fora do leiaute):

1. Razão social longa do emitente cobre a linha do CNPJ

O nome é desenhado com multi_cell(h=5), que quebra linha, mas o endereço logo abaixo é ancorado num Y fixo (y_text + 6) — ou seja, só há uma linha de 5 mm reservada:

https://github.com/Engenere/BrazilFiscalReport/blob/main/brazilfiscalreport/dacte/dacte.py#L358-L363

Com um xNome de 39 caracteres, a segunda linha do nome cai exatamente sobre a linha CNPJ: ... IE: ...:

RODONAVES TRANSPORTES E
CNPJ: ENCOMENDAS LTDA82533118      <- nome e CNPJ sobrepostos

Vale notar que todos os outros nomes do próprio DACTE já são recortadosrem_nome (limit_text(..., 48)), dest_nome, receb_nome, exped_nome, tomador_nome (38) — e o DANFE faz o mesmo em danfe.py com long_field(limit=60). O emitente ficou de fora.

2. Nome de componente invade a coluna do valor

Na seção "COMPONENTES DO VALOR DA PRESTAÇÃO DO SERVIÇO", o xNome é escrito com cell() numa coluna de col_width / 2. O cell() não recorta nem quebra:

OUTROS VALORES331.77      <- nome e valor colados

Medindo: col_width/2 = 23,75 mm, menos 2 × c_margin do fpdf2 = 21,75 mm úteis; "OUTROS VALORES" em Times 8 mede 25,01 mm. Sobra 1,26 mm por cima do primeiro dígito do valor.

xNome de Comp é texto livre preenchido pelo emitente, então nomes largos são comuns na prática.

3. Do 10º componente em diante, o valor some do PDF

A distribuição é fixa em 3 colunas × 3 linhas (comp_list[:3], [3:6], [6:9]). O que passa disso não é desenhado — sem aviso, e sem que a soma exibida corresponda ao que o XML declara.

O que muda

  • (1) e (2): passam a usar long_field — o mesmo recorte por largura medida já usado no DANFE e nos demais nomes do DACTE. Nenhum helper novo.
  • (3): o excedente é agregado numa linha OUTROS, preservando a soma. Um helper local to_float tolera valor ausente/malformado.

Nada disso muta self.emit_name nem self.comp_list — só o texto renderizado.

O item (3) é a parte mais opinativa deste PR. Agregar em "OUTROS" me pareceu melhor que descartar em silêncio, mas se vocês preferirem outra saída (reduzir a fonte, paginar o bloco, ou simplesmente documentar o limite), fico à vontade para ajustar.

Testes

Novo arquivo tests/test_dacte_overflow.py. Os testes renderizam o PDF e leem de volta as posições do texto do content stream, em vez de checar a chamada de um helper — assim medem o resultado e continuam válidos se a implementação mudar:

  • test_razao_social_longa_nao_escreve_por_cima_do_cnpj — nenhum texto pode partilhar a baseline da linha do CNPJ.
  • test_nome_de_componente_longo_nao_invade_a_coluna_do_valor — a largura renderizada do nome tem de caber na coluna.
  • test_componentes_alem_do_nono_aparecem_agregados — com 12 componentes, "OUTROS" aparece no PDF.
  • test_to_float_tolera_valor_ausente_ou_invalido.

Verifiquei que eles falham sem a correção. O primeiro aponta o texto exato que sobrepõe:

AssertionError: texto sobreposto à linha do CNPJ:
  [(735.73, 30.75, 'CARGAS GERAIS DO BRASIL SA ME')]

PDFs de referência

Inalterados. Nenhuma das 13 fixtures de DACTE tem razão social maior que 28 caracteres nem componente com nome largo — que é justamente por que os testes golden nunca pegaram isto. Suíte completa: 96 passed.

O layout do DACTE desenha em posição absoluta, então texto mais largo que a
coluna era escrito por cima do vizinho:

- Razão social longa do emitente quebrava em duas linhas e a segunda saía
  sobre a linha "CNPJ: ... IE: ..." (só há uma linha de 5 mm reservada,
  porque o endereço é ancorado em Y fixo).
- Nome de componente do valor da prestação invadia a coluna do valor
  ("OUTROS VALORES" mede 25,01 mm numa coluna de 21,75 mm).

Ambos passam a usar long_field, o mesmo recorte por largura já usado no
DANFE e nos demais nomes do próprio DACTE.

Além disso, o bloco de componentes comporta 9 linhas e do 10º em diante o
valor era descartado sem aviso; o excedente agora é agregado em OUTROS.
@antoniospneto

Copy link
Copy Markdown
Contributor

@andmit10 Bem vindo ao projeto, sua contribuição é bem vinda. apenas veja que o pre-commit falhou, consegue ajeitar?

@antoniospneto

Copy link
Copy Markdown
Contributor

Obrigado pelo trabalho aqui, @andmit10 — diagnóstico certo nos três casos, e os testes que leem a posição do texto de volta do content stream são a técnica certa pro problema. Confirmei que os quatro falham no main e passam com a correção; os goldens nunca pegariam isso.

Estou dividindo em duas para poder mergear a parte segura já:

Seus commits foram preservados com sua autoria nas duas. Ajustes que fiz em cima:

  • ruff format no arquivo de teste (a docstring começando com aspas reprovava o check pre-commit, que era o único CI vermelho aqui)
  • font_size=8 explícito no long_field do componente — a fonte ambiente já era essa, então o resultado é idêntico, mas sai do estado implícito
  • rótulo OUTROSDEMAIS: como xNome é texto livre de até 15 caracteres, um componente real pode se chamar "OUTROS", e o PDF sairia com duas linhas homônimas e valores diferentes
  • testes a mais na DACTE: agrega componentes do valor além do 9º em vez de descartá-los #191 cobrindo o que faltava: que a linha agregada preserva a soma (420.00), que 9 componentes exatos não agregam nada, e que a soma do impresso fecha com o XML para 6/9/12/20 componentes

Sobre o item 3, que você mesmo marcou como o mais opinativo: fui atrás do que as outras implementações fazem e nenhuma resolve. O ACBr (ACBrCTeDACTeRLRetrato.pas) distribui com case i of 0,3,6,9 / 1,4,7,10 / 2,5,8,11 sem ramo else — descarta em silêncio a partir do 13º, mesmo bug com teto maior. O sped-da desenha todos e deixa vazar pra fora do quadro. Então agregar coloca a lib à frente das duas; está separado na #191 justamente pra essa escolha poder ser discutida sem segurar o resto.

Um detalhe adjacente que saiu da revisão e vai virar issue à parte: o vComp é impresso cru do XML (331.77) enquanto ACBr e sped-da formatam em pt-BR (1.234,56) — e o próprio vTPrest ao lado já sai formatado.

Fechando esta em favor das duas. Valeu mesmo pela contribuição.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants