diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 66 | ||||
-rw-r--r-- | src/app.h | 2 | ||||
-rw-r--r-- | src/defs.h | 1 | ||||
-rw-r--r-- | src/fontpack.c | 120 | ||||
-rw-r--r-- | src/fontpack.h | 4 | ||||
-rw-r--r-- | src/gmdocument.c | 4 | ||||
-rw-r--r-- | src/ui/banner.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 26 | ||||
-rw-r--r-- | src/ui/linkinfo.c | 8 | ||||
-rw-r--r-- | src/ui/text.c | 40 | ||||
-rw-r--r-- | src/ui/text.h | 2 | ||||
-rw-r--r-- | src/ui/util.c | 62 | ||||
-rw-r--r-- | src/ui/util.h | 1 |
13 files changed, 299 insertions, 40 deletions
@@ -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 | ||
2264 | void 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 | |||
2260 | static void invalidateCachedDocuments_App_(void) { | 2271 | static 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); |
@@ -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 | ||
72 | const iString *execPath_App (void); | 73 | const iString *execPath_App (void); |
@@ -142,6 +143,7 @@ iDocumentWidget * document_Command (const char *cmd); | |||
142 | void openInDefaultBrowser_App(const iString *url); | 143 | void openInDefaultBrowser_App(const iString *url); |
143 | void revealPath_App (const iString *path); | 144 | void revealPath_App (const iString *path); |
144 | void resetFonts_App (void); | 145 | void resetFonts_App (void); |
146 | void availableFontsChanged_App(void); | ||
145 | 147 | ||
146 | iMainWindow * mainWindow_App (void); | 148 | iMainWindow * mainWindow_App (void); |
147 | void closePopups_App (void); | 149 | void closePopups_App (void); |
@@ -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 | ||
751 | iString *infoText_FontPack(const iFontPack *d) { | 752 | iString *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 | ||
1023 | void installFontFile_Fonts(const iString *fileName, const iBlock *data) { | 1034 | void 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 | ||
1033 | void enablePack_Fonts(const iString *packId, iBool enable) { | 1045 | void 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 | ||
1050 | iDefineClass(FontFile) | 1063 | static 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 | ||
1150 | void 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 | |||
1159 | iDefineClass(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 | |||
160 | iBool isDisabled_FontPack (const iFontPack *); | 160 | iBool isDisabled_FontPack (const iFontPack *); |
161 | iBool isReadOnly_FontPack (const iFontPack *); | 161 | iBool isReadOnly_FontPack (const iFontPack *); |
162 | const iPtrArray * listSpecs_FontPack (const iFontPack *); | 162 | const iPtrArray * listSpecs_FontPack (const iFontPack *); |
163 | iString * infoText_FontPack (const iFontPack *); | 163 | iString * infoText_FontPack (const iFontPack *, iBool isFull); |
164 | const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); | 164 | const iArray * actions_FontPack (const iFontPack *, iBool showInstalled); |
165 | 165 | ||
166 | const iString * idFromUrl_FontPack (const iString *url); | 166 | const iString * idFromUrl_FontPack (const iString *url); |
@@ -186,3 +186,5 @@ void reload_Fonts (void); | |||
186 | iLocalDef iBool isInstalled_Fonts(const char *packId) { | 186 | iLocalDef iBool isInstalled_Fonts(const char *packId) { |
187 | return pack_Fonts(packId) != NULL; | 187 | return pack_Fonts(packId) != NULL; |
188 | } | 188 | } |
189 | |||
190 | void 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); | |||
358 | static void prerender_DocumentWidget_ (iAny *); | 358 | static void prerender_DocumentWidget_ (iAny *); |
359 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); | 359 | static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t); |
360 | static void refreshWhileScrolling_DocumentWidget_ (iAny *); | 360 | static void refreshWhileScrolling_DocumentWidget_ (iAny *); |
361 | static 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 | ||
2411 | static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { | 2412 | static 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 | ||
260 | struct Impl_Text { | 260 | struct 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 | ||
282 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) | 281 | iDefineTypeConstructionArgs(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 | ||
362 | static void deinitFonts_Text_(iText *d) { | 363 | static 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 | ||
2270 | iChar 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 | |||
2278 | void resetMissing_Text(iText *d) { | ||
2279 | d->missingGlyphs = iFalse; | ||
2280 | iZap(d->missingChars); | ||
2281 | } | ||
2282 | |||
2253 | SDL_Texture *glyphCache_Text(void) { | 2283 | SDL_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 { | |||
227 | iTextMetrics measure_WrapText (iWrapText *, int fontId); | 227 | iTextMetrics measure_WrapText (iWrapText *, int fontId); |
228 | iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); | 228 | iTextMetrics draw_WrapText (iWrapText *, int fontId, iInt2 pos, int color); |
229 | 229 | ||
230 | iChar missing_Text (size_t index); | ||
231 | void resetMissing_Text (iText *); | ||
230 | iBool checkMissing_Text (void); /* returns the flag, and clears it */ | 232 | iBool checkMissing_Text (void); /* returns the flag, and clears it */ |
231 | SDL_Texture * glyphCache_Text (void); | 233 | SDL_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) { | |||
1327 | iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *items, size_t n) { | 1328 | iLabelWidget *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 | ||
3456 | iWidget *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 | |||
3454 | void init_PerfTimer(iPerfTimer *d) { | 3504 | void 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 | |||
342 | iWidget * makeIdentityCreation_Widget (void); | 342 | iWidget * makeIdentityCreation_Widget (void); |
343 | iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); | 343 | iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); |
344 | iWidget * makeTranslation_Widget (iWidget *parent); | 344 | iWidget * makeTranslation_Widget (iWidget *parent); |
345 | iWidget * makeGlyphFinder_Widget (void); | ||
345 | 346 | ||
346 | const char * languageId_String (const iString *menuItemLabel); | 347 | const char * languageId_String (const iString *menuItemLabel); |
347 | int languageIndex_CStr (const char *langId); | 348 | int languageIndex_CStr (const char *langId); |