콘텐츠로 이동
getnextpdf.com

안정성: 실험적

복합 스크립트 셰이핑 지원

옵트인 프리뷰. 복합 스크립트 셰이핑은 기본적으로 꺼져 있습니다. 꺼져 있으면 엔진은 기존 코드포인트-대-cmap 경로를 통해 렌더링합니다 — 그 기능이 없는 빌드와 바이트 단위로 동일합니다. libharfbuzz와 셰이핑 가능한 폰트가 있을 때만 켜고, 결과를 검증하세요.

HTML 렌더러는 티베트어와 몽골어를 위한 옵트인 복합 스크립트 셰이퍼를 추가합니다. 셰이퍼가 켜져 있으면, 감지된 범위 내 티베트어 또는 몽골어 런은 libharfbuzz를 통해 셰이핑되어 Identity-H 글리프 코드로 발행됩니다. 셰이퍼는 줄바꿈을 포함한 TrueType 및 CFF/OTTO 페이스의 가로 티베트어와, 위에서 아래로(TTB) 설정된 세로 몽골어를 다룹니다.

Terminal window
composer require nextpdf/core:^3

셰이퍼는 코어 패키지에 포함되어 제공됩니다. CssFeatureFlags::$complexTextShaping 옵트인은 @since 6.1.0입니다. 플래그가 켜져 있을 때 libharfbuzz는 런타임 요구 사항입니다 — 셰이퍼는 PHP의 FFI 확장을 통해 libharfbuzz를 호출합니다. 플래그가 꺼져 있으면, 라이브러리는 libharfbuzz 의존성을 갖지 않습니다.

복합 스크립트는 문맥에 따라 글리프를 재배열하고, 치환하고, 재배치합니다. 단순한 코드포인트-대-글리프 매핑은 그것들을 눈에 띄게 잘못 렌더링합니다. 셰이퍼는 범위 내 런을 libharfbuzz에 넘기고, libharfbuzz는 폰트의 OpenType 셰이핑 테이블을 적용하며, 엔진은 결과 글리프 시퀀스를 Identity-H 인코딩의 복합 Type 0 폰트로 발행합니다(ISO 32000-2 §9.7.4 — 표시되는 문자열은 2바이트 CID입니다).

범위는 의도적입니다. 셰이퍼는 티베트어와 몽골어 런을 인식하고 셰이핑합니다. 일반적인 복합 스크립트 커버리지를 주장하지 않습니다. 가로 티베트어는 TrueType 및 CFF/OTTO 페이스에서 줄바꿈과 함께 셰이핑됩니다. 몽골어는 위에서 아래로 세로로 셰이핑됩니다.

Fail-closed 경계 — 강하고 타입 지정됨

섹션 제목: “Fail-closed 경계 — 강하고 타입 지정됨”

셰이퍼는 셰이핑되지 않은, 시각적으로 깨진 글리프를 폴백으로 발행하지 않습니다. 충실하게 셰이핑할 수 없는 런은 대신 타입 지정 예외를 발생시킵니다.

  • ComplexScriptShapingException — 런을 충실하게 셰이핑할 수 없음: 폰트에 필요한 글리프가 없거나(.notdef가 발생하게 됨), CFF 페이스에 세로 경로에서 셰이핑을 요청하거나, 런에 링크가 포함되어 있거나, 몽골어 열에 줄바꿈이 필요한 경우(범위 밖 사례).
  • HarfBuzzUnavailableException — 플래그가 켜져 있지만 런타임에 FFI를 통해 libharfbuzz에 도달할 수 없음.

플래그가 꺼져 있으면, 범위 내 런은 기존 코드포인트-대-cmap 경로를 통해 렌더링됩니다. 이는 셰이핑 주장이 아니라 문서화된 한계입니다. 꺼진 경로는 OpenType 셰이핑을 적용하지 않으므로 문맥 의존 형태가 보장되지 않습니다. 꺼진 경로의 출력을 “셰이핑된” 것으로 설명하지 마세요.

정직성 경계 — 객관적 동등성이지 미적 승인이 아님

섹션 제목: “정직성 경계 — 객관적 동등성이지 미적 승인이 아님”

셰이핑 충실도는 HarfBuzz에 대해 객관적으로 검증됩니다. 발행된 글리프 식별자, 클러스터 매핑, 글리프 위치가 HarfBuzz 참조 출력과 일치합니다(글리프, 클러스터, 위치 동등성). 원어민 미적 검토 — 결과가 유창한 독자에게 자연스럽게 읽히는지 판단하는 것 — 는 출시 후 추적되는 후속 작업입니다. NextPDF는 이 API에서도 이 문서에서도 언어 품질 주장을 하지 않습니다. 객관적 동등성은 단언되며, 미적 품질은 단언되지 않습니다.

심볼위치역할
CssFeatureFlags::$complexTextShapingsrc/Html/CssFeatureFlags.php티베트어/몽골어 셰이퍼를 위한 옵트인 플래그(기본값 false).
Config::withCssFeatureFlags(CssFeatureFlags $flags): selfsrc/Core/Config.php플래그 집합을 문서 구성에 연결합니다.
ComplexScriptShapingExceptionsrc/Font/Shaper/ComplexScriptShapingException.php범위 내 런을 충실하게 셰이핑할 수 없을 때 발생합니다.
HarfBuzzUnavailableExceptionsrc/Font/Shaper/HarfBuzzUnavailableException.php플래그가 켜져 있지만 libharfbuzz를 사용할 수 없을 때 발생합니다.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\Html\Css\CssFeatureFlags;
$config = (new Config())->withCssFeatureFlags(
new CssFeatureFlags(complexTextShaping: true),
);
$doc = Document::createStandalone($config);
$doc->addPage();
$doc->writeHtml(
'<div style="font-family: NotoSerifTibetan;">བོད་སྐད་</div>',
);
$doc->save(__DIR__ . '/tibetan.pdf');

셰이핑 가능한 폰트를 등록하고, 셰이퍼로 옵트인하며, 두 가지 타입 지정 실패 모드를 명시적으로 처리합니다. 충실한 렌더 또는 명확한 예외 — 결코 조용히 깨진 글리프 런이 아닙니다.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\DocumentFactory;
use NextPDF\Exception\ComplexScriptShapingException;
use NextPDF\Exception\HarfBuzzUnavailableException;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Html\Css\CssFeatureFlags;
use NextPDF\Typography\FontRegistry;
$fontRegistry = new FontRegistry();
$fontRegistry->register('/path/to/NotoSerifTibetan-Regular.ttf', alias: 'NotoSerifTibetan');
$config = (new Config())->withCssFeatureFlags(
new CssFeatureFlags(complexTextShaping: true),
);
$factory = new DocumentFactory($fontRegistry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create($config);
$doc->setLanguage('bo');
$doc->addPage();
try {
$doc->writeHtml('<div style="font-family: NotoSerifTibetan;">བོད་སྐད་</div>');
} catch (HarfBuzzUnavailableException $e) {
// The flag is on but libharfbuzz is not reachable. Install it, or turn the
// flag off to fall back to the unshaped cmap path.
throw $e;
} catch (ComplexScriptShapingException $e) {
// The run cannot be shaped faithfully (missing glyphs, link in run,
// out-of-scope case). Fix the font or the content; do not ship broken glyphs.
throw $e;
}
$doc->save($out);
  • 켜져 있을 때 libharfbuzz가 필요합니다. 플래그가 켜져 있고 libharfbuzz가 없으면, 엔진은 HarfBuzzUnavailableException을 던집니다. 조용히 격하되지 않습니다.
  • 꺼짐은 “셰이핑된” 것이 아닙니다. 플래그가 꺼져 있으면, 범위 내 런은 OpenType 셰이핑 없이 cmap 경로를 통해 렌더링됩니다. 이는 문서화된 한계입니다. 그것을 셰이핑된 출력이라 부르지 마세요.
  • 범위는 티베트어와 몽골어입니다. 다른 복합 스크립트는 이 슬라이스의 범위 밖입니다.
  • 런 안의 링크는 fail-closed 처리됩니다. 링크 주석을 포함한 런은 ComplexScriptShapingException을 발생시킵니다. 링크 사각형이 셰이핑된 재배열을 따라갈 수 없기 때문입니다.
  • 언어 품질 주장은 없습니다. HarfBuzz와의 동등성은 단언되지만, 원어민 미적 품질은 추적되는 후속 작업이며 주장되지 않습니다.

셰이핑은 범위 내 런당 하나의 libharfbuzz 호출과 글리프 발행 패스를 추가하며, 글리프 개수에 선형입니다. 예산(wall_ms: 2000, peak_mb: 128)은 CJK/복합 스크립트 프로파일을 따릅니다. 셰이핑 폰트가 크고 폰트 처리가 비용을 지배하기 때문입니다.

셰이퍼를 활성화하면 네이티브 라이브러리인 libharfbuzz로의 FFI 호출이 도입됩니다. 폰트 파일은 셰이퍼에 도달하기 전에 타이포그래피 계층의 기존 검증이 처리하는, 신뢰 없는 바이너리 입력으로 남습니다. 셰이퍼는 이미 등록되고 이미 검증된 페이스를 소비합니다. 최종 사용자가 제공한 폰트의 출처는 신뢰 없는 것으로 취급하고, libharfbuzz는 신뢰할 수 있는 출처에서 프로비저닝하세요.

진술사양
셰이핑된 런은 복합 Type 0 폰트에서 Identity-H 2바이트 CID로 발행됩니다.ISO 32000-2§9.7.4
셰이퍼는 폰트의 OpenType 글리프 치환 및 위치 지정을 적용합니다.OpenType SpecificationGSUB / GPOS
클러스터 형성은 티베트어와 몽골어의 스크립트 속성을 따릅니다.Unicode Standard AnnexTibetan and Mongolian

이는 티베트어와 몽골어로 범위가 한정된, 객관적 HarfBuzz 동등성에 대해 검증된 프리뷰 구현입니다. 언어 품질 주장을 하지 않으며 생성된 파일에 대한 종단 간 PDF 적합성을 단언하지 않습니다. 표준 텍스트는 재현되지 않습니다.