Public/Add-VellumPdfPath.ps1
|
function Add-VellumPdfPath { <# .SYNOPSIS Draws a free-form path (lines and Bezier curves) on a page of a page-based document. .DESCRIPTION Describe the outline with -Data, a small subset of SVG path syntax in PDF points (72 pt = 1 inch, origin at the page's bottom-left). Numbers use a dot as the decimal mark, whatever the culture; build the string with interpolation ("$x") rather than -f or .ToString(), which use the current culture's decimal comma: M x y move to (starts a sub-path) L x y straight line to C x1 y1 x2 y2 x y cubic Bezier curve to Z close the current sub-path Only upper-case absolute commands are accepted; numbers after a command may repeat (L 10 10 20 20 draws two lines). A path may hold several sub-paths, for example a ring made of an outer and an inner circle. Alternatively give a polyline or polygon as x,y pairs with -Point (add -Close for a polygon). -FillRule decides which areas count as inside when sub-paths overlap: NonZero (default) or EvenOdd, which cuts the inner sub-path out of the outer one. A path with neither a fill nor a stroke is stroked in black. The PDF operator for "fill even-odd and stroke" does not exist in the engine, so an EvenOdd path with both is painted as a fill followed by a stroke of the same outline; the result is the same. Colours, dashes, caps, joins and opacity work as in Add-VellumPdfShape, including -FillCmyk/-StrokeCmyk and ICC colours (-IccProfilePath with -FillIcc/-StrokeIcc). .PARAMETER Document A document from New-VellumPdfPageDocument. .PARAMETER Page The 1-based page number. Default 1. .PARAMETER Data Path data, e.g. 'M 100 100 L 200 100 L 150 180 Z'. .PARAMETER Point Polyline or polygon vertices as x,y pairs, e.g. (100,100),(200,100),(150,180). .PARAMETER Close Close the -Point outline into a polygon. .PARAMETER FillRule NonZero (default) or EvenOdd. .PARAMETER IccProfilePath An ICC profile that -FillIcc / -StrokeIcc colours are expressed in. .PARAMETER FillIcc Fill colour as components of the ICC profile (1, 3 or 4 values in 0..1). .PARAMETER StrokeIcc Outline colour as components of the ICC profile. .PARAMETER FillColor Fill colour (RGB triple, hex or name). .PARAMETER FillGray Fill colour as a grey level, 0 (black) to 1 (white), written as DeviceGray. .PARAMETER StrokeGray Outline colour as a grey level, 0 (black) to 1 (white). .PARAMETER FillCmyk Fill colour as C,M,Y,K in 0..1. .PARAMETER StrokeColor Outline colour (RGB triple, hex or name). .PARAMETER StrokeCmyk Outline colour as C,M,Y,K in 0..1. .PARAMETER StrokeWidth Outline width in points (default 1). .PARAMETER Dash Dash pattern in points, e.g. 4,2. .PARAMETER LineCap Butt (default), Round or Square. .PARAMETER LineJoin Miter (default), Round or Bevel. .PARAMETER MiterLimit Longest miter, as a multiple of the line width, before a Miter join is bevelled. .PARAMETER Opacity Fill opacity 0..1. .PARAMETER StrokeOpacity Outline opacity 0..1. .PARAMETER AltText Alternate text. In a tagged page document the path is then a Figure with this text; without it the path is an artifact that assistive technology skips. .EXAMPLE $doc | Add-VellumPdfPath -Data 'M 100 100 L 200 100 L 150 180 Z' -FillColor '#f4b400' -StrokeColor black .EXAMPLE # A ring: two circles, the inner one cut out by the even-odd rule. String interpolation always # writes numbers with a dot; -f and .ToString() would use a decimal comma in some cultures. $circle = { param($cx, $cy, $r) $k = 0.5522847498 * $r "M $($cx + $r) $cy C $($cx + $r) $($cy + $k) $($cx + $k) $($cy + $r) $cx $($cy + $r) " + "C $($cx - $k) $($cy + $r) $($cx - $r) $($cy + $k) $($cx - $r) $cy " + "C $($cx - $r) $($cy - $k) $($cx - $k) $($cy - $r) $cx $($cy - $r) " + "C $($cx + $k) $($cy - $r) $($cx + $r) $($cy - $k) $($cx + $r) $cy Z" } $doc | Add-VellumPdfPath -Data ((& $circle 200 400 50) + ' ' + (& $circle 200 400 25)) -FillRule EvenOdd -FillColor navy .OUTPUTS VellumPdf.Document.PdfDocument (the same instance, for chaining) #> [CmdletBinding(DefaultParameterSetName = 'Data')] [OutputType([VellumPdf.Document.PdfDocument])] param( [Parameter(Mandatory, ValueFromPipeline)] [VellumPdf.Document.PdfDocument]$Document, [Parameter(Mandatory, Position = 0, ParameterSetName = 'Data')] [ValidateNotNullOrEmpty()] [string]$Data, [Parameter(Mandatory, ParameterSetName = 'Point')] [ValidateNotNull()] [object[]]$Point, [Parameter(ParameterSetName = 'Point')] [switch]$Close, [ValidateRange(1, 1000000)] [int]$Page = 1, [ValidateSet('NonZero', 'EvenOdd')] [string]$FillRule = 'NonZero', [string]$IccProfilePath, [ValidateRange(0.0, 1.0)] [double[]]$FillIcc, [ValidateRange(0.0, 1.0)] [double[]]$StrokeIcc, [object]$FillColor, [ValidateCount(4, 4)] [ValidateRange(0.0, 1.0)] [double[]]$FillCmyk, [ValidateRange(0.0, 1.0)] [double]$FillGray, [object]$StrokeColor, [ValidateCount(4, 4)] [ValidateRange(0.0, 1.0)] [double[]]$StrokeCmyk, [ValidateRange(0.0, 1.0)] [double]$StrokeGray, [ValidateRange(0.0, 10000.0)] [double]$StrokeWidth = 1, [ValidateRange(0.01, 10000.0)] [double[]]$Dash, [ValidateSet('Butt', 'Round', 'Square')] [string]$LineCap, [ValidateSet('Miter', 'Round', 'Bevel')] [string]$LineJoin, [ValidateRange(1.0, 100.0)] [double]$MiterLimit, [ValidateRange(0.0, 1.0)] [double]$Opacity, [ValidateRange(0.0, 1.0)] [double]$StrokeOpacity, [ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) }, ErrorMessage = '-AltText must not be empty or whitespace.')] [string]$AltText ) process { try { Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Add-VellumPdfPath' $Page = Resolve-VellumPdfPageNumber -Document $Document -Page $(if ($PSBoundParameters.ContainsKey('Page')) { $PSBoundParameters['Page'] } else { 1 }) -Bound $PSBoundParameters.ContainsKey('Page') -CommandName 'Add-VellumPdfPath' $bound = $PSBoundParameters foreach ($side in 'Fill', 'Stroke') { $models = @(@("${side}Color", "${side}Cmyk", "${side}Icc", "${side}Gray") | Where-Object { $bound.ContainsKey($_) }) if ($models.Count -gt 1) { throw "Add-VellumPdfPath: give the $($side.ToLower()) colour as RGB/hex/name, CMYK, grey or ICC components, not several." } } $hasIcc = $bound.ContainsKey('FillIcc') -or $bound.ContainsKey('StrokeIcc') if ($hasIcc -ne $bound.ContainsKey('IccProfilePath')) { throw 'Add-VellumPdfPath: -IccProfilePath and -FillIcc/-StrokeIcc go together; give both.' } if (($bound.ContainsKey('FillIcc') -and ($bound.ContainsKey('FillColor') -or $bound.ContainsKey('FillCmyk'))) -or ($bound.ContainsKey('StrokeIcc') -and ($bound.ContainsKey('StrokeColor') -or $bound.ContainsKey('StrokeCmyk'))) -or ($bound.ContainsKey('FillColor') -and $bound.ContainsKey('FillCmyk')) -or ($bound.ContainsKey('StrokeColor') -and $bound.ContainsKey('StrokeCmyk'))) { throw 'Add-VellumPdfPath: give a colour as RGB/hex/name, CMYK, or ICC components, not several.' } if ($PSCmdlet.ParameterSetName -eq 'Point') { $ops = ConvertTo-VellumPdfPathOp -Point $Point -Close:$Close -CommandName 'Add-VellumPdfPath' } else { $ops = ConvertTo-VellumPdfPathOp -Data $Data -CommandName 'Add-VellumPdfPath' } if (($bound.ContainsKey('FillCmyk') -or $bound.ContainsKey('StrokeCmyk')) -and (Get-VellumPdfConformanceRequirement -Document $Document).IsPdfA -and -not $Document.PSObject.Properties['PSVellumCmykIntent']) { Write-Warning ('Add-VellumPdfPath: PDF/A allows CMYK colour only with a CMYK output intent. Call ' + 'Set-VellumPdfOutputIntent -Cmyk on this document, or use RGB colours.') } $canvas = Get-VellumPdfPageCanvas -Document $Document -Page $Page -CommandName 'Add-VellumPdfPath' $iccName = $null if ($hasIcc) { $icc = Register-VellumPdfIccSpace -Document $Document -Page $Page -IccProfilePath $IccProfilePath -CommandName 'Add-VellumPdfPath' $iccName = $icc.Name foreach ($pair in @(@('FillIcc', $FillIcc), @('StrokeIcc', $StrokeIcc))) { if ($bound.ContainsKey($pair[0]) -and @($pair[1]).Count -ne $icc.Count) { throw "Add-VellumPdfPath: -$($pair[0]) needs $($icc.Count) value(s) for this profile; got $(@($pair[1]).Count)." } } } $hasFill = $bound.ContainsKey('FillColor') -or $bound.ContainsKey('FillCmyk') -or $bound.ContainsKey('FillIcc') -or $bound.ContainsKey('FillGray') $strokeRequested = $bound.ContainsKey('LineCap') -or $bound.ContainsKey('LineJoin') -or $bound.ContainsKey('MiterLimit') -or $bound.ContainsKey('StrokeColor') -or $bound.ContainsKey('StrokeCmyk') -or $bound.ContainsKey('StrokeIcc') -or $bound.ContainsKey('StrokeGray') -or $bound.ContainsKey('StrokeWidth') -or $bound.ContainsKey('Dash') -or $bound.ContainsKey('StrokeOpacity') $hasStroke = $strokeRequested -or -not $hasFill [void]$canvas.SaveState() try { $tagKind = if ($AltText) { 'Structure' } else { 'Artifact' } Invoke-VellumPdfTagged -Document $Document -Canvas $canvas -PdfPage $Document.Pages[$Page - 1] -Kind $tagKind -StructType Figure -AltText $AltText -Draw { if ($bound.ContainsKey('FillColor')) { $c = ConvertTo-VellumColor $FillColor -Document $Document -CommandName 'Add-VellumPdfPath'; [void]$canvas.SetFillColorRgb($c[0], $c[1], $c[2]) } if ($bound.ContainsKey('FillGray')) { [void]$canvas.SetFillColorGray($FillGray) } if ($bound.ContainsKey('FillCmyk')) { [void]$canvas.SetFillColorCmyk($FillCmyk[0], $FillCmyk[1], $FillCmyk[2], $FillCmyk[3]) } if ($bound.ContainsKey('StrokeColor')) { $c = ConvertTo-VellumColor $StrokeColor -Document $Document -CommandName 'Add-VellumPdfPath'; [void]$canvas.SetStrokeColorRgb($c[0], $c[1], $c[2]) } if ($bound.ContainsKey('StrokeGray')) { [void]$canvas.SetStrokeColorGray($StrokeGray) } if ($bound.ContainsKey('StrokeCmyk')) { [void]$canvas.SetStrokeColorCmyk($StrokeCmyk[0], $StrokeCmyk[1], $StrokeCmyk[2], $StrokeCmyk[3]) } if ($bound.ContainsKey('FillIcc')) { [void]$canvas.SetFillColorSpace($iccName) [void][PSVellumPDF.Interop.CanvasHelpers]::SetFillColor($canvas, $FillIcc) } if ($bound.ContainsKey('StrokeIcc')) { [void]$canvas.SetStrokeColorSpace($iccName) [void][PSVellumPDF.Interop.CanvasHelpers]::SetStrokeColor($canvas, $StrokeIcc) } if ($bound.ContainsKey('Opacity')) { [void]$canvas.SetFillAlpha($Opacity) } if ($bound.ContainsKey('StrokeOpacity')) { [void]$canvas.SetStrokeAlpha($StrokeOpacity) } if ($hasStroke) { [void]$canvas.SetLineWidth($StrokeWidth) if ($bound.ContainsKey('LineCap')) { [void]$canvas.SetLineCap(@{ Butt = 0; Round = 1; Square = 2 }[$LineCap]) } if ($bound.ContainsKey('LineJoin')) { [void]$canvas.SetLineJoin(@{ Miter = 0; Round = 1; Bevel = 2 }[$LineJoin]) } if ($bound.ContainsKey('MiterLimit')) { [void]$canvas.SetMiterLimit($MiterLimit) } if ($Dash) { [void][PSVellumPDF.Interop.CanvasHelpers]::SetLineDash($canvas, $Dash, 0) } } Write-VellumPdfPathOp -Canvas $canvas -Ops $ops if ($hasFill -and $hasStroke) { if ($FillRule -eq 'EvenOdd') { # No fill-even-odd-and-stroke operator: paint the fill, then the same outline again as a stroke. [void]$canvas.FillEvenOdd() Write-VellumPdfPathOp -Canvas $canvas -Ops $ops [void]$canvas.Stroke() } else { [void]$canvas.FillAndStroke() } } elseif ($hasFill) { if ($FillRule -eq 'EvenOdd') { [void]$canvas.FillEvenOdd() } else { [void]$canvas.Fill() } } else { [void]$canvas.Stroke() } } } finally { [void]$canvas.RestoreState() } $Document } catch { $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Add-VellumPdfPath')) } } } |