يفتح مسّاح مخطط موقع ويريد إخفاء خطوط الكنتور مع إبقاء المرافق ظاهرة. يريد المراجع أن تكون التعليقات التوضيحية ذات الخط الأحمر مرئية على الشاشة ومخفية في النسخة المطبوعة. يتم شحن ورقة منتج بثلاث لغات من ملف واحد، ويختار القارئ اللغة المعروضة. تمثل الحالات الثلاث نفس ميزة PDF، واللوحة التي تتحكم بها في Acrobat تسمى الطبقات (Layers). الميزة التي تكمن وراء هذه اللوحة هي المحتوى الاختياري (optional content)، وهي ما يسمح لصفحة واحدة بحمل عدة طبقات مرئية مستقلة يقوم العارض بتشغيلها وإيقافها
يتم تحديد المحتوى الاختياري في ISO 32000-1 §8.11. وحدة الرؤية هي مجموعة محتوى اختيارية، وهي OCG، قاموس من نوع /OCG يحمل اسماً. يرتبط المحتوى المحدد في صفحة ما بمجموعة، ويقرر العارض ما إذا كانت تلك المجموعة معروضة حالياً أم لا. بنية ذات صلة، وهي قاموس عضوية المحتوى الاختياري أو OCMD، تجعل الرؤية تعتمد على مجموعة منطقية من عدة مجموعات، ولكن الحالة اليومية هي مجموعة واحدة مسماة تمثل طبقة واحدة. يربط المستند الآلية بأكملها من خلال إدخال كتالوج واحد، /OCProperties، الموضح تالياً
ما الذي يجب أن يحمله الكتالوج
مجموعة المحتوى الاختيارية OCG بمفردها تكون خاملة. لكي يقوم العارض بسرد طبقة وتذكر حالتها، يحتاج كتالوج المستند إلى قاموس /OCProperties، ويحدد §8.11.4 بالضبط ما يدخل فيه. هناك مصفوفة /OCGs تسمي كل مجموعة في الملف، وهناك إدخال /D يحمل التكوين الافتراضي. التكوين الافتراضي هو الجزء الذي يطبقه القارئ عند فتح الملف لأول مرة. يسجل المجموعات التي تبدأ قيد التشغيل والتي تبدأ متوقفة، والإدخالات المقفلة ضد تبديل المستخدم لها، ومن خلال مصفوفة /Order، كيفية ترتيب أسماء الطبقات وتداخلها في اللوحة
النتيجة العملية هي أن إنشاء طبقة ليس أبداً عملاً محلياً بحتاً. يجب أن يتم رسم المجموعة في الصفحة، ويجب أيضاً تسجيلها في بنية على مستوى الكتالوج لم تكن موجودة من قبل. يقوم PDFlibPas بكلا الأمرين نيابة عنك. الاستدعاء الأول الذي يُنشئ مجموعة يضيف إدخال /OCProperties إلى الكتالوج ويزرع التكوين الافتراضي، بحيث يتم رسم الطبقة وإدراجها دون مسك دفاتر منفصل من جانبك
لماذا يمكن لوضع التوافق حجب الميزة
قبل تشغيل أي كود طبقة، يقرر هدف التوافق الخاص بالمستند ما إذا كان المحتوى الاختياري قانونياً من الأساس. يمنع PDF/A-1، وهو ملف تعريف الأرشفة المحدد في ISO 19005-1، إدخال /OCProperties تماماً في §6.1.13. يتناسب هذا المنطق مع غرض التنسيق. يجب أن يعرض الملف الأرشيفي بشكل متطابق لكل قارئ في المستقبل البعيد، والمحتوى الذي يمكن للعارض تغيير رؤيته هو محتوى لا يكون مظهره ثابتاً، لذا يحظر ملف التعريف هذه البنية بدلاً من السماح بأرشيف غامض. يتخذ PDF/A-2 و PDF/A-3، المحددان في ISO 19005-2 و ISO 19005-3، وجهة النظر المعاكسة في §6.9 الخاص بهما ويسمحان بالمحتوى الاختياري، مع قواعد حول الرؤية الافتراضية
يظهر هذا الاختلاف مباشرة في API. عندما يكون المستند في وضع PDF/A-1، يرفض NewOptionalContentGroup إنشاء المجموعة ويُرجع صفراً، لأن تلبية الطلب سينتج ملفاً يفشل في التوافق المُعلن عنه. في وضع PDF/A-2 أو PDF/A-3، وفي ملفات PDF العادية غير المقيدة، ينجح الاستدعاء نفسه ويُرجع معرف مجموعة غير صفري. وبالتالي، النتيجة الصفرية ليست فشلاً عاماً يمكن فحصه لاحقاً؛ بل هي إخبار من المكتبة بأن مستوى التوافق النشط لا يملك مجالاً لهذه الميزة
var
Pdf: TPDFlib;
LayerID: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1); // PDF/A-1a: OCProperties forbidden
LayerID := Pdf.NewOptionalContentGroup('Utilities');
if LayerID = 0 then
// refused under PDF/A-1; not a transient error, the mode bans layers
ShowMessage('Optional content is not available in PDF/A-1 mode.');
finally
Pdf.Free;
end;
end;
حالتان لكل طبقة، وليس حالة واحدة
الطبقة ليست ببساطة مرئية أو غير مرئية. يسجل التكوين الافتراضي حالتها على الشاشة وحالة طباعة منفصلة، لأن §8.11.4 يميز ما يعرضه العارض عما ترسله عملية الطباعة. الاثنان مستقلان عن قصد. يمكن عرض علامة مائية لمسودة على الشاشة وإسقاطها من الورق، ويمكن إخفاء طبقة خط القطع على الشاشة وإرسالها إلى راسمة. دمج الاثنين سيجبر أحدهما على اتباع الآخر ويفقد بالضبط التحكم الذي توجد الميزة لتوفيره
يعرض PDFlibPas الزوج من خلال أداتي تعيين. يأخذ SetOptionalContentGroupVisible معرف المجموعة وعلامة، حيث يعني الواحد مرئياً والصفر يعني مخفياً، ويحكم الحالة الافتراضية على الشاشة. يأخذ SetOptionalContentGroupPrintable معرف المجموعة وعلامة لما إذا كان سيتم إرسال الطبقة عند طباعة المستند. أدوات الجلب المطابقة، GetOptionalContentGroupVisible و GetOptionalContentGroupPrintable، تُرجع كل منها واحد أو صفر، بحيث يمكنك قراءة حالة الشاشة والطباعة للطبقة بشكل منفصل بدلاً من استنتاج أحدهما من الآخر
بناء طبقتين على صفحة
يتبع إنشاء طبقة وملؤها ترتيباً ثابتاً. تقوم برسم محتوى الطبقة على الصفحة الحالية، ثم تستدعي SetContentStreamOptional بمعرف المجموعة، والذي يغلف تيار المحتوى الحالي للصفحة بحيث ينتمي كل ما تم رسمه حتى الآن إلى تلك المجموعة. نظراً لأن الاستدعاء يلتقط كل ما هو موجود على التيار في تلك اللحظة، فإن الانضباط هو وضع علامات طبقة واحدة، وتعيينها، وبعد ذلك فقط تبدأ الطبقة التالية. يضع المثال أدناه المرافق في الصفحة الأولى والخط الأحمر للمراجع في الصفحة الثانية، ويعين حالة الشاشة والطباعة لكل طبقة، ويحفظ
var
Pdf: TPDFlib;
FontID, UtilLayer, RedlineLayer: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument; // unconstrained PDF: layers allowed
Pdf.SetPageDimensions(595, 842); // A4 in points
FontID := Pdf.AddStandardFont(0); // Helvetica
Pdf.SelectFont(FontID);
// Layer 1: utilities, drawn then assigned to its own group
Pdf.SetTextColor(0.10, 0.30, 0.65);
Pdf.DrawText(72, 770, 'Utilities: water main, valve chamber');
UtilLayer := Pdf.NewOptionalContentGroup('Utilities');
Pdf.SetContentStreamOptional(UtilLayer);
Pdf.SetOptionalContentGroupVisible(UtilLayer, 1); // shown on screen
Pdf.SetOptionalContentGroupPrintable(UtilLayer, 1); // and on paper
// Layer 2: reviewer redline on a fresh page
Pdf.InsertPages(2, 1); // append one page after page 1
Pdf.SetTextColor(0.80, 0.10, 0.10);
Pdf.DrawText(72, 770, 'REVIEW: revise valve spec before issue');
RedlineLayer := Pdf.NewOptionalContentGroup('Reviewer markup');
Pdf.SetContentStreamOptional(RedlineLayer);
Pdf.SetOptionalContentGroupVisible(RedlineLayer, 1); // visible while reviewing
Pdf.SetOptionalContentGroupPrintable(RedlineLayer, 0); // never printed
Pdf.SaveToFile('SitePlan_Layers.pdf');
finally
Pdf.Free;
end;
end;
طبقة الخط الأحمر هي الحالة الجديرة بالملاحظة. يتم عرضها على الشاشة بحيث يرى المراجع الملاحظة، وعلامتها القابلة للطباعة هي صفر بحيث لا تحمل النسخة المطبوعة من نفس الملف أي نص مراجعة. عدم التماثل هذا هو الغرض الأساسي من إبقاء الحالتين منفصلتين
قراءة التكوين مرة أخرى
قراءة الطبقات هي مسيرة مختلفة عبر نفس البنية. بعد تحميل الملف، يُبلغ GetOptionalContentConfigCount عن عدد قواميس التكوين التي يحتفظ بها المستند؛ التكوين الافتراضي الأول هو معرف التكوين 1. ضمن التكوين، يعطي GetOptionalContentConfigOrderCount عدد الإدخالات في شجرة الترتيب، وتقوم بفهرستها من 1. لكل إدخال، يُرجع GetOptionalContentConfigOrderItemLabel نص العرض الخاص به ويُرجع GetOptionalContentConfigOrderItemLevel عمق التداخل الخاص به، بحيث يمكن إعادة بناء مخطط اللوحة مع الطبقات الفرعية ذات المسافة البادئة تحت العناوين حرفياً
كل إدخال له نوع أيضاً. يميز GetOptionalContentConfigOrderItemType مجموعة المحتوى الاختيارية الفعلية عن التسمية النصية العادية التي توجد فقط لتكون رأس قسم من الشجرة. هذا التمييز مهم لأن استعلامات الحالة لكل مجموعة تكون منطقية فقط للمجموعات الحقيقية. بالنسبة لإدخال مجموعة، يُبلغ GetOptionalContentConfigState عما إذا كان التكوين يبدأ بتشغيله أو إيقاف تشغيله أو يتركه دون تغيير، ويُبلغ GetOptionalContentConfigLocked عما إذا كان المستخدم ممنوعاً من تبديله. تقدم الحلقة أدناه شجرة الترتيب مع حالة كل مجموعة وحالة القفل، بمسافة بادئة حسب المستوى
var
Pdf: TPDFlib;
Cfg, Count, I, ItemType, GroupID, Indent: Integer;
Line: string;
begin
Pdf := TPDFlib.Create(nil);
try
if Pdf.LoadFromFile('SitePlan_Layers.pdf', '') = 0 then Exit;
if Pdf.GetOptionalContentConfigCount = 0 then Exit;
Cfg := 1; // the default configuration
Count := Pdf.GetOptionalContentConfigOrderCount(Cfg);
for I := 1 to Count do
begin
Indent := Pdf.GetOptionalContentConfigOrderItemLevel(Cfg, I);
Line := StringOfChar(' ', Indent * 2)
+ Pdf.GetOptionalContentConfigOrderItemLabel(Cfg, I);
ItemType := Pdf.GetOptionalContentConfigOrderItemType(Cfg, I);
if ItemType = 1 then // 1 = optional content group
begin
GroupID := Pdf.GetOptionalContentConfigOrderItemID(Cfg, I);
case Pdf.GetOptionalContentConfigState(Cfg, GroupID) of
1: Line := Line + ' [on]';
2: Line := Line + ' [off]';
3: Line := Line + ' [unchanged]';
end;
if Pdf.GetOptionalContentConfigLocked(Cfg, GroupID) = 1 then
Line := Line + ' (locked)';
end;
// ItemType = 2 is a text label heading; it has no per-group state
Writeln(Line);
end;
finally
Pdf.Free;
end;
end;
تفصيلان يبقيان هذه الحلقة صحيحة. فهرس الترتيب يعتمد على واحد، من 1 إلى العدد، ليطابق كيفية ترقيم المكتبة للشجرة داخلياً. ويتم تشغيل الاستدعاءات لكل مجموعة فقط عندما يكون نوع العنصر مجموعة، لأن التسمية النصية هي عنوان يحمل اسماً ومستوى ولكن ليس له حالة تشغيل أو إيقاف أو قفل للاستعلام عنها. تجاوز هذه الحماية وستطلب من تسمية حالة لا تملكها
أين يتناسب هذا
الطبقات هي آلية تقديم، لذا يجب على المحرك احترامها في كل مسار يعرض صفحة، والجانب الخاص بالعرض مغطى في دليلنا للعرض متعدد المحركات في Delphi. كما أنها تتقاطع مع بنية المستند، لأن اسم الطبقة هو نص موجه للمؤلف ويستفيد القارئ من مخطط طبقة منظم، والذي يتصل بالعمل في مقالنا حول ملفات PDF ذات العلامات وبنية إمكانية الوصول. كلاهما يقترن بواجهات برمجة تطبيقات المحتوى الاختياري الموضحة هنا، والتي تُشحن كجزء من مكتبة Delphi PDF إلى جانب الصفحة والنص والخط ومرافق التوافق التي تمت مناقشتها في مكان آخر في هذه المدونة