1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 4 package com.ibm.icu.text; 5 6 import com.ibm.icu.lang.UCharacter; 7 8 /** 9 * Bidi Layout Transformation Engine. 10 * 11 * @author Lina Kemmel 12 * 13 * @stable ICU 58 14 */ 15 public class BidiTransform 16 { 17 /** 18 * <code>{@link Order}</code> indicates the order of text. 19 * <p> 20 * This bidi transformation engine supports all possible combinations (4 in 21 * total) of input and output text order: 22 * <ul> 23 * <li>{logical input, visual output}: unless the output direction is RTL, 24 * this corresponds to a normal operation of the Bidi algorithm as 25 * described in the Unicode Technical Report and implemented by 26 * <code>{@link Bidi}</code> when the reordering mode is set to 27 * <code>Bidi#REORDER_DEFAULT</code>. Visual RTL mode is not supported by 28 * <code>{@link Bidi}</code> and is accomplished through reversing a visual 29 * LTR string,</li> 30 * <li>{visual input, logical output}: unless the input direction is RTL, 31 * this corresponds to an "inverse bidi algorithm" in 32 * <code>{@link Bidi}</code> with the reordering mode set to 33 * <code>{@link Bidi#REORDER_INVERSE_LIKE_DIRECT}</code>. Visual RTL mode 34 * is not not supported by <code>{@link Bidi}</code> and is accomplished 35 * through reversing a visual LTR string,</li> 36 * <li>{logical input, logical output}: if the input and output base 37 * directions mismatch, this corresponds to the <code>{@link Bidi}</code> 38 * implementation with the reordering mode set to 39 * <code>{@link Bidi#REORDER_RUNS_ONLY}</code>; and if the input and output 40 * base directions are identical, the transformation engine will only 41 * handle character mirroring and Arabic shaping operations without 42 * reordering,</li> 43 * <li>{visual input, visual output}: this reordering mode is not supported 44 * by the <code>{@link Bidi}</code> engine; it implies character mirroring, 45 * Arabic shaping, and - if the input/output base directions mismatch - 46 * string reverse operations.</li> 47 * </ul> 48 * 49 * @see Bidi#setInverse 50 * @see Bidi#setReorderingMode 51 * @see Bidi#REORDER_DEFAULT 52 * @see Bidi#REORDER_INVERSE_LIKE_DIRECT 53 * @see Bidi#REORDER_RUNS_ONLY 54 * @stable ICU 58 55 */ 56 public enum Order { 57 /** 58 * Constant indicating a logical order. 59 * 60 * @stable ICU 58 61 */ 62 LOGICAL, 63 /** 64 * Constant indicating a visual order. 65 * 66 * @stable ICU 58 67 */ 68 VISUAL; 69 } 70 71 /** 72 * <code>{@link Mirroring}</code> indicates whether or not characters with 73 * the "mirrored" property in RTL runs should be replaced with their 74 * mirror-image counterparts. 75 * 76 * @see Bidi#DO_MIRRORING 77 * @see Bidi#setReorderingOptions 78 * @see Bidi#writeReordered 79 * @see Bidi#writeReverse 80 * @stable ICU 58 81 */ 82 public enum Mirroring { 83 /** 84 * Constant indicating that character mirroring should not be 85 * performed. 86 * 87 * @stable ICU 58 88 */ 89 OFF, 90 /** 91 * Constant indicating that character mirroring should be performed. 92 * <p> 93 * This corresponds to calling <code>{@link Bidi#writeReordered}</code> 94 * or <code>{@link Bidi#writeReverse}</code> with the 95 * <code>{@link Bidi#DO_MIRRORING}</code> option bit set. 96 * 97 * @stable ICU 58 98 */ 99 ON; 100 } 101 102 private Bidi bidi; 103 private String text; 104 private int reorderingOptions; 105 private int shapingOptions; 106 107 /** 108 * <code>{@link BidiTransform}</code> default constructor. 109 * 110 * @stable ICU 58 111 */ BidiTransform()112 public BidiTransform() 113 { 114 } 115 116 /** 117 * Performs transformation of text from the bidi layout defined by the 118 * input ordering scheme to the bidi layout defined by the output ordering 119 * scheme, and applies character mirroring and Arabic shaping operations. 120 * <p> 121 * In terms of <code>{@link Bidi}</code> class, such a transformation 122 * implies: 123 * <ul> 124 * <li>calling <code>{@link Bidi#setReorderingMode}</code> as needed (when 125 * the reordering mode is other than normal),</li> 126 * <li>calling <code>{@link Bidi#setInverse}</code> as needed (when text 127 * should be transformed from a visual to a logical form),</li> 128 * <li>resolving embedding levels of each character in the input text by 129 * calling <code>{@link Bidi#setPara}</code>,</li> 130 * <li>reordering the characters based on the computed embedding levels, 131 * also performing character mirroring as needed, and streaming the result 132 * to the output, by calling <code>{@link Bidi#writeReordered}</code>,</li> 133 * <li>performing Arabic digit and letter shaping on the output text by 134 * calling <code>{@link ArabicShaping#shape}</code>.</li> 135 * </ul><p> 136 * An "ordering scheme" encompasses the base direction and the order of 137 * text, and these characteristics must be defined by the caller for both 138 * input and output explicitly .<p> 139 * There are 36 possible combinations of {input, output} ordering schemes, 140 * which are partially supported by <code>{@link Bidi}</code> already. 141 * Examples of the currently supported combinations: 142 * <ul> 143 * <li>{Logical LTR, Visual LTR}: this is equivalent to calling 144 * <code>{@link Bidi#setPara}</code> with 145 * <code>paraLevel == {@link Bidi#LTR}</code>,</li> 146 * <li>{Logical RTL, Visual LTR}: this is equivalent to calling 147 * <code>{@link Bidi#setPara}</code> with 148 * <code>paraLevel == {@link Bidi#RTL}</code>,</li> 149 * <li>{Logical Default ("Auto") LTR, Visual LTR}: this is equivalent to 150 * calling <code>{@link Bidi#setPara}</code> with 151 * <code>paraLevel == {@link Bidi#LEVEL_DEFAULT_LTR}</code>,</li> 152 * <li>{Logical Default ("Auto") RTL, Visual LTR}: this is equivalent to 153 * calling <code>{@link Bidi#setPara}</code> with 154 * <code>paraLevel == {@link Bidi#LEVEL_DEFAULT_RTL}</code>,</li> 155 * <li>{Visual LTR, Logical LTR}: this is equivalent to 156 * calling <code>{@link Bidi#setInverse}(true)</code> and then 157 * <code>{@link Bidi#setPara}</code> with 158 * <code>paraLevel == {@link Bidi#LTR}</code>,</li> 159 * <li>{Visual LTR, Logical RTL}: this is equivalent to calling 160 * <code>{@link Bidi#setInverse}(true)</code> and then 161 * <code>{@link Bidi#setPara}</code> with 162 * <code>paraLevel == {@link Bidi#RTL}</code>.</li> 163 * </ul><p> 164 * All combinations that involve the Visual RTL scheme are unsupported by 165 * <code>{@link Bidi}</code>, for instance: 166 * <ul> 167 * <li>{Logical LTR, Visual RTL},</li> 168 * <li>{Visual RTL, Logical RTL}.</li> 169 * </ul> 170 * <p>Example of usage of the transformation engine:</p> 171 * <pre> 172 * BidiTransform bidiTransform = new BidiTransform(); 173 * String in = "abc \u06f0123"; // "abc \\u06f0123" 174 * // Run a transformation. 175 * String out = bidiTransform.transform(in, 176 * Bidi.LTR, Order.VISUAL, 177 * Bidi.RTL, Order.LOGICAL, 178 * Mirroring.OFF, 179 * ArabicShaping.DIGITS_AN2EN | ArabicShaping.DIGIT_TYPE_AN_EXTENDED); 180 * // Result: "0123 abc". 181 * // Do something with out. 182 * out = out.replace('0', '4'); 183 * // Result: "4123 abc". 184 * // Run a reverse transformation. 185 * String inNew = bidiTransform.transform(out, 186 * Bidi.RTL, Order.LOGICAL, 187 * Bidi.LTR, Order.VISUAL, 188 * Mirroring.OFF, 189 * ArabicShaping.DIGITS_EN2AN | ArabicShaping.DIGIT_TYPE_AN_EXTENDED); 190 * // Result: "abc \\u06f4\\u06f1\\u06f2\\u06f3" 191 * </pre> 192 * 193 * @param text An input character sequence that the Bidi layout 194 * transformations will be performed on. 195 * @param inParaLevel A base embedding level of the input as defined in 196 * <code>{@link Bidi#setPara(String, byte, byte[])}</code> 197 * documentation for the <code>paraLevel</code> parameter. 198 * @param inOrder An order of the input, which can be one of the 199 * <code>{@link Order}</code> values. 200 * @param outParaLevel A base embedding level of the output as defined in 201 * <code>{@link Bidi#setPara(String, byte, byte[])}</code> 202 * documentation for the <code>paraLevel</code> parameter. 203 * @param outOrder An order of the output, which can be one of the 204 * <code>{@link Order}</code> values. 205 * @param doMirroring Indicates whether or not to perform character 206 * mirroring, and can accept one of the 207 * <code>{@link Mirroring}</code> values. 208 * @param shapingOptions Arabic digit and letter shaping options defined in 209 * the <code>{@link ArabicShaping}</code> documentation. 210 * <p><strong>Note:</strong> Direction indicator options are 211 * computed by the transformation engine based on the effective 212 * ordering schemes, so user-defined direction indicators will be 213 * ignored. 214 * @return The output string, which is the result of the layout 215 * transformation. 216 * @throws IllegalArgumentException if <code>text</code>, 217 * <code>inOrder</code>, <code>outOrder</code>, or 218 * <code>doMirroring</code> parameter is <code>null</code>. 219 * @stable ICU 58 220 */ transform(CharSequence text, byte inParaLevel, Order inOrder, byte outParaLevel, Order outOrder, Mirroring doMirroring, int shapingOptions)221 public String transform(CharSequence text, 222 byte inParaLevel, Order inOrder, 223 byte outParaLevel, Order outOrder, 224 Mirroring doMirroring, int shapingOptions) 225 { 226 if (text == null || inOrder == null || outOrder == null || doMirroring == null) { 227 throw new IllegalArgumentException(); 228 } 229 this.text = text.toString(); 230 231 byte[] levels = {inParaLevel, outParaLevel}; 232 resolveBaseDirection(levels); 233 234 ReorderingScheme currentScheme = findMatchingScheme(levels[0], inOrder, 235 levels[1], outOrder); 236 if (currentScheme != null) { 237 this.bidi = new Bidi(); 238 this.reorderingOptions = Mirroring.ON.equals(doMirroring) 239 ? Bidi.DO_MIRRORING : Bidi.REORDER_DEFAULT; 240 241 /* Ignore TEXT_DIRECTION_* flags, as we apply our own depending on the 242 text scheme at the time shaping is invoked. */ 243 this.shapingOptions = shapingOptions & ~ArabicShaping.TEXT_DIRECTION_MASK; 244 currentScheme.doTransform(this); 245 } 246 return this.text; 247 } 248 249 /** 250 * When the direction option is 251 * <code>{@link Bidi#LEVEL_DEFAULT_LTR}</code> or 252 * <code>{@link Bidi#LEVEL_DEFAULT_RTL}</code>, resolves the base 253 * direction according to that of the first strong directional character in 254 * the text. 255 * 256 * @param levels Byte array, where levels[0] is an input level levels[1] is 257 * an output level. Resolved levels override these. 258 */ resolveBaseDirection(byte[] levels)259 private void resolveBaseDirection(byte[] levels) { 260 if (Bidi.IsDefaultLevel(levels[0])) { 261 byte level = Bidi.getBaseDirection(text); 262 levels[0] = level != Bidi.NEUTRAL ? level 263 : levels[0] == Bidi.LEVEL_DEFAULT_RTL ? Bidi.RTL : Bidi.LTR; 264 } else { 265 levels[0] &= 1; 266 } 267 if (Bidi.IsDefaultLevel(levels[1])) { 268 levels[1] = levels[0]; 269 } else { 270 levels[1] &= 1; 271 } 272 } 273 274 /** 275 * Finds a valid <code>{@link ReorderingScheme}</code> matching the 276 * caller-defined scheme. 277 * 278 * @return A valid <code>ReorderingScheme</code> object or null 279 */ findMatchingScheme(byte inLevel, Order inOrder, byte outLevel, Order outOrder)280 private ReorderingScheme findMatchingScheme(byte inLevel, Order inOrder, 281 byte outLevel, Order outOrder) { 282 for (ReorderingScheme scheme : ReorderingScheme.values()) { 283 if (scheme.matches(inLevel, inOrder, outLevel, outOrder)) { 284 return scheme; 285 } 286 } 287 return null; 288 } 289 290 /** 291 * Performs bidi resolution of text. 292 * 293 * @param level Base embedding level 294 * @param options Reordering options 295 */ resolve(byte level, int options)296 private void resolve(byte level, int options) { 297 bidi.setInverse((options & Bidi.REORDER_INVERSE_LIKE_DIRECT) != 0); 298 bidi.setReorderingMode(options); 299 bidi.setPara(text, level, null); 300 } 301 302 /** 303 * Performs basic reordering of text (Logical LTR or RTL to Visual LTR). 304 * 305 */ reorder()306 private void reorder() { 307 text = bidi.writeReordered(reorderingOptions); 308 reorderingOptions = Bidi.REORDER_DEFAULT; 309 } 310 311 /** 312 * Performs string reverse. 313 */ reverse()314 private void reverse() { 315 text = Bidi.writeReverse(text, Bidi.OPTION_DEFAULT); 316 } 317 318 /** 319 * Performs character mirroring without reordering. When this method is 320 * called, <code>{@link #text}</code> should be in a Logical form. 321 */ mirror()322 private void mirror() { 323 if ((reorderingOptions & Bidi.DO_MIRRORING) == 0) { 324 return; 325 } 326 StringBuffer sb = new StringBuffer(text); 327 byte[] levels = bidi.getLevels(); 328 for (int i = 0, n = levels.length; i < n;) { 329 int ch = UTF16.charAt(sb, i); 330 if ((levels[i] & 1) != 0) { 331 UTF16.setCharAt(sb, i, UCharacter.getMirror(ch)); 332 } 333 i += UTF16.getCharCount(ch); 334 } 335 text = sb.toString(); 336 reorderingOptions &= ~Bidi.DO_MIRRORING; 337 } 338 339 /** 340 * Performs digit and letter shaping 341 * 342 * @param digitsDir Digit shaping option that indicates whether the text 343 * should be treated as logical or visual. 344 * @param lettersDir Letter shaping option that indicates whether the text 345 * should be treated as logical or visual form (can mismatch the digit 346 * option). 347 */ shapeArabic(int digitsDir, int lettersDir)348 private void shapeArabic(int digitsDir, int lettersDir) { 349 if (digitsDir == lettersDir) { 350 shapeArabic(shapingOptions | digitsDir); 351 } else { 352 /* Honor all shape options other than letters (not necessarily digits 353 only) */ 354 shapeArabic((shapingOptions & ~ArabicShaping.LETTERS_MASK) | digitsDir); 355 356 /* Honor all shape options other than digits (not necessarily letters 357 only) */ 358 shapeArabic((shapingOptions & ~ArabicShaping.DIGITS_MASK) | lettersDir); 359 } 360 } 361 362 /** 363 * Performs digit and letter shaping 364 * 365 * @param options Shaping options covering both letters and digits 366 */ shapeArabic(int options)367 private void shapeArabic(int options) { 368 if (options != 0) { 369 ArabicShaping shaper = new ArabicShaping(options); 370 try { 371 text = shaper.shape(text); 372 } catch(ArabicShapingException e) { 373 } 374 } 375 } 376 377 private enum ReorderingScheme { 378 LOG_LTR_TO_VIS_LTR { 379 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)380 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 381 return IsLTR(inLevel) && IsLogical(inOrder) 382 && IsLTR(outLevel) && IsVisual(outOrder); 383 } 384 @Override doTransform(BidiTransform transform)385 void doTransform(BidiTransform transform) { 386 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 387 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 388 transform.reorder(); 389 } 390 }, 391 LOG_RTL_TO_VIS_LTR { 392 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)393 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 394 return IsRTL(inLevel) && IsLogical(inOrder) 395 && IsLTR(outLevel) && IsVisual(outOrder); 396 } 397 @Override doTransform(BidiTransform transform)398 void doTransform(BidiTransform transform) { 399 transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT); 400 transform.reorder(); 401 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 402 } 403 }, 404 LOG_LTR_TO_VIS_RTL { 405 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)406 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 407 return IsLTR(inLevel) && IsLogical(inOrder) 408 && IsRTL(outLevel) && IsVisual(outOrder); 409 } 410 @Override doTransform(BidiTransform transform)411 void doTransform(BidiTransform transform) { 412 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 413 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 414 transform.reorder(); 415 transform.reverse(); 416 } 417 }, 418 LOG_RTL_TO_VIS_RTL { 419 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)420 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 421 return IsRTL(inLevel) && IsLogical(inOrder) 422 && IsRTL(outLevel) && IsVisual(outOrder); 423 } 424 @Override doTransform(BidiTransform transform)425 void doTransform(BidiTransform transform) { 426 transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT); 427 transform.reorder(); 428 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 429 transform.reverse(); 430 } 431 }, 432 VIS_LTR_TO_LOG_RTL { 433 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)434 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 435 return IsLTR(inLevel) && IsVisual(inOrder) 436 && IsRTL(outLevel) && IsLogical(outOrder); 437 } 438 @Override doTransform(BidiTransform transform)439 void doTransform(BidiTransform transform) { 440 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 441 transform.resolve(Bidi.RTL, Bidi.REORDER_INVERSE_LIKE_DIRECT); 442 transform.reorder(); 443 } 444 }, 445 VIS_RTL_TO_LOG_RTL { 446 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)447 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 448 return IsRTL(inLevel) && IsVisual(inOrder) 449 && IsRTL(outLevel) && IsLogical(outOrder); 450 } 451 @Override doTransform(BidiTransform transform)452 void doTransform(BidiTransform transform) { 453 transform.reverse(); 454 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 455 transform.resolve(Bidi.RTL, Bidi.REORDER_INVERSE_LIKE_DIRECT); 456 transform.reorder(); 457 } 458 }, 459 VIS_LTR_TO_LOG_LTR { 460 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)461 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 462 return IsLTR(inLevel) && IsVisual(inOrder) 463 && IsLTR(outLevel) && IsLogical(outOrder); 464 } 465 @Override doTransform(BidiTransform transform)466 void doTransform(BidiTransform transform) { 467 transform.resolve(Bidi.LTR, Bidi.REORDER_INVERSE_LIKE_DIRECT); 468 transform.reorder(); 469 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 470 } 471 }, 472 VIS_RTL_TO_LOG_LTR { 473 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)474 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 475 return IsRTL(inLevel) && IsVisual(inOrder) 476 && IsLTR(outLevel) && IsLogical(outOrder); 477 } 478 @Override doTransform(BidiTransform transform)479 void doTransform(BidiTransform transform) { 480 transform.reverse(); 481 transform.resolve(Bidi.LTR, Bidi.REORDER_INVERSE_LIKE_DIRECT); 482 transform.reorder(); 483 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 484 } 485 }, 486 LOG_LTR_TO_LOG_RTL { 487 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)488 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 489 return IsLTR(inLevel) && IsLogical(inOrder) 490 && IsRTL(outLevel) && IsLogical(outOrder); 491 } 492 @Override doTransform(BidiTransform transform)493 void doTransform(BidiTransform transform) { 494 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 495 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 496 transform.mirror(); 497 transform.resolve(Bidi.LTR, Bidi.REORDER_RUNS_ONLY); 498 transform.reorder(); 499 } 500 }, 501 LOG_RTL_TO_LOG_LTR { 502 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)503 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 504 return IsRTL(inLevel) && IsLogical(inOrder) 505 && IsLTR(outLevel) && IsLogical(outOrder); 506 } 507 @Override doTransform(BidiTransform transform)508 void doTransform(BidiTransform transform) { 509 transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT); 510 transform.mirror(); 511 transform.resolve(Bidi.RTL, Bidi.REORDER_RUNS_ONLY); 512 transform.reorder(); 513 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 514 } 515 }, 516 VIS_LTR_TO_VIS_RTL { 517 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)518 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 519 return IsLTR(inLevel) && IsVisual(inOrder) 520 && IsRTL(outLevel) && IsVisual(outOrder); 521 } 522 @Override doTransform(BidiTransform transform)523 void doTransform(BidiTransform transform) { 524 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 525 transform.mirror(); 526 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 527 transform.reverse(); 528 } 529 }, 530 VIS_RTL_TO_VIS_LTR { 531 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)532 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 533 return IsRTL(inLevel) && IsVisual(inOrder) 534 && IsLTR(outLevel) && IsVisual(outOrder); 535 } 536 @Override doTransform(BidiTransform transform)537 void doTransform(BidiTransform transform) { 538 transform.reverse(); 539 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 540 transform.mirror(); 541 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 542 } 543 }, 544 LOG_LTR_TO_LOG_LTR { 545 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)546 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 547 return IsLTR(inLevel) && IsLogical(inOrder) 548 && IsLTR(outLevel) && IsLogical(outOrder); 549 } 550 @Override doTransform(BidiTransform transform)551 void doTransform(BidiTransform transform) { 552 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 553 transform.mirror(); 554 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL); 555 } 556 }, 557 LOG_RTL_TO_LOG_RTL { 558 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)559 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 560 return IsRTL(inLevel) && IsLogical(inOrder) 561 && IsRTL(outLevel) && IsLogical(outOrder); 562 } 563 @Override doTransform(BidiTransform transform)564 void doTransform(BidiTransform transform) { 565 transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT); 566 transform.mirror(); 567 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_VISUAL_LTR, ArabicShaping.TEXT_DIRECTION_LOGICAL); 568 } 569 }, 570 VIS_LTR_TO_VIS_LTR { 571 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)572 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 573 return IsLTR(inLevel) && IsVisual(inOrder) 574 && IsLTR(outLevel) && IsVisual(outOrder); 575 } 576 @Override doTransform(BidiTransform transform)577 void doTransform(BidiTransform transform) { 578 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 579 transform.mirror(); 580 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 581 } 582 }, 583 VIS_RTL_TO_VIS_RTL { 584 @Override matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)585 boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) { 586 return IsRTL(inLevel) && IsVisual(inOrder) 587 && IsRTL(outLevel) && IsVisual(outOrder); 588 } 589 @Override doTransform(BidiTransform transform)590 void doTransform(BidiTransform transform) { 591 transform.reverse(); 592 transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT); 593 transform.mirror(); 594 transform.shapeArabic(ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_VISUAL_LTR); 595 transform.reverse(); 596 } 597 }; 598 599 /** 600 * Indicates whether this scheme matches another one in terms of 601 * equality of base direction and ordering scheme. 602 * 603 * @param inLevel Base level of the input text 604 * @param inOrder Order of the input text 605 * @param outLevel Base level of the output text 606 * @param outOrder Order of the output text 607 * 608 * @return <code>true</code> if it's a match, <code>false</code> 609 * otherwise 610 */ matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder)611 abstract boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder); 612 613 /** 614 * Performs a series of bidi layout transformations unique for the current 615 * scheme. 616 617 * @param transform Bidi transformation engine 618 */ doTransform(BidiTransform transform)619 abstract void doTransform(BidiTransform transform); 620 } 621 622 /** 623 * Is level LTR? convenience method 624 625 * @param level Embedding level 626 */ IsLTR(byte level)627 private static boolean IsLTR(byte level) { 628 return (level & 1) == 0; 629 } 630 631 /** 632 * Is level RTL? convenience method 633 634 * @param level Embedding level 635 */ IsRTL(byte level)636 private static boolean IsRTL(byte level) { 637 return (level & 1) == 1; 638 } 639 640 /** 641 * Is order logical? convenience method 642 643 * @param level Order value 644 */ IsLogical(Order order)645 private static boolean IsLogical(Order order) { 646 return Order.LOGICAL.equals(order); 647 } 648 649 /** 650 * Is order visual? convenience method 651 652 * @param level Order value 653 */ IsVisual(Order order)654 private static boolean IsVisual(Order order) { 655 return Order.VISUAL.equals(order); 656 } 657 658 } 659