Public/Use-VellumPdfStructure.ps1

function Use-VellumPdfStructure {
    <#
    .SYNOPSIS
        Groups everything tagged inside a script block under one structure element.
    .DESCRIPTION
        In a tagged page document (PDF/UA-1, PDF/A-2a or -Tagged) every text, shape or image cmdlet adds
        its own element to the structure tree, side by side at the top. Use-VellumPdfStructure nests them:
        the elements created by the script block become children of one grouping element such as a
        section (Sect), a division (Div), an article (Art) or a part (Part), which gives assistive
        technology the outline of the page. Scopes nest, and tables and lists use the same mechanism
        for their rows and items.

        The group is added to the tree when the block ends, and only if something was tagged inside it
        (a warning says so otherwise). In an untagged document the block just runs.

        The block runs in the caller's scope with the document as $_. Its own output is discarded; the
        cmdlet writes the document to the pipeline, so the call chains.
    .PARAMETER Document
        A document from New-VellumPdfPageDocument.
    .PARAMETER Type
        The grouping element: Sect (default), Div, Art, Part, BlockQuote, Index or TOC.
    .PARAMETER ScriptBlock
        The drawing whose elements are grouped.
    .PARAMETER Language
        BCP 47 language tag for the whole group.
    .EXAMPLE
        $doc | Use-VellumPdfStructure -Type Sect -ScriptBlock {
            $_ | Add-VellumPdfText 'Results' -X 72 -Y 760 -Role H1 |
                Add-VellumPdfTextBox 'The results are shown below.' -X 72 -Y 740 -Width 400
        }
    .OUTPUTS
        VellumPdf.Document.PdfDocument (the same instance, for chaining)
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Mutates an in-memory document object only; no external/system state change.')]
    [CmdletBinding()]
    [OutputType([VellumPdf.Document.PdfDocument])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [VellumPdf.Document.PdfDocument]$Document,

        [ValidateSet('Sect', 'Div', 'Art', 'Part', 'BlockQuote', 'Index', 'TOC')]
        [string]$Type = 'Sect',

        [Parameter(Mandatory, Position = 0)]
        [scriptblock]$ScriptBlock,

        [ValidateNotNullOrEmpty()]
        [string]$Language
    )

    process {
        try {
            Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Use-VellumPdfStructure'
            Assert-VellumPdfLanguageTag -Value $Language -CommandName 'Use-VellumPdfStructure'
            if (-not $Document.Tagged) {
                $null = $Document | ForEach-Object -Process $ScriptBlock
                return $Document
            }
            $structType = Resolve-VellumPdfValidSetValue -Command $PSCmdlet.MyInvocation.MyCommand -Parameter 'Type' -Value $Type
            $group = [VellumPdf.Document.PdfStructElem]::new($structType)
            if ($Language) { $group.Language = $Language }
            Push-VellumPdfStructParent -Document $Document -Element $group
            $completed = $false
            try {
                $null = $Document | ForEach-Object -Process $ScriptBlock
                $completed = $true
            }
            finally {
                Pop-VellumPdfStructParent -Document $Document
                # Whatever was tagged before a failure stays in the tree: its marked content is already in the page.
                if ($group.Children.Count -gt 0) { Add-VellumPdfStructElement -Document $Document -Element $group }
                elseif ($completed) { Write-Warning "Use-VellumPdfStructure: nothing was tagged inside the $structType block, so no structure element was written." }
            }
            $Document
        }
        catch {
            $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Use-VellumPdfStructure'))
        }
    }
}