带嵌入字体的PDF文本提取乱码问题及解决方案咨询
解决PDF文本提取乱码问题(字体字符映射异常)
问题说明
在Adobe Acrobat等阅读器中可正常读取PDF文本,但用iText7提取时得到R!"ina sa$!m!js这类乱码,正确文本应为Rēķina saņēmējs。核心原因是嵌入字体的字符映射错位:PDF中存储的Unicode字符(如!)在字体渲染时被映射为目标字符(如ē),导致提取的文本与视觉显示不符。
字体提取代码修正
你提供的C#代码提取的字体无法打开,主要问题在于未处理压缩的字体流、强行统一扩展名、缺少空值判断。以下是修正后的代码:
using System; using System.IO; using iText.IO.Source; using iText.Kernel.Font; using iText.Kernel.Pdf; class PdfFontExtractor { private string pdfPath; private string outputDir; public PdfFontExtractor(string pdfPath, string outputDir) { this.pdfPath = pdfPath; this.outputDir = outputDir; if (!Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); } } public void ExtractFonts() { using (PdfReader reader = new PdfReader(pdfPath)) using (PdfDocument pdfDoc = new PdfDocument(reader)) { for (int i = 1; i <= pdfDoc.GetNumberOfPages(); i++) { ExtractFontsFromPage(pdfDoc.GetPage(i)); } } } private void ExtractFontsFromPage(PdfPage page) { PdfDictionary fontResources = page.GetResources().GetResource(PdfName.Font); if (fontResources == null) return; foreach (PdfName fontName in fontResources.KeySet()) { PdfDictionary fontDict = fontResources.GetAsDictionary(fontName); if (fontDict == null) continue; ExtractFont(fontDict); } } private void ExtractFont(PdfDictionary fontDict) { PdfDictionary fontDescriptor = fontDict.GetAsDictionary(PdfName.FontDescriptor); if (fontDescriptor == null) return; PdfStream fontFileStream = null; string fontExt = ""; // 根据字体流类型设置正确扩展名 if (fontDescriptor.ContainsKey(PdfName.FontFile)) { fontFileStream = fontDescriptor.GetAsStream(PdfName.FontFile); fontExt = ".pfa"; // Type1字体 } else if (fontDescriptor.ContainsKey(PdfName.FontFile2)) { fontFileStream = fontDescriptor.GetAsStream(PdfName.FontFile2); fontExt = ".ttf"; // TrueType字体 } else if (fontDescriptor.ContainsKey(PdfName.FontFile3)) { fontFileStream = fontDescriptor.GetAsStream(PdfName.FontFile3); PdfSubtype subtype = fontFileStream.GetAsName(PdfName.Subtype); fontExt = subtype.Equals(PdfName.Type1C) ? ".otf" : ".ttf"; } if (fontFileStream == null) return; // 解压压缩的字体流 if (fontFileStream.IsFlateDecode()) { fontFileStream.FlateDecode(); } byte[] fontData = fontFileStream.GetBytes(); if (fontData.Length <= 1000) { Console.WriteLine($"Skipped invalid font: {fontDict.GetAsName(PdfName.BaseFont).GetValue()}"); return; } // 清理字体名称中的特殊字符,避免路径非法 string baseFontName = fontDict.GetAsName(PdfName.BaseFont).GetValue() .Replace("/", "").Replace("#", "").Replace("+", "_"); string fontPath = Path.Combine(outputDir, $"{baseFontName}{fontExt}"); File.WriteAllBytes(fontPath, fontData); Console.WriteLine($"Extracted font: {fontPath}"); } }
修正点说明
- 添加了字体流解压处理(
FlateDecode),解决多数PDF压缩字体导致的文件损坏问题 - 根据字体流类型设置对应扩展名,避免强行将非TrueType字体存为
.ttf - 清理字体名称中的特殊字符,防止生成非法文件路径
- 增加空值判断,避免空引用异常
自动化文本校正方案(支持C#/Python)
按照你提出的思路,分四步实现批量校正:
步骤1:提取字体并生成字形图片
C#(SkiaSharp实现)
using SkiaSharp; using System.IO; void GenerateGlyphImages(string fontPath, string outputDir) { if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir); using var typeface = SKTypeface.FromFile(fontPath); using var paint = new SKPaint { Typeface = typeface, TextSize = 100, IsAntialias = true, Color = SKColors.Black }; foreach (var glyphId in typeface.GetGlyphs()) { if (glyphId == 0) continue; var unicode = typeface.GetGlyphUnicode(glyphId); if (unicode == 0) continue; // 测量字形尺寸 SKRect bounds = new(); paint.MeasureText(new string((char)unicode, 1), ref bounds); int width = (int)Math.Ceiling(bounds.Width + 10); int height = (int)Math.Ceiling(bounds.Height + 10); // 绘制并保存字形图片 using var bitmap = new SKBitmap(width, height); using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.White); canvas.DrawText(new string((char)unicode, 1), 5, height - 5, paint); string imgPath = Path.Combine(outputDir, $"{unicode}_{glyphId}.png"); using var stream = File.OpenWrite(imgPath); bitmap.Encode(stream, SKEncodedImageFormat.Png, 100); } }
Python(Pillow+fonttools实现)
from PIL import Image, ImageDraw, ImageFont from fontTools.ttLib import TTFont import os def generate_glyph_images(font_path, output_dir): if not os.path.exists(output_dir): os.makedirs(output_dir) font = ImageFont.truetype(font_path, 100) tt_font = TTFont(font_path) cmap = tt_font.getBestCmap() for glyph_id, unicode_val in cmap.items(): if unicode_val == 0: continue char = chr(unicode_val) bbox = font.getbbox(char) width = bbox[2] - bbox[0] + 10 height = bbox[3] - bbox[1] + 10 img = Image.new('RGB', (width, height), color='white') draw = ImageDraw.Draw(img) draw.text((5, height - 5), char, font=font, fill='black') img_path = os.path.join(output_dir, f"{unicode_val}_{glyph_id}.png") img.save(img_path)
步骤2:OCR识别建立字符映射
用Tesseract OCR识别字形图片,建立PDF提取字符与实际显示字符的映射关系:
import pytesseract from PIL import Image import os def build_char_mapping(glyph_img_dir): mapping = {} for img_name in os.listdir(glyph_img_dir): if not img_name.endswith('.png'): continue unicode_val = int(img_name.split('_')[0]) img_path = os.path.join(glyph_img_dir, img_name) # 需对应目标语言包,示例为拉脱维亚语 actual_char = pytesseract.image_to_string(Image.open(img_path), lang='lav').strip() if actual_char: mapping[chr(unicode_val)] = actual_char return mapping
步骤3:校正提取的文本
用映射表替换乱码字符:
def correct_extracted_text(raw_text, char_mapping): return ''.join([char_mapping.get(c, c) for c in raw_text])
批量处理建议
- 对相同字体的PDF复用映射表,减少重复处理
- OCR语言包需匹配PDF文本的语言类型
- 处理缺失字形时,可保留原字符或做特殊标记
内容的提问来源于stack exchange,提问作者HellOfACode
相关产品推荐
相关产品推荐

