[wip] Initial stab on inline images support, with many problems
[elinks/images.git] / src / document / renderer.c
blob384bec30d1faff10324ac189ddbf2d832c68f938
1 /** Generic renderer multiplexer
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <ctype.h>
9 #include <stdarg.h>
10 #include <stdlib.h>
11 #include <string.h>
13 #include "elinks.h"
15 #include "cache/cache.h"
16 #include "config/options.h"
17 #include "document/document.h"
18 #include "document/dom/renderer.h"
19 #include "document/html/frames.h"
20 #include "document/html/renderer.h"
21 #include "document/plain/renderer.h"
22 #include "document/renderer.h"
23 #include "document/view.h"
24 #include "ecmascript/ecmascript.h"
25 #include "encoding/encoding.h"
26 #include "intl/charsets.h"
27 #include "main/main.h"
28 #include "main/object.h"
29 #include "protocol/header.h"
30 #include "protocol/protocol.h"
31 #include "protocol/uri.h"
32 #include "session/location.h"
33 #include "session/session.h"
34 #include "terminal/terminal.h"
35 #include "terminal/window.h"
36 #include "util/error.h"
37 #include "util/memory.h"
38 #include "util/string.h"
39 #include "viewer/text/form.h"
40 #include "viewer/text/view.h"
41 #include "viewer/text/vs.h"
44 #ifdef CONFIG_ECMASCRIPT
45 /** @todo XXX: This function is de facto obsolete, since we do not need to copy
46 * snippets around anymore (we process them in one go after the document is
47 * loaded; gradual processing was practically impossible because the snippets
48 * could reorder randomly during the loading - consider i.e.
49 * @<body onLoad>@<script>@</body>: first just @<body> is loaded, but then the
50 * rest of the document is loaded and @<script> gets before @<body>; do not even
51 * imagine the trouble with rewritten (through scripting hooks) documents;
52 * besides, implementing document.write() will be much simpler).
53 * But I want to take no risk by reworking that now. --pasky */
54 static void
55 add_snippets(struct ecmascript_interpreter *interpreter,
56 LIST_OF(struct string_list_item) *doc_snippets,
57 LIST_OF(struct string_list_item) *queued_snippets)
59 struct string_list_item *doc_current = doc_snippets->next;
61 #ifdef CONFIG_LEDS
62 if (list_empty(*queued_snippets) && interpreter->vs->doc_view->session)
63 unset_led_value(interpreter->vs->doc_view->session->status.ecmascript_led);
64 #endif
66 if (list_empty(*doc_snippets)
67 || !get_opt_bool("ecmascript.enable", NULL))
68 return;
70 /* We do this all only once per view_state now. */
71 if (!list_empty(*queued_snippets)) {
72 /* So if we already did it, we shouldn't need to do it again.
73 * This is the case of moving around in history - we have all
74 * what happenned recorded in the view_state and needn't bother
75 * again. */
76 #ifdef CONFIG_DEBUG
77 /* Hopefully. */
78 struct string_list_item *iterator = queued_snippets->next;
80 while (iterator != (struct string_list_item *) queued_snippets) {
81 if (doc_current == (struct string_list_item *) doc_snippets) {
82 INTERNAL("add_snippets(): doc_snippets shorter than queued_snippets!");
83 return;
85 #if 0
86 DBG("Comparing snippets\n%.*s\n###### vs #####\n%.*s\n #####",
87 iterator->string.length, iterator->string.source,
88 doc_current->string.length, doc_current->string.source);
89 #endif
90 assert(!strlcmp(iterator->string.source,
91 iterator->string.length,
92 doc_current->string.source,
93 doc_current->string.length));
95 doc_current = doc_current->next;
96 iterator = iterator->next;
98 #endif
99 return;
102 assert(doc_current);
103 for (; doc_current != (struct string_list_item *) doc_snippets;
104 doc_current = doc_current->next) {
105 add_to_string_list(queued_snippets, doc_current->string.source,
106 doc_current->string.length);
107 #if 0
108 DBG("Adding snippet\n%.*s\n #####",
109 doc_current->string.length,
110 doc_current->string.source);
111 #endif
115 static void
116 process_snippets(struct ecmascript_interpreter *interpreter,
117 LIST_OF(struct string_list_item) *snippets,
118 struct string_list_item **current)
120 if (!*current)
121 *current = snippets->next;
122 for (; *current != (struct string_list_item *) snippets;
123 (*current) = (*current)->next) {
124 struct string *string = &(*current)->string;
125 unsigned char *uristring;
126 struct uri *uri;
127 struct cache_entry *cached;
128 struct fragment *fragment;
130 if (string->length == 0)
131 continue;
133 if (*string->source != '^') {
134 /* Evaluate <script>code</script> snippet */
135 ecmascript_eval(interpreter, string, NULL);
136 continue;
139 /* Eval external <script src="reference"></script> snippet */
140 uristring = string->source + 1;
141 if (!*uristring) continue;
143 uri = get_uri(uristring, URI_BASE);
144 if (!uri) continue;
146 cached = get_redirected_cache_entry(uri);
147 done_uri(uri);
149 if (!cached) {
150 /* At this time (!gradual_rerendering), we should've
151 * already retrieved this though. So it must've been
152 * that it went away because unused and the cache was
153 * already too full. */
154 #if 0
155 /* Disabled because gradual rerendering can be triggered
156 * by numerous events other than a ecmascript reference
157 * completing like the original document and CSS. Problem
158 * is that we should never continue this loop but rather
159 * break out if that is the case. Somehow we need to
160 * be able to derive URI loading problems at this point
161 * or maybe remove reference snippets if they fail to load.
163 * This FIFO queue handling should be used for also CSS
164 * imports so it would be cool if it could be general
165 * enough for that. Using it for frames with the FIFOing
166 * disabled probably wouldn't hurt either.
168 * To top this thing off it would be nice if it also
169 * handled dependency tracking between references so that
170 * CSS documents will not disappear from the cache
171 * before all referencing HTML documents has been deleted
172 * from it.
174 * Reported as bug 533. */
175 /* Pasky's explanation: If we get the doc in a single
176 * shot, before calling draw_formatted() we didn't have
177 * anything additional queued for loading and the cache
178 * entry was already loaded, so we didn't get
179 * gradual_loading set. But then while parsing the
180 * document we got some external references and trying
181 * to process them right now. Boom.
183 * The obvious solution would be to always call
184 * draw_formatted() with gradual_loading in
185 * doc_loading_callback() and if we are sure the
186 * loading is really over, call it one more time
187 * without gradual_loading set. I'm not sure about
188 * the implications though so I won't do it before
189 * 0.10.0. --pasky */
190 ERROR("The script of %s was lost in too full a cache!",
191 uristring);
192 #endif
193 continue;
196 fragment = get_cache_fragment(cached);
197 if (fragment) {
198 struct string code = INIT_STRING(fragment->data, fragment->length);
200 ecmascript_eval(interpreter, &code, NULL);
204 #endif
206 static void
207 render_encoded_document(struct cache_entry *cached, struct document *document)
209 struct uri *uri = cached->uri;
210 enum stream_encoding encoding = ENCODING_NONE;
211 struct fragment *fragment = get_cache_fragment(cached);
212 struct string buffer = INIT_STRING("", 0);
214 /* Even empty documents have to be rendered so that info in the protocol
215 * header, such as refresh info, get processed. (bug 625) */
216 if (fragment) {
217 buffer.source = fragment->data;
218 buffer.length = fragment->length;
221 if (uri->protocol != PROTOCOL_FILE) {
222 unsigned char *extension = get_extension_from_uri(uri);
224 if (extension) {
225 encoding = guess_encoding(extension);
226 mem_free(extension);
229 if (encoding != ENCODING_NONE) {
230 int length = 0;
231 unsigned char *source;
232 struct stream_encoded *stream = open_encoded(-1, encoding);
234 if (!stream) {
235 encoding = ENCODING_NONE;
236 } else {
237 source = decode_encoded_buffer(stream, encoding, buffer.source,
238 buffer.length, &length);
239 close_encoded(stream);
241 if (source) {
242 buffer.source = source;
243 buffer.length = length;
244 } else {
245 encoding = ENCODING_NONE;
251 if (document->options.plain) {
252 #ifdef CONFIG_DOM
253 if (cached->content_type
254 && (!c_strcasecmp("text/html", cached->content_type)
255 || !c_strcasecmp("application/xhtml+xml", cached->content_type)
256 || !c_strcasecmp("application/docbook+xml", cached->content_type)
257 || !c_strcasecmp("application/rss+xml", cached->content_type)
258 || !c_strcasecmp("application/xbel+xml", cached->content_type)
259 || !c_strcasecmp("application/x-xbel", cached->content_type)
260 || !c_strcasecmp("application/xbel", cached->content_type)))
261 render_dom_document(cached, document, &buffer);
262 else
263 #endif
264 render_plain_document(cached, document, &buffer);
266 } else {
267 #ifdef CONFIG_DOM
268 if (cached->content_type
269 && (!c_strlcasecmp("application/rss+xml", 19, cached->content_type, -1)))
270 render_dom_document(cached, document, &buffer);
271 else
272 #endif
273 render_html_document(cached, document, &buffer);
276 if (encoding != ENCODING_NONE) {
277 done_string(&buffer);
281 void
282 render_document(struct view_state *vs, struct document_view *doc_view,
283 struct document_options *options)
285 unsigned char *name;
286 struct document *document;
287 struct cache_entry *cached;
289 assert(vs && doc_view && options);
290 if_assert_failed return;
292 #if 0
293 DBG("(Re%u)Rendering %s on doc_view %p [%s] while attaching it to %p",
294 options->gradual_rerendering, struri(vs->uri),
295 doc_view, doc_view->name, vs);
296 #endif
298 name = doc_view->name;
299 doc_view->name = NULL;
301 detach_formatted(doc_view);
303 doc_view->name = name;
304 doc_view->vs = vs;
305 doc_view->last_x = doc_view->last_y = -1;
307 #if 0
308 /* This is a nice idea, but doesn't always work: in particular when
309 * there's a frame name conflict. You loaded something to the vs'
310 * frame, but later something tried to get loaded to a frame with
311 * the same name and we got back this frame again, so we are now
312 * overriding the original document with a cuckoo. This assert()ion
313 * should be re-enabled when we start to get this right (which is
314 * very complex, but someone should rewrite the frames support
315 * anyway). --pasky */
316 assert(!vs->doc_view);
317 #else
318 if (vs->doc_view) {
319 /* It will be still detached, no worries - hopefully it still
320 * resides in ses->scrn_frames. */
321 assert(vs->doc_view->vs == vs);
322 vs->doc_view->used = 0; /* A bit risky, but... */
323 vs->doc_view->vs = NULL;
324 vs->doc_view = NULL;
325 #ifdef CONFIG_ECMASCRIPT
326 vs->ecmascript_fragile = 1; /* And is this good? ;-) */
327 #endif
329 #endif
330 vs->doc_view = doc_view;
332 cached = find_in_cache(vs->uri);
333 if (!cached) {
334 INTERNAL("document %s to format not found", struri(vs->uri));
335 return;
338 document = get_cached_document(cached, options);
339 if (document) {
340 doc_view->document = document;
341 } else {
342 document = init_document(cached, options);
343 if (!document) return;
344 doc_view->document = document;
346 if (doc_view->session
347 && doc_view->session->reloadlevel > CACHE_MODE_NORMAL)
348 for (; vs->form_info_len > 0; vs->form_info_len--)
349 done_form_state(&vs->form_info[vs->form_info_len - 1]);
351 shrink_memory(0);
353 render_encoded_document(cached, document);
354 sort_links(document);
355 if (!document->title) {
356 enum uri_component components;
358 if (document->uri->protocol == PROTOCOL_FILE) {
359 components = URI_PATH;
360 } else {
361 components = URI_PUBLIC;
364 document->title = get_uri_string(document->uri, components);
365 if (document->title) {
366 #ifdef CONFIG_UTF8
367 if (doc_view->document->options.utf8)
368 decode_uri(document->title);
369 else
370 #endif /* CONFIG_UTF8 */
371 decode_uri_for_display(document->title);
375 #ifdef CONFIG_CSS
376 document->css_magic = get_document_css_magic(document);
377 #endif
378 #ifdef CONFIG_IMAGES
379 document->images_magic = get_document_images_magic(document);
380 #endif
382 #ifdef CONFIG_ECMASCRIPT
383 if (!vs->ecmascript_fragile)
384 assert(vs->ecmascript);
385 if (!options->gradual_rerendering) {
386 /* We also reset the state if the underlying document changed
387 * from the last time we did the snippets. This may be
388 * triggered i.e. when redrawing a document which has been
389 * reloaded in a different tab meanwhile (OTOH we don't want
390 * to reset the state if we are redrawing a document we have
391 * already drawn before).
393 * (vs->ecmascript->onload_snippets_owner) check may be
394 * superfluous since we should always have
395 * vs->ecmascript_fragile set in those cases; that's why we
396 * don't ever bother to re-zero it if we are suddenly doing
397 * gradual rendering again.
399 * XXX: What happens if a document is still loading in the
400 * other tab when we press ^L here? */
401 if (vs->ecmascript_fragile
402 || (vs->ecmascript
403 && vs->ecmascript->onload_snippets_cache_id
404 && document->cache_id != vs->ecmascript->onload_snippets_cache_id))
405 ecmascript_reset_state(vs);
406 /* If ecmascript_reset_state cannot construct a new
407 * ECMAScript interpreter, it sets vs->ecmascript =
408 * NULL and vs->ecmascript_fragile = 1. */
409 if (vs->ecmascript) {
410 vs->ecmascript->onload_snippets_cache_id = document->cache_id;
412 /* Passing of the onload_snippets pointers
413 * gives *_snippets() some feeling of
414 * universality, shall we ever get any other
415 * snippets (?). */
416 add_snippets(vs->ecmascript,
417 &document->onload_snippets,
418 &vs->ecmascript->onload_snippets);
419 process_snippets(vs->ecmascript,
420 &vs->ecmascript->onload_snippets,
421 &vs->ecmascript->current_onload_snippet);
424 #endif
426 /* If we do not care about the height and width of the document
427 * just use the setup values. */
429 copy_box(&doc_view->box, &document->options.box);
431 if (!document->options.needs_width)
432 doc_view->box.width = options->box.width;
434 if (!document->options.needs_height)
435 doc_view->box.height = options->box.height;
439 void
440 render_document_frames(struct session *ses, int no_cache)
442 struct document_options doc_opts;
443 struct document_view *doc_view;
444 struct document_view *current_doc_view = NULL;
445 struct view_state *vs = NULL;
447 if (!ses->doc_view) {
448 ses->doc_view = mem_calloc(1, sizeof(*ses->doc_view));
449 if (!ses->doc_view) return;
450 ses->doc_view->session = ses;
451 ses->doc_view->search_word = &ses->search_word;
454 if (have_location(ses)) vs = &cur_loc(ses)->vs;
456 init_document_options(ses, &doc_opts);
458 set_box(&doc_opts.box, 0, 0,
459 ses->tab->term->width, ses->tab->term->height);
461 if (ses->status.show_title_bar) {
462 doc_opts.box.y++;
463 doc_opts.box.height--;
465 if (ses->status.show_status_bar) doc_opts.box.height--;
466 if (ses->status.show_tabs_bar) {
467 doc_opts.box.height--;
468 if (ses->status.show_tabs_bar_at_top) doc_opts.box.y++;
471 doc_opts.color_mode = get_opt_int_tree(ses->tab->term->spec, "colors",
472 NULL);
473 if (!get_opt_bool_tree(ses->tab->term->spec, "underline", NULL))
474 doc_opts.color_flags |= COLOR_ENHANCE_UNDERLINE;
476 doc_opts.cp = get_terminal_codepage(ses->tab->term);
477 doc_opts.no_cache = no_cache & 1;
478 doc_opts.gradual_rerendering = !!(no_cache & 2);
480 if (vs) {
481 if (vs->plain < 0) vs->plain = 0;
482 doc_opts.plain = vs->plain;
483 doc_opts.wrap = vs->wrap;
484 } else {
485 doc_opts.plain = 1;
488 foreach (doc_view, ses->scrn_frames) doc_view->used = 0;
490 if (vs) render_document(vs, ses->doc_view, &doc_opts);
492 if (document_has_frames(ses->doc_view->document)) {
493 current_doc_view = current_frame(ses);
494 format_frames(ses, ses->doc_view->document->frame_desc, &doc_opts, 0);
497 foreach (doc_view, ses->scrn_frames) {
498 struct document_view *prev_doc_view = doc_view->prev;
500 if (doc_view->used) continue;
502 detach_formatted(doc_view);
503 del_from_list(doc_view);
504 mem_free(doc_view);
505 doc_view = prev_doc_view;
508 if (current_doc_view) {
509 int n = 0;
511 foreach (doc_view, ses->scrn_frames) {
512 if (document_has_frames(doc_view->document)) continue;
513 if (doc_view == current_doc_view) {
514 cur_loc(ses)->vs.current_link = n;
515 break;
517 n++;
522 /* comparison function for qsort() */
523 static int
524 comp_links(const void *v1, const void *v2)
526 const struct link *l1 = v1, *l2 = v2;
528 assert(l1 && l2);
529 if_assert_failed return 0;
530 return (l1->number - l2->number);
533 void
534 sort_links(struct document *document)
536 int i;
538 assert(document);
539 if_assert_failed return;
540 if (!document->nlinks) return;
542 if (document->links_sorted) return;
543 assert(document->links);
544 if_assert_failed return;
546 qsort(document->links, document->nlinks, sizeof(*document->links),
547 comp_links);
549 if (!document->height) return;
551 mem_free_if(document->lines1);
552 document->lines1 = mem_calloc(document->height, sizeof(*document->lines1));
553 mem_free_if(document->lines2);
554 if (!document->lines1) return;
555 document->lines2 = mem_calloc(document->height, sizeof(*document->lines2));
556 if (!document->lines2) {
557 mem_free(document->lines1);
558 return;
561 for (i = 0; i < document->nlinks; i++) {
562 struct link *link = &document->links[i];
563 int p, q, j;
565 if (!link->npoints) {
566 done_link_members(link);
567 memmove(link, link + 1,
568 (document->nlinks - i - 1) * sizeof(*link));
569 document->nlinks--;
570 i--;
571 continue;
573 p = link->points[0].y;
574 q = link->points[link->npoints - 1].y;
575 if (p > q) j = p, p = q, q = j;
576 for (j = p; j <= q; j++) {
577 assertm(j < document->height, "link out of screen");
578 if_assert_failed continue;
579 document->lines2[j] = &document->links[i];
580 if (!document->lines1[j])
581 document->lines1[j] = &document->links[i];
584 document->links_sorted = 1;
587 struct conv_table *
588 get_convert_table(unsigned char *head, int to_cp,
589 int default_cp, int *from_cp,
590 enum cp_status *cp_status, int ignore_server_cp)
592 unsigned char *part = head;
593 int cp_index = -1;
595 assert(head);
596 if_assert_failed return NULL;
598 if (ignore_server_cp) {
599 if (cp_status) *cp_status = CP_STATUS_IGNORED;
600 if (from_cp) *from_cp = default_cp;
601 return get_translation_table(default_cp, to_cp);
604 while (cp_index == -1) {
605 unsigned char *ct_charset;
606 /* scan_http_equiv() appends the meta http-equiv directives to
607 * the protocol header before this function is called, but the
608 * HTTP Content-Type header has precedence, so the HTTP header
609 * will be used if it exists and the meta header is only used
610 * as a fallback. See bug 983. */
611 unsigned char *a = parse_header(part, "Content-Type", &part);
613 if (!a) break;
615 parse_header_param(a, "charset", &ct_charset);
616 if (ct_charset) {
617 cp_index = get_cp_index(ct_charset);
618 mem_free(ct_charset);
620 mem_free(a);
623 if (cp_index == -1) {
624 unsigned char *a = parse_header(head, "Content-Charset", NULL);
626 if (a) {
627 cp_index = get_cp_index(a);
628 mem_free(a);
632 if (cp_index == -1) {
633 unsigned char *a = parse_header(head, "Charset", NULL);
635 if (a) {
636 cp_index = get_cp_index(a);
637 mem_free(a);
641 if (cp_index == -1) {
642 cp_index = default_cp;
643 if (cp_status) *cp_status = CP_STATUS_ASSUMED;
644 } else {
645 if (cp_status) *cp_status = CP_STATUS_SERVER;
648 if (from_cp) *from_cp = cp_index;
650 return get_translation_table(cp_index, to_cp);