التظليل التهشيري الذي يُعرض ككتلة رمادية مسطحة واحدة هو الفشل الكلاسيكي لأنماط التبليط. يرسم HotPDF، مكوّن VCL الأصلي لـ PDF في Delphi وC++Builder، النوع PatternType 1 بتحويل المسار الحالي إلى تحديد حواف مؤقت وإعادة تشغيل دفق محتوى النمط مرة واحدة لكل بلاطة مرئية، مع الاحتفاظ باختيار النمط في حالة الرسوميات واستعادته بـ q وQ
الأعراض تصل بنكهتين، وتبدوان غير مرتبطتين حتى تعرف السبب. رسم CAD يفقد تظليله المقطعي ويعود كتعبئات صلبة، لأن العارض حلّ النمط إلى لون متوسط ورسم ذلك. أو يتسرّب التظليل: كتلة عنوان يُفترض أن تكون بيضاء صرفة تلتقط الخطوط القطرية من عرض تفصيلي قبل مسار أو اثنين. كلتاهما مشكلتا حالة نمط، وواحدة منهما فقط تتعلق برسم البلاط أصلًا
لماذا يتسرّب نمط تبليط إلى المسار التالي؟
لأن اسم النمط المختار جزء من حالة الرسوميات، لا خاصية للمُشغِّل الذي استخدمه. تُعرّف الفقرة §8.6.6.2 من ISO 32000-1 مساحة لون النمط بأنها واحدة تكون فيها قيمة اللون اسم نمط مُقدَّم إلى scn أو SCN، وكل مكوّن آخر من حالة اللون يُحفظ بـ q ويُستعاد بـ Q. اسم النمط يجب أن يتبع نفس القاعدة. يحتفظ HotPDF به في سجل الحالة كـ FillPatternName وStrokePatternName، جنبًا إلى جنب مع عائلة مساحة لون التعبئة والحد، بحيث تُعيد Q الاختيار السابق بالضبط كما تُعيد CTM السابقة
خزّن ذلك الاسم في متغيّر محلي داخل مُرسِل المُشغِّل بدلًا من ذلك، وسيبقى ناجيًا عبر كل Q في الدفق. الفشل عندئذٍ يظهر في مكان غير متوقع: كائن نموذج XObject يُرسم بعد المسار المُنمَّط يرث اختيار نمط لم يفعله دفق محتواه الخاص أبدًا، وتظهر تعبئاته مُهشَّرة. النماذج المتداخلة تجعل الأمر أسوأ، لأن كل مستوى تداخل يدفع ويسحب حالة يتجاهلها المتغيّر الشارد. تعيين مساحة لون غير نمط بـ cs أو CS، أو إصدار g / rg / k عادي، يجب أيضًا مسح اسم النمط، وإلا فإن الاختيار المتقادم سيبقى بعد مساحة اللون التي أعطته معناه
q
/Pattern cs % pattern colour space, ISO 32000-1 8.6.6.2
/P1 scn % coloured tiling pattern, PaintType 1
10 10 200 120 re f % this rectangle is hatched
Q
0 0 300 200 re f % must be black again, not hatched
q
/Cs2 cs % [/Pattern /DeviceCMYK] array
0 0.6 1 0 /P2 scn % uncoloured pattern plus its underlying colour
20 20 160 90 re f*
Q
يُرسم النمط عبر تحديد حواف، لا كتعبئة إطلاقًا
النموذج الصحيح طرحي: قيّد تحديد حواف الجهاز إلى الشكل قيد الرسم، ثم شغّل محتوى النمط داخله. لا يرسم HotPDF أبدًا تقريبًا صلبًا أولًا ثم يرسم فوقه، لأن الصلب الوسيط سيكون مرئيًا عبر الفجوات بين البلاطات وسيتصادم مع أي شفافية في محتوى البلاطة. تصف الفقرة §8.7.3.2 نمط التبليط كدفق محتوى مُكرَّر بفواصل أفقية ورأسية ثابتة، والتكرار لا معنى له إلا مقابل تحديد حواف له الشكل الصحيح بالفعل. للتعبئات، التحويل مباشر: تُعيّن HPDFSelectFillPathClip نمط تعبئة المضلع إلى ALTERNATE لـ f* وB* وb* وإلى WINDING للمتغيرات غير الصفرية، وتبني مسار GDI، وتتقاطع معه في التحديد بـ SelectClipPath. ذلك السطر الواحد هو ما يجعل تعبئة مُنمَّطة زوجي-فردي تترك نفس الثقوب التي تتركها تعبئة صلبة زوجي-فردي، وهو بالضبط ما تحتاجه منطقة مُهشَّرة على شكل دونات
الحدود هي الجزء الذي يسهل الخطأ فيه. المسار المحدود بحدّ ليس له داخل، لذا تقاطع المسار نفسه في التحديد يُنتج منطقة فارغة ولا يُرسم شيء. لذا تبني HPDFSelectStrokePathClip قلمًا هندسيًا من الحالة الحالية أولًا، مستخدمةً PS_GEOMETRIC مع نهاية الغطاء من J، ووصلة من j، وحد الزاوية من M، وPS_USERSTYLE عندما تكون مصفوفة تقطيع مفعّلة، ثم تستدعي WidenPath لتحويل المخطط المحدود إلى منطقة قابلة للتعبئة قبل التحديد. سلوك الغطاء والوصلة والزاوية والتقطيع على مسار مُحدَّد مُنمَّط يطابق حينها حدًا عاديًا بالبناء لا بتنفيذ ثانٍ. حدّان صادقان يعيشان هنا: عروض الخطوط تحت وحدة جهاز واحدة تُقيَّد إلى بكسل واحد، ومصفوفة التقطيع تُقتطع عند ستة عشر مدخلًا، وهو السقف الذي تقبله ExtCreatePen
أي البلاطات مرئية فعليًا؟
النطاق المرئي يأتي من تشغيل التحويل بالعكس. وضع البلاط يحدث في مساحة النمط، لكن الشيء الوحيد الذي يعرف كم من الصفحة يُمسّ هو صندوق تحديد حواف الجهاز، الذي يقع في مساحة الجهاز. يُركّب HotPDF BaseMatrix := CTM * PatternMatrix، ويعكسه، ويُخطّط زوايا صندوق تحديد حواف GDI الأربع إلى الخلف عبر المعكوس. الحدود المحاذية للمحاور لتلك الزوايا الأربع المُخطَّطة تُعطي مستطيل مساحة النمط الذي يمكن تغطيته على الأكثر، وقسمة ذلك المستطيل على XStep وYStep مقابل BBox النمط يُعطي نطاقات فهرس مغلقة. كل خلية تُعرض بعد ذلك بـ CTM يساوي CTM * PatternMatrix * Translate(i * XStep, j * YStep)، وتُحدَّد حوافها مرة ثانية إلى مضلع BBox المُحوَّل الخاص بها. ذلك التحديد الثاني مهم عندما يكون XStep أصغر من عرض صندوق الإحاطة، وهي الطريقة التي تُعبَّر بها تصاميم البلاط المتداخلة؛ بدونه، ستُرسم الخلايا المجاورة فوق بعضها خارج نطاقها المُعلَن. إذا عاد تحديد الحواف لكل خلية كـ NULLREGION، تُتخطَّى الخلية دون ترميز أو تنفيذ أي شيء
// Map the device clip box back into pattern space through the inverse of
// CTM * PatternMatrix, then convert those bounds into tile index ranges.
BaseMatrix := HPDFMatMul(FGSStack.State.CTM, PatternMatrix);
if not HPDFMatInvert(BaseMatrix, InverseMatrix) then Exit; // singular: refuse
if GetClipBox(FDC, ClipRect) = ERROR then Exit;
// MinX..MaxY are the axis-aligned bounds of the four mapped clip corners.
I0 := Floor((MinX - BBox[2]) / StepXAbs);
I1 := Ceil ((MaxX - BBox[0]) / StepXAbs);
J0 := Floor((MinY - BBox[3]) / StepYAbs);
J1 := Ceil ((MaxY - BBox[1]) / StepYAbs);
PlannedTiles := Int64(I1 - I0 + 1) * Int64(J1 - J0 + 1);
if (PlannedTiles <= 0) or (PlannedTiles > FPatternTilesRemaining) then Exit;
Dec(FPatternTilesRemaining, Integer(PlannedTiles));
الأنماط غير الملوَّنة واللون القادم من الخارج
نمط PaintType 2 يحمل شكلًا بلا لون، واللون يصل مع اسم النمط. تُحدّد الفقرة §8.7.3.2 أن النمط غير الملوَّن يُستخدم فقط مع مساحة لون نمط تُعلن مساحة أساسية، لذا يستقبل scn قيم المكوّنات أولًا واسم النمط أخيرًا. يحلّ HotPDF تلك المكوّنات عبر المساحة الأساسية المُخزَّنة في مدخل مساحة لون النمط، مما يعني أن تهشيرًا غير ملوَّن يمكن تلوينه بحبر Separation أو دمج DeviceN تمامًا كأي تعبئة أخرى؛ آلية ذلك الحل مشروحة في عرض ألوان النقطة Separation وDeviceN. داخل البلاطة، نوعا الرسم يتباعدان بشكل حاد. لـ PaintType 2 يُعيّن العارض علامة كبت عامل تشغيل اللون طوال مدة البلاطة، بحيث تُتجاهل أي g أو rg أو k أو scn في محتوى النمط وتأخذ كل علامة اللون المُقدَّم خارجيًا. لـ PaintType 1 يُطبَّق العكس: تُعاد تعيين حالة التعبئة والحد إلى افتراضيات PDF، أسود DeviceGray بمساحة لون هوية، وتُلوّن البلاطة نفسها. تخطّي تلك الإعادة يسمح للون الذي صدف أنه كان حاليًا عند مُشغِّل f بالتسرب إلى نمط كان مفترضًا أن يصف نفسه بنفسه
لماذا يجب استعادة عمق مكدس حالة الرسوميات بعد كل بلاطة؟
لأن دفق محتوى النمط يُسمح له بأن يكون غير متوازن، والضرر يتراكم عبر الخلايا. بلاطة يحتوي دفقها ثلاثة مُشغِّلات q ومُشغِّلين Q تترك المكدس بعمق إطار واحد أكبر مما بدأت. استعِد فقط سجل الحالة الحالي بين الخلايا وسيستمر العمق في النمو، بحيث تُنفَّذ الخلية رقم مئتين من إطار مكدس ينتمي إلى الخلية رقم مئة وتسعة وتسعين، بأي CTM وتحديد حواف حملهما ذلك الإطار. لذا يلتقط HotPDF لقطة لسجل الحالة وعمق المكدس قبل حلقة البلاط ويستدعي RestoreSnapshot في أعلى كل تكرار، الذي يقتطع المكدس إلى الطول المحفوظ ويُعيد تركيب الحالة المحفوظة في خطوة واحدة. تُستعاد قاموس موارد الصفحة وعلامة كبت عامل تشغيل اللون عند نفس الحد، لأن بلاطة قد تشير إلى مواردها الخاصة ويجب ألا تُسلّمها لجارتها. تحصل حالة تحديد حواف GDI على نفس المعالجة عبر زوج SaveDC / RestoreDC حول كل خلية، بحيث لا تستطيع بلاطة تُثبِّت تحديد حواف W n خاصًا بها تقليص المنطقة المتاحة للتالية
الميزانيات، والرفض، وما لن يرسمه العارض
أنماط التبليط هي المكان الأسهل في PDF لكتابة ملف حرمان خدمة، لذا الحدود أرقام صارمة لا استدلالات. تداخل الأنماط محدود بعمق 4، نفس الحارس المستخدم لعودية كائن النموذج XObject، الذي يوقف نمطًا يشير إلى نفسه عبر قاموس موارده الخاص. رسم مسار واحد قد يُنفِّذ 16,384 بلاطة كحد أقصى إجمالًا، مُعدودة تنازليًا عبر الأنماط المتداخلة ومُعاد ضبطها فقط عند بدء رسم النمط الأخارجي. شبكة بلاط يتجاوز عدد خلاياها المخطَّط ما تبقى من تلك الميزانية تُرفَض تمامًا، قبل تشغيل خلية واحدة
الهندسة المتدنية تُرفَض بدلًا من تقريبها. BBox مفقود أو صفري المساحة، أو XStep أو YStep بحجم أقل من 1e-6، أو ناتج CTM * PatternMatrix بلا معكوس، أو إحداثيات تحديد حواف مُخطَّطة تتجاوز 1e9، أو حجم فهرس يتجاوز مليونًا، كل هذه تجعل رسم النمط يعود دون رسم. النتيجة منطقة غير مرسومة بدلًا من خيط عرض معلَّق، وهذه هي المفاضلة التي تريدها في مُحوِّل دفعي. الأداء يأتي من قرار واحد: يُرمَّز دفق النمط مرة واحدة لكل عملية رسم بـ HPDFTokenizeContentStream وتُعاد استخدام مصفوفة الرموز عبر كل خلية مرئية، بحيث يُضاعف عدد البلاط تكلفة التنفيذ لكن لا يُضاعف أبدًا تكلفة التحليل اللفظي
عرض صفحة مُنمَّطة من Delphi
لا شيء في دعم الأنماط يُغيّر كود الاستدعاء. حمّل المستند، اطلب صفحة، ويحدث عمل التبليط داخل مُفسِّر دفق المحتوى الذي يُشغّله بالفعل عرض الصفحة إلى صورة نقطية. نفس المُفسِّر يُغذّي سياقات أجهزة الصورة النقطية والملف الوصفي والطابعة، بحيث يطبع رسم مُهشَّر يبدو صحيحًا في صورة مصغّرة للمعاينة بنفس هندسة البلاط. أنماط التظليل PatternType 2 تأخذ فرعًا مختلفًا يشارك مسار تقييمه مع مُشغِّل sh المجرد، المشروح بالتفصيل تحت عرض التظليلات المحورية والشعاعية
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('assembly-drawing.pdf') > 0 then
begin
// Section hatching that previously flattened to a solid block now
// replays the tile content once per visible cell.
Bmp := Pdf.RenderLoadedPageToBitmap(0, 200);
if Assigned(Bmp) then
try
Bmp.SaveToFile('sheet1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
عندما تبدو منطقة مُنمَّطة خاطئة، تحقّق من فئات الفشل الثلاث بالترتيب. منطقة فارغة تمامًا تعني عادةً رفضًا: افحص XStep وYStep وBBox بحثًا عن قيم متدنية، أو عدّ البلاطات التي تحتاجها الشبكة مقابل السقف 16,384. منطقة مرسومة بلون واحد مسطح تعني أن اسم النمط لم يصل أبدًا إلى مُشغِّل الرسم، وهذا يشير إلى ترتيب cs وscn في الدفق. نمط يظهر حيث لا ينتمي يعني استعادة حالة، والمكان الذي يجب النظر فيه هو معالجة q / Q حول النموذج أو المسار الذي ورثه
أنماط التبليط واحدة من ميزات PDF التي تبقى غير مرئية حتى يصل الملف الذي يحتاجها إلى صندوق واردك، وحينها تصبح المهمة بأكملها. إذا كنت تبني عارضات رسم، أو مُحوِّلات مستندات هندسية، أو عارضات تقارير على Delphi أو C++Builder، فالمكوّن الكامل وواجهته البرمجية للعرض موثَّقان على صفحة مكوّن HotPDF لـ Delphi