/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - altium file format support
 *  Copyright (C) 2023 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 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., 31 Milk Street, # 960789 Boston, MA 02196 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 <ctype.h>

#include <genht/hash.h>
#include <genvector/vtd0.h>
#include <librnd/core/error.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_wirenet.h>

#include "io_altium_conf.h"

#include "schdoc.h"

TODO("At the moment this is #included from pcbdoc_ascii.c; this would change once that code is moved to pcb-rnd's inclib")

/* for records that have been executed and intentionally did not create a group;
   for example slotted symbols for the inactive pins */
static const csch_cgrp_t NOGRP_, *NOGRP = &NOGRP_;

/* Record types:
    1: symbol placement (from lib)
    2: pin
    4: text object
    5: bezier
    6: line
    7: polygon
    8: ellipse
    9: arc modified to pie
    10: round rect
    11: elliptic arc
    12: arc
    13: line (single segment, with same fields as rectangle)
    14: rectangle
    15: sheet ref box (green box)
    16: port in sheet entry (yellow arrows within 15)
    17: GND/vcc
    18: port on sheet: hexagon net label
    22: NC
    25: netlabel
    27: wire
    29: junction?
    28: note: multiline text in a rectangle (white background, no frame)
    31: font id
    34: attribute text
    39: sheet template object
    41: attribute text
    43: warning sign
    44: empty?
    45: footprint/model
    46: empty?
    47: DESIMP?
    48: empty?
    209: note (multiline text in a box, yellow background, frame)
*/

/* When a number is specified as an integer part and a fraction, the fracion
   needs to be divided by this number */
#define FRAC_DIV 100000

/* Decide if a color is dark; color is in #rgb */
static int alti_is_dark(unsigned long clr)
{
	double r, g, b, dist;

	r = clr & 0xFF;
	g = (clr & 0xFF00) >> 8;
	b = (clr & 0xFF0000) >> 16;

	if ((r < 64) || (g < 64) || (b < 64))
		return 1;

	dist = r*r + g*g + b*b;
	return (dist < 0xC000);
}

/* Return 1 if instantiation of object shall be skipped because it's for
   a different slot than what parent symbol is instantiated for. 1 if opartid
   is present and it differs from parent symbols "currentpartid" attrib. */
static int alti_slot_skip(csch_cgrp_t *parent, const char *opartid, const char *opartdisp)
{
	const char *cpid;

	if (opartid == NULL)
		return 0;

	/* ownerpartid==-1 means always visible */
	if ((opartid[0] == '-') && (opartid[1] == '1') && (opartid[2] == '\0'))
		return 0;

	/* deal with ownerpartdisplaymode == 0 only */
	if ((opartdisp != NULL) && (*opartdisp != '0'))
		return 1;

	/* find symbol parent */
	while(parent->role != CSCH_ROLE_SYMBOL) {
		parent = parent->hdr.parent;
		if (parent == NULL)
			return 0; /* not in symbol */
	}

	cpid = csch_attrib_get_str(&parent->attr, "-currentpartid");
	if (cpid == NULL)
		return 0;

	return (strcmp(cpid, opartid) != 0);
}

static int alti_coord_equ_x(io_altium_rctx_t *rctx, double x1, double x2)
{
	return csch_alien_coord_x(&rctx->alien, x1) == csch_alien_coord_x(&rctx->alien, x2);
}

static int alti_coord_equ_y(io_altium_rctx_t *rctx, double y1, double y2)
{
	return csch_alien_coord_x(&rctx->alien, y1) == csch_alien_coord_x(&rctx->alien, y2);
}

static double conv_double_field_(io_altium_rctx_t *rctx, altium_record_t *rec, altium_field_t *field)
{
	char *end;
	double res;

	switch(field->val_type) {
		case ALTIUM_FT_DBL: return field->val.dbl;
		case ALTIUM_FT_LNG: return field->val.lng;
		case ALTIUM_FT_CRD: return field->val.crd;
		case ALTIUM_FT_STR:
			res = strtod(field->val.str, &end);
			if (*end != '\0') {
				error(rec, ("io_altium: failed to convert floating point value '%s'\n", field->val));
				return 0;
			}
			return res;
	}
	abort();
}

#define conv_double_field(fld) conv_double_field_(rctx, rec, fld)

static long conv_long_field_(io_altium_rctx_t *rctx, altium_record_t *rec, altium_field_t *field)
{
	char *end;
	long res;

	switch(field->val_type) {
		case ALTIUM_FT_DBL: return field->val.dbl;
		case ALTIUM_FT_LNG: return field->val.lng;
		case ALTIUM_FT_CRD: return field->val.crd;
		case ALTIUM_FT_STR:
			res = strtol(field->val.str, &end, 10);
			if (*end != '\0') {
				error(rec, ("io_altium: failed to convert integer value '%s'\n", field->val.str));
				return 0;
			}
			return res;
	}
	abort();
}

#define conv_long_field(fld) conv_long_field_(rctx, rec, fld)

csch_cgrp_t *altium_get_parent(io_altium_rctx_t *rctx, altium_record_t *rec, long pidx, int is_solid, char **stroke, char **fill)
{

	if (pidx >= 0) {
		altium_record_t *parent_rec = htip_get(&rctx->id2rec, pidx);
		if (parent_rec == NULL) {
			error(rec, ("altium_get_parent(): invalid ownerindex %ld\n", pidx));
			return NULL;
		}
		if (parent_rec->user_data == NULL)
			error(rec, ("altium_get_parent(): invalid group behind ownerindex %ld\n", pidx));
		*stroke = "sym-decor";
		*fill = is_solid ? *stroke : "sym-decor-fill";
		return parent_rec->user_data;
	}

	*stroke = "sheet-decor";
	*fill = is_solid ? *stroke : "sheet-decor-fill";
	return &rctx->alien.sheet->direct;
}


static int altium_parse_arc(io_altium_rctx_t *rctx, altium_record_t *rec, int is_pie, int is_elliptic, int full_circle)
{
	altium_field_t *field;
	long ri = -1, rf = 0, r2i = -1, r2f = 0;
	double r, r2, sa = 0, ea = 0, da, cx = -1, cy = -1, cxf = 0, cyf = 0;
	csch_chdr_t *arc;
	csch_cgrp_t *parent;
	long pidx = -1;
	char *stroke, *fill;
	const char *opartid = NULL, *opartdsp = NULL;

	if (full_circle) {
		sa = 0;
		ea = 360;
	}

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_location_x: cx = conv_long_field(field); break;
			case altium_kw_field_location_y: cy = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: cxf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: cyf = conv_long_field(field); break;
			case altium_kw_field_radius: ri = conv_long_field(field); break;
			case altium_kw_field_radius_frac: rf = conv_long_field(field); break;
			case altium_kw_field_secondaryradius: r2i = conv_long_field(field); break;
			case altium_kw_field_secondaryradius_frac: r2f = conv_long_field(field); break;
			case altium_kw_field_startangle: sa = conv_double_field(field); break;
			case altium_kw_field_endangle: ea = conv_double_field(field); break;
			default: break;
		}
	}

	cx += cxf / FRAC_DIV;
	cy += cyf / FRAC_DIV;

	parent = altium_get_parent(rctx, rec, pidx, 0, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	if ((ea >= 360) && (sa < 0))
		sa = 0;
	if ((cx < 0) || (cy < 0) || (ri < 0)) {
		error(rec, ("altium_parse_arc(): missing coords or radius\n"));
		return -1;
	}

	if (is_elliptic && (r2i < 0)) {
		error(rec, ("altium_parse_arc(): missing secondary radius for elliptical arc\n"));
		return -1;
	}

	r = ri + (double)rf/FRAC_DIV;
	r2 = r2i + (double)r2f/FRAC_DIV;

	if (ea < sa)
		da = 360 - sa + ea;
	else
		da = ea - sa;

	if (is_elliptic && (fabs(r-r2) > 0.1)) {
		double sar = sa / RND_RAD_TO_DEG;
		double dar = da / RND_RAD_TO_DEG;
		arc = csch_alien_mkearc(&rctx->alien, parent, cx, cy, r, r2, sar, dar, stroke, NULL);
	}
	else if (is_pie) {
		arc = csch_alien_mkpoly(&rctx->alien, parent, stroke, fill);
		if (arc != NULL) {
			double ear = ea / RND_RAD_TO_DEG;
			double sar = sa / RND_RAD_TO_DEG;
			csch_alien_append_poly_arc(&rctx->alien, arc, cx, cy, r, sa, da);
			csch_alien_append_poly_line(&rctx->alien, arc, cx + r*cos(ear), cx + r*sin(ear), cx, cy);
			csch_alien_append_poly_line(&rctx->alien, arc, cx, cy, cx + r*cos(sar), cx + r*sin(sar));
		}
	}
	else
		arc = csch_alien_mkarc(&rctx->alien, parent, cx, cy, r, sa, da, stroke);


	if (arc == NULL) {
		error(rec, ("altium_parse_arc(): failed to create arc\n"));
		return -1;
	}

	return 0;
}

static int altium_parse_arcs(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	/* normal arcs */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_12]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_12], rec))
		if (altium_parse_arc(rctx, rec, 0, 0, 0) != 0)
			return -1;

	/* elliptic arcs */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_11]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_11], rec))
		if (altium_parse_arc(rctx, rec, 0, 1, 0) != 0)
			return -1;

	/* pies */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_9]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_9], rec))
		if (altium_parse_arc(rctx, rec, 1, 0, 0) != 0)
			return -1;

	/* elliptical circles */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_8]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_8], rec))
		if (altium_parse_arc(rctx, rec, 0, 1, 1) != 0)
			return -1;

	return 0;
}

/* Read X1, Y1 ... Xn, Yn integer fields into dst in x,y packing */
static int altium_get_multi_xy(io_altium_rctx_t *rctx, vtd0_t *dst, altium_record_t *rec, long *parent_idx, long *aclr)
{
	altium_field_t *field;

	*parent_idx = -1;
	*aclr = -1;
	dst->used = 0;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		if (field->type == altium_kw_field_ownerindex) {
			*parent_idx = conv_long_field(field);
		}
		else if (field->type == altium_kw_field_areacolor) {
			*aclr = conv_long_field(field);
		}
		else if (((field->key[0] == 'X') || (field->key[0] == 'Y')) && isdigit(field->key[1])) {
			long idx;
			char *end;
			
			idx = strtol(field->key+1, &end, 10) - 1;
			idx = idx << 1;
			if (field->key[0] == 'Y') idx++;
			vtd0_enlarge(dst, idx);

			if (strcmp(end, "_FRAC") == 0) { /* fraction part */
				dst->array[idx] += (double)conv_long_field(field) / FRAC_DIV;
			}
			else if (*end == '\0') { /* integer part */
				dst->array[idx] = conv_long_field(field);
			}
			else {
				error(rec, ("altium_get_multi_xy(): invalid index: %s\n", field->key));
				return -1;
			}
		}
	}
	return 0;
}

static int altium_parse_bezier(io_altium_rctx_t *rctx, altium_record_t *rec, vtd0_t *xy)
{
	altium_field_t *field;
	csch_cgrp_t *parent;
	long n, pidx = -1, aclr = -1;
	char *stroke, *fill;
	const char *opartid = NULL, *opartdsp = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			default: break;
		}
	}

	parent = altium_get_parent(rctx, rec, pidx, 0, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	if (altium_get_multi_xy(rctx, xy, rec, &pidx, &aclr) != 0) {
		error(rec, ("altium_parse_lines(): missing coordinate for line\n"));
		return -1;
	}

	for(n = 0; n+7 < xy->used; n += 6)
		csch_alien_mkbezier(&rctx->alien, parent,
			xy->array[n+0], xy->array[n+1],
			xy->array[n+2], xy->array[n+3],
			xy->array[n+4], xy->array[n+5],
			xy->array[n+6], xy->array[n+7],
			stroke);

	return 0;
}

static int altium_parse_beziers(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;
	vtd0_t xy = {0};

	/* normal arcs */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_5]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_5], rec))
		if (altium_parse_bezier(rctx, rec, &xy) != 0)
			return -1;

	vtd0_uninit(&xy);

	return 0;
}


static int altium_create_lines(io_altium_rctx_t *rctx, altium_record_t *rec, vtd0_t *xy, int is_wire, int is_poly, int rect_fields)
{
	long n, pidx, aclr = -1;
	csch_chdr_t *poly;
	csch_cgrp_t *parent;
	char *stroke, *fill;
	const char *opartid = NULL, *opartdsp = NULL;
	int is_solid = 0;

	if (rect_fields) {
		altium_field_t *field;

		pidx = -1;
		xy->used = 0;
		vtd0_enlarge(xy, 3);
		xy->array[0] = xy->array[1] = xy->array[2] = xy->array[3] = -1;

		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_ownerindex: pidx = conv_long_field(field);
				case altium_kw_field_ownerpartid: opartid = field->val.str; break;
				case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
				case altium_kw_field_location_x: xy->array[0] = conv_long_field(field); break;
				case altium_kw_field_location_y: xy->array[1] = conv_long_field(field); break;
				case altium_kw_field_location_x_frac: xy->array[0] += ((double)conv_long_field(field)) / FRAC_DIV; break;
				case altium_kw_field_location_y_frac: xy->array[1] += ((double)conv_long_field(field)) / FRAC_DIV; break;
				case altium_kw_field_corner_x: xy->array[2] = conv_long_field(field); break;
				case altium_kw_field_corner_y: xy->array[3] = conv_long_field(field); break;
				case altium_kw_field_corner_x_frac: xy->array[2] += (double)conv_long_field(field) / FRAC_DIV; break;
				case altium_kw_field_corner_y_frac: xy->array[3] += (double)conv_long_field(field) / FRAC_DIV; break;
				case altium_kw_field_issolid: is_solid = field->val.str[0] == 'T'; break;
				case altium_kw_field_areacolor: aclr = conv_long_field(field); break;
				default: break;
			}
		}
	}
	else if (altium_get_multi_xy(rctx, xy, rec, &pidx, &aclr) != 0) {
		error(rec, ("altium_parse_lines(): missing coordinate for line\n"));
		return -1;
	}

	if (aclr > 0)
		is_solid = alti_is_dark(aclr);

	parent = altium_get_parent(rctx, rec, pidx, is_solid, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	if (is_poly)
		poly = csch_alien_mkpoly(&rctx->alien, parent, stroke, fill);

	for(n = 2; n < xy->used; n += 2) {
		csch_chdr_t *lin;
		double x1 = xy->array[n-2], y1 = xy->array[n-1];
		double x2 = xy->array[n], y2 = xy->array[n+1];

		if (is_poly) {
			csch_alien_append_poly_line(&rctx->alien, poly, x1, y1, x2, y2);
			lin = poly;
		}
		else if (is_wire)
			lin = csch_alien_mknet(&rctx->alien, parent, x1, y1, x2, y2);
		else
			lin = csch_alien_mkline(&rctx->alien, parent, x1, y1, x2, y2, stroke);

		if (lin == NULL) {
			if (!alti_coord_equ_x(rctx, x1, x2) || !alti_coord_equ_y(rctx, y1, y2)) {
				/* it's an error only if line is not 0-long */
				error(rec, ("altium_parse_lines(): failed to create line\n"));
				return -1;
			}
		}
	}

	if (is_poly) {
		double x1 = xy->array[xy->used-2], y1 = xy->array[xy->used-1];
		double x2 = xy->array[0], y2 = xy->array[1];

		csch_alien_append_poly_line(&rctx->alien, poly, x1, y1, x2, y2);
	}


	return 0;
}

static int altium_parse_lines(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;
	altium_field_t *field;
	vtd0_t xy = {0};


	/* decoration lines */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_6]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_6], rec)) {
		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_linewidth: break;
				default: break;
			}
		}
		if (altium_create_lines(rctx, rec, &xy, 0, 0, 0) != 0)
			return -1;
	}

	/* decoration lines */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_13]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_13], rec)) {
		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_linewidth: break;
				default: break;
			}
		}
		if (altium_create_lines(rctx, rec, &xy, 0, 0, 1) != 0)
			return -1;
	}

	/* net lines */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_27]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_27], rec)) {
		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_linewidth: break;
				default: break;
			}
		}
		if (altium_create_lines(rctx, rec, &xy, 1, 0, 0) != 0)
			return -1;
	}

	vtd0_uninit(&xy);
	return 0;
}

static int altium_parse_polys(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;
	altium_field_t *field;
	vtd0_t xy = {0};


	/* decoration lines */
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_7]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_7], rec)) {
		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_linewidth: break;
				default: break;
			}
		}
		if (altium_create_lines(rctx, rec, &xy, 0, 1, 0) != 0)
			return -1;
	}

	vtd0_uninit(&xy);
	return 0;
}

static int altium_parse_rect(io_altium_rctx_t *rctx, altium_record_t *rec, int is_round)
{
	altium_field_t *field;
	long pidx = -1, aclr = -1;
	double x1 = 0, y1 = 0, x2 = 0, y2 = 0, x1f = 0, y1f = 0, x2f = 0, y2f = 0;
	double xr = 0, yr = 0, xrf = 0, yrf = 0;
	csch_chdr_t *poly;
	csch_cgrp_t *parent;
	char *stroke, *fill;
	int is_solid = 0;
	const char *opartid = NULL, *opartdsp = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_location_x: x1 = conv_long_field(field); break;
			case altium_kw_field_location_y: y1 = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: x1f = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: y1f = conv_long_field(field); break;
			case altium_kw_field_corner_x: x2 = conv_long_field(field); break;
			case altium_kw_field_corner_y: y2 = conv_long_field(field); break;
			case altium_kw_field_corner_x_frac: x2f = conv_long_field(field); break;
			case altium_kw_field_corner_y_frac: y2f = conv_long_field(field); break;
			case altium_kw_field_cornerxradius: xr = conv_long_field(field); break;
			case altium_kw_field_corneryradius: yr = conv_long_field(field); break;
			case altium_kw_field_cornerxradius_frac: xrf = conv_long_field(field); break;
			case altium_kw_field_corneryradius_frac: yrf = conv_long_field(field); break;
			case altium_kw_field_issolid: is_solid = field->val.str[0] == 'T'; break;
			case altium_kw_field_areacolor: aclr = conv_long_field(field); break;

			default: break;
		}
	}

	if (aclr > 0)
		is_solid = alti_is_dark(aclr);

	parent = altium_get_parent(rctx, rec, pidx, is_solid, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	if (is_round) {
		if ((xr < 0) || (yr < 0)) {
			error(rec, ("altium_parse_rect(): missing cornerxradius or corneryradius\n"));
			return -1;
		}
		if ((xr != yr)) {
			error(rec, ("altium_parse_rect(): cornerxradius != corneryradius; elliptic corner rounding not yet supported, using circular instead\n"));
			xr = yr = (xr+yr)/2;
		}
	}
	else {
		if ((xr > 0) || (yr > 0))
			error(rec, ("altium_parse_rect(): cornerxradius or corneryradius present in non-round rect (ignoring these fields)\n"));
	}

	x1 += x1f / FRAC_DIV;
	y1 += y1f / FRAC_DIV;
	x2 += x2f / FRAC_DIV;
	y2 += y2f / FRAC_DIV;
	xr += xrf / FRAC_DIV;
	yr += yrf / FRAC_DIV;

	poly = csch_alien_mkpoly(&rctx->alien, parent, stroke, fill);
	if (poly == NULL) {
		error(rec, ("altium_parse_rect(): failed to create poly\n"));
		return -1;
	}

	if (is_round) {
		csch_alien_append_poly_line(&rctx->alien, poly, x1+xr, y1, x2-xr, y1);
		csch_alien_append_poly_arc(&rctx->alien,  poly, x2-xr, y1+yr, xr, -90, 90);
		csch_alien_append_poly_line(&rctx->alien, poly, x2, y1+yr, x2, y2-yr);
		csch_alien_append_poly_arc(&rctx->alien,  poly, x2-xr, y2-yr, xr, 0, 90);
		csch_alien_append_poly_line(&rctx->alien, poly, x2-xr, y2, x1+xr, y2);
		csch_alien_append_poly_arc(&rctx->alien,  poly, x1+xr, y2-yr, xr, 90, 90);
		csch_alien_append_poly_line(&rctx->alien, poly, x1, y2-yr, x1, y1+yr);
		csch_alien_append_poly_arc(&rctx->alien,  poly, x1+xr, y1+yr, xr, 180, 90);
	}
	else {
		csch_alien_append_poly_line(&rctx->alien, poly, x1, y1, x2, y1);
		csch_alien_append_poly_line(&rctx->alien, poly, x2, y1, x2, y2);
		csch_alien_append_poly_line(&rctx->alien, poly, x2, y2, x1, y2);
		csch_alien_append_poly_line(&rctx->alien, poly, x1, y2, x1, y1);
	}

	return 0;
}

static int altium_parse_shref_rect(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	altium_field_t *field;
	csch_source_arg_t *src;
	long pidx = -1, xs = -1, ys = -1;
	long line = rec->idx+1;
	double x1 = -1, y1 = -1, x2 = -1, y2 = -1, x1f = 0, y1f = 0;
	csch_chdr_t *poly;
	csch_cgrp_t *parent, *sym;
	char *stroke = "sym-decor", *fill = "sym-decor-fill", *dummy;
	int is_solid = 0;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
/*			case altium_kw_field_ownerpartid: opartid = field->val.str; break;*/
/*			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;*/
			case altium_kw_field_location_x: x1 = conv_long_field(field); break;
			case altium_kw_field_location_y: y1 = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: x1f = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: y1f = conv_long_field(field); break;
			case altium_kw_field_xsize: xs = conv_long_field(field); break;
			case altium_kw_field_ysize: ys = conv_long_field(field); break;
			default: break;
		}
	}

	if ((x1 < 0) || (y1 < 0) || (xs < 0) || (ys < 0)) {
		error(rec, ("altium_parse_shref_rect(): missing location.x or location.y or xsize or ysize\n"));
		return -1;
	}

	parent = altium_get_parent(rctx, rec, pidx, is_solid, &dummy, &dummy);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	x1 += x1f / FRAC_DIV;
	y1 += y1f / FRAC_DIV;
	x2 = x1 + xs;
	y2 = y1 - ys;

	sym = csch_cgrp_alloc(rctx->alien.sheet, parent, csch_oid_new(rctx->alien.sheet, parent));
	if (sym == NULL) {
		error(rec, ("altium_parse_shref_rect(): Failed to allocate symbol for rail or nc\n"));
		return -1;
	}

	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "role", "symbol", src, NULL);
	sym->role = CSCH_ROLE_SYMBOL;
	TODO("hierarchic: mark this sym as subsheet ref");
	rec->user_data = sym;
	htip_set(&rctx->id2rec, rec->idx, rec);


	sym->x = csch_alien_coord_x(&rctx->alien, x1);
	sym->y = csch_alien_coord_y(&rctx->alien, y1);
	sym->xform.x = xs; sym->xform.y = ys; /* fields are abused to remember xs and ys */

	poly = csch_alien_mkpoly(&rctx->alien, sym, stroke, fill);
	if (poly == NULL) {
		error(rec, ("altium_parse_shref_rect(): failed to create poly\n"));
		return -1;
	}

	/* the whole sym is moved */
	x2 -= x1;
	y2 -= y1;

	csch_alien_append_poly_line(&rctx->alien, poly, 0, 0, x2, 0);
	csch_alien_append_poly_line(&rctx->alien, poly, x2, 0, x2, y2);
	csch_alien_append_poly_line(&rctx->alien, poly, x2, y2, 0, y2);
	csch_alien_append_poly_line(&rctx->alien, poly, 0, y2, 0, 0);

	return 0;
}


static int altium_parse_rects(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_14]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_14], rec))
		if (altium_parse_rect(rctx, rec, 0) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_10]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_10], rec))
		if (altium_parse_rect(rctx, rec, 1) != 0)
			return -1;


	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_15]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_15], rec))
		if (altium_parse_shref_rect(rctx, rec) != 0)
			return -1;

	return 0;
}

static int altium_parse_attrib(io_altium_rctx_t *rctx, altium_record_t *rec, int is_round, const char *force_key, int abs_coord)
{
	altium_field_t *field;
	int ishidden = 0, orientation = 0;
	double x = -1, y = -1, xf = 0, yf = 0;
	long pidx = -1;
	csch_cgrp_t *parent;
	char *stroke, *fill, keytmp[128];
	const char *key = force_key, *val = "";
	csch_source_arg_t *src;
	const char *opartid = NULL, *opartdsp = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_orientation: orientation = conv_long_field(field); break;
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
			case altium_kw_field_name: key = field->val.str; break;
			case altium_kw_field_text: val = field->val.str; break;
			case altium_kw_field_ishidden: ishidden = field->val.str[0] == 'T'; break;
			default: break;
		}
	}

	if (key == NULL) {
		key = keytmp;
		rnd_snprintf(keytmp, sizeof(keytmp), "not_named_in_record_%ld", rec->idx);
	}

	x += xf / FRAC_DIV;
	y += yf / FRAC_DIV;

	parent = altium_get_parent(rctx, rec, pidx, 0, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return 0;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
	csch_attrib_set(&parent->attr, CSCH_ATP_USER_DEFAULT, key, val, src, NULL);

	if (!ishidden) {
		if ((x >= 0) && (y >= 0)) {
			csch_text_t *text;
			int n;

			/* Altium seems to have a next layer of indirection: if key points to
			   a Foo attribute but the value of that attribute is =Bar, then
			   the value of Bar is printed. sch-rnd doesn't have this indirection so
			   the closest thing we can do is to resolve it here and hardwire
			   a reference to the final indirection (at the time of parsing the file) */
			for(n = 0; n < 8; n++) { /* allow at most 8 hops to avoid infinite loop */
				const char *val = csch_attrib_get_str(&parent->attr, key);
				if ((val == NULL) || (*val != '='))
					break;
				key = val+1;
			}

			if (abs_coord) {
				x -= parent->x / rctx->alien.coord_factor;
				y -= parent->y / rctx->alien.coord_factor;
			}

			text = (csch_text_t *)csch_alien_mktext(&rctx->alien, parent, x, y, stroke);
			text->text = rnd_strdup_printf("%%../A.%s%%", key);
			text->dyntext = 1;
			text->hdr.floater = 1;
			if (orientation == 1)
				text->spec_rot = 90;
		}
		else
			error(rec, ("altium_parse_attrib(): can't create visible attrib text with no coords\n"));
	}

	return 0;
}


static int altium_parse_attribs(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_41]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_41], rec))
		if (altium_parse_attrib(rctx, rec, 0, NULL, 0) != 0)
			return -1;


	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_34]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_34], rec))
		if (altium_parse_attrib(rctx, rec, 0, NULL, 0) != 0)
			return -1;

TODO("hierarchical: probably need to use different keys here");

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_32]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_32], rec))
		if (altium_parse_attrib(rctx, rec, 0, "sheet_name", 1) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_33]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_33], rec))
		if (altium_parse_attrib(rctx, rec, 0, "file_name", 1) != 0)
			return -1;

	return 0;
}

static int altium_parse_sign(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_sheet_t *sheet = rctx->alien.sheet;
	altium_field_t *field;
	double x = -1, y = -1, xf = 0, yf = 0;
	long pidx = -1;
	csch_cgrp_t *parent, *grp;
	const char *opartid, *opartdsp, *name;
	char *stroke, *fill;
	csch_text_t *text;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
			case altium_kw_field_name: name = field->val.str; break;
			default: break;
		}
	}

	if (name == NULL) {
		error(rec, ("altium_parse_sign(): sign with no name\n"));
		return -1;
	}

	x += xf / FRAC_DIV;
	y += yf / FRAC_DIV;

	parent = altium_get_parent(rctx, rec, pidx, 0, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return 0;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	grp = csch_cgrp_alloc(sheet, parent, csch_oid_new(sheet, &sheet->direct));
	if (grp == NULL) {
		error(rec, ("altium_parse_sign(): Failed to allocate group for sign\n"));
		return -1;
	}


	grp->x = csch_alien_coord_x(&rctx->alien, x);
	grp->y = csch_alien_coord_y(&rctx->alien, y);

	if (strcmp(name, "DIFFPAIR") == 0) {
		char *stroke = "sheet-decor";
		int x = 5, y = 5;

		csch_alien_mkline(&rctx->alien, grp, 0, 0, x+1, y, stroke);

		csch_alien_mkline(&rctx->alien, grp, x+1, y+1, x+3, y+1, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+3, y+1, x+4, y+2, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+4, y+2, x+6, y+2, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+6, y+2, x+7, y+1, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+7, y+1, x+9, y+1, stroke);

		csch_alien_mkline(&rctx->alien, grp, x+1, y-1, x+3, y-1, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+3, y-1, x+4, y-2, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+4, y-2, x+6, y-2, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+6, y-2, x+7, y-1, stroke);
		csch_alien_mkline(&rctx->alien, grp, x+7, y-1, x+9, y-1, stroke);

	}
	else {
		text = (csch_text_t *)csch_alien_mktext(&rctx->alien, grp, 0, 0, "term-primary");
		text->text = rnd_strdup(name);
	}

	rec->user_data = grp;

	return 0;
}

static int altium_parse_sheet_template(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	rec->user_data = &rctx->alien.sheet->direct;
	return 0;
}

static int altium_parse_signs(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_43]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_43], rec)) {
		if (altium_parse_sign(rctx, rec) != 0)
			return -1;
		htip_set(&rctx->id2rec, rec->idx, rec);
	}
	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_39]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_39], rec)) {
		if (altium_parse_sheet_template(rctx, rec) != 0)
			return -1;
		htip_set(&rctx->id2rec, rec->idx, rec);
	}
	return 0;
}

static int altium_parse_net_labels(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;
	altium_field_t *field;
	csch_line_t *wire, *term = NULL;
	csch_text_t *text;
	csch_source_arg_t *src;
	csch_sheet_t *sheet = rctx->alien.sheet;
	csch_rtree_it_t it;
	csch_rtree_box_t bbox;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_25]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_25], rec)) {
		double x = -1, y = -1, xf = 0, yf = 0;
		int ori = 0;
		const char *name = NULL;
		csch_coord_t x0, y0;

		for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
			switch(field->type) {
				case altium_kw_field_location_x: x = conv_long_field(field); break;
				case altium_kw_field_location_y: y = conv_long_field(field); break;
				case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
				case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
				case altium_kw_field_orientation: ori = conv_long_field(field); break;
				case altium_kw_field_text: name = field->val.str; break;
				default: break;
			}
		}

		if ((x < 0) || (y < 0)) {
			error(rec, ("altium_parse_net_labels(): missing location.x or location.y\n"));
			return -1;
		}
		if (name == NULL) {
			error(rec, ("altium_parse_net_labels(): missing text field for net name - ignoring object\n(This was probably an invisible netname object in Protel)\n"));
			/* Protel 99SE seems to produce net labels with no text; in the Protel
			   GUI these are invisible objects with no effect. Reported by Scott */
			continue;
		}

		x += xf / FRAC_DIV;
		y += yf / FRAC_DIV;

		/* find a wire the label is put on */
		wire = NULL;
		x0 = csch_alien_coord_x(&rctx->alien, x);
		y0 = csch_alien_coord_y(&rctx->alien, y);
		bbox.x1 = x0 - 1;
		bbox.y1 = y0 - 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) {
			csch_comm_str_t pen;

			/* net labels may be placed on terminals... */
			for(term = csch_rtree_first(&it, &sheet->dsply[CSCH_DSPLY_HUBTERM], &bbox); wire != NULL; wire = csch_rtree_next(&it))
				if ((term->hdr.type == CSCH_CTYPE_LINE) && (term->hdr.parent->role == CSCH_ROLE_TERMINAL))
					break;

			if (term == NULL) {
				error(rec, ("altium_parse_net_labels(): label '%s' not on wire line or terminal line - ignoring this label\n", name));
				return 0;
			}


			/* when placed on terminal, the closest thing we can do is a short stub
			   wirenet that should not intersect with anything else */
			pen = csch_comm_str(rctx->alien.sheet, "wire", 1);
			wire = (csch_line_t *)csch_wirenet_draw(rctx->alien.sheet, pen, x0, y0, x0+100, y0+100);
		}

		if (wire != NULL) {
			text = (csch_text_t *)csch_alien_mktext(&rctx->alien, wire->hdr.parent, x, y, "wire");
			text->text = rnd_strdup("%../A.name%");
			text->dyntext = 1;
			text->hdr.floater = 1;
			text->spec_rot = ori * 90;
			src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
			csch_attrib_set(&wire->hdr.parent->attr, CSCH_ATP_USER_DEFAULT, "name", name, src, NULL);
		}
	}

	return 0;
}

typedef struct { int x, y; } alti_sheet_size_t;
static alti_sheet_size_t alti_sheet_sizes[] = {
	{1150, 760},
	{1550, 1110},
	{2230, 1570},
	{3150, 2230},
	{4460, 3150},
	{950, 750},
	{1500, 950},
	{2000, 1500},
	{3200, 2000},
	{4200, 3200},
	{1100, 850},
	{1400, 850},
	{1700, 1100},
	{990, 790},
	{1540, 990},
	{2060, 1560},
	{3260, 2060},
	{4280, 3280}
}; /* size sourc: https://github.com/gsuberland/altium_js */


static int altium_parse_sheet_conf(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;
	altium_field_t *field;
	long csx = -1, csy = -1, sx, sy, marg = -1;
	int bon = 0, sstyle = 0, cus = 0;

	rec = gdl_first(&rctx->tree.rec[altium_kw_record_31]);
	if (rec == NULL)
		return 0;

	if (gdl_next(&rctx->tree.rec[altium_kw_record_31], rec) != NULL)
		error(rec, ("altium_parse_sheet_conf(): multiple RECORD=31 lines are not accepted\n"));

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_customx: csx = conv_long_field(field); break;
			case altium_kw_field_customy: csy = conv_long_field(field); break;
			case altium_kw_field_sheetstyle: sstyle = conv_long_field(field); break;
			case altium_kw_field_usecustomsheet: cus = field->val.str[0] == 'T'; break;
			case altium_kw_field_borderon: bon = field->val.str[0] == 'T'; break;
			case altium_kw_field_custommarginwidth: marg = conv_long_field(field); break;
			default: break;
		}
	}

	if (cus) {
		if ((csx < 0) || (csy < 0)) {
			error(rec, ("altium_parse_sheet_conf(): missing CUSTOMX or CUSTOMY when USECUSTOMSHEET is T\n"));
			return -1;
		}
		sx = csx;
		sy = csy;
	}
	else {
		if (sstyle < 0) {
			error(rec, ("altium_parse_sheet_conf(): missing SHEETSTYLE when USECUSTOMSHEET is false\n"));
			return -1;
		}
		if ((sstyle < 0) || (sstyle >= sizeof(alti_sheet_sizes)/sizeof(alti_sheet_sizes[0]))) {
			error(rec, ("altium_parse_sheet_conf(): unknown SHEETSTYLE value: %d\n", sstyle));
			return -1;
		}
		sx = alti_sheet_sizes[sstyle].x;
		sy = alti_sheet_sizes[sstyle].y;
	}

	if (bon) {
		if ((sx > 0) && (sy > 0)) {
			csch_chdr_t *poly = csch_alien_mkpoly(&rctx->alien, &rctx->alien.sheet->direct, "titlebox-frame", NULL);
			csch_alien_append_poly_line(&rctx->alien, poly, 0,  0,  sx, 0);
			csch_alien_append_poly_line(&rctx->alien, poly, sx, 0,  sx, sy);
			csch_alien_append_poly_line(&rctx->alien, poly, sx, sy, 0,  sy);
			csch_alien_append_poly_line(&rctx->alien, poly, 0,  sy, 0,  0);

			if (marg > 0) {
				csch_chdr_t *poly = csch_alien_mkpoly(&rctx->alien, &rctx->alien.sheet->direct, "titlebox-frame", NULL);
				csch_alien_append_poly_line(&rctx->alien, poly, -marg,   -marg,   sx+marg, -marg);
				csch_alien_append_poly_line(&rctx->alien, poly, sx+marg, -marg,   sx+marg, sy+marg);
				csch_alien_append_poly_line(&rctx->alien, poly, sx+marg, sy+marg, -marg,   sy+marg);
				csch_alien_append_poly_line(&rctx->alien, poly, -marg,   sy+marg, -marg,   -marg);
			}
		}
		else
			error(rec, ("altium_parse_sheet_conf(): invalid border size\n"));
	}

	return 0;
}


static int altium_parse_rail_nc(io_altium_rctx_t *rctx, altium_record_t *rec, int is_rail)
{
	csch_sheet_t *sheet = rctx->alien.sheet;
	csch_cgrp_t *sym, *pin;
	csch_source_arg_t *src;
	altium_field_t *field;
	long line = rec->idx+1, fonth = 3000;
	long plen = 10;
	double rlen = 10.0, rrad = 2.5, tall = 0, x = -1, y = -1, xf = 0, yf = 0, dx, dy;
	int ori = 0, style = 0;
	const char *railname = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_text: railname = field->val.str; break;
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
			case altium_kw_field_orientation: ori = conv_long_field(field); break;
			case altium_kw_field_style: style = conv_long_field(field); break;
			default: break;
		}
	}

	if ((x < 0) || (y < 0)) {
		error(rec, ("altium_parse_rail_nc(): rail object without coords\n"));
		return -1;
	}

	if (ori > 3) {
		error(rec, ("altium_parse_rail_nc(): rail object with invalid orientation %d (should be 0..3)\n", ori));
		return -1;
	}

	if (is_rail && (railname == NULL)) {
		error(rec, ("altium_parse_rail_nc(): rail object without text\n"));
		return -1;
	}

	if (is_rail && (style < 0)) {
		error(rec, ("altium_parse_rail_nc(): rail object without style\n"));
		return -1;
	}

	x += xf / FRAC_DIV;
	y += yf / FRAC_DIV;

	sym = csch_cgrp_alloc(sheet, &sheet->direct, csch_oid_new(sheet, &sheet->direct));
	if (sym == NULL) {
		error(rec, ("altium_parse_rail_nc(): Failed to allocate symbol for rail or nc\n"));
		return -1;
	}

	dx = dy = 0;
	switch(ori) {
		case 0: dx = +1; break;
		case 1: dy = +1; break;
		case 2: dx = -1; break;
		case 3: dy = -1; break;
	}


	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "role", "symbol", src, NULL);
	sym->role = CSCH_ROLE_SYMBOL;
	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "rail", railname, src, NULL);



	if (is_rail) {
		double plinelen = plen;

		/* tune the pin line for decoration */
		switch(style) {
			case 0: plinelen -= rrad*2; break;
			case 3: plinelen -= rrad; break;
			case 6: plinelen -= rlen/2; break;
		}

		src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
		pin = (csch_cgrp_t *)csch_alien_mkpin_line(&rctx->alien, src, sym, x, y, x+dx*plinelen, y+dy*plinelen);

		src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
		csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, "name", "1", src, NULL);


		switch(style) {
			case 4: /* Gnd */
				tall = rlen/5*3;
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/3 + dx*rlen/5, y+dy*plen - dx*rlen/3 + dy*rlen/5,
					x+dx*plen + dy*rlen/3 + dx*rlen/5, y+dy*plen + dx*rlen/3 + dy*rlen/5,
					"sym-decor");
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/6 + dx*rlen/5*2, y+dy*plen - dx*rlen/6 + dy*rlen/5*2,
					x+dx*plen + dy*rlen/6 + dx*rlen/5*2, y+dy*plen + dx*rlen/6 + dy*rlen/5*2,
					"sym-decor");
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/12 + dx*rlen/5*3, y+dy*plen - dx*rlen/12 + dy*rlen/5*3,
					x+dx*plen + dy*rlen/12 + dx*rlen/5*3, y+dy*plen + dx*rlen/12 + dy*rlen/5*3,
					"sym-decor");
				/* fal thru */
			case 2: /* bar  */
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/2, y+dy*plen - dx*rlen/2,
					x+dx*plen + dy*rlen/2, y+dy*plen + dx*rlen/2,
					"sym-decor");
				break;

			case 6: /* earth  */
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plinelen - dy*rlen/2, y+dy*plinelen - dx*rlen/2,
					x+dx*plinelen + dy*rlen/2, y+dy*plinelen + dx*rlen/2,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plinelen - dy*rlen/2, y+dy*plinelen - dx*rlen/2,
					x+dx*plen - dy*rlen/2 + dy*rlen/3, y+dy*plen - dx*rlen/2 + dx*rlen/3,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plinelen, y+dy*plinelen,
					x+dx*plen + dy*rlen/3, y+dy*plen + dx*rlen/3,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plinelen + dy*rlen/2, y+dy*plinelen + dx*rlen/2,
					x+dx*plen + dy*rlen/2 + dy*rlen/3, y+dy*plen + dx*rlen/2 + dx*rlen/3,
					"sym-decor");
				break;

			case 0: /* circle */
				csch_alien_mkarc(&rctx->alien, sym,
					x+dx*plen-dx*rrad, y+dy*plen-dy*rrad, rrad, 0, 360,
					"sym-decor");
				break;

			case 3: /* wave */
				csch_alien_mkarc(&rctx->alien, sym,
					x+dx*plinelen + dy*rrad, y+dy*plinelen, rrad, 180, 180,
					"sym-decor");
				csch_alien_mkarc(&rctx->alien, sym,
					x+dx*plinelen - dy*rrad, y+dy*plinelen, rrad, 0, 180,
					"sym-decor");
				break;

			case 1: /* triangle */
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/8, y+dy*plen - dx*rlen/4,
					x+dx*plen + dy*rlen/8, y+dy*plen + dx*rlen/4,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/8, y+dy*plen - dx*rlen/4,
					x+dx*plen, y+dy*plen + dy*rlen/4,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen + dy*rlen/8, y+dy*plen - dx*rlen/4,
					x+dx*plen, y+dy*plen + dy*rlen/4,
					"sym-decor");
				break;

			case 5: /* sig-gnd: big triangle */
				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/4, y+dy*plen - dx*rlen/4,
					x+dx*plen + dy*rlen/4, y+dy*plen + dx*rlen/4,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen - dy*rlen/4, y+dy*plen - dx*rlen/4,
					x+dx*plen, y+dy*plen + dy*rlen/4,
					"sym-decor");

				csch_alien_mkline(&rctx->alien, sym,
					x+dx*plen + dy*rlen/4, y+dy*plen - dx*rlen/4,
					x+dx*plen, y+dy*plen + dy*rlen/4,
					"sym-decor");
				break;

			default:
				error(rec, ("altium_parse_rail_nc(): invalid style %d\n", style));
		}
	}
	else {
		double xlen = rlen * 0.8;

		/* support the no-connet 'X' only for now */
		x -= dy * plen + dx * plen;
		y -= dy * plen + dx * plen;

		csch_alien_mkline(&rctx->alien, sym,
			x+plen - xlen/2, y+plen - xlen/2,
			x+plen + xlen/2, y+plen + xlen/2,
			"sym-decor");

		csch_alien_mkline(&rctx->alien, sym,
			x+plen + xlen/2, y+plen - xlen/2,
			x+plen - xlen/2, y+plen + xlen/2,
			"sym-decor");
	}

	if (railname != NULL) {
		csch_text_t *text;
		long tcx, tcy, tcx2, ext;
		int has_bbox = 0, align;

		switch(ori) {
			case 1: /* vertical pin, text is horizontal, centered */
			case 3:
				ext = strlen(railname);
				tcx = x+dx*plen - dy*rlen*ext/4;
				tcx2 = x+dx*plen + dy*rlen*ext/4;
				if (tcx > tcx2) {
					tcy = tcx;
					tcx = tcx2;
					tcx2 = tcy;
				}
				if (dy < 0)
					tcy = y+dy*plen*2 - tall - dx*rlen/2;
				else
					tcy = y+dy*plen + tall - dx*rlen/2;
				has_bbox = 1;
				align = CSCH_HALIGN_CENTER;
				break;
			case 0: /* horizontal text, left aligned */
				tcx = x+dx*plen + fonth/rctx->alien.coord_factor/5 + tall;
				tcy = y+dy*plen - fonth/rctx->alien.coord_factor/2;
				align = CSCH_HALIGN_START;
				break;
			case 2: /* horizontal text, right aligned */
				tcx2 = x+dx*plen - fonth/rctx->alien.coord_factor/10 - tall;
				tcx = x+dx*plen - fonth/rctx->alien.coord_factor*strlen(railname) - tall;
				tcy = y+dy*plen - fonth/rctx->alien.coord_factor/2;
				has_bbox = 1;
				align = CSCH_HALIGN_END;
				break;
		}

		text = (csch_text_t *)csch_alien_mktext(&rctx->alien, sym, tcx, tcy, "sym-primary");
		text->text = rnd_strdup(railname);
		text->dyntext = 1;
		text->has_bbox = has_bbox;
		text->spec2.x = csch_alien_coord_x(&rctx->alien, tcx2);
		text->spec2.y = text->spec1.y+fonth;
		text->halign = align;
	}

	/* create the connect arrya attrib to make the actual connection */
	if (is_rail && (railname != NULL)) {
		vts0_t val = {0};
		gds_t tmp = {0};

		gds_append_str(&tmp, "1:");
		gds_append_str(&tmp, railname);
		val.array = &tmp.array;
		val.used = val.alloced = 1;

		src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
		csch_attrib_set_arr(&sym->attr, CSCH_ATP_USER_DEFAULT, "connect", &val, src, NULL);
	}
	return 0;
}


static int altium_parse_rails_ncs(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_17]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_17], rec))
		if (altium_parse_rail_nc(rctx, rec, 1) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_22]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_22], rec))
		if (altium_parse_rail_nc(rctx, rec, 0) != 0)
			return -1;

	return 0;
}

static csch_chdr_t *alti_mkportbox(io_altium_rctx_t *rctx, csch_cgrp_t *parent, double rx1, double ry1, double rx2, double ry2, double left, double right)
{
	csch_chdr_t *poly = csch_alien_mkpoly(&rctx->alien, parent, "sym-decor", "sym-decor-fill");

	csch_alien_append_poly_line(&rctx->alien, poly, rx1+left, ry1, rx2-right, ry1);
	csch_alien_append_poly_line(&rctx->alien, poly, rx2-right, ry1, rx2, 0);
	csch_alien_append_poly_line(&rctx->alien, poly, rx2, 0, rx2-right, ry2);
	csch_alien_append_poly_line(&rctx->alien, poly, rx2-right, ry2, rx1+left, ry2);
	csch_alien_append_poly_line(&rctx->alien, poly, rx1+left, ry2, rx1, 0);
	csch_alien_append_poly_line(&rctx->alien, poly, rx1, 0, rx1+left, ry1);

	return poly;
}


static int altium_parse_port(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_sheet_t *sheet = rctx->alien.sheet;
	csch_cgrp_t *sym;
	csch_source_arg_t *src;
	altium_field_t *field;
	long line = rec->idx+1, fonth = 3000, width, height;
	double x = -1, y = -1, xf = 0, yf = 0, x1, y1, x2, y2, rx1, ry1, rx2, ry2, rot, textrot, textdx, textdy, left, right, ox, tmx1, tmx2;
	int iotype = 3, style = 0, alignment = 0;
	const char *name = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_name: name = field->val.str; break;
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
			case altium_kw_field_iotype: iotype = conv_long_field(field); break;
			case altium_kw_field_style: style = conv_long_field(field); break;
			case altium_kw_field_width: width = conv_long_field(field); break;
			case altium_kw_field_height: height = conv_long_field(field); break;
			case altium_kw_field_alignment: alignment = conv_long_field(field); break;
			default: break;
		}
	}

	if ((x < 0) || (y < 0)) {
		error(rec, ("altium_parse_port(): port without coords\n"));
		return -1;
	}

	if (name == NULL) {
		error(rec, ("altium_parse_port(): port without name\n"));
		return -1;
	}

	x += xf / FRAC_DIV;
	y += yf / FRAC_DIV;

	sym = csch_cgrp_alloc(sheet, &sheet->direct, csch_oid_new(sheet, &sheet->direct));
	if (sym == NULL) {
		error(rec, ("altium_parse_port(): Failed to allocate symbol for rail or nc\n"));
		return -1;
	}

	rot = textrot = textdx = textdy = 0;
	ox = 0;
	tmx1 = 1;
	tmx2 = -1;
	switch(style) {
		case 3: rot = 180; textrot = 180; ox = -width; textdx = width; textdy = height; tmx1 = -1; tmx2 = -3; break;
		case 6: rot = 90; break;
TODO("figure the other directions");
	}

	/* mark target coord for debug */
	/* csch_alien_mkarc(&rctx->alien, &rctx->alien.sheet->direct, x, y, 1, 0, 360, "sheet-decor"); */


	x1 = ox;
	y1 = 0;
	x2 = ox + width;
	y2 = -height;

	rx1 = ox;
	ry1 = 0 + height/2;
	rx2 = x2;
	ry2 = y2 + height/2;

	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "role", "symbol", src, NULL);
	sym->role = CSCH_ROLE_SYMBOL;
	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_attrib_set(&sym->attr, CSCH_ATP_USER_DEFAULT, "rail", name, src, NULL);


	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	csch_alien_mkpin_line(&rctx->alien, src, sym, x1, 0, x1+1, 0);

	if (style == 3) {
		src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
		csch_alien_mkpin_line(&rctx->alien, src, sym, x2, 0, x2-1, 0);
	}

	left = right = 0;
	switch(iotype) {
		case 0:
		case 1:
		case 2:
			left = height/2;
			break;
		case 3:
			left = height/2;
			right = height/2;
			break;
	}

	alti_mkportbox(rctx, sym, rx1, ry1, rx2, ry2, left, right);

	{
		csch_text_t *text;
		long tcx = rx1 + textdx + height/2 * tmx1, tcy = (y1+y2)/2 + textdy, tcx2 = rx2 + textdx + height/2 * tmx2;

		text = (csch_text_t *)csch_alien_mktext(&rctx->alien, sym, tcx, tcy, "sym-primary");
		text->text = rnd_strdup(name);
		text->dyntext = 1;
		text->has_bbox = 1;
		text->spec2.x = csch_alien_coord_x(&rctx->alien, tcx2);
		text->spec2.y = text->spec1.y+fonth;
		text->spec_rot = textrot;
		switch(alignment) {
			case 0: text->halign = CSCH_HALIGN_CENTER; break;
			case 1: text->halign = CSCH_HALIGN_START; break;
		}
	}

	sym->x = csch_alien_coord_x(&rctx->alien, x);
	sym->y = csch_alien_coord_y(&rctx->alien, y);
	sym->spec_rot = rot;

	return 0;
}

static int altium_parse_shref_port(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_cgrp_t *parent, *pin;
	csch_source_arg_t *src;
	altium_field_t *field;
	long line = rec->idx+1, pidx = -1;
	const char *name = NULL, *arrowkind = NULL;
	char *dummy;
	long side = 0, distancefromtop = -1, style = 0, step = 10, iotype = -1;
	double x1, y1, x2, y2, x, y, plen = 15, left = 5, right = 5;
	double fonth = 3000.0, fh = fonth/rctx->alien.coord_factor/2.0, tcx, tcy;
	csch_text_t *text;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_name: name = field->val.str; break;
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
/*			case altium_kw_field_ownerpartid: opartid = field->val.str; break;*/
/*			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;*/
			case altium_kw_field_arrowkind: arrowkind = field->val.str; break;
			case altium_kw_field_side: side = conv_long_field(field); break;
			case altium_kw_field_style: style = conv_long_field(field); break;
			case altium_kw_field_iotype: iotype = conv_long_field(field); break;
			case altium_kw_field_distancefromtop: distancefromtop = conv_long_field(field); break;
			default: break;
		}
	}

	parent = altium_get_parent(rctx, rec, pidx, 0, &dummy, &dummy);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	x1 = 0; y1 = 0;
	x2 = x1 + parent->xform.x; y2 = y1 - parent->xform.y; /* fields are abused to remember sx and sy */


	switch(side) {
		case 0: /* left */
			x = x1; y = y1;
			y -= distancefromtop * step;
			break;
		case 1: /* right */
			x = x2; y = y1;
			y -= distancefromtop * step;
			break;
		case 2: /* top */
			x = x1; y = y1;
			x += distancefromtop * step;
			break;
		case 3: /* bottom */
			x = x1; y = y2;
			x += distancefromtop * step;
			break;
		default:
			error(rec, ("altium_parse_shref_port(): invalid side: %d\n", side));
			return -1;
	}

	switch(style) {
		case 0: /* none, horiz */ left = right = 0; break;
		case 1: /* left */ left = 0; break;
		case 2: /* right */ right = 0; break;
		case 3: /* left and right */ break;
		case 4: /* none, vert */ left = right = 0; break;
		case 5: /* top */ left = 0; break;
		case 6: /* bottom */ right = 0; break;
		case 7: /* top and bottom */ break;
	}

	switch(iotype) {
		case 1: /* output */ right = 0; break;
		case 2: /* input */ left = 0; break;
		case 3: /* bidir */ break;
	}

	tcx = plen+5;
	tcy = -fh;

	if (arrowkind != NULL) {
		if (strcmp(arrowkind, "Arrow Tail") == 0) {
			right = -5;
			plen -= 5;
		}
		else if (strcmp(arrowkind, "Triangle") == 0) {
			plen = 10;
		}
		/* arrow not yet supported */
	}

	src = csch_attrib_src_c(rctx->fn, line, 0, NULL);
	pin = (csch_cgrp_t *)csch_alien_mkpin_line(&rctx->alien, src, parent, 0, 0, 2, 0);
	pin->x = csch_alien_coord_x(&rctx->alien, x);
	pin->y = csch_alien_coord_y(&rctx->alien, y);
	alti_mkportbox(rctx, pin, 0, +4, plen, -4, left, right);


	text = (csch_text_t *)csch_alien_mktext(&rctx->alien, pin, tcx, tcy, "sym-primary");
	text->text = rnd_strdup(name);

	switch(side) {
		case 0: break;
		case 1: pin->mirx = 1; break;
		case 2: pin->spec_rot = -90; pin->mirx = 1; break;
		case 3: pin->spec_rot = 90; break;
	}

	return 0;
}

static int altium_parse_ports(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_18]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_18], rec))
		if (altium_parse_port(rctx, rec) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_16]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_16], rec))
		if (altium_parse_shref_port(rctx, rec) != 0)
			return -1;

	return 0;
}

static void TEXT_JUST(io_altium_rctx_t *rctx, altium_record_t *rec, csch_text_t *text, int just)
{
	csch_coord_t fh = 3000;

/* 1=bottom center
   2=bottom right
   3=center left
   4=center center
   5=center right
   6=top left
   7=top center
   8=top right */

	/* horizontal component */
	switch(just) {
		case 0: case 3: case 6: break; /* left */
		case 1: case 4: case 7: break; /* center: can't do without bbox based */
		case 2: case 5: case 8: text->spec_mirx = 1; break; /* right */
		default:
			error(rec, ("altium_parse_note(): invalid justification value %d\n", just));
	}

	switch(just) {
		case 0: case 1: case 2: break; /* bottom */
		case 3: case 4: case 5: text->spec1.y -= fh/2; break; /* center */
		case 6: case 7: case 8: text->spec1.y -= fh; break; /* top */
	}

}

static int altium_parse_note(io_altium_rctx_t *rctx, altium_record_t *rec, int has_frame, int fold_corner, int allow_multiline)
{
	altium_field_t *field;
	csch_cgrp_t *parent;
	long x1 = 0, y1 = 0, x2 = -1, y2 = -1, aclr = -1, rad = 5, fonth = 3000, pidx = -1;
	int is_solid = 0, just = 0, ori = 0;
	char *stroke, *fill, *textstr = NULL;
	const char *opartid = NULL, *opartdsp = NULL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_text: textstr = (char *)field->val.str; break;
			case altium_kw_field_location_x: x1 = conv_long_field(field); break;
			case altium_kw_field_location_y: y1 = conv_long_field(field); break;
			case altium_kw_field_corner_x: x2 = conv_long_field(field); break;
			case altium_kw_field_corner_y: y2 = conv_long_field(field); break;
			case altium_kw_field_areacolor: aclr = conv_long_field(field); break;
			case altium_kw_field_showborder: if (field->val.str[0] == 'T') has_frame = 1; break;
			case altium_kw_field_issolid: is_solid = field->val.str[0] == 'T'; break;
			case altium_kw_field_justification: just = conv_long_field(field); break;
			case altium_kw_field_orientation: ori = conv_long_field(field); break;

			default: break;
		}
	}

	if (allow_multiline && ((x2 < 0) || (y2 < 0))) {
		error(rec, ("altium_parse_note(): note rectangle without coords (end)\n"));
		return -1;
	}

	if (aclr > 0)
		is_solid = alti_is_dark(aclr);

	parent = altium_get_parent(rctx, rec, pidx, is_solid, &stroke, &fill);
	if (parent == NOGRP)
		return 0;
	if (parent == NULL)
		return -1;

	if (alti_slot_skip(parent, opartid, opartdsp))
		return 0;

	if (has_frame) {
		csch_chdr_t *poly = csch_alien_mkpoly(&rctx->alien, parent, stroke, fill);

		if (poly == NULL) {
			error(rec, ("altium_parse_note(): failed to create poly\n"));
			return -1;
		}

		if (fold_corner) {
			csch_alien_append_poly_line(&rctx->alien, poly, x1, y1, x2-rad, y1);
			csch_alien_append_poly_line(&rctx->alien, poly, x2-rad, y1, x2, y1+rad);
			csch_alien_append_poly_line(&rctx->alien, poly, x2, y1+rad, x2, y2);
			csch_alien_append_poly_line(&rctx->alien, poly, x2, y2, x1, y2);
			csch_alien_append_poly_line(&rctx->alien, poly, x1, y2, x1, y1);
		}
		else {
			csch_alien_append_poly_line(&rctx->alien, poly, x1, y1, x2, y1);
			csch_alien_append_poly_line(&rctx->alien, poly, x2, y1, x2, y2);
			csch_alien_append_poly_line(&rctx->alien, poly, x2, y2, x1, y2);
			csch_alien_append_poly_line(&rctx->alien, poly, x1, y2, x1, y1);
		}
	}

	if (textstr != NULL) {
		csch_text_t *text;

		if (allow_multiline) {
			char *curr, *next = NULL;
			long step = fonth/rctx->alien.coord_factor*1.2, y;

			TODO("multiline: rewrite this when we have multi-line text support - but keep ~ escaping: ~1 is newline, ~~1 is escaped ~1");
			for(y = y2-step, curr = textstr; curr != NULL; y-=step, curr = next) {
				char *srch = curr;
				int has_escaped = 0;

				/* search next non-escaped ~1 for newline */
				for(;;) {
					next = strchr(srch, '~'); /* handle only ~1 for now */
					if ((next == NULL) || (next[1] != '~'))
						break;
					srch = next+2;
					has_escaped = 1;
				}

				if (next != NULL) {
					*next = '\0';
					next += 2;
				}
				if (*curr != '\0') {
					text = (csch_text_t *)csch_alien_mktext(&rctx->alien, parent, x1, y, "sheet-decor");
					text->text = rnd_strdup(curr);
					if (has_escaped) { /* remove duplicate ~ from escaped ~~1 */
						char *si, *so;
						for(si = so = text->text; *si != '\0'; si++,so++) {
							if ((si[0] == '~') && (si[1] == '~'))
								si++;
							if (si != so)
								*so = *si;
						}
						*so = '\0';
					}
					TEXT_JUST(rctx, rec, text, just);
				}
			}
		}
		else {
			text = (csch_text_t *)csch_alien_mktext(&rctx->alien, parent, x1, y1, "sheet-decor");
			text->text = rnd_strdup(textstr);
			text->spec_rot = ori * 90;
			TEXT_JUST(rctx, rec, text, just);
		}
	}

	return 0;
}

static int altium_parse_notes(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_4]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_4], rec))
		if (altium_parse_note(rctx, rec, 0, 0, 0) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_28]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_28], rec))
		if (altium_parse_note(rctx, rec, 1, 0, 1) != 0)
			return -1;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_209]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_209], rec))
		if (altium_parse_note(rctx, rec, 1, 1, 1) != 0)
			return -1;


	return 0;
}


static int altium_parse_junction(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_sheet_t *sheet = rctx->alien.sheet;
	altium_field_t *field;
	long x = -1, y = -1;
	csch_rtree_it_t it;
	csch_rtree_box_t bbox;
	csch_chdr_t *wire, *wires[128];
	int n, numw = 0;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			default: break;
		}
	}

	if ((x < 0) || (y < 0)) {
		error(rec, ("altium_parse_junction(): missing coords\n"));
		return -1;
	}


	wire = NULL;
	bbox.x1 = csch_alien_coord_x(&rctx->alien, x);
	bbox.y1 = csch_alien_coord_y(&rctx->alien, y);
	bbox.x2 = bbox.x1 + 1;
	bbox.y2 = bbox.y1 + 1;
	for(wire = csch_rtree_first(&it, &sheet->dsply[CSCH_DSPLY_WIRE], &bbox); wire != NULL; wire = csch_rtree_next(&it)) {
		if ((wire->type == CSCH_CTYPE_LINE) && (wire->parent->role == CSCH_ROLE_WIRE_NET)) {
			int skip = 0;

			/* add only unique wirenets */
			for(n = 0; n < numw; n++) {
				if (wires[n]->parent == wire->parent) {
					skip = 1;
					break;
				}
			}

			if (!skip) {
				wires[numw] = wire;
				numw++;
				if (numw >= (sizeof(wires)/sizeof(wires[0]))) {
					error(rec, ("altium_parse_junction(): too many wires at %ld %ld\n", x, y));
					return -1;
				}
			}
		}
	}

	/* debug drawing of junction locations */
	if (0) {
		csch_alien_mkarc(&rctx->alien, &rctx->alien.sheet->direct, x, y, 6, 0, 360, "sheet-decor");
		if (numw > 1)
			csch_alien_mkarc(&rctx->alien, &rctx->alien.sheet->direct, x, y, 8, 0, 360, "sheet-decor");
	}

	/* merge wirenets to get junctions; only if there are at least two different
	   wirenets found at the junction location (else ignore the junction) */
	if (numw < 2)
		return 0;

	wires[0]->parent->role = 0; /* disable side effects during the merge */
	for(n = 1; n < numw; n++) {
		wires[0]->parent->role = 0;
		csch_op_merge_into(sheet, wires[0]->parent, wires[n]->parent);
		wires[0]->parent->role = CSCH_ROLE_WIRE_NET;
	}
	wires[0]->parent->role = CSCH_ROLE_WIRE_NET;
	csch_wirenet_recalc_junctions(sheet, wires[0]->parent);

	return 0;
}

static int altium_parse_junctions(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_29]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_29], rec))
		if (altium_parse_junction(rctx, rec) != 0)
			return -1;

	return 0;
}

static void *altium_prealloc_sym(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_cgrp_t *parent = &rctx->alien.sheet->direct;
	csch_cgrp_t *sym;
	altium_field_t *field;
	csch_source_arg_t *src;

	src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
	sym = csch_cgrp_alloc(rctx->alien.sheet, parent, csch_oid_new(rctx->alien.sheet, parent));
	csch_cobj_attrib_set(rctx->alien.sheet, sym, CSCH_ATP_HARDWIRED, "role", "symbol", src);
	sym->role = CSCH_ROLE_SYMBOL;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_currentpartid:
				src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
				csch_cobj_attrib_set(rctx->alien.sheet, sym, CSCH_ATP_HARDWIRED, "-currentpartid", field->val.str, src);
				break;
			default: break;
		}
	}
	return sym;
}

#define allocdst() \
do { \
	if (*dst == NULL) { \
		*dst = csch_cgrp_alloc(rctx->alien.sheet, pin, csch_oid_new(rctx->alien.sheet, pin)); \
		if (*dst == NULL) { \
			error(rec, ("alti_pin_gfx_inside(): Failed to allocate group for pin gfx\n")); \
			return 0; \
		} \
	} \
} while(0)

#define diamond(top, mid, bot) \
do { \
	csch_alien_mkline(&rctx->alien, *dst,  0,  0, -2, -2, pen); \
	csch_alien_mkline(&rctx->alien, *dst, -2, -2, -4,  0, pen); \
	csch_alien_mkline(&rctx->alien, *dst, -4, -0, -2, +2, pen); \
	csch_alien_mkline(&rctx->alien, *dst, -2, +2,  0,  0, pen); \
	if (bot) \
		csch_alien_mkline(&rctx->alien, *dst, +0.5, -2, -4.5, -2, pen); \
	if (mid) \
		csch_alien_mkline(&rctx->alien, *dst, +0.5,  0, -4.5,  0, pen); \
	if (top) \
		csch_alien_mkline(&rctx->alien, *dst, +0.5, +2, -4.5, +2, pen); \
} while(0)

/* Return horizontal size of graphics in altium coords. Graphics is drawn in *dst. */
static int alti_pin_gfx_inside(io_altium_rctx_t *rctx, altium_record_t *rec, csch_cgrp_t *pin, csch_cgrp_t **dst, long inr, long ine, int textrot)
{
	const char *pen = "term-decor";
	int ox = 0;

	*dst = NULL;

	switch(ine) {
		case 3: /* clock */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst,  +2,  +2, -2, 0, pen);
			csch_alien_mkline(&rctx->alien, *dst,  +2,  -2, -2, 0, pen);
			ox = 2;
			break;
	}

	switch(inr) {
		case -1:
		case 0:
			return ox;

		case 8: /* postponed */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst, 0, 0+1, 0, +3+1, pen);
			csch_alien_mkline(&rctx->alien, *dst, 0, +3+1, -3, +3+1, pen);
			return 5+ox;


		case 9: /* open collector */
			allocdst();
			diamond(0, 0, 1);
			return 5+ox;

		case 10: /* hiz */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst,  0,  +1.5, -3,   +1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst,  0,  +1.5, -1.5, -2, pen);
			csch_alien_mkline(&rctx->alien, *dst, -3,  +1.5, -1.5, -2, pen);
			return 5+ox;

		case 11: /* high current */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst,  0,  0, -3.5, +1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst,  0,  0, -3.5, -1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst, -3.5, +1.5, -3.5, -1.5, pen);
			return 5+ox;

		case 12: /* pulse */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst,  0,  0, -2, 0,  pen);
			csch_alien_mkline(&rctx->alien, *dst, -2,  0, -2, +2, pen);
			csch_alien_mkline(&rctx->alien, *dst, -2, +2, -4, +2, pen);
			csch_alien_mkline(&rctx->alien, *dst, -4, +2, -4, 0, pen);
			csch_alien_mkline(&rctx->alien, *dst, -4, 0, -6, 0, pen);
			return 7+ox;

		case 13: /* schmitt */
			{
				int sgn = textrot ? -1 : +1; /* don't y-mirror this one on text rotation */
				allocdst();
				csch_alien_mkline(&rctx->alien, *dst,   1,   +3*sgn,  -1, +2.1*sgn,  pen);
				csch_alien_mkline(&rctx->alien, *dst,  -1, +2.1*sgn,  -1, -1.5*sgn,  pen);
				csch_alien_mkline(&rctx->alien, *dst,  -1, -1.5*sgn,  -8, -3*sgn,    pen);
				csch_alien_mkline(&rctx->alien, *dst,  -8,   -3*sgn,  -6, -2.1*sgn,  pen);
				csch_alien_mkline(&rctx->alien, *dst,  -6, -2.1*sgn,  -6, +1.5*sgn,  pen);
				csch_alien_mkline(&rctx->alien, *dst,  -6, +1.5*sgn,   1, +3*sgn,    pen);
			}
			return 9+ox;

		case 22: /* open collector + pullup */
			allocdst();
			diamond(0, 1, 1);
			return 5+ox;

		case 23: /* open emitter */
			allocdst();
			diamond(1, 0, 0);
			return 5+ox;

		case 24: /* open emitter + pullup */
			allocdst();
			diamond(1, 1, 0);
			return 5+ox;

		case 30: /* shift left */
			allocdst();
			csch_alien_mkline(&rctx->alien, *dst,  0,  0, -2.5, +1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst,  0,  0, -2.5, -1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst, -2.5, +1.5, -2.5, -1.5, pen);
			csch_alien_mkline(&rctx->alien, *dst, -2.5, 0, -4.5, 0, pen);
			return 5+ox;

		case 32: /* open output */
			allocdst();
			diamond(0, 0, 0);
			return 5+ox;


		default:
			error(rec, ("io_altium: unknown pin inner edge type: %ld\n", inr));
	}

	return ox;
}

static void *altium_prealloc_pin(io_altium_rctx_t *rctx, altium_record_t *rec)
{
	csch_cgrp_t *parent = NULL, *pin, *igfx = NULL;
	csch_source_arg_t *src;
	altium_field_t *field;
	altium_record_t *prec;
	const char *designator = NULL, *name = NULL, *opartid = NULL, *opartdsp = NULL;
	double x = 0, y = 0, xf = 0, yf = 0, len = -1, lenf = 0, startx, starty, endx, endy, nameshx;
	long pidx = -1, cong = -1, oute = 0, outr = 0, ine = 0, inr = 0, ele = 0;
	long r = 3, fonth = 3000;
	int textrot, textydir;

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		switch(field->type) {
			case altium_kw_field_ownerindex: pidx = conv_long_field(field); break;
			case altium_kw_field_ownerpartid: opartid = field->val.str; break;
			case altium_kw_field_ownerpartdisplaymode: opartdsp = field->val.str; break;
			case altium_kw_field_name: name = field->val.str; break;
			case altium_kw_field_designator: designator = field->val.str; break;
			case altium_kw_field_location_x: x = conv_long_field(field); break;
			case altium_kw_field_location_y: y = conv_long_field(field); break;
			case altium_kw_field_location_x_frac: xf = conv_long_field(field); break;
			case altium_kw_field_location_y_frac: yf = conv_long_field(field); break;
			case altium_kw_field_pinlength: len = conv_long_field(field); break;
			case altium_kw_field_pinlength_frac: lenf = conv_long_field(field); break;
			case altium_kw_field_pinconglomerate: cong = conv_long_field(field); break;
			case altium_kw_field_symbol_outer: outr = conv_long_field(field); break;
			case altium_kw_field_symbol_outeredge: oute = conv_long_field(field); break;
			case altium_kw_field_symbol_inner: inr = conv_long_field(field); break;
			case altium_kw_field_symbol_inneredge: ine = conv_long_field(field); break;
			case altium_kw_field_electrical: ele = conv_long_field(field); break;
			default: break;
		}
	}

	if (len < 0) {
		error(rec, ("altium_prealloc_pin(): length\n"));
		return NULL;
	}

	x += (double)xf / FRAC_DIV;
	y += (double)yf / FRAC_DIV;
	len += (double)lenf / FRAC_DIV;

	if (cong < 0) {
		error(rec, ("altium_prealloc_pin(): missing pinconglomerate\n"));
		return NULL;
	}

	if (pidx >= 0) {
		prec = htip_get(&rctx->id2rec, pidx);
		if (prec != NULL)
			parent = prec->user_data;
	}

	if (parent == NULL) {
		error(rec, ("altium_prealloc_pin(): missing or invalid ownerindex\n"));
		return NULL;
	}

	if (alti_slot_skip(parent, opartid, opartdsp))
		return (void *)NOGRP;

	/* orientation */
	switch(cong & 0x03) {
		case 0:
		case 1: textrot = 0; textydir = -1; break;
		case 2:
		case 3: textrot = 1; textydir = +1; break;
	}

	/* room for the decoration */
	startx = x;
	starty = y;
	switch(oute) {
		case 1: /* negation circle */
			startx += 2*r;
			break;
		case 4: /* low input? */
		case 17: /* low output? */
			break;
	}

	endx = x+len;
	endy = y;

	src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
	pin = (csch_cgrp_t *)csch_alien_mkpin_line(&rctx->alien, src, parent, startx, starty, endx, endy);

	nameshx = alti_pin_gfx_inside(rctx, rec, pin, &igfx, inr, ine, textrot);

	TODO("set attributes for the DRC, once we have a DRC");
	/* decoration on the outside */
	switch(oute) {
		case 1: /* negation circle */
			csch_alien_mkarc(&rctx->alien, (csch_cgrp_t *)pin, x+r, y, r, 0, 360, "term-decor");
			break;
		case 4: /* active low input */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty, startx+5, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty-1.5, startx+5, starty, "term-decor");
			break;
		case 17: /* active low output */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty, startx, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty-1.5, startx, starty, "term-decor");
			break;
	}

	switch(ele) {
		case 4: /* normal */ break;
		case 0: /* input */ outr = 2; break;
		case 2: /* output */ outr = 33; break;
	}

	switch(outr) {
		case 2:  /* arrow in */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty, startx+5, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty, startx+5, starty+1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty-1.5, startx+5, starty+1.5, "term-decor");
			break;

		case 5:  /* analog */
			csch_alien_mkarc(&rctx->alien, (csch_cgrp_t *)pin, startx+2, starty-1.7, 1.5, 0, -180, "term-decor");
			break;

		case 6:  /* not logic */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty+2, startx+1, starty-2, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty-2, startx+1, starty+2, "term-decor");
			break;

		case 33: /* arrow out */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty, startx, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+5, starty, startx, starty+1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty-1.5, startx, starty+1.5, "term-decor");
			break;

		case 34: /* arrow in-out */
			/* in */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty, startx+4, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx, starty, startx+4, starty+1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+4, starty-1.5, startx+4, starty+1.5, "term-decor");

			/* out */
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+10, starty, startx+6, starty-1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+10, starty, startx+6, starty+1.5, "term-decor");
			csch_alien_mkline(&rctx->alien, (csch_cgrp_t *)pin, startx+6, starty-1.5, startx+6, starty+1.5, "term-decor");
			break;
	}

	if (igfx != NULL) {
		/* place inner graphics in front of name text; y is aligned with the pin line's */
		igfx->x = csch_alien_coord_x(&rctx->alien, x - 2);
		igfx->y = csch_alien_coord_y(&rctx->alien, y);
		if (textrot)
			igfx->miry = 1;
	}

	/* labels and attributes */
	if (name != NULL) {
		src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
		csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, "name", name, src, NULL);

		if (cong & 0x08) { /* show pin name */
			csch_text_t *text = (csch_text_t *)csch_alien_mktext(&rctx->alien, pin, x - 7 - nameshx, y + fonth/rctx->alien.coord_factor/2 * textydir, "sym-decor");
			text->text = rnd_strdup("%../A.name%");
			text->dyntext = 1;
			if (textrot)
				text->spec_rot = 180;
			else
				text->spec_mirx = 1;
		}
	}

	if (designator != NULL) {
		src = csch_attrib_src_c(rctx->fn, rec->idx+1, 0, NULL);
		csch_attrib_set(&pin->attr, CSCH_ATP_USER_DEFAULT, "pinnum", designator, src, NULL);

		if (cong & 0x10) { /* show pin number (designator) */
			csch_text_t *text = (csch_text_t *)csch_alien_mktext(&rctx->alien, pin, (startx+endx)/2, (starty+endy)/2, "term-primary");
			text->text = rnd_strdup("%../a.display/name%");
			text->dyntext = 1;
			if (textrot) {
				text->spec_rot = 180;
				text->spec_mirx = 1;
			}
		}
	}

	csch_rotate90(parent->hdr.sheet, &pin->hdr, csch_alien_coord_x(&rctx->alien, x), csch_alien_coord_y(&rctx->alien, y), cong & 0x03, 0);

	return pin;
}

static int altium_map_indices(io_altium_rctx_t *rctx)
{
	altium_record_t *rec;

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_1]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_1], rec)) {
		rec->user_data = altium_prealloc_sym(rctx, rec);
		htip_set(&rctx->id2rec, rec->idx, rec);
	}

	for(rec = gdl_first(&rctx->tree.rec[altium_kw_record_2]); rec != NULL; rec = gdl_next(&rctx->tree.rec[altium_kw_record_2], rec)) {
		rec->user_data = altium_prealloc_pin(rctx, rec);
		htip_set(&rctx->id2rec, rec->idx, rec);
	}

	return 0;
}

static void rctx_init(io_altium_rctx_t *rctx, csch_sheet_t *dst)
{
	rctx->alien.sheet = dst;
	rctx->alien.fmt_prefix = "io_altium";
	rctx->alien.coord_factor = io_altium_conf.plugins.io_altium.coord_mult;
	rctx->alien.flip_y = 0;

	htip_init(&rctx->id2rec, longhash, longkeyeq);
}

static void rctx_uninit(io_altium_rctx_t *rctx)
{
	htip_uninit(&rctx->id2rec);
}

int altium_parse_sheet(io_altium_rctx_t *rctx, csch_sheet_t *dst)
{
	altium_record_t *rec;
	altium_field_t *field;
	int res = 0;


	/* process the header */
	rec = gdl_first(&rctx->tree.rec[altium_kw_record_header]);
	if (rec == NULL) {
		error(rec, ("altium_parse_sheet(): missing HEADER record\n"));
		return -1;
	}
	field = gdl_first(&rec->fields);
	if (field == NULL) {
		error(rec, ("altium_parse_sheet(): broken HEADER record\n"));
		return -1;
	}

	for(field = gdl_first(&rec->fields); field != NULL; field = gdl_next(&rec->fields, field)) {
		if (field->type == altium_kw_field_header) {
			TODO("save this as a sheet attrib");
			rnd_trace("altium header: '%s'\n", field->val);
		}
	}

	rctx_init(rctx, dst);
	csch_alien_sheet_setup(&rctx->alien, 1);

	res |= altium_map_indices(rctx);
	res |= altium_parse_signs(rctx);
	res |= altium_parse_sheet_conf(rctx);
	res |= altium_parse_arcs(rctx);
	res |= altium_parse_beziers(rctx);
	res |= altium_parse_polys(rctx);
	res |= altium_parse_rects(rctx);
	res |= altium_parse_lines(rctx);
	res |= altium_parse_net_labels(rctx); /* must be after altium_parse_lines() */
	res |= altium_parse_rails_ncs(rctx);
	res |= altium_parse_ports(rctx);
	res |= altium_parse_notes(rctx);
	res |= altium_parse_attribs(rctx);
	res |= altium_parse_junctions(rctx);

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

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

		if (io_altium_conf.plugins.io_altium.rename_redundant_pins)
			csch_alien_postproc_rename_redundant_terms(&rctx->alien);

		if (io_altium_conf.plugins.io_altium.emulate_text_ang_180)
			csch_alien_postproc_text_autorot(&rctx->alien, &dst->direct, 1, 1);

		if ((res == 0) && io_altium_conf.plugins.io_altium.auto_normalize)
			csch_alien_postproc_normalize(&rctx->alien);
	}

	rctx_uninit(rctx);

	return res;
}
