311 lines
8.2 KiB
Markdown
311 lines
8.2 KiB
Markdown
# TTF font utility
|
|
## introduction
|
|
This module is designed to perform two main task
|
|
- Load the font file
|
|
- Render text using a TTF font
|
|
|
|
The render system can be single or multiple, for example it is possible to have a bitmap
|
|
render and a HW accelerated render.
|
|
|
|
## TTF loader
|
|
This part of the module do a simple task, load a TTF file and preprocess all the loaded data
|
|
in order to simplify the rendering phase.
|
|
|
|
Let's start with a simple snippet of code that load a font from the disk:
|
|
```v ignore
|
|
mut ttf_font := ttf.TTF_File{}
|
|
ttf_font.buf = os.read_bytes("arial.ttf") or { panic(err) }
|
|
ttf_font.init()
|
|
```
|
|
*Note: the font must be passed to the `TTF_file` as RAM buffer.*
|
|
At this point the font "arial" is loaded and parsed and if it is a valid TTF font it is
|
|
ready for the rendering.
|
|
We can get some quick info on the font as string using the `get_info_string` function:
|
|
|
|
```v oksyntax
|
|
println(ttf_font.get_info_string())
|
|
```
|
|
produces an output like this:
|
|
```
|
|
----- 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
|
|
-----------------------
|
|
```
|
|
|
|
Once loaded a font the `TTF_File` struct is filled with the font data and texts can be rendered.
|
|
At high level no more action are required to use the loaded font.
|
|
Multiple fonts can be loaded without problems at the same time.
|
|
|
|
## TTF Bitmap render
|
|
In this modue it is possible to have different renders running at the same time.
|
|
At the present time all the rendering are made on the CPU, sokol is used only to draw the
|
|
rendered text to the screen.
|
|
Let's start with a simple snippet of code:
|
|
```v oksyntax
|
|
import os
|
|
import x.ttf
|
|
|
|
[console]
|
|
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())
|
|
}
|
|
```
|
|
This simple code load a TTF font and display its basic informations.
|
|
|
|
### draw_text
|
|
The draw text function draw simple strings without indentation or other imagination tasks.
|
|
At this point we can render a simple text:
|
|
```v oksyntax
|
|
import os
|
|
import x.ttf
|
|
|
|
[console]
|
|
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_heigth := 64
|
|
bmp_layers := 4 // number of planes for an RGBA buffer
|
|
// memory size of the buffer
|
|
bmp_size := bmp_width * bmp_heigth * 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: malloc(bmp_size)
|
|
buf_size: bmp_size
|
|
width: bmp_width
|
|
height: bmp_heigth
|
|
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')
|
|
}
|
|
```
|
|
This is the low level render that draw ther text on a bitmap and save the bitmap on a disk as
|
|
`.ppm` file.
|
|
*Note: The render in this case is a raw rendering without any postfiltering or other processing.*
|
|
|
|
Using the low level rendering you need to manage all the amenities like allocate and release
|
|
memory and other tasks like calc the character dimensions.
|
|
|
|
You can specify the style for the text rendering in the `BitMap` struct::
|
|
```v
|
|
enum Style {
|
|
outline
|
|
outline_aliased
|
|
filled // default syle
|
|
raw
|
|
}
|
|
```
|
|
Use this level only if you want achieve particular result on text rendering.
|
|
|
|
### draw_text_block
|
|
Draw text block draw a justified and indented block of multiline text in the bitmap.
|
|
```v oksyntax
|
|
import os
|
|
import x.ttf
|
|
|
|
[console]
|
|
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_heigth := 200
|
|
bmp_layers := 4 // number of planes for an RGBA buffer
|
|
// memory size of the buffer
|
|
bmp_size := bmp_width * bmp_heigth * 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 I'm 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_heigth
|
|
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_heigth)
|
|
bmp.save_as_ppm('test.ppm')
|
|
}
|
|
```
|
|
This is the low level render that draw text block on the bitmap.
|
|
A text block is defined from a `Text_block` struct:
|
|
```v
|
|
struct Text_block {
|
|
x int // x postion of the left high corner
|
|
y int // y postion of the left high corner
|
|
w int // width of the text block
|
|
h int // heigth of the text block
|
|
cut_lines bool = true // force to cut the line if the length is over the text block width
|
|
}
|
|
```
|
|
and use the following bitmap fields:
|
|
```v ignore
|
|
style Style = .filled // default syle
|
|
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 text
|
|
```
|
|
|
|
It is possible to modify these parameters to obtain the desired effect on the text rendering.
|
|
|
|
## TTF Sokol render
|
|
The sokol render use the bitmap render to create the text and the `gg` functions to render
|
|
the text to the screen.
|
|
It is mor esimpel to use in a `gg app` that the raw bitmap render.
|
|
Each single text rendered need its own reder to be declared, after you can modify it.
|
|
Here a simple example of the usage:
|
|
```v oksyntax
|
|
import gg
|
|
import gx
|
|
import sokol.sapp
|
|
import sokol.sgl
|
|
import x.ttf
|
|
import os
|
|
|
|
const (
|
|
win_width = 600
|
|
win_height = 700
|
|
bg_color = gx.white
|
|
font_paths = [
|
|
'arial.ttf',
|
|
]
|
|
)
|
|
|
|
struct App_data {
|
|
pub mut:
|
|
gg &gg.Context
|
|
sg_img C.sg_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()
|
|
}
|
|
|
|
[console]
|
|
fn main() {
|
|
mut app := &App_data{
|
|
gg: 0
|
|
}
|
|
|
|
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()
|
|
}
|
|
```
|