أبعاد صفحة PDF تكون ثابتة عند إنشاء الصفحة، لذلك لا يمكنك ببساطة إعادة تحجيم المحتوى في مكانه كما تفعل مع صورة. النموذج العملي الذي يجعل التصغير ممكنًا هو الالتقاط ثم إعادة الرسم: نرفع محتوى كل صفحة من المستند إلى معرّف مؤقت، ننشئ صفحة فارغة جديدة بالحجم الأصلي للوسائط، ثم نرسم المحتوى الملتقط داخل مستطيل أصغر. وتتحول المساحة البيضاء المحيطة إلى هوامش. فعند التحجيم إلى 70% في صفحة A4 مثلًا، يقع 15% من العرض على كل جانب، وينطبق الكسر نفسه على الأعلى والأسفل، وهذا هو بالضبط ما تنتجه الحسابات التالية
كيف تعمل CapturePage
CapturePage تأخذ رقم صفحة، وتحول محتوى تلك الصفحة إلى كائن التقاط داخل الذاكرة، ثم تزيل الصفحة من شجرة صفحات المستند. وهذا الحذف مقصود، وهو السبب في أن الحلقة تختار الصفحة 1 دائمًا بغض النظر عن فهرس التكرار: فبمجرد التقاط الصفحة 1 وحذفها، تصبح الصفحة 2 هي الصفحة 1 الجديدة، وهكذا. وإذا زدت محدد الصفحة مع عداد الحلقة فسوف تتخطى كل صفحة ثانية وتنتهي بنصف الناتج المتوقع
المعرّف الذي تُرجعه CapturePage ليس مرجعًا لصفحة، بل أقرب إلى لقطة للمحتوى. ويبقى صالحًا حتى تستدعي DrawCapturedPage أو تحرره صراحة. وتأخذ DrawCapturedPage ذلك المعرّف مع مستطيل هدف يُعطى على شكل إزاحة من اليسار، وإزاحة من الأسفل، وعرض، وارتفاع، وكلها بوحدة النقاط. وتقوم المكتبة بتحجيم المحتوى الملتقط ليلائم ذلك المستطيل تمامًا، مع الحفاظ على نسبة الأبعاد فقط إذا كان المستطيل يطابق النسب الأصلية صدفة. وللتحجيم الموحد تريد أن يكون المستطيل هو الحجم الأصلي مضروبًا في عامل التحجيم، ومتمركزًا في الصفحة
حساب التوسيط
عند استخدام عامل تحجيم 70% يُقسَّم الـ30% المتبقية من كل بُعد بالتساوي بين الجانبين. لذلك تكون الإزاحة الأفقية الداخلية هي pageWidth * (1.0 - 0.70) / 2، أي 15% من العرض، وتتبع الإزاحة العمودية الصيغة نفسها باستخدام ارتفاع الصفحة. ثم يبدأ المستطيل الهدف لـ 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 هنا أن المحتوى المعروض يشغل 70% من كل بُعد في الصفحة، لا أن حجم الملف يصبح 70% من حجمه الأصلي بالبايت. ويعتمد حجم الملف بعد هذه العملية على تعقيد المحتوى الأصلي؛ فالصفحة التي تحتوي على صور كبيرة لن تنكمش بنسبة متناسبة لأن بيانات البكسل يُعاد رسمها بدقة نفسها داخل مساحة أصغر. وإذا كان الهدف هو الضغط على مستوى البايت فالمسار الصحيح هو LinearizeFile أو إعادة الحفظ مع ضغط التدفقات، لا التحجيم الهندسي
كما أن نسبة 70% ليست حدًا صارمًا. فكل قيمة بين 0.0 و1.0 تعمل، والقيم الأعلى من 1.0 تكبّر المحتوى خارج حد الصفحة الأصلي، فيُقص عند حافة مربع الوسائط ما لم تزد أيضًا أبعاد الصفحة. وتُعالج المستندات ذات الأحجام المختلطة طبيعيًا لأن PageWidth وPageHeight تُستدعيان لكل صفحة قبل حساب الحدود، لذلك سيُنتج مستند تكون الصفحات الفردية فيه A4 والصفحات الزوجية A3 إخراجًا مضبوط التوسيط على كل حجم صفحة من دون أي معالجة خاصة
أين يمكن أن يحدث الخطأ
تظهر في التطبيق الحي حالتا فشل أساسيتان. الأولى هي أن يبقى ملف الإخراج مفتوحًا في عارض PDF من تشغيل سابق: فـ SaveToFile سيفشل أو يكتب صفر بايت بحسب المنصة، ولن يظهر الإخراج الجديد. وحارس حذف الملف في أعلى الدالة يعالج هذا أثناء التطوير، لكن في خط إنتاج فعلي يكون الكتابة إلى مسار مؤقت ثم إعادة التسمية عند النجاح أكثر أمانًا
والثانية هي عدم تطابق عدد الصفحات. لأن CapturePage يزيل الصفحات من المستند أثناء معالجته لها، فإن العدد الذي تقرأه من PageCount() قبل الحلقة هو الحد الصحيح للتكرار. أما استدعاء PageCount() داخل الحلقة فسيُرجع عددًا يتناقص في كل مرور ويؤدي إلى الخروج المبكر، تاركًا الصفحات الأخيرة من دون معالجة. ومتغير الحلقة في الأمثلة لا يعمل إلا كعداد للمرات المتبقية، ولا يُستخدم أبدًا لتحديد صفحة، لأن الصفحة التي يجب اختيارها هي دائمًا 1 للسبب الموضح سابقًا
إن استدعاءات معالجة الصفحات المعروضة هنا، بما فيها CapturePage وDrawCapturedPage وSetPageDimensions، هي جزء من مكتبة losLab PDF الخاصة بـ Delphi وC# وVB.NET وC++