summaryrefslogtreecommitdiff
path: root/src/gmdocument.c
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 /src/gmdocument.c
parent4041ff10f50d4b6dc5c14b18f180b2738cbbadd0 (diff)
Refactor: Separate media from GmDocument
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r--src/gmdocument.c178
1 files changed, 41 insertions, 137 deletions
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}