35#define MD_MAX_BLOCKS 1024u
36#define MD_MAX_SPANS 2048u
37#define MD_MAX_ITEMS 1024u
38#define MD_MAX_DEPTH 16
39#define MD_MAX_COLS 32u
40#define MD_MAX_HEADING 6
45 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\f' || c ==
'\v';
49 return c >=
'0' && c <=
'9';
54 char* out = malloc(n + 1);
71 size_t nc = t->
cap ? t->
cap * 2 : 32;
72 char* nb = realloc(t->
b, nc);
121 static const char* h =
"http";
124 if (i >= n || (s[i] | 0x20) != h[i])
126 if (i < n && (s[i] | 0x20) ==
's')
128 return i + 3 <= n && s[i] ==
':' && s[i + 1] ==
'/' && s[i + 2] ==
'/';
138 for (j = i + 1; j < n && s[j] !=
'`'; j++);
139 if (j >= n || j == i + 1)
150 size_t close, url0, k;
153 for (close = i + 1; close < n && s[close] !=
']'; close++);
154 if (close >= n || close + 1 >= n || s[close + 1] !=
'(')
157 for (k = url0; k < n && s[k] !=
')' && !
is_ws(s[k]); k++);
158 if (k >= n || s[k] !=
')' || k == url0)
175 return (c >=
'0' && c <=
'9') || (c >=
'a' && c <=
'z') ||
176 (c >=
'A' && c <=
'Z') || c >= 0x80;
186 while (i > 0 && s[i - 1] ==
'_')
188 return i == 0 || !
md_word_char((
unsigned char)s[i - 1]);
190static int md_us_closes(
const char* s,
size_t n,
size_t j,
size_t len) {
192 while (k < n && s[k] ==
'_')
201 if (c !=
'*' && c !=
'_')
207 if (i + 1 < n && s[i + 1] == c) {
208 for (j = i + 2; j + 1 < n; j++)
209 if (s[j] == c && s[j + 1] == c) {
224 for (j = i + 1; j < n; j++)
246 for (j = i + 2; j + 1 < n; j++)
247 if (s[j] ==
'~' && s[j + 1] ==
'~') {
263static size_t url_end(
const char* s,
size_t n,
size_t i) {
266 while (e < n && !
is_ws(s[e]) && s[e] !=
'\n' && s[e] !=
'<' &&
267 s[e] !=
'>' && s[e] !=
'"' && s[e] !=
'`')
274 if (c ==
'.' || c ==
',' || c ==
';' || c ==
':' || c ==
'!' ||
275 c ==
'?' || c ==
'\'' || c ==
'"' || c ==
'*' || c ==
'_' ||
282 for (k = i; k < e; k++)
283 depth += s[k] ==
'(' ? 1 : s[k] ==
')' ? -1
303 for (k = i + 1; k < n && s[k] !=
'>' && !
is_ws(s[k]) && s[k] !=
'\n'; k++);
304 if (k >= n || s[k] !=
'>')
308 if (e - u0 <= (((s[u0 + 4] | 0x20) ==
's') ? 8u : 7u))
316 if ((s[i] | 0x20) !=
'h' || !
is_http_url(s + i, n - i))
318 if (i > 0 && (
is_digit(s[i - 1]) || ((s[i - 1] | 0x20) >=
'a' && (s[i - 1] | 0x20) <=
'z') ||
319 s[i - 1] ==
'/' || (
unsigned char)s[i - 1] >= 0x80))
323 size_t scheme = ((s[i + 4] | 0x20) ==
's') ? 8 : 7;
338 return (c >=
'!' && c <=
'/') || (c >=
':' && c <=
'@') ||
339 (c >=
'[' && c <=
'`') || (c >=
'{' && c <=
'~');
356 if (
try_code(a, &t, s, n, i, inherited, &ni) ||
357 try_link(a, &t, s, n, i, inherited, &ni) ||
360 try_strike(a, &t, s, n, i, inherited, depth, flags, &ni) ||
361 try_emphasis(a, &t, s, n, i, inherited, depth, flags, &ni)) {
383 for (i = 0; i < n; i++)
384 if (!
is_ws(s[i]) && s[i] !=
'\n')
390 return n >= 3 && s[0] ==
'`' && s[1] ==
'`' && s[2] ==
'`';
395 if (n == 0 || s[0] !=
'>')
398 if (*off < n &&
is_ws(s[*off]))
404static int match_ul(
const char* s,
size_t n,
size_t* off) {
405 if (n < 2 || (s[0] !=
'-' && s[0] !=
'*') || !
is_ws(s[1]))
408 while (*off < n &&
is_ws(s[*off]))
414static int match_ol(
const char* s,
size_t n,
size_t* off) {
418 if (i == 0 || i >= n || s[i] !=
'.')
421 if (i >= n || !
is_ws(s[i]))
423 while (i < n &&
is_ws(s[i]))
433 while (i + h < n && s[i + h] ==
'#')
438 if (i >= n || s[i] !=
' ')
440 while (i < n &&
is_ws(s[i]))
463 while (i < n &&
is_ws(s[i]))
465 if (i < n && s[i] ==
'|')
467 while (n > i &&
is_ws(s[n - 1]))
469 if (n > i && s[n - 1] ==
'|')
473 while (j < n && s[j] !=
'|')
497 for (k = 0; k < c.
n; k++) {
498 size_t a = c.
a[k], b = c.
b[k];
499 while (a < b &&
is_ws(s[a]))
501 while (b > a &&
is_ws(s[b - 1]))
503 if (a < b && s[a] ==
':')
505 if (b > a && s[b - 1] ==
':')
519static unsigned char rule_align(
const char* s,
size_t a,
size_t b) {
521 while (a < b &&
is_ws(s[a]))
523 while (b > a &&
is_ws(s[b - 1]))
525 left = (a < b && s[a] ==
':');
526 right = (b > a && s[b - 1] ==
':');
538 for (i = 0; i < n; i++)
548 return has_pipe(lines[li].p, lines[li].n) && li + 1 < nlines &&
560 const char* s = lines[li].
p;
561 size_t n = lines[li].
n, off;
587typedef size_t (*
strip_fn)(
const char* s,
size_t n);
590 size_t total = 0, i, w = 0;
592 for (i = a; i < b; i++) {
593 size_t off = strip ? strip(lines[i].p, lines[i].n) : 0;
594 total += lines[i].
n - off + (i + 1 < b ? 1 : 0);
596 buf = malloc(total + 1);
601 for (i = a; i < b; i++) {
602 size_t off = strip ? strip(lines[i].p, lines[i].n) : 0;
603 size_t seg = lines[i].
n - off;
604 memcpy(buf + w, lines[i].p + off, seg);
633 while (a < b &&
is_ws(s[a]))
635 while (b > a &&
is_ws(s[b - 1]))
637 ni = realloc(blk->
items, (blk->
n_items + 1) *
sizeof(*ni));
653 for (k = 0; k < blk->
n_cols; k++) {
670 size_t total = 0, i, w = 0;
674 for (i = a; i < b; i++) {
675 size_t skip = (i == a) ? off : 0;
676 while (i != a && skip < lines[i].n &&
is_ws(lines[i].p[skip]))
678 total += lines[i].
n - skip + 1;
680 buf = malloc(total + 1);
683 for (i = a; i < b; i++) {
684 size_t skip = (i == a) ? off : 0;
685 while (i != a && skip < lines[i].n &&
is_ws(lines[i].p[skip]))
687 if (w && lines[i].n > skip)
689 memcpy(buf + w, lines[i].p + skip, lines[i].n - skip);
690 w += lines[i].
n - skip;
693 ni = realloc(blk->
items, (blk->
n_items + 1) *
sizeof(*ni));
713 size_t nlines = 0, i, li, start;
715 memset(out, 0,
sizeof(*out));
720 norm = malloc(len + 1);
725 for (i = 0; i < len; i++) {
726 if (text[i] ==
'\r') {
728 if (i + 1 < len && text[i + 1] ==
'\n')
739 lines = malloc((len + 1) *
sizeof(*lines));
745 for (i = 0; i <= len; i++) {
746 if (i == len || norm[i] ==
'\n') {
747 lines[nlines].
p = norm + start;
748 lines[nlines].
n = i - start;
755 while (li < nlines) {
756 const char* s = lines[li].
p;
757 size_t n = lines[li].
n, off;
780 row_cells(lines[li + 1].p, lines[li + 1].n, &rule);
786 while (li < nlines && !
line_blank(lines[li].p, lines[li].n) &&
787 has_pipe(lines[li].p, lines[li].n) &&
799 for (k = 0; k < blk->
n_cols; k++)
800 blk->
align[k] = k < rule.
n
809 while (li < nlines && !
line_blank(lines[li].p, lines[li].n) &&
810 has_pipe(lines[li].p, lines[li].n) &&
813 row_cells(lines[li].p, lines[li].n, &body);
820 size_t body_a, body_b;
825 while (li < nlines && !
starts_fence(lines[li].p, lines[li].n))
830 code =
join_lines(lines, body_a, body_b, NULL, &clen);
839 size_t a = li, joined_len;
841 while (li < nlines &&
match_quote(lines[li].p, lines[li].n, &off))
852 while (li < nlines &&
match_ul(lines[li].p, lines[li].n, &off)) {
858 while (li < nlines &&
line_indented(lines[li].p, lines[li].n) &&
868 while (li < nlines &&
match_ol(lines[li].p, lines[li].n, &off)) {
874 while (li < nlines &&
line_indented(lines[li].p, lines[li].n) &&
883 size_t a = li, joined_len;
892 while (li < nlines && !
line_blank(lines[li].p, lines[li].n) &&
895 joined =
join_lines(lines, a, li, NULL, &joined_len);
909 for (i = 0; i < inl->
n_spans; i++) {
922 for (i = 0; i < doc->
n_blocks; i++) {
926 for (j = 0; j < b->
n_items; j++)
962 for (i = 0; i + 1 < len; i++)
static void right(N_GUI_CTX *gui, float cx, float cy)
right press
static int try_emphasis(n_markdown_inline *a, tbuf *t, const char *s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t *ni)
static int is_md_punct(char c)
static int is_digit(char c)
static int starts_fence(const char *s, size_t n)
static int try_link(n_markdown_inline *a, tbuf *t, const char *s, size_t n, size_t i, unsigned inherited, size_t *ni)
static int starts_table(const mline *lines, size_t li, size_t nlines)
static int md_us_closes(const char *s, size_t n, size_t j, size_t len)
static int tbuf_add(tbuf *t, char c)
static void flush_text(n_markdown_inline *a, tbuf *t, unsigned style)
size_t(* strip_fn)(const char *s, size_t n)
static size_t url_end(const char *s, size_t n, size_t i)
static void row_cells(const char *s, size_t n, md_cells *out)
static int try_autolink(n_markdown_inline *a, tbuf *t, const char *s, size_t n, size_t i, unsigned inherited, size_t *ni)
static int line_blank(const char *s, size_t n)
static int inl_push(n_markdown_inline *a, char *text, char *url, unsigned style)
static int list_add_wrapped(n_markdown_block *blk, const mline *lines, size_t a, size_t b, size_t off, unsigned flags)
void n_markdown_free(n_markdown_doc *doc)
free everything a n_markdown_doc owns and zero it
static void table_add_row(n_markdown_block *blk, const char *s, const md_cells *c, unsigned flags)
static char * dup_range(const char *s, size_t n)
static int is_http_url(const char *s, size_t n)
static int match_quote(const char *s, size_t n, size_t *off)
static int md_word_char(unsigned char c)
static unsigned char rule_align(const char *s, size_t a, size_t b)
static size_t strip_quote(const char *s, size_t n)
static int has_pipe(const char *s, size_t n)
static int match_heading(const char *s, size_t n, int *level, size_t *off)
static int try_strike(n_markdown_inline *a, tbuf *t, const char *s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t *ni)
static int line_starts_block(const mline *lines, size_t li, size_t nlines, unsigned flags)
int n_markdown_has_markup_ex(const char *text, size_t len, unsigned flags)
1 if text contains any markup the given flags would render, else 0
void n_markdown_parse(const char *text, size_t len, n_markdown_doc *out)
parse text (len bytes, plain UTF-8) into out
static int match_ul(const char *s, size_t n, size_t *off)
static int line_indented(const char *s, size_t n)
static int md_us_opens(const char *s, size_t i)
void n_markdown_parse_ex(const char *text, size_t len, unsigned flags, n_markdown_doc *out)
parse text into out, choosing which optional block kinds to recognise
static int match_ol(const char *s, size_t n, size_t *off)
static n_markdown_block * doc_push(n_markdown_doc *d, int kind)
static char * join_lines(const mline *lines, size_t a, size_t b, strip_fn strip, size_t *out_len)
static int try_code(n_markdown_inline *a, tbuf *t, const char *s, size_t n, size_t i, unsigned inherited, size_t *ni)
static int is_table_rule(const char *s, size_t n)
static void free_inline(n_markdown_inline *inl)
int n_markdown_has_markup(const char *text, size_t len)
1 if text contains any renderable markup, else 0
static void parse_inline(n_markdown_inline *a, const char *s, size_t n, unsigned inherited, int depth, unsigned flags)
static int table_add_cell(n_markdown_block *blk, const char *s, size_t a, size_t b, unsigned flags)
Parse a SAFE, strict subset of Markdown into a block / styled-span model.
#define N_MARKDOWN_CODE
inline code style flag (carries no emphasis)
#define N_MARKDOWN_ESCAPES
n_markdown_parse_ex flag: a backslash before ASCII punctuation makes that character literal ("\\*not ...
#define N_MARKDOWN_ALIGN_CENTER
what "|:---:|" asks for.
#define N_MARKDOWN_ALIGN_RIGHT
what "|---:|" asks for.
#define N_MARKDOWN_ITALIC
italic inline style flag
#define N_MARKDOWN_LINK
[text](http(s)://url) style flag; url is set only when this is present
#define N_MARKDOWN_BOLD
bold inline style flag
n_markdown_inline * items
#define N_MARKDOWN_TABLES
n_markdown_parse_ex flag: recognise GitHub-style pipe tables.
@ N_MARKDOWN_HEADING
"## " heading (inl, level)
@ N_MARKDOWN_OL
ordered list (items)
@ N_MARKDOWN_TABLE
pipe table (items, n_cols)
@ N_MARKDOWN_UL
unordered list (items)
@ N_MARKDOWN_CODE_BLOCK
``` fenced code (raw code)
@ N_MARKDOWN_PARA
paragraph
#define N_MARKDOWN_STRIKE
strike inline style flag (only with N_MARKDOWN_STRIKETHROUGH)
#define N_MARKDOWN_STRIKETHROUGH
n_markdown_parse_ex flag: "~~text~~" carries N_MARKDOWN_STRIKE.
#define N_MARKDOWN_AUTOLINKS
n_markdown_parse_ex flag: a bare http(s) URL, or one in <angle brackets>, is a link whose text is the...
#define N_MARKDOWN_HEADINGS
n_markdown_parse_ex flag: recognise "# " to "###### " headings.
int kind
enum n_markdown_block_kind
unsigned char * align
TABLE: N_MARKDOWN_ALIGN_* per column, n_cols entries, owned.
#define N_MARKDOWN_ALIGN_LEFT
table column alignment: the default, and what "|:---|" asks for.
n_markdown_block * blocks
size_t n_cols
TABLE: cells per row, always at least 1.
int level
HEADING: 1 to 6.
A parsed document: an owned array of blocks.
One flow of inline runs (a paragraph, a quote, or one list item).
One inline run: visible text, a style bitmask, and (for a link) an already-http(s)-validated url.