blob: e9576351dcfece32d85cbe24db5d71f93a5da02d [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"
18
19#include <android_runtime/AndroidRuntime.h>
20
21#include "SkTemplates.h"
22#include "unicode/ubidi.h"
23#include "unicode/ushape.h"
24#include <utils/Log.h>
25
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080026// Log debug messages from RTL related allocations
27#define DEBUG_RTL_ALLOCATIONS 0
Doug Feltf7cb1f72010-07-01 16:20:43 -070028
29namespace android {
30// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if
31// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
32// looking for a character >= the first RTL character in unicode and assume we do if
33// we find one.
34bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
35 if (bidiFlags == kBidi_Force_LTR) {
36 return false;
37 }
38 if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
39 bidiFlags == kBidi_Force_RTL) {
40 return true;
41 }
42 for (int i = 0; i < len; ++i) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080043 if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
Doug Feltf7cb1f72010-07-01 16:20:43 -070044 return true;
45 }
46 }
47 return false;
48}
49
50/**
51 * Character-based Arabic shaping.
52 *
53 * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
54 *
55 * @context the text context
56 * @start the start of the text to render
57 * @count the length of the text to render, start + count must be <= contextCount
58 * @contextCount the length of the context
59 * @shaped where to put the shaped text, must have capacity for count uchars
60 * @return the length of the shaped text, or -1 if error
61 */
62int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
63 jchar* shaped, UErrorCode &status) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080064 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
65 jchar* buffer = tempBuffer.get();
66
67#if DEBUG_RTL_ALLOCATIONS
68 LOGD("TextLayout::shapeRtlText - allocated buffer with size: %d", contextCount);
69#endif
Doug Feltf7cb1f72010-07-01 16:20:43 -070070
71 // Use fixed length since we need to keep start and count valid
72 u_shapeArabic(context, contextCount, buffer, contextCount,
73 U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
74 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
75 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
76
77 if (U_SUCCESS(status)) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080078 // trim out UNICODE_NOT_A_CHAR following ligatures, if any
Doug Feltf7cb1f72010-07-01 16:20:43 -070079 int end = 0;
80 for (int i = start, e = start + count; i < e; ++i) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -080081 if (buffer[i] != UNICODE_NOT_A_CHAR) {
Doug Feltf7cb1f72010-07-01 16:20:43 -070082 buffer[end++] = buffer[i];
83 }
84 }
85 count = end;
86 // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
87 ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
88 | UBIDI_KEEP_BASE_COMBINING, &status);
89 if (U_SUCCESS(status)) {
90 return count;
91 }
92 }
Doug Feltf7cb1f72010-07-01 16:20:43 -070093 return -1;
94}
95
96/**
97 * Basic character-based layout supporting rtl and arabic shaping.
98 * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
99 * the length.
100 * @text the text
101 * @len the length of the text in uchars
102 * @dir receives the resolved paragraph direction
103 * @buffer the buffer to receive the reordered, shaped line. Must have capacity of
104 * at least len jchars.
105 * @flags line bidi flags
106 * @return the length of the reordered, shaped line, or -1 if error
107 */
108jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
109 UErrorCode &status) {
110 static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
111 UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
112
113 UBiDiLevel bidiReq = 0;
114 switch (flags) {
115 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
116 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
117 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
118 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
119 case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
120 case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
121 }
122
123 int32_t result = -1;
124
125 UBiDi* bidi = ubidi_open();
126 if (bidi) {
127 ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
128 if (U_SUCCESS(status)) {
129 dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
130
131 int rc = ubidi_countRuns(bidi, &status);
132 if (U_SUCCESS(status)) {
133 // LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
134
135 int32_t slen = 0;
136 for (int i = 0; i < rc; ++i) {
137 int32_t start;
138 int32_t length;
139 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
140
141 if (runDir == UBIDI_RTL) {
142 slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
143 } else {
144 memcpy(buffer + slen, text + start, length * sizeof(jchar));
145 slen += length;
146 }
147 }
148 if (U_SUCCESS(status)) {
149 result = slen;
150 }
151 }
152 }
153 ubidi_close(bidi);
154 }
155
156 return result;
157}
158
Romain Guye8e62a42010-07-23 18:55:21 -0700159bool TextLayout::prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
Romain Guy92262982010-07-26 11:17:54 -0700160 const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700161 const jchar *workText = text;
162 jchar *buffer = NULL;
163 int dir = kDirection_LTR;
164 if (needsLayout(text, len, bidiFlags)) {
165 buffer =(jchar *) malloc(len * sizeof(jchar));
166 if (!buffer) {
Romain Guye8e62a42010-07-23 18:55:21 -0700167 return false;
Doug Feltf7cb1f72010-07-01 16:20:43 -0700168 }
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800169
170#if DEBUG_RTL_ALLOCATIONS
171 LOGD("TextLayout::prepareText - allocated buffer with size: %d", len);
172#endif
173
Doug Feltf7cb1f72010-07-01 16:20:43 -0700174 UErrorCode status = U_ZERO_ERROR;
175 len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
176 if (!U_SUCCESS(status)) {
177 LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
178 free(buffer);
Romain Guye8e62a42010-07-23 18:55:21 -0700179 return false; // can't render
Doug Feltf7cb1f72010-07-01 16:20:43 -0700180 }
181
182 workText = buffer; // use the shaped text
183 }
184
185 bool trimLeft = false;
186 bool trimRight = false;
187
188 SkPaint::Align horiz = paint->getTextAlign();
189 switch (horiz) {
Romain Guye8e62a42010-07-23 18:55:21 -0700190 case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
191 case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
192 case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
193 default: break;
Doug Feltf7cb1f72010-07-01 16:20:43 -0700194 }
195 const jchar* workLimit = workText + len;
196
197 if (trimLeft) {
198 while (workText < workLimit && *workText == ' ') {
199 ++workText;
200 }
201 }
202 if (trimRight) {
203 while (workLimit > workText && *(workLimit - 1) == ' ') {
204 --workLimit;
205 }
206 }
207
Romain Guye8e62a42010-07-23 18:55:21 -0700208 *outBytes = (workLimit - workText) << 1;
209 *outText = workText;
Romain Guy92262982010-07-26 11:17:54 -0700210 *outBuffer = buffer;
211
Romain Guye8e62a42010-07-23 18:55:21 -0700212 return true;
213}
214
215// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
216// This will draw if canvas is not null, otherwise path must be non-null and it will create
217// a path representing the text that would have been drawn.
218void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
219 jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
220 const jchar *workText;
Romain Guy92262982010-07-26 11:17:54 -0700221 jchar *buffer = NULL;
Romain Guye8e62a42010-07-23 18:55:21 -0700222 int32_t workBytes;
Romain Guy92262982010-07-26 11:17:54 -0700223 if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) {
Romain Guye8e62a42010-07-23 18:55:21 -0700224 SkScalar x_ = SkFloatToScalar(x);
225 SkScalar y_ = SkFloatToScalar(y);
226 if (canvas) {
227 canvas->drawText(workText, workBytes, x_, y_, *paint);
228 } else {
229 paint->getTextPath(workText, workBytes, x_, y_, path);
230 }
Romain Guy92262982010-07-26 11:17:54 -0700231 free(buffer);
Romain Guye8e62a42010-07-23 18:55:21 -0700232 }
Doug Feltf7cb1f72010-07-01 16:20:43 -0700233}
234
Romain Guy61c8c9c2010-08-09 20:48:09 -0700235bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
236 jsize contextCount, jchar* shaped) {
237 UErrorCode status = U_ZERO_ERROR;
238 count = shapeRtlText(context, start, count, contextCount, shaped, status);
239 if (U_SUCCESS(status)) {
240 return true;
241 } else {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800242 LOGW("drawTextRun error %d\n", status);
Romain Guy61c8c9c2010-08-09 20:48:09 -0700243 }
244 return false;
245}
246
Doug Feltf7cb1f72010-07-01 16:20:43 -0700247void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
248 jint start, jint count, jint contextCount,
249 int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
250
251 SkScalar x_ = SkFloatToScalar(x);
252 SkScalar y_ = SkFloatToScalar(y);
253
254 uint8_t rtl = dirFlags & 0x1;
255 if (rtl) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800256 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(contextCount);
Romain Guy61c8c9c2010-08-09 20:48:09 -0700257 if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700258 canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
Doug Feltf7cb1f72010-07-01 16:20:43 -0700259 }
260 } else {
261 canvas->drawText(chars + start, count << 1, x_, y_, *paint);
262 }
263 }
264
265void TextLayout::getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
266 jint count, jint contextCount, jint dirFlags,
267 jfloat *resultAdvances, jfloat &resultTotalAdvance) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700268 resultTotalAdvance = 0;
269
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800270 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
271 jchar* buffer = tempBuffer.get();
272
273#if DEBUG_RTL_ALLOCATIONS
274 LOGD("TextLayout::getTextRunAdvances - allocated buffer with size: %d", contextCount);
275#endif
276
277 SkScalar* scalarArray = (SkScalar*)resultAdvances;
278
Doug Feltf7cb1f72010-07-01 16:20:43 -0700279 // this is where we'd call harfbuzz
280 // for now we just use ushape.c
281
282 int widths;
283 const jchar* text;
284 if (dirFlags & 0x1) { // rtl, call arabic shaping in case
285 UErrorCode status = U_ZERO_ERROR;
286 // Use fixed length since we need to keep start and count valid
287 u_shapeArabic(chars, contextCount, buffer, contextCount,
288 U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
289 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
290 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
291 // we shouldn't fail unless there's an out of memory condition,
292 // in which case we're hosed anyway
293 for (int i = start, e = i + count; i < e; ++i) {
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800294 if (buffer[i] == UNICODE_NOT_A_CHAR) {
295 buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
Doug Feltf7cb1f72010-07-01 16:20:43 -0700296 }
297 }
298 text = buffer + start;
299 widths = paint->getTextWidths(text, count << 1, scalarArray);
300 } else {
301 text = chars + start;
302 widths = paint->getTextWidths(text, count << 1, scalarArray);
303 }
304
305 if (widths < count) {
306 // Skia operates on code points, not code units, so surrogate pairs return only
307 // one value. Expand the result so we have one value per UTF-16 code unit.
308
309 // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
310 // leaving the remaining widths zero. Not nice.
311 for (int i = 0, p = 0; i < widths; ++i) {
312 resultTotalAdvance += resultAdvances[p++] = SkScalarToFloat(scalarArray[i]);
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800313 if (p < count &&
314 text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
315 text[p] < UNICODE_FIRST_PRIVATE_USE &&
316 text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
317 text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
Doug Feltf7cb1f72010-07-01 16:20:43 -0700318 resultAdvances[p++] = 0;
319 }
320 }
321 } else {
322 for (int i = 0; i < count; i++) {
323 resultTotalAdvance += resultAdvances[i] = SkScalarToFloat(scalarArray[i]);
324 }
325 }
326}
327
328
329// Draws a paragraph of text on a single line, running bidi and shaping
330void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
331 int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
332
333 handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
334}
335
336void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
337 jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
338 handleText(paint, text, len, bidiFlags, x, y, NULL, path);
339}
340
341
342void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
343 int bidiFlags, jfloat hOffset, jfloat vOffset,
344 SkPath* path, SkCanvas* canvas) {
345
346 SkScalar h_ = SkFloatToScalar(hOffset);
347 SkScalar v_ = SkFloatToScalar(vOffset);
348
349 if (!needsLayout(text, count, bidiFlags)) {
350 canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint);
351 return;
352 }
353
Fabrice Di Megliodd347df2011-02-17 13:22:08 -0800354 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> buffer(count);
355
356#if DEBUG_RTL_ALLOCATIONS
357 LOGD("TextLayout::drawTextOnPath - allocated buffer with size: %d", count);
358#endif
359
Doug Feltf7cb1f72010-07-01 16:20:43 -0700360 int dir = kDirection_LTR;
361 UErrorCode status = U_ZERO_ERROR;
362 count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
363 if (U_SUCCESS(status)) {
364 canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
365 }
366}
367
368}