feat: 新增 OCRmyPDF 轉換引擎 (v0.1.14)

## 新功能
- OCRmyPDF 轉換引擎:將掃描版 PDF 轉換為可搜尋 PDF
  - 支援 7 種語言:en, zh-TW, zh, ja, ko, de, fr
  - 與 PDFMathTranslate 風格一致的 UI 格式 (pdf-<lang>)
  - 自動偵測頁面方向並旋轉
  - 自動校正傾斜
  - 跳過已有文字層的頁面
  - 詳細的 5 階段處理進度輸出

## 建置
- Dockerfile:安裝 ocrmypdf 與 Tesseract OCR 語言包

## 文件
- 更新 OCR 功能文件
- 文件目錄結構改為中文名稱

## 測試
- 修復 BabelDOC 和 PDFMathTranslate 測試的 OCR mock
- 所有 345 個測試通過
This commit is contained in:
Your Name 2026-01-23 16:28:33 +08:00
parent f24eec070c
commit a06df23b1d
53 changed files with 1427 additions and 675 deletions

View file

@ -55,7 +55,7 @@ describe("PDFMathTranslate converter - Chinese translation", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
pdf2zhCalled = true;
pdf2zhArgs = args;
@ -116,7 +116,7 @@ describe("PDFMathTranslate converter - English translation", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
pdf2zhArgs = args;
const outputDirIndex = args.indexOf("-o");
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
@ -165,7 +165,7 @@ describe("PDFMathTranslate converter - Japanese translation", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
pdf2zhArgs = args;
const outputDirIndex = args.indexOf("-o");
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
@ -214,7 +214,7 @@ describe("PDFMathTranslate converter - Traditional Chinese", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
pdf2zhArgs = args;
const outputDirIndex = args.indexOf("-o");
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
@ -264,7 +264,7 @@ describe("PDFMathTranslate converter - Output structure", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
const outputDirIndex = args.indexOf("-o");
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
const outputDir = args[outputDirIndex + 1];
@ -308,7 +308,7 @@ describe("PDFMathTranslate converter - Output structure", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
const outputDirIndex = args.indexOf("-o");
if (outputDirIndex !== -1 && args[outputDirIndex + 1]) {
const outputDir = args[outputDirIndex + 1];
@ -362,7 +362,7 @@ describe("PDFMathTranslate converter - Error handling", () => {
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
const error = new Error("Translation failed") as ExecFileException;
callback(error, "", "Error: Translation service unavailable");
}
@ -381,7 +381,7 @@ describe("PDFMathTranslate converter - Error handling", () => {
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "pdf2zh") {
if (cmd === "pdftotext") { callback(null, "This is a comprehensive test PDF with more than enough text content to ensure that the OCR detection threshold of 100 characters is exceeded and the PDF is not treated as a scanned document requiring OCR processing.", ""); } else if (cmd === "pdf2zh") {
// Don't create any output files
callback(null, "Complete", "");
} else if (cmd === "tar") {