#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; return flow; } /* Otherwise, create and return a new flow */ flow = calloc(1, sizeof *flow); if(!flow) return NULL; flow->first = flow->last = node; 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 = sup ? TEX_SUBSCRIPT_SHARED_ELEVATION : TEX_SUBSCRIPT_EXCL_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 = sub ? TEX_SUPERSCRIPT_SHARED_DEPTH : TEX_SUPERSCRIPT_EXCL_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); } /* 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) { struct TeX_Node const * node; for(node = flow->first; node; node = node->next) { TeX_node_render(node, x + node->x, y + flow->line - node->line + node->l, color); } } //--- // 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 */