summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-10-01 07:15:04 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-10-01 07:15:04 +0300
commitd719e31a5d38c410e8e2d0795afe91fc59cf352e (patch)
tree7a4d2090c08d985c64df4e9168cc6d1097d8bc38
parent4041ff10f50d4b6dc5c14b18f180b2738cbbadd0 (diff)
Refactor: Separate media from GmDocument
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/gmdocument.c178
-rw-r--r--src/gmdocument.h20
-rw-r--r--src/media.c155
-rw-r--r--src/media.h48
-rw-r--r--src/ui/documentwidget.c67
6 files changed, 283 insertions, 187 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e323d47..15ff9a0a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,6 +97,8 @@ set (SOURCES
97 src/history.h 97 src/history.h
98 src/lookup.c 98 src/lookup.c
99 src/lookup.h 99 src/lookup.h
100 src/media.c
101 src/media.h
100 src/prefs.c 102 src/prefs.c
101 src/prefs.h 103 src/prefs.h
102 src/stb_image.h 104 src/stb_image.h
diff --git a/src/gmdocument.c b/src/gmdocument.c
index a273a8d5..6e139e45 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -32,9 +32,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32#include <the_Foundation/ptrarray.h> 32#include <the_Foundation/ptrarray.h>
33#include <the_Foundation/regexp.h> 33#include <the_Foundation/regexp.h>
34 34
35#include <SDL_hints.h>
36#include <SDL_render.h>
37#include <stb_image.h>
38#include <ctype.h> 35#include <ctype.h>
39 36
40iDeclareType(GmLink) 37iDeclareType(GmLink)
@@ -57,65 +54,24 @@ void deinit_GmLink(iGmLink *d) {
57 54
58iDefineTypeConstruction(GmLink) 55iDefineTypeConstruction(GmLink)
59 56
60iDeclareType(GmImage)
61
62struct Impl_GmImage {
63 iInt2 size;
64 size_t numBytes;
65 iString mime;
66 iGmLinkId linkId;
67 iBool isPermanent;
68 SDL_Texture *texture;
69};
70
71void init_GmImage(iGmImage *d, const iBlock *data) {
72 init_String(&d->mime);
73 d->isPermanent = iFalse;
74 d->numBytes = size_Block(data);
75 uint8_t *imgData = stbi_load_from_memory(
76 constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4);
77 if (!imgData) {
78 d->size = zero_I2();
79 d->texture = NULL;
80 }
81 else {
82 SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom(
83 imgData, d->size.x, d->size.y, 32, d->size.x * 4, SDL_PIXELFORMAT_ABGR8888);
84 /* TODO: In multiwindow case, all windows must have the same shared renderer?
85 Or at least a shared context. */
86 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); /* linear scaling */
87 d->texture = SDL_CreateTextureFromSurface(renderer_Window(get_Window()), surface);
88 SDL_FreeSurface(surface);
89 stbi_image_free(imgData);
90 }
91 d->linkId = 0;
92}
93
94void deinit_GmImage(iGmImage *d) {
95 SDL_DestroyTexture(d->texture);
96 deinit_String(&d->mime);
97}
98
99iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data)
100
101/*----------------------------------------------------------------------------------------------*/ 57/*----------------------------------------------------------------------------------------------*/
102 58
103struct Impl_GmDocument { 59struct Impl_GmDocument {
104 iObject object; 60 iObject object;
105 enum iGmDocumentFormat format; 61 enum iGmDocumentFormat format;
106 iString source; 62 iString source;
107 iString url; /* for resolving relative links */ 63 iString url; /* for resolving relative links */
108 iString localHost; 64 iString localHost;
109 int forceBreakWidth; /* force breaks on very long preformatted lines */ 65 int forceBreakWidth; /* force breaks on very long preformatted lines */
110 iInt2 size; 66 iInt2 size;
111 iArray layout; /* contents of source, laid out in document space */ 67 iArray layout; /* contents of source, laid out in document space */
112 iPtrArray links; 68 iPtrArray links;
113 iString bannerText; 69 iString bannerText;
114 iString title; /* the first top-level title */ 70 iString title; /* the first top-level title */
115 iArray headings; 71 iArray headings;
116 iPtrArray images; /* persistent across layouts, references links by ID */ 72 uint32_t themeSeed;
117 uint32_t themeSeed; 73 iChar siteIcon;
118 iChar siteIcon; 74 iMedia * media;
119}; 75};
120 76
121iDefineObjectConstruction(GmDocument) 77iDefineObjectConstruction(GmDocument)
@@ -180,7 +136,7 @@ static int lastVisibleRunBottom_GmDocument_(const iGmDocument *d) {
180 return 0; 136 return 0;
181} 137}
182 138
183iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) { 139static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) {
184 const iRangecc content = { start, constEnd_String(&d->source) }; 140 const iRangecc content = { start, constEnd_String(&d->source) };
185 iRangecc line = iNullRange; 141 iRangecc line = iNullRange;
186 nextSplit_Rangecc(content, "\n", &line); 142 nextSplit_Rangecc(content, "\n", &line);
@@ -243,7 +199,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li
243 link->flags |= imageFileExtension_GmLinkFlag; 199 link->flags |= imageFileExtension_GmLinkFlag;
244 } 200 }
245 else if (endsWithCase_String(path, ".mp3") || endsWithCase_String(path, ".wav") || 201 else if (endsWithCase_String(path, ".mp3") || endsWithCase_String(path, ".wav") ||
246 endsWithCase_String(path, ".mid")) { 202 endsWithCase_String(path, ".mid") || endsWithCase_String(path, ".ogg")) {
247 link->flags |= audioFileExtension_GmLinkFlag; 203 link->flags |= audioFileExtension_GmLinkFlag;
248 } 204 }
249 delete_String(path); 205 delete_String(path);
@@ -278,17 +234,6 @@ static void clearLinks_GmDocument_(iGmDocument *d) {
278 clear_PtrArray(&d->links); 234 clear_PtrArray(&d->links);
279} 235}
280 236
281static size_t findLinkImage_GmDocument_(const iGmDocument *d, iGmLinkId linkId) {
282 /* TODO: use a hash */
283 iConstForEach(PtrArray, i, &d->images) {
284 const iGmImage *img = i.ptr;
285 if (img->linkId == linkId) {
286 return index_PtrArrayConstIterator(&i);
287 }
288 }
289 return iInvalidPos;
290}
291
292static iBool isGopher_GmDocument_(const iGmDocument *d) { 237static iBool isGopher_GmDocument_(const iGmDocument *d) {
293 return equalCase_Rangecc(urlScheme_String(&d->url), "gopher"); 238 return equalCase_Rangecc(urlScheme_String(&d->url), "gopher");
294} 239}
@@ -576,13 +521,14 @@ static void doLayout_GmDocument_(iGmDocument *d) {
576 ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; 521 ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag;
577 /* Image content. */ 522 /* Image content. */
578 if (type == link_GmLineType) { 523 if (type == link_GmLineType) {
579 const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); 524 const iMediaId imageId = findLinkImage_Media(d->media, run.linkId);
580 if (imgIndex != iInvalidPos) { 525 if (imageId) {
581 const iGmImage *img = constAt_PtrArray(&d->images, imgIndex); 526 iGmImageInfo img;
527 imageInfo_Media(d->media, imageId, &img);
582 /* Mark the link as having content. */ { 528 /* Mark the link as having content. */ {
583 iGmLink *link = at_PtrArray(&d->links, run.linkId - 1); 529 iGmLink *link = at_PtrArray(&d->links, run.linkId - 1);
584 link->flags |= content_GmLinkFlag; 530 link->flags |= content_GmLinkFlag;
585 if (img->isPermanent) { 531 if (img.isPermanent) {
586 link->flags |= permanent_GmLinkFlag; 532 link->flags |= permanent_GmLinkFlag;
587 } 533 }
588 } 534 }
@@ -590,10 +536,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
590 pos.y += margin; 536 pos.y += margin;
591 run.bounds.pos = pos; 537 run.bounds.pos = pos;
592 run.bounds.size.x = d->size.x; 538 run.bounds.size.x = d->size.x;
593 const float aspect = (float) img->size.y / (float) img->size.x; 539 const float aspect = (float) img.size.y / (float) img.size.x;
594 run.bounds.size.y = d->size.x * aspect; 540 run.bounds.size.y = d->size.x * aspect;
595 run.visBounds = run.bounds; 541 run.visBounds = run.bounds;
596 const iInt2 maxSize = mulf_I2(img->size, get_Window()->pixelRatio); 542 const iInt2 maxSize = mulf_I2(img.size, get_Window()->pixelRatio);
597 if (width_Rect(run.visBounds) > maxSize.x) { 543 if (width_Rect(run.visBounds) > maxSize.x) {
598 /* Don't scale the image up. */ 544 /* Don't scale the image up. */
599 run.visBounds.size.y = run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds); 545 run.visBounds.size.y = run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);
@@ -601,10 +547,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
601 run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2; 547 run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;
602 run.bounds.size.y = run.visBounds.size.y; 548 run.bounds.size.y = run.visBounds.size.y;
603 } 549 }
604 run.text = iNullRange; 550 run.text = iNullRange;
605 run.font = 0; 551 run.font = 0;
606 run.color = 0; 552 run.color = 0;
607 run.imageId = imgIndex + 1; 553 run.imageId = imageId;
608 pushBack_Array(&d->layout, &run); 554 pushBack_Array(&d->layout, &run);
609 pos.y += run.bounds.size.y + margin; 555 pos.y += run.bounds.size.y + margin;
610 } 556 }
@@ -625,12 +571,13 @@ void init_GmDocument(iGmDocument *d) {
625 init_String(&d->bannerText); 571 init_String(&d->bannerText);
626 init_String(&d->title); 572 init_String(&d->title);
627 init_Array(&d->headings, sizeof(iGmHeading)); 573 init_Array(&d->headings, sizeof(iGmHeading));
628 init_PtrArray(&d->images);
629 d->themeSeed = 0; 574 d->themeSeed = 0;
630 d->siteIcon = 0; 575 d->siteIcon = 0;
576 d->media = new_Media();
631} 577}
632 578
633void deinit_GmDocument(iGmDocument *d) { 579void deinit_GmDocument(iGmDocument *d) {
580 delete_Media(d->media);
634 deinit_String(&d->bannerText); 581 deinit_String(&d->bannerText);
635 deinit_String(&d->title); 582 deinit_String(&d->title);
636 clearLinks_GmDocument_(d); 583 clearLinks_GmDocument_(d);
@@ -642,12 +589,16 @@ void deinit_GmDocument(iGmDocument *d) {
642 deinit_String(&d->source); 589 deinit_String(&d->source);
643} 590}
644 591
592iMedia *media_GmDocument(iGmDocument *d) {
593 return d->media;
594}
595
596const iMedia *constMedia_GmDocument(const iGmDocument *d) {
597 return d->media;
598}
599
645void reset_GmDocument(iGmDocument *d) { 600void reset_GmDocument(iGmDocument *d) {
646 /* Free the loaded images. */ 601 clear_Media(d->media);
647 iForEach(PtrArray, i, &d->images) {
648 deinit_GmImage(i.ptr);
649 }
650 clear_PtrArray(&d->images);
651 clearLinks_GmDocument_(d); 602 clearLinks_GmDocument_(d);
652 clear_Array(&d->layout); 603 clear_Array(&d->layout);
653 clear_Array(&d->headings); 604 clear_Array(&d->headings);
@@ -947,6 +898,10 @@ void setWidth_GmDocument(iGmDocument *d, int width, int forceBreakWidth) {
947 doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ 898 doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */
948} 899}
949 900
901void redoLayout_GmDocument(iGmDocument *d) {
902 doLayout_GmDocument_(d);
903}
904
950iLocalDef iBool isNormalizableSpace_(char ch) { 905iLocalDef iBool isNormalizableSpace_(char ch) {
951 return ch == ' ' || ch == '\t'; 906 return ch == ' ' || ch == '\t';
952} 907}
@@ -1021,32 +976,6 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int
1021 setWidth_GmDocument(d, width, forceBreakWidth); /* re-do layout */ 976 setWidth_GmDocument(d, width, forceBreakWidth); /* re-do layout */
1022} 977}
1023 978
1024void setImage_GmDocument(iGmDocument *d, iGmLinkId linkId, const iString *mime, const iBlock *data,
1025 iBool allowHide) {
1026 if (!mime || !data) {
1027 iGmImage *img;
1028 if (take_PtrArray(&d->images, findLinkImage_GmDocument_(d, linkId), (void **) &img)) {
1029 delete_GmImage(img);
1030 }
1031 }
1032 else {
1033 /* TODO: check if we know this MIME type */
1034 /* Upload the image. */ {
1035 iGmImage *img = new_GmImage(data);
1036 img->linkId = linkId; /* TODO: use a hash? */
1037 img->isPermanent = !allowHide;
1038 set_String(&img->mime, mime);
1039 if (img->texture) {
1040 pushBack_PtrArray(&d->images, img);
1041 }
1042 else {
1043 delete_GmImage(img);
1044 }
1045 }
1046 }
1047 doLayout_GmDocument_(d);
1048}
1049
1050void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, 979void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render,
1051 void *context) { 980 void *context) {
1052 iBool isInside = iFalse; 981 iBool isInside = iFalse;
@@ -1191,13 +1120,8 @@ const iTime *linkTime_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
1191 return link ? &link->when : NULL; 1120 return link ? &link->when : NULL;
1192} 1121}
1193 1122
1194
1195uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 1123uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
1196 size_t index = findLinkImage_GmDocument_(d, linkId); 1124 return findLinkImage_Media(d->media, linkId);
1197 if (index != iInvalidPos) {
1198 return index + 1;
1199 }
1200 return 0;
1201} 1125}
1202 1126
1203enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { 1127enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) {
@@ -1256,26 +1180,6 @@ iBool isMediaLink_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
1256 (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0; 1180 (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0;
1257} 1181}
1258 1182
1259SDL_Texture *imageTexture_GmDocument(const iGmDocument *d, uint16_t imageId) {
1260 if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
1261 const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
1262 return img->texture;
1263 }
1264 return NULL;
1265}
1266
1267void imageInfo_GmDocument(const iGmDocument *d, uint16_t imageId, iGmImageInfo *info_out) {
1268 if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
1269 const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
1270 info_out->size = img->size;
1271 info_out->numBytes = img->numBytes;
1272 info_out->mime = cstr_String(&img->mime);
1273 }
1274 else {
1275 iZap(*info_out);
1276 }
1277}
1278
1279const iString *title_GmDocument(const iGmDocument *d) { 1183const iString *title_GmDocument(const iGmDocument *d) {
1280 return &d->title; 1184 return &d->title;
1281} 1185}
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 891ac6f2..a04398d9 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -23,15 +23,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#pragma once 23#pragma once
24 24
25#include "gmutil.h" 25#include "gmutil.h"
26#include "media.h"
26 27
27#include <the_Foundation/array.h> 28#include <the_Foundation/array.h>
28#include <the_Foundation/object.h> 29#include <the_Foundation/object.h>
29#include <the_Foundation/rect.h> 30#include <the_Foundation/rect.h>
30#include <the_Foundation/string.h> 31#include <the_Foundation/string.h>
31#include <the_Foundation/time.h> 32#include <the_Foundation/time.h>
32#include <SDL_render.h>
33 33
34iDeclareType(GmImageInfo)
35iDeclareType(GmHeading) 34iDeclareType(GmHeading)
36iDeclareType(GmRun) 35iDeclareType(GmRun)
37 36
@@ -60,12 +59,6 @@ enum iGmLinkFlags {
60 permanent_GmLinkFlag = iBit(15), /* content cannot be dismissed; media link */ 59 permanent_GmLinkFlag = iBit(15), /* content cannot be dismissed; media link */
61}; 60};
62 61
63struct Impl_GmImageInfo {
64 iInt2 size;
65 size_t numBytes;
66 const char *mime;
67};
68
69struct Impl_GmHeading { 62struct Impl_GmHeading {
70 iRangecc text; 63 iRangecc text;
71 int level; /* 0, 1, 2 */ 64 int level; /* 0, 1, 2 */
@@ -86,7 +79,8 @@ struct Impl_GmRun {
86 iRect bounds; /* used for hit testing, may extend to edges */ 79 iRect bounds; /* used for hit testing, may extend to edges */
87 iRect visBounds; /* actual visual bounds */ 80 iRect visBounds; /* actual visual bounds */
88 iGmLinkId linkId; /* zero for non-links */ 81 iGmLinkId linkId; /* zero for non-links */
89 uint16_t imageId; /* zero for images */ 82 uint16_t imageId; /* zero if not an image */
83 uint16_t audioId; /* zero if not audio */
90}; 84};
91 85
92const char * findLoc_GmRun (const iGmRun *, iInt2 pos); 86const char * findLoc_GmRun (const iGmRun *, iInt2 pos);
@@ -103,15 +97,17 @@ enum iGmDocumentFormat {
103void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 97void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
104void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); 98void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format);
105void setWidth_GmDocument (iGmDocument *, int width, int forceBreakWidth); 99void setWidth_GmDocument (iGmDocument *, int width, int forceBreakWidth);
100void redoLayout_GmDocument (iGmDocument *);
106void setUrl_GmDocument (iGmDocument *, const iString *url); 101void setUrl_GmDocument (iGmDocument *, const iString *url);
107void setSource_GmDocument (iGmDocument *, const iString *source, int width, int forceBreakWidth); 102void setSource_GmDocument (iGmDocument *, const iString *source, int width, int forceBreakWidth);
108void setImage_GmDocument (iGmDocument *, iGmLinkId linkId, const iString *mime, const iBlock *data,
109 iBool allowHide);
110 103
111void reset_GmDocument (iGmDocument *); /* free images */ 104void reset_GmDocument (iGmDocument *); /* free images */
112 105
113typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 106typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
114 107
108iMedia * media_GmDocument (iGmDocument *);
109const iMedia * constMedia_GmDocument (const iGmDocument *);
110
115void render_GmDocument (const iGmDocument *, iRangei visRangeY, 111void render_GmDocument (const iGmDocument *, iRangei visRangeY,
116 iGmDocumentRenderFunc render, void *); 112 iGmDocumentRenderFunc render, void *);
117iInt2 size_GmDocument (const iGmDocument *); 113iInt2 size_GmDocument (const iGmDocument *);
@@ -144,5 +140,3 @@ iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId);
144const iString * title_GmDocument (const iGmDocument *); 140const iString * title_GmDocument (const iGmDocument *);
145iChar siteIcon_GmDocument (const iGmDocument *); 141iChar siteIcon_GmDocument (const iGmDocument *);
146 142
147SDL_Texture * imageTexture_GmDocument (const iGmDocument *, uint16_t imageId);
148void imageInfo_GmDocument (const iGmDocument *, uint16_t imageId, iGmImageInfo *info_out);
diff --git a/src/media.c b/src/media.c
new file mode 100644
index 00000000..9f6acd19
--- /dev/null
+++ b/src/media.c
@@ -0,0 +1,155 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "media.h"
24#include "gmdocument.h"
25#include "ui/window.h"
26
27#include <the_Foundation/ptrarray.h>
28#include <stb_image.h>
29#include <SDL_hints.h>
30#include <SDL_render.h>
31
32iDeclareType(GmImage)
33
34struct Impl_GmImage {
35 iInt2 size;
36 size_t numBytes;
37 iString mime;
38 iGmLinkId linkId;
39 iBool isPermanent;
40 SDL_Texture *texture;
41};
42
43void init_GmImage(iGmImage *d, const iBlock *data) {
44 init_String(&d->mime);
45 d->isPermanent = iFalse;
46 d->numBytes = size_Block(data);
47 uint8_t *imgData = stbi_load_from_memory(
48 constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4);
49 if (!imgData) {
50 d->size = zero_I2();
51 d->texture = NULL;
52 }
53 else {
54 SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom(
55 imgData, d->size.x, d->size.y, 32, d->size.x * 4, SDL_PIXELFORMAT_ABGR8888);
56 /* TODO: In multiwindow case, all windows must have the same shared renderer?
57 Or at least a shared context. */
58 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); /* linear scaling */
59 d->texture = SDL_CreateTextureFromSurface(renderer_Window(get_Window()), surface);
60 SDL_FreeSurface(surface);
61 stbi_image_free(imgData);
62 }
63 d->linkId = 0;
64}
65
66void deinit_GmImage(iGmImage *d) {
67 SDL_DestroyTexture(d->texture);
68 deinit_String(&d->mime);
69}
70
71iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data)
72
73/*----------------------------------------------------------------------------------------------*/
74
75struct Impl_Media {
76 iPtrArray images;
77};
78
79iDefineTypeConstruction(Media)
80
81void init_Media(iMedia *d) {
82 init_PtrArray(&d->images);
83}
84
85void deinit_Media(iMedia *d) {
86 clear_Media(d);
87 deinit_PtrArray(&d->images);
88}
89
90void clear_Media(iMedia *d) {
91 iForEach(PtrArray, i, &d->images) {
92 deinit_GmImage(i.ptr);
93 }
94 clear_PtrArray(&d->images);
95}
96
97void setImage_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlock *data,
98 iBool allowHide) {
99 if (!mime || !data) {
100 iGmImage *img;
101 const iMediaId existing = findLinkImage_Media(d, linkId);
102 if (existing) {
103 take_PtrArray(&d->images, existing - 1, (void **) &img);
104 delete_GmImage(img);
105 }
106 }
107 else {
108 /* TODO: check if we know this MIME type */
109 /* Upload the image. */ {
110 iGmImage *img = new_GmImage(data);
111 img->linkId = linkId; /* TODO: use a hash? */
112 img->isPermanent = !allowHide;
113 set_String(&img->mime, mime);
114 if (img->texture) {
115 pushBack_PtrArray(&d->images, img);
116 }
117 else {
118 delete_GmImage(img);
119 }
120 }
121 }
122// doLayout_GmDocument_(d);
123}
124
125iMediaId findLinkImage_Media(const iMedia *d, iGmLinkId linkId) {
126 /* TODO: use a hash */
127 iConstForEach(PtrArray, i, &d->images) {
128 const iGmImage *img = i.ptr;
129 if (img->linkId == linkId) {
130 return index_PtrArrayConstIterator(&i) + 1;
131 }
132 }
133 return 0;
134}
135
136SDL_Texture *imageTexture_Media(const iMedia *d, uint16_t imageId) {
137 if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
138 const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
139 return img->texture;
140 }
141 return NULL;
142}
143
144void imageInfo_Media(const iMedia *d, uint16_t imageId, iGmImageInfo *info_out) {
145 if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
146 const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
147 info_out->size = img->size;
148 info_out->numBytes = img->numBytes;
149 info_out->mime = cstr_String(&img->mime);
150 info_out->isPermanent = img->isPermanent;
151 }
152 else {
153 iZap(*info_out);
154 }
155}
diff --git a/src/media.h b/src/media.h
new file mode 100644
index 00000000..0a6c1a81
--- /dev/null
+++ b/src/media.h
@@ -0,0 +1,48 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include <the_Foundation/block.h>
26#include <the_Foundation/string.h>
27#include <the_Foundation/vec2.h>
28#include <SDL_render.h>
29
30typedef uint16_t iMediaId;
31
32iDeclareType(GmImageInfo)
33
34struct Impl_GmImageInfo {
35 iInt2 size;
36 size_t numBytes;
37 const char *mime;
38 iBool isPermanent;
39};
40
41iDeclareType(Media) iDeclareTypeConstruction(Media)
42
43void clear_Media (iMedia *);
44void setImage_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, iBool allowHide);
45
46iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId);
47SDL_Texture * imageTexture_Media (const iMedia *, iMediaId imageId);
48void imageInfo_Media (const iMedia *, iMediaId imageId, iGmImageInfo *info_out);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index eb248ceb..f63d93f4 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -31,9 +31,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
31#include "util.h" 31#include "util.h"
32#include "history.h" 32#include "history.h"
33#include "app.h" 33#include "app.h"
34#include "../gmdocument.h" 34#include "gmdocument.h"
35#include "../gmrequest.h" 35#include "gmrequest.h"
36#include "../gmutil.h" 36#include "gmutil.h"
37#include "media.h"
37 38
38#include <the_Foundation/file.h> 39#include <the_Foundation/file.h>
39#include <the_Foundation/fileinfo.h> 40#include <the_Foundation/fileinfo.h>
@@ -735,8 +736,12 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
735 baseName_Path(collect_String(newRange_String(parts.path))).start; 736 baseName_Path(collect_String(newRange_String(parts.path))).start;
736 } 737 }
737 format_String(&str, "=> %s %s\n", cstr_String(d->mod.url), imageTitle); 738 format_String(&str, "=> %s %s\n", cstr_String(d->mod.url), imageTitle);
738 setImage_GmDocument( 739 setImage_Media(media_GmDocument(d->doc),
739 d->doc, 1, mimeStr, &response->body, iFalse /* it's fixed */); 740 1,
741 mimeStr,
742 &response->body,
743 iFalse /* it's fixed */);
744 redoLayout_GmDocument(d->doc);
740 } 745 }
741 else { 746 else {
742 clear_String(&str); 747 clear_String(&str);
@@ -1136,13 +1141,13 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
1136 const enum iGmStatusCode code = status_GmRequest(req->req); 1141 const enum iGmStatusCode code = status_GmRequest(req->req);
1137 /* Give the media to the document for presentation. */ 1142 /* Give the media to the document for presentation. */
1138 if (code == success_GmStatusCode) { 1143 if (code == success_GmStatusCode) {
1139// printf("media finished: %s\n size: %zu\n type: %s\n",
1140// cstr_String(url_GmRequest(req->req)),
1141// size_Block(body_GmRequest(req->req)),
1142// cstr_String(meta_GmRequest(req->req)));
1143 if (startsWith_String(meta_GmRequest(req->req), "image/")) { 1144 if (startsWith_String(meta_GmRequest(req->req), "image/")) {
1144 setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), 1145 setImage_Media(media_GmDocument(d->doc),
1145 body_GmRequest(req->req), iTrue); 1146 req->linkId,
1147 meta_GmRequest(req->req),
1148 body_GmRequest(req->req),
1149 iTrue);
1150 redoLayout_GmDocument(d->doc);
1146 updateVisible_DocumentWidget_(d); 1151 updateVisible_DocumentWidget_(d);
1147 invalidate_DocumentWidget_(d); 1152 invalidate_DocumentWidget_(d);
1148 refresh_Widget(as_Widget(d)); 1153 refresh_Widget(as_Widget(d));
@@ -1792,10 +1797,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1792 further to do. */ 1797 further to do. */
1793 return iTrue; 1798 return iTrue;
1794 } 1799 }
1795 if (!requestMedia_DocumentWidget_(d, linkId)) { 1800 if (!requestMedia_DocumentWidget_(d, linkId)) {
1796 if (linkFlags & content_GmLinkFlag) { 1801 if (linkFlags & content_GmLinkFlag) {
1797 /* Dismiss shown content on click. */ 1802 /* Dismiss shown content on click. */
1798 setImage_GmDocument(d->doc, linkId, NULL, NULL, iTrue); 1803 setImage_Media(media_GmDocument(d->doc), linkId, NULL, NULL, iTrue);
1804 redoLayout_GmDocument(d->doc);
1799 d->hoverLink = NULL; 1805 d->hoverLink = NULL;
1800 scroll_DocumentWidget_(d, 0); 1806 scroll_DocumentWidget_(d, 0);
1801 updateVisible_DocumentWidget_(d); 1807 updateVisible_DocumentWidget_(d);
@@ -1807,8 +1813,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1807 /* Show the existing content again if we have it. */ 1813 /* Show the existing content again if we have it. */
1808 iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); 1814 iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId);
1809 if (req) { 1815 if (req) {
1810 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req), 1816 setImage_Media(media_GmDocument(d->doc),
1811 body_GmRequest(req->req), iTrue); 1817 linkId,
1818 meta_GmRequest(req->req),
1819 body_GmRequest(req->req),
1820 iTrue);
1821 redoLayout_GmDocument(d->doc);
1812 updateVisible_DocumentWidget_(d); 1822 updateVisible_DocumentWidget_(d);
1813 invalidate_DocumentWidget_(d); 1823 invalidate_DocumentWidget_(d);
1814 refresh_Widget(w); 1824 refresh_Widget(w);
@@ -1894,7 +1904,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1894 iDrawContext *d = context; 1904 iDrawContext *d = context;
1895 const iInt2 origin = d->viewPos; 1905 const iInt2 origin = d->viewPos;
1896 if (run->imageId) { 1906 if (run->imageId) {
1897 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); 1907 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->imageId);
1898 if (tex) { 1908 if (tex) {
1899 const iRect dst = moved_Rect(run->visBounds, origin); 1909 const iRect dst = moved_Rect(run->visBounds, origin);
1900 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ 1910 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -1981,7 +1991,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1981 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); 1991 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart);
1982 iAssert(!isEmpty_Rect(run->bounds)); 1992 iAssert(!isEmpty_Rect(run->bounds));
1983 iGmImageInfo info; 1993 iGmImageInfo info;
1984 imageInfo_GmDocument(doc, linkImage_GmDocument(doc, run->linkId), &info); 1994 imageInfo_Media(constMedia_GmDocument(doc), linkImage_GmDocument(doc, run->linkId), &info);
1985 iString text; 1995 iString text;
1986 init_String(&text); 1996 init_String(&text);
1987 format_String(&text, "%s \u2014 %d x %d \u2014 %.1fMB", 1997 format_String(&text, "%s \u2014 %d x %d \u2014 %.1fMB",
@@ -2083,7 +2093,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
2083// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 2093// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
2084} 2094}
2085 2095
2086static int drawSideRect_(iPaint *p, iRect rect) { //}, int thickness) { 2096static int drawSideRect_(iPaint *p, iRect rect) {
2087 int bg = tmBannerBackground_ColorId; 2097 int bg = tmBannerBackground_ColorId;
2088 int fg = tmBannerIcon_ColorId; 2098 int fg = tmBannerIcon_ColorId;
2089 if (equal_Color(get_Color(bg), get_Color(tmBackground_ColorId))) { 2099 if (equal_Color(get_Color(bg), get_Color(tmBackground_ColorId))) {
@@ -2131,34 +2141,17 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
2131 const iChar icon = siteIcon_GmDocument(d->doc); 2141 const iChar icon = siteIcon_GmDocument(d->doc);
2132 iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) }; 2142 iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) };
2133 p.alpha = opacity * 255; 2143 p.alpha = opacity * 255;
2134 //int offset = iMax(0, bottom_Rect(banner->visBounds) - d->scrollY); 2144 rect.pos.y += height_Rect(bounds) / 2 - rect.size.y / 2 - (banner ? banner->visBounds.size.y / 2 : 0);
2135 rect.pos.y += height_Rect(bounds) / 2 - rect.size.y / 2 - (banner ? banner->visBounds.size.y / 2 : 0); // offset; 2145 int fg = drawSideRect_(&p, rect);
2136 //rect.pos.y -= lineHeight_Text(heading3_FontId) / 2; /* the heading text underneath */
2137 int fg = drawSideRect_(&p, rect); //, gap_UI / 2);
2138 iString str; 2146 iString str;
2139 initUnicodeN_String(&str, &icon, 1); 2147 initUnicodeN_String(&str, &icon, 1);
2140 drawCentered_Text(banner_FontId, rect, iTrue, fg, "%s", cstr_String(&str)); 2148 drawCentered_Text(banner_FontId, rect, iTrue, fg, "%s", cstr_String(&str));
2141#if 1
2142 if (avail >= minBannerSize * 2.25f) { 2149 if (avail >= minBannerSize * 2.25f) {
2143 iRangecc text = currentHeading_DocumentWidget_(d);// bannerText_DocumentWidget_(d); 2150 iRangecc text = currentHeading_DocumentWidget_(d);// bannerText_DocumentWidget_(d);
2144 iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text); 2151 iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text);
2145 const int font = heading3_FontId; 2152 const int font = heading3_FontId;
2146 drawWrapRange_Text(font, pos, avail - margin, tmBannerSideTitle_ColorId, text); 2153 drawWrapRange_Text(font, pos, avail - margin, tmBannerSideTitle_ColorId, text);
2147#if 0
2148 while (!isEmpty_Range(&text)) {
2149 tryAdvance_Text(font, text, avail - 2 * margin, &endp);
2150 drawRange_Text(
2151 font, pos, tmBannerTitle_ColorId, (iRangecc){ text.start, endp });
2152// drawRange_Text(font,
2153// add_I2(pos, init1_I2(-gap_UI / 4)),
2154// tmBackground_ColorId,
2155// (iRangecc){ text.start, endp });
2156 text.start = endp;
2157 pos.y += lineHeight_Text(font);
2158 }
2159#endif
2160 } 2154 }
2161#endif
2162 setOpacity_Text(1.0f); 2155 setOpacity_Text(1.0f);
2163 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 2156 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
2164 } 2157 }