ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

如何批量处理pdf指定位置的文本或图标内容

如何批量处理pdf指定位置的文本或图标内容 最近工作中经常需要将数百页的pdf文件内容进行批量处理比如替换右下角或右上角的文本或者图像以方便统一排版因此就做了这个pdf处理工具主要支持将右下角的文本内容提供为新内容或者将右下角的图标内容去除等。其他类型的转换工具详见之前文章。主要内容如下供参考。一、主要功能主要实现将pdf格式中指定位置内容的替换或消除 包括直接替换或消除文本内容、直接替换或消除图标内容等。具体支持1.通过OpenCV实现PDF指定位置内容的消除详见inpaint_bottom_right()2.通过OpenCV实现PDF指定位置内容的替换详见draw_replacement()3.替换前后的图标或文本对比显示详见def show_right_bottom_before_after()二、实现效果1.原始pdf截图2.处理后的pdf截图三、核心代码#!/root/anaconda3/envs/llm/bin/python # -*- coding: utf-8 -*- import argparse import os import shutil import subprocess import tempfile from pathlib import Path import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont # 获得字体库 def find_cjk_font(): candidates [ /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc, /usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc, /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc, /usr/share/fonts/truetype/wqy/wqy-microhei.ttc, C:/Windows/Fonts/msyh.ttc, C:/Windows/Fonts/simhei.ttf, /System/Library/Fonts/PingFang.ttc, ] for p in candidates: if os.path.exists(p): return p raise FileNotFoundError(No CJK font found; set --font manually.) # 去掉指定位置如右下角的水印或文字标记 def inpaint_bottom_right(img_bgr, right0.245, bottom0.105, erode9): Inpaint a fixed bottom-right strip where the Gemini Notebook mark lives. h, w img_bgr.shape[:2] x0, y0 int(w * (1 - right)), int(h * (1 - bottom)) x1, y1 w - 2, h - 2 mask np.zeros((h, w), np.uint8) mask[y0:y1, x0:x1] 255 if erode: kernel np.ones((erode, erode), np.uint8) mask cv2.erode(mask, kernel, iterations1) # Telea inpaint is stable for small logo/text marks on textured backgrounds. out cv2.inpaint(img_bgr, mask, inpaintRadius5, flagscv2.INPAINT_TELEA) return out, (x0, y0, x1, y1) # 替换指定位置如右下角的水印或文字标记 def draw_replacement(img_bgr, roi, text, font_path, font_scale0.024, pad_x_scale0.010, pad_y_scale0.010): x0, y0, x1, y1 roi h, w img_bgr.shape[:2] rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) pil Image.fromarray(rgb) draw ImageDraw.Draw(pil) # Choose readable color from local background after inpainting. patch np.array(pil.crop((x0, y0, x1, y1)).resize((64, 32))) mean patch.mean(axis(0, 1)) dark_text tuple(int(c) for c in (np.clip(mean * 0.28, 0, 255))) # e.g. on light bg light_text (248, 248, 248) fill dark_text if mean.mean() 150 else light_text # size max(18, int(h * 0.024)) size max(16, int(h * font_scale)) try: font ImageFont.truetype(font_path, sizesize, index2) except Exception: font ImageFont.truetype(font_path, sizesize) pad_x, pad_y int(w * pad_x_scale), int(h * pad_y_scale) bbox draw.textbbox((0, 0), text, fontfont) tw, th bbox[2] - bbox[0], bbox[3] - bbox[1] tx, ty x1 - pad_x - tw, y1 - pad_y - th # Slight shadow improves legibility on both dark and light blueprint pages. if mean.mean() 150: draw.text((tx 2, ty 2), text, fontfont, fill(0, 0, 0)) draw.text((tx, ty), text, fontfont, fillfill) return cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR) # 标签获取 def _label_panel(img_bgr, text): img img_bgr.copy() cv2.rectangle(img, (0, 0), (img.shape[1], 46), (0, 0, 0), -1) cv2.putText(img, text, (12, 32), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2, cv2.LINE_AA) return img # 替换前后的标签获取和对比显示 def show_right_bottom_before_after(input_pdf, preview_png, page_number1, modereplace, textAI 需求工程, dpi180, fontNone, right0.245, bottom0.105, font_scale0.024): Render one page, cut the bottom-right ROI, and save before/inpaint/after preview. if shutil.which(pdftoppm) is None: raise RuntimeError(pdftoppm not found. Install Poppler and ensure it is in PATH.) font font or find_cjk_font() preview_png str(Path(preview_png).resolve()) with tempfile.TemporaryDirectory(prefixpdf_wm_preview_) as td: prefix os.path.join(td, page) subprocess.run([ pdftoppm, -png, -r, str(dpi), -f, str(page_number), -l, str(page_number), str(Path(input_pdf).resolve()), prefix ], checkTrue) pages sorted(Path(td).glob(page-*.png)) if not pages: raise RuntimeError(pdftoppm produced no page for preview.) bgr cv2.imread(str(pages[0]), cv2.IMREAD_COLOR) if bgr is None: raise RuntimeError(fCannot read rendered preview page: {pages[0]}) h, w bgr.shape[:2] x0, y0 int(w * (1 - right)), int(h * (1 - bottom)) x1, y1 w - 2, h - 2 before bgr[y0:y1, x0:x1].copy() cleaned, roi inpaint_bottom_right(bgr, rightright, bottombottom) after_inpaint cleaned[y0:y1, x0:x1].copy() if mode replace: replaced draw_replacement(cleaned, roi, text, font, font_scalefont_scale) after replaced[y0:y1, x0:x1].copy() title_after replaced ROI else: after after_inpaint title_after removed ROI canvas np.concatenate([ _label_panel(before, original ROI), _label_panel(after_inpaint, inpainted ROI), _label_panel(after, title_after), ], axis1) info (fpage{page_number} ROI: x0{x0}, y0{y0}, x1{x1}, y1{y1}; fw{x1-x0}px, h{y1-y0}px, right{right}, bottom{bottom}, font_scale{font_scale}) # cv2.rectangle(canvas, (0, 46), (canvas.shape[1], 92), (30, 30, 30), -1) # cv2.putText(canvas, info, (12, 78), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (255, 255, 255), 1, cv2.LINE_AA) os.makedirs(os.path.dirname(preview_png) or ., exist_okTrue) if not cv2.imwrite(preview_png, canvas): raise RuntimeError(fFailed to write preview: {preview_png}) return preview_png # 处理PDF文件替换或移除右下角水印或文字标记 def process_pdf(input_pdf, output_pdf, modereplace, textAI 需求工程, dpi220, fontNone, right0.245, bottom0.105, font_scale0.024, previewTrue, preview_page1): if shutil.which(pdftoppm) is None: raise RuntimeError(pdftoppm not found. Install Poppler and ensure it is in PATH.) font font or find_cjk_font() input_pdf str(Path(input_pdf).resolve()) output_pdf str(Path(output_pdf).resolve()) pages_out [] with tempfile.TemporaryDirectory(prefixpdf_wm_) as td: prefix os.path.join(td, page) cmd [pdftoppm, -png, -r, str(dpi), input_pdf, prefix] subprocess.run(cmd, checkTrue) imgs sorted(Path(td).glob(page-*.png)) if not imgs: raise RuntimeError(pdftoppm produced no pages.) for p in imgs: bgr cv2.imread(str(p), cv2.IMREAD_COLOR) if bgr is None: raise RuntimeError(fCannot read rendered page: {p}) bgr, roi inpaint_bottom_right(bgr, rightright, bottombottom) if mode replace: bgr draw_replacement(bgr, roi, text, font, font_scalefont_scale) out_png str(p.with_name(p.stem _clean.png)) cv2.imwrite(out_png, bgr) pages_out.append(Image.open(out_png).convert(RGB)) # Preserve rendered physical size through resolution metadata. pages_out[0].save(output_pdf, save_allTrue, append_imagespages_out[1:], resolutionfloat(dpi), quality95) preview_png None if preview: preview_png str(Path(output_pdf).with_suffix(.right_bottom_preview.png)) show_right_bottom_before_after( input_pdf, preview_png, page_numberpreview_page, modemode, texttext, dpimax(120, min(220, dpi)), fontfont, rightright, bottombottom, font_scalefont_scale, ) return output_pdf, preview_png 使用示例 # 去除右下角 Gemini Notebook 水印 python pdfRemoveTxt.py input.pdf output_del.pdf --mode remove # 去除并替换为 “AI 需求分析” python pdfRemoveTxt.py C:\\temp.pdf C:\\temp-del.pdf --mode replace --text AI 需求工程 # 可调清晰度与字体 python pdfRemoveTxt.py input.pdf out.pdf --mode replace --dpi 300 --font /path/to/font.ttc def main(): ap argparse.ArgumentParser(description__doc__) ap.add_argument(input_pdf) ap.add_argument(output_pdf) ap.add_argument(--mode, choices[remove, replace], defaultreplace) ap.add_argument(--text, defaultAI 需求工程) ap.add_argument(--dpi, typeint, default220) ap.add_argument(--font, defaultNone) ap.add_argument(--right, typefloat, default0.095, helpbottom-right ROI width ratio) #其中原来right0.245 表示从页面右侧向左取 24.5% 高度,想更窄就改小 ap.add_argument(--bottom, typefloat, default0.045, helpbottom-right ROI height ratio) #其中原来bottom0.105 表示从页面底部向上取 10.5% 高度,想更矮就改小 ap.add_argument(--font-scale, typefloat, default0.015, helpreplacement font height ratio) #其中原来font-scale0.024 表示文字高度比例为0.024,想更小就改小 ap.add_argument(--preview-page, typeint, default1) ap.add_argument(--no-preview, actionstore_true, helpdo not write ROI preview png) args ap.parse_args() out, preview process_pdf( args.input_pdf, args.output_pdf, modeargs.mode, textargs.text, dpiargs.dpi, fontargs.font, rightargs.right, bottomargs.bottom, font_scaleargs.font_scale, previewnot args.no_preview, preview_pageargs.preview_page, ) print(out) if preview: print(preview) if __name__ __main__: main()
返回列表