Public/New-VellumPdfDocument.ps1

function New-VellumPdfDocument {
    <#
    .SYNOPSIS
        Creates a new VellumPdf layout document.
    .DESCRIPTION
        Returns a live VellumPdf.Layout.Document. Pipe it through the Add-VellumPdf*
        functions and finish with Save-VellumPdfDocument, which disposes it.

        The document is IDisposable. If you do not call Save-VellumPdfDocument,
        dispose it yourself with $doc.Dispose().

        Page margins can be set uniformly with -Margin, or per-side with
        -MarginTop, -MarginRight, -MarginBottom, -MarginLeft. When any per-side
        parameter is supplied, the uniform -Margin value (if given) is used as
        the baseline for the unspecified sides; otherwise the library defaults
        are kept for unspecified sides.

        -UseObjectStreams enables PDF cross-reference object streams, which
        reduces file size for documents with many objects.
    .PARAMETER Conformance
        The conformance level for the document. Use PdfA2b, PdfA2u, or PdfA2a to
        produce an ISO 19005-2 compliant archive file, or PdfUA1 for ISO 14289-1
        (PDF/UA-1) accessibility; None (default) produces a standard PDF without
        conformance requirements. PDF/UA-1 forces a tagged document. To pass the
        preflight (Test-VellumPdfConformance, or Save-VellumPdfDocument -Validate)
        also set -Language, give the document a title with Set-VellumPdfDocumentInfo
        -Title, and give images, charts and barcodes alternate text. Both levels
        require embedded fonts: a conformant document therefore draws every base-14
        font name it uses (the document default, -Font on paragraphs, headings,
        tables, lists, text runs, headers and footers) in the metric-compatible
        Liberation font (Helvetica -> Liberation Sans, Times -> Serif, Courier ->
        Mono, with bold and italic variants), embedded once per document; layout is
        unchanged. Symbol and ZapfDingbats have no substitute: use -FontHandle
        (Register-VellumPdfFont) for those and for text outside Windows-1252. Text
        in embedded fonts is not extracted by Get-VellumPdfText (its reader decodes
        simple fonts only). A document without -Conformance keeps the plain base-14
        fonts. Hyperlinks (-LinkUri) are not tagged by
        VellumPdf, so a PDF/UA document containing them is not fully conformant.
        Note that PDF/A forbids encryption, so PDF/A is incompatible with
        Protect-VellumPdfDocument; PDF/UA-1 may be encrypted if Extract is kept.
    .PARAMETER PageWidthMm
        Custom page width in millimetres (1..5080), used together with
        -PageHeightMm instead of -PageSize. For a landscape page make the width
        the larger value.
    .PARAMETER PageHeightMm
        Custom page height in millimetres (1..5080); see -PageWidthMm.
    .PARAMETER PageSize
        The paper size for every page in the document. Accepts standard ISO and
        US names (A0-A6, Ledger, Legal, Letter). Defaults to A4.
    .PARAMETER DefaultFont
        The base-14 font name stored as the document-wide default. Content
        cmdlets that receive no explicit -Font fill the gap from this value
        rather than from the library-global Helvetica. Defaults to Helvetica.
    .PARAMETER DefaultFontSize
        The font size in points stored as the document-wide default. Content
        cmdlets that receive no explicit -FontSize fill the gap from this value.
        Must be between 1 and 1000. Defaults to 11.
    .PARAMETER Tagged
        When specified, marks the document as a tagged PDF (sets Document.Tagged
        to $true). Tagged PDFs are required for full PDF/A accessibility
        conformance and enable assistive-technology support.
    .PARAMETER Language
        A BCP 47 language tag written as the PDF /Lang entry (e.g. 'en-US').
        Relevant for tagged PDF and PDF/A accessibility. Requires VellumPdf 1.1+.
    .PARAMETER Margin
        Uniform page margin in points applied to all four sides. When any
        per-side parameter (-MarginTop, -MarginRight, -MarginBottom, -MarginLeft)
        is also supplied, this value becomes the baseline for the unspecified
        sides rather than overriding them.
    .PARAMETER MarginTop
        Top page margin in points. When supplied, overrides the -Margin baseline
        (or the library default) for the top side only.
    .PARAMETER MarginRight
        Right page margin in points. When supplied, overrides the -Margin
        baseline (or the library default) for the right side only.
    .PARAMETER MarginBottom
        Bottom page margin in points. When supplied, overrides the -Margin
        baseline (or the library default) for the bottom side only.
    .PARAMETER MarginLeft
        Left page margin in points. When supplied, overrides the -Margin
        baseline (or the library default) for the left side only.
    .PARAMETER Culture
        The culture used to turn non-string values into text: table cells, list items and pie-chart
        percentage labels (dates, numbers, and numbers read from strings). A culture name ('de-DE') or a
        [cultureinfo] (-Culture (Get-Culture) matches the console); an unknown name is an error. Default:
        the invariant culture, so the output is the same on every machine. Each of Add-VellumPdfTable,
        Add-VellumPdfList, Add-VellumPdfPieChart (and the page-document counterparts) has its own -Culture
        that overrides this default for one call. It is deliberately not derived from -Language: a language
        tag says nothing about how dates and numbers look.
    .PARAMETER InputCulture
        A culture name or [cultureinfo] used to READ numbers that are given as strings in table cells and
        list items (the document default; the table and list cmdlets have a per-call -InputCulture). With it,
        a string that consists only of a number is parsed strictly with that culture's decimal and grouping
        separators ('de-DE': '1.234,5' is 1234.5, '1.5' is not a number). Without it (the default) the
        separators are detected from the text. See Add-VellumPdfTable for the detection rules.
    .PARAMETER UseObjectStreams
        When specified, enables PDF cross-reference object streams in the output
        file. Object streams reduce file size for documents with many objects by
        compressing the cross-reference table.
    .EXAMPLE
        New-VellumPdfDocument -Conformance PdfA2b |
            Add-VellumPdfHeading -Text 'Report' |
            Add-VellumPdfParagraph -Text 'Body text.' |
            Save-VellumPdfDocument -Path ./report.pdf
    .EXAMPLE
        New-VellumPdfDocument -Margin 30
    .EXAMPLE
        New-VellumPdfDocument -Margin 30 -MarginLeft 50
    .OUTPUTS
        VellumPdf.Layout.Document
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Returns a new in-memory document object; performs no external/system state change.')]
    [CmdletBinding(DefaultParameterSetName = 'NamedPage')]
    [OutputType([VellumPdf.Layout.Document])]
    param(
        [ValidateSet('None', 'PdfA2b', 'PdfA2u', 'PdfA2a', 'PdfUA1')]
        [string]$Conformance = 'None',

        [Parameter(ParameterSetName = 'NamedPage')]
        [ValidateSet('A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'Ledger', 'Legal', 'Letter')]
        [string]$PageSize = 'A4',

        # Custom page size in millimetres (max 5080 mm = 14400 pt, the PDF default
        # user-space limit). Use instead of -PageSize; swap the two for landscape.
        [Parameter(Mandatory, ParameterSetName = 'CustomPage')]
        [ValidateRange(1.0, 5080.0)]
        [double]$PageWidthMm,

        [Parameter(Mandatory, ParameterSetName = 'CustomPage')]
        [ValidateRange(1.0, 5080.0)]
        [double]$PageHeightMm,

        [ValidateSet('Courier', 'CourierBold', 'CourierBoldOblique', 'CourierOblique',
            'Helvetica', 'HelveticaBold', 'HelveticaBoldOblique', 'HelveticaOblique',
            'Symbol', 'TimesBold', 'TimesBoldItalic', 'TimesItalic', 'TimesRoman', 'ZapfDingbats')]
        [string]$DefaultFont = 'Helvetica',

        [ValidateRange(1.0, 1000.0)]
        [double]$DefaultFontSize = 11,

        [switch]$Tagged,

        # BCP 47 language tag written as the PDF /Lang entry (e.g. 'en-US').
        # Relevant for tagged PDF and PDF/A accessibility. Requires VellumPdf 1.1+.
        [string]$Language,

        [ValidateRange(0.0, 10000.0)]
        [double]$Margin,

        [ValidateRange(0.0, 10000.0)]
        [double]$MarginTop,

        [ValidateRange(0.0, 10000.0)]
        [double]$MarginRight,

        [ValidateRange(0.0, 10000.0)]
        [double]$MarginBottom,

        [ValidateRange(0.0, 10000.0)]
        [double]$MarginLeft,

        [switch]$UseObjectStreams,

        # Culture for turning values into text (tables, lists, pie labels); a name or a [cultureinfo]. Default: invariant.
        [object]$Culture,

        # Culture used to read numbers given as strings. Default: detect the separators from the text.
        [object]$InputCulture
    )

    try {
        # Resolved first: an unknown culture name must fail before a document exists.
        $documentCulture = if ($PSBoundParameters.ContainsKey('Culture')) { ConvertTo-VellumPdfCulture -Value $Culture -CommandName 'New-VellumPdfDocument' } else { [cultureinfo]::InvariantCulture }
        $documentInputCulture = if ($PSBoundParameters.ContainsKey('InputCulture') -and $null -ne $InputCulture) { ConvertTo-VellumPdfCulture -Value $InputCulture -CommandName 'New-VellumPdfDocument' -ParameterName 'InputCulture' }
        $doc = [VellumPdf.Layout.Document]::new()
        $doc.Conformance = [VellumPdf.Document.PdfConformance]::$Conformance
        $doc.PageSize = if ($PSCmdlet.ParameterSetName -eq 'CustomPage') {
            [VellumPdf.Document.PageSize]::Mm($PageWidthMm, $PageHeightMm)
        } else {
            [VellumPdf.Document.PageSize]::$PageSize
        }
        if ($Tagged) { $doc.Tagged = $true }
        Assert-VellumPdfLanguageTag -Value $Language -CommandName 'New-VellumPdfDocument'
            if ($Language) { $doc.Language = $Language }
        if ($UseObjectStreams) { $doc.UseObjectStreams = $true }

        $style = New-VellumTextStyle -Font $DefaultFont -FontSize $DefaultFontSize -Document $doc -CommandName 'New-VellumPdfDocument'
        [void]$doc.SetDefaultFont($style)

        # VellumPdf has no getter for the default style, and a TextStyle without a
        # font falls back to the library-global Helvetica rather than this default.
        # Stash what we applied so other cmdlets can fill style gaps correctly
        # (read back via Resolve-VellumPdfDefault).
        $doc.PSObject.Properties.Add([psnoteproperty]::new(
            'PSVellumDefault', @{ Font = $DefaultFont; FontSize = $DefaultFontSize }))

        $doc.PSObject.Properties.Add([psnoteproperty]::new('PSVellumCulture', $documentCulture))
        $doc.PSObject.Properties.Add([psnoteproperty]::new('PSVellumInputCulture', $documentInputCulture))

        $hasUniform = $PSBoundParameters.ContainsKey('Margin')
        $hasPerSide = $PSBoundParameters.ContainsKey('MarginTop') -or
                      $PSBoundParameters.ContainsKey('MarginRight') -or
                      $PSBoundParameters.ContainsKey('MarginBottom') -or
                      $PSBoundParameters.ContainsKey('MarginLeft')

        if ($hasPerSide) {
            # Start from -Margin uniform value if given, else from current document margins.
            $baseTop    = if ($hasUniform) { $Margin } else { $doc.Margins.Top }
            $baseRight  = if ($hasUniform) { $Margin } else { $doc.Margins.Right }
            $baseBottom = if ($hasUniform) { $Margin } else { $doc.Margins.Bottom }
            $baseLeft   = if ($hasUniform) { $Margin } else { $doc.Margins.Left }

            $top    = if ($PSBoundParameters.ContainsKey('MarginTop'))    { $MarginTop }    else { $baseTop }
            $right  = if ($PSBoundParameters.ContainsKey('MarginRight'))  { $MarginRight }  else { $baseRight }
            $bottom = if ($PSBoundParameters.ContainsKey('MarginBottom')) { $MarginBottom } else { $baseBottom }
            $left   = if ($PSBoundParameters.ContainsKey('MarginLeft'))   { $MarginLeft }   else { $baseLeft }

            $doc.Margins = [VellumPdf.Layout.Core.EdgeInsets]::new($top, $right, $bottom, $left)
        } elseif ($hasUniform) {
            $doc.Margins = [VellumPdf.Layout.Core.EdgeInsets]::new($Margin)
        }

        return $doc
    }
    catch {
        $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'New-VellumPdfDocument'))
    }
}