summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c277
1 files changed, 10 insertions, 267 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
index edbc6583..c851be90 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -46,6 +46,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
46#include <SDL_version.h> 46#include <SDL_version.h>
47#include <stdarg.h> 47#include <stdarg.h>
48 48
49#if defined (LAGRANGE_ENABLE_HARFBUZZ)
50# include <hb.h>
51#endif
52
49#if SDL_VERSION_ATLEAST(2, 0, 10) 53#if SDL_VERSION_ATLEAST(2, 0, 10)
50# define LAGRANGE_RASTER_DEPTH 8 54# define LAGRANGE_RASTER_DEPTH 8
51# define LAGRANGE_RASTER_FORMAT SDL_PIXELFORMAT_INDEX8 55# define LAGRANGE_RASTER_FORMAT SDL_PIXELFORMAT_INDEX8
@@ -909,278 +913,17 @@ struct Impl_RunArgs {
909 int * runAdvance_out; 913 int * runAdvance_out;
910}; 914};
911 915
916#if defined (LAGRANGE_ENABLE_HARFBUZZ)
912static iRect run_Font_(iFont *d, const iRunArgs *args) { 917static iRect run_Font_(iFont *d, const iRunArgs *args) {
913 iRect bounds = zero_Rect(); 918 iRect bounds = zero_Rect();
914 const iInt2 orig = args->pos; 919 const iInt2 orig = args->pos;
915 float xpos = orig.x;
916 float xposMax = xpos;
917 float monoAdvance = 0;
918 int ypos = orig.y;
919 size_t maxLen = args->maxLen ? args->maxLen : iInvalidSize;
920 float xposExtend = orig.x; /* allows wide glyphs to use more space; restored by whitespace */
921 const enum iRunMode mode = args->mode;
922 const char * lastWordEnd = args->text.start;
923 iAssert(args->xposLimit == 0 || isMeasuring_(mode));
924 iAssert(args->text.end >= args->text.start);
925 if (args->continueFrom_out) {
926 *args->continueFrom_out = args->text.end;
927 }
928 iChar prevCh = 0;
929 const iBool isMonospaced = d->isMonospaced && !(mode & alwaysVariableWidthFlag_RunMode);
930 if (isMonospaced) {
931 monoAdvance = glyph_Font_(d, 'M')->advance;
932 }
933 if (args->mode & fillBackground_RunMode) {
934 const iColor initial = get_Color(args->color);
935 SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0);
936 }
937 /* Text rendering is not very straightforward! Let's dive in... */
938 for (const char *chPos = args->text.start; chPos != args->text.end; ) {
939 iAssert(chPos < args->text.end);
940 const char *currentPos = chPos;
941 if (*chPos == 0x1b) { /* ANSI escape. */
942 chPos++;
943 iRegExpMatch m;
944 init_RegExpMatch(&m);
945 if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) {
946 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
947 /* Change the color. */
948 const iColor clr =
949 ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId);
950 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);
951 if (args->mode & fillBackground_RunMode) {
952 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);
953 }
954 }
955 chPos = end_RegExpMatch(&m);
956 continue;
957 }
958 }
959 iChar ch = nextChar_(&chPos, args->text.end);
960 iBool isEmoji = isEmoji_Char(ch);
961 if (ch == 0x200d) { /* zero-width joiner */
962 /* We don't have the composited Emojis. */
963 if (isEmoji_Char(prevCh)) {
964 /* skip */
965 nextChar_(&chPos, args->text.end);
966 ch = nextChar_(&chPos, args->text.end);
967 }
968 }
969 if (isVariationSelector_Char(ch)) {
970 ch = nextChar_(&chPos, args->text.end); /* skip it */
971 }
972 /* Special instructions. */ {
973 if (ch == 0xad) { /* soft hyphen */
974 lastWordEnd = chPos;
975 if (isMeasuring_(mode)) {
976 if (args->xposLimit > 0) {
977 const char *postHyphen = chPos;
978 iChar nextCh = nextChar_(&postHyphen, args->text.end);
979 if ((int) xpos + glyph_Font_(d, ch)->rect[0].size.x +
980 glyph_Font_(d, nextCh)->rect[0].size.x > args->xposLimit) {
981 /* Wraps after hyphen, should show it. */
982 }
983 else continue;
984 }
985 else continue;
986 }
987 else {
988 /* Only show it at the end. */
989 if (chPos != args->text.end) {
990 continue;
991 }
992 }
993 }
994 /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */
995 if (ch == '\n') {
996 if (args->xposLimit > 0 && mode & stopAtNewline_RunMode) {
997 /* Stop the line here, this is a hard warp. */
998 if (args->continueFrom_out) {
999 *args->continueFrom_out = chPos;
1000 }
1001 break;
1002 }
1003 xpos = xposExtend = orig.x;
1004 ypos += d->height;
1005 prevCh = ch;
1006 continue;
1007 }
1008 if (ch == '\t') {
1009 const int tabStopWidth = d->height * 10;
1010 const int halfWidth = (iMax(args->xposLimit, args->xposLayoutBound) - orig.x) / 2;
1011 const int xRel = xpos - orig.x;
1012 /* First stop is always to half width. */
1013 if (halfWidth > 0 && xRel < halfWidth) {
1014 xpos = orig.x + halfWidth;
1015 }
1016 else if (halfWidth > 0 && xRel < halfWidth * 3 / 2) {
1017 xpos = orig.x + halfWidth * 3 / 2;
1018 }
1019 else {
1020 xpos = orig.x + ((xRel / tabStopWidth) + 1) * tabStopWidth;
1021 }
1022 xposExtend = iMax(xposExtend, xpos);
1023 prevCh = 0;
1024 continue;
1025 }
1026 if (ch == '\v') { /* color change */
1027 iChar esc = nextChar_(&chPos, args->text.end);
1028 int colorNum = args->color;
1029 if (esc == '\v') { /* Extended range. */
1030 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
1031 colorNum = esc - asciiBase_ColorEscape;
1032 }
1033 else if (esc != 0x24) { /* ASCII Cancel */
1034 colorNum = esc - asciiBase_ColorEscape;
1035 }
1036 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
1037 const iColor clr = get_Color(colorNum);
1038 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);
1039 if (args->mode & fillBackground_RunMode) {
1040 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0);
1041 }
1042 }
1043 prevCh = 0;
1044 continue;
1045 }
1046 if (isDefaultIgnorable_Char(ch) || isFitzpatrickType_Char(ch)) {
1047 continue;
1048 }
1049 }
1050 const iGlyph *glyph = glyph_Font_(d, ch);
1051 int x1 = iMax(xpos, xposExtend);
1052 /* Which half of the pixel the glyph falls on? */
1053 const int hoff = enableHalfPixelGlyphs_Text ? (xpos - x1 > 0.5f ? 1 : 0) : 0;
1054 if (mode & draw_RunMode && ch != 0x20 && ch != 0 && !isRasterized_Glyph_(glyph, hoff)) {
1055 /* Need to pause here and make sure all glyphs have been cached in the text. */
1056// printf("[Text] missing from cache: %lc (%x)\n", (int) ch, ch);
1057 cacheTextGlyphs_Font_(d, args->text);
1058 glyph = glyph_Font_(d, ch); /* cache may have been reset */
1059 }
1060 int x2 = x1 + glyph->rect[hoff].size.x;
1061 /* Out of the allotted space on the line? */
1062 if (args->xposLimit > 0 && x2 > args->xposLimit) {
1063 if (args->continueFrom_out) {
1064 if (lastWordEnd != args->text.start && ~mode & noWrapFlag_RunMode) {
1065 *args->continueFrom_out = skipSpace_CStr(lastWordEnd);
1066 *args->continueFrom_out = iMin(*args->continueFrom_out,
1067 args->text.end);
1068 }
1069 else {
1070 *args->continueFrom_out = currentPos; /* forced break */
1071 }
1072 }
1073 break;
1074 }
1075 const int yLineMax = ypos + d->height;
1076 SDL_Rect dst = { x1 + glyph->d[hoff].x,
1077 ypos + glyph->font->baseline + glyph->d[hoff].y,
1078 glyph->rect[hoff].size.x,
1079 glyph->rect[hoff].size.y };
1080 if (glyph->font != d) {
1081 if (glyph->font->height > d->height) {
1082 /* Center-align vertically so the baseline isn't totally offset. */
1083 dst.y -= (glyph->font->height - d->height) / 2;
1084 }
1085 }
1086 /* Update the bounding box. */
1087 if (mode & visualFlag_RunMode) {
1088 if (isEmpty_Rect(bounds)) {
1089 bounds = init_Rect(dst.x, dst.y, dst.w, dst.h);
1090 }
1091 else {
1092 bounds = union_Rect(bounds, init_Rect(dst.x, dst.y, dst.w, dst.h));
1093 }
1094 }
1095 else {
1096 bounds.size.x = iMax(bounds.size.x, x2 - orig.x);
1097 bounds.size.y = iMax(bounds.size.y, ypos + glyph->font->height - orig.y);
1098 }
1099 /* Symbols and emojis are NOT monospaced, so must conform when the primary font
1100 is monospaced. Except with Japanese script, that's larger than the normal monospace. */
1101 const iBool useMonoAdvance =
1102 monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font));
1103 const float advance = (useMonoAdvance && glyph->advance > 0 ? monoAdvance : glyph->advance);
1104 if (!isMeasuring_(mode) && ch != 0x20 /* don't bother rendering spaces */) {
1105 if (useMonoAdvance && dst.w > advance && glyph->font != d && !isEmoji) {
1106 /* Glyphs from a different font may need recentering to look better. */
1107 dst.x -= (dst.w - advance) / 2;
1108 }
1109 SDL_Rect src;
1110 memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect));
1111 /* Clip the glyphs to the font's height. This is useful when the font's line spacing
1112 has been reduced or when the glyph is from a different font. */
1113 if (dst.y + dst.h > yLineMax) {
1114 const int over = dst.y + dst.h - yLineMax;
1115 src.h -= over;
1116 dst.h -= over;
1117 }
1118 if (dst.y < ypos) {
1119 const int over = ypos - dst.y;
1120 dst.y += over;
1121 dst.h -= over;
1122 src.y += over;
1123 src.h -= over;
1124 }
1125 if (args->mode & fillBackground_RunMode) {
1126 /* Alpha blending looks much better if the RGB components don't change in
1127 the partially transparent pixels. */
1128 SDL_RenderFillRect(text_.render, &dst);
1129 }
1130 SDL_RenderCopy(text_.render, text_.cache, &src, &dst);
1131 }
1132 xpos += advance;
1133 if (!isSpace_Char(ch)) {
1134 xposExtend += isEmoji ? glyph->advance : advance;
1135 }
1136#if defined (LAGRANGE_ENABLE_KERNING)
1137 /* Check the next character. */
1138 if (!isMonospaced && glyph->font == d) {
1139 /* TODO: No need to decode the next char twice; check this on the next iteration. */
1140 const char *peek = chPos;
1141 const iChar next = nextChar_(&peek, args->text.end);
1142 if (enableKerning_Text && !d->manualKernOnly && next) {
1143 const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next);
1144 int kern = stbtt_GetGlyphKernAdvance(
1145 &glyph->font->font, glyph->glyphIndex, nextGlyphIndex);
1146 /* Nunito needs some kerning fixes. */
1147 if (glyph->font->family == nunito_TextFont) {
1148 if (ch == 'W' && (next == 'i' || next == 'h')) {
1149 kern = -30;
1150 }
1151 else if (ch == 'T' && next == 'h') {
1152 kern = -15;
1153 }
1154 else if (ch == 'V' && next == 'i') {
1155 kern = -15;
1156 }
1157 }
1158 if (kern) {
1159// printf("%lc(%u) -> %lc(%u): kern %d (%f)\n", ch, glyph->glyphIndex, next,
1160// nextGlyphIndex,
1161// kern, d->xScale * kern);
1162 xpos += glyph->font->xScale * kern;
1163 xposExtend += glyph->font->xScale * kern;
1164 }
1165 }
1166 }
1167#endif
1168 xposExtend = iMax(xposExtend, xpos);
1169 xposMax = iMax(xposMax, xposExtend);
1170 if (args->continueFrom_out && ((mode & noWrapFlag_RunMode) || isWrapBoundary_(prevCh, ch))) {
1171 lastWordEnd = currentPos; /* mark word wrap position */
1172 }
1173 prevCh = ch;
1174 if (--maxLen == 0) {
1175 break;
1176 }
1177 }
1178 if (args->runAdvance_out) {
1179 *args->runAdvance_out = xposMax - orig.x;
1180 }
1181 fflush(stdout);
1182 return bounds; 920 return bounds;
1183} 921}
922#else /* !defined (LAGRANGE_ENABLE_HARFBUZZ) */
923/* The fallback method: an incomplete solution for simple scripts. */
924# define run_Font_ runSimple_Font_
925# include "text_simple.c"
926#endif
1184 927
1185int lineHeight_Text(int fontId) { 928int lineHeight_Text(int fontId) {
1186 return font_Text_(fontId)->height; 929 return font_Text_(fontId)->height;