You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于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

火山引擎 最新活动