汉字库,字模的了解
漢字庫,字模的了解
標簽:?技術類? 2007-09-04 21:50閱讀(726)評論(0) 由于Turbo C應用于DOS操作系統下,在使用Turbo C進行程序設計時,一般情況下只好使用英文進行人機交互。要是想直接用中文界面,就需要另想他法了。如果使用中文DOS系統(如UCDOS),則可以解決在字符界面下的漢字顯示問題。也就是說,可以用printf或其他字符串函數來輸出漢字。
但是,這樣仍然有一些不方便。必須先啟動中文DOS系統,再執行Turbo C或編譯好的程序。并且在中文版DOS下運行Tubor C時,還可能出現一些問題。而對于圖形界面來說,這種辦法也行不通了。
那么在圖形界面下顯示漢字的問題就迫切需要解決了。既然是圖形界面,只要把漢字當成一幅畫,畫在顯示屏上不就可以了。關鍵在于如何取得漢字的圖形,也就是漢字的點陣字模呢。其實那些中文版的DOS顯示漢字的方式也就是在圖形界面下畫出漢字的,它們已經提供了現成的點陣字庫文件。例如常用的16×16點陣HZK16文件,12×12點陣HZK12文件等等,這些文件包括了GB 2312字符集中的所有漢字。現在只要弄清漢字點陣在字庫文件中的格式,就可以按照自己的意愿去顯示漢字了。
下面以HZK16文件為例,分析取得漢字字模的方法。
HZK16文件是按照GB 2312-80標準,也就是通常所說的國標碼或區位碼的標準排列的。國標碼分為 94 個區(Section),每個區 94 個位(Position),所以也稱為區位碼。其中01~09 區為符號、數字區,16~87 區為漢字區。而 10~15 區、88~94 區是空白區域。
如何取得漢字的區位碼呢?在計算機處理漢字和ASCII字符時,使每個ASCII字符占用1個字節,而一個漢字占用兩個字節,其值稱為漢字的內碼。其中第一個字節的值為區號加上32(20H),第二個字節的值為位號加上32(20H)。為了與ASCII字符區別開,表示漢字的兩個字節的最高位都是1,也就是兩個字節的值都又加上了128(80H)。這樣,通過漢字的內碼,就可以計算出漢字的區位碼。
具體算式如下:
qh=c1-32-128=c1-160??? wh=c2-32-128=c2-160
或
qh=c1-0xa0??? wh=c2-0xa0
qh,wh為漢字的區號和位號,c1,c2為漢字的第一字節和第二字節。
根據區號和位號可以得到漢字字模在文件中的位置:
location=(94*(qh-1)+(wh-1))*一個點陣字模的字節數。
那么一個點陣字模究竟占用多少字節數呢?我們來分析一下漢字字模的具體排列方式。
例如下圖中顯示的“漢”字,使用16×16點陣。字模中每一點使用一個二進制位(Bit)表示,如果是1,則說明此處有點,若是0,則說明沒有。這樣,一個16×16點陣的漢字總共需要16*16/8=32個字節表示。字模的表示順序為:先從左到右,再從上到下,也就是先畫左上方的8個點,再是右上方的8個點,然后是第二行左邊8個點,右邊8個點,依此類推,畫滿16×16個點。??
?
對于其它點陣字庫文件,則也是使用類似的方法進行顯示。例如HZK12,但是HZK12文件的格式有些特別,如果你將它的字模當作12*12位計算的話,根本無法正常顯示漢字。因為字庫設計者為了使用的方便,字模每行的位數均補齊為8的整數倍,于是實際該字庫的位長度是16*12,每個字模大小為24字節,雖然每行都多出了4位,但這4位都是0(不顯示),并不影響顯示效果。 還有UCDOS下的HZK24S(宋體)、HZK24K(楷體)或HZK24H(黑體)這些打印字庫文件,每個字模占用24*24/8=72字節,不過這類大字模漢字庫為了打印的方便,將字模都放倒了,所以在顯示時要注意把橫縱方向顛倒過來就可以了。
這樣我們就完全清楚了如何得到漢字的點陣字模,這樣就可以在程序中隨意的顯示漢字了。
如果在程序中使用的漢字數目不多,也可以不必總是在程序里帶上幾百K的字庫文件,也許你的程序才只有幾十K。這樣可以事先將所需要顯示的漢字字模提取出來,放在另一個文件里,按照自己的順序讀取文件就可以了。
下面的程序說明了具體顯示漢字的方法,以16×16漢字為例,使用HZK16文件。
#i nclude
#i nclude
/* x,y為顯示坐標,s為顯示字符串,colour為顏色 */
void hanzi16(int x,int y,char *s,int colour)
{
?FILE *fp;
?char buffer[32];?????????????????? /* 32字節的字模緩沖區 */
?register i,j,k;
?unsigned char qh,wh;
?unsigned long location;
?if((fp=fopen("hzk16","rb"))==NULL)
? {
?? printf("Cant open hzk16!");
?? getch();
?? exit(0);
? }
?while(*s)
? {
?? qh=*s-0xa0;
?? wh=*(s+1)-0xa0;
?? location=(94*(qh-1)+(wh-1))*32L;??? /* 計算漢字字模在文件中的位置 */
?? fseek(fp,location,SEEK_SET);
?? fread(buffer,32,1,fp);
?? for(i=0;i<16;i++)
??? for(j=0;j<2;j++)
???? for(k=0;k<8;k++)
????? if(((buffer[i*2+j]>>(7-k))&0x1)!=NULL)
?????? putpixel(x+8*j+k,y+i,colour);
?? s+=2;
?? x+=16;????????????????????????????? /* 漢字間距 */
? }
?fclose(fp);
}
main()
{
?int gd=DETECT,gm;
?initgraph(&gd,&gm,"");
?hanzi16(246,200,"瘋狂甲蟲樂園!",BROWN);
?getch();
?closegraph();
}
?
DOS下的點陣漢字顯示
???????? 這里以 HZK16 文件為例,分析取得漢字字模的方法。
???????? HZK16 文件是按照國標碼的標準排列的。國標碼分為 94 個區,每個區 94個位(Position),所以也稱為區位碼。
???????? 漢字占用兩個字節,其中第一個字節的值為區號加上 32(20H),第二個字節的值為位號加上32(20H)。為了與ASCII字符區別開,表示漢字的兩個字節的最高位都是1,也就是兩個字節的值都又加上 了 128(80H)。這樣,通過漢字的內碼,就可以計算出漢字的區位碼。具體算式如下:
qh=c1-32-128=c1-160,wh=c2-32-128=c2-160qh,wh為漢字的區號和位號,c1,c2為漢字的第一字節和第二字節。根據區號和位號可以得到漢字字模在文件中的位置:
location=(94*(qh-1)+(wh-1))*一個點陣字模的字節數。???????? 一個點陣字模究竟占用多少字節數是看其點陣數來確定的。如使用16×16點陣,字模中每一點使用一個二進制位(Bit)表示,如果是1,則說明此處有點,若是0,則說明沒有。這樣,一個16×16點陣的漢字總共需要16*16/8=32個字節表示。
???????? 下面是一些例子(特別說明:如果發現例子不能運行,請與站長聯系。)
#include <io.h>
#include <stdio.h>
#include <dos.h>
#include <fcntl.h>
#include <graphics.h>
#include <conio.h>
#define Byte unsigned char
#define Word unsigned int
int flag16,flag24;
void dishz();
main()
{
int Driver=DETECT,Mode;
registerbgidriver(EGAVGA_driver);
initgraph(&Driver,&Mode,"");
directvideo=0;
setbkcolor(1);
cleardevice();
dishz();
getch();
closegraph();
}
/*========================================================================*/
/*------------------------------16×16點陣漢字----------------------------*/
/*========================================================================*/
int out16hz(int x,int y,int z,int color,char *p)/* x,y起點坐標,z字間距 */
{
Word num,QM,WM,flag=0; /* QM,WM 區號和位號 */
int record,i,j,k;
long pointer;
char Bit[32]; /* 16點陣的漢字占32個字節 */
while((num=*p++)!=0)
{
?? if(num>0xA1)
??? if(flag==0) /* 注意一個漢字是兩個字節的,這里是第一個字節 */
??? {
???? QM=(num-0xA1)&0x07F; /* 注意這里是減161了 */
???? flag=1;
??? }
??? else?? /* 閱第二個字節 */
??? {
???? WM=(num-0xA1)&0x07F; /* 與上 0x07F 再檢測一次 */
???? flag=0;
???? record=QM*94+WM;
???? pointer=record*32L; /* 定位出漢字字模在文件中的位置 */
???? lseek(flag16,pointer,SEEK_SET);
???? read(flag16,Bit,32);
???? for(i=0;i<16;i++) /* 寫點 */
????? for(j=0;j<2;j++)
?????? for(k=0;k<8;k++)
??????? if(((Bit[i*2+j]>>(7-k))&0x1)!=NULL)
???????? putpixel(x+j*8+k,y+i,color);
???? x=x+z+16;
??? }
}
}
/*========================================================================*/
/*------------------------------24×24點陣漢字----------------------------*/
/*========================================================================*/
int out24hz(int x,int y,int z,int color,int m,int n,char *p)
{
Word num,QM,WM,flag=0;
int i,j,k,width,height,record;
long pointer;
char Bit[72];
while((num=*p++)!=0)
{
?? if(num>0xA1)
??? if(flag==0)
??? {
???? QM=(num-15-0xA1)&0x07F;
???? flag=1;
??? }
??? else
??? {
???? WM=(num-0xA1)&0x07F;
???? flag=0;
???? record=QM*94+WM;
???? pointer=record*72L;
???? lseek(flag24,pointer,SEEK_SET);
???? read(flag24,Bit,72);
???? for(i=0;i<24*m;i=i+m)
????? for(width=0;width<m;width++)
?????? for(j=0;j<=2;j++)
??????? for(k=0;k<8;k++)
???????? if(((Bit[i/m*3+j]>>(7-k))&0x1)!=NULL)
????????? for(height=0;height<n;height++)
?????????? putpixel(x+i+width,y+j*8*n+k*n+height,color);
???? x=x+24*m+z;
??? }
}
}
/*========================================================================*/
/*---------------------------------輸出演示-------------------------------*/
/*========================================================================*/
void dishz()
{
Byte *str1[]={"16點陣漢字輸出演示"};
Byte *str2[]={"24點陣漢字輸出演示"};
int x=200,y=50,size=10;
flag16=open("c:\\ucdos\\HZK16F",O_RDWR|O_BINARY);
flag24=open("c:\\ucdos\\HZK24F",O_RDWR|O_BINARY);
if(flag16==-1)
{
?? printf("Cannot Open HZK16\n");
?? exit(1);
}
if(flag24==-1)
{
?? printf("Cannot Open HZK24\n");
?? exit(1);
}
out16hz(x,y,size,15,*str1);
y+=30;
out24hz(x,y,size,10,1,1,*str2);
x=100;y+=50;
x=200;y+=50;
out24hz(x,y,size,12,1,2,*str2);
x=100;y+=80;
out24hz(x,y,size,9,2,2,*str2);
close(flag16);
close(flag24);
}
?
?
DOS下的點陣漢字
你是否碰到過用啟動盤啟動系統后用DIR命令得到一串串莫名其妙的字符?有經驗的朋友會告訴你:那是漢字。漢字?你不禁會問:怎么一個我一個也不認識。但那確確實實是漢字,如果你啟動UCDOS或其他的漢字系統后,就會看到那是一個個熟悉的漢字。同樣是漢字,為什么前后會看到不同的結果?呵呵,其實在電腦硬件中,根本沒有漢字這個概念,也沒有英文的概念,這鐵玩意認識的概念只有——內碼。
漢字的內碼
點頭表示什么?是“對”、“YES”,偏偏有的地方表示的意義卻恰恰相反。一個動作,有不同的詮釋;一個問題,有不同的答案;而一個符號,卻有不同的意義,關鍵在于:你是如何地理解。在電腦中亦如此,所有的數據都是以0和1保存的,按不同的數據操作,可以得到不同的結果。對于顯示英文操作,由于英文字母種類很少,只需要8位(一字節)即可。而對于中文,常用卻有5000以上,于是我們的DOS前輩想了一個辦法,就是將ASCII表的高128個很少用到的數值以兩個為一組來表示漢字,即漢字的內碼。而剩下的低128位則留給英文字符使用,即英文的內碼。不信,你可以用記事本寫一C文件:
main()
{
??? unsigned char *s,*e="ABcd",*c="你好";
??? clrscr();
??? printf("English char =");
??? s=e;
??? while(*s!=0) /*C的字符串以0為結束符*/
??? {
??????? printf("%3d,",*s);
??????? s++;
??? }
??? printf("\nChinease char=");
??? s=c;
??? while(*s!=0)
??? {
??????? printf("%3d,",*s);
??????? s++;
??? }
??? getch();
}
再用TC輸入*.txt打開運行,看見了沒有,那些數值即英文和漢字的各字節內碼。
漢字字模
得到了漢字的內碼后,還僅是一組數字,那又如何在屏幕上去顯示呢?這就涉及到文字的字模,字模雖然也是一組數字,但它的意義卻與數字的意義有了根本的變化,它是用數字的各位信息來記載英文或漢字的形狀,如英文的'A'在字模中是這樣記載的:
而中文的“你”在字模中卻是這樣記載的:
在硬件系統內,英文的字模信息一般固化在ROM里,即使在沒有進入系統的CMOS里,也可以讓你看到英文字符。而在DOS下,中文的字模信息一般記錄在漢字庫文件HZK16里。
漢字庫文件
了解字母和漢字是按字模位信息顯示的原理后,那如何得到漢字的字模信息呢?難道要我們自己去做?NO。DOS前輩們經過艱辛的努力,將制作好的字模放到了一個個標準的庫中以免去后輩的麻煩,這就是點陣字庫文件。一般我們使用16*16的點陣宋體字庫,所謂16*16,是每一個漢字在縱、橫各16點的區域內顯示的。不過后來又有了HZK12、HZK24,HZK32和HZK48字庫及黑體、楷體和隸書字庫。雖然漢字庫種類繁多,但都是按照區位的順序排列的。前一個字節為該漢字的區號,后一個字節為該字的位號。每一個區記錄94個漢字,位號則為該字在該區中的位置。因此,漢字在漢字庫中的具體位置計算公式為:94*(區號-1)+位號-1。減1是因為數組是以0為開始而區號位號是以1為開始的。這僅為以漢字為單位該漢字在漢字庫中的位置,那么,如何得到以字節為單位得到該漢字在漢字庫中的位置呢?只需乘上一個漢字字模占用的字節數即可,即:(94*(區號-1)+位號-1)*一個漢字字模占用字節數,而按每種漢字庫的漢字大小不同又會得到不同的結果。以16*16點陣字庫為例,計算公式則為:(94*(區號-1)+(位號-1))*32。漢字庫文該從該位置起的32字節信息即記錄了該字的字模信息。
漢字庫文件
了解點陣漢字及漢字庫的構成原理后,顯示漢字就變得簡單。以16*16點陣字庫為例,通常的方法是:將文件工作指針移到需要的漢字字模處、將漢字庫文件讀入一2*16數組再用for循環一位位地顯示。以使用VGAHI模式顯示“我”字為例,程序如下:
#include "graphics.h"
#include "stdio.h"
main()
{
??? int i=VGA,j=VGAHI,k;
??? unsigned char mat[16][2],chinease[3]="我";
??? FILE *HZK;
??? if((HZK=fopen("hzk16","rb"))==NULL)
??????? exit(0);
??? initgraph(&i,&j,"");
??? i=chinease[0]-0xa0;j=chinease[1]-0xa0; /*獲得區碼與位碼*/
??? fseek(HZK,(94*(i-1)+(j-1))*32,SEEK_SET);
??? fread(mat,32,1,HZK);
??? for(j=0;j<16;j++)
??????? for(i=0;i<2;i++)
??????????? for(k=0;k<8;k++)
??????????????? if(mat[j][i]&(0x80>>k)) /*測試為1的位則顯示*/
??????????????????? putpixel(i*8+k,j,WHITE);
??? getch();
??? closegraph();
??? fclose(HZK);
}
怎么樣?只要掌握了正確的方法,顯示漢字并不復雜。
打印字庫文件和HZK12
如果你有UCDOS的HZK24S(宋體)、HZK24K(楷體)或HZK24H(黑體),你還可以使用不同字體的大字模漢字了。HZK24系列是24*24的點陣字庫,每字模占用3*24字節。如果你按照HZK16的顯示方法的話,你會看到......呵呵,字被放倒了。這是因為該類字庫與一般的漢字庫不同,這類大字模漢字庫是專供打印的打印字庫,為了打印的方便將字模都放倒了,你使用時,只要將字模的位信息縱橫轉置顯示即可。例如你如果定義為mat[24][3]則應該這樣輸出:
??? for(i=0;i<24;i++)
??????? for(j=0;j<24;j++)
??????????? if((0x80>>i%8)&mat[j][i/8]) /*轉置顯示*/
??????????????? putpixel(j+x,y+i,color);
還有一類字庫HZK12,雖然屬于標準字庫類型,但如果你將它的字模當作12*12位計算的話,根本無法正常顯示漢字。因為字庫設計者為了使用的方便,字摸每行的位數均補齊為8的整數倍,于是實際該字庫的位長度是16*12,雖然每行都多出了4位,但這4位都是0(不顯示),并不影響顯示效果。
對于24*24的點陣字庫,存放格式如下:?
??????????縱向存放3個字節(24位),橫向存放24個字節,每個字模占72個字節?
??????????字符排列順序如下:?
??????????1???4???7???10???......?
??????????2???5???8???11???......?
??????????3???6???9???12???......??
????????????????
???????????????對于16*16的點陣字庫,存放格式如下:?
??????????橫向存放2個字節(16位),其中第二個字節沒有多余的數據?
??????????縱向存放16個字節,每個字模占32個字節?
??????????字符排列順序如下:?
???????????????1????2?
???????????????3????4?
???????????????5????6?
???????????????......?
????????????????
???????????????對于14*14的點陣字庫,存放格式如下:?
??????????橫向存放2個字節(16位),其中第二個字節的后2位是多余的數據?
??????????縱向存放14個字節,每個字模占28個字節?
??????????字符排列順序如下:?
???????????????1????2?
???????????????3????4?
???????????????5????6?
???????????????......?
???????????
???????????????對于12*12的點陣字庫,存放格式如下:?
??????????橫向存放2個字節(16位),其中第二個字節的后4位是多余的數據?
??????????縱向存放12個字節,每個字模占24個字節?
??????????字符排列順序如下:?
???????????????1????2?
???????????????3????4?
???????????????5????6?
???????????????......?
總結
- 上一篇: 字模c语言,[C/C++]字模的解析(视
- 下一篇: 致每一位Java初学者