1 /*
2  *  Project   : tin - a Usenet reader
3  *  Module    : color.c
4  *  Original  : Olaf Kaluza <olaf@criseis.ruhr.de>
5  *  Author    : Roland Rosenfeld <roland@spinnaker.rhein.de>
6  *              Giuseppe De Marco <gdm@rebel.net> (light-colors)
7  *              Julien Oster <fuzzy@cu8.cum.de> (word highlighting)
8  *              T.Dickey <dickey@invisible-island.net> (curses support)
9  *  Created   : 1995-06-02
10  *  Updated   : 2015-10-09
11  *  Notes     : This are the basic function for ansi-color
12  *              and word highlighting
13  *
14  * Copyright (c) 1995-2021 Roland Rosenfeld <roland@spinnaker.rhein.de>
15  * All rights reserved.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  *
21  * 1. Redistributions of source code must retain the above copyright notice,
22  *    this list of conditions and the following disclaimer.
23  *
24  * 2. Redistributions in binary form must reproduce the above copyright
25  *    notice, this list of conditions and the following disclaimer in the
26  *    documentation and/or other materials provided with the distribution.
27  *
28  * 3. Neither the name of the copyright holder nor the names of its
29  *    contributors may be used to endorse or promote products derived from
30  *    this software without specific prior written permission.
31  *
32  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
33  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
36  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42  * POSSIBILITY OF SUCH DAMAGE.
43  */
44 
45 #ifndef TIN_H
46 #	include "tin.h"
47 #endif /* !TIN_H */
48 #ifndef TCURSES_H
49 #	include "tcurses.h"
50 #endif /* !TCURSES_H */
51 #ifndef included_trace_h
52 #	include "trace.h"
53 #endif /* !included_trace_h */
54 
55 #ifdef HAVE_COLOR
56 
57 #	define MIN_COLOR -1	/* -1 is default, otherwise 0-7 or 0-15 */
58 
59 int default_fcol = 7;
60 int default_bcol = 0;
61 
62 #	ifdef USE_CURSES
63 	static int current_fcol = 7;
64 	static struct LIST {
65 		struct LIST *link;
66 		int pair;
67 		int fg;
68 		int bg;
69 	} *list;
70 #	endif /* USE_CURSES */
71 static int current_bcol = 0;
72 
73 
74 /*
75  * local prototypes
76  */
77 #	ifdef USE_CURSES
78 	static void set_colors(int fcolor, int bcolor);
79 #	endif /* USE_CURSES */
80 
81 
82 #	ifdef USE_CURSES
83 static void
set_colors(int fcolor,int bcolor)84 set_colors(
85 	int fcolor,
86 	int bcolor)
87 {
88 	static int nextpair;
89 
90 #		ifndef HAVE_USE_DEFAULT_COLORS
91 	if (fcolor < 0)
92 		fcolor = default_fcol;
93 	if (bcolor < 0)
94 		bcolor = default_bcol;
95 #		endif /* !HAVE_USE_DEFAULT_COLORS */
96 	if (cmd_line || !use_color || !has_colors()) {
97 		current_fcol = default_fcol;
98 		current_bcol = default_bcol;
99 	} else if (COLORS > 1 && COLOR_PAIRS > 1) {
100 		chtype attribute = A_NORMAL;
101 		int pair = 0;
102 
103 		TRACE(("set_colors(%d, %d)", fcolor, bcolor));
104 
105 		/*
106 		 * fcolor/bcolor may be negative, if we're using ncurses
107 		 * function use_default_colors().
108 		 */
109 		if (fcolor > COLORS - 1) {
110 			attribute |= A_BOLD;
111 			fcolor %= COLORS;
112 		}
113 		if (bcolor > 0)
114 			bcolor %= COLORS;
115 
116 		if (fcolor != default_fcol || bcolor != default_bcol) {
117 			struct LIST *p;
118 			t_bool found = FALSE;
119 
120 			for (p = list; p != NULL; p = p->link) {
121 				if (p->fg == fcolor && p->bg == bcolor) {
122 					found = TRUE;
123 					break;
124 				}
125 			}
126 			if (found) {
127 				pair = p->pair;
128 			} else if (++nextpair < COLOR_PAIRS) {
129 				p = my_malloc(sizeof(struct LIST));
130 				p->fg = fcolor;
131 				p->bg = bcolor;
132 				p->pair = pair = nextpair;
133 				p->link = list;
134 				list = p;
135 				init_pair(pair, fcolor, bcolor);
136 			} else {
137 				pair = 0;
138 			}
139 		}
140 
141 		bkgdset(attribute | COLOR_PAIR(pair) | ' ');
142 	}
143 }
144 
145 
146 void
refresh_color(void)147 refresh_color(
148 	void)
149 {
150 	set_colors(current_fcol, current_bcol);
151 }
152 
153 
154 void
reset_color(void)155 reset_color(
156 	void)
157 {
158 	current_fcol = default_fcol;
159 	current_bcol = default_bcol;
160 	refresh_color();
161 }
162 
163 
164 void
free_color_pair_arrays(void)165 free_color_pair_arrays(
166 	void)
167 {
168 	struct LIST *p, *q;
169 
170 	for (p = list; p != NULL; p = q) {
171 		q = p->link;
172 		free(p);
173 	}
174 }
175 #	endif /* USE_CURSES */
176 
177 
178 /* setting foreground-color */
179 void
fcol(int color)180 fcol(
181 	int color)
182 {
183 	if (color < MIN_COLOR || color > MAX_COLOR)
184 		color = default_fcol;
185 
186 	TRACE(("fcol(%d) %s", color, txt_colors[color - MIN_COLOR]));
187 
188 	if (use_color) {
189 #	ifdef USE_CURSES
190 		set_colors(color, current_bcol);
191 		current_fcol = color;
192 #	else
193 		int bold;
194 
195 		if (cmd_line)
196 			return;
197 		if (color < 0)
198 			color = default_fcol;
199 		bold = (color >> 3); /* bitwise operation on signed value? ouch */
200 		my_printf("\033[%d;%dm", bold, ((color & 7) + 30));
201 		if (!bold)
202 			bcol(current_bcol);
203 #	endif /* USE_CURSES */
204 	}
205 #	ifdef USE_CURSES
206 	else
207 		set_colors(default_fcol, default_bcol);
208 #	endif /* USE_CURSES */
209 }
210 
211 
212 /* setting background-color */
213 void
bcol(int color)214 bcol(
215 	int color)
216 {
217 	if (color < MIN_COLOR || color > MAX_COLOR)
218 		color = default_bcol;
219 
220 	TRACE(("bcol(%d) %s", color, txt_colors[color - MIN_COLOR]));
221 
222 	if (use_color) {
223 #	ifdef USE_CURSES
224 		set_colors(current_fcol, color);
225 #	else
226 		if (color < 0)
227 			color = default_bcol;
228 		my_printf("\033[%dm", (color + 40));
229 #	endif /* USE_CURSES */
230 		current_bcol = color;
231 	}
232 #	ifdef USE_CURSES
233 	else
234 		set_colors(default_fcol, default_bcol);
235 #	endif /* USE_CURSES */
236 }
237 #endif /* HAVE_COLOR */
238 
239 
240 /*
241  * Output a line of text to the screen with colour if needed
242  * word highlights, signatures etc will be highlighted
243  */
244 void
draw_pager_line(const char * str,int flags,t_bool raw_data)245 draw_pager_line(
246 	const char *str,
247 	int flags,
248 	t_bool raw_data)
249 {
250 #ifdef HAVE_COLOR
251 	if (use_color) {
252 		if (flags & C_SIG) {
253 			fcol(tinrc.col_signature);
254 		} else if (flags & (C_HEADER | C_ATTACH | C_UUE)) {
255 			fcol(tinrc.col_newsheaders);
256 		} else { /* keep order in sync with cook.c:process_text_body_part() */
257 			if (flags & C_VERBATIM) {
258 				fcol(tinrc.col_verbatim);
259 			} else if (flags & C_QUOTE3) {
260 				fcol(tinrc.col_quote3);
261 			} else if (flags & C_QUOTE2) {
262 				fcol(tinrc.col_quote2);
263 			} else if (flags & C_EXTQUOTE) {
264 				fcol(tinrc.col_extquote);
265 			} else if (flags & C_QUOTE1) {
266 				fcol(tinrc.col_quote);
267 			} else
268 				fcol(tinrc.col_text);
269 		}
270 	}
271 #endif /* HAVE_COLOR */
272 
273 	if (!raw_data) {
274 #if defined(HAVE_LIBICUUC) && defined(MULTIBYTE_ABLE) && defined(HAVE_UNICODE_UBIDI_H) && !defined(NO_LOCALE)
275 		/*
276 		 * BiDi support
277 		 */
278 		/* don't run it on empty lines and lines containing only one char (which must be an ASCII one) */
279 		if (tinrc.render_bidi && IS_LOCAL_CHARSET("UTF-8") && strlen(str) > 1) {
280 			char *line;
281 			t_bool is_rtl;
282 
283 			if ((line = render_bidi(str, &is_rtl)) != NULL) {
284 				if (is_rtl) { /* RTL */
285 					/* determine visual length and pad out so that line is right-aligned */
286 					wchar_t *wline;
287 
288 					if ((wline = char2wchar_t(line)) != NULL) {
289 						int visual_len;
290 
291 						wconvert_to_printable(wline, FALSE);
292 						visual_len = wcswidth(wline, wcslen(wline) + 1);
293 						free(wline);
294 
295 						if (visual_len > 0) {
296 							int i;
297 
298 							for (i = 0; i < cCOLS - visual_len - 1; i++)
299 								my_fputc(' ', stdout);
300 						}
301 						my_fputs(line, stdout);
302 					} else /* fallback */
303 						my_fputs(line, stdout);
304 
305 				} else	/* LTR */
306 					my_fputs(line, stdout);
307 
308 				free(line);
309 			} else
310 				my_fputs(str, stdout);
311 		} else
312 #endif /* HAVE_LIBICUUC && MULTIBYTE_ABLE && HAVE_UNICODE_UBIDI_H && !NO_LOCALE */
313 			my_fputs(str, stdout);
314 	} else {
315 		/* in RAW-mode (show_all_headers) display non-printable chars as octals */
316 		const char *c;
317 		char octal[5];
318 
319 		c = str;
320 		while (*c) {
321 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
322 			int num_bytes;
323 			wchar_t wc;
324 
325 			num_bytes = mbtowc(&wc, c, MB_CUR_MAX);
326 			if (num_bytes != -1 && iswprint(wc)) {
327 				my_fputwc((wint_t) wc, stdout);
328 				c += num_bytes;
329 			}
330 #else
331 			if (my_isprint((unsigned char) *c)) {
332 				my_fputc((int) *c, stdout);
333 				c++;
334 			}
335 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
336 			else if (IS_LOCAL_CHARSET("Big5") && (unsigned char) *c >= 0xa1 && (unsigned char) *c <= 0xfe && *(c + 1)) {
337 				/*
338 				 * Big5: ASCII chars are handled by the normal code
339 				 * check only for 2-byte chars
340 				 * TODO: should we also check if the second byte is also valid?
341 				 */
342 				my_fputc((int) *c, stdout);
343 				c++;
344 				my_fputc((int) *c, stdout);
345 				c++;
346 			} else {
347 				/*
348 				 * non-printable char
349 				 * print as an octal value
350 				 */
351 				snprintf(octal, sizeof(octal), "\\%03o", (unsigned int) (*c & 0xff));
352 				my_fputs(octal, stdout);
353 				c++;
354 			}
355 		}
356 	}
357 
358 #ifndef USE_CURSES
359 	my_fputs(cCRLF, stdout);
360 #endif /* !USE_CURSES */
361 }
362