Artigo Técnico

Árvores de Páginas PDF: a Ordem das Páginas Não é a Ordem dos Objetos

O artigo complementar sobre ordenação de páginas PDF cobre a regra base: a ordem de apresentação resulta de uma travessia em profundidade, da esquerda para a direita, das matrizes /Kids na árvore /Pages, nunca dos números dos objetos. Aqui analisamos a forma da árvore: por que razão os escritores PDF usam hierarquias de nós intermédios quando uma matriz plana seria legal, o que muda quando uma ferramenta achata ou reconstrói a árvore e o que acontece quando o controlo /Count deixa de ser fiável

O fan-out é uma decisão de desempenho

Nada obriga um escritor a criar níveis aninhados. Um documento com 10.000 páginas pode ter um único nó /Pages raiz e 10.000 referências folha numa matriz /Kids, em conformidade com a especificação. Ainda assim, a PDF Reference recomenda uma árvore equilibrada para documentos grandes, e os geradores comuns seguem essa orientação com algumas dezenas de filhos por nó intermédio

A razão está no que um visualizador tem de ler antes de mostrar qualquer coisa. Para saltar directamente para a página 8.214, uma árvore plana exige analisar uma matriz enorme até à entrada pretendida. Com um fan-out de 32, o visualizador lê a raiz, compara os totais /Count acumulados, escolhe o filho correcto e desce por três ou quatro dicionários pequenos. É o acesso aleatório O(log n) para o qual a árvore foi concebida, e é precisamente por isso que os nós intermédios têm /Count: permitem ignorar uma subárvore inteira sem abrir os seus objectos

A forma da árvore também determina o custo da edição. Uma actualização incremental que insere uma página reescreve cada nó cujo /Kids ou /Count mudou, isto é, o caminho entre o novo pai e a raiz. Numa árvore equilibrada, esse caminho contém poucos dicionários pequenos; numa árvore plana, a matriz raiz inteira é duplicada em cada revisão

Os nós intermédios transportam atributos herdados

Os nós intermédios não servem apenas para encaminhamento. Os quatro atributos de página herdáveis, /Resources, /MediaBox, /CropBox e /Rotate, podem ser colocados num nó /Pages e aplicar-se a todas as folhas abaixo dele, salvo substituição por um descendente. Um relatório com um apêndice em orientação horizontal pode expressar esse esquema na própria árvore:

5 0 obj   % document root
<< /Type /Pages /Count 6 /Kids [6 0 R  7 0 R] >>
endobj

6 0 obj   % report body: portrait A4, body font
<< /Type /Pages /Parent 5 0 R /Count 3
   /Kids [30 0 R  31 0 R  32 0 R]
   /MediaBox [0 0 595 842]
   /Resources << /Font << /F1 8 0 R >> >> >>
endobj

7 0 obj   % appendix: landscape A4, rotated, its own font
<< /Type /Pages /Parent 5 0 R /Count 3
   /Kids [40 0 R  41 0 R  42 0 R]
   /MediaBox [0 0 842 595] /Rotate 90
   /Resources << /Font << /F2 9 0 R >> >> >>
endobj

40 0 obj  % appendix page: inherits size, rotation, fonts
<< /Type /Page /Parent 7 0 R /Contents 43 0 R >>
endobj

As páginas 40 a 42 ficam quase vazias. O tamanho, a rotação e os recursos de tipos de letra chegam por herança do nó 7, mantendo o ficheiro compacto; uma quarta página sob o nó do apêndice assumiria automaticamente a mesma orientação

O mesmo mecanismo cria o conhecido risco ao mover páginas. Se uma ferramenta mover o objecto 40 para o corpo do relatório, ele passa a herdar /MediaBox vertical, ausência de rotação e /F1. O fluxo de conteúdo continua a seleccionar /F2, que já não resolve. O código robusto materializa na página os quatro valores herdáveis resolvidos antes de a reparentar

Achatar é legal, comum e por vezes dispendioso

Muitas ferramentas fazem o contrário. Escritores minimalistas emitem uma árvore de nível único, e utilitários de fusão e divisão reconstroem frequentemente tudo numa matriz plana /Kids. Uma reconstrução correcta tem de resolver a herança ao mesmo tempo: cada atributo herdado por uma folha deve ser copiado para a folha ou promovido para a nova raiz se for uniforme no documento

Em documentos comuns, achatar é inofensivo. Em grande escala, torna a matriz raiz num objecto grande que cada abertura e salto de página tem de analisar, e cada edição estrutural reescreve-a por inteiro. O que não desaparece é a partilha por referências indirectas: milhares de páginas podem continuar a apontar para o mesmo dicionário /Resources

Quando /Count mente

/Count é apenas contabilidade: deve ser igual ao número de páginas folha na subárvore, mas o formato não impõe essa condição. Dois padrões de corrupção explicam a maioria das contagens incorrectas encontradas na prática

O primeiro é a contagem antiga deixada por uma actualização incremental. O editor actualiza o pai imediato, anexa a nova versão e não toca nos ancestrais:

% Original revision
12 0 obj
<< /Type /Pages /Count 9 /Kids [13 0 R  14 0 R  15 0 R] >>
endobj

14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 3
   /Kids [50 0 R  51 0 R  52 0 R] >>
endobj

% Appended revision: one page inserted into the middle branch.
% Object 14 is superseded; object 12 is never rewritten
14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 4
   /Kids [50 0 R  51 0 R  90 0 R  52 0 R] >>
endobj

A árvore passa a conter dez folhas, mas a raiz continua a indicar nove. Um visualizador que confie na raiz mostra nove páginas; outro que use as contagens intermédias para procurar uma página calcula índices errados depois da inserção; uma travessia completa encontra dez

O segundo padrão é uma contagem que nunca poderia estar correcta: negativa, zero num nó preenchido ou absurdamente grande. Estes valores podem resultar de fuzzing, danos de transmissão ou erros aritméticos. São perigosos para código que dimensiona matrizes a partir de /Count, porque o valor deve ser tratado como entrada não fidedigna

Ferramentas de preflight e validadores PDF/A comparam /Count com o resultado da travessia e rejeitam ou sinalizam o ficheiro. Visualizadores interactivos tendem a calcular a contagem real e ignorar silenciosamente o valor armazenado. Para código de biblioteca, a abordagem defensiva é tratar /Count como uma sugestão útil para pré-alocação, mas manter a travessia como fonte de verdade

Para o algoritmo de travessia, as regras de herança e o percurso entre catálogo e folhas, consulte o artigo sobre ordenação de páginas. Para estes modos de falha num documento real, consulte o estudo de caso sobre depuração da ordem das páginas

O HotPDF Component trata internamente destes casos: percorre árvores aninhadas de qualquer profundidade, resolve atributos herdados ao copiar ou mover páginas e verifica /Count contra a contagem real de folhas, pelo que os índices da API correspondem sempre a páginas lógicas