• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 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 "ScreenRecord"
18 //#define LOG_NDEBUG 0
19 #include <utils/Log.h>
20 
21 #include "TextRenderer.h"
22 
23 #include <assert.h>
24 #include <malloc.h>
25 #include <string.h>
26 
27 namespace android {
28 #include "FontBitmap.h"
29 };
30 
31 using namespace android;
32 
33 const char TextRenderer::kWhitespace[] = " \t\n\r";
34 
35 bool TextRenderer::mInitialized = false;
36 uint32_t TextRenderer::mXOffset[FontBitmap::numGlyphs];
37 
initOnce()38 void TextRenderer::initOnce() {
39     if (!mInitialized) {
40         initXOffset();
41         mInitialized = true;
42     }
43 }
44 
initXOffset()45 void TextRenderer::initXOffset() {
46     // Generate a table of X offsets.  They start at zero and reset whenever
47     // we move down a line (i.e. the Y offset changes).  The offset increases
48     // by one pixel more than the width because the generator left a gap to
49     // avoid reading pixels from adjacent glyphs in the texture filter.
50     uint16_t offset = 0;
51     uint16_t prevYOffset = (int16_t) -1;
52     for (unsigned int i = 0; i < FontBitmap::numGlyphs; i++) {
53         if (prevYOffset != FontBitmap::yoffset[i]) {
54             prevYOffset = FontBitmap::yoffset[i];
55             offset = 0;
56         }
57         mXOffset[i] = offset;
58         offset += FontBitmap::glyphWidth[i] + 1;
59     }
60 }
61 
isPowerOfTwo(uint32_t val)62 static bool isPowerOfTwo(uint32_t val) {
63     // a/k/a "is exactly one bit set"; note returns true for 0
64     return (val & (val -1)) == 0;
65 }
66 
powerOfTwoCeil(uint32_t val)67 static uint32_t powerOfTwoCeil(uint32_t val) {
68     // drop it, smear the bits across, pop it
69     val--;
70     val |= val >> 1;
71     val |= val >> 2;
72     val |= val >> 4;
73     val |= val >> 8;
74     val |= val >> 16;
75     val++;
76 
77     return val;
78 }
79 
getGlyphHeight() const80 float TextRenderer::getGlyphHeight() const {
81     return FontBitmap::maxGlyphHeight;
82 }
83 
loadIntoTexture()84 status_t TextRenderer::loadIntoTexture() {
85     ALOGV("Font::loadIntoTexture");
86 
87     glGenTextures(1, &mTextureName);
88     if (mTextureName == 0) {
89         ALOGE("glGenTextures failed: %#x", glGetError());
90         return UNKNOWN_ERROR;
91     }
92     glBindTexture(GL_TEXTURE_2D, mTextureName);
93     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
94     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
95 
96     // The pixel data is stored as combined color+alpha, 8 bits per pixel.
97     // It's guaranteed to be a power-of-two wide, but we cut off the height
98     // where the data ends.  We want to expand it to a power-of-two bitmap
99     // with ARGB data and hand that to glTexImage2D.
100 
101     if (!isPowerOfTwo(FontBitmap::width)) {
102         ALOGE("npot glyph bitmap width %u", FontBitmap::width);
103         return UNKNOWN_ERROR;
104     }
105 
106     uint32_t potHeight = powerOfTwoCeil(FontBitmap::height);
107     uint8_t* rgbaPixels = new uint8_t[FontBitmap::width * potHeight * 4];
108     memset(rgbaPixels, 0, FontBitmap::width * potHeight * 4);
109     uint8_t* pix = rgbaPixels;
110 
111     for (unsigned int i = 0; i < FontBitmap::width * FontBitmap::height; i++) {
112         uint8_t alpha, color;
113         if ((FontBitmap::pixels[i] & 1) == 0) {
114             // black pixel with varying alpha
115             color = 0x00;
116             alpha = FontBitmap::pixels[i] & ~1;
117         } else {
118             // opaque grey pixel
119             color = FontBitmap::pixels[i] & ~1;
120             alpha = 0xff;
121         }
122         *pix++ = color;
123         *pix++ = color;
124         *pix++ = color;
125         *pix++ = alpha;
126     }
127 
128     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FontBitmap::width, potHeight, 0,
129             GL_RGBA, GL_UNSIGNED_BYTE, rgbaPixels);
130     delete[] rgbaPixels;
131     GLint glErr = glGetError();
132     if (glErr != 0) {
133         ALOGE("glTexImage2D failed: %#x", glErr);
134         return UNKNOWN_ERROR;
135     }
136     return NO_ERROR;
137 }
138 
setProportionalScale(float linesPerScreen)139 void TextRenderer::setProportionalScale(float linesPerScreen) {
140     if (mScreenWidth == 0 || mScreenHeight == 0) {
141         ALOGW("setFontScale: can't set scale for width=%d height=%d",
142                 mScreenWidth, mScreenHeight);
143         return;
144     }
145     float tallest = mScreenWidth > mScreenHeight ? mScreenWidth : mScreenHeight;
146     setScale(tallest / (linesPerScreen * getGlyphHeight()));
147 }
148 
computeScaledStringWidth(const String8 & str8) const149 float TextRenderer::computeScaledStringWidth(const String8& str8) const {
150     // String8.length() isn't documented, but I'm assuming it will return
151     // the number of characters rather than the number of bytes.  Since
152     // we can only display ASCII we want to ignore anything else, so we
153     // just convert to char* -- but String8 doesn't document what it does
154     // with values outside 0-255.  So just convert to char* and use strlen()
155     // to see what we get.
156     const char* str = str8.string();
157     return computeScaledStringWidth(str, strlen(str));
158 }
159 
glyphIndex(char ch) const160 size_t TextRenderer::glyphIndex(char ch) const {
161     size_t chi = ch - FontBitmap::firstGlyphChar;
162     if (chi >= FontBitmap::numGlyphs) {
163         chi = '?' - FontBitmap::firstGlyphChar;
164     }
165     assert(chi < FontBitmap::numGlyphs);
166     return chi;
167 }
168 
computeScaledStringWidth(const char * str,size_t len) const169 float TextRenderer::computeScaledStringWidth(const char* str,
170         size_t len) const {
171     float width = 0.0f;
172     for (size_t i = 0; i < len; i++) {
173         size_t chi = glyphIndex(str[i]);
174         float glyphWidth = FontBitmap::glyphWidth[chi];
175         width += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
176     }
177 
178     return width;
179 }
180 
drawString(const Program & program,const float * texMatrix,float x,float y,const String8 & str8) const181 void TextRenderer::drawString(const Program& program, const float* texMatrix,
182         float x, float y, const String8& str8) const {
183     ALOGV("drawString %.3f,%.3f '%s' (scale=%.3f)", x, y, str8.string(),mScale);
184     initOnce();
185 
186     // We want to draw the entire string with a single GLES call.  We
187     // generate two arrays, one with screen coordinates, one with texture
188     // coordinates.  Need two triangles per character.
189     const char* str = str8.string();
190     size_t len = strlen(str);       // again, unsure about String8 handling
191 
192     const size_t quadCoords =
193             2 /*triangles*/ * 3 /*vertex/tri*/ * 2 /*coord/vertex*/;
194     float vertices[len * quadCoords];
195     float texes[len * quadCoords];
196 
197     float fullTexWidth = FontBitmap::width;
198     float fullTexHeight = powerOfTwoCeil(FontBitmap::height);
199     for (size_t i = 0; i < len; i++) {
200         size_t chi = glyphIndex(str[i]);
201         float glyphWidth = FontBitmap::glyphWidth[chi];
202         float glyphHeight = FontBitmap::maxGlyphHeight;
203 
204         float vertLeft = x;
205         float vertRight = x + glyphWidth * mScale;
206         float vertTop = y;
207         float vertBottom = y + glyphHeight * mScale;
208 
209         // Lowest-numbered glyph is in top-left of bitmap, which puts it at
210         // the bottom-left in texture coordinates.
211         float texLeft = mXOffset[chi] / fullTexWidth;
212         float texRight = (mXOffset[chi] + glyphWidth) / fullTexWidth;
213         float texTop = FontBitmap::yoffset[chi] / fullTexHeight;
214         float texBottom = (FontBitmap::yoffset[chi] + glyphHeight) /
215                 fullTexHeight;
216 
217         size_t off = i * quadCoords;
218         vertices[off +  0] = vertLeft;
219         vertices[off +  1] = vertBottom;
220         vertices[off +  2] = vertRight;
221         vertices[off +  3] = vertBottom;
222         vertices[off +  4] = vertLeft;
223         vertices[off +  5] = vertTop;
224         vertices[off +  6] = vertLeft;
225         vertices[off +  7] = vertTop;
226         vertices[off +  8] = vertRight;
227         vertices[off +  9] = vertBottom;
228         vertices[off + 10] = vertRight;
229         vertices[off + 11] = vertTop;
230         texes[off +  0] = texLeft;
231         texes[off +  1] = texBottom;
232         texes[off +  2] = texRight;
233         texes[off +  3] = texBottom;
234         texes[off +  4] = texLeft;
235         texes[off +  5] = texTop;
236         texes[off +  6] = texLeft;
237         texes[off +  7] = texTop;
238         texes[off +  8] = texRight;
239         texes[off +  9] = texBottom;
240         texes[off + 10] = texRight;
241         texes[off + 11] = texTop;
242 
243         // We added 1-pixel padding in the texture, so we want to advance by
244         // one less.  Also, each glyph is surrounded by a black outline, which
245         // we want to merge.
246         x += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
247     }
248 
249     program.drawTriangles(mTextureName, texMatrix, vertices, texes,
250             len * quadCoords / 2);
251 }
252 
drawWrappedString(const Program & texRender,float xpos,float ypos,const String8 & str)253 float TextRenderer::drawWrappedString(const Program& texRender,
254         float xpos, float ypos, const String8& str) {
255     ALOGV("drawWrappedString %.3f,%.3f '%s'", xpos, ypos, str.string());
256     initOnce();
257 
258     if (mScreenWidth == 0 || mScreenHeight == 0) {
259         ALOGW("drawWrappedString: can't wrap with width=%d height=%d",
260                 mScreenWidth, mScreenHeight);
261         return ypos;
262     }
263 
264     const float indentWidth = mIndentMult * getScale();
265     if (xpos < mBorderWidth) {
266         xpos = mBorderWidth;
267     }
268     if (ypos < mBorderWidth) {
269         ypos = mBorderWidth;
270     }
271 
272     const size_t maxWidth = (mScreenWidth - mBorderWidth) - xpos;
273     if (maxWidth < 1) {
274         ALOGE("Unable to render text: xpos=%.3f border=%.3f width=%u",
275                 xpos, mBorderWidth, mScreenWidth);
276         return ypos;
277     }
278     float stringWidth = computeScaledStringWidth(str);
279     if (stringWidth <= maxWidth) {
280         // Trivial case.
281         drawString(texRender, Program::kIdentity, xpos, ypos, str);
282         ypos += getScaledGlyphHeight();
283     } else {
284         // We need to break the string into pieces, ideally at whitespace
285         // boundaries.
286         char* mangle = strdup(str.string());
287         char* start = mangle;
288         while (start != NULL) {
289             float xposAdj = (start == mangle) ? xpos : xpos + indentWidth;
290             char* brk = breakString(start,
291                     (float) (mScreenWidth - mBorderWidth - xposAdj));
292             if (brk == NULL) {
293                 // draw full string
294                 drawString(texRender, Program::kIdentity, xposAdj, ypos,
295                         String8(start));
296                 start = NULL;
297             } else {
298                 // draw partial string
299                 char ch = *brk;
300                 *brk = '\0';
301                 drawString(texRender, Program::kIdentity, xposAdj, ypos,
302                         String8(start));
303                 *brk = ch;
304                 start = brk;
305                 if (strchr(kWhitespace, ch) != NULL) {
306                     // if we broke on whitespace, skip past it
307                     start++;
308                 }
309             }
310             ypos += getScaledGlyphHeight();
311         }
312         free(mangle);
313     }
314 
315     return ypos;
316 }
317 
breakString(const char * str,float maxWidth) const318 char* TextRenderer::breakString(const char* str, float maxWidth) const {
319     // Ideally we'd do clever things like binary search.  Not bothering.
320     ALOGV("breakString '%s' %.3f", str, maxWidth);
321 
322     size_t len = strlen(str);
323     if (len == 0) {
324         // Caller should detect this and not advance ypos.
325         return NULL;
326     }
327 
328     float stringWidth = computeScaledStringWidth(str, len);
329     if (stringWidth <= maxWidth) {
330         return NULL;        // trivial -- use full string
331     }
332 
333     // Find the longest string that will fit.
334     size_t goodPos = 0;
335     for (size_t i = 0; i < len; i++) {
336         stringWidth = computeScaledStringWidth(str, i);
337         if (stringWidth < maxWidth) {
338             goodPos = i;
339         } else {
340             break;  // too big
341         }
342     }
343     if (goodPos == 0) {
344         // space is too small to hold any glyph; output a single char
345         ALOGW("Couldn't find a nonzero prefix that fit from '%s'", str);
346         goodPos = 1;
347     }
348 
349     // Scan back for whitespace.  If we can't find any we'll just have
350     // an ugly mid-word break.
351     for (size_t i = goodPos; i > 0; i--) {
352         if (strchr(kWhitespace, str[i]) != NULL) {
353             goodPos = i;
354             break;
355         }
356     }
357 
358     ALOGV("goodPos=%zu for str='%s'", goodPos, str);
359     return const_cast<char*>(str + goodPos);
360 }
361