/*****************************************************************************/ /* 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,"")) tptr++; if (tptr < tzptr) tptr += 8; while (tptr < tzptr && *tptr != '\n') tptr++; if (tptr < tzptr) tptr++; if (tptr < tzptr) { ch = *tptr; *tptr = '\0'; if (dbug>1) dbugThis (FI_LI, "prologue %s", dbugMax(cptr)); wasDocAsIs (docptr, cptr); *tptr = ch; } /* unless an explicit style */ docptr->drawStyle = 0; } } if (tptr >= tzptr) return (EINVAL); if (dbug>1) dbugThis (FI_LI, "diagram %s", dbugMax(tptr)); cptr = drawDiagram (tptr, tzptr - tptr); wasDocAsIs (docptr, cptr); free (cptr); tzptr += 7; TPUT (docptr, tzptr); return (0); } /*****************************************************************************/ /* Draw a diagram that is contained in an external file. */ void drawAdHoc (char *fname) { int alen; char *aptr, *cptr, *sptr; cptr = drawReadFile (fname); if (MATCH4(cptr,"\0\0\0\0") && isalpha(cptr[4])) { CgiLibResponseHeader (404, "text/plain"); fputs (cptr+4, stdout); fputc ('\n', stdout); free (cptr); return; } if (!MATCH6 (cptr, "|draw|")) { CgiLibResponseHeader (404, "text/plain"); fputs ("cannot draw from this file\n", stdout); free (cptr); return; } aptr = ""; alen = 0; for (cptr += 6; *cptr && isspace(*cptr) && *cptr != '\n'; cptr++); if (*cptr == '\n') cptr++; if (*cptr) { if (MATCH6(cptr,"")) 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 /*****************************************************************************/