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 36 void TextRenderer::initOnce() { 37 if (!mInitialized) { 38 initXOffset(); 39 mInitialized = true; 40 } 41 } 42 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 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 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 78 float TextRenderer::getGlyphHeight() const { 79 return FontBitmap::maxGlyphHeight; 80 } 81 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 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 147 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 158 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 167 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 179 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 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 316 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