基于C#与iText7的多字体阿拉伯语PDF文本提取问题解决方法及示例请求
我之前在做阿拉伯语PDF文本提取的时候,也碰到过类似的问题——用iText7处理部分文件没问题,但有些文件提取出来的文本糊得没法看。结合我踩过的坑,整理了一套解决思路和代码示例,你可以参考下:
解决阿拉伯语PDF文本提取模糊问题的方法论
1. 先排查字体与编码问题
大部分模糊/乱码问题都和字体嵌入不足、阿拉伯语编码识别错误有关。iText7默认的字体支持可能覆盖不到某些特殊的阿拉伯语字体,所以我们需要手动配置字体提供者,同时处理阿拉伯语的RTL(从右到左)文本顺序问题。
示例代码:配置字体+修正RTL文本顺序
using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Listener; using iText.Layout.Font; using System.Collections.Generic; public class ArabicPdfExtractor { public string ExtractCleanArabicText(string pdfFilePath) { // 初始化字体提供者,添加阿拉伯语支持的字体(比如系统中的Arial Unicode MS,或者自定义阿拉伯字体) FontProvider fontProvider = new FontProvider(); fontProvider.AddFont("C:/Windows/Fonts/arialuni.ttf"); // 替换成你的阿拉伯字体路径 StringBuilder extractedText = new StringBuilder(); using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfFilePath))) { for (int pageNum = 1; pageNum <= pdfDoc.GetNumberOfPages(); pageNum++) { PdfPage page = pdfDoc.GetPage(pageNum); // 使用默认的位置文本提取策略,后续修正顺序 ITextExtractionStrategy strategy = new LocationTextExtractionStrategy(); string rawPageText = PdfTextExtractor.GetTextFromPage(page, strategy); // 修正阿拉伯语文本的顺序(直接提取会因RTL导致字符颠倒) string correctedText = FixArabicTextOrder(rawPageText); extractedText.AppendLine(correctedText); } } return extractedText.ToString(); } // 核心辅助方法:识别阿拉伯语字符并修正顺序 private string FixArabicTextOrder(string rawText) { char[] textChars = rawText.ToCharArray(); List<char> arabicCharBuffer = new List<char>(); StringBuilder resultBuilder = new StringBuilder(); foreach (char c in textChars) { // 匹配阿拉伯语字符的Unicode范围(含变体) bool isArabicChar = (c >= '\u0600' && c <= '\u06FF') || (c >= '\u0750' && c <= '\u077F') || (c >= '\uFB50' && c <= '\uFDFF') || (c >= '\uFE70' && c <= '\uFEFF'); if (isArabicChar) { arabicCharBuffer.Add(c); } else { // 遇到非阿拉伯字符,先反转之前缓存的阿拉伯字符并添加 if (arabicCharBuffer.Count > 0) { arabicCharBuffer.Reverse(); resultBuilder.Append(new string(arabicCharBuffer.ToArray())); arabicCharBuffer.Clear(); } resultBuilder.Append(c); } } // 处理最后一段阿拉伯字符 if (arabicCharBuffer.Count > 0) { arabicCharBuffer.Reverse(); resultBuilder.Append(new string(arabicCharBuffer.ToArray())); } return resultBuilder.ToString(); } }
2. 处理扫描件/图像类PDF
如果提取模糊的PDF是扫描件(文本以图像形式存在),iText7直接提取文本是无效的,这时候需要结合OCR工具(比如Tesseract)来识别图像中的阿拉伯语。
示例代码:iText7 + Tesseract OCR
using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Data; using iText.Kernel.Pdf.Canvas.Parser.Listener; using Tesseract; using System.Drawing; public class ArabicOcrPdfExtractor { public string ExtractTextFromScannedPdf(string pdfFilePath) { StringBuilder ocrText = new StringBuilder(); using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfFilePath))) { for (int pageNum = 1; pageNum <= pdfDoc.GetNumberOfPages(); pageNum++) { PdfPage page = pdfDoc.GetPage(pageNum); // 监听PDF中的图像渲染事件 PdfCanvasProcessor processor = new PdfCanvasProcessor(new ImageOcrListener(pageNum, ocrText)); processor.ProcessPageContent(page); } } return ocrText.ToString(); } // 自定义图像监听器,用于提取图像并OCR private class ImageOcrListener : IEventListener { private readonly int _pageNumber; private readonly StringBuilder _textBuilder; public ImageOcrListener(int pageNumber, StringBuilder textBuilder) { _pageNumber = pageNumber; _textBuilder = textBuilder; } public void EventOccurred(IEventData data, EventType type) { if (type == EventType.RENDER_IMAGE) { RenderImageData imageData = (RenderImageData)data; PdfImageXObject imageXObject = imageData.GetImage(); // 将PDF图像转换为Bitmap using (Bitmap bitmap = new Bitmap(imageXObject.GetImageAsStream())) { // 初始化Tesseract引擎,指定阿拉伯语训练数据 using (var engine = new TesseractEngine("./tessdata", "ara", EngineMode.Default)) { using (var pix = PixConverter.ToPix(bitmap)) using (var ocrPage = engine.Process(pix)) { _textBuilder.AppendLine($"--- 第 {_pageNumber} 页 OCR 结果 ---"); _textBuilder.AppendLine(ocrPage.GetText()); } } } } } public System.Collections.Generic.ICollection<EventType> GetSupportedEvents() { return new List<EventType> { EventType.RENDER_IMAGE }; } } }
3. 自定义文本提取策略处理复杂排版
对于排版特别复杂的阿拉伯语PDF(比如混合LTR和RTL文本),可以继承iText7的LocationTextExtractionStrategy,自定义文本块的排序逻辑。
示例代码:自定义RTL文本提取策略
using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Data; using iText.Kernel.Pdf.Canvas.Parser.Listener; using System.Collections.Generic; public class RtlTextExtractionStrategy : LocationTextExtractionStrategy { protected override void EventOccurred(IEventData data, EventType type) { base.EventOccurred(data, type); // 可以在这里调整文本块的排序逻辑,优先处理RTL方向的文本块 } public override string GetResultantText() { string rawText = base.GetResultantText(); // 复用之前的文本顺序修正方法 return FixArabicTextOrder(rawText); } private string FixArabicTextOrder(string rawText) { char[] textChars = rawText.ToCharArray(); List<char> arabicCharBuffer = new List<char>(); StringBuilder resultBuilder = new StringBuilder(); foreach (char c in textChars) { bool isArabicChar = (c >= '\u0600' && c <= '\u06FF') || (c >= '\u0750' && c <= '\u077F') || (c >= '\uFB50' && c <= '\uFDFF') || (c >= '\uFE70' && c <= '\uFEFF'); if (isArabicChar) { arabicCharBuffer.Add(c); } else { if (arabicCharBuffer.Count > 0) { arabicCharBuffer.Reverse(); resultBuilder.Append(new string(arabicCharBuffer.ToArray())); arabicCharBuffer.Clear(); } resultBuilder.Append(c); } } if (arabicCharBuffer.Count > 0) { arabicCharBuffer.Reverse(); resultBuilder.Append(new string(arabicCharBuffer.ToArray())); } return resultBuilder.ToString(); } } // 使用自定义策略的方法 public string ExtractComplexArabicText(string pdfFilePath) { using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfFilePath))) { StringBuilder extractedText = new StringBuilder(); for (int pageNum = 1; pageNum <= pdfDoc.GetNumberOfPages(); pageNum++) { PdfPage page = pdfDoc.GetPage(pageNum); ITextExtractionStrategy strategy = new RtlTextExtractionStrategy(); string pageText = PdfTextExtractor.GetTextFromPage(page, strategy); extractedText.AppendLine(pageText); } return extractedText.ToString(); } }
4. 先分析PDF的字体嵌入情况
如果上述方法都没用,可以先检查PDF中的字体是否嵌入——未嵌入的字体很容易导致提取模糊。用下面的代码可以快速排查:
using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas; public void AnalyzePdfFonts(string pdfFilePath) { using (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfFilePath))) { for (int pageNum = 1; pageNum <= pdfDoc.GetNumberOfPages(); pageNum++) { PdfPage page = pdfDoc.GetPage(pageNum); PdfDictionary resources = page.GetResources(); PdfDictionary fonts = resources.GetAsDictionary(PdfName.Font); if (fonts != null) { Console.WriteLine($"--- 第 {pageNum} 页字体信息 ---"); foreach (PdfName fontKey in fonts.KeySet()) { PdfDictionary fontDict = fonts.GetAsDictionary(fontKey); PdfName fontSubtype = fontDict.GetAsName(PdfName.Subtype); bool isEmbedded = fontDict.ContainsKey(PdfName.FontFile) || fontDict.ContainsKey(PdfName.FontFile2) || fontDict.ContainsKey(PdfName.FontFile3); Console.WriteLine($"字体名称: {fontKey}, 类型: {fontSubtype}, 是否嵌入: {isEmbedded}"); } } } } }
如果发现有未嵌入的字体,优先让PDF生成方嵌入对应字体;如果无法做到,就把对应的字体文件添加到iText7的FontProvider中,参考第一个示例。
内容的提问来源于stack exchange,提问作者khaloud1980




