Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "TextLayout.h"
     18 #include "TextLayoutCache.h"
     19 
     20 #include <android_runtime/AndroidRuntime.h>
     21 
     22 #include "SkTemplates.h"
     23 #include "unicode/ubidi.h"
     24 #include "unicode/ushape.h"
     25 #include <utils/Log.h>
     26 
     27 namespace android {
     28 
     29 // Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
     30 // bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
     31 // looking for a character >= the first RTL character in unicode and assume we do if
     32 // we find one.
     33 bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
     34     if (bidiFlags == kBidi_Force_LTR) {
     35         return false;
     36     }
     37     if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
     38             bidiFlags == kBidi_Force_RTL) {
     39         return true;
     40     }
     41     for (int i = 0; i < len; ++i) {
     42         if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
     43             return true;
     44         }
     45     }
     46     return false;
     47 }
     48 
     49 /**
     50  * Character-based Arabic shaping.
     51  *
     52  * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
     53  *
     54  * @context the text context
     55  * @start the start of the text to render
     56  * @count the length of the text to render, start + count  must be <= contextCount
     57  * @contextCount the length of the context
     58  * @shaped where to put the shaped text, must have capacity for count uchars
     59  * @return the length of the shaped text, or -1 if error
     60  */
     61 int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
     62                         jchar* shaped, UErrorCode& status) {
     63     SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
     64     jchar* buffer = tempBuffer.get();
     65 
     66     // Use fixed length since we need to keep start and count valid
     67     u_shapeArabic(context, contextCount, buffer, contextCount,
     68                    U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
     69                    U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
     70                    U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
     71 
     72     if (U_SUCCESS(status)) {
     73         // trim out UNICODE_NOT_A_CHAR following ligatures, if any
     74         int end = 0;
     75         for (int i = start, e = start + count; i < e; ++i) {
     76             if (buffer[i] != UNICODE_NOT_A_CHAR) {
     77                 buffer[end++] = buffer[i];
     78             }
     79         }
     80         count = end;
     81         // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
     82         ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
     83                            | UBIDI_KEEP_BASE_COMBINING, &status);
     84         if (U_SUCCESS(status)) {
     85             return count;
     86         }
     87     }
     88     return -1;
     89 }
     90 
     91 /**
     92  * Basic character-based layout supporting rtl and arabic shaping.
     93  * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
     94  * the length.
     95  * @text the text
     96  * @len the length of the text in uchars
     97  * @dir receives the resolved paragraph direction
     98  * @buffer the buffer to receive the reordered, shaped line.  Must have capacity of
     99  * at least len jchars.
    100  * @flags line bidi flags
    101  * @return the length of the reordered, shaped line, or -1 if error
    102  */
    103 jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int& dir, jchar* buffer,
    104         UErrorCode& status) {
    105     static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
    106             UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
    107 
    108     UBiDiLevel bidiReq = 0;
    109     switch (flags) {
    110     case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    111     case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    112     case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    113     case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    114     case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
    115     case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
    116     }
    117 
    118     int32_t result = -1;
    119 
    120     UBiDi* bidi = ubidi_open();
    121     if (bidi) {
    122         ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
    123         if (U_SUCCESS(status)) {
    124             dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
    125 
    126             int rc = ubidi_countRuns(bidi, &status);
    127             if (U_SUCCESS(status)) {
    128                 // LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
    129 
    130                 int32_t slen = 0;
    131                 for (int i = 0; i < rc; ++i) {
    132                     int32_t start;
    133                     int32_t length;
    134                     UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
    135 
    136                     if (runDir == UBIDI_RTL) {
    137                         slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
    138                     } else {
    139                         memcpy(buffer + slen, text + start, length * sizeof(jchar));
    140                         slen += length;
    141                     }
    142                 }
    143                 if (U_SUCCESS(status)) {
    144                     result = slen;
    145                 }
    146             }
    147         }
    148         ubidi_close(bidi);
    149     }
    150 
    151     return result;
    152 }
    153 
    154 bool TextLayout::prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
    155         const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
    156     const jchar *workText = text;
    157     jchar *buffer = NULL;
    158     int dir = kDirection_LTR;
    159     if (needsLayout(text, len, bidiFlags)) {
    160         buffer =(jchar *) malloc(len * sizeof(jchar));
    161         if (!buffer) {
    162             return false;
    163         }
    164         UErrorCode status = U_ZERO_ERROR;
    165         len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
    166         if (!U_SUCCESS(status)) {
    167             LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
    168             free(buffer);
    169             return false; // can't render
    170         }
    171         workText = buffer; // use the shaped text
    172     }
    173 
    174     bool trimLeft = false;
    175     bool trimRight = false;
    176 
    177     SkPaint::Align horiz = paint->getTextAlign();
    178     switch (horiz) {
    179         case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
    180         case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
    181         case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
    182         default: break;
    183     }
    184     const jchar* workLimit = workText + len;
    185 
    186     if (trimLeft) {
    187         while (workText < workLimit && *workText == ' ') {
    188             ++workText;
    189         }
    190     }
    191     if (trimRight) {
    192         while (workLimit > workText && *(workLimit - 1) == ' ') {
    193             --workLimit;
    194         }
    195     }
    196 
    197     *outBytes = (workLimit - workText) << 1;
    198     *outText = workText;
    199     *outBuffer = buffer;
    200 
    201     return true;
    202 }
    203 
    204 // Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
    205 // This will draw if canvas is not null, otherwise path must be non-null and it will create
    206 // a path representing the text that would have been drawn.
    207 void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
    208                             jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
    209     const jchar *workText;
    210     jchar *buffer = NULL;
    211     int32_t workBytes;
    212     if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) {
    213         SkScalar x_ = SkFloatToScalar(x);
    214         SkScalar y_ = SkFloatToScalar(y);
    215         if (canvas) {
    216             canvas->drawText(workText, workBytes, x_, y_, *paint);
    217         } else {
    218             paint->getTextPath(workText, workBytes, x_, y_, path);
    219         }
    220         free(buffer);
    221     }
    222 }
    223 
    224 bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
    225         jsize contextCount, jchar* shaped) {
    226     UErrorCode status = U_ZERO_ERROR;
    227     count = shapeRtlText(context, start, count, contextCount, shaped, status);
    228     if (U_SUCCESS(status)) {
    229         return true;
    230     } else {
    231         LOGW("drawTextRun error %d\n", status);
    232     }
    233     return false;
    234 }
    235 
    236 void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
    237                              jint start, jint count, jint contextCount,
    238                              int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
    239 
    240      SkScalar x_ = SkFloatToScalar(x);
    241      SkScalar y_ = SkFloatToScalar(y);
    242 
    243      uint8_t rtl = dirFlags & 0x1;
    244      if (rtl) {
    245          SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(contextCount);
    246          if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
    247              canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
    248          }
    249      } else {
    250          canvas->drawText(chars + start, count << 1, x_, y_, *paint);
    251      }
    252  }
    253 
    254 void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
    255                                     jint count, jint contextCount, jint dirFlags,
    256                                     jfloat* resultAdvances, jfloat* resultTotalAdvance) {
    257     sp<TextLayoutCacheValue> value;
    258 #if USE_TEXT_LAYOUT_CACHE
    259     // Return advances from the cache. Compute them if needed
    260     value = TextLayoutCache::getInstance().getValue(paint, chars, start, count,
    261             contextCount, dirFlags);
    262 #else
    263     value = new TextLayoutCacheValue();
    264     value->computeValues(paint, chars, start, count, contextCount, dirFlags);
    265 #endif
    266     if (value != NULL) {
    267         if (resultAdvances) {
    268             memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat));
    269         }
    270         if (resultTotalAdvance) {
    271             *resultTotalAdvance = value->getTotalAdvance();
    272         }
    273     }
    274 }
    275 
    276 void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
    277                                     jint count, jint contextCount, jint dirFlags,
    278                                     jfloat* resultAdvances, jfloat& resultTotalAdvance) {
    279     // Compute advances and return them
    280     computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
    281             resultAdvances, &resultTotalAdvance);
    282 }
    283 
    284 // Draws a paragraph of text on a single line, running bidi and shaping
    285 void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
    286                           int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
    287     handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
    288 }
    289 
    290 void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
    291                              jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
    292     handleText(paint, text, len, bidiFlags, x, y, NULL, path);
    293 }
    294 
    295 
    296 void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
    297                                 int bidiFlags, jfloat hOffset, jfloat vOffset,
    298                                 SkPath* path, SkCanvas* canvas) {
    299 
    300     SkScalar h_ = SkFloatToScalar(hOffset);
    301     SkScalar v_ = SkFloatToScalar(vOffset);
    302 
    303     if (!needsLayout(text, count, bidiFlags)) {
    304         canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint);
    305         return;
    306     }
    307 
    308     SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count);
    309 
    310     int dir = kDirection_LTR;
    311     UErrorCode status = U_ZERO_ERROR;
    312     count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
    313     if (U_SUCCESS(status)) {
    314         canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
    315     }
    316 }
    317 
    318 void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
    319         size_t start, size_t count, size_t contextCount, int dirFlags,
    320         jfloat* outAdvances, jfloat* outTotalAdvance) {
    321     SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
    322     jchar* buffer = tempBuffer.get();
    323     SkScalar* scalarArray = (SkScalar*)outAdvances;
    324 
    325     // this is where we'd call harfbuzz
    326     // for now we just use ushape.c
    327     size_t widths;
    328     const jchar* text;
    329     if (dirFlags & 0x1) { // rtl, call arabic shaping in case
    330         UErrorCode status = U_ZERO_ERROR;
    331         // Use fixed length since we need to keep start and count valid
    332         u_shapeArabic(chars, contextCount, buffer, contextCount,
    333                 U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
    334                 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
    335                 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
    336         // we shouldn't fail unless there's an out of memory condition,
    337         // in which case we're hosed anyway
    338         for (int i = start, e = i + count; i < e; ++i) {
    339             if (buffer[i] == UNICODE_NOT_A_CHAR) {
    340                 buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
    341             }
    342         }
    343         text = buffer + start;
    344         widths = paint->getTextWidths(text, count << 1, scalarArray);
    345     } else {
    346         text = chars + start;
    347         widths = paint->getTextWidths(text, count << 1, scalarArray);
    348     }
    349 
    350     jfloat totalAdvance = 0;
    351     if (widths < count) {
    352 #if DEBUG_ADVANCES
    353     LOGD("ICU -- count=%d", widths);
    354 #endif
    355         // Skia operates on code points, not code units, so surrogate pairs return only
    356         // one value. Expand the result so we have one value per UTF-16 code unit.
    357 
    358         // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
    359         // leaving the remaining widths zero.  Not nice.
    360         for (size_t i = 0, p = 0; i < widths; ++i) {
    361             totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
    362             if (p < count &&
    363                     text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
    364                     text[p] < UNICODE_FIRST_PRIVATE_USE &&
    365                     text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
    366                     text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
    367                 outAdvances[p++] = 0;
    368             }
    369 #if DEBUG_ADVANCES
    370             LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
    371 #endif
    372         }
    373     } else {
    374 #if DEBUG_ADVANCES
    375     LOGD("ICU -- count=%d", count);
    376 #endif
    377         for (size_t i = 0; i < count; i++) {
    378             totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
    379 #if DEBUG_ADVANCES
    380             LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
    381 #endif
    382         }
    383     }
    384     *outTotalAdvance = totalAdvance;
    385 }
    386 
    387 }
    388