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