From the Wikipedia article you’ve linked:
The presentation forms are present only for compatibility with older standards, and are not currently needed for coding text.
In other words, you should just use the generic form (i.e. the 0600–06FF range codepoints), instead of the presentation forms of the FE70–FEFF codepoint range. Note that your example string, تشخيصك, consists of 06xx characters only, at least as given to me by the browser.
If you have a legacy source of FE70–FEFF characters, don’t try to fix their order, but just drop the presentation information by converting the string(s) into the canonical form. E.g.
String s = "\uFE97\uFE98\uFE97\uFE96\uFE98";
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("", "\"", "\"")));
which prints
ﺗﺘﺗﺖﺘ
تتتتت
"\u062A\u062A\u062A\u062A\u062A"
The example’s source string has intentionally messed up presentation form characters, to show, how just getting a string with no presentation information, only consisting of repetitions of the generic U+062A character, solves the issue. In other words, the generic string is printed correctly.
This handling is done by the font, a feature known as glyph shaping. We can see it in action, e.g. with
Font font = new Font("DejaVu Sans", 0, 48); // or Droid Sans Arabic
FontRenderContext frc = new FontRenderContext(null, true, false);
String s = "\u062A\u062A\u062A";
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();
Try it online!
تتت
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
Note that the actual glyph numbers are entirely up to the specific font. E.g., some fonts map the middle ت character to the same glyph as the last one—a pure stylistic choice.
The program is only meant to demonstrate that the same codepoint (in this example, char
is sufficient) may get mapped to different font specific glyphs.