当前位置: 首页 > news >正文

retro-go 1.45 编译及显示中文

        最近做了个使用 retro-go 的开源掌机 基于ESP32-S3的C19掌机(适配GBC外壳) - 立创开源硬件平台 ,做完后用提供的固件发现屏幕反显了,估计是屏幕型号不太对,随即自己拉 retro-go 官方库来编译,拉取的最新的 1.45 ,记录下适配的过程和解决的一些问题。

  1. 安装 esp-idf 5.2 ,retro-go 介绍可以用idf5.3 但我用5.3报错
  2. 拉取 retro-go ,在/components/retro-go/targets 下增加 一个target ,参考已经项目与自己硬件最接近版本,注意 key map 的设置格式
  3. 在/components/retro-go 下的 config.h 中 参考其它的target 将自己增加的target 引入进来

支持中文:

  1. 找一个中文的 ttf 可以用这个 https://github.com/TakWolf/fusion-pixel-font
  2. 用 font_converter.py 设置路径与字符范围及字体大小后生成c源文件
  3. 在 fonts.h 中增加生成的源文件内的变量名和设置枚举与 fonts 数组
  4. sdkconfig 中设置 CONFIG_FATFS_API_ENCODING_UTF_8=y 开启 fatfs 使用unicode 编码读取文件名

如果不开启 utf8 会导致 rg_utf8_get_codepoint 方法后台报 invalid utf-8 prefix,也就是无法正确得到文件名 。

参考:

Configuration Options Reference - ESP32-S3 - — ESP-IDF 编程指南 v5.5 文档

https://elm-chan.org/fsw/ff/doc/config.html#lfn_unicode

解决开启中文浏览列表会很卡的问题

        增加中文字体后且能成功显示中文字体后,会发现列表变得很卡,查看源码后知道其查询字库是一个个遍历的,字符数变多后肯定会卡,解决方法是字符数据定长或再加一个索引数组。不管哪种方法其本质都是希望能通过字符号+偏移量定位到具体字符数据,下面是主要代码:

typedef struct
{char name[16];uint8_t type;   // 0=monospace, 1=proportional ,2= location by mapuint8_t width;  // median width of glyphsuint8_t height; // height of tallest glyphsize_t  chars;  // glyph countconst uint32_t *map; //索引数组uint32_t map_len;//索引数组长度uint32_t map_start_code;//索引的第一个字符码uint8_t data[]; // stream of rg_font_glyph_t (end of list indicated by an entry with 0x0000 codepoint)
} rg_font_t;//rg_gui.c  get_glyph  增加 font->type == 2  的逻辑 小于等于255 直接查询,从map_start_code 开始使用索引
//   这只是方法的一部分
static size_t get_glyph(uint32_t *output, const rg_font_t *font, int points, int c)
{// Some glyphs are always zero widthif (!font || c == '\r' || c == '\n' || c == 0) // || c < 8 || c > 0xFFFF)return 0;if (points <= 0)points = font->height;const uint8_t *ptr = font->data;const rg_font_glyph_t *glyph = (rg_font_glyph_t *)ptr;if(font->type == 2){if (c <= 255){int times =0;while (glyph->code && glyph->code != c && times++ <=255){if (glyph->width != 0)ptr += (((glyph->width * glyph->height) - 1) / 8) + 1;ptr += sizeof(rg_font_glyph_t);glyph = (rg_font_glyph_t *)ptr;}}else if(c >= font->map_start_code){uint32_t map_index =  c - font->map_start_code;if (map_index < font->map_len){uint32_t data_index = font->map[map_index];glyph = (rg_font_glyph_t *)(ptr + data_index);}}}else{// for (size_t i = 0; i < font->chars && glyph->code && glyph->code != c; ++i)while (glyph->code && glyph->code != c){if (glyph->width != 0)ptr += (((glyph->width * glyph->height) - 1) / 8) + 1;ptr += sizeof(rg_font_glyph_t);glyph = (rg_font_glyph_t *)ptr;}}

 

        修改后的库:https://github.com/longxiangam/retro-go

       tools 下生成字库的 font_converter.py 脚本增加对索引的支持,使用这个脚本生成字库时选择生成 map 并设置 start code 生成的代码就会生成 索引数组。

from PIL import Image, ImageDraw, ImageFont
from tkinter import Tk, Label, Entry, StringVar, Button, Frame, Canvas, filedialog, ttk, Checkbutton, IntVar
import os
import re
import uuid################################ - Font format - ################################
#
# font:
# |
# ├── glyph_bitmap[] -> 8 bit array containing the bitmap data for all glyph
# |
# └── glyph_data[] -> struct that contains all the data to correctly draw the glyph
#
######################## - Explanation of glyph_bitmap[] - #######################
# First, let's see an example : '!'
#
# we are going to convert glyph_bitmap[] bytes to binary :
# 11111111,
# 11111111,
# 11000111,
# 11100000,
#
# then we rearrange them :
#  [3 bits wide]
#       111
#       111
#       111
# [9    111   We clearly reconize '!' character
# bits  111
# tall] 111
#       000
#       111
#       111
#       (000000)
#
# Second example with '0' :
# 0x30,0x04,0x07,0x09,0x00,0x07,
# 0x7D,0xFB,0xBF,0x7E,0xFD,0xFB,0xFF,0x7C,
#
# - width = 0x07 = 7
# - height = 0x09 = 9
# - data[n] = 0x7D,0xFB,0xBF,0x7E,0xFD,0xFB,0xFF,0x7C
#
# in binary :
# 1111101
# 11111011
# 10111111
# 1111110
# 11111101
# 11111011
# 11111111
# 1111100
#
# We see that everything is not aligned so we add zeros ON THE LEFT :
# ->01111101
#   11111011
#   10111111
# ->01111110
#   11111101
#   11111011
#   11111111
# ->01111100
#
# Next, we rearrange the bits :
#    [ 7 bits wide]
#       0111110
#       1111110
#       1110111
# [9    1110111
# bits  1110111     we can reconize '0' (if you squint a loooot)
# tall] 1110111
#       1110111
#       1111111
#       0111110
#       (0)
#
# And that's basically how characters are encoded using this tool# Example usage (defaults parameters)
list_char_ranges_init = "32-126, 160-255,19968-40959"
font_size_init = 12
map_start_code_init = "19968"  # Default map start codefont_path = ("arial.ttf")  # Replace with your TTF font path# Variables to track panning
start_x = 0
start_y = 0def get_char_list():list_char = []for intervals in list_char_ranges.get().split(','):first = intervals.split('-')[0]# we check if the user input is a single char or an intervaltry:second = intervals.split('-')[1]except IndexError:list_char.append(int(first))else:for char in range(int(first), int(second) + 1):list_char.append(char)return list_chardef find_bounding_box(image):pixels = image.load()width, height = image.sizex_min, y_min = width, heightx_max, y_max = 0, 0for y in range(height):for x in range(width):if pixels[x, y] >= 1:  # Looking for 'on' pixelsx_min = min(x_min, x)y_min = min(y_min, y)x_max = max(x_max, x)y_max = max(y_max, y)if x_min > x_max or y_min > y_max:  # No target pixels foundreturn Nonereturn (x_min, y_min, x_max+1, y_max+1)def load_ttf_font(font_path, font_size):# Load the TTF fontenforce_font_size = enforce_font_size_bool.get()pil_font = ImageFont.truetype(font_path, font_size)font_name = ' '.join(pil_font.getname())font_data = []for char_code in get_char_list():char = chr(char_code)image = Image.new("1", (font_size * 2, font_size * 2), 0) # generate mono bmp, 0 = black colordraw = ImageDraw.Draw(image)# Draw at pos 1 otherwise some glyphs are clipped. we remove the added offset belowdraw.text((1, 0), char, font=pil_font, fill=255)bbox = find_bounding_box(image)  # Get bounding boxif bbox is None: # control character / spacewidth, height = 0, 0offset_x, offset_y = 0, 0else:x0, y0, x1, y1 = bboxwidth, height = x1 - x0, y1 - y0offset_x, offset_y = x0, y0if offset_x:offset_x -= 1try: # Get the real glyph width including padding on the right that the box will removeadv_w = int(draw.textlength(char, font=pil_font))adv_w = max(adv_w, width + offset_x)except:adv_w = width + offset_x# Shift or crop glyphs that would be drawn beyond font_size. Most glyphs are not affected by this.# If enforce_font_size is false, then max_height will be calculated at the end and the font might# be taller than requested.if enforce_font_size and offset_y + height > font_size:print(f"    font_size exceeded: {offset_y+height}")if font_size - height >= 0:offset_y = font_size - heightelse:offset_y = 0height = font_size# Extract bitmap datacropped_image = image.crop(bbox)bitmap = []row = 0i = 0for y in range(height):for x in range(width):if i == 8:bitmap.append(row)row = 0i = 0pixel = 1 if cropped_image.getpixel((x, y)) else 0row = (row << 1) | pixeli += 1bitmap.append(row << 8-i) # to "fill" with zero the remaining empty bitsbitmap = bitmap[0:int((width * height + 7) / 8)]# Create glyph entryglyph_data = {"char_code": char_code,"ofs_y": int(offset_y),"box_w": int(width),"box_h": int(height),"ofs_x": int(offset_x),"adv_w": int(adv_w),"bitmap": bitmap,}font_data.append(glyph_data)# The font render glyphs at font_size but they can shift them up or down which will cause the max_height# to exceed font_size. It's not desirable to remove the padding entirely (the "enforce" option above), # but there are some things we can do to reduce the discrepency without affecting the look.max_height = max(g["ofs_y"] + g["box_h"] for g in font_data)if max_height > font_size:min_ofs_y = min((g["ofs_y"] if g["box_h"] > 0 else 1000) for g in font_data)for key, glyph in enumerate(font_data):offset = glyph["ofs_y"]# If there's a consistent excess of top padding across all glyphs, we can remove itif min_ofs_y > 0 and offset >= min_ofs_y:offset -= min_ofs_y# In some fonts like Vera and DejaVu we can shift _ and | to gain an extra pixelif chr(glyph["char_code"]) in ["_", "|"] and offset + glyph["box_h"] > font_size and offset > 0:offset -= 1font_data[key]["ofs_y"] = offsetmax_height = max(g["ofs_y"] + g["box_h"] for g in font_data)print(f"Glyphs: {len(font_data)}, font_size: {font_size}, max_height: {max_height}")return (font_name, font_size, font_data)def load_c_font(file_path):# Load the C fontfont_name = "Unknown"font_size = 0font_data = []with open(file_path, 'r', encoding='UTF-8') as file:text = file.read()text = re.sub('//.*?$|/\*.*?\*/', '', text, flags=re.S|re.MULTILINE)text = re.sub('[\n\r\t\s]+', ' ', text)# FIXME: Handle parse errors...if m := re.search('\.name\s*=\s*"(.+)",', text):font_name = m.group(1)if m := re.search('\.height\s*=\s*(\d+),', text):font_size = int(m.group(1))if m := re.search('\.data\s*=\s*\{(.+?)\}', text):hexdata = [int(h, base=16) for h in re.findall('0x[0-9A-Fa-f]{2}', text)]while len(hexdata):char_code = hexdata[0] | (hexdata[1] << 8)if not char_code:breakofs_y = hexdata[2]box_w = hexdata[3]box_h = hexdata[4]ofs_x = hexdata[5]adv_w = hexdata[6]bitmap = hexdata[7:int((box_w * box_h + 7) / 8) + 7]glyph_data = {"char_code": char_code,"ofs_y": ofs_y,"box_w": box_w,"box_h": box_h,"ofs_x": ofs_x,"adv_w": adv_w,"bitmap": bitmap,}font_data.append(glyph_data)hexdata = hexdata[7 + len(bitmap):]return (font_name, font_size, font_data)def generate_font_data():if font_path.endswith(".c"):font_name, font_size, font_data = load_c_font(font_path)else:font_name, font_size, font_data = load_ttf_font(font_path, int(font_height_input.get()))window.title(f"Font preview: {font_name} {font_size}")font_height_input.set(font_size)max_height = max(font_size, max(g["ofs_y"] + g["box_h"] for g in font_data))bounding_box = bounding_box_bool.get()canvas.delete("all")offset_x_1 = 1offset_y_1 = 1for glyph_data in font_data:offset_y = glyph_data["ofs_y"]width = glyph_data["box_w"]height = glyph_data["box_h"]offset_x = glyph_data["ofs_x"]adv_w = glyph_data["adv_w"]if offset_x_1+adv_w+1 > canva_width:offset_x_1 = 1offset_y_1 += max_height + 1byte_index = 0byte_value = 0bit_index = 0for y in range(height):for x in range(width):if bit_index == 0:byte_value = glyph_data["bitmap"][byte_index]byte_index += 1if byte_value & (1 << 7-bit_index):canvas.create_rectangle((x+offset_x_1+offset_x)*p_size, (y+offset_y_1+offset_y)*p_size, (x+offset_x_1+offset_x)*p_size+p_size, (y+offset_y_1+offset_y)*p_size+p_size,fill="white")bit_index += 1bit_index %= 8if bounding_box:canvas.create_rectangle((offset_x_1+offset_x)*p_size, (offset_y_1+offset_y)*p_size, (width+offset_x_1+offset_x)*p_size, (height+offset_y_1+offset_y)*p_size, width=1, outline="red", fill='')canvas.create_rectangle((offset_x_1)*p_size, (offset_y_1)*p_size, (offset_x_1+adv_w)*p_size, (offset_y_1+max_height)*p_size, width=1, outline='blue', fill='')offset_x_1 += adv_w + 1return (font_name, font_size, font_data)def save_font_data():font_name, font_size, font_data = generate_font_data()filename = filedialog.asksaveasfilename(title='Save Font',initialdir=os.getcwd(),initialfile=f"{font_name.replace('-', '_').replace(' ', '')}{font_size}",defaultextension=".c",filetypes=(('Retro-Go Font', '*.c'), ('All files', '*.*')))if filename:with open(filename, 'w', encoding='UTF-8') as f:f.write(generate_c_font(font_name, font_size, font_data))def generate_c_font(font_name, font_size, font_data):normalized_name = f"{font_name.replace('-', '_').replace(' ', '')}{font_size}"max_height = max(font_size, max(g["ofs_y"] + g["box_h"] for g in font_data))memory_usage = sum(len(g["bitmap"]) + 7 for g in font_data)  # 7 bytes for header# Calculate map data if enabledgenerate_map = generate_map_bool.get()map_start_code = int(map_start_code_input.get()) if generate_map else 0map_data = []if generate_map:# Find the range for the mapchar_codes = [g["char_code"] for g in font_data]max_char = max(char_codes)map_size = max_char - map_start_code + 1map_data = [0] * map_size  # Initialize with zerosdata_index = 0for glyph in font_data:map_index = glyph["char_code"] - map_start_codeif 0 <= map_index < map_size:map_data[map_index] = data_indexdata_index += 7 + len(glyph["bitmap"])  # 7 bytes header + bitmap sizememory_usage += map_size * 4  # Each map entry is 4 bytes (uint32_t)file_data = "#include \"../rg_gui.h\"\n\n"file_data += "// File generated with font_converter.py (https://github.com/ducalex/retro-go/tree/dev/tools)\n\n"file_data += f"// Font           : {font_name}\n"file_data += f"// Point Size     : {font_size}\n"file_data += f"// Memory usage   : {memory_usage} bytes\n"file_data += f"// # characters   : {len(font_data)}\n"if generate_map:file_data += f"// Map start code : {map_start_code}\n"file_data += f"// Map size       : {len(map_data)} entries\n"file_data += "\n"font_type = 1;if generate_map:file_data += f"static const uint32_t font_{normalized_name}_map[] = {{\n"for i in range(0, len(map_data), 8):line = map_data[i:i+8]file_data += "    " + ", ".join([f"0x{val:04X}" for val in line]) + ",\n"file_data += "};\n\n"font_type = 2;file_data += f"const rg_font_t font_{normalized_name} = {{\n"file_data += f"    .name = \"{font_name}\",\n"file_data += f"    .type = {font_type},\n"file_data += f"    .width = 0,\n"file_data += f"    .height = {max_height},\n"file_data += f"    .chars = {len(font_data)},\n"if generate_map:file_data += f"    .map_start_code = {map_start_code},\n"file_data += f"    .map = font_{normalized_name}_map,\n"file_data += f"    .map_len = sizeof(font_{normalized_name}_map) / 4,\n"file_data += f"    .data = {{\n"for glyph in font_data:char_code = glyph['char_code']header_data = [char_code & 0xFF, char_code >> 8, glyph['ofs_y'], glyph['box_w'],glyph['box_h'], glyph['ofs_x'], glyph['adv_w']]file_data += f"        /* U+{char_code:04X} '{chr(char_code)}' */\n        "file_data += ", ".join([f"0x{byte:02X}" for byte in header_data])file_data += f",\n        "if len(glyph["bitmap"]) > 0:file_data += ", ".join([f"0x{byte:02X}" for byte in glyph["bitmap"]])file_data += f","file_data += "\n"file_data += "\n"file_data += "        // Terminator\n"file_data += "        0x00, 0x00,\n"file_data += "    },\n"file_data += "};\n"return file_datadef select_file():filename = filedialog.askopenfilename(title='Load Font',initialdir=os.getcwd(),filetypes=(('True Type Font', '*.ttf'), ('Retro-Go Font', '*.c'), ('All files', '*.*')))if filename:global font_pathfont_path = filenamegenerate_font_data()# Function to zoom in and out on the canvas
def zoom(event):scale = 1.0if event.delta > 0:  # Scroll up to zoom inscale = 1.2elif event.delta < 0:  # Scroll down to zoom outscale = 0.8# Get the canvas size and adjust scale based on cursor positioncanvas.scale("all", event.x, event.y, scale, scale)# Update the scroll region to reflect the new scalecanvas.configure(scrollregion=canvas.bbox("all"))def start_pan(event):global start_x, start_y# Record the current mouse positionstart_x = event.xstart_y = event.ydef pan_canvas(event):global start_x, start_y# Calculate the distance moveddx = start_x - event.xdy = start_y - event.y# Scroll the canvascanvas.move("all", -dx, -dy)# Update the starting positionstart_x = event.xstart_y = event.yif __name__ == "__main__":window = Tk()window.title("Retro-Go Font Converter")# Get screen width and heightscreen_width = window.winfo_screenwidth()screen_height = window.winfo_screenheight()# Set the window size to fill the entire screenwindow.geometry(f"{screen_width}x{screen_height}")p_size = 8 # pixel size on the renderercanva_width = screen_width//p_sizecanva_height = screen_height//p_size-16frame = Frame(window)frame.pack(anchor="center", padx=10, pady=2)# choose font button (file picker)choose_font_button = ttk.Button(frame, text='Choose font', command=select_file)choose_font_button.pack(side="left", padx=5)# Label and Entry for Font heightLabel(frame, text="Font height").pack(side="left", padx=5)font_height_input = StringVar(value=str(font_size_init))Entry(frame, textvariable=font_height_input, width=4).pack(side="left", padx=5)# Variable to hold the state of the checkboxenforce_font_size_bool = IntVar()  # 0 for unchecked, 1 for checkedCheckbutton(frame, text="Enforce size", variable=enforce_font_size_bool).pack(side="left", padx=5)# Label and Entry for Char ranges to includeLabel(frame, text="Ranges to include").pack(side="left", padx=5)list_char_ranges = StringVar(value=str(list_char_ranges_init))Entry(frame, textvariable=list_char_ranges, width=30).pack(side="left", padx=5)# Variable to hold the state of the checkboxbounding_box_bool = IntVar(value=1)  # 0 for unchecked, 1 for checkedCheckbutton(frame, text="Bounding box", variable=bounding_box_bool).pack(side="left", padx=10)# Variable to hold the state of the map generation checkboxgenerate_map_bool = IntVar()  # 0 for unchecked, 1 for checkedCheckbutton(frame, text="Generate map", variable=generate_map_bool).pack(side="left", padx=5)# Label and Entry for Map start codeLabel(frame, text="Map start code").pack(side="left", padx=5)map_start_code_input = StringVar(value=str(map_start_code_init))Entry(frame, textvariable=map_start_code_input, width=6).pack(side="left", padx=5)# Button to launch the font generation functionb1 = Button(frame, text="Preview", width=14, height=2, background="blue", foreground="white", command=generate_font_data)b1.pack(side="left", padx=5)# Button to launch the font exporting functionb1 = Button(frame, text="Save", width=14, height=2, background="blue", foreground="white", command=save_font_data)b1.pack(side="left", padx=5)frame = Frame(window).pack(anchor="w", padx=2, pady=2)canvas = Canvas(frame, width=canva_width*p_size, height=canva_height*p_size, bg="black")canvas.configure(scrollregion=(0, 0, canva_width*p_size, canva_height*p_size))canvas.bind("<MouseWheel>", zoom)canvas.bind("<ButtonPress-1>", start_pan)  # Start panningcanvas.bind("<B1-Motion>",pan_canvas)canvas.focus_set()canvas.pack(fill="both", expand=True)window.mainloop()

 

http://www.dtcms.com/a/311791.html

相关文章:

  • 联合索引全解析:一棵树,撑起查询的半边天
  • 【01】OpenCV C#——C#开发环境OpenCvSharp 环境配置 工程搭建 及代码测试
  • 【QT】Qt信号与槽机制详解信号和槽的本质自定义信号和槽带参数的信号和槽
  • 计算机网络:为什么IPv6没有选择使用点分十进制
  • 数据结构初学习、单向链表
  • Python 字典为什么查询高效
  • 数据结构---概念、数据与数据之间的关系(逻辑结构、物理结构)、基本功能、数据结构内容、单向链表(该奶奶、对象、应用)
  • SpringBoot3.x入门到精通系列:2.2 依赖注入与IoC容器
  • Spring AI MCP 服务端
  • 边缘智能网关在水务行业中的应用—龙兴物联
  • 沿街晾晒识别准确率↑32%:陌讯多模态融合算法实战解析
  • P2415 集合求和
  • Docker 镜像打包为 ZIP 文件便于分享和转发
  • linux ext4缩容home,扩容根目录
  • 【Kubernetes】Secret配置管理,安全管理敏感配置
  • Effective C++ 条款17:以独立语句将newed对象置入智能指针
  • Python 程序设计讲义(50):Python 的可迭代对象与迭代器
  • Flutter基础知识
  • SpringBoot与TurboGears2跨栈、整合AI服务、智能客服路由系统整合实战
  • SpringCloud学习第一季-4
  • 第15届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2024年3月10日真题
  • 17、原坐标变换和逆变换在实战中用法
  • 无人机数字图传技术的前沿探索与应用
  • 【昇腾推理PaddleOCR】生产级部署方式
  • 机器学习实战:KNN算法全解析 - 从原理到创新应用
  • LangChain框架入门05:输出解析器使用技巧
  • SpringBoot 服务器配置
  • Json简单的实现
  • 【Android】RecyclerView实现新闻列表布局(1)适配器使用相关问题
  • 【Leetcode】2561. 重排水果