Add Chinese text rendering support
Extend the bitmap font pipeline with the Chinese Joker glyph subset and encode supported UTF-8 text for Tonc TTE. Move the affine map clear of the expanded font, add a compile-time VRAM overlap guard, and cover every custom glyph with host tests.
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
Fusion Pixel Font
|
||||||
|
https://github.com/TakWolf/fusion-pixel-font
|
||||||
|
|
||||||
|
Copyright (c) 2022, TakWolf (https://takwolf.com).
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created using
|
||||||
|
the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file chinese_text.h
|
||||||
|
* @brief UTF-8 conversion helpers for the extended TTE font.
|
||||||
|
*/
|
||||||
|
#ifndef CHINESE_TEXT_H
|
||||||
|
#define CHINESE_TEXT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map supported UTF-8 Chinese text to valid UTF-8 sequences for custom TTE glyphs.
|
||||||
|
*
|
||||||
|
* ASCII bytes, including TTE formatting tags, are copied unchanged. Unsupported Unicode
|
||||||
|
* characters are replaced with '?'. Custom glyph IDs are emitted as UTF-8 because Tonc TTE
|
||||||
|
* decodes every byte above 0x7F as part of a UTF-8 sequence.
|
||||||
|
*
|
||||||
|
* @return Number of encoded bytes, excluding the null terminator.
|
||||||
|
*/
|
||||||
|
size_t chinese_text_encode(const char* utf8, char* output, size_t output_size);
|
||||||
|
|
||||||
|
#endif // CHINESE_TEXT_H
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Ordered glyphs appended after ASCII code point 127 in gbalatro_sys8.png.
|
||||||
|
CHINESE_GLYPH(0x5C0F) // 小
|
||||||
|
CHINESE_GLYPH(0x4E11) // 丑
|
||||||
|
CHINESE_GLYPH(0x724C) // 牌
|
||||||
|
CHINESE_GLYPH(0x6C49) // 汉
|
||||||
|
CHINESE_GLYPH(0x5316) // 化
|
||||||
|
CHINESE_GLYPH(0x9884) // 预
|
||||||
|
CHINESE_GLYPH(0x89C8) // 览
|
||||||
|
CHINESE_GLYPH(0x534A) // 半
|
||||||
|
CHINESE_GLYPH(0x5F20) // 张
|
||||||
|
CHINESE_GLYPH(0x6050) // 恐
|
||||||
|
CHINESE_GLYPH(0x6016) // 怖
|
||||||
|
CHINESE_GLYPH(0x9762) // 面
|
||||||
|
CHINESE_GLYPH(0x5B54) // 孔
|
||||||
|
CHINESE_GLYPH(0x5982) // 如
|
||||||
|
CHINESE_GLYPH(0x679C) // 果
|
||||||
|
CHINESE_GLYPH(0x51FA) // 出
|
||||||
|
CHINESE_GLYPH(0x4E0D) // 不
|
||||||
|
CHINESE_GLYPH(0x8D85) // 超
|
||||||
|
CHINESE_GLYPH(0x8FC7) // 过
|
||||||
|
CHINESE_GLYPH(0x6253) // 打
|
||||||
|
CHINESE_GLYPH(0x7684) // 的
|
||||||
|
CHINESE_GLYPH(0x4EBA) // 人
|
||||||
|
CHINESE_GLYPH(0x5934) // 头
|
||||||
|
CHINESE_GLYPH(0x8BA1) // 计
|
||||||
|
CHINESE_GLYPH(0x5206) // 分
|
||||||
|
CHINESE_GLYPH(0x65F6) // 时
|
||||||
|
CHINESE_GLYPH(0x7ED9) // 给
|
||||||
|
CHINESE_GLYPH(0x4E88) // 予
|
||||||
|
CHINESE_GLYPH(0x500D) // 倍
|
||||||
|
CHINESE_GLYPH(0x7387) // 率
|
||||||
|
CHINESE_GLYPH(0x7B79) // 筹
|
||||||
|
CHINESE_GLYPH(0x7801) // 码
|
||||||
|
CHINESE_GLYPH(0x5DE6) // 左
|
||||||
|
CHINESE_GLYPH(0x53F3) // 右
|
||||||
|
CHINESE_GLYPH(0x5207) // 切
|
||||||
|
CHINESE_GLYPH(0x6362) // 换
|
||||||
|
CHINESE_GLYPH(0x52A8) // 动
|
||||||
|
CHINESE_GLYPH(0x753B) // 画
|
||||||
|
CHINESE_GLYPH(0x4E3B) // 主
|
||||||
|
CHINESE_GLYPH(0x83DC) // 菜
|
||||||
|
CHINESE_GLYPH(0x5355) // 单
|
||||||
|
CHINESE_GLYPH(0x4E00) // 一
|
||||||
|
CHINESE_GLYPH(0x4E09) // 三
|
||||||
|
CHINESE_GLYPH(0x4E0B) // 下
|
||||||
|
CHINESE_GLYPH(0x4E24) // 两
|
||||||
|
CHINESE_GLYPH(0x4E2A) // 个
|
||||||
|
CHINESE_GLYPH(0x4E2D) // 中
|
||||||
|
CHINESE_GLYPH(0x4E3A) // 为
|
||||||
|
CHINESE_GLYPH(0x4E8E) // 于
|
||||||
|
CHINESE_GLYPH(0x4F4D) // 位
|
||||||
|
CHINESE_GLYPH(0x4F4E) // 低
|
||||||
|
CHINESE_GLYPH(0x4F59) // 余
|
||||||
|
CHINESE_GLYPH(0x4F8B) // 例
|
||||||
|
CHINESE_GLYPH(0x4FA7) // 侧
|
||||||
|
CHINESE_GLYPH(0x5076) // 偶
|
||||||
|
CHINESE_GLYPH(0x518D) // 再
|
||||||
|
CHINESE_GLYPH(0x51E0) // 几
|
||||||
|
CHINESE_GLYPH(0x5230) // 到
|
||||||
|
CHINESE_GLYPH(0x5236) // 制
|
||||||
|
CHINESE_GLYPH(0x524D) // 前
|
||||||
|
CHINESE_GLYPH(0x5269) // 剩
|
||||||
|
CHINESE_GLYPH(0x529B) // 力
|
||||||
|
CHINESE_GLYPH(0x52A0) // 加
|
||||||
|
CHINESE_GLYPH(0x5305) // 包
|
||||||
|
CHINESE_GLYPH(0x53D1) // 发
|
||||||
|
CHINESE_GLYPH(0x53EF) // 可
|
||||||
|
CHINESE_GLYPH(0x5408) // 合
|
||||||
|
CHINESE_GLYPH(0x540C) // 同
|
||||||
|
CHINESE_GLYPH(0x540E) // 后
|
||||||
|
CHINESE_GLYPH(0x542B) // 含
|
||||||
|
CHINESE_GLYPH(0x548C) // 和
|
||||||
|
CHINESE_GLYPH(0x56DB) // 四
|
||||||
|
CHINESE_GLYPH(0x56DE) // 回
|
||||||
|
CHINESE_GLYPH(0x5747) // 均
|
||||||
|
CHINESE_GLYPH(0x590D) // 复
|
||||||
|
CHINESE_GLYPH(0x5916) // 外
|
||||||
|
CHINESE_GLYPH(0x5947) // 奇
|
||||||
|
CHINESE_GLYPH(0x5B50) // 子
|
||||||
|
CHINESE_GLYPH(0x5BF9) // 对
|
||||||
|
CHINESE_GLYPH(0x5C06) // 将
|
||||||
|
CHINESE_GLYPH(0x5F03) // 弃
|
||||||
|
CHINESE_GLYPH(0x5F53) // 当
|
||||||
|
CHINESE_GLYPH(0x6210) // 成
|
||||||
|
CHINESE_GLYPH(0x6216) // 或
|
||||||
|
CHINESE_GLYPH(0x6240) // 所
|
||||||
|
CHINESE_GLYPH(0x624B) // 手
|
||||||
|
CHINESE_GLYPH(0x62E5) // 拥
|
||||||
|
CHINESE_GLYPH(0x62EC) // 括
|
||||||
|
CHINESE_GLYPH(0x63A5) // 接
|
||||||
|
CHINESE_GLYPH(0x6570) // 数
|
||||||
|
CHINESE_GLYPH(0x65B9) // 方
|
||||||
|
CHINESE_GLYPH(0x65E0) // 无
|
||||||
|
CHINESE_GLYPH(0x6700) // 最
|
||||||
|
CHINESE_GLYPH(0x6709) // 有
|
||||||
|
CHINESE_GLYPH(0x672C) // 本
|
||||||
|
CHINESE_GLYPH(0x673A) // 机
|
||||||
|
CHINESE_GLYPH(0x6761) // 条
|
||||||
|
CHINESE_GLYPH(0x6765) // 来
|
||||||
|
CHINESE_GLYPH(0x6843) // 桃
|
||||||
|
CHINESE_GLYPH(0x6885) // 梅
|
||||||
|
CHINESE_GLYPH(0x69FD) // 槽
|
||||||
|
CHINESE_GLYPH(0x6B21) // 次
|
||||||
|
CHINESE_GLYPH(0x6BCF) // 每
|
||||||
|
CHINESE_GLYPH(0x70B9) // 点
|
||||||
|
CHINESE_GLYPH(0x7247) // 片
|
||||||
|
CHINESE_GLYPH(0x7528) // 用
|
||||||
|
CHINESE_GLYPH(0x7531) // 由
|
||||||
|
CHINESE_GLYPH(0x7A7A) // 空
|
||||||
|
CHINESE_GLYPH(0x7B2C) // 第
|
||||||
|
CHINESE_GLYPH(0x7EA2) // 红
|
||||||
|
CHINESE_GLYPH(0x7EC4) // 组
|
||||||
|
CHINESE_GLYPH(0x80FD) // 能
|
||||||
|
CHINESE_GLYPH(0x8272) // 色
|
||||||
|
CHINESE_GLYPH(0x82B1) // 花
|
||||||
|
CHINESE_GLYPH(0x89C6) // 视
|
||||||
|
CHINESE_GLYPH(0x89E6) // 触
|
||||||
|
CHINESE_GLYPH(0x95F4) // 间
|
||||||
|
CHINESE_GLYPH(0x968F) // 随
|
||||||
|
CHINESE_GLYPH(0x9694) // 隔
|
||||||
|
CHINESE_GLYPH(0x987A) // 顺
|
||||||
|
CHINESE_GLYPH(0x989D) // 额
|
||||||
|
CHINESE_GLYPH(0x9ED1) // 黑
|
||||||
|
CHINESE_GLYPH(0x3001) // 、
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#ifndef GBALATRO_SYS8_FONT_H
|
#ifndef GBALATRO_SYS8_H
|
||||||
#define GBALATRO_SYS8_FONT_H
|
#define GBALATRO_SYS8_H
|
||||||
|
|
||||||
#include <tonc.h>
|
#include <tonc.h>
|
||||||
|
|
||||||
extern const TFont gbalatro_sys8Font;
|
extern const TFont gbalatro_sys8Font;
|
||||||
extern const unsigned int gbalatro_sys8Glyphs[192];
|
extern const unsigned int gbalatro_sys8Glyphs[];
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
#define MAIN_BG_CBB 1
|
#define MAIN_BG_CBB 1
|
||||||
#define TTE_SBB 30
|
#define TTE_SBB 30
|
||||||
#define TTE_CBB 0
|
#define TTE_CBB 0
|
||||||
#define AFFINE_BG_SBB 2
|
#define AFFINE_BG_SBB 4
|
||||||
#define AFFINE_BG_CBB 2
|
#define AFFINE_BG_CBB 2
|
||||||
#define PAL_ROW_LEN 16
|
#define PAL_ROW_LEN 16
|
||||||
#define NUM_PALETTES 16
|
#define NUM_PALETTES 16
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Append the Chinese Joker-description glyph subset to the project's 8x8 ASCII font."""
|
||||||
|
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
ASCII_FONT_HEIGHT = 48
|
||||||
|
CELL_SIZE = 8
|
||||||
|
GLYPHS_PER_ROW = 16
|
||||||
|
|
||||||
|
|
||||||
|
def read_codepoints(table_path: Path) -> list[int]:
|
||||||
|
table = table_path.read_text(encoding="utf-8")
|
||||||
|
return [int(value, 16) for value in re.findall(r"CHINESE_GLYPH\(0x([0-9A-Fa-f]+)\)", table)]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument("--font", required=True, help="Fusion Pixel Font 8px zh_hans TTF")
|
||||||
|
parser.add_argument("--table", default="include/def_chinese_glyph_table.h")
|
||||||
|
parser.add_argument("--image", default="font/gbalatro_sys8.png")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
image_path = Path(args.image)
|
||||||
|
base = Image.open(image_path).convert("RGB").crop((0, 0, 128, ASCII_FONT_HEIGHT))
|
||||||
|
codepoints = read_codepoints(Path(args.table))
|
||||||
|
extra_rows = (len(codepoints) + GLYPHS_PER_ROW - 1) // GLYPHS_PER_ROW
|
||||||
|
|
||||||
|
output = Image.new("RGB", (128, ASCII_FONT_HEIGHT + extra_rows * CELL_SIZE), "white")
|
||||||
|
output.paste(base, (0, 0))
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(output)
|
||||||
|
font = ImageFont.truetype(args.font, CELL_SIZE)
|
||||||
|
for index, codepoint in enumerate(codepoints):
|
||||||
|
x = (index % GLYPHS_PER_ROW) * CELL_SIZE
|
||||||
|
y = ASCII_FONT_HEIGHT + (index // GLYPHS_PER_ROW) * CELL_SIZE
|
||||||
|
draw.text((x, y - 1), chr(codepoint), font=font, fill="black")
|
||||||
|
|
||||||
|
output.save(image_path, optimize=True)
|
||||||
|
print(f"Appended {len(codepoints)} Chinese glyphs to {image_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -5,7 +5,6 @@ import argparse
|
|||||||
|
|
||||||
CHAR_WIDTH = 8
|
CHAR_WIDTH = 8
|
||||||
CHAR_HEIGHT = 8
|
CHAR_HEIGHT = 8
|
||||||
NUM_VERT_CHARS = 6
|
|
||||||
NUM_HORZ_CHARS = 16
|
NUM_HORZ_CHARS = 16
|
||||||
WORD_SIZE = 32
|
WORD_SIZE = 32
|
||||||
|
|
||||||
@@ -48,8 +47,14 @@ img = img.convert("P")
|
|||||||
img = img.convert('1')
|
img = img.convert('1')
|
||||||
img = ImageOps.invert(img)
|
img = ImageOps.invert(img)
|
||||||
|
|
||||||
|
if img.width != NUM_HORZ_CHARS * CHAR_WIDTH or img.height % CHAR_HEIGHT != 0:
|
||||||
|
raise ValueError("font image must be 128px wide and a multiple of 8px high")
|
||||||
|
|
||||||
|
num_vert_chars = img.height // CHAR_HEIGHT
|
||||||
|
num_chars = num_vert_chars * NUM_HORZ_CHARS
|
||||||
|
|
||||||
glyphs = []
|
glyphs = []
|
||||||
for char_y in range(NUM_VERT_CHARS):
|
for char_y in range(num_vert_chars):
|
||||||
for char_x in range(NUM_HORZ_CHARS):
|
for char_x in range(NUM_HORZ_CHARS):
|
||||||
# Get the 8x8 crop area at our current character in the image
|
# Get the 8x8 crop area at our current character in the image
|
||||||
pix_x = char_x * CHAR_WIDTH
|
pix_x = char_x * CHAR_WIDTH
|
||||||
@@ -86,7 +91,7 @@ with open(out_path, "w") as out:
|
|||||||
.global gbalatro_sys8Font
|
.global gbalatro_sys8Font
|
||||||
gbalatro_sys8Font:
|
gbalatro_sys8Font:
|
||||||
.word gbalatro_sys8Glyphs, 0, 0
|
.word gbalatro_sys8Glyphs, 0, 0
|
||||||
.hword 32, 96
|
.hword 32, %d
|
||||||
.byte 8, 8
|
.byte 8, 8
|
||||||
.byte 8, 8
|
.byte 8, 8
|
||||||
.hword 8
|
.hword 8
|
||||||
@@ -94,9 +99,9 @@ gbalatro_sys8Font:
|
|||||||
|
|
||||||
.section .rodata
|
.section .rodata
|
||||||
.align 2
|
.align 2
|
||||||
.global gbalatro_sys8Glyphs @ 768 bytes (192 unsigned ints)
|
.global gbalatro_sys8Glyphs
|
||||||
gbalatro_sys8Glyphs:
|
gbalatro_sys8Glyphs:
|
||||||
""")
|
""" % num_chars)
|
||||||
|
|
||||||
for i, glyph in enumerate(glyphs):
|
for i, glyph in enumerate(glyphs):
|
||||||
if first:
|
if first:
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#include "chinese_text.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define CHINESE_GLYPH_FIRST 128
|
||||||
|
|
||||||
|
static const uint32_t s_chinese_codepoints[] = {
|
||||||
|
#define CHINESE_GLYPH(codepoint) codepoint,
|
||||||
|
#include "def_chinese_glyph_table.h"
|
||||||
|
#undef CHINESE_GLYPH
|
||||||
|
};
|
||||||
|
|
||||||
|
_Static_assert(
|
||||||
|
sizeof(s_chinese_codepoints) / sizeof(s_chinese_codepoints[0]) <= 256 - CHINESE_GLYPH_FIRST,
|
||||||
|
"Chinese glyph table exceeds the single-byte encoding range"
|
||||||
|
);
|
||||||
|
|
||||||
|
static uint32_t decode_utf8_codepoint(const unsigned char** cursor)
|
||||||
|
{
|
||||||
|
const unsigned char* text = *cursor;
|
||||||
|
|
||||||
|
if (text[0] < 0x80)
|
||||||
|
{
|
||||||
|
*cursor += 1;
|
||||||
|
return text[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((text[0] & 0xE0) == 0xC0 && (text[1] & 0xC0) == 0x80)
|
||||||
|
{
|
||||||
|
*cursor += 2;
|
||||||
|
return ((uint32_t)(text[0] & 0x1F) << 6) | (text[1] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((text[0] & 0xF0) == 0xE0 && (text[1] & 0xC0) == 0x80 && (text[2] & 0xC0) == 0x80)
|
||||||
|
{
|
||||||
|
*cursor += 3;
|
||||||
|
return ((uint32_t)(text[0] & 0x0F) << 12) | ((uint32_t)(text[1] & 0x3F) << 6) |
|
||||||
|
(text[2] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
*cursor += 1;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t codepoint_to_glyph(uint32_t codepoint)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sizeof(s_chinese_codepoints) / sizeof(s_chinese_codepoints[0]); i++)
|
||||||
|
{
|
||||||
|
if (s_chinese_codepoints[i] == codepoint)
|
||||||
|
{
|
||||||
|
return CHINESE_GLYPH_FIRST + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t encode_tte_codepoint(uint32_t codepoint, char* output, size_t output_size)
|
||||||
|
{
|
||||||
|
if (codepoint < 0x80)
|
||||||
|
{
|
||||||
|
if (output_size < 1)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = (char)codepoint;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepoint <= 0x7FF)
|
||||||
|
{
|
||||||
|
if (output_size < 2)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = (char)(0xC0 | (codepoint >> 6));
|
||||||
|
output[1] = (char)(0x80 | (codepoint & 0x3F));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t chinese_text_encode(const char* utf8, char* output, size_t output_size)
|
||||||
|
{
|
||||||
|
if (utf8 == NULL || output == NULL || output_size == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned char* cursor = (const unsigned char*)utf8;
|
||||||
|
size_t output_len = 0;
|
||||||
|
|
||||||
|
while (*cursor != '\0')
|
||||||
|
{
|
||||||
|
uint32_t codepoint = decode_utf8_codepoint(&cursor);
|
||||||
|
uint32_t tte_codepoint = codepoint < 0x80 ? codepoint : codepoint_to_glyph(codepoint);
|
||||||
|
size_t encoded_size =
|
||||||
|
encode_tte_codepoint(tte_codepoint, output + output_len, output_size - output_len - 1);
|
||||||
|
|
||||||
|
if (encoded_size == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_len += encoded_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[output_len] = '\0';
|
||||||
|
return output_len;
|
||||||
|
}
|
||||||
@@ -25,6 +25,31 @@
|
|||||||
#include "soundbank.h"
|
#include "soundbank.h"
|
||||||
#include "soundbank_bin.h"
|
#include "soundbank_bin.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TTE_ASCII_GLYPH_COUNT = 96,
|
||||||
|
FONT_TILES_PER_ROW = 16,
|
||||||
|
CHARBLOCK_4BPP_TILE_COUNT = 512,
|
||||||
|
SCREENBLOCK_4BPP_TILE_COUNT = 64,
|
||||||
|
CHINESE_GLYPH_COUNT = 0
|
||||||
|
#define CHINESE_GLYPH(codepoint) +1
|
||||||
|
#include "def_chinese_glyph_table.h"
|
||||||
|
#undef CHINESE_GLYPH
|
||||||
|
,
|
||||||
|
TTE_FONT_TILE_COUNT =
|
||||||
|
TTE_ASCII_GLYPH_COUNT +
|
||||||
|
((CHINESE_GLYPH_COUNT + FONT_TILES_PER_ROW - 1) / FONT_TILES_PER_ROW) * FONT_TILES_PER_ROW,
|
||||||
|
TTE_FONT_FIRST_TILE = TTE_CBB * CHARBLOCK_4BPP_TILE_COUNT,
|
||||||
|
TTE_FONT_END_TILE = TTE_FONT_FIRST_TILE + TTE_FONT_TILE_COUNT,
|
||||||
|
AFFINE_BG_MAP_FIRST_TILE = AFFINE_BG_SBB * SCREENBLOCK_4BPP_TILE_COUNT,
|
||||||
|
AFFINE_BG_MAP_END_TILE = AFFINE_BG_MAP_FIRST_TILE + SCREENBLOCK_4BPP_TILE_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
_Static_assert(
|
||||||
|
TTE_FONT_END_TILE <= AFFINE_BG_MAP_FIRST_TILE || AFFINE_BG_MAP_END_TILE <= TTE_FONT_FIRST_TILE,
|
||||||
|
"TTE font tiles overlap the affine background map"
|
||||||
|
);
|
||||||
|
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
rng_init();
|
rng_init();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
CC := gcc
|
||||||
|
CFLAGS := -I../../include -I. \
|
||||||
|
-g -O3 -std=gnu23 -Wall -Werror
|
||||||
|
SRC := chinese_text_test.c \
|
||||||
|
../../source/chinese_text.c
|
||||||
|
OUT := build/chinese_text_test
|
||||||
|
|
||||||
|
$(OUT): $(SRC) | build
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OUT)
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#include "chinese_text.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const uint32_t CHINESE_CODEPOINTS[] = {
|
||||||
|
#define CHINESE_GLYPH(codepoint) codepoint,
|
||||||
|
#include "def_chinese_glyph_table.h"
|
||||||
|
#undef CHINESE_GLYPH
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t encode_utf8_codepoint(uint32_t codepoint, char output[4])
|
||||||
|
{
|
||||||
|
if (codepoint <= 0x7F)
|
||||||
|
{
|
||||||
|
output[0] = (char)codepoint;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (codepoint <= 0x7FF)
|
||||||
|
{
|
||||||
|
output[0] = (char)(0xC0 | (codepoint >> 6));
|
||||||
|
output[1] = (char)(0x80 | (codepoint & 0x3F));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = (char)(0xE0 | (codepoint >> 12));
|
||||||
|
output[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
|
||||||
|
output[2] = (char)(0x80 | (codepoint & 0x3F));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t decode_like_tonc(const unsigned char encoded[2])
|
||||||
|
{
|
||||||
|
return ((uint32_t)(encoded[0] & 0x1F) << 6) | (encoded[1] & 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_ascii_is_unchanged(void)
|
||||||
|
{
|
||||||
|
char output[32];
|
||||||
|
|
||||||
|
size_t output_len = chinese_text_encode("A: +20", output, sizeof(output));
|
||||||
|
|
||||||
|
assert(output_len == strlen("A: +20"));
|
||||||
|
assert(strcmp(output, "A: +20") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_supported_chinese_uses_extended_glyphs(void)
|
||||||
|
{
|
||||||
|
char output[8];
|
||||||
|
|
||||||
|
size_t output_len = chinese_text_encode("小丑牌", output, sizeof(output));
|
||||||
|
|
||||||
|
assert(output_len == 6);
|
||||||
|
assert((unsigned char)output[0] == 0xC2);
|
||||||
|
assert((unsigned char)output[1] == 0x80);
|
||||||
|
assert((unsigned char)output[2] == 0xC2);
|
||||||
|
assert((unsigned char)output[3] == 0x81);
|
||||||
|
assert((unsigned char)output[4] == 0xC2);
|
||||||
|
assert((unsigned char)output[5] == 0x82);
|
||||||
|
assert(output[6] == '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_unsupported_unicode_becomes_question_mark(void)
|
||||||
|
{
|
||||||
|
char output[8];
|
||||||
|
|
||||||
|
size_t output_len = chinese_text_encode("龘", output, sizeof(output));
|
||||||
|
|
||||||
|
assert(output_len == 1);
|
||||||
|
assert(strcmp(output, "?") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_all_custom_glyphs_round_trip_through_tonc_utf8(void)
|
||||||
|
{
|
||||||
|
size_t glyph_count = sizeof(CHINESE_CODEPOINTS) / sizeof(CHINESE_CODEPOINTS[0]);
|
||||||
|
assert(glyph_count <= 128);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < glyph_count; i++)
|
||||||
|
{
|
||||||
|
char input[4] = {'\0'};
|
||||||
|
size_t input_len = encode_utf8_codepoint(CHINESE_CODEPOINTS[i], input);
|
||||||
|
input[input_len] = '\0';
|
||||||
|
|
||||||
|
char output[4] = {'\0'};
|
||||||
|
size_t output_len = chinese_text_encode(input, output, sizeof(output));
|
||||||
|
uint32_t expected_glyph_codepoint = 128 + i;
|
||||||
|
|
||||||
|
assert(output_len == 2);
|
||||||
|
assert(((unsigned char)output[0] & 0xE0) == 0xC0);
|
||||||
|
assert(((unsigned char)output[1] & 0xC0) == 0x80);
|
||||||
|
assert(decode_like_tonc((const unsigned char*)output) == expected_glyph_codepoint);
|
||||||
|
assert(output[2] == '\0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_small_buffer_does_not_split_utf8_sequence(void)
|
||||||
|
{
|
||||||
|
char output[4] = {'x', 'x', 'x', 'x'};
|
||||||
|
|
||||||
|
size_t output_len = chinese_text_encode("小丑牌", output, sizeof(output));
|
||||||
|
|
||||||
|
assert(output_len == 2);
|
||||||
|
assert((unsigned char)output[0] == 0xC2);
|
||||||
|
assert((unsigned char)output[1] == 0x80);
|
||||||
|
assert(output[2] == '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
test_ascii_is_unchanged();
|
||||||
|
test_supported_chinese_uses_extended_glyphs();
|
||||||
|
test_unsupported_unicode_becomes_question_mark();
|
||||||
|
test_all_custom_glyphs_round_trip_through_tonc_utf8();
|
||||||
|
test_small_buffer_does_not_split_utf8_sequence();
|
||||||
|
|
||||||
|
printf("Chinese text tests passed.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ run_test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_test bitset
|
run_test bitset
|
||||||
|
run_test chinese_text
|
||||||
run_test list
|
run_test list
|
||||||
run_test pool
|
run_test pool
|
||||||
run_test util
|
run_test util
|
||||||
|
|||||||
Reference in New Issue
Block a user