// BitmapFont: Class for using tiny fonts with subpixel antialising
// Formerly LCDFont.
//
// Copyright 2005 Roar Lauritzsen <roarl@pvv.org>
// Modified 2010 for use in BBSSH
// Modifications Copyright (C) 2010 Marc A. Paradise
//
// This class is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This class is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The following link provides a copy of the GNU General Public License:
// http://www.gnu.org/licenses/gpl.txt
// If you are unable to obtain the copy from this address, write to
// the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA
package org.bbssh.terminal.fonts;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.XYPoint;
import org.bbssh.exceptions.FontNotFoundException;
import org.bbssh.util.Tools;
// @todo - merge this with FontRecord itself?
// @todo - BitmapFont instances should implement disposable - this is a
// a fair chunk of data that we might be able to discard.
public class BitmapFont {
private final int imageWidth; // width of font bitmap
private final int imageHeight; // height of font bitmap
private int[] bwBuf; // Black-white version of font
private int[] colorBuf; // Colored version of font
private int[] currentBuf;
private long[] cacheColor; // Color of each cached character
private int FR, FG, FB, BR, BG, BB;
private long currentColor;
private int totalColumns = 0;
FontRecord record;
/**
* Constructor to create a bitmap font based on image resource name.
*
* @param record
* the source font record
* @throws FontNotFoundException
*/
// Create subpixel-antialiased font based on image resource name.
protected BitmapFont(FontRecord record) throws FontNotFoundException {
// @todo we need to retain these images so that we don't load multiple
// copies if multiple sessions are running.
// @todo erm, we should be using the file name...
this.record = record;
Bitmap fontImage = Tools.loadBitmap(record.getFileName());
if (fontImage == null) {
throw new FontNotFoundException();
}
imageWidth = fontImage.getWidth();
imageHeight = fontImage.getHeight();
bwBuf = new int[imageWidth * (imageHeight)];
colorBuf = new int[imageWidth * (imageHeight)];
fontImage.getARGB(bwBuf, 0, imageWidth, 0, 0, imageWidth, imageHeight);
cacheColor = new long[255];
currentBuf = bwBuf;
currentColor = 0;
totalColumns = imageWidth / record.getDimensions().x;
}
// Set the foreground and background color of font
// For readability, input font colors will be modified
public void setColor(int fg, int bg) {
if (fg == 0xffffff && bg == 0) {
currentBuf = bwBuf;
} else {
currentBuf = colorBuf;
long color = ((long) fg << 32) + bg;
if (currentColor != color) {
currentColor = color;
// First isolate the colors
FR = (fg >> 16) & 0xFF;
FG = (fg >> 8) & 0xFF;
FB = fg & 0xFF;
BR = (bg >> 16) & 0xFF;
BG = (bg >> 8) & 0xFF;
BB = bg & 0xFF;
// // Because of the subpixel antialising, we cannot use the color
// // directly. Instead, we have to select a more "pastel" color,
// // and differentiate properly between background and foreground
// if (28 * FR + 55 * FG + 17 * FB >= 28 * BR + 55 * BG + 17 * BB) {
// // bright on dark
// FR = (6 * FR + 10 * 0xff) / 16; // Brighten foreground
// FG = (6 * FG + 10 * 0xff) / 16;
// FB = (6 * FB + 10 * 0xff) / 16;
// BR = (10 * BR) / 16; // Darken background
// BG = (10 * BG) / 16;
// BB = (10 * BB) / 16;
// } else {
// // dark on bright
// FR = (10 * FR) / 16; // Darken foreground
// FG = (10 * FG) / 16;
// FB = (10 * FB) / 16;
// BR = (6 * BR + 10 * 0xff) / 16; // Brighten background
// BG = (6 * BG + 10 * 0xff) / 16;
// BB = (6 * BB + 10 * 0xff) / 16;
// }
//
//
if (record.isLegacyFont()) {
// Scale to range 0-256 for later ">>8" instead of "/255"
FR += FR >> 7;
FG += FG >> 7;
FB += FB >> 7;
BR += BR >> 7;
BG += BG >> 7;
BB += BB >> 7;
}
}
}
}
/**
* Update the character at position "offset" to use the current FG/BG color.
*
* @param offset
*/
private void renderColorChar(int offset) {
// @todo - let's see how often we call this in a typical session. Perhaps
// we could cache a wider range of characters?
int R, G, B, idx, col;
XYPoint dimensions = record.getDimensions();
int maxy = dimensions.y; // not sure how efficient the JVM is
int maxx = dimensions.x; // so let's pull this out instead of putting it in the loop
// Paint this glyph with the current FG/BG colors, starting from the top
// left and moving to the bottom right
for (int y = 0; y < maxy; y++) {
for (int x = 0; x < maxx; x++) {
// Get from the black and white buffer for our baseline.
idx = offset + y * imageWidth + x;
col = bwBuf[idx];
R = (col >> 16) & 0xff;
G = (col >> 8) & 0xff;
B = col & 0xff;
// If this is the background color, we'll do a simple replacement.
if (R == 0 && G == 0 && B == 0) {
R = BR;
G = BG;
B = BB;
} else {
// fonts are drawn with attempted subpixel hinting, so
// we need to adjust each color (except saturated colros which we can
// replace without the additional operations ).
R = (R == 255) ? FR : ((FR * R + BR * (255 - R)) >> 8);
G = (G == 255) ? FG : ((FG * G + BG * (255 - G)) >> 8);
B = (B == 255) ? FB : ((FB * B + BB * (255 - B)) >> 8);
}
// stick it in the color image buffer for this font so that as long as we're
// drawing this color, we don't have to perform this again.
colorBuf[idx] = (R << 16) + (G << 8) + B;
// @todo - profile this. if needed since we have a limited number of colors (8) we can
// maintain bufers for each
}
}
}
/**
* Draws a single character, using the current color as buffered.
*
* @param g
* graphics to render the drawing onto
* @param c
* character to draw
* @param x
* x position to start drawing
* @param y
* y position to start drawing
*/
public void drawChar(Graphics g, char c, int x, int y) {
XYPoint dimensions = record.getDimensions();
// @todo - line-drawing support changes
// @todo we should also cache drawn characters.
// SoftFont sf = SoftFont.getInstance();
// if (sf.inSoftFont(c)) {
// // if ((currAttr & VDUBuffer.INVISIBLE) == 0)
// sf.drawChar(g, c, x, y, dimensions.x, dimensions.y);
// // if ((currAttr & VDUBuffer.UNDERLINE) != 0)
// // g.drawLine(c * charWidth + xoffset,
// // (l + 1) * charHeight - charDescent / 2 + yoffset,
// // c * charWidth + charWidth + xoffset,
// // (l + 1) *
// // continue;
// return;
// }
// Before we can treat "c" as our index into the bitmap...
// 127 through 159 and 173 are control codes; our bitmaps do not haev entries for them
if (c <= ' ' || (c >= 127 && c <= 159) || c == 173) {
return;
}
int offset = 33; // skip the first 33 characters 0 - 32: they are not renderable
// and are not included in our
// bitmap.
// anything after 159 has to account for the gap in the bitmap - we don't have control characters in the bitmap.
// There 33 control characters
// / Legacy fonts don't ahve anything higher than 127.
if (record.isLegacyFont() && c > 127) {
return;
} else {
if (c > 159) {
offset += 33;
}
// 173 is another non-rendered control character
if (c > 173) {
offset++;
}
}
// "c" now becomes our index into the bitmap image for this character.
c -= offset;
offset = (c / totalColumns) * (dimensions.y * imageHeight) + ((c % totalColumns) * dimensions.x);
// Logger.debug("Char: " + c + " offset: " + offset);
if (currentBuf == colorBuf && cacheColor[c] != currentColor) {
renderColorChar(offset);
cacheColor[c] = currentColor;
}
g.drawRGB(currentBuf, offset, imageWidth, x, y, dimensions.x, dimensions.y);
}
public XYPoint getDimensions() {
return record.getDimensions();
}
}