# 将数据表 JPEG 包装成极简单页 A4 PDF (DCTDecode 直嵌, 字节级 xref 偏移) # 用法: powershell -NoProfile -ExecutionPolicy Bypass -File tools\gen-pdf.ps1 # 说明: 中文内容以图片形式嵌入, 规避 PDF 中文字体嵌入问题; 结构最小化, 主流阅读器可打开。 param( [string]$Jpg, [string]$Pdf ) Add-Type -AssemblyName System.Drawing $img = [System.Drawing.Image]::FromFile($Jpg) $w = $img.Width $h = $img.Height $img.Dispose() $jpgBytes = [System.IO.File]::ReadAllBytes($Jpg) $chunks = New-Object System.Collections.Generic.List[byte[]] $offsets = New-Object System.Collections.Generic.List[int] $pos = 0 function Add-Ascii([string]$t) { $b = [System.Text.Encoding]::ASCII.GetBytes($t) $chunks.Add($b) $script:pos += $b.Length } function Mark-Obj() { $offsets.Add($script:pos) } $imgLen = $jpgBytes.Length + 1 $content = 'q 595.28 0 0 841.89 0 0 cm /Im0 Do Q' $contentLen = $content.Length + 1 Add-Ascii "%PDF-1.4`n" Mark-Obj; Add-Ascii "1 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj`n" Mark-Obj; Add-Ascii "2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj`n" Mark-Obj; Add-Ascii "3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595.28 841.89] /Resources << /XObject << /Im0 4 0 R >> >> /Contents 5 0 R >>endobj`n" Mark-Obj; Add-Ascii "4 0 obj<< /Type /XObject /Subtype /Image /Width $w /Height $h /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length $imgLen >>stream`n" $chunks.Add($jpgBytes); $pos += $jpgBytes.Length Add-Ascii "`nendstream`nendobj`n" Mark-Obj; Add-Ascii "5 0 obj<< /Length $contentLen >>stream`n" Add-Ascii "$content`nendstream`nendobj`n" $xrefOffset = $pos $sb = New-Object System.Text.StringBuilder [void]$sb.Append("xref`n0 6`n0000000000 65535 f `n") foreach ($off in $offsets) { [void]$sb.Append(("{0:0000000000} {1:00000} n `n" -f $off, 0)) } Add-Ascii $sb.ToString() Add-Ascii ("trailer`n<< /Size 6 /Root 1 0 R >>`nstartxref`n" + $xrefOffset + "`n%%EOF`n") $fs = [System.IO.File]::Create($Pdf) foreach ($b in $chunks) { $fs.Write($b, 0, $b.Length) } $fs.Close() # ---- 自检: startxref 指向 xref, 且每个对象偏移处是 "N 0 obj" ---- $check = [System.IO.File]::ReadAllBytes($Pdf) $text = [System.Text.Encoding]::ASCII.GetString($check) $m = [regex]::Match($text, 'startxref\s+(\d+)') if (-not $m.Success) { Write-Output ("FAIL: 无 startxref - " + $Pdf); return } $xref = [int64]$m.Groups[1].Value $ok = ($text.Substring($xref, 4) -eq 'xref') $lines = $text.Substring($xref + 4).Split("`n") # xref 段: 行0='xref', 行1='0 6', 行2=第0号自由条目, 行3..7=对象1..5 for ($i = 3; $i -lt 8; $i++) { $entry = $lines[$i] $objNum = $i - 2 if ($entry -match '^(\d{10}) 00000 n') { $off2 = [int64]$entry.Substring(0, 10) if ($text.Substring($off2, 10) -notmatch ('^' + $objNum + ' 0 obj')) { $ok = $false } } else { $ok = $false } } if ($ok) { Write-Output ("OK: " + $Pdf + " (" + (($check.Length / 1KB).ToString('0') + " KB)")) } else { Write-Output ("FAIL: xref 校验失败 - " + $Pdf) }