TextRenderer.cpp revision 7a66622c2c9250ce4ad0091195331ce4cb91a63d
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    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
137void 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
147float 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
158size_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
167float 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
179void 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
251float 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
316char* 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=%d for str='%s'", goodPos, str);
357    return const_cast<char*>(str + goodPos);
358}
359