diff options
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r-- | src/gmdocument.c | 178 |
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 | ||
40 | iDeclareType(GmLink) | 37 | iDeclareType(GmLink) |
@@ -57,65 +54,24 @@ void deinit_GmLink(iGmLink *d) { | |||
57 | 54 | ||
58 | iDefineTypeConstruction(GmLink) | 55 | iDefineTypeConstruction(GmLink) |
59 | 56 | ||
60 | iDeclareType(GmImage) | ||
61 | |||
62 | struct Impl_GmImage { | ||
63 | iInt2 size; | ||
64 | size_t numBytes; | ||
65 | iString mime; | ||
66 | iGmLinkId linkId; | ||
67 | iBool isPermanent; | ||
68 | SDL_Texture *texture; | ||
69 | }; | ||
70 | |||
71 | void 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 | |||
94 | void deinit_GmImage(iGmImage *d) { | ||
95 | SDL_DestroyTexture(d->texture); | ||
96 | deinit_String(&d->mime); | ||
97 | } | ||
98 | |||
99 | iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) | ||
100 | |||
101 | /*----------------------------------------------------------------------------------------------*/ | 57 | /*----------------------------------------------------------------------------------------------*/ |
102 | 58 | ||
103 | struct Impl_GmDocument { | 59 | struct 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 | ||
121 | iDefineObjectConstruction(GmDocument) | 77 | iDefineObjectConstruction(GmDocument) |
@@ -180,7 +136,7 @@ static int lastVisibleRunBottom_GmDocument_(const iGmDocument *d) { | |||
180 | return 0; | 136 | return 0; |
181 | } | 137 | } |
182 | 138 | ||
183 | iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) { | 139 | static 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 | ||
281 | static 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 | |||
292 | static iBool isGopher_GmDocument_(const iGmDocument *d) { | 237 | static 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 | ||
633 | void deinit_GmDocument(iGmDocument *d) { | 579 | void 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 | ||
592 | iMedia *media_GmDocument(iGmDocument *d) { | ||
593 | return d->media; | ||
594 | } | ||
595 | |||
596 | const iMedia *constMedia_GmDocument(const iGmDocument *d) { | ||
597 | return d->media; | ||
598 | } | ||
599 | |||
645 | void reset_GmDocument(iGmDocument *d) { | 600 | void 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 | ||
901 | void redoLayout_GmDocument(iGmDocument *d) { | ||
902 | doLayout_GmDocument_(d); | ||
903 | } | ||
904 | |||
950 | iLocalDef iBool isNormalizableSpace_(char ch) { | 905 | iLocalDef 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 | ||
1024 | void 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 | |||
1050 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | 979 | void 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 | |||
1195 | uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | 1123 | uint16_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 | ||
1203 | enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { | 1127 | enum 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 | ||
1259 | SDL_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 | |||
1267 | void 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 | |||
1279 | const iString *title_GmDocument(const iGmDocument *d) { | 1183 | const iString *title_GmDocument(const iGmDocument *d) { |
1280 | return &d->title; | 1184 | return &d->title; |
1281 | } | 1185 | } |