shithub: neatroff

ref: c55cd099d93cd4a34a48f61bfffdba6866ea263a
dir: /fmt.c/

View raw version
/*
 * line formatting buffer for line adjustment and hyphenation
 *
 * The line formatting buffer does two main functions: breaking
 * words into lines (possibly after hyphenating some of them), and, if
 * requested, adjusting the space between words in a line.  In this
 * file the first step is referred to as filling.
 *
 * Inputs are specified via these functions:
 * + fmt_word(): for appending space-separated words.
 * + fmt_space(): for appending spaces.
 * + fmt_newline(): for appending new lines.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "roff.h"

#define FMT_LLEN(f)	MAX(0, (f)->ll - (f)->li)
#define FMT_FILL(f)	(!n_ce && n_u)
#define FMT_ADJ(f)	(n_u && !n_na && !n_ce && (n_j & AD_B) == AD_B)
#define FMT_SWID(f)	(spacewid(n_f, n_s))

struct word {
	char *s;
	int wid;
	int elsn, elsp;
	int gap;
};

struct line {
	struct sbuf sbuf;
	int wid, li, ll;
	int elsn, elsp;
};

struct fmt {
	/* queued words */
	struct word words[NWORDS];
	int nwords;
	/* queued lines */
	struct line lines[NLINES];
	int l_head, l_tail;
	/* current line */
	int gap;		/* space before the next word */
	int nls;		/* newlines before the next word */
	int li, ll;		/* current line indentation and length */
	int filled;		/* filled all words in the last fmt_fill() */
	int eos;		/* last word ends a sentence */
};

/* .ll, .in and .ti are delayed until the partial line is output */
static void fmt_confupdate(struct fmt *f)
{
	f->ll = n_l;
	f->li = n_ti >= 0 ? n_ti : n_i;
	n_ti = -1;
}

/* move words inside an fmt struct */
static void fmt_movewords(struct fmt *a, int dst, int src, int len)
{
	memmove(a->words + dst, a->words + src, len * sizeof(a->words[0]));
}

static char *fmt_strdup(char *s)
{
	int l = strlen(s);
	char *r = malloc(l + 1);
	memcpy(r, s, l + 1);
	return r;
}

/* copy word buffer wb in fmt->words[i] */
static void fmt_insertword(struct fmt *f, int i, struct wb *wb, int gap)
{
	struct word *w = &f->words[i];
	w->s = fmt_strdup(wb_buf(wb));
	w->wid = wb_wid(wb);
	w->elsn = wb->els_neg;
	w->elsp = wb->els_pos;
	w->gap = gap;
}

/* the total width of the first n words in f->words[] */
static int fmt_wordslen(struct fmt *f, int n)
{
	int i, w = 0;
	for (i = 0; i < n; i++)
		w += f->words[i].wid + f->words[i].gap;
	return w;
}

/* select as many words as can be fit in llen */
static int fmt_linefit(struct fmt *f, int llen)
{
	int i, w = 0;
	for (i = 0; i < f->nwords; i++) {
		w += f->words[i].wid + f->words[i].gap;
		if (w > llen)
			return i;
	}
	return i;
}

/* move n words from the buffer to s */
static int fmt_move(struct fmt *f, int n, struct sbuf *s, int *els_neg, int *els_pos)
{
	struct word *wcur;
	int w = 0;
	int i;
	*els_neg = 0;
	*els_pos = 0;
	for (i = 0; i < n; i++) {
		wcur = &f->words[i];
		sbuf_printf(s, "%ch'%du'", c_ec, wcur->gap);
		sbuf_append(s, wcur->s);
		w += wcur->wid + wcur->gap;
		if (wcur->elsn < *els_neg)
			*els_neg = wcur->elsn;
		if (wcur->elsp > *els_pos)
			*els_pos = wcur->elsp;
		free(wcur->s);
	}
	if (!n)
		return 0;
	f->nwords -= n;
	fmt_movewords(f, 0, n, f->nwords);
	if (f->nwords)		/* apply the new .l and .i */
		fmt_confupdate(f);
	return w;
}

/* try to hyphenate the n-th word */
static void fmt_hyph(struct fmt *f, int n, int w, int hyph)
{
	struct wb w1, w2;
	int flg = hyph | (n ? 0 : HY_ANY);
	wb_init(&w1);
	wb_init(&w2);
	if (!wb_hyph(f->words[n].s, w, &w1, &w2, flg)) {
		fmt_movewords(f, n + 2, n + 1, f->nwords - n);
		free(f->words[n].s);
		fmt_insertword(f, n, &w1, f->words[n].gap);
		fmt_insertword(f, n + 1, &w2, 0);
		f->nwords++;
	}
	wb_done(&w1);
	wb_done(&w2);
}

/* estimated number of lines until traps or the end of a page */
static int ren_safelines(void)
{
	return f_nexttrap() / (MAX(1, n_L) * n_v);
}

static int fmt_nlines(struct fmt *f)
{
	if (f->l_tail <= f->l_head)
		return f->l_head - f->l_tail;
	return NLINES - f->l_tail + f->l_head;
}

int fmt_fill(struct fmt *f, int all)
{
	int llen, fmt_div, fmt_rem;
	int w = 0;
	int i, n;
	struct line *l;
	int hyph = n_hy;
	if (!FMT_FILL(f))
		return 0;
	while (f->nwords) {
		l = &f->lines[f->l_head];
		llen = FMT_LLEN(f);
		if ((f->l_head + 1) % NLINES == f->l_tail)
			return 1;
		l->li = f->li;
		l->ll = f->ll;
		n = fmt_linefit(f, llen);
		if (n == f->nwords && !all)
			break;
		if ((n_hy & HY_LAST) && ren_safelines() < 2 + fmt_nlines(f))
			hyph = 0;	/* disable hyphenation for final lines */
		if (n < f->nwords)
			fmt_hyph(f, n, llen - fmt_wordslen(f, n) -
					f->words[n].gap, hyph);
		n = fmt_linefit(f, llen);
		if (!n && f->nwords)
			n = 1;
		w = fmt_wordslen(f, n);
		if (FMT_ADJ(f) && n > 1) {
			fmt_div = (llen - w) / (n - 1);
			fmt_rem = (llen - w) % (n - 1);
			for (i = 0; i < n - 1; i++)
				f->words[i + 1].gap += fmt_div + (i < fmt_rem);
		}
		sbuf_init(&l->sbuf);
		l->wid = fmt_move(f, n, &l->sbuf, &l->elsn, &l->elsp);
		f->words[0].gap = 0;
		f->filled = n && !f->nwords;
		f->l_head = (f->l_head + 1) % NLINES;
	}
	return 0;
}

/* return the next line in the buffer */
int fmt_nextline(struct fmt *f, struct sbuf *sbuf, int *w,
		int *li, int *ll, int *els_neg, int *els_pos)
{
	struct line *l;
	l = &f->lines[f->l_tail];
	if (f->l_head == f->l_tail)
		return 1;
	*li = l->li;
	*ll = l->ll;
	*w = l->wid;
	*els_neg = l->elsn;
	*els_pos = l->elsp;
	sbuf_append(sbuf, sbuf_buf(&l->sbuf));
	sbuf_done(&l->sbuf);
	f->l_tail = (f->l_tail + 1) % NLINES;
	return 0;
}

static int fmt_sp(struct fmt *f)
{
	struct line *l;
	fmt_fill(f, 0);
	if ((f->l_head + 1) % NLINES == f->l_tail)
		return 1;
	l = &f->lines[f->l_head];
	f->filled = 0;
	f->nls--;
	l->li = f->li;
	l->ll = f->ll;
	sbuf_init(&l->sbuf);
	l->wid = fmt_move(f, f->nwords, &l->sbuf, &l->elsn, &l->elsp);
	f->l_head = (f->l_head + 1) % NLINES;
	return 0;
}

void fmt_br(struct fmt *f)
{
	fmt_fill(f, 0);
	f->filled = 0;
	if (f->nwords)
		fmt_sp(f);
}

void fmt_space(struct fmt *fmt)
{
	fmt->gap += FMT_SWID(fmt);
}

void fmt_newline(struct fmt *f)
{
	f->nls++;
	f->gap = 0;
	if (!FMT_FILL(f)) {
		fmt_sp(f);
		return;
	}
	if (f->nls == 1 && !f->filled && !f->nwords)
		fmt_sp(f);
	if (f->nls > 1) {
		if (!f->filled)
			fmt_sp(f);
		fmt_sp(f);
	}
}

/* insert wb into fmt */
void fmt_word(struct fmt *f, struct wb *wb)
{
	if (f->nwords == NWORDS)
		fmt_fill(f, 0);
	if (wb_empty(wb) || f->nwords == NWORDS)
		return;
	if (FMT_FILL(f) && f->nls && f->gap)
		fmt_sp(f);
	if (!f->nwords)		/* apply the new .l and .i */
		fmt_confupdate(f);
	if (f->nls && !f->gap && f->nwords >= 1)
		f->gap = (f->nwords && f->eos) ? FMT_SWID(f) * 2 : FMT_SWID(f);
	fmt_insertword(f, f->nwords++, wb, f->filled ? 0 : f->gap);
	f->filled = 0;
	f->nls = 0;
	f->gap = 0;
	f->eos = wb_eos(wb);
}

struct fmt *fmt_alloc(void)
{
	struct fmt *fmt = malloc(sizeof(*fmt));
	memset(fmt, 0, sizeof(*fmt));
	return fmt;
}

void fmt_free(struct fmt *fmt)
{
	free(fmt);
}

int fmt_wid(struct fmt *fmt)
{
	return fmt_wordslen(fmt, fmt->nwords) +
		(fmt->nls ? FMT_SWID(fmt) : fmt->gap);
}

int fmt_morewords(struct fmt *fmt)
{
	return fmt_morelines(fmt) || fmt->nwords;
}

int fmt_morelines(struct fmt *fmt)
{
	return fmt->l_head != fmt->l_tail;
}