Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/bbcode.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include <string.h>
8
#include <stdio.h>
9
#include <stdarg.h>
10
#include <stdlib.h>
11
12
#include "common.h"
13
#include "attr.h"
14
#include "term.h"
15
#include "bbcode.h"
16
17
//-------------------------------------------------------------
18
// HTML color table
19
//-------------------------------------------------------------
20
21
#include "bbcode_colors.c"
22
23
//-------------------------------------------------------------
24
// Types
25
//-------------------------------------------------------------
26
27
typedef struct style_s {
28
const char* name; // name of the style
29
attr_t attr; // attribute to apply
30
} style_t;
31
32
typedef enum align_e {
33
IC_ALIGN_LEFT,
34
IC_ALIGN_CENTER,
35
IC_ALIGN_RIGHT
36
} align_t;
37
38
typedef struct width_s {
39
ssize_t w; // > 0
40
align_t align;
41
bool dots; // "..." (e.g. "sentence...")
42
char fill; // " " (e.g. "hello ")
43
} width_t;
44
45
typedef struct tag_s {
46
const char* name; // tag open name
47
attr_t attr; // the saved attribute before applying the style
48
width_t width; // start sequence of at most "width" columns
49
ssize_t pos; // start position in the output (used for width restriction)
50
} tag_t;
51
52
53
static void tag_init(tag_t* tag) {
54
memset(tag,0,sizeof(*tag));
55
}
56
57
struct bbcode_s {
58
tag_t* tags; // stack of tags; one entry for each open tag
59
ssize_t tags_capacity;
60
ssize_t tags_nesting;
61
style_t* styles; // list of used defined styles
62
ssize_t styles_capacity;
63
ssize_t styles_count;
64
term_t* term; // terminal
65
alloc_t* mem; // allocator
66
// caches
67
stringbuf_t* out; // print buffer
68
attrbuf_t* out_attrs;
69
stringbuf_t* vout; // vprintf buffer
70
};
71
72
73
//-------------------------------------------------------------
74
// Create, helpers
75
//-------------------------------------------------------------
76
77
ic_private bbcode_t* bbcode_new( alloc_t* mem, term_t* term ) {
78
bbcode_t* bb = mem_zalloc_tp(mem,bbcode_t);
79
if (bb==NULL) return NULL;
80
bb->mem = mem;
81
bb->term = term;
82
bb->out = sbuf_new(mem);
83
bb->out_attrs = attrbuf_new(mem);
84
bb->vout = sbuf_new(mem);
85
return bb;
86
}
87
88
ic_private void bbcode_free( bbcode_t* bb ) {
89
for(ssize_t i = 0; i < bb->styles_count; i++) {
90
mem_free(bb->mem, bb->styles[i].name);
91
}
92
mem_free(bb->mem, bb->tags);
93
mem_free(bb->mem, bb->styles);
94
sbuf_free(bb->vout);
95
sbuf_free(bb->out);
96
attrbuf_free(bb->out_attrs);
97
mem_free(bb->mem, bb);
98
}
99
100
ic_private void bbcode_style_add( bbcode_t* bb, const char* style_name, attr_t attr ) {
101
if (bb->styles_count >= bb->styles_capacity) {
102
ssize_t newlen = bb->styles_capacity + 32;
103
style_t* p = mem_realloc_tp( bb->mem, style_t, bb->styles, newlen );
104
if (p == NULL) return;
105
bb->styles = p;
106
bb->styles_capacity = newlen;
107
}
108
assert(bb->styles_count < bb->styles_capacity);
109
bb->styles[bb->styles_count].name = mem_strdup( bb->mem, style_name );
110
bb->styles[bb->styles_count].attr = attr;
111
bb->styles_count++;
112
}
113
114
static ssize_t bbcode_tag_push( bbcode_t* bb, const tag_t* tag ) {
115
if (bb->tags_nesting >= bb->tags_capacity) {
116
ssize_t newcap = bb->tags_capacity + 32;
117
tag_t* p = mem_realloc_tp( bb->mem, tag_t, bb->tags, newcap );
118
if (p == NULL) return -1;
119
bb->tags = p;
120
bb->tags_capacity = newcap;
121
}
122
assert(bb->tags_nesting < bb->tags_capacity);
123
bb->tags[bb->tags_nesting] = *tag;
124
bb->tags_nesting++;
125
return (bb->tags_nesting-1);
126
}
127
128
static void bbcode_tag_pop( bbcode_t* bb, tag_t* tag ) {
129
if (bb->tags_nesting <= 0) {
130
if (tag != NULL) { tag_init(tag); }
131
}
132
else {
133
bb->tags_nesting--;
134
if (tag != NULL) {
135
*tag = bb->tags[bb->tags_nesting];
136
}
137
}
138
}
139
140
//-------------------------------------------------------------
141
// Invalid parse/values/balance
142
//-------------------------------------------------------------
143
144
static void bbcode_invalid(const char* fmt, ... ) {
145
if (getenv("ISOCLINE_BBCODE_DEBUG") != NULL) {
146
va_list args;
147
va_start(args,fmt);
148
vfprintf(stderr,fmt,args);
149
va_end(args);
150
}
151
}
152
153
//-------------------------------------------------------------
154
// Set attributes
155
//-------------------------------------------------------------
156
157
158
static attr_t bbcode_open( bbcode_t* bb, ssize_t out_pos, const tag_t* tag, attr_t current ) {
159
// save current and set
160
tag_t cur;
161
tag_init(&cur);
162
cur.name = tag->name;
163
cur.attr = current;
164
cur.width = tag->width;
165
cur.pos = out_pos;
166
bbcode_tag_push(bb,&cur);
167
return attr_update_with( current, tag->attr );
168
}
169
170
static bool bbcode_close( bbcode_t* bb, ssize_t base, const char* name, tag_t* pprev ) {
171
// pop until match
172
while (bb->tags_nesting > base) {
173
tag_t prev;
174
bbcode_tag_pop(bb,&prev);
175
if (name==NULL || prev.name==NULL || ic_stricmp(prev.name,name) == 0) {
176
// matched
177
if (pprev != NULL) { *pprev = prev; }
178
return true;
179
}
180
else {
181
// unbalanced: we either continue popping or restore the tags depending if there is a matching open tag in our tags.
182
bool has_open_tag = false;
183
if (name != NULL) {
184
for( ssize_t i = bb->tags_nesting - 1; i > base; i--) {
185
if (bb->tags[i].name != NULL && ic_stricmp(bb->tags[i].name, name) == 0) {
186
has_open_tag = true;
187
break;
188
}
189
}
190
}
191
bbcode_invalid("bbcode: unbalanced tags: open [%s], close [/%s]\n", prev.name, name);
192
if (!has_open_tag) {
193
bbcode_tag_push( bb, &prev ); // restore the tags and ignore this close tag
194
break;
195
}
196
else {
197
// continue until we hit our open tag
198
}
199
}
200
}
201
if (pprev != NULL) { memset(pprev,0,sizeof(*pprev)); }
202
return false;
203
}
204
205
//-------------------------------------------------------------
206
// Update attributes
207
//-------------------------------------------------------------
208
209
static const char* attr_update_bool( const char* fname, signed int* field, const char* value ) {
210
if (value == NULL || value[0] == 0 || strcmp(value,"on") || strcmp(value,"true") || strcmp(value,"1")) {
211
*field = IC_ON;
212
}
213
else if (strcmp(value,"off") || strcmp(value,"false") || strcmp(value,"0")) {
214
*field = IC_OFF;
215
}
216
else {
217
bbcode_invalid("bbcode: invalid %s value: %s\n", fname, value );
218
}
219
return fname;
220
}
221
222
static const char* attr_update_color( const char* fname, ic_color_t* field, const char* value ) {
223
if (value == NULL || value[0] == 0 || strcmp(value,"none") == 0) {
224
*field = IC_COLOR_NONE;
225
return fname;
226
}
227
228
// hex value
229
if (value[0] == '#') {
230
uint32_t rgb = 0;
231
if (sscanf(value,"#%x",&rgb) == 1) {
232
*field = ic_rgb(rgb);
233
}
234
else {
235
bbcode_invalid("bbcode: invalid color code: %s\n", value);
236
}
237
return fname;
238
}
239
240
// search color names
241
ssize_t lo = 0;
242
ssize_t hi = IC_HTML_COLOR_COUNT-1;
243
while( lo <= hi ) {
244
ssize_t mid = (lo + hi) / 2;
245
style_color_t* info = &html_colors[mid];
246
int cmp = strcmp(info->name,value);
247
if (cmp < 0) {
248
lo = mid+1;
249
}
250
else if (cmp > 0) {
251
hi = mid-1;
252
}
253
else {
254
*field = info->color;
255
return fname;
256
}
257
}
258
bbcode_invalid("bbcode: unknown %s: %s\n", fname, value);
259
*field = IC_COLOR_NONE;
260
return fname;
261
}
262
263
static const char* attr_update_sgr( const char* fname, attr_t* attr, const char* value ) {
264
*attr = attr_update_with(*attr, attr_from_sgr(value, ic_strlen(value)));
265
return fname;
266
}
267
268
static void attr_update_width( width_t* pwidth, char default_fill, const char* value ) {
269
// parse width value: <width>;<left|center|right>;<fill>;<cut>
270
width_t width;
271
memset(&width, 0, sizeof(width));
272
width.fill = default_fill; // use 0 for no-fill (as for max-width)
273
if (ic_atoz(value, &width.w)) {
274
ssize_t i = 0;
275
while( value[i] != ';' && value[i] != 0 ) { i++; }
276
if (value[i] == ';') {
277
i++;
278
ssize_t len = 0;
279
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
280
if (len == 4 && ic_istarts_with(value+i,"left")) {
281
width.align = IC_ALIGN_LEFT;
282
}
283
else if (len == 5 && ic_istarts_with(value+i,"right")) {
284
width.align = IC_ALIGN_RIGHT;
285
}
286
else if (len == 6 && ic_istarts_with(value+i,"center")) {
287
width.align = IC_ALIGN_CENTER;
288
}
289
i += len;
290
if (value[i] == ';') {
291
i++; len = 0;
292
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
293
if (len == 1) { width.fill = value[i]; }
294
i+= len;
295
if (value[i] == ';') {
296
i++; len = 0;
297
while( value[i+len] != ';' && value[i+len] != 0 ) { len++; }
298
if ((len == 2 && ic_istarts_with(value+i,"on")) || (len == 1 && value[i] == '1')) { width.dots = true; }
299
i += len;
300
}
301
}
302
}
303
}
304
else {
305
bbcode_invalid("bbcode: illegal width: %s\n", value);
306
}
307
*pwidth = width;
308
}
309
310
static const char* attr_update_ansi_color( const char* fname, ic_color_t* color, const char* value ) {
311
ssize_t num = 0;
312
if (ic_atoz(value, &num) && num >= 0 && num <= 256) {
313
*color = color_from_ansi256(num);
314
}
315
return fname;
316
}
317
318
319
static const char* attr_update_property( tag_t* tag, const char* attr_name, const char* value ) {
320
const char* fname = NULL;
321
fname = "bold";
322
if (strcmp(attr_name,fname) == 0) {
323
signed int b = IC_NONE;
324
attr_update_bool(fname,&b, value);
325
if (b != IC_NONE) { tag->attr.x.bold = b; }
326
return fname;
327
}
328
fname = "italic";
329
if (strcmp(attr_name,fname) == 0) {
330
signed int b = IC_NONE;
331
attr_update_bool(fname,&b, value);
332
if (b != IC_NONE) { tag->attr.x.italic = b; }
333
return fname;
334
}
335
fname = "underline";
336
if (strcmp(attr_name,fname) == 0) {
337
signed int b = IC_NONE;
338
attr_update_bool(fname,&b, value);
339
if (b != IC_NONE) { tag->attr.x.underline = b; }
340
return fname;
341
}
342
fname = "reverse";
343
if (strcmp(attr_name,fname) == 0) {
344
signed int b = IC_NONE;
345
attr_update_bool(fname,&b, value);
346
if (b != IC_NONE) { tag->attr.x.reverse = b; }
347
return fname;
348
}
349
fname = "color";
350
if (strcmp(attr_name,fname) == 0) {
351
unsigned int color = IC_COLOR_NONE;
352
attr_update_color(fname, &color, value);
353
if (color != IC_COLOR_NONE) { tag->attr.x.color = color; }
354
return fname;
355
}
356
fname = "bgcolor";
357
if (strcmp(attr_name,fname) == 0) {
358
unsigned int color = IC_COLOR_NONE;
359
attr_update_color(fname, &color, value);
360
if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; }
361
return fname;
362
}
363
fname = "ansi-sgr";
364
if (strcmp(attr_name,fname) == 0) {
365
attr_update_sgr(fname, &tag->attr, value);
366
return fname;
367
}
368
fname = "ansi-color";
369
if (strcmp(attr_name,fname) == 0) {
370
ic_color_t color = IC_COLOR_NONE;;
371
attr_update_ansi_color(fname, &color, value);
372
if (color != IC_COLOR_NONE) { tag->attr.x.color = color; }
373
return fname;
374
}
375
fname = "ansi-bgcolor";
376
if (strcmp(attr_name,fname) == 0) {
377
ic_color_t color = IC_COLOR_NONE;;
378
attr_update_ansi_color(fname, &color, value);
379
if (color != IC_COLOR_NONE) { tag->attr.x.bgcolor = color; }
380
return fname;
381
}
382
fname = "width";
383
if (strcmp(attr_name,fname) == 0) {
384
attr_update_width(&tag->width, ' ', value);
385
return fname;
386
}
387
fname = "max-width";
388
if (strcmp(attr_name,fname) == 0) {
389
attr_update_width(&tag->width, 0, value);
390
return "width";
391
}
392
else {
393
return NULL;
394
}
395
}
396
397
static const style_t builtin_styles[] = {
398
{ "b", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } },
399
{ "r", { { IC_COLOR_NONE, IC_NONE, IC_ON , IC_COLOR_NONE, IC_NONE, IC_NONE } } },
400
{ "u", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON , IC_NONE } } },
401
{ "i", { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_ON } } },
402
{ "em", { { IC_COLOR_NONE, IC_ON , IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }, // bold
403
{ "url",{ { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_ON, IC_NONE } } }, // underline
404
{ NULL, { { IC_COLOR_NONE, IC_NONE, IC_NONE, IC_COLOR_NONE, IC_NONE, IC_NONE } } }
405
};
406
407
static void attr_update_with_styles( tag_t* tag, const char* attr_name, const char* value,
408
bool usebgcolor, const style_t* styles, ssize_t count )
409
{
410
// direct hex color?
411
if (attr_name[0] == '#' && (value == NULL || value[0]==0)) {
412
value = attr_name;
413
attr_name = (usebgcolor ? "bgcolor" : "color");
414
}
415
// first try if it is a builtin property
416
const char* name;
417
if ((name = attr_update_property(tag,attr_name,value)) != NULL) {
418
if (tag->name != NULL) tag->name = name;
419
return;
420
}
421
// then check all styles
422
while( count-- > 0 ) {
423
const style_t* style = styles + count;
424
if (strcmp(style->name,attr_name) == 0) {
425
tag->attr = attr_update_with(tag->attr,style->attr);
426
if (tag->name != NULL) tag->name = style->name;
427
return;
428
}
429
}
430
// check builtin styles; todo: binary search?
431
for( const style_t* style = builtin_styles; style->name != NULL; style++) {
432
if (strcmp(style->name,attr_name) == 0) {
433
tag->attr = attr_update_with(tag->attr,style->attr);
434
if (tag->name != NULL) tag->name = style->name;
435
return;
436
}
437
}
438
// check colors as a style
439
ssize_t lo = 0;
440
ssize_t hi = IC_HTML_COLOR_COUNT-1;
441
while( lo <= hi ) {
442
ssize_t mid = (lo + hi) / 2;
443
style_color_t* info = &html_colors[mid];
444
int cmp = strcmp(info->name,attr_name);
445
if (cmp < 0) {
446
lo = mid+1;
447
}
448
else if (cmp > 0) {
449
hi = mid-1;
450
}
451
else {
452
attr_t cattr = attr_none();
453
if (usebgcolor) { cattr.x.bgcolor = info->color; }
454
else { cattr.x.color = info->color; }
455
tag->attr = attr_update_with(tag->attr,cattr);
456
if (tag->name != NULL) tag->name = info->name;
457
return;
458
}
459
}
460
// not found
461
bbcode_invalid("bbcode: unknown style: %s\n", attr_name);
462
}
463
464
465
ic_private attr_t bbcode_style( bbcode_t* bb, const char* style_name ) {
466
tag_t tag;
467
tag_init(&tag);
468
attr_update_with_styles( &tag, style_name, NULL, false, bb->styles, bb->styles_count );
469
return tag.attr;
470
}
471
472
//-------------------------------------------------------------
473
// Parse tags
474
//-------------------------------------------------------------
475
476
ic_private const char* parse_skip_white(const char* s) {
477
while( *s != 0 && *s != ']') {
478
if (!(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) break;
479
s++;
480
}
481
return s;
482
}
483
484
ic_private const char* parse_skip_to_white(const char* s) {
485
while( *s != 0 && *s != ']') {
486
if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') break;
487
s++;
488
}
489
return parse_skip_white(s);
490
}
491
492
ic_private const char* parse_skip_to_end(const char* s) {
493
while( *s != 0 && *s != ']' ) { s++; }
494
return s;
495
}
496
497
ic_private const char* parse_attr_name(const char* s) {
498
if (*s == '#') {
499
s++; // hex rgb color as id
500
while( *s != 0 && *s != ']') {
501
if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break;
502
s++;
503
}
504
}
505
else {
506
while( *s != 0 && *s != ']') {
507
if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
508
(*s >= '0' && *s <= '9') || *s == '_' || *s == '-')) break;
509
s++;
510
}
511
}
512
return s;
513
}
514
515
ic_private const char* parse_value(const char* s, const char** start, const char** end) {
516
if (*s == '"') {
517
s++;
518
*start = s;
519
while( *s != 0 ) {
520
if (*s == '"') break;
521
s++;
522
}
523
*end = s;
524
if (*s == '"') { s++; }
525
}
526
else if (*s == '#') {
527
*start = s;
528
s++;
529
while( *s != 0 ) {
530
if (!((*s >= 'a' && *s <= 'f') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9'))) break;
531
s++;
532
}
533
*end = s;
534
}
535
else {
536
*start = s;
537
while( *s != 0 ) {
538
if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'F') || (*s >= '0' && *s <= '9') || *s == '-' || *s == '_')) break;
539
s++;
540
}
541
*end = s;
542
}
543
return s;
544
}
545
546
ic_private const char* parse_tag_value( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) {
547
// parse: \s*[\w-]+\s*(=\s*<value>)
548
bool usebgcolor = false;
549
const char* id = s;
550
const char* idend = parse_attr_name(id);
551
const char* val = NULL;
552
const char* valend = NULL;
553
if (id == idend) {
554
bbcode_invalid("bbcode: empty identifier? %.10s...\n", id );
555
return parse_skip_to_white(id);
556
}
557
// "on" bgcolor?
558
s = parse_skip_white(idend);
559
if (idend - id == 2 && ic_strnicmp(id,"on",2) == 0 && *s != '=') {
560
usebgcolor = true;
561
id = s;
562
idend = parse_attr_name(id);
563
if (id == idend) {
564
bbcode_invalid("bbcode: empty identifier follows 'on'? %.10s...\n", id );
565
return parse_skip_to_white(id);
566
}
567
s = parse_skip_white(idend);
568
}
569
// value
570
if (*s == '=') {
571
s++;
572
s = parse_skip_white(s);
573
s = parse_value(s, &val, &valend);
574
s = parse_skip_white(s);
575
}
576
// limit name and attr to 128 bytes
577
char valbuf[128];
578
valbuf[0] = 0; // fixes gcc uninitialized warning
579
ic_strncpy( idbuf, 128, id, idend - id);
580
ic_strncpy( valbuf, 128, val, valend - val);
581
ic_str_tolower(idbuf);
582
ic_str_tolower(valbuf);
583
attr_update_with_styles( tag, idbuf, valbuf, usebgcolor, styles, scount );
584
return s;
585
}
586
587
static const char* parse_tag_values( tag_t* tag, char* idbuf, const char* s, const style_t* styles, ssize_t scount ) {
588
s = parse_skip_white(s);
589
idbuf[0] = 0;
590
ssize_t count = 0;
591
while( *s != 0 && *s != ']') {
592
char idbuf_next[128];
593
s = parse_tag_value(tag, (count==0 ? idbuf : idbuf_next), s, styles, scount);
594
count++;
595
}
596
if (*s == ']') { s++; }
597
return s;
598
}
599
600
static const char* parse_tag( tag_t* tag, char* idbuf, bool* open, bool* pre, const char* s, const style_t* styles, ssize_t scount ) {
601
*open = true;
602
*pre = false;
603
if (*s != '[') return s;
604
s = parse_skip_white(s+1);
605
if (*s == '!') { // pre
606
*pre = true;
607
s = parse_skip_white(s+1);
608
}
609
else if (*s == '/') {
610
*open = false;
611
s = parse_skip_white(s+1);
612
};
613
s = parse_tag_values( tag, idbuf, s, styles, scount);
614
return s;
615
}
616
617
618
//---------------------------------------------------------
619
// Styles
620
//---------------------------------------------------------
621
622
static void bbcode_parse_tag_content( bbcode_t* bb, const char* s, tag_t* tag ) {
623
tag_init(tag);
624
if (s != NULL) {
625
char idbuf[128];
626
parse_tag_values(tag, idbuf, s, bb->styles, bb->styles_count);
627
}
628
}
629
630
ic_private void bbcode_style_def( bbcode_t* bb, const char* style_name, const char* s ) {
631
tag_t tag;
632
bbcode_parse_tag_content( bb, s, &tag);
633
bbcode_style_add(bb, style_name, tag.attr);
634
}
635
636
ic_private void bbcode_style_open( bbcode_t* bb, const char* fmt ) {
637
tag_t tag;
638
bbcode_parse_tag_content(bb, fmt, &tag);
639
term_set_attr( bb->term, bbcode_open(bb, 0, &tag, term_get_attr(bb->term)) );
640
}
641
642
ic_private void bbcode_style_close( bbcode_t* bb, const char* fmt ) {
643
const ssize_t base = bb->tags_nesting - 1; // as we end a style
644
tag_t tag;
645
bbcode_parse_tag_content(bb, fmt, &tag);
646
tag_t prev;
647
if (bbcode_close(bb, base, tag.name, &prev)) {
648
term_set_attr( bb->term, prev.attr );
649
}
650
}
651
652
//---------------------------------------------------------
653
// Restrict to width
654
//---------------------------------------------------------
655
656
static void bbcode_restrict_width( ssize_t start, width_t width, stringbuf_t* out, attrbuf_t* attr_out ) {
657
if (width.w <= 0) return;
658
assert(start <= sbuf_len(out));
659
assert(attr_out == NULL || sbuf_len(out) == attrbuf_len(attr_out));
660
const char* s = sbuf_string(out) + start;
661
const ssize_t len = sbuf_len(out) - start;
662
const ssize_t w = str_column_width(s);
663
if (w == width.w) return; // fits exactly
664
if (w > width.w) {
665
// too large
666
ssize_t innerw = (width.dots && width.w > 3 ? width.w-3 : width.w);
667
if (width.align == IC_ALIGN_RIGHT) {
668
// right align
669
const ssize_t ndel = str_skip_until_fit( s, innerw );
670
sbuf_delete_at( out, start, ndel );
671
attrbuf_delete_at( attr_out, start, ndel );
672
if (innerw < width.w) {
673
// add dots
674
sbuf_insert_at( out, "...", start );
675
attr_t attr = attrbuf_attr_at(attr_out, start);
676
attrbuf_insert_at( attr_out, start, 3, attr);
677
}
678
}
679
else {
680
// left or center align
681
ssize_t count = str_take_while_fit( s, innerw );
682
sbuf_delete_at( out, start + count, len - count );
683
attrbuf_delete_at( attr_out, start + count, len - count );
684
if (innerw < width.w) {
685
// add dots
686
attr_t attr = attrbuf_attr_at(attr_out,start);
687
attrbuf_append_n( out, attr_out, "...", 3, attr );
688
}
689
}
690
}
691
else {
692
// too short, pad to width
693
const ssize_t diff = (width.w - w);
694
const ssize_t pad_left = (width.align == IC_ALIGN_RIGHT ? diff : (width.align == IC_ALIGN_LEFT ? 0 : diff / 2));
695
const ssize_t pad_right = (width.align == IC_ALIGN_LEFT ? diff : (width.align == IC_ALIGN_RIGHT ? 0 : diff - pad_left));
696
if (width.fill != 0 && pad_left > 0) {
697
const attr_t attr = attrbuf_attr_at(attr_out,start);
698
for( ssize_t i = 0; i < pad_left; i++) { // todo: optimize
699
sbuf_insert_char_at(out, width.fill, start);
700
}
701
attrbuf_insert_at( attr_out, start, pad_left, attr );
702
}
703
if (width.fill != 0 && pad_right > 0) {
704
const attr_t attr = attrbuf_attr_at(attr_out,sbuf_len(out) - 1);
705
char buf[2];
706
buf[0] = width.fill;
707
buf[1] = 0;
708
for( ssize_t i = 0; i < pad_right; i++) { // todo: optimize
709
attrbuf_append_n( out, attr_out, buf, 1, attr );
710
}
711
}
712
}
713
}
714
715
//---------------------------------------------------------
716
// Print
717
//---------------------------------------------------------
718
719
ic_private ssize_t bbcode_process_tag( bbcode_t* bb, const char* s, const ssize_t nesting_base,
720
stringbuf_t* out, attrbuf_t* attr_out, attr_t* cur_attr ) {
721
assert(*s == '[');
722
tag_t tag;
723
tag_init(&tag);
724
bool open = true;
725
bool ispre = false;
726
char idbuf[128];
727
const char* end = parse_tag( &tag, idbuf, &open, &ispre, s, bb->styles, bb->styles_count ); // todo: styles
728
assert(end > s);
729
if (open) {
730
if (!ispre) {
731
// open tag
732
*cur_attr = bbcode_open( bb, sbuf_len(out), &tag, *cur_attr );
733
}
734
else {
735
// scan pre to end tag
736
attr_t attr = attr_update_with(*cur_attr, tag.attr);
737
char pre[132];
738
if (snprintf(pre, 132, "[/%s]", idbuf) < ssizeof(pre)) {
739
const char* etag = strstr(end,pre);
740
if (etag == NULL) {
741
const ssize_t len = ic_strlen(end);
742
attrbuf_append_n(out, attr_out, end, len, attr);
743
end += len;
744
}
745
else {
746
attrbuf_append_n(out, attr_out, end, (etag - end), attr);
747
end = etag + ic_strlen(pre);
748
}
749
}
750
}
751
}
752
else {
753
// pop the tag
754
tag_t prev;
755
if (bbcode_close( bb, nesting_base, tag.name, &prev)) {
756
*cur_attr = prev.attr;
757
if (prev.width.w > 0) {
758
// closed a width tag; restrict the output to width
759
bbcode_restrict_width( prev.pos, prev.width, out, attr_out);
760
}
761
}
762
}
763
return (end - s);
764
}
765
766
ic_private void bbcode_append( bbcode_t* bb, const char* s, stringbuf_t* out, attrbuf_t* attr_out ) {
767
if (bb == NULL || s == NULL) return;
768
attr_t attr = attr_none();
769
const ssize_t base = bb->tags_nesting; // base; will not be popped
770
ssize_t i = 0;
771
while( s[i] != 0 ) {
772
// handle no tags in bulk
773
ssize_t nobb = 0;
774
char c;
775
while( (c = s[i+nobb]) != 0) {
776
if (c == '[' || c == '\\') { break; }
777
if (c == '\x1B' && s[i+nobb+1] == '[') {
778
nobb++; // don't count 'ESC[' as a tag opener
779
}
780
nobb++;
781
}
782
if (nobb > 0) { attrbuf_append_n(out, attr_out, s+i, nobb, attr); }
783
i += nobb;
784
// tag
785
if (s[i] == '[') {
786
i += bbcode_process_tag(bb, s+i, base, out, attr_out, &attr);
787
}
788
else if (s[i] == '\\') {
789
if (s[i+1] == '\\' || s[i+1] == '[') {
790
attrbuf_append_n(out, attr_out, s+i+1, 1, attr); // escape '\[' and '\\'
791
i += 2;
792
}
793
else {
794
attrbuf_append_n(out, attr_out, s+i, 1, attr); // pass '\\' as is
795
i++;
796
}
797
}
798
}
799
// pop unclosed openings
800
assert(bb->tags_nesting >= base);
801
while( bb->tags_nesting > base ) {
802
bbcode_tag_pop(bb,NULL);
803
};
804
}
805
806
ic_private void bbcode_print( bbcode_t* bb, const char* s ) {
807
if (bb->out == NULL || bb->out_attrs == NULL || s == NULL) return;
808
assert(sbuf_len(bb->out) == 0 && attrbuf_len(bb->out_attrs) == 0);
809
bbcode_append( bb, s, bb->out, bb->out_attrs );
810
term_write_formatted( bb->term, sbuf_string(bb->out), attrbuf_attrs(bb->out_attrs,sbuf_len(bb->out)) );
811
attrbuf_clear(bb->out_attrs);
812
sbuf_clear(bb->out);
813
}
814
815
ic_private void bbcode_println( bbcode_t* bb, const char* s ) {
816
bbcode_print(bb,s);
817
term_writeln(bb->term, "");
818
}
819
820
ic_private void bbcode_vprintf( bbcode_t* bb, const char* fmt, va_list args ) {
821
if (bb->vout == NULL || fmt == NULL) return;
822
assert(sbuf_len(bb->vout) == 0);
823
sbuf_append_vprintf(bb->vout,fmt,args);
824
bbcode_print(bb, sbuf_string(bb->vout));
825
sbuf_clear(bb->vout);
826
}
827
828
ic_private void bbcode_printf( bbcode_t* bb, const char* fmt, ... ) {
829
va_list args;
830
va_start(args,fmt);
831
bbcode_vprintf(bb,fmt,args);
832
va_end(args);
833
}
834
835
ic_private ssize_t bbcode_column_width( bbcode_t* bb, const char* s ) {
836
if (s==NULL || s[0] == 0) return 0;
837
if (bb->vout == NULL) { return str_column_width(s); }
838
assert(sbuf_len(bb->vout) == 0);
839
bbcode_append( bb, s, bb->vout, NULL);
840
const ssize_t w = str_column_width(sbuf_string(bb->vout));
841
sbuf_clear(bb->vout);
842
return w;
843
}
844
845