콘텐츠로 이동

계약 / 문서

문서 도메인은 PDF를 작성할 때 기준이 되는 계약을 담고 있습니다. 여기에는 콘텐츠용 PdfDocumentInterface, 워커 환경에서 안전하게 생성하기 위한 DocumentFactoryInterface, 글꼴 및 이미지 레지스트리 계약, 그리고 세 가지 전달 및 레이아웃 열거형이 포함됩니다. 모두 1.0.0 또는 1.7.0 이후 stable 상태입니다.

Terminal window
composer require nextpdf/core:^3

PdfDocumentInterface는 기본 API 표면입니다. 페이지 관리, 글꼴 선택, 셀과 멀티셀 텍스트 레이아웃, HTML 렌더링, 이미지 임베딩, 최종 출력을 정의합니다. 모든 메서드는 static을 반환하므로 호출을 체이닝할 수 있습니다. Document::createStandalone()는 이 인터페이스를 충족하는 구체 인스턴스를 반환합니다. 직접 작성하는 서비스에서는 이 인터페이스를 타입 힌트로 사용하십시오. 그러면 엔진 내부 구현을 교체 가능한 상태로 유지할 수 있습니다.

문서 생성 경로는 두 가지입니다. 일반적인 PHP-FPM 요청에서는 createStandalone()가 전용 레지스트리를 갖춘 자체 완결 문서를 생성합니다. 장시간 실행되는 워커는 다른 경로를 사용하며, RoadRunner, Swoole, Laravel Octane이 여기에 해당합니다. 이 경우 DocumentFactoryInterface::create()는 매번 새로운 일회용 Document를 반환합니다. 문서는 프로세스 수명 주기 동안 유지되는 레지스트리에서 데이터를 읽지만, 절대 변경하지 않습니다. 팩토리는 FontRegistryInterfaceImageRegistryInterface 싱글턴을 유지합니다. 각 문서에는 자체 렌더링 컨텍스트와 라이터가 있습니다. 이 구조가 장애 격리를 제공합니다. 한 문서가 다른 문서가 의존하는 공유 상태를 손상시킬 수 없습니다.

레지스트리 계약 덕분에 워커는 빠르게 동작합니다. FontRegistryInterface는 글꼴 파일을 한 번만 파싱하고, 파싱된 메타데이터를 프로세스 수명 동안 캐시합니다. 워밍업 후에는 잠글 수 있어 프로덕션 트래픽이 이를 변경할 수 없습니다. ImageRegistryInterface는 디코딩된 이미지 바이너리 데이터를 용량이 제한된 LRU(가장 오래 사용되지 않은 항목 우선) 정책에 따라 캐시합니다. 이미지 메타데이터는 바이너리가 제거된 후에도 메모리에 남습니다. 두 인터페이스 모두 용량 계획을 위해 memoryUsage()를 제공합니다. ImageRegistryInterfaceResettableService를 확장합니다. 이 계약은 구조적 메타데이터를 손상하지 않고 캐시된 데이터를 제거합니다. 워커는 메모리 압박 상황에서 이미지 캐시를 폐기하면서도 서비스를 계속할 수 있습니다.

세 가지 열거형이 도메인을 완성합니다. OutputDestination은 인라인 표시, 강제 다운로드, 파일 시스템 쓰기, 원시 문자열 반환 중 하나를 선택합니다. Orientation은 세로 또는 가로 방향을 선택합니다. Alignment는 왼쪽, 가운데, 오른쪽 또는 양쪽 맞춤 텍스트 정렬을 선택합니다. 각 열거형의 케이스는 레거시 TCPDF 코드를 값으로 가집니다. 따라서 compat-tcpdf 브리지는 깔끔하게 매핑됩니다. 이 열거형의 하위 호환성 약속은 추가만 허용하는 방식입니다. 어떤 케이스도 제거되지 않습니다. 새 케이스는 마이너 릴리스에서 추가될 수 있습니다.

유형종류주요 멤버안정성도입 버전
PdfDocumentInterfaceinterfaceaddPage(), setMargins(), setFont(), cell(), multiCell(), writeHtml(), image(), output(), save()안정1.0.0
DocumentFactoryInterfaceinterfacecreate(?Config): Document안정1.7.0
ResettableServiceinterfacereset(): void안정1.7.0
FontRegistryInterfaceinterfaceregister(), get(), warmup(), lock(), isLocked(), registerFromBinary(), memoryUsage()안정1.7.0
ImageRegistryInterfaceinterfaceload(), loadFromString(), getMetadata(), memoryUsage() (ResettableService 확장)안정2.0.0
OutputDestinationenum (string)Inline, Download, File, String안정1.0.0
Orientationenum (string)Portrait, Landscape안정1.0.0
Alignmentenum (string)Left, Center, Right, Justify안정1.0.0

FontRegistryInterfaceImageRegistryInterface는 타이포그래피 페이지에 상세히 문서화되어 있으며, 이 문서 페이지에서는 생성 수명 주기에서 이들이 맡는 역할을 다룹니다.

examples/01-hello-world.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'This is a minimal PDF generated with NextPDF.', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');
examples/02-pdf-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\PdfFactory;
use NextPDF\ValueObjects\{Margin, PageSize};
$factory = PdfFactory::new()
->withPageSize(PageSize::A4())
->withMargins(new Margin(15.0, 15.0, 15.0, 15.0))
->withCompress(true)
->withLang('en');
// The same configured factory creates independent documents.
$doc = $factory->create();
$doc->setTitle('PdfFactory Example');
$doc->setAuthor('NextPDF');
$doc->addPage();
$doc->setFont('helvetica', '', 16);
$doc->cell(0, 12, 'Created via PdfFactory', newLine: true);
$doc2 = $factory->create();
$doc2->addPage();
$doc2->setFont('helvetica', '', 12);
$doc2->cell(0, 10, 'Second document from the same factory.');
$doc->save(__DIR__ . '/output/02-pdf-factory.pdf');

PdfFactory는 불변 빌더이며, 각 with*()는 새 인스턴스를 반환합니다. 내부적으로 DocumentFactoryInterface를 구성하므로, 개요에서 설명한 워커 레지스트리 모델이 추가 연결 코드 없이 적용됩니다.

  • createStandalone()는 전용 레지스트리를 생성합니다. 워커 루프에서는 요청마다 모든 글꼴을 다시 파싱하게 됩니다. 대신 공유 레지스트리를 사용하는 DocumentFactoryInterface를 사용하십시오.
  • 하나의 Document는 설계상 일회용입니다. 여러 논리적 문서에서 하나의 인스턴스를 재사용하면 상태가 누출됩니다. 문서마다 create()를 호출하고 가비지 컬렉션에 회수를 맡기십시오.
  • FontRegistryInterface::lock()를 호출하면 register(), addFontDirectory(), warmup()LogicException을 던집니다. 워밍업 이후에 잠그고, 요청 처리 중에는 절대 잠그지 마십시오.
  • OutputDestination::File은 서버 파일 시스템에 기록하고 원시 바이트를 반환합니다. save()는 명시적인 파일 경로를 사용합니다. 같은 문서에서 이 둘을 혼용하지 마십시오.
  • cell()은 TCPDF 호환성을 위해 border 인수로 bool|string을 허용하며, 빈 문자열은 false와 동일하지 않습니다. 의도한 타입의 값을 정확히 전달하십시오.

글꼴 및 이미지 레지스트리는 문서 도메인을 요청 단위 시스템이 아니라 메모리 한도 기반 시스템으로 만듭니다. 첫 요청의 글꼴 파싱이 가장 큰 비중을 차지합니다. performance_budget은 워커 예제에서 세 개의 문서에 걸쳐 실제 경과 시간 1500ms, 피크 메모리 64MB입니다. 이 예산의 대부분은 첫 글꼴 파싱에 사용됩니다. 워밍업 이후에는 계약 때문에 발생하는 문서당 작업이 O(1)이며, 그 작업은 레지스트리 조회와 컨텍스트 할당입니다. 어느 레지스트리에서든 memoryUsage()는 실시간 용량 계획을 위해 MemoryReport를 반환합니다. ResettableService::reset()는 지속 부하 상황에서 피크 메모리를 제한합니다.

문서 계약에는 암호화 관련 표면이 없지만, 두 가지 운영상 위험이 적용됩니다. 첫째, image()는 경로 또는 URL을 허용합니다. 신뢰할 수 없는 입력을 다루는 경우 사용자가 제어하는 URL을 직접 전달하지 말고, ExternalResourcePolicyInterface를 통해 원격 페치를 제한하십시오(보안 정책 페이지 참조). 둘째, writeHtml()는 HTML 파이프라인의 진입점입니다. 신뢰할 수 없는 마크업은 렌더링 전에 HtmlSecurityPolicyInterface를 통과해야 합니다. 문서 계층 자체는 새니타이즈를 수행하지 않습니다. 이는 보안 정책 도메인의 역할이며, 계약으로 분리되어 있으므로 포크 없이도 더 엄격한 정책을 제공할 수 있습니다.

문서 계약은 ISO 32000-2에 정의된 PDF 2.0 문서 구조를 구현합니다. 출력, 페이지, 글꼴 처리는 ISO 32000-2 §7에 따라 간접 객체와 상호 참조 스트림을 생성합니다. 콘텐츠는 엔진 계층 계약(ADR-010)에 따라 라이터 계층을 통해 출력됩니다. 이 페이지에서는 구조적 적합성을 넘어서는 조항 수준의 주장을 하지 않습니다. PDF/A 및 PDF/UA 적합성은 규범 표가 포함된 추출 및 접근성 페이지에 문서화되어 있습니다.