901 lines · 19.1 KB · C
RawDownload
// SPDX-License-Identifier: ISC#include <errno.h>#include <stdbool.h>#include <stdint.h>#include <stdlib.h>#include <string.h>#include <wchar.h> #include <langinfo.h> #include <md4c.h> #include "md_render.h" #include "useful.h"#include "banned.h" intmd_display_width(const char *s, size_t n){	mbstate_t ps;	size_t i = 0;	int cols = 0; 	ASSERT_RETURN(s != NULL, 0); 	memset(&ps, 0, sizeof(ps));	while (i < n) {		wchar_t wc;		size_t k = mbrtowc(&wc, s + i, n - i, &ps);		if (k == (size_t)-1 || k == (size_t)-2 || k == 0) {			i++; /* invalid/incomplete: count as one column */			cols++;			continue;		}		int w = wcwidth(wc);		cols += (w > 0) ? w : (w == 0 ? 0 : 1);		i += k;	}	return cols;} /* ---- a growable buffer of styled runs, used for table cells ---- */ struct run {	unsigned style;	char *text; /* owned, NUL-terminated */	size_t len;}; struct runbuf {	struct run *v;	size_t n, cap;}; static intrunbuf_push(struct runbuf *b, unsigned style, const char *text, size_t len){	if (len == 0)		return 0;	if (b->n == b->cap) {		size_t ncap = b->cap ? b->cap * 2 : 8;		struct run *p = realloc(b->v, ncap * sizeof(*p));		if (p == NULL)			return -1;		b->v = p;		b->cap = ncap;	}	char *dup = malloc(len + 1);	if (dup == NULL)		return -1;	memcpy(dup, text, len);	dup[len] = '\0';	b->v[b->n].style = style;	b->v[b->n].text = dup;	b->v[b->n].len = len;	b->n++;	return 0;} static voidrunbuf_free(struct runbuf *b){	for (size_t i = 0; i < b->n; i++)		free(b->v[i].text);	free(b->v);	b->v = NULL;	b->n = b->cap = 0;} static intrunbuf_width(const struct runbuf *b){	int w = 0;	for (size_t i = 0; i < b->n; i++)		w += md_display_width(b->v[i].text, b->v[i].len);	return w;} /* ---- table accumulator ---- */ struct table {	struct runbuf *cells; /* row-major: rows*cols */	unsigned *aligns;     /* per-cell MD_ALIGN */	size_t rows, cols;	size_t cap_cells;	size_t cur_row, cur_col; /* fill cursor */	size_t head_rows;        /* number of leading header rows */	bool in_head;}; /* ---- render context ---- */ struct ctx {	const struct md_opts *opts;	void (*emit)(const struct md_run *, void *);	void *user; 	unsigned attr; /* accumulated span styles */	int blanks;    /* pending blank lines (coalesced) */	bool bol;      /* at beginning of a line */ 	struct table *tbl; /* non-NULL while inside a table */ 	int width;	enum md_table_style tstyle;}; static voidemit_run(struct ctx *c, unsigned style, const char *text, size_t len){	struct md_run r = {.style = style, .text = text, .len = len};	if (len == 0)		return;	c->emit(&r, c->user);} static voidemit_nl(struct ctx *c){	emit_run(c, 0, "\n", 1);	c->bol = true;} static voidflush_blanks(struct ctx *c){	if (c->blanks > 0 && !c->bol)		emit_nl(c);	c->blanks = 0;} /* ---- table layout ---- */ static struct runbuf *tbl_cell(struct table *t, size_t row, size_t col){	return &t->cells[row * t->cols + col];} /* Box-drawing glyphs for one table style. Each column occupies w[j]+2 cells * (a space of padding on each side of the content); rules fill those same * spans so junctions line up under the vertical separators. */struct box {	const char *v;            /* vertical separator */	const char *h;            /* horizontal fill */	const char *tl, *tm, *tr; /* top: left, mid tee, right */	const char *ml, *mm, *mr; /* header sep: left tee, cross, right tee */	const char *bl, *bm, *br; /* bottom: left, mid tee, right */}; static const struct box box_unicode = {    "\u2502",    "\u2500",    "\u250c",    "\u252c",    "\u2510",    "\u251c",    "\u253c",    "\u2524",    "\u2514",    "\u2534",    "\u2518",}; static const struct box box_ascii = {    "|",    "-",    "+",    "+",    "+",    "+",    "+",    "+",    "+",    "+",    "+",}; static voidemit_str(struct ctx *c, const char *s){	emit_run(c, 0, s, strlen(s));} static voidemit_pad(struct ctx *c, int n){	while (n-- > 0)		emit_run(c, 0, " ", 1);} /* A horizontal border: left corner, per-column fill of w[j]+2 glyphs, a * junction between columns, and a right corner -- matching the data-row * geometry so corners and tees align with the vertical separators. */static voidemit_border(struct ctx *c, const struct box *b, const int *w, size_t cols,    const char *left, const char *mid, const char *right){	emit_str(c, left);	for (size_t j = 0; j < cols; j++) {		for (int k = 0; k < w[j] + 2; k++)			emit_str(c, b->h);		emit_str(c, (j + 1 < cols) ? mid : right);	}	emit_nl(c);} #define WRAP_MIN_COL 3 /* A styled slice of text, borrowed from the owning cell's runbuf -- no * copies, just (style, pointer, len) into memory the table already owns. */struct frag {	unsigned style;	const char *text;	size_t len;}; /* One wrapped line of a cell: a sequence of frags plus its cached display * width, so alignment padding doesn't have to re-walk the frags. */struct cline {	struct frag *f;	size_t n, cap;	int width;}; /* A cell wrapped to some column width: zero or more lines. */struct wcell {	struct cline *lines;	size_t n, cap;}; static intcline_push(struct cline *ln, unsigned style, const char *text, size_t len){	if (len == 0)		return 0;	if (ln->n == ln->cap) {		size_t ncap = ln->cap ? ln->cap * 2 : 4;		struct frag *p = realloc(ln->f, ncap * sizeof(*p));		if (p == NULL)			return -1;		ln->f = p;		ln->cap = ncap;	}	ln->f[ln->n].style = style;	ln->f[ln->n].text = text;	ln->f[ln->n].len = len;	ln->n++;	ln->width += md_display_width(text, len);	return 0;} static struct cline *wcell_newline(struct wcell *wc){	if (wc->n == wc->cap) {		size_t ncap = wc->cap ? wc->cap * 2 : 4;		struct cline *p = realloc(wc->lines, ncap * sizeof(*p));		if (p == NULL)			return NULL;		wc->lines = p;		wc->cap = ncap;	}	memset(&wc->lines[wc->n], 0, sizeof(wc->lines[wc->n]));	return &wc->lines[wc->n++];} static voidwcell_free(struct wcell *wc){	for (size_t i = 0; i < wc->n; i++)		free(wc->lines[i].f);	free(wc->lines);	wc->lines = NULL;	wc->n = wc->cap = 0;} /* * Bytes of text that fit in `width` columns, always on a character * boundary. Returns 0 when not even one character fits. */static size_tprefix_within(const char *text, size_t len, int width, int *used){	mbstate_t ps;	size_t i = 0;	int cols = 0; 	memset(&ps, 0, sizeof(ps));	while (i < len) {		wchar_t wc;		size_t k = mbrtowc(&wc, text + i, len - i, &ps);		int cw; 		if (k == (size_t)-1 || k == (size_t)-2 || k == 0) {			k = 1; /* invalid/incomplete: one column, one byte */			cw = 1;		} else {			int t = wcwidth(wc);			cw = (t > 0) ? t : (t == 0 ? 0 : 1);		}		if (cols + cw > width)			break;		cols += cw;		i += k;	}	*used = cols;	return i;} /* Greedy word-wrap: break on runs of ' '. A word longer than the whole * column has no break to use, so it is split at the column edge rather than * left to overflow, which would push everything after it out of line. * Styling is preserved per fragment across the break. */static intwrap_cell(struct wcell *wc, struct runbuf *cell, int width){	struct cline *cur;	bool has_content = false; 	cur = wcell_newline(wc);	if (cur == NULL)		return -1; 	for (size_t r = 0; r < cell->n; r++) {		unsigned style = cell->v[r].style;		const char *text = cell->v[r].text;		size_t len = cell->v[r].len;		size_t i = 0; 		while (i < len) {			size_t start = i;			bool is_space = (text[i] == ' ');			while (i < len && (text[i] == ' ') == is_space)				i++;			size_t tlen = i - start;			int tw = md_display_width(text + start, tlen); 			if (is_space) {				if (!has_content)					continue;				if (cur->width + tw > width) {					cur = wcell_newline(wc);					if (cur == NULL)						return -1;					has_content = false;					continue;				}				if (cline_push(cur, style, text + start, tlen) <				    0)					return -1;				continue;			} 			if (has_content && cur->width + tw > width) {				cur = wcell_newline(wc);				if (cur == NULL)					return -1;				has_content = false;			} 			/* Still too wide on a line of its own: split it. */			while (cur->width + tw > width) {				int room = width - cur->width;				int fit_w = 0;				size_t fit = room > 0				    ? prefix_within(				          text + start, tlen, room, &fit_w)				    : 0; 				if (fit == 0 && cur->width == 0)					break; /* column too narrow to split */				if (fit > 0) {					if (cline_push(cur, style, text + start,					        fit) < 0)						return -1;					start += fit;					tlen -= fit;					tw -= fit_w;				}				cur = wcell_newline(wc);				if (cur == NULL)					return -1;				has_content = false;				if (tlen == 0)					break;			} 			if (tlen == 0)				continue;			if (cline_push(cur, style, text + start, tlen) < 0)				return -1;			has_content = true;		}	}	return 0;} /* One data row, possibly spanning several wrapped lines: an edge/separator * plus a space-padded, aligned fragment set in each w[j]+2 span per line. * Header cells are bolded. */static voidemit_wrapped_row(struct ctx *c, struct table *t, size_t i,    const struct wcell *wcells, const struct box *b, const int *w, bool ruled){	size_t rh = 1; 	for (size_t j = 0; j < t->cols; j++) {		const struct wcell *wc = &wcells[i * t->cols + j];		if (wc->n > rh)			rh = wc->n;	} 	for (size_t k = 0; k < rh; k++) {		if (ruled)			emit_str(c, b->v);		for (size_t j = 0; j < t->cols; j++) {			const struct wcell *wc = &wcells[i * t->cols + j];			unsigned al = t->aligns[i * t->cols + j];			int cw = (k < wc->n) ? wc->lines[k].width : 0;			int padtot = w[j] - cw;			int lpad = 0, rpad = padtot; 			if (al == MD_ALIGN_RIGHT) {				lpad = padtot;				rpad = 0;			} else if (al == MD_ALIGN_CENTER) {				lpad = padtot / 2;				rpad = padtot - lpad;			} 			emit_pad(c, 1 + lpad);			if (k < wc->n) {				const struct cline *ln = &wc->lines[k];				for (size_t f = 0; f < ln->n; f++) {					unsigned st = ln->f[f].style;					if (i < t->head_rows)						st |= MD_ST_BOLD;					emit_run(					    c, st, ln->f[f].text, ln->f[f].len);				}			}			c->bol = false;			emit_pad(c, rpad + 1);			if (ruled)				emit_str(c, b->v);			else if (j + 1 < t->cols)				emit_str(c, " ");		}		emit_nl(c);	}} /* * Fit columns into `avail`, taking the space from the widest. A column * within its fair share keeps its natural width and leaves the remainder to * the others. One ratio applied to every column would squeeze a four-column * "Size" heading as hard as a column of prose. */static voidshrink_columns(int *w, size_t cols, int avail){	size_t open = cols;	int left = avail;	bool *fixed; 	fixed = calloc(cols, sizeof(*fixed));	if (fixed == NULL)		return; 	for (bool changed = true; changed && open > 0;) {		int share = left / (int)open; 		changed = false;		if (share < WRAP_MIN_COL)			share = WRAP_MIN_COL;		for (size_t j = 0; j < cols; j++) {			if (fixed[j] || w[j] > share)				continue;

Lines 1–500 of 901.