מציג PDF ב-Delphi מסתכם בשני רכיבים והחיווט ביניהם. TPdf מחזיק במסמך: הוא פותח את הקובץ, מפענח אותו, ועונה על שאלות לגבי ספירת עמודים ומטא-נתונים. TPdfView הוא הפקד החזותי שמצייר עמודים על המסך ומטפל בגלילה, זום, ובעמוד שהמשתמש מסתכל עליו כרגע. PDFium Component עוטף את אותו מנוע רינדור שמגיע בתוך Chrome, כך שהגליפים, החלקת הקצוות (anti-aliasing) והצבע שאתה מקבל על הקנבס תואמים למה שהמשתמשים שלך כבר רואים בדפדפן שלהם. העבודה אינה ברינדור. היא בחיבור אובייקט המסמך לתצוגה, טעינה ללא קריסה על קובץ פגום או מוגן בסיסמה, ומתן קומץ הפקדים למשתמש שגורמים למציג להרגיש גמור: העברת דף, שינוי הזום, התאמת העמוד לחלון
זה עובר על ההרכבה הזו בסדר שבו אתה באמת בונה אותה. כל דבר כאן מרנדר עמוד אחד בכל פעם, שזה מה שרוב זרימות העבודה של מסמכים רוצות. אם אתה צריך עמודים מוערמים בעמודה נגללת אחת ברצף, זו החלטת פריסה שונה וזה לא הנתיב כאן
חיווט TPdf ל-TPdfView
זרוק (Drop) TPdf ו-TPdfView על הטופס, ואז אמור לתצוגה איזה מסמך להציג. ההקצאה הבודדת הזו היא כל הקישור בין המסמך הלא-חזותי לפקד שמצייר אותו
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf ו-PdfView נזרקו בזמן עיצוב.
PdfView.Pdf := Pdf; // התצוגה מציירת את כל מה שהמסמך הזה מכיל
PdfView.FitMode := pfmFitWidth; // התחל למשתמש בזום הגיוני
end;
לפני שכל זה רץ, הספרייה המקורית של PDFium חייבת להיות על המכונה. PDFium Component קורא ל-pdfium32.dll או pdfium64.dll בהתאם לפלטפורמת היעד שלך, והמסמך פשוט מסרב להיפתח אם לא ניתן למצוא את ה-DLL. ספק (Ship) את ה-DLL התואם לצד קובץ ההפעלה (executable) שלך, או מקם אותו היכן שהטוען של המערכת ימצא אותו. ה-builds תומכי V8 קיימים רק עבור מסמכי PDF שנושאים JavaScript שאתה רוצה לבצע, מה שמציג פשוט לא עושה, לכן הושט יד ל-DLL הסטנדרטי אלא אם יש לך סיבה קונקרטית שלא
טעינת מסמך מבלי לסמוך על הקלט
האינסטינקט הוא לעטוף את הטעינה ב-try/except ולהתייחס לחריגה (exception) שנזרקת כאל כישלון. האינסטינקט הזה שגוי כאן, ולטעות בזה מייצר מציג שנראה בסדר עד שמישהו מוסר לו קובץ שבור. הגדרת Active := True אינה מעלה חריגה בעת כשל טעינה. PDFium Component תופס את השגיאה הפנימית ומשאיר את Active במצב False, כך שהדרך הכנה היחידה לדעת האם המסמך נפתח היא לקרוא את המאפיין בחזרה לאחר שהגדרת אותו
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // לעולם אינו מעלה חריגה; כישלון משאיר Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // התצוגה עוקבת אחר העמוד הנוכחי שלה עצמה
UpdatePageLabel;
end;
שני דברים ראויים לתשומת לב. הראשון הוא ש-PageNumber קיים בשני האובייקטים והשניים עצמאיים. Pdf.PageNumber הוא המושג של המסמך לגבי עמוד נוכחי; PdfView.PageNumber הוא העמוד שהפקד מציג בפועל, והוא זה שאתה מגדיר כדי להזיז את המשתמש דרך הקובץ. הגדרת האחד אינה מזיזה את השני, ולכן מציג תמיד מניע את המאפיין של התצוגה. השני הוא האינדקס המבוסס-1: עמודים רצים מ-1 עד Pdf.PageCount, לא מ-0, מה שתופס כל מי שרגיל למערכים מבוססי-אפס
טיפול בקובץ מוצפן
מסמכים מוצפנים מתקפלים לתוך אותו נתיב טעינה. אם סיסמת הפתיחה מוגדרת לפני ההפעלה, המסמך מפוענח כשהוא נפתח; אם היא שגויה או חסרה, Active נשאר False בדיוק כמו עבור קובץ פגום. לכן השחזור הוא לבקש סיסמה ולנסות את ההפעלה שוב
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // חייב להיות מוגדר לפני Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
end;
מכיוון שהכישלון שקט גם עבור סיסמה שגויה וגם עבור קובץ פגום, אינך יכול להבדיל בין השניים מתוך Active לבדו. בפועל זה מקובל עבור מציג: המשתמש או שמספק את הסיסמה הנכונה או שלומד שהקובץ לא ייפתח, וההודעה נקראת זהה בשני המקרים
דפדוף במסמך
כאשר המסמך פתוח, הניווט הוא אריתמטיקה על PdfView.PageNumber התחום על ידי Pdf.PageCount. העבודה האמיתית היחידה היא הידוק (clamping), כך שהכפתורים לעולם אינם דוחפים את העמוד מחוץ לטווח והכפתורים הראשון והאחרון נשארים מושבתים בקצוות הקובץ
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// ארבעת כפתורי הניווט מצטמצמים לקריאה אחת כל אחד
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
תיבת טקסט "עבור לעמוד N" היא אותה קריאה ל-GoToPage הניזונה ממספר שלם מנותח, וההידוק (clamp) מכסה את המקרה שבו המשתמש מקליד 9999 בקובץ בן עשרה עמודים. שמור על UpdatePageLabel כמקום היחיד שכותב "עמוד 3 מתוך 12" כך שהתצוגה לעולם אינה נסחפת מחוץ לסנכרון עם מה שהתצוגה (view) מראה
זום: אחוזים מפורשים ומצבי התאמה
זום ב-TPdfView מגיע בשני טעמים שיוצרים אינטראקציה, והבנת האינטראקציה היא ההבדל בין פקד זום שמתנהג כראוי לכזה שנלחם במשתמש. הנתיב הישיר הוא המאפיין Zoom, אחוז שבו 100 פירושו גודל אמיתי. הנתיב השני הוא FitMode, שאומר לתצוגה לחשב את הזום עבורך ולהמשיך לחשב אותו מחדש ככל שהחלון משנה את גודלו
// הגדלות קבועות
PdfView.Zoom := 100; // גודל אמיתי
PdfView.Zoom := 50; // חצי
PdfView.Zoom := 200; // כפול
// תן לתצוגה לשנות את גודל העמוד לחלון, ולשמור על גודלו בעת שינוי גודל החלון
PdfView.FitMode := pfmFitWidth; // רוחב העמוד ממלא את הפקד
PdfView.FitMode := pfmFitPage; // כל העמוד גלוי
PdfView.FitMode := pfmActualSize; // 1:1 עם הנקודות של המסמך
הנה החלק שמכשיל אנשים. הקצאת Zoom ישירות מאפסת את FitMode ל-pfmNone. זוהי התנהגות נכונה, לא באג: ברגע שהמשתמש בוחר 150% מדויק, התצוגה לא יכולה עוד גם לכבד "התאמה לרוחב," כיוון ששתי הבקשות מתנגשות. ההשלכה לממשק המשתמש שלך היא שכפתור זום-פנימה וכפתור התאמה-לעמוד הם מצבים סותרים-הדדית, וסרגל הכלים צריך להפוך את המצב הפעיל לגלוי. כאשר המשתמש לוחץ על התאמה-לעמוד, הגדר את FitMode; כאשר הוא לוחץ על זום מספרי, הגדר את Zoom ותן לו לנקות את מצב ההתאמה בעצמו
אם תעדיף לחשב את ערך ההתאמה בעצמך, אולי כדי להזין (seed) סליידר זום עם אחוז ההתאמה הנוכחי, עוזרי הכל-עמוד-בנפרד (per-page helpers) נותנים לך את המספרים מבלי לשנות את המצב. PageWidthZoom[N], PageZoom[N], ו-ActualSizeZoom[N] מחזירים את האחוז שיתאים את עמוד N לרוחב, יתאים אותו בשלמותו, או ירנדר אותו בגודל אמיתי
// הזן קריאת זום מערך ההתאמה-לרוחב של העמוד הנוכחי
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
מה באמת צריך מציג גמור
המציג לעיל הוא בן כמה עשרות שורות, והוא כבר מבצע את העבודה שזרימת עבודה של מסמך צריכה: לפתוח קובץ, לשרוד קובץ פגום, להציג עמוד, לנוע בין עמודים, ולשנות את ההגדלה ידנית או על ידי התאמה. PDFium עושה את החלקים הקשים בשקט. גופנים מוטמעים נפתרים (resolve), הערות ושדות טופס מצוירים היכן שהמסמך מניח אותם, והעמוד שאתה רואה תואם את זה שמשתמש Chrome היה רואה, כי זה אותו מנוע שמצייר את שניהם
מהבסיס הזה התוספות הן מצטברות (incremental) ולא מבניות. בחירת טקסט וחיפוש קוראים מאותה שכבת טקסט ש-PDFium כבר בונה; מטא-נתונים כגון Pdf.Title ו-Pdf.Author נמצאים במרחק קריאת מאפיין אחד; סיבוב (rotation) וגווני אפור הן אפשרויות רינדור שאתה מעביר כשאתה מצייר עמוד למפת-סיביות (bitmap). אף אחד מאלה אינו משנה את עמוד השדרה שיש לך כאן, שהוא אובייקט המסמך, התצוגה (view), וזרימת הטען-ואז-נווט המחברת ביניהם. קבל את עמוד השדרה הזה נכון והשאר הוא קישוט
רכיבי ה-TPdf ו-TPdfView המשמשים לאורך כל הדרך הם חלק מ-PDFium Component עבור Delphi ו-C++Builder, שנושא את ההתייחסות המלאה למציג (viewer) בדף המוצר שלו