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