android-x-server/src/tdm/xserver/FontDataPCF.java

637 lines
23 KiB
Java

package tdm.xserver;
import android.content.res.AssetManager;
import android.util.Log;
import android.graphics.Bitmap;
import android.graphics.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.zip.GZIPInputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
class FontDataPCF extends FontData
{
static final int PATH_TYPE_FILE = 1;
static final int PATH_TYPE_ASSET = 2;
static final int PCF_PROPERTIES = (1<<0);
static final int PCF_ACCELERATORS = (1<<1);
static final int PCF_METRICS = (1<<2);
static final int PCF_BITMAPS = (1<<3);
static final int PCF_INK_METRICS = (1<<4);
static final int PCF_BDF_ENCODINGS = (1<<5);
static final int PCF_SWIDTHS = (1<<6);
static final int PCF_GLYPH_NAMES = (1<<7);
static final int PCF_BDF_ACCELERATORS = (1<<8);
static final int PCF_DEFAULT_FORMAT = 0x00000000;
static final int PCF_INKBOUNDS = 0x00000200;
static final int PCF_ACCEL_W_INKBOUNDS = 0x00000100;
static final int PCF_COMPRESSED_METRICS = 0x00000100;
static final int PCF_GLYPH_PAD_MASK = (3<<0); // See the bitmap table for explanation
static final int PCF_BYTE_MASK = (1<<2); // If set then Most Sig Byte First
static final int PCF_BIT_MASK = (1<<3); // If set then Most Sig Bit First
static final int PCF_SCAN_UNIT_MASK = (3<<4); // See the bitmap table for explanation
class pcf_toc_entry
{
int type;
int format;
int size;
int offset;
}
class pcf_toc_order_by_offset implements Comparator
{
public int compare(Object o1, Object o2) {
pcf_toc_entry e1 = (pcf_toc_entry)o1;
pcf_toc_entry e2 = (pcf_toc_entry)o2;
return e1.offset - e2.offset;
}
}
class pcf_property
{
int name_offset;
byte is_string;
int value;
}
class pcf_metrics
{
short left_side_bearing;
short right_side_bearing;
short character_width;
short character_ascent;
short character_descent;
short character_attributes;
}
class pcf_accelerator
{
byte no_overlap;
byte constant_metrics;
byte terminal_font;
byte constant_width;
byte ink_inside;
byte ink_metrics;
byte draw_direction;
byte padding;
int font_ascent;
int font_descent;
int max_overlap;
pcf_metrics min_bounds;
pcf_metrics max_bounds;
pcf_metrics ink_min_bounds;
pcf_metrics ink_max_bounds;
pcf_accelerator() {
min_bounds = new pcf_metrics();
max_bounds = new pcf_metrics();
ink_min_bounds = new pcf_metrics();
ink_max_bounds = new pcf_metrics();
}
}
class pcf_encoding
{
short min_char_or_byte2;
short max_char_or_byte2;
short min_byte1;
short max_byte1;
short default_char;
short[] glyph_indices;
}
static void globalInit(X11Server server) {
BufferedReader br;
String s;
try {
AssetManager am = server.mContext.getAssets();
br = new BufferedReader(new InputStreamReader(am.open("fonts/fonts.dir")));
br.readLine(); // Skip first line
while ((s = br.readLine()) != null) {
String[] fields = s.split(" ", 2);
String pathname = "fonts/" + fields[0];
FontDataPCF pcf = new FontDataPCF(fields[1], PATH_TYPE_ASSET, pathname);
server.registerFont(pcf);
}
br = new BufferedReader(new InputStreamReader(am.open("fonts/fonts.alias")));
while ((s = br.readLine()) != null) {
if (s.length() == 0 || s.charAt(0) == '!') {
continue;
}
String[] fields = s.split("[ \t]{1,}", 2);
server.registerFontAlias(fields[0].toLowerCase(), fields[1].toLowerCase());
}
}
catch (Exception e) {
Log.e("X", "Failed to init PCF fonts from assets");
}
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream("/data/local/fonts/fonts.dir")));
br.readLine(); // Skip first line
while ((s = br.readLine()) != null) {
if (s.length() == 0 || s.charAt(0) == '!') {
continue;
}
String[] fields = s.split(" ", 2);
if (fields.length != 2) {
Log.e("X", "bad line in fonts.dir: fields=" + fields.length + ", line=" + s);
continue;
}
String pathname = "/data/local/fonts/" + fields[0];
FontDataPCF pcf = new FontDataPCF(fields[1], PATH_TYPE_FILE, pathname);
server.registerFont(pcf);
}
br = new BufferedReader(new InputStreamReader(new FileInputStream("/data/local/fonts/fonts.alias")));
while ((s = br.readLine()) != null) {
if (s.length() == 0 || s.charAt(0) == '!') {
continue;
}
String[] fields = s.split("[ \t]{1,}", 2);
if (fields.length != 2) {
Log.e("X", "bad line in fonts.alias: fields=" + fields.length + ", line=" + s);
continue;
}
server.registerFontAlias(fields[0].toLowerCase(), fields[1].toLowerCase());
}
}
catch (Exception e) {
Log.e("X", "Failed to init PCF fonts from dir");
e.printStackTrace();
}
}
int mPathtype; // FILE or ASSET
String mPathname;
ArrayList<X11CharInfo> mGlyphInfos;
FontDataPCF(String name, int pathtype, String pathname) {
super(name);
mPathtype = pathtype;
mPathname = pathname;
mGlyphInfos = new ArrayList<X11CharInfo>();
}
private void readPropertyTable(X11Server server, int fmt, ByteBuffer b) {
b.order(ByteOrder.LITTLE_ENDIAN);
int format = b.getInt();
b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
int nprops = b.getInt();
pcf_property[] props = new pcf_property[nprops];
int n;
for (n = 0; n < nprops; ++n) {
pcf_property prop = new pcf_property();
prop.name_offset = b.getInt();
prop.is_string = b.get();
prop.value = b.getInt();
props[n] = prop;
}
b.position(MathX.roundup(b.position(), 4));
int string_size = b.getInt();
byte[] string_table = new byte[string_size];
b.get(string_table);
mProperties = new ArrayList<X11FontProp>(nprops);
for (n = 0; n < nprops; ++n) {
String s = new String(string_table, props[n].name_offset);
X11FontProp prop = new X11FontProp();
prop.name = server.doInternAtom(s);
if (props[n].is_string != 0) {
String val = new String(string_table, props[n].value);
prop.value = server.doInternAtom(val);
}
else {
prop.value = props[n].value;
}
mProperties.add(n, prop);
}
}
private void readAcceleratorTable(int fmt, ByteBuffer b) {
b.order(ByteOrder.LITTLE_ENDIAN);
int format = b.getInt();
b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
pcf_accelerator a = new pcf_accelerator();
a.no_overlap = b.get();
a.constant_metrics = b.get();
a.terminal_font = b.get();
a.constant_width = b.get();
a.ink_inside = b.get();
a.ink_metrics = b.get();
a.draw_direction = b.get();
a.padding = b.get();
a.font_ascent = b.getInt();
a.font_descent = b.getInt();
a.max_overlap = b.getInt();
a.min_bounds.left_side_bearing = b.getShort();
a.min_bounds.right_side_bearing = b.getShort();
a.min_bounds.character_width = b.getShort();
a.min_bounds.character_ascent = b.getShort();
a.min_bounds.character_descent = b.getShort();
a.min_bounds.character_attributes = b.getShort();
a.max_bounds.left_side_bearing = b.getShort();
a.max_bounds.right_side_bearing = b.getShort();
a.max_bounds.character_width = b.getShort();
a.max_bounds.character_ascent = b.getShort();
a.max_bounds.character_descent = b.getShort();
a.max_bounds.character_attributes = b.getShort();
if ((fmt & PCF_ACCEL_W_INKBOUNDS) != 0) {
a.ink_min_bounds.left_side_bearing = b.getShort();
a.ink_min_bounds.right_side_bearing = b.getShort();
a.ink_min_bounds.character_width = b.getShort();
a.ink_min_bounds.character_ascent = b.getShort();
a.ink_min_bounds.character_descent = b.getShort();
a.ink_min_bounds.character_attributes = b.getShort();
a.ink_max_bounds.left_side_bearing = b.getShort();
a.ink_max_bounds.right_side_bearing = b.getShort();
a.ink_max_bounds.character_width = b.getShort();
a.ink_max_bounds.character_ascent = b.getShort();
a.ink_max_bounds.character_descent = b.getShort();
a.ink_max_bounds.character_attributes = b.getShort();
}
else {
a.ink_min_bounds = a.min_bounds;
a.ink_max_bounds = a.max_bounds;
}
mMinBounds.left_side_bearing = a.min_bounds.left_side_bearing;
mMinBounds.right_side_bearing = a.min_bounds.right_side_bearing;
mMinBounds.character_width = a.min_bounds.character_width;
mMinBounds.ascent = a.min_bounds.character_ascent;
mMinBounds.descent = a.min_bounds.character_descent;
mMinBounds.attributes = a.min_bounds.character_attributes;
mMaxBounds.left_side_bearing = a.max_bounds.left_side_bearing;
mMaxBounds.right_side_bearing = a.max_bounds.right_side_bearing;
mMaxBounds.character_width = a.max_bounds.character_width;
mMaxBounds.ascent = a.max_bounds.character_ascent;
mMaxBounds.descent = a.max_bounds.character_descent;
mMaxBounds.attributes = a.max_bounds.character_attributes;
mDrawDirection = a.draw_direction;
mAllCharsExist = 0 /* false */; //XXX?
mFontAscent = (short)a.font_ascent;
mFontDescent = (short)a.font_descent;
}
short decompress_metric(byte b) {
if (b < 0) {
return (short)(b & 0x7f);
}
return (short)(b - 0x80);
}
private void readMetricsTable(int fmt, ByteBuffer b, ArrayList<X11CharInfo> infos) {
b.order(ByteOrder.LITTLE_ENDIAN);
int format = b.getInt();
b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
if ((fmt & PCF_COMPRESSED_METRICS) != 0) {
short count = b.getShort();
short n;
for (n = 0; n < count; ++n) {
X11CharInfo info = new X11CharInfo();
info.left_side_bearing = decompress_metric(b.get());
info.right_side_bearing = decompress_metric(b.get());
info.character_width = decompress_metric(b.get());
info.ascent = decompress_metric(b.get());
info.descent = decompress_metric(b.get());
info.attributes = 0;
infos.add(n, info);
}
}
else {
int count = b.getInt();
int n;
for (n = 0; n < count; ++n) {
X11CharInfo info = new X11CharInfo();
info.left_side_bearing = b.getShort();
info.right_side_bearing = b.getShort();
info.character_width = b.getShort();
info.ascent = b.getShort();
info.descent = b.getShort();
info.attributes = b.getShort();
infos.add(n, info);
}
}
}
private void showBitmap(char ch) {
Bitmap bmp = mImages.get((int)ch);
Log.d("X", "Show bitmap for <"+ch+">: w="+bmp.getWidth()+", h="+bmp.getHeight()+" ...");
Log.d("X", "-----------");
for (int y = 0; y < bmp.getHeight(); ++y) {
StringBuffer buf = new StringBuffer();
for (int x = 0; x < bmp.getWidth(); ++x) {
if (bmp.getPixel(x, y) == Color.BLACK) {
buf.append(".");
}
else {
buf.append("X");
}
}
Log.d("X", " " + buf);
}
Log.d("X", "-----------");
}
private void readBitmapTable(int fmt, ByteBuffer b) {
b.order(ByteOrder.LITTLE_ENDIAN);
int format = b.getInt();
b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
int glyph_count = b.getInt();
int n;
int[] offsets = new int[glyph_count];
for (n = 0; n < glyph_count; ++n) {
offsets[n] = b.getInt();
}
int[] bitmap_sizes = new int[4];
bitmap_sizes[0] = b.getInt();
bitmap_sizes[1] = b.getInt();
bitmap_sizes[2] = b.getInt();
bitmap_sizes[3] = b.getInt();
int bitmap_data_len = bitmap_sizes[format&3];
ByteBuffer bitmap_data = b.slice();
bitmap_data.limit(bitmap_data_len);
int bitmap_byte_order = (format&4) >> 2; // 1=LSByteFirst, 0=MSByteFirst
int bitmap_bit_order = (format&8) >> 3; // 1=LSBitFirst, 0=MSBitFirst
mImages = new ArrayList<Bitmap>(glyph_count);
int row_pad_bytes = (1<<(format&3));
int elem_bytes = (1<<((format>>4)&3));
int elem_bits = elem_bytes*8;
Log.d("X", "readBitmapTable: name="+mName+", glyph_count="+glyph_count+", bitmap_data_len="+bitmap_data_len);
for (n = 0; n < glyph_count; ++n) {
X11CharInfo info = mGlyphInfos.get(n);
//XXX: should width be rsb-lsb or width?
short w = info.character_width;
short h = (short)(info.ascent + info.descent);
if (w == 0 || h == 0) {
mImages.add(n, null);
continue;
}
Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
int elem_per_row = (w+elem_bits-1)/elem_bits;
int bytes_per_row = ((elem_per_row*elem_bytes+row_pad_bytes-1)/row_pad_bytes)*row_pad_bytes;
short row, col;
int row_off, col_off, idx;
int elem;
int nbits, bit;
for (row = 0; row < h; ++row) {
row_off = row * bytes_per_row;
for (col = 0; col < w; ++col) {
int elem_off = (col/elem_bits)*elem_bytes;
int elem_byte_off;
if (bitmap_byte_order == 1 /*LSByteFirst*/) {
elem_byte_off = (col%elem_bits)/8;
}
else {
elem_byte_off = elem_bytes-1 - (col%elem_bits)/8;
}
int elem_bit_off;
if (bitmap_bit_order == 1 /*LSBitFirst*/) {
elem_bit_off = 8-1 - (col%8);
}
else {
elem_bit_off = (col%8);
}
int valoff = offsets[n] + row_off + elem_off + elem_byte_off;
byte val = bitmap_data.get(offsets[n] + row_off + elem_off + elem_byte_off);
int pixel = (val >> elem_bit_off) & 1;
int color = (pixel != 0 ? Color.WHITE : Color.BLACK);
bmp.setPixel(col, row, color);
}
}
mImages.add(n, bmp);
}
}
private void readEncodingTable(int fmt, ByteBuffer b) {
b.order(ByteOrder.LITTLE_ENDIAN);
int format = b.getInt();
b.order((format & PCF_BYTE_MASK) != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
mMinCharOrByte2 = b.getShort();
mMaxCharOrByte2 = b.getShort();
mMinByte1 = (byte)b.getShort();
mMaxByte1 = (byte)b.getShort();
mDefaultChar = b.getShort();
//XXX: this fails on cu-pua12.pcf.gz
int num_indices = (mMaxCharOrByte2 -
mMinCharOrByte2 + 1) *
(mMaxByte1 -
mMinByte1 + 1);
short[] glyph_indices = new short[num_indices];
int n;
for (n = 0; n < num_indices; ++n) {
glyph_indices[n] = b.getShort();
}
}
private void readScalableWidthsTable(int fmt, ByteBuffer b) {
//XXX: Skip this data
}
private void readGlyphNamesTable(int fmt, ByteBuffer b) {
//XXX: Skip this data
}
void loadMetadata(X11Server server) throws Exception {
if (mMetadataLoaded) {
return;
}
InputStream is = null;
if (mPathtype == PATH_TYPE_ASSET) {
AssetManager am = server.mContext.getAssets();
is = am.open(mPathname);
}
else {
is = new FileInputStream(mPathname);
}
//XXX: should check for gzip signature, but what about rewind?
if (mPathname.endsWith(".gz")) {
is = new GZIPInputStream(is);
}
long pos = 0; //XXX: seek() sure would be nice
ByteBuffer b;
b = ByteBuffer.allocate(4);
is.read(b.array(), 0, 4); pos += 4;
byte[] sig = b.array();
if (sig[0] != 1 || sig[1] != 'f' || sig[2] != 'c' || sig[3] != 'p') {
throw new Exception("Bad PCF header");
}
b = ByteBuffer.allocate(4);
b.order(ByteOrder.LITTLE_ENDIAN);
is.read(b.array(), 0, 4); pos += 4;
int table_count = b.getInt();
int n;
pcf_toc_entry[] toc = new pcf_toc_entry[table_count];
for (n = 0; n < table_count; ++n) {
b = ByteBuffer.allocate(16);
b.order(ByteOrder.LITTLE_ENDIAN);
is.read(b.array(), 0, 16); pos += 16;
pcf_toc_entry ent = new pcf_toc_entry();
ent.type = b.getInt();
ent.format = b.getInt();
ent.size = b.getInt();
ent.offset = b.getInt();
toc[n] = ent;
}
Arrays.sort(toc, new pcf_toc_order_by_offset());
for (pcf_toc_entry e : toc) {
if (pos != e.offset) {
is.skip(e.offset - pos);
pos = e.offset;
}
b = ByteBuffer.allocate(e.size);
int off = 0;
while (off < e.size) {
int nread = is.read(b.array(), off, e.size-off);
if (nread <= 0) {
Log.w("X", "FontDataPCF.loadMetadata: short read in " + mPathname);
break;
}
off += nread;
pos += nread;
}
switch (e.type) {
case PCF_PROPERTIES: readPropertyTable(server, e.format, b); break;
case PCF_ACCELERATORS: readAcceleratorTable(e.format, b); break;
case PCF_METRICS: readMetricsTable(e.format, b, mGlyphInfos); break;
case PCF_BITMAPS: /* Ignore */ break;
case PCF_INK_METRICS: readMetricsTable(e.format, b, mCharInfos); break;
case PCF_BDF_ENCODINGS: readEncodingTable(e.format, b); break;
case PCF_SWIDTHS: readScalableWidthsTable(e.format, b); break;
case PCF_GLYPH_NAMES: readGlyphNamesTable(e.format, b); break;
case PCF_BDF_ACCELERATORS: readAcceleratorTable(e.format, b); break;
default: throw new Exception("Bad PCF toc");
}
}
mMetadataLoaded = true;
}
void loadGlyphs(X11Server server) throws Exception {
if (mGlyphsLoaded) {
return;
}
Log.d("X", "FontDataPCF.loadGlyphs");
InputStream is = null;
if (mPathtype == PATH_TYPE_ASSET) {
AssetManager am = server.mContext.getAssets();
is = am.open(mPathname);
}
else {
is = new FileInputStream(mPathname);
}
//XXX: should check for gzip signature, but what about rewind?
if (mPathname.endsWith(".gz")) {
is = new GZIPInputStream(is);
}
long pos = 0; //XXX: seek() sure would be nice
ByteBuffer b;
b = ByteBuffer.allocate(4);
is.read(b.array(), 0, 4); pos += 4;
byte[] sig = b.array();
if (sig[0] != 1 || sig[1] != 'f' || sig[2] != 'c' || sig[3] != 'p') {
throw new Exception("Bad PCF header");
}
b = ByteBuffer.allocate(4);
b.order(ByteOrder.LITTLE_ENDIAN);
is.read(b.array(), 0, 4); pos += 4;
int table_count = b.getInt();
int n;
pcf_toc_entry[] toc = new pcf_toc_entry[table_count];
for (n = 0; n < table_count; ++n) {
b = ByteBuffer.allocate(16);
b.order(ByteOrder.LITTLE_ENDIAN);
is.read(b.array(), 0, 16); pos += 16;
pcf_toc_entry ent = new pcf_toc_entry();
ent.type = b.getInt();
ent.format = b.getInt();
ent.size = b.getInt();
ent.offset = b.getInt();
toc[n] = ent;
}
Arrays.sort(toc, new pcf_toc_order_by_offset());
for (pcf_toc_entry e : toc) {
if (pos != e.offset) {
is.skip(e.offset - pos);
pos = e.offset;
}
b = ByteBuffer.allocate(e.size);
int off = 0;
while (off < e.size) {
int nread = is.read(b.array(), off, e.size-off);
if (nread <= 0) {
Log.w("X", "FontDataPCF.loadGlyphs: short read in " + mPathname);
break;
}
off += nread;
pos += nread;
}
switch (e.type) {
case PCF_BITMAPS: readBitmapTable(e.format, b); break;
default: /* Ignore */ break;
}
}
mGlyphsLoaded = true;
}
}