/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 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.
 *
 * Stream parser for HTML 5
 * https://w3c.github.io/html-reference/syntax.html
 *
 */

#include <private-lib-core.h>

#define FAIL_CHAR 0x08
static uint8_t css_lextable[] = { /* the css property names */
	#include "css-lextable.h"
};

static uint8_t css_propconst_lextable[] = { /* the css property values */
	#include "css-propconst-lextable.h"
};

#define LHP_AC_GRANULE 512

enum {
	/* html */

	LHPS_INIT, /* default css injection */

	LHPS_OUTER,
	LHPS_TAG,
	LHPS_BAD_TAG,
	LHPS_DO_START_ELEM,
	LHPS_ATTRIB,
	LHPS_ATTRIB_VAL,
	LHPS_AMP,
	LHPS_AMPHASH,
	LHPS_AMPHASH_HEX,
	LHPS_SCOMMENT1,
	LHPS_SCOMMENT2,
	LHPS_COMMENT,
	LHPS_ECOMMENT1,
	LHPS_ECOMMENT2,

	/* css */

	LCSPS_CSS_OUTER,
	LCSPS_CCOM_S1,
	LCSPS_CCOM_E1,
	LCSPS_CCOM,
	LCSPS_CSS_OUTER_TAG1,
	LCSPS_CSS_NAMES,
	LCSPS_CSS_DEF_NAME,
	LCSPS_CSS_DEF_VALUE,
	LCSPS_SCOMMENT1,
	LCSPS_SCOMMENT2,
	LCSPS_COMMENT,
	LCSPS_ECOMMENT1,
	LCSPS_ECOMMENT2,

	LCSPS_CSS_STANZA,

	/* script */

	LHPS_SCRIPT,
	LHPS_SCRIPT_TAG1,
};

/*
 * 17 well-known colours specified by CSS 2.1
 * https://www.w3.org/TR/CSS21/syndata.html#value-def-color
 */

#if 0
static struct cols {
	const char * const	name;
	uint32_t		rgba;
} cols[] = {
	{ "maroon", 	LWSDC_RGBA(0x80, 0x00, 0x00, 255) },
	{ "red", 	LWSDC_RGBA(0xff, 0x00, 0x00, 255) },
	{ "orange", 	LWSDC_RGBA(0xff, 0xa5, 0x00, 255) },
	{ "yellow", 	LWSDC_RGBA(0xff, 0xff, 0x00, 255) },
	{ "olive", 	LWSDC_RGBA(0x80, 0x80, 0x00, 255) },
	{ "purple", 	LWSDC_RGBA(0x80, 0x00, 0x80, 255) },
	{ "fuchsia", 	LWSDC_RGBA(0xff, 0x00, 0xff, 255) },
	{ "white", 	LWSDC_RGBA(0xff, 0xff, 0xff, 255) },
	{ "lime", 	LWSDC_RGBA(0x00, 0xff, 0x00, 255) },
	{ "green", 	LWSDC_RGBA(0x00, 0x80, 0x00, 255) },
	{ "navy", 	LWSDC_RGBA(0x00, 0x00, 0x80, 255) },
	{ "blue", 	LWSDC_RGBA(0x00, 0x00, 0xff, 255) },
	{ "aqua", 	LWSDC_RGBA(0x00, 0xff, 0xff, 255) },
	{ "teal", 	LWSDC_RGBA(0x00, 0x80, 0x80, 255) },
	{ "black", 	LWSDC_RGBA(0x00, 0x00, 0x00, 255) },
	{ "silver", 	LWSDC_RGBA(0xc0, 0xc0, 0xc0, 255) },
	{ "gray", 	LWSDC_RGBA(0x80, 0x80, 0x80, 255) },
};
#endif

/*
 * "void elements" are html elements that don't have a scope, and so don't
 * have a scope closure
 */
static const char * const void_elems[] = {
	"area", "base", "br", "col", "command", "embed", "hr", "img",
	"input", "keygen", "link", "meta", "param", "source", "track", "wbr"
};
static const uint8_t void_elems_lens[] = /* lengths for the table above */
	{ 4, 4, 2, 3, 7, 5, 2, 3, 5, 6, 4, 4, 5, 6, 5, 3 };

static const struct {
	const char *name;
	uint32_t val;
} entities[] = {
	{ "quot", 34 },
	{ "amp", 38 },
	{ "apos", 39 },
	{ "lt", 60 },
	{ "gt", 62 },
	{ "nbsp", 160 },
	{ "copy", 169 },
	{ "reg", 174 },
	{ "deg", 176 },
	{ "plusmn", 177 },
	{ "sup2", 178 },
	{ "sup3", 179 },
	{ "micro", 181 },
	{ "para", 182 },
	{ "middot", 183 },
	{ "cedil", 184 },
	{ "sup1", 185 },
	{ "ordm", 186 },
	{ "raquo", 187 },
	{ "frac14", 188 },
	{ "frac12", 189 },
	{ "frac34", 190 },
	{ "iquest", 191 },
	{ "times", 215 },
	{ "divide", 247 },
	{ "mdash", 8212 },
	{ "ndash", 8211 },
	{ "bull", 8226 },
	{ "hellip", 8230 },
	{ "prime", 8242 },
	{ "Prime", 8243 },
	{ "oline", 8254 },
	{ "frasl", 8260 },
	{ "weierp", 8472 },
	{ "image", 8465 },
	{ "real", 8476 },
	{ "trade", 8482 },
	{ "alefsym", 8501 },
	{ "larr", 8592 },
	{ "uarr", 8593 },
	{ "rarr", 8594 },
	{ "darr", 8595 },
	{ "harr", 8596 },
	{ "crarr", 8629 },
	{ "lArr", 8656 },
	{ "uArr", 8657 },
	{ "rArr", 8658 },
	{ "dArr", 8659 },
	{ "hArr", 8660 },
	{ "forall", 8704 },
	{ "part", 8706 },
	{ "exist", 8707 },
	{ "empty", 8709 },
	{ "nabla", 8711 },
	{ "isin", 8712 },
	{ "notin", 8713 },
	{ "ni", 8715 },
	{ "prod", 8719 },
	{ "sum", 8721 },
	{ "minus", 8722 },
	{ "lowast", 8727 },
	{ "radic", 8730 },
	{ "prop", 8733 },
	{ "infin", 8734 },
	{ "ang", 8736 },
	{ "and", 8743 },
	{ "or", 8744 },
	{ "cap", 8745 },
	{ "cup", 8746 },
	{ "int", 8747 },
	{ "there4", 8756 },
	{ "sim", 8764 },
	{ "cong", 8773 },
	{ "asymp", 8776 },
	{ "ne", 8800 },
	{ "equiv", 8801 },
	{ "le", 8804 },
	{ "ge", 8805 },
	{ "sub", 8834 },
	{ "sup", 8835 },
	{ "nsub", 8836 },
	{ "sube", 8838 },
	{ "supe", 8839 },
	{ "oplus", 8853 },
	{ "otimes", 8855 },
	{ "perp", 8869 },
	{ "sdot", 8901 },
	{ "lceil", 8968 },
	{ "rceil", 8969 },
	{ "lfloor", 8970 },
	{ "rfloor", 8971 },
	{ "lang", 9001 },
	{ "rang", 9002 },
	{ "loz", 9674 },
	{ "spades", 9824 },
	{ "clubs", 9827 },
	{ "hearts", 9829 },
	{ "diams", 9830 },
	{ "dash", 8212 },
};

static const char *const default_css =
	"/* lws_lhp default css */"
	"html, address,blockquote, dd, div,dl, dt, fieldset, form, frame, "
	"frameset, h1, h2, h3, h4, h5, h6, noframes, ol, p, ul, center, "
	"dir, hr, menu, pre { top: 0px; right: 0px; bottom: 0px; left: 0px;"
		" unicode-bidi: embed; color: #000;"
		"padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px;"
		"margin-top: 2px; margin-left: 2px; margin-bottom: 2px; margin-right: 2px;"
		"position: static; width: auto; height: auto;"
			    "}\n"
	"div             { display: block; width: auto; }\n"
	"body		 { display: block}\n"
	"li              { display: list-item }\n"
	"head, script, style { display: none }\n"
	"table           { display: table;  }\n"
	"tr              { display: table-row }\n"
	"thead           { display: table-header-group }\n"
	"tbody           { display: table-row-group }\n"
	"tfoot           { display: table-footer-group }\n"
	"col             { display: table-column }\n"
	"colgroup        { display: table-column-group }\n"
	"td, th          { display: table-cell }\n"
	"caption         { display: table-caption }\n"
	"th              { font-weight: bolder; text-align: center }\n"
	"caption         { text-align: center }\n"
	"body            { margin: 8px }\n"
	"h1              { font-size: 2em; margin: .67em 0 }\n"
	"h2              { font-size: 1.5em; margin: .75em 0 }\n"
	"h3              { font-size: 1.17em; margin: .83em 0 }\n"
	"h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu "
		"{ margin: 1.12em 0 }\n"
	"h5              { font-size: .83em; margin: 1.5em 0 }\n"
	"h6              { font-size: .75em; margin: 1.67em 0 }\n"
	"h1, h2, h3, h4, h5, h6, b, strong          { font-weight: bolder }\n"
	"blockquote      { margin-left: 40px; margin-right: 40px }\n"
	"i, cite, em, var, address    { font-style: italic }\n"
	" pre, tt, code, kbd, samp       { font-family: monospace }\n"
	"pre             { white-space: pre }\n"
	"button, textarea, input, select   { display: inline-block }\n"
	"big             { font-size: 1.17em }\n"
	"small, sub, sup { font-size: .83em }\n"
	"sub             { vertical-align: sub }\n"
	"sup             { vertical-align: super }\n"
	"table           { border-spacing: 2px; padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px }\n"
	"thead, tbody, tfoot           { vertical-align: middle }\n"
	"td, th, tr      { vertical-align: inherit; width: auto; padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px }\n"
	"s, strike, del  { text-decoration: line-through }\n"
	"hr              { border: 1px inset }\n"
	"ol, ul, dir, menu, dd        { margin-left: 40px }\n"
	"ol              { list-style-type: decimal }\n"
	"ol ul, ul ol, ul ul, ol ol    { margin-top: 0; margin-bottom: 0 }\n"
	"u, ins          { text-decoration: underline }\n"
	"br:before       { content: \"A\"; white-space: pre-line }\n"
	"center          { text-align: center }\n"
	"nav             { text-align: left }\n"
	"span, time, label, a, b, strong, i, em, code { display: inline }\n"
	":link, :visited { text-decoration: underline }\n"
	":focus          { outline: thin dotted invert }\n"

	"BDO[DIR=\"ltr\"]  { direction: ltr; unicode-bidi: bidi-override }"
	"BDO[DIR=\"rtl\"]  { direction: rtl; unicode-bidi: bidi-override }"

	"*[DIR=\"ltr\"]    { direction: ltr; unicode-bidi: embed }"
	"*[DIR=\"rtl\"]    { direction: rtl; unicode-bidi: embed }"

	"@media print {"
	"  h1            { page-break-before: always }\n"
	"  h1, h2, h3, h4, h5, h6    { page-break-after: avoid }\n"
	"  ul, ol, dl    { page-break-before: avoid }\n"
	"}\n"
;



static int
lhp_clean_atr(lws_dll2_t *d, void *user)
{
	lhp_atr_t *atr = lws_container_of(d, lhp_atr_t, list);

	lws_dll2_remove(d);
	lws_free(atr);

	return 0;
}

static void
lhp_clean_level(lhp_pstack_t *ps)
{
	lws_dll2_foreach_safe(&ps->atr, NULL, lhp_clean_atr);
	lws_dll2_remove(&ps->list);

	lws_free(ps);
}

int
lws_lhp_construct(lhp_ctx_t *ctx, lhp_callback cb, void *user,
		  const lws_surface_info_t *ic)
{
	lhp_pstack_t *ps = lws_zalloc(sizeof(*ps), __func__);

	if (!ps)
		return 1;

	memset(ctx, 0, sizeof(*ctx) - sizeof(ctx->buf));
	ctx->user		= user;
	ctx->ic			= *ic;

	/*
	 * these are done implicitly by the memset above
	 * ctx->state			= LHPS_INIT;
	 * ctx->sp			= 0;
	 */

	ps->cb			= cb;
	lws_dll2_add_tail(&ps->list, &ctx->stack);

	return 0;
}

static int
lhp_clean_stack(lws_dll2_t *d, void *user)
{
	lhp_pstack_t *ps = lws_container_of(d, lhp_pstack_t, list);

	lhp_clean_level(ps);
	return 0;
}

static const lws_fx_t c_254= { 2,54000000 }, c_10 = { 10,0 }, c_0 = { 0, 0 },
			     c_72 = { 72,0 }, c_6 = { 6,0 }, c_100 = { 100,0 };

/*
 * We need to go backward until we reach an absolute length for the reference
 * axis, then base off that and go forward applying relative operations (like %)
 * on it in order.
 */

static int
lws_css_compute_cascaded_length(lhp_ctx_t *ctx, int ref, lhp_pstack_t *ps,
				lws_fx_t *t1)
{
	lhp_pstack_t *psb = ps, *psmap[20];
	const struct lcsp_atr *atrmap[20];
	lws_fx_t t2;
	int amp = 0;

	do {
		const struct lcsp_atr *a;

		psb = lws_css_get_parent_block(ctx, psb);
		if (!psb)
			break;

		a = (ref == LWS_LHPREF_WIDTH) ? psb->css_width : psb->css_height;
		if (!a)
			/* skip levels that don't change it */
			continue;

		if (amp + 1 == LWS_ARRAY_SIZE(atrmap))
			/* uhh... */
			break;

		psmap[amp] = psb;
		atrmap[amp++] = a;

		if (a->unit == LCSP_UNIT_LENGTH_PERCENT ||
		    a->unit == LCSP_UNIT_ANGLE_REL_DEG ||
		    a->unit == LCSP_UNIT_NONE)
			/* need earlier info to compute... keep going back */
			continue;

		break;
	} while (1);

	/*
	 * We have the path back through the elements to the first
	 * absolute one
	 */

	while (amp-- > 0) {
		if (atrmap[amp]->unit != LCSP_UNIT_LENGTH_PERCENT) {
			*t1 = *lws_csp_px(atrmap[amp], psmap[amp]);
		} else
			if (amp)
				lws_fx_div(t1,
					lws_fx_mul(&t2, &atrmap[amp]->u.i, t1),
									&c_100);
	}

	return 0;
}

static void
lhp_fx_parse(lws_fx_t *fx, const char *str, size_t len)
{
	const char *dot = NULL;
	int i;

	for (i = 0; i < (int)len; i++)
		if (str[i] == '.') {
			dot = &str[i];
			break;
		}

	if (!dot) {
		fx->whole = atoi(str);
		fx->frac = 0;
		return;
	}

	fx->whole = atoi(str);
	fx->frac = 0;

	dot++;
	len -= (size_t)(dot - str);
	i = 10000000;
	while (len-- && *dot) {
		fx->frac += ((*dot++) - '0') * i;
		i /= 10;
	}
}

const lws_fx_t *
lws_csp_px(const lcsp_atr_t *a, lhp_pstack_t *ps)
{
	lhp_ctx_t *ctx;
	const lws_display_font_t *f;
	lws_fx_t t1, t2, t3;
	int ref;

	assert(ps);

	if (!a)
		return NULL;

	ctx = lws_container_of(ps->list.owner, lhp_ctx_t, stack);
	f = ps->font;

       /*
        * We rely on f being non-null, but if it happens to be
        * NULL, let's return a constant 0
        */

       if (!f) {
               *(lws_fx_t *)&a->r = c_0;
               return &a->r;
       }

	ref = lhp_prop_axis(a);

	switch (a->unit) {
	case LCSP_UNIT_LENGTH_REM:
		if (!ctx->stack.head)
			break;
		return lws_fx_mul((lws_fx_t *)&a->r, &a->u.i,
			&((lhp_pstack_t *)lws_container_of(ctx->stack.head, lhp_pstack_t, list))->font->em);

	case LCSP_UNIT_CALC:
		{
			char buf[128], unit[8];
			const char *p = (const char *)&a[1];
			size_t len = a->value_len;
			lws_fx_t sum = { 0, 0 }, v;
			lcsp_atr_t atr;
			int op = 1;

			memset(&atr, 0, sizeof(atr));

			/* simplistic calc parser: A + B + C... */

			while (len) {
				size_t n = 0, m = 0;

				while (len && (*p == ' ' || *p == '\t' || *p == '\n')) {
					p++;
					len--;
				}

				if (len && (*p == '+' || *p == '-')) {
					op = *p++ == '+';
					len--;
					continue;
				}

				while (len && n < sizeof(buf) - 1 &&
				       ((*p >= '0' && *p <= '9') || *p == '.')) {
					buf[n++] = *p++;
					len--;
				}
				buf[n] = '\0';

				if (!n)
					break;

				lws_fx_set(atr.u.i, 0, 0);
				lhp_fx_parse(&atr.u.i, buf, n);

				while (len && m < sizeof(unit) - 1 &&
				       (*p >= 'a' && *p <= 'z')) {
					unit[m++] = *p++;
					len--;
				}
				unit[m] = '\0';

				if (len && *p == '%') {
					unit[0] = '%';
					unit[1] = '\0';
					p++;
					len--;
				}

				atr.unit = LCSP_UNIT_LENGTH_PX;
				if (!strcmp(unit, "em")) atr.unit = LCSP_UNIT_LENGTH_EM;
				if (!strcmp(unit, "ex")) atr.unit = LCSP_UNIT_LENGTH_EX;
				if (!strcmp(unit, "rem")) atr.unit = LCSP_UNIT_LENGTH_REM;
				if (!strcmp(unit, "in")) atr.unit = LCSP_UNIT_LENGTH_IN;
				if (!strcmp(unit, "cm")) atr.unit = LCSP_UNIT_LENGTH_CM;
				if (!strcmp(unit, "mm")) atr.unit = LCSP_UNIT_LENGTH_MM;
				if (!strcmp(unit, "pt")) atr.unit = LCSP_UNIT_LENGTH_PT;
				if (!strcmp(unit, "pc")) atr.unit = LCSP_UNIT_LENGTH_PC;
				if (!strcmp(unit, "%")) atr.unit = LCSP_UNIT_LENGTH_PERCENT;

				v = *lws_csp_px(&atr, ps);

				if (op)
					lws_fx_add(&sum, &sum, &v);
				else
					lws_fx_sub(&sum, &sum, &v);
			}

			*(lws_fx_t *)&a->r = sum;
			return &a->r;
		}

	case LCSP_UNIT_LENGTH_EM:
		return lws_fx_mul((lws_fx_t *)&a->r, &a->u.i, &f->em);

	case LCSP_UNIT_LENGTH_EX:
		return lws_fx_mul((lws_fx_t *)&a->r, &a->u.i, &f->ex);

	case LCSP_UNIT_LENGTH_IN:	/* (inches * 2.54 * hwmm) / hwpx */
		if (ref == LWS_LHPREF_NONE)
			break;
		return lws_fx_div((lws_fx_t *)&a->r, lws_fx_mul(&t2,
			lws_fx_mul(&t3, &a->u.i, &c_254),
				&ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);

	case LCSP_UNIT_LENGTH_CM:	/* (cm * 10 * hwmm) / hwpx */
		if (ref == LWS_LHPREF_NONE)
			break;
		return lws_fx_div((lws_fx_t *)&a->r,
				lws_fx_mul(&t2,
					lws_fx_mul(&t3, &a->u.i, &c_10),
					&ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);
	case LCSP_UNIT_LENGTH_MM:	/* (mm * hwmm) / hwpx */
		if (ref == LWS_LHPREF_NONE)
			break;
		return lws_fx_div((lws_fx_t *)&a->r, lws_fx_mul(&t2,
				&a->u.i, &ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);

	case LCSP_UNIT_LENGTH_PT:	/* ((pt * 2.54 * hwmm) / hwpx ) / 72 */
		if (ref == LWS_LHPREF_NONE)
			break;
		return lws_fx_div((lws_fx_t *)&a->r, lws_fx_div(&t1,
			 lws_fx_mul(&t2, lws_fx_mul(&t3,
					 &a->u.i, &c_254),
					 &ctx->ic.wh_mm[ref]),
					 &ctx->ic.wh_px[ref]), &c_72);

	case LCSP_UNIT_LENGTH_PC:	/* ((pc * 2.54 * hwmm) / hwpx ) / 6 */
		if (ref == LWS_LHPREF_NONE)
			break;
		return lws_fx_div((lws_fx_t *)&a->r, lws_fx_div(&t1,
				lws_fx_mul(&t2, lws_fx_mul(&t3,
					&a->u.i, &c_254), &ctx->ic.wh_mm[ref]),
						  &ctx->ic.wh_px[ref]), &c_6);
	case LCSP_UNIT_LENGTH_PX:	/* px */
		return &a->u.i;

	case LCSP_UNIT_LENGTH_PERCENT:	/* (percent * psb->w) / 100 */
		if (ref == LWS_LHPREF_NONE)
			break;

		t1.whole = 0;
		t1.frac = 0;

		lws_css_compute_cascaded_length(ctx, ref, ps, &t1);

		return lws_fx_div((lws_fx_t *)&a->r,
				lws_fx_mul(&t2, &a->u.i, &t1), &c_100);

	default:
		break;
	}

	return &a->u.i;
}

static lhp_atr_t *
lhp_atr_new(lhp_ctx_t *ctx, size_t name_len, size_t value_len)
{
	lhp_pstack_t *ps = lws_container_of(ctx->stack.tail, lhp_pstack_t, list);

	/* create the element name attribute */
	lhp_atr_t *a = lws_malloc(sizeof(*a) + name_len + 1 + value_len + 1,
				  "html_elem_atr");
	size_t n;

	if (!a)
		return NULL;

	if (!ps->atr.count) {
		/* only check the tag string, not the attributes */
		ctx->u.f.void_element = 0;

		/*
		 * mark ps that are elements that contain others for layout as
		 * being the parent block
		 */
		if ((name_len == 4 && !strncmp(ctx->buf, "body", 4)) ||
		    (name_len == 3 && !strncmp(ctx->buf, "div", 3)))
			ps->is_block = 1;

		for (n = 0; n < LWS_ARRAY_SIZE(void_elems); n++)
			if (ctx->npos == void_elems_lens[n] &&
			    !strncmp(void_elems[n], ctx->buf, (size_t)ctx->npos))
				ctx->u.f.void_element = 1;
	}

	lws_dll2_clear(&a->list);
	a->name_len = name_len;
	a->value_len = value_len;
	ctx->buf[ctx->npos] = '\0';
	memcpy(&a[1], ctx->buf, (unsigned int)ctx->npos + 1u);
	*(((uint8_t *)&a[1]) + name_len) = '\0';
	*(((uint8_t *)&a[1]) + name_len + 1 + value_len) = '\0';
	lws_dll2_add_tail(&a->list, &ps->atr);

	ctx->npos = 0;

	return a;
}

static int
hspace(uint8_t c)
{
	return c == ' ' || c == 9 || c == 10 || c == 12 || c == 13;
}

void
lhp_uni_emit(lhp_ctx_t *ctx)
{
	/* emit */
	if (ctx->temp <= 0x7f) {
		ctx->buf[ctx->npos++] = (char)(ctx->temp & 0x7f);
		return;
	}
	if (ctx->temp <= 0x7ff) {
		ctx->buf[ctx->npos++] = (char)(0xc0 | ((uint8_t)(ctx->temp >> 6) & 0x1f));
		goto a;
	}
	if (ctx->temp <= 0xffff) {
		ctx->buf[ctx->npos++] = (char)(0xe0 | ((uint8_t)(ctx->temp >> 12) & 0xf));
		goto b;
	}
	if (ctx->temp <= 0x10ffff) {
		ctx->buf[ctx->npos++] = (char)(0xf0 | ((uint8_t)(ctx->temp >> 18) & 7));
		ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp >> 12) & 0x3f));
	}
b:
	ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp >> 6) & 0x3f));
a:
	ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp) & 0x3f));
}

static int
lcsp_append_cssval_int(lhp_ctx_t *ctx)
{
	lcsp_atr_t *atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr), LHP_AC_GRANULE);
	if (!atr)
		return 1;

	/* add this prop value atr to the def */

	//lwsl_err("%s: tf %d.%u\n", __func__, ctx->tf.whole, ctx->tf.frac);
	atr->u.i = ctx->tf;
	atr->unit = ctx->unit;

	lws_dll2_add_tail(&atr->list, &ctx->def->atrs);

	return 0;
}

static int
lcsp_append_cssval_color(lhp_ctx_t *ctx)
{
	lcsp_atr_t *atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr), LHP_AC_GRANULE);
	unsigned int r, g, b, a = 0xff;

	if (!atr)
		return 1;

	/* add this prop value atr to the def */

	switch (ctx->temp_count) {
	case 3:
		r = (ctx->temp >> 8) & 0xf;
		g = (ctx->temp >> 4) & 0xf;
		b = ctx->temp & 0xf;
		atr->u.rgba = (a << 24) | (b << 20) | (b << 16) |
				(g << 12) | (g << 8) | (r << 4) | r;
		break;
	case 4:
		r = (ctx->temp >> 12) & 0xf;
		g = (ctx->temp >> 8) & 0xf;
		b = (ctx->temp >> 4) & 0xf;
		a = ctx->temp & 0xf;
		atr->u.rgba = (a << 28) | (a << 24) | (b << 20) | (b << 16) |
				(g << 12) | (g << 8) | (r << 4) | r;
		break;
	case 6:
		r = (ctx->temp >> 16) & 0xff;
		g = (ctx->temp >> 8) & 0xff;
		b = (ctx->temp) & 0xff;
		atr->u.rgba = (a << 24) | (b << 16) | (g << 8) | r;
		break;
	case 8:
		r = (ctx->temp >> 24) & 0xff;
		g = (ctx->temp >> 16) & 0xff;
		b = (ctx->temp >> 8) & 0xff;
		a = (ctx->temp) & 0xff;
		atr->u.rgba = (a << 24) | (b << 16) | (g << 8) | r;
		break;
	}

	// lwsl_err("%s: %d, 0x%08x, 0x%08x\n", __func__, ctx->temp_count, ctx->temp, atr->u.rgba);

	atr->unit = LCSP_UNIT_RGBA;

	lws_dll2_add_tail(&atr->list, &ctx->def->atrs);

	ctx->u.f.color = 0;
	ctx->temp = 0;
	ctx->temp_count = 0;

	return 0;
}

static int
lcsp_append_cssval_string(lhp_ctx_t *ctx)
{
	lcsp_atr_t *atr;
	char *v, *c = &ctx->buf[0];

	if (c[0] == '\"' || c[0] == '\'') {
		c++;
		ctx->npos--;
	}
	if (ctx->npos && (c[ctx->npos - 1] == '\"' || c[ctx->npos - 1] == '\''))
		ctx->npos--;

	atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr) + (size_t)ctx->npos + 1u,
			     LHP_AC_GRANULE);
	if (!atr)
		return 1;

	v = (char *)&atr[1];
	atr->value_len = (size_t)ctx->npos;
	memcpy(v, c, (size_t)ctx->npos);
	v[ctx->npos] = '\0';
	atr->unit = LCSP_UNIT_STRING;

	//lwsl_notice("%s: %s\n", __func__, v);

	lws_dll2_add_tail(&atr->list, &ctx->def->atrs);

	return 0;
}

const char *
lws_html_get_atr(lhp_pstack_t *ps, const char *aname, size_t aname_len);

static int
lhp_element_has_class(lhp_pstack_t *ps, const char *name, size_t name_len)
{
	const char *c = lws_html_get_atr(ps, "class", 5);
	struct lws_tokenize ts;

	if (!c)
		return 0;

	memset(&ts, 0, sizeof(ts));
	ts.start = c;
	ts.len = strlen(c);
	ts.flags = LWS_TOKENIZE_F_MINUS_NONTERM;

	do {
		ts.e = (int8_t)lws_tokenize(&ts);
		if (ts.e == LWS_TOKZE_TOKEN) {
			if (ts.token_len == name_len &&
			    !memcmp(ts.token, name, name_len))
				return 1;
		}
	} while (ts.e > 0);

	return 0;
}

static int
lws_css_cascade_atr_match(lhp_ctx_t *ctx, lhp_pstack_t *ps, const char *tag,
			  size_t tag_len)
{
	lws_start_foreach_dll(struct lws_dll2 *, q, ctx->css.head) {
		lcsp_stanza_t *stz = lws_container_of(q, lcsp_stanza_t, list);

		/* ... does this stanza mention our name? */

		lws_start_foreach_dll(struct lws_dll2 *, z, stz->names.head) {
			lcsp_names_t *nm = lws_container_of(z, lcsp_names_t,
							    list);
			const char *p = (const char *)&nm[1];
			size_t nl = nm->name_len;
			char is_class_selector = 0;

			if (nl && *p == '.') { /* match .mycss as mycss */
				p++;
				nl--;
				is_class_selector = 1;
			}

			if (nl == tag_len && !memcmp(p, tag, tag_len)) {
				goto matched;
			} else {
				/* check for tag.class or .class.class */
				const char *dot = memchr(p, '.', nl);
				if (dot) {
					size_t p1_len = (size_t)(dot - p);
					size_t p2_len = nl - p1_len - 1;
					const char *p2 = dot + 1;

					/* p1 is tag (or class1), p2 is class (or class2) */
					/* check if 'tag' argument matches p1 */
					if (p1_len && tag_len == p1_len && !memcmp(tag, p, p1_len)) {
						/* we matched p1, now check if element has p2 as class */
						if (lhp_element_has_class(ps, p2, p2_len))
							goto matched;
					}

					/* check if 'tag' argument matches p2 */
					if (p2_len && tag_len == p2_len && !memcmp(tag, p2, p2_len)) {
						/* we matched p2. */
						if (is_class_selector) {
							/* .class1.class2: check if element has p1 as class */
							if (lhp_element_has_class(ps, p, p1_len))
								goto matched;
						} else {
							/* tag.class: check if element has p1 as tag */
							if (ps->atr.head) {
								lhp_atr_t *ta = lws_container_of(ps->atr.head, lhp_atr_t, list);
								if (ta->name_len == p1_len && !memcmp(&ta[1], p, p1_len))
									goto matched;
							}
						}
					}
				}
			}
			continue;

matched:
			{
				lcsp_stanza_ptr_t *sp = lwsac_use_zero(
						&ctx->cascadeac,
						sizeof(*sp), LHP_AC_GRANULE);
				if (!sp)
					return 1;

				sp->stz = stz;
				lws_dll2_add_tail(&sp->list,
						  &ctx->active_stanzas);
				break;
			}
		} lws_end_foreach_dll(z);

	} lws_end_foreach_dll(q);

	return 0;
}

const char *
lws_html_get_atr(lhp_pstack_t *ps, const char *aname, size_t aname_len)
{
	/* look for src= attribute */
	lws_start_foreach_dll(struct lws_dll2 *, p,
			      lws_dll2_get_head(&ps->atr)) {
		const lhp_atr_t *at = lws_container_of(p,
						lhp_atr_t, list);
		const char *ats = (const char *)&at[1];

		if (at->name_len == aname_len && !strcmp(ats, aname))
			return ats + aname_len + 1;

	} lws_end_foreach_dll(p);

	return NULL;
}

const lcsp_atr_t *
lhp_resolve_var_color(lhp_ctx_t *ctx, const lcsp_atr_t *a)
{
	const char *n;
	size_t len;

	if (a->unit != LCSP_UNIT_STRING && a->unit != LCSP_UNIT_URL)
		return a;

	/* check if it is var(--name) */
	n = (const char *)&a[1];
	if (strncmp(n, "var(--", 6))
		return a;

	n += 4; /* skip var( */
	len = 0;
	while (n[len] && n[len] != ')')
		len++;

	/* lwsl_err("RESOLVE: '%.*s'\n", (int)len, n); */

	/* look it up in css_vars */
	lws_start_foreach_dll(struct lws_dll2 *, d, ctx->css_vars.head) {
		lhp_css_var_t *v = lws_container_of(d, lhp_css_var_t, list);
		const char *vn = (const char *)&v[1];

		if (v->name_len == len && !strncmp(vn, n, len)) {
			/* found it */
			if (v->def && v->def->atrs.head) {
				lcsp_atr_t *ra = lws_container_of(v->def->atrs.head, lcsp_atr_t, list);
				if (ra->unit == LCSP_UNIT_RGBA)
					return ra;
			}
			break;
		}
	} lws_end_foreach_dll(d);

	return a;
}

static const lcsp_atr_t *
lhp_shorthand_TRBL(lhp_ctx_t *ctx, lcsp_props_t prop, int idx)
{
	const lcsp_atr_t *a;
	int c;

	a = lws_css_cascade_get_prop_atr(ctx, prop);
	if (!a)
		return NULL;

	/*
	 * Shorthand expansion:
	 * 1 value: all
	 * 2 values: top/bottom, left/right
	 * 3 values: top, left/right, bottom
	 * 4 values: top, right, bottom, left
	 */

	c = (int)ctx->active_atr.count;
	if (!c)
		return NULL;

	if (c == 1) /* apply to all */
		return a; // Head is same as tail if count 1

	/* navigate to the correct index in active_atr based on count and requested idx */
	/* requested idx: 0=TOP, 1=RIGHT, 2=BOTTOM, 3=LEFT */

	int use_idx = 0;
	switch (c) {
	case 2:
		if (idx == 0 || idx == 2) use_idx = 0;
		else use_idx = 1;
		break;
	case 3:
		if (idx == 0) use_idx = 0;
		else if (idx == 1 || idx == 3) use_idx = 1;
		else use_idx = 2;
		break;
	case 4:
		use_idx = idx;
		break;
	default:
		return a;
	}

	lws_start_foreach_dll(struct lws_dll2 *, d, ctx->active_atr.head) {
		lcsp_atr_ptr_t *ap = lws_container_of(d, lcsp_atr_ptr_t, list);
		if (!use_idx--)
			return ap->atr;
	} lws_end_foreach_dll(d);

	return a;
}

static const lcsp_atr_t *
lhp_shorthand_radii(lhp_ctx_t *ctx, lcsp_props_t prop, int idx)
{
	const lcsp_atr_t *a;
	int c;

	a = lws_css_cascade_get_prop_atr(ctx, prop);
	if (!a)
		return NULL;

	/*
	 * Shorthand expansion for border-radius:
	 * 1 value: all
	 * 2 values: top-left/bottom-right, top-right/bottom-left
	 * 3 values: top-left, top-right/bottom-left, bottom-right
	 * 4 values: top-left, top-right, bottom-right, bottom-left
	 */

	c = (int)ctx->active_atr.count;
	if (!c) return NULL;

	if (c == 1) /* apply to all */
		return a; // Head is same as tail if count 1

	/* navigate to the correct index in active_atr based on count and requested idx */
	/* requested idx: 0=TL, 1=TR, 2=BL, 3=BR */

	int use_idx = 0;
	switch (c) {
	case 2:
		/*
		 * [0] is TL/BR
		 * [1] is TR/BL
		 */
		if (idx == 0 || idx == 3) use_idx = 0;
		else use_idx = 1;
		break;
	case 3:
		/*
		 * [0] is TL
		 * [1] is TR/BL
		 * [2] is BR
		 */
		if (idx == 0) use_idx = 0;
		else if (idx == 1 || idx == 2) use_idx = 1;
		else use_idx = 2;
		break;
	case 4:
		/*
		 * [0] is TL
		 * [1] is TR
		 * [2] is BR
		 * [3] is BL
		 */
		if (idx == 0) use_idx = 0;
		else if (idx == 1) use_idx = 1;
		else if (idx == 3) use_idx = 2;
		else use_idx = 3;
		break;
	default:
		return a;
	}

	lws_start_foreach_dll(struct lws_dll2 *, d, ctx->active_atr.head) {
		lcsp_atr_ptr_t *ap = lws_container_of(d, lcsp_atr_ptr_t, list);
		if (!use_idx--)
			return ap->atr;
	} lws_end_foreach_dll(d);

	return a;
}

/*
 * Produce an ordered list of css stanzas that apply to the current html
 * parsing context, accounting for class="xxx" at each level
 */

static int
lws_css_cascade(lhp_ctx_t *ctx)
{
	lws_dll2_owner_clear(&ctx->active_stanzas);
	lwsac_free(&ctx->cascadeac);
	lws_dll2_owner_clear(&ctx->active_atr);
	lwsac_free(&ctx->propatrac);
	ctx->in_body = 0;

	/* let's proceed through the html element stack that applies */

	lws_start_foreach_dll(struct lws_dll2 *, p, ctx->stack.head) {
		lhp_pstack_t *ps = lws_container_of(p, lhp_pstack_t, list);

		/*
		 * if there is a css definition for the html entity at this
		 * stack level, add its stanza to the results
		 */

		lws_start_foreach_dll(struct lws_dll2 *, ha, ps->atr.head) {
			lhp_atr_t *a = lws_container_of(ha, lhp_atr_t, list);
			struct lws_tokenize ts;

			memset(&ts, 0, sizeof(ts));

			if (ha == ps->atr.head) {
				ts.start = (const char *)&a[1];
				ts.len = a->name_len;
			}


			if (a->name_len == 5 &&
			     !strcmp((const char *)&a[1], "class")) {
				ts.start = ((const char *)&a[1]) + 5 + 1;
				ts.len = a->value_len;
				ts.flags |= LWS_TOKENIZE_F_MINUS_NONTERM;
			}

			do {
				ts.e = (int8_t)lws_tokenize(&ts);
				if (ts.e == LWS_TOKZE_TOKEN) {

					if (ha == ps->atr.head &&
					    ts.token_len == 4 &&
					    !memcmp(ts.token, "body", 4))
						ctx->in_body = 1;

					/*
					 * let's look through the css stanzas
					 * for a tag match
					 */

					if (lws_css_cascade_atr_match(ctx, ps,
							ts.token, ts.token_len))
						return 1;
				}

			} while (ts.e > 0);

		} lws_end_foreach_dll(ha);

		/*
		 * ... fill layout-related CSS lookups into the element
		 * stack item... these are all pointers to the attribute
		 * not necessarily computed scalars.  Eg lws_csp_px() can be
		 * used later to resolve atr like 50% to pixel values.
		 */

		ps->css_position = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_POSITION);
		ps->css_width = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_WIDTH);
		ps->css_height = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_HEIGHT);
		ps->css_display = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_DISPLAY);

		ps->css_border_radius[0] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_TOP_LEFT_RADIUS);
		if (!ps->css_border_radius[0]) ps->css_border_radius[0] = lhp_shorthand_radii(ctx, LCSP_PROP_BORDER_RADIUS, 0);
		ps->css_border_radius[1] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_TOP_RIGHT_RADIUS);
		if (!ps->css_border_radius[1]) ps->css_border_radius[1] = lhp_shorthand_radii(ctx, LCSP_PROP_BORDER_RADIUS, 1);
		ps->css_border_radius[2] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_BOTTOM_LEFT_RADIUS);
		if (!ps->css_border_radius[2]) ps->css_border_radius[2] = lhp_shorthand_radii(ctx, LCSP_PROP_BORDER_RADIUS, 2);
		ps->css_border_radius[3] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_BOTTOM_RIGHT_RADIUS);
		if (!ps->css_border_radius[3]) ps->css_border_radius[3] = lhp_shorthand_radii(ctx, LCSP_PROP_BORDER_RADIUS, 3);

		ps->css_background_color = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BACKGROUND_COLOR);
		if (ps->css_background_color)
			ps->css_background_color = lhp_resolve_var_color(ctx, ps->css_background_color);

		ps->css_color = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_COLOR);
		if (ps->css_color)
			ps->css_color = lhp_resolve_var_color(ctx, ps->css_color);

		ps->css_pos[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_TOP);
		ps->css_pos[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_RIGHT);
		ps->css_pos[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BOTTOM);
		ps->css_pos[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_LEFT);

		ps->css_margin[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_TOP);
		if (!ps->css_margin[CCPAS_TOP]) ps->css_margin[CCPAS_TOP] = lhp_shorthand_TRBL(ctx, LCSP_PROP_MARGIN, CCPAS_TOP);
		ps->css_margin[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_RIGHT);
		if (!ps->css_margin[CCPAS_RIGHT]) ps->css_margin[CCPAS_RIGHT] = lhp_shorthand_TRBL(ctx, LCSP_PROP_MARGIN, CCPAS_RIGHT);
		ps->css_margin[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_BOTTOM);
		if (!ps->css_margin[CCPAS_BOTTOM]) ps->css_margin[CCPAS_BOTTOM] = lhp_shorthand_TRBL(ctx, LCSP_PROP_MARGIN, CCPAS_BOTTOM);
		ps->css_margin[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_LEFT);
		if (!ps->css_margin[CCPAS_LEFT]) ps->css_margin[CCPAS_LEFT] = lhp_shorthand_TRBL(ctx, LCSP_PROP_MARGIN, CCPAS_LEFT);

		ps->css_padding[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_TOP);
		if (!ps->css_padding[CCPAS_TOP]) ps->css_padding[CCPAS_TOP] = lhp_shorthand_TRBL(ctx, LCSP_PROP_PADDING, CCPAS_TOP);
		ps->css_padding[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_RIGHT);
		if (!ps->css_padding[CCPAS_RIGHT]) ps->css_padding[CCPAS_RIGHT] = lhp_shorthand_TRBL(ctx, LCSP_PROP_PADDING, CCPAS_RIGHT);
		ps->css_padding[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_BOTTOM);
		if (!ps->css_padding[CCPAS_BOTTOM]) ps->css_padding[CCPAS_BOTTOM] = lhp_shorthand_TRBL(ctx, LCSP_PROP_PADDING, CCPAS_BOTTOM);
		ps->css_padding[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_LEFT);
		if (!ps->css_padding[CCPAS_LEFT]) ps->css_padding[CCPAS_LEFT] = lhp_shorthand_TRBL(ctx, LCSP_PROP_PADDING, CCPAS_LEFT);

	} lws_end_foreach_dll(p);

	return 0;
}

void
lws_lhp_destruct(lhp_ctx_t *ctx)
{
	if (ctx->base_url) {
		free((void *)ctx->base_url);
		ctx->base_url = NULL;
	}
	lws_dll2_foreach_safe(&ctx->stack, NULL, lhp_clean_stack);
	lws_dll2_owner_clear(&ctx->active_stanzas);
	lws_dll2_owner_clear(&ctx->active_atr);
	lws_dll2_owner_clear(&ctx->css_vars);
	lwsac_free(&ctx->propatrac);
	lwsac_free(&ctx->cascadeac);
	lwsac_free(&ctx->cssac);
}

void
lws_lhp_tag_dlo_id(lhp_ctx_t *ctx, lhp_pstack_t *ps, lws_dlo_t *dlo)
{
	const char *pname;

	/* Deal with ID matching */

	pname = lws_html_get_atr(ps, "id", 2);
	if (!pname)
		return;

	lws_start_foreach_dll(struct lws_dll2 *, d, lws_dll2_get_head(ctx->ids)) {
		lws_display_id_t *id = lws_container_of(d, lws_display_id_t, list);

		if (!strcmp(pname, id->id)) {
			dlo->id = id;
			id->exists = 1;
			lwsl_debug("%s: %s tagged\n", __func__, pname);
			return;
		}

	} lws_end_foreach_dll(d);
}

lws_stateful_ret_t
lws_lhp_parse(lhp_ctx_t *ctx, const uint8_t **buf, size_t *len)
{
	lhp_pstack_t *ps1, *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_stateful_ret_t r;
	const uint8_t *rbuf;
	size_t rsize;
	lhp_atr_t *a;

	if (ctx->await_css_done && !ctx->is_css)
		return LWS_SRET_AWAIT_RETRY;

	assert(drt);

	if (!*len && ctx->is_css && ctx->await_css_done && ctx->finish_css) {
		r = ctx->await_css_done;
		ctx->u.s = 0;
		ctx->tag = NULL;
		ctx->tag_len = 0;
		ctx->npos = 0;
		ctx->state = LHPS_TAG;
		ctx->await_css_done = 0;
		ctx->finish_css = 0;
		if (r)
			return LWS_SRET_AWAIT_RETRY;
		ctx->u.f.closing = 1;
	}

	while (*len) {
		uint8_t c = *(*buf)++;
		int is_term = 0;

		(*len)--;

		if (ctx->state == LHPS_DO_START_ELEM) {
			/* we are retrying the inner callback */
			(*len)++;
			(*buf)--;
		}

	//	lwsl_notice("%s: %d, '%c', %02X\n", __func__, ctx->state, c, c);

		switch (ctx->state) {

		case LHPS_INIT:

			/* default css injection first, then... */

			ctx->state = LCSPS_CSS_OUTER;
			ctx->u.f.default_css = 1;
			/*
			 * recurse (there's no stack usage to speak of) to
			 * do the default css parse first,  CSS doesn't have a
			 * way to recurse further.
			 */
			rbuf = (const uint8_t *)default_css;
			rsize = strlen(default_css);
			r = lws_lhp_parse(ctx, &rbuf, &rsize);
			if (r >= LWS_SRET_FATAL) {
				lwsl_err("%s: css parse fail\n", __func__);
				return r;
			}
			ctx->u.f.default_css = 0;
			ctx->npos = 0;
			ctx->state = LHPS_OUTER;

			/* fallthru */

		case LHPS_OUTER:
			switch (c) {
			case '<':
				ctx->u.s = 0;
				ctx->u.f.first = 1;

				ctx->tag = NULL;
				ctx->tag_len = 0;

				ctx->state = LHPS_TAG;

				if (ctx->stack.count == LHP_MAX_ELEMS_NEST /* sanity */) {
					lwsl_err("%s: MAX_ELEMS_NEST\n", __func__);
					ps->cb(ctx, LHPCB_FAILED);
					return LWS_SRET_FATAL;

				}

				ps1 = lws_zalloc(sizeof(*ps1), __func__);
				if (!ps1)
					goto oom;

				/* inherit user and cb to start with */
				ps1->user	= ps->user;
				ps1->cb		= ps->cb;
				lws_dll2_owner_clear(&ps1->atr);
				lws_dll2_add_tail(&ps1->list, &ctx->stack);
				ps		= ps1;
				break;

			case '&':
				if (ctx->npos) {
					if (ctx->in_body &&
					    (ctx->npos != 1 || ctx->buf[0] != ' ')) {
						lws_css_cascade(ctx);
						ps->cb(ctx, LHPCB_CONTENT);
					}
					ctx->npos = 0;
				}
				ctx->saved_state = LHPS_OUTER;
				ctx->entity_start = 0;
				ctx->state = LHPS_AMP;
				ctx->temp_count = 0;
				continue;

			case '\t':
			case '\n':
				c = ' ';
				/* fallthru */
			default:
				if (c != ' ' || !ctx->npos ||
				    ctx->buf[ctx->npos - 1] != ' ')
					ctx->buf[ctx->npos++] = (char)c;
				break;
			}

			if (ctx->npos &&
			    (ctx->state != LHPS_OUTER ||
			     ctx->npos >= LHP_STRING_CHUNK - 4)) {
				if (ctx->in_body && (ctx->npos != 1 || ctx->buf[0] != ' ')) {
					lws_css_cascade(ctx);
					ps->cb(ctx, LHPCB_CONTENT);
				}
				ctx->npos = 0;
			}
			break;

		case LHPS_TAG:
			if (c == '!' && ctx->u.f.first) {
				ctx->state = LHPS_SCOMMENT1;
				ctx->u.f.first = 0;
				break;
			}

			if (c == '/' && ctx->u.f.first) {
				/* remove the level we just prepared for this */
				lhp_clean_level(ps);
				ps = lws_container_of(ctx->stack.tail,
						      lhp_pstack_t, list);
				ctx->u.f.closing = 1;
				ctx->u.f.first = 0;
				break;
			}
			ctx->u.f.first = 0;

			/* it implies the end of the tag name */

			if (hspace(c) || c == '/' || c == '>') {
				if (!ctx->u.f.tag_used && ctx->npos && !ctx->u.f.closing) {
					a = lhp_atr_new(ctx, (size_t)ctx->npos, 0);
					if (!a)
						goto oom;
					ctx->tag = (const char *)&a[1];
					ctx->tag_len = a->name_len;

					ctx->u.f.tag_used = 1;

					if (ctx->tag_len == 8 &&
					    !strncasecmp(ctx->buf, "!doctype", 8))
						ctx->u.f.doctype = 1;
				}

				if (c != '/' && c != '>') {

					/* after that, there may be attributes */
					ctx->state = LHPS_ATTRIB;
					break;
				}

				/* <style> trapdoor into inline css parsing */

				if (ctx->u.f.tag_used && c == '>' &&
				    ctx->tag_len == 5 &&
				    !strncasecmp(ctx->buf, "style", 5)) {
					ctx->npos = 5;

					ps->cb(ctx, LHPCB_ELEMENT_START);
					ctx->npos = 0;
					ctx->state = LCSPS_CSS_OUTER;
					break;
				}

				/* <script> trapdoor into skipping */

				if (ctx->u.f.tag_used && c == '>' &&
				    ctx->tag_len == 6 &&
				    !strncasecmp(ctx->buf, "script", 6) &&
				    !ctx->u.f.closing) {
					/*
					 * We don't want to parse script as
					 * text.
					 */
					ctx->npos = 0;
					ctx->state = LHPS_SCRIPT;
					break;
				}
			}

			if (ctx->u.f.void_element && c == '/') {
				/* we had something like <br and then we see a
				 * closing / */
				ctx->u.f.closing = 1;
				break;
			}

			if (c == '>') {
				ctx->state = LHPS_DO_START_ELEM;
				goto elem_start;
			}

			/* tag names may only contain 0–9, a–z, and A–Z */

			if ( //ctx->closing ||
			     c < '0' ||
			    (c > '9' && c < 'A') ||
			    (c > 'Z' && c < 'a') ||
			     c > 'z') {
				ctx->state = LHPS_BAD_TAG;
				break;
			}

			/* collect the tag name */

			if (!hspace(c))
				ctx->buf[ctx->npos++] = (char)c;
			if (ctx->npos == 32) { /* sanity */
				ctx->npos = 0;
				ctx->state = LHPS_BAD_TAG;
				break;
			}

			break;
		case LHPS_BAD_TAG:
			/* just sit it out until the element end */
			if (c != '>')
				break;

			ctx->state = LHPS_DO_START_ELEM;

			/* fallthru */

		case LHPS_DO_START_ELEM:
elem_start:
			/* present the tag in buf, if any */
			if (ctx->tag_len)
				memcpy(ctx->buf, ctx->tag, ctx->tag_len);
			ctx->buf[ctx->tag_len] = '\0';
			ctx->npos = (int)ctx->tag_len;

			if (!ctx->u.f.closing || ctx->u.f.void_element) {
				const char *pname = NULL, *rel = NULL;
				const struct lcsp_atr *aa = NULL;
				lws_dlo_ss_create_info_t i;
				lws_dlo_image_t u;
				lhp_pstack_t *psb;
				lws_dlo_t *dlo;
				lws_box_t box;
				char url[LHP_URL_LEN];

				memset(&i, 0, sizeof(i));
				lws_css_cascade(ctx);

				if (ctx->npos == 4 && !strncmp(ctx->buf, "body", 4)) {
					lws_display_colour_t col =
						LWSDC_RGBA(255, 255, 255, 255);

					if (ps->css_background_color &&
					    ps->css_background_color->unit == LCSP_UNIT_RGBA)
						col = ps->css_background_color->u.rgba;

					ps->drt.w = ctx->ic.wh_px[LWS_LHPREF_WIDTH];
					if (ps->css_width &&
					    ps->css_width->propval != LCSP_PROPVAL_AUTO// &&
					    //lws_fx_comp(lws_csp_px(ps->css_width, ps), &box.w) < 0
					    )
						ps->drt.w = *lws_csp_px(ps->css_width, ps);

					ps->drt.h = ctx->ic.wh_px[LWS_LHPREF_HEIGHT];
					if (ps->css_height &&
					    ps->css_height->propval != LCSP_PROPVAL_AUTO) //&&
					    //lws_fx_comp(lws_csp_px(ps->css_height, ps),
						//		   &ps->drt.h) < 0)
						ps->drt.h = *lws_csp_px(ps->css_height, ps);

					/* put a default white body background behind everything */

					lws_fx_set(box.x, 0, 0);
					lws_fx_set(box.y, 0, 0);
					box.w = ps->drt.w;
					box.h = ps->drt.h;

					ps->dlo = (lws_dlo_t *)lws_display_dlo_rect_new(
							drt->dl, NULL, &box, 0,
							col);

					ps->dlo->flag_toplevel = 1;

					lhp_set_dlo_padding_margin(ps, ps->dlo);
				}

				/* it's a link? */

				if (ctx->npos == 4 && !strncmp(ctx->buf, "link", 4)) {
					pname = lws_html_get_atr(ps, "href", 4);
					rel = lws_html_get_atr(ps, "rel", 3);

					if (!rel || strncmp(rel, "stylesheet", 10))
						goto issue_elem_start;
				}

				/* it's an img? */

				if (ctx->npos == 3 && !strncmp(ctx->buf, "img", 3))
					pname = lws_html_get_atr(ps, "src", 3);
				else {
					aa = lws_css_cascade_get_prop_atr(ctx,
						LCSP_PROP_BACKGROUND_IMAGE);

					if (aa)
						pname = (const char *)(aa + 1);
				}

				assert(ctx->base_url);

				if (!pname)
					goto issue_elem_start;

				/* we should be in an <img tag or
				 * something with a background image */

				assert(ctx->base_url);

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

				/* decode percent-encoding in the URL */
				{
					char temp[LHP_URL_LEN];
					lws_strncpy(temp, url, sizeof(temp));
					lws_urldecode(url, temp, sizeof(url) - 1);
				}

				psb = lws_css_get_parent_block(ctx, ps);
				//if (!psb)
				//	lwsl_err("%s: NULL psb\n", __func__);

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

				if (psb) {
					box.x = psb->curx;
					box.y = psb->cury;
					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));
				}

				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 (ps->css_height &&
					lws_fx_comp(lws_csp_px(ps->css_height, ps), &box.h) > 0)
					box.h = *lws_csp_px(ps->css_height, ps);

				memset(&u, 0, sizeof(u));
				if (lws_dlo_ss_find(cx, url, &u)) {

					i.cx			= cx;
					i.dl			= drt->dl;
					if (psb)
						i.dlo_parent	= psb->dlo;
					i.box			= &box;
					i.on_rx			= ctx->ssevcb;
					i.on_rx_sul		= ctx->ssevsul;
					i.url			= url;
					i.lhp			= ctx;
					i.u			= &u;
					i.window		= ctx->window;

					lwsl_cx_info(cx, "not already in progress: %s", url);
					if (lws_dlo_ss_create(&i, &dlo)) {
						/* we can't get it */
						lwsl_cx_warn(cx, "Can't get %s", url);
						goto check_closing;
					} else {
						lwsl_cx_info(cx, "Created SS for %s\n", url);
						if (ctx->npos == 3 && !strncmp(ctx->buf, "img", 3))
							ps->dlo = dlo;
					}
				} else {
					// lwsl_cx_warn(cx, "Found in-progress %s\n", url);
					if (ctx->npos == 3 && !strncmp(ctx->buf, "img", 3))
						ps->dlo = &u.u.dlo_png->dlo;
				}

				if (ctx->npos == 4 && !strncmp(ctx->buf, "link", 4)) {
					ps->cb(ctx, LHPCB_ELEMENT_START);
					ctx->npos = 0;
					ctx->state = LCSPS_CSS_OUTER;
					ctx->await_css_done = 1;

					return LWS_SRET_AWAIT_RETRY;
				}

				/*
				 * It's on its way to some extent and *u set...
				 *
				 * If he has given explicit width and height
				 * for the image, no need to wait for them
				 */

				if (lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_HEIGHT), ps)->whole &&
						lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_WIDTH), ps)->whole) {
					lwsl_cx_info(cx, "Have width and height %d x %d",
							(int)lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
								LCSP_PROP_WIDTH), ps)->whole,
							(int)lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
								LCSP_PROP_HEIGHT), ps)->whole);

					u.u.dlo_png->dlo.box.w.whole = lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_WIDTH), ps)->whole;
					u.u.dlo_png->dlo.box.h.whole = lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
							LCSP_PROP_HEIGHT), ps)->whole;
					goto issue_elem_start;
				}

				{
					const char *p = lws_html_get_atr(ps, "width", 5);
					if (p)
						u.u.dlo_png->dlo.box.w.whole = atoi(p);
					p = lws_html_get_atr(ps, "height", 6);
					if (p)
						u.u.dlo_png->dlo.box.h.whole = atoi(p);
				}

				if (u.u.dlo_png->dlo.box.w.whole &&
				    u.u.dlo_png->dlo.box.h.whole)
					goto issue_elem_start;

				/*
				 * Do we have the dimensions?  If not, bail
				 * from here and await a retry (maybe caused by
				 * data coming for the image)
				 */

				if (!lws_dlo_image_width(&u) ||
				    !lws_dlo_image_height(&u)) {
					ps->dlo->budget++;
					if (ps->dlo->budget < 8) {
						lwsl_warn("%s: exiting with AWAIT_RETRY due to no dims\n", __func__);
						return LWS_SRET_AWAIT_RETRY;
					} else
						lwsl_err("%s: ignoring no dims\n", __func__);
				}

				u.u.dlo_png->dlo.box.w.whole = (int32_t)lws_dlo_image_width(&u);
				u.u.dlo_png->dlo.box.h.whole = (int32_t)lws_dlo_image_height(&u);

				/* did it fail to retreive it? */

				if (u.u.dlo_png->dlo.box.w.whole < 0) {
					lwsl_notice("%s: understanding image failed\n", __func__);
					goto check_closing;
				}

				/*
				 * ... we needed it, we have it... we set it...
				 * ... let's go
				 */

issue_elem_start:
				r = ps->cb(ctx, LHPCB_ELEMENT_START);
				ctx->npos = 0;
				if (r) {
					lwsl_notice("%s: inner cb returned %d\n", __func__, r);
					return r;
				}
			}

check_closing:
			if (ctx->u.f.closing || ctx->u.f.void_element){
				if (ctx->stack.count == 1) {
					lwsl_err("%s: element close mismatch\n", __func__);
					ps->cb(ctx, LHPCB_FAILED);
					return LWS_SRET_FATAL;
				}
				if (ps->atr.head) {
					lhp_atr_t *a = lws_container_of(ps->atr.head, lhp_atr_t, list);
					memcpy(ctx->buf, &a[1], a->name_len);
					ctx->npos = (int)a->name_len;
				}
				ps->cb(ctx, LHPCB_ELEMENT_END);
				ctx->npos = 0;
				/* remove the start level */
				lhp_clean_level(ps);
				lws_css_cascade(ctx);
				ps = lws_container_of(ctx->stack.tail,
						      lhp_pstack_t, list);
			}
			ctx->npos = 0;
			ctx->state = LHPS_OUTER;
			break;

		case LHPS_ATTRIB:

			if (ctx->u.f.doctype && c == '\"') {
				ctx->u.f.inq = ctx->u.f.inq ^ 1u;
				if (ctx->u.f.inq)
					break;
			}

			if ((ctx->u.f.inq || !hspace(c)) &&
			    (c != '/' || ctx->u.f.inq) && c != '>') {
				/* collect the attrib name */
				ctx->buf[ctx->npos++] = (char)c;
				/* sanity */
				if (ctx->npos == LHP_STRING_CHUNK) {
					lwsl_err("%s: string chunk\n", __func__);
					ps->cb(ctx, LHPCB_FAILED);
					return LWS_SRET_FATAL;
				}
				if (c == '=') {
					ctx->nl_temp = ctx->npos - 1;
					ctx->state = LHPS_ATTRIB_VAL;
				}
				break;
			}
			if (c == '/') {
				ctx->u.f.closing = 1;
				break;
			}

			if (ctx->npos &&
			    !lhp_atr_new(ctx, (size_t)ctx->npos, 0))
				goto oom;

			if (c == '>') {
				ctx->state = LHPS_DO_START_ELEM;
				goto elem_start;
			}
			break;

		case LHPS_ATTRIB_VAL:

			if (/*ctx->u.f.doctype && */c == '\"') {
				ctx->u.f.inq = ctx->u.f.inq ^ 1u;
				if (ctx->u.f.inq)
					break;
			}

			if (c == '&') {
				ctx->saved_state = LHPS_ATTRIB_VAL;
				ctx->entity_start = ctx->npos;
				ctx->temp_count = 0;
				ctx->state = LHPS_AMP;
				break;
			}

			if ((ctx->u.f.inq || !hspace(c)) &&
			    c != '>' && c != '\'' && c != '\"') {
				/* collect the attrib value */
				ctx->buf[ctx->npos++] = (char)c;
				/* sanity */
				if (ctx->npos == LHP_STRING_CHUNK) {
					lwsl_err("%s: string chunk 2\n", __func__);
					ps->cb(ctx, LHPCB_FAILED);
					return LWS_SRET_FATAL;
				}
				break;
			}
			if (c == '/') {
				ctx->u.f.closing = 1;
				break;
			}

			if (c == '\'' || c == '\"')
				break;

			if (ctx->u.f.inq)
				break;

			if (ctx->npos) {
				ctx->buf[ctx->npos] = '\0';
				if (!lhp_atr_new(ctx, (size_t)ctx->nl_temp,
					 (size_t)ctx->npos - (size_t)ctx->nl_temp - 1u))
					goto oom;
				ctx->state = LHPS_ATTRIB;
				ctx->npos = 0;
				if (c != '>')
					break;
			}

			if (c == '>') {
				ctx->state = LHPS_DO_START_ELEM;
				goto elem_start;
			}

			break;

		case LHPS_AMP:
			/* the character after the & */
			if (c == '#') {
				ctx->state = LHPS_AMPHASH;
				ctx->temp = 0;
				break;
			}

			if ((c >= 'a' && c <= 'z') ||
			    (c >= 'A' && c <= 'Z') ||
			    (c >= '0' && c <= '9')) {
				if (ctx->npos < (int)sizeof(ctx->buf) - 1)
					ctx->buf[ctx->npos++] = (char)c;
				break;
			}

			if (c == ';') {
				size_t n;

				ctx->buf[ctx->npos] = '\0';
				for (n = 0; n < LWS_ARRAY_SIZE(entities); n++) {
					if (!strcmp(ctx->buf + ctx->entity_start, entities[n].name)) {
						ctx->temp = entities[n].val;
						ctx->npos = ctx->entity_start;
						lhp_uni_emit(ctx);
						ctx->state = ctx->saved_state;
						goto done_amp;
					}
				}

				if (ctx->npos + 2 < LHP_STRING_CHUNK) {
					memmove(ctx->buf + ctx->entity_start + 1,
						ctx->buf + ctx->entity_start,
						(size_t)(ctx->npos - ctx->entity_start));
					ctx->buf[ctx->entity_start] = '&';
					ctx->buf[ctx->npos + 1] = ';';
					ctx->npos += 2;
					ctx->state = ctx->saved_state;
				} else {
					ctx->npos = 0;
					ctx->state = ctx->saved_state;
				}
				break;
			}

			if (ctx->npos + 1 < LHP_STRING_CHUNK) {
				memmove(ctx->buf + ctx->entity_start + 1,
					ctx->buf + ctx->entity_start,
					(size_t)(ctx->npos - ctx->entity_start));
				ctx->buf[ctx->entity_start] = '&';
				ctx->npos++;
				(*len)++;
				(*buf)--;
				ctx->state = ctx->saved_state;
			} else {
				ctx->npos = 0;
				ctx->state = ctx->saved_state;
			}
done_amp:
			break;
		case LHPS_AMPHASH:
			/*
			 * This is either decimal or hex unicode like
			 * &#1234; or &#xfc16;
			 */
			if (c == 'x' || c == 'X') {
				ctx->state = LHPS_AMPHASH_HEX;
				break;
			}

			if (ctx->temp_count++ > 32 /* sanity */) {
				ctx->state = ctx->saved_state;
				break;
			}
			if (c == ';') {
				if (ctx->npos >= LHP_STRING_CHUNK - 5) {
					if (ctx->saved_state == LHPS_OUTER && ctx->in_body) {
						ps->cb(ctx, LHPCB_CONTENT);
						ctx->npos = 0;
					} else {
						if (ctx->saved_state != LHPS_OUTER)
							lwsl_err("%s: string chunk\n", __func__);
						ps->cb(ctx, LHPCB_FAILED);
						return LWS_SRET_FATAL;
					}
				}
				ctx->npos = ctx->entity_start;
				lhp_uni_emit(ctx);
				ctx->state = ctx->saved_state;
				break;
			}

			if (c >= '0' && c <= '9')
				ctx->temp = (uint32_t)(((int)ctx->temp * 10) + ((int)c - '0'));
			else
				ctx->state = ctx->saved_state;

			break;

		case LHPS_AMPHASH_HEX:
			if (c == ';') {
				if (ctx->npos >= LHP_STRING_CHUNK - 5) {
					if (ctx->saved_state == LHPS_OUTER && ctx->in_body) {
						ps->cb(ctx, LHPCB_CONTENT);
						ctx->npos = 0;
					} else {
						if (ctx->saved_state != LHPS_OUTER)
							lwsl_err("%s: string chunk\n", __func__);
						ps->cb(ctx, LHPCB_FAILED);
						return LWS_SRET_FATAL;
					}
				}
				ctx->npos = ctx->entity_start;
				lhp_uni_emit(ctx);
				ctx->state = ctx->saved_state;
				break;
			}

			if (ctx->temp_count++ > 8 /* sanity */) {
				ctx->state = ctx->saved_state;
				break;
			}

			if (c >= '0' && c <= '9') {
				ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - '0'));
				break;
			}

			if (c >= 'A' && c <= 'F') {
				ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - 'A') + 10);
				break;
			}

			if (c >= 'a' && c <= 'f') {
				ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - 'a') + 10);
				break;
			}

			ctx->state = ctx->saved_state;
			break;
		case LHPS_SCOMMENT1: /* we have <! */
			if (c == '-') {
				ctx->state = LHPS_SCOMMENT2;
				break;
			}
			/* !doctype is an element tag */
			ctx->buf[ctx->npos++] = '!';
			ctx->buf[ctx->npos++] = (char)c;
			ctx->state = LHPS_TAG;
			break;
		case LHPS_SCOMMENT2: /* we have <!- */
			if (c == '-') {
				ctx->state = LHPS_COMMENT;
				break;
			}
			/* it can't be an element tag with - in it */
			ctx->state = LHPS_BAD_TAG;
			break;
		case LHPS_COMMENT:
			/* sanity */
			if (ctx->npos >= LHP_STRING_CHUNK - 4) {
				ps->cb(ctx, LHPCB_COMMENT);
				ctx->npos = 0;
			}
			if (c == '-') {
				ctx->state = LHPS_ECOMMENT1;
				break;
			}

			/* collect the comment */
			ctx->buf[ctx->npos++] = (char)c;
			/* sanity */
			if (ctx->npos >= LHP_STRING_CHUNK - 4) {
				ps->cb(ctx, LHPCB_COMMENT);
				ctx->npos = 0;
			}

			break;
		case LHPS_ECOMMENT1:
			if (c == '-') {
				ctx->state = LHPS_ECOMMENT2;
				break;
			}
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = (char)c;
			ctx->state = LHPS_COMMENT;
			break;
		case LHPS_ECOMMENT2:
			if (c == '>') {
				if (ctx->npos) {
					ps->cb(ctx, LHPCB_COMMENT);
					ctx->npos = 0;
				}
				ctx->state = LHPS_OUTER;
				break;
			}
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = (char)c;
			ctx->state = LHPS_COMMENT;
			break;

			/*
			 * CSS parser
			 */

		case LCSPS_CSS_OUTER:
			/* comments... */
			ctx->state_css_comm = LCSPS_CSS_OUTER;

			if (c == '<') {
				ctx->state = LCSPS_CSS_OUTER_TAG1;
				ctx->u.f.first = 1;
				break;
			}
			if (c == '/') {
				ctx->state = LCSPS_CCOM_S1;
				break;
			}

			if (c == '{') { /* open stanza */
				struct lws_tokenize ts;

				/* create the stanza object */

				ctx->stz = lwsac_use_zero(&ctx->cssac,
							  sizeof(*ctx->stz),
							  LHP_AC_GRANULE);
				if (!ctx->stz)
					goto oom;

				/* attach names to it */

				memset(&ts, 0, sizeof(ts));
				ts.start = ctx->buf;
				ts.len = (size_t)ctx->npos;
				ts.flags = LWS_TOKENIZE_F_COMMA_SEP_LIST |
						LWS_TOKENIZE_F_DOT_NONTERM |
						LWS_TOKENIZE_F_MINUS_NONTERM;

				do {
					ts.e = (int8_t)lws_tokenize(&ts);
					if (ts.e == LWS_TOKZE_TOKEN) {
						lcsp_names_t *na = lwsac_use_zero(
							&ctx->cssac,
							sizeof(*na) +
							ts.token_len + 1,
							LHP_AC_GRANULE);
						if (!na)
							goto oom;

						//lwsl_notice("%s: CSS name %.*s\n",
						//	__func__,
						//	(int)ts.token_len, ts.token);

						na->name_len = ts.token_len;
						memcpy(&na[1], ts.token, ts.token_len);
						((char *)(&na[1]))[ts.token_len] = '\0';
						lws_dll2_add_tail(&na->list, &ctx->stz->names);
					}

				} while (ts.e > 0);


				/* list this stanza in our lhp context CSS */

				lws_dll2_add_tail(&ctx->stz->list, &ctx->css);

				ctx->buf[ctx->npos] = '\0';

				ctx->npos = 0;
				ctx->state = LCSPS_CSS_STANZA;
				ctx->cssval_state = 0;
				ctx->css_state = 0;
				ctx->u.f.arg = 0;
				ctx->u.f.integer = 0;
				ctx->u.f.color = 0;
				break;
			}

			/* otherwise let's collect the name pieces */

			if (ctx->npos >= LHP_STRING_CHUNK) {
				lwsl_err("%s: css lhs too long\n", __func__);
				return LWS_SRET_FATAL;
			}

			if (!hspace(c))
				ctx->buf[ctx->npos++] = (char)c;

			break;

		case LCSPS_CSS_STANZA:
			ctx->state_css_comm = LCSPS_CSS_STANZA;
			if (c == '}') {
				ctx->state = LCSPS_CSS_OUTER;

				ctx->u.f.arg = 0;

				if (ctx->u.f.color) {
					lcsp_append_cssval_color(ctx);
					ctx->npos = 0;
					break;
				}
				if (ctx->u.f.integer) {/* x: 123} */
					if (lcsp_append_cssval_int(ctx))
						goto oom;

					ctx->u.f.integer = 0;
					ctx->npos = 0;
					break;
				}
				//lwsl_notice("close curly cssval_state %d\n", ctx->cssval_state);
				if (ctx->cssval_state || ctx->npos) {
					is_term = 1;
				} else {
					ctx->npos = 0;
					break;
				}
			}
			if (c == '/') {
				ctx->state = LCSPS_CCOM_S1;
				break;
			}

			if (ctx->u.f.arg) {
				/* we're on the value side of prop: value */

				if (c == ';') {
					/* resync after unknown prop: restart with
					 * whatever is after the ';' */
					ctx->css_state = 0;
					ctx->u.f.arg = 0;

					if (ctx->u.f.color) {
						lcsp_append_cssval_color(ctx);
						ctx->npos = 0;
						break;
					}

					if (ctx->u.f.integer) { /* x: 123; */
						if (lcsp_append_cssval_int(ctx))
							goto oom;
						ctx->u.f.integer = 0;
						ctx->npos = 0;
					}

					if (ctx->cssval_state) {
						is_term = 1;
					} else {
						ctx->npos = 0;
						break;
					}
				}

				if (ctx->cssval_state == (int16_t)-1 &&
				    hspace(c)) {
					/* resync after unknown prop: restart
					 * with whatever is after the ';' */
					ctx->cssval_state = 0;
					break;
				}

				if (ctx->u.f.color &&
					((c >= '0' && c <= '9') ||
					(c >= 'a' && c <= 'f') ||
					(c >= 'A' && c <= 'F'))) {
					ctx->temp = (uint32_t)(((int)ctx->temp << 4) |
						((c <= '9') ? c - '0' :
							(c >= 'a') ? 10 + (c - 'a') :
								10 + (c - 'A')));
					ctx->temp_count++;
					break;
				}

				if (!ctx->u.f.integer && hspace(c))
					break;

				if (!ctx->cssval_state && !ctx->u.f.integer &&
				    ((c >= '0' && c <= '9') || c == '.')) {
					// lwsl_notice("integer...\n");
					lws_fx_set(ctx->tf, 0, 0);
					ctx->u.f.integer = LHP_CSS_PROPVAL_INT_WHOLE;
					ctx->temp = LWS_FX_FRACTION_MSD / 10;
					ctx->unit = LCSP_UNIT_NONE;
				}

				if (ctx->u.f.integer) {
					if (c == '.' &&
					    ctx->u.f.integer <= LHP_CSS_PROPVAL_INT_FRAC) {
						ctx->u.f.integer = LHP_CSS_PROPVAL_INT_FRAC;
						break;
					}

					if (ctx->u.f.integer < LHP_CSS_PROPVAL_INT_UNIT &&
					    c >= '0' && c <= '9') {
						if (ctx->u.f.integer == LHP_CSS_PROPVAL_INT_WHOLE)
							ctx->tf.whole =
								(ctx->tf.whole * 10) +
								(c - '0');
						else {
							if (ctx->temp) {
								ctx->tf.frac += (int32_t)ctx->temp * (c - '0');
								ctx->temp /= 10;
							}
						}
						break;
					}
					if (hspace(c)) {
						ctx->u.f.integer = 0;
						break;
					}

					if (ctx->u.f.integer != LHP_CSS_PROPVAL_INT_UNIT) {
						ctx->u.f.integer = LHP_CSS_PROPVAL_INT_UNIT;
						ctx->npos = 0;
					}

					if (c == '%') {
						ctx->unit = LCSP_UNIT_LENGTH_PERCENT;
						goto issue_post;
					}

					if (ctx->npos < 4 && !ctx->unit) {

						ctx->buf[ctx->npos++] = (char)c;
						ctx->buf[ctx->npos] = '\0';

						if (ctx->npos == 2) {
							if (!strcmp(ctx->buf, "em"))
								ctx->unit = LCSP_UNIT_LENGTH_EM;
							if (!strcmp(ctx->buf, "ex"))
								ctx->unit = LCSP_UNIT_LENGTH_EX;
							if (!strcmp(ctx->buf, "in"))
								ctx->unit = LCSP_UNIT_LENGTH_IN;
							if (!strcmp(ctx->buf, "cm"))
								ctx->unit = LCSP_UNIT_LENGTH_CM;
							if (!strcmp(ctx->buf, "mm"))
								ctx->unit = LCSP_UNIT_LENGTH_MM;
							if (!strcmp(ctx->buf, "pt"))
								ctx->unit = LCSP_UNIT_LENGTH_PT;
							if (!strcmp(ctx->buf, "pc"))
								ctx->unit = LCSP_UNIT_LENGTH_PC;
							if (!strcmp(ctx->buf, "px"))
								ctx->unit = LCSP_UNIT_LENGTH_PX;
						}
						if (ctx->npos == 3) {
							if (!strcmp(ctx->buf, "rem"))
								ctx->unit = LCSP_UNIT_LENGTH_REM;
							if (!strcmp(ctx->buf, "deg"))
								ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
							if (!strcmp(ctx->buf, "rad"))
								ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
						}
						if (ctx->npos == 4) {
							if (!strcmp(ctx->buf, "grad"))
								ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
						}

issue_post:
						if (ctx->unit) {
							if (lcsp_append_cssval_int(ctx))
								goto oom;
							ctx->u.f.integer = 0;
							ctx->npos = 0;
						}
						break;
					}
				}

				if (c == '#') {
					ctx->temp = 0;
					ctx->temp_count = 0;
					ctx->u.f.color = 1;
					break;
				}


				if (!is_term) {
					if (ctx->npos >= LHP_STRING_CHUNK) {
						lwsl_err("%s: prop value string too long\n", __func__);
						goto oom;
					}

					ctx->buf[ctx->npos++] = (char)c;
				}

				/* well-known property value strings */

				if (ctx->npos == 5 && !memcmp(ctx->buf, "calc(", 5)) {
					int nested = 1;

					while (*len) {
						c = *(*buf)++;
						(*len)--;

						if (c == '(')
							nested++;
						if (c == ')') {
							nested--;
							if (!nested) {
								lcsp_atr_t *atr = lwsac_use_zero(&ctx->cssac,
										sizeof(*atr) +
										(unsigned int)ctx->npos + 1 - 5,
										LHP_AC_GRANULE);
								if (!atr)
									goto oom;

								atr->unit = LCSP_UNIT_CALC;
								atr->value_len = (size_t)ctx->npos - 5;
								memcpy(&atr[1], ctx->buf + 5, atr->value_len);
								((char *)&atr[1])[atr->value_len] = '\0';

								lws_dll2_add_tail(&atr->list, &ctx->def->atrs);
								ctx->npos = 0;
								break;
							}
						}
						if (ctx->npos >= LHP_STRING_CHUNK) {
							lwsl_err("%s: calc string too long\n", __func__);
							goto oom;
						}
						ctx->buf[ctx->npos++] = (char)c;
					}
					break;
				}

				switch(lws_minilex_parse(css_propconst_lextable,
							 &ctx->cssval_state,
							 c, &ctx->propval)) {
				case LWS_MINILEX_FAIL:
					/*
					 * We don't know this property value, keep
					 * eating until we can resync at next
					 * ';', or we hit the '}'.
					 */
					//lwsl_notice("minilex val fail %c\n", c);
					/* fallthru */
				case LWS_MINILEX_CONTINUE:
					if (!ctx->u.f.arg) { /* term */
						if (ctx->npos)
							lcsp_append_cssval_string(ctx);
						ctx->npos = 0;
					}
					break;
				case LWS_MINILEX_MATCH:
					/* we have an unambiguous well-known
					 * property value match */
					//lwsl_notice("propval %d\n", ctx->propval);
					{
						lcsp_atr_t *atr = lwsac_use_zero(
							  &ctx->cssac,
							  sizeof(*atr),
							  LHP_AC_GRANULE);
						if (!atr)
							goto oom;
						/* add this prop value atr to the def */

						atr->propval = ctx->propval;

						lws_dll2_add_tail(&atr->list,
								&ctx->def->atrs);

						ctx->npos = 0;
					}
					ctx->cssval_state = 0;
					break;
				}

				break;
			}

			/* we're trying to figure out the well-known prop name
			 * The matches all have the : attached, so they will
			 * match unambiguously */

			if (ctx->css_state == (int16_t)-1 && c == ';') {
				/* resync after unknown prop: restart with
				 * whatever is after the ';' */
				ctx->css_state = 0;
				break;
			}

			if (hspace(c)) {
				ctx->u.f.color = 0;
				if (ctx->css_state) /* space after the start
						     * means no match */
					ctx->css_state = (int16_t)-1;
				break;
			}

			/*
			 * Check if it looks like a CSS variable definition, eg
			 * --color-bg:
			 */

			if (c == '-' && !ctx->npos && !ctx->css_state) {
				ctx->css_state = -2; /* var name */
				ctx->buf[ctx->npos++] = (char)c;
				break;
			}

			if (ctx->css_state == -2) {
				if (c != ':') {
					if (ctx->npos < 64)
						ctx->buf[ctx->npos++] = (char)c;
					break;
				}

				/* it is a var definition */

				ctx->def = lwsac_use_zero(&ctx->cssac,
						  sizeof(*ctx->def),
						  LHP_AC_GRANULE);
				if (!ctx->def)
					goto oom;
				ctx->def->prop = LCSP_PROP__COUNT; /* var definition */
				lws_dll2_add_tail(&ctx->def->list, &ctx->stz->defs);

				{
					lhp_css_var_t *v = lwsac_use_zero(&ctx->cssac,
							sizeof(*v) + (unsigned int)ctx->npos + 1,
							LHP_AC_GRANULE);
					if (!v)
						goto oom;

					v->name_len = (size_t)ctx->npos;
					v->def = ctx->def;
					memcpy(&v[1], ctx->buf, v->name_len);
					*((uint8_t *)&v[1] + v->name_len) = '\0';
					lws_dll2_add_tail(&v->list, &ctx->css_vars);
				}

				ctx->u.f.arg = 1;
				ctx->npos = 0;
				ctx->cssval_state = 0;
				ctx->css_state = 0;
				break;
			}

			switch(lws_minilex_parse(css_lextable, &ctx->css_state,
						 c, &ctx->prop)) {
			case LWS_MINILEX_FAIL:
				/*
				 * We don't know this property, keep eating
				 * until we can resync at next ';', or we hit
				 * the '}'.
				 */
				break;
			case LWS_MINILEX_CONTINUE:
				break;
			case LWS_MINILEX_MATCH:
				/* we have an unambiguous match, now we are
				 * doing the property args */
				ctx->def = lwsac_use_zero(&ctx->cssac,
						  sizeof(*ctx->def),
						  LHP_AC_GRANULE);
				if (!ctx->def)
					goto oom;
				ctx->def->prop = (lcsp_props_t)ctx->prop;
				/* add this prop def to the stanza */
				lws_dll2_add_tail(&ctx->def->list, &ctx->stz->defs);
				ctx->u.f.arg = 1;
				ctx->npos = 0;
				ctx->cssval_state = 0;
				//lwsl_notice("%s: minilex prop match %d\n", __func__, ctx->prop);
				break;
			}
			break;

		case LCSPS_CCOM_S1:
			if (c == '*') {
				ctx->state = LCSPS_CCOM;
				break;
			}
			ctx->state = ctx->state_css_comm;
			break;

		case LCSPS_CSS_OUTER_TAG1:
			/*
			 * We could see <!-- or perhaps </script> if we are
			 * inside a <script> section
			 */
			if (c == '!' && ctx->u.f.first) {
				ctx->state = LCSPS_SCOMMENT1;
				ctx->u.f.first = 0;
				break;
			}
			if (ctx->state_css_comm == LCSPS_CSS_OUTER &&
			    c == '/' && ctx->u.f.first) {
				r = ctx->await_css_done;
				// lwsl_warn("leaving css for tag");
				ctx->u.s = 0;

				ctx->tag = NULL;
				ctx->tag_len = 0;
				ctx->npos = 0;
				ctx->state = LHPS_TAG;
				ctx->await_css_done = 0;
				ctx->finish_css = 0;
				if (r)
					return LWS_SRET_AWAIT_RETRY;
				ctx->u.f.closing = 1;
				break;
			}
			if (hspace(c))
				break;
			break;
		case LCSPS_CSS_NAMES:
			break;
		case LCSPS_CSS_DEF_NAME:
			break;
		case LCSPS_CSS_DEF_VALUE:
			break;

		case LCSPS_SCOMMENT1:
			if (c == '-') {
				ctx->state = LCSPS_SCOMMENT2;
				break;
			}
			/* we saw <! and then not - */
			ctx->state = ctx->state_css_comm;
			break;
		case LCSPS_SCOMMENT2:
			if (c == '-') {
				ctx->state = LCSPS_COMMENT;
				break;
			}
			/* we saw <!- and then not - */
			ctx->state = ctx->state_css_comm;
			break;

		case LCSPS_CCOM:
			/* fallthru */
		case LCSPS_COMMENT:
			/* sanity */
			if (ctx->npos >= LHP_STRING_CHUNK - 4) {
				ps->cb(ctx, LHPCB_COMMENT);
				ctx->npos = 0;
			}
			if (ctx->state == LCSPS_COMMENT && c == '-') {
				ctx->state = LCSPS_ECOMMENT1;
				break;
			}
			if (ctx->state == LCSPS_CCOM && c == '*') {
				ctx->state = LCSPS_CCOM_E1;
				break;
			}

			/* collect the comment */
			ctx->buf[ctx->npos++] = (char)c;
			/* sanity */
			if (ctx->npos >= LHP_STRING_CHUNK - 4) {
				ps->cb(ctx, LHPCB_COMMENT);
				ctx->npos = 0;
			}

			break;

		case LCSPS_CCOM_E1:
			if (c == '/') {
				if (ctx->npos) {
					ps->cb(ctx, LHPCB_COMMENT);
					ctx->npos = 0;
				}
				ctx->state = ctx->state_css_comm;
				break;
			}
			ctx->state = LCSPS_CCOM;
			break;

		case LCSPS_ECOMMENT1:
			if (c == '-') {
				ctx->state = LCSPS_ECOMMENT2;
				break;
			}
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = (char)c;
			ctx->state = LCSPS_COMMENT;
			break;

		case LCSPS_ECOMMENT2:
			if (c == '>') {
				if (ctx->npos) {
					ps->cb(ctx, LHPCB_COMMENT);
					ctx->npos = 0;
				}
				ctx->state = ctx->state_css_comm;
				break;
			}
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = '-';
			ctx->buf[ctx->npos++] = (char)c;
			ctx->state = LCSPS_COMMENT;
			break;

		case LHPS_SCRIPT:
			if (c == '<') {
				ctx->state = LHPS_SCRIPT_TAG1;
				ctx->u.f.first = 1;
				break;
			}
			break;

		case LHPS_SCRIPT_TAG1:
			if (c == '/' && ctx->u.f.first) {
				ctx->u.s = 0;

				ctx->tag = NULL;
				ctx->tag_len = 0;
				ctx->npos = 0;
				ctx->state = LHPS_TAG;
				ctx->await_css_done = 0;
				ctx->finish_css = 0;
				ctx->u.f.closing = 1;
				break;
			}
			if (hspace(c))
				break;

			ctx->state = LHPS_SCRIPT;
			break;

		}
		if (!*len && ctx->is_css && ctx->await_css_done && ctx->finish_css) {
			r = ctx->await_css_done;
			ctx->u.s = 0;
			ctx->tag = NULL;
			ctx->tag_len = 0;
			ctx->npos = 0;
			ctx->state = LHPS_TAG;
			ctx->await_css_done = 0;
			ctx->finish_css = 0;
			if (r)
				return LWS_SRET_AWAIT_RETRY;
			ctx->u.f.closing = 1;
		}
	}

	if (!ctx->u.f.default_css && ctx->flags & LHP_FLAG_DOCUMENT_END) {
		/*
		 * if we're holding on to anything in case more comes, no more
		 * is coming and we should flush it.
		 */

		if (ctx->state == LHPS_OUTER && ctx->npos) {
			if (ctx->in_body && (ctx->npos != 1 || ctx->buf[0] != ' '))
				ps->cb(ctx, LHPCB_CONTENT);
			ctx->npos = 0;
		}

		ps->cb(ctx, LHPCB_COMPLETE);
		return LWS_SRET_NO_FURTHER_OUT;
	}

	return LWS_SRET_WANT_INPUT;

oom:
	lwsl_err("%s: OOM\n", __func__);
	ps->cb(ctx, LHPCB_FAILED);
	return LWS_SRET_FATAL;
}

/*
 * Query the css cascade active at this html parsing point for a list of active
 * css attributes belonging to a particular property, accounting for cascading
 * overriding inside the list.
 */

const lcsp_atr_t *
lws_css_cascade_get_prop_atr(lhp_ctx_t *ctx, lcsp_props_t prop)
{
	lcsp_atr_ptr_t *ap;

	lws_dll2_owner_clear(&ctx->active_atr);
	lwsac_free(&ctx->propatrac);

	/*
	 * Let's go through the active stanzas looking for defs that relate to
	 * the property we care about
	 */

	lws_start_foreach_dll(struct lws_dll2 *, q, ctx->active_stanzas.head) {
		lcsp_stanza_ptr_t *pstz = lws_container_of(q, lcsp_stanza_ptr_t,
							   list);

		/* each def entry in the stanza in turn */

		lws_start_foreach_dll(struct lws_dll2 *, p, pstz->stz->defs.head) {
			lcsp_defs_t *def = lws_container_of(p, lcsp_defs_t, list);

			if (def->prop == prop) {

				lws_start_foreach_dll(struct lws_dll2 *, z,
						      def->atrs.head) {
					lcsp_atr_ptr_t *patr = lwsac_use_zero(
							&ctx->propatrac,
							sizeof(*patr),
							LHP_AC_GRANULE);
					if (!patr)
						return NULL;

					patr->atr = lws_container_of(z,
							lcsp_atr_t, list);

					lws_dll2_add_tail(&patr->list,
							  &ctx->active_atr);
				} lws_end_foreach_dll(z);
			}

		} lws_end_foreach_dll(p);

	} lws_end_foreach_dll(q);

	if (!ctx->active_atr.count)
		return NULL;

	ap = lws_container_of(ctx->active_atr.tail, lcsp_atr_ptr_t, list);

	return ap->atr;
}

lhp_pstack_t *
lws_css_get_parent_block(lhp_ctx_t *ctx, lhp_pstack_t *ps)
{
	do {

		if (!ps->list.prev)
			return NULL;

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

		if (ps->dlo)
			return ps;

	} while(1);
}

const char *
lws_css_pstack_name(lhp_pstack_t *ps)
{
	lhp_atr_t *a;

	if (!ps)
		return "(null ps)";

	if (!ps->atr.head)
		return "no-name";

	a = lws_container_of(ps->atr.head, lhp_atr_t, list);

	return (const char *)&a[1];
}

/*
 * Some properties have an impied affinity for an axis, eg, left: references
 * the parent width if it has a % expression
 */

int
lhp_prop_axis(const lcsp_atr_t *a)
{
	const lcsp_defs_t *d;

	if (!a->list.owner)
		return LWS_LHPREF_NONE;

	d = lws_container_of(a->list.owner, lcsp_defs_t, atrs);

	switch (d->prop) {
	/* referenced to height */
	case LCSP_PROP_BORDER_TOP_WIDTH:
	case LCSP_PROP_BORDER_BOTTOM_WIDTH:
	case LCSP_PROP_HEIGHT:
	case LCSP_PROP_TOP:
	case LCSP_PROP_BOTTOM:
	case LCSP_PROP_MARGIN_TOP:
	case LCSP_PROP_MARGIN_BOTTOM:
	case LCSP_PROP_PADDING_TOP:
	case LCSP_PROP_PADDING_BOTTOM:
	case LCSP_PROP_MAX_HEIGHT:
	case LCSP_PROP_MIN_HEIGHT:
		//lwsl_notice("%s: %d: LWS_LHPREF_HEIGHT\n", __func__, d->prop);
		return LWS_LHPREF_HEIGHT;

	/* referenced to width */
	case LCSP_PROP_BORDER_LEFT_WIDTH:
	case LCSP_PROP_BORDER_RIGHT_WIDTH:
	case LCSP_PROP_WHITE_SPACE:
	case LCSP_PROP_WIDTH:
	case LCSP_PROP_LEFT:
	case LCSP_PROP_RIGHT:
	case LCSP_PROP_MARGIN_LEFT:
	case LCSP_PROP_MARGIN_RIGHT:
	case LCSP_PROP_PADDING_LEFT:
	case LCSP_PROP_PADDING_RIGHT:
	case LCSP_PROP_MAX_WIDTH:
	case LCSP_PROP_MIN_WIDTH:
		//lwsl_notice("%s: %d: LWS_LHPREF_WIDTH\n", __func__, d->prop);
		return LWS_LHPREF_WIDTH;

	default:
		//lwsl_notice("%s: %d: LWS_LHPREF_NONE\n", __func__, d->prop);
		return LWS_LHPREF_NONE;
	}
}
