Teknisk artikel

Automatisk strukturtaggning för tillgänglig PDF i Delphi

PDFlibPas kan tagga ett dokument medan det ritas. Slå på SetAutoTagMode så blir vanliga DrawText-anrop stycken, text ritad omedelbart efter RegisterHeading blir en rubrik på den nivån, löpande sidhuvuden och sidfötter blir artefakter som en läsare hoppar över, bilder blir figurer, och DrawTableRows bär tabellen, dess rader och dess celler in i strukturträdet

Alternativet — och fram till nyligen det enda valet — var att svepa in varje ritningsanrop i BeginTag och EndTag för hand. Det fungerar, och för dokument med ovanlig struktur är det fortfarande rätt verktyg. För den vanliga rapporten, fakturan eller kontoutdraget betyder det att tillgängligheten i utdata beror på att ingen någonsin glömmer ett par, över varje kodväg som ritar någonting

Vad lägesbitarna täcker

SetAutoTagMode tar en bitmask och returnerar det läge som tidigare var aktivt. AUTOTAG_TEXT (1) taggar text som ett stycke, eller som en rubrik när en förfaller. AUTOTAG_FURNITURE (2) markerar löpande sidhuvuden, sidfötter och sidnummer som artefakter. AUTOTAG_FIGURE (4) gör en ritad bild till en figur, eller till en artefakt när den deklarerades som dekorativ. AUTOTAG_TABLE (8) bär ritade tabeller in i strukturträdet. AUTOTAG_DEFAULT är 15, vilket är alla fyra

Att slå på läget markerar också dokumentet som taggat, och det steget är mindre kosmetiskt än det låter. En läsare antar att ett dokument är otaggat om inte katalogen säger annat (ISO 32000-1 §14.7.1), så en fil som bär ett fullständigt strukturträde utan /MarkInfo-deklaration meddelas av hjälpteknik som sakna struktur helt. Trädet finns där; ingenting läser det

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;

Hur vet en rubrik vilken text den hör till?

RegisterHeading anger nivån för nästa text som ritas, och den väntar på text. Om en bild ritas däremellan blir bilden en figur och rubriken förblir väntande på texten som följer. Det beteende är avsiktligt: alternativet, där bilden tar rubriknivån, producerade dokument där en dekorativ linje under en titel utannonserades som titeln

Samma regel om "förbrukas på en post" styr figurer. RegisterFigure tillhandahåller den beskrivning nästa bild bär, och RegisterDecoration deklarerar nästa bild som en linje, ram eller bakgrund som inte bär någon betydelse. Båda förbrukas av en bild, så en senare bild ärver aldrig en beskrivning avsedd för en tidigare — vilket är hur alt-text hamnar fäst vid fel bild i handtaggad kod

Beskrivningen betyder mer än någon annan enskild sträng i ett tillgängligt dokument. En seende läsare får beskrivningen i stället för bilden, och det är allt de får. "Diagram" är inte en beskrivning; "Kvartalsvis intäkt per region, med östra regionen högst i Q3" är det

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

Tabeller, rubrikrader och var repetitionsbeslutet bor

Med tabellbiten på bär DrawTableRows tabellen, dess rader och dess celler in i strukturträdet, så en läsare kan säga vilken kolumn ett värde sitter i i stället för att läsa upp hela tabellen som en sträng av orelaterad text. SetTableHeaderRowCount anger hur många ledande rader som är rubriker; dessa rader skrivs som rubrikceller med en kolumnomfattning, vilket är vad som låter en läsare utannonsera rubriken för det värde användaren står på

Rubrikrader namngivna så här stannar där de är. Att upprepa dem högst upp på varje sida är ett layoutbeslut, och det förblir ett: DrawTaggedTableRows tar ett RepeatHeaderRows-argument för just det syftet. Att hålla de två åtskilda undviker att strukturträdet förvärvar en andra kopia av rubriken vid varje sidbrytning, vilket är vad en automatisk upprepning skulle producera

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;

Blanda automatisk och manuell taggning

Automatisk taggning träder åt sidan inuti en tagg öppnad för hand. En del av ett dokument kan beskrivas av din kod och resten överlåtas till biblioteket, utan att de två nästlar sig i varandra — vilket är arrangemanget som de flesta verkliga dokument vill ha. Omslagssidan och signaturblocket har struktur som bara du förstår; de tvåhundra sidorna brödtext däremellan har det inte

Två säkerhetsregler håller utdata ren. Ingenting taggas inuti en artefakt, eftersom innehåll markerat som en artefakt inte får bära något strukturelement. Och tom text öppnar inget element, så ett vindraget DrawText med en tom sträng kan inte producera ett strukturelement som en läsare skulle utannonsera som tomt. Båda är den typen av defekt som handtaggade dokument ackumulerar tyst och som en validator rapporterar i klump månader senare

Vad automatisk taggning fortfarande inte beslutar åt dig

Läsordning bortom ritordning, semantiska roller som inte är stycke, rubrik, figur eller tabell, och språkdeklarationer. Automatisk taggning tilldelar struktur i den ordning innehåll ritas — om din layoutkod ritar sidospaltet före brödtexten är det den ordning trädet registrerar. För dokument där visuell ordning och läsordning genuint skiljer sig åt förblir det manuella taggnings-API:et rätt verktyg, och genomgången av taggad PDF och tillgänglighetsstruktur täcker roller, omfattningar och rubrikbindningar i detalj

När dokumentet är klart, validera i stället för att anta: noterna om PDF/A- och PDF/UA-preflight visar hur man får ett utslag på den struktur man producerat, och genomgången av datamängdsdriven rapportexport täcker var dessa anrop passar i en rapportmotor som genererar sin layout från data

PDFlibPas är ett inbyggt Pascal PDF-bibliotek för Delphi, C++Builder och Lazarus utan extern PDF-runtime, så tillgänglig utdata produceras av samma kod som ritar dokumentet — se PDFlibPas produktsida för hela API:et och plattformslistan