首页 / 资讯中心 / 文章详情

GBK点阵字模:嵌入式中文显示与打印的索引算法及驱动适配

GBK点阵字模:嵌入式中文显示与打印的索引算法及驱动适配 ★ FEATURED ARTICLE
简介这是一份面向打印机固件开发、LCD汉字显示及嵌入式字库应用场景的GBK点阵字模资源收录GBK1.0规范内22046个汉字不含用户自定义区可解决中文显示与打印中字模缺失、编码转换繁琐的问题。包内共21个文件约3.41MB以bin字库、txt编码表、exe工具、bpl与ptl组件、c与h源码等为主GBK16.BIN与GBK24.BIN分别提供16×16和24×24点阵数据两份GBK编码表按分区与编码顺序排列另附字模提取、牧码字模、PCtoLCD2002等工具以及GBK转BIG5、GBK转Unicode的函数与字体索引算法源码。资源已有1670人学习下载适合需要快速集成字库、验证取模效果或研究编码转换的开发者参考能直接用于显示驱动与打印模块的字模调用。1. 从一次打印乱码说起这套 GBK 点阵字模到底解决了什么问题前阵子帮一个做嵌入式票据打印的朋友看问题设备屏幕能正常显示中文一走到打印环节出来的全是方块或者错位笔画。排查了半天根因不在驱动而在字库——他用的那套点阵数据是按区位码排的而打印模块按 GBK 编码去查表索引对不上自然满纸乱码。这类问题在打印机、POS 机、工控屏、电子价签上特别常见屏幕显示和打印走的是两套字模数据源一旦编码口径不统一就翻车。这份资源给的就是一套完整的 GBK 汉字点阵字模覆盖 16×16 和 24×24 两种规格同时附带索引算法和配套工具。它面向的是需要在嵌入式设备上做中文显示与打印的开发者——尤其是那些不想依赖完整字库芯片、希望把字模数据直接烧进 Flash 或者按需裁剪的场景。16×16 适合小尺寸屏幕和低分辨率打印头24×24 笔画更饱满适合票据、标签这类对可读性要求高的输出。下面我按「这套字模怎么组织、怎么查、怎么跑、怎么改」的顺序拆一遍。2. 字模数据组织与 GBK 索引算法从编码到点阵的映射链路2.1 点阵字模的存储格式16×16 的汉字每个字占 16 行、每行 16 个像素按位存储就是每行 2 字节一个字 32 字节。24×24 则是每行 3 字节一个字 72 字节。这是最朴素的「横向取模、高位在前」格式也是绝大多数打印机和 LCD 驱动直接能吃的格式。但实际拿到的字模文件不一定都是这个排列。常见的有横向取模和纵向取模两种还有高位在前和低位在前的区别。如果取模方向和驱动预期不一致显示出来就是笔画被拉长或者错位。我一般会先拿一个「一」字或者「十」字做验证——这种笔画简单的字最容易看出取模方向对不对。/* 16x16 汉字点阵的单字结构横向取模高位在前 */ typedef struct { unsigned char data[32]; /* 16 行 × 每行 2 字节 */ } Font16x16; /* 24x24 汉字点阵的单字结构 */ typedef struct { unsigned char data[72]; /* 24 行 × 每行 3 字节 */ } Font24x24; /* 取第 row 行第 col 列的像素值0 或 1 */ unsigned char get_pixel_16x16(const Font16x16 *font, int row, int col) { /* 每行 2 字节col/8 定位到字节col%8 定位到 bit */ unsigned char byte font-data[row * 2 col / 8]; return (byte (7 - (col % 8))) 0x01; }这段代码定义了两种规格的字模结构并给出了像素级读取的方法。row * 2 col / 8是横向取模的地址计算方式7 - (col % 8)表示高位在前。如果你的字模文件是纵向取模这个公式要改成按列累加读取逻辑完全不同。参数上row范围 0~15col范围 0~15越界会读到相邻字的数据调试时要注意边界。2.2 GBK 编码到字模索引的换算GBK 是双字节编码第一个字节范围 0x81~0xFE第二个字节范围 0x40~0xFE剔除 0x7F。要把一个 GBK 码位映射到字模数组的下标核心是算「这个码位在 GBK 字符集里排第几」。/* 计算 GBK 码位在字模数组中的索引返回 -1 表示非法码位 */ int gbk_to_font_index(unsigned char high, unsigned char low) { int index; /* 合法性校验GBK 高位 0x81~0xFE低位 0x40~0xFE 且不等于 0x7F */ if (high 0x81 || high 0xFE) return -1; if (low 0x40 || low 0xFE || low 0x7F) return -1; /* 计算该码位之前有多少个合法 GBK 字符 */ index (high - 0x81) * 190; /* 每个高位字节对应 190 个低位 */ if (low 0x7F) { index (low - 0x40 - 1); /* 跳过 0x7F 这个空洞 */ } else { index (low - 0x40); } return index; }这里的关键数字是 190GBK 每个高位字节下低位从 0x40 到 0xFE 共 191 个值去掉 0x7F 剩 190 个。(high - 0x81) * 190算出前面所有高位块的字符总数再加上当前块内的偏移。low 0x7F时要减 1因为 0x7F 不合法不占索引位。注意这个公式假设字模文件是按 GBK 码位顺序连续排列的。如果你的字模文件只包含常用字比如只收了 GB2312 的 6763 个汉字索引算法要换成按区位码或者查表法不能直接用这个公式。2.3 索引表与二分查找的取舍如果字模文件不是全量 GBK 顺序排列而是只挑了部分汉字那就需要一张索引表。索引表通常存「GBK 码位 → 字模偏移」的映射查的时候先算码位再在索引表里找偏移。/* 索引表条目 */ typedef struct { unsigned short gbk_code; /* GBK 双字节合并成一个 short */ unsigned int offset; /* 字模数据在文件中的字节偏移 */ } FontIndexEntry; /* 二分查找索引表按 gbk_code 升序排列 */ int find_font_offset(const FontIndexEntry *table, int count, unsigned short gbk_code, unsigned int *out_offset) { int lo 0, hi count - 1; while (lo hi) { int mid lo (hi - lo) / 2; if (table[mid].gbk_code gbk_code) { *out_offset table[mid].offset; return 0; /* 找到 */ } else if (table[mid].gbk_code gbk_code) { lo mid 1; } else { hi mid - 1; } } return -1; /* 未找到 */ }二分查找的前提是索引表按gbk_code升序排列。如果表是无序的要么先排序要么改用哈希表。在嵌入式环境里索引表通常放在 Flash 里二分查找的log2(N)次访问对 Flash 读取来说完全可以接受。参数count是索引表条目数out_offset是输出参数返回 0 表示成功。3. 显示与打印的驱动适配从字模数据到实际输出3.1 屏幕显示的字模渲染屏幕显示和打印对字模的使用方式有本质区别。屏幕是逐像素刷到显存打印是逐行送到打印头。先看屏幕这边。/* 将 16x16 字模渲染到 LCD 显存x/y 为左上角坐标 */ void draw_font_16x16(unsigned char *lcd_buffer, int lcd_width, int x, int y, const Font16x16 *font, unsigned short color) { for (int row 0; row 16; row) { for (int col 0; col 16; col) { if (get_pixel_16x16(font, row, col)) { /* 计算显存中的像素位置假设 RGB565 每像素 2 字节 */ int px x col; int py y row; int offset (py * lcd_width px) * 2; lcd_buffer[offset] color 8; lcd_buffer[offset 1] color 0xFF; } } } }这段代码把字模的每个像素点映射到 LCD 显存的对应位置。lcd_width是屏幕宽度像素color是 RGB565 格式的颜色值。注意这里只写了前景色背景色需要提前填充或者用透明叠加的方式处理。如果屏幕支持硬件加速或者有专门的字符叠加层可以直接把字模数据送到硬件层省掉逐像素循环。3.2 打印机点阵数据的组装打印机这边更关注「一行一行地送」。热敏打印机通常按行接收点阵数据每行对应打印头的一个加热周期。汉字打印需要把字模的每一行提取出来拼成打印机能识别的位流。/* 将一行汉字字模转换为打印机点阵行数据 */ /* font_data: 连续排列的多个 16x16 字模char_count 为字数 */ void build_print_line(const Font16x16 *font_data, int char_count, int row, unsigned char *out_line) { int out_byte 0; for (int c 0; c char_count; c) { /* 每个字取第 row 行的 2 个字节 */ out_line[out_byte] font_data[c].data[row * 2]; out_line[out_byte] font_data[c].data[row * 2 1]; } }这个函数把一行里所有汉字的第row行数据拼成一个连续的字节流直接送给打印头。char_count是一行打印多少个汉字out_line的长度是char_count * 2。24×24 的字模同理只是每行 3 字节输出长度是char_count * 3。提示打印头的点阵宽度通常是固定的比如 384 点一行能打的汉字数 打印头宽度 / 字宽。16×16 字模一行能打 24 个字24×24 字模一行能打 16 个字。超出部分要换行处理。3.3 混合排版汉字与 ASCII 的宽度对齐实际打印内容往往是汉字和数字、字母混排。ASCII 字符通常用 8×16 或者 12×24 的点阵宽度和汉字不一样。如果直接拼接会出现对不齐的情况。/* 混合排版汉字 16x16ASCII 8x16按行输出 */ void build_mixed_line(const Font16x16 *hanzi, int hanzi_count, const unsigned char *ascii, int ascii_count, int row, unsigned char *out_line) { int pos 0; /* 先输出汉字部分 */ for (int i 0; i hanzi_count; i) { out_line[pos] hanzi[i].data[row * 2]; out_line[pos] hanzi[i].data[row * 2 1]; } /* 再输出 ASCII 部分每个 ASCII 占 1 字节宽 */ for (int i 0; i ascii_count; i) { out_line[pos] ascii[i * 16 row]; } }这里假设 ASCII 字模是 8×16、纵向取模、每字 16 字节。ascii[i * 16 row]取出第i个 ASCII 字符第row行的字节。混排的关键是宽度对齐——如果 ASCII 用 8 点宽汉字 16 点宽那一个汉字正好占两个 ASCII 的位置排版时按这个比例算列数就不会错位。4. 配套工具与完整运行链路从字模文件到设备输出4.1 字模提取工具的使用配套工具通常负责从标准字库文件比如 Windows 的 .ttf 或者点阵字库文件里提取指定规格的字模按 GBK 顺序导出成二进制文件。常见做法是提供一个命令行工具指定字体、字号、输出格式。# 从系统字体提取 16x16 点阵输出 GBK 顺序的二进制字模 ./font_extract --font simsun.ttf --size 16 --encoding gbk \ --format bin --output font16.bin # 提取 24x24 点阵 ./font_extract --font simsun.ttf --size 24 --encoding gbk \ --format bin --output font24.bin参数说明--font指定源字体文件--size指定点阵尺寸--encoding指定编码方式--format指定输出格式bin 是纯二进制c 是 C 数组。输出文件的大小可以验证全量 GBK 汉字约 2 万字16×16 字模约 640KB24×24 约 1.4MB。如果文件大小对不上说明提取范围或者取模方式有问题。4.2 字模数据的烧录与寻址嵌入式设备上字模数据通常放在外部 Flash 或者 SD 卡里。烧录方式取决于硬件常见的是通过烧录器把 bin 文件写到 Flash 的固定偏移或者通过文件系统挂载后按文件读取。/* 从 Flash 读取指定 GBK 码位的字模数据 */ /* flash_base 是字模数据在 Flash 中的起始地址 */ int read_font_from_flash(unsigned int flash_base, unsigned char high, unsigned char low, unsigned char *out, int size) { int index gbk_to_font_index(high, low); if (index 0) return -1; unsigned int addr flash_base index * size; flash_read(addr, out, size); /* 底层 Flash 读取函数 */ return 0; }flash_base是字模数据在 Flash 中的起始地址size是单个字模的字节数16×16 是 3224×24 是 72。flash_read是硬件相关的读取函数不同平台实现不同。如果 Flash 读取速度慢可以考虑加一层缓存把常用字预加载到 RAM。4.3 完整验证流程拿到字模文件和工具后我一般按这个顺序验证用工具提取几个已知汉字比如「中」「文」「测」「试」导出成 C 数组肉眼检查点阵数据是否符合预期。在 PC 上写一个小程序把字模数据渲染成图片确认字形正确、取模方向正确。把字模数据烧到设备先显示单个汉字再显示一行汉字最后测试打印输出。测试边界情况GBK 扩展区汉字、生僻字、ASCII 混排、超长文本换行。/* PC 端验证把字模渲染成 PGM 图片方便肉眼检查 */ void dump_font_to_pgm(const Font16x16 *font, const char *filename) { FILE *fp fopen(filename, wb); fprintf(fp, P1\n16 16\n); /* P1 是 ASCII 位图格式 */ for (int row 0; row 16; row) { for (int col 0; col 16; col) { fprintf(fp, %d , get_pixel_16x16(font, row, col)); } fprintf(fp, \n); } fclose(fp); }这个函数把字模导出成 PGM 图片用任何图片查看器都能打开。如果看到的字形是反的、旋转的或者笔画断裂说明取模方向或者位序有问题回到 2.1 节检查存储格式。5. 避坑与常见问题排查那些让我熬夜的点阵问题5.1 显示正常但打印乱码现象屏幕上汉字显示完全正常一打印就变成方块或者笔画错位。原因屏幕和打印用了两套不同的字模数据或者打印模块的取模方向与字模文件不一致。屏幕驱动可能做了自动旋转或者位序转换打印模块没有。解决确认打印模块用的字模文件和屏幕是同一套检查取模方向参数。用「一」字打印一行看输出的是横线还是竖线就能判断方向对不对。5.2 部分汉字显示为空白现象大部分汉字正常少数汉字显示为空白或者乱码。原因字模文件不完整缺少 GBK 扩展区或者某些生僻字。或者索引算法在计算扩展区码位时越界了。解决用工具检查字模文件的总字数和 GBK 全量字符数对比。如果只收了 GB2312 的 6763 个汉字那 GBK 扩展区的字自然找不到。索引算法要加边界判断找不到时返回默认字模或者空白。5.3 24×24 字模打印出来笔画粘连现象16×16 字模打印正常换成 24×24 后笔画糊在一起。原因打印头的点间距和字模尺寸不匹配或者打印浓度设置过高导致墨迹扩散。也可能是 24×24 字模的取模方式与 16×16 不同。解决先确认 24×24 字模的取模方式和 16×16 一致。如果一致调整打印浓度和走纸速度。热敏打印对浓度很敏感浓度高了小字容易糊。5.4 索引计算速度慢导致刷新卡顿现象屏幕刷新时汉字显示有明显延迟尤其是整屏刷新时。原因每次显示都重新计算索引并读取 Flash没有缓存。解决加一层 RAM 缓存把当前屏幕用到的字模预加载到内存。或者用索引表 二分查找替代实时计算减少 Flash 随机读取次数。5.5 字模文件大小对不上现象提取出来的字模文件比预期小很多或者大很多。原因提取工具的字符范围设置不对或者取模方式导致每个字的字节数不同。解决先算预期大小全量 GBK 约 2 万字16×16 每字 32 字节总共约 640KB。如果文件只有几十 KB说明只提取了部分字符。如果文件特别大检查是不是把 ASCII 或者其他编码的字符也提进来了。6. 二次开发与裁剪按需定制字模数据的几个实用技巧拿到这套字模和工具后最常见的二次开发需求是「裁剪」——设备 Flash 空间有限不可能把全量 GBK 字模都烧进去。我一般会按项目实际用到的字符集来裁剪比如只保留 GB2312 的 6763 个常用汉字加上数字、字母和常用标点文件能缩小到原来的三分之一左右。裁剪的关键是维护一张「需要的 GBK 码位列表」然后用工具按列表提取。如果工具不支持按列表提取可以先用全量字模生成再写脚本按索引表筛选。筛选时注意保持索引表的顺序否则二分查找会失效。# 按码位列表裁剪字模文件 def trim_font(full_font_path, code_list, char_size, output_path): with open(full_font_path, rb) as f: full_data f.read() with open(output_path, wb) as out: for high, low in code_list: index (high - 0x81) * 190 if low 0x7F: index (low - 0x40 - 1) else: index (low - 0x40) offset index * char_size out.write(full_data[offset:offset char_size])这段 Python 脚本按给定的 GBK 码位列表从全量字模里抽取对应的字模数据重新拼成一个裁剪后的文件。char_size是单个字模的字节数16×16 是 3224×24 是 72。裁剪后的文件需要配套一张新的索引表否则原来的索引算法算出来的下标会对不上。另一个常见需求是「换字体」。不同字体的点阵字形差异很大尤其是 16×16 这种小尺寸有些字体在小字号下笔画会糊。我一般会准备两套字模一套宋体用于正文一套黑体用于标题。换字体时只需要重新跑一遍提取工具索引算法和驱动代码都不用动。还有一个技巧是「动态加载」。如果设备有 SD 卡或者外部 Flash可以把字模文件放在文件系统里按需读取。这样既不占内部 Flash又能支持全量字符。代价是读取速度慢一些需要加缓存。我一般会在 RAM 里开一个 4KB 左右的缓存区用 LRU 策略管理命中率能到 90% 以上。最后说一个我踩过的坑裁剪字模时一定要把 ASCII 字符也包含进去。很多项目只裁剪了汉字结果打印数字和字母时找不到字模输出空白。ASCII 字符虽然简单但少了它整个排版就废了。从那以后我每次裁剪字模都强制走一遍「汉字 ASCII 常用标点」的检查清单确认三类字符都齐了才烧录。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站