使用 iText7 阅读希伯来语 PDF 会出现乱码

Reading Hebrew PDF with iText 7 get Gibrish

我正在尝试阅读希伯来语 PDF,但我收到的是 Gibrish。

我正在使用@mkl 一年前给我的代码,当时我遇到了类似的问题,如下所述,但不幸的是,它不起作用。

for (int i = 1; i <= pdfDocument.GetNumberOfPages(); i++) {
  PdfPage page1 = pdfDocument.GetPage(i);
  PdfDictionary fontResources = page1.GetResources().GetResource(PdfName.Font);
  foreach (PdfObject font in fontResources.Values(true))
  {
    if (font is PdfDictionary fontDict)
        fontDict.Put(PdfName.Encoding, PdfName.IdentityH);
  }
  // get page size
  Rectangle pageSize = pdfDocument.GetPage(i).GetMediaBox();
  float pageHeight = pageSize.GetHeight();
  float pageWidth = pageSize.GetWidth();

  // set location
  Rectangle rect = new Rectangle(0, 0, pageWidth, pageHeight);
  TextRegionEventFilter regionFilter = new TextRegionEventFilter(rect);
                    
  ITextExtractionStrategy strategy = new FilteredTextEventListener(new LocationTextExtractionStrategy(), regionFilter);
  inputStr = PdfTextExtractor.GetTextFromPage(page1, strategy);

  // rest of code...
}

输出 (inputStr) 完全是乱码:

�����������\n��������\n������ �����������������\n���\n�����������\n����������������\n��������������������������� ������\n���\n���������\n��������������������������\n����������������������\n�������

由于PDF包含敏感数据,我真的不能公开分享它...

感谢您的帮助, 亚尼夫

此问题的原因无效 ToUnicode PDF 中所有字体的 CMap:这些 CMap 可能对其他用途有效,但在 的上下文中ToUnicode CMaps PDF规范明确限制了此类CMap中可能出现的数据。

不过,可以通过一个小补丁启用 iText 来理解这一点。

问题

文档中的 ToUnicode CMap 无效,特别是因为它们使用 begincidrange ... endcidrange 部分用于映射字符代码,而不是 beginbfrange ... endbfrangebeginbfchar .. . endbfchar 规范要求的部分。

偶然 iText 处理 ~cidrange 部分 ToUnicode CMaps 就像 ~bfrange部分。 (好吧,不是真的偶然,而是因为用于处理 ToUnicode CMap 的 class 扩展了一个用于处理任意 CMap 的抽象基础 class。)

不幸的是,~cidrange 范围具有整数目标起始值(例如 <0003> <0003> 32),而 ~bfrange 范围在 ToUnicode 中,CMap 必须具有十六进制字符串目标起始值(例如 <0000><005E><0020>)。结果,在 iText 中使用 ~cidrange 范围解析这些 ToUnicode CMap 失败并出现异常,留下没有条目的映射。结果是文档中的 none 个字符代码可以映射到任何合理的字符,并且所有文本都被提取为替换字符 ('�')。

解决方法

解决此问题的一个明显方法是使处理范围的代码也能处理整数目标起始值,而不仅仅是十六进制字符串值。

应用该修复后,可以正确提取大部分字符。您只需处理错误的 RTL 脚本文本顺序。

(我只测试了 Java 版本,但 .Net 版本应该也可以。)

Java

com.itextpdf.io.font.cmap.CMapToUnicode class(com.itextpdf.io 工件)中调用以下方法来添加从范围派生的映射:

@Override
void addChar(String mark, CMapObject code) {
    if (mark.length() == 1) {
        char[] dest = createCharsFromDoubleBytes((byte[]) code.getValue());
        byteMappings.put((int) mark.charAt(0), dest);
    } else if (mark.length() == 2) {
        char[] dest = createCharsFromDoubleBytes((byte[]) code.getValue());
        byteMappings.put((mark.charAt(0) << 8) + mark.charAt(1), dest);
    } else {
        Logger logger = LoggerFactory.getLogger(CMapToUnicode.class);
        logger.warn(LogMessageConstant.TOUNICODE_CMAP_MORE_THAN_2_BYTES_NOT_SUPPORTED);
    }
}

这里 code.getValue() 没有任何测试被强制转换为 byte[],因为这就是十六进制字符串在这里的表示方式。相反,整数值将表示为 Integer 个实例。因此,

char[] dest = createCharsFromDoubleBytes((byte[]) code.getValue());

行必须替换为

char[] dest = (code.getValue() instanceof Integer) ? TextUtil.convertFromUtf32((Integer)code.getValue()) : createCharsFromDoubleBytes((byte[]) code.getValue());

行。

.Net

iText.IO.Font.Cmap.CMapToUnicode class(itext.io.netstandard 程序集)中调用以下方法来添加从范围派生的映射:

internal override void AddChar(String mark, CMapObject code) {
    if (mark.Length == 1) {
        char[] dest = CreateCharsFromDoubleBytes((byte[])code.GetValue());
        byteMappings.Put((int)mark[0], dest);
    }
    else {
        if (mark.Length == 2) {
            char[] dest = CreateCharsFromDoubleBytes((byte[])code.GetValue());
            byteMappings.Put((mark[0] << 8) + mark[1], dest);
        }
        else {
            ILog logger = LogManager.GetLogger(typeof(iText.IO.Font.Cmap.CMapToUnicode));
            logger.Warn(iText.IO.LogMessageConstant.TOUNICODE_CMAP_MORE_THAN_2_BYTES_NOT_SUPPORTED);
        }
    }
}

这里 code.GetValue() 没有任何测试被强制转换为 byte[] 因为这就是十六进制字符串在这里的表示方式。相反,整数值将表示为 int 个实例。 因此,与 Java 的情况一样,替换

应该就足够了
char[] dest = CreateCharsFromDoubleBytes((byte[])code.GetValue());

char[] dest = (code.GetValue() is int) ? TextUtil.ConvertFromUtf32((int)code.GetValue()) : CreateCharsFromDoubleBytes((byte[])code.GetValue());

行。