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