markup: wip markup parser and rendering
This commit is contained in:
parent
427b1559db
commit
0f683d32de
192
apps/markup.c
Normal file
192
apps/markup.c
Normal file
@ -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
base/usr/include/toaru/markup.h
Normal file
23
base/usr/include/toaru/markup.h
Normal file
@ -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
lib/markup.c
Normal file
208
lib/markup.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>']),
|
||||
|
Loading…
Reference in New Issue
Block a user