summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-07-16 15:07:41 -0400
committerpommicket <pommicket@gmail.com>2023-07-16 15:42:00 -0400
commit3f734cf1eb86fef82c7390df003124565b8a84c6 (patch)
tree193e24ffab1c2a2397fb73292d60653f5ef1c97f
parentd27853b51f09c080b1c78201a947fcb6b6cc6b72 (diff)
initial draft of variable-width font handling
-rw-r--r--buffer.c239
-rw-r--r--config.c1
-rw-r--r--ted.cfg2
-rw-r--r--ted.h9
-rw-r--r--text.c17
-rw-r--r--text.h7
-rw-r--r--util.c6
-rw-r--r--util.h1
8 files changed, 171 insertions, 111 deletions
diff --git a/buffer.c b/buffer.c
index 4ec789b..3ca57f9 100644
--- a/buffer.c
+++ b/buffer.c
@@ -778,65 +778,123 @@ static void buffer_print(TextBuffer const *buffer) {
fflush(stdout);
}
-static u32 buffer_index_to_column(TextBuffer *buffer, u32 line, u32 index) {
- char32_t *str = buffer->lines[line].str;
- u32 col = 0;
- uint tab_width = buffer_tab_width(buffer);
- for (u32 i = 0; i < index && i < buffer->lines[line].len; ++i) {
- switch (str[i]) {
- case '\t': {
- do
+// convert line character index to offset in pixels
+static double buffer_index_to_xoff(TextBuffer *buffer, u32 line_number, u32 index) {
+ if (line_number >= buffer->nlines) {
+ assert(0);
+ return 0;
+ }
+ Line *line = &buffer->lines[line_number];
+ char32_t *str = line->str;
+ const Settings *settings = buffer_settings(buffer);
+ Font *font = buffer_font(buffer);
+ u8 tab_width = settings->tab_width;
+ if (index > line->len)
+ index = line->len;
+
+ if (settings->force_monospace) {
+ u32 col = 0;
+ for (u32 i = 0; i < index; ++i) {
+ switch (str[i]) {
+ case '\t': {
+ do
+ ++col;
+ while (col % tab_width);
+ } break;
+ default:
++col;
- while (col % tab_width);
- } break;
- default:
- ++col;
- break;
+ break;
+ }
}
+ return col * (double)text_font_char_width(font, ' ');
+ } else {
+ double x = 0;
+ for (u32 i = 0; i < index; ++i) {
+ switch (str[i]) {
+ case '\t': {
+ x += tab_width * text_font_char_width(font, ' ');
+ } break;
+ default:
+ x += text_font_char_width(font, str[i]);
+ break;
+ }
+ }
+ return x;
}
- return col;
}
-static u32 buffer_column_to_index(TextBuffer *buffer, u32 line, u32 column) {
- if (line >= buffer->nlines) {
+// convert line x offset in pixels to character index
+static u32 buffer_xoff_to_index(TextBuffer *buffer, u32 line_number, double xoff) {
+ if (line_number >= buffer->nlines) {
assert(0);
return 0;
}
- char32_t *str = buffer->lines[line].str;
- u32 len = buffer->lines[line].len;
- u32 col = 0;
- uint tab_width = buffer_tab_width(buffer);
- for (u32 i = 0; i < len; ++i) {
- switch (str[i]) {
- case '\t': {
- do {
- if (col == column)
+ if (xoff <= 0) {
+ return 0;
+ }
+ Line *line = &buffer->lines[line_number];
+ char32_t *str = line->str;
+ const Settings *settings = buffer_settings(buffer);
+ Font *font = buffer_font(buffer);
+ u8 tab_width = settings->tab_width;
+
+ if (settings->force_monospace) {
+ u32 target_col = (u32)(xoff / text_font_char_width(font, ' '));
+ u32 col = 0;
+ for (u32 i = 0; i < line->len; ++i) {
+ switch (str[i]) {
+ case '\t': {
+ do {
+ if (col == target_col)
+ return i;
+ ++col;
+ } while (col % tab_width);
+ } break;
+ default:
+ if (col == target_col)
return i;
++col;
- } while (col % tab_width);
- } break;
- default:
- if (col == column)
- return i;
- ++col;
- break;
+ break;
+ }
+ }
+ return line->len;
+ } else {
+ double x = 0;
+ for (u32 i = 0; i < line->len; ++i) {
+ double x_prev = x;
+ switch (str[i]) {
+ case '\t': {
+ x += tab_width * text_font_char_width(font, ' ');
+ } break;
+ default:
+ x += text_font_char_width(font, str[i]);
+ break;
+ }
+ if (x > xoff) {
+ if (xoff - x_prev < x - xoff) {
+ return i;
+ } else {
+ return i + 1;
+ }
+ }
}
+ return line->len;
}
- return len;
}
+
void buffer_text_dimensions(TextBuffer *buffer, u32 *lines, u32 *columns) {
if (lines) {
*lines = buffer->nlines;
}
if (columns) {
- u32 longest_line = 0;
+ double longest_line = 0;
// which line on screen is the longest?
for (u32 l = buffer->first_line_on_screen; l <= buffer->last_line_on_screen && l < buffer->nlines; ++l) {
Line *line = &buffer->lines[l];
- longest_line = max_u32(longest_line, buffer_index_to_column(buffer, l, line->len));
+ longest_line = maxd(longest_line, buffer_index_to_xoff(buffer, l, line->len));
}
- *columns = longest_line;
+ *columns = (u32)(longest_line / text_font_char_width(buffer_font(buffer), ' '));
}
}
@@ -846,7 +904,7 @@ float buffer_display_lines(TextBuffer *buffer) {
}
float buffer_display_cols(TextBuffer *buffer) {
- return (buffer->x2 - buffer->x1) / text_font_char_width(buffer_font(buffer));
+ return (buffer->x2 - buffer->x1) / text_font_char_width(buffer_font(buffer), ' ');
}
// make sure we don't scroll too far
@@ -882,48 +940,30 @@ void buffer_scroll(TextBuffer *buffer, double dx, double dy) {
vec2 buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos) {
buffer_pos_validate(buffer, &pos);
u32 line = pos.line, index = pos.index;
- // we need to convert the index to a column
- u32 col = buffer_index_to_column(buffer, line, index);
+ double xoff = buffer_index_to_xoff(buffer, line, index);
Font *font = buffer_font(buffer);
- float x = (float)((double)col - buffer->scroll_x) * text_font_char_width(font) + buffer->x1;
+ float x = (float)((double)xoff - buffer->scroll_x * text_font_char_width(font, ' ')) + buffer->x1;
float y = (float)((double)line - buffer->scroll_y) * text_font_char_height(font) + buffer->y1;
return Vec2(x, y);
}
bool buffer_pixels_to_pos(TextBuffer *buffer, vec2 pixel_coords, BufferPos *pos) {
bool ret = true;
- float x = pixel_coords.x, y = pixel_coords.y;
+ double x = pixel_coords.x, y = pixel_coords.y;
Font *font = buffer_font(buffer);
pos->line = pos->index = 0;
x -= buffer->x1;
y -= buffer->y1;
- x /= text_font_char_width(font);
- y /= text_font_char_height(font);
- double display_col = (double)x;
- if (display_col < 0) {
- display_col = 0;
- ret = false;
- }
- double display_cols = buffer_display_cols(buffer), display_lines = buffer_display_lines(buffer);
- if (display_col >= display_cols) {
- display_col = display_cols - 1;
- ret = false;
- }
- double display_line = (double)y;
- if (display_line < 0) {
- display_line = 0;
- ret = false;
- }
- if (display_line >= display_lines) {
- display_line = display_lines - 1;
- ret = false;
- }
- u32 line = (u32)floor(display_line + buffer->scroll_y);
+ x = clampd(x, 0, buffer->x2 - buffer->x1);
+ y = clampd(y, 0, buffer->y2 - buffer->y1);
+
+ double xoff = x + buffer->scroll_x * text_font_char_width(font, ' ');
+
+ u32 line = (u32)floor(y / text_font_char_height(font) + buffer->scroll_y);
if (line >= buffer->nlines) line = buffer->nlines - 1;
- u32 column = (u32)round(display_col + buffer->scroll_x);
- u32 index = buffer_column_to_index(buffer, line, column);
+ u32 index = buffer_xoff_to_index(buffer, line, xoff);
pos->line = line;
pos->index = index;
@@ -948,10 +988,13 @@ bool buffer_clip_rect(TextBuffer *buffer, Rect *r) {
void buffer_scroll_to_pos(TextBuffer *buffer, BufferPos pos) {
const Settings *settings = buffer_settings(buffer);
+ Font *font = buffer_font(buffer);
double line = pos.line;
- double col = buffer_index_to_column(buffer, pos.line, pos.index);
- double display_lines = buffer_display_lines(buffer);
- double display_cols = buffer_display_cols(buffer);
+ double space_width = text_font_char_width(font, ' ');
+ double char_height = text_font_char_height(font);
+ double col = buffer_index_to_xoff(buffer, pos.line, pos.index) / space_width;
+ double display_lines = (buffer->y2 - buffer->y1) / char_height;
+ double display_cols = (buffer->x2 - buffer->x1) / space_width;
double scroll_x = buffer->scroll_x, scroll_y = buffer->scroll_y;
double scrolloff = settings->scrolloff;
@@ -978,12 +1021,12 @@ void buffer_scroll_to_pos(TextBuffer *buffer, BufferPos pos) {
void buffer_scroll_center_pos(TextBuffer *buffer, BufferPos pos) {
double line = pos.line;
- double col = buffer_index_to_column(buffer, pos.line, pos.index);
-
- double display_lines = buffer_display_lines(buffer);
- double display_cols = buffer_display_cols(buffer);
- buffer->scroll_x = col - display_cols * 0.5;
- buffer->scroll_y = line - display_lines * 0.5;
+ Font *font = buffer_font(buffer);
+ float space_width = text_font_char_width(font, ' ');
+ float char_height = text_font_char_height(font);
+ double xoff = buffer_index_to_xoff(buffer, pos.line, pos.index);
+ buffer->scroll_x = (xoff - (buffer->x1 - buffer->x1) * 0.5) / space_width;
+ buffer->scroll_y = line - (buffer->y2 - buffer->y1) / char_height * 0.5;
buffer_correct_scroll(buffer);
}
@@ -995,7 +1038,8 @@ void buffer_scroll_to_cursor(TextBuffer *buffer) {
// scroll so that the cursor is in the center of the screen
void buffer_center_cursor(TextBuffer *buffer) {
double cursor_line = buffer->cursor_pos.line;
- double cursor_col = buffer_index_to_column(buffer, (u32)cursor_line, buffer->cursor_pos.index);
+ double cursor_col = buffer_index_to_xoff(buffer, (u32)cursor_line, buffer->cursor_pos.index)
+ / text_font_char_width(buffer_font(buffer), ' ');
double display_lines = buffer_display_lines(buffer);
double display_cols = buffer_display_cols(buffer);
@@ -1061,33 +1105,33 @@ static i64 buffer_pos_move_horizontally(TextBuffer *buffer, BufferPos *p, i64 by
// same as buffer_pos_move_horizontally, but for up and down.
static i64 buffer_pos_move_vertically(TextBuffer *buffer, BufferPos *pos, i64 by) {
buffer_pos_validate(buffer, pos);
- // moving up/down should preserve the column, not the index.
+ // moving up/down should preserve the x offset, not the index.
// consider:
// tab|hello world
// tab|tab|more text
// the character above the 'm' is the 'o', not the 'e'
if (by < 0) {
by = -by;
- u32 column = buffer_index_to_column(buffer, pos->line, pos->index);
+ double xoff = buffer_index_to_xoff(buffer, pos->line, pos->index);
if (pos->line < by) {
i64 ret = pos->line;
pos->line = 0;
return -ret;
}
pos->line -= (u32)by;
- pos->index = buffer_column_to_index(buffer, pos->line, column);
+ pos->index = buffer_xoff_to_index(buffer, pos->line, xoff);
u32 line_len = buffer->lines[pos->line].len;
if (pos->index >= line_len) pos->index = line_len;
return -by;
} else if (by > 0) {
- u32 column = buffer_index_to_column(buffer, pos->line, pos->index);
+ double xoff = buffer_index_to_xoff(buffer, pos->line, pos->index);
if (pos->line + by >= buffer->nlines) {
i64 ret = buffer->nlines-1 - pos->line;
pos->line = buffer->nlines-1;
return ret;
}
pos->line += (u32)by;
- pos->index = buffer_column_to_index(buffer, pos->line, column);
+ pos->index = buffer_xoff_to_index(buffer, pos->line, xoff);
u32 line_len = buffer->lines[pos->line].len;
if (pos->index >= line_len) pos->index = line_len;
return by;
@@ -2865,7 +2909,7 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, vec2 click, u8 times) {
return false;
}
-bool buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos) {
+char32_t buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos) {
Language language = buffer_language(buffer);
char32_t bracket_char = buffer_char_at_pos(buffer, *pos);
char32_t matching_char = syntax_matching_bracket(language, bracket_char);
@@ -2882,10 +2926,10 @@ bool buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos) {
break;
}
}
- return found_bracket;
- } else {
- return false;
+ if (found_bracket)
+ return matching_char;
}
+ return 0;
}
bool buffer_cursor_move_to_matching_bracket(TextBuffer *buffer) {
@@ -2922,15 +2966,14 @@ void buffer_render(TextBuffer *buffer, Rect r) {
Font *font = buffer_font(buffer);
u32 nlines = buffer->nlines;
Line *lines = buffer->lines;
- float char_width = text_font_char_width(font),
- char_height = text_font_char_height(font);
+ float char_height = text_font_char_height(font);
Ted *ted = buffer->ted;
const Settings *settings = buffer_settings(buffer);
const u32 *colors = settings->colors;
const float padding = settings->padding;
const float border_thickness = settings->border_thickness;
-
+
u32 start_line = buffer_first_rendered_line(buffer); // line to start rendering from
@@ -2949,7 +2992,8 @@ void buffer_render(TextBuffer *buffer, Rect r) {
// line numbering
if (!buffer->is_line_buffer && settings->line_numbers) {
- float line_number_width = ndigits_u64(buffer->nlines) * char_width + padding;
+ // TODO: compute max digit width
+ float line_number_width = ndigits_u64(buffer->nlines) * text_font_char_width(font, '8') + padding;
TextRenderState text_state = text_render_state_default;
text_state.min_x = x1;
@@ -2962,7 +3006,7 @@ void buffer_render(TextBuffer *buffer, Rect r) {
for (u32 line = start_line; line < nlines; ++line) {
char str[32] = {0};
strbuf_printf(str, U32_FMT, line + 1); // convert line number to string
- float x = x1 + line_number_width - (float)strlen(str) * char_width; // right justify
+ float x = x1 + line_number_width - text_get_size_vec2(font, str).x; // right justify
// set color
rgba_u32_to_floats(colors[line == cursor_line ? COLOR_CURSOR_LINE_NUMBER : COLOR_LINE_NUMBERS],
text_state.color);
@@ -3022,7 +3066,7 @@ void buffer_render(TextBuffer *buffer, Rect r) {
// what x coordinate to start rendering the text from
- double render_start_x = x1 - (double)buffer->scroll_x * char_width;
+ double render_start_x = x1 - (double)buffer->scroll_x * text_font_char_width(font, ' ');
u32 column = 0;
if (buffer->selection) { // draw selection
@@ -3045,18 +3089,18 @@ void buffer_render(TextBuffer *buffer, Rect r) {
assert(index2 >= index1);
// highlight everything from index1 to index2
- u32 n_columns_highlighted = buffer_index_to_column(buffer, line_idx, index2)
- - buffer_index_to_column(buffer, line_idx, index1);
+ double highlight_width = buffer_index_to_xoff(buffer, line_idx, index2)
+ - buffer_index_to_xoff(buffer, line_idx, index1);
if (line_idx != sel_end.line) {
- ++n_columns_highlighted; // highlight the newline (otherwise empty higlighted lines wouldn't be highlighted at all).
+ highlight_width += text_font_char_width(font, ' '); // highlight the newline (otherwise empty higlighted lines wouldn't be highlighted at all).
}
- if (n_columns_highlighted) {
+ if (highlight_width > 0) {
BufferPos p1 = {.line = line_idx, .index = index1};
vec2 hl_p1 = buffer_pos_to_pixels(buffer, p1);
Rect hl_rect = rect(
hl_p1,
- Vec2((float)n_columns_highlighted * char_width, char_height)
+ Vec2((float)highlight_width, char_height)
);
buffer_clip_rect(buffer, &hl_rect);
gl_geometry_rect(hl_rect, colors[buffer->view_only ? COLOR_VIEW_ONLY_SELECTION_BG : COLOR_SELECTION_BG]);
@@ -3164,9 +3208,10 @@ void buffer_render(TextBuffer *buffer, Rect r) {
if (pos.index > 0) {
// it's more natural to consider the bracket to the left of the cursor
buffer_pos_move_left(buffer, &pos, 1);
- if (buffer_pos_move_to_matching_bracket(buffer, &pos)) {
+ char32_t c = buffer_pos_move_to_matching_bracket(buffer, &pos);
+ if (c) {
vec2 gl_pos = buffer_pos_to_pixels(buffer, pos);
- Rect hl_rect = rect(gl_pos, Vec2(char_width, char_height));
+ Rect hl_rect = rect(gl_pos, Vec2(text_font_char_width(font, c), char_height));
if (buffer_clip_rect(buffer, &hl_rect)) {
gl_geometry_rect(hl_rect, colors[COLOR_MATCHING_BRACKET_HL]);
}
diff --git a/config.c b/config.c
index 0da3a19..e70bf98 100644
--- a/config.c
+++ b/config.c
@@ -100,6 +100,7 @@ static const SettingBool settings_bool[] = {
{"save-backup", &settings_zero.save_backup, true},
{"crlf-windows", &settings_zero.crlf_windows, true},
{"jump-to-build-error", &settings_zero.jump_to_build_error, true},
+ {"force-monospace", &settings_zero.force_monospace, true},
};
static const SettingU8 settings_u8[] = {
{"tab-width", &settings_zero.tab_width, 1, 100, true},
diff --git a/ted.cfg b/ted.cfg
index 1c46b52..268f09b 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -78,6 +78,8 @@ max-file-size = 20000000
max-file-size-view-only = 100000000
# how much ctrl+scroll wheel changes the text size (0 for no change, negative to invert change)
ctrl-scroll-adjust-text-size = 1.0
+# force every letter to get its width from space
+force-monospace = no
# whether to use vsync or not. you probably want this on.
vsync = on
diff --git a/ted.h b/ted.h
index 8186589..4e4f4bb 100644
--- a/ted.h
+++ b/ted.h
@@ -275,6 +275,7 @@ typedef struct {
bool save_backup;
bool crlf_windows;
bool jump_to_build_error;
+ bool force_monospace;
KeyCombo hover_key;
KeyCombo highlight_key;
u8 tab_width;
@@ -360,7 +361,7 @@ typedef struct {
char *path;
/// we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function
struct Ted *ted;
- /// number of characters scrolled in the x direction
+ /// number of characters scrolled in the x direction (multiply by space width to get pixels)
double scroll_x;
/// number of characters scrolled in the y direction
double scroll_y;
@@ -930,8 +931,8 @@ char32_t buffer_char_before_cursor(TextBuffer *buffer);
BufferPos buffer_pos_start_of_file(TextBuffer *buffer);
/// buffer position of end of file
BufferPos buffer_pos_end_of_file(TextBuffer *buffer);
-/// move position to matching bracket. returns true if there was a bracket at the position, otherwise returns false and does nothing.
-bool buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos);
+/// move position to matching bracket. returns the matching bracket character if there is one, otherwise returns 0 and does nothing.
+char32_t buffer_pos_move_to_matching_bracket(TextBuffer *buffer, BufferPos *pos);
/// move cursor to matching bracket. returns true if cursor was to the right of a bracket.
bool buffer_cursor_move_to_matching_bracket(TextBuffer *buffer);
/// ensures that `p` refers to a valid position, moving it if needed.
@@ -981,7 +982,7 @@ void buffer_clear(TextBuffer *buffer);
/// or 0 if `line_number` is out of range.
u32 buffer_line_len(TextBuffer *buffer, u32 line_number);
/// returns the number of lines of text in the buffer into *lines (if not NULL),
-/// and the number of columns of text, i.e. the number of columns in the longest line displayed, into *cols (if not NULL)
+/// and the number of columns of text, i.e. the width of the longest column divided by the width of the space character, into *cols (if not NULL)
void buffer_text_dimensions(TextBuffer *buffer, u32 *lines, u32 *columns);
/// returns the number of rows of text that can fit in the buffer
float buffer_display_lines(TextBuffer *buffer);
diff --git a/text.c b/text.c
index 398bb3f..093c5ec 100644
--- a/text.c
+++ b/text.c
@@ -39,7 +39,7 @@ typedef struct {
struct Font {
bool force_monospace;
- float char_width; // width of the character 'a'. calculated when font is loaded.
+ float space_width; // width of the character ' '. calculated when font is loaded.
float char_height;
GLuint textures[CHAR_PAGE_COUNT];
int tex_widths[CHAR_PAGE_COUNT], tex_heights[CHAR_PAGE_COUNT];
@@ -200,7 +200,7 @@ Font *text_font_load(const char *ttf_filename, float font_size) {
float x = 0, y = 0;
stbtt_GetBakedQuad(font->char_pages[0], font->tex_widths[0], font->tex_heights[0],
'a', &x, &y, &q, 1);
- font->char_width = x;
+ font->space_width = x;
}
} else {
text_set_err("Couldn't read font file.");
@@ -231,8 +231,14 @@ float text_font_char_height(Font *font) {
return font->char_height;
}
-float text_font_char_width(Font *font) {
- return font->char_width;
+float text_font_char_width(Font *font, char32_t c) {
+ if (c == ' ') return font->space_width;
+ uint page = c / CHAR_PAGE_SIZE;
+ uint index = c % CHAR_PAGE_SIZE;
+ if (!font->char_pages[page])
+ if (!text_load_char_page(font, (int)page))
+ return font->space_width;
+ return font->char_pages[page][index].xadvance;
}
void text_render(Font *font) {
@@ -282,7 +288,6 @@ top:
}
stbtt_bakedchar *char_data = font->char_pages[page];
const float char_height = font->char_height;
- const float char_width = font->char_width;
if (char_data) { // if page was successfully loaded
stbtt_aligned_quad q = {0};
@@ -308,7 +313,7 @@ top:
q.y1 += (float)floor(state->y);
if (font->force_monospace) {
- state->x += char_width; // ignore actual character width
+ state->x += font->space_width; // ignore actual character width
} else {
state->x = x + floor(state->x);
state->y = y + floor(state->y);
diff --git a/text.h b/text.h
index 31997d7..c0cb260 100644
--- a/text.h
+++ b/text.h
@@ -70,10 +70,9 @@ void text_clear_err(void);
Font *text_font_load(const char *ttf_filename, float font_size);
/// Height of a character of this font in pixels.
float text_font_char_height(Font *font);
-/// Width of the character 'a' of this font in pixels.
-/// This is meant to be only used for monospace fonts.
-float text_font_char_width(Font *font);
-/// Force text to advance by text_font_char_width(font) pixels per character (actually, per code point).
+/// Width of the given character in pixels.
+float text_font_char_width(Font *font, char32_t c);
+/// Force text to advance by text_font_char_width(font, ' ') pixels per character (actually, per code point).
void text_font_set_force_monospace(Font *font, bool force);
/// Get the dimensions of some text.
void text_get_size(Font *font, const char *text, float *width, float *height);
diff --git a/util.c b/util.c
index f441479..02c5fc7 100644
--- a/util.c
+++ b/util.c
@@ -514,6 +514,12 @@ float clampf(float x, float a, float b) {
return x;
}
+double clampd(double x, double a, double b) {
+ if (x < a) return a;
+ if (x > b) return b;
+ return x;
+}
+
int clampi(int x, int a, int b) {
if (x < a) return a;
if (x > b) return b;
diff --git a/util.h b/util.h
index 9fb078c..15181bc 100644
--- a/util.h
+++ b/util.h
@@ -138,6 +138,7 @@ float radians(float r);
float lerpf(float x, float a, float b);
float normf(float x, float a, float b);
float clampf(float x, float a, float b);
+double clampd(double x, double a, double b);
int clampi(int x, int a, int b);
i16 clamp_i16(i16 x, i16 a, i16 b);
u16 clamp_u16(u16 x, u16 a, u16 b);