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