Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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" |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 18 | #include "TextLayoutCache.h" |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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 | |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 27 | namespace android { |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 28 | |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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) { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 42 | if (text[i] >= UNICODE_FIRST_RTL_CHAR) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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, |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 62 | jchar* shaped, UErrorCode& status) { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 63 | SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); |
| 64 | jchar* buffer = tempBuffer.get(); |
| 65 | |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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)) { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 73 | // trim out UNICODE_NOT_A_CHAR following ligatures, if any |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 74 | int end = 0; |
| 75 | for (int i = start, e = start + count; i < e; ++i) { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 76 | if (buffer[i] != UNICODE_NOT_A_CHAR) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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 | } |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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 | */ |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 103 | jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int& dir, jchar* buffer, |
| 104 | UErrorCode& status) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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 | |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 154 | bool TextLayout::prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags, |
Romain Guy | 9226298 | 2010-07-26 11:17:54 -0700 | [diff] [blame] | 155 | const jchar** outText, int32_t* outBytes, jchar** outBuffer) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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) { |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 162 | return false; |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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); |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 169 | return false; // can't render |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 170 | } |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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) { |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 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; |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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 | |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 197 | *outBytes = (workLimit - workText) << 1; |
| 198 | *outText = workText; |
Romain Guy | 9226298 | 2010-07-26 11:17:54 -0700 | [diff] [blame] | 199 | *outBuffer = buffer; |
| 200 | |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 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; |
Romain Guy | 9226298 | 2010-07-26 11:17:54 -0700 | [diff] [blame] | 210 | jchar *buffer = NULL; |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 211 | int32_t workBytes; |
Romain Guy | 9226298 | 2010-07-26 11:17:54 -0700 | [diff] [blame] | 212 | if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) { |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 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 | } |
Romain Guy | 9226298 | 2010-07-26 11:17:54 -0700 | [diff] [blame] | 220 | free(buffer); |
Romain Guy | e8e62a4 | 2010-07-23 18:55:21 -0700 | [diff] [blame] | 221 | } |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 222 | } |
| 223 | |
Romain Guy | 61c8c9c | 2010-08-09 20:48:09 -0700 | [diff] [blame] | 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 { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 231 | LOGW("drawTextRun error %d\n", status); |
Romain Guy | 61c8c9c | 2010-08-09 20:48:09 -0700 | [diff] [blame] | 232 | } |
| 233 | return false; |
| 234 | } |
| 235 | |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 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) { |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 245 | SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(contextCount); |
Romain Guy | 61c8c9c | 2010-08-09 20:48:09 -0700 | [diff] [blame] | 246 | if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 247 | canvas->drawText(buffer.get(), count << 1, x_, y_, *paint); |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 248 | } |
| 249 | } else { |
| 250 | canvas->drawText(chars + start, count << 1, x_, y_, *paint); |
| 251 | } |
| 252 | } |
| 253 | |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 254 | void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start, |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 255 | jint count, jint contextCount, jint dirFlags, |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 256 | jfloat* resultAdvances, jfloat& resultTotalAdvance) { |
| 257 | #if USE_TEXT_LAYOUT_CACHE |
| 258 | // Return advances from the cache. Compute them if needed |
Fabrice Di Meglio | fcf2be1 | 2011-04-05 17:02:36 -0700 | [diff] [blame] | 259 | sp<TextLayoutCacheValue> layout = gTextLayoutCache.getValue( |
| 260 | paint, chars, start, count, contextCount, dirFlags); |
| 261 | if (layout != NULL) { |
Fabrice Di Meglio | 4f810c8 | 2011-04-19 14:53:58 -0700 | [diff] [blame] | 262 | if (resultAdvances != NULL) { |
| 263 | memcpy(resultAdvances, layout->getAdvances(), layout->getAdvancesCount() * sizeof(jfloat)); |
| 264 | } |
Fabrice Di Meglio | fcf2be1 | 2011-04-05 17:02:36 -0700 | [diff] [blame] | 265 | resultTotalAdvance = layout->getTotalAdvance(); |
| 266 | } |
Fabrice Di Meglio | d313c66 | 2011-02-24 19:56:18 -0800 | [diff] [blame] | 267 | #else |
| 268 | // Compute advances and return them |
Fabrice Di Meglio | fcf2be1 | 2011-04-05 17:02:36 -0700 | [diff] [blame] | 269 | TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, |
| 270 | dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL ); |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 271 | #endif |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 272 | } |
| 273 | |
Fabrice Di Meglio | eee49c6 | 2011-03-24 17:21:23 -0700 | [diff] [blame] | 274 | void TextLayout::getTextRunAdvancesHB(SkPaint* paint, const jchar* chars, jint start, |
| 275 | jint count, jint contextCount, jint dirFlags, |
| 276 | jfloat* resultAdvances, jfloat& resultTotalAdvance) { |
| 277 | // Compute advances and return them |
Fabrice Di Meglio | fcf2be1 | 2011-04-05 17:02:36 -0700 | [diff] [blame] | 278 | TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, |
| 279 | dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL); |
Fabrice Di Meglio | eee49c6 | 2011-03-24 17:21:23 -0700 | [diff] [blame] | 280 | } |
| 281 | |
| 282 | void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, |
| 283 | jint count, jint contextCount, jint dirFlags, |
| 284 | jfloat* resultAdvances, jfloat& resultTotalAdvance) { |
| 285 | // Compute advances and return them |
Fabrice Di Meglio | 1de9e7a | 2011-04-05 13:43:18 -0700 | [diff] [blame] | 286 | TextLayoutCacheValue::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, |
Fabrice Di Meglio | eee49c6 | 2011-03-24 17:21:23 -0700 | [diff] [blame] | 287 | resultAdvances, &resultTotalAdvance); |
| 288 | } |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 289 | |
| 290 | // Draws a paragraph of text on a single line, running bidi and shaping |
| 291 | void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len, |
| 292 | int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) { |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 293 | handleText(paint, text, len, bidiFlags, x, y, canvas, NULL); |
| 294 | } |
| 295 | |
| 296 | void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len, |
| 297 | jint bidiFlags, jfloat x, jfloat y, SkPath *path) { |
| 298 | handleText(paint, text, len, bidiFlags, x, y, NULL, path); |
| 299 | } |
| 300 | |
| 301 | |
| 302 | void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count, |
| 303 | int bidiFlags, jfloat hOffset, jfloat vOffset, |
| 304 | SkPath* path, SkCanvas* canvas) { |
| 305 | |
| 306 | SkScalar h_ = SkFloatToScalar(hOffset); |
| 307 | SkScalar v_ = SkFloatToScalar(vOffset); |
| 308 | |
| 309 | if (!needsLayout(text, count, bidiFlags)) { |
| 310 | canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint); |
| 311 | return; |
| 312 | } |
| 313 | |
Fabrice Di Meglio | dd347df | 2011-02-17 13:22:08 -0800 | [diff] [blame] | 314 | SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count); |
| 315 | |
Doug Felt | f7cb1f7 | 2010-07-01 16:20:43 -0700 | [diff] [blame] | 316 | int dir = kDirection_LTR; |
| 317 | UErrorCode status = U_ZERO_ERROR; |
| 318 | count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status); |
| 319 | if (U_SUCCESS(status)) { |
| 320 | canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint); |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | } |