/*****************************************************************************/
/*
draw.c
Box diagram drawing using a Q&D conversion from ASCII to HTML box characters.
https://en.wikipedia.org/wiki/Box_Drawing_(Unicode_block)
Given a box-like line drawing such as the test one below...
+---------+ +---------+
| |<-- THIS one --->| |
| ONE one | | two TWO +---+
| |<-- this TWO --->| | |
+--+------+ +---------+ |
| ^ ^ |
| | | |
| +-------------------------|--------+
| |
+-----------------------------+
the code scans the characters, taking into consideration adjacent characters in
all directions, and generates equivalent Unicode HTML entities. Reserved (to
this module) characters must be escaped using '\'. The usual '|' wasDOC
behaviour does not apply within |draw|..|!draw|.
Drawing reserved characters; | - + < > ^ # : . * { } \
Immediately following the |draw| directive custom styling may be included.
|draw|
...diagram...
|!draw|
This URL allows ad hoc development outside of a full document. Must begin with
the string "|draw|". Otherwise follows the same rules as for in-document
drawings.
https://the.server.name/cgi-bin/wasdoc/path/to/file.txt?draw
VERSION HISTORY
---------------
24-JAN-2020 MGD initial
*/
/*****************************************************************************/
#include "wasdoc.h"
#include "cgilib.h"
/* https://unicode-search.net/ */
/* http://www.alanwood.net/unicode/index.html */
#define ARROW_DOWN "▲"
#define ARROW_LEFT "◄"
#define ARROW_RIGHT "◄"
#define ARROW_UP "▲"
#define CIRCLE "●"
#define DOWNLEFT "┐"
#define DOWNRIGHT "┌"
#define HORIZONTAL "─"
#define HORIZDASH "╌"
#define HORIZDOWN "┬"
#define HORIZUP "┴"
#define HORIZVERT "┼"
#define UPLEFT "┘"
#define UPRIGHT "└"
#define VERTICAL "│"
#define VERTDASH "╎"
#define VERTLEFT "┤"
#define VERTRIGHT "├"
#define HUH "⸮"
extern int dbug, isCgi, isCgiPlus;
extern char CopyrightDate [],
DefaultType [],
SoftwareID [],
Utility [];
#define ISRES(c) ((c == '-') || (c == '|') || (c == '+') || (c == '<') || \
(c == '>') || (c == '^') || (c == '#') || (c == ':') || \
(c == '.') || (c == '*') || (c == '\\') || \
(c == '{') || (c == '}'))
#define NOTRES(c) (!ISRES(c))
/*****************************************************************************/
/*
Draw the diagram and insert it into the document.
*/
int drawThis (struct wasdoc_st *docptr)
{
char ch;
char *cptr, *tptr, *tzptr;
TGET (docptr, tptr);
if (*tptr == '|') tptr++;
if (*tptr == '\n') tptr++;
if (dbug>1) dbugThis (FI_LI, "drawThis() %s", dbugMax(tptr));
for (cptr = tptr; *cptr; cptr++)
{
if (*cptr != '|') continue;
if (MATCH7 (cptr, "|!draw|")) break;
/* if suspiciously like a wasDOC heading */
if (isdigit(cptr[1]) && isalpha(cptr[2])) break;
}
if (!MATCH7 (cptr, "|!draw|")) return (EINVAL);
tzptr = cptr;
wasDocAsIs (docptr, "\n");
if (!docptr->drawStyle)
{
/* should only need this the once */
wasDocAsIs (docptr, drawStyle());
docptr->drawStyle = 1;
}
if (tptr < tzptr)
{
if (MATCH6(tptr,"")) cptr++;
if (*cptr) cptr += 8;
while (*cptr && *cptr != '\n') cptr++;
if (*cptr) cptr++;
alen = cptr - aptr;
}
}
for (sptr = cptr; *sptr && !MATCH7(sptr,"|!draw|"); sptr++);
*sptr = '\0';
sptr = drawDiagram (cptr, sptr - cptr);
CgiLibResponseHeader (200, "text/html");
fprintf (stdout, "%s%*.*s
%s
\n",
drawStyle(), alen, alen, aptr, sptr);
free (sptr);
}
/*****************************************************************************/
/*
Return a pointer to the HTML-ised diagram.
*/
char* drawDiagram (char* diagram, int dlength)
{
int hsize,
rescount;
char cabove, cbelow, cleft, cright;
char *cptr, *dptr, *dzptr, *sptr, *zptr,
*hiagram;
if (dlength < 0) dlength = strlen(diagram);
dzptr = (dptr = diagram) + dlength;
rescount = 0;
for (cptr = dptr; *cptr; cptr++)
if (ISRES(*cptr) || isspace(*cptr)) rescount++;
hiagram = calloc (1, hsize = (dlength - rescount + (rescount * 8)));
if (!hiagram) exit (vaxc$errno);
zptr = (sptr = hiagram) + hsize;
while (dptr < dzptr)
{
while (dptr < dzptr && NOTRES(*dptr) && !isspace(*dptr))
*sptr++ = *dptr++;
if (dptr >= dzptr) break;
if (*dptr == '\n')
{
dptr++;
cptr = "
\n";
}
else
if (*dptr == '{')
{
/* escaped sequence */
dptr++;
while (dptr < dzptr && *dptr != '}' && sptr < zptr)
{
if (*dptr == '\\' && *(dptr+1) == '}') dptr++;
*sptr++ = *dptr++;
}
if (dptr < dzptr) dptr++;
}
else
if (isspace(*dptr))
{
dptr++;
cptr = " ";
}
else
if (*dptr == '\\')
{
dptr++;
if (*dptr && sptr < zptr) *sptr++ = *dptr++;
cptr = NULL;
}
else
if (*dptr == '-')
{
dptr++;
cptr = HORIZONTAL;
}
else
if (*dptr == '|')
{
dptr++;
cptr = VERTICAL;
}
else
if (*dptr == ':')
{
dptr++;
cptr = VERTDASH;
}
else
if (*dptr == '.')
{
dptr++;
cptr = HORIZDASH;
}
else
if (*dptr == '<')
{
dptr++;
cptr = ARROW_LEFT;
}
else
if (*dptr == '>')
{
dptr++;
cptr = ARROW_RIGHT;
}
else
if (*dptr == '^')
{
dptr++;
cptr = ARROW_UP;
}
else
if (*dptr == '#')
{
dptr++;
cptr = ARROW_DOWN;
}
else
if (*dptr == '*')
{
dptr++;
cptr = CIRCLE;
}
else
if (*dptr == '+')
{
/* now it gets interesting */
cabove = cbelow = cleft = cright = 0;
if (dptr > diagram) cleft = *(dptr-1);
if (dptr < dzptr-1) cright = *(dptr+1);
cabove = drawCharAbove (diagram, dzptr, dptr);
cbelow = drawCharBelow (diagram, dzptr, dptr);
if (cleft == '-' && cright == '-')
{
if ((cabove == '|' || cabove == ':' || cabove == '+') &&
(cbelow == '|' || cbelow == ':' || cbelow == '+'))
cptr = HORIZVERT;
else
if (cabove == '|' || cabove == ':' || cabove == '+')
cptr = HORIZUP;
else
if (cbelow == '|' || cbelow == ':' || cbelow == '+')
cptr = HORIZDOWN;
else
cptr = HUH;
}
else
if (cleft == '-' || cleft == '.')
{
if ((cabove == '|' || cabove == ':' || cabove == '+') &&
(cbelow == '|' || cbelow == ':' || cbelow == '+'))
cptr = VERTLEFT;
else
if (cabove == '|' || cabove == ':' || cabove == '+')
cptr = UPLEFT;
else
if (cbelow == '|' || cbelow == ':' || cbelow == '+')
cptr = DOWNLEFT;
else
cptr = HUH;
}
else
if (cright == '-' || cright == '.')
{
if ((cabove == '|' || cabove == ':' || cabove == '+') &&
(cbelow == '|' || cbelow == ':' || cbelow == '+'))
cptr = VERTRIGHT;
else
if (cabove == '|' || cabove == ':' || cabove == '+')
cptr = UPRIGHT;
else
if (cbelow == '|' || cbelow == ':' || cbelow == '+')
cptr = DOWNRIGHT;
else
cptr = HUH;
}
else
cptr = HUH;
dptr++;
}
else
{
if (sptr < zptr) *sptr++ = *dptr;
dptr++;
}
if (cptr) while (*cptr && sptr < zptr) *sptr++ = *cptr++;
}
return (hiagram);
}
/*****************************************************************************/
/*
Return the character in the line above. Go back to the start of the previous
line. Then count from there to the same location as the supplied character
pointer. Return that character, or 0 if none above.
*/
char drawCharAbove
(
char *diagram,
char *dzptr,
char *here
)
{
char *cptr, *hptr;
/* back to the end of the previous line */
for (cptr = here; cptr > diagram && *cptr != '\n'; cptr--);
if (cptr <= diagram) return (0);
cptr--;
/* then to the beginning of that previous line */
while (cptr > diagram && *cptr != '\n') cptr--;
if (cptr > diagram) cptr++;
/* go to the beginning of the current line */
for (hptr = here; hptr > diagram && *hptr != '\n'; hptr--);
if (hptr > diagram) hptr++;
/* then move down the number of chars to |here| */
while (hptr < here)
{
if (*hptr == '{')
{
/* escaped sequence */
while (hptr < dzptr && *hptr != '}' && *hptr != '\n')
{
if (*hptr == '\\' && *(hptr+1) == '}') hptr++;
hptr++;
}
if (hptr < dzptr && *hptr == '}') hptr++;
continue;
}
if (*cptr == '{')
{
/* escaped sequence */
while (cptr < dzptr && *cptr != '}' && *cptr != '\n')
{
if (*cptr == '\\' && *(cptr+1) == '}') cptr++;
cptr++;
}
if (cptr < dzptr && *cptr == '}') cptr++;
continue;
}
if (*hptr == '\\')
{
/* escaped character */
hptr++;
if (hptr < dzptr) hptr++;
}
else
hptr++;
if (*cptr == '\\')
{
/* escaped character */
cptr += 2;
}
else
cptr++;
/* if previous line has fewer characters then none below */
if (*cptr == '\n') return (0);
}
if (*cptr == '\n') return (0);
return (*cptr);
}
/*****************************************************************************/
/*
Return the character in the line below. Go to the start of the next line.
Then count from there to the same location as the supplied character pointer.
Return that character, or 0 if none below.
*/
char drawCharBelow
(
char *diagram,
char *dzptr,
char *here
)
{
char *cptr, *hptr;
/* to the beginning of the next line */
for (cptr = here; cptr < dzptr && *cptr != '\n'; cptr++);
if (cptr < dzptr) cptr++;
if (cptr >= dzptr) return (0);
/* go to the beginning of the current line */
for (hptr = here; hptr > diagram && *hptr != '\n'; hptr--);
if (hptr > diagram) hptr++;
/* then move down the number of chars to |here| */
while (hptr < here)
{
if (*hptr == '{')
{
/* escaped sequence */
while (hptr < dzptr && *hptr != '}' && *hptr != '\n')
{
if (*hptr == '\\' && *(hptr+1) == '}') hptr++;
hptr++;
}
if (hptr < dzptr && *hptr == '}') hptr++;
continue;
}
if (*cptr == '{')
{
/* escaped sequence */
while (cptr < dzptr && *cptr != '}' && *cptr != '\n')
{
if (*cptr == '\\' && *(cptr+1) == '}') cptr++;
cptr++;
}
if (cptr < dzptr && *cptr == '}') cptr++;
continue;
}
if (*hptr == '\\')
{
/* escaped character */
hptr++;
if (hptr < dzptr) hptr++;
}
else
hptr++;
if (*cptr == '\\')
{
/* escaped character */
cptr++;
if (cptr < dzptr) cptr++;
}
else
cptr++;
/* if next line has fewer characters then none below */
if (cptr >= dzptr || *cptr == '\n') return (0);
}
if (*cptr == '\n') return (0);
return (*cptr);
}
/*****************************************************************************/
/*
Styling for drawing. The .dnoflip seems necessary to retain comparitive sizes
on some platforms. A document can change font (and everything else) by
prologue "" (though these seem to work well
across many platforms.
*/
char* drawStyle ()
{
static char styling[] = "\
\n";
return (styling);
}
/*****************************************************************************/
/*
Read a file external to a WASDOC proper.
Return a string which must bee free()ed.
Error returned as string with two leading and two trailing null characters.
*/
char* drawReadFile (char *fname)
{
int bytes, errnum;
char *aptr, *cptr, *sptr;
FILE *ifptr;
stat_t fstatbuf;
if (ifptr = fopen (fname, "r"))
fstat (fileno(ifptr), &fstatbuf);
if (errnum = vaxc$errno)
{
bytes = strlen (cptr = strerror(errnum));
if (!(aptr = calloc (1, bytes+16))) exit (vaxc$errno);
sprintf (aptr, "%c%c%c%c%s", 0, 0, 0, 0, cptr);
return (aptr);
}
bytes = fstatbuf.st_size + 32;
aptr = sptr = calloc (1, bytes);
if (!aptr) exit (vaxc$errno);
/* read the entire file into memory */
while (fgets (sptr, bytes-(sptr-aptr), ifptr))
while (*sptr) sptr++;
fclose (ifptr);
return (aptr);
}
/*****************************************************************************/
/*
*/
#if 0
char* drawDebugLine (char *line)
{
static char buf [256];
char *cptr, *sptr;
sptr = buf;
for (cptr = line; *cptr && *cptr != '\n'; *sptr++ = *cptr++);
*sptr = '\0';
return (buf);
}
#endif
/*****************************************************************************/