Remember fragment of the splitted char and decode it next time. Idea by Jonas.
[elinks.git] / src / document / html / renderer.c
blob4322c456feddcb38c8ad8b5bdb00e33f45bdda5e
1 /* HTML renderer */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <ctype.h>
8 #include <stdarg.h>
9 #include <string.h>
11 #include "elinks.h"
13 #include "cache/cache.h"
14 #include "config/options.h"
15 #include "document/docdata.h"
16 #include "document/document.h"
17 #include "document/html/frames.h"
18 #include "document/html/parser.h"
19 #include "document/html/parser/parse.h"
20 #include "document/html/renderer.h"
21 #include "document/html/tables.h"
22 #include "document/options.h"
23 #include "document/refresh.h"
24 #include "document/renderer.h"
25 #include "intl/charsets.h"
26 #include "osdep/types.h"
27 #include "protocol/uri.h"
28 #include "session/session.h"
29 #include "terminal/color.h"
30 #include "terminal/draw.h"
31 #include "util/color.h"
32 #include "util/conv.h"
33 #include "util/error.h"
34 #include "util/hash.h"
35 #include "util/lists.h"
36 #include "util/memory.h"
37 #include "util/string.h"
38 #include "util/time.h"
39 #include "viewer/text/form.h"
40 #include "viewer/text/view.h"
41 #include "viewer/text/vs.h"
43 /* Unsafe macros */
44 #include "document/html/internal.h"
46 /* Types and structs */
48 /* Tags are used for ``id''s or anchors in the document referenced by the
49 * fragment part of the URI. */
50 /* FIXME: This and find_tag() should be part of the general infrastructure
51 * in document/document.*. --pasky */
52 struct tag {
53 LIST_HEAD(struct tag);
55 int x, y;
56 unsigned char name[1]; /* must be last of struct. --Zas */
59 enum link_state {
60 LINK_STATE_NONE,
61 LINK_STATE_NEW,
62 LINK_STATE_SAME,
65 struct link_state_info {
66 unsigned char *link;
67 unsigned char *target;
68 unsigned char *image;
69 struct form_control *form;
72 struct table_cache_entry_key {
73 unsigned char *start;
74 unsigned char *end;
75 int align;
76 int margin;
77 int width;
78 int x;
79 int link_num;
82 struct table_cache_entry {
83 LIST_HEAD(struct table_cache_entry);
85 struct table_cache_entry_key key;
86 struct part part;
89 /* Max. entries in table cache used for nested tables. */
90 #define MAX_TABLE_CACHE_ENTRIES 16384
92 /* Global variables */
93 static int table_cache_entries;
94 static struct hash *table_cache;
97 struct renderer_context {
98 int last_link_to_move;
99 struct tag *last_tag_to_move;
100 /* All tags between document->tags and this tag (inclusive) should
101 * be aligned to the next line break, unless some real content follows
102 * the tag. Therefore, this virtual tags list accumulates new tags as
103 * they arrive and empties when some real content is written; if a line
104 * break is inserted in the meanwhile, the tags follow it (ie. imagine
105 * <a name="x"> <p>, then the "x" tag follows the line breaks inserted
106 * by the <p> tag). */
107 struct tag *last_tag_for_newline;
109 struct link_state_info link_state_info;
111 struct conv_table *convert_table;
113 /* Used for setting cache info from HTTP-EQUIV meta tags. */
114 struct cache_entry *cached;
116 int g_ctrl_num;
117 int subscript; /* Count stacked subscripts */
118 int supscript; /* Count stacked supscripts */
120 unsigned int empty_format:1;
121 unsigned int nobreak:1;
122 unsigned int nosearchable:1;
123 unsigned int nowrap:1; /* Activated/deactivated by SP_NOWRAP. */
126 static struct renderer_context renderer_context;
129 /* Prototypes */
130 static void line_break(struct html_context *);
131 static void put_chars(struct html_context *, unsigned char *, int);
133 #define X(x_) (part->box.x + (x_))
134 #define Y(y_) (part->box.y + (y_))
136 #define SPACES_GRANULARITY 0x7F
138 #define ALIGN_SPACES(x, o, n) mem_align_alloc(x, o, n, SPACES_GRANULARITY)
140 static inline void
141 set_screen_char_color(struct screen_char *schar,
142 color_T bgcolor, color_T fgcolor,
143 enum color_flags color_flags,
144 enum color_mode color_mode)
146 struct color_pair colors = INIT_COLOR_PAIR(bgcolor, fgcolor);
148 set_term_color(schar, &colors, color_flags, color_mode);
151 static int
152 realloc_line(struct html_context *html_context, struct document *document,
153 int y, int length)
155 struct screen_char *pos, *end;
156 struct line *line;
158 if (!realloc_lines(document, y))
159 return -1;
161 line = &document->data[y];
163 if (length < line->length)
164 return 0;
166 if (!ALIGN_LINE(&line->chars, line->length, length + 1))
167 return -1;
169 /* We cannot rely on the aligned allocation to clear the members for us
170 * since for line splitting we simply trim the length. Question is if
171 * it is better to to clear the line after the splitting or here. */
172 end = &line->chars[length];
173 end->data = ' ';
174 end->attr = 0;
175 set_screen_char_color(end, par_format.bgcolor, 0x0,
176 0, document->options.color_mode);
178 for (pos = &line->chars[line->length]; pos < end; pos++) {
179 copy_screen_chars(pos, end, 1);
182 line->length = length + 1;
184 return 0;
187 void
188 expand_lines(struct html_context *html_context, struct part *part,
189 int x, int y, int lines, color_T bgcolor)
191 int line;
193 assert(part && part->document);
194 if_assert_failed return;
196 if (!use_document_bg_colors(&part->document->options))
197 return;
199 par_format.bgcolor = bgcolor;
201 for (line = 0; line < lines; line++)
202 realloc_line(html_context, part->document, Y(y + line), X(x));
205 static inline int
206 realloc_spaces(struct part *part, int length)
208 if (length < part->spaces_len)
209 return 0;
211 if (!ALIGN_SPACES(&part->spaces, part->spaces_len, length))
212 return -1;
213 #ifdef CONFIG_UTF_8
214 if (!ALIGN_SPACES(&part->char_width, part->spaces_len, length))
215 return -1;
216 #endif
218 part->spaces_len = length;
220 return 0;
224 #define LINE(y_) part->document->data[Y(y_)]
225 #define POS(x_, y_) LINE(y_).chars[X(x_)]
226 #define LEN(y_) int_max(LINE(y_).length - part->box.x, 0)
229 /* When we clear chars we want to preserve and use the background colors
230 * already in place else we could end up ``staining'' the background especial
231 * when drawing table cells. So make the cleared chars share the colors in
232 * place. */
233 static inline void
234 clear_hchars(struct html_context *html_context, int x, int y, int width)
236 struct part *part;
237 struct screen_char *pos, *end;
239 assert(html_context);
240 if_assert_failed return;
242 part = html_context->part;
244 assert(part && part->document && width > 0);
245 if_assert_failed return;
247 if (realloc_line(html_context, part->document, Y(y), X(x) + width - 1))
248 return;
250 assert(part->document->data);
251 if_assert_failed return;
253 pos = &POS(x, y);
254 end = pos + width - 1;
255 end->data = ' ';
256 end->attr = 0;
257 set_screen_char_color(end, par_format.bgcolor, 0x0,
258 0, part->document->options.color_mode);
260 while (pos < end)
261 copy_screen_chars(pos++, end, 1);
264 /* TODO: Merge parts with get_format_screen_char(). --jonas */
265 /* Allocates the required chars on the given line and returns the char at
266 * position (x, y) ready to be used as a template char. */
267 static inline struct screen_char *
268 get_frame_char(struct html_context *html_context, struct part *part,
269 int x, int y, unsigned char data,
270 color_T bgcolor, color_T fgcolor)
272 struct screen_char *template;
274 assert(html_context);
275 if_assert_failed return NULL;
277 assert(part && part->document && x >= 0 && y >= 0);
278 if_assert_failed return NULL;
280 if (realloc_line(html_context, part->document, Y(y), X(x)))
281 return NULL;
283 assert(part->document->data);
284 if_assert_failed return NULL;
286 template = &POS(x, y);
287 template->data = data;
288 template->attr = SCREEN_ATTR_FRAME;
289 set_screen_char_color(template, bgcolor, fgcolor,
290 part->document->options.color_flags,
291 part->document->options.color_mode);
293 return template;
296 void
297 draw_frame_hchars(struct part *part, int x, int y, int width,
298 unsigned char data, color_T bgcolor, color_T fgcolor,
299 struct html_context *html_context)
301 struct screen_char *template;
303 assert(width > 0);
304 if_assert_failed return;
306 template = get_frame_char(html_context, part, x + width - 1, y, data, bgcolor, fgcolor);
307 if (!template) return;
309 /* The template char is the last we need to draw so only decrease @width. */
310 for (width -= 1; width; width--, x++) {
311 copy_screen_chars(&POS(x, y), template, 1);
315 void
316 draw_frame_vchars(struct part *part, int x, int y, int height,
317 unsigned char data, color_T bgcolor, color_T fgcolor,
318 struct html_context *html_context)
320 struct screen_char *template = get_frame_char(html_context, part, x, y,
321 data, bgcolor, fgcolor);
323 if (!template) return;
325 /* The template char is the first vertical char to be drawn. So
326 * copy it to the rest. */
327 for (height -= 1, y += 1; height; height--, y++) {
328 if (realloc_line(html_context, part->document, Y(y), X(x)))
329 return;
331 copy_screen_chars(&POS(x, y), template, 1);
335 static inline struct screen_char *
336 get_format_screen_char(struct html_context *html_context,
337 enum link_state link_state)
339 static struct text_attrib_style ta_cache = { -1, 0x0, 0x0 };
340 static struct screen_char schar_cache;
342 if (memcmp(&ta_cache, &format.style, sizeof(ta_cache))) {
343 copy_struct(&ta_cache, &format.style);
345 schar_cache.attr = 0;
346 if (format.style.attr) {
347 if (format.style.attr & AT_UNDERLINE) {
348 schar_cache.attr |= SCREEN_ATTR_UNDERLINE;
351 if (format.style.attr & AT_BOLD) {
352 schar_cache.attr |= SCREEN_ATTR_BOLD;
355 if (format.style.attr & AT_ITALIC) {
356 schar_cache.attr |= SCREEN_ATTR_ITALIC;
359 if (format.style.attr & AT_GRAPHICS) {
360 schar_cache.attr |= SCREEN_ATTR_FRAME;
364 if (link_state != LINK_STATE_NONE
365 && html_context->options->underline_links) {
366 schar_cache.attr |= SCREEN_ATTR_UNDERLINE;
369 set_screen_char_color(&schar_cache, format.style.bg, format.style.fg,
370 html_context->options->color_flags,
371 html_context->options->color_mode);
374 if (!!(schar_cache.attr & SCREEN_ATTR_UNSEARCHABLE)
375 ^ !!renderer_context.nosearchable) {
376 schar_cache.attr ^= SCREEN_ATTR_UNSEARCHABLE;
379 return &schar_cache;
382 #ifdef CONFIG_UTF_8
383 /* First possibly do the format change and then find out what coordinates
384 * to use since sub- or superscript might change them */
385 static inline int
386 set_hline(struct html_context *html_context, unsigned char *chars, int charslen,
387 enum link_state link_state)
389 struct part *part = html_context->part;
390 struct screen_char *schar = get_format_screen_char(html_context,
391 link_state);
392 int x = part->cx;
393 int y = part->cy;
394 int x2 = x;
395 int len = charslen;
396 int utf8 = html_context->options->utf8;
398 assert(part);
399 if_assert_failed return len;
401 assert(charslen >= 0);
403 if (realloc_spaces(part, x + charslen))
404 return len;
406 if (part->document) {
407 if (realloc_line(html_context, part->document,
408 Y(y), X(x) + charslen - 1))
409 return len;
410 if (utf8) {
411 unsigned char *end = chars + charslen;
412 unicode_val_T data;
414 if (part->document->buf_length) {
415 /* previous char was broken in the middle */
416 int length = utf8charlen(part->document->buf);
417 unsigned char i;
418 unsigned char *buf_ptr = part->document->buf;
420 for (i = part->document->buf_length; i < length && chars < end;) {
421 part->document->buf[i++] = *chars++;
423 part->document->buf_length = i;
424 part->document->buf[i] = '\0';
425 data = utf_8_to_unicode(&buf_ptr, buf_ptr + i);
426 if (data != UCS_NO_CHAR) {
427 part->document->buf_length = 0;
428 goto good_char;
429 } else {
430 /* Still not full char */
431 return len;
435 for (; chars < end; x++) {
436 if (*chars == NBSP_CHAR) {
437 schar->data = ' ';
438 part->spaces[x] = html_context->options->wrap_nbsp;
439 part->char_width[x] = 1;
440 chars++;
441 } else {
442 part->spaces[x] = (*chars == ' ');
443 data = utf_8_to_unicode(&chars, end);
444 if (data == UCS_NO_CHAR) {
445 if (charslen == 1) {
446 /* HR */
447 unsigned char attr = schar->attr;
449 schar->data = *chars++;
450 schar->attr = SCREEN_ATTR_FRAME;
451 copy_screen_chars(&POS(x, y), schar, 1);
452 schar->attr = attr;
453 part->char_width[x] = 0;
454 continue;
455 } else {
456 unsigned char i;
457 /* broken char */
458 for (i = 0; chars < end;i++) {
459 part->document->buf[i] = *chars++;
461 part->document->buf_length = i;
462 return x - x2;
464 } else {
465 good_char:
466 if (unicode_to_cell(data) == 2) {
467 schar->data = (unicode_val_T)data;
468 part->char_width[x] = 2;
469 copy_screen_chars(&POS(x++, y), schar, 1);
470 schar->data = UCS_NO_CHAR;
471 part->spaces[x] = 0;
472 part->char_width[x] = 0;
473 } else {
474 part->char_width[x] = unicode_to_cell(data);
475 schar->data = (unicode_val_T)data;
479 copy_screen_chars(&POS(x, y), schar, 1);
481 } else {
482 for (; charslen > 0; charslen--, x++, chars++) {
483 part->char_width[x] = 1;
484 if (*chars == NBSP_CHAR) {
485 schar->data = ' ';
486 part->spaces[x] = html_context->options->wrap_nbsp;
487 } else {
488 part->spaces[x] = (*chars == ' ');
489 schar->data = *chars;
491 copy_screen_chars(&POS(x, y), schar, 1);
495 len = x - x2;
496 } else {
497 if (utf8) {
498 unsigned char *end;
500 for (end = chars + charslen; chars < end; x++) {
501 unicode_val_T data;
503 part->spaces[x] = (*chars == ' ');
504 data = utf_8_to_unicode(&chars, end);
505 part->char_width[x] = unicode_to_cell(data);
506 if (part->char_width[x] == 2) {
507 x++;
508 part->spaces[x] = 0;
509 part->char_width[x] = 0;
511 if (data == UCS_NO_CHAR) {
512 chars++;
513 part->char_width[x] = 0;
514 x++;
517 len = x - x2;
518 } else {
519 for (; charslen > 0; charslen--, x++, chars++) {
520 part->spaces[x] = (*chars == ' ');
521 part->char_width[x] = 1;
525 return len;
527 #else
529 /* First possibly do the format change and then find out what coordinates
530 * to use since sub- or superscript might change them */
531 static inline void
532 set_hline(struct html_context *html_context, unsigned char *chars, int charslen,
533 enum link_state link_state)
535 struct part *part = html_context->part;
536 struct screen_char *schar = get_format_screen_char(html_context,
537 link_state);
538 int x = part->cx;
539 int y = part->cy;
541 assert(part);
542 if_assert_failed return;
544 if (realloc_spaces(part, x + charslen))
545 return;
547 if (part->document) {
548 if (realloc_line(html_context, part->document,
549 Y(y), X(x) + charslen - 1))
550 return;
552 for (; charslen > 0; charslen--, x++, chars++) {
553 if (*chars == NBSP_CHAR) {
554 schar->data = ' ';
555 part->spaces[x] = html_context->options->wrap_nbsp;
556 } else {
557 part->spaces[x] = (*chars == ' ');
558 schar->data = *chars;
560 copy_screen_chars(&POS(x, y), schar, 1);
562 } else {
563 for (; charslen > 0; charslen--, x++, chars++) {
564 part->spaces[x] = (*chars == ' ');
568 #endif /* CONFIG_UTF_8 */
570 static void
571 move_links(struct html_context *html_context, int xf, int yf, int xt, int yt)
573 struct part *part;
574 struct tag *tag;
575 int nlink = renderer_context.last_link_to_move;
576 int matched = 0;
578 assert(html_context);
579 if_assert_failed return;
581 part = html_context->part;
583 assert(part && part->document);
584 if_assert_failed return;
586 if (!realloc_lines(part->document, Y(yt)))
587 return;
589 for (; nlink < part->document->nlinks; nlink++) {
590 struct link *link = &part->document->links[nlink];
591 int i;
593 for (i = 0; i < link->npoints; i++) {
594 /* Fix for bug 479 (part one) */
595 /* The scenario that triggered it:
597 * Imagine a centered element containing a really long
598 * word (over half of the screen width long) followed
599 * by a few links with no spaces between them where all
600 * the link text combined with the really long word
601 * will force the line to be wrapped. When rendering
602 * the line first words (including link text words) are
603 * put on one line. Then wrapping is performed moving
604 * all links from current line to the one below. Then
605 * the current line (now only containing the really
606 * long word) is centered. This will trigger a call to
607 * move_links() which will increment.
609 * Without the fix below the centering of the current
610 * line will increment last_link_to_move to that of the
611 * last link which means centering of the next line
612 * with all the links will only move the last link
613 * leaving all the other links' points dangling and
614 * causing buggy link highlighting.
616 * Even links like textareas will be correctly handled
617 * because @last_link_to_move is a way to optimize how
618 * many links move_links() will have to iterate and
619 * this little fix will only decrease the effect of the
620 * optimization by always ensuring it is never
621 * incremented too far. */
622 if (!matched && link->points[i].y > Y(yf)) {
623 matched = 1;
624 continue;
627 if (link->points[i].y != Y(yf))
628 continue;
630 matched = 1;
632 if (link->points[i].x < X(xf))
633 continue;
635 if (yt >= 0) {
636 link->points[i].y = Y(yt);
637 link->points[i].x += -xf + xt;
638 } else {
639 int to_move = link->npoints - (i + 1);
641 assert(to_move >= 0);
643 if (to_move > 0) {
644 memmove(&link->points[i],
645 &link->points[i + 1],
646 to_move *
647 sizeof(*link->points));
648 i--;
651 link->npoints--;
655 if (!matched) {
656 renderer_context.last_link_to_move = nlink;
660 /* Don't move tags when removing links. */
661 if (yt < 0) return;
663 matched = 0;
664 tag = renderer_context.last_tag_to_move;
666 while (list_has_next(part->document->tags, tag)) {
667 tag = tag->next;
669 if (tag->y == Y(yf)) {
670 matched = 1;
671 if (tag->x >= X(xf)) {
672 tag->y = Y(yt);
673 tag->x += -xf + xt;
676 } else if (!matched && tag->y > Y(yf)) {
677 /* Fix for bug 479 (part two) */
678 matched = 1;
681 if (!matched) renderer_context.last_tag_to_move = tag;
685 static inline void
686 copy_chars(struct html_context *html_context, int x, int y, int width, struct screen_char *d)
688 struct part *part;
690 assert(html_context);
691 if_assert_failed return;
693 part = html_context->part;
695 assert(width > 0 && part && part->document && part->document->data);
696 if_assert_failed return;
698 if (realloc_line(html_context, part->document, Y(y), X(x) + width - 1))
699 return;
701 copy_screen_chars(&POS(x, y), d, width);
704 static inline void
705 move_chars(struct html_context *html_context, int x, int y, int nx, int ny)
707 struct part *part;
709 assert(html_context);
710 if_assert_failed return;
712 part = html_context->part;
714 assert(part && part->document && part->document->data);
715 if_assert_failed return;
717 if (LEN(y) - x <= 0) return;
718 copy_chars(html_context, nx, ny, LEN(y) - x, &POS(x, y));
720 LINE(y).length = X(x);
721 move_links(html_context, x, y, nx, ny);
724 static inline void
725 shift_chars(struct html_context *html_context, int y, int shift)
727 struct part *part;
728 struct screen_char *a;
729 int len;
731 assert(html_context);
732 if_assert_failed return;
734 part = html_context->part;
736 assert(part && part->document && part->document->data);
737 if_assert_failed return;
739 len = LEN(y);
741 a = fmem_alloc(len * sizeof(*a));
742 if (!a) return;
744 copy_screen_chars(a, &POS(0, y), len);
746 clear_hchars(html_context, 0, y, shift);
747 copy_chars(html_context, shift, y, len, a);
748 fmem_free(a);
750 move_links(html_context, 0, y, shift, y);
753 static inline void
754 del_chars(struct html_context *html_context, int x, int y)
756 struct part *part;
758 assert(html_context);
759 if_assert_failed return;
761 part = html_context->part;
763 assert(part && part->document && part->document->data);
764 if_assert_failed return;
766 LINE(y).length = X(x);
767 move_links(html_context, x, y, -1, -1);
770 #if TABLE_LINE_PADDING < 0
771 # define overlap_width(x) (x).width
772 #else
773 # define overlap_width(x) int_min((x).width, \
774 html_context->options->box.width - TABLE_LINE_PADDING)
775 #endif
776 #define overlap(x) int_max(overlap_width(x) - (x).rightmargin, 0)
778 static int inline
779 split_line_at(struct html_context *html_context, int width)
781 struct part *part;
782 int tmp;
783 int new_width = width + par_format.rightmargin;
785 assert(html_context);
786 if_assert_failed return 0;
788 part = html_context->part;
790 assert(part);
791 if_assert_failed return 0;
793 /* Make sure that we count the right margin to the total
794 * actual box width. */
795 int_lower_bound(&part->box.width, new_width);
797 if (part->document) {
798 assert(part->document->data);
799 if_assert_failed return 0;
800 #ifdef CONFIG_UTF_8
801 if (html_context->options->utf8
802 && width < part->spaces_len && part->char_width[width] == 2) {
803 move_chars(html_context, width, part->cy, par_format.leftmargin, part->cy + 1);
804 del_chars(html_context, width, part->cy);
805 } else
806 #endif
808 assertm(POS(width, part->cy).data == ' ',
809 "bad split: %c", POS(width, part->cy).data);
810 move_chars(html_context, width + 1, part->cy, par_format.leftmargin, part->cy + 1);
811 del_chars(html_context, width, part->cy);
816 #ifdef CONFIG_UTF_8
817 if (!(html_context->options->utf8
818 && width < part->spaces_len
819 && part->char_width[width] == 2))
820 #endif
821 width++; /* Since we were using (x + 1) only later... */
823 tmp = part->spaces_len - width;
824 if (tmp > 0) {
825 /* 0 is possible and I'm paranoid ... --Zas */
826 memmove(part->spaces, part->spaces + width, tmp);
827 #ifdef CONFIG_UTF_8
828 memmove(part->char_width, part->char_width + width, tmp);
829 #endif
832 assert(tmp >= 0);
833 if_assert_failed tmp = 0;
834 memset(part->spaces + tmp, 0, width);
835 #ifdef CONFIG_UTF_8
836 memset(part->char_width + tmp, 0, width);
837 #endif
839 if (par_format.leftmargin > 0) {
840 tmp = part->spaces_len - par_format.leftmargin;
841 assertm(tmp > 0, "part->spaces_len - par_format.leftmargin == %d", tmp);
842 /* So tmp is zero, memmove() should survive that. Don't recover. */
843 memmove(part->spaces + par_format.leftmargin, part->spaces, tmp);
844 #ifdef CONFIG_UTF_8
845 memmove(part->char_width + par_format.leftmargin, part->char_width, tmp);
846 #endif
849 part->cy++;
851 if (part->cx == width) {
852 part->cx = -1;
853 int_lower_bound(&part->box.height, part->cy);
854 return 2;
855 } else {
856 part->cx -= width - par_format.leftmargin;
857 int_lower_bound(&part->box.height, part->cy + 1);
858 return 1;
862 /* Here, we scan the line for a possible place where we could split it into two
863 * (breaking it, because it is too long), if it is overlapping from the maximal
864 * box width. */
865 /* Returns 0 if there was found no spot suitable for breaking the line.
866 * 1 if the line was split into two.
867 * 2 if the (second) splitted line is blank (that is useful to determine
868 * ie. if the next line_break() should really break the line; we don't
869 * want to see any blank lines to pop up, do we?). */
870 static int
871 split_line(struct html_context *html_context)
873 struct part *part;
874 int x;
876 assert(html_context);
877 if_assert_failed return 0;
879 part = html_context->part;
881 assert(part);
882 if_assert_failed return 0;
884 #ifdef CONFIG_UTF_8
885 if (html_context->options->utf8) {
886 for (x = overlap(par_format); x >= par_format.leftmargin; x--) {
888 if (x < part->spaces_len && (part->spaces[x]
889 || (part->char_width[x] == 2
890 /* Ugly hack. If we haven't place for
891 * double-width characters we print two
892 * double-width characters. */
893 && x != par_format.leftmargin)))
894 return split_line_at(html_context, x);
897 for (x = par_format.leftmargin; x < part->cx ; x++) {
898 if (x < part->spaces_len && (part->spaces[x]
899 || (part->char_width[x] == 2
900 /* We want to break line after _second_
901 * double-width character. */
902 && x > par_format.leftmargin)))
903 return split_line_at(html_context, x);
905 } else
906 #endif
908 for (x = overlap(par_format); x >= par_format.leftmargin; x--)
909 if (x < part->spaces_len && part->spaces[x])
910 return split_line_at(html_context, x);
912 for (x = par_format.leftmargin; x < part->cx ; x++)
913 if (x < part->spaces_len && part->spaces[x])
914 return split_line_at(html_context, x);
917 /* Make sure that we count the right margin to the total
918 * actual box width. */
919 int_lower_bound(&part->box.width, part->cx + par_format.rightmargin);
921 return 0;
924 /* Insert @new_spaces spaces before the coordinates @x and @y,
925 * adding those spaces to whatever link is at those coordinates. */
926 /* TODO: Integrate with move_links. */
927 static void
928 insert_spaces_in_link(struct part *part, int x, int y, int new_spaces)
930 int i = part->document->nlinks;
932 x = X(x);
933 y = Y(y);
935 while (i--) {
936 struct link *link = &part->document->links[i];
937 int j = link->npoints;
939 while (j-- > 1) {
940 struct point *point = &link->points[j];
942 if (point->x != x || point->y != y)
943 continue;
945 if (!realloc_points(link, link->npoints + new_spaces))
946 return;
948 link->npoints += new_spaces;
949 point = &link->points[link->npoints - 1];
951 while (new_spaces--) {
952 point->x = --x;
953 point->y = y;
954 point--;
957 return;
962 /* This function is very rare exemplary of clean and beautyful code here.
963 * Please handle with care. --pasky */
964 static void
965 justify_line(struct html_context *html_context, int y)
967 struct part *part;
968 struct screen_char *line; /* we save original line here */
969 int len;
970 int pos;
971 int *space_list;
972 int spaces;
973 int diff;
975 assert(html_context);
976 if_assert_failed return;
978 part = html_context->part;
980 assert(part && part->document && part->document->data);
981 if_assert_failed return;
983 len = LEN(y);
984 assert(len > 0);
985 if_assert_failed return;
987 line = fmem_alloc(len * sizeof(*line));
988 if (!line) return;
990 /* It may sometimes happen that the line is only one char long and that
991 * char is space - then we're going to write to both [0] and [1], but
992 * we allocated only one field. Thus, we've to do (len + 1). --pasky */
993 space_list = fmem_alloc((len + 1) * sizeof(*space_list));
994 if (!space_list) {
995 fmem_free(line);
996 return;
999 copy_screen_chars(line, &POS(0, y), len);
1001 /* Skip leading spaces */
1003 spaces = 0;
1004 pos = 0;
1006 while (line[pos].data == ' ')
1007 pos++;
1009 /* Yes, this can be negative, we know. But we add one to it always
1010 * anyway, so it's ok. */
1011 space_list[spaces++] = pos - 1;
1013 /* Count spaces */
1015 for (; pos < len; pos++)
1016 if (line[pos].data == ' ')
1017 space_list[spaces++] = pos;
1019 space_list[spaces] = len;
1021 /* Realign line */
1023 /* Diff is the difference between the width of the paragraph
1024 * and the current length of the line. */
1025 diff = overlap(par_format) - len;
1027 /* We check diff > 0 because diff can be negative (i.e., we have
1028 * an unbroken line of length > overlap(par_format))
1029 * even when spaces > 1 if the line has only non-breaking spaces. */
1030 if (spaces > 1 && diff > 0) {
1031 int prev_end = 0;
1032 int word;
1034 clear_hchars(html_context, 0, y, overlap(par_format));
1036 for (word = 0; word < spaces; word++) {
1037 /* We have to increase line length by 'diff' num. of
1038 * characters, so we move 'word'th word 'word_shift'
1039 * characters right. */
1040 int word_start = space_list[word] + 1;
1041 int word_len = space_list[word + 1] - word_start;
1042 int word_shift;
1043 int new_start;
1044 int new_spaces;
1046 assert(word_len >= 0);
1047 if_assert_failed continue;
1048 if (!word_len) continue;
1050 word_shift = (word * diff) / (spaces - 1);
1051 new_start = word_start + word_shift;
1053 copy_chars(html_context, new_start, y, word_len,
1054 &line[word_start]);
1056 new_spaces = new_start - prev_end - 1;
1057 if (word && new_spaces) {
1058 move_links(html_context, prev_end + 1, y, new_start, y);
1059 insert_spaces_in_link(part,
1060 new_start, y, new_spaces);
1063 prev_end = new_start + word_len;
1067 fmem_free(space_list);
1068 fmem_free(line);
1071 static void
1072 align_line(struct html_context *html_context, int y, int last)
1074 struct part *part;
1075 int shift;
1076 int len;
1078 assert(html_context);
1079 if_assert_failed return;
1081 part = html_context->part;
1083 assert(part && part->document && part->document->data);
1084 if_assert_failed return;
1086 len = LEN(y);
1088 if (!len || par_format.align == ALIGN_LEFT)
1089 return;
1091 if (par_format.align == ALIGN_JUSTIFY) {
1092 if (!last)
1093 justify_line(html_context, y);
1094 return;
1097 shift = overlap(par_format) - len;
1098 if (par_format.align == ALIGN_CENTER)
1099 shift /= 2;
1100 if (shift > 0)
1101 shift_chars(html_context, y, shift);
1104 static inline void
1105 init_link_event_hooks(struct html_context *html_context, struct link *link)
1107 link->event_hooks = mem_calloc(1, sizeof(*link->event_hooks));
1108 if (!link->event_hooks) return;
1110 #define add_evhook(list_, type_, src_) \
1111 do { \
1112 struct script_event_hook *evhook; \
1114 if (!src_) break; \
1116 evhook = mem_calloc(1, sizeof(*evhook)); \
1117 if (!evhook) break; \
1119 evhook->type = type_; \
1120 evhook->src = stracpy(src_); \
1121 add_to_list(*(list_), evhook); \
1122 } while (0)
1124 init_list(*link->event_hooks);
1125 add_evhook(link->event_hooks, SEVHOOK_ONCLICK, format.onclick);
1126 add_evhook(link->event_hooks, SEVHOOK_ONDBLCLICK, format.ondblclick);
1127 add_evhook(link->event_hooks, SEVHOOK_ONMOUSEOVER, format.onmouseover);
1128 add_evhook(link->event_hooks, SEVHOOK_ONHOVER, format.onhover);
1129 add_evhook(link->event_hooks, SEVHOOK_ONFOCUS, format.onfocus);
1130 add_evhook(link->event_hooks, SEVHOOK_ONMOUSEOUT, format.onmouseout);
1131 add_evhook(link->event_hooks, SEVHOOK_ONBLUR, format.onblur);
1133 #undef add_evhook
1136 static struct link *
1137 new_link(struct html_context *html_context, unsigned char *name, int namelen)
1139 struct document *document;
1140 struct part *part;
1141 int link_number;
1142 struct link *link;
1144 assert(html_context);
1145 if_assert_failed return NULL;
1147 part = html_context->part;
1149 assert(part);
1150 if_assert_failed return NULL;
1152 document = part->document;
1154 assert(document);
1155 if_assert_failed return NULL;
1157 link_number = part->link_num;
1159 if (!ALIGN_LINK(&document->links, document->nlinks, document->nlinks + 1))
1160 return NULL;
1162 link = &document->links[document->nlinks++];
1163 link->number = link_number - 1;
1164 if (document->options.use_tabindex) link->number += format.tabindex;
1165 link->accesskey = format.accesskey;
1166 link->title = null_or_stracpy(format.title);
1167 link->where_img = null_or_stracpy(format.image);
1169 if (!format.form) {
1170 link->target = null_or_stracpy(format.target);
1171 link->data.name = memacpy(name, namelen);
1172 /* if (strlen(url) > 4 && !strncasecmp(url, "MAP@", 4)) { */
1173 if (format.link
1174 && ((format.link[0]|32) == 'm')
1175 && ((format.link[1]|32) == 'a')
1176 && ((format.link[2]|32) == 'p')
1177 && (format.link[3] == '@')
1178 && format.link[4]) {
1179 link->type = LINK_MAP;
1180 link->where = stracpy(format.link + 4);
1181 } else {
1182 link->type = LINK_HYPERTEXT;
1183 link->where = null_or_stracpy(format.link);
1186 } else {
1187 struct form_control *fc = format.form;
1188 struct form *form;
1190 switch (fc->type) {
1191 case FC_TEXT:
1192 case FC_PASSWORD:
1193 case FC_FILE:
1194 link->type = LINK_FIELD;
1195 break;
1196 case FC_TEXTAREA:
1197 link->type = LINK_AREA;
1198 break;
1199 case FC_CHECKBOX:
1200 case FC_RADIO:
1201 link->type = LINK_CHECKBOX;
1202 break;
1203 case FC_SELECT:
1204 link->type = LINK_SELECT;
1205 break;
1206 case FC_SUBMIT:
1207 case FC_IMAGE:
1208 case FC_RESET:
1209 case FC_BUTTON:
1210 case FC_HIDDEN:
1211 link->type = LINK_BUTTON;
1213 link->data.form_control = fc;
1214 /* At this point, format.form might already be set but
1215 * the form_control not registered through SP_CONTROL
1216 * yet, therefore without fc->form set. It is always
1217 * after the "good" last form was already processed,
1218 * though, so we can safely just take that. */
1219 form = fc->form;
1220 if (!form && !list_empty(document->forms))
1221 form = document->forms.next;
1222 link->target = null_or_stracpy(form ? form->target : NULL);
1225 link->color.background = format.style.bg;
1226 link->color.foreground = link_is_textinput(link)
1227 ? format.style.fg : format.clink;
1229 init_link_event_hooks(html_context, link);
1231 document->links_sorted = 0;
1232 return link;
1235 static void
1236 html_special_tag(struct document *document, unsigned char *t, int x, int y)
1238 struct tag *tag;
1239 int tag_len;
1241 assert(document);
1242 if_assert_failed return;
1244 tag_len = strlen(t);
1245 /* One byte is reserved for name in struct tag. */
1246 tag = mem_alloc(sizeof(*tag) + tag_len);
1247 if (!tag) return;
1249 tag->x = x;
1250 tag->y = y;
1251 memcpy(tag->name, t, tag_len + 1);
1252 add_to_list(document->tags, tag);
1253 if (renderer_context.last_tag_for_newline == (struct tag *) &document->tags)
1254 renderer_context.last_tag_for_newline = tag;
1258 static void
1259 put_chars_conv(struct html_context *html_context,
1260 unsigned char *chars, int charslen)
1262 struct part *part;
1264 assert(html_context);
1265 if_assert_failed return;
1267 part = html_context->part;
1269 assert(part && chars && charslen);
1270 if_assert_failed return;
1272 if (format.style.attr & AT_GRAPHICS) {
1273 put_chars(html_context, chars, charslen);
1274 return;
1277 convert_string(renderer_context.convert_table, chars, charslen,
1278 html_context->options->cp,
1279 CSM_DEFAULT, NULL, (void (*)(void *, unsigned char *, int)) put_chars, html_context);
1282 static inline void
1283 put_link_number(struct html_context *html_context)
1285 struct part *part = html_context->part;
1286 unsigned char s[64];
1287 unsigned char *fl = format.link;
1288 unsigned char *ft = format.target;
1289 unsigned char *fi = format.image;
1290 struct form_control *ff = format.form;
1291 int slen = 0;
1293 format.link = format.target = format.image = NULL;
1294 format.form = NULL;
1296 s[slen++] = '[';
1297 ulongcat(s, &slen, part->link_num, sizeof(s) - 3, 0);
1298 s[slen++] = ']';
1299 s[slen] = '\0';
1301 renderer_context.nosearchable = 1;
1302 put_chars(html_context, s, slen);
1303 renderer_context.nosearchable = 0;
1305 if (ff && ff->type == FC_TEXTAREA) line_break(html_context);
1307 /* We might have ended up on a new line after the line breaking
1308 * or putting the link number chars. */
1309 if (part->cx == -1) part->cx = par_format.leftmargin;
1311 format.link = fl;
1312 format.target = ft;
1313 format.image = fi;
1314 format.form = ff;
1317 #define assert_link_variable(old, new) \
1318 assertm(!(old), "Old link value [%s]. New value [%s]", old, new);
1320 static inline void
1321 init_link_state_info(unsigned char *link, unsigned char *target,
1322 unsigned char *image, struct form_control *form)
1324 assert_link_variable(renderer_context.link_state_info.image, image);
1325 assert_link_variable(renderer_context.link_state_info.target, target);
1326 assert_link_variable(renderer_context.link_state_info.link, link);
1328 renderer_context.link_state_info.link = null_or_stracpy(link);
1329 renderer_context.link_state_info.target = null_or_stracpy(target);
1330 renderer_context.link_state_info.image = null_or_stracpy(image);
1331 renderer_context.link_state_info.form = form;
1334 static inline void
1335 done_link_state_info(void)
1337 mem_free_if(renderer_context.link_state_info.link);
1338 mem_free_if(renderer_context.link_state_info.target);
1339 mem_free_if(renderer_context.link_state_info.image);
1340 memset(&renderer_context.link_state_info, 0,
1341 sizeof(renderer_context.link_state_info));
1344 #ifdef CONFIG_UTF_8
1345 static inline void
1346 process_link(struct html_context *html_context, enum link_state link_state,
1347 unsigned char *chars, int charslen, int cells)
1348 #else
1349 static inline void
1350 process_link(struct html_context *html_context, enum link_state link_state,
1351 unsigned char *chars, int charslen)
1352 #endif /* CONFIG_UTF_8 */
1354 struct part *part = html_context->part;
1355 struct link *link;
1356 int x_offset = 0;
1358 switch (link_state) {
1359 case LINK_STATE_SAME: {
1360 unsigned char *name;
1362 if (!part->document) return;
1364 assertm(part->document->nlinks > 0, "no link");
1365 if_assert_failed return;
1367 link = &part->document->links[part->document->nlinks - 1];
1369 name = get_link_name(link);
1370 if (name) {
1371 unsigned char *new_name;
1373 new_name = straconcat(name, chars, NULL);
1374 if (new_name) {
1375 mem_free(name);
1376 link->data.name = new_name;
1380 /* FIXME: Concatenating two adjectent <a> elements to a single
1381 * link is broken since we lose the event handlers for the
1382 * second one. OTOH simply appending them here won't fly since
1383 * we may get here multiple times for even a single link. We
1384 * will probably need some SP_ for creating a new link or so.
1385 * --pasky */
1387 break;
1390 case LINK_STATE_NEW:
1391 part->link_num++;
1393 init_link_state_info(format.link, format.target,
1394 format.image, format.form);
1395 if (!part->document) return;
1397 /* Trim leading space from the link text */
1398 while (x_offset < charslen && chars[x_offset] <= ' ')
1399 x_offset++;
1401 if (x_offset) {
1402 charslen -= x_offset;
1403 chars += x_offset;
1404 #ifdef CONFIG_UTF_8
1405 cells -= x_offset;
1406 #endif /* CONFIG_UTF_8 */
1409 link = new_link(html_context, chars, charslen);
1410 if (!link) return;
1412 break;
1414 case LINK_STATE_NONE:
1415 default:
1416 INTERNAL("bad link_state %i", (int) link_state);
1417 return;
1420 /* Add new canvas positions to the link. */
1421 #ifdef CONFIG_UTF_8
1422 if (realloc_points(link, link->npoints + cells))
1423 #else
1424 if (realloc_points(link, link->npoints + charslen))
1425 #endif /* CONFIG_UTF_8 */
1427 struct point *point = &link->points[link->npoints];
1428 int x = X(part->cx) + x_offset;
1429 int y = Y(part->cy);
1431 #ifdef CONFIG_UTF_8
1432 link->npoints += cells;
1434 for (; cells > 0; cells--, point++, x++)
1435 #else
1436 link->npoints += charslen;
1438 for (; charslen > 0; charslen--, point++, x++)
1439 #endif /* CONFIG_UTF_8 */
1441 point->x = x;
1442 point->y = y;
1447 static inline enum link_state
1448 get_link_state(struct html_context *html_context)
1450 enum link_state state;
1452 if (!(format.link || format.image || format.form)) {
1453 state = LINK_STATE_NONE;
1455 } else if ((renderer_context.link_state_info.link
1456 || renderer_context.link_state_info.image
1457 || renderer_context.link_state_info.form)
1458 && !xstrcmp(format.link, renderer_context.link_state_info.link)
1459 && !xstrcmp(format.target, renderer_context.link_state_info.target)
1460 && !xstrcmp(format.image, renderer_context.link_state_info.image)
1461 && format.form == renderer_context.link_state_info.form) {
1463 return LINK_STATE_SAME;
1465 } else {
1466 state = LINK_STATE_NEW;
1469 done_link_state_info();
1471 return state;
1474 static inline int
1475 html_has_non_space_chars(unsigned char *chars, int charslen)
1477 int pos = 0;
1479 while (pos < charslen)
1480 if (!isspace(chars[pos++]))
1481 return 1;
1483 return 0;
1486 static void
1487 put_chars(struct html_context *html_context, unsigned char *chars, int charslen)
1489 enum link_state link_state;
1490 struct part *part;
1491 #ifdef CONFIG_UTF_8
1492 int cells;
1493 #endif /* CONFIG_UTF_8 */
1495 assert(html_context);
1496 if_assert_failed return;
1498 part = html_context->part;
1500 assert(part);
1501 if_assert_failed return;
1503 assert(chars && charslen);
1504 if_assert_failed return;
1506 /* If we are not handling verbatim aligning and we are at the begining
1507 * of a line trim whitespace. */
1508 if (part->cx == -1) {
1509 /* If we are not handling verbatim aligning trim leading
1510 * whitespaces. */
1511 if (!html_is_preformatted()) {
1512 while (charslen && *chars == ' ') {
1513 chars++;
1514 charslen--;
1517 if (charslen < 1) return;
1520 part->cx = par_format.leftmargin;
1523 /* For preformatted html always update 'the last tag' so we never end
1524 * up moving tags to the wrong line (Fixes bug 324). For all other html
1525 * it is moved only when the line being rendered carry some real
1526 * non-whitespace content. */
1527 if (html_is_preformatted()
1528 || html_has_non_space_chars(chars, charslen)) {
1529 renderer_context.last_tag_for_newline = (struct tag *) &part->document->tags;
1532 int_lower_bound(&part->box.height, part->cy + 1);
1534 link_state = get_link_state(html_context);
1536 if (link_state == LINK_STATE_NEW) {
1537 int x_offset = 0;
1539 /* Don't add inaccessible links. It seems to be caused
1540 * by the parser putting a space char after stuff like
1541 * <img>-tags or comments wrapped in <a>-tags. See bug
1542 * 30 for test case. */
1543 while (x_offset < charslen && chars[x_offset] <= ' ')
1544 x_offset++;
1546 /* For pure spaces reset the link state */
1547 if (x_offset == charslen)
1548 link_state = LINK_STATE_NONE;
1549 else if (html_context->options->links_numbering)
1550 put_link_number(html_context);
1552 #ifdef CONFIG_UTF_8
1553 cells =
1554 #endif /* CONFIG_UTF_8 */
1555 set_hline(html_context, chars, charslen, link_state);
1557 if (link_state != LINK_STATE_NONE) {
1558 #if 0
1559 /* This all code is from utf8 branch */
1560 #define is_drawing_subs_or_sups() \
1561 ((format.style.attr & AT_SUBSCRIPT \
1562 && html_context->options->display_subs) \
1563 || (format.style.attr & AT_SUPERSCRIPT \
1564 && html_context->options->display_sups))
1566 /* We need to update the current @link_state because <sub> and
1567 * <sup> tags will output to the canvas using an inner
1568 * put_chars() call which results in their process_link() call
1569 * will ``update'' the @link_state. */
1570 if (link_state == LINK_STATE_NEW
1571 && (is_drawing_subs_or_sups()
1572 || update_after_subscript != renderer_context.subscript)) {
1573 link_state = get_link_state(html_context);
1576 #undef is_drawing_subs_or_sups
1577 #endif
1579 #ifdef CONFIG_UTF_8
1580 process_link(html_context, link_state, chars, charslen,
1581 cells);
1582 #else
1583 process_link(html_context, link_state, chars, charslen);
1584 #endif /* CONFIG_UTF_8 */
1587 #ifdef CONFIG_UTF_8
1588 if (renderer_context.nowrap
1589 && part->cx + cells > overlap(par_format))
1590 return;
1592 part->cx += cells;
1593 #else
1594 if (renderer_context.nowrap
1595 && part->cx + charslen > overlap(par_format))
1596 return;
1598 part->cx += charslen;
1599 #endif /* CONFIG_UTF_8 */
1601 renderer_context.nobreak = 0;
1603 if (!(html_context->options->wrap || html_is_preformatted())) {
1604 while (part->cx > overlap(par_format)
1605 && part->cx > par_format.leftmargin) {
1606 int x = split_line(html_context);
1608 if (!x) break;
1609 if (part->document)
1610 align_line(html_context, part->cy - 1, 0);
1611 renderer_context.nobreak = !!(x - 1);
1615 assert(charslen > 0);
1616 #ifdef CONFIG_UTF_8
1617 part->xa += cells;
1618 #else
1619 part->xa += charslen;
1620 #endif /* CONFIG_UTF_8 */
1621 int_lower_bound(&part->max_width, part->xa
1622 + par_format.leftmargin + par_format.rightmargin
1623 - (chars[charslen - 1] == ' '
1624 && !html_is_preformatted()));
1625 return;
1629 #undef overlap
1631 static void
1632 line_break(struct html_context *html_context)
1634 struct part *part;
1635 struct tag *tag;
1637 assert(html_context);
1638 if_assert_failed return;
1640 part = html_context->part;
1642 assert(part);
1643 if_assert_failed return;
1645 int_lower_bound(&part->box.width, part->cx + par_format.rightmargin);
1647 if (renderer_context.nobreak) {
1648 renderer_context.nobreak = 0;
1649 part->cx = -1;
1650 part->xa = 0;
1651 return;
1654 if (!part->document || !part->document->data) goto end;
1656 if (!realloc_lines(part->document, part->box.height + part->cy + 1))
1657 return;
1659 if (part->cx > par_format.leftmargin && LEN(part->cy) > part->cx - 1
1660 && POS(part->cx - 1, part->cy).data == ' ') {
1661 del_chars(html_context, part->cx - 1, part->cy);
1662 part->cx--;
1665 if (part->cx > 0) align_line(html_context, part->cy, 1);
1667 for (tag = renderer_context.last_tag_for_newline;
1668 tag && tag != (struct tag *) &part->document->tags;
1669 tag = tag->prev) {
1670 tag->x = X(0);
1671 tag->y = Y(part->cy + 1);
1674 end:
1675 part->cy++;
1676 part->cx = -1;
1677 part->xa = 0;
1678 memset(part->spaces, 0, part->spaces_len);
1679 #ifdef CONFIG_UTF_8
1680 memset(part->char_width, 0, part->spaces_len);
1681 #endif
1684 static void
1685 html_special_form(struct part *part, struct form *form)
1687 assert(part && form);
1688 if_assert_failed return;
1690 if (!part->document) {
1691 done_form(form);
1692 return;
1695 if (!list_empty(part->document->forms)) {
1696 struct form *nform;
1698 /* Make sure the new form ``claims'' its slice of the form range
1699 * maintained in the form_num and form_end variables. */
1700 foreach (nform, part->document->forms) {
1701 if (form->form_num < nform->form_num
1702 || nform->form_end < form->form_num)
1703 continue;
1705 /* First check if the form has identical form numbers.
1706 * That should only be the case when the form being
1707 * added is in fact the same form in which case it
1708 * should be dropped. The fact that this can happen
1709 * suggests that the table renderering can be confused.
1710 * See bug 647 for a test case. */
1711 if (nform->form_num == form->form_num
1712 && nform->form_end == form->form_end) {
1713 done_form(form);
1714 return;
1717 /* The form start is inside an already added form, so
1718 * partition the space of the existing form and get
1719 * |old|new|. */
1720 nform->form_end = form->form_num - 1;
1721 assertm(nform->form_num <= nform->form_end,
1722 "[%d:%d] [%d:%d]", nform->form_num, nform->form_end,
1723 form->form_num, form->form_end);
1724 break;
1726 } else {
1727 /* If it is the first form make sure it eats the whole form
1728 * range. */
1729 #if 0
1730 /* Disabled because in tables the parse order may lead to a
1731 * later form being parsed before a preceeding one causing the
1732 * wrong order if we set it to zero. Let's hope it doesn't break
1733 * anything else. */
1734 form->form_num = 0;
1735 #endif
1738 add_to_list(part->document->forms, form);
1741 static void
1742 html_special_form_control(struct part *part, struct form_control *fc)
1744 struct form *form;
1746 assert(part && fc);
1747 if_assert_failed return;
1749 if (!part->document) {
1750 done_form_control(fc);
1751 mem_free(fc);
1752 return;
1755 fc->g_ctrl_num = renderer_context.g_ctrl_num++;
1757 /* We don't want to recode hidden fields. */
1758 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD ||
1759 fc->type == FC_TEXTAREA) {
1760 unsigned char *dv = convert_string(renderer_context.convert_table,
1761 fc->default_value,
1762 strlen(fc->default_value),
1763 part->document->options.cp,
1764 CSM_QUERY, NULL, NULL, NULL);
1766 if (dv) mem_free_set(&fc->default_value, dv);
1769 if (list_empty(part->document->forms)) {
1770 /* No forms encountered yet, that means a homeless form
1771 * control. Generate a dummy form for those Flying
1772 * Dutchmans. */
1773 form = init_form();
1774 form->form_num = 0;
1775 add_to_list(part->document->forms, form);
1777 /* Attach this form control to the last form encountered. */
1778 form = part->document->forms.next;
1779 fc->form = form;
1780 add_to_list(form->items, fc);
1783 /* Reparents form items based on position in the source. */
1784 void
1785 check_html_form_hierarchy(struct part *part)
1787 struct document *document = part->document;
1788 INIT_LIST_HEAD(form_controls);
1789 struct form *form;
1790 struct form_control *fc, *next;
1792 if (list_empty(document->forms))
1793 return;
1795 /* Take out all badly placed form items. */
1797 foreach (form, document->forms) {
1799 assertm(form->form_num <= form->form_end,
1800 "%p [%d : %d]", form, form->form_num, form->form_end);
1802 foreachsafe (fc, next, form->items) {
1803 if (form->form_num <= fc->position
1804 && fc->position <= form->form_end)
1805 continue;
1807 move_to_top_of_list(form_controls, fc);
1811 /* Re-insert the form items the correct places. */
1813 foreachsafe (fc, next, form_controls) {
1815 foreach (form, document->forms) {
1816 if (fc->position < form->form_num
1817 || form->form_end < fc->position)
1818 continue;
1820 fc->form = form;
1821 move_to_top_of_list(form->items, fc);
1822 break;
1826 assert(list_empty(form_controls));
1829 static inline void
1830 color_link_lines(struct html_context *html_context)
1832 struct document *document = html_context->part->document;
1833 struct color_pair colors = INIT_COLOR_PAIR(par_format.bgcolor, 0x0);
1834 enum color_mode color_mode = document->options.color_mode;
1835 enum color_flags color_flags = document->options.color_flags;
1836 int y;
1838 for (y = 0; y < document->height; y++) {
1839 int x;
1841 for (x = 0; x < document->data[y].length; x++) {
1842 struct screen_char *schar = &document->data[y].chars[x];
1844 set_term_color(schar, &colors, color_flags, color_mode);
1846 /* XXX: Entering hack zone! Change to clink color after
1847 * link text has been recolored. */
1848 if (schar->data == ':' && colors.foreground == 0x0)
1849 colors.foreground = format.clink;
1852 colors.foreground = 0x0;
1856 static void *
1857 html_special(struct html_context *html_context, enum html_special_type c, ...)
1859 va_list l;
1860 struct part *part;
1861 struct document *document;
1862 void *ret_val = NULL;
1864 assert(html_context);
1865 if_assert_failed return NULL;
1867 part = html_context->part;
1869 assert(part);
1870 if_assert_failed return NULL;
1872 document = part->document;
1874 va_start(l, c);
1875 switch (c) {
1876 case SP_TAG:
1877 if (document) {
1878 unsigned char *t = va_arg(l, unsigned char *);
1880 html_special_tag(document, t, X(part->cx), Y(part->cy));
1882 break;
1883 case SP_FORM:
1885 struct form *form = va_arg(l, struct form *);
1887 html_special_form(part, form);
1888 break;
1890 case SP_CONTROL:
1892 struct form_control *fc = va_arg(l, struct form_control *);
1894 html_special_form_control(part, fc);
1895 break;
1897 case SP_TABLE:
1898 ret_val = renderer_context.convert_table;
1899 break;
1900 case SP_USED:
1901 ret_val = (void *) (long) !!document;
1902 break;
1903 case SP_CACHE_CONTROL:
1905 struct cache_entry *cached = renderer_context.cached;
1907 cached->cache_mode = CACHE_MODE_NEVER;
1908 cached->expire = 0;
1909 break;
1911 case SP_CACHE_EXPIRES:
1913 time_t expires = va_arg(l, time_t);
1914 struct cache_entry *cached = renderer_context.cached;
1916 if (!expires || cached->cache_mode == CACHE_MODE_NEVER)
1917 break;
1919 timeval_from_seconds(&cached->max_age, expires);
1920 cached->expire = 1;
1921 break;
1923 case SP_FRAMESET:
1925 struct frameset_param *fsp = va_arg(l, struct frameset_param *);
1926 struct frameset_desc *frameset_desc;
1928 if (!fsp->parent && document->frame_desc)
1929 break;
1931 frameset_desc = create_frameset(fsp);
1932 if (!fsp->parent && !document->frame_desc)
1933 document->frame_desc = frameset_desc;
1935 ret_val = frameset_desc;
1936 break;
1938 case SP_FRAME:
1940 struct frameset_desc *parent = va_arg(l, struct frameset_desc *);
1941 unsigned char *name = va_arg(l, unsigned char *);
1942 unsigned char *url = va_arg(l, unsigned char *);
1944 add_frameset_entry(parent, NULL, name, url);
1945 break;
1947 case SP_NOWRAP:
1948 renderer_context.nowrap = !!va_arg(l, int);
1949 break;
1950 case SP_REFRESH:
1952 unsigned long seconds = va_arg(l, unsigned long);
1953 unsigned char *t = va_arg(l, unsigned char *);
1955 document->refresh = init_document_refresh(t, seconds);
1956 break;
1958 case SP_COLOR_LINK_LINES:
1959 if (document && use_document_bg_colors(&document->options))
1960 color_link_lines(html_context);
1961 break;
1962 case SP_STYLESHEET:
1963 #ifdef CONFIG_CSS
1964 if (document) {
1965 struct uri *uri = va_arg(l, struct uri *);
1967 add_to_uri_list(&document->css_imports, uri);
1969 #endif
1970 break;
1971 case SP_SCRIPT:
1972 #ifdef CONFIG_ECMASCRIPT
1973 if (document) {
1974 struct uri *uri = va_arg(l, struct uri *);
1976 add_to_uri_list(&document->ecmascript_imports, uri);
1978 #endif
1979 break;
1982 va_end(l);
1984 return ret_val;
1987 void
1988 free_table_cache(void)
1990 if (table_cache) {
1991 struct hash_item *item;
1992 int i;
1994 /* We do not free key here. */
1995 foreach_hash_item (item, *table_cache, i) {
1996 mem_free_if(item->value);
1999 free_hash(&table_cache);
2000 table_cache_entries = 0;
2004 struct part *
2005 format_html_part(struct html_context *html_context,
2006 unsigned char *start, unsigned char *end,
2007 int align, int margin, int width, struct document *document,
2008 int x, int y, unsigned char *head,
2009 int link_num)
2011 struct part *part;
2012 struct html_element *html_state;
2013 int llm = renderer_context.last_link_to_move;
2014 struct tag *ltm = renderer_context.last_tag_to_move;
2015 int ef = renderer_context.empty_format;
2016 int lm = html_context->margin;
2018 /* Hash creation if needed. */
2019 if (!table_cache) {
2020 table_cache = init_hash8();
2021 } else if (!document) {
2022 /* Search for cached entry. */
2023 struct table_cache_entry_key key;
2024 struct hash_item *item;
2026 /* Clear key to prevent potential alignment problem
2027 * when keys are compared. */
2028 memset(&key, 0, sizeof(key));
2030 key.start = start;
2031 key.end = end;
2032 key.align = align;
2033 key.margin = margin;
2034 key.width = width;
2035 key.x = x;
2036 key.link_num = link_num;
2038 item = get_hash_item(table_cache,
2039 (unsigned char *) &key,
2040 sizeof(key));
2041 if (item) { /* We found it in cache, so just copy and return. */
2042 part = mem_alloc(sizeof(*part));
2043 if (part) {
2044 copy_struct(part, &((struct table_cache_entry *)
2045 item->value)->part);
2046 return part;
2051 assertm(y >= 0, "format_html_part: y == %d", y);
2052 if_assert_failed return NULL;
2054 if (document) {
2055 struct node *node = mem_alloc(sizeof(*node));
2057 if (node) {
2058 int node_width = !html_context->table_level ? INT_MAX : width;
2060 set_box(&node->box, x, y, node_width, 1);
2061 add_to_list(document->nodes, node);
2064 renderer_context.last_link_to_move = document->nlinks;
2065 renderer_context.last_tag_to_move = (struct tag *) &document->tags;
2066 renderer_context.last_tag_for_newline = (struct tag *) &document->tags;
2067 } else {
2068 renderer_context.last_link_to_move = 0;
2069 renderer_context.last_tag_to_move = (struct tag *) NULL;
2070 renderer_context.last_tag_for_newline = (struct tag *) NULL;
2073 html_context->margin = margin;
2074 renderer_context.empty_format = !document;
2076 done_link_state_info();
2077 renderer_context.nobreak = 1;
2079 part = mem_calloc(1, sizeof(*part));
2080 if (!part) goto ret;
2082 part->document = document;
2083 part->box.x = x;
2084 part->box.y = y;
2085 part->cx = -1;
2086 part->cy = 0;
2087 part->link_num = link_num;
2089 html_state = init_html_parser_state(html_context, ELEMENT_IMMORTAL, align, margin, width);
2091 parse_html(start, end, part, head, html_context);
2093 done_html_parser_state(html_context, html_state);
2095 int_lower_bound(&part->max_width, part->box.width);
2097 renderer_context.nobreak = 0;
2099 done_link_state_info();
2100 mem_free_if(part->spaces);
2101 #ifdef CONFIG_UTF_8
2102 mem_free_if(part->char_width);
2103 #endif
2105 if (document) {
2106 struct node *node = document->nodes.next;
2108 node->box.height = y - node->box.y + part->box.height;
2111 ret:
2112 renderer_context.last_link_to_move = llm;
2113 renderer_context.last_tag_to_move = ltm;
2114 renderer_context.empty_format = ef;
2116 html_context->margin = lm;
2118 if (html_context->table_level > 1 && !document
2119 && table_cache
2120 && table_cache_entries < MAX_TABLE_CACHE_ENTRIES) {
2121 /* Create a new entry. */
2122 /* Clear memory to prevent bad key comparaison due to alignment
2123 * of key fields. */
2124 struct table_cache_entry *tce = mem_calloc(1, sizeof(*tce));
2125 /* A goto is used here to prevent a test or code
2126 * redundancy. */
2127 if (!tce) goto end;
2129 tce->key.start = start;
2130 tce->key.end = end;
2131 tce->key.align = align;
2132 tce->key.margin = margin;
2133 tce->key.width = width;
2134 tce->key.x = x;
2135 tce->key.link_num = link_num;
2136 copy_struct(&tce->part, part);
2138 if (!add_hash_item(table_cache,
2139 (unsigned char *) &tce->key,
2140 sizeof(tce->key), tce)) {
2141 mem_free(tce);
2142 } else {
2143 table_cache_entries++;
2147 end:
2149 return part;
2152 void
2153 render_html_document(struct cache_entry *cached, struct document *document,
2154 struct string *buffer)
2156 struct html_context *html_context;
2157 struct part *part;
2158 unsigned char *start;
2159 unsigned char *end;
2160 struct string title;
2161 struct string head;
2163 assert(cached && document);
2164 if_assert_failed return;
2166 if (!init_string(&head)) return;
2168 if (cached->head) add_to_string(&head, cached->head);
2170 start = buffer->source;
2171 end = buffer->source + buffer->length;
2173 html_context = init_html_parser(cached->uri, &document->options,
2174 start, end, &head, &title,
2175 put_chars_conv, line_break,
2176 html_special);
2177 if (!html_context) return;
2179 renderer_context.g_ctrl_num = 0;
2180 renderer_context.cached = cached;
2181 renderer_context.convert_table = get_convert_table(head.source,
2182 document->options.cp,
2183 document->options.assume_cp,
2184 &document->cp,
2185 &document->cp_status,
2186 document->options.hard_assume);
2187 #ifdef CONFIG_UTF_8
2188 html_context->options->utf8 = is_cp_utf8(document->options.cp);
2189 #endif /* CONFIG_UTF_8 */
2191 if (title.length) {
2192 document->title = convert_string(renderer_context.convert_table,
2193 title.source, title.length,
2194 document->options.cp,
2195 CSM_DEFAULT, NULL, NULL, NULL);
2197 done_string(&title);
2199 part = format_html_part(html_context, start, end, par_format.align,
2200 par_format.leftmargin,
2201 document->options.box.width, document,
2202 0, 0, head.source, 1);
2204 /* Drop empty allocated lines at end of document if any
2205 * and adjust document height. */
2206 while (document->height && !document->data[document->height - 1].length)
2207 mem_free_if(document->data[--document->height].chars);
2209 /* Calculate document width. */
2211 int i;
2213 document->width = 0;
2214 for (i = 0; i < document->height; i++)
2215 int_lower_bound(&document->width, document->data[i].length);
2218 #if 1
2219 document->options.needs_width = 1;
2220 #else
2221 /* FIXME: This needs more tuning since if we are centering stuff it
2222 * does not work. */
2223 document->options.needs_width =
2224 (document->width + (document->options.margin
2225 >= document->options.width));
2226 #endif
2228 document->bgcolor = par_format.bgcolor;
2230 done_html_parser(html_context);
2232 /* Drop forms which has been serving as a placeholder for form items
2233 * added in the wrong order due to the ordering of table rendering. */
2235 struct form *form;
2237 foreach (form, document->forms) {
2238 if (form->form_num)
2239 continue;
2241 if (list_empty(form->items))
2242 done_form(form);
2244 break;
2248 /* @part was residing in html_context so it has to stay alive until
2249 * done_html_parser(). */
2250 done_string(&head);
2251 mem_free_if(part);
2253 #if 0 /* debug purpose */
2255 FILE *f = fopen("forms", "ab");
2256 struct form_control *form;
2257 unsigned char *qq;
2258 fprintf(f,"FORM:\n");
2259 foreach (form, document->forms) {
2260 fprintf(f, "g=%d f=%d c=%d t:%d\n",
2261 form->g_ctrl_num, form->form_num,
2262 form->ctrl_num, form->type);
2264 fprintf(f,"fragment: \n");
2265 for (qq = start; qq < end; qq++) fprintf(f, "%c", *qq);
2266 fprintf(f,"----------\n\n");
2267 fclose(f);
2269 #endif
2273 find_tag(struct document *document, unsigned char *name, int namelen)
2275 struct tag *tag;
2277 foreach (tag, document->tags)
2278 if (!strlcasecmp(tag->name, -1, name, namelen))
2279 return tag->y;
2281 return -1;