Public/Add-VellumPdfTextBox.ps1
|
function Add-VellumPdfTextBox { <# .SYNOPSIS Places wrapped, optionally justified text in a column on a page of a page-based document. .DESCRIPTION Breaks -Text into lines that fit -Width (measured with the real font metrics) and draws them one under the other. A line break in -Text (`n) starts a new paragraph, a blank line leaves an empty line, and a word wider than the column is broken at character boundaries. -X is the left edge and -Y the top of the box in points from the page's bottom-left; the first baseline sits one -FontSize below -Y and the lines follow at -Leading (default 1.2 times the font size). A tab is expanded to four spaces before measuring; as in any wrapped text, runs of spaces and tabs separate words and are drawn as one space. -Align Justify stretches every line except the last line of a paragraph to the full width by widening the gaps between words. A justified line is drawn word by word, each word followed by an invisible space, so extracted text keeps its spaces. Without -MaxHeight, a box that would reach below the bottom of the page is an error (boxes are not split across pages). With -MaxHeight the box stops after the lines that fit and warns about the rest. Use -PassThru to get the number of lines and the height used (to place the next box); the object also carries the document. In a tagged page document the whole box is one structure element (-Role, default P). Use a base-14 font by name (-Font) or an embedded TrueType font (-FontHandle from Register-VellumPdfFont) for Unicode text. In a PDF/A document base-14 text is embedded automatically with a metric-compatible Liberation font. .PARAMETER Document A document from New-VellumPdfPageDocument. .PARAMETER Text The text to wrap. .PARAMETER Page The 1-based page number. Default 1. .PARAMETER X Left edge of the column in points. .PARAMETER Y Top of the box in points from the bottom of the page. .PARAMETER Width Column width in points. .PARAMETER FontSize Size in points (default 12). .PARAMETER Font A base-14 font name (default Helvetica). .PARAMETER FontHandle An embedded TrueType font from Register-VellumPdfFont. Overrides -Font. .PARAMETER Color Text colour: RGB triple in 0..1, hex or name. Default black. .PARAMETER Align Left (default), Center, Right or Justify. .PARAMETER Leading Distance between baselines in points. Default 1.2 times -FontSize. .PARAMETER MaxHeight Maximum height of the box in points; lines beyond it are dropped with a warning. .PARAMETER Role In a tagged document: the structure role of the box - P (default), H1..H6, Span or Caption. .PARAMETER Language BCP 47 language tag of this text, written on its structure element (tagged documents). .PARAMETER PassThru Return an object with Document, LineCount, Height and Bottom (the y of the box's lower edge) instead of the document. .EXAMPLE $doc | Add-VellumPdfTextBox $text -X 72 -Y 700 -Width 220 -Align Justify .EXAMPLE $box = $doc | Add-VellumPdfTextBox $intro -X 72 -Y 700 -Width 300 -PassThru $box.Document | Add-VellumPdfTextBox $more -X 72 -Y ($box.Bottom - 12) -Width 300 .OUTPUTS VellumPdf.Document.PdfDocument (the same instance, for chaining), or with -PassThru a summary object. #> [CmdletBinding()] [OutputType([VellumPdf.Document.PdfDocument], [pscustomobject])] param( [Parameter(Mandatory, ValueFromPipeline)] [VellumPdf.Document.PdfDocument]$Document, [Parameter(Mandatory, Position = 0)] [AllowEmptyString()] [string]$Text, [ValidateRange(1, 1000000)] [int]$Page = 1, [Parameter(Mandatory)] [double]$X, [Parameter(Mandatory)] [double]$Y, [Parameter(Mandatory)] [ValidateRange(1.0, 1000000.0)] [double]$Width, [ValidateRange(1.0, 1000.0)] [double]$FontSize = 12, [ValidateSet('Courier', 'CourierBold', 'CourierBoldOblique', 'CourierOblique', 'Helvetica', 'HelveticaBold', 'HelveticaBoldOblique', 'HelveticaOblique', 'Symbol', 'TimesBold', 'TimesBoldItalic', 'TimesItalic', 'TimesRoman', 'ZapfDingbats')] [string]$Font = 'Helvetica', [VellumPdf.Fonts.EmbeddedFontHandle]$FontHandle, [object]$Color, [ValidateSet('Left', 'Center', 'Right', 'Justify')] [string]$Align = 'Left', [ValidateRange(0.1, 10000.0)] [double]$Leading, [ValidateRange(0.1, 1000000.0)] [double]$MaxHeight, [ValidateSet('P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Span', 'Caption')] [string]$Role = 'P', [ValidateNotNullOrEmpty()] [string]$Language, [switch]$PassThru ) process { try { Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Add-VellumPdfTextBox' $Page = Resolve-VellumPdfPageNumber -Document $Document -Page $(if ($PSBoundParameters.ContainsKey('Page')) { $PSBoundParameters['Page'] } else { 1 }) -Bound $PSBoundParameters.ContainsKey('Page') -CommandName 'Add-VellumPdfTextBox' Assert-VellumPdfLanguageTag -Value $Language -CommandName 'Add-VellumPdfTextBox' Assert-VellumPdfValidText -Text $Text -CommandName 'Add-VellumPdfTextBox' $Text = Expand-VellumPdfTab -Text $Text $structType = Resolve-VellumPdfValidSetValue -Command $PSCmdlet.MyInvocation.MyCommand -Parameter 'Role' -Value $Role if (-not $FontHandle) { Assert-VellumPdfMeasurableFont -Font $Font -CommandName 'Add-VellumPdfTextBox' -Reason 'it wraps text' } Assert-VellumPdfCoordinate $X 'X' 'Add-VellumPdfTextBox'; Assert-VellumPdfCoordinate $Y 'Y' 'Add-VellumPdfTextBox'; Assert-VellumPdfCoordinate $Width 'Width' 'Add-VellumPdfTextBox' $bound = $PSBoundParameters if (-not $bound.ContainsKey('Leading')) { $Leading = 1.2 * $FontSize } $resolved = Get-VellumPdfPageFont -Document $Document -FontHandle $FontHandle -Font $Font -CommandName 'Add-VellumPdfTextBox' Write-VellumPdfMissingGlyphWarning -Handle $resolved.Handle -Text $Text -CommandName 'Add-VellumPdfTextBox' if (-not $resolved.Handle -and $Document.Conformance -eq [VellumPdf.Document.PdfConformance]::None) { Write-VellumPdfEncodingWarning -Text $Text -CommandName 'Add-VellumPdfTextBox' } $lines = [System.Collections.Generic.List[object]]::new() $lines.AddRange([object[]](Split-VellumPdfTextLine -Text $Text -Width $Width -Resolved $resolved -FontSize $FontSize)) $allowed = $lines.Count $hadWords = [bool]($lines | Where-Object { $_.Words.Count -gt 0 }) if ($bound.ContainsKey('MaxHeight')) { $allowed = [int][Math]::Floor($MaxHeight / $Leading + 1e-9) if ($lines.Count -gt $allowed) { Write-Warning "Add-VellumPdfTextBox: the text needs $($lines.Count) lines but -MaxHeight $MaxHeight fits $allowed; the rest was dropped." $lines.RemoveRange($allowed, $lines.Count - $allowed) } } $height = $lines.Count * $Leading # Without -MaxHeight the whole text is drawn, so it must fit above the bottom of the page # (the same rule Add-VellumPdfPageTable and Add-VellumPdfPageList apply). if (-not $bound.ContainsKey('MaxHeight') -and $hadWords) { $pageBottom = (Get-VellumPdfPage -Document $Document -Page $Page -CommandName 'Add-VellumPdfTextBox').MediaBox.LlY if ($Y - $height -lt $pageBottom) { throw ("Add-VellumPdfTextBox: the text is $([math]::Round($height, 1)) pt tall ($($lines.Count) lines) but only " + "$([math]::Round($Y - $pageBottom, 1)) pt are left below -Y on page $Page. Shorten the text, widen the box or use -MaxHeight to cut it off.") } } # A box without any word draws nothing: an empty tagged paragraph or heading is not valid structure. if (-not ($lines | Where-Object { $_.Words.Count -gt 0 })) { if ($hadWords) { # The -MaxHeight limit dropped every line that had words; there is nothing left to draw. if ($PassThru) { return [pscustomobject]@{ Document = $Document; LineCount = $lines.Count; Height = $height; Bottom = $Y - $height } } return $Document } if ($Document.Tagged -and $structType -match '^H[1-6]$') { throw "Add-VellumPdfTextBox: -Text has no words, so a $structType heading would be empty. Give it text or leave the box out." } Write-Verbose 'Add-VellumPdfTextBox: -Text has no words; nothing was drawn.' if ($PassThru) { return [pscustomobject]@{ Document = $Document; LineCount = $lines.Count; Height = $height; Bottom = $Y - $height } } return $Document } $canvas = Get-VellumPdfPageCanvas -Document $Document -Page $Page -CommandName 'Add-VellumPdfTextBox' $pdfPage = $Document.Pages[$Page - 1] if (-not $Document.Tagged -and ($bound.ContainsKey('Role') -or $bound.ContainsKey('Language'))) { Write-Verbose 'Add-VellumPdfTextBox: -Role/-Language only matter in a tagged document (New-VellumPdfPageDocument -Tagged).' } [void]$canvas.SaveState() try { if ($bound.ContainsKey('Color')) { $c = ConvertTo-VellumColor $Color -Document $Document -CommandName 'Add-VellumPdfTextBox' [void]$canvas.SetFillColorRgb($c[0], $c[1], $c[2]) } if ($resolved.Handle) { $Document.RegisterEmbeddedFontUsage($pdfPage, $resolved.Handle) } Invoke-VellumPdfTagged -Document $Document -Canvas $canvas -PdfPage $pdfPage -Kind Structure -StructType $structType -Language $Language -Draw { Write-VellumPdfTextLine -Canvas $canvas -Resolved $resolved -FontSize $FontSize -Lines $lines.ToArray() -X $X -Top $Y -Width $Width -Leading $Leading -Align $Align } } finally { [void]$canvas.RestoreState() } if ($PassThru) { [pscustomobject]@{ Document = $Document; LineCount = $lines.Count; Height = $height; Bottom = $Y - $height } } else { $Document } } catch { $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Add-VellumPdfTextBox')) } } } |