diff options
-rw-r--r-- | src/ui/text.c | 127 |
1 files changed, 82 insertions, 45 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index cf974366..218e7565 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -52,8 +52,16 @@ int gap_Text; /* cf. gap_UI in metrics.h */ | |||
52 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ | 52 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ |
53 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ | 53 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ |
54 | 54 | ||
55 | static iBool enableRaster_Text_ = iTrue; | ||
56 | |||
57 | enum iGlyphFlag { | ||
58 | rasterized0_GlyphFlag = iBit(1), /* zero offset */ | ||
59 | rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ | ||
60 | }; | ||
61 | |||
55 | struct Impl_Glyph { | 62 | struct Impl_Glyph { |
56 | iHashNode node; | 63 | iHashNode node; |
64 | int flags; | ||
57 | uint32_t glyphIndex; | 65 | uint32_t glyphIndex; |
58 | const iFont *font; /* may come from symbols/emoji */ | 66 | const iFont *font; /* may come from symbols/emoji */ |
59 | iRect rect[2]; /* zero and half pixel offset */ | 67 | iRect rect[2]; /* zero and half pixel offset */ |
@@ -63,6 +71,7 @@ struct Impl_Glyph { | |||
63 | 71 | ||
64 | void init_Glyph(iGlyph *d, iChar ch) { | 72 | void init_Glyph(iGlyph *d, iChar ch) { |
65 | d->node.key = ch; | 73 | d->node.key = ch; |
74 | d->flags = 0; | ||
66 | d->glyphIndex = 0; | 75 | d->glyphIndex = 0; |
67 | d->font = NULL; | 76 | d->font = NULL; |
68 | d->rect[0] = zero_Rect(); | 77 | d->rect[0] = zero_Rect(); |
@@ -74,10 +83,23 @@ void deinit_Glyph(iGlyph *d) { | |||
74 | iUnused(d); | 83 | iUnused(d); |
75 | } | 84 | } |
76 | 85 | ||
77 | iChar codepoint_Glyph(const iGlyph *d) { | 86 | static iChar codepoint_Glyph_(const iGlyph *d) { |
78 | return d->node.key; | 87 | return d->node.key; |
79 | } | 88 | } |
80 | 89 | ||
90 | iLocalDef iBool isRasterized_Glyph_(const iGlyph *d, int hoff) { | ||
91 | return (d->flags & (rasterized0_GlyphFlag << hoff)) != 0; | ||
92 | } | ||
93 | |||
94 | iLocalDef iBool isFullyRasterized_Glyph_(const iGlyph *d) { | ||
95 | return (d->flags & (rasterized0_GlyphFlag | rasterized1_GlyphFlag)) == | ||
96 | (rasterized0_GlyphFlag | rasterized1_GlyphFlag); | ||
97 | } | ||
98 | |||
99 | iLocalDef void setRasterized_Glyph_(iGlyph *d, int hoff) { | ||
100 | d->flags |= rasterized0_GlyphFlag << hoff; | ||
101 | } | ||
102 | |||
81 | iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) | 103 | iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) |
82 | 104 | ||
83 | /*-----------------------------------------------------------------------------------------------*/ | 105 | /*-----------------------------------------------------------------------------------------------*/ |
@@ -496,41 +518,41 @@ static iInt2 assignCachePos_Text_(iText *d, iInt2 size) { | |||
496 | return assigned; | 518 | return assigned; |
497 | } | 519 | } |
498 | 520 | ||
499 | static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | 521 | static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { |
500 | iText *txt = &text_; | ||
501 | SDL_Renderer *render = txt->render; | ||
502 | SDL_Texture *tex = NULL; | ||
503 | SDL_Surface *surface = NULL; | ||
504 | iRect *glRect = &glyph->rect[hoff]; | 522 | iRect *glRect = &glyph->rect[hoff]; |
505 | /* Rasterize the glyph using stbtt. */ { | 523 | int x0, y0, x1, y1; |
506 | surface = rasterizeGlyph_Font_(d, glyph->glyphIndex, hoff * 0.5f); | 524 | stbtt_GetGlyphBitmapBoxSubpixel( |
507 | if (hoff == 0) { /* hoff==1 uses same `glyph` */ | 525 | &d->font, glyph->glyphIndex, d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); |
508 | int adv; | 526 | glRect->size = init_I2(x1 - x0, y1 - y0); |
509 | const uint32_t gIndex = glyph->glyphIndex; | 527 | /* Determine placement in the glyph cache texture, advancing in rows. */ |
510 | stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL); | 528 | glRect->pos = assignCachePos_Text_(&text_, glRect->size); |
511 | glyph->advance = d->xScale * adv; | 529 | glyph->d[hoff] = init_I2(x0, y0); |
512 | } | 530 | glyph->d[hoff].y += d->vertOffset; |
513 | stbtt_GetGlyphBitmapBoxSubpixel(&d->font, | 531 | if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ |
514 | glyph->glyphIndex, | 532 | int adv; |
515 | d->xScale, | 533 | const uint32_t gIndex = glyph->glyphIndex; |
516 | d->yScale, | 534 | stbtt_GetGlyphHMetrics(&d->font, gIndex, &adv, NULL); |
517 | hoff * 0.5f, | 535 | glyph->advance = d->xScale * adv; |
518 | 0.0f, | ||
519 | &glyph->d[hoff].x, | ||
520 | &glyph->d[hoff].y, | ||
521 | NULL, | ||
522 | NULL); | ||
523 | glyph->d[hoff].y += d->vertOffset; | ||
524 | tex = SDL_CreateTextureFromSurface(render, surface); | ||
525 | SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE); | ||
526 | glRect->size = init_I2(surface->w, surface->h); | ||
527 | } | 536 | } |
537 | } | ||
538 | |||
539 | static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) { | ||
540 | iText * txt = &text_; | ||
541 | SDL_Renderer *render = txt->render; | ||
542 | SDL_Texture * tex = NULL; | ||
543 | SDL_Surface * surface = NULL; | ||
544 | iRect * glRect = &glyph->rect[hoff]; | ||
545 | /* Rasterize the glyph using stbtt. */ | ||
546 | iAssert(!isRasterized_Glyph_(glyph, hoff)); | ||
547 | surface = rasterizeGlyph_Font_(d, glyph->glyphIndex, hoff * 0.5f); | ||
548 | tex = SDL_CreateTextureFromSurface(render, surface); | ||
549 | iAssert(isEqual_I2(glRect->size, init_I2(surface->w, surface->h))); | ||
528 | if (tex) { | 550 | if (tex) { |
529 | /* Determine placement in the glyph cache texture, advancing in rows. */ | 551 | SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE); |
530 | glRect->pos = assignCachePos_Text_(txt, glRect->size); | ||
531 | const SDL_Rect dstRect = sdlRect_(*glRect); | 552 | const SDL_Rect dstRect = sdlRect_(*glRect); |
532 | SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); | 553 | SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); |
533 | SDL_DestroyTexture(tex); | 554 | SDL_DestroyTexture(tex); |
555 | setRasterized_Glyph_(glyph, hoff); | ||
534 | } | 556 | } |
535 | if (surface) { | 557 | if (surface) { |
536 | SDL_FreeSurface(surface); | 558 | SDL_FreeSurface(surface); |
@@ -580,29 +602,42 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | |||
580 | } | 602 | } |
581 | 603 | ||
582 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | 604 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { |
605 | iGlyph * glyph; | ||
583 | uint32_t glyphIndex = 0; | 606 | uint32_t glyphIndex = 0; |
584 | /* The glyph may actually come from a different font; look up the right font. */ | 607 | /* The glyph may actually come from a different font; look up the right font. */ |
585 | iFont *font = characterFont_Font_(d, ch, &glyphIndex); | 608 | iFont *font = characterFont_Font_(d, ch, &glyphIndex); |
586 | const void *node = value_Hash(&font->glyphs, ch); | 609 | void * node = value_Hash(&font->glyphs, ch); |
587 | if (node) { | 610 | if (node) { |
588 | return node; | 611 | glyph = node; |
589 | } | 612 | } |
590 | iGlyph *glyph = new_Glyph(ch); | 613 | else { |
591 | glyph->glyphIndex = glyphIndex; | 614 | /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ |
592 | glyph->font = font; | 615 | if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { |
593 | /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ | ||
594 | if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { | ||
595 | #if !defined (NDEBUG) | 616 | #if !defined (NDEBUG) |
596 | printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); | 617 | printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); |
597 | #endif | 618 | #endif |
598 | resetCache_Text_(&text_); | 619 | resetCache_Text_(&text_); |
620 | } | ||
621 | glyph = new_Glyph(ch); | ||
622 | glyph->glyphIndex = glyphIndex; | ||
623 | glyph->font = font; | ||
624 | /* New glyphs are always allocated at least. This reserves a position in the cache | ||
625 | and updates the glyph metrics. */ | ||
626 | allocate_Font_(font, glyph, 0); | ||
627 | allocate_Font_(font, glyph, 1); | ||
628 | insert_Hash(&font->glyphs, &glyph->node); | ||
629 | } | ||
630 | if (enableRaster_Text_ && !isFullyRasterized_Glyph_(glyph)) { | ||
631 | SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); | ||
632 | SDL_SetRenderTarget(text_.render, text_.cache); | ||
633 | if (!isRasterized_Glyph_(glyph, 0)) { | ||
634 | cache_Font_(font, glyph, 0); | ||
635 | } | ||
636 | if (!isRasterized_Glyph_(glyph, 1)) { | ||
637 | cache_Font_(font, glyph, 1); /* half-pixel offset */ | ||
638 | } | ||
639 | SDL_SetRenderTarget(text_.render, oldTarget); | ||
599 | } | 640 | } |
600 | SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); | ||
601 | SDL_SetRenderTarget(text_.render, text_.cache); | ||
602 | cache_Font_(font, glyph, 0); | ||
603 | cache_Font_(font, glyph, 1); /* half-pixel offset */ | ||
604 | SDL_SetRenderTarget(text_.render, oldTarget); | ||
605 | insert_Hash(&font->glyphs, &glyph->node); | ||
606 | return glyph; | 641 | return glyph; |
607 | } | 642 | } |
608 | 643 | ||
@@ -693,6 +728,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
693 | if (isMonospaced) { | 728 | if (isMonospaced) { |
694 | monoAdvance = glyph_Font_(d, 'M')->advance; | 729 | monoAdvance = glyph_Font_(d, 'M')->advance; |
695 | } | 730 | } |
731 | /* Global flag that allows glyph rasterization. */ | ||
732 | enableRaster_Text_ = !isMeasuring_(mode); | ||
696 | for (const char *chPos = args->text.start; chPos != args->text.end; ) { | 733 | for (const char *chPos = args->text.start; chPos != args->text.end; ) { |
697 | iAssert(chPos < args->text.end); | 734 | iAssert(chPos < args->text.end); |
698 | const char *currentPos = chPos; | 735 | const char *currentPos = chPos; |