TextLayout.cpp revision 163268b3a8d4dd7e650e6c540f832bf60f6bf4c9
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 // 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 */ 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 // 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 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 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. 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 LOGW("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( 261 paint, chars, start, count, 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 != NULL) { 268 memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat)); 269 } 270 resultTotalAdvance = value->getTotalAdvance(); 271 } 272} 273 274void 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 278 TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, 279 dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL); 280} 281 282void 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 286 TextLayoutCacheValue::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, 287 resultAdvances, &resultTotalAdvance); 288} 289 290// Draws a paragraph of text on a single line, running bidi and shaping 291void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len, 292 int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) { 293 handleText(paint, text, len, bidiFlags, x, y, canvas, NULL); 294} 295 296void 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 302void 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 314 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count); 315 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} 325