前言
最近在批次處理大量 PDF 文件的 OCR 工作時,遇到了一個非常棘手的問題:明明原始 PDF 頁面方向正常,OCR 處理後某些頁面卻莫名其妙地旋轉了。這個問題困擾了我好一陣子,最後發現罪魁禍首竟然是 pdftk 的書籤還原功能。在此記錄整個偵錯過程,希望能幫助遇到相同問題的人。
背景
我需要對大量 PDF 進行 OCR 處理,這些 PDF 包含中英文混合內容,且原始文件帶有書籤。我的處理流程是:
- 使用
pdftk dump_data提取原始書籤 - 使用
ocrmypdf進行 OCR 處理 - 使用
ghostscript壓縮檔案 - 使用
pdftk update_info還原書籤
OCR 的參數設定如下:
ocrmypdf -l eng+chi_tra \
--force-ocr \
--optimize 3 \
--output-type pdf \
input.pdf output.pdf
問題發現
處理完成後,我發現某些 PDF 的第 4、5 頁方向不對,原本正常的頁面變成了旋轉 90 度的狀態。奇怪的是,原始檔案開啟完全正常。
我首先懷疑是 ocrmypdf 的 --rotate-pages 參數造成的問題(雖然我沒有使用這個參數),於是開始了漫長的偵錯之旅。
偵錯過程
第一步:檢查原始 PDF 的頁面資訊
使用 pdftk dump_data 檢查原始 PDF:
pdftk original.pdf dump_data | grep -E "PageMedia"
輸出結果:
PageMediaNumber: 1
PageMediaRotation: 0
...
PageMediaNumber: 4
PageMediaRotation: 270
PageMediaRect: 0 0 841.92 595.2
...
PageMediaNumber: 5
PageMediaRotation: 270
這裡發現了關鍵資訊:原始 PDF 的第 4、5 頁帶有 PageMediaRotation: 270 的 metadata。這表示這些頁面在 PDF 結構中是以旋轉 270 度的方式儲存的,PDF 閱讀器會根據這個 metadata 將頁面旋轉回正確方向顯示。
這種情況通常發生在掃描檔:第 4、5 頁可能是橫式文件,掃描時「躺著」進紙,同事後來用 PDF 編輯器(如 PDFelement)旋轉讓它顯示正常。但多數 PDF 編輯器的旋轉功能只修改 metadata,不會重新渲染像素內容——這樣處理速度快、不損失品質,但會留下 PageMediaRotation 記錄。
換句話說,原始 PDF 的實際結構是:
- 內容層:第 4、5 頁的像素是「躺著的」
- Metadata 層:
Rotation: 270告訴閱讀器「顯示時轉 270 度」
兩者配合,顯示結果正常。但這個「表面正常」在後續處理中埋下了隱患。
第二步:檢查 OCR 後的輸出
對 OCR 處理後(但尚未還原書籤)的 PDF 進行檢查:
pdftk ocr_output.pdf dump_data | grep -E "PageMediaRotation"
輸出結果:
PageMediaRotation: 0
PageMediaRotation: 0
PageMediaRotation: 0
PageMediaRotation: 0
PageMediaRotation: 0
所有頁面的 Rotation 都是 0! 這表示 ocrmypdf 在處理過程中已經將旋轉「烤入」(bake in) 頁面內容中,輸出的 PDF 不再需要 rotation metadata。
第三步:發現真正的問題
接下來檢查還原書籤後的 PDF:
pdftk final_with_bookmarks.pdf dump_data | grep -E "PageMediaRotation"
輸出結果:
PageMediaRotation: 0
PageMediaRotation: 0
PageMediaRotation: 0
PageMediaRotation: 270 # 問題出現了!
PageMediaRotation: 270
第 4、5 頁的 rotation 又變回 270 了!
這時我才恍然大悟:pdftk dump_data 輸出的不只是書籤資訊,還包含了 PageMedia 資訊(包括 rotation)。當我使用 pdftk update_info 還原書籤時,它同時也把原始的 PageMediaRotation: 270 寫回去了。
但此時 PDF 的實際內容已經是正確方向的(因為 ocrmypdf 已經處理過),再加上 rotation metadata 就導致頁面被「二次旋轉」,造成顯示異常。
解決方案
解決方法很簡單:在還原書籤時,過濾掉 PageMedia 相關的資訊,只保留 Info 和 Bookmark 資料。
原本的書籤提取方式:
pdftk input.pdf dump_data > bookmarks.txt
修改後:
pdftk input.pdf dump_data | grep -E "^(InfoBegin|InfoKey:|InfoValue:|NumberOfPages:|BookmarkBegin|BookmarkTitle:|BookmarkLevel:|BookmarkPageNumber:)" > bookmarks.txt
這個 grep 命令只保留:
InfoBegin,InfoKey:,InfoValue:- PDF 文件資訊NumberOfPages:- 頁數資訊BookmarkBegin,BookmarkTitle:,BookmarkLevel:,BookmarkPageNumber:- 書籤資訊
排除了所有 PageMedia 開頭的行,包括 PageMediaRotation。
完整的處理腳本
最終的處理腳本如下:
#!/bin/bash
# OCR 處理 PDF
# 書籤還原時過濾 PageMedia 避免旋轉問題
process_pdf() {
local input="$1"
local output="$2"
# 步驟 1: 提取書籤 (過濾 PageMedia)
pdftk "$input" dump_data | \
grep -E "^(InfoBegin|InfoKey:|InfoValue:|NumberOfPages:|BookmarkBegin|BookmarkTitle:|BookmarkLevel:|BookmarkPageNumber:)" \
> /tmp/bookmarks.txt
# 步驟 2: OCR 處理
ocrmypdf -l eng+chi_tra \
--force-ocr \
--optimize 3 \
--output-type pdf \
"$input" /tmp/ocr_output.pdf
# 步驟 3: 壓縮
gs -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.7 \
-dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile=/tmp/compressed.pdf \
/tmp/ocr_output.pdf
# 步驟 4: 還原書籤
if grep -q "BookmarkTitle" /tmp/bookmarks.txt; then
pdftk /tmp/compressed.pdf update_info /tmp/bookmarks.txt output "$output"
else
mv /tmp/compressed.pdf "$output"
fi
# 清理暫存檔
rm -f /tmp/bookmarks.txt /tmp/ocr_output.pdf /tmp/compressed.pdf
}
技術要點總結
PDF 的頁面旋轉有兩種方式:
- 實際內容旋轉(像素層面)
- Rotation metadata(告訴閱讀器如何顯示)
ocrmypdf 的 --force-ocr 會重新渲染頁面:
- 輸出的 PDF 內容已經是正確方向
- Rotation metadata 會被設為 0
pdftk dump_data 輸出包含多種資訊:
- Info(文件資訊)
- Bookmark(書籤)
- PageMedia(頁面尺寸、旋轉等)
pdftk update_info 會覆蓋所有對應的 metadata:
- 如果輸入檔包含 PageMedia 資訊,會一併寫入
- 這可能導致與實際內容不符的 rotation 被寫入
為什麼混用工具會出問題?
這個問題的根本原因是混用不同工具,而每個工具對 metadata 的處理邏輯不同:
| 工具 | 行為 |
|---|---|
| PDFelement | 旋轉時只改 metadata,保留原始像素 |
| ocrmypdf | OCR 時「烤入」旋轉,重置 metadata 為 0 |
| pdftk | 還原書籤時連 PageMediaRotation 也一起寫回 |
如果全程使用同一個工具(例如 Wondershare PDFelement 從掃描、旋轉、OCR 到壓縮),它內部會保持一致性,知道自己的 metadata 狀態,應該不會出現這種問題。
但當你像我一樣,用 PDFelement 編輯、ocrmypdf 做 OCR、pdftk 還原書籤時,每個工具各自為政,metadata 的不同步就會導致意外的結果。
結語
這個問題的根本原因是 pdftk 的 dump_data 和 update_info 功能設計上是處理所有 PDF metadata,而不只是書籤。在大多數情況下這不會造成問題,但當 PDF 經過 OCR 處理(內容被重新渲染)後,原始的 rotation metadata 就不再適用了。
這次經驗讓我深刻體會到:在處理 PDF 時,要特別注意 metadata 和實際內容之間的一致性。看似簡單的「保留書籤」操作,如果沒有仔細過濾資料,可能會帶來意想不到的副作用。
希望這篇文章能幫助到遇到類似問題的朋友。如果你也在進行 PDF OCR 處理並需要保留書籤,記得過濾掉 PageMedia 資訊!