A junção de páginas (page stitching) compõe o conteúdo de páginas existentes em uma tela (canvas) compartilhada, o que é uma operação diferente de mesclar arquivos. A losLab PDF Library implementa a junção de páginas com Form XObject e modelos de marca-d'água combináveis para Delphi: StitchPageOverlay, StitchPagesSideBySide e StitchPagesVertically posicionam páginas capturadas em qualquer escala com total fidelidade vetorial, e o TPDFlibCompositeWatermark empilha marcas-d'água de texto e imagem em um intervalo de páginas com uma única chamada. Se o que você realmente precisa é anexar páginas inteiras de vários documentos em um único arquivo, essa é a família de mesclagem (merge), abordada no artigo sobre mesclagem rápida de PDF em nível de byte; este artigo trata da combinação do conteúdo de páginas dentro de um mesmo espaço de coordenadas
As solicitações que levam a isso são comuns e persistentes. Uma escola quer duas páginas de prova impressas em uma única folha em formato paisagem para reduzir o uso de papel pela metade. Um revisor de localização quer a página em inglês e sua tradução lado a lado em uma única página, rolando juntas. Uma equipe jurídica quer que cada rascunho seja carimbado com três camadas ao mesmo tempo: um logotipo no canto, um CONFIDENCIAL diagonal no meio e uma pequena linha de rodapé, apenas nas páginas 1 a 3. Todos os três cenários representam o mesmo problema subjacente: posicionar conteúdo de página já diagramado em outro lugar, com outro tamanho, sem degradá-lo para um bitmap
Por que a concatenação de fluxos de conteúdo PDF falha?
A junção ingênua, que copia o fluxo /Contents da página de origem para a página de destino, falha por três razões estruturais. Primeiro, as coordenadas colidem: ambos os fluxos de conteúdo pressupõem sua própria origem no canto inferior esquerdo de seu próprio MediaBox, de modo que o conteúdo colado cai sobre o conteúdo existente no lugar errado. Segundo, os nomes de recursos colidem: cada página resolve operadores como /F1 Tf e /Im0 Do em relação ao seu próprio dicionário /Resources, e duas páginas rotineiramente vinculam o mesmo nome a fontes ou imagens diferentes, fazendo com que uma delas seja renderizada silenciosamente com o recurso incorreto. Terceiro, uma cópia simples de fluxo não oferece nenhum dimensionamento; o máximo que você pode fazer é transladar
Os Form XObjects resolvem os três problemas de uma só vez, e é por isso que a losLab PDF Library baseia sua junção neles. A norma ISO 32000-1 §8.10 define um Form XObject como um fluxo de conteúdo autocontido com seu próprio dicionário /Resources e seu próprio espaço de coordenadas, inserido em uma página ao invocar seu nome sob qualquer matriz de transformação ativa. O método CapturePage envolve uma página existente inteira, conteúdo e recursos, em um XObject /Subtype /Form, e as funções de junção então o posicionam com uma matriz cm: dimensionando, transladando e rotacionando como desejado. Como a origem permanece vetorial, o texto continua texto e os caminhos continuam caminhos em qualquer nível de zoom, a mesma propriedade que torna a computação gráfica vetorial e os padrões de sombreamento independentes de resolução. Nada é rasterizado, e o dicionário de recursos isolado significa que a página capturada não conflitará com a página de destino pelo nome /F1
Como combinar duas páginas PDF em uma única página?
O método StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) é a resposta direta: ele captura ambas as páginas de origem, cria uma nova página mais larga e posiciona as duas capturas à esquerda e à direita com um espaçamento (gutter) entre elas, cada uma dimensionada para caber em TargetHeight. A função retorna o número da nova página (baseado em 1) ou 0 em caso de falha, e ambas as páginas de origem são capturadas e ocultadas como parte da operação. O caso das provas com duas páginas por folha é resolvido em 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 em uma nova página mais larga com espaçamento de 18pt,
// ambas dimensionadas para uma altura de saída de 595pt (A4 paisagem)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Para composição vertical, o método StitchPagesVertically(PageRanges, Gap, TargetWidth) empilha todo um intervalo de páginas em uma única página alta, estilo pôster. PageRanges aceita a sintaxe padrão de intervalo, como '1-3,5'. Cada página de origem é dimensionada para que sua largura corresponda a TargetWidth, de modo que a contribuição de altura de cada página seja sua própria altura multiplicada pelo seu fator de escala, e a altura da página de saída seja a soma dessas alturas mais um Gap entre cada par. Esse é o formato ideal para documentos de revisão de rolagem contínua ou recibos longos montados a partir de origens de tamanho carta
Como sobrepor uma página em outra?
O método StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) desenha uma página existente sobre outra página existente, em vez de criar uma nova. A página de origem é capturada como um Form XObject e desenhada sobre o destino no retângulo fornecido, com Opacity de 0 a 1 (1 é opaco). A largura e a altura são parâmetros livres, de modo que o posicionamento se estica se a sua proporção diferir da proporção da própria página de origem; passe um par proporcional quando desejar uma miniatura sem distorções da origem. Um uso típico é um documento cujo bloco de aprovação é mantido como sua própria página e carimbado na capa
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// A página 5 contém o bloco de aprovação; posicione-o na página 1
// em um quadro de 260 x 180 com 45% de opacidade. A página 5 é consumida:
// ela é capturada e ocultada, de modo que as páginas 6..n tornam-se 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 com os números de página após uma captura?
O método CapturePage consome a página de origem: seu conteúdo é movido para o XObject e a página agora vazia é ocultada, de modo que cada página com número superior ao da origem é imediatamente deslocada uma posição para baixo. Essa é a semântica mais importante em toda a API de junção. A losLab PDF Library gerencia a aritmética dentro de cada chamada de junção, o StitchPageOverlay ajusta o índice de destino quando o destino está acima da origem e o StitchPagesSideBySide captura a página com número maior primeiro para que o índice da segunda captura ainda seja válido, mas o seu próprio código ao redor deve contabilizar o deslocamento entre as chamadas. Duas regras mantêm o código de junção múltipla correto: registre quaisquer tamanhos de página ou números de página que você precisará antes da primeira captura, porque eles mudam depois; e, quando você capturar várias páginas em um loop, itere em ordem decrescente de página, para que a ocultação de uma página posterior nunca altere o índice de uma página anterior que você ainda não capturou
Como aplicar uma marca-d'água de várias camadas a um intervalo de páginas?
A camada de modelo de marca-d'água em PDFlibWatermark.pas transforma o conceito de "definir uma vez, aplicar a muitas páginas" em objetos. A classe TPDFlibWatermarkBase carrega o que toda marca-d'água compartilha: uma Position de nove pontos (de wpTopLeft a wpBottomRight, mais wpCustom que usa coordenadas X/Y brutas), um Angle em graus, uma Opacity de 0 a 1 e uma string PageRange onde vazio significa todas as páginas. O TPDFlibTextWatermark adiciona texto, um ID de fonte (padrão Helvetica), tamanho e uma cor RGB; o TPDFlibImageWatermark adiciona um ID de imagem de uma imagem adicionada anteriormente, além de uma largura e altura de posicionamento. A classe TPDFlibCompositeWatermark mantém uma lista de sub-marcas-d'água e as aplica recursivamente, e o método TPDFlib.ApplyWatermark é o ponto de entrada de chamada única que honra o próprio intervalo de páginas do modelo e retorna o número de páginas carimbadas. O selo jurídico de três camadas se parece com isto
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'; // os filhos herdam este intervalo
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // o composto libera seus filhos
end;
finally
Lib.Free;
end;
end;
Limites que vale a pena conhecer
Dois limites honestos se aplicam, e ambos decorrem de como o mecanismo funciona. Primeiro, uma captura envolve apenas o fluxo de conteúdo e os recursos da página. Anotações, links, campos de formulário e comentários vivem como objetos separados no dicionário da página, não no fluxo de conteúdo, de modo que não se transferem para o XObject; um layout com junção carrega o conteúdo visível da página, mas não a camada interativa de suas origens. Segundo, o posicionamento do texto em nove pontos é uma aproximação: sem um mecanismo de layout completo, a biblioteca não pode saber a largura renderizada exata de uma string, portanto estima a caixa de texto em 40% da largura da página ao resolver posições como wpCenter. Isso é preciso o suficiente para centralizar um rótulo curto, mas para o posicionamento exato de strings longas em pixels, alterne para wpCustom e calcule você mesmo as coordenadas
A escala não é uma restrição prática aqui, uma vez que a captura e a junção manipulam referências de objetos em vez de codificar novamente o conteúdo da página; se suas origens tiverem centenas de megabytes, a mesma disciplina descrita no guia para mesclagem e divisão de arquivos grandes com acesso direto se aplica antes de você realizar a junção. As funções de junção e as classes de modelo de marca-d'água mostradas aqui são fornecidas na atual losLab PDF Library para Delphi, C# e VB.NET, juntamente com as primitivas de nível inferior CapturePage, DrawCapturedPage, e DrawCapturedPageMatrix para quando você precisar de um layout que as três chamadas de junção pré-construídas não cobrem