מידות דף ה-PDF קבועות בזמן יצירת הדף, ולכן אינך יכול פשוט לשנות את קנה המידה של התוכן במקומו באותו אופן שבו היית משנה גודל של תמונה. מודל הספרייה שהופך כיווץ למעשי הוא לכידה וציור-מחדש (capture-and-redraw): שליפת התוכן של כל דף מתוך המסמך לתוך מצביע (handle), יצירת דף ריק חדש בגודל המדיה המקורי, ולאחר מכן ציור התוכן שנלכד בחזרה פנימה לתוך תיבה תוחמת מוקטנת. הרווח הלבן שמסביב הופך לשוליים. בקנה מידה של 70% על דף A4, למשל, 15% מהרוחב נופל על כל צד ואותו שבר בחלק העליון והתחתון, שזה בדיוק מה שהאריתמטיקה של הגבולות להלן מפיקה
כיצד CapturePage עובד
CapturePage לוקח מספר דף, מעלה את התוכן של אותו דף לאובייקט לכידה בזיכרון, ומסיר את הדף מעץ הדפים של המסמך. הסרה זו מכוונת והיא הסיבה לכך שהלולאה תמיד בוחרת בדף 1 ללא קשר לאינדקס האיטרציה: לאחר שדף 1 נלכד ונמחק, מה שהיה דף 2 הופך לדף 1 החדש, וכן הלאה. אם תקדם את בורר הדפים יחד עם מונה הלולאה תדלג על כל דף שני ותסיים עם חצי מהפלט המצופה
ידית הלכידה (capture handle) המוחזרת על ידי CapturePage אינה הפניה לדף; היא יותר כמו תמונת מצב של התוכן. היא נשארת תקפה עד שאתה קורא ל-DrawCapturedPage או משחרר אותה במפורש. DrawCapturedPage לוקח את הידית הזו בתוספת מלבן יעד שניתן כהיסט שמאלי, היסט תחתון, רוחב וגובה, כולם בנקודות (points). הספרייה משנה את קנה המידה של התוכן שנלכד כדי שיתאים בדיוק למלבן הזה, ומשמרת את יחס הגובה-רוחב רק אם המלבן שלך מתאים במקרה לפרופורציות המקוריות. עבור קנה מידה אחיד, תרצה שהמלבן יהיה הגודל המקורי כפול גורם קנה המידה, ממורכז בדף
מתמטיקת המרכוז
עם גורם קנה מידה של 70%, ה-30% הנותרים של כל ממד מתפצלים שווה בשווה בין שני הצדדים. לכן, ההטמעה האופקית היא pageWidth * (1.0 - 0.70) / 2, שהיא 15% מהרוחב, וההטמעה האנכית פועלת על פי אותה נוסחה תוך שימוש בגובה הדף. מלבן היעד עבור DrawCapturedPage מתחיל אז ב-(horizBorder, vertBorder) ומשתרע על פני pageWidth - 2 * horizBorder על pageHeight - 2 * vertBorder. האריתמטיקה הזו אינה ספציפית לספרייה; זוהי פשוט הגיאומטריה של התאמת מלבן קטן יותר באופן סימטרי בתוך מלבן גדול יותר
דבר אחד שראוי לציין: SetOrigin(1) ממקם את ראשית הצירים (coordinate origin) בפינה השמאלית העליונה במקום בפינה השמאלית התחתונה. ערכי הגבול שאתה מעביר ל-DrawCapturedPage נמדדים מכל ראשית שהגדרת, כך שאם תחליף מצבי ראשית בין טעינה לציור, המרכוז יהיה שגוי
דוגמה ב-C#
הקוד הבא מעבד כל דף של Pages.pdf דרך מחזור הלכידה והציור-מחדש וכותב את התוצאה ל-newpages.pdf. PDFL הוא אובייקט המעטפת ActiveX/COM שנוסף לפרויקט מ-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");
}
דוגמה ב-Delphi
גרסת ה-Delphi משתמשת ב-TPDFlib ישירות במקום דרך שכבת ה-COM, אך רצף הקריאות זהה. הבדל מעשי אחד הוא שומר קובץ הפלט: FileExists בתוספת DeleteFile במקום File.Delete, מכיוון ש-SaveToFile ייכשל אם היעד נעול על ידי הרצה קודמת שעדיין פתוחה במציג
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;
במה למעשה שולט גורם קנה המידה
הערך 0.70 כאן אומר שהתוכן המרונדר תופס 70% מכל ממד דף, לא שהקובץ הוא 70% מגודל הבתים המקורי שלו. גודל הקובץ לאחר פעולה זו תלוי במורכבות התוכן המקורי; דף עם תמונות גדולות לא יתכווץ באופן יחסי מכיוון שנתוני הפיקסלים משורטטים מחדש באותה רזולוציה לאזור קטן יותר. אם דחיסה ברמת הבתים היא המטרה, הגישה הנכונה היא LinearizeFile או שמירה מחדש עם דחיסת זרם (stream compression), ולא שינוי קנה מידה גיאומטרי
נתון ה-70% אינו גבול נוקשה. כל ערך בין 0.0 ל-1.0 עובד, וערכים מעל 1.0 מגדילים את התוכן אל מעבר לגבול הדף המקורי, שייחתך בקצה קופסת המדיה (media box) אלא אם כן תגדיל גם את מידות הדף. מסמכים בגדלים מעורבים מטופלים באופן טבעי מכיוון ש-PageWidth ו-PageHeight נשאלים עבור כל דף לפני חישוב הגבולות, כך שמסמך שבו דפים אי-זוגיים הם A4 ודפים זוגיים הם A3 יפיק פלט ממורכז כהלכה על כל גודל דף ללא כל טיפול מיוחד (special casing)
היכן דברים יכולים להשתבש
שני מצבי כשל עולים בפועל. הראשון הוא קובץ פלט שנשאר פתוח במציג PDF מהרצה קודמת: SaveToFile ייכשל או יכתוב אפס בתים בהתאם לפלטפורמה, והפלט החדש לעולם לא נוחת. שומר מחיקת-הקובץ בתחילת הפונקציה מטפל בכך עבור פיתוח, אך בתהליך (pipeline) ייצור, כתיבה לנתיב זמני ושינוי שם במקרה של הצלחה בטוחה יותר
השני הוא חוסר התאמה בספירת דפים. מכיוון ש-CapturePage מסיר דפים מהמסמך כשהוא מעבד אותם, הספירה שאתה קורא מ-PageCount() לפני הלולאה היא הגבול הנכון לאיטרציה מולו. קריאה ל-PageCount() בתוך הלולאה תחזיר מספר פוחת בכל מעבר ותצא מוקדם, ותשאיר את הדפים האחרונים לא מעובדים. משתנה הלולאה בדוגמאות משמש רק כמונה לאיטרציות שנותרו; לעולם לא משתמשים בו לבחירת דף, מכיוון שהדף שיש לבחור הוא תמיד 1 מהסיבה שהוסברה קודם לכן
קריאות מניפולציית הדפים המוצגות כאן, כולל CapturePage, DrawCapturedPage, ו-SetPageDimensions, הן חלק מ-losLab PDF Library עבור Delphi, C#, VB.NET ו-C++