/*
 * lws abstract display
 *
 * Copyright (C) 2019 - 2022 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Display List LHP layout
 *
 * The basic flow is logical elements exist in a stack as they are parsed, the
 * job of lhp_displaylist_layout() is to translate these into a tree of DLOs,
 * having parent-child relationships with (x,y) of the DLO box being an offset
 * into a local origin formed from the DLO parent box (which in turn may be
 * a child with its origin defined by its parent, etc).
 *
 * The element stack only exists while it and its parent elements are being
 * parsed, it goes out of scope as the element ends.  So we must create related
 * DLOs by stream-parsing, while we still have everything relevant to hand.
 *
 * This gets us out of having to run around fixing up DLO (x,y) as we do the
 * layout, since the DLO parent-child relationships are static even if their
 * content size isn't.
 *
 *
 */

#include <private-lib-core.h>
#include "private-lib-drivers-display-dlo.h"

/*
 * HTML Elements we can deal with for layout
 */

enum {
	/* 0 is no match */
	LHP_ELEM_BR = 1,
	LHP_ELEM_DIV,
	LHP_ELEM_TABLE,
	LHP_ELEM_TR,
	LHP_ELEM_TD,
	LHP_ELEM_IMG,
};

static const struct {
	const char	*elem;
	uint8_t		elem_len;
} elems[] = {
	{ "br",		2 },
	{ "div",	3 },
	{ "table",	5 },
	{ "tr",		2 },
	{ "td",		2 },
	{ "img",	3 },
};

/*
 * Newline moves the psb->cury to cover text that was already placed using the
 * old psb->cury as to top of it.  So a final newline on the last line of text
 * does not create an extra blank line.
 */

static const lws_fx_t two = { 2,0 };

static void
newline(lhp_ctx_t *ctx, lhp_pstack_t *psb, lhp_pstack_t *ps,
	lws_displaylist_t *dl)
{
	int16_t group_baseline = 9999, group_height = 0;
	lws_fx_t line_height = { 0, 0 }, w, add, ew, t1;
	const struct lcsp_atr *a;
	lws_dlo_t *dlo, *d, *d1;
	int t = 0;

	if (!psb || !ps) {
		lwsl_err("%s: psb/ps NULL!\n", __func__);
		return;
	}

	dlo = (lws_dlo_t *)psb->dlo;

	lws_fx_add(&w, lws_csp_px(ps->css_padding[CCPAS_LEFT], ps),
		       lws_csp_px(ps->css_padding[CCPAS_RIGHT], ps));

	if (lws_fx_comp(&w, &psb->widest) > 0)
		psb->widest = w;

	if (!dlo || !dlo->children.tail)
		return;

	d = lws_container_of(dlo->children.tail, lws_dlo_t, list);

	/*
	 * We may be at the end of a line of text
	 *
	 * Figure out the biggest height on the line, and the total width
	 */

	while (d) {
		t |= d->_destroy == lws_display_dlo_text_destroy;
		/* find the "worst" height on the line */
		if (lws_fx_comp(&d->box.h, &line_height) > 0)
			line_height = d->box.h;

		if (d->_destroy == lws_display_dlo_text_destroy) {
			lws_dlo_text_t *text = lws_container_of(d,
						lws_dlo_text_t, dlo.list);

			if (text->font_y_baseline < group_baseline)
				group_baseline = text->font_y_baseline;
			if (text->font_height > group_height)
				group_height = text->font_height;
		}

		if (!d->flag_runon)
			break;
		d = lws_container_of(d->list.prev, lws_dlo_t, list);
	};

	/* mark the related text dlos with information about group bl and h,
	 * offset box y to align to group baseline if necessary */

	d1 = d;
	while (d) {
		if (d->_destroy == lws_display_dlo_text_destroy) {
			lws_dlo_text_t *t1 = lws_container_of(d1,
						lws_dlo_text_t, dlo.list);
			lws_fx_t ft;

			t1->group_height = group_height;
			t1->group_y_baseline = group_baseline;

			ft.whole = (t1->font_height - t1->font_y_baseline) -
					(group_height - group_baseline);
			ft.frac = 0;

			lws_fx_sub(&t1->dlo.box.y,  &t1->dlo.box.y, &ft);
		}
		if (!d1->list.next)
			break;
		d1 = lws_container_of(d1->list.next, lws_dlo_t, list);
	};

	w = psb->curx;
	ew = ctx->ic.wh_px[0];
	if (psb->css_width && psb->css_width->unit != LCSP_UNIT_NONE)
		ew = *lws_csp_px(psb->css_width, psb);
	lws_fx_sub(&ew, &ew, lws_csp_px(ps->css_margin[CCPAS_RIGHT], ps));
	lws_fx_sub(&ew, &ew, lws_csp_px(ps->css_padding[CCPAS_RIGHT], ps));

	if (lws_fx_comp(&w, &psb->widest) > 0)
		psb->widest = w;

	if (!t) /* no textual children to newline (eg, <div></div>) */
		return;

	 /*
	  * now is our chance to fix up dlos that are part of the line for
	  * text-align rule of the container.
	  */

	a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_TEXT_ALIGN);
	if (a) {

		switch (a->propval) {
		case LCSP_PROPVAL_CENTER:
			add = *lws_csp_px(ps->css_padding[CCPAS_LEFT], ps);
			lws_fx_sub(&t1, &ew, &w);
			lws_fx_div(&t1, &t1, &two);
			lws_fx_add(&add, &add, &t1);
			goto fixup;
		case LCSP_PROPVAL_RIGHT:
			lws_fx_sub(&add, &ew, &w);
			lws_fx_sub(&add, &add, &d->box.x);

fixup:
			lws_fx_add(&t1, &add, &w);
			if (lws_fx_comp(&t1, &psb->widest) > 0)
				psb->widest = t1;

			do {
				lws_fx_add(&d->box.x, &d->box.x, &add);
				if (!d->list.next)
					break;
				d = lws_container_of(d->list.next, lws_dlo_t,
							list);
			} while (1);
			break;
		default:
			break;
		}
	}

	lws_fx_add(&psb->cury, &psb->cury, &line_height);
	lws_fx_set(psb->curx, 0, 0);
	psb->dlo_set_curx = NULL;
	psb->dlo_set_cury = NULL;
	psb->runon = 0;
}

void
lhp_set_dlo_padding_margin(lhp_pstack_t *ps, lws_dlo_t *dlo)
{
	int n;

	for (n = 0; n < 4; n ++) {
		if (ps->css_margin[n])
			dlo->margin[n] = *lws_csp_px(ps->css_margin[n], ps);
		else
			lws_fx_set(dlo->margin[n], 0, 0);
		if (ps->css_padding[n])
			dlo->padding[n] = *lws_csp_px(ps->css_padding[n], ps);
		else
			lws_fx_set(dlo->padding[n], 0, 0);
	}
}

void
lhp_set_dlo_adjust_to_contents(lhp_pstack_t *ps)
{
	lhp_pstack_t *psb = lws_container_of(ps->list.prev, lhp_pstack_t, list);
	lws_dlo_dim_t dim;

	lws_dlo_contents(ps->dlo, &dim);
	lws_display_dlo_adjust_dims(ps->dlo, &dim);

	if (lws_fx_comp(&dim.w, &psb->widest) > 0)
		psb->widest = dim.w;

	if (lws_fx_comp(&dim.h, &psb->deepest) > 0)
		psb->deepest = dim.h;
}

static void
runon(lhp_pstack_t *ps, lws_dlo_t *dlo)
{
	dlo->flag_runon = (uint8_t)(ps->runon & 1);
	ps->runon = 1;
}

/*
 * Handle end-of-div, table, tr, td retrospective dlo dimension adjustment
 */

int
lws_lhp_dlo_adjust_div_type_element(lhp_ctx_t *ctx, lhp_pstack_t *psb,
				    lhp_pstack_t *pst, lhp_pstack_t *ps,
				    int elem_match)
{
	lws_dlo_rect_t *rect = (lws_dlo_rect_t *)ps->dlo;
	lws_fx_t t1, w, wd;
	char rd = 0;

	/* need this to get bottom clearance for next block */

	lws_fx_add(&ps->cury, &ps->cury,
		lws_csp_px(ps->css_padding[CCPAS_BOTTOM], ps));

	if (psb && ps->dlo &&
	    ps->css_margin[CCPAS_LEFT]->propval == LCSP_PROPVAL_AUTO &&
	    ps->css_margin[CCPAS_RIGHT]->propval == LCSP_PROPVAL_AUTO) {
		lws_dlo_rect_t *re = (lws_dlo_rect_t *)ps->dlo;

		/* h-center a div... find the available h space first */
		w = ctx->ic.wh_px[LWS_LHPREF_WIDTH];
		if (psb->css_width &&
		    psb->css_width->propval != LCSP_PROPVAL_AUTO)
			w = *lws_csp_px(psb->css_width, psb);

		lws_fx_sub(&t1, &w, &re->dlo.box.w);
		lws_fx_div(&t1, &t1, &two);
		lws_fx_sub(&wd, &t1, &re->dlo.box.x);

		lws_fx_add(&re->dlo.box.x, &re->dlo.box.x, &wd);
	}

	/* fix up the dimensions of div rectangle */
	if (!rect) {
		lwsl_notice("%s: elem %d: NO RECT\n", __func__, elem_match);
		return 1;
	}

	lhp_set_dlo_adjust_to_contents(ps);

	/* if a td, deal with columnar changes in width */

	if (ps->dlo->col_list.owner) {
		lhp_table_col_t *tc = lws_container_of(
				ps->dlo->col_list.owner,
				lhp_table_col_t, col_dlos);
		lws_fx_t wdelta, ow;

		ow = tc->width;
		lws_fx_set(tc->width, 0, 0);

		/* discover the new width of column */

		lws_start_foreach_dll(struct lws_dll2 *, c1,
				      lws_dll2_get_head(&tc->col_dlos)) {
			lws_dlo_t *dloc = lws_container_of(c1,
					lws_dlo_t, col_list);

			if (lws_fx_comp(&dloc->box.w, &tc->width) > 0)
				tc->width = dloc->box.w;
		} lws_end_foreach_dll(c1);

		/* new width - old column width */
		lws_fx_sub(&wdelta, &tc->width, &ow);

		/*
		 * Update all dlos in our column (except
		 * ourselves) with the increased column width
		 */

		lws_start_foreach_dll(struct lws_dll2 *, cold,
				      lws_dll2_get_head(&tc->col_dlos)) {
			lws_dlo_t *dloc = lws_container_of(cold,
					lws_dlo_t, col_list);

			if (dloc != &rect->dlo)
				/* we already did this for the
				 * affected dlo */
				lws_fx_add(&dloc->box.w,
					   &dloc->box.w, &wdelta);

			rd = 1;

			/* ... and then all of their row-mates
			 * to the right also need their
			 * x adjusting then */

			while (dloc->row_list.next) {
				dloc = lws_container_of(
					dloc->row_list.next,
					lws_dlo_t, row_list);

				lws_fx_add(&dloc->box.x,
					   &dloc->box.x, &wdelta);
			}
		} lws_end_foreach_dll(cold);
	}

	/* if a td, deal with row changes in height */

	if (ps->dlo->row_list.owner) {
		lhp_table_row_t *tr = lws_container_of(
				ps->dlo->row_list.owner,
				lhp_table_row_t, row_dlos);
		lws_fx_t hdelta, oh;

		oh = tr->height;
		lws_fx_set(tr->height, 0, 0);

		/* discover the new width of column */

		lws_start_foreach_dll(struct lws_dll2 *, r1,
				      lws_dll2_get_head(&tr->row_dlos)) {
			lws_dlo_t *dlor = lws_container_of(r1,
					lws_dlo_t, row_list);

			if (lws_fx_comp(&dlor->box.h, &tr->height) > 0)
				tr->height = dlor->box.h;
		} lws_end_foreach_dll(r1);

		/* new height - old row height */
		lws_fx_sub(&hdelta, &tr->height, &oh);

		/*
		 * Update all dlos in our row (except
		 * ourselves) with the increased row height
		 */

		lws_start_foreach_dll(struct lws_dll2 *, rold,
				      lws_dll2_get_head(&tr->row_dlos)) {
			lws_dlo_t *dlor = lws_container_of(rold,
					lws_dlo_t, row_list);

			if (dlor != &rect->dlo)
				/* we already did this for the
				 * affected dlo */
				lws_fx_add(&dlor->box.h,
					   &dlor->box.h, &hdelta);

			/* ... so all of their col-mates below
			 * also need their y adjusting then */

			while (dlor->col_list.next) {
				dlor = lws_container_of(
					dlor->col_list.next,
					lws_dlo_t, col_list);

				lws_fx_add(&dlor->box.y,
					   &dlor->box.y, &hdelta);
			}

			rd = 1;

		} lws_end_foreach_dll(rold);
	}

	/*
	 * Row dimensions have to be reassessed?
	 */

	if (rd) {
		lws_start_foreach_dll(struct lws_dll2 *, ro,
		       lws_dll2_get_head(&pst->dlo->children)) {
			lws_dlo_t *dlo = lws_container_of(ro, lws_dlo_t, list);
			lws_dlo_dim_t dim;

			lws_dlo_contents(dlo, &dim);
			lws_display_dlo_adjust_dims(dlo, &dim);
		} lws_end_foreach_dll(ro);
	}

	if (psb && ps->css_position->propval != LCSP_PROPVAL_ABSOLUTE) {
		/* parent should account for our margin */
		if (elem_match == LHP_ELEM_DIV) {
			lws_fx_add(&psb->curx, &psb->curx, &ps->widest);
			/* now we applied ps->widest, reset it */
			lws_fx_set(ps->widest, 0, 0);
			psb->dlo_set_curx = ps->dlo;
		} else {
			/* needed for margin between table cells */
			lws_fx_add(&psb->curx, &psb->curx, lws_csp_px(ps->css_margin[CCPAS_LEFT], ps));
			lws_fx_add(&psb->curx, &psb->curx, lws_csp_px(ps->css_margin[CCPAS_RIGHT], ps));
		}

		if (elem_match != LHP_ELEM_TD) {
			if (ps->css_display->propval != LCSP_PROPVAL_INLINE_BLOCK) {
				lws_fx_add(&psb->cury, &psb->cury, &ps->dlo->box.h);
				psb->dlo_set_cury = ps->dlo;
			}
		//	lws_fx_add(&psb->cury, &psb->cury, &ps->dlo->margin[CCPAS_BOTTOM]);
		} else
			ps->widest = ps->dlo->box.w;
	}

	return 0;
}

/*
 * Generic LHP displaylist object layout callback... converts html elements
 * into DLOs on the display list
 */

lws_stateful_ret_t
lhp_displaylist_layout(lhp_ctx_t *ctx, char reason)
{
	lhp_pstack_t *psb = NULL, *pst = NULL, *psp = NULL,
		     *ps = lws_container_of(ctx->stack.tail, lhp_pstack_t, list);
	struct lws_context *cx = (struct lws_context *)ctx->user1;
	lws_dl_rend_t *drt = (lws_dl_rend_t *)ctx->user;
	lws_fx_t br[4], t1, indent, ox, w, h;
	const lws_display_font_t *f = NULL;
	lhp_table_col_t *tcol = NULL;
	lhp_table_row_t *trow = NULL;
	lws_dlo_t *abut_x, *abut_y;
	uint32_t col = 0xff000000;
	lws_dlo_text_t *txt;
	const lcsp_atr_t *a;
	lws_dlo_image_t u;
	const char *pname;
	char lastm = 0;
	int elem_match;
	lws_box_t box;
	char url[128];
	int n, s = 0;

	/* default font choice */
	lws_font_choice_t fc = {
		.family_name		= "term, serif",
		.fixed_height		= 16,
		.weight			= 400,
	};

	if (!ps->font) {
		a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_SIZE);
		if (a)
			fc.fixed_height = (uint16_t)a->u.i.whole;

		a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_FAMILY);
		if (a)
			fc.family_name = (const char *)&a[1];

		a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_WEIGHT);
		if (a) {
			switch (a->propval) {
			case LCSP_PROPVAL_BOLD:
				fc.weight = 700;
				break;
			case LCSP_PROPVAL_BOLDER:
				fc.weight = 800;
				break;
			default:
				if (a->u.i.whole)
					fc.weight = (uint16_t)a->u.i.whole;
				break;
			}
		}

		ps->font = lws_font_choose(cx, &fc);
	}
	f = ps->font;

	psb = lws_css_get_parent_block(ctx, ps);

	elem_match = 0;
	for (n = 0; n < (int)LWS_ARRAY_SIZE(elems); n++)
		if (ctx->npos == elems[n].elem_len &&
		    !memcmp(ctx->buf, elems[n].elem, elems[n].elem_len))
			elem_match = n + 1;

	switch (reason) {
	case LHPCB_CONSTRUCTED:
	case LHPCB_DESTRUCTED:
	case LHPCB_COMPLETE:
	case LHPCB_FAILED:
		break;

	case LHPCB_ELEMENT_START:

		switch (elem_match) {
		case LHP_ELEM_BR:
			newline(ctx, psb, ps, drt->dl);
			break;

		case LHP_ELEM_TR:
			if (!psb)
				break;

			pst = ps;
			while (pst && !pst->is_table)
				pst = lws_css_get_parent_block(ctx, pst);
			if (!pst) {
				lwsl_err("%s: td: no table found\n", __func__);
				break;
			}

			pst->curx.whole = 0;
			pst->curx.frac = 0;
			psb->dlo_set_curx = NULL;

			trow = lws_zalloc(sizeof(*trow), __func__);
			if (!trow) {
				lwsl_err("%s: OOM\n", __func__);
				return LWS_SRET_FATAL;
			}
			lws_dll2_add_tail(&trow->list, &pst->dlo->table_rows);
			trow = NULL;
			pst->td_idx = 0;

			goto do_rect;

		case LHP_ELEM_TD:
			if (!psb) {
				lwsl_err("%s: td: no psb found\n", __func__);
				break;
			}

			pst = ps;
			while (pst && !pst->is_table)
				pst = lws_css_get_parent_block(ctx, pst);
			if (!pst) {
				lwsl_err("%s: td: no table found\n", __func__);
				break;
			}

			if (pst->td_idx >= (int)pst->dlo->table_cols.count) {
				tcol = lws_zalloc(sizeof(*tcol), __func__);
				if (!tcol) {
					lwsl_err("%s: OOM\n", __func__);
					return LWS_SRET_FATAL;
				}
				lws_dll2_add_tail(&tcol->list, &pst->dlo->table_cols);
			} else {
				tcol = lws_container_of(pst->dlo->table_cols.head, lhp_table_col_t, list);
				n = pst->td_idx;
				while (n--)
					tcol = lws_container_of(tcol->list.next, lhp_table_col_t, list);
			}

			if (pst->dlo->table_rows.tail)
				trow = lws_container_of(pst->dlo->table_rows.tail, lhp_table_row_t, list);

			goto do_rect;

		case LHP_ELEM_TABLE:
			ps->is_table = 1;
			/* fallthru */
		case LHP_ELEM_DIV:

do_rect:
			lws_fx_set(box.x, 0, 0);
			lws_fx_set(box.y, 0, 0);
			lws_fx_set(box.h, 0, 0);
			lws_fx_set(box.w, 0, 0);
			abut_x = NULL;
			abut_y = NULL;

			if (ps->css_position->propval == LCSP_PROPVAL_ABSOLUTE) {
				box.x = *lws_csp_px(ps->css_pos[CCPAS_LEFT], ps);
				box.y = *lws_csp_px(ps->css_pos[CCPAS_TOP], ps);
			} else {
				if (psb) {

						/* margin adjusts our child box origin */
					lws_fx_add(&box.x, &psb->curx,
							lws_csp_px(ps->css_margin[CCPAS_LEFT], ps));
					box.y = psb->cury;
					abut_x = psb->dlo_set_curx;
					abut_y = psb->dlo_set_cury;
					//lws_fx_add(&box.y, &psb->cury,
					//	   lws_csp_px(ps->css_margin[CCPAS_TOP], ps));
				}
			}

			/* If there's an explicit width, try to go with that */

			if (ps->css_width &&
			    ps->css_width->unit != LCSP_UNIT_NONE &&
			    lws_fx_comp(lws_csp_px(ps->css_width, ps), &box.w) < 0)
				box.w = *lws_csp_px(ps->css_width, ps);

			/* !!! we rely on this being nonzero to not infinite loop at text layout */

			lws_fx_add(&box.w, &box.w,
			   lws_csp_px(ps->css_padding[CCPAS_LEFT], ps));
			lws_fx_add(&box.w, &box.w,
			   lws_csp_px(ps->css_padding[CCPAS_RIGHT], ps));

			ps->drt.w = box.w;
			ps->curx = *lws_csp_px(ps->css_padding[CCPAS_LEFT], ps);
			ps->cury = *lws_csp_px(ps->css_padding[CCPAS_TOP], ps);

			memset(br, 0, sizeof(br));

			if (ps->css_border_radius[0])
				br[0] = *lws_csp_px(ps->css_border_radius[0], ps);
			if (ps->css_border_radius[1])
				br[1] = *lws_csp_px(ps->css_border_radius[1], ps);
			if (ps->css_border_radius[2])
				br[2] = *lws_csp_px(ps->css_border_radius[2], ps);
			if (ps->css_border_radius[3])
				br[3] = *lws_csp_px(ps->css_border_radius[3], ps);

			psp = lws_container_of(ps->list.prev, lhp_pstack_t, list);

			ps->dlo = (lws_dlo_t *)lws_display_dlo_rect_new(drt->dl,
					ps->css_position->propval == LCSP_PROPVAL_ABSOLUTE ? NULL : psp->dlo,
					&box, br, ps->css_background_color ?
					  ps->css_background_color->u.rgba : 0);
			if (!ps->dlo) {
				lwsl_err("%s: FAILED to create rect\n", __func__);
				return LWS_SRET_FATAL;
			}

			ps->dlo->abut_x = abut_x;
			ps->dlo->abut_y = abut_y;

			if (psb)
				lws_fx_add(&psb->curx, &psb->curx,
					   lws_csp_px(ps->css_margin[CCPAS_RIGHT], ps));

			if (tcol)
				lws_dll2_add_tail(&ps->dlo->col_list, &tcol->col_dlos);
			if (trow)
				lws_dll2_add_tail(&ps->dlo->row_list, &trow->row_dlos);

			lws_lhp_tag_dlo_id(ctx, ps, ps->dlo);
			lhp_set_dlo_padding_margin(ps, ps->dlo);
			break;

		case LHP_ELEM_IMG:
			pname = lws_html_get_atr(ps, "src", 3);

			if (!psb)
				break;

			if (ps->css_position->propval == LCSP_PROPVAL_ABSOLUTE) {
				box.x = *lws_csp_px(ps->css_pos[CCPAS_LEFT], ps);
				box.y = *lws_csp_px(ps->css_pos[CCPAS_TOP], ps);
			} else {
				box.x = psb->curx;
				box.y = psb->cury;
			}

			lws_fx_set(box.x, 0, 0);
			lws_fx_set(box.y, 0, 0);

			if (psb) {
				lws_fx_add(&box.x, &box.x,
					lws_csp_px(psb->css_margin[CCPAS_LEFT], psb));
				lws_fx_add(&box.y, &box.y,
					lws_csp_px(psb->css_margin[CCPAS_TOP], psb));
			}

			box.h = ctx->ic.wh_px[1]; /* placeholder */
			lws_fx_sub(&box.w, &ctx->ic.wh_px[0], &box.x);

			if (ps->css_width &&
			    lws_fx_comp(lws_csp_px(ps->css_width, ps), &box.w) > 0)
				box.w = *lws_csp_px(ps->css_width, ps);

			if (lws_http_rel_to_url(url, sizeof(url),
						ctx->base_url, pname))
				break;

			if (lws_dlo_ss_find(cx, url, &u))
				break;

			lws_lhp_tag_dlo_id(ctx, ps, (lws_dlo_t *)(u.u.dlo_jpeg));

			w = *lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_WIDTH), ps);
			h = *lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_HEIGHT), ps);

			if (!w.whole || !h.whole) {
				w = ((lws_dlo_t *)(u.u.dlo_jpeg))->box.w;
				h = ((lws_dlo_t *)(u.u.dlo_jpeg))->box.w;
			}

			if (psb) {
				lws_fx_add(&psb->curx, &psb->curx, &w);
				lws_fx_add(&psb->cury, &psb->cury, &h);
				psb->dlo_set_curx = ps->dlo;
				psb->dlo_set_cury = ps->dlo;
				if (lws_fx_comp(&psb->curx, &psb->widest) > 0)
					psb->widest = psb->curx;
			}

			break;
		default:
			break;
		}
		break;

	case LHPCB_ELEMENT_END:

		if (ctx->npos == 2 && ctx->buf[0] == 'h' &&
		    ctx->buf[1] > '0' && ctx->buf[1] <= '6') {

			if (!psb)
				break;

			newline(ctx, psb, ps, drt->dl);
			lws_fx_add(&psb->cury, &psb->cury,
				lws_csp_px(ps->css_padding[CCPAS_BOTTOM], ps));
			lws_fx_add(&psb->cury, &psb->cury,
				lws_csp_px(ps->css_margin[CCPAS_BOTTOM], ps));
			break;
		}

		switch (elem_match) {

		case LHP_ELEM_TR:
			pst = ps;
			while (pst && !pst->is_table)
				pst = lws_css_get_parent_block(ctx, pst);
			if (!pst) {
				lwsl_err("%s:  /td: no table\n", __func__);
				break;
			}

			pst->tr_idx++;
			pst->td_idx = 0;
			goto do_end_rect;

		case LHP_ELEM_TD:
			pst = ps;
			while (pst && !pst->is_table)
				pst = lws_css_get_parent_block(ctx, pst);
			if (!pst) {
				lwsl_err("%s:  /td: no table\n", __func__);
				break;
			}
			pst->td_idx++;
			goto do_end_rect;


			/* fallthru */

		case LHP_ELEM_TABLE:
		case LHP_ELEM_DIV:
do_end_rect:
			ox = ps->curx;

			if (lws_fx_comp(&ox, &ps->widest) > 0)
				ps->widest = ox;

			newline(ctx, ps, ps, drt->dl);

			if (lws_lhp_dlo_adjust_div_type_element(ctx, psb, pst, ps, elem_match))
				break;

			if (lws_fx_comp(&ps->curx, &ps->widest) > 0)
				ps->widest = ps->curx;

			/* move parent on according to used area plus bottom margin */

			if (psb && ps->css_position->propval != LCSP_PROPVAL_ABSOLUTE) {

				switch (ps->css_display->propval) {
				case LCSP_PROPVAL_BLOCK:
				case LCSP_PROPVAL_TABLE:
				case LCSP_PROPVAL_TABLE_ROW:
					lws_fx_set(psb->curx, 0, 0);
					psb->dlo_set_curx = NULL;

					if (ps->css_display->propval == LCSP_PROPVAL_TABLE_ROW)
						break;
					lws_fx_add(&psb->cury, &psb->cury, lws_csp_px(ps->css_margin[CCPAS_BOTTOM], ps));
					break;

				case LCSP_PROPVAL_INLINE_BLOCK:
					//lws_fx_add(&psb->cury, &psb->cury, lws_csp_px(ps->css_margin[CCPAS_BOTTOM], ps));
					lws_fx_add(&psb->curx, &psb->curx, &ps->widest);
					lws_fx_add(&psb->curx, &psb->curx, lws_csp_px(ps->css_margin[CCPAS_RIGHT], ps));
					lws_fx_set(ps->widest, 0, 0);
					psb->dlo_set_curx = ps->dlo;
					psb->dlo_set_cury = ps->dlo;
					break;

				default:
					lws_fx_add(&psb->curx, &psb->curx, &ps->widest);
					psb->dlo_set_curx = ps->dlo;
					break;
				}

				if (lws_fx_comp(&psb->curx, &psb->widest) > 0)
					psb->widest = psb->curx;
			}

			ps->dlo = NULL;
			break;
		default:
			break;
		}
		break;

	case LHPCB_CONTENT:

		if (!ps->css_display ||
		    ps->css_display->propval == LCSP_PROPVAL_NONE)
			break;

		if (ps->css_color)
			col = ps->css_color->u.rgba;

		a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_SIZE);
		if (a)
			fc.fixed_height = (uint16_t)a->u.i.whole;

		a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_FAMILY);
		if (a)
			fc.family_name = (const char *)&a[1];

		for (n = 0; n < ctx->npos; n++)
			if (ctx->buf[n] == '\n')
				s++;

		if (s == ctx->npos)
			return 0;

		/*
		 * Let's not deal with things off the bottom of the display
		 * surface.
		 */

		if (psb && psb->cury.whole > ctx->ic.wh_px[LWS_LHPREF_HEIGHT].whole)
			return 0;

		if (!psb)
			return 0;

		f = lws_font_choose(cx, &fc);

		n = s;
		while (n < ctx->npos) {
			int m;

			lws_fx_set(box.x, 0, 0);
			lws_fx_set(box.y, 0, 0);
			lws_fx_set(box.w, 0, 0);

			if (n == s && !(psb->runon & 1)) {
				lws_fx_set(indent, 0, 0);
			} else
				indent = psb->curx;
			lws_fx_add(&box.x, &indent,
					  lws_csp_px(ps->css_padding[CCPAS_LEFT], ps));
			lws_fx_add(&box.y, &box.y, &psb->cury);

			box.h.whole = (int32_t)f->choice.fixed_height;
			box.h.frac = 0;

			if (psb->css_width &&
				(psb->css_width->propval == LCSP_PROPVAL_AUTO ||
				 ps->css_width->propval == LCSP_PROPVAL_AUTO)) {
				//lws_fx_sub(&box.w, &ctx->ic.wh_px[0], &box.x);
				box.w = ctx->ic.wh_px[0];
			} else {
				lws_fx_sub(&t1, &psb->drt.w,
					   lws_csp_px(psb->css_padding[CCPAS_LEFT], psb));
				lws_fx_sub(&box.w, &t1,
					   lws_csp_px(psb->css_padding[CCPAS_RIGHT], psb));
			}

			if (!box.w.whole)
				lws_fx_sub(&box.w, &ctx->ic.wh_px[0], &box.x);
			assert(psb);

			txt = lws_display_dlo_text_new(drt->dl,
					(lws_dlo_t *)psb->dlo, &box, f);
			if (!txt) {
				lwsl_err("%s: failed to alloc text\n", __func__);
				return 1;
			}
			runon(psb, &txt->dlo);
			txt->flags |= LWSDLO_TEXT_FLAG_WRAP;

			lhp_set_dlo_padding_margin(ps, &txt->dlo);

//			a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_TEXT_ALIGN);

			//lwsl_hexdump_notice(ctx->buf + n, (size_t)(ctx->npos - n));
			m = lws_display_dlo_text_update(txt, col, indent,
							ctx->buf + n,
							(size_t)(ctx->npos - n));
			if (m < 0) {
				lwsl_err("text_update ret %d\n", m);
				break;
			}

			if (m == 2 && lastm)
				return 0;

			lastm = m == 2;

			n = (int)((size_t)n + txt->text_len);
			txt->dlo.box.w = txt->bounding_box.w;
			txt->dlo.box.h = txt->bounding_box.h;

			lws_fx_add(&psb->curx, &psb->curx, &txt->bounding_box.w);
			psb->dlo_set_curx = &txt->dlo;

			//lwsl_user("%s: bounding width %d, m: %d, text %.*s\n",
			//	  __func__, txt->bounding_box.w.whole, m,
			//	  ctx->npos, ctx->buf);

			if (m > 0) { /* wrapping */
				newline(ctx, psb, ps, drt->dl);
				lws_fx_set(ps->curx, 0, 0);
				lws_fx_set(psb->curx, 0, 0);
				psb->dlo_set_curx = NULL;
				lws_fx_add(&ps->cury, &ps->cury, &txt->bounding_box.h);
				psb->dlo_set_cury = &txt->dlo;
			}
		}
		break;
	case LHPCB_COMMENT:
		break;
	}

	return 0;
}
