[1mdiff --git a/res/arabic.fontpack/fontpack.ini b/res/arabic.fontpack/fontpack.ini[m
[1mindex 00ad5241..305878ce 100644[m
[1m--- a/res/arabic.fontpack/fontpack.ini[m
[1m+++ b/res/arabic.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[arabic][m
name = "Noto Sans Arabic UI"[m
auxiliary = true[m
[1mdiff --git a/res/cjk.fontpack/fontpack.ini b/res/cjk.fontpack/fontpack.ini[m
[1mindex fbac54be..6e0d274c 100644[m
[1m--- a/res/cjk.fontpack/fontpack.ini[m
[1m+++ b/res/cjk.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[notosansjp][m
name = "Noto Sans JP"[m
auxiliary = true[m
[1mdiff --git a/res/default.fontpack/fontpack.ini b/res/default.fontpack/fontpack.ini[m
[1mindex 68316ef6..f8ef31ce 100644[m
[1m--- a/res/default.fontpack/fontpack.ini[m
[1m+++ b/res/default.fontpack/fontpack.ini[m
[36m@@ -17,6 +17,8 @@[m
glyphscale
and voffset
can also be specified separately for the UI and[mui.
or doc.
to the key.[m[m
[32m+[m[32mversion = 1[m
[32m+[m
[default][m
name = "Source Sans"[m
regular = "SourceSans3-Regular.ttf"[m
[1mdiff --git a/res/firasans.fontpack/fontpack.ini b/res/firasans.fontpack/fontpack.ini[m
[1mindex 4378a757..c6eb4c77 100644[m
[1m--- a/res/firasans.fontpack/fontpack.ini[m
[1m+++ b/res/firasans.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[firasans][m
name = "Fira Sans"[m
glyphscale = 0.85[m
[1mdiff --git a/res/literata.fontpack/fontpack.ini b/res/literata.fontpack/fontpack.ini[m
[1mindex e4e49bcb..7c29491d 100644[m
[1m--- a/res/literata.fontpack/fontpack.ini[m
[1m+++ b/res/literata.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[literata][m
name = "Literata"[m
regular = "Literata-Regular-opsz=14.ttf"[m
[1mdiff --git a/res/nunito.fontpack/fontpack.ini b/res/nunito.fontpack/fontpack.ini[m
[1mindex ea4a12b8..2f2471e1 100644[m
[1m--- a/res/nunito.fontpack/fontpack.ini[m
[1m+++ b/res/nunito.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[nunito][m
name = "Nunito"[m
tweaks = 0x1 # some hardcoded kerning changes (Th
, etc.)[m
[1mdiff --git a/res/tinos.fontpack/fontpack.ini b/res/tinos.fontpack/fontpack.ini[m
[1mindex 8759b752..a2cf811e 100644[m
[1m--- a/res/tinos.fontpack/fontpack.ini[m
[1m+++ b/res/tinos.fontpack/fontpack.ini[m
[36m@@ -1,3 +1,5 @@[m
[32m+[m[32mversion = 1[m
[32m+[m
[tinos][m
name = "Tinos"[m
glyphscale = 0.850[m
[1mdiff --git a/src/app.c b/src/app.c[m
[1mindex b317e7b3..cb5479e8 100644[m
[1m--- a/src/app.c[m
[1m+++ b/src/app.c[m
[36m@@ -2669,6 +2669,7 @@[m [miBool handleCommand_App(const char *cmd) {[m
const iBool isSplit = numRoots_Window(get_Window()) > 1;[m
if (tabCount_Widget(tabs) > 1 || isSplit) {[m
iWidget *closed = removeTabPage_Widget(tabs, index);[m
[32m+[m[32m cancelAllRequests_DocumentWidget((iDocumentWidget *) closed);[m
destroy_Widget(closed); /* released later */[m
if (index == tabCount_Widget(tabs)) {[m
index--;[m
[1mdiff --git a/src/defs.h b/src/defs.h[m
[1mindex 65096389..f5479cf3 100644[m
[1m--- a/src/defs.h[m
[1m+++ b/src/defs.h[m
[36m@@ -125,11 +125,13 @@[m [miLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {[m
#define delete_Icon "\u232b"[m
#define copy_Icon "\u2398" //"\u2bba"[m
#define check_Icon "\u2714"[m
[31m-#define ballotCheck_Icon "\U0001f5f9"[m
[32m+[m[32m#define ballotChecked_Icon "\U0001f5f9"[m
[32m+[m[32m#define ballotUnchecked_Icon "\U0001f5f9"[m
#define inbox_Icon "\U0001f4e5"[m
#define book_Icon "\U0001f56e"[m
#define bookmark_Icon "\U0001f516"[m
#define folder_Icon "\U0001f4c1"[m
[32m+[m[32m#define file_Icon "\U0001f5ce"[m
#define openTab_Icon "\u2750"[m
#define openTabBg_Icon "\u2b1a"[m
#define openExt_Icon "\u27a0"[m
[1mdiff --git a/src/fontpack.c b/src/fontpack.c[m
[1mindex ca1d1582..fb1c98ee 100644[m
[1m--- a/src/fontpack.c[m
[1m+++ b/src/fontpack.c[m
[36m@@ -33,7 +33,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#include <the_Foundation/string.h>[m
#include <the_Foundation/toml.h>[m
[m
[31m-/* TODO: Clean up and/or reorder this file, it's a bit unorganized. */[m
[32m+[m[32mconst char *mimeType_FontPack = "application/lagrange-fontpack+zip";[m
[m
float scale_FontSize(enum iFontSize size) {[m
static const float sizes[max_FontSize] = {[m
[36m@@ -57,38 +57,9 @@[m [mfloat scale_FontSize(enum iFontSize size) {[m
return sizes[size];[m
}[m
[m
[31m-iDeclareType(Fonts) [m
[31m- [m
[31m-struct Impl_Fonts {[m
[31m- iString userDir;[m
[31m- iPtrArray packs;[m
[31m- iPtrArray files;[m
[31m- iPtrArray specOrder; /* specs sorted by priority */[m
[31m-};[m
[31m-[m
[31m-static iFonts fonts_;[m
[31m-[m
[31m-static void unloadFiles_Fonts_(iFonts *d) {[m
[31m- /* TODO: Mark all files in font packs as not resident. */ [m
[31m- iForEach(PtrArray, i, &d->files) {[m
[31m- delete_FontFile(i.ptr);[m
[31m- }[m
[31m- clear_PtrArray(&d->files);[m
[31m-}[m
[31m-[m
[31m-static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) {[m
[31m- iForEach(PtrArray, i, &d->files) {[m
[31m- iFontFile *ff = i.ptr;[m
[31m- if (equal_String(&ff->id, id)) {[m
[31m- return ff;[m
[31m- }[m
[31m- }[m
[31m- return NULL;[m
[31m-}[m
[31m-[m
/----------------------------------------------------------------------------------------------/[m
[m
[31m-iDefineTypeConstruction(FontFile)[m
[32m+[m[32miDefineObjectConstruction(FontFile)[m
[m
void init_FontFile(iFontFile *d) {[m
init_String(&d->id);[m
[36m@@ -114,7 +85,7 @@[m [mstatic void load_FontFile_(iFontFile *d, const iBlock *data) {[m
HB_MEMORY_MODE_READONLY, NULL, NULL);[m
d->hbFace = hb_face_create(d->hbBlob, 0);[m
d->hbFont = hb_font_create(d->hbFace);[m
[31m-#endif [m
[32m+[m[32m#endif[m
}[m
[m
static void unload_FontFile_(iFontFile *d) {[m
[36m@@ -126,12 +97,13 @@[m [mstatic void unload_FontFile_(iFontFile *d) {[m
d->hbFont = NULL;[m
d->hbFace = NULL;[m
d->hbBlob = NULL;[m
[31m-#endif [m
[32m+[m[32m#endif[m
clear_Block(&d->sourceData);[m
iZap(d->stbInfo);[m
}[m
[m
void deinit_FontFile(iFontFile *d) {[m
[32m+[m[32m printf("FontFile %p {%s} is DESTROYED\n", d, cstr_String(&d->id));[m
unload_FontFile_(d);[m
deinit_Block(&d->sourceData);[m
deinit_String(&d->id);[m
[36m@@ -156,12 +128,12 @@[m [mvoid measureGlyph_FontFile(const iFontFile *d, uint32_t glyphIndex,[m
[m
/----------------------------------------------------------------------------------------------/[m
[m
[31m-[m
iDefineTypeConstruction(FontSpec)[m
[m
void init_FontSpec(iFontSpec *d) {[m
init_String(&d->id);[m
init_String(&d->name);[m
[32m+[m[32m init_String(&d->sourcePath);[m
d->flags = 0;[m
d->priority = 0;[m
for (int i = 0; i < 2; ++i) {[m
[36m@@ -173,52 +145,119 @@[m [mvoid init_FontSpec(iFontSpec *d) {[m
}[m
[m
void deinit_FontSpec(iFontSpec *d) {[m
[32m+[m[32m /* FontFile references are held by FontSpecs. */[m
[32m+[m[32m iForIndices(i, d->styles) {[m
[32m+[m[32m iRelease(d->styles[i]);[m
[32m+[m[32m }[m
[32m+[m[32m deinit_String(&d->sourcePath);[m
deinit_String(&d->name);[m
deinit_String(&d->id);[m
}[m
[m
/----------------------------------------------------------------------------------------------/[m
[m
[31m-iDeclareType(FontPack)[m
[31m-iDeclareTypeConstruction(FontPack)[m
[32m+[m[32miDeclareType(Fonts)[m
[32m+[m
[32m+[m[32mstruct Impl_Fonts {[m
[32m+[m[32m iString userDir;[m
[32m+[m[32m iPtrArray packs;[m
[32m+[m[32m iObjectList *files;[m
[32m+[m[32m iPtrArray specOrder; /* specs sorted by priority */[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32mstatic iFonts fonts_;[m
[32m+[m
[32m+[m[32mstatic void unloadFiles_Fonts_(iFonts *d) {[m
[32m+[m[32m /* TODO: Mark all files in font packs as not resident. */[m[41m [m
[32m+[m[32m clear_ObjectList(d->files);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic iFontFile *findFile_Fonts_(iFonts *d, const iString *id) {[m
[32m+[m[32m iForEach(ObjectList, i, d->files) {[m
[32m+[m[32m iFontFile *ff = i.object;[m
[32m+[m[32m if (equal_String(&ff->id, id)) {[m
[32m+[m[32m return ff;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return NULL;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void releaseUnusedFiles_Fonts_(iFonts *d) {[m
[32m+[m[32m iForEach(ObjectList, i, d->files) {[m
[32m+[m[32m iFontFile *ff = i.object;[m
[32m+[m[32m if (ff->object.refCount == 1) {[m
[32m+[m[32m /* No specs use this. */[m
[32m+[m[32m //printf("[Fonts] releasing unused font file: %p {%s}\n", ff, cstr_String(&ff->id));[m
[32m+[m[32m remove_ObjectListIterator(&i);[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[m
struct Impl_FontPack {[m
[31m- const iArchive archive; / opened ZIP archive */[m
[32m+[m[32m iString id; /* lowercase filename without the .fontpack extension */[m
[32m+[m[32m int version;[m
[32m+[m[32m iBool isStandalone;[m
[32m+[m[32m iBool isReadOnly;[m
iArray fonts; /* array of FontSpecs */[m
[32m+[m[32m const iArchive archive; / opened ZIP archive */[m
iString * loadPath;[m
iFontSpec * loadSpec;[m
};[m
[m
[32m+[m[32miDefineTypeConstruction(FontPack)[m
[32m+[m
void init_FontPack(iFontPack *d) {[m
[31m- d->archive = NULL;[m
[32m+[m[32m init_String(&d->id);[m
[32m+[m[32m d->version = 0;[m
[32m+[m[32m d->isStandalone = iFalse;[m
[32m+[m[32m d->isReadOnly = iFalse;[m
init_Array(&d->fonts, sizeof(iFontSpec));[m
[32m+[m[32m d->archive = NULL;[m
d->loadSpec = NULL;[m
d->loadPath = NULL;[m
}[m
[m
void deinit_FontPack(iFontPack *d) {[m
[32m+[m[32m iAssert(d->archive == NULL);[m
[32m+[m[32m iAssert(d->loadSpec == NULL);[m
delete_String(d->loadPath);[m
iForEach(Array, i, &d->fonts) {[m
deinit_FontSpec(i.value);[m
}[m
deinit_Array(&d->fonts);[m
[31m- iAssert(d->archive == NULL);[m
[31m- iAssert(d->loadSpec == NULL);[m
[32m+[m[32m deinit_String(&d->id);[m
[32m+[m[32m releaseUnusedFiles_Fonts_(&fonts_);[m
}[m
[m
[31m-iDefineTypeConstruction(FontPack)[m
[32m+[m[32miFontPackId id_FontPack(const iFontPack *d) {[m
[32m+[m[32m return (iFontPackId){ &d->id, d->version };[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mconst iPtrArray *listSpecs_FontPack(const iFontPack *d) {[m
[32m+[m[32m if (!d) return NULL;[m
[32m+[m[32m iPtrArray *list = collectNew_PtrArray();[m
[32m+[m[32m iConstForEach(Array, i, &d->fonts) {[m
[32m+[m[32m pushBack_PtrArray(list, i.value);[m
[32m+[m[32m }[m
[32m+[m[32m return list;[m
[32m+[m[32m}[m
[m
void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) {[m
iFontPack *d = context;[m
if (isStart) {[m
iAssert(!d->loadSpec);[m
[31m- /* Each font ID must be unique. */[m
[31m- if (!findSpec_Fonts(cstr_String(table))) {[m
[32m+[m[32m /* Each font ID must be unique in the non-standalone packs. */[m
[32m+[m[32m if (d->isStandalone || !findSpec_Fonts(cstr_String(table))) {[m
d->loadSpec = new_FontSpec();[m
set_String(&d->loadSpec->id, table);[m
[32m+[m[32m if (d->loadPath) {[m
[32m+[m[32m set_String(&d->loadSpec->sourcePath, d->loadPath);[m
[32m+[m[32m }[m
}[m
}[m
[31m- else {[m
[32m+[m[32m else if (d->loadSpec) {[m
/* Set fallback font files. */ {[m
const iFontFile **styles = d->loadSpec->styles;[m
if (!styles[regular_FontStyle]) {[m
[36m@@ -229,11 +268,11 @@[m [mvoid handleIniTable_FontPack_(void *context, const iString *table, iBool isStart[m
return;[m
}[m
if (!styles[semiBold_FontStyle]) {[m
[31m- styles[semiBold_FontStyle] = styles[bold_FontStyle];[m
[32m+[m[32m styles[semiBold_FontStyle] = ref_Object(styles[bold_FontStyle]);[m
}[m
for (size_t s = 0; s < max_FontStyle; s++) {[m
if (!styles[s]) {[m
[31m- styles[s] = styles[regular_FontStyle];[m
[32m+[m[32m styles[s] = ref_Object(styles[regular_FontStyle]);[m
}[m
}[m
}[m
[36m@@ -263,7 +302,15 @@[m [mstatic iBlock *readFile_FontPack_(const iFontPack *d, const iString *path) {[m
void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key,[m
const iTomlValue *value) {[m
iFontPack *d = context;[m
[31m- if (!d->loadSpec) return;[m
[32m+[m[32m if (isEmpty_String(table)) {[m
[32m+[m[32m if (!cmp_String(key, "version")) {[m
[32m+[m[32m d->version = number_TomlValue(value);[m
[32m+[m[32m }[m
[32m+[m[32m return;[m
[32m+[m[32m }[m
[32m+[m[32m if (!d->loadSpec) {[m
[32m+[m[32m return;[m
[32m+[m[32m }[m
iUnused(table);[m
if (!cmp_String(key, "name") && value->type == string_TomlType) {[m
set_String(&d->loadSpec->name, value->value.string); [m
[36m@@ -322,12 +369,13 @@[m [mvoid handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr[m
ff = new_FontFile();[m
set_String(&ff->id, fontFileId);[m
load_FontFile_(ff, data);[m
[31m- pushBack_PtrArray(&fonts_.files, ff); /* centralized ownership */[m
[32m+[m[32m pushBack_ObjectList(fonts_.files, ff); /* centralized ownership */[m
[32m+[m[32m iRelease(ff);[m
delete_Block(data);[m
// printf("[FontPack] loaded file: %s\n", cstr_String(fontFileId));[m
}[m
}[m
[31m- d->loadSpec->styles[i] = ff;[m
[32m+[m[32m d->loadSpec->styles[i] = ref_Object(ff);[m
delete_String(fontFileId);[m
break;[m
}[m
[36m@@ -348,29 +396,6 @@[m [mstatic iBool load_FontPack_(iFontPack *d, const iString *ini) {[m
return ok;[m
}[m
[m
[31m-#if 0[m
[31m-iBool loadIniFile_FontPack(iFontPack *d, const iString *iniPath) {[m
[31m- iBeginCollect();[m
[31m- iBool ok = iFalse;[m
[31m- iFile *f = iClob(new_File(iniPath));[m
[31m- if (open_File(f, text_FileMode | readOnly_FileMode)) {[m
[31m- d->loadPath = collect_String(newRange_String(dirName_Path(iniPath)));[m
[31m- iString *src = collect_String(readString_File(f));[m
[31m- [m
[31m- iTomlParser *ini = collect_TomlParser(new_TomlParser());[m
[31m- setHandlers_TomlParser(ini, handleIniTable_FontPack_, handleIniKeyValue_FontPack_, d);[m
[31m- if (!parse_TomlParser(ini, src)) {[m
[31m- fprintf(stderr, "[FontPack] error parsing %s\n", cstr_String(iniPath));[m
[31m- }[m
[31m- iAssert(d->loadSpec == NULL);[m
[31m- d->loadPath = NULL;[m
[31m- ok = iTrue;[m
[31m- }[m
[31m- iEndCollect();[m
[31m- return ok;[m
[31m-}[m
[31m-#endif[m
[31m-[m
iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) {[m
d->archive = zip;[m
iBool ok = iFalse;[m
[36m@@ -387,6 +412,28 @@[m [miBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) {[m
return ok;[m
}[m
[m
[32m+[m[32mvoid setLoadPath_FontPack(iFontPack *d, const iString *path) {[m
[32m+[m[32m if (!d->loadPath) {[m
[32m+[m[32m d->loadPath = new_String();[m
[32m+[m[32m }[m
[32m+[m[32m set_String(d->loadPath, path);[m
[32m+[m[32m /* Pack ID is based on the file name. */[m
[32m+[m[32m setRange_String(&d->id, baseName_Path(path));[m
[32m+[m[32m setRange_String(&d->id, withoutExtension_Path(&d->id));[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid setStandalone_FontPack(iFontPack *d, iBool standalone) {[m
[32m+[m[32m d->isStandalone = standalone;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid setReadOnly_FontPack(iFontPack *d, iBool readOnly) {[m
[32m+[m[32m d->isReadOnly = readOnly;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miBool isReadOnly_FontPack(const iFontPack *d) {[m
[32m+[m[32m return d->isReadOnly;[m
[32m+[m[32m}[m
[32m+[m
/----------------------------------------------------------------------------------------------/[m
[m
static void unloadFonts_Fonts_(iFonts *d) {[m
[36m@@ -397,14 +444,23 @@[m [mstatic void unloadFonts_Fonts_(iFonts *d) {[m
clear_PtrArray(&d->packs);[m
}[m
[m
[32m+[m[32mstatic int cmpName_FontSpecPtr_(const void *a, const void *b) {[m
[32m+[m[32m const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b;[m
[32m+[m[32m return cmpStringCase_String(&(*p1)->name, &(*p2)->name);[m
[32m+[m[32m}[m
[32m+[m
static int cmpPriority_FontSpecPtr_(const void *a, const void *b) {[m
const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b;[m
[31m- return -iCmp((*p1)->priority, (p2)->priority); / highest priority first */[m
[32m+[m[32m const int cmp = -iCmp((*p1)->priority, (p2)->priority); / highest priority first */[m
[32m+[m[32m if (cmp) return cmp;[m
[32m+[m[32m return cmpName_FontSpecPtr_(a, b);[m
}[m
[m
[31m-static int cmpName_FontSpecPtr_(const void *a, const void *b) {[m
[32m+[m[32mstatic int cmpSourceAndPriority_FontSpecPtr_(const void *a, const void *b) {[m
const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b;[m
[31m- return cmpStringCase_String(&(*p1)->name, &(*p2)->name);[m
[32m+[m[32m const int cmp = cmpStringCase_String(&(*p1)->sourcePath, &(*p2)->sourcePath);[m
[32m+[m[32m if (cmp) return cmp;[m
[32m+[m[32m return cmpPriority_FontSpecPtr_(a, b);[m
}[m
[m
static void sortSpecs_Fonts_(iFonts *d) {[m
[36m@@ -422,11 +478,13 @@[m [mvoid init_Fonts(const char *userDir) {[m
iFonts *d = &fonts_;[m
initCStr_String(&d->userDir, userDir);[m
init_PtrArray(&d->packs);[m
[31m- init_PtrArray(&d->files);[m
[32m+[m[32m d->files = new_ObjectList();[m
init_PtrArray(&d->specOrder);[m
/* Load the required fonts. */ {[m
iFontPack *pack = new_FontPack();[m
[32m+[m[32m setCStr_String(&pack->id, "default");[m
iArchive *arch = new_Archive();[m
[32m+[m[32m setReadOnly_FontPack(pack, iTrue);[m
openData_Archive(arch, &fontpackDefault_Embedded);[m
loadArchive_FontPack(pack, arch); /* should never fail if we've made it this far */[m
iRelease(arch);[m
[36m@@ -436,7 +494,7 @@[m [mvoid init_Fonts(const char *userDir) {[m
const char *locations[] = {[m
".",[m
"./fonts",[m
[31m- "../share/lagrange",[m
[32m+[m[32m "../share/lagrange", /* Note: These must match CMakeLists.txt install destination */[m
"../../share/lagrange",[m
concatPath_CStr(userDir, "fonts"),[m
userDir,[m
[36m@@ -450,7 +508,13 @@[m [mvoid init_Fonts(const char *userDir) {[m
iArchive *arch = new_Archive();[m
if (openFile_Archive(arch, entryPath)) {[m
iFontPack *pack = new_FontPack();[m
[31m- pack->loadPath = copy_String(entryPath);[m
[32m+[m[32m setLoadPath_FontPack(pack, entryPath);[m
[32m+[m[32m setReadOnly_FontPack(pack, !isWritable_FileInfo(entry.value));[m
[32m+[m[32m#if defined (iPlatformApple)[m
[32m+[m[32m if (startsWith_String(pack->loadPath, cstr_String(execDir))) {[m
[32m+[m[32m setReadOnly_FontPack(pack, iTrue);[m
[32m+[m[32m }[m
[32m+[m[32m#endif[m
if (loadArchive_FontPack(pack, arch)) {[m
pushBack_PtrArray(&d->packs, pack);[m
}[m
[36m@@ -491,13 +555,18 @@[m [mvoid init_Fonts(const char *userDir) {[m
void deinit_Fonts(void) {[m
iFonts *d = &fonts_;[m
unloadFonts_Fonts_(d);[m
[31m- unloadFiles_Fonts_(d);[m
[32m+[m[32m //unloadFiles_Fonts_(d);[m
[32m+[m[32m iAssert(isEmpty_ObjectList(d->files));[m
deinit_PtrArray(&d->specOrder);[m
deinit_PtrArray(&d->packs);[m
[31m- deinit_PtrArray(&d->files);[m
[32m+[m[32m iRelease(d->files);[m
deinit_String(&d->userDir);[m
}[m
[m
[32m+[m[32mconst iPtrArray *listPacks_Fonts(void) {[m
[32m+[m[32m return &fonts_.packs;[m
[32m+[m[32m}[m
[32m+[m
const iFontSpec *findSpec_Fonts(const char *fontId) {[m
iFonts *d = &fonts_;[m
iConstForEach(PtrArray, i, &d->specOrder) {[m
[36m@@ -524,3 +593,73 @@[m [mconst iPtrArray *listSpecs_Fonts(iBool (*filterFunc)(const iFontSpec *)) {[m
const iPtrArray *listSpecsByPriority_Fonts(void) {[m
return &fonts_.specOrder;[m
}[m
[32m+[m
[32m+[m[32mconst iString *infoPage_Fonts(void) {[m
[32m+[m[32m iFonts *d = &fonts_;[m
[32m+[m[32m iString *str = collectNewCStr_String("# Fonts\n");[m
[32m+[m[32m iPtrArray *specsByPack = collectNew_PtrArray();[m
[32m+[m[32m setCopy_PtrArray(specsByPack, &d->specOrder);[m
[32m+[m[32m sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_);[m
[32m+[m[32m iString *currentSourcePath = collectNew_String();[m
[32m+[m[32m iConstForEach(PtrArray, i, specsByPack) {[m
[32m+[m[32m const iFontSpec *spec = i.ptr;[m
[32m+[m[32m if (isEmpty_String(&spec->sourcePath)) {[m
[32m+[m[32m continue; /* built-in font */[m
[32m+[m[32m }[m
[32m+[m[32m if (!equal_String(&spec->sourcePath, currentSourcePath)) {[m
[32m+[m[32m appendFormat_String(str, "=> %s %s%s\n",[m
[32m+[m[32m cstrCollect_String(makeFileUrl_String(&spec->sourcePath)),[m
[32m+[m[32m endsWithCase_String(&spec->sourcePath, ".fontpack") ? "\U0001f520 " : "",[m
[32m+[m[32m cstr_Rangecc(baseName_Path(&spec->sourcePath)));[m
[32m+[m[32m set_String(currentSourcePath, &spec->sourcePath);[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return str;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mconst iFontPack *findPack_Fonts(const iString *path) {[m
[32m+[m[32m iFonts *d = &fonts_;[m
[32m+[m[32m iConstForEach(PtrArray, i, &d->packs) {[m
[32m+[m[32m const iFontPack *pack = i.ptr;[m
[32m+[m[32m if (pack->loadPath && equal_String(pack->loadPath, path)) {[m
[32m+[m[32m return pack;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return NULL;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miBool preloadLocalFontpackForPreview_Fonts(iGmDocument *doc) {[m
[32m+[m[32m iBool wasLoaded = iFalse;[m
[32m+[m[32m for (size_t linkId = 1; ; linkId++) {[m
[32m+[m[32m const iString *linkUrl = linkUrl_GmDocument(doc, linkId);[m
[32m+[m[32m if (!linkUrl) {[m
[32m+[m[32m break; /* ran out of links */[m
[32m+[m[32m }[m
[32m+[m[32m const int linkFlags = linkFlags_GmDocument(doc, linkId);[m
[32m+[m[32m if (linkFlags & fontpackFileExtension_GmLinkFlag &&[m
[32m+[m[32m scheme_GmLinkFlag(linkFlags) == file_GmLinkScheme) {[m
[32m+[m[32m iMediaId linkMedia = findMediaForLink_Media(media_GmDocument(doc), linkId, fontpack_MediaType);[m
[32m+[m[32m if (linkMedia.type) {[m
[32m+[m[32m continue; /* got this one already */[m
[32m+[m[32m }[m
[32m+[m[32m iString *filePath = localFilePathFromUrl_String(linkUrl);[m
[32m+[m[32m iFile *f = new_File(filePath);[m
[32m+[m[32m if (open_File(f, readOnly_FileMode)) {[m
[32m+[m[32m iBlock *fontPackArchiveData = readAll_File(f);[m
[32m+[m[32m setUrl_Media(media_GmDocument(doc), linkId, fontpack_MediaType, linkUrl);[m
[32m+[m[32m setData_Media(media_GmDocument(doc),[m
[32m+[m[32m linkId,[m
[32m+[m[32m collectNewCStr_String(mimeType_FontPack),[m
[32m+[m[32m fontPackArchiveData,[m
[32m+[m[32m 0);[m
[32m+[m[32m delete_Block(fontPackArchiveData);[m
[32m+[m[32m wasLoaded = iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m iRelease(f);[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m return wasLoaded;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miDefineClass(FontFile)[m
[41m+[m
[1mdiff --git a/src/fontpack.h b/src/fontpack.h[m
[1mindex e59154e3..429afb5d 100644[m
[1m--- a/src/fontpack.h[m
[1m+++ b/src/fontpack.h[m
[36m@@ -30,6 +30,8 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#endif[m
[m
[32m+[m[32mextern const char *mimeType_FontPack;[m
[32m+[m
/* Fontpacks are ZIP archives that contain a configuration file and one of more font[m
files. The fontpack format is used instead of plain TTF/OTF because the text renderer[m
uses additional metadata about each font.[m
[36m@@ -68,21 +70,13 @@[m [menum iFontStyle {[m
[m
float scale_FontSize (enum iFontSize size);[m
[m
[31m-iDeclareType(FontSpec)[m
[31m-iDeclareTypeConstruction(FontSpec)[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[m
[31m-enum iFontSpecFlags {[m
[31m- override_FontSpecFlag = iBit(1),[m
[31m- monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */[m
[31m- auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */[m
[31m- arabic_FontSpecFlag = iBit(4),[m
[31m- fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */[m
[31m-};[m
[31m-[m
[31m-iDeclareType(FontFile)[m
[31m-iDeclareTypeConstruction(FontFile)[m
[32m+[m[32miDeclareClass(FontFile)[m
[32m+[m[32miDeclareObjectConstruction(FontFile)[m
[m
struct Impl_FontFile {[m
[32m+[m[32m iObject object; /* reference-counted */[m
iString id; /* for detecting when the same file is used in many places */[m
enum iFontStyle style;[m
iBlock sourceData;[m
[36m@@ -107,9 +101,26 @@[m [muint8_t * rasterizeGlyph_FontFile(const iFontFile *, float xScale, float yScal[m
void measureGlyph_FontFile (const iFontFile *, uint32_t glyphIndex,[m
float xScale, float yScale, float xShift,[m
int *x0, int *y0, int *x1, int *y1);[m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
[32m+[m[32m/* FontSpec describes a typeface, combining multiple fonts into a group.[m
[32m+[m[32m The user will be choosing FontSpecs instead of individual font files. */[m
[32m+[m[32miDeclareType(FontSpec)[m
[32m+[m[32miDeclareTypeConstruction(FontSpec)[m
[32m+[m
[32m+[m[32menum iFontSpecFlags {[m
[32m+[m[32m override_FontSpecFlag = iBit(1),[m
[32m+[m[32m monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */[m
[32m+[m[32m auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */[m
[32m+[m[32m arabic_FontSpecFlag = iBit(4),[m
[32m+[m[32m fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */[m
[32m+[m[32m};[m
[32m+[m
struct Impl_FontSpec {[m
iString id; /* unique ID */[m
iString name; /* human-readable label */[m
[32m+[m[32m iString sourcePath; /* file where the path was loaded, could be a .fontpack */[m
int flags;[m
int priority;[m
float heightScale[2]; /* overall height scaling; ui, document */[m
[36m@@ -121,10 +132,38 @@[m [mstruct Impl_FontSpec {[m
iLocalDef int scaleType_FontSpec(enum iFontSize sizeId) {[m
return sizeId / contentRegular_FontSize;[m
}[m
[31m- [m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
[32m+[m[32miDeclareType(FontPack)[m
[32m+[m[32miDeclareTypeConstruction(FontPack)[m
[32m+[m
[32m+[m[32miDeclareType(FontPackId)[m
[32m+[m
[32m+[m[32mstruct Impl_FontPackId {[m
[32m+[m[32m const iString *id;[m
[32m+[m[32m int version;[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32mvoid setReadOnly_FontPack (iFontPack *, iBool readOnly);[m
[32m+[m[32mvoid setStandalone_FontPack (iFontPack *, iBool standalone);[m
[32m+[m[32mvoid setLoadPath_FontPack (iFontPack *, const iString *path);[m
[32m+[m[32miBool loadArchive_FontPack (iFontPack *, const iArchive *zip);[m
[32m+[m
[32m+[m[32miFontPackId id_FontPack (const iFontPack *);[m
[32m+[m[32mconst iPtrArray * listSpecs_FontPack (const iFontPack *);[m
[32m+[m[32miBool isReadOnly_FontPack (const iFontPack *);[m
[32m+[m
[32m+[m[32miDeclareType(GmDocument)[m
[32m+[m
void init_Fonts (const char *userDir);[m
void deinit_Fonts (void);[m
[m
[32m+[m[32mconst iFontPack * findPack_Fonts (const iString *path);[m
const iFontSpec * findSpec_Fonts (const char *fontId);[m
[32m+[m[32mconst iPtrArray * listPacks_Fonts (void);[m
const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *));[m
const iPtrArray * listSpecsByPriority_Fonts (void);[m
[32m+[m[32mconst iString * infoPage_Fonts (void);[m
[32m+[m
[32m+[m[32miBool preloadLocalFontpackForPreview_Fonts (iGmDocument *doc);[m
[1mdiff --git a/src/gempub.c b/src/gempub.c[m
[1mindex 23846414..952d72a1 100644[m
[1m--- a/src/gempub.c[m
[1m+++ b/src/gempub.c[m
[36m@@ -337,7 +337,7 @@[m [miBool preloadCoverImage_Gempub(const iGempub *d, iGmDocument *doc) {[m
for (size_t linkId = 1; ; linkId++) {[m
const iString *linkUrl = linkUrl_GmDocument(doc, linkId);[m
if (!linkUrl) break;[m
[31m- if (findLinkImage_Media(media_GmDocument(doc), linkId)) {[m
[32m+[m[32m if (findLinkImage_Media(media_GmDocument(doc), linkId).type) {[m
continue; /* got this already */[m
}[m
if (linkFlags_GmDocument(doc, linkId) & imageFileExtension_GmLinkFlag) {[m
[1mdiff --git a/src/gmdocument.c b/src/gmdocument.c[m
[1mindex c98b0bb8..c271ad94 100644[m
[1m--- a/src/gmdocument.c[m
[1m+++ b/src/gmdocument.c[m
[36m@@ -27,6 +27,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
#include "ui/color.h"[m
#include "ui/text.h"[m
#include "ui/metrics.h"[m
[32m+[m[32m#include "ui/mediaui.h"[m
#include "ui/window.h"[m
#include "visited.h"[m
#include "bookmarks.h"[m
[36m@@ -224,7 +225,7 @@[m [mstatic iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li[m
setScheme_GmLink_(link, finger_GmLinkScheme);[m
}[m
else if (equalCase_Rangecc(parts.scheme, "file")) {[m
[31m- setScheme_GmLink_(link, file_GmLinkScheme);[m
[32m+[m[32m setScheme_GmLink_(link, file_GmLinkScheme);[m[41m [m
}[m
else if (equalCase_Rangecc(parts.scheme, "data")) {[m
setScheme_GmLink_(link, data_GmLinkScheme);[m
[36m@@ -251,6 +252,9 @@[m [mstatic iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li[m
endsWithCase_String(path, ".mid") || endsWithCase_String(path, ".ogg")) {[m
link->flags |= audioFileExtension_GmLinkFlag;[m
}[m
[32m+[m[32m else if (endsWithCase_String(path, ".fontpack")) {[m
[32m+[m[32m link->flags |= fontpackFileExtension_GmLinkFlag;[m
[32m+[m[32m }[m
delete_String(path);[m
}[m
/* Check if visited. */[m
[36m@@ -503,7 +507,7 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m
static const char *arrow = rightArrowhead_Icon;[m
static const char *envelope = envelope_Icon;[m
static const char *bullet = "\u2022";[m
[31m- static const char *folder = "\U0001f4c1";[m
[32m+[m[32m static const char *folder = file_Icon;[m
static const char *globe = globe_Icon;[m
static const char *quote = "\u201c";[m
static const char *magnifyingGlass = "\U0001f50d";[m
[36m@@ -900,77 +904,77 @@[m [mstatic void doLayout_GmDocument_(iGmDocument *d) {[m
((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag;[m
/* Image or audio content. */[m
if (type == link_GmLineType) {[m
[31m- const iMediaId imageId = findLinkImage_Media(d->media, run.linkId);[m
[31m- const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0;[m
[31m- const iMediaId downloadId = !imageId && !audioId ? findLinkDownload_Media(d->media, run.linkId) : 0;[m
[31m- if (imageId) {[m
[31m- iGmMediaInfo img;[m
[31m- imageInfo_Media(d->media, imageId, &img);[m
[31m- const iInt2 imgSize = imageSize_Media(d->media, imageId);[m
[31m- linkContentWasLaidOut_GmDocument_(d, &img, run.linkId);[m
[31m- const int margin = lineHeight_Text(paragraph_FontId) / 2;[m
[32m+[m[32m /* TODO: Cleanup here? Move to a function of its own. */[m
[32m+[m[32m// enum iMediaType mediaType = none_MediaType;[m
[32m+[m[32m const iMediaId media = findMediaForLink_Media(d->media, run.linkId, none_MediaType);[m
[32m+[m[32m iGmMediaInfo info;[m
[32m+[m[32m info_Media(d->media, media, &info);[m
[32m+[m[32m run.mediaType = media.type;[m
[32m+[m[32m run.mediaId = media.id;[m
[32m+[m[32m run.text = iNullRange;[m
[32m+[m[32m run.font = uiLabel_FontId;[m
[32m+[m[32m run.color = 0;[m
[32m+[m[32m const int margin = lineHeight_Text(paragraph_FontId) / 2;[m
[32m+[m[32m if (media.type) {[m
pos.y += margin;[m
[31m- run.bounds.pos = pos;[m
[31m- run.bounds.size.x = d->size.x;[m
[31m- const float aspect = (float) imgSize.y / (float) imgSize.x;[m
[31m- run.bounds.size.y = d->size.x * aspect;[m
[31m- /* Extend the image to full width, including outside margin, if the viewport[m
[31m- is narrow enough. */[m
[31m- if (isFullWidthImages) {[m
[31m- run.bounds.size.x += d->outsideMargin * 2;[m
[31m- run.bounds.size.y += d->outsideMargin * 2 * aspect;[m
[31m- run.bounds.pos.x -= d->outsideMargin;[m
[31m- } [m
[31m- run.visBounds = run.bounds;[m
[31m- const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio);[m
[31m- if (width_Rect(run.visBounds) > maxSize.x) {[m
[31m- /* Don't scale the image up. */[m
[31m- run.visBounds.size.y =[m
[31m- run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);[m
[31m- run.visBounds.size.x = maxSize.x;[m
[31m- run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;[m
[31m- run.bounds.size.y = run.visBounds.size.y;[m
[31m- }[m
[31m- run.text = iNullRange;[m
[31m- run.font = 0;[m
[31m- run.color = 0;[m
[31m- run.mediaType = image_GmRunMediaType;[m
[31m- run.mediaId = imageId;[m
[31m- pushBack_Array(&d->layout, &run);[m
[31m- pos.y += run.bounds.size.y + margin;[m
[31m- }[m
[31m- else if (audioId) {[m
[31m- iGmMediaInfo info;[m
[31m- audioInfo_Media(d->media, audioId, &info);[m
[32m+[m[32m run.bounds.size.y = 0;[m
linkContentWasLaidOut_GmDocument_(d, &info, run.linkId);[m
[31m- const int margin = lineHeight_Text(paragraph_FontId) / 2;[m
[31m- pos.y += margin;[m
[31m- run.bounds.pos = pos;[m
[31m- run.bounds.size.x = d->size.x;[m
[31m- run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI;[m
[31m- run.visBounds = run.bounds;[m
[31m- run.text = iNullRange;[m
[31m- run.color = 0;[m
[31m- run.mediaType = audio_GmRunMediaType;[m
[31m- run.mediaId = audioId;[m
[31m- pushBack_Array(&d->layout, &run);[m
[31m- pos.y += run.bounds.size.y + margin;[m
}[m
[31m- else if (downloadId) {[m
[31m- iGmMediaInfo info;[m
[31m- downloadInfo_Media(d->media, downloadId, &info);[m
[31m- linkContentWasLaidOut_GmDocument_(d, &info, run.linkId);[m
[31m- const int margin = lineHeight_Text(paragraph_FontId) / 2;[m
[31m- pos.y += margin;[m
[31m- run.bounds.pos = pos;[m
[31m- run.bounds.size.x = d->size.x;[m
[31m- run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI;[m
[31m- run.visBounds = run.bounds;[m
[31m- run.text = iNullRange;[m
[31m- run.color = 0;[m
[31m- run.mediaType = download_GmRunMediaType;[m
[31m- run.mediaId = downloadId;[m
[31m- pushBack_Array(&d->layout, &run);[m
[32m+[m[32m switch (media.type) {[m
[32m+[m[32m case image_MediaType: {[m
[32m+[m[32m const iInt2 imgSize = imageSize_Media(d->media, media);[m
[32m+[m[32m run.bounds.pos = pos;[m
[32m+[m[32m run.bounds.size.x = d->size.x;[m
[32m+[m[32m const float aspect = (float) imgSize.y / (float) imgSize.x;[m
[32m+[m[32m run.bounds.size.y = d->size.x * aspect;[m
[32m+[m[32m /* Extend the image to full width, including outside margin, if the viewport[m
[32m+[m[32m is narrow enough. */[m
[32m+[m[32m if (isFullWidthImages) {[m
[32m+[m[32m run.bounds.size.x += d->outsideMargin * 2;[m
[32m+[m[32m run.bounds.size.y += d->outsideMargin * 2 * aspect;[m
[32m+[m[32m run.bounds.pos.x -= d->outsideMargin;[m
[32m+[m[32m }[m
[32m+[m[32m run.visBounds = run.bounds;[m
[32m+[m[32m const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio);[m
[32m+[m[32m if (width_Rect(run.visBounds) > maxSize.x) {[m
[32m+[m[32m /* Don't scale the image up. */[m
[32m+[m[32m run.visBounds.size.y =[m
[32m+[m[32m run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);[m
[32m+[m[32m run.visBounds.size.x = maxSize.x;[m
[32m+[m[32m run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;[m
[32m+[m[32m run.bounds.size.y = run.visBounds.size.y;[m
[32m+[m[32m }[m
[32m+[m[32m pushBack_Array(&d->layout, &run);[m
[32m+[m[32m break;[m
[32m+[m[32m }[m
[32m+[m[32m case audio_MediaType: {[m
[32m+[m[32m run.bounds.pos = pos;[m
[32m+[m[32m run.bounds.size.x = d->size.x;[m
[32m+[m[32m run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI;[m
[32m+[m[32m run.visBounds = run.bounds;[m
[32m+[m[32m pushBack_Array(&d->layout, &run);[m
[32m+[m[32m break;[m
[32m+[m[32m }[m
[32m+[m[32m case download_MediaType: {[m
[32m+[m[32m run.bounds.pos = pos;[m
[32m+[m[32m run.bounds.size.x = d->size.x;[m
[32m+[m[32m run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI;[m
[32m+[m[32m run.visBounds = run.bounds;[m
[32m+[m[32m pushBack_Array(&d->layout, &run);[m
[32m+[m[32m break;[m
[32m+[m[32m }[m
[32m+[m[32m case fontpack_MediaType: {[m
[32m+[m[32m run.bounds.pos = pos;[m
[32m+[m[32m run.bounds.size.x = d->size.x;[m
[32m+[m[32m run.bounds.size.y = height_FontpackUI(d->media, media.id, d->size.x);[m
[32m+[m[32m run.visBounds = run.bounds;[m
[32m+[m[32m pushBack_Array(&d->layout, &run);[m
[32m+[m[32m break;[m
[32m+[m[32m }[m
[32m+[m[32m default:[m
[32m+[m[32m break;[m
[32m+[m[32m }[m
[32m+[m[32m if (media.type && run.bounds.size.y) {[m
pos.y += run.bounds.size.y + margin;[m
}[m
}[m
[36m@@ -1057,21 +1061,6 @@[m [mconst iString *url_GmDocument(const iGmDocument *d) {[m
return &d->url;[m
}[m
[m
[31m-#if 0[m
[31m-void reset_GmDocument(iGmDocument *d) {[m
[31m- clear_Media(d->media);[m
[31m- clearLinks_GmDocument_(d);[m
[31m- clear_Array(&d->layout);[m
[31m- clear_Array(&d->headings);[m
[31m- clear_Array(&d->preMeta);[m
[31m- clear_String(&d->url);[m
[31m- clear_String(&d->localHost);[m
[31m- clear_String(&d->source);[m
[31m- clear_String(&d->unormSource);[m
[31m- d->themeSeed = 0;[m
[31m-}[m
[31m-#endif[m
[31m-[m
static void setDerivedThemeColors_(enum iGmDocumentTheme theme) {[m
set_Color(tmQuoteIcon_ColorId,[m
mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f));[m
[1mdiff --git a/src/gmdocument.h b/src/gmdocument.h[m
[1mindex b2c6d9b7..20bc9890 100644[m
[1m--- a/src/gmdocument.h[m
[1m+++ b/src/gmdocument.h[m
[36m@@ -90,6 +90,7 @@[m [menum iGmLinkFlag {[m
query_GmLinkFlag = iBit(14), /* Gopher query link */[m
iconFromLabel_GmLinkFlag = iBit(15), /* use an Emoji/special character from label */[m
isOpen_GmLinkFlag = iBit(16), /* currently open in a tab */[m
[32m+[m[32m fontpackFileExtension_GmLinkFlag = iBit(17),[m
};[m
[m
iLocalDef enum iGmLinkScheme scheme_GmLinkFlag(int flags) {[m
[36m@@ -126,13 +127,6 @@[m [menum iGmRunFlags {[m
altText_GmRunFlag = iBit(8),[m
};[m
[m
[31m-enum iGmRunMediaType {[m
[31m- none_GmRunMediaType,[m
[31m- image_GmRunMediaType,[m
[31m- audio_GmRunMediaType,[m
[31m- download_GmRunMediaType,[m
[31m-};[m
[31m-[m
/* This structure is tightly packed because GmDocuments are mostly composed of[m
a large number of GmRuns. */[m
struct Impl_GmRun {[m
[36m@@ -146,12 +140,16 @@[m [mstruct Impl_GmRun {[m
uint32_t color : 7; /* see max_ColorId */[m
[m
uint32_t font : 10;[m
[31m- uint32_t mediaType : 2;[m
[31m- uint32_t mediaId : 10; /* zero if not an image */[m
[32m+[m[32m uint32_t mediaType : 3;[m
[32m+[m[32m uint32_t mediaId : 9; /* zero if not an image */[m
uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */[m
};[m
};[m
[m
[32m+[m[32miLocalDef iMediaId mediaId_GmRun(const iGmRun *d) {[m
[32m+[m[32m return (iMediaId){ .type = d->mediaType, .id = d->mediaId };[m
[32m+[m[32m}[m
[32m+[m
iDeclareType(GmRunRange)[m
[m
struct Impl_GmRunRange {[m
[1mdiff --git a/src/gmrequest.c b/src/gmrequest.c[m
[1mindex 1a9e83a9..f7a22e0a 100644[m
[1m--- a/src/gmrequest.c[m
[1m+++ b/src/gmrequest.c[m
[36m@@ -361,6 +361,9 @@[m [mstatic const iBlock *aboutPageSource_(iRangecc path, iRangecc query) {[m
if (equalCase_Rangecc(path, "debug")) {[m
return utf8_String(debugInfo_App());[m
}[m
[32m+[m[32m if (equalCase_Rangecc(path, "fonts")) {[m
[32m+[m[32m return utf8_String(infoPage_Fonts());[m
[32m+[m[32m }[m
if (equalCase_Rangecc(path, "feeds")) {[m
return utf8_String(entryListPage_Feeds());[m
}[m
[36m@@ -710,8 +713,9 @@[m [mvoid submit_GmRequest(iGmRequest *d) {[m
sort_Array(sortedInfo, (int (*)(const void *, const void *)) cmp_FileInfoPtr_);[m
iForEach(PtrArray, s, sortedInfo) {[m
const iFileInfo *entry = s.ptr;[m
[31m- appendFormat_String(page, "=> %s %s%s\n",[m
[32m+[m[32m appendFormat_String(page, "=> %s %s%s%s\n",[m
cstrCollect_String(makeFileUrl_String(path_FileInfo(entry))),[m
[32m+[m[32m isDirectory_FileInfo(entry) ? folder_Icon " " : "",[m
cstr_Rangecc(baseName_Path(path_FileInfo(entry))),[m
isDirectory_FileInfo(entry) ? iPathSeparator : "");[m
iRelease(entry);[m
[36m@@ -808,9 +812,10 @@[m [mvoid submit_GmRequest(iGmRequest *d) {[m
const iString *subPath = e.value;[m
iRangecc relSub = range_String(subPath);[m
relSub.start += size_String(entryPath);[m
[31m- appendFormat_String(page, "=> %s/%s %s\n",[m
[32m+[m[32m appendFormat_String(page, "=> %s/%s %s%s\n",[m
cstr_String(&d->url),[m
cstr_String(withSpacesEncoded_String(collectNewRange_String(relSub))),[m
[32m+[m[32m endsWith_Rangecc(relSub, "/") ? folder_Icon " " : "",[m
cstr_Rangecc(relSub));[m
}[m
resp->statusCode = success_GmStatusCode;[m
[1mdiff --git a/src/gmutil.c b/src/gmutil.c[m
[1mindex 971747d4..5be7e198 100644[m
[1m--- a/src/gmutil.c[m
[1m+++ b/src/gmutil.c[m
[36m@@ -21,6 +21,7 @@[m [mANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT[m
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[m
#include "gmutil.h"[m
[32m+[m[32m#include "fontpack.h"[m
[m
#include <the_Foundation/file.h>[m
#include <the_Foundation/fileinfo.h>[m
[36m@@ -511,7 +512,8 @@[m [mconst iString *findContainerArchive_Path(const iString *path) {[m
while (!isEmpty_String(path) && cmp_String(path, ".")) {[m
iString *dir = newRange_String(dirName_Path(path));[m
if (endsWithCase_String(dir, ".zip") ||[m
[31m- endsWithCase_String(dir, ".gpub")) {[m
[32m+[m[32m endsWithCase_String(dir, ".gpub") ||[m
[32m+[m[32m endsWithCase_String(dir, ".fontpack")) {[m
iEndCollect();[m
return collect_String(dir);[m
}[m
[36m@@ -534,6 +536,9 @@[m [mconst char *mediaTypeFromFileExtension_String(const iString *d) {[m
else if (endsWithCase_String(d, ".gpub")) {[m
return "application/gpub+zip";[m
}[m
[32m+[m[32m else if (endsWithCase_String(d, ".fontpack")) {[m
[32m+[m[32m return mimeType_FontPack;[m
[32m+[m[32m }[m
else if (endsWithCase_String(d, ".xml")) {[m
return "text/xml";[m
}[m
[36m@@ -562,6 +567,7 @@[m [mconst char *mediaTypeFromFileExtension_String(const iString *d) {[m
return "audio/midi";[m
}[m
else if (endsWithCase_String(d, ".txt") ||[m
[32m+[m[32m endsWithCase_String(d, ".ini") ||[m
endsWithCase_String(d, ".md") ||[m
endsWithCase_String(d, ".c") ||[m
endsWithCase_String(d, ".h") ||[m
[1mdiff --git a/src/media.c b/src/media.c[m
[1mindex 26f0af4b..0ce2ac5c 100644[m
[1m--- a/src/media.c[m
[1m+++ b/src/media.c[m
[36m@@ -36,6 +36,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[m
#include <the_Foundation/file.h>[m
#include <the_Foundation/ptrarray.h>[m
[32m+[m[32m#include <the_Foundation/stringlist.h>[m
#include <SDL_hints.h>[m
#include <SDL_render.h>[m
#include <SDL_timer.h>[m
[36m@@ -287,45 +288,112 @@[m [miDefineTypeConstruction(GmDownload)[m
[m
/----------------------------------------------------------------------------------------------/[m
[m
[32m+[m[32miDeclareType(GmFontpack)[m
[32m+[m
[32m+[m[32mstruct Impl_GmFontpack {[m
[32m+[m[32m iGmMediaProps props;[m
[32m+[m[32m iString packId;[m
[32m+[m[32m iFontpackMediaInfo info;[m
[32m+[m[32m /* TODO: Font preview images? */[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32mvoid init_GmFontpack(iGmFontpack *d) {[m
[32m+[m[32m init_GmMediaProps_(&d->props);[m
[32m+[m[32m init_String(&d->packId);[m
[32m+[m[32m iZap(d->info);[m
[32m+[m[32m d->info.names = new_StringList();[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mvoid deinit_GmFontpack(iGmFontpack *d) {[m
[32m+[m[32m iRelease(d->info.names);[m
[32m+[m[32m deinit_String(&d->packId);[m
[32m+[m[32m deinit_GmMediaProps_(&d->props);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32mstatic void loadData_GmFontpack_(iGmFontpack *d, const iBlock *data) {[m
[32m+[m[32m const iString *loadPath = collect_String(localFilePathFromUrl_String(&d->props.url));[m
[32m+[m[32m const iFontPack *pack = findPack_Fonts(loadPath);[m
[32m+[m[32m d->info.isValid = d->info.isInstalled = pack != NULL;[m
[32m+[m[32m d->info.isReadOnly = iFalse;[m
[32m+[m[32m if (!pack) {[m
[32m+[m[32m /* Let's load it now temporarily and see what's inside. */[m
[32m+[m[32m iArchive *zip = new_Archive();[m
[32m+[m[32m if (openData_Archive(zip, data)) {[m
[32m+[m[32m iFontPack *fp = collect_FontPack(new_FontPack());[m
[32m+[m[32m setLoadPath_FontPack(fp, loadPath);[m
[32m+[m[32m setStandalone_FontPack(fp, iTrue);[m
[32m+[m[32m if (loadArchive_FontPack(fp, zip)) {[m
[32m+[m[32m d->info.isValid = iTrue;[m
[32m+[m[32m pack = fp;[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m iRelease(zip);[m
[32m+[m[32m }[m
[32m+[m[32m if (pack) {[m
[32m+[m[32m set_String(&d->packId, id_FontPack(pack).id);[m
[32m+[m[32m d->info.packId.id = &d->packId; /* we own this String */[m
[32m+[m[32m d->info.packId.version = id_FontPack(pack).version;[m
[32m+[m[32m d->info.isReadOnly = isReadOnly_FontPack(pack);[m
[32m+[m[32m }[m
[32m+[m[32m iPtrSet *unique = new_PtrSet();[m
[32m+[m[32m iConstForEach(PtrArray, i, listSpecs_FontPack(pack)) {[m
[32m+[m[32m const iFontSpec *spec = i.ptr;[m
[32m+[m[32m pushBack_StringList(d->info.names, &spec->name);[m
[32m+[m[32m iForIndices(j, spec->styles) {[m
[32m+[m[32m insert_PtrSet(unique, spec->styles[j]);[m
[32m+[m[32m }[m
[32m+[m[32m }[m
[32m+[m[32m iConstForEach(PtrSet, j, unique) {[m
[32m+[m[32m d->info.sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData);[m
[32m+[m[32m }[m
[32m+[m[32m delete_PtrSet(unique);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miDefineTypeConstruction(GmFontpack)[m
[32m+[m
[32m+[m[32m/----------------------------------------------------------------------------------------------/[m
[32m+[m
struct Impl_Media {[m
[31m- iPtrArray images;[m
[31m- iPtrArray audio;[m
[31m- iPtrArray downloads;[m
[32m+[m[32m iPtrArray items[max_MediaType];[m
[32m+[m[32m /* TODO: Add a hash to quickly look up a link's media. */[m
};[m
[m
iDefineTypeConstruction(Media)[m
[m
void init_Media(iMedia *d) {[m
[31m- init_PtrArray(&d->images);[m
[31m- init_PtrArray(&d->audio);[m
[31m- init_PtrArray(&d->downloads);[m
[32m+[m[32m iForIndices(i, d->items) {[m
[32m+[m[32m init_PtrArray(&d->items[i]);[m
[32m+[m[32m }[m
}[m
[m
void deinit_Media(iMedia *d) {[m
clear_Media(d);[m
[31m- deinit_PtrArray(&d->downloads);[m
[31m- deinit_PtrArray(&d->audio);[m
[31m- deinit_PtrArray(&d->images);[m
[32m+[m[32m iForIndices(i, d->items) {[m
[32m+[m[32m deinit_PtrArray(&d->items[i]);[m
[32m+[m[32m }[m
}[m
[m
void clear_Media(iMedia *d) {[m
[31m- iForEach(PtrArray, i, &d->images) {[m
[32m+[m[32m iForEach(PtrArray, i, &d->items[image_MediaType]) {[m
deinit_GmImage(i.ptr);[m
}[m
[31m- clear_PtrArray(&d->images);[m
[31m- iForEach(PtrArray, a, &d->audio) {[m
[32m+[m[32m iForEach(PtrArray, a, &d->items[audio_MediaType]) {[m
deinit_GmAudio(a.ptr);[m
}[m
[31m- clear_PtrArray(&d->audio);[m
[31m- iForEach(PtrArray, n, &d->downloads) {[m
[32m+[m[32m iForEach(PtrArray, n, &d->items[download_MediaType]) {[m
deinit_GmDownload(n.ptr);[m
}[m
[31m- clear_PtrArray(&d->downloads);[m
[32m+[m[32m iForEach(PtrArray, f, &d->items[fontpack_MediaType]) {[m
[32m+[m[32m deinit_GmFontpack(f.ptr);[m
[32m+[m[32m }[m
[32m+[m[32m iForIndices(type, d->items) {[m
[32m+[m[32m clear_PtrArray(&d->items[type]);[m
[32m+[m[32m }[m
}[m
[m
size_t memorySize_Media(const iMedia *d) {[m
size_t memSize = 0;[m
[31m- iConstForEach(PtrArray, i, &d->images) {[m
[32m+[m[32m iConstForEach(PtrArray, i, &d->items[image_MediaType]) {[m
const iGmImage *img = i.ptr;[m
if (img->texture) {[m
const iInt2 texSize = size_SDLTexture(img->texture);[m
[36m@@ -335,34 +403,49 @@[m [msize_t memorySize_Media(const iMedia *d) {[m
memSize += size_Block(&img->partialData);[m
}[m
}[m
[31m- iConstForEach(PtrArray, a, &d->audio) {[m
[32m+[m[32m iConstForEach(PtrArray, a, &d->items[audio_MediaType]) {[m
const iGmAudio *audio = a.ptr;[m
if (audio->player) {[m
memSize += sourceDataSize_Player(audio->player);[m
}[m
}[m
[31m- iConstForEach(PtrArray, n, &d->downloads) {[m
[32m+[m[32m iConstForEach(PtrArray, n, &d->items[download_MediaType]) {[m
const iGmDownload *down = n.ptr;[m
memSize += down->numBytes;[m
}[m
return memSize; [m
}[m
[m
[31m-iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) {[m
[31m- iGmDownload *dl = NULL;[m
[31m- iMediaId existing = findLinkDownload_Media(d, linkId);[m
[31m- iBool isNew = iFalse;[m
[31m- if (!existing) {[m
[31m- isNew = iTrue;[m
[31m- dl = new_GmDownload();[m
[31m- dl->props.linkId = linkId;[m
[31m- dl->props.isPermanent = iTrue;[m
[31m- set_String(&dl->props.url, url);[m
[31m- pushBack_PtrArray(&d->downloads, dl);[m
[32m+[m[32miBool setUrl_Media(iMedia *d, iGmLinkId linkId, enum iMediaType mediaType, const iString *url) {[m
[32m+[m[32m iMediaId existing = findMediaForLink_Media(d, linkId, mediaType);[m
[32m+[m[32m const iBool isNew = !existing.id;[m
[32m+[m[32m iGmMediaProps *props = NULL;[m
[32m+[m[32m if (mediaType == download_MediaType) {[m
[32m+[m[32m iGmDownload *dl = NULL;[m
[32m+[m[32m if (isNew) {[m
[32m+[m[32m dl = new_GmDownload();[m
[32m+[m[32m pushBack_PtrArray(&d->items[download_MediaType], dl);[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m dl = at_PtrArray(&d->items[download_MediaType], index_MediaId(existing));[m
[32m+[m[32m }[m
[32m+[m[32m props = &dl->props;[m
}[m
[31m- else {[m
[31m- iGmDownload *dl = at_PtrArray(&d->downloads, existing - 1);[m
[31m- set_String(&dl->props.url, url);[m
[32m+[m[32m else if (mediaType == fontpack_MediaType) {[m
[32m+[m[32m iGmFontpack *fp = NULL;[m
[32m+[m[32m if (isNew) {[m
[32m+[m[32m fp = new_GmFontpack();[m
[32m+[m[32m pushBack_PtrArray(&d->items[fontpack_MediaType], fp);[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m fp = at_PtrArray(&d->items[fontpack_MediaType], index_MediaId(existing));[m
[32m+[m[32m }[m
[32m+[m[32m props = &fp->props;[m
[32m+[m[32m }[m
[32m+[m[32m if (props) {[m
[32m+[m[32m props->linkId = linkId;[m
[32m+[m[32m props->isPermanent = iTrue;[m
[32m+[m[32m set_String(&props->url, url);[m
}[m
return isNew;[m
}[m
[36m@@ -372,16 +455,17 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
const iBool isPartial = (flags & partialData_MediaFlag) != 0;[m
const iBool allowHide = (flags & allowHide_MediaFlag) != 0;[m
const iBool isDeleting = (!mime || !data);[m
[31m- iMediaId existing = findLinkImage_Media(d, linkId);[m
[32m+[m[32m iMediaId existing = findMediaForLink_Media(d, linkId, none_MediaType);// findLinkImage_Media(d, linkId);[m
[32m+[m[32m const size_t existingIndex = index_MediaId(existing);[m
iBool isNew = iFalse;[m
[31m- if (existing) {[m
[32m+[m[32m if (existing.type == image_MediaType) {[m
iGmImage *img;[m
if (isDeleting) {[m
[31m- take_PtrArray(&d->images, existing - 1, (void **) &img);[m
[32m+[m[32m take_PtrArray(&d->items[image_MediaType], existingIndex, (void **) &img);[m
delete_GmImage(img);[m
}[m
else {[m
[31m- img = at_PtrArray(&d->images, existing - 1);[m
[32m+[m[32m img = at_PtrArray(&d->items[image_MediaType], existingIndex);[m
iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */[m
set_Block(&img->partialData, data);[m
if (!isPartial) {[m
[36m@@ -389,14 +473,14 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
}[m
}[m
}[m
[31m- else if ((existing = findLinkAudio_Media(d, linkId)) != 0) {[m
[32m+[m[32m else if (existing.type == audio_MediaType) {[m
iGmAudio *audio;[m
if (isDeleting) {[m
[31m- take_PtrArray(&d->audio, existing - 1, (void **) &audio);[m
[32m+[m[32m take_PtrArray(&d->items[audio_MediaType], existingIndex, (void **) &audio);[m
delete_GmAudio(audio);[m
}[m
else {[m
[31m- audio = at_PtrArray(&d->audio, existing - 1);[m
[32m+[m[32m audio = at_PtrArray(&d->items[audio_MediaType], existingIndex);[m
iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */[m
updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate);[m
if (!isPartial) {[m
[36m@@ -408,14 +492,14 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
}[m
}[m
}[m
[31m- else if ((existing = findLinkDownload_Media(d, linkId)) != 0) {[m
[32m+[m[32m else if (existing.type == download_MediaType) {[m
iGmDownload *dl;[m
if (isDeleting) {[m
[31m- take_PtrArray(&d->downloads, existing - 1, (void **) &dl);[m
[32m+[m[32m take_PtrArray(&d->items[download_MediaType], existingIndex, (void **) &dl);[m
delete_GmDownload(dl);[m
}[m
else {[m
[31m- dl = at_PtrArray(&d->downloads, existing - 1);[m
[32m+[m[32m dl = at_PtrArray(&d->items[download_MediaType], existingIndex);[m
if (isEmpty_String(&dl->props.mime)) {[m
set_String(&dl->props.mime, mime);[m
}[m
[36m@@ -428,6 +512,21 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
}[m
}[m
}[m
[32m+[m[32m else if (existing.type == fontpack_MediaType) {[m
[32m+[m[32m iGmFontpack *fp;[m
[32m+[m[32m if (isDeleting) {[m
[32m+[m[32m take_PtrArray(&d->items[fontpack_MediaType], existingIndex, (void **) &fp);[m
[32m+[m[32m delete_GmFontpack(fp);[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m iAssert(!isPartial);[m
[32m+[m[32m fp = at_PtrArray(&d->items[fontpack_MediaType], existingIndex);[m
[32m+[m[32m if (isEmpty_String(&fp->props.mime)) {[m
[32m+[m[32m set_String(&fp->props.mime, mime);[m
[32m+[m[32m }[m
[32m+[m[32m loadData_GmFontpack_(fp, data);[m
[32m+[m[32m }[m
[32m+[m[32m }[m
else if (!isDeleting) {[m
if (startsWith_String(mime, "image/")) {[m
/* Copy the image to a texture. */[m
[36m@@ -435,7 +534,7 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
img->props.linkId = linkId; /* TODO: use a hash? */[m
img->props.isPermanent = !allowHide;[m
set_String(&img->props.mime, mime);[m
[31m- pushBack_PtrArray(&d->images, img);[m
[32m+[m[32m pushBack_PtrArray(&d->items[image_MediaType], img);[m
if (!isPartial) {[m
makeTexture_GmImage(img);[m
}[m
[36m@@ -450,7 +549,7 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
if (!isPartial) {[m
updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate);[m
}[m
[31m- pushBack_PtrArray(&d->audio, audio);[m
[32m+[m[32m pushBack_PtrArray(&d->items[audio_MediaType], audio);[m
/* Start playing right away. */[m
start_Player(audio->player);[m
postCommandf_App("media.player.started player:%p", audio->player);[m
[36m@@ -460,125 +559,135 @@[m [miBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo[m
return isNew;[m
}[m
[m
[31m-iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) {[m
[31m- /* TODO: use a hash */[m
[31m- iConstForEach(PtrArray, i, &d->images) {[m
[31m- const iGmImage *img = i.ptr;[m
[31m- if (img->props.linkId == linkId) {[m
[31m- return index_PtrArrayConstIterator(&i) + 1;[m
[32m+[m[32mstatic iMediaId findMediaPtr_Media_(const iPtrArray *items, enum iMediaType mediaType, iGmLinkId linkId) {[m
[32m+[m[32m iConstForEach(PtrArray, i, items) {[m
[32m+[m[32m const iGmMediaProps *props = i.ptr;[m
[32m+[m[32m if (props->linkId == linkId) {[m
[32m+[m[32m return (iMediaId){[m
[32m+[m[32m .type = mediaType,[m
[32m+[m[32m .id = index_PtrArrayConstIterator(&i) + 1[m
[32m+[m[32m };[m
}[m
}[m
[31m- return 0;[m
[31m-}[m
[31m-[m
[31m-size_t numAudio_Media(const iMedia *d) {[m
[31m- return size_PtrArray(&d->audio);[m
[32m+[m[32m return iInvalidMediaId;[m
}[m
[m
[31m-iMediaId findLinkAudio_Media(const iMedia *d, iGmLinkId linkId) {[m
[31m- /* TODO: use a hash */[m
[31m- iConstForEach(PtrArray, i, &d->audio) {[m
[31m- const iGmAudio *audio = i.ptr;[m
[31m- if (audio->props.linkId == linkId) {[m
[31m- return index_PtrArrayConstIterator(&i) + 1;[m
[32m+[m[32miMediaId findMediaForLink_Media(const iMedia *d, iGmLinkId linkId, enum iMediaType mediaType) {[m
[32m+[m[32m /* TODO: Use hashes, this will get very slow if there is a large number of media items. */[m
[32m+[m[32m iMediaId mid;[m
[32m+[m[32m for (int i = 0; i < max_MediaType; i++) {[m
[32m+[m[32m if (mediaType == i || !mediaType) {[m
[32m+[m[32m mid = findMediaPtr_Media_(&d->items[i], i, linkId);[m
[32m+[m[32m if (mid.type) {[m
[32m+[m[32m return mid;[m
[32m+[m[32m }[m
}[m
}[m
[31m- return 0;[m
[32m+[m[32m return iInvalidMediaId;[m
}[m
[m
[31m-iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) {[m
[31m- iConstForEach(PtrArray, i, &d->downloads) {[m
[31m- const iGmDownload *dl = i.ptr;[m
[31m- if (dl->props.linkId == linkId) {[m
[31m- return index_PtrArrayConstIterator(&i) + 1;[m
[31m- }[m
[31m- }[m
[31m- return 0;[m
[32m+[m[32msize_t numAudio_Media(const iMedia *d) {[m
[32m+[m[32m return size_PtrArray(&d->items[audio_MediaType]);[m
}[m
[m
iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) {[m
[31m- if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {[m
[31m- const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);[m
[32m+[m[32m iAssert(imageId.type == image_MediaType);[m
[32m+[m[32m const size_t index = index_MediaId(imageId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[image_MediaType])) {[m
[32m+[m[32m const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index);[m
return img->size;[m
}[m
return zero_I2();[m
}[m
[m
[31m-SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) {[m
[31m- if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {[m
[31m- const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);[m
[32m+[m[32mSDL_Texture *imageTexture_Media(const iMedia *d, iMediaId imageId) {[m
[32m+[m[32m iAssert(imageId.type == image_MediaType);[m
[32m+[m[32m const size_t index = index_MediaId(imageId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[image_MediaType])) {[m
[32m+[m[32m const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index);[m
return img->texture;[m
}[m
return NULL;[m
}[m
[m
[31m-iBool imageInfo_Media(const iMedia *d, iMediaId imageId, iGmMediaInfo *info_out) {[m
[31m- if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {[m
[31m- const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);[m
[31m- info_out->numBytes = img->numBytes;[m
[31m- info_out->type = cstr_String(&img->props.mime);[m
[31m- info_out->isPermanent = img->props.isPermanent;[m
[31m- return iTrue;[m
[32m+[m[32miBool info_Media(const iMedia *d, iMediaId mediaId, iGmMediaInfo *info_out) {[m
[32m+[m[32m /* TODO: Use a hash. */[m
[32m+[m[32m const size_t index = index_MediaId(mediaId);[m
[32m+[m[32m switch (mediaId.type) {[m
[32m+[m[32m case image_MediaType:[m
[32m+[m[32m if (index < size_PtrArray(&d->items[image_MediaType])) {[m
[32m+[m[32m const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index);[m
[32m+[m[32m info_out->numBytes = img->numBytes;[m
[32m+[m[32m info_out->type = cstr_String(&img->props.mime);[m
[32m+[m[32m info_out->isPermanent = img->props.isPermanent;[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m break;[m
[32m+[m[32m case audio_MediaType:[m
[32m+[m[32m if (index < size_PtrArray(&d->items[audio_MediaType])) {[m
[32m+[m[32m const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index);[m
[32m+[m[32m info_out->type = cstr_String(&audio->props.mime);[m
[32m+[m[32m info_out->isPermanent = audio->props.isPermanent;[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m break;[m
[32m+[m[32m case download_MediaType:[m
[32m+[m[32m if (index < size_PtrArray(&d->items[download_MediaType])) {[m
[32m+[m[32m const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index);[m
[32m+[m[32m info_out->type = cstr_String(&dl->props.mime);[m
[32m+[m[32m info_out->isPermanent = dl->props.isPermanent;[m
[32m+[m[32m info_out->numBytes = dl->numBytes;[m
[32m+[m[32m return iTrue;[m
[32m+[m[32m }[m
[32m+[m[32m break;[m
[32m+[m[32m case fontpack_MediaType:[m
[32m+[m[32m /* TODO */[m
[32m+[m[32m break;[m
[32m+[m[32m default:[m
[32m+[m[32m break;[m
}[m
iZap(*info_out);[m
return iFalse;[m
}[m
[m
iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) {[m
[31m- if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) {[m
[31m- const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1);[m
[32m+[m[32m iAssert(audioId.type == audio_MediaType);[m
[32m+[m[32m const size_t index = index_MediaId(audioId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[audio_MediaType])) {[m
[32m+[m[32m const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index);[m
return audio->player;[m
}[m
return NULL;[m
}[m
[m
[31m-iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmMediaInfo *info_out) {[m
[31m- if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) {[m
[31m- const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1);[m
[31m- info_out->type = cstr_String(&audio->props.mime);[m
[31m- info_out->isPermanent = audio->props.isPermanent;[m
[31m- return iTrue;[m
[31m- }[m
[31m- iZap(*info_out);[m
[31m- return iFalse;[m
[31m-}[m
[31m-[m
iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) {[m
[31m- if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) {[m
[31m- const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1);[m
[32m+[m[32m iAssert(audioId.type == audio_MediaType);[m
[32m+[m[32m const size_t index = index_MediaId(audioId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[audio_MediaType])) {[m
[32m+[m[32m const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index);[m
return audio->player;[m
}[m
return NULL;[m
}[m
[m
void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) {[m
[31m- for (size_t i = 0; i < size_PtrArray(&d->audio); ++i) {[m
[31m- const iGmAudio *audio = constAt_PtrArray(&d->audio, i);[m
[32m+[m[32m for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) {[m
[32m+[m[32m const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i);[m
if (audio->player) {[m
setPaused_Player(audio->player, setPaused);[m
}[m
}[m
}[m
[m
[31m-iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) {[m
[31m- if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) {[m
[31m- const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1);[m
[31m- info_out->type = cstr_String(&dl->props.mime);[m
[31m- info_out->isPermanent = dl->props.isPermanent;[m
[31m- info_out->numBytes = dl->numBytes;[m
[31m- return iTrue;[m
[31m- }[m
[31m- iZap(*info_out);[m
[31m- return iFalse;[m
[31m-}[m
[31m-[m
void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out,[m
float *bytesPerSecond_out, iBool *isFinished_out) {[m
[31m- *path_out = NULL;[m
[32m+[m[32m iAssert(downloadId.type == download_MediaType);[m
[32m+[m[32m *path_out = NULL;[m
*bytesPerSecond_out = 0.0f;[m
[31m- *isFinished_out = iFalse;[m
[31m- if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) {[m
[31m- const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1);[m
[32m+[m[32m *isFinished_out = iFalse;[m
[32m+[m[32m const size_t index = index_MediaId(downloadId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[download_MediaType])) {[m
[32m+[m[32m const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index);[m
if (dl->path) {[m
*path_out = dl->path;[m
}[m
[36m@@ -587,6 +696,16 @@[m [mvoid downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **p[m
}[m
}[m
[m
[32m+[m[32mvoid fontpackInfo_Media(const iMedia *d, iMediaId fontpackId, iFontpackMediaInfo *info_out) {[m
[32m+[m[32m iAssert(fontpackId.type == fontpack_MediaType);[m
[32m+[m[32m iZap(*info_out);[m
[32m+[m[32m const size_t index = index_MediaId(fontpackId);[m
[32m+[m[32m if (index < size_PtrArray(&d->items[fontpack_MediaType])) {[m
[32m+[m[32m const iGmFontpack *fp = constAt_PtrArray(&d->items[fontpack_MediaType], index);[m
[32m+[m[32m *info_out = fp->info;[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
/----------------------------------------------------------------------------------------------/[m
[m
static void updated_MediaRequest_(iAnyObject *obj) {[m
[1mdiff --git a/src/media.h b/src/media.h[m
[1mindex f7ad6efd..47a4da93 100644[m
[1m--- a/src/media.h[m
[1m+++ b/src/media.h[m
[36m@@ -22,13 +22,13 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m
[m
#pragma once[m
[m
[32m+[m[32m#include "fontpack.h"[m
[32m+[m
#include <the_Foundation/block.h>[m
#include <the_Foundation/string.h>[m
#include <the_Foundation/vec2.h>[m
#include <SDL_render.h>[m
[m
[31m-typedef uint16_t iMediaId;[m
[31m-[m
iDeclareType(Player)[m
iDeclareType(GmMediaInfo)[m
[m
[36m@@ -38,6 +38,7 @@[m [mstruct Impl_GmMediaInfo {[m
iBool isPermanent;[m
};[m
[m
[32m+[m[32miDeclareType(MediaId)[m
iDeclareType(Media)[m
iDeclareTypeConstruction(Media)[m
[m
[36m@@ -46,28 +47,81 @@[m [menum iMediaFlags {[m
partialData_MediaFlag = iBit(2),[m
};[m
[m
[31m-void clear_Media (iMedia *);[m
[31m-iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url);[m
[31m-iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);[m
[31m-[m
[31m-size_t memorySize_Media (const iMedia *);[m
[32m+[m[32menum iMediaType { /* Note: There is a limited number of bits for these; see GmRun below. */[m
[32m+[m[32m none_MediaType,[m
[32m+[m[32m image_MediaType,[m
[32m+[m[32m //animatedImage_MediaType, /* TODO */[m
[32m+[m[32m audio_MediaType,[m
[32m+[m[32m download_MediaType,[m
[32m+[m[32m fontpack_MediaType,[m
[32m+[m[32m max_MediaType[m
[32m+[m[32m};[m
[m
[31m-iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);[m
[31m-iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out);[m
[31m-iInt2 imageSize_Media (const iMedia *, iMediaId imageId);[m
[31m-SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId);[m
[32m+[m[32mstruct Impl_MediaId {[m
[32m+[m[32m enum iMediaType type;[m
[32m+[m[32m uint16_t id; /* see GmRun for actually used number of bits */[m
[32m+[m[32m};[m
[m
[31m-size_t numAudio_Media (const iMedia *);[m
[31m-iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId);[m
[31m-iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out);[m
[31m-iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId);[m
[31m-void pauseAllPlayers_Media(const iMedia *, iBool setPaused);[m
[32m+[m[32miLocalDef size_t index_MediaId(const iMediaId mediaId) {[m
[32m+[m[32m return (size_t) mediaId.id - 1;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m#define iInvalidMediaId (iMediaId){ none_MediaType, 0 }[m
[32m+[m
[32m+[m[32mvoid clear_Media (iMedia *);[m
[32m+[m[32miBool setUrl_Media (iMedia *, uint16_t linkId, enum iMediaType mediaType, const iString *url);[m
[32m+[m[32miBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags);[m
[32m+[m
[32m+[m[32msize_t memorySize_Media (const iMedia *);[m
[32m+[m[32miMediaId findMediaForLink_Media (const iMedia *, uint16_t linkId, enum iMediaType mediaType);[m
[32m+[m
[32m+[m[32miMediaId id_Media (const iMedia *, uint16_t linkId, enum iMediaType type);[m
[32m+[m[32miBool info_Media (const iMedia *, iMediaId mediaId, iGmMediaInfo *info_out);[m
[32m+[m
[32m+[m[32miLocalDef iMediaId findLinkImage_Media(const iMedia *d, uint16_t linkId) {[m
[32m+[m[32m return findMediaForLink_Media(d, linkId, image_MediaType);[m
[32m+[m[32m}[m
[32m+[m[32miLocalDef iMediaId findLinkAudio_Media (const iMedia *d, uint16_t linkId) {[m
[32m+[m[32m return findMediaForLink_Media(d, linkId, audio_MediaType);[m
[32m+[m[32m}[m
[32m+[m[32miLocalDef iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) {[m
[32m+[m[32m return findMediaForLink_Media(d, linkId, download_MediaType);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miLocalDef iBool imageInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) {[m
[32m+[m[32m return info_Media(d, (iMediaId){ image_MediaType, mediaId }, info_out);[m
[32m+[m[32m}[m
[32m+[m[32miLocalDef iBool audioInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) {[m
[32m+[m[32m return info_Media(d, (iMediaId){ audio_MediaType, mediaId }, info_out);[m
[32m+[m[32m}[m
[32m+[m[32miLocalDef iBool downloadInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) {[m
[32m+[m[32m return info_Media(d, (iMediaId){ download_MediaType, mediaId }, info_out);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32miInt2 imageSize_Media (const iMedia *, iMediaId imageId);[m
[32m+[m[32mSDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId);[m
[32m+[m
[32m+[m[32msize_t numAudio_Media (const iMedia *);[m
[32m+[m[32miPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId);[m
[32m+[m[32mvoid pauseAllPlayers_Media (const iMedia *, iBool setPaused);[m
[m
[31m-iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId);[m
[31m-iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out);[m
void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out,[m
float *bytesPerSecond_out, iBool *isFinished_out);[m
[m
[32m+[m[32miDeclareType(FontpackMediaInfo)[m
[32m+[m
[32m+[m[32mstruct Impl_FontpackMediaInfo {[m
[32m+[m[32m iFontPackId packId;[m
[32m+[m[32m iBool isValid;[m
[32m+[m[32m iBool isInstalled;[m
[32m+[m[32m iBool isReadOnly;[m
[32m+[m[32m size_t sizeInBytes;[m
[32m+[m[32m iStringList *names;[m
[32m+[m[32m};[m
[32m+[m
[32m+[m[32mvoid fontpackInfo_Media (const iMedia *, iMediaId fontpackId,[m
[32m+[m[32m iFontpackMediaInfo *info_out);[m
[32m+[m
/----------------------------------------------------------------------------------------------/[m
[m
iDeclareType(GmRequest)[m
[36m@@ -78,7 +132,7 @@[m [miDeclareClass(MediaRequest)[m
struct Impl_MediaRequest {[m
iObject object;[m
iDocumentWidget *doc;[m
[31m- unsigned int linkId;[m
[32m+[m[32m unsigned int linkId;[m[41m [m
iGmRequest * req;[m
};[m
[m
[1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m
[1mindex 45a8cf2d..44db3e5b 100644[m
[1m--- a/src/ui/documentwidget.c[m
[1m+++ b/src/ui/documentwidget.c[m
[36m@@ -400,7 +400,18 @@[m [mvoid init_DocumentWidget(iDocumentWidget *d) {[m
addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");[m
}[m
[m
[32m+[m[32mvoid cancelAllRequests_DocumentWidget(iDocumentWidget *d) {[m
[32m+[m[32m iForEach(ObjectList, i, d->media) {[m
[32m+[m[32m iMediaRequest *mr = i.object;[m
[32m+[m[32m cancel_GmRequest(mr->req);[m
[32m+[m[32m }[m
[32m+[m[32m if (d->request) {[m
[32m+[m[32m cancel_GmRequest(d->request);[m
[32m+[m[32m }[m
[32m+[m[32m}[m
[32m+[m
void deinit_DocumentWidget(iDocumentWidget *d) {[m
[32m+[m[32m cancelAllRequests_DocumentWidget(d);[m
pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);[m
removeTicker_App(animate_DocumentWidget_, d);[m
removeTicker_App(prerender_DocumentWidget_, d);[m
[36m@@ -564,7 +575,8 @@[m [mstatic void addVisible_DocumentWidget_(void *context, const iGmRun *run) {[m
pushBack_PtrArray(&d->visibleWideRuns, run);[m
}[m
}[m
[31m- if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) {[m
[32m+[m[32m /* Image runs are static so they're drawn as part of the content. */[m
[32m+[m[32m if (run->mediaType && run->mediaType != image_MediaType) {[m
iAssert(run->mediaId);[m
pushBack_PtrArray(&d->visibleMedia, run);[m
}[m
[36m@@ -758,14 +770,14 @@[m [mstatic uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {[m
uint32_t interval = invalidInterval_;[m
iConstForEach(PtrArray, i, &d->visibleMedia) {[m
const iGmRun *run = i.ptr;[m
[31m- if (run->mediaType == audio_GmRunMediaType) {[m
[31m- iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);[m
[32m+[m[32m if (run->mediaType == audio_MediaType) {[m
[32m+[m[32m iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));[m
if (flags_Player(plr) & adjustingVolume_PlayerFlag ||[m
(isStarted_Player(plr) && !isPaused_Player(plr))) {[m
interval = iMin(interval, 1000 / 15);[m
}[m
}[m
[31m- else if (run->mediaType == download_GmRunMediaType) {[m
[32m+[m[32m else if (run->mediaType == download_MediaType) {[m
interval = iMin(interval, 1000);[m
}[m
}[m
[36m@@ -784,8 +796,8 @@[m [mstatic void updateMedia_DocumentWidget_(iDocumentWidget *d) {[m
refresh_Widget(d);[m
iConstForEach(PtrArray, i, &d->visibleMedia) {[m
const iGmRun *run = i.ptr;[m
[31m- if (run->mediaType == audio_GmRunMediaType) {[m
[31m- iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId);[m
[32m+[m[32m if (run->mediaType == audio_MediaType) {[m
[32m+[m[32m iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));[m
if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&[m
flags_Player(plr) & adjustingVolume_PlayerFlag) {[m
setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);[m
[36m@@ -1244,6 +1256,9 @@[m [mstatic const char *zipPageHeading_(const iRangecc mime) {[m
if (equalCase_Rangecc(mime, "application/gpub+zip")) {[m
return book_Icon " Gempub";[m
}[m
[32m+[m[32m else if (equalCase_Rangecc(mime, mimeType_FontPack)) {[m
[32m+[m[32m return "\U0001f520 Fontpack";[m
[32m+[m[32m }[m
iRangecc type = iNullRange;[m
nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */[m
nextSplit_Rangecc(mime, "/", &type);[m
[36m@@ -1258,165 +1273,175 @@[m [mstatic const char *zipPageHeading_(const iRangecc mime) {[m
[m
static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {[m
iWidget *w = as_Widget(d);[m
[31m- delete_Gempub(d->sourceGempub);[m
[31m- d->sourceGempub = NULL;[m
[31m- if (!cmpCase_String(&d->sourceMime, "application/octet-stream") ||[m
[31m- !cmpCase_String(&d->sourceMime, mimeType_Gempub) ||[m
[31m- endsWithCase_String(d->mod.url, ".gpub")) {[m
[31m- iGempub *gempub = new_Gempub();[m
[31m- if (open_Gempub(gempub, &d->sourceContent)) {[m
[31m- setBaseUrl_Gempub(gempub, d->mod.url);[m
[31m- setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));[m
[31m- setCStr_String(&d->sourceMime, mimeType_Gempub);[m
[31m- d->sourceGempub = gempub;[m
[31m- }[m
[31m- else {[m
[31m- delete_Gempub(gempub);[m
[31m- }[m
[31m- }[m
[31m- if (!d->sourceGempub) {[m
[31m- const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));[m
[31m- iBool isInside = iFalse;[m
[31m- if (localPath && !fileExists_FileInfo(localPath)) {[m
[31m- /* This URL may refer to a file inside the archive. */[m
[31m- localPath = findContainerArchive_Path(localPath);[m
[31m- isInside = iTrue;[m
[31m- }[m
[31m- if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) {[m
[32m+[m[32m /* Gempub page behavior and footer actions. */ {[m
[32m+[m[32m /* TODO: move this to gempub.c */[m
[32m+[m[32m delete_Gempub(d->sourceGempub);[m
[32m+[m[32m d->sourceGempub = NULL;[m
[32m+[m[32m if (!cmpCase_String(&d->sourceMime, "application/octet-stream") ||[m
[32m+[m[32m !cmpCase_String(&d->sourceMime, mimeType_Gempub) ||[m
[32m+[m[32m endsWithCase_String(d->mod.url, ".gpub")) {[m
iGempub *gempub = new_Gempub();[m
[31m- if (openFile_Gempub(gempub, localPath)) {[m
[31m- setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath)));[m
[31m- if (!isInside) {[m
[31m- setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));[m
[31m- setCStr_String(&d->sourceMime, mimeType_Gempub);[m
[31m- }[m
[32m+[m[32m if (open_Gempub(gempub, &d->sourceContent)) {[m
[32m+[m[32m setBaseUrl_Gempub(gempub, d->mod.url);[m
[32m+[m[32m setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));[m
[32m+[m[32m setCStr_String(&d->sourceMime, mimeType_Gempub);[m
d->sourceGempub = gempub;[m
}[m
else {[m
delete_Gempub(gempub);[m
}[m
}[m
[31m- }[m
[31m- if (d->sourceGempub) {[m
[31m- if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) {[m
[31m- if (!isRemote_Gempub(d->sourceGempub)) {[m
[31m- iArray *items = collectNew_Array(sizeof(iMenuItem));[m
[31m- pushBack_Array([m
[31m- items,[m
[31m- &(iMenuItem){ book_Icon " ${gempub.cover.view}",[m
[31m- 0,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });[m
[31m- if (navSize_Gempub(d->sourceGempub) > 0) {[m
[31m- pushBack_Array([m
[31m- items,[m
[31m- &(iMenuItem){[m
[31m- format_CStr(forwardArrow_Icon " %s",[m
[31m- cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),[m
[31m- SDLK_RIGHT,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });[m
[31m- }[m
[31m- makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));[m
[32m+[m[32m if (!d->sourceGempub) {[m
[32m+[m[32m const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));[m
[32m+[m[32m iBool isInside = iFalse;[m
[32m+[m[32m if (localPath && !fileExists_FileInfo(localPath)) {[m
[32m+[m[32m /* This URL may refer to a file inside the archive. */[m
[32m+[m[32m localPath = findContainerArchive_Path(localPath);[m
[32m+[m[32m isInside = iTrue;[m
}[m
[31m- else {[m
[31m- makeFooterButtons_DocumentWidget_([m
[31m- d,[m
[31m- (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}",[m
[31m- SDLK_s,[m
[31m- KMOD_PRIMARY | KMOD_SHIFT,[m
[31m- "document.save open:1" },[m
[31m- { download_Icon " " saveToDownloads_Label,[m
[31m- SDLK_s,[m
[31m- KMOD_PRIMARY,[m
[31m- "document.save" } },[m
[31m- 2);[m
[31m- }[m
[31m- if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {[m
[31m- redoLayout_GmDocument(d->doc);[m
[31m- updateVisible_DocumentWidget_(d);[m
[31m- invalidate_DocumentWidget_(d);[m
[32m+[m[32m if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) {[m
[32m+[m[32m iGempub *gempub = new_Gempub();[m
[32m+[m[32m if (openFile_Gempub(gempub, localPath)) {[m
[32m+[m[32m setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath)));[m
[32m+[m[32m if (!isInside) {[m
[32m+[m[32m setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));[m
[32m+[m[32m setCStr_String(&d->sourceMime, mimeType_Gempub);[m
[32m+[m[32m }[m
[32m+[m[32m d->sourceGempub = gempub;[m
[32m+[m[32m }[m
[32m+[m[32m else {[m
[32m+[m[32m delete_Gempub(gempub);[m
[32m+[m[32m }[m
}[m
}[m
[31m- else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {[m
[31m- makeFooterButtons_DocumentWidget_([m
[31m- d,[m
[31m- (iMenuItem[]){ { format_CStr(book_Icon " %s",[m
[31m- cstr_String(property_Gempub(d->sourceGempub,[m
[31m- title_GempubProperty))),[m
[31m- SDLK_LEFT,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },[m
[31m- 1);[m
[31m- }[m
[31m- else {[m
[31m- /* Navigation buttons. */[m
[31m- iArray *items = collectNew_Array(sizeof(iMenuItem));[m
[31m- const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);[m
[31m- if (navIndex != iInvalidPos) {[m
[31m- if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {[m
[32m+[m[32m if (d->sourceGempub) {[m
[32m+[m[32m if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) {[m
[32m+[m[32m if (!isRemote_Gempub(d->sourceGempub)) {[m
[32m+[m[32m iArray *items = collectNew_Array(sizeof(iMenuItem));[m
pushBack_Array([m
items,[m
[31m- &(iMenuItem){[m
[31m- format_CStr(forwardArrow_Icon " %s",[m
[31m- cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))),[m
[31m- SDLK_RIGHT,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) });[m
[32m+[m[32m &(iMenuItem){ book_Icon " ${gempub.cover.view}",[m
[32m+[m[32m 0,[m
[32m+[m[32m 0,[m
[32m+[m[32m format_CStr("!open url:%s",[m
[32m+[m[32m cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });[m
[32m+[m[32m if (navSize_Gempub(d->sourceGempub) > 0) {[m
[32m+[m[32m pushBack_Array([m
[32m+[m[32m items,[m
[32m+[m[32m &(iMenuItem){[m
[32m+[m[32m format_CStr(forwardArrow_Icon " %s",[m
[32m+[m[32m cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),[m
[32m+[m[32m SDLK_RIGHT,[m
[32m+[m[32m 0,[m
[32m+[m[32m format_CStr("!open url:%s",[m
[32m+[m[32m cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });[m
[32m+[m[32m }[m
[32m+[m[32m makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));[m
}[m
[31m- if (navIndex > 0) {[m
[31m- pushBack_Array([m
[31m- items,[m
[31m- &(iMenuItem){[m
[31m- format_CStr(backArrow_Icon " %s",[m
[31m- cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))),[m
[31m- SDLK_LEFT,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) });[m
[32m+[m[32m else {[m
[32m+[m[32m makeFooterButtons_DocumentWidget_([m
[32m+[m[32m d,[m
[32m+[m[32m (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}",[m
[32m+[m[32m SDLK_s,[m
[32m+[m[32m KMOD_PRIMARY | KMOD_SHIFT,[m
[32m+[m[32m "document.save open:1" },[m
[32m+[m[32m { download_Icon " " saveToDownloads_Label,[m
[32m+[m[32m SDLK_s,[m
[32m+[m[32m KMOD_PRIMARY,[m
[32m+[m[32m "document.save" } },[m
[32m+[m[32m 2);[m
}[m
[31m- else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {[m
[31m- pushBack_Array([m
[31m- items,[m
[31m- &(iMenuItem){[m
[31m- format_CStr(book_Icon " %s",[m
[31m- cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),[m
[31m- SDLK_LEFT,[m
[31m- 0,[m
[31m- format_CStr("!open url:%s",[m
[31m- cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });[m
[32m+[m[32m if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {[m
[32m+[m[32m redoLayout_GmDocument(d->doc);[m
[32m+[m[32m updateVisible_DocumentWidget_(d);[m
[32m+[m[32m invalidate_DocumentWidget_(d);[m
}[m
}[m
[31m- if (!isEmpty_Array(items)) {[m
[31m- makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); [m
[32m+[m[32m else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {[m
[32m+[m[32m makeFooterButtons_DocumentWidget_([m
[32m+[m[32m d,[m
[32m+[m[32m (iMenuItem[]){ { format_CStr(book_Icon " %s",[m
[32m+[m[32m cstr_String(property_Gempub(d->sourceGempub,[m
[32m+[m[32m title_GempubProperty))),[m
[32m+[m[32m SDLK_LEFT,[m
[32m+[m[32m 0,[m
[32m+[m[32m format_CStr("!open url:%s",[m
[32m+[m[32m cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },[m
[32m+[m[32m 1);[m
}[m
[31m- }[m
[31m- if (!isCached && prefs_App()->pinSplit &&[m
[31m- equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {[m
[31m- const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub);[m
[31m- if (navStart) {[m
[31m- iWindow *win = get_Window();[m
[31m- /* Auto-split to show index and the first navigation link. */[m
[31m- if (numRoots_Window(win) == 2) {[m
[31m- /* This document is showing the index page. */[m
[31m- iRoot *other = otherRoot_Window(win, w->root);[m
[31m- postCommandf_Root(other, "open url:%s", cstr_String(navStart));[m
[31m- if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) {[m
[31m- /* On the wrong side. */[m
text/plain
This content has been proxied by September (ba2dc).