summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/color.c7
-rw-r--r--src/ui/color.h2
-rw-r--r--src/ui/command.h4
-rw-r--r--src/ui/documentwidget.c483
-rw-r--r--src/ui/documentwidget.h2
-rw-r--r--src/ui/labelwidget.c4
-rw-r--r--src/ui/mediaui.c92
-rw-r--r--src/ui/mediaui.h20
-rw-r--r--src/ui/mobile.c8
-rw-r--r--src/ui/root.c16
-rw-r--r--src/ui/sidebarwidget.c18
-rw-r--r--src/ui/text.c397
-rw-r--r--src/ui/text.h145
-rw-r--r--src/ui/uploadwidget.c2
-rw-r--r--src/ui/util.c169
-rw-r--r--src/ui/widget.c3
-rw-r--r--src/ui/window.c8
17 files changed, 794 insertions, 586 deletions
diff --git a/src/ui/color.c b/src/ui/color.c
index 4f275ae5..0f7d4368 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -309,9 +309,10 @@ void setThemePalette_Color(enum iColorTheme theme) {
309 setHsl_Color(uiBackgroundFolder_ColorId, 309 setHsl_Color(uiBackgroundFolder_ColorId,
310 addSatLum_HSLColor(get_HSLColor(uiBackgroundSidebar_ColorId), 310 addSatLum_HSLColor(get_HSLColor(uiBackgroundSidebar_ColorId),
311 0, 311 0,
312 theme == pureBlack_ColorTheme ? -1 312 theme == pureBlack_ColorTheme ? -1
313 : theme == dark_ColorTheme ? -0.04 313 : theme == dark_ColorTheme ? -0.04
314 : -0.075)); 314 : theme == pureWhite_ColorTheme ? -0.04
315 : -0.055));
315 set_Color(uiTextShortcut_ColorId, mix_Color(get_Color(uiTextShortcut_ColorId), 316 set_Color(uiTextShortcut_ColorId, mix_Color(get_Color(uiTextShortcut_ColorId),
316 get_Color(uiBackground_ColorId), 317 get_Color(uiBackground_ColorId),
317 0.4f)); 318 0.4f));
diff --git a/src/ui/color.h b/src/ui/color.h
index 179db3e9..c9dff598 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -160,7 +160,7 @@ enum iColorId {
160 tmGopherLinkDomain_ColorId, 160 tmGopherLinkDomain_ColorId,
161 tmGopherLinkLastVisitDate_ColorId, 161 tmGopherLinkLastVisitDate_ColorId,
162 162
163 max_ColorId, 163 max_ColorId, /* note: GmRun packs color into limited number of bits */
164 tmMax_ColorId = max_ColorId - tmFirst_ColorId 164 tmMax_ColorId = max_ColorId - tmFirst_ColorId
165}; 165};
166 166
diff --git a/src/ui/command.h b/src/ui/command.h
index 8dbaafad..8dcae892 100644
--- a/src/ui/command.h
+++ b/src/ui/command.h
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#pragma once 23#pragma once
24 24
25#include <the_Foundation/range.h> 25#include <the_Foundation/range.h>
26#include <the_Foundation/string.h>
26#include <the_Foundation/vec2.h> 27#include <the_Foundation/vec2.h>
27 28
28iBool equal_Command (const char *commandWithArgs, const char *command); 29iBool equal_Command (const char *commandWithArgs, const char *command);
@@ -45,3 +46,6 @@ iString * suffix_Command (const char *, const char *label); /* until
45iLocalDef iBool hasLabel_Command(const char *d, const char *label) { 46iLocalDef iBool hasLabel_Command(const char *d, const char *label) {
46 return suffixPtr_Command(d, label) != NULL; 47 return suffixPtr_Command(d, label) != NULL;
47} 48}
49iLocalDef const char *cstr_Command(const char *d, const char *label) {
50 return cstr_Rangecc(range_Command(d, label));
51}
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 93b2166b..ee669c1a 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -400,7 +400,18 @@ void init_DocumentWidget(iDocumentWidget *d) {
400 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); 400 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");
401} 401}
402 402
403void cancelAllRequests_DocumentWidget(iDocumentWidget *d) {
404 iForEach(ObjectList, i, d->media) {
405 iMediaRequest *mr = i.object;
406 cancel_GmRequest(mr->req);
407 }
408 if (d->request) {
409 cancel_GmRequest(d->request);
410 }
411}
412
403void deinit_DocumentWidget(iDocumentWidget *d) { 413void deinit_DocumentWidget(iDocumentWidget *d) {
414 cancelAllRequests_DocumentWidget(d);
404 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 415 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
405 removeTicker_App(animate_DocumentWidget_, d); 416 removeTicker_App(animate_DocumentWidget_, d);
406 removeTicker_App(prerender_DocumentWidget_, d); 417 removeTicker_App(prerender_DocumentWidget_, d);
@@ -496,7 +507,8 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
496 -1.0f, 10.0f); /* adapt to width */ 507 -1.0f, 10.0f); /* adapt to width */
497 //printf("%f\n", adjust); fflush(stdout); 508 //printf("%f\n", adjust); fflush(stdout);
498 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), 509 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2),
499 fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100); 510 fontSize_UI * //emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */
511 prefs->lineWidth * prefs->zoomPercent / 100);
500} 512}
501 513
502static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 514static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
@@ -563,7 +575,8 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
563 pushBack_PtrArray(&d->visibleWideRuns, run); 575 pushBack_PtrArray(&d->visibleWideRuns, run);
564 } 576 }
565 } 577 }
566 if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { 578 /* Image runs are static so they're drawn as part of the content. */
579 if (run->mediaType && run->mediaType != image_MediaType) {
567 iAssert(run->mediaId); 580 iAssert(run->mediaId);
568 pushBack_PtrArray(&d->visibleMedia, run); 581 pushBack_PtrArray(&d->visibleMedia, run);
569 } 582 }
@@ -757,14 +770,14 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
757 uint32_t interval = invalidInterval_; 770 uint32_t interval = invalidInterval_;
758 iConstForEach(PtrArray, i, &d->visibleMedia) { 771 iConstForEach(PtrArray, i, &d->visibleMedia) {
759 const iGmRun *run = i.ptr; 772 const iGmRun *run = i.ptr;
760 if (run->mediaType == audio_GmRunMediaType) { 773 if (run->mediaType == audio_MediaType) {
761 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 774 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
762 if (flags_Player(plr) & adjustingVolume_PlayerFlag || 775 if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
763 (isStarted_Player(plr) && !isPaused_Player(plr))) { 776 (isStarted_Player(plr) && !isPaused_Player(plr))) {
764 interval = iMin(interval, 1000 / 15); 777 interval = iMin(interval, 1000 / 15);
765 } 778 }
766 } 779 }
767 else if (run->mediaType == download_GmRunMediaType) { 780 else if (run->mediaType == download_MediaType) {
768 interval = iMin(interval, 1000); 781 interval = iMin(interval, 1000);
769 } 782 }
770 } 783 }
@@ -783,8 +796,8 @@ static void updateMedia_DocumentWidget_(iDocumentWidget *d) {
783 refresh_Widget(d); 796 refresh_Widget(d);
784 iConstForEach(PtrArray, i, &d->visibleMedia) { 797 iConstForEach(PtrArray, i, &d->visibleMedia) {
785 const iGmRun *run = i.ptr; 798 const iGmRun *run = i.ptr;
786 if (run->mediaType == audio_GmRunMediaType) { 799 if (run->mediaType == audio_MediaType) {
787 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 800 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
788 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && 801 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
789 flags_Player(plr) & adjustingVolume_PlayerFlag) { 802 flags_Player(plr) & adjustingVolume_PlayerFlag) {
790 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); 803 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
@@ -928,8 +941,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
928 pushBackCStr_StringArray(title, "Lagrange"); 941 pushBackCStr_StringArray(title, "Lagrange");
929 } 942 }
930 /* Take away parts if it doesn't fit. */ 943 /* Take away parts if it doesn't fit. */
931 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI; 944 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI;
932 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d)); 945 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d));
946 const int font = uiLabel_FontId;
933 for (;;) { 947 for (;;) {
934 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); 948 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 "));
935 if (setWindow) { 949 if (setWindow) {
@@ -945,7 +959,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
945 prependChar_String(text, siteIcon); 959 prependChar_String(text, siteIcon);
946 prependCStr_String(text, escape_Color(uiIcon_ColorId)); 960 prependCStr_String(text, escape_Color(uiIcon_ColorId));
947 } 961 }
948 const int width = measureRange_Text(default_FontId, range_String(text)).advance.x; 962 const int width = measureRange_Text(font, range_String(text)).advance.x;
949 if (width <= avail || 963 if (width <= avail ||
950 isEmpty_StringArray(title)) { 964 isEmpty_StringArray(title)) {
951 updateText_LabelWidget(tabButton, text); 965 updateText_LabelWidget(tabButton, text);
@@ -954,9 +968,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
954 if (size_StringArray(title) == 1) { 968 if (size_StringArray(title) == 1) {
955 /* Just truncate to fit. */ 969 /* Just truncate to fit. */
956 const char *endPos; 970 const char *endPos;
957 tryAdvanceNoWrap_Text(default_FontId, 971 tryAdvanceNoWrap_Text(font,
958 range_String(text), 972 range_String(text),
959 avail - measure_Text(default_FontId, "...").advance.x, 973 avail - measure_Text(font, "...").advance.x,
960 &endPos); 974 &endPos);
961 updateText_LabelWidget( 975 updateText_LabelWidget(
962 tabButton, 976 tabButton,
@@ -1242,6 +1256,9 @@ static const char *zipPageHeading_(const iRangecc mime) {
1242 if (equalCase_Rangecc(mime, "application/gpub+zip")) { 1256 if (equalCase_Rangecc(mime, "application/gpub+zip")) {
1243 return book_Icon " Gempub"; 1257 return book_Icon " Gempub";
1244 } 1258 }
1259 else if (equalCase_Rangecc(mime, mimeType_FontPack)) {
1260 return "\U0001f520 Fontpack";
1261 }
1245 iRangecc type = iNullRange; 1262 iRangecc type = iNullRange;
1246 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ 1263 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */
1247 nextSplit_Rangecc(mime, "/", &type); 1264 nextSplit_Rangecc(mime, "/", &type);
@@ -1256,165 +1273,175 @@ static const char *zipPageHeading_(const iRangecc mime) {
1256 1273
1257static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { 1274static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {
1258 iWidget *w = as_Widget(d); 1275 iWidget *w = as_Widget(d);
1259 delete_Gempub(d->sourceGempub); 1276 /* Gempub page behavior and footer actions. */ {
1260 d->sourceGempub = NULL; 1277 /* TODO: move this to gempub.c */
1261 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || 1278 delete_Gempub(d->sourceGempub);
1262 !cmpCase_String(&d->sourceMime, mimeType_Gempub) || 1279 d->sourceGempub = NULL;
1263 endsWithCase_String(d->mod.url, ".gpub")) { 1280 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") ||
1264 iGempub *gempub = new_Gempub(); 1281 !cmpCase_String(&d->sourceMime, mimeType_Gempub) ||
1265 if (open_Gempub(gempub, &d->sourceContent)) { 1282 endsWithCase_String(d->mod.url, ".gpub")) {
1266 setBaseUrl_Gempub(gempub, d->mod.url);
1267 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1268 setCStr_String(&d->sourceMime, mimeType_Gempub);
1269 d->sourceGempub = gempub;
1270 }
1271 else {
1272 delete_Gempub(gempub);
1273 }
1274 }
1275 if (!d->sourceGempub) {
1276 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1277 iBool isInside = iFalse;
1278 if (localPath && !fileExists_FileInfo(localPath)) {
1279 /* This URL may refer to a file inside the archive. */
1280 localPath = findContainerArchive_Path(localPath);
1281 isInside = iTrue;
1282 }
1283 if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) {
1284 iGempub *gempub = new_Gempub(); 1283 iGempub *gempub = new_Gempub();
1285 if (openFile_Gempub(gempub, localPath)) { 1284 if (open_Gempub(gempub, &d->sourceContent)) {
1286 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); 1285 setBaseUrl_Gempub(gempub, d->mod.url);
1287 if (!isInside) { 1286 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1288 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); 1287 setCStr_String(&d->sourceMime, mimeType_Gempub);
1289 setCStr_String(&d->sourceMime, mimeType_Gempub);
1290 }
1291 d->sourceGempub = gempub; 1288 d->sourceGempub = gempub;
1292 } 1289 }
1293 else { 1290 else {
1294 delete_Gempub(gempub); 1291 delete_Gempub(gempub);
1295 } 1292 }
1296 } 1293 }
1297 } 1294 if (!d->sourceGempub) {
1298 if (d->sourceGempub) { 1295 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1299 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { 1296 iBool isInside = iFalse;
1300 if (!isRemote_Gempub(d->sourceGempub)) { 1297 if (localPath && !fileExists_FileInfo(localPath)) {
1301 iArray *items = collectNew_Array(sizeof(iMenuItem)); 1298 /* This URL may refer to a file inside the archive. */
1302 pushBack_Array( 1299 localPath = findContainerArchive_Path(localPath);
1303 items, 1300 isInside = iTrue;
1304 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1305 0,
1306 0,
1307 format_CStr("!open url:%s",
1308 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1309 if (navSize_Gempub(d->sourceGempub) > 0) {
1310 pushBack_Array(
1311 items,
1312 &(iMenuItem){
1313 format_CStr(forwardArrow_Icon " %s",
1314 cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),
1315 SDLK_RIGHT,
1316 0,
1317 format_CStr("!open url:%s",
1318 cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });
1319 }
1320 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1321 } 1301 }
1322 else { 1302 if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) {
1323 makeFooterButtons_DocumentWidget_( 1303 iGempub *gempub = new_Gempub();
1324 d, 1304 if (openFile_Gempub(gempub, localPath)) {
1325 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", 1305 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath)));
1326 SDLK_s, 1306 if (!isInside) {
1327 KMOD_PRIMARY | KMOD_SHIFT, 1307 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1328 "document.save open:1" }, 1308 setCStr_String(&d->sourceMime, mimeType_Gempub);
1329 { download_Icon " " saveToDownloads_Label, 1309 }
1330 SDLK_s, 1310 d->sourceGempub = gempub;
1331 KMOD_PRIMARY, 1311 }
1332 "document.save" } }, 1312 else {
1333 2); 1313 delete_Gempub(gempub);
1334 } 1314 }
1335 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1336 redoLayout_GmDocument(d->doc);
1337 updateVisible_DocumentWidget_(d);
1338 invalidate_DocumentWidget_(d);
1339 } 1315 }
1340 } 1316 }
1341 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1317 if (d->sourceGempub) {
1342 makeFooterButtons_DocumentWidget_( 1318 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) {
1343 d, 1319 if (!isRemote_Gempub(d->sourceGempub)) {
1344 (iMenuItem[]){ { format_CStr(book_Icon " %s", 1320 iArray *items = collectNew_Array(sizeof(iMenuItem));
1345 cstr_String(property_Gempub(d->sourceGempub,
1346 title_GempubProperty))),
1347 SDLK_LEFT,
1348 0,
1349 format_CStr("!open url:%s",
1350 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },
1351 1);
1352 }
1353 else {
1354 /* Navigation buttons. */
1355 iArray *items = collectNew_Array(sizeof(iMenuItem));
1356 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1357 if (navIndex != iInvalidPos) {
1358 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1359 pushBack_Array( 1321 pushBack_Array(
1360 items, 1322 items,
1361 &(iMenuItem){ 1323 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1362 format_CStr(forwardArrow_Icon " %s", 1324 0,
1363 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), 1325 0,
1364 SDLK_RIGHT, 1326 format_CStr("!open url:%s",
1365 0, 1327 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1366 format_CStr("!open url:%s", 1328 if (navSize_Gempub(d->sourceGempub) > 0) {
1367 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); 1329 pushBack_Array(
1330 items,
1331 &(iMenuItem){
1332 format_CStr(forwardArrow_Icon " %s",
1333 cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),
1334 SDLK_RIGHT,
1335 0,
1336 format_CStr("!open url:%s",
1337 cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });
1338 }
1339 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1368 } 1340 }
1369 if (navIndex > 0) { 1341 else {
1370 pushBack_Array( 1342 makeFooterButtons_DocumentWidget_(
1371 items, 1343 d,
1372 &(iMenuItem){ 1344 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}",
1373 format_CStr(backArrow_Icon " %s", 1345 SDLK_s,
1374 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), 1346 KMOD_PRIMARY | KMOD_SHIFT,
1375 SDLK_LEFT, 1347 "document.save open:1" },
1376 0, 1348 { download_Icon " " saveToDownloads_Label,
1377 format_CStr("!open url:%s", 1349 SDLK_s,
1378 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); 1350 KMOD_PRIMARY,
1351 "document.save" } },
1352 2);
1379 } 1353 }
1380 else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1354 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1381 pushBack_Array( 1355 redoLayout_GmDocument(d->doc);
1382 items, 1356 updateVisible_DocumentWidget_(d);
1383 &(iMenuItem){ 1357 invalidate_DocumentWidget_(d);
1384 format_CStr(book_Icon " %s",
1385 cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),
1386 SDLK_LEFT,
1387 0,
1388 format_CStr("!open url:%s",
1389 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });
1390 } 1358 }
1391 } 1359 }
1392 if (!isEmpty_Array(items)) { 1360 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1393 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); 1361 makeFooterButtons_DocumentWidget_(
1362 d,
1363 (iMenuItem[]){ { format_CStr(book_Icon " %s",
1364 cstr_String(property_Gempub(d->sourceGempub,
1365 title_GempubProperty))),
1366 SDLK_LEFT,
1367 0,
1368 format_CStr("!open url:%s",
1369 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },
1370 1);
1394 } 1371 }
1395 } 1372 else {
1396 if (!isCached && prefs_App()->pinSplit && 1373 /* Navigation buttons. */
1397 equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1374 iArray *items = collectNew_Array(sizeof(iMenuItem));
1398 const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); 1375 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1399 if (navStart) { 1376 if (navIndex != iInvalidPos) {
1400 iWindow *win = get_Window(); 1377 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1401 /* Auto-split to show index and the first navigation link. */ 1378 pushBack_Array(
1402 if (numRoots_Window(win) == 2) { 1379 items,
1403 /* This document is showing the index page. */ 1380 &(iMenuItem){
1404 iRoot *other = otherRoot_Window(win, w->root); 1381 format_CStr(forwardArrow_Icon " %s",
1405 postCommandf_Root(other, "open url:%s", cstr_String(navStart)); 1382 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))),
1406 if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { 1383 SDLK_RIGHT,
1407 /* On the wrong side. */ 1384 0,
1408 postCommand_App("ui.split swap:1"); 1385 format_CStr("!open url:%s",
1386 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) });
1387 }
1388 if (navIndex > 0) {
1389 pushBack_Array(
1390 items,
1391 &(iMenuItem){
1392 format_CStr(backArrow_Icon " %s",
1393 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))),
1394 SDLK_LEFT,
1395 0,
1396 format_CStr("!open url:%s",
1397 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) });
1398 }
1399 else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1400 pushBack_Array(
1401 items,
1402 &(iMenuItem){
1403 format_CStr(book_Icon " %s",
1404 cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),
1405 SDLK_LEFT,
1406 0,
1407 format_CStr("!open url:%s",
1408 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });
1409 } 1409 }
1410 } 1410 }
1411 else { 1411 if (!isEmpty_Array(items)) {
1412 postCommandf_App( 1412 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1413 "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); 1413 }
1414 }
1415 if (!isCached && prefs_App()->pinSplit &&
1416 equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1417 const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub);
1418 if (navStart) {
1419 iWindow *win = get_Window();
1420 /* Auto-split to show index and the first navigation link. */
1421 if (numRoots_Window(win) == 2) {
1422 /* This document is showing the index page. */
1423 iRoot *other = otherRoot_Window(win, w->root);
1424 postCommandf_Root(other, "open url:%s", cstr_String(navStart));
1425 if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) {
1426 /* On the wrong side. */
1427 postCommand_App("ui.split swap:1");
1428 }
1429 }
1430 else {
1431 postCommandf_App(
1432 "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart));
1433 }
1414 } 1434 }
1415 } 1435 }
1416 } 1436 }
1417 } 1437 }
1438 /* Local fontpacks are automatically shown. */
1439 if (preloadLocalFontpackForPreview_Fonts(d->doc)) {
1440 documentRunsInvalidated_DocumentWidget_(d);
1441 redoLayout_GmDocument(d->doc);
1442 updateVisible_DocumentWidget_(d);
1443 invalidate_DocumentWidget_(d);
1444 }
1418} 1445}
1419 1446
1420static void updateDocument_DocumentWidget_(iDocumentWidget *d, 1447static void updateDocument_DocumentWidget_(iDocumentWidget *d,
@@ -1482,7 +1509,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1482 } 1509 }
1483 delete_String(localPath); 1510 delete_String(localPath);
1484 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { 1511 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
1485 appendFormat_String(&str, "=> %s/ ${doc.archive.view}\n", 1512 appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n",
1486 cstr_String(withSpacesEncoded_String(d->mod.url))); 1513 cstr_String(withSpacesEncoded_String(d->mod.url)));
1487 } 1514 }
1488 translate_Lang(&str); 1515 translate_Lang(&str);
@@ -1619,7 +1646,7 @@ static void parseUser_DocumentWidget_(iDocumentWidget *d) {
1619static void cacheRunGlyphs_(void *data, const iGmRun *run) { 1646static void cacheRunGlyphs_(void *data, const iGmRun *run) {
1620 iUnused(data); 1647 iUnused(data);
1621 if (!isEmpty_Range(&run->text)) { 1648 if (!isEmpty_Range(&run->text)) {
1622 cache_Text(run->textParams.font, run->text); 1649 cache_Text(run->font, run->text);
1623 } 1650 }
1624} 1651}
1625 1652
@@ -2087,7 +2114,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId,
2087} 2114}
2088 2115
2089static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { 2116static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) {
2090 return findLinkDownload_Media(constMedia_GmDocument(d->doc), req->linkId) != 0; 2117 return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0;
2091} 2118}
2092 2119
2093static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2120static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -2172,7 +2199,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
2172static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { 2199static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
2173 iConstForEach(PtrArray, i, &d->visibleLinks) { 2200 iConstForEach(PtrArray, i, &d->visibleLinks) {
2174 const iGmRun *run = i.ptr; 2201 const iGmRun *run = i.ptr;
2175 if (run->linkId && run->mediaType == none_GmRunMediaType && 2202 if (run->linkId && run->mediaType == none_MediaType &&
2176 ~run->flags & decoration_GmRunFlag) { 2203 ~run->flags & decoration_GmRunFlag) {
2177 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); 2204 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId);
2178 if (isMediaLink_GmDocument(d->doc, run->linkId) && 2205 if (isMediaLink_GmDocument(d->doc, run->linkId) &&
@@ -2761,8 +2788,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2761 else if (equalWidget_Command(cmd, w, "document.downloadlink")) { 2788 else if (equalWidget_Command(cmd, w, "document.downloadlink")) {
2762 if (d->contextLink) { 2789 if (d->contextLink) {
2763 const iGmLinkId linkId = d->contextLink->linkId; 2790 const iGmLinkId linkId = d->contextLink->linkId;
2764 setDownloadUrl_Media( 2791 setUrl_Media(media_GmDocument(d->doc),
2765 media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId)); 2792 linkId,
2793 download_MediaType,
2794 linkUrl_GmDocument(d->doc, linkId));
2766 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); 2795 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */);
2767 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ 2796 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */
2768 updateVisible_DocumentWidget_(d); 2797 updateVisible_DocumentWidget_(d);
@@ -2872,7 +2901,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2872 const iMedia * media = media_GmDocument(d->doc); 2901 const iMedia * media = media_GmDocument(d->doc);
2873 const size_t num = numAudio_Media(media); 2902 const size_t num = numAudio_Media(media);
2874 for (size_t id = 1; id <= num; id++) { 2903 for (size_t id = 1; id <= num; id++) {
2875 iPlayer *plr = audioPlayer_Media(media, id); 2904 iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id });
2876 if (plr != startedPlr) { 2905 if (plr != startedPlr) {
2877 setPaused_Player(plr, iTrue); 2906 setPaused_Player(plr, iTrue);
2878 } 2907 }
@@ -3210,8 +3239,8 @@ static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run
3210} 3239}
3211 3240
3212static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 3241static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
3213 if (run && run->mediaType == audio_GmRunMediaType) { 3242 if (run && run->mediaType == audio_MediaType) {
3214 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3243 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3215 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); 3244 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue);
3216 d->grabbedStartVolume = volume_Player(plr); 3245 d->grabbedStartVolume = volume_Player(plr);
3217 d->grabbedPlayer = run; 3246 d->grabbedPlayer = run;
@@ -3219,7 +3248,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
3219 } 3248 }
3220 else if (d->grabbedPlayer) { 3249 else if (d->grabbedPlayer) {
3221 setFlags_Player( 3250 setFlags_Player(
3222 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId), 3251 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)),
3223 volumeGrabbed_PlayerFlag, 3252 volumeGrabbed_PlayerFlag,
3224 iFalse); 3253 iFalse);
3225 d->grabbedPlayer = NULL; 3254 d->grabbedPlayer = NULL;
@@ -3247,11 +3276,21 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
3247 const iInt2 mouse = init_I2(ev->button.x, ev->button.y); 3276 const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
3248 iConstForEach(PtrArray, i, &d->visibleMedia) { 3277 iConstForEach(PtrArray, i, &d->visibleMedia) {
3249 const iGmRun *run = i.ptr; 3278 const iGmRun *run = i.ptr;
3250 if (run->mediaType != audio_GmRunMediaType) { 3279 if (run->mediaType == fontpack_MediaType) {
3280 iFontpackUI ui;
3281 init_FontpackUI(&ui, media_GmDocument(d->doc), run->mediaId,
3282 runRect_DocumentWidget_(d, run));
3283 if (processEvent_FontpackUI(&ui, ev)) {
3284 refresh_Widget(d);
3285 return iTrue;
3286 }
3287 }
3288 if (run->mediaType != audio_MediaType) {
3251 continue; 3289 continue;
3252 } 3290 }
3291 /* TODO: move this to mediaui.c */
3253 const iRect rect = runRect_DocumentWidget_(d, run); 3292 const iRect rect = runRect_DocumentWidget_(d, run);
3254 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3293 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3255 if (contains_Rect(rect, mouse)) { 3294 if (contains_Rect(rect, mouse)) {
3256 iPlayerUI ui; 3295 iPlayerUI ui;
3257 init_PlayerUI(&ui, plr, rect); 3296 init_PlayerUI(&ui, plr, rect);
@@ -3439,7 +3478,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3439 return iTrue; 3478 return iTrue;
3440 } 3479 }
3441 break; 3480 break;
3442#if 1 3481#if !defined (NDEBUG)
3443 case SDLK_KP_1: 3482 case SDLK_KP_1:
3444 case '`': { 3483 case '`': {
3445 iBlock *seed = new_Block(64); 3484 iBlock *seed = new_Block(64);
@@ -3631,7 +3670,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3631 cstr_String(linkUrl)) }, 3670 cstr_String(linkUrl)) },
3632 }, 3671 },
3633 3); 3672 3);
3634 if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { 3673 if (isNative && d->contextLink->mediaType != download_MediaType) {
3635 pushBackN_Array(&items, (iMenuItem[]){ 3674 pushBackN_Array(&items, (iMenuItem[]){
3636 { "---" }, 3675 { "---" },
3637 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, 3676 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" },
@@ -3639,7 +3678,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3639 } 3678 }
3640 iMediaRequest *mediaReq; 3679 iMediaRequest *mediaReq;
3641 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && 3680 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL &&
3642 d->contextLink->mediaType != download_GmRunMediaType) { 3681 d->contextLink->mediaType != download_MediaType) {
3643 if (isFinished_GmRequest(mediaReq->req)) { 3682 if (isFinished_GmRequest(mediaReq->req)) {
3644 pushBack_Array(&items, 3683 pushBack_Array(&items,
3645 &(iMenuItem){ download_Icon " " saveToDownloads_Label, 3684 &(iMenuItem){ download_Icon " " saveToDownloads_Label,
@@ -3761,7 +3800,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3761 case drag_ClickResult: { 3800 case drag_ClickResult: {
3762 if (d->grabbedPlayer) { 3801 if (d->grabbedPlayer) {
3763 iPlayer *plr = 3802 iPlayer *plr =
3764 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId); 3803 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer));
3765 iPlayerUI ui; 3804 iPlayerUI ui;
3766 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); 3805 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer));
3767 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); 3806 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider);
@@ -3887,8 +3926,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3887 } 3926 }
3888 if (d->hoverLink) { 3927 if (d->hoverLink) {
3889 /* TODO: Move this to a method. */ 3928 /* TODO: Move this to a method. */
3890 const iGmLinkId linkId = d->hoverLink->linkId; 3929 const iGmLinkId linkId = d->hoverLink->linkId;
3891 const int linkFlags = linkFlags_GmDocument(d->doc, linkId); 3930 const iMediaId linkMedia = mediaId_GmRun(d->hoverLink);
3931 const int linkFlags = linkFlags_GmDocument(d->doc, linkId);
3892 iAssert(linkId); 3932 iAssert(linkId);
3893 /* Media links are opened inline by default. */ 3933 /* Media links are opened inline by default. */
3894 if (isMediaLink_GmDocument(d->doc, linkId)) { 3934 if (isMediaLink_GmDocument(d->doc, linkId)) {
@@ -3941,6 +3981,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3941 } 3981 }
3942 refresh_Widget(w); 3982 refresh_Widget(w);
3943 } 3983 }
3984 else if (linkMedia.type == download_MediaType ||
3985 findMediaRequest_DocumentWidget_(d, linkId)) {
3986 /* TODO: What should be done when clicking on an inline download?
3987 Maybe dismiss if finished? */
3988 return iTrue;
3989 }
3944 else if (linkFlags & supportedScheme_GmLinkFlag) { 3990 else if (linkFlags & supportedScheme_GmLinkFlag) {
3945 int tabMode = openTabMode_Sym(modState_Keys()); 3991 int tabMode = openTabMode_Sym(modState_Keys());
3946 if (isPinned_DocumentWidget_(d)) { 3992 if (isPinned_DocumentWidget_(d)) {
@@ -4026,7 +4072,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4026 contains_Range(&mark, run->text.start))) { 4072 contains_Range(&mark, run->text.start))) {
4027 int x = 0; 4073 int x = 0;
4028 if (!*isInside) { 4074 if (!*isInside) {
4029 x = measureRange_Text(run->textParams.font, 4075 x = measureRange_Text(run->font,
4030 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) 4076 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) })
4031 .advance.x; 4077 .advance.x;
4032 } 4078 }
@@ -4035,7 +4081,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4035 iRangecc mk = !*isInside ? mark 4081 iRangecc mk = !*isInside ? mark
4036 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }; 4082 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) };
4037 mk.start = iMax(mk.start, run->text.start); 4083 mk.start = iMax(mk.start, run->text.start);
4038 w = measureRange_Text(run->textParams.font, mk).advance.x; 4084 w = measureRange_Text(run->font, mk).advance.x;
4039 *isInside = iFalse; 4085 *isInside = iFalse;
4040 } 4086 }
4041 else { 4087 else {
@@ -4074,7 +4120,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4074 4120
4075static void drawMark_DrawContext_(void *context, const iGmRun *run) { 4121static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4076 iDrawContext *d = context; 4122 iDrawContext *d = context;
4077 if (run->mediaType == none_GmRunMediaType) { 4123 if (run->mediaType == none_MediaType) {
4078 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); 4124 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
4079 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); 4125 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
4080 } 4126 }
@@ -4088,15 +4134,15 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
4088 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); 4134 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
4089 if (icon) { 4135 if (icon) {
4090 appendChar_String(&str, icon); 4136 appendChar_String(&str, icon);
4091 const iRect iconRect = visualBounds_Text(run->textParams.font, range_String(&str)); 4137 const iRect iconRect = visualBounds_Text(run->font, range_String(&str));
4092 drawRange_Text( 4138 drawRange_Text(
4093 run->textParams.font, 4139 run->font,
4094 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->textParams.font) / 2), 4140 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
4095 tmBannerIcon_ColorId, 4141 tmBannerIcon_ColorId,
4096 range_String(&str)); 4142 range_String(&str));
4097 bpos.x += right_Rect(iconRect) + 3 * gap_Text; 4143 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
4098 } 4144 }
4099 drawRange_Text(run->textParams.font, 4145 drawRange_Text(run->font,
4100 bpos, 4146 bpos,
4101 tmBannerTitle_ColorId, 4147 tmBannerTitle_ColorId,
4102 bannerText_DocumentWidget_(d->widget)); 4148 bannerText_DocumentWidget_(d->widget));
@@ -4181,8 +4227,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4181 d->runsDrawn.end = run; 4227 d->runsDrawn.end = run;
4182 } 4228 }
4183 } 4229 }
4184 if (run->mediaType == image_GmRunMediaType) { 4230 if (run->mediaType == image_MediaType) {
4185 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); 4231 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run));
4186 const iRect dst = moved_Rect(run->visBounds, origin); 4232 const iRect dst = moved_Rect(run->visBounds, origin);
4187 if (tex) { 4233 if (tex) {
4188 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ 4234 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -4203,7 +4249,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4203 /* Media UIs are drawn afterwards as a dynamic overlay. */ 4249 /* Media UIs are drawn afterwards as a dynamic overlay. */
4204 return; 4250 return;
4205 } 4251 }
4206 enum iColorId fg = run->textParams.color; 4252 enum iColorId fg = run->color;
4207 const iGmDocument *doc = d->widget->doc; 4253 const iGmDocument *doc = d->widget->doc;
4208 const int linkFlags = linkFlags_GmDocument(doc, run->linkId); 4254 const int linkFlags = linkFlags_GmDocument(doc, run->linkId);
4209 /* Hover state of a link. */ 4255 /* Hover state of a link. */
@@ -4270,10 +4316,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4270 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); 4316 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId);
4271 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); 4317 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);
4272 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); 4318 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId);
4273 drawWrapRange_Text(run->textParams.font, 4319 drawWrapRange_Text(run->font,
4274 add_I2(visPos, margin), 4320 add_I2(visPos, margin),
4275 run->visBounds.size.x - 2 * margin.x, 4321 run->visBounds.size.x - 2 * margin.x,
4276 run->textParams.color, 4322 run->color,
4277 run->text); 4323 run->text);
4278 } 4324 }
4279 else if (run->flags & siteBanner_GmRunFlag) { 4325 else if (run->flags & siteBanner_GmRunFlag) {
@@ -4292,17 +4338,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4292 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4338 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase);
4293 if (ordChar) { 4339 if (ordChar) {
4294 const char *circle = "\u25ef"; /* Large Circle */ 4340 const char *circle = "\u25ef"; /* Large Circle */
4295 const int circleFont = defaultContentRegular_FontId; 4341 const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize);
4296 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 4342 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
4297 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) }; 4343 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) };
4298 drawRange_Text( 4344 drawRange_Text(
4299 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); 4345 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle));
4300 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle)); 4346 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle));
4301 addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); 4347 addv_I2(&circleArea.pos, topLeft_Rect(nbArea));
4302 drawCentered_Text(defaultContentSmall_FontId, 4348 drawCentered_Text(FONT_ID(default_FontId, regular_FontStyle, contentSmall_FontSize),
4303 circleArea, 4349 circleArea,
4304 iTrue, 4350 iTrue,
4305 tmQuote_ColorId, 4351 tmQuote_ColorId,
4306 "%lc", 4352 "%lc",
4307 (int) ordChar); 4353 (int) ordChar);
4308 goto runDrawn; 4354 goto runDrawn;
@@ -4312,15 +4358,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4312 if (run->flags & quoteBorder_GmRunFlag) { 4358 if (run->flags & quoteBorder_GmRunFlag) {
4313 drawVLine_Paint(&d->paint, 4359 drawVLine_Paint(&d->paint,
4314 addX_I2(visPos, 4360 addX_I2(visPos,
4315 !run->textParams.isRTL 4361 !run->isRTL
4316 ? -gap_Text * 5 / 2 4362 ? -gap_Text * 5 / 2
4317 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)), 4363 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)),
4318 height_Rect(run->visBounds), 4364 height_Rect(run->visBounds),
4319 tmQuoteIcon_ColorId); 4365 tmQuoteIcon_ColorId);
4320 } 4366 }
4321 drawBoundRange_Text(run->textParams.font, 4367 drawBoundRange_Text(run->font,
4322 visPos, 4368 visPos,
4323 (run->textParams.isRTL ? -1 : 1) * width_Rect(run->visBounds), 4369 (run->isRTL ? -1 : 1) * width_Rect(run->visBounds),
4324 fg, 4370 fg,
4325 run->text); 4371 run->text);
4326 runDrawn:; 4372 runDrawn:;
@@ -4337,31 +4383,31 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4337 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); 4383 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart);
4338 iString text; 4384 iString text;
4339 init_String(&text); 4385 init_String(&text);
4340 iMediaId imageId = linkImage_GmDocument(doc, run->linkId); 4386 const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc),
4341 iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0; 4387 run->linkId, none_MediaType);
4342 iMediaId downloadId = !imageId && !audioId ? 4388 iAssert(linkMedia.type != none_MediaType);
4343 findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0; 4389 iGmMediaInfo info;
4344 iAssert(imageId || audioId || downloadId); 4390 info_Media(constMedia_GmDocument(doc), linkMedia, &info);
4345 if (imageId) { 4391 switch (linkMedia.type) {
4346 iAssert(!isEmpty_Rect(run->bounds)); 4392 case image_MediaType: {
4347 iGmMediaInfo info; 4393 iAssert(!isEmpty_Rect(run->bounds));
4348 imageInfo_Media(constMedia_GmDocument(doc), imageId, &info); 4394 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia);
4349 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId); 4395 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s",
4350 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", 4396 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f,
4351 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, 4397 cstr_Lang("mb"));
4352 cstr_Lang("mb")); 4398 break;
4353 } 4399 }
4354 else if (audioId) { 4400 case audio_MediaType:
4355 iGmMediaInfo info; 4401 format_String(&text, "%s", info.type);
4356 audioInfo_Media(constMedia_GmDocument(doc), audioId, &info); 4402 break;
4357 format_String(&text, "%s", info.type); 4403 case download_MediaType:
4358 } 4404 format_String(&text, "%s", info.type);
4359 else if (downloadId) { 4405 break;
4360 iGmMediaInfo info; 4406 default:
4361 downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info); 4407 break;
4362 format_String(&text, "%s", info.type);
4363 } 4408 }
4364 if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { 4409 if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */
4410 findMediaRequest_DocumentWidget_(d->widget, run->linkId)) {
4365 appendFormat_String( 4411 appendFormat_String(
4366 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); 4412 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : "");
4367 } 4413 }
@@ -4436,7 +4482,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4436 append_String(&str, collect_String(format_Date(&date, "%b %d"))); 4482 append_String(&str, collect_String(format_Date(&date, "%b %d")));
4437 } 4483 }
4438 if (!isEmpty_String(&str)) { 4484 if (!isEmpty_String(&str)) {
4439 if (run->textParams.isRTL) { 4485 if (run->isRTL) {
4440 appendCStr_String(&str, " \u2014 "); 4486 appendCStr_String(&str, " \u2014 ");
4441 } 4487 }
4442 else { 4488 else {
@@ -4445,7 +4491,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4445 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size; 4491 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size;
4446 int tx = topRight_Rect(linkRect).x; 4492 int tx = topRight_Rect(linkRect).x;
4447 const char *msg = cstr_String(&str); 4493 const char *msg = cstr_String(&str);
4448 if (run->textParams.isRTL) { 4494 if (run->isRTL) {
4449 tx = topLeft_Rect(linkRect).x - textSize.x; 4495 tx = topLeft_Rect(linkRect).x - textSize.x;
4450 } 4496 }
4451 if (tx + textSize.x > right_Rect(d->widgetBounds)) { 4497 if (tx + textSize.x > right_Rect(d->widgetBounds)) {
@@ -4592,18 +4638,25 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4592static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { 4638static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
4593 iConstForEach(PtrArray, i, &d->visibleMedia) { 4639 iConstForEach(PtrArray, i, &d->visibleMedia) {
4594 const iGmRun * run = i.ptr; 4640 const iGmRun * run = i.ptr;
4595 if (run->mediaType == audio_GmRunMediaType) { 4641 if (run->mediaType == audio_MediaType) {
4596 iPlayerUI ui; 4642 iPlayerUI ui;
4597 init_PlayerUI(&ui, 4643 init_PlayerUI(&ui,
4598 audioPlayer_Media(media_GmDocument(d->doc), run->mediaId), 4644 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)),
4599 runRect_DocumentWidget_(d, run)); 4645 runRect_DocumentWidget_(d, run));
4600 draw_PlayerUI(&ui, p); 4646 draw_PlayerUI(&ui, p);
4601 } 4647 }
4602 else if (run->mediaType == download_GmRunMediaType) { 4648 else if (run->mediaType == download_MediaType) {
4603 iDownloadUI ui; 4649 iDownloadUI ui;
4604 init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run)); 4650 init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4651 runRect_DocumentWidget_(d, run));
4605 draw_DownloadUI(&ui, p); 4652 draw_DownloadUI(&ui, p);
4606 } 4653 }
4654 else if (run->mediaType == fontpack_MediaType) {
4655 iFontpackUI ui;
4656 init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4657 runRect_DocumentWidget_(d, run));
4658 draw_FontpackUI(&ui, p);
4659 }
4607 } 4660 }
4608} 4661}
4609 4662
@@ -4914,7 +4967,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4914 } 4967 }
4915 /* Pinch zoom indicator. */ 4968 /* Pinch zoom indicator. */
4916 if (d->flags & pinchZoom_DocumentWidgetFlag) { 4969 if (d->flags & pinchZoom_DocumentWidgetFlag) {
4917 const int font = defaultLargeBold_FontId; 4970 const int font = uiLabelLargeBold_FontId;
4918 const int height = lineHeight_Text(font) * 2; 4971 const int height = lineHeight_Text(font) * 2;
4919 const iInt2 size = init_I2(height * 2, height); 4972 const iInt2 size = init_I2(height * 2, height);
4920 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; 4973 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size };
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index cc09c72d..1f2ecfc0 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -32,6 +32,8 @@ iDeclareType(History)
32iDeclareWidgetClass(DocumentWidget) 32iDeclareWidgetClass(DocumentWidget)
33iDeclareObjectConstruction(DocumentWidget) 33iDeclareObjectConstruction(DocumentWidget)
34 34
35void cancelAllRequests_DocumentWidget(iDocumentWidget *);
36
35void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs); 37void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs);
36void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins); 38void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins);
37 39
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index cfc81863..9713e1f2 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -384,7 +384,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
384 } 384 }
385 else if (flags & alignLeft_WidgetFlag) { 385 else if (flags & alignLeft_WidgetFlag) {
386 draw_Text(d->font, add_I2(bounds.pos, addX_I2(padding_LabelWidget_(d, 0), iconPad)), 386 draw_Text(d->font, add_I2(bounds.pos, addX_I2(padding_LabelWidget_(d, 0), iconPad)),
387 fg, cstr_String(&d->label)); 387 fg, "%s", cstr_String(&d->label));
388 if ((flags & drawKey_WidgetFlag) && d->key) { 388 if ((flags & drawKey_WidgetFlag) && d->key) {
389 iString str; 389 iString str;
390 init_String(&str); 390 init_String(&str);
@@ -399,6 +399,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
399 : colorEscape != none_ColorId ? colorEscape 399 : colorEscape != none_ColorId ? colorEscape
400 : uiTextShortcut_ColorId,*/ 400 : uiTextShortcut_ColorId,*/
401 right_Alignment, 401 right_Alignment,
402 "%s",
402 cstr_String(&str)); 403 cstr_String(&str));
403 deinit_String(&str); 404 deinit_String(&str);
404 } 405 }
@@ -409,6 +410,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
409 add_I2(topRight_Rect(bounds), negX_I2(padding_LabelWidget_(d, 1))), 410 add_I2(topRight_Rect(bounds), negX_I2(padding_LabelWidget_(d, 1))),
410 fg, 411 fg,
411 right_Alignment, 412 right_Alignment,
413 "%s",
412 cstr_String(&d->label)); 414 cstr_String(&d->label));
413 } 415 }
414 else { 416 else {
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c
index b622a554..aa45d73a 100644
--- a/src/ui/mediaui.c
+++ b/src/ui/mediaui.c
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "lang.h" 30#include "lang.h"
31 31
32#include <the_Foundation/path.h> 32#include <the_Foundation/path.h>
33#include <the_Foundation/stringlist.h>
33 34
34static const char *volumeChar_(float volume) { 35static const char *volumeChar_(float volume) {
35 if (volume <= 0) { 36 if (volume <= 0) {
@@ -61,7 +62,7 @@ void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) {
61 } 62 }
62} 63}
63 64
64static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font) { 65static void drawInlineButton_(iPaint *p, iRect rect, const char *label, int font) {
65 const iInt2 mouse = mouseCoord_Window(get_Window(), 0); 66 const iInt2 mouse = mouseCoord_Window(get_Window(), 0);
66 const iBool isHover = contains_Rect(rect, mouse); 67 const iBool isHover = contains_Rect(rect, mouse);
67 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; 68 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0;
@@ -86,7 +87,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) {
86 const int hours = seconds / 3600; 87 const int hours = seconds / 3600;
87 const int mins = (seconds / 60) % 60; 88 const int mins = (seconds / 60) % 60;
88 const int secs = seconds % 60; 89 const int secs = seconds % 60;
89 const int font = defaultBig_FontId; 90 const int font = uiLabelBig_FontId;
90 iString num; 91 iString num;
91 init_String(&num); 92 init_String(&num);
92 if (hours) { 93 if (hours) {
@@ -113,17 +114,17 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
113 const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; 114 const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0;
114 fillRect_Paint(p, d->bounds, playerBackground_ColorId); 115 fillRect_Paint(p, d->bounds, playerBackground_ColorId);
115 drawRect_Paint(p, d->bounds, playerFrame_ColorId); 116 drawRect_Paint(p, d->bounds, playerFrame_ColorId);
116 drawPlayerButton_(p, 117 drawInlineButton_(p,
117 d->playPauseRect, 118 d->playPauseRect,
118 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", 119 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8",
119 uiContent_FontId); 120 uiContent_FontId);
120 drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); 121 drawInlineButton_(p, d->rewindRect, "\u23ee", uiContent_FontId);
121 drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId); 122 drawInlineButton_(p, d->menuRect, menu_Icon, uiContent_FontId);
122 if (!isAdjusting) { 123 if (!isAdjusting) {
123 drawPlayerButton_( 124 drawInlineButton_(
124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); 125 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId);
125 } 126 }
126 const int hgt = lineHeight_Text(defaultBig_FontId); 127 const int hgt = lineHeight_Text(uiLabelBig_FontId);
127 const int yMid = mid_Rect(d->scrubberRect).y; 128 const int yMid = mid_Rect(d->scrubberRect).y;
128 const float playTime = time_Player(d->player); 129 const float playTime = time_Player(d->player);
129 const float totalTime = duration_Player(d->player); 130 const float totalTime = duration_Player(d->player);
@@ -228,24 +229,26 @@ static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) {
228 deinit_String(&digits); 229 deinit_String(&digits);
229} 230}
230 231
231void init_DownloadUI(iDownloadUI *d, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds) { 232void init_DownloadUI(iDownloadUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) {
232 d->doc = doc; 233 d->media = media;
233 d->mediaId = mediaId; 234 d->mediaId = mediaId;
234 d->bounds = bounds; 235 d->bounds = bounds;
235} 236}
236 237
238/*----------------------------------------------------------------------------------------------*/
239
237iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { 240iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) {
238 return iFalse; 241 return iFalse;
239} 242}
240 243
241void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { 244void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
242 const iMedia *media = constMedia_GmDocument(document_DocumentWidget(d->doc));
243 iGmMediaInfo info; 245 iGmMediaInfo info;
244 float bytesPerSecond; 246 float bytesPerSecond;
245 const iString *path; 247 const iString *path;
246 iBool isFinished; 248 iBool isFinished;
247 downloadInfo_Media(media, d->mediaId, &info); 249 downloadInfo_Media(d->media, d->mediaId, &info);
248 downloadStats_Media(media, d->mediaId, &path, &bytesPerSecond, &isFinished); 250 downloadStats_Media(d->media, (iMediaId){ download_MediaType, d->mediaId },
251 &path, &bytesPerSecond, &isFinished);
249 fillRect_Paint(p, d->bounds, uiBackground_ColorId); 252 fillRect_Paint(p, d->bounds, uiBackground_ColorId);
250 drawRect_Paint(p, d->bounds, uiSeparator_ColorId); 253 drawRect_Paint(p, d->bounds, uiSeparator_ColorId);
251 iRect rect = d->bounds; 254 iRect rect = d->bounds;
@@ -275,3 +278,68 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
275 translateCStr_Lang("\u2014 ${mb.per.sec}")); 278 translateCStr_Lang("\u2014 ${mb.per.sec}"));
276 } 279 }
277} 280}
281
282/*----------------------------------------------------------------------------------------------*/
283
284void init_FontpackUI(iFontpackUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) {
285 d->media = media;
286 d->mediaId = mediaId;
287 d->bounds = bounds;
288 d->installRect.size = add_I2(measure_Text(uiLabel_FontId, "${media.fontpack.install}").bounds.size,
289 muli_I2(gap2_UI, 3));
290 d->installRect.pos.x = right_Rect(d->bounds) - gap_UI - d->installRect.size.x;
291 d->installRect.pos.y = mid_Rect(d->bounds).y - d->installRect.size.y / 2;
292}
293
294iBool processEvent_FontpackUI(iFontpackUI *d, const SDL_Event *ev) {
295 switch (ev->type) {
296 case SDL_MOUSEBUTTONDOWN:
297 case SDL_MOUSEBUTTONUP: {
298 const iInt2 pos = init_I2(ev->button.x, ev->button.y);
299 if (contains_Rect(d->installRect, pos)) {
300 return iTrue;
301 }
302 break;
303 }
304 case SDL_MOUSEMOTION:
305 if (contains_Rect(d->bounds, init_I2(ev->motion.x, ev->motion.y))) {
306 return iTrue;
307 }
308 break;
309 }
310 return iFalse;
311}
312
313int height_FontpackUI(const iMedia *media, uint16_t mediaId, int width) {
314 const iStringList *names;
315 iFontpackMediaInfo info;
316 fontpackInfo_Media(media, (iMediaId){ fontpack_MediaType, mediaId }, &info);
317 return lineHeight_Text(uiContent_FontId) +
318 lineHeight_Text(uiLabel_FontId) * (1 + size_StringList(info.names));
319}
320
321void draw_FontpackUI(const iFontpackUI *d, iPaint *p) {
322 /* Draw a background box. */
323 fillRect_Paint(p, d->bounds, uiBackground_ColorId);
324 drawRect_Paint(p, d->bounds, uiSeparator_ColorId);
325 iFontpackMediaInfo info;
326 fontpackInfo_Media(d->media, (iMediaId){ fontpack_MediaType, d->mediaId }, &info);
327 iInt2 pos = topLeft_Rect(d->bounds);
328 const char *checks[] = { "\u2610", "\u2611" };
329 draw_Text(uiContentBold_FontId, pos, uiHeading_ColorId, "\"%s\" v%d",
330 cstr_String(info.packId.id), info.packId.version);
331 pos.y += lineHeight_Text(uiContentBold_FontId);
332 draw_Text(uiLabelBold_FontId, pos, uiText_ColorId, "%.1f MB, %d fonts %s %s %s",
333 info.sizeInBytes / 1.0e6, size_StringList(info.names),
334// checks[info.isValid], info.isValid ? "No errors" : "Errors detected",
335 checks[info.isInstalled], info.isInstalled ? "Installed" : "Not installed",
336 info.isReadOnly ? "Read-Only" : "");
337 pos.y += lineHeight_Text(uiLabelBold_FontId);
338 iConstForEach(StringList, i, info.names) {
339 drawRange_Text(uiLabel_FontId, pos, uiText_ColorId, range_String(i.value));
340 pos.y += lineHeight_Text(uiLabel_FontId);
341 }
342 /* Buttons. */
343 drawInlineButton_(p, d->installRect,
344 "${media.fontpack.install}", uiLabel_FontId);
345}
diff --git a/src/ui/mediaui.h b/src/ui/mediaui.h
index e79dedc0..73de1994 100644
--- a/src/ui/mediaui.h
+++ b/src/ui/mediaui.h
@@ -51,11 +51,27 @@ iDeclareType(Media)
51iDeclareType(DownloadUI) 51iDeclareType(DownloadUI)
52 52
53struct Impl_DownloadUI { 53struct Impl_DownloadUI {
54 const iDocumentWidget *doc; 54 const iMedia *media;
55 uint16_t mediaId; 55 uint16_t mediaId;
56 iRect bounds; 56 iRect bounds;
57}; 57};
58 58
59void init_DownloadUI (iDownloadUI *, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds); 59void init_DownloadUI (iDownloadUI *, const iMedia *media, uint16_t mediaId, iRect bounds);
60iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); 60iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev);
61void draw_DownloadUI (const iDownloadUI *, iPaint *p); 61void draw_DownloadUI (const iDownloadUI *, iPaint *p);
62
63/*----------------------------------------------------------------------------------------------*/
64
65iDeclareType(FontpackUI)
66
67struct Impl_FontpackUI {
68 const iMedia *media;
69 uint16_t mediaId;
70 iRect bounds;
71 iRect installRect;
72};
73
74void init_FontpackUI (iFontpackUI *, const iMedia *media, uint16_t mediaId, iRect bounds);
75int height_FontpackUI (const iMedia *media, uint16_t mediaId, int width);
76iBool processEvent_FontpackUI (iFontpackUI *, const SDL_Event *ev);
77void draw_FontpackUI (const iFontpackUI *, iPaint *p);
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 3cb6e631..f11769f5 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -48,11 +48,11 @@ static iBool isSideBySideLayout_(void) {
48} 48}
49 49
50static enum iFontId labelFont_(void) { 50static enum iFontId labelFont_(void) {
51 return deviceType_App() == phone_AppDeviceType ? defaultBig_FontId : defaultMedium_FontId; 51 return deviceType_App() == phone_AppDeviceType ? uiLabelBig_FontId : uiLabelMedium_FontId;
52} 52}
53 53
54static enum iFontId labelBoldFont_(void) { 54static enum iFontId labelBoldFont_(void) {
55 return deviceType_App() == phone_AppDeviceType ? defaultBigBold_FontId : defaultMediumBold_FontId; 55 return deviceType_App() == phone_AppDeviceType ? uiLabelBigBold_FontId : uiLabelMediumBold_FontId;
56} 56}
57 57
58static void updatePanelSheetMetrics_(iWidget *sheet) { 58static void updatePanelSheetMetrics_(iWidget *sheet) {
@@ -546,7 +546,7 @@ void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) {
546 updateSize_LabelWidget(button); 546 updateSize_LabelWidget(button);
547 } 547 }
548 setId_Widget(as_Widget(button), radId); 548 setId_Widget(as_Widget(button), radId);
549 setFont_LabelWidget(button, defaultMedium_FontId); 549 setFont_LabelWidget(button, uiLabelMedium_FontId);
550 addChildFlags_Widget(widget, iClob(button), flags); 550 addChildFlags_Widget(widget, iClob(button), flags);
551 } 551 }
552 } 552 }
@@ -1056,7 +1056,7 @@ void initPanels_Mobile(iWidget *panels, iWidget *parentWidget,
1056 iForEach(ObjectList, sub, children_Widget(value)) { 1056 iForEach(ObjectList, sub, children_Widget(value)) {
1057 if (isInstance_Object(sub.object, &Class_LabelWidget)) { 1057 if (isInstance_Object(sub.object, &Class_LabelWidget)) {
1058 iLabelWidget *opt = sub.object; 1058 iLabelWidget *opt = sub.object;
1059 setFont_LabelWidget(opt, defaultMedium_FontId); 1059 setFont_LabelWidget(opt, uiLabelMedium_FontId);
1060 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); 1060 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue);
1061 } 1061 }
1062 } 1062 }
diff --git a/src/ui/root.c b/src/ui/root.c
index 63cce62d..f6d6f11d 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -447,7 +447,7 @@ static void updateNavBarIdentity_(iWidget *navBar) {
447 if (toolName) { 447 if (toolName) {
448 setOutline_LabelWidget(toolButton, ident == NULL); 448 setOutline_LabelWidget(toolButton, ident == NULL);
449 updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); 449 updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : "");
450 setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId); 450 setFont_LabelWidget(toolButton, subjectName ? uiLabelMedium_FontId : uiLabelLarge_FontId);
451 arrange_Widget(parent_Widget(toolButton)); 451 arrange_Widget(parent_Widget(toolButton));
452 } 452 }
453} 453}
@@ -566,7 +566,8 @@ static iBool willPerformSearchQuery_(const iString *userInput) {
566 if (isEmpty_String(clean)) { 566 if (isEmpty_String(clean)) {
567 return iFalse; 567 return iFalse;
568 } 568 }
569 return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); 569 return !isEmpty_String(&prefs_App()->strings[searchUrl_PrefsString]) &&
570 !isLikelyUrl_String(userInput);
570} 571}
571 572
572static void updateUrlInputContentPadding_(iWidget *navBar) { 573static void updateUrlInputContentPadding_(iWidget *navBar) {
@@ -996,7 +997,7 @@ void updateMetrics_Root(iRoot *d) {
996 const iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); 997 const iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
997 const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view"); 998 const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view");
998 const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident"); 999 const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident");
999 const int font = defaultTiny_FontId; 1000 const int font = uiLabelTiny_FontId;
1000 setFont_LabelWidget(idName, font); 1001 setFont_LabelWidget(idName, font);
1001 setPos_Widget(as_Widget(idName), 1002 setPos_Widget(as_Widget(idName),
1002 windowToLocal_Widget(as_Widget(idName), 1003 windowToLocal_Widget(as_Widget(idName),
@@ -1125,7 +1126,7 @@ void createUserInterface_Root(iRoot *d) {
1125 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")), 1126 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")),
1126 embedFlags | moveToParentLeftEdge_WidgetFlag); 1127 embedFlags | moveToParentLeftEdge_WidgetFlag);
1127 setId_Widget(as_Widget(lock), "navbar.lock"); 1128 setId_Widget(as_Widget(lock), "navbar.lock");
1128 setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); 1129// setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize);
1129 updateTextCStr_LabelWidget(lock, "\U0001f512"); 1130 updateTextCStr_LabelWidget(lock, "\U0001f512");
1130 } 1131 }
1131 /* Button for clearing the URL bar contents. */ { 1132 /* Button for clearing the URL bar contents. */ {
@@ -1134,7 +1135,8 @@ void createUserInterface_Root(iRoot *d) {
1134 iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")), 1135 iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")),
1135 hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag | tight_WidgetFlag); 1136 hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag | tight_WidgetFlag);
1136 setId_Widget(as_Widget(clear), "navbar.clear"); 1137 setId_Widget(as_Widget(clear), "navbar.clear");
1137 setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize); 1138// setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize);
1139 setFont_LabelWidget(clear, uiLabelSymbols_FontId);
1138// setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse); 1140// setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse);
1139// setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId); 1141// setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId);
1140 } 1142 }
@@ -1149,7 +1151,7 @@ void createUserInterface_Root(iRoot *d) {
1149 iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL); 1151 iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL);
1150 setId_Widget(as_Widget(queryInd), "input.indicator.search"); 1152 setId_Widget(as_Widget(queryInd), "input.indicator.search");
1151 setTextColor_LabelWidget(queryInd, uiTextAction_ColorId); 1153 setTextColor_LabelWidget(queryInd, uiTextAction_ColorId);
1152 setFont_LabelWidget(queryInd, defaultSmall_FontId); 1154 setFont_LabelWidget(queryInd, uiLabelSmall_FontId);
1153 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); 1155 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId);
1154 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); 1156 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId);
1155// setAlignVisually_LabelWidget(queryInd, iTrue); 1157// setAlignVisually_LabelWidget(queryInd, iTrue);
@@ -1162,7 +1164,7 @@ void createUserInterface_Root(iRoot *d) {
1162 iLabelWidget *fprog = new_LabelWidget("", NULL); 1164 iLabelWidget *fprog = new_LabelWidget("", NULL);
1163 setId_Widget(as_Widget(fprog), "feeds.progress"); 1165 setId_Widget(as_Widget(fprog), "feeds.progress");
1164 setTextColor_LabelWidget(fprog, uiTextCaution_ColorId); 1166 setTextColor_LabelWidget(fprog, uiTextCaution_ColorId);
1165 setFont_LabelWidget(fprog, defaultSmall_FontId); 1167 setFont_LabelWidget(fprog, uiLabelSmall_FontId);
1166 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); 1168 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId);
1167// setAlignVisually_LabelWidget(fprog, iTrue); 1169// setAlignVisually_LabelWidget(fprog, iTrue);
1168 setNoAutoMinHeight_LabelWidget(fprog, iTrue); 1170 setNoAutoMinHeight_LabelWidget(fprog, iTrue);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 057ff614..42661f6b 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -188,7 +188,7 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha
188 // extraPadding_WidgetFlag : 0) | 188 // extraPadding_WidgetFlag : 0) |
189 flags); 189 flags);
190 setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide 190 setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide
191 ? defaultBig_FontId 191 ? uiLabelBig_FontId
192 : d->buttonFont); 192 : d->buttonFont);
193 checkIcon_LabelWidget(btn); 193 checkIcon_LabelWidget(btn);
194 return btn; 194 return btn;
@@ -742,7 +742,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
742 d->itemFonts[1] = uiContentBold_FontId; 742 d->itemFonts[1] = uiContentBold_FontId;
743#if defined (iPlatformMobile) 743#if defined (iPlatformMobile)
744 if (deviceType_App() == phone_AppDeviceType) { 744 if (deviceType_App() == phone_AppDeviceType) {
745 d->itemFonts[0] = defaultBig_FontId; 745 d->itemFonts[0] = uiLabelBig_FontId;
746 d->itemFonts[1] = defaultBigBold_FontId; 746 d->itemFonts[1] = defaultBigBold_FontId;
747 } 747 }
748 d->widthAsGaps = 73.0f; 748 d->widthAsGaps = 73.0f;
@@ -774,7 +774,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
774 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))), 774 format_CStr("%s.mode arg:%d", cstr_String(id_Widget(w)), i))),
775 frameless_WidgetFlag | noBackground_WidgetFlag); 775 frameless_WidgetFlag | noBackground_WidgetFlag);
776 } 776 }
777 setButtonFont_SidebarWidget(d, isPhone ? defaultBig_FontId : uiLabel_FontId); 777 setButtonFont_SidebarWidget(d, isPhone ? uiLabelBig_FontId : uiLabel_FontId);
778 addChildFlags_Widget(vdiv, 778 addChildFlags_Widget(vdiv,
779 iClob(buttons), 779 iClob(buttons),
780 arrangeHorizontal_WidgetFlag | 780 arrangeHorizontal_WidgetFlag |
@@ -936,8 +936,8 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
936 if (deviceType_App() == phone_AppDeviceType) { 936 if (deviceType_App() == phone_AppDeviceType) {
937 /* Change font size depending on orientation. */ 937 /* Change font size depending on orientation. */
938 const int fonts[2] = { 938 const int fonts[2] = {
939 isPortrait_App() ? defaultBig_FontId : uiContent_FontId, 939 isPortrait_App() ? uiLabelBig_FontId : uiContent_FontId,
940 isPortrait_App() ? defaultBigBold_FontId : uiContentBold_FontId 940 isPortrait_App() ? uiLabelBigBold_FontId : uiContentBold_FontId
941 }; 941 };
942 if (d->itemFonts[0] != fonts[0]) { 942 if (d->itemFonts[0] != fonts[0]) {
943 d->itemFonts[0] = fonts[0]; 943 d->itemFonts[0] = fonts[0];
@@ -945,7 +945,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
945// updateMetrics_SidebarWidget_(d); 945// updateMetrics_SidebarWidget_(d);
946 updateItemHeight_SidebarWidget_(d); 946 updateItemHeight_SidebarWidget_(d);
947 } 947 }
948 setButtonFont_SidebarWidget(d, isPortrait_App() ? defaultBig_FontId : uiLabel_FontId); 948 setButtonFont_SidebarWidget(d, isPortrait_App() ? uiLabelBig_FontId : uiLabel_FontId);
949 } 949 }
950 const iBool isTight = 950 const iBool isTight =
951 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); 951 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth);
@@ -1962,7 +1962,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1962 deinit_String(&str); 1962 deinit_String(&str);
1963 const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2); 1963 const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2);
1964 drawRange_Text(font, textPos, fg, range_String(&d->label)); 1964 drawRange_Text(font, textPos, fg, range_String(&d->label));
1965 const int metaFont = default_FontId; 1965 const int metaFont = uiLabel_FontId;
1966 const int metaIconWidth = 4.5f * gap_UI; 1966 const int metaIconWidth = 4.5f * gap_UI;
1967 const iInt2 metaPos = 1967 const iInt2 metaPos =
1968 init_I2(right_Rect(itemRect) - 1968 init_I2(right_Rect(itemRect) -
@@ -2045,7 +2045,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2045 const int indent = 1.4f * lineHeight_Text(font); 2045 const int indent = 1.4f * lineHeight_Text(font);
2046 addv_I2(&cPos, 2046 addv_I2(&cPos,
2047 init_I2(3 * gap_UI, 2047 init_I2(3 * gap_UI,
2048 (itemHeight - lineHeight_Text(default_FontId) * 2 - lineHeight_Text(font)) / 2048 (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) /
2049 2)); 2049 2));
2050 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId 2050 const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId
2051 : uiTextFramelessHover_ColorId) 2051 : uiTextFramelessHover_ColorId)
@@ -2064,7 +2064,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2064 add_I2(cPos, init_I2(indent, 0)), 2064 add_I2(cPos, init_I2(indent, 0)),
2065 fg, 2065 fg,
2066 range_String(&d->label)); 2066 range_String(&d->label));
2067 drawRange_Text(default_FontId, 2067 drawRange_Text(uiLabel_FontId,
2068 add_I2(cPos, init_I2(indent, lineHeight_Text(font))), 2068 add_I2(cPos, init_I2(indent, lineHeight_Text(font))),
2069 metaFg, 2069 metaFg,
2070 range_String(&d->meta)); 2070 range_String(&d->meta));
diff --git a/src/ui/text.c b/src/ui/text.c
index cbce4715..3ed5b327 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -123,40 +123,60 @@ iDefineTypeConstructionArgs(Glyph, (iChar ch), ch)
123 123
124static iGlyph *glyph_Font_(iFont *d, iChar ch); 124static iGlyph *glyph_Font_(iFont *d, iChar ch);
125 125
126struct Impl_Font { 126iDeclareType(GlyphTable)
127 iBlock * data; 127
128 enum iTextFont family; 128struct Impl_GlyphTable {
129 stbtt_fontinfo font; 129 iHash glyphs; /* key is glyph index in the font */
130 float xScale, yScale; 130 /* TODO: `glyphs` does not need to be a Hash.
131 int vertOffset; /* offset due to scaling */ 131 We could lazily allocate an array with glyphCount elements instead. */
132 int height;
133 int baseline;
134 iHash glyphs; /* key is glyph index in the font */ /* TODO: does not need to be a Hash */
135 iBool isMonospaced;
136 float emAdvance;
137 enum iFontSize sizeId; /* used to look up different fonts of matching size */
138 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ 132 uint32_t indexTable[128 - 32]; /* quick ASCII lookup */
139#if defined (LAGRANGE_ENABLE_HARFBUZZ)
140 hb_blob_t * hbBlob; /* raw TrueType data */
141 hb_face_t * hbFace;
142 hb_font_t * hbMainFont;
143 hb_font_t * hbFont; /* may be a sub-font with customized font metrics */
144#endif
145}; 133};
146 134
147static iFont *font_Text_(enum iFontId id); 135static void clearGlyphs_GlyphTable_(iGlyphTable *d) {
148 136 if (d) {
149#if 0 137 iForEach(Hash, i, &d->glyphs) {
150static hb_position_t hbGlyphHKernForNunito_(hb_font_t *font, void *fontData, 138 delete_Glyph((iGlyph *) i.value);
151 hb_codepoint_t firstGlyph, hb_codepoint_t secondGlyph, 139 }
152 void *userData) { 140 clear_Hash(&d->glyphs);
153 return 100; 141 }
154} 142}
155#endif
156 143
157static void init_Font(iFont *d, const iBlock *data, int height, float scale, 144static void init_GlyphTable(iGlyphTable *d) {
158 enum iFontSize sizeId, iBool isMonospaced) {
159 init_Hash(&d->glyphs); 145 init_Hash(&d->glyphs);
146 memset(d->indexTable, 0xff, sizeof(d->indexTable));
147}
148
149static void deinit_GlyphTable(iGlyphTable *d) {
150 clearGlyphs_GlyphTable_(d);
151 deinit_Hash(&d->glyphs);
152}
153
154iDefineTypeConstruction(GlyphTable)
155
156struct Impl_Font {
157 const iFontSpec *fontSpec;
158 const iFontFile *fontFile;
159 int height;
160 int baseline;
161 int vertOffset; /* offset due to glyph scaling */
162 float xScale, yScale;
163 float emAdvance;
164 iGlyphTable * table;
165};
166
167iLocalDef iBool isMonospaced_Font(const iFont *d) {
168 return (d->fontSpec->flags & monospace_FontSpecFlag) != 0;
169}
170
171static iFont *font_Text_(enum iFontId id);
172
173static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *fontFile,
174 enum iFontSize sizeId, float height) {
175 const int scaleType = scaleType_FontSpec(sizeId);
176 d->fontSpec = fontSpec;
177 d->fontFile = fontFile;
178 /* TODO: Nunito kerning fixes need to be a font parameter of its own. */
179#if 0
160 d->data = NULL; 180 d->data = NULL;
161 d->family = undefined_TextFont; 181 d->family = undefined_TextFont;
162 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */ 182 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */
@@ -177,95 +197,44 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
177 data == &fontSmolEmojiRegular_Embedded) { 197 data == &fontSmolEmojiRegular_Embedded) {
178 d->family = emojiAndSymbols_TextFont; 198 d->family = emojiAndSymbols_TextFont;
179 } 199 }
180 d->isMonospaced = isMonospaced; 200#endif
181 d->height = height; 201 d->height = (int) (height * fontSpec->heightScale[scaleType]);
182 iZap(d->font); 202 const float glyphScale = fontSpec->glyphScale[scaleType];
183 stbtt_InitFont(&d->font, constData_Block(data), 0); 203 d->xScale = d->yScale = scaleForPixelHeight_FontFile(fontFile, d->height) * glyphScale;
184 int ascent, descent, emAdv; 204 if (isMonospaced_Font(d)) {
185 stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL);
186 stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL);
187 d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale;
188 if (d->isMonospaced) {
189 /* It is important that monospaced fonts align 1:1 with the pixel grid so that 205 /* It is important that monospaced fonts align 1:1 with the pixel grid so that
190 box-drawing characters don't have partially occupied edge pixels, leading to seams 206 box-drawing characters don't have partially occupied edge pixels, leading to seams
191 between adjacent glyphs. */ 207 between adjacent glyphs. */
192 const float advance = (float) emAdv * d->xScale; 208 const float advance = (float) fontFile->emAdvance * d->xScale;
193 if (advance > 4) { /* not too tiny */ 209 if (advance > 4) { /* not too tiny */
194 d->xScale *= floorf(advance) / advance; 210 d->xScale *= floorf(advance) / advance;
195 } 211 }
196 } 212 }
197 d->emAdvance = emAdv * d->xScale; 213 d->emAdvance = fontFile->emAdvance * d->xScale;
198 d->baseline = ascent * d->yScale; 214 d->baseline = fontFile->ascent * d->yScale;
199 d->vertOffset = height * (1.0f - scale) / 2; 215 d->vertOffset = d->height * (1.0f - glyphScale) / 2 * fontSpec->vertOffsetScale[scaleType];
200 /* Custom tweaks. */ 216 d->table = NULL;
201 if (data == &fontNotoSansSymbolsRegular_Embedded) { 217 // printf("{%s} height:%d baseline:%d\n", cstr_String(&d->fontSpec->id), d->height, d->baseline);
202 d->vertOffset *= 1.2f;
203 }
204 else if (data == &fontNotoSansSymbols2Regular_Embedded) {
205 d->vertOffset /= 2;
206 }
207 else if (data == &fontNotoEmojiRegular_Embedded) {
208 //d->vertOffset -= height / 30;
209 }
210 d->sizeId = sizeId;
211 memset(d->indexTable, 0xff, sizeof(d->indexTable));
212#if defined(LAGRANGE_ENABLE_HARFBUZZ)
213 /* HarfBuzz will read the font data. */ {
214 d->hbBlob = hb_blob_create(constData_Block(data), size_Block(data),
215 HB_MEMORY_MODE_READONLY, NULL, NULL);
216 d->hbFace = hb_face_create(d->hbBlob, 0);
217 d->hbMainFont = hb_font_create(d->hbFace);
218#if 0
219 /* TODO: The custom kerning function doesn't get called?
220 Maybe HarfBuzz needs FreeType to do kerning? */
221 if (d->family == nunito_TextFont) {
222 /* Customize the kerning of Nunito. */
223 d->hbFont = hb_font_create_sub_font(d->hbMainFont);
224 hb_font_funcs_t *ffs = hb_font_funcs_create();
225 hb_font_funcs_set_glyph_h_kerning_func(ffs, hbGlyphHKernForNunito_, d, NULL);
226 hb_font_set_funcs(d->hbFont, ffs, NULL, NULL);
227 hb_font_funcs_destroy(ffs);
228 }
229 else
230#endif
231 {
232 d->hbFont = hb_font_reference(d->hbMainFont);
233 }
234 }
235#endif
236}
237
238static void clearGlyphs_Font_(iFont *d) {
239 iForEach(Hash, i, &d->glyphs) {
240 delete_Glyph((iGlyph *) i.value);
241 }
242 clear_Hash(&d->glyphs);
243} 218}
244 219
245static void deinit_Font(iFont *d) { 220static void deinit_Font(iFont *d) {
246#if defined(LAGRANGE_ENABLE_HARFBUZZ) 221 delete_GlyphTable(d->table);
247 /* HarfBuzz objects. */ {
248 hb_font_destroy(d->hbFont);
249 hb_font_destroy(d->hbMainFont);
250 hb_face_destroy(d->hbFace);
251 hb_blob_destroy(d->hbBlob);
252 }
253#endif
254 clearGlyphs_Font_(d);
255 deinit_Hash(&d->glyphs);
256 delete_Block(d->data);
257} 222}
258 223
259static uint32_t glyphIndex_Font_(iFont *d, iChar ch) { 224static uint32_t glyphIndex_Font_(iFont *d, iChar ch) {
260 /* TODO: Add a small cache of ~5 most recently found indices. */ 225 /* TODO: Add a small cache of ~5 most recently found indices. */
261 const size_t entry = ch - 32; 226 const size_t entry = ch - 32;
262 if (entry < iElemCount(d->indexTable)) { 227 if (!d->table) {
263 if (d->indexTable[entry] == ~0u) { 228 d->table = new_GlyphTable();
264 d->indexTable[entry] = stbtt_FindGlyphIndex(&d->font, ch); 229 }
230 iGlyphTable *table = d->table;
231 if (entry < iElemCount(table->indexTable)) {
232 if (table->indexTable[entry] == ~0u) {
233 table->indexTable[entry] = findGlyphIndex_FontFile(d->fontFile, ch);
265 } 234 }
266 return d->indexTable[entry]; 235 return table->indexTable[entry];
267 } 236 }
268 return stbtt_FindGlyphIndex(&d->font, ch); 237 return findGlyphIndex_FontFile(d->fontFile, ch);
269} 238}
270 239
271/*----------------------------------------------------------------------------------------------*/ 240/*----------------------------------------------------------------------------------------------*/
@@ -279,10 +248,11 @@ struct Impl_CacheRow {
279}; 248};
280 249
281struct Impl_Text { 250struct Impl_Text {
282 enum iTextFont contentFont; 251// enum iTextFont contentFont;
283 enum iTextFont headingFont; 252// enum iTextFont headingFont;
284 float contentFontSize; 253 float contentFontSize;
285 iFont fonts[max_FontId]; 254 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
255 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
286 SDL_Renderer * render; 256 SDL_Renderer * render;
287 SDL_Texture * cache; 257 SDL_Texture * cache;
288 iInt2 cacheSize; 258 iInt2 cacheSize;
@@ -296,12 +266,55 @@ struct Impl_Text {
296iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 266iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
297 267
298static iText *activeText_; 268static iText *activeText_;
299static iBlock *userFont_; 269
270static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId) {
271#if defined (iPlatformMobile)
272 const float uiSize = fontSize_UI * 1.1f;
273#else
274 const float uiSize = fontSize_UI;
275#endif
276 const float textSize = fontSize_UI * d->contentFontSize;
277// const float monoSize = textSize * 0.71f;
278// const float smallMonoSize = monoSize * 0.8f;
279 if (spec->flags & override_FontSpecFlag && d->overrideFontId < 0) {
280 /* This is the highest priority override font. */
281 d->overrideFontId = baseId;
282 }
283 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
284 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
285 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)),
286 spec,
287 spec->styles[style],
288 sizeId,
289 (sizeId < contentRegular_FontSize ? uiSize : textSize) *
290 scale_FontSize(sizeId));
291 }
292 }
293}
294
295iLocalDef iFont *font_Text_(enum iFontId id) {
296 return at_Array(&activeText_->fonts, id & mask_FontId);
297}
298
299static enum iFontId fontId_Text_(const iFont *font) {
300 return (enum iFontId) (font - (const iFont *) constData_Array(&activeText_->fonts));
301}
302
303iLocalDef enum iFontSize sizeId_Text_(const iFont *d) {
304 return fontId_Text_(d) % max_FontSize;
305}
306
307iLocalDef enum iFontStyle styleId_Text_(const iFont *d) {
308 return (fontId_Text_(d) / max_FontSize) % max_FontStyle;
309}
310
311static const iFontSpec *tryFindSpec_(enum iPrefsString ps, const char *fallback) {
312 const iFontSpec *spec = findSpec_Fonts(cstr_String(&prefs_App()->strings[ps]));
313 return spec ? spec : findSpec_Fonts(fallback);
314}
300 315
301static void initFonts_Text_(iText *d) { 316static void initFonts_Text_(iText *d) {
302 const float textSize = fontSize_UI * d->contentFontSize; 317#if 0
303 const float monoSize = textSize * 0.71f;
304 const float smallMonoSize = monoSize * 0.8f;
305 const iBlock *regularFont = &fontNunitoRegular_Embedded; 318 const iBlock *regularFont = &fontNunitoRegular_Embedded;
306 const iBlock *boldFont = &fontNunitoBold_Embedded; 319 const iBlock *boldFont = &fontNunitoBold_Embedded;
307 const iBlock *italicFont = &fontNunitoLightItalic_Embedded; 320 const iBlock *italicFont = &fontNunitoLightItalic_Embedded;
@@ -367,15 +380,10 @@ static void initFonts_Text_(iText *d) {
367 h12Font = &fontIosevkaTermExtended_Embedded; 380 h12Font = &fontIosevkaTermExtended_Embedded;
368 h3Font = &fontIosevkaTermExtended_Embedded; 381 h3Font = &fontIosevkaTermExtended_Embedded;
369 } 382 }
370#if defined (iPlatformMobile)
371 const float uiSize = fontSize_UI * 1.1f;
372#else
373 const float uiSize = fontSize_UI;
374#endif
375 const struct { 383 const struct {
376 const iBlock *ttf; 384 const iFontFile *fontFile;
377 int size; 385 int size; /* pixels */
378 float scaling; 386// float scaling;
379 enum iFontSize sizeId; 387 enum iFontSize sizeId;
380 /* UI sizes: 1.0, 1.125, 1.333, 1.666 */ 388 /* UI sizes: 1.0, 1.125, 1.333, 1.666 */
381 /* Content sizes: smallmono, mono, 1.0, 1.2, 1.333, 1.666, 2.0 */ 389 /* Content sizes: smallmono, mono, 1.0, 1.2, 1.333, 1.666, 2.0 */
@@ -385,8 +393,8 @@ static void initFonts_Text_(iText *d) {
385 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 393 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
386 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize }, 394 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize },
387 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, 395 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize },
388 { &fontSourceSans3Regular_Embedded, uiSize * 0.900f, 1.0f, uiSmall_FontSize },
389 { &fontSourceSans3Semibold_Embedded, uiSize * 0.800f, 1.0f, uiTiny_FontSize }, 396 { &fontSourceSans3Semibold_Embedded, uiSize * 0.800f, 1.0f, uiTiny_FontSize },
397 { &fontSourceSans3Regular_Embedded, uiSize * 0.900f, 1.0f, uiSmall_FontSize },
390 /* UI fonts: bold weight */ 398 /* UI fonts: bold weight */
391 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize }, 399 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize },
392 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 400 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
@@ -408,12 +416,12 @@ static void initFonts_Text_(iText *d) {
408 { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize }, 416 { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize },
409 /* symbols and scripts */ 417 /* symbols and scripts */
410#define DEFINE_FONT_SET(data, glyphScale) \ 418#define DEFINE_FONT_SET(data, glyphScale) \
419 { (data), uiSize * 0.800f, glyphScale, uiTiny_FontSize }, \
420 { (data), uiSize * 0.900f, glyphScale, uiSmall_FontSize }, \
411 { (data), uiSize, glyphScale, uiNormal_FontSize }, \ 421 { (data), uiSize, glyphScale, uiNormal_FontSize }, \
412 { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \ 422 { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \
413 { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \ 423 { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \
414 { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \ 424 { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \
415 { (data), uiSize * 0.900f, glyphScale, uiSmall_FontSize }, \
416 { (data), uiSize * 0.800f, glyphScale, uiTiny_FontSize }, \
417 { (data), textSize, glyphScale, contentRegular_FontSize }, \ 425 { (data), textSize, glyphScale, contentRegular_FontSize }, \
418 { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \ 426 { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \
419 { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \ 427 { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \
@@ -433,26 +441,39 @@ static void initFonts_Text_(iText *d) {
433 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f), 441 DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f),
434// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f), 442// DEFINE_FONT_SET(&fontScheherazadeNewRegular_Embedded, 1.0f),
435 }; 443 };
436 iForIndices(i, fontData) { 444#endif
437 iFont *font = &d->fonts[i]; 445 /* The `fonts` array has precomputed scaling factors and other parameters in all sizes
438 init_Font(font, 446 and styles for each available font. Indices to `fonts` act as font runtime IDs. */
439 fontData[i].ttf, 447 /* First the mandatory fonts. */
440 fontData[i].size, 448 d->overrideFontId = -1;
441 fontData[i].scaling, 449 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */
442 fontData[i].sizeId, 450 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId);
443 fontData[i].ttf == &fontIosevkaTermExtended_Embedded); 451 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId);
452 setupFontVariants_Text_(d, tryFindSpec_(headingFont_PrefsString, "default"), documentHeading_FontId);
453 setupFontVariants_Text_(d, tryFindSpec_(bodyFont_PrefsString, "default"), documentBody_FontId);
454 setupFontVariants_Text_(d, tryFindSpec_(monospaceDocumentFont_PrefsString, "iosevka-body"), documentMonospace_FontId);
455 /* Check if there are auxiliary fonts available and set those up, too. */
456 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
457 const iFontSpec *spec = s.ptr;
458 if (spec->flags & auxiliary_FontSpecFlag) {
459 const int fontId = size_Array(&d->fonts);
460 resize_Array(&d->fonts, fontId + maxVariants_Fonts);
461 setupFontVariants_Text_(d, spec, fontId);
462 }
444 } 463 }
445 gap_Text = iRound(gap_UI * d->contentFontSize); 464 gap_Text = iRound(gap_UI * d->contentFontSize);
446} 465}
447 466
448static void deinitFonts_Text_(iText *d) { 467static void deinitFonts_Text_(iText *d) {
449 iForIndices(i, d->fonts) { 468 iForEach(Array, i, &d->fonts) {
450 deinit_Font(&d->fonts[i]); 469 deinit_Font(i.value);
451 } 470 }
471 clear_Array(&d->fonts);
452} 472}
453 473
454static int maxGlyphHeight_Text_(const iText *d) { 474static int maxGlyphHeight_Text_(const iText *d) {
455 return 2 * d->contentFontSize * fontSize_UI; 475 /* Huge size is 2 * contentFontSize. */
476 return 4 * d->contentFontSize * fontSize_UI;
456} 477}
457 478
458static void initCache_Text_(iText *d) { 479static void initCache_Text_(iText *d) {
@@ -471,7 +492,7 @@ static void initCache_Text_(iText *d) {
471 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache 492 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache
472 once at least one glyph is stored. */ 493 once at least one glyph is stored. */
473 for (int h = d->cacheRowAllocStep; 494 for (int h = d->cacheRowAllocStep;
474 h <= 2.5 * textSize + d->cacheRowAllocStep; 495 h <= 5 * textSize + d->cacheRowAllocStep;
475 h += d->cacheRowAllocStep) { 496 h += d->cacheRowAllocStep) {
476 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 }); 497 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 });
477 } 498 }
@@ -490,6 +511,7 @@ static void deinitCache_Text_(iText *d) {
490 SDL_DestroyTexture(d->cache); 511 SDL_DestroyTexture(d->cache);
491} 512}
492 513
514#if 0
493void loadUserFonts_Text(void) { 515void loadUserFonts_Text(void) {
494 if (userFont_) { 516 if (userFont_) {
495 delete_Block(userFont_); 517 delete_Block(userFont_);
@@ -508,11 +530,14 @@ void loadUserFonts_Text(void) {
508 iRelease(f); 530 iRelease(f);
509 } 531 }
510} 532}
533#endif
511 534
512void init_Text(iText *d, SDL_Renderer *render) { 535void init_Text(iText *d, SDL_Renderer *render) {
513 loadUserFonts_Text(); 536 iText *oldActive = activeText_;
514 d->contentFont = nunito_TextFont; 537 activeText_ = d;
515 d->headingFont = nunito_TextFont; 538 init_Array(&d->fonts, sizeof(iFont));
539// d->contentFont = nunito_TextFont;
540// d->headingFont = nunito_TextFont;
516 d->contentFontSize = contentScale_Text_; 541 d->contentFontSize = contentScale_Text_;
517 d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0); 542 d->ansiEscape = new_RegExp("[[()]([0-9;AB]*)m", 0);
518 d->render = render; 543 d->render = render;
@@ -526,6 +551,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
526 } 551 }
527 initCache_Text_(d); 552 initCache_Text_(d);
528 initFonts_Text_(d); 553 initFonts_Text_(d);
554 activeText_ = oldActive;
529} 555}
530 556
531void deinit_Text(iText *d) { 557void deinit_Text(iText *d) {
@@ -534,6 +560,7 @@ void deinit_Text(iText *d) {
534 deinitCache_Text_(d); 560 deinitCache_Text_(d);
535 d->render = NULL; 561 d->render = NULL;
536 iRelease(d->ansiEscape); 562 iRelease(d->ansiEscape);
563 deinit_Array(&d->fonts);
537} 564}
538 565
539void setCurrent_Text(iText *d) { 566void setCurrent_Text(iText *d) {
@@ -544,21 +571,15 @@ void setOpacity_Text(float opacity) {
544 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 571 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
545} 572}
546 573
547void setContentFont_Text(iText *d, enum iTextFont font) { 574//void setFont_Text(iText *d, int fontId, const char *fontSpecId) {
548 if (d->contentFont != font) { 575// setupFontVariants_Text_(d, findSpec_Fonts(fontSpecId), fontId);
549 d->contentFont = font; 576// if (d->contentFont != font) {
550 resetFonts_Text(d); 577// d->contentFont = font;
551 } 578// resetFonts_Text(d);
552} 579// }
580//}
553 581
554void setHeadingFont_Text(iText *d, enum iTextFont font) { 582void setDocumentFontSize_Text(iText *d, float fontSizeFactor) {
555 if (d->headingFont != font) {
556 d->headingFont = font;
557 resetFonts_Text(d);
558 }
559}
560
561void setContentFontSize_Text(iText *d, float fontSizeFactor) {
562 fontSizeFactor *= contentScale_Text_; 583 fontSizeFactor *= contentScale_Text_;
563 iAssert(fontSizeFactor > 0); 584 iAssert(fontSizeFactor > 0);
564 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) { 585 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {
@@ -569,8 +590,8 @@ void setContentFontSize_Text(iText *d, float fontSizeFactor) {
569 590
570static void resetCache_Text_(iText *d) { 591static void resetCache_Text_(iText *d) {
571 deinitCache_Text_(d); 592 deinitCache_Text_(d);
572 for (int i = 0; i < max_FontId; i++) { 593 iForEach(Array, i, &d->fonts) {
573 clearGlyphs_Font_(&d->fonts[i]); 594 clearGlyphs_GlyphTable_(((iFont *) i.value)->table);
574 } 595 }
575 initCache_Text_(d); 596 initCache_Text_(d);
576} 597}
@@ -582,14 +603,10 @@ void resetFonts_Text(iText *d) {
582 initFonts_Text_(d); 603 initFonts_Text_(d);
583} 604}
584 605
585iLocalDef iFont *font_Text_(enum iFontId id) {
586 return &activeText_->fonts[id & mask_FontId];
587}
588
589static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { 606static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {
590 int w, h; 607 int w, h;
591 uint8_t *bmp = stbtt_GetGlyphBitmapSubpixel( 608 uint8_t *bmp = rasterizeGlyph_FontFile(d->fontFile, d->xScale, d->yScale, xShift, glyphIndex,
592 &d->font, d->xScale, d->yScale, xShift, 0.0f, glyphIndex, &w, &h, 0, 0); 609 &w, &h);
593 SDL_Surface *surface8 = 610 SDL_Surface *surface8 =
594 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); 611 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
595 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); 612 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);
@@ -636,8 +653,8 @@ static iInt2 assignCachePos_Text_(iText *d, iInt2 size) {
636static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { 653static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
637 iRect *glRect = &glyph->rect[hoff]; 654 iRect *glRect = &glyph->rect[hoff];
638 int x0, y0, x1, y1; 655 int x0, y0, x1, y1;
639 stbtt_GetGlyphBitmapBoxSubpixel( 656 measureGlyph_FontFile(d->fontFile, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f,
640 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); 657 &x0, &y0, &x1, &y1);
641 glRect->size = init_I2(x1 - x0, y1 - y0); 658 glRect->size = init_I2(x1 - x0, y1 - y0);
642 /* Determine placement in the glyph cache texture, advancing in rows. */ 659 /* Determine placement in the glyph cache texture, advancing in rows. */
643 glRect->pos = assignCachePos_Text_(activeText_, glRect->size); 660 glRect->pos = assignCachePos_Text_(activeText_, glRect->size);
@@ -645,7 +662,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
645 glyph->d[hoff].y += d->vertOffset; 662 glyph->d[hoff].y += d->vertOffset;
646 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ 663 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
647 int adv; 664 int adv;
648 stbtt_GetGlyphHMetrics(&d->font, index_Glyph_(glyph), &adv, NULL); 665 stbtt_GetGlyphHMetrics(&d->fontFile->stbInfo, index_Glyph_(glyph), &adv, NULL);
649 glyph->advance = d->xScale * adv; 666 glyph->advance = d->xScale * adv;
650 } 667 }
651} 668}
@@ -654,13 +671,18 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
654 if (isVariationSelector_Char(ch)) { 671 if (isVariationSelector_Char(ch)) {
655 return d; 672 return d;
656 } 673 }
657 /* Smol Emoji overrides all other fonts. */ 674 const enum iFontStyle styleId = styleId_Text_(d);
658 if (ch != 0x20) { 675 const enum iFontSize sizeId = sizeId_Text_(d);
659 iFont *smol = font_Text_(smolEmoji_FontId + d->sizeId); 676 iFont *overrideFont = NULL;
660 if (smol != d && (*glyphIndex = glyphIndex_Font_(smol, ch)) != 0) { 677 if (ch != 0x20 && activeText_->overrideFontId >= 0) {
661 return smol; 678 /* Override font is checked first. */
679 overrideFont = font_Text_(FONT_ID(activeText_->overrideFontId, styleId, sizeId));
680 if (overrideFont != d && (*glyphIndex = glyphIndex_Font_(overrideFont, ch)) != 0) {
681 return overrideFont;
662 } 682 }
663 } 683 }
684#if 0
685 /* TODO: Put arrows in Smol Emoji. */
664 /* Manual exceptions. */ { 686 /* Manual exceptions. */ {
665 if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) { 687 if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) {
666 d = font_Text_(iosevka_FontId + d->sizeId); 688 d = font_Text_(iosevka_FontId + d->sizeId);
@@ -668,9 +690,33 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
668 return d; 690 return d;
669 } 691 }
670 } 692 }
693#endif
694 /* The font's own version of the glyph. */
671 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 695 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
672 return d; 696 return d;
673 } 697 }
698 /* As a fallback, check all other available fonts of this size. */
699 for (int aux = 0; aux < 2; aux++) {
700 for (iFont *font = font_Text_(FONT_ID(0, styleId, sizeId));
701 font < (iFont *) end_Array(&activeText_->fonts);
702 font += maxVariants_Fonts) {
703 const iBool isAuxiliary = (font->fontSpec->flags & auxiliary_FontSpecFlag) ? 1 : 0;
704 if (aux == isAuxiliary) {
705 /* First try auxiliary fonts, then other remaining fonts. */
706 continue;
707 }
708 if (font == d || font == overrideFont) {
709 continue; /* already checked this one */
710 }
711 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) {
712// printf("using %s[%f] for %lc (%x) => %d\n",
713// cstr_String(&font->fontSpec->name), font->fontSpec->scaling,
714// (int) ch, ch, glyphIndex_Font_(font, ch));
715 return font;
716 }
717 }
718 }
719#if 0
674 const int fallbacks[] = { 720 const int fallbacks[] = {
675 notoEmoji_FontId, 721 notoEmoji_FontId,
676 symbols2_FontId, 722 symbols2_FontId,
@@ -732,6 +778,7 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
732 if (d != font) { 778 if (d != font) {
733 *glyphIndex = glyphIndex_Font_(font, ch); 779 *glyphIndex = glyphIndex_Font_(font, ch);
734 } 780 }
781#endif // 0
735 if (!*glyphIndex) { 782 if (!*glyphIndex) {
736 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 783 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr);
737 } 784 }
@@ -739,8 +786,9 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
739} 786}
740 787
741static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { 788static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
789 iAssert(d->table);
742 iGlyph* glyph = NULL; 790 iGlyph* glyph = NULL;
743 void * node = value_Hash(&d->glyphs, glyphIndex); 791 void * node = value_Hash(&d->table->glyphs, glyphIndex);
744 if (node) { 792 if (node) {
745 glyph = node; 793 glyph = node;
746 } 794 }
@@ -758,7 +806,7 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
758 and updates the glyph metrics. */ 806 and updates the glyph metrics. */
759 allocate_Font_(d, glyph, 0); 807 allocate_Font_(d, glyph, 0);
760 allocate_Font_(d, glyph, 1); 808 allocate_Font_(d, glyph, 1);
761 insert_Hash(&d->glyphs, &glyph->node); 809 insert_Hash(&d->table->glyphs, &glyph->node);
762 } 810 }
763 return glyph; 811 return glyph;
764} 812}
@@ -866,10 +914,6 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
866 run->logical.start = endAt; 914 run->logical.start = endAt;
867} 915}
868 916
869static enum iFontId fontId_Text_(const iFont *font) {
870 return (enum iFontId) (font - activeText_->fonts);
871}
872
873static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 917static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
874 iAssert(isEmpty_Array(&d->runs)); 918 iAssert(isEmpty_Array(&d->runs));
875 size_t length = 0; 919 size_t length = 0;
@@ -997,14 +1041,14 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
997 continue; 1041 continue;
998 } 1042 }
999 if (ch == 0x20) { 1043 if (ch == 0x20) {
1000 if (run.font->family == emojiAndSymbols_TextFont) { 1044 if (run.font->fontSpec->flags & auxiliary_FontSpecFlag) {
1001 finishRun_AttributedText_(d, &run, pos); 1045 finishRun_AttributedText_(d, &run, pos);
1002 run.font = d->font; /* never use space from the symbols font, it's too wide */ 1046 run.font = d->font; /* never use space from the symbols font, it's too wide */
1003 } 1047 }
1004 continue; 1048 continue;
1005 } 1049 }
1006 iFont *currentFont = d->font; 1050 iFont *currentFont = d->font;
1007 if (run.font->family == arabic_TextFont && isPunct_Char(ch)) { 1051 if (run.font->fontSpec->flags & arabic_FontSpecFlag && isPunct_Char(ch)) {
1008 currentFont = run.font; /* remain as Arabic for whitespace */ 1052 currentFont = run.font; /* remain as Arabic for whitespace */
1009 } 1053 }
1010 const iGlyph *glyph = glyph_Font_(currentFont, ch); 1054 const iGlyph *glyph = glyph_Font_(currentFont, ch);
@@ -1283,7 +1327,7 @@ static iBool notify_WrapText_(iWrapText *d, const char *ending, int origin, int
1283 1327
1284float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) { 1328float horizKern_Font_(iFont *d, uint32_t glyph1, uint32_t glyph2) {
1285#if defined (LAGRANGE_ENABLE_KERNING) 1329#if defined (LAGRANGE_ENABLE_KERNING)
1286 if (!enableKerning_Text || d->family != nunito_TextFont) { 1330 if (!enableKerning_Text || ~d->fontSpec->flags & fixNunitoKerning_FontSpecFlag) {
1287 return 0.0f; 1331 return 0.0f;
1288 } 1332 }
1289 if (glyph1 && glyph2) { 1333 if (glyph1 && glyph2) {
@@ -1335,7 +1379,7 @@ static void deinit_GlyphBuffer_(iGlyphBuffer *d) {
1335 1379
1336static void shape_GlyphBuffer_(iGlyphBuffer *d) { 1380static void shape_GlyphBuffer_(iGlyphBuffer *d) {
1337 if (!d->glyphInfo) { 1381 if (!d->glyphInfo) {
1338 hb_shape(d->font->hbFont, d->hb, NULL, 0); 1382 hb_shape(d->font->fontFile->hbFont, d->hb, NULL, 0);
1339 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount); 1383 d->glyphInfo = hb_buffer_get_glyph_infos(d->hb, &d->glyphCount);
1340 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount); 1384 d->glyphPos = hb_buffer_get_glyph_positions(d->hb, &d->glyphCount);
1341 } 1385 }
@@ -1389,7 +1433,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1389 float xCursor = 0.0f; 1433 float xCursor = 0.0f;
1390 float yCursor = 0.0f; 1434 float yCursor = 0.0f;
1391 float xCursorMax = 0.0f; 1435 float xCursorMax = 0.0f;
1392 const iBool isMonospaced = d->isMonospaced; 1436 const iBool isMonospaced = isMonospaced_Font(d);
1393 iWrapText *wrap = args->wrap; 1437 iWrapText *wrap = args->wrap;
1394 iAssert(args->text.end >= args->text.start); 1438 iAssert(args->text.end >= args->text.start);
1395 /* Split the text into a number of attributed runs that specify exactly which 1439 /* Split the text into a number of attributed runs that specify exactly which
@@ -1824,6 +1868,11 @@ int lineHeight_Text(int fontId) {
1824 return font_Text_(fontId)->height; 1868 return font_Text_(fontId)->height;
1825} 1869}
1826 1870
1871float emRatio_Text(int fontId) {
1872 const iFont *font = font_Text_(fontId);
1873 return font->emAdvance / font->height;
1874}
1875
1827iTextMetrics measureRange_Text(int fontId, iRangecc text) { 1876iTextMetrics measureRange_Text(int fontId, iRangecc text) {
1828 if (isEmpty_Range(&text)) { 1877 if (isEmpty_Range(&text)) {
1829 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() }; 1878 return (iTextMetrics){ init_Rect(0, 0, 0, lineHeight_Text(fontId)), zero_I2() };
diff --git a/src/ui/text.h b/src/ui/text.h
index 35ae48f8..ac59e7c8 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -25,108 +25,68 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25#include <the_Foundation/rect.h> 25#include <the_Foundation/rect.h>
26#include <the_Foundation/string.h> 26#include <the_Foundation/string.h>
27#include <the_Foundation/vec2.h> 27#include <the_Foundation/vec2.h>
28
29#include <SDL_render.h> 28#include <SDL_render.h>
30 29
31/* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */ 30#include "fontpack.h"
32 31
33enum iFontSize { 32/* Content sizes: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */
34 uiNormal_FontSize, /* 1.000 */
35 uiMedium_FontSize, /* 1.125 */
36 uiBig_FontSize, /* 1.333 */
37 uiLarge_FontSize, /* 1.666 */
38 uiSmall_FontSize, /* 0.900 */
39 uiTiny_FontSize, /* 0.800 */
40 contentRegular_FontSize,
41 contentMedium_FontSize,
42 contentBig_FontSize,
43 contentLarge_FontSize,
44 contentHuge_FontSize,
45 contentMonoSmall_FontSize,
46 contentMono_FontSize,
47 max_FontSize,
48};
49 33
50iLocalDef enum iFontSize larger_FontSize(enum iFontSize size) { 34#define FONT_ID(name, style, size) ((name) + ((style) * max_FontSize) + (size))
51 if (size == uiLarge_FontSize || size == contentHuge_FontSize || size == contentMono_FontSize) {
52 return size; /* biggest available */
53 }
54 return size + 1;
55}
56 35
57enum iFontId { 36enum iFontId {
58 /* UI fonts: normal weight (1x, 1.125x, 1.33x, 1.67x) */ 37 default_FontId = 0, /* default is always the first font */
59 default_FontId = 0, 38 monospace_FontId = maxVariants_Fonts, /* 2nd font is always the monospace font */
60 defaultMedium_FontId, 39 documentHeading_FontId = maxVariants_Fonts * 2, /* heading font */
61 defaultBig_FontId, 40 documentBody_FontId = maxVariants_Fonts * 3, /* body font */
62 defaultLarge_FontId, 41 documentMonospace_FontId = maxVariants_Fonts * 4,
63 defaultSmall_FontId, 42 auxiliary_FontId = maxVariants_Fonts * 5, /* the first auxiliary font (e.g., symbols) */
64 defaultTiny_FontId,
65 /* UI fonts: bold weight */
66 defaultBold_FontId,
67 defaultMediumBold_FontId,
68 defaultBigBold_FontId,
69 defaultLargeBold_FontId,
70 /* content fonts */
71 regular_FontId,
72 bold_FontId,
73 italic_FontId,
74 medium_FontId,
75 big_FontId,
76 largeBold_FontId,
77 largeLight_FontId,
78 hugeBold_FontId,
79 monospaceSmall_FontId,
80 monospace_FontId,
81 /* extra content fonts */
82 defaultContentRegular_FontId, /* UI font but sized to regular_FontId */
83 defaultContentSmall_FontId, /* UI font but sized smaller */
84 /* symbols and scripts */
85 userSymbols_FontId,
86 iosevka_FontId = userSymbols_FontId + max_FontSize,
87 symbols_FontId = iosevka_FontId + max_FontSize,
88 symbols2_FontId = symbols_FontId + max_FontSize,
89 smolEmoji_FontId = symbols2_FontId + max_FontSize,
90 notoEmoji_FontId = smolEmoji_FontId + max_FontSize,
91 japanese_FontId = notoEmoji_FontId + max_FontSize,
92 chineseSimplified_FontId = japanese_FontId + max_FontSize,
93 korean_FontId = chineseSimplified_FontId + max_FontSize,
94 arabic_FontId = korean_FontId + max_FontSize,
95 max_FontId = arabic_FontId + max_FontSize,
96 43
97 /* Meta: */ 44 /* Meta: */
98 mask_FontId = 0xffff, 45 mask_FontId = 0x0000ffff, /* font IDs are 16-bit; see GmRun's packing */
99 alwaysVariableFlag_FontId = 0x10000, 46 alwaysVariableFlag_FontId = 0x00010000,
100 47
101 /* UI fonts: */ 48 /* UI fonts: */
102 uiLabel_FontId = default_FontId, 49 uiLabelTiny_FontId = FONT_ID(default_FontId, semiBold_FontStyle, uiTiny_FontSize),
103 uiLabelBold_FontId = defaultBold_FontId, 50 uiLabelSmall_FontId = FONT_ID(default_FontId, regular_FontStyle, uiSmall_FontSize),
104 uiLabelLarge_FontId = defaultLarge_FontId, 51 uiLabel_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize),
105 uiLabelLargeBold_FontId = defaultLargeBold_FontId, 52 uiLabelMedium_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
106 uiShortcuts_FontId = default_FontId, 53 uiLabelMediumBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize),
107 uiInput_FontId = defaultMedium_FontId, 54 uiLabelBig_FontId = FONT_ID(default_FontId, regular_FontStyle, uiBig_FontSize),
108 uiContent_FontId = defaultMedium_FontId, 55 uiLabelBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiNormal_FontSize),
109 uiContentBold_FontId = defaultMediumBold_FontId, 56 uiLabelBigBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiBig_FontSize),
110 uiContentSymbols_FontId = symbols_FontId + uiMedium_FontSize, 57 uiLabelLarge_FontId = FONT_ID(default_FontId, regular_FontStyle, uiLarge_FontSize),
111 /* Document fonts: */ 58 uiLabelLargeBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiLarge_FontSize),
112 paragraph_FontId = regular_FontId, 59 uiLabelSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiNormal_FontSize),
113 firstParagraph_FontId = medium_FontId, 60 uiShortcuts_FontId = FONT_ID(default_FontId, regular_FontStyle, uiNormal_FontSize),
114 preformatted_FontId = monospace_FontId, 61 uiInput_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
115 preformattedSmall_FontId = monospaceSmall_FontId, 62 uiContent_FontId = FONT_ID(default_FontId, regular_FontStyle, uiMedium_FontSize),
116 quote_FontId = italic_FontId, 63 uiContentBold_FontId = FONT_ID(default_FontId, bold_FontStyle, uiMedium_FontSize),
117 heading1_FontId = hugeBold_FontId, 64 uiContentSymbols_FontId = FONT_ID(auxiliary_FontId, regular_FontStyle, uiMedium_FontSize),
118 heading2_FontId = largeBold_FontId, 65
119 heading3_FontId = big_FontId, 66 /* Document fonts: */
120 banner_FontId = largeLight_FontId, 67 paragraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentRegular_FontSize),
121 regularMonospace_FontId = iosevka_FontId + contentRegular_FontSize 68 bold_FontId = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentRegular_FontSize),
69 firstParagraph_FontId = FONT_ID(documentBody_FontId, regular_FontStyle, contentMedium_FontSize),
70 preformatted_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentSmall_FontSize),
71 preformattedSmall_FontId = FONT_ID(monospace_FontId, regular_FontStyle, contentTiny_FontSize),
72 quote_FontId = FONT_ID(documentBody_FontId, italic_FontStyle, contentRegular_FontSize),
73 heading1_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentHuge_FontSize),
74 heading2_FontId = FONT_ID(documentHeading_FontId, bold_FontStyle, contentLarge_FontSize),
75 heading3_FontId = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize),
76 banner_FontId = FONT_ID(documentHeading_FontId, light_FontStyle, contentLarge_FontSize),
77 monospaceParagraph_FontId = FONT_ID(documentMonospace_FontId, regular_FontStyle, contentRegular_FontSize),
78 monospaceBold_FontId = FONT_ID(documentMonospace_FontId, semiBold_FontStyle, contentRegular_FontSize),
79 plainText_FontId = FONT_ID(documentMonospace_FontId, regular_FontStyle, contentRegular_FontSize),
122}; 80};
123 81
124iLocalDef iBool isJapanese_FontId(enum iFontId id) { 82//iLocalDef iBool isJapanese_FontId(enum iFontId id) {
125 return id >= japanese_FontId && id < japanese_FontId + max_FontSize; 83// return id >= japanese_FontId && id < japanese_FontId + max_FontSize;
126} 84//}
127 85
128#define emojiVariationSelector_Char ((iChar) 0xfe0f) 86#define emojiVariationSelector_Char ((iChar) 0xfe0f)
129 87
88#if 0
89/* TODO: get rid of this; configure using font ID strings, check RTL from FontFile flags */
130enum iTextFont { 90enum iTextFont {
131 undefined_TextFont = -1, 91 undefined_TextFont = -1,
132 nunito_TextFont = 0, 92 nunito_TextFont = 0,
@@ -139,6 +99,7 @@ enum iTextFont {
139 arabic_TextFont, 99 arabic_TextFont,
140 emojiAndSymbols_TextFont, 100 emojiAndSymbols_TextFont,
141}; 101};
102#endif
142 103
143extern int gap_Text; /* affected by content font size */ 104extern int gap_Text; /* affected by content font size */
144 105
@@ -150,14 +111,14 @@ void deinit_Text (iText *);
150 111
151void setCurrent_Text (iText *); 112void setCurrent_Text (iText *);
152 113
153void loadUserFonts_Text (void); /* based on Prefs */ 114//void setContentFont_Text (iText *, enum iTextFont font);
154 115//void setHeadingFont_Text (iText *, enum iTextFont font);
155void setContentFont_Text (iText *, enum iTextFont font); 116//void setFont_Text (iText *, int fontId, const char *fontSpecId);
156void setHeadingFont_Text (iText *, enum iTextFont font); 117void setDocumentFontSize_Text(iText *, float fontSizeFactor); /* affects all except `default*` fonts */
157void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */
158void resetFonts_Text (iText *); 118void resetFonts_Text (iText *);
159 119
160int lineHeight_Text (int fontId); 120int lineHeight_Text (int fontId);
121float emRatio_Text (int fontId); /* em advance to line height ratio */
161iRect visualBounds_Text (int fontId, iRangecc text); 122iRect visualBounds_Text (int fontId, iRangecc text);
162 123
163iDeclareType(TextMetrics) 124iDeclareType(TextMetrics)
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index ba7545fd..3bd11cfb 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -255,7 +255,7 @@ void init_UploadWidget(iUploadWidget *d) {
255 setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); 255 setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue);
256 setFocus_Widget(as_Widget(d->input)); 256 setFocus_Widget(as_Widget(d->input));
257 } 257 }
258 setFont_InputWidget(d->input, iosevka_FontId); 258 setFont_InputWidget(d->input, FONT_ID(monospace_FontId, regular_FontStyle, uiSmall_FontSize));
259 setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ 259 setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */
260 setLineLimits_InputWidget(d->input, 7, 20); 260 setLineLimits_InputWidget(d->input, 7, 20);
261 setHint_InputWidget(d->input, "${hint.upload.text}"); 261 setHint_InputWidget(d->input, "${hint.upload.text}");
diff --git a/src/ui/util.c b/src/ui/util.c
index adca6269..ab799a36 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -841,7 +841,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
841 (isPortraitPhone_App() ? drawBackgroundToVerticalSafeArea_WidgetFlag : 0), 841 (isPortraitPhone_App() ? drawBackgroundToVerticalSafeArea_WidgetFlag : 0),
842 iTrue); 842 iTrue);
843 if (!isPortraitPhone_App()) { 843 if (!isPortraitPhone_App()) {
844 setFrameColor_Widget(menu, uiSeparator_ColorId); 844 setFrameColor_Widget(menu, uiBackgroundSelected_ColorId);
845 } 845 }
846 makeMenuItems_Widget(menu, items, n); 846 makeMenuItems_Widget(menu, items, n);
847 addChild_Widget(parent, menu); 847 addChild_Widget(parent, menu);
@@ -873,7 +873,7 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {
873 } 873 }
874 else if (isPortraitPhone) { 874 else if (isPortraitPhone) {
875 if (!isSlidePanel) { 875 if (!isSlidePanel) {
876 setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); 876 setFont_LabelWidget(label, isCaution ? uiLabelBigBold_FontId : uiLabelBig_FontId);
877 } 877 }
878 } 878 }
879 else { 879 else {
@@ -1626,8 +1626,8 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) {
1626 } 1626 }
1627 int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId }; 1627 int fonts[2] = { uiLabel_FontId, uiLabelBold_FontId };
1628 if (deviceType_App() == phone_AppDeviceType) { 1628 if (deviceType_App() == phone_AppDeviceType) {
1629 fonts[0] = defaultMedium_FontId; 1629 fonts[0] = uiLabelMedium_FontId;
1630 fonts[1] = defaultMediumBold_FontId; 1630 fonts[1] = uiLabelMediumBold_FontId;
1631 } 1631 }
1632 for (size_t i = 0; i < numActions; i++) { 1632 for (size_t i = 0; i < numActions; i++) {
1633 const char *label = actions[i].label; 1633 const char *label = actions[i].label;
@@ -1694,7 +1694,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
1694 resizeToParentWidth_WidgetFlag); 1694 resizeToParentWidth_WidgetFlag);
1695 setContentPadding_InputWidget(input, 0.5f * gap_UI, 0.5f * gap_UI); 1695 setContentPadding_InputWidget(input, 0.5f * gap_UI, 0.5f * gap_UI);
1696 if (deviceType_App() == phone_AppDeviceType) { 1696 if (deviceType_App() == phone_AppDeviceType) {
1697 setFont_InputWidget(input, defaultBig_FontId); 1697 setFont_InputWidget(input, uiLabelBig_FontId);
1698 setBackgroundColor_Widget(dlg, uiBackgroundSidebar_ColorId); 1698 setBackgroundColor_Widget(dlg, uiBackgroundSidebar_ColorId);
1699 setContentPadding_InputWidget(input, gap_UI, gap_UI); 1699 setContentPadding_InputWidget(input, gap_UI, gap_UI);
1700 } 1700 }
@@ -1811,7 +1811,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg,
1811 resizeToParentWidth_WidgetFlag | 1811 resizeToParentWidth_WidgetFlag |
1812 (first == '&' ? selected_WidgetFlag : 0)); 1812 (first == '&' ? selected_WidgetFlag : 0));
1813 if (deviceType_App() != desktop_AppDeviceType) { 1813 if (deviceType_App() != desktop_AppDeviceType) {
1814 setFont_LabelWidget(option, defaultBig_FontId); 1814 setFont_LabelWidget(option, uiLabelBig_FontId);
1815 } 1815 }
1816 } 1816 }
1817 } 1817 }
@@ -1931,7 +1931,38 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label,
1931 id); 1931 id);
1932} 1932}
1933 1933
1934static iBool proportionalFonts_(const iFontSpec *spec) {
1935 return (spec->flags & monospace_FontSpecFlag) == 0 && ~spec->flags & auxiliary_FontSpecFlag;
1936}
1937
1938static iBool monospaceFonts_(const iFontSpec *spec) {
1939 return (spec->flags & monospace_FontSpecFlag) != 0 && ~spec->flags & auxiliary_FontSpecFlag;
1940}
1941
1934static const iArray *makeFontItems_(const char *id) { 1942static const iArray *makeFontItems_(const char *id) {
1943 iArray *items = collectNew_Array(sizeof(iMenuItem));
1944 if (!startsWith_CStr(id, "mono")) {
1945 iConstForEach(PtrArray, i, listSpecs_Fonts(proportionalFonts_)) {
1946 const iFontSpec *spec = i.ptr;
1947 pushBack_Array(
1948 items,
1949 &(iMenuItem){ cstr_String(&spec->name),
1950 0,
1951 0,
1952 format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) });
1953 }
1954 pushBack_Array(items, &(iMenuItem){ "---" });
1955 }
1956 iConstForEach(PtrArray, j, listSpecs_Fonts(monospaceFonts_)) {
1957 const iFontSpec *spec = j.ptr;
1958 pushBack_Array(
1959 items,
1960 &(iMenuItem){ cstr_String(&spec->name),
1961 0,
1962 0,
1963 format_CStr("!font.set %s:%s", id, cstr_String(&spec->id)) });
1964 }
1965#if 0
1935 const struct { 1966 const struct {
1936 const char * name; 1967 const char * name;
1937 enum iTextFont cfgId; 1968 enum iTextFont cfgId;
@@ -1943,7 +1974,6 @@ static const iArray *makeFontItems_(const char *id) {
1943 { "Tinos", tinos_TextFont }, 1974 { "Tinos", tinos_TextFont },
1944 { "---", -1 }, 1975 { "---", -1 },
1945 { "Iosevka", iosevka_TextFont } }; 1976 { "Iosevka", iosevka_TextFont } };
1946 iArray *items = collectNew_Array(sizeof(iMenuItem));
1947 iForIndices(i, fonts) { 1977 iForIndices(i, fonts) {
1948 pushBack_Array(items, 1978 pushBack_Array(items,
1949 &(iMenuItem){ fonts[i].name, 1979 &(iMenuItem){ fonts[i].name,
@@ -1953,17 +1983,19 @@ static const iArray *makeFontItems_(const char *id) {
1953 ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) 1983 ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId)
1954 : NULL }); 1984 : NULL });
1955 } 1985 }
1986#endif
1956 pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ 1987 pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */
1957 return items; 1988 return items;
1958} 1989}
1959 1990
1960static void addFontButtons_(iWidget *parent, const char *id) { 1991static void addFontButtons_(iWidget *parent, const char *id) {
1961 const iArray *items = makeFontItems_(id); 1992 const iArray *items = makeFontItems_(id);
1962 iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", 1993 size_t widestIndex = findWidestLabel_MenuItem(constData_Array(items), size_Array(items));
1994 iLabelWidget *button = makeMenuButton_LabelWidget(constValue_Array(items, widestIndex, iMenuItem).label,
1963 constData_Array(items), size_Array(items)); 1995 constData_Array(items), size_Array(items));
1964 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), 1996 setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"),
1965 uiBackgroundMenu_ColorId); 1997 uiBackgroundMenu_ColorId);
1966 setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); 1998 setId_Widget(as_Widget(button), format_CStr("prefs.font.%s", id));
1967 addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); 1999 addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag);
1968} 2000}
1969 2001
@@ -2169,6 +2201,15 @@ iWidget *makePreferences_Widget(void) {
2169 { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, 2201 { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) },
2170 { NULL } 2202 { NULL }
2171 }; 2203 };
2204 const iMenuItem lineWidthItems[] = {
2205 { "button id:prefs.linewidth.30 text:\u20132", 0, 0, "linewidth.set arg:30" },
2206 { "button id:prefs.linewidth.34 text:\u20131", 0, 0, "linewidth.set arg:34" },
2207 { "button id:prefs.linewidth.38 label:prefs.linewidth.normal", 0, 0, "linewidth.set arg:38" },
2208 { "button id:prefs.linewidth.43 text:+1", 0, 0, "linewidth.set arg:43" },
2209 { "button id:prefs.linewidth.48 text:+2", 0, 0, "linewidth.set arg:48" },
2210 { "button id:prefs.linewidth.1000 label:prefs.linewidth.fill", 0, 0, "linewidth.set arg:1000" },
2211 { NULL }
2212 };
2172 /* Create the Preferences UI. */ 2213 /* Create the Preferences UI. */
2173 if (isUsingPanelLayout_Mobile()) { 2214 if (isUsingPanelLayout_Mobile()) {
2174 const iMenuItem pinSplitItems[] = { 2215 const iMenuItem pinSplitItems[] = {
@@ -2206,15 +2247,6 @@ iWidget *makePreferences_Widget(void) {
2206 { "button id:prefs.boldlink.light" }, 2247 { "button id:prefs.boldlink.light" },
2207 { NULL } 2248 { NULL }
2208 }; 2249 };
2209 const iMenuItem lineWidthItems[] = {
2210 { "button id:prefs.linewidth.30 text:\u20132", 0, 0, "linewidth.set arg:30" },
2211 { "button id:prefs.linewidth.34 text:\u20131", 0, 0, "linewidth.set arg:34" },
2212 { "button id:prefs.linewidth.38 label:prefs.linewidth.normal", 0, 0, "linewidth.set arg:38" },
2213 { "button id:prefs.linewidth.43 text:+1", 0, 0, "linewidth.set arg:43" },
2214 { "button id:prefs.linewidth.48 text:+2", 0, 0, "linewidth.set arg:48" },
2215 { "button id:prefs.linewidth.1000 label:prefs.linewidth.fill", 0, 0, "linewidth.set arg:1000" },
2216 { NULL }
2217 };
2218 const iMenuItem quoteItems[] = { 2250 const iMenuItem quoteItems[] = {
2219 { "button id:prefs.quoteicon.1 label:prefs.quoteicon.icon", 0, 0, "quoteicon.set arg:1" }, 2251 { "button id:prefs.quoteicon.1 label:prefs.quoteicon.icon", 0, 0, "quoteicon.set arg:1" },
2220 { "button id:prefs.quoteicon.0 label:prefs.quoteicon.line", 0, 0, "quoteicon.set arg:0" }, 2252 { "button id:prefs.quoteicon.0 label:prefs.quoteicon.line", 0, 0, "quoteicon.set arg:0" },
@@ -2491,26 +2523,13 @@ iWidget *makePreferences_Widget(void) {
2491 /* Fonts. */ { 2523 /* Fonts. */ {
2492 setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); 2524 setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts");
2493 /* Fonts. */ { 2525 /* Fonts. */ {
2494 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}"))); 2526 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.heading}")));
2495 addFontButtons_(values, "headingfont"); 2527 addFontButtons_(values, "heading");
2496 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); 2528 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.body}")));
2497 addFontButtons_(values, "font"); 2529 addFontButtons_(values, "body");
2530 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.mono}")));
2531 addFontButtons_(values, "mono");
2498 addDialogPadding_(headings, values); 2532 addDialogPadding_(headings, values);
2499 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}")));
2500 iWidget *mono = new_Widget(); {
2501 iWidget *tog;
2502 setTextCStr_LabelWidget(
2503 addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gemini"))),
2504 "${prefs.mono.gemini}");
2505 setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse);
2506 updateSize_LabelWidget((iLabelWidget *) tog);
2507 setTextCStr_LabelWidget(
2508 addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gopher"))),
2509 "${prefs.mono.gopher}");
2510 setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse);
2511 updateSize_LabelWidget((iLabelWidget *) tog);
2512 }
2513 addChildFlags_Widget(values, iClob(mono), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
2514 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}"))); 2533 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.boldlink}")));
2515 iWidget *boldLink = new_Widget(); { 2534 iWidget *boldLink = new_Widget(); {
2516 /* TODO: Add a utility function for this type of toggles? (also for above) */ 2535 /* TODO: Add a utility function for this type of toggles? (also for above) */
@@ -2528,11 +2547,32 @@ iWidget *makePreferences_Widget(void) {
2528 } 2547 }
2529 addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 2548 addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
2530 addDialogPadding_(headings, values); 2549 addDialogPadding_(headings, values);
2531 /* Custom font. */ { 2550 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}")));
2532 iInputWidget *customFont = new_InputWidget(0); 2551 iWidget *mono = new_Widget(); {
2533 setHint_InputWidget(customFont, "${hint.prefs.userfont}"); 2552 iWidget *tog;
2534 addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont)); 2553 setTextCStr_LabelWidget(
2554 addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gemini"))),
2555 "${prefs.mono.gemini}");
2556 setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse);
2557 updateSize_LabelWidget((iLabelWidget *) tog);
2558 setTextCStr_LabelWidget(
2559 addChild_Widget(mono, tog = iClob(makeToggle_Widget("prefs.mono.gopher"))),
2560 "${prefs.mono.gopher}");
2561 setFlags_Widget(tog, fixedWidth_WidgetFlag, iFalse);
2562 updateSize_LabelWidget((iLabelWidget *) tog);
2535 } 2563 }
2564 addChildFlags_Widget(values, iClob(mono), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
2565 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.monodoc}")));
2566 addFontButtons_(values, "monodoc");
2567 addDialogPadding_(headings, values);
2568 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font.ui}")));
2569 addFontButtons_(values, "ui");
2570 // addDialogPadding_(headings, values);
2571// /* Custom font. */ {
2572// iInputWidget *customFont = new_InputWidget(0);
2573// setHint_InputWidget(customFont, "${hint.prefs.userfont}");
2574// addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont));
2575// }
2536 } 2576 }
2537 } 2577 }
2538 /* Style. */ { 2578 /* Style. */ {
@@ -2541,12 +2581,16 @@ iWidget *makePreferences_Widget(void) {
2541 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); 2581 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}")));
2542 iWidget *widths = new_Widget(); 2582 iWidget *widths = new_Widget();
2543 /* Line widths. */ { 2583 /* Line widths. */ {
2544 addRadioButton_(widths, "prefs.linewidth.30", "\u20132", "linewidth.set arg:30"); 2584 /* TODO: Make this a utility function to build radio buttons from items. */
2545 addRadioButton_(widths, "prefs.linewidth.34", "\u20131", "linewidth.set arg:34"); 2585 for (size_t i = 0; lineWidthItems[i].label; i++) {
2546 addRadioButton_(widths, "prefs.linewidth.38", "${prefs.linewidth.normal}", "linewidth.set arg:38"); 2586 const iMenuItem *lw = &lineWidthItems[i];
2547 addRadioButton_(widths, "prefs.linewidth.43", "+1", "linewidth.set arg:43"); 2587 addRadioButton_(widths,
2548 addRadioButton_(widths, "prefs.linewidth.48", "+2", "linewidth.set arg:48"); 2588 cstr_Command(lw->label, "id"),
2549 addRadioButton_(widths, "prefs.linewidth.1000", "${prefs.linewidth.fill}", "linewidth.set arg:1000"); 2589 hasLabel_Command(lw->label, "label")
2590 ? cstr_Lang(cstr_Command(lw->label, "label"))
2591 : cstr_Command(lw->label, "text"),
2592 lw->command);
2593 }
2550 } 2594 }
2551 addChildFlags_Widget(values, iClob(widths), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 2595 addChildFlags_Widget(values, iClob(widths), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
2552 addPrefsInputWithHeading_(headings, values, "prefs.linespacing", iClob(new_InputWidget(5))); 2596 addPrefsInputWithHeading_(headings, values, "prefs.linespacing", iClob(new_InputWidget(5)));
@@ -2738,27 +2782,27 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) {
2738 } 2782 }
2739 int id = argLabel_Command(cmd, "bmid"); 2783 int id = argLabel_Command(cmd, "bmid");
2740 const iBool headings = isSelected_Widget(findChild_Widget(dlg, "feedcfg.type.headings")); 2784 const iBool headings = isSelected_Widget(findChild_Widget(dlg, "feedcfg.type.headings"));
2741 const iString *tags = collectNewFormat_String("subscribed%s", headings ? " headings" : ""); 2785 const iBool ignoreWeb = isSelected_Widget(findChild_Widget(dlg, "feedcfg.ignoreweb"));
2742 if (!id) { 2786 if (!id) {
2743 const size_t numSubs = numSubscribed_Feeds(); 2787 const size_t numSubs = numSubscribed_Feeds();
2744 const iString *url = url_DocumentWidget(document_App()); 2788 const iString *url = url_DocumentWidget(document_App());
2745 add_Bookmarks(bookmarks_App(), 2789 id = add_Bookmarks(bookmarks_App(),
2746 url, 2790 url,
2747 feedTitle, 2791 feedTitle,
2748 tags, 2792 NULL,
2749 siteIcon_GmDocument(document_DocumentWidget(document_App()))); 2793 siteIcon_GmDocument(document_DocumentWidget(document_App())));
2750 if (numSubs == 0) { 2794 if (numSubs == 0) {
2751 /* Auto-refresh after first addition. */ 2795 /* Auto-refresh after first addition. */
2796 /* TODO: Also when settings changed? */
2752 postCommand_App("feeds.refresh"); 2797 postCommand_App("feeds.refresh");
2753 } 2798 }
2754 } 2799 }
2755 else { 2800 iBookmark *bm = get_Bookmarks(bookmarks_App(), id);
2756 iBookmark *bm = get_Bookmarks(bookmarks_App(), id); 2801 iAssert(bm);
2757 if (bm) { 2802 set_String(&bm->title, feedTitle);
2758 set_String(&bm->title, feedTitle); 2803 addOrRemoveTag_Bookmark(bm, subscribed_BookmarkTag, iTrue);
2759 set_String(&bm->tags, tags); 2804 addOrRemoveTag_Bookmark(bm, headings_BookmarkTag, headings);
2760 } 2805 addOrRemoveTag_Bookmark(bm, ignoreWeb_BookmarkTag, ignoreWeb);
2761 }
2762 postCommand_App("bookmarks.changed"); 2806 postCommand_App("bookmarks.changed");
2763 setupSheetTransition_Mobile(dlg, iFalse); 2807 setupSheetTransition_Mobile(dlg, iFalse);
2764 destroy_Widget(dlg); 2808 destroy_Widget(dlg);
@@ -2787,6 +2831,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2787 { format_CStr("title id:feedcfg.heading text:%s", headingText) }, 2831 { format_CStr("title id:feedcfg.heading text:%s", headingText) },
2788 { "input id:feedcfg.title text:${dlg.feed.title}" }, 2832 { "input id:feedcfg.title text:${dlg.feed.title}" },
2789 { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems }, 2833 { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems },
2834 { "toggle id:feedcfg.ignoreweb text:${dlg.feed.ignoreweb}" },
2790 { NULL } 2835 { NULL }
2791 }, actions, iElemCount(actions)); 2836 }, actions, iElemCount(actions));
2792 } 2837 }
@@ -2805,6 +2850,8 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2805 addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1"); 2850 addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1");
2806 } 2851 }
2807 addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 2852 addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
2853 addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.ignoreweb}")));
2854 addChild_Widget(values, iClob(makeToggle_Widget("feedcfg.ignoreweb")));
2808 iWidget *buttons = 2855 iWidget *buttons =
2809 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); 2856 addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions))));
2810 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); 2857 setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save");
@@ -2823,6 +2870,8 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2823 : "feedcfg.type.gemini"), 2870 : "feedcfg.type.gemini"),
2824 selected_WidgetFlag, 2871 selected_WidgetFlag,
2825 iTrue); 2872 iTrue);
2873 setToggle_Widget(findChild_Widget(dlg, "feedcfg.ignoreweb"),
2874 hasTag_Bookmark(bm, ignoreWeb_BookmarkTag));
2826 setCommandHandler_Widget(dlg, handleFeedSettingCommands_); 2875 setCommandHandler_Widget(dlg, handleFeedSettingCommands_);
2827 } 2876 }
2828 setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); 2877 setupSheetTransition_Mobile(dlg, incoming_TransitionFlag);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index ec92ac5a..910c31a9 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -1407,7 +1407,8 @@ void drawBackground_Widget(const iWidget *d) {
1407 fillRect_Paint(&p, rect, d->bgColor); 1407 fillRect_Paint(&p, rect, d->bgColor);
1408 } 1408 }
1409 if (d->frameColor >= 0 && ~d->flags & frameless_WidgetFlag) { 1409 if (d->frameColor >= 0 && ~d->flags & frameless_WidgetFlag) {
1410 drawRectThickness_Paint(&p, rect, gap_UI / 4, d->frameColor); 1410 drawRectThickness_Paint(&p, adjusted_Rect(rect, zero_I2(), neg_I2(one_I2())),
1411 gap_UI / 4, d->frameColor);
1411 } 1412 }
1412 } 1413 }
1413 if (d->flags & (borderTop_WidgetFlag | borderBottom_WidgetFlag)) { 1414 if (d->flags & (borderTop_WidgetFlag | borderBottom_WidgetFlag)) {
diff --git a/src/ui/window.c b/src/ui/window.c
index 686a6dd6..569ec919 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1188,12 +1188,12 @@ void draw_Window(iWindow *d) {
1188 extern int drawCount_; 1188 extern int drawCount_;
1189 drawRoot_Widget(root->widget); 1189 drawRoot_Widget(root->widget);
1190#if !defined (NDEBUG) 1190#if !defined (NDEBUG)
1191 draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_); 1191 draw_Text(uiLabelBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);
1192 drawCount_ = 0; 1192 drawCount_ = 0;
1193#endif 1193#endif
1194 } 1194 }
1195 drawRectThickness_Paint( 1195 drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4,
1196 &p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId); 1196 uiBackgroundSelected_ColorId);
1197 setCurrent_Root(NULL); 1197 setCurrent_Root(NULL);
1198 SDL_RenderPresent(d->render); 1198 SDL_RenderPresent(d->render);
1199} 1199}
@@ -1287,7 +1287,7 @@ void draw_MainWindow(iMainWindow *d) {
1287 } 1287 }
1288 setCurrent_Root(NULL); 1288 setCurrent_Root(NULL);
1289#if !defined (NDEBUG) 1289#if !defined (NDEBUG)
1290 draw_Text(defaultBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_); 1290 draw_Text(uiLabelBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_);
1291 drawCount_ = 0; 1291 drawCount_ = 0;
1292#endif 1292#endif
1293 } 1293 }