يصل طلب إلى مكتبك: خذ مجموعة من البيانات المعروضة بالفعل، وقم بتعتيم أرقام الحسابات، واشحن صفحتين لكل ورقة لتوفير الورق. كلا نصفي هذه المهمة هما جراحة تدفق المحتوى على ملف PDF لم تقم بإنشائه، لذا لا توجد لوحة صفحة ودية للرسم عليها ولا توجد إدارة خطوط للاعتماد عليها. أنت تقوم بتحرير الرسم البياني لكائن مستند محمل مباشرة، وإلحاق مشغلات رسم خام بصفحة صممتها أداة أخرى. ويكشف HotPDF عن نقطتي دخول لهذا الغرض، والأكثر خطورة منهما هي تلك التي تبدو غير ضارة
إن HotPDF هو مكون PDF أصلي لـ VCL لدلفي وC++Builder. وأضافت واجهة برمجة تطبيقات المستندات المحملة في الجولة التاسعة أولى الأساليب التي تنشئ محتوى جديداً تماماً على صفحة فتحتها من القرص بدلاً من صفحة بنيتها من الصفر. اثنان منها هما الموضوع هنا: RedactLoadedRect، الذي يرسم مستطيلاً معتماً فوق منطقة ما، وStitchLoadedPage، الذي يغير حجم صفحة ويرسمها على صفحة أخرى. يعمل كلاهما عن طريق كتابة مشغلات تدفق المحتوى ISO 32000-1 الفقرة 8.5 في تدفق /Contents للصفحة. وفهم ما تفعله تلك المشغلات، وما لا تفعله بنفس القدر من الأهمية، هو الفرق بين أداة العمل واختراق البيانات
لماذا لا تعمل حسابات نموذج PDF في PDFium؟
عندما تبني صفحة باستخدام واجهة برمجة تطبيقات HotPDF العادية، يمتلك المكون تدفق المحتوى ويسلسل استدعاءات TextOut والمتجهات من أجلك. والصفحة المحملة مختلفة: فتدفق /Contents الخاص بها هو كائن تدفق موجود بالفعل، وربما يكون مشتركاً أو جزءاً من مصفوفة محتوى, وعليك الربط فيه دون إفساد ما هو موجود بالفعل. وقدمت الجولة التاسعة ثلاثة مساعدين صغيرين يجعلون ذلك آمناً. حيث يخصص NewIndirectStream كائناً غير مباشر جديداً لـ THPDFStreamObject بمخزن مؤقت فارغ وإدخال /Length 0؛ ويتتبع ResolveLoadedStream مرجعاً غير مباشر وصولاً إلى التدفق أساسي؛ ويكتب AppendLoadedStream بايتات خام في نهاية التدفق ويعيد كتابة /Length بحيث يظل الكائن المحفوظ جيد التنسيق
النمط الذي تتبعه كلا الطريقتين العامتين هو نفسه. ابحث عن /Contents للصفحة، وحلله إلى تدفق، وإذا لم يكن هناك تدفق قابل للاستخدام، فأنشئ واحداً وأرفقه. ثم ألحق المشغلات. ولأن البايتات الجديدة تذهب في نهاية التدفق، فإن نموذج الرسام يضمن عرضها فوق كل ما رسمه التخطيط الأصلي. وهذا الترتيب هو الآلية بأكملها وراء مستطيل التعتيم، وهو أيضاً السبب في أن هذا المستطيل ليس ما يفترضه معظم الناس
RedactLoadedRect: غطاء معتم، وليس حذفاً
تأخذ RedactLoadedRect فهرس صفحة صفري الأساس، وأربعة إحداثيات لمساحة المستخدم، وثلاثة مكونات ألوان في النطاق 0-1:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('statement.pdf') > 0 then
begin
// Cover the account-number band on page 1 with solid black.
// Coordinates are PDF user space: origin bottom-left, points.
Pdf.RedactLoadedRect(0, 56, 690, 320, 706, 0, 0, 0);
Pdf.SaveLoadedDocument('statement-covered.pdf');
end;
finally
Pdf.Free;
end;
end;
تحت الغطاء، تصدر الطريقة ثلاثة مشغلات في تدفق المحتوى: تعيين لون التعبئة في DeviceRGB (وهو r g b rg)، ومسار مستطيل (وهو x y w h re)، وتعبئة (وهي f). ويتم اشتقاق العرض والارتفاع كـ X2 - X1 وY2 - Y1، لذا تقوم بتمرير زاويتين متقابلتين وتترك الطريقة تحسب المدى. مرر 0, 0, 0 للون وستحصل على شريط أسود؛ مرر 1, 1, 1 لشريط أبيض يطابق صفحة بيضاء. والإحداثيات هي مساحة المستخدم الخاصة بالصفحة المحملة، مما يعني أن الأصل هو الزاوية اليسرى السفلية والوحدات هي النقاط، ويعني أيضاً أنك بحاجة إلى /MediaBox للصفحة لوضع أي شيء بدقة؛ وتمنحك GetLoadedPageBox مع pbMediaBox ذلك
اقرأ هذا مرتين: يغطي المستطيل المملوء المحتوى بصرياً، ولا يزيله. النص أو الصورة أو الرسم المتجه الموجود أسفل المستطيل لا يزال موجوداً في ملف PDF، ولا يزال في الرسم البياني للكائن، ولا يزال قابلاً للاستخراج من قبل أي شخص ينسخ الصفحة أو يشغل مستخرج نصوص أو ببساطة يحذف مستطيلك من تدفق المحتوى. هذا قناع مرئي، وليس تعتيماً (redaction) بالمعنى القانوني أو الأمني. وإذا كنت تخفي بيانات حساسة حقاً — أرقام حسابات، أو سجلات طبية، أو هويات، أو أي شيء خاضع للتنظيم — فإن تغطيتها بصندوق أسود وشحن الملف هو تسريب بيانات ينتظر من يكتشفه. يتطلب التعتيم الحقيقي حذف كائنات المحتوى الأساسية، وليس الطلاء فوقها
يقول اسم الطريقة "Redact" (تعتيم)، وهذا تحذير مفيد حول كيفية إساءة تفسير النتيجة، وليس وعداً بما تحذفه. والتنفيذ صادق بشأن هذا في تعليقه الخاص: حيث يسمي نفسه "بدائية التعتيم المرئي" ويشير إلى أن التعتيم الذي يزيل المحتوى يحتاج إلى مترجم لتدفق المحتوى يمر ويعيد كتابة المشغلات الحالية. ولا يفعل مسار المستند المحمل لـ HotPDF ذلك هنا. لذا فإن القاعدة الآمنة ضيقة: استخدم RedactLoadedRect للقناع التجميلي غير الحساس — إخفاء علامة مائية للمسودة، أو مسح منطقة قبل لقطة الشاشة، أو تغطية شعار قديم على نسخة إثبات داخلية. وفي اللحظة التي يكون فيها الشيء الموجود أسفل الصندوق مهماً إذا تم تسريبه، فإن هذه الطريقة هي الأداة الخاطئة، والحل الصحيح هو إعادة توليد المستند بدون البيانات أو استخدام خط أنابيب حقيقي لإزالة المحتوى
StitchLoadedPage: تغيير الحجم، الإزاحة، الرسم
تعتبر فرضية N-up مشكلة أكثر ودية لأنه لا يتم إخفاء شيء، بل يتم إعادة ترتيبه فقط. تأخذ StitchLoadedPage فهرس الصفحة المستهدفة، وفهرس الصفحة المصدر، وإزاحة X/Y، وعامل مقياس، وترسم الصفحة المصدر على الهدف عند ذلك الموضع والحجم:
// Overlay page 2 (index 1) onto page 1 (index 0),
// scaled to 70% and nudged up-right.
Pdf.StitchLoadedPage(0, 1, 40, 380, 0.7);
// Convenience 2-up: source page on the right half of the target.
Pdf.StitchLoadedPageSideBySide(0, 1);
سلسلة المشغلات التي تلحقها هي تسلسل تحويل ورسم قياسي: q لحفظ حالة الرسومات، ومصفوفة cm تحمل المقياس على القطر والإزاحة في فتحات الترجمة، و/StitchSrc Do لاستدعاء كائن خارجي، وQ لاستعادة الحالة. ويهمنا زوج q/Q: فهو يعزل التحويل بحيث لا تنزف الصفحة المخيطة نظام الإحداثيات الخاص بها إلى أي شيء يتم إلحاقه بعد ذلك. كما تحرس الطريقة الأخطاء الواضحة — الفهارس الخارجة عن النطاق، والهدف المساوي للمصدر، والمقياس غير الإيجابي (الذي تقيده عند 1.0) — وتخرج بصمت بدلاً من الرفع، لذا تحقق من مدخلاتك لأن الإجراء الفارغ الصامت يبدو متطابقاً مع النجاح
تعتبر StitchLoadedPageSideBySide ملاءمة رقيقة مقارنة بالطريقة العامة. فهي تقرأ عرض media-box للهدف، وتقسمه إلى النصف، وتستدعي StitchLoadedPage مع ذلك نصف العرض كإزاحة X ومقياس ثابت 0.5، مما يضع المصدر في النصف الأيمن. ويفترض ذلك الرقم 0.5 المرمز بشدة أن المصدر والهدف يشتركان في العرض; وإذا لم يفعلا، فلن يملأ المصدر نصفه بشكل نظيف، وستحتاج إلى StitchLoadedPage العامة مع مقياس تحسبه بنفسك من كلا الصندوقين
استراتيجية XObject المبسطة ومقايضتها مع معايير ISO
هنا يقوم التنفيذ باختصار متعمد تحتاج إلى معرفته قبل أن تثق في المخرجات عبر العارضين. فرضية N-up الصحيحة تغلف محتوى الصفحة المصدر في Form XObject — وهو كائن قابل للرسم قائم بذاته ينص معيار ISO 32000-1 الفقرة 8.10.1 على أنه يجب أن يحمل /Type /XObject، و/Subtype /Form، وصندوق القص /BBox الخاص به. ولا يبني غرز الجولة التاسعة لـ HotPDF هذا الغلاف. بدلاً من ذلك، فإنه يسجل قاموس الصفحة المصدر نفسه مباشرة تحت /Resources /XObject للهدف باسم StitchSrc، ثم يرسمه باستخدام Do. ويشترك قاموس الصفحة وForm XObject في ما يكفي من نموذج المحتوى الخاص بهما — حيث يشير كلاهما إلى تدفق محتوى وقاموس موارد — بحيث يعرض العديد من القراء النتيجة
ولكنه ليس Form XObject متوافقاً. فهو يفتقر إلى علامة /Subtype /Form وصندوق /BBox الخاص به، مما يعني أن المستهلك الصارم في حدود حقوقه لتجاهل Do أو قصه بشكل مختلف عما تتوقعه. وتقول الملاحظات التقنية (TechnicalNotes) لهذه الجولة ذلك صراحة: النهج "يعرض تحت معظم القراء" ولكنه "ليس Form XObject متوافقاً بشكل صارم مع ISO"، ويتطلب الامتثال الكامل توليف تدفق Form XObject حقيقي كخطوة منفصلة. لذا عامل مخرجات الغرز بالطريقة التي تعامل بها أي بناء غير متوافق: تحقق منه في العارضين المحددين الذين يشغلهم عملاؤك، وليس فقط العارض الموجود على جهازك، وإذا كنت بحاجة إلى ملفات PDF أرشيفية أو نظيفة من أدوات التحقق الصارمة، فلا تعتمد على هذا المسار. وينطبق نفس النظام على أي شيء تبنيه على الرسم البياني للكائن المحمل، ولهذا السبب يستحق فحص PDF المسبق في دلفي مكانه في خط إمداد الإصدار كلما قمت بتحويل المستندات برمجياً
كلتا الطريقتين هما أداتا تدفق محتوى، لذا فإن النموذج الذهني هو نفسه الذي تستخدمه للرسم المباشر. وإذا قمت ببناء صفحات من الصفر باستخدام المكون، فستبدو مشغلات المتجهات والألوان خلف هذه الاستدعاءات مألوفة من رسم لوحة HotPDF في دلفي؛ والفرق الوحيد هو أنك هنا تلحق بتدفق صممه شخص آخر بدلاً من تدفق تملكه. ضع ثلاثة حدود في اعتبارك:
- التعتيم تجميلي. ترسم
RedactLoadedRectفوق المحتوى ولا تحذفه أبداً. ولأي شيء حساس، أعد توليد المصدر أو استخدم إزالة المحتوى الحقيقي — فالصندوق الأسود ليس أماناً - الغرز غير متوافق بالتصميم. تتم الإشارة إلى الصفحة المصدر كـ pseudo-XObject بدون
/Subtype /Formو/BBoxالخاص بالفقرة 8.10.1، لذا أكد العرض في العارضين المستهدفين وتجنبه عندما يكون التحقق الصارم مطلوباً - الإحداثيات هي مساحة مستخدم الصفحة. أصل يسار سفلي، نقاط، مدفوعة بـ media box الخاص بالصفحة. اقرأ الصندوق باستخدام
GetLoadedPageBoxقبل وضع أي شيء، لأن الصفحة التي قمت بتحميلها قد لا تكون بالحجم الذي افترضته
يغطي الزوج، المستخدم ضمن تلك الحدود، تدفق عمل حقيقي: إعادة ترتيب الصفحات للطباعة، وقناع المناطق غير السرية، وكتابة النتيجة مرة أخرى باستخدام SaveLoadedDocument — كل ذلك بدون إعادة عرض كاملة. وتُشحن واجهة برمجة تطبيقات المستندات المحملة التي تتضمن بدائيات الغرز والقناع مع مكون HotPDF لدلفي وC++Builder، إلى جانب حقول النماذج والتعليقات التوضيحية وأساليب FDF من الجولة نفسها