You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带嵌入字体的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}");
    }
}

修正点说明

  1. 添加了字体流解压处理(FlateDecode),解决多数PDF压缩字体导致的文件损坏问题
  2. 根据字体流类型设置对应扩展名,避免强行将非TrueType字体存为.ttf
  3. 清理字体名称中的特殊字符,防止生成非法文件路径
  4. 增加空值判断,避免空引用异常

自动化文本校正方案(支持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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:52:32