As dimensões da página PDF são fixadas no momento em que uma página é criada, portanto, você não pode simplesmente redimensionar o conteúdo no local como faria com uma imagem. O modelo da biblioteca que torna a redução prática é o de captura e redesenho: retirar o conteúdo de cada página do documento para um handle, criar uma nova página em branco no tamanho original da mídia, em seguida, desenhar o conteúdo capturado de volta em uma caixa delimitadora reduzida. O espaço em branco ao redor se torna a margem. Em uma escala de 70% em uma página A4, por exemplo, 15% da largura recai de cada lado e a mesma fração na parte superior e inferior, o que é exatamente o que a aritmética de borda abaixo produz
Como o CapturePage funciona
O CapturePage recebe um número de página, promove o conteúdo dessa página para um objeto de captura na memória e remove a página da árvore de páginas do documento. Essa remoção é intencional e é o motivo pelo qual o loop sempre seleciona a página 1 independentemente do índice de iteração: uma vez que a página 1 é capturada e excluída, o que era a página 2 torna-se a nova página 1 e assim por diante. Se você incrementar o seletor de página junto com o contador de loop, pulará páginas intercaladas e acabará com metade da saída esperada
O handle de captura retornado por CapturePage não é uma referência de página; é mais como um instantâneo do conteúdo. Ele permanece válido até que você chame DrawCapturedPage ou o libere explicitamente. DrawCapturedPage recebe esse handle mais um retângulo de destino dado como deslocamento esquerdo, deslocamento inferior, largura e altura, todos em pontos. A biblioteca redimensiona o conteúdo capturado para se ajustar exatamente a esse retângulo, preservando a proporção apenas se o seu retângulo coincidir com as proporções originais. Para redimensionamento uniforme, você quer que o retângulo seja o tamanho original multiplicado pelo fator de escala, centralizado na página
A matemática de centralização
Com um fator de escala de 70%, os 30% restantes de cada dimensão são divididos igualmente entre os dois lados. Portanto, o recuo horizontal é pageWidth * (1.0 - 0.70) / 2, o que equivale a 15% da largura, e o recuo vertical segue a mesma fórmula usando a altura da página. O retângulo de destino para DrawCapturedPage então começa em (horizBorder, vertBorder) e abrange pageWidth - 2 * horizBorder por pageHeight - 2 * vertBorder. Essa aritmética não é específica da biblioteca; é apenas a geometria de ajustar um retângulo menor simetricamente dentro de um maior
Um aspecto que vale a pena notar: SetOrigin(1) coloca a origem das coordenadas no canto superior esquerdo em vez do canto inferior esquerdo. Os valores de borda que você passa para DrawCapturedPage são medidos a partir de qualquer origem que você definir; portanto, se você alternar os modos de origem entre o carregamento e o desenho, a centralização ficará incorreta
Exemplo em C#
O código a seguir processa cada página de Pages.pdf pelo ciclo de captura e redesenho e grava o resultado em newpages.pdf. PDFL é o objeto wrapper ActiveX/COM adicionado ao projeto a partir de PDFlibDLL64.dll
private void ScalePages_Click(object sender, EventArgs e)
{
File.Delete("newpages.pdf");
double pageWidth, pageHeight, horizBorder, vertBorder;
double scaleFactor = 0.70;
int capturedPageId, ret;
PDFL.LoadFromFile("Pages.pdf", "");
PDFL.SetOrigin(1);
int numPages = PDFL.PageCount();
for (int i = 1; i <= numPages; i++)
{
// Always select page 1: CapturePage removes the page, so page 2
// becomes page 1 on the next iteration.
PDFL.SelectPage(1);
pageWidth = PDFL.PageWidth();
pageHeight = PDFL.PageHeight();
horizBorder = pageWidth * (1.0 - scaleFactor) / 2;
vertBorder = pageHeight * (1.0 - scaleFactor) / 2;
capturedPageId = PDFL.CapturePage(1);
PDFL.NewPage();
PDFL.SetPageDimensions(pageWidth, pageHeight);
ret = PDFL.DrawCapturedPage(
capturedPageId,
horizBorder, vertBorder,
pageWidth - 2 * horizBorder,
pageHeight - 2 * vertBorder);
}
PDFL.SaveToFile("newpages.pdf");
}
Exemplo em Delphi
A versão em Delphi usa TPDFlib diretamente em vez da camada COM, mas a sequência de chamadas é idêntica. Uma diferença prática é a proteção do arquivo de saída: FileExists mais DeleteFile em vez de File.Delete, porque SaveToFile falhará se o destino estiver bloqueado por uma execução anterior ainda aberta em um visualizador
procedure TForm1.ScalePagesClick(Sender: TObject);
var
PDFLib: TPDFlib;
pageWidth, pageHeight, horizBorder, vertBorder: Double;
scaleFactor: Double;
capturedPageId, ret, numPages, i: Integer;
begin
if FileExists('newpages.pdf') then
DeleteFile('newpages.pdf');
scaleFactor := 0.70;
PDFLib := TPDFlib.Create;
try
PDFLib.LoadFromFile('Pages.pdf', '');
PDFLib.SetOrigin(1);
numPages := PDFLib.PageCount();
for i := 1 to numPages do
begin
PDFLib.SelectPage(1);
pageWidth := PDFLib.PageWidth();
pageHeight := PDFLib.PageHeight();
horizBorder := pageWidth * (1.0 - scaleFactor) / 2;
vertBorder := pageHeight * (1.0 - scaleFactor) / 2;
capturedPageId := PDFLib.CapturePage(1);
PDFLib.NewPage();
PDFLib.SetPageDimensions(pageWidth, pageHeight);
ret := PDFLib.DrawCapturedPage(
capturedPageId,
horizBorder, vertBorder,
pageWidth - 2 * horizBorder,
pageHeight - 2 * vertBorder);
end;
PDFLib.SaveToFile('newpages.pdf');
finally
PDFLib.Free;
end;
end;
O que o fator de escala realmente controla
O valor 0,70 aqui significa que o conteúdo renderizado ocupa 70% de cada dimensão da página, não que o arquivo tem 70% de seu tamanho original em bytes. O tamanho do arquivo após esta operação depende da complexidade do conteúdo original; uma página com imagens grandes não encolherá proporcionalmente porque os dados de pixel são redesenhados com a mesma resolução em uma área menor. Se a compressão no nível de bytes for o objetivo, a abordagem correta é LinearizeFile ou salvar novamente com compressão de stream, não redimensionamento geométrico
O número de 70% também não é um limite estrito. Qualquer valor entre 0,0 e 1,0 funciona, e valores acima de 1,0 ampliam o conteúdo além do limite original da página, o que causará corte na borda da caixa de mídia (media box) a menos que você também aumente as dimensões da página. Documentos de tamanhos mistos são manipulados naturalmente porque PageWidth e PageHeight são consultados por página antes do cálculo da borda, de modo que um documento onde as páginas ímpares são A4 e as páginas pares são A3 produzirá uma saída corretamente centralizada em cada tamanho de página sem nenhum tratamento especial
Onde as coisas podem dar errado
Dois modos de falha surgem na prática. O primeiro é um arquivo de saída deixado aberto em um visualizador de PDF de uma execução anterior: SaveToFile falhará ou gravará zero bytes dependendo da plataforma, e a nova saída nunca é salva. A proteção de exclusão de arquivo no topo da função lida com isso para desenvolvimento, mas em um pipeline de produção gravar em um caminho temporário e renomear em caso de sucesso é mais seguro
O segundo é a incompatibilidade de contagem de páginas. Como CapturePage remove páginas do documento à medida que as processa, a contagem que você lê de `PageCount()` antes do loop é o limite correto para a iteração. Chamar PageCount() dentro do loop retornaria um número decrescente a cada passagem e sairia prematuramente, deixando as últimas páginas não processadas. A variável de loop nos exemplos serve apenas como um contador de iterações restantes; ela nunca é usada para selecionar uma página, porque a página a ser selecionada é sempre a 1, pelo motivo explicado anteriormente
As chamadas de manipulação de página mostradas aqui, incluindo CapturePage, DrawCapturedPage e SetPageDimensions, fazem parte da Biblioteca PDF losLab para Delphi, C#, VB.NET e C++