Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit b89907ba6b59ff9915544d1c07e76382bb9ac450 0 parents
Mikko Mononen authored
Showing with 11,951 additions and 0 deletions.
  1. +28 −0 .gitignore
  2. +138 −0 README.md
  3. BIN  example/FiraSans-Bold.ttf
  4. BIN  example/FiraSans-Light.ttf
  5. BIN  example/FiraSans-Regular.ttf
  6. BIN  example/Roboto-Bold.ttf
  7. BIN  example/Roboto-Light.ttf
  8. BIN  example/Roboto-Regular.ttf
  9. BIN  example/entypo.ttf
  10. +767 −0 example/example.c
  11. +116 −0 example/glstash.h
  12. +431 −0 example/icons.txt
  13. +13 −0 example/images.txt
  14. BIN  example/images/image1.jpg
  15. BIN  example/images/image10.jpg
  16. BIN  example/images/image11.jpg
  17. BIN  example/images/image12.jpg
  18. BIN  example/images/image2.jpg
  19. BIN  example/images/image3.jpg
  20. BIN  example/images/image4.jpg
  21. BIN  example/images/image5.jpg
  22. BIN  example/images/image6.jpg
  23. BIN  example/images/image7.jpg
  24. BIN  example/images/image8.jpg
  25. BIN  example/images/image9.jpg
  26. BIN  example/mfglabsiconset-webfont.ttf
  27. +84 −0 example/templates.xml
  28. +32 −0 premake4.lua
  29. +1,134 −0 src/fontstash.h
  30. +690 −0 src/glnanovg.h
  31. +1,586 −0 src/nanovg.c
  32. +194 −0 src/nanovg.h
  33. +4,673 −0 src/stb_image.c
  34. +2,065 −0 src/stb_truetype.h
28 .gitignore
@@ -0,0 +1,28 @@
+## Compiled source #
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
+test
+
+## Logs and databases #
+*.log
+*.sql
+*.sqlite
+
+## OS generated files #
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+## Build dir
+build/*
+
+## xcode specific
+*xcuserdata*
138 README.md
@@ -0,0 +1,138 @@
+Font Stash
+==========
+
+Font stash is light-weight online font texture atlas builder written in C. It uses [stb_truetype](http://nothings.org) to render fonts on demand to a texture atlas.
+
+The code is split in two parts, the font atlas and glyph quad generator [fontstash.h](/src/fontstash.h), and an example OpenGL backend ([glstash.h](/glstash.h).
+
+## Screenshot
+
+![screenshot of some text rendered witht the sample program](/screenshots/screen-01.png?raw=true)
+
+## Example
+``` C
+// Create stash for 512x512 texture, our coordinate system has zero at top-left.
+struct FONSparams params;
+memset(&params, 0, sizeof(params));
+params.width = 512;
+params.height = 512;
+params.flags = FONS_ZERO_TOPLEFT;
+glstInit(&params);
+struct FONScontext* fs = fonsCreate(&params);
+
+// Add font to stash.
+int fontNormal = fonsAddFont(fs, "DroidSerif-Regular.ttf");
+
+// Render some text
+float dx = 10, dy = 10;
+unsigned int white = glstRGBA(255,255,255,255);
+unsigned int brown = glstRGBA(192,128,0,128);
+
+struct fontstash_style styleBig = { FONT_NORMAL, 124.0f, white };
+
+fonsSetFont(fs, fontNormal);
+fonsSetSize(fs, 124.0f);
+fonsSetColor(fs, white);
+fonsDrawText(fs, dx,dy,"The big ", &dx);
+
+fonsSetSize(fs, 24.0f);
+fonsSetColor(fs, brown);
+fonsDrawText(fs, dx,dy,"brown fox", &dx);
+```
+
+## Using Font Stash in your project
+
+In order to use fontstash in your own project, just copy fontstash.h, stb_truetype.h, and potentially glstash.h to your project.
+In one C/C++ define FONTSTASH_IMPLEMENTATION before including the library to expand the font stash implementation in that file.
+
+``` C
+#include <stdio.h> // malloc, free, fopen, fclose, ftell, fseek, fread
+#include <string.h> // memset
+#define FONTSTASH_IMPLEMENTATION // Expands implementation
+#include "fontstash.h"
+```
+
+``` C
+#include <GLFW/glfw3.h> // Or any other GL header of your choice.
+#define GLSTASH_IMPLEMENTATION // Expands implementation
+#include "glstash.h"
+```
+
+## Creating new rendering backend
+
+The default rendering backend uses OpenGL to render the glyphs. If you want to render the text using some other API, or want tighter integration with your code base you can write your own rendering backend. Take a look at the [glstash.h](/src/glstash.h) for reference implementation.
+
+The rendering interface FontStash assumes access to is defined in the FONSparams structure. The call to `glstInit()` fills in variables.
+
+```C
+struct FONSparams {
+ ...
+ void* userPtr;
+ int (*renderCreate)(void* uptr, int width, int height);
+ void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data);
+ void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts);
+ void (*renderDelete)(void* uptr);
+};
+```
+
+- **renderCreate** is called to create renderer for specific API, this is where you should create a texture of given size.
+ - return 1 of success, or 0 on failure.
+- **renderUpdate** is called to update texture data
+ - _rect_ describes the region of the texture that has changed
+ - _data_ pointer to full texture data
+- **renderDraw** is called when the font triangles should be drawn
+ - _verts_ pointer to vertex position data, 2 floats per vertex
+ - _tcoords_ pointer to texture coordinate data, 2 floats per vertex
+ - _colors_ pointer to color data, 1 uint per vertex (or 4 bytes)
+ - _nverts_ is the number of vertices to draw
+- **renderDelete** is called when the renderer should be deleted
+- **userPtr** is passed to all calls as first parameter
+
+FontStash uses this API as follows:
+
+```
+fonsDrawText() {
+ foreach (glyph in input string) {
+ if (internal buffer full) {
+ updateTexture()
+ render()
+ }
+ add glyph to interal draw buffer
+ }
+ updateTexture()
+ render()
+}
+```
+
+The size of the internal buffer is defined using `FONS_VERTEX_COUNT` define. The default value is 1024, you can override it when you include fontstash.h and specify the implementation:
+
+``` C
+#define FONS_VERTEX_COUNT 2048
+#define FONTSTASH_IMPLEMENTATION // Expands implementation
+#include "fontstash.h"
+```
+
+## Compiling
+
+In order to compile the demo project, your will need to install [GLFW](http://www.glfw.org/) to compile.
+
+FontStash example project uses [premake4](http://industriousone.com/premake) to build platform specific projects, now is good time to install it if you don't have it already. To build the example, navigate into the root folder in your favorite terminal, then:
+
+- *OS X*: `premake4 xcode4`
+- *Windows*: `premake4 vs2010`
+- *Linux*: `premake4 gmake`
+
+See premake4 documentation for full list of supported build file types. The projects will be created in `build` folder. An example of building and running the example on OS X:
+
+```bash
+$ premake4 gmake
+$ cd build/
+$ make
+$ ./example
+```
+
+# License
+The library is licensed under [zlib license](LICENSE.txt)
+
+## Links
+Uses [stb_truetype](http://nothings.org) for font rendering.
BIN  example/FiraSans-Bold.ttf
Binary file not shown
BIN  example/FiraSans-Light.ttf
Binary file not shown
BIN  example/FiraSans-Regular.ttf
Binary file not shown
BIN  example/Roboto-Bold.ttf
Binary file not shown
BIN  example/Roboto-Light.ttf
Binary file not shown
BIN  example/Roboto-Regular.ttf
Binary file not shown
BIN  example/entypo.ttf
Binary file not shown
767 example/example.c
@@ -0,0 +1,767 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <iconv.h>
+#include <math.h>
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define GLNANOVG_IMPLEMENTATION
+#include "glnanovg.h"
+
+#define ICON_SEARCH 0x1F50D
+#define ICON_CIRCLED_CROSS 0x2716
+#define ICON_CHEVRON_RIGHT 0xE75E
+#define ICON_CHECK 0x2713
+#define ICON_LOGIN 0xE740
+#define ICON_TRASH 0xE729
+
+static char* cpToUTF8(int cp, char* str)
+{
+ int n = 0;
+ if (cp < 0x80) n = 1;
+ else if (cp < 0x800) n = 2;
+ else if (cp < 0x10000) n = 3;
+ else if (cp < 0x200000) n = 4;
+ else if (cp < 0x4000000) n = 5;
+ else if (cp <= 0x7fffffff) n = 6;
+ str[n] = '\0';
+ switch (n) {
+ case 6: str[5] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x4000000;
+ case 5: str[4] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x200000;
+ case 4: str[3] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x10000;
+ case 3: str[2] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x800;
+ case 2: str[1] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0xc0;
+ case 1: str[0] = cp;
+ }
+ return str;
+}
+
+
+void drawWindow(struct NVGcontext* vg, const char* title, float x, float y, float w, float h)
+{
+ float cornerRadius = 3.0f;
+ struct NVGpaint shadowPaint;
+ struct NVGpaint headerPaint;
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Window
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgFillColor(vg, nvgRGBA(28,30,34,192));
+// nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgFill(vg);
+
+ // Drop shadow
+ shadowPaint = nvgBoxGradient(vg, x,y+2, w,h, cornerRadius*2, 10, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x-10,y-10, w+20,h+30);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ // Header
+ headerPaint = nvgLinearGradient(vg, x,y,x,y+15, nvgRGBA(255,255,255,8), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,30, cornerRadius-1);
+ nvgFillPaint(vg, headerPaint);
+ nvgFill(vg);
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, x+0.5f, y+0.5f+30);
+ nvgLineTo(vg, x+0.5f+w-1, y+0.5f+30);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,32));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans-bold");
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+
+ nvgFontBlur(vg,2);
+ nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgText(vg, x+w/2,y+16+1, title);
+
+ nvgFontBlur(vg,0);
+ nvgFillColor(vg, nvgRGBA(220,220,220,160));
+ nvgText(vg, x+w/2,y+16, title);
+
+ nvgRestore(vg);
+}
+
+void drawSearchBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ struct NVGpaint bg;
+ char icon[8];
+ float cornerRadius = h/2-1;
+
+ // Edit
+ bg = nvgBoxGradient(vg, x,y+1.5f, w,h, h/2,5, nvgRGBA(0,0,0,16), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+/* nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);*/
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.55f, y+h*0.55f, cpToUTF8(ICON_SEARCH,icon));
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*1.05f,y+h*0.5f,text);
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.55f, y+h*0.55f, cpToUTF8(ICON_CIRCLED_CROSS,icon));
+}
+
+void drawDropDown(struct NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ struct NVGpaint bg;
+ char icon[8];
+ float cornerRadius = 4.0f;
+
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.3f,y+h*0.5f,text);
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.5f, y+h*0.5f, cpToUTF8(ICON_CHEVRON_RIGHT,icon));
+}
+
+void drawLabel(struct NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x,y+h*0.5f,text);
+}
+
+void drawEditBoxBase(struct NVGcontext* vg, float x, float y, float w, float h)
+{
+ struct NVGpaint bg;
+ // Edit
+ bg = nvgBoxGradient(vg, x+1,y+1+1.5f, w-2,h-2, 3,4, nvgRGBA(255,255,255,32), nvgRGBA(32,32,32,32));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, 4-1);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, 4-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+}
+
+void drawEditBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+
+ drawEditBoxBase(vg, x,y, w,h);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.3f,y+h*0.5f,text);
+}
+
+void drawEditBoxNum(struct NVGcontext* vg,
+ const char* text, const char* units, float x, float y, float w, float h)
+{
+ float uw;
+
+ drawEditBoxBase(vg, x,y, w,h);
+
+ nvgTextBounds(vg, units, &uw, NULL);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.3f,y+h*0.5f,units);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-uw-h*0.5f,y+h*0.5f,text);
+}
+
+void drawCheckBox(struct NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ struct NVGpaint bg;
+ char icon[8];
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+28,y+h*0.5f,text);
+
+ bg = nvgBoxGradient(vg, x+1,y+(int)(h*0.5f)-9+1, 18,18, 3,3, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+(int)(h*0.5f)-9, 18,18, 3);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgFontSize(vg, 40);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+9+2, y+h*0.5f, cpToUTF8(ICON_CHECK,icon));
+}
+
+void drawButton(struct NVGcontext* vg, int preicon, const char* text, float x, float y, float w, float h, unsigned int col)
+{
+ struct NVGpaint bg;
+ char icon[8];
+ float cornerRadius = 4.0f;
+ float tw = 0, iw = 0;
+
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,col==0?16:32), nvgRGBA(0,0,0,col==0?16:32));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1);
+ if (col != 0) {
+ nvgFillColor(vg, col);
+ nvgFill(vg);
+ }
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans-bold");
+ nvgTextBounds(vg, text, &tw, NULL);
+ if (preicon != 0) {
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgTextBounds(vg, cpToUTF8(preicon,icon), &iw, NULL);
+ iw += h*0.15f;
+ }
+
+ if (preicon != 0) {
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,96));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w*0.5f-tw*0.5f-iw*0.75f, y+h*0.5f, cpToUTF8(preicon,icon));
+ }
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans-bold");
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgFillColor(vg, nvgRGBA(0,0,0,160));
+ nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f-1,text);
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+ nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f,text);
+}
+
+void drawSlider(struct NVGcontext* vg, float pos, float x, float y, float w, float h)
+{
+ struct NVGpaint bg, knob;
+ float cy = y+(int)(h*0.5f);
+ float kr = (int)(h*0.25f);
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Slot
+ bg = nvgBoxGradient(vg, x,cy-2+1, w,4, 2,2, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,128));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,cy-2, w,4, 2);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Knob Shadow
+ bg = nvgRadialGradient(vg, x+(int)(pos*w),cy+1, kr-3,kr+3, nvgRGBA(0,0,0,64), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+(int)(pos*w)-kr-5,cy-kr-5,kr*2+5+5,kr*2+5+5+3);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Knob
+ knob = nvgLinearGradient(vg, x,cy-kr,x,cy+kr, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr-1);
+ nvgFillColor(vg, nvgRGBA(40,43,48,255));
+ nvgFill(vg);
+ nvgFillPaint(vg, knob);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,92));
+ nvgStroke(vg);
+
+ nvgRestore(vg);
+}
+
+void drawEyes(struct NVGcontext* vg, float x, float y, float w, float h, float mx, float my, float t)
+{
+ struct NVGpaint gloss, bg;
+ float ex = w *0.23f;
+ float ey = h * 0.5f;
+ float lx = x + ex;
+ float ly = y + ey;
+ float rx = x + w - ex;
+ float ry = y + ey;
+ float dx,dy,d;
+ float br = (ex < ey ? ex : ey) * 0.5f;
+ float blink = 1 - pow(sinf(t*0.5f),200)*0.8f;
+
+ bg = nvgLinearGradient(vg, x,y+h*0.5f,x+w*0.1f,y+h, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx+3.0f,ly+16.0f, ex,ey);
+ nvgEllipse(vg, rx+3.0f,ry+16.0f, ex,ey);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ bg = nvgLinearGradient(vg, x,y+h*0.25f,x+w*0.1f,y+h, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx,ly, ex,ey);
+ nvgEllipse(vg, rx,ry, ex,ey);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ dx = (mx - rx) / (ex * 10);
+ dy = (my - ry) / (ey * 10);
+ d = sqrtf(dx*dx+dy*dy);
+ if (d > 1.0f) {
+ dx /= d; dy /= d;
+ }
+ dx *= ex*0.4f;
+ dy *= ey*0.5f;
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx+dx,ly+dy+ey*0.25f*(1-blink), br,br*blink);
+ nvgFillColor(vg, nvgRGBA(32,32,32,255));
+ nvgFill(vg);
+
+ dx = (mx - rx) / (ex * 10);
+ dy = (my - ry) / (ey * 10);
+ d = sqrtf(dx*dx+dy*dy);
+ if (d > 1.0f) {
+ dx /= d; dy /= d;
+ }
+ dx *= ex*0.4f;
+ dy *= ey*0.5f;
+ nvgBeginPath(vg);
+ nvgEllipse(vg, rx+dx,ry+dy+ey*0.25f*(1-blink), br,br*blink);
+ nvgFillColor(vg, nvgRGBA(32,32,32,255));
+ nvgFill(vg);
+
+ gloss = nvgRadialGradient(vg, lx-ex*0.25f,ly-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx,ly, ex,ey);
+ nvgFillPaint(vg, gloss);
+ nvgFill(vg);
+
+ gloss = nvgRadialGradient(vg, rx-ex*0.25f,ry-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, rx,ry, ex,ey);
+ nvgFillPaint(vg, gloss);
+ nvgFill(vg);
+}
+
+void drawGraph(struct NVGcontext* vg, float x, float y, float w, float h, float t)
+{
+ struct NVGpaint bg;
+ float samples[6];
+ float sx[6], sy[6];
+ float dx = w/5.0f;
+ int i;
+
+ samples[0] = (1+sinf(t*1.2345f+cosf(t*0.33457f)*0.44f))*0.5f;
+ samples[1] = (1+sinf(t*0.68363f+cosf(t*1.3f)*1.55f))*0.5f;
+ samples[2] = (1+sinf(t*1.1642f+cosf(t*0.33457)*1.24f))*0.5f;
+ samples[3] = (1+sinf(t*0.56345f+cosf(t*1.63f)*0.14f))*0.5f;
+ samples[4] = (1+sinf(t*1.6245f+cosf(t*0.254f)*0.3f))*0.5f;
+ samples[5] = (1+sinf(t*0.345f+cosf(t*0.03f)*0.6f))*0.5f;
+
+ for (i = 0; i < 6; i++) {
+ sx[i] = x+i*dx;
+ sy[i] = y+h*samples[i]*0.8f;
+ }
+
+ // Graph background
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(0,160,192,0), nvgRGBA(0,160,192,64));
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]);
+ nvgLineTo(vg, x+w, y+h);
+ nvgLineTo(vg, x, y+h);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Graph line
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]+2);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1]+2, sx[i]-dx*0.5f,sy[i]+2, sx[i],sy[i]+2);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,32));
+ nvgStrokeWidth(vg, 3.0f);
+ nvgStroke(vg);
+
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]);
+ nvgStrokeColor(vg, nvgRGBA(0,160,192,255));
+ nvgStrokeWidth(vg, 3.0f);
+ nvgStroke(vg);
+
+ // Graph sample pos
+ for (i = 0; i < 6; i++) {
+ bg = nvgRadialGradient(vg, sx[i],sy[i]+2, 3.0f,8.0f, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, sx[i]-10, sy[i]-10+2, 20,20);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+ }
+
+ nvgBeginPath(vg);
+ for (i = 0; i < 6; i++)
+ nvgCircle(vg, sx[i], sy[i], 4.0f);
+ nvgFillColor(vg, nvgRGBA(0,160,192,255));
+ nvgFill(vg);
+ nvgBeginPath(vg);
+ for (i = 0; i < 6; i++)
+ nvgCircle(vg, sx[i], sy[i], 2.0f);
+ nvgFillColor(vg, nvgRGBA(220,220,220,255));
+ nvgFill(vg);
+
+ nvgStrokeWidth(vg, 1.0f);
+}
+
+void drawThumbnails(struct NVGcontext* vg, float x, float y, float w, float h, const int* images, int nimages, float t)
+{
+ float cornerRadius = 3.0f;
+ struct NVGpaint shadowPaint, imgPaint, fadePaint;
+ float ix,iy,iw,ih;
+ float thumb = 60.0f;
+ float arry = 30.5f;
+ int imgw, imgh;
+ float stackh = (nimages/2) * (thumb+10) + 10;
+ int i;
+ float u = (1+cosf(t*0.5f))*0.5f;
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Drop shadow
+ shadowPaint = nvgBoxGradient(vg, x,y+4, w,h, cornerRadius*2, 20, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x-10,y-10, w+20,h+30);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ // Window
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgMoveTo(vg, x-10,y+arry);
+ nvgLineTo(vg, x+1,y+arry-11);
+ nvgLineTo(vg, x+1,y+arry+11);
+ nvgFillColor(vg, nvgRGBA(200,200,200,255));
+ nvgFill(vg);
+
+ nvgSave(vg);
+ nvgScissor(vg, x,y,w,h);
+ nvgTranslate(vg, 0, -(stackh - h)*u);
+
+ for (i = 0; i < nimages; i++) {
+ float tx, ty;
+ tx = x+10;
+ ty = y+10;
+ tx += (i%2) * (thumb+10);
+ ty += (i/2) * (thumb+10);
+ nvgImageSize(vg, images[i], &imgw, &imgh);
+ if (imgw < imgh) {
+ iw = thumb;
+ ih = iw * (float)imgh/(float)imgw;
+ ix = 0;
+ iy = -(ih-thumb)*0.5f;
+ } else {
+ ih = thumb;
+ iw = ih * (float)imgw/(float)imgh;
+ ix = -(iw-thumb)*0.5f;
+ iy = 0;
+ }
+ imgPaint = nvgImagePattern(vg, tx+ix, ty+iy, iw,ih, 0.0f/180.0f*NVG_PI, images[i], 0);
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, tx,ty, thumb,thumb, 5);
+ nvgFillPaint(vg, imgPaint);
+ nvgFill(vg);
+
+ shadowPaint = nvgBoxGradient(vg, tx-1,ty, thumb+2,thumb+2, 5, 3, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, tx-5,ty-5, thumb+10,thumb+10);
+ nvgRoundedRect(vg, tx,ty, thumb,thumb, 6);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, tx+0.5f,ty+0.5f, thumb-1,thumb-1, 4-0.5f);
+ nvgStrokeWidth(vg,1.0f);
+ nvgStrokeColor(vg, nvgRGBA(255,255,255,192));
+ nvgStroke(vg);
+ }
+ nvgRestore(vg);
+
+ // Hide fades
+ fadePaint = nvgLinearGradient(vg, x,y,x,y+6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+4,y,w-8,6);
+ nvgFillPaint(vg, fadePaint);
+ nvgFill(vg);
+
+ fadePaint = nvgLinearGradient(vg, x,y+h,x,y+h-6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+4,y+h-6,w-8,6);
+ nvgFillPaint(vg, fadePaint);
+ nvgFill(vg);
+
+ // Scroll bar
+ shadowPaint = nvgBoxGradient(vg, x+w-12+1,y+4+1, 8,h-8, 3,4, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+w-12,y+4, 8,h-8, 3);
+ nvgFillPaint(vg, shadowPaint);
+// nvgFillColor(vg, nvgRGBA(255,0,0,128));
+ nvgFill(vg);
+
+ float scrollh = (h/stackh) * (h-8);
+ shadowPaint = nvgBoxGradient(vg, x+w-12-1,y+4+(h-8-scrollh)*u-1, 8,scrollh, 3,4, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+w-12+1,y+4+1 + (h-8-scrollh)*u, 8-2,scrollh-2, 2);
+ nvgFillPaint(vg, shadowPaint);
+// nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgFill(vg);
+
+ nvgRestore(vg);
+}
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error: %s\n", desc);
+}
+
+int blowup = 0;
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ blowup = !blowup;
+}
+
+int main()
+{
+ GLFWwindow* window;
+ int fontNormal = -1, fontBold = -1, fontIcons = -1;
+ struct NVGcontext* vg = NULL;
+ int images[12];
+ int i;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ glfwSetErrorCallback(errorcb);
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+
+ vg = glnvgCreate();
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ for (i = 0; i < 12; i++) {
+ char file[128];
+ snprintf(file, 128, "../example/images/image%d.jpg", i+1);
+ images[i] = nvgCreateImage(vg, file);
+ if (images[i] == 0) {
+ printf("Could not load %s.\n", file);
+ return -1;
+ }
+ }
+
+ fontIcons = nvgCreateFont(vg, "icons", "../example/entypo.ttf");
+ if (fontIcons == -1) {
+ printf("Could not add font icons.\n");
+ return -1;
+ }
+ fontNormal = nvgCreateFont(vg, "sans", "../example/Roboto-Regular.ttf");
+// fontNormal = nvgAddFont(vg, "sans", "../example/FiraSans-Regular.ttf");
+ if (fontNormal == -1) {
+ printf("Could not add font italic.\n");
+ return -1;
+ }
+ fontBold = nvgCreateFont(vg, "sans-bold", "../example/Roboto-Bold.ttf");
+// fontBold = nvgAddFont(vg, "sans-bold", "../example/FiraSans-Bold.ttf");
+ if (fontBold == -1) {
+ printf("Could not add font bold.\n");
+ return -1;
+ }
+
+
+ glfwSetTime(0);
+
+ while (!glfwWindowShouldClose(window))
+ {
+// float sx, sy, dx, dy, lh = 0;
+ double mx, my;
+ int width, height;
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetFramebufferSize(window, &width, &height);
+ // Update and render
+ glViewport(0, 0, width, height);
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_CULL_FACE);
+ glDisable(GL_TEXTURE_2D);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0,width,height,0,-1,1);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glDisable(GL_DEPTH_TEST);
+ glColor4ub(255,255,255,255);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+
+ float t = glfwGetTime();
+ float x,y,popy;
+
+ nvgBeginFrame(vg);
+
+ drawEyes(vg, width - 250, 50, 200, 120, mx, my, t);
+ drawGraph(vg, 0, height/2, width, height/2, t);
+
+ nvgSave(vg);
+ if (blowup) {
+// nvgRotate(vg, 3.0f/180.0f*NVG_PI);
+ nvgScale(vg, 2.0f, 2.0f);
+ }
+
+ // Widgets
+ drawWindow(vg, "Widgets `n Stuff", 20, 20, 300, 400);
+ x = 30; y = 65;
+ drawSearchBox(vg, "Search", x,y,280,25);
+ y += 40;
+ drawDropDown(vg, "Effects", x,y,280,28);
+ popy = y + 14;
+ y += 45;
+
+ // Form
+ drawLabel(vg, "Login", x,y, 280,20);
+ y += 25;
+ drawEditBox(vg, "Email", x,y, 280,28);
+ y += 35;
+ drawEditBox(vg, "Password", x,y, 280,28);
+ y += 38;
+ drawCheckBox(vg, "Remember me", x,y, 140,28);
+ drawButton(vg, ICON_LOGIN, "Sign in", x+138, y, 140, 28, nvgRGBA(0,96,128,255));
+ y += 45;
+
+ // Slider
+ drawLabel(vg, "Diameter", x,y, 280,20);
+ y += 25;
+ drawEditBoxNum(vg, "123.00", "px", x+180,y, 100,28);
+ drawSlider(vg, 0.4f, x,y, 170,28);
+ y += 55;
+
+ drawButton(vg, ICON_TRASH, "Delete", x, y, 160, 28, nvgRGBA(128,16,8,255));
+ drawButton(vg, 0, "Cancel", x+170, y, 110, 28, nvgRGBA(0,0,0,0));
+
+ // Thumbnails box
+ drawThumbnails(vg, 325, popy-30, 160, 300, images, 12, t);
+
+ nvgRestore(vg);
+
+ glEnable(GL_DEPTH_TEST);
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ for (i = 0; i < 12; i++)
+ nvgDeleteImage(vg, images[i]);
+
+ glnvgDelete(vg);
+
+ glfwTerminate();
+ return 0;
+}
116 example/glstash.h
@@ -0,0 +1,116 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+#ifndef GLSTASH_H
+#define GLSTASH_H
+
+int glstInit(struct FONSparams* params);
+unsigned int glstRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+#endif
+
+#ifdef GLSTASH_IMPLEMENTATION
+
+struct GLSTcontext {
+ GLuint tex;
+ int width, height;
+};
+
+static int glst__renderCreate(void* userPtr, int width, int height)
+{
+ struct GLSTcontext* gl = (struct GLSTcontext*)userPtr;
+ glGenTextures(1, &gl->tex);
+ if (!gl->tex) return 0;
+ gl->width = width;
+ gl->height = width;
+ glBindTexture(GL_TEXTURE_2D, gl->tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, gl->width, gl->height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ return 1;
+}
+
+static void glst__renderUpdate(void* userPtr, int* rect, const unsigned char* data)
+{
+ struct GLSTcontext* gl = (struct GLSTcontext*)userPtr;
+ if (gl->tex == 0) return;
+ int w = rect[2] - rect[0];
+ int h = rect[3] - rect[1];
+ glBindTexture(GL_TEXTURE_2D, gl->tex);
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect[0]);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, rect[1]);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, rect[0], rect[1], w, h, GL_ALPHA,GL_UNSIGNED_BYTE, data);
+}
+
+static void glst__renderDraw(void* userPtr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts)
+{
+ struct GLSTcontext* gl = (struct GLSTcontext*)userPtr;
+ if (gl->tex == 0) return;
+ glBindTexture(GL_TEXTURE_2D, gl->tex);
+ glEnable(GL_TEXTURE_2D);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glVertexPointer(2, GL_FLOAT, sizeof(float)*2, verts);
+ glTexCoordPointer(2, GL_FLOAT, sizeof(float)*2, tcoords);
+ glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(unsigned int), colors);
+
+ glDrawArrays(GL_TRIANGLES, 0, nverts);
+
+ glDisable(GL_TEXTURE_2D);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+}
+
+static void glst__renderDelete(void* userPtr)
+{
+ struct GLSTcontext* gl = (struct GLSTcontext*)userPtr;
+ if (gl->tex)
+ glDeleteTextures(1, &gl->tex);
+ gl->tex = 0;
+ free(gl);
+}
+
+
+int glstInit(struct FONSparams* params)
+{
+ struct GLSTcontext* gl = (struct GLSTcontext*)malloc(sizeof(struct GLSTcontext));
+ if (gl == NULL) goto error;
+ memset(gl, 0, sizeof(struct GLSTcontext));
+
+ params->renderCreate = glst__renderCreate;
+ params->renderUpdate = glst__renderUpdate;
+ params->renderDraw = glst__renderDraw;
+ params->renderDelete = glst__renderDelete;
+ params->userPtr = gl;
+
+ return 1;
+
+error:
+ if (gl != NULL) free(gl);
+ return 0;
+}
+
+unsigned int glstRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ return (r) | (g << 8) | (b << 16) | (a << 24);
+}
+
+#endif
431 example/icons.txt
@@ -0,0 +1,431 @@
+struct Icon {
+ const char* name;
+ int codepoint;
+};
+
+
+const char* getIcon(const char* name)
+{
+ static struct Icon icons[] = {
+ { "phone", 0x1F4DE },
+ { "mobile", 0x1F4F1 },
+ { "mouse", 0xE789 },
+ { "address", 0xE723 },
+ { "mail", 0x2709 },
+ { "paper-plane", 0x1F53F },
+ { "pencil", 0x270E },
+ { "feather", 0x2712 },
+ { "attach", 0x1F4CE },
+ { "inbox", 0xE777 },
+ { "reply", 0xE712 },
+ { "reply-all", 0xE713 },
+ { "forward", 0x27A6 },
+ { "user", 0x1F464 },
+ { "users", 0x1F465 },
+ { "add-user", 0xE700 },
+ { "vcard", 0xE722 },
+ { "export", 0xE715 },
+ { "location", 0xE724 },
+ { "map", 0xE727 },
+ { "compass", 0xE728 },
+ { "direction", 0x27A2 },
+ { "hair-cross", 0x1F3AF },
+ { "share", 0xE73C },
+ { "shareable", 0xE73E },
+ { "heart", 0x2665 },
+ { "heart-empty", 0x2661 },
+ { "star", 0x2605 },
+ { "star-empty", 0x2606 },
+ { "thumbs-up", 0x1F44D },
+ { "thumbs-down", 0x1F44E },
+ { "chat", 0xE720 },
+ { "comment", 0xE718 },
+ { "quote", 0x275E },
+ { "home", 0x2302 },
+ { "popup", 0xE74C },
+ { "search", 0x1F50D },
+ { "flashlight", 0x1F526 },
+ { "print", 0xE716 },
+ { "bell", 0x1F514 },
+ { "link", 0x1F517 },
+ { "flag", 0x2691 },
+ { "cog", 0x2699 },
+ { "tools", 0x2692 },
+ { "trophy", 0x1F3C6 },
+ { "tag", 0xE70C },
+ { "camera", 0x1F4F7 },
+ { "megaphone", 0x1F4E3 },
+ { "moon", 0x263D },
+ { "palette", 0x1F3A8 },
+ { "leaf", 0x1F342 },
+ { "note", 0x266A },
+ { "beamed-note", 0x266B },
+ { "new", 0x1F4A5 },
+ { "graduation-cap", 0x1F393 },
+ { "book", 0x1F4D5 },
+ { "newspaper", 0x1F4F0 },
+ { "bag", 0x1F45C },
+ { "airplane", 0x2708 },
+ { "lifebuoy", 0xE788 },
+ { "eye", 0xE70A },
+ { "clock", 0x1F554 },
+ { "mic", 0x1F3A4 },
+ { "calendar", 0x1F4C5 },
+ { "flash", 0x26A1 },
+ { "thunder-cloud", 0x26C8 },
+ { "droplet", 0x1F4A7 },
+ { "cd", 0x1F4BF },
+ { "briefcase", 0x1F4BC },
+ { "air", 0x1F4A8 },
+ { "hourglass", 0x23F3 },
+ { "gauge", 0x1F6C7 },
+ { "language", 0x1F394 },
+ { "network", 0xE776 },
+ { "key", 0x1F511 },
+ { "battery", 0x1F50B },
+ { "bucket", 0x1F4FE },
+ { "magnet", 0xE7A1 },
+ { "drive", 0x1F4FD },
+ { "cup", 0x2615 },
+ { "rocket", 0x1F680 },
+ { "brush", 0xE79A },
+ { "suitcase", 0x1F6C6 },
+ { "traffic-cone", 0x1F6C8 },
+ { "globe", 0x1F30E },
+ { "keyboard", 0x2328 },
+ { "browser", 0xE74E },
+ { "publish", 0xE74D },
+ { "progress-3", 0xE76B },
+ { "progress-2", 0xE76A },
+ { "progress-1", 0xE769 },
+ { "progress-0", 0xE768 },
+ { "light-down", 0x1F505 },
+ { "light-up", 0x1F506 },
+ { "adjust", 0x25D1 },
+ { "code", 0xE714 },
+ { "monitor", 0x1F4BB },
+ { "infinity", 0x221E },
+ { "light-bulb", 0x1F4A1 },
+ { "credit-card", 0x1F4B3 },
+ { "database", 0x1F4F8 },
+ { "voicemail", 0x2707 },
+ { "clipboard", 0x1F4CB },
+ { "cart", 0xE73D },
+ { "box", 0x1F4E6 },
+ { "ticket", 0x1F3AB },
+ { "rss", 0xE73A },
+ { "signal", 0x1F4F6 },
+ { "thermometer", 0x1F4FF },
+ { "water", 0x1F4A6 },
+ { "sweden", 0xF601 },
+ { "line-graph", 0x1F4C8 },
+ { "pie-chart", 0x25F4 },
+ { "bar-graph", 0x1F4CA },
+ { "area-graph", 0x1F53E },
+ { "lock", 0x1F512 },
+ { "lock-open", 0x1F513 },
+ { "logout", 0xE741 },
+ { "login", 0xE740 },
+ { "check", 0x2713 },
+ { "cross", 0x274C },
+ { "squared-minus", 0x229F },
+ { "squared-plus", 0x229E },
+ { "squared-cross", 0x274E },
+ { "circled-minus", 0x2296 },
+ { "circled-plus", 0x2295 },
+ { "circled-cross", 0x2716 },
+ { "minus", 0x2796 },
+ { "plus", 0x2795 },
+ { "erase", 0x232B },
+ { "block", 0x1F6AB },
+ { "info", 0x2139 },
+ { "circled-info", 0xE705 },
+ { "help", 0x2753 },
+ { "circled-help", 0xE704 },
+ { "warning", 0x26A0 },
+ { "cycle", 0x1F504 },
+ { "cw", 0x27F3 },
+ { "ccw", 0x27F2 },
+ { "shuffle", 0x1F500 },
+ { "back", 0x1F519 },
+ { "level-down", 0x21B3 },
+ { "retweet", 0xE717 },
+ { "loop", 0x1F501 },
+ { "back-in-time", 0xE771 },
+ { "level-up", 0x21B0 },
+ { "switch", 0x21C6 },
+ { "numbered-list", 0xE005 },
+ { "add-to-list", 0xE003 },
+ { "layout", 0x268F },
+ { "list", 0x2630 },
+ { "text-doc", 0x1F4C4 },
+ { "text-doc-inverted", 0xE731},
+ { "doc", 0xE730 },
+ { "docs", 0xE736 },
+ { "landscape-doc", 0xE737 },
+ { "picture", 0x1F304 },
+ { "video", 0x1F3AC },
+ { "music", 0x1F3B5 },
+ { "folder", 0x1F4C1 },
+ { "archive", 0xE800 },
+ { "trash", 0xE729 },
+ { "upload", 0x1F4E4 },
+ { "download", 0x1F4E5 },
+ { "save", 0x1F4BE },
+ { "install", 0xE778 },
+ { "cloud", 0x2601 },
+ { "upload-cloud", 0xE711 },
+ { "bookmark", 0x1F516 },
+ { "bookmarks", 0x1F4D1 },
+ { "open-book", 0x1F4D6 },
+ { "play", 0x25B6 },
+ { "paus", 0x2016 },
+ { "record", 0x25CF },
+ { "stop", 0x25A0 },
+ { "ff", 0x23E9 },
+ { "fb", 0x23EA },
+ { "to-start", 0x23EE },
+ { "to-end", 0x23ED },
+ { "resize-full", 0xE744 },
+ { "resize-small", 0xE746 },
+ { "volume", 0x23F7 },
+ { "sound", 0x1F50A },
+ { "mute", 0x1F507 },
+ { "flow-cascade", 0x1F568 },
+ { "flow-branch", 0x1F569 },
+ { "flow-tree", 0x1F56A },
+ { "flow-line", 0x1F56B },
+ { "flow-parallel", 0x1F56C },
+ { "left-bold", 0xE4AD },
+ { "down-bold", 0xE4B0 },
+ { "up-bold", 0xE4AF },
+ { "right-bold", 0xE4AE },
+ { "left", 0x2B05 },
+ { "down", 0x2B07 },
+ { "up", 0x2B06 },
+ { "right", 0x27A1 },
+ { "circled-left", 0xE759 },
+ { "circled-down", 0xE758 },
+ { "circled-up", 0xE75B },
+ { "circled-right", 0xE75A },
+ { "triangle-left", 0x25C2 },
+ { "triangle-down", 0x25BE },
+ { "triangle-up", 0x25B4 },
+ { "triangle-right", 0x25B8 },
+ { "chevron-left", 0xE75D },
+ { "chevron-down", 0xE75C },
+ { "chevron-up", 0xE75F },
+ { "chevron-right", 0xE75E },
+ { "chevron-small-left", 0xE761 },
+ { "chevron-small-down", 0xE760 },
+ { "chevron-small-up", 0xE763 },
+ { "chevron-small-right",0xE762 },
+ { "chevron-thin-left", 0xE765 },
+ { "chevron-thin-down", 0xE764 },
+ { "chevron-thin-up", 0xE767 },
+ { "chevron-thin-right", 0xE766 },
+ { "left-thin", 0x2190 },
+ { "down-thin", 0x2193 },
+ { "up-thin", 0x2191 },
+ { "right-thin", 0x2192 },
+ { "arrow-combo", 0xE74F },
+ { "three-dots", 0x23F6 },
+ { "two-dots", 0x23F5 },
+ { "dot", 0x23F4 },
+ { "cc", 0x1F545 },
+ { "cc-by", 0x1F546 },
+ { "cc-nc", 0x1F547 },
+ { "cc-nc-eu", 0x1F548 },
+ { "cc-nc-jp", 0x1F549 },
+ { "cc-sa", 0x1F54A },
+ { "cc-nd", 0x1F54B },
+ { "cc-pd", 0x1F54C },
+ { "cc-zero", 0x1F54D },
+ { "cc-share", 0x1F54E },
+ { "cc-remix", 0x1F54F },
+ { "db-logo", 0x1F5F9 },
+ { "db-shape", 0x1F5FA },
+
+/* { "icon-cloud", 0x2601 },
+ { "icon-at", 0x0040 },
+ { "icon-plus", 0x002B },
+
+ { "icon-arrow_up", 0x2191 },
+ { "icon-arrow_down", 0x2193 },
+ { "icon-arrow_right", 0x2192 },
+ { "icon-arrow_left", 0x2190 },
+ { "icon-chevron_down", 0xf004 },
+ { "icon-chevron_up", 0xf005 },
+ { "icon-chevron_right", 0xf006 },
+ { "icon-chevron_left", 0xf007 },
+ { "icon-reorder", 0xf008 },
+ { "icon-list", 0xf009 },
+ { "icon-reorder_square", 0xf00a },
+ { "icon-reorder_square_line", 0xf00b },
+ { "icon-coverflow", 0xf00c },
+ { "icon-coverflow_line", 0xf00d },
+ { "icon-pause", 0xf00e },
+ { "icon-play", 0xf00f },
+
+ { "icon-step_forward", 0xf010 },
+ { "icon-step_backward", 0xf011 },
+ { "icon-fast_forward", 0xf012 },
+ { "icon-fast_backward", 0xf013 },
+ { "icon-cloud_upload", 0xf014 },
+ { "icon-cloud_download", 0xf015 },
+ { "icon-data_science", 0xf016 },
+ { "icon-data_science_black", 0xf017 },
+ { "icon-globe", 0xf018 },
+ { "icon-globe_black", 0xf019 },
+ { "icon-math_ico", 0xf01a },
+ { "icon-math", 0xf01b },
+ { "icon-math_black", 0xf01c },
+ { "icon-paperplane_ico", 0xf01d },
+ { "icon-paperplane", 0xf01e },
+ { "icon-paperplane_black", 0xf01f },
+
+ { "icon-color_balance", 0xf020 },
+ { "icon-star", 0x2605 },
+ { "icon-star_half", 0xf022 },
+ { "icon-star_empty", 0x2606 },
+ { "icon-star_half_empty", 0xf024 },
+ { "icon-reload", 0xf025 },
+
+ { "icon-heart", 0x2665 },
+ { "icon-heart_broken", 0xf028 },
+ { "icon-hashtag", 0xf029 },
+ { "icon-reply", 0xf02a },
+ { "icon-retweet", 0xf02b },
+ { "icon-signin", 0xf02c },
+ { "icon-signout", 0xf02d },
+ { "icon-download", 0xf02e },
+ { "icon-upload", 0xf02f },
+
+
+ { "icon-placepin", 0xf031 },
+ { "icon-display_screen", 0xf032 },
+ { "icon-tablet", 0xf033 },
+ { "icon-smartphone", 0xf034 },
+ { "icon-connected_object", 0xf035 },
+ { "icon-lock", 0xF512 },
+ { "icon-unlock", 0xF513 },
+ { "icon-camera", 0xF4F7 },
+ { "icon-isight", 0xf039 },
+ { "icon-video_camera", 0xf03a },
+ { "icon-random", 0xf03b },
+ { "icon-message", 0xF4AC },
+ { "icon-discussion", 0xf03d },
+ { "icon-calendar", 0xF4C5 },
+ { "icon-ringbell", 0xf03f },
+
+ { "icon-movie", 0xf040 },
+ { "icon-mail", 0x2709 },
+ { "icon-pen", 0x270F },
+ { "icon-settings", 0x9881 },
+ { "icon-measure", 0xf044 },
+ { "icon-vector", 0xf045 },
+ { "icon-vector_pen", 0x2712 },
+ { "icon-mute_on", 0xf047 },
+ { "icon-mute_off", 0xf048 },
+ { "icon-home", 0x2302 },
+ { "icon-sheet", 0xf04a },
+ { "icon-arrow_big_right", 0x21C9 },
+ { "icon-arrow_big_left", 0x21C7 },
+ { "icon-arrow_big_down", 0x21CA },
+ { "icon-arrow_big_up", 0x21C8 },
+ { "icon-dribbble_circle", 0xf04f },
+
+ { "icon-dribbble", 0xf050 },
+ { "icon-facebook_circle", 0xf051 },
+ { "icon-facebook", 0xf052 },
+ { "icon-git_circle_alt", 0xf053 },
+ { "icon-git_circle", 0xf054 },
+ { "icon-git", 0xf055 },
+ { "icon-octopus", 0xf056 },
+ { "icon-twitter_circle", 0xf057 },
+ { "icon-twitter", 0xf058 },
+ { "icon-google_plus_circle", 0xf059 },
+ { "icon-google_plus", 0xf05a },
+ { "icon-linked_in_circle", 0xf05b },
+ { "icon-linked_in", 0xf05c },
+ { "icon-instagram", 0xf05d },
+ { "icon-instagram_circle", 0xf05e },
+ { "icon-mfg_icon", 0xf05f },
+
+ { "icon-mfg_icon_circle", 0xf060 },
+ { "icon-user", 0xf061 },
+ { "icon-user_male", 0xf062 },
+ { "icon-user_female", 0xf063 },
+ { "icon-users", 0xf064 },
+
+ { "icon-file_open", 0xF4C2 },
+ { "icon-file_close", 0xf067 },
+ { "icon-file_alt", 0xf068 },
+ { "icon-file_close_alt", 0xf069 },
+ { "icon-attachment", 0xf06a },
+ { "icon-check", 0x2713 },
+ { "icon-cross_mark", 0x274C },
+ { "icon-cancel_circle", 0xF06E },
+ { "icon-check_circle", 0xf06d },
+ { "icon-magnifying", 0xF50D },
+
+ { "icon-inbox", 0xf070 },
+ { "icon-clock", 0x23F2 },
+ { "icon-stopwatch", 0x23F1 },
+ { "icon-hourglass", 0x231B },
+ { "icon-trophy", 0xf074 },
+ { "icon-unlock_alt", 0xF075 },
+ { "icon-lock_alt", 0xF510 },
+ { "icon-arrow_doubled_right", 0x21D2 },
+ { "icon-arrow_doubled_left", 0x21D0 },
+ { "icon-arrow_doubled_down", 0x21D3 },
+ { "icon-arrow_doubled_up", 0x21D1 },
+ { "icon-link", 0xf07B },
+ { "icon-warning", 0x2757 },
+ { "icon-warning_alt", 0x2755 },
+ { "icon-magnifying_plus", 0xf07E },
+ { "icon-magnifying_minus", 0xf07F },
+
+ { "icon-white_question", 0x2754 },
+ { "icon-black_question", 0x2753 },
+ { "icon-stop", 0xf080 },
+ { "icon-share", 0xf081 },
+ { "icon-eye", 0xf082 },
+ { "icon-trash_can", 0xf083 },
+ { "icon-hard_drive", 0xf084 },
+ { "icon-information_black", 0xf085 },
+ { "icon-information_white", 0xf086 },
+ { "icon-printer", 0xf087 },
+ { "icon-letter", 0xf088 },
+ { "icon-soundcloud", 0xf089 },
+ { "icon-soundcloud_circle", 0xf08A },
+ { "icon-anchor", 0x2693 },
+
+ { "icon-female_sign", 0x2640 },
+ { "icon-male_sign", 0x2642 },
+ { "icon-joystick", 0xF514 },
+ { "icon-high_voltage", 0x26A1 },
+ { "icon-fire", 0xF525 },
+ { "icon-newspaper", 0xF4F0 },
+ { "icon-chart", 0xF526 },
+ { "icon-spread", 0xF527 },
+
+ { "icon-spinner_1", 0xF528 },
+ { "icon-spinner_2", 0xF529 },
+
+ { "icon-chart_alt", 0xF530 },
+ { "icon-label", 0xF531 },*/
+ };
+ static const int nicons = sizeof(icons) / sizeof(struct Icon);
+ int i;
+ static char str[8];
+
+ for (i = 0; i < nicons; i++) {
+ if (strcmp(icons[i].name, name) == 0) {
+ cpToUTF8(icons[i].codepoint, str);
+ return str;
+ }
+ }
+
+ return "";
+}
13 example/images.txt
@@ -0,0 +1,13 @@
+Image credits
+http://cuteoverload.com/2013/11/05/mom-taxi-xvi-birthday-party/
+http://cuteoverload.com/2013/11/05/benson-hedges-private-eye-in-the-case-of-the-crafty-craftsman/
+http://cuteoverload.com/2013/11/05/no-underwater-ballets/
+http://cuteoverload.com/2013/11/05/every-nose-has-a-story/
+http://cuteoverload.com/2013/11/04/nosevember-nozzle-nose/
+http://cuteoverload.com/2013/11/04/this-just-in-super-strength-cute/
+http://cuteoverload.com/2013/11/03/have-a-bunderful-sunday/
+http://cuteoverload.com/2013/11/02/caturday-sense-a-common-theme-here/
+http://cuteoverload.com/2013/11/01/nosevember-1st-24-hours-of-noses-1148pm-pt/
+http://cuteoverload.com/2013/04/02/there-might-be-something-cuter-than-this/
+http://cuteoverload.com/2013/07/17/snorting-micro-peeg-gets-belleh-rubs-interwebs-explode/
+http://cuteoverload.com/2013/08/07/bark-in-the-park-v3-0/
BIN  example/images/image1.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image10.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image11.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image12.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image2.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image3.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image4.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image5.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image6.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image7.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image8.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/images/image9.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  example/mfglabsiconset-webfont.ttf
Binary file not shown
84 example/templates.xml
@@ -0,0 +1,84 @@
+<style>
+ $color = rgba(255,255,255,255);
+
+ @font-face {
+ font-family: "Roboto";
+ font-weight: bold;
+ src: "../examples/Roboto-light.ttf";
+ }
+ @font-face {
+ font-family: "Entypo";
+ src: "../examples/entypo.ttf";
+ }
+
+ .icon-search {
+ font-family: "Entypo";
+ font-size: 28px;
+ content: 0x1F50D;
+ }
+ .icon-circled-cross {
+ font-family: "Entypo";
+ font-size: 28px;
+ content: 0x2716;
+ }
+ .icon-plus {
+ font-family: "Entypo";
+ font-size: 28px;
+ content: "\2796";
+ }
+
+ .search-box {
+ padding: 5px;
+ }
+
+ .footer-buttons {
+ padding: 5px;
+ }
+
+ /* default style */
+ header {
+ height: 30px;
+ /*font-size: 26px;*/
+ }
+
+ input {
+ height: ;
+ font-size: 26px;
+ }
+
+</style>
+
+<template id="material-window">
+ <win width="300px" height="500px" align="justify">
+ <col align="justify">
+ <header>Materials</header>
+ <row height="auto" style="search-box">
+ <input id="search" grow="1">
+ <icon style="icon-search" />
+ <field grow="1" />
+ <icon style="icon-circled-cross" />
+ </input>
+ </row>
+ <col id="materials" grow="1" height="10px" align="justify" />
+ <row height="auto" style="footer-buttons">
+ <spacer grow="1" />
+ <button id="add-item"><icon style="icon-plus"/>Add</button>
+ <button id="remove">Remove</button>
+ </row>
+ </col>
+ </win>
+</template>
+
+<template id="material-item">
+ <item padding="4px" align="center">
+ <img id="thumbnail" width="25px" height="25px" />
+ <label id="name" grow="1" />
+ </item>
+</template>
+
+<template id="material-noitems">
+ <col padding="4px" pack="center" align="center">
+ <icon src="sad-face" />
+ <label>Sorry, no items found.</label>
+ </col>
+</template>
32 premake4.lua
@@ -0,0 +1,32 @@
+
+local action = _ACTION or ""
+
+solution "nanovg"
+ location ( "build" )
+ configurations { "Debug", "Release" }
+ platforms {"native", "x64", "x32"}
+
+ project "example"
+ kind "ConsoleApp"
+ language "C"
+ files { "example/*.c", "src/*.h", "src/*.c" }
+ includedirs { "src" }
+ targetdir("build")
+
+ configuration { "linux" }
+ links { "X11","Xrandr", "rt", "GL", "GLU", "pthread" }
+
+ configuration { "windows" }
+ links { "glu32","opengl32", "gdi32", "winmm", "user32" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
1,134 src/fontstash.h
@@ -0,0 +1,1134 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#ifndef FONS_H
+#define FONS_H
+
+#define FONS_INVALID -1
+
+enum FONSflags {
+ FONS_ZERO_TOPLEFT = 1,
+ FONS_ZERO_BOTTOMLEFT = 2,
+};
+
+enum FONSaling {
+ // Horizontal align
+ FONS_ALIGN_LEFT = 1<<0, // Default
+ FONS_ALIGN_CENTER = 1<<1,
+ FONS_ALIGN_RIGHT = 1<<2,
+ // Vertical align
+ FONS_ALIGN_TOP = 1<<3,
+ FONS_ALIGN_MIDDLE = 1<<4,
+ FONS_ALIGN_BOTTOM = 1<<5,
+ FONS_ALIGN_BASELINE = 1<<6, // Default
+};
+
+struct FONSparams {
+ int width, height;
+ unsigned char flags;
+ void* userPtr;
+ int (*renderCreate)(void* uptr, int width, int height);
+ void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data);
+ void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts);
+ void (*renderDelete)(void* uptr);
+};
+
+struct FONSquad
+{
+ float x0,y0,s0,t0;
+ float x1,y1,s1,t1;
+};
+
+struct FONStextIter {
+ float x, y, scale, spacing;
+ short isize, iblur;
+ struct FONSfont* font;
+ struct FONSglyph* prevGlyph;
+ const char* str;
+ unsigned int utf8state;
+};
+
+// Contructor and destructor.
+struct FONScontext* fonsCreate(struct FONSparams* params);
+void fonsDelete(struct FONScontext* s);
+
+// Add fonts
+int fonsAddFont(struct FONScontext* s, const char* name, const char* path);
+int fonsAddFontMem(struct FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData);
+int fonsGetFontByName(struct FONScontext* s, const char* name);
+
+// State handling
+void fonsPushState(struct FONScontext* s);
+void fonsPopState(struct FONScontext* s);
+void fonsClearState(struct FONScontext* s);
+
+// State setting
+void fonsSetSize(struct FONScontext* s, float size);
+void fonsSetColor(struct FONScontext* s, unsigned int color);
+void fonsSetSpacing(struct FONScontext* s, float spacing);
+void fonsSetBlur(struct FONScontext* s, float blur);
+void fonsSetAlign(struct FONScontext* s, int align);
+void fonsSetFont(struct FONScontext* s, int font);
+
+// Draw text
+void fonsDrawText(struct FONScontext* s, float x, float y, const char* string, float* dx);
+
+// Measure text
+void fonsTextBounds(struct FONScontext* s, const char* string, float* width, float* bounds);
+void fonsVertMetrics(struct FONScontext* s, float* ascender, float* descender, float* lineh);
+
+// Text iterator
+int fonsTextIterInit(struct FONScontext* stash, struct FONStextIter* iter, float x, float y, const char* s);
+int fonsTextIterNext(struct FONScontext* stash, struct FONStextIter* iter, struct FONSquad* quad);
+
+// Pull texture data
+const unsigned char* fonsGetTextureData(struct FONScontext* stash, int* width, int* height);
+int fonsValidateTexture(struct FONScontext* s, int* dirty);
+
+// Draws the stash texture for debugging
+void fonsDrawDebug(struct FONScontext* s, float x, float y);
+
+#endif // FONS_H
+
+
+#ifdef FONTSTASH_IMPLEMENTATION
+
+#define STB_TRUETYPE_IMPLEMENTATION
+static void* fons__tmpalloc(size_t size, void* up);
+static void fons__tmpfree(void* ptr, void* up);
+#define STBTT_malloc(x,u) fons__tmpalloc(x,u)
+#define STBTT_free(x,u) fons__tmpfree(x,u)
+#include "stb_truetype.h"
+
+#ifndef FONS_SCRATCH_BUF_SIZE
+# define FONS_SCRATCH_BUF_SIZE 16000
+#endif
+#ifndef FONS_HASH_LUT_SIZE
+# define FONS_HASH_LUT_SIZE 256
+#endif
+#ifndef FONS_INIT_FONTS
+# define FONS_INIT_FONTS 4
+#endif
+#ifndef FONS_INIT_ROWS
+# define FONS_INIT_ROWS 64
+#endif
+#ifndef FONS_INIT_GLYPHS
+# define FONS_INIT_GLYPHS 256
+#endif
+#ifndef FONS_VERTEX_COUNT
+# define FONS_VERTEX_COUNT 1024
+#endif
+#ifndef FONS_MAX_STATES
+# define FONS_MAX_STATES 20
+#endif
+
+static unsigned int fons__hashint(unsigned int a)
+{
+ a += ~(a<<15);
+ a ^= (a>>10);
+ a += (a<<3);
+ a ^= (a>>6);
+ a += ~(a<<11);
+ a ^= (a>>16);
+ return a;
+}
+
+static int fons__mini(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+static int fons__maxi(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+struct FONSrow
+{
+ short x,y,h;
+};
+
+struct FONSglyph
+{
+ unsigned int codepoint;
+ int index;
+ int next;
+ short size, blur;
+ short x0,y0,x1,y1;
+ short xadv,xoff,yoff;
+};
+
+struct FONSfont
+{
+ stbtt_fontinfo font;
+ char name[64];
+ unsigned char* data;
+ int dataSize;
+ unsigned char freeData;
+ float ascender;
+ float descender;
+ float lineh;
+ struct FONSglyph* glyphs;
+ int cglyphs;
+ int nglyphs;
+ int lut[FONS_HASH_LUT_SIZE];
+};
+
+struct FONSstate
+{
+ int font;
+ int align;
+ float size;
+ unsigned int color;
+ float blur;
+ float spacing;
+};
+
+struct FONScontext
+{
+ struct FONSparams params;
+ float itw,ith;
+ unsigned char* texData;
+ int dirtyRect[4];
+ struct FONSrow* rows;
+ int crows;
+ int nrows;
+ struct FONSfont** fonts;
+ int cfonts;
+ int nfonts;
+ float verts[FONS_VERTEX_COUNT*2];
+ float tcoords[FONS_VERTEX_COUNT*2];
+ unsigned int colors[FONS_VERTEX_COUNT];
+ int nverts;
+ unsigned char scratch[FONS_SCRATCH_BUF_SIZE];
+ int nscratch;
+ struct FONSstate states[FONS_MAX_STATES];
+ int nstates;
+};
+
+static void* fons__tmpalloc(size_t size, void* up)
+{
+ struct FONScontext* stash = (struct FONScontext*)up;
+ if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE)
+ return NULL;
+ unsigned char* ptr = stash->scratch + stash->nscratch;
+ stash->nscratch += (int)size;
+ return ptr;
+}
+
+static void fons__tmpfree(void* ptr, void* up)
+{
+ // empty
+}
+
+// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+
+#define FONS_UTF8_ACCEPT 0
+#define FONS_UTF8_REJECT 12
+
+static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte)
+{
+ static const unsigned char utf8d[] = {
+ // The first part of the table maps bytes to character classes that
+ // to reduce the size of the transition table and create bitmasks.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+ // The second part is a transition table that maps a combination
+ // of a state of the automaton and a character class to a state.
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12,
+ };
+
+ unsigned int type = utf8d[byte];
+
+ *codep = (*state != FONS_UTF8_ACCEPT) ?
+ (byte & 0x3fu) | (*codep << 6) :
+ (0xff >> type) & (byte);
+
+ *state = utf8d[256 + *state + type];
+ return *state;
+}
+
+struct FONScontext* fonsCreate(struct FONSparams* params)
+{
+ struct FONScontext* stash = NULL;
+
+ // Allocate memory for the font stash.
+ stash = (struct FONScontext*)malloc(sizeof(struct FONScontext));
+ if (stash == NULL) goto error;
+ memset(stash, 0, sizeof(struct FONScontext));
+
+ stash->params = *params;
+
+ if (stash->params.renderCreate != NULL)
+ if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) goto error;
+
+ // Allocate space for rows
+ stash->rows = (struct FONSrow*)malloc(sizeof(struct FONSrow) * FONS_INIT_ROWS);
+ if (stash->rows == NULL) goto error;
+ memset(stash->rows, 0, sizeof(struct FONSrow) * FONS_INIT_ROWS);
+ stash->crows = FONS_INIT_ROWS;
+ stash->nrows = 0;
+
+ // Allocate space for fonts.
+ stash->fonts = (struct FONSfont**)malloc(sizeof(struct FONSfont*) * FONS_INIT_FONTS);
+ if (stash->fonts == NULL) goto error;
+ memset(stash->fonts, 0, sizeof(struct FONSfont*) * FONS_INIT_FONTS);
+ stash->cfonts = FONS_INIT_FONTS;
+ stash->nfonts = 0;
+
+ // Create texture for the cache.
+ stash->itw = 1.0f/stash->params.width;
+ stash->ith = 1.0f/stash->params.height;
+ stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height);
+ if (stash->texData == NULL) goto error;
+ memset(stash->texData, 0, stash->params.width * stash->params.height);
+
+ stash->dirtyRect[0] = stash->params.width;
+ stash->dirtyRect[1] = stash->params.height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+
+ fonsPushState(stash);
+ fonsClearState(stash);
+
+ return stash;
+
+error:
+ fonsDelete(stash);
+ return NULL;
+}
+
+static struct FONSstate* fons__getState(struct FONScontext* stash)
+{
+ return &stash->states[stash->nstates-1];
+}
+
+void fonsSetSize(struct FONScontext* stash, float size)
+{
+ fons__getState(stash)->size = size;
+}
+
+void fonsSetColor(struct FONScontext* stash, unsigned int color)
+{
+ fons__getState(stash)->color = color;
+}
+
+void fonsSetSpacing(struct FONScontext* stash, float spacing)
+{
+ fons__getState(stash)->spacing = spacing;
+}
+
+void fonsSetBlur(struct FONScontext* stash, float blur)
+{
+ fons__getState(stash)->blur = blur;
+}
+
+void fonsSetAlign(struct FONScontext* stash, int align)
+{
+ fons__getState(stash)->align = align;
+}
+
+void fonsSetFont(struct FONScontext* stash, int font)
+{
+ fons__getState(stash)->font = font;
+}
+
+void fonsPushState(struct FONScontext* stash)
+{
+ if (stash->nstates >= FONS_MAX_STATES)
+ return;
+ if (stash->nstates > 0)
+ memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(struct FONSstate));
+ stash->nstates++;
+}
+
+void fonsPopState(struct FONScontext* stash)
+{
+ if (stash->nstates <= 1)
+ return;
+ stash->nstates--;
+}
+
+void fonsClearState(struct FONScontext* stash)
+{
+ struct FONSstate* state = fons__getState(stash);
+ state->size = 12.0f;
+ state->color = 0xffffffff;
+ state->font = 0;
+ state->blur = 0;
+ state->spacing = 0;
+ state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE;
+}
+
+
+
+int fonsAddFont(struct FONScontext* stash, const char* name, const char* path)
+{
+ FILE* fp = 0;
+ int dataSize = 0;
+ unsigned char* data = NULL;
+
+ // Read in the font data.
+ fp = fopen(path, "rb");
+ if (!fp) goto error;
+ fseek(fp,0,SEEK_END);
+ dataSize = (int)ftell(fp);
+ fseek(fp,0,SEEK_SET);
+ data = (unsigned char*)malloc(dataSize);
+ if (data == NULL) goto error;
+ fread(data, 1, dataSize, fp);
+ fclose(fp);
+ fp = 0;
+
+ return fonsAddFontMem(stash, name, data, dataSize, 1);
+
+error:
+ if (data) free(data);
+ if (fp) fclose(fp);
+ return 0;
+}
+
+static void fons__freeFont(struct FONSfont* font)
+{
+ if (font == NULL) return;
+ if (font->glyphs) free(font->glyphs);
+ if (font->freeData && font->data) free(font->data);
+ free(font);
+}
+
+static int fons__allocFont(struct FONScontext* stash)
+{
+ struct FONSfont** fonts = NULL;
+ struct FONSfont* font = NULL;
+ if (stash->nfonts+1 > stash->cfonts) {
+ stash->cfonts *= 2;
+ fonts = (struct FONSfont**)malloc(sizeof(struct FONSfont*) * stash->cfonts);
+ if (fonts == NULL) goto error;
+ memset(fonts, 0, sizeof(struct FONSfont*) * stash->cfonts);
+ if (stash->nfonts > 0)
+ memcpy(fonts, stash->fonts, sizeof(struct FONSfont*));
+
+ free(stash->fonts);
+ stash->fonts = fonts;
+ }
+ font = (struct FONSfont*)malloc(sizeof(struct FONSfont));
+ if (font == NULL) goto error;
+ memset(font, 0, sizeof(struct FONSfont));
+
+ font->glyphs = (struct FONSglyph*)malloc(sizeof(struct FONSglyph) * FONS_INIT_GLYPHS);
+ if (font->glyphs == NULL) goto error;
+ font->cglyphs = FONS_INIT_GLYPHS;
+ font->nglyphs = 0;
+
+ stash->fonts[stash->nfonts++] = font;
+ return stash->nfonts-1;
+
+error:
+ fons__freeFont(font);
+
+ return FONS_INVALID;
+}
+
+int fonsAddFontMem(struct FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData)
+{
+ int i, ascent, descent, fh, lineGap;
+ struct FONSfont* font;
+
+ int idx = fons__allocFont(stash);
+ if (idx == FONS_INVALID)
+ return FONS_INVALID;
+
+ font = stash->fonts[idx];
+
+ strncpy(font->name, name, sizeof(font->name));
+ font->name[sizeof(font->name)-1] = '\0';
+
+ // Init hash lookup.
+ for (i = 0; i < FONS_HASH_LUT_SIZE; ++i)
+ font->lut[i] = -1;
+
+ // Read in the font data.
+ font->dataSize = dataSize;
+ font->data = data;
+ font->freeData = freeData;
+
+ // Init stb_truetype
+ stash->nscratch = 0;
+ font->font.userdata = stash;
+ if (!stbtt_InitFont(&font->font, font->data, 0)) goto error;
+
+ // Store normalized line height. The real line height is got
+ // by multiplying the lineh by font size.
+ stbtt_GetFontVMetrics(&font->font, &ascent, &descent, &lineGap);
+ fh = ascent - descent;
+ font->ascender = (float)ascent / (float)fh;
+ font->descender = (float)descent / (float)fh;
+ font->lineh = (float)(fh + lineGap) / (float)fh;
+
+ return idx;
+
+error:
+ fons__freeFont(font);
+ stash->nfonts--;
+ return FONS_INVALID;
+}
+
+int fonsGetFontByName(struct FONScontext* s, const char* name)
+{
+ int i;
+ for (i = 0; i < s->nfonts; i++) {
+ if (strcmp(s->fonts[i]->name, name) == 0)
+ return i;
+ }
+ return FONS_INVALID;
+}
+
+const unsigned char* fonsGetTextureData(struct FONScontext* stash, int* width, int* height)
+{
+ if (width != NULL)
+ *width = stash->params.width;
+ if (height != NULL)
+ *height = stash->params.height;
+ return stash->texData;
+}
+
+int fonsValidateTexture(struct FONScontext* stash, int* dirty)
+{
+ if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) {
+ dirty[0] = stash->dirtyRect[0];
+ dirty[1] = stash->dirtyRect[1];
+ dirty[2] = stash->dirtyRect[2];
+ dirty[3] = stash->dirtyRect[3];
+ // Reset dirty rect
+ stash->dirtyRect[0] = stash->params.width;
+ stash->dirtyRect[1] = stash->params.height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static struct FONSrow* fons__allocRow(struct FONScontext* stash)
+{
+ struct FONSrow* rows = NULL;
+ if (stash->nrows+1 > stash->crows) {
+ stash->crows *= 2;
+ rows = (struct FONSrow*)malloc(sizeof(struct FONSrow) * stash->crows);
+ if (rows == NULL) return NULL;
+ memset(rows, 0, sizeof(struct FONSrow) * stash->crows);
+ if (stash->nrows > 0)
+ memcpy(rows, stash->rows, sizeof(struct FONSrow) * stash->nrows);
+ free(stash->rows);
+ stash->rows = rows;
+ }
+ stash->nrows++;
+ return &stash->rows[stash->nrows-1];
+}
+
+static struct FONSglyph* fons__allocGlyph(struct FONSfont* font)
+{
+ struct FONSglyph* glyphs = NULL;
+ if (font->nglyphs+1 > font->cglyphs) {
+ font->cglyphs *= 2;
+ glyphs = (struct FONSglyph*)malloc(sizeof(struct FONSglyph) * font->cglyphs);
+ if (glyphs == NULL) return NULL;
+ memset(glyphs, 0, sizeof(struct FONSglyph) * font->cglyphs);
+ if (font->nglyphs > 0)
+ memcpy(glyphs, font->glyphs, sizeof(struct FONSglyph) * font->nglyphs);
+ free(font->glyphs);
+ font->glyphs = glyphs;
+ }
+ font->nglyphs++;
+ return &font->glyphs[font->nglyphs-1];
+}
+
+static int fons__clamp(int a, int amin, int amax) { return a < amin ? amin : (a > amax ? amax : a); }
+
+static void fons__hblur(unsigned char* dst, int dstStride, unsigned char* src, int srcStride,
+ int w, int h, int blur, unsigned short* idx)
+{
+ int x,y,acc;
+ int d = 1 + 2*blur;
+ int w1 = w-1;
+ idx += blur;
+
+ for (x = -blur; x <= w+blur; x++) {
+ idx[x] = fons__clamp(x,0,w1);
+ }
+
+ for (y = 0; y < h; y++) {
+ // warm up accumulator
+ acc = 0;
+ for (x = -blur; x < blur; x++)
+ acc += src[idx[x]];
+ for (x = 0; x < w; x++) {
+ acc += src[idx[x+blur]];
+ dst[x] = (unsigned char)(acc / d);
+ acc -= src[idx[x-blur]];
+ }
+ src += srcStride;
+ dst += dstStride;
+ }
+
+}
+
+static void fons__vblur(unsigned char* dst, int dstStride, unsigned char* src, int srcStride,
+ int w, int h, int blur, unsigned short* idx)
+{
+ int x,y,acc;
+ int d = 1 + 2*blur;
+ int h1 = h-1;
+ idx += blur;
+
+ for (y = -blur; y <= h+blur; y++)
+ idx[y] = fons__clamp(y,0,h1) * srcStride;
+
+ for (x = 0; x < w; x++) {
+ // warm up accumulator
+ acc = 0;
+ for (y = -blur; y < blur; y++)
+ acc += src[idx[y]];
+ for (y = 0; y < h; y++) {
+ acc += src[idx[y+blur]];
+ dst[y*dstStride] = (unsigned char)(acc / d);
+ acc -= src[idx[y-blur]];
+ }
+ src++;
+ dst++;
+ }
+}
+
+static void fons__blur(struct FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur)
+{
+ // It is possible that our scratch is too small for blurring (16k scratch can handle about 120x120 blur).
+ int bufStride = w;
+ unsigned short* idx;
+ unsigned char* buf;
+ buf = (unsigned char*)fons__tmpalloc(w*h, stash);
+ if (buf == NULL) {
+ return;
+ }
+ idx = (unsigned short*)fons__tmpalloc((1+2*blur+fons__maxi(w,h))*sizeof(unsigned short), stash);
+ if (idx == NULL) {
+ return;
+ }
+
+ // Tent.
+ int b1 = (blur+1)/2, b2 = blur-b1;
+ fons__hblur(buf,bufStride, dst,dstStride, w,h, b1, idx);
+ fons__vblur(dst,dstStride, buf,bufStride, w,h, b1, idx);
+ if (b2 > 0) {
+ fons__hblur(buf,bufStride, dst,dstStride, w,h, b2, idx);
+ fons__vblur(dst,dstStride, buf,bufStride, w,h, b2, idx);
+ }
+}
+
+
+static struct FONSglyph* fons__getGlyph(struct FONScontext* stash, struct FONSfont* font, unsigned int codepoint,
+ short isize, short iblur)
+{
+ int i, g, advance, lsb, x0, y0, x1, y1, gw, gh;
+ float scale;
+ struct FONSglyph* glyph = NULL;
+ unsigned int h;
+ float size = isize/10.0f;
+ int rh, pad, x, y;
+ struct FONSrow* br;
+ unsigned char* dst;
+
+ if (isize < 2) return NULL;
+ if (iblur > 20) iblur = 20;
+ pad = iblur+2;
+
+ // Reset allocator.
+ stash->nscratch = 0;
+
+ // Find code point and size.
+ h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1);
+ i = font->lut[h];
+ while (i != -1) {
+ if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur)
+ return &font->glyphs[i];
+ i = font->glyphs[i].next;
+ }
+
+ // Could not find glyph, create it.
+ scale = stbtt_ScaleForPixelHeight(&font->font, size);
+ g = stbtt_FindGlyphIndex(&font->font, codepoint);
+ stbtt_GetGlyphHMetrics(&font->font, g, &advance, &lsb);
+ stbtt_GetGlyphBitmapBox(&font->font, g, scale,scale, &x0,&y0,&x1,&y1);
+ gw = x1-x0 + pad*2;
+ gh = y1-y0 + pad*2;
+
+ // Find row where the glyph can be fit.
+ br = NULL;
+ rh = (gh+7) & ~7;
+ for (i = 0; i < stash->nrows; ++i) {
+ int rmax = stash->rows[i].h, rmin = rmax - rmax/4;
+ if (rh >= rmin && rh <= rmax && (stash->rows[i].x+gw) <= stash->params.width) {
+ br = &stash->rows[i];
+ break;
+ }
+ }
+
+ // If no row found, add new.
+ if (br == NULL) {
+ short py = 0;
+ // Check that there is enough space.
+ if (stash->nrows > 0) {
+ py = stash->rows[stash->nrows-1].y + stash->rows[stash->nrows-1].h;
+ if (py+rh > stash->params.height)
+ return NULL;
+ }
+ // Init and add row
+ br = fons__allocRow(stash);
+ if (br == NULL)
+ return NULL;
+ br->x = 0;
+ br->y = py;
+ br->h = rh;
+ }
+
+ // Init glyph.
+ glyph = fons__allocGlyph(font);
+ glyph->codepoint = codepoint;
+ glyph->size = isize;
+ glyph->blur = iblur;
+ glyph->index = g;
+ glyph->x0 = br->x;
+ glyph->y0 = br->y;
+ glyph->x1 = glyph->x0+gw;
+ glyph->y1 = glyph->y0+gh;
+ glyph->xadv = (short)(scale * advance * 10.0f);
+ glyph->xoff = x0 - pad;
+ glyph->yoff = y0 - pad;
+ glyph->next = 0;
+
+ // Advance row location.
+ br->x += gw+1;
+
+ // Insert char to hash lookup.
+ glyph->next = font->lut[h];
+ font->lut[h] = font->nglyphs-1;
+
+ // Rasterize
+ dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width];
+ stbtt_MakeGlyphBitmap(&font->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g);
+
+ // Make sure there is one pixel empty border.
+ dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
+ for (y = 0; y < gh; y++) {
+ dst[y*stash->params.width] = 0;
+ dst[gw-1 + y*stash->params.width] = 0;
+ }
+ for (x = 0; x < gw; x++) {
+ dst[x] = 0;
+ dst[x + (gh-1)*stash->params.width] = 0;
+ }
+
+ // Debug code to color the glyph background
+/* dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
+ for (y = 0; y < gh; y++) {
+ for (x = 0; x < gw; x++) {
+ int a = (int)dst[x+y*stash->params.width] + 20;
+ if (a > 255) a = 255;