diff --git a/TODO.md b/TODO.md index 1b44d49..859478a 100644 --- a/TODO.md +++ b/TODO.md @@ -28,7 +28,7 @@ * Parametrize character-level and word-level spacing * Be more generic in what nodes have display mode by default * Reduce horizontal spacing by using a proportional font -* Have a decent variable-height font system in gint +* Have a decent variable-height font system in gint (argh) * Add square roots, and honor TEX_SQRT_SLANTED and TEX_SQRT_BAR_LENGTH -* Add matrices +* Make more matrix tests diff --git a/include/TeX/config.h b/include/TeX/config.h index 3b025a7..0566924 100644 --- a/include/TeX/config.h +++ b/include/TeX/config.h @@ -73,4 +73,8 @@ /* Length of the top-right bar of square roots (0 to disable) */ #define TEX_SQRT_BAR_LENGTH 0 +/* Spacing between rows and columns in matrices */ +#define TEX_MATRIX_COL_SPACING 4 +#define TEX_MATRIX_ROW_SPACING 4 + #endif /* TEX_CONFIG */ diff --git a/include/TeX/env.h b/include/TeX/env.h index 940328c..70347f4 100644 --- a/include/TeX/env.h +++ b/include/TeX/env.h @@ -30,8 +30,8 @@ struct TeX_Env /* add_break(): Add a break ("\\") */ void (*add_break)(TeX_Env *env); - /* size(): Compute environment size */ - void (*size)(TeX_Env *env); + /* layout(): Compute environment layout */ + void (*layout)(TeX_Env *env); /* render(): Render environment */ void (*render)(TeX_Env *env, int x, int y, int color); @@ -51,8 +51,15 @@ struct TeX_Env /* TeX_env_primary(): make a primary, single-flow environment This is the environment type of whole formulas. It consists of a single flow and ignores separators and breaks. - Creates and returns a new environment [env] which must be freed by a call - to [env->free(env)]. */ + Creates and returns a new environment which must be freed by TeX_free(). */ struct TeX_Env *TeX_env_primary(void); +/* TeX_env_matrix(): make a variable-size matrix + This environment builds matrices without surrounding delimiters. Separators + are used to separate elements, and breaks mark the end of each line. Rows of + uneven length are all padded with empty elements to the maximum row length + of the whole matrix. + Returns a new environment; free with TeX_free(). */ +struct TeX_Env *TeX_env_matrix(void); + #endif /* TEX_ENV */ diff --git a/include/TeX/node.h b/include/TeX/node.h index 2c010d3..1284ce6 100644 --- a/include/TeX/node.h +++ b/include/TeX/node.h @@ -10,11 +10,13 @@ #include #include -/* A special class number for nodes that contain plain text. Class numbers from - 1 onwards are used by TeX_Class. See also the class array in nodes.c. */ +/* A special class number for nodes that contain plain text. */ #define TEX_NODECLASS_TEXT 0 +/* Special class for environment nodes, also a special case */ +#define TEX_NODECLASS_ENV 1 struct TeX_Flow; +struct TeX_Env; /* TeX_Node This object represents a mathematical construct. It can be plain text, a @@ -28,6 +30,8 @@ struct TeX_Node union { /* Plain text content encoded as UTF-8 */ uint8_t *text; + /* Environment pointer */ + struct TeX_Env *env; /* Functional arguments */ struct TeX_Flow *args[TEX_NODE_MAX_CHILDREN]; }; @@ -75,6 +79,11 @@ struct TeX_Node *TeX_node_add_arg(struct TeX_Node *node, struct TeX_Flow *arg); not freed. Always returns [node]. */ struct TeX_Node *TeX_node_absorb(struct TeX_Node *node, char const *text); +/* TeX_node_env(): Make a environment node + This function creates an node that wraps an environment and inserts it into + a formula. */ +struct TeX_Node *TeX_node_env(struct TeX_Env *env); + /* TeX_node_free(): Free a TeX_Node and all its children */ void TeX_node_free(struct TeX_Node *node); diff --git a/include/TeX/vector.h b/include/TeX/vector.h index c8d8ff2..4d5ee83 100644 --- a/include/TeX/vector.h +++ b/include/TeX/vector.h @@ -1,5 +1,6 @@ //--- // vector: Simple vectors that only grow +// Preprocessing is a bit ugly here, but it makes it really easy to use. //--- #ifndef TEX_VECTOR @@ -8,29 +9,33 @@ #include #include -typedef struct -{ - int len; - int size; - int elsize; +/* Declare three variables for every vector */ +#define vector_type(type, name) \ + type * name; \ + short name ## _len; \ + short name ## _size; - /* This pointer can be cast to the vector type and indexed */ - void *data; -} vector_t; +/* Trivial initialization */ +#define vector_init(name) { \ + name = NULL; \ + name ## _len = 0; \ + name ## _size = 0; \ +} -/* vector_make(): Make a new vector of a given type - @vector Pointer to preallocated vector structure - @type Data type */ -void vector_make(vector_t *vector, size_t elsize); +/* Extension */ +#define vector_extend(name) { \ + if(name ## _len >= name ## _size) \ + name = realloc(name, sizeof *name * \ + (name ## _size += 8)); \ +} -#define vector_make(v, T) vector_make(v, sizeof(T)) +/* Append */ +#define vector_append(name, value) { \ + vector_extend(name); \ + (name)[name ## _len++] = (value); \ +} -/* vector_append(): Append a new element to a vector - @vector Pointer to initialized vector - @element Pointer to new element, which will be *copied* */ -void vector_append(vector_t *v, void *element); - -/* vector_free(): Free a vector's contents */ -void vector_free(vector_t *v); +/* Last elements */ +#define vector_last(name) ((name)[name ## _len - 1]) #endif /* TEX_VECTOR */ diff --git a/src/TeX.c b/src/TeX.c index d6a8d82..92a7843 100644 --- a/src/TeX.c +++ b/src/TeX.c @@ -51,7 +51,7 @@ void TeX_intf_text(void (*draw_text)(char const *str, int x, int y,int color)) /* TeX_free(): Free an allocated TeX formula */ void TeX_free(struct TeX_Env *env) { - env->free(env); + if(env) env->free(env); } //--- @@ -67,7 +67,7 @@ struct TeX_Env *TeX_parse(char const *formula) struct TeX_Env *env = parse(formula); if(!env) return NULL; - env->size(env); + env->layout(env); return env; } diff --git a/src/TeX.y b/src/TeX.y index 12bf030..380efe4 100644 --- a/src/TeX.y +++ b/src/TeX.y @@ -30,9 +30,8 @@ void env_push(char const *type) if(!strcmp(type, "primary")) new_env = TeX_env_primary(); - /* TODO: Matrix environment creation if(!strcmp(type, "matrix")) - new_env = TeX_env_matrix(); */ + new_env = TeX_env_matrix(); /* If new_env is NULL... still insert it. This is more likely to cause an error, and avoids asymmetry with pops */ @@ -82,10 +81,6 @@ struct TeX_Node *mknode_t(char const *name, char const *text) %token ENV_BEGIN %token ENV_END -/* Separators ("&") and breaks ("\\") */ -%token SEPARATOR -%token BREAK - %left '^' '_' %type flow @@ -99,8 +94,8 @@ struct TeX_Node *mknode_t(char const *name, char const *text) env: %empty { } | env node { env->add_node(env, $2); } - | env SEPARATOR { env->add_separator(env); } - | env BREAK { env->add_break(env); } + | env '&' { env->add_separator(env); } + | env '\\' { env->add_break(env); } flow: %empty { $$ = NULL; } @@ -124,7 +119,7 @@ node_abs: env_node: /* TODO: Add TeX_mknode_env() */ - env_begin env env_end { $$ = NULL; } //TeX_mknode_env($3); } + env_begin env env_end { $$ = TeX_node_env($3); } env_begin: ENV_BEGIN '{' TEXT '}' { env_push($3); } @@ -191,6 +186,8 @@ static enum { subscript = '_', lbrace = '{', rbrace = '}', + break_symbol = '\\', + separator = '&', } state = text; /* Single-character mode. When a command name, '^' or '_' is not followed by a @@ -318,9 +315,17 @@ static int lexer_text(void) return release(TEXT); } + /* Breaks */ + if(la == '\\') + { + la = *lex++; + state = '\\'; + return release(TEXT); + } + /* Escaped character: accumulate lookahead and feed lexer. Feeding is safe because current lookahead is not EOF */ - if(strchr("\\{}^_", la)) + if(strchr("{}^_&", la)) { c = la; la = *lex++; @@ -333,7 +338,7 @@ static int lexer_text(void) } /* Opening and closing braces are always syntactic elements */ - else if(c == '{' || c == '}') + else if(c == '{' || c == '}' || c == '&') { state = c; return release(TEXT); @@ -399,6 +404,12 @@ static int lexer_command(void) return release(COMMAND_ABS); } + /* Special environment commands */ + if(!acccmp("begin")) + return release(ENV_BEGIN); + if(!acccmp("end")) + return release(ENV_END); + return release(COMMAND); } diff --git a/src/classes.c b/src/classes.c index 8b527c8..602be7a 100644 --- a/src/classes.c +++ b/src/classes.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,24 @@ void text_render(struct TeX_Node const * node, int x, int y, int color) TeX_text((void *)node->text, x, y, color); } +//--- +// Environment nodes. +//--- + +void env_layout(struct TeX_Node *node) +{ + node->env->layout(node->env); + + node->width = node->env->width; + node->height = node->env->height; + node->line = node->env->line; +} + +void env_render(struct TeX_Node *node, int x, int y, int color) +{ + node->env->render(node->env, x, y, color); +} + //--- // Fractions. // * args: 2 (<2 invalidate, >2 ignore) @@ -416,8 +435,9 @@ void lim_render(__attribute__((unused)) struct TeX_Node const * node, int x, //--- static struct TeX_Class const class_table[] = { - /* Text has ID 0 */ + /* Text and environments */ { "\\text", text_layout, text_render }, + { "\\end", env_layout, env_render }, /* Fractions */ { "frac", frac_layout, frac_render }, diff --git a/src/env.c b/src/env.c index 445bb9a..dae0856 100644 --- a/src/env.c +++ b/src/env.c @@ -2,9 +2,20 @@ #include #include +#include + #include #include +#define fill_prototype(env, prefix) { \ + env->free = prefix ## _free; \ + env->add_node = prefix ## _add_node; \ + env->add_separator = prefix ## _add_separator; \ + env->add_break = prefix ## _add_break; \ + env->layout = prefix ## _layout; \ + env->render = prefix ## _render; \ +} + //--- // Primary environment: a simple flow // @@ -24,6 +35,7 @@ static void primary_free(struct TeX_Env *env) { struct TeX_Env_Primary *p = (void *)env; TeX_flow_free(p->flow); + free(env); } static void primary_add_node(struct TeX_Env *env, struct TeX_Node *node) @@ -32,14 +44,22 @@ static void primary_add_node(struct TeX_Env *env, struct TeX_Node *node) p->flow = TeX_flow_add_node(p->flow, node); } -static void primary_size(struct TeX_Env *env) +static void primary_add_separator(__attribute__((unused)) struct TeX_Env *env) +{ +} + +static void primary_add_break(__attribute__((unused)) struct TeX_Env *env) +{ +} + +static void primary_layout(struct TeX_Env *env) { struct TeX_Env_Primary *p = (void *)env; TeX_flow_layout(p->flow); - p->env.width = p->flow->width; - p->env.height = p->flow->height; - p->env.line = p->flow->line; + env->width = p->flow->width; + env->height = p->flow->height; + env->line = p->flow->line; } static void primary_render(struct TeX_Env *env, int x, int y, int color) @@ -54,12 +74,7 @@ struct TeX_Env *TeX_env_primary(void) struct TeX_Env *env = (void *)p; env->name = "primary"; - env->free = primary_free; - env->add_node = primary_add_node; - env->add_separator = NULL; - env->add_break = NULL; - env->size = primary_size; - env->render = primary_render; + fill_prototype(env, primary); p->flow = NULL; @@ -70,7 +85,175 @@ struct TeX_Env *TeX_env_primary(void) // Matrix environment. //--- -/* TODO */ +struct TeX_Env_Matrix +{ + struct TeX_Env env; + + vector_type(struct TeX_Flow *, elements); + vector_type(int, rowstart); + + int *rowdepth; + int *rowline; + int *colwidth; + + int rows; + int cols; +}; + +static void matrix_free(struct TeX_Env *env) +{ + struct TeX_Env_Matrix *m = (void *)env; + + free(m->elements); + free(m->rowstart); + free(m->rowdepth); + free(m->rowline); + free(m->colwidth); + + free(env); +} + +static void matrix_add_node(struct TeX_Env *env, struct TeX_Node *node) +{ + struct TeX_Env_Matrix *m = (void *)env; + + int last = m->elements_len - 1; + m->elements[last] = TeX_flow_add_node(m->elements[last], node); +} + +static void matrix_add_separator(struct TeX_Env *env) +{ + struct TeX_Env_Matrix *m = (void *)env; + + /* Create a new flow at the end of the element array */ + vector_append(m->elements, NULL); + + /* Current row length is automatically increased. Set column count */ + int row_length = m->elements_len - m->rowstart[m->rows - 1]; + m->cols = max(m->cols, row_length); +} + +static void matrix_add_break(struct TeX_Env *env) +{ + struct TeX_Env_Matrix *m = (void *)env; + + /* First add a new flow */ + vector_append(m->elements, NULL); + + /* Then add a row and its start index */ + vector_append(m->rowstart, m->elements_len - 1); + m->rows++; +} + +static void matrix_layout(struct TeX_Env *env) +{ + struct TeX_Env_Matrix *m = (void *)env; + + int row = 0; + int col = 0; + + env->width = 0; + env->height = 0; + env->line = 0; + + m->rowdepth = calloc(m->rows, sizeof *m->rowdepth); + m->rowline = calloc(m->rows, sizeof *m->rowline); + m->colwidth = calloc(m->cols, sizeof *m->colwidth); + if(!m->rowdepth || !m->rowline || !m->colwidth) return; + + /* Compute the layout of the grid */ + for(int i = 0; i < m->elements_len; i++) + { + if(row + 1 < m->rows && i == m->rowstart[row + 1]) + { + col = 0; + row++; + } + + struct TeX_Flow *f = m->elements[i]; + TeX_flow_layout(f); + + /* Update the current row and column */ + m->rowline[row] = max(m->rowline[row], f->line); + m->rowdepth[row] = max(m->rowdepth[row], f->height - f->line); + m->colwidth[col] = max(m->colwidth[col], f->width); + + col++; + } + + /* TODO: Add width spacing and height spacing */ + + for(int i = 0; i < m->rows; i++) + { + env->height += m->rowline[i] + m->rowdepth[i]; + } + for(int j = 0; j < m->cols; j++) + { + env->width += m->colwidth[j]; + } + + env->width += TEX_MATRIX_COL_SPACING * (m->cols - 1); + env->height += TEX_MATRIX_ROW_SPACING * (m->rows - 1); + env->line = env->height >> 1; +} + +static void matrix_render(struct TeX_Env *env, int x, int y, int color) +{ + struct TeX_Env_Matrix *m = (void *)env; + + int n = 0, dy = 0; + + for(int row = 0; row < m->rows; row++) + { + int col = 0, dx = 0; + + while((row + 1 < m->rows && n < m->rowstart[row + 1]) || + (row + 1 >= m->rows && n < m->elements_len)) + { + struct TeX_Flow *flow = m->elements[n++]; + int cw = m->colwidth[col]; + int rh = m->rowline[row] + m->rowdepth[row]; + + TeX_flow_render(flow, + x + dx + ((cw - flow->width) >> 1), + y + dy + ((rh - flow->height) >> 1), + color); + + dx += cw + TEX_MATRIX_COL_SPACING; + col++; + } + + dy += m->rowline[row]+m->rowdepth[row]+TEX_MATRIX_ROW_SPACING; + } +} + +struct TeX_Env *TeX_env_matrix(void) +{ + struct TeX_Env_Matrix *m = malloc(sizeof *m); + struct TeX_Env *env = (void *)m; + + env->name = "matrix"; + fill_prototype(env, matrix); + + vector_init(m->elements); + vector_init(m->rowstart); + m->rowdepth = NULL; + m->rowline = NULL; + m->colwidth = NULL; + + vector_extend(m->elements); + m->elements[0] = NULL; + m->elements_len = 1; + + vector_extend(m->rowstart); + m->rowstart[0] = 0; + m->rowstart_len = 1; + + m->rows = 1; + m->cols = 0; + + return env; +} //--- // Environment printers @@ -86,10 +269,39 @@ void TeX_print_env_primary(struct TeX_Env *env, int indent) { struct TeX_Env_Primary *p = (void *)env; - printf("%*s", indent, ""); - printf("{primary}\n"); + printf("%*senv:primary", indent, ""); + printf(GRAY " %dx%d,%d" END "\n", env->width, env->height, env->line); - TeX_print_flow(p->flow, indent + 2); + TeX_print_flow(p->flow, indent + 4); +} + +/* TeX_print_env_matrix(): Print a matrix environment to stdout */ +void TeX_print_env_matrix(struct TeX_Env *env, int indent) +{ + struct TeX_Env_Matrix *m = (void *)env; + + printf("%*senv:matrix %dx%d", indent, "", m->rows, m->cols); + printf(GRAY " %dx%d,%d", env->width, env->height, env->line); + + for(int i = 0; i < m->cols; i++) + { + printf("%c%d", (i ? ';' : ' '), m->colwidth[i]); + } + printf(END "\n"); + + int row = 0; + for(int i = 0; i < m->elements_len; i++) + { + if(row < m->rows && i == m->rowstart[row]) + { + printf("%*s%d: " GRAY "%d,%d" END "\n", indent + 4, "", + row, m->rowline[row] + m->rowdepth[row], + m->rowline[row]); + row++; + } + + TeX_print_flow(m->elements[i], indent + 8); + } } /* TeX_print_env(): Recursively print an environment */ @@ -99,9 +311,13 @@ void TeX_print_env(struct TeX_Env *env, int indent) { TeX_print_env_primary(env, indent); } + else if(!strcmp(env->name, "matrix")) + { + TeX_print_env_matrix(env, indent); + } else { - printf("%*s{%s} (?)\n", indent, "", env->name); + printf("%*senv:%s (?)\n", indent, "", env->name); } } diff --git a/src/node.c b/src/node.c index 9603807..6b26358 100644 --- a/src/node.c +++ b/src/node.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,10 @@ // Node creation and destruction functions //--- +#define special(node) \ + ((node)->type == TEX_NODECLASS_TEXT || \ + (node)->type == TEX_NODECLASS_ENV) + /* TeX_node_text(): Make a text node */ struct TeX_Node *TeX_node_text(char const *utf8) { @@ -45,8 +50,8 @@ struct TeX_Node *TeX_node_add_arg(struct TeX_Node *node, struct TeX_Flow *arg) { if(!node || !arg) return node; - /* Drop the argument if it's for a plain text node */ - if(node->type == TEX_NODECLASS_TEXT) + /* Drop the argument if it's for a plain text or environment node */ + if(special(node)) { TeX_flow_free(arg); return node; @@ -77,12 +82,27 @@ struct TeX_Node *TeX_node_absorb(struct TeX_Node *node, char const *text) TeX_node_text(text))); } +/* TeX_node_env(): Make a environment node */ +struct TeX_Node *TeX_node_env(struct TeX_Env *env) +{ + struct TeX_Node *node = calloc(1, sizeof *node); + if(!node) return NULL; + + node->env = env; + node->type = TEX_NODECLASS_ENV; + + return node; +} + /* TeX_node_free(): Free a TeX_Node and all its children */ void TeX_node_free(struct TeX_Node *node) { /* Text nodes: free the allocated string */ if(node->type == TEX_NODECLASS_TEXT) free(node->text); + /* Environment nodes: free the environment */ + else if(node->type == TEX_NODECLASS_ENV) node->env->free(node->env); + /* Class nodes: recursively free children */ else for(int i = 0; i < TEX_NODE_MAX_CHILDREN; i++) { @@ -102,7 +122,7 @@ void TeX_node_free(struct TeX_Node *node) void TeX_node_layout(struct TeX_Node *node) { /* First compute the layout of the children */ - if(node->type != TEX_NODECLASS_TEXT) + if(!special(node)) for(int i = 0; i < TEX_NODE_MAX_CHILDREN && node->args[i]; i++) TeX_flow_layout(node->args[i]); @@ -141,6 +161,13 @@ void TeX_print_node(struct TeX_Node *node, int indent) printf(GRAY " %dx%d,%d %+d%+d" END "\n", node->width, node->height, node->line, node->x, node->l); } + else if(node->type == TEX_NODECLASS_ENV) + { + printf("\\env" GRAY " %dx%d,%d %+d%+d" END "\n", node->width, + node->height, node->line, node->x, node->l); + + TeX_print_env(node->env, indent + 4); + } else { printf("<%s>", TeX_class_of(node)->name); diff --git a/src/platform/sdl2.c b/src/platform/sdl2.c index 11307db..0cfe847 100644 --- a/src/platform/sdl2.c +++ b/src/platform/sdl2.c @@ -169,7 +169,11 @@ int main(void) char const * formula = "\\frac{x^7\\left[X,Y\\right]+3\\left|\\frac{A}{B}\\right>}" "{\\left\\{\\frac{a_k+b_k}{k!}\\right\\}^5}" - " + \\int_a^b\\frac{\\left(b-t\\right)^{n+1}}{n!}dt"; + " + \\int_a^b\\frac{\\left(b-t\\right)^{n+1}}{n!}dt" + " + \\left(\\begin{matrix}" + "\\frac{1}{2}&5\\\\" + "-1&a+b" + "\\end{matrix}\\right)"; struct TeX_Env *env = TeX_parse(formula); if(!env) { puts("parsing error!"); return 1; } diff --git a/src/vector.c b/src/vector.c deleted file mode 100644 index 99721b3..0000000 --- a/src/vector.c +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -#undef vector_make - -/* vector_make(): Make a new vector of a given type */ -void vector_make(vector_t *v, size_t elsize) -{ - v->len = 0; - v->size = 0; - v->elsize = elsize; - v->data = NULL; -} - -/* vector_append(): Append a new element to a vector */ -void vector_append(vector_t *v, void *element) -{ - /* Realloc whenever the vector runs out of space */ - if(v->len >= v->size) - { - v->data = realloc(v->data, (v->size + 8) * v->elsize); - v->size += 8; - } - - memcpy(v->data + v->len * v->elsize, element, v->elsize); - v->len++; -} - -/* vector_free(): Free a vector's contents */ -void vector_free(vector_t *v) -{ - free(v->data); -}