Skip to content

fix: permite corpo do artigo começar na página 1 quando há espaço - #1296

Open
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/pdf-body-starts-page-one
Open

fix: permite corpo do artigo começar na página 1 quando há espaço#1296
Rossi-Luciano wants to merge 1 commit into
scieloorg:masterfrom
Rossi-Luciano:fix/pdf-body-starts-page-one

Conversation

@Rossi-Luciano

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

Copy link
Copy Markdown
Contributor

O que esse PR faz?

Problema existente: a transição do rosto do artigo (título/autores/resumo/palavras-chave, 1 coluna) para o corpo (Introdução em diante, 2 colunas) usa docx.add_section() sem argumento — que no python-docx tem como padrão WD_SECTION.NEW_PAGE, uma quebra de página forçada e incondicional. O corpo do artigo nunca pode começar na página 1, mesmo quando o rosto é curto o bastante para sobrar espaço — a página 1 fica com um vazio entre o fim do resumo e o rodapé "CITE AS" (issue #1295).

Solução proposta: trocar a quebra de WD_SECTION.NEW_PAGE por WD_SECTION.CONTINUOUS, deixando o corpo fluir para a página 1 quando houver espaço. Essa troca isolada quebrou o cabeçalho corrente ("nome do periódico" + título do artigo) em todas as páginas a partir da 2ª — confirmado com um teste mínimo isolado (só python-docx + LibreOffice, sem código do packtools) que uma seção contínua e desvinculada da anterior (is_linked_to_previous = False) não tem seu cabeçalho renderizado pelo LibreOffice na conversão pra PDF. Corrigido escrevendo o cabeçalho corrente no header padrão da própria primeira seção (distinto do first_page_header, que guarda só o rosto da capa) em vez de numa seção separada e desvinculada — a seção do corpo passa a herdar esse cabeçalho em vez de redefini-lo.

get_or_create_second_section já nasce parametrizável (page_attributes['start_type'], default CONTINUOUS) em vez de deixar o novo comportamento hardcoded — evita ter que mexer na mesma função de novo quando a configuração de layout por JSON (issue #1278) for implementada.

Resultado: a página 1 passa a aproveitar o espaço disponível, com o corpo do artigo começando ali quando o rosto permite — sem quebrar cabeçalho nem rodapé em nenhuma página. Quem precisar da convenção antiga (rosto sempre isolado na página 1) pode obter isso hoje passando page_attributes com start_type: WD_SECTION.NEW_PAGE diretamente na API Python — a exposição via CLI/JSON fica pra uma etapa seguinte.

Onde a revisão poderia começar?

packtools/sps/formats/pdf/renderer/docx/section.py, funções get_or_create_second_section e get_default_header (nova). Depois packtools/sps/formats/pdf/pipeline/docx.py::docx_second_header_pipe.

Como este poderia ser testado manualmente?

python -m packtools.sps.formats.pdf_generator -i tests/fixtures/pdf/a4.xml -l tests/fixtures/pdf/layout.docx -o saida.pdf

Comparar a página 1 antes/depois (imagens anexadas) — e confirmar que o cabeçalho ("ECONOMIA E SOCIEDADE" + título) continua aparecendo normalmente da página 2 em diante.

Testes automatizados: python -m unittest discover -s tests/sps/formats/pdf -v.

Algum cenário de contexto que queira dar?

Encontrado durante um levantamento mais amplo de qualidade do gerador de PDF contra um corpus de artigos reais (tests/fixtures/pdf/), comparando a saída atual contra os PDFs editoriais canônicos (*.gt.pdf). Faz parte do escopo da issue #1278 (layout de página/coluna configurável).

Screenshots

comparison_p1_before_after

Quais são os tickets relevantes?

Refs #1278. Closes #1295.

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)

get_or_create_second_section criava a seção do corpo (2 colunas) com
docx.add_section() sem argumento - que no python-docx tem como padrão
WD_SECTION.NEW_PAGE, uma quebra de página forçada e incondicional. Isso
significa que o corpo do artigo (Introdução em diante) nunca podia começar
na página 1, mesmo quando o rosto (título/autores/resumo/palavras-chave)
era curto o bastante para sobrar espaço - a página 1 ficava com um vazio
entre o fim do resumo e o rodapé "CITE AS".

Reproduzido nos 4 fixtures reais de tests/fixtures/pdf/ (a1-a4). Comparando
com os PDFs canônicos (ground_truth), pelo menos parte deles preenche a
página 1 com o início do corpo quando cabe.

Trocar a quebra para WD_SECTION.CONTINUOUS resolve o vazio, mas quebra o
cabeçalho corrente ("ECONOMIA E SOCIEDADE" + título do artigo) em todas as
páginas a partir da 2ª - confirmado com um teste mínimo isolado (só
python-docx + LibreOffice, sem código do packtools): um cabeçalho definido
numa seção contínua e desvinculada da anterior (is_linked_to_previous =
False) simplesmente não é renderizado pelo LibreOffice na conversão pra
PDF. É uma limitação real do LibreOffice, não bug do packtools.

Corrigido escrevendo o cabeçalho corrente no header padrão da própria
seção 0 (get_default_header, distinto do first_page_header que guarda o
rosto) em vez de numa seção 2 separada e desvinculada - a seção do corpo
fica vinculada (herda o cabeçalho) em vez de redefini-lo, o que renderiza
corretamente no LibreOffice mesmo através de uma quebra contínua. Também
removido o unlink forçado da seção de índice 1 em
_apply_header_footer_linking, que reintroduzia o mesmo problema no fim do
pipeline.

get_or_create_second_section agora aceita page_attributes e lê start_type
de lá (default continua CONTINUOUS) - o comportamento novo já nasce
configurável, em vez de ficar hardcoded e precisar ser retrabalhado depois
quando a configuração de layout por JSON (issue scieloorg#1278) for implementada.
Atualizado também o valor default de PAGE_ATTRIBUTES['start_type'] em
enum.py, que já existia como campo mas nunca era lido em lugar nenhum e
ainda apontava pro comportamento antigo (NEW_PAGE).

Validado ponta a ponta nos 4 fixtures: página 1 aproveitada, cabeçalho e
rodapé corretos em todas as páginas (checado explicitamente na 2ª e na
última página de cada um). Saída idêntica à versão anterior deste commit
(antes de parametrizar) confirmada por diff de texto.

Refs scieloorg#1278.
current_page_number = 1

second_section = docx_renderer.section.get_or_create_second_section(docx)
docx_renderer.section.set_start_page_number(second_section, current_page_number)

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.

Esse reinício aplicado somente à segunda seção não é respeitado como esperado pelo LibreOffice quando a seção é contínua. Nos fixtures, a página 2 passa a exibir o número físico (2), inclusive no A1, onde deveria exibir 272.

Testei aplicar a numeração à primeira seção, mas, se as demais seções tiverem configuração diferente, o LibreOffice promove a quebra contínua para uma nova página. Uma solução que preservou simultaneamente o corpo na página 1 e a numeração correta foi, depois de docx_setup_sections, aplicar o mesmo w:pgNumType a todas as seções: usar fpage como início ou 0 quando ele estiver ausente. Assim, obtive A1 271 --> 272 e A2–A4 com página 2 igual a 1, sem reintroduzir a página em branco.

Sugiro também retirar essa responsabilidade de docx_second_footer_pipe, deixando essa função apenas montar o rodapé, e configurar a numeração globalmente após todas as seções terem sido criadas.

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

A mudança para uma seção contínua resolve o espaço em branco e permite que o corpo comece na primeira página, mas introduz uma regressão bloqueante na numeração. Na conversão com LibreOffice, o reinício configurado apenas na segunda seção não é respeitado como esperado: no A1, por exemplo, a página 2 passa a exibir 2, quando deveria exibir 272. A2–A4 também exibem 2, quando o esperado é 1.

Testei algumas alternativas localmente. Configurar somente a primeira seção corrige os números, mas faz o LibreOffice promover a quebra contínua para uma nova página, eliminando o benefício principal deste PR. A solução que preservou os dois comportamentos foi aplicar o mesmo início de numeração a todas as seções, depois que elas já estiverem criadas:

docx_renderer.section.docx_setup_sections(docx)

try:
    start_page_number = int(footer_data["fpage"])
except ValueError:
    start_page_number = 0

for section in docx.sections:
    docx_renderer.section.set_start_page_number(
        section,
        start_page_number,
    )

O início deve ser fpage, e não fpage + 1, porque o campo PAGE avança naturalmente nas páginas seguintes. Quando fpage estiver ausente, iniciar em 0 faz a segunda página ser numerada como 1.

Com essa configuração uniforme, obtive:

  • A1: página 1 = 271, página 2 = 272, mantendo 13 páginas;
  • A2: página 2 = 1, última página = 8;
  • A3 e A4: página 2 = 1, última página = 7;
  • corpo permanecendo na primeira página e cabeçalho corrido preservado.

Sugiro também retirar a configuração da numeração de docx_second_footer_pipe. Essa função pode ficar responsável apenas pela composição do rodapé, enquanto a numeração global é aplicada no final de pipeline_docx, depois de docx_setup_sections.

Por fim, seria importante acrescentar uma validação que proteja conjuntamente os dois resultados após a conversão pelo LibreOffice: o corpo deve começar na página 1 e a página 2 deve apresentar o número editorial correto. Os testes estruturais atuais passam mesmo quando o PDF renderizado contém a numeração incorreta.

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.

pdf_generator: corpo do artigo nunca começa na página 1, mesmo com espaço sobrando

2 participants