콘텐츠로 이동

계약 / 타이포그래피

타이포그래피 도메인에는 글꼴 레지스트리 계약과 텍스트 전처리 계약이 포함됩니다. FontRegistryInterface, TextPreprocessorInterface, 그리고 불변 TextPreprocessResultTextSegment 값 객체입니다. 모두 stable 상태입니다.

Terminal window
composer require nextpdf/core:^3

FontRegistryInterface는 프로세스 수명 동안 유지되는 글꼴 저장소입니다. TrueType, OpenType, TTC 또는 PFB 글꼴을 등록하고 파싱된 FontInfo 메타데이터를 반환합니다. 레지스트리는 개별 문서보다 오래 유지되므로, 워커는 각 글꼴을 한 번만 파싱합니다. 부팅 시 글꼴 모음을 예열한 뒤 잠그면, 프로덕션 트래픽이 이를 변경하지 못하게 할 수 있습니다. 잠긴 레지스트리에서 register(), addFontDirectory() 또는 warmup()을 호출하면 LogicException이 발생하며, 조회는 계속 사용할 수 있습니다. 또한 레지스트리는 registerFromBinary()를 통해 원시 바이너리에서 글꼴을 받아들입니다. @font-face 브리지는 이 메서드를 사용하여 원격 소스나 데이터 URI에서 가져온 글꼴을 등록합니다. 레지스트리는 순수 PHP 데이터만 보관하고 리소스 핸들은 보유하지 않으므로, 워커 풀 전반에서 공유해도 안전합니다.

엔진은 사용하는 모든 글꼴을 임베드하고 서브셋팅합니다. 임베드된 글꼴 프로그램은 PDF 안에 함께 포함되어 전달되므로, 설치된 시스템 글꼴과 관계없이 어떤 뷰어에서도 문서가 동일하게 렌더링됩니다 — ISO 32000-2 §9. 글꼴 서브셋은 문서가 실제로 참조하는 글리프만 담으며, 이는 CJK 또는 유니코드가 풍부한 콘텐츠에서 매우 중요합니다 — ISO 32000-2 §9. 레지스트리 계약은 서브셋팅 및 임베딩 단계가 사용하는 파싱된 메타데이터를 노출합니다.

TextPreprocessorInterface는 텍스트가 글리프 레이아웃, 글꼴 서브셋팅, ToUnicode CMap 및 구조 트리로 전달되기 전에 가로챕니다. 이 배치 자체가 보안 속성입니다. 콘텐츠를 수정 삭제하는 전처리기는 그 콘텐츠가 콘텐츠 스트림, 글꼴 서브셋 또는 메타데이터에 도달하기 전에 제거합니다. 이 계약에는 두 가지 불변 조건이 있습니다. 전처리기는 레이아웃에 영향을 미치는 문자를 도입해서는 안 되며, 논리적 읽기 순서를 보존해야 합니다. 이 계약의 책임은 레이아웃이 아니라 콘텐츠 치환입니다. 처리 결과는 불변 TextPreprocessResult이며, 이는 TextSegment 값의 정렬된 목록을 담고 있습니다. 세그먼트는 통과(pass-through) 세그먼트이거나 수정 삭제(redacted) 세그먼트입니다. 수정 삭제된 세그먼트의 경우, 표시 텍스트는 마스킹 모드에 따라 달라집니다. 블랙박스 직사각형이면 비어 있고, 원본 길이에 맞춘 별표 또는 고정 레이블일 수 있습니다. 세그먼트의 originalCharCount는 수정 삭제 직사각형의 크기를 정하는 데만 사용되는 비가역적 측정 힌트입니다. 이를 원본 콘텐츠 재구성에 절대 사용해서는 안 됩니다.

유형종류주요 멤버안정성도입 버전
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()안정1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResult안정1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()안정1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColor안정1.9.0

TextPreprocessResultTextSegment는 생성자 시그니처와 public 속성을 고정합니다. 새 메서드는 추가될 수 있지만 속성은 변경될 수 없습니다.

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를 반환하므로, 워커가 글꼴 캐시를 예산 대비 추적할 수 있습니다. 텍스트 전처리는 입력 길이에 선형적으로 비례합니다. 세그먼트 목록은 수정 삭제 매칭 수에 비례하는 제한적인 오버헤드만 추가합니다. wall time 1500 ms와 최대 64 MB의 performance_budget는 일반적인 글꼴 세트의 예열과 문서 렌더링을 포함합니다. 서브셋팅 비용은 글꼴의 전체 글리프 테이블이 아니라 실제로 사용된 글리프 수에 따라 확장됩니다. 따라서 서브셋팅은 CJK 콘텐츠의 출력 크기와 렌더링 비용을 줄입니다.

타이포그래피 도메인에는 보안과 관련된 표면이 두 가지 있습니다. 첫 번째는 글꼴 입력입니다. registerFromBinary()는 임의의 바이트를 파싱합니다. 신뢰할 수 없는 글꼴 데이터는 파서에 도달하기 전에 파일 크기와 글리프 수를 제한하는 ExternalResourcePolicyInterface를 통과해야 합니다. 두 번째는 수정 삭제입니다. TextPreprocessorInterface는 수정 삭제된 콘텐츠가 렌더링 산출물에 절대 들어가지 않도록 글리프 레이아웃, 글꼴 서브셋팅, ToUnicode CMap 및 구조 트리보다 앞에 배치됩니다. 페인트 시점의 오버레이로 구현한 수정 삭제는 콘텐츠 스트림과 서브셋에서 원본 텍스트를 노출합니다. 이 계약 배치는 이러한 유형의 결함을 방지합니다. 세그먼트의 측정 힌트는 의도적으로 비가역적입니다. 외부에서 제공된 모든 글꼴 또는 텍스트는 신뢰할 수 없는 것으로 취급하십시오.

주장표준조항증거
문서가 사용하는 모든 글꼴이 임베드되므로 문서는 시스템 글꼴에 의존하지 않은 채 렌더링됩니다.ISO 32000-2§9
임베드된 글꼴은 문서가 참조하는 글리프로 서브셋됩니다.ISO 32000-2§9

두 조항 모두 의역되었습니다. NextPDF는 규범적 텍스트를 복제하지 않습니다. PDF/A-4는 모든 글꼴에 대해 임베딩을 의무화합니다. 해당 적합성은 추출 및 접근성 페이지에 문서화되어 있습니다.