added optional font handling

This commit is contained in:
vurtun 2015-09-15 18:13:43 +02:00
parent a08febda9d
commit c875e59710
9 changed files with 4798 additions and 556 deletions

141
Readme.md
View File

@ -8,13 +8,16 @@ application and does not have any direct dependencies.
## Features ## Features
- Immediate mode graphical user interface toolkit - Immediate mode graphical user interface toolkit
- Written in C89 (ANSI C) - Written in C89 (ANSI C)
- Small codebase (~7kLOC) - Small codebase (~8kLOC)
- Focus on portability, efficiency, simplicity and minimal internal state - Focus on portability, efficiency, simplicity and minimal internal state
- No global or hidden state - No global or hidden state
- No direct dependencies (not even libc!) - No direct dependencies
- Configurable style and colors - Configurable style and colors
- UTF-8 support - UTF-8 support
- Optional vertex buffer output
## Optional
- vertex buffer output
- font handling
## Gallery ## Gallery
![gui demo](/screen/demo.png?raw=true) ![gui demo](/screen/demo.png?raw=true)
@ -24,11 +27,8 @@ application and does not have any direct dependencies.
## Example ## Example
```c ```c
/* setup configuration */ /* setup configuration */
struct gui_font font;
struct gui_style style; struct gui_style style;
font.userdata.ptr = your_font_data; struct gui_user_font font = {...};
font.height = your_font_data.height;
font.width = your_font_string_width_callback_function;
gui_style_default(&style, GUI_DEFAULT_ALL, &font); gui_style_default(&style, GUI_DEFAULT_ALL, &font);
/* allocate memory to hold draw commands */ /* allocate memory to hold draw commands */
@ -84,12 +84,13 @@ while (1) {
Immediate mode in contrast to classical retained mode GUIs store as little state as possible Immediate mode in contrast to classical retained mode GUIs store as little state as possible
by using procedural function calls as "widgets" instead of storing objects. by using procedural function calls as "widgets" instead of storing objects.
Each "widget" function call takes hereby all its necessary data and immediately returns Each "widget" function call takes hereby all its necessary data and immediately returns
the through the user modified state back to the caller. Immediate mode graphical the user modified state back to the caller. Immediate mode graphical
user interfaces therefore combine drawing and input handling into one unit user interfaces therefore combine drawing and input handling into one unit
instead of separating them like retain mode GUIs. instead of separating them like retain mode GUIs.
Since there is no to minimal internal state in immediate mode user interfaces, Since there is no to minimal internal state in immediate mode user interfaces,
updates have to occur every frame which on one hand is more drawing expensive than classic updates have to occur every frame, on every user input update or program state change
which on one hand is more drawing expensive than classic
retained GUI implementations but on the other hand grants a lot more flexibility and retained GUI implementations but on the other hand grants a lot more flexibility and
support for overall layout changes. In addition without any state there is no support for overall layout changes. In addition without any state there is no
duplicated state between your program, the gui and the user which greatly duplicated state between your program, the gui and the user which greatly
@ -97,126 +98,6 @@ simplifies code. Further traits of immediate mode graphic user interfaces are a
code driven style, centralized flow control, easy extensibility and code driven style, centralized flow control, easy extensibility and
understandability. understandability.
### Input
The `gui_input` struct holds the user input over the course of the frame and
manages the complete modification of widget and panel state. To fill the
structure with data over the frame there are a number of functions provided for
key, motion, button and text input. The input is hereby completly independent of
the underlying platform or way of input so even touch or other ways of input are
possible.
Like the panel and the buffer, input is based on an immediate mode API and
consist of an begin sequence with `gui_input_begin` and a end sequence point
with `gui_input_end`. All modifications can only occur between both of these
sequence points while all outside modification provoke undefined behavior.
```c
struct gui_input input = {0};
while (1) {
gui_input_begin(&input);
if (/*mouse moved*/)
gui_input_motion(&input, mouse.x, mouse.y);
if (/*key pressed*/)
gui_input_key(&input, key, gui_true);
if (/*key released*/)
gui_input_key(&input, key, gui_false);
if (/*mouse button pressed*/)
gui_input_button(&input, mouse.x, mouse.y, gui_true);
if (/*mouse button released */)
gui_input_button(&input, mouse.x, mouse.y, gui_false);
gui_input_end(&input);
}
```
### Configuration
The gui toolkit provides a number of different attributes that can be
configured, like spacing, padding, size and color.
While the widget API even expects you to provide the configuration
for each and every widget the panel layer provides you with a set of
attributes in the `gui_config` structure. The structure either needs to be
filled by the user or can be setup with some default values by the function
`gui_config_default`. Modification on the fly to the `gui_config` struct is in
true immediate mode fashion possible and supported.
```c
struct gui_style {
gui_float rounding[GUI_ROUNDING_MAX];
struct gui_vec2 properties[GUI_PROPERTY_MAX];
struct gui_color colors[GUI_COLOR_COUNT];
};
```
In addition to modifing the `gui_style` struct directly the styleration API
enables you to temporarily change a property or color and revert back directly
after the change is no longer needed. The number of temporary changes are
limited but can be changed with constants `GUI_MAX_COLOR_STACK` and
`GUI_MAX_ATTRIB_STACK`.
```c
gui_style_push_color(style, GUI_COLORS_PANEL, 255, 0, 0, 255);
gui_style_push_attribute(style, GUI_ATTRIBUTE_PADDING, 10.0f, 5.0f);
/* use the styleuration data */
gui_style_pop_attribute(style);
gui_style_pop_color(style);
```
Since there is no direct font implementation in the toolkit but font handling is
still an aspect of a gui implementation, the `gui_font` struct was introduced. It only
contains the bare minimum of what is needed for font handling.
For widgets the `gui_font` data has to be persistent while the
panel hold the font internally. Important to node is that the font does not hold
your font data but merely references it so you have to make sure that the font
always points to a valid object.
```c
struct gui_font {
gui_handle userdata;
gui_float height;
gui_text_width_f width;
};
font.userdata.ptr = your_font_data;
font.height = your_font_data.height;
font.width = your_font_string_width_callback_function;
```
### Memory
Almost all memory as well as object management for the toolkit
is left to the user for maximum control. In fact a big subset of the toolkit can
be used without any heap allocation at all. The only place where heap allocation
is needed at all is for buffering draw calls. While the standart way of
memory allocation in that case for libraries is to just provide allocator callbacks
which is implemented aswell with the `gui_allocator`
structure, there are two addition ways to provided memory. The
first one is to just providing a static fixed size memory block to fill up which
is handy for UIs with roughly known memory requirements. The other way of memory
managment is to extend the fixed size block with the abiltiy to resize your block
at the end of the frame if there is not enough memory.
For the purpose of resizable fixed size memory blocks and for general
information about memory consumption the `gui_memory_info` structure was
added. It contains information about the allocated amount of data in the current
frame as well as the needed amount if not enough memory was provided.
```c
/* fixed size queue */
void *memory = malloc(size);
gui_command_queue queue;
gui_command_queue_init_fixed(&queue, memory, MEMORY_SIZE, GUI_CLIP);
```
```c
/* dynamically growing queue */
struct gui_allocator alloc;
alloc.userdata = your_allocator;
alloc.alloc = your_allocation_callback;
alloc.relloac = your_reallocation_callback;
alloc.free = your_free_callback;
struct gui_command_queue queue;
const gui_size initial_size = 4*1024;
const gui_float grow_factor = 2.0f;
gui_command_queue_init(&queue, &alloc, initial_size, grow_factor);
```
## FAQ ## FAQ
#### Where is the demo/example code? #### Where is the demo/example code?
The demo and example code can be found in the demo folder. The demo and example code can be found in the demo folder.
@ -261,7 +142,7 @@ platform (Xlib, Win32) itself already provides a solution.
## References ## References
- [Tutorial from Jari Komppa about imgui libraries](http://www.johno.se/book/imgui.html) - [Tutorial from Jari Komppa about imgui libraries](http://www.johno.se/book/imgui.html)
- [Johannes 'johno' Norneby's article](http://iki.fi/sol/imgui/) - [Johannes 'johno' Norneby's article](http://iki.fi/sol/imgui/)
- [ImGui: The inspiration for this project](https://github.com/ocornut/imgui) - [ImGui: The inspiration for this project from ocornut](https://github.com/ocornut/imgui)
- [Nvidia's imgui toolkit](https://code.google.com/p/nvidia-widgets/) - [Nvidia's imgui toolkit](https://code.google.com/p/nvidia-widgets/)
# License # License

View File

@ -87,7 +87,6 @@ enum menu_edit_items {
#undef ITEM #undef ITEM
MENU_EDIT_MAX MENU_EDIT_MAX
}; };
static const char *weapons[] = { static const char *weapons[] = {
#define WEAPON(id,name) name, #define WEAPON(id,name) name,
WEAPON_MAP(WEAPON) WEAPON_MAP(WEAPON)
@ -296,7 +295,7 @@ color_picker(struct gui_context *panel, struct color_picker* control,
*iter = (gui_byte)t; *iter = (gui_byte)t;
} }
gui_layout_row_dynamic(&popup, 30, 3); gui_layout_row_dynamic(&popup, 30, 4);
gui_spacing(&popup, 1); gui_spacing(&popup, 1);
if (gui_button_text(&popup, "ok", GUI_BUTTON_DEFAULT)) { if (gui_button_text(&popup, "ok", GUI_BUTTON_DEFAULT)) {
gui_popup_close(&popup); gui_popup_close(&popup);
@ -540,15 +539,13 @@ widget_panel(struct gui_context *panel, struct state *demo)
{ {
/* tiled widgets layout */ /* tiled widgets layout */
gui_uint i = 0;
struct gui_tiled_layout tiled; struct gui_tiled_layout tiled;
const char *items[] = {"item0", "item1", "item2", "item3"};
enum gui_layout_format fmt = (demo->scaleable) ? GUI_DYNAMIC : GUI_STATIC; enum gui_layout_format fmt = (demo->scaleable) ? GUI_DYNAMIC : GUI_STATIC;
/* setup tiled layout */ /* setup tiled layout */
gui_tiled_begin(&tiled, fmt, 300, 150); gui_tiled_begin(&tiled, fmt, 250, 150);
if (!demo->scaleable) { if (!demo->scaleable) {
gui_tiled_slot(&tiled, GUI_SLOT_LEFT, 150, GUI_SLOT_VERTICAL, 4); gui_tiled_slot(&tiled, GUI_SLOT_LEFT, 100, GUI_SLOT_VERTICAL, 4);
gui_tiled_slot(&tiled, GUI_SLOT_RIGHT, 150, GUI_SLOT_VERTICAL, 4); gui_tiled_slot(&tiled, GUI_SLOT_RIGHT, 150, GUI_SLOT_VERTICAL, 4);
} else { } else {
gui_tiled_slot(&tiled, GUI_SLOT_LEFT, 0.50, GUI_SLOT_VERTICAL, 4); gui_tiled_slot(&tiled, GUI_SLOT_LEFT, 0.50, GUI_SLOT_VERTICAL, 4);
@ -559,20 +556,22 @@ widget_panel(struct gui_context *panel, struct state *demo)
/* setup widgets with tiled layout */ /* setup widgets with tiled layout */
gui_layout_row_tiled_begin(panel, &tiled); gui_layout_row_tiled_begin(panel, &tiled);
{ {
gui_uint i = 0;
gui_layout_row_tiled_push(panel, GUI_SLOT_LEFT, 1);
gui_label(panel, "Test0", GUI_TEXT_CENTERED);
gui_layout_row_tiled_push(panel, GUI_SLOT_LEFT, 2);
gui_label(panel, "Test1", GUI_TEXT_CENTERED);
for (i = 0; i < 4; ++i) { for (i = 0; i < 4; ++i) {
gui_layout_row_tiled_push(panel, GUI_SLOT_LEFT, i); const char *items[] = {"item0", "item1", "item2", "item3"};
gui_layout_row_tiled_push(panel, GUI_SLOT_RIGHT, i);
demo->list[i] = gui_button_toggle(panel, items[i], demo->list[i]); demo->list[i] = gui_button_toggle(panel, items[i], demo->list[i]);
} }
gui_layout_row_tiled_push(panel, GUI_SLOT_RIGHT, 1);
gui_label(panel, "Test0", GUI_TEXT_CENTERED);
gui_layout_row_tiled_push(panel, GUI_SLOT_RIGHT, 2);
gui_label(panel, "Test1", GUI_TEXT_CENTERED);
} }
gui_layout_row_tiled_end(panel); gui_layout_row_tiled_end(panel);
} }
/* item selection */ /* item selection */
if (!demo->scaleable) gui_layout_row_static(panel, 30, 30, 1); if (!demo->scaleable) gui_layout_row_static(panel, 30, 150, 1);
else gui_layout_row_dynamic(panel, 30, 1); else gui_layout_row_dynamic(panel, 30, 1);
demo->spinner = gui_spinner(panel, 0, demo->spinner, 250, 10, &demo->spinner_active); demo->spinner = gui_spinner(panel, 0, demo->spinner, 250, 10, &demo->spinner_active);
@ -585,7 +584,7 @@ widget_panel(struct gui_context *panel, struct state *demo)
check_combo_box(panel, demo->check_values, LEN(demo->check_values), &demo->checkcom); check_combo_box(panel, demo->check_values, LEN(demo->check_values), &demo->checkcom);
{ {
/* immediate mode custom row layout */ /* custom row layout by im */
enum gui_layout_format fmt = (demo->scaleable) ? GUI_DYNAMIC : GUI_STATIC; enum gui_layout_format fmt = (demo->scaleable) ? GUI_DYNAMIC : GUI_STATIC;
gui_layout_row_begin(panel, fmt, 30, 2); gui_layout_row_begin(panel, fmt, 30, 2);
{ {
@ -830,7 +829,8 @@ run_demo(struct demo_gui *gui)
} }
/* popup panel */ /* popup panel */
if (state->popup) { if (state->popup)
{
gui_popup_begin(&layout, &tab, GUI_POPUP_STATIC, 0, gui_rect(20, 100, 220, 150), gui_vec2(0,0)); gui_popup_begin(&layout, &tab, GUI_POPUP_STATIC, 0, gui_rect(20, 100, 220, 150), gui_vec2(0,0));
{ {
if (gui_header(&tab, "Popup", GUI_CLOSEABLE, GUI_CLOSEABLE, GUI_HEADER_LEFT)) { if (gui_header(&tab, "Popup", GUI_CLOSEABLE, GUI_CLOSEABLE, GUI_HEADER_LEFT)) {

View File

@ -275,7 +275,7 @@ main(int argc, char *argv[])
/* GUI */ /* GUI */
memset(&gui, 0, sizeof gui); memset(&gui, 0, sizeof gui);
gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY); gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY);
gui.font.userdata.ptr = vg; gui.font.userdata = gui_handle_ptr(vg);
gui.font.width = font_get_width; gui.font.width = font_get_width;
nvgTextMetrics(vg, NULL, NULL, &gui.font.height); nvgTextMetrics(vg, NULL, NULL, &gui.font.height);
init_demo(&gui); init_demo(&gui);
@ -304,7 +304,7 @@ main(int argc, char *argv[])
run_demo(&gui); run_demo(&gui);
/* Draw */ /* Draw */
glClearColor(0.8588f, 0.835f, 0.86f, 1.0f); glClearColor(0.4f, 0.4f, 0.4f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
draw(vg, &gui.queue, width, height); draw(vg, &gui.queue, width, height);
SDL_GL_SwapWindow(win); SDL_GL_SwapWindow(win);

View File

@ -17,14 +17,8 @@
#include <GL/glu.h> #include <GL/glu.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
/* macros */ /* macros */
#define DTIME 17 #define DTIME 17
#define FONT_ATLAS_DEPTH 4
#define MAX_DRAW_COMMAND_MEMORY (4 * 1024) #define MAX_DRAW_COMMAND_MEMORY (4 * 1024)
#define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MIN(a,b) ((a) < (b) ? (a) : (b))
@ -72,286 +66,6 @@ file_load(const char* path, size_t* siz)
return buf; return buf;
} }
/* ==============================================================
*
* Font
*
* ===============================================================*/
struct texCoord {
float u;
float v;
};
enum font_atlas_dimension {
FONT_ATLAS_DIM_64 = 64,
FONT_ATLAS_DIM_128 = 128,
FONT_ATLAS_DIM_256 = 256,
FONT_ATLAS_DIM_512 = 512,
FONT_ATLAS_DIM_1024 = 1024,
FONT_ATLAS_DIM_2048 = 2048
};
struct font_atlas {
enum font_atlas_dimension dim;
gui_size range;
gui_size size;
gui_byte *memory;
};
struct font_glyph {
unsigned int code;
float xadvance;
short width, height;
float xoff, yoff;
struct texCoord uv[2];
};
struct font {
float height;
float scale;
GLuint texture;
unsigned int glyph_count;
struct font_glyph *glyphes;
const struct font_glyph *fallback;
};
static void
font_load_glyph(unsigned int code, struct font_glyph *glyph, FT_GlyphSlot slot)
{
glyph->code = code;
glyph->width = (short)slot->bitmap.width;
glyph->height = (short)slot->bitmap.rows;
glyph->xoff = (float)slot->bitmap_left;
glyph->yoff = (float)slot->bitmap_top;
glyph->xadvance = (float)(slot->advance.x >> 6);
}
static void
font_load_glyphes(FT_Face face, struct font *font, size_t range)
{
size_t i;
int ft_err;
for (i = 0; i < range; ++i) {
unsigned int index = FT_Get_Char_Index(face, i);
if (!index) continue;
ft_err = FT_Load_Glyph(face, index, 0);
if (ft_err) continue;
ft_err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if (ft_err) continue;
font_load_glyph(index, &font->glyphes[i], face->glyph);
}
}
static void
font_pack_glyphes(struct font *font, float width, float height, size_t range)
{
size_t i;
float xoff = 0, yoff = 0;
float max_height = 0.0f;
for (i = 0; i < range; ++i) {
struct font_glyph *glyph = &font->glyphes[i];
if ((xoff + glyph->width) > width) {
yoff += max_height;
max_height = 0.0f;
xoff = 0.0f;
}
glyph->uv[0].u = xoff / width;
glyph->uv[0].v = yoff / height;
glyph->uv[1].u = (xoff + glyph->width) / width;
glyph->uv[1].v = (yoff + glyph->height) / height;
if (glyph->height > max_height)
max_height = glyph->height;
xoff += glyph->width;
}
}
static void
font_atlas_blit(struct font_atlas *atlas, FT_GlyphSlot glyph,
size_t off_x, size_t off_y)
{
size_t y, x;
size_t width = glyph->bitmap.width;
size_t height = glyph->bitmap.rows;
const size_t pitch = atlas->dim * FONT_ATLAS_DEPTH;
for (y = 0; y < height; y++) {
size_t x_off = off_x * FONT_ATLAS_DEPTH;
size_t y_off = (off_y + y) * pitch;
unsigned char *dst = &atlas->memory[y_off + x_off];
for (x = 0; x < width; ++x) {
dst[0] = 255;
dst[1] = 255;
dst[2] = 255;
dst[3] = glyph->bitmap.buffer[y * width + x];
dst += FONT_ATLAS_DEPTH;
}
}
}
static void
font_bake_glyphes(FT_Face face, struct font_atlas *atlas,
const struct font *font)
{
size_t i;
int ft_err;
for (i = 0; i < atlas->range; ++i) {
size_t x, y;
struct font_glyph *glyph = &font->glyphes[i];
unsigned int index = FT_Get_Char_Index(face, i);
if (!index) continue;
ft_err = FT_Load_Glyph(face, index, 0);
if (ft_err) continue;
ft_err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if (ft_err) continue;
x = (gui_size)(glyph->uv[0].u * (gui_float)atlas->dim);
y = (gui_size)(glyph->uv[0].v * (gui_float)atlas->dim);
font_atlas_blit(atlas, face->glyph, x, y);
}
}
static int
font_load(struct font *font, struct font_atlas *atlas, unsigned int height,
const unsigned char *data, size_t size)
{
int ret = 1;
FT_Library ft_lib;
FT_Face ft_face;
assert(font);
assert(atlas);
assert(font->glyphes);
assert(atlas->memory);
if (!font || !atlas)
return gui_false;
if (!font->glyphes || !atlas->memory)
return gui_false;
if (FT_Init_FreeType(&ft_lib))
return gui_false;
if (FT_New_Memory_Face(ft_lib, data, (FT_Long)size, 0, &ft_face))
goto cleanup;
if (FT_Select_Charmap(ft_face, FT_ENCODING_UNICODE))
goto failed;
if (FT_Set_Char_Size(ft_face, height << 6, height << 6, 96, 96))
goto failed;
font_load_glyphes(ft_face, font, atlas->range);
font_pack_glyphes(font, atlas->dim, atlas->dim, atlas->range);
font_bake_glyphes(ft_face, atlas, font);
failed:
FT_Done_Face(ft_face);
cleanup:
FT_Done_FreeType(ft_lib);
return ret;
}
static gui_size
font_get_text_width(gui_handle handle, const gui_char *t, gui_size l)
{
long unicode;
size_t text_width = 0;
const struct font_glyph *glyph;
size_t text_len = 0;
size_t glyph_len;
struct font *font = (struct font*)handle.ptr;
assert(font);
if (!t || !l) return 0;
glyph_len = gui_utf_decode(t, &unicode, l);
while (text_len < l && glyph_len) {
if (unicode == GUI_UTF_INVALID) return 0;
glyph = (unicode < font->glyph_count) ? &font->glyphes[unicode] : font->fallback;
glyph = (glyph->code == 0) ? font->fallback : glyph;
text_len += glyph_len;
text_width += (gui_size)((float)glyph->xadvance * font->scale);
glyph_len = gui_utf_decode(t + text_len, &unicode, l - text_len);
}
return text_width;
}
static void
font_query_font_glyph(gui_handle handle, struct gui_user_font_glyph *glyph,
gui_long unicode, gui_long next_codepoint)
{
const struct font_glyph *g;
const struct font *font = (struct font*)handle.ptr;
UNUSED(next_codepoint);
if (unicode == GUI_UTF_INVALID) return;
g = (unicode < font->glyph_count) ?
&font->glyphes[unicode] :
font->fallback;
g = (g->code == 0) ? font->fallback : g;
glyph->offset.x = g->xoff * font->scale;
glyph->offset.y = g->yoff * font->scale;
glyph->width = g->width * font->scale;
glyph->height = g->height * font->scale;
glyph->xadvance = g->xadvance * font->scale;
glyph->uv[0] = gui_vec2(g->uv[0].u, g->uv[0].v);
glyph->uv[1] = gui_vec2(g->uv[1].u, g->uv[1].v);
}
static void
font_del(struct font *font)
{
glDeleteTextures(1, &font->texture);
free(font->glyphes);
free(font);
}
static struct font*
font_new(const char *path, unsigned int font_height, unsigned int bake_height,
size_t range, enum font_atlas_dimension dim)
{
gui_byte *ttf_blob;
gui_size ttf_blob_size;
struct font_atlas atlas;
struct font *font = (struct font*)calloc(sizeof(struct font), 1);
atlas.dim = dim;
atlas.range = range;
atlas.size = atlas.dim * atlas.dim * FONT_ATLAS_DEPTH;
atlas.memory = (gui_byte*)calloc((gui_size)atlas.size, 1);
font->glyph_count = (unsigned int)atlas.range;
font->glyphes = (struct font_glyph*)calloc(atlas.range, sizeof(struct font_glyph));
font->fallback = &font->glyphes['?'];
font->scale = (float)font_height / (gui_float)bake_height;
font->height = (float)font_height;
ttf_blob = (unsigned char*)file_load(path, &ttf_blob_size);
if (!font_load(font, &atlas, bake_height, ttf_blob, ttf_blob_size))
goto failed;
glGenTextures(1, &font->texture);
glBindTexture(GL_TEXTURE_2D, font->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim, dim, 0,
GL_RGBA, GL_UNSIGNED_BYTE, atlas.memory);
free(atlas.memory);
free(ttf_blob);
return font;
failed:
free(atlas.memory);
free(ttf_blob);
font_del(font);
return NULL;
}
/* ==============================================================
*
* Draw
*
* ===============================================================*/
struct device { struct device {
GLuint vbo, vao, ebo; GLuint vbo, vao, ebo;
GLuint prog; GLuint prog;
@ -362,8 +76,7 @@ struct device {
GLint attrib_col; GLint attrib_col;
GLint uniform_tex; GLint uniform_tex;
GLint uniform_proj; GLint uniform_proj;
GLuint null_tex; GLuint font_tex;
struct gui_draw_list draw_list;
struct gui_draw_null_texture null; struct gui_draw_null_texture null;
struct gui_buffer cmds; struct gui_buffer cmds;
}; };
@ -466,24 +179,6 @@ device_init(struct device *dev)
glerror(); glerror();
} }
{
/* create default white texture which is needed to draw primitives. Any
texture with a white pixel would do but since I do not have one I have
to create one. */
void *mem = calloc(64*64*4, 1);
memset(mem, 255, 64*64*4);
glGenTextures(1, &dev->null_tex);
glBindTexture(GL_TEXTURE_2D, dev->null_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0,
GL_RGBA, GL_UNSIGNED_BYTE, mem);
free(mem);
glerror();
}
dev->null.texture.id = (gui_int)dev->null_tex;
dev->null.uv = gui_vec2(0.0f, 0.0f);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
@ -491,6 +186,92 @@ device_init(struct device *dev)
glerror(); glerror();
} }
static struct gui_user_font
font_bake_and_upload(struct device *dev, struct gui_font *font,
const char *path, unsigned int font_height, const gui_long *range)
{
gui_size glyph_count;
gui_size img_width, img_height;
struct gui_font_glyph *glyphes;
struct gui_baked_font baked_font;
struct gui_user_font user_font;
struct gui_recti custom;
memset(&baked_font, 0, sizeof(baked_font));
memset(&user_font, 0, sizeof(user_font));
memset(&custom, 0, sizeof(custom));
{
/* bake and upload font texture */
void *img, *tmp;
size_t ttf_size;
gui_size tmp_size, img_size;
const char *custom_data = "....";
struct gui_font_config config;
char *ttf_blob = file_load(path, &ttf_size);
if (!ttf_blob)
die("[Font]: %s is not a file or cannot be found!\n", path);
/* setup font configuration */
memset(&config, 0, sizeof(config));
config.ttf_blob = ttf_blob;
config.ttf_size = ttf_size;
config.font = &baked_font;
config.coord_type = GUI_COORD_UV;
config.range = range;
config.pixel_snap = gui_false;
config.size = font_height;
config.spacing = gui_vec2(0,0);
config.oversample_h = 3;
config.oversample_v = 1;
/* query needed amount of memory for the font baking process */
gui_font_bake_memory(&tmp_size, &glyph_count, &config, 1);
glyphes = (struct gui_font_glyph*)calloc(sizeof(struct gui_font_glyph), glyph_count);
tmp = calloc(1, tmp_size);
/* pack all glyphes and return needed image width height and memory size*/
custom.w = 2; custom.h = 2;
if (!gui_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1))
die("[Font]: failed to load font!\n");
/* bake all glyphes and custom white pixel into image */
img = calloc(1, img_size);
gui_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1);
gui_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X');
{
/* convert alpha8 image into rgba8 image */
void *img_rgba = calloc(4, img_height * img_width);
gui_font_bake_convert(img_rgba, (gui_ushort)img_width, (gui_ushort)img_height, img);
free(img);
img = img_rgba;
}
{
/* upload baked font image */
glGenTextures(1, &dev->font_tex);
glBindTexture(GL_TEXTURE_2D, dev->font_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, img);
}
free(ttf_blob);
free(tmp);
free(img);
}
/* default white pixel in a texture which is needed to draw primitives */
dev->null.texture.id = (gui_int)dev->font_tex;
dev->null.uv = gui_vec2((custom.x + 0.5f)/(gui_float)img_width, (custom.y + 0.5f)/(gui_float)img_height);
/* setup font with glyphes. IMPORTANT: the font only references the glyphes
this was done to have the possibility to have multible fonts with one
total glyph array. Not quite sure if it is a good thing since the
glyphes have to be freed as well. */
gui_font_init(font, font_height, '?', glyphes, &baked_font, dev->null.texture);
user_font = gui_font_ref(font);
return user_font;
}
static void static void
device_shutdown(struct device *dev) device_shutdown(struct device *dev)
{ {
@ -499,11 +280,15 @@ device_shutdown(struct device *dev)
glDeleteShader(dev->vert_shdr); glDeleteShader(dev->vert_shdr);
glDeleteShader(dev->frag_shdr); glDeleteShader(dev->frag_shdr);
glDeleteProgram(dev->prog); glDeleteProgram(dev->prog);
glDeleteTextures(1, &dev->null_tex); glDeleteTextures(1, &dev->font_tex);
glDeleteBuffers(1, &dev->vbo); glDeleteBuffers(1, &dev->vbo);
glDeleteBuffers(1, &dev->ebo); glDeleteBuffers(1, &dev->ebo);
} }
/* this is stupid but needed for C89 since sinf and cosf do not exist in that library version */
static gui_float fsin(gui_float f) {return (gui_float)sin(f);}
static gui_float fcos(gui_float f) {return (gui_float)cos(f);}
static void static void
device_draw(struct device *dev, struct gui_command_queue *queue, int width, int height) device_draw(struct device *dev, struct gui_command_queue *queue, int width, int height)
{ {
@ -557,6 +342,7 @@ device_draw(struct device *dev, struct gui_command_queue *queue, int width, int
glBindVertexArray(dev->vao); glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glBufferData(GL_ARRAY_BUFFER, max_vertex_memory, NULL, GL_STREAM_DRAW); glBufferData(GL_ARRAY_BUFFER, max_vertex_memory, NULL, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_memory, NULL, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_memory, NULL, GL_STREAM_DRAW);
glerror(); glerror();
@ -569,7 +355,7 @@ device_draw(struct device *dev, struct gui_command_queue *queue, int width, int
gui_buffer_init_fixed(&vbuf, vertexes, (gui_size)max_vertex_memory); gui_buffer_init_fixed(&vbuf, vertexes, (gui_size)max_vertex_memory);
gui_buffer_init_fixed(&ebuf, elements, (gui_size)max_element_memory); gui_buffer_init_fixed(&ebuf, elements, (gui_size)max_element_memory);
gui_draw_list_init(&draw_list, &dev->cmds, &vbuf, &ebuf, gui_draw_list_init(&draw_list, &dev->cmds, &vbuf, &ebuf,
sinf, cosf, dev->null, GUI_ANTI_ALIASING_ON); fsin, fcos, dev->null, GUI_ANTI_ALIASING_ON);
gui_draw_list_load(&draw_list, queue, 1.0f, 22); gui_draw_list_load(&draw_list, queue, 1.0f, 22);
} }
glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_ARRAY_BUFFER);
@ -595,17 +381,12 @@ device_draw(struct device *dev, struct gui_command_queue *queue, int width, int
glUseProgram((GLuint)last_prog); glUseProgram((GLuint)last_prog);
glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex); glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex);
glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo); glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo);
glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo);
glBindVertexArray((GLuint)last_vao); glBindVertexArray((GLuint)last_vao);
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
glerror(); glerror();
} }
/* ==============================================================
*
* APP
*
* ===============================================================*/
static void static void
key(struct gui_input *in, SDL_Event *evt, gui_bool down) key(struct gui_input *in, SDL_Event *evt, gui_bool down)
{ {
@ -675,7 +456,6 @@ main(int argc, char *argv[])
const char *font_path; const char *font_path;
SDL_Window *win; SDL_Window *win;
SDL_GLContext glContext; SDL_GLContext glContext;
struct font *glfont;
int win_width, win_height; int win_width, win_height;
unsigned int started; unsigned int started;
unsigned int dt; unsigned int dt;
@ -684,6 +464,8 @@ main(int argc, char *argv[])
/* GUI */ /* GUI */
struct device device; struct device device;
struct demo_gui gui; struct demo_gui gui;
struct gui_font font;
font_path = argv[1]; font_path = argv[1];
if (argc < 2) if (argc < 2)
die("Missing TTF Font file argument!"); die("Missing TTF Font file argument!");
@ -699,7 +481,6 @@ main(int argc, char *argv[])
/* OpenGL */ /* OpenGL */
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
glfont = font_new(font_path, 10, 10, 255, FONT_ATLAS_DIM_256);
glewExperimental = 1; glewExperimental = 1;
if (glewInit() != GLEW_OK) if (glewInit() != GLEW_OK)
die("Failed to setup GLEW\n"); die("Failed to setup GLEW\n");
@ -707,13 +488,10 @@ main(int argc, char *argv[])
/* GUI */ /* GUI */
memset(&gui, 0, sizeof gui); memset(&gui, 0, sizeof gui);
gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY); gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY);
gui.font.userdata.ptr = glfont;
gui.font.height = glfont->height;
gui.font.width = font_get_text_width;
gui.font.query = font_query_font_glyph;
gui.font.texture.id = (gui_int)glfont->texture;
mem = gui_buffer_alloc(&gui.memory, GUI_BUFFER_FRONT, MAX_DRAW_COMMAND_MEMORY, 0); mem = gui_buffer_alloc(&gui.memory, GUI_BUFFER_FRONT, MAX_DRAW_COMMAND_MEMORY, 0);
gui_buffer_init_fixed(&device.cmds, mem, MAX_DRAW_COMMAND_MEMORY); gui_buffer_init_fixed(&device.cmds, mem, MAX_DRAW_COMMAND_MEMORY);
gui.font = font_bake_and_upload(&device, &font, font_path, 14,
gui_font_default_glyph_ranges());
init_demo(&gui); init_demo(&gui);
device_init(&device); device_init(&device);
@ -757,8 +535,8 @@ main(int argc, char *argv[])
cleanup: cleanup:
/* Cleanup */ /* Cleanup */
free(font.glyphes);
free(gui_buffer_memory(&gui.memory)); free(gui_buffer_memory(&gui.memory));
font_del(glfont);
device_shutdown(&device); device_shutdown(&device);
SDL_GL_DeleteContext(glContext); SDL_GL_DeleteContext(glContext);
SDL_DestroyWindow(win); SDL_DestroyWindow(win);

View File

@ -468,7 +468,7 @@ main(int argc, char *argv[])
/* GUI */ /* GUI */
memset(&gui, 0, sizeof gui); memset(&gui, 0, sizeof gui);
gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY); gui_buffer_init_fixed(&gui.memory, calloc(MAX_MEMORY, 1), MAX_MEMORY);
gui.font.userdata.ptr = xw.font; gui.font.userdata = gui_handle_ptr(xw.font);
gui.font.height = (gui_float)xw.font->height; gui.font.height = (gui_float)xw.font->height;
gui.font.width = font_get_text_width; gui.font.width = font_get_text_width;
init_demo(&gui); init_demo(&gui);

640
gui.c
View File

@ -23,6 +23,7 @@
#define GUI_UTF_INVALID 0xFFFD #define GUI_UTF_INVALID 0xFFFD
#define GUI_MAX_NUMBER_BUFFER 64 #define GUI_MAX_NUMBER_BUFFER 64
#define GUI_UNUSED(x) ((void)(x))
#define GUI_LERP(a, b, t) ((a) + ((b) - (a)) * (t)) #define GUI_LERP(a, b, t) ((a) + ((b) - (a)) * (t))
#define GUI_SATURATE(x) (MAX(0, MIN(1.0f, x))) #define GUI_SATURATE(x) (MAX(0, MIN(1.0f, x)))
#define GUI_LEN(a) (sizeof(a)/sizeof(a)[0]) #define GUI_LEN(a) (sizeof(a)/sizeof(a)[0])
@ -75,6 +76,19 @@ static const long gui_utfmax[GUI_UTF_SIZE+1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0
* *
* =============================================================== * ===============================================================
*/ */
static gui_uint
gui_round_up_pow2(gui_uint v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static gui_float static gui_float
gui_inv_sqrt(gui_float number) gui_inv_sqrt(gui_float number)
{ {
@ -164,6 +178,22 @@ gui_rgb(gui_byte r, gui_byte g, gui_byte b)
return ret; return ret;
} }
gui_handle
gui_handle_ptr(void *ptr)
{
gui_handle handle;
handle.ptr = ptr;
return handle;
}
gui_handle
gui_handle_id(gui_int id)
{
gui_handle handle;
handle.id = id;
return handle;
}
struct gui_image struct gui_image
gui_subimage_ptr(void *ptr, gui_ushort w, gui_ushort h, struct gui_rect r) gui_subimage_ptr(void *ptr, gui_ushort w, gui_ushort h, struct gui_rect r)
{ {
@ -1706,7 +1736,8 @@ gui_draw_list_push_image(struct gui_draw_list *list, gui_handle texture)
gui_draw_list_push_command(list, gui_null_rect, list->null.texture); gui_draw_list_push_command(list, gui_null_rect, list->null.texture);
} else { } else {
struct gui_draw_command *prev = gui_draw_list_command_last(list); struct gui_draw_command *prev = gui_draw_list_command_last(list);
gui_draw_list_push_command(list, prev->clip_rect, texture); if (prev->texture.id != texture.id)
gui_draw_list_push_command(list, prev->clip_rect, texture);
} }
} }
@ -2268,6 +2299,10 @@ gui_draw_list_add_text(struct gui_draw_list *list, const struct gui_user_font *f
struct gui_user_font_glyph g; struct gui_user_font_glyph g;
GUI_ASSERT(list); GUI_ASSERT(list);
if (!list || !len || !text) return; if (!list || !len || !text) return;
if (rect.x > (list->clip_rect.x + list->clip_rect.w) ||
rect.y > (list->clip_rect.y + list->clip_rect.h) ||
rect.x < list->clip_rect.x || rect.y < list->clip_rect.y)
return;
/* draw text background */ /* draw text background */
gui_draw_list_add_rect_filled(list, rect, bg, 0.0f); gui_draw_list_add_rect_filled(list, rect, bg, 0.0f);
@ -2288,7 +2323,8 @@ gui_draw_list_add_text(struct gui_draw_list *list, const struct gui_user_font *f
/* calculate and draw glyph drawing rectangle and image */ /* calculate and draw glyph drawing rectangle and image */
gx = x + g.offset.x; gx = x + g.offset.x;
gy = rect.y + (font->height/2) + (rect.h/2) - g.offset.y; /*gy = rect.y + (font->height/2) + (rect.h/2) - g.offset.y;*/
gy = rect.y + (rect.h/2) - (font->height/2) + g.offset.y;
gw = g.width; gh = g.height; gw = g.width; gh = g.height;
char_width = g.xadvance; char_width = g.xadvance;
gui_draw_list_push_rect_uv(list, gui_vec2(gx,gy), gui_vec2(gx + gw, gy+ gh), gui_draw_list_push_rect_uv(list, gui_vec2(gx,gy), gui_vec2(gx + gw, gy+ gh),
@ -2452,6 +2488,563 @@ gui_draw_list_path_stroke(struct gui_draw_list *list, struct gui_color color,
gui_draw_list_path_clear(list); gui_draw_list_path_clear(list);
} }
#endif #endif
/*
* ==============================================================
*
* Font
*
* ===============================================================
*/
#ifdef GUI_COMPILE_WITH_FONT
/* this is a bloody mess but 'fixing' both stb libraries would be a pain */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wfloat-equal"
#pragma clang diagnostic ignored "-Wbad-function-cast"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#pragma clang diagnostic ignored "-Wdeclaration-after-statement"
#pragma clang diagnostic ignored "-Wunused-function"
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wbad-function-cast"
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
#pragma GCC diagnostic ignored "-Wtype-limits"
#pragma GCC diagnostic ignored "-Wswitch-default"
#pragma GCC diagnostic ignored "-Wunused-function"
#elif _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4456)
#endif
#ifndef GUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
#define STBRP_STATIC
#define STB_RECT_PACK_IMPLEMENTATION
#endif
#include "stb_rect_pack.h"
#ifndef GUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#endif
#include "stb_truetype.h"
#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop
#elif _MSC_VER
#pragma warning (pop)
#endif
struct gui_font_bake_data {
stbtt_fontinfo info;
stbrp_rect *rects;
stbtt_pack_range *ranges;
gui_uint range_count;
};
struct gui_font_baker {
stbtt_pack_context spc;
struct gui_font_bake_data *build;
stbtt_packedchar *packed_chars;
stbrp_rect *rects;
stbtt_pack_range *ranges;
};
static const gui_size gui_rect_align = GUI_ALIGNOF(stbrp_rect);
static const gui_size gui_range_align = GUI_ALIGNOF(stbtt_pack_range);
static const gui_size gui_char_align = GUI_ALIGNOF(stbtt_packedchar);
static const gui_size gui_build_align = GUI_ALIGNOF(struct gui_font_bake_data);
static const gui_size gui_baker_align = GUI_ALIGNOF(struct gui_font_baker);
static gui_size
gui_range_count(const gui_long *range)
{
const gui_long *iter = range;
GUI_ASSERT(range);
if (!range) return 0;
while (*(iter++) != 0);
return (iter == range) ? 0 : (gui_size)((iter - range)/2);
}
static gui_size
gui_range_glyph_count(const gui_long *range, gui_size count)
{
gui_size i = 0;
gui_size total_glyphes = 0;
for (i = 0; i < count; ++i) {
gui_size diff;
gui_long f = range[(i*2)+0];
gui_long t = range[(i*2)+1];
GUI_ASSERT(t >= f);
diff = (gui_size)((t - f) + 1);
total_glyphes += diff;
}
return total_glyphes;
}
const gui_long*
gui_font_default_glyph_ranges(void)
{
static const gui_long ranges[] = {0x0020, 0x00FF, 0};
return ranges;
}
const gui_long*
gui_font_chinese_glyph_ranges(void)
{
static const gui_long ranges[] = {
0x0020, 0x00FF,
0x3000, 0x30FF,
0x31F0, 0x31FF,
0xFF00, 0xFFEF,
0x4e00, 0x9FAF,
0
};
return ranges;
}
const gui_long*
gui_font_cyrillic_glyph_ranges(void)
{
static const gui_long ranges[] = {
0x0020, 0x00FF,
0x0400, 0x052F,
0x2DE0, 0x2DFF,
0xA640, 0xA69F,
0
};
return ranges;
}
void gui_font_bake_memory(gui_size *temp, gui_size *glyph_count,
struct gui_font_config *config, gui_size count)
{
gui_size i;
gui_size range_count = 0;
GUI_ASSERT(config);
GUI_ASSERT(glyph_count);
if (!config) {
*temp = 0;
*glyph_count = 0;
return;
}
*glyph_count = 0;
if (!config->range)
config->range = gui_font_default_glyph_ranges();
for (i = 0; i < count; ++i) {
range_count += gui_range_count(config[i].range);
*glyph_count += gui_range_glyph_count(config[i].range, range_count);
}
*temp = *glyph_count * sizeof(stbrp_rect);
*temp += range_count * sizeof(stbtt_pack_range);
*temp += *glyph_count * sizeof(stbtt_packedchar);
*temp += count * sizeof(struct gui_font_bake_data);
*temp += sizeof(struct gui_font_baker);
*temp += gui_rect_align + gui_range_align + gui_char_align;
*temp += gui_build_align + gui_baker_align;
}
static struct gui_font_baker*
gui_font_baker(void *memory, gui_size glyph_count, gui_size count)
{
struct gui_font_baker *baker;
if (!memory) return 0;
/* setup baker inside a memory block */
baker = (struct gui_font_baker*)GUI_ALIGN_PTR(memory, gui_baker_align);
baker->build = (struct gui_font_bake_data*)GUI_ALIGN_PTR((baker + 1), gui_build_align);
baker->packed_chars = (stbtt_packedchar*)GUI_ALIGN_PTR((baker->build + count), gui_char_align);
baker->rects = (stbrp_rect*)GUI_ALIGN_PTR((baker->packed_chars + glyph_count), gui_rect_align);
baker->ranges = (stbtt_pack_range*)GUI_ALIGN_PTR((baker->rects + glyph_count), gui_range_align);
return baker;
}
gui_bool
gui_font_bake_pack(gui_size *image_memory, gui_size *width, gui_size *height,
struct gui_recti *custom, void *temp, gui_size temp_size,
const struct gui_font_config *config, gui_size count)
{
static const gui_size max_height = 1024 * 32;
struct gui_font_baker* baker;
gui_size total_glyph_count = 0;
gui_size total_range_count = 0;
gui_size i = 0;
GUI_ASSERT(image_memory);
GUI_ASSERT(width);
GUI_ASSERT(height);
GUI_ASSERT(config);
GUI_ASSERT(temp);
GUI_ASSERT(temp_size);
GUI_ASSERT(count);
if (!image_memory || !width || !height || !config || !temp ||
!temp_size || !count) return gui_false;
for (i = 0; i < count; ++i) {
total_range_count += gui_range_count(config[i].range);
total_glyph_count += gui_range_glyph_count(config[i].range, total_range_count);
}
/* setup font baker from temporary memory */
gui_zero(temp, temp_size);
baker = gui_font_baker(temp, total_glyph_count, count);
if (!baker) return gui_false;
for (i = 0; i < count; ++i) {
const struct gui_font_config *cfg = &config[i];
if (!stbtt_InitFont(&baker->build[i].info, (const unsigned char*)cfg->ttf_blob, 0))
return gui_false;
}
*height = 0;
*width = (total_glyph_count > 1000) ? 1024 : 512;
stbtt_PackBegin(&baker->spc, NULL, (gui_int)*width, (int)max_height, 0, 1, 0);
{
gui_size input_i = 0;
gui_size range_n = 0, rect_n = 0, char_n = 0;
/* pack custom user data first so it will be in the upper left corner */
if (custom) {
stbrp_rect custom_space;
gui_zero(&custom_space, sizeof(custom_space));
custom_space.w = (stbrp_coord)((custom->w * 2) + 1);
custom_space.h = (stbrp_coord)(custom->h + 1);
stbtt_PackSetOversampling(&baker->spc, 1, 1);
stbrp_pack_rects((stbrp_context*)baker->spc.pack_info, &custom_space, 1);
*height = MAX(*height, (size_t)(custom_space.y + custom_space.h));
custom->x = custom_space.x;
custom->y = custom_space.y;
custom->w = custom_space.w;
custom->h = custom_space.h;
}
/* first font pass: pack all glyphes */
for (input_i = 0; input_i < count; input_i++) {
gui_size n = 0;
const gui_long *in_range;
const struct gui_font_config *cfg = &config[input_i];
struct gui_font_bake_data *tmp = &baker->build[input_i];
gui_size glyph_count, range_count;
/* count glyphes + ranges in current font */
glyph_count = 0; range_count = 0;
for (in_range = cfg->range; in_range[0] && in_range[1]; in_range += 2) {
glyph_count += (gui_size)(in_range[1] - in_range[0]) + 1;
range_count++;
}
/* setup ranges */
tmp->ranges = baker->ranges + range_n;
tmp->range_count = (gui_uint)range_count;
range_n += range_count;
for (i = 0; i < range_count; ++i) {
/*stbtt_pack_range *range = &tmp->ranges[i];*/
in_range = &cfg->range[i * 2];
tmp->ranges[i].font_size = cfg->size;
tmp->ranges[i].first_unicode_codepoint_in_range = (gui_int)in_range[0];
tmp->ranges[i].num_chars = (gui_int)(in_range[1]- in_range[0]) + 1;
tmp->ranges[i].chardata_for_range = baker->packed_chars + char_n;
char_n += (gui_size)tmp->ranges[i].num_chars;
}
/* pack */
tmp->rects = baker->rects + rect_n;
rect_n += glyph_count;
stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v);
n = (gui_size)stbtt_PackFontRangesGatherRects(&baker->spc, &tmp->info,
tmp->ranges, (gui_int)tmp->range_count, tmp->rects);
stbrp_pack_rects((stbrp_context*)baker->spc.pack_info, tmp->rects, (gui_int)n);
/* texture height */
for (i = 0; i < n; ++i) {
if (tmp->rects[i].was_packed)
*height = MAX(*height, (gui_size)(tmp->rects[i].y + tmp->rects[i].h));
}
}
GUI_ASSERT(rect_n == total_glyph_count);
GUI_ASSERT(char_n == total_glyph_count);
GUI_ASSERT(range_n == total_range_count);
}
*height = gui_round_up_pow2((gui_uint)*height);
*image_memory = (*width) * (*height);
return gui_true;
}
void
gui_font_bake(void *image_memory, gui_size width, gui_size height,
void *temp, gui_size temp_size, struct gui_font_glyph *glyphes,
gui_size glyphes_count, const struct gui_font_config *config, gui_size font_count)
{
gui_size input_i = 0;
struct gui_font_baker* baker;
gui_long glyph_n = 0;
GUI_ASSERT(image_memory);
GUI_ASSERT(width);
GUI_ASSERT(height);
GUI_ASSERT(config);
GUI_ASSERT(temp);
GUI_ASSERT(temp_size);
GUI_ASSERT(font_count);
GUI_ASSERT(glyphes_count);
if (!image_memory || !width || !height || !config || !temp ||
!temp_size || !font_count || !glyphes || !glyphes_count)
return;
/* second font pass: render glyphes */
baker = (struct gui_font_baker*)temp;
gui_zero(image_memory, width * height);
baker->spc.pixels = (unsigned char*)image_memory;
baker->spc.height = (gui_int)height;
for (input_i = 0; input_i < font_count; ++input_i) {
const struct gui_font_config *cfg = &config[input_i];
struct gui_font_bake_data *tmp = &baker->build[input_i];
stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v);
stbtt_PackFontRangesRenderIntoRects(&baker->spc, &tmp->info, tmp->ranges,
(gui_int)tmp->range_count, tmp->rects);
}
stbtt_PackEnd(&baker->spc);
/* third pass: setup font and glyphes */
for (input_i = 0; input_i < font_count; ++input_i) {
gui_size i = 0;
gui_int char_idx = 0;
gui_uint glyph_count = 0;
const struct gui_font_config *cfg = &config[input_i];
struct gui_font_bake_data *tmp = &baker->build[input_i];
struct gui_baked_font *dst_font = cfg->font;
gui_float font_scale = stbtt_ScaleForPixelHeight(&tmp->info, cfg->size);
gui_int unscaled_ascent, unscaled_descent, unscaled_line_gap;
stbtt_GetFontVMetrics(&tmp->info, &unscaled_ascent, &unscaled_descent,
&unscaled_line_gap);
/* fill baked font */
dst_font->ranges = cfg->range;
dst_font->height = cfg->size;
dst_font->ascent = ((gui_float)unscaled_ascent * font_scale);
dst_font->descent = ((gui_float)unscaled_descent * font_scale);
dst_font->glyph_offset = glyph_n;
/* fill own baked font glyph array */
for (i = 0; i < tmp->range_count; ++i) {
stbtt_pack_range *range = &tmp->ranges[i];
for (char_idx = 0; char_idx < range->num_chars; char_idx++) {
gui_int codepoint = 0;
gui_float dummy_x = 0, dummy_y = 0;
stbtt_aligned_quad q;
struct gui_font_glyph *glyph;
const stbtt_packedchar *pc = &range->chardata_for_range[char_idx];
glyph_count++;
if (!pc->x0 && !pc->x1 && !pc->y0 && !pc->y1) continue;
codepoint = range->first_unicode_codepoint_in_range + char_idx;
stbtt_GetPackedQuad(range->chardata_for_range, (gui_int)width,
(gui_int)height, char_idx, &dummy_x, &dummy_y, &q, 0);
/* fill own glyph type with data */
glyph = &glyphes[dst_font->glyph_offset + char_idx];
glyph->codepoint = (gui_long)codepoint;
glyph->x0 = q.x0; glyph->y0 = q.y0; glyph->x1 = q.x1; glyph->y1 = q.y1;
glyph->y0 += (dst_font->ascent + 0.5f);
glyph->y1 += (dst_font->ascent + 0.5f);
if (cfg->coord_type == GUI_COORD_PIXEL) {
glyph->u0 = q.s0 * (gui_float)width;
glyph->v0 = q.t0 * (gui_float)height;
glyph->u1 = q.s1 * (gui_float)width;
glyph->v1 = q.t1 * (gui_float)height;
} else {
glyph->u0 = q.s0;
glyph->v0 = q.t0;
glyph->u1 = q.s1;
glyph->v1 = q.t1;
}
glyph->xadvance = (pc->xadvance + cfg->spacing.x);
if (cfg->pixel_snap)
glyph->xadvance = (gui_float)(gui_int)(glyph->xadvance + 0.5f);
}
}
dst_font->glyph_count = glyph_count;
glyph_n += dst_font->glyph_count;
}
}
void
gui_font_bake_custom_data(void *img_memory, gui_size img_width, gui_size img_height,
struct gui_recti img_dst, const char *texture_data_mask, gui_size tex_width,
gui_size tex_height, gui_char white, gui_char black)
{
gui_byte *pixels;
gui_size y = 0, x = 0, n = 0;
GUI_ASSERT(img_memory);
GUI_ASSERT(img_width);
GUI_ASSERT(img_height);
GUI_ASSERT(texture_data_mask);
GUI_UNUSED(tex_height);
if (!img_memory || !img_width || !img_height || !texture_data_mask)
return;
pixels = (gui_byte*)img_memory;
for (y = 0, n = 0; y < tex_height; ++y) {
for (x = 0; x < tex_width; ++x, ++n) {
const gui_size off0 = (img_dst.x + x) + (img_dst.y + y) * img_width;
const gui_size off1 = off0 + 1 + tex_width;
pixels[off0] = (texture_data_mask[n] == white) ? 0xFF : 0x00;
pixels[off1] = (texture_data_mask[n] == black) ? 0xFF : 0x00;
}
}
}
void
gui_font_bake_convert(void *out_memory, gui_ushort img_width, gui_ushort img_height,
const void *in_memory)
{
gui_int n = 0;
const gui_byte *src;
gui_uint *dst;
GUI_ASSERT(out_memory);
GUI_ASSERT(in_memory);
GUI_ASSERT(img_width);
GUI_ASSERT(img_height);
if (!out_memory || !in_memory || !img_height || !img_width) return;
dst = (gui_uint*)out_memory;
src = (const gui_byte*)in_memory;
for (n = (gui_int)(img_width * img_height); n > 0; n--)
*dst++ = ((gui_uint)(*src++) << 24) | 0x00FFFFFF;
}
/*
* -------------------------------------------------------------
*
* Font
*
* --------------------------------------------------------------
*/
void
gui_font_init(struct gui_font *font, gui_float pixel_height,
gui_long fallback_codepoint, struct gui_font_glyph *glyphes,
const struct gui_baked_font *baked_font, gui_handle atlas)
{
GUI_ASSERT(font);
GUI_ASSERT(glyphes);
GUI_ASSERT(baked_font);
if (!font || !glyphes || !baked_font)
return;
gui_zero(font, sizeof(*font));
font->ascent = baked_font->ascent;
font->descent = baked_font->descent;
font->size = baked_font->height;
font->scale = (gui_float)pixel_height / (gui_float)font->size;
font->glyphes = &glyphes[baked_font->glyph_offset];
font->glyph_count = baked_font->glyph_count;
font->ranges = baked_font->ranges;
font->atlas = atlas;
font->fallback_codepoint = fallback_codepoint;
font->fallback = gui_font_find_glyph(font, fallback_codepoint);
}
const struct gui_font_glyph*
gui_font_find_glyph(struct gui_font *font, gui_long unicode)
{
gui_size i = 0;
gui_size count;
gui_size total_glyphes = 0;
const struct gui_font_glyph *glyph = 0;
GUI_ASSERT(font);
glyph = font->fallback;
count = gui_range_count(font->ranges);
for (i = 0; i < count; ++i) {
gui_size diff;
gui_long f = font->ranges[(i*2)+0];
gui_long t = font->ranges[(i*2)+1];
diff = (gui_size)((t - f) + 1);
if (unicode >= f && unicode <= t)
return &font->glyphes[(total_glyphes + (gui_size)(unicode - f))];
total_glyphes += diff;
}
return glyph;
}
static gui_size
gui_font_text_width(gui_handle handle, const gui_char *text, gui_size len)
{
gui_long unicode;
const struct gui_font_glyph *glyph;
struct gui_font *font;
gui_size text_len = 0;
gui_size text_width = 0;
gui_size glyph_len;
font = (struct gui_font*)handle.ptr;
GUI_ASSERT(font);
if (!font || !text || !len)
return 0;
glyph_len = gui_utf_decode(text, &unicode, len);
while (text_len < len && glyph_len) {
if (unicode == GUI_UTF_INVALID) return 0;
glyph = gui_font_find_glyph(font, unicode);
text_len += glyph_len;
text_width += (gui_size)((glyph->xadvance * font->scale));
glyph_len = gui_utf_decode(text + text_len, &unicode, len - text_len);
}
return text_width;
}
#if GUI_COMPILE_WITH_VERTEX_BUFFER
static void
gui_font_query_font_glyph(gui_handle handle, struct gui_user_font_glyph *glyph,
gui_long codepoint, gui_long next_codepoint)
{
const struct gui_font_glyph *g;
struct gui_font *font;
GUI_ASSERT(glyph);
GUI_UNUSED(next_codepoint);
font = (struct gui_font*)handle.ptr;
GUI_ASSERT(font);
if (!font || !glyph)
return;
g = gui_font_find_glyph(font, codepoint);
glyph->width = (g->x1 - g->x0) * font->scale;
glyph->height = (g->y1 - g->y0) * font->scale;
glyph->offset = gui_vec2(g->x0 * font->scale, g->y0 * font->scale);
glyph->xadvance = (g->xadvance * font->scale);
glyph->uv[0] = gui_vec2(g->u0, g->v0);
glyph->uv[1] = gui_vec2(g->u1, g->v1);
}
#endif
struct gui_user_font
gui_font_ref(struct gui_font *font)
{
struct gui_user_font user_font;
gui_zero(&user_font, sizeof(user_font));
user_font.height = font->size * font->scale;
user_font.width = gui_font_text_width;
user_font.userdata.ptr = font;
#if GUI_COMPILE_WITH_VERTEX_BUFFER
user_font.query = gui_font_query_font_glyph;
user_font.texture = font->atlas;
#endif
return user_font;
}
#endif
/* /*
* ============================================================== * ==============================================================
* *
@ -2488,7 +3081,6 @@ gui_edit_buffer_insert(gui_edit_buffer *buffer, gui_size pos,
gui_edit_buffer_append(buffer, str, len); gui_edit_buffer_append(buffer, str, len);
return 1; return 1;
} }
mem = gui_buffer_alloc(buffer, GUI_BUFFER_FRONT, len * sizeof(char), 0); mem = gui_buffer_alloc(buffer, GUI_BUFFER_FRONT, len * sizeof(char), 0);
if (!mem) return 0; if (!mem) return 0;
@ -4114,7 +4706,6 @@ void
gui_tiled_slot_bounds(struct gui_rect *bounds, gui_tiled_slot_bounds(struct gui_rect *bounds,
const struct gui_tiled_layout *layout, enum gui_tiled_layout_slot_index slot) const struct gui_tiled_layout *layout, enum gui_tiled_layout_slot_index slot)
{ {
gui_float width, height;
const struct gui_tiled_slot *s; const struct gui_tiled_slot *s;
GUI_ASSERT(layout); GUI_ASSERT(layout);
if (!layout) return; if (!layout) return;
@ -4162,7 +4753,6 @@ gui_tiled_bounds(struct gui_rect *bounds, const struct gui_tiled_layout *layout,
void void
gui_tiled_end(struct gui_tiled_layout *layout) gui_tiled_end(struct gui_tiled_layout *layout)
{ {
gui_float w;
gui_float centerh, centerv; gui_float centerh, centerv;
const struct gui_tiled_slot *top, *bottom; const struct gui_tiled_slot *top, *bottom;
const struct gui_tiled_slot *left, *right; const struct gui_tiled_slot *left, *right;
@ -4227,7 +4817,6 @@ gui_window_init(struct gui_window *window, struct gui_rect bounds,
gui_command_queue_insert_back(queue, &window->buffer); gui_command_queue_insert_back(queue, &window->buffer);
} }
} }
void void
gui_window_set_config(struct gui_window *panel, const struct gui_style *config) gui_window_set_config(struct gui_window *panel, const struct gui_style *config)
{ {
@ -4327,11 +4916,11 @@ gui_window_is_minimized(struct gui_window *panel)
{return panel->flags & GUI_WINDOW_MINIMIZED;} {return panel->flags & GUI_WINDOW_MINIMIZED;}
/* /*
* ============================================================== * -------------------------------------------------------------
* *
* Context * Context
* *
* =============================================================== * --------------------------------------------------------------
*/ */
void void
gui_begin(struct gui_context *context, struct gui_window *window) gui_begin(struct gui_context *context, struct gui_window *window)
@ -4493,7 +5082,6 @@ gui_begin_tiled(struct gui_context *context, struct gui_window *window,
struct gui_tiled_layout *tiled, enum gui_tiled_layout_slot_index slot, struct gui_tiled_layout *tiled, enum gui_tiled_layout_slot_index slot,
gui_uint index) gui_uint index)
{ {
struct gui_command_queue *queue;
GUI_ASSERT(context); GUI_ASSERT(context);
GUI_ASSERT(window); GUI_ASSERT(window);
GUI_ASSERT(tiled); GUI_ASSERT(tiled);
@ -5368,26 +5956,9 @@ gui_layout_row_tiled_begin(struct gui_context *layout,
layout->row.tiled = tiled; layout->row.tiled = tiled;
gui_panel_layout(layout, tiled->height, 2); gui_panel_layout(layout, tiled->height, 2);
if (tiled->fmt == GUI_STATIC) { layout->row.type = (tiled->fmt == GUI_STATIC) ?
/* calculate bounds of the tiled layout */ GUI_LAYOUT_STATIC_TILED:
struct gui_rect clip, space; GUI_LAYOUT_DYNAMIC_TILED;
space.x = layout->at_x;
space.y = layout->at_y;
space.w = layout->width;
space.h = layout->row.height;
/* setup clipping rect for the free space to prevent overdraw */
gui_unify(&clip, &layout->clip, space.x,space.y,space.x+space.w,space.y+space.h);
gui_command_buffer_push_scissor(layout->buffer, clip);
layout->row.clip = layout->clip;
layout->clip = clip;
layout->row.type = GUI_LAYOUT_STATIC_TILED;
} else layout->row.type = GUI_LAYOUT_DYNAMIC_TILED;
layout->row.ratio = 0;
layout->row.item_width = 0;
layout->row.item_offset = 0;
layout->row.filled = 0;
} }
void void
@ -5452,13 +6023,9 @@ gui_layout_row_tiled_end(struct gui_context *layout)
if (!layout) return; if (!layout) return;
if (!layout->valid) return; if (!layout->valid) return;
layout->row.item_width = 0;
layout->row.item_height = 0;
layout->row.item_offset = 0;
gui_zero(&layout->row.item, sizeof(layout->row.item)); gui_zero(&layout->row.item, sizeof(layout->row.item));
if (layout->row.tiled->fmt == GUI_STATIC)
gui_command_buffer_push_scissor(layout->buffer, layout->clip);
layout->row.tiled = 0; layout->row.tiled = 0;
layout->row.columns = 0;
} }
static void static void
@ -5517,6 +6084,7 @@ gui_panel_alloc_space(struct gui_rect *bounds, struct gui_context *layout)
layout->row.filled += layout->row.item_width; layout->row.filled += layout->row.item_width;
layout->row.index = 0; layout->row.index = 0;
} break; } break;
case GUI_LAYOUT_STATIC_TILED:
case GUI_LAYOUT_DYNAMIC_TILED: { case GUI_LAYOUT_DYNAMIC_TILED: {
/* dynamic tiled layout widget placing */ /* dynamic tiled layout widget placing */
bounds->x = layout->at_x + layout->row.item.x + padding.x; bounds->x = layout->at_x + layout->row.item.x + padding.x;
@ -5569,9 +6137,6 @@ gui_panel_alloc_space(struct gui_rect *bounds, struct gui_context *layout)
layout->row.item_offset += item_width + spacing.x; layout->row.item_offset += item_width + spacing.x;
layout->row.index = 0; layout->row.index = 0;
} break; } break;
case GUI_LAYOUT_STATIC_TILED:
/* static tiled layout widget placing */
layout->row.index = 0;
case GUI_LAYOUT_STATIC_FREE: { case GUI_LAYOUT_STATIC_FREE: {
/* free widget placing */ /* free widget placing */
bounds->x = layout->clip.x + layout->row.item.x; bounds->x = layout->clip.x + layout->row.item.x;
@ -6612,7 +7177,6 @@ gui_graph_callback(struct gui_context *layout, enum gui_graph_type type,
gui_graph_end(layout, &graph); gui_graph_end(layout, &graph);
return index; return index;
} }
/* /*
* ------------------------------------------------------------- * -------------------------------------------------------------
* *

354
gui.h
View File

@ -54,10 +54,10 @@ extern "C" {
and the module of the library will not be compiled */ and the module of the library will not be compiled */
#define GUI_COMPILE_WITH_FONT 1 #define GUI_COMPILE_WITH_FONT 1
/* setting this define to 1 adds the `stb_truetype` and `stb_rect_pack` header /* setting this define to 1 adds the `stb_truetype` and `stb_rect_pack` header
to this library and provided a default font for font loading and rendering. to this library and provides a default font for font loading and rendering.
If you already have font handling or do not want to use this font handler If you already have font handling or do not want to use this font handler
you can just set this define to zero and the font module will not be compiled you can just set this define to zero and the font module will not be compiled
and the two header will not be needed. */ and the two headers will not be needed. */
/* /*
* ============================================================== * ==============================================================
* *
@ -109,6 +109,7 @@ struct gui_color {gui_byte r,g,b,a;};
struct gui_vec2 {gui_float x,y;}; struct gui_vec2 {gui_float x,y;};
struct gui_vec2i {gui_short x, y;}; struct gui_vec2i {gui_short x, y;};
struct gui_rect {gui_float x,y,w,h;}; struct gui_rect {gui_float x,y,w,h;};
struct gui_recti {gui_ushort x,y,w,h;};
typedef gui_char gui_glyph[GUI_UTF_SIZE]; typedef gui_char gui_glyph[GUI_UTF_SIZE];
struct gui_key {gui_bool down; gui_uint clicked;}; struct gui_key {gui_bool down; gui_uint clicked;};
typedef union {void *ptr; gui_int id;} gui_handle; typedef union {void *ptr; gui_int id;} gui_handle;
@ -120,12 +121,6 @@ enum gui_collapse_states {GUI_MINIMIZED = gui_false, GUI_MAXIMIZED = gui_true};
struct gui_user_font; struct gui_user_font;
struct gui_edit_box; struct gui_edit_box;
struct gui_user_font_glyph; struct gui_user_font_glyph;
typedef gui_bool(*gui_filter)(gui_long unicode);
typedef void(*gui_paste_f)(gui_handle, struct gui_edit_box*);
typedef void(*gui_copy_f)(gui_handle, const char*, gui_size size);
typedef gui_size(*gui_text_width_f)(gui_handle, const gui_char*, gui_size);
typedef void(*gui_query_font_glyph_f)(gui_handle, struct gui_user_font_glyph*,
gui_long codepoint, gui_long next_codepoint);
/* /*
* ============================================================== * ==============================================================
* *
@ -167,6 +162,8 @@ struct gui_rect gui_get_null_rect(void);
gui_size gui_utf_decode(const gui_char*, gui_long*, gui_size); gui_size gui_utf_decode(const gui_char*, gui_long*, gui_size);
gui_size gui_utf_encode(gui_long, gui_char*, gui_size); gui_size gui_utf_encode(gui_long, gui_char*, gui_size);
gui_size gui_utf_len(const gui_char*, gui_size len); gui_size gui_utf_len(const gui_char*, gui_size len);
gui_handle gui_handle_ptr(void*);
gui_handle gui_handle_id(gui_int);
struct gui_image gui_image_ptr(void*); struct gui_image gui_image_ptr(void*);
struct gui_image gui_image_id(gui_int); struct gui_image gui_image_id(gui_int);
struct gui_image gui_subimage_ptr(void*, gui_ushort w, gui_ushort h, struct gui_rect); struct gui_image gui_subimage_ptr(void*, gui_ushort w, gui_ushort h, struct gui_rect);
@ -326,12 +323,14 @@ void gui_input_char(struct gui_input*, char);
*/ */
void gui_input_end(struct gui_input*); void gui_input_end(struct gui_input*);
/* this function sets the input state to readable */ /* this function sets the input state to readable */
gui_bool gui_input_has_mouse_click_in_rect(const struct gui_input*, enum gui_buttons, struct gui_rect); gui_bool gui_input_has_mouse_click_in_rect(const struct gui_input*,enum gui_buttons,
struct gui_rect);
/* this function returns true if a mouse click inside a rectangle occured in prev frames */ /* this function returns true if a mouse click inside a rectangle occured in prev frames */
gui_bool gui_input_has_mouse_click_down_in_rect(const struct gui_input*, enum gui_buttons, gui_bool gui_input_has_mouse_click_down_in_rect(const struct gui_input*, enum gui_buttons,
struct gui_rect, gui_bool down); struct gui_rect, gui_bool down);
/* this function returns true if a mouse down click inside a rectangle occured in prev frames */ /* this function returns true if a mouse down click inside a rectangle occured in prev frames */
gui_bool gui_input_is_mouse_click_in_rect(const struct gui_input*, enum gui_buttons, struct gui_rect); gui_bool gui_input_is_mouse_click_in_rect(const struct gui_input*, enum gui_buttons,
struct gui_rect);
/* this function returns true if a mouse click inside a rectangle occured and was just clicked */ /* this function returns true if a mouse click inside a rectangle occured and was just clicked */
gui_bool gui_input_is_mouse_prev_hovering_rect(const struct gui_input*, struct gui_rect); gui_bool gui_input_is_mouse_prev_hovering_rect(const struct gui_input*, struct gui_rect);
/* this function returns true if the mouse hovers over a rectangle */ /* this function returns true if the mouse hovers over a rectangle */
@ -814,8 +813,10 @@ const struct gui_command *gui_command_buffer_next(struct gui_command_buffer*,
simplifies and reduces the amount of code needed with just using command buffers. simplifies and reduces the amount of code needed with just using command buffers.
Internally the command queue has a list of command buffers which can be Internally the command queue has a list of command buffers which can be
modified to create a certain sequence, for example the `gui_window_begin` modified to create a certain sequence, for example the `gui_begin`
function changes the list to create overlapping windows. function changes the list to create overlapping windows, while `gui_begin_tiled`
always draws all its windows in the background by pushing its buffers to the
beginning of the list.
USAGE USAGE
---------------------------- ----------------------------
@ -885,7 +886,7 @@ struct gui_command_queue {
struct gui_command_buffer_list list; struct gui_command_buffer_list list;
/* list of each memory buffer inside the queue */ /* list of each memory buffer inside the queue */
struct gui_command_sub_buffer_stack stack; struct gui_command_sub_buffer_stack stack;
/* subbuffer stack for overlapping child windows in windows */ /* subbuffer stack for overlapping popup windows in windows */
gui_bool build; gui_bool build;
/* flag indicating if a complete command list was build inside the queue*/ /* flag indicating if a complete command list was build inside the queue*/
}; };
@ -1060,7 +1061,9 @@ struct gui_draw_vertex {
enum gui_draw_list_stroke { enum gui_draw_list_stroke {
GUI_STROKE_OPEN = gui_false, GUI_STROKE_OPEN = gui_false,
/* build up path has no connection back to the beginning */
GUI_STROKE_CLOSED = gui_true GUI_STROKE_CLOSED = gui_true
/* build up path has a connection back to the beginning */
}; };
struct gui_draw_command { struct gui_draw_command {
@ -1288,12 +1291,72 @@ void gui_draw_list_path_stroke(struct gui_draw_list*, struct gui_color,
* *
* =============================================================== * ===============================================================
*/ */
/* FONT
----------------------------
Font handling in this library can be achived in three different ways.
The first and simplest ways is by just using your font handling mechanism
and provide a simple callback for text string width calculation with
`gui_user_font`. This requires the default drawing output
and is not possible for the optional vertex buffer output.
The second way of font handling is by using the same `gui_user_font` struct
to reference a font as before but providing a second callback for
`gui_user_font_glyph` querying which is used for text drawing in the optional vertex
buffer output. In addition to the callback it is also required to provide
a texture atlas from the font to draw.
The final and most complex way is to use the optional font baker
and font handling function, which requires two additional headers for
TTF font baking. While the previous two methods did no need any functions
outside callbacks and are therefore rather simple to handle, the final
font handling method quite complex and you need to handle the complex
font baking API. The reason why it is complex is because there are multible
ways of using the API. For example it must be possible to use the font
for default command output as well as vertex buffer output. So for example
texture coordinates can either be UV for vertex buffer output or absolute pixel
for drawing function based on pixels. Furthermore it is possible to incoperate
custom user data into the resulting baked image (for example a white pixel for the
vertex buffer output). In addition and probably the most complex aspect of
the baking API was to incoperate baking of multible fonts into one image.
In general the font baking API can be understood as having a number of
loaded in memory TTF-fonts, font baking configuration and optional custom
render data as input, while the output is made of font specific data, a big
glyph array of all baked glyphes and the baked image. The API
was designed that way to have a typical file format and not
a perfectly ready in memory library instance of a font. The reason is more
control and seperates the font baking code from the in library used font format.
USAGE
----------------------------
font baking functions
gui_font_bake_memory -- calculates the needed font baking memory
gui_font_bake_pack -- packs all glyphes and calculates needed image memory
gui_font_bake -- renders all glyphes inside an image and setups glyphes
gui_font_bake_custom_data -- renders custom user data into the image
gui_font_bake_convert -- converts the baked image from alpha8 to rgba8
font functions
gui_font_init -- initilizes the font
gui_font_ref -- create a user font out of the font
gui_font_find_glyph -- finds and returns a glyph from the font
*/
typedef gui_size(*gui_text_width_f)(gui_handle, const gui_char*, gui_size);
typedef void(*gui_query_font_glyph_f)(gui_handle, struct gui_user_font_glyph*,
gui_long codepoint, gui_long next_codepoint);
#if GUI_COMPILE_WITH_VERTEX_BUFFER
struct gui_user_font_glyph { struct gui_user_font_glyph {
struct gui_vec2 uv[2]; struct gui_vec2 uv[2];
/* texture coordinates */
struct gui_vec2 offset; struct gui_vec2 offset;
/* offset between top left and glyph */
gui_float width, height; gui_float width, height;
/* size of the glyph */
gui_float xadvance; gui_float xadvance;
/* offset to the next glyph */
}; };
#endif
struct gui_user_font { struct gui_user_font {
gui_handle userdata; gui_handle userdata;
@ -1311,31 +1374,187 @@ struct gui_user_font {
}; };
#ifdef GUI_COMPILE_WITH_FONT #ifdef GUI_COMPILE_WITH_FONT
struct gui_font_atlas { enum gui_font_coord_type {
gui_byte *pixels; GUI_COORD_UV,
gui_int tex_width; /* texture coordinates inside font glyphes are clamped between 0.0f and 1.0f */
gui_int tex_height; GUI_COORD_PIXEL
struct gui_vec2 white_pixel; /* texture coordinates inside font glyphes are in absolute pixel */
};
struct gui_baked_font {
gui_float height;
/* height of the font */
gui_float ascent, descent;
/* font glyphes ascent and descent */
gui_long glyph_offset;
/* glyph array offset inside the font glyph baking output array */
gui_long glyph_count;
/* number of glyphes of this font inside the glyph baking array output */
const gui_long *ranges;
/* font codepoint ranges as pairs of (from/to) and 0 as last element */
};
struct gui_font_config {
void *ttf_blob;
/* pointer to loaded TTF file memory block */
gui_size ttf_size;
/* size of the loaded TTF file memory block */
gui_float size;
/* bake pixel height of the font */
gui_uint oversample_h, oversample_v;
/* rasterize at hight quality for sub-pixel position */
gui_bool pixel_snap;
/* align very character to pixel boundry (if true set oversample (1,1)) */
enum gui_font_coord_type coord_type;
/* baked glyph texture coordinate format with either pixel or UV coordinates */
struct gui_vec2 spacing;
/* extra pixel spacing between glyphs */
const gui_long *range;
/* list of unicode ranges (2 values per range, zero terminated) */
struct gui_baked_font *font;
/* font to setup in the baking process */
}; };
struct gui_font_glyph { struct gui_font_glyph {
gui_long codepoint; gui_long codepoint;
/* unicode codepoint */
gui_float xadvance; gui_float xadvance;
struct gui_vec2 point[2]; /* xoffset to the next character */
struct gui_vec2 uv[2]; gui_float x0, y0, x1, y1;
/* glyph bounding points in pixel inside the glyph image with top left and bottom right */
gui_float u0, v0, u1, v1;
/* texture coordinates either in pixel or clamped (0.0 - 1.0) */
}; };
struct gui_font { struct gui_font {
gui_float size; gui_float size;
/* pixel height of the font */
gui_float scale; gui_float scale;
struct gui_vec2 offset; /* scale factor for different font size */
gui_float ascent, descent; gui_float ascent, descent;
struct gui_buffer glyphes; /* font ascent and descent */
gui_long fallback_code; struct gui_font_glyph *glyphes;
struct gui_font_glyph *fallback_glyph; /* font glyph array */
gui_float fallback_xadvance; const struct gui_font_glyph *fallback;
struct gui_user_font handle; /* fallback glyph */
gui_long fallback_codepoint;
/* fallback glyph codepoint */
gui_long glyph_count;
/* font glyph array size */
const gui_long *ranges;
/* glyph unicode ranges in the font */
gui_handle atlas;
/* font image atlas handle */
}; };
/* some language glyph codepoint ranges */
const gui_long *gui_font_default_glyph_ranges(void);
const gui_long *gui_font_chinese_glyph_ranges(void);
const gui_long *gui_font_cyrillic_glyph_ranges(void);
/* ---------------------------------------------------------------
* Baking
* ---------------------------------------------------------------*/
/* font baking functions (need to be called sequentially top to bottom) */
void gui_font_bake_memory(gui_size *temporary_memory, gui_size *glyph_count,
struct gui_font_config*, gui_size count);
/* this function calculates the needed memory for the baking process
Input:
- array of configuration for every font that should be baked into one image
- number of configuration fonts in the array
Output:
- amount of memory needed in the baking process
- total number of glyphes that need to be allocated
*/
gui_bool gui_font_bake_pack(gui_size *img_memory, gui_size *img_width, gui_size *img_height,
struct gui_recti *custom_space,
void *temporary_memory, gui_size temporary_size,
const struct gui_font_config*, gui_size font_count);
/* this function packs together all glyphes and an optional space into one
total image space and returns the needed image width and height.
Input:
- NULL or custom space inside the image with width and height will be updated!
- temporary memory block that will be used in the baking process
- size of the temporary memory block
- array of configuration for every font that should be baked into one image
- number of configuration fonts in the array
Output:
- calculated resulting size of the image in bytes
- pixel width of the resulting image
- pixel height of the resulting image
- custom space bounds with position and size inside image which can be filled by the user
*/
void gui_font_bake(void *image_memory, gui_size image_width, gui_size image_height,
void *temporary_memory, gui_size temporary_memory_size,
struct gui_font_glyph*, gui_size glyphes_count,
const struct gui_font_config*, gui_size font_count);
/* this function bakes all glyphes into the pre-allocated image and
fills a glyph array with information.
Input:
- image memory buffer to bake the glyph into
- pixel width/height of the image
- temporary memory block that will be used in the baking process
- size of the temporary memory block
Output:
- image filled with glyphes
- filled glyph array
*/
void gui_font_bake_custom_data(void *img_memory, gui_size img_width, gui_size img_height,
struct gui_recti img_dst, const char *texture_data_mask,
gui_size tex_width, gui_size tex_height,
gui_char white, gui_char black);
/* this function bakes custom data in string format with white, black and zero
alpha pixels into the font image. The zero alpha pixel is represented as
any character beside the black and zero pixel character.
Input:
- image memory buffer to bake the custom data into
- image size (width/height) of the image in pixels
- custom texture data in string format
- texture size (width/height) of the custom image content
- character representing a white pixel in the texture data format
- character representing a black pixel in the texture data format
Output:
- image filled with custom texture data
*/
void gui_font_bake_convert(void *out_memory, gui_ushort img_width, gui_ushort img_height,
const void *in_memory);
/* this function converts the alpha8 baking input image into a
preallocated rgba8 output image.
Input:
- image pixel size (width, height)
- memory block containing the alpha8 image
Output:
- rgba8 output image
*/
/* ---------------------------------------------------------------
* Font
* ---------------------------------------------------------------*/
void gui_font_init(struct gui_font*, gui_float pixel_height,
gui_long fallback_codepoint, struct gui_font_glyph*,
const struct gui_baked_font*, gui_handle atlas);
/* this function initializes a font. IMPORTANT: The font only references
* its glyphes since it allows to have multible font glyph in one big array.
Input:
- pixel height of the font can be different than the baked font height
- unicode fallback codepoint for a glyph that will be used if a glyph is requested
that does not exist in the font
- glyph array of all glyphes inside the font
- number of glyphes inside the glyph array
- font information for this font from the baking process
- handle to the baked font image atlas
*/
struct gui_user_font gui_font_ref(struct gui_font*);
/* this function
Output:
- gui font handle used in the library
*/
const struct gui_font_glyph* gui_font_find_glyph(struct gui_font*, gui_long unicode);
/* this function
Input:
- unicode glyph codepoint of the glyph
Output:
- either the glyph with the given codepoint or a fallback glyph if does not exist
*/
#endif #endif
/* /*
* =============================================================== * ===============================================================
@ -1377,6 +1596,10 @@ struct gui_font {
gui_edit_box_len_char -- returns the length of the string in bytes gui_edit_box_len_char -- returns the length of the string in bytes
gui_edit_box_len -- returns the length of the string in glyphes gui_edit_box_len -- returns the length of the string in glyphes
*/ */
typedef gui_bool(*gui_filter)(gui_long unicode);
typedef void(*gui_paste_f)(gui_handle, struct gui_edit_box*);
typedef void(*gui_copy_f)(gui_handle, const char*, gui_size size);
struct gui_clipboard { struct gui_clipboard {
gui_handle userdata; gui_handle userdata;
/* user memory for callback */ /* user memory for callback */
@ -2241,14 +2464,48 @@ void gui_style_reset(struct gui_style*);
* *
* =============================================================== * ===============================================================
TILING TILING
----------------------------
Tiling provides a way to divide a space into fixed slots which again can be
divided into either horizontal or vertical spaces. The tiled layout consists
of five slots (Top, Left, Center, Right and Bottom) what is also known
as a Border layout. Since slots can either be filled or empty, horizontally or
vertically fillable, have either none, one or many subspaces and can even
have a tiled layout inside it is possible to achive a great number of layout.
In addition it is possible to define the space inside the tiled layout either
in pixel or as a ratio. Ratio based layout are great for scaling but
are less usefull in cases where scaling destroys the UI. Pixel based layouts
provided a layout which always look the same but are less flexible.
The tiled layout is used in the library inside windows as a widget layout
and for window placing inside the screen with each case having its own functions
to handle and use the tiled layout.
USAGE
----------------------------
To use the tiled layout you have to define which slot inside the layout
should be active, how the slot should be filled and how much space the
it takes. To define each slot you first have to call `gui_tiled_begin`
to start the layout slot definiton and to end the definition
the corresponding function `gui_tiled_end` has to be called.
Between both sequence points you can define each slot with `gui_tiled_slot`.
-----------------------------
| TOP |
-----------------------------
| | | |
| LEFT | CENTER | RIGHT |
| | | |
-----------------------------
| BOTTOM |
-----------------------------
tiling API tiling API
----------------------------
gui_tiled_begin -- starts the definition of a tiled layout gui_tiled_begin -- starts the definition of a tiled layout
gui_tiled_slot_bounds -- returns the bounds of a slot in the tiled layout
gui_tiled_bounds -- returns the bounds of a widget in the tiled layout
gui_tiled_slot -- activates and setups a slot inside the tile layout gui_tiled_slot -- activates and setups a slot inside the tile layout
gui_tiled_end -- ends the definiition of the tiled layout slots gui_tiled_end -- ends the definiition of the tiled layout slots
gui_tiled_slot_bounds -- returns the bounds of a slot in the tiled layout
gui_tiled_bounds -- returns the bounds of a widget in the tiled layout
*/ */
enum gui_tiled_layout_slot_index { enum gui_tiled_layout_slot_index {
GUI_SLOT_TOP, GUI_SLOT_TOP,
@ -2301,6 +2558,20 @@ void gui_tiled_begin(struct gui_tiled_layout*, enum gui_layout_format,
- pixel width of the tiled layout space - pixel width of the tiled layout space
- pixel height of the tiled layout space - pixel height of the tiled layout space
*/ */
void gui_tiled_slot(struct gui_tiled_layout *layout,
enum gui_tiled_layout_slot_index, gui_float ratio,
enum gui_tiled_slot_format, gui_uint widget_count);
/* this functions defines a slot in the tiled layout which then can be filled
* with widgets
Input:
- slot identifier
- either ratio or pixel size of the slot
- slot filling format with either horizontal or vertical filling
- number of widgets inside the slot
*/
void gui_tiled_end(struct gui_tiled_layout*);
/* this functions ends the definitions of the tiled layout slots */
void gui_tiled_slot_bounds(struct gui_rect*, const struct gui_tiled_layout*, void gui_tiled_slot_bounds(struct gui_rect*, const struct gui_tiled_layout*,
enum gui_tiled_layout_slot_index); enum gui_tiled_layout_slot_index);
/* this functions queries the bounds (position + size) of a tiled layout slot /* this functions queries the bounds (position + size) of a tiled layout slot
@ -2318,19 +2589,6 @@ void gui_tiled_bounds(struct gui_rect*, const struct gui_tiled_layout*,
Output: Output:
- rectangle with position and size of the slot entry - rectangle with position and size of the slot entry
*/ */
void gui_tiled_slot(struct gui_tiled_layout *layout,
enum gui_tiled_layout_slot_index, gui_float ratio,
enum gui_tiled_slot_format, gui_uint widget_count);
/* this functions defines a slot in the tiled layout which then can be filled
* with widgets
Input:
- slot identifier
- either ratio or pixel size of the slot
- slot filling format with either horizontal or vertical filling
- number of widgets inside the slot
*/
void gui_tiled_end(struct gui_tiled_layout*);
/* this functions ends the definitions of the tiled layout slots */
/* /*
* ============================================================== * ==============================================================
* *
@ -3322,9 +3580,8 @@ enum gui_popup_type {
/* dynamically growing popup with maximum height */ /* dynamically growing popup with maximum height */
}; };
gui_flags gui_popup_begin(struct gui_context *parent, gui_flags gui_popup_begin(struct gui_context *parent, struct gui_context *popup,
struct gui_context *popup, enum gui_popup_type, enum gui_popup_type, gui_flags, struct gui_rect bounds,
gui_flags, struct gui_rect bounds,
struct gui_vec2 offset); struct gui_vec2 offset);
/* this function adds a overlapping blocking popup menu /* this function adds a overlapping blocking popup menu
Input: Input:
@ -3606,9 +3863,8 @@ struct gui_tree {
/* current depth of the tree */ /* current depth of the tree */
}; };
void gui_tree_begin(struct gui_context*, struct gui_tree*, void gui_tree_begin(struct gui_context*, struct gui_tree*, const char *title,
const char*, gui_float row_height, gui_float row_height, struct gui_vec2 scrollbar);
struct gui_vec2 scrollbar);
/* this function begins the tree building process /* this function begins the tree building process
Input: Input:
- title describing the tree or NULL - title describing the tree or NULL
@ -3618,7 +3874,7 @@ void gui_tree_begin(struct gui_context*, struct gui_tree*,
- tree build up state structure - tree build up state structure
*/ */
enum gui_tree_node_operation gui_tree_begin_node(struct gui_tree*, const char*, enum gui_tree_node_operation gui_tree_begin_node(struct gui_tree*, const char*,
gui_state*); gui_state*);
/* this function begins a parent node /* this function begins a parent node
Input: Input:
- title of the node - title of the node

545
stb_rect_pack.h Normal file
View File

@ -0,0 +1,545 @@
/* stb_rect_pack.h - v0.05 - public domain - rectangle packing */
/* Sean Barrett 2014 */
/* */
/* Useful for e.g. packing rectangular textures into an atlas. */
/* Does not do rotation. */
/* */
/* Not necessarily the awesomest packing method, but better than */
/* the totally naive one in stb_truetype (which is primarily what */
/* this is meant to replace). */
/* */
/* Has only had a few tests run, may have issues. */
/* */
/* More docs to come. */
/* */
/* No memory allocations; uses qsort() and assert() from stdlib. */
/* */
/* This library currently uses the Skyline Bottom-Left algorithm. */
/* */
/* Please note: better rectangle packers are welcome! Please */
/* implement them to the same API, but with a different init */
/* function. */
/* */
/* Version history: */
/* */
/* 0.05: added STBRP_ASSERT to allow replacing assert */
/* 0.04: fixed minor bug in STBRP_LARGE_RECTS support */
/* 0.01: initial release */
/*//////////////////////////////////////////////////////////////////////////// */
/* */
/* INCLUDE SECTION */
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
#ifdef STBRP_LARGE_RECTS
typedef int stbrp_coord;
#else
typedef unsigned short stbrp_coord;
#endif
STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
/* Assign packed locations to rectangles. The rectangles are of type */
/* 'stbrp_rect' defined below, stored in the array 'rects', and there */
/* are 'num_rects' many of them. */
/* */
/* Rectangles which are successfully packed have the 'was_packed' flag */
/* set to a non-zero value and 'x' and 'y' store the minimum location */
/* on each axis (i.e. bottom-left in cartesian coordinates, top-left */
/* if you imagine y increasing downwards). Rectangles which do not fit */
/* have the 'was_packed' flag set to 0. */
/* */
/* You should not try to access the 'rects' array from another thread */
/* while this function is running, as the function temporarily reorders */
/* the array while it executes. */
/* */
/* To pack into another rectangle, you need to call stbrp_init_target */
/* again. To continue packing into the same rectangle, you can call */
/* this function again. Calling this multiple times with multiple rect */
/* arrays will probably produce worse packing results than calling it */
/* a single time with the full rectangle array, but the option is */
/* available. */
struct stbrp_rect
{
/* reserved for your use: */
int id;
/* input: */
stbrp_coord w, h;
/* output: */
stbrp_coord x, y;
int was_packed; /* non-zero if valid packing */
}; /* 16 bytes, nominally */
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
/* Initialize a rectangle packer to: */
/* pack a rectangle that is 'width' by 'height' in dimensions */
/* using temporary storage provided by the array 'nodes', which is 'num_nodes' long */
/* */
/* You must call this function every time you start packing into a new target. */
/* */
/* There is no "shutdown" function. The 'nodes' memory must stay valid for */
/* the following stbrp_pack_rects() call (or calls), but can be freed after */
/* the call (or calls) finish. */
/* */
/* Note: to guarantee best results, either: */
/* 1. make sure 'num_nodes' >= 'width' */
/* or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' */
/* */
/* If you don't do either of the above things, widths will be quantized to multiples */
/* of small integers to guarantee the algorithm doesn't run out of temporary storage. */
/* */
/* If you do #2, then the non-quantized algorithm will be used, but the algorithm */
/* may run out of temporary storage and be unable to pack some rectangles. */
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
/* Optionally call this function after init but before doing any packing to */
/* change the handling of the out-of-temp-memory scenario, described above. */
/* If you call init again, this will be reset to the default (false). */
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
/* Optionally select which packing heuristic the library should use. Different */
/* heuristics will produce better/worse results for different data sets. */
/* If you call init again, this will be reset to the default. */
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
/*//////////////////////////////////////////////////////////////////////////// */
/* */
/* the details of the following structures don't matter to you, but they must */
/* be visible so you can handle the memory allocations for them */
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; /* we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' */
};
#ifdef __cplusplus
}
#endif
#endif
/*//////////////////////////////////////////////////////////////////////////// */
/* */
/* IMPLEMENTATION SECTION */
/* */
#ifdef STB_RECT_PACK_IMPLEMENTATION
#include <stdlib.h>
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
/* if it's ok to run out of memory, then don't bother aligning them; */
/* this gives better packing, but may fail due to OOM (even though */
/* the rectangles easily fit). @TODO a smarter approach would be to only */
/* quantize once we've hit OOM, then we could get rid of this parameter. */
context->align = 1;
else {
/* if it's not ok to run out of memory, then quantize the widths */
/* so that num_nodes is always enough nodes. */
/* */
/* I.e. num_nodes * align >= width */
/* align >= width / num_nodes */
/* align = ceil(width/num_nodes) */
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
#ifndef STBRP_LARGE_RECTS
STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
#endif
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
/* node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) */
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
#ifdef STBRP_LARGE_RECTS
context->extra[1].y = (1<<30);
#else
context->extra[1].y = 65535;
#endif
context->extra[1].next = NULL;
}
/* find minimum y position if it starts at x1 */
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP_ASSERT(first->x <= x0);
(void)c;
#if 0
/* skip in case we're past the node */
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); /* we ended up handling this in the caller for efficiency */
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
/* raise min_y higher. */
/* we've accounted for all waste up to min_y, */
/* but we'll now add more waste for everything we've visted */
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
/* the first time through, visited_width might be reduced */
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
/* add waste area */
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
/* align to multiple of c->align */
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { /* actually just want to test BL */
/* bottom left */
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
/* best-fit */
if (y + height <= c->height) {
/* can only use it if it first vertically */
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
/* if doing best-fit (BF), we also have to try aligning right edge to each node position */
/* */
/* e.g, if fitting */
/* */
/* ____________________ */
/* |____________________| */
/* */
/* into */
/* */
/* | | */
/* | ____________| */
/* |____________| */
/* */
/* then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned */
/* */
/* This makes BF take about 2x the time */
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
/* find first node that's admissible */
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
/* find the left position that matches this */
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height < c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
STBRP_ASSERT(y <= best_y);
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
/* find best position according to heuristic */
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
/* bail if: */
/* 1. it failed */
/* 2. the best node doesn't fit (we don't always check this) */
/* 3. we're out of memory */
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
/* on success, create new node */
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
/* insert the new node into the right starting point, and */
/* let 'cur' point to the remaining nodes needing to be */
/* stiched back in */
cur = *res.prev_link;
if (cur->x < res.x) {
/* preserve the existing one, so start testing with the next one */
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
/* from here, traverse cur and free the nodes, until we get to one */
/* that shouldn't be freed */
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
/* move the current node to the free list */
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
/* stitch the list back in */
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
stbrp_node *L1 = NULL, *L2 = NULL;
int count=0;
cur = context->active_head;
while (cur) {
L1 = cur;
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
L2 = cur;
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int rect_width_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->w > q->w)
return -1;
if (p->w < q->w)
return 1;
return (p->h > q->h) ? -1 : (p->h < q->h);
}
static int rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
#ifdef STBRP_LARGE_RECTS
#define STBRP__MAXVAL 0xffffffff
#else
#define STBRP__MAXVAL 0xffff
#endif
STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i;
/* we use the 'was_packed' field internally to allow sorting/unsorting */
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
#ifndef STBRP_LARGE_RECTS
STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff);
#endif
}
/* sort according to heuristic */
qsort(rects, (size_t)num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
/* unsort */
qsort(rects, (size_t)num_rects, sizeof(rects[0]), rect_original_order);
/* set was_packed flags */
for (i=0; i < num_rects; ++i)
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
}
#endif

3218
stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff