TextLayout.cpp revision a51f0e707f1f3142358aa919ea60ad2842803139
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 27namespace 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. 33bool 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 */ 61int 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 // ALOG(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 */ 103jint 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 // ALOG(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 154bool 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 ALOG(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. 207void 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 224bool 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 ALOGW("drawTextRun error %d\n", status); 232 } 233 return false; 234} 235 236void 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 254void 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 276void 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 285void 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 290void 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 296void 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 318void 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 ALOGD("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 ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 371#endif 372 } 373 } else { 374#if DEBUG_ADVANCES 375 ALOGD("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 ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 381#endif 382 } 383 } 384 *outTotalAdvance = totalAdvance; 385} 386 387} 388