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 27namespace android { 28#include "FontBitmap.h" 29}; 30 31using namespace android; 32 33const char TextRenderer::kWhitespace[] = " \t\n\r"; 34 35bool TextRenderer::mInitialized = false; 36uint32_t TextRenderer::mXOffset[FontBitmap::numGlyphs]; 37 38void TextRenderer::initOnce() { 39 if (!mInitialized) { 40 initXOffset(); 41 mInitialized = true; 42 } 43} 44 45void 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 62static 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 67static 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 80float TextRenderer::getGlyphHeight() const { 81 return FontBitmap::maxGlyphHeight; 82} 83 84status_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 139void 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 149float 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 160size_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 169float 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 181void 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 253float 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 318char* 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