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