blob: 46e6c2b3bc616d4b636dea61549e22dbd0678b4d [file] [log] [blame]
Doug Feltf7cb1f72010-07-01 16:20:43 -07001/*
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 Megliod313c662011-02-24 19:56:18 -080018#include "TextLayoutCache.h"
Doug Feltf7cb1f72010-07-01 16:20:43 -070019
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 Feltf7cb1f72010-07-01 16:20:43 -070027namespace android {
Fabrice Di Megliod313c662011-02-24 19:56:18 -080028
Doug Feltf7cb1f72010-07-01 16:20:43 -070029// 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) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080042 if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
Doug Feltf7cb1f72010-07-01 16:20:43 -070043 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,
Fabrice Di Megliod313c662011-02-24 19:56:18 -080062 jchar* shaped, UErrorCode& status) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080063 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
64 jchar* buffer = tempBuffer.get();
65
Doug Feltf7cb1f72010-07-01 16:20:43 -070066 // 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 Megliodd347df2011-02-17 13:22:08 -080073 // trim out UNICODE_NOT_A_CHAR following ligatures, if any
Doug Feltf7cb1f72010-07-01 16:20:43 -070074 int end = 0;
75 for (int i = start, e = start + count; i < e; ++i) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080076 if (buffer[i] != UNICODE_NOT_A_CHAR) {
Doug Feltf7cb1f72010-07-01 16:20:43 -070077 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 Feltf7cb1f72010-07-01 16:20:43 -070088 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 Megliod313c662011-02-24 19:56:18 -0800103jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int& dir, jchar* buffer,
104 UErrorCode& status) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700105 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 Megliod313c662011-02-24 19:56:18 -0800154bool TextLayout::prepareText(SkPaint* paint, const jchar* text, jsize len, jint bidiFlags,
Romain Guy92262982010-07-26 11:17:54 -0700155 const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700156 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 Guye8e62a42010-07-23 18:55:21 -0700162 return false;
Doug Feltf7cb1f72010-07-01 16:20:43 -0700163 }
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 Guye8e62a42010-07-23 18:55:21 -0700169 return false; // can't render
Doug Feltf7cb1f72010-07-01 16:20:43 -0700170 }
Doug Feltf7cb1f72010-07-01 16:20:43 -0700171 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 Guye8e62a42010-07-23 18:55:21 -0700179 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 Feltf7cb1f72010-07-01 16:20:43 -0700183 }
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 Guye8e62a42010-07-23 18:55:21 -0700197 *outBytes = (workLimit - workText) << 1;
198 *outText = workText;
Romain Guy92262982010-07-26 11:17:54 -0700199 *outBuffer = buffer;
200
Romain Guye8e62a42010-07-23 18:55:21 -0700201 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;
Romain Guy92262982010-07-26 11:17:54 -0700210 jchar *buffer = NULL;
Romain Guye8e62a42010-07-23 18:55:21 -0700211 int32_t workBytes;
Romain Guy92262982010-07-26 11:17:54 -0700212 if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) {
Romain Guye8e62a42010-07-23 18:55:21 -0700213 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 Guy92262982010-07-26 11:17:54 -0700220 free(buffer);
Romain Guye8e62a42010-07-23 18:55:21 -0700221 }
Doug Feltf7cb1f72010-07-01 16:20:43 -0700222}
223
Romain Guy61c8c9c2010-08-09 20:48:09 -0700224bool 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 Megliodd347df2011-02-17 13:22:08 -0800231 LOGW("drawTextRun error %d\n", status);
Romain Guy61c8c9c2010-08-09 20:48:09 -0700232 }
233 return false;
234}
235
Doug Feltf7cb1f72010-07-01 16:20:43 -0700236void 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 Megliodd347df2011-02-17 13:22:08 -0800245 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(contextCount);
Romain Guy61c8c9c2010-08-09 20:48:09 -0700246 if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700247 canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
Doug Feltf7cb1f72010-07-01 16:20:43 -0700248 }
249 } else {
250 canvas->drawText(chars + start, count << 1, x_, y_, *paint);
251 }
252 }
253
Fabrice Di Megliod313c662011-02-24 19:56:18 -0800254void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
Doug Feltf7cb1f72010-07-01 16:20:43 -0700255 jint count, jint contextCount, jint dirFlags,
Fabrice Di Megliod313c662011-02-24 19:56:18 -0800256 jfloat* resultAdvances, jfloat& resultTotalAdvance) {
257#if USE_TEXT_LAYOUT_CACHE
258 // Return advances from the cache. Compute them if needed
Fabrice Di Megliofcf2be12011-04-05 17:02:36 -0700259 sp<TextLayoutCacheValue> layout = gTextLayoutCache.getValue(
260 paint, chars, start, count, contextCount, dirFlags);
261 if (layout != NULL) {
Fabrice Di Meglio4f810c82011-04-19 14:53:58 -0700262 if (resultAdvances != NULL) {
263 memcpy(resultAdvances, layout->getAdvances(), layout->getAdvancesCount() * sizeof(jfloat));
264 }
Fabrice Di Megliofcf2be12011-04-05 17:02:36 -0700265 resultTotalAdvance = layout->getTotalAdvance();
266 }
Fabrice Di Megliod313c662011-02-24 19:56:18 -0800267#else
268 // Compute advances and return them
Fabrice Di Megliofcf2be12011-04-05 17:02:36 -0700269 TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount,
270 dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL );
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800271#endif
Doug Feltf7cb1f72010-07-01 16:20:43 -0700272}
273
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -0700274void 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 Megliofcf2be12011-04-05 17:02:36 -0700278 TextLayoutCacheValue::computeValuesWithHarfbuzz(paint, chars, start, count, contextCount,
279 dirFlags, resultAdvances, &resultTotalAdvance, NULL, NULL);
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -0700280}
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
Fabrice Di Meglio1de9e7a2011-04-05 13:43:18 -0700286 TextLayoutCacheValue::computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
Fabrice Di Meglioeee49c62011-03-24 17:21:23 -0700287 resultAdvances, &resultTotalAdvance);
288}
Doug Feltf7cb1f72010-07-01 16:20:43 -0700289
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) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700293 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
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800314 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count);
315
Doug Feltf7cb1f72010-07-01 16:20:43 -0700316 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}