PdfUa2.cs
//
// This code is part of Document Solutions for PDF .NET demos.
// Copyright (c) MESCIUS inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Pdf.Structure;
using GrapeCity.Documents.Pdf.MarkedContent;
using GCTEXT = GrapeCity.Documents.Text;

namespace DsPdfWeb.Demos.Basics
{
    // This sample shows how to create a PDF/UA-2 (accessible) compliant document.
    public class PdfUa2
    {
        public int CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument();

            // PDF/UA-2 is based on PDF 2.0:
            doc.PdfVersion = "2.0";
            // The document language must be specified for PDF/UA:
            doc.Lang = "en";
            // Mark the document as PDF/UA-2 conformant:
            doc.Metadata.PdfUa = 2;
            // PDF/UA-2 requires the "revision" year of the ISO 14289-2:2024 standard itself (not the document's date):
            doc.Metadata.PdfUaRev = "2024";
            doc.Metadata.Title = "PDF/UA-2 compliant document";
            doc.ViewerPreferences.DisplayDocTitle = true;
            // Mark the document as conforming to Tagged PDF conventions (required for PDF/UA):
            doc.MarkInfo.Marked = true;

            var fnt = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSans-Regular.ttf"));

            // PDF/UA-2 requires the structure tree to declare the namespaces it uses.
            // Add the standard PDF 2.0 structure namespace:
            var ns = new Namespace("http://iso.org/pdf2/ssn");
            doc.StructTreeRoot.Namespaces.Add(ns);

            // PDF/UA-2 requires a "Document" structure element as the root of the structure tree,
            // associated with the namespace declared above:
            var seDocument = new StructElement("Document") { NS = ns };
            doc.StructTreeRoot.Children.Add(seDocument);

            const int chapterCount = 3;
            for (int i = 0; i < chapterCount; ++i)
            {
                var page = doc.Pages.Add();
                var g = page.Graphics;
                float headingTop = 72;

                if (i == 0)
                {
                    // Structure Destinations are a very new PDF 2.0 mechanism - at the time of writing,
                    // neither the DsPdfViewer used in these demos, nor Adobe Acrobat, resolve them yet,
                    // so clicking a bookmark below may not navigate to its heading even though the
                    // destination itself is generated correctly (per the PDF/UA-2 and PDF 2.0 specs).
                    // This note is not part of the document's real content, so it is drawn using the
                    // already-embedded font (PDF/UA-2 requires every font used for rendering - including
                    // the standard 14 fonts - to be embedded) and marked as a layout artifact rather than
                    // tagged content (PDF/UA-2 requires all content to be either tagged or an artifact):
                    var tlNote = g.CreateTextLayout();
                    tlNote.DefaultFormat.Font = fnt;
                    tlNote.DefaultFormat.FontSize = 12;
                    tlNote.MarginAll = 72;
                    tlNote.MaxWidth = page.Size.Width;
                    tlNote.Append(
                        "This document's outline (bookmarks) use PDF 2.0 \"Structure Destinations\" to point directly at each " +
                        "chapter heading below, as required by PDF/UA-2. Neither the DsPdfViewer used in these demos, nor " +
                        "Adobe Acrobat, currently resolve this new destination type, so clicking a bookmark may not navigate " +
                        "to the target heading, even though the destination itself is generated correctly.");
                    tlNote.PerformLayout(true);

                    var noteRect = tlNote.ContentRectangle;
                    noteRect.Inflate(9, 9);

                    g.BeginMarkedContent(new TagArtifact { Type = TagArtifact.Types.TypeLayout });
                    g.FillRectangle(noteRect, Color.FromArgb(213, 221, 240));
                    g.DrawRectangle(noteRect, Color.FromArgb(59, 92, 170), 0.5f);
                    g.DrawTextLayout(tlNote, PointF.Empty);
                    g.EndMarkedContent();

                    headingTop = noteRect.Bottom + 24;
                }

                // Chapter heading, tagged as a top-level "H1" element under the Document root:
                var seHeading = new StructElement("H1") { DefaultPage = page };
                seDocument.Children.Add(seHeading);

                var tlHeading = g.CreateTextLayout();
                tlHeading.MarginAll = 72;
                tlHeading.MarginTop = headingTop;
                tlHeading.MaxWidth = page.Size.Width;
                tlHeading.DefaultFormat.Font = fnt;
                tlHeading.DefaultFormat.FontBold = true;
                tlHeading.DefaultFormat.FontSize = 20;
                tlHeading.Append($"Chapter {i + 1}");

                g.BeginMarkedContent(new TagMcid("H1", 0));
                g.DrawTextLayout(tlHeading, PointF.Empty);
                g.EndMarkedContent();
                seHeading.ContentItems.Add(new McidContentItemLink(0));

                // Chapter body, tagged as a "P" element under the same Document root:
                var seParagraph = new StructElement("P") { DefaultPage = page };
                seDocument.Children.Add(seParagraph);

                var tlBody = g.CreateTextLayout();
                tlBody.MarginAll = 72;
                tlBody.MarginTop = tlHeading.ContentRectangle.Bottom + 24;
                tlBody.MaxWidth = page.Size.Width;
                tlBody.DefaultFormat.Font = fnt;
                tlBody.DefaultFormat.FontSize = 12;
                tlBody.Append(Common.Util.LoremIpsum(3, 2, 5));

                g.BeginMarkedContent(new TagMcid("P", 1));
                g.DrawTextLayout(tlBody, PointF.Empty);
                g.EndMarkedContent();
                seParagraph.ContentItems.Add(new McidContentItemLink(1));

                // Add an outline entry for this chapter using a "Structure Destination" - one that points
                // at the heading's structure element rather than a page/coordinate pair, as required by
                // PDF/UA-2 (see PDF 2.0 spec, 12.3.2.3 "Structure Destinations"):
                doc.Outlines.Add(new OutlineNode($"Chapter {i + 1}", new DestinationFitBV(seHeading, null)));
            }

            doc.Save(stream);
            return doc.Pages.Count;
        }
    }
}