diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-11 12:22:54 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-10-11 12:22:54 +0300 |
commit | 960df03c17091aca37f53eaab8fc27c669d26a5e (patch) | |
tree | 18721567f227928c528430daf9d06248a2443598 | |
parent | bf9158b29df506698e267d4583459ee4ebf20685 (diff) |
Media refactoring; working on FontPack management
Media still needs more work to get rid of redundancies and make lookups faster.
FontPacks are manipulated as Media items (not unlike images) so they can be previewed on page, and installed via a click.
FontPack management is not trivial as it includes such details as versioning and whether individual packs are enabled or disabled.
-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); | ||