diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-02 18:52:40 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-02 18:52:40 +0300 |
commit | f8c1735fe857b68569bfc9c2db00fe62a56b7fcf (patch) | |
tree | 3841637076ea7330784904cc28e22e8c7ca05393 | |
parent | 005a6a725422864b5f19f3c53cea6c615a8635f0 (diff) |
Text: Monospace glyph fitting; trying out soft hyphens
If glyphs are borrowed from a different font, the offset/advance are adjusted to fit the correct monospacing (for pictographs/emoji).
-rw-r--r-- | src/ui/text.c | 54 |
1 files changed, 40 insertions, 14 deletions
diff --git a/src/ui/text.c b/src/ui/text.c index 3f6fce4a..be1685ad 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -1124,6 +1124,8 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1124 | float yCursor = 0.0f; | 1124 | float yCursor = 0.0f; |
1125 | float xCursorMax = 0.0f; | 1125 | float xCursorMax = 0.0f; |
1126 | iRangecc wrapRange = args->text; | 1126 | iRangecc wrapRange = args->text; |
1127 | const iBool isMonospaced = d->isMonospaced; | ||
1128 | const float monoAdvance = isMonospaced ? glyph_Font_(d, 'M')->advance : 0.0f; | ||
1127 | iAssert(args->text.end >= args->text.start); | 1129 | iAssert(args->text.end >= args->text.start); |
1128 | if (args->continueFrom_out) { | 1130 | if (args->continueFrom_out) { |
1129 | *args->continueFrom_out = args->text.end; | 1131 | *args->continueFrom_out = args->text.end; |
@@ -1167,6 +1169,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1167 | for (const char *pos = runText.start; pos < runText.end; ) { | 1169 | for (const char *pos = runText.start; pos < runText.end; ) { |
1168 | iChar ucp = 0; | 1170 | iChar ucp = 0; |
1169 | const int len = decodeBytes_MultibyteChar(pos, runText.end, &ucp); | 1171 | const int len = decodeBytes_MultibyteChar(pos, runText.end, &ucp); |
1172 | // if (ucp == 0xad) ucp = '-'; | ||
1170 | if (len > 0) { | 1173 | if (len > 0) { |
1171 | hb_buffer_add(hbBuf, ucp, pos - runText.start); | 1174 | hb_buffer_add(hbBuf, ucp, pos - runText.start); |
1172 | pos += len; | 1175 | pos += len; |
@@ -1176,33 +1179,62 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1176 | hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); | 1179 | hb_buffer_set_content_type(hbBuf, HB_BUFFER_CONTENT_TYPE_UNICODE); |
1177 | hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR); /* TODO: FriBidi? */ | 1180 | hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR); /* TODO: FriBidi? */ |
1178 | /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ | 1181 | /* hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN); */ /* will be autodetected */ |
1179 | hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */ | 1182 | //hb_buffer_set_language(hbBuf, hb_language_from_string("en", -1)); /* TODO: language from document/UI, if known */ |
1180 | hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */ | 1183 | hb_shape(run->font->hbFont, hbBuf, NULL, 0); /* TODO: Specify features, too? */ |
1181 | unsigned int glyphCount = 0; | 1184 | unsigned int glyphCount = 0; |
1182 | const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); | 1185 | const hb_glyph_info_t * glyphInfo = hb_buffer_get_glyph_infos(hbBuf, &glyphCount); |
1183 | const hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); | 1186 | hb_glyph_position_t * glyphPos = hb_buffer_get_glyph_positions(hbBuf, &glyphCount); |
1184 | const char *breakPos = NULL; | 1187 | const char *breakPos = NULL; |
1188 | iBool isSoftHyphenBreak = iFalse; | ||
1189 | /* Fit foreign glyphs into the expected monospacing. */ | ||
1190 | if (isMonospaced) { | ||
1191 | for (unsigned int i = 0; i < glyphCount; ++i) { | ||
1192 | const hb_glyph_info_t *info = &glyphInfo[i]; | ||
1193 | const hb_codepoint_t glyphId = info->codepoint; | ||
1194 | const char *textPos = runText.start + info->cluster; | ||
1195 | if (run->font != d) { | ||
1196 | iChar ch = 0; | ||
1197 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); | ||
1198 | if (isPictograph_Char(ch) || isEmoji_Char(ch)) { | ||
1199 | const float dw = run->font->xScale * glyphPos[i].x_advance - monoAdvance; | ||
1200 | glyphPos[i].x_offset -= dw / 2 / run->font->xScale - 1; | ||
1201 | glyphPos[i].x_advance -= dw / run->font->xScale - 1; | ||
1202 | } | ||
1203 | } | ||
1204 | } | ||
1205 | } | ||
1185 | /* Check if this run needs to be wrapped. If so, we'll draw the portion that fits on | 1206 | /* Check if this run needs to be wrapped. If so, we'll draw the portion that fits on |
1186 | the line, and re-run the loop resuming the run from the wrap point. */ | 1207 | the line, and re-run the loop resuming the run from the wrap point. */ |
1187 | if (args->wrap && args->wrap->maxWidth > 0) { | 1208 | if (args->wrap && args->wrap->maxWidth > 0) { |
1188 | float x = xCursor; | 1209 | float x = xCursor; |
1189 | const char *safeBreak = NULL; | 1210 | const char *safeBreak = NULL; |
1211 | iChar prevCh = 0; | ||
1190 | for (unsigned int i = 0; i < glyphCount; i++) { | 1212 | for (unsigned int i = 0; i < glyphCount; i++) { |
1191 | const hb_glyph_info_t *info = &glyphInfo[i]; | 1213 | const hb_glyph_info_t *info = &glyphInfo[i]; |
1192 | const hb_codepoint_t glyphId = info->codepoint; | 1214 | const hb_codepoint_t glyphId = info->codepoint; |
1215 | const char *textPos = runText.start + info->cluster; | ||
1193 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1216 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1194 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); | 1217 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); |
1195 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; | 1218 | const float xOffset = run->font->xScale * glyphPos[i].x_offset; |
1196 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; | 1219 | const float xAdvance = run->font->xScale * glyphPos[i].x_advance; |
1197 | const char *textPos = runText.start + info->cluster; | ||
1198 | if (args->wrap->mode == word_WrapTextMode) { | 1220 | if (args->wrap->mode == word_WrapTextMode) { |
1199 | /* When word wrapping, only consider certain places breakable. */ | 1221 | /* When word wrapping, only consider certain places breakable. */ |
1200 | iChar ch = 0; | 1222 | iChar ch = 0; |
1201 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); | 1223 | decodeBytes_MultibyteChar(textPos, runText.end, &ch); |
1202 | /* TODO: Allow some punctuation to wrap words. */ | 1224 | /*if (prevCh == 0xad) { |
1203 | if (isSpace_Char(ch)) { | ||
1204 | safeBreak = textPos; | 1225 | safeBreak = textPos; |
1226 | isSoftHyphenBreak = iTrue; | ||
1205 | } | 1227 | } |
1228 | else*/ | ||
1229 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { | ||
1230 | safeBreak = textPos; | ||
1231 | isSoftHyphenBreak = iFalse; | ||
1232 | } | ||
1233 | else if (isSpace_Char(ch)) { | ||
1234 | safeBreak = textPos; | ||
1235 | isSoftHyphenBreak = iFalse; | ||
1236 | } | ||
1237 | prevCh = ch; | ||
1206 | } | 1238 | } |
1207 | else { | 1239 | else { |
1208 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { | 1240 | if (~glyphFlags & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { |
@@ -1234,6 +1266,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1234 | } | 1266 | } |
1235 | /* We have determined a possible wrap position inside the text run, so now we can | 1267 | /* We have determined a possible wrap position inside the text run, so now we can |
1236 | draw the glyphs. */ | 1268 | draw the glyphs. */ |
1269 | float surplusAdvance = 0.0f; | ||
1237 | for (unsigned int i = 0; i < glyphCount; i++) { | 1270 | for (unsigned int i = 0; i < glyphCount; i++) { |
1238 | const hb_glyph_info_t *info = &glyphInfo[i]; | 1271 | const hb_glyph_info_t *info = &glyphInfo[i]; |
1239 | const hb_codepoint_t glyphId = info->codepoint; | 1272 | const hb_codepoint_t glyphId = info->codepoint; |
@@ -1253,7 +1286,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1253 | } | 1286 | } |
1254 | /* Draw the glyph. */ { | 1287 | /* Draw the glyph. */ { |
1255 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, | 1288 | SDL_Rect dst = { orig.x + xCursor + xOffset + glyph->d[hoff].x, |
1256 | orig.y + yCursor + yOffset + glyph->font->baseline + glyph->d[hoff].y, | 1289 | orig.y + yCursor - yOffset + glyph->font->baseline + glyph->d[hoff].y, |
1257 | glyph->rect[hoff].size.x, | 1290 | glyph->rect[hoff].size.x, |
1258 | glyph->rect[hoff].size.y }; | 1291 | glyph->rect[hoff].size.y }; |
1259 | if (mode & visualFlag_RunMode) { | 1292 | if (mode & visualFlag_RunMode) { |
@@ -1268,17 +1301,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1268 | bounds.size.x = iMax(bounds.size.x, dst.x + dst.w); | 1301 | bounds.size.x = iMax(bounds.size.x, dst.x + dst.w); |
1269 | bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); | 1302 | bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); |
1270 | } | 1303 | } |
1271 | if (mode & draw_RunMode) { | 1304 | if (mode & draw_RunMode && *textPos != 0x20) { |
1272 | if (!isRasterized_Glyph_(glyph, hoff)) { | 1305 | if (!isRasterized_Glyph_(glyph, hoff)) { |
1273 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ | 1306 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ |
1274 | glyph = glyphByIndex_Font_(run->font, glyphId); | 1307 | glyph = glyphByIndex_Font_(run->font, glyphId); |
1275 | #if 0 | ||
1276 | if (!isRasterized_Glyph_(glyph, hoff)) { | ||
1277 | /* TODO: Should not be needed! The glyph cache should retry automatically if running out of buffer. */ | ||
1278 | cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ | ||
1279 | glyph = glyphByIndex_Font_(run->font, glyphId); | ||
1280 | } | ||
1281 | #endif | ||
1282 | iAssert(isRasterized_Glyph_(glyph, hoff)); | 1308 | iAssert(isRasterized_Glyph_(glyph, hoff)); |
1283 | } | 1309 | } |
1284 | if (~mode & permanentColorFlag_RunMode) { | 1310 | if (~mode & permanentColorFlag_RunMode) { |