[BACK]Return to tbl_term.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mandoc

File: [local] / src / usr.bin / mandoc / tbl_term.c (download)

Revision 1.3, Fri Oct 15 22:07:12 2010 UTC (13 years, 7 months ago) by schwarze
Branch: MAIN
Changes since 1.2: +25 -26 lines

Move tbl width calculation from plain strlen to mandoc terminal width
calculation routines.  This gives us mostly sane table column widths.
"move forward" deraadt@

/*	$Id: tbl_term.c,v 1.3 2010/10/15 22:07:12 schwarze Exp $ */
/*
 * Copyright (c) 2009 Kristaps Dzonsons <kristaps@kth.se>
 * Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/queue.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "out.h"
#include "term.h"
#include "tbl_extern.h"

/* FIXME: `n' modifier doesn't always do the right thing. */
/* FIXME: `n' modifier doesn't use the cell-spacing buffer. */

static	void		 calc_data(struct termp *, struct tbl_data *);
static	void		 calc_data_literal(struct termp *, struct tbl_data *);
static	void		 calc_data_number(struct termp *, struct tbl_data *);
static	void		 calc_data_spanner(struct termp *, struct tbl_data *);
static	inline void	 write_char(struct termp *, char, int);
static	void		 write_data(struct termp *,
				const struct tbl_data *, int);
static	void		 write_data_literal(struct termp *,
				const struct tbl_data *, int);
static	void		 write_data_number(struct termp *,
				const struct tbl_data *, int);
static	void		 write_data_spanner(struct termp *,
				const struct tbl_data *, int);
static	void		 write_hframe(struct termp *, const struct tbl *);
static	void		 write_hrule(struct termp *, const struct tbl_span *);
static	void		 write_spanner(struct termp *, const struct tbl_head *);
static	void		 write_vframe(struct termp *, const struct tbl *);


int
tbl_write_term(struct termp *p, const struct tbl *tbl)
{
	const struct tbl_span	*span;
	const struct tbl_data	*data;
	const struct tbl_head	*head;

	/*
	 * Note that the absolute widths and decimal places for headers
	 * were set when tbl_calc_term was called.
	 */

	term_newln(p);
	p->flags |= TERMP_NOSPACE | TERMP_NONOSPACE;

	/* First, write out our head horizontal frame. */

	write_hframe(p, tbl);

	/*
	 * Iterate through each span, and inside, through the global
	 * headers.  If the global header's a spanner, print it
	 * directly; if it's data, use the corresponding data in the
	 * span as the object to print.
	 */

	TAILQ_FOREACH(span, &tbl->span, entries) {
		write_vframe(p, tbl);

		/* Accomodate for the horizontal rule. */
		if (TBL_DATA_DHORIZ & span->flags || 
				TBL_DATA_HORIZ & span->flags) {
			write_hrule(p, span);
			write_vframe(p, tbl);
			term_flushln(p);
			continue;
		}

		data = TAILQ_FIRST(&span->data);
		TAILQ_FOREACH(head, &tbl->head, entries) {
			switch (head->pos) {
			case (TBL_HEAD_VERT):
				/* FALLTHROUGH */
			case (TBL_HEAD_DVERT):
				write_spanner(p, head);
				break;
			case (TBL_HEAD_DATA):
				write_data(p, data, head->width);
				if (data)
					data = TAILQ_NEXT(data, entries);
				break;
			default:
				abort();
				/* NOTREACHED */
			}
		}
		write_vframe(p, tbl);
		term_flushln(p);
	}

	/* Last, write out our tail horizontal frame. */

	write_hframe(p, tbl);

	p->flags &= ~TERMP_NONOSPACE;

	return(1);
}


int
tbl_calc_term(struct termp *p, struct tbl *tbl)
{
	struct tbl_span	*span;
	struct tbl_data	*data;
	struct tbl_head	*head;

	/* Calculate width as the max of column cells' widths. */

	TAILQ_FOREACH(span, &tbl->span, entries) {
		if (TBL_DATA_HORIZ & span->flags)
			continue;
		if (TBL_DATA_DHORIZ & span->flags)
			continue;
		if (TBL_DATA_NHORIZ & span->flags)
			continue;
		if (TBL_DATA_NDHORIZ & span->flags)
			continue;
		TAILQ_FOREACH(data, &span->data, entries)
			calc_data(p, data);
	}

	/* Calculate width as the simple spanner value. */

	TAILQ_FOREACH(head, &tbl->head, entries) 
		switch (head->pos) {
		case (TBL_HEAD_VERT):
			head->width = term_len(p, 1);
			break;
		case (TBL_HEAD_DVERT):
			head->width = term_len(p, 2);
			break;
		default:
			break;
		}

	return(1);
}


static void
write_hrule(struct termp *p, const struct tbl_span *span)
{
	const struct tbl_head	*head;
	char			 c;

	/*
	 * An hrule extends across the entire table and is demarked by a
	 * standalone `_' or whatnot in lieu of a table row.  Spanning
	 * headers are marked by a `+', as are table boundaries.
	 */

	c = '-';
	if (TBL_SPAN_DHORIZ & span->flags)
		c = '=';

	/* FIXME: don't use `+' between data and a spanner! */

	TAILQ_FOREACH(head, &span->tbl->head, entries) {
		switch (head->pos) {
		case (TBL_HEAD_DATA):
			write_char(p, c, head->width);
			break;
		case (TBL_HEAD_DVERT):
			write_char(p, '+', head->width);
			/* FALLTHROUGH */
		case (TBL_HEAD_VERT):
			write_char(p, '+', head->width);
			break;
		default:
			abort();
			/* NOTREACHED */
		}
	}
}


static void
write_hframe(struct termp *p, const struct tbl *tbl)
{
	const struct tbl_head	*head;

	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
		return;

	/* 
	 * Print out the horizontal part of a frame or double frame.  A
	 * double frame has an unbroken `-' outer line the width of the
	 * table, bordered by `+'.  The frame (or inner frame, in the
	 * case of the double frame) is a `-' bordered by `+' and broken
	 * by `+' whenever a span is encountered.
	 */

	if (TBL_OPT_DBOX & tbl->opts) {
		term_word(p, "+");
		TAILQ_FOREACH(head, &tbl->head, entries)
			write_char(p, '-', head->width);
		term_word(p, "+");
		term_flushln(p);
	}

	term_word(p, "+");
	TAILQ_FOREACH(head, &tbl->head, entries) {
		switch (head->pos) {
		case (TBL_HEAD_DATA):
			write_char(p, '-', head->width);
			break;
		default:
			write_char(p, '+', head->width);
			break;
		}
	}
	term_word(p, "+");
	term_flushln(p);
}


static void
write_vframe(struct termp *p, const struct tbl *tbl)
{
	/* Always just a single vertical line. */

	if ( ! (TBL_OPT_BOX & tbl->opts || TBL_OPT_DBOX & tbl->opts))
		return;
	term_word(p, "|");
}


static void
calc_data_spanner(struct termp *p, struct tbl_data *data)
{

	/* N.B., these are horiz spanners (not vert) so always 1. */
	data->cell->head->width = term_len(p, 1);
}


static void
calc_data_number(struct termp *p, struct tbl_data *data)
{
	int 		 sz, d;
	char		*dp, pnt;

	/*
	 * First calculate number width and decimal place (last + 1 for
	 * no-decimal numbers).  If the stored decimal is subsequent
	 * ours, make our size longer by that difference
	 * (right-"shifting"); similarly, if ours is subsequent the
	 * stored, then extend the stored size by the difference.
	 * Finally, re-assign the stored values.
	 */

	/* TODO: use spacing modifier. */

	assert(data->string);
	sz = (int)term_strlen(p, data->string);
	pnt = data->span->tbl->decimal;

	dp = strchr(data->string, pnt);
	d = dp ? sz - (int)term_strlen(p, dp) : sz;
	d += term_len(p, 1);

	sz += term_len(p, 2);

	if (data->cell->head->decimal > d) {
		sz += data->cell->head->decimal - d;
		d = data->cell->head->decimal;
	} else
		data->cell->head->width += 
			d - data->cell->head->decimal;

	if (sz > data->cell->head->width)
		data->cell->head->width = sz;
	if (d > data->cell->head->decimal)
		data->cell->head->decimal = d;
}


static void
calc_data_literal(struct termp *p, struct tbl_data *data)
{
	int		 sz, bufsz;

	/* 
	 * Calculate our width and use the spacing, with a minimum
	 * spacing dictated by position (centre, e.g,. gets a space on
	 * either side, while right/left get a single adjacent space).
	 */

	assert(data->string);
	sz = (int)term_strlen(p, data->string);

	switch (data->cell->pos) {
	case (TBL_CELL_LONG):
		/* FALLTHROUGH */
	case (TBL_CELL_CENTRE):
		bufsz = 2;
		break;
	default:
		bufsz = 1;
		break;
	}

	if (data->cell->spacing)
		bufsz = bufsz > data->cell->spacing ? 
			bufsz : data->cell->spacing;

	sz += term_len(p, bufsz);
	if (data->cell->head->width < sz)
		data->cell->head->width = sz;
}


static void
calc_data(struct termp *p, struct tbl_data *data)
{

	switch (data->cell->pos) {
	case (TBL_CELL_HORIZ):
		/* FALLTHROUGH */
	case (TBL_CELL_DHORIZ):
		calc_data_spanner(p, data);
		break;
	case (TBL_CELL_LONG):
		/* FALLTHROUGH */
	case (TBL_CELL_CENTRE):
		/* FALLTHROUGH */
	case (TBL_CELL_LEFT):
		/* FALLTHROUGH */
	case (TBL_CELL_RIGHT):
		calc_data_literal(p, data);
		break;
	case (TBL_CELL_NUMBER):
		calc_data_number(p, data);
		break;
	default:
		abort();
		/* NOTREACHED */
	}
}


static void
write_data_spanner(struct termp *p, const struct tbl_data *data, int width)
{

	/*
	 * Write spanners dictated by both our cell designation (in the
	 * layout) or as data.
	 */
	if (TBL_DATA_HORIZ & data->flags)
		write_char(p, '-', width);
	else if (TBL_DATA_DHORIZ & data->flags)
		write_char(p, '=', width);
	else if (TBL_CELL_HORIZ == data->cell->pos)
		write_char(p, '-', width);
	else if (TBL_CELL_DHORIZ == data->cell->pos)
		write_char(p, '=', width);
}


static void
write_data_number(struct termp *p, const struct tbl_data *data, int width)
{
	char		*dp, pnt;
	int		 d, padl, sz;

	/*
	 * See calc_data_number().  Left-pad by taking the offset of our
	 * and the maximum decimal; right-pad by the remaining amount.
	 */

	sz = (int)term_strlen(p, data->string);
	pnt = data->span->tbl->decimal;

	if (NULL == (dp = strchr(data->string, pnt))) {
		d = sz + 1;
	} else {
		d = (int)(dp - data->string) + 1;
	}

	assert(d <= data->cell->head->decimal);
	assert(sz - d <= data->cell->head->width -
			data->cell->head->decimal);

	padl = data->cell->head->decimal - d + 1;
	assert(width - sz - padl);

	write_char(p, ' ', padl);
	term_word(p, data->string);
	write_char(p, ' ', width - sz - padl);
}


static void
write_data_literal(struct termp *p, const struct tbl_data *data, int width)
{
	int		 padl, padr;

	padl = padr = 0;

	switch (data->cell->pos) {
	case (TBL_CELL_LONG):
		padl = 1;
		padr = width - (int)term_strlen(p, data->string) - 1;
		break;
	case (TBL_CELL_CENTRE):
		padl = width - (int)term_strlen(p, data->string);
		if (padl % 2)
			padr++;
		padl /= 2;
		padr += padl;
		break;
	case (TBL_CELL_RIGHT):
		padl = width - (int)term_strlen(p, data->string);
		break;
	default:
		padr = width - (int)term_strlen(p, data->string);
		break;
	}

	write_char(p, ' ', padl);
	term_word(p, data->string);
	write_char(p, ' ', padr);
}


static void
write_data(struct termp *p, const struct tbl_data *data, int width)
{

	if (NULL == data) {
		write_char(p, ' ', width);
		return;
	}

	if (TBL_DATA_HORIZ & data->flags || 
			TBL_DATA_DHORIZ & data->flags) {
		write_data_spanner(p, data, width);
		return;
	}

	switch (data->cell->pos) {
	case (TBL_CELL_HORIZ):
		/* FALLTHROUGH */
	case (TBL_CELL_DHORIZ):
		write_data_spanner(p, data, width);
		break;
	case (TBL_CELL_LONG):
		/* FALLTHROUGH */
	case (TBL_CELL_CENTRE):
		/* FALLTHROUGH */
	case (TBL_CELL_LEFT):
		/* FALLTHROUGH */
	case (TBL_CELL_RIGHT):
		write_data_literal(p, data, width);
		break;
	case (TBL_CELL_NUMBER):
		write_data_number(p, data, width);
		break;
	default:
		abort();
		/* NOTREACHED */
	}
}


static void
write_spanner(struct termp *p, const struct tbl_head *head)
{
	char		*w;

	w = NULL;
	switch (head->pos) {
	case (TBL_HEAD_VERT):
		w = "|";
		break;
	case (TBL_HEAD_DVERT):
		w = "||";
		break;
	default:
		break;
	}

	assert(p);
	term_word(p, w);
}


static inline void
write_char(struct termp *p, char c, int len)
{
	int		 i;
	static char	 w[2];

	w[0] = c;
	for (i = 0; i < len; i++)
		term_word(p, w);
}