Ga naar inhoud

Tekst: shaping-grens, CJK en run-verwerking

De tekstmodule definieert de shaping-grens. De module stelt een kleine interface beschikbaar die een 8-bits Unicode Transformation Format (UTF-8)-run omzet in gepositioneerde glyphs, een echte OpenType-backend selecteert zodra die beschikbaar is, deterministisch terugvalt wanneer dat niet zo is, en een register biedt voor script-specifieke shapers.

Terminal window
composer require nextpdf/core:^3

ShaperInterface verbindt de tekst-layout-pipeline met een OpenType-shaping-engine. De interface blijft bewust klein: één shape()-methode neemt een ShaperInput aan en retourneert een ShapingResult. Dat retourtype is de enige uitvoer die consumenten zien. Implementaties mogen geen interne details van de shaping-engine lekken; het getypeerde retourtype dwingt die grens af. ShapingResult bevat de lijst met GlyphRun-records, de teruggegeven brontekst, het script en de richting, plus een shaperImpl-tag die de backend identificeert die het resultaat heeft geproduceerd.

Backend-selectie is expliciet en rapporteert mogelijkheden zonder giswerk. ShaperFactory voert één capability-test uit. Als de host een werkende HarfBuzz-binding heeft, retourneert create() de door HarfBuzz ondersteunde shaper. Anders retourneert de factory NullShaper. NullShaper is een doorgeef-fallback: deze genereert één synthetische glyph per Unicode-codepunt, met nul advances en nul offsets. Het resultaat krijgt een tag zodat observability de fallback kan detecteren, en het bepalen van advances blijft bij de font-metrics-module. Dit pad is een gedocumenteerde degradatie, geen volledige shaping. Substitutie, ligaturen, mark-positionering en contextuele vormen vereisen de echte backend. wouldUseRealShaper() is een diagnostisch predicaat. Productiecode moet in plaats daarvan vertakken op de shaperImpl-tag van het resultaat.

Script-specifieke shaping is een service provider interface (SPI), geen meegeleverde implementatie. ScriptShaperRegistry is een register in PHP Standards Recommendation 11 (PSR-11)-stijl dat op basis van de International Organization for Standardization (ISO) 15924-scripttag een MongolianShaperInterface of TibetanShaperInterface oplost. Het register slaat sleutels hoofdletterongevoelig op en vertrouwt op één bron van waarheid voor de geldigheid van scriptcodes. Het register en de script-shaper-interfaces vormen een bevroren contract, zodat een extensie een Phase-12-provider kan registreren zonder de aanroepplaatsen aan te raken. De engine levert de grens. Consumenten leveren de providers voor complexe scripts.

De run-verwerking van Chinees, Japans en Koreaans (CJK) ligt aan de coderingsgrens van de typografie. Een ingebed CJK-TrueType-lettertype wordt uitgeschreven als een Type 0-lettertype met een Identity-H-CMap en een CIDFontType2-afstammeling, zoals behandeld in ISO 32000-2 §9.7.4 (de digest van retrieval-augmented generation (RAG) is afgekapt door de licentielimiet; vastgelegd in _downgraded-claims-o3.md). Wanneer het TrueType-programma is ingebed, koppelt de Type 2-CIDFont character identifiers aan glyph-indices via de CIDToGIDMap-entry, zoals behandeld in ISO 32000-2 §9 (de digest is vastgepind door de B1-contractpagina). De subsetter behoudt de oorspronkelijke glyph-nummering, zodat een /CIDToGIDMap /Identity geldig blijft voor de subset. CjkFontValidator controleert of een kandidaat-lettertype de Unicode-blokken dekt die een script nodig heeft voordat dat lettertype wordt gekozen.

TypeSoortBelangrijkste ledenStabiliteitSinds
ShaperInterfaceinterfaceshape(ShaperInput): ShapingResultstabiel3.2.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()stabiel3.2.0
NullShaperfinal readonly classdoorgeef-fallback-shaperstabiel3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImplstabiel3.2.0
ScriptShaperRegistryfinal classregisterMongolian(), getMongolian(), hasMongolian(), en de Tibetaanse equivalentenstabiel3.1.0
CjkFontValidatorfinal classvalidateCoverage(), detectScript(), isCjkCodepoint()stabiel1.0.0

Het methodepatroon register*, get* en has* van ScriptShaperRegistry en de script-shaper-interfaces vormt een bevroren contract. Volgens het ontwerp is ShapingResult de enige shaper-uitvoer die consumenten kunnen zien.

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() configureert de capability-test voor productie. create() memoïseert de geselecteerde backend voor de levensduur van de factory. Gebruik wouldUseRealShaper() en de shaperImpl-tag op elk resultaat om de mogelijkheden te inspecteren.

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();
}
}

Het register is het integratiepunt voor providers van complexe scripts. De engine levert de grens en de bevroren accessorvorm. Consumenten leveren de Mongoolse en Tibetaanse implementaties.

  • Een NullShaper-resultaat heeft nul advances en nul offsets. Geef die posities niet rechtstreeks door aan de tekst-layout. Bepaal advances via de font-metrics-module en detecteer de fallback via de shaperImpl-tag.
  • Lege invoer produceert een lege glyphRuns-lijst, geen lege run. Iteratiecode aan de kant van de consument heeft geen speciaal geval nodig voor een run met lengte nul.
  • ScriptShaperRegistry implementeert Psr\Container\ContainerInterface niet rechtstreeks, zodat getypeerde accessors bij statische analyse hun versmalde retourtype behouden. Gebruik getMongolian() en getTibetan(), geen generieke get().
  • Scripttags worden gematcht op de canonieke ISO 15924 alpha-4-waarde en hoofdletterongevoelig opgeslagen. Geef Mong of Tibt door. Hoofdlettergebruik heeft geen invloed op de lookup.
  • Tekens uit CJK Extension B bevinden zich in Unicode plane 2 en dwingen een cmap Format 12-subtabel af in de subset. Het coderingspad verwerkt dit. Ga er niet van uit dat de Basic Multilingual Plane alle CJK-tekst dekt.

De capability-test wordt eenmaal per ShaperFactory-instantie uitgevoerd en de backend wordt gememoïseerd, zodat herhaalde create()-aanroepen gratis zijn. NullShaper is lineair in het aantal codepunten van de invoer-run en voert geen input/output (I/O) uit. Een lookup in ScriptShaperRegistry gebeurt op sleutelbasis en heeft constante tijd. CjkFontValidator bemonstert codepunten met een stride in plaats van elk afzonderlijk te testen, waardoor dekkingscontroles goedkoop blijven, zelfs voor een CJK-lettertype met 20,000 glyphs. Het performance_budget van 1500 ms wall en 64 MB peak dekt een typische run. Bij echte shaping vormt de OpenType-backend de dominante kostenpost. Die kosten vallen buiten de scope van deze module wanneer de fallback actief is.

De shaper-grens verwerkt een UTF-8-string. NullShaper tolereert misvormde UTF-8 door op best-effort-basis te splitsen in plaats van een fout op te werpen, omdat het gedocumenteerde fallback-contract toch al “geen echte shaping” is. De aanroeper is voorbereid op uitvoer van lage kwaliteit. Het byte-offset-clustercontract gebruikt bytegeoriënteerde lengte, wat correct is voor multi-byte-invoer en een off-by-codepoint-fout in de clustertoewijzing vermijdt. Wanneer aanwezig, is de echte backend een native bibliotheek van derden. Behandel de invoer daarvan als niet-vertrouwd en beperk de run-lengte stroomopwaarts. Het script-shaper-register slaat door consumenten aangeleverde providers op. Die implementaties vallen binnen de vertrouwensgrens van de consument, niet die van de engine.

BeweringStandaardClausuleBewijs
Een ingebed CJK-TrueType-lettertype wordt uitgevoerd als een Type 0-lettertype met een Identity-H-CMap en een CIDFontType2-afstammeling.ISO 32000-2§9.7.4De digest van retrieval-augmented generation (RAG) is afgekapt door de licentielimiet; prefix 7a5258772f508e3b, zie _downgraded-claims-o3.md
Een ingebed Type 2-CIDFont koppelt character identifiers aan glyph-indices via CIDToGIDMap.ISO 32000-2§9

Beide clausules zijn geparafraseerd. De tweede is digest-vastgepind (hergebruikt van de B1-contractpagina), en de eerste wordt onderbouwd door ADR-013 en het cmap-encoder-overzicht voor ontwikkelaars. NextPDF reproduceert geen normatieve tekst. De shaper-backend staat los van conformiteit met Portable Document Format (PDF). De conformiteitsbeweringen hier gaan over de emissie van de CJK-font dictionary die door de coderingsgrens wordt geproduceerd. ADR-013 en het cmap-encoder-overzicht voor ontwikkelaars documenteren dat pad in meer detail.

De geavanceerde pipeline voor tekst-voorverwerking en de extractieservices bouwen voort op de Core-shaper-grens en de waardetypen voor run-verwerking. De Core-tekstmodule levert de grens, de fallback en het script-shaper-register zonder licentie. De ontbrekende conversielink is opzettelijk.