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

Commit b02d0ca5 authored by Fabrice Di Meglio's avatar Fabrice Di Meglio
Browse files

Clean TextLayout code and remove RTL_USE_HARFBUZZ

- remove dependencies on ICU
- use TextLayouCache
- remove RTL_USE_HARFBUZZ define (we *are* using Harfbuzz now)
- also fix compilation warning

Change-Id: I022e11703438d07032e49e42724184f6bf16653e
parent 4a46ee57
Loading
Loading
Loading
Loading
+5 −21
Original line number Diff line number Diff line
@@ -732,11 +732,7 @@ public:
                                      jcharArray text, int index, int count,
                                      jfloat x, jfloat y, int flags, SkPaint* paint) {
        jchar* textArray = env->GetCharArrayElements(text, NULL);
#if RTL_USE_HARFBUZZ
        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint);
#else
        TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
#endif
        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
    }

@@ -745,11 +741,7 @@ public:
                                          int start, int end,
                                          jfloat x, jfloat y, int flags, SkPaint* paint) {
        const jchar* textArray = env->GetStringChars(text, NULL);
#if RTL_USE_HARFBUZZ
        drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint);
#else
        TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
#endif
        env->ReleaseStringChars(text, textArray);
    }

@@ -770,12 +762,14 @@ public:
        value = TextLayoutCache::getInstance().getValue(paint, textArray, start, count,
                contextCount, flags);
        if (value == NULL) {
            LOGE("Cannot get TextLayoutCache value");
            LOGE("Cannot get TextLayoutCache value for text = '%s'",
                    String8(textArray + start, count).string());
            return ;
        }
#else
        value = new TextLayoutCacheValue();
        value->computeValues(paint, textArray, start, count, contextCount, flags);
        value = new TextLayoutCacheValue(contextCount);
        TextLayoutEngine::getInstance().computeValues(value.get(), paint,
                reinterpret_cast<const UChar*>(textArray), start, count, contextCount, flags);
#endif
        doDrawGlyphs(canvas, value->getGlyphs(), 0, value->getGlyphsCount(), x, y, flags, paint);
    }
@@ -802,13 +796,8 @@ public:
        jfloat x, jfloat y, int dirFlags, SkPaint* paint) {

        jchar* chars = env->GetCharArrayElements(text, NULL);
#if RTL_USE_HARFBUZZ
        drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
                count, contextCount, x, y, dirFlags, paint);
#else
        TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
                count, contextCount, dirFlags, x, y, canvas);
#endif
        env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
    }

@@ -820,13 +809,8 @@ public:
        jint count = end - start;
        jint contextCount = contextEnd - contextStart;
        const jchar* chars = env->GetStringChars(text, NULL);
#if RTL_USE_HARFBUZZ
        drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
                count, contextCount, x, y, dirFlags, paint);
#else
        TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
                count, contextCount, dirFlags, x, y, canvas);
#endif
        env->ReleaseStringChars(text, chars);
    }

+3 −65
Original line number Diff line number Diff line
@@ -350,14 +350,10 @@ public:
        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
        const jchar* textArray = env->GetCharArrayElements(text, NULL);
        jfloat result = 0;
#if RTL_USE_HARFBUZZ

        TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
                paint->getFlags(), NULL /* dont need all advances */, &result);
#else
        // we double count, since measureText wants a byteLength
        SkScalar width = paint->measureText(textArray + index, count << 1);
        result = SkScalarToFloat(width);
#endif

        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
        return result;
    }
@@ -380,13 +376,9 @@ public:
        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
        jfloat width = 0;

#if RTL_USE_HARFBUZZ
        TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
                paint->getFlags(), NULL /* dont need all advances */, &width);
#else

        width = SkScalarToFloat(paint->measureText(textArray + start, count << 1));
#endif
        env->ReleaseStringChars(text, textArray);
        return width;
    }
@@ -404,12 +396,9 @@ public:
        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
        jfloat width = 0;

#if RTL_USE_HARFBUZZ
        TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
                paint->getFlags(), NULL /* dont need all advances */, &width);
#else
        width = SkScalarToFloat(paint->measureText(textArray, textLength << 1));
#endif

        env->ReleaseStringChars(text, textArray);
        return width;
    }
@@ -434,17 +423,9 @@ public:
        AutoJavaFloatArray autoWidths(env, widths, count);
        jfloat* widthsArray = autoWidths.ptr();

#if RTL_USE_HARFBUZZ
        TextLayout::getTextRunAdvances(paint, text, 0, count, count,
                paint->getFlags(), widthsArray, NULL /* dont need totalAdvance */);
#else
        SkScalar* scalarArray = (SkScalar*)widthsArray;

        count = paint->getTextWidths(text, count << 1, scalarArray);
        for (int i = 0; i < count; i++) {
            widthsArray[i] = SkScalarToFloat(scalarArray[i]);
        }
#endif
        return count;
    }

@@ -597,54 +578,11 @@ public:

    static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
            jint count, jint flags, jint offset, jint opt) {
#if RTL_USE_HARFBUZZ
        jfloat scalarArray[count];

        TextLayout::getTextRunAdvances(paint, text, start, count, count, flags,
                scalarArray, NULL /* dont need totalAdvance */);
#else
        SkScalar scalarArray[count];
        jchar buffer[count];

        // this is where we'd call harfbuzz
        // for now we just use ushape.c and widths returned from skia

        int widths;
        if (flags & 0x1) { // rtl, call arabic shaping in case
            UErrorCode status = U_ZERO_ERROR;
            // Use fixed length since we need to keep start and count valid
            u_shapeArabic(text + start, count, buffer, count,
                    U_SHAPE_LENGTH_FIXED_SPACES_NEAR | U_SHAPE_TEXT_DIRECTION_LOGICAL |
                    U_SHAPE_LETTERS_SHAPE | U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
            // we shouldn't fail unless there's an out of memory condition,
            // in which case we're hosed anyway
            for (int i = 0; i < count; ++i) {
              if (buffer[i] == 0xffff) {
                buffer[i] = 0x200b; // zero-width-space for skia
              }
            }
            widths = paint->getTextWidths(buffer, count << 1, scalarArray);
        } else {
            widths = paint->getTextWidths(text + start, count << 1, scalarArray);
        }

        if (widths < count) {
            // Skia operates on code points, not code units, so surrogate pairs return only one
            // value. Expand the result so we have one value per UTF-16 code unit.

            // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
            // leaving the remaining widths zero.  Not nice.
            const jchar *chars = text + start;
            for (int i = count, p = widths - 1; --i > p;) {
                if (chars[i] >= 0xdc00 && chars[i] < 0xe000 &&
                        chars[i-1] >= 0xd800 && chars[i-1] < 0xdc00) {
                    scalarArray[i] = 0;
                } else {
                  scalarArray[i] = scalarArray[--p];
                }
            }
        }
#endif
        jint pos = offset - start;
        switch (opt) {
        case AFTER:
+0 −3
Original line number Diff line number Diff line
@@ -45,9 +45,6 @@ static RtlDebugLevel readRtlDebugLevel() {
    return kRtlDebugDisabled;
}

// Define if we want to use Harfbuzz (1) or not (0)
#define RTL_USE_HARFBUZZ 1

// Define if we want (1) to have Advances debug values or not (0)
#define DEBUG_ADVANCES 0

+58 −218
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@
 * limitations under the License.
 */

#define LOG_TAG "TextLayout"

#include "TextLayout.h"
#include "TextLayoutCache.h"

@@ -46,208 +48,32 @@ bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
    return false;
}

/**
 * Character-based Arabic shaping.
 *
 * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
 *
 * @context the text context
 * @start the start of the text to render
 * @count the length of the text to render, start + count  must be <= contextCount
 * @contextCount the length of the context
 * @shaped where to put the shaped text, must have capacity for count uchars
 * @return the length of the shaped text, or -1 if error
 */
int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
                        jchar* shaped, UErrorCode& status) {
    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
    jchar* buffer = tempBuffer.get();

    // Use fixed length since we need to keep start and count valid
    u_shapeArabic(context, contextCount, buffer, contextCount,
                   U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
                   U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
                   U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);

    if (U_SUCCESS(status)) {
        // trim out UNICODE_NOT_A_CHAR following ligatures, if any
        int end = 0;
        for (int i = start, e = start + count; i < e; ++i) {
            if (buffer[i] != UNICODE_NOT_A_CHAR) {
                buffer[end++] = buffer[i];
            }
        }
        count = end;
        // ALOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
        ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
                           | UBIDI_KEEP_BASE_COMBINING, &status);
        if (U_SUCCESS(status)) {
            return count;
        }
    }
    return -1;
}

/**
 * Basic character-based layout supporting rtl and arabic shaping.
 * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
 * the length.
 * @text the text
 * @len the length of the text in uchars
 * @dir receives the resolved paragraph direction
 * @buffer the buffer to receive the reordered, shaped line.  Must have capacity of
 * at least len jchars.
 * @flags line bidi flags
 * @return the length of the reordered, shaped line, or -1 if error
 */
jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int& dir, jchar* buffer,
        UErrorCode& status) {
    static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
            UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;

    UBiDiLevel bidiReq = 0;
    switch (flags) {
    case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
    case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
    }

    int32_t result = -1;

    UBiDi* bidi = ubidi_open();
    if (bidi) {
        ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
        if (U_SUCCESS(status)) {
            dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl

            int rc = ubidi_countRuns(bidi, &status);
            if (U_SUCCESS(status)) {
                // ALOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);

                int32_t slen = 0;
                for (int i = 0; i < rc; ++i) {
                    int32_t start;
                    int32_t length;
                    UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);

                    if (runDir == UBIDI_RTL) {
                        slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
                    } else {
                        memcpy(buffer + slen, text + start, length * sizeof(jchar));
                        slen += length;
                    }
                }
                if (U_SUCCESS(status)) {
                    result = slen;
                }
            }
        }
        ubidi_close(bidi);
    }

    return result;
}

bool TextLayout::prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
        const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
    const jchar *workText = text;
    jchar *buffer = NULL;
    int dir = kDirection_LTR;
    if (needsLayout(text, len, bidiFlags)) {
        buffer =(jchar *) malloc(len * sizeof(jchar));
        if (!buffer) {
            return false;
        }
        UErrorCode status = U_ZERO_ERROR;
        len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
        if (!U_SUCCESS(status)) {
            ALOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
            free(buffer);
            return false; // can't render
        }
        workText = buffer; // use the shaped text
    }

    bool trimLeft = false;
    bool trimRight = false;

    SkPaint::Align horiz = paint->getTextAlign();
    switch (horiz) {
        case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
        case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
        case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
        default: break;
    }
    const jchar* workLimit = workText + len;

    if (trimLeft) {
        while (workText < workLimit && *workText == ' ') {
            ++workText;
        }
    }
    if (trimRight) {
        while (workLimit > workText && *(workLimit - 1) == ' ') {
            --workLimit;
        }
    }

    *outBytes = (workLimit - workText) << 1;
    *outText = workText;
    *outBuffer = buffer;

    return true;
}

// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
// This will draw if canvas is not null, otherwise path must be non-null and it will create
// a path representing the text that would have been drawn.
void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
                            jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
    const jchar *workText;
    jchar *buffer = NULL;
    int32_t workBytes;
    if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) {
        SkScalar x_ = SkFloatToScalar(x);
        SkScalar y_ = SkFloatToScalar(y);
        if (canvas) {
            canvas->drawText(workText, workBytes, x_, y_, *paint);
        } else {
            paint->getTextPath(workText, workBytes, x_, y_, path);
        }
        free(buffer);
    }
}

bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
        jsize contextCount, jchar* shaped) {
    UErrorCode status = U_ZERO_ERROR;
    count = shapeRtlText(context, start, count, contextCount, shaped, status);
    if (U_SUCCESS(status)) {
        return true;
    } else {
        LOGW("drawTextRun error %d\n", status);
    }
    return false;
    sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
    // Return advances from the cache. Compute them if needed
    value = TextLayoutCache::getInstance().getValue(paint, text, 0, len,
            len, bidiFlags);
#else
    value = new TextLayoutCacheValue(len);
    TextLayoutEngine::getInstance().computeValues(value.get(), paint,
            reinterpret_cast<const UChar*>(text), 0, len, len, bidiFlags);
#endif
    if (value == NULL) {
        LOGE("Cannot get TextLayoutCache value for text = '%s'",
                String8(text, len).string());
        return ;
    }

void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
                             jint start, jint count, jint contextCount,
                             int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {

    SkScalar x_ = SkFloatToScalar(x);
    SkScalar y_ = SkFloatToScalar(y);

     uint8_t rtl = dirFlags & 0x1;
     if (rtl) {
         SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(contextCount);
         if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
             canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
         }
    if (canvas) {
        canvas->drawText(value->getGlyphs(), value->getGlyphsCount() * 2, x_, y_, *paint);
    } else {
         canvas->drawText(chars + start, count << 1, x_, y_, *paint);
        paint->getTextPath(value->getGlyphs(), value->getGlyphsCount() * 2, x_, y_, path);
    }
}

@@ -260,10 +86,15 @@ void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint sta
    value = TextLayoutCache::getInstance().getValue(paint, chars, start, count,
            contextCount, dirFlags);
#else
    value = new TextLayoutCacheValue();
    value->computeValues(paint, chars, start, count, contextCount, dirFlags);
    value = new TextLayoutCacheValue(contextCount);
    TextLayoutEngine::getInstance().computeValues(value.get(), paint,
            reinterpret_cast<const UChar*>(chars), start, count, contextCount, dirFlags);
#endif
    if (value != NULL) {
    if (value == NULL) {
        LOGE("Cannot get TextLayoutCache value for text = '%s'",
                String8(chars + start, count).string());
        return ;
    }
    if (resultAdvances) {
        memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat));
    }
@@ -271,7 +102,6 @@ void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint sta
        *resultTotalAdvance = value->getTotalAdvance();
    }
}
}

void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
                                    jint count, jint contextCount, jint dirFlags,
@@ -281,12 +111,6 @@ void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint
            resultAdvances, &resultTotalAdvance);
}

// Draws a paragraph of text on a single line, running bidi and shaping
void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
                          int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
    handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
}

void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
    handleText(paint, text, len, bidiFlags, x, y, NULL, path);
@@ -305,14 +129,30 @@ void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
        return;
    }

    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count);

    int dir = kDirection_LTR;
    UErrorCode status = U_ZERO_ERROR;
    count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
    if (U_SUCCESS(status)) {
        canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
    sp<TextLayoutCacheValue> value;
#if USE_TEXT_LAYOUT_CACHE
    value = TextLayoutCache::getInstance().getValue(paint, text, 0, count,
            count, bidiFlags);
#else
    value = new TextLayoutCacheValue(count);
    TextLayoutEngine::getInstance().computeValues(value.get(), paint,
            reinterpret_cast<const UChar*>(text), 0, count, count, bidiFlags);
#endif
    if (value == NULL) {
        LOGE("Cannot get TextLayoutCache value for text = '%s'",
                String8(text, count).string());
        return ;
    }

    // Save old text encoding
    SkPaint::TextEncoding oldEncoding = paint->getTextEncoding();
    // Define Glyph encoding
    paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);

    canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path, h_, v_, *paint);

    // Get back old encoding
    paint->setTextEncoding(oldEncoding);
}

void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+1 −21
Original line number Diff line number Diff line
@@ -62,13 +62,6 @@ enum {
class TextLayout {
public:

    /*
     * Draws a unidirectional run of text.
     */
    static void drawTextRun(SkPaint* paint, const jchar* chars,
                            jint start, jint count, jint contextCount,
                            int dirFlags, jfloat x, jfloat y, SkCanvas* canvas);

    static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
                                   jint count, jint contextCount, jint dirFlags,
                                   jfloat* resultAdvances, jfloat* resultTotalAdvance);
@@ -77,9 +70,6 @@ public:
                                   jint count, jint contextCount, jint dirFlags,
                                   jfloat* resultAdvances, jfloat& resultTotalAdvance);

    static void drawText(SkPaint* paint, const jchar* text, jsize len,
                         jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);

    static void getTextPath(SkPaint* paint, const jchar* text, jsize len,
                            jint bidiFlags, jfloat x, jfloat y, SkPath* path);

@@ -87,19 +77,9 @@ public:
                               int bidiFlags, jfloat hOffset, jfloat vOffset,
                               SkPath* path, SkCanvas* canvas);

    static bool prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
        const jchar** outText, int32_t* outBytes, jchar** outBuffer);

    static bool prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
        jsize contextCount, jchar* shaped);
        

private:
    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
    static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
                            jchar* shaped, UErrorCode& status);
    static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
                           UErrorCode &status);

    static void handleText(SkPaint* paint, const jchar* text, jsize len,
                           int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas, SkPath* path);

Loading