• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 <cutils/compiler.h>
18 
19 #include <utils/JenkinsHash.h>
20 #include <utils/Trace.h>
21 
22 #include <SkGlyph.h>
23 #include <SkGlyphCache.h>
24 #include <SkSurfaceProps.h>
25 #include <SkUtils.h>
26 
27 #include "../Debug.h"
28 #include "../FontRenderer.h"
29 #include "../PixelBuffer.h"
30 #include "../Properties.h"
31 #include "Font.h"
32 #include "FontUtil.h"
33 
34 namespace android {
35 namespace uirenderer {
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 // Font
39 ///////////////////////////////////////////////////////////////////////////////
40 
Font(FontRenderer * state,const Font::FontDescription & desc)41 Font::Font(FontRenderer* state, const Font::FontDescription& desc)
42         : mState(state), mDescription(desc) {}
43 
FontDescription(const SkPaint * paint,const SkMatrix & rasterMatrix)44 Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& rasterMatrix)
45         : mLookupTransform(rasterMatrix) {
46     mFontId = SkTypeface::UniqueID(paint->getTypeface());
47     mFontSize = paint->getTextSize();
48     mFlags = 0;
49     if (paint->isFakeBoldText()) {
50         mFlags |= Font::kFakeBold;
51     }
52     mItalicStyle = paint->getTextSkewX();
53     mScaleX = paint->getTextScaleX();
54     mStyle = paint->getStyle();
55     mStrokeWidth = paint->getStrokeWidth();
56     mAntiAliasing = paint->isAntiAlias();
57     mHinting = paint->getHinting();
58     if (!mLookupTransform.invert(&mInverseLookupTransform)) {
59         ALOGW("Could not query the inverse lookup transform for this font");
60     }
61 }
62 
~Font()63 Font::~Font() {
64     for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
65         delete mCachedGlyphs.valueAt(i);
66     }
67 }
68 
hash() const69 hash_t Font::FontDescription::hash() const {
70     uint32_t hash = JenkinsHashMix(0, mFontId);
71     hash = JenkinsHashMix(hash, android::hash_type(mFontSize));
72     hash = JenkinsHashMix(hash, android::hash_type(mFlags));
73     hash = JenkinsHashMix(hash, android::hash_type(mItalicStyle));
74     hash = JenkinsHashMix(hash, android::hash_type(mScaleX));
75     hash = JenkinsHashMix(hash, android::hash_type(mStyle));
76     hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth));
77     hash = JenkinsHashMix(hash, int(mAntiAliasing));
78     hash = JenkinsHashMix(hash, android::hash_type(mHinting));
79     hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
80     hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
81     return JenkinsHashWhiten(hash);
82 }
83 
compare(const Font::FontDescription & lhs,const Font::FontDescription & rhs)84 int Font::FontDescription::compare(const Font::FontDescription& lhs,
85                                    const Font::FontDescription& rhs) {
86     int deltaInt = int(lhs.mFontId) - int(rhs.mFontId);
87     if (deltaInt != 0) return deltaInt;
88 
89     if (lhs.mFontSize < rhs.mFontSize) return -1;
90     if (lhs.mFontSize > rhs.mFontSize) return +1;
91 
92     if (lhs.mItalicStyle < rhs.mItalicStyle) return -1;
93     if (lhs.mItalicStyle > rhs.mItalicStyle) return +1;
94 
95     deltaInt = int(lhs.mFlags) - int(rhs.mFlags);
96     if (deltaInt != 0) return deltaInt;
97 
98     if (lhs.mScaleX < rhs.mScaleX) return -1;
99     if (lhs.mScaleX > rhs.mScaleX) return +1;
100 
101     deltaInt = int(lhs.mStyle) - int(rhs.mStyle);
102     if (deltaInt != 0) return deltaInt;
103 
104     if (lhs.mStrokeWidth < rhs.mStrokeWidth) return -1;
105     if (lhs.mStrokeWidth > rhs.mStrokeWidth) return +1;
106 
107     deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing);
108     if (deltaInt != 0) return deltaInt;
109 
110     deltaInt = int(lhs.mHinting) - int(rhs.mHinting);
111     if (deltaInt != 0) return deltaInt;
112 
113     if (lhs.mLookupTransform[SkMatrix::kMScaleX] < rhs.mLookupTransform[SkMatrix::kMScaleX])
114         return -1;
115     if (lhs.mLookupTransform[SkMatrix::kMScaleX] > rhs.mLookupTransform[SkMatrix::kMScaleX])
116         return +1;
117 
118     if (lhs.mLookupTransform[SkMatrix::kMScaleY] < rhs.mLookupTransform[SkMatrix::kMScaleY])
119         return -1;
120     if (lhs.mLookupTransform[SkMatrix::kMScaleY] > rhs.mLookupTransform[SkMatrix::kMScaleY])
121         return +1;
122 
123     return 0;
124 }
125 
invalidateTextureCache(CacheTexture * cacheTexture)126 void Font::invalidateTextureCache(CacheTexture* cacheTexture) {
127     for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
128         CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i);
129         if (!cacheTexture || cachedGlyph->mCacheTexture == cacheTexture) {
130             cachedGlyph->mIsValid = false;
131         }
132     }
133 }
134 
measureCachedGlyph(CachedGlyphInfo * glyph,int x,int y,uint8_t * bitmap,uint32_t bitmapW,uint32_t bitmapH,Rect * bounds,const float * pos)135 void Font::measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
136                               uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
137     int width = (int)glyph->mBitmapWidth;
138     int height = (int)glyph->mBitmapHeight;
139 
140     int nPenX = x + glyph->mBitmapLeft;
141     int nPenY = y + glyph->mBitmapTop;
142 
143     if (bounds->bottom > nPenY) {
144         bounds->bottom = nPenY;
145     }
146     if (bounds->left > nPenX) {
147         bounds->left = nPenX;
148     }
149     if (bounds->right < nPenX + width) {
150         bounds->right = nPenX + width;
151     }
152     if (bounds->top < nPenY + height) {
153         bounds->top = nPenY + height;
154     }
155 }
156 
drawCachedGlyph(CachedGlyphInfo * glyph,int x,int y,uint8_t * bitmap,uint32_t bitmapW,uint32_t bitmapH,Rect * bounds,const float * pos)157 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW,
158                            uint32_t bitmapH, Rect* bounds, const float* pos) {
159     float width = (float)glyph->mBitmapWidth;
160     float height = (float)glyph->mBitmapHeight;
161 
162     float nPenX = x + glyph->mBitmapLeft;
163     float nPenY = y + glyph->mBitmapTop + height;
164 
165     float u1 = glyph->mBitmapMinU;
166     float u2 = glyph->mBitmapMaxU;
167     float v1 = glyph->mBitmapMinV;
168     float v2 = glyph->mBitmapMaxV;
169 
170     mState->appendMeshQuad(nPenX, nPenY, u1, v2, nPenX + width, nPenY, u2, v2, nPenX + width,
171                            nPenY - height, u2, v1, nPenX, nPenY - height, u1, v1,
172                            glyph->mCacheTexture);
173 }
174 
drawCachedGlyphTransformed(CachedGlyphInfo * glyph,int x,int y,uint8_t * bitmap,uint32_t bitmapW,uint32_t bitmapH,Rect * bounds,const float * pos)175 void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
176                                       uint32_t bitmapW, uint32_t bitmapH, Rect* bounds,
177                                       const float* pos) {
178     float width = (float)glyph->mBitmapWidth;
179     float height = (float)glyph->mBitmapHeight;
180 
181     SkPoint p[4];
182     p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + height);
183     p[1].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop + height);
184     p[2].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop);
185     p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
186 
187     mDescription.mInverseLookupTransform.mapPoints(p, 4);
188 
189     p[0].offset(x, y);
190     p[1].offset(x, y);
191     p[2].offset(x, y);
192     p[3].offset(x, y);
193 
194     float u1 = glyph->mBitmapMinU;
195     float u2 = glyph->mBitmapMaxU;
196     float v1 = glyph->mBitmapMinV;
197     float v2 = glyph->mBitmapMaxV;
198 
199     mState->appendRotatedMeshQuad(p[0].x(), p[0].y(), u1, v2, p[1].x(), p[1].y(), u2, v2, p[2].x(),
200                                   p[2].y(), u2, v1, p[3].x(), p[3].y(), u1, v1,
201                                   glyph->mCacheTexture);
202 }
203 
drawCachedGlyphBitmap(CachedGlyphInfo * glyph,int x,int y,uint8_t * bitmap,uint32_t bitmapWidth,uint32_t bitmapHeight,Rect * bounds,const float * pos)204 void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
205                                  uint32_t bitmapWidth, uint32_t bitmapHeight, Rect* bounds,
206                                  const float* pos) {
207     int dstX = x + glyph->mBitmapLeft;
208     int dstY = y + glyph->mBitmapTop;
209 
210     CacheTexture* cacheTexture = glyph->mCacheTexture;
211     PixelBuffer* pixelBuffer = cacheTexture->getPixelBuffer();
212 
213     uint32_t formatSize = PixelBuffer::formatSize(pixelBuffer->getFormat());
214     uint32_t alpha_channel_offset = PixelBuffer::formatAlphaOffset(pixelBuffer->getFormat());
215     uint32_t cacheWidth = cacheTexture->getWidth();
216     uint32_t srcStride = formatSize * cacheWidth;
217     uint32_t startY = glyph->mStartY * srcStride;
218     uint32_t endY = startY + (glyph->mBitmapHeight * srcStride);
219 
220     const uint8_t* cacheBuffer = pixelBuffer->map();
221 
222     for (uint32_t cacheY = startY, bitmapY = dstY * bitmapWidth; cacheY < endY;
223          cacheY += srcStride, bitmapY += bitmapWidth) {
224         for (uint32_t i = 0; i < glyph->mBitmapWidth; ++i) {
225             uint8_t* dst = &(bitmap[bitmapY + dstX + i]);
226             const uint8_t& src =
227                     cacheBuffer[cacheY + (glyph->mStartX + i) * formatSize + alpha_channel_offset];
228             // Add alpha values to a max of 255, full opacity. This is done to handle
229             // fonts/strings where glyphs overlap.
230             *dst = std::min(*dst + src, 255);
231         }
232     }
233 }
234 
drawCachedGlyph(CachedGlyphInfo * glyph,float x,float hOffset,float vOffset,SkPathMeasure & measure,SkPoint * position,SkVector * tangent)235 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
236                            SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
237     const float halfWidth = glyph->mBitmapWidth * 0.5f;
238     const float height = glyph->mBitmapHeight;
239 
240     vOffset += glyph->mBitmapTop + height;
241 
242     SkPoint destination[4];
243     bool ok = measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent);
244     if (!ok) {
245         ALOGW("The path for drawTextOnPath is empty or null");
246     }
247 
248     // Move along the tangent and offset by the normal
249     destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
250                        -tangent->fY * halfWidth + tangent->fX * vOffset);
251     destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
252                        tangent->fY * halfWidth + tangent->fX * vOffset);
253     destination[2].set(destination[1].fX + tangent->fY * height,
254                        destination[1].fY - tangent->fX * height);
255     destination[3].set(destination[0].fX + tangent->fY * height,
256                        destination[0].fY - tangent->fX * height);
257 
258     const float u1 = glyph->mBitmapMinU;
259     const float u2 = glyph->mBitmapMaxU;
260     const float v1 = glyph->mBitmapMinV;
261     const float v2 = glyph->mBitmapMaxV;
262 
263     mState->appendRotatedMeshQuad(
264             position->x() + destination[0].x(), position->y() + destination[0].y(), u1, v2,
265             position->x() + destination[1].x(), position->y() + destination[1].y(), u2, v2,
266             position->x() + destination[2].x(), position->y() + destination[2].y(), u2, v1,
267             position->x() + destination[3].x(), position->y() + destination[3].y(), u1, v1,
268             glyph->mCacheTexture);
269 }
270 
getCachedGlyph(const SkPaint * paint,glyph_t textUnit,bool precaching)271 CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bool precaching) {
272     CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
273     if (cachedGlyph) {
274         // Is the glyph still in texture cache?
275         if (!cachedGlyph->mIsValid) {
276             SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
277             SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps,
278                                               &mDescription.mLookupTransform);
279             const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
280             updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
281         }
282     } else {
283         cachedGlyph = cacheGlyph(paint, textUnit, precaching);
284     }
285 
286     return cachedGlyph;
287 }
288 
render(const SkPaint * paint,const glyph_t * glyphs,int numGlyphs,int x,int y,const float * positions)289 void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
290                   const float* positions) {
291     render(paint, glyphs, numGlyphs, x, y, FRAMEBUFFER, nullptr, 0, 0, nullptr, positions);
292 }
293 
render(const SkPaint * paint,const glyph_t * glyphs,int numGlyphs,const SkPath * path,float hOffset,float vOffset)294 void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path,
295                   float hOffset, float vOffset) {
296     if (numGlyphs == 0 || glyphs == nullptr) {
297         return;
298     }
299 
300     int glyphsCount = 0;
301     int prevRsbDelta = 0;
302 
303     float penX = 0.0f;
304 
305     SkPoint position;
306     SkVector tangent;
307 
308     SkPathMeasure measure(*path, false);
309     float pathLength = SkScalarToFloat(measure.getLength());
310 
311     if (paint->getTextAlign() != SkPaint::kLeft_Align) {
312         float textWidth = SkScalarToFloat(paint->measureText(glyphs, numGlyphs * 2));
313         float pathOffset = pathLength;
314         if (paint->getTextAlign() == SkPaint::kCenter_Align) {
315             textWidth *= 0.5f;
316             pathOffset *= 0.5f;
317         }
318         penX += pathOffset - textWidth;
319     }
320 
321     while (glyphsCount < numGlyphs && penX < pathLength) {
322         glyph_t glyph = *(glyphs++);
323 
324         if (IS_END_OF_STRING(glyph)) {
325             break;
326         }
327 
328         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
329         penX += AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta);
330         prevRsbDelta = cachedGlyph->mRsbDelta;
331 
332         if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
333             drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
334         }
335 
336         penX += cachedGlyph->mAdvanceX;
337 
338         glyphsCount++;
339     }
340 }
341 
measure(const SkPaint * paint,const glyph_t * glyphs,int numGlyphs,Rect * bounds,const float * positions)342 void Font::measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect* bounds,
343                    const float* positions) {
344     if (bounds == nullptr) {
345         ALOGE("No return rectangle provided to measure text");
346         return;
347     }
348     bounds->set(1e6, -1e6, -1e6, 1e6);
349     render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
350 }
351 
precache(const SkPaint * paint,const glyph_t * glyphs,int numGlyphs)352 void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) {
353     if (numGlyphs == 0 || glyphs == nullptr) {
354         return;
355     }
356 
357     int glyphsCount = 0;
358     while (glyphsCount < numGlyphs) {
359         glyph_t glyph = *(glyphs++);
360 
361         // Reached the end of the string
362         if (IS_END_OF_STRING(glyph)) {
363             break;
364         }
365 
366         getCachedGlyph(paint, glyph, true);
367         glyphsCount++;
368     }
369 }
370 
render(const SkPaint * paint,const glyph_t * glyphs,int numGlyphs,int x,int y,RenderMode mode,uint8_t * bitmap,uint32_t bitmapW,uint32_t bitmapH,Rect * bounds,const float * positions)371 void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
372                   RenderMode mode, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH,
373                   Rect* bounds, const float* positions) {
374     if (numGlyphs == 0 || glyphs == nullptr) {
375         return;
376     }
377 
378     static RenderGlyph gRenderGlyph[] = {&android::uirenderer::Font::drawCachedGlyph,
379                                          &android::uirenderer::Font::drawCachedGlyphTransformed,
380                                          &android::uirenderer::Font::drawCachedGlyphBitmap,
381                                          &android::uirenderer::Font::drawCachedGlyphBitmap,
382                                          &android::uirenderer::Font::measureCachedGlyph,
383                                          &android::uirenderer::Font::measureCachedGlyph};
384     RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
385 
386     int glyphsCount = 0;
387 
388     while (glyphsCount < numGlyphs) {
389         glyph_t glyph = *(glyphs++);
390 
391         // Reached the end of the string
392         if (IS_END_OF_STRING(glyph)) {
393             break;
394         }
395 
396         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
397 
398         // If it's still not valid, we couldn't cache it, so we shouldn't
399         // draw garbage; also skip empty glyphs (spaces)
400         if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
401             int penX = x + (int)roundf(positions[(glyphsCount << 1)]);
402             int penY = y + (int)roundf(positions[(glyphsCount << 1) + 1]);
403 #ifdef BUGREPORT_FONT_CACHE_USAGE
404             mState->historyTracker().glyphRendered(cachedGlyph, penX, penY);
405 #endif
406             (*this.*render)(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH, bounds, positions);
407         } else {
408 #ifdef BUGREPORT_FONT_CACHE_USAGE
409             mState->historyTracker().glyphRendered(cachedGlyph, -1, -1);
410 #endif
411         }
412 
413         glyphsCount++;
414     }
415 }
416 
updateGlyphCache(const SkPaint * paint,const SkGlyph & skiaGlyph,SkGlyphCache * skiaGlyphCache,CachedGlyphInfo * glyph,bool precaching)417 void Font::updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph,
418                             SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching) {
419     glyph->mAdvanceX = skiaGlyph.fAdvanceX;
420     glyph->mAdvanceY = skiaGlyph.fAdvanceY;
421     glyph->mBitmapLeft = skiaGlyph.fLeft;
422     glyph->mBitmapTop = skiaGlyph.fTop;
423     glyph->mLsbDelta = skiaGlyph.fLsbDelta;
424     glyph->mRsbDelta = skiaGlyph.fRsbDelta;
425 
426     uint32_t startX = 0;
427     uint32_t startY = 0;
428 
429     // Get the bitmap for the glyph
430     if (!skiaGlyph.fImage) {
431         skiaGlyphCache->findImage(skiaGlyph);
432     }
433     mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
434 
435     if (!glyph->mIsValid) {
436         return;
437     }
438 
439     uint32_t endX = startX + skiaGlyph.fWidth;
440     uint32_t endY = startY + skiaGlyph.fHeight;
441 
442     glyph->mStartX = startX;
443     glyph->mStartY = startY;
444     glyph->mBitmapWidth = skiaGlyph.fWidth;
445     glyph->mBitmapHeight = skiaGlyph.fHeight;
446 
447     bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
448     if (!empty) {
449         uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
450         uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
451 
452         glyph->mBitmapMinU = startX / (float)cacheWidth;
453         glyph->mBitmapMinV = startY / (float)cacheHeight;
454         glyph->mBitmapMaxU = endX / (float)cacheWidth;
455         glyph->mBitmapMaxV = endY / (float)cacheHeight;
456 
457         mState->setTextureDirty();
458     }
459 }
460 
cacheGlyph(const SkPaint * paint,glyph_t glyph,bool precaching)461 CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching) {
462     CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
463     mCachedGlyphs.add(glyph, newGlyph);
464 
465     SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
466     SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform);
467     const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
468     newGlyph->mIsValid = false;
469     newGlyph->mGlyphIndex = skiaGlyph.fID;
470 
471     updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), newGlyph, precaching);
472 
473     return newGlyph;
474 }
475 
create(FontRenderer * state,const SkPaint * paint,const SkMatrix & matrix)476 Font* Font::create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix) {
477     FontDescription description(paint, matrix);
478     Font* font = state->mActiveFonts.get(description);
479 
480     if (!font) {
481         font = new Font(state, description);
482         state->mActiveFonts.put(description, font);
483     }
484     font->mIdentityTransform = matrix.isIdentity();
485 
486     return font;
487 }
488 
489 };  // namespace uirenderer
490 };  // namespace android
491