ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

V 语言 x.ttf 字体模块实战:TTF 加载、位图渲染与 gg/Sokol 屏幕文本绘制

V 语言 x.ttf 字体模块实战:TTF 加载、位图渲染与 gg/Sokol 屏幕文本绘制 V 语言 x.ttf 字体模块实战TTF 加载、位图渲染与 gg/Sokol 屏幕文本绘制【免费下载链接】vSimple, fast, safe, compiled language for developing maintainable software. Compiles itself in 1s with zero library dependencies. Supports automatic C V translation. https://vlang.io项目地址: https://gitcode.com/GitHub_Trending/v/v本文以 V 语言标准库实验模块vlib/x/ttf为对象系统讲解其两大核心能力TTF 字体文件加载与解析、文本渲染CPU 位图渲染与基于 sokol/gg 的屏幕渲染。读完本文你将掌握如何在 V 项目中从磁盘加载任意 TTF 字体、提取字体元信息、将文本渲染为 RGBA 位图并导出 PPM 图片以及在 gg 图形应用中实现实时文本含多行文本块、对齐、两端对齐与旋转的完整方案。模块概述加载 渲染的双任务设计x.ttf是一个纯 V 语言实现的 TrueType 字体工具模块设计上只做两件主任务加载字体文件Load the font file使用 TTF 字体渲染文本Render text using a TTF font渲染系统可以是单个也可以是多个并存——例如同一时刻可以同时存在一个 CPU 位图渲染器和一个硬件加速渲染器如 sokol/gg。目前模块内所有渲染都发生在 CPU 上sokol 仅被用来把渲染好的文本贴图绘制到屏幕上这一点在 render_sokol/cpu.v 的注释中也有明确说明At the present time all the rendering are made on the CPU, sokol is used only to draw the rendered text to the screen。模块整体文件结构如下vlib/x/ttf/ ├── README.md # 模块文档本文主体 ├── ttf.v # TTF 文件解析核心表结构、cmap、glyph 读取 ├── common.v # 公共枚举、颜色工具、PPM 导出等 ├── render_bmp.v # BitMap 位图渲染器 ├── text_block.v # 多行文本块渲染对齐/两端对齐 ├── render_sokol/ │ └── cpu.v # sokol/gg 屏幕渲染器 TTF_render_Sokol ├── ttf_test.v # 单元测试内嵌字体二进制数据 └── ttf_test_data.bin # 测试用字体数据TTF 加载器从文件到内存中的字体模型加载器部分的职责很单一读取一个 TTF 文件并预处理所有加载的数据为后续渲染阶段做好铺垫。三步加载一个字体从磁盘加载一个字体的最小代码如下加载完成后即可用于渲染mut ttf_font : ttf.TTF_File{} ttf_font.buf os.read_bytes(arial.ttf) or { panic(err) } ttf_font.init()注意字体必须以 RAM 缓冲区[]u8字节数组的形式传给TTF_File即先通过os.read_bytes把整个文件读进内存再调用init()解析。经过这三步arial.ttf已被加载并完成解析只要它是一个合法 TTF 字体就随时可以投入渲染。从源码看init()方法ttf.v依次执行了 7 个表的解析流程pub fn (mut tf TTF_File) init() { tf.read_offset_tables() // 读取字体表目录offset table校验各表 checksum tf.read_head_table() // head 表版本、em 单位、全局边界框等 tf.read_name_table() // name 表字族、子族、全名、PostScript 名 tf.read_cmap_table() // cmap 表字符码 - glyph 索引映射 tf.read_hhea_table() // hhea 表水平度量ascent/descent/line_gap 等 tf.read_kern_table() // kern 表字距调整kerning tf.read_panose_table() // OS/2 表中的 Panose 分类信息 tf.length tf.glyph_count() }其中read_offset_tables会遍历字体头部声明的所有表head、name、cmap、hhea、hmtx、maxp、loca、glyf、kern、OS/2等并把每张表的偏移量、长度、校验和记录到tf.tables这个 map 中——后续所有表级读取都通过这个 map 定位。注意head表除外其余各表都会在校验时用calc_checksum与文件中记录的 checksum 比对见 ttf.v非法或损坏的字体文件会在这一步被拦截。用 get_info_string 快速查看字体信息字体加载并解析完成后可以调用get_info_string以字符串形式快速获取字体信息println(ttf_font.get_info_string())输出效果如下以 Arial 为例----- Font Info ----- font_family : Arial font_sub_family : Normal full_name : Arial postscript_name : ArialMT version : 1 font_revision : 5.06 magic_number : 5f0f3cf5 flags : 81b created unixTS : 649950890 modified unixTS : 1282151447 units_per_em : 2048 box : [x_min:-1361, y_min:-665, x_Max:4096, y_Max:2060] mac_style : 0 -----------------------该方法的实现位于 ttf.v直接读取TTF_File中已解析好的字段拼装字符串。其中font_family/font_sub_family/full_name/postscript_name来自 name 表的 name_id 1/2/4/6见read_name_tablemagic_number是 TTF 规范要求的固定魔数0x5f0f3cf5加载时若不一致会直接 assert 失败units_per_em每 em 的像素单位是后续缩放计算的核心参数boxx_min/y_min/x_max/y_max是全局字形边界框用于计算基线baseline与行高。多字体并发加载TTF_File是一个独立的可复制结构体加载完成后它内部已包含字体数据与解析结果glyph 缓存、cmap、kern 表等。因此多个字体可以同时加载互不干扰例如在下面的 Sokol 渲染示例中font_paths数组会循环加载多个字体并分别存入app.tf切片每个字体可绑定独立的渲染器。从结构设计看ttf.vTTF_File还内置了glyph_cache map[int]Glyph字形缓存read_glyph命中缓存时直接返回避免重复解析glyf表。TTF 位图渲染draw_text 与 draw_text_block位图渲染在 CPU 上完成输出是一块 RGBA 内存缓冲区。先看一个最简的加载 打印字体信息程序import os import x.ttf fn main() { mut ttf_font : ttf.TTF_File{} ttf_font.buf os.read_bytes(arial.ttf) or { panic(err) } ttf_font.init() // print font info println(ttf_font.get_info_string()) }基础概念缩放比例scale位图渲染前必须先计算字体缩放比例。模块给出的标准公式README 与render_sokol/cpu.v中均有注释scaler : (font_size * device_dpi) / (72dpi * em_unit)即scale f32(font_size * device_dpi) / f32(72 * ttf_font.units_per_em)。默认device_dpi 72、字号单位是 pointsunits_per_em从字体的 head 表读出。以 Arialunits_per_em2048、32pt、72dpi 为例scale ≈ 32 * 72 / (72 * 2048) ≈ 0.015625。draw_text单行文本的低层渲染draw_text负责把简单字符串不带缩进等复杂排版绘制到位图上。以下示例完整演示了分配位图缓冲 → 初始化填充器 → 清屏 → 定位 → 绘制 → 导出 PPM的完整流程import os import x.ttf fn main() { mut ttf_font : ttf.TTF_File{} ttf_font.buf os.read_bytes(arial.ttf) or { panic(err) } ttf_font.init() // print font info println(ttf_font.get_info_string()) bmp_width : 200 bmp_height : 64 bmp_layers : 4 // number of planes for an RGBA buffer // memory size of the buffer bmp_size : bmp_width * bmp_height * bmp_layers font_size : 32 // font size in points device_dpi : 72 // default screen DPI // Formula for scale calculation // scaler : (font_size * device dpi) / (72dpi * em_unit) scale : f32(font_size * device_dpi) / f32(72 * ttf_font.units_per_em) // height of the font to use in the buffer to separate the lines y_base : int((ttf_font.y_max - ttf_font.y_min) * scale) // declare the bitmap struct mut bmp : ttf.BitMap{ tf: ttf_font buf: unsafe { malloc(bmp_size) } buf_size: bmp_size width: bmp_width height: bmp_height bp: bmp_layers color: 0x000000_FF // RGBA black scale: scale } bmp.init_filler() bmp.clear() bmp.set_pos(10, y_base) bmp.draw_text(Test Text!) bmp.save_as_ppm(test.ppm) }这是文本的低层渲染把文字画到一块位图上再把位图保存为.ppm文件。注意这里的渲染是原始渲染raw rendering没有任何后过滤post-filtering或其他处理。低层渲染意味着你必须自行管理所有配套工作分配与释放内存、计算字符尺寸如get_bbox、get_chars_bbox等。只有当你想在文本渲染上实现特殊效果时才建议使用这一层 API。渲染样式通过BitMap结构体中的style字段指定Style枚举定义在 common.venum Style { outline outline_aliased filled // default style raw }各样式在 render_bmp.v 的line()中产生分支.filled默认用抗锯齿边线aline 填充扫描线fline组合填充字形.outline_aliased只画抗锯齿轮廓.raw只走填充扫描线不做抗锯齿.outline走 Bresenham 式逐点描边。此外BitMap还提供set_rotation(angle)按弧度旋转文本见 render_bmp.v以及tr_matrix/ch_matrix两套 3x3 变换矩阵分别作用于整段文本与单个字形。draw_text的核心执行路径render_bmp.v为对每个字符调用map_code查 glyph 索引 →next_kern读取字距调整 →get_horizontal_metrics取水平度量 →draw_glyph用直线 二次贝塞尔曲线quadratic填充轮廓 → 游标前进。字符未命中glyph 索引为 0时绘制一个notdef占位框draw_notdef_glyph。draw_text_block多行、缩进与两端对齐draw_text_block可以在位图内绘制一个两端对齐justified且带缩进的多行文本块import os import x.ttf fn main() { mut ttf_font : ttf.TTF_File{} ttf_font.buf os.read_bytes(arial.ttf) or { panic(err) } ttf_font.init() // print font info println(ttf_font.get_info_string()) bmp_width : 200 bmp_height : 200 bmp_layers : 4 // number of planes for an RGBA buffer // memory size of the buffer bmp_size : bmp_width * bmp_height * bmp_layers font_size : 32 // font size in points device_dpi : 72 // default screen DPI // Formula for scale calculation // scaler : (font_size * device dpi) / (72dpi * em_unit) scale : f32(font_size * device_dpi) / f32(72 * ttf_font.units_per_em) // height of the font to use in the buffer to separate the lines y_base : int((ttf_font.y_max - ttf_font.y_min) * scale) text : Today it is a good day! Tomorrow Im not so sure :( But Vwill prevail for sure, V is the way!! òàèìò!£$% // declare the bitmap struct mut bmp : ttf.BitMap{ tf: ttf_font buf: malloc(bmp_size) buf_size: bmp_size width: bmp_width height: bmp_height bp: bmp_layers color: 0x000000_FF // RGBA black scale: scale } bmp.init_filler() bmp.clear() bmp.justify true bmp.align .left bmp.draw_text_block(text, x: 0, y: 0, w: bmp_width - 20, h: bmp_height) bmp.save_as_ppm(test.ppm) }文本块由一个Text_block结构体定义见 text_block.vstruct Text_block { x int // x position of the left high corner y int // y position of the left high corner w int // width of the text block h int // height of the text block cut_lines bool true // force to cut the line if the length is over the text block width }字段含义x、y为文本块左上角坐标w、h为块宽高cut_lines默认true表示当行内容超过块宽时强制断行。draw_text_block会使用BitMap上的以下渲染字段style Style .filled // default style align Text_align .left // default text align justify bool // justify text flag, default deactivated justify_fill_ratio f32 0.5 // justify fill ratio, if the ratio of the filled // row is of this then justify the textalign为Text_align枚举common.v可取.left/.center/.right/.justify决定行在块内的水平位置justify为两端对齐开关默认关闭justify_fill_ratio默认 0.5是两端对齐触发阈值只有当某行实际宽度与块宽的比值 ≥ 该值时这行才会被两端对齐避免在内容过少的短行上强行拉伸空格造成怪异间距。这些参数都可以按需修改以获得期望的渲染效果。其底层机制在 text_block.v 的get_justify_space_cw中把块宽与行宽的差值delta均摊到该行的每个空格上得到每个空格需要额外增加的宽度再换算成空格字宽倍数。draw_text_block对每行先测宽get_bbox超宽时按空格位置逐词回退尝试断行直到该子串宽度落入块宽内然后按对齐方式计算left_offset后调用draw_text落字见 text_block.v。TTF Sokol 渲染把文本画到 gg 窗口sokol 渲染器基于位图渲染生成文字再借助gg的绘图函数把文字贴到屏幕上。相比裸位图渲染它在 gg 应用中更易用不需要手工管理分配/释放、计算字符尺寸等琐事。使用要点每个要渲染的文本都需要一个独立的渲染器声明之后可以随时修改换内容、换字号、换颜色、旋转等。以下是完整的TTF_render_Sokol使用示例来自 README与 examples/ttf_font/example_ttf.v 同源import gg import sokol.sapp import sokol.sgl import sokol.gfx import x.ttf import os const win_width 600 const win_height 700 const bg_color gg.white const font_paths [ arial.ttf, ] struct App_data { pub mut: gg gg.Context unsafe { nil } sg_img gfx.Image init_flag bool frame_c int tf []ttf.TTF_File ttf_render []ttf.TTF_render_Sokol } fn my_init(mut app App_data) { app.init_flag true } fn draw_frame(mut app App_data) { cframe_txt : Current Frame: ${app.frame_c} app.gg.begin() sgl.defaults() sgl.matrix_mode_projection() sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) // draw text only if the app is already initialized if app.init_flag true { // update the text mut txt1 : app.ttf_render[0] txt1.destroy_texture() txt1.create_text(cframe_txt, 43) txt1.create_texture() txt1.draw_text_bmp(app.gg, 30, 60) } app.frame_c app.gg.end() } fn main() { mut app : App_data{} app.gg gg.new_context( width: win_width height: win_height create_window: true window_title: Test TTF module user_data: app bg_color: bg_color frame_fn: draw_frame init_fn: my_init ) // load TTF fonts for font_path in font_paths { mut tf : ttf.TTF_File{} tf.buf os.read_bytes(font_path) or { panic(err) } println(TrueTypeFont file [${font_path}] len: ${tf.buf.len}) tf.init() println(tf.get_info_string()) app.tf tf } // TTF render 0 Frame counter app.ttf_render ttf.TTF_render_Sokol{ bmp: ttf.BitMap{ tf: app.tf[0] buf: unsafe { malloc(32000000) } buf_size: (32000000) color: 0xFF0000FF // style: .raw } } app.gg.run() }注意BitMap.buf预分配了一块较大的缓冲示例中为 32 MBcreate_text会根据文本实际尺寸自动在需要时重新分配render_sokol/cpu.v的create_text中若sz buf_size会free旧缓冲并malloc_noscan新缓冲。渲染出的文本通过draw_text_bmp(app.gg, x, y)以纹理四边形quad形式绘制到屏幕上绘制时还会应用BitMap.angle旋转角在draw_text_bmp中构造旋转矩阵见 render_sokol/cpu.v。TTF_render_Sokolrender_sokol/cpu.v关键字段与方法字段/方法说明bmp ttf.BitMap底层位图渲染器所有绘制都委托给它scale_reduct f32 2.0CPU 纹理的超采样倍数用于过滤后缩小提升文字边缘质量device_dpi int 72设备 DPI参与缩放公式计算create_text(in_txt, in_font_size)以指定字号把单行文本渲染进内部位图create_text_block(in_txt, in_w, in_h, in_font_size)以指定字号把多行文本块渲染进内部位图create_texture()把当前位图内容上传为 sokol 纹理gfx.make_imagegfx.make_sampler线性过滤、边缘 clampupdate_text_texture()用当前位图状态原地更新纹理仅当纹理以.dynamic方式创建时使用destroy_texture()销毁内部 sokol 纹理与采样器draw_text_bmp(ctx, x, y)把纹理四边形绘制到当前 sokol 管线多渲染器与动态文本的典型模式仓库中 examples/ttf_font/example_ttf.v 展示了更完整的用法同时声明 3 个渲染器——帧计数器文本、多行文本块、鼠标坐标文本每帧或隔帧先destroy_texture→create_text/create_text_block→create_texture更新内容再绘制从而实现动态刷新文本文本块渲染器还演示了通过切换bmp.justify与bmp.align.left/.right实时改变排版以及通过bmp.color ttf.color_multiply(...)改变文字颜色。示例中注释掉的代码还展示了文本旋转txt1.bmp.angle 3.141592 / 180 * f32(app.frame_c % 360) txt1.draw_text_bmp(app.gg, 300, 350)另一个示例 examples/ttf_font/draw_static_text.v 支持通过命令行参数指定自定义字体与文本文件v run draw_static_text.v [FONT_PATH] [TEXT_FILE_PATH]它会把文件内容渲染成多行文本块并显示在窗口内适合快速验证任意 TTF 字体和任意文本的渲染效果示例默认字体位于 examples/assets/fonts。测试与验证ttf_test.v 的像素级回归x.ttf自带单元测试 ttf_test.v验证方式值得借鉴测试字体被$embed_file(ttf_test_data.bin)内嵌进测试二进制Qarmic_sans_Abridged.ttf的裁剪版测试用 64×32、RGBA 4 通道的位图以 20pt/72dpi 渲染Test Text然后与test_data中预先固化的一组十六进制像素数据逐字节比对assert ram_buf.len test_buf.len后逐元素断言实现像素级回归。测试文件头部注释给出了重新生成基准数据的方法v -d create_data vlib/x/ttf/ttf_test.v该命令会用真实的Qarmic_sans_Abridged.ttf重新渲染并通过bmp.save_as_ppm(test_ttf.ppm)与bmp.save_raw_data(test_ttf.bin)输出基准位图与原始像素供后续比对。这意味着任何对渲染逻辑的修改都必须保持逐像素输出不变否则测试立即失败——这也为位图渲染的正确性提供了强保证。总结如何选择渲染层级场景推荐 API复杂度命令行工具加载字体、导出元信息TTF_Fileinitget_info_string低生成静态位图如 PPM 图片、离屏文本BitMapdraw_text/draw_text_blocksave_as_ppm中需自行管理内存与尺寸gg 窗口中的实时/动态文本TTF_render_Sokolcreate_text/create_text_block/draw_text_bmp低推荐x.ttf把字体解析与文本光栅化解耦为TTF_File数据模型与BitMap/TTF_render_Sokol渲染器两层前者提供表解析、字符映射cmap format 0/4、字形读取简单/复合字形、字距调整、水平度量等完整能力后者提供直线 二次贝塞尔曲线轮廓填充、四种绘制样式、多行排版与对齐并可选接入 sokol 纹理管线。无论你要做 PDF 文字导出、图片水印、游戏 HUD 还是自定义排版引擎都可以在这一模块的基础上按需裁剪使用。【免费下载链接】vSimple, fast, safe, compiled language for developing maintainable software. Compiles itself in 1s with zero library dependencies. Supports automatic C V translation. https://vlang.io项目地址: https://gitcode.com/GitHub_Trending/v/v创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表