reg: use snprintf for string values in num_str()
[neatroff.git] / fmt.c
blobce48cb4b1223e6c1a08b45dcd293e62469593a8a
1 /*
2 * line formatting buffer for line adjustment and hyphenation
4 * The line formatting buffer does two main functions: breaking
5 * words into lines (possibly after breaking them at their
6 * hyphenation points), and, if requested, adjusting the space
7 * between words in a line. In this file the first step is
8 * referred to as filling.
10 * Functions like fmt_word() return nonzero on failure, which
11 * means the call should be repeated after fetching previously
12 * formatted lines via fmt_nextline().
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include "roff.h"
19 #define FMT_LLEN(f) MAX(0, (f)->ll - (f)->li - (f)->lI)
20 #define FMT_FILL(f) (!n_ce && n_u)
21 #define FMT_ADJ(f) (n_u && !n_na && !n_ce && (n_j & AD_B) == AD_B)
23 static int fmt_fillwords(struct fmt *f, int br);
25 struct word {
26 char *s;
27 int wid; /* word's width */
28 int elsn, elsp; /* els_neg and els_pos */
29 int gap; /* the space before this word */
30 int hy; /* hyphen width if inserted after this word */
31 int str; /* does the space before it stretch */
32 int cost; /* the extra cost of line break after this word */
33 int swid; /* space width after this word (i.e., \w' ') */
36 struct line {
37 struct sbuf sbuf;
38 int wid, li, ll, lI;
39 int elsn, elsp;
42 struct fmt {
43 /* queued words */
44 struct word *words;
45 int words_n, words_sz;
46 /* queued lines */
47 struct line *lines;
48 int lines_head, lines_tail, lines_sz;
49 /* for paragraph adjustment */
50 long *best;
51 int *best_pos;
52 int *best_dep;
53 /* current line */
54 int gap; /* space before the next word */
55 int nls; /* newlines before the next word */
56 int nls_sup; /* suppressed newlines */
57 int li, ll, lI; /* current line indentation and length */
58 int filled; /* filled all words in the last fmt_fill() */
59 int eos; /* last word ends a sentence */
60 int fillreq; /* fill after the last word (\p) */
63 /* .ll, .in and .ti are delayed until the partial line is output */
64 static void fmt_confupdate(struct fmt *f)
66 f->ll = n_l;
67 f->li = n_ti >= 0 ? n_ti : n_i;
68 f->lI = n_tI >= 0 ? n_tI : n_I;
69 n_ti = -1;
70 n_tI = -1;
73 static int fmt_confchanged(struct fmt *f)
75 return f->ll != n_l || f->li != (n_ti >= 0 ? n_ti : n_i) ||
76 f->lI != (n_tI >= 0 ? n_tI : n_I);
79 /* move words inside an fmt struct */
80 static void fmt_movewords(struct fmt *a, int dst, int src, int len)
82 memmove(a->words + dst, a->words + src, len * sizeof(a->words[0]));
85 /* move words from the buffer to s */
86 static int fmt_wordscopy(struct fmt *f, int beg, int end,
87 struct sbuf *s, int *els_neg, int *els_pos)
89 struct word *wcur;
90 int w = 0;
91 int i;
92 *els_neg = 0;
93 *els_pos = 0;
94 for (i = beg; i < end; i++) {
95 wcur = &f->words[i];
96 sbuf_printf(s, "%ch'%du'", c_ec, wcur->gap);
97 sbuf_append(s, wcur->s);
98 w += wcur->wid + wcur->gap;
99 if (wcur->elsn < *els_neg)
100 *els_neg = wcur->elsn;
101 if (wcur->elsp > *els_pos)
102 *els_pos = wcur->elsp;
103 free(wcur->s);
105 if (beg < end) {
106 wcur = &f->words[end - 1];
107 if (wcur->hy)
108 sbuf_append(s, "\\(hy");
109 w += wcur->hy;
111 return w;
114 static int fmt_nlines(struct fmt *f)
116 return f->lines_head - f->lines_tail;
119 /* the total width of the specified words in f->words[] */
120 static int fmt_wordslen(struct fmt *f, int beg, int end)
122 int i, w = 0;
123 for (i = beg; i < end; i++)
124 w += f->words[i].wid + f->words[i].gap;
125 return beg < end ? w + f->words[end - 1].hy : 0;
128 /* the number of stretchable spaces in f */
129 static int fmt_spaces(struct fmt *f, int beg, int end)
131 int i, n = 0;
132 for (i = beg + 1; i < end; i++)
133 if (f->words[i].str)
134 n++;
135 return n;
138 /* the amount of stretchable spaces in f */
139 static int fmt_spacessum(struct fmt *f, int beg, int end)
141 int i, n = 0;
142 for (i = beg + 1; i < end; i++)
143 if (f->words[i].str)
144 n += f->words[i].gap;
145 return n;
148 /* return the next line in the buffer */
149 char *fmt_nextline(struct fmt *f, int *w,
150 int *li, int *lI, int *ll, int *els_neg, int *els_pos)
152 struct line *l;
153 if (f->lines_head == f->lines_tail)
154 return NULL;
155 l = &f->lines[f->lines_tail++];
156 *li = l->li;
157 *lI = l->lI;
158 *ll = l->ll;
159 *w = l->wid;
160 *els_neg = l->elsn;
161 *els_pos = l->elsp;
162 return sbuf_out(&l->sbuf);
165 static struct line *fmt_mkline(struct fmt *f)
167 struct line *l;
168 if (f->lines_head == f->lines_tail) {
169 f->lines_head = 0;
170 f->lines_tail = 0;
172 if (f->lines_head == f->lines_sz) {
173 f->lines_sz += 256;
174 f->lines = mextend(f->lines, f->lines_head,
175 f->lines_sz, sizeof(f->lines[0]));
177 l = &f->lines[f->lines_head++];
178 l->li = f->li;
179 l->lI = f->lI;
180 l->ll = f->ll;
181 sbuf_init(&l->sbuf);
182 return l;
185 static void fmt_keshideh(struct fmt *f, int beg, int end, int wid);
187 /* extract words from beg to end; shrink or stretch spaces if needed */
188 static int fmt_extractline(struct fmt *f, int beg, int end, int str)
190 int fmt_div, fmt_rem;
191 int w, i, nspc, llen;
192 struct line *l;
193 if (!(l = fmt_mkline(f)))
194 return 1;
195 llen = FMT_LLEN(f);
196 w = fmt_wordslen(f, beg, end);
197 if (str && FMT_ADJ(f) && n_j & AD_K) {
198 fmt_keshideh(f, beg, end, llen - w);
199 w = fmt_wordslen(f, beg, end);
201 nspc = fmt_spaces(f, beg, end);
202 if (nspc && FMT_ADJ(f) && (llen < w || str)) {
203 fmt_div = (llen - w) / nspc;
204 fmt_rem = (llen - w) % nspc;
205 if (fmt_rem < 0) {
206 fmt_div--;
207 fmt_rem += nspc;
209 for (i = beg + 1; i < end; i++)
210 if (f->words[i].str)
211 f->words[i].gap += fmt_div + (fmt_rem-- > 0);
213 l->wid = fmt_wordscopy(f, beg, end, &l->sbuf, &l->elsn, &l->elsp);
214 return 0;
217 static int fmt_sp(struct fmt *f)
219 if (fmt_fillwords(f, 1))
220 return 1;
221 if (fmt_extractline(f, 0, f->words_n, 0))
222 return 1;
223 f->filled = 0;
224 f->nls--;
225 f->nls_sup = 0;
226 f->words_n = 0;
227 f->fillreq = 0;
228 return 0;
231 /* fill as many lines as possible; if br, put the remaining words in a line */
232 int fmt_fill(struct fmt *f, int br)
234 if (fmt_fillwords(f, br))
235 return 1;
236 if (br) {
237 f->filled = 0;
238 if (f->words_n)
239 if (fmt_sp(f))
240 return 1;
242 return 0;
245 void fmt_space(struct fmt *fmt)
247 fmt->gap += font_swid(dev_font(n_f), n_s, n_ss);
250 int fmt_newline(struct fmt *f)
252 f->gap = 0;
253 if (!FMT_FILL(f)) {
254 f->nls++;
255 fmt_sp(f);
256 return 0;
258 if (f->nls >= 1)
259 if (fmt_sp(f))
260 return 1;
261 if (f->nls == 0 && !f->filled && !f->words_n)
262 fmt_sp(f);
263 f->nls++;
264 return 0;
267 /* format the paragraph after the next word (\p) */
268 int fmt_fillreq(struct fmt *f)
270 if (f->fillreq > 0)
271 if (fmt_fillwords(f, 0))
272 return 1;
273 f->fillreq = f->words_n + 1;
274 return 0;
277 static void fmt_wb2word(struct fmt *f, struct word *word, struct wb *wb,
278 int hy, int str, int gap, int cost)
280 int len = strlen(wb_buf(wb));
281 word->s = xmalloc(len + 1);
282 memcpy(word->s, wb_buf(wb), len + 1);
283 word->wid = wb_wid(wb);
284 word->elsn = wb->els_neg;
285 word->elsp = wb->els_pos;
286 word->hy = hy ? wb_hywid(wb) : 0;
287 word->str = str;
288 word->gap = gap;
289 word->cost = cost;
290 word->swid = wb_swid(wb);
293 /* find explicit break positions: dashes, \:, \%, and \~ */
294 static int fmt_hyphmarks(char *word, int *hyidx, int *hyins, int *hygap)
296 char *s = word;
297 char *d = NULL;
298 int c, n = 0;
299 int lastchar = 0;
300 while ((c = escread(&s, &d)) > 0)
302 if (c < 0 || !strcmp(c_hc, d))
303 return -1;
304 while ((c = escread(&s, &d)) >= 0 && n < NHYPHSWORD) {
305 if (!c) {
306 if (!strcmp(c_hc, d)) {
307 hyins[n] = 1;
308 hyidx[n++] = s - word;
310 if (c_hydash(d)) {
311 hyins[n] = 0;
312 hyidx[n++] = s - word;
314 if (!strcmp(c_nb, d)) {
315 hygap[n] = 1;
316 hyidx[n++] = s - word;
318 lastchar = s - word;
321 /* cannot break the end of a word */
322 while (n > 0 && hyidx[n - 1] == lastchar)
323 n--;
324 return n;
327 static struct word *fmt_mkword(struct fmt *f)
329 if (f->words_n == f->words_sz) {
330 f->words_sz += 256;
331 f->words = mextend(f->words, f->words_n,
332 f->words_sz, sizeof(f->words[0]));
334 return &f->words[f->words_n++];
337 static void fmt_insertword(struct fmt *f, struct wb *wb, int gap)
339 int hyidx[NHYPHSWORD]; /* sub-word boundaries */
340 int hyins[NHYPHSWORD] = {0}; /* insert dash */
341 int hygap[NHYPHSWORD] = {0}; /* stretchable no-break space */
342 char *src = wb_buf(wb);
343 struct wb wbc;
344 char *beg;
345 char *end;
346 int n, i;
347 int cf, cs, cm, ccd;
348 n = fmt_hyphmarks(src, hyidx, hyins, hygap);
349 if (n <= 0) {
350 fmt_wb2word(f, fmt_mkword(f), wb, 0, 1, gap, wb_cost(wb));
351 return;
353 /* update f->fillreq considering the new sub-words */
354 if (f->fillreq == f->words_n + 1)
355 f->fillreq += n;
356 wb_init(&wbc);
357 /* add sub-words */
358 for (i = 0; i <= n; i++) {
359 int ihy = i < n && hyins[i]; /* dash width */
360 int istr = i == 0 || hygap[i - 1]; /* stretchable */
361 int igap; /* gap width */
362 int icost; /* hyphenation cost */
363 beg = src + (i > 0 ? hyidx[i - 1] : 0);
364 end = src + (i < n ? hyidx[i] : strlen(src));
365 if (i < n && hygap[i]) /* remove \~ */
366 end -= strlen(c_nb);
367 wb_catstr(&wbc, beg, end);
368 wb_fnszget(&wbc, &cf, &cs, &cm, &ccd);
369 icost = i == n ? wb_cost(&wbc) : hygap[i] * 10000000;
370 igap = i == 0 ? gap : hygap[i - 1] * wb_swid(&wbc);
371 fmt_wb2word(f, fmt_mkword(f), &wbc, ihy, istr, igap, icost);
372 wb_reset(&wbc);
373 wb_fnszset(&wbc, cf, cs, cm, ccd); /* restoring wbc */
375 wb_done(&wbc);
378 /* the amount of space necessary before the next word */
379 static int fmt_wordgap(struct fmt *f)
381 int nls = f->nls || f->nls_sup;
382 int swid = font_swid(dev_font(n_f), n_s, n_ss);
383 if (f->eos && f->words_n)
384 if ((nls && !f->gap) || (!nls && f->gap == 2 * swid))
385 return swid + font_swid(dev_font(n_f), n_s, n_sss);
386 return (nls && !f->gap && f->words_n) ? swid : f->gap;
389 /* insert wb into fmt */
390 int fmt_word(struct fmt *f, struct wb *wb)
392 if (wb_empty(wb))
393 return 0;
394 if (fmt_confchanged(f))
395 if (fmt_fillwords(f, 0))
396 return 1;
397 if (FMT_FILL(f) && f->nls && f->gap)
398 if (fmt_sp(f))
399 return 1;
400 if (!f->words_n) /* apply the new .l and .i */
401 fmt_confupdate(f);
402 f->gap = fmt_wordgap(f);
403 f->eos = wb_eos(wb);
404 fmt_insertword(f, wb, f->filled ? 0 : f->gap);
405 f->filled = 0;
406 f->nls = 0;
407 f->nls_sup = 0;
408 f->gap = 0;
409 return 0;
412 /* insert keshideh characters */
413 static void fmt_keshideh(struct fmt *f, int beg, int end, int wid)
415 struct wb wb;
416 int kw, i = 0, c = 0;
417 struct word *w;
418 int cnt = 0;
419 do {
420 cnt = 0;
421 for (c = 0; c < 2; c++) {
422 for (i = end - 1 - c; i >= beg; i -= 2) {
423 w = &f->words[i];
424 wb_init(&wb);
425 kw = wb_keshideh(w->s, &wb, wid);
426 if (kw > 0) {
427 free(w->s);
428 w->s = xmalloc(strlen(wb_buf(&wb)) + 1);
429 strcpy(w->s, wb_buf(&wb));
430 w->wid = wb_wid(&wb);
431 wid -= kw;
432 cnt++;
434 wb_done(&wb);
437 } while (cnt);
440 /* approximate 8 * sqrt(cost) */
441 static long scaledown(long cost)
443 long ret = 0;
444 int i;
445 for (i = 0; i < 14; i++)
446 ret += ((cost >> (i * 2)) & 3) << (i + 3);
447 return ret < (1 << 13) ? ret : (1 << 13);
450 /* the cost of putting lwid words in a line of length llen */
451 static long FMT_COST(int llen, int lwid, int swid, int nspc)
453 /* the ratio that the stretchable spaces of the line should be spread */
454 long ratio = labs((llen - lwid) * 100l / (swid ? swid : 1));
455 /* ratio too large; scaling it down */
456 if (ratio > 4000)
457 ratio = 4000 + scaledown(ratio - 4000);
458 /* assigning a cost of 100 to each space stretching 100 percent */
459 return ratio * ratio / 100l * (nspc ? nspc : 1);
462 /* the number of hyphenations in consecutive lines ending at pos */
463 static int fmt_hydepth(struct fmt *f, int pos)
465 int n = 0;
466 while (pos > 0 && f->words[pos - 1].hy && ++n < 5)
467 pos = f->best_pos[pos];
468 return n;
471 static long hycost(int depth)
473 if (n_hlm > 0 && depth > n_hlm)
474 return 10000000;
475 if (depth >= 3)
476 return n_hycost + n_hycost2 + n_hycost3;
477 if (depth == 2)
478 return n_hycost + n_hycost2;
479 return depth ? n_hycost : 0;
482 /* the cost of putting a line break before word pos */
483 static long fmt_findcost(struct fmt *f, int pos)
485 int i, hyphenated;
486 long cur;
487 int llen = MAX(1, FMT_LLEN(f));
488 int lwid = 0; /* current line length */
489 int swid = 0; /* amount of stretchable spaces */
490 int nspc = 0; /* number of stretchable spaces */
491 int dwid = 0; /* equal to swid, unless swid is zero */
492 if (pos <= 0)
493 return 0;
494 if (f->best_pos[pos] >= 0)
495 return f->best[pos] + f->words[pos - 1].cost;
496 lwid = f->words[pos - 1].hy; /* non-zero if the last word is hyphenated */
497 hyphenated = f->words[pos - 1].hy != 0;
498 i = pos - 1;
499 while (i >= 0) {
500 lwid += f->words[i].wid;
501 if (i + 1 < pos)
502 lwid += f->words[i + 1].gap;
503 if (i + 1 < pos && f->words[i + 1].str) {
504 swid += f->words[i + 1].gap;
505 nspc++;
507 if (lwid > llen + swid * n_ssh / 100 && i + 1 < pos)
508 break;
509 dwid = swid;
510 if (!dwid && i > 0) /* no stretchable spaces */
511 dwid = f->words[i - 1].swid;
512 cur = fmt_findcost(f, i) + FMT_COST(llen, lwid, dwid, nspc);
513 if (hyphenated)
514 cur += hycost(1 + fmt_hydepth(f, i));
515 if (f->best_pos[pos] < 0 || cur < f->best[pos]) {
516 f->best_pos[pos] = i;
517 f->best_dep[pos] = f->best_dep[i] + 1;
518 f->best[pos] = cur;
520 i--;
522 return f->best[pos] + f->words[pos - 1].cost;
525 static int fmt_bestpos(struct fmt *f, int pos)
527 fmt_findcost(f, pos);
528 return MAX(0, f->best_pos[pos]);
531 static int fmt_bestdep(struct fmt *f, int pos)
533 fmt_findcost(f, pos);
534 return MAX(0, f->best_dep[pos]);
537 /* return the last filled word */
538 static int fmt_breakparagraph(struct fmt *f, int pos, int br)
540 int i;
541 int best = -1;
542 long cost, best_cost = 0;
543 int llen = FMT_LLEN(f);
544 int lwid = 0; /* current line length */
545 int swid = 0; /* amount of stretchable spaces */
546 int nspc = 0; /* number of stretchable spaces */
547 if (f->fillreq > 0 && f->fillreq <= f->words_n) {
548 fmt_findcost(f, f->fillreq);
549 return f->fillreq;
551 if (pos > 0 && f->words[pos - 1].wid >= llen) {
552 fmt_findcost(f, pos);
553 return pos;
555 i = pos - 1;
556 lwid = 0;
557 if (f->words[i].hy) /* the last word is hyphenated */
558 lwid += f->words[i].hy;
559 while (i >= 0) {
560 lwid += f->words[i].wid;
561 if (i + 1 < pos)
562 lwid += f->words[i + 1].gap;
563 if (i + 1 < pos && f->words[i + 1].str) {
564 swid += f->words[i + 1].gap;
565 nspc++;
567 if (lwid > llen && i + 1 < pos)
568 break;
569 cost = fmt_findcost(f, i);
570 /* the cost of formatting short lines; should prevent widows */
571 if (br && n_pmll && lwid < llen * n_pmll / 100) {
572 int pmll = llen * n_pmll / 100;
573 cost += (long) n_pmllcost * (pmll - lwid) / pmll;
575 if (best < 0 || cost < best_cost) {
576 best = i;
577 best_cost = cost;
579 i--;
581 return best;
584 /* extract the first nreq formatted lines before the word at pos */
585 static int fmt_head(struct fmt *f, int nreq, int pos, int nohy)
587 int best = pos; /* best line break for nreq-th line */
588 int prev, next; /* best line breaks without hyphenation */
589 if (nreq <= 0 || fmt_bestdep(f, pos) < nreq)
590 return pos;
591 /* finding the optimal line break for nreq-th line */
592 while (best > 0 && fmt_bestdep(f, best) > nreq)
593 best = fmt_bestpos(f, best);
594 prev = best;
595 next = best;
596 if (!nohy)
597 return best;
598 /* finding closest line breaks without hyphenation */
599 while (prev > 1 && f->words[prev - 1].hy &&
600 fmt_bestdep(f, prev - 1) == nreq)
601 prev--;
602 while (next < pos && f->words[next - 1].hy &&
603 fmt_bestdep(f, next) == nreq)
604 next++;
605 /* choosing the best of them */
606 if (!f->words[prev - 1].hy && !f->words[next - 1].hy)
607 return fmt_findcost(f, prev) <= fmt_findcost(f, next) ? prev : next;
608 if (!f->words[prev - 1].hy)
609 return prev;
610 if (!f->words[next - 1].hy)
611 return next;
612 return best;
615 /* break f->words[0..end] into lines according to fmt_bestpos() */
616 static int fmt_break(struct fmt *f, int end)
618 int beg, ret = 0;
619 beg = fmt_bestpos(f, end);
620 if (beg > 0)
621 ret += fmt_break(f, beg);
622 f->words[beg].gap = 0;
623 if (fmt_extractline(f, beg, end, 1))
624 return ret;
625 if (beg > 0)
626 fmt_confupdate(f);
627 return ret + (end - beg);
630 /* estimated number of lines until traps or the end of a page */
631 static int fmt_safelines(void)
633 int lnht = MAX(1, n_L) * n_v;
634 return n_v > 0 ? (f_nexttrap() + lnht - 1) / lnht : 1000;
637 /* fill the words collected in the buffer */
638 static int fmt_fillwords(struct fmt *f, int br)
640 int nreq; /* the number of lines until a trap */
641 int end; /* the final line ends before this word */
642 int end_head; /* like end, but only the first nreq lines included */
643 int head = 0; /* only nreq first lines have been formatted */
644 int llen; /* line length, taking shrinkable spaces into account */
645 int n, i;
646 if (!FMT_FILL(f))
647 return 0;
648 llen = fmt_wordslen(f, 0, f->words_n) -
649 fmt_spacessum(f, 0, f->words_n) * n_ssh / 100;
650 /* not enough words to fill */
651 if ((f->fillreq <= 0 || f->words_n < f->fillreq) && llen <= FMT_LLEN(f))
652 return 0;
653 /* lines until a trap or page end */
654 nreq = fmt_safelines();
655 /* if line settings are changed, output a single line */
656 if (fmt_confchanged(f))
657 nreq = 1;
658 /* enough lines are collected already */
659 if (nreq > 0 && nreq <= fmt_nlines(f))
660 return 1;
661 /* resetting positions */
662 f->best = malloc((f->words_n + 1) * sizeof(f->best[0]));
663 f->best_pos = malloc((f->words_n + 1) * sizeof(f->best_pos[0]));
664 f->best_dep = malloc((f->words_n + 1) * sizeof(f->best_dep[0]));
665 memset(f->best, 0, (f->words_n + 1) * sizeof(f->best[0]));
666 memset(f->best_dep, 0, (f->words_n + 1) * sizeof(f->best_dep[0]));
667 for (i = 0; i < f->words_n + 1; i++)
668 f->best_pos[i] = -1;
669 end = fmt_breakparagraph(f, f->words_n, br);
670 if (nreq > 0) {
671 int nohy = 0; /* do not hyphenate the last line */
672 if (n_hy & HY_LAST && nreq == fmt_nlines(f))
673 nohy = 1;
674 end_head = fmt_head(f, nreq - fmt_nlines(f), end, nohy);
675 head = end_head < end;
676 end = end_head;
678 /* recursively add lines */
679 n = end > 0 ? fmt_break(f, end) : 0;
680 f->words_n -= n;
681 f->fillreq -= n;
682 fmt_movewords(f, 0, n, f->words_n);
683 f->filled = n && !f->words_n;
684 if (f->words_n)
685 f->words[0].gap = 0;
686 if (f->words_n) /* apply the new .l and .i */
687 fmt_confupdate(f);
688 free(f->best);
689 free(f->best_pos);
690 free(f->best_dep);
691 f->best = NULL;
692 f->best_pos = NULL;
693 f->best_dep = NULL;
694 return head || n != end;
697 struct fmt *fmt_alloc(void)
699 struct fmt *fmt = xmalloc(sizeof(*fmt));
700 memset(fmt, 0, sizeof(*fmt));
701 return fmt;
704 void fmt_free(struct fmt *fmt)
706 free(fmt->lines);
707 free(fmt->words);
708 free(fmt);
711 int fmt_wid(struct fmt *fmt)
713 return fmt_wordslen(fmt, 0, fmt->words_n) + fmt_wordgap(fmt);
716 int fmt_morewords(struct fmt *fmt)
718 return fmt_morelines(fmt) || fmt->words_n;
721 int fmt_morelines(struct fmt *fmt)
723 return fmt->lines_head != fmt->lines_tail;
726 /* suppress the last newline */
727 void fmt_suppressnl(struct fmt *fmt)
729 if (fmt->nls) {
730 fmt->nls--;
731 fmt->nls_sup = 1;