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