version 1.7, 2014/09/28 20:14:20 |
version 1.13, 2017/06/23 02:32:12 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
* Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> |
|
* Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
#include <sys/types.h> |
#include <sys/types.h> |
|
|
#include <assert.h> |
#include <assert.h> |
|
#include <ctype.h> |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
|
#include "out.h" |
#include "out.h" |
#include "html.h" |
#include "html.h" |
|
|
static const enum htmltag fontmap[EQNFONT__MAX] = { |
static void |
TAG_SPAN, /* EQNFONT_NONE */ |
eqn_box(struct html *p, const struct eqn_box *bp) |
TAG_SPAN, /* EQNFONT_ROMAN */ |
|
TAG_B, /* EQNFONT_BOLD */ |
|
TAG_B, /* EQNFONT_FAT */ |
|
TAG_I /* EQNFONT_ITALIC */ |
|
}; |
|
|
|
static const struct eqn_box * |
|
eqn_box(struct html *, const struct eqn_box *, int); |
|
|
|
|
|
void |
|
print_eqn(struct html *p, const struct eqn *ep) |
|
{ |
{ |
struct htmlpair tag; |
struct tag *post, *row, *cell, *t; |
struct tag *t; |
const struct eqn_box *child, *parent; |
|
const unsigned char *cp; |
|
size_t i, j, rows; |
|
enum htmltag tag; |
|
enum eqn_fontt font; |
|
|
PAIR_CLASS_INIT(&tag, "eqn"); |
|
t = print_otag(p, TAG_MATH, 1, &tag); |
|
|
|
p->flags |= HTML_NONOSPACE; |
|
eqn_box(p, ep->root, 1); |
|
p->flags &= ~HTML_NONOSPACE; |
|
|
|
print_tagq(p, t); |
|
} |
|
|
|
/* |
|
* This function is fairly brittle. |
|
* This is because the eqn syntax doesn't play so nicely with recusive |
|
* formats, e.g., |
|
* foo sub bar sub baz |
|
* ...needs to resolve into |
|
* <msub> foo <msub> bar, baz </msub> </msub> |
|
* In other words, we need to embed some recursive work. |
|
* FIXME: this does NOT handle right-left associativity or precedence! |
|
*/ |
|
static const struct eqn_box * |
|
eqn_box(struct html *p, const struct eqn_box *bp, int next) |
|
{ |
|
struct tag *post, *pilet, *tmp; |
|
struct htmlpair tag[2]; |
|
int skiptwo; |
|
|
|
if (NULL == bp) |
if (NULL == bp) |
return(NULL); |
return; |
|
|
post = pilet = NULL; |
post = NULL; |
skiptwo = 0; |
|
|
|
/* |
/* |
* If we're a "row" under a pile, then open up the piling |
* Special handling for a matrix, which is presented to us in |
* context here. |
* column order, but must be printed in row-order. |
* We do this first because the pile surrounds the content of |
|
* the contained expression. |
|
*/ |
*/ |
if (NULL != bp->parent && bp->parent->pile != EQNPILE_NONE) { |
if (EQN_MATRIX == bp->type) { |
pilet = print_otag(p, TAG_MTR, 0, NULL); |
if (NULL == bp->first) |
print_otag(p, TAG_MTD, 0, NULL); |
goto out; |
|
if (EQN_LIST != bp->first->type) { |
|
eqn_box(p, bp->first); |
|
goto out; |
|
} |
|
if (NULL == (parent = bp->first->first)) |
|
goto out; |
|
/* Estimate the number of rows, first. */ |
|
if (NULL == (child = parent->first)) |
|
goto out; |
|
for (rows = 0; NULL != child; rows++) |
|
child = child->next; |
|
/* Print row-by-row. */ |
|
post = print_otag(p, TAG_MTABLE, ""); |
|
for (i = 0; i < rows; i++) { |
|
parent = bp->first->first; |
|
row = print_otag(p, TAG_MTR, ""); |
|
while (NULL != parent) { |
|
child = parent->first; |
|
for (j = 0; j < i; j++) { |
|
if (NULL == child) |
|
break; |
|
child = child->next; |
|
} |
|
cell = print_otag(p, TAG_MTD, ""); |
|
/* |
|
* If we have no data for this |
|
* particular cell, then print a |
|
* placeholder and continue--don't puke. |
|
*/ |
|
if (NULL != child) |
|
eqn_box(p, child->first); |
|
print_tagq(p, cell); |
|
parent = parent->next; |
|
} |
|
print_tagq(p, row); |
|
} |
|
goto out; |
} |
} |
if (NULL != bp->parent && bp->parent->type == EQN_MATRIX) { |
|
pilet = print_otag(p, TAG_MTABLE, 0, NULL); |
|
print_otag(p, TAG_MTR, 0, NULL); |
|
print_otag(p, TAG_MTD, 0, NULL); |
|
} |
|
|
|
/* |
|
* If we're establishing a pile, start the table mode now. |
|
* If we've already in a pile row, then don't override "pilet", |
|
* because we'll be closed out anyway. |
|
*/ |
|
if (bp->pile != EQNPILE_NONE) { |
|
tmp = print_otag(p, TAG_MTABLE, 0, NULL); |
|
pilet = (NULL == pilet) ? tmp : pilet; |
|
} |
|
|
|
/* |
|
* Positioning. |
|
* This is the most complicated part, and actually doesn't quite |
|
* work (FIXME) because it doesn't account for associativity. |
|
* Setting "post" will mean that we're only going to process a |
|
* single or double following expression. |
|
*/ |
|
switch (bp->pos) { |
switch (bp->pos) { |
case (EQNPOS_TO): |
case EQNPOS_TO: |
post = print_otag(p, TAG_MOVER, 0, NULL); |
post = print_otag(p, TAG_MOVER, ""); |
break; |
break; |
case (EQNPOS_SUP): |
case EQNPOS_SUP: |
post = print_otag(p, TAG_MSUP, 0, NULL); |
post = print_otag(p, TAG_MSUP, ""); |
break; |
break; |
case (EQNPOS_FROM): |
case EQNPOS_FROM: |
post = print_otag(p, TAG_MUNDER, 0, NULL); |
post = print_otag(p, TAG_MUNDER, ""); |
break; |
break; |
case (EQNPOS_SUB): |
case EQNPOS_SUB: |
post = print_otag(p, TAG_MSUB, 0, NULL); |
post = print_otag(p, TAG_MSUB, ""); |
break; |
break; |
case (EQNPOS_OVER): |
case EQNPOS_OVER: |
post = print_otag(p, TAG_MFRAC, 0, NULL); |
post = print_otag(p, TAG_MFRAC, ""); |
break; |
break; |
case (EQNPOS_FROMTO): |
case EQNPOS_FROMTO: |
post = print_otag(p, TAG_MUNDEROVER, 0, NULL); |
post = print_otag(p, TAG_MUNDEROVER, ""); |
skiptwo = 1; |
|
break; |
break; |
case (EQNPOS_SUBSUP): |
case EQNPOS_SUBSUP: |
post = print_otag(p, TAG_MSUBSUP, 0, NULL); |
post = print_otag(p, TAG_MSUBSUP, ""); |
skiptwo = 1; |
|
break; |
break; |
|
case EQNPOS_SQRT: |
|
post = print_otag(p, TAG_MSQRT, ""); |
|
break; |
default: |
default: |
break; |
break; |
} |
} |
|
|
/*t = EQNFONT_NONE == bp->font ? NULL : |
if (bp->top || bp->bottom) { |
print_otag(p, fontmap[(int)bp->font], 0, NULL);*/ |
assert(NULL == post); |
|
if (bp->top && NULL == bp->bottom) |
|
post = print_otag(p, TAG_MOVER, ""); |
|
else if (bp->top && bp->bottom) |
|
post = print_otag(p, TAG_MUNDEROVER, ""); |
|
else if (bp->bottom) |
|
post = print_otag(p, TAG_MUNDER, ""); |
|
} |
|
|
if (NULL != bp->text) { |
if (EQN_PILE == bp->type) { |
assert(NULL == bp->first); |
assert(NULL == post); |
/* |
if (bp->first != NULL && bp->first->type == EQN_LIST) |
* We have text. |
post = print_otag(p, TAG_MTABLE, ""); |
* This can be a number, a function, a variable, or |
} else if (bp->type == EQN_LIST && |
* pretty much anything else. |
bp->parent && bp->parent->type == EQN_PILE) { |
* First, check for some known functions. |
assert(NULL == post); |
* If we're going to create a structural node (e.g., |
post = print_otag(p, TAG_MTR, ""); |
* sqrt), then set the "post" variable only if it's not |
print_otag(p, TAG_MTD, ""); |
* already set. |
} |
*/ |
|
if (0 == strcmp(bp->text, "sqrt")) { |
if (bp->text != NULL) { |
tmp = print_otag(p, TAG_MSQRT, 0, NULL); |
assert(post == NULL); |
post = (NULL == post) ? tmp : post; |
tag = TAG_MI; |
} else if (0 == strcmp(bp->text, "+") || |
cp = (unsigned char *)bp->text; |
0 == strcmp(bp->text, "-") || |
if (isdigit(cp[0]) || (cp[0] == '.' && isdigit(cp[1]))) { |
0 == strcmp(bp->text, "=") || |
tag = TAG_MN; |
0 == strcmp(bp->text, "(") || |
while (*++cp != '\0') { |
0 == strcmp(bp->text, ")") || |
if (*cp != '.' && !isdigit(*cp)) { |
0 == strcmp(bp->text, "/")) { |
tag = TAG_MI; |
tmp = print_otag(p, TAG_MO, 0, NULL); |
break; |
print_text(p, bp->text); |
} |
print_tagq(p, tmp); |
} |
} else { |
} else if (*cp != '\0' && isalpha(*cp) == 0) { |
tmp = print_otag(p, TAG_MI, 0, NULL); |
tag = TAG_MO; |
print_text(p, bp->text); |
while (*++cp != '\0') { |
print_tagq(p, tmp); |
if (isalnum(*cp)) { |
|
tag = TAG_MI; |
|
break; |
|
} |
|
} |
} |
} |
} else if (NULL != bp->first) { |
font = bp->font; |
assert(NULL == bp->text); |
if (bp->text[0] != '\0' && |
/* |
(((tag == TAG_MN || tag == TAG_MO) && |
* If we're a "fenced" component (i.e., having |
font == EQNFONT_ROMAN) || |
* brackets), then process those brackets now. |
(tag == TAG_MI && font == (bp->text[1] == '\0' ? |
* Otherwise, introduce a dummy row (if we're not |
EQNFONT_ITALIC : EQNFONT_ROMAN)))) |
* already in a table context). |
font = EQNFONT_NONE; |
*/ |
switch (font) { |
tmp = NULL; |
case EQNFONT_NONE: |
if (NULL != bp->left || NULL != bp->right) { |
post = print_otag(p, tag, ""); |
PAIR_INIT(&tag[0], ATTR_OPEN, |
break; |
NULL != bp->left ? bp->left : ""); |
case EQNFONT_ROMAN: |
PAIR_INIT(&tag[1], ATTR_CLOSE, |
post = print_otag(p, tag, "?", "fontstyle", "normal"); |
NULL != bp->right ? bp->right : ""); |
break; |
tmp = print_otag(p, TAG_MFENCED, 2, tag); |
case EQNFONT_BOLD: |
print_otag(p, TAG_MROW, 0, NULL); |
case EQNFONT_FAT: |
} else if (NULL == pilet) |
post = print_otag(p, tag, "?", "fontweight", "bold"); |
tmp = print_otag(p, TAG_MROW, 0, NULL); |
break; |
eqn_box(p, bp->first, 1); |
case EQNFONT_ITALIC: |
if (NULL != tmp) |
post = print_otag(p, tag, "?", "fontstyle", "italic"); |
print_tagq(p, tmp); |
break; |
|
default: |
|
abort(); |
|
} |
|
print_text(p, bp->text); |
|
} else if (NULL == post) { |
|
if (NULL != bp->left || NULL != bp->right) |
|
post = print_otag(p, TAG_MFENCED, "??", |
|
"open", bp->left == NULL ? "" : bp->left, |
|
"close", bp->right == NULL ? "" : bp->right); |
|
if (NULL == post) |
|
post = print_otag(p, TAG_MROW, ""); |
|
else |
|
print_otag(p, TAG_MROW, ""); |
} |
} |
|
|
/* |
eqn_box(p, bp->first); |
* If a positional context, invoke the "next" context. |
|
* This is recursive and will return the end of the recursive |
out: |
* chain of "next" contexts. |
if (NULL != bp->bottom) { |
*/ |
t = print_otag(p, TAG_MO, ""); |
if (NULL != post) { |
print_text(p, bp->bottom); |
bp = eqn_box(p, bp->next, 0); |
print_tagq(p, t); |
if (skiptwo) |
|
bp = eqn_box(p, bp->next, 0); |
|
print_tagq(p, post); |
|
} |
} |
|
if (NULL != bp->top) { |
|
t = print_otag(p, TAG_MO, ""); |
|
print_text(p, bp->top); |
|
print_tagq(p, t); |
|
} |
|
|
/* |
if (NULL != post) |
* If we're being piled (either directly, in the table, or |
print_tagq(p, post); |
* indirectly in a table row), then close that out. |
|
*/ |
|
if (NULL != pilet) |
|
print_tagq(p, pilet); |
|
|
|
/* |
eqn_box(p, bp->next); |
* If we're normally processing, then grab the next node. |
} |
* If we're in a recursive context, then don't seek to the next |
|
* node; further recursion has already been handled. |
void |
*/ |
print_eqn(struct html *p, const struct eqn *ep) |
return(next ? eqn_box(p, bp->next, 1) : bp); |
{ |
|
struct tag *t; |
|
|
|
t = print_otag(p, TAG_MATH, "c", "eqn"); |
|
|
|
p->flags |= HTML_NONOSPACE; |
|
eqn_box(p, ep->root); |
|
p->flags &= ~HTML_NONOSPACE; |
|
|
|
print_tagq(p, t); |
} |
} |