Public/Add-VellumPdfPageImage.ps1

function Add-VellumPdfPageImage {
    <#
    .SYNOPSIS
        Places an image at x/y on a page of a page-based document.
    .DESCRIPTION
        Loads a JPEG, PNG, BMP, GIF, TIFF, JBIG2 or JPEG 2000 image (from -Path or
        from -ImageBytes with -Format) and draws it with its lower-left corner at
        (-X, -Y), in points from the page's bottom-left. Give -Width, -Height or
        both: with one the aspect ratio is kept; with neither the image is drawn at
        one pixel per point. For load options (16-bit reduction, raster decoding) or
        CCITT fax data use the flow-layout Add-VellumPdfImage.

        In a tagged page document (New-VellumPdfPageDocument -Tagged, PdfA2a or PdfUA1)
        every image needs -AltText, or -Decorative to be treated as an artifact.
    .PARAMETER Document
        A document from New-VellumPdfPageDocument.
    .PARAMETER Path
        The image file; the format is taken from its extension.
    .PARAMETER ImageBytes
        In-memory image bytes; also give -Format.
    .PARAMETER Format
        Jpeg, Png, Bmp, Gif, Tiff, Jbig2 or Jpeg2000 for -ImageBytes.
    .PARAMETER Page
        The 1-based page number. Default 1.
    .PARAMETER X
        Left edge in points.
    .PARAMETER Y
        Bottom edge in points.
    .PARAMETER Width
        Drawn width in points.
    .PARAMETER Height
        Drawn height in points.
    .PARAMETER AltText
        Alternate text for the image in a tagged document.
    .PARAMETER Decorative
        Mark the image as an artifact (not read by assistive technology) in a tagged document.
    .PARAMETER Rotation
        Rotates the image about its centre, in degrees counter-clockwise. -X and -Y, -Width and
        -Height describe the unrotated image.
    .EXAMPLE
        $doc | Add-VellumPdfPageImage ./logo.png -X 72 -Y 740 -Width 120
    .OUTPUTS
        VellumPdf.Document.PdfDocument (the same instance, for chaining)
    #>

    [CmdletBinding(DefaultParameterSetName = 'Path')]
    [OutputType([VellumPdf.Document.PdfDocument])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [VellumPdf.Document.PdfDocument]$Document,

        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')]
        [string]$Path,

        [Parameter(Mandatory, ParameterSetName = 'Bytes')]
        [byte[]]$ImageBytes,

        [Parameter(Mandatory, ParameterSetName = 'Bytes')]
        [ValidateSet('Jpeg', 'Png', 'Bmp', 'Gif', 'Tiff', 'Jbig2', 'Jpeg2000')]
        [string]$Format,

        [ValidateRange(1, 1000000)]
        [int]$Page = 1,

        [Parameter(Mandatory)] [double]$X,
        [Parameter(Mandatory)] [double]$Y,

        [ValidateRange(0.01, 1000000.0)]
        [double]$Width,

        [ValidateRange(0.01, 1000000.0)]
        [double]$Height,

        # Tagged documents: alternate text for the image, or -Decorative to mark it as an artifact.
        [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) }, ErrorMessage = '-AltText must not be empty or whitespace.')]
        [string]$AltText,

        [switch]$Decorative,

        # Rotation about the image centre in degrees, counter-clockwise.
        [double]$Rotation
    )

    process {
        try {
            Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Add-VellumPdfPageImage'
            $bound = $PSBoundParameters
            if ($bound.ContainsKey('Rotation') -and -not [double]::IsFinite($Rotation)) { throw 'Add-VellumPdfPageImage: -Rotation must be a finite number of degrees.' }
            $Page = Resolve-VellumPdfPageNumber -Document $Document -Page $(if ($PSBoundParameters.ContainsKey('Page')) { $PSBoundParameters['Page'] } else { 1 }) -Bound $PSBoundParameters.ContainsKey('Page') -CommandName 'Add-VellumPdfPageImage'
            Assert-VellumPdfCoordinate $X 'X' 'Add-VellumPdfPageImage'; Assert-VellumPdfCoordinate $Y 'Y' 'Add-VellumPdfPageImage'
            $pdfPage = Get-VellumPdfPage -Document $Document -Page $Page -CommandName 'Add-VellumPdfPageImage'
            if ($Decorative -and $AltText) { throw 'Add-VellumPdfPageImage: -AltText and -Decorative cannot be combined.' }
            if ($Document.Tagged -and -not $Decorative -and -not $AltText) {
                throw ('Add-VellumPdfPageImage: this document is tagged, so every image needs -AltText (what it shows) ' +
                    'or -Decorative (assistive technology should skip it).')
            }

            if ($PSCmdlet.ParameterSetName -eq 'Bytes') {
                $bytes = $ImageBytes; $key = $Format.ToLowerInvariant(); $source = 'the supplied image bytes'
            }
            else {
                $resolved = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
                if (-not [System.IO.File]::Exists($resolved)) { throw (New-VellumPdfErrorRecord FileNotFound "Add-VellumPdfPageImage: file not found: '$resolved'.") }
                $bytes = [System.IO.File]::ReadAllBytes($resolved)
                $source = "'$resolved'"
                $key = Get-VellumPdfImageFormat -Extension ([System.IO.Path]::GetExtension($resolved)) -CommandName 'Add-VellumPdfPageImage'
            }
            $image = Import-VellumPdfImage -Bytes $bytes -Format $key -CommandName 'Add-VellumPdfPageImage' -Source $source

            Write-VellumPdfRgbIntentWarning -Document $Document -CommandName 'Add-VellumPdfPageImage' -Image $image
            $w = if ($PSBoundParameters.ContainsKey('Width')) { $Width } elseif ($PSBoundParameters.ContainsKey('Height')) { $Height * $image.Width / $image.Height } else { [double]$image.Width }
            $h = if ($PSBoundParameters.ContainsKey('Height')) { $Height } elseif ($PSBoundParameters.ContainsKey('Width')) { $Width * $image.Height / $image.Width } else { [double]$image.Height }

            $counterProp = $Document.PSObject.Properties['PSVellumImageCount']
            if (-not $counterProp) {
                $Document.PSObject.Properties.Add([psnoteproperty]::new('PSVellumImageCount', 0))
                $counterProp = $Document.PSObject.Properties['PSVellumImageCount']
            }
            $counterProp.Value++
            $name = "VIm$($counterProp.Value)"
            [void]$Document.RegisterImageXObject($pdfPage, $image, $name)

            $canvas = Get-VellumPdfPageCanvas -Document $Document -Page $Page -CommandName 'Add-VellumPdfPageImage'
            [void]$canvas.SaveState()
            try {
                $kind = if ($Decorative) { 'Artifact' } else { 'Structure' }
                Invoke-VellumPdfTagged -Document $Document -Canvas $canvas -PdfPage $pdfPage -Kind $kind -StructType Figure -AltText $AltText -Draw {
                    if ($bound.ContainsKey('Rotation') -and $Rotation -ne 0) {
                        # Rotate about the image centre: the unit square is scaled to w x h, turned, and put back.
                        $radians = $Rotation * [math]::PI / 180
                        $cos = [math]::Cos($radians); $sin = [math]::Sin($radians)
                        $cx = $X + $w / 2; $cy = $Y + $h / 2
                        $m = ($w * $cos), ($w * $sin), (0.0 - $h * $sin), ($h * $cos), ($cx - 0.5 * $w * $cos + 0.5 * $h * $sin), ($cy - 0.5 * $w * $sin - 0.5 * $h * $cos) |
                            ForEach-Object { [math]::Round($_, 8) + 0.0 }
                        [void]$canvas.Concat($m[0], $m[1], $m[2], $m[3], $m[4], $m[5]).DoXObject($name)
                    }
                    else { [void]$canvas.Concat($w, 0, 0, $h, $X, $Y).DoXObject($name) }
                }
            }
            finally { [void]$canvas.RestoreState() }
            $Document
        }
        catch {
            $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Add-VellumPdfPageImage'))
        }
    }
}