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