Public/Use-VellumPdfClip.ps1

function Use-VellumPdfClip {
    <#
    .SYNOPSIS
        Runs a script block whose page drawing is clipped to a region.
    .DESCRIPTION
        Everything drawn on the page by the script block (Add-VellumPdfShape, -Path,
        -Text, -Gradient, -PageImage, Invoke-VellumPdfCanvas ...) is visible only inside the
        clip region; the clip is lifted again when the block ends, even if it throws. Clip
        scopes can be nested, which intersects the regions.

        Give the region as -Data/-Point (see Add-VellumPdfPath), as a rectangle with
        -X/-Y/-Width/-Height, or as an ellipse with -Ellipse. The default -FillRule NonZero
        or EvenOdd decides what is inside when sub-paths overlap.

        Links and form fields (Add-VellumPdfLink, Add-VellumPdfFormField) are annotations in page coordinates
        and are not affected by the clip.

        The block runs in the caller's scope with the document as $_. Its own output is
        discarded; the cmdlet writes the document to the pipeline, so the call chains.
    .PARAMETER Document
        A document from New-VellumPdfPageDocument.
    .PARAMETER Page
        The 1-based page number. Default 1.
    .PARAMETER ScriptBlock
        The drawing to clip.
    .PARAMETER Data
        Path data for the region, e.g. 'M 100 100 L 300 100 L 200 250 Z'.
    .PARAMETER Point
        Polygon vertices as x,y pairs.
    .PARAMETER X
        Rectangle/ellipse left edge.
    .PARAMETER Y
        Rectangle/ellipse bottom edge.
    .PARAMETER Width
        Rectangle/ellipse width.
    .PARAMETER Height
        Rectangle/ellipse height.
    .PARAMETER Ellipse
        Make the box an ellipse instead of a rectangle.
    .PARAMETER FillRule
        NonZero (default) or EvenOdd.
    .EXAMPLE
        $doc | Use-VellumPdfClip -X 72 -Y 500 -Width 200 -Height 100 -Ellipse -ScriptBlock {
            $_ | Add-VellumPdfGradient -Direction LeftToRight -X 72 -Y 500 -Width 200 -Height 100 -StartColor red -EndColor blue
        }
    .OUTPUTS
        VellumPdf.Document.PdfDocument (the same instance, for chaining)
    #>

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

        [Parameter(Mandatory, Position = 0)]
        [scriptblock]$ScriptBlock,

        [Parameter(Mandatory, ParameterSetName = 'Data')]
        [ValidateNotNullOrEmpty()]
        [string]$Data,

        [Parameter(Mandatory, ParameterSetName = 'Point')]
        [ValidateNotNull()]
        [object[]]$Point,

        [Parameter(Mandatory, ParameterSetName = 'Box')] [double]$X,
        [Parameter(Mandatory, ParameterSetName = 'Box')] [double]$Y,
        [Parameter(Mandatory, ParameterSetName = 'Box')] [ValidateRange(0.01, 1000000.0)] [double]$Width,
        [Parameter(Mandatory, ParameterSetName = 'Box')] [ValidateRange(0.01, 1000000.0)] [double]$Height,
        [Parameter(ParameterSetName = 'Box')] [switch]$Ellipse,

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

        [ValidateSet('NonZero', 'EvenOdd')]
        [string]$FillRule = 'NonZero'
    )

    process {
        try {
            Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Use-VellumPdfClip'
            $Page = Resolve-VellumPdfPageNumber -Document $Document -Page $(if ($PSBoundParameters.ContainsKey('Page')) { $PSBoundParameters['Page'] } else { 1 }) -Bound $PSBoundParameters.ContainsKey('Page') -CommandName 'Use-VellumPdfClip'
            switch ($PSCmdlet.ParameterSetName) {
                'Data' { $ops = ConvertTo-VellumPdfPathOp -Data $Data -CommandName 'Use-VellumPdfClip' }
                'Point' { $ops = ConvertTo-VellumPdfPathOp -Point $Point -Close -CommandName 'Use-VellumPdfClip' }
                default {
                    foreach ($pair in @('X', $X), @('Y', $Y), @('Width', $Width), @('Height', $Height)) {
                        Assert-VellumPdfCoordinate $pair[1] $pair[0] 'Use-VellumPdfClip'
                    }
                    if ($Ellipse) {
                        $k = 0.5522847498
                        $rx = $Width / 2; $ry = $Height / 2
                        $cx = $X + $rx; $cy = $Y + $ry
                        $kx = $k * $rx; $ky = $k * $ry
                        $ops = @(
                            [pscustomobject]@{ Op = 'M'; Args = [double[]]@(($cx + $rx), $cy) }
                            [pscustomobject]@{ Op = 'C'; Args = [double[]]@(($cx + $rx), ($cy + $ky), ($cx + $kx), ($cy + $ry), $cx, ($cy + $ry)) }
                            [pscustomobject]@{ Op = 'C'; Args = [double[]]@(($cx - $kx), ($cy + $ry), ($cx - $rx), ($cy + $ky), ($cx - $rx), $cy) }
                            [pscustomobject]@{ Op = 'C'; Args = [double[]]@(($cx - $rx), ($cy - $ky), ($cx - $kx), ($cy - $ry), $cx, ($cy - $ry)) }
                            [pscustomobject]@{ Op = 'C'; Args = [double[]]@(($cx + $kx), ($cy - $ry), ($cx + $rx), ($cy - $ky), ($cx + $rx), $cy) }
                            [pscustomobject]@{ Op = 'Z'; Args = [double[]]@() }
                        )
                    }
                    else {
                        $ops = @(
                            [pscustomobject]@{ Op = 'M'; Args = [double[]]@($X, $Y) }
                            [pscustomobject]@{ Op = 'L'; Args = [double[]]@(($X + $Width), $Y) }
                            [pscustomobject]@{ Op = 'L'; Args = [double[]]@(($X + $Width), ($Y + $Height)) }
                            [pscustomobject]@{ Op = 'L'; Args = [double[]]@($X, ($Y + $Height)) }
                            [pscustomobject]@{ Op = 'Z'; Args = [double[]]@() }
                        )
                    }
                }
            }
            $canvas = Get-VellumPdfPageCanvas -Document $Document -Page $Page -CommandName 'Use-VellumPdfClip'
            [void]$canvas.SaveState()
            Push-VellumPdfScopePage -Document $Document -Page $Page
            try {
                Write-VellumPdfPathOp -Canvas $canvas -Ops $ops
                if ($FillRule -eq 'EvenOdd') { [void]$canvas.ClipEvenOdd() } else { [void]$canvas.Clip() }
                [void]$canvas.EndPath()
                # Run in the caller's scope with the document as $_; the block's own output is dropped.
                $null = $Document | ForEach-Object -Process $ScriptBlock
            }
            finally { Pop-VellumPdfScopePage -Document $Document; [void]$canvas.RestoreState() }
            $Document
        }
        catch {
            $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Use-VellumPdfClip'))
        }
    }
}