summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
m---------lib/the_Foundation0
-rw-r--r--po/en.po27
-rw-r--r--res/lang/cs.binbin32086 -> 32719 bytes
-rw-r--r--res/lang/de.binbin30896 -> 31529 bytes
-rw-r--r--res/lang/en.binbin26965 -> 27598 bytes
-rw-r--r--res/lang/es.binbin30808 -> 31441 bytes
-rw-r--r--res/lang/es_MX.binbin28035 -> 28668 bytes
-rw-r--r--res/lang/fi.binbin30606 -> 31239 bytes
-rw-r--r--res/lang/fr.binbin31693 -> 32326 bytes
-rw-r--r--res/lang/hu.binbin31718 -> 32351 bytes
-rw-r--r--res/lang/ia.binbin29748 -> 30381 bytes
-rw-r--r--res/lang/isv.binbin25686 -> 26319 bytes
-rw-r--r--res/lang/nl.binbin29075 -> 29708 bytes
-rw-r--r--res/lang/pl.binbin30321 -> 30954 bytes
-rw-r--r--res/lang/ru.binbin45668 -> 46301 bytes
-rw-r--r--res/lang/sk.binbin26022 -> 26655 bytes
-rw-r--r--res/lang/sr.binbin44964 -> 45597 bytes
-rw-r--r--res/lang/tok.binbin27801 -> 28434 bytes
-rw-r--r--res/lang/tr.binbin29912 -> 30545 bytes
-rw-r--r--res/lang/uk.binbin45045 -> 45678 bytes
-rw-r--r--res/lang/zh_Hans.binbin25930 -> 26563 bytes
-rw-r--r--res/lang/zh_Hant.binbin26328 -> 26961 bytes
-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
36 files changed, 327 insertions, 41 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 69ae5f6f..11348fcc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@
18cmake_minimum_required (VERSION 3.9) 18cmake_minimum_required (VERSION 3.9)
19 19
20project (Lagrange 20project (Lagrange
21 VERSION 1.10.5 21 VERSION 1.11.0
22 DESCRIPTION "A Beautiful Gemini Client" 22 DESCRIPTION "A Beautiful Gemini Client"
23 LANGUAGES C 23 LANGUAGES C
24) 24)
diff --git a/lib/the_Foundation b/lib/the_Foundation
Subproject 453f05f6efb46824ff0dca0174036c7624473e4 Subproject f2f7fef560fc8642e3e449239d7e00f14491e13
diff --git a/po/en.po b/po/en.po
index e0e086e3..cc5f2091 100644
--- a/po/en.po
+++ b/po/en.po
@@ -1181,6 +1181,33 @@ msgstr "Russian"
1181msgid "lang.es" 1181msgid "lang.es"
1182msgstr "Spanish" 1182msgstr "Spanish"
1183 1183
1184msgid "heading.glyphfinder"
1185msgstr "Missing Glyphs"
1186
1187msgid "dlg.glyphfinder.missing"
1188msgstr "The following characters could not be shown:"
1189
1190msgid "dlg.glyphfinder.help"
1191msgstr "You can try searching the skyjake.fi Font Library for fonts that provide glyphs for these characters, or manually install new TrueType fonts."
1192
1193msgid "dlg.glyphfinder.help.empty"
1194msgstr "Please reload the page to check again for missing glyphs."
1195
1196msgid "dlg.glyphfinder.disable"
1197msgstr "Disable Warnings"
1198
1199msgid "dlg.glyphfinder.search"
1200msgstr "Search Font Library"
1201
1202msgid "heading.glyphfinder.results"
1203msgstr "Search Results"
1204
1205msgid "glyphfinder.results"
1206msgstr "The following fontpacks provide one or more of the missing glyphs:"
1207
1208msgid "glyphfinder.results.empty"
1209msgstr "Sorry, no matching fontpacks were found."
1210
1184msgid "heading.newident" 1211msgid "heading.newident"
1185msgstr "New Identity" 1212msgstr "New Identity"
1186 1213
diff --git a/res/lang/cs.bin b/res/lang/cs.bin
index 6624fbd4..98cd5d32 100644
--- a/res/lang/cs.bin
+++ b/res/lang/cs.bin
Binary files differ
diff --git a/res/lang/de.bin b/res/lang/de.bin
index 9c3d4541..ecb601aa 100644
--- a/res/lang/de.bin
+++ b/res/lang/de.bin
Binary files differ
diff --git a/res/lang/en.bin b/res/lang/en.bin
index 5f649846..758adbba 100644
--- a/res/lang/en.bin
+++ b/res/lang/en.bin
Binary files differ
diff --git a/res/lang/es.bin b/res/lang/es.bin
index 09b7151b..d50a2cae 100644
--- a/res/lang/es.bin
+++ b/res/lang/es.bin
Binary files differ
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin
index dfcd6306..40379ee0 100644
--- a/res/lang/es_MX.bin
+++ b/res/lang/es_MX.bin
Binary files differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 44d2741d..18a4c11d 100644
--- a/res/lang/fi.bin
+++ b/res/lang/fi.bin
Binary files differ
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index ad16b2e7..ba098242 100644
--- a/res/lang/fr.bin
+++ b/res/lang/fr.bin
Binary files differ
diff --git a/res/lang/hu.bin b/res/lang/hu.bin
index c070dd49..94af8aa8 100644
--- a/res/lang/hu.bin
+++ b/res/lang/hu.bin
Binary files differ
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index 34633dd1..1aac22fe 100644
--- a/res/lang/ia.bin
+++ b/res/lang/ia.bin
Binary files differ
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 3be01643..c3931a80 100644
--- a/res/lang/isv.bin
+++ b/res/lang/isv.bin
Binary files differ
diff --git a/res/lang/nl.bin b/res/lang/nl.bin
index dcc9fe97..579f0df8 100644
--- a/res/lang/nl.bin
+++ b/res/lang/nl.bin
Binary files differ
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index a32e9d10..448be098 100644
--- a/res/lang/pl.bin
+++ b/res/lang/pl.bin
Binary files differ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index 6a9b8e67..098c2b11 100644
--- a/res/lang/ru.bin
+++ b/res/lang/ru.bin
Binary files differ
diff --git a/res/lang/sk.bin b/res/lang/sk.bin
index 833872c9..fc984d97 100644
--- a/res/lang/sk.bin
+++ b/res/lang/sk.bin
Binary files differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index a1e87e95..49ec2b90 100644
--- a/res/lang/sr.bin
+++ b/res/lang/sr.bin
Binary files differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index d17f075e..cfbee57e 100644
--- a/res/lang/tok.bin
+++ b/res/lang/tok.bin
Binary files differ
diff --git a/res/lang/tr.bin b/res/lang/tr.bin
index 981ced04..8e9a09c4 100644
--- a/res/lang/tr.bin
+++ b/res/lang/tr.bin
Binary files differ
diff --git a/res/lang/uk.bin b/res/lang/uk.bin
index d5e78f6f..ae80d456 100644
--- a/res/lang/uk.bin
+++ b/res/lang/uk.bin
Binary files differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 246b7c42..2f0c03fa 100644
--- a/res/lang/zh_Hans.bin
+++ b/res/lang/zh_Hans.bin
Binary files differ
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index 0e94f66a..f978013c 100644
--- a/res/lang/zh_Hant.bin
+++ b/res/lang/zh_Hant.bin
Binary files differ
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 dbe29c4e..3d2e31b2 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"));
@@ -5544,10 +5558,10 @@ static void prerender_DocumentWidget_(iAny *context) {
5544 } 5558 }
5545 const iDocumentWidget *d = context; 5559 const iDocumentWidget *d = context;
5546 iDrawContext ctx = { 5560 iDrawContext ctx = {
5547 .view = &d->view, 5561 .view = &d->view,
5548 .docBounds = documentBounds_DocumentView_(&d->view), 5562 .docBounds = documentBounds_DocumentView_(&d->view),
5549 .vis = visibleRange_DocumentView_(&d->view), 5563 .vis = visibleRange_DocumentView_(&d->view),
5550 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 5564 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0
5551 }; 5565 };
5552 // printf("%u prerendering\n", SDL_GetTicks()); 5566 // printf("%u prerendering\n", SDL_GetTicks());
5553 if (d->view.visBuf->buffers[0].texture) { 5567 if (d->view.visBuf->buffers[0].texture) {
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c
index e800d6a3..72d76678 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 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); 103 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " ");
96 appendCStrN_String(text_out, cstr_String(url), iMin(500, size_String(url))); 104 appendCStrN_String(text_out, cstr_String(url), iMin(500, size_String(url)));
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);