summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-12-07 20:44:46 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-12-07 20:44:46 +0200
commit73c96402a21872dba8999caba8f4374a91f0d8e4 (patch)
treeed314087ba441592801810628ac62814082b4b5f /src/ui/text.c
parentc5234b742900ca9cde25e584cb2da7b97d3df9a3 (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/text.c')
-rw-r--r--src/ui/text.c49
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)
85struct Impl_Font { 85struct 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
101static iFont *font_Text_(enum iFontId id); 101static iFont *font_Text_(enum iFontId id);
102 102
103static void init_Font(iFont *d, const iBlock *data, int height, float scale, enum iFontId symbolsFont) { 103static 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) {
423static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { 436static 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