阿拉伯语、希伯来语、波斯语这些语言是从右往左书写的,而里面混杂的阿拉伯数字、英文等又是正常从左往右读的,所以也叫双向文字。图片OCR和翻译软件碰到这类语言,就需要额外处理一串问题:界面怎么显示、OCR输出的字符顺序怎么还原、单词怎么按正确顺序拼成行、导出PDF时又怎么让文字按阅读顺序排列。

这篇讲一下ImageTrans里对应的处理。

界面显示和文字渲染

JavaFX的Node有一个nodeOrientation属性。把它设成RIGHT_TO_LEFT之后,JavaFX会自动处理文本对齐、光标移动方向、标点符号落在哪一侧这些细节,不需要手工干预。

ImageTrans在显示双向文本的控件上都设了这个属性,包括原文框、译文框,以及文字排版引擎。

除了显示,还有一个从右往左阅读的项目设置,它影响的是排序

阿拉伯语的段落,第一个词在最右边。如果按我们习惯的方式,用X坐标从小到大排序,整段的阅读顺序会完全反过来。所以这个开关会传给排序和合并相关的多个模块:

  • 文本框排序
  • 行内框排序
  • 分镜检测
  • 框合并

OCR的字符处理

ImageTrans用的是PaddleOCR系列模型(通过RapidOCR集成)。这类模型识别阿拉伯语时,输出的是视觉顺序——它是按图像像素从左往右扫的。

视觉顺序意味着:字母的实际书写顺序是反的,但数字因为视觉上本来就从左往右排,顺序反而是对的。

所以要做一次反转,但保留数字段的转换:

  • 纯数字串:原样返回
  • 其它:先整串反转,再把每一段连续的数字倒回来

举例来说,م٢٠٢٦要还原成٢٠٢٦م

同时还要做一次括号镜像,把左右括号换过来。

但其它OCR不需要这个操作。

单词合并成行:从右往左

OCR出来的是一堆单词框,要先合并成行,再合并成段。

合并的做法是从一个种子框开始,不断往某个方向吃相邻的框。往哪个方向先吃,阿语要反过来:

growRightFirstRound = True              ' 默认先往右
If right2left And horizontal Then
    growRightFirstRound = False         ' 阿语先往左
End If

先往左把同一行的词收进来;这一轮收不动了,再换个方向试一次;两个方向都收不动,这一行就结束了。

但只用合并方向还不够,还有一类混排的情况要单独处理。

阿拉伯语的行从右往左合并,这个顺序对阿拉伯词是正确的,但夹在行里的拉丁文必须保持从左往右——否则一个hello world合并完就变成了world hello

所以当第一个框的末尾拖着两个字符以上的非阿拉伯字母,而第二个框也不是阿拉伯词时,就认为这两个框同属一个LTR文本段,需要把顺序对调。实现上就是把第一个框的尾段摘下来,接到第二个框后面:

source1 = source1 去掉尾巴  +  " "  +  source2  +  尾巴
source2 = ""

比如第一个框是world、第二个框是hello,处理完就还原成了hello world

输出PDF:shape和reverse

这是最麻烦的一步。

PDF本身不做复杂文本排版。PDFBoxshowText拿到一个字符串,会逐个Unicode码点去查字体的cmap,查到字形就画出来。它不管阿拉伯语的上下文变形——同一个字母在词首、词中、词尾、单独出现时形状不同,这件事PDFBox不会帮你做。

所以ImageTrans自己做了两步。

第一步是shape:把每个字母替换成它在当前位置对应的变形。Unicode里有一块「阿拉伯语表述形式」区(U+FE70–FEFF)专门放这些变形字形。比如ت单独出现是FE95,在词首是FE97,词中是FE98,词尾是FE96。判断依据是前后字母能不能连写——像ادر这些字母只能跟前面连,后面连不上。

第二步是reverse:上一步的产物是一串「字形码」而不是「字符」。字符的顺序是逻辑序,而字形码的顺序必须是视觉序,也就是画在纸上的顺序。所以要把整串倒过来,这样从左到右画出来才是对的。

反转有个坑:数字不能跟着反转。所以这里不是简单的一步反转,而是按空格切词、逐词处理:

  • 纯数字词:还原回去
  • 阿拉伯词:保留,但把词内部的连续数字段倒回来
  • 其它词(拉丁词等):攒成一组,整组反转

最后再镜像一次括号、清掉方向控制字符,才交给PDFBox画出去。

对字体的要求

这套做法依赖字体自带U+FE80–FEFC的表述形式字形。Arial、Tahoma、Segoe UI这些常见系统字体都有(历史原因,为了兼容老软件)。但也有一些字体没有,比如Windows自带的Arabic Typesetting,用它导出会直接报错。

可搜索性

有个意外的好处:PDF里存的明明是表述形式字形,但复制出来的却是正常的阿拉伯文。

原因是提取文本时会对每个词做一次NFKC归一化,而NFKC恰好会把U+FE70–FEFF兼容分解回基础字母。这是通用行为,不是某个库特有的,所以其它PDF阅读器也一样。导出的PDF既能正常显示,也能复制和搜索。

已知限制

  • 字体必须包含表述形式字形(U+FE80–FEFC),否则导出会失败。如果你的字体踩到了这个问题,换一个覆盖完整的字体即可。
  • 如果原文带变音符(harakat),它们会一起进入文字层。搜索时可能需要带上变音符才能精确匹配,具体行为取决于阅读器的实现。
  • 连字(比如لا)目前是用两个连着的字形拼出来的,视觉效果接近,提取时也能正确还原成两个字母。

相关阅读:如何确定图片中文字的阅读顺序