diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-07 20:44:46 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-12-07 20:44:46 +0200 |
commit | 73c96402a21872dba8999caba8f4374a91f0d8e4 (patch) | |
tree | ed314087ba441592801810628ac62814082b4b5f /src/ui | |
parent | c5234b742900ca9cde25e584cb2da7b97d3df9a3 (diff) |
Text: Align monospace glyph sizing to pixel grid
This is a better solution than a magic 0.25 pixel offset.
To eliminate partially occupied glyph edge pixels, scale the glyphs slightly so they align with full pixels. Scaling is only done horizontally so it doesn't affect line height.
IssueID #86
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/text.c | 49 |
1 files changed, 29 insertions, 20 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 1761a87c..a8be33b6 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -85,7 +85,7 @@ iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) | |||
85 | struct Impl_Font { | 85 | struct Impl_Font { |
86 | iBlock * data; | 86 | iBlock * data; |
87 | stbtt_fontinfo font; | 87 | stbtt_fontinfo font; |
88 | float scale; | 88 | float xScale, yScale; |
89 | int vertOffset; /* offset due to scaling */ | 89 | int vertOffset; /* offset due to scaling */ |
90 | int height; | 90 | int height; |
91 | int baseline; | 91 | int baseline; |
@@ -100,21 +100,33 @@ struct Impl_Font { | |||
100 | 100 | ||
101 | static iFont *font_Text_(enum iFontId id); | 101 | static iFont *font_Text_(enum iFontId id); |
102 | 102 | ||
103 | static void init_Font(iFont *d, const iBlock *data, int height, float scale, enum iFontId symbolsFont) { | 103 | static void init_Font(iFont *d, const iBlock *data, int height, float scale, |
104 | enum iFontId symbolsFont, iBool isMonospaced) { | ||
104 | init_Hash(&d->glyphs); | 105 | init_Hash(&d->glyphs); |
105 | d->data = NULL; | 106 | d->data = NULL; |
107 | d->isMonospaced = isMonospaced; | ||
106 | d->height = height; | 108 | d->height = height; |
107 | iZap(d->font); | 109 | iZap(d->font); |
108 | stbtt_InitFont(&d->font, constData_Block(data), 0); | 110 | stbtt_InitFont(&d->font, constData_Block(data), 0); |
109 | d->scale = stbtt_ScaleForPixelHeight(&d->font, height) * scale; | 111 | d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale; |
112 | if (d->isMonospaced) { | ||
113 | /* It is important that monospaced fonts align 1:1 with the pixel grid so that | ||
114 | box-drawing characters don't have partially occupied edge pixels, leading to seams | ||
115 | between adjacent glyphs. */ | ||
116 | int adv; | ||
117 | stbtt_GetCodepointHMetrics(&d->font, 'M', &adv, NULL); | ||
118 | const float advance = (float) adv * d->xScale; | ||
119 | if (advance > 4) { /* not too tiny */ | ||
120 | d->xScale *= floorf(advance) / advance; | ||
121 | } | ||
122 | } | ||
110 | d->vertOffset = height * (1.0f - scale) / 2; | 123 | d->vertOffset = height * (1.0f - scale) / 2; |
111 | int ascent; | 124 | int ascent; |
112 | stbtt_GetFontVMetrics(&d->font, &ascent, NULL, NULL); | 125 | stbtt_GetFontVMetrics(&d->font, &ascent, NULL, NULL); |
113 | d->baseline = ceil(ascent * d->scale); | 126 | d->baseline = ceil(ascent * d->yScale); |
114 | d->symbolsFont = symbolsFont; | 127 | d->symbolsFont = symbolsFont; |
115 | d->japaneseFont = regularJapanese_FontId; | 128 | d->japaneseFont = regularJapanese_FontId; |
116 | d->koreanFont = regularKorean_FontId; | 129 | d->koreanFont = regularKorean_FontId; |
117 | d->isMonospaced = iFalse; | ||
118 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); | 130 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); |
119 | } | 131 | } |
120 | 132 | ||
@@ -271,11 +283,12 @@ static void initFonts_Text_(iText *d) { | |||
271 | }; | 283 | }; |
272 | iForIndices(i, fontData) { | 284 | iForIndices(i, fontData) { |
273 | iFont *font = &d->fonts[i]; | 285 | iFont *font = &d->fonts[i]; |
274 | init_Font( | 286 | init_Font(font, |
275 | font, fontData[i].ttf, fontData[i].size, fontData[i].scaling, fontData[i].symbolsFont); | 287 | fontData[i].ttf, |
276 | if (fontData[i].ttf == &fontFiraMonoRegular_Embedded) { | 288 | fontData[i].size, |
277 | font->isMonospaced = iTrue; | 289 | fontData[i].scaling, |
278 | } | 290 | fontData[i].symbolsFont, |
291 | fontData[i].ttf == &fontFiraMonoRegular_Embedded); | ||
279 | if (i == default_FontId || i == defaultMedium_FontId) { | 292 | if (i == default_FontId || i == defaultMedium_FontId) { |
280 | font->manualKernOnly = iTrue; | 293 | font->manualKernOnly = iTrue; |
281 | } | 294 | } |
@@ -423,7 +436,7 @@ static void freeBmp_(void *ptr) { | |||
423 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { | 436 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { |
424 | int w, h; | 437 | int w, h; |
425 | uint8_t *bmp = stbtt_GetGlyphBitmapSubpixel( | 438 | uint8_t *bmp = stbtt_GetGlyphBitmapSubpixel( |
426 | &d->font, d->scale, d->scale, xShift, 0.0f, glyphIndex, &w, &h, 0, 0); | 439 | &d->font, d->xScale, d->yScale, xShift, 0.0f, glyphIndex, &w, &h, 0, 0); |
427 | collect_Garbage(bmp, freeBmp_); /* `bmp` must be freed afterwards. */ | 440 | collect_Garbage(bmp, freeBmp_); /* `bmp` must be freed afterwards. */ |
428 | SDL_Surface *surface8 = | 441 | SDL_Surface *surface8 = |
429 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); | 442 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); |
@@ -478,12 +491,12 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
478 | int adv; | 491 | int adv; |
479 | const uint32_t gIndex = glyph->glyphIndex; | 492 | const uint32_t gIndex = glyph->glyphIndex; |
480 | stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL); | 493 | stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL); |
481 | glyph->advance = d->scale * adv; | 494 | glyph->advance = d->xScale * adv; |
482 | } | 495 | } |
483 | stbtt_GetGlyphBitmapBoxSubpixel(&d->font, | 496 | stbtt_GetGlyphBitmapBoxSubpixel(&d->font, |
484 | glyph->glyphIndex, | 497 | glyph->glyphIndex, |
485 | d->scale, | 498 | d->xScale, |
486 | d->scale, | 499 | d->yScale, |
487 | hoff * 0.5f, | 500 | hoff * 0.5f, |
488 | 0.0f, | 501 | 0.0f, |
489 | &glyph->d[hoff].x, | 502 | &glyph->d[hoff].x, |
@@ -734,11 +747,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
734 | } | 747 | } |
735 | const iBool useMonoAdvance = | 748 | const iBool useMonoAdvance = |
736 | monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font)); | 749 | monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font)); |
737 | /* The -0.25f is to mitigate issues with box-drawing characters sometimes rounding | 750 | const float advance = (useMonoAdvance ? monoAdvance : glyph->advance); |
738 | up to leave a hairline gap with the previous character. The purpose is to overlap | ||
739 | the glyphs slightly, since they are rendered antialiased and unhinted, which means | ||
740 | the right edge pixels may end up partially non-opaque. */ | ||
741 | const float advance = (useMonoAdvance ? monoAdvance - 0.25f : glyph->advance); | ||
742 | if (!isMeasuring_(mode)) { | 751 | if (!isMeasuring_(mode)) { |
743 | if (useMonoAdvance && dst.w > advance && glyph->font != d) { | 752 | if (useMonoAdvance && dst.w > advance && glyph->font != d) { |
744 | /* Glyphs from a different font may need recentering to look better. */ | 753 | /* Glyphs from a different font may need recentering to look better. */ |
@@ -769,7 +778,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
769 | const char *peek = chPos; | 778 | const char *peek = chPos; |
770 | const iChar next = nextChar_(&peek, text.end); | 779 | const iChar next = nextChar_(&peek, text.end); |
771 | if (enableKerning_Text && !d->manualKernOnly && next) { | 780 | if (enableKerning_Text && !d->manualKernOnly && next) { |
772 | xpos += d->scale * stbtt_GetGlyphKernAdvance(&d->font, glyph->glyphIndex, next); | 781 | xpos += d->xScale * stbtt_GetGlyphKernAdvance(&d->font, glyph->glyphIndex, next); |
773 | } | 782 | } |
774 | } | 783 | } |
775 | #endif | 784 | #endif |