aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/x/ttf
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/x/ttf')
-rw-r--r--v_windows/v/vlib/x/ttf/README.md310
-rw-r--r--v_windows/v/vlib/x/ttf/common.v205
-rw-r--r--v_windows/v/vlib/x/ttf/render_bmp.v825
-rw-r--r--v_windows/v/vlib/x/ttf/render_sokol_cpu.v210
-rw-r--r--v_windows/v/vlib/x/ttf/text_block.v120
-rw-r--r--v_windows/v/vlib/x/ttf/ttf.v1085
-rw-r--r--v_windows/v/vlib/x/ttf/ttf_test.v237
-rw-r--r--v_windows/v/vlib/x/ttf/ttf_test_data.binbin0 -> 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
new file mode 100644
index 0000000..6d6408c
--- /dev/null
+++ b/v_windows/v/vlib/x/ttf/ttf_test_data.bin
Binary files differ