Rozměry stránky PDF jsou fixně stanoveny v okamžiku jejího vytvoření, takže nemůžete jednoduše přeškálovat obsah na místě tak, jak byste změnili velikost obrázku. Model knihovny, díky kterému je zmenšování praktické, je zachycení-a-překreslení (capture-and-redraw): vyjměte obsah každé stránky z dokumentu do identifikátoru (handle), vytvořte novou prázdnou stránku s původní velikostí média a poté nakreslete zachycený obsah zpět ve zmenšeném ohraničujícím rámečku (bounding box). Okolní bílé místo se stane okrajem. Například při zmenšení na 70 % na stránce A4 připadne 15 % šířky na každou stranu a stejný zlomek nahoru a dolů, což je přesně to, co produkuje aritmetika okrajů níže
Jak funguje CapturePage
CapturePage převezme číslo stránky, povýší obsah této stránky do objektu zachycení v paměti a odstraní stránku ze stromu stránek dokumentu. Toto odstranění je záměrné a je důvodem, proč smyčka vždy vybírá stránku 1 bez ohledu na index iterace: jakmile je stránka 1 zachycena a odstraněna, to, co bylo stránkou 2, se stane novou stránkou 1, a tak dále. Pokud zvýšíte selektor stránek spolu s počítadlem smyčky, přeskočíte každou druhou stránku a skončíte s polovinou očekávaného výstupu
Identifikátor (handle) zachycení vrácený funkcí CapturePage není odkazem na stránku; je to spíše snímek obsahu. Zůstává platný, dokud nezavoláte DrawCapturedPage nebo jej explicitně neuvolníte. DrawCapturedPage přebírá tento identifikátor a cílový obdélník zadaný jako levý offset, spodní offset, šířka a výška, vše v bodech. Knihovna škáluje zachycený obsah tak, aby se přesně vešel do tohoto obdélníku, a zachovává poměr stran, pouze pokud se váš obdélník shoduje s původními proporcemi. Pro rovnoměrné škálování potřebujete obdélník o původní velikosti vynásobený faktorem změny měřítka a vycentrovaný na stránce
Matematika centrování
S faktorem měřítka 70 % se zbývajících 30 % každého rozměru rozdělí rovným dílem mezi obě strany. Horizontální odsazení je tedy pageWidth * (1.0 - 0.70) / 2, což je 15 % šířky, a vertikální odsazení se řídí stejným vzorcem s použitím výšky stránky. Cílový obdélník pro DrawCapturedPage pak začíná na (horizBorder, vertBorder) a pokrývá pageWidth - 2 * horizBorder krát pageHeight - 2 * vertBorder. Tato aritmetika není specifická pro knihovnu; je to prostě geometrie symetrického umístění menšího obdélníku dovnitř většího
Jedna věc, která stojí za zmínku: SetOrigin(1) umístí počátek souřadnic do levého horního rohu spíše než do levého dolního rohu. Hodnoty okrajů, které předáte DrawCapturedPage, se měří od počátku, který nastavíte, takže pokud mezi načítáním a kreslením přepnete režimy počátku, centrování bude posunuté
Příklad v C#
Následující kód zpracuje každou stránku Pages.pdf cyklem zachycení a překreslení a zapíše výsledek do newpages.pdf. PDFL je objekt wrapperu ActiveX/COM přidaný do projektu z 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++)
{
// Vždy vybírejte stránku 1: CapturePage odstraní stránku, takže stránka 2
// se stane stránkou 1 v další iteraci.
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");
}
Příklad v Delphi
Verze Delphi používá TPDFlib přímo spíše než přes vrstvu COM, ale sekvence volání je identická. Jedním z praktických rozdílů je ochrana výstupního souboru: FileExists plus DeleteFile namísto File.Delete, protože SaveToFile selže, pokud je cíl uzamčen předchozím spuštěním, které je stále otevřené v prohlížeči
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;
Co vlastně faktor škálování řídí
Hodnota 0,70 zde znamená, že vykreslený obsah zabírá 70 % každého rozměru stránky, nikoli že soubor má 70 % své původní velikosti v bajtech. Velikost souboru po této operaci závisí na složitosti původního obsahu; stránka s velkými obrázky se proporcionálně nezmenší, protože data pixelů se překreslí ve stejném rozlišení do menší oblasti. Pokud je cílem komprese na úrovni bajtů, správným přístupem je LinearizeFile nebo opětovné uložení s kompresí proudů, nikoli geometrické škálování
Hodnota 70 % také není tvrdým limitem. Jakákoli hodnota mezi 0,0 a 1,0 funguje, a hodnoty nad 1,0 zvětší obsah za původní hranici stránky, což dojde k oříznutí na okraji rámečku média (media box), pokud nezvětšíte také rozměry stránky. S dokumenty o smíšených velikostech se zachází přirozeně, protože hodnoty PageWidth a PageHeight se dotazují (query) pro každou stránku před výpočtem okrajů, takže dokument, ve kterém jsou liché stránky A4 a sudé stránky A3, vytvoří správně vycentrovaný výstup na každé velikosti stránky bez jakéhokoli speciálního ošetřování případů
Kde se věci mohou pokazit
V praxi se objevují dva režimy selhání. Prvním je výstupní soubor ponechaný otevřený v prohlížeči PDF z předchozího spuštění: SaveToFile selže nebo zapíše nula bajtů v závislosti na platformě a nový výstup se nikdy neuloží. Pojistka odstranění souboru na začátku funkce to řeší pro vývoj, ale v produkčním řetězci je bezpečnější zápis do dočasné cesty a po úspěšném dokončení jeho přejmenování
Druhým je neshoda v počtu stránek. Vzhledem k tomu, že CapturePage během zpracování odstraňuje stránky z dokumentu, je počet, který načtete z PageCount() před cyklem, správnou hranicí, podle které se má iterovat. Volání PageCount() uvnitř smyčky by vrátilo klesající číslo při každém průchodu a skončilo by předčasně a ponechalo by poslední stránky nezpracované. Proměnná cyklu v příkladech slouží pouze jako počítadlo zbývajících iterací; nikdy se nepoužívá k výběru stránky, protože stránka, která se má vybrat, je vždy 1 z dříve vysvětleného důvodu
Zde uvedená volání pro manipulaci se stránkami, včetně CapturePage, DrawCapturedPage a SetPageDimensions, jsou součástí losLab PDF Library pro Delphi, C#, VB.NET a C++