This is dmmarkup.c in view mode; [Download] [Up]
/* dmmarkup.c */
/* Copyright 1995 by Steve Kirkendall */
char id_dmmarkup[] = "$Id: dmmarkup.c,v 2.43 1996/09/18 16:42:14 steve Exp $";
/* This file contains some fairly generic text formatting code -- generic
* in the sense that it can be easily tweaked to format a variety of types
* marked-up text. Currently, it supports useful subsets of NROFF and HTML
* instructions.
*/
#include "elvis.h"
#ifdef DISPLAY_MARKUP
#define GRANULARITY 64 /* number of LINEINFOs to allocate at a time */
typedef struct
{
CHAR text[200]; /* raw text of token */
long offset[200]; /* offsets of characters */
int nchars; /* number of characters in text[] */
int width; /* normal displayed width of text[] */
struct markup_s *markup;/* info about markup token */
} TOKEN;
typedef struct markup_s
{
char *name; /* name of the markup */
char attr[8]; /* attributes of markup */
BOOLEAN (*fn)P_((TOKEN *));/* ptr to special function */
} MARKUP;
#define TITLE attr[0] /* in title: -, N, Y */
#define BREAKLN attr[1] /* line break: -, 0, 1, 2, c, or p */
#define INDENT attr[2] /* -, <, >, or a number */
#define LIST attr[3] /* in list: -, N, Y, # */
#define FONT attr[4] /* font: -, =, n, b, u, i, f, e, N, B, U, I, F, E */
#define FILL attr[5] /* Y=fill, N=preformatted, -=no chg. */
#define DEST attr[6] /* S=section, P=paragraph, T=<tab> key */
typedef struct
{
long offset; /* offset of start of line */
struct
{
unsigned indent : 8; /* indentation amount, in spaces */
unsigned listcnt : 8; /* Counter for nest#1 numbered list; 0=not numbered */
unsigned nest : 6; /* nesting level of list/menu; 0=not in list*/
unsigned prefmt : 1; /* 1=literal whitespace, 0=fill */
unsigned graphic : 1; /* 1=replace |-^. with graphic chars */
unsigned midline: 1; /* 1=after a newline, 0=after other char */
unsigned reduce : 1; /* 1=fewer newlines, 0=normal qty newlines */
unsigned deffont : 3; /* index into "nbiufe" of default font char */
unsigned curfont : 3; /* index into "nbiufe" of current font char */
} state;
} LINEINFO;
typedef struct
{
TOKEN *(*get)P_((CHAR **)); /* mode-dependent get() */
void (*escape)P_((TOKEN *)); /* mode-dependent escape() */
LINEINFO *line; /* line array */
long nlines; /* number of lines in line array */
long endtitle; /* offset of the end of the title */
CHAR *title; /* title of document, or NULL */
CHAR **defs; /* macros within the text */
} MUINFO;
static BOOLEAN first; /* is this the first token on this line? */
static BOOLEAN anyspc; /* has whitespace been encountered? */
static BOOLEAN title; /* collecting characters of the title */
static BOOLEAN list; /* o_list */
static int textwidth;/* o_columns */
static int tabstop;/* o_tabstop */
static int listind;/* o_shiftwidth/2, or 2 if shiftwidth<=4 */
static int col; /* logical column number */
static MUINFO *mui; /* pointer to muinfo */
static BOOLEAN prefmt; /* True=literal whitespace, False=fill */
static BOOLEAN graphic;/* True=replace |-^. with graphic chars */
static BOOLEAN midline;/* False=after newline, True=after other character */
static BOOLEAN reduce; /* True=fewer newlines, False=normal qty newlines */
static char deffont;/* default font */
static char curfont;/* current font */
static int indent; /* indentation amount */
static int nest; /* nesting level of list/menu; 0=not in list */
static int listcnt;/* Counter for nest#1 numbered list; 0=not numbered */
/* These variables store the string and font collected by the manarg() function
* and output by the manput() function. The "manlen" variable should be
* initialized to 0 before the first call to manarg(). NOTE: These variables
* and the manput() function are also used by htmlimg().
*/
static int manlen; /* length of "mantext" string */
static CHAR mantext[80]; /* buffer, holds args from .XX macro */
static long manoffset[80]; /* holds offsets of mantext[] chars */
static char manfont[80]; /* holds fonts of mantext[] chars */
/* Forward declarations of some functions which are static to this file */
static void htmlescape P_((TOKEN *tok));
static BOOLEAN htmlhr P_((TOKEN *token));
static BOOLEAN htmlimg P_((TOKEN *token));
static BOOLEAN htmlpre P_((TOKEN *token));
static BOOLEAN htmlli P_((TOKEN *token));
static BOOLEAN htmlinput P_((TOKEN *token));
static void htmlmarkup P_((TOKEN *token));
static TOKEN *htmlget P_((CHAR **refp));
static DMINFO *htmlinit P_((WINDOW win));
static CHAR *htmltagatcursor P_((WINDOW win, MARK cursor));
static MARK htmltagload P_((CHAR *tagname, MARK from));
static MARK htmltagnext P_((MARK cursor));
static void manescape P_((TOKEN *tok));
static int manarg P_((TOKEN *token, int start, _char_ font, BOOLEAN spc));
static BOOLEAN manput P_((void));
static BOOLEAN manTH P_((TOKEN *token));
static BOOLEAN manSH P_((TOKEN *token));
static BOOLEAN manBI P_((TOKEN *token));
static BOOLEAN manIP P_((TOKEN *token));
static void manmarkup P_((TOKEN *token));
static TOKEN *manget P_((CHAR **refp));
static DMINFO *maninit P_((WINDOW win));
static void countchar P_((CHAR *p, long qty, _char_ font, long offset));
static BOOLEAN put P_((TOKEN *token));
static void term P_((DMINFO *info));
static long mark2col P_((WINDOW w, MARK mark, BOOLEAN cmd));
static MARK move P_((WINDOW w, MARK from, long linedelta, long column, BOOLEAN cmd));
static MARK setup P_((MARK top, long cursor, MARK bottom, DMINFO *info));
static MARK image P_((WINDOW w, MARK line, DMINFO *info, void (*draw)(CHAR *p, long qty, _char_ font, long offset)));
static void header P_((WINDOW w, int pagenum, DMINFO *info, void (*draw)(CHAR *p, long qty, _char_ font, long offset)));
static int start P_((WINDOW win, MARK from, void (*draw)(CHAR *p, long qty, _char_ font, long offset)));
static void storestate P_((long offset, LINEINFO *dest));
static void findtitle P_((BUFFER buf));
/* Only a single TOKEN is ever really needed at one time */
static TOKEN rettok;
/* Offset of cursor. This affects the expansion of escapes, and the visibility
* of markups.
*/
static long cursoff;
/* Offset of a space character, if "anyspc" is True. */
static long spcoffset;
/* This the drawchar pointer points to a function for outputting a single
* character.
*/
static void (*drawchar) P_((CHAR *p, long qty, _char_ font, long offset));
/* Special characters. These are stored in variables rather than macros so
* that we can pass their address to (*drawchar)().
*/
static CHAR hyphen = '-';
static CHAR newline = '\n';
static CHAR formfeed = '\f';
static CHAR vtab = '\013';
static CHAR space = ' ';
static CHAR bullet = '*';
/*----------------------------------------------------------------------------*/
/* HTML-specific functions and variables */
/* Replace entities such as < with their single-character equivelent. */
static void htmlescape(token)
TOKEN *token; /* a token whose text is to be expanded */
{
char *src, *dst;
long *off;
size_t len;
int width;
/* step through the string */
for (src = dst = (char *)token->text, off = token->offset, width = 0;
src < (char *)&token->text[token->nchars];
src++, off++, width++)
{
/* if not a &, then this can't be an escape */
if (*src != '&')
goto NoEscape;
/* find the length of this escape's name */
for (len = 1; src[len] != ';'; len++)
{
if (!src[len] || isspace(src[len]))
goto NoEscape;
}
len++;
/* if the cursor is on this escape, then don't expand it */
if ((o_showmarkups && off[0] <= cursoff && cursoff <= off[len - 1]) || list)
{
/* Tweak the value of "width" so that after all of
* this escape's characters have been counted, "width"
* will have been incremented by only 1 since that's
* how wide the escape would normally be.
*/
width -= (len - 1);
/* Don't expand the escape */
goto NoEscape;
}
/* recognize it? */
if (len == 4 && (!strncmp(&src[1], "lt", 2) || !strncmp(&src[1], "LT", 2)))
*dst++ = '<';
else if (len == 4 && (!strncmp(&src[1], "gt", 2) || !strncmp(&src[1], "GT", 2)))
*dst++ = '>';
else if (len == 5 && (!strncmp(&src[1], "amp", 3) || !strncmp(&src[1], "AMP", 3)))
*dst++ = '&';
else if (len == 6 && (!strncmp(&src[1], "quot", 4) || !strncmp(&src[1], "QUOT", 4)))
*dst++ = '"';
else if (len == 6 && (!strncmp(&src[1], "nbsp", 4) || !strncmp(&src[1], "NBSP", 4)))
*dst++ = ' ';
else if (len == 7 && !strncmp(&src[1], "AElig", 5))
*dst++ = digraph('E', 'A');
else if (len == 7 && !strncmp(&src[1], "aelig", 5))
*dst++ = digraph('e', 'a');
else if (len == 5 && !strncmp(&src[1], "ETH", 3))
*dst++ = digraph('-', 'D');
else if (len == 5 && !strncmp(&src[1], "eth", 3))
*dst++ = digraph('-', 'd');
else if (len == 7 && !strncmp(&src[1], "THORN", 5))
*dst++ = digraph('T', 'P');
else if (len == 7 && !strncmp(&src[1], "thorn", 5))
*dst++ = digraph('t', 'p');
else if (len == 7 && !strncmp(&src[1], "szlig", 5))
*dst++ = digraph('s', 'z');
else if (len == 8 && !strncmp(&src[2], "grave", 5))
*dst++ = digraph('`', (_CHAR_)src[1]);
else if (len == 8 && !strncmp(&src[2], "acute", 5))
*dst++ = digraph('\'', (_CHAR_)src[1]);
else if (len == 7 && !strncmp(&src[2], "circ", 4))
*dst++ = digraph('^', (_CHAR_)src[1]);
else if (len == 8 && !strncmp(&src[2], "tilde", 5))
*dst++ = digraph('~', (_CHAR_)src[1]);
else if (len == 6 && !strncmp(&src[2], "uml", 3))
*dst++ = digraph('"', (_CHAR_)src[1]);
else if (len == 7 && !strncmp(&src[2], "ring", 4))
*dst++ = digraph('*', (_CHAR_)src[1]);
else if (len == 8 && !strncmp(&src[2], "cedil", 5))
*dst++ = digraph(',', (_CHAR_)src[1]);
else if (len == 8 && !strncmp(&src[2], "slash", 5))
*dst++ = digraph('/', (_CHAR_)src[1]);
else if (src[1] == '#')
*dst++ = atoi(&src[2]);
else if (len == 6 && (!strncmp(&src[1], "copy", 4) || !strncmp(&src[1], "COPY", 4)))
*dst++ = digraph('O', 'c');
else if (len == 5 && (!strncmp(&src[1], "reg", 3) || !strncmp(&src[1], "REG", 3)))
*dst++ = digraph('O', 'r');
else if (len == 7 && (!strncmp(&src[1], "ldquo", 5) || !strncmp(&src[1], "LDQUO", 5)
|| !strncmp(&src[1], "rdquo", 5) || !strncmp(&src[1], "RDQUO", 5)))
*dst++ = '"';
else if (len == 7 && (!strncmp(&src[1], "lsquo", 5) || !strncmp(&src[1], "LSQUO", 5)))
*dst++ = '`';
else if (len == 7 && (!strncmp(&src[1], "rsquo", 5) || !strncmp(&src[1], "RSQUO", 5)))
*dst++ = '\'';
else
{
/* Tweak the value of "width" so that after all of
* this escape's characters have been counted, "width"
* will have been incremented by only 1 since that's
* how wide the escape would normally be if it was
* recognized.
*/
width -= (len - 1);
/* Don't expand the escape */
goto NoEscape;
}
/* Skip past the escape sequence. */
token->offset[(int)(dst - (char *)token->text) - 1] = *off;
src += len - 1;
off += len - 1;
/* plus one more at the for-loop */
continue;
NoEscape:
/* Not an escape -- copy it literally */
*dst++ = *src;
token->offset[(int)(dst - (char *)token->text) - 1] = *off;
}
/* compute the new length */
token->nchars = (int)(dst - (char *)token->text);
token->text[token->nchars] = '\0';
token->width = width;
}
/* output a horizontal rule */
static BOOLEAN htmlhr(token)
TOKEN *token;
{
int len;
long offset;
assert(col == 0 && textwidth > 0);
/* Normally the hrule is as wide as the line, but if the cursor is on
* the "<hr>" token then we want to shrink it slightly so that "<hr>"
* will fit on the same row of the window. This allows the window to
* be updated more efficiently.
*/
len = textwidth;
if (list || (o_showmarkups && token->offset[0] <= cursoff && cursoff <= token->offset[token->nchars - 1]))
{
/* leave room for showing the <hr> tag */
len -= textwidth - token->nchars;
offset = -1;
}
else
{
/* make it as wide as the screen; the <hr> won't show */
len = textwidth;
offset = token->offset[0];
}
if (len < 3)
len = 3;
/* draw the hrule */
(*drawchar)(&space, -1, 'g', -1);
(*drawchar)(&hyphen, -(len - 2), 'g', offset);
col = len - 1;
return False;
}
/* Output the "alt" text from an <img> tag */
static BOOLEAN htmlimg(token)
TOKEN *token;
{
int i, j;
/* look for an "alt=..." argument */
for (i = 5; i < token->nchars && CHARncmp(&token->text[i - 5], toCHAR(" alt="), 5); i++)
{
}
/* decide how to display this image */
if (i >= token->nchars)
{
/* there is no "alt=..." string, so display the whole tag */
i = 0;
j = token->nchars;
}
else if (token->text[i] == '"')
{
/* the "alt=" argument has a quoted argument */
i++;
for (j = i; j < token->nchars && token->text[j] != '"'; j++)
{
}
}
else
{
/* the "alt=" argument is unquoted */
for (j = i; j < token->nchars && token->text[j] != ' ' && token->text[j] != '>'; j++)
{
}
}
/* copy it into the manput() arguments, and then put it. */
for (manlen = 0; i < j && manlen < QTY(mantext); i++, manlen++)
{
mantext[manlen] = token->text[i];
manoffset[manlen] = token->offset[i];
manfont[manlen] = 'b';
}
return manput();
}
/* Set the modeinfo's "graphic" flag if <pre graphic> */
static BOOLEAN htmlpre(token)
TOKEN *token;
{
int i;
for (graphic = False, i = 0; i < token->nchars; i++)
{
if (token->text[i] == 'g')
{
graphic = True;
break;
}
}
return False;
}
/* List items are preceded by a less-indented number or bullet */
static BOOLEAN htmlli(token)
TOKEN *token;
{
CHAR buf[10];
int len;
assert(col == 0);
/* output a bullet or count */
if (nest == 1 && listcnt > 0)
{
/* convert item# to characters */
long2CHAR(buf, (long)listcnt++);
CHARcat(buf, toCHAR(")"));
len = CHARlen(buf);
/* output whitespace for indentation */
if (indent - len > 1)
{
(*drawchar)(&space, 1 + len - indent, 'n', -1);
col += indent - len - 1;
}
/* output the item number */
(*drawchar)(buf, len, 'n', -1);
col += len;
}
else
{
/* output whitespace for indentation */
if (indent > 2)
{
(*drawchar)(&space, 2 - indent, 'n', -1);
col += indent - 2;
}
/* output a bullet */
(*drawchar)(&bullet, 1, 'g', -1);
col++;
}
/* Note: We would like to do an assert(mui->col == mui->indent - 1)
* here, but if the number/bullet doesn't fit within the indentation
* space then our indentation might be off. So we won't.
*/
return False;
}
/* Form elements are shown as reverse-video areas */
static BOOLEAN htmlinput(token)
TOKEN *token;
{
int height; /* 1 for input, 2 for textarea */
int width; /* displayed width of item */
int vallen; /* length of the value */
int validx; /* index into token->text[] of initial value */
BOOLEAN button; /* does this form item appear to be a button? */
BOOLEAN radio; /* does this form item appear to be a radio button? */
char font; /* font - 'E' for buttons, or 'N' for any other */
int mycol;
int i;
/* parse the arguments */
height = (token->text[1] == 't') ? 3 : 1;
width = vallen = validx = 0;
button = radio = False;
font = 'u';
for (i = 4; i < token->nchars; i++)
{
if (!CHARncmp(&token->text[i], toCHAR("value="), 6))
{
i += 6;
if (token->text[i] == '"')
{
i++;
validx = i;
for (vallen = i; i < token->nchars && token->text[i] != '"'; i++)
{
}
}
else
{
validx = i;
for (vallen = i; i < token->nchars && !isspace(token->text[i]); i++)
{
}
}
vallen = i - vallen;
}
else if (!CHARncmp(&token->text[i], toCHAR("size="), 5)
|| !CHARncmp(&token->text[i], toCHAR("cols="), 5))
{
i += 5;
width = atoi(tochar8(&token->text[i]));
}
else if (!CHARncmp(&token->text[i], toCHAR("type="), 5))
{
i += 5;
if (token->text[i] == '"')
i++;
if (!CHARncmp(&token->text[i], toCHAR("checkbox"), 8)
|| !CHARncmp(&token->text[i], toCHAR("CHECKBOX"), 8))
{
/* CHECKBOX button */
button = radio = True;
i += 8;
}
else if (!CHARncmp(&token->text[i], toCHAR("hidden"), 6)
|| !CHARncmp(&token->text[i], toCHAR("HIDDEN"), 6))
{
/* HIDDEN field -- do nothing with it */
return False;
}
else if (!CHARncmp(&token->text[i], toCHAR("radio"), 5)
|| !CHARncmp(&token->text[i], toCHAR("RADIO"), 5))
{
/* RADIO button */
button = radio = True;
i += 5;
}
else if (token->text[i] != 't' && token->text[i] != 'T')
{
/* not TEXT, probably SUBMIT or RESET button */
button = True;
font = 'B';
}
}
else if (!CHARncmp(&token->text[i], toCHAR("checked"), 7))
{
font = 'N';
i += 7;
}
}
/* Most buttons are always as wide as their value, but radio & checkbox
* buttons only need to show a single character.
*/
if (radio)
{
vallen = 1;
}
if (button)
{
width = vallen;
}
/* remember the column */
mycol = col;
if (mycol < indent)
mycol = indent;
else if (anyspc)
{
mycol++;
anyspc = False;
}
/* will it fit on this line? */
if (!first && mycol + width > textwidth)
{
/* no it won't */
(*drawchar)(&newline, 1, 'n', -1);
col = 0;
return True;
}
/* output the image */
for (i = 1; i <= height; i++)
{
if (col > mycol)
{
(*drawchar)(&newline, 1, 'n', -1);
col = 0;
}
if (col < mycol)
{
(*drawchar)(&space, col - mycol , 'n', -1);
col = mycol;
}
if (vallen > 0)
(*drawchar)(&token->text[validx], vallen, font, token->offset[validx]);
(*drawchar)(&space, vallen - width, font, token->offset[token->nchars - 1]);
col += width;
}
anyspc = False;
return False;
}
/* Look up an html markup token in a table */
static void htmlmarkup(token)
TOKEN *token; /* the token to lookup */
{
static MARKUP tbl[] =
{
/* Tag Effects Function */
/* TBILFFD */
{ "html", "Y-2-NY-" },
{ "/html", "N-2-NY-" },
{ "head", "Y-2-NY-" },
{ "/head", "N-2-NY-" },
{ "title", "Y-2-NY-" },
{ "/title", "N-2-NY-" },
{ "body", "N-2-NY-" },
{ "/body", "N-2-NY-" },
{ "h1", "Np0-BYS" },
{ "/h1", "N12-NY-" },
{ "h2", "Nc1-BYS" },
{ "/h2", "N12-NY-" },
{ "h3", "N12-BYS" },
{ "/h3", "N02-NY-" },
{ "h4", "N12-IY-" },
{ "/h4", "N02-NY-" },
{ "h5", "N12-IY-" },
{ "/h5", "N02-NY-" },
{ "h6", "N12-IY-" },
{ "/h6", "N02-NY-" },
{ "p", "N1--NYP" },
{ "hr", "N02--Y-", htmlhr },
{ "img", "N------", htmlimg },
{ "br", "N0---Y-" },
{ "table", "N02-NY-" },
{ "/table", "N02-NY-" },
{ "tr", "N02--Y-" },
{ "th", "N-=-BY-" },
{ "td", "N-=-NY-" },
{ "blockquote", "N14-NYP" },
{ "/blockquote","N12-NY-" },
{ "pre", "N0--FNP", htmlpre },
{ "/pre", "N0--NY-" },
{ "dir", "N0>-FNP", htmlpre },
{ "/dir", "N0<-NY-" },
{ "xmp", "N0>-FN-", htmlpre },
{ "/xmp", "N0<-NY-" },
{ "dl", "N-2-NYS" },
{ "/dl", "N02-NY-" },
{ "dt", "N12-BYP" },
{ "dd", "N03-NY-" },
{ "ol", "N->#-YP" },
{ "/ol", "N0<N-Y-" },
{ "ul", "N->Y-YP" },
{ "/ul", "N0<N-Y-" },
{ "menu", "N->Y-Y-" },
{ "/menu", "N-<N-Y-" },
{ "li", "N0-----", htmlli },
{ "input", "N-----T", htmlinput },
{ "textarea", "N-----T", htmlinput },
{ "a", "N---u-T" },
{ "/a", "N---=--" },
{ "cite", "N---i--" },
{ "/cite", "N---=--" },
{ "dfn", "N---i--" },
{ "/dfn", "N---=--" },
{ "em", "N---i--" },
{ "/em", "N---=--" },
{ "kbd", "N---b--" },
{ "/kbd", "N---=--" },
{ "strong", "N---b--" },
{ "/strong", "N---=--" },
{ "var", "N---i--" },
{ "/var", "N---=--" },
{ "address", "N---i--" },
{ "/address", "N---=--" },
{ "b", "N---b--" },
{ "/b", "N---=--" },
{ "i", "N---i--" },
{ "/i", "N---=--" },
{ "u", "N---u--" },
{ "/u", "N---=--" },
{ "tt", "N---f--" },
{ "/tt", "N---=--" },
{ (char *)0, "N------" }
};
MARKUP *scan; /* used for scanning the tbl[] array */
int len; /* length of the markup */
/* find the length of the markup's name */
assert(token->nchars > 1 && token->text[0] == '<');
for (len = 1;
len < token->nchars &&
((len == 1 && token->text[len] == '/') || isalnum(token->text[len]));
len++)
{
}
len--; /* since we started at 1 */
/* look it up in the table */
for (scan = tbl;
scan->name &&
(strlen(scan->name) != (unsigned)len ||
strncmp(scan->name, (char *)token->text + 1, (size_t)len));
scan++)
{
}
/* remember it */
token->markup = scan;
}
/* Read the next token. */
static TOKEN *htmlget(refp)
CHAR **refp; /* address of a (CHAR *) used for scanning */
{
long offset;
BOOLEAN lower;
/* if the CHAR pointer is NULL, then return NULL */
if (!*refp)
return NULL;
/* Get first character of token */
offset = markoffset(scanmark(refp));
rettok.text[0] = **refp;
rettok.offset[0] = offset++;
rettok.nchars = 1;
rettok.markup = NULL;
scannext(refp);
/* If '<' then token is a markup */
if (rettok.text[0] == '<')
{
/* This is a markup. Collect characters up to next '>' */
for (lower = True; *refp && **refp != '>'; offset++, scannext(refp))
{
/* if token text is full, then skip this char */
if (rettok.nchars >= QTY(rettok.text) - 2)
continue;
/* Store the character. This is a little complex
* because we want to convert uppercase tags and
* parameter names to lowercase, but leave parameter
* values unchanged. Also, any whitespace character
* should be displayed as a space.
*/
if (**refp == '=')
lower = False;
else if (isspace(**refp))
lower = True;
if (**refp <= ' ')
rettok.text[rettok.nchars] = ' ';
else if (isupper(**refp) && lower)
rettok.text[rettok.nchars] = tolower(**refp);
else
rettok.text[rettok.nchars] = **refp;
rettok.offset[rettok.nchars++] = offset;
}
/* store the terminating '>' */
rettok.text[rettok.nchars] = '>';
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
rettok.text[rettok.nchars] = '\0';
if (*refp)
scannext(refp);
/* lookup the markup */
htmlmarkup(&rettok);
/* when computing line breaks, assume this markup is hidden */
rettok.width = 0;
}
else if (rettok.text[0] <= ' ')
{
/* This is a whitespace token. Each whitespace token contains
* a SINGLE whitespace character. Control characters other
* than '\t' and '\n' are treated as spaces.
*/
if (rettok.text[0] != '\t' && rettok.text[0] != '\n')
{
rettok.text[0] = ' ';
}
/* when computing line breaks, assume this whitespace shows */
rettok.width = 1;
}
else
{
/* This is a word. Collect characters up to next whitespace */
for (;
*refp
&& rettok.nchars < QTY(rettok.text) - 1
&& !isspace(**refp)
&& **refp != '<';
offset++, scannext(refp))
{
rettok.text[rettok.nchars] = **refp;
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
}
/* For now, when computing line breaks, assume each character
* of this word is normally visible. When character escapes
* are processed, this may change.
*/
rettok.width = rettok.nchars;
}
/* Mark the end of the text, and return the token */
rettok.text[rettok.nchars] = '\0';
return &rettok;
}
/* start the mode, and allocate dminfo */
static DMINFO *htmlinit(win)
WINDOW win;
{
long cursoffset; /* offset of cursor */
TOKEN *token;
MARKBUF top;
CHAR *p;
/* inherit some functions from dmnormal */
dmhtml.wordmove = dmnormal.wordmove;
/* allocate the info struct */
mui = (MUINFO *)safealloc(1, sizeof(MUINFO));
mui->get = htmlget;
mui->escape = htmlescape;
mui->line = safealloc(GRANULARITY, sizeof(LINEINFO));
mui->nlines = 1; /* every buffer has at least one line */
/* temporarily move the cursor someplace harmless */
cursoffset = markoffset(win->cursor);
marksetoffset(win->cursor, o_bufchars(markbuffer(win->cursor)));
/* format the buffer */
win->mi = (DMINFO *)mui;
(void)start(win, marktmp(top, markbuffer(win->cursor), 0), NULL);
p = scanalloc(&p, &top);
for (token = (*mui->get)(&p);
token;
token = (*mui->get)(&p))
{
/* if cursor is at position 0, and this is a text token,
* then move the cursor here. This is done because otherwise
* the cursor would always start on an ugly formatting code.
*/
if (cursoffset == 0L
&& !title
&& !isspace(token->text[0])
&& !token->markup)
{
cursoffset = token->offset[0];
}
/* output the token. If it forces a new line, remember where
* that new line started.
*/
if (put(token))
{
assert(first == True && col == 0);
storestate(token->offset[0], NULL);
(void)put(token);
}
}
scanfree(&p);
/* locate the title */
findtitle(markbuffer(win->cursor));
/* set the cursor back to its original position, or its adjusted one */
marksetoffset(win->cursor, cursoffset);
/* Done! */
return (DMINFO *)mui;
}
/* Return a dynamically-allocated string containing the name of the tag at
* the cursor, or NULL if the cursor isn't on a tag.
*/
static CHAR *htmltagatcursor(win, cursor)
WINDOW win;
MARK cursor;
{
CHAR *ret; /* the return value */
CHAR *p; /* used for scanning */
TOKEN *token; /* a token from the buffer */
TOKEN anchor; /* copy of last <a...> or </a> token */
long endoffset;/* where to stop scanning */
int i; /* index into mui->line */
MARKBUF tmp;
/* We need to find the last <a ...> or </a> tag which occurs before
* before the cursor. Since we can't read tokens backward, we must
* start scanning at the beginning of the line and read forward until
* we pass the cursor, remembering the last <a ...> or </a> as we go.
* If we haven't found one by the time we pass the cursor, then we
* need to restart scanning at the beginning of the preceding line.
*/
/* find the start of the cursor's line */
mui = (MUINFO *)win->mi;
if (mui->nlines == 0)
return NULL;
endoffset = markoffset(cursor) + 1;
for (i = 1; i < mui->nlines && mui->line[i].offset < endoffset; i++)
{
}
i--; /* the above loop went one line too far */
/* read forward from the start of the line, watching for <a ...> and
* </a> tags. Stop when we know we've found the last one before the
* cursor.
*/
anchor.text[0] = 0;
do
{
scanalloc(&p, marktmp(tmp, markbuffer(cursor), mui->line[i].offset));
while ((token = htmlget(&p)) != NULL && token->offset[0] < endoffset)
{
if (token->markup && (!CHARncmp(token->text, toCHAR("<a href="), 8) || !CHARcmp(token->text, toCHAR("</a>"))))
{
anchor = *token;
}
}
scanfree(&p);
endoffset = mui->line[i].offset;
} while (anchor.text[0] == 0 && --i >= 0);
/* If we found an <a ...> tag with an href parameter, then generate
* a dynamically-allocated copy of the URL.
*/
ret = NULL;
if (!CHARncmp(anchor.text, toCHAR("<a href="), 8))
{
p = &anchor.text[8];
if (*p == '"')
{
while (*++p != '"')
buildCHAR(&ret, *p);
}
else
{
do
{
buildCHAR(&ret, *p);
} while (*++p != ' ' && *p != '>');
}
}
/* return the URL or a NULL pointer */
return ret;
}
static MARK htmltagload(tagname, from)
CHAR *tagname;
MARK from; /* where the cursor is while we're loading */
{
static MARKBUF retmark; /* the return value */
char *filename; /* name of file containing tag */
CHAR *anchorname; /* name of tag's anchor */
CHAR *p;
TOKEN *token;
int i, j;
/* separate the tagname into filename and anchorname */
filename = safealloc((int)CHARlen(tagname) + 1, sizeof(char));
for (anchorname = tagname, i = 0;
*anchorname && *anchorname != '#';
anchorname++, i++)
{
filename[i] = *anchorname;
if (!CHARncmp(anchorname, toCHAR("://"), 3))
{
msg(MSG_ERROR, "non-local URLs aren't supported yet");
safefree(filename);
return NULL;
}
}
filename[i] = 0;
/* Skip the '#' at the start of the anchor name, if any */
if (*anchorname == '#')
anchorname++;
/* Load the file into a buffer. If the filename isn't a full pathname
* then assume that the file is in the same directory as the current
* file. In fact, check to see if it is the same buffer first!
*/
if (!filename[0]
|| (from
&& o_filename(markbuffer(from))
&& !CHARcmp(o_filename(markbuffer(from)), toCHAR(filename))))
{
marktmp(retmark, markbuffer(from), 0);
}
else if (from && isalpha(*filename) && o_filename(markbuffer(from)))
{
marktmp(retmark,
bufload(NULL,
dirpath(dirdir(tochar8(o_filename(markbuffer(from)))),
filename), False),
0);
}
else
{
marktmp(retmark, bufload(NULL, filename, False), 0);
}
safefree(filename);
if (!markbuffer(&retmark) || o_bufchars(markbuffer(&retmark)) == 0)
{
return NULL;
}
/* scan forward from top for an anchor token with this name */
if (*anchorname)
{
i = (int)CHARlen(anchorname);
scanalloc(&p, &retmark);
#if 0
for (token = htmlget(&p);
token
&& (CHARncmp(token->text, toCHAR("<a name="), 8)
|| (token->text[8] == '"'
? CHARncmp(&token->text[9], anchorname, (size_t)i) || token->text[9 + i] != '"'
: CHARncmp(&token->text[8], anchorname, (size_t)i) || token->text[8 + i] != '>'));
token = htmlget(&p))
{
}
#else
for (token = htmlget(&p); token; token = htmlget(&p))
{
/* ignore if not markup or not a possible target */
if (!token->markup || token->markup->DEST == '-')
continue;
/* scan for "id=" or "name=" */
for (j = 2; j < token->nchars; j++)
{
if (!CHARncmp(&token->text[j], toCHAR(" name="), 6))
{
j += 6;
break;
}
if (!CHARncmp(&token->text[j], toCHAR(" id="), 4))
{
j += 4;
break;
}
}
if (j >= token->nchars)
continue;
/* compare to sought name. Beware of quotes */
if (token->text[j] == '"'
? !CHARncmp(&token->text[++j], anchorname, (size_t)i)
&& token->text[j + i] == '"'
: !CHARncmp(&token->text[j], anchorname, (size_t)i)
&& (token->text[j + i] == '>' || token->text[j + i] == ' '))
{
break;
}
}
#endif
/* skip to the following non-whitespace text token */
while (token && (token->markup || isspace(token->text[0])))
{
token = htmlget(&p);
}
/* did we find the tag? */
if (token)
{
marksetoffset(&retmark, token->offset[0]);
}
else
{
msg(MSG_WARNING, "[S]anchor $1 not found", anchorname);
}
scanfree(&p);
}
return &retmark;
}
static MARK htmltagnext(cursor)
MARK cursor;
{
CHAR *p; /* used for scanning */
TOKEN *token; /* a token from the text */
static MARKBUF ret; /* return value */
/* read forward to next <a ...> token with an href parameter */
for (scanalloc(&p, cursor);
(token = htmlget(&p)) != NULL
&& (!token->markup
|| token->markup->DEST != 'T'
|| !CHARncmp(token->text, "<a name=", 8)
|| token->offset[0] == markoffset(cursor));
)
{
}
/* if we found an <a ...> token, then skip ahead to the next text token
* or the </a> token.
*/
if (p && !CHARncmp(token->text, toCHAR("<a "), 3))
{
while ((token = htmlget(&p)) != NULL
&& isspace(token->text[0]))
{
}
}
scanfree(&p);
/* if we couldn't find anyplace, return NULL */
if (!token)
{
return NULL;
}
/* else construct a mark for the start of the last token */
return marktmp(ret, markbuffer(cursor), token->offset[0]);
}
/*----------------------------------------------------------------------------*/
/* "man" mode functions */
/* This function interprets codes in a text token, converting them to regular
* text. Note that none of the codes can change the font.
*/
static void manescape(token)
TOKEN *token;
{
int i, j;
TOKEN temp;
/* if the cursor is in this somewhere, then don't change it but we
* still need to compute the width so change a temporary copy.
*/
if (cursoff >= token->offset[0] && cursoff <= token->offset[token->nchars - 1])
{
temp = *token;
temp.offset[0] = cursoff + 1;
manescape(&temp);
token->width = temp.width;
return;
}
/* for each character... */
for (i = j = 0; i < token->nchars; i++)
{
/* most characters are copied as-is */
if (token->text[i] != '\\')
{
token->text[j] = token->text[i];
token->offset[j++] = token->offset[i];
}
else if (i + 1 < token->nchars)
{
switch (token->text[++i])
{
case '|':
case '&':
/* delete the \| or \& */
break;
case 's':
/* delete the \s and the number that follows */
if (token->text[i + 1] == '+' || token->text[i + 1] == '-')
i++;
do
{
i++;
} while (isdigit(token->text[i]));
i--;
break;
case '*':
case 'n':
/* keep the \* or \n as-is */
token->text[j] = '\\';
token->offset[j++] = token->offset[i - 1];
token->text[j] = token->text[i];
token->offset[j++] = token->offset[i];
break;
case 'e':
/* convert \e to backslash */
token->text[j] = '\\';
token->offset[j++] = token->offset[i];
break;
default:
/* convert \X to just plain X */
token->text[j] = token->text[i];
token->offset[j++] = token->offset[i];
}
}
}
/* mark the end of the string */
token ->width = token->nchars = j;
token->text[j] = '\0';
}
/* This function is used to collect the arguments to a .XX macro together
* as a string with a parallel array storing the font.
*/
static int manarg(token, start, font, spc)
TOKEN *token; /* the token to parse */
int start; /* where to begin scanning */
_char_ font; /* initial font of arg */
BOOLEAN spc; /* insert a space before the word? */
{
BOOLEAN quote; /* is this arg enclosed in quotes? */
/* skip leading whitespace */
while (isspace(token->text[start]))
{
start++;
}
/* is this arg quoted? */
quote = (BOOLEAN)(token->text[start] == '"');
if (quote)
start++;
/* are we supposed to insert a space? */
if (spc && start < token->nchars - 2)
{
assert(start > 0);
mantext[manlen] = ' ';
manoffset[manlen] = token->offset[start - 1];
manfont[manlen] = 'b';
manlen++;
}
/* collect text to end of arg, or until mantext[] is full */
while (manlen < QTY(mantext) - 1
&& start < token->nchars - 2
&& (quote ? token->text[start] != '"' : !isspace(token->text[start])))
{
/* handle \fX */
if (token->text[start] == '\\' && token->text[start + 1] == 'f')
{
switch (token->text[start + 2])
{
case '1':
case 'B': font = 'b'; break;
case '2':
case 'I': font = 'i'; break;
default: font = 'n'; break;
}
start += 3;
}
else if (token->text[start] == '\\' && start < token->nchars - 2)
{
switch (token->text[++start])
{
case '|':
case '&':
break;
case 'e':
mantext[manlen] = '\\';
manoffset[manlen] = token->offset[start];
manfont[manlen] = font;
manlen++;
break;
case 's':
/* delete the \s and the number that follows */
if (token->text[start] == '+' || token->text[start] == '-')
start++;
do
{
start++;
} while (isdigit(token->text[start]));
start--;
break;
default:
mantext[manlen] = token->text[start];
manoffset[manlen] = token->offset[start];
manfont[manlen] = font;
manlen++;
}
start++;
}
else if (token->text[start] <= ' ')
{
mantext[manlen] = ' ';
manoffset[manlen] = token->offset[start];
manfont[manlen] = font;
manlen++;
start++;
}
else
{
mantext[manlen] = token->text[start];
manoffset[manlen] = token->offset[start];
manfont[manlen] = font;
manlen++;
start++;
}
}
/* skip the closing quote (if quoted) */
if (quote && token->text[start] == '"')
start++;
/* return the index of the end of the arg */
return start;
}
/* This function either outputs mantext[] and returns False, or (if mantext[]
* is too wide to fit on this line) it outputs a newline and returns True.
*/
static BOOLEAN manput()
{
int i, start;
/* if it won't fit, then output a newline instead */
if (!first && col + manlen > textwidth - listind)
{
/* output a newline */
(*drawchar)(&newline, 1, 'n', anyspc ? spcoffset : -1);
col = 0;
first = True;
return True;
}
/* It will fit. If we need to adjust our indent, do it now */
if (col < indent)
{
(*drawchar)(&space, col - indent, 'n', anyspc ? spcoffset : -1);
col = indent;
anyspc = False;
}
/* Output a space between tokens, usually. */
if (anyspc)
{
(*drawchar)(&space, 1, 'n', spcoffset);
col++;
anyspc = False;
}
/* Output mantext[] in as few chunks as possible */
for (start = 0; start < manlen; start = i)
{
for (i = start + 1;
i < manlen
&& manfont[i] == manfont[start]
&& manoffset[i] == manoffset[i - 1] + 1;
i++)
{
}
(*drawchar)(&mantext[start], (long)(i - start), manfont[start], manoffset[start]);
}
col += manlen;
return False;
}
/* This function implements the .TH macro, which declares the page's title */
static BOOLEAN manTH(token)
TOKEN *token;
{
int i;
/* combine the first & second args as the document name */
manlen = 0;
i = manarg(token, 3, 'n', False);
mantext[manlen++] = '(';
(void)manarg(token, i, 'n', False);
mantext[manlen++] = ')';
mantext[manlen] = '\0';
/* If the title is different, then store it now */
if (!mui->title || CHARcmp(mui->title, mantext))
{
if (mui->title)
safefree(mui->title);
mui->title = CHARdup(mantext);
}
return False;
}
/* This function implements the .SH and .SS macros */
static BOOLEAN manSH(token)
TOKEN *token;
{
long nloff; /* offset of newline */
int i;
/* Get the arguments, as the section title */
manlen = 0;
for (i = 4; i < token->nchars; i++)
{
i = manarg(token, i, 'b', (BOOLEAN)(i != 4));
}
/* If the cursor is located in this token, or we're doing some sort
* of move operation, then pretend that the offsets of all the
* generated characters are -1.
*/
nloff = token->offset[token->nchars - 1];
if ((o_showmarkups && token->offset[0] <= cursoff && cursoff <= token->offset[token->nchars - 1])
|| list || drawchar == countchar)
{
for (i = 0; i < manlen; i++)
manoffset[i] = -1;
nloff = -1;
}
/* output the title, followed by a newline */
anyspc = False;
manput();
(*drawchar)(&newline, 1, 'n', nloff);
col = 0;
first = True;
anyspc = False;
reduce = True;
/* force the indentation to be the default */
indent = listind * 2;
/* Return False even though we did output a newline. The newline
* (and the text before it) is considered to be part of the same
* "line" as far as movement is concerned.
*/
return False;
}
/* This function implements the font-changing macros such as .BI, .RB, and
* so on. The resulting text is treated as a single word; if it can't fit
* on the current line, then this function merely outputs a newline.
*/
static BOOLEAN manBI(token)
TOKEN *token;
{
char font1, font2;
int start;
/* choose the fonts & start, based on macro name */
switch (token->text[1])
{
case 'B': font1 = 'b'; break;
case 'I': font1 = 'i'; break;
default: font1 = 'n';
}
switch (token->text[2])
{
case 'B': font2 = 'b'; start = 4; break;
case 'I': font2 = 'i'; start = 4; break;
case 'S':
case 'R': font2 = 'n'; start = 4; break;
default: font2 = font1; start = 3;
}
/* collect the args, with their fonts */
manlen = 0;
start = manarg(token, start, font1, False);
start = manarg(token, start, font2, (BOOLEAN)(font1 == font2));
start = manarg(token, start, font1, (BOOLEAN)(font1 == font2));
start = manarg(token, start, font2, (BOOLEAN)(font1 == font2));
start = manarg(token, start, font1, (BOOLEAN)(font1 == font2));
start = manarg(token, start, font2, (BOOLEAN)(font1 == font2));
/* If the cursor is on this token, then just tweak its width and
* return False so the remaining token-putting code will handle
* line-wrap. Else perform the token's output.
*/
if (list || (o_showmarkups && token->offset[0] <= cursoff && cursoff <= token->offset[token->nchars - 1]))
{
token->width = manlen + 1; /* "+ 1" to allow for whitespace */
return False;
}
else if (manput())
{
return True;
}
/* assume there should be some whitespace after this */
anyspc = True;
spcoffset = token->offset[token->nchars - 1];
return False;
}
/* This function implements the .IP macro. It outputs its first argument,
* and then increases the indentation. If necessary, it will then output
* a newline character, but this newline character isn't considered to be
* the end of "line" as far as moving the cursor is concerned; it is just
* a funny type of wrapping.
*/
static BOOLEAN manIP(token)
TOKEN *token;
{
int i, start;
char font, font2;
/* if this token is going to be displayed anyway, then don't bother
* performing its output.
*/
if (list || (o_showmarkups && token->offset[0] <= cursoff && cursoff <= token->offset[token->nchars - 1]))
return False;
/* get the first arg, as the paragraph tag */
manlen = 0;
start = manarg(token, 4, 'n', False);
/* For .TP (but not .IP) get other args as part of the tag, too */
if (token->text[1] == 'T')
{
/* if first word was .B or .I, then change font */
if (manlen == 2 && mantext[0] == '.' && (mantext[1] == 'B' || mantext[1] == 'I'))
{
font = font2 = tolower(mantext[1]);
manlen = 0;
}
else if (manlen == 3 && mantext[0] == '.')
{
font = tolower(mantext[1]);
if (font != 'b' && font != 'i')
font = 'n';
font2 = tolower(mantext[2]);
if (font2 != 'b' && font2 != 'i')
font2 = 'n';
manlen = 0;
}
else
{
font = font2 = 'n';
}
/* Copy the remaining args, in the desired font[s]. If the
* fonts are different, then delete whitespace from between
* arguments.
*/
for (i = 0; i < 5; i++)
{
start = manarg(token, start, (i & 1) ? font2 : font, (BOOLEAN)(font == font2 && manlen > 0));
}
}
/* output the paragraph tag */
(void)manput();
/* increase the indentation by one full tab for .IP, two for .TP */
if (token->text[1] == 'T')
indent += 4 * listind;
else
indent += 2 * listind;
/* If necessary, output a newline as part of the text */
if (col >= indent)
{
(*drawchar)(&newline, 1, 'n', token->offset[token->nchars - 1]);
col = 0;
}
/* return False, even if we output a newline */
return False;
}
/* Look up a man markup token in a table */
static void manmarkup(token)
TOKEN *token; /* the token to lookup */
{
static MARKUP tbl[] =
{
/* Tag Effects Function */
/* TBILFFD */
{ "\\\"", "Y------" },
{ "TH", "Y----Y-", manTH },
{ "SH", "Nc0-NYS", manSH },
{ "SS", "N11-NYS", manSH },
{ "B", "N------", manBI },
{ "I", "N------", manBI },
{ "SM", "N------", manBI },
{ "BI", "N------", manBI },
{ "IB", "N------", manBI },
{ "RI", "N------", manBI },
{ "IR", "N------", manBI },
{ "BR", "N------", manBI },
{ "RB", "N------", manBI },
{ "BS", "N------", manBI },
{ "SB", "N------", manBI },
{ "IP", "N12-NYP", manIP },
{ "TP", "N12-NYP", manIP },
{ "PP", "N12-NYP" },
{ "P", "N12-NYP" },
{ "LP", "N12-NYP" },
{ "RS", "N->----" },
{ "RE", "N-<----" },
{ "br", "N0-----" },
{ "sp", "N1-----" },
{ "nf", "N0---N-" },
{ "fi", "N0---Y-" },
{ "DS", "N1---N-" },
{ "DE", "N0---Y-" },
{ (char *)0, "N------" }
};
MARKUP *scan; /* used for scanning the tbl[] array */
/* look it up in the table */
for (scan = tbl;
scan->name &&
(strncmp(scan->name, (char *)token->text + 1, strlen(scan->name))
|| isalnum(token->text[strlen(scan->name) + 1]));
scan++)
{
}
/* remember it */
token->markup = scan;
}
/* Get the next token from a "man" document, or return NULL at end. */
static TOKEN *manget(refp)
CHAR **refp;
{
MARK back; /* address of a backslash */
long offset; /* offset of character that *refp points to */
static MARKUP fontchg;/* describes font change markups */
/* Initialize "back" just to silence a compiler warning */
back = NULL;
/* if the CHAR pointer is NULL, then return NULL */
if (!*refp)
return NULL;
/* Get first character of token */
offset = markoffset(scanmark(refp));
rettok.text[0] = **refp;
rettok.offset[0] = offset++;
rettok.nchars = 1;
rettok.width = 0;
rettok.markup = NULL;
scannext(refp);
/* If '.' at the start of a line, then token is a markup */
if ((rettok.text[0] == '.' || rettok.text[0] == '\'') && !midline)
{
/* This is a markup. Collect characters up to next '\n' */
for (;
*refp && rettok.nchars < QTY(rettok.text) - 3 && **refp != '\n';
offset++, scannext(refp))
{
rettok.text[rettok.nchars] = **refp;
rettok.offset[rettok.nchars++] = offset;
}
/* If this is ".TP" then read the following line as part of
* this token.
*/
if (!CHARncmp(rettok.text, toCHAR(".TP"), 3)
&& *refp
&& **refp == '\n'
&& scannext(refp))
{
/* the newline after ".TP" becomes a space */
rettok.nchars = 3;
rettok.text[rettok.nchars] = ' ';
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
offset++;
for (;
*refp && rettok.nchars < QTY(rettok.text) - 3 && **refp != '\n';
offset++, scannext(refp))
{
rettok.text[rettok.nchars] = **refp;
rettok.offset[rettok.nchars++] = offset;
}
}
/* store the terminating '\n' as "^J" */
rettok.text[rettok.nchars] = '^';
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
rettok.text[rettok.nchars] = 'J';
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
rettok.text[rettok.nchars] = '\0';
if (*refp)
scannext(refp);
/* lookup the markup */
manmarkup(&rettok);
/* remember that we stopped after a newline */
midline = False;
}
else if (*refp && rettok.text[0] == '\\' && **refp == 'f')
{
/* This is a font-change macro. Get the final character */
rettok.text[1] = 'f';
rettok.offset[1] = offset++;
scannext(refp);
rettok.text[2] = (*refp ? **refp : 'R');
rettok.offset[2] = offset++;
rettok.nchars = 3;
if (*refp)
scannext(refp);
/* construct a simple MARKUP struct */
fontchg.name = tochar8(rettok.text);
strcpy(fontchg.attr, "----n--");
switch (rettok.text[2])
{
case '1':
case 'B': fontchg.FONT = 'b'; break;
case '2':
case 'I': fontchg.FONT = 'i'; break;
case 'P': fontchg.FONT = '='; break;
default: fontchg.FONT = 'n'; break;
}
rettok.markup = &fontchg;
/* no newline at the end of this token! */
midline = True;
}
else if (rettok.text[0] <= ' ')
{
/* This is a whitespace token. Each whitespace token contains
* a SINGLE whitespace character. Control characters other
* than '\t' and '\n' are treated as spaces.
*/
if (rettok.text[0] != '\t' && rettok.text[0] != '\n')
rettok.text[0] = ' ';
/* remember if this is a newline or not */
midline = (BOOLEAN)(rettok.text[0] != '\n');
/* assume this whitespace will show, for computing line break */
rettok.width = 1;
}
else
{
/* This is a word. Collect chars up to next whitespace or
* "\fX" string.
*/
for (;
*refp
&& rettok.nchars < QTY(rettok.text) - 1
&& !isspace(**refp)
&& (rettok.nchars < 2
|| rettok.text[rettok.nchars - 2] != '\\'
|| rettok.text[rettok.nchars - 1] != 'f');
offset++, scannext(refp))
{
if (**refp == '\\')
back = scanmark(refp);
rettok.text[rettok.nchars] = **refp;
rettok.offset[rettok.nchars] = offset;
rettok.nchars++;
}
/* if this ended with a \fX then we need to adjust *refp */
if (rettok.nchars >= 2
&& rettok.text[rettok.nchars - 2] == '\\'
&& rettok.text[rettok.nchars - 1] == 'f')
{
scanseek(refp, back);
rettok.nchars -= 2;
}
/* this didn't end with a newline */
midline = True;
/* For now, assume all characters of this work will be visible.
* When escapes are processed, this may change.
*/
rettok.width = rettok.nchars;
}
/* Mark the end of the text, and return the token */
rettok.text[rettok.nchars] = '\0';
return &rettok;
}
static DMINFO *maninit(win)
WINDOW win;
{
TOKEN *token;
MARKBUF top;
CHAR *p;
long cursoffset;
/* inherit some functions from dmnormal */
dmman.wordmove = dmnormal.wordmove;
dmman.tagatcursor = dmnormal.tagatcursor;
dmman.tagload = dmnormal.tagload;
dmman.tagnext = dmnormal.tagnext;
/* allocate the info struct */
mui = (MUINFO *)safealloc(1, sizeof(MUINFO));
mui->get = manget;
mui->escape = manescape;
mui->line = safealloc(GRANULARITY, sizeof(LINEINFO));
mui->nlines = 1; /* every buffer has at least one line */
/* move the cursor someplace harmless, so it won't affect formatting */
cursoffset = markoffset(win->cursor);
marksetoffset(win->cursor, o_bufchars(markbuffer(win->cursor)));
/* format the buffer */
win->mi = (DMINFO *)mui;
(void)start(win, marktmp(top, markbuffer(win->cursor), 0), NULL);
p = scanalloc(&p, &top);
for (token = (*mui->get)(&p);
token;
token = (*mui->get)(&p))
{
/* if cursor is at position 0, and this is a text token,
* then move the cursor here. This is done because otherwise
* the cursor would always start on an ugly formatting code.
*/
if (cursoffset == 0L
&& !title
&& !isspace(token->text[0])
&& !token->markup)
{
cursoffset = token->offset[0];
}
/* output the token. If it forces a new line, remember where
* that new line started.
*/
if (put(token))
{
assert(first == True && col == 0);
storestate(token->offset[0], NULL);
(void)put(token);
}
}
scanfree(&p);
/* restore the cursor position */
marksetoffset(win->cursor, cursoffset);
/* Done! */
return (DMINFO *)mui;
}
/*----------------------------------------------------------------------------*/
/* Generic markup functions */
/* This function performs a single token. If the token is markup, then the
* side effects are performed and the function (if any) is called. If text,
* or if the cursor is in the token, then the text of the token is output.
* It is assumed that drawchar and cursoff are set before this function is
* called. Returns False if the token fits on this line. If it didn't fit,
* or was a markup that caused a line break, then it returns True in which
* case put() should be called again for the same token to start generating
* the next line.
*/
static BOOLEAN put(token)
TOKEN *token;
{
char tmpfont;
CHAR tmpch, lch, rch;
int i, origcol;
BOOLEAN hascursor; /* indicates whether this token contains cursor */
/* determine whether the cursor is in this token. If the "list"
* option is set, then pretend that all tokens contain the cursor.
*/
hascursor = (BOOLEAN)(list || (o_showmarkups && token->offset[0] <= cursoff && cursoff <= token->offset[token->nchars - 1]));
/* is it a markup? */
if (token->markup)
{
/* if this token will cause a line break, and no line break
* was already pending anyway, then do the line break and
* nothing else. We'll do the rest of this markup's job
* at the start of the next line.
*/
if (token->markup->BREAKLN != '-' && !first)
{
(*drawchar)(&newline, 1, 'n', anyspc ? spcoffset : -1);
reduce = (BOOLEAN)(col == 0);
col = 0;
first = True;
return True;
}
/* do all the standard effects */
switch (token->markup->TITLE)
{
case 'Y': title = True; break;
case 'N': title = False; break;
}
switch (token->markup->BREAKLN)
{
case '0':
reduce = True;
break;
case '1':
if (!reduce)
(*drawchar)(&newline, 1, 'n', anyspc ? spcoffset : -1);
reduce = True;
break;
case '2':
(*drawchar)(&newline, reduce ? 1 : 2, 'n', anyspc ? spcoffset : -1);
reduce = True;
break;
case 'c':
(*drawchar)(&vtab, 1, 'n', -1);
reduce = True;
break;
case 'p':
(*drawchar)(&formfeed, 1, 'n', -1);
reduce = True;
break;
}
switch (token->markup->INDENT)
{
case '<':
indent = (indent < listind ? 0 : indent - listind);
break;
case '>':
indent += listind;
break;
case '=':
if (col < tabstop)
indent = tabstop;
else
indent = col + 2 * tabstop - ((col + tabstop) % (2 * tabstop));
break;
case '0':
case '1':
case '2':
case '3':
case '4':
indent = (token->markup->INDENT - '0') * listind;
break;
}
switch (token->markup->LIST)
{
case 'N':
if (nest > 0)
nest--;
break;
case 'Y':
nest++;
if (nest == 1)
listcnt = 0;
break;
case '#':
nest++;
if (nest == 1)
listcnt = 1;
break;
}
switch (token->markup->FONT)
{
case '=': curfont = deffont; break;
case 'n':
case 'b':
case 'u':
case 'f':
case 'e':
case 'i': curfont = token->markup->FONT; break;
case 'N':
case 'B':
case 'U':
case 'F':
case 'E':
case 'I': curfont = deffont = tolower(token->markup->FONT); break;
}
switch (token->markup->FILL)
{
case 'Y': graphic = prefmt = False; break;
case 'N': prefmt = True; break;
}
/* If there is a function, call it too. If the function
* returns True, then act as though the markup caused a
* newline.
*/
if (token->markup->fn)
{
if ((*token->markup->fn)(token))
return True;
}
/* if the cursor isn't on this token, don't show it */
if (!hascursor)
{
first = False;
return False;
}
}
else
{
reduce = False;
}
/* no token causes visible output when in "title" mode, unless the
* cursor happens to be located on it.
*/
if (title && !hascursor)
{
first = False;
return False;
}
/* Newlines are handled differently that other whitespace, when in
* prefmt mode. This is because newlines mark the end of one line,
* so they become the first token of the next line... but when we
* get to the next line we don't want to process the newline again,
* or we'd get into an endless loop. Here we catch the case were
* we are calling put() the second time.
*/
if (prefmt && token->text[0] == '\n' && first)
{
first = False;
anyspc = True;
spcoffset = token->offset[0];
return False;
}
/* Expand any escape sequences in the token's text */
if (mui->escape && !token->markup)
(*mui->escape)(token);
/* Is it whitespace? Are we supposed to adjust the text formatting? */
if (isspace(token->text[0]) && !prefmt)
{
/* Just set a flag indicating that a space has been
* encountered. Also remember its offset if it is the first
* space encountered, or if the cursor is on this space.
*/
if (!anyspc || hascursor)
spcoffset = token->offset[0];
anyspc = True;
first = False;
return False;
}
/* if not a markup, and it won't fit on the current line, then output
* a '\n' and return True so it'll appear on the next line.
*/
else if (col + token->width + 1 > textwidth - 4
&& !prefmt && col != 0 && !token->markup)
{
(*drawchar)(&newline, 1, 'n', anyspc ? spcoffset : -1);
col = 0;
anyspc = False;
first = True;
return True;
}
/* if we need to adjust our indent, do it now */
if (col < indent)
{
(*drawchar)(&space, col - indent, 'n', anyspc ? spcoffset : -1);
col = indent;
anyspc = False;
}
/* Output a space between tokens, usually. */
if (anyspc)
{
(*drawchar)(&space, 1, 'n', spcoffset);
col++;
anyspc = False;
}
/* remember our current column, so we can pretend visible markups are
* zero characters wide later.
*/
origcol = col;
/* Output the token's text. Watch for whitespace and maybe graphics. */
for (i = 0; i < token->nchars; i++)
{
switch (token->text[i])
{
case '\t':
/* convert to spaces */
col = tabstop - (col - indent) % tabstop;
(*drawchar)(&space, -col, curfont, token->offset[0]);
col += origcol;
break;
case '\n':
/* newlines are tricky. If we get here, then we must
* be in no-fill mode, so the newline should be output.
* However, we'll need to be on the lookout for this
* same newline next time.
*/
(*drawchar)(&newline, 1, 'n', -1);
col = 0;
anyspc = True;
first = True;
return True;
case '|':
case '.':
case '^':
case '-':
tmpfont = curfont;
tmpch = token->text[i];
if (graphic)
{
lch = (i > 0 ? token->text[i - 1] : ' ');
rch = (i < token->nchars - 1 ? token->text[i + 1] : ' ');
if (lch == '-' && rch == '-')
{
if (tmpch == '|') tmpch = '5';
else if (tmpch == '.') tmpch = '8';
else if (tmpch == '^') tmpch = '2';
tmpfont = 'g';
}
else if (lch == '-')
{
if (tmpch == '|') tmpch = '6';
else if (tmpch == '.') tmpch = '9';
else if (tmpch == '^') tmpch = '3';
tmpfont = 'g';
}
else if (rch == '-')
{
if (tmpch == '|') tmpch = '4';
else if (tmpch == '.') tmpch = '7';
else if (tmpch == '^') tmpch = '1';
tmpfont = 'g';
}
else if (tmpch == '|')
{
tmpfont = 'g';
}
}
(*drawchar)(&tmpch, 1, tmpfont, token->offset[i]);
col++;
break;
default:
(*drawchar)(&token->text[i], 1, token->markup ? 'e' : curfont, token->offset[i]);
col++;
}
}
/* we won't need any more implied spaces between tokens until we hit
* the next whitespace token in fill mode.
*/
anyspc = False;
/* markup tokens aren't supposed to affect the logical column number */
#if 0
if (token->markup)
{
col = origcol;
}
#else
col = origcol + token->width;
#endif
/* Done! */
first = False;
return False;
}
/* This is a version of drawchar() which does nothing except count the column
* position. It is used by mark2col() to figure out which column the cursor
* is on, and move() to find the offset for a given column.
*/
static long physcol; /* physical column counter - initialize to 0 */
static long wantoffset; /* desired offset, whose column we'll be tracking */
static long outcol; /* physical column of wantoffset - initialize to -1 */
static long wantcol; /* desired column, whose offsets we'll be tracking */
static long outoffset; /* offset of char at a given column - init to -1 */
static long prevoffset; /* previous offset which was >= 0 */
static void countchar(p, qty, font, offset)
CHAR *p; /* the character being output */
long qty; /* quantity of characters */
_char_ font; /* the font to show it in (ignored) */
long offset; /* which buffer char this corresponds to */
{
long delta;
register CHAR ch;
/* a negative qty indicates that the same character repeats */
if (qty < 0)
{
delta = 0;
qty = -qty;
}
else
{
delta = 1;
}
/* for each character... */
for ( ; qty > 0; qty--, p += delta, offset += delta)
{
/* copy *p into a register variable */
ch = *p;
/* if this is the desired offset, remember its column */
if (offset >= wantoffset && outcol < 0)
outcol = physcol;
/* if this offset >= 0, then remember it */
if (offset >= 0)
prevoffset = offset;
/* process the character */
if (ch == '\n' || ch == '\f' || ch == '\013')
{
if (outoffset == -1)
{
outoffset = prevoffset;
}
physcol = 0;
}
else
{
if (physcol >= wantcol && outoffset < 0)
{
outoffset = prevoffset;
}
physcol++;
}
}
}
/*----------------------------------------------------------------------------*/
/* Elvis display mode functions */
/* end the mode, and free the modeinfo */
static void term(info)
DMINFO *info; /* window-specific info about mode */
{
MUINFO *mui = (MUINFO *)info;
if (mui->title)
safefree(mui->title);
if (mui->line)
safefree(mui->line);
safefree(info);
}
/* Convert a mark to a screen-relative column number */
static long mark2col(w, mark, cmd)
WINDOW w; /* window where buffer is shown */
MARK mark; /* mark to convert */
BOOLEAN cmd; /* if True, we're in command mode; else input mode */
{
CHAR *p; /* used for scanning */
TOKEN *token;
MARKBUF tmp;
int i;
/* Start scanning at the start of the line which contains this mark.
* Count characters until we find the mark's offset, or until the
* end of the line is reached.
*/
i = start(w, mark, NULL);
p = scanalloc(&p, marktmp(tmp, markbuffer(mark), mui->line[i].offset));
while ((token = (*mui->get)(&p)) != NULL && !put(token) && outcol < 0)
{
}
scanfree(&p);
return (outcol>=0 ? outcol : physcol);
}
/* Move vertically, and to a given column (or as close to column as possible) */
static MARK move(w, from, linedelta, column, cmd)
WINDOW w; /* window where buffer is shown */
MARK from; /* old location */
long linedelta; /* line movement */
long column; /* desired column number */
BOOLEAN cmd; /* if True, we're in command mode; else input mode */
{
int i, j;
CHAR *p;
TOKEN *token;
static MARKBUF tmp;
/* find the start of the line containing the mark */
i = start(w, from, NULL);
/* Add linedelta to the index, being careful not to move outside
* the array.
*/
i += linedelta;
if (i >= mui->nlines)
i = mui->nlines - 1;
if (i < 0)
i = 0;
/* Move to the given column, or as close as possible */
if (column == 0)
{
outoffset = mui->line[i].offset;
}
else if (column >= textwidth)
{
if (i < mui->nlines - 1)
outoffset = mui->line[i + 1].offset - 1;
else
outoffset = o_bufchars(markbuffer(from)) - 1;
}
else
{
j = start(w, marktmp(tmp, markbuffer(from), mui->line[i].offset), NULL);
assert(j == i);
wantcol = column;
scanalloc(&p, marktmp(tmp, markbuffer(from), mui->line[j].offset));
while ((token = (*mui->get)(&p)) != NULL && !put(token) && outoffset < 0)
{
}
scanfree(&p);
}
/* return the found mark */
if (outoffset < mui->line[i].offset)
outoffset = mui->line[i].offset;
return marktmp(tmp, markbuffer(from), outoffset);
}
/* Choose a line to appear at the top of the screen, and return its mark.
* Also, initialize the info for the next line.
*/
static MARK setup(top, cursor, bottom, info)
MARK top; /* where previous image started */
long cursor; /* offset of cursor */
MARK bottom; /* where previous image ended */
DMINFO *info; /* window-specific info about mode */
{
WINDOW win; /* window we're probably doing this for */
int topi, bottomi; /* line indicies of the top & bottom */
static MARKBUF tmp;
int i;
/* I kind of wish I has passed "win" as an argument. Oh, well,
* its easy enough to find the window.
*/
win = NULL;
do
{
win = winofbuf(win, markbuffer(top));
assert(win);
} while (win->mi != info);
/* we can optimize if "nolist noshowmarkup" */
dmhtml.canopt = dmman.canopt = (BOOLEAN)(!o_list(win) && !o_showmarkups);
/* find the line indicies of the top & bottom marks, and the cursor */
/* NOTE: This could have been implemented more efficiently! */
topi = start(win, top, NULL);
bottomi = start(win, bottom, NULL);
i = start(win, marktmp(tmp, markbuffer(top), cursor), NULL);
/* if the cursor is between top & bottom, great! Else we need to
* either scroll or jump.
*/
if (topi <= i && i < bottomi)
{
/* great! */
}
else if (topi - o_nearscroll <= i && i < topi)
{
topi = i;
}
else if (bottomi <= i && i < bottomi + o_nearscroll)
{
topi = i - o_lines(win) - 1;
/* NOTE: if sidescrolling is disabled and the window is narrow,
* then the above computation may set topi back farther than
* necessary. This is inefficient, but doesn't result in any
* visible problems because the Draw module will compensate by
* slop scrolling.
*/
}
else
{
topi = i - o_lines(win) / 2;
win->di->logic = DRAW_CENTER;
}
/* topi should never be negative */
if (topi < 0)
topi = 0;
/* return the top of the starting line */
return marktmp(tmp, markbuffer(top), mui->line[topi].offset);
}
static MARK image(w, line, info, draw)
WINDOW w; /* window where drawing will go */
MARK line; /* start of line to draw */
DMINFO *info; /* window-specific info about mode */
void (*draw)P_((CHAR *p, long qty, _char_ font, long offset));
/* function for drawing a single character */
{
CHAR *p;
TOKEN *token;
static MARKBUF tmp;
/* generate the line image, using the given "draw" function */
(void)start(w, line, draw);
scanalloc(&p, line);
for (token = (*mui->get)(&p); token && !put(token); token = (*mui->get)(&p))
{
}
scanfree(&p);
/* if we hit the end of the buffer, and didn't output a newline,
* then output a newline now.
*/
if (!token)
{
(*draw)(&newline, 1, 'n', anyspc ? spcoffset : -1);
}
/* return the offset of the first token of the next line */
if (token)
return marktmp(tmp, markbuffer(line), token->offset[0]);
else
return marktmp(tmp, markbuffer(line), o_bufchars(markbuffer(line)));
}
static void header(w, pagenum, info, draw)
WINDOW w; /* window from which we're printing */
int pagenum;/* page number */
DMINFO *info; /* drawing state */
void (*draw)P_((CHAR *p, long qty, _char_ font, long offset));
{
CHAR pg[20]; /* page number, as a text string */
CHAR *title; /* title of the document */
CHAR *sides; /* text to show on left & right sides of page */
CHAR *middle;/* text to show in middle of page */
int slen; /* length of side string */
int mlen; /* length of middle string */
long gap1; /* width of gap between left side & middle */
long gap2; /* width of gap between middle & right side */
assert(info == (DMINFO *)mui);
/* covert page number to text */
long2CHAR(pg, (long)pagenum);
/* find the title of the document */
title = mui->title ? mui->title
: o_bufname(markbuffer(w->cursor));
/* Find the widths of things */
/* figure out what goes where */
if (mui->escape == manescape)
{
sides = title;
middle = pg;
}
else
{
sides = pg;
middle = title;
}
/* find the lengths of things */
slen = CHARlen(sides);
mlen = CHARlen(middle);
if (slen + 2 + mlen + 2 + slen > textwidth)
{
mlen = textwidth - 4 - 2 * slen;
if (mlen <= 0)
{
mlen = CHARlen(middle);
slen = (textwidth - 4 - mlen) / 2;
}
}
gap1 = (textwidth - mlen) / 2 - slen;
gap2 = textwidth - mlen - 2 * slen - gap1;
/* Output the parts of the headings */
(*draw)(sides, slen, 'n', 0);
(*draw)(&space, -gap1, 'n', -1);
(*draw)(middle, mlen, 'n', 0);
(*draw)(&space, -gap2, 'n', -1);
(*draw)(sides, slen, 'n', 0);
/* End the header line, and then skip one or two more lines */
(*draw)(&newline, pagenum==1 ? -2L : -3L, 'n', -1);
reduce = True;
}
/*----------------------------------------------------------------------------*/
/* Maintenance functions */
/* store the offset and parse state in "dest"... or append it to mui->line
* if "dest" is a NULL pointer.
*/
static void storestate(offset, dest)
long offset;
LINEINFO *dest;
{
/* if "dest" is NULL, then we'll be appending */
if (!dest)
{
/* we may need to reallocate mui->line */
if (mui->nlines % GRANULARITY == 0)
{
dest = mui->line;
mui->line = safealloc((int)(mui->nlines + GRANULARITY), sizeof(LINEINFO));
if (dest)
{
memcpy(mui->line, dest, (size_t)mui->nlines * sizeof(LINEINFO));
safefree(dest);
}
}
/* locate the end of mui->line[], and increment mui->nlines */
dest = &mui->line[mui->nlines++];
}
/* store the info */
dest->offset = offset;
dest->state.prefmt = prefmt ? 1 : 0;
dest->state.graphic = graphic ? 1 : 0;
dest->state.midline = midline ? 1 : 0;
dest->state.reduce = reduce ? 1 : 0;
switch (deffont)
{
case 'b': dest->state.deffont = 1; break;
case 'u': dest->state.deffont = 2; break;
case 'i': dest->state.deffont = 3; break;
case 'f': dest->state.deffont = 4; break;
case 'e': dest->state.deffont = 5; break;
default: dest->state.deffont = 0;
}
switch (curfont)
{
case 'b': dest->state.curfont = 1; break;
case 'u': dest->state.curfont = 2; break;
case 'i': dest->state.curfont = 3; break;
case 'f': dest->state.curfont = 4; break;
case 'e': dest->state.curfont = 5; break;
default: dest->state.curfont = 0;
}
dest->state.indent = indent;
dest->state.nest = nest;
dest->state.listcnt = listcnt;
}
/* This function copies sets "mui" and the other parsing variables from the
* line information of a given window, for a given starting point. This
* function also sets "drawchar" function pointer to a given value; if no
* "drawchar" function is given, then it uses the internal dummy function
* and sets up that function's initial state.
*
* Returns the index into mui->line[] of the starting line
*/
static int start(win, from, draw)
WINDOW win; /* window to draw for */
MARK from; /* starting point */
void (*draw) P_((CHAR *p, long qty, _char_ font, long offset));
{
int i;
/* find the start of the line which contains "from" */
mui = (MUINFO *)win->mi;
for (i = 0; i < mui->nlines && mui->line[i].offset <= markoffset(from); i++)
{
}
if (i > 0) i--; /* the above loop took us one line too far */
/* copy that line's parsing state into parsing variables */
prefmt = (BOOLEAN)mui->line[i].state.prefmt;
graphic = (BOOLEAN)mui->line[i].state.graphic;
midline = (BOOLEAN)mui->line[i].state.midline;
reduce = (BOOLEAN)mui->line[i].state.reduce;
deffont = "nbuife"[mui->line[i].state.deffont];
curfont = "nbuife"[mui->line[i].state.curfont];
indent = mui->line[i].state.indent;
nest = mui->line[i].state.nest;
listcnt = mui->line[i].state.listcnt;
/* initialize other variables, too */
first = True;
anyspc = False;
title = False; /* nothing that causes a linebreak can appear in title */
list = o_list(win);
#if 1
textwidth = o_columns(win);
#else
textwidth = o_textwidth(markbuffer(win->cursor));
if (textwidth == 0)
textwidth = 80;
#endif
tabstop = o_tabstop(markbuffer(win->cursor));
listind = o_shiftwidth(markbuffer(win->cursor)) / 2;
if (listind < 2)
listind = 2;
col = 0;
cursoff = markoffset(win->cursor);
/* set up the drawchar function pointer */
if (draw)
{
drawchar = draw;
}
else
{
drawchar = countchar;
physcol = 0;
wantoffset = markoffset(from);
outcol = -1;
wantcol = 0;
outoffset = -1;
prevoffset = -1;
}
return i;
}
/* Locate the title in a given buffer, and store it in mui->title. Also
* sets mui->endtitle to an appropriate value. It is assumed that the mui
* pointer has been initialized before this function is called.
*/
static void findtitle(buf)
BUFFER buf; /* the buffer to scan */
{
CHAR *p; /* used for scanning the buffer */
TOKEN *token; /* a token from the buffer */
MARKBUF tmp;
int i;
/* free the old title, if any */
if (mui->title)
{
safefree(mui->title);
mui->title = NULL;
}
/* read the first non-whitespace token from the buffer */
scanalloc(&p, marktmp(tmp, buf, 0));
do
{
token = (*mui->get)(&p);
} while (token && isspace(token->text[0]));
/* if no token, then no title */
if (!token)
{
mui->endtitle = o_bufchars(buf);
scanfree(&p);
return;
}
/* if token doesn't explicitly allow title to follow, then no title */
if (!token->markup || token->markup->TITLE != 'Y')
{
mui->endtitle = token->offset[0];
scanfree(&p);
return;
}
/* Collect characters from text tokens, up to next markup which
* can't appear in the title.
*/
while ((token = (*mui->get)(&p)) != NULL && (!token->markup || token->markup->TITLE == 'Y'))
{
/* if text, then append it to title, with a leading blank */
if (!token->markup && !isspace(token->text[0]))
{
if (mui->title)
buildCHAR(&mui->title, ' ');
for (i = 0; i < token->nchars; i++)
buildCHAR(&mui->title, token->text[i]);
}
}
scanfree(&p);
}
/* Adjust the line tables of any window whose main buffer has been altered.
* This function is meant to be called only from bufreplace().
*/
void dmmuadjust(from, to, delta)
MARK from; /* old start of text */
MARK to; /* old end of text */
long delta; /* difference between old "to" and new "to" offsets */
{
WINDOW win; /* used for scanning windows */
CHAR *p; /* used for scanning text */
TOKEN *token; /* used for scanning text */
LINEINFO li; /* buffer for generating LINEINFO record */
LINEINFO *old; /* old value of mui->line (previous line array) */
#if 0
int noldlines;/* old value of mui->nlines */
#endif
MARKBUF tmp;
int i;
#ifdef DEBUG_MARKUP
fprintf(stderr, "dmmuadjust({%s,%ld}, {%s,%ld}, %ld)\n",
o_bufname(markbuffer(from)), markoffset(from),
o_bufname(markbuffer(to)), markoffset(to),
delta);
#endif
/* for each window that uses this buffer... */
for (win = winofbuf(NULL, markbuffer(from));
win;
win = winofbuf(win, markbuffer(from)))
{
/* ignore if not in a markup display mode */
if (win->md->setup != setup)
{
continue;
}
/* If necessary, rescan the title of this buffer */
mui = (MUINFO *)win->mi;
if (mui->endtitle <= markoffset(from))
findtitle(markbuffer(from));
/* Start on the line BEFORE the one where changes begin.
* This is because if we insert a small word at the front of
* a line, it might fit at the end of the preceding line.
*/
i = start(win, from, (void(*)P_((CHAR *,long,_char_,long)))0);
if (i > 0) i--;
/* Pretend the cursor is someplace harmless. This is done
* because the cursor position affects the appearance of the
* text, and sometimes this affects where line breaks occur.
*/
cursoff = o_bufchars(markbuffer(from));
/* regenerate that preceding line */
p = scanalloc(&p, marktmp(tmp, markbuffer(from), mui->line[i].offset));
do
{
token = (*mui->get)(&p);
} while (token && !put(token));
/* regenerate the line where the change took place */
if (token && token->offset[token->nchars - 1] != o_bufchars(markbuffer(from)) - 1)
{
/* but first, store results of preceding line's scan */
i++;
storestate(token->offset[0], &mui->line[i]);
/* okay, now scan the changed line. We already have
* the first token
*/
while (token && !put(token))
{
token = (*mui->get)(&p);
}
}
/* If we hit the end of the buffer, then no adjustments are
* necessary. This may leave superfluous data at the end of
* the array, but who cares?
*/
if (!token || token->offset[token->nchars - 1] == o_bufchars(markbuffer(from)) - 1)
{
mui->nlines = i + 1;
scanfree(&p);
#ifdef DEBUG_MARKUP
fprintf(stderr, "\tNo adjustment needed; hit end of buffer\n");
#endif
continue;
}
/* If the new line ended with the same (adjusted) offset and
* same state, then no adjustments are necessary except to
* the offsets.
*/
storestate(token->offset[0], &li);
if (i + 1 < mui->nlines
&& mui->line[i + 1].offset + delta == token->offset[0]
&& !memcmp(&li.state, &mui->line[i + 1].state, sizeof li.state)
&& mui->line[i + 1].offset >= markoffset(to))
{
if (delta == 0)
{
#ifdef DEBUG_MARKUP
fprintf(stderr, "\tNo adjustment needed; same state and delta=0\n");
#endif
scanfree(&p);
continue;
}
for (i++; i < mui->nlines; i++)
{
mui->line[i].offset += delta;
}
scanfree(&p);
#ifdef DEBUG_MARKUP
fprintf(stderr, "\tAdjusted the offsets only!\n");
#endif
continue;
}
/* Adjustments are going to be complex. Start a new array,
* but don't throw the old one out yet because it may save
* us some work.
*/
old = mui->line;
#if 0
noldlines = mui->nlines;
#endif
mui->line = safealloc((i + 1 + GRANULARITY) - ((i + 1) % GRANULARITY), sizeof(LINEINFO));
for (mui->nlines = 0; mui->nlines <= i; mui->nlines++)
{
mui->line[mui->nlines] = old[mui->nlines];
}
mui->line[mui->nlines++] = li;
/* regenerate lines until we have same (adjusted) offset and
* state as we had before the change.
*/
do
{
/* Regenerate a line */
put(token);
do
{
token = (*mui->get)(&p);
} while (token && !put(token));
if (!token)
{
break;
}
/* Append its state to the new array. */
storestate(token->offset[0], (LINEINFO *)0);
/* if the line's offset comes after the adjusted "to"
* offset, and there is an old state & adjusted offset
* which match, then just copy the old info.
*/
/*!!!*/
} while (token);
scanfree(&p);
safefree(old);
#ifdef DEBUG_MARKUP
fprintf(stderr, "\tRegenerated info for every following line\n");
#endif
}
}
/*----------------------------------------------------------------------------*/
DISPMODE dmhtml =
{
"html",
"WWW hypertext",
False, /* display generating can't be optimized */
False, /* shouldn't use standard wordwrap */
0, /* no window options */
NULL,
0, /* no global options */
NULL,
NULL,
htmlinit,
term,
mark2col,
move,
NULL, /* wordmove will be set to dmnormal.wordmove in init() */
setup,
image,
header,
NULL, /* no autoindent */
htmltagatcursor,
htmltagload,
htmltagnext
};
DISPMODE dmman =
{
"man",
"nroff -man",
False, /* display generating can't be optimized */
False, /* shouldn't use standard wordwrap */
0, /* no window options */
NULL,
0, /* no global options */
NULL,
NULL,
maninit,
term,
mark2col,
move,
NULL, /* wordmove will be set to dmnormal.wordmove in init() */
setup,
image,
header,
NULL, /* no autoindent */
NULL, /* tagatcursor will be set to dmnormal.tagatcursor in init() */
NULL, /* tagload will be set to dmnormal.tagload in init() */
NULL, /* tagnext will be set to dmnormal.tagnext in init() */
};
#endif /* DISPLAY_MARKUP */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.