Src/Public/Public.ps1

function BlankLine {
<#
    .SYNOPSIS
        Initializes a new PScribo blank line object.
#>

    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline, Position = 0)]
        [System.UInt32] $Count = 1
    )
    process
    {
        Write-PScriboMessage -Message $localized.ProcessingBlankLine
        return (New-PScriboBlankLine @PSBoundParameters)
    }
}

function Document
{
<#
    .SYNOPSIS
        Initializes a new PScribo document object.
#>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments','pluginName')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## PScribo document name
        [Parameter(Mandatory, Position = 0)]
        [System.String] $Name,

        ## PScribo document DSL script block containing Section, Paragraph and/or Table etc. commands.
        [Parameter(Position = 1)]
        [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError),

        ## PScribo document Id
        [Parameter()]
        [System.String] $Id = $Name.Replace(' ','')
    )
    process
    {
        $pluginName = 'Document'
        $stopwatch = [Diagnostics.Stopwatch]::StartNew()
        $pscriboDocument = New-PScriboDocument -Name $Name -Id $Id
        $pscriboDocument.Properties['Pages']++
        $script:currentOrientation = $pscriboDocument.Options['PageOrientation']

        ## Call the Document script block
        foreach ($result in & $ScriptBlock)
        {
            ## Ensure we don't have something errant passed down the pipeline (#29)
            if ($result -is [System.Management.Automation.PSObject])
            {
                if (('Id' -in $result.PSObject.Properties.Name) -and
                    ('Type' -in $result.PSObject.Properties.Name) -and
                    ($result.Type -match '^PScribo.'))
                    {
                        [ref] $null = $pscriboDocument.Sections.Add($result)
                    }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectWarning -f $Name) -IsWarning
                }
            }
            else
            {
                Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), $Name) -IsWarning
            }
        }

        Invoke-PScriboSection

        ## Process IsSectionBreakEnd (for Word plugin)
        if ($pscriboDocument.Sections.Count -gt 0)
        {
            $previousPScriboSection = $pscriboDocument.Sections[0]
            for ($i = 0; $i -lt $pscriboDocument.Sections.Count; $i++)
            {
                $pscriboSection = $pscriboDocument.Sections[$i]
                if ($pscriboSection.Type -in 'PScribo.Section','PScribo.Paragraph','PScriboTable')
                {
                    if (($null -ne $pscriboSection.PSObject.Properties['IsSectionBreak']) -and ($pscriboSection.IsSectionBreak))
                    {
                        if (($previousPScriboSection.Type -in 'PScribo.Paragraph','PScribo.Table') -or
                            ($previousPScriboSection.Sections.Count -eq 0))
                        {
                            ## Set the last childless section, paragraph or table as the section end
                            $previousPScriboSection.IsSectionBreakEnd = $true
                        }
                        else
                        {
                            ## Set the last child section/paragraph element as the section end
                            Set-PScriboSectionBreakEnd -Section $previousPScriboSection
                        }
                    }
                    $previousPScriboSection = $pscriboSection
                }
            }
        }

        Write-PScriboMessage -Message ($localized.DocumentProcessingCompleted -f $pscriboDocument.Name)
        $stopwatch.Stop()

        if ($stopwatch.Elapsed.TotalSeconds -gt 90)
        {
            Write-PScriboMessage -Message ($localized.TotalProcessingTimeMinutes -f $stopwatch.Elapsed.TotalMinutes)
        }
        else
        {
            Write-PScriboMessage -Message ($localized.TotalProcessingTimeSeconds -f $stopwatch.Elapsed.TotalSeconds)
        }

        return $pscriboDocument
    }
}

function DocumentOption {
<#
    .SYNOPSIS
        Initializes a new PScribo global/document options/settings.

    .NOTES
        Options are reset upon each invocation.
#>

    [CmdletBinding(DefaultParameterSetName = 'Margin')]
    [Alias('GlobalOption')]
    param
    (
        ## Forces document header to be displayed in upper case.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $ForceUppercaseHeader,

        ## Forces all section headers to be displayed in upper case.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $ForceUppercaseSection,

        ## Enable section/heading numbering
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $EnableSectionNumbering,

        ## Default space replacement separator
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Separator')]
        [AllowNull()]
        [ValidateLength(0,1)]
        [System.String] $SpaceSeparator,

        ## Default page top, bottom, left and right margin (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Margin')]
        [System.UInt16] $Margin = 72,

        ## Default page top and bottom margins (pt)
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'CustomMargin')]
        [System.UInt16] $MarginTopAndBottom,

        ## Default page left and right margins (pt)
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'CustomMargin')]
        [System.UInt16] $MarginLeftAndRight,

        ## Default page size
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('A4','Legal','Letter')]
        [System.String] $PageSize = 'A4',

        ## Page orientation
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Portrait','Landscape')]
        [System.String] $Orientation = 'Portrait',

        ## Default font(s) used for new style definitions
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String[]] $DefaultFont = @('Calibri','Candara','Segoe','Segoe UI','Optima','Arial','Sans-Serif')
    )
    process
    {
        $localized.DocumentOptions | Write-PScriboMessage
        if ($SpaceSeparator)
        {
            Write-PScriboMessage -Message ($localized.DocumentOptionSpaceSeparator -f $SpaceSeparator)
            $pscriboDocument.Options['SpaceSeparator'] = $SpaceSeparator
        }

        if ($ForceUppercaseHeader)
        {
            $localized.DocumentOptionUppercaseHeadings | Write-PScriboMessage
            $pscriboDocument.Options['ForceUppercaseHeader'] = $true
            $pscriboDocument.Name = $pscriboDocument.Name.ToUpper()
        }

        if ($ForceUppercaseSection)
        {
            $localized.DocumentOptionUppercaseSections | Write-PScriboMessage
            $pscriboDocument.Options['ForceUppercaseSection'] = $true
        }

        if ($EnableSectionNumbering)
        {
            $localized.DocumentOptionSectionNumbering | Write-PScriboMessage
            $pscriboDocument.Options['EnableSectionNumbering'] = $true
        }

        if ($DefaultFont)
        {
            Write-PScriboMessage -Message ($localized.DocumentOptionDefaultFont -f ([System.String]::Join(', ', $DefaultFont)))
            $pscriboDocument.Options['DefaultFont'] = $DefaultFont
        }

        if ($PSCmdlet.ParameterSetName -eq 'CustomMargin')
        {
            if ($MarginTopAndBottom -eq 0) { $MarginTopAndBottom = 72 }
            if ($MarginLeftAndRight -eq 0) { $MarginTopAndBottom = 72 }
            $pscriboDocument.Options['MarginTop'] = ConvertTo-Mm -Point $MarginTopAndBottom
            $pscriboDocument.Options['MarginBottom'] = $pscriboDocument.Options['MarginTop']
            $pscriboDocument.Options['MarginLeft'] = ConvertTo-Mm -Point $MarginLeftAndRight
            $pscriboDocument.Options['MarginRight'] = $pscriboDocument.Options['MarginLeft']
        }
        else
        {
            $pscriboDocument.Options['MarginTop'] = ConvertTo-Mm -Point $Margin
            $pscriboDocument.Options['MarginBottom'] = $pscriboDocument.Options['MarginTop']
            $pscriboDocument.Options['MarginLeft'] = $pscriboDocument.Options['MarginTop']
            $pscriboDocument.Options['MarginRight'] = $pscriboDocument.Options['MarginTop']
        }
        Write-PScriboMessage -Message ($localized.DocumentOptionPageTopMargin -f $pscriboDocument.Options['MarginTop'])
        Write-PScriboMessage -Message ($localized.DocumentOptionPageRightMargin -f $pscriboDocument.Options['MarginRight'])
        Write-PScriboMessage -Message ($localized.DocumentOptionPageBottomMargin -f $pscriboDocument.Options['MarginBottom'])
        Write-PScriboMessage -Message ($localized.DocumentOptionPageLeftMargin -f $pscriboDocument.Options['MarginLeft'])

        ## Convert page size
        ($localized.DocumentOptionPageSize -f $PageSize) | Write-PScriboMessage
        switch ($PageSize)
        {
            'A4' {
                $pscriboDocument.Options['PageWidth'] = 210.0
                $pscriboDocument.Options['PageHeight'] = 297.0
            }
            'Legal' {
                $pscriboDocument.Options['PageWidth'] = 215.9
                $pscriboDocument.Options['PageHeight'] = 355.6
            }
            'Letter' {
                $pscriboDocument.Options['PageWidth'] = 215.9
                $pscriboDocument.Options['PageHeight'] = 279.4
            }
        }

        ## Convert page size
        ($localized.DocumentOptionPageOrientation -f $Orientation) | Write-PScriboMessage
        $pscriboDocument.Options['PageOrientation'] = $Orientation
        $script:currentOrientation = $Orientation
        ($localized.DocumentOptionPageHeight -f $pscriboDocument.Options['PageHeight']) | Write-PScriboMessage
        ($localized.DocumentOptionPageWidth -f $pscriboDocument.Options['PageWidth']) | Write-PScriboMessage
    }
}

function Export-Document {
<#
    .SYNOPSIS
        Exports a PScribo document object to one or more output formats.
#>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock','')]
    [OutputType([System.IO.FileInfo])]
    param
    (
        ## PScribo document object
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [System.Management.Automation.PSObject] $Document,

        ## Output file path
        [Parameter(Position = 2, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Path = (Get-Location -PSProvider FileSystem),

        ## PScribo document export option
        [Parameter(Position = 3, ValueFromPipelineByPropertyName)]
        [AllowNull()]
        [System.Collections.Hashtable] $Options,

        [Parameter(Position = 4, ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $PassThru
    )
    DynamicParam
    {
        ## Adds a dynamic -Format parameter that returns the available plugins
        $parameterAttribute = New-Object -TypeName 'System.Management.Automation.ParameterAttribute'
        $parameterAttribute.ParameterSetName = '__AllParameterSets'
        $parameterAttribute.Mandatory = $true
        $parameterAttribute.Position = 1

        $attributeCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Attribute]'
        $attributeCollection.Add($parameterAttribute)
        $validateSetAttribute = New-Object -TypeName 'System.Management.Automation.ValidateSetAttribute' -ArgumentList (Get-PScriboPlugin)
        $attributeCollection.Add($validateSetAttribute)

        $runtimeParameter = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameter' -ArgumentList @('Format', [System.String[]], $attributeCollection)
        $runtimeParameterDictionary = New-Object -TypeName 'System.Management.Automation.RuntimeDefinedParameterDictionary'
        $runtimeParameterDictionary.Add('Format', $runtimeParameter)
        return $runtimeParameterDictionary
    }
    begin
    {
        try { $Path = Resolve-Path $Path -ErrorAction SilentlyContinue; }
        catch { }

        if ( $(Test-CharsInPath -Path $Path -SkipCheckCharsInFileNamePart -Verbose:$false) -eq 2 )
        {
            throw $localized.IncorrectCharsInPathError
        }

        if (-not (Test-Path $Path -PathType Container))
        {
            ## Check $Path is a directory
            throw ($localized.InvalidDirectoryPathError -f $Path)
        }
    }
    process
    {
        foreach ($f in $PSBoundParameters['Format'])
        {
            Write-PScriboMessage -Message ($localized.DocumentInvokePlugin -f $f) -Plugin 'Export';

            ## Dynamically generate the output format function name
            $outputFormat = 'Out-{0}Document' -f $f
            $outputParams = @{
                Document = $Document
                Path = $Path
            }
            if ($PSBoundParameters.ContainsKey('Options'))
            {
                $outputParams['Options'] = $Options
            }

            $fileInfo = & $outputFormat @outputParams
            Write-PScriboMessage -Message ($localized.DocumentExportPluginComplete -f $f) -Plugin 'Export'

            if ($PassThru)
            {
                Write-Output -InputObject $fileInfo
            }
        }
    }
}

function Footer
{
<#
    .SYNOPSIS
        Initializes a new PScribo footer object.

    .NOTES
        Footer sections can only contain Paragraph and Table elements.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## PScribo document script block.
        [Parameter(ValueFromPipelineByPropertyName, Position = 0)]
        [ValidateNotNull()]
        [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError),

        ## Set the default page footer.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $Default,

        ## Set the First page footer.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FirstPage')]
        [System.Management.Automation.SwitchParameter] $FirstPage,

        ## PScribo inserts a default blank line between the footer and main docoument.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $NoSpace,

        ## Include default footer on the first page.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $IncludeOnFirstPage
    )
    process
    {
        Write-PScriboMessage -Message $localized.ProcessingFooterStarted

        $pscriboFooter = New-PScriboHeaderFooter -Footer

        if (-not $NoSpace)
        {
            ## Add a blank line before the footer text
            [ref] $null = $pscriboFooter.Sections.Add((New-PScriboBlankLine))
        }

        foreach ($result in & $ScriptBlock)
        {
            ## Headers/footers only support paragraphs and tables
            if ($result -is [System.Management.Automation.PSObject])
            {
                if (('Id' -in $result.PSObject.Properties.Name) -and
                    ('Type' -in $result.PSObject.Properties.Name) -and
                    ($result.Type -in 'PScribo.Paragraph','PScribo.Table','PScribo.LineBreak'))
                {
                    [ref] $null = $pscriboFooter.Sections.Add($result)
                }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectWarning -f 'Footer') -IsWarning
                }
            }
            else
            {
                Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), 'Footer') -IsWarning
            }
        }

        if ($FirstPage -or $IncludeOnFirstPage)
        {
            if ($pscriboDocument.Footer.HasFirstPageFooter -eq $true)
            {
                Write-PScriboMessage $localized.FirstPageFooterOverwriteWarning -IsWarning
            }
            $pscriboDocument.Footer.HasFirstPageFooter = $true
            $pscriboDocument.Footer.FirstPageFooter = $pscriboFooter
        }

        if ($Default)
        {
            if ($pscriboDocument.Footer.HasDefaultFooter -eq $true)
            {
                Write-PScriboMessage $localized.DefaultFooterOverwriteWarning -IsWarning
            }
            $pscriboDocument.Footer.HasDefaultFooter = $true
            $pscriboDocument.Footer.DefaultFooter = $pscriboFooter
        }

        Write-PScriboMessage -Message $localized.ProcessingFooterCompleted
    }
}

function Header
{
<#
    .SYNOPSIS
        Initializes a new PScribo header object.

    .NOTES
        Header sections can only contain Paragraph and Table elements.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## PScribo document script block.
        [Parameter(ValueFromPipelineByPropertyName, Position = 0)]
        [ValidateNotNull()]
        [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError),

        ## Set the default page header.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $Default,

        ## Set the First page header.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FirstPage')]
        [System.Management.Automation.SwitchParameter] $FirstPage,

        ## PScribo inserts a default blank line between the header and main docoument.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $NoSpace,

        ## Include default header on the first page.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $IncludeOnFirstPage
    )
    process
    {
        Write-PScriboMessage -Message $localized.ProcessingHeaderStarted

        $pscriboHeader = New-PScriboHeaderFooter -Header

        foreach ($result in & $ScriptBlock)
        {
            ## Headers/footers only support paragraphs and tables
            if ($result -is [System.Management.Automation.PSObject])
            {
                if (('Id' -in $result.PSObject.Properties.Name) -and
                    ('Type' -in $result.PSObject.Properties.Name) -and
                    ($result.Type -in 'PScribo.Paragraph','PScribo.Table','PScribo.LineBreak'))
                {
                    [ref] $null = $pscriboHeader.Sections.Add($result)
                }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectWarning -f 'Header') -IsWarning
                }
            }
            else
            {
                Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), 'Header') -IsWarning
            }
        }

        if (-not $NoSpace)
        {
            ## Add a blank line after the header text
            [ref] $null = $pscriboHeader.Sections.Add((New-PScriboBlankLine))
        }

        if ($FirstPage -or $IncludeOnFirstPage)
        {
            if ($pscriboDocument.Header.HasFirstPageHeader -eq $true)
            {
                Write-PScriboMessage $localized.FirstPageHeaderOverwriteWarning -IsWarning
            }
            $pscriboDocument.Header.HasFirstPageHeader = $true
            $pscriboDocument.Header.FirstPageHeader = $pscriboHeader
        }

        if ($Default)
        {
            if ($pscriboDocument.Header.HasDefaultHeader -eq $true)
            {
                Write-PScriboMessage $localized.DefaultHeaderOverwriteWarning -IsWarning
            }
            $pscriboDocument.Header.HasDefaultHeader = $true
            $pscriboDocument.Header.DefaultHeader = $pscriboHeader
        }

        Write-PScriboMessage -Message $localized.ProcessingHeaderCompleted
    }
}

function Image {
<#
    .SYNOPSIS
        Initializes a new PScribo Image object.
#>

    [CmdletBinding(DefaultParameterSetName = 'PathSize')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## Local file path to the image
        [Parameter(Mandatory, ParameterSetName = 'PathSize')]
        [Parameter(Mandatory, ParameterSetName = 'PathPercent')]
        [System.String] $Path,

        ## Remote image web URL path
        [Parameter(Mandatory, ParameterSetName = 'UriSize')]
        [Parameter(Mandatory, ParameterSetName = 'UriPercent')]
        [System.String] $Uri,

        ## Base64 encoded image file
        [Parameter(Mandatory, ParameterSetName = 'Base64Size')]
        [Parameter(Mandatory, ParameterSetName = 'Base64Percent')]
        [System.String] $Base64,

        ## Specifies required the image pixel width
        [Parameter(ParameterSetName = 'PathSize')]
        [Parameter(ParameterSetName = 'UriSize')]
        [Parameter(ParameterSetName = 'Base64Size')]
        [System.UInt32] $Height,

        ## Specifies required the image pixel width
        [Parameter(ParameterSetName = 'PathSize')]
        [Parameter(ParameterSetName = 'UriSize')]
        [Parameter(ParameterSetName = 'Base64Size')]
        [System.UInt32] $Width,

        ## Specifies the required scaling percentage
        [Parameter(Mandatory, ParameterSetName = 'PathPercent')]
        [Parameter(Mandatory, ParameterSetName = 'UriPercent')]
        [Parameter(Mandatory, ParameterSetName = 'Base64Percent')]
        [System.UInt32] $Percent,

        ## Image alignment
        [Parameter()]
        [ValidateSet('Left','Center','Right')]
        [System.String] $Align = 'Left',

        ## Accessibility image description
        [Parameter(Mandatory, ParameterSetName = 'Base64Size')]
        [Parameter(Mandatory, ParameterSetName = 'Base64Percent')]
        [Parameter(ParameterSetName = 'PathSize')]
        [Parameter(ParameterSetName = 'PathPercent')]
        [Parameter(ParameterSetName = 'UriSize')]
        [Parameter(ParameterSetName = 'UriPercent')]
        [System.String] $Text,

        ## Internal image Id
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String] $Id = [System.Guid]::NewGuid().ToString()
    )
    process
    {
        if ($PSBoundParameters.ContainsKey('Path'))
        {
            $Uri = Resolve-ImageUri -Path $Path
            $null = $PSBoundParameters.Remove('Path')
            $PSBoundParameters['Uri'] = $Uri
        }
        elseif ($PSBoundParameters.ContainsKey('Uri'))
        {
            $Uri = Resolve-ImageUri -Path $Uri
        }
        elseif ($PSBoundParameters.ContainsKey('Base64'))
        {
            $Uri = Resolve-ImageUri -Path 'about:blank'
        }

        if (-not ($PSBoundParameters.ContainsKey('Text')))
        {
            $Text = $Uri
        }

        $imageDisplayName = $Text
        if ($Text.Length -gt 40)
        {
            $imageDisplayName = '{0}[..]' -f $Text.Substring(0, 36)
        }

        Write-PScriboMessage -Message ($localized.ProcessingImage -f $ImageDisplayName)
        return (New-PScriboImage @PSBoundParameters)
    }
}

function Item
{
<#
    .SYNOPSIS
        Initializes a new PScribo list Item object.
#>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## List item text.
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [ValidateNotNull()]
        [System.String] $Text,

        ## List item style Name/Id reference.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Style')]
        [System.String] $Style,

        ## Override the bold style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Bold,

        ## Override the italic style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Italic,

        ## Override the underline style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Underline,

        ## Override the font name(s)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Font,

        ## Override the font size (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [AllowNull()]
        [System.UInt16] $Size = $null,

        ## Override the font color/colour
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [AllowNull()]
        [System.String] $Color = $null
    )
    begin
    {
        $psCallStack = Get-PSCallStack | Where-Object { $_.FunctionName -ne '<ScriptBlock>' }
        if ($psCallStack[1].FunctionName -ne 'List<Process>')
        {
            throw $localized.ItemRootError
        }
    }
    process
    {
        return (New-PScriboItem @PSBoundParameters)
    }
}

function LineBreak {
<#
    .SYNOPSIS
        Initializes a new PScribo line break object.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Id = [System.Guid]::NewGuid().ToString()
    )
    process
    {
        Write-PScriboMessage -Message $localized.ProcessingLineBreak
        return (New-PScriboLineBreak @PSBoundParameters)
    }
}

function List
{
<#
    .SYNOPSIS
        Initializes a new PScribo bulleted or numbered List object.
#>

    [CmdletBinding(DefaultParameterSetName = 'BulletItem')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## List item(s).
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'ItemBullet')]
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'ItemNumbered')]
        [ValidateNotNull()]
        [System.String[]] $Item,

        ## PScribo nested list/items.
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, ParameterSetName = 'ListBullet')]
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, ParameterSetName = 'ListNumbered')]
        [ValidateNotNull()]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,

        ## Display name used in verbose output when processing.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Name,

        ## List style Name/Id reference.
        [Parameter(Position = 1, ValueFromPipelineByPropertyName)]
        [System.String] $Style,

        ## Numbered list.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'ItemNumbered')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'ListNumbered')]
        [System.Management.Automation.SwitchParameter] $Numbered,

        ## Numbered list style.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ItemNumbered')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListNumbered')]
        [System.String] $NumberStyle,

        ## Bullet list style.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ItemBullet')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListBullet')]
        [ValidateSet('Circle', 'Dash', 'Disc', 'Square')]
        [System.String] $BulletStyle = 'Disc'
    )
    begin
    {
        $psCallStack = Get-PSCallStack | Where-Object { $_.FunctionName -ne '<ScriptBlock>' }
        if ($psCallStack[1].FunctionName -notin 'List<Process>','Document<Process>','Section<Process>')
        {
            Write-Warning $psCallStack[1].FunctionName
            throw $localized.ListRootError
        }
    }
    process
    {
        $null = $PSBoundParameters.Remove('ScriptBlock')
        $null = $PSBoundParameters.Remove('Item')

        $pscriboList = New-PScriboList @PSBoundParameters

        if ($PSCmdlet.ParameterSetName -in 'ItemBullet','ItemNumbered')
        {
            foreach ($listItem in $Item)
            {
                $pscriboListItem = New-PScriboItem -Text $listItem
                [ref] $null = $pscriboList.Items.Add($pscriboListItem)
            }
        }
        elseif ($PSCmdlet.ParameterSetName -in 'ListBullet','ListNumbered')
        {
            foreach ($result in & $ScriptBlock)
            {
                ## Ensure we don't have something errant passed down the pipeline (#29)
                if ($result -is [System.Management.Automation.PSObject])
                {
                    if (('Id' -in $result.PSObject.Properties.Name) -and
                        ('Type' -in $result.PSObject.Properties.Name) -and
                        ($result.Type -match '^PScribo.'))
                    {
                        [ref] $null = $pscriboList.Items.Add($result)
                    }
                    else
                    {
                        Write-PScriboMessage -Message ($localized.UnexpectedObjectWarning -f $Name) -IsWarning
                    }
                }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), $Name) -IsWarning
                }
            }
        }

        ## Only process list levels on the root list object
        if ($psCallStack[1].FunctionName -in 'Document<Process>','Section<Process>')
        {
            Write-PScriboMessage -Message ($localized.ProcessingList -f $pscriboList.Name)
            $hasItem = $false
            $itemNumber = 0

            foreach ($listItem in $pscriboList.Items)
            {
                if ($listItem.Type -eq 'PScribo.Item')
                {
                    $itemNumber++
                    $listItem.Level = 1
                    $listItem.Index = $itemNumber
                    $listItem.Number = $itemNumber.ToString()

                    $hasItem = $true
                }
                elseif ($listItem.Type -eq 'PScribo.List')
                {
                    if ($hasItem)
                    {
                        $pscriboList.IsMultiLevel = $true
                        Invoke-PScriboListLevel -List $listItem -Number $itemNumber.ToString()
                    }
                    else
                    {
                        Write-PScriboMessage -Message $localized.NoPriorListItemWarning -IsWarning
                    }
                }
            }

            ## Store lists for Word numbering.xml
            $pscriboDocument.Properties['Lists']++
            $pscriboList.Number = $pscriboDocument.Properties['Lists']
            [ref] $null = $pscriboDocument.Lists.Add($pscriboList)

            ## Return list reference
            $pscriboListReference = New-PScriboListReference -Name $pscriboList.Name -Number $pscriboList.Number
            return $pscriboListReference
        }
        else
        {
            return $pscriboList
        }
    }
}

function NumberStyle
{
<#
    .SYNOPSIS
        Defines a new PScribo numbered list formatting style.

    .DESCRIPTION
        Creates a number list formatting style that can be applied to the PScribo 'List' keyword.

    .NOTES
        Not all plugins support all options.
#>

    [CmdletBinding(DefaultParameterSetName = 'Predefined')]
    param
    (
        ## Table Style name/id
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Predefined')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Custom')]
        [ValidateNotNullOrEmpty()]
        [Alias('Name')]
        [System.String] $Id,

        ## NOTE: Only supported in Text, Html and Word output.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [ValidateSet('Number','Letter','Roman')]
        [System.String] $Format,

        ## Custom number 'XYZ-###' NOTE: Only supported in Text and Word output.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Custom')]
        [System.String] $Custom,

        ## Number format suffix, e.g. '.' or ')'. NOTE: Only supported in text and Word output
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [ValidateLength(1, 1)]
        [System.String] $Suffix = '.',

        ## Only applicable to 'Letter' and 'Roman' formats
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [System.Management.Automation.SwitchParameter] $Uppercase,

        ## Set as default table style. NOTE: Cannot set custom styles as default as they're not supported by all plugins.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [System.Management.Automation.SwitchParameter] $Default,

        ## Number alignment.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Custom')]
        [ValidateSet('Left', 'Right')]
        [System.String] $Align = 'Right',

        ## Override the default Word indentation level.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Custom')]
        [System.Int32] $Indent,

        ## Override the default Word hanging indentation level.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Predefined')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Custom')]
        [System.Int32] $Hanging
    )
    process
    {
        Write-PScriboMessage -Message ($localized.ProcessingNumberStyle -f $Id)
        Add-PScriboNumberStyle @PSBoundParameters
    }
}

function PageBreak {
<#
    .SYNOPSIS
        Creates a PScribo page break object.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Id = [System.Guid]::NewGuid().ToString()
    )
    process
    {
        Write-PScriboMessage -Message $localized.ProcessingPageBreak
        return (New-PScriboPageBreak -Id $Id)
    }
}

function Paragraph {
<#
    .SYNOPSIS
        Initializes a new PScribo paragraph object.
#>

    [CmdletBinding(DefaultParameterSetName = 'Legacy')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## PScribo paragraph run script block.
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ParagraphRun')]
        [ValidateNotNull()]
        [System.Management.Automation.ScriptBlock] $ScriptBlock,

        ## Paragraph Id and Xml element name
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Legacy')]
        [ValidateNotNullOrEmpty()]
        [System.String] $Name,

        ## Paragraph text. If empty $Name/Id will be used.
        [Parameter(ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Legacy')]
        [AllowNull()]
        [System.String] $Text = $null,

        ## Output value override, i.e. for Xml elements. If empty $Text will be used.
        [Parameter(ValueFromPipelineByPropertyName, Position = 2, ParameterSetName = 'Legacy')]
        [AllowNull()]
        [System.String] $Value = $null,

        ## Paragraph style Name/Id reference.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ParagraphRun')]
        [AllowNull()]
        [System.String] $Style = $null,

        ## DEPRECATED - Use paragraph runs (Text)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [System.Management.Automation.SwitchParameter] $NoNewLine,

        ## Override the bold style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [System.Management.Automation.SwitchParameter] $Bold,

        ## Override the italic style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [System.Management.Automation.SwitchParameter] $Italic,

        ## Override the underline style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [System.Management.Automation.SwitchParameter] $Underline,

        ## Override the font name(s)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Font,

        ## Override the font size (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [AllowNull()]
        [System.UInt16] $Size = $null,

        ## Override the font color/colour
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [AllowNull()]
        [System.String] $Color = $null,

        ## Tab indent
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Legacy')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ParagraphRun')]
        [ValidateRange(0,10)]
        [System.Int32] $Tabs = 0
    )
    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Legacy')
        {
            $paragraphId = $Name
            $paragraphDisplayName = $Name
            if ($Name.Length -gt 40)
            {
                $paragraphId = $Name.Substring(0,40)
                $paragraphDisplayName = '{0}[..]' -f$Name.Substring(0,36)
            }
            Write-PScriboMessage -Message ($localized.ProcessingParagraph -f $paragraphDisplayName)

            if ($NoNewLine -eq $true)
            {
                Write-PScriboMessage -Message $localized.NoNewLineDeprecatedWarning -IsWarning
            }
            if ($PSBoundParameters.ContainsKey('Value'))
            {
                Write-PScriboMessage -Message $localized.ValueParameterRemovedWarning -IsWarning
            }

            ## Create paragraph
            $newPScriboParagraphParams = @{
                Id          = $paragraphId
                ScriptBlock = { }
                Tabs        = $Tabs
            }
            if ($PSBoundParameters.ContainsKey('Style'))
            {
                $newPScriboParagraphParams['Style'] = $Style
            }
            $paragraph = New-PScriboParagraph @newPScriboParagraphParams

            ## Create a single run
            $paragraphRunText = $Name
            if ($PSBoundParameters.ContainsKey('Text'))
            {
                $paragraphRunText = $Text
            }
            $null = $PSBoundParameters.Remove('Name')
            $null = $PSBoundParameters.Remove('Text')
            $null = $PSBoundParameters.Remove('Value')
            $null = $PSBoundParameters.Remove('Style')
            $null = $PSBoundParameters.Remove('Tabs')
            $null = $PSBoundParameters.Remove('NoNewLine')
            $paragraphRun = New-PScriboParagraphRun -Text $paragraphRunText @PSBoundParameters
            $paragraphRun.IsParagraphRunEnd = $true
            $null = $paragraph.Sections.Add($paragraphRun)

            return $paragraph
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'ParagraphRun')
        {
            Write-PScriboMessage -Message ($localized.ProcessingParagraphRunsStarted)
            $paragraph = New-PScriboParagraph @PSBoundParameters

            foreach ($result in & $ScriptBlock)
            {
                ## Ensure we don't have something errant passed down the pipeline
                if ($result -is [System.Management.Automation.PSObject])
                {
                    if (('Type' -in $result.PSObject.Properties.Name) -and
                        ($result.Type -eq 'PScribo.ParagraphRun'))
                    {
                        [ref] $null = $paragraph.Sections.Add($result)
                    }
                    elseif (('Type' -in $result.PSObject.Properties.Name) -and
                        ($result.Type -match '^PScribo\.'))
                    {
                        Write-PScriboMessage -Message ($localized.UnsupportedPScriboTypeWarning -f $result.Type, 'Paragraph') -IsWarning
                    }
                    else
                    {
                        Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), 'Paragraph') -IsWarning
                    }
                }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), 'Paragraph') -IsWarning
                }
            }

            if ($paragraph.Sections.Count -gt 0)
            {
                ## Set the paragraph Id to the text of the first run
                if ($paragraph.Sections[0].Text.Length -gt 40)
                {
                    $paragraphDisplayName = '{0}[..]' -f $paragraph.Sections[0].Text.Substring(0,36)
                }
                else
                {
                    $paragraphDisplayName = $paragraph.Sections[0].Text
                }
                $paragraph.Id = $paragraphDisplayName
                $paragraph.Sections[-1].IsParagraphRunEnd = $true
            }

            Write-PScriboMessage -Message ($localized.ProcessingParagraphRunsCompleted)
            return $paragraph
        }
        Write-PScriboMessage -Message ($localized.ProcessingParagraph -f $paragraphDisplayName)

        return (New-PScriboParagraph @PSBoundParameters)
    }
}

function Section
{
<#
    .SYNOPSIS
        Initializes a new PScribo section object.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## PScribo section heading/name.
        [Parameter(Mandatory, Position = 0)]
        [System.String] $Name,

        ## PScribo document script block.
        [Parameter(Position = 1)]
        [ValidateNotNull()]
        [System.Management.Automation.ScriptBlock] $ScriptBlock = $(throw $localized.NoScriptBlockProvidedError),

        ## PScribo style applied to document section.
        [Parameter(ValueFromPipelineByPropertyName)]
        [AllowNull()]
        [System.String] $Style = $null,

        ## Section is excluded from TOC/section numbering.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $ExcludeFromTOC,

        ## Tab indent
        [Parameter()]
        [ValidateRange(0,10)]
        [System.Int32] $Tabs = 0,

        ## Section orientation
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Portrait','Landscape')]
        [System.String] $Orientation
    )
    process
    {
        Write-PScriboMessage -Message ($localized.ProcessingSectionStarted -f $Name)

        $newPScriboSectionParams = @{
            Name        = $Name
            Style       = $Style
            IsExcluded  = $ExcludeFromTOC
            Tabs        = $Tabs
        }
        if ($PSBoundParameters.ContainsKey('Orientation'))
        {
            $newPScriboSectionParams['Orientation'] = $Orientation
        }
        $pscriboSection = New-PScriboSection @newPScriboSectionParams

        foreach ($result in & $ScriptBlock)
        {
            ## Ensure we don't have something errant passed down the pipeline (#29)
            if ($result -is [System.Management.Automation.PSObject])
            {
                if (('Id' -in $result.PSObject.Properties.Name) -and
                    ('Type' -in $result.PSObject.Properties.Name) -and
                    ($result.Type -match '^PScribo.'))
                {
                    [ref] $null = $pscriboSection.Sections.Add($result)
                }
                else
                {
                    Write-PScriboMessage -Message ($localized.UnexpectedObjectWarning -f $Name) -IsWarning
                }
            }
            else
            {
                Write-PScriboMessage -Message ($localized.UnexpectedObjectTypeWarning -f $result.GetType(), $Name) -IsWarning
            }
        }
        Write-PScriboMessage -Message ($localized.ProcessingSectionCompleted -f $Name)
        return $pscriboSection;
    }
}

function Set-Style
{
<#
    .SYNOPSIS
        Sets the style for an individual table row or cell.
#>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidMultipleTypeAttributes','')]
    [OutputType([System.Object])]
    param
    (
        ## PSCustomObject to apply the style to
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Object[]] [Ref] $InputObject,

        ## PScribo style Id to apply
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $Style,

        ## Property name(s) to apply the selected style to. Leave blank to apply the style to the entire row.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Property = '',

        ## Passes the modified object back to the pipeline
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $PassThru
    )
    begin
    {
        if (-not (Test-PScriboStyle -Name $Style))
        {
            Write-Error ($localized.UndefinedStyleError -f $Style)
            return
        }
    }
    process
    {
        foreach ($object in $InputObject)
        {
            foreach ($p in $Property)
            {
                ## If $Property not set, __Style will apply to the whole row.
                $propertyName = '{0}__Style' -f $p
                $object | Add-Member -MemberType NoteProperty -Name $propertyName -Value $Style -Force
            }
        }
        if ($PassThru)
        {
            return $object
        }
    }
}


function Style
{
<#
    .SYNOPSIS
        Defines a new PScribo formatting style.

    .DESCRIPTION
        Creates a standard format formatting style that can be applied
        to PScribo document keywords, e.g. a combination of font style, font
        weight and font size.

    .NOTES
        Not all plugins support all options.
#>

    [CmdletBinding()]
    param
    (
        ## Style name
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Name,

        ## Font size (pt)
        [Parameter(ValueFromPipelineByPropertyName, Position = 1)]
        [System.UInt16] $Size = 11,

        ## Font color/colour
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Colour')]
        [ValidateNotNullOrEmpty()]
        [System.String] $Color = '000',

        ## Background color/colour
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('BackgroundColour')]
        [ValidateNotNullOrEmpty()]
        [System.String] $BackgroundColor,

        ## Bold typeface
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Bold,

        ## Italic typeface
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Italic,

        ## Underline typeface
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Underline,

        ## Text alignment
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Left','Center','Right','Justify')]
        [System.String] $Align = 'Left',

        ## Set as default style
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Default,

        ## Style id
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Id = $Name -Replace(' ',''),

        ## Font name (array of names for HTML output)
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String[]] $Font,

        ## Html CSS class id - to override Style.Id in HTML output.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $ClassId = $Id,

        ## Hide style from UI (Word)
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Hide')]
        [System.Management.Automation.SwitchParameter] $Hidden
    )
    process
    {
        Write-PScriboMessage -Message ($localized.ProcessingStyle -f $Id)
        Add-PScriboStyle @PSBoundParameters
    }
}

function Table
{
<#
    .SYNOPSIS
        Defines a new PScribo document table.

    .PARAMETER Name

    .PARAMETER InputObject

    .PARAMETER Hashtable

    .PARAMETER Columns

    .PARAMETER ColumnWidths

    .PARAMETER Headers

    .PARAMETER Style

    .PARAMETER Width

    .PARAMETER Tabs

    .PARAMETER List

    .PARAMETER Key

    .PARAMETER Caption

    .EXAMPLE
        Table -Name 'Table 1' -InputObject $(Get-Service) -Columns 'Name','DisplayName','Status' -ColumnWidths 40,20,40
#>

    [CmdletBinding(DefaultParameterSetName = 'InputObject')]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        ## Table name/Id
        [Parameter(ValueFromPipelineByPropertyName, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Name = ([System.Guid]::NewGuid().ToString()),

        # Array of objects
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObjectList')]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObjectListKey')]
        [Alias('CustomObject','Object')]
        [ValidateNotNullOrEmpty()]
        [System.Object[]] $InputObject,

        # Array of Hashtables
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Hashtable')]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'HashtableList')]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'HashtableListKey')]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Specialized.OrderedDictionary[]] $Hashtable,

        # Array of Hashtable key names or object/PSCustomObject property names to include, in display order.
        # If not supplied then all Hashtable keys or all object properties will be used.
        [Parameter(ValueFromPipelineByPropertyName, Position = 1)]
        [Alias('Properties')]
        [AllowNull()]
        [System.String[]] $Columns = $null,

        ## Column widths as percentages. Total should not exceed 100.
        [Parameter(ValueFromPipelineByPropertyName)]
        [AllowNull()]
        [System.UInt16[]] $ColumnWidths,

        # Array of custom table header strings in display order.
        [Parameter(ValueFromPipelineByPropertyName, Position = 2)]
        [AllowNull()]
        [System.String[]] $Headers = $null,

        ## Table style
        [Parameter(ValueFromPipelineByPropertyName, Position = 3)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Style = $pscriboDocument.DefaultTableStyle,

        ## Table width (%), 0 = Autofit
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateRange(0,100)]
        [System.UInt16] $Width = 100,

        ## Indent table
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateRange(0,10)]
        [System.UInt16] $Tabs,

        ## List view
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'InputObjectList')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'HashtableList')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'InputObjectListKey')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'HashtableListKey')]
        [System.Management.Automation.SwitchParameter] $List,

        ## Combine list view based upon the specified key property name
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'InputObjectListKey')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'HashtableListKey')]
        [System.String] $Key,

        ## Hide the key name in the table output. Note: Key name will always be displayed in text output.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'InputObjectListKey')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'HashtableListKey')]
        [System.Management.Automation.SwitchParameter] $HideKey,

        ## Table caption
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Caption
    )
    begin
    {
        Write-Debug ('Using parameter set "{0}".' -f $PSCmdlet.ParameterSetName)
        [System.Collections.ArrayList] $rows = New-Object -TypeName System.Collections.ArrayList
        Write-PScriboMessage -Message ($localized.ProcessingTable -f $Name)

        if ($Headers -and (-not $Columns))
        {
            Write-PScriboMessage -Message $localized.TableHeadersWithNoColumnsWarning -IsWarning
            $Headers = $Columns
        }
        elseif (($null -ne $Columns) -and ($null -ne $Headers))
        {
            ## Check the number of -Headers matches the number of -Properties
            if ($Headers.Count -ne $Columns.Count)
            {
                Write-PScriboMessage -Message $localized.TableHeadersCountMismatchWarning -IsWarning
                $Headers = $Columns
            }
        }

        if ($ColumnWidths)
        {
            $columnWidthsSum = $ColumnWidths | Measure-Object -Sum | Select-Object -ExpandProperty Sum
            if ($columnWidthsSum -ne 100)
            {
                Write-PScriboMessage -Message ($localized.TableColumnWidthSumWarning -f $columnWidthsSum) -IsWarning
                $ColumnWidths = $null
            }
            elseif ($List -and (-not $Key) -and ($ColumnWidths.Count -ne 2))
            {
                Write-PScriboMessage -Message $localized.ListTableColumnCountWarning -IsWarning
                $ColumnWidths = $null
            }
            elseif (($PSCmdlet.ParameterSetName -eq 'Hashtable') -and (-not $List) -and ($Hashtable[0].Keys.Count -ne $ColumnWidths.Count))
            {
                Write-PScriboMessage -Message $localized.TableColumnWidthMismatchWarning -IsWarning
                $ColumnWidths = $null
            }
            elseif (($PSCmdlet.ParameterSetName -eq 'HashtableListKey') -and (($Hashtable.Count + 1) -ne $ColumnWidths.Count))
            {
                Write-PScriboMessage -Message $localized.TableColumnWidthMismatchWarning -IsWarning
                $ColumnWidths = $null
            }
            elseif (($PSCmdlet.ParameterSetName -eq 'InputObject') -and (-not $List))
            {
                ## Columns might not have been passed and there is no object in the pipeline here, so check $Columns is an array.
                if (($Columns -is [System.Object[]]) -and ($Columns.Count -ne $ColumnWidths.Count))
                {
                    Write-PScriboMessage -Message $localized.TableColumnWidthMismatchWarning -IsWarning
                    $ColumnWidths = $null
                }
            }
        }
    }
    process
    {
        if ($null -eq $Columns) {
            ## Use all available properties
            if ($PSCmdlet.ParameterSetName -in 'Hashtable','HashtableList','HashtableListKey')
            {
                $Columns = $Hashtable | Select-Object -First 1 -ExpandProperty Keys | Where-Object { $_ -notlike '*__Style' }
            }
            elseif ($PSCmdlet.ParameterSetName -in 'InputObject','InputObjectList','InputObjectListKey')
            {
                ## Pipeline objects are not available in the begin scriptblock
                $object = $InputObject | Select-Object -First 1
                if ($object -is [System.Management.Automation.PSCustomObject])
                {
                    $Columns = $object.PSObject.Properties | Where-Object Name -notlike '*__Style' | Select-Object -ExpandProperty Name
                }
                elseif ($object -is [System.Collections.Specialized.OrderedDictionary])
                {
                    $Columns = $object.Keys | Where-Object { $_ -notlike '*__Style' }
                }
                else
                {
                    $Columns = Get-Member -InputObject $object -MemberType Properties | Where-Object Name -notlike '*__Style' | Select-Object -ExpandProperty Name
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -in 'Hashtable','HashtableList','HashtableListKey')
        {
            foreach ($nestedHashtable in $Hashtable)
            {
                $customObject = New-PScriboTableRow -Hashtable $nestedHashtable
                [ref] $null = $rows.Add($customObject)
            } #end foreach nested hashtable entry
        }
        elseif ($PSCmdlet.ParameterSetName -in 'InputObject','InputObjectList','InputObjectListKey')
        {
            foreach ($object in $InputObject)
            {
                if ($object -is [System.Collections.Specialized.OrderedDictionary])
                {
                    $customObject = New-PScriboTableRow -Hashtable $object
                }
                else
                {
                    $customObject = New-PScriboTableRow -InputObject $object -Properties $Columns -Headers $Headers
                }
                [ref] $null = $rows.Add($customObject)
            }
        }
    }
    end
    {
        if (($PSBoundParameters.ContainsKey('Key')) -and ($PSBoundParameters.ContainsKey('Headers')))
        {
            ## Update the key name to match the rewritten property display name
            $Key = $Headers[$Columns.IndexOf($Key)]
        }

        ## Update the column names as the objects have been have been rewritten with their display names (Headers)
        if ($Headers)
        {
            $Columns = $Headers
        }

        $newPScriboTableParams = @{
            Name         = $Name
            Columns      = $Columns
            ColumnWidths = $ColumnWidths
            Rows         = $rows
            List         = $List
            Style        = $Style
            Width        = $Width
            Tabs         = $Tabs
        }

        if ($PSBoundParameters.ContainsKey('Key'))
        {
            $newPScriboTableParams['ListKey'] = $Key
            $newPScriboTableParams['DisplayListKey'] = -not $HideKey
        }

        if ($PSBoundParameters.ContainsKey('Caption'))
        {
            $newPScriboTableParams['Caption'] = $Caption
        }

        return (New-PScriboTable @newPScriboTableParams)
    }
}

function TableStyle
{
<#
    .SYNOPSIS
        Defines a new PScribo table formatting style.

    .DESCRIPTION
        Creates a standard table formatting style that can be applied
        to the PScribo table keyword, e.g. a combination of header and
        row styles and borders.

    .NOTES
        Not all plugins support all options.
#>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param
    (
        ## Table Style name/id
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Padding')]
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Default')]
        [ValidateNotNullOrEmpty()]
        [Alias('Name')]
        [System.String] $Id,

        ## Header Row Style Id
        [Parameter(ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Default')]
        [ValidateNotNullOrEmpty()]
        [System.String] $HeaderStyle = 'Default',

        ## Row Style Id
        [Parameter(ValueFromPipelineByPropertyName, Position = 2, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 2, ParameterSetName = 'Default')]
        [ValidateNotNullOrEmpty()]
        [System.String] $RowStyle = 'Default',

        ## Alternating Row Style Id
        [Parameter(ValueFromPipelineByPropertyName, Position = 3, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, Position = 3, ParameterSetName = 'Default')]
        [AllowNull()]
        [Alias('AlternatingRowStyle')]
        [System.String] $AlternateRowStyle = $RowStyle,

        ## Table border size/width (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [AllowNull()]
        [System.Single] $BorderWidth = 0,

        ## Table border colour
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [ValidateNotNullOrEmpty()]
        [Alias('BorderColour')]
        [System.String] $BorderColor = '000',

        ## Table cell padding (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [ValidateNotNull()]
        [System.Single] $Padding,

        ## Table cell top padding (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [ValidateNotNull()]
        [System.Single] $PaddingTop = 1.0,

        ## Table cell left padding (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [ValidateNotNull()]
        [System.Single] $PaddingLeft = 4.0,

        ## Table cell bottom padding (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [ValidateNotNull()]
        [System.Single] $PaddingBottom = 0.0,

        ## Table cell right padding (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [ValidateNotNull()]
        [System.Single] $PaddingRight = 4.0,

        ## Table alignment
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [ValidateSet('Left','Center','Right')]
        [System.String] $Align = 'Left',

        ## Table caption prefix
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.String] $CaptionPrefix = 'Table',

        ## Table caption prefix
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.String] $CaptionStyle = 'Caption',

        ## Table caption display location.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [ValidateSet('Above', 'Below')]
        [System.String] $CaptionLocation = 'Below',

        ## Set as default table style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Padding')]
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $Default
    )
    process
    {
        if ($PSBoundParameters.ContainsKey('Padding'))
        {
            $PSBoundParameters['PaddingTop'] = $Padding
            $PSBoundParameters['PaddingLeft'] = $Padding
            $PSBoundParameters['PaddingBottom'] = $Padding
            $PSBoundParameters['PaddingRight'] = $Padding
            $null = $PSBoundParameters.Remove('Padding')
        }
        Write-PScriboMessage -Message ($localized.ProcessingTableStyle -f $Id)
        Add-PScriboTableStyle @PSBoundParameters
    }
}

function Text
{
<#
    .SYNOPSIS
        Paragraphs can be made up of text "blocks" (runs) concatenated together to
        form a body of text. Separating the text into runs permits alteration
        of styling element within a single paragraph.

    .NOTES
        The "Text" block command can only be used within a Paragraph -ScriptBlock { }
#>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [AllowEmptyString()]
        [System.String] $Text,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Style')]
        [System.String] $Style,

        ## No space applied between this text block and the next
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $NoSpace,

        ## Override the bold style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Bold,

        ## Override the italic style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Italic,

        ## Override the underline style
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [System.Management.Automation.SwitchParameter] $Underline,

        ## Override the font name(s)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [ValidateNotNullOrEmpty()]
        [System.String[]] $Font,

        ## Override the font size (pt)
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [AllowNull()]
        [System.UInt16] $Size = $null,

        ## Override the font color/colour
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Inline')]
        [AllowNull()]
        [System.String] $Color = $null
    )
    begin
    {
        $psCallStack = Get-PSCallStack | Where-Object { $_.FunctionName -ne '<ScriptBlock>' }
        if ($psCallStack[1].FunctionName -ne 'Paragraph<Process>')
        {
            throw $localized.ParagraphRunRootError
        }
    }
    process
    {
        $paragraphRunDisplayName = $Text
        if ($Text.Length -gt 40)
        {
            $paragraphRunDisplayName = '{0}[..]' -f $Text.Substring(0,36)
        }
        Write-PScriboMessage -Message ($localized.ProcessingParagraphRun -f $paragraphRunDisplayName)
        return (New-PScriboParagraphRun @PSBoundParameters)
    }
}

function TOC {
<#
    .SYNOPSIS
        Initializes a new PScribo Table of Contents (TOC) object.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter(ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Name = 'Contents',

        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.String] $ClassId = 'TOC'
    )
    process
    {
        Write-PScriboMessage -Message ($localized.ProcessingTOC -f $Name)
        return (New-PScriboTOC @PSBoundParameters)
    }
}

function Write-PScriboMessage {
<#
    .SYNOPSIS
        Writes PScribo-formatted message to the verbose, warning or debug streams. Output is
        prefixed with the time and PScribo plugin name.
#>

    [CmdletBinding(DefaultParameterSetName = 'Verbose')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter','IsWarning')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter','IsDebug')]
    param
    (
        ## Message to send to the stream
        [Parameter(Position = 0, ValueFromPipeline, ParameterSetName = 'Verbose')]
        [Parameter(Position = 0, ValueFromPipeline, ParameterSetName = 'Warning')]
        [Parameter(Position = 0, ValueFromPipeline, ParameterSetName = 'Debug')]
        [ValidateNotNullOrEmpty()]
        [System.String] $Message,

        ## PScribo plugin name
        [Parameter(Position = 1, ValueFromPipelineByPropertyName)]
        [System.String] $Plugin,

        ## Redirect message to the Warning stream
        [Parameter(ParameterSetName = 'Warning')]
        [System.Management.Automation.SwitchParameter] $IsWarning,

        ## Redirect message to the Debug stream
        [Parameter(ParameterSetName = 'Debug')]
        [System.Management.Automation.SwitchParameter] $IsDebug,

        ## Padding/indent section level
        [Parameter(ValueFromPipeline, ParameterSetName = 'Verbose')]
        [Parameter(ValueFromPipeline, ParameterSetName = 'Warning')]
        [Parameter(ValueFromPipeline, ParameterSetName = 'Debug')]
        [ValidateNotNullOrEmpty()]
        [System.UInt16] $Indent
    )
    process
    {
        if ([System.String]::IsNullOrEmpty($Plugin))
        {
            ## Attempt to resolve the plugin name from the parent scope
            if (Test-Path -Path Variable:\pluginName)
            {
                $Plugin = Get-Variable -Name pluginName -ValueOnly
            }
            else
            {
                $Plugin = 'Unknown'
            }
        }
        ## Center plugin name
        $pluginPaddingSize = [System.Math]::Floor((10 - $Plugin.Length) / 2)
        $pluginPaddingString = ''.PadRight($pluginPaddingSize)
        $Plugin = '{0}{1}' -f $pluginPaddingString, $Plugin
        $Plugin = $Plugin.PadRight(10)
        $date = Get-Date
        $sectionLevelPadding = ''.PadRight($Indent)
        $formattedMessage = '[ {0} ] [{1}] - {2}{3}' -f $date.ToString('HH:mm:ss:fff'), $Plugin, $sectionLevelPadding, $Message
        switch ($PSCmdlet.ParameterSetName)
        {
            'Warning' { Write-Warning -Message $formattedMessage }
            'Debug' { Write-Debug -Message $formattedMessage }
            Default { Write-Verbose -Message $formattedMessage }
        }
    }
}


# SIG # Begin signature block
# MIIuuwYJKoZIhvcNAQcCoIIurDCCLqgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBx/cTkbYPqv84Z
# LaHdvstlcyV4HZWYtLI3/bGu+q2AnKCCE6QwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggdYMIIFQKADAgECAhAIfHT3o/FeY5ksO94AUhTmMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjMxMDE4MDAwMDAwWhcNMjYxMjE2MjM1OTU5WjBgMQsw
# CQYDVQQGEwJHQjEPMA0GA1UEBxMGTG9uZG9uMR8wHQYDVQQKExZWaXJ0dWFsIEVu
# Z2luZSBMaW1pdGVkMR8wHQYDVQQDExZWaXJ0dWFsIEVuZ2luZSBMaW1pdGVkMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtyhrsCMi6pgLcX5sWY7I09dO
# WKweRHfDwW5AN6ffgLCYO9dqWWxvqu95FqnNVRyt1VNzEl3TevKVhRE0GGdirei3
# VqnFFjLDwD2jHhGY8qoSYyfffj/WYq2DkvNI62C3gUwSeP3FeqKRalc2c3V2v4jh
# yEYhrgG3nfnWQ/Oq2xzuiCqHy1E4U+IKKDtrXls4JX2Z4J/uAHZIAyKfrcTRQOhZ
# R4ZS1cQkeSBU9Urx578rOmxL0si0GAoaYQC49W7OimRelbahxZw/R+f5ch+C1ycU
# CpeXLg+bFhpa0+EXnkGidlILZbJiZJn7qvMQTZgipQKZ8nhX3rtJLqTeodPWzcCk
# tXQUy0q5fxhR3e6Ls7XQesq/G2yMcCMTCd6eltze0OgabvL6Xkhir5lATHaJtnmw
# FlcKzRr1YXK1k1D84hWfKSAdUg8T1O72ztimbqFLg6WoC8M2qqsHcm2DOc9hM3i2
# CWRpegikitRvZ9i1wkA9arGh7+a7UD+nLh2hnGmO06wONLNqABOEn4JOVnNrQ1gY
# eDeH9FDx7IYuAvMsfXG9Bo+I97TR2VfwDAx+ccR+UQLON3aQyFZ3BefYnvUu0gUR
# ikEAnAS4Jnc3BHizgb0voz0iWRDjFoTTmCmrInCVDGc+5KMy0xyoUwdQvYvRGAWB
# 61OCWnXBXbAEPniTZ80CAwEAAaOCAgMwggH/MB8GA1UdIwQYMBaAFGg34Ou2O/hf
# EYb7/mF7CIhl9E5CMB0GA1UdDgQWBBRuAv58K4EDYLmb7WNcxt5+r4NfnzA+BgNV
# HSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMD
# MIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEu
# Y3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDCBlAYIKwYB
# BQUHAQEEgYcwgYQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
# bTBcBggrBgEFBQcwAoZQaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQw
# CQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnXMg6efkBrwLIvd1Xmuh0dam
# 9FhUtDEj+P5SIqdP/U4veOv66NEQhBHLbW2Dvrdm6ec0HMj9b4e8pt4ylKFzHIPj
# fpuRffHVR9JQSx8qpryN6pP49DfCkAYeZGqjY3pGRzd/xQ0cfwcuYbUF+vwVk7tj
# q8c93VHCM0rb5M4N2hD1Ze1pvZxtaf9QnFKFzgXZjr02K6bswQc2+n5jFCp7zV1f
# KTstyb68rhSJBWKK1tMeFk6a6HXr5buTD3skluC0oyPmD7yAd97r2owjDMEveEso
# kADP/z7XQk7wqbwbpi4W6Uju2qHK/9UUsVRF5KTVEAIzVw2V1Aq/Jh3JuSV7b7C1
# 4CghNekltBb+w7YVp8/IFcj7axqnpNQ/+f7RVc3A5hyjV+MkoSwn8Sg7a7hn6SzX
# jec/TfRVvWCmG94MQHko+6206uIXrZnmQ6UQYFyOHRlyKDEozzkZhIcVlsZloUjL
# 3FZ5V/l8TIIzbc3bkEnu4iByksNvRxI6c5264OLauYlWv50ZUPwXmZ9gX8bs3BqZ
# avbGUrOW2PIjtWhtvs4zHhMBCoU1Z0OMvXcF9rUDqefmVCZK46xz3DGKVkDQnxY6
# UWQ3GL60/lEzju4YS99LJVQks2UGmP6LAzlEZ1dnGqi1aQ51OidCYEs39B75PsvO
# By2iAR8pBVi/byWBypExghptMIIaaQIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBH
# NCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEAh8dPej8V5j
# mSw73gBSFOYwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg0oCkTDH7XZZ/echJZG6k0l/i
# fxdcPaBqtbzT+9AB5qMwDQYJKoZIhvcNAQEBBQAEggIAon+JHIBPGH+sG0JTDob5
# UHvoClLebkVHI/niXnnfPNpUdXa7ScSKI5Y+yM80RVF/LIIGjOWliYXQ6z1q8FkB
# U5EdZnrIWyaUCOCcMf4dsq1Z3MZ5cAKdc/4n5LsWgRXxjsCmkBUaFf9iLNJPS+h6
# qJAEpJBqwGZNq9HedYmeEDjfOTZa+A08Pb6u8LPHJBozd6lwsLhig2AXyeEwAWYB
# 33smnGyPhQT5XRhUOH+G9BpCEMrZ1EDStXToVOcIgynvzLKui3sps7+ZOxRB95px
# 5pzP9LDDG+N56t+LLmbv64NgOArHiv//9Q30xDvDnLcLSm0GKzNWjBIAS6ogJtbC
# a79Vdymi+EGGmn784f2W/F7RR0GMqe08dvHVcRGy3yUy83djgUJjley94c3g/iFm
# T+8Xkp3sseAfacpZxJZ1Qe4yb+1T8m19DGYZIGU+oVN+9kqwHIPOB3pZTK6AV7RX
# SkSN1LvBpS9cMlI0sc7V7L2JTRux4jEUmYBhKYmg3FYvltWfI7WhOI/V79VBae3M
# 5gcKK+tIOFY3HlNmtVDW3i2xCFB0L5J0qxLB+lhmHHQ3gBHu9FfQe74JP960W+/1
# e3WHTAsHrumAqOeTEDTlplDw6j9PBm5slLnmJc4xpTEle3S8bmSpaKbMgEPbElPX
# TO4bbkfnDzrl/HRNHcWvjXWhghc6MIIXNgYKKwYBBAGCNwMDATGCFyYwghciBgkq
# hkiG9w0BBwKgghcTMIIXDwIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3DQEJ
# EAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgOJsDy0wG
# m38ZkGsGoavkiJWmGjiYB5dEYGzNaP+oRToCEQDaLHvbUYNAwzGS3sH4ObZnGA8y
# MDI1MDMwNzA4NTUxNVqgghMDMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnpBOMz
# BDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
# cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBT
# SEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEyNTIz
# NTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYDVQQD
# ExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBDEr4I
# xHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo76EO
# 7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rOH3bp
# LEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9ReNZ8
# hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgXj3o5
# WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTVDSup
# WJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16Jidj5
# XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/CacBq
# U0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93NRxvd
# 1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1XCB+1
# rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMBAAGj
# ggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8E
# DDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw
# HwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9XLAN3
# DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3Rh
# bXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRw
# Oi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l
# U3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQSR9lD
# kfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWBb0Hv
# qT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDCzFzU
# y34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1UruJ
# KlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3WpByXt
# gVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGEsshJ
# mLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8a1u7
# cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNFYagL
# DBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7QEY7M
# hKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgEdeoH
# NHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/JceEN
# c2Sg8h3KeFUCS7tpFk7CrDqkMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipe
# WzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdp
# Q2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1
# OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5
# BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0
# YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1Bkmz
# wT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkL
# f50fng8zH1ATCyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C
# 3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5
# n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUd
# zTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWH
# po9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/
# oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPV
# A+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg
# 0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mM
# DDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6E
# VO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB
# /wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1Ud
# IwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNV
# HSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0
# dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2Vy
# dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0f
# BDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB
# MA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBT
# zr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/E
# UExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fm
# niye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szw
# cqMj+sAngkSumScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8TH
# wcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/
# JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9
# Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm
# 228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVB
# tzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnw
# ZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv2
# 7dZ8/DCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEM
# BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE
# CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ
# RCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zC
# pyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf
# 1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x
# 4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEio
# ZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7ax
# xLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZ
# OjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJ
# l2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz
# 2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH
# 4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb
# 5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ
# 9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYD
# VR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuC
# MS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3
# aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v
# dENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0g
# ADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs
# 7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq
# 3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/
# Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9
# /HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWoj
# ayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3YwggNyAgEB
# MHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYD
# VQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFt
# cGluZyBDQQIQC65mvFq6f5WHxvnpBOMzBDANBglghkgBZQMEAgEFAKCB0TAaBgkq
# hkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI1MDMwNzA4
# NTUxNVowKwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQU29OF7mLb0j575PZxSFCHJNWG
# W0UwLwYJKoZIhvcNAQkEMSIEIOCOhgcs2RQvr6npwz+bFpj+BNiFsg26bhHcS7En
# uqZMMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEIHZ2n6jyYy8fQws6IzCu1lZ1/tdz
# 2wXWZbkFk5hDj5rbMA0GCSqGSIb3DQEBAQUABIICAEByiulamfCR5jCyO0n6i86p
# pjsxQtCoNIW/Q1uqU9Cjso9tq7qMejW7CI6HyloqpovsTlMlmoMDJOge0mh+jj9t
# a1o57UmISxsDje/QAnlGvNJ5h9pvJlK4Yret2kOXxqEE1bl2HqV4/v5OBXJzngiB
# dLvRfwYGjaxT/HaK23AGVWBAxT70brOSHTTtzE7w4DuUlPu3n29STmt4fj2Su2qS
# tgL+1+TwU2Qn71L0RaU3quEsSvMbsdyd4ICyUFXn02gm1IzRqSuJIAGcQkhIPjpJ
# 7vPJ1YZz39TnHA2DS4vyxMTCocBP/Kkx6npYI+MwPgJ81ZrBMcyrySmpc88u+IXB
# pfYrDZRTzWtXXoa0QBb3Z5O+3GgWhIwYHXmI+b0ewHvaU9dtku0MSGv7pi3v0jN7
# w2Z0Jfx1NvkZ398ISyTusgYhjY2V0QT+2PTeeuqeLUFhP8Zy8bngPklrIJvlcCvt
# HGumR0RDOXyUwQwbgS1LmerwY3A0ZlRgABlmEcJqj0bJT/Foat8kpqSh3JrCJeae
# X1QCJJvX52J2RnIkywyDMokLp6GhGxkNre/kOUZsh1bfcUgt2NFL8UY9utWTlBH5
# 4MQbs8+gCGZkRDMcK10AH90VzOj30mWeuIxQJNdjd/a4qjhbhcu4Vzlat6/F2u5p
# mQw24HYtvjzrA6bEK/RX
# SIG # End signature block