Aller au contenu

Accessibilité : primitives de balisage et modèle de structure PDF/UA-2

NextPDF Core fournit des primitives qui facilitent une rédaction accessible : un arbre de structure logique, un mappage de rôles standard, le balisage du contenu marqué et des attributs de langue BCP-47 cohérents avec le modèle d’arbre de structure défini dans ISO 14289-2 (PDF/UA-2) et ISO 32000-2 §14.7. La conformité du fichier produit dépend du document final, des choix de contenu de l’auteur et d’un outil de contrôle externe. Ce n’est pas une garantie que la bibliothèque revendique en ton nom.

Fenêtre de terminal
composer require nextpdf/core

Un PDF balisé contient un arbre de structure logique dont la racine contient un unique élément de structure Document. Les technologies d’assistance lisent cet arbre pour en établir un ordre de lecture cohérent, indépendant de la mise en page visuelle (ISO 32000-2 §14.7.2 ; ISO 14289-2 §8.2.5.2). NextPDF modélise cela avec trois types qui coopèrent dans l’espace de noms NextPDF\Accessibility.

StructureTree gère la hiérarchie. Il alloue les identifiants de contenu marqué par page. Il suit les relations parent-enfant. Il sérialise la racine de l’arbre de structure, les éléments de structure, l’arbre des parents, le mappage de rôles et l’espace de noms de structure standard PDF 2.0, conformément à ISO 32000-2 §14.7. createRoot() initialise l’unique élément Document obligatoire avec un attribut de langue. addElement() rattache des enfants typés. hasRoot() et rootHasChildren() indiquent si l’arbre existe et s’il contient des descendants.

StructureElement est l’objet valeur d’un dictionnaire d’élément de structure. Il enregistre le type de structure standard (noms de la Table 368 tels que H1 à H6, P, L, LI, Table, Figure, Link), les entrées d’identifiant de contenu marqué et les attributs d’accessibilité optionnels pour le texte alternatif, le texte de remplacement, le titre et la langue. Un même élément peut couvrir plusieurs pages, en accumulant une entrée d’identifiant par page afin que le tableau des enfants référence le contenu marqué au-delà des limites de page.

TaggedContentEmitter fait le lien entre le pipeline HTML et l’arbre de structure. Lorsque Document::enableTaggedPdf() est actif, le moteur de rendu HTML connecte l’émetteur afin que les éléments de niveau bloc produisent des opérateurs de contenu marqué appariés aux nœuds d’élément de structure correspondants. HtmlToStructureMap fournit le mappage fondé sur une table entre les balises HTML et les types de structure PDF (ISO 14289-2 §8). L’émetteur envoie le contenu décoratif récurrent, comme les zones HTML d’en-tête et de pied de page, vers un artefact, ce qui le maintient hors de l’ordre de lecture.

Le balisage de langue est validé par Bcp47Validator (RFC 5646). Il propose un contrôle syntaxique de bonne formation et un contrôle de validité adossé au registre. Le mode strict (ConformancePolicy::strictUa2()) rejette les balises malformées à la frontière de l’API, plutôt que de les abandonner silencieusement au moment de l’écriture. Cela correspond à l’exigence de la norme ISO 14289-2 §8.4.4 selon laquelle l’entrée de langue du catalogue doit se résoudre en une langue spécifique.

SymboleNatureRésumé
Document::enableTaggedPdf(string $lang = 'en', ?ConformancePolicy $policy = null): staticméthodeActive l’arbre de structure et la passerelle HTML ; renseigne les entrées mark-info et la langue du catalogue.
Document::setLanguage(string $lang): staticméthodeDéfinit la langue naturelle au niveau du document (BCP-47).
Document::isTaggedPdfEnabled(): boolméthodeIndique si le mode de conformité actif impose le balisage structurel.
StructureTree::createRoot(string $lang = 'en'): intméthodeCrée l’unique élément racine Document obligatoire.
StructureTree::addElement(int $parentIndex, string $type, int $pageIndex, ...): intméthodeRattache un élément de structure enfant typé.
StructureTree::hasRoot(): bool et rootHasChildren(): boolméthodeIndique si l’arbre existe et s’il possède des descendants.
StructureElementclasse finaleObjet valeur d’un élément de structure (texte alternatif, texte de remplacement, titre, langue, identifiants).
RoleMap::standard(): array<string,string>statiqueVocabulaire de types de structure standard (ISO 32000-2 Table 368 plus les types PDF 2.0).
Bcp47Validator::isWellFormed/isValid/validate/normaliseméthodeValidation des balises de langue RFC 5646, à la fois syntaxique et adossée au registre.
AccessibilityAutoFixerRegistryclasse finaleRegistre optionnel de style PSR-11 pour les correcteurs heuristiques de structure.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// The BCP 47 tag drives the catalog language entry and the
// structure-tree root language attribute.
$doc->enableTaggedPdf(lang: 'en');
$doc->setTitle('Tagged accessibility demo');
$doc->addPage();
// Semantic HTML maps to structure elements: h1 to /H1, p to /P,
// ul and li to /L plus /LI. Text runs are wrapped in
// marked-content operators with stable identifiers.
$doc->writeHtml('<h1>Document title</h1><p>Body paragraph.</p>');
$doc->save(__DIR__ . '/output/tagged.pdf');
<?php
declare(strict_types=1);
use NextPDF\Conformance\ConformancePolicy;
use NextPDF\Core\Document;
use NextPDF\Exception\InvalidConfigException;
use Psr\Log\LoggerInterface;
final class AccessibleReportWriter
{
public function __construct(private readonly LoggerInterface $logger)
{
}
public function render(string $html, string $bcp47Lang, string $outPath): void
{
$doc = Document::createStandalone();
try {
// strictUa2() rejects malformed BCP 47 tags at the API
// boundary (ISO 14289-2 §8.4.4) instead of dropping silently.
$doc->enableTaggedPdf($bcp47Lang, ConformancePolicy::strictUa2());
} catch (InvalidConfigException $e) {
$this->logger->error('Rejected language tag for tagged PDF', [
'lang' => $bcp47Lang,
'reason' => $e->getMessage(),
]);
throw $e;
}
$doc->setTitle('Quarterly accessibility report')
->setLanguage($bcp47Lang)
->addPage();
$doc->writeHtml($html);
// The engine emits a Degraded / ComplianceRisk advisory directing
// the caller to validate externally; surface it to operators
// rather than treating tagged output as certified.
foreach ($doc->getWarnings() as $warning) {
$this->logger->warning('Tagged-PDF advisory', [
'code' => $warning->code->value,
'message' => $warning->message,
]);
}
$doc->save($outPath);
}
}
  • Ordre des appels. Appelle enableTaggedPdf() avant writeHtml(). Le pipeline HTML vérifie le mode de conformité au moment de construire le parseur et ne connecte pas rétroactivement l’émetteur pour le contenu déjà rendu.
  • Arbre de structure vide. Un document avec enableTaggedPdf() mais sans descendant de structure rattaché ne déclare pas PDF/UA-2 dans ses métadonnées. Le critère de publication est rootHasChildren(), et non hasRoot(), car un fichier qui revendique PDF/UA-2 avec un arbre de structure vide est rejeté par les validateurs (ISO 14289-2 §5 ; vérifié par EmptyTaggedPdfDoesNotAdvertisePdfUa2Test).
  • Effondrement du mode de conformité. Appeler enablePdfA() et enableTaggedPdf() sur le même document remplace le discriminant de conformité à valeur unique par celui du dernier appel. Les effets de bord (arbre de structure, mark-info) restent additifs, et un avertissement CONFORMANCE_MODE_CLOBBERED est émis pour rendre cet écrasement observable.
  • Les correcteurs automatiques ne sont pas automatiques. Les correcteurs intégrés (EmptyTagStripper, LegacyLangNormaliser, RootLangFallback) sont fournis sous NextPDF\Accessibility\AutoFixer\* mais ne sont jamais enregistrés automatiquement. Le consommateur doit les enregistrer explicitement sur AccessibilityAutoFixerRegistry.

NextPDF émet une structure cohérente avec le modèle d’arbre de structure PDF/UA-2, mais il ne crée pas automatiquement la sémantique qu’il ne peut pas déduire. Les éléments suivants requièrent un balisage ou des attributs fournis par l’auteur et ne sont pas générés pour toi :

  • le texte alternatif des images et autres contenus non textuels ;
  • la portée des en-têtes de table et l’association entre en-têtes et cellules au-delà de ce qu’exprime le balisage HTML ;
  • le texte décrivant l’objet d’un lien lorsque le texte visible du lien n’est pas auto-descriptif ;
  • la sémantique de liste pour un contenu présenté visuellement comme une liste, sans balisage de liste ;
  • un ordre de lecture corrigé lorsque l’ordre de la source diffère de l’ordre de lecture voulu ;
  • la classification décoratif-versus-significatif d’un contenu ambigu.

La bibliothèque n’effectue aucune vérification PDF/UA-2 de bout en bout. Le moteur d’exécution lui-même émet un avis Degraded / ComplianceRisk (PDFUA2_FOUNDATIONAL) invitant l’appelant à faire contrôler la sortie avec un outil externe pour la validation finale en production. Valide-la avec un outil de contrôle PDF/UA (par exemple, veraPDF). NextPDF ne revendique pas la conformité en ton nom. La conformité du document final dépend des choix de rédaction et d’un validateur, pas du simple appel à l’API.

La construction de l’arbre de structure est linéaire par rapport au nombre d’éléments de structure. L’allocation d’identifiant est en temps constant amorti par séquence de contenu marqué. La sérialisation s’effectue en une seule passe linéaire sur l’ensemble des éléments. Le coût dominant du balisage piloté par HTML est le pipeline HTML lui-même, et non l’émission des balises. Le plafond par recipe déclaré dans performance_budget (1500 ms de temps écoulé, 64 Mo de pic) s’applique à un document multipage sémantique typique. Les documents volumineux passent à l’échelle linéairement selon le nombre d’éléments plutôt que selon le nombre de pages.

Les balises de langue et les attributs d’accessibilité sont écrits dans les objets nom et chaîne du PDF. NextPDF les échappe via PdfStringEscaper afin que des valeurs de langue, de texte alternatif, de texte de remplacement et de titre malformées ou hostiles ne puissent pas sortir de leur contexte d’objet PDF. Le mode strict rejette en outre les balises BCP-47 non enregistrées à la frontière de l’API, réduisant la surface d’entrée avant qu’elle n’atteigne le writer. Les attributs d’accessibilité peuvent contenir du texte libre fourni par l’auteur. Traite-les comme une sortie non fiable et applique-leur la même relecture que celle que tu appliques au reste du contenu du document. Consulte le module Conformance pour le comportement des outils de contrôle de profil.

Cette page met en correspondance le comportement de la bibliothèque avec les identifiants de clauses. Elle n’affirme pas que ta sortie est conforme. Les clauses citées sont paraphrasées, jamais citées textuellement. Consulte le mappage de la spécification PDF/UA-2 pour le tableau des dispositions et la non-couverture explicite. Les empreintes des fragments de citation sont consignées dans docs/public/modules/core/_normative-evidence-a11y.md.