Introduz LayoutConfig: layout de página/coluna configurável por periódico - #1279
Open
Rossi-Luciano wants to merge 18 commits into
Open
Introduz LayoutConfig: layout de página/coluna configurável por periódico#1279Rossi-Luciano wants to merge 18 commits into
Rossi-Luciano wants to merge 18 commits into
Conversation
…ura + justificativa)
Propósito:
Nenhuma abstração reutilizável existe hoje pra decisão de layout (1/2
colunas, largura de tabela/figura) — cada ponto do pipeline resolve isso
sozinho, sem retornar largura real nem justificativa.
Solução técnica:
- PageProfile: geometria de página/coluna/margem, com default igual ao
comportamento atual (A4, 2 colunas) e campos calibráveis por periódico.
- LayoutDecision: largura + justificativa + origem (medido/heurística),
não só um rótulo de string.
- LayoutConfig: decide_table_layout/decide_figure_layout unificam a
decisão de largura; full_width() abstrai quebra/restauração temporária
de coluna.
- load_profile(issn_epub): carrega profiles/{issn}.json, com fallback pro
default quando o periódico não estiver calibrado.
Refs scieloorg#1278
Propósito: Fornecer o primeiro perfil real de periódico para LayoutConfig, calibrado por medição direta contra PDF publicado. Solução técnica: - profiles/1809-4392.json: altura de página 793.7pt (não é A4) e 2 colunas, medidos contra tests/fixtures/pdf/a1.pdf. Refs scieloorg#1278
Propósito: Segundo periódico calibrado, usado para validar que LayoutConfig generaliza além do primeiro caso. Solução técnica: - profiles/1677-941X.json: altura de página 793.7pt e 2 colunas. - Documenta em known_limitations que o dimensionamento de largura de figura dentro do teto não segue regra geral descoberta (Figura 1 do PDF real usa ~2.2x o tamanho nativo confiável) — deliberadamente não resolvido aqui, ver scieloorg#1278. Refs scieloorg#1278
Propósito: Terceiro periódico calibrado — o único de 1 coluna entre os três, usado para expor bugs que só aparecem fora do caso 2-colunas. Solução técnica: - profiles/2357-738X.json: altura de página A4 (841.89pt) e 1 coluna. Refs scieloorg#1278
Propósito: Cobrir o módulo novo antes de qualquer código do pipeline passar a depender dele. Solução técnica: - TestPageProfile: aritmética de largura de conteúdo/coluna, roundtrip to_dict/from_dict, to_page_attributes. - TestLayoutConfigColumns: column_count default vs. explícito, full_width() com restauração (inclusive aninhado e sob exceção). - TestDecideTableLayout/TestDecideFigureLayout: fallback heurístico vs. largura medida, threshold configurável. - TestLoadProfile: perfil ausente cai no default; os 3 perfis reais calibrados carregam com os valores esperados. Refs scieloorg#1278
…trônico
Propósito:
A decisão de largura de tabela era uma heurística isolada (">4 colunas"),
sem acesso à geometria real de coluna do periódico; e não havia como
descobrir automaticamente qual periódico um XML pertence.
Solução técnica:
- determine_table_layout/extract_table_data/extract_body_data recebem
layout_config opcional; comportamento anterior preservado quando
omitido.
- extract_issn_epub(xml_tree): lê <issn pub-type="epub">, o mesmo
identificador que o SciELO já usa como chave em
minio.scielo.br/documentstore/ — permite carregar o perfil do
periódico direto do XML de entrada, sem flag manual.
Refs scieloorg#1278
Propósito: Cobrir as mudanças em xml.py, incluindo o caso limite descoberto durante a escrita do teste. Solução técnica: - TestExtractIssnEpub: ISSN presente/ausente. - TestDetermineTableLayout: comportamento sem layout_config (heurística legada preservada) e com layout_config. Documenta explicitamente uma limitação encontrada ao escrever o teste: determine_table_layout não tem largura real por coluna disponível nesse ponto do pipeline, então mesmo com layout_config a decisão ainda cai no fallback heurístico de contagem de coluna — column_count por si só não muda esse resultado ainda (ver subtarefa aberta na scieloorg#1278). Refs scieloorg#1278
…rrige inserção de imagem sem largura explícita Propósito: A decisão de largura de figura era uma heurística de DPI isolada, sem acesso à geometria real de coluna do periódico; e _compute_single_column_width assumia sempre 2 colunas, o que só quebra visivelmente num periódico de 1 coluna. Revisando o diff antes do commit, achado um terceiro problema: _try_insert_picture inseria a imagem sem largura explícita, deixando o python-docx usar sua própria leitura de DPI — independente da usada pelo resto do código (_infer_image_dpi) — e silenciosamente ignorando a largura calculada sempre que essa leitura própria desse um valor maior. Solução técnica: - decide_figure_layout/add_figure/_compute_single_column_width recebem layout_config opcional; comportamento anterior preservado quando omitido. - _compute_single_column_width, com layout_config, usa layout_config.column_width_pt em vez da fórmula fixa "sempre 2 colunas". - _natural_width_capped: tamanho natural da imagem (via DPI) limitado pelo teto disponível, sem nunca esticar além do tamanho nativo. - _try_insert_picture passa a chamar add_picture(img_path, width=content_width) explicitamente, em vez de deixar o python-docx inferir sozinho — confirmado por medição que uma imagem sem metadado de DPI diverge ~33% entre as duas leituras (72dpi vs. 96dpi de fallback). Refs scieloorg#1278
Propósito: Primeira cobertura de teste pra renderer/docx/ (inexistente até então, gap já catalogado na issue scieloorg#1275). Inclui teste de regressão pro bug achado em revisão no _try_insert_picture. Solução técnica: - TestComputeSingleColumnWidth: fórmula legada vs. layout_config em periódico de 1 e 2 colunas. - TestNaturalWidthCapped: fallback pro teto quando a imagem não existe. - TestTryInsertPictureUsesExplicitWidth: confirma que a largura inserida é exatamente a largura pedida, tanto menor quanto maior que a leitura própria do python-docx — este último caso é o que expõe o bug; testado contra uma cópia temporária do código sem a correção pra confirmar que falha (3810000 != 5000000) antes de aceitar o teste como válido. Refs scieloorg#1278
Propósito: Mesmo bug de _compute_single_column_width (figure.py), agora em tabela: a largura "cabe na coluna" assumia sempre 2 colunas, dividindo o conteúdo por 2 mesmo quando o periódico só tem 1. Solução técnica: - add_table/_compute_table_width recebem layout_config opcional; comportamento anterior preservado quando omitido. - Com layout_config, usa layout_config.column_width_pt em vez da fórmula fixa. Refs scieloorg#1278
Propósito: Cobrir _compute_table_width, incluindo o bug de largura fixa "sempre 2 colunas" corrigido no commit anterior. Solução técnica: - Layout de largura total ignora layout_config (já usa content_width inteiro, correto por definição). - Layout "cabe na coluna" testado sem layout_config (fórmula legada) e com layout_config de 1 e 2 colunas. Refs scieloorg#1278
…icamente Propósito: Amarra o trabalho dos commits anteriores: sem isso, LayoutConfig existia mas nada no pipeline principal o construía nem o propagava. Solução técnica: - pipeline_docx detecta o ISSN eletrônico do XML de entrada (xml.py::extract_issn_epub) e carrega o perfil calibrado (layout_config.py::load_profile), com fallback pro default (A4, 2 colunas) quando o periódico não estiver calibrado. Um data['layout_config'] explícito tem precedência sobre a detecção automática. - docx_body_pipe, _setup_two_column_body_section, _add_two_column_section, _render_tables, _render_figures, _figure_layout propagam layout_config pro resto da árvore de chamada. - docx_setup_sections passa a receber page_attributes derivado de layout_config.profile.to_page_attributes(), aplicando a altura de página calibrada em vez do A4 hardcoded. Confirmado localmente: 143 testes na suíte de packtools/sps/formats/pdf (0 regressão). Reconfirmado ponta a ponta via CLI real (packtools.sps.formats.pdf_generator, sem monkeypatch) nos 3 periódicos calibrados — mesmos resultados de página/coluna já validados antes desta migração pro repositório. Refs scieloorg#1278
…erride Propósito: O campo existia no schema desde a migração inicial mas nunca tinha sido lido em lugar nenhum do código — a docstring também não definia o que ele deveria escalar. Achado revisando o PR: a comparação visual da Acta Amazonica mostrou a Figura 1 bem mais larga que o alvo real, e o campo que deveria calibrar isso estava vazio E sem uso. Solução técnica: - Docstring de PageProfile atualizada: figure_width_scale_override escala o teto de largura (não o tamanho natural da imagem), então só afeta figura que já estaria sendo limitada pelo teto — não achata uma figura pequena que já cabe no espaço disponível. - Documenta o contra-exemplo já conhecido (Acta Botanica Brasilica, profiles/1677-941X.json): um fator único por periódico não serve pra esse caso, porque a Figura 1 de lá precisa ser MAIOR, não menor, que o tamanho nativo confiável — direção oposta. Refs scieloorg#1278
Propósito: Sem isso, o campo documentado no commit anterior continuava sem efeito nenhum no PDF gerado — o teto de largura calculado nunca era ajustado por ele, então nenhum periódico conseguia de fato ser calibrado. Solução técnica: - add_figure multiplica ceiling_width por layout_config.profile.figure_width_scale_override quando definido, antes de passar pra _natural_width_capped. None (default) mantém o comportamento atual, sem ajuste. Confirmado localmente: Com profiles/1809-4392.json calibrado (ver próximo commit), a Figura 1 da Acta Amazonica passou a sair em 348.7pt no PDF gerado — antes 481.9pt — batendo com a medição real do PDF publicado (348.7pt), conferido por correspondência de hash de imagem, não só posição de página. Refs scieloorg#1278
…Amazonica Propósito: Fechar o gap encontrado na revisão visual do PR: a Figura 1 gerada saía ~38% mais larga que o PDF oficial (481.9pt vs. 348.7pt medidos). Solução técnica: - figure_width_scale_override: 0.7236 = 348.7pt (Figura 1 medida no PDF real) / 481.9pt (teto de largura total computado pelo packtools). Calibrado a partir de um único dado (Figura 1, a única figura deste fixture) — pode não generalizar pra outras figuras deste periódico se mais forem calibradas no futuro. Refs scieloorg#1278
… Anuário Antropológico Propósito: Registrar um achado da revisão visual do PR antes que se perca: ao verificar por hash de imagem 7 das 11 figuras do artigo contra o PDF real, um padrão diferente do da Acta Amazonica apareceu. Solução técnica: - known_limitations documenta: Figuras 1/9/10/11 (tamanho natural já cabe no teto) batem quase exatamente com o alvo (~357-358pt nos dois lados); Figuras 3/4/5 saem ~30% mais largas no gerado (ex.: Figura 4, 357pt no alvo vs. ~468pt gerado), mesmo tendo tamanho natural provavelmente diferente entre si. Isso sugere que o periódico pode usar uma largura FIXA (~357pt) pra figura dentro do texto, independente do tamanho natural de cada imagem — um tipo de parâmetro diferente do figure_width_scale_override (que escala o teto, não fixa um valor). Deliberadamente não implementado: precisaria de um campo novo (ex.: figure_width_fixed_pt) e confirmação em mais de 7 figuras amostradas antes de virar regra confiável. Refs scieloorg#1278
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que esse PR faz?
Resolve o problema descrito na #1278: o gerador de PDF decide layout (1/2 colunas, largura de tabela/figura) através de duas heurísticas isoladas e desconectadas — DPI de imagem em
figure.py, contagem de coluna emxml.py— que não retornam largura real nem justificativa, sobre uma geometria de página fixa em A4/2-colunas (enum.py::PAGE_ATTRIBUTES), sem possibilidade de configuração por periódico.Introduz
LayoutConfig/PageProfile/LayoutDecision(packtools/sps/formats/pdf/layout_config.py) como abstração central: tabela e figura passam a consultar o mesmo contexto de layout, cada decisão vira um objeto com largura + justificativa + origem (medido/heurística), e um perfil de página por periódico pode ser calibrado viaprofiles/{issn_epub}.json— detectado automaticamente a partir do ISSN eletrônico no XML de entrada, sem precisar de flag manual, com fallback para o default atual (A4, 2 colunas) quando o periódico não estiver calibrado.Calibrar 3 periódicos reais por medição contra PDF publicado (Acta Amazonica, Acta Botanica Brasilica, Anuário Antropológico) revelou e corrigiu, no caminho, dois bugs reais e independentes deste trabalho:
_compute_single_column_width/_compute_table_widthassumiam sempre 2 colunas (/2fixo) — só quebra visivelmente num periódico calibrado pra 1 coluna._try_insert_pictureinseria imagem sem largura explícita, deixando o python-docx usar sua própria leitura de DPI, independente da usada pelo resto do código — confirmado por medição que uma imagem sem metadado de DPI diverge ~33% entre as duas leituras (72dpi vs. 96dpi de fallback).Onde a revisão poderia começar?
packtools/sps/formats/pdf/layout_config.py— o módulo novo (PageProfile,LayoutDecision,LayoutConfig,load_profile).packtools/sps/formats/pdf/pipeline/docx.py::pipeline_docx— onde o ISSN é detectado e o perfil carregado automaticamente.packtools/sps/formats/pdf/renderer/docx/figure.py::_try_insert_picture— a correção do bug de inserção de imagem sem largura explícita (achada em revisão, com teste de regressão verificado contra o código sem a correção).Como este poderia ser testado manualmente?
Com a fixture já presente no repositório:
O PDF gerado deve ter altura de página 793.7pt (não A4) e a Figura 1 em largura total na página 3 — comparável a
tests/fixtures/pdf/a1.pdf.Os outros 2 periódicos calibrados (Acta Botanica Brasilica, Anuário Antropológico) foram validados com artigos reais baixados do SciELO — XML/PDF não fazem parte das fixtures deste repositório, então não são reproduzíveis diretamente a partir daqui; ver "Screenshots" abaixo para a evidência visual.
A suíte automatizada cobre os 3 casos (1 coluna, 2 colunas, alternância temporária):
Algum cenário de contexto que queira dar?
Este PR nasceu de investigação empírica, não de design abstrato: parti da fixture real já existente no repositório (
a1.xml/a1.pdf, Acta Amazonica) e rodei o pipeline completo comparando com o PDF oficial publicado pelo periódico. Isso expôs o problema de raiz. Calibrando mais dois periódicos com características deliberadamente diferentes (Acta Botanica Brasilica: 2 colunas mas altura de página diferente da Acta Amazonica; Anuário Antropológico: o único de 1 coluna) os dois bugs adicionais listados acima só apareceram porque saí do caso "sempre 2 colunas" — ficaram invisíveis nos testes anteriores.Uma limitação foi encontrada e deliberadamente não resolvida neste PR: o dimensionamento de largura de figura dentro do teto disponível (quando não precisa de largura total) não tem regra geral confirmada — uma hipótese de DPI de metadado não confiável bateu para a Acta Amazonica e foi contrariada pela Acta Botanica Brasilica (onde o alvo real usa ~2.2x o tamanho nativo confiável da imagem). Documentado como limitação conhecida em
profiles/1677-941X.jsone na própria #1278, não escondido.Screenshots
Acta Amazonica — altura de página e quebra/retorno de coluna ao redor da Figura 1 batendo com o PDF oficial:
Acta Botanica Brasilica — Tabela 1 convergindo bem; Figura 1 mostrando a limitação conhecida (destacada, não escondida):
Anuário Antropológico — único periódico de 1 coluna calibrado; corpo de texto e Figura 2 (largura total) batendo com o PDF oficial:
Quais são os tickets relevantes?
Refs #1278 — não fecha a issue: ficam em aberto lá a seleção manual de 1/2 colunas via API/CLI, ligar largura real medida na decisão de tabela, e calibrar mais periódicos além dos 3 iniciais.
Referências
renderer/docx/— este PR adiciona a primeira cobertura parafigure.py/table.py, não fecha a issue por completo)LayoutConfignão duplica essa modelagem, mas vale coordenar antes de expandir)pdf_generatorCLI com caminho relativo, reproduzido ao vivo durante os testes deste PR, não corrigido aqui — fora de escopo)Segurança da informação (NSI.04)
Este PR manipula dados sensíveis ou pessoais (LGPD)?
Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?
Este PR introduz, atualiza ou remove dependências de terceiros?
python-docx,Pillow,lxml).Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?
tests/sps/formats/pdf/, 143 testes) e regeneração ponta a ponta via CLI real.Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?
profiles/*.json, empacotados com o próprio pacote).Este PR expõe novos endpoints, telas ou serviços?
Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?