Code
#!/usr/bin/env pwsh
# (c) 2020-26 T.Hass
#
# USAGE: pdfprint.ps1 [-OutputDir <path>] [-Printer <name>] <file>
#
# OPTIONS:
# <file> RAW or Postscript file to print
# -OutputDir Output directory for PDFs (default: Documents)
# -Printer Printer to use (default: PDF)
#
# PURPOSE: Pre-process printer output from DOSBox/PC-GEOS and convert to PDF
# Extracts document title from PS input, converts MacRoman to CP1252,
# handles custom page sizes, outputs to user-specified folder.
param(
[Parameter(Mandatory=$false)]
[string]$OutputDir = "$env:USERPROFILE\Documents",
[Parameter(Mandatory=$false)]
[string]$Printer = "PDF",
[Parameter(Mandatory=$true, Position=0)]
[string]$InputFile
)
$ErrorActionPreference = "Stop"
# Ghostscript location - adjust for your installation
$GS_PATH = "gs\gswin32c.exe"
# Logging - only saved to file on error
$LogMessages = @()
function Write-Log {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogMessages += "$timestamp $Message"
}
# MacRoman to CP1252 mapping (GEOS umlauts)
$MacRomanToCp1252 = @{
[byte]0x80 = [byte]0xC4 # Ä
[byte]0x8A = [byte]0xE4 # ä
[byte]0x85 = [byte]0xD6 # Ö
[byte]0x9A = [byte]0xF6 # ö
[byte]0x86 = [byte]0xDC # Ü
[byte]0x9F = [byte]0xFC # ü
[byte]0xA7 = [byte]0xDF # ß
}
function ConvertFrom-MacRomanToCp1252 {
param([byte[]]$Bytes)
$result = [System.Collections.Generic.List[byte]]::new()
foreach ($b in $Bytes) {
$key = [byte]$b
if ($MacRomanToCp1252.ContainsKey($key)) {
$result.Add($MacRomanToCp1252[$key])
} else {
$result.Add($b)
}
}
return [System.Text.Encoding]::GetEncoding(1252).GetString($result.ToArray())
}
function Get-DocumentTitle {
param([string]$File)
$fileBytes = [System.IO.File]::ReadAllBytes($File)
# Find %%Title: in raw bytes
$titleMarker = [System.Text.Encoding]::ASCII.GetBytes("%%Title:")
$idx = -1
for ($i = 0; $i -lt $fileBytes.Length - $titleMarker.Length; $i++) {
$found = $true
for ($j = 0; $j -lt $titleMarker.Length; $j++) {
if ($fileBytes[$i + $j] -ne $titleMarker[$j]) { $found = $false; break }
}
if ($found) { $idx = $i; break }
}
if ($idx -ge 0) {
$start = $idx + $titleMarker.Length
# Skip whitespace
while ($start -lt $fileBytes.Length -and ($fileBytes[$start] -eq 0x20 -or $fileBytes[$start] -eq 0x09)) { $start++ }
# Find end of line
$end = $start
while ($end -lt $fileBytes.Length -and $fileBytes[$end] -ne 0x0D -and $fileBytes[$end] -ne 0x0A) { $end++ }
$titleBytes = $fileBytes[$start..($end-1)]
$title = ConvertFrom-MacRomanToCp1252 -Bytes $titleBytes
$title = $title.Trim() -replace '\s', '_'
return $title
}
return "unknown"
}
function Get-PageSize {
param([string]$File)
$bytes = [System.IO.File]::ReadAllBytes($File)
$content = [System.Text.Encoding]::ASCII.GetString($bytes)
# Find PageSetup section and look for SPS (SetPageSize)
if ($content -match '(?s)%%BeginPageSetup.*?SPS\s+(\d+)\s+(\d+).*?%%EndPageSetup') {
return @{
Width = [int]$Matches[1]
Height = [int]$Matches[2]
}
}
# Fallback: use BoundingBox
if ($content -match '%%BoundingBox:\s+\d+\s+\d+\s+(\d+)\s+(\d+)') {
return @{
Width = [int]$Matches[1]
Height = [int]$Matches[2]
}
}
# Default to A4 (595x842 points at 72 DPI)
return @{ Width = 595; Height = 842 }
}
function Get-DocumentType {
param([string]$File)
# Check for %!PS or %%PostScript magic bytes
$bytes = [System.IO.File]::ReadAllBytes($File)
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0x25 -and $bytes[1] -eq 0x21) {
return "application/postscript"
}
return "application/octet-stream"
}
function New-PdfFromPostScript {
param(
[string]$InputFile,
[string]$OutputPdf,
[int]$PageWidth,
[int]$PageHeight
)
# Find Ghostscript - check specified location first, then PATH
if (Test-Path $GS_PATH) {
$gs = $GS_PATH
} else {
$gs = Get-Command gswin64c -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $gs) {
$gs = Get-Command gs -ErrorAction SilentlyContinue | Select-Object -First 1
}
if (-not $gs) {
throw "Ghostscript not found. Install or update `$GS_PATH in script."
}
$gs = $gs.Source
}
$gsArgs = @(
"-dNOPAUSE",
"-dBATCH",
"-sDEVICE=pdfwrite",
"-dCompatibilityLevel=1.1",
"-r72",
"-g${PageWidth}x${PageHeight}",
"-sOutputFile=$OutputPdf",
"$InputFile"
)
Write-Log "Running: $gs $($gsArgs -join ' ')"
& $gs @gsArgs
if ($LASTEXITCODE -ne 0) {
throw "Ghostscript failed with exit code $LASTEXITCODE"
}
}
# Main execution
try {
if (-not (Test-Path $InputFile)) {
throw "Input file '$InputFile' not found"
}
Write-Log "Using printer '$Printer' to print '$InputFile'"
# Extract page size
$pageSize = Get-PageSize -File $InputFile
Write-Log "Page size: $($pageSize.Width)x$($pageSize.Height)"
# Create output directory if needed
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
# Validate PostScript format
$docType = Get-DocumentType -File $InputFile
if ($docType -ne "application/postscript") {
Write-Log "Warning: Expected application/postscript, got $docType"
}
# Get document title for filename
$docTitle = Get-DocumentTitle -File $InputFile
Write-Log "Document title: $docTitle"
# Build output PDF path
$outputPdf = Join-Path $OutputDir "$docTitle.pdf"
# Convert to PDF (72 DPI resolution)
New-PdfFromPostScript -InputFile $InputFile `
-OutputPdf $outputPdf `
-PageWidth $pageSize.Width `
-PageHeight $pageSize.Height
Write-Log "PDF created: $outputPdf"
Write-Host "PDF saved to: $outputPdf"
exit 0
} catch {
$LogFile = if ($env:TEMP) { "$env:TEMP\pigeos-print.log" } else { "/tmp/pigeos-print.log" }
$LogMessages += "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Error: $_"
$LogMessages | Out-File -FilePath $LogFile
Write-Error $_
exit 1
}
Display More
Ich habe echt keine Ahnung mehr, warum wir in dieser kleinen Runde das Rad zweimal erfinden müssen oder was an dem GEOS4Windows-Paket nicht paßt, aber wenn's Dich glücklich macht kannst Du Dich gerne inspirieren lassen.
Thomas