diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-22 21:40:36 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-22 21:40:36 +0300 |
commit | 366f4036453ee879d50820110275bf7ed69d6f8c (patch) | |
tree | 4b917f9336bb094c07fa13b7c6bc5cfbd434de36 /src | |
parent | baddad63d8c775d9d024ef8ee80b978dcf030af9 (diff) |
Text: Half-pixel glyph variants
The cache contains two variants of each glyph: zero and half-pixel offsets. This allows positioning glyph more accurately inside a text run.
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/documentwidget.c | 18 | ||||
-rw-r--r-- | src/ui/text.c | 76 |
2 files changed, 62 insertions, 32 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 55ac6b80..eae7c00a 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -172,9 +172,21 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
172 | if (ev->type == SDL_KEYDOWN) { | 172 | if (ev->type == SDL_KEYDOWN) { |
173 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 173 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
174 | const int key = ev->key.keysym.sym; | 174 | const int key = ev->key.keysym.sym; |
175 | if (mods == KMOD_PRIMARY && key == 'r') { | 175 | switch (key) { |
176 | fetch_DocumentWidget_(d); | 176 | case 'r': |
177 | return iTrue; | 177 | if (mods == KMOD_PRIMARY) { |
178 | fetch_DocumentWidget_(d); | ||
179 | return iTrue; | ||
180 | } | ||
181 | break; | ||
182 | case '0': { | ||
183 | extern int enableHalfPixelGlyphs_Text; | ||
184 | enableHalfPixelGlyphs_Text = !enableHalfPixelGlyphs_Text; | ||
185 | postRefresh_App(); | ||
186 | printf("halfpixel: %d\n", enableHalfPixelGlyphs_Text); | ||
187 | fflush(stdout); | ||
188 | break; | ||
189 | } | ||
178 | } | 190 | } |
179 | } | 191 | } |
180 | else if (ev->type == SDL_MOUSEWHEEL) { | 192 | else if (ev->type == SDL_MOUSEWHEEL) { |
diff --git a/src/ui/text.c b/src/ui/text.c index 6ed95dc0..e5045899 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -23,14 +23,16 @@ iDeclareTypeConstructionArgs(Glyph, iChar ch) | |||
23 | 23 | ||
24 | struct Impl_Glyph { | 24 | struct Impl_Glyph { |
25 | iHashNode node; | 25 | iHashNode node; |
26 | iRect rect; | 26 | iRect rect[2]; /* zero and half pixel offset */ |
27 | int advance; | 27 | int advance; |
28 | int dx, dy; | 28 | //int dx, dy; |
29 | iInt2 d[2]; | ||
29 | }; | 30 | }; |
30 | 31 | ||
31 | void init_Glyph(iGlyph *d, iChar ch) { | 32 | void init_Glyph(iGlyph *d, iChar ch) { |
32 | d->node.key = ch; | 33 | d->node.key = ch; |
33 | d->rect = zero_Rect(); | 34 | d->rect[0] = zero_Rect(); |
35 | d->rect[1] = zero_Rect(); | ||
34 | d->advance = 0; | 36 | d->advance = 0; |
35 | } | 37 | } |
36 | 38 | ||
@@ -141,9 +143,10 @@ void deinit_Text(void) { | |||
141 | d->render = NULL; | 143 | d->render = NULL; |
142 | } | 144 | } |
143 | 145 | ||
144 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, iChar ch) { | 146 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, iChar ch, float xShift) { |
145 | int w, h; | 147 | int w, h; |
146 | uint8_t *bmp = stbtt_GetCodepointBitmap(&d->font, d->scale, d->scale, ch, &w, &h, 0, 0); | 148 | uint8_t *bmp = stbtt_GetCodepointBitmapSubpixel( |
149 | &d->font, d->scale, d->scale, xShift, 0.0f, ch, &w, &h, 0, 0); | ||
147 | /* Note: `bmp` must be freed afterwards. */ | 150 | /* Note: `bmp` must be freed afterwards. */ |
148 | SDL_Surface *surface = | 151 | SDL_Surface *surface = |
149 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); | 152 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); |
@@ -204,33 +207,44 @@ static void fillTriangle_(SDL_Surface *surface, const SDL_Rect *rect, int dir) { | |||
204 | } | 207 | } |
205 | #endif | 208 | #endif |
206 | 209 | ||
207 | static void cache_Font_(iFont *d, iGlyph *glyph) { | 210 | static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { |
208 | iText *txt = &text_; | 211 | iText *txt = &text_; |
209 | SDL_Renderer *render = txt->render; | 212 | SDL_Renderer *render = txt->render; |
210 | SDL_Texture *tex = NULL; | 213 | SDL_Texture *tex = NULL; |
211 | SDL_Surface *surface = NULL; | 214 | SDL_Surface *surface = NULL; |
212 | const iChar ch = char_Glyph(glyph); | 215 | const iChar ch = char_Glyph(glyph); |
213 | iBool fromStb = iFalse; | 216 | iBool fromStb = iFalse; |
217 | iRect *glRect = &glyph->rect[hoff]; | ||
214 | if (!isSpecialChar_(ch)) { | 218 | if (!isSpecialChar_(ch)) { |
215 | /* Rasterize the glyph using stbtt. */ | 219 | /* Rasterize the glyph using stbtt. */ |
216 | surface = rasterizeGlyph_Font_(d, ch); | 220 | surface = rasterizeGlyph_Font_(d, ch, hoff * 0.5f); |
217 | int lsb; | 221 | if (hoff == 0) { |
218 | stbtt_GetCodepointHMetrics(&d->font, ch, &glyph->advance, &lsb); | 222 | int lsb; |
219 | stbtt_GetCodepointBitmapBox( | 223 | stbtt_GetCodepointHMetrics(&d->font, ch, &glyph->advance, &lsb); |
220 | &d->font, ch, d->scale, d->scale, &glyph->dx, &glyph->dy, NULL, NULL); | 224 | } |
225 | stbtt_GetCodepointBitmapBoxSubpixel(&d->font, | ||
226 | ch, | ||
227 | d->scale, | ||
228 | d->scale, | ||
229 | hoff * 0.5f, | ||
230 | 0.0f, | ||
231 | &glyph->d[hoff].x, | ||
232 | &glyph->d[hoff].y, | ||
233 | NULL, | ||
234 | NULL); | ||
221 | fromStb = iTrue; | 235 | fromStb = iTrue; |
222 | tex = SDL_CreateTextureFromSurface(render, surface); | 236 | tex = SDL_CreateTextureFromSurface(render, surface); |
223 | glyph->rect.size = init_I2(surface->w, surface->h); | 237 | glRect->size = init_I2(surface->w, surface->h); |
224 | } | 238 | } |
225 | else { | 239 | else { |
226 | /* Metrics for special symbols. */ | 240 | /* Metrics for special symbols. */ |
227 | int em, lsb; | 241 | int em, lsb; |
228 | const int symbol = specialChar_(ch); | 242 | const int symbol = specialChar_(ch); |
229 | stbtt_GetCodepointHMetrics(&d->font, 'M', &em, &lsb); | 243 | stbtt_GetCodepointHMetrics(&d->font, 'M', &em, &lsb); |
230 | glyph->dx = d->baseline / 10; | 244 | glyph->d[hoff].x = d->baseline / 10; |
231 | glyph->dy = -d->baseline; | 245 | glyph->d[hoff].y = -d->baseline; |
232 | glyph->advance = em * symbolAdvance_(symbol); | 246 | glyph->advance = em * symbolAdvance_(symbol); |
233 | glyph->rect.size = init_I2(symbolEmWidth_(symbol) * em * d->scale, d->height); | 247 | glyph->rect[hoff].size = init_I2(symbolEmWidth_(symbol) * em * d->scale, d->height); |
234 | #if 0 | 248 | #if 0 |
235 | if (isRasterizedSymbol_(ch)) { | 249 | if (isRasterizedSymbol_(ch)) { |
236 | /* Rasterize manually. */ | 250 | /* Rasterize manually. */ |
@@ -264,18 +278,19 @@ static void cache_Font_(iFont *d, iGlyph *glyph) { | |||
264 | #endif | 278 | #endif |
265 | } | 279 | } |
266 | /* Determine placement in the glyph cache texture, advancing in rows. */ | 280 | /* Determine placement in the glyph cache texture, advancing in rows. */ |
267 | if (txt->cachePos.x + glyph->rect.size.x > txt->cacheSize.x) { | 281 | if (txt->cachePos.x + glRect->size.x > txt->cacheSize.x) { |
268 | txt->cachePos.x = 0; | 282 | txt->cachePos.x = 0; |
269 | txt->cachePos.y += txt->cacheRowHeight; | 283 | txt->cachePos.y += txt->cacheRowHeight; |
270 | txt->cacheRowHeight = 0; | 284 | txt->cacheRowHeight = 0; |
271 | } | 285 | } |
272 | glyph->rect.pos = txt->cachePos; | 286 | glRect->pos = txt->cachePos; |
273 | SDL_SetRenderTarget(render, txt->cache); | 287 | SDL_SetRenderTarget(render, txt->cache); |
274 | const SDL_Rect dstRect = sdlRect_(glyph->rect); | 288 | const SDL_Rect dstRect = sdlRect_(*glRect); |
275 | if (surface) { | 289 | if (surface) { |
276 | SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); | 290 | SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); |
277 | } | 291 | } |
278 | else { | 292 | else { |
293 | #if 0 | ||
279 | /* Draw a special symbol. */ | 294 | /* Draw a special symbol. */ |
280 | SDL_SetRenderDrawColor(render, 255, 255, 255, 255); | 295 | SDL_SetRenderDrawColor(render, 255, 255, 255, 255); |
281 | const iInt2 tl = init_I2(dstRect.x, dstRect.y); | 296 | const iInt2 tl = init_I2(dstRect.x, dstRect.y); |
@@ -283,7 +298,6 @@ static void cache_Font_(iFont *d, iGlyph *glyph) { | |||
283 | const int midX = tl.x + dstRect.w / 2; | 298 | const int midX = tl.x + dstRect.w / 2; |
284 | const int midY = tl.y + dstRect.h / 2; | 299 | const int midY = tl.y + dstRect.h / 2; |
285 | const int symH = dstRect.h * 2 / 6; | 300 | const int symH = dstRect.h * 2 / 6; |
286 | #if 0 | ||
287 | /* Frame. */ | 301 | /* Frame. */ |
288 | if (isFramedSymbol_(ch)) { | 302 | if (isFramedSymbol_(ch)) { |
289 | SDL_RenderDrawLines( | 303 | SDL_RenderDrawLines( |
@@ -350,8 +364,8 @@ static void cache_Font_(iFont *d, iGlyph *glyph) { | |||
350 | SDL_FreeSurface(surface); | 364 | SDL_FreeSurface(surface); |
351 | } | 365 | } |
352 | /* Update cache cursor. */ | 366 | /* Update cache cursor. */ |
353 | txt->cachePos.x += glyph->rect.size.x; | 367 | txt->cachePos.x += glRect->size.x; |
354 | txt->cacheRowHeight = iMax(txt->cacheRowHeight, glyph->rect.size.y); | 368 | txt->cacheRowHeight = iMax(txt->cacheRowHeight, glRect->size.y); |
355 | } | 369 | } |
356 | 370 | ||
357 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | 371 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { |
@@ -360,7 +374,8 @@ static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | |||
360 | return node; | 374 | return node; |
361 | } | 375 | } |
362 | iGlyph *glyph = new_Glyph(ch); | 376 | iGlyph *glyph = new_Glyph(ch); |
363 | cache_Font_(d, glyph); | 377 | cache_Font_(d, glyph, 0); |
378 | cache_Font_(d, glyph, 1); /* half-pixel offset */ | ||
364 | insert_Hash(&d->glyphs, &glyph->node); | 379 | insert_Hash(&d->glyphs, &glyph->node); |
365 | return glyph; | 380 | return glyph; |
366 | } | 381 | } |
@@ -381,6 +396,8 @@ static iChar nextChar_(const char **chPos, const char *end) { | |||
381 | return ch; | 396 | return ch; |
382 | } | 397 | } |
383 | 398 | ||
399 | int enableHalfPixelGlyphs_Text = iTrue; | ||
400 | |||
384 | static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, | 401 | static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLen, iInt2 pos, |
385 | int xposLimit, const char **continueFrom_out, int *runAdvance_out) { | 402 | int xposLimit, const char **continueFrom_out, int *runAdvance_out) { |
386 | iInt2 size = zero_I2(); | 403 | iInt2 size = zero_I2(); |
@@ -415,8 +432,9 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
415 | } | 432 | } |
416 | } | 433 | } |
417 | const iGlyph *glyph = glyph_Font_(d, ch); | 434 | const iGlyph *glyph = glyph_Font_(d, ch); |
418 | float x1 = xpos; | 435 | int x1 = xpos; |
419 | float x2 = x1 + glyph->rect.size.x; | 436 | const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; |
437 | int x2 = x1 + glyph->rect[hoff].size.x; | ||
420 | if (xposLimit > 0 && x2 > xposLimit) { | 438 | if (xposLimit > 0 && x2 > xposLimit) { |
421 | /* Out of space. */ | 439 | /* Out of space. */ |
422 | *continueFrom_out = lastWordEnd; | 440 | *continueFrom_out = lastWordEnd; |
@@ -425,11 +443,11 @@ static iInt2 run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
425 | size.x = iMax(size.x, x2 - orig.x); | 443 | size.x = iMax(size.x, x2 - orig.x); |
426 | size.y = iMax(size.y, pos.y + d->height - orig.y); | 444 | size.y = iMax(size.y, pos.y + d->height - orig.y); |
427 | if (mode != measure_RunMode) { | 445 | if (mode != measure_RunMode) { |
428 | SDL_FRect dst = { x1 + glyph->dx, | 446 | SDL_Rect dst = { x1 + glyph->d[hoff].x, |
429 | pos.y + d->baseline + glyph->dy, | 447 | pos.y + d->baseline + glyph->d[hoff].y, |
430 | glyph->rect.size.x, | 448 | glyph->rect[hoff].size.x, |
431 | glyph->rect.size.y }; | 449 | glyph->rect[hoff].size.y }; |
432 | SDL_RenderCopyF(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect, &dst); | 450 | SDL_RenderCopy(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect[hoff], &dst); |
433 | } | 451 | } |
434 | xpos += d->scale * glyph->advance; | 452 | xpos += d->scale * glyph->advance; |
435 | xposMax = iMax(xposMax, xpos); | 453 | xposMax = iMax(xposMax, xpos); |