shithub: neatroff

ref: 7e4c525d86df55de3f726b5718c739d4bb0dce5c
dir: /wb.c/

View raw version
/* word buffer */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "roff.h"

/* the current font, size and color */
#define R_F(wb)		((wb)->r_f >= 0 ? (wb)->r_f : n_f)	/* current font */
#define R_S(wb)		((wb)->r_s >= 0 ? (wb)->r_s : n_s)	/* current size */
#define R_M(wb)		((wb)->r_m >= 0 ? (wb)->r_m : n_m)	/* current color */
/* italic correction */
#define glyph_ic(g)	(MAX(0, (g)->urx - (g)->wid))
#define glyph_icleft(g)	(MAX(0, -(g)->llx))
/* like DEVWID() but handles negative w */
#define SDEVWID(sz, w)	((w) >= 0 ? DEVWID((sz), (w)) : -DEVWID((sz), -(w)))
/* the maximum and minimum values of bounding box coordinates */
#define BBMAX		(1 << 29)
#define BBMIN		-BBMAX

void wb_init(struct wb *wb)
{
	memset(wb, 0, sizeof(*wb));
	sbuf_init(&wb->sbuf);
	wb->f = -1;
	wb->s = -1;
	wb->m = -1;
	wb->r_f = -1;
	wb->r_s = -1;
	wb->r_m = -1;
	wb->icleft_ll = -1;
	wb->llx = BBMAX;
	wb->lly = BBMAX;
	wb->urx = BBMIN;
	wb->ury = BBMIN;
}

void wb_done(struct wb *wb)
{
	sbuf_done(&wb->sbuf);
}

/* update wb->st and wb->sb */
static void wb_stsb(struct wb *wb)
{
	wb->st = MIN(wb->st, wb->v - (wb->s * SC_IN / 72));
	wb->sb = MAX(wb->sb, wb->v);
}

/* update bounding box */
static void wb_bbox(struct wb *wb, int llx, int lly, int urx, int ury)
{
	wb->llx = MIN(wb->llx, wb->h + llx);
	wb->lly = MIN(wb->lly, -wb->v + lly);
	wb->urx = MAX(wb->urx, wb->h + urx);
	wb->ury = MAX(wb->ury, -wb->v + ury);
}

/* append font and size to the buffer if needed */
static void wb_font(struct wb *wb)
{
	if (wb->f != R_F(wb)) {
		sbuf_printf(&wb->sbuf, "%cf(%02d", c_ec, R_F(wb));
		wb->f = R_F(wb);
	}
	if (wb->s != R_S(wb)) {
		sbuf_printf(&wb->sbuf, "%cs(%02d", c_ec, R_S(wb));
		wb->s = R_S(wb);
	}
	if (!n_cp && wb->m != R_M(wb)) {
		sbuf_printf(&wb->sbuf, "%cm[%s]", c_ec, clr_str(R_M(wb)));
		wb->m = R_M(wb);
	}
	wb_stsb(wb);
}

/* pending font, size or color changes */
static int wb_pendingfont(struct wb *wb)
{
	return wb->f != R_F(wb) || wb->s != R_S(wb) ||
			(!n_cp && wb->m != R_M(wb));
}

void wb_hmov(struct wb *wb, int n)
{
	wb->h += n;
	sbuf_printf(&wb->sbuf, "%ch'%du'", c_ec, n);
}

void wb_vmov(struct wb *wb, int n)
{
	wb->v += n;
	sbuf_printf(&wb->sbuf, "%cv'%du'", c_ec, n);
}

void wb_els(struct wb *wb, int els)
{
	if (els > wb->els_pos)
		wb->els_pos = els;
	if (els < wb->els_neg)
		wb->els_neg = els;
	sbuf_printf(&wb->sbuf, "%cx'%du'", c_ec, els);
}

void wb_etc(struct wb *wb, char *x)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cX%s", c_ec, x);
}

/* make sure nothing is appended to wb after the last wb_put() */
static void wb_prevcheck(struct wb *wb)
{
	if (wb->prev_ll != sbuf_len(&wb->sbuf))
		wb->prev_n = 0;
}

/* mark wb->prev_c[] as valid */
static void wb_prevok(struct wb *wb)
{
	wb->prev_ll = sbuf_len(&wb->sbuf);
}

/* append c to wb->prev_c[] */
static void wb_prevput(struct wb *wb, char *c, int ll)
{
	if (wb->prev_n == LEN(wb->prev_c))
		wb->prev_n--;
	memmove(wb->prev_l + 1, wb->prev_l, wb->prev_n * sizeof(wb->prev_l[0]));
	memmove(wb->prev_h + 1, wb->prev_h, wb->prev_n * sizeof(wb->prev_h[0]));
	memmove(wb->prev_c + 1, wb->prev_c, wb->prev_n * sizeof(wb->prev_c[0]));
	wb->prev_l[0] = ll;
	wb->prev_h[0] = wb->h;
	strcpy(wb->prev_c[0], c);
	wb->prev_n++;
	wb_prevok(wb);
}

/* strip the last i characters from wb */
static void wb_prevpop(struct wb *wb, int i)
{
	int n = wb->prev_n - i;
	sbuf_cut(&wb->sbuf, wb->prev_l[i - 1]);
	wb->h = wb->prev_h[i - 1];
	memmove(wb->prev_l, wb->prev_l + i, n * sizeof(wb->prev_l[0]));
	memmove(wb->prev_h, wb->prev_h + i, n * sizeof(wb->prev_h[0]));
	memmove(wb->prev_c, wb->prev_c + i, n * sizeof(wb->prev_c[0]));
	wb->prev_n = n;
	wb->prev_ll = sbuf_len(&wb->sbuf);
}

/* return the i-th last character inserted via wb_put() */
static char *wb_prev(struct wb *wb, int i)
{
	wb_prevcheck(wb);
	return i < wb->prev_n ? wb->prev_c[i] : NULL;
}

static struct glyph *wb_prevglyph(struct wb *wb)
{
	return wb_prev(wb, 0) ? dev_glyph(wb_prev(wb, 0), wb->f) : NULL;
}

void wb_put(struct wb *wb, char *c)
{
	struct glyph *g;
	int ll, zerowidth;
	if (c[0] == '\n') {
		wb->part = 0;
		return;
	}
	if (c[0] == ' ') {
		wb_hmov(wb, spacewid(R_F(wb), R_S(wb)));
		return;
	}
	if (c[0] == '\t' || c[0] == '' ||
			(c[0] == c_ni && (c[1] == '\t' || c[1] == ''))) {
		sbuf_append(&wb->sbuf, c);
		return;
	}
	g = dev_glyph(c, R_F(wb));
	zerowidth = !strcmp(c_hc, c) || !strcmp(c_bp, c);
	if (!g && c[0] == c_ec && !zerowidth) {	/* unknown escape */
		memmove(c, c + 1, strlen(c));
		g = dev_glyph(c, R_F(wb));
	}
	if (g && !zerowidth && wb->icleft_ll == sbuf_len(&wb->sbuf))
		if (glyph_icleft(g))
			wb_hmov(wb, SDEVWID(R_S(wb), glyph_icleft(g)));
	wb->icleft_ll = -1;
	wb_font(wb);
	wb_prevcheck(wb);		/* make sure wb->prev_c[] is valid */
	ll = sbuf_len(&wb->sbuf);	/* sbuf length before inserting c */
	if (!c[1] || c[0] == c_ec || c[0] == c_ni || utf8one(c)) {
		if (c[0] == c_ni && c[1] == c_ec)
			sbuf_printf(&wb->sbuf, "%c%c", c_ec, c_ec);
		else
			sbuf_append(&wb->sbuf, c);
	} else {
		if (c[1] && !c[2])
			sbuf_printf(&wb->sbuf, "%c(%s", c_ec, c);
		else
			sbuf_printf(&wb->sbuf, "%cC'%s'", c_ec, c);
	}
	if (!zerowidth) {
		wb_prevput(wb, c, ll);
		if (!n_cp && g)
			wb_bbox(wb, SDEVWID(wb->s, g->llx),
				SDEVWID(wb->s, g->lly),
				SDEVWID(wb->s, g->urx),
				SDEVWID(wb->s, g->ury));
		wb->h += charwid(wb->f, wb->s, g ? g->wid : SC_DW);
		wb->ct |= g ? g->type : 0;
		wb_stsb(wb);
	}
}

/* just like wb_put(), but call cdef_expand() if c is defined */
void wb_putexpand(struct wb *wb, char *c)
{
	if (cdef_expand(wb, c, R_F(wb)))
		wb_put(wb, c);
}

/* return zero if c formed a ligature with its previous character */
int wb_lig(struct wb *wb, char *c)
{
	char lig[GNLEN] = "";
	char *cs[LIGLEN + 2];
	int i = -1;
	int ligpos;
	if (wb_pendingfont(wb))		/* font changes disable ligatures */
		return 1;
	cs[0] = c;
	while (wb_prev(wb, ++i))
		cs[i + 1] = wb_prev(wb, i);
	ligpos = font_lig(dev_font(R_F(wb)), cs, i + 1);
	if (ligpos > 1) {
		for (i = 0; i < ligpos - 1; i++)
			strcat(lig, wb_prev(wb, ligpos - i - 2));
		strcat(lig, c);
		wb_prevpop(wb, ligpos - 1);
		wb_put(wb, lig);
		return 0;
	}
	return 1;
}

/* return 0 if pairwise kerning was done */
int wb_kern(struct wb *wb, char *c)
{
	int val;
	if (wb_pendingfont(wb) || !wb_prev(wb, 0))
		return 1;
	val = font_kern(dev_font(R_F(wb)), wb_prev(wb, 0), c);
	if (val)
		wb_hmov(wb, charwid(R_F(wb), R_S(wb), val));
	wb_prevok(wb);		/* kerning should not prevent ligatures */
	return !val;
}

int wb_part(struct wb *wb)
{
	return wb->part;
}

void wb_setpart(struct wb *wb)
{
	wb->part = 1;
}

void wb_drawl(struct wb *wb, int c, int h, int v)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cD'%c %du %du'", c_ec, c, h, v);
	wb->h += h;
	wb->v += v;
	wb_stsb(wb);
}

void wb_drawc(struct wb *wb, int c, int r)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cD'%c %du'", c_ec, c, r);
	wb->h += r;
}

void wb_drawe(struct wb *wb, int c, int h, int v)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cD'%c %du %du'", c_ec, c, h, v);
	wb->h += h;
}

void wb_drawa(struct wb *wb, int c, int h1, int v1, int h2, int v2)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cD'%c %du %du %du %du'", c_ec, c, h1, v1, h2, v2);
	wb->h += h1 + h2;
	wb->v += v1 + v2;
	wb_stsb(wb);
}

void wb_drawxbeg(struct wb *wb, int c)
{
	wb_font(wb);
	sbuf_printf(&wb->sbuf, "%cD'%c", c_ec, c);
}

void wb_drawxdot(struct wb *wb, int h, int v)
{
	sbuf_printf(&wb->sbuf, " %du %du", h, v);
	wb->h += h;
	wb->v += v;
	wb_stsb(wb);
}

void wb_drawxend(struct wb *wb)
{
	sbuf_printf(&wb->sbuf, "'");
}

static void wb_reset(struct wb *wb)
{
	wb_done(wb);
	wb_init(wb);
}

static void wb_putc(struct wb *wb, int t, char *s)
{
	switch (t) {
	case 0:
	case 'C':
		wb_put(wb, s);
		break;
	case 'D':
		ren_dcmd(wb, s);
		break;
	case 'f':
		wb->r_f = atoi(s);
		break;
	case 'h':
		wb_hmov(wb, atoi(s));
		break;
	case 'm':
		wb->r_m = clr_get(s);
		break;
	case 's':
		wb->r_s = atoi(s);
		break;
	case 'v':
		wb_vmov(wb, atoi(s));
		break;
	case 'x':
		wb_els(wb, atoi(s));
		break;
	case 'X':
		wb_etc(wb, s);
		break;
	}
}

void wb_cat(struct wb *wb, struct wb *src)
{
	char *s = sbuf_buf(&src->sbuf);
	char d[ILNLEN];
	int c, part;
	while ((c = escread(&s, d)) >= 0)
		wb_putc(wb, c, d);
	part = src->part;
	wb->r_s = -1;
	wb->r_f = -1;
	wb->r_m = -1;
	wb_reset(src);
	src->part = part;
}

int wb_wid(struct wb *wb)
{
	return wb->h;
}

int wb_empty(struct wb *wb)
{
	return sbuf_empty(&wb->sbuf);
}

/* return 1 if wb ends a sentence (.?!) */
int wb_eos(struct wb *wb)
{
	int i = 0;
	while (wb_prev(wb, i) && strchr("'\")]*", wb_prev(wb, i)[0]))
		i++;
	return wb_prev(wb, i) && strchr(".?!", wb_prev(wb, i)[0]);
}

void wb_wconf(struct wb *wb, int *ct, int *st, int *sb,
		int *llx, int *lly, int *urx, int *ury)
{
	*ct = wb->ct;
	*st = -wb->st;
	*sb = -wb->sb;
	*llx = wb->llx < BBMAX ? wb->llx : 0;
	*lly = wb->lly < BBMAX ? -wb->lly : 0;
	*urx = wb->urx > BBMIN ? wb->urx : 0;
	*ury = wb->ury > BBMIN ? -wb->ury : 0;
}

/* skip troff requests; return 1 if read c_hc */
static int skipreqs(char **s, struct wb *w1)
{
	char d[ILNLEN];
	char *r = *s;
	int c;
	wb_reset(w1);
	while ((c = escread(s, d)) > 0) {
		wb_putc(w1, c, d);
		r = *s;
	}
	if (c < 0 || !strcmp(c_hc, d))
		return 1;
	*s = r;
	return 0;
}

/* the position marked with hyphens or \: */
static char *bp_pos(char *s, int w, struct wb *w1, int flg)
{
	char d[ILNLEN];
	char *r = NULL;
	int c;
	skipreqs(&s, w1);
	while ((c = escread(&s, d)) >= 0) {
		wb_putc(w1, c, d);
		if (wb_wid(w1) > w && (!(flg & HY_ANY) || r))
			continue;
		if (!c && (!strcmp("-", d) || (!strcmp("em", d) ||
					!strcmp("hy", d)) || !strcmp(c_bp, d)))
			r = s;
	}
	return r;
}

static int wb_dashwid(struct wb *wb)
{
	struct glyph *g = dev_glyph("hy", R_F(wb));
	return charwid(R_F(wb), R_S(wb), g ? g->wid : SC_DW);
}

/* the position marked with \% */
static char *hc_pos(char *s, int w, struct wb *w1, int flg)
{
	char d[ILNLEN];
	char *r = NULL;
	int c;
	skipreqs(&s, w1);
	while ((c = escread(&s, d)) >= 0) {
		wb_putc(w1, c, d);
		if (wb_wid(w1) + wb_dashwid(w1) > w && (!(flg & HY_ANY) || r))
			continue;
		if (!c && !strcmp(c_hc, d))
			r = s;
	}
	return r;
}

static char *hyphpos(char *s, int w, struct wb *w1, int flg)
{
	char word[ILNLEN];	/* word to pass to hyphenate() */
	char hyph[ILNLEN];	/* hyphenation points returned from hyphenate() */
	char *iw[ILNLEN];	/* beginning of i-th char in word */
	char *is[ILNLEN];	/* beginning of i-th char in s */
	int fits[ILNLEN];	/* fits[i] is 1, if the first i chars fit w */
	int n = 0;		/* the number of characters in word */
	char d[ILNLEN];
	char *prev_s = s;
	char *r = NULL;
	char *wp = word, *we = word + sizeof(word);
	int i, c;
	skipreqs(&s, w1);
	while ((c = escread(&s, d)) >= 0 && (c > 0 || strlen(d) + 1 < we - wp)) {
		fits[n] = wb_wid(w1) + wb_dashwid(w1) <= w;
		wb_putc(w1, c, d);
		if (c == 0) {
			iw[n] = wp;
			is[n] = prev_s;
			/* ignore multi-char aliases except for ligatures */
			if (!utf8one(d) && !font_islig(dev_font(R_F(w1)), d))
				strcpy(d, ".");
			strcpy(wp, d);
			wp = strchr(wp, '\0');
			n++;
		}
		prev_s = s;
	}
	if (n < 3)
		return NULL;
	hyphenate(hyph, word, flg);
	for (i = 1; i < n - 1; i++)
		if (hyph[iw[i] - word] && (fits[i] || ((flg & HY_ANY) && !r)))
			r = is[i];
	return r;
}

static void dohyph(char *s, char *pos, int dash, struct wb *w1, struct wb *w2)
{
	char d[ILNLEN];
	int c = -1;
	wb_reset(w1);
	wb_reset(w2);
	while (s != pos && (c = escread(&s, d)) >= 0)
		wb_putc(w1, c, d);
	if (dash)
		wb_putc(w1, 0, "hy");
	w2->r_s = w1->r_s;
	w2->r_f = w1->r_f;
	w2->r_m = w1->r_m;
	while ((c = escread(&s, d)) >= 0)
		wb_putc(w2, c, d);
}

/* hyphenate wb into w1 and w2; return zero on success */
int wb_hyph(struct wb *wb, int w, struct wb *w1, struct wb *w2, int flg)
{
	char *s = sbuf_buf(&wb->sbuf);
	char *dp, *hp, *p;
	if (skipreqs(&s, w1))
		return 1;
	dp = bp_pos(sbuf_buf(&wb->sbuf), w, w1, flg);
	hp = hc_pos(sbuf_buf(&wb->sbuf), w, w1, flg);
	if (hp && dp)
		p = flg & HY_ANY ? MIN(dp, hp) : MAX(dp, hp);
	else
		p = dp ? dp : hp;
	if (!p && flg & HY_MASK)
		p = hyphpos(sbuf_buf(&wb->sbuf), w, w1, flg);
	if (p)
		dohyph(sbuf_buf(&wb->sbuf), p, p != dp, w1, w2);
	return !p;
}

void wb_italiccorrection(struct wb *wb)
{
	struct glyph *g = wb_prevglyph(wb);
	if (g && glyph_ic(g))
		wb_hmov(wb, SDEVWID(wb->s, glyph_ic(g)));
}

void wb_italiccorrectionleft(struct wb *wb)
{
	wb->icleft_ll = sbuf_len(&wb->sbuf);
}