Quand HotXLS exporte une feuille de calcul vers PDF avec le balisage automatique activé, les images de feuille de calcul qui portent un texte alternatif s'émettent maintenant comme des éléments de structure /Figure indépendants avec une entrée Unicode /Alt, des identifiants de contenu marqué denses propres à la page et des entrées exactes dans l'arbre parent. Les images sans texte alternatif restent des artefacts décoratifs, et les graphiques restent des artefacts aussi. Ce périmètre précis compte : il rend les images informatives accessibles à un lecteur d'écran, et ce n'est pas la même chose qu'une conformité PDF/UA complète
La mécanique derrière cela est plus intéressante que la description de la fonctionnalité, car deux d'entre elles sont du genre de détail qui produit silencieusement un PDF structurellement valide dont la structure pointe vers le mauvais contenu
Qu'est-ce qui compte comme une image informative ?
Seul un AltText non vide. La propriété TXLSXImage.AltText fait l'aller-retour avec l'attribut OOXML descr des propriétés non visuelles de l'image, qui est là où Excel stocke le texte qu'un utilisateur tape dans le volet de texte alternatif. C'est le seul signal du fichier que l'auteur a considéré l'image comme porteuse d'information plutôt que de décoration, donc c'est le seul signal auquel l'exportateur fait confiance
Deux quasi-bons sont délibérément refusés. Le champ titre, stocké séparément de la description, n'est pas un substitut : un titre est un nom pour l'objet, pas un équivalent textuel de celui-ci, et le promouvoir en /Alt produirait un document qui passe une vérification automatisée tout en annonçant « Image 3 » à un lecteur d'écran. Une description vide n'est pas non plus un écart à remplir par un espace réservé ; elle signifie que l'image reste un artefact, ce qui est le résultat correct pour un logo ou un filet séparateur. Les graphiques restent aussi des artefacts pour l'instant, car l'équivalent textuel d'un graphique est ses données et en synthétiser un depuis les séries serait de l'invention plutôt que de l'extraction
uses
lxHandleX, lxPDF;
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Exporter: TXLSPDFExport;
I: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('regional-review.xlsx');
Sheet := Book.Sheets.ByPos[0];
// Auditez avant d'exporter : une image sans description
// sera exportée comme un artefact décoratif
for I := 0 to Sheet.Images.Count - 1 do
if Sheet.Images[I].AltText = '' then
Sheet.Images[I].AltText := DescribeImage(Sheet.Images[I].Name);
Exporter := TXLSPDFExport.Create;
try
Exporter.TagMode := xlsPdfTagsAutomatic;
Exporter.DocumentLanguage := 'en-US';
Exporter.SaveAsPDF(Sheet, 'regional-review.pdf');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
Pourquoi la page a-t-elle besoin d'un allocateur MCID unique ?
Parce que l'arbre parent est un tableau indexé par identifiant de contenu marqué, et deux allocateurs produisent deux entrées revendiquant le même emplacement. Le PDF balisé relie le contenu à la structure dans les deux sens. Du côté du contenu, une étendue du flux de contenu de la page est enveloppée dans des opérateurs BDC et EMC portant un numéro /MCID unique au sein de cette page. Du côté de la structure, le dictionnaire de page porte une clé /StructParents nommant une ligne du /ParentTree du document, et cette ligne est un tableau dont l'élément à l'index n est l'élément de structure possédant le MCID n
Une page de feuille de calcul contient des cellules de tableau et, maintenant, des figures. Si le baliseur de cellules compte ses identifiants depuis zéro et le baliseur de figures compte aussi depuis zéro, la première figure revendique l'emplacement que la première cellule possède déjà. Rien dans le fichier résultant n'est assez malformé pour qu'un analyseur le rejette : l'arbre de structure est intact, le contenu marqué est équilibré, et un validateur voit un document avec un arbre parent. Ce qu'obtient un lecteur d'écran est une cellule de tableau annoncée comme image, ou une image annoncée avec le texte d'une cellule. L'exportateur alloue donc depuis un compteur au niveau de la page partagé par les deux baliseurs, et gèle l'enregistrement de page seulement une fois le numéro d'objet de page connu, puisque la ligne d'arbre parent ne peut pas être écrite avant que la page à laquelle elle se réfère ait une identité
La Figure doit envelopper toute l'instance visible
Le placement naïf est d'envelopper l'opérateur Do qui invoque le XObject d'image, puisque c'est l'opérateur qui dessine l'image. Ce n'est pas suffisant. Une image de feuille de calcul est souvent dessinée avec une ombre derrière elle et un chemin de détourage autour d'elle, et ces marques font partie de l'objet visible. Laissées hors de la portée /Figure, elles deviennent du contenu non marqué, qui est exactement l'état qu'un audit de structure signale
La portée de contenu marqué s'ouvre donc avant l'ombre et se ferme après le dessin de l'image, couvrant le détourage aussi. Le partage est préservé là où le partage est correct : deux cellules montrant la même charge d'image référencent toujours un seul XObject d'image, car c'est une optimisation au niveau des ressources et n'a rien à voir avec la sémantique. Ce que chaque instance visible obtient est son propre MCID et son propre élément de structure, car deux occurrences du même logo à des endroits différents sont deux choses qu'un lecteur rencontre. Le placement d'images et la géométrie EMU qui positionne ces objets est couvert dans l'article sur la géométrie d'images
Ordre de lecture sur une page de feuille de calcul
L'ordre de lecture est une décision que l'exportateur doit prendre, car un tableur n'a pas de flux rédigé comme l'est un document. La règle adoptée est stable et facile à expliquer : pour chaque page, le tableau vient d'abord, puis les figures en ordre de dessin. Un lecteur entend donc le contenu tabulaire de la page puis ses images, plutôt que d'avoir des images entrelacées à la position quelconque que les objets de dessin ont occupée par hasard dans le fichier
Cet ordre est par page plutôt que par document, ce qui compte sur un classeur qui se pagine en dizaines de pages : la branche de structure de chaque page est autonome, donc un lecteur se déplaçant entre pages ne saute pas en arrière dans un tableau antérieur. Si vous avez besoin de contrôler la façon dont la feuille se pagine en premier lieu, l'interaction de mise en page et de zone d'impression est décrite dans l'article sur la protection et la mise en page
Ce que cela certifie, et ce qu'il ne certifie pas
Il certifie que les images informatives atteignent la technologie d'assistance avec leur description fournie par l'auteur, et que le mappage contenu-vers-structure est correct plutôt que simplement présent. Il ne rend pas la sortie conforme PDF/UA, et le décrire ainsi serait une affirmation que l'implémentation ne peut pas soutenir : les graphiques sont encore des artefacts, et une déclaration de conformité complète exige un audit de chaque type de structure, de chaque police, et des métadonnées du document dans leur ensemble
Si votre exigence est un profil d'archivage ou de conformité plutôt qu'une amélioration d'accessibilité, c'est une configuration d'export différente et un jeu de vérifications différent, décrits dans l'article sur l'export d'archivage PDF/A. Les deux se combinent, mais ils répondent à des auditeurs différents
Une suggestion pratique pour un pipeline de rapports : auditez le texte alternatif au point où le classeur est généré, pas au moment de l'export. Le générateur sait ce que représente chaque image de graphique ou diagramme intégré, et peut écrire une vraie description dans AltText ; une passe au moment de l'export ne peut que vous dire qu'une description manque. HotXLS lit et écrit XLS, XLSX, ODS et CSV nativement depuis Delphi et C++Builder sans aucune dépendance Excel, et ses options de configuration d'export sont listées sur la page produit du HotXLS Delphi spreadsheet component