ضغط PDF في 5 خطوات: دراسة حالة من 80 ميجابايت إلى 8 ميجابايت

تستلم ملف PDF لعقد ممسوح ضوئيًا بحجم 80 ميجابايت. يُرفض من البريد الإلكتروني، وتُرفضه تطبيقات الدردشة، ويتجاوز حدود الرفع في كل نظام تجربه. أليس مألوفًا؟ تصبح ملفات PDF كبيرة لسبب ما، وتصغيرها بأمان إلى عُشر حجمها ليس سحرًا — بل هو مسألة فهم ما بداخل الحاوية وتطبيق التقنية المناسبة على كل نوع من المحتوى. يحلل هذا المقال خمس تقنيات أساسية للضغط من مواصفات PDF، ثم يمرّ عبر عقد ممسوح ضوئيًا حقيقي بحجم 80 ميجابايت وصولًا إلى 8.2 ميجابايت.

1. من أين يأتي حجم PDF

PDF هو صيغة حاوية. داخليًا هو مجموعة من الكائنات: شجرة صفحات، وقوائم موارد، وتدفقات محتوى، وكائنات صور XObjects، وخطوط، وبيانات وصفية. ينجم تضخم الحجم دائمًا تقريبًا عن ثلاث فئات.

الصور المضمّنة. هذا هو التكلفة المهيمنة في المستندات الممسوحة ضوئيًا. ماسح ضوئي مضبوط افتراضيًا على 600 DPI ينتج صورة نقطية لصفحة A4 بحجم تقريبًا 4960×7016 بكسل. عند عمق ألوان 24 بت، تبلغ البيانات الخام لصفحة واحدة حوالي 100 ميجابايت؛ حتى بعد ضغط FlateDecode (zlib) بدون فقدان، يزن كل صفحة من 2 إلى 5 ميجابايت. عقد من 30 صفحة يصل إلى 60–150 ميجابايت.

الخطوط المضمّنة. لضمان عرض متطابق عبر الأجهزة، تُضمّن ملفات PDF خطوطًا كاملة. خط CJK كامل (على سبيل المثال، خط Source Han) يحمل أكثر من 20000 حرف، وملف TTF وحده يتراوح بين 10 و20 ميجابايت؛ وقد يكون OTF أكبر. أضِف عدة أوزان أو عدة عائلات وتتضاعف هذه الفئة بسرعة.

الرسومات المتجهة والكائنات المكررة. المتجهات بحد ذاتها صغيرة، لكن تصديرات CAD المعقدة، وحقول النماذج المتداخلة، وموارد المراجعات المحذوفة، وتدفقات البيانات الوصفية المكررة تتراكم بصمت. آلية التحديث التزايدي في PDF هي مُلطِّشة سيئة السمعة: كل حفظ يضيف مراجعة جديدة دون إزالة الكائنات القديمة، لذا ينمو الملف مع كل تعديل.

بمجرد أن تعرف أين تقع البايتات، يأتي الحل تلقائيًا. طريقة الخطوات الخمس أدناه تغطي الغالبية العظمى من سيناريوهات الضغط الواقعية.

2. طريقة الخطوات الخمس بنظرة سريعة

PDF compression 5-step method flowchart: image resampling, format conversion, font subsetting, redundancy removal, linearization

الخطوة 1: إعادة أخذ عينات الصور

هذه هي الخطوة الأكثر فعالية للمستندات الممسوحة ضوئيًا. مسح بدقة 600 DPI لا يُفرَّق عن 150 DPI على أي شاشة عادية، لكن عدد البكسلات يختلف بمعامل 16 (DPI خطي؛ البكسلات تربيعية، إذن (600/150)² ≈ 16).

القرار الأساسي هو DPI المستهدف. محتوى المستندات (العقود، التقارير، الفواتير) يبدو جيدًا عند 150 DPI. للطباعة، ارفعه إلى 200–300 DPI. يمكن للمسوحات الأرشيفية عالية الدقة الاحتفاظ بـ 400 DPI فأكثر. لخوارزمية إعادة أخذ العينات، Lanczos هو الخيار الموصى به — فهو يحافظ على حدة حواف النص عند التصغير أفضل من الاستيفاء الثنائي ويتجنب تأثير الكتل في طريقة أقرب جار.

في التنفيذ، تقرأ معلومات /Width و/Height و DPI لكائن صورة XObject من /DecodeParms، وتقوم بالتحجيم النسبي، ثم تعيد الكتابة. تأكد من تحديث /Width و/Height و/Intent معًا، وإلا سيكون العرض غير محاذى.

الخطوة 2: تحويل صيغة الصور

يحدد PDF ترميز الصورة بحقل /Filter. تستخدم المستندات الممسوحة ضوئيًا عادةً /FlateDecode (بدون فقدان بنمط PNG)، وهو أقل كفاءة بكثير من /DCTDecode (JPEG) للصور الواقعية مثل الصور الفوتوغرافية أو الصفحات الممسوحة.

نفس مسح A4 الذي يشغل 4 ميجابايت تحت FlateDecode يمكن أن ينخفض إلى 300–500 كيلوبايت كـ JPEG بجودة 72 — انخفاض بنسبة 85% دون فرق محسوس على الشاشة. أمران للانتباه: حوّل المستندات بالأبيض والأسود إلى تدرج رمادي (/ColorSpace /DeviceGray) قبل الترميز لتقليل الحجم إلى النصف مرة أخرى، وارفع جودة JPEG إلى 80+ في الصفحات التي تحتوي على توقيعات لتبقى ضربات القلم سليمة.

/DCTDecode ليس الخيار الوحيد. للمخططات ذات المساحات اللونية المسطحة الكبيرة، /JPXDecode (JPEG 2000) أصغر عند جودة مكافئة، رغم أن التوافق مع القراء القدامى أسوأ.

الخطوة 3: تضمين الخطوط الجزئي

تضمين خط CJK كامل يكلف 10 ميجابايت أو أكثر، لكن عقدًا من 30 صفحة قد يستخدم فعليًا فقط 800 إلى 1500 حرف متميز. التضمين الجزئي للخطوط يحتفظ فقط بالحروف التي تظهر في المستند ويتجاهل كل ما عداها.

الخط الجزئي عادةً ما يتقلص إلى 100–300 كيلوبايت — انخفاض بأكثر من 95%. النهج: تجول في تدفق محتوى كل صفحة، استخرج الحروف من عوامل التشغيل Tj و TJ، حلّها عبر خريطة /ToUnicode للحصول على مجموعة نقاط الشفرة المستخدمة، ثم استخدم أداة خطوط (مثل fontTools) لبناء ملف خط جديد يحتوي فقط على تلك الحروف.

تحذيران: خطوط CID تتطلب معالجة /CIDToGIDMap لتجنب سوء محاذاة الحروف، وتُعاد تسمية الخطوط الجزئية تقليديًا ببادئة (مثل ABCDEF+) لتمييزها كمجموعات جزئية، مما لا يؤثر على العرض.

الخطوة 4: إزالة الكائنات المكررة

بعد تحرير متكرر ودمج وحفظ تزايدي، تتراكم ملفات PDF كائنات «يتيمة» — موارد استُبدلت بمراجعات أحدث لكنها لم تُزل قط من جدول المراجع المتقاطعة. الجناة النموذجيون: صور قديمة لم تعد أي صفحة تشير إليها، نسخ خطوط مستبدلة، قوائم موارد فارغة، وتدفقات بيانات وصفية مكررة.

منطق التنظيف: ابدأ من فهرس المستند (/Root)، قم بعبور أولي لشجرة الصفحات وسلسلة مراجع الموارد، ضع علامة على كل كائن قابل للوصول، ثم أعد بناء جدول المراجع المتقاطعة وتخلص من الكائنات غير القابلة للوصول. هذه الخطوة فعالة بشكل خاص على المستندات التي تم تحريرها كثيرًا: تمريرة واحدة عادةً ما تزيل 5%–15% من الحجم، وفي الحالات القصوى من التحديثات التزايدية المتراكمة، أكثر من 30%.

الخطوة 5: الترتيب الخطي

الترتيب الخطي لا يقلل من إجمالي عدد البايتات، لكنه يعيد تنظيم بنية الملف لتمكين العرض السريع على الويب (Fast Web View). ملف PDF المرتب خطيًا يضع الكائنات اللازمة للصفحة الأولى في رأس الملف ويدخل جداول تلميحات، بحيث يمكن للقارئ عرض الصفحة الأولى قبل اكتمال التنزيل.

رغم أن الترتيب الخطي لا يقلص الملف مباشرة، إلا أنه خطوة الإغلاق الطبيعية لخط أنابيب الضغط. مقترنًا بضغط تدفق الكائنات وتدفقات المراجع المتقاطعة، تؤدي إعادة الهيكلة الشاملة إلى انخفاض صغير في الحجم (1%–3%). والأهم من ذلك، أن ملفات PDF المرتبة خطيًا تعمل بشكل ملحوظ أفضل في سيناريوهات تضمين الويب ومعاينة السحابة.

3. مقارنة قبل وبعد

إليك كيف يتغير عقد الـ 80 ميجابايت بعد كل خطوة:

PDF compression before/after file size comparison: 80MB scanned contract compressed to 8MB effect demonstration

الخطوة 1 (إعادة أخذ العينات) تحقق أكبر انخفاض، من 80 ميجابايت مباشرة إلى 15.2 ميجابايت. الخطوة 2 (تحويل الصيغة) تزيل 4 ميجابايت أخرى. الخطوات الثلاث المتبقية معًا تقتطع حوالي 7 ميجابايت. نسبة الضغط الإجمالية تقريبًا 9.8:1.

4. شيفرة زائفة بلغة Python

الشيفرة الزائفة أدناه تظهر منطق الخطوات الخمس الكامل. في الإنتاج ستجمع بين مكتبات مثل pikepdf و PyMuPDF و reportlab.


import io
from PIL import Image

def compress_pdf(input_path, output_path,
                 target_dpi=150, jpeg_quality=72, grayscale=True):
    """
    نقطة دخول ضغط PDF بخمس خطوات.
    :param input_path: مسار PDF الدخل
    :param output_path: مسار PDF الخرج
    :param target_dpi: DPI المستهدف (افتراضي 150، جيد للشاشة)
    :param jpeg_quality: جودة JPEG (افتراضي 72)
    :param grayscale: التحويل إلى تدرج رمادي (موصى به للمستندات بالأبيض والأسود)
    :return: (الحجم_الأصلي، الحجم_المضغوط)
    """
    pdf = open_pdf(input_path)
    original_size = file_size(input_path)

    # الخطوة 1: إعادة أخذ عينات الصور (600DPI -> DPI المستهدف)
    for page in pdf.pages:
        for image in page.images:
            resampled = resample_image(image, target_dpi)
            image.replace(resampled)

    # الخطوة 2: تحويل الصيغة (FlateDecode -> DCTDecode/JPEG)
    for page in pdf.pages:
        for image in page.images:
            if image.filter == "FlateDecode":
                jpeg_data = encode_jpeg(image, jpeg_quality, grayscale)
                image.replace(jpeg_data, filter="DCTDecode")

    # الخطوة 3: تضمين الخطوط الجزئي (الاحتفاظ بالحروف المستخدمة فقط)
    used_chars = collect_used_chars(pdf)
    for font in pdf.fonts:
        subset = build_subset(font, used_chars)
        font.replace(subset)

    # الخطوة 4: إزالة الكائنات المكررة
    pdf.remove_unreferenced_resources()
    pdf.compact_xref()  # إعادة بناء جدول المراجع المتقاطعة

    # الخطوة 5: الترتيب الخطي (Fast Web View)
    pdf.save(output_path, linearize=True,
             object_streams=True, compress_streams=True)
    pdf.close()

    compressed_size = file_size(output_path)
    return original_size, compressed_size


def resample_image(image, target_dpi):
    """إعادة أخذ عينات صورة إلى DPI المستهدف."""
    src_dpi = image.dpi
    if src_dpi <= target_dpi:
        return image  # عدم التكبير أبدًا، تخطٍّ
    scale = target_dpi / src_dpi
    new_w = int(image.width * scale)
    new_h = int(image.height * scale)
    pil_img = Image.open(io.BytesIO(image.data))
    return pil_img.resize((new_w, new_h), Image.LANCZOS)


def encode_jpeg(image, quality, grayscale):
    """التحويل إلى تدرج رمادي والترميز كـ JPEG."""
    pil_img = Image.open(io.BytesIO(image.data))
    if grayscale and pil_img.mode != "L":
        pil_img = pil_img.convert("L")  # إلى تدرج رمادي
    buf = io.BytesIO()
    pil_img.save(buf, format="JPEG", quality=quality, optimize=True)
    return buf.getvalue()


def collect_used_chars(pdf):
    """المرور عبر تدفقات المحتوى لجمع كل نقطة شفرة مستخدمة فعليًا."""
    used = set()
    for page in pdf.pages:
        for text_op in page.content_stream.text_ops:
            used.update(extract_codepoints(text_op))
    return used


def build_subset(font, used_chars):
    """بناء خط جزئي يحتوي فقط على الحروف المستخدمة."""
    subset = font.subset(glyphs=used_chars)
    subset.name = prefix + "+" + font.name  # اصطلاح تسمية المجموعات الجزئية
    return subset

هذه شيفرة زائفة — معالجة الأخطاء، تعيين خطوط CID، وإعادة بناء ToUnicode محذوفة — لكن الهيكل واضح: إعادة أخذ العينات، التحويل، المجموعة الجزئية، التنظيف، الترتيب الخطي، بهذا الترتيب.

5. دراسة حالة: عقد ممسوح ضوئيًا من 80 ميجابايت إلى 8.2 ميجابايت

المستند المصدر عقد صيني حقيقي من 32 صفحة ممسوح ضوئيًا بدقة 600 DPI، الحجم الأصلي 80.4 ميجابايت.

ملف المستند:

  • 32 صفحة، A4، مسح بالأبيض والأسود بدقة 600 DPI
  • 32 صورة مضمّنة، جميعها FlateDecode، بمتوسط 2.4 ميجابايت لكل صفحة
  • 2 خطوط مضمّنة (Source Han Serif Regular + Bold)، 28 ميجابايت مجتمعة
  • محفوظ تزايديًا 3 مرات، مع وجود كائنات مكررة

الخطوات والمعاملات:

الخطوةالعمليةالمعاملات الأساسيةالحجم
1إعادة أخذ عينات الصور600 DPI → 150 DPI، Lanczos80.4 → 15.2 ميجابايت
2تحويل الصيغةFlateDecode → JPEG، جودة 72، تدرج رمادي15.2 → 11.0 ميجابايت
3تضمين الخطوط الجزئيالاحتفاظ بـ 1,287 حرفًا مستخدمًا11.0 → 10.5 ميجابايت
4إزالة التكرارإعادة بناء جدول المراجع المتقاطعة10.5 → 9.8 ميجابايت
5الترتيب الخطيObject Stream + Fast Web View9.8 → 8.2 ميجابايت

النتيجة: 8.2 ميجابايت، نسبة 9.8:1. النص حاد وواضح، التوقيعات سليمة، حدود الجداول لا تظهر أي كسر، والملف يُطبع بشكل نظيف.

6. توصيات DPI حسب السيناريو

DPI المستهدف هو الرافعة الرئيسية التي توازن بين الضغط والوضوح. يسرد الجدول أدناه القيم الموصى بها لحالات الاستخدام الشائعة.

حالة الاستخدامDPI الموصى بهحجم الصورة لكل صفحةملاحظات
قراءة على الشاشة / معاينة ويب150150–300 كيلوبايتنص حاد، تحميل سريع، مناسب للبريد الإلكتروني
طباعة قياسية (A4 ليزر/حبري)200–300400–800 كيلوبايتحاد عند الطباعة؛ 300 للعقود
نسخ احتياطي أرشيفي (دقة عالية)400–6001–3 ميجابايتيحافظ على تفاصيل المسح للتخزين طويل الأمد
أبيض وأسود نص فقط150–20080–200 كيلوبايتصغير جدًا بعد التدرج الرمادي، مناسب للأرشفة الدفعية
مستند ملون مع صور200–300500 كيلوبايت–1.2 ميجابايتجودة JPEG 75–85، موازنة اللون والحجم

قاعدة عملية: افتراضيًا 150 DPI للشاشة، 300 DPI فقط عند الطباعة، واحتفظ بنسخة رئيسية عالية DPI منفصلة للأرشفة. لا تحاول جعل ملف واحد بدقة 600 DPI يخدم كل الأغراض.

7. الأسئلة الشائعة

س1: الملف المضغوط خرج أكبر من الأصل. لماذا؟

يحدث هذا عادةً عند إعادة ضغط PDF يحتوي بالفعل على صور صغيرة مرمّزة بـ JPEG. لإعادة أخذ العينات وإعادة الترميز تكاليف إضافية: إذا كانت الصورة بالفعل JPEG بدقة 150 DPI، فإن «إعادة أخذ العينات» إلى 150 DPI لا تصغرها، وقد يكون مرمّز JPEG الجديد أقل كفاءة من الأصلي. أضف تكاليف البيانات الوصفية من إعادة هيكلة تدفق الكائنات وقد ينمو الملف قليلًا. الحل: افحص DPI والترميز الحاليين لكل صورة قبل الضغط، وتخطَّ الصور التي تفي بالهدف بالفعل (≤150 DPI و JPEG). عالج فقط الكائنات التي تتجاوز العتبة فعلًا.

س2: هل سيقلل الضغط جودة الصورة؟

نعم، لكن يمكن إبقاؤه غير محسوس بصريًا. يأتي الضغط مع الفقدان أساسًا من الخطوة 2 (ترميز JPEG). عند جودة 72 الفرق غير مرئي على الشاشة؛ في الطباعة قد تلاحظ تموجًا خفيفًا على التفاصيل عالية التردد مثل الخطوط الرفيعة أو الخطوط الصغيرة. إذا كانت الجودة أهم، ارفع جودة JPEG إلى 85 أو بدّل إلى وضع JPEG 2000 بدون فقدان (/JPXDecode مع تحويل عكسي). تضمين الخطوط الجزئي، وإزالة التكرار، والترتيب الخطي كلها بدون فقدان وليس لها أي تأثير على العرض.

س3: هل يمكن ضغط PDF مضغوط بالفعل مرة أخرى؟

نعم، لكن بعوائد متناقصة. التمريرة الأولى تحصد الثمار السهلة (DPI عالي، ترميز FlateDecode، خطوط كاملة). التمريرة الثانية لم يبقَ لديها الكثير لتحسينه. للحكم: تحقق مما إذا كانت الصور لا تزال تتجاوز DPI المستهدف، وما إذا كان أي ترميز غير JPEG لا يزال موجودًا، وما إذا كانت الخطوط لا تزال مجموعات كاملة. إذا كان الثلاثة جميعًا مثالية بالفعل، يعتمد الضغط الإضافي أساسًا على إزالة التكرار والترتيب الخطي، عادةً موفّرًا 5%–10% فقط. لا تضغط PDF محسّنًا بالفعل بشكل متكرر — كل ترميز مع فقدان يراكم فقدان الجودة.

8. مرجع سريع للمعاملات

المعاملالقيمة الموصى بهاملاحظات
DPI المستهدف (شاشة)150معيار لمحتوى المستندات
DPI المستهدف (طباعة)300يحافظ على حدة طباعة A4
جودة JPEG (شاشة)70–75الحجم أولًا، الوضوح كافٍ
جودة JPEG (طباعة)80–90الجودة أولًا، الحجم ثانوي
وضع اللون (مستند أبيض/أسود)DeviceGrayيقلل الحجم إلى النصف مقابل اللون
تضمين الخطوط الجزئيمفعّلإلزامي لخطوط CJK، تقليل 95%+
الترتيب الخطيمفعّليحسّن تجربة معاينة الويب
Object Streamمفعّليضغط البيانات الوصفية للكائنات
تنظيف التكرارمفعّلفعّال بشكل خاص على الملفات المُحرّرة بشكل متكرر

الخلاصة

ضغط PDF ليس فنًا غامضًا — بل معالجة موجهة لكل نوع من الكائنات داخل الملف. طريقة الخطوات الخمس تتبع منطقًا واضحًا: تعامل أولًا مع أكبر مساهم (الصور، عبر إعادة أخذ العينات وتحويل الصيغة)، ثم الخطوط (التضمين الجزئي)، ثم البنية (إزالة التكرار والترتيب الخطي). شغّل هذا الخط أنابيب على عقد ممسوح نموذجي بحجم 80 ميجابايت وستصل بشكل موثوق إلى 8–10 ميجابايت مع بقاء قابلية القراءة والطباعة كاملة.

ثلاث فحوصات لتتذكرها: للمسوحات، انظر إلى DPI أولًا؛ للمستندات، تحقق مما إذا كانت الخطوط مجموعات كاملة؛ للملفات القديمة، افحص الكائنات المكررة. اعثر على التكلفة المهيمنة، ونسبة الضغط تتكفل بنفسها.

مقالات ذات صلة:

هل تحتاج لضغط الملفات؟ جرّب SmartSlim

على أساس محرك ضغط Rust خاص، يدعم 10 فئات رئيسية و40+ صيغة بما في ذلك PDF/الصور/الفيديو/Office/OFD. معالجة محلية — البيانات لا تغادر نطاقك.