#include #include #include #include #include #include #include #include #include //--- // Flow construction and destruction functions //--- /* TeX_flow_add_node(): Add a new node to a flow */ struct TeX_Flow *TeX_flow_add_node(struct TeX_Flow *flow, struct TeX_Node*node) { if(!node) return flow; /* If the flow is non-empty, add the node to it */ if(flow) { flow->last->next = node; flow->last = node; flow->elements++; return flow; } /* Otherwise, create and return a new flow */ flow = calloc(1, sizeof *flow); if(!flow) return NULL; flow->first = flow->last = node; flow->elements = 1; return flow; } /* TeX_flow_free(): Free TeX_Flow objects */ void TeX_flow_free(struct TeX_Flow *flow) { if(!flow) return; struct TeX_Node *node, *next = NULL; for(node = flow->first; node; node = next) { next = node->next; TeX_node_free(node); } free(flow); } //--- // Chunks to place subscripts and superscripts //--- #define TEX_CHUNK_INLINE 0 #define TEX_CHUNK_DISPLAY 1 #define TEX_CHUNK_SLANTED 2 /* struct chunk: Chunk of nodes positioned relative to each other The notion of "chunk" comprises a base with a subscript and a superscript. Together they can be arranged in three different ways: * TEX_CHUNK_INLINE: Normal inline setting, subscript and superscript are placed to the right of the base and moved below and above the baseline. * TEX_CHUNK_DISPLAY: Similar to LaTeX's display style, subscript and superscript are placed under and above the base, and are centered horizontally. * TEX_CHUNK_SLANTED: Similar to inline mode, but subscript is slightly moved to the left and superscript is slightly moved to the right. Looks good on integrals. The user of a chunk sets the [x] and [l] members of the [base] node, then selects the display mode and optionally adds subscript and superscript. When the chunk_finalize() function is called, the position of the elements is computed and the [x] and [l] members of the [sub] and [sup] nodes, as well as the geometry of the chunk, are set. */ struct chunk { /* Base node, baseline aligned on the flow */ struct TeX_Node *base; /* Subscript and superscript (might both be NULL) */ struct TeX_Node *sub, *sup; /* Display mode */ int mode; /* Chunk geometry */ int width; int height; int line; }; /* chunk_make(): Make a new chunk for a base node */ static void chunk_make(struct chunk *chunk, struct TeX_Node *base, int mode) { chunk->base = base; chunk->mode = mode; chunk->sub = NULL; chunk->sup = NULL; chunk->width = 0; chunk->height = 0; chunk->line = 0; } /* chunk_reset(): Empty a chunk */ static void chunk_reset(struct chunk *chunk) { memset(chunk, 0, sizeof *chunk); } /* chunk_set(): Check whether a chunk is empty */ static int chunk_set(struct chunk *chunk) { return chunk->base != NULL; } /* chunk_set_subscript(): Add a subscript on a chunk */ static void chunk_set_subscript(struct chunk *chunk, struct TeX_Node *sub) { /* Silently refuse double index */ if(chunk->sub) return; chunk->sub = sub; } /* chunk_set_superscript(): Add a superscript on a chunk */ static void chunk_set_superscript(struct chunk *chunk, struct TeX_Node *sup) { /* Silently refuse double exponent */ if(chunk->sup) return; chunk->sup = sup; } /* chunk_layout_inline(): Compute chunk layout for inline mode */ static void chunk_layout_inline(struct chunk *chunk) { struct TeX_Node *base = chunk->base; struct TeX_Node *sup = chunk->sup; struct TeX_Node *sub = chunk->sub; /* Height over and below baseline */ int over = base->line; int under = base->height - base->line; /* Additional width */ int right = 0; if(sub) { int elevation = TEX_SUBSCRIPT_ELEVATION; sub->x = base->x + base->width + TEX_SUBSCRIPT_SPACING; sub->l = base->l + (base->height - base->line + sub->line - elevation); under = max(under, under + sub->height - elevation); right = max(right, sub->width + TEX_SUBSCRIPT_SPACING); } if(sup) { int depth = TEX_SUPERSCRIPT_DEPTH; sup->x = base->x + base->width + TEX_SUPERSCRIPT_SPACING; sup->l = base->l - (base->line + (sup->height - sup->line) - depth); over = max(over, over + sup->height - depth); right = max(right, sup->width + TEX_SUPERSCRIPT_SPACING); } chunk->width = base->width + right; chunk->height = over + under; chunk->line = over; } /* chunk_layout_display(): Compute chunk layout for display mode */ static void chunk_layout_display(struct chunk *chunk) { struct TeX_Node *base = chunk->base; struct TeX_Node *sup = chunk->sup; struct TeX_Node *sub = chunk->sub; int spacing = TEX_DISPLAY_SPACING; /* Chunk width and height */ chunk->width = base->width; chunk->height = base->height; chunk->line = base->line; if(sub) { chunk->width = max(chunk->width, sub->width); chunk->height += sub->height + spacing; } if(sup) { chunk->width = max(chunk->width, sup->width); chunk->height += sup->height + spacing; chunk->line += sup->height + spacing; } /* Now that the dimensions are clear, compute the position */ if(sub) { sub->l += sub->line + (base->height - base->line) + spacing; sub->x = base->x + ((chunk->width - sub->width) >> 1); } if(sup) { sup->l -= (sup->height - sup->line) + base->line + spacing; sup->x = base->x + ((chunk->width - sup->width) >> 1); } base->x += (chunk->width - base->width) >> 1; } /* chunk_layout_slanted(): Compute chunk layout for slanted mode */ static void chunk_layout_slanted(struct chunk *chunk) { /* TODO: Slanted mode for exponents and subscripts */ chunk_layout_inline(chunk); } /* chunk_layout(): Compute the layout of a chunk after it's filled */ static void chunk_layout(struct chunk *chunk) { if(chunk->mode == TEX_CHUNK_INLINE) { chunk_layout_inline(chunk); } if(chunk->mode == TEX_CHUNK_DISPLAY) { chunk_layout_display(chunk); } if(chunk->mode == TEX_CHUNK_SLANTED) { chunk_layout_slanted(chunk); } } //--- // Groups to compute the size of parenthesis-like elements //--- struct group { /* Opening parenthesis/bracket/whatever */ struct TeX_Node *left; /* Maximum height above and below baseline */ int above; int below; }; /* group_open(): Create new group initialized with a left node */ static void group_open(struct group *group, struct TeX_Node *left) { group->left = left; group->above = 0; group->below = 0; } /* group_update(): Update a group with a new node */ static void group_update(struct group *group, struct chunk *chunk) { /* Account for node displacement around baseline */ group->above = max(chunk->line, group->above); group->below = max(chunk->height - chunk->line, group->below); } /* group_close(): Close a group with its right node */ static void group_close(struct group *group, struct TeX_Node *right) { struct TeX_Node *left = group->left; int above = group->above; int below = group->below; int line = above; #if TEX_LEFTRIGHT_SYMMETRICAL above = below = max(above, below); #endif #if ! TEX_LEFTRIGHT_ALIGNED line = (above + below) >> 1; #endif /* Set the height of the left and right node */ left->height = above + below; left->line = line; left->l += (line - above); right->height = above + below; right->line = line; right->l += (line - above); } //--- // Layout and rendering //--- static void update(struct chunk *c, struct group *g, int *x, int *above, int *below) { if(!chunk_set(c)) return; chunk_layout(c); group_update(g, c); *x += c->width + TEX_LAYOUT_SPACING; *above = max(*above, c->line - c->base->l); *below = max(*below, c->height - c->line + c->base->l); chunk_reset(c); } /* TeX_flow_layout(): Calculate the layout of a flow */ void TeX_flow_layout(struct TeX_Flow *flow, int display) { /* Current node and current chunk */ struct TeX_Node *node; struct chunk c = { NULL }; /* Position in flow */ int x = 0; /* Height above baseline (excluded), and below baseline (included) */ int above = 0; int below = 0; /* Nested groups and current position */ struct group groups[TEX_LEFTRIGHT_DEPTH]; struct group *g = groups; for(node = flow->first; node; node = node->next) { char const *class = TeX_class_of(node)->name; TeX_node_layout(node, display); if(!strcmp(class, "\\sup")) { chunk_set_superscript(&c, node); continue; } if(!strcmp(class, "\\sub")) { chunk_set_subscript(&c, node); continue; } /* Leave the last chunk and make a new one */ update(&c, g, &x, &above, &below); if(!strcmp(class, "left") && g-groups < TEX_LEFTRIGHT_DEPTH) { g++; group_open(g, node); } if(!strcmp(class, "right") && g - groups > 0) { group_close(g, node); g--; } int mode = TEX_CHUNK_INLINE; int pref = TeX_class_of(node)->mode; if((pref == TEX_FLOW_PREFER_DISPLAY && display) || pref == TEX_FLOW_DISPLAY) { mode = TEX_CHUNK_DISPLAY; } node->x = x; chunk_make(&c, node, mode); } /* Finish the last chunk */ update(&c, g, &x, &above, &below); flow->height = above + below; flow->line = above; 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, 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) { 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; } } //--- // Printing function //--- #ifdef TEX_PRINT #include #include /* TeX_print_flow(): Print a flow's tree */ void TeX_print_flow(struct TeX_Flow *flow, int indent) { printf("%*s", indent, ""); if(!flow) { puts("flow (null)"); return; } printf("flow " GRAY "%dx%d,%d" END "\n", flow->width, flow->height, flow->line); struct TeX_Node *node = flow->first; while(node) { TeX_print_node(node, indent + 4); node = node->next; } } #endif /* TEX_PRINT */