Skip to content

fix: passa largura explícita ao inserir figura, evitando mismatch de DPI - #1300

Open
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/figure-insert-explicit-width-pr
Open

fix: passa largura explícita ao inserir figura, evitando mismatch de DPI#1300
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/figure-insert-explicit-width-pr

Conversation

@Rossi-Luciano

@Rossi-Luciano Rossi-Luciano commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Problema existente: _try_insert_picture chama run.add_picture(img_path) sem passar width. O python-docx então lê o DPI do arquivo por conta própria pra decidir o tamanho — independente de _infer_image_dpi, a função que o resto do código já usa (em decide_figure_layout) pra decidir se a figura cabe na coluna. As duas leituras de DPI podem discordar: sem metadado de DPI, o python-docx assume 72 DPI enquanto _infer_image_dpi assume outro valor — resultando em ~33% de diferença entre "a largura que o código decidiu" e "a largura que realmente foi inserida".

Solução proposta: calcular a largura natural da imagem via _infer_image_dpi (a mesma função já usada na decisão), capada ao teto disponível — nunca ampliando além do espaço da coluna/página — e passar esse valor explicitamente como width= no add_picture(), em vez de deixar o python-docx inferir sozinho.

Resultado: a largura inserida na figura passa a bater exatamente com a largura que o código decidiu. Construí um exemplo controlado (imagem de teste de 200px sem metadado de DPI, inserida numa cópia do a1.xml) pra medir a diferença de forma isolada: antes, a imagem era inserida a 7,06cm (72 DPI, inferência própria do python-docx); depois, a 5,29cm (96 DPI, _infer_image_dpi) — exatamente a razão 96/72 do bug descrito. Não consegui reproduzir esse caso específico com os 4 artigos reais de tests/fixtures/pdf/ porque as imagens deles já têm DPI explícito no arquivo (divergência de DPI entre figuras, não ausência de DPI — esse é o achado #2, já em outro PR).

Onde a revisão poderia começar?

packtools/sps/formats/pdf/renderer/docx/figure.py, funções add_figure, _natural_width_capped (nova) e _try_insert_picture.

Como este poderia ser testado manualmente?

Testes automatizados: python -m unittest discover -s tests/sps/formats/pdf -v (inclui teste de regressão que insere uma figura sem DPI e confirma que a largura bate com _infer_image_dpi, não com o default do python-docx).

Validado também ponta a ponta nos 4 fixtures reais (a1-a4): mesma contagem de página, sem regressão — o fix não afeta imagens que já têm DPI correto/consistente.

Algum cenário de contexto que queira dar?

Encontrado e corrigido originalmente durante o trabalho do PR #1279 (fechado por ser grande demais — 14 arquivos, 54% de um módulo novo em docstring). O comentário de fechamento registra este bugfix como "já validado" e destinado a ser resubmetido separadamente, junto com outro (/2 fixo no número de colunas, já em outro PR).

Screenshots

demo_width_before_after

Quais são os tickets relevantes?

Closes #1299.

Referências

N/A


Segurança da informação (NSI.04)

Seção obrigatória. Marque as opções aplicáveis e justifique quando necessário. Referência: NSI.04 - Norma de Desenvolvimento Seguro.

Este PR manipula dados sensíveis ou pessoais (LGPD)?

  • Sim — descreva os controles de proteção aplicados (criptografia, mascaramento, anonimização, etc.):
  • Não

Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?

  • Sim — descreva o que mudou e por quê:
  • Não

Este PR introduz, atualiza ou remove dependências de terceiros?

  • Sim — as novas dependências foram verificadas no SBOM/Trivy sem vulnerabilidades críticas/altas em aberto?
    • Verificado e aprovado
    • Pendente / vulnerabilidade aceita com justificativa:
  • Não

Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?

  • Sim — link do job:
  • Não aplicável a este PR (justifique): Trivy/SonarQube não estão configurados neste repositório (packtools é biblioteca Python, não serviço containerizado) — ver SECURITY_ADHERENCE.md. Os gates automáticos reais deste repositório (Snyk e GitGuardian) rodam via CI neste PR.

Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?

  • Sim — confirme que há sanitização/parametrização (prepared statements, escaping, etc.):
  • Não

Este PR expõe novos endpoints, telas ou serviços?

  • Sim — HTTPS obrigatório está garantido e o acesso segue o princípio de menor privilégio?
  • Não

Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?

  • Não, nenhum segredo foi commitado
  • Sim (bloquear merge e corrigir antes de prosseguir)

_try_insert_picture deixava o python-docx inferir a largura da imagem por
conta própria (padrão 72 DPI quando ausente metadado), independente da
_infer_image_dpi já usada para decidir a largura da figura (padrão 96 DPI) -
os dois podiam divergir em ~33% para a mesma imagem. Passa a largura já
decidida (capada ao teto disponível, nunca ampliada) explicitamente para
add_picture(width=...).

Refs scieloorg#1278.
if not (img_path and os.path.exists(img_path)):
return ceiling_width
try:
from PIL import Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mover este import para o topo, removendo desta linha e de outra neste arquivo.


return img_path

def _natural_width_capped(img_path, ceiling_width):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note que:

  • decide_figure_layout escolhe entre largura total e largura de coluna.
  • _natural_width_capped determina a largura efetiva de inserção, limitada ao teto.

Nelas há um trecho duplicado que se refere ao cálculo da largura natural:

px_w = im.width
dpi = _infer_image_dpi(im)
natural_width = (px_w / max(1.0, dpi)) * 2.54

Ainda,

  • decide_figure_layout mantém o resultado como float em centímetros;
  • _natural_width_capped converte o resultado com Cm(...), produzindo um objeto Length representado internamente em EMU.

As funções têm responsabilidades diferentes, mas ambas abrem a imagem e calculam sua largura natural a partir de pixels e DPI. Além da duplicação, atualmente elas produzem unidades diferentes: decide_figure_layout mantém um float em centímetros, enquanto _natural_width_capped retorna Cm/EMU.

Podemos extrair uma operação única que devolva a largura natural em Cm e reutilizá-la tanto na decisão quanto no limite de inserção? Isso mantém interpretação de DPI e unidade consistentes nos dois fluxos.

@pitangainnovare pitangainnovare left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seria desejável atender aos dois comentários apresentados, embora isso não seja obrigatório.

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.

figure.py: imagem inserida sem largura explícita diverge da decisão de layout (~33%)

2 participants