Quantcast
Viewing all articles
Browse latest Browse all 2480

Coding4Fun - 點陣中文字型顯示

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

在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字幕機式的中文顯示效果就完成囉~

Image may be NSFW.
Clik here to view.

Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 2480

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>