Public/Add-VellumPdfDrawing.ps1
|
function Add-VellumPdfDrawing { <# .SYNOPSIS Reserves a block in the page flow and draws into it with a script block. .DESCRIPTION Gives you the full VellumPdf canvas (PdfCanvas) for free-form vector drawing - shapes, gradients, transparency, CMYK colour, clipping, dashes, arcs, text - inside the normal flow of a Layout document. The script block receives: $canvas the VellumPdf.Canvas.PdfCanvas, already in PDF page space $box a record with X, Y (bottom-left corner), Width and Height in points: the area reserved for the drawing $ctx the VellumPdf.Layout.Core.DrawContext, for fonts: $font = $ctx.GetFont([VellumPdf.Fonts.Standard14]::Helvetica) $canvas.SetFont($font, 12) ; embedded fonts: $ctx.UseEmbeddedFont($handle) Coordinates are PDF points with the origin at the page's bottom-left. The canvas state is saved before and restored after your script, so you cannot leak colours or transforms into later content. PowerShell cannot pass arrays to the canvas operators that take spans (dash patterns, raw colour components, glyph ids); use the helpers in [PSVellumPDF.Interop.CanvasHelpers] (SetLineDash, SetFillColor, SetStrokeColor, ShowGlyphs) instead. The script block runs while the document is saved, on the PowerShell thread. The variables it reads (such as a loop variable or a function's locals) are captured when Add-VellumPdfDrawing is called, so the block draws with the values they had then; $script: and $global: variables and functions stay live, and assignments to a captured variable stay inside the block. SaveState/RestoreState must balance inside the block: an uneven block makes the save fail with an error naming Add-VellumPdfDrawing. In a PDF/A or PDF/UA document draw text with an embedded font ($ctx.UseEmbeddedFont), since $ctx.GetFont uses a non-embedded base-14 font. Because it runs at save time it works with Save-VellumPdfDocument but not with an asynchronous save or an external asynchronous signer. In a tagged document the drawing is a Figure with -AltText (or the text 'Drawing'); pass -Decorative for purely decorative graphics. .PARAMETER Document The live VellumPdf document flowing through the pipeline. .PARAMETER Height Height in points of the block to reserve. It spans the content width. .PARAMETER ScriptBlock Draws into the block: param($canvas, $box, $ctx). .PARAMETER AltText Alternate text for a tagged document. .PARAMETER Decorative Mark the drawing as an artifact (not read by screen readers). .PARAMETER MarginTop Extra spacing in points above the block. .PARAMETER MarginBottom Extra spacing in points below the block. .EXAMPLE $doc | Add-VellumPdfDrawing -Height 60 -AltText 'A red box with a blue border' -ScriptBlock { param($canvas, $box) $canvas.SetFillColorRgb(1, 0, 0).SetStrokeColorRgb(0, 0, 1).SetLineWidth(2) $canvas.Rectangle($box.X + 5, $box.Y + 5, 100, $box.Height - 10).FillAndStroke() | Out-Null } .OUTPUTS VellumPdf.Layout.Document (the same instance, for chaining) #> [CmdletBinding()] [OutputType([VellumPdf.Layout.Document])] param( [Parameter(Mandatory, ValueFromPipeline)] [VellumPdf.Layout.Document]$Document, [Parameter(Mandatory)] [ValidateRange(1.0, 100000.0)] [double]$Height, [Parameter(Mandatory, Position = 0)] [scriptblock]$ScriptBlock, [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) }, ErrorMessage = '-AltText must not be empty or whitespace.')] [string]$AltText, [switch]$Decorative, [ValidateRange(0.0, 10000.0)] [double]$MarginTop, [ValidateRange(0.0, 10000.0)] [double]$MarginBottom ) process { try { Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Add-VellumPdfDrawing' if ($Decorative -and $AltText) { throw 'Add-VellumPdfDrawing: -AltText and -Decorative cannot be combined.' } $staged = $Document.PSObject.Properties['PSVellumSignature'] if ($staged -and $staged.Value.ExternalSigner) { throw ('Add-VellumPdfDrawing: this document is staged for signing with an -ExternalSigner, which signs ' + 'asynchronously and cannot run drawing script blocks. Sign with the certificate or an -ExternalPrivateKey instead.') } # Space deferred by the previous element's -MarginBottom goes above this block. $top = if ($PSBoundParameters.ContainsKey('MarginTop')) { $MarginTop } else { 0.0 } $pending = $Document.PSObject.Properties['PSVellumPendingSpace'] if ($pending -and $pending.Value) { $top += [double]$pending.Value $pending.Value = 0.0 } # The block runs at save time: hand it the values its variables have now (a loop variable, a # function's locals), not whatever they hold when the document is saved. $captured = [System.Collections.Generic.List[System.Management.Automation.PSVariable]]::new() foreach ($variable in @(Get-VellumPdfBlockVariable -ScriptBlock $ScriptBlock)) { $captured.Add($variable) } $renderer = [PSVellumPDF.Interop.ScriptBlockRenderer]::new($Height, $ScriptBlock, $AltText, [bool]$Decorative, $top, $MarginBottom, $captured) [void]$Document.Add($renderer) # Remember that the document holds script-block content: a signature # produced asynchronously (external signer) cannot run it. if (-not $Document.PSObject.Properties['PSVellumHasDrawing']) { $Document.PSObject.Properties.Add([psnoteproperty]::new('PSVellumHasDrawing', $true)) } $Document } catch { $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Add-VellumPdfDrawing')) } } } |