elinks-0.11.0
[elinks/images.git] / src / document / plain / renderer.c
blobcdbad84ff6a347c80927151cd82292bd166a77f5
1 /* Plain text document renderer */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <ctype.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "bookmarks/bookmarks.h"
13 #include "cache/cache.h"
14 #include "config/options.h"
15 #include "document/docdata.h"
16 #include "document/document.h"
17 #include "document/options.h"
18 #include "document/plain/renderer.h"
19 #include "document/renderer.h"
20 #include "globhist/globhist.h"
21 #include "intl/charsets.h"
22 #include "protocol/protocol.h"
23 #include "protocol/uri.h"
24 #include "terminal/color.h"
25 #include "terminal/draw.h"
26 #include "util/color.h"
27 #include "util/error.h"
28 #include "util/memory.h"
29 #include "util/string.h"
32 struct plain_renderer {
33 /* The document being renderered */
34 struct document *document;
36 /* The data and data length of the defragmented cache entry */
37 unsigned char *source;
38 int length;
40 /* The convert table that should be used for converting line strings to
41 * the rendered strings. */
42 struct conv_table *convert_table;
44 /* The default template char data for text */
45 struct screen_char template;
47 /* The maximum width any line can have (used for wrapping text) */
48 int max_width;
50 /* The current line number */
51 int lineno;
53 /* Are we doing line compression */
54 unsigned int compress:1;
57 #define realloc_document_links(doc, size) \
58 ALIGN_LINK(&(doc)->links, (doc)->nlinks, size)
60 static struct screen_char *
61 realloc_line(struct document *document, int x, int y)
63 struct line *line = realloc_lines(document, y);
65 if (!line) return NULL;
67 if (x != line->length) {
68 if (!ALIGN_LINE(&line->chars, line->length, x))
69 return NULL;
71 line->length = x;
74 return line->chars;
77 static inline struct link *
78 add_document_link(struct document *document, unsigned char *uri, int length,
79 int x, int y)
81 struct link *link;
82 struct point *point;
84 if (!realloc_document_links(document, document->nlinks + 1))
85 return NULL;
87 link = &document->links[document->nlinks];
89 if (!realloc_points(link, length))
90 return NULL;
92 link->npoints = length;
93 link->type = LINK_HYPERTEXT;
94 link->where = uri;
95 link->color.background = document->options.default_bg;
96 link->color.foreground = document->options.default_link;
97 link->number = document->nlinks;
99 for (point = link->points; length > 0; length--, point++, x++) {
100 point->x = x;
101 point->y = y;
104 document->nlinks++;
106 return link;
109 /* Searches a word to find an email adress or an URI to add as a link. */
110 static inline struct link *
111 check_link_word(struct document *document, unsigned char *uri, int length,
112 int x, int y)
114 struct uri test;
115 unsigned char *where = NULL;
116 unsigned char *mailto = memchr(uri, '@', length);
117 int keep = uri[length];
118 struct link *new_link;
120 assert(document);
121 if_assert_failed return NULL;
123 uri[length] = 0;
125 if (mailto && mailto > uri && mailto - uri < length - 1) {
126 where = straconcat("mailto:", uri, NULL);
128 } else if (parse_uri(&test, uri) == URI_ERRNO_OK
129 && test.protocol != PROTOCOL_UNKNOWN
130 && (test.datalen || test.hostlen)) {
131 where = memacpy(uri, length);
134 uri[length] = keep;
136 if (!where) return NULL;
138 new_link = add_document_link(document, where, length, x, y);
140 if (!new_link) mem_free(where);
142 return new_link;
145 #define url_char(c) ( \
146 (c) > ' ' \
147 && (c) != '<' \
148 && (c) != '>' \
149 && (c) != '(' \
150 && (c) != ')' \
151 && !isquote(c))
153 static inline int
154 get_uri_length(unsigned char *line, int length)
156 int uri_end = 0;
158 while (uri_end < length
159 && url_char(line[uri_end]))
160 uri_end++;
162 for (; uri_end > 0; uri_end--) {
163 if (line[uri_end - 1] != '.'
164 && line[uri_end - 1] != ',')
165 break;
168 return uri_end;
171 static int
172 print_document_link(struct plain_renderer *renderer, int lineno,
173 unsigned char *line, int line_pos, int width,
174 int expanded, struct screen_char *pos)
176 struct document *document = renderer->document;
177 unsigned char *start = &line[line_pos];
178 int len = get_uri_length(start, width - line_pos);
179 int screen_column = line_pos + expanded;
180 struct link *new_link;
181 int link_end = line_pos + len;
182 unsigned char saved_char;
183 struct document_options *doc_opts = &document->options;
184 struct screen_char template = renderer->template;
185 int i;
187 if (!len) return 0;
189 new_link = check_link_word(document, start, len, screen_column,
190 lineno);
192 if (!new_link) return 0;
194 saved_char = line[link_end];
195 line[link_end] = '\0';
197 if (0)
198 ; /* Shut up compiler */
199 #ifdef CONFIG_GLOBHIST
200 else if (get_global_history_item(start))
201 new_link->color.foreground = doc_opts->default_vlink;
202 #endif
203 #ifdef CONFIG_BOOKMARKS
204 else if (get_bookmark(start))
205 new_link->color.foreground = doc_opts->default_bookmark_link;
206 #endif
207 else
208 new_link->color.foreground = doc_opts->default_link;
210 line[link_end] = saved_char;
212 new_link->color.background = doc_opts->default_bg;
214 set_term_color(&template, &new_link->color,
215 doc_opts->color_flags, doc_opts->color_mode);
217 for (i = len; i; i--) {
218 template.data = line[line_pos++];
219 copy_screen_chars(pos++, &template, 1);
222 return len;
225 static inline int
226 add_document_line(struct plain_renderer *renderer,
227 unsigned char *line, int line_width)
229 struct document *document = renderer->document;
230 struct screen_char *template = &renderer->template;
231 struct screen_char saved_renderer_template = *template;
232 struct screen_char *pos, *startpos;
233 int lineno = renderer->lineno;
234 int expanded = 0;
235 int width = line_width;
236 int line_pos;
238 line = convert_string(renderer->convert_table, line, width,
239 document->options.cp, CSM_NONE, &width,
240 NULL, NULL);
241 if (!line) return 0;
243 /* Now expand tabs */
244 for (line_pos = 0; line_pos < width; line_pos++) {
245 unsigned char line_char = line[line_pos];
247 if (line_char == ASCII_TAB
248 && (line_pos + 1 == width
249 || line[line_pos + 1] != ASCII_BS)) {
250 int tab_width = 7 - ((line_pos + expanded) & 7);
252 expanded += tab_width;
253 } else if (line_char == ASCII_BS) {
254 #if 0
255 This does not work: Suppose we have seventeen spaces
256 followed by a back-space; that will call for sixteen
257 bytes of memory, but we will print seventeen spaces
258 before we hit the back-space -- overflow!
260 /* Don't count the character
261 * that the back-space character will delete */
262 if (expanded + line_pos)
263 expanded--;
264 #endif
265 #if 0
266 /* Don't count the back-space character */
267 if (expanded > 0)
268 expanded--;
269 #endif
273 assert(expanded >= 0);
275 startpos = pos = realloc_line(document, width + expanded, lineno);
276 if (!pos) {
277 mem_free(line);
278 return 0;
281 expanded = 0;
282 for (line_pos = 0; line_pos < width; line_pos++) {
283 unsigned char line_char = line[line_pos];
284 unsigned char next_char, prev_char;
286 prev_char = line_pos > 0 ? line[line_pos - 1] : '\0';
287 next_char = (line_pos + 1 < width) ? line[line_pos + 1]
288 : '\0';
290 /* Do not expand tabs that precede back-spaces; this saves the
291 * back-space code some trouble. */
292 if (line_char == ASCII_TAB && next_char != ASCII_BS) {
293 int tab_width = 7 - ((line_pos + expanded) & 7);
295 expanded += tab_width;
297 template->data = ' ';
299 copy_screen_chars(pos++, template, 1);
300 while (tab_width--);
302 *template = saved_renderer_template;
304 } else if (line_char == ASCII_BS) {
305 if (!(expanded + line_pos)) {
306 /* We've backspaced to the start of the line */
307 continue;
310 if (pos > startpos)
311 pos--; /* Backspace */
313 /* Handle x^H_ as _^Hx, but prevent an infinite loop
314 * swapping two underscores. */
315 if (next_char == '_' && prev_char != '_') {
316 /* x^H_ becomes _^Hx */
317 if (line_pos - 1 >= 0)
318 line[line_pos - 1] = next_char;
319 if (line_pos + 1 < width)
320 line[line_pos + 1] = prev_char;
322 /* Go back and reparse the swapped characters */
323 if (line_pos - 2 >= 0)
324 line_pos -= 2;
325 continue;
328 if ((expanded + line_pos) - 2 >= 0) {
329 /* Don't count the backspace character or the
330 * deleted character when returning the line's
331 * width or when expanding tabs. */
332 expanded -= 2;
335 if (pos->data == '_' && next_char == '_') {
336 /* Is _^H_ an underlined underscore
337 * or an emboldened underscore? */
339 if (expanded + line_pos >= 0
340 && pos - 1 >= startpos
341 && (pos - 1)->attr) {
342 /* There is some preceding text,
343 * and it has an attribute; copy it */
344 template->attr |= (pos - 1)->attr;
345 } else {
346 /* Default to bold; seems more useful
347 * than underlining the underscore */
348 template->attr |= SCREEN_ATTR_BOLD;
351 } else if (pos->data == '_') {
352 /* Underline _^Hx */
354 template->attr |= SCREEN_ATTR_UNDERLINE;
356 } else if (pos->data == next_char) {
357 /* Embolden x^Hx */
359 template->attr |= SCREEN_ATTR_BOLD;
362 /* Handle _^Hx^Hx as both bold and underlined */
363 if (template->attr)
364 template->attr |= pos->attr;
365 } else {
366 int added_chars = 0;
368 if (document->options.plain_display_links
369 && isalpha(line_char) && isalpha(next_char)) {
370 /* We only want to check for a URI if there are
371 * at least two consecutive alphabetic
372 * characters, or if we are at the very start of
373 * the line. It improves performance a bit.
374 * --Zas */
375 added_chars = print_document_link(renderer,
376 lineno, line,
377 line_pos,
378 width,
379 expanded,
380 pos);
383 if (added_chars) {
384 line_pos += added_chars - 1;
385 pos += added_chars;
386 } else {
387 if (!isscreensafe(line_char))
388 line_char = '.';
389 template->data = line_char;
390 copy_screen_chars(pos++, template, 1);
392 /* Detect copy of nul chars to screen, this
393 * should not occur. --Zas */
394 assert(line_char);
397 *template = saved_renderer_template;
401 mem_free(line);
403 realloc_line(document, pos - startpos, lineno);
405 return width + expanded;
408 static void
409 init_template(struct screen_char *template, struct document_options *options)
411 color_T background = options->default_bg;
412 color_T foreground = options->default_fg;
413 struct color_pair colors = INIT_COLOR_PAIR(background, foreground);
415 template->attr = 0;
416 template->data = ' ';
417 set_term_color(template, &colors,
418 options->color_flags, options->color_mode);
421 static struct node *
422 add_node(struct plain_renderer *renderer, int x, int width, int height)
424 struct node *node = mem_alloc(sizeof(*node));
426 if (node) {
427 struct document *document = renderer->document;
429 set_box(&node->box, x, renderer->lineno, width, height);
431 int_lower_bound(&document->width, width);
432 int_lower_bound(&document->height, height);
434 add_to_list(document->nodes, node);
437 return node;
440 static void
441 add_document_lines(struct plain_renderer *renderer)
443 unsigned char *source = renderer->source;
444 int length = renderer->length;
445 int was_empty_line = 0;
447 for (; length > 0; renderer->lineno++) {
448 unsigned char *xsource;
449 int width, added, only_spaces = 1, spaces = 0, was_spaces = 0;
450 int last_space = 0;
451 int step = 0;
452 int doc_width = int_min(renderer->max_width, length);
454 /* End of line detection: We handle \r, \r\n and \n types. */
455 for (width = 0; width < doc_width; width++) {
456 if (source[width] == ASCII_CR)
457 step++;
458 if (source[width + step] == ASCII_LF)
459 step++;
460 if (step) break;
462 if (isspace(source[width])) {
463 last_space = width;
464 if (only_spaces)
465 spaces++;
466 else
467 was_spaces++;
468 } else {
469 only_spaces = 0;
470 was_spaces = 0;
474 if (only_spaces && step) {
475 if (renderer->compress && was_empty_line) {
476 /* Successive empty lines will appear as one. */
477 length -= step + spaces;
478 source += step + spaces;
479 renderer->lineno--;
480 assert(renderer->lineno >= 0);
481 continue;
483 was_empty_line = 1;
485 /* No need to keep whitespaces on an empty line. */
486 source += spaces;
487 length -= spaces;
488 width -= spaces;
490 } else {
491 was_empty_line = 0;
493 if (was_spaces && step) {
494 /* Drop trailing whitespaces. */
495 width -= was_spaces;
496 step += was_spaces;
498 if (!step && (width < length) && last_space) {
499 width = last_space;
500 step = 1;
504 assert(width >= 0);
506 /* We will touch the supplied source, so better replicate it. */
507 xsource = memacpy(source, width);
508 if (!xsource) continue;
510 added = add_document_line(renderer, xsource, width);
511 mem_free(xsource);
513 if (added) {
514 /* Add (search) nodes on a line by line basis */
515 add_node(renderer, 0, added, 1);
518 /* Skip end of line chars too. */
519 width += step;
520 length -= width;
521 source += width;
524 assert(!length);
527 void
528 render_plain_document(struct cache_entry *cached, struct document *document,
529 struct string *buffer)
531 struct conv_table *convert_table;
532 unsigned char *head = empty_string_or_(cached->head);
533 struct plain_renderer renderer;
535 convert_table = get_convert_table(head, document->options.cp,
536 document->options.assume_cp,
537 &document->cp,
538 &document->cp_status,
539 document->options.hard_assume);
541 renderer.source = buffer->source;
542 renderer.length = buffer->length;
544 renderer.document = document;
545 renderer.lineno = 0;
546 renderer.convert_table = convert_table;
547 renderer.compress = document->options.plain_compress_empty_lines;
548 renderer.max_width = document->options.wrap ? document->options.box.width
549 : INT_MAX;
551 document->bgcolor = document->options.default_bg;
552 document->width = 0;
554 /* Setup the style */
555 init_template(&renderer.template, &document->options);
557 add_document_lines(&renderer);