مقاله فنی

ساخت یک نمایشگر تعاملی PDF در دلفی با TPDFlibViewer

جاسازی یک پنل خواندن PDF در یک برنامه تجاری دلفی نیازی به نوشتن حلقه رندرینگ، موتور اسکرول یا مدل زوم ندارد. کتابخانه PDF losLab ابزار TPDFlibViewer را ارائه می‌دهد؛ یک کنترل VCL که یک سند را با یک فراخوانی باز می‌کند و ویژگی‌هایی مانند انتخاب متن، جابجایی (panning)، زوم با انتخاب محدوده (marquee zoom)، عکس‌برداری سریع (snapshots)، هایلایت کردن نواحی، هایلایت کردن نتایج جستجوی متنی کامل، تعامل با یادداشت‌ها و چاپ را به عنوان رفتارهای داخلی به همراه دارد. این مقاله پنج مرحله را برای تبدیل یک فرم خالی به یک نمایشگر کاربردی بررسی می‌کند: قرار دادن کنترل، متصل کردن اکشن‌های آماده، افزودن هایلایت جستجو، مدیریت کلیک‌های یادداشت و چاپ

چرا به جای نوشتن حلقه رندرینگ از یک کنترل نمایشگر استفاده کنیم؟

بحث لایه‌بندی در اولویت قرار دارد، زیرا نشان می‌دهد شما در واقع چه چیزی به دست می‌آورید. رندر کردن یک صفحه در یک بیت‌مپ، ۲۰ درصد کار است و آسان است؛ ۸۰ درصد دیگر همه چیزهای اطراف آن است: یک حافظه پنهان (cache) بیت‌مپ صفحه با بودجه آزادسازی، چیدمان اسکرول در میان صفحاتی با اندازه‌های مختلف، حالت‌های زوم که هنگام تغییر اندازه مجدداً محاسبه می‌شوند، تست ضربه (hit-testing) که یک پیکسل ماوس را در هر زوم و چرخشی به مختصات صفحه نگاشت می‌کند، و برنامه‌ریزی مجدد رسم که دچار پرش نمی‌شود. TPDFlibViewer مالک تمام این بخش‌ها است و کار رندرینگ را به همان موتوری که در مقاله رندرینگ چند موتوره توضیح داده شده واگذار می‌کند، بنابراین لایه کنترل و لایه رندرینگ به صورت مجزا قابل تعویض باقی می‌مانند. کد فرم شما با یک کنترل اسکرول‌پذیر ویندوز صحبت می‌کند، نه با یک رندرکننده

چگونه یک نمایشگر PDF را در یک برنامه دلفی جاسازی کنیم؟

تابع TPDFlibViewer.LoadFromFile یک سند را از طریق نمونه داخلی TPDFlib خود کنترل باز می‌کند و در صورت موفقیت مقدار ۱ را برمی‌گرداند، بنابراین حداقل نمایشگر شامل یک فراخوانی سازنده، انتساب 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 را در دلفی بدون کدهای چسبان متصل کنیم؟

یونیت 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 کتابخانه بستگی دارد: با مبدأ 0 یا 3، محور Y از پایین به بالا مانند فضای کاربر بومی PDF حرکت می‌کند، و با مبدأ 2 یا 3، محور X از لبه سمت راست اندازه‌گیری می‌شود. هم‌پوشانی نمایشگر در فضای صفحه به صورت بالا به پایین و چپ‌محور کار می‌کند، بنابراین هر مستطیل نتیجه قبل از ذخیره شدن باید معکوس شود (Y = PageHeight - Bottom، و در صورت نیاز X = PageWidth - Right) — در نظر گرفتن لبه بالایی گزارش‌شده به همان شکل تحت مبدأ پیش‌فرض، هایلایت را در موقعیت قرینه‌سازی‌شده عمودی روی صفحه ترسیم می‌کند. TPDFlibViewer این تبدیل را بر اساس مبدأ فعال در هنگام ثبت هر نتیجه انجام می‌دهد، سپس نگاشت چرخش رو به جلو را در زمان ترسیم اعمال می‌کند، به همین دلیل نوارها در هر زاویه‌ای به درستی قرار می‌گیرند. خود نیمه‌شفافی از API ویندوز AlphaBlend با یک آلفای منبع ثابت استفاده می‌کند، زیرا کانواس VCL هیچ ترکیب آلفای بومی ندارد. APIهای جستجوی متن و هندسه پایه در مقاله جستجوی متن و شمارش عناصر صفحه به طور عمیق پوشش داده شده‌اند

کلیک روی یادداشت‌ها، راهنماها و خروجی گرفتن از فایل‌های پیوست

کامپوننت TPDFlibViewer با یادداشت‌ها (annotations) به عنوان اشیاء تعاملی درجه اول رفتار می‌کند. کلیک کردن روی یک یادداشت غیرلینکی، رویداد OnAnnotClick را با شماره صفحه، ایندکس یادداشت بر پایه ۱ و رشته نوع فرعی (subtype) فراخوانی می‌کند؛ متد AnnotAtPagePoint همان تست ضربه را به صورت برنامه‌نویسی در دسترس قرار می‌دهد و از یادداشت‌های همراه Popup صرف‌نظر می‌کند تا شما همیشه یادداشتی را که کاربر واقعاً می‌بیند دریافت کنید. نگه داشتن ماوس روی یک یادداشت، متن Contents آن را به عنوان راهنمای کنترل (hint) از طریق سوئیچ AnnotHints نشان می‌دهد که به طور پیش‌فرض فعال است و با ویژگی استاندارد ShowHint در VCL همکاری می‌کند. برای یادداشت‌های FileAttachment، متد SaveAnnotAttachmentToFile فایل تعبیه‌شده را روی دیسک می‌نویسد و در صورت موفقیت مقدار ۱ را برمی‌گرداند، که یک جریان کاری کلیک برای ذخیره را در چند خط کد کامل می‌کند

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 دیالوگ چاپ استاندارد ویندوز — انتخاب چاپگر، محدوده صفحه، تعداد کپی، ترتیب صفحات — را نشان می‌دهد و کار را به موتور چاپ کتابخانه می‌سپارد، در حالی که PrintDoc(False) کل سند را بدون هیچ رابط کاربری به چاپگر پیش‌فرض ارسال می‌کند. PrintDocRange به صورت کاملاً برنامه‌نویسی شده با نام چاپگر مشخص، صفحه شروع و پایان، تعداد کپی و پرچم ترتیب کار می‌کند، که نقطه ورود مناسبی برای کارهای چاپ دسته‌ای یا سرورمحور است که از همان نمونه نمایشگر هدایت می‌شوند

دو محدودیت شایسته ذکر صادقانه است. اول اینکه TPDFlibViewer یک کنترل VCL ویندوز است؛ لایه نمایشگر از بیلدهای NOVCL حذف شده است، بنابراین سرویس‌های کنسول و تارگت‌های غیر VCL باید مستقیماً از API رندرینگ زیربنایی TPDFlib استفاده کنند. دوم اینکه استفاده بدون رابط کاربری (headless) به طور تعمدی پشتیبانی می‌شود اما محدود است: متد CaptureRegion هر ناحیه کلاینت — صفحات به علاوه هایلایت‌ها — را به یک بیت‌مپ متعلق به فراخوان‌کننده رندر می‌کند و روی یک کنترل بدون والد و بدون هندل پنجره کار می‌کند، زیرا مسیر رسم از فراخوانی‌های VCL که ایجاد هندل را تحمیل می‌کنند اجتناب می‌کند. این امر تایید سطح پیکسل را در تست‌های خودکار کاربردی می‌کند، اما هر چیزی که واقعاً به یک حلقه پیام نیاز دارد، مانند ابزارهای تعاملی ماوس، همچنان متعلق به یک نشست واقعی رابط کاربری است. در بخش عملکرد، کنترل یک حافظه پنهان صفحه در حافظه را با بودجه قابل تنظیم از طریق SetCacheLimit نگه می‌دارد و می‌تواند صفحات رندر شده را در بین نشست‌ها ذخیره کند؛ مقاله کش صفحه دیسک نمایشگر آن لایه و رفتار DPI هر مانیتور را به تفصیل پوشش می‌دهد

بنابراین پنج مرحله: قرار دادن کنترل و فراخوانی LoadFromFile، متصل کردن اکشن‌های آماده از PDFlibViewerActions، فراخوانی HighlightSearchHits برای جستجوی درون صفحه‌ای، مدیریت OnAnnotClick برای جریان‌های کاری یادداشت، و ارائه PrintDoc. هر تکه‌ای که در اینجا نشان داده شده است در کتابخانه استاندارد losLab PDF Library برای دلفی ارائه می‌شود، جایی که کنترل نمایشگر، کلاس‌های اکشن و موتور رندرینگ در کنار هم نصب می‌شوند