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

@ -67,7 +67,10 @@ describe("BabelDOC converter - Chinese translation", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
if (cmd === "pdftotext") {
// 返回超過 100 個字元,表示 PDF 不需要 OCR
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 === "babeldoc") {
babeldocCalled = true;
babeldocArgs = args;
@ -131,7 +134,9 @@ describe("BabelDOC converter - English translation", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
babeldocArgs = args;
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {
@ -181,7 +186,9 @@ describe("BabelDOC converter - Traditional Chinese", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
babeldocArgs = args;
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {
@ -231,7 +238,9 @@ describe("BabelDOC converter - Output structure", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {
const outputPath = args[outputIndex + 1];
@ -267,7 +276,9 @@ describe("BabelDOC converter - Output structure", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {
const outputPath = args[outputIndex + 1];
@ -317,7 +328,9 @@ describe("BabelDOC converter - Error handling", () => {
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
const error = new Error("Translation failed") as ExecFileException;
error.code = 1;
callback(error, "", "babeldoc: Translation error");
@ -337,7 +350,9 @@ describe("BabelDOC converter - Error handling", () => {
_args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
// Don't create output file, simulating failed translation
callback(null, "Complete", "");
}
@ -376,7 +391,9 @@ describe("BabelDOC converter - Markdown output format", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
babeldocArgs = args;
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {
@ -428,7 +445,9 @@ describe("BabelDOC converter - HTML output format", () => {
args: string[],
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
) => {
if (cmd === "babeldoc") {
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 === "babeldoc") {
babeldocArgs = args;
const outputIndex = args.indexOf("-o");
if (outputIndex !== -1 && args[outputIndex + 1]) {