1 /* Copyright (C) 2016-2017 Shengyu Zhang <i@silverrainz.me>
2  *
3  * This file is part of Srain.
4  *
5  * Srain is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <stdlib.h>
20 #include <glib.h>
21 #include <string.h>
22 
23 #include "srain.h"
24 #include "log.h"
25 #include "i18n.h"
26 #include "markup_renderer.h"
27 
28 #include "render/render.h"
29 #include "./renderer.h"
30 #include "./mirc.h"
31 
32 #define MAX_CTX_STACK_SIZE 128
33 
34 typedef struct _ColorlizeContext {
35     int stack[MAX_CTX_STACK_SIZE];
36     int ptr; // Stack pointer
37     unsigned fg_color;
38     unsigned bg_color;
39     GString *str;
40 } ColorlizeContext;
41 
42 static void init(void);
43 static void finalize(void);
44 static SrnRet render(SrnMessage *msg);
45 static void text(GMarkupParseContext *context, const gchar *text,
46         gsize text_len, gpointer user_data, GError **error);
47 static void do_colorize(ColorlizeContext *ctx, char ch);
48 
49 /**
50  * @brief mirc_strip_renderer is a render moduele for rendering mIRC color in
51  * message.
52  *
53  * ref: https://en.wikichip.org/wiki/irc/colors
54  */
55 SrnMessageRenderer mirc_colorize_renderer = {
56     .name = "mirc_colorize",
57     .init = init,
58     .finalize = finalize,
59     .render = render,
60 };
61 
62 // TODO: define in theme CSS?
63 static const char *color_map[] = {
64     [MIRC_COLOR_WHITE]          = "#FFFFFF",
65     [MIRC_COLOR_BLACK]          = "#000000",
66     [MIRC_COLOR_NAVY]           = "#00007F",
67     [MIRC_COLOR_GREEN]          = "#009300",
68     [MIRC_COLOR_RED]            = "#FF0000",
69     [MIRC_COLOR_MAROON]         = "#7F0000",
70     [MIRC_COLOR_PURPLE]         = "#9C009C",
71     [MIRC_COLOR_OLIVE]          = "#FC7F00",
72     [MIRC_COLOR_YELLOW]         = "#FFFF00",
73     [MIRC_COLOR_LIGHT_GREEN]    = "#00FC00",
74     [MIRC_COLOR_TEAL]           = "#009393",
75     [MIRC_COLOR_CYAN]           = "#00FFFF",
76     [MIRC_COLOR_ROYAL_BLUE]     = "#0000FC",
77     [MIRC_COLOR_MAGENTA]        = "#FF00FF",
78     [MIRC_COLOR_GRAY]           = "#7F7F7F",
79     [MIRC_COLOR_LIGHT_GRAY]     = "#D2D2D2",
80     [MIRC_COLOR_UNKNOWN]        = "", // Preventing out of bound
81 };
82 
83 static SrnMarkupRenderer *markup_renderer;
84 
init(void)85 void init(void) {
86     GMarkupParser *parser;
87 
88     markup_renderer = srn_markup_renderer_new();
89     parser = srn_markup_renderer_get_markup_parser(markup_renderer);
90     parser->text = text;
91 }
92 
finalize(void)93 void finalize(void) {
94     srn_markup_renderer_free(markup_renderer);
95 }
96 
render(SrnMessage * msg)97 SrnRet render(SrnMessage *msg) {
98     char *rendered_content;
99     SrnRet ret;
100 
101     rendered_content = NULL;
102     ret = srn_markup_renderer_render(markup_renderer,
103             msg->rendered_content, &rendered_content, msg);
104     if (!RET_IS_OK(ret)){
105         return RET_ERR(_("Failed to render markup text: %1$s"), RET_MSG(ret));
106     }
107     if (rendered_content) {
108         g_free(msg->rendered_content);
109         msg->rendered_content = rendered_content;
110     }
111 
112     return SRN_OK;
113 }
114 
text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)115 void text(GMarkupParseContext *context, const gchar *text,
116         gsize text_len, gpointer user_data, GError **error) {
117     ColorlizeContext *ctx;
118 
119     ctx = g_malloc0(sizeof(ColorlizeContext));
120     ctx->fg_color = MIRC_COLOR_UNKNOWN;
121     ctx->bg_color = MIRC_COLOR_UNKNOWN;
122     ctx->str = srn_markup_renderer_get_markup(user_data);
123 
124     for (int i = 0; i < text_len; i++){
125         switch (text[i]){
126             case MIRC_COLOR:
127                 {
128                     /* Format: "\30[fg_color],[bg_color]",
129                      * 0 <= length of fg_color or bg_color <= 2*/
130                     const char *startptr = &text[i] + 1;
131                     char *endptr = NULL;
132                     bool has_fg_color = FALSE;
133                     bool has_bg_color = FALSE;
134                     unsigned fg_color = strtoul(startptr, &endptr, 10);
135                     if (endptr > startptr){ // Get foreground color
136                         has_fg_color = TRUE;
137                         while (endptr - startptr > 2){ // Wrong number of digits
138                             fg_color = fg_color / 10;
139                             endptr--;
140                         }
141                         DBG_FR("Get foreground color: %u", fg_color);
142                         ctx->fg_color = fg_color;
143                     }
144                     i += endptr - startptr;
145                     if (*endptr == ',') { // background color exists
146                         endptr++;
147                         startptr = endptr;
148                         endptr = NULL;
149                         unsigned bg_color = strtoul(startptr, &endptr, 10);
150                         if (endptr > startptr){ // Get background color
151                             has_bg_color = TRUE;
152                             while (endptr - startptr > 2){ // Wrong number of digits
153                                 bg_color = bg_color / 10;
154                                 endptr--;
155                             }
156                             DBG_FR("Get background color: %u", bg_color);
157                             ctx->bg_color = bg_color;
158                         }
159                         i += endptr - startptr;
160                     }
161                     if (!has_fg_color && !has_bg_color) { // Clear previous color
162                         ctx->fg_color = MIRC_COLOR_UNKNOWN;
163                         ctx->bg_color = MIRC_COLOR_UNKNOWN;
164                     }
165                     do_colorize(ctx, MIRC_COLOR);
166                     break;
167                 }
168             case MIRC_BOLD:
169             case MIRC_ITALICS:
170             case MIRC_UNDERLINE:
171             case MIRC_BLINK:
172             case MIRC_REVERSE:
173             case MIRC_PLAIN:
174                 do_colorize(ctx, text[i]);
175                 break;
176             default:
177                 {
178                     // No control character, it is a utf-8 sequence
179                     const char *next = g_utf8_next_char(&text[i]);
180                     char *escape = g_markup_escape_text(&text[i], next - &text[i]);
181                     g_string_append(ctx->str, escape);
182                     g_free(escape);
183                     i += next - &text[i] - 1;
184                     break;
185                 }
186         }
187     }
188 
189     // Close all unclosed tags
190     do_colorize(ctx, MIRC_PLAIN);
191     g_free(ctx);
192 }
193 
do_colorize(ColorlizeContext * ctx,char ch)194 static void do_colorize(ColorlizeContext *ctx, char ch){
195     int ptr;
196     bool open_tag = TRUE;
197 
198     ptr = ctx->ptr - 1;
199 
200     if (ch == MIRC_PLAIN){
201         DBG_FR("Reset all format");
202         // Reset color
203         ctx->fg_color = MIRC_COLOR_UNKNOWN;
204         ctx->bg_color = MIRC_COLOR_UNKNOWN;
205         // Clear stack
206         while (ptr >= 0){
207             do_colorize(ctx, ctx->stack[ptr]);
208             ptr--;
209         }
210         return;
211     }
212 
213     while (ptr >= 0){
214         if (ctx->stack[ptr] == ch){
215             open_tag = FALSE;
216             break;
217         }
218         ptr--;
219     }
220 
221     if (open_tag) {
222         DBG_FR("Opening tag: 0x%x", ch);
223 
224         ctx->stack[ctx->ptr++] = ch;
225         if (ctx->ptr >= MAX_CTX_STACK_SIZE){
226             ERR_FR("Too many tags in stack, abort");
227             return;
228         }
229 
230         switch (ch){
231             case MIRC_BOLD:
232                 g_string_append(ctx->str, "<b>");
233                 break;
234             case MIRC_ITALICS:
235                 g_string_append(ctx->str, "<i>");
236                 break;
237             case MIRC_UNDERLINE:
238                 g_string_append(ctx->str, "<u>");
239                 break;
240             case MIRC_REVERSE:
241                 // TODO: Not supported yet
242                 break;
243             case MIRC_BLINK:
244                 // TODO: Not supported yet
245                 break;
246             case MIRC_COLOR:
247                 if (ctx->fg_color > MIRC_COLOR_UNKNOWN){
248                     WARN_FR("Invalid mirc foreground color: %u", ctx->fg_color);
249                     ctx->fg_color = MIRC_COLOR_UNKNOWN;
250                 }
251                 if (ctx->bg_color > MIRC_COLOR_UNKNOWN){
252                     WARN_FR("Invalid mirc background color: %u", ctx->bg_color);
253                     ctx->bg_color = MIRC_COLOR_UNKNOWN;
254                 }
255                 if (ctx->fg_color == MIRC_COLOR_UNKNOWN) {
256                     if (ctx->bg_color == MIRC_COLOR_UNKNOWN) {
257                         ctx->ptr--; // Default color, tag can be omitted
258                     } else {
259                         g_string_append_printf(ctx->str,
260                                 "<span background=\"%s\">",
261                                 color_map[ctx->bg_color]);
262                     }
263                 } else {
264                     g_string_append_printf(ctx->str,
265                             "<span foreground=\"%s\">",
266                             color_map[ctx->fg_color]);
267                 }
268                 break;
269         }
270     } else {
271         DBG_FR("Closing tag: 0x%x", ch);
272 
273         // Closes all unclosed tags before the start tag corresponding to current tag
274         for (int i = ctx->ptr - 1; i >= ptr; i--){
275             DBG_FR("Tag: 0x%x will be closed because of 0x%x", ctx->stack[i], ch);
276             switch (ctx->stack[i]){
277                 case MIRC_BOLD:
278                     g_string_append(ctx->str, "</b>");
279                     break;
280                 case MIRC_ITALICS:
281                     g_string_append(ctx->str, "</i>");
282                     break;
283                 case MIRC_UNDERLINE:
284                     g_string_append(ctx->str, "</u>");
285                     break;
286                 case MIRC_REVERSE:
287                     // TODO: Not supported yet
288                     break;
289                 case MIRC_BLINK:
290                     // TODO: Not supported yet
291                     break;
292                 case MIRC_COLOR:
293                     g_string_append(ctx->str, "</span>");
294                     break;
295             }
296         }
297         int tmp_ptr = ctx->ptr;
298         ctx->ptr = ptr;
299         // Reopen the closed tag excepting the current tag
300         for (int i = ptr + 1; i < tmp_ptr; i++){
301             do_colorize(ctx, ctx->stack[i]);
302         }
303 
304         if (ch == MIRC_COLOR) {
305             if (ctx->fg_color == MIRC_COLOR_UNKNOWN && ctx->bg_color == MIRC_COLOR_UNKNOWN ){
306                 DBG_FR("Tag 0x%x closed with default {fore,back}ground color", ch);
307             } else {
308                 DBG_FR("Reopening tag 0x%x because foreground color = %u and background = %u",
309                         ch, ctx->fg_color, ctx->bg_color);
310                 // One or more of fg_color and bg_color is not default color,
311                 // we should open a new tag for showing them.
312                 do_colorize(ctx, MIRC_COLOR);
313             }
314         }
315     }
316 }
317