Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+330
View File
@@ -0,0 +1,330 @@
# 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 module 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
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())
}
```
This simple code load a TTF font and display its basic information.
### 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
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')
}
```
This is the low level render that draw the 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 style
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
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 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_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')
}
```
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 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
}
```
and use the following bitmap fields:
```v ignore
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 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 simpler to use in a `gg app` than 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 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()
}
```
+209
View File
@@ -0,0 +1,209 @@
module ttf
/**********************************************************************
*
* Common data for the module
*
* Copyright (c) 2021 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* Note:
*
* TODO:
**********************************************************************/
import os
import math
// text align
pub enum Text_align {
left
center
right
justify
}
// draw style
pub enum Style {
outline
outline_aliased
filled
raw
}
/******************************************************************************
*
* DEBUG Utility
*
******************************************************************************/
// dprintln prints the `txt` message, *only* when `-d debug_flag` is passed while compiling a program
@[if debug_flag ?]
pub fn dprintln(txt string) {
println(txt)
}
/******************************************************************************
*
* Utility
*
******************************************************************************/
// format_texture transforms the bitmap from one layer to color layers
pub fn (mut bmp BitMap) format_texture() {
r := u8(bmp.color >> 24)
g := u8((bmp.color >> 16) & 0xFF)
b := u8((bmp.color >> 8) & 0xFF)
a := u8(bmp.color & 0xFF)
b_r := u8(bmp.bg_color >> 24)
b_g := u8((bmp.bg_color >> 16) & 0xFF)
b_b := u8((bmp.bg_color >> 8) & 0xFF)
b_a := u8(bmp.bg_color & 0xFF)
// transform buffer in a texture
x := 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] = u8(u16(u16(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
}
}
}
// save_as_ppm saves the `BitMap` data in .ppm file format to `file_name`.
pub fn (mut bmp BitMap) save_as_ppm(file_name string) {
tmp_buf := bmp.buf
mut buf := unsafe { malloc_noscan(bmp.buf_size) }
unsafe { vmemcpy(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') or { panic(err) }
f_out.writeln('${bmp.width} ${bmp.height}') or { panic(err) }
f_out.writeln('255') or { panic(err) }
for i in 0 .. npixels {
pos := i * bmp.bp
unsafe {
c_r := bmp.buf[pos]
c_g := bmp.buf[pos + 1]
c_b := bmp.buf[pos + 2]
f_out.write_string('${c_r} ${c_g} ${c_b} ') or { panic(err) }
}
}
f_out.close()
unsafe {
free(buf)
}
bmp.buf = tmp_buf
}
// get_raw_bytes returns the raw bytes of the bitmap.
pub fn (mut bmp BitMap) get_raw_bytes() []u8 {
mut f_buf := []u8{len: bmp.buf_size / 4}
mut i := 0
for i < bmp.buf_size {
unsafe {
f_buf[i >> 2] = *(bmp.buf + i)
}
i += 4
}
return f_buf
}
// save_raw_data saves the raw data to `file_name`.
pub fn (mut bmp BitMap) save_raw_data(file_name string) {
os.write_file_array(file_name, bmp.get_raw_bytes()) or { panic(err) }
}
//
// Math functions
//
// integer part of x
@[inline]
fn ipart(x f32) f32 {
return f32(math.floor(x))
}
@[inline]
fn round(x f32) f32 {
return ipart(x + 0.5)
}
// fractional part of x
@[inline]
fn fpart(x f32) f32 {
return x - f32(math.floor(x))
}
@[inline]
fn rfpart(x f32) f32 {
return 1 - fpart(x)
}
/******************************************************************************
*
* Colors
*
******************************************************************************/
// @[inline]
// pub fn (mut dev BitMap) get_color(x int, y int) (int, int, int, int){
// if x < 0 || x >= dev.width || y < 0 || y >= dev.height {
// return 0,0,0,0
// }
// mut i := (x + y * dev.width)*dev.bp
// unsafe{
// return dev.buf[i], dev.buf[i+1], dev.buf[i+2], dev.buf[i+3]
// }
// }
// @[inline]
// pub fn (mut dev BitMap) get_color_u32(x int, y int) u32{
// r, g, b, a := dev.get_color(x, y)
// unsafe{
// return u32(r<<24) | u32(g<<16) | u32(b<<8) | u32(a)
// }
// }
/******************************************************************************
*
* Drawing
*
******************************************************************************/
// color_multiply_alpha multiplies color `c`'s alpha channel with the `level` value.
@[inline]
pub fn color_multiply_alpha(c u32, level f32) u32 {
return u32(f32(c & 0xFF) * level)
}
// color_multiply multiplies R,G,B channels of color `c` with the `level` value.
@[inline]
pub fn color_multiply(c u32, level f32) u32 {
mut r := (f32((c >> 24) & 0xFF) / 255.0) * level
mut g := (f32((c >> 16) & 0xFF) / 255.0) * level
mut b := (f32((c >> 8) & 0xFF) / 255.0) * level
mut a := (f32(c & 0xFF) / 255.0) * level
r = if r > 1.0 { 1.0 } else { r }
g = if g > 1.0 { 1.0 } else { g }
b = if b > 1.0 { 1.0 } else { b }
a = if a > 1.0 { 1.0 } else { a }
return (u32(r * 255) << 24) | (u32(g * 255) << 16) | (u32(b * 255) << 8) | u32(a * 255)
}
+842
View File
@@ -0,0 +1,842 @@
module ttf
/**********************************************************************
*
* BMP render module utility functions
*
* Copyright (c) 2021 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* Note:
*
* TODO:
* - manage text directions R to L
**********************************************************************/
import encoding.utf8
import math
// BitMap represents a bitmap image of text rendered with the font supplied via the `tf` field.
pub struct BitMap {
pub mut:
tf &TTF_File = unsafe { nil }
buf &u8 = unsafe { nil } // pointer to the memory buffer
buf_size int // allocated buf size in bytes
width int = 1 // width of the buffer
height int = 1 // height of the buffer
bp int = 4 // byte per pixel of the buffer
bg_color u32 = 0xFFFFFF_00 // background RGBA format
color u32 = 0x000000_FF // RGBA format
scale f32 = 1.0 // internal usage!!
scale_x f32 = 1.0 // X scale of the single glyph
scale_y f32 = 1.0 // Y scale of the single glyph
angle f32 = 0.0 // angle of rotation of the bitmap
// spaces
space_cw f32 = 1.0 // width of the space glyph internal usage!!
space_mult f32 = f32(0.0) // 1.0/16.0 // space between letter, is a multiplier for a standard space ax
// used only by internal text rendering!!
tr_matrix []f32 = [f32(1), 0, 0, 0, 1, 0, 0, 0, 0] // transformation matrix
ch_matrix []f32 = [f32(1), 0, 0, 0, 1, 0, 0, 0, 0] // character matrix
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 text
filler [][]int // filler buffer for the renderer
// flag to force font embedded metrics
use_font_metrics bool
}
/******************************************************************************
*
* Utility
*
******************************************************************************/
// clear clears the bitmap with 0 bytes.
pub fn (mut bmp BitMap) clear() {
mut sz := bmp.width * bmp.height * bmp.bp
unsafe {
vmemset(bmp.buf, 0x00, sz)
}
}
// trf_txt returns the transform matrix applied to the text.
pub fn (bmp &BitMap) trf_txt(p &Point) (int, int) {
return int(p.x * bmp.tr_matrix[0] + p.y * bmp.tr_matrix[3] + bmp.tr_matrix[6]), int(
p.x * bmp.tr_matrix[1] + p.y * bmp.tr_matrix[4] + bmp.tr_matrix[7])
}
// trf_ch returns the transform matrix applied to the char.
pub fn (bmp &BitMap) trf_ch(p &Point) (int, int) {
return int(p.x * bmp.ch_matrix[0] + p.y * bmp.ch_matrix[3] + bmp.ch_matrix[6]), int(
p.x * bmp.ch_matrix[1] + p.y * bmp.ch_matrix[4] + bmp.ch_matrix[7])
}
// set_pos sets the draw position in the buffer.
pub fn (mut bmp BitMap) set_pos(x f32, y f32) {
bmp.tr_matrix[6] = x
bmp.tr_matrix[7] = y
}
// set_rotation sets the rotation angle in radians `a`.
pub fn (mut bmp BitMap) set_rotation(a f32) {
bmp.tr_matrix[0] = f32(math.cos(a)) // 1
bmp.tr_matrix[1] = f32(-math.sin(a)) // 0
bmp.tr_matrix[3] = f32(math.sin(a)) // 0
bmp.tr_matrix[4] = f32(math.cos(a)) // 1
}
/******************************************************************************
*
* Filler functions
*
******************************************************************************/
// init_filler initializes the internal `filler` buffer.
pub fn (mut bmp BitMap) init_filler() {
h := bmp.height - bmp.filler.len
if h < 1 {
return
}
for _ in 0 .. h {
bmp.filler << []int{len: 4}
}
// dprintln("Init filler: ${bmp.filler.len} rows")
}
// clear_filler clears the internal `filler` buffer.
pub fn (mut bmp BitMap) clear_filler() {
for i in 0 .. bmp.height {
bmp.filler[i].clear()
}
}
// exec_filler plots the pixels of the `BitMap` to the internal buffer.
pub fn (mut bmp BitMap) exec_filler() {
for y in 0 .. bmp.height {
if bmp.filler[y].len > 0 {
bmp.filler[y].sort()
if bmp.filler[y].len & 1 != 0 {
// dprintln("even line!! ${y} => ${bmp.filler[y]}")
continue
}
mut index := 0
for index < bmp.filler[y].len {
startx := bmp.filler[y][index] + 1
endx := bmp.filler[y][index + 1]
if startx >= endx {
index += 2
continue
}
for x in startx .. endx {
bmp.plot(x, y, bmp.color)
}
index += 2
}
}
}
}
// fline populates the internal `filler` buffer with a line segment from `in_x0`,`in_y0` to `in_x1`,`in_y1`.
pub fn (mut bmp BitMap) fline(in_x0 int, in_y0 int, in_x1 int, in_y1 int, c u32) {
mut x0 := f32(in_x0)
mut x1 := f32(in_x1)
mut y0 := f32(in_y0)
mut y1 := f32(in_y1)
mut tmp := f32(0)
// check bounds
if (in_x0 < 0 && in_x1 < 0) || (in_x0 > bmp.width && in_x1 > bmp.width) {
return
}
if y1 < y0 {
tmp = x0
x0 = x1
x1 = tmp
tmp = y0
y0 = y1
y1 = tmp
}
mut dx := x1 - x0
mut dy := y1 - y0
if dy == 0 {
if in_y0 >= 0 && in_y0 < bmp.filler.len {
if in_x0 <= in_x1 {
bmp.filler[in_y0] << in_x0
bmp.filler[in_y0] << in_x1
} else {
bmp.filler[in_y0] << in_x1
bmp.filler[in_y0] << in_x0
}
}
return
}
mut n := dx / dy
for y in 0 .. int(dy + 0.5) {
yd := int(y + y0)
x := n * y + x0
if x > bmp.width || yd >= bmp.filler.len {
break
}
if yd >= 0 && yd < bmp.filler.len {
bmp.filler[yd] << int(x + 0.5)
// bmp.plot(int(x+0.5), yd, bmp.color)
}
}
}
/******************************************************************************
*
* Draw functions
*
******************************************************************************/
// plot plots a pixel at `x`,`y` in color `c` in the internal bitmap buffer.
@[inline]
pub fn (mut bmp BitMap) plot(x int, y int, c u32) bool {
if x < 0 || x >= bmp.width || y < 0 || y >= bmp.height {
return false
}
mut index := (x + y * bmp.width) * bmp.bp
unsafe {
// bmp.buf[index]=0xFF
bmp.buf[index] = u8(c & 0xFF) // write only the alpha
}
/*
for count in 0..(bmp.bp) {
unsafe{
bmp.buf[index + count] = u8((c >> (bmp.bp - count - 1) * 8) & 0x0000_00FF)
}
}
*/
return true
}
/******************************************************************************
*
* smooth draw functions
*
******************************************************************************/
// aline draws an aliased line on the bitmap.
pub fn (mut bmp BitMap) aline(in_x0 int, in_y0 int, in_x1 int, in_y1 int, c u32) {
// mut c1 := c
mut x0 := f32(in_x0)
mut x1 := f32(in_x1)
mut y0 := f32(in_y0)
mut y1 := f32(in_y1)
mut tmp := f32(0)
mut dx := x1 - x0
mut dy := y1 - y0
dist := f32(0.4)
if math.abs(dx) > math.abs(dy) {
if x1 < x0 {
tmp = x0
x0 = x1
x1 = tmp
tmp = y0
y0 = y1
y1 = tmp
}
dx = x1 - x0
dy = y1 - y0
x0 += 0.5
y0 += 0.5
m := dy / dx
mut x := x0
for x <= x1 + 0.5 {
y := m * (x - x0) + y0
e := 1 - math.abs(y - 0.5 - int(y))
bmp.plot(int(x), int(y), color_multiply_alpha(c, e * 0.75))
ys1 := y + dist
if int(ys1) != int(y) {
v1 := math.abs(ys1 - y) / dist * (1 - e)
bmp.plot(int(x), int(ys1), color_multiply_alpha(c, v1))
}
ys2 := y - dist
if int(ys2) != int(y) {
v2 := math.abs(y - ys2) / dist * (1 - e)
bmp.plot(int(x), int(ys2), color_multiply_alpha(c, v2))
}
x += 1.0
}
} else {
if y1 < y0 {
tmp = x0
x0 = x1
x1 = tmp
tmp = y0
y0 = y1
y1 = tmp
}
dx = x1 - x0
dy = y1 - y0
x0 += 0.5
y0 += 0.5
n := dx / dy
mut y := y0
for y <= y1 + 0.5 {
x := n * (y - y0) + x0
if !math.is_nan(x) && !math.is_nan(y) {
e := f32(1 - math.abs(x - 0.5 - int(x)))
bmp.plot(int(x), int(y), color_multiply_alpha(c, f32(e * 0.75)))
xs1 := x + dist
if int(xs1) != int(x) {
v1 := math.abs(xs1 - x) / dist * (1 - e)
bmp.plot(int(xs1), int(y), color_multiply_alpha(c, f32(v1)))
}
xs2 := x - dist
if int(xs2) != int(x) {
v2 := math.abs(x - xs1) / dist * (1 - e)
bmp.plot(int(xs2), int(y), color_multiply_alpha(c, f32(v2)))
}
}
y += 1.0
}
}
}
/******************************************************************************
*
* draw functions
*
******************************************************************************/
// line plots a line segment to the internal buffer from `in_x0`,`in_y0` to `in_x1`,`in_y1` in the color `c`.
pub fn (mut bmp BitMap) line(in_x0 int, in_y0 int, in_x1 int, in_y1 int, c u32) {
// outline with aliased borders
if bmp.style == .outline_aliased {
bmp.aline(in_x0, in_y0, in_x1, in_y1, c)
return
}
// filled with aliased borders
else if bmp.style == .filled {
bmp.aline(in_x0, in_y0, in_x1, in_y1, c)
bmp.fline(in_x0, in_y0, in_x1, in_y1, c)
return
}
// only the filler is drawn
else if bmp.style == .raw {
bmp.fline(in_x0, in_y0, in_x1, in_y1, c)
return
}
// if we are here we are drawing an outlined border
x0 := int(in_x0)
x1 := int(in_x1)
y0 := int(in_y0)
y1 := int(in_y1)
// dprintln("line[${x0},${y0},${x1},${y1}]")
mut x := x0
mut y := y0
dx := math.abs(x1 - x0)
sx := if x0 < x1 { 1 } else { -1 }
dy := -math.abs(y1 - y0)
sy := if y0 < y1 { 1 } else { -1 }
// vertical line
if dx == 0 {
if y0 < y1 {
for yt in y0 .. y1 + 1 {
bmp.plot(x0, yt, c)
}
return
}
for yt in y1 .. y0 + 1 {
bmp.plot(x0, yt, c)
}
// horizontal line
return
} else if dy == 0 {
if x0 < x1 {
for xt in x0 .. x1 + 1 {
bmp.plot(xt, y0, c)
}
return
}
for xt in x1 .. x0 + 1 {
bmp.plot(xt, y0, c)
}
return
}
mut err := dx + dy // error value e_xy
for {
// bmp.plot(x, y, u32(0xFF00))
bmp.plot(x, y, c)
// dprintln("${x} ${y} [${x0},${y0},${x1},${y1}]")
if x == x1 && y == y1 {
break
}
e2 := 2 * err
if e2 >= dy { // e_xy+e_x > 0
err += dy
x += sx
}
if e2 <= dx { // e_xy+e_y < 0
err += dx
y += sy
}
}
}
// box plots a (hollow) box to the internal buffer from top-left `in_x0`, `in_y0` to bottom right `in_x1`, `in_y1` in color `c`.
pub fn (mut bmp BitMap) box(in_x0 int, in_y0 int, in_x1 int, in_y1 int, c u32) {
bmp.line(in_x0, in_y0, in_x1, in_y0, c)
bmp.line(in_x1, in_y0, in_x1, in_y1, c)
bmp.line(in_x0, in_y1, in_x1, in_y1, c)
bmp.line(in_x0, in_y0, in_x0, in_y1, c)
}
// quadratic plots a quadratic Bezier curve in color `c`.
pub fn (mut bmp BitMap) quadratic(in_x0 int, in_y0 int, in_x1 int, in_y1 int, in_cx int, in_cy int, c u32) {
/*
x0 := int(in_x0 * bmp.scale)
x1 := int(in_x1 * bmp.scale)
y0 := int(in_y0 * bmp.scale)
y1 := int(in_y1 * bmp.scale)
cx := int(in_cx * bmp.scale)
cy := int(in_cy * bmp.scale)
*/
x0 := int(in_x0)
x1 := int(in_x1)
y0 := int(in_y0)
y1 := int(in_y1)
cx := int(in_cx)
cy := int(in_cy)
mut division := f64(1.0)
dx := math.abs(x0 - x1)
dy := math.abs(y0 - y1)
// if few pixel draw a simple line
// if dx == 0 && dy == 0 {
if dx <= 2 || dy <= 2 {
// bmp.plot(x0, y0, c)
bmp.line(x0, y0, x1, y1, c)
return
}
division = 1.0 / (f64(if dx > dy { dx } else { dy }))
// division = 0.1 // 10 division
// division = 0.25 // 4 division
// dprintln("div: ${division}")
/*
----- Bezier quadratic form -----
t = 0.5; // given example value, half length of the curve
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;
---------------------------------
*/
mut x_old := x0
mut y_old := y0
mut t := 0.0
for t <= (1.0 + division / 2.0) {
s := 1.0 - t
x := s * s * x0 + 2.0 * s * t * cx + t * t * x1
y := s * s * y0 + 2.0 * s * t * cy + t * t * y1
xi := int(x + 0.5)
yi := int(y + 0.5)
// bmp.plot(xi, yi, c)
bmp.line(x_old, y_old, xi, yi, c)
x_old = xi
y_old = yi
t += division
}
}
/******************************************************************************
*
* TTF Query functions
*
******************************************************************************/
// get_chars_bbox returns all characters found in bounding box of string `in_string`.
pub fn (mut bmp BitMap) get_chars_bbox(in_string string) []int {
mut res := []int{}
mut w := 0
mut space_cw, _ := bmp.tf.get_horizontal_metrics(u16(` `))
div_space_cw := int((f32(space_cw) * bmp.space_mult) * bmp.scale)
space_cw = int(space_cw * bmp.scale)
bmp.tf.reset_kern()
mut i := 0
for i < in_string.len {
mut chr := u16(in_string[i])
// draw the space
if int(chr) == 32 {
w += int(space_cw * bmp.space_cw)
i++
continue
}
// manage unicode chars like latin greek etc
c_len := int(((u32(0xe5000000) >> ((chr >> 3) & 0x1e)) & 3) + 1)
if c_len > 1 {
tmp_char := utf8.get_rune(in_string, i)
// dprintln("tmp_char: ${tmp_char.hex()}")
chr = u16(tmp_char)
}
c_index := bmp.tf.map_code(int(chr))
// Glyph not found
if c_index == 0 {
w += int(space_cw * bmp.space_cw)
i += c_len
continue
}
ax, ay := bmp.tf.next_kern(c_index)
// dprintln("char_index: ${c_index} ax: ${ax} ay: ${ay}")
// cw, lsb := bmp.tf.get_horizontal_metrics(u16(chr))
// dprintln("metrics: [${u16(chr):c}] cw:${cw} lsb:${lsb}")
//----- Calc Glyph transformations -----
mut x0 := w + int(ax * bmp.scale)
mut y0 := 0 + int(ay * bmp.scale)
p := Point{x0, y0, false}
x1, y1 := bmp.trf_txt(p)
// init ch_matrix
bmp.ch_matrix[0] = bmp.tr_matrix[0] * bmp.scale * bmp.scale_x
bmp.ch_matrix[1] = bmp.tr_matrix[1] * bmp.scale * bmp.scale_x
bmp.ch_matrix[3] = bmp.tr_matrix[3] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[4] = bmp.tr_matrix[4] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[6] = f32(x1)
bmp.ch_matrix[7] = f32(y1)
// x_min, x_max, y_min, y_max := bmp.tf.read_glyph_dim(c_index)
x_min, x_max, _, _ := bmp.tf.read_glyph_dim(c_index)
//-----------------
width := int((math.abs(x_max + x_min) + ax) * bmp.scale)
// width := int((cw+ax) * bmp.scale)
w += width + div_space_cw
h := int(math.abs(int(bmp.tf.y_max - bmp.tf.y_min)) * bmp.scale)
res << w
res << h
i += c_len
}
return res
}
// get_bbox returns the bounding box (width and height) of text `in_string`.
pub fn (mut bmp BitMap) get_bbox(in_string string) (int, int) {
mut w := 0
mut space_cw, _ := bmp.tf.get_horizontal_metrics(u16(` `))
div_space_cw := int((f32(space_cw) * bmp.space_mult) * bmp.scale)
space_cw = int(space_cw * bmp.scale)
bmp.tf.reset_kern()
mut i := 0
for i < in_string.len {
mut chr := u16(in_string[i])
// draw the space
if int(chr) == 32 {
w += int(space_cw * bmp.space_cw)
i++
continue
}
// manage unicode chars like latin greek etc
c_len := int(((u32(0xe5000000) >> ((chr >> 3) & 0x1e)) & 3) + 1)
if c_len > 1 {
tmp_char := utf8.get_rune(in_string, i)
// dprintln("tmp_char: ${tmp_char.hex()}")
chr = u16(tmp_char)
}
c_index := bmp.tf.map_code(int(chr))
// Glyph not found
if c_index == 0 {
w += int(space_cw * bmp.space_cw)
i += c_len
continue
}
ax, ay := bmp.tf.next_kern(c_index)
// dprintln("char_index: ${c_index} ax: ${ax} ay: ${ay}")
// cw, lsb := bmp.tf.get_horizontal_metrics(u16(chr))
// dprintln("metrics: [${u16(chr):c}] cw:${cw} lsb:${lsb}")
//----- Calc Glyph transformations -----
mut x0 := w + int(ax * bmp.scale)
mut y0 := 0 + int(ay * bmp.scale)
p := Point{x0, y0, false}
x1, y1 := bmp.trf_txt(p)
// init ch_matrix
bmp.ch_matrix[0] = bmp.tr_matrix[0] * bmp.scale * bmp.scale_x
bmp.ch_matrix[1] = bmp.tr_matrix[1] * bmp.scale * bmp.scale_x
bmp.ch_matrix[3] = bmp.tr_matrix[3] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[4] = bmp.tr_matrix[4] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[6] = f32(x1)
bmp.ch_matrix[7] = f32(y1)
x_min, x_max, _, _ := bmp.tf.read_glyph_dim(c_index)
// x_min := 1
// x_max := 2
//-----------------
width := int((math.abs(x_max + x_min) + ax) * bmp.scale)
// width := int((cw+ax) * bmp.scale)
w += width + div_space_cw
i += c_len
}
// dprintln("y_min: ${bmp.tf.y_min} y_max: ${bmp.tf.y_max} res: ${int((bmp.tf.y_max - bmp.tf.y_min)*buf.scale)} width: ${int( (cw) * buf.scale)}")
// buf.box(0,y_base - int((bmp.tf.y_min)*buf.scale), int( (x_max) * buf.scale), y_base-int((bmp.tf.y_max)*buf.scale), u32(0xFF00_0000) )
return w, int(math.abs(int(bmp.tf.y_max - bmp.tf.y_min)) * bmp.scale)
}
/******************************************************************************
*
* TTF draw glyph
*
******************************************************************************/
fn (mut bmp BitMap) draw_notdef_glyph(in_x int, in_w int) {
mut p := Point{in_x, 0, false}
x1, y1 := bmp.trf_txt(p)
// init ch_matrix
bmp.ch_matrix[0] = bmp.tr_matrix[0] * bmp.scale * bmp.scale_x
bmp.ch_matrix[1] = bmp.tr_matrix[1] * bmp.scale * bmp.scale_x
bmp.ch_matrix[3] = bmp.tr_matrix[3] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[4] = bmp.tr_matrix[4] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[6] = f32(x1)
bmp.ch_matrix[7] = f32(y1)
x, y := bmp.trf_ch(p)
y_h := math.abs(bmp.tf.y_max - bmp.tf.y_min) * bmp.scale * 0.5
bmp.box(int(x), int(y), int(x - in_w), int(y - y_h), bmp.color)
bmp.line(int(x), int(y), int(x - in_w), int(y - y_h), bmp.color)
bmp.line(int(x - in_w), int(y), int(x), int(y - y_h), bmp.color)
}
// draw_text plots the pixels of the text `in_string` to the internal buffer.
// It returns the text bounding box.
pub fn (mut bmp BitMap) draw_text(in_string string) (int, int) {
mut w := 0
mut space_cw, _ := bmp.tf.get_horizontal_metrics(u16(` `))
div_space_cw := int((f32(space_cw) * bmp.space_mult) * bmp.scale)
space_cw = int(space_cw * bmp.scale)
bmp.tf.reset_kern()
mut i := 0
for i < in_string.len {
mut chr := u16(in_string[i])
// draw the space
if int(chr) == 32 {
w += int(space_cw * bmp.space_cw)
i++
continue
}
// manage unicode chars like latin greek etc
c_len := int(((u32(0xe5000000) >> ((chr >> 3) & 0x1e)) & 3) + 1)
if c_len > 1 {
tmp_char := utf8.get_rune(in_string, i)
// dprintln("tmp_char: ${tmp_char.hex()}")
chr = u16(tmp_char)
}
c_index := bmp.tf.map_code(int(chr))
// Glyph not found
if c_index == 0 {
bmp.draw_notdef_glyph(w, int(space_cw * bmp.space_cw))
w += int(space_cw * bmp.space_cw)
i += c_len
continue
}
ax, ay := bmp.tf.next_kern(c_index)
// dprintln("char_index: ${c_index} ax: ${ax} ay: ${ay}")
cw, _ := bmp.tf.get_horizontal_metrics(u16(chr))
// cw, lsb := bmp.tf.get_horizontal_metrics(u16(chr))
// dprintln("metrics: [${u16(chr):c}] cw:${cw} lsb:${lsb}")
//----- Draw_Glyph transformations -----
mut x0 := w + int(ax * bmp.scale)
mut y0 := 0 + int(ay * bmp.scale)
p := Point{x0, y0, false}
x1, y1 := bmp.trf_txt(p)
// init ch_matrix
bmp.ch_matrix[0] = bmp.tr_matrix[0] * bmp.scale * bmp.scale_x
bmp.ch_matrix[1] = bmp.tr_matrix[1] * bmp.scale * bmp.scale_x
bmp.ch_matrix[3] = bmp.tr_matrix[3] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[4] = bmp.tr_matrix[4] * -bmp.scale * bmp.scale_y
bmp.ch_matrix[6] = f32(x1)
bmp.ch_matrix[7] = f32(y1)
x_min, x_max := bmp.draw_glyph(c_index)
// x_min := 1
// x_max := 2
//-----------------
mut width := int((math.abs(x_max + x_min) + ax) * bmp.scale)
if bmp.use_font_metrics {
width = int((cw + ax) * bmp.scale)
}
w += width + div_space_cw
i += c_len
}
// dprintln("y_min: ${bmp.tf.y_min} y_max: ${bmp.tf.y_max} res: ${int((bmp.tf.y_max - bmp.tf.y_min)*buf.scale)} width: ${int( (cw) * buf.scale)}")
// buf.box(0,y_base - int((bmp.tf.y_min)*buf.scale), int( (x_max) * buf.scale), y_base-int((bmp.tf.y_max)*buf.scale), u32(0xFF00_0000) )
return w, int(math.abs(int(bmp.tf.y_max - bmp.tf.y_min)) * bmp.scale)
}
// draw_glyph plots the pixels of the glyph at `index` to the internal buffer.
// It returns the `x_max` and `x_min` values.
pub fn (mut bmp BitMap) draw_glyph(index u16) (int, int) {
glyph := bmp.tf.read_glyph(index)
if !glyph.valid_glyph {
return 0, 0
}
if bmp.style == .filled || bmp.style == .raw {
bmp.clear_filler()
}
mut s := 0 // status
mut c := 0 // contours count
mut contour_start := 0
mut x0 := 0
mut y0 := 0
color := bmp.color // u32(0xFFFF_FF00) // RGBA white
// color1 := u32(0xFF00_0000) // RGBA red
// color2 := u32(0x00FF_0000) // RGBA green
mut sp_x := 0
mut sp_y := 0
mut point := Point{}
for count, point_raw in glyph.points {
// dprintln("count: ${count}, state: ${s} pl:${glyph.points.len}")
point.x = point_raw.x
point.y = point_raw.y
point.x, point.y = bmp.trf_ch(point)
point.on_curve = point_raw.on_curve
if s == 0 {
x0 = point.x
y0 = point.y
sp_x = x0
sp_y = y0
s = 1 // next state
continue
} else if s == 1 {
if point.on_curve {
bmp.line(x0, y0, point.x, point.y, color)
// bmp.aline(x0, y0, point.x, point.y, u32(0xFFFF0000))
x0 = point.x
y0 = point.y
} else {
s = 2
}
} else {
// dprintln("s==2")
mut prev := glyph.points[count - 1]
prev.x, prev.y = bmp.trf_ch(prev)
if point.on_curve {
// dprintln("HERE1")
// ctx.quadraticCurveTo(prev.x + x, prev.y + y,point.x + x, point.y + y);
// bmp.line(x0, y0, point.x + in_x, point.y + in_y, color1)
// bmp.quadratic(x0, y0, point.x + in_x, point.y + in_y, prev.x + in_x, prev.y + in_y, u32(0xa0a00000))
bmp.quadratic(x0, y0, point.x, point.y, prev.x, prev.y, color)
x0 = point.x
y0 = point.y
s = 1
} else {
// dprintln("HERE2")
// ctx.quadraticCurveTo(prev.x + x, prev.y + y,
// (prev.x + point.x) / 2 + x,
// (prev.y + point.y) / 2 + y);
// bmp.line(x0, y0, (prev.x + point.x)/2, (prev.y + point.y)/2, color2)
// bmp.quadratic(x0, y0, (prev.x + point.x)/2, (prev.y + point.y)/2, prev.x, prev.y, color2)
bmp.quadratic(x0, y0, (prev.x + point.x) / 2, (prev.y + point.y) / 2, prev.x,
prev.y, color)
x0 = (prev.x + point.x) / 2
y0 = (prev.y + point.y) / 2
}
}
if count == glyph.contour_ends[c] {
// dprintln("count == glyph.contour_ends[count]")
if s == 2 { // final point was off-curve. connect to start
mut start_point := glyph.points[contour_start]
start_point.x, start_point.y = bmp.trf_ch(start_point)
if point.on_curve {
// ctx.quadraticCurveTo(prev.x + x, prev.y + y,
// point.x + x, point.y + y);
// bmp.line(x0, y0, start_point.x + in_x, start_point.y + in_y, u32(0x00FF0000))
// start_point.x + in_x, start_point.y + in_y, u32(0xFF00FF00))
bmp.quadratic(x0, y0, start_point.x, start_point.y, start_point.x,
start_point.y, color)
} else {
// ctx.quadraticCurveTo(prev.x + x, prev.y + y,
// (prev.x + point.x) / 2 + x,
// (prev.y + point.y) / 2 + y);
// bmp.line(x0, y0, start_point.x, start_point.y, u32(0x00FF0000)
// u32(0xFF000000))
bmp.quadratic(x0, y0, start_point.x, start_point.y,
(point.x + start_point.x) / 2, (point.y + start_point.y) / 2, color)
}
} else {
// last point not in a curve
// bmp.line(point.x, point.y, sp_x, sp_y, u32(0x00FF0000))
bmp.line(point.x, point.y, sp_x, sp_y, color)
}
contour_start = count + 1
s = 0
c++
}
}
if bmp.style == .filled || bmp.style == .raw {
bmp.exec_filler()
}
x_min := glyph.x_min
x_max := glyph.x_max
return x_min, x_max
// return glyph.x_min, glyph.x_max
}
+229
View File
@@ -0,0 +1,229 @@
module render_sokol
/**********************************************************************
*
* BMP render module utility functions
*
* Copyright (c) 2021 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* Note:
*
* TODO:
**********************************************************************/
import math
import gg
import sokol.sgl
import sokol.gfx
import x.ttf
// TTF_render_Sokol is a structure containing data for rendering a TTF font as a sokol texture.
pub struct TTF_render_Sokol {
pub mut:
bmp &ttf.BitMap = unsafe { nil } // Base bitmap render
// rendering fields
sg_img gfx.Image // sokol image
sg_smp gfx.Sampler // sokol sampler
scale_reduct f32 = 2.0 // scale of the cpu texture for filtering
device_dpi int = 72 // device DPI
}
/******************************************************************************
*
* Render functions
*
******************************************************************************/
// format_texture formats the BMP (bitmap).
pub fn (mut tf_skl TTF_render_Sokol) format_texture() {
tf_skl.bmp.format_texture()
}
// create_text prepares the text `in_txt` in size `in_font_size` as a sokol texture.
pub fn (mut tf_skl TTF_render_Sokol) create_text(in_txt string, in_font_size f32) {
scale_reduct := tf_skl.scale_reduct
device_dpi := tf_skl.device_dpi
font_size := in_font_size //* scale_reduct
// Formula: (font_size * device dpi) / (72dpi * em_unit)
// scale := ((1.0 * device_dpi )/ f32(72 * tf_skl.bmp.tf.units_per_em))* font_size
scale := f32(font_size * device_dpi) / f32(72 * int(tf_skl.bmp.tf.units_per_em))
// ttf.dprintln("Scale: ${scale}")
tf_skl.bmp.scale = scale * scale_reduct
w, h := tf_skl.bmp.get_bbox(in_txt)
tf_skl.bmp.width = int(w)
tf_skl.bmp.height = int(h + 8)
sz := tf_skl.bmp.width * tf_skl.bmp.height * tf_skl.bmp.bp
// RAM buffer
if sz > tf_skl.bmp.buf_size {
if sz > 0 {
unsafe { free(tf_skl.bmp.buf) }
}
ttf.dprintln('create_text Alloc: ${sz} bytes')
tf_skl.bmp.buf = unsafe { malloc_noscan(sz) }
tf_skl.bmp.buf_size = sz
}
tf_skl.bmp.init_filler()
// draw the text
mut y_base := int((tf_skl.bmp.tf.y_max - tf_skl.bmp.tf.y_min) * tf_skl.bmp.scale)
tf_skl.bmp.set_pos(0, y_base)
tf_skl.bmp.clear()
tf_skl.bmp.draw_text(in_txt)
tf_skl.format_texture()
}
// create_text_block prepares a block of text as a sokol texture.
pub fn (mut tf_skl TTF_render_Sokol) create_text_block(in_txt string, in_w int, in_h int, in_font_size f32) {
scale_reduct := tf_skl.scale_reduct
device_dpi := tf_skl.device_dpi
font_size := in_font_size //* scale_reduct
// Formula: (font_size * device dpi) / (72dpi * em_unit)
// scale := ((1.0 * device_dpi )/ f32(72 * tf_skl.bmp.tf.units_per_em))* font_size
scale := f32(font_size * device_dpi) / f32(72 * int(tf_skl.bmp.tf.units_per_em))
// ttf.dprintln("Scale: ${scale}")
tf_skl.bmp.scale = scale * scale_reduct
w := in_w
h := in_h
tf_skl.bmp.width = int(w * scale_reduct + 0.5)
tf_skl.bmp.height = int((h + 2) * scale_reduct + 0.5)
sz := tf_skl.bmp.width * tf_skl.bmp.height * tf_skl.bmp.bp
// if true { return }
// RAM buffer
if sz > tf_skl.bmp.buf_size {
if sz > 0 {
unsafe { free(tf_skl.bmp.buf) }
}
ttf.dprintln('Alloc: ${sz} bytes')
tf_skl.bmp.buf = unsafe { malloc_noscan(sz) }
tf_skl.bmp.buf_size = sz
}
tf_skl.bmp.init_filler()
// draw the text
mut y_base := int((tf_skl.bmp.tf.y_max - tf_skl.bmp.tf.y_min) * tf_skl.bmp.scale)
tf_skl.bmp.set_pos(0, y_base)
tf_skl.bmp.clear()
tf_skl.bmp.draw_text_block(in_txt, x: 0, y: 0, w: w, h: h)
tf_skl.format_texture()
}
/******************************************************************************
*
* Sokol Render functions
*
******************************************************************************/
// create_texture creates the sokol texture from the internal buffer state.
pub fn (mut tf_skl TTF_render_Sokol) create_texture() {
w := tf_skl.bmp.width
h := tf_skl.bmp.height
sz := tf_skl.bmp.width * tf_skl.bmp.height * tf_skl.bmp.bp
mut img_desc := gfx.ImageDesc{
width: w
height: h
num_mipmaps: 0
// usage: .dynamic
label: &char(unsafe { nil })
d3d11_texture: 0
}
// comment for dynamic
img_desc.data.subimage[0][0] = gfx.Range{
ptr: tf_skl.bmp.buf
size: usize(sz)
}
simg := gfx.make_image(&img_desc)
// free(tf_skl.bmp.buf) // DONT FREE IF Dynamic
mut smp_desc := gfx.SamplerDesc{
min_filter: .linear
mag_filter: .linear
wrap_u: .clamp_to_edge
wrap_v: .clamp_to_edge
}
ssmp := gfx.make_sampler(&smp_desc)
tf_skl.sg_img = simg
tf_skl.sg_smp = ssmp
}
// destroy_texture detroys the internal sokol texture.
pub fn (tf_skl TTF_render_Sokol) destroy_texture() {
gfx.destroy_image(tf_skl.sg_img)
gfx.destroy_sampler(tf_skl.sg_smp)
}
// update_text_texture updates the sokol texture with current internal state.
// NOTE: Only use if `.dynamic` is set.
pub fn (mut tf_skl TTF_render_Sokol) update_text_texture() {
sz := tf_skl.bmp.width * tf_skl.bmp.height * tf_skl.bmp.bp
mut tmp_sbc := gfx.ImageData{}
tmp_sbc.subimage[0][0] = gfx.Range{
ptr: tf_skl.bmp.buf
size: usize(sz)
}
gfx.update_image(tf_skl.sg_img, &tmp_sbc)
}
// draw_text_bmp renders the internal state to the current sokol pipeline.
pub fn (tf_skl TTF_render_Sokol) draw_text_bmp(ctx &gg.Context, x f32, y f32) {
// width := tf_skl.bmp.width >> 1
// height := tf_skl.bmp.height >> 1
sgl.push_matrix()
width := tf_skl.bmp.width / (tf_skl.scale_reduct)
height := tf_skl.bmp.height / (tf_skl.scale_reduct)
u0 := f32(0.0)
v0 := f32(0.0)
u1 := f32(1.0)
v1 := f32(1.0)
x0 := f32(0)
y0 := f32(0)
x1 := f32(width) * ctx.scale
y1 := f32(height) * ctx.scale
ca := f32(math.cos(tf_skl.bmp.angle))
sa := f32(math.sin(tf_skl.bmp.angle))
m := [
f32(ca),
-sa,
0,
0,
sa,
ca,
0,
0,
0,
0,
1,
0,
x * ctx.scale,
y * ctx.scale,
0,
1,
]
sgl.mult_matrix(m)
sgl.load_pipeline(ctx.pipeline.alpha)
sgl.enable_texture()
sgl.texture(tf_skl.sg_img, tf_skl.sg_smp)
sgl.begin_quads()
sgl.c4b(255, 255, 255, 255)
sgl.v2f_t2f(x0, y0, u0, v0)
sgl.v2f_t2f(x1, y0, u1, v0)
sgl.v2f_t2f(x1, y1, u1, v1)
sgl.v2f_t2f(x0, y1, u0, v1)
sgl.end()
sgl.disable_texture()
sgl.pop_matrix()
}
+123
View File
@@ -0,0 +1,123 @@
module ttf
/**********************************************************************
*
* BMP render module utility functions
*
* Copyright (c) 2021 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* Note:
*
* TODO:
**********************************************************************/
// Text_block represents a visual block of TTF text.
pub struct Text_block {
pub:
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
}
// get_justify_space_cw returns the space needed to justify `txt`.
pub 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 {
return 0
}
delta := block_w - w
// println("num spc: ${num_spaces}")
// println("delta: ${txt} w:${w} bw:${block_w} space_cw:${space_cw}")
res := f32(delta) / f32(num_spaces) / f32(space_cw)
// println("res: ${res}")
return res
}
// draw_text_block renders out `text` in the `Text_block` `block`.
pub fn (mut bmp BitMap) draw_text_block(text string, block Text_block) {
mut x := block.x
mut y := block.y
mut y_base := int((bmp.tf.y_max - bmp.tf.y_min) * bmp.scale)
// bmp.box(x, y, x + block.w, y + block.h, u32(0xFF00_0000))
// spaces data
mut space_cw, _ := bmp.tf.get_horizontal_metrics(u16(` `))
space_cw = int(space_cw * bmp.scale)
old_space_cw := bmp.space_cw
mut offset_flag := f32(0) // default .left align
if bmp.align == .right {
offset_flag = 1
} else if bmp.align == .center {
offset_flag = 0.5
}
for txt in text.split_into_lines() {
bmp.space_cw = old_space_cw
mut w, _ := bmp.get_bbox(txt)
if w <= block.w || block.cut_lines == false {
// println("Solid block!")
left_offset := int((block.w - w) * offset_flag)
if bmp.justify && (f32(w) / f32(block.w)) >= bmp.justify_fill_ratio {
bmp.space_cw = old_space_cw + bmp.get_justify_space_cw(txt, w, block.w, space_cw)
}
bmp.set_pos(x + left_offset, y + y_base)
bmp.draw_text(txt)
//---- DEBUG ----
// mut txt_w , mut txt_h := bmp.draw_text(txt)
// bmp.box(x + left_offset,y+y_base - int((bmp.tf.y_min)*bmp.scale), x + txt_w + left_offset, y + y_base - int((bmp.tf.y_max) * bmp.scale), u32(0x00ff_0000) )
//---------------
y += y_base
} else {
// println("to cut: ${txt}")
mut txt1 := txt.split(' ')
mut c := txt1.len
// mut done := false
for c > 0 {
tmp_str := txt1[0..c].join(' ')
// println("tmp_str: ${tmp_str}")
if tmp_str == '' {
break
}
bmp.space_cw = old_space_cw
w, _ = bmp.get_bbox(tmp_str)
if w <= block.w {
mut left_offset := int((block.w - w) * offset_flag)
if bmp.justify && (f32(w) / f32(block.w)) >= bmp.justify_fill_ratio {
// println("cut phase!")
bmp.space_cw = 0.0
w, _ = bmp.get_bbox(tmp_str)
left_offset = int((block.w - w) * offset_flag)
bmp.space_cw = bmp.get_justify_space_cw(tmp_str, w, block.w, space_cw)
} else {
bmp.space_cw = old_space_cw
}
bmp.set_pos(x + left_offset, y + y_base)
bmp.draw_text(tmp_str)
//---- DEBUG ----
// txt_w , txt_h := bmp.draw_text(tmp_str)
// println("printing [${x},${y}] => '${tmp_str}' space_cw: ${bmp.space_cw}")
// bmp.box(x + left_offset,y + y_base - int((bmp.tf.y_min)*bmp.scale), x + txt_w + left_offset, y + y_base - int((bmp.tf.y_max) * bmp.scale), u32(0x00ff_0000) )
//---------------
y += y_base
txt1 = unsafe { txt1[c..] }
c = txt1.len
//---- DEBUG ----
// txt2 := txt1.join(' ')
// println("new string: ${txt2} len: ${c}")
//---------------
} else {
c--
}
}
}
}
bmp.space_cw = old_space_cw
}
File diff suppressed because it is too large. Load diff
+238
View File
@@ -0,0 +1,238 @@
// vtest build: !docker-ubuntu-musl
import x.ttf
import os
import strings
/**********************************************************************
*
* BMP render module utility functions
*
* Copyright (c) 2021 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* Note:
* use `v -d create_data vlib/x/ttf/ttf_test.v` to generate binary data for this test file
*
* TODO:
* - manage text directions R to L
**********************************************************************/
const font_path = 'Qarmic_sans_Abridged.ttf'
const font_bytes = $embed_file('ttf_test_data.bin')
const test_data = '
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
00bf bfbf bfbf bfbf bfbf bfbf bf00 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
00bf bfbf bfbf bfbf bfbf bfbf bf00 0000
bfff ffff ffff ffff ffff ffff ffbf 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
bfff ffff ffff ffff ffff ffff ffbf 0000
00bf ffff ffbf ffff bfff ffff bf00 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
00bf ffff ffbf ffff bfff ffff bf00 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 00bf
ffbf 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 00bf
ffbf 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 bfbf
ffbf bfbf bf00 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0032 72bf
bfbf 0000 0000 bfbf bfbf 5400 00bf ffff
ffff ffff ffbf 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0000 0032 72bf
0000 0000 00bf ffff bf00 0065 9999 ffff
ffff bf00 00bf ffff ffff ff7f 0000 bfff
bfff bfff bf00 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 0065 9999 6500
0000 0000 00bf ffff bf00 bfff ffff ffbf
ffff ffbf bfff bfff bfbf ffff bf00 bfff
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 bf72 3300 7fbf
0000 0000 00bf ffff bf7f 5fff ffbf 3f7f
8fbf ffbf ffbf 5500 0000 5fbf 0000 bfff
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf7f 5fff ffbf 3f7f
0000 0000 00bf ffff bfbf ffbf bfbf ffff
ffff ffbf ffff ff7f 0000 0000 0000 bfff
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfbf 00bf bfbf 8f5f
0000 0000 00bf ffff 7f5f ffff ffff ffff
ffff ffbf 5fbf ffff bfbf bfbf 0000 bfff
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff 7f5f 0000 0000 0000
0000 0000 00bf ffff bfff bfff ffbf ffff
ffff ffbf 0000 5fbf ffff ffff bf00 bfff
bf00 0000 0000 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfff bfff ffbf ffff
0000 0000 00bf ffff bfff bf00 0000 0000
0000 0000 0000 0000 7f7f ffff bf00 bfff
bf00 0000 bf00 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfff bf00 0000 0000
0000 0000 00bf ffff bfff bf00 0000 0000
0000 bf00 bf00 0000 0055 bfff ffbf bfff
ff7f 00bf ff5f 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfff bf00 0000 0000
0000 0000 00bf ffff bfbf ffbf 0000 0055
7fbf ffbf ffbf 7f55 00bf ffff bf00 7f5f
ff7f 7f5f ffbf 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfbf ffbf 0000 0055
0000 0000 00bf ffff bfbf ffff bfbf bfff
ffff bfbf ffff ffff ffff ffff bf00 00bf
ffff ffff ffbf 0000 0000 0000 0000 0000
0000 0000 00bf ffff bfbf 0000 bfbf bf7f
0000 0000 00bf ffff bf00 bfff ffff ffff
ffbf 0000 bfbf ffff ffff bfbf 0000 00bf
ffbf ffff bf00 0000 0000 0000 0000 0000
0000 0000 00bf ffff bf00 bf00 0000 3f7f
0000 0000 0000 5fbf 0000 00bf ffbf 8f5f
3f00 0000 0000 5fbf bf5f 0000 0000 0000
0000 bf5f 0000 0000 0000 0000 0000 0000
0000 0000 0000 5fbf 0000 00bf ffbf 8f5f
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
'
fn save_raw_data_as_array(buf_bin []u8, file_name string) {
mut buf := strings.new_builder(buf_bin.len * 5)
for x in buf_bin {
buf.write_string('0x${x:02x},')
}
os.write_file_array(file_name, buf) or { panic(err) }
}
fn test_main() {
mut tf := ttf.TTF_File{}
$if create_data ? {
tf.buf = os.read_bytes(font_path) or { panic(err) }
println('TrueTypeFont file [${font_path}] len: ${tf.buf.len}')
save_raw_data_as_array(tf.buf, 'test_ttf_Font_arr.bin')
} $else {
mut mut_font_bytes := font_bytes
tf.buf = unsafe { mut_font_bytes.data().vbytes(font_bytes.len) }
}
tf.init()
// println("Unit per EM: ${tf.units_per_em}")
w := 64
h := 32
bp := 4
sz := w * h * bp
font_size := 20
device_dpi := 72
scale := f32(font_size * device_dpi) / f32(72 * int(tf.units_per_em))
mut bmp := ttf.BitMap{
tf: &tf
buf: unsafe { malloc(sz) }
buf_size: sz
scale: scale
width: w
height: h
}
y_base := int((tf.y_max - tf.y_min) * bmp.scale)
bmp.clear()
bmp.set_pos(0, y_base)
bmp.init_filler()
bmp.draw_text('Test Text')
mut test_buf := get_raw_data(test_data)
$if create_data ? {
bmp.save_as_ppm('test_ttf.ppm')
bmp.save_raw_data('test_ttf.bin')
test_buf = os.read_bytes('test_ttf.bin') or { panic(err) }
}
ram_buf := bmp.get_raw_bytes()
assert ram_buf.len == test_buf.len
for i in 0 .. ram_buf.len {
if test_buf[i] != ram_buf[i] {
assert false
}
}
}
fn get_raw_data(data string) []u8 {
mut buf := []u8{}
mut c := 0
mut b := u32(0)
for ch in data {
if ch >= `0` && ch <= `9` {
b = b << 4
b += u32(ch - `0`)
c++
} else if ch >= `a` && ch <= `f` {
b = b << 4
b += u32(ch - `a` + 10)
c++
}
if c == 2 {
buf << u8(b)
b = 0
c = 0
}
}
return buf
}
Binary file not shown.