Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 273906bb authored by Alan Lau's avatar Alan Lau
Browse files

Revert "Revert "DO NOT MERGE Implement line breaking using ICU break iterator""

This reverts commit 77d6f36b.

Change-Id: I10b6d9e3d82b0f2b6dc154465a4b2f1db8eed058
parent 77d6f36b
Loading
Loading
Loading
Loading
+17 −106
Original line number Diff line number Diff line
@@ -161,6 +161,9 @@ public class StaticLayout extends Layout {
                        float spacingadd, boolean includepad,
                        boolean trackpad, float ellipsizedWidth,
                        TextUtils.TruncateAt ellipsize) {
        int[] breakOpp = null;
        final String localeLanguageTag = paint.getTextLocale().toLanguageTag();

        mLineCount = 0;

        int v = 0;
@@ -175,8 +178,6 @@ public class StaticLayout extends Layout {
        if (source instanceof Spanned)
            spanned = (Spanned) source;

        int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX

        int paraEnd;
        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
@@ -243,6 +244,9 @@ public class StaticLayout extends Layout {
            int dir = measured.mDir;
            boolean easy = measured.mEasy;

            breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp);
            int breakOppIndex = 0;

            int width = firstWidth;

            float w = 0;
@@ -355,15 +359,12 @@ public class StaticLayout extends Layout {
                        if (fmBottom > fitBottom)
                            fitBottom = fmBottom;

                        // From the Unicode Line Breaking Algorithm (at least approximately)
                        boolean isLineBreak = isSpaceOrTab ||
                                // / is class SY and - is class HY, except when followed by a digit
                                ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
                                (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
                                // Ideographs are class ID: breakpoints when adjacent, except for NS
                                // (non-starters), which can be broken after but not before
                                (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
                                j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false));
                        while (breakOpp[breakOppIndex] != -1
                                && breakOpp[breakOppIndex] < j - paraStart + 1) {
                            breakOppIndex++;
                        }
                        boolean isLineBreak = breakOppIndex < breakOpp.length &&
                                breakOpp[breakOppIndex] == j - paraStart + 1;

                        if (isLineBreak) {
                            okWidth = w;
@@ -491,97 +492,6 @@ public class StaticLayout extends Layout {
        }
    }

    /**
     * Returns true if the specified character is one of those specified
     * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
     * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
     * to break between a pair of.
     *
     * @param includeNonStarters also return true for category NS
     *                           (non-starters), which can be broken
     *                           after but not before.
     */
    private static final boolean isIdeographic(char c, boolean includeNonStarters) {
        if (c >= '\u2E80' && c <= '\u2FFF') {
            return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
        }
        if (c == '\u3000') {
            return true; // IDEOGRAPHIC SPACE
        }
        if (c >= '\u3040' && c <= '\u309F') {
            if (!includeNonStarters) {
                switch (c) {
                case '\u3041': //  # HIRAGANA LETTER SMALL A
                case '\u3043': //  # HIRAGANA LETTER SMALL I
                case '\u3045': //  # HIRAGANA LETTER SMALL U
                case '\u3047': //  # HIRAGANA LETTER SMALL E
                case '\u3049': //  # HIRAGANA LETTER SMALL O
                case '\u3063': //  # HIRAGANA LETTER SMALL TU
                case '\u3083': //  # HIRAGANA LETTER SMALL YA
                case '\u3085': //  # HIRAGANA LETTER SMALL YU
                case '\u3087': //  # HIRAGANA LETTER SMALL YO
                case '\u308E': //  # HIRAGANA LETTER SMALL WA
                case '\u3095': //  # HIRAGANA LETTER SMALL KA
                case '\u3096': //  # HIRAGANA LETTER SMALL KE
                case '\u309B': //  # KATAKANA-HIRAGANA VOICED SOUND MARK
                case '\u309C': //  # KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
                case '\u309D': //  # HIRAGANA ITERATION MARK
                case '\u309E': //  # HIRAGANA VOICED ITERATION MARK
                    return false;
                }
            }
            return true; // Hiragana (except small characters)
        }
        if (c >= '\u30A0' && c <= '\u30FF') {
            if (!includeNonStarters) {
                switch (c) {
                case '\u30A0': //  # KATAKANA-HIRAGANA DOUBLE HYPHEN
                case '\u30A1': //  # KATAKANA LETTER SMALL A
                case '\u30A3': //  # KATAKANA LETTER SMALL I
                case '\u30A5': //  # KATAKANA LETTER SMALL U
                case '\u30A7': //  # KATAKANA LETTER SMALL E
                case '\u30A9': //  # KATAKANA LETTER SMALL O
                case '\u30C3': //  # KATAKANA LETTER SMALL TU
                case '\u30E3': //  # KATAKANA LETTER SMALL YA
                case '\u30E5': //  # KATAKANA LETTER SMALL YU
                case '\u30E7': //  # KATAKANA LETTER SMALL YO
                case '\u30EE': //  # KATAKANA LETTER SMALL WA
                case '\u30F5': //  # KATAKANA LETTER SMALL KA
                case '\u30F6': //  # KATAKANA LETTER SMALL KE
                case '\u30FB': //  # KATAKANA MIDDLE DOT
                case '\u30FC': //  # KATAKANA-HIRAGANA PROLONGED SOUND MARK
                case '\u30FD': //  # KATAKANA ITERATION MARK
                case '\u30FE': //  # KATAKANA VOICED ITERATION MARK
                    return false;
                }
            }
            return true; // Katakana (except small characters)
        }
        if (c >= '\u3400' && c <= '\u4DB5') {
            return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
        }
        if (c >= '\u4E00' && c <= '\u9FBB') {
            return true; // CJK UNIFIED IDEOGRAPHS
        }
        if (c >= '\uF900' && c <= '\uFAD9') {
            return true; // CJK COMPATIBILITY IDEOGRAPHS
        }
        if (c >= '\uA000' && c <= '\uA48F') {
            return true; // YI SYLLABLES
        }
        if (c >= '\uA490' && c <= '\uA4CF') {
            return true; // YI RADICALS
        }
        if (c >= '\uFE62' && c <= '\uFE66') {
            return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
        }
        if (c >= '\uFF10' && c <= '\uFF19') {
            return true; // WIDE DIGITS
        }

        return false;
    }

    private int out(CharSequence text, int start, int end,
                      int above, int below, int top, int bottom, int v,
                      float spacingmult, float spacingadd,
@@ -930,6 +840,11 @@ public class StaticLayout extends Layout {
        mMeasured = MeasuredText.recycle(mMeasured);
    }

    // returns an array with terminal sentinel value -1 to indicate end
    // this is so that arrays can be recycled instead of allocating new arrays
    // every time
    private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle);

    private int mLineCount;
    private int mTopPadding, mBottomPadding;
    private int mColumns;
@@ -955,13 +870,9 @@ public class StaticLayout extends Layout {

    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private

    private static final char CHAR_FIRST_CJK = '\u2E80';

    private static final char CHAR_NEW_LINE = '\n';
    private static final char CHAR_TAB = '\t';
    private static final char CHAR_SPACE = ' ';
    private static final char CHAR_SLASH = '/';
    private static final char CHAR_HYPHEN = '-';
    private static final char CHAR_ZWSP = '\u200B';

    private static final double EXTRA_ROUNDING = 0.5;
+1 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ LOCAL_SRC_FILES:= \
	android_view_VelocityTracker.cpp \
	android_text_AndroidCharacter.cpp \
	android_text_AndroidBidi.cpp \
	android_text_StaticLayout.cpp \
	android_os_Debug.cpp \
	android_os_MemoryFile.cpp \
	android_os_MessageQueue.cpp \
+2 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_net_TrafficStats(JNIEnv* env);
extern int register_android_net_wifi_WifiNative(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_StaticLayout(JNIEnv *env);
extern int register_android_text_AndroidBidi(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
@@ -1105,6 +1106,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_content_XmlBlock),
    REG_JNI(register_android_emoji_EmojiFactory),
    REG_JNI(register_android_text_AndroidCharacter),
    REG_JNI(register_android_text_StaticLayout),
    REG_JNI(register_android_text_AndroidBidi),
    REG_JNI(register_android_view_InputDevice),
    REG_JNI(register_android_view_KeyCharacterMap),
+110 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "StaticLayout"

#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
#include "utils/misc.h"
#include "utils/Log.h"
#include "ScopedPrimitiveArray.h"
#include "JNIHelp.h"
#include <android_runtime/AndroidRuntime.h>
#include <vector>

namespace android {

class ScopedBreakIterator {
    public:
        ScopedBreakIterator(JNIEnv* env, BreakIterator* breakIterator, jcharArray inputText,
                            jint length) : mBreakIterator(breakIterator), mChars(env, inputText) {
            UErrorCode status = U_ZERO_ERROR;
            mUText = utext_openUChars(NULL, mChars.get(), length, &status);
            if (mUText == NULL) {
                return;
            }

            mBreakIterator->setText(mUText, status);
        }

        inline BreakIterator* operator->() {
            return mBreakIterator;
        }

        ~ScopedBreakIterator() {
            utext_close(mUText);
            delete mBreakIterator;
        }
    private:
        BreakIterator* mBreakIterator;
        ScopedCharArrayRO mChars;
        UText* mUText;

        // disable copying and assignment
        ScopedBreakIterator(const ScopedBreakIterator&);
        void operator=(const ScopedBreakIterator&);
};

static jintArray nLineBreakOpportunities(JNIEnv* env, jclass, jstring javaLocaleName,
                                        jcharArray inputText, jint length,
                                        jintArray recycle) {
    jintArray ret;
    std::vector<jint> breaks(16);

    ScopedIcuLocale icuLocale(env, javaLocaleName);
    if (icuLocale.valid()) {
        UErrorCode status = U_ZERO_ERROR;
        BreakIterator* it = BreakIterator::createLineInstance(icuLocale.locale(), status);
        if (!U_SUCCESS(status) || it == NULL) {
            if (it) {
                delete it;
            }
        } else {
            ScopedBreakIterator breakIterator(env, it, inputText, length);
            for (int loc = breakIterator->first(); loc != BreakIterator::DONE;
                    loc = breakIterator->next()) {
                breaks.push_back(loc);
            }
        }
    }

    breaks.push_back(-1); // sentinel terminal value

    if (recycle != NULL && env->GetArrayLength(recycle) >= breaks.size()) {
        ret = recycle;
    } else {
        ret = env->NewIntArray(breaks.size());
    }

    if (ret != NULL) {
        env->SetIntArrayRegion(ret, 0, breaks.size(), &breaks.front());
    }

    return ret;
}

static JNINativeMethod gMethods[] = {
    {"nLineBreakOpportunities", "(Ljava/lang/String;[CI[I)[I", (void*) nLineBreakOpportunities}
};

int register_android_text_StaticLayout(JNIEnv* env)
{
    return AndroidRuntime::registerNativeMethods(env, "android/text/StaticLayout",
            gMethods, NELEM(gMethods));
}

}