Private/New-VellumPdfBarcodeSet.ps1

function New-VellumPdfBarcodeSet {
    <#
    .SYNOPSIS
        Validates barcode options and builds the VellumPdf.Barcodes symbol(s).
    .DESCRIPTION
        Shared by Add-VellumPdfBarcode (flow layout) and Add-VellumPdfPageBarcode
        (page canvas). -Parameters is the caller's $PSBoundParameters, using the
        parameter names of Add-VellumPdfBarcode. Layout-specific concerns - margins,
        the page-width check, adding to a document - stay with the callers. Error
        messages start with 'Add-VellumPdfBarcode:'; callers re-prefix them.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Builds new in-memory barcode objects; performs no external/system state change.')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [hashtable]$Parameters,
        [Parameter(Mandatory)] [bool]$IsPdfA,
        [Parameter(Mandatory)] [string]$ParameterSetName
    )

    # Unpack the caller's parameters into variables (unbound ones are $null) so the
    # validation below reads exactly like it did inside the cmdlet.
    foreach ($n in @('Type', 'Content', 'Bytes', 'SplitInto', 'FileId', 'ModuleSize', 'TargetWidth', 'Foreground', 'Background', 'NoQuietZone', 'Alignment', 'AltText', 'Decorative', 'BarHeight', 'ShowText', 'TextSize', 'TextFont', 'Caption', 'FontHandle', 'ErrorCorrection', 'QrVersion', 'QrMask', 'TextEncoding', 'QrGs1', 'Gs1', 'Shape', 'AztecFormat', 'AztecErrorCorrectionPercent', 'Columns', 'Rows', 'Compact', 'ErrorCorrectionLevel', 'PreferredAspectRatio', 'RowHeight', 'MacroFileName', 'MacroSender', 'MacroAddressee', 'MacroTimestamp', 'MacroFileSize', 'MacroChecksum', 'CheckDigit', 'FullAscii', 'WideNarrowRatio', 'AddOn', 'BearerBars', 'MarginTop', 'MarginBottom', 'MarginLeft', 'MarginRight')) { Set-Variable -Name $n -Value $null }
    $Alignment = 'Left'   # the only parameter with a default; $PSBoundParameters omits defaults
    foreach ($k in $Parameters.Keys) { Set-Variable -Name $k -Value $Parameters[$k] }
    $bound = $Parameters
    $isPdfA = $IsPdfA

        $is1D = $Type -in @('Code128', 'Code39', 'Ean13', 'Ean8', 'UpcA', 'UpcE', 'Itf14')
        $isEan = $Type -in @('Ean13', 'Ean8', 'UpcA', 'UpcE')

        # --- reject options that do not belong to this symbology -------------------
        $allowed = @{
            ErrorCorrection             = @('QrCode', 'MicroQrCode')
            QrVersion                   = @('QrCode', 'MicroQrCode')
            QrMask                      = @('QrCode')
            TextEncoding                = @('QrCode')
            QrGs1                       = @('QrCode')
            Gs1                         = @('Code128', 'DataMatrix')
            Shape                       = @('DataMatrix')
            AztecFormat                 = @('Aztec')
            AztecErrorCorrectionPercent = @('Aztec')
            Columns                     = @('Pdf417')
            Rows                        = @('Pdf417')
            Compact                     = @('Pdf417')
            ErrorCorrectionLevel        = @('Pdf417')
            PreferredAspectRatio        = @('Pdf417')
            RowHeight                   = @('Pdf417')
            MacroFileName               = @('Pdf417')
            MacroSender                 = @('Pdf417')
            MacroAddressee              = @('Pdf417')
            MacroTimestamp              = @('Pdf417')
            MacroFileSize               = @('Pdf417')
            MacroChecksum               = @('Pdf417')
            CheckDigit                  = @('Code39')
            FullAscii                   = @('Code39')
            WideNarrowRatio             = @('Code39', 'Itf14')
            AddOn                       = @('Ean13', 'Ean8', 'UpcA', 'UpcE')
            BearerBars                  = @('Itf14')
            FileId                      = @('Pdf417')
        }
        foreach ($name in $allowed.Keys) {
            if ($bound.ContainsKey($name) -and $Type -notin $allowed[$name]) {
                throw (New-VellumPdfErrorRecord InvalidArgument ("Add-VellumPdfBarcode: -$name does not apply to -Type $Type (it applies to: " +
                    "$($allowed[$name] -join ', '))."))
            }
        }
        foreach ($name in 'BarHeight', 'ShowText', 'TextSize', 'TextFont') {
            if ($bound.ContainsKey($name) -and -not $is1D) {
                throw (New-VellumPdfErrorRecord InvalidArgument "Add-VellumPdfBarcode: -$name only applies to linear (1-D) barcodes, not -Type $Type.")
            }
        }
        if ($bound.ContainsKey('Bytes') -and $Type -ne 'QrCode') {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -Bytes is supported for -Type QrCode only; use -Content for other symbologies.')
        }
        if ($bound.ContainsKey('SplitInto') -and $Type -notin @('QrCode', 'Pdf417')) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -SplitInto applies to QrCode (Structured Append) and Pdf417 (Macro PDF417) only.')
        }
        if ($bound.ContainsKey('SplitInto') -and $bound.ContainsKey('Bytes')) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -SplitInto needs -Content text, not -Bytes.')
        }
        $macroKeys = 'MacroFileName', 'MacroSender', 'MacroAddressee', 'MacroTimestamp', 'MacroFileSize', 'MacroChecksum'
        if (-not $bound.ContainsKey('SplitInto') -and ($macroKeys | Where-Object { $bound.ContainsKey($_) })) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: the -Macro* options describe a Macro PDF417 set and need -SplitInto.')
        }
        if ($bound.ContainsKey('ModuleSize') -and $bound.ContainsKey('TargetWidth')) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -ModuleSize and -TargetWidth cannot be combined; choose one.')
        }
        if ($Decorative -and $AltText) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -AltText and -Decorative cannot be combined.')
        }
        $showTextValue = if ($bound.ContainsKey('ShowText')) { $ShowText } else { -not $isPdfA }
        if ($is1D -and $isPdfA -and $showTextValue) {
            throw (New-VellumPdfErrorRecord InvalidArgument ('Add-VellumPdfBarcode: the human-readable line of a linear barcode uses a non-embedded base-14 ' +
                'font, which PDF/A and PDF/UA forbid. Leave -ShowText off and use -Caption with an embedded -FontHandle instead.'))
        }
        if ($is1D -and $isPdfA -and $bound.ContainsKey('TextFont')) {
            throw (New-VellumPdfErrorRecord InvalidArgument 'Add-VellumPdfBarcode: -TextFont uses a base-14 font, which PDF/A and PDF/UA forbid; use -Caption with -FontHandle.')
        }

        # --- build the symbol(s) ----------------------------------------------------
        $barcodes = [System.Collections.Generic.List[VellumPdf.Barcodes.Barcode]]::new()
        try {
            switch ($Type) {
                'QrCode' {
                    if ($bound.ContainsKey('SplitInto')) {
                        $ec = if ($bound.ContainsKey('ErrorCorrection')) { [VellumPdf.Barcodes.QrErrorCorrection]::$ErrorCorrection } else { [VellumPdf.Barcodes.QrErrorCorrection]::M }
                        $te = if ($bound.ContainsKey('TextEncoding')) { [VellumPdf.Barcodes.QrTextEncoding]::$TextEncoding } else { [VellumPdf.Barcodes.QrTextEncoding]::Auto }
                        foreach ($b in [VellumPdf.Barcodes.QrCode]::StructuredAppend($Content, $SplitInto, $ec, $te)) { $barcodes.Add($b) }
                    }
                    elseif ($ParameterSetName -eq 'Bytes') { $barcodes.Add([VellumPdf.Barcodes.QrCode]::new($Bytes)) }
                    else { $barcodes.Add([VellumPdf.Barcodes.QrCode]::new($Content)) }
                }
                'MicroQrCode' { $barcodes.Add([VellumPdf.Barcodes.MicroQrCode]::new($Content)) }
                'DataMatrix'  { $barcodes.Add([VellumPdf.Barcodes.DataMatrixBarcode]::new($Content)) }
                'Aztec'       { $barcodes.Add([VellumPdf.Barcodes.AztecCode]::new($Content)) }
                'Pdf417' {
                    if ($bound.ContainsKey('SplitInto')) {
                        $id = if ($bound.ContainsKey('FileId')) { $FileId } else { Get-Random -Minimum 0 -Maximum 900 }
                        $macro = [VellumPdf.Barcodes.MacroPdf417Options]::new()
                        if ($bound.ContainsKey('MacroFileName')) { $macro.FileName = $MacroFileName }
                        if ($bound.ContainsKey('MacroSender')) { $macro.Sender = $MacroSender }
                        if ($bound.ContainsKey('MacroAddressee')) { $macro.Addressee = $MacroAddressee }
                        if ($bound.ContainsKey('MacroTimestamp')) { $macro.Timestamp = $MacroTimestamp }
                        if ($bound.ContainsKey('MacroFileSize')) { $macro.FileSize = $MacroFileSize }
                        if ($bound.ContainsKey('MacroChecksum')) { $macro.Checksum = $MacroChecksum }
                        foreach ($b in [VellumPdf.Barcodes.Pdf417Barcode]::MacroSet($Content, $SplitInto, $id, $macro)) { $barcodes.Add($b) }
                    }
                    else { $barcodes.Add([VellumPdf.Barcodes.Pdf417Barcode]::new($Content)) }
                }
                'Code128' { $barcodes.Add([VellumPdf.Barcodes.Code128Barcode]::new($Content)) }
                'Code39'  { $barcodes.Add([VellumPdf.Barcodes.Code39Barcode]::new($Content)) }
                'Itf14'   { $barcodes.Add([VellumPdf.Barcodes.Itf14Barcode]::new($Content)) }
                default   { $barcodes.Add([VellumPdf.Barcodes.EanBarcode]::new([VellumPdf.Barcodes.EanSymbology]::$Type, $Content)) }
            }

            $fg = if ($bound.ContainsKey('Foreground')) { ConvertTo-VellumColor $Foreground }
            $bg = if ($bound.ContainsKey('Background')) { ConvertTo-VellumColor $Background }
            foreach ($barcode in $barcodes) {
                if ($fg) { $barcode.Foreground = [VellumPdf.Layout.Core.ColorRgb]::new($fg[0], $fg[1], $fg[2]) }
                if ($bg) { $barcode.Background = [VellumPdf.Layout.Core.ColorRgb]::new($bg[0], $bg[1], $bg[2]) }
                if ($NoQuietZone) { $barcode.IncludeQuietZone = $false }
                $barcode.Alignment = [VellumPdf.Layout.Core.HorizontalAlignment]::$Alignment
                if ($bound.ContainsKey('ModuleSize')) { $barcode.ModuleSize = $ModuleSize }
                if ($bound.ContainsKey('TargetWidth')) { $barcode.TargetWidth = $TargetWidth }
                if ($AltText) { $barcode.AltText = $AltText }
                if ($Decorative) { $barcode.Decorative = $true }

                if ($is1D) {
                    $barcode.ShowText = $showTextValue
                    if ($bound.ContainsKey('BarHeight')) { $barcode.BarHeight = $BarHeight }
                    if ($bound.ContainsKey('TextSize')) { $barcode.TextSize = $TextSize }
                    if ($bound.ContainsKey('TextFont')) { $barcode.TextFont = [VellumPdf.Fonts.Standard14]::$TextFont }
                }
                switch ($Type) {
                    { $_ -in 'QrCode', 'MicroQrCode' } {
                        if ($bound.ContainsKey('ErrorCorrection')) { $barcode.ErrorCorrection = [VellumPdf.Barcodes.QrErrorCorrection]::$ErrorCorrection }
                        if ($bound.ContainsKey('QrVersion')) { $barcode.Version = $QrVersion }
                    }
                }
                if ($Type -eq 'QrCode') {
                    if ($bound.ContainsKey('QrMask')) { $barcode.Mask = $QrMask }
                    if ($bound.ContainsKey('TextEncoding')) { $barcode.TextEncoding = [VellumPdf.Barcodes.QrTextEncoding]::$TextEncoding }
                    if ($bound.ContainsKey('QrGs1')) { $barcode.Gs1 = [VellumPdf.Barcodes.QrGs1Mode]::$QrGs1 }
                }
                if (($Type -in 'Code128', 'DataMatrix') -and $Gs1) { $barcode.Gs1 = $true }
                if ($Type -eq 'DataMatrix' -and $bound.ContainsKey('Shape')) { $barcode.Shape = [VellumPdf.Barcodes.DataMatrixShape]::$Shape }
                if ($Type -eq 'Aztec') {
                    if ($bound.ContainsKey('AztecFormat')) { $barcode.Format = [VellumPdf.Barcodes.AztecFormat]::$AztecFormat }
                    if ($bound.ContainsKey('AztecErrorCorrectionPercent')) { $barcode.ErrorCorrectionPercent = $AztecErrorCorrectionPercent }
                }
                if ($Type -eq 'Pdf417') {
                    if ($bound.ContainsKey('Columns')) { $barcode.Columns = $Columns }
                    if ($bound.ContainsKey('Rows')) { $barcode.Rows = $Rows }
                    if ($Compact) { $barcode.Compact = $true }
                    if ($bound.ContainsKey('ErrorCorrectionLevel')) { $barcode.ErrorCorrectionLevel = $ErrorCorrectionLevel }
                    if ($bound.ContainsKey('PreferredAspectRatio')) { $barcode.PreferredAspectRatio = $PreferredAspectRatio }
                    if ($bound.ContainsKey('RowHeight')) { $barcode.RowHeight = $RowHeight }
                }
                if ($Type -eq 'Code39') {
                    if ($CheckDigit) { $barcode.CheckDigit = $true }
                    if ($FullAscii) { $barcode.FullAscii = $true }
                }
                if (($Type -in 'Code39', 'Itf14') -and $bound.ContainsKey('WideNarrowRatio')) { $barcode.WideNarrowRatio = $WideNarrowRatio }
                if ($isEan -and $bound.ContainsKey('AddOn')) { $barcode.AddOn = $AddOn }
                if ($Type -eq 'Itf14' -and $bound.ContainsKey('BearerBars')) { $barcode.BearerBars = [VellumPdf.Barcodes.ItfBearerBarStyle]::$BearerBars }


                # Fail now, at the call site, for content or options the symbology cannot encode.
                [void]$barcode.Measure()
            }
        }
        catch {
            if ($_.Exception.Message -like 'Add-VellumPdfBarcode:*') { throw }
            $inner = if ($_.Exception.InnerException) { $_.Exception.InnerException.Message } else { $_.Exception.Message }
            throw "Add-VellumPdfBarcode: cannot create the $Type symbol: $inner"
        }
        $barcodes.ToArray()
}