Dimenzije PDF stranice fiksne su u trenutku njezina stvaranja, tako da ne možete jednostavno skalirati sadržaj na mjestu na način na koji biste promijenili veličinu slike. Model knjižnice (library) koji smanjivanje čini praktičnim je hvatanje-i-ponovno-crtanje (capture-and-redraw): izvucite sadržaj svake stranice iz dokumenta u ručicu (handle), stvorite novu praznu stranicu u izvornoj veličini medija, a zatim ponovno iscrtajte uhvaćeni sadržaj unutar smanjenog okvira. Okolni prazni prostor postaje margina. Pri mjerilu od 70% na stranici A4, na primjer, 15% širine pada na svaku stranu i isti udio na vrh i dno, što je točno ono što proizvodi aritmetika okvira u nastavku
Kako radi CapturePage
CapturePage preuzima broj stranice, promiče sadržaj te stranice u objekt za hvatanje u memoriji i uklanja stranicu iz stabla stranica dokumenta. To uklanjanje je namjerno i razlog je zašto petlja uvijek bira stranicu 1 bez obzira na indeks iteracije: nakon što se stranica 1 uhvati i izbriše, ono što je bila stranica 2 postaje nova stranica 1, i tako dalje. Ako povećavate birač stranice zajedno s brojačem petlje, preskočit ćete svaku drugu stranicu i završiti s upola manje očekivanog izlaza
Ručica (handle) za hvatanje koju vraća CapturePage nije referenca na stranicu; više je poput snimke sadržaja. Ostaje valjana sve dok ne pozovete DrawCapturedPage ili ju eksplicitno ne oslobodite. DrawCapturedPage preuzima tu ručicu plus odredišni pravokutnik zadan kao lijevi pomak, donji pomak, širinu i visinu, sve u točkama. Biblioteka skalira uhvaćeni sadržaj kako bi točno odgovarao tom pravokutniku, čuvajući omjer stranica samo ako vaš pravokutnik slučajno odgovara izvornim proporcijama. Za ravnomjerno skaliranje želite da pravokutnik bude izvorne veličine pomnožene s faktorom mjerila, centriran na stranici
Matematika za centriranje
Uz faktor mjerila od 70%, preostalih 30% svake dimenzije se ravnomjerno dijeli između dviju strana. Dakle, vodoravni umetak je pageWidth * (1.0 - 0.70) / 2, što je 15% širine, a okomiti umetak slijedi istu formulu pomoću visine stranice. Odredišni pravokutnik za DrawCapturedPage tada počinje na (horizBorder, vertBorder) i proteže se na pageWidth - 2 * horizBorder do pageHeight - 2 * vertBorder. Ta aritmetika nije specifična za biblioteku; to je samo geometrija simetričnog uklapanja manjeg pravokutnika u veći
Jedna stvar koju vrijedi primijetiti: SetOrigin(1) postavlja ishodište koordinata gore lijevo (top-left) umjesto dolje lijevo (bottom-left). Vrijednosti obruba koje prosljeđujete DrawCapturedPage mjere se od onog ishodišta kojeg ste postavili, pa ako prebacujete načine rada ishodišta između učitavanja i crtanja, centriranje će biti pogrešno
Primjer u C#-u
Sljedeći kod obrađuje svaku stranicu Pages.pdf kroz ciklus hvatanja i ponovnog crtanja te ispisuje rezultat u newpages.pdf. PDFL je objekt ActiveX/COM omotača dodan u projekt iz 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++)
{
// Uvijek odaberite stranicu 1: CapturePage uklanja stranicu, pa stranica 2
// postaje stranica 1 u sljedećoj iteraciji.
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");
}
Primjer u Delphiju
Verzija u Delphiju koristi TPDFlib izravno umjesto putem COM sloja, ali je redoslijed poziva identičan. Jedna je praktična razlika u zaštiti izlazne datoteke: FileExists plus DeleteFile umjesto File.Delete, jer će SaveToFile ne uspjeti ako je odredište zaključano prethodnim pokretanjem koje je još uvijek otvoreno u pregledniku
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;
Što zapravo kontrolira faktor mjerila
Vrijednost 0.70 ovdje znači da iscrtani sadržaj zauzima 70% svake dimenzije stranice, a ne da je datoteka veličine 70% izvornih bajtova. Veličina datoteke nakon ove operacije ovisi o složenosti izvornog sadržaja; stranica s velikim slikama neće se proporcionalno smanjiti jer se podaci o pikselima precrtavaju istom razlučivošću na manje područje. Ako je cilj kompresija na razini bajta, pravi pristup je LinearizeFile ili ponovno spremanje s kompresijom toka, a ne geometrijsko skaliranje
Brojka od 70% također nije čvrsta granica. Svaka vrijednost između 0.0 i 1.0 radi, a vrijednosti iznad 1.0 povećavaju sadržaj izvan izvorne granice stranice, što reže na rubu medijskog okvira osim ako također ne povećate dimenzije stranice. Dokumenti mješovite veličine rukuju se prirodno jer se za PageWidth i PageHeight postavlja upit po stranici prije izračuna granice, tako da će dokument u kojem su neparne stranice A4, a parne A3, proizvesti ispravno centriran izlaz na svakoj veličini stranice bez ikakvih posebnih slučajeva
Gdje stvari mogu poći po zlu
U praksi se pojavljuju dva načina neuspjeha. Prvi je izlazna datoteka ostavljena otvorena u PDF pregledniku od prethodnog pokretanja: SaveToFile neće uspjeti ili će zapisati nula bajtova ovisno o platformi, a novi izlaz nikad ne sleti. Zaštita od brisanja datoteke na vrhu funkcije rješava to tijekom razvoja, ali u produkcijskom cjevovodu (pipeline) sigurnije je pisati na privremenu stazu i preimenovati kada je uspješno
Drugi je nepodudaranje u broju stranica. Budući da CapturePage uklanja stranice iz dokumenta dok ih obrađuje, broj koji očitate iz PageCount() prije petlje predstavlja ispravnu granicu prema kojoj trebate iterirati. Pozivanje PageCount() unutar petlje vratilo bi opadajući broj pri svakom prolasku i izašlo bi rano, ostavljajući posljednje stranice neobrađenima. Varijabla petlje u primjerima služi samo kao brojač preostalih iteracija; nikada se ne koristi za odabir stranice, jer je stranica koju treba odabrati uvijek 1 iz ranije objašnjenog razloga
Ovdje prikazani pozivi za manipulaciju stranicama, uključujući CapturePage, DrawCapturedPage i SetPageDimensions, dio su losLab PDF Library za Delphi, C#, VB.NET i C++