425 lines
9.8 KiB
C
425 lines
9.8 KiB
C
#include <TeX/node.h>
|
|
#include <TeX/flow.h>
|
|
#include <TeX/classes.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
//---
|
|
// 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 <stdio.h>
|
|
#include <TeX/print.h>
|
|
|
|
/* 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 */
|