6 日文文字显示(2)(harib25f)

好啦,接下来我们该挑战全角字符的显示了。在全角字符显示方面,Shift-JIS和日文EUC的处理方法是不同的,我们先从Shift-JIS开始。

各种半角字符,包括字母、数字、符号、片假名等,加起来总的字符数也不是很多,用1个字节完全可以容纳,不过汉字就不行了,汉字需要使用2个字节来表示(在某些编码方式中,甚至会使用3个甚至更多的字节来表示)。

例如“あ”在Shift-JIS中的编码为0x82、0xa0两个字节。只要用文本编辑器输入一个“あ”并保存,再用二进制编辑器打开就能看到这个编码了(注:在中文系统下,需要用支持选择编码方式的文本编辑器,并选择用Shift-JIS编码保存)。下面我们来讲解一下如何将0x82、0xa0这两个字节的编码转换为区点的编号。

■■■■■

我们先来看第一个字节。如果这个字节为0x81,则代表01区或02区;0x82则代表03区或04区;0x83则代表05区或06区……我们把规则整理成下面这张表,顺便将0x00~0x7f也加进去了。 Shift-JIS的第一个字节

0x00 控制字符 (中略)
0x01 控制字符 0xdd 半角片假名(“ン”)
0x02 控制字符 0xde 半角片假名(“゚”)
(中略) 0xdf 半角片假名(“゙”)
0x1d 控制字符 0xe0 全角字符(1面63区~64区)
0x1e 控制字符 0xe1 全角字符(1面65区~66区)
0x1f 控制字符 0xe2 全角字符(1面67区~68区)
0x20 半角字符(空格) (中略)
0x21 半角字符(“!”) 0xed 全角字符(1面89区~90区)
0x22 半角字符(“””) 0xee 全角字符(1面91区~92区)
(中略) 0xef 全角字符(1面93区~94区)
0x7c 半角字符(“|”) 0xf0 全角字符(2面01区或08区)
0x7d 半角字符(“}”) 0xf1 全角字符(2面03区~04区)
0x7e 半角字符(“~”) 0xf2 全角字符(2面05区或12区)
0x7f 控制字符 0xf3 全角字符(2面13区~14区)
0x80 不使用 0xf4 全角字符(2面15区或78区)
0x81 全角字符(1面01区~02区) 0xf5 全角字符(2面79区~80区)
0x82 全角字符(1面03区~04区) 0xf6 全角字符(2面81区~82区)
0x83 全角字符(1面05区~06区) 0xf7 全角字符(2面83区~84区)
(中略) (中略)
0x9d 全角字符(1面57区~58区) 0xfa 全角字符(2面89区~90区)
0x9e 全角字符(1面59区~60区) 0xfb 全角字符(2面91区~92区)
0x9f 全角字符(1面61区~62区) 0xfc 全角字符(2面93区~94区)
0xa0 不使用 0xfd 不使用
0xa1 半角片假名(“。”) 0xfe 不使用
0xa2 半角片假名(“「”) 0xff 不使用

接下来是第二个字节,如下表。 Shift-JIS的第二个字节

0x00 不使用 0x81 全角字符(较小的区的65点)
0x01 不使用 0x82 全角字符(较小的区的66点)
0x02 不使用 (中略)
(中略) 0x9c 全角字符(较小的区的92点)
0x3d 不使用 0x9d 全角字符(较小的区的93点)
0x3e 不使用 0x9e 全角字符(较小的区的94点)
0x3f 不使用 0x9f 全角字符(较大的区的01点)
0x40 全角字符(较小的区的01点) 0xa0 全角字符(较大的区的02点)
0x41 全角字符(较小的区的02点) 0xa1 全角字符(较大的区的03点)
0x42 全角字符(较小的区的03点) (中略)
(中略) 0xfa 全角字符(较大的区的92点)
0x7c 全角字符(较小的区的61点) 0xfb 全角字符(较大的区的93点)
0x7d 全角字符(较小的区的62点) 0xfc 全角字符(较大的区的94点)
0x7e 全角字符(较小的区的63点) 0xfd 不使用
0x7f 不使用 0xfe 不使用
0x80 全角字符(较小的区的64点) 0xff 不使用

参照上面的两张表,我们就可以得到“あ”的编码0x82、0xa0对应04区02点。

知道了区点编号我们就可以计算出字模的内存地址,再显示出来也就很容易了。

■■■■■

我们来修改一下操作系统吧。

本次的graphic.c节选

  1. void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
  2. {
  3. extern char hankaku[4096];
  4. struct TASK *task = task_now();
  5. char *nihongo = (char *) *((int *) 0x0fe8), *font; /*从此开始*/
  6. int k, t; /*到此结束*/
  7. if (task->langmode == 0) {
  8. for (; *s != 0x00; s++) {
  9. putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
  10. x += 8;
  11. }
  12. }
  13. if (task->langmode == 1) {
  14. for (; *s != 0x00; s++) {
  15. if (task->langbyte1 == 0) { /*从此开始*/
  16. if ((0x81 <= *s && *s <= 0x9f) || (0xe0 <= *s && *s <= 0xfc)) {
  17. task->langbyte1 = *s;
  18. } else {
  19. putfont8(vram, xsize, x, y, c, nihongo + *s * 16);
  20. }
  21. } else {
  22. if (0x81 <= task->langbyte1 && task->langbyte1 <= 0x9f) {
  23. k = (task->langbyte1 - 0x81) * 2;
  24. } else {
  25. k = (task->langbyte1 - 0xe0) * 2 + 62;
  26. }
  27. if (0x40 <= *s && *s <= 0x7e) {
  28. t = *s - 0x40;
  29. } else if (0x80 <= *s && *s <= 0x9e) {
  30. t = *s - 0x80 + 63;
  31. } else {
  32. t = *s - 0x9f;
  33. k++;
  34. }
  35. task->langbyte1 = 0;
  36. font = nihongo + 256 * 16 + (k * 94 + t) * 32;
  37. putfont8(vram, xsize, x - 8, y, c, font ); /*左半部分*/
  38. putfont8(vram, xsize, x , y, c, font + 16); /*右半部分*/
  39. } /*到此结束*/
  40. x += 8;
  41. }
  42. }
  43. return;
  44. }

本次的bootpack.h节选

  1. struct TASK {
  2. (中略)
  3. unsigned char langmode, langbyte1;
  4. };

这里的变量k用来存放区号,变量t用来存放点号,为了方便计算,我们存放的是减1之后的值。由于我们没有考虑第2面的字符,因此如果以后要支持第四水准汉字会比较麻烦,不过要支持第二和第三水准汉字还是比较容易的,只要修改载入nihongo.fnt的部分就可以了。

struct TASK中的langbyte1是当接收到全角字符时用来存放第1个字节内容的变量。当接收到半角字符,或者全角字符显示完成之后,该变量被置为0。

putfonts8_asc中每接收到1个字节就会执行x += 8;,当显示全角字符时,需要在接收到第2个字节之后,再往左回移8个像素并绘制字模的左半部分。

采用这种方式时,如果一开始langbyte1不置为0,显示就会出问题,因此我们还需要再修改一下console.c。

本次的console.c节选

  1. void console_task(struct SHEET *sheet, int memtotal)
  2. {
  3. (中略)
  4. if (nihongo[4096] != 0xff) { /*是否载入了日文字库?*/
  5. task->langmode = 1;
  6. } else {
  7. task->langmode = 0;
  8. }
  9. task->langbyte1 = 0; /*这里!*/
  10. (中略)
  11. }
  12. int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
  13. {
  14. (中略)
  15. if (finfo != 0) {
  16. /*找到文件的情况*/
  17. (中略)
  18. if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
  19. (中略)
  20. start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0));
  21. (中略)
  22. timer_cancelall(&task->fifo);
  23. memman_free_4k(memman, (int) q, segsiz);
  24. task->langbyte1 = 0; /*这里!*/
  25. } else {
  26. cons_putstr0(cons, ".hrb file format error.\n");
  27. }
  28. (中略)
  29. }
  30. (中略)
  31. }

对console_task所做的修改只是在决定langmode默认值时顺便将langbyte1置为0而已。

当程序出现bug或者强制结束时可能出现在显示全角字符第1个字节时停止的情况,而对cmd_app所做的修改就是为了应对这种情况。

不过换行还有一点问题,当字符串很长时,可能在全角字符的第1个字节处就遇到自动换行了,这样一来当收到第2个字节时,字模的左半部分就会画到命令行窗口外面去。所以我们在遇到第1个字节换行时,可以特意将cur_x再右移8个像素。

本次的console.c节选

  1. void cons_newline(struct CONSOLE *cons)
  2. {
  3. int x, y;
  4. struct SHEET *sheet = cons->sht;
  5. struct TASK *task = task_now(); /*这里!*/
  6. if (cons->cur_y < 28 + 112) {
  7. cons->cur_y += 16; /*到下一行*/
  8. } else {
  9. /*屏幕滚动*/
  10. (中略)
  11. }
  12. cons->cur_x = 8;
  13. if (task->langmode == 1 && task->langbyte1 != 0) { /*从此开始*/
  14. cons->cur_x += 8;
  15. } /*到此结束*/
  16. return;
  17. }

完工了,我们来“make run”试试看。虽然没有编写用于测试的应用程序,不过我们可以执行“type ipl10.nas”,如果显示出日文就算成功啦。……出来啦!

6 日文文字显示(2)(harib25f) - 图1

终于显示出日文了哦

不过现在高兴还太早了,仔细看看画面就发现有什么地方不对,为什么“埋”这个汉字没有显示出来呢?k和t的计算应该没有问题啊,嗯……

哦对了,一定是nihongo.fnt文件太大,用ipl10.nas无法全部载入。嗯,一定是这样!