تخطَّ إلى المحتوى

العقود / الطباعة

يعرّف مجال الطباعة عقود سجل الخطوط والمعالجة المسبقة للنص: FontRegistryInterface، وTextPreprocessorInterface، وكائنَي القيمة غير القابلين للتغيير TextPreprocessResult وTextSegment. جميعها stable.

Terminal window
composer require nextpdf/core:^3

FontRegistryInterface هو مخزن الخطوط الذي يمتد طوال عمر العملية. يسجّل خطاً من نوع ⁨TrueType⁩ أو ⁨OpenType⁩ أو ⁨TrueType Collection⁩ ‏(⁨TTC⁩) أو ⁨Printer Font Binary⁩ ‏(⁨PFB⁩)، ويعيد بيانات FontInfo الوصفية بعد تحليلها. ولأن السجل يبقى أطول عمراً من المستندات الفردية، يحلّل العامل كل خط مرة واحدة فقط. يمكنك تحميل مجموعة من الخطوط مسبقاً عند الإقلاع، ثم قفل السجل حتى لا تستطيع حركة الإنتاج تغييره. يطرح السجل المقفل استثناء LogicException عند استدعاء register() أو addFontDirectory() أو warmup()؛ وتبقى عمليات البحث متاحة. يقبل السجل أيضاً بايتات الخط الخام عبر registerFromBinary(). يستخدم جسر @font-face هذه الطريقة لتسجيل خط جُلب من مصدر بعيد أو من ⁨data URI⁩ ‏(معرّف موارد موحّد). يخزّن السجل بيانات ⁨PHP⁩ خالصة فقط، دون أي مقابض موارد، لذلك يمكن مشاركته عبر مجمع عمال.

يُضمّن المحرك كل خط يستخدمه وينشئ منه مجموعة فرعية. ينتقل برنامج الخط المضمّن داخل ملف ⁨PDF⁩ ‏(⁨Portable Document Format⁩)، فيُعرض المستند بالشكل نفسه في أي عارض، بصرف النظر عن خطوط النظام المثبّتة — ⁨ISO 32000-2⁩ §9. لا تحمل المجموعة الفرعية من الخط إلا المحارف الرسومية التي يشير إليها المستند فعلياً. وتزداد أهمية ذلك مع المحتوى الصيني والياباني والكوري ‏(⁨CJK⁩) أو غيره من المحتوى الغني بمحارف ⁨Unicode⁩ — ⁨ISO 32000-2⁩ §9. يكشف عقد السجل البيانات الوصفية المُحلّلة التي تستخدمها مرحلتا إنشاء المجموعة الفرعية والتضمين.

يعترض TextPreprocessorInterface النص قبل دخوله إلى تخطيط المحارف الرسومية، وإنشاء المجموعة الفرعية من الخط، وخريطة محارف ⁨ToUnicode⁩ ‏(⁨CMap⁩)، وشجرة البنية. هذا الموضع هو الخاصية الأمنية: فالمعالج المسبق الذي ينقّح المحتوى يزيله قبل أن يصل إلى دفق المحتوى، أو المجموعة الفرعية من الخط، أو البيانات الوصفية. يحمل العقد ثابتَين لا يتغيّران. يجب ألا يُدخل المعالج المسبق محارف تؤثر في التخطيط، ويجب أن يحافظ على ترتيب القراءة المنطقي؛ فمسؤوليته هي استبدال المحتوى، لا التخطيط. النتيجة هي TextPreprocessResult غير قابل للتغيير، ويضم قائمة مرتبة من قيم TextSegment. يكون المقطع إما عابراً دون تغيير وإما منقّحاً. في المقطع المنقّح، يعتمد نص العرض على وضع الإخفاء: يكون فارغاً في حالة المستطيل الأسود، أو نجمات تطابق الطول الأصلي، أو تسمية ثابتة. قيمة originalCharCount في المقطع هي تلميح قياس غير قابل للعكس، يُستخدم فقط لتحديد حجم مستطيل التنقيح. يجب ألا تُستخدم أبداً لإعادة بناء المحتوى الأصلي.

النوعالصنفالأعضاء الرئيسيونالاستقرارمنذ
FontRegistryInterface⁨interface⁩register()، get()، has()، all()، addFontDirectory()، warmup()، lock()، isLocked()، registerBase14()، registerFromBinary()، memoryUsage()⁨stable⁩1.7.0
TextPreprocessorInterface⁨interface⁩process(string): TextPreprocessResult⁨stable⁩1.9.0
TextPreprocessResult⁨final readonly class⁩$segments، hasRedactions()، getDisplayText()⁨stable⁩1.9.0
TextSegment⁨final readonly class⁩$displayText، $isRedacted، $originalCharCount، $fillColor⁨stable⁩1.9.0

يُجمّد TextPreprocessResult وTextSegment تواقيع باني كل منهما وخصائصه العامة؛ ويجوز إضافة طرائق جديدة، لكن لا يجوز تغيير الخصائص.

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

يحلّ setFont() عائلة الخط عبر FontRegistryInterface. يستخدم المستند المستقل سجلاً خاصاً. في العامل، شارك سجلاً واحداً؛ راجع صفحة المستند.

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

استدعاء warmup() متبوعاً بـlock() هو تسلسل إقلاع العامل. بعد lock()، يطرح أي تغيير استثناءً. وتستمر عمليات البحث في خدمة الحركة.

  • يرفض السجل المقفل كل طريقة تغيير. حمّل السجل مسبقاً واقفله عند الإقلاع؛ ولا تستدعِ register() أبداً أثناء معالجة الطلب.
  • يكتب registerFromBinary() بايتات الخط إلى ملف مؤقت قبل التحليل. بيانات الخط غير الموثوقة سطح هجوم للمحلل — قيّدها عبر ExternalResourcePolicyInterface ‏(راجع صفحة سياسة الأمان).
  • يجب ألا يضيف TextPreprocessor فواصل أسطر أو محارف إرجاع أو علامات جدولة. تغيّر هذه المحارف التخطيط وتنتهك ثابت العقد الأول.
  • TextSegment::$originalCharCount هو تلميح عرض فقط. استخدامه لاستنتاج المحتوى الأصلي يُبطل التنقيح وينتهك ثابت العقد الثالث.
  • يعيد TextPreprocessResult::getDisplayText() سلسلة فارغة لمقاطع المستطيل الأسود عن قصد في التصميم. لا تعامل المقطع الفارغ على أنه فشل في المعالجة المسبقة.

يهيمن تحليل الخط على الاستخدام الأول؛ ويوزّع السجل تلك التكلفة بحيث تُدفع مرة واحدة في كل عملية. بعد التحميل المسبق، يكون get() وhas() عمليتَي بحث في الخريطة بزمن ⁨O⁩(1). يعيد memoryUsage() كائن MemoryReport حتى يتمكن العامل من تتبع ذاكرة التخزين المؤقت للخطوط مقابل ميزانيته. المعالجة المسبقة للنص خطية بالنسبة إلى طول المدخلات. تضيف قائمة المقاطع عبئاً محدوداً يتناسب مع عدد مطابقات التنقيح. تغطي قيمة performance_budget البالغة 1500 ⁨ms⁩ زمناً جدارياً و64 ⁨MB⁩ ذروةً التحميلَ المسبق لمجموعة خطوط نموذجية إضافة إلى عرض المستند. تتناسب تكلفة إنشاء المجموعة الفرعية مع عدد المحارف الرسومية المستخدمة فعلياً، لا مع جدول المحارف الرسومية الكامل للخط. لذلك يقلّل إنشاء المجموعة الفرعية حجم المخرجات وتكلفة العرض للمحتوى ⁨CJK.⁩

لمجال الطباعة سطحان متعلقان بالأمان. الأول هو إدخال الخط: يحلّل registerFromBinary() بايتات عشوائية. يجب أن تمر بيانات الخط غير الموثوقة عبر ExternalResourcePolicyInterface يحدّ حجم الملف وعدد المحارف الرسومية قبل وصولها إلى المحلل. الثاني هو التنقيح: يعمل TextPreprocessorInterface قبل تخطيط المحارف الرسومية، وإنشاء المجموعة الفرعية من الخط، و ⁨CMap⁩ الخاصة بـ ⁨ToUnicode⁩، وشجرة البنية، لذلك لا يدخل المحتوى المنقّح أبداً في القطعة المعروضة. يسرّب التنقيح بطبقة فوقية وقت الرسم النص الأصلي في دفق المحتوى وفي المجموعة الفرعية. يمنع موضع العقد هذا الصنف من العيوب. تلميح القياس في المقطع غير قابل للعكس عن قصد. عامل أي خط أو نص يأتي من مصدر خارجي على أنه غير موثوق.

الادعاءالمعيارالبندالدليل
يُضمَّن كل خط يستخدمه المستند بحيث يُعرض المستند دون الاعتماد على خطوط النظام.⁨ISO 32000-2⁩§9
يُختصر الخط المضمّن إلى مجموعة فرعية تقتصر على المحارف الرسومية التي يشير إليها المستند.⁨ISO 32000-2⁩§9

كلا البندين معاد صياغته. لا يستنسخ ⁨NextPDF⁩ النص المعياري. يفرض ⁨PDF/A-4⁩ تضمين كل خط. هذه المطابقة موثّقة في صفحتَي الاستخراج وإمكانية الوصول.