كيف يعمل منطق CSS للطباعة في التحويل من HTML إلى PDF؟
7 دقيقة للقراءة
حين تحوّل صفحة ويب إلى PDF تحصل على نتيجة تختلف عما تراه على الشاشة — وهذا ليس خطأً بل نتيجة طبيعية للانتقال بين نموذجَي تخطيط مختلفين. نشرح في هذا المقال كيف تُقسَّم صفحات الويب إلى صفحات، وقواعد CSS للطباعة، ولماذا يسبب المحتوى الديناميكي مشكلات.
نموذجان للتخطيط
صفحة الويب وسط متدفق (continuous). فلا مفهوم للصفحة فيه؛ بل يتدفق المحتوى من أعلى إلى أسفل كعمود لا نهائي. ويتغير العرض تبعاً لنافذة المتصفح ويتكيف التخطيط معه.
وPDF وسط مقسّم إلى صفحات (paged). فثمة صفحات ثابتة الأبعاد ويُوزَّع المحتوى عليها.
ويفصل CSS بين الاثنين بمفهوم «نوع الوسط» (media type) ويسمح بتطبيق قواعد مختلفة.
media print@: طقمان مختلفان من الأنماط
يمكن لصفحات الويب أن تعرّف أنماطاً مختلفة للشاشة والطباعة:
/* For everyone */
body { font-family: sans-serif; }
/* Screen only */
@media screen {
nav { position: fixed; background: #222; }
}
/* Print only */
@media print {
nav, .sidebar, .ads, .comments { display: none; }
body { font-size: 11pt; color: #000; background: #fff; }
a { text-decoration: underline; }
}
ويستخدم إنتاج PDF قواعد print. وفي المواقع المصممة جيداً يؤدي ذلك إلى تنظيف واضح للمخرجات:
- تُخفى قوائم التنقل والأشرطة الجانبية والإعلانات.
- تصير الألوان بسيطة ومقروءة.
- تُضبط أحجام الخطوط بوحدة النقطة.
لكنه يولّد نوعين من المشكلات أيضاً:
الإخفاء المفرط. إذ تخفي بعض المواقع في نمط الطباعة صناديق معلومات مهمة كذلك.
غياب أي نمط. فالمواقع التي لم تعرّف نمط طباعة تستخدم تخطيط الشاشة كما هو والنتيجة سيئة عادةً — فقد تتكرر القوائم ثابتة الموضع في كل صفحة، وقد تسبب الخلفيات الداكنة مشكلات.
المقابل الفيزيائي لوحدات CSS
في سياق الطباعة تحمل وحدات CSS قيماً فيزيائية دقيقة:
| الوحدة | المقابل الفيزيائي | |---|---| | 1in (بوصة) | 2.54 سم | | 1cm | 1 سم | | 1mm | 1 مم | | 1pt (نقطة) | 1/72 بوصة | | 1pc (بايكا) | 12 نقطة | | 1px (بكسل CSS) | 1/96 بوصة |
والسطر الأخير حاسم: 96 بكسل CSS تساوي بوصة واحدة تماماً.
ونتيجة هذا الثابت: أن عرض صفحة A4 (210 مم = 8.27 بوصة) يصير نحو 794 بكسل CSS قبل طرح الهوامش.
وهذا يُحدث أثراً مهماً في التصاميم المتجاوبة. فكثير من المواقع يستخدم نقاط انكسار كهذه:
@media (max-width: 768px) { /* tablet and mobile layout */ }
@media (max-width: 1024px) { /* small screen layout */ }
و794 بكسلاً دون القاعدة الثانية، وقريبة من الأولى في بعض المواقع. والنتيجة: تنتقل الصفحة في PDF إلى تخطيط مختلف عما تراه على سطح المكتب — فتنزل الأعمدة بعضها تحت بعض، وتتحول القائمة إلى أيقونة همبرغر، وتأخذ الصور العرض الكامل.
وهذا جواب سؤال «لماذا تبدو الصفحة في PDF كأنها للجوال؟».
وسبل الحل: استخدام الاتجاه الأفقي (فـ A4 أفقياً نحو 1123 بكسل)، أو تقليل الهوامش، أو تطبيق تحجيم.
page@: تعريف الصفحة نفسها
تعرّف القاعدة @page في CSS صندوق الصفحة:
@page {
size: A4;
margin: 2cm 1.5cm;
}
ويمكن استخدام مقاسات جاهزة لـ size (مثل A4 وA3 وLetter وLegal) أو أبعاد مخصصة (size: 210mm 297mm). وتغيّر الكلمة المفتاحية landscape الاتجاه.
ويمكن تطبيق قواعد مختلفة على صفحات مختلفة بالأصناف الزائفة:
@page :first {
margin-top: 5cm; /* space at top of cover page */
}
@page :left {
margin-left: 3cm; /* binding margin */
}
@page :right {
margin-right: 3cm;
}
ويُستخدم ذلك لترك هامش تجليد في المخرجات المطبوعة على الوجهين بأسلوب الكتب.
خوارزمية تقسيم الصفحات
يُقطَّع تدفق المحتوى بمقدار ارتفاع الصفحة. وحين تقع نقطة التقسيم في وسط عنصر يضطر المتصفح إلى اتخاذ قرار.
ويقدم CSS خصائص توجّه هذا القرار:
/* Don't split this element */
table, figure, .card { page-break-inside: avoid; }
/* Start a new page before this element */
h1, .chapter { page-break-before: always; }
/* No break right after this element */
h2, h3 { page-break-after: avoid; }
والقاعدة الثالثة مفيدة بوجه خاص: فهي تمنع بقاء عنوان وحيداً في أسفل الصفحة (والنص الذي تحته في الصفحة التالية).
التحكم في الأسطر اليتيمة والأرملة:
p {
orphans: 3; /* at least 3 lines of a paragraph at page bottom */
widows: 3; /* at least 3 lines at page top */
}
وهذه قواعد كلاسيكية في فن الطباعة: فبقاء سطر واحد من فقرة وحيداً في نهاية الصفحة أو بدايتها يبدو رديئاً.
سلوك التقسيم بحسب نوع العنصر:
| العنصر | سلوك التقسيم | |---|---| | كتلة نصية | تُقسَّم عند حدود الأسطر | | جدول | يُقسَّم عند حدود الصفوف | | صورة | لا تقبل التقسيم؛ تنتقل أو تُقتطع | | عنصر مموضَع | نتائج غير متوقعة | | Flexbox / Grid | الدعم متفاوت بحسب المتصفح |
والسطران الأخيران مهمان: فالتموضع المطلق وأنظمة التخطيط الحديثة لا تتصرف تصرفاً حسناً دائماً في سياق الطباعة. وقد يختل تخطيط grid معقد اختلالاً غير متوقع عند حدّ الصفحة.
لماذا لا تُطبع رسوم الخلفية
لا تطبع محركات الطباعة ألوان الخلفية وصورها افتراضياً. وهذا سلوك مضمَّن غايته توفير الحبر.
وثمة طريقة لفرض ذلك في CSS:
* {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
كما يوجد خيار «رسوم الخلفية» في نافذة الطباعة بالمتصفح.
وحين يكون هذا الإعداد مغلقاً تتحول التصاميم داكنة الأرضية إلى كارثة: فالنص الأبيض على أرضية سوداء يصير أبيض على أبيض حين لا تُطبع الأرضية، فلا يظهر شيء.
وإن كنت تُعدّ HTML بنفسك، فقلب الألوان في نمط الطباعة نهج أكثر أماناً:
@media print {
.dark-section { background: #fff; color: #000; }
}
الروابط والتفاعل
الروابط التشعبية تُحوَّل عادةً إلى تعليقات ارتباط في PDF وتبقى قابلة للنقر. ويتوقف ذلك على قدرة المحوّل.
وفي مستند مطبوع لا يظهر إلى أين يذهب الرابط. ويمكن إضافة العنوان إلى النص عبر CSS:
@media print {
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #555;
}
}
وحقول النماذج تتحول عادةً إلى مظهرها الساكن. وتستطيع بعض المحوّلات تحويل نماذج HTML إلى حقول نماذج PDF لكنها ليست خاصية شائعة.
وتفاعلات جافاسكربت تضيع تماماً: القوائم المنسدلة، والألسنة، والأكورديونات. فالمحتوى في الحالة المطوية لا يظهر إطلاقاً في PDF — فإن كان قسم الأسئلة الشائعة على شكل أكورديون، ظهرت الأسئلة وحدها في PDF دون الأجوبة.
ويلزم لهذه الحالة فتح المحتويات المخفية في نمط الطباعة:
@media print {
.accordion-content { display: block !important; }
}
المحتوى الديناميكي ومشكلة التوقيت
يجلب معظم صفحات الويب الحديثة محتواه بعد تحميل الصفحة: استدعاءات API، وصور تُحمَّل بتكاسل، وتمرير لا نهائي.
ويعدّ المحوّل الصفحة «جاهزة» عند نقطة معينة ثم يبدأ إنتاج PDF. وتُستخدم استراتيجيات مختلفة لتحديد هذه النقطة:
- حدث التحميل (load event): عند نزول موارد الصفحة الأولى. وهو غير كافٍ عادةً.
- خمول الشبكة (network idle): حين لا يصل طلب جديد مدة معينة. وهو إشارة أفضل.
- انتظار ثابت: مثل الانتظار 3 ثوانٍ. خشن لكنه بسيط.
- انتظار عنصر: الانتظار إلى أن يظهر محدِّد CSS معين. وهو الأدق لكنه يتطلب تهيئة.
ولا تستطيع أي استراتيجية إعطاء المحتوى كاملاً في الصفحات التي تستخدم التمرير اللانهائي — إذ لا يُحمَّل المحتوى إلا مع تمرير المستخدم، والمحوّل لا يمرّر.
والصور المحمَّلة بتكاسل (lazy loading) تعاني المشكلة نفسها: فالصور التي لا تدخل منطقة العرض لا تُحمَّل إطلاقاً وتبقى فارغة في PDF.
تحميل الخطوط
تُنزَّل خطوط الويب من مصادر خارجية:
@font-face {
font-family: 'Custom';
src: url('https://fonts.example.com/custom.woff2');
}
فإن لم تستطع بيئة التحويل الوصول إلى هذا المصدر لم يُحمَّل الخط وتراجع المتصفح إلى خط احتياطي. ولأن عرض الحروف يتغير ينزاح التخطيط.
كما توجد مشكلة توقيت: فإن أُنتج ملف PDF قبل تحميل الخط، رُسمت الصفحة بالخط الاحتياطي.
والحلول في المستندات الحساسة:
- تضمين الخط في CSS بترميز base64.
- استخدام خطوط النظام.
- إضافة فحص ينتظر تحميل الخط.
الخلاصة
التحويل من HTML إلى PDF انتقال من وسط متدفق إلى وسط مقسّم إلى صفحات، ويدير CSS هذا الانتقال عبر @media print و@page وخصائص تقسيم الصفحات. وتعريف بكسل CSS بأنه 1/96 من البوصة يجعل عرض صفحة A4 نحو 794 بكسلاً ويفضي إلى انتقال التصاميم المتجاوبة إلى تخطيط الجوال. ولا تُطبع رسوم الخلفية افتراضياً؛ وهذه مشكلة جدية في التصاميم داكنة الأرضية. ويمكن توجيه قرارات تقسيم الصفحات بخصائص مثل page-break-inside وorphans وwidows، لكن ينبغي أن تكون هذه القواعد معرّفة في شفرة الصفحة نفسها. وقد يأتي المحتوى الديناميكي ناقصاً تبعاً للحظة التي يقرر فيها المحوّل أن الصفحة «جاهزة» — وفي الصفحات التي تستخدم التمرير اللانهائي والتحميل المتكاسل يتعذر عادةً الحصول على المحتوى كاملاً.
الأسئلة الشائعة
كيف يقابل البكسل في CSS مقداراً فيزيائياً؟
في سياق الطباعة يُعرَّف بكسل CSS بأنه 1/96 من البوصة. أي أن 96 بكسلاً تساوي بوصة واحدة تماماً، أي نحو 2.54 سنتيمتر. وبفضل هذا الثابت يصير عرض صفحة A4 (210 مم) نحو 794 بكسل CSS. وهذا هو سبب انتقال التصاميم المتجاوبة إلى تخطيط الجوال في ملف PDF — إذ إن 794 بكسلاً دون نقطة الانكسار الخاصة بالجهاز اللوحي أو الجوال في كثير من المواقع.
ماذا تفعل قاعدة page@؟
تعرّف الصفحة نفسها: الحجم والاتجاه والهوامش. فمثلاً القاعدة @page { size: A4 landscape; margin: 1.5cm; } تجعل المخرجات بمقاس A4 أفقي وتترك هامشاً 1.5 سم من كل جهة. كما يمكن تطبيق قواعد مختلفة على الصفحة الأولى أو على الصفحات الفردية/الزوجية عبر الأصناف الزائفة :first و:left و:right.
كيف تقرر خوارزمية تقسيم الصفحات؟
يُقطَّع تدفق المحتوى بمقدار ارتفاع الصفحة. فإن وقعت نقطة التقسيم في وسط عنصر، فحص المتصفح أولاً خصائص تقسيم الصفحات (مثل page-break-inside)؛ فإن لم يوجد قيد قسّم العنصر إلى نصفين. وتُقسَّم كتل النص عند حدود الأسطر، والجداول عند حدود الصفوف، أما الصور فلأنها لا تقبل التقسيم فإما أن تنتقل كاملة إلى الصفحة التالية وإما أن تُقتطع.
لماذا لا يصل أحياناً المحتوى المولَّد بجافاسكربت إلى ملف PDF؟
لأن المحوّل يعدّ الصفحة «جاهزة» عند نقطة معينة ثم يبدأ إنتاج PDF. فإذا كان المحتوى يصل بعد تحميل الصفحة عبر طلبات شبكة منفصلة ولم ينتظر المحوّل مدة كافية، أُخذت المخرجات قبل اكتمال تلك الطلبات. وتنتظر بعض الأدوات توقف حركة الشبكة، لكن هذه الحالة لا تتحقق إطلاقاً في الصفحات التي تستخدم التمرير اللانهائي أو التحديث الدوري.
جرّب ذلك الآن باستخدام HTML/URL → PDF.
جرّب HTML/URL → PDF