מאמר טכני

בניית מציג PDF אינטראקטיבי ב-Delphi עם TPDFlibViewer

הטמעת פאנל קריאת PDF באפליקציה עסקית ב-Delphi אינה דורשת כתיבת לולאת רינדור, מנגנון גלילה או מודל תקריב. ספריית losLab PDF Library מספקת את TPDFlibViewer, פקד VCL הפותח מסמך בקריאה אחת ומביא בחירה של טקסט, גרירה (panning), זום מרובע (marquee zoom), צילומי מסך (snapshots), הדגשת אזורים, הדגשת תוצאות בחיפוש טקסט מלא, אינטראקציה עם הערות והדפסה כהתנהגות מובנית. מאמר זה עובר על חמישה שלבים שהופכים טופס ריק למציג עובד: הנחת הפקד, חיבור פעולות מוכנות מראש, הוספת הדגשת חיפוש, טיפול בלחיצות על הערות והדפסה

מדוע להשתמש בפקד מציג במקום לכתוב לולאת רינדור?

טיעון השכבות בא קודם, כי הוא מסביר מה אתם באמת קונים. רינדור דף למפת סיביות (bitmap) הוא ה-20 אחוז הקלים; ה-80 אחוז האחרים הם כל מה שעוטף אותו: מטמון מפות סיביות של דפים עם תקציב פינוי, פריסת גלילה על פני דפים בגדלים שונים, מצבי תקריב שמחושבים מחדש בעת שינוי גודל, בדיקת פגיעה (hit-testing) שממפה פיקסל עכבר חזרה לקואורדינטות דף בכל תקריב וסיבוב, ותזמון צביעה מחדש ללא ריצוד. TPDFlibViewer מנהל את כל זה ומאציל את עבודת הראסטר לאותו מנוע המתואר במאמר על רינדור מרובה מנועים, כך ששכבת הבקרה ושכבת הרינדור נשארות ניתנות להחלפה בנפרד. קוד הטופס שלכם מדבר עם פקד Windows נגלל, ולא עם מרנדר

כיצד מטמיעים מציג PDF באפליקציית Delphi?

TPDFlibViewer.LoadFromFile פותח מסמך דרך מופע TPDFlib הפנימי של הפקד עצמו ומחזיר 1 בהצלחה, כך שמציג מינימלי הוא קריאת בנאי (constructor), הקצאת Parent וטעינה אחת. אם אתם כבר מחזיקים במופע TPDFlib במקום אחר באפליקציה, AttachLibrary מציג את המסמך שנבחר במקום זאת, כאשר הבעלות נשארת אצלכם

uses PDFlibViewer;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FViewer := TPDFlibViewer.Create(Self);
  FViewer.Parent := Self;
  FViewer.Align := alClient;
  if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
    FViewer.ViewerMode := vmHand;  // pan by dragging; a plain click still follows links
end;

המאפיין ViewerMode בוחר מה העכבר עושה, מתוך חמישה ערכים: vmSelect הוא כלי ברירת המחדל לבחירת טקסט, vmHand גורר עם סמן יד בעוד שלחיצה רגילה עדיין עוקבת אחר קישורים, vmZoom הוא זום מרובע המגדיל את האזור הנגרר למילוי תצוגת המראה, vmSnapshot לוכד אזור נגרר ללוח הגזירים (עם אירוע OnSnapshot ומתג SnapshotToClipboard), ו-vmHighlight מצייר הדגשות אזור חצי-שקופות קבועות. כלי ההדגשה ראוי לציון בנוגע לקואורדינטות: אזורים נשמרים במרחב המשתמש של הדף, ולא בפיקסלים של הלקוח, כך שסימוני AddHighlightRegion שורדים זום, סיבוב תצוגה וניווט בדפים ללא צורך במעקב מצדכם. בתצוגה מסובבת, כיוון הגרירה יכול אפילו לנוע נגד צירי הדף, מה שמניב רוחב או גובה שליליים לאחר מיפוי הסיבוב ההפוך — הפקד מנרמל את המלבן עבורכם

כיצד לחבר סרגל כלים של PDF ב-Delphi ללא קוד קישור?

היחידה PDFlibViewerActions מייתרת לחלוטין את הצורך בקוד קישור לסרגל הכלים. היא מספקת עשרים מחלקות נגזרות של TAction מוכנות מראש המכסות ניווט בדפים (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), תקריב (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), סיבוב תצוגה (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), הדפסה, דיאלוגים של פתיחת ושמירת קובץ בשם, העתקת טקסט שנבחר, ופעולת מתג אחת לכל כלי עכבר (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). כל פעולה מצביעה על פקד דרך מאפיין ה-Viewer שפורסם שלה ושומרת על המצב שלה עצמה: כל פעולה מנטרלת את עצמה כאשר אין מסמך פתוח, פעולות הניווט עוקבות אחר מיקום הדף, העתקה עוקבת אחר קיומה של בחירה, ופעולות המצב וההתאמה שומרות על מצב הסימון שלהן מסונכרן עם הכלי הפעיל ומצב התקריב. בזמן העיצוב הן כולן נרשמות בקטגוריית losLab PDF של עורך ה-Action List; בזמן הריצה אותן מחלקות הן שלוש שורות כל אחת

uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;

procedure TMainForm.BuildToolbar;
var
  NextPage: TPDFlibViewerNextPage;
  FitWidth: TPDFlibViewerFitWidth;
  HandMode: TPDFlibViewerHandMode;
begin
  NextPage := TPDFlibViewerNextPage.Create(Self);
  NextPage.ActionList := ActionList1;
  NextPage.Viewer := FViewer;
  btnNext.Action := NextPage;

  FitWidth := TPDFlibViewerFitWidth.Create(Self);
  FitWidth.ActionList := ActionList1;
  FitWidth.Viewer := FViewer;
  btnFitWidth.Action := FitWidth;

  HandMode := TPDFlibViewerHandMode.Create(Self);
  HandMode.ActionList := ActionList1;
  HandMode.Viewer := FViewer;
  btnHand.Action := HandMode;
end;

מלכודת אחת ברמת היחידה כדאי להכיר אם אתם בונים רישום פעולות משלכם מעל דפוס זה. RegisterActions מוצהר ב-System.Actions, ולא ב-Vcl.ActnList, ושגיאת הקומפילציה שתקבלו עם Vcl.ActnList בלבד בהצהרת ה-uses היא מטעה מאוד: ליטרל מערך המחלקות [TAction1, TAction2, ...] מפורש כקבוצה (set), מה שמייצר שרשרת של שגיאות E2010 Incompatible types: 'Integer' and 'class of ...' שנראות כמו אי-תאימות סוגים במחלקות הפעולות שלכם. התיקון הוא תוספת יחידה ב-uses, אך הדיאגנוסטיקה מצביעה לכל מקום פרט אליה. עבור סרגלי כלים של תקריב, TPDFlibViewer.EffectiveZoomPercent מדווח על התקריב בפועל — כולל הגורם המחושב במצבי התאמת רוחב והתאמת דף — כך ששלב התקרבות יכול להתבסס על ההגדלה האמיתית הנוכחית במקום על הערך האחרון שהקציתם

הדגשת תוצאות חיפוש והיפוך הקואורדינטות שמאחוריה

HighlightSearchHits מחפש במסמך כולו שאילתה ומצייר פס חצי-שקוף מעל כל התאמה, ומחזיר את מספר הפגיעות; ClearSearchHighlights, SearchHighlightCount והמאפיין SearchHighlightColor משלימים את הממשק. מכיוון שמלבני הפגיעה נשמרים במרחב הדף ומנוקדים מחדש בכל צביעה, הפסים נשארים צמודים לטקסט שלהם בזמן שהמשתמש גולל, מקרב ומסובב את התצוגה. אם אתם משתמשים בלוח הנלווה TPDFlibSearchPanel, ה-RunSearch שלו מצייר את אותם פסים במציג המחובר באופן אוטומטי דרך המאפיין HighlightInView, שמופעל כברירת מחדל

procedure TMainForm.btnSearchClick(Sender: TObject);
var
  Hits: Integer;
begin
  FViewer.SearchHighlightColor := clYellow;
  Hits := FViewer.HighlightSearchHits(edtQuery.Text, False);  // case-insensitive
  StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;

החלק המתוחכם, והסיבה להעדיף את הנתיב המובנה על פני בנייה עצמית מתוצאות חיפוש גולמיות, הוא המרת מערכת הקואורדינטות. מנוע הטקסט מדווח על מלבני התאמה בקואורדינטות התלויות בהגדרת ה-Origin של הספרייה: עם Origin 0 או 3 ציר ה-Y פועל מלמטה למעלה כמו במרחב המשתמש המקורי של PDF, ועם Origin 2 או 3 ציר ה-X נמדד מהקצה הימני. שכבת המציג עובדת במרחב דף מלמעלה למטה, מבוסס שמאל, כך שכל מלבן פגיעה חייב לעבור היפוך (Y = PageHeight - Bottom, ובמקומות הרלוונטיים X = PageWidth - Right) לפני שהוא נשמר — לקיחת הקצה העליון המדווח כפי שהוא תחת נקודת מוצא כברירת מחדל תצייר את ההדגשה במיקום המשתקף אנכית בדף. TPDFlibViewer מבצע המרה זו לפי נקודת המוצא הפעילה כאשר הוא רושם כל פגיעה, ואז מחיל את מיפוי הסיבוב קדימה בזמן הצביעה, וזו הסיבה שהפסים נוחתים נכון בכל זווית. החצי-שקיפות עצמה משתמשת ב-API של Windows בשם AlphaBlend עם אלפא מקור קבועה, מכיוון שלקנבס VCL אין שילוב אלפא מובנה. ממשקי ה-API של חיפוש הטקסט הבסיסי וגיאומטריית הדפים מכוסים בהרחבה במאמר על חיפוש טקסט ופירוט אלמנטים בדף

לחיצות על הערות, רמזים וייצוא קבצים מצורפים

TPDFlibViewer מתייחס להערות (annotations) כאל אובייקטים אינטראקטיביים ממדרגה ראשונה. לחיצה על הערה שאינה קישור מפעילה את OnAnnotClick עם מספר הדף, אינדקס ההערה (מבוסס 1) ומחרוזת תת-הסוג; AnnotAtPagePoint חושף את אותה בדיקת פגיעה תוכניתית ומדלג על הערות נלוות מסוג Popup כדי שתמיד תקבלו את ההערה שהמשתמש רואה בפועל. ריחוף מעל הערה מציג את טקסט התוכן שלה כרמז (hint) של הפקד דרך מתג AnnotHints, המופעל כברירת מחדל ומשתף פעולה עם מאפיין ה-VCL הסטנדרטי ShowHint. עבור הערות מסוג FileAttachment, הפונקציה SaveAnnotAttachmentToFile כותבת את הקובץ המוטמע לדיסק ומחזירה 1 בהצלחה, מה שמשלים תהליך עבודה של לחיצה לשמירה במספר שורות בודדות

procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
  const Subtype: WideString);
begin
  if Subtype = 'FileAttachment' then
    if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
        'C:\Temp\attached-invoice.xml') = 1 then
      StatusBar1.SimpleText := 'Attachment saved';
end;

הדפסה והגבולות שעליכם לתכנן עבורם

הדפסה אינה דורשת אינטגרציה נפרדת. PrintDoc מציג את דיאלוג ההדפסה הסטנדרטי של Windows — בחירת מדפסת, טווח דפים, עותקים, איסוף (collation) — ומעביר את המשימה למנוע ההדפסה של הספרייה, בעוד ש-PrintDoc(False) שולח את המסמך כולו למדפסת ברירת המחדל ללא ממשק משתמש כלל. PrintDocRange פועל בצורה תוכניתית מלאה עם שם מדפסת מפורש, דף התחלה וסוף, ספירת עותקים ודגל איסוף, שהיא נקודת הכניסה הנכונה לעבודות הדפסה באצוות או מופעלות שרת מאותו מופע מציג

שני גבולות ראויים לאזכור כנה. ראשית, TPDFlibViewer הוא פקד Windows VCL; שכבת המציג אינה כלולה בבניות NOVCL, ולכן שירותי קונסול ויעדים שאינם VCL צריכים להשתמש ישירות ב-API הרינדור הבסיסי TPDFlib. שנית, שימוש ללא ממשק (headless) נתמך במכוון אך מוגבל: CaptureRegion מרנדר כל אזור לקוח — דפים פלוס הדגשות — לתוך מפת סיביות בבעלות הקורא ועובד על פקד ללא הורה וללא מזהה חלון (window handle), מכיוון שנתיב הצביעה נמנע מקריאות VCL שיאלצו יצירת מזהה. זה הופך אימות ברמת הפיקסל בבדיקות אוטומטיות למעשי, אך כל דבר שבאמת זקוק ללולאת הודעות, כגון כלי העכבר האינטראקטיביים, עדיין שייך לסשן ממשק משתמש אמיתי. בצד הביצועים, הפקד שומר על מטמון דפי זיכרון עם תקציב הניתן להגדרה באמצעות SetCacheLimit, ויכול לשמור דפים מרונדרים בין סשנים; המאמר על מטמון דפי דיסק של המציג מכסה שכבה זו ואת התנהגות ה-DPI שלה לכל מסך בפירוט

חמישה שלבים, אם כן: הנחת הפקד וקריאה ל-LoadFromFile, חיבור הפעולות המוכנות מראש מ-PDFlibViewerActions, קריאה ל-HighlightSearchHits לחיפוש בתוך הדף, טיפול ב-OnAnnotClick עבור תהליכי עבודה של הערות, וחשיפת PrintDoc. כל חלק המוצג כאן מגיע בתוך losLab PDF Library הסטנדרטית עבור Delphi, שבה פקד המציג, מחלקות הפעולה ומנוע הרינדור מותקנים יחד