מאמר טכני

השוואת PDF זה לצד זה ב-Delphi עם PDFium Component

שני מסמכים פתוחים בו-זמנית, באותו מספר דף, כל אחד בפאנל הניתן לגלילה משלו: זוהי הליבה של מציג השוואה. PDFium Component מספק זאת דרך מודל אובייקטים ישיר שבו TPdf מחזיק בקובץ ו-TPdfView מחזיק בתצוגה. מסמך אחד, TPdf אחד, TPdfView אחד. אתה רוצה שלושה פאנלים, יש לך שלושה זוגות. החלקים הקשים הם לא קריאות ה-API; הם החשבון של הפריסה כאשר החלון משנה את גודלו, ולוגיקת סנכרון הדפים כאשר אתה מחליט איזו תצוגה צריכה לעקוב אחרי איזו

פריסת טופס (Form Layout)

טופס ה-VCL מחזיק שלושה מכלי TScrollBox זה לצד זה, כל אחד עם TPdfView בתוכו המיושר ל-alClient כך שהוא ממלא את התיבה. שני רכיבי TSplitter יושבים בין התיבות כך שהמשתמש יכול להתאים את רוחבי העמודות בזמן ריצה. סרגל כלים מעל הפאנלים נושא את כפתורי הפתיחה, פקדי הזום, והמתג בין מצב של שתי תצוגות לשלוש תצוגות

מצב של שלוש תצוגות הוא בוליאני שהטופס עוקב אחריו פנימית. כשהוא משתנה, אתה מחשב מחדש רוחבים ומציג או מסתיר את העמודה השלישית. הגישה הפשוטה ביותר היא לנקות את כל מאפייני ה-Align, להסתיר את ה-splitters, ואז להגדיר מיקומים מוחלטים:

procedure TFormMain.UpdateLayout;
var
  TotalWidth: Integer;
begin
  TotalWidth := ClientWidth;

  if ThreeViewMode then
  begin
    ScrollBox3.Visible := True;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 3;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth div 3;
    ScrollBox3.Left   := ScrollBox2.Left + ScrollBox2.Width;
    ScrollBox3.Width  := TotalWidth - ScrollBox3.Left;
    // החל את אותו (ClientHeight - גובה סרגל כלים) על כל שלושת ערכי ה-Height
  end
  else
  begin
    ScrollBox3.Visible := False;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 2;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth - ScrollBox2.Left;
  end;
end;

הגדרת Align := alNone על כל שלוש התיבות לפני חשבון המספרים השלמים (integer arithmetic) מונעת ממנוע האילוצים של VCL להילחם בהשמות שלך. שחזר את הנראות של ה-splitters לאחר המיקום אם אתה רוצה שינוי גודל בגרירה (drag-to-resize) במצב של שתי תצוגות

הגובה של כל תיבת גלילה הוא אזור הלקוח (client area) פחות גובה פאנל סרגל הכלים. מכיוון שסרגל הכלים מעוגן למעלה עם alTop, ה-ClientHeight - PanelButtons.Height נותן לך את המרווח האנכי השמיש. הקצה זאת לכל שלוש התיבות בתוך אותה קריאת UpdateLayout כדי שלעולם לא תהיה מסגרת שבה תיבה אחת גבוהה מהאחרות וגורמת להבהוב (flicker) של הפריסה

פתיחת מסמך

כל זוג פאנלים זקוק לשגרת פתיחה (open procedure) משלו. התבנית קצרה: השבת את הרכיב, הגדר את שם הקובץ, נסה להפעיל (activate), ותפוס EPdfError אם הקובץ דורש סיסמה. שים לב ש-TPdfView.Active הוא מה ששולט ברינדור, אך TPdf.Active הוא מה שפותח את הקובץ בפועל; הם בלתי תלויים. הגדרת PdfView.Active := True כאשר ה-TPdf המקושר שלו עדיין לא פעיל אינה מזיקה אך לא מציגה דבר

procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
  PdfViewComponent: TPdfView);
var
  Password: string;
begin
  if not OpenDialog.Execute then
    Exit;

  PdfComponent.Active   := False;
  PdfComponent.FileName := OpenDialog.FileName;
  PdfComponent.Password := '';

  try
    PdfComponent.Active := True;
  except
    on E: EPdfError do
    begin
      if InputQuery('Password', 'Enter document password:', Password) then
      begin
        PdfComponent.Password := Password;
        PdfComponent.Active   := True;
      end
      else
        raise;
    end;
  end;

  if PdfComponent.Active then
  begin
    PdfViewComponent.PageNumber := 1;
    SetActivePdfView(PdfViewComponent);
  end;
end;

בדוק תמיד את PdfComponent.Active לאחר ההקצאה; קובץ פגום או סיסמה שגויה גורמים לטעינה להיכשל בשקט מבלי להעלות חריגה (exception) בנתיב ברירת המחדל. הגדרת PdfViewComponent.PageNumber := 1 במפורש לאחר פתיחה מוצלחת מונעת מספר דף ישן מהמסמך הקודם

קוד הטיפול בסיסמה שלמעלה מעלה שגיאה על כל שגיאה שאינה הודעת הסיסמה הידועה. זה מכוון: אתה רוצה שקבצים פגומים או שאינם נתמכים יצופו מיד במקום להיבלע כפאנל ריק ושקט. משתמש שאינו רואה דבר אין לו מושג האם הקובץ נטען והוא פשוט ריק, או האם הרכיב דחה אותו. העלאת השגיאה משאירה אותה גלויה

מעקב אחר פאנל פעיל

כאשר המשתמש לוחץ בתוך פאנל, פאנל זה הופך לפעיל. הטופס עוקב אחר שדה פרטי של FActivePdfView: TPdfView. המשוב החזותי הוא שינוי צבע גבול על ה-TScrollBox המכיל: הגדר אותו ל-clHighlight עבור זה הפעיל ו-clWindow עבור האחרים. חבר זאת לכל TPdfView.OnClick ולשגרת הפתיחה כך שהמיקוד (focus) יעקוב אחר המסמך שזה עתה פתחת

פעולות מסוימות חלות על כל הפאנלים הגלויים במקום רק על הפעיל. בוליאני בשם FAllViewsMode על הטופס מניע את הענף הזה. כאשר הוא אמת, שינויי זום וניווט דפים מתפשטים לכל פאנל שיש לו מסמך פעיל:

procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
  if PdfView1.Active then PdfView1.Zoom := NewZoom;
  if PdfView2.Active then PdfView2.Zoom := NewZoom;
  if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;

ניווט דפים מסונכרן

ניווט מסונכרן הוא אופציונלי אך שימושי עבור זרימות עבודה של עדכון מסמכים שבהן שני הקבצים מכסים את אותו טווח דפים. הלוגיקה שייכת לטפלן אירועים (event handler) שמופעל לאחר שהמשתמש מנווט בתצוגה אחת. כאשר תצוגת מקור משנה את ה-PageNumber שלה, הטפלן מעביר את המספר הזה לתצוגות האחרות, בכפוף לשומר אחד: תצוגת היעד חייבת לכלול לפחות מספר דפים זה, אחרת דלג

ה-PageNumber ב-TPdfView וב-TPdf הם בלתי תלויים. TPdf.PageNumber עוקב אחר איזה דף רכיב המסמך מחשיב כנוכחי; TPdfView.PageNumber עוקב אחר מה שמוצג על המסך. למטרות ניווט, אתה רוצה את מאפיין התצוגה, לא את מאפיין המסמך

תיבת סימון עם תווית כמו "סנכרן דפים" מעניקה למשתמש שליטה. כשהיא אינה מסומנת, כל פאנל מנווט באופן עצמאי והטפלן יוצא מיד. העצמאות הזו חשובה עבור מקרי שימוש שבהם לשני המסמכים יש מספרי דפים שונים, או כאשר המשתמש רוצה למצוא את הקטע המקביל בתרגום שמתחיל בדף אחר. אילוץ סנכרון תמיד יהפוך את הכלי לקשה יותר לשימוש מאשר סידור פשוט של שני חלונות בשולחן העבודה

דבר אחד שיש לשים לב אליו: קביעת PdfView.PageNumber בצורה תכנותית (programmatically) בתוך טפלן הסנכרון תעורר בעצמה את אירוע השינוי על תצוגה זו. הגן מפני רקורסיה אינסופית עם דגל בוליאני שאתה מגדיר לפני ההקצאה ומנקה מיד לאחריה. הדגל הוא פר-טופס, לא פר-תצוגה, מכיוון שכל שלוש התצוגות חולקות את אותו טפלן

זום לכל פאנל

כל TPdfView נושא מאפיין Zoom משלו, ערך Double באחוזים שבו Zoom := 100 פירושו גודל ממשי (100%). הגדרתו עוקפת (overrides) כל FitMode פעיל. עבור כפתור "התאם לרוחב" בפאנל הפעיל, קרא את זום ההתאמה מ-PdfView.PageWidthZoom[PdfView.PageNumber] והקצה אותו. עבור "התאם לדף", השתמש ב-PageZoom[PageNumber]. שניהם הם מאפייני מערך המאונדקסים על ידי מספר דף מבוסס-1, לכן היזהר ממספר דף אפס לפני שניגשים אליהם

כאשר אתה מייצא את הדף הנוכחי לתמונה, קרא את הסיבוב מהתצוגה אך קרא ל-RenderPage ברכיב ה-TPdf, לא בתצוגה. תצורת מפת הסיביות של TPdf.RenderPage מקבלת מידות פיקסלים מפורשות יחד עם ערך TRotation וקבוצת TRenderOptions. חלופת הפונקציה מחזירה TBitmap בבעלות הקורא שאתה משחרר בעצמך לאחר השמירה:

procedure TFormMain.SaveActiveViewAsImage;
var
  Pdf: TPdf;
  Bmp: TBitmap;
  Jpeg: TJpegImage;
begin
  if not Assigned(FActivePdfView) or not FActivePdfView.Active then
    Exit;

  Pdf := FActivePdfView.Pdf;
  Pdf.PageNumber := FActivePdfView.PageNumber;

  Bmp := Pdf.RenderPage(
    0, 0,
    Round(Pdf.PageWidth * 2),
    Round(Pdf.PageHeight * 2),
    FActivePdfView.Rotation, [], clWhite);
  try
    if SavePictureDialog.Execute then
    begin
      Jpeg := TJpegImage.Create;
      try
        Jpeg.Assign(Bmp);
        Jpeg.CompressionQuality := 90;
        Jpeg.SaveToFile(SavePictureDialog.FileName);
      finally
        Jpeg.Free;
      end;
    end;
  finally
    Bmp.Free;
  end;
end;

המכפיל 2x על הרוחב והגובה נותן פלט חד יותר למסמכים עם טקסט עדין. ה-try/finally מסביב לשחרור מפת הסיביות אינו אופציונלי; ביטול של TSaveDialog עדיין מגיע לבלוק ה-finally, ואתה רוצה שמפת הסיביות תשוחרר ללא קשר למה שהמשתמש עשה

דרישות DLL

PDFium Component עוטף את הספרייה המקורית (native) pdfium. תהליך מארח (host process) של 32 סיביות צריך את pdfium32.dll; מארח של 64 סיביות צריך את pdfium64.dll. וריאציות עם מנוע ה-JavaScript מסוג V8 מוסיפות את הסיומת v8 ושוקלות בערך 23-27 מגה-בייט לעומת ה-5-6 מגה-בייט של בניות (builds) הסטנדרט. עבור מציג השוואה שמנטרל מילוי טפסים (Pdf.FormFill := False), בניית הסטנדרט ללא V8 מספיקה ושומרת על ההפצה קטנה יותר

הנח את ה-DLL באותה ספרייה של קובץ ההפעלה (executable), או בכל ספרייה שנמצאת ב-PATH של המערכת. הרכיב טוען אותו לפי דרישה כאשר ה-TPdf הראשון מופעל, כך ש-DLL חסר צף בנקודה זו ולא בהתחלת היישום. אם אתה מספק מתקין, הגישה האמינה ביותר היא להעתיק את ה-DLL לתיקיית היישום במהלך ההתקנה במקום להסתמך על ספריית מערכת שמנהל מערכת עשוי לנקות מאוחר יותר

בניות ה-V8 שימושיות בעיקר כאשר אתה צריך לקיים אינטראקציה עם פעולות PDF JavaScript, למשל כדי להפעיל שדות חישוב או טפלני שליחה. למציג השוואה פסיבי אין שום סיבה להריץ JavaScript; הגדרת Pdf.FormFill := False לפני Active := True מדלגת לחלוטין על סביבת מילוי הטפסים, מה שאומר גם שאף מנוע JS לא מאותחל גם אם משתמשים בבניית הסטנדרט. זוהי ברירת המחדל הנכונה עבור מציג לקריאה-בלבד ללא קשר לאיזו גרסת DLL אתה מפיץ

לפרטים נוספים על רכיב ה-PDFium Component וה-API המלא שלו, בקר בדף המוצר Delphi PDFium Component