Lagrange [work/v1.8]

Installing individual TTF files; generate fontpack.ini

=> 57cbcc6e864abd368bde93154b3580147936201c

diff --git a/po/en.po b/po/en.po
index 0d5707b1..cc6ea545 100644
--- a/po/en.po
+++ b/po/en.po
@@ -1921,6 +1921,9 @@ msgstr "Enable \"%s\""
 msgid "fontpack.disable"
 msgstr "Disable \"%s\""
 
+msgid "fontpack.export"
+msgstr "View fontpack.ini template"
+
 #, c-format
 msgid "fontpack.install"
 msgstr "Install \"%s\""
@@ -1943,3 +1946,21 @@ msgstr "Do you really want to permanently delete\nthe fontpack \"%s\"?"
 msgid "dlg.fontpack.delete"
 msgstr "Delete Fontpack"
 
+msgid "fontpack.help"
+msgstr "Lagrange fontpacks are ZIP archives that contain a set of font files and associated configuration parameters. Once installed, the fonts can be used for document content and the UI. The active fonts are selected using Preferences > Fonts."
+
+msgid "fontpack.install.ttf"
+msgstr "Install TrueType Font"
+
+msgid "fontpack.open.fontsdir"
+msgstr "Open User Fonts Directory"
+
+msgid "fontpack.open.aboutfonts"
+msgstr "Show Installed Fonts"
+
+msgid "truetype.help"
+msgstr "Lagrange attempts to load all individual TrueType files that are copied to the user fonts directory."
+
+msgid "truetype.help.installed"
+msgstr "This font is installed in the user fonts directory."
+
diff --git a/res/lang/de.bin b/res/lang/de.bin
index cf3250a5..e6b1bf85 100644
Binary files a/res/lang/de.bin and b/res/lang/de.bin differ
diff --git a/res/lang/en.bin b/res/lang/en.bin
index ffdd4226..2ef67f55 100644
Binary files a/res/lang/en.bin and b/res/lang/en.bin differ
diff --git a/res/lang/eo.bin b/res/lang/eo.bin
index 8ff3294e..89ec6fea 100644
Binary files a/res/lang/eo.bin and b/res/lang/eo.bin differ
diff --git a/res/lang/es.bin b/res/lang/es.bin
index 810fe574..701f0478 100644
Binary files a/res/lang/es.bin and b/res/lang/es.bin differ
diff --git a/res/lang/es_MX.bin b/res/lang/es_MX.bin
index 98234c64..3013590a 100644
Binary files a/res/lang/es_MX.bin and b/res/lang/es_MX.bin differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 8d307700..edc0df2b 100644
Binary files a/res/lang/fi.bin and b/res/lang/fi.bin differ
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index 313e4043..c4b482de 100644
Binary files a/res/lang/fr.bin and b/res/lang/fr.bin differ
diff --git a/res/lang/gl.bin b/res/lang/gl.bin
index cee70fde..6b1ca49a 100644
Binary files a/res/lang/gl.bin and b/res/lang/gl.bin differ
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index e7e4a2ea..7f677aab 100644
Binary files a/res/lang/ia.bin and b/res/lang/ia.bin differ
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index b29318ae..6488b20f 100644
Binary files a/res/lang/ie.bin and b/res/lang/ie.bin differ
diff --git a/res/lang/isv.bin b/res/lang/isv.bin
index 80bc275f..0f783c31 100644
Binary files a/res/lang/isv.bin and b/res/lang/isv.bin differ
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index 208e9e3c..e2d8bc6a 100644
Binary files a/res/lang/pl.bin and b/res/lang/pl.bin differ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index 2a8554a5..a0f75c6c 100644
Binary files a/res/lang/ru.bin and b/res/lang/ru.bin differ
diff --git a/res/lang/sk.bin b/res/lang/sk.bin
index f5172ada..91bd1358 100644
Binary files a/res/lang/sk.bin and b/res/lang/sk.bin differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index 14ca2240..55a289bd 100644
Binary files a/res/lang/sr.bin and b/res/lang/sr.bin differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index fad37087..938f85d4 100644
Binary files a/res/lang/tok.bin and b/res/lang/tok.bin differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index 2f8e6beb..c6beace1 100644
Binary files a/res/lang/zh_Hans.bin and b/res/lang/zh_Hans.bin differ
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index 2b11cbd2..20128ba3 100644
Binary files a/res/lang/zh_Hant.bin and b/res/lang/zh_Hant.bin differ
diff --git a/src/app.c b/src/app.c
index 485c2495..46856e22 100644
--- a/src/app.c
+++ b/src/app.c
@@ -836,6 +836,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
     loadPalette_Color(dataDir_App_());
     setThemePalette_Color(d->prefs.theme); /* default UI colors */
     loadPrefs_App_(d);
+    updateActive_Fonts();
     load_Keys(dataDir_App_());
     /* See if the user wants to override the window size. */ {
         iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption));
@@ -3069,13 +3070,7 @@ iBool handleCommand_App(const char *cmd) {
     }
     else if (equal_Command(cmd, "fontpack.enable")) {
         const iString *packId = collect_String(suffix_Command(cmd, "id"));
-        if (arg_Command(cmd)) {
-            remove_StringSet(d->prefs.disabledFontPacks, packId);
-        }
-        else {
-            insert_StringSet(d->prefs.disabledFontPacks, packId);
-        }
-        resetFonts_App();
+        enablePack_Fonts(packId, arg_Command(cmd));
         postCommand_App("navigate.reload");
         return iTrue;
     }
diff --git a/src/fontpack.c b/src/fontpack.c
index 226392bc..f22ab8dc 100644
--- a/src/fontpack.c
+++ b/src/fontpack.c
@@ -90,6 +90,14 @@ static void load_FontFile_(iFontFile *d, const iBlock *data) {
 #endif
 }
 
+static iBool detectMonospace_FontFile_(const iFontFile *d) {
+    int em, i, period;
+    stbtt_GetCodepointHMetrics(&d->stbInfo, 'M', &em, NULL);
+    stbtt_GetCodepointHMetrics(&d->stbInfo, 'i', &i, NULL);
+    stbtt_GetCodepointHMetrics(&d->stbInfo, '.', &period, NULL);
+    return em == i && em == period;
+}
+
 static void unload_FontFile_(iFontFile *d) {
 #if defined(LAGRANGE_ENABLE_HARFBUZZ)
     /* HarfBuzz objects. */
@@ -300,6 +308,8 @@ static iBlock *readFile_FontPack_(const iFontPack *d, const iString *path) {
     return data;
 }
 
+static const char *styles_[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" };
+
 void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key,
                                  const iTomlValue *value) {
     iFontPack *d = context;
@@ -359,9 +369,8 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr
                      ((int) number_TomlValue(value)) & 1);
     }
     else if (value->type == string_TomlType) {
-        const char *styles[max_FontStyle] = { "regular", "italic", "light", "semibold", "bold" };
-        iForIndices(i, styles) {
-            if (!cmp_String(key, styles[i]) && !d->loadSpec->styles[i]) {
+        iForIndices(i, styles_) {
+            if (!cmp_String(key, styles_[i]) && !d->loadSpec->styles[i]) {
                 iFontFile *ff = NULL;
                 iString *fontFileId = concat_Path(d->loadPath, value->value.string);
                 if (!(ff = findFile_Fonts_(&fonts_, fontFileId))) {
@@ -441,6 +450,7 @@ void setLoadPath_FontPack(iFontPack *d, const iString *path) {
     /* Pack ID is based on the file name. */
     setRange_String(&d->id, baseName_Path(path));
     setRange_String(&d->id, withoutExtension_Path(&d->id));
+    replace_String(&d->id, " ", "-");
 }
 
 const iString *idFromUrl_FontPack(const iString *url) {
@@ -449,6 +459,7 @@ const iString *idFromUrl_FontPack(const iString *url) {
     init_Url(&parts, url);
     setRange_String(id, baseName_Path(collectNewRange_String(parts.path)));
     setRange_String(id, withoutExtension_Path(id));
+    replace_String(id, " ", "-");
     return collect_String(id);
 }
 
@@ -592,6 +603,49 @@ void init_Fonts(const char *userDir) {
         }
         iRelease(f);
     }
+    /* Individual TrueType files in the user fonts directory. */ {
+        iForEach(DirFileInfo, entry, iClob(new_DirFileInfo(userFontsDirectory_Fonts_(d)))) {
+            const iString *entryPath = path_FileInfo(entry.value);
+            if (endsWithCase_String(entryPath, ".ttf")) {
+                iFile *f = new_File(entryPath);
+                iFontFile *font = NULL;
+                if (open_File(f, readOnly_FileMode)) {
+                    iBlock *data = readAll_File(f);
+                    font = new_FontFile();
+                    load_FontFile_(font, data);
+                    set_String(&font->id, entryPath);
+                    pushBack_ObjectList(fonts_.files, font); /* centralized ownership */
+                    iRelease(font);
+                    delete_Block(data);
+                }
+                iRelease(f);
+                if (!font) {
+                    fprintf(stderr, "[fonts] failed to load: %s\n", cstr_String(entryPath));
+                    continue;
+                }
+                iFontPack *pack = new_FontPack();
+                setStandalone_FontPack(pack, iTrue);                
+                iFontSpec *spec = new_FontSpec();
+                spec->flags |= user_FontSpecFlag;
+                if (detectMonospace_FontFile_(font)) {
+                    spec->flags |= monospace_FontSpecFlag;
+                }
+                setRange_String(&spec->id, baseName_Path(collect_String(lower_String(&font->id))));
+                setRange_String(&spec->id, withoutExtension_Path(&spec->id));                
+                replace_String(&spec->id, " ", "-");
+                setRange_String(&spec->name, baseName_Path(&font->id));
+                setRange_String(&spec->name, withoutExtension_Path(&spec->name));
+                set_String(&spec->sourcePath, entryPath);
+                iForIndices(j, spec->styles) {
+                    spec->styles[j] = ref_Object(font);
+                }
+                pushBack_PtrArray(&pack->fonts, spec);
+                set_String(&pack->id, &spec->id);
+                pack->loadPath = copy_String(entryPath);
+                pushBack_PtrArray(&d->packs, pack);
+            }
+        }
+    }    
     sortSpecs_Fonts_(d);
 }
 
@@ -679,7 +733,7 @@ iString *infoText_FontPack(const iFontPack *d) {
     return str;
 }
 
-const iArray *actions_FontPack(const iFontPack *d) {
+const iArray *actions_FontPack(const iFontPack *d, iBool showInstalled) {
     iArray           *items     = new_Array(sizeof(iMenuItem));
     const iFontPackId fp        = id_FontPack(d);
     const char       *fpId      = cstr_String(fp.id);
@@ -687,33 +741,44 @@ const iArray *actions_FontPack(const iFontPack *d) {
     const iBool       isEnabled = !isDisabled_FontPack(d);
     if (isInstalled_Fonts(fpId)) {
         if (d->version > installed->version) {
-            pushBack_Array(items, &(iMenuItem){
-                format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version),
-                SDLK_RETURN, 0, "fontpack.install"
-            });
+            pushBack_Array(
+                items,
+                &(iMenuItem){ format_Lang(add_Icon " ${fontpack.upgrade}", fpId, d->version),
+                              SDLK_RETURN,
+                              0,
+                              "fontpack.install" });
         }
-        pushBack_Array(items, &(iMenuItem){
-            format_Lang(isEnabled ? close_Icon " ${fontpack.disable}"
-                      : leftArrowhead_Icon " ${fontpack.enable}", fpId), 0, 0,
-            format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) });
-        if (!d->isReadOnly && installed->loadPath && d->loadPath &&
+        pushBack_Array(
+            items,
+            &(iMenuItem){ format_Lang(isEnabled ? close_Icon " ${fontpack.disable}"
+                                                : leftArrowhead_Icon " ${fontpack.enable}",
+                                      fpId),
+                          0,
+                          0,
+                          format_CStr("fontpack.enable arg:%d id:%s", !isEnabled, fpId) });
+        if (!d->isReadOnly && !d->isStandalone && installed->loadPath && d->loadPath &&
             !cmpString_String(installed->loadPath, d->loadPath)) {
-            pushBack_Array(items, &(iMenuItem){
-                format_Lang(delete_Icon " ${fontpack.delete}", fpId), 0, 0,
-                format_CStr("fontpack.delete id:%s", fpId) });
+            pushBack_Array(items,
+                           &(iMenuItem){ format_Lang(delete_Icon " ${fontpack.delete}", fpId),
+                                         0,
+                                         0,
+                                         format_CStr("fontpack.delete id:%s", fpId) });
         }
     }
     else if (d->isStandalone) {
-        pushBack_Array(items, &(iMenuItem){
-            format_Lang(add_Icon " ${fontpack.install}", fpId),
-            SDLK_RETURN, 0, "fontpack.install"
-        });
-        pushBack_Array(items, &(iMenuItem){
-            download_Icon " " saveToDownloads_Label,
-            0,
-            0,
-            "document.save"
-        });
+        pushBack_Array(items,
+                       &(iMenuItem){ format_Lang(add_Icon " ${fontpack.install}", fpId),
+                                     SDLK_RETURN,
+                                     0,
+                                     "fontpack.install" });
+        pushBack_Array(
+            items, &(iMenuItem){ download_Icon " " saveToDownloads_Label, 0, 0, "document.save" });
+    }
+    if (showInstalled) {
+        pushBack_Array(
+            items,
+            &(iMenuItem){
+                fontpack_Icon " ${fontpack.open.aboutfonts}", 0, 0, "!open url:about:fonts" });
     }
     return collect_Array(items);
 }
@@ -722,8 +787,64 @@ iBool isDisabled_FontPack(const iFontPack *d) {
     return contains_StringSet(prefs_App()->disabledFontPacks, &d->id);
 }
 
-const iString *infoPage_Fonts(void) {
+const iPtrArray *disabledSpecs_Fonts_(const iFonts *d) {
+    iPtrArray *list = collectNew_PtrArray();
+    iConstForEach(PtrArray, i, &d->packs) {
+        const iFontPack *pack = i.ptr;
+        if (isDisabled_FontPack(pack)) {
+            iConstForEach(PtrArray, j, &pack->fonts) {
+                pushBack_PtrArray(list, j.ptr);
+            }
+        }
+    }
+    return list;
+}
+
+static const char *boolStr_(int value) {
+    return value ? "true" : "false";
+}
+
+static const iString *exportFontPackIni_Fonts_(const iFonts *d, const iRangecc packId) {
+    iString *str = collectNew_String();
+    const iFontPack *pack = pack_Fonts(cstr_Rangecc(packId));
+    if (!pack) {
+        appendFormat_String(str, "Fontpack \"%s\" not found.\n", cstr_Rangecc(packId));
+        return str;
+    }
+    const iFontPackId fp = id_FontPack(pack);
+    appendCStr_String(str, "To create a fontpack, add this fontpack.ini into a ZIP archive whose "
+                      "name has the .fontpack file extension.\n```Fontpack configuration\n");
+    appendFormat_String(str, "version = %d\n", fp.version);
+    iConstForEach(PtrArray, i, &pack->fonts) {
+        const iFontSpec *spec = i.ptr;
+        appendFormat_String(str, "\n[%s]\n", cstr_String(&spec->id));
+        appendFormat_String(str, "name = \"%s\"\n", cstrCollect_String(quote_String(&spec->name, iFalse)));
+        appendFormat_String(str, "priority = %d\n", spec->priority);
+        appendFormat_String(str, "override = %s\n", boolStr_(spec->flags & override_FontSpecFlag));
+        appendFormat_String(str, "monospace = %s\n", boolStr_(spec->flags & monospace_FontSpecFlag));
+        appendFormat_String(str, "auxiliary = %s\n", boolStr_(spec->flags & auxiliary_FontSpecFlag));
+        appendFormat_String(str, "allowspace = %s\n", boolStr_(spec->flags & allowSpacePunct_FontSpecFlag));
+        for (int j = 0; j < 2; ++j) {
+            const char *scope = (j == 0 ? "ui" : "doc");
+            appendFormat_String(str, "%s.height = %.3f\n", scope, spec->heightScale[j]);
+            appendFormat_String(str, "%s.glyphscale = %.3f\n", scope, spec->glyphScale[j]);
+            appendFormat_String(str, "%s.voffset = %.3f\n", scope, spec->vertOffsetScale[j]);
+        }
+        iForIndices(j, styles_) {
+            appendFormat_String(str, "%s = \"%s\"\n", styles_[j],
+                                cstrCollect_String(quote_String(&spec->sourcePath, iFalse)));
+        }
+    }
+    appendCStr_String(str, "```\n");
+    return str;
+}
+
+const iString *infoPage_Fonts(iRangecc query) {
     iFonts *d = &fonts_;
+    if (!isEmpty_Range(&query)) {
+        query.start++; /* skip the ? */
+        return exportFontPackIni_Fonts_(d, query);
+    }
     iString *str = collectNewCStr_String("# ${heading.fontpack.meta}\n"
          "=> gemini://skyjake.fi/fonts  Download more fonts\n"
          "=> about:command?!open%20newtab:1%20gotoheading:1%20url:about:help  Using fonts in Lagrange\n"
@@ -734,7 +855,7 @@ const iString *infoPage_Fonts(void) {
     iString *currentSourcePath = collectNew_String();
     for (int group = 0; group < 2; group++) {
         iBool isFirst = iTrue;
-        iConstForEach(PtrArray, i, specsByPack) {
+        iConstForEach(PtrArray, i, group == 0 ? specsByPack : disabledSpecs_Fonts_(d)) {
             const iFontSpec *spec = i.ptr;
             if (isEmpty_String(&spec->sourcePath)) {
                 continue; /* built-in font */
@@ -753,15 +874,22 @@ const iString *infoPage_Fonts(void) {
                             isFirst = iFalse;
                         }
                         const iString *packId = id_FontPack(pack).id;
-                        appendFormat_String(str, "### %s\n=> %s ${fontpack.meta.viewfile}\n",
+                        appendFormat_String(str, "### %s\n",
                                             isEmpty_String(packId) ? "fonts.ini" :
-                                            cstr_String(packId),
-                                            cstrCollect_String(makeFileUrl_String(&spec->sourcePath)));
+                                            cstr_String(packId));
                         append_String(str, collect_String(infoText_FontPack(pack)));
-                        iConstForEach(Array, a, actions_FontPack(pack)) {
+                        appendFormat_String(str, "=> %s ${fontpack.meta.viewfile}\n",
+                                            cstrCollect_String(makeFileUrl_String(&spec->sourcePath)));
+                        if (pack->isStandalone) {
+                            appendFormat_String(str, "=> about:fonts?%s ${fontpack.export}\n",
+                                                cstr_String(packId));
+                        }
+                        iConstForEach(Array, a, actions_FontPack(pack, iFalse)) {
                             const iMenuItem *item = a.value;
-                            appendFormat_String(str, "=> about:command?%s %s\n",
-                                                cstr_String(withSpacesEncoded_String(collectNewCStr_String(item->command))),
+                            appendFormat_String(str,
+                                                "=> about:command?%s %s\n",
+                                                cstr_String(withSpacesEncoded_String(
+                                                    collectNewCStr_String(item->command))),
                                                 item->label);
                         }
                     }
@@ -824,5 +952,32 @@ void install_Fonts(const iString *packId, const iBlock *data) {
     reload_Fonts();
 }
 
+void installFontFile_Fonts(const iString *fileName, const iBlock *data) {
+    iFonts *d = &fonts_;
+    iFile *f = new_File(collect_String(concat_Path(userFontsDirectory_Fonts_(d), fileName)));
+    if (open_File(f, writeOnly_FileMode)) {
+        write_File(f, data);
+    }
+    iRelease(f);
+    reload_Fonts();
+}
+
+void enablePack_Fonts(const iString *packId, iBool enable) {
+    iFonts *d = &fonts_;
+    if (enable) {
+        remove_StringSet(prefs_App()->disabledFontPacks, packId);
+    }
+    else {
+        insert_StringSet(prefs_App()->disabledFontPacks, packId);
+    }
+    updateActive_Fonts();
+    resetFonts_App();
+    invalidate_Window(get_MainWindow());
+}
+
+void updateActive_Fonts(void) {
+    sortSpecs_Fonts_(&fonts_);
+}
+
 iDefineClass(FontFile)
 
diff --git a/src/fontpack.h b/src/fontpack.h
index fb8d757e..5d592822 100644
--- a/src/fontpack.h
+++ b/src/fontpack.h
@@ -110,22 +110,23 @@ iDeclareType(FontSpec)
 iDeclareTypeConstruction(FontSpec)
 
 enum iFontSpecFlags {
-    override_FontSpecFlag   = iBit(1),
-    monospace_FontSpecFlag  = iBit(2), /* can be used in preformatted content */
-    auxiliary_FontSpecFlag  = iBit(3), /* only used for looking up glyphs missing from other fonts */
-    allowSpacePunct_FontSpecFlag  = iBit(4),  /* space/punctuation glyphs from this auxiliary font can be used */
+    user_FontSpecFlag             = iBit(1),  /* user's standalone font, can be used for anything */
+    override_FontSpecFlag         = iBit(2),
+    monospace_FontSpecFlag        = iBit(3),  /* can be used in preformatted content */
+    auxiliary_FontSpecFlag        = iBit(4),  /* only used for looking up glyphs missing from other fonts */
+    allowSpacePunct_FontSpecFlag  = iBit(5),  /* space/punctuation glyphs from this auxiliary font can be used */
     fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */
 };
 
 struct Impl_FontSpec {
-    iString id;   /* unique ID */
-    iString name; /* human-readable label */
-    iString sourcePath; /* file where the path was loaded, could be a .fontpack */
-    int     flags;
-    int     priority;
-    float   heightScale[2];     /* overall height scaling; ui, document */
-    float   glyphScale[2];      /* ui, document */
-    float   vertOffsetScale[2]; /* ui, document */
+    iString          id;         /* unique ID */
+    iString          name;       /* human-readable label */
+    iString          sourcePath; /* file where the path was loaded, could be a .fontpack */
+    int              flags;
+    int              priority;
+    float            heightScale[2];     /* overall height scaling; ui, document */
+    float            glyphScale[2];      /* ui, document */
+    float            vertOffsetScale[2]; /* ui, document */
     const iFontFile *styles[max_FontStyle];
 };
 
@@ -158,25 +159,26 @@ iBool               isDisabled_FontPack     (const iFontPack *);
 iBool               isReadOnly_FontPack     (const iFontPack *);
 const iPtrArray *   listSpecs_FontPack      (const iFontPack *);
 iString *           infoText_FontPack       (const iFontPack *);
-const iArray *      actions_FontPack        (const iFontPack *);
+const iArray *      actions_FontPack        (const iFontPack *, iBool showInstalled);
 
 const iString *     idFromUrl_FontPack      (const iString *url);
 
 /*----------------------------------------------------------------------------------------------*/
 
-iDeclareType(GmDocument)
-
 void    init_Fonts      (const char *userDir);
 void    deinit_Fonts    (void);
 
+void                enablePack_Fonts            (const iString *packId, iBool enable);
+void                updateActive_Fonts          (void);
 const iFontPack *   pack_Fonts                  (const char *packId);
 const iFontPack *   packByPath_Fonts            (const iString *path);
 const iFontSpec *   findSpec_Fonts              (const char *fontId);
 const iPtrArray *   listPacks_Fonts             (void);
 const iPtrArray *   listSpecs_Fonts             (iBool (*filterFunc)(const iFontSpec *));
 const iPtrArray *   listSpecsByPriority_Fonts   (void);
-const iString *     infoPage_Fonts              (void);
+const iString *     infoPage_Fonts              (iRangecc query);
 void                install_Fonts               (const iString *fontId, const iBlock *data);
+void                installFontFile_Fonts       (const iString *fileName, const iBlock *data);
 void                reload_Fonts                (void);
 
 iLocalDef iBool isInstalled_Fonts(const char *packId) {
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 23ebace3..b0851fec 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -479,7 +479,7 @@ static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) {
 
 static const int maxLedeLines_ = 10;
 
-static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) {
+static void applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib) {
     /* WARNING: This is duplicated in run_Font_(). Make sure they behave identically. */
     if (attrib.bold) {
         d->run.font = fontWithStyle_Text(d->baseFont, bold_FontStyle);
@@ -495,7 +495,7 @@ static int applyAttributes_RunTypesetter_(iRunTypesetter *d, iTextAttrib attrib)
     else {
         d->run.font  = d->baseFont;
         d->run.color = d->baseColor;
-    }
+    }    
 }
 
 static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, iTextAttrib attrib,
diff --git a/src/gmrequest.c b/src/gmrequest.c
index f7a22e0a..03a6d999 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -362,7 +362,7 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) {
         return utf8_String(debugInfo_App());
     }
     if (equalCase_Rangecc(path, "fonts")) {
-        return utf8_String(infoPage_Fonts());
+        return utf8_String(infoPage_Fonts(query));
     }
     if (equalCase_Rangecc(path, "feeds")) {
         return utf8_String(entryListPage_Feeds());
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 1039fc2b..48ce5b5f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1130,7 +1130,8 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte
             d->footerButtons,
             iClob(newKeyMods_LabelWidget(
                 items[i].label, items[i].key, items[i].kmods, items[i].command)),
-            alignLeft_WidgetFlag | drawKey_WidgetFlag);
+            alignLeft_WidgetFlag | drawKey_WidgetFlag | extraPadding_WidgetFlag);
+        setPadding1_Widget(as_Widget(button), gap_UI / 2);
         checkIcon_LabelWidget(button);
         setFont_LabelWidget(button, uiContent_FontId);
     }
@@ -1473,7 +1474,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
                 trim_Rangecc(¶m);
                 /* Detect fontpacks even if the server doesn't use the right media type. */
                 if (isRequestFinished && equal_Rangecc(param, "application/octet-stream")) {
-                    if (detect_FontPack(&d->sourceContent)) {
+                    if (detect_FontPack(&response->body)) {
                         param = range_CStr(mimeType_FontPack);
                     }
                 }
@@ -1492,6 +1493,42 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
                     docFormat = plainText_SourceFormat;
                     setRange_String(&d->sourceMime, param);
                 }
+                else if (isRequestFinished && equal_Rangecc(param, "font/ttf")) {
+                    clear_String(&str);
+                    docFormat = gemini_SourceFormat;
+                    setRange_String(&d->sourceMime, param);
+                    format_String(&str, "# TrueType Font\n");
+                    iString *decUrl      = collect_String(urlDecode_String(d->mod.url));
+                    iRangecc name        = baseName_Path(decUrl);
+                    iBool    isInstalled = iFalse;
+                    if (startsWith_String(collect_String(localFilePathFromUrl_String(d->mod.url)),
+                                          cstr_String(dataDir_App()))) {
+                        isInstalled = iTrue;
+                    }
+                    appendCStr_String(&str, "## ");
+                    appendRange_String(&str, name);
+                    appendCStr_String(&str, "\n\n");
+                    appendCStr_String(
+                        &str, cstr_Lang(isInstalled ? "truetype.help.installed" : "truetype.help"));
+                    appendCStr_String(&str, "\n");
+                    if (!isInstalled) {
+                        makeFooterButtons_DocumentWidget_(
+                            d,
+                            (iMenuItem[]){
+                                { add_Icon " ${fontpack.install.ttf}",
+                                  SDLK_RETURN,
+                                  0,
+                                  format_CStr("!fontpack.install ttf:1 name:%s",
+                                              cstr_Rangecc(name)) },
+                                { folder_Icon " ${fontpack.open.fontsdir}",
+                                  SDLK_d,
+                                  0,
+                                  format_CStr("!open url:%s/fonts",
+                                              cstrCollect_String(makeFileUrl_String(dataDir_App())))
+                                }
+                            }, 2);
+                    }
+                }
                 else if (isRequestFinished &&
                          (equal_Rangecc(param, "application/zip") ||
                          (startsWith_Rangecc(param, "application/") &&
@@ -1499,32 +1536,39 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
                     clear_String(&str);
                     docFormat = gemini_SourceFormat;
                     setRange_String(&d->sourceMime, param);
-                    iString *key = collectNew_String();
-                    toString_Sym(SDLK_s, KMOD_PRIMARY, key);
                     format_String(&str, "# %s\n", zipPageHeading_(param));
-                    appendFormat_String(&str,
-                                        cstr_Lang("doc.archive"),
-                                        cstr_Rangecc(baseName_Path(d->mod.url)));
                     if (equal_Rangecc(param, mimeType_FontPack)) {
                         /* Show some information about fontpacks, and set up footer actions. */
                         iArchive *zip = iClob(new_Archive());
-                        if (openData_Archive(zip, &d->sourceContent)) {
+                        if (openData_Archive(zip, &response->body)) {
                             iFontPack *fp = new_FontPack();
                             setUrl_FontPack(fp, d->mod.url);
                             setStandalone_FontPack(fp, iTrue);
                             if (loadArchive_FontPack(fp, zip)) {
-                                appendFormat_String(&str, "\n\n%s",
+                                appendFormat_String(&str, "## %s\n%s",
+                                                    cstr_String(id_FontPack(fp).id),
                                                     cstrCollect_String(infoText_FontPack(fp)));
                             }
-                            const iArray *actions = actions_FontPack(fp);
+                            appendCStr_String(&str, "\n");
+                            appendCStr_String(&str, cstr_Lang("fontpack.help"));
+                            appendCStr_String(&str, "\n");
+                            const iArray *actions = actions_FontPack(fp, iTrue);
                             makeFooterButtons_DocumentWidget_(d, constData_Array(actions),
                                                               size_Array(actions));
                             delete_FontPack(fp);
                         }
                     }
-                    appendCStr_String(&str, "\n\n");
+                    else {
+                        appendFormat_String(&str,
+                                            cstr_Lang("doc.archive"),
+                                            cstr_Rangecc(baseName_Path(d->mod.url)));
+                        appendCStr_String(&str, "\n");                        
+                    }
+                    appendCStr_String(&str, "\n");
                     iString *localPath = localFilePathFromUrl_String(d->mod.url);
                     if (!localPath) {
+                        iString *key = collectNew_String();
+                        toString_Sym(SDLK_s, KMOD_PRIMARY, key);
                         appendFormat_String(&str, "%s\n\n",
                                             format_CStr(cstr_Lang("error.unsupported.suggestsave"),
                                                         cstr_String(key),
@@ -3267,10 +3311,17 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
         }
         return iTrue;
     }
-    else if (equalWidget_Command(cmd, w, "fontpack.install")) {
-        const iString *id = idFromUrl_FontPack(d->mod.url);
-        install_Fonts(id, &d->sourceContent);
-        postCommandf_App("open gotoheading:%s url:about:fonts", cstr_String(id));
+    else if (equal_Command(cmd, "fontpack.install") && document_App() == d) {
+        if (argLabel_Command(cmd, "ttf")) {
+            iAssert(!cmp_String(&d->sourceMime, "font/ttf"));
+            installFontFile_Fonts(collect_String(suffix_Command(cmd, "name")), &d->sourceContent);
+            postCommand_App("open url:about:fonts");                
+        }
+        else {
+            const iString *id = idFromUrl_FontPack(d->mod.url);
+            install_Fonts(id, &d->sourceContent);
+            postCommandf_App("open gotoheading:%s url:about:fonts", cstr_String(id));
+        }
         return iTrue;
     }
     return iFalse;
@@ -5199,6 +5250,7 @@ void updateSize_DocumentWidget(iDocumentWidget *d) {
     d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
     updateVisible_DocumentWidget_(d);
     invalidate_DocumentWidget_(d);
+    arrange_Widget(d->footerButtons);
 }
 
 #if 0
diff --git a/src/ui/text.c b/src/ui/text.c
index 4baf60d3..106c55e9 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -458,7 +458,7 @@ static void initFonts_Text_(iText *d) {
     /* Check if there are auxiliary fonts available and set those up, too. */
     iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
         const iFontSpec *spec = s.ptr;
-        if (spec->flags & auxiliary_FontSpecFlag) {
+        if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) {
             const int fontId = size_Array(&d->fonts);
             resize_Array(&d->fonts, fontId + maxVariants_Fonts);
             setupFontVariants_Text_(d, spec, fontId);
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.8/cdiff/57cbcc6e864abd368bde93154b3580147936201c
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
32.403035 milliseconds
Gemini-to-HTML Time
1.058702 milliseconds

This content has been proxied by September (ba2dc).