مقال تقني

HotPDF CompressDocument: مجموعات خطوط مدمجة في Delphi

‏THotPDF.CompressDocument في HotPDF مفتاح واحد يجعل BeginDoc تنتج أصغر PDF بلا خسارة يستطيع المكون كتابته: ‏FlateDecode بأقصى مستوى، وتدفق مراجع متبادلة مع تدفقات كائنات، وتجزئة خطوط ومجموعات خطوط مدمجة تعيد ترقيم المحارف المحفوظة خلف /CIDToGIDMap صريحة. ثم تعيد EndDoc إعداداتك أنت. ومستند اختبار من ثلاث صفحات بخطي Arial و SimSun هبط من 10.2 MB إلى 20 KB بعرض مطابق

ماذا يشغّل CompressDocument فعلاً؟

‏CompressDocument تتجاوز ست إعدادات كاتب، إضافة إلى سقف تدفقات الكائنات، لمستند واحد وتعيد كلها بعده. وعند BeginDoc، قبل أن يستقر إصدار PDF، تسجل HotPDF قيمك وتضبط Compression على cmFlateDecode، و CompressionLevel على clMaximum، وتشغل EnableFontSubsetting و CompactFontSubsetting، وتفعل UseXRefStream مع UseObjectStreams (ISO 32000-1 §7.5.7 و §7.5.8). تدفقات الكائنات تحتاج PDF 1.5، فيُرفع Version الأقدم إلى 1.5 متى لم يكن مقفولاً. و PDF/A-1 يحظر البنيتين معاً، فمستند PDF/A-1 يبقي جدول مراجعه المتبادلة الكلاسيكي ولا يحصل إلا على عمل Flate والخطوط. والصور تُترك تماماً كما ضممتها

مخطط دورة حياة HotPDF ‏CompressDocument في Delphi: تسجل BeginDoc قيم الكاتب نفسها، وتتجاوز ست إعدادات منها Compression و UseObjectStreams لمستند واحد، وتعيد EndDoc كل قيمة مستعارة في finally الخارجية لديها بينما تبقى خاصية CompressDocument نفسها على True
ست إعدادات كاتب وسقف تدفقات الكائنات تُستعار لمستند واحد بالضبط وتُعاد حين تعمل EndDoc، فتقرير فاشل لا يترك المكون عالقاً عند أقصى ضغط أبداً
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'invoice-2026-1042.pdf';
    Pdf.CompressDocument := True;    // يطبقها BeginDoc ويلغيها EndDoc
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

يحدث الاسترجاع في finally الخارجية لـ EndDoc، فاستثناء في منتصف تقرير لا يترك مكوناً طويل العمر عالقاً على أقصى ضغط للمهمة التالية. وتبقى خاصية CompressDocument نفسها على True؛ الإعدادات الست المستعارة وحدها هي التي تعود. أما الإصدار فيدار بعناية أكبر. تلغي HotPDF رفعها الخاص إلى 1.5 فقط إذا كان المستند ما زال ينتهي عند 1.5، فحين دفعت خاصية أخرى الملف إلى 1.6 خلال التشغيل (خط OpenType مضمّن مثلاً) يبقى الإصدار الأعلى، تماماً كما كان سيكون دون ضغط

لماذا ما زالت مجموعات الخطوط كبيرة بلا ضغط؟

مجموعة TrueType الكلاسيكية تطيح بالمخططات التي لن ترسمها أبداً لكنها تبقي كل معرف glyph مكانه، وهذا الترقيم هو ما يجعلها ثقيلة. تدفق المحتوى يعرض CIDs تساوي GIDs الأصلية، فلا بد للمجموعة أن تحمل إزاحة loca ومدخلاً في hmtx لكل خانة حتى أعلى glyph تحتفظ به، فارغةً أم لا. وخط لاتيني هذا الحمل ضجيج. أما خط CJK مثل SimSun، الذي تجلس أيديوغراماته في عمق جدول glyphs ضخم جداً، فمحرفان صينيان يسحبان معهما جداول مقاسة للخط كله. وقواعد إغلاق مجموعة الخطوط للمحارف المشكَّلة تقرر أي glyphs تنجو؛ والضغط مسألة كم يكلف الناجون

تعيد CompactFontSubsetting ترقيم المحارف المحفوظة نطاقاً كثيفاً يبدأ من الصفر وتكتب تدفق /CIDToGIDMap على الـ CIDFont، الذي يعرفه ISO 32000-1 §9.7.4.2 بوصفه جدول GIDs من بايتين مفهرساً بالـ CID. ذلك الجدول هو الحيلة كلها. تدفقات المحتوى ومصفوفة العروض /W و CMap الخاص بـ ToUnicode كلها تحتفظ بالـ CIDs الأصلية، فلا شيء كُتب سابقاً يحتاج تغييراً؛ الانتقال من CID إلى glyph وحده هو ما ينتقل إلى الخريطة. وفي الاختبار الذي حفّز الخاصية هبط SimSun بمحرفين من 24.8 KB من بيانات خطوط إلى 3.1 KB

مقارنة بين مجموعة خطوط HotPDF متناثرة تحتفظ بمدخلات loca و hmtx لكل معرف glyph أصلي حتى أعلى GID محتفظ به، وبمخرج CompactFontSubsetting الذي يعيد ترقيم المحارف المحفوظة كثافة من الصفر ويعين CIDs عبر تدفق CIDToGIDMap بينما تبقى تدفقات المحتوى و /W و ToUnicode دون تغيير
إعادة الترقيم تنقل الكلفة من برنامج الخط إلى تدفق خريطة صغير واحد — محرفا SimSun هبطا من 24.8 KB إلى 3.1 KB دون لمس بايت واحد من المحتوى المكتوب سابقاً

للضغط حدود صلبة، وهو يتدهور بهدوء بدل أن يفشل. تبني HotPDF المجموعات المدمجة لوجوه TrueType من النوع 0 فقط، سواء المضبوطة عبر SetFont والتجزئة شغالة أو الوجه المسجل عبر RegisterUnicodeTTF. الخط TrueType البسيط يجد glyphs عبر الـ cmap داخل برنامج الخط، وإعادة الترقيم ستكسره، فيبقي المجموعة المتناثرة. ووجوه OpenType-CFF ليس لها مسار مدمج أيضاً. والبناء المدمج الذي يفشل يرجع إلى المجموعة المتناثرة بدل أن يرفع. والخاصية مطفأة افتراضياً، فالمخرج القائم يبقى متطابقاً بايتاً ببايت، بينما تحت PDF/A يحصل الوجه الموحد المسجل دائماً على مجموعة مدمجة

Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True;   // تعمل دون CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;

كيف يعصر الكاتب المحزم بنية الملف؟

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

  • صياغة مدمجة وفق ISO 32000-1 §7.2.2: تُكتب مسافة فقط بين رمزين كانا سيلازمان محرفي نظام، فتصير /Type /Page على صورة /Type/Page
  • حقول تدفق المراجع المتبادلة تأخذ أي عرض يسمح به §7.5.8.2، فملف تحت 16 MB يخزن كل إزاحة في 3 بايتات بدل 4
  • حتى 250 كائناً تدخل كل تدفق كائنات بدل الـ 100 المعتادة، إلا إن ضبطت سقفاً خاصاً بك عبر ConfigureAdaptiveObjectStreamPacking
  • حين يكون الملف غير مشفر تُحزم الكتالوج وقاموس Info في تدفقات كائنات أيضاً؛ والمخرج المشفر يبقيهما في المستوى الأعلى

جاءت الصياغة المدمجة مع فخ يستحق المعرفة إن مددت الكاتب. التوقيع يملأ التواقيع بعد كتابة الملف بالبحث في البايتات عن الموضعين الحرفيين /ByteRange ( و /Contents <، وكانت الصياغة المدمجة ستحولهما إلى /ByteRange( و /Contents< الذي لا يجده البحث أبداً. لذا تبقي قواميس التواقيع (Type Sig أو DocTimeStamp، و FT Sig) وقاموس التشفير التخطيط المتباعد. وعيب ذو صلة أصاب البناءات قبل v2.766.41: كل حفظ بتدفقات كائنات، ومنه CompressDocument، كان يبدأ بسطري ترويسة %PDF-، فارقِ إن وسم مدقق صارم مخرجك

أيمكن ضغط PDF محمّل أصلاً؟

نعم، عبر التحميل الزائد بالخيارات CompressLoadedDocument(Options, Info)، الذي يشغل الخطوات نفسها بلا خسارة على ملف قائم. ومع THPDFLoadedDocumentCompressionOptions.Default يزيل موارد الصفحات غير المستعملة، ويدمج الخطوط والنماذج المتطابقة، وينجز تجزئة للخطوط المضمّنة بالمجموعات المدمجة شغالة، ويعيد ضغط التدفقات غير المفلترة و Flate و LZW و ASCII و RunLength بـ Flate حين تكون النتيجة أصغر، ويجعل الحفظ التالي يستخدم تدفقات كائنات. و HighRatioFlate مطفأة افتراضياً، وتُتخطى تدفقات الكائنات لـ PDF/A-1 والحفظ التزايدي. والتحميل الزائد CompressLoadedDocument بلا معاملات هو الاستدعاء الأقدم الأضيق الذي يضغط Flate التدفقات غير المضغوطة فقط

تدفق HotPDF ‏CompressLoadedDocument في Delphi: يزيل الاستدعاء موارد الصفحات غير المستعملة، ويدمج الخطوط والنماذج المتطابقة، وينجز تجزئة الخطوط المضمّنة بمجموعات مدمجة، ويعيد ضغط التدفقات بـ Flate حين تكون النتيجة أصغر فقط، ويشغل تدفقات الكائنات للحفظ التالي، بينما تطلق حقول التواقيع RefusedBySignaturePolicy وتبقي الملف بلا مساس
كل خطوة تعيد كتابة بايتات يغطيها توقيع، فيُرفض المستند كله إلا إذا أذنت صراحة بالإبطال — و Info.BytesSaved حينئذ يجمع عمل الموارد والخطوط والتدفقات فقط
var
  Doc: THotPDF;
  Options: THPDFLoadedDocumentCompressionOptions;
  Info: THPDFLoadedDocumentCompressionInfo;
begin
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    Doc.LoadFromFile('quarterly-report.pdf');
    Options := THPDFLoadedDocumentCompressionOptions.Default;
    Doc.CompressLoadedDocument(Options, Info);
    if Info.RefusedBySignaturePolicy then
      Writeln(Format('Left untouched: %d signature fields', [Info.SignatureCount]))
    else
    begin
      Writeln(Format('Compact fonts: %d, stream bytes saved: %d',
        [Info.Fonts.CompactSubsetFontCount, Info.BytesSaved]));
      Doc.SaveLoadedDocument('quarterly-report-compact.pdf');
    end;
  finally
    Doc.Free;
  end;
end;

حدّان يهمان على المسار المحمّل. كل خطوة تعيد كتابة بايتات يغطيها توقيع، فالمستند ذو حقول التواقيع يُرفض ككتلة واحدة: يعيد الاستدعاء 0 ويضبط RefusedBySignaturePolicy ولا يغير شيئاً، إلا إذا ضبطت AllowSignatureInvalidation، وبعدها تخبرك Info.SignaturesInvalidated بما تنازلت عنه. والضغط هنا أيضاً أكثر تحفظاً منه على مسار الإنشاء. تضغط HotPDF برامج الخطوط المستعملة من خطوط CIDFontType2 وحدها بخريطة /CIDToGIDMap هوية، حيث يساوي CID الـ GID، وتتخطى البرامج التي تحمل تدفق خريطة قائماً أو /CIDSet أو جداول glyphs ملونة مثل COLR و sbix و CBDT و SVG، لأن البناء المدمج كان سيطيح بطبقات الألوان. وانتبه أيضاً إلى أن Info.BytesSaved يجمع خطوات الموارد والخطوط والتدفقات وحدها؛ وكسب تدفقات الكائنات يظهر حين يُكتب الملف

ما النتائج التي تتوقعها عملياً؟

تتتبع المكاسب كم من الملف بنية غير مضغوطة وبيانات خطوط متضخمة، لا كم صفحة فيه. وعينة Arial و SimSun من ثلاث صفحات انكمشت من 10.2 MB إلى 20 KB حين وُلدت بـ CompressDocument، ومن 10.2 MB إلى 19.8 KB حين حُمّل الأصل غير المضغوط ومر عبر CompressLoadedDocument، بعرض مطابق في الحالين. و PDF مدمج أصلاً بالكاد يتحرك: في مجموعة الانحدار وفرت تلك الملفات بين -0.07% و +0.06% من حجمها الأصلي. وملفات كثيفة الصور تكسب قليلاً، لأن لا مسار يمس بيانات الصور

إن كنت تولّد تقارير CJK نفسها كل ليلة فاجمع بين المجموعات المدمجة و ذاكرة تجزئة الخطوط الدائمة على القرص كي لا يتكرر عمل التجزئة في كل تشغيل، وقارن مخرجات الضغط بمحتوى الكائنات لا بالبايتات، لأن حقلاً واحداً متغيراً يعيد ضغط Flate لتدفق كائنات كامل. ومراجع الخصائص والسجلات كاملة على صفحة منتج HotPDF Delphi PDF component