الطباعة: سجل الخطوط، التجزئة الفرعية، CMap، الترميز، BiDi
لمحة سريعة
قسم بعنوان «لمحة سريعة»تحوّل وحدة الطباعة ملف خط وسلسلة Unicode إلى البايتات اللازمة لدفق محتوى Portable Document Format (PDF). وتتولى تحليل الخطوط، وسجل الخطوط طوال عمر العملية، والتجزئة الفرعية للحروف الرسومية، وإخراج ToUnicode CMap، واستراتيجيات الترميز الواعية بـ cmap، ومحرك Unicode ثنائي الاتجاه.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3نظرة مفاهيمية عامة
قسم بعنوان «نظرة مفاهيمية عامة»FontRegistry يخزّن الخطوط طوال عمر العملية وينفّذ FontRegistryInterface. يحلّل ملف TrueType أو OpenType أو TrueType Collection (TTC) أو Type 1 (Printer Font Binary (PFB) وAdobe Font Metrics (AFM)) مرة واحدة، ويُعيد FontInfo غير قابل للتغيير. استخدمه مع العمليات العاملة طويلة الأمد: سخّن مجموعة الخطوط عند الإقلاع، ثم استدعِ lock(). بعد ذلك يرفض السجل أي تعديل، بينما تستمر عمليات البحث في خدمة حركة المرور. ولا يحتفظ إلا ببيانات PHP خالصة: البيانات الوصفية المحلَّلة وبايتات الخط الخام. يمكن لمجموعة العمليات العاملة مشاركة نسخة واحدة. يقبل registerFromBinary() بايتات خط خام، وهو ما يستخدمه جسر @font-face الخاص بـ HyperText Markup Language (HTML) لخط جُلب من مصدر بعيد أو من data URI.
يُضمّن المحرك كل خط يستخدمه ويجري له تجزئة فرعية. ينتقل برنامج الخط المضمَّن داخل ملف PDF، لذلك يُعرض المستند بالشكل نفسه في أي عارض ولا يعتمد على خطوط النظام المثبَّتة — ISO 32000-2 §9. لا تحمل التجزئة الفرعية إلا الحروف الرسومية التي يشير إليها المستند، وهذا مهم للمحتوى الصيني والياباني والكوري (CJK) أو المحتوى الغني بـ Unicode — ISO 32000-2 §9. يحلّل FontSubsetter دليل الجداول الأصلي، ويستخرج cmap، ويحل تبعيات الحروف الرسومية المركَّبة بوصفها إغلاقاً متعدياً، ويعيد بناء جداول head وhhea وmaxp وcmap وloca وglyf وhmtx. ويحافظ على ترقيم معرّف الحرف الرسومي الأصلي ويملأ الفتحات غير المستخدمة بالأصفار، بحيث يبقى CIDToGIDMap من النوع /Identity صالحاً. ويُعيد الخط الأصلي دون تغيير عندما تكون التجزئة الفرعية ستوفّر أقل من عشرة بالمئة، تجنباً لعمل لا يبرر نفسه. ينفّذ CffSubsetter العملية نفسها لخطوط OpenType التي تحمل جدول مخططات Compact Font Format.
لإصدار النص ثلاث ترجمات: نقطة رمز Unicode، ورمز الحرف في دفق المحتوى، ومعرّف الحرف الرسومي داخل الخط. تُبقي الوحدة هذا المسار صريحاً. FontInfo::encodeText() هي الواجهة؛ ويختار FontEncodingStrategyResolver الاستراتيجية لكل خط على حدة. يُوجَّه خط TrueType أو OpenType مضمَّن ومزوَّد بـ cmap من نوع Unicode إلى TrueTypeCmapStrategy، الذي يُصدر دفقاً سداسي عشري Identity-H من بايتين. وهذا هو الشكل المطلوب لخط Type 0 مزوَّد بـ Identity-H CMap وسليل CIDFontType2 (ISO 32000-2 §9.7.4؛ أُعيد ملخّص مقطع retrieval-augmented generation (RAG) المطابق مقتطعاً بسبب حدّ الترخيص، وقد سُجِّل في _downgraded-claims-o3.md). أما كل خط آخر — خطوط Base 14 القياسية، وType 1 من نوعي PFB وAFM — فيُوجَّه إلى Base14EncodingStrategy، الذي يُصدر سلسلة WinAnsi حرفية من بايت واحد. يغطّي ذلك الدفق ذخيرة WinAnsiEncoding (Windows code page 1252) الكاملة — اللاتينية المُشكَّلة، وعلامة اليورو، وعلامات الترقيم الطباعية الشائعة. وتُسقَط نقاط الرمز الواقعة خارجها من الدفق أحادي البايت، ويُستخدم بديل الخط لكل عنقود عندما يُسجَّل خط يغطّيها (ISO 32000-2 Annex D.2). يغطي المُحلِّل كامل فضاء قيم FontInfo؛ ولا يوجد مسار يقبل قيمة فارغة. يبني ToUnicodeCMapBuilder مورد /ToUnicode الذي يتيح للقارئ استرجاع Unicode الأصلي من خط Identity-H. ويطبّق دمج bfrange الجشع وحدّاً أقصاه 100 إدخال لكل كتلة.
BidiEngine هو خدمة الحدّ لخوارزمية Unicode ثنائية الاتجاه، المعرَّفة في Unicode Standard Annex #9 (UAX #9)، Unicode 16. عند إيقاف دعم العزل، يفوّض إلى المُحلِّل القديم بحيث يرى المستدعون الحاليون السلوك نفسه. وعند تشغيل دعم العزل، يشغّل خط الأنابيب الواعي بالعزل: مكدّس العزل الصريح بعمق أقصى يبلغ 125، وتمريرات النوع الضعيف، وتمريرات النوع المحايد بما في ذلك حل الأقواس المزدوجة، وتمريرات المستوى الضمني وإعادة ترتيب السطر. أما تغطية الحروف الرسومية لـ CJK في خط مرشَّح فهي تشخيص منفصل: يأخذ CjkFontValidator عينات من كتل Unicode المطلوبة لكل نص ويبلّغ عن نسبة تغطية مئوية.
واجهة API
قسم بعنوان «واجهة API»| النوع | الصنف | الأعضاء الرئيسية | الاستقرار | منذ |
|---|---|---|---|---|
FontRegistry | فئة نهائية | register(), registerType1(), registerFromBinary(), registerFromDirectory(), get(), has(), all(), warmup(), lock(), isLocked(), memoryUsage() | مستقر | 1.7.0 |
FontInfo | فئة نهائية للقراءة فقط | $family, $type, $widths, $unicodeMap, $cmapForward, getKey(), encodeText() | مستقر | 1.0.0 |
FontSubsetter | فئة نهائية | subset(string, array<int>, int): string | مستقر | 1.0.0 |
CffSubsetter | فئة نهائية | التجزئة الفرعية لمخططات OpenType/CFF | مستقر | 1.0.0 |
FontEncodingStrategyResolver | فئة نهائية | resolve(FontInfo): FontEncodingStrategy | مستقر | 2.7.0 |
ToUnicodeCMapBuilder | فئة نهائية | buildFromRun(), buildFromMap(), encodeUnicodeUtf16Be() | مستقر | 2.7.0 |
BidiEngine | فئة نهائية | حل مدرك للعزل وفق UAX #9 | مستقر | 3.1.0 |
CjkFontValidator | فئة نهائية | validateCoverage(), detectScript(), isCjkCodepoint() | مستقر | 1.0.0 |
FontInfo غير قابل للتغيير: توقيع باني الكائن وخصائصه العامة مجمَّدة. استراتيجيات الترميز دوال خالصة لـ (FontInfo, UTF-8 text): يُعيد المُدخل نفسه كائن EncodedGlyphRun نفسه في كل استدعاء.
عيّنة برمجية — بداية سريعة
قسم بعنوان «عيّنة برمجية — بداية سريعة»<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Typography\Encoding\EncodingMode;use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$cjkFont = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
$encoded = $cjkFont->encodeText('PDF 2.0 引擎 — 使用 CMap 編碼');
// An embedded CJK TrueType face resolves to the two-byte Identity-H path.assert($encoded->mode === EncodingMode::TwoByteCid);يحلّل register() الخط مرة واحدة ويُعيد FontInfo غير قابل للتغيير. ويمرّ encodeText() عبر المُحلِّل ويُعيد EncodedGlyphRun مزوَّداً بدفق البايتات، ومُعامل سلسلة PDF، وعروض التقدّم لكل حرف رسومي، وخريطة معرّف الحرف الرسومي (GID) إلى Unicode التي تستهلكها CMap من نوع /ToUnicode.
عيّنة برمجية — إنتاج
قسم بعنوان «عيّنة برمجية — إنتاج»<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Exception\NextPdfException;use NextPDF\Typography\FontRegistry;use Psr\Log\LoggerInterface;
final readonly class FontBootstrap{ public function __construct( private FontRegistry $registry, private LoggerInterface $logger, ) {}
/** * Warm a font set at worker boot, then lock the registry for the * lifetime of the process. * * @param list<string> $fontFiles Absolute paths to font files. */ public function boot(array $fontFiles): void { try { $this->registry->warmup($fontFiles); $this->registry->lock(); } catch (NextPdfException $e) { $this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e; }
$report = $this->registry->memoryUsage(); $this->logger->info('Font cache primed', [ 'fonts' => $report->entryCount, 'bytes' => $report->currentBytes, ]); }}تسلسل إقلاع العملية العاملة هو warmup() متبوعاً بـ lock(). بعد lock()، يُطلق كل تعديل استثناءً، وتستمر عمليات البحث في خدمة حركة المرور. يُعيد memoryUsage() كائن MemoryReport، بحيث تستطيع العملية العاملة تتبّع ذاكرة الخطوط المؤقتة مقابل ميزانيتها.
الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»- عندما يكون السجل مقفلاً، فإنه يرفض
register()وregisterFromBinary()وaddFontDirectory()وwarmup(). سخّن السجل واقفله عند الإقلاع؛ لا تسجّل قط أثناء معالجة الطلبات. - يُعيد
FontSubsetter::subset()البايتات الأصلية دون تغيير عندما يكون التوفير أقل من عشرة بالمئة أو عندما يكون جدول أساسي مفقوداً. الخط المُعاد الذي يساوي المُدخل هو مسار انعدام الكسب الموثَّق، لا فشل. - يحافظ مُجزّئ التجزئة الفرعية على ترقيم معرّف الحرف الرسومي الأصلي ويملأ الحروف الرسومية غير المستخدمة بالأصفار. وهذا يُبقي
CIDToGIDMap /Identityصالحاً؛ لا تفترض أن معرّفات الحروف الرسومية يُعاد ترقيمها ضمن نطاق متجاور. - يكتب
registerFromBinary()البايتات إلى ملف مؤقت للتحليل، ويحذف كلاً من ملف الامتداد وملفtempnam()الأساسي في كتلةfinally. بيانات الخط غير الموثوقة هي سطح هجوم على المُحلِّل؛ تحكّم بها قبل أن تصل إليه (انظر ملاحظات الأمان). - يفوّض
BidiEngineحرفياً إلى المُحلِّل القديم عندما يكون دعم العزل متوقفاً. عندئذٍ تمرّ أحرف تنسيق العزل بوصفها محايدة عند الحدود. شغّل دعم العزل عبر سياسة المطابقة للحصول على سلوك UAX #9 الكامل. - يأخذ
CjkFontValidatorعينات من نقاط الرمز على خطوة بدلاً من اختبار كل نقطة، لذا فإن رقم التغطية الخاص به تقدير كافٍ إحصائياً، لا عدّ شامل.
الأداء
قسم بعنوان «الأداء»يهيمن تحليل الخطوط على الاستخدام الأول؛ ويوزّع السجل تلك التكلفة لتصبح مرة واحدة لكل عملية. بعد التسخين، يكون get() وhas() عمليتي بحث في خريطة بترتيب O(1). تتناسب تكلفة التجزئة الفرعية مع عدد الحروف الرسومية التي يستخدمها المستند، لا مع جدول الحروف الرسومية الكامل للخط. لذلك تحسّن التجزئة الفرعية الحجم والسرعة معاً لمحتوى CJK: يتعامل مُجزّئ التجزئة الفرعية مع خطوط تتجاوز 20,000 حرف رسومي عبر البحث الثنائي، والمخازن المؤقتة المخصَّصة مسبقاً، وعمليات السلاسل المجمّعة. حل الحروف الرسومية المركَّبة محدود؛ إذ يبلغ حدّه الأقصى 100 تكرار إغلاق للدفاع ضد المراجع الدائرية للمكوّنات. يحدّ محلّل cmap من نوع Format 12 من أعداد المجموعات والإدخالات للحدّ من استخدام الذاكرة مع مُدخلات الخطوط العدائية. يغطي performance_budget البالغ 1500 ms للزمن الجداري و64 MB للذروة تسخين خط نموذجي إضافة إلى عرض المستند.
ملاحظات الأمان
قسم بعنوان «ملاحظات الأمان»هناك سطحان يحملان ثقلاً أمنياً. الأول هو مُدخل الخط. يحلّل register() وregisterFromBinary() بايتات عشوائية. وينشئ registerFromBinary() ملفاً مؤقتاً. يرفض الحدّ مُغلّفات الدفق وبايتات null في المسارات. يجب أن تجتاز بيانات الخط غير الموثوقة سياسة موارد خارجية تحدّ من حجم الملف وعدد الحروف الرسومية قبل أن تصل إلى المُحلِّل. تتحقق القارئات الثنائية لمُجزّئ التجزئة الفرعية من حدود كل إزاحة. تحدّ محلّلات cmap من أعداد المجموعات والإدخالات والجداول (numGroups > 31000 وحدّ إدخالات يبلغ 200,000 في Format 12) بحيث لا يستطيع خط مُصمَّم خصيصاً دفع تخصيص غير محدود. السطح الثاني هو استرجاع النص: يتحقق ToUnicodeCMapBuilder من أن كل رمز حرف داخل فضاء الرموز 16 بت وأن كل قيمة Unicode عددية صالحة. ويرفض أنصاف البدائل، بحيث لا تستطيع خريطة مشوَّهة إنتاج مورد استخراج تالف. عامِل أي خط أو نص يُورَّد من الخارج بوصفه غير موثوق.
المطابقة
قسم بعنوان «المطابقة»| الادّعاء | المعيار | البند | الدليل |
|---|---|---|---|
| كل خط يستخدمه المستند مضمَّن بحيث يُعرض المستند دون الاعتماد على خطوط النظام. | ISO 32000-2 | §9 | |
| الخط المضمَّن مُجزّأ فرعياً إلى الحروف الرسومية التي يشير إليها المستند. | ISO 32000-2 | §9 | |
يُصدَر خط TrueType من نوع CJK مضمَّن بوصفه خط Type 0 مزوَّداً بـ Identity-H CMap وسليل CIDFontType2. | ISO 32000-2 | §9.7.4 | ملخّص RAG مقتطع بسبب حدّ الترخيص؛ البادئة 7a5258772f508e3b، انظر _downgraded-claims-o3.md |
البندان الأولان مُعاد صياغتهما ومُثبَّتان بالملخّص. لم يُعَد ملخّص RAG الكامل للبند الثالث (اقتطاع بسبب حدّ الترخيص)؛ ويؤكده ADR-013 والنظرة العامة للمطوّر حول مُرمِّز cmap، وقد سُجِّل بوصفه مخفَّضاً. لا يُعيد NextPDF إنتاج نص معياري. مطابقة PDF/A-4 وPDF/UA-2 لمحتوى CJK مشروطة بالتجزئة الفرعية على جانب الكاتب وتوصيل /ToUnicode المتتبَّعَين هناك.
السياق التجاري
قسم بعنوان «السياق التجاري»تبني حزمة ميزات OpenType التجارية وسلاسل البديل المتميّزة للخطوط على سجل Core وطبقة الترميز. تُضمّن وحدة طباعة Core كل خط وتجري له تجزئة فرعية وترميزاً دون ترخيص؛ وتضيف الحزمة المدفوعة حلّ بديل منسَّقاً. إغفال رابط التحويل مقصود: هذه الصفحة وثائق، وليست مسار مبيعات.
انظر أيضاً
قسم بعنوان «انظر أيضاً»- الخط: TrueType وOpenType وسجل CID — أنواع قيم الخطوط والتضمين والبديل.
- النص: التشكيل والتقطيع وBiDi — معالجة المقاطع والتشكيل الذي يستهلك الحروف الرسومية المرمَّزة.
- العقود / الطباعة — عقود
FontRegistryInterfaceومُعالِج النص المسبق. - محرك عرض HTML — جسر
@font-faceالذي يستدعيregisterFromBinary().