مقاله فنی

رندر صفحات PDF به بیت‌مپ در دلفی با HotPDF

کامپوننت HotPDF یک صفحه PDF بارگذاری‌شده را از طریق یک فراخوانی واحد به TBitmap دلفی رندر می‌کند: RenderLoadedPageToBitmap(PageIndex, DPI). این تابع جریان محتوای صفحه را تفسیر می‌کند و یک بیت‌مپ 24 بیتی RGB متعلق به فراخوان را در رزولوشن انتخابی شما برمی‌گرداند، که دقیقاً همان چیزی است که یک نوار تصاویر بندانگشتی (thumbnail)، پیش‌نمایش چاپ یا خط لوله صادرات PDF به تصویر به آن نیاز دارد. این مقاله ابتدا API را بررسی کرده و سپس به بخشی می‌پردازد که یک رندرکننده کاربردی را از یک اسباب‌بازی متمایز می‌کند: رسم متن از خود برنامه‌های فونت جاسازی‌شده به جای استفاده از فونت‌های سیستمی مشابه

چرا رندر کردن یک صفحه PDF سخت‌تر از رسم یک تصویر است؟

یک صفحه PDF یک تصویر نیست. بلکه یک برنامه است: جریانی از عملگرها که مسیرها را می‌سازند، فونت‌ها را انتخاب می‌کنند، رنگ‌ها را تنظیم کرده و گلیف‌ها را قرار می‌دهند، و در برابر مدل گرافیکی تعریف‌شده در استاندارد ISO 32000-1 §8 اجرا می‌شوند. هیچ چیزی در فایل نمی‌گوید که هر پیکسل چه شکلی دارد. برای تولید یک بیت‌مپ، باید آن برنامه را اجرا کنید — یک ماتریس تبدیل فعلی، یک پشته حالت گرافیکی برای q/Q, یک مسیر برش (clipping path)، فضاهای رنگی پر کردن (fill) و ضخامت خط (stroke) را حفظ کنید — و در نهایت نتیجه را شطرنجی (rasterize) نمایید. به همین دلیل است که عبارت "فقط صفحه 3 را به عنوان یک تصویر نشان بده" در واقع به معنای یک مفسر جریان محتوا است، نه تبدیل فرمت فایل

رندرکننده HotPDF که در نسخه v2.253.0 معرفی شد، از شش بخش مجزا ساخته شده که منعکس‌کننده آن مدل است: هسته ماتریس آفین (affine-matrix) برای جبر تبدیل PDF [a b c d e f]، یک پشته حالت گرافیکی، یک حل‌کننده فضای رنگی (DeviceRGB، DeviceGray، DeviceCMYK، Indexed),یک مسیرساز که عملگرهای مسیر PDF را به GDI پل می‌زند، یک لایه متریک فونت که آرایه‌های /Widths را برای پیشروی صحیح می‌خواند، و مفسری که عملگرها را ارسال کرده و پنج بخش دیگر را هدایت می‌کند. شیء تصویری Image XObjects از همان پشته رمزگشایی که کتابخانه برای استخراج استفاده می‌کند عبور می‌نماید، بنابراین هر فیلتر تصویری که HotPDF می‌تواند برای استخراج رمزگشایی کند — از جمله تصاویر JPEG 2000 فشرده شده با JPXDecode — در خروجی رندر شده نیز ظاهر می‌شود

رندر کردن یک صفحه بارگذاری‌شده به TBitmap

متد RenderLoadedPageToBitmap یک اندیس صفحه مبتنی بر صفر و یک مقدار DPI را دریافت می‌کند، که در آن 72 DPI یک واحد فضای کاربر PDF را به یک پیکسل نقشه‌نگاری می‌کند. این متد در صورت شکست (اندیس خارج از محدوده، کمبود منابع) به جای بروز خطا، مقدار nil را برمی‌گرداند، بنابراین یک نمایشگر می‌تواند از صفحه خراب عبور کرده و به کار خود ادامه دهد. فراخوان مالک بیت‌مپ برگشتی است و باید آن را آزاد کند

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);  // صفحه 1 در 144 DPI
      if Bmp <> nil then
      try
        Image1.Picture.Assign(Bmp);
      finally
        Bmp.Free;  // فراخوان مالک بیت‌مپ است
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

آرگومان DPI کار مقیاس‌بندی را برای هر سناریوی رایجی انجام می‌دهد. یک نوار تصاویر بندانگشتی در 36 یا 48 DPI رندر می‌شود و بیت‌مپ‌های کوچک و سهلی دریافت می‌کند؛ پیش‌نمایش روی صفحه در 96 یا 144 DPI با چگالی صفحه نمایش معمولی مطابقت دارد؛ یک مسیر صادرات در 300 DPI تصاویری با کیفیت چاپ تولید می‌کند. چرخش صفحه از ورودی /Rotate و چرخش مبدا /MediaBox (در PDF مبدا در پایین سمت چپ و در GDI در بالا سمت چپ است) در داخل ماتریس صفحه به دستگاه مدیریت می‌شوند، بنابراین یک صفحه US Letter در 72 DPI دقیقاً به صورت 612×792 پیکسل در جهت صحیح برگردانده می‌شود

چرا تصاویر بندانگشتی رندر شده PDF گلیف‌های اشتباهی را نشان می‌دهند؟

گلیف‌های اشتباه یا تقریبی در خروجی رندر شده PDF تقریباً همیشه به این معنی است که رندرکننده به جای استفاده از فونت جاسازی‌شده در فایل، یک فونت سیستمی را جایگزین می‌کند. اولین رندرکننده HotPDF دقیقاً همین کار را انجام داد: پیشوند زیرمجموعه را از /BaseFont حذف کرد (با تبدیل ABCDEF+Arial به Arial)، از GDI یک فونت سیستمی با آن نام را درخواست کرد و متن را با آن رسم نمود. برای سندی که از Arial یا Times New Roman with standard encoding استفاده می‌کند، نتیجه نزدیک به نظر می‌رسد. اما این یک تقریب است و در موارد مشخصی خراب می‌شود

فونت‌های زیرمجموعه جاسازی‌شده بدترین حالت هستند. یک فونت زیرمجموعه ممکن است فقط شامل چهل گلیفی باشد که سند واقعاً از آن‌ها استفاده می‌کند، با کدهای کاراکتری که با ترتیبی خصوصی برای آن فایل اختصاص داده شده‌اند — برای مثال کد 1 ممکن است "T" باشد، کد 2 "h" و به همین ترتیب. یک فونت سیستمی چیزی درباره این تخصیص خصوصی نمی‌داند، بنابراین متن یا ناپدید می‌شود یا کاملاً به عنوان کاراکترهای اشتباه خارج می‌گردد. رمزگذاری‌های سفارشی، فونت‌های نماد (symbol)، فونت‌های بارکد و هر قلمی که روی ماشین رندر نصب نشده باشد، به همین ترتیب با شکست مواجه می‌شوند. رندرکننده‌ای که به جایگزینی فونت سیستمی بسنده می‌کند، تصاویر بندانگشتی تولید می‌کند که قابل تشخیص هستند — تا زمانی که صفحه از فونت‌هایی استفاده کند که در وهله اول جاسازی را ضروری ساخته بودند

رندر گلیف جاسازی‌شده: رسم از روی خود برنامه فونت

کامپوننت HotPDF این شکاف را در طول پنج انتشار (نسخه‌های v2.268.0 تا v2.272.0) با تجزیه برنامه‌های فونت جاسازی‌شده و بازخوانی خطوط گلیف آن‌ها به عنوان مسیرهای برداری پر شده GDI برطرف کرد. اکنون متن در یک صفحه رندر شده از همان داده‌های خطوط کلی می‌آید که یک نمایشگر منطبق از آن‌ها استفاده می‌کند، به این معنی که فونت‌های زیرمجموعه، رمزگذاری‌های سفارشی و قلم‌های نصب‌نشده با شکل دقیق خود رندر می‌شوند. این پوشش بر اساس نوع فونت ایجاد شده است:

برای فونت‌های Type0/CIDFontType2 با یک برنامه TrueType جاسازی‌شده (FontFile2)، رندرکننده جداول glyf و loca را مستقیماً تجزیه می‌کند: خطوط درجه دوم به منحنی‌های بزیه درجه سوم که GDI می‌فهمد تبدیل می‌شوند، نقاط روی منحنی ضمنی بین نقاط متوالی خارج از منحنی بازسازی می‌شوند و گلیف‌های ترکیبی به صورت بازگشتی اجرا می‌گردند. هر دو طرح‌بندی Identity و جریان صریح CIDToGIDMap پشتیبانی می‌شوند، و پیشروی‌های CID به ورودی‌های عرض /W و /DW احترام می‌گذارند، بنابراین متن دو بایتی Identity-H به درستی گام برمی‌دارد

برنامه‌های CFF (مربوط به FontFile3، خواه CIDFontType0C، Type1C یا یک پوشش OpenType) یک مفسر کامل charstring نوع 2 دریافت می‌کنند: خطوط، منحنی‌ها، خانواده فلکس، ماسک‌های راهنما (hint masks) و فراخوانی‌های زیرروال محلی/سراسری با انحراف زیرروال صحیح. برنامه‌های CFF با کلید CID کدهای کاراکتر را از طریق مجموعه کاراکتر فونت نگاشت می‌کنند، که این موضوع برای فونت‌های زیرمجموعه‌ای که ترتیب گلیف آن‌ها با ترتیب CID متفاوت است اهمیت دارد، و انتخاب دیکشنری فونت برای هر گلیف از طریق FDArray/FDSelect رعایت می‌شود. فونت‌های ساده TrueType (غیر CID) کدهای یک بایتی را از طریق جدول cmap خود فونت جاسازی‌شده با یک زنجیره جدول فرعی قوی حل می‌کنند — ابتدا فرمت‌های یونیکد 4 و 12، سپس جدول‌های فرعی نماد با آینه استفاده خصوصی F000 و سپس فرمت‌های قدیمی مکینتاش — در حالی که فونت‌های ساده Type1 از طریق رمزگذاری داخلی برنامه CFF حل می‌شوند

دو اصلاح تصویر را کامل می‌کنند. اول اینکه، دیکشنری‌های /Encoding فونت ساده بر اساس اولویت تجویزشده در استاندارد ISO 32000-1 §9.6.6 حل می‌شوند: آرایه‌های /Differences رمزگذاری پایه را بازنویسی می‌کنند، که این خود نقشه خود برنامه فونت را بازنویسی می‌نماید — مسیری که زنجیره‌های ابزار مشتق‌شده از TeX و PostScript به آن وابسته هستند، با نام‌های گلیفی که از طریق لیست گلیف ادوبی، مجموعه کاراکتر CFF یا cmap TrueType حل می‌شوند. دوم، فونت‌های Type3 که گلیف‌های آن‌ها خود جریان‌های محتوای کوچکی هستند، از طریق رندرکننده با ماتریس فونت، اندازه فونت و ماتریس متن ترکیب‌شده بازخوانی می‌شوند؛ مقادیر /Widths فضای گلیف از طریق /FontMatrix طبق الزامات استاندارد ISO 32000-1 §9.6.5 تفسیر می‌شوند و پروسه‌های گلیفی که یک جعبه محدودکننده d1 را اعلام می‌کنند به آن محدود می‌شوند، بنابراین یک گلیف بارکد ناقص نمی‌تواند خارج از سلول خود ترسیم شود. هنگامی که یک کد نتواند نقشه‌نگاری شود — یک برنامه آسیب دیده، یک کاراکتر نقشه‌نگاری‌نشده — رندرکننده به جای حذف اجرای متن، به رسم فونت سیستمی برای آن گلیف رجوع می‌کند

چگونه رندرهای مکرر را سریع انجام می‌دهید؟

پاسخی که HotPDF ارائه می‌دهد، یک کش صفحه اخیراً استفاده شده است: متد RenderLoadedPageToBitmapCached تا سقف RenderCacheCapacity صفحات رندر شده (پیش‌فرض 8) را با اندیس صفحه و DPI کلیدگذاری می‌کند و در صورت وجود در کش، یک کپی جدید متعلق به فراخوان را بدون دست زدن به جریان محتوا برمی‌گرداند — که معمولاً هزاران بار سریع‌تر از تفسیر مجدد صفحه است. این الگو دقیقاً مناسب نمایشگرها است: کاربری که بین دو صفحه جابجا می‌شود، یا یک رویداد تغییر اندازه که همان صفحه را با همان DPI دوباره درخواست می‌کند، هر بار به کش برخورد می‌نماید

// نوار تصاویر بندانگشتی: اولین مرحله رندر می‌کند، اسکرول به عقب به کش برخورد می‌کند
for I := 0 to ThumbCount - 1 do
begin
  Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
  if Bmp <> nil then
  try
    ThumbList.AddThumbnail(I, Bmp);
  finally
    Bmp.Free;
  end;
end;

// پس از ویرایش یک صفحه بارگذاری شده در محل:
Pdf.InvalidateRenderedPageCache;  // next render reflects the change

قبل از افزایش ظرفیت، صادقانه هزینه حافظه را بررسی کنید. یک صفحه US Letter در 300 DPI ابعاد 2550×3300 پیکسل دارد که حدود 25 مگابایت به عنوان یک بیت‌مپ 24 بیتی است، بنابراین هشت صفحه کش شده در رزولوشن صادراتی تقریباً 200 مگابایت فضا می‌گیرد. در DPI تصاویر بندانگشتی، همان هشت ورودی کمتر از یک مگابایت هزینه دارد. RenderCacheCapacity را متناسب با DPI واقعی که کش می‌کنید تنظیم کنید و پس از هر بار ویرایش در محل، متد InvalidateRenderedPageCache را فراخوانی نمایید — کش فقط با صفحه و DPI کلیدگذاری می‌شود و نمی‌تواند متوجه تغییر محتوای زیرین شود. بارگذاری یک سند جدید آن را به طور خودکار پاک می‌کند

یک کش دوم در زیر کش صفحه کار می‌کند: شیء تصویری رمزگشایی شده Image XObjects در یک مخزن با بافر بایت محدود شده به ImageCacheMaxBytes (پیش‌فرض 32 مگابایت) با حذف بر اساس کمترین استفاده اخیر نگهداری می‌شود. یک لوگو یا تصویر سربرگ که در هر صفحه تکرار می‌شود، به جای یک بار برای هر عملگر Do، یک بار در هر بارگذاری سند رمزگشایی می‌شود که زمان رندر را برای صفحات با تصویر مشترک تقریباً به نصف کاهش می‌دهد و صادرات TIFF چند صفحه‌ای را به همان میزان سرعت می‌بخشد. متد InvalidateRenderedPageCache این کش را نیز پاک می‌کند

چه چیزهایی هنوز به طور تقریبی رندر می‌شوند

رندرکننده زیرمجموعه متداول اسناد PDF را هدف قرار می‌دهد و ارزش آن را دارد که بدانید مرزهای آن کجاست. فضاهای رنگی CalRGB، Lab و ICC-based به جای مدیریت رنگ، تقریبی محاسبه می‌شوند — فضاهای رنگی دستگاه، پالت‌های Indexed و جستجوهای رنگی تابع نوع 0 نمونه‌برداری شده مدیریت می‌شوند، اما یک فایل تولید چاپ که به اهداف رندر ICC متکی است، از نظر رنگی دقیق نخواهد بود. الگوهای سایه‌زنی (sh) و حالت‌های ترکیب فراتر از آلفای ساده نیز خارج از محدوده هستند و بازگشت Form XObject برای جلوگیری از حلقه، محدود به عمق است. برای فاکتورها، گزارش‌ها، قراردادها و فرم‌ها — صفحاتی ساخته شده از متن، مسیرها و تصاویر — خروجی وفادارانه است؛ اما برای یک سند طراحی پر از گرادیان و گروه‌های شفافیت، با بیت‌مپ به عنوان یک پیش‌نمایش رفتار کنید، نه یک نمونه نهایی

برداشت عملی: اگر خط لوله شما اسنادی را با HotPDF تولید می‌کند یا PDFهای تجاری معمولی را مصرف می‌نماید، متد RenderLoadedPageToBitmap آن‌ها را با اشکال دقیق گلیف‌های جاسازی‌شده، پیشروی‌های صحیح CID و هندسه صحیح صفحه بازخوانی می‌کند. تقریب‌ها در گوشه‌هایی از مدل گرافیکی زندگی می‌کنند که اسناد تجاری به ندرت به آنجا سر می‌زنند

متد RenderLoadedPageToBitmap، نسخه کش شده آن و خط لوله رندر گلیف جاسازی‌شده که در اینجا توضیح داده شد، به عنوان بخشی از کامپوننت HotPDF برای دلفی و C++Builder ارائه می‌شوند — یک کتابخانه بومی VCL بدون وابستگی به DLLهای خارجی، که ایجاد، ویرایش، استخراج متن و رندر صفحات PDF را در یک بسته پوشش می‌دهد