WIP: Equation editing #1

Draft
Heath123 wants to merge 9 commits from Heath123/TeX:equedit into master
11 changed files with 417 additions and 38 deletions

View File

@ -6,6 +6,8 @@
#define TEX_TEX
#include <TeX/config.h>
#include <TeX/edit.h>
#include <stddef.h>
//---
@ -85,10 +87,12 @@ void TeX_free(struct TeX_Env *formula);
/* TeX_draw(): Render a parsed formula
@formula Formula to render
@context Used to place the cursor; can be set to NULL is there is none.
This function does not draw the cursor but may its position.
@x x coordinate of the left-side of the bounding box (included)
@y y coordinate of the top-size of the bounding box (included)
@color Requested color for the rendered pixels */
void TeX_draw(struct TeX_Env *formula, int x, int y, int color);
void TeX_draw(struct TeX_Env *formula, struct editContext * context, int x, int y, int color);
/* TeX_interpret(): Parse and render, in sequence
This function parses the provided formula, renders it and then frees it.

View File

@ -43,11 +43,13 @@ struct TeX_Class
void TeX_line(int x1, int y1, int x2, int y2, int color);
void TeX_text(char const *str, int x, int y, int color);
@node A node of the described class, for rendering
@x Horizontal coordinate of the requested rendering position
@y Vertical coordinate of the requested rendering position
@color Requested rendering color */
void (*render)(struct TeX_Node const * node, int x, int y, int color);
@node A node of the described class, for rendering
@context Used to place the cursor; can be set to NULL is there is none.
This function does not draw the cursor but may its position.
@x Horizontal coordinate of the requested rendering position
@y Vertical coordinate of the requested rendering position
@color Requested rendering color */
void (*render)(struct TeX_Node const * node, struct editContext * context, int x, int y, int color);
/* Sub- and superscript mode, see <TeX/flow.h> for available modes */
int mode;

46
include/TeX/edit.h Normal file
View File

@ -0,0 +1,46 @@
//---
// edit: Structures related to cursor movement and editing
//---
#ifndef TEX_EDIT
#define TEX_EDIT
#include <stdbool.h>
struct editContext {
bool elementIsText;
union {
struct TeX_Flow* cursorFlow;
struct TeX_Node* cursorText;
};
int offset;
int cursorX;
int cursorY;
};
// An enum for action that the cursor can take
enum cursorAction {
CURSOR_MOVE_LEFT,
CURSOR_MOVE_RIGHT
};
// An enum of return values for the move_cursor function
enum cursorMoveResult {
// The cursor was found and updated, and the recursive search can end
SUCCESS,
// The cursor was not found, and the recursive search should continue
CURSOR_NOT_HERE,
// For these values where the cursor exits, the parent is responsible for
// moving the cursor out of the flow or node.
// It may also choose to ingore the cursor exit and keep the cursor in the
// flow or node, for example if there is no parent to move the cursor to.
// The cursor exited the flow or node out of the right side
CURSOR_PAST_END,
// The cursor exited the flow or node out of the left side
CURSOR_PAST_START
};
#endif /* TEX_EDIT */

View File

@ -6,6 +6,7 @@
#define TEX_ENV
#include <stddef.h>
#include <stdint.h>
struct TeX_Node;
@ -34,7 +35,7 @@ struct TeX_Env
void (*layout)(TeX_Env *env, int display);
/* render(): Render environment */
void (*render)(TeX_Env *env, int x, int y, int color);
void (*render)(TeX_Env *env, struct editContext * context, int x, int y, int color);
/* Dimensions */
uint16_t width;

View File

@ -5,6 +5,7 @@
#ifndef TEX_FLOW
#define TEX_FLOW
#include <TeX/edit.h>
#include <stdint.h>
struct TeX_Node;
@ -19,6 +20,8 @@ struct TeX_Flow
struct TeX_Node *first;
struct TeX_Node *last;
uint32_t elements;
uint16_t width;
uint16_t height;
uint16_t line;
@ -64,9 +67,22 @@ void TeX_flow_free(struct TeX_Flow *flow);
characters and subscripts/superscripts. Modifies the flow's metadata. */
void TeX_flow_layout(struct TeX_Flow *flow, int display);
/* TeX_flow_render(): Render a flow and all its components
This function renders all horizontal objects in a flow. The layout of the
flow must have been computed. */
void TeX_flow_render(struct TeX_Flow const * flow, int x, int y, int color);
void TeX_flow_render(struct TeX_Flow const * flow, struct editContext * context, int x, int y, int color);
//---
// Cursor and editing
//---
/* TeX_flow_cursor_action(): Perform a cursor action on a flow
This function performs a cursor action on a flow, like moving left or right.
This is recursive, and should usually be called on the top-level flow.
If it is called on a non-top-level flow, it will restrict the movement to
within the flow; the cursor will be to able to move within it but not exit.
Returns a cursorMoveResult enum; see edit.h for details of the return value. */
enum cursorMoveResult TeX_flow_cursor_action(struct TeX_Flow * flow, struct editContext * context, enum cursorAction action);
#endif /* TEX_FLOW */

View File

@ -7,6 +7,8 @@
#include <TeX/config.h>
#include <TeX/defs.h>
#include <TeX/edit.h>
#include <stddef.h>
#include <stdint.h>
@ -28,8 +30,11 @@ struct TeX_Node
struct TeX_Node *next;
union {
/* Plain text content encoded as UTF-8 */
uint8_t *text;
/* Plain text content encoded as UTF-8 with length */
struct {
uint8_t *text;
size_t text_len;
};
/* Environment pointer */
struct TeX_Env *env;
/* Functional arguments */
@ -97,6 +102,25 @@ void TeX_node_layout(struct TeX_Node *node, int display);
/* TeX_node_render(): Render a node and its children
The node's layout must have been computed first. */
void TeX_node_render(struct TeX_Node const * node, int x, int y, int color);
void TeX_node_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color);
//---
// Cursor and editing
//---
/* TeX_flow_cursor_action(): Perform a cursor action on a flow
See TeX_flow_cursor_action in flow.h for more information.
If this node contains flows, this will propagate the action to them,
and nagivate between them. by updating the cursor context. */
enum cursorMoveResult TeX_node_cursor_action(struct TeX_Node *node, struct editContext * context, enum cursorAction action);
/* TeX_node_cursor_enter(): Enter a node
This function is called when the cursor navigates into a node.
If the node is just a simple symbol or character, this will return
CURSOR_PAST_END/START, and the parent flow will skip over the node.
If the node contains flows that can be navigated into, this will usually
nagivate into the first/last flow depending on the direction that the
cursor enters from, and return SUCCESS. */
enum cursorMoveResult TeX_node_cursor_enter(struct TeX_Node *node, struct editContext * context, enum cursorAction action);
#endif /* TEX_NODE */

View File

@ -72,9 +72,9 @@ struct TeX_Env *TeX_parse(char const *formula, int display)
}
/* TeX_draw(): Render a parsed formula */
void TeX_draw(struct TeX_Env *formula, int x, int y, int color)
void TeX_draw(struct TeX_Env *formula, struct editContext * context, int x, int y, int color)
{
formula->render(formula, x, y, color);
formula->render(formula, context, x, y, color);
}
/* TeX_interpret(): Parse and render, in sequence */
@ -83,6 +83,6 @@ void TeX_interpret(char const *formula, int display, int x, int y, int color)
struct TeX_Env *env = TeX_parse(formula, display);
if(!env) return;
TeX_draw(env, x, y, color);
TeX_draw(env, NULL, x, y, color);
TeX_free(env);
}

View File

@ -7,10 +7,12 @@
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <TeX/edit.h>
#include <TeX/interface.h>
#include <TeX/defs.h>
#include <stdbool.h>
#include <string.h>
@ -36,8 +38,22 @@ void text_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h >> 1;
}
void text_render(struct TeX_Node const * node, int x, int y, int color)
void text_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
if (context != NULL && context->elementIsText && context->cursorText == node) {
// Set the cursor position
// Temporarily cap the length of the string to the cursor position
char old = node->text[context->offset];
node->text[context->offset] = '\0';
// Measure the width of the string
int w, h;
TeX_size((char const *)node->text, &w, &h);
// Restore the string
node->text[context->offset] = old;
// Update the cursor position to inside the text
context->cursorX = x + w;
context->cursorY = y;
}
TeX_text((void *)node->text, x, y, color);
}
@ -54,9 +70,9 @@ void env_layout(struct TeX_Node *node, int display)
node->line = node->env->line;
}
void env_render(struct TeX_Node const * node, int x, int y, int color)
void env_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
node->env->render(node->env, x, y, color);
node->env->render(node->env, context, x, y, color);
}
//---
@ -96,7 +112,7 @@ void frac_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h(0) + spacing + thickness / 2;
}
void frac_render(struct TeX_Node const * node, int x, int y, int color)
void frac_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
/* Fraction bar */
for(int i = 0; i < TEX_FRACTION_BAR_THICKNESS; i++)
@ -107,12 +123,14 @@ void frac_render(struct TeX_Node const * node, int x, int y, int color)
/* First argument */
TeX_flow_render(args[0],
context,
x + ((node->width - w(0)) >> 1),
y,
color);
/* Second argument */
TeX_flow_render(args[1],
context,
x + ((node->width - w(1)) >> 1),
y + h(0) + 2*TEX_FRACTION_SPACING + TEX_FRACTION_BAR_THICKNESS,
color);
@ -165,7 +183,7 @@ void leftright_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->width = 1;
}
void leftright_render(struct TeX_Node const * node, int x, int y, int color)
void leftright_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int h = node->height, w = node->width, l = node->line;
@ -285,10 +303,10 @@ void supsubscript_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0);
}
void supsubscript_render(struct TeX_Node const * node, int x, int y,
int color)
void supsubscript_render(struct TeX_Node const * node, struct editContext * context,
int x, int y, int color)
{
TeX_flow_render(args[0], x, y, color);
TeX_flow_render(args[0], context, x, y, color);
}
//---
@ -306,8 +324,8 @@ void sum_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = 4;
}
void sum_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
int color)
void sum_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context,
int x, int y, int color)
{
TeX_line(x, y, x + 8, y, color);
TeX_line(x, y, x + 4, y + 4, color);
@ -330,7 +348,7 @@ void prod_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = 4;
}
void prod_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
void prod_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context, int x, int y,
int color)
{
TeX_line(x, y, x + 8, y, color);
@ -353,7 +371,7 @@ void int_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = TEX_INT_HEIGHT >> 1;
}
void int_render(struct TeX_Node const * node, int x, int y, int color)
void int_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int h = node->height;
@ -397,7 +415,7 @@ void vec_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0) + TEX_VEC_SPACING + arrow_height;
}
void vec_render(struct TeX_Node const * node, int x, int y, int color)
void vec_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int length = TEX_VEC_ARROW_LENGTH;
int arrow_height = 2 * length + 1;
@ -411,6 +429,7 @@ void vec_render(struct TeX_Node const * node, int x, int y, int color)
/* First argument */
TeX_flow_render(args[0],
context,
x + TEX_VEC_MARGIN,
y + TEX_VEC_SPACING + arrow_height,
color);
@ -431,7 +450,7 @@ void lim_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h >> 1;
}
void lim_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
void lim_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context, int x, int y,
int color)
{
TeX_text("lim", x, y, color);
@ -492,7 +511,7 @@ void sqrt_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0) + TEX_SQRT_MARGIN + 1;
}
void sqrt_render(struct TeX_Node const * node, int x, int y, int color)
void sqrt_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int slanted = TEX_SQRT_SLANTED && h(0) <= TEX_SQRT_SLANT_MAX;
int w = node->width;
@ -509,6 +528,7 @@ void sqrt_render(struct TeX_Node const * node, int x, int y, int color)
TeX_line(x + w - 1, y, x + w - 1, y + TEX_SQRT_BAR_LENGTH, color);
TeX_flow_render(args[0],
context,
x + base + TEX_SQRT_MARGIN,
y + 1 + TEX_SQRT_MARGIN,
color);

View File

@ -63,10 +63,10 @@ static void primary_layout(struct TeX_Env *env, int display)
env->line = p->flow->line;
}
static void primary_render(struct TeX_Env *env, int x, int y, int color)
static void primary_render(struct TeX_Env *env, struct editContext * context, int x, int y, int color)
{
struct TeX_Env_Primary *p = (void *)env;
TeX_flow_render(p->flow, x, y, color);
TeX_flow_render(p->flow, context, x, y, color);
}
struct TeX_Env *TeX_env_primary(void)
@ -200,7 +200,7 @@ static void matrix_layout(struct TeX_Env *env, int display)
env->line = env->height >> 1;
}
static void matrix_render(struct TeX_Env *env, int x, int y, int color)
static void matrix_render(struct TeX_Env *env, struct editContext * context, int x, int y, int color)
{
struct TeX_Env_Matrix *m = (void *)env;
@ -218,6 +218,7 @@ static void matrix_render(struct TeX_Env *env, int x, int y, int color)
int rh = m->rowline[row] + m->rowdepth[row];
if(flow) TeX_flow_render(flow,
context,
x + dx + ((cw - flow->width) >> 1),
y + dy + ((rh - flow->height) >> 1),
color);

View File

@ -1,9 +1,13 @@
#include <TeX/node.h>
#include <TeX/flow.h>
#include <TeX/classes.h>
#include <TeX/interface.h>
#include <TeX/edit.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//---
// Flow construction and destruction functions
@ -19,6 +23,7 @@ struct TeX_Flow *TeX_flow_add_node(struct TeX_Flow *flow, struct TeX_Node*node)
{
flow->last->next = node;
flow->last = node;
flow->elements++;
return flow;
}
@ -27,6 +32,7 @@ struct TeX_Flow *TeX_flow_add_node(struct TeX_Flow *flow, struct TeX_Node*node)
if(!flow) return NULL;
flow->first = flow->last = node;
flow->elements = 1;
return flow;
}
@ -378,15 +384,141 @@ void TeX_flow_layout(struct TeX_Flow *flow, int display)
flow->width = max(x - TEX_LAYOUT_SPACING, 0);
}
enum cursorMoveResult TeX_flow_cursor_action(struct TeX_Flow * flow, struct editContext * context, enum cursorAction action) {
// If this isn't the flow the cursor is in, continue the recursive search
if (context->cursorFlow != flow)
{
// Search all the nodes in the flow for the cursor
struct TeX_Node* node = flow->first;
int offset = 0;
while (node != NULL)
{
int returnCode = TeX_node_cursor_action(node, context, action);
if (returnCode == SUCCESS)
{
return SUCCESS;
}
else if (returnCode == CURSOR_NOT_HERE)
{
// Keep searching
}
else if (returnCode == CURSOR_PAST_END)
{
bool wasText = context->elementIsText;
// Put the cursor in this flow after the node
context->elementIsText = false;
context->cursorFlow = flow;
context->offset = offset + 1;
// If we just left a text node, do the cursor action again
// This is because if we just put the cursor after the text
// node then it will look like it's just at the end of it
// and also it will be immedately put back in again
if (wasText)
{
return TeX_flow_cursor_action(flow, context, action);
}
return SUCCESS;
}
else if (returnCode == CURSOR_PAST_START)
{
bool wasText = context->elementIsText;
// Put the cursor in this flow before the node
context->elementIsText = false;
context->cursorFlow = flow;
context->offset = offset;
if (wasText)
{
return TeX_flow_cursor_action(flow, context, action);
}
return SUCCESS;
}
node = node->next;
offset++;
}
return CURSOR_NOT_HERE;
}
// If this is the flow the cursor is in, move the cursor
switch (action)
{
case CURSOR_MOVE_LEFT:
if (context->offset <= 0)
{
context->offset = 0;
return CURSOR_PAST_START;
}
else
{
// Find the node at offset
// As this is a linked list we have to iterate through it which is inefficient
// TODO: Change it to a normal array, or store the pointer to the node in the editContext (which will need a doubly linked list)
struct TeX_Node* node = flow->first;
for (int i = 0; i < context->offset - 1; i++)
{
node = node->next;
}
int returncode = TeX_node_cursor_enter(node, context, action);
if (returncode == CURSOR_PAST_START)
{
// Put the cursor in this flow before the node
context->offset--;
}
}
break;
case CURSOR_MOVE_RIGHT:
if (context->offset >= flow->elements)
{
context->offset = flow->elements;
return CURSOR_PAST_END;
}
else
{
// Find the node at offset
// As this is a linked list we have to interate through it which is inefficient
// TODO: Change it to a normal array, or store the pointer to the node in the editContext (which will need a doubly linked list)
struct TeX_Node* node = flow->first;
for (int i = 0; i < context->offset; i++)
{
node = node->next;
}
int returncode = TeX_node_cursor_enter(node, context, action);
if (returncode == CURSOR_PAST_END)
{
context->offset++;
}
}
break;
}
return SUCCESS;
}
/* TeX_flow_render(): Render a flow and all its components */
void TeX_flow_render(struct TeX_Flow const * flow, int x, int y, int color)
void TeX_flow_render(struct TeX_Flow const * flow, struct editContext * context, int x, int y, int color)
{
struct TeX_Node const * node;
int offset = 0;
for(node = flow->first; node; node = node->next)
{
TeX_node_render(node, x + node->x, y + flow->line -
node->line + node->l, color);
if (context != NULL && !context->elementIsText && context->cursorFlow == flow && context->offset == offset)
{
// Set the cursor position
context->cursorX = x + node->x;
context->cursorY = y + flow->line - 4;
}
TeX_node_render(node, context, x + node->x,
y + flow->line - node->line + node->l, color);
offset++;
}
if (context != NULL && !context->elementIsText &&context->cursorFlow == flow && context->offset == offset)
{
// Set the cursor position
context->cursorX = x + flow->width;
context->cursorY = y + flow->line - 4;
}
}

View File

@ -3,7 +3,10 @@
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <TeX/interface.h>
#include <TeX/edit.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
@ -15,6 +18,134 @@
((node)->type == TEX_NODECLASS_TEXT || \
(node)->type == TEX_NODECLASS_ENV)
enum cursorMoveResult TeX_node_cursor_action(struct TeX_Node *node, struct editContext * context, enum cursorAction action)
{
// Text
// TODO: Make sure the editing works with UTF-8 (it probably doesn't as-is)
if (node->type == 0)
{
// If this isn't where the cursor is, continue the recursive search
if (!context->elementIsText || context->cursorText != node)
return CURSOR_NOT_HERE;
if (action == CURSOR_MOVE_LEFT)
{
if (context->offset <= 0)
return CURSOR_PAST_START;
context->offset--;
return SUCCESS;
}
else if (action == CURSOR_MOVE_RIGHT) {
if (context->offset >= node->text_len)
return CURSOR_PAST_END;
context->offset++;
return SUCCESS;
}
}
// Regular nodes
else if (node->type > 1)
{
// If this isn't where the cursor is, continue the recursive search
if (node->args[0] == NULL)
return CURSOR_NOT_HERE;
// Propagate the cursor action to the first child flow
int returnCode = TeX_flow_cursor_action(node->args[0], context, action);
if (returnCode == SUCCESS)
return returnCode;
else if (returnCode == CURSOR_PAST_END)
{
// If it moved past the end of the first child, move to the second child
// If there is no second child, leave the node
if (node->args[1] == NULL)
return CURSOR_PAST_END;
// Update the cursor to be in the right place
context->elementIsText = false;
context->cursorFlow = node->args[1];
context->offset = 0;
return SUCCESS;
}
else if (returnCode != CURSOR_NOT_HERE)
return returnCode;
if (node->args[1] == NULL)
return CURSOR_NOT_HERE;
// Propagate the cursor action to the second child flow
returnCode = TeX_flow_cursor_action(node->args[1], context, action);
if (returnCode == CURSOR_PAST_START)
{
// If it moved past the start of the second child, move to the first child
// The first child should always exist if the second child exists so we don't need to check
context->elementIsText = false;
context->cursorFlow = node->args[0];
context->offset = node->args[0]->elements;
return SUCCESS;
}
return returnCode;
}
// TODO: matrices
return CURSOR_NOT_HERE;
}
enum cursorMoveResult TeX_node_cursor_enter(struct TeX_Node *node, struct editContext * context, enum cursorAction action)
{
enum cursorMoveResult skipPast = action == CURSOR_MOVE_RIGHT ? CURSOR_PAST_END : CURSOR_PAST_START;
// Text
if (node->type == 0)
{
context->elementIsText = true;
context->cursorText = node;
context->offset = action == CURSOR_MOVE_RIGHT ? 0 : node->text_len;
return SUCCESS;
}
// Regular nodes
else if (node->type > 1)
{
if (action == CURSOR_MOVE_RIGHT)
{
// Enter the first child flow
// If there is no child, skip past this node
if (node->args[0] == NULL)
return CURSOR_PAST_END;
// Update the cursor to be in the right place
context->elementIsText = false;
context->cursorFlow = node->args[0];
context->offset = 0;
return SUCCESS;
}
else if (action == CURSOR_MOVE_LEFT)
{
// Enter the last child flow
struct TeX_Flow *denom = node->args[1];
// If there is only one child, go to it instead
if (denom == NULL)
denom = node->args[0];
// If there is no child, skip past this node
if (denom == NULL)
return CURSOR_PAST_START;
// Update the cursor to be in the right place
context->elementIsText = false;
context->cursorFlow = denom;
context->offset = denom->elements;
return SUCCESS;
}
}
// TODO: matrices
return skipPast;
}
/* TeX_node_text(): Make a text node */
struct TeX_Node *TeX_node_text(char const *utf8)
{
@ -22,9 +153,11 @@ struct TeX_Node *TeX_node_text(char const *utf8)
if(!node) return NULL;
/* TODO: Don't use strdup(); convert from utf8 instead */
node->text = malloc(strlen(utf8) + 1);
size_t len = strlen(utf8);
node->text = malloc(len + 1);
if(!node->text) { free(node); return NULL; }
strcpy((void *)node->text, utf8);
node->text_len = len;
node->type = TEX_NODECLASS_TEXT;
return node;
@ -131,13 +264,13 @@ void TeX_node_layout(struct TeX_Node *node, int display)
}
/* TeX_node_render(): Render a node and its children */
void TeX_node_render(struct TeX_Node const * node, int x, int y, int color)
void TeX_node_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
/* Don't render hidden or invalid elements */
if(node->hidden) return;
/* For non trivial classes, use the class' special function */
TeX_class_of(node)->render(node, x, y, color);
TeX_class_of(node)->render(node, context, x, y, color);
}
//---