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