TeX/src/flow.c

360 lines
8.4 KiB
C
Raw Normal View History

#include <TeX/TeX.h>
#include <TeX/structure.h>
#include <TeX/render.h>
#include <string.h>
//---
// 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 */
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 */
void chunk_reset(struct chunk *chunk)
{
memset(chunk, 0, sizeof *chunk);
}
/* chunk_set(): Check whether a chunk is empty */
int chunk_set(struct chunk *chunk)
{
return chunk->base != NULL;
}
/* chunk_set_subscript(): Add a subscript on a chunk */
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 */
void chunk_set_superscript(struct chunk *chunk, struct TeX_Node *sup)
{
/* Silently refuse double exponent */
if(chunk->sup) return;
chunk->sup = sup;
}
/* chunk_compute_inline(): Compute chunk layout for inline mode */
void chunk_compute_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_LAYOUT_SPACING;
sub->l = base->l + (base->height - base->line + sub->line -
elevation);
under = max(under, under + sub->height - elevation);
right = max(right, sub->width);
}
if(sup)
{
int depth = TEX_SUPERSCRIPT_DEPTH;
sup->x = base->x + base->width + TEX_LAYOUT_SPACING;
sup->l = base->l - (base->line + (sup->height - sup->line) -
depth);
over = max(over, over + sup->height - depth);
right = max(right, sup->width);
}
if(right > 0) right += TEX_LAYOUT_SPACING;
chunk->width = base->width + right;
chunk->height = over + under;
chunk->line = over;
}
/* chunk_compute_display(): Compute chunk layout for display mode */
void chunk_compute_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_LAYOUT_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_compute_slanted(): Compute chunk layout for slanted mode */
void chunk_compute_slanted(struct chunk *chunk)
{
/* TODO: Slanted mode for exponents and subscripts */
chunk_compute_inline(chunk);
}
/* chunk_compute(): Compute the layout of a chunk after it's filled */
void chunk_compute(struct chunk *chunk)
{
if(chunk->mode == TEX_CHUNK_INLINE)
{
chunk_compute_inline(chunk);
}
if(chunk->mode == TEX_CHUNK_DISPLAY)
{
chunk_compute_display(chunk);
}
if(chunk->mode == TEX_CHUNK_SLANTED)
{
chunk_compute_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 */
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 */
void group_update(struct group *group, struct TeX_Node *node)
{
/* Account for node displacement around baseline */
group->above = max(node->line - node->l, group->above);
group->below = max(node->height - node->line + node->l, group->below);
}
/* group_close(): Close a group with its right node */
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);
}
//---
// Laying out flows
//---
static void update(struct chunk *c, int *x, int *above, int *below)
{
if(!chunk_set(c)) return;
chunk_compute(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);
}
/* size_flow(): Calculate the layout of a flow */
void size_flow(struct TeX_Flow *flow)
{
/* 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_NESTING];
struct group *g = groups;
for(node = flow->first; node; node = node->next)
{
char const *class = "";
size_node(node);
group_update(g, node);
if(node->type != TEX_NODECLASS_TEXT)
class = TeX_table[node->type - 1].name;
if(!strcmp(class, "\\sup"))
{
chunk_set_superscript(&c, node);
continue;
}
if(!strcmp(class, "\\sub"))
{
chunk_set_subscript(&c, node);
continue;
}
if(!strcmp(class, "left") && g-groups < TEX_LEFTRIGHT_NESTING)
{
g++;
group_open(g, node);
group_update(g, node);
}
if(!strcmp(class, "right") && g - groups > 0)
{
group_close(g, node);
g--;
}
/* Leave the last chunk and make a new one */
update(&c, &x, &above, &below);
int mode = TEX_CHUNK_INLINE;
if(!strcmp(class, "sum") || !strcmp(class, "prod")
#if TEX_INT_DISPLAY
|| !strcmp(class, "int")
#endif
)
{
mode = TEX_CHUNK_DISPLAY;
}
node->x = x;
chunk_make(&c, node, mode);
}
/* Finish the last chunk */
update(&c, &x, &above, &below);
flow->height = above + below;
flow->line = above;
flow->width = max(x - TEX_LAYOUT_SPACING, 0);
}
//---
// Rendering flows
//---
/* render_flow() - Render a flow and all its components */
void render_flow(struct TeX_Flow const * flow, int x, int y, int color)
{
struct TeX_Node const * node;
for(node = flow->first; node; node = node->next)
{
render_node(node, x + node->x, y + flow->line - node->line +
node->l, color);
}
}