ข้ามไปยังเนื้อหา

Contracts / งานตัวอักษร

โดเมนงานตัวอักษรกำหนดสัญญาของรีจิสทรีฟอนต์และการประมวลผลข้อความเบื้องต้น ได้แก่ 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 (uniform resource identifier) รีจิสทรีจัดเก็บเฉพาะข้อมูล PHP ล้วน ไม่มี resource handle จึงแชร์ข้ามพูลเวิร์กเกอร์ได้

เอนจินจะฝังและทำซับเซ็ตฟอนต์ทุกตัวที่ใช้งาน โปรแกรมฟอนต์ที่ฝังไว้จะติดไปกับไฟล์ Portable Document Format (PDF) เอกสารจึงเรนเดอร์เหมือนกันในโปรแกรมแสดงผลทุกตัว โดยไม่ขึ้นอยู่กับฟอนต์ระบบที่ติดตั้งไว้ — ISO 32000-2 §9 ซับเซ็ตของฟอนต์บรรจุเฉพาะกลิฟที่เอกสารอ้างอิงใช้งานจริงเท่านั้น เรื่องนี้สำคัญเป็นพิเศษสำหรับเนื้อหาภาษาจีน ญี่ปุ่น และเกาหลี (CJK) หรือเนื้อหาอื่นที่มี Unicode จำนวนมาก — ISO 32000-2 §9 สัญญาของรีจิสทรีเปิดเผยเมตาดาตาที่ผ่านการวิเคราะห์แล้ว ซึ่งขั้นตอนการทำซับเซ็ตและการฝังนำไปใช้

TextPreprocessorInterface ดักจับข้อความก่อนที่ข้อความจะเข้าสู่การจัดวางกลิฟ การทำซับเซ็ตฟอนต์ ToUnicode character map (CMap) และต้นไม้โครงสร้าง ตำแหน่งนี้เป็นคุณสมบัติด้านความปลอดภัย กล่าวคือพรีโพรเซสเซอร์ที่ปกปิดเนื้อหาจะลบเนื้อหานั้นออกก่อนที่เนื้อหาจะไปถึง content stream ซับเซ็ตของฟอนต์ หรือเมตาดาตา สัญญานี้มีอินแวเรียนต์สองข้อ พรีโพรเซสเซอร์ต้องไม่เพิ่มอักขระที่ส่งผลต่อเค้าโครง และต้องรักษาลำดับการอ่านเชิงตรรกะไว้ ความรับผิดชอบของพรีโพรเซสเซอร์คือการแทนที่เนื้อหา ไม่ใช่การจัดเค้าโครง ผลลัพธ์คือ TextPreprocessResult ที่เปลี่ยนแปลงไม่ได้ ซึ่งมีรายการค่า TextSegment ที่เรียงลำดับไว้ เซกเมนต์เป็นได้ทั้งแบบส่งผ่าน (pass-through) หรือแบบถูกปกปิด สำหรับเซกเมนต์ที่ถูกปกปิด ข้อความที่แสดงจะขึ้นอยู่กับโหมดการมาสก์ ได้แก่ ค่าว่างสำหรับสี่เหลี่ยมทึบสีดำ เครื่องหมายดอกจันตามความยาวของข้อความต้นฉบับ หรือป้ายกำกับแบบคงที่ originalCharCount บนเซกเมนต์เป็นคำใบ้ด้านการวัดที่ย้อนกลับไม่ได้ ใช้เพียงเพื่อกำหนดขนาดของสี่เหลี่ยมการปกปิดเท่านั้น ต้องไม่นำคำใบ้นี้ไปใช้สร้างเนื้อหาต้นฉบับขึ้นใหม่โดยเด็ดขาด

ชนิดประเภทสมาชิกหลักความเสถียรตั้งแต่
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorstable1.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 เอกสารแบบสแตนด์อโลนใช้รีจิสทรีส่วนตัว สำหรับเวิร์กเกอร์ ให้แชร์รีจิสทรีเดียวกัน ดูรายละเอียดได้ที่หน้า document

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 (ดูได้ที่หน้า security-policy)
  • TextPreprocessor ต้องไม่เพิ่มการขึ้นบรรทัดใหม่ อักขระปัดแคร่ (carriage return) หรือแท็บ อักขระเหล่านี้เปลี่ยนเค้าโครงและทำให้อินแวเรียนต์ข้อแรกของสัญญาเสียหาย
  • TextSegment::$originalCharCount เป็นเพียงคำใบ้เรื่องความกว้างเท่านั้น การนำคำใบ้นี้ไปใช้อนุมานเนื้อหาต้นฉบับจะทำให้การปกปิดไร้ผลและละเมิดอินแวเรียนต์ข้อที่สามของสัญญา
  • TextPreprocessResult::getDisplayText() คืนค่าสตริงว่างสำหรับเซกเมนต์แบบกล่องทึบดำตามที่ออกแบบไว้ อย่าถือว่าเซกเมนต์ว่างเป็นความล้มเหลวของการประมวลผลเบื้องต้น

การวิเคราะห์ฟอนต์เป็นภาระหลักในการใช้งานครั้งแรก รีจิสทรีจะเฉลี่ยต้นทุนนั้นให้เกิดเพียงครั้งเดียวต่อหนึ่งโพรเซส หลังการวอร์มอัป get() และ has() เป็นการค้นหาในแมปแบบ O(1) memoryUsage() คืนค่า MemoryReport เพื่อให้เวิร์กเกอร์ติดตามแคชฟอนต์เทียบกับงบประมาณของตนได้ การประมวลผลข้อความเบื้องต้นเป็นเชิงเส้นตามความยาวของอินพุต รายการเซกเมนต์เพิ่มโอเวอร์เฮดที่มีขอบเขตจำกัดตามจำนวนรายการที่ตรงกับการปกปิด performance_budget ที่ 1500 ms wall และ 64 MB peak ครอบคลุมการวอร์มอัปสำหรับชุดฟอนต์ทั่วไปและการเรนเดอร์เอกสาร ต้นทุนการทำซับเซ็ตปรับตามจำนวนกลิฟที่ใช้งานจริง ไม่ใช่ตามตารางกลิฟทั้งหมดของฟอนต์ ดังนั้นการทำซับเซ็ตจึงลดขนาดเอาต์พุตและต้นทุนการเรนเดอร์สำหรับเนื้อหา CJK

โดเมนงานตัวอักษรมีพื้นผิวที่เกี่ยวข้องกับความปลอดภัยสองส่วน ส่วนแรกคืออินพุตฟอนต์ registerFromBinary() วิเคราะห์ไบต์ใด ๆ ก็ตาม ข้อมูลฟอนต์ที่ไม่น่าเชื่อถือต้องผ่าน ExternalResourcePolicyInterface ที่จำกัดขนาดไฟล์และจำนวนกลิฟก่อนจะไปถึงตัววิเคราะห์ ส่วนที่สองคือการปกปิด TextPreprocessorInterface ทำงานก่อนการจัดวางกลิฟ การทำซับเซ็ตฟอนต์ ToUnicode CMap และต้นไม้โครงสร้าง เพื่อให้เนื้อหาที่ถูกปกปิดไม่เข้าสู่อาร์ทิแฟกต์ที่เรนเดอร์ออกมาเลย การปกปิดด้วยการวางทับขณะวาด (paint-time overlay) จะทำให้ข้อความต้นฉบับรั่วไหลออกมาใน content stream และในซับเซ็ต ตำแหน่งของสัญญานี้ช่วยป้องกันข้อบกพร่องประเภทนั้น คำใบ้ด้านการวัดบนเซกเมนต์ถูกออกแบบให้ย้อนกลับไม่ได้โดยตั้งใจ ให้ถือว่าฟอนต์หรือข้อความใด ๆ ที่จัดหามาจากภายนอกเป็นสิ่งที่ไม่น่าเชื่อถือ

ข้อกล่าวอ้างมาตรฐานข้อกำหนดหลักฐาน
ฟอนต์ทุกตัวที่เอกสารใช้งานจะถูกฝังไว้ เพื่อให้เอกสารเรนเดอร์ได้โดยไม่ต้องพึ่งพาฟอนต์ของระบบISO 32000-2§9
ฟอนต์ที่ฝังไว้จะถูกทำซับเซ็ตให้เหลือเฉพาะกลิฟที่เอกสารอ้างอิงISO 32000-2§9

ข้อกำหนดทั้งสองข้อเป็นการเรียบเรียงใหม่ NextPDF ไม่ได้คัดลอกข้อความเชิงบรรทัดฐานมาแสดงซ้ำ PDF/A-4 บังคับให้ฝังฟอนต์ทุกตัว ความสอดคล้องดังกล่าวมีบันทึกไว้ในหน้า extraction และ accessibility

  • Contracts: 41 อินเทอร์เฟซสาธารณะ (SPI) — ภาพรวมของ service provider interface และระดับความเสถียร
  • Contracts / Document — บทบาทของรีจิสทรีในวงจรชีวิตของเอกสาร
  • Contracts / Security PolicyExternalResourcePolicyInterface ควบคุมไบต์ฟอนต์ที่ไม่น่าเชื่อถือ
  • Typography — โมดูลการจัดรูปทรงข้อความและเค้าโครง
  • Font — การวิเคราะห์ฟอนต์ การทำซับเซ็ต และการฝัง
  • Text — เอาต์พุตข้อความที่ใช้ผลลัพธ์จากพรีโพรเซสเซอร์