Przejdź do głównej zawartości

Kontrakty / Typografia

Domena typografii definiuje kontrakty dla rejestru czcionek oraz wstępnego przetwarzania tekstu: FontRegistryInterface, TextPreprocessorInterface oraz niezmienne obiekty wartości TextPreprocessResult i TextSegment. Wszystkie mają status stable.

Okno terminala
composer require nextpdf/core:^3

FontRegistryInterface to magazyn czcionek działający w cyklu życia procesu. Rejestruje czcionkę TrueType, OpenType, TrueType Collection (TTC) lub Printer Font Binary (PFB) i zwraca przetworzone metadane FontInfo. Ponieważ rejestr istnieje dłużej niż poszczególne dokumenty, proces roboczy przetwarza każdą czcionkę tylko raz. Podczas rozruchu można rozgrzać zestaw czcionek, a następnie zablokować rejestr, tak aby ruch produkcyjny nie mógł go modyfikować. Zablokowany rejestr zgłasza LogicException przy wywołaniu register(), addFontDirectory() lub warmup(); wyszukiwania pozostają dostępne. Rejestr przyjmuje również surowe bajty czcionki za pośrednictwem registerFromBinary(). Mostek @font-face używa tej metody do rejestrowania czcionki pobranej ze zdalnego źródła lub z identyfikatora URI danych (uniform resource identifier). Rejestr przechowuje wyłącznie czyste dane PHP, bez uchwytów zasobów, dzięki czemu można go współdzielić w puli procesów roboczych.

Silnik osadza każdą używaną czcionkę i tworzy jej podzbiór. Osadzony program czcionki znajduje się wewnątrz pliku Portable Document Format (PDF), więc dokument renderuje się tak samo w każdej przeglądarce, niezależnie od zainstalowanych czcionek systemowych — ISO 32000-2 §9. Podzbiór czcionki zawiera tylko glify, do których dokument faktycznie się odwołuje. Ma to największe znaczenie dla treści w językach chińskim, japońskim i koreańskim (CJK) albo innych treści bogatych w znaki Unicode — ISO 32000-2 §9. Kontrakt rejestru udostępnia przetworzone metadane, z których korzystają etapy tworzenia podzbioru i osadzania.

TextPreprocessorInterface przechwytuje tekst, zanim trafi do etapu układania glifów, tworzenia podzbioru czcionki, mapy znaków ToUnicode (CMap) oraz drzewa struktury. Takie umiejscowienie jest właściwością zabezpieczającą: preprocesor wykonujący redakcję usuwa treść, zanim zdąży dotrzeć do strumienia treści, podzbioru czcionki lub metadanych. Kontrakt narzuca dwa niezmienniki. Preprocesor nie może wprowadzać znaków wpływających na układ i musi zachowywać logiczną kolejność odczytu; ma podstawiać treść, a nie zajmować się układem. Wynikiem jest niezmienny TextPreprocessResult z uporządkowaną listą wartości TextSegment. Segment jest albo przepuszczany bez zmian, albo zredagowany. W przypadku zredagowanego segmentu wyświetlany tekst zależy od trybu maskowania: pusty dla prostokąta typu black-box, gwiazdki odpowiadające pierwotnej długości lub stała etykieta. Pole originalCharCount w segmencie jest nieodwracalną wskazówką pomiarową używaną wyłącznie do ustalenia rozmiaru prostokąta redakcji. Nigdy nie wolno używać go do odtworzenia pierwotnej treści.

TypRodzajKluczowe składoweStabilnośćOd wersji
FontRegistryInterfaceinterfejsregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfejsprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorstable1.9.0

TextPreprocessResult i TextSegment mają zamrożone sygnatury konstruktorów oraz właściwości publiczne; można dodawać nowe metody, ale nie wolno zmieniać właściwości.

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() rozwiązuje rodzinę czcionek za pośrednictwem FontRegistryInterface. Samodzielny dokument używa prywatnego rejestru. W procesie roboczym współdziel jeden rejestr; zobacz stronę dotyczącą dokumentu.

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(), a następnie lock(), tworzą sekwencję rozruchu procesu roboczego. Po wywołaniu lock() próba modyfikacji kończy się wyjątkiem. Wyszukiwania nadal obsługują ruch.

  • Zablokowany rejestr odrzuca każdą metodę modyfikującą. Rozgrzej i zablokuj rejestr podczas rozruchu; nigdy nie wywołuj register() podczas obsługi żądania.
  • registerFromBinary() zapisuje bajty czcionki do pliku tymczasowego przed przetworzeniem. Niezaufane dane czcionki są powierzchnią ataku dla parsera — kontroluj je za pomocą ExternalResourcePolicyInterface (zobacz stronę zasad bezpieczeństwa).
  • TextPreprocessor nie może dodawać podziałów wierszy, powrotów karetki ani tabulatorów. Znaki te zmieniają układ i naruszają pierwszy niezmiennik kontraktu.
  • TextSegment::$originalCharCount jest wyłącznie wskazówką dotyczącą szerokości. Użycie tej wartości do wywnioskowania pierwotnej treści niweczy redakcję i narusza trzeci niezmiennik kontraktu.
  • TextPreprocessResult::getDisplayText() celowo zwraca pusty łańcuch znaków dla segmentów typu black-box. Nie traktuj pustego segmentu jako błędu wstępnego przetwarzania.

Przetwarzanie czcionki jest dominującym kosztem przy pierwszym użyciu; rejestr sprawia, że ponosi się go tylko raz na proces. Po rozgrzaniu get() i has() wykonują wyszukiwania w mapie o złożoności O(1). memoryUsage() zwraca MemoryReport, dzięki czemu proces roboczy może śledzić pamięć podręczną czcionek względem własnego budżetu. Wstępne przetwarzanie tekstu jest liniowe względem długości wejścia. Lista segmentów dodaje ograniczony narzut proporcjonalny do liczby dopasowań redakcji. Wartość performance_budget wynosząca 1500 ms czasu rzeczywistego i 64 MB szczytowego zużycia obejmuje rozgrzewkę typowego zestawu czcionek wraz z renderowaniem dokumentu. Koszt tworzenia podzbioru skaluje się wraz z liczbą faktycznie używanych glifów, a nie z pełną tablicą glifów czcionki. Tworzenie podzbioru zmniejsza zatem rozmiar wyjścia i koszt renderowania treści CJK.

Domena typografii ma dwie powierzchnie istotne dla bezpieczeństwa. Pierwszą są dane wejściowe czcionki: registerFromBinary() przetwarza dowolne bajty. Niezaufane dane czcionki muszą przejść przez ExternalResourcePolicyInterface, który ogranicza rozmiar pliku i liczbę glifów, zanim dane trafią do parsera. Drugą jest redakcja: TextPreprocessorInterface działa przed etapem układania glifów, tworzeniem podzbioru czcionki, mapą znaków ToUnicode (CMap) oraz drzewem struktury, dzięki czemu zredagowana treść nigdy nie trafia do wyrenderowanego artefaktu. Redakcja realizowana jako nakładka podczas rysowania ujawnia pierwotny tekst w strumieniu treści i w podzbiorze. Umiejscowienie tego kontraktu zapobiega tej klasie defektów. Wskazówka pomiarowa w segmencie jest celowo nieodwracalna. Czcionki i tekst dostarczane z zewnątrz traktuj zawsze jako niezaufane.

TwierdzenieStandardKlauzulaDowód
Każda czcionka używana przez dokument jest osadzona, dzięki czemu dokument renderuje się bez polegania na czcionkach systemowych.ISO 32000-2§9
Osadzona czcionka jest ograniczona do podzbioru glifów, do których odwołuje się dokument.ISO 32000-2§9

Obie klauzule podano w parafrazie. NextPDF nie odtwarza tekstu normatywnego. PDF/A-4 nakazuje osadzanie każdej czcionki. Zgodność ta jest udokumentowana na stronach dotyczących ekstrakcji i dostępności.