diff --git a/src/fontstash.h b/src/fontstash.h index 1f1e6a7..add81e6 100644 --- a/src/fontstash.h +++ b/src/fontstash.h @@ -125,12 +125,12 @@ static void fons__tmpfree(void* ptr, void* up); #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_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif #ifndef FONS_VERTEX_COUNT # define FONS_VERTEX_COUNT 1024 #endif @@ -195,17 +195,16 @@ struct FONSstate float spacing; }; -struct FONSrow -{ - short x,y,h; +struct FONSatlasNode { + short x, y, width; }; struct FONSatlas { int width, height; - struct FONSrow* rows; - int crows; - int nrows; + struct FONSatlasNode* nodes; + int nnodes; + int cnodes; }; struct FONScontext @@ -284,14 +283,16 @@ static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsi return *state; } +// Atlas based on Skyline Pin Packer by Jukka Jylänki + static void fons__deleteAtlas(struct FONSatlas* atlas) { if (atlas == NULL) return; - if (atlas->rows != NULL) free(atlas->rows); + if (atlas->nodes != NULL) free(atlas->nodes); free(atlas); } -static struct FONSatlas* fons__allocAtlas(int w, int h, int rows) +static struct FONSatlas* fons__allocAtlas(int w, int h, int nnodes) { struct FONSatlas* atlas = NULL; @@ -303,12 +304,18 @@ static struct FONSatlas* fons__allocAtlas(int w, int h, int rows) atlas->width = w; atlas->height = h; - // Allocate space for rows - atlas->rows = (struct FONSrow*)malloc(sizeof(struct FONSrow) * rows); - if (atlas->rows == NULL) goto error; - memset(atlas->rows, 0, sizeof(struct FONSrow) * rows); - atlas->crows = rows; - atlas->nrows = 0; + // Allocate space for skyline nodes + atlas->nodes = (struct FONSatlasNode*)malloc(sizeof(struct FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(struct FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = w; + atlas->nnodes++; return atlas; @@ -317,63 +324,120 @@ error: return NULL; } -static struct FONSrow* fons__atlasAllocRow(struct FONSatlas* atlas) +static int fons__atlasInsertNode(struct FONSatlas* atlas, int idx, int x, int y, int w) { - struct FONSrow* rows = NULL; - if (atlas->nrows+1 > atlas->crows) { - atlas->crows *= 2; - rows = (struct FONSrow*)malloc(sizeof(struct FONSrow) * atlas->crows); - if (rows == NULL) return NULL; - memset(rows, 0, sizeof(struct FONSrow) * atlas->crows); - if (atlas->nrows > 0) - memcpy(rows, atlas->rows, sizeof(struct FONSrow) * atlas->nrows); - free(atlas->rows); - atlas->rows = rows; + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (struct FONSatlasNode*)realloc(atlas->nodes, sizeof(struct FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; } - atlas->nrows++; - return &atlas->rows[atlas->nrows-1]; + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = x; + atlas->nodes[idx].y = y; + atlas->nodes[idx].width = w; + atlas->nnodes++; + + return 1; } -static int fons__atlasAddRect(struct FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +static void fons__atlasRemoveNode(struct FONSatlas* atlas, int idx) { - int i, py; - struct FONSrow* br = NULL; + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} - // Round row height - rh = (rh+7) & ~7;; - - // Find row where the glyph can be fit. - for (i = 0; i < atlas->nrows; ++i) { - int rmax = atlas->rows[i].h, rmin = rmax - rmax/4; - if (rh >= rmin && rh <= rmax && (atlas->rows[i].x+rw+1) <= atlas->width) { - br = &atlas->rows[i]; +static int fons__atlasAddSkylineLevel(struct FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shaodw of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += shrink; + atlas->nodes[i].width -= shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { break; } } - // If no row found, add new. - if (br == NULL) { - py = 0; - // Check that there is enough space. - if (atlas->nrows > 0) { - py = atlas->rows[atlas->nrows-1].y + atlas->rows[atlas->nrows-1].h+1; - if (py+rh > atlas->height) - return 0; + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; } - // Init and add row - br = fons__atlasAllocRow(atlas); - if (br == NULL) - return 0; - br->x = 0; - br->y = py; - br->h = rh; } - *rx = br->x; - *ry = br->y; + return 1; +} + +static int fons__atlasRectFits(struct FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + if (x + w > atlas->width) + return -1; + int spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(struct FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx, besty, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; - // Advance row location. - br->x += rw+1; + *rx = bestx; + *ry = besty; return 1; } @@ -395,7 +459,7 @@ struct FONScontext* fonsCreateInternal(struct FONSparams* params) goto error; } - stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ROWS); + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); if (stash->atlas == NULL) goto error; // Allocate space for fonts. @@ -499,18 +563,12 @@ static void fons__freeFont(struct FONSfont* 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; + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (struct FONSfont**)realloc(stash->fonts, sizeof(struct FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; } font = (struct FONSfont*)malloc(sizeof(struct FONSfont)); if (font == NULL) goto error; @@ -613,16 +671,10 @@ int fonsGetFontByName(struct FONScontext* s, const char* name) 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->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (struct FONSglyph*)realloc(font->glyphs, sizeof(struct FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; } font->nglyphs++; return &font->glyphs[font->nglyphs-1];