<commit_before>/*globals $, svgedit, svgroot*/
/*jslint vars: true, eqeq: true, continue: true*/
/**
 * Package: svgedit.path
 *
 * Licensed under the MIT License
 *
 * Copyright(c) 2011 <NAME>
 * Copyright(c) 2011 <NAME>
 */

// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) math.js
// 4) svgutils.js

(function() {'use strict';

if (!svgedit.path) {
	svgedit.path = {};
}

var NS = svgedit.NS;
var uiStrings = {
	'pathNodeTooltip': 'Drag node to move it. Double-click node to change segment type',
	'pathCtrlPtTooltip': 'Drag control point to adjust curve properties'
};

var segData = {
	2: ['x', 'y'],
	4: ['x', 'y'],
	6: ['x', 'y', 'x1', 'y1', 'x2', 'y2'],
	8: ['x', 'y', 'x1', 'y1'],
	10: ['x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag'],
	12: ['x'],
	14: ['y'],
	16: ['x', 'y', 'x2', 'y2'],
	18: ['x', 'y']
};

var pathFuncs = [];

var link_control_pts = true;

// Stores references to paths via IDs.
// TODO: Make this cross-document happy.
var pathData = {};

svgedit.path.setLinkControlPoints = function(lcp) {
	link_control_pts = lcp;
};

svgedit.path.path = null;

var editorContext_ = null;

svgedit.path.init = function(editorContext) {
	editorContext_ = editorContext;

	pathFuncs = [0, 'ClosePath'];
	var pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
		'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'];
	$.each(pathFuncsStrs, function(i, s) {
		pathFuncs.push(s+'Abs');
		pathFuncs.push(s+'Rel');
	});
};

svgedit.path.insertItemBefore = function(elem, newseg, index) {
	// Support insertItemBefore on paths for FF2
	var list = elem.pathSegList;

	if (svgedit.browser.supportsPathInsertItemBefore()) {
		list.insertItemBefore(newseg, index);
		return;
	}
	var len = list.numberOfItems;
	var arr = [];
	var i;
	for (i=0; i < len; i++) {
		var cur_seg = list.getItem(i);
		arr.push(cur_seg);
	}
	list.clear();
	for (i=0; i < len; i++) {
		if (i == index) { //index+1
			list.appendItem(newseg);
		}
		list.appendItem(arr[i]);
	}
};

// TODO: See if this should just live in replacePathSeg
svgedit.path.ptObjToArr = function(type, seg_item) {
	var arr = segData[type], len = arr.length;
	var i, out = [];
	for (i = 0; i < len; i++) {
		out[i] = seg_item[arr[i]];
	}
	return out;
};

svgedit.path.getGripPt = function(seg, alt_pt) {
	var out = {
		x: alt_pt? alt_pt.x : seg.item.x,
		y: alt_pt? alt_pt.y : seg.item.y
	}, path = seg.path;

	if (path.matrix) {
		var pt = svgedit.math.transformPoint(out.x, out.y, path.matrix);
		out = pt;
	}

	out.x *= editorContext_.getCurrentZoom();
	out.y *= editorContext_.getCurrentZoom();

	return out;
};

svgedit.path.getPointFromGrip = function(pt, path) {
	var out = {
		x: pt.x,
		y: pt.y
	};

	if (path.matrix) {
		pt = svgedit.math.transformPoint(out.x, out.y, path.imatrix);
		out.x = pt.x;
		out.y = pt.y;
	}

	out.x /= editorContext_.getCurrentZoom();
	out.y /= editorContext_.getCurrentZoom();

	return out;
};

svgedit.path.addPointGrip = function(index, x, y) {
	// create the container of all the point grips
	var pointGripContainer = svgedit.path.getGripContainer();

	var pointGrip = svgedit.utilities.getElem('pathpointgrip_'+index);
	// create it
	if (!pointGrip) {
		pointGrip = document.createElementNS(NS.SVG, 'circle');
		svgedit.utilities.assignAttributes(pointGrip, {
			'id': 'pathpointgrip_' + index,
			'display': 'none',
			'r': 4,
			'fill': '#0FF',
			'stroke': '#00F',
			'stroke-width': 2,
			'cursor': 'move',
			'style': 'pointer-events:all',
			'xlink:title': uiStrings.pathNodeTooltip
		});
		pointGrip = pointGripContainer.appendChild(pointGrip);

		var grip = $('#pathpointgrip_'+index);
		grip.dblclick(function() {
			if (svgedit.path.path) {
				svgedit.path.path.setSegType();
			}
		});
	}
	if (x && y) {
		// set up the point grip element and display it
		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': x,
			'cy': y,
			'display': 'inline'
		});
	}
	return pointGrip;
};

svgedit.path.getGripContainer = function() {
	var c = svgedit.utilities.getElem('pathpointgrip_container');
	if (!c) {
		var parent = svgedit.utilities.getElem('selectorParentGroup');
		c = parent.appendChild(document.createElementNS(NS.SVG, 'g'));
		c.id = 'pathpointgrip_container';
	}
	return c;
};

svgedit.path.addCtrlGrip = function(id) {
	var pointGrip = svgedit.utilities.getElem('ctrlpointgrip_'+id);
	if (pointGrip) {return pointGrip;}

	pointGrip = document.createElementNS(NS.SVG, 'circle');
	svgedit.utilities.assignAttributes(pointGrip, {
		'id': 'ctrlpointgrip_' + id,
		'display': 'none',
		'r': 4,
		'fill': '#0FF',
		'stroke': '#55F',
		'stroke-width': 1,
		'cursor': 'move',
		'style': 'pointer-events:all',
		'xlink:title': uiStrings.pathCtrlPtTooltip
	});
	svgedit.path.getGripContainer().appendChild(pointGrip);
	return pointGrip;
};

svgedit.path.getCtrlLine = function(id) {
	var ctrlLine = svgedit.utilities.getElem('ctrlLine_'+id);
	if (ctrlLine) {return ctrlLine;}

	ctrlLine = document.createElementNS(NS.SVG, 'line');
	svgedit.utilities.assignAttributes(ctrlLine, {
		'id': 'ctrlLine_'+id,
		'stroke': '#555',
		'stroke-width': 1,
		'style': 'pointer-events:none'
	});
	svgedit.path.getGripContainer().appendChild(ctrlLine);
	return ctrlLine;
};

svgedit.path.getPointGrip = function(seg, update) {
	var index = seg.index;
	var pointGrip = svgedit.path.addPointGrip(index);

	if (update) {
		var pt = svgedit.path.getGripPt(seg);
		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': pt.x,
			'cy': pt.y,
			'display': 'inline'
		});
	}

	return pointGrip;
};

svgedit.path.getControlPoints = function(seg) {
	var item = seg.item;
	var index = seg.index;
	if (!('x1' in item) || !('x2' in item)) {return null;}
	var cpt = {};
	var pointGripContainer = svgedit.path.getGripContainer();

	// Note that this is intentionally not seg.prev.item
	var prev = svgedit.path.path.segs[index-1].item;

	var seg_items = [prev, item];

	var i;
	for (i = 1; i < 3; i++) {
		var id = index + 'c' + i;

		var ctrlLine = cpt['c' + i + '_line'] = svgedit.path.getCtrlLine(id);

		var pt = svgedit.path.getGripPt(seg, {x:item['x' + i], y:item['y' + i]});
		var gpt = svgedit.path.getGripPt(seg, {x:seg_items[i-1].x, y:seg_items[i-1].y});

		svgedit.utilities.assignAttributes(ctrlLine, {
			'x1': pt.x,
			'y1': pt.y,
			'x2': gpt.x,
			'y2': gpt.y,
			'display': 'inline'
		});

		cpt['c' + i + '_line'] = ctrlLine;

		// create it
		var pointGrip = cpt['c' + i] = svgedit.path.addCtrlGrip(id);

		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': pt.x,
			'cy': pt.y,
			'display': 'inline'
		});
		cpt['c' + i] = pointGrip;
	}
	return cpt;
};

// This replaces the segment at the given index. Type is given as number.
svgedit.path.replacePathSeg = function(type, index, pts, elem) {
	var path = elem || svgedit.path.path.elem;

	var func = 'createSVGPathSeg' + pathFuncs[type];
	var seg = path[func].apply(path, pts);

	if (svgedit.browser.supportsPathReplaceItem()) {
		path.pathSegList.replaceItem(seg, index);
	} else {
		var segList = path.pathSegList;
		var len = segList.numberOfItems;
		var arr = [];
		var i;
		for (i = 0; i < len; i++) {
			var cur_seg = segList.getItem(i);
			arr.push(cur_seg);
		}
		segList.clear();
		for (i = 0; i < len; i++) {
			if (i == index) {
				segList.appendItem(seg);
			} else {
				segList.appendItem(arr[i]);
			}
		}
	}
};


svgedit.path.getSegSelector = function(seg, update) {
	var index = seg.index;
	var segLine = svgedit.utilities.getElem('segline_' + index);
	if (!segLine) {
		var pointGripContainer = svgedit.path.getGripContainer();
		// create segline
		segLine = document.createElementNS(NS.SVG, 'path');
		svgedit.utilities.assignAttributes(segLine, {
			'id': 'segline_' + index,
			'display': 'none',
			'fill': 'none',
			'stroke': '#0FF',
			'stroke-width': 2,
			'style':'pointer-events:none',
			'd': 'M0,0 0,0'
		});
		pointGripContainer.appendChild(segLine);
	}

	if (update) {
		var prev = seg.prev;
		if (!prev) {
			segLine.setAttribute('display', 'none');
			return segLine;
		}

		var pt = svgedit.path.getGripPt(prev);
		// Set start point
		svgedit.path.replacePathSeg(2, 0, [pt.x, pt.y], segLine);

		var pts = svgedit.path.ptObjToArr(seg.type, seg.item, true);
		var i;
		for (i = 0; i < pts.length; i += 2) {
			pt = svgedit.path.getGripPt(seg, {x:pts[i], y:pts[i+1]});
			pts[i] = pt.x;
			pts[i+1] = pt.y;
		}

		svgedit.path.replacePathSeg(seg.type, 1, pts, segLine);
	}
	return segLine;
};

// Function: smoothControlPoints
// Takes three points and creates a smoother line based on them
// 
// Parameters: 
// ct1 - Object with x and y values (first control point)
// ct2 - Object with x and y values (second control point)
// pt - Object with x and y values (third point)
//
// Returns: 
// Array of two "smoothed" point objects
svgedit.path.smoothControlPoints = function(ct1, ct2, pt) {
	// each point must not be the origin
	var x1 = ct1.x - pt.x,
		y1 = ct1.y - pt.y,
		x2 = ct2.x - pt.x,
		y2 = ct2.y - pt.y;

	if ( (x1 != 0 || y1 != 0) && (x2 != 0 || y2 != 0) ) {
		var anglea = Math.atan2(y1, x1),
			angleb = Math.atan2(y2, x2),
			r1 = Math.sqrt(x1*x1+y1*y1),
			r2 = Math.sqrt(x2*x2+y2*y2),
			nct1 = editorContext_.getSVGRoot().createSVGPoint(),
			nct2 = editorContext_.getSVGRoot().createSVGPoint();
		if (anglea < 0) { anglea += 2*Math.PI; }
		if (angleb < 0) { angleb += 2*Math.PI; }

		var angleBetween = Math.abs(anglea - angleb),
			angleDiff = Math.abs(Math.PI - angleBetween)/2;

		var new_anglea, new_angleb;
		if (anglea - angleb > 0) {
			new_anglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff);
			new_angleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff);
		}
		else {
			new_anglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff);
			new_angleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff);
		}

		// rotate the points
		nct1.x = r1 * Math.cos(new_anglea) + pt.x;
		nct1.y = r1 * Math.sin(new_anglea) + pt.y;
		nct2.x = r2 * Math.cos(new_angleb) + pt.x;
		nct2.y = r2 * Math.sin(new_angleb) + pt.y;

		return [nct1, nct2];
	}
	return undefined;
};

svgedit.path.Segment = function(index, item) {
	this.selected = false;
	this.index = index;
	this.item = item;
	this.type = item.pathSegType;

	this.ctrlpts = [];
	this.ptgrip = null;
	this.segsel = null;
};

svgedit.path.Segment.prototype.showCtrlPts = function(y) {
	var i;
	for (i in this.ctrlpts) {
		if (this.ctrlpts.hasOwnProperty(i)) {
			this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none');
		}
	}
};

svgedit.path.Segment.prototype.selectCtrls = function(y) {
	$('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2').
		attr('fill', y ? '#0FF' : '#EEE');
};

svgedit.path.Segment.prototype.show = function(y) {
	if (this.ptgrip) {
		this.ptgrip.setAttribute('display', y ? 'inline' : 'none');
		this.segsel.setAttribute('display', y ? 'inline' : 'none');
		// Show/hide all control points if available
		this.showCtrlPts(y);
	}
};

svgedit.path.Segment.prototype.select = function(y) {
	if (this.ptgrip) {
		this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F');
		this.segsel.setAttribute('display', y ? 'inline' : 'none');
		if (this.ctrlpts) {
			this.selectCtrls(y);
		}
		this.selected = y;
	}
};

svgedit.path.Segment.prototype.addGrip = function() {
	this.ptgrip = svgedit.path.getPointGrip(this, true);
	this.ctrlpts = svgedit.path.getControlPoints(this, true);
	this.segsel = svgedit.path.getSegSelector(this, true);
};

svgedit.path.Segment.prototype.update = function(full) {
	if (this.ptgrip) {
		var pt = svgedit.path.getGripPt(this);
		svgedit.utilities.assignAttributes(this.ptgrip, {
			'cx': pt.x,
			'cy': pt.y
		});

		svgedit.path.getSegSelector(this, true);

		if (this.ctrlpts) {
			if (full) {
				this.item = svgedit.path.path.elem.pathSegList.getItem(this.index);
				this.type = this.item.pathSegType;
			}
			svgedit.path.getControlPoints(this);
		}
		// this.segsel.setAttribute('display', y?'inline':'none');
	}
};

svgedit.path.Segment.prototype.move = function(dx, dy) {
	var cur_pts, item = this.item;

	// fix for path tool dom breakage, amending item does bad things now, so we take a copy and use that. Can probably improve to just take a shallow copy of object
	var cloneItem = $.extend({}, item);

        if(this.ctrlpts) {
                var cur_pts = [cloneItem.x += dx, cloneItem.y += dy,
                        cloneItem.x1, cloneItem.y1, cloneItem.x2 += dx, cloneItem.y2 += dy];
        } else {
                var cur_pts = [cloneItem.x += dx, cloneItem.y += dy];
        }
	//fix

	svgedit.path.replacePathSeg(this.type, this.index, cur_pts);

	if (this.next && this.next.ctrlpts) {
		var next = this.next.item;
		var next_pts = [next.x, next.y, 
			next.x1 += dx, next.y1 += dy, next.x2, next.y2];
		svgedit.path.replacePathSeg(this.next.type, this.next.index, next_pts);
	}

	if (this.mate) {
		// The last point of a closed subpath has a 'mate',
		// which is the 'M' segment of the subpath
		item = this.mate.item;
		var pts = [item.x += dx, item.y += dy];
		svgedit.path.replacePathSeg(this.mate.type, this.mate.index, pts);
		// Has no grip, so does not need 'updating'?
	}

	this.update(true);
	if (this.next) {this.next.update(true);}
};



svgedit.path.Segment.prototype.setLinked = function(num) {
	var seg, anum, pt;
	if (num == 2) {
		anum = 1;
		seg = this.next;
		if (!seg) {return;}
		pt = this.item;
	} else {
		anum = 2;
		seg = this.prev;
		if (!seg) {return;}
		pt = seg.item;
	}

	var item = seg.item;
	// fix for path tool dom breakage, amending item does bad things now, so we take a copy and use that. Can probably improve to just take a shallow copy of object
	var cloneItem = $.extend({}, item);
        cloneItem['x' + anum ] = pt.x + (pt.x - this.item['x' + num]);
        cloneItem['y' + anum ] = pt.y + (pt.y - this.item['y' + num]);

        var pts = [cloneItem.x, cloneItem.y,<commit_msg>formatting<commit_after>/*globals $, svgedit, svgroot*/
/*jslint vars: true, eqeq: true, continue: true*/
/**
 * Package: svgedit.path
 *
 * Licensed under the MIT License
 *
 * Copyright(c) 2011 <NAME>
 * Copyright(c) 2011 <NAME>
 */

// Dependencies:
// 1) jQuery
// 2) browser.js
// 3) math.js
// 4) svgutils.js

(function() {'use strict';

if (!svgedit.path) {
	svgedit.path = {};
}

var NS = svgedit.NS;
var uiStrings = {
	'pathNodeTooltip': 'Drag node to move it. Double-click node to change segment type',
	'pathCtrlPtTooltip': 'Drag control point to adjust curve properties'
};

var segData = {
	2: ['x', 'y'],
	4: ['x', 'y'],
	6: ['x', 'y', 'x1', 'y1', 'x2', 'y2'],
	8: ['x', 'y', 'x1', 'y1'],
	10: ['x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag'],
	12: ['x'],
	14: ['y'],
	16: ['x', 'y', 'x2', 'y2'],
	18: ['x', 'y']
};

var pathFuncs = [];

var link_control_pts = true;

// Stores references to paths via IDs.
// TODO: Make this cross-document happy.
var pathData = {};

svgedit.path.setLinkControlPoints = function(lcp) {
	link_control_pts = lcp;
};

svgedit.path.path = null;

var editorContext_ = null;

svgedit.path.init = function(editorContext) {
	editorContext_ = editorContext;

	pathFuncs = [0, 'ClosePath'];
	var pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
		'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'];
	$.each(pathFuncsStrs, function(i, s) {
		pathFuncs.push(s+'Abs');
		pathFuncs.push(s+'Rel');
	});
};

svgedit.path.insertItemBefore = function(elem, newseg, index) {
	// Support insertItemBefore on paths for FF2
	var list = elem.pathSegList;

	if (svgedit.browser.supportsPathInsertItemBefore()) {
		list.insertItemBefore(newseg, index);
		return;
	}
	var len = list.numberOfItems;
	var arr = [];
	var i;
	for (i=0; i < len; i++) {
		var cur_seg = list.getItem(i);
		arr.push(cur_seg);
	}
	list.clear();
	for (i=0; i < len; i++) {
		if (i == index) { //index+1
			list.appendItem(newseg);
		}
		list.appendItem(arr[i]);
	}
};

// TODO: See if this should just live in replacePathSeg
svgedit.path.ptObjToArr = function(type, seg_item) {
	var arr = segData[type], len = arr.length;
	var i, out = [];
	for (i = 0; i < len; i++) {
		out[i] = seg_item[arr[i]];
	}
	return out;
};

svgedit.path.getGripPt = function(seg, alt_pt) {
	var out = {
		x: alt_pt? alt_pt.x : seg.item.x,
		y: alt_pt? alt_pt.y : seg.item.y
	}, path = seg.path;

	if (path.matrix) {
		var pt = svgedit.math.transformPoint(out.x, out.y, path.matrix);
		out = pt;
	}

	out.x *= editorContext_.getCurrentZoom();
	out.y *= editorContext_.getCurrentZoom();

	return out;
};

svgedit.path.getPointFromGrip = function(pt, path) {
	var out = {
		x: pt.x,
		y: pt.y
	};

	if (path.matrix) {
		pt = svgedit.math.transformPoint(out.x, out.y, path.imatrix);
		out.x = pt.x;
		out.y = pt.y;
	}

	out.x /= editorContext_.getCurrentZoom();
	out.y /= editorContext_.getCurrentZoom();

	return out;
};

svgedit.path.addPointGrip = function(index, x, y) {
	// create the container of all the point grips
	var pointGripContainer = svgedit.path.getGripContainer();

	var pointGrip = svgedit.utilities.getElem('pathpointgrip_'+index);
	// create it
	if (!pointGrip) {
		pointGrip = document.createElementNS(NS.SVG, 'circle');
		svgedit.utilities.assignAttributes(pointGrip, {
			'id': 'pathpointgrip_' + index,
			'display': 'none',
			'r': 4,
			'fill': '#0FF',
			'stroke': '#00F',
			'stroke-width': 2,
			'cursor': 'move',
			'style': 'pointer-events:all',
			'xlink:title': uiStrings.pathNodeTooltip
		});
		pointGrip = pointGripContainer.appendChild(pointGrip);

		var grip = $('#pathpointgrip_'+index);
		grip.dblclick(function() {
			if (svgedit.path.path) {
				svgedit.path.path.setSegType();
			}
		});
	}
	if (x && y) {
		// set up the point grip element and display it
		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': x,
			'cy': y,
			'display': 'inline'
		});
	}
	return pointGrip;
};

svgedit.path.getGripContainer = function() {
	var c = svgedit.utilities.getElem('pathpointgrip_container');
	if (!c) {
		var parent = svgedit.utilities.getElem('selectorParentGroup');
		c = parent.appendChild(document.createElementNS(NS.SVG, 'g'));
		c.id = 'pathpointgrip_container';
	}
	return c;
};

svgedit.path.addCtrlGrip = function(id) {
	var pointGrip = svgedit.utilities.getElem('ctrlpointgrip_'+id);
	if (pointGrip) {return pointGrip;}

	pointGrip = document.createElementNS(NS.SVG, 'circle');
	svgedit.utilities.assignAttributes(pointGrip, {
		'id': 'ctrlpointgrip_' + id,
		'display': 'none',
		'r': 4,
		'fill': '#0FF',
		'stroke': '#55F',
		'stroke-width': 1,
		'cursor': 'move',
		'style': 'pointer-events:all',
		'xlink:title': uiStrings.pathCtrlPtTooltip
	});
	svgedit.path.getGripContainer().appendChild(pointGrip);
	return pointGrip;
};

svgedit.path.getCtrlLine = function(id) {
	var ctrlLine = svgedit.utilities.getElem('ctrlLine_'+id);
	if (ctrlLine) {return ctrlLine;}

	ctrlLine = document.createElementNS(NS.SVG, 'line');
	svgedit.utilities.assignAttributes(ctrlLine, {
		'id': 'ctrlLine_'+id,
		'stroke': '#555',
		'stroke-width': 1,
		'style': 'pointer-events:none'
	});
	svgedit.path.getGripContainer().appendChild(ctrlLine);
	return ctrlLine;
};

svgedit.path.getPointGrip = function(seg, update) {
	var index = seg.index;
	var pointGrip = svgedit.path.addPointGrip(index);

	if (update) {
		var pt = svgedit.path.getGripPt(seg);
		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': pt.x,
			'cy': pt.y,
			'display': 'inline'
		});
	}

	return pointGrip;
};

svgedit.path.getControlPoints = function(seg) {
	var item = seg.item;
	var index = seg.index;
	if (!('x1' in item) || !('x2' in item)) {return null;}
	var cpt = {};
	var pointGripContainer = svgedit.path.getGripContainer();

	// Note that this is intentionally not seg.prev.item
	var prev = svgedit.path.path.segs[index-1].item;

	var seg_items = [prev, item];

	var i;
	for (i = 1; i < 3; i++) {
		var id = index + 'c' + i;

		var ctrlLine = cpt['c' + i + '_line'] = svgedit.path.getCtrlLine(id);

		var pt = svgedit.path.getGripPt(seg, {x:item['x' + i], y:item['y' + i]});
		var gpt = svgedit.path.getGripPt(seg, {x:seg_items[i-1].x, y:seg_items[i-1].y});

		svgedit.utilities.assignAttributes(ctrlLine, {
			'x1': pt.x,
			'y1': pt.y,
			'x2': gpt.x,
			'y2': gpt.y,
			'display': 'inline'
		});

		cpt['c' + i + '_line'] = ctrlLine;

		// create it
		var pointGrip = cpt['c' + i] = svgedit.path.addCtrlGrip(id);

		svgedit.utilities.assignAttributes(pointGrip, {
			'cx': pt.x,
			'cy': pt.y,
			'display': 'inline'
		});
		cpt['c' + i] = pointGrip;
	}
	return cpt;
};

// This replaces the segment at the given index. Type is given as number.
svgedit.path.replacePathSeg = function(type, index, pts, elem) {
	var path = elem || svgedit.path.path.elem;

	var func = 'createSVGPathSeg' + pathFuncs[type];
	var seg = path[func].apply(path, pts);

	if (svgedit.browser.supportsPathReplaceItem()) {
		path.pathSegList.replaceItem(seg, index);
	} else {
		var segList = path.pathSegList;
		var len = segList.numberOfItems;
		var arr = [];
		var i;
		for (i = 0; i < len; i++) {
			var cur_seg = segList.getItem(i);
			arr.push(cur_seg);
		}
		segList.clear();
		for (i = 0; i < len; i++) {
			if (i == index) {
				segList.appendItem(seg);
			} else {
				segList.appendItem(arr[i]);
			}
		}
	}
};

svgedit.path.getSegSelector = function(seg, update) {
	var index = seg.index;
	var segLine = svgedit.utilities.getElem('segline_' + index);
	if (!segLine) {
		var pointGripContainer = svgedit.path.getGripContainer();
		// create segline
		segLine = document.createElementNS(NS.SVG, 'path');
		svgedit.utilities.assignAttributes(segLine, {
			'id': 'segline_' + index,
			'display': 'none',
			'fill': 'none',
			'stroke': '#0FF',
			'stroke-width': 2,
			'style':'pointer-events:none',
			'd': 'M0,0 0,0'
		});
		pointGripContainer.appendChild(segLine);
	}

	if (update) {
		var prev = seg.prev;
		if (!prev) {
			segLine.setAttribute('display', 'none');
			return segLine;
		}

		var pt = svgedit.path.getGripPt(prev);
		// Set start point
		svgedit.path.replacePathSeg(2, 0, [pt.x, pt.y], segLine);

		var pts = svgedit.path.ptObjToArr(seg.type, seg.item, true);
		var i;
		for (i = 0; i < pts.length; i += 2) {
			pt = svgedit.path.getGripPt(seg, {x:pts[i], y:pts[i+1]});
			pts[i] = pt.x;
			pts[i+1] = pt.y;
		}

		svgedit.path.replacePathSeg(seg.type, 1, pts, segLine);
	}
	return segLine;
};

// Function: smoothControlPoints
// Takes three points and creates a smoother line based on them
// 
// Parameters: 
// ct1 - Object with x and y values (first control point)
// ct2 - Object with x and y values (second control point)
// pt - Object with x and y values (third point)
//
// Returns: 
// Array of two "smoothed" point objects
svgedit.path.smoothControlPoints = function(ct1, ct2, pt) {
	// each point must not be the origin
	var x1 = ct1.x - pt.x,
		y1 = ct1.y - pt.y,
		x2 = ct2.x - pt.x,
		y2 = ct2.y - pt.y;

	if ( (x1 != 0 || y1 != 0) && (x2 != 0 || y2 != 0) ) {
		var anglea = Math.atan2(y1, x1),
			angleb = Math.atan2(y2, x2),
			r1 = Math.sqrt(x1*x1+y1*y1),
			r2 = Math.sqrt(x2*x2+y2*y2),
			nct1 = editorContext_.getSVGRoot().createSVGPoint(),
			nct2 = editorContext_.getSVGRoot().createSVGPoint();
		if (anglea < 0) { anglea += 2*Math.PI; }
		if (angleb < 0) { angleb += 2*Math.PI; }

		var angleBetween = Math.abs(anglea - angleb),
			angleDiff = Math.abs(Math.PI - angleBetween)/2;

		var new_anglea, new_angleb;
		if (anglea - angleb > 0) {
			new_anglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff);
			new_angleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff);
		}
		else {
			new_anglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff);
			new_angleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff);
		}

		// rotate the points
		nct1.x = r1 * Math.cos(new_anglea) + pt.x;
		nct1.y = r1 * Math.sin(new_anglea) + pt.y;
		nct2.x = r2 * Math.cos(new_angleb) + pt.x;
		nct2.y = r2 * Math.sin(new_angleb) + pt.y;

		return [nct1, nct2];
	}
	return undefined;
};

svgedit.path.Segment = function(index, item) {
	this.selected = false;
	this.index = index;
	this.item = item;
	this.type = item.pathSegType;

	this.ctrlpts = [];
	this.ptgrip = null;
	this.segsel = null;
};

svgedit.path.Segment.prototype.showCtrlPts = function(y) {
	var i;
	for (i in this.ctrlpts) {
		if (this.ctrlpts.hasOwnProperty(i)) {
			this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none');
		}
	}
};

svgedit.path.Segment.prototype.selectCtrls = function(y) {
	$('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2').
		attr('fill', y ? '#0FF' : '#EEE');
};

svgedit.path.Segment.prototype.show = function(y) {
	if (this.ptgrip) {
		this.ptgrip.setAttribute('display', y ? 'inline' : 'none');
		this.segsel.setAttribute('display', y ? 'inline' : 'none');
		// Show/hide all control points if available
		this.showCtrlPts(y);
	}
};

svgedit.path.Segment.prototype.select = function(y) {
	if (this.ptgrip) {
		this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F');
		this.segsel.setAttribute('display', y ? 'inline' : 'none');
		if (this.ctrlpts) {
			this.selectCtrls(y);
		}
		this.selected = y;
	}
};

svgedit.path.Segment.prototype.addGrip = function() {
	this.ptgrip = svgedit.path.getPointGrip(this, true);
	this.ctrlpts = svgedit.path.getControlPoints(this, true);
	this.segsel = svgedit.path.getSegSelector(this, true);
};

svgedit.path.Segment.prototype.update = function(full) {
	if (this.ptgrip) {
		var pt = svgedit.path.getGripPt(this);
		svgedit.utilities.assignAttributes(this.ptgrip, {
			'cx': pt.x,
			'cy': pt.y
		});

		svgedit.path.getSegSelector(this, true);

		if (this.ctrlpts) {
			if (full) {
				this.item = svgedit.path.path.elem.pathSegList.getItem(this.index);
				this.type = this.item.pathSegType;
			}
			svgedit.path.getControlPoints(this);
		}
		// this.segsel.setAttribute('display', y?'inline':'none');
	}
};

svgedit.path.Segment.prototype.move = function(dx, dy) {
	var cur_pts, item = this.item;

	// fix for path tool dom breakage, amending item does bad things now, so we take a copy and use that. Can probably improve to just take a shallow copy of object
	var cloneItem = $.extend({}, item);

        if(this.ctrlpts) {
                var cur_pts = [cloneItem.x += dx, cloneItem.y += dy,
                        cloneItem.x1, cloneItem.y1, cloneItem.x2 += dx, cloneItem.y2 += dy];
        } else {
                var cur_pts = [cloneItem.x += dx, cloneItem.y += dy];
        }
	//fix

	svgedit.path.replacePathSeg(this.type, this.index, cur_pts);

	if (this.next && this.next.ctrlpts) {
		var next = this.next.item;
		var next_pts = [next.x, next.y, 
			next.x1 += dx, next.y1 += dy, next.x2, next.y2];
		svgedit.path.replacePathSeg(this.next.type, this.next.index, next_pts);
	}

	if (this.mate) {
		// The last point of a closed subpath has a 'mate',
		// which is the 'M' segment of the subpath
		item = this.mate.item;
		var pts = [item.x += dx, item.y += dy];
		svgedit.path.replacePathSeg(this.mate.type, this.mate.index, pts);
		// Has no grip, so does not need 'updating'?
	}

	this.update(true);
	if (this.next) {this.next.update(true);}
};

svgedit.path.Segment.prototype.setLinked = function(num) {
	var seg, anum, pt;
	if (num == 2) {
		anum = 1;
		seg = this.next;
		if (!seg) {return;}
		pt = this.item;
	} else {
		anum = 2;
		seg = this.prev;
		if (!seg) {return;}
		pt = seg.item;
	}

	var item = seg.item;
	// fix for path tool dom breakage, amending item does bad things now, so we take a copy and use that. Can probably improve to just take a shallow copy of object
	var cloneItem = $.extend({}, item);
        cloneItem['x' + anum ] = pt.x + (pt.x - this.item['x' + num]);
        cloneItem['y' + anum ] = pt.y + (pt.y - this.item['y' + num]);

        var pts = [cloneItem.x, cloneItem.y,
{'fraction_non_alphanumeric': 0.17650852802063924, 'fraction_numerical': 0.010964597964741293, 'mean_word_length': 3.5365734720416127, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7269375', 'n_tokens_mistral': 11815, 'n_tokens_neox': 11075, 'n_words': 2425}