Annotation of src/usr.bin/mandoc/out.c, Revision 1.12
1.12 ! schwarze 1: /* $Id: out.c,v 1.11 2011/01/25 12:07:26 schwarze Exp $ */
1.1 schwarze 2: /*
1.9 schwarze 3: * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.12 ! schwarze 4: * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include <sys/types.h>
19:
1.2 schwarze 20: #include <assert.h>
1.1 schwarze 21: #include <ctype.h>
22: #include <stdio.h>
23: #include <stdlib.h>
1.2 schwarze 24: #include <string.h>
25: #include <time.h>
1.1 schwarze 26:
1.9 schwarze 27: #include "mandoc.h"
1.1 schwarze 28: #include "out.h"
29:
1.9 schwarze 30: static void tblcalc_data(struct rofftbl *, struct roffcol *,
31: const struct tbl *, const struct tbl_dat *);
32: static void tblcalc_literal(struct rofftbl *, struct roffcol *,
33: const struct tbl_dat *);
34: static void tblcalc_number(struct rofftbl *, struct roffcol *,
35: const struct tbl *, const struct tbl_dat *);
36:
1.1 schwarze 37: /*
38: * Convert a `scaling unit' to a consistent form, or fail. Scaling
39: * units are documented in groff.7, mdoc.7, man.7.
40: */
41: int
42: a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
43: {
44: char buf[BUFSIZ], hasd;
45: int i;
46: enum roffscale unit;
47:
48: if ('\0' == *src)
49: return(0);
50:
51: i = hasd = 0;
52:
53: switch (*src) {
54: case ('+'):
55: src++;
56: break;
57: case ('-'):
58: buf[i++] = *src++;
59: break;
60: default:
61: break;
62: }
63:
64: if ('\0' == *src)
65: return(0);
66:
67: while (i < BUFSIZ) {
68: if ( ! isdigit((u_char)*src)) {
69: if ('.' != *src)
70: break;
71: else if (hasd)
72: break;
73: else
74: hasd = 1;
75: }
76: buf[i++] = *src++;
77: }
78:
79: if (BUFSIZ == i || (*src && *(src + 1)))
80: return(0);
81:
82: buf[i] = '\0';
83:
84: switch (*src) {
85: case ('c'):
86: unit = SCALE_CM;
87: break;
88: case ('i'):
89: unit = SCALE_IN;
90: break;
91: case ('P'):
92: unit = SCALE_PC;
93: break;
94: case ('p'):
95: unit = SCALE_PT;
96: break;
97: case ('f'):
98: unit = SCALE_FS;
99: break;
100: case ('v'):
101: unit = SCALE_VS;
102: break;
103: case ('m'):
104: unit = SCALE_EM;
105: break;
106: case ('\0'):
107: if (SCALE_MAX == def)
108: return(0);
109: unit = SCALE_BU;
110: break;
111: case ('u'):
112: unit = SCALE_BU;
113: break;
114: case ('M'):
115: unit = SCALE_MM;
116: break;
117: case ('n'):
118: unit = SCALE_EN;
119: break;
120: default:
121: return(0);
122: }
123:
1.6 schwarze 124: /* FIXME: do this in the caller. */
1.1 schwarze 125: if ((dst->scale = atof(buf)) < 0)
126: dst->scale = 0;
127: dst->unit = unit;
128: return(1);
129: }
1.2 schwarze 130:
131:
132: /*
133: * Correctly writes the time in nroff form, which differs from standard
134: * form in that a space isn't printed in lieu of the extra %e field for
135: * single-digit dates.
136: */
137: void
138: time2a(time_t t, char *dst, size_t sz)
139: {
140: struct tm tm;
141: char buf[5];
142: char *p;
143: size_t nsz;
144:
145: assert(sz > 1);
146: localtime_r(&t, &tm);
147:
148: p = dst;
149: nsz = 0;
150:
151: dst[0] = '\0';
152:
153: if (0 == (nsz = strftime(p, sz, "%B ", &tm)))
154: return;
155:
156: p += (int)nsz;
157: sz -= nsz;
158:
159: if (0 == strftime(buf, sizeof(buf), "%e, ", &tm))
160: return;
161:
162: nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz);
163:
164: if (nsz >= sz)
165: return;
166:
167: p += (int)nsz;
168: sz -= nsz;
169:
170: (void)strftime(p, sz, "%Y", &tm);
171: }
172:
1.3 schwarze 173:
174: int
1.6 schwarze 175: a2roffdeco(enum roffdeco *d, const char **word, size_t *sz)
1.3 schwarze 176: {
1.6 schwarze 177: int i, j, lim;
178: char term, c;
179: const char *wp;
1.8 schwarze 180: enum roffdeco dd;
1.3 schwarze 181:
182: *d = DECO_NONE;
1.6 schwarze 183: lim = i = 0;
184: term = '\0';
1.3 schwarze 185: wp = *word;
186:
1.6 schwarze 187: switch ((c = wp[i++])) {
1.3 schwarze 188: case ('('):
189: *d = DECO_SPECIAL;
1.6 schwarze 190: lim = 2;
191: break;
1.4 schwarze 192: case ('F'):
193: /* FALLTHROUGH */
194: case ('f'):
1.6 schwarze 195: *d = 'F' == c ? DECO_FFONT : DECO_FONT;
196:
197: switch (wp[i++]) {
198: case ('('):
199: lim = 2;
200: break;
201: case ('['):
202: term = ']';
203: break;
1.4 schwarze 204: case ('3'):
205: /* FALLTHROUGH */
206: case ('B'):
207: *d = DECO_BOLD;
1.6 schwarze 208: return(i);
1.4 schwarze 209: case ('2'):
210: /* FALLTHROUGH */
211: case ('I'):
212: *d = DECO_ITALIC;
1.6 schwarze 213: return(i);
1.4 schwarze 214: case ('P'):
215: *d = DECO_PREVIOUS;
1.6 schwarze 216: return(i);
1.4 schwarze 217: case ('1'):
218: /* FALLTHROUGH */
219: case ('R'):
220: *d = DECO_ROMAN;
1.6 schwarze 221: return(i);
1.4 schwarze 222: default:
1.6 schwarze 223: i--;
224: lim = 1;
1.4 schwarze 225: break;
226: }
1.6 schwarze 227: break;
1.8 schwarze 228: case ('k'):
229: /* FALLTHROUGH */
1.6 schwarze 230: case ('M'):
231: /* FALLTHROUGH */
232: case ('m'):
233: /* FALLTHROUGH */
1.3 schwarze 234: case ('*'):
1.6 schwarze 235: if ('*' == c)
236: *d = DECO_RESERVED;
1.3 schwarze 237:
1.6 schwarze 238: switch (wp[i++]) {
1.3 schwarze 239: case ('('):
1.6 schwarze 240: lim = 2;
241: break;
1.3 schwarze 242: case ('['):
1.6 schwarze 243: term = ']';
244: break;
1.3 schwarze 245: default:
1.6 schwarze 246: i--;
247: lim = 1;
1.4 schwarze 248: break;
1.3 schwarze 249: }
1.6 schwarze 250: break;
1.12 ! schwarze 251:
! 252: case ('N'):
! 253:
! 254: /*
! 255: * Sequence of characters: backslash, 'N' (i = 0),
! 256: * starting delimiter (i = 1), character number (i = 2).
! 257: */
! 258:
! 259: *word = wp + 2;
! 260: *sz = 0;
! 261:
! 262: /*
! 263: * Cannot use a digit as a starting delimiter;
! 264: * but skip the digit anyway.
! 265: */
! 266:
! 267: if (isdigit((int)wp[1]))
! 268: return(2);
! 269:
! 270: /*
! 271: * Any non-digit terminates the character number.
! 272: * That is, the terminating delimiter need not
! 273: * match the starting delimiter.
! 274: */
! 275:
! 276: for (i = 2; isdigit((int)wp[i]); i++)
! 277: (*sz)++;
! 278:
! 279: /*
! 280: * This is only a numbered character
! 281: * if the character number has at least one digit.
! 282: */
! 283:
! 284: if (*sz)
! 285: *d = DECO_NUMBERED;
! 286:
! 287: /*
! 288: * Skip the terminating delimiter, even if it does not
! 289: * match, and even if there is no character number.
! 290: */
! 291:
! 292: return(++i);
! 293:
1.7 schwarze 294: case ('h'):
295: /* FALLTHROUGH */
296: case ('v'):
297: /* FALLTHROUGH */
1.3 schwarze 298: case ('s'):
1.7 schwarze 299: j = 0;
300: if ('+' == wp[i] || '-' == wp[i]) {
1.6 schwarze 301: i++;
1.7 schwarze 302: j = 1;
303: }
1.3 schwarze 304:
1.6 schwarze 305: switch (wp[i++]) {
306: case ('('):
307: lim = 2;
308: break;
309: case ('['):
310: term = ']';
311: break;
1.3 schwarze 312: case ('\''):
1.6 schwarze 313: term = '\'';
314: break;
315: case ('0'):
1.7 schwarze 316: j = 1;
1.3 schwarze 317: /* FALLTHROUGH */
318: default:
1.6 schwarze 319: i--;
320: lim = 1;
1.3 schwarze 321: break;
322: }
323:
1.6 schwarze 324: if ('+' == wp[i] || '-' == wp[i]) {
1.7 schwarze 325: if (j)
1.6 schwarze 326: return(i);
327: i++;
328: }
1.8 schwarze 329:
330: /* Handle embedded numerical subexp or escape. */
331:
332: if ('(' == wp[i]) {
333: while (wp[i] && ')' != wp[i])
334: if ('\\' == wp[i++]) {
335: /* Handle embedded escape. */
336: *word = &wp[i];
337: i += a2roffdeco(&dd, word, sz);
338: }
339:
340: if (')' == wp[i++])
341: break;
342:
343: *d = DECO_NONE;
344: return(i - 1);
345: } else if ('\\' == wp[i]) {
346: *word = &wp[++i];
347: i += a2roffdeco(&dd, word, sz);
348: }
349:
1.6 schwarze 350: break;
351: case ('['):
352: *d = DECO_SPECIAL;
353: term = ']';
354: break;
355: case ('c'):
356: *d = DECO_NOSPACE;
357: return(i);
1.8 schwarze 358: case ('z'):
359: *d = DECO_NONE;
360: if ('\\' == wp[i]) {
361: *word = &wp[++i];
362: return(i + a2roffdeco(&dd, word, sz));
363: } else
364: lim = 1;
365: break;
366: case ('o'):
367: /* FALLTHROUGH */
368: case ('w'):
369: if ('\'' == wp[i++]) {
370: term = '\'';
371: break;
372: }
373: /* FALLTHROUGH */
1.6 schwarze 374: default:
375: *d = DECO_SSPECIAL;
376: i--;
377: lim = 1;
378: break;
379: }
1.3 schwarze 380:
1.6 schwarze 381: assert(term || lim);
382: *word = &wp[i];
1.3 schwarze 383:
1.6 schwarze 384: if (term) {
385: j = i;
386: while (wp[i] && wp[i] != term)
387: i++;
388: if ('\0' == wp[i]) {
389: *d = DECO_NONE;
390: return(i);
1.3 schwarze 391: }
392:
1.6 schwarze 393: assert(i >= j);
394: *sz = (size_t)(i - j);
1.3 schwarze 395:
1.6 schwarze 396: return(i + 1);
397: }
1.3 schwarze 398:
1.6 schwarze 399: assert(lim > 0);
400: *sz = (size_t)lim;
1.3 schwarze 401:
1.6 schwarze 402: for (j = 0; wp[i] && j < lim; j++)
403: i++;
404: if (j < lim)
405: *d = DECO_NONE;
1.3 schwarze 406:
1.6 schwarze 407: return(i);
1.3 schwarze 408: }
1.9 schwarze 409:
410: /*
411: * Calculate the abstract widths and decimal positions of columns in a
412: * table. This routine allocates the columns structures then runs over
413: * all rows and cells in the table. The function pointers in "tbl" are
414: * used for the actual width calculations.
415: */
416: void
417: tblcalc(struct rofftbl *tbl, const struct tbl_span *sp)
418: {
419: const struct tbl_dat *dp;
420: const struct tbl_head *hp;
421: struct roffcol *col;
422:
423: /*
424: * Allocate the master column specifiers. These will hold the
425: * widths and decimal positions for all cells in the column. It
426: * must be freed and nullified by the caller.
427: */
428:
429: assert(NULL == tbl->cols);
430: tbl->cols = calloc(sp->tbl->cols, sizeof(struct roffcol));
431:
432: hp = sp->head;
433:
434: for ( ; sp; sp = sp->next) {
435: if (TBL_SPAN_DATA != sp->pos)
436: continue;
437: /*
438: * Account for the data cells in the layout, matching it
439: * to data cells in the data section.
440: */
441: for (dp = sp->first; dp; dp = dp->next) {
1.10 schwarze 442: assert(dp->layout);
1.9 schwarze 443: col = &tbl->cols[dp->layout->head->ident];
444: tblcalc_data(tbl, col, sp->tbl, dp);
445: }
446: }
447:
448: /*
449: * Calculate width of the spanners. These get one space for a
450: * vertical line, two for a double-vertical line.
451: */
452:
453: for ( ; hp; hp = hp->next) {
454: col = &tbl->cols[hp->ident];
455: switch (hp->pos) {
456: case (TBL_HEAD_VERT):
457: col->width = (*tbl->len)(1, tbl->arg);
458: break;
459: case (TBL_HEAD_DVERT):
460: col->width = (*tbl->len)(2, tbl->arg);
461: break;
462: default:
463: break;
464: }
465: }
466: }
467:
468: static void
469: tblcalc_data(struct rofftbl *tbl, struct roffcol *col,
470: const struct tbl *tp, const struct tbl_dat *dp)
471: {
472: size_t sz;
473:
474: /* Branch down into data sub-types. */
475:
476: switch (dp->layout->pos) {
477: case (TBL_CELL_HORIZ):
478: /* FALLTHROUGH */
479: case (TBL_CELL_DHORIZ):
480: sz = (*tbl->len)(1, tbl->arg);
481: if (col->width < sz)
482: col->width = sz;
483: break;
484: case (TBL_CELL_LONG):
485: /* FALLTHROUGH */
486: case (TBL_CELL_CENTRE):
487: /* FALLTHROUGH */
488: case (TBL_CELL_LEFT):
489: /* FALLTHROUGH */
490: case (TBL_CELL_RIGHT):
491: tblcalc_literal(tbl, col, dp);
492: break;
493: case (TBL_CELL_NUMBER):
494: tblcalc_number(tbl, col, tp, dp);
495: break;
1.10 schwarze 496: case (TBL_CELL_DOWN):
497: break;
1.9 schwarze 498: default:
499: abort();
500: /* NOTREACHED */
501: }
502: }
503:
504: static void
505: tblcalc_literal(struct rofftbl *tbl, struct roffcol *col,
506: const struct tbl_dat *dp)
507: {
508: size_t sz, bufsz, spsz;
1.10 schwarze 509: const char *str;
1.9 schwarze 510:
511: /*
512: * Calculate our width and use the spacing, with a minimum
513: * spacing dictated by position (centre, e.g,. gets a space on
514: * either side, while right/left get a single adjacent space).
515: */
516:
1.10 schwarze 517: bufsz = spsz = 0;
518: str = dp->string ? dp->string : "";
519: sz = (*tbl->slen)(str, tbl->arg);
520:
521: /* FIXME: TBL_DATA_HORIZ et al.? */
1.9 schwarze 522:
523: assert(dp->layout);
524: switch (dp->layout->pos) {
525: case (TBL_CELL_LONG):
526: /* FALLTHROUGH */
527: case (TBL_CELL_CENTRE):
1.11 schwarze 528: bufsz = (*tbl->len)(1, tbl->arg);
1.9 schwarze 529: break;
530: default:
531: bufsz = (*tbl->len)(1, tbl->arg);
532: break;
533: }
534:
535: if (dp->layout->spacing) {
536: spsz = (*tbl->len)(dp->layout->spacing, tbl->arg);
537: bufsz = bufsz > spsz ? bufsz : spsz;
538: }
539:
540: sz += bufsz;
541: if (col->width < sz)
542: col->width = sz;
543: }
544:
545: static void
546: tblcalc_number(struct rofftbl *tbl, struct roffcol *col,
547: const struct tbl *tp, const struct tbl_dat *dp)
548: {
549: int i;
1.10 schwarze 550: size_t sz, psz, ssz, d;
551: const char *str;
1.9 schwarze 552: char *cp;
553: char buf[2];
554:
555: /*
556: * First calculate number width and decimal place (last + 1 for
557: * no-decimal numbers). If the stored decimal is subsequent
558: * ours, make our size longer by that difference
559: * (right-"shifting"); similarly, if ours is subsequent the
560: * stored, then extend the stored size by the difference.
561: * Finally, re-assign the stored values.
562: */
563:
1.10 schwarze 564: str = dp->string ? dp->string : "";
565: sz = (*tbl->slen)(str, tbl->arg);
1.9 schwarze 566:
1.10 schwarze 567: /* FIXME: TBL_DATA_HORIZ et al.? */
1.9 schwarze 568:
569: buf[0] = tp->decimal;
570: buf[1] = '\0';
571:
572: psz = (*tbl->slen)(buf, tbl->arg);
573:
574: if (NULL != (cp = strrchr(str, tp->decimal))) {
575: buf[1] = '\0';
576: for (ssz = 0, i = 0; cp != &str[i]; i++) {
577: buf[0] = str[i];
578: ssz += (*tbl->slen)(buf, tbl->arg);
579: }
580: d = ssz + psz;
581: } else
582: d = sz + psz;
583:
584: /* Padding. */
585:
586: sz += (*tbl->len)(2, tbl->arg);
587: d += (*tbl->len)(1, tbl->arg);
588:
589: /* Adjust the settings for this column. */
590:
591: if (col->decimal > d) {
592: sz += col->decimal - d;
593: d = col->decimal;
594: } else
595: col->width += d - col->decimal;
596:
597: if (sz > col->width)
598: col->width = sz;
599: if (d > col->decimal)
600: col->decimal = d;
601:
602: /* Adjust for stipulated width. */
603:
1.10 schwarze 604: if (col->width < dp->layout->spacing)
605: col->width = dp->layout->spacing;
1.9 schwarze 606: }
607:
608: