تفكيك ملف PDF باستعمال PDFLang
على الشاشة يبدو ملف PDF كتلة مسطحة واحدة، صورةً لصفحة. وهو ليس كذلك. إنه صندوق قطع، ومعظم ما يسأله الناس عن ملف — هل سيُطبع كما ينبغي؟ هل ينقصه شيء؟ — هو في حقيقته سؤال عن واحدة من تلك القطع.
يمنحك PDFLang خمس طرق للوصول إلى الداخل. أربع منها موجودة أصلًا حين يبدأ نصك البرمجي، والخامسة ترسمها أنت.
المستند
Section titled “المستند”يبدأ كل نص برمجي بمتغير اسمه doc. لا شيء تفتحه ولا شيء تحمّله: doc هو الملف
الجاري فحصه.
check "ما الذي أنظر إليه" { print("الملف:", doc.filename) print("الصفحات:", doc.page_count) print("العنوان:", doc.title)}يأتي doc.title وdoc.author من البيانات الوصفية للملف — وهي المعلومات التي
يحملها الـ PDF عن نفسه ولا تراها على الصفحة أبدًا. وكثيرًا ما تكون فارغة، لأن من
صدّر الملف لم يملأها قط. وهذا وحده يستحق فحصًا:
check "الملف يقول ما هو" { assert doc.title != "", "لا يحمل الـ PDF عنوانًا في بياناته الوصفية"}ويحمل doc كذلك ثلاث قوائم: doc.pages وdoc.fonts وdoc.images. وهي قوائم
عادية، فكل ما تفعله بقائمة تفعله بها.
الصفحات
Section titled “الصفحات”يعطيك doc.pages الصفحات واحدة واحدة. وكل صفحة تعرف مقاسها، وكمية الحبر التي
تحملها، وأي مربعاتها معرَّف.
المقاسات بالنقاط، وهي الوحدة التي يفكر بها الـ PDF: ٧٢ نقطة في البوصة، فتكون
صفحة A4 ٥٩٥ × ٨٤٢. لكنك لست مضطرًا للتفكير بالنقاط — اكتب 210mm أو 8.5in
وتعمل المقارنة:
check "كل الصفحات A4" { doc.pages.each { |page| assert page.width > 200mm, "الصفحة #{page.number} ضيقة أكثر من اللازم: #{page.width}pt" }}انتبه إلى page.number. الصفحات تُعدّ من ١، كما في الحديث العادي، فيكون الرقم
الظاهر في رسالة الفشل هو الصفحة التي سيفتحها الإنسان فعلًا.
ثم هناك المربعات. الصفحة الجاهزة للطباعة تحمل عدة مستطيلات تحدد ما يُفعل بها: TrimBox هو موضع القص، وBleedBox هي المساحة الأكبر قليلًا حيث يجب أن يتجاوز اللون خط القص، حتى لا يترك انحرافٌ بمقدار ملّيمتر حافةً بيضاء. والملف الخالي منها قد يبدو مثاليًا على الشاشة ويظل غير صالح في المطبعة:
check "جاهز للمقصلة" { doc.pages.each { |page| assert page.has_trim_box, "الصفحة #{page.number} بلا TrimBox" assert page.has_bleed_box, "الصفحة #{page.number} بلا BleedBox" }}وتُبلغ الصفحة أيضًا عن page.tac: مقدار الحبر في أثقل موضع فيها، بالنسبة
المئوية. أربعة أحبار بكامل قوتها تعني ٤٠٠٪، ولا تقبلها أي مطبعة؛ و٣٠٠٪ سقف شائع.
الخطوط
Section titled “الخطوط”يسرد doc.fonts الخطوط التي يستعملها الملف، ويجيب كل خط عن سؤال واحد مهم: هل هو
مضمَّن؟
الخط المضمَّن يسافر داخل الـ PDF. أما الخط غير المضمَّن فمذكور بالاسم فقط، ويستبدله الجهاز الذي يفتح الملف بما لديه. النص لا يختفي — بل يتغير شكله. تتبدل مواضع قطع الأسطر، ويصير عنوان كان يسع سطرًا واحدًا في سطرين، ولا ينتبه أحد حتى تتم الطباعة.
check "كل خط يسافر مع الملف" { doc.fonts.each { |font| assert font.is_embedded, "الخط '#{font.name}' غير مضمَّن" }}يعطيك doc.images كل صور الملف، وpage.images صور صفحة بعينها. والخاصية التي
يستحق فهمها هي dpi.
DPI هي النقاط في البوصة — مقدار التفصيل في كل بوصة مطبوعة. ولا يُبلغ PDFLang عن الرقم المخزَّن في البيانات الوصفية للصورة، بل عن الدقة الفعلية: البكسلات التي تملكها الصورة حقًا مقسومةً على المقاس الذي تُطبع به. مُدّ صورة من ١٠٠٠ بكسل على ٢٠ سنتيمترًا فتصبح صورة منخفضة الدقة مهما ادّعت عن نفسها. وهذا هو الرقم الذي يقرر إن كانت الطباعة ستخرج حادة.
check "حادة بما يكفي للطباعة" { doc.images.each { |img| assert img.dpi >= 300, "صورة في الصفحة #{img.page_number}: #{img.dpi} DPI، والمطلوب 300" }}وimg.color_space هي الخاصية الأخرى التي يقصدها الناس. الطباعة الأوفست تعمل
بـ CMYK؛ والصورة التي ما تزال RGB تحتاج إلى تحويل، والتحويل يزيح الألوان.
والأفضل اكتشاف ذلك قبل تجهيز الألواح.
المناطق التي ترسمها بنفسك
Section titled “المناطق التي ترسمها بنفسك”الأربع السابقة تأتي مع الملف، أما الخامسة فلا: الـ region مستطيل تعرّفه أنت، لتسأل عن جزء من الصفحة بدل الصفحة كلها.
tadhyil = region(0, 0, 595, 60, "التذييل")sharit = region(20mm, 250mm, 60mm, 15mm, "شريط التحذير")الشيء الوحيد الذي عليك تذكّره هو موضع نقطة الأصل. في الـ PDF، الإحداثي 0, 0 هو
الزاوية السفلية اليسرى للصفحة لا العلوية. والمحور Y ينمو صعودًا.
المناطق هي ما يتيح كتابة القواعد التي تهمّ العمل فعلًا. ليس «هل يحتوي هذا الملف على النص القانوني» بل «هل النص القانوني داخل الشريط المخصص له»:
check "شريط التحذير" { sharit = region(0, 700, 595, 142, "الشريط") muhtawa = text::extract_from_region(1, sharit) assert muhtawa.contains("يُصرف بوصفة طبية"), "الشريط ينقصه النص الإلزامي"}والمناطق تُقارن أيضًا. يخبرك tadhyil.intersects(ras) إن كانت منطقتان
متداخلتين، وهكذا تُمسك عنصرًا تسلّل إلى مساحة محجوزة لغيره. ويعطيك
tadhyil.expand(5mm) وtadhyil.inset(3mm) نسخة أوسع أو أضيق، من أجل التسامح.
إلى أين بعد ذلك
Section titled “إلى أين بعد ذلك”يسرد فصل أنواع المستند كل خاصية وكل دالة للأنواع الخمسة، مع أمثلة لكل منها. وهو مادة مرجعية — راجعه حين تحتاج شيئًا بعينه، لا من أوله إلى آخره.
وإن أردت رؤية الخمسة تعمل معًا في أعمال حقيقية، فإن الوصفات العملية والأمثلة هي المكان.