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
# MIIuugYJKoZIhvcNAQcCoIIuqzCCLqcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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/byWBypExghpsMIIaaAIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYD
# 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/HRNHcWvjXWhghc5MIIXNQYKKwYBBAGCNwMDATGCFyUwghchBgkq
# hkiG9w0BBwKgghcSMIIXDgIBAzEPMA0GCWCGSAFlAwQCAQUAMHcGCyqGSIb3DQEJ
# EAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgOJsDy0wG
# m38ZkGsGoavkiJWmGjiYB5dEYGzNaP+oRToCEBWrUMdVNi8EAr2WDgFm5NAYDzIw
# MjUwMzA0MTI0MjA0WqCCEwMwgga8MIIEpKADAgECAhALrma8Wrp/lYfG+ekE4zME
# MA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy
# dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI
# QTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjQwOTI2MDAwMDAwWhcNMzUxMTI1MjM1
# OTU5WjBCMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQxIDAeBgNVBAMT
# F0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
# MIICCgKCAgEAvmpzn/aVIauWMLpbbeZZo7Xo/ZEfGMSIO2qZ46XB/QowIEMSvgjE
# dEZ3v4vrrTHleW1JWGErrjOL0J4L0HqVR1czSzvUQ5xF7z4IQmn7dHY7yijvoQ7u
# jm0u6yXF2v1CrzZopykD07/9fpAT4BxpT9vJoJqAsP8YuhRvflJ9YeHjes4fduks
# THulntq9WelRWY++TFPxzZrbILRYynyEy7rS1lHQKFpXvo2GePfsMRhNf1F41nyE
# g5h7iOXv+vjX0K8RhUisfqw3TTLHj1uhS66YX2LZPxS4oaf33rp9HlfqSBePejlY
# eEdU740GKQM7SaVSH3TbBL8R6HwX9QVpGnXPlKdE4fBIn5BBFnV+KwPxRNUNK6lY
# k2y1WSKour4hJN0SMkoaNV8hyyADiX1xuTxKaXN12HgR+8WulU2d6zhzXomJ2Ple
# I9V2yfmfXSPGYanGgxzqI+ShoOGLomMd3mJt92nm7Mheng/TBeSA2z4I78JpwGpT
# RHiT7yHqBiV2ngUIyCtd0pZ8zg3S7bk4QC4RrcnKJ3FbjyPAGogmoiZ33c1HG93V
# p6lJ415ERcC7bFQMRbxqrMVANiav1k425zYyFMyLNyE1QulQSgDpW9rtvVcIH7Wv
# G9sqYup9j8z9J1XqbBZPJ5XLln8mS8wWmdDLnBHXgYly/p1DhoQo5fkCAwEAAaOC
# AYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM
# MAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAf
# BgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4EFgQUn1csA3cO
# KBWQZqVjXu5Pkh92oFswWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFt
# cGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6
# Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDovL2NhY2VydHMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVT
# dGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAPa0eH3aZW+M4hBJH2UOR
# 9hHbm04IHdEoT8/T3HuBSyZeq3jSi5GXeWP7xCKhVireKCnCs+8GZl2uVYFvQe+p
# PTScVJeCZSsMo1JCoZN2mMew/L4tpqVNbSpWO9QGFwfMEy60HofN6V51sMLMXNTL
# fhVqs+e8haupWiArSozyAmGH/6oMQAh078qRh6wvJNU6gnh5OruCP1QUAvVSu4kq
# VOcJVozZR5RRb/zPd++PGE3qF1P3xWvYViUJLsxtvge/mzA75oBfFZSbdakHJe2B
# VDGIGVNVjOp8sNt70+kEoMF+T6tptMUNlehSR7vM+C13v9+9ZOUKzfRUAYSyyEmY
# tsnpltD/GWX8eM70ls1V6QG/ZOB6b6Yum1HvIiulqJ1Elesj5TMHq8CWT/xrW7tw
# ipXTJ5/i5pkU5E16RSBAdOp12aw8IQhhA/vEbFkEiF2abhuFixUDobZaA0VhqAsM
# HOmaT3XThZDNi5U2zHKhUs5uHHdG6BoQau75KiNbh0c+hatSF+02kULkftARjsyE
# pHKsF7u5zKRbt5oK5YGwFvgc4pEVUNytmB3BpIiowOIIuDgP5M9WArHYSAR16gc0
# dP2XdkMEP5eBsX7bf/MGN4K3HP50v/01ZHo/Z5lGLvNwQ7XHBx1yomzLP8lx4Q1z
# ZKDyHcp4VQJLu2kWTsKsOqQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5b
# MA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5
# NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkG
# A1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3Rh
# bXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPB
# PXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/
# nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLc
# Z47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mf
# XazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3N
# Ng1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yem
# j052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g
# 3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD
# 4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDS
# LFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwM
# O1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU
# 7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/
# BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0j
# BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E
# PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw
# DQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPO
# vxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQ
# TGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWae
# LJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPBy
# oyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfB
# wWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8l
# Y5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/
# O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbb
# bxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3
# OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBl
# dkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt
# 1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0BAQwF
# ADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElE
# IFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQswCQYD
# VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln
# aWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKn
# JS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/W
# BTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHi
# LQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhm
# V1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHE
# tWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
# MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mX
# aXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZ
# xd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfh
# vbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvl
# EFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn1
# 5GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
# HQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SSy4Ix
# LVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
# Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRVHSAA
# MA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyhhyzs
# hV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO0Cre
# +i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo8L8v
# C6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++hUD38
# dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5xaiNr
# Iv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYIDdjCCA3ICAQEw
# dzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNV
# BAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1w
# aW5nIENBAhALrma8Wrp/lYfG+ekE4zMEMA0GCWCGSAFlAwQCAQUAoIHRMBoGCSqG
# SIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjUwMzA0MTI0
# MjA0WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTb04XuYtvSPnvk9nFIUIck1YZb
# RTAvBgkqhkiG9w0BCQQxIgQgyTIDqTXJ+rMQ5vXt2sW9QVKSLC1Nc8W2hu0m3rag
# U0EwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgdnafqPJjLx9DCzojMK7WVnX+13Pb
# BdZluQWTmEOPmtswDQYJKoZIhvcNAQEBBQAEggIALCsSo98mEbBXQFDe6KWPxHGc
# Y8ZSVSIRvN/tMX2QI0SrTyvBZ5bFXRmReCcrd3QGSnyCsXI1uampORd961VYQ6IT
# GfkG3P/iWGe9qXU3NueqR3R0Wlm3TLfymBtWrZylIjravoAm1AfHN7YRga/+2slb
# ciEoDHbOA9Jld0pgkLfBzCxD87weZBuXmKkqz4JKZeK/dqrktFn1TEE4rtftvkED
# Ou9wwUBTSjGYGUAYr7FgHmZHpI5VaInt2wiHFuSOiVddk/0JaMgrFpOXVTnXuItt
# JSev437tzmHORila2t4QYngOnMrEZ4GvnBKL2rHjRni2PjimBTP+tNFPlmgvK9z2
# 63LnHMh6Pr69CgN9dgM6aIrDZ8fsFRJdk8hOWnH2qLFfUTP/0CdO7XHvop/1qOMS
# AZYURfTeBEXuFVYyc3m8kJL/5H++QsuHmS0FZa0noBZFXklzCtRiDJudYbzAGcMN
# /FQdS/rdIFY+wf8w+Zpc603lEeLdJj6J3BO3Ynn/YJwlM9QSDlk8VzEMGgqzjbxn
# xDladqEmgZ5VvkvMY7cLmdbF/oYM0gUknedkWXBF7agodzEBFsA9VHO1xS6rzjsV
# 49cGG6OTGjcIRbY5/KHjwX5yeVt3n6TBRisXkIcEul8j0fzk8bpMMtMQfMOf5JuD
# W4Rw03ClW7GzfBtMpnM=
# SIG # End signature block