Skip to content

fix: alinha DOI à direita e confina título do artigo à coluna própria - #1330

Open
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/pdf-header-doi-article-title-alignment
Open

fix: alinha DOI à direita e confina título do artigo à coluna própria#1330
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/pdf-header-doi-article-title-alignment

Conversation

@Rossi-Luciano

@Rossi-Luciano Rossi-Luciano commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Resolve dois problemas do cabeçalho do PDF: o DOI (página 1) não ficava alinhado à margem direita, e o título do artigo nas páginas internas (>1) aparecia colado ao título do periódico em vez de confinado à própria área.

Os dois compartilhavam a mesma causa: DOI e título do periódico dividiam o mesmo parágrafo, separados só por um caractere de tabulação (\t). Uma tab stop define onde um texto começa, não onde ele quebra linha — uma linha que estoura volta para a margem esquerda do parágrafo, não para a posição da tab. Quando a segunda linha do título do periódico (_format_journal_title_two_lines) já era longa, não sobrava espaço na linha para o DOI alcançar a margem direita, e ele quebrava para uma 3ª linha em vez de ficar alinhado (visto em a30.xml: "URBE. / REVISTA BRASILEIRA DE GESTÃO URBANA" colidindo com o DOI).

A correção usa uma tabela sem borda de 2 colunas no cabeçalho (helper novo _add_two_column_header_table, reaproveitado pelos dois pares): título do periódico + DOI na página 1, título do periódico + título do artigo nas páginas seguintes (esse segundo par já usava uma tabela desde o fix de #1301; o helper foi extraído para os dois casos compartilharem). Cada coluna tem largura própria, então o conteúdo de uma nunca disputa espaço horizontal com o da outra.

O par periódico/DOI usa split 65/35 em vez de 50/50 (usado no par periódico/artigo, sem mudança): testei contra vários títulos longos do corpus real — títulos de 5-6 palavras não são raros (8 dos 26 artigos) — e contra o maior DOI do corpus (54 caracteres). 50/50 empurrava títulos longos para 3 linhas sem necessidade, já que o DOI cabe com folga em bem menos da metade do espaço. Não existe um split único que garanta 1 linha dos dois lados sempre (quebra de texto acontece em pontos discretos, não contínuos); 65/35 foi o que manteve o título do periódico em 2 linhas nos casos testados, aceitando que o DOI às vezes quebre em 2 linhas — mas sempre alinhado à direita, nunca colidindo com o título.

Onde a revisão poderia começar?

packtools/sps/formats/pdf/pipeline/docx.py:

  • _add_two_column_header_table (helper novo, no bloco de helpers privados)
  • docx_journal_title_pipe e docx_doi_pipe (página 1)
  • docx_second_header_pipe (páginas >1, já era tabela, só passou a reusar o helper)

Como este poderia ser testado manualmente?

python -m packtools.sps.formats.pdf_generator \
  -i <artigo-com-titulo-de-periodico-longo>.xml \
  -l layout.docx \
  -o saida.pdf \
  --libreoffice-binary libreoffice

Conferir a página 1 (DOI alinhado à direita, mesmo com título de periódico longo) e uma página interna (título do artigo confinado à direita, sem colidir com o título do periódico).

Algum cenário de contexto que queira dar?

Achado revisando visualmente o corpus de teste de 26 artigos reais (layout_examples_rafael/corpus/). Validado contra o corpus inteiro (as 26 amostras geram sem erro) e visualmente em a30.xml (o caso original do DOI colidindo) e a6.xml (título de artigo mais longo do corpus, 172 caracteres, para estressar a quebra de linha na coluna própria).

Screenshots

itemD_before_after

Quais são os tickets relevantes?

Nenhuma issue aberta associada; achado durante revisão visual do corpus de teste do gerador de PDF.

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): mudança isolada de layout de cabeçalho do DOCX (tabela em vez de tab stop), sem I/O externo ou entrada não confiável

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)

🤖 Generated with Claude Code

https://claude.ai/code/session_01X8r2LRJ3PGTT9vLaPtb373

Itens 1 e 5 de revisao visual do corpus: o DOI (pagina 1) e o titulo do
artigo no cabecalho corrido (paginas >1) dividiam o mesmo paragrafo do
titulo do periodico, separados so por um caractere de tabulacao.

Uma tab stop define so onde um run COMECA, nao onde ele quebra linha -
uma linha quebrada volta para a margem esquerda do paragrafo, nao para
a posicao da tab. Isso tinha dois efeitos:

- DOI (docx_doi_pipe): quando a segunda linha do titulo do periodico
  (_format_journal_title_two_lines) ja era longa, nao sobrava espaco
  na linha pra tab+DOI chegar na margem direita, e ele quebrava pra
  uma 3a linha em vez de ficar alinhado a direita (visto em a30.xml:
  "URBE. / REVISTA BRASILEIRA DE GESTAO URBANA" + DOI colidindo).
- Titulo do artigo (docx_second_header_pipe, ja tinha virado tabela no
  fix anterior de scieloorg#1301): esse problema nao se aplicava mais ali, mas
  o DOI da pagina 1 ainda usava o padrao antigo de tab.

Os dois casos agora usam uma tabela sem borda de 2 colunas (helper
compartilhado _add_two_column_header_table): titulo do periodico/DOI
na pagina 1, titulo do periodico/titulo do artigo nas paginas
seguintes. Cada coluna tem largura propria, entao o conteudo de uma
nunca disputa espaco horizontal com o da outra na mesma linha.

Par titulo do periodico/DOI usa split 65/35 em vez de 50/50 (usado no
par titulo do periodico/titulo do artigo, sem mudanca): testado contra
varios titulos longos do corpus real (a30, a13 - titulos de 5-6
palavras nao sao raros, 8 dos 26 artigos do corpus) e o maior DOI do
corpus (54 chars) - 50/50 empurrava titulos assim pra 3 linhas sem
necessidade, ja que o DOI cabe com folga em menos da metade do espaco.
Nao existe split unico que garanta 1 linha nos dois lados sempre (texto
quebra em pontos discretos, nao continuos); 65/35 foi o que manteve o
titulo do periodico em 2 linhas nos casos testados, aceitando que o DOI
as vezes quebra em 2 linhas (ainda alinhado a direita, nunca colidindo).

Validado contra as 26 amostras do corpus (todas geram sem erro) e
visualmente em a30.xml (DOI) e a6.xml (titulo de artigo mais longo do
corpus, 172 chars).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8r2LRJ3PGTT9vLaPtb373
r1 = para.add_run(_format_journal_title_two_lines(journal_title))
journal_para = journal_cell.paragraphs[0]
journal_para.style = docx.styles[paragraph_header_style_name]
r1 = journal_para.add_run(_format_journal_title_two_lines(journal_title))

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.

O cabeçalho corrido não deve reutilizar a formatação do masthead da primeira página. Essa quebra forçada faz urbe. Revista Brasileira de Gestão Urbana ocupar três linhas na página 2 quando combinada com a coluna de 50%. Use journal_title sem _format_journal_title_two_lines() e aplique SCL Header Paragraph Char ao run do periódico.

left_width = int(content_width * left_ratio)
right_width = content_width - left_width

table = container.add_table(rows=1, cols=2, width=content_width)

@pitangainnovare pitangainnovare Sep 5, 2026

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.

Header/Footer já começa com um parágrafo vazio. A tabela é inserida depois dele, deslocando o cabeçalho verticalmente. Sugiro remover os parágrafos de placeholder criados automaticamente pelo python-docx, após confirmar que eles não contém texto, runs ou conteúdo gráfico.

table = container.add_table(rows=1, cols=2, width=content_width)
table.autofit = False
table.allow_autofit = False

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.

A tabela padrão mantém w:tblCellMar (≈5,4 pt), criando recuo nas duas extremidades e desalinhando o cabeçalho do corpo. Configure w:tblCellMar com top/left/bottom/right = 0.

@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.

Em teste manual com vários XMLs, percebi alguns problemas:

  1. No cabeçalho corrido, o título do periódico continua usando _format_journal_title_two_lines() e o estilo SCL Journal Title Char, que pertence ao masthead da primeira página. Com a nova coluna de 50%,
    isso faz urbe. Revista Brasileira de Gestão Urbana ocupar três linhas. A sugestão é usar diretamente journal_title e aplicar SCL Header Paragraph Char, mantendo o periódico em uma única linha sempre que
    houver espaço.

  2. O Header/Footer criado pelo python-docx contém um parágrafo placeholder vazio. Como add_table() insere a tabela depois desse parágrafo, o cabeçalho da primeira página passa de aproximadamente y=28,3 pt para y=41,0 pt. A sugestão é remover somente esse placeholder automático, verificando que ele realmente não possui texto, runs ou conteúdo gráfico, sem apagar parágrafos vazios intencionais.

  3. As células da tabela mantêm margens internas laterais de aproximadamente 5,4 pt. Na renderização, o cabeçalho começa em x=62,2 pt, enquanto o corpo começa em x=56,8 pt. A sugestão é sobrescrever
    w:tblCellMar, ao menos para left e right, com valor zero. Zerar os quatro lados também é aceitável para esse cabeçalho.

  4. Como ajuste adicional, para.alignment = WD_ALIGN_PARAGRAPH.RIGHT deveria ficar depois do if/else externo de docx_doi_pipe. Atualmente, quando o chamador fornece paragraph, o DOI perde tanto a
    tabulação removida pelo PR quanto o alinhamento à direita. Esse caminho deveria receber um teste específico.

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