C++程序  |  361行  |  11.96 KB

/*
 * Copyright 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "ScreenRecord"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#include "TextRenderer.h"

#include <assert.h>
#include <malloc.h>
#include <string.h>

namespace android {
#include "FontBitmap.h"
};

using namespace android;

const char TextRenderer::kWhitespace[] = " \t\n\r";

bool TextRenderer::mInitialized = false;
uint32_t TextRenderer::mXOffset[FontBitmap::numGlyphs];

void TextRenderer::initOnce() {
    if (!mInitialized) {
        initXOffset();
        mInitialized = true;
    }
}

void TextRenderer::initXOffset() {
    // Generate a table of X offsets.  They start at zero and reset whenever
    // we move down a line (i.e. the Y offset changes).  The offset increases
    // by one pixel more than the width because the generator left a gap to
    // avoid reading pixels from adjacent glyphs in the texture filter.
    uint16_t offset = 0;
    uint16_t prevYOffset = (int16_t) -1;
    for (unsigned int i = 0; i < FontBitmap::numGlyphs; i++) {
        if (prevYOffset != FontBitmap::yoffset[i]) {
            prevYOffset = FontBitmap::yoffset[i];
            offset = 0;
        }
        mXOffset[i] = offset;
        offset += FontBitmap::glyphWidth[i] + 1;
    }
}

static bool isPowerOfTwo(uint32_t val) {
    // a/k/a "is exactly one bit set"; note returns true for 0
    return (val & (val -1)) == 0;
}

static uint32_t powerOfTwoCeil(uint32_t val) {
    // drop it, smear the bits across, pop it
    val--;
    val |= val >> 1;
    val |= val >> 2;
    val |= val >> 4;
    val |= val >> 8;
    val |= val >> 16;
    val++;

    return val;
}

float TextRenderer::getGlyphHeight() const {
    return FontBitmap::maxGlyphHeight;
}

status_t TextRenderer::loadIntoTexture() {
    ALOGV("Font::loadIntoTexture");

    glGenTextures(1, &mTextureName);
    if (mTextureName == 0) {
        ALOGE("glGenTextures failed: %#x", glGetError());
        return UNKNOWN_ERROR;
    }
    glBindTexture(GL_TEXTURE_2D, mTextureName);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // The pixel data is stored as combined color+alpha, 8 bits per pixel.
    // It's guaranteed to be a power-of-two wide, but we cut off the height
    // where the data ends.  We want to expand it to a power-of-two bitmap
    // with ARGB data and hand that to glTexImage2D.

    if (!isPowerOfTwo(FontBitmap::width)) {
        ALOGE("npot glyph bitmap width %u", FontBitmap::width);
        return UNKNOWN_ERROR;
    }

    uint32_t potHeight = powerOfTwoCeil(FontBitmap::height);
    uint8_t* rgbaPixels = new uint8_t[FontBitmap::width * potHeight * 4];
    memset(rgbaPixels, 0, FontBitmap::width * potHeight * 4);
    uint8_t* pix = rgbaPixels;

    for (unsigned int i = 0; i < FontBitmap::width * FontBitmap::height; i++) {
        uint8_t alpha, color;
        if ((FontBitmap::pixels[i] & 1) == 0) {
            // black pixel with varying alpha
            color = 0x00;
            alpha = FontBitmap::pixels[i] & ~1;
        } else {
            // opaque grey pixel
            color = FontBitmap::pixels[i] & ~1;
            alpha = 0xff;
        }
        *pix++ = color;
        *pix++ = color;
        *pix++ = color;
        *pix++ = alpha;
    }

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FontBitmap::width, potHeight, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, rgbaPixels);
    delete[] rgbaPixels;
    GLint glErr = glGetError();
    if (glErr != 0) {
        ALOGE("glTexImage2D failed: %#x", glErr);
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}

void TextRenderer::setProportionalScale(float linesPerScreen) {
    if (mScreenWidth == 0 || mScreenHeight == 0) {
        ALOGW("setFontScale: can't set scale for width=%d height=%d",
                mScreenWidth, mScreenHeight);
        return;
    }
    float tallest = mScreenWidth > mScreenHeight ? mScreenWidth : mScreenHeight;
    setScale(tallest / (linesPerScreen * getGlyphHeight()));
}

float TextRenderer::computeScaledStringWidth(const String8& str8) const {
    // String8.length() isn't documented, but I'm assuming it will return
    // the number of characters rather than the number of bytes.  Since
    // we can only display ASCII we want to ignore anything else, so we
    // just convert to char* -- but String8 doesn't document what it does
    // with values outside 0-255.  So just convert to char* and use strlen()
    // to see what we get.
    const char* str = str8.string();
    return computeScaledStringWidth(str, strlen(str));
}

size_t TextRenderer::glyphIndex(char ch) const {
    size_t chi = ch - FontBitmap::firstGlyphChar;
    if (chi >= FontBitmap::numGlyphs) {
        chi = '?' - FontBitmap::firstGlyphChar;
    }
    assert(chi < FontBitmap::numGlyphs);
    return chi;
}

float TextRenderer::computeScaledStringWidth(const char* str,
        size_t len) const {
    float width = 0.0f;
    for (size_t i = 0; i < len; i++) {
        size_t chi = glyphIndex(str[i]);
        float glyphWidth = FontBitmap::glyphWidth[chi];
        width += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
    }

    return width;
}

void TextRenderer::drawString(const Program& program, const float* texMatrix,
        float x, float y, const String8& str8) const {
    ALOGV("drawString %.3f,%.3f '%s' (scale=%.3f)", x, y, str8.string(),mScale);
    initOnce();

    // We want to draw the entire string with a single GLES call.  We
    // generate two arrays, one with screen coordinates, one with texture
    // coordinates.  Need two triangles per character.
    const char* str = str8.string();
    size_t len = strlen(str);       // again, unsure about String8 handling

    const size_t quadCoords =
            2 /*triangles*/ * 3 /*vertex/tri*/ * 2 /*coord/vertex*/;
    float vertices[len * quadCoords];
    float texes[len * quadCoords];

    float fullTexWidth = FontBitmap::width;
    float fullTexHeight = powerOfTwoCeil(FontBitmap::height);
    for (size_t i = 0; i < len; i++) {
        size_t chi = glyphIndex(str[i]);
        float glyphWidth = FontBitmap::glyphWidth[chi];
        float glyphHeight = FontBitmap::maxGlyphHeight;

        float vertLeft = x;
        float vertRight = x + glyphWidth * mScale;
        float vertTop = y;
        float vertBottom = y + glyphHeight * mScale;

        // Lowest-numbered glyph is in top-left of bitmap, which puts it at
        // the bottom-left in texture coordinates.
        float texLeft = mXOffset[chi] / fullTexWidth;
        float texRight = (mXOffset[chi] + glyphWidth) / fullTexWidth;
        float texTop = FontBitmap::yoffset[chi] / fullTexHeight;
        float texBottom = (FontBitmap::yoffset[chi] + glyphHeight) /
                fullTexHeight;

        size_t off = i * quadCoords;
        vertices[off +  0] = vertLeft;
        vertices[off +  1] = vertBottom;
        vertices[off +  2] = vertRight;
        vertices[off +  3] = vertBottom;
        vertices[off +  4] = vertLeft;
        vertices[off +  5] = vertTop;
        vertices[off +  6] = vertLeft;
        vertices[off +  7] = vertTop;
        vertices[off +  8] = vertRight;
        vertices[off +  9] = vertBottom;
        vertices[off + 10] = vertRight;
        vertices[off + 11] = vertTop;
        texes[off +  0] = texLeft;
        texes[off +  1] = texBottom;
        texes[off +  2] = texRight;
        texes[off +  3] = texBottom;
        texes[off +  4] = texLeft;
        texes[off +  5] = texTop;
        texes[off +  6] = texLeft;
        texes[off +  7] = texTop;
        texes[off +  8] = texRight;
        texes[off +  9] = texBottom;
        texes[off + 10] = texRight;
        texes[off + 11] = texTop;

        // We added 1-pixel padding in the texture, so we want to advance by
        // one less.  Also, each glyph is surrounded by a black outline, which
        // we want to merge.
        x += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
    }

    program.drawTriangles(mTextureName, texMatrix, vertices, texes,
            len * quadCoords / 2);
}

float TextRenderer::drawWrappedString(const Program& texRender,
        float xpos, float ypos, const String8& str) {
    ALOGV("drawWrappedString %.3f,%.3f '%s'", xpos, ypos, str.string());
    initOnce();

    if (mScreenWidth == 0 || mScreenHeight == 0) {
        ALOGW("drawWrappedString: can't wrap with width=%d height=%d",
                mScreenWidth, mScreenHeight);
        return ypos;
    }

    const float indentWidth = mIndentMult * getScale();
    if (xpos < mBorderWidth) {
        xpos = mBorderWidth;
    }
    if (ypos < mBorderWidth) {
        ypos = mBorderWidth;
    }

    const size_t maxWidth = (mScreenWidth - mBorderWidth) - xpos;
    if (maxWidth < 1) {
        ALOGE("Unable to render text: xpos=%.3f border=%.3f width=%u",
                xpos, mBorderWidth, mScreenWidth);
        return ypos;
    }
    float stringWidth = computeScaledStringWidth(str);
    if (stringWidth <= maxWidth) {
        // Trivial case.
        drawString(texRender, Program::kIdentity, xpos, ypos, str);
        ypos += getScaledGlyphHeight();
    } else {
        // We need to break the string into pieces, ideally at whitespace
        // boundaries.
        char* mangle = strdup(str.string());
        char* start = mangle;
        while (start != NULL) {
            float xposAdj = (start == mangle) ? xpos : xpos + indentWidth;
            char* brk = breakString(start,
                    (float) (mScreenWidth - mBorderWidth - xposAdj));
            if (brk == NULL) {
                // draw full string
                drawString(texRender, Program::kIdentity, xposAdj, ypos,
                        String8(start));
                start = NULL;
            } else {
                // draw partial string
                char ch = *brk;
                *brk = '\0';
                drawString(texRender, Program::kIdentity, xposAdj, ypos,
                        String8(start));
                *brk = ch;
                start = brk;
                if (strchr(kWhitespace, ch) != NULL) {
                    // if we broke on whitespace, skip past it
                    start++;
                }
            }
            ypos += getScaledGlyphHeight();
        }
        free(mangle);
    }

    return ypos;
}

char* TextRenderer::breakString(const char* str, float maxWidth) const {
    // Ideally we'd do clever things like binary search.  Not bothering.
    ALOGV("breakString '%s' %.3f", str, maxWidth);

    size_t len = strlen(str);
    if (len == 0) {
        // Caller should detect this and not advance ypos.
        return NULL;
    }

    float stringWidth = computeScaledStringWidth(str, len);
    if (stringWidth <= maxWidth) {
        return NULL;        // trivial -- use full string
    }

    // Find the longest string that will fit.
    size_t goodPos = 0;
    for (size_t i = 0; i < len; i++) {
        stringWidth = computeScaledStringWidth(str, i);
        if (stringWidth < maxWidth) {
            goodPos = i;
        } else {
            break;  // too big
        }
    }
    if (goodPos == 0) {
        // space is too small to hold any glyph; output a single char
        ALOGW("Couldn't find a nonzero prefix that fit from '%s'", str);
        goodPos = 1;
    }

    // Scan back for whitespace.  If we can't find any we'll just have
    // an ugly mid-word break.
    for (size_t i = goodPos; i > 0; i--) {
        if (strchr(kWhitespace, str[i]) != NULL) {
            goodPos = i;
            break;
        }
    }

    ALOGV("goodPos=%zu for str='%s'", goodPos, str);
    return const_cast<char*>(str + goodPos);
}