summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 22:26:40 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 22:26:40 +0300
commit896c024b4ff1a44affe3d749e09e7b7e8c31970c (patch)
tree78f8c81d30b5ac0d8143fd142b40a82ab7db4508
parentd623d42d937d8e7f90deda3f49fe1eb680a7b15e (diff)
Added GmDocument; very basic layout and render
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/gemini.h24
-rw-r--r--src/gmdocument.c78
-rw-r--r--src/gmdocument.h28
-rw-r--r--src/ui/documentwidget.c67
-rw-r--r--src/ui/text.c4
-rw-r--r--src/ui/text.h3
8 files changed, 195 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index 59577bd5..1d5e5a5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
1.DS_Store 1.DS_Store
2*.user 2*.user
3build-*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3d1d9b3..8498cd6e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,9 @@ set (SOURCES
31 src/main.c 31 src/main.c
32 src/app.c 32 src/app.c
33 src/app.h 33 src/app.h
34 src/gemini.h
35 src/gmdocument.h
36 src/gmdocument.c
34 src/stb_image.h 37 src/stb_image.h
35 src/stb_truetype.h 38 src/stb_truetype.h
36 # User interface: 39 # User interface:
diff --git a/src/gemini.h b/src/gemini.h
new file mode 100644
index 00000000..dd281e20
--- /dev/null
+++ b/src/gemini.h
@@ -0,0 +1,24 @@
1#pragma once
2
3/* Response status codes. */
4enum iGmStatusCode {
5 none_GmStatusCode = 0,
6 input_GmStatusCode = 10,
7 sensitiveInput_GmStatusCode = 11,
8 success_GmStatusCode = 20,
9 redirectTemporary_GmStatusCode = 30,
10 redirectPermanent_GmStatusCode = 31,
11 temporaryFailure_GmStatusCode = 40,
12 serverUnavailable_GmStatusCode = 41,
13 cgiError_GmStatusCode = 42,
14 proxyError_GmStatusCode = 43,
15 slowDown_GmStatusCode = 44,
16 permanentFailure_GmStatusCode = 50,
17 notFound_GmStatusCode = 51,
18 gone_GmStatusCode = 52,
19 proxyRequestRefused_GmStatusCode = 53,
20 badRequest_GmStatusCode = 59,
21 clientCertificateRequired_GmStatusCode = 60,
22 certificateNotAuthorized_GmStatusCode = 61,
23 certificateNotValid_GmStatusCode = 62,
24};
diff --git a/src/gmdocument.c b/src/gmdocument.c
new file mode 100644
index 00000000..2c12a5a9
--- /dev/null
+++ b/src/gmdocument.c
@@ -0,0 +1,78 @@
1#include "gmdocument.h"
2#include "ui/color.h"
3#include "ui/text.h"
4#include <the_Foundation/array.h>
5
6struct Impl_GmDocument {
7 iObject object;
8 iString source;
9 iInt2 size;
10 iArray layout; /* contents of source, laid out in document space */
11};
12
13iDefineObjectConstruction(GmDocument)
14
15static void doLayout_GmDocument_(iGmDocument *d) {
16 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
17 return;
18 }
19 clear_Array(&d->layout);
20 iBool isPreformat = iFalse;
21 iInt2 pos = zero_I2();
22 const iRangecc content = range_String(&d->source);
23 iRangecc line = iNullRange;
24 while (nextSplit_Rangecc(&content, "\n", &line)) {
25 iGmRun run;
26 run.text = line;
27 run.font = paragraph_FontId;
28 run.color = white_ColorId;
29 run.bounds.pos = pos;
30 run.bounds.size = advanceN_Text(run.font, line.start, size_Range(&line));
31 run.linkId = 0;
32 pushBack_Array(&d->layout, &run);
33 pos.y += run.bounds.size.y;
34 }
35 d->size.y = pos.y;
36}
37
38void init_GmDocument(iGmDocument *d) {
39 init_String(&d->source);
40 d->size = zero_I2();
41 init_Array(&d->layout, sizeof(iGmRun));
42}
43
44void deinit_GmDocument(iGmDocument *d) {
45 deinit_Array(&d->layout);
46 deinit_String(&d->source);
47}
48
49void setWidth_GmDocument(iGmDocument *d, int width) {
50 d->size.x = width;
51 doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */
52}
53
54void setSource_GmDocument(iGmDocument *d, const iString *source, int width) {
55 set_String(&d->source, source);
56 setWidth_GmDocument(d, width);
57 /* TODO: just flag need-layout and do it later */
58}
59
60void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render,
61 void *context) {
62 iBool isInside = iFalse;
63 iConstForEach(Array, i, &d->layout) {
64 const iGmRun *run = i.value;
65 if (isInside) {
66 if (top_Rect(run->bounds) > visRangeY.end) {
67 break;
68 }
69 render(context, run);
70 }
71 else if (bottom_Rect(run->bounds) >= visRangeY.start) {
72 isInside = iTrue;
73 render(context, run);
74 }
75 }
76}
77
78iDefineClass(GmDocument)
diff --git a/src/gmdocument.h b/src/gmdocument.h
new file mode 100644
index 00000000..dfad1d46
--- /dev/null
+++ b/src/gmdocument.h
@@ -0,0 +1,28 @@
1#pragma once
2
3#include "gemini.h"
4#include <the_Foundation/object.h>
5#include <the_Foundation/rect.h>
6#include <the_Foundation/string.h>
7
8iDeclareType(GmRun)
9
10struct Impl_GmRun {
11 iRangecc text;
12 iRect bounds; /* advance metrics */
13 uint8_t font;
14 uint8_t color;
15 uint16_t linkId;
16};
17
18iDeclareType(GmDocument)
19iDeclareClass(GmDocument)
20
21iDeclareObjectConstruction(GmDocument)
22
23void setWidth_GmDocument (iGmDocument *, int width);
24void setSource_GmDocument (iGmDocument *, const iString *source, int width);
25
26typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
27
28void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index e8a0ded9..aab010c9 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1,5 +1,8 @@
1#include "documentwidget.h" 1#include "documentwidget.h"
2#include "paint.h" 2#include "paint.h"
3#include "util.h"
4#include "../gemini.h"
5#include "../gmdocument.h"
3 6
4#include <the_Foundation/regexp.h> 7#include <the_Foundation/regexp.h>
5#include <the_Foundation/tlsrequest.h> 8#include <the_Foundation/tlsrequest.h>
@@ -15,9 +18,10 @@ struct Impl_DocumentWidget {
15 iWidget widget; 18 iWidget widget;
16 enum iDocumentState state; 19 enum iDocumentState state;
17 iString *url; 20 iString *url;
18 iString *source;
19 int statusCode;
20 iTlsRequest *request; 21 iTlsRequest *request;
22 int statusCode;
23 iString *newSource;
24 iGmDocument *doc;
21}; 25};
22 26
23iDeclareType(Url) 27iDeclareType(Url)
@@ -31,7 +35,8 @@ struct Impl_Url {
31}; 35};
32 36
33void init_Url(iUrl *d, const iString *text) { 37void init_Url(iUrl *d, const iString *text) {
34 iRegExp *pattern = new_RegExp("(.+)://([^/:?]+)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption); 38 iRegExp *pattern =
39 new_RegExp("(.+)://([^/:?]+)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption);
35 iRegExpMatch m; 40 iRegExpMatch m;
36 if (matchString_RegExp(pattern, text, &m)) { 41 if (matchString_RegExp(pattern, text, &m)) {
37 capturedRange_RegExpMatch(&m, 1, &d->protocol); 42 capturedRange_RegExpMatch(&m, 1, &d->protocol);
@@ -59,22 +64,28 @@ void init_DocumentWidget(iDocumentWidget *d) {
59 d->state = blank_DocumentState; 64 d->state = blank_DocumentState;
60 d->url = new_String(); 65 d->url = new_String();
61 d->statusCode = 0; 66 d->statusCode = 0;
62 d->source = new_String();
63 d->request = NULL; 67 d->request = NULL;
64 68 d->newSource = new_String();
69 d->doc = new_GmDocument();
65 setUrl_DocumentWidget(d, collectNewCStr_String("gemini.circumlunar.space/")); 70 setUrl_DocumentWidget(d, collectNewCStr_String("gemini.circumlunar.space/"));
66} 71}
67 72
68void deinit_DocumentWidget(iDocumentWidget *d) { 73void deinit_DocumentWidget(iDocumentWidget *d) {
69 delete_String(d->source);
70 delete_String(d->url); 74 delete_String(d->url);
75 delete_String(d->newSource);
76 iRelease(d->doc);
77}
78
79static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
80 const iWidget *w = constAs_Widget(d);
81 const iRect bounds = bounds_Widget(w);
82 return bounds.size.x;
71} 83}
72 84
73void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 85void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
74 /* TODO: lock source during update */ 86 /* TODO: lock source during update */
75 set_String(d->source, source); 87 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
76 printf("%s\n", cstr_String(d->source)); 88 d->state = ready_DocumentState;
77 d->state = layout_DocumentState;
78} 89}
79 90
80static iRangecc getLine_(iRangecc text) { 91static iRangecc getLine_(iRangecc text) {
@@ -92,10 +103,12 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) {
92 /* First line is the status code. */ { 103 /* First line is the status code. */ {
93 iString *line = newRange_String(respLine); 104 iString *line = newRange_String(respLine);
94 trim_String(line); 105 trim_String(line);
95 printf("response: %s\n", cstr_String(line)); 106 d->statusCode = toInt_String(line);
107 printf("response (%02d): %s\n", d->statusCode, cstr_String(line));
108 /* TODO: post a command with the status code */
96 delete_String(line); 109 delete_String(line);
97 } 110 }
98 setSource_DocumentWidget(d, collect_String(newRange_String(responseRange))); 111 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange));
99 delete_Block(response); 112 delete_Block(response);
100 iReleaseLater(d->request); 113 iReleaseLater(d->request);
101 d->request = NULL; 114 d->request = NULL;
@@ -135,14 +148,44 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
135 148
136static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 149static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
137 iWidget *w = as_Widget(d); 150 iWidget *w = as_Widget(d);
151 if (isResize_UserEvent(ev)) {
152 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
153 }
138 return processEvent_Widget(w, ev); 154 return processEvent_Widget(w, ev);
139} 155}
140 156
157iDeclareType(DrawContext)
158
159struct Impl_DrawContext {
160 const iDocumentWidget *d;
161 iRect bounds;
162 iPaint paint;
163};
164
165static void drawRun_DrawContext_(void *context, const iGmRun *run) {
166 iDrawContext *d = context;
167 iString text;
168 /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */
169 initRange_String(&text, run->text);
170 drawString_Text(run->font, add_I2(d->bounds.pos, run->bounds.pos), run->color, &text);
171 deinit_String(&text);
172}
173
141static void draw_DocumentWidget_(const iDocumentWidget *d) { 174static void draw_DocumentWidget_(const iDocumentWidget *d) {
142 const iWidget *w = constAs_Widget(d); 175 const iWidget *w = constAs_Widget(d);
143 draw_Widget(w); 176 draw_Widget(w);
177 /* Update the document? */
178 if (!isEmpty_String(d->newSource)) {
179 /* TODO: Do this in the background. However, that requires a text metrics calculator
180 that does not try to cache the glyph bitmaps. */
181 setSource_GmDocument(d->doc, d->newSource, documentWidth_DocumentWidget_(d));
182 clear_String(d->newSource);
183 iConstCast(iDocumentWidget *, d)->state = ready_DocumentState;
184 }
144 if (d->state != ready_DocumentState) return; 185 if (d->state != ready_DocumentState) return;
145 /* TODO: lock source during draw */ 186 iDrawContext ctx = {.d = d, .bounds = bounds_Widget(w) };
187 init_Paint(&ctx.paint);
188 render_GmDocument(d->doc, (iRangei){ 0, height_Rect(ctx.bounds) }, drawRun_DrawContext_, &ctx);
146} 189}
147 190
148iBeginDefineSubclass(DocumentWidget, Widget) 191iBeginDefineSubclass(DocumentWidget, Widget)
diff --git a/src/ui/text.c b/src/ui/text.c
index ac211481..edd1f69d 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -484,6 +484,10 @@ void draw_Text(int fontId, iInt2 pos, int color, const char *text, ...) {
484 deinit_Block(&chars); 484 deinit_Block(&chars);
485} 485}
486 486
487void drawString_Text(int fontId, iInt2 pos, int color, const iString *text) {
488 draw_Text_(fontId, pos, color, cstr_String(text));
489}
490
487void drawCentered_Text(int fontId, iRect rect, int color, const char *text, ...) { 491void drawCentered_Text(int fontId, iRect rect, int color, const char *text, ...) {
488 iBlock chars; 492 iBlock chars;
489 init_Block(&chars, 0); { 493 init_Block(&chars, 0); {
diff --git a/src/ui/text.h b/src/ui/text.h
index b689582e..85cd39cf 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -32,9 +32,10 @@ void deinit_Text (void);
32int lineHeight_Text (int font); 32int lineHeight_Text (int font);
33iInt2 measure_Text (int font, const char *text); 33iInt2 measure_Text (int font, const char *text);
34iInt2 advance_Text (int font, const char *text); 34iInt2 advance_Text (int font, const char *text);
35iInt2 advanceN_Text (int font, const char *text, size_t n); 35iInt2 advanceN_Text (int font, const char *text, size_t n); /* `n` in characters */
36 36
37void draw_Text (int font, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */ 37void draw_Text (int font, iInt2 pos, int color, const char *text, ...); /* negative pos to switch alignment */
38void drawString_Text (int font, iInt2 pos, int color, const iString *text);
38void drawCentered_Text (int font, iRect rect, int color, const char *text, ...); 39void drawCentered_Text (int font, iRect rect, int color, const char *text, ...);
39 40
40SDL_Texture * glyphCache_Text (void); 41SDL_Texture * glyphCache_Text (void);