summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-02 09:07:15 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-02 09:07:15 +0300
commita14895149fad1724e0f4b4df1fd5834bddc4ead4 (patch)
tree15795172fe0f47c6ed06a707a520d506d768e564 /src/ui
parenteae0ef64d071e2702fffc1d00f223124f8c6d8b4 (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.c145
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
1052static const char *zipPageHeading_(const iRangecc mime) { 1053static 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
1069static 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
1068static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1119static 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
1293static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { 1312static 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;