diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-02 09:07:15 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-02 09:07:15 +0300 |
commit | a14895149fad1724e0f4b4df1fd5834bddc4ead4 (patch) | |
tree | 15795172fe0f47c6ed06a707a520d506d768e564 /src/ui | |
parent | eae0ef64d071e2702fffc1d00f223124f8c6d8b4 (diff) |
Added a Gempub helper
`Gempub` opens and parses a Gempub archive and provides access to the contents in a common way.
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 145 |
1 files changed, 87 insertions, 58 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 410b793e..f146a2df 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "bookmarks.h" | 30 | #include "bookmarks.h" |
31 | #include "command.h" | 31 | #include "command.h" |
32 | #include "defs.h" | 32 | #include "defs.h" |
33 | #include "gempub.h" | ||
33 | #include "gmcerts.h" | 34 | #include "gmcerts.h" |
34 | #include "gmdocument.h" | 35 | #include "gmdocument.h" |
35 | #include "gmrequest.h" | 36 | #include "gmrequest.h" |
@@ -1051,7 +1052,7 @@ static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { | |||
1051 | 1052 | ||
1052 | static const char *zipPageHeading_(const iRangecc mime) { | 1053 | static const char *zipPageHeading_(const iRangecc mime) { |
1053 | if (equalCase_Rangecc(mime, "application/gpub+zip")) { | 1054 | if (equalCase_Rangecc(mime, "application/gpub+zip")) { |
1054 | return book_Icon " Gempub Book"; | 1055 | return book_Icon " Gempub"; |
1055 | } | 1056 | } |
1056 | iRangecc type = iNullRange; | 1057 | iRangecc type = iNullRange; |
1057 | nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ | 1058 | nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ |
@@ -1065,6 +1066,56 @@ static const char *zipPageHeading_(const iRangecc mime) { | |||
1065 | return cstrCollect_String(heading); | 1066 | return cstrCollect_String(heading); |
1066 | } | 1067 | } |
1067 | 1068 | ||
1069 | static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d) { | ||
1070 | if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || | ||
1071 | !cmpCase_String(&d->sourceMime, mimeType_Gempub) || | ||
1072 | endsWithCase_String(d->mod.url, ".gpub")) { | ||
1073 | iGempub *gempub = new_Gempub(); | ||
1074 | if (open_Gempub(gempub, &d->sourceContent)) { | ||
1075 | setBaseUrl_Gempub(gempub, d->mod.url); | ||
1076 | /* TODO: just return a String from coverPageSource_Gempub... */ | ||
1077 | setSource_DocumentWidget(d, collect_String(newBlock_String(collect_Block(coverPageSource_Gempub(gempub))))); | ||
1078 | setCStr_String(&d->sourceMime, mimeType_Gempub); | ||
1079 | } | ||
1080 | delete_Gempub(gempub); | ||
1081 | } | ||
1082 | /* Gempub: Preload cover image. */ { | ||
1083 | /* TODO: move to gempub.c along with other related code */ | ||
1084 | iString *localPath = localFilePathFromUrl_String(d->mod.url); | ||
1085 | if (localPath) { | ||
1086 | if (!iCmpStr(mediaType_Path(localPath), "application/gpub+zip")) { | ||
1087 | iArchive *arch = iClob(new_Archive()); | ||
1088 | if (openFile_Archive(arch, localPath)) { | ||
1089 | iBool haveImage = iFalse; | ||
1090 | for (size_t linkId = 1; ; linkId++) { | ||
1091 | const iString *linkUrl = linkUrl_GmDocument(d->doc, linkId); | ||
1092 | if (!linkUrl) break; | ||
1093 | if (findLinkImage_Media(media_GmDocument(d->doc), linkId)) { | ||
1094 | continue; /* got this already */ | ||
1095 | } | ||
1096 | if (linkFlags_GmDocument(d->doc, linkId) & imageFileExtension_GmLinkFlag) { | ||
1097 | iString *imgEntryPath = collect_String(localFilePathFromUrl_String(linkUrl)); | ||
1098 | remove_Block(&imgEntryPath->chars, 0, size_String(localPath) + 1 /* slash, too */); | ||
1099 | setData_Media(media_GmDocument(d->doc), | ||
1100 | linkId, | ||
1101 | collectNewCStr_String(mediaType_Path(linkUrl)), | ||
1102 | data_Archive(arch, imgEntryPath), | ||
1103 | 0); | ||
1104 | haveImage = iTrue; | ||
1105 | } | ||
1106 | } | ||
1107 | if (haveImage) { | ||
1108 | redoLayout_GmDocument(d->doc); | ||
1109 | updateVisible_DocumentWidget_(d); | ||
1110 | invalidate_DocumentWidget_(d); | ||
1111 | } | ||
1112 | } | ||
1113 | } | ||
1114 | delete_String(localPath); | ||
1115 | } | ||
1116 | } | ||
1117 | } | ||
1118 | |||
1068 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, | 1119 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, |
1069 | const iBool isInitialUpdate) { | 1120 | const iBool isInitialUpdate) { |
1070 | if (d->state == ready_RequestState) { | 1121 | if (d->state == ready_RequestState) { |
@@ -1113,12 +1164,17 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1113 | iString *key = collectNew_String(); | 1164 | iString *key = collectNew_String(); |
1114 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1165 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
1115 | format_String(&str, "# %s\n" | 1166 | format_String(&str, "# %s\n" |
1116 | "%s is a compressed archive.\n\n%s\n\n", | 1167 | "%s is a compressed archive.\n\n", |
1117 | zipPageHeading_(param), | 1168 | zipPageHeading_(param), |
1118 | cstr_Rangecc(baseName_Path(d->mod.url)), | 1169 | cstr_Rangecc(baseName_Path(d->mod.url))); |
1119 | format_CStr(cstr_Lang("error.unsupported.suggestsave"), | 1170 | iString *localPath = localFilePathFromUrl_String(d->mod.url); |
1120 | cstr_String(key), | 1171 | if (!localPath) { |
1121 | saveToDownloads_Label)); | 1172 | appendFormat_String(&str, "%s\n\n", |
1173 | format_CStr(cstr_Lang("error.unsupported.suggestsave"), | ||
1174 | cstr_String(key), | ||
1175 | saveToDownloads_Label)); | ||
1176 | } | ||
1177 | delete_String(localPath); | ||
1122 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { | 1178 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { |
1123 | appendFormat_String(&str, "=> %s/ View archive contents\n", | 1179 | appendFormat_String(&str, "=> %s/ View archive contents\n", |
1124 | cstr_String(withSpacesEncoded_String(d->mod.url))); | 1180 | cstr_String(withSpacesEncoded_String(d->mod.url))); |
@@ -1188,43 +1244,6 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1188 | if (setSource) { | 1244 | if (setSource) { |
1189 | setSource_DocumentWidget(d, &str); | 1245 | setSource_DocumentWidget(d, &str); |
1190 | } | 1246 | } |
1191 | if (isRequestFinished) { | ||
1192 | /* Gempub: Preload cover image. */ { | ||
1193 | /* TODO: move to a gempub.c along with other related code */ | ||
1194 | iString *localPath = localFilePathFromUrl_String(d->mod.url); | ||
1195 | if (localPath) { | ||
1196 | if (!iCmpStr(mediaTypeFromPath_String(localPath), "application/gpub+zip")) { | ||
1197 | iArchive *arch = iClob(new_Archive()); | ||
1198 | if (openFile_Archive(arch, localPath)) { | ||
1199 | iBool haveImage = iFalse; | ||
1200 | for (size_t linkId = 1; ; linkId++) { | ||
1201 | const iString *linkUrl = linkUrl_GmDocument(d->doc, linkId); | ||
1202 | if (!linkUrl) break; | ||
1203 | if (findLinkImage_Media(media_GmDocument(d->doc), linkId)) { | ||
1204 | continue; /* got this already */ | ||
1205 | } | ||
1206 | if (linkFlags_GmDocument(d->doc, linkId) & imageFileExtension_GmLinkFlag) { | ||
1207 | iString *imgEntryPath = collect_String(localFilePathFromUrl_String(linkUrl)); | ||
1208 | remove_Block(&imgEntryPath->chars, 0, size_String(localPath) + 1 /* slash, too */); | ||
1209 | setData_Media(media_GmDocument(d->doc), | ||
1210 | linkId, | ||
1211 | collectNewCStr_String(mediaTypeFromPath_String(linkUrl)), | ||
1212 | data_Archive(arch, imgEntryPath), | ||
1213 | 0); | ||
1214 | haveImage = iTrue; | ||
1215 | } | ||
1216 | } | ||
1217 | if (haveImage) { | ||
1218 | redoLayout_GmDocument(d->doc); | ||
1219 | updateVisible_DocumentWidget_(d); | ||
1220 | invalidate_DocumentWidget_(d); | ||
1221 | } | ||
1222 | } | ||
1223 | } | ||
1224 | delete_String(localPath); | ||
1225 | } | ||
1226 | } | ||
1227 | } | ||
1228 | deinit_String(&str); | 1247 | deinit_String(&str); |
1229 | } | 1248 | } |
1230 | } | 1249 | } |
@@ -1291,8 +1310,13 @@ static void cacheRunGlyphs_(void *data, const iGmRun *run) { | |||
1291 | } | 1310 | } |
1292 | 1311 | ||
1293 | static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | 1312 | static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { |
1294 | if (isExposed_Window(get_Window())) { | 1313 | if (isFinishedLaunching_App() && isExposed_Window(get_Window())) { |
1295 | render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, cacheRunGlyphs_, NULL); | 1314 | /* Just cache the top of the document, since this is what we usually need. */ |
1315 | int maxY = height_Widget(&d->widget) * 2; | ||
1316 | if (maxY == 0) { | ||
1317 | maxY = size_GmDocument(d->doc).y; | ||
1318 | } | ||
1319 | render_GmDocument(d->doc, (iRangei){ 0, maxY }, cacheRunGlyphs_, NULL); | ||
1296 | } | 1320 | } |
1297 | } | 1321 | } |
1298 | 1322 | ||
@@ -1302,26 +1326,28 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
1302 | const iGmResponse *resp = recent->cachedResponse; | 1326 | const iGmResponse *resp = recent->cachedResponse; |
1303 | clear_ObjectList(d->media); | 1327 | clear_ObjectList(d->media); |
1304 | reset_GmDocument(d->doc); | 1328 | reset_GmDocument(d->doc); |
1305 | d->state = fetching_RequestState; | ||
1306 | d->initNormScrollY = recent->normScrollY; | ||
1307 | resetWideRuns_DocumentWidget_(d); | 1329 | resetWideRuns_DocumentWidget_(d); |
1308 | /* Use the cached response data. */ | 1330 | d->state = fetching_RequestState; |
1309 | updateTrust_DocumentWidget_(d, resp); | 1331 | /* Do the fetch. */ { |
1310 | d->sourceTime = resp->when; | 1332 | d->initNormScrollY = recent->normScrollY; |
1311 | d->sourceStatus = success_GmStatusCode; | 1333 | /* Use the cached response data. */ |
1312 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | 1334 | updateTrust_DocumentWidget_(d, resp); |
1313 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; | 1335 | d->sourceTime = resp->when; |
1314 | set_Block(&d->sourceContent, &resp->body); | 1336 | d->sourceStatus = success_GmStatusCode; |
1315 | updateDocument_DocumentWidget_(d, resp, iTrue); | 1337 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); |
1316 | init_Anim(&d->altTextOpacity, 0); | 1338 | set_Block(&d->sourceContent, &resp->body); |
1339 | updateDocument_DocumentWidget_(d, resp, iTrue); | ||
1340 | postProcessRequestContent_DocumentWidget_(d); | ||
1341 | } | ||
1317 | d->state = ready_RequestState; | 1342 | d->state = ready_RequestState; |
1343 | init_Anim(&d->altTextOpacity, 0); | ||
1318 | reset_SmoothScroll(&d->scrollY); | 1344 | reset_SmoothScroll(&d->scrollY); |
1319 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | 1345 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); |
1320 | updateSideOpacity_DocumentWidget_(d, iFalse); | 1346 | updateSideOpacity_DocumentWidget_(d, iFalse); |
1321 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | ||
1322 | updateVisible_DocumentWidget_(d); | 1347 | updateVisible_DocumentWidget_(d); |
1323 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | 1348 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ |
1324 | cacheDocumentGlyphs_DocumentWidget_(d); | 1349 | cacheDocumentGlyphs_DocumentWidget_(d); |
1350 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
1325 | postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | 1351 | postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); |
1326 | return iTrue; | 1352 | return iTrue; |
1327 | } | 1353 | } |
@@ -2143,10 +2169,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2143 | checkResponse_DocumentWidget_(d); | 2169 | checkResponse_DocumentWidget_(d); |
2144 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | 2170 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ |
2145 | d->state = ready_RequestState; | 2171 | d->state = ready_RequestState; |
2172 | postProcessRequestContent_DocumentWidget_(d); | ||
2146 | /* The response may be cached. */ | 2173 | /* The response may be cached. */ |
2147 | if (d->request) { | 2174 | if (d->request) { |
2148 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && | 2175 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && |
2149 | startsWithCase_String(meta_GmRequest(d->request), "text/")) { | 2176 | (startsWithCase_String(meta_GmRequest(d->request), "text/") || |
2177 | !cmp_String(&d->sourceMime, mimeType_Gempub))) { | ||
2150 | setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request)); | 2178 | setCachedResponse_History(d->mod.history, lockResponse_GmRequest(d->request)); |
2151 | unlockResponse_GmRequest(d->request); | 2179 | unlockResponse_GmRequest(d->request); |
2152 | } | 2180 | } |
@@ -2856,6 +2884,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2856 | 0, 0, NULL }); | 2884 | 0, 0, NULL }); |
2857 | } | 2885 | } |
2858 | if (willUseProxy_App(scheme) || isGemini || | 2886 | if (willUseProxy_App(scheme) || isGemini || |
2887 | equalCase_Rangecc(scheme, "file") || | ||
2859 | equalCase_Rangecc(scheme, "finger") || | 2888 | equalCase_Rangecc(scheme, "finger") || |
2860 | equalCase_Rangecc(scheme, "gopher")) { | 2889 | equalCase_Rangecc(scheme, "gopher")) { |
2861 | isNative = iTrue; | 2890 | isNative = iTrue; |