diff --git a/AUTHORS b/AUTHORS index f21717a..3330634 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,4 +11,6 @@ dopsi alx741 mnalt +guobin2312 +lukebond diff --git a/include/main.h b/include/main.h index 047d2fc..a3f6c77 100644 --- a/include/main.h +++ b/include/main.h @@ -25,6 +25,6 @@ #define MDP_VER_MAJOR 1 #define MDP_VER_MINOR 0 -#define MDP_VER_REVISION 6 +#define MDP_VER_REVISION 7 #endif // !defined( MAIN_H ) diff --git a/include/parser.h b/include/parser.h index e8ad380..db95f65 100644 --- a/include/parser.h +++ b/include/parser.h @@ -49,9 +49,10 @@ #define CODE_INDENT 4 #define UNORDERED_LIST_MAX_LEVEL 3 -deck_t *markdown_load(FILE *input); +deck_t *markdown_load(FILE *input, int noexpand); int markdown_analyse(cstring_t *text, int prev); void markdown_debug(deck_t *deck, int debug); +void expand_character_entities(line_t *line); void adjust_line_length(line_t *line); int next_nonblank(cstring_t *text, int i); int prev_blank(cstring_t *text, int i); diff --git a/include/viewer.h b/include/viewer.h index 94578b3..c980f1d 100644 --- a/include/viewer.h +++ b/include/viewer.h @@ -62,5 +62,6 @@ void fade_in(WINDOW *window, int trans, int colors, int invert); int int_length (int val); int get_slide_number(char init); +void setup_list_strings(void); #endif // !defined( VIEWER_H ) diff --git a/mdp.1 b/mdp.1 index 6179d61..01523af 100644 --- a/mdp.1 +++ b/mdp.1 @@ -5,7 +5,7 @@ .\" . . -.TH MDP 1 "2016-02-07" "User Commands" +.TH MDP 1 "2016-04-02" "User Commands" .SH NAME mdp \- A command-line based markdown presentation tool @@ -33,6 +33,9 @@ the presentation is read from standard input. .SS "Output Control" .TP +.BR \-e ", " \-\^\-expand +Enable character entity expansion (e.g. '>' becomes '>'). +.TP .BR \-f ", " \-\^\-nofade Disable color fading in 256 color mode. .TP @@ -52,6 +55,26 @@ .TP .BR \-v ", " \-\^\-version Display version and license information. +. +.SH ENVIRONMENT VARIABLES +.SS "Output Control" +.TP +.BR MDP_LIST_HEAD[1-3],\ MDP_LIST_OPEN[1-3] +Controls the list characters of unordered lists. + +The default is equivalent to: +.br +MDP_LIST_OPEN1=' | ' +.br +MDP_LIST_OPEN2=' | ' +.br +MDP_LIST_OPEN3=' | ' +.br +MDP_LIST_HEAD1=' +- ' +.br +MDP_LIST_HEAD2=' +- ' +.br +MDP_LIST_HEAD3=' +- ' . .SH MARKDOWN FORMATTING For a complete list of supported markups, refer the sample presentation diff --git a/src/main.c b/src/main.c index 3165a00..5aefd75 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ fprintf(stderr, "%s", "A command-line based markdown presentation tool.\n\n"); fprintf(stderr, "%s", " -d, --debug enable debug messages on STDERR\n"); fprintf(stderr, "%s", " add it multiple times to increases debug level\n"); + fprintf(stderr, "%s", " -e, --expand enable character entity expansion\n"); fprintf(stderr, "%s", " -f, --nofade disable color fading in 256 color mode\n"); fprintf(stderr, "%s", " -h, --help display this help and exit\n"); fprintf(stderr, "%s", " -i, --invert swap black and white color\n"); @@ -56,18 +57,20 @@ int notrans = 0; // disable transparency int nofade = 0; // disable fading int invert = 0; // invert color (black on white) + int noexpand = 1; // disable character entity expansion int reload = 0; // reload page N (0 means no reload) int noreload = 1; // reload disabled until we know input is a file int slidenum = 2; // 0:don't show; 1:show #; 2:show #/# // define command-line options struct option longopts[] = { - { "debug", no_argument, 0, 'd' }, - { "nofade", no_argument, 0, 'f' }, - { "help", no_argument, 0, 'h' }, - { "invert", no_argument, 0, 'i' }, - { "notrans", no_argument, 0, 't' }, - { "version", no_argument, 0, 'v' }, + { "debug", no_argument, 0, 'd' }, + { "expand", no_argument, 0, 'e' }, + { "nofade", no_argument, 0, 'f' }, + { "help", no_argument, 0, 'h' }, + { "invert", no_argument, 0, 'i' }, + { "notrans", no_argument, 0, 't' }, + { "version", no_argument, 0, 'v' }, { "noslidenum", no_argument, 0, 's' }, { "noslidemax", no_argument, 0, 'x' }, { 0, 0, 0, 0 } @@ -75,14 +78,15 @@ // parse command-line options int opt, debug = 0; - while ((opt = getopt_long(argc, argv, ":dfhitvsx", longopts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, ":defhitvsx", longopts, NULL)) != -1) { switch(opt) { - case 'd': debug += 1; break; - case 'f': nofade = 1; break; - case 'h': usage(); break; - case 'i': invert = 1; break; - case 't': notrans = 1; break; - case 'v': version(); break; + case 'd': debug += 1; break; + case 'e': noexpand = 0; break; + case 'f': nofade = 1; break; + case 'h': usage(); break; + case 'i': invert = 1; break; + case 't': notrans = 1; break; + case 'v': version(); break; case 's': slidenum = 0; break; case 'x': slidenum = 1; break; case ':': fprintf(stderr, "%s: '%c' requires an argument\n", argv[0], optopt); usage(); break; @@ -94,6 +98,9 @@ // set locale to that of the environment, so that ncurses properly renders // UTF-8 characters if the system supports it setlocale(LC_CTYPE, ""); + + // setup list string + setup_list_strings(); // open file or set input to STDIN char *file = NULL; @@ -137,7 +144,7 @@ // load deck object from input deck_t *deck; - deck = markdown_load(input); + deck = markdown_load(input, noexpand); // close file fclose(input); diff --git a/src/parser.c b/src/parser.c index eb50016..465bdc1 100644 --- a/src/parser.c +++ b/src/parser.c @@ -31,7 +31,76 @@ #include "parser.h" -deck_t *markdown_load(FILE *input) { +// char entry translation table +static struct named_character_entity { + wchar_t ucs; + const wchar_t *name; +} named_character_entities[] = { + { L'\x0022', L"quot" }, + { L'\x0026', L"amp" }, + { L'\x0027', L"apos" }, + { L'\x003C', L"lt" }, + { L'\x003E', L"gt" }, + { L'\x00A2', L"cent" }, + { L'\x00A3', L"pound" }, + { L'\x00A5', L"yen" }, + { L'\x00A7', L"sect" }, + { L'\x00A9', L"copy" }, + { L'\x00AA', L"laquo" }, + { L'\x00AE', L"reg" }, + { L'\x00B0', L"deg" }, + { L'\x00B1', L"plusmn" }, + { L'\x00B2', L"sup2" }, + { L'\x00B3', L"sup3" }, + { L'\x00B6', L"para" }, + { L'\x00B9', L"sup1" }, + { L'\x00BB', L"raquo" }, + { L'\x00BC', L"frac14" }, + { L'\x00BD', L"frac12" }, + { L'\x00BE', L"frac34" }, + { L'\x00D7', L"times" }, + { L'\x00F7', L"divide" }, + { L'\x2018', L"lsquo" }, + { L'\x2019', L"rsquo" }, + { L'\x201C', L"ldquo" }, + { L'\x201D', L"rdquo" }, + { L'\x2020', L"dagger" }, + { L'\x2021', L"Dagger" }, + { L'\x2022', L"bull" }, + { L'\x2026', L"hellip" }, + { L'\x2030', L"permil" }, + { L'\x2032', L"prime" }, + { L'\x2033', L"Prime" }, + { L'\x2039', L"lsaquo" }, + { L'\x203A', L"rsaquo" }, + { L'\x20AC', L"euro" }, + { L'\x2122', L"trade" }, + { L'\x2190', L"larr" }, + { L'\x2191', L"uarr" }, + { L'\x2192', L"rarr" }, + { L'\x2193', L"darr" }, + { L'\x2194', L"harr" }, + { L'\x21B5', L"crarr" }, + { L'\x21D0', L"lArr" }, + { L'\x21D1', L"uArr" }, + { L'\x21D2', L"rArr" }, + { L'\x21D3', L"dArr" }, + { L'\x21D4', L"hArr" }, + { L'\x221E', L"infin" }, + { L'\x2261', L"equiv" }, + { L'\x2308', L"lceil" }, + { L'\x2309', L"rceil" }, + { L'\x230A', L"lfloor" }, + { L'\x230B', L"rfloor" }, + { L'\x25CA', L"loz" }, + { L'\x2660', L"spades" }, + { L'\x2663', L"clubs" }, + { L'\x2665', L"hearts" }, + { L'\x2666', L"diams" }, + { L'\0', NULL }, +}; + +deck_t *markdown_load(FILE *input, int noexpand) { wchar_t c = L'\0'; // char int i = 0; // increment @@ -120,6 +189,12 @@ // calc offset line->offset = next_nonblank(text, 0); + + // expand character entities if enabled + if(line->text->value && + !noexpand && + !CHECK_BIT(line->bits, IS_CODE)) + expand_character_entities(line); // adjust line length dynamicaly - excluding markup if(line->text->value) @@ -603,6 +678,83 @@ } } +void expand_character_entities(line_t *line) +{ + wchar_t *ampersand; + wchar_t *prev, *curr; + + ampersand = NULL; + curr = &line->text->value[0]; + + // for each char in line + for(prev = NULL; *curr; prev = curr++) { + if (*curr == L'&' && (prev == NULL || *prev != L'\\')) { + ampersand = curr; + continue; + } + if (ampersand == NULL) { + continue; + } + if (*curr == L'#') { + if (prev == ampersand) + continue; + ampersand = NULL; + continue; + } + if (iswalpha(*curr) || iswxdigit(*curr)) { + continue; + } + if (*curr == L';') { + int cnt; + wchar_t ucs = L'\0'; + if (ampersand + 1 >= curr || ampersand + 16 < curr) { // what is a good limit? + ampersand = NULL; + continue; + } + if (ampersand[1] == L'#') { // &#nnnn; or &#xhhhh; + if (ampersand + 2 >= curr) { + ampersand = NULL; + continue; + } + if (ampersand[2] != L'x') { // &#nnnn; + cnt = wcsspn(&ersand[2], L"0123456789"); + if (ampersand + 2 + cnt != curr) { + ampersand = NULL; + continue; + } + ucs = wcstoul(&ersand[2], NULL, 10); + } else { // &#xhhhh; + if (ampersand + 3 >= curr) { + ampersand = NULL; + continue; + } + cnt = wcsspn(&ersand[3], L"0123456789abcdefABCDEF"); + if (ampersand + 3 + cnt != curr) { + ampersand = NULL; + continue; + } + ucs = wcstoul(&ersand[3], NULL, 16); + } + } else { // &name; + for (cnt = 0; cnt < sizeof(named_character_entities)/sizeof(named_character_entities[0]); ++cnt) { + if (wcsncmp(named_character_entities[cnt].name, &ersand[1], curr - ampersand - 1)) + continue; + ucs = named_character_entities[cnt].ucs; + break; + } + if (ucs == L'\0') { + ampersand = NULL; + continue; + } + } + *ampersand = ucs; + cstring_strip(line->text, ampersand + 1 - &line->text->value[0], curr - ampersand); + curr = ampersand; + ampersand = NULL; + } + } +} + void adjust_line_length(line_t *line) { int l = 0; const static wchar_t *special = L"\\*_`"; // list of interpreted chars diff --git a/src/viewer.c b/src/viewer.c index 925efb1..cbb1498 100644 --- a/src/viewer.c +++ b/src/viewer.c @@ -26,6 +26,7 @@ #include // iswalnum #include // strcpy #include // usleep +#include // getenv #include "viewer.h" // color ramp for fading from black to color @@ -60,20 +61,33 @@ 206, 207, 201, 200, 199, 199, 198, 198, 197, 197, 196, 196}; +// unordered list characters +// +// override via env vars: +// export MDP_LIST_OPEN1=" " MDP_LIST_OPEN2=" " MDP_LIST_OPEN3=" " +// export MDP_LIST_HEAD1=" ■ " MDP_LIST_HEAD2=" ● " MDP_LIST_HEAD3=" ▫ " +static const char *list_open1 = " | "; +static const char *list_open2 = " | "; +static const char *list_open3 = " | "; +static const char *list_head1 = " +- "; +static const char *list_head2 = " +- "; +static const char *list_head3 = " +- "; + int ncurses_display(deck_t *deck, int notrans, int nofade, int invert, int reload, int noreload, int slidenum) { - int c = 0; // char - int i = 0; // iterate - int l = 0; // line number - int lc = 0; // line count - int sc = 1; // slide count - int colors = 0; // amount of colors supported - int fade = 0; // disable color fading by default - int trans = -1; // enable transparency if term supports it - int max_lines = 0; // max lines per slide - int max_cols = 0; // max columns per line - int offset; // text offset - int stop = 0; // passed stop bits per slide + int c = 0; // char + int i = 0; // iterate + int l = 0; // line number + int lc = 0; // line count + int sc = 1; // slide count + int colors = 0; // amount of colors supported + int fade = 0; // disable color fading by default + int trans = -1; // enable transparency if term supports it + int max_lines = 0; // max lines per slide + int max_lines_slide = -1; // the slide that has the most lines + int max_cols = 0; // max columns per line + int offset; // text offset + int stop = 0; // passed stop bits per slide // header line 1 is displayed at the top int bar_top = (deck->headers > 0) ? 1 : 0; @@ -140,8 +154,12 @@ } max_lines = MAX(lc, max_lines); + if (lc == max_lines) { + max_lines_slide = sc; + } slide = slide->next; + ++sc; } // not enough lines @@ -151,7 +169,7 @@ endwin(); // print error - fwprintf(stderr, L"Error: Terminal height (%i lines) too small. Need at least %i lines.\n", LINES, max_lines + bar_top + bar_bottom); + fwprintf(stderr, L"Error: Terminal height (%i lines) too small. Need at least %i lines for slide #%i.\n", LINES, max_lines + bar_top + bar_bottom, max_lines_slide); fwprintf(stderr, L"You may need to add additional horizontal rules (---) to split your file in shorter slides.\n"); // no reload @@ -243,6 +261,7 @@ slide = deck->slide; // find slide to reload + sc = 0; while(reload > 1 && reload <= deck->slides) { slide = slide->next; sc++; @@ -333,8 +352,9 @@ } // print pandoc URL references - // only if we already printed all lines of the current slide - if(!line) { + // only if we already printed all lines of the current slide (or output is stopped) + if(!line || + stop > slide->stop) { int i, ymax; getmaxyx( content, ymax, i ); for (i = 0; i < url_get_amount(); i++) { @@ -516,6 +536,33 @@ return reload; } +void setup_list_strings(void) +{ + const char *str; + + /* utf8 can require 6 bytes */ + if ((str = getenv("MDP_LIST_OPEN")) != NULL && strlen(str) <= 4*6) { + list_open1 = list_open2 = list_open3 = str; + } else { + if ((str = getenv("MDP_LIST_OPEN1")) != NULL && strlen(str) <= 4*6) + list_open1 = str; + if ((str = getenv("MDP_LIST_OPEN2")) != NULL && strlen(str) <= 4*6) + list_open2 = str; + if ((str = getenv("MDP_LIST_OPEN3")) != NULL && strlen(str) <= 4*6) + list_open3 = str; + } + if ((str = getenv("MDP_LIST_HEAD")) != NULL && strlen(str) <= 4*6) { + list_head1 = list_head2 = list_head3 = str; + } else { + if ((str = getenv("MDP_LIST_HEAD1")) != NULL && strlen(str) <= 4*6) + list_head1 = str; + if ((str = getenv("MDP_LIST_HEAD2")) != NULL && strlen(str) <= 4*6) + list_head2 = str; + if ((str = getenv("MDP_LIST_HEAD3")) != NULL && strlen(str) <= 4*6) + list_head3 = str; + } +} + void add_line(WINDOW *window, int y, int x, line_t *line, int max_cols, int colors) { int i; // increment @@ -541,14 +588,20 @@ // IS_UNORDERED_LIST_3 if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_3)) { offset = next_nonblank(line->text, 0); - char prompt[13]; - strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); - strcpy(&prompt[4], CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? " | " : " "); + char prompt[13 * 6]; + int pos = 0, len, cnt; + len = sizeof(prompt) - pos; + cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); + pos += (cnt > len - 1 ? len - 1 : cnt); + len = sizeof(prompt) - pos; + cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)? list_open2 : " "); + pos += (cnt > len - 1 ? len - 1 : cnt); + len = sizeof(prompt) - pos; if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { - strcpy(&prompt[8], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? " | " : " "); + snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_3)? list_open3 : " "); } else { - strcpy(&prompt[8], " +- "); + snprintf(&prompt[pos], len, "%s", list_head3); offset += 2; } @@ -561,13 +614,17 @@ // IS_UNORDERED_LIST_2 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_2)) { offset = next_nonblank(line->text, 0); - char prompt[9]; - strcpy(&prompt[0], CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? " | " : " "); + char prompt[9 * 6]; + int pos = 0, len, cnt; + len = sizeof(prompt) - pos; + cnt = snprintf(&prompt[pos], len, "%s", CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); + pos += (cnt > len - 1 ? len - 1 : cnt); + len = sizeof(prompt) - pos; if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { - strcpy(&prompt[4], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? " | " : " "); + snprintf(&prompt[pos], len, "%s", line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_2)? list_open2 : " "); } else { - strcpy(&prompt[4], " +- "); + snprintf(&prompt[pos], len, "%s", list_head2); offset += 2; } @@ -580,12 +637,12 @@ // IS_UNORDERED_LIST_1 } else if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_1)) { offset = next_nonblank(line->text, 0); - char prompt[5]; + char prompt[5 * 6]; if(CHECK_BIT(line->bits, IS_UNORDERED_LIST_EXT)) { - strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? " | " : " "); + strcpy(&prompt[0], line->next && CHECK_BIT(line->next->bits, IS_UNORDERED_LIST_1)? list_open1 : " "); } else { - strcpy(&prompt[0], " +- "); + strcpy(&prompt[0], list_head1); offset += 2; }