Browse Source

markup: wip markup parser and rendering

K. Lange 2 years ago
parent
commit
0f683d32de
4 changed files with 424 additions and 0 deletions
  1. 192 0
      apps/markup.c
  2. 23 0
      base/usr/include/toaru/markup.h
  3. 208 0
      lib/markup.c
  4. 1 0
      util/auto-dep.py

+ 192 - 0
apps/markup.c

@@ -0,0 +1,192 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * marked up text demo
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include <toaru/yutani.h>
+#include <toaru/graphics.h>
+#include <toaru/decorations.h>
+#include <toaru/sdf.h>
+#include <toaru/markup.h>
+
+/* Pointer to graphics memory */
+static yutani_t * yctx;
+static yutani_window_t * window = NULL;
+static gfx_context_t * ctx = NULL;
+
+static int width = 500;
+static int height = 500;
+
+static int left = 200;
+static int top = 200;
+
+static int size = 16;
+
+static void decors() {
+	render_decorations(window, ctx, "Markup Demo");
+}
+
+static int cursor_x = 0;
+static int state = 0;
+
+static int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) {
+	if (!strcmp(tag->name, "b")) {
+		state = 1; /* State append bold */
+	}
+	markup_free_tag(tag);
+	return 0;
+}
+
+static int parser_close(struct markup_state * self, void * user, char * tag_name) {
+	if (!strcmp(tag_name, "b")) {
+		state = 0; /* State pop bold */
+	}
+	return 0;
+}
+
+static int parser_data(struct markup_state * self, void * user, char * data) {
+	cursor_x += draw_sdf_string(ctx, cursor_x, 30, data, size, rgb(0,0,0), state ? SDF_FONT_BOLD : SDF_FONT_THIN);
+	return 0;
+}
+
+
+void redraw() {
+	draw_fill(ctx, rgb(255,255,255));
+
+	decors();
+
+	struct markup_state * parser = markup_init(NULL, parser_open, parser_close, parser_data);
+
+	char * str = "<b>This <i foo=bar baz=qux>is</i> a test</b> with <data fun=123>data</data> at the end";
+	cursor_x = 20;
+	state = 0;
+
+	while (*str) {
+		//fprintf(stderr, "Parser state in: %d  Character: %c\n", parser->state, *str);
+		if (markup_parse(parser, *str++)) {
+			fprintf(stderr, "bailing\n");
+			return;
+		}
+	}
+	markup_finish(parser);
+
+}
+
+void resize_finish(int w, int h) {
+	yutani_window_resize_accept(yctx, window, w, h);
+	reinit_graphics_yutani(ctx, window);
+
+	struct decor_bounds bounds;
+	decor_get_bounds(window, &bounds);
+
+	width  = w - bounds.left_width - bounds.right_width;
+	height = h - bounds.top_height - bounds.bottom_height;
+
+	redraw();
+
+	yutani_window_resize_done(yctx, window);
+	yutani_flip(yctx, window);
+}
+
+
+int main(int argc, char * argv[]) {
+
+	yctx = yutani_init();
+	if (!yctx) {
+		fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]);
+		return 1;
+	}
+	init_decorations();
+
+	struct decor_bounds bounds;
+	decor_get_bounds(NULL, &bounds);
+
+	window = yutani_window_create(yctx, width + bounds.width, height + bounds.height);
+	yutani_window_move(yctx, window, left, top);
+
+	yutani_window_advertise_icon(yctx, window, "SDF Demo", "sdf");
+
+	ctx = init_graphics_yutani(window);
+
+	redraw();
+	yutani_flip(yctx, window);
+
+	int playing = 1;
+	while (playing) {
+		yutani_msg_t * m = yutani_poll(yctx);
+		if (m) {
+			switch (m->type) {
+				case YUTANI_MSG_KEY_EVENT:
+					{
+						struct yutani_msg_key_event * ke = (void*)m->data;
+						if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {
+							playing = 0;
+						} else if (ke->event.action == KEY_ACTION_DOWN) {
+							if (size <= 20) {
+								size += 1;
+							} else if (size > 20) {
+								size += 5;
+							}
+							if (size > 100) {
+								size = 1;
+							}
+							redraw();
+							yutani_flip(yctx,window);
+						}
+					}
+					break;
+				case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
+					{
+						struct yutani_msg_window_focus_change * wf = (void*)m->data;
+						yutani_window_t * win = hashmap_get(yctx->windows, (void*)wf->wid);
+						if (win) {
+							win->focused = wf->focused;
+							decors();
+							yutani_flip(yctx, window);
+						}
+					}
+					break;
+				case YUTANI_MSG_RESIZE_OFFER:
+					{
+						struct yutani_msg_window_resize * wr = (void*)m->data;
+						resize_finish(wr->width, wr->height);
+					}
+					break;
+				case YUTANI_MSG_WINDOW_MOUSE_EVENT:
+					{
+						int result = decor_handle_event(yctx, m);
+						switch (result) {
+							case DECOR_CLOSE:
+								playing = 0;
+								break;
+							default:
+								/* Other actions */
+								break;
+						}
+					}
+					break;
+				case YUTANI_MSG_SESSION_END:
+					playing = 0;
+					break;
+				default:
+					break;
+			}
+		}
+		free(m);
+	}
+
+	yutani_close(yctx, window);
+
+	return 0;
+}
+

+ 23 - 0
base/usr/include/toaru/markup.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <_cheader.h>
+#include <toaru/hashmap.h>
+
+_Begin_C_Header
+
+struct markup_tag {
+	char * name;
+	hashmap_t * options;
+};
+
+struct markup_state;
+typedef int (*markup_callback_tag_open)(struct markup_state * self, void * user, struct markup_tag * tag);
+typedef int (*markup_callback_tag_close)(struct markup_state * self, void * user, char * tag_name);
+typedef int (*markup_callback_data)(struct markup_state * self, void * user, char * data);
+
+extern struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data);
+extern int markup_free_tag(struct markup_tag * tag);
+extern int markup_parse(struct markup_state * state, char c);
+extern int markup_finish(struct markup_state * state);
+
+_End_C_Header

+ 208 - 0
lib/markup.c

@@ -0,0 +1,208 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * Markup parser.
+ */
+#include <stdio.h>
+#include <toaru/markup.h>
+
+struct markup_state {
+	int state;
+	void * user;
+	markup_callback_tag_open  callback_tag_open;
+	markup_callback_tag_close callback_tag_close;
+	markup_callback_data  callback_data;
+
+	/* Private stuff */
+	struct markup_tag tag;
+	size_t len;
+	char data[64];
+	char * attr;
+};
+
+struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data) {
+	struct markup_state * out = malloc(sizeof(out));
+
+	out->state = 0;
+	out->user = user;
+	out->len = 0;
+
+	out->callback_tag_open  = open;
+	out->callback_tag_close = close;
+	out->callback_data  = data;
+
+	return out;
+}
+
+static void _dump_buffer(struct markup_state * state) {
+	if (state->len) {
+		state->data[state->len] = '\0';
+		state->callback_data(state, state->user, state->data);
+		state->data[0] = '\0';
+		state->len = 0;
+	}
+}
+
+static void _finish_name(struct markup_state * state) {
+	state->data[state->len] = '\0';
+	state->tag.name = strdup(state->data);
+	state->tag.options = hashmap_create(5);
+	state->data[0] = '\0';
+	state->len = 0;
+	state->state = 2;
+}
+
+static void _finish_close(struct markup_state * state) {
+	state->data[state->len] = '\0';
+	state->callback_tag_close(state, state->user, state->data);
+	state->data[0] = '\0';
+	state->len = 0;
+	state->state = 0;
+}
+
+static void _finish_tag(struct markup_state * state) {
+	state->callback_tag_open(state, state->user, &state->tag);
+	state->state = 0;
+}
+
+static void _finish_bare_attr(struct markup_state * state) {
+	state->data[state->len] = '\0';
+	hashmap_set(state->tag.options, state->data, strdup(state->data));
+	state->data[0] = '\0';
+	state->len = 0;
+}
+
+static void _finish_attr(struct markup_state * state) {
+	state->data[state->len] = '\0';
+	state->attr = strdup(state->data);
+	state->data[0] = '\0';
+	state->len = 0;
+	state->state = 4;
+}
+
+static void _finish_attr_value(struct markup_state * state) {
+	state->data[state->len] = '\0';
+	hashmap_set(state->tag.options, state->attr, strdup(state->data));
+	free(state->attr);
+	state->data[0] = '\0';
+	state->len = 0;
+	state->state = 2;
+}
+
+int markup_free_tag(struct markup_tag * tag) {
+	free(tag->name);
+	list_t * keys = hashmap_keys(tag->options);
+	if (keys->length) {
+		foreach(node, keys) {
+			free(hashmap_get(tag->options, node->value));
+		}
+	}
+	list_free(keys);
+	free(keys);
+	hashmap_free(tag->options);
+	return 0;
+}
+
+int markup_parse(struct markup_state * state, char c) {
+	switch (state->state) {
+		case 0: /* STATE_NORMAL */
+			if (state->len == 63) {
+				_dump_buffer(state);
+			}
+			switch (c) {
+				case '<':
+					_dump_buffer(state);
+					state->state = 1;
+					return 0;
+				default:
+					state->data[state->len] = c;
+					state->len++;
+					return 0;
+			}
+			break;
+		case 1: /* STATE_TAG_OPEN */
+			switch (c) {
+				case '/':
+					if (state->len) {
+						fprintf(stderr, "syntax error\n");
+						return 1;
+					}
+					state->state = 3; /* STATE_TAG_CLOSE */
+					return 0;
+				case '>':
+					_finish_name(state);
+					_finish_tag(state);
+					return 0;
+				case ' ':
+					_finish_name(state);
+					return 0;
+				default:
+					state->data[state->len] = c;
+					state->len++;
+					return 0;
+			}
+			break;
+		case 2: /* STATE_TAG_ATTRIB */
+			switch (c) {
+				case ' ': /* attribute has no value, end it and append it with = self */
+					_finish_bare_attr(state);
+					return 0;
+				case '>':
+					_finish_bare_attr(state);
+					_finish_tag(state);
+					return 0;
+				case '=': /* attribute has a value, go to next mode */
+					_finish_attr(state);
+					return 0;
+				default:
+					state->data[state->len] = c;
+					state->len++;
+					return 0;
+			}
+			return 0;
+		case 3: /* STATE_TAG_CLOSE */
+			switch (c) {
+				case '>':
+					_finish_close(state);
+					return 0;
+				default:
+					state->data[state->len] = c;
+					state->len++;
+					return 0;
+			}
+			break;
+		case 4: /* STATE_ATTR_VALUE */
+			switch (c) {
+				case ' ':
+					_finish_attr_value(state);
+					return 0;
+				case '>':
+					_finish_attr_value(state);
+					_finish_tag(state);
+					return 0;
+				default:
+					state->data[state->len] = c;
+					state->len++;
+					return 0;
+			}
+			break;
+		default:
+			fprintf(stderr, "parser in unknown state\n");
+			return 1;
+	}
+	return 0;
+}
+
+int markup_finish(struct markup_state * state) {
+	if (state->state != 0) {
+		fprintf(stderr, "unexpected end of data\n");
+		return 1;
+	} else {
+		_dump_buffer(state);
+		free(state);
+		return 0;
+	}
+}
+

+ 1 - 0
util/auto-dep.py

@@ -23,6 +23,7 @@ class Classifier(object):
         '<toaru/rline.h>':       (None, '-ltoaru_rline',       ['<toaru/kbd.h>']),
         '<toaru/rline_exp.h>':   (None, '-ltoaru_rline_exp',   ['<toaru/rline.h>']),
         '<toaru/confreader.h>':  (None, '-ltoaru_confreader',  ['<toaru/hashmap.h>']),
+        '<toaru/markup.h>':      (None, '-ltoaru_markup',       ['<toaru/hashmap.h>']),
         '<toaru/yutani.h>':      (None, '-ltoaru_yutani',      ['<toaru/kbd.h>', '<toaru/list.h>', '<toaru/pex.h>', '<toaru/graphics.h>', '<toaru/hashmap.h>']),
         '<toaru/decorations.h>': (None, '-ltoaru_decorations', ['<toaru/menu.h>', '<toaru/sdf.h>', '<toaru/graphics.h>', '<toaru/yutani.h>']),
         '<toaru/termemu.h>':     (None, '-ltoaru_termemu',     ['<toaru/graphics.h>']),