Browse Source

Added text break line and glyph position functions

- 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 tolerance
shared-context
Mikko Mononen 11 years ago
parent
commit
ff8b521506
5 changed files with 301 additions and 10 deletions
  1. +68
    -0
      example/demo.c
  2. +8
    -0
      premake4.lua
  3. +9
    -5
      src/fontstash.h
  4. +202
    -4
      src/nanovg.c
  5. +14
    -1
      src/nanovg.h

+ 68
- 0
example/demo.c View File

@@ -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);




+ 8
- 0
premake4.lua View File

@@ -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"


+ 9
- 5
src/fontstash.h View File

@@ -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;
} }


+ 202
- 4
src/nanovg.c View File

@@ -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);




+ 14
- 1
src/nanovg.h View File

@@ -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


Loading…
Cancel
Save