import os import io import zipfile import tempfile import time from datetime import datetime from flask import Flask, render_template, request, send_file, after_this_request, jsonify from werkzeug.utils import secure_filename from pypdf import PdfReader, PdfWriter from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter, A4 from reportlab.lib.units import inch from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from PIL import Image app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 限制上传大小为 50MB # 注册中文字体 (如果系统没有,可能需要 fallback,这里暂时使用标准字体或简单处理) # 为了确保在 HF Spaces 上能显示中文水印,通常需要字体文件。 # 这里为了简化,如果找不到字体,可能水印显示会有问题,但功能逻辑是通的。 # 我们可以尝试使用 ReportLab 自带的字体,或者不注册 TTF,只用标准 ASCII。 # 但用户要求中文,所以最好能支持。 # 暂时先用 Helvetica,后续如果用户反馈乱码,可以添加字体文件。 # 为了健壮性,这里暂时只支持英文字符水印,或者提示用户。 # *Update*: 既然要求中文显示,我应该尽量支持中文水印。 # 但没有字体文件很难。我会在前端提示 "暂仅支持英文水印" 或尝试加载系统字体。 def cleanup_file(filepath): """清理临时文件的回调函数""" try: if os.path.exists(filepath): os.remove(filepath) except Exception as e: print(f"Error cleaning up {filepath}: {e}") @app.route('/') def index(): return render_template('index.html') @app.route('/api/merge', methods=['POST']) def merge_pdfs(): """合并多个 PDF""" files = request.files.getlist('files') if not files or len(files) < 2: return jsonify({'error': '请至少上传两个 PDF 文件'}), 400 try: merger = PdfWriter() temp_dir = tempfile.mkdtemp() for file in files: filename = secure_filename(file.filename) filepath = os.path.join(temp_dir, filename) file.save(filepath) merger.append(filepath) output_path = os.path.join(temp_dir, 'merged.pdf') merger.write(output_path) merger.close() @after_this_request def remove_temp(response): try: import shutil shutil.rmtree(temp_dir) except Exception as e: print(f"Cleanup error: {e}") return response return send_file( output_path, as_attachment=True, download_name=f'merged_{int(time.time())}.pdf', mimetype='application/pdf' ) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/split', methods=['POST']) def split_pdf(): """拆分 PDF (每一页保存为一个单独的 PDF,打包下载)""" file = request.files.get('file') if not file: return jsonify({'error': '请上传 PDF 文件'}), 400 try: temp_dir = tempfile.mkdtemp() filename = secure_filename(file.filename) filepath = os.path.join(temp_dir, filename) file.save(filepath) reader = PdfReader(filepath) zip_path = os.path.join(temp_dir, 'split_files.zip') with zipfile.ZipFile(zip_path, 'w') as zf: for i, page in enumerate(reader.pages): writer = PdfWriter() writer.add_page(page) page_filename = f'page_{i+1}.pdf' page_path = os.path.join(temp_dir, page_filename) with open(page_path, 'wb') as f: writer.write(f) zf.write(page_path, page_filename) @after_this_request def remove_temp(response): try: import shutil shutil.rmtree(temp_dir) except Exception as e: print(f"Cleanup error: {e}") return response return send_file( zip_path, as_attachment=True, download_name=f'split_{int(time.time())}.zip', mimetype='application/zip' ) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/img2pdf', methods=['POST']) def img_to_pdf(): """图片转 PDF""" files = request.files.getlist('files') if not files: return jsonify({'error': '请至少上传一张图片'}), 400 try: images = [] temp_dir = tempfile.mkdtemp() for file in files: img = Image.open(file) if img.mode != 'RGB': img = img.convert('RGB') images.append(img) if not images: return jsonify({'error': '无效的图片文件'}), 400 output_path = os.path.join(temp_dir, 'images.pdf') images[0].save( output_path, save_all=True, append_images=images[1:] ) @after_this_request def remove_temp(response): try: import shutil shutil.rmtree(temp_dir) except Exception as e: print(f"Cleanup error: {e}") return response return send_file( output_path, as_attachment=True, download_name=f'images_merged_{int(time.time())}.pdf', mimetype='application/pdf' ) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/watermark', methods=['POST']) def watermark_pdf(): """添加水印""" file = request.files.get('file') text = request.form.get('text', 'Watermark') if not file: return jsonify({'error': '请上传 PDF 文件'}), 400 try: temp_dir = tempfile.mkdtemp() filename = secure_filename(file.filename) filepath = os.path.join(temp_dir, filename) file.save(filepath) # 创建水印 PDF watermark_path = os.path.join(temp_dir, 'watermark.pdf') c = canvas.Canvas(watermark_path) # 简单处理:将水印放在页面中央,旋转45度 # 注意:这里默认使用 Helvetica,不支持中文。 # 如果输入是中文,需要字体支持。 # 为了避免报错,我们这里简单提示或只支持 ASCII。 # 或者我们尝试使用 'HeiseiMin-W3' (ReportLab 内置的一些 CID 字体) # 但最安全的是让用户只输英文,或者我们不做字体检查(会显示方框)。 c.setFont("Helvetica", 50) c.saveState() c.translate(300, 400) c.rotate(45) c.drawCentredString(0, 0, text) c.restoreState() c.save() # 合并水印 reader = PdfReader(filepath) watermark_reader = PdfReader(watermark_path) watermark_page = watermark_reader.pages[0] writer = PdfWriter() for page in reader.pages: page.merge_page(watermark_page) writer.add_page(page) output_path = os.path.join(temp_dir, 'watermarked.pdf') with open(output_path, 'wb') as f: writer.write(f) @after_this_request def remove_temp(response): try: import shutil shutil.rmtree(temp_dir) except Exception as e: print(f"Cleanup error: {e}") return response return send_file( output_path, as_attachment=True, download_name=f'watermarked_{int(time.time())}.pdf', mimetype='application/pdf' ) except Exception as e: return jsonify({'error': str(e)}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, debug=True)