• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 namespace 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.
needsLayout(const jchar * text,jint len,jint bidiFlags)33 bool 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  */
shapeRtlText(const jchar * context,jsize start,jsize count,jsize contextCount,jchar * shaped,UErrorCode & status)61 int 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  */
layoutLine(const jchar * text,jint len,jint flags,int & dir,jchar * buffer,UErrorCode & status)103 jint 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 
prepareText(SkPaint * paint,const jchar * text,jsize len,jint bidiFlags,const jchar ** outText,int32_t * outBytes,jchar ** outBuffer)154 bool 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.
handleText(SkPaint * paint,const jchar * text,jsize len,jint bidiFlags,jfloat x,jfloat y,SkCanvas * canvas,SkPath * path)207 void 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 
prepareRtlTextRun(const jchar * context,jsize start,jsize & count,jsize contextCount,jchar * shaped)224 bool 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 
drawTextRun(SkPaint * paint,const jchar * chars,jint start,jint count,jint contextCount,int dirFlags,jfloat x,jfloat y,SkCanvas * canvas)236 void 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 
getTextRunAdvances(SkPaint * paint,const jchar * chars,jint start,jint count,jint contextCount,jint dirFlags,jfloat * resultAdvances,jfloat * resultTotalAdvance)254 void 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 
getTextRunAdvancesICU(SkPaint * paint,const jchar * chars,jint start,jint count,jint contextCount,jint dirFlags,jfloat * resultAdvances,jfloat & resultTotalAdvance)276 void 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
drawText(SkPaint * paint,const jchar * text,jsize len,int bidiFlags,jfloat x,jfloat y,SkCanvas * canvas)285 void 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 
getTextPath(SkPaint * paint,const jchar * text,jsize len,jint bidiFlags,jfloat x,jfloat y,SkPath * path)290 void 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 
drawTextOnPath(SkPaint * paint,const jchar * text,int count,int bidiFlags,jfloat hOffset,jfloat vOffset,SkPath * path,SkCanvas * canvas)296 void 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 
computeAdvancesWithICU(SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags,jfloat * outAdvances,jfloat * outTotalAdvance)318 void 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     LOGD("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             LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
371 #endif
372         }
373     } else {
374 #if DEBUG_ADVANCES
375     LOGD("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             LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
381 #endif
382         }
383     }
384     *outTotalAdvance = totalAdvance;
385 }
386 
387 }
388