blob: 14ad7d7227f9d05b21bec5ffd30bd4e485ac18dd [file] [log] [blame]
Romain Guy694b5192010-07-21 21:33:20 -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#define LOG_TAG "OpenGLRenderer"
18
Romain Guy694b5192010-07-21 21:33:20 -070019#include <SkUtils.h>
20
Romain Guy51769a62010-07-23 00:28:00 -070021#include <cutils/properties.h>
22#include <utils/Log.h>
23
24#include "FontRenderer.h"
25
Romain Guy694b5192010-07-21 21:33:20 -070026namespace android {
27namespace uirenderer {
28
29///////////////////////////////////////////////////////////////////////////////
Romain Guy51769a62010-07-23 00:28:00 -070030// Defines
31///////////////////////////////////////////////////////////////////////////////
32
33#define DEFAULT_TEXT_CACHE_WIDTH 1024
34#define DEFAULT_TEXT_CACHE_HEIGHT 256
35
36///////////////////////////////////////////////////////////////////////////////
Romain Guy694b5192010-07-21 21:33:20 -070037// Font
38///////////////////////////////////////////////////////////////////////////////
39
40Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
41 mState(state), mFontId(fontId), mFontSize(fontSize) {
42}
43
44
45Font::~Font() {
46 for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
47 if (mState->mActiveFonts[ct] == this) {
48 mState->mActiveFonts.removeAt(ct);
49 break;
50 }
51 }
52
53 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -070054 CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
Romain Guy694b5192010-07-21 21:33:20 -070055 delete glyph;
56 }
57}
58
59void Font::invalidateTextureCache() {
60 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
61 mCachedGlyphs.valueAt(i)->mIsValid = false;
62 }
63}
64
65void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -070066 int nPenX = x + glyph->mBitmapLeft;
67 int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
68
Romain Guy51769a62010-07-23 00:28:00 -070069 float u1 = glyph->mBitmapMinU;
70 float u2 = glyph->mBitmapMaxU;
71 float v1 = glyph->mBitmapMinV;
72 float v2 = glyph->mBitmapMaxV;
73
74 int width = (int) glyph->mBitmapWidth;
75 int height = (int) glyph->mBitmapHeight;
76
77 mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
78 nPenX + width, nPenY, 0, u2, v2,
79 nPenX + width, nPenY - height, 0, u2, v1,
80 nPenX, nPenY - height, 0, u1, v1);
Romain Guy694b5192010-07-21 21:33:20 -070081}
82
Romain Guy51769a62010-07-23 00:28:00 -070083void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
Romain Guy09147fb2010-07-22 13:08:20 -070084 int numGlyphs, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -070085 if (numGlyphs == 0 || text == NULL || len == 0) {
86 return;
87 }
88
89 int penX = x, penY = y;
90 int glyphsLeft = 1;
91 if (numGlyphs > 0) {
92 glyphsLeft = numGlyphs;
93 }
94
Romain Guy694b5192010-07-21 21:33:20 -070095 text += start;
96
97 while (glyphsLeft > 0) {
Romain Guy694b5192010-07-21 21:33:20 -070098 int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
99
100 // Reached the end of the string or encountered
101 if (utfChar < 0) {
102 break;
103 }
104
Romain Guy51769a62010-07-23 00:28:00 -0700105 CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar);
Romain Guy694b5192010-07-21 21:33:20 -0700106 if (cachedGlyph == NULL) {
107 cachedGlyph = cacheGlyph(paint, utfChar);
108 }
Romain Guy51769a62010-07-23 00:28:00 -0700109
Romain Guy694b5192010-07-21 21:33:20 -0700110 // Is the glyph still in texture cache?
111 if (!cachedGlyph->mIsValid) {
112 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
113 updateGlyphCache(paint, skiaGlyph, cachedGlyph);
114 }
115
116 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
117 if (cachedGlyph->mIsValid) {
118 drawCachedGlyph(cachedGlyph, penX, penY);
119 }
120
Romain Guy09147fb2010-07-22 13:08:20 -0700121 penX += SkFixedFloor(cachedGlyph->mAdvanceX);
Romain Guy694b5192010-07-21 21:33:20 -0700122
123 // If we were given a specific number of glyphs, decrement
124 if (numGlyphs > 0) {
125 glyphsLeft--;
126 }
127 }
128}
129
Romain Guy51769a62010-07-23 00:28:00 -0700130void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
Romain Guy694b5192010-07-21 21:33:20 -0700131 glyph->mAdvanceX = skiaGlyph.fAdvanceX;
132 glyph->mAdvanceY = skiaGlyph.fAdvanceY;
133 glyph->mBitmapLeft = skiaGlyph.fLeft;
134 glyph->mBitmapTop = skiaGlyph.fTop;
135
136 uint32_t startX = 0;
137 uint32_t startY = 0;
138
Romain Guy694b5192010-07-21 21:33:20 -0700139 // Get the bitmap for the glyph
140 paint->findImage(skiaGlyph);
Romain Guy51769a62010-07-23 00:28:00 -0700141 glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
Romain Guy694b5192010-07-21 21:33:20 -0700142
143 if (!glyph->mIsValid) {
144 return;
145 }
146
147 uint32_t endX = startX + skiaGlyph.fWidth;
148 uint32_t endY = startY + skiaGlyph.fHeight;
149
150 glyph->mBitmapWidth = skiaGlyph.fWidth;
151 glyph->mBitmapHeight = skiaGlyph.fHeight;
152
Romain Guy51769a62010-07-23 00:28:00 -0700153 uint32_t cacheWidth = mState->getCacheWidth();
154 uint32_t cacheHeight = mState->getCacheHeight();
Romain Guy694b5192010-07-21 21:33:20 -0700155
156 glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
157 glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
158 glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
159 glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
160
Romain Guy51769a62010-07-23 00:28:00 -0700161 mState->mUploadTexture = true;
Romain Guy694b5192010-07-21 21:33:20 -0700162}
163
Romain Guy51769a62010-07-23 00:28:00 -0700164Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
165 CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
Romain Guy694b5192010-07-21 21:33:20 -0700166 mCachedGlyphs.add(glyph, newGlyph);
167
168 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
169 newGlyph->mGlyphIndex = skiaGlyph.fID;
170 newGlyph->mIsValid = false;
171
172 updateGlyphCache(paint, skiaGlyph, newGlyph);
173
174 return newGlyph;
175}
176
177Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
178 Vector<Font*> &activeFonts = state->mActiveFonts;
179
180 for (uint32_t i = 0; i < activeFonts.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -0700181 Font* font = activeFonts[i];
182 if (font->mFontId == fontId && font->mFontSize == fontSize) {
183 return font;
Romain Guy694b5192010-07-21 21:33:20 -0700184 }
185 }
186
187 Font* newFont = new Font(state, fontId, fontSize);
188 activeFonts.push(newFont);
189 return newFont;
190}
191
192///////////////////////////////////////////////////////////////////////////////
193// FontRenderer
194///////////////////////////////////////////////////////////////////////////////
195
196FontRenderer::FontRenderer() {
Romain Guy51769a62010-07-23 00:28:00 -0700197 LOGD("Creating FontRenderer");
198
Romain Guy694b5192010-07-21 21:33:20 -0700199 mInitialized = false;
200 mMaxNumberOfQuads = 1024;
201 mCurrentQuadIndex = 0;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700202 mTextureId = 0;
Romain Guy694b5192010-07-21 21:33:20 -0700203
204 mIndexBufferID = 0;
205
Romain Guy51769a62010-07-23 00:28:00 -0700206 mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700207 mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
Romain Guy51769a62010-07-23 00:28:00 -0700208
209 char property[PROPERTY_VALUE_MAX];
210 if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
211 LOGD(" Setting text cache width to %s pixels", property);
212 mCacheWidth = atoi(property);
213 } else {
214 LOGD(" Using default text cache width of %i pixels", mCacheWidth);
215 }
216
217 if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
218 LOGD(" Setting text cache width to %s pixels", property);
219 mCacheHeight = atoi(property);
220 } else {
221 LOGD(" Using default text cache height of %i pixels", mCacheHeight);
222 }
Romain Guy694b5192010-07-21 21:33:20 -0700223}
224
225FontRenderer::~FontRenderer() {
226 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
227 delete mCacheLines[i];
228 }
229 mCacheLines.clear();
230
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700231 delete mTextMeshPtr;
232
Romain Guy694b5192010-07-21 21:33:20 -0700233 delete mTextTexture;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700234 if(mTextureId) {
235 glDeleteTextures(1, &mTextureId);
236 }
Romain Guy694b5192010-07-21 21:33:20 -0700237
238 Vector<Font*> fontsToDereference = mActiveFonts;
239 for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
240 delete fontsToDereference[i];
241 }
242}
243
244void FontRenderer::flushAllAndInvalidate() {
245 if (mCurrentQuadIndex != 0) {
246 issueDrawCommand();
247 mCurrentQuadIndex = 0;
248 }
249 for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
250 mActiveFonts[i]->invalidateTextureCache();
251 }
252 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
253 mCacheLines[i]->mCurrentCol = 0;
254 }
255}
256
Romain Guy51769a62010-07-23 00:28:00 -0700257bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Romain Guy694b5192010-07-21 21:33:20 -0700258 // If the glyph is too tall, don't cache it
259 if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
260 LOGE("Font size to large to fit in cache. width, height = %i, %i",
261 (int) glyph.fWidth, (int) glyph.fHeight);
262 return false;
263 }
264
265 // Now copy the bitmap into the cache texture
266 uint32_t startX = 0;
267 uint32_t startY = 0;
268
269 bool bitmapFit = false;
270 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
271 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
272 if (bitmapFit) {
273 break;
274 }
275 }
276
277 // If the new glyph didn't fit, flush the state so far and invalidate everything
278 if (!bitmapFit) {
279 flushAllAndInvalidate();
280
281 // Try to fit it again
282 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
283 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
284 if (bitmapFit) {
285 break;
286 }
287 }
288
289 // if we still don't fit, something is wrong and we shouldn't draw
290 if (!bitmapFit) {
291 LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
292 (int) glyph.fWidth, (int) glyph.fHeight);
293 return false;
294 }
295 }
296
297 *retOriginX = startX;
298 *retOriginY = startY;
299
300 uint32_t endX = startX + glyph.fWidth;
301 uint32_t endY = startY + glyph.fHeight;
302
303 uint32_t cacheWidth = mCacheWidth;
304
Romain Guy51769a62010-07-23 00:28:00 -0700305 unsigned char* cacheBuffer = mTextTexture;
306 unsigned char* bitmapBuffer = (unsigned char*) glyph.fImage;
Romain Guy694b5192010-07-21 21:33:20 -0700307 unsigned int stride = glyph.rowBytes();
308
309 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
310 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
311 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
312 unsigned char tempCol = bitmapBuffer[bY * stride + bX];
313 cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
314 }
315 }
316
317 return true;
318}
319
320void FontRenderer::initTextTexture() {
321 mTextTexture = new unsigned char[mCacheWidth * mCacheHeight];
322 mUploadTexture = false;
323
324 glGenTextures(1, &mTextureId);
325 glBindTexture(GL_TEXTURE_2D, mTextureId);
326 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700327 // Initialize texture dimentions
328 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
329 GL_ALPHA, GL_UNSIGNED_BYTE, 0);
Romain Guy694b5192010-07-21 21:33:20 -0700330
331 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
332 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
333
334 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
335 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
336
337 // Split up our cache texture into lines of certain widths
338 int nextLine = 0;
Romain Guy51769a62010-07-23 00:28:00 -0700339 mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700340 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700341 mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700342 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700343 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700344 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700345 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700346 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700347 mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700348 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700349 mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700350}
351
352// Avoid having to reallocate memory and render quad by quad
353void FontRenderer::initVertexArrayBuffers() {
354 uint32_t numIndicies = mMaxNumberOfQuads * 6;
355 uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
Romain Guy51769a62010-07-23 00:28:00 -0700356 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
Romain Guy694b5192010-07-21 21:33:20 -0700357
358 // Four verts, two triangles , six indices per quad
359 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
360 int i6 = i * 6;
361 int i4 = i * 4;
362
363 indexBufferData[i6 + 0] = i4 + 0;
364 indexBufferData[i6 + 1] = i4 + 1;
365 indexBufferData[i6 + 2] = i4 + 2;
366
367 indexBufferData[i6 + 3] = i4 + 0;
368 indexBufferData[i6 + 4] = i4 + 2;
369 indexBufferData[i6 + 5] = i4 + 3;
370 }
371
372 glGenBuffers(1, &mIndexBufferID);
373 glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
374 glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
375 glBindBuffer(GL_ARRAY_BUFFER, 0);
376
377 free(indexBufferData);
378
379 uint32_t coordSize = 3;
380 uint32_t uvSize = 2;
381 uint32_t vertsPerQuad = 4;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700382 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
383 mTextMeshPtr = new float[vertexBufferSize];
Romain Guy694b5192010-07-21 21:33:20 -0700384}
385
386// We don't want to allocate anything unless we actually draw text
387void FontRenderer::checkInit() {
388 if (mInitialized) {
389 return;
390 }
391
392 initTextTexture();
393 initVertexArrayBuffers();
394
395 mInitialized = true;
396}
397
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700398void FontRenderer::checkTextureUpdate() {
399 if (!mUploadTexture) {
400 return;
Romain Guy694b5192010-07-21 21:33:20 -0700401 }
402
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700403 glBindTexture(GL_TEXTURE_2D, mTextureId);
404
405 // Iterate over all the cache lines and see which ones need to be updated
406 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
407 CacheTextureLine* cl = mCacheLines[i];
408 if(cl->mDirty) {
409 uint32_t xOffset = 0;
410 uint32_t yOffset = cl->mCurrentRow;
411 uint32_t width = mCacheWidth;
412 uint32_t height = cl->mMaxHeight;
413 void* textureData = mTextTexture + yOffset*width;
414
415 glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
416 GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
417
418 cl->mDirty = false;
419 }
420 }
421
422 mUploadTexture = false;
423}
424
425void FontRenderer::issueDrawCommand() {
426
427 checkTextureUpdate();
428
Romain Guy51769a62010-07-23 00:28:00 -0700429 float* vtx = mTextMeshPtr;
430 float* tex = vtx + 3;
Romain Guy694b5192010-07-21 21:33:20 -0700431
432 // position is slot 0
433 uint32_t slot = 0;
434 glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
435
436 // texture0 is slot 1
437 slot = 1;
438 glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
439
440 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
441 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
442}
443
444void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
445 float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
446 float x4, float y4, float z4, float u4, float v4) {
Romain Guy09147fb2010-07-22 13:08:20 -0700447 if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) {
448 return;
449 }
450
Romain Guy694b5192010-07-21 21:33:20 -0700451 const uint32_t vertsPerQuad = 4;
452 const uint32_t floatsPerVert = 5;
Romain Guy51769a62010-07-23 00:28:00 -0700453 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
Romain Guy694b5192010-07-21 21:33:20 -0700454
Romain Guy694b5192010-07-21 21:33:20 -0700455 (*currentPos++) = x1;
456 (*currentPos++) = y1;
457 (*currentPos++) = z1;
458 (*currentPos++) = u1;
459 (*currentPos++) = v1;
460
461 (*currentPos++) = x2;
462 (*currentPos++) = y2;
463 (*currentPos++) = z2;
464 (*currentPos++) = u2;
465 (*currentPos++) = v2;
466
467 (*currentPos++) = x3;
468 (*currentPos++) = y3;
469 (*currentPos++) = z3;
470 (*currentPos++) = u3;
471 (*currentPos++) = v3;
472
473 (*currentPos++) = x4;
474 (*currentPos++) = y4;
475 (*currentPos++) = z4;
476 (*currentPos++) = u4;
477 (*currentPos++) = v4;
478
479 mCurrentQuadIndex++;
480
481 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
482 issueDrawCommand();
483 mCurrentQuadIndex = 0;
484 }
485}
486
487void FontRenderer::setFont(uint32_t fontId, float fontSize) {
488 mCurrentFont = Font::create(this, fontId, fontSize);
489}
490
Romain Guy51769a62010-07-23 00:28:00 -0700491void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
492 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -0700493 checkInit();
494
Romain Guy09147fb2010-07-22 13:08:20 -0700495 if (!mCurrentFont) {
496 LOGE("No font set");
Romain Guy694b5192010-07-21 21:33:20 -0700497 return;
498 }
499
Romain Guy09147fb2010-07-22 13:08:20 -0700500 mClip = clip;
Romain Guy51769a62010-07-23 00:28:00 -0700501 mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
Romain Guy694b5192010-07-21 21:33:20 -0700502
503 if (mCurrentQuadIndex != 0) {
504 issueDrawCommand();
505 mCurrentQuadIndex = 0;
506 }
507}
508
Romain Guy694b5192010-07-21 21:33:20 -0700509}; // namespace uirenderer
510}; // namespace android