diff options
Diffstat (limited to 'v_windows/v/vlib/x/ttf')
-rw-r--r-- | v_windows/v/vlib/x/ttf/README.md | 310 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/common.v | 205 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/render_bmp.v | 825 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/render_sokol_cpu.v | 210 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/text_block.v | 120 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/ttf.v | 1085 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/ttf_test.v | 237 | ||||
-rw-r--r-- | v_windows/v/vlib/x/ttf/ttf_test_data.bin | bin | 0 -> 16124 bytes |
8 files changed, 2992 insertions, 0 deletions
diff --git a/v_windows/v/vlib/x/ttf/README.md b/v_windows/v/vlib/x/ttf/README.md new file mode 100644 index 0000000..6394ac1 --- /dev/null +++ b/v_windows/v/vlib/x/ttf/README.md @@ -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 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() +} +``` diff --git a/v_windows/v/vlib/x/ttf/common.v b/v_windows/v/vlib/x/ttf/common.v new file mode 100644 index 0000000..ad88d33 --- /dev/null +++ b/v_windows/v/vlib/x/ttf/common.v @@ -0,0 +1,205 @@ +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 +* +******************************************************************************/ +const debug_flag = false + +fn dprintln(txt string) { + if ttf.debug_flag { + println(txt) + } +} + +/****************************************************************************** +* +* 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 := 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 := unsafe { malloc_noscan(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') 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 +} + +pub fn (mut bmp BitMap) get_raw_bytes() []byte { + mut f_buf := []byte{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 +} + +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 +* +******************************************************************************/ +[inline] +pub fn color_multiply_alpha(c u32, level f32) u32 { + return u32(f32(c & 0xFF) * level) +} + +[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) +} diff --git a/v_windows/v/vlib/x/ttf/render_bmp.v b/v_windows/v/vlib/x/ttf/render_bmp.v new file mode 100644 index 0000000..c0cf6dc --- /dev/null +++ b/v_windows/v/vlib/x/ttf/render_bmp.v @@ -0,0 +1,825 @@ +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 +import math.mathutil as mu + +pub struct BitMap { +pub mut: + tf &TTF_File + buf &byte = 0 // 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 standrd 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 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 + filler [][]int // filler buffer for the renderer + // flag to force font embedded metrics + use_font_metrics bool +} + +/****************************************************************************** +* +* Utility +* +******************************************************************************/ +// clear clear the bitmap with 0 bytes +pub fn (mut bmp BitMap) clear() { + mut sz := bmp.width * bmp.height * bmp.bp + unsafe { + C.memset(bmp.buf, 0x00, sz) + } +} + +// transform matrix applied to the text +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]) +} + +// transform matrix applied to the char +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 draw postion 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 the rotation angle in radiants +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 +* +******************************************************************************/ +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") +} + +pub fn (mut bmp BitMap) clear_filler() { + for i in 0 .. bmp.height { + bmp.filler[i].clear() + } +} + +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 + } + } + } +} + +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 +* +******************************************************************************/ +[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] = byte(c & 0xFF) // write only the alpha + } + /* + for count in 0..(bmp.bp) { + unsafe{ + bmp.buf[index + count] = byte((c >> (bmp.bp - count - 1) * 8) & 0x0000_00FF) + } + } + */ + return true +} + +/****************************************************************************** +* +* smooth draw functions +* +******************************************************************************/ +// aline draw 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 mu.abs(dx) > mu.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 - mu.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 := mu.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 := mu.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 + e := f32(1 - mu.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 := mu.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 := mu.abs(x - xs1) / dist * (1 - e) + bmp.plot(int(xs2), int(y), color_multiply_alpha(c, f32(v2))) + } + y += 1.0 + } + } +} + +/****************************************************************************** +* +* draw functions +* +******************************************************************************/ +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 := mu.abs(x1 - x0) + sx := if x0 < x1 { 1 } else { -1 } + dy := -mu.abs(y1 - y0) + sy := if y0 < y1 { 1 } else { -1 } + + // verical 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 + } + } +} + +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) +} + +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 := mu.abs(x0 - x1) + dy := mu.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 +* +******************************************************************************/ +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 char := u16(in_string[i]) + + // draw the space + if int(char) == 32 { + w += int(space_cw * bmp.space_cw) + i++ + continue + } + // manage unicode chars like latin greek etc + c_len := ((0xe5000000 >> ((char >> 3) & 0x1e)) & 3) + 1 + if c_len > 1 { + tmp_char := utf8.get_uchar(in_string, i) + // dprintln("tmp_char: ${tmp_char.hex()}") + char = u16(tmp_char) + } + + c_index := bmp.tf.map_code(int(char)) + // 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(char)) + // dprintln("metrics: [${u16(char):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] = int(x1) + bmp.ch_matrix[7] = int(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((mu.abs(x_max + x_min) + ax) * bmp.scale) + // width := int((cw+ax) * bmp.scale) + w += width + div_space_cw + h := int(mu.abs(int(bmp.tf.y_max - bmp.tf.y_min)) * bmp.scale) + res << w + res << h + + i += c_len + } + return res +} + +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 char := u16(in_string[i]) + + // draw the space + if int(char) == 32 { + w += int(space_cw * bmp.space_cw) + i++ + continue + } + // manage unicode chars like latin greek etc + c_len := ((0xe5000000 >> ((char >> 3) & 0x1e)) & 3) + 1 + if c_len > 1 { + tmp_char := utf8.get_uchar(in_string, i) + // dprintln("tmp_char: ${tmp_char.hex()}") + char = u16(tmp_char) + } + + c_index := bmp.tf.map_code(int(char)) + // 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(char)) + // dprintln("metrics: [${u16(char):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] = int(x1) + bmp.ch_matrix[7] = int(y1) + + x_min, x_max, _, _ := bmp.tf.read_glyph_dim(c_index) + // x_min := 1 + // x_max := 2 + //----------------- + + width := int((mu.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(mu.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] = int(x1) + bmp.ch_matrix[7] = int(y1) + x, y := bmp.trf_ch(p) + + y_h := mu.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) +} + +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 char := u16(in_string[i]) + + // draw the space + if int(char) == 32 { + w += int(space_cw * bmp.space_cw) + i++ + continue + } + // manage unicode chars like latin greek etc + c_len := ((0xe5000000 >> ((char >> 3) & 0x1e)) & 3) + 1 + if c_len > 1 { + tmp_char := utf8.get_uchar(in_string, i) + // dprintln("tmp_char: ${tmp_char.hex()}") + char = u16(tmp_char) + } + + c_index := bmp.tf.map_code(int(char)) + // 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(char)) + // cw, lsb := bmp.tf.get_horizontal_metrics(u16(char)) + // dprintln("metrics: [${u16(char):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] = int(x1) + bmp.ch_matrix[7] = int(y1) + + x_min, x_max := bmp.draw_glyph(c_index) + // x_min := 1 + // x_max := 2 + //----------------- + + mut width := int((mu.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(mu.abs(int(bmp.tf.y_max - bmp.tf.y_min)) * bmp.scale) +} + +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 +} diff --git a/v_windows/v/vlib/x/ttf/render_sokol_cpu.v b/v_windows/v/vlib/x/ttf/render_sokol_cpu.v new file mode 100644 index 0000000..f0e60eb --- /dev/null +++ b/v_windows/v/vlib/x/ttf/render_sokol_cpu.v @@ -0,0 +1,210 @@ +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: +**********************************************************************/ +import math +import gg +import sokol.sgl + +pub struct TTF_render_Sokol { +pub mut: + bmp &BitMap // Base bitmap render + // rendering fields + sg_img C.sg_image // sokol image + scale_reduct f32 = 2.0 // scale of the cpu texture for filtering + device_dpi int = 72 // device DPI +} + +/****************************************************************************** +* +* Render functions +* +******************************************************************************/ +pub fn (mut tf_skl TTF_render_Sokol) format_texture() { + tf_skl.bmp.format_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 * devide_dpi )/ f32(72 * tf_skl.bmp.tf.units_per_em))* font_size + scale := f32(font_size * device_dpi) / f32(72 * tf_skl.bmp.tf.units_per_em) + // 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) } + } + 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() +} + +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 * devide_dpi )/ f32(72 * tf_skl.bmp.tf.units_per_em))* font_size + scale := f32(font_size * device_dpi) / f32(72 * tf_skl.bmp.tf.units_per_em) + // 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) } + } + 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 +* +******************************************************************************/ +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 := C.sg_image_desc{ + width: w + height: h + num_mipmaps: 0 + min_filter: .linear + mag_filter: .linear + // usage: .dynamic + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge + label: &char(0) + d3d11_texture: 0 + } + // comment for dynamic + img_desc.data.subimage[0][0] = C.sg_range{ + ptr: tf_skl.bmp.buf + size: size_t(sz) + } + + simg := C.sg_make_image(&img_desc) + // free(tf_skl.bmp.buf) // DONT FREE IF Dynamic + tf_skl.sg_img = simg +} + +pub fn (tf_skl TTF_render_Sokol) destroy_texture() { + C.sg_destroy_image(tf_skl.sg_img) +} + +// Use only if usage: .dynamic +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 := C.sg_image_data{} + tmp_sbc.subimage[0][0] = C.sg_range{ + ptr: tf_skl.bmp.buf + size: size_t(sz) + } + C.sg_update_image(tf_skl.sg_img, &tmp_sbc) +} + +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.timage_pip) + sgl.enable_texture() + sgl.texture(tf_skl.sg_img) + 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() +} diff --git a/v_windows/v/vlib/x/ttf/text_block.v b/v_windows/v/vlib/x/ttf/text_block.v new file mode 100644 index 0000000..58e909d --- /dev/null +++ b/v_windows/v/vlib/x/ttf/text_block.v @@ -0,0 +1,120 @@ +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: +**********************************************************************/ +pub 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 +} + +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 +} + +// write out a text +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.len < 1 { + 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 = txt1[c..] + c = txt1.len + //---- DEBUG ---- + // txt2 := txt1.join(' ') + // println("new string: ${txt2} len: ${c}") + //--------------- + } else { + c-- + } + } + } + } + + bmp.space_cw = old_space_cw +} diff --git a/v_windows/v/vlib/x/ttf/ttf.v b/v_windows/v/vlib/x/ttf/ttf.v new file mode 100644 index 0000000..f482cc5 --- /dev/null +++ b/v_windows/v/vlib/x/ttf/ttf.v @@ -0,0 +1,1085 @@ +module ttf + +/********************************************************************** +* +* TrueTypeFont reader V implementation +* +* 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: +* - inspired by: http://stevehanov.ca/blog/?id=143 +* +* TODO: +* - check for unicode > 0xFFFF if supported +* - evaluate use a buffer for the points in the glyph +**********************************************************************/ +import strings + +/****************************************************************************** +* +* CMAP structs +* +******************************************************************************/ +struct Segment { +mut: + id_range_offset u32 + start_code u16 + end_code u16 + id_delta u16 +} + +struct TrueTypeCmap { +mut: + format int + cache []int = []int{len: 65536, init: -1} // for now we allocate 2^16 charcode + segments []Segment + arr []int +} + +/****************************************************************************** +* +* TTF_File structs +* +******************************************************************************/ +pub struct TTF_File { +pub mut: + buf []byte + pos u32 + length u16 + scalar_type u32 + search_range u16 + entry_selector u16 + range_shift u16 + tables map[string]Offset_Table + version f32 + font_revision f32 + checksum_adjustment u32 + magic_number u32 + flags u16 + units_per_em u16 + created u64 + modified u64 + x_min f32 + y_min f32 + x_max f32 + y_max f32 + mac_style u16 + lowest_rec_ppem u16 + font_direction_hint i16 + index_to_loc_format i16 + glyph_data_format i16 + font_family string + font_sub_family string + full_name string + postscript_name string + cmaps []TrueTypeCmap + ascent i16 + descent i16 + line_gap i16 + advance_width_max u16 + min_left_side_bearing i16 + min_right_side_bearing i16 + x_max_extent i16 + caret_slope_rise i16 + caret_slope_run i16 + caret_offset i16 + metric_data_format i16 + num_of_long_hor_metrics u16 + kern []Kern0Table + // cache + glyph_cache map[int]Glyph +} + +pub fn (mut tf TTF_File) init() { + tf.read_offset_tables() + tf.read_head_table() + // dprintln(tf.get_info_string()) + tf.read_name_table() + tf.read_cmap_table() + tf.read_hhea_table() + tf.read_kern_table() + tf.length = tf.glyph_count() + dprintln('Number of symbols: $tf.length') + dprintln('*****************************') +} + +/****************************************************************************** +* +* TTF_File Glyph Structs +* +******************************************************************************/ +pub struct Point { +pub mut: + x int + y int + on_curve bool +} + +struct Gylph_Component { +mut: + points []Point +} + +// type of glyph +const ( + g_type_simple = u16(1) // simple type + g_type_complex = u16(2) // compound type +) + +pub struct Glyph { +pub mut: + g_type u16 = ttf.g_type_simple + contour_ends []u16 + number_of_contours i16 + points []Point + x_min i16 + x_max i16 + y_min i16 + y_max i16 + valid_glyph bool + components []Component +} + +/****************************************************************************** +* +* TTF_File metrics and glyph +* +******************************************************************************/ +pub fn (mut tf TTF_File) get_horizontal_metrics(glyph_index u16) (int, int) { + assert 'hmtx' in tf.tables + old_pos := tf.pos + mut offset := tf.tables['hmtx'].offset + + mut advance_width := 0 + mut left_side_bearing := 0 + if glyph_index < tf.num_of_long_hor_metrics { + offset += glyph_index * 4 + tf.pos = offset + advance_width = tf.get_u16() + left_side_bearing = tf.get_i16() + // dprintln("Found h_metric aw: $advance_width lsb: $left_side_bearing") + } else { + // read the last entry of the hMetrics array + tf.pos = offset + (tf.num_of_long_hor_metrics - 1) * 4 + advance_width = tf.get_u16() + tf.pos = offset + tf.num_of_long_hor_metrics * 4 + + 2 * (glyph_index - tf.num_of_long_hor_metrics) + left_side_bearing = tf.get_fword() + } + tf.pos = old_pos + return advance_width, left_side_bearing +} + +fn (mut tf TTF_File) get_glyph_offset(index u32) u32 { + // check if needed tables exists + assert 'loca' in tf.tables + assert 'glyf' in tf.tables + mut old_pos := tf.pos + + table := tf.tables['loca'] + mut offset := u32(0) + mut next := u32(0) + if tf.index_to_loc_format == 1 { + tf.pos = table.offset + (index << 2) + offset = tf.get_u32() + next = tf.get_u32() + } else { + tf.pos = table.offset + (index << 1) + offset = tf.get_u16() << 1 + next = tf.get_u16() << 1 + } + + if offset == next { + // indicates glyph has no outline( eg space) + return 0 + } + // dprintln("Offset for glyph index $index is $offset") + tf.pos = old_pos + return offset + tf.tables['glyf'].offset +} + +fn (mut tf TTF_File) glyph_count() u16 { + assert 'maxp' in tf.tables + old_pos := tf.pos + tf.pos = tf.tables['maxp'].offset + 4 + count := tf.get_u16() + tf.pos = old_pos + return count +} + +pub fn (mut tf TTF_File) read_glyph_dim(index u16) (int, int, int, int) { + offset := tf.get_glyph_offset(index) + // dprintln("offset: $offset") + if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length { + dprintln('No glyph found!') + return 0, 0, 0, 0 + } + + assert offset >= tf.tables['glyf'].offset + assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length + + tf.pos = offset + // dprintln("file seek read_glyph: $tf.pos") + + // number_of_contours + _ := tf.get_i16() + x_min := tf.get_fword() + y_min := tf.get_fword() + x_max := tf.get_fword() + y_max := tf.get_fword() + + return x_min, x_max, y_min, y_max +} + +pub fn (mut tf TTF_File) read_glyph(index u16) Glyph { + index_int := int(index) // index.str() + if index_int in tf.glyph_cache { + // dprintln("Found glyp: ${index}") + return tf.glyph_cache[index_int] + } + // dprintln("Create glyp: ${index}") + + offset := tf.get_glyph_offset(index) + // dprintln("offset: $offset") + if offset == 0 || offset >= tf.tables['glyf'].offset + tf.tables['glyf'].length { + dprintln('No glyph found!') + return Glyph{} + } + + assert offset >= tf.tables['glyf'].offset + assert offset < tf.tables['glyf'].offset + tf.tables['glyf'].length + + tf.pos = offset + // dprintln("file seek read_glyph: $tf.pos") + + /* + ---- BUG TO SOLVE ----- + --- Order of the data if printed in the main is shuffled!! Very Strange + mut tmp_glyph := Glyph{ + number_of_contours : tf.get_i16() + x_min : tf.get_fword() + y_min : tf.get_fword() + x_max : tf.get_fword() + y_max : tf.get_fword() + } + */ + + mut tmp_glyph := Glyph{} + tmp_glyph.number_of_contours = tf.get_i16() + tmp_glyph.x_min = tf.get_fword() + tmp_glyph.y_min = tf.get_fword() + tmp_glyph.x_max = tf.get_fword() + tmp_glyph.y_max = tf.get_fword() + + // dprintln("file seek after read_glyph: $tf.pos") + + assert tmp_glyph.number_of_contours >= -1 + + if tmp_glyph.number_of_contours == -1 { + // dprintln("read_compound_glyph") + tf.read_compound_glyph(mut tmp_glyph) + } else { + // dprintln("read_simple_glyph") + tf.read_simple_glyph(mut tmp_glyph) + } + + tf.glyph_cache[index_int] = tmp_glyph + return tmp_glyph +} + +const ( + tfk_on_curve = 1 + tfk_x_is_byte = 2 + tfk_y_is_byte = 4 + tfk_repeat = 8 + tfk_x_delta = 16 + tfk_y_delta = 32 +) + +fn (mut tf TTF_File) read_simple_glyph(mut in_glyph Glyph) { + if in_glyph.number_of_contours == 0 { + return + } + + for _ in 0 .. in_glyph.number_of_contours { + in_glyph.contour_ends << tf.get_u16() + } + + // skip over intructions + tf.pos = tf.get_u16() + tf.pos + + mut num_points := 0 + for ce in in_glyph.contour_ends { + if ce > num_points { + num_points = ce + } + } + num_points++ + + mut i := 0 + mut flags := []byte{} + for i < num_points { + flag := tf.get_u8() + flags << flag + in_glyph.points << Point{ + x: 0 + y: 0 + on_curve: (flag & ttf.tfk_on_curve) > 0 + } + if (flag & ttf.tfk_repeat) > 0 { + mut repeat_count := tf.get_u8() + assert repeat_count > 0 + i += repeat_count + for repeat_count > 0 { + flags << flag + in_glyph.points << Point{ + x: 0 + y: 0 + on_curve: (flag & ttf.tfk_on_curve) > 0 + } + repeat_count-- + } + } + i++ + } + + // read coords x + mut value := 0 + for i_x in 0 .. num_points { + flag_x := flags[i_x] + if (flag_x & ttf.tfk_x_is_byte) > 0 { + if (flag_x & ttf.tfk_x_delta) > 0 { + value += tf.get_u8() + } else { + value -= tf.get_u8() + } + } else if (~flag_x & ttf.tfk_x_delta) > 0 { + value += tf.get_i16() + } else { + // value is unchanged + } + // dprintln("$i_x x: $value") + in_glyph.points[i_x].x = value + } + + // read coords y + value = 0 + for i_y in 0 .. num_points { + flag_y := flags[i_y] + if (flag_y & ttf.tfk_y_is_byte) > 0 { + if (flag_y & ttf.tfk_y_delta) > 0 { + value += tf.get_u8() + } else { + value -= tf.get_u8() + } + } else if (~flag_y & ttf.tfk_y_delta) > 0 { + value += tf.get_i16() + } else { + // value is unchanged + } + // dprintln("$i_y y: $value") + in_glyph.points[i_y].y = value + } + + // ok we have a valid glyph + in_glyph.valid_glyph = true +} + +const ( + tfkc_arg_1_and_2_are_words = 1 + tfkc_args_are_xy_values = 2 + tfkc_round_xy_to_grid = 4 + tfkc_we_have_a_scale = 8 + // reserved = 16 + tfkc_more_components = 32 + tfkc_we_have_an_x_and_y_scale = 64 + tfkc_we_have_a_two_by_two = 128 + tfkc_we_have_instructions = 256 + tfkc_use_my_metrics = 512 + tfkc_overlap_component = 1024 +) + +struct Component { +mut: + glyph_index u16 + dest_point_index i16 + src_point_index i16 + matrix []f32 = [f32(1.0), 0, 0, 1.0, 0, 0] +} + +fn (mut tf TTF_File) read_compound_glyph(mut in_glyph Glyph) { + in_glyph.g_type = ttf.g_type_complex + mut component := Component{} + mut flags := ttf.tfkc_more_components + for (flags & ttf.tfkc_more_components) > 0 { + mut arg1 := i16(0) + mut arg2 := i16(0) + + flags = tf.get_u16() + + component.glyph_index = tf.get_u16() + + if (flags & ttf.tfkc_arg_1_and_2_are_words) > 0 { + arg1 = tf.get_i16() + arg2 = tf.get_i16() + } else { + arg1 = tf.get_u8() + arg2 = tf.get_u8() + } + + if (flags & ttf.tfkc_args_are_xy_values) > 0 { + component.matrix[4] = arg1 + component.matrix[5] = arg2 + } else { + component.dest_point_index = arg1 + component.src_point_index = arg2 + } + + if (flags & ttf.tfkc_we_have_a_scale) > 0 { + component.matrix[0] = tf.get_2dot14() + component.matrix[3] = component.matrix[0] + } else if (flags & ttf.tfkc_we_have_an_x_and_y_scale) > 0 { + component.matrix[0] = tf.get_2dot14() + component.matrix[3] = tf.get_2dot14() + } else if (flags & ttf.tfkc_we_have_a_two_by_two) > 0 { + component.matrix[0] = tf.get_2dot14() + component.matrix[1] = tf.get_2dot14() + component.matrix[2] = tf.get_2dot14() + component.matrix[3] = tf.get_2dot14() + } + // dprintln("Read component glyph index ${component.glyph_index}") + // dprintln("Transform: ${component.matrix}") + + old_pos := tf.pos + + simple_glyph := tf.read_glyph(component.glyph_index) + if simple_glyph.valid_glyph { + point_offset := in_glyph.points.len + for i in 0 .. simple_glyph.contour_ends.len { + in_glyph.contour_ends << u16(simple_glyph.contour_ends[i] + point_offset) + } + + for p in simple_glyph.points { + mut x := f32(p.x) + mut y := f32(p.y) + x = component.matrix[0] * x + component.matrix[1] * y + component.matrix[4] + y = component.matrix[2] * x + component.matrix[3] * y + component.matrix[5] + in_glyph.points << Point{ + x: int(x) + y: int(y) + on_curve: p.on_curve + } + } + } + tf.pos = old_pos + } + + in_glyph.number_of_contours = i16(in_glyph.contour_ends.len) + + if (flags & ttf.tfkc_we_have_instructions) > 0 { + tf.pos = tf.get_u16() + tf.pos + } + // ok we have a valid glyph + in_glyph.valid_glyph = true +} + +/****************************************************************************** +* +* TTF_File get functions +* +******************************************************************************/ +fn (mut tf TTF_File) get_u8() byte { + x := tf.buf[tf.pos] + tf.pos++ + return byte(x) +} + +fn (mut tf TTF_File) get_i8() i8 { + return i8(tf.get_u8()) +} + +fn (mut tf TTF_File) get_u16() u16 { + x := u16(tf.buf[tf.pos] << u16(8)) | u16(tf.buf[tf.pos + 1]) + tf.pos += 2 + return x +} + +fn (mut tf TTF_File) get_ufword() u16 { + return tf.get_u16() +} + +fn (mut tf TTF_File) get_i16() i16 { + return i16(tf.get_u16()) +} + +fn (mut tf TTF_File) get_fword() i16 { + return tf.get_i16() +} + +fn (mut tf TTF_File) get_u32() u32 { + x := (u32(tf.buf[tf.pos]) << u32(24)) | (u32(tf.buf[tf.pos + 1]) << u32(16)) | (u32(tf.buf[ + tf.pos + 2]) << u32(8)) | u32(tf.buf[tf.pos + 3]) + tf.pos += 4 + return x +} + +fn (mut tf TTF_File) get_i32() int { + return int(tf.get_u32()) +} + +fn (mut tf TTF_File) get_2dot14() f32 { + return f32(tf.get_i16()) / f32(i16(1 << 14)) +} + +fn (mut tf TTF_File) get_fixed() f32 { + return f32(tf.get_i32() / f32(1 << 16)) +} + +fn (mut tf TTF_File) get_string(length int) string { + tmp_pos := u64(tf.pos) + tf.pos += u32(length) + return unsafe { tos(&byte(u64(tf.buf.data) + tmp_pos), length) } +} + +fn (mut tf TTF_File) get_unicode_string(length int) string { + mut tmp_txt := strings.new_builder(length) + mut real_len := 0 + + for _ in 0 .. (length >> 1) { + c := tf.get_u16() + c_len := ((0xe5000000 >> ((c >> 3) & 0x1e)) & 3) + 1 + real_len += c_len + if c_len == 1 { + tmp_txt.write_b(byte(c & 0xff)) + } else { + tmp_txt.write_b(byte((c >> 8) & 0xff)) + tmp_txt.write_b(byte(c & 0xff)) + } + // dprintln("c: ${c:c}|${ byte(c &0xff) :c} c_len: ${c_len} str_len: ${real_len} in_len: ${length}") + } + tf.pos += u32(real_len) + res_txt := tmp_txt.str() + // dprintln("get_unicode_string: ${res_txt}") + return res_txt +} + +fn (mut tf TTF_File) get_date() u64 { + // 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 { + old_index := tf.pos + mut sum := u64(0) + mut nlongs := int((length + 3) >> 2) + tf.pos = offset + // dprintln("offs: $offset nlongs: $nlongs") + for nlongs > 0 { + sum = sum + u64(tf.get_u32()) + nlongs-- + } + tf.pos = old_index + return u32(sum & u64(0xffff_ffff)) +} + +/****************************************************************************** +* +* Offset_Table +* +******************************************************************************/ +struct Offset_Table { +mut: + checksum u32 + offset u32 + length u32 +} + +fn (mut tf TTF_File) read_offset_tables() { + dprintln('*** READ TABLES OFFSET ***') + tf.pos = 0 + tf.scalar_type = tf.get_u32() + num_tables := tf.get_u16() + tf.search_range = tf.get_u16() + tf.entry_selector = tf.get_u16() + tf.range_shift = tf.get_u16() + + dprintln('scalar_type : [0x$tf.scalar_type.hex()]') + dprintln('num tables : [$num_tables]') + dprintln('search_range : [0x$tf.search_range.hex()]') + dprintln('entry_selector: [0x$tf.entry_selector.hex()]') + dprintln('range_shift : [0x$tf.range_shift.hex()]') + + mut i := 0 + for i < num_tables { + tag := tf.get_string(4) + tf.tables[tag] = Offset_Table{ + checksum: tf.get_u32() + offset: tf.get_u32() + length: tf.get_u32() + } + dprintln('Table: [$tag]') + // dprintln("${tf.tables[tag]}") + + if tag != 'head' { + assert tf.calc_checksum(tf.tables[tag].offset, tf.tables[tag].length) == tf.tables[tag].checksum + } + i++ + } + dprintln('*** END READ TABLES OFFSET ***') +} + +/****************************************************************************** +* +* Head_Table +* +******************************************************************************/ +fn (mut tf TTF_File) read_head_table() { + dprintln('*** READ HEAD TABLE ***') + tf.pos = tf.tables['head'].offset + dprintln('Offset: $tf.pos') + + tf.version = tf.get_fixed() + tf.font_revision = tf.get_fixed() + tf.checksum_adjustment = tf.get_u32() + tf.magic_number = tf.get_u32() + assert tf.magic_number == 0x5f0f3cf5 + tf.flags = tf.get_u16() + tf.units_per_em = tf.get_u16() + tf.created = tf.get_date() + tf.modified = tf.get_date() + tf.x_min = tf.get_i16() + tf.y_min = tf.get_i16() + tf.x_max = tf.get_i16() + tf.y_max = tf.get_i16() + tf.mac_style = tf.get_u16() + tf.lowest_rec_ppem = tf.get_u16() + tf.font_direction_hint = tf.get_i16() + tf.index_to_loc_format = tf.get_i16() + tf.glyph_data_format = tf.get_i16() +} + +/****************************************************************************** +* +* Name_Table +* +******************************************************************************/ +fn (mut tf TTF_File) read_name_table() { + dprintln('*** READ NAME TABLE ***') + assert 'name' in tf.tables + table_offset := tf.tables['name'].offset + tf.pos = tf.tables['name'].offset + + format := tf.get_u16() // must be 0 + assert format == 0 + count := tf.get_u16() + string_offset := tf.get_u16() + + for _ in 0 .. count { + platform_id := tf.get_u16() + // platform_specific_id := + tf.get_u16() + // language_id := + tf.get_u16() + name_id := tf.get_u16() + length := tf.get_u16() + offset := tf.get_u16() + + old_pos := tf.pos + tf.pos = table_offset + string_offset + offset + + mut name := '' + if platform_id == 0 || platform_id == 3 { + name = tf.get_unicode_string(length) + } else { + name = tf.get_string(length) + } + // dprintln("Name [${platform_id} / ${platform_specific_id}] id:[$name_id] language:[$language_id] [$name]") + tf.pos = old_pos + + match name_id { + 1 { tf.font_family = name } + 2 { tf.font_sub_family = name } + 4 { tf.full_name = name } + 6 { tf.postscript_name = name } + else {} + } + } +} + +/****************************************************************************** +* +* Cmap_Table +* +******************************************************************************/ +fn (mut tf TTF_File) read_cmap_table() { + dprintln('*** READ CMAP TABLE ***') + assert 'cmap' in tf.tables + table_offset := tf.tables['cmap'].offset + tf.pos = table_offset + + version := tf.get_u16() // must be 0 + assert version == 0 + number_sub_tables := tf.get_u16() + + // tables must be sorted by platform id and then platform specific + // encoding. + for _ in 0 .. number_sub_tables { + // platforms are: + // 0 - Unicode -- use specific id 6 for full coverage. 0/4 common. + // 1 - Macintosh (Discouraged) + // 2 - reserved + // 3 - Microsoft + platform_id := tf.get_u16() + platform_specific_id := tf.get_u16() + offset := tf.get_u32() + dprintln('CMap platform_id=$platform_id specific_id=$platform_specific_id offset=$offset') + if platform_id == 3 && platform_specific_id <= 1 { + tf.read_cmap(table_offset + offset) + } + } +} + +fn (mut tf TTF_File) read_cmap(offset u32) { + old_pos := tf.pos + tf.pos = offset + format := tf.get_u16() + length := tf.get_u16() + language := tf.get_u16() + + dprintln(' Cmap format: $format length: $length language: $language') + if format == 0 { + dprintln(' Cmap 0 Init...') + mut cmap := TrueTypeCmap{} + cmap.init_0(mut tf) + tf.cmaps << cmap + } else if format == 4 { + dprintln(' Cmap 4 Init...') + mut cmap := TrueTypeCmap{} + cmap.init_4(mut tf) + tf.cmaps << cmap + } + + tf.pos = old_pos +} + +/****************************************************************************** +* +* CMAPS 0/4 +* +******************************************************************************/ +fn (mut tf TTF_File) map_code(char_code int) u16 { + mut index := 0 + for i in 0 .. tf.cmaps.len { + mut cmap := tf.cmaps[i] + if cmap.format == 0 { + // dprintln("format 0") + index = cmap.map_0(char_code) + } else if cmap.format == 4 { + // dprintln("format 4") + index = cmap.map_4(char_code, mut tf) + } + } + return u16(index) +} + +fn (mut tm TrueTypeCmap) init_0(mut tf TTF_File) { + tm.format = 0 + for i in 0 .. 256 { + glyph_index := tf.get_u8() + dprintln(' Glyph[$i] = %glyph_index') + tm.arr << glyph_index + } +} + +fn (mut tm TrueTypeCmap) map_0(char_code int) int { + if char_code >= 0 && char_code <= 255 { + // dprintln("charCode $char_code maps to ${tm.arr[char_code]}") + return tm.arr[char_code] + } + return 0 +} + +fn (mut tm TrueTypeCmap) init_4(mut tf TTF_File) { + tm.format = 4 + + // 2x segcount + seg_count := tf.get_u16() >> 1 + // search_range := + tf.get_u16() + // entry_selector := + tf.get_u16() + // range_shift := + tf.get_u16() + + // Ending character code for each segment, last is 0xffff + for _ in 0 .. seg_count { + tm.segments << Segment{0, 0, tf.get_u16(), 0} + } + + // reservePAD + tf.get_u16() + + // starting character code for each segment + for i in 0 .. seg_count { + tm.segments[i].start_code = tf.get_u16() + } + + // Delta for all character codes in segment + for i in 0 .. seg_count { + tm.segments[i].id_delta = tf.get_u16() + } + + // offset in bytes to glyph indexArray, or 0 + for i in 0 .. seg_count { + ro := u32(tf.get_u16()) + if ro != 0 { + tm.segments[i].id_range_offset = tf.pos - 2 + ro + } else { + tm.segments[i].id_range_offset = 0 + } + } + /* + // DEBUG LOG + for i in 0..seg_count { + seg := tm.segments[i] + dprintln(" segments[$i] = $seg.start_code $seg.end_code $seg.id_delta $seg.id_range_offset") + } + */ +} + +fn (mut tm TrueTypeCmap) map_4(char_code int, mut tf TTF_File) int { + // dprintln("HERE map_4 for char [$char_code]") + old_pos := tf.pos + if tm.cache[char_code] == -1 { + // dprintln("Not found, search for it!") + mut found := false + for segment in tm.segments { + if segment.start_code <= char_code && segment.end_code >= char_code { + mut index := (segment.id_delta + char_code) & 0xffff + if segment.id_range_offset > 0 { + glyph_index_address := segment.id_range_offset + + 2 * u32(char_code - segment.start_code) + tf.pos = glyph_index_address + index = tf.get_u16() + } + + tm.cache[char_code] = index + found = true + break + } + } + if !found { + tm.cache[char_code] = 0 + } + } + tf.pos = old_pos + return tm.cache[char_code] +} + +/****************************************************************************** +* +* Hhea table +* +******************************************************************************/ +fn (mut tf TTF_File) read_hhea_table() { + dprintln('*** READ HHEA TABLE ***') + assert 'hhea' in tf.tables + table_offset := tf.tables['hhea'].offset + tf.pos = table_offset + + // version := + tf.get_fixed() // 0x00010000 + + tf.ascent = tf.get_fword() + tf.descent = tf.get_fword() + tf.line_gap = tf.get_fword() + tf.advance_width_max = tf.get_ufword() + tf.min_left_side_bearing = tf.get_fword() + tf.min_right_side_bearing = tf.get_fword() + tf.x_max_extent = tf.get_fword() + tf.caret_slope_rise = tf.get_i16() + tf.caret_slope_run = tf.get_i16() + tf.caret_offset = tf.get_fword() + tf.get_i16() // reserved + tf.get_i16() // reserved + tf.get_i16() // reserved + tf.get_i16() // reserved + tf.metric_data_format = tf.get_i16() + tf.num_of_long_hor_metrics = tf.get_u16() +} + +/****************************************************************************** +* +* Kern table +* +******************************************************************************/ +struct Kern0Table { +mut: + swap bool + offset u32 + n_pairs int + kmap map[u32]i16 + old_index int = -1 +} + +fn (mut kt Kern0Table) reset() { + kt.old_index = -1 +} + +fn (mut kt Kern0Table) get(glyph_index int) (int, int) { + mut x := 0 + + if kt.old_index >= 0 { + ch := ((u32(kt.old_index & 0xFFFF) << 16) | u32(glyph_index & 0xFFFF)) + // dprintln("kern_get: $ch") + if ch in kt.kmap { + x = int(kt.kmap[ch]) + } + } + kt.old_index = glyph_index + if kt.swap { + return 0, x + } + return x, 0 +} + +fn (mut tf TTF_File) create_kern_table0(vertical bool, cross bool) Kern0Table { + offset := tf.pos + n_pairs := tf.get_u16() + search_range := tf.get_u16() + entry_selector := tf.get_u16() + range_shift := tf.get_u16() + dprintln('n_pairs: $n_pairs search_range: $search_range entry_selector: $entry_selector range_shift: $range_shift') + + mut kt0 := Kern0Table{ + swap: (vertical && !cross) || (!vertical && cross) + offset: offset + n_pairs: n_pairs + } + + for _ in 0 .. n_pairs { + left := tf.get_u16() + right := tf.get_u16() + value := tf.get_fword() + tmp_index := (u32(left) << 16) | u32(right) + kt0.kmap[tmp_index] = value + // dprintln("index: ${tmp_index.hex()} val: ${value.hex()}") + } + kt0.old_index = -1 + return kt0 +} + +fn (mut tf TTF_File) read_kern_table() { + dprintln('*** READ KERN TABLE ***') + if 'kern' !in tf.tables { + return + } + table_offset := tf.tables['kern'].offset + tf.pos = table_offset + + version := tf.get_u16() // must be 0 + assert version == 0 // must be 0 + n_tables := tf.get_u16() + + dprintln('Kern Table version: $version Kern nTables: $n_tables') + + for _ in 0 .. n_tables { + st_version := tf.get_u16() // sub table version + length := tf.get_u16() + coverage := tf.get_u16() + format := coverage >> 8 + cross := coverage & 4 + vertical := (coverage & 0x1) == 0 + dprintln('Kerning subtable version [$st_version] format [$format] length [$length] coverage: [$coverage.hex()]') + if format == 0 { + dprintln('kern format: 0') + kern := tf.create_kern_table0(vertical, cross != 0) + tf.kern << kern + } else { + dprintln('Unknown format -- skip') + tf.pos = tf.pos + length + } + } +} + +fn (mut tf TTF_File) reset_kern() { + for i in 0 .. tf.kern.len { + tf.kern[i].reset() + } +} + +fn (mut tf TTF_File) next_kern(glyph_index int) (int, int) { + mut x := 0 + mut y := 0 + for i in 0 .. tf.kern.len { + tmp_x, tmp_y := tf.kern[i].get(glyph_index) + x = x + tmp_x + y = y + tmp_y + } + return x, y +} + +/****************************************************************************** +* +* TTF_File Utility +* +******************************************************************************/ +pub fn (tf TTF_File) get_info_string() string { + txt := '----- Font Info ----- +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 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 +} + +/****************************************************************************** +* +* TTF_File test +* +******************************************************************************/ +fn tst() { + mut tf := TTF_File{} + + tf.buf = [ + byte(0xFF), /* 8 bit */ + 0xF1, + 0xF2, /* 16 bit */ + 0x81, + 0x23, + 0x45, + 0x67, /* 32 bit */ + 0x12, + 0x34, + 0x12, + 0x34, /* get_2dot14 16 bit */ + 0x12, + 0x34, + 0x12, + 0x34 /* get_fixed 32 bit int */, + ] + assert tf.get_u8().hex() == 'ff' + assert tf.get_u16().hex() == 'f1f2' + assert tf.get_u32().hex() == '81234567' + + dprintln('buf len: $tf.buf.len') + // dprintln( tf.get_u8().hex() ) + // dprintln( tf.get_u16().hex() ) + // dprintln( tf.get_u32().hex() ) + // dprintln( tf.get_2dot14() ) + // dprintln( tf.get_fixed() ) +} diff --git a/v_windows/v/vlib/x/ttf/ttf_test.v b/v_windows/v/vlib/x/ttf/ttf_test.v new file mode 100644 index 0000000..7a6f020 --- /dev/null +++ b/v_windows/v/vlib/x/ttf/ttf_test.v @@ -0,0 +1,237 @@ +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 []byte, 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 * 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) []byte { + mut buf := []byte{} + mut c := 0 + mut b := 0 + for ch in data { + if ch >= `0` && ch <= `9` { + b = b << 4 + b += int(ch - `0`) + c++ + } else if ch >= `a` && ch <= `f` { + b = b << 4 + b += int(ch - `a` + 10) + c++ + } + + if c == 2 { + buf << byte(b) + b = 0 + c = 0 + } + } + return buf +} diff --git a/v_windows/v/vlib/x/ttf/ttf_test_data.bin b/v_windows/v/vlib/x/ttf/ttf_test_data.bin Binary files differnew file mode 100644 index 0000000..6d6408c --- /dev/null +++ b/v_windows/v/vlib/x/ttf/ttf_test_data.bin |