如何从java中的字符串中获得正确的阿拉伯字符(正确的形式)



我试图用java计算阿拉伯字符串中字符的宽度,但有时无法获得正确的字符。如果你不知道阿拉伯字符可以根据它们在单词中的位置进行连接,并且每个变体都有不同的十六进制代码:https://en.wikipedia.org/wiki/Arabic_script_in_Unicode#:~:text=外部%20链接-,上下文%20表单,-%5编辑%5D

发生的事情是,假设我在这个字符中读到:ت(十六进制值FE95(。现在,根据它在单词中的位置,它可以采取不同的形式(请参阅上下文链接,这是表中的第三个字母(。当它出现在一个单词的开头时,它应该看起来像:ﺗ(十六进制代码FE97(。在我读入的字符串中,它显示为ﺗ(十六进制代码FE97(,但当我使用string.charAt(index(读取它时,我得到了ت(十六进制值FE95(。有人知道用正确的十六进制值读取正确字符的方法吗?

编辑:这里有一个字符串作为参考。中间的字母都没有以正确的形式读入:

  • 第一个字母是我上面描述的那个
  • 第二个字母是这个字母是一个字母Ş(十六进制FEB5(。在单词中,它表示为ﺸ(十六进制FEB8(,但当它通过字符串charAt读取时,它会保持值Ş(十六进制FEM5(
  • 第三个字母是خ(十六进制FEA5(。在单词中,它表示为ﺨ(十六进制FEA8(,但当它通过字符串charAt读取时,它保持值خ(十六进制FEA5(

等等。。。任何帮助都将不胜感激!

从您链接的维基百科文章:

演示表单仅用于与旧标准兼容,目前不需要用于编码文本。

换句话说,您应该只使用通用形式(即0600–06FF范围代码点(,而不是FE70–FEFF代码点范围的表示形式。请注意,您的示例字符串"仅由06xx个字符组成,至少在浏览器提供给我的情况下是这样。

如果你有一个FE70–FEFF字符的遗留源,不要试图修复它们的顺序,而是通过将字符串转换为规范形式来删除表示信息。例如

String s = "uFE97uFE98uFE97uFE96uFE98";
System.out.println(s);
s = Normalizer.normalize(s, Normalizer.Form.NFKD);
System.out.println(s);
System.out.println(s.chars().mapToObj(i -> String.format("\u%04X", i))
.collect(Collectors.joining("", """, """)));

它打印

ﺗﺘﺗﺖﺘ
تتتتت
"u062Au062Au062Au062Au062A"

该示例的源字符串故意弄乱了表示形式的字符,以表明获得一个没有表示信息的字符串,只由通用U+0062A字符的重复组成,是如何解决问题的。换句话说,通用字符串打印正确。

这种处理是由字体完成的,这一功能被称为字形塑造。我们可以看到它的作用,例如

Font font = new Font("DejaVu Sans", 0, 48); // or Droid Sans Arabic
FontRenderContext frc = new FontRenderContext(null, true, false);
String s = "u062Au062Au062A";
System.out.println(s);
if(font.canDisplayUpTo(s) >= 0) {
System.out.println("can't display string");
System.exit(0);
}
GlyphVector g = font.layoutGlyphVector(frc,
s.toCharArray(), 0, s.length(), Font.LAYOUT_RIGHT_TO_LEFT);
Rectangle r = g.getPixelBounds(frc, 0, 0);
System.out.println("Total width " + r.width);
for(int i = 0, n = g.getNumGlyphs(); i < n; i++) {
int chPos = g.getGlyphCharIndex(i);
System.out.printf("%2d (U+%04X) glyph code %4d, width %.0f%n",
chPos,(int)s.charAt(chPos),g.getGlyphCode(i),g.getGlyphMetrics(i).getAdvance());
}
BufferedImage bi = new BufferedImage(r.width, r.height, BufferedImage.TYPE_BYTE_BINARY);
Graphics2D gfx = bi.createGraphics();
//System.out.println(r);
gfx.drawGlyphVector(g, -r.x, -r.y);
gfx.dispose();
for(int line = 0, nLines = r.height; line < nLines; line++) {
for(int ch = 0, nChars = r.width; ch < nChars; ch++) {
System.out.print((bi.getRGB(ch, line) & 0xff) > 0? 'X': ' ');
}
System.out.println();
}
System.out.println();

在线试用!

تتت
Total width 70
2 (U+062A) glyph code 5261, width 47
1 (U+062A) glyph code 5263, width 14
0 (U+062A) glyph code 5262, width 13
XX    XX      XX    XX 
XXXX  XXXX    XXXX  XXXX
XXXX  XXXX    XXXX  XXXX
XXXX  XXXX    XXXX  XXXX

XX    XX                                                
XXXX  XXXX                                               
XXXX  XXXX                                               
XXXX  XXXX           XXXX                                
XXXX                                
XXXX                             XXXXX          XXXX          XXXX   
XXXX                             XXXXX          XXXX          XXXX   
XXXXX                             XXXXX          XXXX          XXXX   
XXXX                              XXXXX          XXXX          XXXX   
XXXX                              XXXXX          XXXX          XXXX   
XXXX                             XXXXXX          XXXX          XXXX   
XXXXX                          XXXXXXXX          XXXX          XXXX   
XXXXX                        XXXXXXXXXXX        XXXXXX        XXXXX   
XXXXXX                    XXXXXXXXXXXXX        XXXXXX        XXXXX   
XXXXXXXX              XXXXXXXXXXX  XXXXXX    XXXXXXXXXX    XXXXXX    
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX    
XXXXXXXXXXXXXXXXXXXXXXXXXXXX      XXXXXXXXXXXXXXXXXXXXXXXXXXXX     
XXXXXXXXXXXXXXXXXXXXXXX          XXXXXXXXXXXX  XXXXXXXXXXXX      
XXXXXXXXXXXXXXXX                XXXXXXXXX    XXXXXXXXXX       

请注意,实际的字形编号完全取决于特定的字体。例如,有些字体将中间的字符映射到与最后一个字符相同的字形——这是一种纯粹的风格选择。

该程序只是为了证明相同的代码点(在本例中,char就足够了(可以映射到不同的字体特定的字形。

最新更新