Public/Get-VellumPdfText.ps1
|
function Get-VellumPdfText { <# .SYNOPSIS Extracts the text of an existing PDF. .DESCRIPTION EXPERIMENTAL - built on VellumPdf.Reader, which is still a Preview package. Returns the text of the whole document as one string, or of the pages given with -Page. Limits of the current reader: only simple (single-byte) fonts are decoded, so text set in composite/CID fonts such as many embedded TrueType subsets is not extracted; no spaces are inserted between text runs that the PDF positions apart; there is no OCR for scanned pages. Diagnostics about text it could not decode are written as warnings. Several files can be given (or piped); they are opened one at a time. A failure for one file (not found, unreadable, malformed, wrong password, ...) is a non-terminating error with that file as TargetObject and the next file is still processed; use -ErrorAction Stop to stop at the first one. A -Page beyond the end of a file counts as a failure for that file. Invalid parameters terminate. .PARAMETER Path The PDF file(s). Accepts wildcards. Piped strings bind here, and so do piped objects with a Path property; piped FileInfo objects (Get-ChildItem) bind to -LiteralPath through their PSPath, so names containing [ ] work. .PARAMETER LiteralPath The PDF file(s), taken literally. Aliases PSPath and FullName, so objects with either property (FileInfo, Select-Object FullName) bind by name. .PARAMETER Bytes The PDF as a byte array. .PARAMETER Page 1-based page numbers to extract. Default: all pages. .PARAMETER PerPage Return one object per page (Page, Text) instead of a single string. .PARAMETER Password Password for an encrypted PDF (a SecureString). .PARAMETER AllowReconstruction Try to recover a damaged file. .PARAMETER MaxDecodedStreamBytes Refuse to decode any stream larger than this many bytes (1 MiB to 512 MiB, the default ceiling). Lower it for untrusted files. .PARAMETER MaxDiagnostics Stop collecting reader diagnostics after this many (1 to 1000). .PARAMETER MaxFormXObjectDepth Deepest nesting of form XObjects followed while reading content (1 to 32). .PARAMETER ReconstructionBudgetMultiplier How many times the file size reconstruction may scan (1 to 8); used with -AllowReconstruction. .EXAMPLE Get-VellumPdfText ./report.pdf -Page 1 .EXAMPLE Get-VellumPdfText ./report.pdf -PerPage | Where-Object Text -match 'invoice' .OUTPUTS System.String, or PSVellumPDF.PdfPageText with -PerPage #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([string])] [OutputType('PSVellumPDF.PdfPageText')] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path')] [string[]]$Path, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')] [Alias('PSPath', 'FullName')] [string[]]$LiteralPath, [Parameter(Mandatory, ParameterSetName = 'Bytes')] [AllowEmptyCollection()] [byte[]]$Bytes, [ValidateRange(1, 1000000)] [int[]]$Page, [switch]$PerPage, [securestring]$Password, [switch]$AllowReconstruction, # Resource limits for hostile or huge files. They can only tighten the reader's # built-in ceilings (512 MiB decoded, 1000 diagnostics, 32 form levels, budget 8). [ValidateRange(1MB, 512MB)] [long]$MaxDecodedStreamBytes, [ValidateRange(1, 1000)] [int]$MaxDiagnostics, [ValidateRange(1, 32)] [int]$MaxFormXObjectDepth, [ValidateRange(1, 8)] [int]$ReconstructionBudgetMultiplier ) begin { try { $limits = @{} foreach ($n in 'MaxDecodedStreamBytes', 'MaxDiagnostics', 'MaxFormXObjectDepth', 'ReconstructionBudgetMultiplier') { if ($PSBoundParameters.ContainsKey($n)) { $limits[$n] = $PSBoundParameters[$n] } } $plain = if ($Password) { [System.Net.NetworkCredential]::new('', $Password).Password } } catch { $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Get-VellumPdfText')) } } process { try { foreach ($source in Get-VellumPdfInput -Path $Path -LiteralPath $LiteralPath -Bytes $Bytes -CommandName 'Get-VellumPdfText') { try { Read-VellumPdfInput -Source $source $reader = Open-VellumPdfReader -Limit $limits -Bytes $source.Data -Password $plain -AllowReconstruction:$AllowReconstruction ` -CommandName 'Get-VellumPdfText' -Source $source.Name try { $numbers = if ($Page) { $Page } elseif ($reader.PageCount -gt 0) { 1..$reader.PageCount } else { @() } # 1..0 would count down to page 0 foreach ($n in $numbers) { if ($n -gt $reader.PageCount) { throw "Get-VellumPdfText: '$($source.Name)' has $($reader.PageCount) page(s); page $n does not exist." } } $texts = foreach ($n in $numbers) { $result = $reader.GetPage($n - 1).ExtractText() foreach ($d in $result.Diagnostics) { Write-Warning "Get-VellumPdfText: page ${n}: $($d.Message)" } [pscustomobject]@{ PSTypeName = 'PSVellumPDF.PdfPageText'; Page = $n; Text = $result.Text } } if ($PerPage) { $texts } else { $separator = [VellumPdf.Reader.PdfTextExtractionOptions]::new().PageSeparator ($texts | ForEach-Object Text) -join $separator } } finally { $reader.Dispose() } } catch { Write-VellumPdfFileError -Cmdlet $PSCmdlet -ErrorRecord (ConvertTo-VellumPdfFileError -ErrorRecord $_ -Target $source.Name -CommandName 'Get-VellumPdfText') -ErrorActionPreferenceValue $ErrorActionPreference } finally { $source.Data = $null } # one file in memory at a time } } catch { $PSCmdlet.ThrowTerminatingError((Resolve-VellumPdfErrorRecord -ErrorRecord $_ -CommandName 'Get-VellumPdfText')) } } } |