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