TextLayout.cpp revision 5baa3a62a97544669fba6d65a11c07f252e654dd
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#define LOG_TAG "TextLayout" 18 19#include "TextLayout.h" 20#include "TextLayoutCache.h" 21 22#include <android_runtime/AndroidRuntime.h> 23 24#include "SkTemplates.h" 25#include "unicode/ubidi.h" 26#include "unicode/ushape.h" 27#include <utils/Log.h> 28 29namespace android { 30 31// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if 32// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text 33// looking for a character >= the first RTL character in unicode and assume we do if 34// we find one. 35bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) { 36 if (bidiFlags == kBidi_Force_LTR) { 37 return false; 38 } 39 if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) || 40 bidiFlags == kBidi_Force_RTL) { 41 return true; 42 } 43 for (int i = 0; i < len; ++i) { 44 if (text[i] >= UNICODE_FIRST_RTL_CHAR) { 45 return true; 46 } 47 } 48 return false; 49} 50 51// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping. 52// This will draw if canvas is not null, otherwise path must be non-null and it will create 53// a path representing the text that would have been drawn. 54void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len, 55 jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) { 56 sp<TextLayoutCacheValue> value; 57#if USE_TEXT_LAYOUT_CACHE 58 // Return advances from the cache. Compute them if needed 59 value = TextLayoutCache::getInstance().getValue(paint, text, 0, len, 60 len, bidiFlags); 61#else 62 value = new TextLayoutCacheValue(len); 63 TextLayoutEngine::getInstance().computeValues(value.get(), paint, 64 reinterpret_cast<const UChar*>(text), 0, len, len, bidiFlags); 65#endif 66 if (value == NULL) { 67 LOGE("Cannot get TextLayoutCache value for text = '%s'", 68 String8(text, len).string()); 69 return ; 70 } 71 SkScalar x_ = SkFloatToScalar(x); 72 SkScalar y_ = SkFloatToScalar(y); 73 if (canvas) { 74 canvas->drawText(value->getGlyphs(), value->getGlyphsCount() * 2, x_, y_, *paint); 75 } else { 76 paint->getTextPath(value->getGlyphs(), value->getGlyphsCount() * 2, x_, y_, path); 77 } 78} 79 80void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start, 81 jint count, jint contextCount, jint dirFlags, 82 jfloat* resultAdvances, jfloat* resultTotalAdvance) { 83 sp<TextLayoutCacheValue> value; 84#if USE_TEXT_LAYOUT_CACHE 85 // Return advances from the cache. Compute them if needed 86 value = TextLayoutCache::getInstance().getValue(paint, chars, start, count, 87 contextCount, dirFlags); 88#else 89 value = new TextLayoutCacheValue(contextCount); 90 TextLayoutEngine::getInstance().computeValues(value.get(), paint, 91 reinterpret_cast<const UChar*>(chars), start, count, contextCount, dirFlags); 92#endif 93 if (value == NULL) { 94 LOGE("Cannot get TextLayoutCache value for text = '%s'", 95 String8(chars + start, count).string()); 96 return ; 97 } 98 if (resultAdvances) { 99 memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat)); 100 } 101 if (resultTotalAdvance) { 102 *resultTotalAdvance = value->getTotalAdvance(); 103 } 104} 105 106void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, 107 jint count, jint contextCount, jint dirFlags, 108 jfloat* resultAdvances, jfloat& resultTotalAdvance) { 109 // Compute advances and return them 110 computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, 111 resultAdvances, &resultTotalAdvance); 112} 113 114void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len, 115 jint bidiFlags, jfloat x, jfloat y, SkPath *path) { 116 handleText(paint, text, len, bidiFlags, x, y, NULL, path); 117} 118 119 120void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count, 121 int bidiFlags, jfloat hOffset, jfloat vOffset, 122 SkPath* path, SkCanvas* canvas) { 123 124 SkScalar h_ = SkFloatToScalar(hOffset); 125 SkScalar v_ = SkFloatToScalar(vOffset); 126 127 if (!needsLayout(text, count, bidiFlags)) { 128 canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint); 129 return; 130 } 131 132 sp<TextLayoutCacheValue> value; 133#if USE_TEXT_LAYOUT_CACHE 134 value = TextLayoutCache::getInstance().getValue(paint, text, 0, count, 135 count, bidiFlags); 136#else 137 value = new TextLayoutCacheValue(count); 138 TextLayoutEngine::getInstance().computeValues(value.get(), paint, 139 reinterpret_cast<const UChar*>(text), 0, count, count, bidiFlags); 140#endif 141 if (value == NULL) { 142 LOGE("Cannot get TextLayoutCache value for text = '%s'", 143 String8(text, count).string()); 144 return ; 145 } 146 147 // Save old text encoding 148 SkPaint::TextEncoding oldEncoding = paint->getTextEncoding(); 149 // Define Glyph encoding 150 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); 151 152 canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path, h_, v_, *paint); 153 154 // Get back old encoding 155 paint->setTextEncoding(oldEncoding); 156} 157 158void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars, 159 size_t start, size_t count, size_t contextCount, int dirFlags, 160 jfloat* outAdvances, jfloat* outTotalAdvance) { 161 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); 162 jchar* buffer = tempBuffer.get(); 163 SkScalar* scalarArray = (SkScalar*)outAdvances; 164 165 // this is where we'd call harfbuzz 166 // for now we just use ushape.c 167 size_t widths; 168 const jchar* text; 169 if (dirFlags & 0x1) { // rtl, call arabic shaping in case 170 UErrorCode status = U_ZERO_ERROR; 171 // Use fixed length since we need to keep start and count valid 172 u_shapeArabic(chars, contextCount, buffer, contextCount, 173 U_SHAPE_LENGTH_FIXED_SPACES_NEAR | 174 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE | 175 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status); 176 // we shouldn't fail unless there's an out of memory condition, 177 // in which case we're hosed anyway 178 for (int i = start, e = i + count; i < e; ++i) { 179 if (buffer[i] == UNICODE_NOT_A_CHAR) { 180 buffer[i] = UNICODE_ZWSP; // zero-width-space for skia 181 } 182 } 183 text = buffer + start; 184 widths = paint->getTextWidths(text, count << 1, scalarArray); 185 } else { 186 text = chars + start; 187 widths = paint->getTextWidths(text, count << 1, scalarArray); 188 } 189 190 jfloat totalAdvance = 0; 191 if (widths < count) { 192#if DEBUG_ADVANCES 193 ALOGD("ICU -- count=%d", widths); 194#endif 195 // Skia operates on code points, not code units, so surrogate pairs return only 196 // one value. Expand the result so we have one value per UTF-16 code unit. 197 198 // Note, skia's getTextWidth gets confused if it encounters a surrogate pair, 199 // leaving the remaining widths zero. Not nice. 200 for (size_t i = 0, p = 0; i < widths; ++i) { 201 totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]); 202 if (p < count && 203 text[p] >= UNICODE_FIRST_LOW_SURROGATE && 204 text[p] < UNICODE_FIRST_PRIVATE_USE && 205 text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE && 206 text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { 207 outAdvances[p++] = 0; 208 } 209#if DEBUG_ADVANCES 210 ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 211#endif 212 } 213 } else { 214#if DEBUG_ADVANCES 215 ALOGD("ICU -- count=%d", count); 216#endif 217 for (size_t i = 0; i < count; i++) { 218 totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); 219#if DEBUG_ADVANCES 220 ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 221#endif 222 } 223 } 224 *outTotalAdvance = totalAdvance; 225} 226 227} 228