summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.c66
-rw-r--r--src/app.h2
-rw-r--r--src/defs.h1
-rw-r--r--src/fontpack.c120
-rw-r--r--src/fontpack.h4
-rw-r--r--src/gmdocument.c4
-rw-r--r--src/ui/banner.c3
-rw-r--r--src/ui/documentwidget.c26
-rw-r--r--src/ui/linkinfo.c8
-rw-r--r--src/ui/text.c40
-rw-r--r--src/ui/text.h2
-rw-r--r--src/ui/util.c62
-rw-r--r--src/ui/util.h1
13 files changed, 299 insertions, 40 deletions
diff --git a/src/app.c b/src/app.c
index b6c48062..0f9249cc 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1365,6 +1365,10 @@ void processEvents_App(enum iAppEventMode eventMode) {
1365 dispatchCommands_Periodic(&d->periodic); 1365 dispatchCommands_Periodic(&d->periodic);
1366 continue; 1366 continue;
1367 } 1367 }
1368 if (ev.type == SDL_USEREVENT && ev.user.code == releaseObject_UserEventCode) {
1369 iRelease(ev.user.data1);
1370 continue;
1371 }
1368 if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) { 1372 if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) {
1369 gotRefresh = iTrue; 1373 gotRefresh = iTrue;
1370 continue; 1374 continue;
@@ -2257,6 +2261,13 @@ void resetFonts_App(void) {
2257 } 2261 }
2258} 2262}
2259 2263
2264void availableFontsChanged_App(void) {
2265 iApp *d = &app_;
2266 iConstForEach(PtrArray, win, listWindows_App_(d, collectNew_PtrArray())) {
2267 resetMissing_Text(text_Window(win.ptr));
2268 }
2269}
2270
2260static void invalidateCachedDocuments_App_(void) { 2271static void invalidateCachedDocuments_App_(void) {
2261 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { 2272 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
2262 invalidateCachedLayout_History(history_DocumentWidget(i.object)); 2273 invalidateCachedLayout_History(history_DocumentWidget(i.object));
@@ -2383,24 +2394,49 @@ iBool handleCommand_App(const char *cmd) {
2383 reload_Fonts(); /* also does font cache reset, window invalidation */ 2394 reload_Fonts(); /* also does font cache reset, window invalidation */
2384 return iTrue; 2395 return iTrue;
2385 } 2396 }
2386#if 0 2397 else if (equal_Command(cmd, "font.find")) {
2387 else if (equal_Command(cmd, "font.user")) { 2398 searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars"));
2388 const char *path = suffixPtr_Command(cmd, "path"); 2399 return iTrue;
2389 if (cmp_String(&d->prefs.symbolFontPath, path)) { 2400 }
2390 if (!isFrozen) { 2401 else if (equal_Command(cmd, "font.found")) {
2391 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); 2402 if (hasLabel_Command(cmd, "error")) {
2392 } 2403 makeSimpleMessage_Widget("${heading.glyphfinder}",
2393 setCStr_String(&d->prefs.symbolFontPath, path); 2404 format_CStr("%d %s",
2394 loadUserFonts_Text(); 2405 argLabel_Command(cmd, "error"),
2395 resetFonts_App(d); 2406 suffixPtr_Command(cmd, "msg")));
2396 if (!isFrozen) { 2407 return iTrue;
2397 postCommand_App("font.changed");
2398 postCommand_App("window.unfreeze");
2399 }
2400 } 2408 }
2409 iString *src = collectNew_String();
2410 setCStr_String(src, "# ${heading.glyphfinder.results}\n\n");
2411 iRangecc path = iNullRange;
2412 iBool isFirst = iTrue;
2413 while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) {
2414 if (isFirst) {
2415 appendCStr_String(src, "${glyphfinder.results}\n\n");
2416 }
2417 iRangecc fpath = path;
2418 iRangecc fsize = path;
2419 fpath.end = strchr(fpath.start, ';');
2420 fsize.start = fpath.end + 1;
2421 const uint32_t size = strtoul(fsize.start, NULL, 10);
2422 appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n",
2423 cstr_Rangecc(fpath),
2424 cstr_Rangecc(fpath),
2425 (double) size / 1.0e6);
2426 isFirst = iFalse;
2427 }
2428 if (isFirst) {
2429 appendFormat_String(src, "${glyphfinder.results.empty}\n");
2430 }
2431 appendCStr_String(src, "\n=> about:fonts ${menu.fonts}");
2432 iDocumentWidget *page = newTab_App(NULL, iTrue);
2433 translate_Lang(src);
2434 setUrlAndSource_DocumentWidget(page,
2435 collectNewCStr_String(""),
2436 collectNewCStr_String("text/gemini"),
2437 utf8_String(src));
2401 return iTrue; 2438 return iTrue;
2402 } 2439 }
2403#endif
2404 else if (equal_Command(cmd, "font.set")) { 2440 else if (equal_Command(cmd, "font.set")) {
2405 if (!isFrozen) { 2441 if (!isFrozen) {
2406 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); 2442 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
diff --git a/src/app.h b/src/app.h
index 5968de0d..22fe5d46 100644
--- a/src/app.h
+++ b/src/app.h
@@ -67,6 +67,7 @@ enum iUserEventCode {
67 take, it could turn into a tap-and-hold for example. */ 67 take, it could turn into a tap-and-hold for example. */
68 widgetTapBegins_UserEventCode, 68 widgetTapBegins_UserEventCode,
69 widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ 69 widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */
70 releaseObject_UserEventCode, /* object that needs releasing in the main thread */
70}; 71};
71 72
72const iString *execPath_App (void); 73const iString *execPath_App (void);
@@ -142,6 +143,7 @@ iDocumentWidget * document_Command (const char *cmd);
142void openInDefaultBrowser_App(const iString *url); 143void openInDefaultBrowser_App(const iString *url);
143void revealPath_App (const iString *path); 144void revealPath_App (const iString *path);
144void resetFonts_App (void); 145void resetFonts_App (void);
146void availableFontsChanged_App(void);
145 147
146iMainWindow * mainWindow_App (void); 148iMainWindow * mainWindow_App (void);
147void closePopups_App (void); 149void closePopups_App (void);
diff --git a/src/defs.h b/src/defs.h
index e2edd100..550ae5bd 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -186,6 +186,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
186#define downAngle_Icon "\ufe40" 186#define downAngle_Icon "\ufe40"
187#define photo_Icon "\U0001f5bc" 187#define photo_Icon "\U0001f5bc"
188#define fontpack_Icon "\U0001f520" 188#define fontpack_Icon "\U0001f520"
189#define package_Icon "\U0001f4e6"
189 190
190#if defined (iPlatformApple) 191#if defined (iPlatformApple)
191# define shift_Icon "\u21e7" 192# define shift_Icon "\u21e7"
diff --git a/src/fontpack.c b/src/fontpack.c
index a440234e..96006226 100644
--- a/src/fontpack.c
+++ b/src/fontpack.c
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#include "fontpack.h" 23#include "fontpack.h"
24#include "resources.h" 24#include "resources.h"
25#include "ui/window.h" 25#include "ui/window.h"
26#include "gmrequest.h"
26#include "app.h" 27#include "app.h"
27 28
28#include <the_Foundation/archive.h> 29#include <the_Foundation/archive.h>
@@ -748,7 +749,7 @@ const iPtrArray *listSpecsByPriority_Fonts(void) {
748 return &fonts_.specOrder; 749 return &fonts_.specOrder;
749} 750}
750 751
751iString *infoText_FontPack(const iFontPack *d) { 752iString *infoText_FontPack(const iFontPack *d, iBool isFull) {
752 const iFontPack *installed = pack_Fonts(cstr_String(&d->id)); 753 const iFontPack *installed = pack_Fonts(cstr_String(&d->id));
753 const iBool isInstalled = (installed != NULL); 754 const iBool isInstalled = (installed != NULL);
754 const int installedVersion = installed ? installed->version : 0; 755 const int installedVersion = installed ? installed->version : 0;
@@ -757,9 +758,17 @@ iString *infoText_FontPack(const iFontPack *d) {
757 size_t sizeInBytes = 0; 758 size_t sizeInBytes = 0;
758 iPtrSet *uniqueFiles = new_PtrSet(); 759 iPtrSet *uniqueFiles = new_PtrSet();
759 iStringList *names = new_StringList(); 760 iStringList *names = new_StringList();
761 size_t numNames = 0;
762 iBool isAbbreviated = iFalse;
760 iConstForEach(PtrArray, i, listSpecs_FontPack(d)) { 763 iConstForEach(PtrArray, i, listSpecs_FontPack(d)) {
761 const iFontSpec *spec = i.ptr; 764 const iFontSpec *spec = i.ptr;
762 pushBack_StringList(names, &spec->name); 765 numNames++;
766 if (isFull || size_StringList(names) < 20) {
767 pushBack_StringList(names, &spec->name);
768 }
769 else {
770 isAbbreviated = iTrue;
771 }
763 iForIndices(j, spec->styles) { 772 iForIndices(j, spec->styles) {
764 insert_PtrSet(uniqueFiles, spec->styles[j]->sourceData.i); 773 insert_PtrSet(uniqueFiles, spec->styles[j]->sourceData.i);
765 } 774 }
@@ -777,11 +786,12 @@ iString *infoText_FontPack(const iFontPack *d) {
777 if (!endsWith_String(str, "(")) { 786 if (!endsWith_String(str, "(")) {
778 appendCStr_String(str, ", "); 787 appendCStr_String(str, ", ");
779 } 788 }
780 appendCStr_String(str, formatCStrs_Lang("num.fonts.n", size_StringList(names))); 789 appendCStr_String(str, formatCStrs_Lang("num.fonts.n", numNames));
781 } 790 }
782 appendFormat_String(str, ")"); 791 appendFormat_String(str, ")");
783 } 792 }
784 appendFormat_String(str, " \u2014 %s\n", cstrCollect_String(joinCStr_StringList(names, ", "))); 793 appendFormat_String(str, " \u2014 %s%s\n", cstrCollect_String(joinCStr_StringList(names, ", ")),
794 isAbbreviated ? ", ..." : "");
785 if (isInstalled && installedVersion != d->version) { 795 if (isInstalled && installedVersion != d->version) {
786 appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version)); 796 appendCStr_String(str, format_Lang("${fontpack.meta.version}\n", d->version));
787 } 797 }
@@ -945,7 +955,7 @@ const iString *infoPage_Fonts(iRangecc query) {
945 appendFormat_String(str, "### %s\n", 955 appendFormat_String(str, "### %s\n",
946 isEmpty_String(packId) ? "fonts.ini" : 956 isEmpty_String(packId) ? "fonts.ini" :
947 cstr_String(packId)); 957 cstr_String(packId));
948 append_String(str, collect_String(infoText_FontPack(pack))); 958 append_String(str, collect_String(infoText_FontPack(pack, iFalse)));
949 appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n", 959 appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n",
950 cstrCollect_String(makeFileUrl_String(&spec->sourcePath))); 960 cstrCollect_String(makeFileUrl_String(&spec->sourcePath)));
951 if (pack->isStandalone) { 961 if (pack->isStandalone) {
@@ -1018,6 +1028,7 @@ void install_Fonts(const iString *packId, const iBlock *data) {
1018 iRelease(f); 1028 iRelease(f);
1019 /* Newly installed fontpacks may have a higher priority that overrides other fonts. */ 1029 /* Newly installed fontpacks may have a higher priority that overrides other fonts. */
1020 reload_Fonts(); 1030 reload_Fonts();
1031 availableFontsChanged_App();
1021} 1032}
1022 1033
1023void installFontFile_Fonts(const iString *fileName, const iBlock *data) { 1034void installFontFile_Fonts(const iString *fileName, const iBlock *data) {
@@ -1028,6 +1039,7 @@ void installFontFile_Fonts(const iString *fileName, const iBlock *data) {
1028 } 1039 }
1029 iRelease(f); 1040 iRelease(f);
1030 reload_Fonts(); 1041 reload_Fonts();
1042 availableFontsChanged_App();
1031} 1043}
1032 1044
1033void enablePack_Fonts(const iString *packId, iBool enable) { 1045void enablePack_Fonts(const iString *packId, iBool enable) {
@@ -1040,6 +1052,7 @@ void enablePack_Fonts(const iString *packId, iBool enable) {
1040 } 1052 }
1041 updateActive_Fonts(); 1053 updateActive_Fonts();
1042 resetFonts_App(); 1054 resetFonts_App();
1055 availableFontsChanged_App();
1043 invalidate_Window(get_MainWindow()); 1056 invalidate_Window(get_MainWindow());
1044} 1057}
1045 1058
@@ -1047,5 +1060,100 @@ void updateActive_Fonts(void) {
1047 sortSpecs_Fonts_(&fonts_); 1060 sortSpecs_Fonts_(&fonts_);
1048} 1061}
1049 1062
1050iDefineClass(FontFile) 1063static void findCharactersInCMap_(iGmRequest *d, iGmRequest *req) {
1064 /* Note: Called in background thread. */
1065 iUnused(req);
1066 const iString *missingChars = userData_Object(d);
1067 if (isSuccess_GmStatusCode(status_GmRequest(d))) {
1068 iStringList *matchingPacks = new_StringList();
1069 iChar needed[20];
1070 iChar minChar = UINT32_MAX, maxChar = 0;
1071 size_t numNeeded = 0;
1072 iConstForEach(String, ch, missingChars) {
1073 needed[numNeeded++] = ch.value;
1074 minChar = iMin(minChar, ch.value);
1075 maxChar = iMax(maxChar, ch.value);
1076 if (numNeeded == iElemCount(needed)) {
1077 /* Shouldn't be that many. */
1078 break;
1079 }
1080 }
1081 iBlock *data = decompressGzip_Block(body_GmRequest(d));
1082 iRangecc line = iNullRange;
1083 while (nextSplit_Rangecc(range_Block(data), "\n", &line)) {
1084 iRangecc fontpackPath = iNullRange;
1085 for (const char *pos = line.start; pos < line.end; pos++) {
1086 if (*pos == ':') {
1087 fontpackPath.start = line.start;
1088 fontpackPath.end = pos;
1089 line.start = pos + 1;
1090 trimStart_Rangecc(&line);
1091 break;
1092 }
1093 }
1094 if (fontpackPath.start) {
1095 /* Parse the character ranges and see if any match what we need. */
1096 const char *pos = line.start;
1097 while (pos < line.end) {
1098 char *endp;
1099 uint32_t first = strtoul(pos, &endp, 10);
1100 uint32_t last = first;
1101 if (*endp == '-') {
1102 last = strtoul(endp + 1, &endp, 10);
1103 }
1104 if (maxChar < first) {
1105 break; /* The rest are even higher. */
1106 }
1107 if (minChar <= last) {
1108 for (size_t i = 0; i < numNeeded; i++) {
1109 if (needed[i] >= first && needed[i] <= last) {
1110 /* Got it. */
1111 pushBackRange_StringList(matchingPacks, fontpackPath);
1112 break;
1113 }
1114 }
1115 }
1116 pos = endp + 1;
1117 }
1118 }
1119 }
1120 delete_Block(data);
1121 iString result;
1122 init_String(&result);
1123 format_String(&result, "font.found chars:%s packs:", cstr_String(missingChars));
1124 iConstForEach(StringList, s, matchingPacks) {
1125 if (s.pos != 0) {
1126 appendCStr_String(&result, ",");
1127 }
1128 append_String(&result, s.value);
1129 }
1130 postCommandString_Root(NULL, &result);
1131 deinit_String(&result);
1132 iRelease(matchingPacks);
1133 }
1134 else {
1135 /* Report error. */
1136 postCommandf_Root(NULL,
1137 "font.found chars:%s error:%d msg:\x1b[1m%s\x1b[0m\n%s",
1138 cstr_String(missingChars),
1139 status_GmRequest(d),
1140 cstr_String(meta_GmRequest(d)),
1141 cstr_String(url_GmRequest(d)));
1142 }
1143// fflush(stdout);
1144 delete_String(userData_Object(d));
1145 /* We can't delete ourselves; threads must be joined from another thread. */
1146 SDL_PushEvent((SDL_Event *) &(SDL_UserEvent){
1147 .type = SDL_USEREVENT, .code = releaseObject_UserEventCode, .data1 = d });
1148}
1051 1149
1150void searchOnlineLibraryForCharacters_Fonts(const iString *chars) {
1151 /* Fetch the character map from skyjake.fi. */
1152 iGmRequest *req = new_GmRequest(certs_App());
1153 setUrl_GmRequest(req, collectNewCStr_String("gemini://skyjake.fi/fonts/cmap.txt.gz"));
1154 setUserData_Object(req, copy_String(chars));
1155 iConnect(GmRequest, req, finished, req, findCharactersInCMap_);
1156 submit_GmRequest(req);
1157}
1158
1159iDefineClass(FontFile)
diff --git a/src/fontpack.h b/src/fontpack.h
index aa6f2b9f..f6d4d483 100644
--- a/src/fontpack.h
+++ b/src/fontpack.h
@@ -160,7 +160,7 @@ const iString * loadPath_FontPack (const iFontPack *); /* may return N
160iBool isDisabled_FontPack (const iFontPack *); 160iBool isDisabled_FontPack (const iFontPack *);
161iBool isReadOnly_FontPack (const iFontPack *); 161iBool isReadOnly_FontPack (const iFontPack *);
162const iPtrArray * listSpecs_FontPack (const iFontPack *); 162const iPtrArray * listSpecs_FontPack (const iFontPack *);
163iString * infoText_FontPack (const iFontPack *); 163iString * infoText_FontPack (const iFontPack *, iBool isFull);
164const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); 164const iArray * actions_FontPack (const iFontPack *, iBool showInstalled);
165 165
166const iString * idFromUrl_FontPack (const iString *url); 166const iString * idFromUrl_FontPack (const iString *url);
@@ -186,3 +186,5 @@ void reload_Fonts (void);
186iLocalDef iBool isInstalled_Fonts(const char *packId) { 186iLocalDef iBool isInstalled_Fonts(const char *packId) {
187 return pack_Fonts(packId) != NULL; 187 return pack_Fonts(packId) != NULL;
188} 188}
189
190void searchOnlineLibraryForCharacters_Fonts (const iString *chars);
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 5ff8c72f..546dc6f5 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -370,6 +370,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
370 } 370 }
371 else if (equalCase_Rangecc(parts.scheme, "data")) { 371 else if (equalCase_Rangecc(parts.scheme, "data")) {
372 setScheme_GmLink_(link, data_GmLinkScheme); 372 setScheme_GmLink_(link, data_GmLinkScheme);
373 if (startsWith_Rangecc(parts.path, "image/")) {
374 link->flags |= imageFileExtension_GmLinkFlag;
375 }
373 } 376 }
374 else if (equalCase_Rangecc(parts.scheme, "about")) { 377 else if (equalCase_Rangecc(parts.scheme, "about")) {
375 setScheme_GmLink_(link, about_GmLinkScheme); 378 setScheme_GmLink_(link, about_GmLinkScheme);
@@ -904,6 +907,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
904 : scheme == titan_GmLinkScheme ? uploadArrow 907 : scheme == titan_GmLinkScheme ? uploadArrow
905 : scheme == finger_GmLinkScheme ? pointingFinger 908 : scheme == finger_GmLinkScheme ? pointingFinger
906 : scheme == mailto_GmLinkScheme ? envelope 909 : scheme == mailto_GmLinkScheme ? envelope
910 : scheme == data_GmLinkScheme ? package_Icon
907 : link->flags & remote_GmLinkFlag ? globe 911 : link->flags & remote_GmLinkFlag ? globe
908 : link->flags & imageFileExtension_GmLinkFlag ? image 912 : link->flags & imageFileExtension_GmLinkFlag ? image
909 : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon 913 : link->flags & fontpackFileExtension_GmLinkFlag ? fontpack_Icon
diff --git a/src/ui/banner.c b/src/ui/banner.c
index 11ae1574..79d70039 100644
--- a/src/ui/banner.c
+++ b/src/ui/banner.c
@@ -327,7 +327,8 @@ iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) {
327 else { 327 else {
328 switch (item->code) { 328 switch (item->code) {
329 case missingGlyphs_GmStatusCode: 329 case missingGlyphs_GmStatusCode:
330 postCommandf_App("open newtab:1 url:about:fonts"); 330 //postCommandf_App("open newtab:1 url:about:fonts");
331 makeGlyphFinder_Widget();
331 break; 332 break;
332 case ansiEscapes_GmStatusCode: 333 case ansiEscapes_GmStatusCode:
333 makeQuestion_Widget( 334 makeQuestion_Widget(
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index a52e99af..6513fc33 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -358,6 +358,7 @@ static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d);
358static void prerender_DocumentWidget_ (iAny *); 358static void prerender_DocumentWidget_ (iAny *);
359static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); 359static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t);
360static void refreshWhileScrolling_DocumentWidget_ (iAny *); 360static void refreshWhileScrolling_DocumentWidget_ (iAny *);
361static iBool requestMedia_DocumentWidget_ (iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters);
361 362
362/* TODO: The following methods are called from DocumentView, which goes the wrong way. */ 363/* TODO: The following methods are called from DocumentView, which goes the wrong way. */
363 364
@@ -1824,7 +1825,7 @@ static void draw_DocumentView_(const iDocumentView *d) {
1824 } 1825 }
1825 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { 1826 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) {
1826 updateSideIconBuf_DocumentView_(d); 1827 updateSideIconBuf_DocumentView_(d);
1827 } 1828 }
1828 const iRect docBounds = documentBounds_DocumentView_(d); 1829 const iRect docBounds = documentBounds_DocumentView_(d);
1829 const iRangei vis = visibleRange_DocumentView_(d); 1830 const iRangei vis = visibleRange_DocumentView_(d);
1830 iDrawContext ctx = { 1831 iDrawContext ctx = {
@@ -2410,6 +2411,19 @@ static const char *zipPageHeading_(const iRangecc mime) {
2410 2411
2411static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { 2412static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {
2412 iWidget *w = as_Widget(d); 2413 iWidget *w = as_Widget(d);
2414 /* Embedded images in data links should be shown immediately as they are already fetched
2415 data that is part of the document. */ {
2416 iGmDocument *doc = d->view.doc;
2417 for (size_t linkId = 1; ; linkId++) {
2418 const int linkFlags = linkFlags_GmDocument(doc, linkId);
2419 const iString *linkUrl = linkUrl_GmDocument(doc, linkId);
2420 if (!linkUrl) break;
2421 if (scheme_GmLinkFlag(linkFlags) == data_GmLinkScheme &&
2422 (linkFlags & imageFileExtension_GmLinkFlag)) {
2423 requestMedia_DocumentWidget_(d, linkId, 0);
2424 }
2425 }
2426 }
2413 /* Gempub page behavior and footer actions. */ { 2427 /* Gempub page behavior and footer actions. */ {
2414 /* TODO: move this to gempub.c */ 2428 /* TODO: move this to gempub.c */
2415 delete_Gempub(d->sourceGempub); 2429 delete_Gempub(d->sourceGempub);
@@ -2681,7 +2695,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
2681 if (loadArchive_FontPack(fp, zip)) { 2695 if (loadArchive_FontPack(fp, zip)) {
2682 appendFormat_String(&str, "# " fontpack_Icon "%s\n%s", 2696 appendFormat_String(&str, "# " fontpack_Icon "%s\n%s",
2683 cstr_String(id_FontPack(fp).id), 2697 cstr_String(id_FontPack(fp).id),
2684 cstrCollect_String(infoText_FontPack(fp))); 2698 cstrCollect_String(infoText_FontPack(fp, iTrue)));
2685 } 2699 }
2686 appendCStr_String(&str, "\n"); 2700 appendCStr_String(&str, "\n");
2687 appendCStr_String(&str, cstr_Lang("fontpack.help")); 2701 appendCStr_String(&str, cstr_Lang("fontpack.help"));
@@ -5545,10 +5559,10 @@ static void prerender_DocumentWidget_(iAny *context) {
5545 } 5559 }
5546 const iDocumentWidget *d = context; 5560 const iDocumentWidget *d = context;
5547 iDrawContext ctx = { 5561 iDrawContext ctx = {
5548 .view = &d->view, 5562 .view = &d->view,
5549 .docBounds = documentBounds_DocumentView_(&d->view), 5563 .docBounds = documentBounds_DocumentView_(&d->view),
5550 .vis = visibleRange_DocumentView_(&d->view), 5564 .vis = visibleRange_DocumentView_(&d->view),
5551 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 5565 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0
5552 }; 5566 };
5553 // printf("%u prerendering\n", SDL_GetTicks()); 5567 // printf("%u prerendering\n", SDL_GetTicks());
5554 if (d->view.visBuf->buffers[0].texture) { 5568 if (d->view.visBuf->buffers[0].texture) {
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c
index 36ab00c8..46aa6663 100644
--- a/src/ui/linkinfo.c
+++ b/src/ui/linkinfo.c
@@ -91,6 +91,14 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o
91 appendCStr_String(text_out, "\x1b[0m"); 91 appendCStr_String(text_out, "\x1b[0m");
92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); 92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) });
93 } 93 }
94 else if (scheme == data_GmLinkScheme) {
95 appendCStr_String(text_out, "\U0001f4e6 ");
96 const char *comma = strchr(cstr_String(url), ',');
97 if (!comma) {
98 comma = iMin(constEnd_String(url), constBegin_String(url) + 256);
99 }
100 appendRange_String(text_out, (iRangecc){ constBegin_String(url), comma });
101 }
94 else if (scheme != gemini_GmLinkScheme) { 102 else if (scheme != gemini_GmLinkScheme) {
95 const size_t maxDispLen = 300; 103 const size_t maxDispLen = 300;
96 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); 104 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " ");
diff --git a/src/ui/text.c b/src/ui/text.c
index c19aed2f..51531057 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -258,8 +258,6 @@ static int cmp_PrioMapItem_(const void *a, const void *b) {
258} 258}
259 259
260struct Impl_Text { 260struct Impl_Text {
261// enum iTextFont contentFont;
262// enum iTextFont headingFont;
263 float contentFontSize; 261 float contentFontSize;
264 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ 262 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
265 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ 263 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
@@ -276,7 +274,8 @@ struct Impl_Text {
276 int ansiFlags; 274 int ansiFlags;
277 int baseFontId; /* base attributes (for restoring via escapes) */ 275 int baseFontId; /* base attributes (for restoring via escapes) */
278 int baseFgColorId; 276 int baseFgColorId;
279 iBool missingGlyphs; /* true if a glyph couldn't be found */ 277 iBool missingGlyphs; /* true if a glyph couldn't be found */
278 iChar missingChars[20]; /* rotating buffer of the latest missing characters */
280}; 279};
281 280
282iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) 281iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
@@ -357,6 +356,8 @@ static void initFonts_Text_(iText *d) {
357 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts)); 356 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts));
358#endif 357#endif
359 gap_Text = iRound(gap_UI * d->contentFontSize); 358 gap_Text = iRound(gap_UI * d->contentFontSize);
359// d->missingGlyphs = iFalse;
360// iZap(d->missingChars);
360} 361}
361 362
362static void deinitFonts_Text_(iText *d) { 363static void deinitFonts_Text_(iText *d) {
@@ -424,6 +425,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
424 d->baseFontId = -1; 425 d->baseFontId = -1;
425 d->baseFgColorId = -1; 426 d->baseFgColorId = -1;
426 d->missingGlyphs = iFalse; 427 d->missingGlyphs = iFalse;
428 iZap(d->missingChars);
427 d->render = render; 429 d->render = render;
428 /* A grayscale palette for rasterized glyphs. */ { 430 /* A grayscale palette for rasterized glyphs. */ {
429 SDL_Color colors[256]; 431 SDL_Color colors[256];
@@ -610,8 +612,23 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
610 } 612 }
611 } 613 }
612 if (!*glyphIndex) { 614 if (!*glyphIndex) {
613 activeText_->missingGlyphs = iTrue; 615 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int) ch); fflush(stderr);
614 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 616 iText *tx = activeText_;
617 tx->missingGlyphs = iTrue;
618 /* Remember a few of the latest missing characters. */
619 iBool gotIt = iFalse;
620 for (size_t i = 0; i < iElemCount(tx->missingChars); i++) {
621 if (tx->missingChars[i] == ch) {
622 gotIt = iTrue;
623 break;
624 }
625 }
626 if (!gotIt) {
627 memmove(tx->missingChars + 1,
628 tx->missingChars,
629 sizeof(tx->missingChars) - sizeof(tx->missingChars[0]));
630 tx->missingChars[0] = ch;
631 }
615 } 632 }
616 return d; 633 return d;
617} 634}
@@ -2250,6 +2267,19 @@ iBool checkMissing_Text(void) {
2250 return missing; 2267 return missing;
2251} 2268}
2252 2269
2270iChar missing_Text(size_t index) {
2271 const iText *d = activeText_;
2272 if (index >= iElemCount(d->missingChars)) {
2273 return 0;
2274 }
2275 return d->missingChars[index];
2276}
2277
2278void resetMissing_Text(iText *d) {
2279 d->missingGlyphs = iFalse;
2280 iZap(d->missingChars);
2281}
2282
2253SDL_Texture *glyphCache_Text(void) { 2283SDL_Texture *glyphCache_Text(void) {
2254 return activeText_->cache; 2284 return activeText_->cache;
2255} 2285}
diff --git a/src/ui/text.h b/src/ui/text.h
index b952df84..e741880d 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -227,6 +227,8 @@ struct Impl_WrapText {
227iTextMetrics measure_WrapText (iWrapText *, int fontId); 227iTextMetrics measure_WrapText (iWrapText *, int fontId);
228iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); 228iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color);
229 229
230iChar missing_Text (size_t index);
231void resetMissing_Text (iText *);
230iBool checkMissing_Text (void); /* returns the flag, and clears it */ 232iBool checkMissing_Text (void); /* returns the flag, and clears it */
231SDL_Texture * glyphCache_Text (void); 233SDL_Texture * glyphCache_Text (void);
232 234
diff --git a/src/ui/util.c b/src/ui/util.c
index 5dd8a0bd..41f8eaa9 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -903,6 +903,7 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
903#else 903#else
904 /* Non-native custom popup menu. This may still be displayed inside a separate window. */ 904 /* Non-native custom popup menu. This may still be displayed inside a separate window. */
905 setDrawBufferEnabled_Widget(menu, iTrue); 905 setDrawBufferEnabled_Widget(menu, iTrue);
906 setFrameColor_Widget(menu, uiSeparator_ColorId);
906 setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); 907 setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId);
907 if (deviceType_App() != desktop_AppDeviceType) { 908 if (deviceType_App() != desktop_AppDeviceType) {
908 setPadding1_Widget(menu, 2 * gap_UI); 909 setPadding1_Widget(menu, 2 * gap_UI);
@@ -1084,12 +1085,12 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
1084 setFlags_Widget(d, hidden_WidgetFlag, iFalse); 1085 setFlags_Widget(d, hidden_WidgetFlag, iFalse);
1085 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); 1086 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
1086 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); 1087 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
1087 if (!isPortraitPhone) { 1088// if (!isPortraitPhone) {
1088 setFrameColor_Widget(d, uiBackgroundSelected_ColorId); 1089// setFrameColor_Widget(d, uiSeparator_ColorId);
1089 } 1090// }
1090 else { 1091// else {
1091 setFrameColor_Widget(d, none_ColorId); 1092// setFrameColor_Widget(d, none_ColorId);
1092 } 1093// }
1093 arrange_Widget(d); /* need to know the height */ 1094 arrange_Widget(d); /* need to know the height */
1094 iBool allowOverflow = iFalse; 1095 iBool allowOverflow = iFalse;
1095 /* A vertical offset determined by a possible selected label in the menu. */ 1096 /* A vertical offset determined by a possible selected label in the menu. */
@@ -1327,6 +1328,7 @@ int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) {
1327iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) { 1328iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) {
1328 iLabelWidget *button = new_LabelWidget(label, "menu.open"); 1329 iLabelWidget *button = new_LabelWidget(label, "menu.open");
1329 iWidget *menu = makeMenu_Widget(as_Widget(button), items, n); 1330 iWidget *menu = makeMenu_Widget(as_Widget(button), items, n);
1331 setFrameColor_Widget(menu, uiBackgroundSelected_ColorId);
1330 setId_Widget(menu, "menu"); 1332 setId_Widget(menu, "menu");
1331 return button; 1333 return button;
1332} 1334}
@@ -3451,6 +3453,54 @@ iWidget *makeTranslation_Widget(iWidget *parent) {
3451 return dlg; 3453 return dlg;
3452} 3454}
3453 3455
3456iWidget *makeGlyphFinder_Widget(void) {
3457 iString msg;
3458 iString command;
3459 init_String(&msg);
3460 initCStr_String(&command, "!font.find chars:");
3461 for (size_t i = 0; ; i++) {
3462 iChar ch = missing_Text(i);
3463 if (!ch) break;
3464 appendFormat_String(&msg, " U+%04X", ch);
3465 appendChar_String(&command, ch);
3466 }
3467 iArray items;
3468 init_Array(&items, sizeof(iMenuItem));
3469 if (!isEmpty_String(&msg)) {
3470 prependCStr_String(&msg, "${dlg.glyphfinder.missing} ");
3471 appendCStr_String(&msg, "\n\n${dlg.glyphfinder.help}");
3472 pushBackN_Array(
3473 &items,
3474 (iMenuItem[]){
3475 { "${menu.fonts}", 0, 0, "!open newtab:1 url:about:fonts" },
3476 { "${dlg.glyphfinder.disable}", 0, 0, "prefs.font.warnmissing.changed arg:0" },
3477 { "---" },
3478 { uiTextCaution_ColorEscape magnifyingGlass_Icon " ${dlg.glyphfinder.search}",
3479 0,
3480 0,
3481 cstr_String(&command) },
3482 { "${close}", 0, 0, "cancel" } },
3483 5);
3484 }
3485 else {
3486 setCStr_String(&msg, "${dlg.glyphfinder.help.empty}");
3487 pushBackN_Array(&items,
3488 (iMenuItem[]){ { "${menu.reload}", 0, 0, "navigate.reload" },
3489 { "${close}", 0, 0, "cancel" } },
3490 2);
3491 }
3492 iWidget *dlg = makeQuestion_Widget("${heading.glyphfinder}", cstr_String(&msg),
3493 constData_Array(&items),
3494 size_Array(&items));
3495 arrange_Widget(dlg);
3496 deinit_Array(&items);
3497 deinit_String(&command);
3498 deinit_String(&msg);
3499 return dlg;
3500}
3501
3502/*----------------------------------------------------------------------------------------------*/
3503
3454void init_PerfTimer(iPerfTimer *d) { 3504void init_PerfTimer(iPerfTimer *d) {
3455 d->ticks = SDL_GetPerformanceCounter(); 3505 d->ticks = SDL_GetPerformanceCounter();
3456} 3506}
diff --git a/src/ui/util.h b/src/ui/util.h
index 98ce784c..0289d579 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -342,6 +342,7 @@ iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *titl
342iWidget * makeIdentityCreation_Widget (void); 342iWidget * makeIdentityCreation_Widget (void);
343iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); 343iWidget * makeFeedSettings_Widget (uint32_t bookmarkId);
344iWidget * makeTranslation_Widget (iWidget *parent); 344iWidget * makeTranslation_Widget (iWidget *parent);
345iWidget * makeGlyphFinder_Widget (void);
345 346
346const char * languageId_String (const iString *menuItemLabel); 347const char * languageId_String (const iString *menuItemLabel);
347int languageIndex_CStr (const char *langId); 348int languageIndex_CStr (const char *langId);