add environment nodes and matrices

This commit is contained in:
Lephenixnoir 2019-06-17 21:27:04 -04:00
parent 97b60443f8
commit d54f17fab2
12 changed files with 364 additions and 95 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -10,11 +10,13 @@
#include <stddef.h>
#include <stdint.h>
/* 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);

View File

@ -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 <stdint.h>
#include <stddef.h>
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 */

View File

@ -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;
}

View File

@ -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 <struct TeX_Flow *> 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);
}

View File

@ -5,6 +5,7 @@
#include <TeX/config.h>
#include <TeX/node.h>
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <TeX/interface.h>
@ -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 },

246
src/env.c
View File

@ -2,9 +2,20 @@
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/vector.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}

View File

@ -1,6 +1,7 @@
#include <TeX/config.h>
#include <TeX/node.h>
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <stdlib.h>
@ -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);

View File

@ -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; }

View File

@ -1,34 +0,0 @@
#include <TeX/vector.h>
#include <stdlib.h>
#include <string.h>
#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);
}