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