TextRenderer.cpp revision 441e847feb0e055ecb004802802cea07782ab228
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 25namespace android { 26#include "FontBitmap.h" 27}; 28 29using namespace android; 30 31const char TextRenderer::kWhitespace[] = " \t\n\r"; 32 33bool TextRenderer::mInitialized = false; 34uint32_t TextRenderer::mXOffset[FontBitmap::numGlyphs]; 35 36void TextRenderer::initOnce() { 37 if (!mInitialized) { 38 initXOffset(); 39 mInitialized = true; 40 } 41} 42 43void 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 60static 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 65static 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 78float TextRenderer::getGlyphHeight() const { 79 return FontBitmap::maxGlyphHeight; 80} 81 82status_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 uint32_t* rgbaPixels = new uint32_t[FontBitmap::width * potHeight]; 106 memset(rgbaPixels, 0, FontBitmap::width * potHeight * 4); 107 108 for (unsigned int i = 0; i < FontBitmap::width * FontBitmap::height; i++) { 109 uint8_t alpha, color; 110 if ((FontBitmap::pixels[i] & 1) == 0) { 111 // black pixel with varying alpha 112 color = 0x00; 113 alpha = FontBitmap::pixels[i] & ~1; 114 } else { 115 // opaque grey pixel 116 color = FontBitmap::pixels[i] & ~1; 117 alpha = 0xff; 118 } 119 rgbaPixels[i] = (alpha << 24) | (color << 16) | (color << 8) | color; 120 } 121 122 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FontBitmap::width, potHeight, 0, 123 GL_RGBA, GL_UNSIGNED_BYTE, rgbaPixels); 124 delete[] rgbaPixels; 125 GLint glErr = glGetError(); 126 if (glErr != 0) { 127 ALOGE("glTexImage2D failed: %#x", glErr); 128 return UNKNOWN_ERROR; 129 } 130 return NO_ERROR; 131} 132 133void TextRenderer::setProportionalScale(float linesPerScreen) { 134 if (mScreenWidth == 0 || mScreenHeight == 0) { 135 ALOGW("setFontScale: can't set scale for width=%d height=%d", 136 mScreenWidth, mScreenHeight); 137 return; 138 } 139 float tallest = mScreenWidth > mScreenHeight ? mScreenWidth : mScreenHeight; 140 setScale(tallest / (linesPerScreen * getGlyphHeight())); 141} 142 143float TextRenderer::computeScaledStringWidth(const String8& str8) const { 144 // String8.length() isn't documented, but I'm assuming it will return 145 // the number of characters rather than the number of bytes. Since 146 // we can only display ASCII we want to ignore anything else, so we 147 // just convert to char* -- but String8 doesn't document what it does 148 // with values outside 0-255. So just convert to char* and use strlen() 149 // to see what we get. 150 const char* str = str8.string(); 151 return computeScaledStringWidth(str, strlen(str)); 152} 153 154float TextRenderer::computeScaledStringWidth(const char* str, 155 size_t len) const { 156 float width = 0.0f; 157 for (size_t i = 0; i < len; i++) { 158 size_t chi = str[i] - FontBitmap::firstGlyphChar; 159 float glyphWidth = FontBitmap::glyphWidth[chi]; 160 width += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale; 161 } 162 163 return width; 164} 165 166void TextRenderer::drawString(const Program& program, const float* texMatrix, 167 float x, float y, const String8& str8) const { 168 ALOGV("drawString %.3f,%.3f '%s' (scale=%.3f)", x, y, str8.string(),mScale); 169 initOnce(); 170 171 // We want to draw the entire string with a single GLES call. We 172 // generate two arrays, one with screen coordinates, one with texture 173 // coordinates. Need two triangles per character. 174 const char* str = str8.string(); 175 size_t len = strlen(str); // again, unsure about String8 handling 176 177 const size_t quadCoords = 178 2 /*triangles*/ * 3 /*vertex/tri*/ * 2 /*coord/vertex*/; 179 float vertices[len * quadCoords]; 180 float texes[len * quadCoords]; 181 182 float fullTexWidth = FontBitmap::width; 183 float fullTexHeight = powerOfTwoCeil(FontBitmap::height); 184 for (size_t i = 0; i < len; i++) { 185 size_t chi = str[i] - FontBitmap::firstGlyphChar; 186 if (chi >= FontBitmap::numGlyphs) { 187 chi = '?' - FontBitmap::firstGlyphChar; 188 assert(chi < FontBitmap::numGlyphs); 189 } 190 float glyphWidth = FontBitmap::glyphWidth[chi]; 191 float glyphHeight = FontBitmap::maxGlyphHeight; 192 193 float vertLeft = x; 194 float vertRight = x + glyphWidth * mScale; 195 float vertTop = y; 196 float vertBottom = y + glyphHeight * mScale; 197 198 // Lowest-numbered glyph is in top-left of bitmap, which puts it at 199 // the bottom-left in texture coordinates. 200 float texLeft = mXOffset[chi] / fullTexWidth; 201 float texRight = (mXOffset[chi] + glyphWidth) / fullTexWidth; 202 float texTop = FontBitmap::yoffset[chi] / fullTexHeight; 203 float texBottom = (FontBitmap::yoffset[chi] + glyphHeight) / 204 fullTexHeight; 205 206 size_t off = i * quadCoords; 207 vertices[off + 0] = vertLeft; 208 vertices[off + 1] = vertBottom; 209 vertices[off + 2] = vertRight; 210 vertices[off + 3] = vertBottom; 211 vertices[off + 4] = vertLeft; 212 vertices[off + 5] = vertTop; 213 vertices[off + 6] = vertLeft; 214 vertices[off + 7] = vertTop; 215 vertices[off + 8] = vertRight; 216 vertices[off + 9] = vertBottom; 217 vertices[off + 10] = vertRight; 218 vertices[off + 11] = vertTop; 219 texes[off + 0] = texLeft; 220 texes[off + 1] = texBottom; 221 texes[off + 2] = texRight; 222 texes[off + 3] = texBottom; 223 texes[off + 4] = texLeft; 224 texes[off + 5] = texTop; 225 texes[off + 6] = texLeft; 226 texes[off + 7] = texTop; 227 texes[off + 8] = texRight; 228 texes[off + 9] = texBottom; 229 texes[off + 10] = texRight; 230 texes[off + 11] = texTop; 231 232 // We added 1-pixel padding in the texture, so we want to advance by 233 // one less. Also, each glyph is surrounded by a black outline, which 234 // we want to merge. 235 x += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale; 236 } 237 238 program.drawTriangles(mTextureName, texMatrix, vertices, texes, 239 len * quadCoords / 2); 240} 241 242float TextRenderer::drawWrappedString(const Program& texRender, 243 float xpos, float ypos, const String8& str) { 244 ALOGV("drawWrappedString %.3f,%.3f '%s'", xpos, ypos, str.string()); 245 initOnce(); 246 247 if (mScreenWidth == 0 || mScreenHeight == 0) { 248 ALOGW("drawWrappedString: can't wrap with width=%d height=%d", 249 mScreenWidth, mScreenHeight); 250 return ypos; 251 } 252 253 const float indentWidth = mIndentMult * getScale(); 254 if (xpos < mBorderWidth) { 255 xpos = mBorderWidth; 256 } 257 if (ypos < mBorderWidth) { 258 ypos = mBorderWidth; 259 } 260 261 const size_t maxWidth = (mScreenWidth - mBorderWidth) - xpos; 262 if (maxWidth < 1) { 263 ALOGE("Unable to render text: xpos=%.3f border=%.3f width=%u", 264 xpos, mBorderWidth, mScreenWidth); 265 return ypos; 266 } 267 float stringWidth = computeScaledStringWidth(str); 268 if (stringWidth <= maxWidth) { 269 // Trivial case. 270 drawString(texRender, Program::kIdentity, xpos, ypos, str); 271 ypos += getScaledGlyphHeight(); 272 } else { 273 // We need to break the string into pieces, ideally at whitespace 274 // boundaries. 275 char* mangle = strdup(str.string()); 276 char* start = mangle; 277 while (start != NULL) { 278 float xposAdj = (start == mangle) ? xpos : xpos + indentWidth; 279 char* brk = breakString(start, 280 (float) (mScreenWidth - mBorderWidth - xposAdj)); 281 if (brk == NULL) { 282 // draw full string 283 drawString(texRender, Program::kIdentity, xposAdj, ypos, 284 String8(start)); 285 start = NULL; 286 } else { 287 // draw partial string 288 char ch = *brk; 289 *brk = '\0'; 290 drawString(texRender, Program::kIdentity, xposAdj, ypos, 291 String8(start)); 292 *brk = ch; 293 start = brk; 294 if (strchr(kWhitespace, ch) != NULL) { 295 // if we broke on whitespace, skip past it 296 start++; 297 } 298 } 299 ypos += getScaledGlyphHeight(); 300 } 301 free(mangle); 302 } 303 304 return ypos; 305} 306 307char* TextRenderer::breakString(const char* str, float maxWidth) const { 308 // Ideally we'd do clever things like binary search. Not bothering. 309 ALOGV("breakString '%s' %.3f", str, maxWidth); 310 311 size_t len = strlen(str); 312 if (len == 0) { 313 // Caller should detect this and not advance ypos. 314 return NULL; 315 } 316 317 float stringWidth = computeScaledStringWidth(str, len); 318 if (stringWidth <= maxWidth) { 319 return NULL; // trivial -- use full string 320 } 321 322 // Find the longest string that will fit. 323 size_t goodPos = 0; 324 for (size_t i = 0; i < len; i++) { 325 stringWidth = computeScaledStringWidth(str, i); 326 if (stringWidth < maxWidth) { 327 goodPos = i; 328 } else { 329 break; // too big 330 } 331 } 332 if (goodPos == 0) { 333 // space is too small to hold any glyph; output a single char 334 ALOGW("Couldn't find a nonzero prefix that fit from '%s'", str); 335 goodPos = 1; 336 } 337 338 // Scan back for whitespace. If we can't find any we'll just have 339 // an ugly mid-word break. 340 for (size_t i = goodPos; i > 0; i--) { 341 if (strchr(kWhitespace, str[i]) != NULL) { 342 goodPos = i; 343 break; 344 } 345 } 346 347 ALOGV("goodPos=%d for str='%s'", goodPos, str); 348 return const_cast<char*>(str + goodPos); 349} 350