- added debug configs for the lib - added more data for font stash text iterator (ptr to current and next position in string, code point) - renamed nvgVertMetrics() to nvgTextMetrics() - added nvgTextGlyphPositions() which returns glyph x position for the text - added nvgTextBreakLines breaks a text into multiple rows based on newlines and max row width - tweaked tessellation toleranceshared-context
@@ -808,12 +808,80 @@ void freeDemoData(struct NVGcontext* vg, struct DemoData* data) | |||||
nvgDeleteImage(vg, data->images[i]); | nvgDeleteImage(vg, data->images[i]); | ||||
} | } | ||||
void drawParagraph(struct NVGcontext* vg, float x, float y, float width, float height, float mx, float my) | |||||
{ | |||||
struct NVGtextRow rows[3]; | |||||
struct NVGglyphPosition glyphs[100]; | |||||
const char* text = "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party."; | |||||
const char* start; | |||||
const char* end; | |||||
int nrows, i, nglyphs, j; | |||||
float lineh; | |||||
float caretx, px; | |||||
nvgSave(vg); | |||||
nvgFontSize(vg, 18.0f); | |||||
nvgFontFace(vg, "sans"); | |||||
nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); | |||||
nvgTextMetrics(vg, NULL, NULL, &lineh); | |||||
/* nvgBeginPath(vg); | |||||
nvgFillColor(vg, nvgRGBA(255,255,0,128)); | |||||
nvgRect(vg, x, y, width, height); | |||||
nvgFill(vg);*/ | |||||
// The text break API can be used to fill a large buffer of rows, | |||||
// or to iterate over the text just few lines (or just one) at a time. | |||||
// The "next" variable of the last returned item tells where to continue. | |||||
start = text; | |||||
end = text + strlen(text); | |||||
while ((nrows = nvgTextBreakLines(vg, start, end, width, rows, 3))) { | |||||
for (i = 0; i < nrows; i++) { | |||||
struct NVGtextRow* row = &rows[i]; | |||||
int hit = mx > x && mx < (x+width) && my >= y && my < (y+lineh); | |||||
nvgBeginPath(vg); | |||||
nvgFillColor(vg, nvgRGBA(255,255,255,hit?64:8)); | |||||
nvgRect(vg, x, y, row->width, lineh); | |||||
nvgFill(vg); | |||||
nvgFillColor(vg, nvgRGBA(255,255,255,255)); | |||||
nvgText(vg, x, y, row->start, row->end); | |||||
if (hit) { | |||||
caretx = (mx < x+row->width/2) ? x : x+row->width; | |||||
px = x; | |||||
nglyphs = nvgTextGlyphPositions(vg, row->start, row->end, x, y, glyphs, 100); | |||||
for (j = 0; j < nglyphs; j++) { | |||||
float x0 = glyphs[j].x; | |||||
float x1 = (j+1 < nglyphs) ? glyphs[j+1].x : x+row->width; | |||||
float gx = (x0 + x1)/2; | |||||
if (mx > px && mx <= gx) | |||||
caretx = glyphs[j].x; | |||||
px = gx; | |||||
} | |||||
nvgBeginPath(vg); | |||||
nvgFillColor(vg, nvgRGBA(255,192,0,255)); | |||||
nvgRect(vg, caretx, y, 2, lineh); | |||||
nvgFill(vg); | |||||
} | |||||
y += lineh; | |||||
} | |||||
// Keep going... | |||||
start = rows[nrows-1].next; | |||||
} | |||||
nvgRestore(vg); | |||||
} | |||||
void renderDemo(struct NVGcontext* vg, float mx, float my, float width, float height, | void renderDemo(struct NVGcontext* vg, float mx, float my, float width, float height, | ||||
float t, int blowup, struct DemoData* data) | float t, int blowup, struct DemoData* data) | ||||
{ | { | ||||
float x,y,popy; | float x,y,popy; | ||||
drawEyes(vg, width - 250, 50, 150, 100, mx, my, t); | drawEyes(vg, width - 250, 50, 150, 100, mx, my, t); | ||||
drawParagraph(vg, width - 450, 50, 150, 100, mx, my); | |||||
drawGraph(vg, 0, height/2, width, height/2, t); | drawGraph(vg, 0, height/2, width, height/2, t); | ||||
drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t); | drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t); | ||||
@@ -13,6 +13,14 @@ solution "nanovg" | |||||
files { "src/*.c" } | files { "src/*.c" } | ||||
targetdir("build") | targetdir("build") | ||||
configuration "Debug" | |||||
defines { "DEBUG" } | |||||
flags { "Symbols", "ExtraWarnings"} | |||||
configuration "Release" | |||||
defines { "NDEBUG" } | |||||
flags { "Optimize", "ExtraWarnings"} | |||||
project "example_gl2" | project "example_gl2" | ||||
kind "ConsoleApp" | kind "ConsoleApp" | ||||
language "C" | language "C" | ||||
@@ -68,10 +68,12 @@ struct FONSquad | |||||
struct FONStextIter { | struct FONStextIter { | ||||
float x, y, scale, spacing; | float x, y, scale, spacing; | ||||
unsigned int codepoint; | |||||
short isize, iblur; | short isize, iblur; | ||||
struct FONSfont* font; | struct FONSfont* font; | ||||
struct FONSglyph* prevGlyph; | struct FONSglyph* prevGlyph; | ||||
const char* str; | const char* str; | ||||
const char* next; | |||||
const char* end; | const char* end; | ||||
unsigned int utf8state; | unsigned int utf8state; | ||||
}; | }; | ||||
@@ -1292,7 +1294,9 @@ int fonsTextIterInit(struct FONScontext* stash, struct FONStextIter* iter, | |||||
iter->y = y; | iter->y = y; | ||||
iter->spacing = state->spacing; | iter->spacing = state->spacing; | ||||
iter->str = str; | iter->str = str; | ||||
iter->next = str; | |||||
iter->end = end; | iter->end = end; | ||||
iter->codepoint = 0; | |||||
return 1; | return 1; | ||||
} | } | ||||
@@ -1300,24 +1304,24 @@ int fonsTextIterInit(struct FONScontext* stash, struct FONStextIter* iter, | |||||
int fonsTextIterNext(struct FONScontext* stash, struct FONStextIter* iter, struct FONSquad* quad) | int fonsTextIterNext(struct FONScontext* stash, struct FONStextIter* iter, struct FONSquad* quad) | ||||
{ | { | ||||
struct FONSglyph* glyph = NULL; | struct FONSglyph* glyph = NULL; | ||||
unsigned int codepoint = 0; | |||||
const char* str = iter->str; | |||||
const char* str = iter->next; | |||||
iter->str = iter->next; | |||||
if (str == iter->end) | if (str == iter->end) | ||||
return 0; | return 0; | ||||
for (; str != iter->end; str++) { | for (; str != iter->end; str++) { | ||||
if (fons__decutf8(&iter->utf8state, &codepoint, *(const unsigned char*)str)) | |||||
if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) | |||||
continue; | continue; | ||||
str++; | str++; | ||||
// Get glyph and quad | // Get glyph and quad | ||||
glyph = fons__getGlyph(stash, iter->font, codepoint, iter->isize, iter->iblur); | |||||
glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); | |||||
if (glyph != NULL) | if (glyph != NULL) | ||||
fons__getQuad(stash, iter->font, iter->prevGlyph, glyph, iter->scale, iter->spacing, &iter->x, &iter->y, quad); | fons__getQuad(stash, iter->font, iter->prevGlyph, glyph, iter->scale, iter->spacing, &iter->x, &iter->y, quad); | ||||
iter->prevGlyph = glyph; | iter->prevGlyph = glyph; | ||||
break; | break; | ||||
} | } | ||||
iter->str = str; | |||||
iter->next = str; | |||||
return 1; | return 1; | ||||
} | } | ||||
@@ -181,7 +181,7 @@ error: | |||||
static void nvg__setDevicePixelRatio(struct NVGcontext* ctx, float ratio) | static void nvg__setDevicePixelRatio(struct NVGcontext* ctx, float ratio) | ||||
{ | { | ||||
ctx->tessTol = 0.25f * 4.0f / ratio; | |||||
ctx->tessTol = 1.0f / ratio; | |||||
ctx->distTol = 0.01f / ratio; | ctx->distTol = 0.01f / ratio; | ||||
ctx->fringeWidth = 1.0f / ratio; | ctx->fringeWidth = 1.0f / ratio; | ||||
ctx->devicePxRatio = ratio; | ctx->devicePxRatio = ratio; | ||||
@@ -978,7 +978,6 @@ static void nvg__tesselateBezier(struct NVGcontext* ctx, | |||||
{ | { | ||||
float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; | float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; | ||||
float dx,dy,d2,d3; | float dx,dy,d2,d3; | ||||
float tol = 0.5f; | |||||
if (level > 10) return; | if (level > 10) return; | ||||
@@ -996,7 +995,7 @@ static void nvg__tesselateBezier(struct NVGcontext* ctx, | |||||
d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); | d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); | ||||
d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); | d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); | ||||
if ((d2 + d3)*(d2 + d3) < tol * (dx*dx + dy*dy)) { | |||||
if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) { | |||||
nvg__addPoint(ctx, x4, y4, type); | nvg__addPoint(ctx, x4, y4, type); | ||||
return; | return; | ||||
} | } | ||||
@@ -1985,6 +1984,205 @@ float nvgText(struct NVGcontext* ctx, float x, float y, const char* string, cons | |||||
return iter.x; | return iter.x; | ||||
} | } | ||||
int nvgTextGlyphPositions(struct NVGcontext* ctx, const char* string, const char* end, float x, float y, struct NVGglyphPosition* positions, int maxPositions) | |||||
{ | |||||
struct NVGstate* state = nvg__getState(ctx); | |||||
struct FONStextIter iter; | |||||
struct FONSquad q; | |||||
int npos = 0; | |||||
float px; | |||||
if (state->fontId == FONS_INVALID) return 0; | |||||
if (end == NULL) | |||||
end = string + strlen(string); | |||||
if (string == end) | |||||
return 0; | |||||
fonsSetSize(ctx->fs, state->fontSize); | |||||
fonsSetSpacing(ctx->fs, state->letterSpacing); | |||||
fonsSetBlur(ctx->fs, state->fontBlur); | |||||
fonsSetAlign(ctx->fs, state->textAlign); | |||||
fonsSetFont(ctx->fs, state->fontId); | |||||
px = x; | |||||
fonsTextIterInit(ctx->fs, &iter, x, y, string, end); | |||||
while (fonsTextIterNext(ctx->fs, &iter, &q)) { | |||||
positions[npos].str = iter.str; | |||||
positions[npos].x = px; | |||||
px = iter.x; | |||||
npos++; | |||||
if (npos >= maxPositions) | |||||
break; | |||||
} | |||||
return npos; | |||||
} | |||||
enum NVGcodepointType { | |||||
NVG_SPACE, | |||||
NVG_NEWLINE, | |||||
NVG_CHAR, | |||||
}; | |||||
int nvgTextBreakLines(struct NVGcontext* ctx, const char* string, const char* end, float maxRowWidth, struct NVGtextRow* rows, int maxRows) | |||||
{ | |||||
struct NVGstate* state = nvg__getState(ctx); | |||||
struct FONStextIter iter; | |||||
struct FONSquad q; | |||||
int nrows = 0; | |||||
float rowStartX = 0; | |||||
float rowWidth = 0; | |||||
const char* rowStart = NULL; | |||||
const char* rowEnd = NULL; | |||||
const char* wordStart = NULL; | |||||
float wordStartX = 0; | |||||
const char* breakEnd = NULL; | |||||
float breakWidth = 0; | |||||
int type = NVG_SPACE, ptype = NVG_SPACE; | |||||
unsigned int pcodepoint = 0; | |||||
if (maxRows == 0) return 0; | |||||
if (state->fontId == FONS_INVALID) return 0; | |||||
if (end == NULL) | |||||
end = string + strlen(string); | |||||
if (string == end) return 0; | |||||
fonsSetSize(ctx->fs, state->fontSize); | |||||
fonsSetSpacing(ctx->fs, state->letterSpacing); | |||||
fonsSetBlur(ctx->fs, state->fontBlur); | |||||
fonsSetAlign(ctx->fs, state->textAlign); | |||||
fonsSetFont(ctx->fs, state->fontId); | |||||
fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end); | |||||
while (fonsTextIterNext(ctx->fs, &iter, &q)) { | |||||
switch (iter.codepoint) { | |||||
case 9: // \t | |||||
case 11: // \v | |||||
case 12: // \f | |||||
case 32: // space | |||||
case 0x00a0: // NBSP | |||||
type = NVG_SPACE; | |||||
break; | |||||
case 10: // \n | |||||
type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE; | |||||
break; | |||||
case 13: // \r | |||||
type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE; | |||||
break; | |||||
case 0x0085: // NEL | |||||
type = NVG_NEWLINE; | |||||
break; | |||||
default: | |||||
type = NVG_CHAR; | |||||
break; | |||||
} | |||||
if (type == NVG_NEWLINE) { | |||||
// Always handle new lines. | |||||
rows[nrows].start = rowStart != NULL ? rowStart : iter.str; | |||||
rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str; | |||||
rows[nrows].width = rowWidth; | |||||
rows[nrows].next = iter.next; | |||||
nrows++; | |||||
if (nrows >= maxRows) | |||||
return nrows; | |||||
// Indicate to skip the white space at the beginning of the row. | |||||
rowStart = NULL; | |||||
rowEnd = NULL; | |||||
rowWidth = 0; | |||||
} else { | |||||
if (rowStart == NULL) { | |||||
// Skip white space until the beginning of the line | |||||
if (type == NVG_CHAR) { | |||||
// The current char is the row so far | |||||
rowStartX = q.x0; | |||||
rowStart = iter.str; | |||||
rowEnd = iter.next; | |||||
rowWidth = q.x1 - rowStartX; | |||||
wordStart = iter.str; | |||||
wordStartX = q.x0; | |||||
// Set null break point | |||||
breakEnd = rowStart; | |||||
breakWidth = 0.0; | |||||
} | |||||
} else { | |||||
float nextWidth = q.x1 - rowStartX; | |||||
if (nextWidth > maxRowWidth) { | |||||
// The run length is too long, need to break to new line. | |||||
if (breakEnd == rowStart) { | |||||
// The current word is longer than the row length, just break it from here. | |||||
rows[nrows].start = rowStart; | |||||
rows[nrows].end = iter.str; | |||||
rows[nrows].width = rowWidth; | |||||
rows[nrows].next = iter.str; | |||||
nrows++; | |||||
if (nrows >= maxRows) | |||||
return nrows; | |||||
rowStartX = q.x0; | |||||
rowStart = iter.str; | |||||
rowEnd = iter.next; | |||||
rowWidth = q.x1 - rowStartX; | |||||
wordStart = iter.str; | |||||
wordStartX = q.x0; | |||||
} else { | |||||
// Break the line from the end of the last word, and start new line from the begining of the new. | |||||
rows[nrows].start = rowStart; | |||||
rows[nrows].end = breakEnd; | |||||
rows[nrows].width = breakWidth; | |||||
rows[nrows].next = wordStart; | |||||
nrows++; | |||||
if (nrows >= maxRows) | |||||
return nrows; | |||||
rowStartX = wordStartX; | |||||
rowStart = wordStart; | |||||
rowEnd = iter.next; | |||||
rowWidth = q.x1 - rowStartX; | |||||
// No change to the word start | |||||
} | |||||
// Set null break point | |||||
breakEnd = rowStart; | |||||
breakWidth = 0.0; | |||||
} | |||||
// track last non-white space character | |||||
if (type == NVG_CHAR) { | |||||
rowEnd = iter.next; | |||||
rowWidth = q.x1 - rowStartX; | |||||
} | |||||
// track last end of a word | |||||
if (ptype == NVG_CHAR && (type == NVG_SPACE || type == NVG_SPACE)) { | |||||
breakEnd = iter.str; | |||||
breakWidth = rowWidth; | |||||
} | |||||
// track last beginning of a word | |||||
if ((ptype == NVG_SPACE || ptype == NVG_SPACE) && type == NVG_CHAR) { | |||||
wordStart = iter.str; | |||||
wordStartX = q.x0; | |||||
} | |||||
} | |||||
} | |||||
pcodepoint = iter.codepoint; | |||||
ptype = type; | |||||
} | |||||
// Break the line from the end of the last word, and start new line from the begining of the new. | |||||
if (rowStart != NULL) { | |||||
rows[nrows].start = rowStart; | |||||
rows[nrows].end = rowEnd; | |||||
rows[nrows].width = rowWidth; | |||||
rows[nrows].next = end; | |||||
nrows++; | |||||
} | |||||
return nrows; | |||||
} | |||||
float nvgTextBounds(struct NVGcontext* ctx, const char* string, const char* end, float* bounds) | float nvgTextBounds(struct NVGcontext* ctx, const char* string, const char* end, float* bounds) | ||||
{ | { | ||||
struct NVGstate* state = nvg__getState(ctx); | struct NVGstate* state = nvg__getState(ctx); | ||||
@@ -2000,7 +2198,7 @@ float nvgTextBounds(struct NVGcontext* ctx, const char* string, const char* end, | |||||
return fonsTextBounds(ctx->fs, string, end, bounds); | return fonsTextBounds(ctx->fs, string, end, bounds); | ||||
} | } | ||||
void nvgVertMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh) | |||||
void nvgTextMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh) | |||||
{ | { | ||||
struct NVGstate* state = nvg__getState(ctx); | struct NVGstate* state = nvg__getState(ctx); | ||||
@@ -419,8 +419,21 @@ float nvgTextBounds(struct NVGcontext* ctx, const char* string, const char* end, | |||||
// Returns the vertical metrics based on the current text style. | // Returns the vertical metrics based on the current text style. | ||||
// Current transform does not affect the measured values. | // Current transform does not affect the measured values. | ||||
void nvgVertMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh); | |||||
void nvgTextMetrics(struct NVGcontext* ctx, float* ascender, float* descender, float* lineh); | |||||
struct NVGglyphPosition { | |||||
const char* str; | |||||
float x; | |||||
}; | |||||
int nvgTextGlyphPositions(struct NVGcontext* ctx, const char* string, const char* end, float x, float y, struct NVGglyphPosition* positions, int maxPositions); | |||||
struct NVGtextRow { | |||||
const char* start; | |||||
const char* end; | |||||
const char* next; | |||||
float width; | |||||
}; | |||||
int nvgTextBreakLines(struct NVGcontext* ctx, const char* string, const char* end, float maxRowWidth, struct NVGtextRow* rows, int maxRows); | |||||
// | // | ||||
// Internal Render API | // Internal Render API | ||||