Skip to main content Skip to footer

Document Accessibility for PDF Documents in C# .NET

GrapeCity Documents An accessible document can be accessed by people with disabilities. PDF documents include accessibility features and are therefore considered accessible. The PDF format follows the standard accessibility guidelines defined by the Web Content Accessibility Guidelines (WCAG) 2.0 (ISO/IEC 40500:2012) and the PDF/UA (ISO 14289-1) standard. Adding tags to PDFs makes them accessible.

Tagged PDF Documents

An accessible PDF is also referred to as a tagged PDF document. PDF tags have names similar to HTML tags. Here is a list of standard tags used in PDF documents. Adding these tags has no visual effect on the document. They add a hidden structure to the document which represents the document content in a manner recognizable by a screen reader or other text to speech recognition software.

GrapeCity Documents for PDF (GcPdf) allows users to create tagged PDF documents. Below we discuss how to add tags to different content elements such as text, paragraphs, lists, images, etc.

How to Create a Tagged PDF Document

A PDF document is composed of different content elements such as text, paragraphs, lists, images, and tables. Each of these elements can be represented by a standard PDF tag such as 'P' for paragraph, 'L' for list, and 'Figure' for an image.

When creating a tagged PDF document, add a tag for each content element. This collection of these tags is represented by a tree with child nodes. This tree is presented to the screen reader, which then uses it to read the PDF content out loud for people with disabilities.

Following the same approach as described above, GcPdf tags a PDF document using a set of structural elements. Each structural element represents a content element in the document.

For example, to represent a paragraph create a structural element of the 'P' tag type. structural elements are represented by StructElement class provided by the GcPdf library.

Follow the steps below to create a tagged PDF document with GcPdf:

  1. Create a new PDF document by initializing the GcPdfDocument class and add a new page to it by accessing the Pages property of the GcPdfDocument class.
  2. Fetch the page graphics by accessing the Graphics property of the Page class. The returned GcPdfGraphics class instance will be used to render the document content and create the logical structure tree by adding the appropriate tags.
  3. Create a container element by initializing an instance of the StructElement class and it to the tree root by accessing the StructTreeRoot property of the GcPdfDocument class. The container elements are added at the highest level of hierarchy to provide grouping for other block-level elements.
  4. Create a block-level element by initializing an instance of the StructElement class, based on the type of content that you adding to the document (such as paragraph, list, table, and images)
  5. Add the block element as a child in the container element (created in the above step) by accessing the Children property of the StructElement class.
  6. Generate the content for the block-level element by invoking the appropriate method such as DrawImage for rendering image, DrawTextLayout for rendering text/paragraphs, and so on. The content being generated must be rendered as marked content by invoking the BeginMarkedContent method of GcPdfGraphics class which must be enclosed by the EndMarkedContent method of GcPdfGraphics class. The BeginMarkedContent method accepts a parameter of type TagMcid class, which acts as the identification of the marked content.
  7. Append the generated marked content to the related structure element by accessing the ContentItems property of the StructElement class.
  8. Set the Marked property of MarkInfo class to True, by accessing the MarkInfo property of the GcPdfDocument class, this would indicate that the document conforms to the Tagged PDF conventions.
  9. Save the PDF document by invoking the Save method of the GcPdfDocument class.

Creating an Accessible PDF Document

  1. Create a PDF document consisting of several paragraphs, a list, and an image.

  2. Tag each content element added to the document. This creates the hidden structure presenting the document content in a form compatible with screen reader devices.

  3. Add two 'Part' container elements. The first Part element contains two paragraphs and a list, while the second contains an image.

The process decribed above allows you to tag any content element in a PDF. The code snippets below follow the above steps and implement them using GcPdf library members. They depict tagging paragraphs, images, and lists.

Creating a Document and Adding Container Elements:

public GcPdfDocument CreatePDF()
        {
            //Define general formatting for the PDF document
            ip = new PointF(52, 24);
            tl = new TextLayout(72);

            tftitle = new TextFormat();
            tftitle.Font = StandardFonts.TimesBold;
            tftitle.Underline = true;
            tftitle.FontSize = 24F;

            tfheading = new TextFormat();
            tfheading.FontBold = true;
            tfheading.Font = StandardFonts.TimesBold;
            tfheading.Underline = true;
            tfheading.FontSize = 20F;

            tfcontent = new TextFormat();
            tfcontent.FontBold = false;
            tfcontent.Font = StandardFonts.Times;
            tfcontent.FontSize = 12F;

            //Create new GcPdfDocument
            var doc = new GcPdfDocument();
            //Add first page to document
            lpage = doc.Pages.Add();
            //Fetch Page Graphics
            g = lpage.Graphics;

            //Create first "Part"(P) Struct element, this will contain two paragraphs(P) and a list(L)                       
            sePart1 = new StructElement("Part");
            //Add the part element to structure tree root
            doc.StructTreeRoot.Children.Add(sePart1);                                 

            //Create second Part element, it will contain image(Figure) element
            sePart2 = new StructElement("Part");
            //Add the part element to structure tree root
            doc.StructTreeRoot.Children.Add(sePart2);

            return doc;
        }
Add and Tag a Paragraph
public void TagParagraph(GcPdfDocument doc)
        {
            //TAG FIRST PARAGRAPH (Used for document title)
            //Create Paragraph struct element and add it to Part element:
            StructElement seTitle = new StructElement("P") { DefaultPage = lpage };
            sePart1.Children.Add(seTitle);           
            tl.MaxWidth = tl.MaxHeight = 500;            
            tl.TextAlignment = TextAlignment.Center;
            tl.Append("Create a document in Word \n", tftitle);
            tl.PerformLayout(true);
            //Render marked content for Paragraph element
            g.BeginMarkedContent(new TagMcid("P", 10));
            g.DrawTextLayout(tl, ip);
            g.EndMarkedContent();
            //Add marked content to Paragraph element
            seTitle.ContentItems.Add(new McidContentItemLink(10));

            tl.Clear();
            //TAG SECOND PARAGRAPH
            //Create Paragraph struct element and add it to Part element:
            StructElement sePara = new StructElement("P") { DefaultPage = lpage };
            sePart1.Children.Add(sePara);
            tl.MaxWidth = tl.MaxHeight = 500;
            tl.TextAlignment = TextAlignment.Justified;
            tl.Append("\nMicrosoft Word, also referred to as simply Word, is a word processor developed by Microsoft. " +
                "With Word on your PC, Mac, or mobile device, you can create documents from scratch, or using a template." +
                "Further, you can add text, images, art, and videos. It can also be used to access your documents from a " +
                "computer, tablet, or phone with OneDrive and share your documents to work with others. Track and review " +
                "changes is other interesting feature of Word.", tfcontent);
            tl.Append("\n \nCreate a document \n", tfheading);
            tl.PerformLayout(true);
            //Render marked content for Paragraph element
            g.BeginMarkedContent(new TagMcid("P", 20));
            g.DrawTextLayout(tl, new PointF(ip.X, ip.Y + 50));
            g.EndMarkedContent();
            //Add marked content to Paragraph element
            sePara.ContentItems.Add(new McidContentItemLink(20));           
        }
Add and Tag a List
public void TagList(GcPdfDocument doc)
        {          
            //TAG LIST
            //Create list element and add it to Part element:
            StructElement seList = new StructElement("L") { DefaultPage = lpage };
            sePart1.Children.Add(seList);

            //List items
            string[] steps = new string[] {" Open Word. Or, if Word is already open, select File > New.",
                                           " Select Blank document.",
                                           " Or double-click a template to open it and create the document using the selected template."};

            //Create ListItem elements and add them to list
            int itemNo = 1;
            int lblID = 1;
            int lbodyID = 0;
            for (var i = 0; i < steps.Length; i++)
            {
                if (i == 0)
                    ip.Y = ip.Y + 230;
                else
                    ip.Y = ip.Y + 20;
                lblID = lblID + 10;
                lbodyID = lblID + 1;

                //Create list element and add it to list:
                StructElement seListItem = new StructElement("LI") { DefaultPage = lpage };
                seList.Children.Add(seListItem);

                //Create list element label and add it to list item element:
                StructElement seListItemLabel = new StructElement("Lbl") { DefaultPage = lpage };
                seListItem.Children.Add(seListItemLabel);

                //Create list element body and add it to list item element:
                StructElement seListItemBody = new StructElement("LBody") { DefaultPage = lpage };
                seListItem.Children.Add(seListItemBody);

                //Render marked content for ListItem label element
                tl.Clear();
                tl.DefaultFormat.Font = StandardFonts.TimesBold;
                tl.Append($"{itemNo})", tfcontent);
                tl.PerformLayout(true);
                g.BeginMarkedContent(new TagMcid("Lbl", lblID));
                g.DrawTextLayout(tl, ip);
                g.EndMarkedContent();
                //Add marked content to ListItem label element
                seListItemLabel.ContentItems.Add(new McidContentItemLink(lblID));

                //Render marked content for ListItem body element
                tl.Clear();               
                tl.Append(steps[i] + "\n", tfcontent);
                tl.PerformLayout(true);
                g.BeginMarkedContent(new TagMcid("LBody", lbodyID));
                g.DrawTextLayout(tl, new PointF(ip.X + 10, ip.Y));
                g.EndMarkedContent();
                //Add marked content to ListItem body element
                seListItemBody.ContentItems.Add(new McidContentItemLink(lbodyID));

                itemNo++;
            }          
        }
Add and Tag an Image
public void TagImage(GcPdfDocument doc)
        {
            //TAG IMAGE           
            //Create image struct element and add to Part element:
            StructElement seImage = new StructElement("Figure") { DefaultPage = lpage };
            //Set alternative text for Image Tag
            seImage.AlternateDescription = "New Document Image";         
            sePart2.Children.Add(seImage);

            //Draw image in the document as marked content
            var image = Image.FromFile(Path.Combine("Resources", "Images", "WordDocument.png"));
            g.BeginMarkedContent(new TagMcid("Figure", 100));
            g.DrawImage(image, new RectangleF(ip.X + 250, ip.Y + 230, 0.5F, 0.5F), null, ImageAlign.CenterImage);
            g.EndMarkedContent();
            //Add marked content to image element
            seImage.ContentItems.Add(new McidContentItemLink(100));
        }
Set the Document as 'Marked' and Save the Tagged PDF Document

Invoke the methods defined above to generate and save the tagged PDF document using the following code snippet:

static void Main(string[] args)
        {
            Console.WriteLine("Generating tagged PDF document");

            //Instantiate TagPDF class to invoke the tag methods
            TagPDF tagpdf = new TagPDF();

            //Create a new PDF document
            //and add container elements to it           
            GcPdfDocument doc = tagpdf.CreatePDF();           

            //Add and tag the document title paragraph
            //and first content paragraph
            tagpdf.TagParagraph(doc);

            //Add and tag a list
            tagpdf.TagList(doc);

            //Add and tag an image
            tagpdf.TagImage(doc);           

            //Mark document as tagged:
            doc.MarkInfo.Marked = true;

            //Save the tagged PDF document:
            FileStream fs = new FileStream("TaggedPDF.pdf", FileMode.Create);
            doc.Save(fs);

            Console.WriteLine("Successfully created tagged PDF document !!!");
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }

Exploring the Tag Structure in Adobe Acrobat

The steps above generate a tagged PDF document. Open the PDF document in Adobe Acrobat and explore the document tag structure in the Tags Panel.

Access the panel by clicking on the View Show/Hide Navigation Panes Tags option.

The Tags pane appears in a sidebar on the left side of Acrobat. Click the "Tag" icon in on the far-left side of the window to show or hide this pane in the future. Clicking on any element in the tree displayed in the Tags Panel would highlight the element in the document and vice versa.

The image below depicts the tagged PDF document and its logical structure through the tags panel when viewed using Adobe Acrobat. We have two part elements. The first part element consists of two paragraphs and a list. The second part element consists of the image element:

GrapeCity Documents

For complete implementation, you can download the sample here. For further details, you can refer to the demo and documentation.

Manpreet Kaur - Senior Software Engineer

Manpreet Kaur

Senior Software Engineer
comments powered by Disqus