TeX/src/flow.c

425 lines
9.8 KiB
C
Raw Permalink Normal View History

2019-06-12 18:53:50 +02:00
#include <TeX/node.h>
#include <TeX/flow.h>
#include <TeX/classes.h>
#include <string.h>
2019-06-12 18:53:50 +02:00
#include <stdlib.h>
2019-06-12 18:53:50 +02:00
//---
// 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 */
2019-06-12 18:53:50 +02:00
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 */
2019-06-12 18:53:50 +02:00
static void chunk_reset(struct chunk *chunk)
{
memset(chunk, 0, sizeof *chunk);
}
/* chunk_set(): Check whether a chunk is empty */
2019-06-12 18:53:50 +02:00
static int chunk_set(struct chunk *chunk)
{
return chunk->base != NULL;
}
/* chunk_set_subscript(): Add a subscript on a chunk */
2019-06-12 18:53:50 +02:00
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 */
2019-06-12 18:53:50 +02:00
static void chunk_set_superscript(struct chunk *chunk, struct TeX_Node *sup)
{
/* Silently refuse double exponent */
if(chunk->sup) return;
chunk->sup = sup;
}
2019-06-12 18:53:50 +02:00
/* 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;
2019-06-18 17:30:49 +02:00
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);
2019-06-18 17:30:49 +02:00
right = max(right, sub->width + TEX_SUBSCRIPT_SPACING);
}
if(sup)
{
int depth = sub
? TEX_SUPERSCRIPT_SHARED_DEPTH
: TEX_SUPERSCRIPT_EXCL_DEPTH;
2019-06-18 17:30:49 +02:00
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);
2019-06-18 17:30:49 +02:00
right = max(right, sup->width + TEX_SUPERSCRIPT_SPACING);
}
chunk->width = base->width + right;
chunk->height = over + under;
chunk->line = over;
}
2019-06-12 18:53:50 +02:00
/* 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;
2019-06-18 17:30:49 +02:00
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;
}
2019-06-12 18:53:50 +02:00
/* chunk_layout_slanted(): Compute chunk layout for slanted mode */
static void chunk_layout_slanted(struct chunk *chunk)
{
/* TODO: Slanted mode for exponents and subscripts */
2019-06-12 18:53:50 +02:00
chunk_layout_inline(chunk);
}
2019-06-12 18:53:50 +02:00
/* 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)
{
2019-06-12 18:53:50 +02:00
chunk_layout_inline(chunk);
}
if(chunk->mode == TEX_CHUNK_DISPLAY)
{
2019-06-12 18:53:50 +02:00
chunk_layout_display(chunk);
}
if(chunk->mode == TEX_CHUNK_SLANTED)
{
2019-06-12 18:53:50 +02:00
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 */
2019-06-12 18:53:50 +02:00
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 */
2019-06-12 18:53:50 +02:00
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 */
2019-06-12 18:53:50 +02:00
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);
}
//---
2019-06-12 18:53:50 +02:00
// Layout and rendering
//---
static void update(struct chunk *c, struct group *g, int *x, int *above,
int *below)
{
if(!chunk_set(c)) return;
2019-06-12 18:53:50 +02:00
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);
}
2019-06-12 18:53:50 +02:00
/* TeX_flow_layout(): Calculate the layout of a flow */
2019-06-19 17:52:09 +02:00
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 */
2019-06-12 18:53:50 +02:00
struct group groups[TEX_LEFTRIGHT_DEPTH];
struct group *g = groups;
for(node = flow->first; node; node = node->next)
{
2019-06-12 18:53:50 +02:00
char const *class = TeX_class_of(node)->name;
2019-06-19 17:52:09 +02:00
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);
2019-06-12 18:53:50 +02:00
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;
2019-06-19 17:52:09 +02:00
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);
}
2019-06-12 18:53:50 +02:00
/* 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);
}
}
//---
2019-06-12 18:53:50 +02:00
// Printing function
//---
2019-06-12 18:53:50 +02:00
#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)
{
2019-06-12 18:53:50 +02:00
printf("%*s", indent, "");
if(!flow) { puts("flow (null)"); return; }
2019-06-12 18:53:50 +02:00
printf("flow " GRAY "%dx%d,%d" END "\n", flow->width, flow->height,
flow->line);
struct TeX_Node *node = flow->first;
while(node)
{
2019-06-12 18:53:50 +02:00
TeX_print_node(node, indent + 4);
node = node->next;
}
}
2019-06-12 18:53:50 +02:00
#endif /* TEX_PRINT */