x.ttf: add README, some improvements (#8157)
parent
5ae55731b9
commit
ee9f9c9d81
|
@ -0,0 +1,310 @@
|
|||
# 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 ignore
|
||||
println(ttf_font.get_info_string())
|
||||
```
|
||||
that give an outpul 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 ignore
|
||||
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 ignore
|
||||
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 ignore
|
||||
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 ignore
|
||||
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 ignore
|
||||
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 ignore
|
||||
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
|
||||
use_ortho: true // This is needed for 2D drawing
|
||||
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: malloc(32000000)
|
||||
buf_size: (32000000)
|
||||
color : 0xFF0000FF
|
||||
//style: .raw
|
||||
}
|
||||
}
|
||||
|
||||
app.gg.run()
|
||||
}
|
||||
```
|
|
@ -49,9 +49,50 @@ fn dprintln(txt string){
|
|||
* Utility
|
||||
*
|
||||
******************************************************************************/
|
||||
// transform the bitmap from one layer to color layers
|
||||
fn (mut bmp BitMap) format_texture(){
|
||||
r := byte(bmp.color >> 24)
|
||||
g := byte((bmp.color >> 16) & 0xFF)
|
||||
b := byte((bmp.color >> 8 ) & 0xFF)
|
||||
a := byte(bmp.color & 0xFF)
|
||||
|
||||
b_r := byte(bmp.bg_color >> 24)
|
||||
b_g := byte((bmp.bg_color >> 16) & 0xFF)
|
||||
b_b := byte((bmp.bg_color >> 8 ) & 0xFF)
|
||||
b_a := byte(bmp.bg_color & 0xFF)
|
||||
|
||||
// trasform buffer in a texture
|
||||
x := byteptr(bmp.buf)
|
||||
unsafe{
|
||||
mut i := 0
|
||||
for i<bmp.buf_size {
|
||||
data := x[i]
|
||||
if data > 0 {
|
||||
x[i+0] = r
|
||||
x[i+1] = g
|
||||
x[i+2] = b
|
||||
// alpha
|
||||
x[i+3] = byte((a * data) >> 8)
|
||||
} else {
|
||||
x[i+0] = b_r
|
||||
x[i+1] = b_g
|
||||
x[i+2] = b_b
|
||||
x[i+3] = b_a
|
||||
}
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write out a .ppm file
|
||||
pub
|
||||
fn (mut bmp BitMap) save_as_ppm(file_name string) {
|
||||
tmp_buf := bmp.buf
|
||||
mut buf := malloc(bmp.buf_size)
|
||||
unsafe { C.memcpy(buf, tmp_buf, bmp.buf_size) }
|
||||
bmp.buf = buf
|
||||
|
||||
bmp.format_texture()
|
||||
npixels := bmp.width * bmp.height
|
||||
mut f_out := os.create(file_name) or { panic(err) }
|
||||
f_out.writeln('P3')
|
||||
|
@ -60,14 +101,17 @@ fn (mut bmp BitMap) save_as_ppm(file_name string) {
|
|||
for i in 0..npixels {
|
||||
pos := i * bmp.bp
|
||||
unsafe {
|
||||
c_r := 0xFF - bmp.buf[pos]
|
||||
c_g := 0xFF - bmp.buf[pos +1 ]
|
||||
c_b := 0xFF - bmp.buf[pos + 2]
|
||||
c_r := bmp.buf[pos]
|
||||
c_g := bmp.buf[pos +1 ]
|
||||
c_b := bmp.buf[pos + 2]
|
||||
f_out.write_str('${c_r} ${c_g} ${c_b} ')
|
||||
}
|
||||
|
||||
}
|
||||
f_out.close()
|
||||
|
||||
free(buf)
|
||||
bmp.buf = tmp_buf
|
||||
}
|
||||
|
||||
pub
|
||||
|
|
|
@ -32,37 +32,7 @@ pub mut:
|
|||
*
|
||||
******************************************************************************/
|
||||
fn (mut tf_skl TTF_render_Sokol) format_texture(){
|
||||
r := byte(tf_skl.bmp.color >> 24)
|
||||
g := byte((tf_skl.bmp.color >> 16) & 0xFF)
|
||||
b := byte((tf_skl.bmp.color >> 8 ) & 0xFF)
|
||||
a := byte(tf_skl.bmp.color & 0xFF)
|
||||
|
||||
b_r := byte(tf_skl.bmp.bg_color >> 24)
|
||||
b_g := byte((tf_skl.bmp.bg_color >> 16) & 0xFF)
|
||||
b_b := byte((tf_skl.bmp.bg_color >> 8 ) & 0xFF)
|
||||
b_a := byte(tf_skl.bmp.bg_color & 0xFF)
|
||||
|
||||
// trasform buffer in a texture
|
||||
x := byteptr(tf_skl.bmp.buf)
|
||||
unsafe{
|
||||
mut i := 0
|
||||
for i<tf_skl.bmp.buf_size {
|
||||
data := x[i]
|
||||
if data > 0 {
|
||||
x[i+0] = r
|
||||
x[i+1] = g
|
||||
x[i+2] = b
|
||||
// alpha
|
||||
x[i+3] = byte((a * data) >> 8)
|
||||
} else {
|
||||
x[i+0] = b_r
|
||||
x[i+1] = b_g
|
||||
x[i+2] = b_b
|
||||
x[i+3] = b_a
|
||||
}
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
tf_skl.bmp.format_texture()
|
||||
}
|
||||
|
||||
pub
|
||||
|
|
|
@ -13,14 +13,13 @@ module ttf
|
|||
**********************************************************************/
|
||||
pub
|
||||
struct Text_block {
|
||||
x int
|
||||
y int
|
||||
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
|
||||
cut_lines bool = true // force to cut the line if the length is over the text block width
|
||||
}
|
||||
|
||||
|
||||
fn (mut dev BitMap) get_justify_space_cw(txt string, w int, block_w int, space_cw int) f32 {
|
||||
num_spaces := txt.count(" ")
|
||||
if num_spaces < 1 {
|
||||
|
|
|
@ -558,9 +558,9 @@ fn (mut tf TTF_File) get_fixed() f32 {
|
|||
}
|
||||
|
||||
fn (mut tf TTF_File) get_string(length int) string {
|
||||
tmp_pos := tf.pos
|
||||
tmp_pos := u64(tf.pos)
|
||||
tf.pos += u32(length)
|
||||
return unsafe{ tos(byteptr(u64(tf.buf.data)+u64(tmp_pos)), length) }
|
||||
return unsafe{ tos(byteptr(u64(tf.buf.data)+tmp_pos), length) }
|
||||
}
|
||||
|
||||
fn (mut tf TTF_File) get_unicode_string(length int) string {
|
||||
|
@ -586,12 +586,10 @@ fn (mut tf TTF_File) get_unicode_string(length int) string {
|
|||
}
|
||||
|
||||
fn (mut tf TTF_File) get_date() u64 {
|
||||
// var macTime = this.getUint32() * 0x100000000 + this.getUint32();
|
||||
// utcTime = macTime * 1000 + Date.UTC(1904, 1, 1);
|
||||
// return new Date(utcTime);
|
||||
|
||||
mac_time := u64( (tf.get_u32()) << 32) + u64(tf.get_u32())
|
||||
return mac_time
|
||||
// get mac time and covert it to unix timestamp
|
||||
mac_time := (u64(tf.get_u32()) << 32) + u64(tf.get_u32())
|
||||
utc_time := mac_time - u64(2082844800)
|
||||
return utc_time
|
||||
}
|
||||
|
||||
fn (mut tf TTF_File) calc_checksum(offset u32, length u32) u32 {
|
||||
|
@ -1053,23 +1051,25 @@ fn (mut tf TTF_File) next_kern(glyph_index int) (int, int){
|
|||
* TTF_File Utility
|
||||
*
|
||||
******************************************************************************/
|
||||
/*
|
||||
pub
|
||||
fn (tf TTF_File) get_info_string() string{
|
||||
txt := "----- Font Info -----
|
||||
version: $tf.version
|
||||
font_revision: $tf.font_revision
|
||||
font_family : $tf.font_family
|
||||
font_sub_family : $tf.font_sub_family
|
||||
full_name : $tf.full_name
|
||||
postscript_name : $tf.postscript_name
|
||||
version : $tf.version
|
||||
font_revision : $tf.font_revision
|
||||
magic_number : ${tf.magic_number.hex()}
|
||||
flags : ${tf.flags.hex()}
|
||||
created : ${tf.created}
|
||||
modified : ${tf.modified}
|
||||
box : [xm:${tf.x_min}, ym:${tf.y_min}, xM:${tf.x_max}, yM:${tf.y_max}]
|
||||
created unixTS : ${tf.created}
|
||||
modified unixTS : ${tf.modified}
|
||||
box : [x_min:${tf.x_min}, y_min:${tf.y_min}, x_Max:${tf.x_max}, y_Max:${tf.y_max}]
|
||||
mac_style : ${tf.mac_style}
|
||||
-----------------------
|
||||
"
|
||||
return txt
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue