Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2447

Coding4Fun - 點陣中文字型顯示

$
0
0

因緣巧合,最近剛好需要處理中文點陣字型。

在DOS+倚天中文的古早年代,曾經用BASICA寫過解析倚天中文字型檔的程式,沒想到二十多年後居然還有機會重新回味,只是這回手上的兵器已由當年的BASICA小開山刀,換成C#加農砲,語言特性已不可同日而言、自己的程式技巧也遠比當年成熟,對照起來格外有趣。

時代演進大大地改變了寫程式的方法,當年要自己瞎摸亂湊好一陣子才能拼湊出檔案規格,現在稍稍爬文就能找到網友的熱心分享:

有了字型檔規格,加上C#的物件導向,我試寫了以不同字型檔為基礎的點陣中文字型元件:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
 
namespace TestChineseFont
{
//允許以不同廠商字型檔作為來源的點陣字型物件
publicabstractclass DotArrayFontProvider
    {
//宣告不同的尺寸規格
publicenum FontSize
        {
            Size15 = 15, Size24 = 24, Size32 = 32, Size48 = 48
        }
//不同字型檔轉為byte[]的實作方式不同
publicabstractbyte[] GetFontData(FontSize sz, bool halfWidth);
//由寬度計算所需要的位元數(半形時用量減半)
protectedstaticint GetWidthBytes(int w, bool halfWidth)
        {
if (halfWidth) w = (w / 2);
return (w / 8) + (w % 8 > 0 ? 1 : 0);
        }
//取得特定字元的點陣資料(byte[])
publicbyte[] GetCharData(char ch, FontSize sz = FontSize.Size24)
        {
byte[] b = Encoding.GetEncoding(950).GetBytes(newchar[] { ch });
int iSz = (int)sz;
bool halfWidth = false;
int offset = -1;
//ASCII 0-255採半形
if (b.Length == 1) halfWidth = true;
int arraySize = GetWidthBytes(iSz, halfWidth) * iSz;
byte[] result = newbyte[arraySize];
//半形時依ASCII碼決定資料起始位址
if (halfWidth)
            {
                offset = arraySize * b[0];
            }
else
            {
//全形文字依倚天字型檔的存放規則
//http://www.cnblogs.com/armstrong-cn/archive/2011/09/01/2161567.html
byte hi = b[0], lo = b[1];
int serCode = (hi - 161) * 157 + (lo >= 161 ? lo - 161 + 1 + 63 : lo - 64 + 1);
if (serCode >= 472 && serCode < 5872)
                    offset = (serCode - 472) * arraySize;
elseif (serCode >= 6281 && serCode <= 13973)
                    offset = (serCode - 6281) * arraySize + 5401 * arraySize;
            }
if (offset < 0) returnnull;
            Buffer.BlockCopy(GetFontData(sz, halfWidth), offset, result, 0, arraySize);
return result;
 
        }
//將點陣內容由byte[]轉為長*寬的二維byte[,],1表示該點要顯示, 0表示該點留白
publicstaticbyte[,] GetDotArray(byte[] data, int w, int h)
        {
//偵測是否為半形字
bool halfWidth = data.Length == GetWidthBytes(w, true) * h;
//如為半形字,寬度減半
if (halfWidth) w = w / 2; 
if (w < 8) w = 8;
//宣告二維陣列以存放點陣資料
byte[,] dotArray = newbyte[h, w];
int widthBytes = data.Length / h;
for (int y = 0; y < h; y++)
            {
int offset = widthBytes * y;
byte b = data[offset];
for (int x = 0; x < w; x++)
                {
if (x % 8 == 0)
                    {
                        b = data[offset];
                        offset++;
                    }
                    dotArray[y, x] = (byte)(((b << (x % 8)) & 0x80) != 0 ? 1 : 0);
                }
            }
return dotArray;            
        }
    }
}

DotArrayFontProvider抽象類別提供了GetCharData(char ch, FontSize sz)以取出指定字型尺寸(例如: 15x15, 24x24)的特定字元點陣資料(一點一個Bit,一個Byte代表8點,以byte[]方式傳回),而另外有靜態方法GetDotArray()可將前述byte[]轉成二維陣列,一點一個byte,1代表顯示,0代表留白,以配合X、Y軸座標運算轉成圖檔。DotArrayFontProvider是個抽象類別,子類別必須實作一個byte[] GetFontData(FontSize sz, bool halfWidth)傳回特定尺寸、全形/半形的全部點陣資料。(ASCII 0-255字元使用半形資料) 這部分與各廠商字型檔規格高度相依,就留給各子類別自行實現。

針對國喬字型檔所寫的KCFontProvider,邏輯很簡單,就只是讀入KCCHIN16.F00、KCTEXT16.F00、KCCHIN24.F00、KCTEXT24.F00等字型檔,從中取出15x15及24x24的全形及半形文字點陣資料,存入靜態Dictionary後,供GetFontData()時取出回傳。其中有些位址計算,純粹是要由國喬字型檔取出資料,只保留點資料的部分,看似複雜,但沒什麼營養。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
 
namespace TestChinFont
{
publicclass KCFontProvider : DotArrayFontProvider
    {
static Dictionary<string, byte[]> dataPool = 
new Dictionary<string, byte[]>();
//http://bbs.unix-like.org:8080/boards/FB_chinese/M.1023317709.A
static KCFontProvider()
        {
byte[] buff = File.ReadAllBytes("KCCHIN16.F00");
 
int offset = 256 + 765 * 2 * 16;
constint charCount = 13195;
byte[] data = newbyte[charCount * 2 * 15];
for (int i = 0; i < charCount; i++)
            {
                Buffer.BlockCopy(buff, offset + i * 2 * 14, data, i * 2 * 15, 2 * 14); 
            }
            dataPool.Add("C" + FontSize.Size15, data);
            buff = File.ReadAllBytes("KCTEXT16.F00");
            data = newbyte[256 * 15];
            offset = 256;
for (int i = 0; i < 256; i++)
                Buffer.BlockCopy(buff, offset + i * 16, data, i * 15, 15);
            dataPool.Add("A" + FontSize.Size15, data);
 
            buff = File.ReadAllBytes("KCCHIN24.F00");
            data = newbyte[charCount * 72];
            offset = 256 + 765 * 72;
for (int i = 0; i < charCount; i++)
            {
                Buffer.BlockCopy(buff, offset + i * 72, data, i * 72, 72);
            }
 
            dataPool.Add("C" + FontSize.Size24, data);
            buff = File.ReadAllBytes("KCTEXT24.F00");
            data = newbyte[256 * 48];
            offset = 256;
for (int i = 0; i < 256; i++)
                Buffer.BlockCopy(buff, offset + i * 48, data, i * 48, 48);
            dataPool.Add("A" + FontSize.Size24, data);
        }
 
publicoverridebyte[] GetFontData(FontSize sz, bool halfWidth)
        {
string key = (halfWidth ? "A" : "C") + sz;
if (!dataPool.ContainsKey(key))
thrownew NotImplementedException();
return dataPool[key];
        }
    }
}

這樣就差不多就能精準取出不同字元的點陣資料。雖然當年用BASICA寫的版本已不復記憶,但我很肯定,這段C#讀檔程式碼的長度絕對不到當年的十分之一!

最後,配合一小段程式,將點陣資料轉成Bitmap,來驗證一下執行結果:

private Bitmap DrawDotArrayText(string text, DotArrayFontProvider.FontSize fs)
        {
int sz = (int)fs;
            Bitmap bmp = new Bitmap(text.Length * sz * 4, sz * 4);
            SolidBrush bb = new SolidBrush(Color.Black);
            SolidBrush yb = new SolidBrush(Color.Orange);
            Graphics g = Graphics.FromImage(bmp);
            g.FillRectangle(bb, 0, 0, bmp.Width, bmp.Height);
int offset = 0;
            var kcfp = new KCFontProvider();
foreach (char ch in text.ToCharArray())
            {
byte[,] d = DotArrayFontProvider.GetDotArray(
                                                 kcfp.GetCharData(ch, fs), sz, sz);
for (int y = 0; y < sz; y++)
                {
for (int x = 0; x < sz; x++)
                    {
if (d[y, x] == 1)
                        {
                            g.FillRectangle(yb, offset + x * 4, y * 4, 3, 3);
                        }
                    }
                }
                offset += sz * 4;
            }
return bmp;
        }

LED字幕機式的中文顯示效果就完成囉~


Viewing all articles
Browse latest Browse all 2447

Trending Articles