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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 306 private void reorder() { 307 text = bidi.writeReordered(reorderingOptions); 308 reorderingOptions = Bidi.REORDER_DEFAULT; 309 } 310 311 /** 312 * Performs string reverse. 313 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 */ 619 abstract void doTransform(BidiTransform transform); 620 } 621 622 /** 623 * Is level LTR? convenience method 624 625 * @param level Embedding level 626 */ 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 */ 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 */ 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 */ 654 private static boolean IsVisual(Order order) { 655 return Order.VISUAL.equals(order); 656 } 657 658 } 659