NeoMutt  2025-12-11-980-ge38c27
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
enriched.c
Go to the documentation of this file.
1
22
28
32
33#include "config.h"
34#include <stdbool.h>
35#include <stdio.h>
36#include <wchar.h>
37#include <wctype.h>
38#include "mutt/lib.h"
39#include "enriched.h"
40#include "body.h"
41
43#define INDENT_SIZE 4
44
64
68struct Etags
69{
70 const wchar_t *tag_name;
71 int index;
72};
73
75static const struct Etags EnrichedTags[] = {
76 // clang-format off
77 { L"param", RICH_PARAM },
78 { L"bold", RICH_BOLD },
79 { L"italic", RICH_ITALIC },
80 { L"underline", RICH_UNDERLINE },
81 { L"nofill", RICH_NOFILL },
82 { L"excerpt", RICH_EXCERPT },
83 { L"indent", RICH_INDENT },
84 { L"indentright", RICH_INDENT_RIGHT },
85 { L"center", RICH_CENTER },
86 { L"flushleft", RICH_FLUSHLEFT },
87 { L"flushright", RICH_FLUSHRIGHT },
88 { L"flushboth", RICH_FLUSHLEFT },
89 { L"color", RICH_COLOR },
90 { L"x-color", RICH_COLOR },
91 { NULL, -1 },
92 // clang-format on
93};
94
99{
100 wchar_t *buffer;
101 wchar_t *line;
102 wchar_t *param;
103 size_t buf_len;
104 size_t line_len;
105 size_t line_used;
106 size_t line_max;
107 size_t indent_len;
108 size_t word_len;
109 size_t buf_used;
110 size_t param_used;
111 size_t param_len;
114 struct State *state;
115};
116
121static void enriched_wrap(struct EnrichedState *enriched)
122{
123 if (!enriched)
124 return;
125
126 int x;
127
128 /* Output the current line content, applying center/flush-right alignment
129 * by stripping whitespace and adding leading spaces as needed */
130 if (enriched->line_len)
131 {
132 if (enriched->tag_level[RICH_CENTER] || enriched->tag_level[RICH_FLUSHRIGHT])
133 {
134 /* Strip trailing white space */
135 size_t y = enriched->line_used - 1;
136
137 while (y && iswspace(enriched->line[y]))
138 {
139 enriched->line[y] = (wchar_t) '\0';
140 y--;
141 enriched->line_used--;
142 enriched->line_len--;
143 }
144 if (enriched->tag_level[RICH_CENTER])
145 {
146 /* Strip leading whitespace */
147 y = 0;
148
149 while (enriched->line[y] && iswspace(enriched->line[y]))
150 y++;
151 if (y)
152 {
153 for (size_t z = y; z <= enriched->line_used; z++)
154 {
155 enriched->line[z - y] = enriched->line[z];
156 }
157
158 enriched->line_len -= y;
159 enriched->line_used -= y;
160 }
161 }
162 }
163
164 const int extra = enriched->wrap_margin - enriched->line_len - enriched->indent_len -
166 if (extra > 0)
167 {
168 if (enriched->tag_level[RICH_CENTER])
169 {
170 x = extra / 2;
171 while (x)
172 {
173 state_putc(enriched->state, ' ');
174 x--;
175 }
176 }
177 else if (enriched->tag_level[RICH_FLUSHRIGHT])
178 {
179 x = extra - 1;
180 while (x)
181 {
182 state_putc(enriched->state, ' ');
183 x--;
184 }
185 }
186 }
187 state_putws(enriched->state, (const wchar_t *) enriched->line);
188 }
189
190 /* Start a new line: reset line buffer and apply indentation for
191 * the current nesting level (prefix, excerpt markers, indent spaces) */
192 state_putc(enriched->state, '\n');
193 enriched->line[0] = (wchar_t) '\0';
194 enriched->line_len = 0;
195 enriched->line_used = 0;
196 enriched->indent_len = 0;
197 if (enriched->state->prefix)
198 {
199 state_puts(enriched->state, enriched->state->prefix);
200 enriched->indent_len += mutt_str_len(enriched->state->prefix);
201 }
202
203 if (enriched->tag_level[RICH_EXCERPT])
204 {
205 x = enriched->tag_level[RICH_EXCERPT];
206 while (x)
207 {
208 if (enriched->state->prefix)
209 {
210 state_puts(enriched->state, enriched->state->prefix);
211 enriched->indent_len += mutt_str_len(enriched->state->prefix);
212 }
213 else
214 {
215 state_puts(enriched->state, "> ");
216 enriched->indent_len += mutt_str_len("> ");
217 }
218 x--;
219 }
220 }
221 else
222 {
223 enriched->indent_len = 0;
224 }
225 if (enriched->tag_level[RICH_INDENT])
226 {
227 x = enriched->tag_level[RICH_INDENT] * INDENT_SIZE;
228 enriched->indent_len += x;
229 while (x)
230 {
231 state_putc(enriched->state, ' ');
232 x--;
233 }
234 }
235}
236
242static void enriched_flush(struct EnrichedState *enriched, bool wrap)
243{
244 if (!enriched || !enriched->buffer)
245 return;
246
247 if (!enriched->tag_level[RICH_NOFILL] &&
248 ((enriched->line_len + enriched->word_len) >
249 (enriched->wrap_margin - (enriched->tag_level[RICH_INDENT_RIGHT] * INDENT_SIZE) -
250 enriched->indent_len)))
251 {
252 enriched_wrap(enriched);
253 }
254
255 if (enriched->buf_used)
256 {
257 enriched->buffer[enriched->buf_used] = (wchar_t) '\0';
258 enriched->line_used += enriched->buf_used;
259 if (enriched->line_used > enriched->line_max)
260 {
261 enriched->line_max = enriched->line_used;
262 MUTT_MEM_REALLOC(&enriched->line, enriched->line_max + 1, wchar_t);
263 }
264 wcscat(enriched->line, enriched->buffer);
265 enriched->line_len += enriched->word_len;
266 enriched->word_len = 0;
267 enriched->buf_used = 0;
268 }
269 if (wrap)
270 enriched_wrap(enriched);
271 fflush(enriched->state->fp_out);
272}
273
279static void enriched_putwc(wchar_t c, struct EnrichedState *enriched)
280{
281 if (!enriched)
282 return;
283
284 if (enriched->tag_level[RICH_PARAM])
285 {
286 if (enriched->tag_level[RICH_COLOR])
287 {
288 if ((enriched->param_used + 1) >= enriched->param_len)
289 {
290 enriched->param_len += 256;
291 MUTT_MEM_REALLOC(&enriched->param, enriched->param_len, wchar_t);
292 }
293
294 enriched->param[enriched->param_used++] = c;
295 }
296 return; /* nothing to do */
297 }
298
299 /* see if more space is needed (plus extra for possible rich characters) */
300 if ((enriched->buf_len < (enriched->buf_used + 3)) || !enriched->buffer)
301 {
302 enriched->buf_len += 1024;
303 MUTT_MEM_REALLOC(&enriched->buffer, enriched->buf_len + 1, wchar_t);
304 }
305
306 if ((!enriched->tag_level[RICH_NOFILL] && iswspace(c)) || (c == (wchar_t) '\0'))
307 {
308 if (c == (wchar_t) '\t')
309 enriched->word_len += 8 - (enriched->line_len + enriched->word_len) % 8;
310 else
311 enriched->word_len++;
312
313 enriched->buffer[enriched->buf_used++] = c;
314 enriched_flush(enriched, false);
315 }
316 else
317 {
318 if (enriched->state->flags & STATE_DISPLAY)
319 {
320 if (enriched->tag_level[RICH_BOLD])
321 {
322 enriched->buffer[enriched->buf_used++] = c;
323 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
324 enriched->buffer[enriched->buf_used++] = c;
325 }
326 else if (enriched->tag_level[RICH_UNDERLINE])
327 {
328 enriched->buffer[enriched->buf_used++] = '_';
329 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
330 enriched->buffer[enriched->buf_used++] = c;
331 }
332 else if (enriched->tag_level[RICH_ITALIC])
333 {
334 enriched->buffer[enriched->buf_used++] = c;
335 enriched->buffer[enriched->buf_used++] = (wchar_t) '\010'; // Ctrl-H (backspace)
336 enriched->buffer[enriched->buf_used++] = '_';
337 }
338 else
339 {
340 enriched->buffer[enriched->buf_used++] = c;
341 }
342 }
343 else
344 {
345 enriched->buffer[enriched->buf_used++] = c;
346 }
347 enriched->word_len++;
348 }
349}
350
356static void enriched_puts(const char *s, struct EnrichedState *enriched)
357{
358 if (!enriched)
359 return;
360
361 const char *c = NULL;
362
363 if ((enriched->buf_len < (enriched->buf_used + mutt_str_len(s))) || !enriched->buffer)
364 {
365 enriched->buf_len += 1024;
366 MUTT_MEM_REALLOC(&enriched->buffer, enriched->buf_len + 1, wchar_t);
367 }
368 c = s;
369 while (*c)
370 {
371 enriched->buffer[enriched->buf_used++] = (wchar_t) *c;
372 c++;
373 }
374}
375
381static void enriched_set_flags(const wchar_t *tag, struct EnrichedState *enriched)
382{
383 if (!enriched)
384 return;
385
386 const wchar_t *tagptr = tag;
387 int i;
388 int j;
389
390 if (*tagptr == (wchar_t) '/')
391 tagptr++;
392
393 for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
394 {
395 if (wcscasecmp(EnrichedTags[i].tag_name, tagptr) == 0)
396 {
397 j = EnrichedTags[i].index;
398 break;
399 }
400 }
401
402 if (j != -1)
403 {
404 if ((j == RICH_CENTER) || (j == RICH_FLUSHLEFT) || (j == RICH_FLUSHRIGHT))
405 enriched_flush(enriched, true);
406
407 if (*tag == (wchar_t) '/')
408 {
409 if (enriched->tag_level[j]) /* make sure not to go negative */
410 enriched->tag_level[j]--;
411 if ((enriched->state->flags & STATE_DISPLAY) && (j == RICH_PARAM) &&
412 enriched->tag_level[RICH_COLOR])
413 {
414 enriched->param[enriched->param_used] = (wchar_t) '\0';
415 if (wcscasecmp(L"black", enriched->param) == 0)
416 {
417 enriched_puts("\033[30m", enriched); // Escape
418 }
419 else if (wcscasecmp(L"red", enriched->param) == 0)
420 {
421 enriched_puts("\033[31m", enriched); // Escape
422 }
423 else if (wcscasecmp(L"green", enriched->param) == 0)
424 {
425 enriched_puts("\033[32m", enriched); // Escape
426 }
427 else if (wcscasecmp(L"yellow", enriched->param) == 0)
428 {
429 enriched_puts("\033[33m", enriched); // Escape
430 }
431 else if (wcscasecmp(L"blue", enriched->param) == 0)
432 {
433 enriched_puts("\033[34m", enriched); // Escape
434 }
435 else if (wcscasecmp(L"magenta", enriched->param) == 0)
436 {
437 enriched_puts("\033[35m", enriched); // Escape
438 }
439 else if (wcscasecmp(L"cyan", enriched->param) == 0)
440 {
441 enriched_puts("\033[36m", enriched); // Escape
442 }
443 else if (wcscasecmp(L"white", enriched->param) == 0)
444 {
445 enriched_puts("\033[37m", enriched); // Escape
446 }
447 }
448 if ((enriched->state->flags & STATE_DISPLAY) && (j == RICH_COLOR))
449 {
450 enriched_puts("\033[0m", enriched); // Escape
451 }
452
453 /* flush parameter buffer when closing the tag */
454 if (j == RICH_PARAM)
455 {
456 enriched->param_used = 0;
457 enriched->param[0] = (wchar_t) '\0';
458 }
459 }
460 else
461 {
462 enriched->tag_level[j]++;
463 }
464
465 if (j == RICH_EXCERPT)
466 enriched_flush(enriched, true);
467 }
468}
469
474int text_enriched_handler(struct Body *b_email, struct State *state)
475{
476 enum
477 {
478 TEXT,
479 LANGLE,
480 TAG,
481 BOGUS_TAG,
482 NEWLINE,
483 ST_EOF,
484 DONE
485 } text_state = TEXT;
486
487 long bytes = b_email->length;
488 struct EnrichedState enriched = { 0 };
489 wint_t wc = 0;
490 int tag_len = 0;
491 wchar_t tag[1024 + 1] = { 0 };
492
493 enriched.state = state;
494 enriched.wrap_margin = ((state->wraplen > 4) &&
495 ((state->flags & STATE_DISPLAY) || (state->wraplen < 76))) ?
496 state->wraplen - 4 :
497 72;
498 enriched.line_max = enriched.wrap_margin * 4;
499 enriched.line = MUTT_MEM_CALLOC(enriched.line_max + 1, wchar_t);
500 enriched.param = MUTT_MEM_CALLOC(256, wchar_t);
501
502 enriched.param_len = 256;
503 enriched.param_used = 0;
504
505 if (state->prefix)
506 {
508 enriched.indent_len += mutt_str_len(state->prefix);
509 }
510
511 while (text_state != DONE)
512 {
513 if (text_state != ST_EOF)
514 {
515 if (!bytes || ((wc = fgetwc(state->fp_in)) == WEOF))
516 text_state = ST_EOF;
517 else
518 bytes--;
519 }
520
521 switch (text_state)
522 {
523 case TEXT:
524 switch (wc)
525 {
526 case '<':
527 text_state = LANGLE;
528 break;
529
530 case '\n':
531 if (enriched.tag_level[RICH_NOFILL])
532 {
533 enriched_flush(&enriched, true);
534 }
535 else
536 {
537 enriched_putwc((wchar_t) ' ', &enriched);
538 text_state = NEWLINE;
539 }
540 break;
541
542 default:
543 enriched_putwc(wc, &enriched);
544 }
545 break;
546
547 case LANGLE:
548 if (wc == (wchar_t) '<')
549 {
550 enriched_putwc(wc, &enriched);
551 text_state = TEXT;
552 break;
553 }
554 else
555 {
556 tag_len = 0;
557 text_state = TAG;
558 }
559 /* Yes, (it wasn't a <<, so this char is first in TAG) */
561
562 case TAG:
563 if (wc == (wchar_t) '>')
564 {
565 tag[tag_len] = (wchar_t) '\0';
566 enriched_set_flags(tag, &enriched);
567 text_state = TEXT;
568 }
569 else if (tag_len < 1024) /* ignore overly long tags */
570 {
571 tag[tag_len++] = wc;
572 }
573 else
574 {
575 text_state = BOGUS_TAG;
576 }
577 break;
578
579 case BOGUS_TAG:
580 if (wc == (wchar_t) '>')
581 text_state = TEXT;
582 break;
583
584 case NEWLINE:
585 if (wc == (wchar_t) '\n')
586 {
587 enriched_flush(&enriched, true);
588 }
589 else
590 {
591 ungetwc(wc, state->fp_in);
592 bytes++;
593 text_state = TEXT;
594 }
595 break;
596
597 case ST_EOF:
598 enriched_putwc((wchar_t) '\0', &enriched);
599 enriched_flush(&enriched, true);
600 text_state = DONE;
601 break;
602
603 default:
604 /* not reached */
605 break;
606 }
607 }
608
609 state_putc(state, '\n'); /* add a final newline */
610
611 FREE(&(enriched.buffer));
612 FREE(&(enriched.line));
613 FREE(&(enriched.param));
614
615 return 0;
616}
Representation of the body of an email.
static void enriched_set_flags(const wchar_t *tag, struct EnrichedState *enriched)
Set flags on the enriched text state.
Definition enriched.c:381
static void enriched_wrap(struct EnrichedState *enriched)
Wrap enriched text.
Definition enriched.c:121
RichAttribs
Rich text attributes.
Definition enriched.c:49
@ RICH_FLUSHRIGHT
Right-justified text.
Definition enriched.c:60
@ RICH_NOFILL
Text will not be reformatted.
Definition enriched.c:54
@ RICH_COLOR
Coloured text.
Definition enriched.c:61
@ RICH_PARAM
Parameter label.
Definition enriched.c:50
@ RICH_INDENT
Indented text.
Definition enriched.c:55
@ RICH_BOLD
Bold text.
Definition enriched.c:51
@ RICH_CENTER
Centred text.
Definition enriched.c:58
@ RICH_ITALIC
Italic text.
Definition enriched.c:53
@ RICH_UNDERLINE
Underlined text.
Definition enriched.c:52
@ RICH_EXCERPT
Excerpt text.
Definition enriched.c:57
@ RICH_INDENT_RIGHT
Right-indented text.
Definition enriched.c:56
@ RICH_MAX
Definition enriched.c:62
@ RICH_FLUSHLEFT
Left-justified text.
Definition enriched.c:59
static void enriched_putwc(wchar_t c, struct EnrichedState *enriched)
Write one wide character to the state.
Definition enriched.c:279
static const struct Etags EnrichedTags[]
EnrichedTags - Lookup table of tags allowed in enriched text.
Definition enriched.c:75
#define INDENT_SIZE
A (not so) minimal implementation of RFC1563.
Definition enriched.c:43
static void enriched_flush(struct EnrichedState *enriched, bool wrap)
Write enriched text to the State.
Definition enriched.c:242
static void enriched_puts(const char *s, struct EnrichedState *enriched)
Write an enriched text string to the State.
Definition enriched.c:356
Rich text handler.
int text_enriched_handler(struct Body *b_email, struct State *state)
Handler for enriched text - Implements handler_t -.
Definition enriched.c:474
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
Convenience wrapper for the library headers.
#define FALLTHROUGH
Definition lib.h:117
int wcscasecmp(const wchar_t *a, const wchar_t *b)
Compare two wide-character strings, ignoring case.
Definition wcscasecmp.c:41
int state_putws(struct State *state, const wchar_t *ws)
Write a wide string to the state.
Definition state.c:147
#define state_puts(STATE, STR)
Definition state.h:64
#define state_putc(STATE, STR)
Definition state.h:65
@ STATE_DISPLAY
Output is displayed to the user.
Definition state.h:37
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
The body of an email.
Definition body.h:36
LOFF_T length
length (in bytes) of attachment
Definition body.h:53
State of enriched-text parser.
Definition enriched.c:99
wchar_t * buffer
Output buffer.
Definition enriched.c:100
size_t buf_len
Length of buffer.
Definition enriched.c:103
size_t param_used
Used bytes in param buffer.
Definition enriched.c:110
wchar_t * param
Current parameter.
Definition enriched.c:102
wchar_t * line
Current line.
Definition enriched.c:101
int tag_level[RICH_MAX]
Nesting level of each tag type.
Definition enriched.c:112
size_t buf_used
Used bytes in output buffer.
Definition enriched.c:109
size_t line_used
Used bytes in line buffer.
Definition enriched.c:105
struct State * state
State wrapper.
Definition enriched.c:114
size_t word_len
Current word length.
Definition enriched.c:108
size_t param_len
Capacity of param buffer.
Definition enriched.c:111
size_t line_len
Capacity of line buffer.
Definition enriched.c:104
size_t line_max
Maximum line width.
Definition enriched.c:106
int wrap_margin
Wrap margin.
Definition enriched.c:113
size_t indent_len
Current indentation level.
Definition enriched.c:107
Enriched text tags.
Definition enriched.c:69
int index
Index number.
Definition enriched.c:71
const wchar_t * tag_name
Tag name.
Definition enriched.c:70
Keep track when processing files.
Definition state.h:54
int wraplen
Width to wrap lines to (when flags & STATE_DISPLAY)
Definition state.h:59
StateFlags flags
Flags, e.g. STATE_DISPLAY.
Definition state.h:58
FILE * fp_out
File to write to.
Definition state.h:56
FILE * fp_in
File to read from.
Definition state.h:55
const char * prefix
String to add to the beginning of each output line.
Definition state.h:57