简单双层PDF制作

用途

将扫描版PDF通过百度OCR识别后,生成包含原始图像和可搜索文本的双层PDF文档。

优势

  • 相比ABBYY FineReader OCR Editor,百度OCR在中文识别准确率方面表现更优
  • 提供每月1000次免费调用额度,适合小规模使用免费

用法

百度OCR控制台:https://console.bce.baidu.com/ai-engine/ocr/overview/index

将获得的参数填入常量,根据图片分辨率指定OUTDPI​参数。

代码

import base64
import requests
import fitz  # PyMuPDF
from aip import AipOcr  # 百度官方 Python SDK

OUTDPI = 260

# ———— 1. 配置百度 OCR 凭证 ————
APP_ID = 'APP_ID '
API_KEY = 'API_KEY '
SECRET_KEY = 'SECRET_KEY '

# 使用 SDK 获取 access_token
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# SDK 内部也可直接调用 client.get_access_token(),此处演示手动获取:
auth_url = 'https://aip.baidubce.com/oauth/2.0/token'
auth_params = {
    'grant_type': 'client_credentials',
    'client_id': API_KEY,
    'client_secret': SECRET_KEY
}
auth_resp = requests.get(auth_url, params=auth_params)
auth_resp.raise_for_status()
access_token = auth_resp.json()['access_token']

# ———— 2. 打开源 PDF 和准备输出 PDF ————
src_path = '有机化学结构与功能 第八版戴立信 席振峰_401.pdf'
out_path = 'output_searchable.pdf'
doc = fitz.open(src_path)
out_doc = fitz.open()  # 新建一个空 PDF

# ———— 检查和准备中文字体 ————
def get_chinese_font():
    """获取可用的中文字体"""
    
    # 优先级顺序的中文字体列表
    font_candidates = [
        "china-s",      # PyMuPDF 内置简体中文字体
        "china-t",      # PyMuPDF 内置繁体中文字体  
        "cjk",          # CJK 字体
        "china-ss",     # 简体中文宋体
        "china-ts",     # 繁体中文宋体
    ]
    
    # 创建临时页面测试字体
    test_doc = fitz.open()
    test_page = test_doc.new_page()
    
    for font in font_candidates:
        try:
            test_page.insert_text((10, 50), "测试中文", fontname=font, fontsize=12)
            test_doc.close()
            print(f"使用中文字体: {font}")
            return font, None
        except:
            continue
    
    test_doc.close()
    print("警告:未找到合适的中文字体,可能出现乱码")
    return None, None

chinese_font, font_file_path = get_chinese_font()

# ———— 3. 通用文字识别(标准含位置版)接口地址 ————
ocr_url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general'  # :contentReference[oaicite:0]{index=0}

for page_index in range(len(doc)):
    # if page_index<=250:
    #     continue
    page = doc.load_page(page_index)
    
    # 获取页面尺寸
    rect = page.rect
    
    max_width = 2000
    # PyMuPDF中,像素 = 尺寸(点) * DPI / 72
    # 所以 DPI = 像素 * 72 / 尺寸(点)
    max_dpi = int(max_width * 72 / rect.height)
    
    # 使用计算出的DPI值,但不低于150dpi以保证质量
    dpi = min(300, max(0, max_dpi))
    pix = page.get_pixmap(dpi=dpi)
    
    # 首先尝试PNG格式
    img_bytes = pix.tobytes(output="png")
    with open("tmp.png", "wb") as f:
        f.write(img_bytes)
        

    # 如果图像大小超过K8M,尝试JPEG格式并调整质量
    max_size = 8 * 1024 * 1024  # 8MB
    if len(img_bytes) > max_size:
        # 尝试JPEG格式,从高质量开始逐步降低
        for quality in [95, 85, 75, 65, 55, 45, 35]:
            img_bytes = pix.tobytes(output="jpeg", jpg_quality=quality)
            if len(img_bytes) <= max_size:
                break
    
    img_b64 = base64.b64encode(img_bytes).decode()
    print(len(img_b64),max_size)

    # 调用通用文字识别(标准含位置版)
    params = {
        'image': img_b64,
        # 可选增强识别:
        # 'language_type': 'CHN_ENG',
        # 'detect_direction': 'true',
        # 'vertexes_location': 'true',
        # 'probability': 'true'
    }
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    resp = requests.post(
        f"{ocr_url}?access_token={access_token}",
        data=params,
        headers=headers
    )
    resp.raise_for_status()
    result = resp.json()

    # 在新文档中创建同等大小的页面
    rect = page.rect
    pix_write = page.get_pixmap(dpi=OUTDPI)
    new_page = out_doc.new_page(width=rect.width, height=rect.height)
    new_page.insert_image(rect, stream=pix_write.tobytes())

    img_width = pix.width
    img_height = pix.height
    
    # PDF页面尺寸(点)
    pdf_width = rect.width
    pdf_height = rect.height
    
    # 缩放比例:PDF坐标 = 图片坐标 * 缩放比例
    scale_x = pdf_width / img_width
    scale_y = pdf_height / img_height
    
    print(f"页面 {page_index + 1}: 图片尺寸({img_width}x{img_height}), PDF尺寸({pdf_width:.1f}x{pdf_height:.1f}), 缩放比例({scale_x:.3f}, {scale_y:.3f})")
    
    # 统计文字处理情况
    total_words = len(result.get('words_result', []))
    print(f"页面 {page_index + 1}: 检测到 {total_words} 个文字块")
    
    for item in result.get('words_result', []):
        txt = item['words']
        loc = item['location']
        left, top, w, h = loc['left'], loc['top'], loc['width'], loc['height']
        
        # 1. 将图片坐标转换为PDF坐标(考虑缩放)
        left_pdf = left * scale_x
        top_pdf = top * scale_y
        w_pdf = w * scale_x
        h_pdf = h * scale_y
        
        y_pdf = (top_pdf + h_pdf)
        
        if chinese_font:
            new_page.insert_text(
                (left_pdf, y_pdf),
                txt,
                fontname=chinese_font,
                fontsize=max(6, h_pdf * 0.8),  # 字体大小稍小于文字框高度,避免过大
                render_mode=3,    # 填充+描边
                overlay=True
            )
        elif font_file_path:
            new_page.insert_text(
                (left_pdf, y_pdf),
                txt,
                fontfile=font_file_path,
                fontsize=max(6, h_pdf * 0.8),
                render_mode=3,
                overlay=True
            )
        else:
            print(f"警告:使用默认字体,可能出现乱码: {txt[:10]}...")
            new_page.insert_text(
                (left_pdf, y_pdf),
                txt,
                fontsize=max(6, h_pdf * 0.8),
                render_mode=3,
                overlay=True
            )
    # break
    try:
        if page_index % 10 == 0:
            out_doc.save(out_path, garbage=4, deflate=True)
    except Exception as e:
        print(f"保存失败:{e}",page_index)

out_doc.save(out_path, garbage=4, deflate=True)
print(f"已生成可搜索的双层 PDF:{out_path}")