《30天自制操作系統》最近一直再看,最近已經看到后面了,看到第28天,里面講到可以實現對全角字符的支持,而原操作系統代碼里面只是支持了日語顯示,而中文版的這本書也只是講了一個思路,具體的實現也是沒有的。網上也好像沒有人實現過這個吧,我是找不到。(由於書中每一章每一小節都有代碼,我看書的時候就懶得去實際寫代碼,就簡單看看。不過這次就可以寫一下了,加深對這個系統的了解)反正沒事做,就准備實現對這個系統的漢字全角支持。
一、了解HZK編碼
在改造之前,我們先了解一下符合GB2312標准的中文點陣字庫文件的HZK16。百度搜索HZK16第一個那個百度百科連接就是了。
HZK16字庫是符合GB2312標准的16×16點陣字庫,HZK16的GB2312-80支持的漢字有6763個,符號682個。其中一級漢字有3755個,按聲序排列,二級漢字有3008個,按偏旁部首排列。我們在一些應用場合根本用不到這么多漢字字模,所以在應用時就可以只提取部分字體作為己用。
HZK16字庫里的16×16漢字一共需要256個點來顯示,也就是說需要32個字節才能達到顯示一個普通漢字的目的。
我們知道一個GB2312漢字是由兩個字節編碼的,范圍為A1A1~FEFE。A1-A9為符號區,B0到F7為漢字區。每一個區有94個字符(注意:這只是編碼的許可范圍,不一定都有字型對應,比如符號區就有很多編碼空白區域)。下面以漢字“我”為例,介紹如何在HZK16文件中找到它對應的32個字節的字模數據。
前面說到一個漢字占兩個字節,這兩個中前一個字節為該漢字的區號,后一個字節為該字的位號。其中,每個區記錄94個漢字,位號為該字在該區中的位置。
區碼和區號,其實是一個東西
區碼:區號(漢字的第一個字節)- 0xa0 (因為漢字編碼是從0xa0區開始的,所以文件最前面就是從0xa0區開始,要算出相對區碼)
位碼:位號(漢字的第二個字節)- 0xa0
這樣我們就可以得到漢字在HZK16中的絕對偏移位置:
offset=(94*(區碼-1)+(位碼-1))*32
注解: 1、區碼減1是因為數組是以0為開始而區號位號是以1為開始的
2、(94*(區號-1)+位號-1)是一個漢字字模占用的字節數
3、最后乘以32是因為漢字庫文應從該位置起的32字節信息記錄該字的字模信息(前面提到一個漢字要有32個字節顯示)
二、添加代碼
首先查看一下c語言里面的中文編碼是否真的跟書上講的是否一樣。我們先修改iroha/iroha.c這個文件,代碼如下

1 #include "apilib.h" 2 #include <stdio.h> 3 4 void HariMain(void) 5 { 6 static char s[9] = { 0xb2, 0xdb, 0xca, 0xc6, 0xce, 0xcd, 0xc4, 0x0a, 0x00 }; 7 char ch[10]; 8 char str[100]="我 啊\n"; 9 int i,j; 10 api_putstr0(s); 11 sprintf(ch,"%x %x %x %x\n",s[0],s[1],s[2],s[3]); 12 api_putstr0(ch); 13 sprintf(ch,"%x %x %x %x %x %x\n",str[0],str[1],str[2],str[3],str[4],str[5],str[6],str[7]); 14 api_putstr0(ch); 15 api_putstr0(str); 16 api_putstr0("4"); 17 18 api_end(); 19 }
運行的結果為
可以看出0xce 0xd2 0x20 0xb0 0xa1 0x0a 分別表示0x20是空格 0x0a是回車,看來我當前系統windows7下的作者默認編譯器,編譯的結構是符合EUC方式。我們就可以繼續了。我們以harib26a這個進行改造。
首先我們下載一個HZK16的字庫文件放到nihongo/HZK16.fnt,修改所有makefile文件harib26/Makefile和harib26/app_make.txt.更改這兩個文件里面的nihongo.fnt為HZK16.fnt.
接着修改haribote/bootpack.c里面約109行處修改所載入庫文件的大小,由於日文的nihongo.fnt比HZK16.fnt小所以要改大一點。至於多大,一般想法是右鍵屬性查看HZK16的文件大小,不過我是寫上
nihongo = (unsigned char *) memman_alloc_4k(memman, 0x5d5d*32);
因為0XFEFE-0XA1A1=0X5D5D.
往下三行,修改做載入字庫文件的文件名
finfo = file_search("HZK16.fnt", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
由於我們是增加漢字的支持所以我想定義task->langmode=3為漢字。我們在haribot/console.c約39行處加上一句task->langmode=3表示漢字。使每次都選擇漢字。
接下來就是輸出了,這次是在haribote/graphic.c約168行處增加下面一段代碼

1 if (task->langmode == 3) { 2 for (; *s != 0x00; s++) { 3 if (task->langbyte1 == 0) { 4 if (0xa1 <= *s && *s <= 0xfe) { 5 task->langbyte1 = *s; 6 } else { 7 putfont8(vram, xsize, x, y, c, nihongo + *s * 16); 8 } 9 } else { 10 k = task->langbyte1 - 0xa1; 11 t = *s - 0xa1; 12 task->langbyte1 = 0; 13 font = nihongo + 256 * 16 + (k * 94 + t) * 32; 14 putfont8(vram, xsize, x - 8, y, c, font ); 15 putfont8(vram, xsize, x , y, c, font + 16); 16 } 17 x += 8; 18 } 19 }
增加一個可以查看效果的程序,我們以chklang/chklang.c這個小程序為例吧。

1 #include "apilib.h" 2 3 void HariMain(void) 4 { 5 int langmode = api_getlang(); 6 static char s1[23] = { 7 0x93, 0xfa, 0x96, 0x7b, 0x8c, 0xea, 0x83, 0x56, 0x83, 0x74, 0x83, 0x67, 8 0x4a, 0x49, 0x53, 0x83, 0x82, 0x81, 0x5b, 0x83, 0x68, 0x0a, 0x00 9 }; 10 static char s2[17] = { 11 0xc6, 0xfc, 0xcb, 0xdc, 0xb8, 0xec, 0x45, 0x55, 0x43, 0xa5, 0xe2, 0xa1, 12 0xbc, 0xa5, 0xc9, 0x0a, 0x00 13 }; 14 static char s3[20] = { 15 0xce, 0xd2, 0x20, 0xca, 0xc7, 0xa1, 0xa2, 0xa1, 0xa3, 0x0a, 0x00 16 }; 17 int i;char j; 18 if (langmode == 0) { 19 api_putstr0("English ASCII mode\n"); 20 } 21 if (langmode == 1) { 22 api_putstr0(s1); 23 } 24 if (langmode == 2) { 25 api_putstr0(s2); 26 } 27 if (langmode == 3) {//增加這個表示對漢字的支持 28 api_putstr0("Chinese 中文! 我 !\n"); 29 api_putstr0("博客園 www.cnblogs.com/wunaozai \n"); 30 api_putstr0("無腦仔的小明"); 31 api_putstr0(s3); 32 for(i=0xa1;i<0xcc;i++) 33 { 34 s3[0]=0xa1; 35 j=i; 36 s3[1]=j; 37 s3[2]=0x00; 38 api_putstr0(s3); 39 } 40 } 41 api_end(); 42 }
大概就修改這些了吧,根據書中這樣修改,好像也不是很難嘛。好了我們make run一下。結果竟然是?????
萬惡的馬賽克?????
三、再次了解HZK這個編碼
果然還是功夫不到家。沒有仔細的看代碼。我們先了解一下字庫,我下載了一個軟件用於字庫的生成和查看,
可以正常的顯示,用這個軟件打開系統自帶的日文字庫,是顯示亂碼的。我但是就在想我們系統顯示亂碼是不是編碼方式不同還是因為壓縮的原因,試着好多種辦法。
這時想到了一個問題,自帶的日文字庫,好像是前半部分是半角,后半部分是全角。也就是做一個字庫里面已經有了ASCII 256個半角在字庫開頭,而我們的HZK16,看上面我們也知道,HZK開頭沒有ascii的半角,直接就是全角的字符了。所以我們要修改haribote/graphic.c文件里面的task->langmode==3這里面的代碼:
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);//只要是半角就使用hankaku里面的字符
后果又是失敗的,不過有了一點成功的跡象了。
這次再改一下task->langmode==3,改font = nihongo + (k * 94 + t) * 32; 由於沒有256個ascii所以這里也要該。再次make run。
對了就是這個界面,昨天困擾我好久好久啊,由於上面的逗號和句號又可以顯示,而其他的又顯示不了。這是為什么呢?
四、書上是不是講錯,或是講的不清楚
網上找了一個能顯示HZK編碼的C程序。

1 #include <stdio.h> 2 3 int main() 4 { 5 int i,j,k; 6 unsigned char incode[3]="中"; 7 unsigned char qh,wh; 8 unsigned long offset; 9 FILE * HZK; 10 char mat[16][2]; 11 char mat32[16]; 12 char mat64[16]; 13 qh = incode[0] - 0xa0; 14 wh = incode[1] - 0xa0; 15 //qh=18; 16 //wh=51; 17 printf("%d %d\n",qh,wh); 18 offset = (94*(qh-1)+(wh-1))*32; 19 20 if((HZK=fopen("HZK16","rb")) == NULL) 21 { 22 printf("Can't Open hzk16\n"); 23 return 0; 24 } 25 fseek(HZK, offset, SEEK_SET); 26 fread(mat, 32, 1, HZK); 27 fseek(HZK, offset, SEEK_SET); 28 fread(mat32, 16, 1, HZK); 29 fread(mat64, 16, 1, HZK); 30 for(j=0;j<16;j++) 31 { 32 for(i=0;i<2;i++) 33 { 34 for(k=0;k<8;k++) 35 { 36 if(mat[j][i]&(0x80>>k)) 37 { 38 printf("%c",'#'); 39 }else{ 40 printf("%c",'-'); 41 } 42 } 43 } 44 printf("\n"); 45 } 46 printf("\n"); 47 for(i=0;i<16;i++) 48 { 49 for(k=0;k<8;k++) 50 { 51 if(mat32[i]&(0x80>>k)) 52 { 53 printf("%c",'#'); 54 }else{ 55 printf("%c",'-'); 56 } 57 } 58 if(i%2) 59 printf("\n"); 60 } 61 for(i=0;i<16;i++) 62 { 63 for(k=0;k<8;k++) 64 { 65 if(mat64[i]&(0x80>>k)) 66 { 67 printf("%c",'#'); 68 }else{ 69 printf("%c",'-'); 70 } 71 } 72 if(i%2) 73 printf("\n"); 74 } 75 76 fclose(HZK); 77 78 return 0; 79 }
經過分析這個代碼才知道,原來日文的編碼是分左半部分和右半部分。而我們使用的HZK16是分上半部分和下半部分的。這一點坑了好久。
修改haribote/graphic.c
增加一個函數putfont32用於顯示漢字(這個函數寫的有點丑,能用就行了)

1 void putfont32(char *vram, int xsize, int x, int y, char c, char *font1, char *font2) 2 { 3 int i,k,j,f; 4 char *p, d ; 5 j=0; 6 p=vram+(y+j)*xsize+x; 7 j++; 8 //上半部分 9 for(i=0;i<16;i++) 10 { 11 for(k=0;k<8;k++) 12 { 13 if(font1[i]&(0x80>>k)) 14 { 15 p[k+(i%2)*8]=c; 16 } 17 } 18 for(k=0;k<8/2;k++) 19 { 20 f=p[k+(i%2)*8]; 21 p[k+(i%2)*8]=p[8-1-k+(i%2)*8]; 22 p[8-1-k+(i%2)*8]=f; 23 } 24 if(i%2) 25 { 26 p=vram+(y+j)*xsize+x; 27 j++; 28 } 29 } 30 //下半部分 31 for(i=0;i<16;i++) 32 { 33 for(k=0;k<8;k++) 34 { 35 if(font2[i]&(0x80>>k)) 36 { 37 p[k+(i%2)*8]=c; 38 } 39 } 40 for(k=0;k<8/2;k++) 41 { 42 f=p[k+(i%2)*8]; 43 p[k+(i%2)*8]=p[8-1-k+(i%2)*8]; 44 p[8-1-k+(i%2)*8]=f; 45 } 46 if(i%2) 47 { 48 p=vram+(y+j)*xsize+x; 49 j++; 50 } 51 } 52 return; 53 }
修改putfonts8_asc函數里if (task->langmode == 3)語句塊里這兩句
putfont8(vram, xsize, x - 8, y, c, font ); putfont8(vram, xsize, x , y, c, font + 16);
為
putfont32(vram,xsize,x-8,y,c,font,font+16);
終於改完了,應該可以了,有點小激動了,趕快make run一下
艾瑪總算可以實現中文了。
參考資料
HZK編碼 鏈接
所用到的工具和可以顯示中文的代碼:http://files.cnblogs.com/wunaozai/OS-in-30-days.zip
本文地址:http://www.cnblogs.com/wunaozai/p/3858473.html