BidiTransform.java

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

package com.ibm.icu.text;

import com.ibm.icu.lang.UCharacter;

/**
 * Bidi Layout Transformation Engine.
 *
 * @author Lina Kemmel
 * @stable ICU 58
 */
public class BidiTransform {
    /**
     * <code>{@link Order}</code> indicates the order of text.
     *
     * <p>This bidi transformation engine supports all possible combinations (4 in total) of input
     * and output text order:
     *
     * <ul>
     *   <li>{logical input, visual output}: unless the output direction is RTL, this corresponds to
     *       a normal operation of the Bidi algorithm as described in the Unicode Technical Report
     *       and implemented by <code>{@link Bidi}</code> when the reordering mode is set to <code>
     *       Bidi#REORDER_DEFAULT</code>. Visual RTL mode is not supported by <code>{@link Bidi}
     *       </code> and is accomplished through reversing a visual LTR string,
     *   <li>{visual input, logical output}: unless the input direction is RTL, this corresponds to
     *       an "inverse bidi algorithm" in <code>{@link Bidi}</code> with the reordering mode set
     *       to <code>{@link Bidi#REORDER_INVERSE_LIKE_DIRECT}</code>. Visual RTL mode is not not
     *       supported by <code>{@link Bidi}</code> and is accomplished through reversing a visual
     *       LTR string,
     *   <li>{logical input, logical output}: if the input and output base directions mismatch, this
     *       corresponds to the <code>{@link Bidi}</code> implementation with the reordering mode
     *       set to <code>{@link Bidi#REORDER_RUNS_ONLY}</code>; and if the input and output base
     *       directions are identical, the transformation engine will only handle character
     *       mirroring and Arabic shaping operations without reordering,
     *   <li>{visual input, visual output}: this reordering mode is not supported by the <code>
     *       {@link Bidi}</code> engine; it implies character mirroring, Arabic shaping, and - if
     *       the input/output base directions mismatch - string reverse operations.
     * </ul>
     *
     * @see Bidi#setInverse
     * @see Bidi#setReorderingMode
     * @see Bidi#REORDER_DEFAULT
     * @see Bidi#REORDER_INVERSE_LIKE_DIRECT
     * @see Bidi#REORDER_RUNS_ONLY
     * @stable ICU 58
     */
    public enum Order {
        /**
         * Constant indicating a logical order.
         *
         * @stable ICU 58
         */
        LOGICAL,
        /**
         * Constant indicating a visual order.
         *
         * @stable ICU 58
         */
        VISUAL;
    }

    /**
     * <code>{@link Mirroring}</code> indicates whether or not characters with the "mirrored"
     * property in RTL runs should be replaced with their mirror-image counterparts.
     *
     * @see Bidi#DO_MIRRORING
     * @see Bidi#setReorderingOptions
     * @see Bidi#writeReordered
     * @see Bidi#writeReverse
     * @stable ICU 58
     */
    public enum Mirroring {
        /**
         * Constant indicating that character mirroring should not be performed.
         *
         * @stable ICU 58
         */
        OFF,
        /**
         * Constant indicating that character mirroring should be performed.
         *
         * <p>This corresponds to calling <code>{@link Bidi#writeReordered}</code> or <code>
         * {@link Bidi#writeReverse}</code> with the <code>{@link Bidi#DO_MIRRORING}</code> option
         * bit set.
         *
         * @stable ICU 58
         */
        ON;
    }

    private Bidi bidi;
    private String text;
    private int reorderingOptions;
    private int shapingOptions;

    /**
     * <code>{@link BidiTransform}</code> default constructor.
     *
     * @stable ICU 58
     */
    public BidiTransform() {}

    /**
     * Performs transformation of text from the bidi layout defined by the input ordering scheme to
     * the bidi layout defined by the output ordering scheme, and applies character mirroring and
     * Arabic shaping operations.
     *
     * <p>In terms of <code>{@link Bidi}</code> class, such a transformation implies:
     *
     * <ul>
     *   <li>calling <code>{@link Bidi#setReorderingMode}</code> as needed (when the reordering mode
     *       is other than normal),
     *   <li>calling <code>{@link Bidi#setInverse}</code> as needed (when text should be transformed
     *       from a visual to a logical form),
     *   <li>resolving embedding levels of each character in the input text by calling <code>
     *       {@link Bidi#setPara}</code>,
     *   <li>reordering the characters based on the computed embedding levels, also performing
     *       character mirroring as needed, and streaming the result to the output, by calling
     *       <code>{@link Bidi#writeReordered}</code>,
     *   <li>performing Arabic digit and letter shaping on the output text by calling <code>
     *       {@link ArabicShaping#shape}</code>.
     * </ul>
     *
     * <p>An "ordering scheme" encompasses the base direction and the order of text, and these
     * characteristics must be defined by the caller for both input and output explicitly .
     *
     * <p>There are 36 possible combinations of {input, output} ordering schemes, which are
     * partially supported by <code>{@link Bidi}</code> already. Examples of the currently supported
     * combinations:
     *
     * <ul>
     *   <li>{Logical LTR, Visual LTR}: this is equivalent to calling <code>{@link Bidi#setPara}
     *       </code> with <code>paraLevel == {@link Bidi#LTR}</code>,
     *   <li>{Logical RTL, Visual LTR}: this is equivalent to calling <code>{@link Bidi#setPara}
     *       </code> with <code>paraLevel == {@link Bidi#RTL}</code>,
     *   <li>{Logical Default ("Auto") LTR, Visual LTR}: this is equivalent to calling <code>
     *       {@link Bidi#setPara}</code> with <code>paraLevel == {@link Bidi#LEVEL_DEFAULT_LTR}
     *       </code>,
     *   <li>{Logical Default ("Auto") RTL, Visual LTR}: this is equivalent to calling <code>
     *       {@link Bidi#setPara}</code> with <code>paraLevel == {@link Bidi#LEVEL_DEFAULT_RTL}
     *       </code>,
     *   <li>{Visual LTR, Logical LTR}: this is equivalent to calling <code>
     *       {@link Bidi#setInverse}(true)</code> and then <code>{@link Bidi#setPara}</code> with
     *       <code>paraLevel == {@link Bidi#LTR}</code>,
     *   <li>{Visual LTR, Logical RTL}: this is equivalent to calling <code>
     *       {@link Bidi#setInverse}(true)</code> and then <code>{@link Bidi#setPara}</code> with
     *       <code>paraLevel == {@link Bidi#RTL}</code>.
     * </ul>
     *
     * <p>All combinations that involve the Visual RTL scheme are unsupported by <code>{@link Bidi}
     * </code>, for instance:
     *
     * <ul>
     *   <li>{Logical LTR, Visual RTL},
     *   <li>{Visual RTL, Logical RTL}.
     * </ul>
     *
     * <p>Example of usage of the transformation engine:
     *
     * <pre>
     * BidiTransform bidiTransform = new BidiTransform();
     * String in = "abc \u06f0123"; // "abc \\u06f0123"
     * // Run a transformation.
     * String out = bidiTransform.transform(in,
     *          Bidi.LTR, Order.VISUAL,
     *          Bidi.RTL, Order.LOGICAL,
     *          Mirroring.OFF,
     *          ArabicShaping.DIGITS_AN2EN | ArabicShaping.DIGIT_TYPE_AN_EXTENDED);
     * // Result: "0123 abc".
     * // Do something with out.
     * out = out.replace('0', '4');
     * // Result: "4123 abc".
     * // Run a reverse transformation.
     * String inNew = bidiTransform.transform(out,
     *          Bidi.RTL, Order.LOGICAL,
     *          Bidi.LTR, Order.VISUAL,
     *          Mirroring.OFF,
     *          ArabicShaping.DIGITS_EN2AN | ArabicShaping.DIGIT_TYPE_AN_EXTENDED);
     * // Result: "abc \\u06f4\\u06f1\\u06f2\\u06f3"
     * </pre>
     *
     * @param text An input character sequence that the Bidi layout transformations will be
     *     performed on.
     * @param inParaLevel A base embedding level of the input as defined in <code>
     *     {@link Bidi#setPara(String, byte, byte[])}</code> documentation for the <code>paraLevel
     *     </code> parameter.
     * @param inOrder An order of the input, which can be one of the <code>{@link Order}</code>
     *     values.
     * @param outParaLevel A base embedding level of the output as defined in <code>
     *     {@link Bidi#setPara(String, byte, byte[])}</code> documentation for the <code>paraLevel
     *     </code> parameter.
     * @param outOrder An order of the output, which can be one of the <code>{@link Order}</code>
     *     values.
     * @param doMirroring Indicates whether or not to perform character mirroring, and can accept
     *     one of the <code>{@link Mirroring}</code> values.
     * @param shapingOptions Arabic digit and letter shaping options defined in the <code>
     *     {@link ArabicShaping}</code> documentation.
     *     <p><strong>Note:</strong> Direction indicator options are computed by the transformation
     *     engine based on the effective ordering schemes, so user-defined direction indicators will
     *     be ignored.
     * @return The output string, which is the result of the layout transformation.
     * @throws IllegalArgumentException if <code>text</code>, <code>inOrder</code>, <code>outOrder
     *     </code>, or <code>doMirroring</code> parameter is <code>null</code>.
     * @stable ICU 58
     */
    public String transform(
            CharSequence text,
            byte inParaLevel,
            Order inOrder,
            byte outParaLevel,
            Order outOrder,
            Mirroring doMirroring,
            int shapingOptions) {
        if (text == null || inOrder == null || outOrder == null || doMirroring == null) {
            throw new IllegalArgumentException();
        }
        this.text = text.toString();

        byte[] levels = {inParaLevel, outParaLevel};
        resolveBaseDirection(levels);

        ReorderingScheme currentScheme =
                findMatchingScheme(levels[0], inOrder, levels[1], outOrder);
        if (currentScheme != null) {
            this.bidi = new Bidi();
            this.reorderingOptions =
                    Mirroring.ON.equals(doMirroring) ? Bidi.DO_MIRRORING : Bidi.REORDER_DEFAULT;

            /* Ignore TEXT_DIRECTION_* flags, as we apply our own depending on the
            text scheme at the time shaping is invoked. */
            this.shapingOptions = shapingOptions & ~ArabicShaping.TEXT_DIRECTION_MASK;
            currentScheme.doTransform(this);
        }
        return this.text;
    }

    /**
     * When the direction option is <code>{@link Bidi#LEVEL_DEFAULT_LTR}</code> or <code>
     * {@link Bidi#LEVEL_DEFAULT_RTL}</code>, resolves the base direction according to that of the
     * first strong directional character in the text.
     *
     * @param levels Byte array, where levels[0] is an input level levels[1] is an output level.
     *     Resolved levels override these.
     */
    private void resolveBaseDirection(byte[] levels) {
        if (Bidi.IsDefaultLevel(levels[0])) {
            byte level = Bidi.getBaseDirection(text);
            levels[0] =
                    level != Bidi.NEUTRAL
                            ? level
                            : levels[0] == Bidi.LEVEL_DEFAULT_RTL ? Bidi.RTL : Bidi.LTR;
        } else {
            levels[0] &= 1;
        }
        if (Bidi.IsDefaultLevel(levels[1])) {
            levels[1] = levels[0];
        } else {
            levels[1] &= 1;
        }
    }

    /**
     * Finds a valid <code>{@link ReorderingScheme}</code> matching the caller-defined scheme.
     *
     * @return A valid <code>ReorderingScheme</code> object or null
     */
    private ReorderingScheme findMatchingScheme(
            byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
        for (ReorderingScheme scheme : ReorderingScheme.values()) {
            if (scheme.matches(inLevel, inOrder, outLevel, outOrder)) {
                return scheme;
            }
        }
        return null;
    }

    /**
     * Performs bidi resolution of text.
     *
     * @param level Base embedding level
     * @param options Reordering options
     */
    private void resolve(byte level, int options) {
        bidi.setInverse((options & Bidi.REORDER_INVERSE_LIKE_DIRECT) != 0);
        bidi.setReorderingMode(options);
        bidi.setPara(text, level, null);
    }

    /** Performs basic reordering of text (Logical LTR or RTL to Visual LTR). */
    private void reorder() {
        text = bidi.writeReordered(reorderingOptions);
        reorderingOptions = Bidi.REORDER_DEFAULT;
    }

    /** Performs string reverse. */
    private void reverse() {
        text = Bidi.writeReverse(text, Bidi.OPTION_DEFAULT);
    }

    /**
     * Performs character mirroring without reordering. When this method is called, <code>
     * {@link #text}</code> should be in a Logical form.
     */
    private void mirror() {
        if ((reorderingOptions & Bidi.DO_MIRRORING) == 0) {
            return;
        }
        StringBuffer sb = new StringBuffer(text);
        byte[] levels = bidi.getLevels();
        for (int i = 0, n = levels.length; i < n; ) {
            int ch = UTF16.charAt(sb, i);
            if ((levels[i] & 1) != 0) {
                UTF16.setCharAt(sb, i, UCharacter.getMirror(ch));
            }
            i += UTF16.getCharCount(ch);
        }
        text = sb.toString();
        reorderingOptions &= ~Bidi.DO_MIRRORING;
    }

    /**
     * Performs digit and letter shaping
     *
     * @param digitsDir Digit shaping option that indicates whether the text should be treated as
     *     logical or visual.
     * @param lettersDir Letter shaping option that indicates whether the text should be treated as
     *     logical or visual form (can mismatch the digit option).
     */
    private void shapeArabic(int digitsDir, int lettersDir) {
        if (digitsDir == lettersDir) {
            shapeArabic(shapingOptions | digitsDir);
        } else {
            /* Honor all shape options other than letters (not necessarily digits
            only) */
            shapeArabic((shapingOptions & ~ArabicShaping.LETTERS_MASK) | digitsDir);

            /* Honor all shape options other than digits (not necessarily letters
            only) */
            shapeArabic((shapingOptions & ~ArabicShaping.DIGITS_MASK) | lettersDir);
        }
    }

    /**
     * Performs digit and letter shaping
     *
     * @param options Shaping options covering both letters and digits
     */
    private void shapeArabic(int options) {
        if (options != 0) {
            ArabicShaping shaper = new ArabicShaping(options);
            try {
                text = shaper.shape(text);
            } catch (ArabicShapingException e) {
            }
        }
    }

    private enum ReorderingScheme {
        LOG_LTR_TO_VIS_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsLogical(inOrder)
                        && IsLTR(outLevel)
                        && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.reorder();
            }
        },
        LOG_RTL_TO_VIS_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsLogical(inOrder)
                        && IsLTR(outLevel)
                        && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT);
                transform.reorder();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
            }
        },
        LOG_LTR_TO_VIS_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsLogical(inOrder)
                        && IsRTL(outLevel)
                        && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.reorder();
                transform.reverse();
            }
        },
        LOG_RTL_TO_VIS_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsLogical(inOrder)
                        && IsRTL(outLevel)
                        && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT);
                transform.reorder();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
                transform.reverse();
            }
        },
        VIS_LTR_TO_LOG_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsVisual(inOrder)
                        && IsRTL(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
                transform.resolve(Bidi.RTL, Bidi.REORDER_INVERSE_LIKE_DIRECT);
                transform.reorder();
            }
        },
        VIS_RTL_TO_LOG_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsVisual(inOrder)
                        && IsRTL(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.reverse();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
                transform.resolve(Bidi.RTL, Bidi.REORDER_INVERSE_LIKE_DIRECT);
                transform.reorder();
            }
        },
        VIS_LTR_TO_LOG_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsVisual(inOrder)
                        && IsLTR(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.LTR, Bidi.REORDER_INVERSE_LIKE_DIRECT);
                transform.reorder();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
            }
        },
        VIS_RTL_TO_LOG_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsVisual(inOrder)
                        && IsLTR(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.reverse();
                transform.resolve(Bidi.LTR, Bidi.REORDER_INVERSE_LIKE_DIRECT);
                transform.reorder();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
            }
        },
        LOG_LTR_TO_LOG_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsLogical(inOrder)
                        && IsRTL(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.resolve(Bidi.LTR, Bidi.REORDER_RUNS_ONLY);
                transform.reorder();
            }
        },
        LOG_RTL_TO_LOG_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsLogical(inOrder)
                        && IsLTR(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.resolve(Bidi.RTL, Bidi.REORDER_RUNS_ONLY);
                transform.reorder();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
            }
        },
        VIS_LTR_TO_VIS_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel) && IsVisual(inOrder) && IsRTL(outLevel) && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
                transform.reverse();
            }
        },
        VIS_RTL_TO_VIS_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel) && IsVisual(inOrder) && IsLTR(outLevel) && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.reverse();
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
            }
        },
        LOG_LTR_TO_LOG_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel)
                        && IsLogical(inOrder)
                        && IsLTR(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL, ArabicShaping.TEXT_DIRECTION_LOGICAL);
            }
        },
        LOG_RTL_TO_LOG_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel)
                        && IsLogical(inOrder)
                        && IsRTL(outLevel)
                        && IsLogical(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.RTL, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR,
                        ArabicShaping.TEXT_DIRECTION_LOGICAL);
            }
        },
        VIS_LTR_TO_VIS_LTR {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsLTR(inLevel) && IsVisual(inOrder) && IsLTR(outLevel) && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
            }
        },
        VIS_RTL_TO_VIS_RTL {
            @Override
            boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder) {
                return IsRTL(inLevel) && IsVisual(inOrder) && IsRTL(outLevel) && IsVisual(outOrder);
            }

            @Override
            void doTransform(BidiTransform transform) {
                transform.reverse();
                transform.resolve(Bidi.LTR, Bidi.REORDER_DEFAULT);
                transform.mirror();
                transform.shapeArabic(
                        ArabicShaping.TEXT_DIRECTION_LOGICAL,
                        ArabicShaping.TEXT_DIRECTION_VISUAL_LTR);
                transform.reverse();
            }
        };

        /**
         * Indicates whether this scheme matches another one in terms of equality of base direction
         * and ordering scheme.
         *
         * @param inLevel Base level of the input text
         * @param inOrder Order of the input text
         * @param outLevel Base level of the output text
         * @param outOrder Order of the output text
         * @return <code>true</code> if it's a match, <code>false</code> otherwise
         */
        abstract boolean matches(byte inLevel, Order inOrder, byte outLevel, Order outOrder);

        /**
         * Performs a series of bidi layout transformations unique for the current scheme.
         *
         * @param transform Bidi transformation engine
         */
        abstract void doTransform(BidiTransform transform);
    }

    /**
     * Is level LTR? convenience method
     *
     * @param level Embedding level
     */
    private static boolean IsLTR(byte level) {
        return (level & 1) == 0;
    }

    /**
     * Is level RTL? convenience method
     *
     * @param level Embedding level
     */
    private static boolean IsRTL(byte level) {
        return (level & 1) == 1;
    }

    /**
     * Is order logical? convenience method
     *
     * @param level Order value
     */
    private static boolean IsLogical(Order order) {
        return Order.LOGICAL.equals(order);
    }

    /**
     * Is order visual? convenience method
     *
     * @param level Order value
     */
    private static boolean IsVisual(Order order) {
        return Order.VISUAL.equals(order);
    }
}