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