由于本人在做 MOTO 手機(jī)上的一款閱讀器 Anyview 時,并不滿意手機(jī)自帶的字庫,因此,使用了外掛字庫,在編碼過程中,總結(jié)了一些經(jīng)驗,特記錄下來與大家分享。雖然閱讀器是使用 J2ME 開發(fā),但其基本原理實現(xiàn)并未使用特別的 J2ME 的 API 。
前言:為什么要使用點(diǎn)陣字庫
在某些場合,系統(tǒng)自帶的字庫并不能令人滿意,或者,在你需要特別的字體時,你希望能附帶上該字庫。
那為什么又需要點(diǎn)陣字庫呢?因為在使用較小的字體的時候,點(diǎn)陣字庫能更清晰,同時,由于點(diǎn)陣字庫并不包含路徑等信息,因此,字庫文件的大小也很小,便于攜帶。
如何生成點(diǎn)陣字庫
在此,我并沒有打算在此文中說明如何生成點(diǎn)陣字庫,其實有很多現(xiàn)成的點(diǎn)陣字庫可以選擇,當(dāng)然,目前都是使用 TrueType 字庫,但很多比較老的程序都會帶有點(diǎn)陣字庫,你可以直接拿來使用,或者,網(wǎng)上也有很多生成點(diǎn)陣字庫的工具,當(dāng)然,你甚至可以自己寫一個小程序來生成點(diǎn)陣字庫。
我們這里將直接使用 UCODS 自帶的字庫來舉例子,你也可以使用《特大點(diǎn)陣字庫制作軟件》來生成一個點(diǎn)陣字庫。
點(diǎn)陣字庫的組成
點(diǎn)陣字庫的組成基本上都類似,除了有些點(diǎn)陣字庫可能會在文件頭上附加一些版權(quán)信息外,基本上沒有其它變化,另外,他們在點(diǎn)陣信息的壓縮上都會采用相同的方式。下面我們舉個例子看看:
上圖是一個 12*12 大小的“我”字,從柵格中可以看到,每個點(diǎn)的位置上只會有“有”和“無”兩種狀態(tài),由左至右。因此,上面的點(diǎn)陣信息可以如下描述:
第 1 行
0
0
0
0
1
0
1
0
1
0
0
0
第 2 行
1
1
1
1
0
0
1
0
0
1
0
0
第 3 行
0
0
0
1
0
0
1
0
0
0
0
0
第 4 行
1
1
1
1
1
1
1
1
1
1
1
0
第 5 行
0
0
0
1
0
0
1
0
0
0
0
0
第 6 行
……
由于每個點(diǎn)的信息只有 0 和 1 兩種狀態(tài),因此,為了節(jié)省存儲空間,我們使用位的存儲點(diǎn)的信息。我們知道,一個字節(jié)有 8 位,因此,上面的一個點(diǎn)陣字,可以用若干個字節(jié)來進(jìn)行描述。
當(dāng)然,不同的點(diǎn)陣大小,每一行的點(diǎn)不一樣多,并不能保證他們總是 8 的倍數(shù),因此,最后不足 8 個字節(jié)的仍然占用一個字節(jié),且從最高位向最低位排列,不
足的地方補(bǔ) 0 。
1 : 00001010 1000 →補(bǔ)位→ 00001010 10000000 → Ox0a 0x80
2 : 11110010 0100 →補(bǔ)位→ 11110010 01000000 → 0xf2 0x40
3 : 00010010 0000 →補(bǔ)位→ 00010010 00000000 → 0x12 0x00
4 : 11111111 1110 →補(bǔ)位→ 11111111 11100000 → 0xff 0xe0
5 : 00010010 0000 →補(bǔ)位→ 00010010 00000000 → 0x12 0x00
……
由上可知,對于一個 12*12 的漢字來說,一個字需要占用 2 × 12 = 24 個字節(jié)。我們已經(jīng)知道了每一個字的存儲方式,那么,在字庫中,這些字是按照什么順序來存儲的呢?
我們知道,不同的語言使用不同的字符集,對于漢字來說,有很多種字符集如 GB2312 、 BIG5 、 GBK 等,當(dāng)然,還有更大的字符集如 UTF-8 、 UNICODE 等,在我們的程序中,我們必須確定一種字符集,為了簡單起見,我們以 GB2312 舉例子。(關(guān)于各字符集的說明,自己可以在網(wǎng)上搜索一下)
GB2312 規(guī)定“對任意一個圖形字符都采用兩個字節(jié)表示,每個字節(jié)均采用七位編碼表示”,習(xí)慣上稱第一個字節(jié)為“高字節(jié)”,第二個字節(jié)為“低字節(jié)”。 GB2312-80 包含了大部分常用的一、二級漢字,和 9 區(qū)的符號。該字符集是幾乎所有的中文系統(tǒng)和國際化的軟件都支持的中文字符集,這也是最基本的中文字符集。其編碼范圍是高位 0xa1 - 0xfe ,低位也是 0xa1-0xfe ;漢字從 0xb0a1 開始,結(jié)束于 0xf7fe 。
GB2312 將代碼表分為 94 個區(qū),對應(yīng)第一字節(jié)( 0xa1-0xfe );每個區(qū) 94 個位( 0xa1-0xfe ),對應(yīng)第二字節(jié),兩個字節(jié)的值分別為區(qū)號值和位號值加 32 ( 2OH ),因此也稱為區(qū)位碼。 01-09 區(qū)為符號、數(shù)字區(qū), 16-87 區(qū)為漢字區(qū)( 0xb0-0xf7 ), 10-15 區(qū)、 88-94 區(qū)是有待進(jìn)一步標(biāo)準(zhǔn)化的空白區(qū)。 GB2312 將收錄的漢字分成兩級:第一級是常用漢字計 3755 個,置于 16-55 區(qū),按漢語拼音字母 / 筆形順序排列;第二級漢字是次常用漢字計 3008 個,置于 56-87 區(qū),按部首 / 筆畫順序排列。故而 GB2312 最多
仍然是 12*12 點(diǎn)陣的“我”字為例,“我”的編碼為 0xCED2 ,因此,在字庫文件中,“我”字的偏移為: ((0xCE-0xA1)*94+(0xD2-0xA1))*24 字節(jié) / 字 =102696 ,其后的連續(xù) 24 字節(jié)即為“我”字的點(diǎn)陣信息。
public class CharCode {
public CharCode() {
String str = "我";
try {
byte[] b = str.getBytes("GB2312");
for (int i = 0; i < b.length; i++) {
System.out.println(Integer.toHexString(b[i]));
}
} catch (Exception ex) {
}
}
public static void main(String[] args) {
CharCode charcode = new CharCode();
}
}
將點(diǎn)陣信息還原為漢字
由于我們已經(jīng)能夠順利的找到漢字的點(diǎn)陣數(shù)據(jù),因此,我們只需要簡單的將點(diǎn)陣信息還原即可。還原的方法很簡單,只需要按照上述格式逆向操作就行了:
“我”字的信息第一個字節(jié)為 Ox0a ,從高位向低位依次取當(dāng)前位上的數(shù)據(jù),如果為 1 ,表示該點(diǎn)需要著色, 0 則表示該點(diǎn)為空,為了取位的方便,我們可以直接定義一個數(shù)組來與該字節(jié)異或:
public final static int[] verify = {128, 64, 32, 16, 8, 4, 2, 1};
然后,作如下判斷:
for (int w = 0; w < 8; w++) {
if ((b & verify[w]) == verify[w]) { //需要著色
//著色
}
}
下面,我們以一段小的程序來完成該工作:
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
public class Test extends JFrame {
byte[] dotfont;
BufferedImage imgCH;
int[] verify = {128, 64, 32, 16, 8, 4, 2, 1};
String test = "點(diǎn)陣漢字的測試";
int imgWidth = 300;
int imgHeight = 200;
public Test() {
super("DotFont");
File file = new File("gb.dat");
try {
FileInputStream fis = new FileInputStream(file);
dotfont = new byte[fis.available()];
fis.read(dotfont);
fis.close();
} catch (FileNotFoundException ex) {
} catch (IOException ex) {
}
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300, 240);
this.show();
}
void createCH(byte[] ch, int off) {
int q1 = ch[off] & 0xff;
int q2 = ch[off + 1] & 0xff;
int offset = (q1 - 0xa1) * 94 * 24;
q2 -= 0xa1;
offset += q2 * 24;
imgCH = new BufferedImage(12, 12, BufferedImage.TYPE_INT_RGB);
for (int h = 0; h < 12; h++) {
byte b = dotfont[offset++];
for (int w = 0; w < 8; w++) {
if ((b & verify[w]) == verify[w]) {
imgCH.setRGB(w, h, 0xffffffff);
} else {
imgCH.setRGB(w, h, 0);
}
}
b = dotfont[offset++];
for (int w = 0; w < 4; w++) {
if ((b & verify[w]) == verify[w]) {
imgCH.setRGB(w + 8, h, 0xffffffff);
} else {
imgCH.setRGB(w + 8, h, 0);
}
}
}
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
byte[] an = str2bytes(test);
int offset = 0;
int x = 10, y = 34;
while (y < imgHeight && offset < an.length) {
int b = an[offset] & 0xff;
if (b > 0x7f) {
createCH(an, offset);
g.drawImage(imgCH, x, y, null);
x += 12;
offset += 2;
} else { //英文暫時不考慮
x += 6;
offset++;
}
if (x > imgWidth) {
x = 10;
y += 14;
}
}
}
byte[] str2bytes(String s) {
if (null == s || "".equals(s)) {
return null;
}
byte[] abytes = null;
try {
abytes = s.getBytes("gb2312");
} catch (UnsupportedEncodingException ex) {
}
return abytes;
}
public static void main(String[] args) {
new Test();
}
}
在上述代碼中, gb.dat 是直接從 UCDOS 中自帶的 12 點(diǎn)陣字庫,當(dāng)然,你也可以使用其它工具生成,為了提高效率,我們直接將整個字庫加載到內(nèi)存中。 str2bytes(String s)是用來將字符串轉(zhuǎn)換成為 GB2312 編碼格式的二進(jìn)制字節(jié)數(shù)組,在 createCH(byte[] ch, int off) 中,我們創(chuàng)建了一個空白的圖片,然后根據(jù)字庫的點(diǎn)陣信息,來決定是否使用 setRGB()方法為該點(diǎn)著色,最后,將創(chuàng)建的圖片畫在程序的畫布上。