Home | History | Annotate | Download | only in text
      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