diff options
-rw-r--r-- | res/arabic.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/cjk.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/default.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/firasans.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/literata.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/nunito.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | res/tinos.fontpack/fontpack.ini | 2 | ||||
-rw-r--r-- | src/app.c | 1 | ||||
-rw-r--r-- | src/defs.h | 4 | ||||
-rw-r--r-- | src/fontpack.c | 299 | ||||
-rw-r--r-- | src/fontpack.h | 65 | ||||
-rw-r--r-- | src/gempub.c | 2 | ||||
-rw-r--r-- | src/gmdocument.c | 159 | ||||
-rw-r--r-- | src/gmdocument.h | 16 | ||||
-rw-r--r-- | src/gmrequest.c | 9 | ||||
-rw-r--r-- | src/gmutil.c | 8 | ||||
-rw-r--r-- | src/media.c | 349 | ||||
-rw-r--r-- | src/media.h | 92 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 429 | ||||
-rw-r--r-- | src/ui/documentwidget.h | 2 | ||||
-rw-r--r-- | src/ui/mediaui.c | 88 | ||||
-rw-r--r-- | src/ui/mediaui.h | 20 |
22 files changed, 1030 insertions, 527 deletions
diff --git a/res/arabic.fontpack/fontpack.ini b/res/arabic.fontpack/fontpack.ini index 00ad5241..305878ce 100644 --- a/res/arabic.fontpack/fontpack.ini +++ b/res/arabic.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [arabic] | 3 | [arabic] |
2 | name = "Noto Sans Arabic UI" | 4 | name = "Noto Sans Arabic UI" |
3 | auxiliary = true | 5 | auxiliary = true |
diff --git a/res/cjk.fontpack/fontpack.ini b/res/cjk.fontpack/fontpack.ini index fbac54be..6e0d274c 100644 --- a/res/cjk.fontpack/fontpack.ini +++ b/res/cjk.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [notosansjp] | 3 | [notosansjp] |
2 | name = "Noto Sans JP" | 4 | name = "Noto Sans JP" |
3 | auxiliary = true | 5 | auxiliary = true |
diff --git a/res/default.fontpack/fontpack.ini b/res/default.fontpack/fontpack.ini index 68316ef6..f8ef31ce 100644 --- a/res/default.fontpack/fontpack.ini +++ b/res/default.fontpack/fontpack.ini | |||
@@ -17,6 +17,8 @@ | |||
17 | # `glyphscale` and `voffset` can also be specified separately for the UI and | 17 | # `glyphscale` and `voffset` can also be specified separately for the UI and |
18 | # document domains by prefixing `ui.` or `doc.` to the key. | 18 | # document domains by prefixing `ui.` or `doc.` to the key. |
19 | 19 | ||
20 | version = 1 | ||
21 | |||
20 | [default] | 22 | [default] |
21 | name = "Source Sans" | 23 | name = "Source Sans" |
22 | regular = "SourceSans3-Regular.ttf" | 24 | regular = "SourceSans3-Regular.ttf" |
diff --git a/res/firasans.fontpack/fontpack.ini b/res/firasans.fontpack/fontpack.ini index 4378a757..c6eb4c77 100644 --- a/res/firasans.fontpack/fontpack.ini +++ b/res/firasans.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [firasans] | 3 | [firasans] |
2 | name = "Fira Sans" | 4 | name = "Fira Sans" |
3 | glyphscale = 0.85 | 5 | glyphscale = 0.85 |
diff --git a/res/literata.fontpack/fontpack.ini b/res/literata.fontpack/fontpack.ini index e4e49bcb..7c29491d 100644 --- a/res/literata.fontpack/fontpack.ini +++ b/res/literata.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [literata] | 3 | [literata] |
2 | name = "Literata" | 4 | name = "Literata" |
3 | regular = "Literata-Regular-opsz=14.ttf" | 5 | regular = "Literata-Regular-opsz=14.ttf" |
diff --git a/res/nunito.fontpack/fontpack.ini b/res/nunito.fontpack/fontpack.ini index ea4a12b8..2f2471e1 100644 --- a/res/nunito.fontpack/fontpack.ini +++ b/res/nunito.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [nunito] | 3 | [nunito] |
2 | name = "Nunito" | 4 | name = "Nunito" |
3 | tweaks = 0x1 # some hardcoded kerning changes (`Th`, etc.) | 5 | tweaks = 0x1 # some hardcoded kerning changes (`Th`, etc.) |
diff --git a/res/tinos.fontpack/fontpack.ini b/res/tinos.fontpack/fontpack.ini index 8759b752..a2cf811e 100644 --- a/res/tinos.fontpack/fontpack.ini +++ b/res/tinos.fontpack/fontpack.ini | |||
@@ -1,3 +1,5 @@ | |||
1 | version = 1 | ||
2 | |||
1 | [tinos] | 3 | [tinos] |
2 | name = "Tinos" | 4 | name = "Tinos" |
3 | glyphscale = 0.850 | 5 | glyphscale = 0.850 |
@@ -2669,6 +2669,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2669 | const iBool isSplit = numRoots_Window(get_Window()) > 1; | 2669 | const iBool isSplit = numRoots_Window(get_Window()) > 1; |
2670 | if (tabCount_Widget(tabs) > 1 || isSplit) { | 2670 | if (tabCount_Widget(tabs) > 1 || isSplit) { |
2671 | iWidget *closed = removeTabPage_Widget(tabs, index); | 2671 | iWidget *closed = removeTabPage_Widget(tabs, index); |
2672 | cancelAllRequests_DocumentWidget((iDocumentWidget *) closed); | ||
2672 | destroy_Widget(closed); /* released later */ | 2673 | destroy_Widget(closed); /* released later */ |
2673 | if (index == tabCount_Widget(tabs)) { | 2674 | if (index == tabCount_Widget(tabs)) { |
2674 | index--; | 2675 | index--; |
@@ -125,11 +125,13 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
125 | #define delete_Icon "\u232b" | 125 | #define delete_Icon "\u232b" |
126 | #define copy_Icon "\u2398" //"\u2bba" | 126 | #define copy_Icon "\u2398" //"\u2bba" |
127 | #define check_Icon "\u2714" | 127 | #define check_Icon "\u2714" |
128 | #define ballotCheck_Icon "\U0001f5f9" | 128 | #define ballotChecked_Icon "\U0001f5f9" |
129 | #define ballotUnchecked_Icon "\U0001f5f9" | ||
129 | #define inbox_Icon "\U0001f4e5" | 130 | #define inbox_Icon "\U0001f4e5" |
130 | #define book_Icon "\U0001f56e" | 131 | #define book_Icon "\U0001f56e" |
131 | #define bookmark_Icon "\U0001f516" | 132 | #define bookmark_Icon "\U0001f516" |
132 | #define folder_Icon "\U0001f4c1" | 133 | #define folder_Icon "\U0001f4c1" |
134 | #define file_Icon "\U0001f5ce" | ||
133 | #define openTab_Icon "\u2750" | 135 | #define openTab_Icon "\u2750" |
134 | #define openTabBg_Icon "\u2b1a" | 136 | #define openTabBg_Icon "\u2b1a" |
135 | #define openExt_Icon "\u27a0" | 137 | #define openExt_Icon "\u27a0" |
diff --git a/src/fontpack.c b/src/fontpack.c index ca1d1582..fb1c98ee 100644 --- a/src/fontpack.c +++ b/src/fontpack.c | |||
@@ -33,7 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
33 | #include <the_Foundation/string.h> | 33 | #include <the_Foundation/string.h> |
34 | #include <the_Foundation/toml.h> | 34 | #include <the_Foundation/toml.h> |
35 | 35 | ||
36 | /* TODO: Clean up and/or reorder this file, it's a bit unorganized. */ | 36 | const char *mimeType_FontPack = "application/lagrange-fontpack+zip"; |
37 | 37 | ||
38 | float scale_FontSize(enum iFontSize size) { | 38 | float scale_FontSize(enum iFontSize size) { |
39 | static const float sizes[max_FontSize] = { | 39 | static const float sizes[max_FontSize] = { |
@@ -57,38 +57,9 @@ float scale_FontSize(enum iFontSize size) { | |||
57 | return sizes[size]; | 57 | return sizes[size]; |
58 | } | 58 | } |
59 | 59 | ||
60 | iDeclareType(Fonts) | ||
61 | |||
62 | struct Impl_Fonts { | ||
63 | iString userDir; | ||
64 | iPtrArray packs; | ||
65 | iPtrArray files; | ||
66 | iPtrArray specOrder; /* specs sorted by priority */ | ||
67 | }; | ||
68 | |||
69 | static iFonts fonts_; | ||
70 | |||
71 | static void unloadFiles_Fonts_(iFonts *d) { | ||
72 | /* TODO: Mark all files in font packs as not resident. */ | ||
73 | iForEach(PtrArray, i, &d->files) { | ||
74 | delete_FontFile(i.ptr); | ||
75 | } | ||
76 | clear_PtrArray(&d->files); | ||
77 | } | ||
78 | |||
79 | static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) { | ||
80 | iForEach(PtrArray, i, &d->files) { | ||
81 | iFontFile *ff = i.ptr; | ||
82 | if (equal_String(&ff->id, id)) { | ||
83 | return ff; | ||
84 | } | ||
85 | } | ||
86 | return NULL; | ||
87 | } | ||
88 | |||
89 | /*----------------------------------------------------------------------------------------------*/ | 60 | /*----------------------------------------------------------------------------------------------*/ |
90 | 61 | ||
91 | iDefineTypeConstruction(FontFile) | 62 | iDefineObjectConstruction(FontFile) |
92 | 63 | ||
93 | void init_FontFile(iFontFile *d) { | 64 | void init_FontFile(iFontFile *d) { |
94 | init_String(&d->id); | 65 | init_String(&d->id); |
@@ -114,7 +85,7 @@ static void load_FontFile_(iFontFile *d, const iBlock *data) { | |||
114 | HB_MEMORY_MODE_READONLY, NULL, NULL); | 85 | HB_MEMORY_MODE_READONLY, NULL, NULL); |
115 | d->hbFace = hb_face_create(d->hbBlob, 0); | 86 | d->hbFace = hb_face_create(d->hbBlob, 0); |
116 | d->hbFont = hb_font_create(d->hbFace); | 87 | d->hbFont = hb_font_create(d->hbFace); |
117 | #endif | 88 | #endif |
118 | } | 89 | } |
119 | 90 | ||
120 | static void unload_FontFile_(iFontFile *d) { | 91 | static void unload_FontFile_(iFontFile *d) { |
@@ -126,12 +97,13 @@ static void unload_FontFile_(iFontFile *d) { | |||
126 | d->hbFont = NULL; | 97 | d->hbFont = NULL; |
127 | d->hbFace = NULL; | 98 | d->hbFace = NULL; |
128 | d->hbBlob = NULL; | 99 | d->hbBlob = NULL; |
129 | #endif | 100 | #endif |
130 | clear_Block(&d->sourceData); | 101 | clear_Block(&d->sourceData); |
131 | iZap(d->stbInfo); | 102 | iZap(d->stbInfo); |
132 | } | 103 | } |
133 | 104 | ||
134 | void deinit_FontFile(iFontFile *d) { | 105 | void deinit_FontFile(iFontFile *d) { |
106 | printf("FontFile %p {%s} is DESTROYED\n", d, cstr_String(&d->id)); | ||
135 | unload_FontFile_(d); | 107 | unload_FontFile_(d); |
136 | deinit_Block(&d->sourceData); | 108 | deinit_Block(&d->sourceData); |
137 | deinit_String(&d->id); | 109 | deinit_String(&d->id); |
@@ -156,12 +128,12 @@ void measureGlyph_FontFile(const iFontFile *d, uint32_t glyphIndex, | |||
156 | 128 | ||
157 | /*----------------------------------------------------------------------------------------------*/ | 129 | /*----------------------------------------------------------------------------------------------*/ |
158 | 130 | ||
159 | |||
160 | iDefineTypeConstruction(FontSpec) | 131 | iDefineTypeConstruction(FontSpec) |
161 | 132 | ||
162 | void init_FontSpec(iFontSpec *d) { | 133 | void init_FontSpec(iFontSpec *d) { |
163 | init_String(&d->id); | 134 | init_String(&d->id); |
164 | init_String(&d->name); | 135 | init_String(&d->name); |
136 | init_String(&d->sourcePath); | ||
165 | d->flags = 0; | 137 | d->flags = 0; |
166 | d->priority = 0; | 138 | d->priority = 0; |
167 | for (int i = 0; i < 2; ++i) { | 139 | for (int i = 0; i < 2; ++i) { |
@@ -173,52 +145,119 @@ void init_FontSpec(iFontSpec *d) { | |||
173 | } | 145 | } |
174 | 146 | ||
175 | void deinit_FontSpec(iFontSpec *d) { | 147 | void deinit_FontSpec(iFontSpec *d) { |
148 | /* FontFile references are held by FontSpecs. */ | ||
149 | iForIndices(i, d->styles) { | ||
150 | iRelease(d->styles[i]); | ||
151 | } | ||
152 | deinit_String(&d->sourcePath); | ||
176 | deinit_String(&d->name); | 153 | deinit_String(&d->name); |
177 | deinit_String(&d->id); | 154 | deinit_String(&d->id); |
178 | } | 155 | } |
179 | 156 | ||
180 | /*----------------------------------------------------------------------------------------------*/ | 157 | /*----------------------------------------------------------------------------------------------*/ |
181 | 158 | ||
182 | iDeclareType(FontPack) | 159 | iDeclareType(Fonts) |
183 | iDeclareTypeConstruction(FontPack) | 160 | |
161 | struct Impl_Fonts { | ||
162 | iString userDir; | ||
163 | iPtrArray packs; | ||
164 | iObjectList *files; | ||
165 | iPtrArray specOrder; /* specs sorted by priority */ | ||
166 | }; | ||
167 | |||
168 | static iFonts fonts_; | ||
169 | |||
170 | static void unloadFiles_Fonts_(iFonts *d) { | ||
171 | /* TODO: Mark all files in font packs as not resident. */ | ||
172 | clear_ObjectList(d->files); | ||
173 | } | ||
174 | |||
175 | static iFontFile *findFile_Fonts_(iFonts *d, const iString *id) { | ||
176 | iForEach(ObjectList, i, d->files) { | ||
177 | iFontFile *ff = i.object; | ||
178 | if (equal_String(&ff->id, id)) { | ||
179 | return ff; | ||
180 | } | ||
181 | } | ||
182 | return NULL; | ||
183 | } | ||
184 | |||
185 | static void releaseUnusedFiles_Fonts_(iFonts *d) { | ||
186 | iForEach(ObjectList, i, d->files) { | ||
187 | iFontFile *ff = i.object; | ||
188 | if (ff->object.refCount == 1) { | ||
189 | /* No specs use this. */ | ||
190 | //printf("[Fonts] releasing unused font file: %p {%s}\n", ff, cstr_String(&ff->id)); | ||
191 | remove_ObjectListIterator(&i); | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | |||
196 | /*----------------------------------------------------------------------------------------------*/ | ||
184 | 197 | ||
185 | struct Impl_FontPack { | 198 | struct Impl_FontPack { |
186 | const iArchive *archive; /* opened ZIP archive */ | 199 | iString id; /* lowercase filename without the .fontpack extension */ |
200 | int version; | ||
201 | iBool isStandalone; | ||
202 | iBool isReadOnly; | ||
187 | iArray fonts; /* array of FontSpecs */ | 203 | iArray fonts; /* array of FontSpecs */ |
204 | const iArchive *archive; /* opened ZIP archive */ | ||
188 | iString * loadPath; | 205 | iString * loadPath; |
189 | iFontSpec * loadSpec; | 206 | iFontSpec * loadSpec; |
190 | }; | 207 | }; |
191 | 208 | ||
209 | iDefineTypeConstruction(FontPack) | ||
210 | |||
192 | void init_FontPack(iFontPack *d) { | 211 | void init_FontPack(iFontPack *d) { |
193 | d->archive = NULL; | 212 | init_String(&d->id); |
213 | d->version = 0; | ||
214 | d->isStandalone = iFalse; | ||
215 | d->isReadOnly = iFalse; | ||
194 | init_Array(&d->fonts, sizeof(iFontSpec)); | 216 | init_Array(&d->fonts, sizeof(iFontSpec)); |
217 | d->archive = NULL; | ||
195 | d->loadSpec = NULL; | 218 | d->loadSpec = NULL; |
196 | d->loadPath = NULL; | 219 | d->loadPath = NULL; |
197 | } | 220 | } |
198 | 221 | ||
199 | void deinit_FontPack(iFontPack *d) { | 222 | void deinit_FontPack(iFontPack *d) { |
223 | iAssert(d->archive == NULL); | ||
224 | iAssert(d->loadSpec == NULL); | ||
200 | delete_String(d->loadPath); | 225 | delete_String(d->loadPath); |
201 | iForEach(Array, i, &d->fonts) { | 226 | iForEach(Array, i, &d->fonts) { |
202 | deinit_FontSpec(i.value); | 227 | deinit_FontSpec(i.value); |
203 | } | 228 | } |
204 | deinit_Array(&d->fonts); | 229 | deinit_Array(&d->fonts); |
205 | iAssert(d->archive == NULL); | 230 | deinit_String(&d->id); |
206 | iAssert(d->loadSpec == NULL); | 231 | releaseUnusedFiles_Fonts_(&fonts_); |
207 | } | 232 | } |
208 | 233 | ||
209 | iDefineTypeConstruction(FontPack) | 234 | iFontPackId id_FontPack(const iFontPack *d) { |
235 | return (iFontPackId){ &d->id, d->version }; | ||
236 | } | ||
237 | |||
238 | const iPtrArray *listSpecs_FontPack(const iFontPack *d) { | ||
239 | if (!d) return NULL; | ||
240 | iPtrArray *list = collectNew_PtrArray(); | ||
241 | iConstForEach(Array, i, &d->fonts) { | ||
242 | pushBack_PtrArray(list, i.value); | ||
243 | } | ||
244 | return list; | ||
245 | } | ||
210 | 246 | ||
211 | void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) { | 247 | void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart) { |
212 | iFontPack *d = context; | 248 | iFontPack *d = context; |
213 | if (isStart) { | 249 | if (isStart) { |
214 | iAssert(!d->loadSpec); | 250 | iAssert(!d->loadSpec); |
215 | /* Each font ID must be unique. */ | 251 | /* Each font ID must be unique in the non-standalone packs. */ |
216 | if (!findSpec_Fonts(cstr_String(table))) { | 252 | if (d->isStandalone || !findSpec_Fonts(cstr_String(table))) { |
217 | d->loadSpec = new_FontSpec(); | 253 | d->loadSpec = new_FontSpec(); |
218 | set_String(&d->loadSpec->id, table); | 254 | set_String(&d->loadSpec->id, table); |
255 | if (d->loadPath) { | ||
256 | set_String(&d->loadSpec->sourcePath, d->loadPath); | ||
257 | } | ||
219 | } | 258 | } |
220 | } | 259 | } |
221 | else { | 260 | else if (d->loadSpec) { |
222 | /* Set fallback font files. */ { | 261 | /* Set fallback font files. */ { |
223 | const iFontFile **styles = d->loadSpec->styles; | 262 | const iFontFile **styles = d->loadSpec->styles; |
224 | if (!styles[regular_FontStyle]) { | 263 | if (!styles[regular_FontStyle]) { |
@@ -229,11 +268,11 @@ void handleIniTable_FontPack_(void *context, const iString *table, iBool isStart | |||
229 | return; | 268 | return; |
230 | } | 269 | } |
231 | if (!styles[semiBold_FontStyle]) { | 270 | if (!styles[semiBold_FontStyle]) { |
232 | styles[semiBold_FontStyle] = styles[bold_FontStyle]; | 271 | styles[semiBold_FontStyle] = ref_Object(styles[bold_FontStyle]); |
233 | } | 272 | } |
234 | for (size_t s = 0; s < max_FontStyle; s++) { | 273 | for (size_t s = 0; s < max_FontStyle; s++) { |
235 | if (!styles[s]) { | 274 | if (!styles[s]) { |
236 | styles[s] = styles[regular_FontStyle]; | 275 | styles[s] = ref_Object(styles[regular_FontStyle]); |
237 | } | 276 | } |
238 | } | 277 | } |
239 | } | 278 | } |
@@ -263,7 +302,15 @@ static iBlock *readFile_FontPack_(const iFontPack *d, const iString *path) { | |||
263 | void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key, | 302 | void handleIniKeyValue_FontPack_(void *context, const iString *table, const iString *key, |
264 | const iTomlValue *value) { | 303 | const iTomlValue *value) { |
265 | iFontPack *d = context; | 304 | iFontPack *d = context; |
266 | if (!d->loadSpec) return; | 305 | if (isEmpty_String(table)) { |
306 | if (!cmp_String(key, "version")) { | ||
307 | d->version = number_TomlValue(value); | ||
308 | } | ||
309 | return; | ||
310 | } | ||
311 | if (!d->loadSpec) { | ||
312 | return; | ||
313 | } | ||
267 | iUnused(table); | 314 | iUnused(table); |
268 | if (!cmp_String(key, "name") && value->type == string_TomlType) { | 315 | if (!cmp_String(key, "name") && value->type == string_TomlType) { |
269 | set_String(&d->loadSpec->name, value->value.string); | 316 | set_String(&d->loadSpec->name, value->value.string); |
@@ -322,12 +369,13 @@ void handleIniKeyValue_FontPack_(void *context, const iString *table, const iStr | |||
322 | ff = new_FontFile(); | 369 | ff = new_FontFile(); |
323 | set_String(&ff->id, fontFileId); | 370 | set_String(&ff->id, fontFileId); |
324 | load_FontFile_(ff, data); | 371 | load_FontFile_(ff, data); |
325 | pushBack_PtrArray(&fonts_.files, ff); /* centralized ownership */ | 372 | pushBack_ObjectList(fonts_.files, ff); /* centralized ownership */ |
373 | iRelease(ff); | ||
326 | delete_Block(data); | 374 | delete_Block(data); |
327 | // printf("[FontPack] loaded file: %s\n", cstr_String(fontFileId)); | 375 | // printf("[FontPack] loaded file: %s\n", cstr_String(fontFileId)); |
328 | } | 376 | } |
329 | } | 377 | } |
330 | d->loadSpec->styles[i] = ff; | 378 | d->loadSpec->styles[i] = ref_Object(ff); |
331 | delete_String(fontFileId); | 379 | delete_String(fontFileId); |
332 | break; | 380 | break; |
333 | } | 381 | } |
@@ -348,29 +396,6 @@ static iBool load_FontPack_(iFontPack *d, const iString *ini) { | |||
348 | return ok; | 396 | return ok; |
349 | } | 397 | } |
350 | 398 | ||
351 | #if 0 | ||
352 | iBool loadIniFile_FontPack(iFontPack *d, const iString *iniPath) { | ||
353 | iBeginCollect(); | ||
354 | iBool ok = iFalse; | ||
355 | iFile *f = iClob(new_File(iniPath)); | ||
356 | if (open_File(f, text_FileMode | readOnly_FileMode)) { | ||
357 | d->loadPath = collect_String(newRange_String(dirName_Path(iniPath))); | ||
358 | iString *src = collect_String(readString_File(f)); | ||
359 | |||
360 | iTomlParser *ini = collect_TomlParser(new_TomlParser()); | ||
361 | setHandlers_TomlParser(ini, handleIniTable_FontPack_, handleIniKeyValue_FontPack_, d); | ||
362 | if (!parse_TomlParser(ini, src)) { | ||
363 | fprintf(stderr, "[FontPack] error parsing %s\n", cstr_String(iniPath)); | ||
364 | } | ||
365 | iAssert(d->loadSpec == NULL); | ||
366 | d->loadPath = NULL; | ||
367 | ok = iTrue; | ||
368 | } | ||
369 | iEndCollect(); | ||
370 | return ok; | ||
371 | } | ||
372 | #endif | ||
373 | |||
374 | iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { | 399 | iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { |
375 | d->archive = zip; | 400 | d->archive = zip; |
376 | iBool ok = iFalse; | 401 | iBool ok = iFalse; |
@@ -387,6 +412,28 @@ iBool loadArchive_FontPack(iFontPack *d, const iArchive *zip) { | |||
387 | return ok; | 412 | return ok; |
388 | } | 413 | } |
389 | 414 | ||
415 | void setLoadPath_FontPack(iFontPack *d, const iString *path) { | ||
416 | if (!d->loadPath) { | ||
417 | d->loadPath = new_String(); | ||
418 | } | ||
419 | set_String(d->loadPath, path); | ||
420 | /* Pack ID is based on the file name. */ | ||
421 | setRange_String(&d->id, baseName_Path(path)); | ||
422 | setRange_String(&d->id, withoutExtension_Path(&d->id)); | ||
423 | } | ||
424 | |||
425 | void setStandalone_FontPack(iFontPack *d, iBool standalone) { | ||
426 | d->isStandalone = standalone; | ||
427 | } | ||
428 | |||
429 | void setReadOnly_FontPack(iFontPack *d, iBool readOnly) { | ||
430 | d->isReadOnly = readOnly; | ||
431 | } | ||
432 | |||
433 | iBool isReadOnly_FontPack(const iFontPack *d) { | ||
434 | return d->isReadOnly; | ||
435 | } | ||
436 | |||
390 | /*----------------------------------------------------------------------------------------------*/ | 437 | /*----------------------------------------------------------------------------------------------*/ |
391 | 438 | ||
392 | static void unloadFonts_Fonts_(iFonts *d) { | 439 | static void unloadFonts_Fonts_(iFonts *d) { |
@@ -397,14 +444,23 @@ static void unloadFonts_Fonts_(iFonts *d) { | |||
397 | clear_PtrArray(&d->packs); | 444 | clear_PtrArray(&d->packs); |
398 | } | 445 | } |
399 | 446 | ||
447 | static int cmpName_FontSpecPtr_(const void *a, const void *b) { | ||
448 | const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; | ||
449 | return cmpStringCase_String(&(*p1)->name, &(*p2)->name); | ||
450 | } | ||
451 | |||
400 | static int cmpPriority_FontSpecPtr_(const void *a, const void *b) { | 452 | static int cmpPriority_FontSpecPtr_(const void *a, const void *b) { |
401 | const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; | 453 | const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; |
402 | return -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */ | 454 | const int cmp = -iCmp((*p1)->priority, (*p2)->priority); /* highest priority first */ |
455 | if (cmp) return cmp; | ||
456 | return cmpName_FontSpecPtr_(a, b); | ||
403 | } | 457 | } |
404 | 458 | ||
405 | static int cmpName_FontSpecPtr_(const void *a, const void *b) { | 459 | static int cmpSourceAndPriority_FontSpecPtr_(const void *a, const void *b) { |
406 | const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; | 460 | const iFontSpec **p1 = (const iFontSpec **) a, **p2 = (const iFontSpec **) b; |
407 | return cmpStringCase_String(&(*p1)->name, &(*p2)->name); | 461 | const int cmp = cmpStringCase_String(&(*p1)->sourcePath, &(*p2)->sourcePath); |
462 | if (cmp) return cmp; | ||
463 | return cmpPriority_FontSpecPtr_(a, b); | ||
408 | } | 464 | } |
409 | 465 | ||
410 | static void sortSpecs_Fonts_(iFonts *d) { | 466 | static void sortSpecs_Fonts_(iFonts *d) { |
@@ -422,11 +478,13 @@ void init_Fonts(const char *userDir) { | |||
422 | iFonts *d = &fonts_; | 478 | iFonts *d = &fonts_; |
423 | initCStr_String(&d->userDir, userDir); | 479 | initCStr_String(&d->userDir, userDir); |
424 | init_PtrArray(&d->packs); | 480 | init_PtrArray(&d->packs); |
425 | init_PtrArray(&d->files); | 481 | d->files = new_ObjectList(); |
426 | init_PtrArray(&d->specOrder); | 482 | init_PtrArray(&d->specOrder); |
427 | /* Load the required fonts. */ { | 483 | /* Load the required fonts. */ { |
428 | iFontPack *pack = new_FontPack(); | 484 | iFontPack *pack = new_FontPack(); |
485 | setCStr_String(&pack->id, "default"); | ||
429 | iArchive *arch = new_Archive(); | 486 | iArchive *arch = new_Archive(); |
487 | setReadOnly_FontPack(pack, iTrue); | ||
430 | openData_Archive(arch, &fontpackDefault_Embedded); | 488 | openData_Archive(arch, &fontpackDefault_Embedded); |
431 | loadArchive_FontPack(pack, arch); /* should never fail if we've made it this far */ | 489 | loadArchive_FontPack(pack, arch); /* should never fail if we've made it this far */ |
432 | iRelease(arch); | 490 | iRelease(arch); |
@@ -436,7 +494,7 @@ void init_Fonts(const char *userDir) { | |||
436 | const char *locations[] = { | 494 | const char *locations[] = { |
437 | ".", | 495 | ".", |
438 | "./fonts", | 496 | "./fonts", |
439 | "../share/lagrange", | 497 | "../share/lagrange", /* Note: These must match CMakeLists.txt install destination */ |
440 | "../../share/lagrange", | 498 | "../../share/lagrange", |
441 | concatPath_CStr(userDir, "fonts"), | 499 | concatPath_CStr(userDir, "fonts"), |
442 | userDir, | 500 | userDir, |
@@ -450,7 +508,13 @@ void init_Fonts(const char *userDir) { | |||
450 | iArchive *arch = new_Archive(); | 508 | iArchive *arch = new_Archive(); |
451 | if (openFile_Archive(arch, entryPath)) { | 509 | if (openFile_Archive(arch, entryPath)) { |
452 | iFontPack *pack = new_FontPack(); | 510 | iFontPack *pack = new_FontPack(); |
453 | pack->loadPath = copy_String(entryPath); | 511 | setLoadPath_FontPack(pack, entryPath); |
512 | setReadOnly_FontPack(pack, !isWritable_FileInfo(entry.value)); | ||
513 | #if defined (iPlatformApple) | ||
514 | if (startsWith_String(pack->loadPath, cstr_String(execDir))) { | ||
515 | setReadOnly_FontPack(pack, iTrue); | ||
516 | } | ||
517 | #endif | ||
454 | if (loadArchive_FontPack(pack, arch)) { | 518 | if (loadArchive_FontPack(pack, arch)) { |
455 | pushBack_PtrArray(&d->packs, pack); | 519 | pushBack_PtrArray(&d->packs, pack); |
456 | } | 520 | } |
@@ -491,13 +555,18 @@ void init_Fonts(const char *userDir) { | |||
491 | void deinit_Fonts(void) { | 555 | void deinit_Fonts(void) { |
492 | iFonts *d = &fonts_; | 556 | iFonts *d = &fonts_; |
493 | unloadFonts_Fonts_(d); | 557 | unloadFonts_Fonts_(d); |
494 | unloadFiles_Fonts_(d); | 558 | //unloadFiles_Fonts_(d); |
559 | iAssert(isEmpty_ObjectList(d->files)); | ||
495 | deinit_PtrArray(&d->specOrder); | 560 | deinit_PtrArray(&d->specOrder); |
496 | deinit_PtrArray(&d->packs); | 561 | deinit_PtrArray(&d->packs); |
497 | deinit_PtrArray(&d->files); | 562 | iRelease(d->files); |
498 | deinit_String(&d->userDir); | 563 | deinit_String(&d->userDir); |
499 | } | 564 | } |
500 | 565 | ||
566 | const iPtrArray *listPacks_Fonts(void) { | ||
567 | return &fonts_.packs; | ||
568 | } | ||
569 | |||
501 | const iFontSpec *findSpec_Fonts(const char *fontId) { | 570 | const iFontSpec *findSpec_Fonts(const char *fontId) { |
502 | iFonts *d = &fonts_; | 571 | iFonts *d = &fonts_; |
503 | iConstForEach(PtrArray, i, &d->specOrder) { | 572 | iConstForEach(PtrArray, i, &d->specOrder) { |
@@ -524,3 +593,73 @@ const iPtrArray *listSpecs_Fonts(iBool (*filterFunc)(const iFontSpec *)) { | |||
524 | const iPtrArray *listSpecsByPriority_Fonts(void) { | 593 | const iPtrArray *listSpecsByPriority_Fonts(void) { |
525 | return &fonts_.specOrder; | 594 | return &fonts_.specOrder; |
526 | } | 595 | } |
596 | |||
597 | const iString *infoPage_Fonts(void) { | ||
598 | iFonts *d = &fonts_; | ||
599 | iString *str = collectNewCStr_String("# Fonts\n"); | ||
600 | iPtrArray *specsByPack = collectNew_PtrArray(); | ||
601 | setCopy_PtrArray(specsByPack, &d->specOrder); | ||
602 | sort_Array(specsByPack, cmpSourceAndPriority_FontSpecPtr_); | ||
603 | iString *currentSourcePath = collectNew_String(); | ||
604 | iConstForEach(PtrArray, i, specsByPack) { | ||
605 | const iFontSpec *spec = i.ptr; | ||
606 | if (isEmpty_String(&spec->sourcePath)) { | ||
607 | continue; /* built-in font */ | ||
608 | } | ||
609 | if (!equal_String(&spec->sourcePath, currentSourcePath)) { | ||
610 | appendFormat_String(str, "=> %s %s%s\n", | ||
611 | cstrCollect_String(makeFileUrl_String(&spec->sourcePath)), | ||
612 | endsWithCase_String(&spec->sourcePath, ".fontpack") ? "\U0001f520 " : "", | ||
613 | cstr_Rangecc(baseName_Path(&spec->sourcePath))); | ||
614 | set_String(currentSourcePath, &spec->sourcePath); | ||
615 | } | ||
616 | } | ||
617 | return str; | ||
618 | } | ||
619 | |||
620 | const iFontPack *findPack_Fonts(const iString *path) { | ||
621 | iFonts *d = &fonts_; | ||
622 | iConstForEach(PtrArray, i, &d->packs) { | ||
623 | const iFontPack *pack = i.ptr; | ||
624 | if (pack->loadPath && equal_String(pack->loadPath, path)) { | ||
625 | return pack; | ||
626 | } | ||
627 | } | ||
628 | return NULL; | ||
629 | } | ||
630 | |||
631 | iBool preloadLocalFontpackForPreview_Fonts(iGmDocument *doc) { | ||
632 | iBool wasLoaded = iFalse; | ||
633 | for (size_t linkId = 1; ; linkId++) { | ||
634 | const iString *linkUrl = linkUrl_GmDocument(doc, linkId); | ||
635 | if (!linkUrl) { | ||
636 | break; /* ran out of links */ | ||
637 | } | ||
638 | const int linkFlags = linkFlags_GmDocument(doc, linkId); | ||
639 | if (linkFlags & fontpackFileExtension_GmLinkFlag && | ||
640 | scheme_GmLinkFlag(linkFlags) == file_GmLinkScheme) { | ||
641 | iMediaId linkMedia = findMediaForLink_Media(media_GmDocument(doc), linkId, fontpack_MediaType); | ||
642 | if (linkMedia.type) { | ||
643 | continue; /* got this one already */ | ||
644 | } | ||
645 | iString *filePath = localFilePathFromUrl_String(linkUrl); | ||
646 | iFile *f = new_File(filePath); | ||
647 | if (open_File(f, readOnly_FileMode)) { | ||
648 | iBlock *fontPackArchiveData = readAll_File(f); | ||
649 | setUrl_Media(media_GmDocument(doc), linkId, fontpack_MediaType, linkUrl); | ||
650 | setData_Media(media_GmDocument(doc), | ||
651 | linkId, | ||
652 | collectNewCStr_String(mimeType_FontPack), | ||
653 | fontPackArchiveData, | ||
654 | 0); | ||
655 | delete_Block(fontPackArchiveData); | ||
656 | wasLoaded = iTrue; | ||
657 | } | ||
658 | iRelease(f); | ||
659 | } | ||
660 | } | ||
661 | return wasLoaded; | ||
662 | } | ||
663 | |||
664 | iDefineClass(FontFile) | ||
665 | |||
diff --git a/src/fontpack.h b/src/fontpack.h index e59154e3..429afb5d 100644 --- a/src/fontpack.h +++ b/src/fontpack.h | |||
@@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | # include <hb.h> | 30 | # include <hb.h> |
31 | #endif | 31 | #endif |
32 | 32 | ||
33 | extern const char *mimeType_FontPack; | ||
34 | |||
33 | /* Fontpacks are ZIP archives that contain a configuration file and one of more font | 35 | /* Fontpacks are ZIP archives that contain a configuration file and one of more font |
34 | files. The fontpack format is used instead of plain TTF/OTF because the text renderer | 36 | files. The fontpack format is used instead of plain TTF/OTF because the text renderer |
35 | uses additional metadata about each font. | 37 | uses additional metadata about each font. |
@@ -68,21 +70,13 @@ enum iFontStyle { | |||
68 | 70 | ||
69 | float scale_FontSize (enum iFontSize size); | 71 | float scale_FontSize (enum iFontSize size); |
70 | 72 | ||
71 | iDeclareType(FontSpec) | 73 | /*----------------------------------------------------------------------------------------------*/ |
72 | iDeclareTypeConstruction(FontSpec) | ||
73 | 74 | ||
74 | enum iFontSpecFlags { | 75 | iDeclareClass(FontFile) |
75 | override_FontSpecFlag = iBit(1), | 76 | iDeclareObjectConstruction(FontFile) |
76 | monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ | ||
77 | auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ | ||
78 | arabic_FontSpecFlag = iBit(4), | ||
79 | fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ | ||
80 | }; | ||
81 | |||
82 | iDeclareType(FontFile) | ||
83 | iDeclareTypeConstruction(FontFile) | ||
84 | 77 | ||
85 | struct Impl_FontFile { | 78 | struct Impl_FontFile { |
79 | iObject object; /* reference-counted */ | ||
86 | iString id; /* for detecting when the same file is used in many places */ | 80 | iString id; /* for detecting when the same file is used in many places */ |
87 | enum iFontStyle style; | 81 | enum iFontStyle style; |
88 | iBlock sourceData; | 82 | iBlock sourceData; |
@@ -107,9 +101,26 @@ uint8_t * rasterizeGlyph_FontFile(const iFontFile *, float xScale, float yScal | |||
107 | void measureGlyph_FontFile (const iFontFile *, uint32_t glyphIndex, | 101 | void measureGlyph_FontFile (const iFontFile *, uint32_t glyphIndex, |
108 | float xScale, float yScale, float xShift, | 102 | float xScale, float yScale, float xShift, |
109 | int *x0, int *y0, int *x1, int *y1); | 103 | int *x0, int *y0, int *x1, int *y1); |
104 | |||
105 | /*----------------------------------------------------------------------------------------------*/ | ||
106 | |||
107 | /* FontSpec describes a typeface, combining multiple fonts into a group. | ||
108 | The user will be choosing FontSpecs instead of individual font files. */ | ||
109 | iDeclareType(FontSpec) | ||
110 | iDeclareTypeConstruction(FontSpec) | ||
111 | |||
112 | enum iFontSpecFlags { | ||
113 | override_FontSpecFlag = iBit(1), | ||
114 | monospace_FontSpecFlag = iBit(2), /* can be used in preformatted content */ | ||
115 | auxiliary_FontSpecFlag = iBit(3), /* only used for looking up glyphs missing from other fonts */ | ||
116 | arabic_FontSpecFlag = iBit(4), | ||
117 | fixNunitoKerning_FontSpecFlag = iBit(31), /* manual hardcoded kerning tweaks for Nunito */ | ||
118 | }; | ||
119 | |||
110 | struct Impl_FontSpec { | 120 | struct Impl_FontSpec { |
111 | iString id; /* unique ID */ | 121 | iString id; /* unique ID */ |
112 | iString name; /* human-readable label */ | 122 | iString name; /* human-readable label */ |
123 | iString sourcePath; /* file where the path was loaded, could be a .fontpack */ | ||
113 | int flags; | 124 | int flags; |
114 | int priority; | 125 | int priority; |
115 | float heightScale[2]; /* overall height scaling; ui, document */ | 126 | float heightScale[2]; /* overall height scaling; ui, document */ |
@@ -121,10 +132,38 @@ struct Impl_FontSpec { | |||
121 | iLocalDef int scaleType_FontSpec(enum iFontSize sizeId) { | 132 | iLocalDef int scaleType_FontSpec(enum iFontSize sizeId) { |
122 | return sizeId / contentRegular_FontSize; | 133 | return sizeId / contentRegular_FontSize; |
123 | } | 134 | } |
124 | 135 | ||
136 | /*----------------------------------------------------------------------------------------------*/ | ||
137 | |||
138 | iDeclareType(FontPack) | ||
139 | iDeclareTypeConstruction(FontPack) | ||
140 | |||
141 | iDeclareType(FontPackId) | ||
142 | |||
143 | struct Impl_FontPackId { | ||
144 | const iString *id; | ||
145 | int version; | ||
146 | }; | ||
147 | |||
148 | void setReadOnly_FontPack (iFontPack *, iBool readOnly); | ||
149 | void setStandalone_FontPack (iFontPack *, iBool standalone); | ||
150 | void setLoadPath_FontPack (iFontPack *, const iString *path); | ||
151 | iBool loadArchive_FontPack (iFontPack *, const iArchive *zip); | ||
152 | |||
153 | iFontPackId id_FontPack (const iFontPack *); | ||
154 | const iPtrArray * listSpecs_FontPack (const iFontPack *); | ||
155 | iBool isReadOnly_FontPack (const iFontPack *); | ||
156 | |||
157 | iDeclareType(GmDocument) | ||
158 | |||
125 | void init_Fonts (const char *userDir); | 159 | void init_Fonts (const char *userDir); |
126 | void deinit_Fonts (void); | 160 | void deinit_Fonts (void); |
127 | 161 | ||
162 | const iFontPack * findPack_Fonts (const iString *path); | ||
128 | const iFontSpec * findSpec_Fonts (const char *fontId); | 163 | const iFontSpec * findSpec_Fonts (const char *fontId); |
164 | const iPtrArray * listPacks_Fonts (void); | ||
129 | const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *)); | 165 | const iPtrArray * listSpecs_Fonts (iBool (*filterFunc)(const iFontSpec *)); |
130 | const iPtrArray * listSpecsByPriority_Fonts (void); | 166 | const iPtrArray * listSpecsByPriority_Fonts (void); |
167 | const iString * infoPage_Fonts (void); | ||
168 | |||
169 | iBool preloadLocalFontpackForPreview_Fonts (iGmDocument *doc); | ||
diff --git a/src/gempub.c b/src/gempub.c index 23846414..952d72a1 100644 --- a/src/gempub.c +++ b/src/gempub.c | |||
@@ -337,7 +337,7 @@ iBool preloadCoverImage_Gempub(const iGempub *d, iGmDocument *doc) { | |||
337 | for (size_t linkId = 1; ; linkId++) { | 337 | for (size_t linkId = 1; ; linkId++) { |
338 | const iString *linkUrl = linkUrl_GmDocument(doc, linkId); | 338 | const iString *linkUrl = linkUrl_GmDocument(doc, linkId); |
339 | if (!linkUrl) break; | 339 | if (!linkUrl) break; |
340 | if (findLinkImage_Media(media_GmDocument(doc), linkId)) { | 340 | if (findLinkImage_Media(media_GmDocument(doc), linkId).type) { |
341 | continue; /* got this already */ | 341 | continue; /* got this already */ |
342 | } | 342 | } |
343 | if (linkFlags_GmDocument(doc, linkId) & imageFileExtension_GmLinkFlag) { | 343 | if (linkFlags_GmDocument(doc, linkId) & imageFileExtension_GmLinkFlag) { |
diff --git a/src/gmdocument.c b/src/gmdocument.c index c98b0bb8..c271ad94 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | #include "ui/color.h" | 27 | #include "ui/color.h" |
28 | #include "ui/text.h" | 28 | #include "ui/text.h" |
29 | #include "ui/metrics.h" | 29 | #include "ui/metrics.h" |
30 | #include "ui/mediaui.h" | ||
30 | #include "ui/window.h" | 31 | #include "ui/window.h" |
31 | #include "visited.h" | 32 | #include "visited.h" |
32 | #include "bookmarks.h" | 33 | #include "bookmarks.h" |
@@ -224,7 +225,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
224 | setScheme_GmLink_(link, finger_GmLinkScheme); | 225 | setScheme_GmLink_(link, finger_GmLinkScheme); |
225 | } | 226 | } |
226 | else if (equalCase_Rangecc(parts.scheme, "file")) { | 227 | else if (equalCase_Rangecc(parts.scheme, "file")) { |
227 | setScheme_GmLink_(link, file_GmLinkScheme); | 228 | setScheme_GmLink_(link, file_GmLinkScheme); |
228 | } | 229 | } |
229 | else if (equalCase_Rangecc(parts.scheme, "data")) { | 230 | else if (equalCase_Rangecc(parts.scheme, "data")) { |
230 | setScheme_GmLink_(link, data_GmLinkScheme); | 231 | setScheme_GmLink_(link, data_GmLinkScheme); |
@@ -251,6 +252,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
251 | endsWithCase_String(path, ".mid") || endsWithCase_String(path, ".ogg")) { | 252 | endsWithCase_String(path, ".mid") || endsWithCase_String(path, ".ogg")) { |
252 | link->flags |= audioFileExtension_GmLinkFlag; | 253 | link->flags |= audioFileExtension_GmLinkFlag; |
253 | } | 254 | } |
255 | else if (endsWithCase_String(path, ".fontpack")) { | ||
256 | link->flags |= fontpackFileExtension_GmLinkFlag; | ||
257 | } | ||
254 | delete_String(path); | 258 | delete_String(path); |
255 | } | 259 | } |
256 | /* Check if visited. */ | 260 | /* Check if visited. */ |
@@ -503,7 +507,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
503 | static const char *arrow = rightArrowhead_Icon; | 507 | static const char *arrow = rightArrowhead_Icon; |
504 | static const char *envelope = envelope_Icon; | 508 | static const char *envelope = envelope_Icon; |
505 | static const char *bullet = "\u2022"; | 509 | static const char *bullet = "\u2022"; |
506 | static const char *folder = "\U0001f4c1"; | 510 | static const char *folder = file_Icon; |
507 | static const char *globe = globe_Icon; | 511 | static const char *globe = globe_Icon; |
508 | static const char *quote = "\u201c"; | 512 | static const char *quote = "\u201c"; |
509 | static const char *magnifyingGlass = "\U0001f50d"; | 513 | static const char *magnifyingGlass = "\U0001f50d"; |
@@ -900,77 +904,77 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
900 | ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; | 904 | ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; |
901 | /* Image or audio content. */ | 905 | /* Image or audio content. */ |
902 | if (type == link_GmLineType) { | 906 | if (type == link_GmLineType) { |
903 | const iMediaId imageId = findLinkImage_Media(d->media, run.linkId); | 907 | /* TODO: Cleanup here? Move to a function of its own. */ |
904 | const iMediaId audioId = !imageId ? findLinkAudio_Media(d->media, run.linkId) : 0; | 908 | // enum iMediaType mediaType = none_MediaType; |
905 | const iMediaId downloadId = !imageId && !audioId ? findLinkDownload_Media(d->media, run.linkId) : 0; | 909 | const iMediaId media = findMediaForLink_Media(d->media, run.linkId, none_MediaType); |
906 | if (imageId) { | 910 | iGmMediaInfo info; |
907 | iGmMediaInfo img; | 911 | info_Media(d->media, media, &info); |
908 | imageInfo_Media(d->media, imageId, &img); | 912 | run.mediaType = media.type; |
909 | const iInt2 imgSize = imageSize_Media(d->media, imageId); | 913 | run.mediaId = media.id; |
910 | linkContentWasLaidOut_GmDocument_(d, &img, run.linkId); | 914 | run.text = iNullRange; |
911 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 915 | run.font = uiLabel_FontId; |
916 | run.color = 0; | ||
917 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | ||
918 | if (media.type) { | ||
912 | pos.y += margin; | 919 | pos.y += margin; |
913 | run.bounds.pos = pos; | 920 | run.bounds.size.y = 0; |
914 | run.bounds.size.x = d->size.x; | ||
915 | const float aspect = (float) imgSize.y / (float) imgSize.x; | ||
916 | run.bounds.size.y = d->size.x * aspect; | ||
917 | /* Extend the image to full width, including outside margin, if the viewport | ||
918 | is narrow enough. */ | ||
919 | if (isFullWidthImages) { | ||
920 | run.bounds.size.x += d->outsideMargin * 2; | ||
921 | run.bounds.size.y += d->outsideMargin * 2 * aspect; | ||
922 | run.bounds.pos.x -= d->outsideMargin; | ||
923 | } | ||
924 | run.visBounds = run.bounds; | ||
925 | const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); | ||
926 | if (width_Rect(run.visBounds) > maxSize.x) { | ||
927 | /* Don't scale the image up. */ | ||
928 | run.visBounds.size.y = | ||
929 | run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds); | ||
930 | run.visBounds.size.x = maxSize.x; | ||
931 | run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; | ||
932 | run.bounds.size.y = run.visBounds.size.y; | ||
933 | } | ||
934 | run.text = iNullRange; | ||
935 | run.font = 0; | ||
936 | run.color = 0; | ||
937 | run.mediaType = image_GmRunMediaType; | ||
938 | run.mediaId = imageId; | ||
939 | pushBack_Array(&d->layout, &run); | ||
940 | pos.y += run.bounds.size.y + margin; | ||
941 | } | ||
942 | else if (audioId) { | ||
943 | iGmMediaInfo info; | ||
944 | audioInfo_Media(d->media, audioId, &info); | ||
945 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); | 921 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); |
946 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | ||
947 | pos.y += margin; | ||
948 | run.bounds.pos = pos; | ||
949 | run.bounds.size.x = d->size.x; | ||
950 | run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; | ||
951 | run.visBounds = run.bounds; | ||
952 | run.text = iNullRange; | ||
953 | run.color = 0; | ||
954 | run.mediaType = audio_GmRunMediaType; | ||
955 | run.mediaId = audioId; | ||
956 | pushBack_Array(&d->layout, &run); | ||
957 | pos.y += run.bounds.size.y + margin; | ||
958 | } | 922 | } |
959 | else if (downloadId) { | 923 | switch (media.type) { |
960 | iGmMediaInfo info; | 924 | case image_MediaType: { |
961 | downloadInfo_Media(d->media, downloadId, &info); | 925 | const iInt2 imgSize = imageSize_Media(d->media, media); |
962 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); | 926 | run.bounds.pos = pos; |
963 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 927 | run.bounds.size.x = d->size.x; |
964 | pos.y += margin; | 928 | const float aspect = (float) imgSize.y / (float) imgSize.x; |
965 | run.bounds.pos = pos; | 929 | run.bounds.size.y = d->size.x * aspect; |
966 | run.bounds.size.x = d->size.x; | 930 | /* Extend the image to full width, including outside margin, if the viewport |
967 | run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; | 931 | is narrow enough. */ |
968 | run.visBounds = run.bounds; | 932 | if (isFullWidthImages) { |
969 | run.text = iNullRange; | 933 | run.bounds.size.x += d->outsideMargin * 2; |
970 | run.color = 0; | 934 | run.bounds.size.y += d->outsideMargin * 2 * aspect; |
971 | run.mediaType = download_GmRunMediaType; | 935 | run.bounds.pos.x -= d->outsideMargin; |
972 | run.mediaId = downloadId; | 936 | } |
973 | pushBack_Array(&d->layout, &run); | 937 | run.visBounds = run.bounds; |
938 | const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); | ||
939 | if (width_Rect(run.visBounds) > maxSize.x) { | ||
940 | /* Don't scale the image up. */ | ||
941 | run.visBounds.size.y = | ||
942 | run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds); | ||
943 | run.visBounds.size.x = maxSize.x; | ||
944 | run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; | ||
945 | run.bounds.size.y = run.visBounds.size.y; | ||
946 | } | ||
947 | pushBack_Array(&d->layout, &run); | ||
948 | break; | ||
949 | } | ||
950 | case audio_MediaType: { | ||
951 | run.bounds.pos = pos; | ||
952 | run.bounds.size.x = d->size.x; | ||
953 | run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; | ||
954 | run.visBounds = run.bounds; | ||
955 | pushBack_Array(&d->layout, &run); | ||
956 | break; | ||
957 | } | ||
958 | case download_MediaType: { | ||
959 | run.bounds.pos = pos; | ||
960 | run.bounds.size.x = d->size.x; | ||
961 | run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId) + 4 * gap_UI; | ||
962 | run.visBounds = run.bounds; | ||
963 | pushBack_Array(&d->layout, &run); | ||
964 | break; | ||
965 | } | ||
966 | case fontpack_MediaType: { | ||
967 | run.bounds.pos = pos; | ||
968 | run.bounds.size.x = d->size.x; | ||
969 | run.bounds.size.y = height_FontpackUI(d->media, media.id, d->size.x); | ||
970 | run.visBounds = run.bounds; | ||
971 | pushBack_Array(&d->layout, &run); | ||
972 | break; | ||
973 | } | ||
974 | default: | ||
975 | break; | ||
976 | } | ||
977 | if (media.type && run.bounds.size.y) { | ||
974 | pos.y += run.bounds.size.y + margin; | 978 | pos.y += run.bounds.size.y + margin; |
975 | } | 979 | } |
976 | } | 980 | } |
@@ -1057,21 +1061,6 @@ const iString *url_GmDocument(const iGmDocument *d) { | |||
1057 | return &d->url; | 1061 | return &d->url; |
1058 | } | 1062 | } |
1059 | 1063 | ||
1060 | #if 0 | ||
1061 | void reset_GmDocument(iGmDocument *d) { | ||
1062 | clear_Media(d->media); | ||
1063 | clearLinks_GmDocument_(d); | ||
1064 | clear_Array(&d->layout); | ||
1065 | clear_Array(&d->headings); | ||
1066 | clear_Array(&d->preMeta); | ||
1067 | clear_String(&d->url); | ||
1068 | clear_String(&d->localHost); | ||
1069 | clear_String(&d->source); | ||
1070 | clear_String(&d->unormSource); | ||
1071 | d->themeSeed = 0; | ||
1072 | } | ||
1073 | #endif | ||
1074 | |||
1075 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | 1064 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { |
1076 | set_Color(tmQuoteIcon_ColorId, | 1065 | set_Color(tmQuoteIcon_ColorId, |
1077 | mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f)); | 1066 | mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f)); |
diff --git a/src/gmdocument.h b/src/gmdocument.h index b2c6d9b7..20bc9890 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -90,6 +90,7 @@ enum iGmLinkFlag { | |||
90 | query_GmLinkFlag = iBit(14), /* Gopher query link */ | 90 | query_GmLinkFlag = iBit(14), /* Gopher query link */ |
91 | iconFromLabel_GmLinkFlag = iBit(15), /* use an Emoji/special character from label */ | 91 | iconFromLabel_GmLinkFlag = iBit(15), /* use an Emoji/special character from label */ |
92 | isOpen_GmLinkFlag = iBit(16), /* currently open in a tab */ | 92 | isOpen_GmLinkFlag = iBit(16), /* currently open in a tab */ |
93 | fontpackFileExtension_GmLinkFlag = iBit(17), | ||
93 | }; | 94 | }; |
94 | 95 | ||
95 | iLocalDef enum iGmLinkScheme scheme_GmLinkFlag(int flags) { | 96 | iLocalDef enum iGmLinkScheme scheme_GmLinkFlag(int flags) { |
@@ -126,13 +127,6 @@ enum iGmRunFlags { | |||
126 | altText_GmRunFlag = iBit(8), | 127 | altText_GmRunFlag = iBit(8), |
127 | }; | 128 | }; |
128 | 129 | ||
129 | enum iGmRunMediaType { | ||
130 | none_GmRunMediaType, | ||
131 | image_GmRunMediaType, | ||
132 | audio_GmRunMediaType, | ||
133 | download_GmRunMediaType, | ||
134 | }; | ||
135 | |||
136 | /* This structure is tightly packed because GmDocuments are mostly composed of | 130 | /* This structure is tightly packed because GmDocuments are mostly composed of |
137 | a large number of GmRuns. */ | 131 | a large number of GmRuns. */ |
138 | struct Impl_GmRun { | 132 | struct Impl_GmRun { |
@@ -146,12 +140,16 @@ struct Impl_GmRun { | |||
146 | uint32_t color : 7; /* see max_ColorId */ | 140 | uint32_t color : 7; /* see max_ColorId */ |
147 | 141 | ||
148 | uint32_t font : 10; | 142 | uint32_t font : 10; |
149 | uint32_t mediaType : 2; | 143 | uint32_t mediaType : 3; |
150 | uint32_t mediaId : 10; /* zero if not an image */ | 144 | uint32_t mediaId : 9; /* zero if not an image */ |
151 | uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */ | 145 | uint32_t preId : 10; /* preformatted block ID (sequential); merge with mediaId? */ |
152 | }; | 146 | }; |
153 | }; | 147 | }; |
154 | 148 | ||
149 | iLocalDef iMediaId mediaId_GmRun(const iGmRun *d) { | ||
150 | return (iMediaId){ .type = d->mediaType, .id = d->mediaId }; | ||
151 | } | ||
152 | |||
155 | iDeclareType(GmRunRange) | 153 | iDeclareType(GmRunRange) |
156 | 154 | ||
157 | struct Impl_GmRunRange { | 155 | struct Impl_GmRunRange { |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 1a9e83a9..f7a22e0a 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -361,6 +361,9 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) { | |||
361 | if (equalCase_Rangecc(path, "debug")) { | 361 | if (equalCase_Rangecc(path, "debug")) { |
362 | return utf8_String(debugInfo_App()); | 362 | return utf8_String(debugInfo_App()); |
363 | } | 363 | } |
364 | if (equalCase_Rangecc(path, "fonts")) { | ||
365 | return utf8_String(infoPage_Fonts()); | ||
366 | } | ||
364 | if (equalCase_Rangecc(path, "feeds")) { | 367 | if (equalCase_Rangecc(path, "feeds")) { |
365 | return utf8_String(entryListPage_Feeds()); | 368 | return utf8_String(entryListPage_Feeds()); |
366 | } | 369 | } |
@@ -710,8 +713,9 @@ void submit_GmRequest(iGmRequest *d) { | |||
710 | sort_Array(sortedInfo, (int (*)(const void *, const void *)) cmp_FileInfoPtr_); | 713 | sort_Array(sortedInfo, (int (*)(const void *, const void *)) cmp_FileInfoPtr_); |
711 | iForEach(PtrArray, s, sortedInfo) { | 714 | iForEach(PtrArray, s, sortedInfo) { |
712 | const iFileInfo *entry = s.ptr; | 715 | const iFileInfo *entry = s.ptr; |
713 | appendFormat_String(page, "=> %s %s%s\n", | 716 | appendFormat_String(page, "=> %s %s%s%s\n", |
714 | cstrCollect_String(makeFileUrl_String(path_FileInfo(entry))), | 717 | cstrCollect_String(makeFileUrl_String(path_FileInfo(entry))), |
718 | isDirectory_FileInfo(entry) ? folder_Icon " " : "", | ||
715 | cstr_Rangecc(baseName_Path(path_FileInfo(entry))), | 719 | cstr_Rangecc(baseName_Path(path_FileInfo(entry))), |
716 | isDirectory_FileInfo(entry) ? iPathSeparator : ""); | 720 | isDirectory_FileInfo(entry) ? iPathSeparator : ""); |
717 | iRelease(entry); | 721 | iRelease(entry); |
@@ -808,9 +812,10 @@ void submit_GmRequest(iGmRequest *d) { | |||
808 | const iString *subPath = e.value; | 812 | const iString *subPath = e.value; |
809 | iRangecc relSub = range_String(subPath); | 813 | iRangecc relSub = range_String(subPath); |
810 | relSub.start += size_String(entryPath); | 814 | relSub.start += size_String(entryPath); |
811 | appendFormat_String(page, "=> %s/%s %s\n", | 815 | appendFormat_String(page, "=> %s/%s %s%s\n", |
812 | cstr_String(&d->url), | 816 | cstr_String(&d->url), |
813 | cstr_String(withSpacesEncoded_String(collectNewRange_String(relSub))), | 817 | cstr_String(withSpacesEncoded_String(collectNewRange_String(relSub))), |
818 | endsWith_Rangecc(relSub, "/") ? folder_Icon " " : "", | ||
814 | cstr_Rangecc(relSub)); | 819 | cstr_Rangecc(relSub)); |
815 | } | 820 | } |
816 | resp->statusCode = success_GmStatusCode; | 821 | resp->statusCode = success_GmStatusCode; |
diff --git a/src/gmutil.c b/src/gmutil.c index 971747d4..5be7e198 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "gmutil.h" | 23 | #include "gmutil.h" |
24 | #include "fontpack.h" | ||
24 | 25 | ||
25 | #include <the_Foundation/file.h> | 26 | #include <the_Foundation/file.h> |
26 | #include <the_Foundation/fileinfo.h> | 27 | #include <the_Foundation/fileinfo.h> |
@@ -511,7 +512,8 @@ const iString *findContainerArchive_Path(const iString *path) { | |||
511 | while (!isEmpty_String(path) && cmp_String(path, ".")) { | 512 | while (!isEmpty_String(path) && cmp_String(path, ".")) { |
512 | iString *dir = newRange_String(dirName_Path(path)); | 513 | iString *dir = newRange_String(dirName_Path(path)); |
513 | if (endsWithCase_String(dir, ".zip") || | 514 | if (endsWithCase_String(dir, ".zip") || |
514 | endsWithCase_String(dir, ".gpub")) { | 515 | endsWithCase_String(dir, ".gpub") || |
516 | endsWithCase_String(dir, ".fontpack")) { | ||
515 | iEndCollect(); | 517 | iEndCollect(); |
516 | return collect_String(dir); | 518 | return collect_String(dir); |
517 | } | 519 | } |
@@ -534,6 +536,9 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { | |||
534 | else if (endsWithCase_String(d, ".gpub")) { | 536 | else if (endsWithCase_String(d, ".gpub")) { |
535 | return "application/gpub+zip"; | 537 | return "application/gpub+zip"; |
536 | } | 538 | } |
539 | else if (endsWithCase_String(d, ".fontpack")) { | ||
540 | return mimeType_FontPack; | ||
541 | } | ||
537 | else if (endsWithCase_String(d, ".xml")) { | 542 | else if (endsWithCase_String(d, ".xml")) { |
538 | return "text/xml"; | 543 | return "text/xml"; |
539 | } | 544 | } |
@@ -562,6 +567,7 @@ const char *mediaTypeFromFileExtension_String(const iString *d) { | |||
562 | return "audio/midi"; | 567 | return "audio/midi"; |
563 | } | 568 | } |
564 | else if (endsWithCase_String(d, ".txt") || | 569 | else if (endsWithCase_String(d, ".txt") || |
570 | endsWithCase_String(d, ".ini") || | ||
565 | endsWithCase_String(d, ".md") || | 571 | endsWithCase_String(d, ".md") || |
566 | endsWithCase_String(d, ".c") || | 572 | endsWithCase_String(d, ".c") || |
567 | endsWithCase_String(d, ".h") || | 573 | endsWithCase_String(d, ".h") || |
diff --git a/src/media.c b/src/media.c index 26f0af4b..0ce2ac5c 100644 --- a/src/media.c +++ b/src/media.c | |||
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | 36 | ||
37 | #include <the_Foundation/file.h> | 37 | #include <the_Foundation/file.h> |
38 | #include <the_Foundation/ptrarray.h> | 38 | #include <the_Foundation/ptrarray.h> |
39 | #include <the_Foundation/stringlist.h> | ||
39 | #include <SDL_hints.h> | 40 | #include <SDL_hints.h> |
40 | #include <SDL_render.h> | 41 | #include <SDL_render.h> |
41 | #include <SDL_timer.h> | 42 | #include <SDL_timer.h> |
@@ -287,45 +288,112 @@ iDefineTypeConstruction(GmDownload) | |||
287 | 288 | ||
288 | /*----------------------------------------------------------------------------------------------*/ | 289 | /*----------------------------------------------------------------------------------------------*/ |
289 | 290 | ||
291 | iDeclareType(GmFontpack) | ||
292 | |||
293 | struct Impl_GmFontpack { | ||
294 | iGmMediaProps props; | ||
295 | iString packId; | ||
296 | iFontpackMediaInfo info; | ||
297 | /* TODO: Font preview images? */ | ||
298 | }; | ||
299 | |||
300 | void init_GmFontpack(iGmFontpack *d) { | ||
301 | init_GmMediaProps_(&d->props); | ||
302 | init_String(&d->packId); | ||
303 | iZap(d->info); | ||
304 | d->info.names = new_StringList(); | ||
305 | } | ||
306 | |||
307 | void deinit_GmFontpack(iGmFontpack *d) { | ||
308 | iRelease(d->info.names); | ||
309 | deinit_String(&d->packId); | ||
310 | deinit_GmMediaProps_(&d->props); | ||
311 | } | ||
312 | |||
313 | static void loadData_GmFontpack_(iGmFontpack *d, const iBlock *data) { | ||
314 | const iString *loadPath = collect_String(localFilePathFromUrl_String(&d->props.url)); | ||
315 | const iFontPack *pack = findPack_Fonts(loadPath); | ||
316 | d->info.isValid = d->info.isInstalled = pack != NULL; | ||
317 | d->info.isReadOnly = iFalse; | ||
318 | if (!pack) { | ||
319 | /* Let's load it now temporarily and see what's inside. */ | ||
320 | iArchive *zip = new_Archive(); | ||
321 | if (openData_Archive(zip, data)) { | ||
322 | iFontPack *fp = collect_FontPack(new_FontPack()); | ||
323 | setLoadPath_FontPack(fp, loadPath); | ||
324 | setStandalone_FontPack(fp, iTrue); | ||
325 | if (loadArchive_FontPack(fp, zip)) { | ||
326 | d->info.isValid = iTrue; | ||
327 | pack = fp; | ||
328 | } | ||
329 | } | ||
330 | iRelease(zip); | ||
331 | } | ||
332 | if (pack) { | ||
333 | set_String(&d->packId, id_FontPack(pack).id); | ||
334 | d->info.packId.id = &d->packId; /* we own this String */ | ||
335 | d->info.packId.version = id_FontPack(pack).version; | ||
336 | d->info.isReadOnly = isReadOnly_FontPack(pack); | ||
337 | } | ||
338 | iPtrSet *unique = new_PtrSet(); | ||
339 | iConstForEach(PtrArray, i, listSpecs_FontPack(pack)) { | ||
340 | const iFontSpec *spec = i.ptr; | ||
341 | pushBack_StringList(d->info.names, &spec->name); | ||
342 | iForIndices(j, spec->styles) { | ||
343 | insert_PtrSet(unique, spec->styles[j]); | ||
344 | } | ||
345 | } | ||
346 | iConstForEach(PtrSet, j, unique) { | ||
347 | d->info.sizeInBytes += size_Block(&((const iFontFile *) *j.value)->sourceData); | ||
348 | } | ||
349 | delete_PtrSet(unique); | ||
350 | } | ||
351 | |||
352 | iDefineTypeConstruction(GmFontpack) | ||
353 | |||
354 | /*----------------------------------------------------------------------------------------------*/ | ||
355 | |||
290 | struct Impl_Media { | 356 | struct Impl_Media { |
291 | iPtrArray images; | 357 | iPtrArray items[max_MediaType]; |
292 | iPtrArray audio; | 358 | /* TODO: Add a hash to quickly look up a link's media. */ |
293 | iPtrArray downloads; | ||
294 | }; | 359 | }; |
295 | 360 | ||
296 | iDefineTypeConstruction(Media) | 361 | iDefineTypeConstruction(Media) |
297 | 362 | ||
298 | void init_Media(iMedia *d) { | 363 | void init_Media(iMedia *d) { |
299 | init_PtrArray(&d->images); | 364 | iForIndices(i, d->items) { |
300 | init_PtrArray(&d->audio); | 365 | init_PtrArray(&d->items[i]); |
301 | init_PtrArray(&d->downloads); | 366 | } |
302 | } | 367 | } |
303 | 368 | ||
304 | void deinit_Media(iMedia *d) { | 369 | void deinit_Media(iMedia *d) { |
305 | clear_Media(d); | 370 | clear_Media(d); |
306 | deinit_PtrArray(&d->downloads); | 371 | iForIndices(i, d->items) { |
307 | deinit_PtrArray(&d->audio); | 372 | deinit_PtrArray(&d->items[i]); |
308 | deinit_PtrArray(&d->images); | 373 | } |
309 | } | 374 | } |
310 | 375 | ||
311 | void clear_Media(iMedia *d) { | 376 | void clear_Media(iMedia *d) { |
312 | iForEach(PtrArray, i, &d->images) { | 377 | iForEach(PtrArray, i, &d->items[image_MediaType]) { |
313 | deinit_GmImage(i.ptr); | 378 | deinit_GmImage(i.ptr); |
314 | } | 379 | } |
315 | clear_PtrArray(&d->images); | 380 | iForEach(PtrArray, a, &d->items[audio_MediaType]) { |
316 | iForEach(PtrArray, a, &d->audio) { | ||
317 | deinit_GmAudio(a.ptr); | 381 | deinit_GmAudio(a.ptr); |
318 | } | 382 | } |
319 | clear_PtrArray(&d->audio); | 383 | iForEach(PtrArray, n, &d->items[download_MediaType]) { |
320 | iForEach(PtrArray, n, &d->downloads) { | ||
321 | deinit_GmDownload(n.ptr); | 384 | deinit_GmDownload(n.ptr); |
322 | } | 385 | } |
323 | clear_PtrArray(&d->downloads); | 386 | iForEach(PtrArray, f, &d->items[fontpack_MediaType]) { |
387 | deinit_GmFontpack(f.ptr); | ||
388 | } | ||
389 | iForIndices(type, d->items) { | ||
390 | clear_PtrArray(&d->items[type]); | ||
391 | } | ||
324 | } | 392 | } |
325 | 393 | ||
326 | size_t memorySize_Media(const iMedia *d) { | 394 | size_t memorySize_Media(const iMedia *d) { |
327 | size_t memSize = 0; | 395 | size_t memSize = 0; |
328 | iConstForEach(PtrArray, i, &d->images) { | 396 | iConstForEach(PtrArray, i, &d->items[image_MediaType]) { |
329 | const iGmImage *img = i.ptr; | 397 | const iGmImage *img = i.ptr; |
330 | if (img->texture) { | 398 | if (img->texture) { |
331 | const iInt2 texSize = size_SDLTexture(img->texture); | 399 | const iInt2 texSize = size_SDLTexture(img->texture); |
@@ -335,34 +403,49 @@ size_t memorySize_Media(const iMedia *d) { | |||
335 | memSize += size_Block(&img->partialData); | 403 | memSize += size_Block(&img->partialData); |
336 | } | 404 | } |
337 | } | 405 | } |
338 | iConstForEach(PtrArray, a, &d->audio) { | 406 | iConstForEach(PtrArray, a, &d->items[audio_MediaType]) { |
339 | const iGmAudio *audio = a.ptr; | 407 | const iGmAudio *audio = a.ptr; |
340 | if (audio->player) { | 408 | if (audio->player) { |
341 | memSize += sourceDataSize_Player(audio->player); | 409 | memSize += sourceDataSize_Player(audio->player); |
342 | } | 410 | } |
343 | } | 411 | } |
344 | iConstForEach(PtrArray, n, &d->downloads) { | 412 | iConstForEach(PtrArray, n, &d->items[download_MediaType]) { |
345 | const iGmDownload *down = n.ptr; | 413 | const iGmDownload *down = n.ptr; |
346 | memSize += down->numBytes; | 414 | memSize += down->numBytes; |
347 | } | 415 | } |
348 | return memSize; | 416 | return memSize; |
349 | } | 417 | } |
350 | 418 | ||
351 | iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { | 419 | iBool setUrl_Media(iMedia *d, iGmLinkId linkId, enum iMediaType mediaType, const iString *url) { |
352 | iGmDownload *dl = NULL; | 420 | iMediaId existing = findMediaForLink_Media(d, linkId, mediaType); |
353 | iMediaId existing = findLinkDownload_Media(d, linkId); | 421 | const iBool isNew = !existing.id; |
354 | iBool isNew = iFalse; | 422 | iGmMediaProps *props = NULL; |
355 | if (!existing) { | 423 | if (mediaType == download_MediaType) { |
356 | isNew = iTrue; | 424 | iGmDownload *dl = NULL; |
357 | dl = new_GmDownload(); | 425 | if (isNew) { |
358 | dl->props.linkId = linkId; | 426 | dl = new_GmDownload(); |
359 | dl->props.isPermanent = iTrue; | 427 | pushBack_PtrArray(&d->items[download_MediaType], dl); |
360 | set_String(&dl->props.url, url); | 428 | } |
361 | pushBack_PtrArray(&d->downloads, dl); | 429 | else { |
430 | dl = at_PtrArray(&d->items[download_MediaType], index_MediaId(existing)); | ||
431 | } | ||
432 | props = &dl->props; | ||
362 | } | 433 | } |
363 | else { | 434 | else if (mediaType == fontpack_MediaType) { |
364 | iGmDownload *dl = at_PtrArray(&d->downloads, existing - 1); | 435 | iGmFontpack *fp = NULL; |
365 | set_String(&dl->props.url, url); | 436 | if (isNew) { |
437 | fp = new_GmFontpack(); | ||
438 | pushBack_PtrArray(&d->items[fontpack_MediaType], fp); | ||
439 | } | ||
440 | else { | ||
441 | fp = at_PtrArray(&d->items[fontpack_MediaType], index_MediaId(existing)); | ||
442 | } | ||
443 | props = &fp->props; | ||
444 | } | ||
445 | if (props) { | ||
446 | props->linkId = linkId; | ||
447 | props->isPermanent = iTrue; | ||
448 | set_String(&props->url, url); | ||
366 | } | 449 | } |
367 | return isNew; | 450 | return isNew; |
368 | } | 451 | } |
@@ -372,16 +455,17 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
372 | const iBool isPartial = (flags & partialData_MediaFlag) != 0; | 455 | const iBool isPartial = (flags & partialData_MediaFlag) != 0; |
373 | const iBool allowHide = (flags & allowHide_MediaFlag) != 0; | 456 | const iBool allowHide = (flags & allowHide_MediaFlag) != 0; |
374 | const iBool isDeleting = (!mime || !data); | 457 | const iBool isDeleting = (!mime || !data); |
375 | iMediaId existing = findLinkImage_Media(d, linkId); | 458 | iMediaId existing = findMediaForLink_Media(d, linkId, none_MediaType);// findLinkImage_Media(d, linkId); |
459 | const size_t existingIndex = index_MediaId(existing); | ||
376 | iBool isNew = iFalse; | 460 | iBool isNew = iFalse; |
377 | if (existing) { | 461 | if (existing.type == image_MediaType) { |
378 | iGmImage *img; | 462 | iGmImage *img; |
379 | if (isDeleting) { | 463 | if (isDeleting) { |
380 | take_PtrArray(&d->images, existing - 1, (void **) &img); | 464 | take_PtrArray(&d->items[image_MediaType], existingIndex, (void **) &img); |
381 | delete_GmImage(img); | 465 | delete_GmImage(img); |
382 | } | 466 | } |
383 | else { | 467 | else { |
384 | img = at_PtrArray(&d->images, existing - 1); | 468 | img = at_PtrArray(&d->items[image_MediaType], existingIndex); |
385 | iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */ | 469 | iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */ |
386 | set_Block(&img->partialData, data); | 470 | set_Block(&img->partialData, data); |
387 | if (!isPartial) { | 471 | if (!isPartial) { |
@@ -389,14 +473,14 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
389 | } | 473 | } |
390 | } | 474 | } |
391 | } | 475 | } |
392 | else if ((existing = findLinkAudio_Media(d, linkId)) != 0) { | 476 | else if (existing.type == audio_MediaType) { |
393 | iGmAudio *audio; | 477 | iGmAudio *audio; |
394 | if (isDeleting) { | 478 | if (isDeleting) { |
395 | take_PtrArray(&d->audio, existing - 1, (void **) &audio); | 479 | take_PtrArray(&d->items[audio_MediaType], existingIndex, (void **) &audio); |
396 | delete_GmAudio(audio); | 480 | delete_GmAudio(audio); |
397 | } | 481 | } |
398 | else { | 482 | else { |
399 | audio = at_PtrArray(&d->audio, existing - 1); | 483 | audio = at_PtrArray(&d->items[audio_MediaType], existingIndex); |
400 | iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ | 484 | iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ |
401 | updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); | 485 | updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); |
402 | if (!isPartial) { | 486 | if (!isPartial) { |
@@ -408,14 +492,14 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
408 | } | 492 | } |
409 | } | 493 | } |
410 | } | 494 | } |
411 | else if ((existing = findLinkDownload_Media(d, linkId)) != 0) { | 495 | else if (existing.type == download_MediaType) { |
412 | iGmDownload *dl; | 496 | iGmDownload *dl; |
413 | if (isDeleting) { | 497 | if (isDeleting) { |
414 | take_PtrArray(&d->downloads, existing - 1, (void **) &dl); | 498 | take_PtrArray(&d->items[download_MediaType], existingIndex, (void **) &dl); |
415 | delete_GmDownload(dl); | 499 | delete_GmDownload(dl); |
416 | } | 500 | } |
417 | else { | 501 | else { |
418 | dl = at_PtrArray(&d->downloads, existing - 1); | 502 | dl = at_PtrArray(&d->items[download_MediaType], existingIndex); |
419 | if (isEmpty_String(&dl->props.mime)) { | 503 | if (isEmpty_String(&dl->props.mime)) { |
420 | set_String(&dl->props.mime, mime); | 504 | set_String(&dl->props.mime, mime); |
421 | } | 505 | } |
@@ -428,6 +512,21 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
428 | } | 512 | } |
429 | } | 513 | } |
430 | } | 514 | } |
515 | else if (existing.type == fontpack_MediaType) { | ||
516 | iGmFontpack *fp; | ||
517 | if (isDeleting) { | ||
518 | take_PtrArray(&d->items[fontpack_MediaType], existingIndex, (void **) &fp); | ||
519 | delete_GmFontpack(fp); | ||
520 | } | ||
521 | else { | ||
522 | iAssert(!isPartial); | ||
523 | fp = at_PtrArray(&d->items[fontpack_MediaType], existingIndex); | ||
524 | if (isEmpty_String(&fp->props.mime)) { | ||
525 | set_String(&fp->props.mime, mime); | ||
526 | } | ||
527 | loadData_GmFontpack_(fp, data); | ||
528 | } | ||
529 | } | ||
431 | else if (!isDeleting) { | 530 | else if (!isDeleting) { |
432 | if (startsWith_String(mime, "image/")) { | 531 | if (startsWith_String(mime, "image/")) { |
433 | /* Copy the image to a texture. */ | 532 | /* Copy the image to a texture. */ |
@@ -435,7 +534,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
435 | img->props.linkId = linkId; /* TODO: use a hash? */ | 534 | img->props.linkId = linkId; /* TODO: use a hash? */ |
436 | img->props.isPermanent = !allowHide; | 535 | img->props.isPermanent = !allowHide; |
437 | set_String(&img->props.mime, mime); | 536 | set_String(&img->props.mime, mime); |
438 | pushBack_PtrArray(&d->images, img); | 537 | pushBack_PtrArray(&d->items[image_MediaType], img); |
439 | if (!isPartial) { | 538 | if (!isPartial) { |
440 | makeTexture_GmImage(img); | 539 | makeTexture_GmImage(img); |
441 | } | 540 | } |
@@ -450,7 +549,7 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
450 | if (!isPartial) { | 549 | if (!isPartial) { |
451 | updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); | 550 | updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); |
452 | } | 551 | } |
453 | pushBack_PtrArray(&d->audio, audio); | 552 | pushBack_PtrArray(&d->items[audio_MediaType], audio); |
454 | /* Start playing right away. */ | 553 | /* Start playing right away. */ |
455 | start_Player(audio->player); | 554 | start_Player(audio->player); |
456 | postCommandf_App("media.player.started player:%p", audio->player); | 555 | postCommandf_App("media.player.started player:%p", audio->player); |
@@ -460,125 +559,135 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo | |||
460 | return isNew; | 559 | return isNew; |
461 | } | 560 | } |
462 | 561 | ||
463 | iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) { | 562 | static iMediaId findMediaPtr_Media_(const iPtrArray *items, enum iMediaType mediaType, iGmLinkId linkId) { |
464 | /* TODO: use a hash */ | 563 | iConstForEach(PtrArray, i, items) { |
465 | iConstForEach(PtrArray, i, &d->images) { | 564 | const iGmMediaProps *props = i.ptr; |
466 | const iGmImage *img = i.ptr; | 565 | if (props->linkId == linkId) { |
467 | if (img->props.linkId == linkId) { | 566 | return (iMediaId){ |
468 | return index_PtrArrayConstIterator(&i) + 1; | 567 | .type = mediaType, |
568 | .id = index_PtrArrayConstIterator(&i) + 1 | ||
569 | }; | ||
469 | } | 570 | } |
470 | } | 571 | } |
471 | return 0; | 572 | return iInvalidMediaId; |
472 | } | ||
473 | |||
474 | size_t numAudio_Media(const iMedia *d) { | ||
475 | return size_PtrArray(&d->audio); | ||
476 | } | 573 | } |
477 | 574 | ||
478 | iMediaId findLinkAudio_Media(const iMedia *d, iGmLinkId linkId) { | 575 | iMediaId findMediaForLink_Media(const iMedia *d, iGmLinkId linkId, enum iMediaType mediaType) { |
479 | /* TODO: use a hash */ | 576 | /* TODO: Use hashes, this will get very slow if there is a large number of media items. */ |
480 | iConstForEach(PtrArray, i, &d->audio) { | 577 | iMediaId mid; |
481 | const iGmAudio *audio = i.ptr; | 578 | for (int i = 0; i < max_MediaType; i++) { |
482 | if (audio->props.linkId == linkId) { | 579 | if (mediaType == i || !mediaType) { |
483 | return index_PtrArrayConstIterator(&i) + 1; | 580 | mid = findMediaPtr_Media_(&d->items[i], i, linkId); |
581 | if (mid.type) { | ||
582 | return mid; | ||
583 | } | ||
484 | } | 584 | } |
485 | } | 585 | } |
486 | return 0; | 586 | return iInvalidMediaId; |
487 | } | 587 | } |
488 | 588 | ||
489 | iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) { | 589 | size_t numAudio_Media(const iMedia *d) { |
490 | iConstForEach(PtrArray, i, &d->downloads) { | 590 | return size_PtrArray(&d->items[audio_MediaType]); |
491 | const iGmDownload *dl = i.ptr; | ||
492 | if (dl->props.linkId == linkId) { | ||
493 | return index_PtrArrayConstIterator(&i) + 1; | ||
494 | } | ||
495 | } | ||
496 | return 0; | ||
497 | } | 591 | } |
498 | 592 | ||
499 | iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) { | 593 | iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) { |
500 | if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { | 594 | iAssert(imageId.type == image_MediaType); |
501 | const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); | 595 | const size_t index = index_MediaId(imageId); |
596 | if (index < size_PtrArray(&d->items[image_MediaType])) { | ||
597 | const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); | ||
502 | return img->size; | 598 | return img->size; |
503 | } | 599 | } |
504 | return zero_I2(); | 600 | return zero_I2(); |
505 | } | 601 | } |
506 | 602 | ||
507 | SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) { | 603 | SDL_Texture *imageTexture_Media(const iMedia *d, iMediaId imageId) { |
508 | if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { | 604 | iAssert(imageId.type == image_MediaType); |
509 | const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); | 605 | const size_t index = index_MediaId(imageId); |
606 | if (index < size_PtrArray(&d->items[image_MediaType])) { | ||
607 | const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); | ||
510 | return img->texture; | 608 | return img->texture; |
511 | } | 609 | } |
512 | return NULL; | 610 | return NULL; |
513 | } | 611 | } |
514 | 612 | ||
515 | iBool imageInfo_Media(const iMedia *d, iMediaId imageId, iGmMediaInfo *info_out) { | 613 | iBool info_Media(const iMedia *d, iMediaId mediaId, iGmMediaInfo *info_out) { |
516 | if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { | 614 | /* TODO: Use a hash. */ |
517 | const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); | 615 | const size_t index = index_MediaId(mediaId); |
518 | info_out->numBytes = img->numBytes; | 616 | switch (mediaId.type) { |
519 | info_out->type = cstr_String(&img->props.mime); | 617 | case image_MediaType: |
520 | info_out->isPermanent = img->props.isPermanent; | 618 | if (index < size_PtrArray(&d->items[image_MediaType])) { |
521 | return iTrue; | 619 | const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); |
620 | info_out->numBytes = img->numBytes; | ||
621 | info_out->type = cstr_String(&img->props.mime); | ||
622 | info_out->isPermanent = img->props.isPermanent; | ||
623 | return iTrue; | ||
624 | } | ||
625 | break; | ||
626 | case audio_MediaType: | ||
627 | if (index < size_PtrArray(&d->items[audio_MediaType])) { | ||
628 | const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); | ||
629 | info_out->type = cstr_String(&audio->props.mime); | ||
630 | info_out->isPermanent = audio->props.isPermanent; | ||
631 | return iTrue; | ||
632 | } | ||
633 | break; | ||
634 | case download_MediaType: | ||
635 | if (index < size_PtrArray(&d->items[download_MediaType])) { | ||
636 | const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); | ||
637 | info_out->type = cstr_String(&dl->props.mime); | ||
638 | info_out->isPermanent = dl->props.isPermanent; | ||
639 | info_out->numBytes = dl->numBytes; | ||
640 | return iTrue; | ||
641 | } | ||
642 | break; | ||
643 | case fontpack_MediaType: | ||
644 | /* TODO */ | ||
645 | break; | ||
646 | default: | ||
647 | break; | ||
522 | } | 648 | } |
523 | iZap(*info_out); | 649 | iZap(*info_out); |
524 | return iFalse; | 650 | return iFalse; |
525 | } | 651 | } |
526 | 652 | ||
527 | iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) { | 653 | iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) { |
528 | if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { | 654 | iAssert(audioId.type == audio_MediaType); |
529 | const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); | 655 | const size_t index = index_MediaId(audioId); |
656 | if (index < size_PtrArray(&d->items[audio_MediaType])) { | ||
657 | const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); | ||
530 | return audio->player; | 658 | return audio->player; |
531 | } | 659 | } |
532 | return NULL; | 660 | return NULL; |
533 | } | 661 | } |
534 | 662 | ||
535 | iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmMediaInfo *info_out) { | ||
536 | if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { | ||
537 | const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); | ||
538 | info_out->type = cstr_String(&audio->props.mime); | ||
539 | info_out->isPermanent = audio->props.isPermanent; | ||
540 | return iTrue; | ||
541 | } | ||
542 | iZap(*info_out); | ||
543 | return iFalse; | ||
544 | } | ||
545 | |||
546 | iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { | 663 | iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { |
547 | if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { | 664 | iAssert(audioId.type == audio_MediaType); |
548 | const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); | 665 | const size_t index = index_MediaId(audioId); |
666 | if (index < size_PtrArray(&d->items[audio_MediaType])) { | ||
667 | const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); | ||
549 | return audio->player; | 668 | return audio->player; |
550 | } | 669 | } |
551 | return NULL; | 670 | return NULL; |
552 | } | 671 | } |
553 | 672 | ||
554 | void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) { | 673 | void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) { |
555 | for (size_t i = 0; i < size_PtrArray(&d->audio); ++i) { | 674 | for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { |
556 | const iGmAudio *audio = constAt_PtrArray(&d->audio, i); | 675 | const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); |
557 | if (audio->player) { | 676 | if (audio->player) { |
558 | setPaused_Player(audio->player, setPaused); | 677 | setPaused_Player(audio->player, setPaused); |
559 | } | 678 | } |
560 | } | 679 | } |
561 | } | 680 | } |
562 | 681 | ||
563 | iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) { | ||
564 | if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { | ||
565 | const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); | ||
566 | info_out->type = cstr_String(&dl->props.mime); | ||
567 | info_out->isPermanent = dl->props.isPermanent; | ||
568 | info_out->numBytes = dl->numBytes; | ||
569 | return iTrue; | ||
570 | } | ||
571 | iZap(*info_out); | ||
572 | return iFalse; | ||
573 | } | ||
574 | |||
575 | void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out, | 682 | void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out, |
576 | float *bytesPerSecond_out, iBool *isFinished_out) { | 683 | float *bytesPerSecond_out, iBool *isFinished_out) { |
577 | *path_out = NULL; | 684 | iAssert(downloadId.type == download_MediaType); |
685 | *path_out = NULL; | ||
578 | *bytesPerSecond_out = 0.0f; | 686 | *bytesPerSecond_out = 0.0f; |
579 | *isFinished_out = iFalse; | 687 | *isFinished_out = iFalse; |
580 | if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { | 688 | const size_t index = index_MediaId(downloadId); |
581 | const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); | 689 | if (index < size_PtrArray(&d->items[download_MediaType])) { |
690 | const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); | ||
582 | if (dl->path) { | 691 | if (dl->path) { |
583 | *path_out = dl->path; | 692 | *path_out = dl->path; |
584 | } | 693 | } |
@@ -587,6 +696,16 @@ void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **p | |||
587 | } | 696 | } |
588 | } | 697 | } |
589 | 698 | ||
699 | void fontpackInfo_Media(const iMedia *d, iMediaId fontpackId, iFontpackMediaInfo *info_out) { | ||
700 | iAssert(fontpackId.type == fontpack_MediaType); | ||
701 | iZap(*info_out); | ||
702 | const size_t index = index_MediaId(fontpackId); | ||
703 | if (index < size_PtrArray(&d->items[fontpack_MediaType])) { | ||
704 | const iGmFontpack *fp = constAt_PtrArray(&d->items[fontpack_MediaType], index); | ||
705 | *info_out = fp->info; | ||
706 | } | ||
707 | } | ||
708 | |||
590 | /*----------------------------------------------------------------------------------------------*/ | 709 | /*----------------------------------------------------------------------------------------------*/ |
591 | 710 | ||
592 | static void updated_MediaRequest_(iAnyObject *obj) { | 711 | static void updated_MediaRequest_(iAnyObject *obj) { |
diff --git a/src/media.h b/src/media.h index f7ad6efd..47a4da93 100644 --- a/src/media.h +++ b/src/media.h | |||
@@ -22,13 +22,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "fontpack.h" | ||
26 | |||
25 | #include <the_Foundation/block.h> | 27 | #include <the_Foundation/block.h> |
26 | #include <the_Foundation/string.h> | 28 | #include <the_Foundation/string.h> |
27 | #include <the_Foundation/vec2.h> | 29 | #include <the_Foundation/vec2.h> |
28 | #include <SDL_render.h> | 30 | #include <SDL_render.h> |
29 | 31 | ||
30 | typedef uint16_t iMediaId; | ||
31 | |||
32 | iDeclareType(Player) | 32 | iDeclareType(Player) |
33 | iDeclareType(GmMediaInfo) | 33 | iDeclareType(GmMediaInfo) |
34 | 34 | ||
@@ -38,6 +38,7 @@ struct Impl_GmMediaInfo { | |||
38 | iBool isPermanent; | 38 | iBool isPermanent; |
39 | }; | 39 | }; |
40 | 40 | ||
41 | iDeclareType(MediaId) | ||
41 | iDeclareType(Media) | 42 | iDeclareType(Media) |
42 | iDeclareTypeConstruction(Media) | 43 | iDeclareTypeConstruction(Media) |
43 | 44 | ||
@@ -46,28 +47,81 @@ enum iMediaFlags { | |||
46 | partialData_MediaFlag = iBit(2), | 47 | partialData_MediaFlag = iBit(2), |
47 | }; | 48 | }; |
48 | 49 | ||
49 | void clear_Media (iMedia *); | 50 | enum iMediaType { /* Note: There is a limited number of bits for these; see GmRun below. */ |
50 | iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); | 51 | none_MediaType, |
51 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); | 52 | image_MediaType, |
52 | 53 | //animatedImage_MediaType, /* TODO */ | |
53 | size_t memorySize_Media (const iMedia *); | 54 | audio_MediaType, |
55 | download_MediaType, | ||
56 | fontpack_MediaType, | ||
57 | max_MediaType | ||
58 | }; | ||
54 | 59 | ||
55 | iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); | 60 | struct Impl_MediaId { |
56 | iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); | 61 | enum iMediaType type; |
57 | iInt2 imageSize_Media (const iMedia *, iMediaId imageId); | 62 | uint16_t id; /* see GmRun for actually used number of bits */ |
58 | SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId); | 63 | }; |
59 | 64 | ||
60 | size_t numAudio_Media (const iMedia *); | 65 | iLocalDef size_t index_MediaId(const iMediaId mediaId) { |
61 | iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId); | 66 | return (size_t) mediaId.id - 1; |
62 | iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out); | 67 | } |
63 | iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); | 68 | |
64 | void pauseAllPlayers_Media(const iMedia *, iBool setPaused); | 69 | #define iInvalidMediaId (iMediaId){ none_MediaType, 0 } |
70 | |||
71 | void clear_Media (iMedia *); | ||
72 | iBool setUrl_Media (iMedia *, uint16_t linkId, enum iMediaType mediaType, const iString *url); | ||
73 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); | ||
74 | |||
75 | size_t memorySize_Media (const iMedia *); | ||
76 | iMediaId findMediaForLink_Media (const iMedia *, uint16_t linkId, enum iMediaType mediaType); | ||
77 | |||
78 | iMediaId id_Media (const iMedia *, uint16_t linkId, enum iMediaType type); | ||
79 | iBool info_Media (const iMedia *, iMediaId mediaId, iGmMediaInfo *info_out); | ||
80 | |||
81 | iLocalDef iMediaId findLinkImage_Media(const iMedia *d, uint16_t linkId) { | ||
82 | return findMediaForLink_Media(d, linkId, image_MediaType); | ||
83 | } | ||
84 | iLocalDef iMediaId findLinkAudio_Media (const iMedia *d, uint16_t linkId) { | ||
85 | return findMediaForLink_Media(d, linkId, audio_MediaType); | ||
86 | } | ||
87 | iLocalDef iMediaId findLinkDownload_Media(const iMedia *d, uint16_t linkId) { | ||
88 | return findMediaForLink_Media(d, linkId, download_MediaType); | ||
89 | } | ||
90 | |||
91 | iLocalDef iBool imageInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { | ||
92 | return info_Media(d, (iMediaId){ image_MediaType, mediaId }, info_out); | ||
93 | } | ||
94 | iLocalDef iBool audioInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { | ||
95 | return info_Media(d, (iMediaId){ audio_MediaType, mediaId }, info_out); | ||
96 | } | ||
97 | iLocalDef iBool downloadInfo_Media(const iMedia *d, uint16_t mediaId, iGmMediaInfo *info_out) { | ||
98 | return info_Media(d, (iMediaId){ download_MediaType, mediaId }, info_out); | ||
99 | } | ||
100 | |||
101 | iInt2 imageSize_Media (const iMedia *, iMediaId imageId); | ||
102 | SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId); | ||
103 | |||
104 | size_t numAudio_Media (const iMedia *); | ||
105 | iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); | ||
106 | void pauseAllPlayers_Media (const iMedia *, iBool setPaused); | ||
65 | 107 | ||
66 | iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId); | ||
67 | iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out); | ||
68 | void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out, | 108 | void downloadStats_Media (const iMedia *, iMediaId downloadId, const iString **path_out, |
69 | float *bytesPerSecond_out, iBool *isFinished_out); | 109 | float *bytesPerSecond_out, iBool *isFinished_out); |
70 | 110 | ||
111 | iDeclareType(FontpackMediaInfo) | ||
112 | |||
113 | struct Impl_FontpackMediaInfo { | ||
114 | iFontPackId packId; | ||
115 | iBool isValid; | ||
116 | iBool isInstalled; | ||
117 | iBool isReadOnly; | ||
118 | size_t sizeInBytes; | ||
119 | iStringList *names; | ||
120 | }; | ||
121 | |||
122 | void fontpackInfo_Media (const iMedia *, iMediaId fontpackId, | ||
123 | iFontpackMediaInfo *info_out); | ||
124 | |||
71 | /*----------------------------------------------------------------------------------------------*/ | 125 | /*----------------------------------------------------------------------------------------------*/ |
72 | 126 | ||
73 | iDeclareType(GmRequest) | 127 | iDeclareType(GmRequest) |
@@ -78,7 +132,7 @@ iDeclareClass(MediaRequest) | |||
78 | struct Impl_MediaRequest { | 132 | struct Impl_MediaRequest { |
79 | iObject object; | 133 | iObject object; |
80 | iDocumentWidget *doc; | 134 | iDocumentWidget *doc; |
81 | unsigned int linkId; | 135 | unsigned int linkId; |
82 | iGmRequest * req; | 136 | iGmRequest * req; |
83 | }; | 137 | }; |
84 | 138 | ||
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 45a8cf2d..44db3e5b 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -400,7 +400,18 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
400 | addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); | 400 | addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); |
401 | } | 401 | } |
402 | 402 | ||
403 | void cancelAllRequests_DocumentWidget(iDocumentWidget *d) { | ||
404 | iForEach(ObjectList, i, d->media) { | ||
405 | iMediaRequest *mr = i.object; | ||
406 | cancel_GmRequest(mr->req); | ||
407 | } | ||
408 | if (d->request) { | ||
409 | cancel_GmRequest(d->request); | ||
410 | } | ||
411 | } | ||
412 | |||
403 | void deinit_DocumentWidget(iDocumentWidget *d) { | 413 | void deinit_DocumentWidget(iDocumentWidget *d) { |
414 | cancelAllRequests_DocumentWidget(d); | ||
404 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | 415 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); |
405 | removeTicker_App(animate_DocumentWidget_, d); | 416 | removeTicker_App(animate_DocumentWidget_, d); |
406 | removeTicker_App(prerender_DocumentWidget_, d); | 417 | removeTicker_App(prerender_DocumentWidget_, d); |
@@ -564,7 +575,8 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
564 | pushBack_PtrArray(&d->visibleWideRuns, run); | 575 | pushBack_PtrArray(&d->visibleWideRuns, run); |
565 | } | 576 | } |
566 | } | 577 | } |
567 | if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { | 578 | /* Image runs are static so they're drawn as part of the content. */ |
579 | if (run->mediaType && run->mediaType != image_MediaType) { | ||
568 | iAssert(run->mediaId); | 580 | iAssert(run->mediaId); |
569 | pushBack_PtrArray(&d->visibleMedia, run); | 581 | pushBack_PtrArray(&d->visibleMedia, run); |
570 | } | 582 | } |
@@ -758,14 +770,14 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { | |||
758 | uint32_t interval = invalidInterval_; | 770 | uint32_t interval = invalidInterval_; |
759 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 771 | iConstForEach(PtrArray, i, &d->visibleMedia) { |
760 | const iGmRun *run = i.ptr; | 772 | const iGmRun *run = i.ptr; |
761 | if (run->mediaType == audio_GmRunMediaType) { | 773 | if (run->mediaType == audio_MediaType) { |
762 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); | 774 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); |
763 | if (flags_Player(plr) & adjustingVolume_PlayerFlag || | 775 | if (flags_Player(plr) & adjustingVolume_PlayerFlag || |
764 | (isStarted_Player(plr) && !isPaused_Player(plr))) { | 776 | (isStarted_Player(plr) && !isPaused_Player(plr))) { |
765 | interval = iMin(interval, 1000 / 15); | 777 | interval = iMin(interval, 1000 / 15); |
766 | } | 778 | } |
767 | } | 779 | } |
768 | else if (run->mediaType == download_GmRunMediaType) { | 780 | else if (run->mediaType == download_MediaType) { |
769 | interval = iMin(interval, 1000); | 781 | interval = iMin(interval, 1000); |
770 | } | 782 | } |
771 | } | 783 | } |
@@ -784,8 +796,8 @@ static void updateMedia_DocumentWidget_(iDocumentWidget *d) { | |||
784 | refresh_Widget(d); | 796 | refresh_Widget(d); |
785 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 797 | iConstForEach(PtrArray, i, &d->visibleMedia) { |
786 | const iGmRun *run = i.ptr; | 798 | const iGmRun *run = i.ptr; |
787 | if (run->mediaType == audio_GmRunMediaType) { | 799 | if (run->mediaType == audio_MediaType) { |
788 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); | 800 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); |
789 | if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && | 801 | if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && |
790 | flags_Player(plr) & adjustingVolume_PlayerFlag) { | 802 | flags_Player(plr) & adjustingVolume_PlayerFlag) { |
791 | setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); | 803 | setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); |
@@ -1244,6 +1256,9 @@ static const char *zipPageHeading_(const iRangecc mime) { | |||
1244 | if (equalCase_Rangecc(mime, "application/gpub+zip")) { | 1256 | if (equalCase_Rangecc(mime, "application/gpub+zip")) { |
1245 | return book_Icon " Gempub"; | 1257 | return book_Icon " Gempub"; |
1246 | } | 1258 | } |
1259 | else if (equalCase_Rangecc(mime, mimeType_FontPack)) { | ||
1260 | return "\U0001f520 Fontpack"; | ||
1261 | } | ||
1247 | iRangecc type = iNullRange; | 1262 | iRangecc type = iNullRange; |
1248 | nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ | 1263 | nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ |
1249 | nextSplit_Rangecc(mime, "/", &type); | 1264 | nextSplit_Rangecc(mime, "/", &type); |
@@ -1258,165 +1273,175 @@ static const char *zipPageHeading_(const iRangecc mime) { | |||
1258 | 1273 | ||
1259 | static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { | 1274 | static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { |
1260 | iWidget *w = as_Widget(d); | 1275 | iWidget *w = as_Widget(d); |
1261 | delete_Gempub(d->sourceGempub); | 1276 | /* Gempub page behavior and footer actions. */ { |
1262 | d->sourceGempub = NULL; | 1277 | /* TODO: move this to gempub.c */ |
1263 | if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || | 1278 | delete_Gempub(d->sourceGempub); |
1264 | !cmpCase_String(&d->sourceMime, mimeType_Gempub) || | 1279 | d->sourceGempub = NULL; |
1265 | endsWithCase_String(d->mod.url, ".gpub")) { | 1280 | if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || |
1266 | iGempub *gempub = new_Gempub(); | 1281 | !cmpCase_String(&d->sourceMime, mimeType_Gempub) || |
1267 | if (open_Gempub(gempub, &d->sourceContent)) { | 1282 | endsWithCase_String(d->mod.url, ".gpub")) { |
1268 | setBaseUrl_Gempub(gempub, d->mod.url); | ||
1269 | setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); | ||
1270 | setCStr_String(&d->sourceMime, mimeType_Gempub); | ||
1271 | d->sourceGempub = gempub; | ||
1272 | } | ||
1273 | else { | ||
1274 | delete_Gempub(gempub); | ||
1275 | } | ||
1276 | } | ||
1277 | if (!d->sourceGempub) { | ||
1278 | const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url)); | ||
1279 | iBool isInside = iFalse; | ||
1280 | if (localPath && !fileExists_FileInfo(localPath)) { | ||
1281 | /* This URL may refer to a file inside the archive. */ | ||
1282 | localPath = findContainerArchive_Path(localPath); | ||
1283 | isInside = iTrue; | ||
1284 | } | ||
1285 | if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) { | ||
1286 | iGempub *gempub = new_Gempub(); | 1283 | iGempub *gempub = new_Gempub(); |
1287 | if (openFile_Gempub(gempub, localPath)) { | 1284 | if (open_Gempub(gempub, &d->sourceContent)) { |
1288 | setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); | 1285 | setBaseUrl_Gempub(gempub, d->mod.url); |
1289 | if (!isInside) { | 1286 | setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); |
1290 | setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); | 1287 | setCStr_String(&d->sourceMime, mimeType_Gempub); |
1291 | setCStr_String(&d->sourceMime, mimeType_Gempub); | ||
1292 | } | ||
1293 | d->sourceGempub = gempub; | 1288 | d->sourceGempub = gempub; |
1294 | } | 1289 | } |
1295 | else { | 1290 | else { |
1296 | delete_Gempub(gempub); | 1291 | delete_Gempub(gempub); |
1297 | } | 1292 | } |
1298 | } | 1293 | } |
1299 | } | 1294 | if (!d->sourceGempub) { |
1300 | if (d->sourceGempub) { | 1295 | const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url)); |
1301 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { | 1296 | iBool isInside = iFalse; |
1302 | if (!isRemote_Gempub(d->sourceGempub)) { | 1297 | if (localPath && !fileExists_FileInfo(localPath)) { |
1303 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | 1298 | /* This URL may refer to a file inside the archive. */ |
1304 | pushBack_Array( | 1299 | localPath = findContainerArchive_Path(localPath); |
1305 | items, | 1300 | isInside = iTrue; |
1306 | &(iMenuItem){ book_Icon " ${gempub.cover.view}", | ||
1307 | 0, | ||
1308 | 0, | ||
1309 | format_CStr("!open url:%s", | ||
1310 | cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); | ||
1311 | if (navSize_Gempub(d->sourceGempub) > 0) { | ||
1312 | pushBack_Array( | ||
1313 | items, | ||
1314 | &(iMenuItem){ | ||
1315 | format_CStr(forwardArrow_Icon " %s", | ||
1316 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), | ||
1317 | SDLK_RIGHT, | ||
1318 | 0, | ||
1319 | format_CStr("!open url:%s", | ||
1320 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); | ||
1321 | } | ||
1322 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1323 | } | 1301 | } |
1324 | else { | 1302 | if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) { |
1325 | makeFooterButtons_DocumentWidget_( | 1303 | iGempub *gempub = new_Gempub(); |
1326 | d, | 1304 | if (openFile_Gempub(gempub, localPath)) { |
1327 | (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", | 1305 | setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); |
1328 | SDLK_s, | 1306 | if (!isInside) { |
1329 | KMOD_PRIMARY | KMOD_SHIFT, | 1307 | setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); |
1330 | "document.save open:1" }, | 1308 | setCStr_String(&d->sourceMime, mimeType_Gempub); |
1331 | { download_Icon " " saveToDownloads_Label, | 1309 | } |
1332 | SDLK_s, | 1310 | d->sourceGempub = gempub; |
1333 | KMOD_PRIMARY, | 1311 | } |
1334 | "document.save" } }, | 1312 | else { |
1335 | 2); | 1313 | delete_Gempub(gempub); |
1336 | } | 1314 | } |
1337 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | ||
1338 | redoLayout_GmDocument(d->doc); | ||
1339 | updateVisible_DocumentWidget_(d); | ||
1340 | invalidate_DocumentWidget_(d); | ||
1341 | } | 1315 | } |
1342 | } | 1316 | } |
1343 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1317 | if (d->sourceGempub) { |
1344 | makeFooterButtons_DocumentWidget_( | 1318 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { |
1345 | d, | 1319 | if (!isRemote_Gempub(d->sourceGempub)) { |
1346 | (iMenuItem[]){ { format_CStr(book_Icon " %s", | 1320 | iArray *items = collectNew_Array(sizeof(iMenuItem)); |
1347 | cstr_String(property_Gempub(d->sourceGempub, | ||
1348 | title_GempubProperty))), | ||
1349 | SDLK_LEFT, | ||
1350 | 0, | ||
1351 | format_CStr("!open url:%s", | ||
1352 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, | ||
1353 | 1); | ||
1354 | } | ||
1355 | else { | ||
1356 | /* Navigation buttons. */ | ||
1357 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
1358 | const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); | ||
1359 | if (navIndex != iInvalidPos) { | ||
1360 | if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { | ||
1361 | pushBack_Array( | 1321 | pushBack_Array( |
1362 | items, | 1322 | items, |
1363 | &(iMenuItem){ | 1323 | &(iMenuItem){ book_Icon " ${gempub.cover.view}", |
1364 | format_CStr(forwardArrow_Icon " %s", | 1324 | 0, |
1365 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), | 1325 | 0, |
1366 | SDLK_RIGHT, | 1326 | format_CStr("!open url:%s", |
1367 | 0, | 1327 | cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); |
1368 | format_CStr("!open url:%s", | 1328 | if (navSize_Gempub(d->sourceGempub) > 0) { |
1369 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); | 1329 | pushBack_Array( |
1330 | items, | ||
1331 | &(iMenuItem){ | ||
1332 | format_CStr(forwardArrow_Icon " %s", | ||
1333 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), | ||
1334 | SDLK_RIGHT, | ||
1335 | 0, | ||
1336 | format_CStr("!open url:%s", | ||
1337 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); | ||
1338 | } | ||
1339 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1370 | } | 1340 | } |
1371 | if (navIndex > 0) { | 1341 | else { |
1372 | pushBack_Array( | 1342 | makeFooterButtons_DocumentWidget_( |
1373 | items, | 1343 | d, |
1374 | &(iMenuItem){ | 1344 | (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", |
1375 | format_CStr(backArrow_Icon " %s", | 1345 | SDLK_s, |
1376 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), | 1346 | KMOD_PRIMARY | KMOD_SHIFT, |
1377 | SDLK_LEFT, | 1347 | "document.save open:1" }, |
1378 | 0, | 1348 | { download_Icon " " saveToDownloads_Label, |
1379 | format_CStr("!open url:%s", | 1349 | SDLK_s, |
1380 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); | 1350 | KMOD_PRIMARY, |
1351 | "document.save" } }, | ||
1352 | 2); | ||
1381 | } | 1353 | } |
1382 | else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1354 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { |
1383 | pushBack_Array( | 1355 | redoLayout_GmDocument(d->doc); |
1384 | items, | 1356 | updateVisible_DocumentWidget_(d); |
1385 | &(iMenuItem){ | 1357 | invalidate_DocumentWidget_(d); |
1386 | format_CStr(book_Icon " %s", | ||
1387 | cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), | ||
1388 | SDLK_LEFT, | ||
1389 | 0, | ||
1390 | format_CStr("!open url:%s", | ||
1391 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); | ||
1392 | } | 1358 | } |
1393 | } | 1359 | } |
1394 | if (!isEmpty_Array(items)) { | 1360 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { |
1395 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | 1361 | makeFooterButtons_DocumentWidget_( |
1362 | d, | ||
1363 | (iMenuItem[]){ { format_CStr(book_Icon " %s", | ||
1364 | cstr_String(property_Gempub(d->sourceGempub, | ||
1365 | title_GempubProperty))), | ||
1366 | SDLK_LEFT, | ||
1367 | 0, | ||
1368 | format_CStr("!open url:%s", | ||
1369 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, | ||
1370 | 1); | ||
1396 | } | 1371 | } |
1397 | } | 1372 | else { |
1398 | if (!isCached && prefs_App()->pinSplit && | 1373 | /* Navigation buttons. */ |
1399 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1374 | iArray *items = collectNew_Array(sizeof(iMenuItem)); |
1400 | const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); | 1375 | const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); |
1401 | if (navStart) { | 1376 | if (navIndex != iInvalidPos) { |
1402 | iWindow *win = get_Window(); | 1377 | if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { |
1403 | /* Auto-split to show index and the first navigation link. */ | 1378 | pushBack_Array( |
1404 | if (numRoots_Window(win) == 2) { | 1379 | items, |
1405 | /* This document is showing the index page. */ | 1380 | &(iMenuItem){ |
1406 | iRoot *other = otherRoot_Window(win, w->root); | 1381 | format_CStr(forwardArrow_Icon " %s", |
1407 | postCommandf_Root(other, "open url:%s", cstr_String(navStart)); | 1382 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), |
1408 | if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { | 1383 | SDLK_RIGHT, |
1409 | /* On the wrong side. */ | 1384 | 0, |
1410 | postCommand_App("ui.split swap:1"); | 1385 | format_CStr("!open url:%s", |
1386 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); | ||
1387 | } | ||
1388 | if (navIndex > 0) { | ||
1389 | pushBack_Array( | ||
1390 | items, | ||
1391 | &(iMenuItem){ | ||
1392 | format_CStr(backArrow_Icon " %s", | ||
1393 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), | ||
1394 | SDLK_LEFT, | ||
1395 | 0, | ||
1396 | format_CStr("!open url:%s", | ||
1397 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); | ||
1398 | } | ||
1399 | else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1400 | pushBack_Array( | ||
1401 | items, | ||
1402 | &(iMenuItem){ | ||
1403 | format_CStr(book_Icon " %s", | ||
1404 | cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), | ||
1405 | SDLK_LEFT, | ||
1406 | 0, | ||
1407 | format_CStr("!open url:%s", | ||
1408 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); | ||
1411 | } | 1409 | } |
1412 | } | 1410 | } |
1413 | else { | 1411 | if (!isEmpty_Array(items)) { |
1414 | postCommandf_App( | 1412 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); |
1415 | "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); | 1413 | } |
1414 | } | ||
1415 | if (!isCached && prefs_App()->pinSplit && | ||
1416 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1417 | const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); | ||
1418 | if (navStart) { | ||
1419 | iWindow *win = get_Window(); | ||
1420 | /* Auto-split to show index and the first navigation link. */ | ||
1421 | if (numRoots_Window(win) == 2) { | ||
1422 | /* This document is showing the index page. */ | ||
1423 | iRoot *other = otherRoot_Window(win, w->root); | ||
1424 | postCommandf_Root(other, "open url:%s", cstr_String(navStart)); | ||
1425 | if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { | ||
1426 | /* On the wrong side. */ | ||
1427 | postCommand_App("ui.split swap:1"); | ||
1428 | } | ||
1429 | } | ||
1430 | else { | ||
1431 | postCommandf_App( | ||
1432 | "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); | ||
1433 | } | ||
1416 | } | 1434 | } |
1417 | } | 1435 | } |
1418 | } | 1436 | } |
1419 | } | 1437 | } |
1438 | /* Local fontpacks are automatically shown. */ | ||
1439 | if (preloadLocalFontpackForPreview_Fonts(d->doc)) { | ||
1440 | documentRunsInvalidated_DocumentWidget_(d); | ||
1441 | redoLayout_GmDocument(d->doc); | ||
1442 | updateVisible_DocumentWidget_(d); | ||
1443 | invalidate_DocumentWidget_(d); | ||
1444 | } | ||
1420 | } | 1445 | } |
1421 | 1446 | ||
1422 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, | 1447 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, |
@@ -1484,7 +1509,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1484 | } | 1509 | } |
1485 | delete_String(localPath); | 1510 | delete_String(localPath); |
1486 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { | 1511 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { |
1487 | appendFormat_String(&str, "=> %s/ ${doc.archive.view}\n", | 1512 | appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n", |
1488 | cstr_String(withSpacesEncoded_String(d->mod.url))); | 1513 | cstr_String(withSpacesEncoded_String(d->mod.url))); |
1489 | } | 1514 | } |
1490 | translate_Lang(&str); | 1515 | translate_Lang(&str); |
@@ -2089,7 +2114,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, | |||
2089 | } | 2114 | } |
2090 | 2115 | ||
2091 | static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { | 2116 | static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { |
2092 | return findLinkDownload_Media(constMedia_GmDocument(d->doc), req->linkId) != 0; | 2117 | return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0; |
2093 | } | 2118 | } |
2094 | 2119 | ||
2095 | static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 2120 | static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
@@ -2174,7 +2199,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) { | |||
2174 | static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { | 2199 | static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { |
2175 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 2200 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
2176 | const iGmRun *run = i.ptr; | 2201 | const iGmRun *run = i.ptr; |
2177 | if (run->linkId && run->mediaType == none_GmRunMediaType && | 2202 | if (run->linkId && run->mediaType == none_MediaType && |
2178 | ~run->flags & decoration_GmRunFlag) { | 2203 | ~run->flags & decoration_GmRunFlag) { |
2179 | const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); | 2204 | const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); |
2180 | if (isMediaLink_GmDocument(d->doc, run->linkId) && | 2205 | if (isMediaLink_GmDocument(d->doc, run->linkId) && |
@@ -2763,8 +2788,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2763 | else if (equalWidget_Command(cmd, w, "document.downloadlink")) { | 2788 | else if (equalWidget_Command(cmd, w, "document.downloadlink")) { |
2764 | if (d->contextLink) { | 2789 | if (d->contextLink) { |
2765 | const iGmLinkId linkId = d->contextLink->linkId; | 2790 | const iGmLinkId linkId = d->contextLink->linkId; |
2766 | setDownloadUrl_Media( | 2791 | setUrl_Media(media_GmDocument(d->doc), |
2767 | media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId)); | 2792 | linkId, |
2793 | download_MediaType, | ||
2794 | linkUrl_GmDocument(d->doc, linkId)); | ||
2768 | requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); | 2795 | requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); |
2769 | redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ | 2796 | redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ |
2770 | updateVisible_DocumentWidget_(d); | 2797 | updateVisible_DocumentWidget_(d); |
@@ -2874,7 +2901,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2874 | const iMedia * media = media_GmDocument(d->doc); | 2901 | const iMedia * media = media_GmDocument(d->doc); |
2875 | const size_t num = numAudio_Media(media); | 2902 | const size_t num = numAudio_Media(media); |
2876 | for (size_t id = 1; id <= num; id++) { | 2903 | for (size_t id = 1; id <= num; id++) { |
2877 | iPlayer *plr = audioPlayer_Media(media, id); | 2904 | iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id }); |
2878 | if (plr != startedPlr) { | 2905 | if (plr != startedPlr) { |
2879 | setPaused_Player(plr, iTrue); | 2906 | setPaused_Player(plr, iTrue); |
2880 | } | 2907 | } |
@@ -3212,8 +3239,8 @@ static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run | |||
3212 | } | 3239 | } |
3213 | 3240 | ||
3214 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { | 3241 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { |
3215 | if (run && run->mediaType == audio_GmRunMediaType) { | 3242 | if (run && run->mediaType == audio_MediaType) { |
3216 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); | 3243 | iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); |
3217 | setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); | 3244 | setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); |
3218 | d->grabbedStartVolume = volume_Player(plr); | 3245 | d->grabbedStartVolume = volume_Player(plr); |
3219 | d->grabbedPlayer = run; | 3246 | d->grabbedPlayer = run; |
@@ -3221,7 +3248,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r | |||
3221 | } | 3248 | } |
3222 | else if (d->grabbedPlayer) { | 3249 | else if (d->grabbedPlayer) { |
3223 | setFlags_Player( | 3250 | setFlags_Player( |
3224 | audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId), | 3251 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)), |
3225 | volumeGrabbed_PlayerFlag, | 3252 | volumeGrabbed_PlayerFlag, |
3226 | iFalse); | 3253 | iFalse); |
3227 | d->grabbedPlayer = NULL; | 3254 | d->grabbedPlayer = NULL; |
@@ -3249,11 +3276,21 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev | |||
3249 | const iInt2 mouse = init_I2(ev->button.x, ev->button.y); | 3276 | const iInt2 mouse = init_I2(ev->button.x, ev->button.y); |
3250 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 3277 | iConstForEach(PtrArray, i, &d->visibleMedia) { |
3251 | const iGmRun *run = i.ptr; | 3278 | const iGmRun *run = i.ptr; |
3252 | if (run->mediaType != audio_GmRunMediaType) { | 3279 | if (run->mediaType == fontpack_MediaType) { |
3280 | iFontpackUI ui; | ||
3281 | init_FontpackUI(&ui, media_GmDocument(d->doc), run->mediaId, | ||
3282 | runRect_DocumentWidget_(d, run)); | ||
3283 | if (processEvent_FontpackUI(&ui, ev)) { | ||
3284 | refresh_Widget(d); | ||
3285 | return iTrue; | ||
3286 | } | ||
3287 | } | ||
3288 | if (run->mediaType != audio_MediaType) { | ||
3253 | continue; | 3289 | continue; |
3254 | } | 3290 | } |
3291 | /* TODO: move this to mediaui.c */ | ||
3255 | const iRect rect = runRect_DocumentWidget_(d, run); | 3292 | const iRect rect = runRect_DocumentWidget_(d, run); |
3256 | iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); | 3293 | iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); |
3257 | if (contains_Rect(rect, mouse)) { | 3294 | if (contains_Rect(rect, mouse)) { |
3258 | iPlayerUI ui; | 3295 | iPlayerUI ui; |
3259 | init_PlayerUI(&ui, plr, rect); | 3296 | init_PlayerUI(&ui, plr, rect); |
@@ -3633,7 +3670,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3633 | cstr_String(linkUrl)) }, | 3670 | cstr_String(linkUrl)) }, |
3634 | }, | 3671 | }, |
3635 | 3); | 3672 | 3); |
3636 | if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { | 3673 | if (isNative && d->contextLink->mediaType != download_MediaType) { |
3637 | pushBackN_Array(&items, (iMenuItem[]){ | 3674 | pushBackN_Array(&items, (iMenuItem[]){ |
3638 | { "---" }, | 3675 | { "---" }, |
3639 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, | 3676 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, |
@@ -3641,7 +3678,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3641 | } | 3678 | } |
3642 | iMediaRequest *mediaReq; | 3679 | iMediaRequest *mediaReq; |
3643 | if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && | 3680 | if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && |
3644 | d->contextLink->mediaType != download_GmRunMediaType) { | 3681 | d->contextLink->mediaType != download_MediaType) { |
3645 | if (isFinished_GmRequest(mediaReq->req)) { | 3682 | if (isFinished_GmRequest(mediaReq->req)) { |
3646 | pushBack_Array(&items, | 3683 | pushBack_Array(&items, |
3647 | &(iMenuItem){ download_Icon " " saveToDownloads_Label, | 3684 | &(iMenuItem){ download_Icon " " saveToDownloads_Label, |
@@ -3763,7 +3800,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3763 | case drag_ClickResult: { | 3800 | case drag_ClickResult: { |
3764 | if (d->grabbedPlayer) { | 3801 | if (d->grabbedPlayer) { |
3765 | iPlayer *plr = | 3802 | iPlayer *plr = |
3766 | audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId); | 3803 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)); |
3767 | iPlayerUI ui; | 3804 | iPlayerUI ui; |
3768 | init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); | 3805 | init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); |
3769 | float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); | 3806 | float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); |
@@ -3883,8 +3920,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3883 | } | 3920 | } |
3884 | if (d->hoverLink) { | 3921 | if (d->hoverLink) { |
3885 | /* TODO: Move this to a method. */ | 3922 | /* TODO: Move this to a method. */ |
3886 | const iGmLinkId linkId = d->hoverLink->linkId; | 3923 | const iGmLinkId linkId = d->hoverLink->linkId; |
3887 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | 3924 | const iMediaId linkMedia = mediaId_GmRun(d->hoverLink); |
3925 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | ||
3888 | iAssert(linkId); | 3926 | iAssert(linkId); |
3889 | /* Media links are opened inline by default. */ | 3927 | /* Media links are opened inline by default. */ |
3890 | if (isMediaLink_GmDocument(d->doc, linkId)) { | 3928 | if (isMediaLink_GmDocument(d->doc, linkId)) { |
@@ -3937,6 +3975,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3937 | } | 3975 | } |
3938 | refresh_Widget(w); | 3976 | refresh_Widget(w); |
3939 | } | 3977 | } |
3978 | else if (linkMedia.type == download_MediaType || | ||
3979 | findMediaRequest_DocumentWidget_(d, linkId)) { | ||
3980 | /* TODO: What should be done when clicking on an inline download? | ||
3981 | Maybe dismiss if finished? */ | ||
3982 | return iTrue; | ||
3983 | } | ||
3940 | else if (linkFlags & supportedScheme_GmLinkFlag) { | 3984 | else if (linkFlags & supportedScheme_GmLinkFlag) { |
3941 | int tabMode = openTabMode_Sym(modState_Keys()); | 3985 | int tabMode = openTabMode_Sym(modState_Keys()); |
3942 | if (isPinned_DocumentWidget_(d)) { | 3986 | if (isPinned_DocumentWidget_(d)) { |
@@ -4071,7 +4115,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol | |||
4071 | 4115 | ||
4072 | static void drawMark_DrawContext_(void *context, const iGmRun *run) { | 4116 | static void drawMark_DrawContext_(void *context, const iGmRun *run) { |
4073 | iDrawContext *d = context; | 4117 | iDrawContext *d = context; |
4074 | if (run->mediaType == none_GmRunMediaType) { | 4118 | if (run->mediaType == none_MediaType) { |
4075 | fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); | 4119 | fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); |
4076 | fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); | 4120 | fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); |
4077 | } | 4121 | } |
@@ -4178,8 +4222,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4178 | d->runsDrawn.end = run; | 4222 | d->runsDrawn.end = run; |
4179 | } | 4223 | } |
4180 | } | 4224 | } |
4181 | if (run->mediaType == image_GmRunMediaType) { | 4225 | if (run->mediaType == image_MediaType) { |
4182 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); | 4226 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run)); |
4183 | const iRect dst = moved_Rect(run->visBounds, origin); | 4227 | const iRect dst = moved_Rect(run->visBounds, origin); |
4184 | if (tex) { | 4228 | if (tex) { |
4185 | fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ | 4229 | fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ |
@@ -4334,31 +4378,31 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4334 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); | 4378 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); |
4335 | iString text; | 4379 | iString text; |
4336 | init_String(&text); | 4380 | init_String(&text); |
4337 | iMediaId imageId = linkImage_GmDocument(doc, run->linkId); | 4381 | const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc), |
4338 | iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0; | 4382 | run->linkId, none_MediaType); |
4339 | iMediaId downloadId = !imageId && !audioId ? | 4383 | iAssert(linkMedia.type != none_MediaType); |
4340 | findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0; | 4384 | iGmMediaInfo info; |
4341 | iAssert(imageId || audioId || downloadId); | 4385 | info_Media(constMedia_GmDocument(doc), linkMedia, &info); |
4342 | if (imageId) { | 4386 | switch (linkMedia.type) { |
4343 | iAssert(!isEmpty_Rect(run->bounds)); | 4387 | case image_MediaType: { |
4344 | iGmMediaInfo info; | 4388 | iAssert(!isEmpty_Rect(run->bounds)); |
4345 | imageInfo_Media(constMedia_GmDocument(doc), imageId, &info); | 4389 | const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia); |
4346 | const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId); | 4390 | format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", |
4347 | format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", | 4391 | info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, |
4348 | info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, | 4392 | cstr_Lang("mb")); |
4349 | cstr_Lang("mb")); | 4393 | break; |
4350 | } | 4394 | } |
4351 | else if (audioId) { | 4395 | case audio_MediaType: |
4352 | iGmMediaInfo info; | 4396 | format_String(&text, "%s", info.type); |
4353 | audioInfo_Media(constMedia_GmDocument(doc), audioId, &info); | 4397 | break; |
4354 | format_String(&text, "%s", info.type); | 4398 | case download_MediaType: |
4355 | } | 4399 | format_String(&text, "%s", info.type); |
4356 | else if (downloadId) { | 4400 | break; |
4357 | iGmMediaInfo info; | 4401 | default: |
4358 | downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info); | 4402 | break; |
4359 | format_String(&text, "%s", info.type); | ||
4360 | } | 4403 | } |
4361 | if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { | 4404 | if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */ |
4405 | findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { | ||
4362 | appendFormat_String( | 4406 | appendFormat_String( |
4363 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); | 4407 | &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); |
4364 | } | 4408 | } |
@@ -4589,18 +4633,25 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | |||
4589 | static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { | 4633 | static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { |
4590 | iConstForEach(PtrArray, i, &d->visibleMedia) { | 4634 | iConstForEach(PtrArray, i, &d->visibleMedia) { |
4591 | const iGmRun * run = i.ptr; | 4635 | const iGmRun * run = i.ptr; |
4592 | if (run->mediaType == audio_GmRunMediaType) { | 4636 | if (run->mediaType == audio_MediaType) { |
4593 | iPlayerUI ui; | 4637 | iPlayerUI ui; |
4594 | init_PlayerUI(&ui, | 4638 | init_PlayerUI(&ui, |
4595 | audioPlayer_Media(media_GmDocument(d->doc), run->mediaId), | 4639 | audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)), |
4596 | runRect_DocumentWidget_(d, run)); | 4640 | runRect_DocumentWidget_(d, run)); |
4597 | draw_PlayerUI(&ui, p); | 4641 | draw_PlayerUI(&ui, p); |
4598 | } | 4642 | } |
4599 | else if (run->mediaType == download_GmRunMediaType) { | 4643 | else if (run->mediaType == download_MediaType) { |
4600 | iDownloadUI ui; | 4644 | iDownloadUI ui; |
4601 | init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run)); | 4645 | init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, |
4646 | runRect_DocumentWidget_(d, run)); | ||
4602 | draw_DownloadUI(&ui, p); | 4647 | draw_DownloadUI(&ui, p); |
4603 | } | 4648 | } |
4649 | else if (run->mediaType == fontpack_MediaType) { | ||
4650 | iFontpackUI ui; | ||
4651 | init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, | ||
4652 | runRect_DocumentWidget_(d, run)); | ||
4653 | draw_FontpackUI(&ui, p); | ||
4654 | } | ||
4604 | } | 4655 | } |
4605 | } | 4656 | } |
4606 | 4657 | ||
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index cc09c72d..1f2ecfc0 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h | |||
@@ -32,6 +32,8 @@ iDeclareType(History) | |||
32 | iDeclareWidgetClass(DocumentWidget) | 32 | iDeclareWidgetClass(DocumentWidget) |
33 | iDeclareObjectConstruction(DocumentWidget) | 33 | iDeclareObjectConstruction(DocumentWidget) |
34 | 34 | ||
35 | void cancelAllRequests_DocumentWidget(iDocumentWidget *); | ||
36 | |||
35 | void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs); | 37 | void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs); |
36 | void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins); | 38 | void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins); |
37 | 39 | ||
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index 22552027..aa45d73a 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c | |||
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "lang.h" | 30 | #include "lang.h" |
31 | 31 | ||
32 | #include <the_Foundation/path.h> | 32 | #include <the_Foundation/path.h> |
33 | #include <the_Foundation/stringlist.h> | ||
33 | 34 | ||
34 | static const char *volumeChar_(float volume) { | 35 | static const char *volumeChar_(float volume) { |
35 | if (volume <= 0) { | 36 | if (volume <= 0) { |
@@ -61,7 +62,7 @@ void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) { | |||
61 | } | 62 | } |
62 | } | 63 | } |
63 | 64 | ||
64 | static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font) { | 65 | static void drawInlineButton_(iPaint *p, iRect rect, const char *label, int font) { |
65 | const iInt2 mouse = mouseCoord_Window(get_Window(), 0); | 66 | const iInt2 mouse = mouseCoord_Window(get_Window(), 0); |
66 | const iBool isHover = contains_Rect(rect, mouse); | 67 | const iBool isHover = contains_Rect(rect, mouse); |
67 | const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; | 68 | const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; |
@@ -113,14 +114,14 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
113 | const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; | 114 | const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; |
114 | fillRect_Paint(p, d->bounds, playerBackground_ColorId); | 115 | fillRect_Paint(p, d->bounds, playerBackground_ColorId); |
115 | drawRect_Paint(p, d->bounds, playerFrame_ColorId); | 116 | drawRect_Paint(p, d->bounds, playerFrame_ColorId); |
116 | drawPlayerButton_(p, | 117 | drawInlineButton_(p, |
117 | d->playPauseRect, | 118 | d->playPauseRect, |
118 | isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", | 119 | isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", |
119 | uiContent_FontId); | 120 | uiContent_FontId); |
120 | drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); | 121 | drawInlineButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); |
121 | drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId); | 122 | drawInlineButton_(p, d->menuRect, menu_Icon, uiContent_FontId); |
122 | if (!isAdjusting) { | 123 | if (!isAdjusting) { |
123 | drawPlayerButton_( | 124 | drawInlineButton_( |
124 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); | 125 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); |
125 | } | 126 | } |
126 | const int hgt = lineHeight_Text(uiLabelBig_FontId); | 127 | const int hgt = lineHeight_Text(uiLabelBig_FontId); |
@@ -228,24 +229,26 @@ static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) { | |||
228 | deinit_String(&digits); | 229 | deinit_String(&digits); |
229 | } | 230 | } |
230 | 231 | ||
231 | void init_DownloadUI(iDownloadUI *d, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds) { | 232 | void init_DownloadUI(iDownloadUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) { |
232 | d->doc = doc; | 233 | d->media = media; |
233 | d->mediaId = mediaId; | 234 | d->mediaId = mediaId; |
234 | d->bounds = bounds; | 235 | d->bounds = bounds; |
235 | } | 236 | } |
236 | 237 | ||
238 | /*----------------------------------------------------------------------------------------------*/ | ||
239 | |||
237 | iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { | 240 | iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { |
238 | return iFalse; | 241 | return iFalse; |
239 | } | 242 | } |
240 | 243 | ||
241 | void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { | 244 | void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { |
242 | const iMedia *media = constMedia_GmDocument(document_DocumentWidget(d->doc)); | ||
243 | iGmMediaInfo info; | 245 | iGmMediaInfo info; |
244 | float bytesPerSecond; | 246 | float bytesPerSecond; |
245 | const iString *path; | 247 | const iString *path; |
246 | iBool isFinished; | 248 | iBool isFinished; |
247 | downloadInfo_Media(media, d->mediaId, &info); | 249 | downloadInfo_Media(d->media, d->mediaId, &info); |
248 | downloadStats_Media(media, d->mediaId, &path, &bytesPerSecond, &isFinished); | 250 | downloadStats_Media(d->media, (iMediaId){ download_MediaType, d->mediaId }, |
251 | &path, &bytesPerSecond, &isFinished); | ||
249 | fillRect_Paint(p, d->bounds, uiBackground_ColorId); | 252 | fillRect_Paint(p, d->bounds, uiBackground_ColorId); |
250 | drawRect_Paint(p, d->bounds, uiSeparator_ColorId); | 253 | drawRect_Paint(p, d->bounds, uiSeparator_ColorId); |
251 | iRect rect = d->bounds; | 254 | iRect rect = d->bounds; |
@@ -275,3 +278,68 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { | |||
275 | translateCStr_Lang("\u2014 ${mb.per.sec}")); | 278 | translateCStr_Lang("\u2014 ${mb.per.sec}")); |
276 | } | 279 | } |
277 | } | 280 | } |
281 | |||
282 | /*----------------------------------------------------------------------------------------------*/ | ||
283 | |||
284 | void init_FontpackUI(iFontpackUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) { | ||
285 | d->media = media; | ||
286 | d->mediaId = mediaId; | ||
287 | d->bounds = bounds; | ||
288 | d->installRect.size = add_I2(measure_Text(uiLabel_FontId, "${media.fontpack.install}").bounds.size, | ||
289 | muli_I2(gap2_UI, 3)); | ||
290 | d->installRect.pos.x = right_Rect(d->bounds) - gap_UI - d->installRect.size.x; | ||
291 | d->installRect.pos.y = mid_Rect(d->bounds).y - d->installRect.size.y / 2; | ||
292 | } | ||
293 | |||
294 | iBool processEvent_FontpackUI(iFontpackUI *d, const SDL_Event *ev) { | ||
295 | switch (ev->type) { | ||
296 | case SDL_MOUSEBUTTONDOWN: | ||
297 | case SDL_MOUSEBUTTONUP: { | ||
298 | const iInt2 pos = init_I2(ev->button.x, ev->button.y); | ||
299 | if (contains_Rect(d->installRect, pos)) { | ||
300 | return iTrue; | ||
301 | } | ||
302 | break; | ||
303 | } | ||
304 | case SDL_MOUSEMOTION: | ||
305 | if (contains_Rect(d->bounds, init_I2(ev->motion.x, ev->motion.y))) { | ||
306 | return iTrue; | ||
307 | } | ||
308 | break; | ||
309 | } | ||
310 | return iFalse; | ||
311 | } | ||
312 | |||
313 | int height_FontpackUI(const iMedia *media, uint16_t mediaId, int width) { | ||
314 | const iStringList *names; | ||
315 | iFontpackMediaInfo info; | ||
316 | fontpackInfo_Media(media, (iMediaId){ fontpack_MediaType, mediaId }, &info); | ||
317 | return lineHeight_Text(uiContent_FontId) + | ||
318 | lineHeight_Text(uiLabel_FontId) * (1 + size_StringList(info.names)); | ||
319 | } | ||
320 | |||
321 | void draw_FontpackUI(const iFontpackUI *d, iPaint *p) { | ||
322 | /* Draw a background box. */ | ||
323 | fillRect_Paint(p, d->bounds, uiBackground_ColorId); | ||
324 | drawRect_Paint(p, d->bounds, uiSeparator_ColorId); | ||
325 | iFontpackMediaInfo info; | ||
326 | fontpackInfo_Media(d->media, (iMediaId){ fontpack_MediaType, d->mediaId }, &info); | ||
327 | iInt2 pos = topLeft_Rect(d->bounds); | ||
328 | const char *checks[] = { "\u2610", "\u2611" }; | ||
329 | draw_Text(uiContentBold_FontId, pos, uiHeading_ColorId, "\"%s\" v%d", | ||
330 | cstr_String(info.packId.id), info.packId.version); | ||
331 | pos.y += lineHeight_Text(uiContentBold_FontId); | ||
332 | draw_Text(uiLabelBold_FontId, pos, uiText_ColorId, "%.1f MB, %d fonts %s %s %s", | ||
333 | info.sizeInBytes / 1.0e6, size_StringList(info.names), | ||
334 | // checks[info.isValid], info.isValid ? "No errors" : "Errors detected", | ||
335 | checks[info.isInstalled], info.isInstalled ? "Installed" : "Not installed", | ||
336 | info.isReadOnly ? "Read-Only" : ""); | ||
337 | pos.y += lineHeight_Text(uiLabelBold_FontId); | ||
338 | iConstForEach(StringList, i, info.names) { | ||
339 | drawRange_Text(uiLabel_FontId, pos, uiText_ColorId, range_String(i.value)); | ||
340 | pos.y += lineHeight_Text(uiLabel_FontId); | ||
341 | } | ||
342 | /* Buttons. */ | ||
343 | drawInlineButton_(p, d->installRect, | ||
344 | "${media.fontpack.install}", uiLabel_FontId); | ||
345 | } | ||
diff --git a/src/ui/mediaui.h b/src/ui/mediaui.h index e79dedc0..73de1994 100644 --- a/src/ui/mediaui.h +++ b/src/ui/mediaui.h | |||
@@ -51,11 +51,27 @@ iDeclareType(Media) | |||
51 | iDeclareType(DownloadUI) | 51 | iDeclareType(DownloadUI) |
52 | 52 | ||
53 | struct Impl_DownloadUI { | 53 | struct Impl_DownloadUI { |
54 | const iDocumentWidget *doc; | 54 | const iMedia *media; |
55 | uint16_t mediaId; | 55 | uint16_t mediaId; |
56 | iRect bounds; | 56 | iRect bounds; |
57 | }; | 57 | }; |
58 | 58 | ||
59 | void init_DownloadUI (iDownloadUI *, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds); | 59 | void init_DownloadUI (iDownloadUI *, const iMedia *media, uint16_t mediaId, iRect bounds); |
60 | iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); | 60 | iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); |
61 | void draw_DownloadUI (const iDownloadUI *, iPaint *p); | 61 | void draw_DownloadUI (const iDownloadUI *, iPaint *p); |
62 | |||
63 | /*----------------------------------------------------------------------------------------------*/ | ||
64 | |||
65 | iDeclareType(FontpackUI) | ||
66 | |||
67 | struct Impl_FontpackUI { | ||
68 | const iMedia *media; | ||
69 | uint16_t mediaId; | ||
70 | iRect bounds; | ||
71 | iRect installRect; | ||
72 | }; | ||
73 | |||
74 | void init_FontpackUI (iFontpackUI *, const iMedia *media, uint16_t mediaId, iRect bounds); | ||
75 | int height_FontpackUI (const iMedia *media, uint16_t mediaId, int width); | ||
76 | iBool processEvent_FontpackUI (iFontpackUI *, const SDL_Event *ev); | ||
77 | void draw_FontpackUI (const iFontpackUI *, iPaint *p); | ||