Las dimensiones de la página PDF se fijan en el momento en que se crea una página, por lo que no se puede simplemente reescalar el contenido en el lugar de la forma en que se redimensionaría una imagen. El modelo de la biblioteca que hace práctica la reducción es capturar y redibujar: extraer el contenido de cada página del documento a un identificador, crear una nueva página en blanco con el tamaño del medio original, y luego volver a dibujar el contenido capturado en un cuadro delimitador reducido. El espacio en blanco circundante se convierte en el margen. A un 70% de escala en una página A4, por ejemplo, el 15% del ancho queda a cada lado y la misma fracción arriba y abajo, que es exactamente lo que produce la aritmética de bordes a continuación
Cómo funciona CapturePage
CapturePage toma un número de página, promueve el contenido de esa página a un objeto de captura en memoria y elimina la página del árbol de páginas del documento. Esa eliminación es intencional y es la razón por la que el bucle siempre selecciona la página 1 independientemente del índice de iteración: una vez que se captura y elimina la página 1, lo que era la página 2 se convierte en la nueva página 1, y así sucesivamente. Si incrementa el selector de páginas junto con el contador del bucle, omitirá una de cada dos páginas y terminará con la mitad de la salida esperada
El identificador de captura devuelto por CapturePage no es una referencia de página; es más bien una instantánea de contenido. Se mantiene válido hasta que llame a DrawCapturedPage o lo libere explícitamente. DrawCapturedPage toma ese identificador más un rectángulo de destino dado como desplazamiento izquierdo, desplazamiento inferior, ancho y alto, todo en puntos. La biblioteca escala el contenido capturado para que encaje exactamente en ese rectángulo, preservando la relación de aspecto solo si su rectángulo resulta coincidir con las proporciones originales. Para un escalado uniforme, querrá que el rectángulo sea el tamaño original multiplicado por el factor de escala, centrado en la página
Las matemáticas del centrado
Con un factor de escala del 70%, el 30% restante de cada dimensión se divide en partes iguales entre los dos lados. Por lo que el margen insertado horizontal es pageWidth * (1.0 - 0.70) / 2, que es el 15% del ancho, y el margen vertical sigue la misma fórmula usando el alto de la página. El rectángulo de destino para DrawCapturedPage entonces comienza en (horizBorder, vertBorder) y abarca pageWidth - 2 * horizBorder por pageHeight - 2 * vertBorder. Esa aritmética no es específica de la biblioteca; es solo la geometría de encajar un rectángulo más pequeño simétricamente dentro de uno más grande
Una cosa que vale la pena notar: SetOrigin(1) coloca el origen de coordenadas en la parte superior izquierda en lugar de la parte inferior izquierda. Los valores de los bordes que pase a DrawCapturedPage se miden desde el origen que haya establecido, por lo que si cambia de modo de origen entre la carga y el dibujo, el centrado será incorrecto
Ejemplo en C#
El siguiente código procesa cada página de Pages.pdf a través del ciclo de captura y redibujado y escribe el resultado en newpages.pdf. PDFL es el objeto contenedor ActiveX/COM agregado al proyecto desde 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");
}
Ejemplo en Delphi
La versión de Delphi usa TPDFlib directamente en lugar de a través de la capa COM, pero la secuencia de llamadas es idéntica. Una diferencia práctica es la protección del archivo de salida: FileExists más DeleteFile en lugar de File.Delete, porque SaveToFile fallará si el destino está bloqueado por una ejecución anterior que aún está abierta en un visor
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;
Qué controla realmente el factor de escala
El valor 0.70 aquí significa que el contenido renderizado ocupa el 70% de cada dimensión de la página, no que el archivo tenga el 70% de su tamaño original en bytes. El tamaño del archivo después de esta operación depende de la complejidad del contenido original; una página con imágenes grandes no se encogerá proporcionalmente porque los datos de píxeles se vuelven a dibujar a la misma resolución en un área más pequeña. Si el objetivo es la compresión a nivel de bytes, el enfoque correcto es LinearizeFile o volver a guardar con compresión de flujo, no el escalado geométrico
La cifra del 70% tampoco es un límite estricto. Cualquier valor entre 0.0 y 1.0 funciona, y los valores por encima de 1.0 agrandan el contenido más allá del límite original de la página, lo cual se recorta en el borde del cuadro del medio (media box) a menos que también aumente las dimensiones de la página. Los documentos de tamaños mixtos se manejan de forma natural porque PageWidth y PageHeight se consultan por página antes del cálculo de los bordes, por lo que un documento donde las páginas impares son A4 y las pares son A3 producirá una salida correctamente centrada en cada tamaño de página sin ningún caso especial
Dónde pueden fallar las cosas
Dos modos de fallo surgen en la práctica. El primero es un archivo de salida que queda abierto en un visor de PDF de una ejecución anterior: SaveToFile fallará o escribirá cero bytes dependiendo de la plataforma, y la nueva salida nunca aterriza. La protección de eliminación de archivos en la parte superior de la función maneja eso para el desarrollo, pero en una tubería de producción, escribir en una ruta temporal y cambiar el nombre en caso de éxito es más seguro
El segundo es el desajuste en el conteo de páginas. Debido a que CapturePage elimina las páginas del documento a medida que las procesa, el conteo que lee de PageCount() antes del bucle es el límite correcto contra el cual iterar. Llamar a PageCount() dentro del bucle devolvería un número decreciente en cada pasada y saldría temprano, dejando las últimas páginas sin procesar. La variable del bucle en los ejemplos solo sirve como un contador de iteraciones restantes; nunca se usa para seleccionar una página, porque la página a seleccionar siempre es la 1 por la razón explicada anteriormente
Las llamadas de manipulación de páginas mostradas aquí, incluyendo CapturePage, DrawCapturedPage y SetPageDimensions, son parte de la losLab PDF Library para Delphi, C#, VB.NET y C++