summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-02 18:52:40 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-02 18:52:40 +0300
commitf8c1735fe857b68569bfc9c2db00fe62a56b7fcf (patch)
tree3841637076ea7330784904cc28e22e8c7ca05393
parent005a6a725422864b5f19f3c53cea6c615a8635f0 (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.c54
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) {