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