ينشئ PDFium Component تدرجات PDF المحورية والقطرية داخل Delphi عبر TPdf.SaveAsShadingCreated. تصف كل تدرج في سجل TPdfShadingCreate (النوع وفضاء الألوان والإحداثيات والألوان أو المحطات وصفحة الهدف)، وتكتب الطريقة نسخة متحقق منها يكون كل تعبئة فيها قاموس تظليل حقيقي من ISO 32000-1، مسجلاً في قاموس /Resources /Shading للصفحة ومرسوماً بمعامل sh، فتعرضه PDFium أصلياً
تظهر الحاجة في مولدات التقارير والشهادات. يسلم المصمم نموذجاً بشريط ترويسة أزرق إلى أبيض ناعم أو توهجاً قطرياً خلف ختم، ولا شيء يقدمه API كائنات الصفحة الذي يغطي النص والمسارات والصور لأجله. تستطيع انتحال تدرج بمئة مستطيل رفيع بألوان متباينة قليلاً، لكن النتيجة تشرائط بوضوح عند التكبير وتنفخ تدفق المحتوى وتُطبع بشكل سيئ. تملك PDF تظليلات ناعمة منذ 1.3؛ والعمل هو كتابتها بحيث يرسمها عارض فعلاً
لماذا يكتب PDFium Component التدرجات حفظاً منفصلاً؟
TPdf.SaveAsShadingCreated عملية حفظ لا استدعاء كائنات صفحة، لأن الـ ABI العام في PDFium بلا دالة تنشئ قاموس تظليل أو تبث معامل sh. طبقة كائنات الصفحة المستخدمة عند إنشاء ملفات PDF من الصفر بواسطة PDFium Component تبني المسارات والنص والصور، وهناك تتوقف. التظليلات تسكن درجة أدنى، في مخطط الكائنات وقواعد تدفق المحتوى
لذا تعمل الطريقة بمراحل. يحفظ PDFium أولاً المستند المفتوح أصلياً، محافظاً على كل تحرير أجريته عبر الـ API العادي. ثم يعيد PDFium Component كتابة ذلك المخرج المرصوص: يخصص كائناً غير مباشر واحداً لكل تظليل، ويضيف مدخل المورد إلى كل صفحة هدف، ويلحق تدفق محتوى جديداً إلى /Contents للصفحة، ويكتب جدول مراجع متقاطعة جديداً. وقبل أن يصل أي شيء إلى تدفقك أو ملفك يعاد تحميل النتيجة بفحص مراجع متقاطعة صارم، وتعيد الطريقة False مع رسالة في التقرير إن فشل ذلك التحميل. تكتب الصيغة الزائدة بالملف أولاً إلى ملف مؤقت، فحفظ فاشل لا يطمس هدفاً قائماً
بناء تدرج محوري بلونين
التظليل المحوري (ISO 32000-1 §8.7.4.5.3، النوع 2) يمزج الألوان على طول خط من x0 y0 إلى x1 y1، فتأخذ Coords أربع قيم. والتظليل القطري (§8.7.4.5.4، النوع 3) يمزج بين دائرتين ويأخذ ستاً: x0 y0 r0 x1 y1 r1. مع StartColor و EndColor عاديين يبث PDFium Component دالة أسية من النوع 2 (§7.10.3) بـ /N 1، وهو استيفاء خطي مستقيم، ويضبط /Extend [true true] فتستمر ألوان الطرفين بعد طرفي المحور
uses
System.SysUtils, PDFium, FPdfCompress;
procedure AddHeaderBand(const InFile, OutFile: string);
var
Pdf: TPdf;
Fills: TPdfShadingCreateShadings;
Report: TPdfShadingCreateReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InFile;
Pdf.Active := True;
SetLength(Fills, 1);
Fills[0] := Default(TPdfShadingCreate);
Fills[0].ShadingType := pstAxial;
Fills[0].ColorSpace := pscDeviceRGB;
Fills[0].Coords := [0, 792, 0, 640]; // x0 y0 x1 y1، من الأعلى إلى الأسفل
Fills[0].StartColor := [0.12, 0.35, 0.75]; // أزرق داكن عند الحافة العليا
Fills[0].EndColor := [1, 1, 1]; // أبيض في الأسفل
Fills[0].PageNumber := 1; // تبدأ من 1
Fills[0].ResourceName := 'HeaderBand'; // اختياري، الافتراضي Sh1 و Sh2...
if not Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
raise Exception.Create('Shading creation failed: ' + Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
يجب أن يطابق عدد مكونات كل لون فضاء الألوان: واحد لـ pscDeviceGray و pscCalGray، وثلاثة لـ pscDeviceRGB و pscCalRGB و pscLab، وأربعة لـ pscDeviceCMYK. ولـ pscICCBased يأتي العدد من بايتات الملف التعريفي التي تمررها في IccProfile، التي يجب أن تكشف 1 أو 3 أو 4 مكونات؛ ويغرز الملف التعريفي تدفق ICC خاصاً به مع /N المطابقة. وتقبل الفضاءات المعايرة CalWhitePoint اختيارياً (ثلاث قيم، Y موجب)، و CalGamma (قيمة واحدة لـ CalGray وثلاث لـ CalRGB) و، لـ Lab، LabRange من ست قيم. ويُفحص كل إحداثي ومكون من NaN واللانهاية قبل كتابة أي بايت
لاحظ أن الإحداثيات في فضاء المستخدم الافتراضي، والأصل أسفل يسار الصفحة. وتذكر أن المحور يحدد فقط أين يجري المزج: المنطقة المرسومة هي ما يسمح به القص، وهذا أهم مما يبدو كما ستُري الأقسام التالية
تدرجات متعددة المحطات تُترجم إلى دالة ربط من النوع 3
يحول PDFium Component مصفوفة Stops إلى الدالة التي يتوقعها قارئ PDF: محطتان تصيران دالة واحدة من النوع 2، ومن 3 إلى 256 محطة تصير دالة ربط من النوع 3 (ISO 32000-1 §7.10.4) مكونة من مقطع خطي واحد من النوع 2 لكل زوج متجاور. ويحمل كل TPdfShadingCreateStop Position و Color. وعندما تكون Stops غير فارغة تحل محل StartColor و EndColor كليهما
تحتاج دالة الربط ثلاث مصفوفات يسهل الخطأ فيها بخفة يدوياً. تحمل /Functions المقاطع، وتحمل /Bounds مواضع المحطات الداخلية فقط (k مقاطع تحتاج k-1 حداً)، وتعيد /Encode كل نطاق فرعي إلى [0 1] فيرى كل مقطع مدى مدخله الكامل. تشتق المكوّن الثلاثة من محطاتك، ولهذا صارم في المدخل: يجب أن تكون المواضع منتهية، متزايدة تماماً، تبدأ عند 0 وتنتهي عند 1 (في حدود 1e-6). قائمة محطات تبدأ عند 0.1 ستترك جزءاً من النطاق دون دالة معرفة، وترفضها الطريقة بدل التخمين
function Stop(Position: Double; const Rgb: array of Double): TPdfShadingCreateStop;
var
I: Integer;
begin
Result.Position := Position;
SetLength(Result.Color, Length(Rgb));
for I := 0 to High(Rgb) do
Result.Color[I] := Rgb[I];
end;
procedure BuildSealGlow(var Fill: TPdfShadingCreate);
begin
Fill := Default(TPdfShadingCreate);
Fill.ShadingType := pstRadial;
Fill.ColorSpace := pscDeviceRGB;
// x0 y0 r0 x1 y1 r1: نقطة في مركز الختم تنمو إلى دائرة 90 pt
Fill.Coords := [450, 160, 0, 450, 160, 90];
Fill.Stops := [Stop(0.0, [1.00, 0.95, 0.70]),
Stop(0.4, [0.95, 0.75, 0.30]),
Stop(1.0, [1.00, 1.00, 1.00])];
Fill.PageNumber := 1;
end;
التدرج ذو الثلاث محطات ينتج /Functions بمقطعين من النوع 2، و /Bounds [0.4] و /Encode [0 1 0 1]. حدود المقاطع وصلات لونية صلبة، فقائمة المحطات لا تنعّم عبرها؛ وإن بدت وصلة حادة أكثر من اللازم فأضف محطات بدل توقع القارئ استيفاء انحناء
لماذا ترسم PDFium شيئاً بصمت؟
لا تعرض PDFium تظليلاً منشأ حديثاً إلا عندما يكون تفصيلان صحيحين: يجب أن يكون /ShadingType عدداً صحيحاً، وأن يجلس استدعاء sh داخل مسار قص. أخطئ في أيهما فيفتح الملف ويتحقق بنيوياً ويعرض صفحة لم تُمس. لا خطأ ولا تحذير ولا سطر سجل
الفخ الأول: /ShadingType عدد لا اسم
يعرف الجدول 78 في ISO 32000-1 خاصية /ShadingType عدداً صحيحاً: 2 للمحوري و3 للقطري. بثت أول نسخة من هذا الكاتب /ShadingType /Axial لأنها قرأت أفضل في تفريغ سداسي. تقرأ PDFium المفتاح بجالب عدد صحيح، فتنال 0 لاسماً، وتصنف التظليل غير صالح، وتفشل في تحميله، وتتخطى معامل sh دون شكوى. عُرضت الصفحة فارغة ولم يقل شيء في PDFium لماذا. لم يصبح السبب مرئياً إلا بعد بناء PDF يدوي مصغر وفتحه في عارض ثانٍ مستقل، أبلغ عن نوع تظليل مجهول يساوي 0. ويتعمم الدرس على كل كاتب PDF: عندما تصف المواصفة تعداداً عدداً صحيحاً، اكتب العدد الصحيح، مهما بدت الاسم الموصف لنفسه مغرياً
الفخ الثاني: تدفق محتوى لا يحوي إلا sh بلا صندوق محيط
معامل sh (ISO 32000-1 §8.7.4.2) يرسم التظليل فوق منطقة القص الجارية، فيحده مدى الصفحة نفسها على الورق. لكن PDFium يبني كائن صفحة تظليل يأتي مستطيله إما من مسار القص النشط وإما من الصندوق المحيط المتراكم لمعاملات الرسم السابقة في التدفق نفسه، وتلغي العارضة الكائنات ذات المستطيل الفارغ. تدفق ملحق حديث لا يحوي سوى /Sh1 sh ليس لهما، فيُهجر التدرج قبل التنقيط. لذلك تقرأ TPdf.SaveAsShadingCreated صندوق القص لكل صفحة (مع اللجوء إلى صندوق الوسائط ثم إلى حجم الصفحة) وتلف كل استدعاء بقص re W n صريح. على صفحة US Letter بأصلها عند الصفر يبدو التدفق الملحق هكذا:
q
0 0 612 792 re W n
/HeaderBand sh
Q
يعزل زوج q/Q القص وأي تحويل عن محتوى الصفحة القائم، فلا يرثهما ما يُرسم لاحقاً على الصفحة. وإن كتبت استدعاءات تظليل بنفسك في أداة أخرى فانسخ هذا الشكل: قص صريح في التدفق نفسه رخيص ويزيل الاعتماد على ما سبقه
كيف يتفاعل مصفوفة تدرج مخصصة مع القص؟
تُطبق مصفوفة التدرج في PDFium Component بوصفها معامل cm يوضع بعد القص وقبل sh، فيبقى القص بكامل الصفحة بينما تتحول هندسة التدرج فقط. اضبط HasMatrix على True واملأ MatrixA حتى MatrixF، الأعداد الستة نفسها لأي مصفوفة تحويل PDF. يجب أن تكون القيم منتهية والمحدد غير صفري، لأن مصفوفة شاذة ستطوي التظليل على خط. يسجل المكوّن أيضاً القيم الست بوصفها /Matrix في قاموس التظليل، لكن الاستدعاء المباشر لـ sh هو ما تضبطه الـ cm في تدفق المحتوى نظامَ إحداثيات يرسم فيه PDFium
الترتيب مهم هنا، وهو الفخ الثالث. تُعامل معاملات re في مصفوفة التحويل الجارية. ضع الـ cm أولاً فيُقاس مستطيل القص بحجم الصفحة ويُدوَّر ويُمَال مع التدرج، فمصفوفة تسحق قطع ناقص تسحق أيضاً المنطقة المرئية وتفقد أجزاء من الصفحة تعبئتها. القص أولاً في فضاء المستخدم غير المحول ثم ضم المصفوفة يعطي النتيجة المتوقعة: تدرج متحول يغطي الصفحة كلها. وتشرح دليل أختام Form XObject ومصفوفتها ذات الأعداد الستة ما يفعله كل مكوّن مصفوفة إن احتجت بناء واحدة
procedure MakeEllipticalGlow(var Fill: TPdfShadingCreate);
begin
BuildSealGlow(Fill); // تعبئة قطرية مركزة عند 450,160
Fill.Coords := [0, 0, 0, 0, 0, 90]; // دائرات عند الأصل
Fill.HasMatrix := True;
Fill.MatrixA := 1.6; // مدّ أفقياً
Fill.MatrixB := 0;
Fill.MatrixC := 0;
Fill.MatrixD := 0.8; // اضغط رأسياً
Fill.MatrixE := 450; // ثم انقل إلى مركز الختم
Fill.MatrixF := 160;
end;
قراءة التقرير ومعرفة الحدود
يخبرك TPdfShadingCreateReport بما كُتب: SourceByteCount و OutputByteCount، و CreatedShadingObjectNumbers (رقم كائن واحد لكل مدخل، بالترتيب)، و AttachedShadingCount و Succeeded و ErrorMessage. تسمي أخطاء التحقق المدخل والحقل، مثل قائمة محطات ليست متزايدة تماماً، فتستحق التسجيل حرفياً
if Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
Writeln(Format('%d shading(s) attached, %d -> %d bytes',
[Report.AttachedShadingCount, Report.SourceByteCount,
Report.OutputByteCount]))
else
Writeln('Rejected: ' + Report.ErrorMessage);
الملف التعريفي محدود عن قصد. ينشئ تظليلات محورية وقطريّة فقط؛ وتُرفض التظليلات الدالية والشبكية (الأنواع 1 و4 إلى 7) وفضاءات ألوان الأنماط. تقبل الدعوة الواحدة من 1 إلى 4096 تعبئة، ويجب أن توجد أرقام الصفحات، وتُقصر أسماء الموارد على 127 محرفاً من الحروف والأرقام والشرطة السفلية والناقصة والنقطة. ويُرفض المدخل المشفر عند مرحلة المقطع الختامي. المستند الموقع يحتاج AllowSignedDocument := True، وبما أن المخرج إعادة كتابة تامة لا تحديث تدريجي فلن تتحقق التواقيع القائمة بعد ذلك، فوقّع بعد إضافة التدرجات. تُرسم كل تعبئة في نهاية محتوى الصفحة فوق ما هو موجود؛ ولمفعول خلفية أنشئ التدرج أولاً وأضف النص والصور بعده، أو استخدم المحتوى الاختياري كما في مقالة التحكم بطبقات المحتوى الاختياري في PDF داخل Delphi عندما ينبغي أن تكون التعبئة قابلة للتبديل
FAQ
هل يدعم SaveAsShadingCreated التدرجات متعددة المحطات؟
نعم. مرر من 2 إلى 256 مدخلاً في Stops بمواضع متزايدة تماماً من 0 إلى 1. محطتان تنتجان دالة من النوع 2، وثلاث أو أكثر تنتج دالة ربط من النوع 3 بمصفوفتي /Bounds و /Encode مشتقتين
لماذا يختفي تدرجي في PDFium والملف يبدو صالحاً؟
تحقق أن /ShadingType هي العدد الصحيح 2 أو 3 لا اسماً، وأن معامل sh يجري داخل قص re W n في تدفق المحتوى نفسه. تتخطى PDFium بصمت أنواع التظليل غير الصالحة وتلغي كائنات التظليل ذات المستطيل الفارغ
أي فضاءات ألوان يمكن للتظليل المنشأ استخدامها؟
يقبل TPdfShadingCreate DeviceGray و DeviceRGB و DeviceCMYK و CalGray و CalRGB و Lab و ICCBased بملف تعريفي من 1 أو 3 أو 4 مكونات. فضاءا Pattern و Indexed ليسا جزءاً من ملف الإنشاء
إنشاء التدرجات والمحتوى الاختياري وبقية الكاتبين المرصوصين متوفرة مع PDFium Component لـ Delphi و C++Builder، الذي يغلّف PDFium لمشاريع Delphi و C++Builder و Lazarus