مقاله فنی

مقیاس‌بندی صفحات PDF به ۷۰٪ با losLab PDF Library

ابعاد صفحه PDF در زمان ایجاد صفحه ثابت می‌شوند، بنابراین نمی‌توانید محتوا را به سادگی در همانجا مانند تغییر اندازه یک تصویر مقیاس‌بندی کنید. مدل کتابخانه‌ای که کوچک‌سازی را عملی می‌سازد، جذب‌ورسم‌مجدد (capture-and-redraw) است: محتوای هر صفحه را از سند به داخل یک دستگیره (handle) بردارید، یک صفحه خالی جدید در اندازه اصلی رسانه ایجاد کنید، سپس محتوای جذب‌شده را دوباره با یک کادر محدودکننده کوچک‌تر رسم کنید. فضای سفید اطراف تبدیل به حاشیه می‌شود. برای مثال، در مقیاس ۷۰٪ روی یک صفحه A4، ۱۵٪ از عرض در هر طرف و همان کسر در بالا و پایین قرار می‌گیرد، که دقیقاً همان چیزی است که محاسبات مرزی زیر تولید می‌کند

نحوه کار CapturePage

تابع CapturePage یک شماره صفحه می‌گیرد، محتوای آن صفحه را به یک شیء ضبط درون‌حافظه‌ای ارتقا می‌دهد و صفحه را از درخت صفحه سند حذف می‌کند. این حذف عمدی است و دلیل آن است که حلقه همیشه صفحه ۱ را صرف نظر از شاخص تکرار انتخاب می‌کند: وقتی صفحه ۱ جذب و حذف شد، آنچه صفحه ۲ بود تبدیل به صفحه ۱ جدید می‌شود، و به همین ترتیب. اگر انتخاب‌گر صفحه را همراه با شمارنده حلقه افزایش دهید، از هر صفحه یکی در میان عبور خواهید کرد و به نصف خروجی مورد انتظار خواهید رسید

دستگیره ضبط بازگردانده شده توسط CapturePage یک ارجاع صفحه نیست؛ بیشتر شبیه یک اسنپ‌شات محتوا است. این دستگیره تا زمانی که DrawCapturedPage را فراخوانی کنید یا آن را صراحتاً آزاد کنید معتبر باقی می‌ماند. DrawCapturedPage آن دستگیره را به علاوه یک مستطیل مقصد که به عنوان افست چپ، افست پایین، عرض و ارتفاع، همگی در واحد point داده شده‌اند، می‌گیرد. کتابخانه محتوای جذب‌شده را به گونه‌ای مقیاس‌بندی می‌کند که دقیقاً در آن مستطیل جا بگیرد، و نسبت ابعاد را تنها در صورتی حفظ می‌کند که مستطیل شما اتفاقاً با تناسبات اصلی مطابقت داشته باشد. برای مقیاس‌بندی یکنواخت شما می‌خواهید مستطیل اندازه اصلی ضرب در ضریب مقیاس، و متمرکز در صفحه باشد

ریاضیات مرکزگذاری

با ضریب مقیاس ۷۰٪، ۳۰٪ باقیمانده از هر بعد به طور مساوی بین دو طرف تقسیم می‌شود. بنابراین درج افقی pageWidth * (1.0 - 0.70) / 2 است که ۱۵٪ از عرض است، و درج عمودی از همان فرمول با استفاده از ارتفاع صفحه پیروی می‌کند. مستطیل مقصد برای DrawCapturedPage سپس از (horizBorder, vertBorder) شروع می‌شود و pageWidth - 2 * horizBorder در pageHeight - 2 * vertBorder را پوشش می‌دهد. این محاسبات مختص کتابخانه نیست؛ این صرفاً هندسه قرار دادن یک مستطیل کوچک‌تر به طور متقارن در داخل یک مستطیل بزرگ‌تر است

یک نکته شایان ذکر: SetOrigin(1) مبدأ مختصات را به جای پایین-چپ در بالا-چپ قرار می‌دهد. مقادیر حاشیه‌ای که به DrawCapturedPage ارسال می‌کنید از هر مبدئی که تنظیم کرده‌اید اندازه‌گیری می‌شوند، بنابراین اگر حالت‌های مبدأ را بین بارگیری و رسم تغییر دهید، مرکزگذاری به هم خواهد خورد

نمونه C#

کد زیر هر صفحه از Pages.pdf را از طریق چرخه جذب‌ورسم‌مجدد پردازش می‌کند و نتیجه را در newpages.pdf می‌نویسد. PDFL شیء پوشاننده ActiveX/COM است که از PDFlibDLL64.dll به پروژه اضافه شده است

private void ScalePages_Click(object sender, EventArgs e)
{
    File.Delete("newpages.pdf");

    double pageWidth, pageHeight, horizBorder, vertBorder;
    double scaleFactor = 0.70;
    int capturedPageId, ret;

    PDFL.LoadFromFile("Pages.pdf", "");
    PDFL.SetOrigin(1);

    int numPages = PDFL.PageCount();

    for (int i = 1; i <= numPages; i++)
    {
        // Always select page 1: CapturePage removes the page, so page 2
        // becomes page 1 on the next iteration.
        PDFL.SelectPage(1);

        pageWidth  = PDFL.PageWidth();
        pageHeight = PDFL.PageHeight();

        horizBorder = pageWidth  * (1.0 - scaleFactor) / 2;
        vertBorder  = pageHeight * (1.0 - scaleFactor) / 2;

        capturedPageId = PDFL.CapturePage(1);

        PDFL.NewPage();
        PDFL.SetPageDimensions(pageWidth, pageHeight);

        ret = PDFL.DrawCapturedPage(
            capturedPageId,
            horizBorder, vertBorder,
            pageWidth  - 2 * horizBorder,
            pageHeight - 2 * vertBorder);
    }

    PDFL.SaveToFile("newpages.pdf");
}

نمونه Delphi

نسخه Delphi از TPDFlib به طور مستقیم و نه از طریق لایه COM استفاده می‌کند، اما توالی فراخوانی یکسان است. یک تفاوت عملی محافظ فایل خروجی است: FileExists به علاوه DeleteFile به جای File.Delete، زیرا SaveToFile در صورتی که مقصد توسط اجرای قبلی که هنوز در یک نمایشگر باز است قفل شده باشد، با شکست مواجه می‌شود

procedure TForm1.ScalePagesClick(Sender: TObject);
var
  PDFLib: TPDFlib;
  pageWidth, pageHeight, horizBorder, vertBorder: Double;
  scaleFactor: Double;
  capturedPageId, ret, numPages, i: Integer;
begin
  if FileExists('newpages.pdf') then
    DeleteFile('newpages.pdf');

  scaleFactor := 0.70;

  PDFLib := TPDFlib.Create;
  try
    PDFLib.LoadFromFile('Pages.pdf', '');
    PDFLib.SetOrigin(1);

    numPages := PDFLib.PageCount();

    for i := 1 to numPages do
    begin
      PDFLib.SelectPage(1);

      pageWidth  := PDFLib.PageWidth();
      pageHeight := PDFLib.PageHeight();

      horizBorder := pageWidth  * (1.0 - scaleFactor) / 2;
      vertBorder  := pageHeight * (1.0 - scaleFactor) / 2;

      capturedPageId := PDFLib.CapturePage(1);

      PDFLib.NewPage();
      PDFLib.SetPageDimensions(pageWidth, pageHeight);

      ret := PDFLib.DrawCapturedPage(
        capturedPageId,
        horizBorder, vertBorder,
        pageWidth  - 2 * horizBorder,
        pageHeight - 2 * vertBorder);
    end;

    PDFLib.SaveToFile('newpages.pdf');
  finally
    PDFLib.Free;
  end;
end;

ضریب مقیاس واقعاً چه چیزی را کنترل می‌کند

مقدار 0.70 در اینجا به این معنی است که محتوای رندر شده ۷۰٪ از هر بعد صفحه را اشغال می‌کند، نه اینکه فایل ۷۰٪ از اندازه بایت اصلی خود است. اندازه فایل پس از این عملیات به پیچیدگی محتوای اصلی بستگی دارد؛ صفحه‌ای با تصاویر بزرگ به نسبت کوچک نمی‌شود زیرا داده‌های پیکسل در همان رزولوشن به ناحیه کوچک‌تری دوباره رسم می‌شوند. اگر فشرده‌سازی در سطح بایت هدف باشد، رویکرد صحیح LinearizeFile یا ذخیره مجدد با فشرده‌سازی جریان است، نه مقیاس‌بندی هندسی

رقم ۷۰٪ نیز یک محدودیت سخت نیست. هر مقداری بین 0.0 و 1.0 کار می‌کند، و مقادیر بالای 1.0 محتوا را فراتر از مرز اصلی صفحه بزرگ می‌کند، که در لبه جعبه رسانه برش می‌خورد مگر اینکه ابعاد صفحه را نیز افزایش دهید. اسناد با اندازه مختلط به طور طبیعی کنترل می‌شوند زیرا PageWidth و PageHeight برای هر صفحه قبل از محاسبه حاشیه پرس‌وجو می‌شوند، بنابراین سندی که صفحات فرد آن A4 و صفحات زوج آن A3 هستند، خروجی به درستی متمرکز شده روی هر اندازه صفحه بدون هیچ مورد خاصی تولید می‌کند

جاهایی که ممکن است اشتباه پیش بیاید

دو حالت شکست در عمل ظاهر می‌شوند. اولی یک فایل خروجی است که در یک نمایشگر PDF از اجرای قبلی باز مانده است: SaveToFile با شکست مواجه می‌شود یا بسته به پلتفرم صفر بایت می‌نویسد، و خروجی جدید هرگز فرود نمی‌آید. محافظ حذف فایل در بالای تابع این موضوع را برای توسعه مدیریت می‌کند، اما در یک خط لوله تولید، نوشتن در یک مسیر موقت و تغییر نام در صورت موفقیت ایمن‌تر است

دومی عدم تطابق تعداد صفحات است. از آنجایی که CapturePage صفحات را در حین پردازش از سند حذف می‌کند، تعدادی که از PageCount() قبل از حلقه می‌خوانید حد صحیح برای تکرار در برابر آن است. فراخوانی PageCount() در داخل حلقه عدد کاهنده‌ای را در هر عبور برمی‌گرداند و زودتر خارج می‌شود و صفحات آخر را پردازش‌نشده می‌گذارد. متغیر حلقه در مثال‌ها تنها به عنوان یک شمارنده تکرارهای باقیمانده عمل می‌کند؛ هرگز برای انتخاب یک صفحه استفاده نمی‌شود، زیرا صفحه‌ای که باید انتخاب شود به دلیلی که قبلا توضیح داده شد همیشه ۱ است

فراخوانی‌های دستکاری صفحه نشان داده شده در اینجا، از جمله CapturePage، DrawCapturedPage و SetPageDimensions، بخشی از losLab PDF Library برای Delphi، C#، VB.NET و C++ هستند