/* Released under the Modified BSD license attached to Axiom sources. * TeX Display Math Mode Line Breaking Program * * Author: Robert S. Sutor * * Date: 1991 * * Change History: * * 01/19/92 RSS Change to use \[ \] instead of $$ $$ * * 09/01/92 RSS Format and fix =-. * * Operation: * * This program reads standard input and writes to standard output. Display math * mode starts with \[ at the beginning of a line and ends with \]. All lines * not in display math mode are simply printed on standard output. The * expressions in display math mode are broken so that they fit on a page * better (line breaking). * * The array stuff is being converted to use the array node type. * * Restrictions: * * 1. Assume \[ and \] start in column 1 and are the only things on the lines. * * 2. Comments in display math mode are not preserved. * * 3. This is meant to deal with output from the AXIOM computer algebra system. * Unpredictable results may occur if used with hand-generated TeX code or * TeX code generated by other programs. */ /* * Include files and #defines. */ #include "useproto.h" #include #include #include #include #define MATHBUFLEN 8*8192 #define MAXMATHTOKEN 80 #define MAXCHARSINLINE 60 #define FATDELIMMULT 2 #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif #define TRUE 1 #define FALSE 0 #define STRCHREQ(str,char) (str[0] == char) /* * Type declarations. */ enum nodeTypes { N_NODE, N_TEXT, N_ARRAY }; typedef struct listNodeStruct { struct listNodeStruct *nextListNode; struct listNodeStruct *prevListNode; enum nodeTypes nodeType; long width; long realWidth; union { char *text; struct listNodeStruct *node; struct arrayNodeStruct *array; } data; } listNode; typedef struct arrayNodeStruct { int cols; listNode *argsNode; listNode *entries; /* linked list of nodes, each pointing to a * row */ } arrayNode; /* * Global Variables. */ char line[1024]; char blanks[] = " "; char lastPrinted = '\0'; int indent = 0; char mathBuffer[MATHBUFLEN], mathToken[MAXMATHTOKEN]; char bufout[2*MATHBUFLEN]; int lineLen, mathBufferLen, mathBufferPtr, mathTokenLen; listNode *mathList; int charsOut, fatDelimiter; int maxLineWidth = 4500; /* 4.5 inches * 1000 */ int maxLineSlop = 0; /* 0.0 inches * 1000 */ int charTable[256]; int avgCharWidth; int spaceWidths[5], extraOverWidth; int arrayDepth = 0, arrayMaxDepth = 3; int charMultNum, charMultDenom; int sinWidth, cosWidth, tanWidth, erfWidth; long outLineNum = 0L; /* * Function Prototypes. */ #ifndef _NO_PROTO void arrayTooDeep(); int breakFracProd(listNode *, long, char *, char *, int); int breakFunction(listNode *, long); int breakList(listNode *, long); int breakMathList(listNode *, long); int breakNumber(listNode *, long); int breakPMEB(listNode *, long); int breakParen(listNode *, long); int bufferMathLines(); void buildMathList(); int charWidth(char); void computeNodeWidth(listNode *); long computeWidth(listNode *); void displaySplitMsg(char *, int); void error(char *, char *); void freeMathList(listNode *); void getOptions(int, char **); void initCharTable(); listNode *insertStringAfter(char *, listNode *); listNode *insertStringAtBack(char *, listNode *); listNode *insertStringAtFront(char *, listNode *); int newLineIfNecessary(int); listNode *newListNode(enum nodeTypes); int nextMathToken(); int printChar(char); int printMathList(listNode *, int); int printString(char *); void putcBuf(char, char []); void putsBuf(char [], char []); void resetCharMults(); listNode *string2NodeList(char *, listNode *); void ttCharMults(); #else void arrayTooDeep(); int breakFracProd(); int breakFunction(); int breakList(); int breakMathList(); int breakNumber(); int breakPMEB(); int breakParen(); int bufferMathLines(); void buildMathList(); int charWidth(); void computeNodeWidth(); long computeWidth(); void displaySplitMsg(); void error(); void freeMathList(); void getOptions(); void initCharTable(); listNode *insertStringAfter(); listNode *insertStringAtBack(); listNode *insertStringAtFront(); int newLineIfNecessary(); listNode *newListNode(); int nextMathToken(); int printChar(); int printMathList(); int printString(); void putsBuf(); void putcBuf(); void resetCharMults(); listNode *string2NodeList(); void ttCharMults(); #endif /* * Function Definitions. */ int #ifndef _NO_PROTO texbreak(char bufinp[]) #else texbreak(bufinp) char bufinp[]; #endif { initCharTable(); strcpy(bufout,""); mathBufferLen=strlen(bufinp); if (mathBufferLen > MATHBUFLEN) { fprintf(stderr, "mathBuffer too small. Need: %d\n", mathBufferLen); return 0; }; mathBufferPtr = 0; strcpy(mathBuffer,bufinp); /* fprintf(stderr, "bufinp: %s\n", mathBuffer); */ fatDelimiter = 1; arrayDepth = 0; mathList = newListNode(N_NODE); buildMathList(mathList); resetCharMults(); computeWidth(mathList); /* if (mathList->width > maxLineWidth) fprintf(stderr, "Width = %ld units, Output line = %ld.\n", mathList->width, outLineNum); */ breakMathList(mathList, maxLineWidth); charsOut = 0; printMathList(mathList->data.node, TRUE); freeMathList(mathList); return 1; } /* * breakFracProd: * * Arguments: * * n : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * match : either "\\over" or "\\ " * * label : either "quotient" or "product" * * paren : add parentheses (TRUE or FALSE) * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries to break up a quotient t1 \over t2 or a product t1 \ t2 by * splitting and parenthesizing the numerator and/or the denominator. */ int #ifndef _NO_PROTO breakFracProd(listNode * n, long lineWidth, char *match, char *label, int paren) #else breakFracProd(n,lineWidth,match,label,paren) listNode * n; long lineWidth; char *match; char *label; int paren; #endif { listNode *rootNode, *lastNode, *t1, *t2; int ok; long workWidth1, workWidth2; if (n->nodeType != N_NODE) return FALSE; rootNode = n; ok = FALSE; t1 = n = rootNode->data.node; n = n->nextListNode; if (n) { if ((n->nodeType == N_TEXT) && (0 == strcmp(n->data.text, match))) { t2 = n->nextListNode; if (t2 && (NULL == t2->nextListNode)) ok = TRUE; } } displaySplitMsg(label, ok); if (ok) { /* for products, determine rough widths for the two factors */ if (0 == strcmp(label, "product")) { computeNodeWidth(t1); computeNodeWidth(t2); workWidth1 = lineWidth - charWidth(' '); if (workWidth1 / 2 > t1->realWidth) { workWidth2 = workWidth1 - t1->realWidth; workWidth1 = t1->realWidth; } else if (workWidth1 / 2 > t2->realWidth) { workWidth1 = workWidth1 - t2->realWidth; workWidth2 = t2->realWidth; } else workWidth1 = workWidth2 = workWidth1 / 2; if (paren) { if (t1->realWidth > workWidth1) workWidth1 = workWidth1 - 4 * FATDELIMMULT * charWidth('('); if (t2->realWidth > workWidth2) workWidth2 = workWidth2 - 4 * FATDELIMMULT * charWidth('('); } } else /* "quotient" */ workWidth1 = workWidth2 = lineWidth - paren * 4 * FATDELIMMULT * charWidth('('); if ((t1->nodeType == N_NODE) && (t1->realWidth > workWidth1)) { t1->width = t1->realWidth; if (breakMathList(t1, workWidth1) && paren) { /* insert the \left( */ lastNode = insertStringAtFront("\\left(", t1); while (lastNode->nextListNode) lastNode = lastNode->nextListNode; /* insert the \right) */ insertStringAtBack("\\right)", lastNode); } } if ((t2->nodeType == N_NODE) && (t2->realWidth > workWidth2)) { t2->width = t2->realWidth; if (breakMathList(t2, workWidth2) && paren) { /* insert the \left( */ lastNode = insertStringAtFront("\\left(", t2); while (lastNode->nextListNode) lastNode = lastNode->nextListNode; /* insert the \right) */ insertStringAtBack("\\right)", lastNode); } } return TRUE; } return FALSE; } int #ifndef _NO_PROTO breakFunction(listNode * n, long lineWidth) #else breakFunction(n,lineWidth) listNode * n; long lineWidth; #endif { listNode *rootNode, *tmpNode, *lastNode, *t1, *t2, *t3; int ok = FALSE; long workWidth, maxWidth = 0; if (n->nodeType != N_NODE) return FALSE; n = n->data.node; if (n->nodeType == N_NODE) return FALSE; if ((0 == strcmp(n->data.text, "\\sin")) || (0 == strcmp(n->data.text, "\\cos")) || (0 == strcmp(n->data.text, "\\tan")) || (0 == strcmp(n->data.text, "\\log")) || (0 == strcmp(n->data.text, "\\arctan")) || (0 == strcmp(n->data.text, "\\erf"))) { computeNodeWidth(n); ok = TRUE; } displaySplitMsg("function", ok); if (ok) { t2 = newListNode(N_NODE); t2->data.node = n->nextListNode; t2->prevListNode = n; n->nextListNode = t2; ok = breakMathList(t2, lineWidth - n->realWidth); } return ok; } /* * breakList: * * Arguments: * * n : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries to split an expression that is bracketed by \left[ and * \right] (or \left\{ and \right\} and contains at least one comma. */ int #ifndef _NO_PROTO breakList(listNode * n, long lineWidth) #else breakList(n,lineWidth) listNode * n; long lineWidth; #endif { listNode *rootNode, *tmpNode, *lastNode, *t1, *t2, *t3; int ok, comma; long workWidth, maxWidth = 0; if (n->nodeType != N_NODE) return FALSE; rootNode = n; t1 = n = rootNode->data.node; comma = ok = FALSE; if ((t1->nodeType == N_TEXT) && (0 == strcmp(t1->data.text, "\\left")) && (t1->nextListNode) && (t1->nextListNode->nodeType == N_TEXT) && ((0 == strcmp(t1->nextListNode->data.text, "[")) || (0 == strcmp(t1->nextListNode->data.text, "\\{")))) { t1 = t1->nextListNode->nextListNode; /* * Check for a special case: sometimes the whole body of the list is * a node. Flatten this, if possible. */ if ((t1->nodeType == N_NODE) && (t1->nextListNode->nodeType == N_TEXT) && (0 == strcmp(t1->nextListNode->data.text, "\\right"))) { tmpNode = t1->prevListNode; t2 = t1->nextListNode; t3 = t1->data.node; tmpNode->nextListNode = t3; t3->prevListNode = tmpNode; while (t3->nextListNode) t3 = t3->nextListNode; t3->nextListNode = t2; t2->prevListNode = t3; free(t1); t1 = tmpNode->nextListNode; } while (t1->nextListNode && !ok) { if ((t1->nodeType == N_TEXT) && (0 == strcmp(t1->data.text, ","))) comma = TRUE; else if ((t1->nodeType == N_TEXT) && (0 == strcmp(t1->data.text, "\\right")) && (t1->nextListNode->nodeType == N_TEXT) && ((0 == strcmp(t1->nextListNode->data.text, "]")) || (0 == strcmp(t1->nextListNode->data.text, "\\}"))) && (NULL == t1->nextListNode->nextListNode)) { ok = comma; tmpNode = t1->nextListNode; } t1 = t1->nextListNode; } } displaySplitMsg("list", ok); if (ok) { if (arrayDepth >= arrayMaxDepth) { arrayTooDeep(); return FALSE; } /* * Create array environment */ lastNode = insertStringAtFront("\\begin{array}{@{}l}\\displaystyle", rootNode); arrayDepth++; insertStringAtBack("\\end{array}", tmpNode); /* * Now break at best place short of width. Start after the * environment begins and after the \left( */ n = lastNode->nextListNode->nextListNode->nextListNode; /* * try to split the first expression if too big */ tmpNode = n->nextListNode; if (breakMathList(n, lineWidth)) { workWidth = n->width; n = tmpNode; } else workWidth = n->width; maxWidth = workWidth; while (n->nextListNode) { if ((n->nodeType == N_TEXT) && ((0 == strcmp(n->data.text, ",")) || (0 == strcmp(n->data.text, "\\:"))) && (workWidth + n->nextListNode->width > lineWidth)) { maxWidth = max(maxWidth, workWidth); n = insertStringAfter("\\right. \\\\ \\\\ \\displaystyle \\left.", n); /* * try to split the next expression if too big */ tmpNode = n->nextListNode; if (breakMathList(n, lineWidth)) { workWidth = n->width; n = tmpNode; } else workWidth = n->width; } else { workWidth += n->nextListNode->width; n = n->nextListNode; } } rootNode->width = rootNode->realWidth = rootNode->data.node->width = rootNode->data.node->realWidth = maxWidth; arrayDepth--; return TRUE; } return FALSE; } /* * breakNumber: * * Arguments: * * rootNode : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries to break an expression that contains only digits and possibly * a decimal point. */ int #ifndef _NO_PROTO breakNumber(listNode * rootNode, long lineWidth) #else breakNumber(rootNode,lineWidth) listNode * rootNode; long lineWidth; #endif { int ok = TRUE; listNode *n, *arrNode, *rowNode, *colNode; long workWidth, maxWidth = 0; if (rootNode->nodeType != N_NODE) return FALSE; n = rootNode->data.node; while (n && ok) { if ((n->nodeType == N_TEXT) && (n->data.text[1] == '\0') && (isdigit(n->data.text[0]) || ('.' == n->data.text[0]))) { n = n->nextListNode; } else ok = FALSE; } displaySplitMsg("number", ok); if (ok) { if (arrayDepth >= arrayMaxDepth) { arrayTooDeep(); return FALSE; } arrayDepth++; arrNode = newListNode(N_ARRAY); arrNode->data.array->entries = rowNode = newListNode(N_NODE); arrNode->data.array->cols = 1; arrNode->data.array->argsNode = newListNode(N_NODE); string2NodeList("{@{}l}", arrNode->data.array->argsNode); n = rootNode->data.node; computeWidth(n); maxWidth = workWidth = n->width; rowNode->data.node = colNode = newListNode(N_NODE); colNode->data.node = n; n = n->nextListNode; while (n) { computeWidth(n); if (workWidth + n->width > lineWidth) { maxWidth = max(maxWidth, workWidth); /* * time to start a new row */ n->prevListNode->nextListNode = NULL; n->prevListNode = NULL; workWidth = n->width; rowNode->nextListNode = newListNode(N_NODE); rowNode = rowNode->nextListNode; rowNode->data.node = colNode = newListNode(N_NODE); colNode->data.node = n; } else workWidth += (n->nextListNode) ? n->nextListNode->width :0 ; n = n->nextListNode; } rootNode->data.node = arrNode; rootNode->width = rootNode->realWidth = arrNode->width = arrNode->realWidth = maxWidth; arrayDepth--; return TRUE; } return FALSE; } void #ifndef _NO_PROTO resetWidths(listNode * n) #else resetWidths(n) listNode * n; #endif { if (n) { n->width = -1; n->realWidth = 0; if (n->nodeType == N_NODE) resetWidths(n->data.node); resetWidths(n->nextListNode); } } /* * breakParen: * * Arguments: * * n : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries to split an expression that is bracketed by left( and \right) * (e.g., a factor). */ int #ifndef _NO_PROTO breakParen(listNode * n, long lineWidth) #else breakParen(n,lineWidth) listNode * n; long lineWidth; #endif { listNode *tmpNode, *workNode; int ok = FALSE; if (n->nodeType != N_NODE) goto say_msg; tmpNode = n->data.node; /* * check for \left */ if ((tmpNode == NULL) || (tmpNode->nodeType == N_NODE) || (0 != strcmp(tmpNode->data.text, "\\left"))) goto say_msg; /* * check for '(' */ tmpNode = tmpNode->nextListNode; if ((tmpNode == NULL) || (tmpNode->nodeType == N_NODE) || ('(' != tmpNode->data.text[0])) goto say_msg; /* * now move to the end */ tmpNode = tmpNode->nextListNode; if (tmpNode != NULL) { while (tmpNode->nextListNode) tmpNode = tmpNode->nextListNode; tmpNode = tmpNode->prevListNode; } /* * check for \right */ if ((tmpNode == NULL) || (tmpNode->nodeType == N_NODE) || (0 != strcmp(tmpNode->data.text, "\\right"))) goto say_msg; /* * check for ')' */ tmpNode = tmpNode->nextListNode; if ((tmpNode == NULL) || (tmpNode->nodeType == N_NODE) || (')' != tmpNode->data.text[0])) goto say_msg; ok = TRUE; say_msg: displaySplitMsg("parenthesized expression", ok); if (ok) { /* * nest the whole inside if necessary, i.e., there is more than one * term between the ( and the \right */ if (tmpNode->prevListNode->prevListNode != n->data.node->nextListNode->nextListNode) { workNode = newListNode(N_NODE); workNode->data.node = n->data.node->nextListNode->nextListNode; n->data.node->nextListNode->nextListNode = workNode; tmpNode->prevListNode->prevListNode->nextListNode = NULL; tmpNode->prevListNode->prevListNode = workNode; workNode->prevListNode = n->data.node->nextListNode; workNode->nextListNode = tmpNode->prevListNode; resetWidths(workNode); computeWidth(workNode); } return breakMathList(n->data.node->nextListNode->nextListNode, lineWidth - 4 * FATDELIMMULT * charWidth('(')); } return FALSE; } /* * breakPMEB: * * Arguments: * * n : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries to split an expression that contains only +, -, = or \ as * operators. The split occurs after the operator. */ int #ifndef _NO_PROTO breakPMEB(listNode * n, long lineWidth) #else breakPMEB(n,lineWidth) listNode * n; long lineWidth; #endif { char *s; listNode *rootNode, *tmpNode, *lastNode; int ok, op; long workWidth, maxWidth = 0; if (n->nodeType != N_NODE) return FALSE; if (n->width <= lineWidth + maxLineSlop) /* allow a little slop here */ return FALSE; rootNode = n; tmpNode = n = n->data.node; ok = TRUE; op = FALSE; while (n && ok) { if (n->nodeType == N_TEXT) { s = n->data.text; if (STRCHREQ(s, '+') || STRCHREQ(s, '-') || STRCHREQ(s, '=') || (0 == strcmp(s, "\\ "))) op = TRUE; else if ((0 == strcmp(s, "\\left")) || (0 == strcmp(s, "\\right")) || (0 == strcmp(s, "\\over")) || STRCHREQ(s, ',')) { ok = FALSE; break; } } tmpNode = n; n = n->nextListNode; } ok = ok & op; displaySplitMsg("(+,-,=, )-expression", ok); if (ok) { if (arrayDepth >= arrayMaxDepth) { arrayTooDeep(); return FALSE; } /* * Create array environment */ lastNode = insertStringAtFront("\\begin{array}{@{}l}\\displaystyle", rootNode); arrayDepth++; insertStringAtBack("\\end{array}", tmpNode); /* * Now break at best place short of width. Start after the * environment begins. */ n = lastNode->nextListNode; /* * try to split the first expression if too big */ tmpNode = n->nextListNode; if (breakMathList(n, lineWidth)) { workWidth = n->width; n = tmpNode; } else workWidth = n->width; maxWidth = workWidth; while (n->nextListNode) { loop_top: if ((n->nodeType == N_TEXT) && (STRCHREQ(n->data.text, '+') || STRCHREQ(n->data.text, '-') || STRCHREQ(n->data.text, '=') || (0 == strcmp(n->data.text, "\\ "))) && (workWidth > 24) && /* avoid - or + on their own line */ (workWidth + n->nextListNode->width > lineWidth)) { if ((workWidth < lineWidth / 3) && (breakMathList(n->nextListNode, lineWidth - workWidth))) { n->nextListNode->width = -1; n->nextListNode->realWidth = 0; computeNodeWidth(n->nextListNode); goto loop_top; } /* * \ means multiplication. Use a \cdot to make this clearer */ if (0 == strcmp(n->data.text, "\\ ")) n = insertStringAfter("\\cdot \\\\ \\\\ \\displaystyle", n); else n = insertStringAfter("\\\\ \\\\ \\displaystyle", n); maxWidth = max(maxWidth, workWidth); /* * try to split the next expression if too big */ tmpNode = n->nextListNode; if (breakMathList(n, lineWidth)) { workWidth = n->width; n = tmpNode; } else workWidth = n->width; } else { workWidth += n->nextListNode->width; n = n->nextListNode; } } rootNode->width = rootNode->realWidth = rootNode->data.node->width = rootNode->data.node->realWidth = maxWidth; arrayDepth--; return TRUE; } return FALSE; } /* * breakMathList: * * Arguments: * * n : the starting node at which we are to try to break * * lineWidth : the maximum width of a line * * * Returns: TRUE or FALSE, depending on whether the expression was broken * * * Function: Tries various methods to break the expression up into multiple * lines if the expression is too big. */ int #ifndef _NO_PROTO breakMathList(listNode * n, long lineWidth) #else breakMathList(n,lineWidth) listNode * n; long lineWidth; #endif { int split = FALSE; /* * Don't do anything if already short enough. */ if (n->width <= lineWidth) return FALSE; /* * Can't split strings, so just return. */ if (n->nodeType == N_TEXT) return FALSE; blanks[indent] = ' '; indent += 2; blanks[indent] = '\0'; /* * We know we have a node, so see what we can do. */ /* * Case 1: a product: t1 \ t2 */ if (split = breakFracProd(n, lineWidth, "\\ ", "product", FALSE)) goto done; /* * Case 2: a sequence of tokens separated by +, - or = */ if (split = breakPMEB(n, lineWidth)) goto done; /* * Case 3: a fraction of terms: t1 \over t2 */ if (split = breakFracProd(n, lineWidth, "\\over", "quotient", TRUE)) goto done; /* * Case 4: a list of terms bracketed by \left[ and \right] with a comma */ if (split = breakList(n, lineWidth)) goto done; /* * Case 5: a list of digits, possibly with one "." */ if (split = breakNumber(n, lineWidth)) goto done; /* * Case 6: a parenthesized expression (e.g., a factor) */ if (split = breakParen(n, lineWidth)) goto done; /* * Case 7: a function application */ if (split = breakFunction(n, lineWidth)) goto done; done: blanks[indent] = ' '; indent -= 2; blanks[indent] = '\0'; return split; } void #ifndef _NO_PROTO buildMathList(listNode * oldNode) #else buildMathList(oldNode) listNode * oldNode; #endif { listNode *curNode, *tmpNode; curNode = NULL; while (nextMathToken()) { if (mathToken[0] == '}') break; if (mathToken[0] == '{') { tmpNode = newListNode(N_NODE); buildMathList(tmpNode); } else { tmpNode = newListNode(N_TEXT); tmpNode->data.text = strdup(mathToken); } if (curNode == NULL) { oldNode->data.node = tmpNode; } else { tmpNode->prevListNode = curNode; curNode->nextListNode = tmpNode; } curNode = tmpNode; } /* * leave with one level of nesting, e.g., {{{x}}} --> {x} */ tmpNode = oldNode->data.node; while ( tmpNode && (tmpNode->nodeType == N_NODE) && (tmpNode->nextListNode == NULL) ) { oldNode->data.node = tmpNode->data.node; free(tmpNode); tmpNode = oldNode->data.node; } } void #ifndef _NO_PROTO computeNodeWidth(listNode * n) #else computeNodeWidth(n) listNode * n; #endif { char *s; int i; listNode *tmp; if (n->width != -1) /* only = -1 if unprocessed */ return; n->realWidth = 0; if (n->nodeType == N_TEXT) { s = n->data.text; if (s[0] == '\\') { if (s[2] == '\0') { switch (s[1]) { case ' ': n->width = spaceWidths[0]; break; case ',': n->width = spaceWidths[1]; break; case '!': n->width = spaceWidths[2]; break; case ':': n->width = spaceWidths[3]; break; case ';': n->width = spaceWidths[4]; break; default: n->width = avgCharWidth; } n->realWidth = n->width; } else if ((0 == strcmp(s, "\\displaystyle")) || (0 == strcmp(s, "\\bf")) || (0 == strcmp(s, "\\sf")) || (0 == strcmp(s, "\\tt")) || (0 == strcmp(s, "\\rm")) || (0 == strcmp(s, "\\hbox")) || (0 == strcmp(s, "\\mbox")) || (0 == strcmp(s, "\\overline")) || (0 == strcmp(s, "\\textstyle")) || (0 == strcmp(s, "\\scriptstyle")) || (0 == strcmp(s, "\\scriptscriptstyle"))) { n->width = 0; } else if (0 == strcmp(s, "\\ldots")) n->width = 3 * charWidth('.'); else if (0 == strcmp(s, "\\left")) { tmp = n->nextListNode; if (tmp->nodeType != N_TEXT) error("unusual token following \\left", ""); n->realWidth = n->width = (tmp->data.text[0] == '.') ? 0 : charWidth(tmp->data.text[0]); tmp->width = 0; fatDelimiter = 1; } else if (0 == strcmp(s, "\\over")) { /* * have already added in width of numerator */ computeNodeWidth(n->nextListNode); n->realWidth = extraOverWidth + max(n->prevListNode->width, n->nextListNode->width); n->width = n->realWidth - n->prevListNode->width; n->nextListNode->width = 0; fatDelimiter = FATDELIMMULT; } else if (0 == strcmp(s, "\\right")) { tmp = n->nextListNode; if (tmp->nodeType != N_TEXT) error("unusual token following \\right", ""); n->realWidth = n->width = fatDelimiter * ((tmp->data.text[0] == '.') ? 0 : charWidth(tmp->data.text[0])); tmp->width = 0; fatDelimiter = 1; } else if (0 == strcmp(s, "\\root")) { computeNodeWidth(n->nextListNode); /* which root */ n->nextListNode->nextListNode->width = 0; /* \of */ tmp = n->nextListNode->nextListNode->nextListNode; computeNodeWidth(tmp); /* root of */ n->realWidth = n->width = tmp->width + (avgCharWidth / 2) + max(avgCharWidth, n->nextListNode->width); n->nextListNode->width = 0; tmp->width = 0; } else if (0 == strcmp(s, "\\sqrt")) { computeNodeWidth(n->nextListNode); n->realWidth = n->width = avgCharWidth + (avgCharWidth / 2) + n->nextListNode->width; n->nextListNode->width = 0; } else if (0 == strcmp(s, "\\zag")) { computeNodeWidth(n->nextListNode); computeNodeWidth(n->nextListNode->nextListNode); n->realWidth = n->width = avgCharWidth + max(n->nextListNode->width, n->nextListNode->nextListNode->width); n->nextListNode->width = 0; n->nextListNode->nextListNode->width = 0; fatDelimiter = FATDELIMMULT; } else if ((0 == strcmp(s, "\\alpha")) || (0 == strcmp(s, "\\beta")) || (0 == strcmp(s, "\\pi"))) { n->realWidth = n->width = avgCharWidth; } else if (0 == strcmp(s, "\\sin")) /* should use table lookup here */ n->realWidth = n->width = sinWidth; else if (0 == strcmp(s, "\\cos")) n->realWidth = n->width = cosWidth; else if (0 == strcmp(s, "\\tan")) n->realWidth = n->width = tanWidth; else if (0 == strcmp(s, "\\erf")) n->realWidth = n->width = erfWidth; /* * otherwise just compute length of token after \ */ else { n->width = 0; for (i = 1; i < strlen(s); i++) n->width += charWidth(s[i]); n->realWidth = n->width; } } else if (s[1] == '\0') switch (s[0]) { case '^': case '_': tmp = n->nextListNode; computeNodeWidth(tmp); n->width = n->width = tmp->width; tmp->width = 0; break; default: n->realWidth = n->width = charWidth(s[0]); } else { n->width = 0; for (i = 0; i < strlen(s); i++) n->width += charWidth(s[i]); n->realWidth = n->width; } } else { n->realWidth = n->width = computeWidth(n->data.node); } } long #ifndef _NO_PROTO computeWidth(listNode * n) #else computeWidth(n) listNode * n; #endif { long w = 0; while (n != NULL) { if (n->width == -1) { computeNodeWidth(n); w += n->width; } n = n->nextListNode; } return w; } /* * displaySplitMsg: * * Arguments: * * s : a string describing the kind of expression we are trying to split. * * ok : whether we can split it (TRUE or FALSE) * * * Returns: nothing * * * Function: Displays a message on stderr about whether a particular method of * line breaking will be successful. */ void #ifndef _NO_PROTO displaySplitMsg(char *s, int ok) #else displaySplitMsg(s,ok) char *s; int ok; #endif { /* fprintf(stderr, "%sCan split %s: %s\n", blanks, s, ok ? "TRUE" : "FALSE"); */ } void arrayTooDeep() { fprintf(stderr, "%s->Array nesting too deep!\n", blanks); } void #ifndef _NO_PROTO error(char *msg, char *insert) #else error(msg,insert) char *msg; char *insert; #endif { fputs("Error (texbreak): ", stderr); fputs(msg, stderr); fputs(insert, stderr); fputc('\n', stderr); fputs("% Error (texbreak): ", stdout); fputs(msg, stdout); fputs(insert, stdout); fputc('\n', stdout); exit(1); } void #ifndef _NO_PROTO freeMathList(listNode * n) #else freeMathList(n) listNode * n; #endif { listNode *tmpNode; while (n != NULL) { if (n->nodeType == N_NODE) freeMathList(n->data.node); else if (n->nodeType == N_TEXT) free(n->data.text); else { freeMathList(n->data.array->argsNode); freeMathList(n->data.array->entries); free(n->data.array); } tmpNode = n->nextListNode; free(n); n = tmpNode; } } listNode * #ifndef _NO_PROTO insertStringAfter(char *s, listNode * n) #else insertStringAfter(s,n) char *s; listNode * n; #endif { /* * returns node after inserted string */ listNode *workNode, *lastNode; workNode = newListNode(N_NODE); lastNode = string2NodeList(s, workNode); n->nextListNode->prevListNode = lastNode; lastNode->nextListNode = n->nextListNode; n->nextListNode = workNode->data.node; workNode->data.node->prevListNode = n; free(workNode); return lastNode->nextListNode; } listNode * #ifndef _NO_PROTO insertStringAtBack(char *s, listNode * n) #else insertStringAtBack(s,n) char *s; listNode * n; #endif { /* * Breaks s up into a list of tokens and appends them onto the end of n. * n must be non-NULL. */ listNode *workNode, *lastNode; workNode = newListNode(N_NODE); lastNode = string2NodeList(s, workNode); n->nextListNode = workNode->data.node; workNode->data.node->prevListNode = n; free(workNode); return lastNode; } listNode * #ifndef _NO_PROTO insertStringAtFront(char *s, listNode * n) #else insertStringAtFront(s,n) char *s; listNode * n; #endif { /* * Breaks s up into a list of tokens and appends them onto the front of * n. n must be a node. */ listNode *workNode, *lastNode; workNode = newListNode(N_NODE); lastNode = string2NodeList(s, workNode); lastNode->nextListNode = n->data.node; n->data.node->prevListNode = lastNode; n->data.node = workNode->data.node; free(workNode); return lastNode; } int #ifndef _NO_PROTO newLineIfNecessary(int lastWasNewLine) #else newLineIfNecessary(lastWasNewLine) int lastWasNewLine; #endif { if (!lastWasNewLine || (charsOut > 0)) { putcBuf('\n', bufout); outLineNum++; charsOut = 0; } return TRUE; } listNode * #ifndef _NO_PROTO newListNode(enum nodeTypes nt) #else newListNode(nt) enum nodeTypes nt; #endif { listNode *n; n = (listNode *) malloc(sizeof(listNode)); n->nextListNode = n->prevListNode = NULL; n->nodeType = nt; n->width = -1; n->realWidth = -1; if (nt == N_NODE) n->data.node = NULL; else if (nt == N_TEXT) n->data.text = NULL; else { n->data.array = (arrayNode *) malloc(sizeof(arrayNode)); n->data.array->argsNode = NULL; n->data.array->entries = NULL; n->data.array->cols = 0; } return n; } int nextMathToken() { /* * Sets mathToken. Returns 1 if ok, 0 if no more tokens. */ char curChar, errChar[2]; errChar[1] = '\0'; mathToken[0] = '\0'; mathTokenLen = 0; /* * Kill any blanks. */ while ((mathBufferPtr < mathBufferLen) && (mathBuffer[mathBufferPtr] == ' ')) mathBufferPtr++; /* * If at end, exit saying so. */ if (mathBufferPtr >= mathBufferLen) return 0; mathToken[mathTokenLen++] = curChar = mathBuffer[mathBufferPtr++]; if (curChar == '\\') { curChar = mathBuffer[mathBufferPtr++]; switch (curChar) { case '\0': /* at end of buffer */ mathToken[mathTokenLen++] = ' '; goto done; case '\\': case ' ': case '!': case '#': case '$': case '%': case '&': case ',': case ':': case ';': case '^': case '_': case '{': case '}': mathToken[mathTokenLen++] = curChar; goto done; } if (isalpha(curChar) || (curChar == '@')) { mathToken[mathTokenLen++] = curChar; while ((curChar = mathBuffer[mathBufferPtr]) && (isalpha(curChar) || (curChar == '@'))) { mathToken[mathTokenLen++] = curChar; mathBufferPtr++; } } else { errChar[0] = curChar; errChar[1] = '\0'; error("strange character following \\: ", errChar); } } else if (isdigit(curChar)) /* digits are individual tokens */ ; else if (isalpha(curChar)) { while ((curChar = mathBuffer[mathBufferPtr]) && (isalpha(curChar))) { mathToken[mathTokenLen++] = curChar; mathBufferPtr++; } } else if (curChar == '"') { /* handle strings */ while ((curChar = mathBuffer[mathBufferPtr]) && (curChar != '"')) { mathToken[mathTokenLen++] = curChar; mathBufferPtr++; } mathToken[mathTokenLen++] = '"'; mathBufferPtr++; } done: mathToken[mathTokenLen--] = '\0'; /* * Some translations. */ if (0 == strcmp(mathToken, "\\sp")) { mathToken[0] = '^'; mathToken[1] = '\0'; mathTokenLen = 1; } else if (0 == strcmp(mathToken, "\\sb")) { mathToken[0] = '_'; mathToken[1] = '\0'; mathTokenLen = 1; } return 1; } int #ifndef _NO_PROTO printChar(char c) #else printChar(c) char c; #endif { if ((charsOut > MAXCHARSINLINE) && isdigit(lastPrinted) && isdigit(c)) { putcBuf('\n', bufout); outLineNum++; charsOut = 0; } putcBuf(c, bufout); lastPrinted = c; charsOut++; /* * break lines after following characters */ if ((charsOut > MAXCHARSINLINE) && strchr("+- ,_^", c)) { putcBuf('\n', bufout); outLineNum++; charsOut = 0; lastPrinted = '\0'; return TRUE; } return FALSE; } int #ifndef _NO_PROTO printMathList(listNode * n, int lastWasNewLine) #else printMathList(n,lastWasNewLine) listNode * n; int lastWasNewLine; #endif { listNode *tmpNode, *rowNode, *colNode; int begin, group, r, c; while (n != NULL) { if (n->nodeType == N_NODE) { lastWasNewLine = printChar('{'); lastWasNewLine = printMathList(n->data.node, lastWasNewLine); lastWasNewLine = printChar('}'); } else if (n->nodeType == N_ARRAY) { lastWasNewLine = newLineIfNecessary(lastWasNewLine); lastWasNewLine = printString("\\begin{array}"); lastWasNewLine = printMathList(n->data.array->argsNode, lastWasNewLine); lastWasNewLine = printString("\\displaystyle"); lastWasNewLine = newLineIfNecessary(lastWasNewLine); rowNode = n->data.array->entries; /* node pointing to first row */ while (rowNode) { colNode = rowNode->data.node; while (colNode) { if (colNode->prevListNode) { /* if not first column */ lastWasNewLine = printString(" & "); lastWasNewLine = newLineIfNecessary(lastWasNewLine); } lastWasNewLine = printMathList(colNode->data.node, lastWasNewLine); colNode = colNode->nextListNode; } if (rowNode->nextListNode) /* if not last row */ lastWasNewLine = printString(" \\\\"); lastWasNewLine = newLineIfNecessary(lastWasNewLine); rowNode = rowNode->nextListNode; } lastWasNewLine = printString("\\end{array}"); lastWasNewLine = newLineIfNecessary(lastWasNewLine); } else if (n->nodeType == N_TEXT) { /* * handle keywords that might appear in math mode */ if ((0 == strcmp(n->data.text, "by")) || (0 == strcmp(n->data.text, "if")) || (0 == strcmp(n->data.text, "then")) || (0 == strcmp(n->data.text, "else"))) { lastWasNewLine = printString(" \\hbox{ "); lastWasNewLine = printString(n->data.text); lastWasNewLine = printString(" } "); } /* * handle things that should be in a special font */ else if ((0 == strcmp(n->data.text, "true")) || (0 == strcmp(n->data.text, "false")) || (0 == strcmp(n->data.text, "table")) || (0 == strcmp(n->data.text, "Aleph")) ) { lastWasNewLine = printString(" \\mbox{\\rm "); lastWasNewLine = printString(n->data.text); lastWasNewLine = printString("} "); } /* * handle things that should always be on their own line */ else if ((0 == strcmp(n->data.text, "\\\\")) || (0 == strcmp(n->data.text, "\\displaystyle"))) { lastWasNewLine = newLineIfNecessary(lastWasNewLine); lastWasNewLine = printString(n->data.text); lastWasNewLine = newLineIfNecessary(lastWasNewLine); } /* * handle phrases that should be on their own line. */ else if ((0 == strcmp(n->data.text, "\\begin")) || (0 == strcmp(n->data.text, "\\end"))) { lastWasNewLine = newLineIfNecessary(lastWasNewLine); lastWasNewLine = printString(n->data.text); begin = (n->data.text[1] == 'b') ? TRUE : FALSE; n = n->nextListNode; /* had better be a node */ tmpNode = n->data.node; lastWasNewLine = printChar('{'); lastWasNewLine = printMathList(tmpNode, lastWasNewLine); lastWasNewLine = printChar('}'); if (begin) { /* * if array, print the argument. */ if (0 == strcmp(tmpNode->data.text, "array")) { n = n->nextListNode; /* had better be a node */ lastWasNewLine = printChar('{'); lastWasNewLine = printMathList(n->data.node, lastWasNewLine); lastWasNewLine = printChar('}'); } } lastWasNewLine = newLineIfNecessary(FALSE); } /* * handle everything else, paying attention as to whether we * should include a trailing blank. */ else { group = 0; /* guess whether next word is part of a type */ if ((strlen(n->data.text) > 2) && ('A' <= n->data.text[0]) && ('Z' >= n->data.text[0])) { group = 1; lastWasNewLine = printString("\\hbox{\\axiomType{"); } lastWasNewLine = printString(n->data.text); if (group) { lastWasNewLine = printString("}\\ }"); group = 0; } tmpNode = n->nextListNode; if ((n->data.text[0] == '_') || (n->data.text[0] == '^') || (n->data.text[0] == '.') || (n->data.text[0] == '(') || (0 == strcmp(n->data.text, "\\left")) || (0 == strcmp(n->data.text, "\\right")) || (0 == strcmp(n->data.text, "\\%"))); else if (tmpNode && (tmpNode->nodeType == N_TEXT)) { if (((isdigit(n->data.text[0])) && (isdigit(tmpNode->data.text[0]))) || ((isdigit(n->data.text[0])) && (',' == tmpNode->data.text[0])) || (tmpNode->data.text[0] == '\'') || (tmpNode->data.text[0] == '_') || (tmpNode->data.text[0] == '^') || (tmpNode->data.text[0] == '.') || (tmpNode->data.text[0] == ')')); else lastWasNewLine = printChar(' '); } } } n = n->nextListNode; } return lastWasNewLine; } int #ifndef _NO_PROTO printString(char *s) #else printString(s) char *s; #endif { if (s[0]) { if (!s[1]) return printChar(s[0]); else { putsBuf(s,bufout); charsOut += strlen(s); } } return FALSE; } void #ifndef _NO_PROTO putsBuf(char s[], char buf[]) #else putsBuf(s,buf) char s[], buf[] #endif { strcat(buf,s); } void #ifndef _NO_PROTO putcBuf(char c,char buf[]) #else putcBuf(c,buf) char c, buf[] #endif { char s[2]; s[0] = c; s[1] = '\0'; strcat(buf,s); } listNode * #ifndef _NO_PROTO string2NodeList(char *s, listNode * n) #else string2NodeList(s,n) char *s; listNode * n; #endif { /* * First argument is string to be broken up, second is a node. Return * value is last item in list. */ mathBufferPtr = 0; strcpy(mathBuffer, s); mathBufferLen = strlen(s); buildMathList(n); n = n->data.node; while (n->nextListNode) { /* * set width to 0: other funs will have to set for real */ n->width = 0; n = n->nextListNode; } n->width = 0; return n; } void resetCharMults() { /* * this is a ratio by which the standard \mit should be multiplied to get * other fonts, roughly */ charMultNum = charMultDenom = 1; } void ttCharMults() { /* * this is a ratio by which the standard \mit should be multiplied to get * the \tt font, roughly */ charMultNum = 11; charMultDenom = 10; } int #ifndef _NO_PROTO charWidth(char c) #else charWidth(c) char c; #endif { return (charMultNum * charTable[c]) / charMultDenom; } void #ifndef _NO_PROTO #else #endif initCharTable() { int i; avgCharWidth = 95; /* where 1000 = 1 inch */ spaceWidths[0] = 51; /* \ */ spaceWidths[1] = 25; /* \, */ spaceWidths[2] = -25; /* \! */ spaceWidths[3] = 37; /* \: */ spaceWidths[4] = 42; /* \; */ extraOverWidth = 33; /* extra space in fraction bar */ sinWidth = 186; /* width of \sin */ cosWidth = 203; tanWidth = 219; erfWidth = 185; for (i = 0; i < 256; i++) charTable[i] = avgCharWidth; charTable['!'] = 42; charTable['"'] = 76; charTable['%'] = 126; charTable['('] = 59; charTable[')'] = 59; charTable['+'] = 185; charTable[','] = 42; charTable['-'] = 185; charTable['.'] = 42; charTable['/'] = 76; charTable['0'] = 76; charTable['1'] = 76; charTable['2'] = 76; charTable['3'] = 76; charTable['4'] = 76; charTable['5'] = 76; charTable['6'] = 76; charTable['7'] = 76; charTable['8'] = 76; charTable['9'] = 76; charTable[':'] = 42; charTable[';'] = 42; charTable['<'] = 202; charTable['='] = 202; charTable['>'] = 202; charTable['A'] = 114; charTable['B'] = 123; charTable['C'] = 119; charTable['D'] = 130; charTable['E'] = 121; charTable['F'] = 119; charTable['G'] = 119; charTable['H'] = 138; charTable['I'] = 79; charTable['J'] = 99; charTable['K'] = 140; charTable['L'] = 103; charTable['M'] = 164; charTable['N'] = 138; charTable['O'] = 120; charTable['P'] = 118; charTable['Q'] = 120; charTable['R'] = 116; charTable['S'] = 102; charTable['T'] = 110; charTable['U'] = 120; charTable['V'] = 122; charTable['W'] = 164; charTable['X'] = 137; charTable['Y'] = 122; charTable['Z'] = 114; charTable['['] = 42; charTable[']'] = 42; charTable['a'] = 80; charTable['b'] = 65; charTable['c'] = 66; charTable['d'] = 79; charTable['e'] = 71; charTable['f'] = 91; charTable['g'] = 78; charTable['h'] = 87; charTable['i'] = 52; charTable['j'] = 71; charTable['k'] = 84; charTable['l'] = 48; charTable['m'] = 133; charTable['n'] = 91; charTable['o'] = 73; charTable['p'] = 76; charTable['q'] = 73; charTable['r'] = 73; charTable['s'] = 71; charTable['t'] = 55; charTable['u'] = 87; charTable['v'] = 79; charTable['w'] = 113; charTable['x'] = 87; charTable['y'] = 80; charTable['z'] = 77; charTable['{'] = 76; charTable['|'] = 42; charTable['}'] = 76; }