//- Challenge (nested transforms): https://a.hrc.onl/img/mark-h.svg
//-
//- Clean (zoom out to see controls...):
//- <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
//- viewBox="0 0 336 288" height="288" width="336" >
//- <g transform="matrix(1.3333333,0,0,-1.3333333,0,288)" >
//- <g transform="scale(0.1)" style="fill:#254da3;fill-opacity:1;fill-rule:nonzero;stroke:none" >
//- <path d="M 720,0 0,0 l 0,720 90,360 -90,360 0,720 720,0 0,-2160" />
//- <path d="m 2160,0 -720,0 90,1080 -90,1080 720,0 0,-2160" />
//- <path d="m 2520,1080 -1080,1080 0,-720 -1440,0 0,-720 1440,0 0,-720 1080,1080" style="fill:#e51b2d" />
//- </g>
//- </g>
//- </svg>
//-
//- Possible tools to flatten transforms:
//- https://github.com/adobe-webplatform/Snap.svg/issues/199
//- https://gist.github.com/timo22345/9413158
//- http://jsfiddle.net/Nv78L/35/
//- http://stackoverflow.com/questions/5149301/baking-transforms-into-svg-path-element-commands
//- https://github.com/svg/svgo
//- https://github.com/svg/svgo/issues/344
//- https://github.com/RazrFalcon/SVGCleaner
//- http://stackoverflow.com/a/15113751/1869660
//-
#input
h2 SVG or path data:
textarea(rows=2) M90 150 c10 143 80-73 146 140 l-27-170-70 166 c113-205 121 31 171-115
//- M83.042,5.1183 h50v50 c-17.494-0.0693-32.193,12.866-40.115,27.573-15.016,28.085-14.637,64.08,1.47,91.619,8.2224,13.896,22.852,25.561,39.678,25.065,17.327-0.61091,31.829-13.477,39.421-28.33,13.688-25.755,13.954-58.007,1.7246-84.359-7.21-15.386-20.89-29.537-38.524-31.39-1.214-0.1219-2.434-0.1833-3.654-0.1823z
input#show-original(type='checkbox', checked)
label(for='show-original') Show original
input#show-controls(type='checkbox', checked)
label(for='show-controls') Show controls
#drawing-board
#original.layer
#drawing.layer
#controls.layer
svg(xmlns='http://www.w3.org/2000/svg')
#output
h2 Edited
//pre
textarea(disabled, rows=5)
/*Downloaded from https://www.codeseek.co/Sphinxxxx/this-is-not-the-svg-editor-youandaposre-looking-for-gaXXQE */
$colorBG: rgba(0,0,0, 0);
$colorBGPattern: #cdf;
html, body { margin: 0; padding: 0; }
body { font-family: Georgia, sans-serif; }
h2 { margin:0; text-align:center; }
textarea {
width: 100%;
box-sizing: border-box;
white-space: pre;
}
#input {
background: lawngreen;
}
#drawing-board {
position: relative;
width: 601px;
height: 501px;
margin: 1em auto;
//background: linear-gradient(to right, $colorBG 48%, $colorBGPattern 48%, $colorBGPattern 50%, $colorBG 50%, $colorBG 100%),
// linear-gradient(to bottom, $colorBG 48%, $colorBGPattern 48%, $colorBGPattern 50%, $colorBG 50%, $colorBG 100%);
background: linear-gradient(to right, $colorBGPattern 1px, $colorBG 1px),
linear-gradient(to bottom, $colorBGPattern 1px, $colorBG 1px);
background-size: 20px 20px;
.layer {
position: absolute;
top:0; left:0; bottom:0; right:0;
}
#original {
opacity: .3;
}
#controls {
//Base for stroke-width of controls UI, which is 'em'-sized to make resizing easier.
//(when zooming, the child SVG changes its font-size +-1em).
font-size: 10px;
$stroke: .2em;
svg {
position: relative;
z-index: 99;
overflow: visible;
}
.endpoint {
fill: transparent;
stroke: blue;
stroke-width: $stroke;
}
.endpoint, .bez-control {
cursor: pointer;
}
.bez-control, .bez-handle {
fill: lightgreen;
stroke: green;
stroke-width: $stroke;
stroke-dasharray: $stroke * 2;
}
.bez-handle {
stroke: gold;
z-index: -1;
pointer-events: none;
}
}
}
#show-original:not(:checked) ~ #drawing-board #original {
display: none;
}
#show-controls:not(:checked) ~ #drawing-board #controls {
display: none;
}
#output {
background: deepskyblue;
padding-bottom: 0.5em;
//pre {
// width: 100%;
// min-height: 1em;
// max-height: 50vh;
// margin: 0;
// background: white;
// overflow: auto;
//}
textarea {
background: white;
}
}
/*Downloaded from https://www.codeseek.co/Sphinxxxx/this-is-not-the-svg-editor-youandaposre-looking-for-gaXXQE */
window.onerror = function(msg, url, line) { alert('Error: '+msg+'\nURL: '+url+'\nLine: '+line); };
ABOUtils.log2screen();
(function(undefined) {
"use strict";
const RAD_CONTROL = 16,
RAD_END = 16,
ZOOM_MAX = 1000;
var _svg,
_origSvg,
_viewBox,
_viewport,
_zoomer,
_input = $$1('#input textarea'),
_output = $$1('#output textarea'),
_board = $$1('#drawing-board'),
_original = $$1('#original'),
_drawing = $$1('#drawing'),
_controls = $$1('#controls svg');
function Coord(x, y) {
this.x = x;
this.y = y;
}
Coord.prototype.negate = function() {
return new Coord(-this.x, -this.y);
};
Coord.prototype.subtract = function(c2) {
return new Coord(this.x - c2.x, this.y - c2.y);
};
Coord.prototype.toArray = function() {
return [this.x, this.y];
};
function PathKeeper(ui, segments) {
this.ui = ui;
this.segments = segments;
}
function UIDataBinding(keeper, segment, coordIndex) {
this.keeper = keeper;
this.segment = segment;
this.coordIndex = coordIndex;
}
const svgUI = {
createElement: function(parent, name, attributes) {
//http://stackoverflow.com/questions/16488884/add-svg-element-to-existing-svg-using-dom
var svgNS = 'http://www.w3.org/2000/svg';
var elm = document.createElementNS(svgNS, name);
for(var attr in attributes) {
elm.setAttributeNS(null, attr, attributes[attr]);
}
parent.appendChild(elm);
return elm;
},
getAttr: function(element, attr) {
return element.getAttributeNS(null, attr);
},
setAttr: function(element, attr, val) {
if(val || (val === 0)) {
element.setAttributeNS(null, attr, val);
}
else {
element.removeAttributeNS(null, attr);
}
},
unwrapVal: function(prop) {
//[SVGAnimatedLength]...
var val = prop.baseVal.value;
//console.log(prop, val);
return val;
},
};
function updatePath(keeper) {
var pathUI = keeper.ui,
segments = keeper.segments,
data = RosomanSVG.serialize(segments);
svgUI.setAttr(pathUI, 'd', data);
}
function updateHandles(pointUI) {
var handles = pointUI.__handles;
if(!handles) { return; }
handles.forEach(function(h) {
var p1 = h.__p1,
p2 = h.__p2;
//console.log(h, p1, p2);
var x1 = svgUI.unwrapVal(p1.cx),
y1 = svgUI.unwrapVal(p1.cy),
x2 = svgUI.unwrapVal(p2.cx),
y2 = svgUI.unwrapVal(p2.cy),
data = ['M', [x1,y1], 'L', [x2,y2]].join(' ');
svgUI.setAttr(h, 'd', data);
});
}
function movePoint(pointUI, viewportCoord) {
var coord = _zoomer.vp2vb(viewportCoord);
svgUI.setAttr(pointUI, 'cx', coord.x);
svgUI.setAttr(pointUI, 'cy', coord.y);
////[_bezier, 'c2'] => _bezier['c2'] = ...
//pointUI.dataBinding[0][pointUI.dataBinding[1]] = dragPos;
var binding = pointUI.dataBinding;
binding.segment[binding.coordIndex] = coord.x;
binding.segment[binding.coordIndex+1] = coord.y;
updatePath(binding.keeper);
updateHandles(pointUI);
}
function init(svml) {
_controls.innerHTML = '';
_drawing.innerHTML = svml;
_svg = $$1('svg', _drawing);
svgUI.setAttr(_svg, 'width', '');
svgUI.setAttr(_svg, 'height', '');
//http://stackoverflow.com/questions/12592417/outerhtml-of-an-svg-element
_original.innerHTML = _drawing.innerHTML; //_svg.outerHTML;
_origSvg = $$1('svg', _original);
//__updateViewBox();
function onZoomed() {
//Sync all SVGs:
const zoomer = this,
vbAttr = '' + zoomer.getViewBox();
svgUI.setAttr(_controls, 'viewBox', vbAttr);
svgUI.setAttr(_svg, 'viewBox', vbAttr);
svgUI.setAttr(_origSvg, 'viewBox', vbAttr);
//Adjust the controls UI to stay the same size:
let zoom = zoomer.getZoom();
_controls.style.fontSize = (1/zoom) + 'em';
$$('.bez-control').forEach(function(x) { svgUI.setAttr(x, 'r', RAD_CONTROL/zoom); });
$$('.endpoint').forEach(function(x) { svgUI.setAttr(x, 'r', RAD_END/zoom); });
outputSvg();
}
_zoomer = zoomableSvg(_svg, {
container: _board,
onChanged: onZoomed
});
//Create UI for Bezier end and control points:
function createControlPoint(keeper, segment, index) {
//console.log('control-point', segment);
const c = svgUI.createElement(_controls, 'circle', {
class: 'bez-control',
'data-draggable': '',
cx: segment[index],
cy: segment[index+1],
r: RAD_CONTROL
});
c.dataBinding = new UIDataBinding(keeper, segment, index);
return c;
}
function createEndPoint(keeper, segment, index) {
const c = svgUI.createElement(_controls, 'circle', {
class: 'endpoint',
'data-draggable': '',
cx: segment[index],
cy: segment[index+1],
r: RAD_END
});
c.dataBinding = new UIDataBinding(keeper, segment, index);
return c;
}
function createHandle(p1, p2) {
const handle = svgUI.createElement(_controls, 'path', { class: 'bez-handle' });
function addHandle(p) {
p.__handles = p.__handles || [];
p.__handles.push(handle);
}
handle.__p1 = p1;
handle.__p2 = p2;
addHandle(p1, handle);
addHandle(p2, handle);
updateHandles(p1);
}
const paths = $$('path', _svg);
paths.forEach(function(path) {
const data = svgUI.getAttr(path, 'd'),
segmentsRel = RosomanSVG.parse(data),
segments = RosomanSVG.absolutize(segmentsRel),
keeper = new PathKeeper(path, segments);
let prevEndPoint, prevEndCoord;
//console.log(data, segmentsRel, segments);
//For simplify.js:
//console.log('points', JSON.stringify(segments.map(s => s.endPoint)));
segments.forEach(function(seg) {
const segType = seg[0];
let c1, c2, end;
//First, transform H/V to L so they get proper endpoint coordinates:
switch(segType) {
case 'H':
seg[0] = 'L';
seg[2] = prevEndCoord.y;
break;
case 'V':
seg[0] = 'L';
seg[2] = seg[1];
seg[1] = prevEndCoord.x;
break;
}
switch(segType) {
case 'C':
c1 = createControlPoint(keeper, seg, 1);
c2 = createControlPoint(keeper, seg, 3);
end = createEndPoint(keeper, seg, 5);
createHandle(c1, prevEndPoint);
createHandle(c2, end);
prevEndPoint = end;
prevEndCoord = new Coord(seg[5], seg[6]);
break;
//Standalone quad, or continued cubic:
case 'Q':
case 'S':
c1 = createControlPoint(keeper, seg, 1);
end = createEndPoint(keeper, seg, 3);
if(segType === 'Q') {
createHandle(c1, prevEndPoint);
}
createHandle(c1, end);
prevEndPoint = end;
prevEndCoord = new Coord(seg[3], seg[4]);
break;
case 'Z':
break;
//"L"/"H"/"V" (see above)
//"T" (continued quad - doesn't control its control point)
//"A" (TODO: Usable editor UI?)
default:
prevEndPoint = createEndPoint(keeper, seg, seg.length-2);
prevEndCoord = new Coord(seg[seg.length-2], seg[seg.length-1]);
break;
}
});
});
}
_input.onclick = function() {
//If no current selection
if(_input.selectionStart === _input.selectionEnd) {
this.select();
}
}
_input.oninput = function() {
var svg = _input.value.trim();
if(svg[0] !== '<') {
svg = '<svg xmlns="http://www.w3.org/2000/svg" >\n' +
' <path d="' + svg + '" fill="none" stroke="black" stroke-width="2"></path>\n' +
'</svg>';
}
init(svg);
};
_input.oninput();
function outputSvg() {
_output.value/*textContent*/ = _drawing.innerHTML.trim();
}
/*User interaction (drag & drop)*/
dragTracker({
container: _controls,
selector: '[data-draggable]',
callback: (box, pos, start) => {
movePoint(box, new Coord(pos[0], pos[1]));
outputSvg();
},
});
})();