diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-13 19:59:41 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-13 19:59:41 +0200 |
commit | f18c5e9a259c88f7bc3bbc9cf8afcf805ba00a60 (patch) | |
tree | e7838b803cad78eb1b0c5c0d15b4f6d639b0d400 | |
parent | 379f8cea2044854be02622a561dd3c9cd9fb06c2 (diff) |
Text: Separate glyph caching procedure
Previously glyph caching was done during text rendering, but that would mean lots of swapping between rendering stuff to the cache and then immediately afterward rendering to the display. There would be a swap per each new glyph.
Now the entire document's glyphs are precached at once when a request is finished. Glyphs are also cached in larger batches when new text needs to be drawn.
-rw-r--r-- | src/app.c | 4 | ||||
-rw-r--r-- | src/app.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 13 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 10 | ||||
-rw-r--r-- | src/ui/text.c | 157 | ||||
-rw-r--r-- | src/ui/text.h | 2 |
6 files changed, 167 insertions, 20 deletions
@@ -1050,6 +1050,10 @@ iBool isRefreshPending_App(void) { | |||
1050 | return value_Atomic(&app_.pendingRefresh); | 1050 | return value_Atomic(&app_.pendingRefresh); |
1051 | } | 1051 | } |
1052 | 1052 | ||
1053 | iBool isFinishedLaunching_App(void) { | ||
1054 | return app_.isFinishedLaunching; | ||
1055 | } | ||
1056 | |||
1053 | uint32_t elapsedSinceLastTicker_App(void) { | 1057 | uint32_t elapsedSinceLastTicker_App(void) { |
1054 | return app_.elapsedSinceLastTicker; | 1058 | return app_.elapsedSinceLastTicker; |
1055 | } | 1059 | } |
@@ -68,6 +68,7 @@ void processEvents_App (enum iAppEventMode mode); | |||
68 | iBool handleCommand_App (const char *cmd); | 68 | iBool handleCommand_App (const char *cmd); |
69 | void refresh_App (void); | 69 | void refresh_App (void); |
70 | iBool isRefreshPending_App (void); | 70 | iBool isRefreshPending_App (void); |
71 | iBool isFinishedLaunching_App (void); | ||
71 | uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ | 72 | uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ |
72 | 73 | ||
73 | iBool isLandscape_App (void); | 74 | iBool isLandscape_App (void); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index a7666865..9111b546 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1004,6 +1004,17 @@ static void parseUser_DocumentWidget_(iDocumentWidget *d) { | |||
1004 | setRange_String(d->titleUser, urlUser_String(d->mod.url)); | 1004 | setRange_String(d->titleUser, urlUser_String(d->mod.url)); |
1005 | } | 1005 | } |
1006 | 1006 | ||
1007 | static void cacheRunGlyphs_(void *data, const iGmRun *run) { | ||
1008 | iUnused(data); | ||
1009 | if (!isEmpty_Range(&run->text)) { | ||
1010 | cache_Text(run->font, run->text); | ||
1011 | } | ||
1012 | } | ||
1013 | |||
1014 | static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | ||
1015 | render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, cacheRunGlyphs_, NULL); | ||
1016 | } | ||
1017 | |||
1007 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 1018 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { |
1008 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); | 1019 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); |
1009 | if (recent && recent->cachedResponse) { | 1020 | if (recent && recent->cachedResponse) { |
@@ -1026,6 +1037,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
1026 | updateSideOpacity_DocumentWidget_(d, iFalse); | 1037 | updateSideOpacity_DocumentWidget_(d, iFalse); |
1027 | updateSideIconBuf_DocumentWidget_(d); | 1038 | updateSideIconBuf_DocumentWidget_(d); |
1028 | updateVisible_DocumentWidget_(d); | 1039 | updateVisible_DocumentWidget_(d); |
1040 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1029 | postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | 1041 | postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); |
1030 | return iTrue; | 1042 | return iTrue; |
1031 | } | 1043 | } |
@@ -1762,6 +1774,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1762 | scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); | 1774 | scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); |
1763 | clear_String(&d->pendingGotoHeading); | 1775 | clear_String(&d->pendingGotoHeading); |
1764 | } | 1776 | } |
1777 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1765 | return iFalse; | 1778 | return iFalse; |
1766 | } | 1779 | } |
1767 | else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) { | 1780 | else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) { |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 27f0217c..06e6373e 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -256,10 +256,12 @@ static iString *visText_InputWidget_(const iInputWidget *d) { | |||
256 | } | 256 | } |
257 | 257 | ||
258 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 258 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
259 | invalidateBuffered_InputWidget_(d); | 259 | if (isFinishedLaunching_App()) { |
260 | iString *visText = visText_InputWidget_(d); | 260 | invalidateBuffered_InputWidget_(d); |
261 | d->buffered = new_TextBuf(d->font, cstr_String(visText)); | 261 | iString *visText = visText_InputWidget_(d); |
262 | delete_String(visText); | 262 | d->buffered = new_TextBuf(d->font, cstr_String(visText)); |
263 | delete_String(visText); | ||
264 | } | ||
263 | } | 265 | } |
264 | 266 | ||
265 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 267 | void setText_InputWidget(iInputWidget *d, const iString *text) { |
diff --git a/src/ui/text.c b/src/ui/text.c index 36e98e11..4a39bf72 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -53,8 +53,6 @@ int gap_Text; /* cf. gap_UI in metrics.h */ | |||
53 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ | 53 | int enableHalfPixelGlyphs_Text = iTrue; /* debug setting */ |
54 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ | 54 | int enableKerning_Text = iTrue; /* looking up kern pairs is slow */ |
55 | 55 | ||
56 | static iBool enableRaster_Text_ = iTrue; | ||
57 | |||
58 | enum iGlyphFlag { | 56 | enum iGlyphFlag { |
59 | rasterized0_GlyphFlag = iBit(1), /* zero offset */ | 57 | rasterized0_GlyphFlag = iBit(1), /* zero offset */ |
60 | rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ | 58 | rasterized1_GlyphFlag = iBit(2), /* half-pixel offset */ |
@@ -521,12 +519,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl | |||
521 | SDL_Surface *surface8 = | 519 | SDL_Surface *surface8 = |
522 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); | 520 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); |
523 | SDL_SetSurfacePalette(surface8, text_.grayscale); | 521 | SDL_SetSurfacePalette(surface8, text_.grayscale); |
524 | SDL_PixelFormat *fmt = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888); | 522 | return surface8; |
525 | SDL_Surface *surface = SDL_ConvertSurface(surface8, fmt, 0); | ||
526 | SDL_FreeFormat(fmt); | ||
527 | SDL_FreeSurface(surface8); | ||
528 | stbtt_FreeBitmap(bmp, NULL); | ||
529 | return surface; | ||
530 | } | 523 | } |
531 | 524 | ||
532 | iLocalDef SDL_Rect sdlRect_(const iRect rect) { | 525 | iLocalDef SDL_Rect sdlRect_(const iRect rect) { |
@@ -578,6 +571,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
578 | } | 571 | } |
579 | } | 572 | } |
580 | 573 | ||
574 | #if 0 | ||
581 | static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { | 575 | static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { |
582 | iText * txt = &text_; | 576 | iText * txt = &text_; |
583 | SDL_Renderer *render = txt->render; | 577 | SDL_Renderer *render = txt->render; |
@@ -601,6 +595,7 @@ static iBool cache_Font_(const iFont *d, iGlyph *glyph, int hoff) { | |||
601 | } | 595 | } |
602 | return isRasterized_Glyph_(glyph, hoff); | 596 | return isRasterized_Glyph_(glyph, hoff); |
603 | } | 597 | } |
598 | #endif | ||
604 | 599 | ||
605 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | 600 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { |
606 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { | 601 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { |
@@ -644,6 +639,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | |||
644 | return font; | 639 | return font; |
645 | } | 640 | } |
646 | 641 | ||
642 | #if 0 | ||
647 | static void doRaster_Font_(const iFont *font, iGlyph *glyph) { | 643 | static void doRaster_Font_(const iFont *font, iGlyph *glyph) { |
648 | SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); | 644 | SDL_Texture *oldTarget = SDL_GetRenderTarget(text_.render); |
649 | SDL_SetRenderTarget(text_.render, text_.cache); | 645 | SDL_SetRenderTarget(text_.render, text_.cache); |
@@ -655,8 +651,9 @@ static void doRaster_Font_(const iFont *font, iGlyph *glyph) { | |||
655 | } | 651 | } |
656 | SDL_SetRenderTarget(text_.render, oldTarget); | 652 | SDL_SetRenderTarget(text_.render, oldTarget); |
657 | } | 653 | } |
654 | #endif | ||
658 | 655 | ||
659 | static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | 656 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { |
660 | iGlyph * glyph; | 657 | iGlyph * glyph; |
661 | uint32_t glyphIndex = 0; | 658 | uint32_t glyphIndex = 0; |
662 | /* The glyph may actually come from a different font; look up the right font. */ | 659 | /* The glyph may actually come from a different font; look up the right font. */ |
@@ -682,9 +679,6 @@ static const iGlyph *glyph_Font_(iFont *d, iChar ch) { | |||
682 | allocate_Font_(font, glyph, 1); | 679 | allocate_Font_(font, glyph, 1); |
683 | insert_Hash(&font->glyphs, &glyph->node); | 680 | insert_Hash(&font->glyphs, &glyph->node); |
684 | } | 681 | } |
685 | if (enableRaster_Text_ && !isFullyRasterized_Glyph_(glyph)) { | ||
686 | doRaster_Font_(font, glyph); | ||
687 | } | ||
688 | return glyph; | 682 | return glyph; |
689 | } | 683 | } |
690 | 684 | ||
@@ -702,6 +696,129 @@ static iChar nextChar_(const char **chPos, const char *end) { | |||
702 | return ch; | 696 | return ch; |
703 | } | 697 | } |
704 | 698 | ||
699 | iDeclareType(RasterGlyph) | ||
700 | |||
701 | struct Impl_RasterGlyph { | ||
702 | iGlyph *glyph; | ||
703 | int hoff; | ||
704 | iRect rect; | ||
705 | }; | ||
706 | |||
707 | void cacheTextGlyphs_Font_(iFont *d, const iRangecc text) { | ||
708 | const char * chPos = text.start; | ||
709 | SDL_Surface *buf = NULL; | ||
710 | const iInt2 bufSize = init_I2(iMin(512, d->height * iMin(2 * size_Range(&text), 20)), | ||
711 | d->height * 4 / 3); | ||
712 | int bufX = 0; | ||
713 | iArray * rasters = NULL; | ||
714 | SDL_Texture *oldTarget = NULL; | ||
715 | iBool isTargetChanged = iFalse; | ||
716 | /* We'll flush the buffered rasters periodically until everything is cached. */ | ||
717 | while (chPos < text.end) { | ||
718 | while (chPos < text.end) { | ||
719 | const char *lastPos = chPos; | ||
720 | const iChar ch = nextChar_(&chPos, text.end); | ||
721 | if (ch == 0 || isSpace_Char(ch) || isDefaultIgnorable_Char(ch) || | ||
722 | isFitzpatrickType_Char(ch)) { | ||
723 | continue; | ||
724 | } | ||
725 | const int lastCacheBottom = text_.cacheBottom; | ||
726 | iGlyph *glyph = glyph_Font_(d, ch); | ||
727 | if (text_.cacheBottom < lastCacheBottom) { | ||
728 | /* The cache was reset due to running out of space. We need to restart from | ||
729 | the beginning! */ | ||
730 | chPos = text.start; | ||
731 | bufX = 0; | ||
732 | if (rasters) { | ||
733 | clear_Array(rasters); | ||
734 | } | ||
735 | } | ||
736 | if (!isFullyRasterized_Glyph_(glyph)) { | ||
737 | /* Need to cache this. */ | ||
738 | if (buf == NULL) { | ||
739 | rasters = new_Array(sizeof(iRasterGlyph)); | ||
740 | buf = SDL_CreateRGBSurfaceWithFormat( | ||
741 | 0, bufSize.x, bufSize.y, 8, SDL_PIXELFORMAT_INDEX8); | ||
742 | SDL_SetSurfacePalette(buf, text_.grayscale); | ||
743 | } | ||
744 | SDL_Surface *surfaces[2] = { | ||
745 | !isRasterized_Glyph_(glyph, 0) ? | ||
746 | rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0) : NULL, | ||
747 | !isRasterized_Glyph_(glyph, 1) ? | ||
748 | rasterizeGlyph_Font_(glyph->font, glyph->glyphIndex, 0.5f) : NULL | ||
749 | }; | ||
750 | iBool outOfSpace = iFalse; | ||
751 | iForIndices(i, surfaces) { | ||
752 | if (surfaces[i]) { | ||
753 | const int w = surfaces[i]->w; | ||
754 | const int h = surfaces[i]->h; | ||
755 | if (bufX + w <= bufSize.x) { | ||
756 | SDL_BlitSurface(surfaces[i], | ||
757 | NULL, | ||
758 | buf, | ||
759 | &(SDL_Rect){ bufX, 0, w, h }); | ||
760 | pushBack_Array(rasters, | ||
761 | &(iRasterGlyph){ glyph, i, init_Rect(bufX, 0, w, h) }); | ||
762 | bufX += w; | ||
763 | } | ||
764 | else { | ||
765 | outOfSpace = iTrue; | ||
766 | break; | ||
767 | } | ||
768 | } | ||
769 | } | ||
770 | iForIndices(i, surfaces) { | ||
771 | if (surfaces[i]) { | ||
772 | free(surfaces[i]->pixels); | ||
773 | SDL_FreeSurface(surfaces[i]); | ||
774 | } | ||
775 | } | ||
776 | if (outOfSpace) { | ||
777 | chPos = lastPos; | ||
778 | break; | ||
779 | } | ||
780 | } | ||
781 | } | ||
782 | /* Finished or the buffer is full, copy the glyphs to the cache texture. */ | ||
783 | if (!isEmpty_Array(rasters)) { | ||
784 | SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); | ||
785 | SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); | ||
786 | if (!isTargetChanged) { | ||
787 | isTargetChanged = iTrue; | ||
788 | oldTarget = SDL_GetRenderTarget(text_.render); | ||
789 | SDL_SetRenderTarget(text_.render, text_.cache); | ||
790 | } | ||
791 | //printf("copying %d rasters\n", size_Array(rasters)); fflush(stdout); | ||
792 | iConstForEach(Array, i, rasters) { | ||
793 | const iRasterGlyph *rg = i.value; | ||
794 | // iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); | ||
795 | const iRect *glRect = &rg->glyph->rect[rg->hoff]; | ||
796 | SDL_RenderCopy(text_.render, | ||
797 | bufTex, | ||
798 | (const SDL_Rect *) &rg->rect, | ||
799 | (const SDL_Rect *) glRect); | ||
800 | setRasterized_Glyph_(rg->glyph, rg->hoff); | ||
801 | } | ||
802 | SDL_DestroyTexture(bufTex); | ||
803 | /* Resume with an empty buffer. */ | ||
804 | clear_Array(rasters); | ||
805 | bufX = 0; | ||
806 | } | ||
807 | else { | ||
808 | iAssert(chPos >= text.end); | ||
809 | } | ||
810 | } | ||
811 | if (rasters) { | ||
812 | delete_Array(rasters); | ||
813 | } | ||
814 | if (buf) { | ||
815 | SDL_FreeSurface(buf); | ||
816 | } | ||
817 | if (isTargetChanged) { | ||
818 | SDL_SetRenderTarget(text_.render, oldTarget); | ||
819 | } | ||
820 | } | ||
821 | |||
705 | enum iRunMode { | 822 | enum iRunMode { |
706 | measure_RunMode = 0, | 823 | measure_RunMode = 0, |
707 | draw_RunMode = 1, | 824 | draw_RunMode = 1, |
@@ -777,8 +894,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
777 | if (isMonospaced) { | 894 | if (isMonospaced) { |
778 | monoAdvance = glyph_Font_(d, 'M')->advance; | 895 | monoAdvance = glyph_Font_(d, 'M')->advance; |
779 | } | 896 | } |
780 | /* Global flag that allows glyph rasterization. */ | 897 | /* Text rendering is not very straightforward! Let's dive in... */ |
781 | enableRaster_Text_ = !isMeasuring_(mode); | ||
782 | for (const char *chPos = args->text.start; chPos != args->text.end; ) { | 898 | for (const char *chPos = args->text.start; chPos != args->text.end; ) { |
783 | iAssert(chPos < args->text.end); | 899 | iAssert(chPos < args->text.end); |
784 | const char *currentPos = chPos; | 900 | const char *currentPos = chPos; |
@@ -880,6 +996,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
880 | const iGlyph *glyph = glyph_Font_(d, ch); | 996 | const iGlyph *glyph = glyph_Font_(d, ch); |
881 | int x1 = iMax(xpos, xposExtend); | 997 | int x1 = iMax(xpos, xposExtend); |
882 | const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; | 998 | const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0; |
999 | if (!isRasterized_Glyph_(glyph, hoff)) { | ||
1000 | /* Need to pause here and make sure all glyphs have been cached in the text. */ | ||
1001 | cacheTextGlyphs_Font_(d, args->text); | ||
1002 | } | ||
883 | int x2 = x1 + glyph->rect[hoff].size.x; | 1003 | int x2 = x1 + glyph->rect[hoff].size.x; |
884 | /* Out of the allotted space? */ | 1004 | /* Out of the allotted space? */ |
885 | if (args->xposLimit > 0 && x2 > args->xposLimit) { | 1005 | if (args->xposLimit > 0 && x2 > args->xposLimit) { |
@@ -999,6 +1119,10 @@ iInt2 measure_Text(int fontId, const char *text) { | |||
999 | return measureRange_Text(fontId, range_CStr(text)); | 1119 | return measureRange_Text(fontId, range_CStr(text)); |
1000 | } | 1120 | } |
1001 | 1121 | ||
1122 | void cache_Text(int fontId, iRangecc text) { | ||
1123 | cacheTextGlyphs_Font_(font_Text_(fontId), text); | ||
1124 | } | ||
1125 | |||
1002 | static int runFlagsFromId_(enum iFontId fontId) { | 1126 | static int runFlagsFromId_(enum iFontId fontId) { |
1003 | int runFlags = 0; | 1127 | int runFlags = 0; |
1004 | if (fontId & alwaysVariableFlag_FontId) { | 1128 | if (fontId & alwaysVariableFlag_FontId) { |
@@ -1060,10 +1184,11 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) { | |||
1060 | } | 1184 | } |
1061 | 1185 | ||
1062 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | 1186 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { |
1063 | iText *d = &text_; | 1187 | iText *d = &text_; |
1188 | iFont *font = font_Text_(fontId); | ||
1064 | const iColor clr = get_Color(color & mask_ColorId); | 1189 | const iColor clr = get_Color(color & mask_ColorId); |
1065 | SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); | 1190 | SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); |
1066 | run_Font_(font_Text_(fontId), | 1191 | run_Font_(font, |
1067 | &(iRunArgs){ .mode = draw_RunMode | | 1192 | &(iRunArgs){ .mode = draw_RunMode | |
1068 | (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | | 1193 | (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) | |
1069 | runFlagsFromId_(fontId), | 1194 | runFlagsFromId_(fontId), |
diff --git a/src/ui/text.h b/src/ui/text.h index 897dfed0..606096b6 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -192,6 +192,8 @@ enum iAlignment { | |||
192 | 192 | ||
193 | void setOpacity_Text (float opacity); | 193 | void setOpacity_Text (float opacity); |
194 | 194 | ||
195 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ | ||
196 | |||
195 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); | 197 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); |
196 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); | 198 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); |
197 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); | 199 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); |