تزيل PDF Library for Delphi معامل مصفوفة النص الهوية، 1 0 0 1 0 0 Tm، أثناء تحسين تدفق المحتوى peephole وقت الحفظ فقط عندما تكون مصفوفة النص ومصفوفة سطر النص هوية أصلاً: مباشرة بعد BT، أو مباشرة بعد Tm هوية سابقة. أما cm الهوية فما زالت تُسقط دائماً، لأن cm تضرب في CTM بينما Tm تستبدل مصفوفتي النص كلتيهما قطعاً. ومنذ v3.539.28 تبقى كل Tm هوية أخرى في التدفق
العيب الذي يصلحه هذا من النوع الهادئ. مولد تقارير يطلق BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET، معتمداً على Tm الهوية لتعيد السلسلة الثانية إلى أصل فضاء النص قبل أن يطبق منطق تموضعه الخاص. كان المُحسِّن الأقدم يرى ستة أعداد تهجئ المصفوفة الهوية، فيقرر أن المعامل لا يمكن أن يغيّر شيئاً، ويمحوه. لم يفشل شيء، ولم يسجل شيء تحذيراً، والصفحة المحفوظة رسمت «Total» مباشرة بعد «Invoice» على خط الأساس نفسه، وهي بالضبط فئة العيوب التي لا ينتبه إليها أحد حتى يطبع عميلٌ ملف PDF
لماذا ليست 1 0 0 1 0 0 Tm دائماً no-op؟
Tm الهوية بلا عمل فقط حين تستبدل مصفوفتين تحملان الهوية أصلاً، وهذه خاصية للمعاملات التي قبلها لا لمعاملاتها هي. تقول ISO 32000-1 §9.4.1 إن BT تهيئ مصفوفة النص (Tm) ومصفوفة سطر النص (Tlm) إلى الهوية، وتعرف §9.4.2 Tm بأنها تضبط كلتيهما إلى القيم المعطاة، لا أنها تضم إلى ما عليهما. قارن ذلك بـ cm (§8.4.4) التي تضرب من اليمين في مصفوفة التحويل الحالية: الضرب في الهوية يترك أي CTM كما هو، فـ 1 0 0 1 0 0 cm محذوفة بأمان في أي مكان. داخل كائن نص الصورة مختلفة. Td و TD و T* و Tm غير الهوية كلها تحرك Tlm، وكل معامل إظهار نص (Tj و TJ و ' و ") يقدّم Tm بعرض المحارف التي رسمها. بعد أي منها، Tm الهوية إعادة ضبط حقيقية إلى الأصل. وإن كنت تتبعت مواضع النص يدوياً يوماً مع متتبع حالة CTM ومصفوفة النص لتدفق المحتوى فهذا هو التمييز نفسه بين تضمين الحالة واستبدالها
كيف يقرر المسح الخلفي أي Tm هوية يُسقط
يبدأ TPDFContentPeepholeOptimizer.RemoveIdentityMatrices الآن مسحه خلفاً من كل Tm هوية ولا يسقطها إلا إذا بلغ المسح BT أو Tm هوية أخرى أولاً. و Tm الهوية السابقة تحسب سواء أبقيت أم أُجدولت هي ذاتها للحذف للتو، لأنها في الحالين تركت المصفوفتين على الهوية، تماماً كما تفعل BT. والقاعدة تصنف كل معامل يمكن أن تقابله في إحدى مجموعتين:
- تتوقف وتُبقي الـ Tm:
TdوTDوT*وTmغير هوية وTjوTJو'و"وET، وأي معامل لا يعرفه المحلل، أو بداية التدفق - تخطو فوقه وتواصل المسح: معاملات لا تلمس Tm أو Tlm أبداً، مثل
TfوTcومضبطات الألوان وgsومعاملات المحتوى الموسوم وcm
الحالات المحافظة متعمدة. المعامل المجهول يمكن أن يكون أي شيء، فيرفض المسح أن يستنتج ما بعده. و ET تُغلق كائن النص، فلا BT تشهد لقيم المصفوفة لـ Tm تليها. ويعمل المسح على تدفق محتوى واحد في كل مرة، وهذا مهم للصفحات التي /Contents فيها مصفوفة: طبقة تبدأ في وسط كائن نص، بلا BT خاصة بها، تبقي Tm هويتها حتى لو كانت الطبقة السابقة ستجعلها زائدة عن الحاجة. ذلك يكلف بايتات قليلة على ملفات غريبة ولا يحرك محرفاً أبداً. وإن كنت تحرر نص الصفحة على مستوى التعليمة، كما في جولة تعيين المحارف إلى بايتات المحتوى، فنموذج TPDFContentProgram المحلول نفسه هو ما يعيد المُحسِّن كتابته
uses
PDFlibContentModel, PDFlibContentOptimize;
function OptimizeSnippet(const Source: AnsiString): AnsiString;
var
Prog: TPDFContentProgram;
Optimizer: TPDFContentPeepholeOptimizer;
begin
Result := Source;
Prog := TPDFContentProgram.Create;
try
if not Prog.Parse(Source) then
Exit; // تدفق تالف: اترك البايتات وشأنها
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // يعيد عدد التعليمات المزالة
finally
Optimizer.Free;
end;
Result := Prog.Emit; // تعليمة واحدة لكل سطر
finally
Prog.Free;
end;
end;
// أزيلت: Tm مباشرة بعد BT، والثانية من Tm هويتين متتاليتين
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// أبقيت: Tm بعد Td، وبعد Tj، وبعد Tm غير هوية، أو خارج BT
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
شغّل المساعدة على تدفق الفاتورة من المقدمة تبقى Tm الهوية حية، لأن المسح الخلفي يصطدم بـ Tj قبل أن يبلغ BT. ضع /F1 12 Tf و 2 Tc و 0 g بين BT و Tm الهوية وستزال رغم ذلك، لأن أياً منها لا يلمس مصفوفتي النص. وتسلسل مثل BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm يخسر معاملاً واحداً بالضبط: Tm الهوية الأولى تعيد ضبط المصفوفة التي حركتها Td، والثانية وحدها هي الزائدة
متى يعمل مُحسِّن peephole فعلاً؟
يعمل المُحسِّن فقط خلال تمريرة الضغط، داخل TPDFPageTree.Compress، وفقط على تدفقات المحتوى غير المضغوطة بـ Flate أصلاً. TPDFlib.SetOptimizeContentStreams(1) هي الافتراضية، والمفتاح نفسه متاح بحقل OptimizeContentStreams في TPDFlibSaveOptions؛ ويحترمه CompressContent و CompressPage كلاهما. والتدفق الذي /Filter فيه أصلاً /FlateDecode يُتخطى كلياً، فتحميل PDF مضغوط قائم وحفظه من جديد لا يعيد كتابة معاملاته. وإذا أخفق التدفق في التحليل ضغطت البايتات المفكوك الأصلية دون تغيير. TPDFlib.NormalizeContentStreams تحلل المحتوى وتعيد إطلاقه بتباعد وأعداد قياسية لكنها لا تستدعي المُحسِّن أبداً، وهذا يجعلها مرجعاً مفيداً حين تريد أن ترى كم يساهم من فرق حجم قواعد peephole، إلى جانب المكاسب الأكبر المغطاة في تحسين حجم ملف PDF بتجزئة الخطوط
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// التدفقات غير المضغوطة تمر بقواعد peephole ثم Flate
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// الخيار نفسه عبر خيارات الحفظ المجمعة؛ False يعني الاستغناء
Options.CompressContent := True;
Options.CompressFonts := True;
Options.CompressImages := True;
Options.Linearize := False;
Options.KeepModDate := False;
Options.OptimizeContentStreams := False;
Options.GarbageCollect := False;
Options.PackObjectStreams := True;
Lib.SaveToFileOptions('report-plain.pdf', Options);
finally
Lib.Free;
end;
end;
ماذا كان اختبار الانحدار القديم يضمن فعلاً؟
ضمن اختبار الانحدار القديم صورة واحدة فقط: Tm هوية مباشرة بعد BT تُزال. Peephole_RemovesIdentityTextMatrix يلقي BT 1 0 0 1 0 0 Tm (hello) Tj ET على المُحسِّن ويجزم أنه لم يبق Tm. وإصدار أقدم كان قد لاحظ أصلاً أن إسقاط Tm هوية غير آمن حين لا تكون Tlm هوية، ثم أبقى السلوك على أي حال لأن الاختبار «أقفله». اقرأه بعناية من جديد لا يقول الاختبار شيئاً عن Tm هوية بعد Td أو بعد نص معروض؛ ومعاملة تغطية عينة واحدة بوصفها عقد القاعدة كلها كان الخطأ الفعلي. الإصلاح يبقي الحالة الأصلية ناجحة ويضيف ست حالات تثبت الصور القابلة للإزالة والمبقية معاً، بما فيها Tm خارج أي كائن نص وأخرى تلي ET
المقايضة سهلة القبول متى كُتبت على الورق. المولدات التي تغلف كل كائن نص بـ BT 1 0 0 1 0 0 Tm ... ما زالت تحصل على إزالة ذلك المعامل الزائد، ومن هناك جاءت تقريباً كل المدخرات. وما يتخلى عنه المُحسِّن هو Tm هوية عابرة في وسط كائن نص، حفنة بايتات لكل صفحة قبل أن تراها Flate أصلاً، مقابل ضمان ينص عليه رأس الوحدة بوضوح: كل تحويل يعادل مخرجاته ولا يغير الصفحة المرئية أبداً. مُحسِّن حجم يحرك النص ليس مُحسِّناً، إنه عيب عرض بنسب ضغط جيدة
محلل تدفق المحتوى ومُحسِّن peephole وخيارات الضغط وقت الحفظ الموصوفة هنا تأتي كلها في PDF Library for Delphi and C++Builder، التي تكشف أيضاً NormalizeContentStreams و CompressContent و TPDFlibSaveOptions لضبط كيفية كتابة كل مستند