ul.c revision 146466
13070Spst/*
23070Spst * Copyright (c) 1980, 1993
33070Spst *	The Regents of the University of California.  All rights reserved.
43070Spst *
53070Spst * Redistribution and use in source and binary forms, with or without
63070Spst * modification, are permitted provided that the following conditions
73070Spst * are met:
83070Spst * 1. Redistributions of source code must retain the above copyright
93070Spst *    notice, this list of conditions and the following disclaimer.
103070Spst * 2. Redistributions in binary form must reproduce the above copyright
113070Spst *    notice, this list of conditions and the following disclaimer in the
123070Spst *    documentation and/or other materials provided with the distribution.
133070Spst * 3. All advertising materials mentioning features or use of this software
143070Spst *    must display the following acknowledgement:
153070Spst *	This product includes software developed by the University of
163070Spst *	California, Berkeley and its contributors.
173070Spst * 4. Neither the name of the University nor the names of its contributors
183070Spst *    may be used to endorse or promote products derived from this software
193070Spst *    without specific prior written permission.
203070Spst *
213070Spst * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
223070Spst * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
233070Spst * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
243070Spst * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
253070Spst * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
263070Spst * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
273070Spst * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
283070Spst * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
293070Spst * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
303070Spst * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
313070Spst * SUCH DAMAGE.
323070Spst */
333070Spst
343070Spst#ifndef lint
353070Spststatic const char copyright[] =
363070Spst"@(#) Copyright (c) 1980, 1993\n\
373070Spst	The Regents of the University of California.  All rights reserved.\n";
383070Spst#endif /* not lint */
393070Spst
403070Spst#ifndef lint
413070Spst#if 0
423070Spststatic char sccsid[] = "@(#)ul.c	8.1 (Berkeley) 6/6/93";
433070Spst#endif
443070Spststatic const char rcsid[] =
453070Spst  "$FreeBSD: head/usr.bin/ul/ul.c 146466 2005-05-21 09:55:10Z ru $";
463070Spst#endif /* not lint */
473070Spst
483070Spst#include <err.h>
493070Spst#include <locale.h>
503070Spst#include <stdio.h>
513070Spst#include <stdlib.h>
523070Spst#include <string.h>
533070Spst#include <termcap.h>
543070Spst#include <unistd.h>
553070Spst#include <wchar.h>
563070Spst#include <wctype.h>
573070Spst
583070Spst#define	IESC	'\033'
593070Spst#define	SO	'\016'
603070Spst#define	SI	'\017'
613070Spst#define	HFWD	'9'
623070Spst#define	HREV	'8'
633070Spst#define	FREV	'7'
643070Spst#define	MAXBUF	512
653070Spst
663070Spst#define	NORMAL	000
673070Spst#define	ALTSET	001	/* Reverse */
683070Spst#define	SUPERSC	002	/* Dim */
693070Spst#define	SUBSC	004	/* Dim | Ul */
703070Spst#define	UNDERL	010	/* Ul */
713070Spst#define	BOLD	020	/* Bold */
723070Spst
733070Spstint	must_use_uc, must_overstrike;
743070Spstconst char
753070Spst	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
763070Spst	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
773070Spst	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
783070Spst
793070Spststruct	CHAR	{
803070Spst	char	c_mode;
813070Spst	wchar_t	c_char;
823070Spst	int	c_width;	/* width or -1 if multi-column char. filler */
833070Spst} ;
843070Spst
853070Spststruct	CHAR	obuf[MAXBUF];
863070Spstint	col, maxcol;
873070Spstint	mode;
883070Spstint	halfpos;
893070Spstint	upln;
903070Spstint	iflag;
913070Spst
923070Spststatic void usage(void);
933070Spstvoid setnewmode(int);
943070Spstvoid initcap(void);
953070Spstvoid reverse(void);
963070Spstint outchar(int);
973070Spstvoid fwd(void);
983070Spstvoid initbuf(void);
993070Spstvoid iattr(void);
1003070Spstvoid overstrike(void);
1013070Spstvoid flushln(void);
1023070Spstvoid filter(FILE *);
1033070Spstvoid outc(wint_t, int);
1043070Spst
1053070Spst#define	PRINT(s)	if (s == NULL) /* void */; else tputs(s, 1, outchar)
1063070Spst
1073070Spstint
1083070Spstmain(int argc, char **argv)
1093070Spst{
1103070Spst	int c;
1113070Spst	const char *termtype;
1123070Spst	FILE *f;
1133070Spst	char termcap[1024];
1143070Spst
1153070Spst	setlocale(LC_ALL, "");
1163070Spst
1173070Spst	termtype = getenv("TERM");
1183070Spst	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
1193070Spst		termtype = "lpr";
1203070Spst	while ((c=getopt(argc, argv, "it:T:")) != -1)
1213070Spst		switch(c) {
1223070Spst
1233070Spst		case 't':
1243070Spst		case 'T': /* for nroff compatibility */
1253070Spst			termtype = optarg;
1263070Spst			break;
1273070Spst		case 'i':
1283070Spst			iflag = 1;
1293070Spst			break;
1303070Spst		default:
1313070Spst			usage();
1323070Spst		}
1333070Spst
1343070Spst	switch(tgetent(termcap, termtype)) {
1353070Spst
1363070Spst	case 1:
1373070Spst		break;
1383070Spst
1393070Spst	default:
1403070Spst		warnx("trouble reading termcap");
1413070Spst		/* FALLTHROUGH */
1423070Spst
1433070Spst	case 0:
1443070Spst		/* No such terminal type - assume dumb */
1453070Spst		(void)strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
1463070Spst		break;
1473070Spst	}
1483070Spst	initcap();
1493070Spst	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
1503070Spst		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
1513070Spst			must_overstrike = 1;
1523070Spst	initbuf();
1533070Spst	if (optind == argc)
1543070Spst		filter(stdin);
1553070Spst	else for (; optind<argc; optind++) {
1563070Spst		f = fopen(argv[optind],"r");
1573070Spst		if (f == NULL)
1583070Spst			err(1, "%s", argv[optind]);
1593070Spst		else
1603070Spst			filter(f);
1613070Spst	}
1623070Spst	exit(0);
1633070Spst}
1643070Spst
1653070Spststatic void
1663070Spstusage(void)
1673070Spst{
1683070Spst	fprintf(stderr, "usage: ul [-i] [-t terminal] [file ...]\n");
1693070Spst	exit(1);
1703070Spst}
1713070Spst
1723070Spstvoid
1733070Spstfilter(FILE *f)
1743070Spst{
1753070Spst	wint_t c;
1763070Spst	int i, w;
1773070Spst
1783070Spst	while ((c = getwc(f)) != WEOF && col < MAXBUF) switch(c) {
1793070Spst
1803070Spst	case '\b':
1813070Spst		if (col > 0)
1823070Spst			col--;
183		continue;
184
185	case '\t':
186		col = (col+8) & ~07;
187		if (col > maxcol)
188			maxcol = col;
189		continue;
190
191	case '\r':
192		col = 0;
193		continue;
194
195	case SO:
196		mode |= ALTSET;
197		continue;
198
199	case SI:
200		mode &= ~ALTSET;
201		continue;
202
203	case IESC:
204		switch (c = getwc(f)) {
205
206		case HREV:
207			if (halfpos == 0) {
208				mode |= SUPERSC;
209				halfpos--;
210			} else if (halfpos > 0) {
211				mode &= ~SUBSC;
212				halfpos--;
213			} else {
214				halfpos = 0;
215				reverse();
216			}
217			continue;
218
219		case HFWD:
220			if (halfpos == 0) {
221				mode |= SUBSC;
222				halfpos++;
223			} else if (halfpos < 0) {
224				mode &= ~SUPERSC;
225				halfpos++;
226			} else {
227				halfpos = 0;
228				fwd();
229			}
230			continue;
231
232		case FREV:
233			reverse();
234			continue;
235
236		default:
237			errx(1, "unknown escape sequence in input: %o, %o", IESC, c);
238		}
239		continue;
240
241	case '_':
242		if (obuf[col].c_char || obuf[col].c_width < 0) {
243			while (col > 0 && obuf[col].c_width < 0)
244				col--;
245			w = obuf[col].c_width;
246			for (i = 0; i < w; i++)
247				obuf[col++].c_mode |= UNDERL | mode;
248			if (col > maxcol)
249				maxcol = col;
250			continue;
251		}
252		obuf[col].c_char = '_';
253		obuf[col].c_width = 1;
254		/* FALLTHROUGH */
255	case ' ':
256		col++;
257		if (col > maxcol)
258			maxcol = col;
259		continue;
260
261	case '\n':
262		flushln();
263		continue;
264
265	case '\f':
266		flushln();
267		putwchar('\f');
268		continue;
269
270	default:
271		if ((w = wcwidth(c)) <= 0)	/* non printing */
272			continue;
273		if (obuf[col].c_char == '\0') {
274			obuf[col].c_char = c;
275			for (i = 0; i < w; i++)
276				obuf[col + i].c_mode = mode;
277			obuf[col].c_width = w;
278			for (i = 1; i < w; i++)
279				obuf[col + i].c_width = -1;
280		} else if (obuf[col].c_char == '_') {
281			obuf[col].c_char = c;
282			for (i = 0; i < w; i++)
283				obuf[col + i].c_mode |= UNDERL|mode;
284			obuf[col].c_width = w;
285			for (i = 1; i < w; i++)
286				obuf[col + i].c_width = -1;
287		} else if (obuf[col].c_char == c) {
288			for (i = 0; i < w; i++)
289				obuf[col + i].c_mode |= BOLD|mode;
290		} else {
291			w = obuf[col].c_width;
292			for (i = 0; i < w; i++)
293				obuf[col + i].c_mode = mode;
294		}
295		col += w;
296		if (col > maxcol)
297			maxcol = col;
298		continue;
299	}
300	if (ferror(f))
301		err(1, NULL);
302	if (maxcol)
303		flushln();
304}
305
306void
307flushln(void)
308{
309	int lastmode;
310	int i;
311	int hadmodes = 0;
312
313	lastmode = NORMAL;
314	for (i=0; i<maxcol; i++) {
315		if (obuf[i].c_mode != lastmode) {
316			hadmodes++;
317			setnewmode(obuf[i].c_mode);
318			lastmode = obuf[i].c_mode;
319		}
320		if (obuf[i].c_char == '\0') {
321			if (upln)
322				PRINT(CURS_RIGHT);
323			else
324				outc(' ', 1);
325		} else
326			outc(obuf[i].c_char, obuf[i].c_width);
327		if (obuf[i].c_width > 1)
328			i += obuf[i].c_width - 1;
329	}
330	if (lastmode != NORMAL) {
331		setnewmode(0);
332	}
333	if (must_overstrike && hadmodes)
334		overstrike();
335	putwchar('\n');
336	if (iflag && hadmodes)
337		iattr();
338	(void)fflush(stdout);
339	if (upln)
340		upln--;
341	initbuf();
342}
343
344/*
345 * For terminals that can overstrike, overstrike underlines and bolds.
346 * We don't do anything with halfline ups and downs, or Greek.
347 */
348void
349overstrike(void)
350{
351	int i;
352	wchar_t lbuf[256];
353	wchar_t *cp = lbuf;
354	int hadbold=0;
355
356	/* Set up overstrike buffer */
357	for (i=0; i<maxcol; i++)
358		switch (obuf[i].c_mode) {
359		case NORMAL:
360		default:
361			*cp++ = ' ';
362			break;
363		case UNDERL:
364			*cp++ = '_';
365			break;
366		case BOLD:
367			*cp++ = obuf[i].c_char;
368			if (obuf[i].c_width > 1)
369				i += obuf[i].c_width - 1;
370			hadbold=1;
371			break;
372		}
373	putwchar('\r');
374	for (*cp=' '; *cp==' '; cp--)
375		*cp = 0;
376	for (cp=lbuf; *cp; cp++)
377		putwchar(*cp);
378	if (hadbold) {
379		putwchar('\r');
380		for (cp=lbuf; *cp; cp++)
381			putwchar(*cp=='_' ? ' ' : *cp);
382		putwchar('\r');
383		for (cp=lbuf; *cp; cp++)
384			putwchar(*cp=='_' ? ' ' : *cp);
385	}
386}
387
388void
389iattr(void)
390{
391	int i;
392	wchar_t lbuf[256];
393	wchar_t *cp = lbuf;
394
395	for (i=0; i<maxcol; i++)
396		switch (obuf[i].c_mode) {
397		case NORMAL:	*cp++ = ' '; break;
398		case ALTSET:	*cp++ = 'g'; break;
399		case SUPERSC:	*cp++ = '^'; break;
400		case SUBSC:	*cp++ = 'v'; break;
401		case UNDERL:	*cp++ = '_'; break;
402		case BOLD:	*cp++ = '!'; break;
403		default:	*cp++ = 'X'; break;
404		}
405	for (*cp=' '; *cp==' '; cp--)
406		*cp = 0;
407	for (cp=lbuf; *cp; cp++)
408		putwchar(*cp);
409	putwchar('\n');
410}
411
412void
413initbuf(void)
414{
415
416	bzero((char *)obuf, sizeof (obuf));	/* depends on NORMAL == 0 */
417	col = 0;
418	maxcol = 0;
419	mode &= ALTSET;
420}
421
422void
423fwd(void)
424{
425	int oldcol, oldmax;
426
427	oldcol = col;
428	oldmax = maxcol;
429	flushln();
430	col = oldcol;
431	maxcol = oldmax;
432}
433
434void
435reverse(void)
436{
437	upln++;
438	fwd();
439	PRINT(CURS_UP);
440	PRINT(CURS_UP);
441	upln++;
442}
443
444void
445initcap(void)
446{
447	static char tcapbuf[512];
448	char *bp = tcapbuf;
449
450	/* This nonsense attempts to work with both old and new termcap */
451	CURS_UP =		tgetstr("up", &bp);
452	CURS_RIGHT =		tgetstr("ri", &bp);
453	if (CURS_RIGHT == NULL)
454		CURS_RIGHT =	tgetstr("nd", &bp);
455	CURS_LEFT =		tgetstr("le", &bp);
456	if (CURS_LEFT == NULL)
457		CURS_LEFT =	tgetstr("bc", &bp);
458	if (CURS_LEFT == NULL && tgetflag("bs"))
459		CURS_LEFT =	"\b";
460
461	ENTER_STANDOUT =	tgetstr("so", &bp);
462	EXIT_STANDOUT =		tgetstr("se", &bp);
463	ENTER_UNDERLINE =	tgetstr("us", &bp);
464	EXIT_UNDERLINE =	tgetstr("ue", &bp);
465	ENTER_DIM =		tgetstr("mh", &bp);
466	ENTER_BOLD =		tgetstr("md", &bp);
467	ENTER_REVERSE =		tgetstr("mr", &bp);
468	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
469
470	if (!ENTER_BOLD && ENTER_REVERSE)
471		ENTER_BOLD = ENTER_REVERSE;
472	if (!ENTER_BOLD && ENTER_STANDOUT)
473		ENTER_BOLD = ENTER_STANDOUT;
474	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
475		ENTER_UNDERLINE = ENTER_STANDOUT;
476		EXIT_UNDERLINE = EXIT_STANDOUT;
477	}
478	if (!ENTER_DIM && ENTER_STANDOUT)
479		ENTER_DIM = ENTER_STANDOUT;
480	if (!ENTER_REVERSE && ENTER_STANDOUT)
481		ENTER_REVERSE = ENTER_STANDOUT;
482	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
483		EXIT_ATTRIBUTES = EXIT_STANDOUT;
484
485	/*
486	 * Note that we use REVERSE for the alternate character set,
487	 * not the as/ae capabilities.  This is because we are modelling
488	 * the model 37 teletype (since that's what nroff outputs) and
489	 * the typical as/ae is more of a graphics set, not the greek
490	 * letters the 37 has.
491	 */
492
493	UNDER_CHAR =		tgetstr("uc", &bp);
494	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
495}
496
497int
498outchar(int c)
499{
500	return (putwchar(c) != WEOF ? c : EOF);
501}
502
503static int curmode = 0;
504
505void
506outc(wint_t c, int width)
507{
508	int i;
509
510	putwchar(c);
511	if (must_use_uc && (curmode&UNDERL)) {
512		for (i = 0; i < width; i++)
513			PRINT(CURS_LEFT);
514		for (i = 0; i < width; i++)
515			PRINT(UNDER_CHAR);
516	}
517}
518
519void
520setnewmode(int newmode)
521{
522	if (!iflag) {
523		if (curmode != NORMAL && newmode != NORMAL)
524			setnewmode(NORMAL);
525		switch (newmode) {
526		case NORMAL:
527			switch(curmode) {
528			case NORMAL:
529				break;
530			case UNDERL:
531				PRINT(EXIT_UNDERLINE);
532				break;
533			default:
534				/* This includes standout */
535				PRINT(EXIT_ATTRIBUTES);
536				break;
537			}
538			break;
539		case ALTSET:
540			PRINT(ENTER_REVERSE);
541			break;
542		case SUPERSC:
543			/*
544			 * This only works on a few terminals.
545			 * It should be fixed.
546			 */
547			PRINT(ENTER_UNDERLINE);
548			PRINT(ENTER_DIM);
549			break;
550		case SUBSC:
551			PRINT(ENTER_DIM);
552			break;
553		case UNDERL:
554			PRINT(ENTER_UNDERLINE);
555			break;
556		case BOLD:
557			PRINT(ENTER_BOLD);
558			break;
559		default:
560			/*
561			 * We should have some provision here for multiple modes
562			 * on at once.  This will have to come later.
563			 */
564			PRINT(ENTER_STANDOUT);
565			break;
566		}
567	}
568	curmode = newmode;
569}
570