/*******************************************************************

Part of the Fritzing project - http://fritzing.org
Copyright (c) 2007-2019 Fritzing

Fritzing 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 3 of the License, or
(at your option) any later version.

Fritzing 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 Fritzing.  If not, see <http://www.gnu.org/licenses/>.

********************************************************************/

#include "fsvgrenderer.h"
#include "debugdialog.h"
#include "svg/svgfilesplitter.h"
#include "utils/fmessagebox.h"
#include "utils/textutils.h"
#include "utils/graphicsutils.h"
#include "connectors/svgidlayer.h"

#include <QTextStream>
#include <QPainter>
#include <QCoreApplication>
#include <QtGlobal>
#include <QFileInfo>
#include <QtSvgWidgets/QGraphicsSvgItem>

#include <qnumeric.h>

/////////////////////////////////////////////

QString FSvgRenderer::NonConnectorName("nonconn");

static ConnectorInfo VanillaConnectorInfo;

FSvgRenderer::FSvgRenderer(QObject * parent) : QSvgRenderer(parent)
{
	m_defaultSizeF = QSizeF(0,0);
}

FSvgRenderer::~FSvgRenderer()
{
	clearConnectorInfoHash(m_connectorInfoHash);
	clearConnectorInfoHash(m_nonConnectorInfoHash);
}

void FSvgRenderer::initNames() {
	VanillaConnectorInfo.gotPath = false;
	VanillaConnectorInfo.gotCircle = false;
}

void FSvgRenderer::clearConnectorInfoHash(QHash<QString, ConnectorInfo *> & hash) {
	Q_FOREACH (ConnectorInfo * connectorInfo, hash.values()) {
		delete connectorInfo;
	}
	hash.clear();
}

void FSvgRenderer::cleanup() {

}

QByteArray FSvgRenderer::loadSvg(const QString & filename) {
	LoadInfo loadInfo(filename);
	return loadSvg(loadInfo);
}

QByteArray FSvgRenderer::loadSvg(const LoadInfo & loadInfo) {
	if (!QFileInfo(loadInfo.filename).exists() || !QFileInfo(loadInfo.filename).isFile()) {
		return QByteArray();
	}

	QFile file(loadInfo.filename);
	if (!file.open(QFile::ReadOnly | QFile::Text)) {
		return QByteArray();
	}

	QByteArray contents = file.readAll();
	file.close();

	if (contents.length() <= 0) return QByteArray();

	return loadAux(contents, loadInfo);

}

bool FSvgRenderer::loadSvgString(const QString & svg) {
	QByteArray byteArray(svg.toUtf8());
	QByteArray result = loadSvg(byteArray, "", true);
	return (!result.isEmpty());
}

bool FSvgRenderer::loadSvgString(const QString & svg, QString & newSvg) {
	QByteArray byteArray(svg.toUtf8());
	QByteArray result = loadSvg(byteArray, "", true);
	newSvg = QString(result);
	return (!result.isEmpty());
}

QByteArray FSvgRenderer::loadSvg(const QByteArray & contents, const QString & filename, bool findNonConnectors) {
	LoadInfo loadInfo(filename, findNonConnectors);
	return loadAux(contents, loadInfo);
}

QByteArray FSvgRenderer::loadSvg(const QByteArray & contents, const LoadInfo & loadInfo) {
	return loadAux(contents, loadInfo);
}

QByteArray FSvgRenderer::loadAux(const QByteArray & theContents, const LoadInfo & loadInfo)
{
	QByteArray cleanContents(theContents);
	bool cleaned = false;

	QString string(cleanContents);
	if (TextUtils::fixMuch(string, false)) {
		cleaned = true;
	}
	if (TextUtils::fixPixelDimensionsIn(string)) {
		cleaned = true;
	}
	if (cleaned) {
		cleanContents = string.toUtf8();
	}

	QString errorStr;
	int errorLine;
	int errorColumn;
	QDomDocument doc;
	if (!doc.setContent(cleanContents, &errorStr, &errorLine, &errorColumn)) {
		DebugDialog::debug(QString("renderer loadAux failed %1 %2 %3 %4").arg(loadInfo.filename).arg(errorStr).arg(errorLine).arg(errorColumn));
	}

	bool resetContents = false;

	QDomElement root = doc.documentElement();
	if (!loadInfo.setColor.isEmpty()) {
		QDomElement element = TextUtils::findElementWithAttribute(root, "id", loadInfo.colorElementID);
		if (!element.isNull()) {
			QStringList exceptions;
			exceptions << "black" << "#000000";
			SvgFileSplitter::fixColorRecurse(element, loadInfo.setColor, exceptions);
			resetContents = true;
		}
	}
	if (loadInfo.connectorIDs.count() > 0) {
		bool init =  initConnectorInfo(doc, loadInfo);
		resetContents = resetContents || init;
	}
	if (loadInfo.findNonConnectors) {
		initNonConnectorInfo(doc, loadInfo.filename);
	}

	if (resetContents) {
		cleanContents = TextUtils::removeXMLEntities(doc.toString()).toUtf8();
	}


	/*
	cleanContents = doc.toByteArray();

	//QFile file("all.txt");
	//if (file.open(QIODevice::Append)) {
		//QTextStream t(&file);
		//t << cleanContents;
		//file.close();
	//}

	*/

	//DebugDialog::debug(cleanContents.data());

	return finalLoad(cleanContents, loadInfo.filename);
}

QByteArray FSvgRenderer::finalLoad(QByteArray & cleanContents, const QString & filename) {

	QXmlStreamReader xml(cleanContents);
	bool result = determineDefaultSize(xml);
	if (!result) {
		return QByteArray();
	}

	result = QSvgRenderer::load(cleanContents);
	if (result) {
		m_filename = filename;
		return cleanContents;
	}

	return QByteArray();
}

bool FSvgRenderer::fastLoad(const QByteArray & contents) {
	return QSvgRenderer::load(contents);
}

QPixmap * FSvgRenderer::getPixmap(QSvgRenderer * renderer, QSize size)
{
	auto *pixmap = new QPixmap(size);
	pixmap->fill(Qt::transparent);
	QPainter painter(pixmap);
	// preserve aspect ratio
	QSizeF def = renderer->defaultSize();
	auto * frenderer = qobject_cast<FSvgRenderer *>(renderer);
	if (frenderer != nullptr) {
		def = frenderer->defaultSizeF();
	}
	double newW = size.width();
	double newH = newW * def.height() / def.width();
	if (newH > size.height()) {
		newH = size.height();
		newW = newH * def.width() / def.height();
	}
	QRectF bounds((size.width() - newW) / 2.0, (size.height() - newH) / 2.0, newW, newH);
	renderer->render(&painter, bounds);
	painter.end();

	return pixmap;
}

bool FSvgRenderer::determineDefaultSize(QXmlStreamReader & xml)
{
	QSizeF size = parseForWidthAndHeight(xml);

	m_defaultSizeF = QSizeF(size.width() * GraphicsUtils::SVGDPI, size.height() * GraphicsUtils::SVGDPI);
	return (size.width() != 0 && size.height() != 0);
}

QSizeF FSvgRenderer::parseForWidthAndHeight(QXmlStreamReader & xml)
{
	QSizeF size = TextUtils::parseForWidthAndHeight(xml);
	if (size.width() != 0 && size.height() != 0) return size;

	QIODevice * device = xml.device();
	DebugDialog::debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
	DebugDialog::debug("bad width and/or bad height in svg:");
	if (device != nullptr) {
		device->reset();
		QString string(device->readAll());
		DebugDialog::debug(string);
	}
	DebugDialog::debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

	return size;
}

QSizeF FSvgRenderer::defaultSizeF() {
	if (m_defaultSizeF.width() == 0 && m_defaultSizeF.height() == 0) {
		return defaultSize();
	}

	return m_defaultSizeF;
}


void FSvgRenderer::initNonConnectorInfo(QDomDocument & domDocument, const QString & filename)
{
	clearConnectorInfoHash(m_nonConnectorInfoHash);
	QDomElement root = domDocument.documentElement();
	initNonConnectorInfoAux(root, filename);
}

void FSvgRenderer::initNonConnectorInfoAux(QDomElement & element, const QString & filename)
{
	QString id = element.attribute("id");
	if (id.startsWith(NonConnectorName, Qt::CaseInsensitive)) {
		ConnectorInfo * connectorInfo = initConnectorInfoStruct(element, filename, false);
		m_nonConnectorInfoHash.insert(id, connectorInfo);
	}
	QDomElement child = element.firstChildElement();
	while (!child.isNull()) {
		initNonConnectorInfoAux(child, filename);
		child = child.nextSiblingElement();
	}
}

bool FSvgRenderer::initConnectorInfo(QDomDocument & domDocument, const LoadInfo & loadInfo)
{
	bool result = false;
	clearConnectorInfoHash(m_connectorInfoHash);
	QDomElement root = domDocument.documentElement();
	initConnectorInfoAux(root, loadInfo);
	if (loadInfo.terminalIDs.count() > 0) {
		initTerminalInfoAux(root, loadInfo);
	}
	if (loadInfo.legIDs.count() > 0) {
		initLegInfoAux(root, loadInfo, result);
	}

	return result;
}

void FSvgRenderer::initLegInfoAux(QDomElement & element, const LoadInfo & loadInfo, bool & gotOne)
{
	QString id = element.attribute("id");
	if (!id.isEmpty()) {
		int ix = loadInfo.legIDs.indexOf(id);
		if (ix >= 0) {
			//DebugDialog::debug("init leg info " + id);
			//foreach (QString lid, legIDs) {
			//	DebugDialog::debug("\tleg id:" + lid);
			//}

			element.setTagName("g");			// don't want this element to actually be drawn
			gotOne = true;
			ConnectorInfo * connectorInfo = m_connectorInfoHash.value(loadInfo.connectorIDs.at(ix), NULL);
			if (connectorInfo != nullptr) {
				//QString temp;
				//QTextStream stream(&temp);
				//element.save(stream, 0);
				//DebugDialog::debug("\t matched " + connectorIDs.at(ix) + " " + temp);
				connectorInfo->legMatrix = TextUtils::elementToTransform(element);
				connectorInfo->legColor = TextUtils::getStrokeColor(element, "#8C8C8C");
				connectorInfo->legLine = QLineF();
				connectorInfo->legStrokeWidth = 0;
				initLegInfoAux(element, connectorInfo);
			}
			// don't return here, might miss other legs
		}
	}

	QDomElement child = element.firstChildElement();
	while (!child.isNull()) {
		initLegInfoAux(child, loadInfo, gotOne);
		child = child.nextSiblingElement();
	}
}

bool FSvgRenderer::initLegInfoAux(QDomElement & element, ConnectorInfo * connectorInfo)
{
	bool ok;
	auto extractDoubleFromAttribute = [&element, &ok](const QString& attribute) {
		return element.attribute(attribute).toDouble(&ok);
	};

	// Use TextUtils::getStrokeWidth with 29mil (0.029in) as default
	double sw = TextUtils::getStrokeWidth(element, 0.029);

	double x1 = extractDoubleFromAttribute("x1");
	if (!ok) return false;

	double y1 = extractDoubleFromAttribute("y1");
	if (!ok) return false;

	double x2 = extractDoubleFromAttribute("x2");
	if (!ok) return false;

	double y2 = extractDoubleFromAttribute("y2");
	if (!ok) return false;

	connectorInfo->legStrokeWidth = sw;
	connectorInfo->legLine = QLineF(x1, y1, x2, y2);
	return true;
}

void FSvgRenderer::initTerminalInfoAux(QDomElement & element, const LoadInfo & loadInfo)
{
	QString id = element.attribute("id");
	if (!id.isEmpty()) {
		int ix = loadInfo.terminalIDs.indexOf(id);
		if (ix >= 0) {
			ConnectorInfo * connectorInfo = m_connectorInfoHash.value(loadInfo.connectorIDs.at(ix), NULL);
			if (connectorInfo != nullptr) {
				connectorInfo->terminalMatrix = TextUtils::elementToTransform(element);
			}
			// don't return here, might miss other terminal ids
		}
	}

	QDomElement child = element.firstChildElement();
	while (!child.isNull()) {
		initTerminalInfoAux(child, loadInfo);
		child = child.nextSiblingElement();
	}
}

void FSvgRenderer::initConnectorInfoAux(QDomElement & element, const LoadInfo & loadInfo)
{
	QString id = element.attribute("id");
	if (!id.isEmpty()) {
		if (loadInfo.connectorIDs.contains(id)) {
			ConnectorInfo * connectorInfo = initConnectorInfoStruct(element, loadInfo.filename, loadInfo.parsePaths);
			m_connectorInfoHash.insert(id, connectorInfo);
		}
		// don't return here, might miss other connectors
	}

	QDomElement child = element.firstChildElement();
	while (!child.isNull()) {
		initConnectorInfoAux(child, loadInfo);
		child = child.nextSiblingElement();
	}
}

ConnectorInfo * FSvgRenderer::initConnectorInfoStruct(QDomElement & connectorElement, const QString & filename, bool parsePaths) {
	auto * connectorInfo = new ConnectorInfo();
	connectorInfo->radius = 0;
	connectorInfo->strokeWidth = 0;
	connectorInfo->gotPath = false;
	connectorInfo->gotCircle = false;

	if (connectorElement.isNull()) return connectorInfo;

	connectorInfo->matrix = TextUtils::elementToTransform(connectorElement);
	initConnectorInfoStructAux(connectorElement, connectorInfo, filename, parsePaths);
	return connectorInfo;
}

bool FSvgRenderer::initConnectorInfoStructAux(QDomElement & element, ConnectorInfo * connectorInfo, const QString & filename, bool parsePaths)
{
	if (element.nodeName().compare("circle") == 0) {
		return initConnectorInfoCircle(element, connectorInfo, filename);
	}

	if (element.nodeName().compare("path") == 0) {
		if (!parsePaths) return false;
		return initConnectorInfoPath(element, connectorInfo, filename);
	}

	QDomElement child = element.firstChildElement();
	while (!child.isNull()) {
		if (initConnectorInfoStructAux(child, connectorInfo, filename, parsePaths)) return true;

		child = child.nextSiblingElement();
	}

	return false;
}

bool FSvgRenderer::initConnectorInfoPath(QDomElement & element, ConnectorInfo * connectorInfo, const QString & filename)
{
	QString id = element.attribute("id");
	if (id.isEmpty()) return false;         // shouldn't be here

	QString stroke = element.attribute("stroke");
	if (stroke == "none") return false;     // cannot be a circle with a hole in the center

	connectorInfo->gotPath = true;
	double sw = TextUtils::getStrokeWidth(element, 1);

	if (!stroke.isEmpty()) element.setAttribute("stroke", "black");

	QString fill = element.attribute("fill");
	if (!fill.isEmpty() && (fill != "none")) element.setAttribute("fill", "black");

	QDomDocument doc = element.ownerDocument();
	FSvgRenderer renderer;
	QByteArray byteArray = doc.toByteArray();
	renderer.finalLoad(byteArray, filename);
	QRectF bounds = renderer.boundsOnElement(id);

	static constexpr int dim = 101;
	int width = dim;
	int height = dim;
	if (bounds.width() - bounds.height() / (bounds.width() + bounds.height()) > .01) {
		height = (int) (float(dim) * bounds.height() / bounds.width());
	}
	else if (bounds.height() - bounds.width() / (bounds.width() + bounds.height()) > .01) {
		width = (int) (float(dim) * bounds.width() / bounds.height());
	}

	QImage image(width, height, QImage::Format_Mono);
	image.fill(0xffffffff);
	QPainter painter;
	painter.begin(&image);
	renderer.render(&painter, id);
	painter.end();

#ifndef QT_NO_DEBUG
	//image.save(FolderUtils::getUserDataStorePath("") + "/donutcheck.png");
#endif

	if (!fill.isEmpty()) element.setAttribute("fill", fill);
	if (!stroke.isEmpty()) element.setAttribute("stroke", stroke);

	//DebugDialog::debug(QString("checking connector path %1").arg(id));

	int lxStart = -1, lxEnd = -1, rxStart = -1, rxEnd = -1;
	for (int x = 0; x < width; x++) {
		if (image.pixel(x, height / 2) == 0xff000000) {
			lxStart = x;
			break;
		}
	}
	if (lxStart < 0) return false;
	if (lxStart >= width / 2) return false;        // not an ellipse
	for (int x = lxStart + 1; x < width; x++) {
		if (image.pixel(x, height / 2) == 0xff000000) {
			lxEnd = x;
		}
		else break;
	}
	if (lxEnd < 0) return false;
	if (lxEnd > width / 2) return false;       // not an ellipse

	for (int x = lxEnd + 1; x < width; x++) {
		if (image.pixel(x, height / 2) == 0xff000000) {
			rxStart = x;
			break;
		}
	}
	if (rxStart < 0) return false;
	if (rxStart < height / 2) return false;        // not an ellipse;
	for (int x = rxStart + 1; x < width; x++) {
		if (image.pixel(x, height / 2) == 0xff000000) {
			rxEnd = x;
		}
		else break;
	}
	if (rxEnd < 0) return false;
	if (qAbs(rxEnd - rxStart - (lxEnd - lxStart)) > 1) return false;   // sides are not symmetric


	int lyStart = -1, lyEnd = -1, ryStart = -1, ryEnd = -1;
	for (int y = 0; y < height; y++) {
		if (image.pixel(width / 2, y) == 0xff000000) {
			lyStart = y;
			break;
		}
	}
	if (lyStart < 0) return false;
	if (lyStart >= height / 2) return false;        // not an ellipse
	for (int y = lyStart + 1; y < height; y++) {
		if (image.pixel(width / 2, y) == 0xff000000) {
			lyEnd = y;
		}
		else break;
	}
	if (lyEnd < 0) return false;
	if (lyEnd > height / 2) return false;       // not an ellipse

	for (int y = lyEnd + 1; y < height; y++) {
		if (image.pixel(width / 2, y) == 0xff000000) {
			ryStart = y;
			break;
		}
	}
	if (ryStart < 0) return false;
	if (ryStart < height / 2) return false;        // not an ellipse
	for (int y = ryStart + 1; y < height; y++) {
		if (image.pixel(width / 2, y) == 0xff000000) {
			ryEnd = y;
		}
		else break;
	}
	if (ryEnd < 0) return false;
	if (qAbs((ryEnd - ryStart) - (lyEnd - lyStart)) > 1) return false;  // tops not symmetric

	if (qAbs((rxStart - lxEnd) - (ryStart - lyEnd)) > 1) return false;  // inner drill hole is not circular

	double r = (qMin(bounds.width(), bounds.height()) -  sw) / 2;

	QTransform matrix = TextUtils::elementToTransform(element);
	if (!matrix.isIdentity()) {
		QRectF r1(0,0,r,r);
		QRectF r2 = matrix.mapRect(r1);
		if (r2.width() != r1.width()) {
			r = r2.width();
			sw = sw * r2.width() / r1.width();
		}
	}

	connectorInfo->radius = r;
	connectorInfo->strokeWidth = sw;
	connectorInfo->gotCircle = true;

	//DebugDialog::debug(QString("got connector path %1").arg(id));

	return true;
}

bool FSvgRenderer::initConnectorInfoCircle(QDomElement & element, ConnectorInfo * connectorInfo, const QString & filename)
{
	Q_UNUSED(filename);

	bool ok;
	element.attribute("cx").toDouble(&ok);
	if (!ok) return false;

	element.attribute("cy").toDouble(&ok);
	if (!ok) return false;

	double r = element.attribute("r").toDouble(&ok);
	if (!ok) return false;

	double sw = TextUtils::getStrokeWidth(element, 1);

	QTransform matrix = TextUtils::elementToTransform(element);
	if (!matrix.isIdentity()) {
		QRectF r1(0,0,r,r);
		QRectF r2 = matrix.mapRect(r1);
		if (r2.width() != r1.width()) {
			r = r2.width();
			sw = sw * r2.width() / r1.width();
		}
	}

	//DebugDialog::debug("got a circle");
	connectorInfo->gotCircle = true;
	//connectorInfo->cbounds.setRect(cx - r - (sw / 2.0), cy - r - (sw / 2.0), (r * 2) + sw, (r * 2) + sw);
	connectorInfo->radius = r;
	connectorInfo->strokeWidth = sw;
	return true;
}

ConnectorInfo * FSvgRenderer::getConnectorInfo(const QString & connectorID) {
	return m_connectorInfoHash.value(connectorID, &VanillaConnectorInfo);
}

bool FSvgRenderer::setUpConnector(SvgIdLayer * svgIdLayer, bool ignoreTerminalPoint, ViewLayer::ViewLayerPlacement viewLayerPlacement) {

	if (svgIdLayer == nullptr) return false;

	if (svgIdLayer->processed(viewLayerPlacement)) {
		// hybrids are not visible in some views
		return svgIdLayer->svgVisible(viewLayerPlacement) || svgIdLayer->m_hybrid;
	}

	QString connectorID = svgIdLayer->m_svgId;

	// boundsOnElement seems to include any matrix on the element itself.
	// I would swear this wasn't true before Qt4.7, but maybe I am crazy
	QRectF bounds = this->boundsOnElement(connectorID);

	if (bounds.isNull() && !svgIdLayer->m_hybrid) {		// hybrids can have zero size
		svgIdLayer->setInvisible(viewLayerPlacement);
		DebugDialog::debug("renderer::setupconnector: null bounds");
		return false;
	}

	QSizeF defaultSizeF = this->defaultSizeF();
	QRectF viewBox = this->viewBoxF();

	ConnectorInfo * connectorInfo = getConnectorInfo(connectorID);

	/*
	DebugDialog::debug(QString("connectorid:%1 m11:%2 m12:%3 m21:%4 m22:%5 dx:%6 dy:%7")
						.arg(connectorID)
						.arg(connectorInfo->matrix.m11())
						.arg(connectorInfo->matrix.m12())
						.arg(connectorInfo->matrix.m21())
						.arg(connectorInfo->matrix.m22())
						.arg(connectorInfo->matrix.dx())
						.arg(connectorInfo->matrix.dy()),
				bounds);
	*/


	/*DebugDialog::debug(QString("identity matrix %11 %1 %2, viewbox: %3 %4 %5 %6, bounds: %7 %8 %9 %10, size: %12 %13").arg(m_modelPart->title()).arg(connectorSharedID())
					   .arg(viewBox.x()).arg(viewBox.y()).arg(viewBox.width()).arg(viewBox.height())
					   .arg(bounds.x()).arg(bounds.y()).arg(bounds.width()).arg(bounds.height())
					   .arg(viewID)
					   .arg(defaultSizeF.width()).arg(defaultSizeF.height())
	);
	*/

	// some strangeness in the way that svg items and non-svg items map to screen space
	// might be a qt problem.
	//QTransform matrix0 = connectorInfo->matrix * this->transformForElement(connectorID);
	//QRectF r1 = matrix0.mapRect(bounds);

	QTransform elementMatrix = this->transformForElement(connectorID);
	QRectF r1 = elementMatrix.mapRect(bounds);

	if (connectorInfo->gotCircle) {
		QLineF l(0,0,connectorInfo->radius, 0);
		QLineF lm = elementMatrix.map(l);
		svgIdLayer->m_radius = lm.length() * defaultSizeF.width() / viewBox.width();

		QLineF k(0,0,connectorInfo->strokeWidth, 0);
		QLineF km = elementMatrix.map(k);
		svgIdLayer->m_strokeWidth = km.length() * defaultSizeF.width() / viewBox.width();
		//bounds = connectorInfo->cbounds;
	}
	if (connectorInfo->gotPath) {
		svgIdLayer->m_path = true;
	}

	/*
	svgIdLayer->m_rect.setRect(r1.x() * defaultSize.width() / viewBox.width(),
							   r1.y() * defaultSize.height() / viewBox.height(),
							   r1.width() * defaultSize.width() / viewBox.width(),
							   r1.height() * defaultSize.height() / viewBox.height());
	*/

	QRectF svgRect(r1.x() * defaultSizeF.width() / viewBox.width(),
	               r1.y() * defaultSizeF.height() / viewBox.height(),
	               r1.width() * defaultSizeF.width() / viewBox.width(),
	               r1.height() * defaultSizeF.height() / viewBox.height());

	//if (!svgIdLayer->m_svgVisible) {
	//DebugDialog::debug("not vis");
	//}
	QPointF terminal;
	if (svgIdLayer->m_viewID == ViewLayer::SchematicView && svgIdLayer->m_terminalId.isEmpty()) {
		terminal = autoTerminalPoint(svgRect);
	} else {
		terminal = calcTerminalPoint(svgIdLayer->m_terminalId,
									 svgRect,
									 ignoreTerminalPoint,
									 viewBox,
									 connectorInfo->terminalMatrix);
	}

	svgIdLayer->setPointRect(viewLayerPlacement, terminal, svgRect, !bounds.isNull());
	calcLeg(svgIdLayer, viewBox, connectorInfo);

	return true;
}

void FSvgRenderer::calcLeg(SvgIdLayer * svgIdLayer, const QRectF & viewBox, ConnectorInfo * connectorInfo)
{
	if (svgIdLayer->m_legId.isEmpty()) return;

	svgIdLayer->m_legColor = connectorInfo->legColor;

	QSizeF defaultSizeF = this->defaultSizeF();
	svgIdLayer->m_legStrokeWidth = connectorInfo->legStrokeWidth * defaultSizeF.width() / viewBox.width();

	/*
	DebugDialog::debug(	QString("calcleg leg %1 %2 %3 %4")
		.arg(connectorInfo->legLine.p1().x())
		.arg(connectorInfo->legLine.p1().y())
		.arg(connectorInfo->legLine.p2().x())
		.arg(connectorInfo->legLine.p2().y())
		);
	*/

	QTransform matrix = this->transformForElement(svgIdLayer->m_legId) * connectorInfo->legMatrix;
	QPointF p1 = matrix.map(connectorInfo->legLine.p1());
	QPointF p2 = matrix.map(connectorInfo->legLine.p2());

	double x1 = p1.x() * defaultSizeF.width() / viewBox.width();
	double y1 = p1.y() * defaultSizeF.height() / viewBox.height();
	double x2 = p2.x() * defaultSizeF.width() / viewBox.width();
	double y2 = p2.y() * defaultSizeF.height() / viewBox.height();
	QPointF center(defaultSizeF.width() / 2, defaultSizeF.height() / 2);
	double d1 = GraphicsUtils::distanceSqd(QPointF(x1, y1), center);
	double d2 = GraphicsUtils::distanceSqd(QPointF(x2, y2), center);

	// find the end which is closer to the center of the viewBox (which shouldn't include the leg)
	if (d1 <= d2) {
		svgIdLayer->m_legLine = QLineF(x1, y1, x2, y2);
	}
	else {
		svgIdLayer->m_legLine = QLineF(x2, y2, x1, y1);
	}
}

QPointF FSvgRenderer::calcTerminalPoint(const QString & terminalId, const QRectF & connectorRect, bool ignoreTerminalPoint, const QRectF & viewBox, QTransform & terminalMatrix)
{
	Q_UNUSED(terminalMatrix);
	QPointF terminalPoint = connectorRect.center() - connectorRect.topLeft();    // default spot is centered
	if (ignoreTerminalPoint) {
		return terminalPoint;
	}
	if (terminalId.isEmpty()) {
		return terminalPoint;
	}

	if (!this->elementExists(terminalId)) {
		DebugDialog::debug(
			QString("missing expected terminal point element %1 in %2").arg(terminalId, m_filename));
		return terminalPoint;
	}

	QRectF tBounds = this->boundsOnElement(terminalId);
	if (tBounds.isNull()) {
		return terminalPoint;
	}

	QSizeF defaultSizeF = this->defaultSizeF();
	//DebugDialog::debug(	QString("terminal %5 rect %1,%2,%3,%4").arg(tBounds.x()).
	//arg(tBounds.y()).
	//arg(tBounds.width()).
	//arg(tBounds.height()).
	//arg(terminalID) );


	// transformForElement only grabs parent matrices, not any transforms in the element itself
	//QTransform tMatrix = this->transformForElement(terminalId) ; //* terminalMatrix;
	//QRectF terminalRect = tMatrix.mapRect(tBounds);
	QRectF terminalRect = this->transformForElement(terminalId).mapRect(tBounds);
	QPointF c = terminalRect.center();
	QPointF q(c.x() * defaultSizeF.width() / viewBox.width(), c.y() * defaultSizeF.height() / viewBox.height());
	terminalPoint = q - connectorRect.topLeft();
	//DebugDialog::debug(	QString("terminalagain %3 rect %1,%2 ").arg(terminalPoint.x()).
	//arg(terminalPoint.y()).
	//arg(terminalID) );

	return terminalPoint;
}

QPointF FSvgRenderer::autoTerminalPoint(const QRectF & connectorRect)
{
	const double epsilon = 0.001;
	// Directly return the center if width or height is near zero, treating as almost quadratic
	bool widthNearZero = std::abs(connectorRect.width()) < epsilon;
	bool heightNearZero = std::abs(connectorRect.height()) < epsilon;
	if (widthNearZero && heightNearZero) {
		return connectorRect.center() - connectorRect.topLeft();
	}

	if (!heightNearZero) {
		double widthHeightRatio = connectorRect.width() / connectorRect.height();
		if (widthHeightRatio >= 0.9 && widthHeightRatio <= 1.1) {
			// connectorRect is almost quadratic (within +- 10 percent)
			return connectorRect.center() - connectorRect.topLeft();
		}
	}

	QSizeF defaultSizeF = this->defaultSize();
	QPointF defaultCenter(defaultSizeF.width() / 2, defaultSizeF.height() / 2);
	if (connectorRect.width() > connectorRect.height()) {
		if (connectorRect.center().x() < defaultCenter.x()) {
			return QPointF(connectorRect.left(), connectorRect.center().y()) - connectorRect.topLeft();
		} else {
			return QPointF(connectorRect.right(), connectorRect.center().y()) - connectorRect.topLeft();
		}
	} else {
		if (connectorRect.center().y() < defaultCenter.y()) {
			return QPointF(connectorRect.center().x(), connectorRect.top()) - connectorRect.topLeft();
		} else {
			return QPointF(connectorRect.center().x(), connectorRect.bottom()) - connectorRect.topLeft();
		}
	}
}

QList<SvgIdLayer *> FSvgRenderer::setUpNonConnectors(ViewLayer::ViewLayerPlacement viewLayerPlacement) {

	QList<SvgIdLayer *> list;
	if (m_nonConnectorInfoHash.count() == 0) return list;

	Q_FOREACH (QString nonConnectorID, m_nonConnectorInfoHash.keys()) {
		auto * svgIdLayer = new SvgIdLayer(ViewLayer::PCBView);
		svgIdLayer->m_svgId = nonConnectorID;
		QRectF bounds = this->boundsOnElement(nonConnectorID);
		if (bounds.isNull()) {
			delete svgIdLayer;
			continue;
		}

		QSizeF defaultSizeF = this->defaultSizeF();
		QSize defaultSize = this->defaultSize();
		if ((bounds.width()) == defaultSizeF.width() && (bounds.height()) == defaultSizeF.height()) {
			delete svgIdLayer;
			continue;
		}

		QRectF viewBox = this->viewBoxF();

		ConnectorInfo * connectorInfo = m_nonConnectorInfoHash.value(nonConnectorID, NULL);
		if ((connectorInfo != nullptr) && connectorInfo->gotCircle) {
			svgIdLayer->m_radius = connectorInfo->radius * defaultSizeF.width() / viewBox.width();
			svgIdLayer->m_strokeWidth = connectorInfo->strokeWidth * defaultSizeF.width() / viewBox.width();
			//bounds = connectorInfo->cbounds;
		}

		// transformForElement only grabs parent matrices, not any transforms in the element itself
		//QTransform matrix0 = connectorInfo->matrix * this->transformForElement(nonConnectorID);
		//QRectF r1 = matrix0.mapRect(bounds);
		QRectF r1 = this->transformForElement(nonConnectorID).mapRect(bounds);
		QRectF svgRect(r1.x() * defaultSize.width() / viewBox.width(), r1.y() * defaultSize.height() / viewBox.height(), r1.width() * defaultSize.width() / viewBox.width(), r1.height() * defaultSize.height() / viewBox.height());
		QPointF center = svgRect.center() - svgRect.topLeft();
		svgIdLayer->setPointRect(viewLayerPlacement, center, svgRect, true);
		list.append(svgIdLayer);
	}

	return list;

}
