diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-28 23:41:35 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-28 23:41:35 +0300 |
commit | d921021132367076cd2a5f120b3a49db6e29acf7 (patch) | |
tree | b118977ae998023c71d5e2e2c69d3051dfb77e4a /src/gmdocument.c | |
parent | 1a8a0170168568d1c3f075cc2996aceda08e790d (diff) |
Loading and rendering images in the document
There can be multiple inline media requests on the page. Images are always associated with a link.
Diffstat (limited to 'src/gmdocument.c')
-rw-r--r-- | src/gmdocument.c | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 3dbef514..f8998c17 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -3,10 +3,15 @@ | |||
3 | #include "ui/color.h" | 3 | #include "ui/color.h" |
4 | #include "ui/text.h" | 4 | #include "ui/text.h" |
5 | #include "ui/metrics.h" | 5 | #include "ui/metrics.h" |
6 | #include "ui/window.h" | ||
6 | 7 | ||
7 | #include <the_Foundation/ptrarray.h> | 8 | #include <the_Foundation/ptrarray.h> |
8 | #include <the_Foundation/regexp.h> | 9 | #include <the_Foundation/regexp.h> |
9 | 10 | ||
11 | #include <SDL_hints.h> | ||
12 | #include <SDL_render.h> | ||
13 | #include <stb_image.h> | ||
14 | |||
10 | iDeclareType(GmLink) | 15 | iDeclareType(GmLink) |
11 | 16 | ||
12 | struct Impl_GmLink { | 17 | struct Impl_GmLink { |
@@ -25,6 +30,40 @@ void deinit_GmLink(iGmLink *d) { | |||
25 | 30 | ||
26 | iDefineTypeConstruction(GmLink) | 31 | iDefineTypeConstruction(GmLink) |
27 | 32 | ||
33 | iDeclareType(GmImage) | ||
34 | |||
35 | struct Impl_GmImage { | ||
36 | iInt2 size; | ||
37 | SDL_Texture *texture; | ||
38 | iGmLinkId linkId; | ||
39 | }; | ||
40 | |||
41 | void init_GmImage(iGmImage *d, const iBlock *data) { | ||
42 | uint8_t *imgData = stbi_load_from_memory( | ||
43 | constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); | ||
44 | if (!imgData) { | ||
45 | d->size = zero_I2(); | ||
46 | d->texture = NULL; | ||
47 | } | ||
48 | else { | ||
49 | SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom( | ||
50 | imgData, d->size.x, d->size.y, 32, d->size.x * 4, SDL_PIXELFORMAT_ABGR8888); | ||
51 | /* TODO: In multiwindow case, all windows must have the same shared renderer? | ||
52 | Or at least a shared context. */ | ||
53 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); /* linear scaling */ | ||
54 | d->texture = SDL_CreateTextureFromSurface(renderer_Window(get_Window()), surface); | ||
55 | SDL_FreeSurface(surface); | ||
56 | stbi_image_free(imgData); | ||
57 | } | ||
58 | d->linkId = 0; | ||
59 | } | ||
60 | |||
61 | void deinit_GmImage(iGmImage *d) { | ||
62 | SDL_DestroyTexture(d->texture); | ||
63 | } | ||
64 | |||
65 | iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) | ||
66 | |||
28 | struct Impl_GmDocument { | 67 | struct Impl_GmDocument { |
29 | iObject object; | 68 | iObject object; |
30 | enum iGmDocumentFormat format; | 69 | enum iGmDocumentFormat format; |
@@ -34,6 +73,7 @@ struct Impl_GmDocument { | |||
34 | iArray layout; /* contents of source, laid out in document space */ | 73 | iArray layout; /* contents of source, laid out in document space */ |
35 | iPtrArray links; | 74 | iPtrArray links; |
36 | iString title; /* the first top-level title */ | 75 | iString title; /* the first top-level title */ |
76 | iPtrArray images; /* persistent across layouts, references links by ID */ | ||
37 | }; | 77 | }; |
38 | 78 | ||
39 | iDefineObjectConstruction(GmDocument) | 79 | iDefineObjectConstruction(GmDocument) |
@@ -178,6 +218,17 @@ static void clearLinks_GmDocument_(iGmDocument *d) { | |||
178 | clear_PtrArray(&d->links); | 218 | clear_PtrArray(&d->links); |
179 | } | 219 | } |
180 | 220 | ||
221 | static size_t findLinkImage_GmDocument_(const iGmDocument *d, iGmLinkId linkId) { | ||
222 | /* TODO: use a hash */ | ||
223 | iConstForEach(PtrArray, i, &d->images) { | ||
224 | const iGmImage *img = i.ptr; | ||
225 | if (img->linkId == linkId) { | ||
226 | return index_PtrArrayConstIterator(&i); | ||
227 | } | ||
228 | } | ||
229 | return iInvalidPos; | ||
230 | } | ||
231 | |||
181 | static void doLayout_GmDocument_(iGmDocument *d) { | 232 | static void doLayout_GmDocument_(iGmDocument *d) { |
182 | /* TODO: Collect these parameters into a GmTheme. */ | 233 | /* TODO: Collect these parameters into a GmTheme. */ |
183 | static const int fonts[max_GmLineType] = { | 234 | static const int fonts[max_GmLineType] = { |
@@ -236,6 +287,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
236 | iGmRun run; | 287 | iGmRun run; |
237 | run.color = white_ColorId; | 288 | run.color = white_ColorId; |
238 | run.linkId = 0; | 289 | run.linkId = 0; |
290 | run.imageId = 0; | ||
239 | enum iGmLineType type; | 291 | enum iGmLineType type; |
240 | int indent = 0; | 292 | int indent = 0; |
241 | if (!isPreformat) { | 293 | if (!isPreformat) { |
@@ -373,6 +425,24 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
373 | trimStart_Rangecc(&runLine); | 425 | trimStart_Rangecc(&runLine); |
374 | pos.y += lineHeight_Text(run.font); | 426 | pos.y += lineHeight_Text(run.font); |
375 | } | 427 | } |
428 | /* Image content. */ | ||
429 | if (type == link_GmLineType) { | ||
430 | const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); | ||
431 | if (imgIndex != iInvalidPos) { | ||
432 | const iGmImage *img = constAt_PtrArray(&d->images, imgIndex); | ||
433 | run.bounds.pos = pos; | ||
434 | run.bounds.size.x = d->size.x; | ||
435 | const float aspect = (float) img->size.y / (float) img->size.x; | ||
436 | run.bounds.size.y = d->size.x * aspect; | ||
437 | run.visBounds = run.bounds; /* TODO: limit max height? */ | ||
438 | run.text = iNullRange; | ||
439 | run.font = 0; | ||
440 | run.color = 0; | ||
441 | run.imageId = imgIndex + 1; | ||
442 | pushBack_Array(&d->layout, &run); | ||
443 | pos.y += run.bounds.size.y; | ||
444 | } | ||
445 | } | ||
376 | prevType = type; | 446 | prevType = type; |
377 | } | 447 | } |
378 | d->size.y = pos.y; | 448 | d->size.y = pos.y; |
@@ -386,6 +456,7 @@ void init_GmDocument(iGmDocument *d) { | |||
386 | init_Array(&d->layout, sizeof(iGmRun)); | 456 | init_Array(&d->layout, sizeof(iGmRun)); |
387 | init_PtrArray(&d->links); | 457 | init_PtrArray(&d->links); |
388 | init_String(&d->title); | 458 | init_String(&d->title); |
459 | init_PtrArray(&d->images); | ||
389 | } | 460 | } |
390 | 461 | ||
391 | void deinit_GmDocument(iGmDocument *d) { | 462 | void deinit_GmDocument(iGmDocument *d) { |
@@ -397,6 +468,16 @@ void deinit_GmDocument(iGmDocument *d) { | |||
397 | deinit_String(&d->source); | 468 | deinit_String(&d->source); |
398 | } | 469 | } |
399 | 470 | ||
471 | void reset_GmDocument(iGmDocument *d) { | ||
472 | /* Free the loaded images. */ | ||
473 | iForEach(PtrArray, i, &d->images) { | ||
474 | deinit_GmImage(i.ptr); | ||
475 | } | ||
476 | clear_PtrArray(&d->images); | ||
477 | clearLinks_GmDocument_(d); | ||
478 | clear_Array(&d->layout); | ||
479 | } | ||
480 | |||
400 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | 481 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { |
401 | d->format = format; | 482 | d->format = format; |
402 | } | 483 | } |
@@ -477,6 +558,21 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | |||
477 | /* TODO: just flag need-layout and do it later */ | 558 | /* TODO: just flag need-layout and do it later */ |
478 | } | 559 | } |
479 | 560 | ||
561 | void setImage_GmDocument(iGmDocument *d, iGmLinkId linkId, const iString *mime, const iBlock *data) { | ||
562 | /* TODO: check if we know this MIME type */ | ||
563 | /* Load the image. */ { | ||
564 | iGmImage *img = new_GmImage(data); | ||
565 | img->linkId = linkId; /* TODO: use a hash? */ | ||
566 | if (img->texture) { | ||
567 | pushBack_PtrArray(&d->images, img); | ||
568 | } | ||
569 | else { | ||
570 | delete_GmImage(img); | ||
571 | } | ||
572 | } | ||
573 | doLayout_GmDocument_(d); | ||
574 | } | ||
575 | |||
480 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | 576 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, |
481 | void *context) { | 577 | void *context) { |
482 | iBool isInside = iFalse; | 578 | iBool isInside = iFalse; |
@@ -579,6 +675,19 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | |||
579 | return white_ColorId; | 675 | return white_ColorId; |
580 | } | 676 | } |
581 | 677 | ||
678 | iBool isMediaLink_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | ||
679 | return (linkFlags_GmDocument(d, linkId) & | ||
680 | (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0; | ||
681 | } | ||
682 | |||
683 | SDL_Texture *imageTexture_GmDocument(const iGmDocument *d, uint16_t imageId) { | ||
684 | if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { | ||
685 | const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); | ||
686 | return img->texture; | ||
687 | } | ||
688 | return NULL; | ||
689 | } | ||
690 | |||
582 | const iString *title_GmDocument(const iGmDocument *d) { | 691 | const iString *title_GmDocument(const iGmDocument *d) { |
583 | return &d->title; | 692 | return &d->title; |
584 | } | 693 | } |