ref: c55cd099d93cd4a34a48f61bfffdba6866ea263a
dir: /fmt.c/
/* * 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; }