Artigo Técnico

Imposição de Páginas (Stitching) e Modelos de Marca de Água em PDFs em Delphi

A imposição de páginas (stitching) combina o conteúdo de páginas existentes numa única tela comum, distinguindo-se da simples fusão de ficheiros. A losLab PDF Library disponibiliza lógicas de stitching baseadas em Form XObject e modelos de marca de água compostos para Delphi: os métodos StitchPageOverlay, StitchPagesSideBySide e StitchPagesVertically posicionam as páginas capturadas em qualquer escala mantendo a fidelidade vetorial, e TPDFlibCompositeWatermark aplica marcas de água de texto e imagem num intervalo de páginas numa única chamada. Se o seu requisito consiste em anexar páginas completas de vários documentos num único ficheiro, consulte o artigo sobre fusão rápida de PDFs ao nível dos bytes; este artigo foca a combinação de conteúdos de páginas dentro do mesmo espaço de coordenadas

Os cenários que motivam esta necessidade são habituais: uma instituição de ensino que pretende dispor duas páginas de exame numa única folha horizontal (landscape) para reduzir o consumo de papel; um revisor de tradução que deseja visualizar a página em inglês e a respetiva versão traduzida lado a lado num único ecrã; ou uma equipa jurídica que necessita de marcar rascunhos com três camadas (um logótipo no canto, a palavra CONFIDENCIAL na diagonal ao centro e um rodapé, apenas nas páginas 1 a 3). O desafio subjacente a todos é idêntico: dispor o conteúdo de páginas já estruturadas noutra coordenada e escala sem converter os dados para bitmaps

Por que razão a concatenação direta de fluxos PDF falha?

O stitching básico, que se limita a copiar o fluxo /Contents da página de origem para a de destino, falha por três razões estruturais: em primeiro lugar, ocorrem colisões de coordenadas, dado que ambos os fluxos assumem a origem no canto inferior esquerdo da respetiva MediaBox (sobrepondo-se os conteúdos incorretamente); em segundo lugar, surgem colisões nos recursos, resolvendo cada página operadores como /F1 Tf e /Im0 Do no seu dicionário /Resources, pelo que mapeamentos do mesmo identificador para fontes ou imagens diferentes aplicam recursos incorretos; em terceiro lugar, a cópia direta de fluxos não suporta qualquer escala, permitindo apenas translações

A tecnologia Form XObjects resolve estes três problemas. A norma ISO 32000-1 §8.10 define um Form XObject como um fluxo de conteúdo autónomo com dicionário /Resources e espaço de coordenadas próprios, inserido na página invocando o respetivo identificador sob a matriz de transformação (CTM) ativa. O método CapturePage encapsula uma página existente (dados e recursos) num objeto /Subtype /Form XObject, permitindo às rotinas de stitching escalá-la, transladá-la ou rodá-la à discrição com a matriz cm. Como a origem de dados é preservada em vetor, os textos e caminhos mantêm a resolução gráfica em qualquer nível de zoom (característica comum às graduações abordadas no artigo sobre gráficos vetoriais e shaders). Nada é convertido em imagem, e o dicionário de recursos isolado evita conflitos na definição de nomes de recursos como /F1

Como combinar duas páginas PDF numa única página?

O método StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) resolve o problema: captura ambas as páginas, cria uma nova página mais larga e posiciona as capturas à esquerda e à direita com um espaçamento (gutter) entre elas, adaptando ambas à escala TargetHeight. A chamada devolve o índice da nova página (baseado em 1) ou 0 em caso de erro, sendo as páginas de origem ocultadas. O caso de dispor duas páginas numa só folha de exame resolve-se com três linhas de código:

var
  Lib: TPDFlib;
  NewPage: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('exam.pdf', '');
    // Páginas 1 e 2 dispostas numa nova página com espaçamento de 18pt
    // ambas ajustadas para a altura de 595pt (A4 horizontal/landscape)
    NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
    if NewPage > 0 then
      Lib.SaveToFile('exam-2up.pdf');
  finally
    Lib.Free;
  end;
end;

Na disposição vertical, StitchPagesVertically(PageRanges, Gap, TargetWidth) empilha um intervalo de páginas num único documento longo (estilo poster). O argumento PageRanges aceita a sintaxe padrão de intervalos (como '1-3,5'). Cada página é redimensionada para ajustar a largura a TargetWidth, sendo a altura de saída determinada pela soma das alturas parciais multiplicadas pelo respetivo fator de escala, acrescida do espaçamento Gap entre cada par. Trata-se da opção indicada para visualizações contínuas de documentos ou recibos extensos

Como sobrepor uma página a outra?

O método StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) desenha uma página existente sobre outra, sem criar novas páginas. A página de origem é capturada como Form XObject e posicionada na coordenada indicada com a opacidade Opacity definida entre 0 e 1. Como a largura e altura são livres, o desenho sofre distorção se a proporção divergir do aspeto original (deve passar valores proporcionais para evitar distorções). O cenário comum envolve a sobreposição de um carimbo ou bloco de aprovação guardado numa página isolada sobre a capa do documento:

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('report.pdf', '');
    // A página 5 contém o bloco de aprovação; sobrepor à página 1
    // numa caixa de 260 x 180 com 45% de opacidade. A página 5 é consumida:
    // sendo ocultada, as páginas 6..n passam a ser identificadas por 5..n-1
    if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
      Lib.SaveToFile('report-approved.pdf');
  finally
    Lib.Free;
  end;
end;

O que acontece aos índices das páginas após a captura?

O método CapturePage consome a página de origem: o seu conteúdo é movido para o XObject e a página (agora vazia) é ocultada, fazendo com que as páginas superiores recuem uma posição. Esta lógica é fundamental no processamento de stitching. A losLab PDF Library trata esta aritmética internamente: StitchPageOverlay corrige o índice de destino se este for superior ao de origem, e StitchPagesSideBySide captura primeiro a página com índice maior para preservar a validade da segunda chamada. Contudo, o código da sua aplicação deve salvaguardar estas variações. Siga estas duas regras: registe as dimensões e índices de páginas necessários antes de iniciar as capturas e, ao capturar múltiplos elementos num ciclo, processe-os por ordem decrescente de modo a que a remoção de páginas superiores não altere as coordenadas de páginas anteriores

Como aplicar marcas de água multicamada num intervalo de páginas?

A gestão de marcas de água em PDFlibWatermark.pas baseia-se em modelos reutilizáveis. A classe TPDFlibWatermarkBase define as propriedades genéricas: alinhamento Position baseado em nove quadrantes (de wpTopLeft a wpBottomRight, ou a opção livre wpCustom com coordenadas X/Y), rotação Angle, opacidade Opacity e o intervalo PageRange (vazio assume todas as páginas). A classe TPDFlibTextWatermark parametriza o texto, tipo de letra (Helvetica por padrão), tamanho e cor RGB; por sua vez, TPDFlibImageWatermark associa o identificador de uma imagem anteriormente registada e as dimensões pretendidas. Para gerir conjuntos, o contentor TPDFlibCompositeWatermark agrupa marcas de água aplicando-as recursivamente via método TPDFlib.ApplyWatermark, que valida os intervalos e devolve a contagem de páginas processadas. A aplicação de três camadas de marcação executa-se da seguinte forma:

var
  Lib: TPDFlib;
  Logo: TPDFlibImageWatermark;
  Diagonal, Footer: TPDFlibTextWatermark;
  Stack: TPDFlibCompositeWatermark;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('draft.pdf', '');
    Stack := TPDFlibCompositeWatermark.Create;
    try
      Logo := TPDFlibImageWatermark.Create;
      Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
      Logo.Position := wpTopRight;
      Logo.Width := 90;
      Logo.Height := 32;
      Stack.Add(Logo);

      Diagonal := TPDFlibTextWatermark.Create;
      Diagonal.Text := 'CONFIDENTIAL';
      Diagonal.Position := wpCenter;
      Diagonal.Angle := 45;
      Diagonal.FontSize := 60;
      Diagonal.Opacity := 0.15;
      Stack.Add(Diagonal);

      Footer := TPDFlibTextWatermark.Create;
      Footer.Text := 'Internal review copy';
      Footer.Position := wpBottomCenter;
      Footer.FontSize := 9;
      Footer.Opacity := 0.6;
      Stack.Add(Footer);

      Stack.PageRange := '1-3';   // sub-elementos herdam este intervalo
      Lib.ApplyWatermark(Stack);
      Lib.SaveToFile('draft-marked.pdf');
    finally
      Stack.Free;                 // o composite liberta os seus filhos automaticamente
    end;
  finally
    Lib.Free;
  end;
end;

Particularidades de composição: a herança de intervalos é unidirecional e não destrutiva, herdando elementos sem definição o intervalo do composite durante a chamada Apply, enquanto elementos com intervalo próprio preservam o seu valor (o que permite desenhar o rodapé em todas as páginas e o carimbo CONFIDENCIAL apenas nas páginas selecionadas). A libertação de memória é automática: o destruidor de TPDFlibCompositeWatermark elimina os elementos filhos, exigindo apenas libertar o composite principal

Limitações técnicas

Existem dois limites técnicos decorrentes desta implementação: em primeiro lugar, a captura foca exclusivamente os recursos e o fluxo de conteúdo da página. Anotações, hiperligações, campos de formulários e comentários residem em dicionários próprios do PDF (e não no fluxo de conteúdo), pelo que não são exportados para o XObject (o resultado final replica o aspeto visual da página original mas descarta a interatividade); em segundo lugar, o posicionamento do texto com base nos nove pontos é aproximado. Na ausência de um motor de renderização completo para calcular a largura exata de cada string, a biblioteca assume uma largura de 40% da página ao posicionar elementos como wpCenter. Esta aproximação é suficiente para rótulos curtos; contudo, para posicionamentos milimétricos de textos longos deve recorrer a wpCustom e calcular as coordenadas manualmente

O tamanho de dados não constitui impedimento, visto que as rotinas de captura e imposição manipulam referências aos objetos sem reescrever os dados das páginas. Se processar ficheiros com centenas de megabytes, deve aplicar as boas práticas detalhadas no guia de fusão e separação de ficheiros grandes antes de proceder ao stitching. As funções de stitching e gestão de marcas de água integram o catálogo padrão do losLab PDF Library para Delphi, C# e VB.NET, disponibilizando-se também os métodos CapturePage, DrawCapturedPage e DrawCapturedPageMatrix para lógicas de desenho personalizadas