Technischer Artikel

Skalieren von PDF-Seiten auf 70 % mit der losLab PDF-Bibliothek

Die Abmessungen von PDF-Seiten werden zum Zeitpunkt der Seitenerstellung festgelegt, sodass Sie den Inhalt nicht einfach an Ort und Stelle skalieren können, wie Sie es möglicherweise bei einem Bild tun würden. Das Bibliotheksmodell, das das Verkleinern praktikabel macht, ist das Erfassen und Neuzeichnen: Heben Sie den Inhalt jeder Seite aus dem Dokument in ein Handle, erstellen Sie eine neue leere Seite in der ursprünglichen Mediengröße und zeichnen Sie den erfassten Inhalt mit einem reduzierten Begrenzungsrahmen wieder ein. Der umgebende Leerraum wird zum Rand. Bei 70 % Skalierung auf einer A4-Seite fallen beispielsweise 15 % der Breite auf jede Seite und derselbe Bruchteil oben und unten, was genau das ist, was die Randarithmetik unten erzeugt

Wie CapturePage funktioniert

CapturePage nimmt eine Seitenzahl entgegen, stuft den Inhalt dieser Seite in ein In-Memory-Capture-Objekt hoch und entfernt die Seite aus dem Seitenbaum des Dokuments. Diese Entfernung ist beabsichtigt und der Grund dafür, dass die Schleife unabhängig vom Iterationsindex immer Seite 1 auswählt: Sobald Seite 1 erfasst und gelöscht wurde, wird die ehemalige Seite 2 zur neuen Seite 1 und so weiter. Wenn Sie den Seitenwähler zusammen mit dem Schleifenzähler erhöhen, überspringen Sie jede zweite Seite und erhalten nur die Hälfte der erwarteten Ausgabe

Das von CapturePage zurückgegebene Capture-Handle ist kein Seitenverweis; es gleicht eher einem Inhalts-Snapshot. Es bleibt gültig, bis Sie DrawCapturedPage aufrufen oder es explizit freigeben. DrawCapturedPage nimmt dieses Handle plus ein Zielrechteck an, das als linker Offset, unterer Offset, Breite und Höhe angegeben ist, alles in Punkten. Die Bibliothek skaliert den erfassten Inhalt so, dass er genau in dieses Rechteck passt, und behält das Seitenverhältnis nur bei, wenn Ihr Rechteck zufällig mit den ursprünglichen Proportionen übereinstimmt. Für eine einheitliche Skalierung sollte das Rechteck die ursprüngliche Größe multipliziert mit dem Skalierungsfaktor haben, zentriert auf der Seite

Die Zentrierungsmathematik

Bei einem Skalierungsfaktor von 70 % werden die verbleibenden 30 % jeder Abmessung gleichmäßig auf die beiden Seiten aufgeteilt. Der horizontale Versatz beträgt also pageWidth * (1.0 - 0.70) / 2, was 15 % der Breite entspricht, und der vertikale Versatz folgt derselben Formel unter Verwendung der Seitenhöhe. Das Zielrechteck für DrawCapturedPage beginnt dann bei (horizBorder, vertBorder) und erstreckt sich über pageWidth - 2 * horizBorder mal pageHeight - 2 * vertBorder. Diese Arithmetik ist nicht bibliotheksspezifisch; es ist einfach die Geometrie, ein kleineres Rechteck symmetrisch in ein größeres einzupassen

Eine Sache ist erwähnenswert: SetOrigin(1) platziert den Koordinatenursprung oben links anstatt unten links. Die Randwerte, die Sie an DrawCapturedPage übergeben, werden von dem von Ihnen festgelegten Ursprung aus gemessen. Wenn Sie also die Ursprungsmodi zwischen Laden und Zeichnen wechseln, ist die Zentrierung falsch

C#-Beispiel

Der folgende Code verarbeitet jede Seite von Pages.pdf durch den Erfassungs- und Neuzeichnungszyklus und schreibt das Ergebnis in newpages.pdf. PDFL ist das ActiveX/COM-Wrapper-Objekt, das dem Projekt aus PDFlibDLL64.dll hinzugefügt wurde

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");
}

Delphi-Beispiel

Die Delphi-Version verwendet TPDFlib direkt und nicht über die COM-Schicht, aber die Aufruffolge ist identisch. Ein praktischer Unterschied ist der Ausgabedatei-Schutz: FileExists plus DeleteFile anstelle von File.Delete, da SaveToFile fehlschlägt, wenn das Ziel durch einen vorherigen Durchlauf gesperrt ist, der noch in einem Viewer geöffnet ist

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;

Was der Skalierungsfaktor tatsächlich steuert

Der Wert 0,70 bedeutet hier, dass der gerenderte Inhalt 70 % jeder Seitenabmessung einnimmt, und nicht, dass die Datei 70 % ihrer ursprünglichen Bytegröße hat. Die Dateigröße nach diesem Vorgang hängt von der Komplexität des ursprünglichen Inhalts ab; eine Seite mit großen Bildern wird nicht proportional schrumpfen, da die Pixeldaten mit derselben Auflösung in einen kleineren Bereich neu gezeichnet werden. Wenn das Ziel eine Komprimierung auf Byte-Ebene ist, ist der richtige Ansatz LinearizeFile oder ein erneutes Speichern mit Stream-Komprimierung und nicht eine geometrische Skalierung

Die Zahl 70 % ist auch keine feste Grenze. Jeder Wert zwischen 0,0 und 1,0 funktioniert, und Werte über 1,0 vergrößern den Inhalt über die ursprüngliche Seitengrenze hinaus, was am Rand des Medienrahmens abgeschnitten wird, es sei denn, Sie erhöhen auch die Seitenabmessungen. Dokumente mit gemischten Größen werden natürlich verarbeitet, da PageWidth und PageHeight pro Seite vor der Randberechnung abgefragt werden. Ein Dokument, bei dem ungerade Seiten A4 und gerade Seiten A3 sind, erzeugt also eine korrekt zentrierte Ausgabe auf jeder Seitengröße, ohne dass besondere Fälle berücksichtigt werden müssen

Wo die Dinge schiefgehen können

In der Praxis treten zwei Fehlermodi auf. Der erste ist eine Ausgabedatei, die in einem PDF-Viewer aus einem vorherigen Durchlauf geöffnet gelassen wurde: SaveToFile schlägt fehl oder schreibt je nach Plattform null Byte, und die neue Ausgabe landet nie. Der Datei-Lösch-Schutz oben in der Funktion handhabt dies für die Entwicklung, aber in einer Produktionspipeline ist das Schreiben in einen temporären Pfad und das Umbenennen bei Erfolg sicherer

Der zweite ist eine Nichtübereinstimmung der Seitenanzahl. Da CapturePage Seiten aus dem Dokument entfernt, während sie verarbeitet werden, ist die Anzahl, die Sie aus PageCount() vor der Schleife lesen, die richtige Grenze, um dagegen zu iterieren. Der Aufruf von PageCount() innerhalb der Schleife würde bei jedem Durchlauf eine abnehmende Zahl zurückgeben und vorzeitig beendet werden, wobei die letzten Seiten unverarbeitet blieben. Die Schleifenvariable in den Beispielen dient nur als Zähler für die verbleibenden Iterationen; sie wird nie zur Auswahl einer Seite verwendet, da die auszuwählende Seite aus dem zuvor erläuterten Grund immer 1 ist

Die hier gezeigten Aufrufe zur Seitenmanipulation, einschließlich CapturePage, DrawCapturedPage und SetPageDimensions, sind Teil der losLab PDF-Bibliothek für Delphi, C#, VB.NET und C++