/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - tinycad file format support
 *  Copyright (C) 2022, 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022 and Entrust in 2023)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

#include <libcschem/config.h>

#include <genht/htpp.h>
#include <genht/hash.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <librnd/core/error.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/rnd_printf.h>
#include <librnd/core/rotate.h>

#include <libcschem/cnc_grp.h>
#include <libcschem/cnc_grp_child.h>
#include <libcschem/cnc_line.h>
#include <libcschem/cnc_text.h>
#include <libcschem/cnc_poly.h>
#include <libcschem/cnc_pen.h>
#include <libcschem/operation.h>
#include <libcschem/util_loclib.h>
#include <libcschem/vtoid.h>
#include <plugins/lib_alien/read_helper.h>
#include <plugins/lib_alien/read_postproc.h>

#include "io_tinycad_conf.h"

extern conf_io_tinycad_t io_tinycad_conf;


#include "read.h"

#define error(node, args) \
	do { \
		if (!ctx->silent) { \
			rnd_message(RND_MSG_ERROR, "tinycad parse error at %s:%ld:\n", ctx->fn, (long)(node)->line); \
			rnd_msg_error args; \
		} \
	} while(0)

typedef struct read_ctx_s {
	const char *fn;
	xmlDoc *doc;
	xmlNode *root, *next_sheet_nd;
	csch_alien_read_ctx_t alien;
	csch_cgrp_t *loclib_sym; /* 'symbol' master group in sheet->indirect */
	csch_cgrp_t *symdef; /* symdef we are reading at the moment */
	double symdef_dx, symdef_dy;
	double symdef_dx_power, symdef_dy_power;
	double symdef_attr_dx, symdef_attr_dy;
	int parent_rot; /* used in some contexts to remember parent rotation while calling parsers on the children */
	htpp_t sym2xml;  /* key: (csch_cgrp_t *); value: (xmlNode *) */
	unsigned silent:1;
	unsigned symdef_dxy_power_valid:1; /* whether symdef_d[xy]_power got loaded */
	long sheetno;
} read_ctx_t;

#define PROP(node, propname) \
	((const char *)xmlGetProp((node), (const xmlChar *)(propname)))

/* Return 1 if nd->name matches expected */
static int node_name_eq(xmlNode *nd, const char *expected)
{
	return (xmlStrcmp(nd->name, (const unsigned char *)expected) == 0);
}

#define PINATTR_SLOT     "io_tinycad_slot"
#define PINATTR_POWER    "io_tinycad_power"
#define SYMATTR_DX_POWER "io_tinycad_dx_power"
#define SYMATTR_DY_POWER "io_tinycad_dy_power"
#define SYMATTR_SYM_DXBB "io_tinycad_dx_bbox"
#define SYMATTR_SYM_DYBB "io_tinycad_dy_bbox"

static int parse_coords(read_ctx_t *ctx, xmlNode *nd, const char *str, double *x, double *y)
{
	char *end;
	*x = strtod(str, &end);
	if (*end != ',') {
		error(nd, ("Missing comma in coords\n"));
		return -1;
	}
	*y = strtod(end+1, &end);
	if (*end != '\0') {
		error(nd, ("Invalid y coord (need numeric)\n"));
		return -1;
	}
	return 0;
}

static const char *parse_text(read_ctx_t *ctx, xmlNode *nd, int mandatory)
{
	if (nd->children == NULL) {
		if (mandatory)
			error(nd, ("Missing text child\n"));
		return NULL;
	}
	return (const char *)nd->children->content;
}

static int parse_bool(read_ctx_t *ctx, xmlNode *nd, const char *inp)
{
	if (inp == NULL) return 0;
	if ((inp[0] == '1') && (inp[1] == '\0')) return 1;
	if ((inp[0] == '0') && (inp[1] == '\0')) return 0;
	error(nd, ("Invalid boolean value %s; expected 0 or 1\n", inp));
	return -1;
}

/* Parse integer, overwrite dst if inp is not NULL */
static int parse_long(read_ctx_t *ctx, xmlNode *nd, const char *inp, long *dst, int mandatory)
{
	char *end;
	long tmp;

	if (inp == NULL) {
		if (mandatory) {
			error(nd, ("missing integer data\n"));
			return -1;
		}
		return 0;
	}

	tmp = strtol(inp, &end, 10);
	if (*end != '\0') {
		if (mandatory)
			error(nd, ("Invalid integer value '%s' (should be decimal)\n", inp));
		return -1;
	}

	*dst = tmp;
	return 0;
}

/* Parse decimal, overwrite dst if inp is not NULL */
static int parse_double(read_ctx_t *ctx, xmlNode *nd, const char *inp, double *dst, int mandatory)
{
	char *end;
	double tmp;

	if (inp == NULL) {
		if (mandatory) {
			error(nd, ("missing decimal data\n"));
			return -1;
		}
		return 0;
	}

	tmp = strtod(inp, &end);
	if (*end != '\0') {
		if (mandatory)
			error(nd, ("Invalid decimal value '%s'\n", inp));
		return -1;
	}

	*dst = tmp;
	return 0;
}

static double rot2deg(read_ctx_t *ctx, xmlNode *nd, int rot)
{
	switch(rot) {
		case 0: return 0;
		case 1: return 180;
		case 2: return 270;
		case 3: return 90;
	}
	error(nd, ("Invalid rotation value %d: should be 0..3\n", rot));
	return rot2deg(ctx, nd, rot % 4);
}

static int dir2dxy(read_ctx_t *ctx, xmlNode *root, int dir, double *dx, double *dy, double len)
{
	switch(dir) {
		case 0: *dy = -len; break;
		case 1: *dy = len; break;
		case 2: *dx = -len; break;
		case 3: *dx = +len; break;
		default:
			error(root, ("Invalid direction %d: must be 0..3\n", dir));
			return -1;
	}
	return 0;
}

/* Calculate sym placement; dx and dy are multiplier (0 or +-1) for moving
   by (non-floater) bbox w and h; swbb is set to 1 if bbox w and h need to
   be swapped (if used before rot+mir applied, which is not the case, so
   it is safe to ignore swbb). */
static void rot2sym(read_ctx_t *ctx, xmlNode *nd, int rotin, double *rot_out, int *mirx, int *miry, int *dx, int *dy, int *swbb)
{
	switch(rotin) {
		case 0: *mirx = 0; *miry = 0; *rot_out = 0;   *dx = 0;  *dy = 0; *swbb=0; return;
		case 1: *mirx = 0; *miry = 1; *rot_out = 0;   *dx = 0;  *dy = 1; *swbb=0; return;
		case 2: *mirx = 0; *miry = 1; *rot_out = 90;  *dx = 0;  *dy = 0; *swbb=0; return;
		case 3: *mirx = 0; *miry = 0; *rot_out = -90; *dx = -1; *dy = 0; *swbb=1; return;

		case 4: *mirx = 1; *miry = 0; *rot_out = 0;   *dx = -1; *dy = 0; *swbb=0; return;
		case 5: *mirx = 1; *miry = 1; *rot_out = 0;   *dx = -1; *dy = 1; *swbb=0; return;
		case 6: *mirx = 1; *miry = 1; *rot_out = -90; *dx = 0;  *dy = 1; *swbb=1; return;
		case 7: *mirx = 1; *miry = 0; *rot_out = 90;  *dx = -1; *dy = 1; *swbb=1; return;
	}
	*mirx = *miry = 0;
	*rot_out = 0;
	*dx = *dy = 0;
	*swbb = 0;
	error(nd, ("Invalid rotation value %d: should be 0..7\n", rotin));
}


typedef struct {
	const char *nodename;
	int (*cb)(read_ctx_t *ctx, void *dst, xmlNode *root);
} parser_t;

static int parse_all(read_ctx_t *ctx, void *dst, xmlNode *root, const parser_t *parsers)
{
	const parser_t *p;
	xmlNode *n;
	int res;

	for(n = root->children; n != NULL; n = n->next) {
		for(p = parsers; p->nodename != NULL; p++) {
			if (xmlStrcmp(n->name, (const unsigned char *)p->nodename) == 0) {
				res = p->cb(ctx, dst, n);
				if (res != 0)
					return res;
			}
		}
	}

	return 0;
}

/* Convert line 'style' proeprty to a sheet-decor pen, allocating new pen
   as needed */
static int parse_pen_style2sheet_decor(read_ctx_t *ctx, xmlNode *root, char penname[32])
{
	const char *sstyle = PROP(root, "style");
	long style = 2;

	if (parse_long(ctx, root, sstyle, &style, 0) != 0)
		return -1;

	strcpy(penname, "sheet-decor");
	if (style > 2) {
		csch_cpen_t *pen, *dpen;
		sprintf(penname+11, "-%ld", style);
		pen = csch_pen_get(ctx->alien.sheet, &ctx->alien.sheet->direct, penname);
		if (pen == NULL) {
			dpen = csch_pen_get(ctx->alien.sheet, &ctx->alien.sheet->direct, "sheet-decor");
			if (dpen != NULL)
				pen = csch_pen_dup2(ctx->alien.sheet, &ctx->alien.sheet->direct, dpen, penname);
			else
				pen = csch_pen_alloc(ctx->alien.sheet, &ctx->alien.sheet->direct, penname);
			pen->size = (style - 1) * 125;
		}
	}

	return 0;
}


static int parse_name(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	ctx->alien.sheet->hidlib.loadname = rnd_strdup(parse_text(ctx, root, 0));
	return 0;
}

static int parse_detail_size(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *sw = PROP(root, "width"), *sh = PROP(root, "height");
	char tmp[64];
	long lw, lh;
	csch_coord_t w, h;
	csch_source_arg_t *src;
	csch_sheet_t *sheet = dst;

	if (parse_long(ctx, root, sw, &lw, 1) != 0)    return -1;
	if (parse_long(ctx, root, sh, &lh, 1) != 0)    return -1;

	w = csch_alien_coord(&ctx->alien, lw)/5;
	h = csch_alien_coord(&ctx->alien, lh)/5;

	sprintf(tmp, "%ld", w);
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sheet->direct.attr, CSCH_ATP_USER_DEFAULT, "drawing_min_width", tmp, src, NULL);

	sprintf(tmp, "%ld", h);
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sheet->direct.attr, CSCH_ATP_USER_DEFAULT, "drawing_min_height", tmp, src, NULL);

	ctx->alien.oy = lh/5;
	return 0;
}

static int parse_detail_attr(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *key, *val;
	csch_source_arg_t *src;
	csch_sheet_t *sheet = dst;

	switch(root->name[0]) {
		case 'T': key = "title"; break;
		case 'A': key = "maintainer"; break;
		case 'S': key = "page"; break;
		default: return 0; /* shouldn't get here: parse_all ran strcmp() on the name */
	}

	val = parse_text(ctx, root, 0);
	if (val == NULL)
		val = "";

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sheet->direct.attr, CSCH_ATP_USER_DEFAULT, key, val, src, NULL);
	return 0;
}


static int parse_details(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	static const parser_t parsers[] = {
		{"Size", parse_detail_size},
		{"TITLE", parse_detail_attr},
		{"AUTHOR", parse_detail_attr},
		{"SHEETS", parse_detail_attr},
		/*{"SHOWS", parse_detail_...}, this tells whether the titlebox is shown */
		{NULL, NULL}
	};

	return parse_all(ctx, dst, root, parsers);
}


/*** symdef and sym ***/

static int parse_symdef_ref(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	char *ref;
	csch_source_arg_t *src;

	if (root->children == NULL) {
		error(root, ("Invalid symdef ref name: empty subtree, no text node\n"));
		return -1;
	}
	ref = (char *)root->children->content;
	if ((ref == NULL) || (*ref == '\0')) {
		error(root, ("Invalid symdef ref name: empty string\n"));
		return -1;
	}

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&ctx->symdef->attr, CSCH_ATP_USER_DEFAULT, "name", ref, src, NULL);

	return 0;
}

static int parse_symdef_field(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *key = NULL, *val = NULL;
	double x = 0, y = 0;
	csch_source_arg_t *src;
	xmlNode *n;

	for(n = root->children; n != NULL; n = n->next) {
		if (node_name_eq(n, "NAME"))  key = parse_text(ctx, n, 1);
		if (node_name_eq(n, "VALUE")) val = parse_text(ctx, n, 0);
		if (node_name_eq(n, "POS")) parse_coords(ctx, n, parse_text(ctx, n, 0), &x, &y);
	}

	if ((key == NULL) || (val == NULL) || (*val == '\0'))
		return 0; /* don't create empty attributes */

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&ctx->symdef->attr, CSCH_ATP_USER_DEFAULT, key, val, src, NULL);
	return 0;
}

static int parse_symdef_ref_point(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *pos = PROP(root, "pos"), *spower = PROP(root, "power");
	double x, y;

	if (parse_coords(ctx, root, pos, &x, &y) != 0)
		return -1;


	if ((spower != NULL) && (spower[0] == '0')) {
		/* power=0 is our true origin */
		ctx->symdef_dx = -x;
		ctx->symdef_dy = -y;
	}
	else {
		/* remember the power=1 variant for later */
		ctx->symdef_dx_power = -x;
		ctx->symdef_dy_power = -y;
		ctx->symdef_dxy_power_valid = 1;
	}
	return 0;
}

static int parse_symdef_pin(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *spos = PROP(root, "pos"), *sdir = PROP(root, "direction");
	const char *slen = PROP(root, "length"), *snum = PROP(root, "number");
	const char *sshow = PROP(root, "show"), *swhich = PROP(root, "which");
	const char *part = PROP(root, "part"), *selec = PROP(root, "elec");
	long dir, llen, num, which, show, elec;
	double ox, oy, Dx = 0, Dy = 0, dx = 0, tdx = 0, dy = 0, shorten = 0, len;
	csch_cgrp_t *pin;
	csch_source_arg_t *src;
	const char *pinlab = NULL;
	int fonth = 3000;

	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return -1;
	if (parse_long(ctx, root, sdir, &dir, 1) != 0)      return -1;
	if (parse_long(ctx, root, slen, &llen, 1) != 0)     return -1;
	if (parse_long(ctx, root, snum, &num, 1) != 0)      return -1;
	if (parse_long(ctx, root, swhich, &which, 1) != 0)  return -1;
	if (parse_long(ctx, root, selec, &elec, 1) != 0)    return -1;
	if (parse_long(ctx, root, sshow, &show, 1) != 0)    return -1;
	pinlab = parse_text(ctx, root, 0);

	if (which != 6)
		len = (double)llen/5.0;
	else
		len = 0;

	if (dir2dxy(ctx, root, dir, &dx, &dy, 1) != 0)
		return -1;

	Dx = dx * len;
	Dy = dy * len;

	ox += ctx->symdef_dx; oy += ctx->symdef_dy;


	/* shorten pin to make room for decoration */
	if ((which == 1) || (which == 3))
		shorten = 2;

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	pin = (csch_cgrp_t *)csch_alien_mkpin_line(&ctx->alien, src, ctx->symdef, ox, oy, ox+Dx-shorten*dx, oy+Dy-shorten*dy);

	/* decoration */
	if ((which == 1) || (which == 3)) /* circle on the inner end */
		csch_alien_mkarc(&ctx->alien, pin, +ox+Dx-dx*1, -oy-Dy+dy*1, 1, 0, 360, "term-decor");
	if ((which == 2) || (which == 3)) { /* arrow beyond the inner end */
		csch_alien_mkline(&ctx->alien, pin, ox+Dx + dy*+1, +oy+Dy + dx*+1,    ox+Dx + dy*-1, +oy+Dy + dx*-1, "term-decor");
		csch_alien_mkline(&ctx->alien, pin, ox+Dx + dy*+1, +oy+Dy + dx*+1,    ox+Dx + dx*+2, +oy+Dy + dy*+2, "term-decor");
		csch_alien_mkline(&ctx->alien, pin, ox+Dx + dy*-1, +oy+Dy + dx*-1,    ox+Dx + dx*+2, +oy+Dy + dy*+2, "term-decor");
		tdx = 2;
	}
	if ((which == 6) || (elec == 6)) { /* X mark on the outer end */
		csch_alien_mkline(&ctx->alien, pin, ox + dy*+1 + dx, +oy + dx*+1 + dy,    ox + dy*-1 - dx, +oy + dx*-1 - dy, "term-decor");
		csch_alien_mkline(&ctx->alien, pin, ox + dy*+1 - dx, +oy + dx*+1 + dy,    ox + dy*-1 + dx, +oy + dx*-1 - dy, "term-decor");
	}
	if (which == 6)
		tdx = dx * (double)llen/5.0 + 2;


	/* validate and remember slotting info */
	if (part != NULL) {
		long tmp;
		src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
		csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, PINATTR_SLOT, part, src, NULL);

			/* make sure it's valid data so we can throw the error here, on the right node, not later when we use the attrib */
		if (parse_long(ctx, root, part, &tmp, 1) < 0)
			return -1;
	}

	/* labels and attributes */
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, "name", snum, src, NULL);

	/* show is a bitfield */
	if (show & 0x01) { /* show pin name (inside the box) */
		if (pinlab != NULL) {
			csch_text_t *text = (csch_text_t *)csch_alien_mktext(&ctx->alien, pin, ox+Dx+tdx, oy+Dy, "sym-decor");
			text->text = rnd_strdup(pinlab);
			switch(dir) {
				case 0: text->spec1.x += fonth/2; text->spec1.y += fonth/6; text->spec_rot = 90; break; /* sticking out downward */
				case 1: text->spec1.x += fonth/2; text->spec1.y -= fonth/6; text->spec_rot = -90; text->spec_mirx = 1; break; /* sticking out upward */
				case 2: text->spec1.x -= fonth/6; text->spec1.y -= fonth/2; text->spec_mirx = 1; break; /* sticking out to the right */
				case 3: text->spec1.x += fonth/6; text->spec1.y -= fonth/2; break; /* sticking out to the left */
			}
		}
	}


	if (show & 0x02) { /* show pin number */
		csch_text_t *text = (csch_text_t *)csch_alien_mktext(&ctx->alien, pin, ox, oy, "term-primary");
		text->text = rnd_strdup("%../a.display/name%");
		text->dyntext = 1;
		switch(dir) {
			case 0: text->spec_rot = 90; break; /* sticking out downward */
			case 1: text->spec_rot = -90; text->spec_mirx = 1; break; /* sticking out upward */
			case 2: text->spec_mirx = 1; break; /* sticking out to the right */
			case 3: break; /* sticking out to the left */
		}
	}


	if (which == 4) {
		src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
		csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, PINATTR_POWER, "1", src, NULL);
	}

	return 0;
}

static int parse_polygon(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *spos = PROP(root, "pos");
	const char *sfill = PROP(root, "fill"), *spoly = PROP(root, "polygon");
	double ox, oy;
	int fill, poly;
	long len;
	xmlNode *n;
	csch_cgrp_t *grp = dst;

	fill = parse_bool(ctx, root, sfill);
	poly = parse_bool(ctx, root, spoly);
	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)
		return -1;

TODO("TYC#3: fill is really an enum with different hatching, see ppfill.dsn");
	if (fill)
		poly = 1; /* required by the format */

	if (grp == ctx->symdef) {
		ox += ctx->symdef_dx;
		oy += ctx->symdef_dy;
	}

	/* verify and count points */
	for(n = root->children, len = 0; n != NULL; n = n->next, len++) {
		if (node_name_eq(n, "text")) {
			/* ignore */
		}
		else if (node_name_eq(n, "POINT")) {
			len++;
		}
		else{
			error(n, ("Invalid node in polygon: must be POINT, not '%s'\n", n->name));
			return -1;
		}
		
	}

	if (len == 1) {
		error(n, ("Invalid polygon: must contain more than one POINTs\n"));
		return -1;
	}

	if (fill) { /* real polygon */
		csch_chdr_t *poly = csch_alien_mkpoly(&ctx->alien, grp, "sym-decor", NULL);
		csch_cpoly_t *cp;
		double lx, ly, x, y;
		int first = 1;
		for(n = root->children; n != NULL; n = n->next) {
			if (node_name_eq(n, "POINT")) {
				const char *spos = PROP(n, "pos");

				if (parse_coords(ctx, n, spos, &x, &y) != 0)
					return -1;

				x += ox; y += oy;

				if (!first && ((x != lx) || (y != ly)))
					csch_alien_append_poly_line(&ctx->alien, poly, lx, ly, x, y);

				lx = x; ly = y;
				first = 0;
			}
		}
		cp = (csch_cpoly_t *)poly;
		cp->has_fill = 1;
		cp->hdr.fill_name = cp->hdr.stroke_name;
	}
	else { /* polyline */
		double lx, ly, fx, fy, x, y;
		int first = 1;
		for(n = root->children; n != NULL; n = n->next) {
			if (node_name_eq(n, "POINT")) {
				const char *spos = PROP(n, "pos");

				if (parse_coords(ctx, n, spos, &x, &y) != 0)
					return -1;

				x += ox; y += oy;

				if (first) {
					fx = x; fy = y;
				}
				else
					csch_alien_mkline(&ctx->alien, grp, lx, ly, x, y, "sym-decor");

				lx = x; ly = y;
				first = 0;
			}
		}
		if (poly) /* close polygon */
			csch_alien_mkline(&ctx->alien, grp, lx, ly, fx, fy, "sym-decor");
	}

	return 0;
}

static int parse_symdef_polygon(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	return parse_polygon(ctx, ctx->symdef, root);
}

static int parse_sheet_polygon(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	return parse_polygon(ctx, &ctx->alien.sheet->direct, root);
}


static int parse_rectangle(read_ctx_t *ctx, void *dst, xmlNode *root, const char *penname)
{
	const char *sa = PROP(root, "a"), *sb = PROP(root, "b");
	const char *sfill = PROP(root, "fill");
	double ax, ay, bx, by;
	csch_cgrp_t *grp = dst;
	csch_chdr_t *poly;
	long fill;

	if (parse_long(ctx, root, sfill, &fill, 1) != 0)   return -1;
	if (parse_coords(ctx, root, sa, &ax, &ay) != 0)    return -1;
	if (parse_coords(ctx, root, sb, &bx, &by) != 0)    return -1;

	if (grp == ctx->symdef) {
		ax += ctx->symdef_dx; ay += ctx->symdef_dy;
		bx += ctx->symdef_dx; by += ctx->symdef_dy;
	}

	TODO("TYC#4: fill is for different hatching, see rectfill.dsn");

	poly = csch_alien_mkpoly(&ctx->alien, ctx->symdef == NULL ? grp : ctx->symdef, penname, (fill ? penname : NULL));
	csch_alien_append_poly_line(&ctx->alien, poly, ax, ay, bx, ay);
	csch_alien_append_poly_line(&ctx->alien, poly, bx, ay, bx, by);
	csch_alien_append_poly_line(&ctx->alien, poly, bx, by, ax, by);
	csch_alien_append_poly_line(&ctx->alien, poly, ax, by, ax, ay);

	return 0;
}

static int parse_symdef_rectangle(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	return parse_rectangle(ctx, ctx->symdef, root, "sym-decor");
}

static int parse_sheet_rectangle(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	char penname[32];
	if (parse_pen_style2sheet_decor(ctx, root, penname) != 0) return -1;
	return parse_rectangle(ctx, &ctx->alien.sheet->direct, root, penname);
}

static int parse_ellipse(read_ctx_t *ctx, void *dst, xmlNode *root, const char *penname)
{
	const char *sa = PROP(root, "a"), *sb = PROP(root, "b");
	const char *sfill = PROP(root, "fill");
	double ax, ay, bx, by, cx, cy, rx, ry;
	csch_cgrp_t *grp = dst;
	long fill;

	if (parse_long(ctx, root, sfill, &fill, 1) != 0)   return -1;
	if (parse_coords(ctx, root, sa, &ax, &ay) != 0)    return -1;
	if (parse_coords(ctx, root, sb, &bx, &by) != 0)    return -1;

	if (grp == ctx->symdef) {
		ax += ctx->symdef_dx; ay += ctx->symdef_dy;
		bx += ctx->symdef_dx; by += ctx->symdef_dy;
	}

	rx = fabs(ax - bx) / 2;
	ry = fabs(ay - by) / 2;
	cx = (ax+bx)/2;
	cy = (ay+by)/2;

	csch_alien_mkearc(&ctx->alien, ctx->symdef == NULL ? grp : ctx->symdef,
		cx, cy, rx, ry, 0, 8, penname, (fill ? penname : NULL));

	return 0;
}

static int parse_sheet_noconnect(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	csch_sheet_t *sheet = dst;
	const char *pos = PROP(root, "pos");
	double x, y;
	csch_cgrp_t *sym;
	csch_alien_read_ctx_t a0 = {0};

	a0.sheet = ctx->alien.sheet;
	a0.fmt_prefix = ctx->alien.fmt_prefix;
	a0.coord_factor = ctx->alien.coord_factor;

	if (parse_coords(ctx, root, pos, &x, &y) != 0)    return -1;

	sym = csch_cgrp_alloc(dst, &sheet->direct, csch_oid_new(dst, &sheet->direct));
	if (sym == NULL) {
		error(root, ("Failed to allocate symbol for noconnect\n"));
		return -1;
	}

	sym->x = csch_alien_coord_x(&ctx->alien, x);
	sym->y = csch_alien_coord_y(&ctx->alien, y);

	csch_alien_mkline(&a0, sym, -1, -1, +1, +1, "sheet-decor");
	csch_alien_mkline(&a0, sym, +1, -1, -1, +1, "sheet-decor");

	return 0;
}

static int parse_symdef_ellipse(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	return parse_ellipse(ctx, ctx->symdef, root, "sym-decor");
}


static int parse_sheet_ellipse(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	char penname[32];
	if (parse_pen_style2sheet_decor(ctx, root, penname) != 0) return -1;
	return parse_ellipse(ctx, &ctx->alien.sheet->direct, root, penname);
}


static int parse_symdef_drawing(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	int res;
	double save;

	static const parser_t parsers[] = {
		{"PIN", parse_symdef_pin},
		{"POLYGON", parse_symdef_polygon},
		{"RECTANGLE", parse_symdef_rectangle},
		{"ELLIPSE", parse_symdef_ellipse},
		{NULL, NULL}
	};

	save = ctx->alien.oy;
	ctx->alien.oy = 0;
	res = parse_all(ctx, dst, root, parsers);
	ctx->alien.oy = save;

	return res;
}


static int parse_symdef(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	long oid;
	int res;
	const char *sid = PROP(root, "id");
	char *end;
	csch_source_arg_t *src;
	csch_sheet_t *sheet = dst;
	static const parser_t parsers1[] = {
		{"REF_POINT", parse_symdef_ref_point},
		{NULL, NULL}
	};
	static const parser_t parsers2[] = {
		{"REF", parse_symdef_ref},
		{"FIELD", parse_symdef_field},
		{"TinyCAD", parse_symdef_drawing},
		{NULL, NULL}
	};

	oid = strtol(sid, &end, 10);
	if (*end != '\0') {
		error(root, ("Invalid symdef id: must be an integer\n"));
		return -1;
	}
	if (oid <= 0) {
		error(root, ("Invalid symdef id: must be greater than zero\n"));
		return -1;
	}

	/* make sure symbol loclib is available - need to create all symdefs there */
	if (ctx->loclib_sym == NULL) {
		int alloced;
		src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
		ctx->loclib_sym = csch_loclib_get_root_by_name(sheet, "symbol", src, 1, &alloced);
		if (ctx->loclib_sym == NULL) {
			error(root, ("Failed to allocate symbol local lib (root)\n"));
			return -1;
		}
	}

	ctx->symdef_dxy_power_valid = 0;
	ctx->symdef = csch_cgrp_alloc(dst, ctx->loclib_sym, oid);
	if (ctx->symdef == NULL) {
		error(root, ("Failed to allocate symdef in local lib (symdef)\n"));
		return -1;
	}

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&ctx->symdef->attr, CSCH_ATP_USER_DEFAULT, "role", "symbol", src, NULL);
	csch_attr_side_effects(ctx->symdef, "role");

	res = parse_all(ctx, dst, root, parsers1); /* need to get the reference first for the offsets */
	res = parse_all(ctx, dst, root, parsers2);

	/* calculate origin-difference for the case when power pins are shown */
	if (ctx->symdef_dxy_power_valid) {
		double dx = ctx->symdef_dx_power - ctx->symdef_dx;
		double dy = ctx->symdef_dy_power - ctx->symdef_dy;
		char tmp[64];

		sprintf(tmp, "%f", dx);
		src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
		csch_attrib_set(&ctx->symdef->attr, CSCH_ATP_USER_DEFAULT, SYMATTR_DX_POWER, tmp, src, NULL);

		sprintf(tmp, "%f", dy);
		src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
		csch_attrib_set(&ctx->symdef->attr, CSCH_ATP_USER_DEFAULT, SYMATTR_DY_POWER, tmp, src, NULL);
	}

	ctx->symdef = NULL;
	return res;
}

/* Return dyntext in the referee group rgrp that prints attribute key sdesc */
static csch_text_t *grp_ref_attr_text(read_ctx_t *ctx, csch_cgrp_t *rgrp, const char *sdesc, int alloc)
{
	char *tmp = rnd_strdup_printf("%%../A.%s%%", sdesc);
	csch_text_t *text;
	htip_entry_t *e;
	int isref;

	/* Note on placement: the local lib variant should have all text objects
	   ever used, then references would move or remove those. The local lib
	   symbol should have text labels at 0;0 and symbol instantiation
	   should always move the text object to its final place. Tinycad ignores
	   coords in symdef fields, see symdef_fldcrd*.dsn. */

	/* find existing dyntext matching attribute template */
	for(e = htip_first(&rgrp->id2obj); e != NULL; e = htip_next(&rgrp->id2obj, e)) {
		text = e->value;
		if (text->hdr.type == CSCH_CTYPE_TEXT) {
			if (text->dyntext && (strcmp(text->text, tmp) == 0)) {
				free(tmp);
				return text;
			}
		}
	}

	if (!alloc) {
		free(tmp);
		return NULL;
	}

	/* not found, need to create */
	isref = (strcmp(sdesc, "Ref") == 0);
	text = (csch_text_t *)csch_alien_mktext(&ctx->alien, rgrp, 0, 0, (isref ? "sym-primary" : "sym-secondary"));
	text->spec1.x = text->spec1.y = 0; /* override to drop sheet transformation */
	text->text = tmp;
	text->dyntext = 1;
	text->hdr.floater = 1;

	return text;
}

static int parse_sym_field(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *spos = PROP(root, "pos"), *sshow = PROP(root, "show");
	const char *sdesc = PROP(root, "description"), *sval = PROP(root, "value");
	/* attributes that we can't figure: type */
	double ox, oy;
	long show;
	csch_source_arg_t *src;
	csch_cgrp_t *sym = (csch_cgrp_t *)dst;
	csch_coord_t bbx, bby, fonth = 3000;

	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return -1;
	if (parse_long(ctx, root, sshow, &show, 1) != 0)    return -1;

	if ((sdesc == NULL) || (*sdesc == '\0'))
		return 0;

	if ((sval[0] == '.') && (sval[1] == '.') && (sval[2] == '\0') && !show)
		return 0;

	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, sdesc, sval, src, NULL);

	if (show) {
		csch_text_t *text = grp_ref_attr_text(ctx, sym->data.ref.grp, sdesc, 1);
		csch_child_xform_t *xf = calloc(sizeof(csch_child_xform_t), 1);

		bbx = (ctx->symdef->hdr.bbox.x2 - ctx->symdef->hdr.bbox.x1);
		bby = (ctx->symdef->hdr.bbox.y2 - ctx->symdef->hdr.bbox.y1);
		ox = csch_alien_coord(&ctx->alien, ox - ctx->symdef_attr_dx);
		oy = csch_alien_coord(&ctx->alien, oy - ctx->symdef_attr_dy);

		csch_vtoid_append(&xf->path.vt, text->hdr.oid);

		switch(ctx->parent_rot) {
			case 0: xf->rot = 0; xf->mirx = 0; xf->miry = 0; xf->movex = ox; xf->movey = -oy; break;
			case 1: xf->rot = 0; xf->mirx = 0; xf->miry = 0; xf->movex = ox; xf->movey = bby+oy-fonth; break;
			case 2: xf->rot = -90; xf->mirx = 0; xf->miry = 0; xf->movex = oy-fonth; xf->movey = -ox; break;
			case 3: xf->rot = +90; xf->mirx = 0; xf->miry = 0; xf->movex = oy; xf->movey = -ox-bby; break;

			case 4: xf->rot = 0; xf->mirx = 1; xf->miry = 0; xf->movex = ox+bbx; xf->movey = -oy; break;
			case 5: xf->rot = 0; xf->mirx = 1; xf->miry = 0; xf->movex = ox+bbx; xf->movey = bby+oy-fonth; break;
			case 6: xf->rot = -90; xf->mirx = 1; xf->miry = 0; xf->movex = -oy-bby; xf->movey = -ox; break;
			case 7: xf->rot = +90; xf->mirx = 1; xf->miry = 0; xf->movex = -oy-bby; xf->movey = -ox-bby; break;
		}

		vtp0_append(&sym->data.ref.child_xform, xf);
	}

	return 0;
}

static int parse_sym(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *spos = PROP(root, "pos");
	const char *sid = PROP(root, "id");
	const char *sscx = PROP(root, "scale_x"), *sscy = PROP(root, "scale_y");
	const char *srot = PROP(root, "rotate");
	const char *sshow_power = PROP(root, "show_power");
	csch_cgrp_t *symdef = NULL, *sym;
	csch_coord_t bbw, bbh;
	double ox, oy, scx, scy;
	long id, rot;
	int mx, my, dx, dy, swbb, res, show_power = 0;
	csch_sheet_t *sheet = dst;
	static const parser_t parsers[] = {
		{"FIELD", parse_sym_field},
		{NULL, NULL}
	};


	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return -1;
	if (parse_long(ctx, root, sid, &id, 1) != 0)        return -1;
	if (parse_double(ctx, root, sscx, &scx, 1) != 0)    return -1;
	if (parse_double(ctx, root, sscy, &scy, 1) != 0)    return -1;
	if (parse_long(ctx, root, srot, &rot, 1) != 0)      return -1;

	if (sshow_power != NULL) {
		show_power = parse_bool(ctx, root, sshow_power);
		if (show_power < 0)
			return -1;
	}


	if ((scx != 1.0) || (scy != 1.0)) {
		error(root, ("Scaled symbols are not yet supported\n"));
		return -1;
	}

	if (ctx->loclib_sym != NULL)
		symdef = htip_get(&ctx->loclib_sym->id2obj, id);
	if (symdef == NULL) {
		error(root, ("Can not find symbol def id %ld\n", id));
		return -1;
	}

	sym = csch_cgrp_ref_alloc(dst, &sheet->direct, csch_oid_new(dst, &sheet->direct));
	if (sym == NULL) {
		error(root, ("Failed to create blank symbol\n"));
		return -1;
	}

	csch_cgrp_update(sheet, symdef, 1);
	bbw = symdef->hdr.bbox.x2 - symdef->hdr.bbox.x1;
	bbh = symdef->hdr.bbox.y2 - symdef->hdr.bbox.y1;

	rot2sym(ctx, root, rot, &sym->spec_rot, &mx, &my, &dx, &dy, &swbb);
	if (swbb)
		rnd_swap(csch_coord_t, bbw, bbh);

	ctx->symdef_attr_dx = ctx->symdef_attr_dy = 0;

	/* show_power = 1 needs to use a different placement ref, compensate for that */
	if (show_power) {
		const char *spx = csch_attrib_get_str(&symdef->attr, SYMATTR_DX_POWER);
		const char *spy = csch_attrib_get_str(&symdef->attr, SYMATTR_DY_POWER);
		if ((spx != NULL) && (spy != NULL)) {
			double px = strtol(spx, NULL, 10), py = strtol(spy, NULL, 10); /* syntax checked when the attribute got written */
			if (sym->spec_rot != 0) {
				int steps = 0;
				if (sym->spec_rot == 90) steps = 1;
				else if (sym->spec_rot == -90) steps = 3;
				RND_COORD_ROTATE90(px, py, 0, 0, steps);
			}
			if (mx) px = -px;
			if (my) py = -py;
			ctx->symdef_attr_dx = px;
			ctx->symdef_attr_dy = py;
/*			rnd_trace("NKD#1 px %f %f | ox %f %f bb=%d %d (%d) bbflt=%d %d\n", px, py, ox, oy, bbw, bbh, swbb, bbw, bbh, symdef->bbox_flt.x2 - symdef->bbox_flt.x1, symdef->bbox_flt.y2 - symdef->bbox_flt.y1);*/
			ox += px;
			oy += py;
		}
	}

	sym->mirx = mx;
	sym->miry = my;
	sym->data.ref.grp = symdef;

/*	rnd_trace("symdef bbox NKD#2: %d*%ld %d*%ld\n", dx, bbw, dy, bbh);*/
	sym->x = csch_alien_coord_x(&ctx->alien, ox) /*+ dx * bbw*/;
	sym->y = csch_alien_coord_y(&ctx->alien, oy) /*+ dy * bbh*/;

	/* save dx and dy here and postpone the actual translation, because
	   the final bounding box depends on what pins are shown which is
	   figured only in postprocessing; do sym->x += dx*bbw and sym-yx += dy*bbw there */
	{
		csch_source_arg_t *src;
		char tmp[32];

		if (dx != 0) {
			sprintf(tmp, "%d", dx);
			src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
			csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, SYMATTR_SYM_DXBB, tmp, src, NULL);
		}

		if (dy != 0) {
			sprintf(tmp, "%d", dy);
			src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
			csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, SYMATTR_SYM_DYBB, tmp, src, NULL);
		}
	}

	/* do not ref-embed here, let postprocessor do that */

	htpp_set(&ctx->sym2xml, sym, root);

	ctx->symdef = symdef;
	ctx->parent_rot = rot;

	res = parse_all(ctx, sym, root, parsers);

	ctx->symdef = NULL;
	ctx->parent_rot = 0;

	return res;
}

/*** wires and other drawing objects ***/

static int parse_wire(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	double x1, y1, x2, y2;
	const char *a = PROP(root, "a"), *b = PROP(root, "b");
	csch_sheet_t *sheet = dst;

	if ((parse_coords(ctx, root, a, &x1, &y1) != 0) || (parse_coords(ctx, root, b, &x2, &y2) != 0))
		return -1;

	csch_alien_mknet(&ctx->alien, &sheet->direct, x1, y1, x2, y2);
	return 0;
}

/* Adjust test mirx and rotate for style:dir */
static void text_adjust_dir(csch_text_t *text, int style, int dir)
{
	if ((dir == 1) || (dir == 2))
		text->spec_mirx = 1;
	if (dir == 0)
		text->spec_rot = 90;
	else if (dir == 1)
		text->spec_rot = -90;
}


static void create_net_label(read_ctx_t *ctx, csch_line_t *wire, xmlNode *root, const char *textstr, double ox, double oy, int style, int dir)
{
	csch_sheet_t *sheet = ctx->alien.sheet;
	csch_coord_t cox, coy;
	csch_text_t *text;
	csch_source_arg_t *src;
	csch_cgrp_t *parent;
	const char *templ;
	long tw, th, thy;
	double w, h, step;

	if (dir == 4)
		return;

	if (style != 0) {
		parent = csch_cgrp_alloc(sheet, wire->hdr.parent, csch_oid_new(sheet, wire->hdr.parent));
		parent->hdr.floater = 1;
		templ = "%../../A.name%";
	}
	else {
		parent = wire->hdr.parent;
		templ = "%../A.name%";
	}

	text = (csch_text_t *)csch_alien_mktext(&ctx->alien, parent, ox, oy, "wire");
	text->text = rnd_strdup(templ);
	text->dyntext = 1;
	text->hdr.floater = 1;
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&wire->hdr.parent->attr, CSCH_ATP_USER_DEFAULT, "name", textstr, src, NULL);

	if (style == 0) {
		text_adjust_dir(text, style, dir);
		return;
	}


	csch_text_update(sheet, text, 1);
	tw = (text->hdr.bbox.x2 - text->hdr.bbox.x1);
	th = (text->hdr.bbox.y2 - text->hdr.bbox.y1);
	w = (double)tw / ctx->alien.coord_factor;
	h = (double)th / ctx->alien.coord_factor;
	step = h / 2;

	thy = th;
	if ((dir == 1) || (dir == 2)) thy = -thy;

	switch(style) {
		case 1: /* input: right arrow */
			text->spec_mirx = 1;
			text->spec1.x -= th/2;
			text->spec1.y -= thy/2;
			csch_alien_mkline(&ctx->alien, parent, ox, oy, ox-step, oy-step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox, oy, ox-step, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step, oy+step, ox-step*1.25-w, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step, oy-step, ox-step*1.25-w, oy-step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step*1.25-w, oy-step, ox-step*1.25-w, oy+step, "sheet-decor");
			break;
		case 2: /* output: left arrow */
			text->spec_mirx = 1;
			text->spec1.y -= thy/2;
			csch_alien_mkline(&ctx->alien, parent, ox, oy-step, ox, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox, oy-step, ox-w-step, oy-step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox, oy+step, ox-w-step, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-w-step, oy+step, ox-w-step*2, oy, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-w-step, oy-step, ox-w-step*2, oy, "sheet-decor");
			break;
		case 3: /* io: dual arrow */
			text->spec_mirx = 1;
			text->spec1.x -= th/2;
			text->spec1.y -= thy/2;
			csch_alien_mkline(&ctx->alien, parent, ox, oy, ox-step, oy-step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox, oy, ox-step, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step, oy+step, ox-step*1.25-w, oy+step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step, oy-step, ox-step*1.25-w, oy-step, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step*1.25-w, oy-step, ox-step*2.25-w, oy, "sheet-decor");
			csch_alien_mkline(&ctx->alien, parent, ox-step*1.25-w, oy+step, ox-step*2.25-w, oy, "sheet-decor");
			break;
	}

	/* rotate the whole label group and adjust the text object */

	cox = csch_alien_coord_x(&ctx->alien, ox);
	coy = csch_alien_coord_y(&ctx->alien, oy);

	switch(dir) {
		case 0:
			csch_rotate90(sheet, &parent->hdr, cox, coy, 1, 0);
			break;
		case 1:
			csch_rotate90(sheet, &parent->hdr, cox, coy, 3, 0);
			text->spec_mirx = 0;
			text->spec_rot = 180;
			break;
		case 2:
			csch_rotate90(sheet, &parent->hdr, cox, coy, 2, 0);
			text->spec_mirx = 0;
			text->spec_rot = 180;
			break;
		case 3: break;
		case 4: break;
		default:
			error(root, ("invalid label direction"));
			break;
	}
}

static int parse_label(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *spos = PROP(root, "pos"), *sdir = PROP(root, "direction");
	const char *sstyle = PROP(root, "style");
	const char *textstr;
	csch_line_t *wire;
	csch_text_t *text;
	csch_rtree_it_t it;
	csch_rtree_box_t bbox;
	csch_sheet_t *sheet = dst;
	double ox, oy;
	long dir, style;


	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return -1;
	if (parse_long(ctx, root, sdir, &dir, 1) != 0)      return -1;
	if (parse_long(ctx, root, sstyle, &style, 1) != 0)  return -1;
	if ((textstr = parse_text(ctx, root, 1)) == NULL)   return -1;
/*	double rot = rot2deg(ctx, root, dir);*/

	bbox.x1 = csch_alien_coord_x(&ctx->alien, ox) - 1;
	bbox.y1 = csch_alien_coord_y(&ctx->alien, oy) - 1;
	bbox.x2 = bbox.x1 + 2;
	bbox.y2 = bbox.y1 + 2;
	for(wire = csch_rtree_first(&it, &sheet->dsply[CSCH_DSPLY_WIRE], &bbox); wire != NULL; wire = csch_rtree_next(&it))
		if ((wire->hdr.type == CSCH_CTYPE_LINE) && (wire->hdr.parent->role == CSCH_ROLE_WIRE_NET))
			break;

	if (wire == NULL) { /* create text object */
		text = (csch_text_t *)csch_alien_mktext(&ctx->alien, &sheet->direct, ox, oy, "sheet-decor");
		text->text = rnd_strdup(textstr);
		text_adjust_dir(text, style, dir);
	}
	else /* create floater */
		create_net_label(ctx, wire, root, textstr, ox, oy, style, dir);

	return 0;
}

static csch_text_t *parse_text_obj_in(read_ctx_t *ctx, void *dst, xmlNode *root, const char *posname, csch_cgrp_t *parent)
{
	const char *spos = PROP(root, posname), *sdir = PROP(root, "direction");
	const char *sstyle = PROP(root, "style");
	const char *textstr;
	csch_text_t *text;
	double ox, oy;
	long dir, style = 0;

	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return NULL;
	if (parse_long(ctx, root, sdir, &dir, 1) != 0)      return NULL;
	if (parse_long(ctx, root, sstyle, &style, 0) != 0)  return NULL;
	if ((textstr = parse_text(ctx, root, 1)) == NULL)   return NULL;
/*	double rot = rot2deg(ctx, root, dir);*/

	text = (csch_text_t *)csch_alien_mktext(&ctx->alien, parent, ox, oy, "sheet-decor");
	text->text = rnd_strdup(textstr);

	text_adjust_dir(text, style, dir);

	return text;
}

static int parse_text_obj(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	csch_sheet_t *sheet = dst;
	if (parse_text_obj_in(ctx, dst, root, "pos", &sheet->direct) == NULL)
		return -1;
	return 0;
}

#include <libcschem/cnc_obj.h>

static int parse_note(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	csch_cgrp_t *grp;
	const char *sa = PROP(root, "a"), *sb = PROP(root, "b");
	double x1, y1, x2, y2;
	csch_sheet_t *sheet = dst;
	csch_text_t *text;

	if (parse_coords(ctx, root, sa, &x1, &y1) != 0)   return -1;
	if (parse_coords(ctx, root, sb, &x2, &y2) != 0)   return -1;

	grp = csch_cgrp_alloc(dst, &sheet->direct, csch_oid_new(dst, &sheet->direct));
	csch_alien_mkrect(&ctx->alien, grp, x1, y1, x2, y2, "sheet-decor", "note-fill");
	text = parse_text_obj_in(ctx, dst, root, "a", grp);
	if (text == NULL)
		return -1;

	csch_cobj_update_pen(&text->hdr);
	text->spec1.y -= text->hdr.stroke->font_height;


	TODO("TYC#1: Multiline text is not yet supported; ojjects.dsn");

	return 0;
}

static csch_cgrp_t *power_sym(read_ctx_t *ctx, void *dst, xmlNode *root, const char *netname)
{
	csch_cgrp_t *sym;
	csch_source_arg_t *src;
	csch_sheet_t *sheet = dst;
	static const char *forge[] = {
		"delete,forge/tmp",
		"scalar,forge/tmp",
		"sub,^,1:,forge/tmp",
		"suba,$,rail,forge/tmp",
		"array,connect",
		"append,connect,forge/tmp",
		NULL
	};

	sym = csch_cgrp_alloc(dst, &sheet->direct, csch_oid_new(dst, &sheet->direct));
	if (sym == NULL) {
		error(root, ("Failed to allocate symbol for power\n"));
		return NULL;
	}
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "role", "symbol", src, NULL);
	csch_attr_side_effects(sym, "role");
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "rail", netname, src, NULL);
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_attrib_set_arr_c(&sym->attr, CSCH_ATP_USER_DEFAULT, "forge", forge, src, NULL);

	return sym;
}

static int parse_power(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	const char *netname = parse_text(ctx, root, 0);
	const char *spos = PROP(root, "pos"), *sdir = PROP(root, "direction");
	const char *swhich = PROP(root, "which");
	double ox, oy, ty = 0, tym, tcx = 0, tcx2 = 0, tcy = 0;
	const double dx = 1, dy = 1;
	long which, dir, fonth = 3000;
	csch_cgrp_t *sym;
	csch_source_arg_t *src;
	csch_text_t *text;
	csch_alien_read_ctx_t a0 = {0};

	a0.sheet = ctx->alien.sheet;
	a0.fmt_prefix = ctx->alien.fmt_prefix;
	a0.coord_factor = ctx->alien.coord_factor;

	if (parse_coords(ctx, root, spos, &ox, &oy) != 0)   return -1;
	if (parse_long(ctx, root, swhich, &which, 1) != 0)  return -1;
	if (parse_long(ctx, root, sdir, &dir, 1) != 0)      return -1;

	if (netname == NULL)
		netname = "unknown";

	sym = power_sym(ctx, dst, root, netname);
	if (sym == NULL)
		return -1;

	sym->spec_rot = -rot2deg(ctx, root, dir);
	sym->x = csch_alien_coord_x(&ctx->alien, ox);
	sym->y = csch_alien_coord_y(&ctx->alien, oy);


	/* graphics: terminal */
	src = csch_attrib_src_c(ctx->fn, root->line, 0, NULL);
	csch_alien_mkpin_line(&a0, src, sym, 0, 0, 0, +dy*3);

	/* graphics: text */
	switch(which) {
		case 0: ty = 6; break;
		case 1: ty = 8; break;
		case 2: ty = 8; break;
		case 3: ty = 8; break;
		case 4: ty = 8; break;
		default:
			error(root, ("unhandled power 'which'\n"));
	}


	switch(dir) {
		case 0: /* pointing up (text is above) */
			tym = (sym->spec_rot >= 180) ? 0 : -(double)fonth/ctx->alien.coord_factor;
			tcx  = -8*dx;
			tcx2 = +8*dx;
			tcy  = ty*dy+tym;
			break;
		case 1: /* pointing down (text is under) */
			tcx  = +8*dx;
			tcx2 = +24*dx;
			tcy  = ty*dy;
			break;
		case 2: /* pointing left (text is to the left) */
			tcx  = ((double)-fonth/2.0) / ctx->alien.coord_factor;
			tcx2 = ((double)-fonth/2.0+fonth) / ctx->alien.coord_factor;
			tcy  = ty*dy;
			break;
		case 3: /* pointing right (text is to the right) */
			tcx  = (fonth / 2.0) / ctx->alien.coord_factor;
			tcx2 = ((fonth / 2.0) + fonth) / ctx->alien.coord_factor;
			tcy  = ty*dy - (double)fonth / ctx->alien.coord_factor;
			break;
		default:
			error(root, ("unhandled power direction\n"));
	}

	text = (csch_text_t *)csch_alien_mktext(&a0, sym, tcx, tcy, "sym-primary");
	text->text = rnd_strdup("%../A.rail%");
	text->dyntext = 1;
	text->has_bbox = 1;
	text->spec2.x = csch_alien_coord_x(&a0, tcx2);
	text->spec2.y = text->spec1.y+fonth;
	text->halign = CSCH_HALIGN_CENTER;
	text->spec_rot = -sym->spec_rot;
	text->spec_mirx = (sym->spec_rot >= 180);

	/* graphics: decoration */
	switch(which) {
		case 0: /* T */
			csch_alien_mkline(&a0, sym, -1*dx, +3*dy, +1*dx, +3*dy, "sym-decor");
			break;
		case 1: /* circ */
			csch_alien_mkarc(&a0, sym, 0, +4*dy, +1, 0, 360, "sym-decor");
			break;
		case 2: /* wave */
			csch_alien_mkarc(&a0, sym, +1*dx, +4*dy, +1, 0, 180, "sym-decor");
			csch_alien_mkarc(&a0, sym, -1*dx, +4*dy, +1, 0, -180, "sym-decor");
			break;
		case 3: /* arrow */
			csch_alien_mkline(&a0, sym, -1*dx, +3*dy, +1*dx, +3*dy, "sym-decor");
			csch_alien_mkline(&a0, sym, -1*dx, +3*dy, 0,     +5*dy, "sym-decor");
			csch_alien_mkline(&a0, sym, +1*dx, +3*dy, 0,     +5*dy, "sym-decor");
			break;
		case 4: /* earth */
			csch_alien_mkline(&a0, sym, -1*dx,    +3*dy,   +1*dx,    +3*dy, "sym-decor");
			csch_alien_mkline(&a0, sym, -0.70*dx, +4*dy,   +0.70*dx, +4*dy, "sym-decor");
			csch_alien_mkline(&a0, sym, -0.30*dx, +5*dy,   +0.30*dx, +5*dy, "sym-decor");
			break;
		default:
			error(root, ("Failed to create power: unknown style which=%s\n", swhich));
			return -1;
	}
	return 0;
}

#define TEXT_SYM_ATTR_HANDLED(text) ((text)->tmp[0].l)

/* Remove a child object using child_xform in the group_ref sym */
static void sym_child_hide(read_ctx_t *ctx, csch_cgrp_t *sym, csch_chdr_t *child)
{
	csch_child_xform_t *xf = calloc(sizeof(csch_child_xform_t), 1);
	csch_vtoid_append(&xf->path.vt, child->oid);
	xf->remove = 1;
	vtp0_append(&sym->data.ref.child_xform, xf);
}

/* Finalize attribute visibility: delete inivisible attribute printouts */
static int sym_attr_vis(read_ctx_t *ctx, csch_cgrp_t *sym)
{
	xmlNode *n, *root = htpp_get(&ctx->sym2xml, sym);
	csch_cgrp_t *symdef = sym->data.ref.grp;
	htip_entry_t *e;


	if (root == NULL) {
		rnd_message(RND_MSG_ERROR, "io_tinycad: internal error: no xml node in sym_attr_vis()\n");
		return -1;
	}

	/* check each FIELD and if it's show=0 and has a floater text, child-remove it */
	for(n = root->children; n != NULL; n = n->next) {
		if (xmlStrcmp(n->name, (const unsigned char *)"FIELD") == 0) {
			const char *sdesc = PROP(n, "description"), *sshow = PROP(n, "show");
			int show = parse_bool(ctx, n, sshow);
			csch_text_t *text = grp_ref_attr_text(ctx, sym->data.ref.grp, sdesc, 0);

			if (text == NULL) continue;

			TEXT_SYM_ATTR_HANDLED(text) = 1; /* mark the text handled so it doesn't get removed below */

			if (!show) /* attr text created, remove it */
				sym_child_hide(ctx, sym, &text->hdr);
		}
	}

	/* check each text object and delete unhandled text objects. Example when
	   this happens: a sheet has R1 and R2 from a common symdef; R1 refers to
	   symdef defined fields only, but R2 creates a new field called 'foo' with
	   show=1. The new dyntext object needs to be added in the common symdef
	   because group_refs can havve their own children. In turn, R1 gets the
	   same 'foo' text object which needs to be removed. The above loop doesn't
	   remove it because in R1 or in the common symdef field 'foo' is not listed.
	   However, anything shown at R1 needs to have an explicit field tag in
	   the xml with show=1, so it is marked as handled above; any text object not
	   marked so can be safely removed */
	for(e = htip_first(&symdef->id2obj); e != NULL; e = htip_next(&symdef->id2obj, e)) {
		csch_text_t *text = e->value;
		if ((text->hdr.type == CSCH_CTYPE_TEXT) && (!TEXT_SYM_ATTR_HANDLED(text)))
			sym_child_hide(ctx, sym, &text->hdr);
	}

	return 0;
}

/* Finalize pin visibility: sym decides whether power pins are shown and
   slotted symbols should hide any non-power-pin that's not in the sym's slot  */
static int sym_pin_vis(read_ctx_t *ctx, csch_cgrp_t *sym)
{
	xmlNode *root = htpp_get(&ctx->sym2xml, sym);
	csch_cgrp_t *symdef = sym->data.ref.grp;
	htip_entry_t *e;
	const char *spart = PROP(root, "part");
	const char *sshowpwr = PROP(root, "show_power");
	int showpwr = 0;
	long part = 0;

	/* fetch power pin and slot control of the symbol instance */
	if (sshowpwr != NULL) {
		showpwr = parse_bool(ctx, root, sshowpwr);
		if (showpwr < 0)
			return -1;
	}

	if (parse_long(ctx, root, spart, &part, 0) < 0)
		return -1;


	/* check each "pin" and xform-remove those that should not be shown (hidden
	   power pins and other-slot pins) */
	for(e = htip_first(&symdef->id2obj); e != NULL; e = htip_next(&symdef->id2obj, e)) {
		csch_cgrp_t *pin = e->value;
		if ((pin->hdr.type == CSCH_CTYPE_GRP) && (pin->role == CSCH_ROLE_TERMINAL)) {
			const char *spin_part, *spin_power = csch_attrib_get_str(&pin->attr, PINATTR_POWER);

			if ((spin_power != NULL) && (spin_power[0] == '1')) { /* power pins are special case; this is a power pin */
				if (!showpwr)
					sym_child_hide(ctx, sym, &pin->hdr);
			}
			else if (spart != NULL) { /* slotted sym: hide anyhting that's not in this slow */
				spin_part = csch_attrib_get_str(&pin->attr, PINATTR_SLOT);
				if (spin_part != NULL) {
					long pin_part = strtol(spin_part, NULL, 10); /* syntax already checked in symdef read code */
					if (pin_part != part)
						sym_child_hide(ctx, sym, &pin->hdr);
				}
			}
		}
	}
	return 0;
}

static void postproc_sym_placement(read_ctx_t *ctx, csch_cgrp_t *sym)
{
	int dx = 0, dy = 0;
	csch_coord_t bbw, bbh;
	const char *tmp;
	csch_rtree_box_t bb;

	tmp = csch_attrib_get_str(&sym->attr, SYMATTR_SYM_DXBB);
	if (tmp != NULL) dx = strtol(tmp, NULL, 10); /* always valid (code generated) */
	tmp = csch_attrib_get_str(&sym->attr, SYMATTR_SYM_DYBB);
	if (tmp != NULL) dy = strtol(tmp, NULL, 10); /* always valid (code generated) */

	if ((dx == 0) && (dy == 0))
		return;

	/* get all objects rendered for and centerline bbox calculated */
	csch_cgrp_ref_render(ctx->alien.sheet, sym);
	csch_alien_centerline_bbox(&ctx->alien, &sym->hdr, &bb);
	bbw = bb.x2 - bb.x1;
	bbh = bb.y2 - bb.y1;
/*	if (swbb) rnd_swap(csch_coord_t, bbw, bbh);*/

/*	rnd_trace("NKD#5: d=%d;%d bb=%d;%d sw=%d\n", dx, dy, (int)bbw, (int)bbh, swbb);*/


	sym->x += dx * bbw;
	sym->y += dy * bbh;
}

static int postproc_slot(read_ctx_t *ctx, csch_cgrp_t *grp)
{
	htip_entry_t *e;

	if ((grp->role == CSCH_ROLE_SYMBOL) || (grp->hdr.type == CSCH_CTYPE_GRP_REF)) {
		if ((grp->hdr.type == CSCH_CTYPE_GRP_REF) && (sym_attr_vis(ctx, grp) != 0))
			return -1;
		if ((grp->hdr.type == CSCH_CTYPE_GRP_REF) && (sym_pin_vis(ctx, grp) != 0))
			return -1;
	}

	for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) {
		csch_cgrp_t *child = e->value;
		if (csch_obj_is_grp(&child->hdr)) {
			if (postproc_slot(ctx, child) != 0)
				return -1;
		}
	}

	if (grp->hdr.type == CSCH_CTYPE_GRP_REF)
		postproc_sym_placement(ctx, grp);

	return 0;
}


static int parse_sheet(read_ctx_t *ctx, void *dst, xmlNode *root)
{
	int res;

	static const parser_t parsers0[] = { /* read details first so that page size is known (for the mirroring) */
		{"DETAILS", parse_details},
		{NULL, NULL}
	};
	static const parser_t parsers1[] = {
		{"NAME", parse_name},
		{"SYMBOLDEF", parse_symdef},
		{"SYMBOL", parse_sym},
		{"WIRE", parse_wire},
		{"TEXT", parse_text_obj},
		{"NOTE_TEXT", parse_note},
		{"POWER", parse_power},
		{"POLYGON", parse_sheet_polygon},
		{"RECTANGLE", parse_sheet_rectangle},
		{"ELLIPSE", parse_sheet_ellipse},
		{"NOCONNECT", parse_sheet_noconnect},
		{NULL, NULL}
	};
	static const parser_t parsers2[] = {
		{"LABEL", parse_label}, /* labels are placed on wires, best if wires already exist */
		{NULL, NULL}
	};

	res = parse_all(ctx, dst, root, parsers0) || parse_all(ctx, dst, root, parsers1) || parse_all(ctx, dst, root, parsers2);

	if (res == 0)
		postproc_slot(ctx, &ctx->alien.sheet->direct);

	/* clear cache so the next sheet doesn't inherit current sheet's */
	ctx->loclib_sym = NULL;

	return res;
}

/*** file level parsing and entry points ***/
static int io_tinycad_load_sheet(read_ctx_t *ctx, xmlNode *sheet_node, const char *fn, csch_sheet_t *dst)
{
	int res;

	res = parse_sheet(ctx, dst, sheet_node);
	if (res == 0) {
		csch_cgrp_render_all(dst, &dst->direct);
		res = csch_alien_postproc_sheet(&ctx->alien);

		csch_cgrp_update(dst, &dst->direct, 1);
		csch_alien_update_conns(&ctx->alien);

		if (io_tinycad_conf.plugins.io_tinycad.emulate_text_ang_180)
			csch_alien_postproc_text_autorot(&ctx->alien, &dst->direct, 1, 1);

		if ((res == 0) && io_tinycad_conf.plugins.io_tinycad.auto_normalize)
			csch_alien_postproc_normalize(&ctx->alien);
	}

	return res;
}

static int io_tinycad_test_parse(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	char line[512], *s;
	int n;

	if (type != CSCH_IOTYP_SHEET)
		return -1;

	s = fgets(line, sizeof(line), f);
	if ((s == NULL) || (strncmp(s, "<?xml", 4) != 0))
		return -1;

	for(n = 0; n < 32; n++) {
		s = fgets(line, sizeof(line), f);
		if (s == NULL)
			return -1;
		if (strstr(s, "<TinyCADSheets>") != NULL)
			return 0;
	}
	return -1;
}

/* Start from next_sheet_nd; if it's a sheet, return; else keep stepping on
   ->next until it's a sheet or NULL */
static void io_tinycad_seek_next(read_ctx_t *ctx)
{
	for(;ctx->next_sheet_nd != NULL; ctx->next_sheet_nd = ctx->next_sheet_nd->next) {
		if (xmlStrcmp(ctx->next_sheet_nd->name, (const unsigned char *)"TinyCAD") == 0)
			return;
	}
}

void *io_tinycad_test_parse_bundled(FILE *f, const char *fn, const char *fmt, csch_plug_io_type_t type)
{
	if (io_tinycad_test_parse(f, fn, fmt, type) == 0) {
		read_ctx_t *ctx = calloc(sizeof(read_ctx_t), 1);

		ctx->alien.fmt_prefix = "io_tinycad";
		ctx->alien.flip_y = 1;
		ctx->fn = fn;
		ctx->doc = xmlReadFile(fn, NULL, 0);

		if (ctx->doc == NULL) {
			rnd_message(RND_MSG_ERROR, "io_tinycad: xml parsing error on file %s\n", fn);
			free(ctx);
			return NULL;
		}

		ctx->root = xmlDocGetRootElement(ctx->doc);
		if (xmlStrcmp(ctx->root->name, (xmlChar *)"TinyCADSheets") != 0) {
			rnd_message(RND_MSG_ERROR, "io_tinycad: xml error: root is not <TinyCADSheets>\n");
			xmlFreeDoc(ctx->doc);
			free(ctx);
			return NULL;
		}

		ctx->next_sheet_nd = ctx->root->children;
		io_tinycad_seek_next(ctx);
		if (ctx->next_sheet_nd == NULL) {
			rnd_message(RND_MSG_ERROR, "io_tinycad: xml error: no sheets under <TinyCADSheets>\n");
			xmlFreeDoc(ctx->doc);
			free(ctx);
			return NULL;
		}

		htpp_init(&ctx->sym2xml, ptrhash, ptrkeyeq);
		return ctx;
	}
	return NULL;
}

int io_tinycad_load_sheet_bundled(void *cookie, FILE *f, const char *fn, csch_sheet_t *dst)
{
	read_ctx_t *ctx = cookie;

	ctx->alien.sheet = dst;
	ctx->alien.coord_factor = io_tinycad_conf.plugins.io_tinycad.coord_mult;
	csch_alien_sheet_setup(&ctx->alien, 1);
	if (io_tinycad_load_sheet(ctx, ctx->next_sheet_nd, fn, dst) != 0)
		return -1;
	ctx->alien.sheet->hidlib.fullpath = rnd_strdup_printf("%s_%ld.rs", fn, ++ctx->sheetno);
	ctx->alien.sheet = NULL;

	/* return 1 on no more sheet */
	ctx->next_sheet_nd = ctx->next_sheet_nd->next;
	io_tinycad_seek_next(ctx);
	return (ctx->next_sheet_nd == NULL) ? 1 : 0;
}

void io_tinycad_end_bundled(void *cookie, const char *fn)
{
	read_ctx_t *ctx = cookie;

	htpp_uninit(&ctx->sym2xml);
	xmlFreeDoc(ctx->doc);
	free(ctx);
}
