콘텐츠로 이동

텍스트: 셰이핑 심, CJK와 런 처리

텍스트 모듈은 셰이핑 심 역할을 합니다. UTF-8 런을 위치가 지정된 글리프로 변환하는 작은 인터페이스, 사용 가능한 경우의 실제 OpenType 백엔드, 사용할 수 없을 때의 결정론적 폴백, 그리고 스크립트별 셰이퍼 레지스트리로 구성됩니다.

Terminal window
composer require nextpdf/core:^3

ShaperInterface는 텍스트 레이아웃 파이프라인과 OpenType 셰이핑 엔진 사이의 심입니다. 이 인터페이스는 의도적으로 최소화되어 있습니다. shape() 메서드 하나로 이루어지며, 이 메서드는 ShaperInput을 소비하여 ShapingResult를 반환합니다. 반환 타입은 소비자에게 노출되는 유일한 출력입니다. 구현체는 셰이핑 엔진의 내부 구조를 누출해서는 안 되며, 타입이 지정된 반환값이 이를 구조적으로 강제합니다. ShapingResultGlyphRun 레코드 목록, 입력 그대로 되돌린 원본 텍스트, 스크립트와 방향, 그리고 결과를 생성한 백엔드를 명시하는 shaperImpl 태그를 담고 있습니다.

백엔드 선택은 명시적이며 기능 상태를 숨기지 않습니다. ShaperFactory는 기능 탐지를 한 번 실행합니다. 호스트에 동작하는 HarfBuzz 바인딩이 있으면 create()는 HarfBuzz 기반 셰이퍼를 반환합니다. 그렇지 않으면 NullShaper를 반환합니다. NullShaper는 통과형(pass-through) 폴백입니다. 유니코드 코드포인트마다 어드밴스와 오프셋이 0인 합성 글리프 하나를 방출합니다. 또한 결과에 태그를 붙여 관측 가능성(observability) 도구가 폴백을 감지할 수 있도록 합니다. 어드밴스 해석은 글꼴 메트릭 모듈에 맡깁니다. 이는 완전한 셰이핑이 아니라 문서화된 저하 경로입니다. 치환, 합자, 마크 위치 지정, 문맥 형태에는 실제 백엔드가 필요합니다. wouldUseRealShaper()는 진단용 술어입니다. 프로덕션 코드는 그 대신 결과의 shaperImpl 태그를 기준으로 분기해야 합니다.

스크립트별 셰이핑은 번들로 제공되는 구현이 아니라 SPI입니다. ScriptShaperRegistry는 ISO 15924 스크립트 태그로 MongolianShaperInterface 또는 TibetanShaperInterface를 해석하는 PSR-11 방식의 레지스트리입니다. 레지스트리는 키를 대소문자 구분 없이 저장하며, 스크립트 코드의 허용 여부 판단을 단일 진실 공급원에 위임합니다. 레지스트리와 스크립트 셰이퍼 인터페이스는 고정된 계약이므로, 확장 기능은 호출 지점을 건드리지 않고도 Phase-12 제공자를 등록할 수 있습니다. 엔진은 심을 제공하며, 복합 스크립트 제공자는 소비자가 공급합니다.

CJK 런 처리는 타이포그래피 인코딩 심 위에 놓입니다. 임베드된 CJK TrueType 페이스는 Identity-H CMap과 CIDFontType2 하위 글꼴을 갖춘 Type 0 글꼴로 방출됩니다 — ISO 32000-2 §9.7.4 (RAG 다이제스트는 라이선스 한도로 인해 잘렸으며, _downgraded-claims-o3.md에 기록됨). TrueType 프로그램이 임베드되면, Type 2 CIDFont는 CIDToGIDMap 항목을 통해 문자 식별자를 글리프 인덱스로 매핑합니다 — ISO 32000-2 §9 (다이제스트는 B1 계약 페이지에 고정되어 있음). 서브세터는 원본 글리프 번호를 정확히 보존하므로 서브셋에 대해 /CIDToGIDMap /Identity가 유효하게 유지됩니다. CjkFontValidator는 후보 글꼴이 선택되기 전에 해당 글꼴이 스크립트에 필요한 유니코드 블록을 포함하는지 확인하는 진단 도구입니다.

타입종류주요 멤버안정성도입 버전
ShaperInterfaceinterfaceshape(ShaperInput): ShapingResultstable (안정)3.2.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()stable (안정)3.2.0
NullShaperfinal readonly class통과형 폴백 셰이퍼stable (안정)3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImplstable (안정)3.2.0
ScriptShaperRegistryfinal classregisterMongolian(), getMongolian(), hasMongolian() 및 티베트어 대응 메서드stable (안정)3.1.0
CjkFontValidatorfinal classvalidateCoverage(), detectScript(), isCjkCodepoint()stable (안정)1.0.0

공개 API에서 register*, get*, has* 형태, 즉 ScriptShaperRegistry의 접근자 형태와 스크립트 셰이퍼 인터페이스는 고정된 계약입니다. 설계상 ShapingResult는 소비자에게 노출되는 유일한 셰이퍼 출력입니다.

examples/text/shaper-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Font\Shaper\ShaperFactory;
use NextPDF\Font\Shaper\ShaperImpl;
$factory = ShaperFactory::default();
$shaper = $factory->create();
// Branch on the result tag, not on the concrete class.
$wouldShape = $factory->wouldUseRealShaper()
? 'HarfBuzz backend available'
: 'NullShaper fallback (degraded — no substitution or positioning)';
echo $wouldShape, "\n";

ShaperFactory::default()는 프로덕션 기능 탐지를 구성합니다. create()는 선택된 백엔드를 팩토리의 수명 동안 메모이즈합니다. 기능 상태를 있는 그대로 보여 주는 정보는 wouldUseRealShaper()와 각 결과의 shaperImpl 태그에서 나옵니다.

examples/text/script-shaper-registry.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Text\Shaping\MongolianShaperInterface;
use NextPDF\Text\Shaping\ScriptShaperRegistry;
final readonly class ComplexScriptBootstrap
{
public function __construct(private ScriptShaperRegistry $registry) {}
/**
* Register a consumer-supplied Mongolian shaper provider at boot so
* the layout pipeline can resolve it by ISO 15924 script tag.
*/
public function register(MongolianShaperInterface $mongolian): void
{
$this->registry->registerMongolian($mongolian);
}
public function hasMongolian(): bool
{
return $this->registry->hasMongolian();
}
}

레지스트리는 복합 스크립트 제공자의 통합 지점입니다. 엔진은 심과 고정된 접근자 형태를 제공합니다. 몽골어와 티베트어 구현은 소비자가 공급합니다.

  • 모든 NullShaper 결과는 어드밴스와 오프셋이 0입니다. 이러한 위치값을 텍스트 레이아웃에 곧바로 넘기지 마십시오. 어드밴스는 글꼴 메트릭 모듈에서 해석하고, 폴백은 shaperImpl 태그로 감지하십시오.
  • 빈 입력은 빈 런이 아니라 빈 glyphRuns 목록을 생성합니다. 소비자 쪽 반복 코드에는 길이가 0인 런을 위한 특별 처리가 필요하지 않습니다.
  • ScriptShaperRegistryPsr\Container\ContainerInterface를 직접 구현하지 않으므로, 타입 지정 접근자가 정적 분석에서 좁혀진 반환 타입을 유지합니다. getMongolian()getTibetan()을 사용하고, 범용 get()은 사용하지 마십시오.
  • 스크립트 태그는 정규 ISO 15924 alpha-4 값으로 매칭되며, 대소문자 구분 없이 저장됩니다. Mong 또는 Tibt를 전달하십시오. 조회 시 대소문자는 영향을 주지 않습니다.
  • CJK 확장 B 문자는 유니코드 평면 2에 위치하며, 서브셋에서 cmap Format 12 서브테이블을 강제합니다. 인코딩 경로가 이를 처리합니다. 기본 다국어 평면이 CJK의 전부라고 가정하지 마십시오.

기능 탐지는 ShaperFactory 인스턴스마다 한 번 실행되고 백엔드는 메모이즈되므로, 반복되는 create() 호출에는 추가 비용이 들지 않습니다. NullShaper는 입력 런의 코드포인트 수에 선형으로 비례하며 I/O가 없습니다. ScriptShaperRegistry 해석은 상수 시간 키 조회입니다. CjkFontValidator는 모든 코드포인트를 검사하는 대신 일정 간격으로 표본을 추출하므로, 20,000 글리프 CJK 글꼴에 대해서도 커버리지 검사를 저렴하게 유지합니다. 경과 시간 1500 ms와 피크 64 MB의 performance_budget는 일반적인 실행을 포괄합니다. 실제 셰이핑에서 비용을 지배하는 요소는 OpenType 백엔드 자체이며, 폴백이 활성화된 경우에는 이 처리 경로의 범위를 벗어납니다.

셰이퍼 심은 UTF-8 문자열을 소비합니다. NullShaper는 잘못된 형식의 UTF-8을 예외를 발생시키지 않고 최선의 방식으로 분할해 허용합니다. 폴백의 문서화된 계약이 이미 “실제 셰이핑 없음”이기 때문입니다. 호출자는 낮은 품질의 출력에 대비해야 합니다. 바이트 오프셋 클러스터 계약은 바이트 기반 길이를 사용하며, 이는 멀티바이트 입력에 대해 올바르고 코드포인트 단위로 어긋나는 클러스터 매핑 결함을 방지합니다. 실제 백엔드는 있는 경우 서드파티 네이티브 라이브러리입니다. 해당 입력을 신뢰할 수 없는 것으로 취급하고 런 길이를 상위 계층에서 제한하십시오. 스크립트 셰이퍼 레지스트리는 소비자가 공급한 제공자를 저장합니다. 이러한 구현의 신뢰 경계는 엔진이 아니라 소비자의 책임입니다.

주장표준증거
임베드된 CJK TrueType 페이스는 Identity-H CMap과 CIDFontType2 하위 글꼴을 갖춘 Type 0 글꼴로 방출됩니다.ISO 32000-2§9.7.4RAG 다이제스트는 라이선스 한도로 인해 잘림. 접두사 7a5258772f508e3b, 참조 문서 _downgraded-claims-o3.md
임베드된 Type 2 CIDFont는 CIDToGIDMap을 통해 문자 식별자를 글리프 인덱스로 매핑합니다.ISO 32000-2§9

두 절 모두 의역되었습니다. 두 번째는 다이제스트에 고정되어 있으며(B1 계약 페이지에서 재사용됨), 첫 번째는 ADR-013과 cmap 인코더 개발자 개요로 뒷받침됩니다. NextPDF는 규범 텍스트를 복제하지 않습니다. 셰이퍼 백엔드는 PDF 적합성과 독립적입니다. 여기의 적합성 주장은 인코딩 심이 생성하는 CJK 글꼴 사전 방출과 관련되며, ADR-013과 cmap 인코더 개발자 개요에 추가로 문서화되어 있습니다.

고급 텍스트 전처리 파이프라인과 추출 서비스는 Core 셰이퍼 심과 런 처리 값 타입 위에 구축됩니다. Core 텍스트 모듈은 심, 폴백, 스크립트 셰이퍼 레지스트리를 라이선스 없이 제공합니다. 전환 링크를 생략한 것은 의도적입니다.