A PDF oldalak mérete az oldal létrehozásakor rögzített, így nem lehet egyszerűen átméretezni a tartalmat a helyén úgy, ahogyan egy képet átméretezne. A könyvtári modell, amely praktikussá teszi a kicsinyítést, a rögzítés és újrarajzolás (capture-and-redraw): emelje ki minden oldal tartalmát a dokumentumból egy leíróba (handle), hozzon létre egy új, üres oldalt az eredeti hordozómérettel, majd rajzolja vissza a rögzített tartalmat egy csökkentett határolókeretbe. A környező üres tér lesz a margó. Egy A4-es oldalon 70%-os léptékben például a szélesség 15%-a esik mindkét oldalra, és ugyanekkora arányban felülre és alulra, ami pontosan az, amit az alábbi szegély-aritmetika eredményez
Hogyan működik a CapturePage
A CapturePage egy oldalszámot vesz fel, az adott oldal tartalmát egy memóriabeli rögzítési objektumba lépteti elő, és eltávolítja az oldalt a dokumentum oldalfájából. Ez az eltávolítás szándékos, és ez az oka annak, hogy a ciklus mindig az 1. oldalt választja ki, függetlenül az iterációs indextől: amint az 1. oldalt rögzítették és törölték, ami a 2. oldal volt, az lesz az új 1. oldal, és így tovább. Ha az oldalválasztót a ciklusszámlálóval együtt növeli, minden második oldalt átugorja, és végül a várt kimenet felét kapja
A CapturePage által visszaadott rögzítési leíró (capture handle) nem egy oldalhivatkozás; inkább egy tartalom pillanatkép. Érvényes marad egészen addig, amíg meg nem hívja a DrawCapturedPage-t, vagy kifejezetten fel nem szabadítja azt. A DrawCapturedPage ezt a leírót és egy cél téglalapot vesz fel, amely bal oldali eltolásként, alsó eltolásként, szélességként és magasságként van megadva, mindegyik pontokban (points). A könyvtár úgy méretezi a rögzített tartalmat, hogy pontosan illeszkedjen ebbe a téglalapba, és csak akkor őrzi meg a képarányt, ha a téglalapja történetesen megegyezik az eredeti arányokkal. Az egyenletes méretezéshez azt szeretné, ha a téglalap az eredeti méret szorozva a léptéktényezővel, és az oldal közepén helyezkedne el
A középre igazítási matematika
70%-os léptéktényező esetén az egyes dimenziók fennmaradó 30%-a egyenlően oszlik meg a két oldal között. Tehát a vízszintes betét pageWidth * (1.0 - 0.70) / 2, ami a szélesség 15%-a, és a függőleges betét ugyanazt a képletet követi az oldalmagasság használatával. A DrawCapturedPage cél téglalapja ekkor (horizBorder, vertBorder)-nél kezdődik, és áthidalja a pageWidth - 2 * horizBorder és pageHeight - 2 * vertBorder értékeket. Ez az aritmetika nem könyvtár-specifikus; ez csak annak a geometriája, hogy egy kisebb téglalapot szimmetrikusan beillesztünk egy nagyobb belsejébe
Egy dolog, amit érdemes megjegyezni: a SetOrigin(1) a koordináta origót a bal felső sarokba helyezi a bal alsó sarok helyett. A DrawCapturedPage-nek átadott szegélyértékeket onnan az origóból mérik, amelyet beállított, így ha a betöltés és a rajzolás között váltja az origó módot, a középre igazítás elcsúszik
C# példa
A következő kód a Pages.pdf minden oldalát feldolgozza a rögzítési és újrarajzolási (capture-and-redraw) cikluson keresztül, és az eredményt a newpages.pdf fájlba írja. A PDFL az ActiveX/COM burkoló objektum, amelyet a PDFlibDLL64.dll-ből adtak hozzá a projekthez
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 példa
A Delphi verzió a TPDFlib-et közvetlenül használja, nem pedig a COM rétegen keresztül, de a hívási sorrend azonos. Egy gyakorlati különbség a kimeneti fájlvédő (output file guard): FileExists plusz DeleteFile a File.Delete helyett, mert a SaveToFile sikertelen lesz, ha a célt egy korábbi futtatás zárolja, amely még meg van nyitva egy megjelenítőben
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;
Mit is szabályoz valójában a léptéktényező
A 0,70 érték itt azt jelenti, hogy a renderelt tartalom minden oldaldimenzió 70%-át foglalja el, nem pedig azt, hogy a fájl az eredeti bájtméretének 70%-a. A fájlméret e művelet után az eredeti tartalom összetettségétől függ; egy nagy képeket tartalmazó oldal nem fog arányosan zsugorodni, mert a képpontadatokat azonos felbontásban rajzolják újra egy kisebb területre. Ha bájtszintű tömörítés a cél, akkor a megfelelő megközelítés a LinearizeFile vagy az újra mentés folyamtömörítéssel (stream compression), nem pedig a geometriai méretezés
A 70%-os érték szintén nem egy szigorú korlát. Bármely 0,0 és 1,0 közötti érték működik, az 1,0 feletti értékek pedig megnövelik a tartalmat az eredeti oldalhatáron túlra, amely levágja azt a média doboz (media box) szélénél, hacsak nem növeli meg az oldal méreteit is. A vegyes méretű dokumentumokat természetesen kezelik, mivel a PageWidth és a PageHeight oldalanként kerül lekérdezésre a szegélyszámítás előtt, így egy olyan dokumentum, ahol a páratlan oldalak A4-esek, a páros oldalak pedig A3-asok, minden oldalméreten megfelelően középre igazított kimenetet fog eredményezni anélkül, hogy különösebb egyedi esetkezelésre (special casing) lenne szükség
Hol romolhatnak el a dolgok
A gyakorlatban két hiba mód merül fel. Az első az, ha egy kimeneti fájlt nyitva hagynak egy PDF megjelenítőben egy korábbi futtatásból: a SaveToFile sikertelen lesz, vagy nulla bájtot ír a platformtól függően, és az új kimenet soha nem érkezik meg. A fájltörlő védő (file-delete guard) a függvény tetején ezt a fejlesztéshez kezeli, de egy termelési folyamatban biztonságosabb, ha egy ideiglenes útvonalra ír, és siker esetén átnevezi azt
A második az oldalszám eltérése. Mivel a CapturePage eltávolítja az oldalakat a dokumentumból feldolgozás közben, a cikluson kívül, a ciklus előtt lekérdezett PageCount() által olvasott szám a helyes határ az iterációhoz. A PageCount() cikluson belüli meghívása minden egyes lépésben csökkenő számot adna vissza, és korán kilépne, így az utolsó oldalak feldolgozatlanok maradnának. A példákban szereplő ciklusváltozó csak hátralévő iterációk számlálójaként szolgál; soha nem használják egy oldal kiválasztására, mert a kiválasztandó oldal mindig 1, a korábban elmagyarázott okokból
Az itt bemutatott oldal manipulációs hívások, beleértve a CapturePage, DrawCapturedPage és a SetPageDimensions hívásokat, a Delphi, C#, VB.NET és C++ nyelvekhez készült losLab PDF Library részét képezik