/* html_r.c
 * (c) 2002 Mikulas Patocka
 * This file is a part of the Links program, released under GPL.
 */

#include "links.h"

struct f_data *init_formatted(struct document_options *opt)
{
	struct f_data *scr;
	scr = mem_calloc(sizeof(struct f_data));
	copy_opt(&scr->opt, opt);
	scr->data = DUMMY;
	scr->nlinks = 0;
	scr->links = DUMMY;
	if (!F) init_list(scr->compressed_chars);
#ifdef JS
	scr->nlink_events = 0;
	scr->link_events = DUMMY;
#endif
	init_list(scr->forms);
	init_list(scr->tags);
	init_list(scr->nodes);
#ifdef G
	scr->n_images = 0;
	init_list(scr->images);
	scr->search_positions = DUMMY;
	scr->search_lengths = DUMMY;
	init_list(scr->image_refresh);
	scr->start_highlight_x = -1;
	scr->start_highlight_y = -1;
	scr->hlt_pos = -1;
#endif
	return scr;
}

void destroy_fc(struct form_control *fc)
{
	int i;
	if (fc->action) mem_free(fc->action);
	if (fc->target) mem_free(fc->target);
	if (fc->form_name) mem_free(fc->form_name);
	if (fc->onsubmit) mem_free(fc->onsubmit);
	if (fc->name) mem_free(fc->name);
	if (fc->alt) mem_free(fc->alt);
	if (fc->default_value) mem_free(fc->default_value);
	for (i = 0; i < fc->nvalues; i++) {
		if (fc->values[i]) mem_free(fc->values[i]);
		if (fc->labels[i]) mem_free(fc->labels[i]);
	}
	if (fc->values) mem_free(fc->values);
	if (fc->labels) mem_free(fc->labels);
	if (fc->menu) free_menu(fc->menu);
}

void free_frameset_desc(struct frameset_desc *fd)
{
	int i;
	for (i = 0; i < fd->n; i++) {
		if (fd->f[i].subframe) free_frameset_desc(fd->f[i].subframe);
		if (fd->f[i].name) mem_free(fd->f[i].name);
		if (fd->f[i].url) mem_free(fd->f[i].url);
	}
#ifdef JS
	if (fd->onload_code)mem_free(fd->onload_code);
#endif
	mem_free(fd);
}

struct frameset_desc *copy_frameset_desc(struct frameset_desc *fd)
{
	int i;
	struct frameset_desc *neww;
	if ((unsigned)fd->n > MAXINT / sizeof(struct frame_desc)) overalloc();
	neww = mem_alloc(sizeof(struct frameset_desc) + fd->n * sizeof(struct frame_desc));
	memcpy(neww, fd, sizeof(struct frameset_desc) + fd->n * sizeof(struct frame_desc));
#ifdef JS
	if (neww->onload_code) neww->onload_code = stracpy(neww->onload_code);
#endif
	for (i = 0; i < neww->n; i++) {
		if (neww->f[i].subframe) neww->f[i].subframe = copy_frameset_desc(neww->f[i].subframe);
		if (neww->f[i].name) neww->f[i].name = stracpy(neww->f[i].name);
		if (neww->f[i].url) neww->f[i].url = stracpy(neww->f[i].url);
	}
	return neww;
}

void free_additional_files(struct additional_files **a)
{
	struct additional_file *af;
	struct list_head *laf;
	if (!*a) return;
	if (--(*a)->refcount) {
		*a = NULL;
		return;
	}
	foreach(struct additional_file, af, laf, (*a)->af) release_object(&af->rq);
	free_list(struct additional_file, (*a)->af);
	mem_free(*a);
	*a = NULL;
}

static void clear_formatted(struct f_data *scr)
{
	int n;
	int y;
	struct form_control *fc;
	struct list_head *lfc;
	if (!scr) return;

	if (scr->search_chr) {
		mem_free(scr->search_chr);
	}
	if (scr->search_pos) {
		mem_free(scr->search_pos);
	}
	if (scr->slines1) mem_free(scr->slines1);
	if (scr->slines2) mem_free(scr->slines2);

#ifdef G
	if (scr->root) scr->root->destruct(scr->root);
#endif
	release_object(&scr->rq);
	free_additional_files(&scr->af);
	if (scr->title) mem_free(scr->title);
	if (scr->frame_desc) {
		free_frameset_desc(scr->frame_desc);
	}
	for (n = 0; n < scr->nlinks; n++) {
		struct link *l = &scr->links[n];
		if (l->where) mem_free(l->where);
		if (l->target) mem_free(l->target);
		if (l->where_img) mem_free(l->where_img);
		if (l->img_alt) mem_free(l->img_alt);
		if (l->pos) mem_free(l->pos);
		free_js_event_spec(l->js_event);
	}
	mem_free(scr->links);
#ifdef JS
	for (n = 0; n < scr->nlink_events; n++) {
		free_js_event_spec(scr->link_events[n]);
	}
	mem_free(scr->link_events);
#endif
	if (!F) {
		for (y = 0; y < scr->y; y++)
			if (scr->data[y].allocated >= 0) {
				mem_free(scr->data[y].u.du);
			}
		mem_free(scr->data);
		free_list(struct compressed_chars, scr->compressed_chars);
	}
	if (scr->lines1) mem_free(scr->lines1);
	if (scr->lines2) mem_free(scr->lines2);
	if (scr->opt.framename) mem_free(scr->opt.framename);
	foreach(struct form_control, fc, lfc, scr->forms) {
		destroy_fc(fc);
	}
	free_list(struct form_control, scr->forms);
	free_list(struct tag, scr->tags);
	free_list(struct node, scr->nodes);
#ifdef G
	free_list(struct image_refresh, scr->image_refresh);
	if (scr->srch_string) mem_free(scr->srch_string);
	if (scr->last_search) mem_free(scr->last_search);
	if (scr->search_positions) mem_free(scr->search_positions);
	if (scr->search_lengths) mem_free(scr->search_lengths);
#endif
	if (scr->refresh) mem_free(scr->refresh);
#ifdef JS
	if (scr->script_href_base) mem_free(scr->script_href_base);
	free_js_event_spec(scr->js_event);
#endif
}

void destroy_formatted(struct f_data *scr)
{
	if (scr->fd) {
		internal_error("trying to free locked formatted data");
		return;
	}
	clear_formatted(scr);
	mem_free(scr);
}

static inline int color_distance(struct rgb *c1, struct rgb *c2)
{
	return
		3 * (c1->r - c2->r) * (c1->r - c2->r) +
		4 * (c1->g - c2->g) * (c1->g - c2->g) +
		2 * (c1->b - c2->b) * (c1->b - c2->b);
}

struct rgb palette_16_colors[16] = {
	{0x00, 0x00, 0x00, 0},
	{0x80, 0x00, 0x00, 0},
	{0x00, 0x80, 0x00, 0},
	{0xaa, 0x55, 0x00, 0},
	{0x00, 0x00, 0x80, 0},
	{0x80, 0x00, 0x80, 0},
	{0x00, 0x80, 0x80, 0},
	{0xaa, 0xaa, 0xaa, 0},
	{0x55, 0x55, 0x55, 0},
	{0xff, 0x55, 0x55, 0},
	{0x55, 0xff, 0x55, 0},
	{0xff, 0xff, 0x55, 0},
	{0x55, 0x55, 0xff, 0},
	{0xff, 0x55, 0xff, 0},
	{0x55, 0xff, 0xff, 0},
	{0xff, 0xff, 0xff, 0},
};

struct rgb_cache_entry {
	int color;
	int l;
	struct rgb rgb;
};

#define RGB_HASH_SIZE 4096

#define HASH_RGB(r, l) ((((r)->r << 3) + ((r)->g << 2) + (r)->b + (l)) & (RGB_HASH_SIZE - 1))

int find_nearest_color(struct rgb *r, int l)
{
	int dist, dst, min, i;
	static struct rgb_cache_entry rgb_cache[RGB_HASH_SIZE];
	static int cache_init = 0;
	int h;
	if ((size_t)l > array_elements(palette_16_colors))
		internal_error("invalid length %d", l);
	if (!cache_init) goto initialize;
	back:
	h = HASH_RGB(r, l);
	if (rgb_cache[h].color != -1 && rgb_cache[h].l == l && rgb_cache[h].rgb.r == r->r && rgb_cache[h].rgb.g == r->g && rgb_cache[h].rgb.b == r->b) return rgb_cache[h].color;
	dist = 0xffffff;
	min = 0;
	for (i = 0; i < l; i++) if ((dst = color_distance(r, &palette_16_colors[i])) < dist)
		dist = dst, min = i;
	rgb_cache[h].color = min;
	rgb_cache[h].l = l;
	rgb_cache[h].rgb.r = r->r;
	rgb_cache[h].rgb.g = r->g;
	rgb_cache[h].rgb.b = r->b;
	return min;

	initialize:
	for (h = 0; h < RGB_HASH_SIZE; h++) rgb_cache[h].color = -1;
	cache_init = 1;
	goto back;
}

int fg_color(int fg, int bg)
{
	int l = bg < fg ? bg : fg;
	int h = bg < fg ? fg : bg;
	if (l == h || (!l && (h == 4 || h == 8 || h == 12)) ||
	   (l == 1 && (h == 3 || h == 5 || h == 8 || h == 12)) ||
	   (l == 2 && h == 6) || (l == 3 && (h == 5 || h == 12)) ||
	   (l == 4 && (h == 8 || h == 12)) || (l == 5 && (h == 8 || h == 12)))
		return (fg == 4 || fg == 12) && (bg == 0 || bg == 8) ? 6 : (7 - 7 * (bg == 2 || bg == 6 || bg == 7));
	return fg;
}

static int nowrap = 0;

static void xpand_lines(struct part *p, int y)
{
	if (!p->data) return;
	if (y < 0) return;
	y = safe_add(y, safe_add(p->yp, 1));
	if (y > p->data->y) {
		int i;
		if ((y ^ p->data->y) > p->data->y) {
			unsigned s;
			for (s = 1; s < (unsigned)y; s = s * 2 + 1) {
				if (s > MAXINT / sizeof(struct line)) overalloc();
			}
			p->data->data = mem_realloc(p->data->data, s * sizeof(struct line));
		}
		/*
#define YALIGN(y) (((y)+0x3ff)&~0x3ff)
		if (YALIGN(y + 1) > YALIGN(p->data->y)) {
			if (YALIGN((unsigned)y + 1) > MAXINT / sizeof(struct line)) overalloc();
			p->data->data = mem_realloc(p->data->data, YALIGN(y+1)*sizeof(struct line));
		}*/
		for (i = p->data->y; i < y; i++) {
			p->data->data[i].l = 0;
			p->data->data[i].allocated = 0;
			p->data->data[i].u.du = DUMMY;
		}
		p->data->y = y;
	}
}

static void xpand_line(struct part *p, int y, int x)
{
	struct line *ln;
	if (!p->data) return;
	x = safe_add(x, p->xp);
	y = safe_add(y, p->yp);
#ifdef DEBUG
	if (y >= p->data->y) {
		internal_error("line does not exist");
		return;
	}
#endif
	ln = &p->data->data[y];
	if (x >= ln->l) {
		int i;
		if (x >= ln->allocated) {
			if (x >= 0x4000) ln->allocated = safe_add(x, x);
			else ln->allocated = safe_add(x, 0x10) & ~0xf;
			if ((unsigned)ln->allocated > MAXINT / sizeof(chr)) overalloc();
			ln->u.du = mem_realloc(ln->u.du, ln->allocated * sizeof(chr));
		}
		for (i = ln->l; i <= x; i++) {
			ln->u.du[i].at = p->attribute;
			ln->u.du[i].ch = ' ';
		}
		ln->l = i;
	}
}

static void r_xpand_spaces(struct part *p, int l)
{
	unsigned char *c;
	if ((unsigned)l >= MAXINT) overalloc();
	c = mem_realloc(p->spaces, l + 1);
	memset(c + p->spl, 0, l - p->spl + 1);
	p->spl = l + 1;
	p->spaces = c;
}

static inline void xpand_spaces(struct part *p, int l)
{
	if ((unsigned)l >= (unsigned)p->spl) r_xpand_spaces(p, l);
}

#define POS(x, y) (p->data->data[safe_add(p->yp, (y))].u.du[safe_add(p->xp, (x))])
#define LEN(y) (p->data->data[safe_add(p->yp, (y))].l - p->xp < 0 ? 0 : p->data->data[p->yp + (y)].l - p->xp)
#define SLEN(y, x) p->data->data[safe_add(p->yp, (y))].l = safe_add(p->xp, x)
#define X(x) safe_add(p->xp, (x))
#define Y(y) safe_add(p->yp, (y))

static inline void set_hchar(struct part *p, int x, int y, unsigned ch, unsigned char at)
{
	chr *cc;
	xpand_lines(p, y);
	xpand_line(p, y, x);
	cc = &POS(x, y);
	cc->ch = ch;
	cc->at = at;
}

static inline void set_hchars(struct part *p, int x, int y, int xl, unsigned ch, unsigned char at)
{
	chr *cc;
	xpand_lines(p, y);
	xpand_line(p, y, safe_add(x, xl) - 1);
	cc = &POS(x, y);
	for (; xl; xl--) {
		cc->ch = ch;
		cc->at = at;
		cc++;
	}
}

void xset_hchar(struct part *p, int x, int y, unsigned ch, unsigned char at)
{
	set_hchar(p, x, y, ch, at);
}

void xset_hchars(struct part *p, int x, int y, int xl, unsigned ch, unsigned char at)
{
	set_hchars(p, x, y, xl, ch, at);
}

void xxpand_lines(struct part *p, int y)
{
	xpand_lines(p, y);
}

void xxpand_line(struct part *p, int y, int x)
{
	xpand_line(p, y, x);
}

static inline void set_hline(struct part *p, int x, int y, int xl, unsigned char *d, unsigned char at)
{
	chr *cc;
	int xp;
	xpand_lines(p, y);
	xpand_line(p, y, safe_add(x,xl)-1);
	xp = par_format.align != AL_NO;
	if (xp) xpand_spaces(p, safe_add(x,xl)-1);
	cc = NULL;
	if (p->data) cc = &POS(x, y);
	for (; xl; xl--, x++, d++) {
		if (xp) p->spaces[x] = *d == ' ';
		if (p->data) {
			cc->ch = *d;
			cc->at = at;
			cc++;
		}
	}
}

static inline void set_hline_uni(struct part *p, int x, int y, int xl, char_t *d, unsigned char at)
{
	chr *cc;
	int xp;
	xpand_lines(p, y);
	xpand_line(p, y, safe_add(x,xl)-1);
	xp = par_format.align != AL_NO;
	if (xp) xpand_spaces(p, safe_add(x,xl)-1);
	cc = NULL;
	if (p->data) cc = &POS(x, y);
	for (; xl; xl--, x++, d++) {
		if (xp) p->spaces[x] = *d == ' ';
		if (p->data) {
			cc->ch = *d;
			cc->at = at;
			cc++;
		}
	}
}

static int last_link_to_move;
static struct list_head *last_tag_to_move;
static struct list_head *last_tag_for_newline;

static inline void move_links(struct part *p, int xf, int yf, int xt, int yt)
{
	int n;
	struct tag *t;
	struct list_head *lt;
	int w = 0;
	if (!p->data) return;
	xpand_lines(p, yt);
	for (n = last_link_to_move; n < p->data->nlinks; n++) {
		int i;
		struct link *link = &p->data->links[n];
			/*printf("ml: %d %d %d %d",link->pos[0].x,link->pos[0].y,X(xf),Y(yf));fflush(stdout);portable_sleep(1000);*/
		for (i = link->first_point_to_move; i < link->n; i++) if (link->pos[i].y >= Y(yf)) {
			w = 1;
			if (link->pos[i].y == Y(yf) && link->pos[i].x >= X(xf)) {
				if (yt >= 0) {
					link->pos[i].y = Y(yt);
					if (xt > xf)
						link->pos[i].x = safe_add(link->pos[i].x, -xf + xt);
					else
						link->pos[i].x = link->pos[i].x -xf + xt;
				}
				else memmove(&link->pos[i], &link->pos[i+1], (link->n-i-1) * sizeof(struct point)), link->n--, i--;
			}
		} else {
			link->first_point_to_move = safe_add(i, 1);
		}
		if (!w) last_link_to_move = n;
	}
	w = 0;
	if (yt >= 0) foreachfrom(struct tag, t, lt, p->data->tags, last_tag_to_move->next) {
		if (t->y == Y(yf)) {
			w = 1;
			if (t->x >= X(xf)) {
				t->y = Y(yt);
				if (xt > xf)
					t->x = safe_add(t->x, -xf + xt);
				else
					t->x += -xf + xt;
			}
		}
		if (!w) last_tag_to_move = &t->list_entry;
	}
}

static inline void copy_chars(struct part *p, int x, int y, int xl, chr *d)
{
	if (xl <= 0) return;
	xpand_lines(p, y);
	xpand_line(p, y, safe_add(x,xl)-1);
	for (; xl; xl--, x++, d++) POS(x, y) = *d;
}

static inline void move_chars(struct part *p, int x, int y, int nx, int ny)
{
	if (LEN(y) - x <= 0) return;
	copy_chars(p, nx, ny, LEN(y) - x, &POS(x, y));
	SLEN(y, x);
	move_links(p, x, y, nx, ny);
}

static inline void shift_chars(struct part *p, int y, int s)
{
	chr *a;
	int l = LEN(y);
	if ((unsigned)l > MAXINT / sizeof(chr)) overalloc();
	a = mem_alloc(l * sizeof(chr));
	memcpy(a, &POS(0, y), l * sizeof(chr));
	set_hchars(p, 0, y, s, ' ', p->attribute);
	copy_chars(p, s, y, l, a);
	mem_free(a);
	move_links(p, 0, y, s, y);
}

static inline void del_chars(struct part *p, int x, int y)
{
	SLEN(y, x);
	move_links(p, x, y, -1, -1);
}

#define rm(x) ((x).width - (x).rightmargin > 0 ? (x).width - (x).rightmargin : 0)

static void line_break(void *);

static int split_line(struct part *p)
{
	int i;
	if (rm(par_format) >= p->z_spaces) {
		for (i = rm(par_format); i >= par_format.leftmargin; i--)
			if (i < p->spl && p->spaces[i]) goto split;
	}
	i = rm(par_format) + 1;
	if (i < p->z_spaces) i = p->z_spaces;
	if (i < par_format.leftmargin) i = par_format.leftmargin;
	for (; i < p->cx ; i++)
		if (i < p->spl && p->spaces[i]) goto split;
	p->z_spaces = i;
	if (p->cx >= 0 && safe_add(p->cx, par_format.rightmargin) > p->x) p->x = p->cx + par_format.rightmargin;
	return 0;
	split:
	if (safe_add(i, par_format.rightmargin) > p->x) p->x = i + par_format.rightmargin;
	if (p->data) {
#ifdef DEBUG
		if (POS(i, p->cy).ch != ' ') internal_error("bad split: %c", (unsigned char)POS(i, p->cy).ch);
#endif
		move_chars(p, safe_add(i,1), p->cy, par_format.leftmargin, safe_add(p->cy,1));
		del_chars(p, i, p->cy);
	}
	memmove(p->spaces, p->spaces + safe_add(i, 1), p->spl - i - 1);
	memset(p->spaces + p->spl - i - 1, 0, i + 1);
	memmove(p->spaces + par_format.leftmargin, p->spaces, p->spl - par_format.leftmargin);
	p->z_spaces = 0;
	p->cy = safe_add(p->cy, 1);
	p->cx -= i - par_format.leftmargin + 1;
	if (p->cx == par_format.leftmargin) p->cx = -1;
	if (p->y < safe_add(p->cy, (p->cx != -1))) p->y = p->cy + (p->cx != -1);
	return 1 + (p->cx == -1);
}

static inline void compress_line(struct f_data *f, int y)
{
	int i, want_len;
	struct line *ln = &f->data[y];
	struct compressed_chars *cs;
	for (i = 0; i < ln->l; i++) {
		if (ln->u.du[i].ch + 0 >= 256)
			return;
		if (ln->u.du[i].at != ln->u.du[0].at)
			return;
	}
	if (!list_empty(f->compressed_chars)) {
		cs = list_struct(f->compressed_chars.next, struct compressed_chars);
		if (ln->l > cs->total_len - cs->cpos)
			goto out_of_space;
		goto fill_it;
	}
out_of_space:
	want_len = (int)(page_size - alloc_overhead - offsetof(struct compressed_chars, chars));
	if (want_len < ln->l)
		want_len = ln->l;
	cs = mem_alloc_mayfail(offsetof(struct compressed_chars, chars) + want_len);
	if (!cs)
		return;
	cs->total_len = want_len;
	cs->cpos = 0;
	add_to_list(f->compressed_chars, cs);
fill_it:
	for (i = 0; i < ln->l; i++) {
		cs->chars[cs->cpos + i] = (unsigned char)ln->u.du[i].ch;
	}
	if (ln->l)
		ln->allocated = -0x100 + ln->u.du[0].at;
	else
		ln->allocated = -0x100;
	mem_free(ln->u.du);
	ln->u.dc = &cs->chars[cs->cpos];
	cs->cpos += ln->l;
}

static void align_line(struct part *p, int y)
{
	int na;
	if (!p->data) return;
	if (!LEN(y) || par_format.align == AL_LEFT || par_format.align == AL_NO || par_format.align == AL_NO_BREAKABLE || par_format.align == AL_BLOCK /* !!! fixme! */) goto compr;
	na = rm(par_format) - LEN(y);
	if (par_format.align == AL_CENTER) na /= 2;
	if (na > 0) shift_chars(p, y, na);
compr:
	if (!table_level) compress_line(p->data, y);
}

struct link *new_link(struct f_data *f)
{
	if (!f) return NULL;
	if (!(f->nlinks & (ALLOC_GR - 1))) {
		if ((unsigned)f->nlinks > MAXINT / sizeof(struct link) - ALLOC_GR) overalloc();
		f->links = mem_realloc(f->links, (f->nlinks + ALLOC_GR) * sizeof(struct link));
	}
	memset(&f->links[f->nlinks], 0, sizeof(struct link));
#ifdef G
	f->links[f->nlinks].r.x1 = MAXINT;
	f->links[f->nlinks].r.y1 = MAXINT;
#endif
	return &f->links[f->nlinks++];
}

void html_tag(struct f_data *f, unsigned char *t, int x, int y)
{
	struct tag *tag;
	size_t sl;
	unsigned char *tt;
	int ll;
	if (!f) return;
	tt = init_str();
	ll = 0;
	add_conv_str(&tt, &ll, t, (int)strlen(cast_const_char t), -2);
	sl = strlen(cast_const_char tt);
	if (sl > MAXINT - sizeof(struct tag)) overalloc();
	tag = mem_alloc(sizeof(struct tag) + sl);
	tag->x = x;
	tag->y = y;
	strcpy(cast_char tag->name, cast_const_char tt);
	add_to_list(f->tags, tag);
	if (last_tag_for_newline == &f->tags) last_tag_for_newline = &tag->list_entry;
	mem_free(tt);
}

unsigned char *last_link = NULL;
unsigned char *last_target = NULL;
unsigned char *last_image = NULL;
struct form_control *last_form = NULL;
struct js_event_spec *last_js_event = NULL;

static int nobreak;

struct conv_table *convert_table;

static void put_chars(void *p_, unsigned char *c, int l)
{
	struct part *p = p_;

	static struct text_attrib_beginning ta_cache = { -1, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0, 0 };
	static int bg_cache;
	static int fg_cache;
	char_t *uni_c = DUMMY;

	int bg, fg;
	int i;
	struct link *link;
	struct point *pt;
	int ll;

	if (l < 0) overalloc();

	/*printf("%d-", p->cx);for (i=0; i<l; i++) printf("%c", c[i]); printf("-\n");portable_sleep(1000);*/
	while (p->cx <= par_format.leftmargin && l && *c == ' ' && par_format.align != AL_NO && par_format.align != AL_NO_BREAKABLE) c++, l--;
	if (!l) return;
	if (p->cx < par_format.leftmargin) p->cx = par_format.leftmargin;
	if (c[0] != ' ' || (c[1] && c[1] != ' ')) {
		if (p->data)
			last_tag_for_newline = &p->data->tags;
	}
#ifdef ENABLE_UTF8
	if (d_opt->cp == utf8_table && !(format_.attr & AT_GRAPHICS)) {
		int pl;
		unsigned char *cc;
		if (p->utf8_part_len) {
			unsigned char new_part[7];
			unsigned char *q;
			next_utf_byte:
			if ((*c & 0xc0) != 0x80)
				goto bad_utf;
			p->utf8_part[p->utf8_part_len++] = *c;
			p->utf8_part[p->utf8_part_len] = 0;
			c++;
			l--;
			q = p->utf8_part;
			if (!get_utf_8(&q)) {
				if (p->utf8_part_len == sizeof(p->utf8_part) - 1)
					goto bad_utf;
				if (l) goto next_utf_byte;
				return;
			}
			pl = p->utf8_part_len;
			p->utf8_part_len = 0;
			strcpy(cast_char new_part, cast_const_char p->utf8_part);
			put_chars(p, new_part, pl);
		}
		bad_utf:
		p->utf8_part_len = 0;
		if (!l) return;
		if ((unsigned)l > (unsigned)MAXINT / sizeof(char_t)) overalloc();
		uni_c = mem_alloc(l * sizeof(char_t));
		ll = 0;
		cc = c;
		next_utf_char:
		pl = utf8chrlen(*cc);
		if (safe_add((int)(cc - c), pl) > l) {
			memcpy(p->utf8_part, cc, p->utf8_part_len = l - (int)(cc - c));
			goto utf_done;
		} else {
			if (!pl) {
				cc++;
			} else {
				unsigned un;
				GET_UTF_8(cc, un);
				if (un != 0xad) {
					uni_c[ll] = un;
					ll = safe_add(ll, 1);
				}
			}
			if (cc < c + l) goto next_utf_char;
		}
		utf_done:
		if (!ll) {
			mem_free(uni_c);
			return;
		}
	} else
#endif
	{
		ll = l;
	}
	if (last_link || last_image || last_form || format_.link || format_.image
	|| format_.form || format_.js_event || last_js_event
	) goto process_link;
	no_l:
	/*printf("%d %d\n",p->cx, p->cy);*/
	if (memcmp(&ta_cache, &format_, sizeof(struct text_attrib_beginning))) goto format_change;
	bg = bg_cache, fg = fg_cache;
	end_format_change:
	if (p->y < safe_add(p->cy, 1)) p->y = p->cy + 1;
	if (nowrap && safe_add(p->cx, ll) > rm(par_format)) {
		mem_free(uni_c);
		return;
	}
#ifdef ENABLE_UTF8
	if (d_opt->cp == utf8_table && !(format_.attr & AT_GRAPHICS)) {
		set_hline_uni(p, p->cx, p->cy, ll, uni_c, ((fg&0x08)<<3)|(bg<<3)|(fg&0x07));
	} else
#endif
	{
		set_hline(p, p->cx, p->cy, l, c, ((fg&0x08)<<3)|(bg<<3)|(fg&0x07));
	}
	p->cx += ll;
	nobreak = 0;
	if (par_format.align != AL_NO)
		while (p->cx > rm(par_format) && p->cx > par_format.leftmargin) {
			int x;
			if (!(x = split_line(p))) break;
			align_line(p, p->cy - 1);
			nobreak = x - 1;
		}
	p->xa = safe_add(p->xa, ll);
	if (safe_add(p->xa - (c[l-1] == ' ' && par_format.align != AL_NO && par_format.align != AL_NO_BREAKABLE), safe_add(par_format.leftmargin, par_format.rightmargin)) > p->xmax) p->xmax = p->xa - (c[l-1] == ' ' && par_format.align != AL_NO && par_format.align != AL_NO_BREAKABLE) + par_format.leftmargin + par_format.rightmargin;
	mem_free(uni_c);
	return;

	/* !!! WARNING: THE FOLLOWING CODE IS SHADOWED IN HTML_GR.C */

	process_link:
	if ((last_link || last_image || last_form) &&
	    !xstrcmp(format_.link, last_link) && !xstrcmp(format_.target, last_target) &&
	    !xstrcmp(format_.image, last_image) && format_.form == last_form
	    && ((!format_.js_event && !last_js_event) || !compare_js_event_spec(format_.js_event, last_js_event))
	    ) {
		if (!p->data) goto x;
		link = &p->data->links[p->data->nlinks - 1];
		if (!p->data->nlinks) {
			internal_error("no link");
			goto no_l;
		}
		goto set_link;
		x:;
	} else {
		if (last_link) mem_free(last_link);
		if (last_target) mem_free(last_target);
		if (last_image) mem_free(last_image);
		free_js_event_spec(last_js_event);
		last_link = last_target = last_image = NULL;
		last_form = NULL;
		last_js_event = NULL;
		if (!(format_.link || format_.image || format_.form || format_.js_event)) goto no_l;
		if (d_opt->num_links || d_opt->braille) {
			unsigned char s[64];
			unsigned char *fl = format_.link, *ft = format_.target, *fi = format_.image;
			struct form_control *ff = format_.form;
			struct js_event_spec *js = format_.js_event;
			format_.link = format_.target = format_.image = NULL;
			format_.form = NULL;
			format_.js_event = NULL;
			if (d_opt->num_links) {
				s[0] = '[';
				snzprint(s + 1, 62, p->link_num);
				strcat(cast_char s, "]");
			} else {
				if (ff && (ff->type == FC_TEXT || ff->type == FC_PASSWORD || ff->type == FC_FILE_UPLOAD || ff->type == FC_TEXTAREA)) {
					strcpy(cast_char s, ">");
				} else if (ff && (ff->type == FC_CHECKBOX || ff->type == FC_RADIO || ff->type == FC_SELECT)) {
					strcpy(cast_char s, "");
				} else {
					strcpy(cast_char s, "~");
				}
			}
			put_chars(p, s, (int)strlen(cast_const_char s));
			if (ff && ff->type == FC_TEXTAREA) line_break(p);
			if (p->cx < par_format.leftmargin) p->cx = par_format.leftmargin;
			format_.link = fl, format_.target = ft, format_.image = fi;
			format_.form = ff;
			format_.js_event = js;
		}
		p->link_num++;
		last_link = stracpy(format_.link);
		last_target = stracpy(format_.target);
		last_image = stracpy(format_.image);
		last_form = format_.form;
		copy_js_event_spec(&last_js_event, format_.js_event);
		if (!p->data) goto no_l;
		if (!(link = new_link(p->data))) goto no_l;
		link->num = p->link_num - 1;
		link->pos = DUMMY;
		copy_js_event_spec(&link->js_event, format_.js_event);
		if (!last_form) {
			link->type = L_LINK;
			link->where = stracpy(last_link);
			link->target = stracpy(last_target);
		} else {
			link->type = last_form->type == FC_TEXT || last_form->type == FC_PASSWORD || last_form->type == FC_FILE_UPLOAD ? L_FIELD : last_form->type == FC_TEXTAREA ? L_AREA : last_form->type == FC_CHECKBOX || last_form->type == FC_RADIO ? L_CHECKBOX : last_form->type == FC_SELECT ? L_SELECT : L_BUTTON;
			link->form = last_form;
			link->target = stracpy(last_form->target);
		}
		link->where_img = stracpy(last_image);
		if (link->type != L_FIELD && link->type != L_AREA) {
			bg = find_nearest_color(&format_.clink, 8);
			fg = find_nearest_color(&format_.bg, 8);
			fg = fg_color(fg, bg);
		} else {
			fg = find_nearest_color(&format_.fg, 8);
			bg = find_nearest_color(&format_.bg, 8);
			fg = fg_color(fg, bg);
		}
		link->sel_color = get_attribute(fg, bg);
		link->n = 0;
		set_link:
		if ((unsigned)link->n + (unsigned)ll > MAXINT / sizeof(struct point)) overalloc();
		pt = mem_realloc(link->pos, (link->n + ll) * sizeof(struct point));
		link->pos = pt;
		for (i = 0; i < ll; i++) pt[link->n + i].x = X(p->cx) + i,
					 pt[link->n + i].y = Y(p->cy);
		link->n += ll;
	}
	goto no_l;

		format_change:
		bg = find_nearest_color(&format_.bg, 8);
		fg = find_nearest_color(&format_.fg, 16);
		fg = fg_color(fg, bg);
		if (format_.attr & AT_ITALIC) fg = fg ^ 0x01;
		if (format_.attr & AT_UNDERLINE) fg = (fg ^ 0x04) | 0x08;
		if (format_.attr & AT_BOLD) fg = fg | 0x08;
		fg = fg_color(fg, bg);
		if (format_.attr & AT_GRAPHICS) bg = bg | 0x10;
		memcpy(&ta_cache, &format_, sizeof(struct text_attrib_beginning));
		fg_cache = fg; bg_cache = bg;
		goto end_format_change;
}

static void line_break(void *p_)
{
	struct part *p = p_;
	struct tag *t;
	struct list_head *lt;
	if (p->cx >= 0 && safe_add(p->cx, par_format.rightmargin) > p->x) p->x = p->cx + par_format.rightmargin;
	if (nobreak) {
		nobreak = 0;
		p->cx = -1;
		p->xa = 0;
		return;
	}
	if (!p->data) goto e;
	xpand_lines(p, safe_add(p->cy, 1));
	if (p->cx > par_format.leftmargin && LEN(p->cy) > p->cx - 1 && POS(p->cx-1, p->cy).ch == ' ') del_chars(p, p->cx-1, p->cy), p->cx--;
	if (p->cx >= 0) align_line(p, p->cy);
	if (p->data) foreachbackfrom(struct tag, t, lt, p->data->tags, last_tag_for_newline) {
		t->x = X(0);
		t->y = Y(p->cy + 1);
	}
	e:
	p->cy++; p->cx = -1; p->xa = 0;
	if (p->spl > d_opt->xw) p->spl = d_opt->xw;
	memset(p->spaces, 0, p->spl);
	p->z_spaces = 0;
}

int g_ctrl_num;

/* SHADOWED IN g_html_form_control */
static void html_form_control(struct part *p, struct form_control *fc)
{
	if (!p->data) {
		add_to_list(p->uf, fc);
		return;
	}
	fc->g_ctrl_num = g_ctrl_num;
	g_ctrl_num = safe_add(g_ctrl_num, 1);
	if (fc->type == FC_TEXT || fc->type == FC_PASSWORD || fc->type == FC_TEXTAREA) {
		unsigned char *dv = convert_string(convert_table, fc->default_value, (int)strlen(cast_const_char fc->default_value), d_opt);
		if (dv) {
			mem_free(fc->default_value);
			fc->default_value = dv;
		}
	}
	if (fc->type == FC_TEXTAREA) {
		unsigned char *p;
		for (p = fc->default_value; p[0]; p++) if (p[0] == '\r') {
			if (p[1] == '\n') memmove(p, p + 1, strlen(cast_const_char p)), p--;
			else p[0] = '\n';
		}
	}
	add_to_list(p->data->forms, fc);
}

static void add_frameset_entry(struct frameset_desc *fsd, struct frameset_desc *subframe, unsigned char *name, unsigned char *url, int marginwidth, int marginheight, unsigned char scrolling)
{
	if (fsd->yp >= fsd->y) return;
	fsd->f[fsd->xp + fsd->yp * fsd->x].subframe = subframe;
	fsd->f[fsd->xp + fsd->yp * fsd->x].name = stracpy(name);
	fsd->f[fsd->xp + fsd->yp * fsd->x].url = stracpy(url);
	fsd->f[fsd->xp + fsd->yp * fsd->x].marginwidth = marginwidth;
	fsd->f[fsd->xp + fsd->yp * fsd->x].marginheight = marginheight;
	fsd->f[fsd->xp + fsd->yp * fsd->x].scrolling = scrolling;
	if (++fsd->xp >= fsd->x) fsd->xp = 0, fsd->yp++;
}

struct frameset_desc *create_frameset(struct f_data *fda, struct frameset_param *fp)
{
	int i;
	struct frameset_desc *fd;
	if (!fp->x || !fp->y) {
		internal_error("zero size of frameset");
		return NULL;
	}
	if (fp->x && (unsigned)fp->x * (unsigned)fp->y / (unsigned)fp->x != (unsigned)fp->y) overalloc();
	if ((unsigned)fp->x * (unsigned)fp->y > (MAXINT - sizeof(struct frameset_desc)) / sizeof(struct frame_desc)) overalloc();
	fd = mem_calloc(sizeof(struct frameset_desc) + fp->x * fp->y * sizeof(struct frame_desc));
	fd->n = fp->x * fp->y;
	fd->x = fp->x;
	fd->y = fp->y;
	for (i = 0; i < fd->n; i++) {
		fd->f[i].xw = fp->xw[i % fp->x];
		fd->f[i].yw = fp->yw[i / fp->x];
	}
	if (fp->parent) add_frameset_entry(fp->parent, fd, NULL, NULL, -1, -1, SCROLLING_AUTO);
	else if (!fda->frame_desc) fda->frame_desc = fd;
	     else mem_free(fd), fd = NULL;
	return fd;
}

void create_frame(struct frame_param *fp)
{
	add_frameset_entry(fp->parent, NULL, fp->name, fp->url, fp->marginwidth, fp->marginheight, fp->scrolling);
}

void process_script(struct f_data *f, unsigned char *t)
{
#ifdef JS
	if (t && !f->script_href_base) f->script_href_base = stracpy(format_.href_base);
	if (!d_opt->js_enable) return;
	if (t) {
		unsigned char *u;
		u = join_urls(f->script_href_base, t);
		if (u) {
			request_additional_file(f, u);
			mem_free(u);
		}
	}
	f->are_there_scripts = 1;
#endif
}

void set_base(struct f_data *f, unsigned char *t)
{
#ifdef JS
	if (!f->script_href_base) f->script_href_base = stracpy(format_.href_base);
#endif
}

void html_process_refresh(struct f_data *f, unsigned char *url, int time)
{
	if (!f) return;
	if (f->refresh) return;
	if (!url) f->refresh = stracpy(f->rq->url);
	else f->refresh = join_urls(f->rq->url, url);
	f->refresh_seconds = time;
}

static void *html_special(void *p_, int c, ...)
{
	struct part *p = p_;
	va_list l;
	unsigned char *t;
	struct form_control *fc;
	struct frameset_param *fsp;
	struct frame_param *fp;
	struct refresh_param *rp;
	va_start(l, c);
	switch (c) {
		case SP_TAG:
			t = va_arg(l, unsigned char *);
			va_end(l);
			html_tag(p->data, t, X(p->cx >= 0 ? p->cx : 0), Y(p->cy));
			break;
		case SP_CONTROL:
			fc = va_arg(l, struct form_control *);
			va_end(l);
			html_form_control(p, fc);
			break;
		case SP_TABLE:
			va_end(l);
			return convert_table;
		case SP_USED:
			va_end(l);
			return (void *)(my_intptr_t)!!p->data;
		case SP_FRAMESET:
			fsp = va_arg(l, struct frameset_param *);
			va_end(l);
			return create_frameset(p->data, fsp);
		case SP_FRAME:
			fp = va_arg(l, struct frame_param *);
			va_end(l);
			create_frame(fp);
			break;
		case SP_NOWRAP:
			nowrap = va_arg(l, int);
			va_end(l);
			break;
		case SP_SCRIPT:
			t = va_arg(l, unsigned char *);
			va_end(l);
			if (p->data) process_script(p->data, t);
			break;
		case SP_REFRESH:
			rp = va_arg(l, struct refresh_param *);
			va_end(l);
			html_process_refresh(p->data, rp->url, rp->time);
			break;
		case SP_SET_BASE:
			t = va_arg(l, unsigned char *);
			va_end(l);
			if (p->data) set_base(p->data, t);
			break;
		case SP_FORCE_BREAK:
			if (p->cx > par_format.leftmargin) {
				nobreak = 0;
				line_break(p);
			}
			break;
		default:
			va_end(l);
			internal_error("html_special: unknown code %d", c);
	}
	return NULL;
}

static void do_format(unsigned char *start, unsigned char *end, struct part *part, unsigned char *head)
{
	pr(
	parse_html(start, end, put_chars, line_break, html_special, part, head);
	) {};
}

int margin;

struct part *format_html_part(unsigned char *start, unsigned char *end, int align, int m, int width, struct f_data *data, int xs, int ys, unsigned char *head, int link_num)
{
	struct part *p;
	struct html_element *e;
	int llm = last_link_to_move;
	struct list_head *ltm = last_tag_to_move;
	int lm = margin;
	int ef = empty_format;
	struct form_control *fc;
	struct list_head *lfc;

	if (par_format.implicit_pre_wrap) {
		if (width > d_opt->xw)
			width = d_opt->xw;
	}

	if (!data) {
		p = find_table_cache_entry(start, end, align, m, width, xs, link_num);
		if (p) return p;
	}
	if (ys < 0) {
		internal_error("format_html_part: ys == %d", ys);
		return NULL;
	}
	if (data) {
		struct node *n;
		n = mem_alloc(sizeof(struct node));
		n->x = xs;
		n->y = ys;
		n->xw = !table_level ? MAXINT - 1 : width;
		add_to_list(data->nodes, n);
	}
	last_link_to_move = data ? data->nlinks : 0;
	last_tag_to_move = data ? &data->tags : NULL;
	last_tag_for_newline = data ? &data->tags : NULL;
	margin = m;
	empty_format = !data;
	if (last_link) mem_free(last_link);
	if (last_image) mem_free(last_image);
	if (last_target) mem_free(last_target);
	free_js_event_spec(last_js_event);
	last_link = last_image = last_target = NULL;
	last_form = NULL;
	last_js_event = NULL;
	nobreak = align != AL_NO && align != AL_NO_BREAKABLE;
	p = mem_calloc(sizeof(struct part));
	/*p->x = p->y = 0;*/
	p->data = data;
	p->xp = xs; p->yp = ys;
	/*p->xmax = p->xa = 0;*/
	p->attribute = get_attribute(find_nearest_color(&format_.fg, 16), find_nearest_color(&par_format.bgcolor, 8));
	p->spaces = DUMMY;
	/*p->z_spaces = 0;*/
	/*p->spl = 0;*/
	p->link_num = link_num;
	init_list(p->uf);
	html_stack_dup();
	e = &html_top;
	html_top.dontkill = 2;
	html_top.namelen = 0;
	par_format.align = align;
	par_format.leftmargin = m;
	par_format.rightmargin = m;
	par_format.width = width;
	par_format.list_level = 0;
	par_format.list_number = 0;
	par_format.dd_margin = 0;
	if (align == AL_NO || align == AL_NO_BREAKABLE)
		format_.attr |= AT_FIXED;
	p->cx = -1;
	p->cy = 0;
	do_format(start, end, p, head);
	if (p->xmax < p->x) p->xmax = p->x;
	if (align == AL_NO || align == AL_NO_BREAKABLE) {
		if (p->cy > p->y)
			p->y = p->cy;
	}
	nobreak = 0;
	line_breax = 1;
	if (last_link) mem_free(last_link);
	if (last_image) mem_free(last_image);
	if (last_target) mem_free(last_target);
	free_js_event_spec(last_js_event);
	while (&html_top != e) {
		kill_html_stack_item(&html_top);
		if (!&html_top || (void *)&html_top == (void *)&html_stack) {
			internal_error("html stack trashed");
			break;
		}
	}
	html_top.dontkill = 0;
	kill_html_stack_item(&html_top);
	mem_free(p->spaces);
	if (data) {
		struct node *n = list_struct(data->nodes.next, struct node);
		n->yw = ys - n->y + p->y;
	}
	foreach(struct form_control, fc, lfc, p->uf) destroy_fc(fc);
	free_list(struct form_control, p->uf);
	last_link_to_move = llm;
	last_tag_to_move = ltm;
	margin = lm;
	empty_format = ef;
	last_link = last_image = last_target = NULL;
	last_form = NULL;
	last_js_event = NULL;

	if (table_level > 1 && !data) {
		add_table_cache_entry(start, end, align, m, width, xs, link_num, p);
	}
	return p;
}

static void release_part(struct part *p)
{
	mem_free(p);
}

static void push_base_format(unsigned char *url, struct document_options *opt, int frame, int implicit_pre_wrap)
{
	struct html_element *e;
	if (!list_empty(html_stack)) {
		internal_error("something on html stack");
		init_list(html_stack);
	}
	e = mem_calloc(sizeof(struct html_element));
	add_to_list(html_stack, e);
	format_.attr = opt->plain & 1 ? AT_FIXED : 0;
	format_.fontsize = 3;
	format_.link = format_.target = format_.image = format_.select = NULL;
	format_.form = NULL;
	memcpy(&format_.fg, &opt->default_fg, sizeof(struct rgb));
	memcpy(&format_.bg, &opt->default_bg, sizeof(struct rgb));
	memcpy(&format_.clink, &opt->default_link, sizeof(struct rgb));
	format_.href_base = stracpy(url);
	format_.target_base = stracpy(opt->framename);
	par_format.align = !(opt->plain & 1) ? AL_LEFT : !implicit_pre_wrap ? AL_NO : AL_NO_BREAKABLE;
	par_format.leftmargin = opt->plain & 1 ? 0 : opt->margin;
	par_format.rightmargin = opt->plain & 1 ? 0 : opt->margin;
	if (frame && par_format.leftmargin) par_format.leftmargin = 1;
	if (frame && par_format.rightmargin) par_format.rightmargin = 1;
	par_format.width = opt->xw;
	par_format.list_level = par_format.list_number = 0;
	par_format.dd_margin = opt->margin;
	par_format.flags = 0;
	memcpy(&par_format.bgcolor, &opt->default_bg, sizeof(struct rgb));
	par_format.implicit_pre_wrap = implicit_pre_wrap;
	html_top.invisible = 0;
	html_top.name = NULL; html_top.namelen = 0; html_top.options = NULL;
	html_top.linebreak = 1;
	html_top.dontkill = 1;
}

struct conv_table *get_convert_table(unsigned char *head, int to, int def, int *frm, int *aa, int hard)
{
	int from = -1;
	unsigned char *a, *b;
	unsigned char *p = head;
	while (from == -1 && (a = parse_http_header(p, cast_uchar "Content-Type", &p))) {
		if ((b = parse_header_param(a, cast_uchar "charset", 0))) {
			from = get_cp_index(b);
			mem_free(b);
		}
		mem_free(a);
	}
	if (from == -1 && (a = parse_http_header(head, cast_uchar "Content-Charset", NULL))) {
		from = get_cp_index(a);
		mem_free(a);
	}
	if (from == -1 && (a = parse_http_header(head, cast_uchar "Charset", NULL))) {
		from = get_cp_index(a);
		mem_free(a);
	}
	if (aa) {
		*aa = from == -1;
		if (hard && !*aa) *aa = 2;
	}
	if (hard || from == -1) from = def;
	if (frm) *frm = from;
	return get_translation_table(from, to);
}

struct document_options dd_opt;

struct document_options *d_opt = &dd_opt;

struct f_data *current_f_data = NULL;

void really_format_html(struct cache_entry *ce, unsigned char *start, unsigned char *end, struct f_data *screen, int frame)
{
	unsigned char *url = ce->url;
	unsigned char *head, *t;
	int hdl;
	int i;
	unsigned char *bg = NULL, *bgcolor = NULL;
	int implicit_pre_wrap;
	int bg_col, fg_col;
	current_f_data = screen;
	d_opt = &screen->opt;
	screen->use_tag = ce->count;
	startf = start;
	eofff = end;
	head = init_str(), hdl = 0;
	if (ce->head) add_to_str(&head, &hdl, ce->head);
	scan_http_equiv(start, end, &head, &hdl, &t, d_opt->plain ? NULL : &bg, d_opt->plain || d_opt->col < 2 ? NULL : &bgcolor, &implicit_pre_wrap,
#ifdef JS
		&screen->js_event
#else
		NULL
#endif
		);
	if (d_opt->break_long_lines) implicit_pre_wrap = 1;
	if (d_opt->plain) *t = 0;
	if (screen->opt.plain == 2) {
		screen->cp = utf8_table;
		screen->ass = -1;
		convert_table = get_translation_table(utf8_table, screen->opt.cp);
	} else {
		convert_table = get_convert_table(head, screen->opt.cp, screen->opt.assume_cp, &screen->cp, &screen->ass, screen->opt.hard_assume);
	}
	screen->opt.real_cp = screen->cp;
	i = d_opt->plain; d_opt->plain = 0;
	screen->title = convert_string(convert_table, t, (int)strlen(cast_const_char t), d_opt);
	d_opt->plain = i;
	mem_free(t);
	push_base_format(url, &screen->opt, frame, implicit_pre_wrap);
	table_level = 0;
	g_ctrl_num = 0;
	last_form_tag = NULL;
	last_form_attr = NULL;
	last_input_tag = NULL;
	if (!F) {
		struct part *rp;
		if ((rp = format_html_part(start, end, par_format.align, par_format.leftmargin, screen->opt.xw, screen, 0, 0, head, 1))) release_part(rp);
#ifdef G
	} else {
		struct g_part *rp;
		if ((rp = g_format_html_part(start, end, par_format.align, par_format.leftmargin, screen->opt.xw - G_SCROLL_BAR_WIDTH, head, 1, bg, bgcolor, screen))) {
			int w = screen->opt.xw;
			int h = screen->opt.yw;
			screen->x = rp->x;
			screen->y = rp->root->go.yw;
			if (screen->x > w) w = screen->x;
			if (screen->y > h) h = screen->y;
			g_x_extend_area(rp->root, w, h, AL_LEFT);
			screen->root = &rp->root->go, rp->root = NULL;
			g_release_part(rp);
			mem_free(rp);
			get_parents(screen, screen->root);
		}
#endif
	}
	mem_free(head);
	if (bg) mem_free(bg);
	if (bgcolor) mem_free(bgcolor);
	if (!F) {
		screen->x = 0;
		for (i = screen->y - 1; i >= 0; i--) {
			if (!screen->data[i].l) {
				if (screen->data[i].allocated >= 0)
					mem_free(screen->data[i].u.du);
				screen->y--;
			} else {
				break;
			}
		}
		for (i = 0; i < screen->y; i++) if (screen->data[i].l > screen->x) screen->x = screen->data[i].l;
		screen->data = mem_realloc(screen->data, screen->y * sizeof(struct line));
	}
	if (form.action) mem_free(form.action), form.action = NULL;
	if (form.target) mem_free(form.target), form.target = NULL;
	if (form.form_name) mem_free(form.form_name), form.form_name = NULL;
	if (form.onsubmit) mem_free(form.onsubmit), form.onsubmit = NULL;
	bg_col = find_nearest_color(&format_.bg, 8);
	fg_col = find_nearest_color(&format_.fg, 16);
	fg_col = fg_color(fg_col, bg_col);
	screen->bg = get_attribute(fg_col, bg_col);
	kill_html_stack_item(&html_top);
	if (!list_empty(html_stack)) {
		internal_error("html stack not empty after operation");
		init_list(html_stack);
	}
	sort_links(screen);
	current_f_data = NULL;
	d_opt = &dd_opt;
}

int compare_opt(struct document_options *o1, struct document_options *o2)
{
	if (o1->xw == o2->xw &&
	    o1->yw == o2->yw &&
	    o1->xp == o2->xp &&
	    o1->yp == o2->yp &&
	    o1->scrolling == o2->scrolling &&
	    o1->col == o2->col &&
	    o1->cp == o2->cp &&
	    o1->assume_cp == o2->assume_cp &&
	    o1->hard_assume == o2->hard_assume &&
	    o1->braille == o2->braille &&
	    o1->tables == o2->tables &&
	    o1->frames == o2->frames &&
	    o1->break_long_lines == o2->break_long_lines &&
	    o1->images == o2->images &&
	    o1->image_names == o2->image_names &&
	    o1->margin == o2->margin &&
	    o1->js_enable == o2->js_enable &&
	    o1->plain == o2->plain &&
	    o1->num_links == o2->num_links &&
	    o1->table_order == o2->table_order &&
	    o1->auto_refresh == o2->auto_refresh &&
	    o1->font_size == o2->font_size &&
	    o1->display_images == o2->display_images &&
	    o1->image_scale == o2->image_scale &&
	    o1->porn_enable == o2->porn_enable &&
	    o1->gamma_stamp == o2->gamma_stamp &&
	    !memcmp(&o1->default_fg, &o2->default_fg, sizeof(struct rgb)) &&
	    !memcmp(&o1->default_bg, &o2->default_bg, sizeof(struct rgb)) &&
	    !memcmp(&o1->default_link, &o2->default_link, sizeof(struct rgb)) &&
	    ((o1->framename && o2->framename && !casestrcmp(o1->framename, o2->framename)) || (!o1->framename && !o2->framename))) return 0;
	return 1;
}

void copy_opt(struct document_options *o1, struct document_options *o2)
{
	memcpy(o1, o2, sizeof(struct document_options));
	o1->framename = stracpy(o2->framename);
}

struct link *get_link_at_location(struct f_data *f, int x, int y)
{
	struct link *l1, *l2, *l;
	if (y < 0 || y >= f->y) return NULL;
	l1 = f->lines1[y];
	l2 = f->lines2[y];
	if (!l1 || !l2) return NULL;
	for (l = l1; l <= l2; l++) {
		int i;
		for (i = 0; i < l->n; i++) if (l->pos[i].x == x && l->pos[i].y == y) return l;
	}
	return NULL;
}

static int sort_srch(struct f_data *f)
{
	int i;
	int *min, *max;
	if ((unsigned)f->y > MAXINT / sizeof(struct search *)) overalloc();
	if ((unsigned)f->y > MAXINT / sizeof(int)) overalloc();
	f->slines1 = mem_alloc_mayfail(f->y * sizeof(int));
	f->slines2 = mem_alloc_mayfail(f->y * sizeof(int));
	min = mem_alloc_mayfail(f->y * sizeof(int));
	max = mem_alloc_mayfail(f->y * sizeof(int));
	if (!f->slines1 || !f->slines2 || !min || !max) {
		if (f->slines1) mem_free(f->slines1), f->slines1 = NULL;
		if (f->slines2) mem_free(f->slines2), f->slines2 = NULL;
		if (min) mem_free(min);
		if (max) mem_free(max);
		return -1;
	}
	for (i = 0; i < f->y; i++)
		f->slines1[i] = f->slines2[i] = -1;
	for (i = 0; i < f->y; i++) min[i] = MAXINT, max[i] = 0;
	for (i = 0; i < f->nsearch_pos; i++) {
		struct search *s = &f->search_pos[i];
		int xe;
		if (s->x < min[s->y]) min[s->y] = s->x, f->slines1[s->y] = s->idx;
		if (s->n == 1) xe = safe_add(s->x, s->co);
		else xe = safe_add(s->x, s->n);
		if (xe > max[s->y]) max[s->y] = xe, f->slines2[s->y] = s->idx + s->co - 1;
	}
	mem_free(min);
	mem_free(max);
	return 0;
}

static int n_chr, n_pos;
static char_t srch_last_chr;
static int srch_last_x, srch_last_y;
static int srch_cont;

static void get_srch_reset(void)
{
	n_chr = n_pos = 0;
	srch_last_chr = ' ';
	srch_last_x = srch_last_y = -1;
	srch_cont = 0;
}

static int add_srch_chr(struct f_data *f, unsigned c, int x, int y, int nn)
{
	if (c == ' ' && srch_last_chr == ' ') return 0;
	if (c == '_') {
		struct link *l = get_link_at_location(f, x, y);
		if (l && (l->type == L_SELECT || l->type == L_FIELD || l->type == L_AREA))
			return 0;
	}
	srch_last_chr = c;
	if (f->search_chr) f->search_chr[n_chr] = c;
	if (n_chr == MAXINT) return -1;
	n_chr++;
	if (srch_cont < 0xffff && x == srch_last_x + 1 && y == srch_last_y && nn == 1) {
		srch_cont++;
		if (f->search_pos)
			f->search_pos[n_pos - 1].co = (unsigned short)srch_cont;
	} else {
		if (f->search_pos) {
			f->search_pos[n_pos].idx = n_chr - 1;
			f->search_pos[n_pos].x = x;
			f->search_pos[n_pos].y = y;
			f->search_pos[n_pos].n = (unsigned short)nn;
			if (f->search_pos[n_pos].n != nn)
				f->search_pos[n_pos].n = (unsigned short)~0U;
			f->search_pos[n_pos].co = 1;
		}
		srch_cont = 1;
		if (n_pos == MAXINT) return -1;
		n_pos++;
	}
	if (nn == 1) {
		srch_last_x = x;
		srch_last_y = y;
	} else {
		srch_last_x = -1;
		srch_last_y = -1;
	}
	return 0;
}

static inline int is_spc(struct f_data *f, int x, int y)
{
	unsigned ch;
	unsigned char at;
	get_char_attr(f, x, y, &ch, &at);
	return ch <= ' ' || at & ATTR_FRAME;
}

static int get_srch(struct f_data *f)
{
	struct node *n;
	struct list_head *ln;
	get_srch_reset();
#define add_srch(c_, x_, y_, n_)		\
do {						\
	if (add_srch_chr(f, c_, x_, y_, n_))	\
		return -1;			\
} while (0)
	foreachback(struct node, n, ln, f->nodes) {
		int x, y;
		int xm = safe_add(n->x, n->xw), ym = safe_add(n->y, n->yw);
		/*printf("%d %d - %d %d\n", n->x, n->y, xm, ym);
		fflush(stdout);*/
		for (y = n->y; y < ym && y < f->y; y++) {
			struct line *ln = &f->data[y];
			int ns = 1;
			for (x = n->x; x < xm && x < ln->l; x++) {
				unsigned c;
				if (ln->allocated >= 0)
					c = ln->u.du[x].ch;
				else
					c = ln->u.dc[x];
				if (is_spc(f, x, y)) c = ' ';
				if (c == ' ' && ns) continue;
				c = charset_upcase(c, f->opt.cp);
				if (ns) {
					add_srch(c, x, y, 1);
					ns = 0;
					continue;
				}
				if (c != ' ') {
					add_srch(c, x, y, 1);
				} else {
					int xx;
					for (xx = safe_add(x, 1); xx < xm && xx < f->data[y].l; xx++) if (!is_spc(f, xx, y)) goto ja_uz_z_toho_programovani_asi_zcvoknu;
					xx = x;
					ja_uz_z_toho_programovani_asi_zcvoknu:
				/* uz jsem zcvoknul, trpim poruchou osobnosti */
					add_srch(' ', x, y, xx - x);
					if (xx == x) goto uz_jsem_zcvoknul__jsem_psychopat__trpim_poruchou_osobnosti;
					x = xx - 1;
				}
			}
			uz_jsem_zcvoknul__jsem_psychopat__trpim_poruchou_osobnosti:
			add_srch(' ', x, y, 0);
		}
	}
#undef add_srch
	return 0;
}

int get_search_data(struct f_data *f)
{
	if (f->search_pos) return 0;
	if (get_srch(f)) return -1;
	if ((size_t)n_chr > MAX_SIZE_T / sizeof(char_t) || (size_t)n_pos > MAX_SIZE_T / sizeof(struct search)) return -1;
	f->search_chr = mem_alloc_mayfail(n_chr * sizeof(char_t));
	if (!f->search_chr) return -1;
	f->search_pos = mem_alloc_mayfail(n_pos * sizeof(struct search));
	if (!f->search_pos) {
		mem_free(f->search_chr);
		f->search_chr = NULL;
		return -1;
	}
	if (get_srch(f)) internal_error("get_search_data: get_srch should not fail second time");
	while (n_chr && f->search_chr[n_chr - 1] == ' ') n_chr--;
	f->nsearch_chr = n_chr;
	f->nsearch_pos = n_pos;
	if (sort_srch(f)) {
		mem_free(f->search_pos);
		f->search_pos = NULL;
		mem_free(f->search_chr);
		f->search_chr = NULL;
		f->nsearch_chr = f->nsearch_pos = 0;
		return -1;
	}
	return 0;
}
