使用PyPDF处理PDF时,如何将特殊编码文本转为可读格式?
解决方案:将PDF中十六进制字形编码转为可读文本
问题根源
你看到的<0004>这类十六进制值是PDF字体的字形索引(GID),不是通用字符编码(如UTF-8),它和字体内置的字符映射表(ToUnicode CMap/标准编码)直接绑定,所以单纯用latin-1/UTF-8解码无效。
方案1:用PyMuPDF(fitz)直接提取可读文本
PyMuPDF会自动处理字体的字符映射,即使PDF没有ToUnicode表,也会尝试通过字形名或标准编码映射到Unicode字符,提取结果可直接传入你的__process_raw_data函数:
import fitz def extract_processable_text(pdf_path): doc = fitz.open(pdf_path) raw_text = [] for page in doc: # 按文本块提取,保留基础排版结构 text_dict = page.get_text("dict") for block in text_dict["blocks"]: if block["type"] == 0: for line in block["lines"]: line_text = "".join([span["text"] for span in line["spans"]]) raw_text.append(line_text) doc.close() return "\n".join(raw_text) # 使用示例 readable_text = extract_processable_text("source.pdf") processed_text = __process_raw_data(readable_text)
方案2:手动解码字形索引(针对PyPDF操作场景)
如果必须用PyPDF直接处理原始PDF内容,可通过字体的映射表将十六进制字形转为Unicode:
from pypdf import PdfReader from pypdf.generic import NameObject from pypdf._encodings import WINANSI_ENCODING, MACROMAN_ENCODING from pypdf._cmap import CMap def get_font_char_map(font): # 优先获取ToUnicode映射表 if "/ToUnicode" in font: cmap_stream = font["/ToUnicode"].get_object() return CMap.from_stream(cmap_stream) # 无ToUnicode时用标准编码映射 encoding = font.get("/Encoding", NameObject("/WinAnsiEncoding")) if encoding == NameObject("/WinAnsiEncoding"): return {v: k for k, v in WINANSI_ENCODING.items()} elif encoding == NameObject("/MacRomanEncoding"): return {v: k for k, v in MACROMAN_ENCODING.items()} return None def decode_glyph_hex(hex_str, char_map): # 解析<0004>格式的字形编码 glyphs = [hex_str[i:i+4] for i in range(1, len(hex_str)-1, 4)] chars = [] for g in glyphs: gid = int(g, 16) chars.append(char_map.get(gid, f"<{g}>")) return "".join(chars) # 使用示例 reader = PdfReader("source.pdf") page = reader.pages[0] # 获取页面字体及映射 font_res = page["/Resources"]["/Font"] for font_name in font_res: font = font_res[font_name].get_object() char_map = get_font_char_map(font) if char_map: # 假设你从页面内容中拿到了<0004>这类字符串 raw_glyph = "<0074006800690073>" # 对应"this"的字形编码 readable = decode_glyph_hex(raw_glyph, char_map) processed = __process_raw_data(readable)
方案3:替换字体并保留格式
如果接受替换字体,用pikepdf替换时可最大程度保留原有排版、图片:
import pikepdf def replace_pdf_font(source_path, output_path, font_path): with pikepdf.open(source_path) as doc: # 加载目标字体(建议用覆盖字形全的字体,如Noto Sans) new_font = pikepdf.Font.open(font_path) # 遍历所有页面替换字体 for page in doc.pages: if "/Font" in page.Resources: fonts = page.Resources["/Font"] for font_key in fonts: fonts[font_key] = new_font doc.save(output_path) # 使用示例:替换为系统中的Noto Sans字体 replace_pdf_font("source.pdf", "processed.pdf", "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf")
注意:替换字体时需确保新字体包含原PDF中所有字形,否则会出现方块替代字符。
内容的提问来源于stack exchange,提问作者TeemSy
相关产品推荐
相关产品推荐

