PDFlibPas peut baliser un document pendant son dessin. Activez SetAutoTagMode et les appels DrawText ordinaires deviennent des paragraphes, le texte dessiné juste après RegisterHeading devient un titre de ce niveau, les en-têtes et pieds de page courants deviennent des artéfacts qu'un lecteur ignore, les images deviennent des figures, et DrawTableRows porte le tableau, ses lignes et ses cellules dans l'arborescence de structure
L alternative — et jusqu'à récemment la seule option — consistait à envelopper chaque appel de dessin dans BeginTag et EndTag à la main. Cela fonctionne, et pour les documents à structure inhabituelle c'est encore l'outil approprié. Pour le rapport, la facture ou le relevé ordinaires, cela signifie que l'accessibilité de la sortie dépend de ce que personne n'oublie jamais une paire, sur chaque chemin de code qui dessine quoi que ce soit
Ce que couvrent les bits de mode
SetAutoTagMode prend un masque de bits et renvoie le mode précédemment en vigueur. AUTOTAG_TEXT (1) balise le texte comme un paragraphe, ou comme un titre lorsqu'un est dû. AUTOTAG_FURNITURE (2) marque les en-têtes, pieds de page et numéros de page courants comme des artéfacts. AUTOTAG_FIGURE (4) transforme une image dessinée en figure, ou en artéfact lorsqu'elle a été déclarée décorative. AUTOTAG_TABLE (8) porte les tableaux dessinés dans l'arborescence de structure. AUTOTAG_DEFAULT vaut 15, ce qui correspond aux quatre
Activer le mode marque aussi le document comme balisé, et cette étape est moins cosmétique qu'il y paraît. Un lecteur considère un document comme non balisé à moins que le catalogue n'indique le contraire (ISO 32000-1 §14.7.1), donc un fichier portant une arborescence de structure complète sans déclaration /MarkInfo est annoncé par les technologies d'assistance comme n'ayant aucune structure du tout. L arborescence est là ; rien ne la lit
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // text + furniture + figures + tables
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // becomes H1
Lib.SetTextSize(11);
Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Comment un titre sait-il à quel texte il appartient ?
RegisterHeading nomme le niveau pour le prochain texte dessiné, et il attend du texte. Si une image est dessinée entre-temps, l'image devient une figure et le titre reste en attente pour le texte qui suit. Ce comportement est délibéré : l'alternative, où l'image prend le niveau de titre, produisait des documents où un filet décoratif sous un titre était annoncé comme le titre
La même règle de « consommé par un seul élément » régit les figures. RegisterFigure fournit la description que la prochaine image porte, et RegisterDecoration déclare la prochaine image comme un filet, une bordure ou un arrière-plan qui ne porte aucune signification. Les deux sont consommés par une seule image, donc une image ultérieure n'hérite jamais d'une description destinée à une précédente — c'est ainsi que le texte alternatif se retrouve attaché à la mauvaise image dans du code balisé à la main
La description compte plus que toute autre chaîne unique dans un document accessible. Un lecteur non voyant reçoit la description à la place de l'image, et c'est l'intégralité de ce qu'il obtient. « Graphique » n'est pas une description ; « Chiffre d'affaires trimestriel par région, avec la région est la plus élevée au T3 » en est une
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // becomes a tagged Figure
Lib.RegisterDecoration; // meaningless rule
Lib.AddImageFromFile('divider.png', 0); // drawn inside a layout artifact
Tableaux, en-têtes et où réside la décision de répétition
Avec le bit de tableau activé, DrawTableRows porte le tableau, ses lignes et ses cellules dans l'arborescence de structure, donc un lecteur peut dire dans quelle colonne se trouve une valeur plutôt que de lire tout le tableau comme une suite de texte sans rapport. SetTableHeaderRowCount nomme combien de lignes d'en-tête sont des en-têtes ; ces lignes sont écrites comme des cellules d'en-tête portant une portée de colonne, ce qui permet à un lecteur d'annoncer l'en-tête de la valeur sur laquelle se trouve l'utilisateur
Les lignes d'en-tête nommées ainsi restent où elles sont. Les répéter en haut de chaque page est une décision de mise en page, et elle le reste : DrawTaggedTableRows prend un argument RepeatHeaderRows exactement à cette fin. Garder les deux séparés évite que l'arborescence de structure n'acquière une seconde copie de l'en-tête à chaque saut de page, ce qu'une répétition automatique produirait
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // row 1 is the header band
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... fill the data rows ...
// Draw rows 1..40 into a 600pt band, repeating one header row per page
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
Mélanger balisage automatique et manuel
Le balisage automatique se tient à l'écart à l'intérieur d'une balise ouverte à la main. Une partie d'un document peut être décrite par votre code et le reste laissée à la bibliothèque, sans que les deux ne s'emboîtent — c'est l'arrangement que la plupart des documents réels souhaitent. La page de couverture et le bloc de signature ont une structure que vous seul comprenez ; les deux cents pages de texte courant entre les deux non
Deux règles de sécurité gardent la sortie propre. Rien n'est balisé à l'intérieur d'un artéfact, car le contenu marqué comme artéfact ne doit porter aucun élément de structure. Et un texte vide n'ouvre aucun élément, donc un DrawText parasites avec une chaîne vide ne peut pas produire un élément de structure qu'un lecteur annoncerait comme vide. Les deux sont le type de défaut que les documents balisés à la main accumulent silencieusement et qu'un validateur signale en bloc des mois plus tard
Ce que le balisage automatique ne décide toujours pas pour vous
L ordre de lecture au-delà de l'ordre de dessin, les rôles sémantiques qui ne sont ni paragraphe, titre, figure ou tableau, et les déclarations de langue. Le balisage automatique assigne la structure dans l'ordre où le contenu est dessiné — si votre code de mise en page dessine la barre latérale avant le corps, c'est l'ordre que l'arborescence enregistre. Pour les documents où l'ordre visuel et l'ordre de lecture diffèrent réellement, l'API de balisage manuel reste le bon outil, et la visite guidée de PDF balisé et structure d'accessibilité couvre les rôles, portées et liaisons d'en-tête en détail
Quand le document est terminé, validez plutôt que de présumer : les notes sur le préflight PDF/A et PDF/UA montrent comment obtenir un verdict sur la structure que vous avez produite, et la visite guidée de export de rapport piloté par dataset couvre où ces appels s'insèrent dans un moteur de rapport qui génère sa mise en page à partir des données
PDFlibPas est une bibliothèque PDF native en Pascal pour Delphi, C++Builder et Lazarus sans aucun moteur PDF externe, donc la sortie accessible est produite par le même code qui dessine le document — voir la page produit PDFlibPas pour l'API complète et la liste des plateformes