eigenmath/cmdisplay.cpp

1708 lines
29 KiB
C++

#include "stdafx.h"
#include "defs.h"
extern int text_width(int, char *);
extern void get_height_width(int *, int *, int, char *s);
static void displayf(void);
static void emit_expr(U *);
static void emit_term(U *);
static void emit_multiply(U *, int);
static void emit_factor(U *);
static void emit_power(U *);
static void emit_denominator(U *, int);
static void emit_subexpr(U *);
static void fixup_power(int, int);
static void move(int, int, int, int);
static void get_size(int, int, int *, int *, int *);
static void emit_function(U *);
static void emit_symbol(U *);
static void emit_string(U *);
static void cm_fixup_fraction(int, int, int);
static void cm_emit_number(U *, int);
static void emit_str(int, char *);
static void emit_char(int, int);
static int count_denominators(U *);
static void emit_fraction(U *, int);
static void emit_numerical_fraction(U *);
static void emit_tensor(U *);
static int isdenominator(U *);
static void emit_flat_tensor(U *);
static void emit_tensor_inner(U *, int, int *);
static void emit_top_expr(U *);
static void emit_index_function(U *);
static void emit_factorial_function(U *);
static void normy(void);
static int xheight(void);
static void emit_thick_space(void);
static void emit_thin_space(void);
static int mheight(void);
static int shim(void);
static int hrule_thickness(void);
static void emit_hrule(int);
static int isgreek(char *);
static void do_groups(void);
static void do_group(int, int);
static void emit_numerators(U *);
static void emit_denominators(U *);
static void cmdisplay_done(void);
static int subscript_dy(void);
static void emit_minus_sign_space(void);
static void emit_thin_space_maybe(void);
static void emit_paren_space_maybe(void);
#define SMALL_FONT 1
#define DEFAULT_FONT 2
#define TIMES_FONT 3
#define ITALIC_TIMES_FONT 4
#define SYMBOL_FONT 5
#define ITALIC_SYMBOL_FONT 6
#define SMALL_TIMES_FONT 7
#define SMALL_ITALIC_TIMES_FONT 8
#define SMALL_SYMBOL_FONT 9
#define SMALL_ITALIC_SYMBOL_FONT 10
#define DRAW_HRULE 20
#define DRAW_LEFT_DELIMETER 21
#define DRAW_RIGHT_DELIMETER 22
extern struct text_metric text_metric[11];
#define NGREEK 34
static struct {
char *s;
int font, n;
} greek[NGREEK] = {
{"Gamma", SYMBOL_FONT, 71},
{"Delta", SYMBOL_FONT, 68},
{"Theta", SYMBOL_FONT, 81},
{"Lambda", SYMBOL_FONT, 76},
{"Xi", SYMBOL_FONT, 88},
{"Pi", SYMBOL_FONT, 80},
{"Sigma", SYMBOL_FONT, 83},
{"Upsilon", SYMBOL_FONT, 85},
{"Phi", SYMBOL_FONT, 70},
{"Psi", SYMBOL_FONT, 89},
{"Omega", SYMBOL_FONT, 87},
{"alpha", ITALIC_SYMBOL_FONT, 97},
{"beta", ITALIC_SYMBOL_FONT, 98},
{"gamma", ITALIC_SYMBOL_FONT, 103},
{"delta", ITALIC_SYMBOL_FONT, 100},
{"epsilon", ITALIC_SYMBOL_FONT, 101},
{"zeta", ITALIC_SYMBOL_FONT, 122},
{"eta", ITALIC_SYMBOL_FONT, 104},
{"theta", ITALIC_SYMBOL_FONT, 113},
{"iota", ITALIC_SYMBOL_FONT, 105},
{"kappa", ITALIC_SYMBOL_FONT, 107},
{"lambda", ITALIC_SYMBOL_FONT, 108},
{"mu", ITALIC_SYMBOL_FONT, 109},
{"nu", ITALIC_SYMBOL_FONT, 110},
{"xi", ITALIC_SYMBOL_FONT, 120},
{"pi", ITALIC_SYMBOL_FONT, 112},
{"rho", ITALIC_SYMBOL_FONT, 114},
{"sigma", ITALIC_SYMBOL_FONT, 115},
{"tau", ITALIC_SYMBOL_FONT, 116},
{"upsilon", ITALIC_SYMBOL_FONT, 117},
{"phi", ITALIC_SYMBOL_FONT, 102},
{"chi", ITALIC_SYMBOL_FONT, 99},
{"psi", ITALIC_SYMBOL_FONT, 121},
{"omega", ITALIC_SYMBOL_FONT, 119},
};
extern void shipout(unsigned char *, int, int);
#define YMAX 100000
static struct {
int cmd, h, w, x, y;
char *s;
} chartab[YMAX];
static int indx, level, xpos;
void
cmdisplay(U *p)
{
indx = 0;
level = 0;
xpos = 0;
push(p);
displayf();
cmdisplay_done();
}
void
eval_display(void)
{
indx = 0;
level = 0;
xpos = 0;
// special form: display(symbol)
if (issymbol(cadr(p1)) && cadr(p1) != symbol(LAST) && cddr(p1) == symbol(NIL)) {
push(cadr(p1));
eval();
p2 = pop();
if (cadr(p1) != p2) {
push_symbol(SETQ);
push(cadr(p1));
push(p2);
list(3);
} else
push(p2);
displayf();
} else {
p1 = cdr(p1);
while (iscons(p1)) {
push(car(p1));
eval();
p2 = pop();
push(p2);
displayf();
p1 = cdr(p1);
}
}
cmdisplay_done();
push(symbol(NIL));
}
static void
displayf(void)
{
save();
p1 = pop();
emit_top_expr(p1);
restore();
}
#define SEG 1000
static void
cmdisplay_done(void)
{
int h, i, k, len, n, w, y;
unsigned char *buf;
do_groups();
normy();
get_size(0, indx, &h, &w, &y);
// figure out how much space is needed
n = 1; // extra for \0 that strcpy writes into buffer
for (i = 0; i < indx; i++) {
if (chartab[i].cmd == 32)
continue; // we're not going to output spaces
else if (chartab[i].s)
n = n + 13 + (int) strlen(chartab[i].s);
else
n += 17;
}
buf = (unsigned char *) malloc(n);
if (buf == 0)
stop("malloc failed");
k = 0;
for (i = 0; i < indx; i++) {
if (chartab[i].cmd == 32)
continue;
buf[k++] = (unsigned char) chartab[i].cmd;
buf[k++] = (unsigned char) (chartab[i].x >> 24);
buf[k++] = (unsigned char) (chartab[i].x >> 16);
buf[k++] = (unsigned char) (chartab[i].x >> 8);
buf[k++] = (unsigned char) chartab[i].x;
buf[k++] = (unsigned char) (chartab[i].y >> 24);
buf[k++] = (unsigned char) (chartab[i].y >> 16);
buf[k++] = (unsigned char) (chartab[i].y >> 8);
buf[k++] = (unsigned char) chartab[i].y;
if (chartab[i].s) {
len = (int) strlen(chartab[i].s);
buf[k++] = (unsigned char) (len >> 24);
buf[k++] = (unsigned char) (len >> 16);
buf[k++] = (unsigned char) (len >> 8);
buf[k++] = (unsigned char) len;
strcpy((char *) buf + k, chartab[i].s);
k += len;
} else {
buf[k++] = (unsigned char) (chartab[i].w >> 24);
buf[k++] = (unsigned char) (chartab[i].w >> 16);
buf[k++] = (unsigned char) (chartab[i].w >> 8);
buf[k++] = (unsigned char) chartab[i].w;
buf[k++] = (unsigned char) (chartab[i].h >> 24);
buf[k++] = (unsigned char) (chartab[i].h >> 16);
buf[k++] = (unsigned char) (chartab[i].h >> 8);
buf[k++] = (unsigned char) chartab[i].h;
}
}
buf[k++] = 0; // end of buffer
shipout(buf, w, h);
}
static void
emit_top_expr(U *p)
{
if (car(p) == symbol(SETQ)) {
emit_expr(cadr(p));
emit_thick_space();
emit_char(SYMBOL_FONT, '=');
emit_thick_space();
emit_expr(caddr(p));
return;
}
if (istensor(p))
emit_tensor(p);
else
emit_expr(p);
}
static void
emit_expr(U *p)
{
if (car(p) == symbol(ADD)) {
save();
p1 = cdr(p);
if (isnegativeterm(car(p1))) {
emit_char(SYMBOL_FONT, '-');
//emit_very_thin_space();
}
emit_term(car(p1));
p1 = cdr(p1);
while (iscons(p1)) {
if (isnegativeterm(car(p1))) {
emit_thick_space();
emit_char(SYMBOL_FONT, '-');
emit_thick_space();
} else {
emit_thick_space();
emit_char(SYMBOL_FONT, '+');
emit_thick_space();
}
emit_term(car(p1));
p1 = cdr(p1);
}
restore();
} else {
if (isnegativeterm(p)) {
emit_char(SYMBOL_FONT, '-');
//emit_very_thin_space();
}
emit_term(p);
}
}
static void
emit_term(U *p)
{
int n;
if (car(p) == symbol(MULTIPLY)) {
n = count_denominators(p);
if (n && level == 0)
emit_fraction(p, n);
else
emit_multiply(p, n);
} else
emit_factor(p);
}
static int
isdenominator(U *p)
{
if (car(p) == symbol(POWER) && cadr(p) != symbol(E) && isnegativeterm(caddr(p)))
return 1;
else
return 0;
}
static int
count_denominators(U *p)
{
int count = 0;
U *q;
p = cdr(p);
while (iscons(p)) {
q = car(p);
if (isdenominator(q))
count++;
p = cdr(p);
}
return count;
}
// an integer factor is 2, 2^3, etc.
static int
count_integer_factors(U *p)
{
int n = 0;
while (iscons(p)) {
if (isintegerfactor(car(p)))
n++;
p = cdr(p);
}
return n;
}
// n is the number of denominators, not counting a fraction like 1/2
static void
emit_multiply(U *p, int n)
{
int k;
if (n == 0) {
p = cdr(p);
if (isplusone(car(p)) || isminusone(car(p)))
p = cdr(p);
k = count_integer_factors(p);
emit_factor(car(p));
p = cdr(p);
while (iscons(p)) {
if (k > 1) {
emit_thin_space();
emit_char(SYMBOL_FONT, 180); // multiplication symbol
emit_thin_space();
} else {
#ifndef MAC
emit_thin_space_maybe(); // because sigma sqrt(a + b) looks funny
#endif
emit_thin_space();
}
emit_factor(car(p));
p = cdr(p);
}
} else {
emit_numerators(p);
emit_char(TIMES_FONT, '/');
// need grouping if more than one denominator
if (n > 1 || isfraction(cadr(p))) {
emit_char(TIMES_FONT, '(');
emit_denominators(p);
emit_char(TIMES_FONT, ')');
} else
emit_denominators(p);
}
}
#define A p3
#define B p4
// sign of term has already been emitted
static void
emit_fraction(U *p, int d)
{
int count, k1, k2, n, x;
emit_minus_sign_space();
save();
A = one;
B = one;
// handle numerical coefficient
if (isrational(cadr(p))) {
push(cadr(p));
mp_numerator();
absval();
A = pop();
push(cadr(p));
mp_denominator();
B = pop();
}
if (isdouble(cadr(p))) {
push(cadr(p));
absval();
A = pop();
}
// count numerators
if (isplusone(A))
n = 0;
else
n = 1;
p1 = cdr(p);
if (isnum(car(p1)))
p1 = cdr(p1);
while (iscons(p1)) {
p2 = car(p1);
if (isdenominator(p2))
;
else
n++;
p1 = cdr(p1);
}
// emit numerators
x = xpos;
k1 = indx;
count = 0;
// emit numerical coefficient
if (!isplusone(A)) {
cm_emit_number(A, 0);
count++;
}
// skip over "multiply"
p1 = cdr(p);
// skip over numerical coefficient, already handled
if (isnum(car(p1)))
p1 = cdr(p1);
while (iscons(p1)) {
p2 = car(p1);
if (isdenominator(p2))
;
else {
if (count > 0)
emit_thin_space();
if (n == 1)
emit_expr(p2);
else
emit_factor(p2);
count++;
}
p1 = cdr(p1);
}
if (count == 0)
emit_char(TIMES_FONT, '1');
// emit denominators
k2 = indx;
count = 0;
if (!isplusone(B)) {
cm_emit_number(B, 0);
count++;
d++;
}
p1 = cdr(p);
if (isrational(car(p1)))
p1 = cdr(p1);
while (iscons(p1)) {
p2 = car(p1);
if (isdenominator(p2)) {
if (count > 0)
emit_thin_space();
emit_denominator(p2, d);
count++;
}
p1 = cdr(p1);
}
cm_fixup_fraction(x, k1, k2);
restore();
}
// p points to a multiply
static void
emit_numerators(U *p)
{
int n;
save();
p1 = one;
p = cdr(p);
if (isrational(car(p))) {
push(car(p));
mp_numerator();
absval();
p1 = pop();
p = cdr(p);
} else if (isdouble(car(p))) {
push(car(p));
absval();
p1 = pop();
p = cdr(p);
}
n = 0;
if (!isplusone(p1)) {
cm_emit_number(p1, 0);
n++;
}
while (iscons(p)) {
if (isdenominator(car(p)))
;
else {
if (n > 0)
emit_thin_space();
emit_factor(car(p));
n++;
}
p = cdr(p);
}
if (n == 0)
emit_char(TIMES_FONT, '1');
restore();
}
// p points to a multiply
static void
emit_denominators(U *p)
{
int n;
save();
n = 0;
p = cdr(p);
if (isfraction(car(p))) {
push(car(p));
mp_denominator();
p1 = pop();
cm_emit_number(p1, 0);
n++;
p = cdr(p);
}
while (iscons(p)) {
if (isdenominator(car(p))) {
if (n > 0)
emit_thin_space();
emit_denominator(car(p), 0);
n++;
}
p = cdr(p);
}
restore();
}
static void
emit_factor(U *p)
{
if (istensor(p)) {
if (level == 0)
emit_tensor(p);
else
emit_flat_tensor(p);
return;
}
if (isdouble(p)) {
cm_emit_number(p, 0);
return;
}
if (car(p) == symbol(ADD) || car(p) == symbol(MULTIPLY)) {
emit_subexpr(p);
return;
}
if (car(p) == symbol(POWER)) {
emit_power(p);
return;
}
if (iscons(p)) {
emit_function(p);
return;
}
if (isnum(p)) {
if (level == 0)
emit_numerical_fraction(p);
else
cm_emit_number(p, 0);
return;
}
if (isstr(p)) {
emit_string(p);
return;
}
emit_symbol(p);
}
static void
emit_numerical_fraction(U *p)
{
int k1, k2, x;
save();
push(p);
mp_numerator();
absval();
A = pop();
push(p);
mp_denominator();
B = pop();
if (isplusone(B)) {
cm_emit_number(A, 0);
restore();
return;
}
level++;
emit_minus_sign_space();
x = xpos;
k1 = indx;
cm_emit_number(A, 0);
k2 = indx;
cm_emit_number(B, 0);
level--;
cm_fixup_fraction(x, k1, k2);
restore();
}
// if it's a factor then it doesn't need parens around it, i.e. 1/sin(theta)^2
static int
isfactor(U *p)
{
if (iscons(p) && car(p) != symbol(ADD) && car(p) != symbol(MULTIPLY) && car(p) != symbol(POWER))
return 1;
if (issymbol(p))
return 1;
if (isfraction(p))
return 0;
if (isnegativenumber(p))
return 0;
if (isnum(p))
return 1;
if (istensor(p))
return 1;
return 0;
}
static void
emit_power(U *p)
{
int k1, k2, x;
if (cadr(p) == symbol(E)) {
emit_str(TIMES_FONT, "exp");
emit_char(TIMES_FONT, '(');
emit_expr(caddr(p));
emit_char(TIMES_FONT, ')');
return;
}
if (equal(p, imaginaryunit)) {
emit_char(ITALIC_TIMES_FONT, 'i');
return;
}
// special case: level > 0 which means we are already in a superscript
//
// 1
// so, display something like x^(-1) as 1/x instead of ---
// x
if (level > 0 && isminusone(caddr(p))) {
emit_char(TIMES_FONT, '1');
emit_char(TIMES_FONT, '/');
if (isfactor(cadr(p)))
emit_factor(cadr(p));
else
emit_subexpr(cadr(p));
return;
}
// special case: something like x^(-1)
//
// 1 -1
// display as --- instead of x
// x
if (isnegativeterm(caddr(p))) {
emit_minus_sign_space();
x = xpos;
k1 = indx;
emit_char(TIMES_FONT, '1');
k2 = indx;
emit_denominator(p, 1);
cm_fixup_fraction(x, k1, k2);
return;
}
k1 = indx;
if (isfactor(cadr(p)))
emit_factor(cadr(p));
else
emit_subexpr(cadr(p));
k2 = indx;
level++;
emit_thin_space_maybe();
emit_expr(caddr(p));
level--;
fixup_power(k1, k2);
}
// if n == 1 then emit as expr (no parens)
// p is a power
static void
emit_denominator(U *p, int n)
{
int k1, k2;
// special case: 1 over something
if (isminusone(caddr(p))) {
if (n == 1)
emit_expr(cadr(p));
else
emit_factor(cadr(p));
return;
}
k1 = indx;
// emit base
if (isfactor(cadr(p)))
emit_factor(cadr(p));
else
emit_subexpr(cadr(p));
k2 = indx;
// emit exponent, don't emit minus sign
// to get here, caddr(p) must be a term, not an expr
// see isdenominator()
level++;
emit_thin_space_maybe();
emit_term(caddr(p));
level--;
fixup_power(k1, k2);
}
static void
emit_function(U *p)
{
if (car(p) == symbol(INDEX) && issymbol(cadr(p))) {
emit_index_function(p);
return;
}
if (car(p) == symbol(FACTORIAL)) {
emit_factorial_function(p);
return;
}
emit_symbol(car(p));
emit_char(TIMES_FONT, '(');
p = cdr(p);
if (iscons(p)) {
emit_expr(car(p));
p = cdr(p);
while (iscons(p)) {
emit_char(TIMES_FONT, ',');
emit_expr(car(p));
p = cdr(p);
}
}
emit_char(TIMES_FONT, ')');
}
static void
emit_index_function(U *p)
{
p = cdr(p);
if (caar(p) == symbol(ADD) || caar(p) == symbol(MULTIPLY) || caar(p) == symbol(POWER) || caar(p) == symbol(FACTORIAL))
emit_subexpr(car(p));
else
emit_expr(car(p));
emit_char(TIMES_FONT, '[');
p = cdr(p);
if (iscons(p)) {
emit_expr(car(p));
p = cdr(p);
while(iscons(p)) {
emit_char(TIMES_FONT, ',');
emit_expr(car(p));
p = cdr(p);
}
}
emit_char(TIMES_FONT, ']');
}
static void
emit_factorial_function(U *p)
{
p = cadr(p);
if (car(p) == symbol(ADD) || car(p) == symbol(MULTIPLY) || car(p) == symbol(POWER) || car(p) == symbol(FACTORIAL))
emit_subexpr(p);
else
emit_expr(p);
emit_char(TIMES_FONT, '!');
}
static void
emit_subexpr(U *p)
{
emit_char(TIMES_FONT, '(');
emit_expr(p);
emit_char(TIMES_FONT, ')');
}
static void
emit_symbol(U *p)
{
int i, k, n, w;
char *s;
if (!issymbol(p)) { // should not get here, but just in case...
emit_char(TIMES_FONT, '(');
emit_expr(p);
emit_char(TIMES_FONT, ')');
return;
}
if (p == symbol(E)) {
emit_str(TIMES_FONT, "exp");
emit_char(TIMES_FONT, '(');
emit_char(TIMES_FONT, '1');
emit_char(TIMES_FONT, ')');
return;
}
if (p == symbol(DERIVATIVE)) {
emit_char(SYMBOL_FONT, 182);
return;
}
s = get_printname(p);
if (symbol_index(p) < PI) {
emit_str(TIMES_FONT, s);
return;
}
// parse greek letters
k = indx;
n = isgreek(s);
if (n == -1)
if (isalpha(*s))
emit_char(ITALIC_TIMES_FONT, *s++);
else
emit_char(TIMES_FONT, *s++);
else {
emit_char(greek[n].font, greek[n].n);
s += strlen(greek[n].s);
}
// feynman slash?
if (strncmp(s, "slash", 5) == 0) {
emit_char(SYMBOL_FONT, '/');
w = chartab[k].w - chartab[indx - 1].w;
chartab[indx - 1].x = chartab[k].x + w / 2;
xpos = chartab[k].x + chartab[k].w;
s += 5;
}
k = indx;
level++;
while (*s) {
n = isgreek(s);
if (n == -1)
if (isalpha(*s))
emit_char(ITALIC_TIMES_FONT, *s++);
else
emit_char(TIMES_FONT, *s++);
else {
emit_char(greek[n].font, greek[n].n);
s += strlen(greek[n].s);
}
}
level--;
// subscript
for (i = k; i < indx; i++)
chartab[i].y += subscript_dy();
}
static void
emit_string(U *p)
{
//emit_str(TIMES_FONT, "\"");
emit_str(TIMES_FONT, p->u.str);
//emit_str(TIMES_FONT, "\"");
}
static void
cm_fixup_fraction(int x, int k1, int k2)
{
int dx, dy, w;
int h1, w1, y1;
int h2, w2, y2;
get_size(k1, k2, &h1, &w1, &y1);
get_size(k2, indx, &h2, &w2, &y2);
// We want to shift the numerator horizontally so it's centered.
// Rounding up looks better.
if (w2 > w1)
dx = (w2 - w1 + 1) / 2; // shift numerator right
else
dx = 0;
// We want to shift the numerator vertically so it's above the rule.
//
// h1 + shim + mheight = 1 - y
//
// h1 + shim + mheight = 1 - y1 - dy
//
// dy = 1 - y1 - h1 - shim - mheight
dy = 1 - y1 - h1 - shim() - mheight();
move(k1, k2, dx, dy);
if (w2 > w1)
dx = -w1;
else
dx = -w1 + (w1 - w2) / 2;
// y2 + dy = -mheight + hrule_thickness + shim
//
// dy = -mheight + hrule_thickness + shim - y2
dy = -mheight() + shim() - y2;
move(k2, indx, dx, dy);
if (w2 > w1)
w = w2;
else
w = w1;
xpos = x;
emit_hrule(w);
}
// Indices k1 through k2-1 are the glyph on the left.
//
// Indices k2 through index-1 are the glyph to be superscripted.
static void
fixup_power(int k1, int k2)
{
int dy;
int h1, w1, y1;
int h2, w2, y2;
get_size(k1, k2, &h1, &w1, &y1);
get_size(k2, indx, &h2, &w2, &y2);
// move bottom of superscript to x height
//
// Example:
//
// y = -9 **** ]
// -8 * ]
// -7 *** ] h2 = 5
// -6 * ]
// -5 **** ]
// -4 ]
// -3 ]
// -2 ] xheight = 5
// -1 ]
// 0 ]
// 1
//
// want y such that
//
// h2 + xheight = 1 - y
//
// already have y2 which we need to adjust by dy so that y = y2 + dy
//
// h2 + xheight = 1 - y2 - dy
//
// dy = 1 - y2 - h2 - xheight
dy = 1 - y2 - h2 - xheight();
// if y1 < y2 + dy then boost up some more
if (y1 < y2 + dy)
dy = y1 - y2;
move(k2, indx, 0, dy);
}
static void
move(int j, int k, int dx, int dy)
{
int i;
for (i = j; i < k; i++) {
chartab[i].x += dx;
chartab[i].y += dy;
}
}
// finds the bounding rectangle and vertical position
static void
get_size(int j, int k, int *h, int *w, int *y)
{
int i;
int min_x, max_x, min_y, max_y;
min_x = chartab[j].x;
max_x = chartab[j].x + chartab[j].w - 1;
min_y = chartab[j].y;
max_y = chartab[j].y + chartab[j].h - 1;
for (i = j + 1; i < k; i++) {
if (chartab[i].x < min_x)
min_x = chartab[i].x;
if (chartab[i].x + chartab[i].w - 1 > max_x)
max_x = chartab[i].x + chartab[i].w - 1;
if (chartab[i].y < min_y)
min_y = chartab[i].y;
if (chartab[i].y + chartab[i].h - 1 > max_y)
max_y = chartab[i].y + chartab[i].h - 1;
}
*h = max_y - min_y + 1;
*w = max_x - min_x + 1;
*y = min_y;
}
static void
emit_char(int font, int c)
{
char s[2];
s[0] = c;
s[1] = 0;
emit_str(font, s);
}
static void
emit_str(int font, char *s)
{
int h, w;
if (indx == YMAX)
stop("The result has more than 10,000 symbols.\nPlease use print() or set tty = 1 and then repeat the computation.");
if (strlen(s) > 4000)
stop("The result has more than 4,000 digits.\nPlease use print() or set tty = 1 and then repeat the computation.");
if (*s == '(' || *s == ')' || *s == '[' || *s == ']')
emit_paren_space_maybe();
if (level)
font += 4;
get_height_width(&h, &w, font, s);
if (chartab[indx].s)
free(chartab[indx].s);
chartab[indx].s = strdup(s);
chartab[indx].cmd = font;
chartab[indx].x = xpos;
chartab[indx].y = -text_metric[font].ascent;
//chartab[indx].h = text_metric[font].ascent + text_metric[font].descent;
//chartab[indx].w = text_width(font, s);
chartab[indx].h = h;
chartab[indx].w = w;
xpos += chartab[indx].w;
indx++;
}
static void
cm_emit_number(U *p, int emit_sign)
{
int i, k1, k2, len;
char *s;
static char buf[100];
switch (p->k) {
case NUM:
s = mstr(p->u.q.a);
if (*s == '-' && emit_sign == 0)
s++;
emit_str(TIMES_FONT, s);
s = mstr(p->u.q.b);
if (strcmp(s, "1") == 0)
break;
emit_char(TIMES_FONT, '/');
emit_str(TIMES_FONT, s);
break;
case DOUBLE:
len = sprintf(buf, "%g", p->u.d);
for (i = 0; i < len; i++)
if (buf[i] == 'E' || buf[i] == 'e') {
buf[i] = 0;
break;
}
s = buf;
if (*s == '-' && emit_sign == 0)
s++;
emit_str(TIMES_FONT, s);
if (i < len) {
s = buf + i + 1;
emit_char(SYMBOL_FONT, 180);
k1 = indx;
emit_str(TIMES_FONT, "10");
k2 = indx;
level++;
if (*s == '+')
s++;
else if (*s == '-') {
emit_char(SYMBOL_FONT, '-');
s++;
}
// don't emit leading zeroes
while (*s && *s == '0')
s++;
if (*s)
emit_str(TIMES_FONT, s);
level--;
fixup_power(k1, k2);
}
break;
default:
break;
}
}
#define SPACE_BETWEEN_COLUMNS 20
#define SPACE_BETWEEN_ROWS 20
static void
emit_tensor(U *p)
{
int h, i, j, k, n, w, x, y;
int nrow, ncol;
int dw, dx, dy;
int col_width[100], ymin[100], ymax[100];
struct {
int y, h, w, index1, index2;
} elem[10000];
if (p->u.tensor->ndim > 2)
stop("Tensor rank > 2 in display().\nTry using print() or set tty=1.");
nrow = p->u.tensor->dim[0];
if (p->u.tensor->ndim == 2)
ncol = p->u.tensor->dim[1];
else
ncol = 1;
n = nrow * ncol;
if (nrow > 100 || ncol > 100)
stop("too many tensor components to display, try using print() or set tty=1");
emit_char(TIMES_FONT, '(');
// horizontal coordinate of the matrix
x = xpos;
// emit each component
for (i = 0; i < n; i++) {
elem[i].index1 = indx;
emit_expr(p->u.tensor->elem[i]);
elem[i].index2 = indx;
get_size(elem[i].index1, indx, &elem[i].h, &elem[i].w, &elem[i].y);
xpos = x; // put all components at the same x
}
// calculate the height of each row
for (i = 0; i < nrow; i++) {
ymin[i] = elem[i * ncol].y;
ymax[i] = elem[i * ncol].y + elem[i * ncol].h;
for (j = 1; j < ncol; j++) {
k = i * ncol + j;
if (elem[k].y < ymin[i])
ymin[i] = elem[k].y;
y = elem[k].y + elem[k].h;
if (y > ymax[i])
ymax[i] = y;
}
}
// calculate the width of each column
for (i = 0; i < ncol; i++) {
col_width[i] = elem[i].w;
for (j = 1; j < nrow; j++) {
k = j * ncol + i;
if (elem[k].w > col_width[i])
col_width[i] = elem[k].w;
}
}
// this is the overall height of the matrix
h = (nrow - 1) * SPACE_BETWEEN_ROWS;
for (i = 0; i < nrow; i++)
h += ymax[i] - ymin[i];
// this is the overall width of the matrix
w = (ncol - 1) * SPACE_BETWEEN_COLUMNS;
for (i = 0; i < ncol; i++)
w += col_width[i];
// this is the y coordinate of the entire tensor
y = -(h / 2) - mheight();
// move rows up and down
for (i = 0; i < nrow; i++) {
dy = y - ymin[i];
for (j = 0; j < ncol; j++) {
k = i * ncol + j;
move(elem[k].index1, elem[k].index2, 0, dy);
}
y += ymax[i] - ymin[i] + SPACE_BETWEEN_ROWS;
}
// move columns to the right
dw = 0;
for (i = 0; i < nrow; i++) {
dw = 0;
for (j = 0; j < ncol; j++) {
k = i * ncol + j;
dx = dw + (col_width[j] - elem[k].w) / 2;
move(elem[k].index1, elem[k].index2, dx, 0);
dw += col_width[j] + SPACE_BETWEEN_COLUMNS;
}
}
xpos = x + w;
emit_char(TIMES_FONT, ')');
}
static void
emit_flat_tensor(U *p)
{
int k = 0;
emit_tensor_inner(p, 0, &k);
}
static void
emit_tensor_inner(U *p, int j, int *k)
{
int i;
emit_char(TIMES_FONT, '(');
for (i = 0; i < p->u.tensor->dim[j]; i++) {
if (j + 1 == p->u.tensor->ndim) {
emit_expr(p->u.tensor->elem[*k]);
*k = *k + 1;
} else
emit_tensor_inner(p, j + 1, k);
if (i + 1 < p->u.tensor->dim[j])
emit_char(TIMES_FONT, ',');
}
emit_char(TIMES_FONT, ')');
}
static void
emit_hrule(int w)
{
if (indx == YMAX)
stop("display buffer overflow, try using print() or set tty=1");
if (chartab[indx].s)
free(chartab[indx].s);
chartab[indx].s = NULL;
chartab[indx].cmd = DRAW_HRULE;
chartab[indx].x = xpos;
chartab[indx].y = -mheight();
chartab[indx].w = w;
chartab[indx].h = hrule_thickness();
xpos += w;
indx++;
}
static void
emit_space(int w)
{
if (indx == YMAX)
stop("The result has more than 10,000 symbols.\nPlease use print() or set tty = 1 and then repeat the computation.");
if (chartab[indx].s) {
free(chartab[indx].s);
chartab[indx].s = 0;
}
chartab[indx].cmd = 32;
chartab[indx].x = xpos;
chartab[indx].y = 0;
chartab[indx].h = 0;
chartab[indx].w = w;
xpos += w;
indx++;
}
static void
emit_thick_space(void)
{
int w;
if (level == 0)
w = text_metric[TIMES_FONT].width;
else
w = text_metric[SMALL_TIMES_FONT].width;
emit_space(w);
}
static void
emit_thin_space(void)
{
int w;
if (level == 0)
w = text_metric[TIMES_FONT].width / 2;
else
w = text_metric[SMALL_TIMES_FONT].width / 2;
emit_space(w);
}
// if minus sign previously then emit thin space
static void
emit_minus_sign_space(void)
{
if (indx && chartab[indx - 1].s && strcmp(chartab[indx - 1].s, "-") == 0)
emit_thin_space();
}
// some chars need a little extra space before superscripts
static void
emit_thin_space_maybe(void)
{
int c, cmd, len;
if (indx == 0 || chartab[indx - 1].s == 0)
return;
len = (int) strlen(chartab[indx - 1].s);
if (len == 0)
return;
c = chartab[indx - 1].s[len - 1];
cmd = chartab[indx - 1].cmd;
if (cmd == ITALIC_SYMBOL_FONT || cmd == SMALL_ITALIC_SYMBOL_FONT) {
if (c == 108 || c == 119) // lambda or omega
;
else
emit_thin_space();
return;
}
if (cmd == SYMBOL_FONT || cmd == SMALL_SYMBOL_FONT) {
if (c == 68 || c == 76) // Lambda or Delta
;
else
emit_thin_space();
return;
}
if (cmd == ITALIC_TIMES_FONT || cmd == SMALL_ITALIC_TIMES_FONT) {
#ifdef MAC
emit_thin_space();
#else
if (c == 'f')
emit_thick_space();
#endif
return;
}
if (cmd == TIMES_FONT || cmd == SMALL_TIMES_FONT) {
if (c >= '0' || c <= '9')
; // numerals are ok
else
; // placeholder
return;
}
}
// some chars need a little extra space before parens (and braces)
static void
emit_paren_space_maybe(void)
{
#ifndef MAC
int c, cmd, len;
if (indx == 0 || chartab[indx - 1].s == 0)
return;
len = (int) strlen(chartab[indx - 1].s);
if (len == 0)
return;
c = chartab[indx - 1].s[len - 1];
cmd = chartab[indx - 1].cmd;
if (cmd == ITALIC_SYMBOL_FONT || cmd == SMALL_ITALIC_SYMBOL_FONT) {
switch (c) {
case 98: // beta
case 100: // delta
case 102: // phi
case 113: // theta
case 120: // xi
case 122: // zeta
emit_thin_space();
break;
default:
break;
}
return;
}
if (cmd == SYMBOL_FONT || cmd == SMALL_SYMBOL_FONT) {
return;
}
if (cmd == ITALIC_TIMES_FONT || cmd == SMALL_ITALIC_TIMES_FONT) {
if (c == 'f')
emit_thin_space();
return;
}
if (cmd == TIMES_FONT || cmd == SMALL_TIMES_FONT) {
return;
}
#endif
}
static int
xheight(void)
{
if (level == 0)
return text_metric[TIMES_FONT].ascent / 3;
else
return text_metric[SMALL_TIMES_FONT].ascent / 3;
}
static int
subscript_dy(void)
{
if (level == 0)
return text_metric[TIMES_FONT].ascent / 3 + 1;
else
return text_metric[SMALL_TIMES_FONT].ascent / 3 + 1;
}
// math height
static int
mheight(void)
{
#ifdef MAC
if (level == 0)
return 7;
else
return 5;
#else
if (level == 0)
return 8;
else
return 6;
#endif
}
static int
shim(void)
{
return 2;
}
static int
hrule_thickness(void)
{
return 1;
}
static int
isgreek(char *s)
{
int i;
for (i = 0; i < NGREEK; i++)
if (strncmp(greek[i].s, s, strlen(greek[i].s)) == 0)
return i;
return -1;
}
// for each pair of delimiters adjust the size if necessary
static void
do_groups(void)
{
int i, j, n;
for (i = 0; i < indx; i++) {
if (chartab[i].s == NULL)
continue;
if (strcmp(chartab[i].s, "(") != 0)
continue;
n = 0;
for (j = i + 1; j < indx; j++) {
if (chartab[j].s == NULL)
continue;
if (strcmp(chartab[j].s, "(") == 0) {
n++;
continue;
}
if (strcmp(chartab[j].s, ")") != 0)
continue;
if (n) {
n--;
continue;
}
do_group(i, j);
break;
}
}
}
// grow delimiters, maybe
static void
do_group(int i, int j)
{
int h, w, y, ymin, ymax;
get_size(i, j, &h, &w, &y);
ymin = chartab[i].y - chartab[i].h / 2;
ymax = chartab[i].y + chartab[i].h + chartab[i].h / 3;
if (y < ymin || y + h > ymax) {
free(chartab[i].s);
free(chartab[j].s);
chartab[i].s = NULL;
chartab[j].s = NULL;
chartab[i].cmd = DRAW_LEFT_DELIMETER;
chartab[j].cmd = DRAW_RIGHT_DELIMETER;
chartab[i].y = y;
chartab[j].y = y;
chartab[i].h = h;
chartab[j].h = h;
}
}
// scan chartab and normalize y so the minimum y is 0
static void
normy(void)
{
int i, miny;
miny = chartab[0].y;
for (i = 1; i < indx; i++)
if (chartab[i].y < miny)
miny = chartab[i].y;
for (i = 0; i < indx; i++)
chartab[i].y -= miny;
}
static char *s[] = {
"format=1",
"",
"((a,b),(c,d))",
"a b\n"
"\n"
"c d",
"1/sqrt(-15)",
" i\n"
"- -----------\n"
" 1/2 1/2\n"
" 3 5",
};
void
test_cmdisplay(void)
{
test(__FILE__, s, sizeof s / sizeof (char *));
}