diff --git a/history.md b/history.md deleted file mode 100644 index 3b771c4..0000000 --- a/history.md +++ /dev/null @@ -1,66 +0,0 @@ -#0.5.2 - -* pack fix -* transform into esm - -#0.5.1 - -* Bug fix - -#0.5.0 - -* Added color palettes for Material and FlatUI -* Added methods for gradients: `Element.stops()`, `Element.addStop()`, `Element.setStops()` -* Fixed matrix splitting for better animation of matrices` -* Various bug fixes -* Better integration of tests and ESlint - -#0.4.1 - -* Bug fixes. - -#0.4.0 - -* Moved class and element related code into separate plugins -* Added `Element.align()` and `Element.getAlign()` methods -* Added animation support for `viewBox` -* Added support for `` -* Added method `Paper.toDataURL()` -* Added method `Snap.closest()` -* Added methods to work with degrees instead of radians: `Snap.sin()`, `Snap.cos()`, `Snap.tan()`, `Snap.asin()`, `Snap.acos()`, `Snap.atan()` and `Snap.atan2()` -* Added methods `Snap.len()`, `Snap.len2()` and `Snap.closestPoint()` -* Added methods `Element.children()` and `Element.toJSON()` -* Various bug fixes - -#0.3.0 - -* Added `.addClass()`, `.removeClass()`, `.toggleClass()` and `.hasClass()` APIs -* Added `Paper.mask()`, `Paper.ptrn()`, `Paper.use()`, `Paper.svg()` -* Mask & pattern elements are sharing paper methods (just like group) -* Added `Set.bind()` method -* Added syncronisation for `Set.animate()` -* Added opacity to the shadow filter -* Added ability to specify attributes as `"+=10"` or `"-=1em"` or `"*=2"` -* Fix negative scale -* Fix for `path2curve` -* Fixed shared `` issue -* Various bug fixes - -#0.2.0 - -* Added support for text path -* Added `getBBox` method to the paper object -* Added `Element.appendTo()` and `Element.prependTo()` -* Added `getElementByPoint()` -* Added `Set.remove()` method -* Get rid of internal SVG parser in favor of the browser -* Fix for `xlink:href` setting for images -* Fix `Element.animate()` -* Fix for animate and stroke-dashoffset -* Absolute transforms fix -* Fix for animation of SVG transformations, matrices and polygon points -* Various bug fixes - -#0.1.0 - -* Initial release diff --git a/src/align.js b/src/align.js index b76591a..c667b51 100644 --- a/src/align.js +++ b/src/align.js @@ -11,17 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' +import { is } from './utils.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var box = Snap._.box, - is = Snap.is, - firstLetter = /^[^a-z]*([tbmlrc])/i, - toString = function () { - return 'T' + this.dx + ',' + this.dy - } - /*\ - * Element.getAlign +let box = Snap._.box, + firstLetter = /^[^a-z]*([tbmlrc])/i, + toString = function () { + return 'T' + this.dx + ',' + this.dy + } +/*\ + * SnapElement.getAlign [ method ] ** * Returns shift needed to align the element relatively to given element. @@ -32,50 +31,50 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { > Usage | el.transform(el.getAlign(el2, "top")); * or - | var dy = el.getAlign(el2, "top").dy; + | let dy = el.getAlign(el2, "top").dy; \*/ - Element.prototype.getAlign = function (el, way) { - if (way == null && is(el, 'string')) { - way = el - el = null - } - el = el || this.paper - var bx = el.getBBox ? el.getBBox() : box(el), - bb = this.getBBox(), - out = {} - way = way && way.match(firstLetter) - way = way ? way[1].toLowerCase() : 'c' - switch (way) { - case 't': - out.dx = 0 - out.dy = bx.y - bb.y - break - case 'b': - out.dx = 0 - out.dy = bx.y2 - bb.y2 - break - case 'm': - out.dx = 0 - out.dy = bx.cy - bb.cy - break - case 'l': - out.dx = bx.x - bb.x - out.dy = 0 - break - case 'r': - out.dx = bx.x2 - bb.x2 - out.dy = 0 - break - default: - out.dx = bx.cx - bb.cx - out.dy = 0 - break - } - out.toString = toString - return out +SnapElement.prototype.getAlign = function (el, way) { + if (way == null && is(el, 'string')) { + way = el + el = null } - /*\ - * Element.align + el = el || this.paper + let bx = el.getBBox ? el.getBBox() : box(el), + bb = this.getBBox(), + out = {} + way = way && way.match(firstLetter) + way = way ? way[1].toLowerCase() : 'c' + switch (way) { + case 't': + out.dx = 0 + out.dy = bx.y - bb.y + break + case 'b': + out.dx = 0 + out.dy = bx.y2 - bb.y2 + break + case 'm': + out.dx = 0 + out.dy = bx.cy - bb.cy + break + case 'l': + out.dx = bx.x - bb.x + out.dy = 0 + break + case 'r': + out.dx = bx.x2 - bb.x2 + out.dy = 0 + break + default: + out.dx = bx.cx - bb.cx + out.dy = 0 + break + } + out.toString = toString + return out +} +/*\ + * SnapElement.align [ method ] ** * Aligns the element relatively to given one via transformation. @@ -88,7 +87,6 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { * or | el.align("middle"); \*/ - Element.prototype.align = function (el, way) { - return this.transform('...' + this.getAlign(el, way)) - } -}) +SnapElement.prototype.align = function (el, way) { + return this.transform('...' + this.getAlign(el, way)) +} diff --git a/src/animation.js b/src/animation.js index 2fb468b..b9abcd5 100644 --- a/src/animation.js +++ b/src/animation.js @@ -13,35 +13,34 @@ // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' import mina from './mina.js' +import { is } from './utils.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var elproto = Element.prototype, - is = Snap.is, - Str = String, - has = 'hasOwnProperty' - function slice(from, to, f) { - return function (arr) { - var res = arr.slice(from, to) - if (res.length == 1) { - res = res[0] - } - return f ? f(res) : res +let elproto = SnapElement.prototype, + Str = String, + has = 'hasOwnProperty' +function slice(from, to, f) { + return function (arr) { + let res = arr.slice(from, to) + if (res.length == 1) { + res = res[0] } + return f ? f(res) : res } - var Animation = function (attr, ms, easing, callback) { - if (typeof easing == 'function' && !easing.length) { - callback = easing - easing = mina.linear - } - this.attr = attr - this.dur = ms - easing && (this.easing = easing) - callback && (this.callback = callback) +} +let Animation = function (attr, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear } - Snap._.Animation = Animation - /*\ + this.attr = attr + this.dur = ms + easing && (this.easing = easing) + callback && (this.callback = callback) +} +Snap._.Animation = Animation +/*\ * Snap.animation [ method ] ** @@ -53,11 +52,11 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - callback (function) #optional callback function that fires when animation ends = (object) animation object \*/ - Snap.animation = function (attr, ms, easing, callback) { - return new Animation(attr, ms, easing, callback) - } - /*\ - * Element.inAnim +Snap.animation = function (attr, ms, easing, callback) { + return new Animation(attr, ms, easing, callback) +} +/*\ + * SnapElement.inAnim [ method ] ** * Returns a set of animations that may be able to manipulate the current element @@ -71,28 +70,28 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { o stop (function) stops the animation o } \*/ - elproto.inAnim = function () { - var el = this, - res = [] - for (var id in el.anims) - if (el.anims[has](id)) { - ;(function (a) { - res.push({ - anim: new Animation(a._attrs, a.dur, a.easing, a._callback), - mina: a, - curStatus: a.status(), - status: function (val) { - return a.status(val) - }, - stop: function () { - a.stop() - } - }) - })(el.anims[id]) - } - return res - } - /*\ +elproto.inAnim = function () { + let el = this, + res = [] + for (let id in el.anims) + if (el.anims[has](id)) { + ;(function (a) { + res.push({ + anim: new Animation(a._attrs, a.dur, a.easing, a._callback), + mina: a, + curStatus: a.status(), + status: function (val) { + return a.status(val) + }, + stop: function () { + a.stop() + } + }) + })(el.anims[id]) + } + return res +} +/*\ * Snap.animate [ method ] ** @@ -113,7 +112,7 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } - | var rect = Snap().rect(0, 0, 10, 10); + | let rect = new Snap().rect(0, 0, 10, 10); | Snap.animate(0, 10, function (val) { | rect.attr({ | x: val @@ -122,33 +121,33 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { | // in given context is equivalent to | rect.animate({x: 10}, 1000); \*/ - Snap.animate = function (from, to, setter, ms, easing, callback) { - if (typeof easing == 'function' && !easing.length) { - callback = easing - easing = mina.linear - } - var now = mina.time(), - anim = mina(from, to, now, now + ms, mina.time, setter, easing) - callback && eve.once('mina.finish.' + anim.id, callback) - return anim +Snap.animate = function (from, to, setter, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear } - /*\ - * Element.stop + let now = mina.time(), + anim = mina(from, to, now, now + ms, mina.time, setter, easing) + callback && eve.once('mina.finish.' + anim.id, callback) + return anim +} +/*\ + * SnapElement.stop [ method ] ** * Stops all the animations for the current element ** - = (Element) the current element + = (SnapElement) the current element \*/ - elproto.stop = function () { - var anims = this.inAnim() - for (var i = 0, ii = anims.length; i < ii; i++) { - anims[i].stop() - } - return this +elproto.stop = function () { + let anims = this.inAnim() + for (let i = 0, ii = anims.length; i < ii; i++) { + anims[i].stop() } - /*\ - * Element.animate + return this +} +/*\ + * SnapElement.animate [ method ] ** * Animates the given attributes of the element @@ -157,73 +156,72 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends - = (Element) the current element + = (SnapElement) the current element \*/ - elproto.animate = function (attrs, ms, easing, callback) { - if (typeof easing == 'function' && !easing.length) { - callback = easing - easing = mina.linear - } - if (attrs instanceof Animation) { - callback = attrs.callback - easing = attrs.easing - ms = attrs.dur - attrs = attrs.attr - } - var fkeys = [], - tkeys = [], - keys = {}, - from, - to, - f, - eq, - el = this - for (var key in attrs) - if (attrs[has](key)) { - if (el.equal) { - eq = el.equal(key, Str(attrs[key])) - from = eq.from - to = eq.to - f = eq.f - } else { - from = +el.attr(key) - to = +attrs[key] - } - var len = is(from, 'array') ? from.length : 1 - keys[key] = slice(fkeys.length, fkeys.length + len, f) - fkeys = fkeys.concat(from) - tkeys = tkeys.concat(to) - } - var now = mina.time(), - anim = mina( - fkeys, - tkeys, - now, - now + ms, - mina.time, - function (val) { - var attr = {} - for (var key in keys) - if (keys[has](key)) { - attr[key] = keys[key](val) - } - el.attr(attr) - }, - easing - ) - el.anims[anim.id] = anim - anim._attrs = attrs - anim._callback = callback - eve('snap.animcreated.' + el.id, anim) - eve.once('mina.finish.' + anim.id, function () { - eve.off('mina.*.' + anim.id) - delete el.anims[anim.id] - callback && callback.call(el) - }) - eve.once('mina.stop.' + anim.id, function () { - eve.off('mina.*.' + anim.id) - delete el.anims[anim.id] - }) - return el +elproto.animate = function (attrs, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear } -}) + if (attrs instanceof Animation) { + callback = attrs.callback + easing = attrs.easing + ms = attrs.dur + attrs = attrs.attr + } + let fkeys = [], + tkeys = [], + keys = {}, + from, + to, + f, + eq, + el = this + for (let key in attrs) + if (attrs[has](key)) { + if (el.equal) { + eq = el.equal(key, Str(attrs[key])) + from = eq.from + to = eq.to + f = eq.f + } else { + from = +el.attr(key) + to = +attrs[key] + } + let len = is(from, 'array') ? from.length : 1 + keys[key] = slice(fkeys.length, fkeys.length + len, f) + fkeys = fkeys.concat(from) + tkeys = tkeys.concat(to) + } + let now = mina.time(), + anim = mina( + fkeys, + tkeys, + now, + now + ms, + mina.time, + function (val) { + let attr = {} + for (let key in keys) + if (keys[has](key)) { + attr[key] = keys[key](val) + } + el.attr(attr) + }, + easing + ) + el.anims[anim.id] = anim + anim._attrs = attrs + anim._callback = callback + eve('snap.animcreated.' + el.id, anim) + eve.once('mina.finish.' + anim.id, function () { + eve.off('mina.*.' + anim.id) + delete el.anims[anim.id] + callback && callback.call(el) + }) + eve.once('mina.stop.' + anim.id, function () { + eve.off('mina.*.' + anim.id) + delete el.anims[anim.id] + }) + return el +} diff --git a/src/attr.js b/src/attr.js index 1761d36..64f2273 100644 --- a/src/attr.js +++ b/src/attr.js @@ -12,21 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment, getSomeDefs } from './svg.js' +import { $, is, url } from './utils.js' +import { doc, win, SEPARATOR } from './lib/constants.js' +import { parseColor } from './lib/color.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var has = 'hasOwnProperty', - make = Snap._.make, - wrap = Snap._.wrap, - is = Snap.is, - getSomeDefs = Snap._.getSomeDefs, - reURLValue = /^url\((['"]?)([^)]+)\1\)$/, - $ = Snap._.$, - URL = Snap.url, - Str = String, - separator = Snap._.separator, - E = '' - /*\ +let has = 'hasOwnProperty', + reURLValue = /^url\((['"]?)([^)]+)\1\)$/, + Str = String, + E = '' +/*\ * Snap.deurl [ method ] ** @@ -34,447 +29,447 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - value (string) url path = (string) unwrapped path \*/ - Snap.deurl = function (value) { - var res = String(value).match(reURLValue) - return res ? res[2] : value - } - // Attributes event handlers - eve.on('snap.util.attr.mask', function (value) { - if (value instanceof Element || value instanceof Fragment) { - eve.stop() - if (value instanceof Fragment && value.node.childNodes.length == 1) { - value = value.node.firstChild - getSomeDefs(this).appendChild(value) - value = wrap(value) - } - if (value.type == 'mask') { - var mask = value - } else { - mask = make('mask', getSomeDefs(this)) - mask.node.appendChild(value.node) - } - !mask.node.id && - $(mask.node, { - id: mask.id - }) - $(this.node, { - mask: URL(mask.id) - }) - } - }) - ;(function (clipIt) { - eve.on('snap.util.attr.clip', clipIt) - eve.on('snap.util.attr.clip-path', clipIt) - eve.on('snap.util.attr.clipPath', clipIt) - })(function (value) { - if (value instanceof Element || value instanceof Fragment) { - eve.stop() - var clip, - node = value.node - while (node) { - if (node.nodeName === 'clipPath') { - clip = new Element(node) - break - } - if (node.nodeName === 'svg') { - clip = undefined - break - } - node = node.parentNode - } - if (!clip) { - clip = make('clipPath', getSomeDefs(this)) - clip.node.appendChild(value.node) - !clip.node.id && - $(clip.node, { - id: clip.id - }) - } - $(this.node, { - 'clip-path': URL(clip.node.id || clip.id) - }) - } - }) - function fillStroke(name) { - return function (value) { - eve.stop() - if ( - value instanceof Fragment && - value.node.childNodes.length == 1 && - (value.node.firstChild.tagName == 'radialGradient' || - value.node.firstChild.tagName == 'linearGradient' || - value.node.firstChild.tagName == 'pattern') - ) { - value = value.node.firstChild - getSomeDefs(this).appendChild(value) - value = wrap(value) - } - if (value instanceof Element) { - if ( - value.type == 'radialGradient' || - value.type == 'linearGradient' || - value.type == 'pattern' - ) { - if (!value.node.id) { - $(value.node, { - id: value.id - }) - } - var fill = URL(value.node.id) - } else { - fill = value.attr(name) - } - } else { - fill = Snap.color(value) - if (fill.error) { - var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value) - if (grad) { - if (!grad.node.id) { - $(grad.node, { - id: grad.id - }) - } - fill = URL(grad.node.id) - } else { - fill = value - } - } else { - fill = Str(fill) - } - } - var attrs = {} - attrs[name] = fill - $(this.node, attrs) - this.node.style[name] = E - } - } - eve.on('snap.util.attr.fill', fillStroke('fill')) - eve.on('snap.util.attr.stroke', fillStroke('stroke')) - var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i - eve.on('snap.util.grad.parse', function parseGrad(string) { - string = Str(string) - var tokens = string.match(gradrg) - if (!tokens) { - return null - } - var type = tokens[1], - params = tokens[2], - stops = tokens[3] - params = params.split(/\s*,\s*/).map(function (el) { - return +el == el ? +el : el - }) - if (params.length == 1 && params[0] == 0) { - params = [] - } - stops = stops.split('-') - stops = stops.map(function (el) { - el = el.split(':') - var out = { - color: el[0] - } - if (el[1]) { - out.offset = parseFloat(el[1]) - } - return out - }) - var len = stops.length, - start = 0, - j = 0 - function seed(i, end) { - var step = (end - start) / (i - j) - for (var k = j; k < i; k++) { - stops[k].offset = +(+start + step * (k - j)).toFixed(2) - } - j = i - start = end - } - len-- - for (var i = 0; i < len; i++) - if ('offset' in stops[i]) { - seed(i, stops[i].offset) - } - stops[len].offset = stops[len].offset || 100 - seed(len, stops[len].offset) - return { - type: type, - params: params, - stops: stops - } - }) - - eve.on('snap.util.attr.d', function (value) { +Snap.deurl = function (value) { + let res = String(value).match(reURLValue) + return res ? res[2] : value +} +// Attributes event handlers +eve.on('snap.util.attr.mask', function (value) { + if (value instanceof SnapElement || value instanceof Fragment) { eve.stop() - if (is(value, 'array') && is(value[0], 'array')) { - value = Snap.path.toString.call(value) + if (value instanceof Fragment && value.node.childNodes.length == 1) { + value = value.node.firstChild + getSomeDefs(this).appendChild(value) + value = wrap(value) } - value = Str(value) - if (value.match(/[ruo]/i)) { - value = Snap.path.toAbsolute(value) - } - $(this.node, { d: value }) - })(-1) - eve.on('snap.util.attr.#text', function (value) { - eve.stop() - value = Str(value) - var txt = glob.doc.createTextNode(value) - while (this.node.firstChild) { - this.node.removeChild(this.node.firstChild) - } - this.node.appendChild(txt) - })(-1) - eve.on('snap.util.attr.path', function (value) { - eve.stop() - this.attr({ d: value }) - })(-1) - eve.on('snap.util.attr.class', function (value) { - eve.stop() - this.node.className.baseVal = value - })(-1) - eve.on('snap.util.attr.viewBox', function (value) { - var vb - if (is(value, 'object') && 'x' in value) { - vb = [value.x, value.y, value.width, value.height].join(' ') - } else if (is(value, 'array')) { - vb = value.join(' ') + if (value.type == 'mask') { + let mask = value } else { - vb = value + mask = make('mask', getSomeDefs(this)) + mask.node.appendChild(value.node) + } + !mask.node.id && + $(mask.node, { + id: mask.id + }) + $(this.node, { + mask: url(mask.id) + }) + } +}) +;(function (clipIt) { + eve.on('snap.util.attr.clip', clipIt) + eve.on('snap.util.attr.clip-path', clipIt) + eve.on('snap.util.attr.clipPath', clipIt) +})(function (value) { + if (value instanceof SnapElement || value instanceof Fragment) { + eve.stop() + let clip, + node = value.node + while (node) { + if (node.nodeName === 'clipPath') { + clip = new SnapElement(node) + break + } + if (node.nodeName === 'svg') { + clip = undefined + break + } + node = node.parentNode + } + if (!clip) { + clip = make('clipPath', getSomeDefs(this)) + clip.node.appendChild(value.node) + !clip.node.id && + $(clip.node, { + id: clip.id + }) } $(this.node, { - viewBox: vb + 'clip-path': url(clip.node.id || clip.id) }) - eve.stop() - })(-1) - eve.on('snap.util.attr.transform', function (value) { - this.transform(value) - eve.stop() - })(-1) - eve.on('snap.util.attr.r', function (value) { - if (this.type == 'rect') { - eve.stop() - $(this.node, { - rx: value, - ry: value - }) - } - })(-1) - eve.on('snap.util.attr.textpath', function (value) { - eve.stop() - if (this.type == 'text') { - var id, tp, node - if (!value && this.textPath) { - tp = this.textPath - while (tp.node.firstChild) { - this.node.appendChild(tp.node.firstChild) - } - tp.remove() - delete this.textPath - return - } - if (is(value, 'string')) { - var defs = getSomeDefs(this), - path = wrap(defs.parentNode).path(value) - defs.appendChild(path.node) - id = path.id - path.attr({ id: id }) - } else { - value = wrap(value) - if (value instanceof Element) { - id = value.attr('id') - if (!id) { - id = value.id - value.attr({ id: id }) - } - } - } - if (id) { - tp = this.textPath - node = this.node - if (tp) { - tp.attr({ 'xlink:href': '#' + id }) - } else { - tp = $('textPath', { - 'xlink:href': '#' + id - }) - while (node.firstChild) { - tp.appendChild(node.firstChild) - } - node.appendChild(tp) - this.textPath = wrap(tp) - } - } - } - })(-1) - eve.on('snap.util.attr.text', function (value) { - if (this.type == 'text') { - var i = 0, - node = this.node, - tuner = function (chunk) { - var out = $('tspan') - if (is(chunk, 'array')) { - for (var i = 0; i < chunk.length; i++) { - out.appendChild(tuner(chunk[i])) - } - } else { - out.appendChild(glob.doc.createTextNode(chunk)) - } - out.normalize && out.normalize() - return out - } - while (node.firstChild) { - node.removeChild(node.firstChild) - } - var tuned = tuner(value) - while (tuned.firstChild) { - node.appendChild(tuned.firstChild) - } - } - eve.stop() - })(-1) - function setFontSize(value) { - eve.stop() - if (value == +value) { - value += 'px' - } - this.node.style.fontSize = value } - eve.on('snap.util.attr.fontSize', setFontSize)(-1) - eve.on('snap.util.attr.font-size', setFontSize)(-1) - - eve.on('snap.util.getattr.transform', function () { +}) +function fillStroke(name) { + return function (value) { eve.stop() - return this.transform() - })(-1) - eve.on('snap.util.getattr.textpath', function () { - eve.stop() - return this.textPath - })(-1) - // Markers - ;(function () { - function getter(end) { - return function () { - eve.stop() - var style = glob.doc.defaultView - .getComputedStyle(this.node, null) - .getPropertyValue('marker-' + end) - if (style == 'none') { - return style - } else { - return Snap(glob.doc.getElementById(style.match(reURLValue)[1])) - } - } + let fill + if ( + value instanceof Fragment && + value.node.childNodes.length == 1 && + (value.node.firstChild.tagName == 'radialGradient' || + value.node.firstChild.tagName == 'linearGradient' || + value.node.firstChild.tagName == 'pattern') + ) { + value = value.node.firstChild + getSomeDefs(this).appendChild(value) + value = wrap(value) } - function setter(end) { - return function (value) { - eve.stop() - var name = 'marker' + end.charAt(0).toUpperCase() + end.substring(1) - if (value == '' || !value) { - this.node.style[name] = 'none' - return + if (value instanceof SnapElement) { + if ( + value.type == 'radialGradient' || + value.type == 'linearGradient' || + value.type == 'pattern' + ) { + if (!value.node.id) { + $(value.node, { + id: value.id + }) } - if (value.type == 'marker') { - var id = value.node.id - if (!id) { - $(value.node, { id: value.id }) + fill = url(value.node.id) + } else { + fill = value.attr(name) + } + } else { + fill = parseColor(value) + if (fill.error) { + let grad = new Snap(getSomeDefs(this).ownerSVGElement).gradient(value) + if (grad) { + if (!grad.node.id) { + $(grad.node, { + id: grad.id + }) } - this.node.style[name] = URL(id) - return - } - } - } - eve.on('snap.util.getattr.marker-end', getter('end'))(-1) - eve.on('snap.util.getattr.markerEnd', getter('end'))(-1) - eve.on('snap.util.getattr.marker-start', getter('start'))(-1) - eve.on('snap.util.getattr.markerStart', getter('start'))(-1) - eve.on('snap.util.getattr.marker-mid', getter('mid'))(-1) - eve.on('snap.util.getattr.markerMid', getter('mid'))(-1) - eve.on('snap.util.attr.marker-end', setter('end'))(-1) - eve.on('snap.util.attr.markerEnd', setter('end'))(-1) - eve.on('snap.util.attr.marker-start', setter('start'))(-1) - eve.on('snap.util.attr.markerStart', setter('start'))(-1) - eve.on('snap.util.attr.marker-mid', setter('mid'))(-1) - eve.on('snap.util.attr.markerMid', setter('mid'))(-1) - })() - eve.on('snap.util.getattr.r', function () { - if (this.type == 'rect' && $(this.node, 'rx') == $(this.node, 'ry')) { - eve.stop() - return $(this.node, 'rx') - } - })(-1) - function textExtract(node) { - var out = [] - var children = node.childNodes - for (var i = 0, ii = children.length; i < ii; i++) { - var chi = children[i] - if (chi.nodeType == 3) { - out.push(chi.nodeValue) - } - if (chi.tagName == 'tspan') { - if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { - out.push(chi.firstChild.nodeValue) + fill = url(grad.node.id) } else { - out.push(textExtract(chi)) + fill = value } + } else { + fill = Str(fill) } } + let attrs = {} + attrs[name] = fill + $(this.node, attrs) + this.node.style[name] = E + } +} +eve.on('snap.util.attr.fill', fillStroke('fill')) +eve.on('snap.util.attr.stroke', fillStroke('stroke')) +let gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i +eve.on('snap.util.grad.parse', function parseGrad(string) { + string = Str(string) + let tokens = string.match(gradrg) + if (!tokens) { + return null + } + let type = tokens[1], + params = tokens[2], + stops = tokens[3] + params = params.split(/\s*,\s*/).map(function (el) { + return +el == el ? +el : el + }) + if (params.length == 1 && params[0] == 0) { + params = [] + } + stops = stops.split('-') + stops = stops.map(function (el) { + el = el.split(':') + let out = { + color: el[0] + } + if (el[1]) { + out.offset = parseFloat(el[1]) + } return out + }) + let len = stops.length, + start = 0, + j = 0 + function seed(i, end) { + let step = (end - start) / (i - j) + for (let k = j; k < i; k++) { + stops[k].offset = +(+start + step * (k - j)).toFixed(2) + } + j = i + start = end } - eve.on('snap.util.getattr.text', function () { - if (this.type == 'text' || this.type == 'tspan') { - eve.stop() - var out = textExtract(this.node) - return out.length == 1 ? out[0] : out + len-- + for (let i = 0; i < len; i++) + if ('offset' in stops[i]) { + seed(i, stops[i].offset) } - })(-1) - eve.on('snap.util.getattr.#text', function () { - return this.node.textContent - })(-1) - eve.on('snap.util.getattr.fill', function (internal) { - if (internal) { - return - } - eve.stop() - var value = eve('snap.util.getattr.fill', this, true).firstDefined() - return Snap(Snap.deurl(value)) || value - })(-1) - eve.on('snap.util.getattr.stroke', function (internal) { - if (internal) { - return - } - eve.stop() - var value = eve('snap.util.getattr.stroke', this, true).firstDefined() - return Snap(Snap.deurl(value)) || value - })(-1) - eve.on('snap.util.getattr.viewBox', function () { - eve.stop() - var vb = $(this.node, 'viewBox') - if (vb) { - vb = vb.split(separator) - return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]) - } else { - return - } - })(-1) - eve.on('snap.util.getattr.points', function () { - var p = $(this.node, 'points') - eve.stop() - if (p) { - return p.split(separator) - } else { - return - } - })(-1) - eve.on('snap.util.getattr.path', function () { - var p = $(this.node, 'd') - eve.stop() - return p - })(-1) - eve.on('snap.util.getattr.class', function () { - return this.node.className.baseVal - })(-1) - function getFontSize() { - eve.stop() - return this.node.style.fontSize + stops[len].offset = stops[len].offset || 100 + seed(len, stops[len].offset) + return { + type: type, + params: params, + stops: stops } - eve.on('snap.util.getattr.fontSize', getFontSize)(-1) - eve.on('snap.util.getattr.font-size', getFontSize)(-1) }) + +eve.on('snap.util.attr.d', function (value) { + eve.stop() + if (is(value, 'array') && is(value[0], 'array')) { + value = Snap.path.toString.call(value) + } + value = Str(value) + if (value.match(/[ruo]/i)) { + value = Snap.path.toAbsolute(value) + } + $(this.node, { d: value }) +})(-1) +eve.on('snap.util.attr.#text', function (value) { + eve.stop() + value = Str(value) + let txt = doc.createTextNode(value) + while (this.node.firstChild) { + this.node.removeChild(this.node.firstChild) + } + this.node.appendChild(txt) +})(-1) +eve.on('snap.util.attr.path', function (value) { + eve.stop() + this.attr({ d: value }) +})(-1) +eve.on('snap.util.attr.class', function (value) { + eve.stop() + this.node.className.baseVal = value +})(-1) +eve.on('snap.util.attr.viewBox', function (value) { + let vb + if (is(value, 'object') && 'x' in value) { + vb = [value.x, value.y, value.width, value.height].join(' ') + } else if (is(value, 'array')) { + vb = value.join(' ') + } else { + vb = value + } + $(this.node, { + viewBox: vb + }) + eve.stop() +})(-1) +eve.on('snap.util.attr.transform', function (value) { + this.transform(value) + eve.stop() +})(-1) +eve.on('snap.util.attr.r', function (value) { + if (this.type == 'rect') { + eve.stop() + $(this.node, { + rx: value, + ry: value + }) + } +})(-1) +eve.on('snap.util.attr.textpath', function (value) { + eve.stop() + if (this.type == 'text') { + let id, tp, node + if (!value && this.textPath) { + tp = this.textPath + while (tp.node.firstChild) { + this.node.appendChild(tp.node.firstChild) + } + tp.remove() + delete this.textPath + return + } + if (is(value, 'string')) { + let defs = getSomeDefs(this), + path = wrap(defs.parentNode).path(value) + defs.appendChild(path.node) + id = path.id + path.attr({ id: id }) + } else { + value = wrap(value) + if (value instanceof SnapElement) { + id = value.attr('id') + if (!id) { + id = value.id + value.attr({ id: id }) + } + } + } + if (id) { + tp = this.textPath + node = this.node + if (tp) { + tp.attr({ 'xlink:href': '#' + id }) + } else { + tp = $('textPath', { + 'xlink:href': '#' + id + }) + while (node.firstChild) { + tp.appendChild(node.firstChild) + } + node.appendChild(tp) + this.textPath = wrap(tp) + } + } + } +})(-1) +eve.on('snap.util.attr.text', function (value) { + if (this.type == 'text') { + let i = 0, + node = this.node, + tuner = function (chunk) { + let out = $('tspan') + if (is(chunk, 'array')) { + for (let i = 0; i < chunk.length; i++) { + out.appendChild(tuner(chunk[i])) + } + } else { + out.appendChild(doc.createTextNode(chunk)) + } + out.normalize && out.normalize() + return out + } + while (node.firstChild) { + node.removeChild(node.firstChild) + } + let tuned = tuner(value) + while (tuned.firstChild) { + node.appendChild(tuned.firstChild) + } + } + eve.stop() +})(-1) +function setFontSize(value) { + eve.stop() + if (value == +value) { + value += 'px' + } + this.node.style.fontSize = value +} +eve.on('snap.util.attr.fontSize', setFontSize)(-1) +eve.on('snap.util.attr.font-size', setFontSize)(-1) + +eve.on('snap.util.getattr.transform', function () { + eve.stop() + return this.transform() +})(-1) +eve.on('snap.util.getattr.textpath', function () { + eve.stop() + return this.textPath +})(-1) +// Markers +;(function () { + function getter(end) { + return function () { + eve.stop() + let style = doc.defaultView + .getComputedStyle(this.node, null) + .getPropertyValue('marker-' + end) + if (style == 'none') { + return style + } else { + return new Snap(doc.getElementById(style.match(reURLValue)[1])) + } + } + } + function setter(end) { + return function (value) { + eve.stop() + let name = 'marker' + end.charAt(0).toUpperCase() + end.substring(1) + if (value == '' || !value) { + this.node.style[name] = 'none' + return + } + if (value.type == 'marker') { + let id = value.node.id + if (!id) { + $(value.node, { id: value.id }) + } + this.node.style[name] = url(id) + return + } + } + } + eve.on('snap.util.getattr.marker-end', getter('end'))(-1) + eve.on('snap.util.getattr.markerEnd', getter('end'))(-1) + eve.on('snap.util.getattr.marker-start', getter('start'))(-1) + eve.on('snap.util.getattr.markerStart', getter('start'))(-1) + eve.on('snap.util.getattr.marker-mid', getter('mid'))(-1) + eve.on('snap.util.getattr.markerMid', getter('mid'))(-1) + eve.on('snap.util.attr.marker-end', setter('end'))(-1) + eve.on('snap.util.attr.markerEnd', setter('end'))(-1) + eve.on('snap.util.attr.marker-start', setter('start'))(-1) + eve.on('snap.util.attr.markerStart', setter('start'))(-1) + eve.on('snap.util.attr.marker-mid', setter('mid'))(-1) + eve.on('snap.util.attr.markerMid', setter('mid'))(-1) +})() +eve.on('snap.util.getattr.r', function () { + if (this.type == 'rect' && $(this.node, 'rx') == $(this.node, 'ry')) { + eve.stop() + return $(this.node, 'rx') + } +})(-1) +function textExtract(node) { + let out = [] + let children = node.childNodes + for (let i = 0, ii = children.length; i < ii; i++) { + let chi = children[i] + if (chi.nodeType == 3) { + out.push(chi.nodeValue) + } + if (chi.tagName == 'tspan') { + if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { + out.push(chi.firstChild.nodeValue) + } else { + out.push(textExtract(chi)) + } + } + } + return out +} +eve.on('snap.util.getattr.text', function () { + if (this.type == 'text' || this.type == 'tspan') { + eve.stop() + let out = textExtract(this.node) + return out.length == 1 ? out[0] : out + } +})(-1) +eve.on('snap.util.getattr.#text', function () { + return this.node.textContent +})(-1) +eve.on('snap.util.getattr.fill', function (internal) { + if (internal) { + return + } + eve.stop() + let value = eve('snap.util.getattr.fill', this, true).firstDefined() + return new Snap(Snap.deurl(value)) || value +})(-1) +eve.on('snap.util.getattr.stroke', function (internal) { + if (internal) { + return + } + eve.stop() + let value = eve('snap.util.getattr.stroke', this, true).firstDefined() + return new Snap(Snap.deurl(value)) || value +})(-1) +eve.on('snap.util.getattr.viewBox', function () { + eve.stop() + let vb = $(this.node, 'viewBox') + if (vb) { + vb = vb.split(SEPARATOR) + return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]) + } else { + return + } +})(-1) +eve.on('snap.util.getattr.points', function () { + let p = $(this.node, 'points') + eve.stop() + if (p) { + return p.split(SEPARATOR) + } else { + return + } +})(-1) +eve.on('snap.util.getattr.path', function () { + let p = $(this.node, 'd') + eve.stop() + return p +})(-1) +eve.on('snap.util.getattr.class', function () { + return this.node.className.baseVal +})(-1) +function getFontSize() { + eve.stop() + return this.node.style.fontSize +} +eve.on('snap.util.getattr.fontSize', getFontSize)(-1) +eve.on('snap.util.getattr.font-size', getFontSize)(-1) diff --git a/src/attradd.js b/src/attradd.js index 897af3e..b45b15e 100644 --- a/src/attradd.js +++ b/src/attradd.js @@ -12,83 +12,81 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var operators = { - '+': function (x, y) { - return x + y - }, - '-': function (x, y) { - return x - y - }, - '/': function (x, y) { - return x / y - }, - '*': function (x, y) { - return x * y - } +let operators = { + '+': function (x, y) { + return x + y }, - Str = String, - reUnit = /[a-z]+$/i, - reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/ - function getNumber(val) { - return val + '-': function (x, y) { + return x - y + }, + '/': function (x, y) { + return x / y + }, + '*': function (x, y) { + return x * y + } + }, + Str = String, + reUnit = /[a-z]+$/i, + reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/ +function getNumber(val) { + return val +} +function getUnit(unit) { + return function (val) { + return +val.toFixed(3) + unit } - function getUnit(unit) { - return function (val) { - return +val.toFixed(3) + unit +} +eve.on('snap.util.attr', function (val) { + let plus = Str(val).match(reAddon) + if (plus) { + let evnt = eve.nt(), + name = evnt.substring(evnt.lastIndexOf('.') + 1), + a = this.attr(name), + atr = {} + eve.stop() + let unit = plus[3] || '', + aUnit = a.match(reUnit), + op = operators[plus[1]] + if (aUnit && aUnit == unit) { + val = op(parseFloat(a), +plus[2]) + } else { + a = this.asPX(name) + val = op(this.asPX(name), this.asPX(name, plus[2] + unit)) + } + if (isNaN(a) || isNaN(val)) { + return + } + atr[name] = val + this.attr(atr) + } +})(-10) +eve.on('snap.util.equal', function (name, b) { + let A, + B, + a = Str(this.attr(name) || ''), + el = this, + bplus = Str(b).match(reAddon) + if (bplus) { + eve.stop() + let unit = bplus[3] || '', + aUnit = a.match(reUnit), + op = operators[bplus[1]] + if (aUnit && aUnit == unit) { + return { + from: parseFloat(a), + to: op(parseFloat(a), +bplus[2]), + f: getUnit(aUnit) + } + } else { + a = this.asPX(name) + return { + from: a, + to: op(a, this.asPX(name, bplus[2] + unit)), + f: getNumber + } } } - eve.on('snap.util.attr', function (val) { - var plus = Str(val).match(reAddon) - if (plus) { - var evnt = eve.nt(), - name = evnt.substring(evnt.lastIndexOf('.') + 1), - a = this.attr(name), - atr = {} - eve.stop() - var unit = plus[3] || '', - aUnit = a.match(reUnit), - op = operators[plus[1]] - if (aUnit && aUnit == unit) { - val = op(parseFloat(a), +plus[2]) - } else { - a = this.asPX(name) - val = op(this.asPX(name), this.asPX(name, plus[2] + unit)) - } - if (isNaN(a) || isNaN(val)) { - return - } - atr[name] = val - this.attr(atr) - } - })(-10) - eve.on('snap.util.equal', function (name, b) { - var A, - B, - a = Str(this.attr(name) || ''), - el = this, - bplus = Str(b).match(reAddon) - if (bplus) { - eve.stop() - var unit = bplus[3] || '', - aUnit = a.match(reUnit), - op = operators[bplus[1]] - if (aUnit && aUnit == unit) { - return { - from: parseFloat(a), - to: op(parseFloat(a), +bplus[2]), - f: getUnit(aUnit) - } - } else { - a = this.asPX(name) - return { - from: a, - to: op(a, this.asPX(name, bplus[2] + unit)), - f: getNumber - } - } - } - })(-10) -}) +})(-10) diff --git a/src/class.js b/src/class.js index 1e51f20..526aaef 100644 --- a/src/class.js +++ b/src/class.js @@ -1,140 +1,39 @@ -// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import { Snap } from './svg.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var rgNotSpace = /\S+/g, - rgBadSpace = /[\t\r\n\f]/g, - rgTrim = /(^\s+|\s+$)/g, - Str = String, - elproto = Element.prototype - /*\ - * Element.addClass - [ method ] +/** + * {} + * @author yutent + * @date 2024/03/06 16:34:24 + */ + +import { Snap, SnapElement, Paper, Fragment } from './svg.js' + +let rgNotSpace = /\S+/g, + rgBadSpace = /[\t\r\n\f]/g, + rgTrim = /(^\s+|\s+$)/g, + Str = String, + elproto = SnapElement.prototype +/*\ + ** * Adds given class name or list of class names to the element. - value (string) class name or space separated list of class names ** - = (Element) original element. + = (SnapElement) original element. \*/ - elproto.addClass = function (value) { - var classes = Str(value || '').match(rgNotSpace) || [], - elem = this.node, - className = elem.className.baseVal, - curClasses = className.match(rgNotSpace) || [], - j, - pos, - clazz, - finalValue +elproto.addClass = function (value) { + let classes = Str(value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue - if (classes.length) { - j = 0 - while ((clazz = classes[j++])) { - pos = curClasses.indexOf(clazz) - if (!~pos) { - curClasses.push(clazz) - } - } - - finalValue = curClasses.join(' ') - if (className != finalValue) { - elem.className.baseVal = finalValue - } - } - return this - } - /*\ - * Element.removeClass - [ method ] - ** - * Removes given class name or list of class names from the element. - - value (string) class name or space separated list of class names - ** - = (Element) original element. - \*/ - elproto.removeClass = function (value) { - var classes = Str(value || '').match(rgNotSpace) || [], - elem = this.node, - className = elem.className.baseVal, - curClasses = className.match(rgNotSpace) || [], - j, - pos, - clazz, - finalValue - if (curClasses.length) { - j = 0 - while ((clazz = classes[j++])) { - pos = curClasses.indexOf(clazz) - if (~pos) { - curClasses.splice(pos, 1) - } - } - - finalValue = curClasses.join(' ') - if (className != finalValue) { - elem.className.baseVal = finalValue - } - } - return this - } - /*\ - * Element.hasClass - [ method ] - ** - * Checks if the element has a given class name in the list of class names applied to it. - - value (string) class name - ** - = (boolean) `true` if the element has given class - \*/ - elproto.hasClass = function (value) { - var elem = this.node, - className = elem.className.baseVal, - curClasses = className.match(rgNotSpace) || [] - return !!~curClasses.indexOf(value) - } - /*\ - * Element.toggleClass - [ method ] - ** - * Add or remove one or more classes from the element, depending on either - * the class’s presence or the value of the `flag` argument. - - value (string) class name or space separated list of class names - - flag (boolean) value to determine whether the class should be added or removed - ** - = (Element) original element. - \*/ - elproto.toggleClass = function (value, flag) { - if (flag != null) { - if (flag) { - return this.addClass(value) - } else { - return this.removeClass(value) - } - } - var classes = (value || '').match(rgNotSpace) || [], - elem = this.node, - className = elem.className.baseVal, - curClasses = className.match(rgNotSpace) || [], - j, - pos, - clazz, - finalValue + if (classes.length) { j = 0 while ((clazz = classes[j++])) { pos = curClasses.indexOf(clazz) - if (~pos) { - curClasses.splice(pos, 1) - } else { + if (!~pos) { curClasses.push(clazz) } } @@ -143,6 +42,95 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { if (className != finalValue) { elem.className.baseVal = finalValue } - return this } -}) + return this +} +/*\ + + ** + * Removes given class name or list of class names from the element. + - value (string) class name or space separated list of class names + ** + = (SnapElement) original element. + \*/ +elproto.removeClass = function (value) { + let classes = Str(value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue + if (curClasses.length) { + j = 0 + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz) + if (~pos) { + curClasses.splice(pos, 1) + } + } + + finalValue = curClasses.join(' ') + if (className != finalValue) { + elem.className.baseVal = finalValue + } + } + return this +} +/*\ + + ** + * Checks if the element has a given class name in the list of class names applied to it. + - value (string) class name + ** + = (boolean) `true` if the element has given class + \*/ +elproto.hasClass = function (value) { + let elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [] + return !!~curClasses.indexOf(value) +} +/*\ + + ** + * Add or remove one or more classes from the element, depending on either + * the class’s presence or the value of the `flag` argument. + - value (string) class name or space separated list of class names + - flag (boolean) value to determine whether the class should be added or removed + ** + = (SnapElement) original element. + \*/ +elproto.toggleClass = function (value, flag) { + if (flag != null) { + if (flag) { + return this.addClass(value) + } else { + return this.removeClass(value) + } + } + let classes = (value || '').match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue + j = 0 + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz) + if (~pos) { + curClasses.splice(pos, 1) + } else { + curClasses.push(clazz) + } + } + + finalValue = curClasses.join(' ') + if (className != finalValue) { + elem.className.baseVal = finalValue + } + return this +} diff --git a/src/colors.js b/src/colors.js deleted file mode 100644 index 5e2bc97..0000000 --- a/src/colors.js +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import { Snap } from './svg.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - // Colours are from https://www.materialui.co - var red = - '#ffebee#ffcdd2#ef9a9a#e57373#ef5350#f44336#e53935#d32f2f#c62828#b71c1c#ff8a80#ff5252#ff1744#d50000', - pink = - '#FCE4EC#F8BBD0#F48FB1#F06292#EC407A#E91E63#D81B60#C2185B#AD1457#880E4F#FF80AB#FF4081#F50057#C51162', - purple = - '#F3E5F5#E1BEE7#CE93D8#BA68C8#AB47BC#9C27B0#8E24AA#7B1FA2#6A1B9A#4A148C#EA80FC#E040FB#D500F9#AA00FF', - deeppurple = - '#EDE7F6#D1C4E9#B39DDB#9575CD#7E57C2#673AB7#5E35B1#512DA8#4527A0#311B92#B388FF#7C4DFF#651FFF#6200EA', - indigo = - '#E8EAF6#C5CAE9#9FA8DA#7986CB#5C6BC0#3F51B5#3949AB#303F9F#283593#1A237E#8C9EFF#536DFE#3D5AFE#304FFE', - blue = - '#E3F2FD#BBDEFB#90CAF9#64B5F6#64B5F6#2196F3#1E88E5#1976D2#1565C0#0D47A1#82B1FF#448AFF#2979FF#2962FF', - lightblue = - '#E1F5FE#B3E5FC#81D4FA#4FC3F7#29B6F6#03A9F4#039BE5#0288D1#0277BD#01579B#80D8FF#40C4FF#00B0FF#0091EA', - cyan = - '#E0F7FA#B2EBF2#80DEEA#4DD0E1#26C6DA#00BCD4#00ACC1#0097A7#00838F#006064#84FFFF#18FFFF#00E5FF#00B8D4', - teal = - '#E0F2F1#B2DFDB#80CBC4#4DB6AC#26A69A#009688#00897B#00796B#00695C#004D40#A7FFEB#64FFDA#1DE9B6#00BFA5', - green = - '#E8F5E9#C8E6C9#A5D6A7#81C784#66BB6A#4CAF50#43A047#388E3C#2E7D32#1B5E20#B9F6CA#69F0AE#00E676#00C853', - lightgreen = - '#F1F8E9#DCEDC8#C5E1A5#AED581#9CCC65#8BC34A#7CB342#689F38#558B2F#33691E#CCFF90#B2FF59#76FF03#64DD17', - lime = - '#F9FBE7#F0F4C3#E6EE9C#DCE775#D4E157#CDDC39#C0CA33#AFB42B#9E9D24#827717#F4FF81#EEFF41#C6FF00#AEEA00', - yellow = - '#FFFDE7#FFF9C4#FFF59D#FFF176#FFEE58#FFEB3B#FDD835#FBC02D#F9A825#F57F17#FFFF8D#FFFF00#FFEA00#FFD600', - amber = - '#FFF8E1#FFECB3#FFE082#FFD54F#FFCA28#FFC107#FFB300#FFA000#FF8F00#FF6F00#FFE57F#FFD740#FFC400#FFAB00', - orange = - '#FFF3E0#FFE0B2#FFCC80#FFB74D#FFA726#FF9800#FB8C00#F57C00#EF6C00#E65100#FFD180#FFAB40#FF9100#FF6D00', - deeporange = - '#FBE9E7#FFCCBC#FFAB91#FF8A65#FF7043#FF5722#F4511E#E64A19#D84315#BF360C#FF9E80#FF6E40#FF3D00#DD2C00', - brown = - '#EFEBE9#D7CCC8#BCAAA4#A1887F#8D6E63#795548#6D4C41#5D4037#4E342E#3E2723', - grey = - '#FAFAFA#F5F5F5#EEEEEE#E0E0E0#BDBDBD#9E9E9E#757575#616161#424242#212121', - bluegrey = - '#ECEFF1#CFD8DC#B0BEC5#90A4AE#78909C#607D8B#546E7A#455A64#37474F#263238' - /*\ - * Snap.mui - [ property ] - ** - * Contain Material UI colours. - | Snap().rect(0, 0, 10, 10).attr({fill: Snap.mui.deeppurple, stroke: Snap.mui.amber[600]}); - # For colour reference: https://www.materialui.co. - \*/ - Snap.mui = {} - /*\ - * Snap.flat - [ property ] - ** - * Contain Flat UI colours. - | Snap().rect(0, 0, 10, 10).attr({fill: Snap.flat.carrot, stroke: Snap.flat.wetasphalt}); - # For colour reference: https://www.materialui.co. - \*/ - Snap.flat = {} - function saveColor(colors) { - colors = colors.split(/(?=#)/) - var color = new String(colors[5]) - color[50] = colors[0] - color[100] = colors[1] - color[200] = colors[2] - color[300] = colors[3] - color[400] = colors[4] - color[500] = colors[5] - color[600] = colors[6] - color[700] = colors[7] - color[800] = colors[8] - color[900] = colors[9] - if (colors[10]) { - color.A100 = colors[10] - color.A200 = colors[11] - color.A400 = colors[12] - color.A700 = colors[13] - } - return color - } - Snap.mui.red = saveColor(red) - Snap.mui.pink = saveColor(pink) - Snap.mui.purple = saveColor(purple) - Snap.mui.deeppurple = saveColor(deeppurple) - Snap.mui.indigo = saveColor(indigo) - Snap.mui.blue = saveColor(blue) - Snap.mui.lightblue = saveColor(lightblue) - Snap.mui.cyan = saveColor(cyan) - Snap.mui.teal = saveColor(teal) - Snap.mui.green = saveColor(green) - Snap.mui.lightgreen = saveColor(lightgreen) - Snap.mui.lime = saveColor(lime) - Snap.mui.yellow = saveColor(yellow) - Snap.mui.amber = saveColor(amber) - Snap.mui.orange = saveColor(orange) - Snap.mui.deeporange = saveColor(deeporange) - Snap.mui.brown = saveColor(brown) - Snap.mui.grey = saveColor(grey) - Snap.mui.bluegrey = saveColor(bluegrey) - Snap.flat.turquoise = '#1abc9c' - Snap.flat.greensea = '#16a085' - Snap.flat.sunflower = '#f1c40f' - Snap.flat.orange = '#f39c12' - Snap.flat.emerland = '#2ecc71' - Snap.flat.nephritis = '#27ae60' - Snap.flat.carrot = '#e67e22' - Snap.flat.pumpkin = '#d35400' - Snap.flat.peterriver = '#3498db' - Snap.flat.belizehole = '#2980b9' - Snap.flat.alizarin = '#e74c3c' - Snap.flat.pomegranate = '#c0392b' - Snap.flat.amethyst = '#9b59b6' - Snap.flat.wisteria = '#8e44ad' - Snap.flat.clouds = '#ecf0f1' - Snap.flat.silver = '#bdc3c7' - Snap.flat.wetasphalt = '#34495e' - Snap.flat.midnightblue = '#2c3e50' - Snap.flat.concrete = '#95a5a6' - Snap.flat.asbestos = '#7f8c8d' - /*\ - * Snap.importMUIColors - [ method ] - ** - * Imports Material UI colours into global object. - | Snap.importMUIColors(); - | Snap().rect(0, 0, 10, 10).attr({fill: deeppurple, stroke: amber[600]}); - # For colour reference: https://www.materialui.co. - \*/ - Snap.importMUIColors = function () { - for (var color in Snap.mui) { - if (Snap.mui.hasOwnProperty(color)) { - window[color] = Snap.mui[color] - } - } - } -}) diff --git a/src/element.js b/src/element.js index ef76cad..3ad7d02 100644 --- a/src/element.js +++ b/src/element.js @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { + Snap, + SnapElement, + Paper, + Fragment, + getSomeDefs, + make, + wrap, + unit2px +} from './svg.js' +import { $, is } from './utils.js' + +let elproto = SnapElement.prototype, + Str = String, + has = 'hasOwnProperty' +/*\ -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var elproto = Element.prototype, - is = Snap.is, - Str = String, - unit2px = Snap._unit2px, - $ = Snap._.$, - make = Snap._.make, - getSomeDefs = Snap._.getSomeDefs, - has = 'hasOwnProperty', - wrap = Snap._.wrap - /*\ - * Element.getBBox - [ method ] ** * Returns the bounding box descriptor for the given element ** @@ -49,97 +51,98 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { o y: (number) y of the top edge o } \*/ - elproto.getBBox = function (isWithoutTransform) { - if (this.type == 'tspan') { - return Snap._.box(this.node.getClientRects().item(0)) +elproto.getBBox = function (isWithoutTransform) { + if (this.type == 'tspan') { + return Snap._.box(this.node.getClientRects().item(0)) + } + if (!Snap.Matrix || !Snap.path) { + return this.node.getBBox() + } + let el = this, + m = new Snap.Matrix() + if (el.removed) { + return Snap._.box() + } + while (el.type == 'use') { + if (!isWithoutTransform) { + m = m.add( + el + .transform() + .localMatrix.translate(el.attr('x') || 0, el.attr('y') || 0) + ) } - if (!Snap.Matrix || !Snap.path) { - return this.node.getBBox() - } - var el = this, - m = new Snap.Matrix() - if (el.removed) { - return Snap._.box() - } - while (el.type == 'use') { - if (!isWithoutTransform) { - m = m.add( - el - .transform() - .localMatrix.translate(el.attr('x') || 0, el.attr('y') || 0) - ) - } - if (el.original) { - el = el.original - } else { - var href = el.attr('xlink:href') - el = el.original = el.node.ownerDocument.getElementById( - href.substring(href.indexOf('#') + 1) - ) - } - } - var _ = el._, - pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt - try { - if (isWithoutTransform) { - _.bboxwt = pathfinder - ? Snap.path.getBBox((el.realPath = pathfinder(el))) - : Snap._.box(el.node.getBBox()) - return Snap._.box(_.bboxwt) - } else { - el.realPath = pathfinder(el) - el.matrix = el.transform().localMatrix - _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))) - return Snap._.box(_.bbox) - } - } catch (e) { - // Firefox doesn’t give you bbox of hidden element - return Snap._.box() + if (el.original) { + el = el.original + } else { + let href = el.attr('xlink:href') + el = el.original = el.node.ownerDocument.getElementById( + href.substring(href.indexOf('#') + 1) + ) } } - var propString = function () { - return this.string + let _ = el._, + pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt + try { + if (isWithoutTransform) { + _.bboxwt = pathfinder + ? Snap.path.getBBox((el.realPath = pathfinder(el))) + : Snap._.box(el.node.getBBox()) + return Snap._.box(_.bboxwt) + } else { + el.realPath = pathfinder(el) + el.matrix = el.transform().localMatrix + _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))) + return Snap._.box(_.bbox) + } + } catch (e) { + // Firefox doesn’t give you bbox of hidden element + return Snap._.box() } - function extractTransform(el, tstr) { - if (tstr == null) { - var doReturn = true - if (el.type == 'linearGradient' || el.type == 'radialGradient') { - tstr = el.node.getAttribute('gradientTransform') - } else if (el.type == 'pattern') { - tstr = el.node.getAttribute('patternTransform') - } else { - tstr = el.node.getAttribute('transform') - } - if (!tstr) { - return new Snap.Matrix() - } +} +let propString = function () { + return this.string +} +function extractTransform(el, tstr) { + let doReturn + if (tstr == null) { + doReturn = true + if (el.type == 'linearGradient' || el.type == 'radialGradient') { + tstr = el.node.getAttribute('gradientTransform') + } else if (el.type == 'pattern') { + tstr = el.node.getAttribute('patternTransform') + } else { + tstr = el.node.getAttribute('transform') + } + if (!tstr) { + return new Snap.Matrix() + } + tstr = Snap._.svgTransform2string(tstr) + } else { + if (!Snap._.rgTransform.test(tstr)) { tstr = Snap._.svgTransform2string(tstr) } else { - if (!Snap._.rgTransform.test(tstr)) { - tstr = Snap._.svgTransform2string(tstr) - } else { - tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || '') - } - if (is(tstr, 'array')) { - tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr) - } - el._.transform = tstr + tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || '') } - var m = Snap._.transform2matrix(tstr, el.getBBox(1)) - if (doReturn) { - return m - } else { - el.matrix = m + if (is(tstr, 'array')) { + tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr) } + el._.transform = tstr } - /*\ - * Element.transform + let m = Snap._.transform2matrix(tstr, el.getBBox(1)) + if (doReturn) { + return m + } else { + el.matrix = m + } +} +/*\ + * SnapElement.transform [ method ] ** * Gets or sets transformation of the element ** - tstr (string) transform string in Snap or SVG format - = (Element) the current element + = (SnapElement) the current element * or = (object) transformation descriptor: o { @@ -152,433 +155,433 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { o toString (function) returns `string` property o } \*/ - elproto.transform = function (tstr) { - var _ = this._ - if (tstr == null) { - var papa = this, - global = new Snap.Matrix(this.node.getCTM()), - local = extractTransform(this), - ms = [local], - m = new Snap.Matrix(), - i, - localString = local.toTransformString(), - string = Str(local) == Str(this.matrix) ? Str(_.transform) : localString - while (papa.type != 'svg' && (papa = papa.parent())) { - ms.push(extractTransform(papa)) - } - i = ms.length - while (i--) { - m.add(ms[i]) - } - return { - string: string, - globalMatrix: global, - totalMatrix: m, - localMatrix: local, - diffMatrix: global.clone().add(local.invert()), - global: global.toTransformString(), - total: m.toTransformString(), - local: localString, - toString: propString - } +elproto.transform = function (tstr) { + let _ = this._ + if (tstr == null) { + let papa = this, + global = new Snap.Matrix(this.node.getCTM()), + local = extractTransform(this), + ms = [local], + m = new Snap.Matrix(), + i, + localString = local.toTransformString(), + string = Str(local) == Str(this.matrix) ? Str(_.transform) : localString + while (papa.type != 'svg' && (papa = papa.parent())) { + ms.push(extractTransform(papa)) } - if (tstr instanceof Snap.Matrix) { - this.matrix = tstr - this._.transform = tstr.toTransformString() - } else { - extractTransform(this, tstr) + i = ms.length + while (i--) { + m.add(ms[i]) } - - if (this.node) { - if (this.type == 'linearGradient' || this.type == 'radialGradient') { - $(this.node, { gradientTransform: this.matrix }) - } else if (this.type == 'pattern') { - $(this.node, { patternTransform: this.matrix }) - } else { - $(this.node, { transform: this.matrix }) - } + return { + string: string, + globalMatrix: global, + totalMatrix: m, + localMatrix: local, + diffMatrix: global.clone().add(local.invert()), + global: global.toTransformString(), + total: m.toTransformString(), + local: localString, + toString: propString } - - return this } - /*\ - * Element.parent + if (tstr instanceof Snap.Matrix) { + this.matrix = tstr + this._.transform = tstr.toTransformString() + } else { + extractTransform(this, tstr) + } + + if (this.node) { + if (this.type == 'linearGradient' || this.type == 'radialGradient') { + $(this.node, { gradientTransform: this.matrix }) + } else if (this.type == 'pattern') { + $(this.node, { patternTransform: this.matrix }) + } else { + $(this.node, { transform: this.matrix }) + } + } + + return this +} +/*\ + * SnapElement.parent [ method ] ** * Returns the element's parent ** - = (Element) the parent element + = (SnapElement) the parent element \*/ - elproto.parent = function () { - return wrap(this.node.parentNode) - } - /*\ - * Element.append +elproto.parent = function () { + return wrap(this.node.parentNode) +} +/*\ + * SnapElement.append [ method ] ** * Appends the given element to current one ** - - el (Element|Set) element to append - = (Element) the parent element + - el (SnapElement|Set) element to append + = (SnapElement) the parent element \*/ - /*\ - * Element.add +/*\ + * SnapElement.add [ method ] ** - * See @Element.append + * See @SnapElement.append \*/ - elproto.append = elproto.add = function (el) { - if (el) { - if (el.type == 'set') { - var it = this - el.forEach(function (el) { - it.add(el) - }) - return this - } - el = wrap(el) - this.node.appendChild(el.node) - el.paper = this.paper +elproto.append = elproto.add = function (el) { + if (el) { + if (el.type == 'set') { + let it = this + el.forEach(function (el) { + it.add(el) + }) + return this } - return this + el = wrap(el) + this.node.appendChild(el.node) + el.paper = this.paper } - /*\ - * Element.appendTo + return this +} +/*\ + * SnapElement.appendTo [ method ] ** * Appends the current element to the given one ** - - el (Element) parent element to append to - = (Element) the child element + - el (SnapElement) parent element to append to + = (SnapElement) the child element \*/ - elproto.appendTo = function (el) { - if (el) { - el = wrap(el) - el.append(this) - } - return this +elproto.appendTo = function (el) { + if (el) { + el = wrap(el) + el.append(this) } - /*\ - * Element.prepend + return this +} +/*\ + * SnapElement.prepend [ method ] ** * Prepends the given element to the current one ** - - el (Element) element to prepend - = (Element) the parent element + - el (SnapElement) element to prepend + = (SnapElement) the parent element \*/ - elproto.prepend = function (el) { - if (el) { - if (el.type == 'set') { - var it = this, - first - el.forEach(function (el) { - if (first) { - first.after(el) - } else { - it.prepend(el) - } - first = el - }) - return this - } - el = wrap(el) - var parent = el.parent() - this.node.insertBefore(el.node, this.node.firstChild) - this.add && this.add() - el.paper = this.paper - this.parent() && this.parent().add() - parent && parent.add() +elproto.prepend = function (el) { + if (el) { + if (el.type == 'set') { + let it = this, + first + el.forEach(function (el) { + if (first) { + first.after(el) + } else { + it.prepend(el) + } + first = el + }) + return this } - return this + el = wrap(el) + let parent = el.parent() + this.node.insertBefore(el.node, this.node.firstChild) + this.add && this.add() + el.paper = this.paper + this.parent() && this.parent().add() + parent && parent.add() } - /*\ - * Element.prependTo + return this +} +/*\ + * SnapElement.prependTo [ method ] ** * Prepends the current element to the given one ** - - el (Element) parent element to prepend to - = (Element) the child element + - el (SnapElement) parent element to prepend to + = (SnapElement) the child element \*/ - elproto.prependTo = function (el) { - el = wrap(el) - el.prepend(this) - return this - } - /*\ - * Element.before +elproto.prependTo = function (el) { + el = wrap(el) + el.prepend(this) + return this +} +/*\ + * SnapElement.before [ method ] ** * Inserts given element before the current one ** - - el (Element) element to insert - = (Element) the parent element + - el (SnapElement) element to insert + = (SnapElement) the parent element \*/ - elproto.before = function (el) { - if (el.type == 'set') { - var it = this - el.forEach(function (el) { - var parent = el.parent() - it.node.parentNode.insertBefore(el.node, it.node) - parent && parent.add() - }) - this.parent().add() - return this - } - el = wrap(el) - var parent = el.parent() - this.node.parentNode.insertBefore(el.node, this.node) - this.parent() && this.parent().add() - parent && parent.add() - el.paper = this.paper +elproto.before = function (el) { + if (el.type == 'set') { + let it = this + el.forEach(function (el) { + let parent = el.parent() + it.node.parentNode.insertBefore(el.node, it.node) + parent && parent.add() + }) + this.parent().add() return this } - /*\ - * Element.after + el = wrap(el) + let parent = el.parent() + this.node.parentNode.insertBefore(el.node, this.node) + this.parent() && this.parent().add() + parent && parent.add() + el.paper = this.paper + return this +} +/*\ + * SnapElement.after [ method ] ** * Inserts given element after the current one ** - - el (Element) element to insert - = (Element) the parent element + - el (SnapElement) element to insert + = (SnapElement) the parent element \*/ - elproto.after = function (el) { - el = wrap(el) - var parent = el.parent() - if (this.node.nextSibling) { - this.node.parentNode.insertBefore(el.node, this.node.nextSibling) - } else { - this.node.parentNode.appendChild(el.node) - } - this.parent() && this.parent().add() - parent && parent.add() - el.paper = this.paper - return this +elproto.after = function (el) { + el = wrap(el) + let parent = el.parent() + if (this.node.nextSibling) { + this.node.parentNode.insertBefore(el.node, this.node.nextSibling) + } else { + this.node.parentNode.appendChild(el.node) } - /*\ - * Element.insertBefore + this.parent() && this.parent().add() + parent && parent.add() + el.paper = this.paper + return this +} +/*\ + * SnapElement.insertBefore [ method ] ** * Inserts the element after the given one ** - - el (Element) element next to whom insert to - = (Element) the parent element + - el (SnapElement) element next to whom insert to + = (SnapElement) the parent element \*/ - elproto.insertBefore = function (el) { - el = wrap(el) - var parent = this.parent() - el.node.parentNode.insertBefore(this.node, el.node) - this.paper = el.paper - parent && parent.add() - el.parent() && el.parent().add() - return this - } - /*\ - * Element.insertAfter +elproto.insertBefore = function (el) { + el = wrap(el) + let parent = this.parent() + el.node.parentNode.insertBefore(this.node, el.node) + this.paper = el.paper + parent && parent.add() + el.parent() && el.parent().add() + return this +} +/*\ + * SnapElement.insertAfter [ method ] ** * Inserts the element after the given one ** - - el (Element) element next to whom insert to - = (Element) the parent element + - el (SnapElement) element next to whom insert to + = (SnapElement) the parent element \*/ - elproto.insertAfter = function (el) { - el = wrap(el) - var parent = this.parent() - el.node.parentNode.insertBefore(this.node, el.node.nextSibling) - this.paper = el.paper - parent && parent.add() - el.parent() && el.parent().add() - return this - } - /*\ - * Element.remove +elproto.insertAfter = function (el) { + el = wrap(el) + let parent = this.parent() + el.node.parentNode.insertBefore(this.node, el.node.nextSibling) + this.paper = el.paper + parent && parent.add() + el.parent() && el.parent().add() + return this +} +/*\ + * SnapElement.remove [ method ] ** * Removes element from the DOM - = (Element) the detached element + = (SnapElement) the detached element \*/ - elproto.remove = function () { - var parent = this.parent() - this.node.parentNode && this.node.parentNode.removeChild(this.node) - delete this.paper - this.removed = true - parent && parent.add() - return this - } - /*\ - * Element.select +elproto.remove = function () { + let parent = this.parent() + this.node.parentNode && this.node.parentNode.removeChild(this.node) + delete this.paper + this.removed = true + parent && parent.add() + return this +} +/*\ + * SnapElement.select [ method ] ** - * Gathers the nested @Element matching the given set of CSS selectors + * Gathers the nested @SnapElement matching the given set of CSS selectors ** - query (string) CSS selector - = (Element) result of query selection + = (SnapElement) result of query selection \*/ - elproto.select = function (query) { - return wrap(this.node.querySelector(query)) - } - /*\ - * Element.selectAll +elproto.select = function (query) { + return wrap(this.node.querySelector(query)) +} +/*\ + * SnapElement.selectAll [ method ] ** - * Gathers nested @Element objects matching the given set of CSS selectors + * Gathers nested @SnapElement objects matching the given set of CSS selectors ** - query (string) CSS selector = (Set|array) result of query selection \*/ - elproto.selectAll = function (query) { - var nodelist = this.node.querySelectorAll(query), - set = (Snap.set || Array)() - for (var i = 0; i < nodelist.length; i++) { - set.push(wrap(nodelist[i])) - } - return set +elproto.selectAll = function (query) { + let nodelist = this.node.querySelectorAll(query), + set = (Snap.set || Array)() + for (let i = 0; i < nodelist.length; i++) { + set.push(wrap(nodelist[i])) } - /*\ - * Element.asPX + return set +} +/*\ + * SnapElement.asPX [ method ] ** * Returns given attribute of the element as a `px` value (not %, em, etc.) ** - attr (string) attribute name - value (string) #optional attribute value - = (Element) result of query selection + = (SnapElement) result of query selection \*/ - elproto.asPX = function (attr, value) { - if (value == null) { - value = this.attr(attr) - } - return +unit2px(this, attr, value) +elproto.asPX = function (attr, value) { + if (value == null) { + value = this.attr(attr) } - // SIERRA Element.use(): I suggest adding a note about how to access the original element the returned instantiates. It's a part of SVG with which ordinary web developers may be least familiar. - /*\ - * Element.use + return +unit2px(this, attr, value) +} +// SIERRA SnapElement.use(): I suggest adding a note about how to access the original element the returned instantiates. It's a part of SVG with which ordinary web developers may be least familiar. +/*\ + * SnapElement.use [ method ] ** * Creates a `` element linked to the current element ** - = (Element) the `` element + = (SnapElement) the `` element \*/ - elproto.use = function () { - var use, - id = this.node.id - if (!id) { - id = this.id - $(this.node, { - id: id +elproto.use = function () { + let use, + id = this.node.id + if (!id) { + id = this.id + $(this.node, { + id: id + }) + } + if ( + this.type == 'linearGradient' || + this.type == 'radialGradient' || + this.type == 'pattern' + ) { + use = make(this.type, this.node.parentNode) + } else { + use = make('use', this.node.parentNode) + } + $(use.node, { + 'xlink:href': '#' + id + }) + use.original = this + return use +} +function fixids(el) { + let els = el.selectAll('*'), + it, + url = /^\s*url\(("|'|)(.*)\1\)\s*$/, + ids = [], + uses = {} + function urltest(it, name) { + let val = $(it.node, name) + val = val && val.match(url) + val = val && val[2] + if (val && val.charAt() == '#') { + val = val.substring(1) + } else { + return + } + if (val) { + uses[val] = (uses[val] || []).concat(function (id) { + let attr = {} + attr[name] = Snap.url(id) + $(it.node, attr) }) } - if ( - this.type == 'linearGradient' || - this.type == 'radialGradient' || - this.type == 'pattern' - ) { - use = make(this.type, this.node.parentNode) + } + function linktest(it) { + let val = $(it.node, 'xlink:href') + if (val && val.charAt() == '#') { + val = val.substring(1) } else { - use = make('use', this.node.parentNode) + return + } + if (val) { + uses[val] = (uses[val] || []).concat(function (id) { + it.attr('xlink:href', '#' + id) + }) } - $(use.node, { - 'xlink:href': '#' + id - }) - use.original = this - return use } - function fixids(el) { - var els = el.selectAll('*'), - it, - url = /^\s*url\(("|'|)(.*)\1\)\s*$/, - ids = [], - uses = {} - function urltest(it, name) { - var val = $(it.node, name) - val = val && val.match(url) - val = val && val[2] - if (val && val.charAt() == '#') { - val = val.substring(1) - } else { - return - } - if (val) { - uses[val] = (uses[val] || []).concat(function (id) { - var attr = {} - attr[name] = Snap.url(id) - $(it.node, attr) - }) - } + for (let i = 0, ii = els.length; i < ii; i++) { + it = els[i] + urltest(it, 'fill') + urltest(it, 'stroke') + urltest(it, 'filter') + urltest(it, 'mask') + urltest(it, 'clip-path') + linktest(it) + let oldid = $(it.node, 'id') + if (oldid) { + $(it.node, { id: it.id }) + ids.push({ + old: oldid, + id: it.id + }) } - function linktest(it) { - var val = $(it.node, 'xlink:href') - if (val && val.charAt() == '#') { - val = val.substring(1) - } else { - return - } - if (val) { - uses[val] = (uses[val] || []).concat(function (id) { - it.attr('xlink:href', '#' + id) - }) - } - } - for (var i = 0, ii = els.length; i < ii; i++) { - it = els[i] - urltest(it, 'fill') - urltest(it, 'stroke') - urltest(it, 'filter') - urltest(it, 'mask') - urltest(it, 'clip-path') - linktest(it) - var oldid = $(it.node, 'id') - if (oldid) { - $(it.node, { id: it.id }) - ids.push({ - old: oldid, - id: it.id - }) - } - } - for (i = 0, ii = ids.length; i < ii; i++) { - var fs = uses[ids[i].old] - if (fs) { - for (var j = 0, jj = fs.length; j < jj; j++) { - fs[j](ids[i].id) - } + } + for (i = 0, ii = ids.length; i < ii; i++) { + let fs = uses[ids[i].old] + if (fs) { + for (let j = 0, jj = fs.length; j < jj; j++) { + fs[j](ids[i].id) } } } - /*\ - * Element.clone +} +/*\ + * SnapElement.clone [ method ] ** * Creates a clone of the element and inserts it after the element ** - = (Element) the clone + = (SnapElement) the clone \*/ - elproto.clone = function () { - var clone = wrap(this.node.cloneNode(true)) - if ($(clone.node, 'id')) { - $(clone.node, { id: clone.id }) - } - fixids(clone) - clone.insertAfter(this) - return clone +elproto.clone = function () { + let clone = wrap(this.node.cloneNode(true)) + if ($(clone.node, 'id')) { + $(clone.node, { id: clone.id }) } - /*\ - * Element.toDefs + fixids(clone) + clone.insertAfter(this) + return clone +} +/*\ + * SnapElement.toDefs [ method ] ** * Moves element to the shared `` area ** - = (Element) the element + = (SnapElement) the element \*/ - elproto.toDefs = function () { - var defs = getSomeDefs(this) - defs.appendChild(this.node) - return this - } - /*\ - * Element.toPattern +elproto.toDefs = function () { + let defs = getSomeDefs(this) + defs.appendChild(this.node) + return this +} +/*\ + * SnapElement.toPattern [ method ] ** * Creates a `` element from the current element @@ -588,9 +591,9 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - y (string|number) - width (string|number) - height (string|number) - = (Element) the `` element + = (SnapElement) the `` element * You can use pattern later on as an argument for `fill` attribute: - | var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ + | let p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ | fill: "none", | stroke: "#bada55", | strokeWidth: 5 @@ -600,33 +603,33 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { | fill: p | }); \*/ - elproto.pattern = elproto.toPattern = function (x, y, width, height) { - var p = make('pattern', getSomeDefs(this)) - if (x == null) { - x = this.getBBox() - } - if (is(x, 'object') && 'x' in x) { - y = x.y - width = x.width - height = x.height - x = x.x - } - $(p.node, { - x: x, - y: y, - width: width, - height: height, - patternUnits: 'userSpaceOnUse', - id: p.id, - viewBox: [x, y, width, height].join(' ') - }) - p.node.appendChild(this.node) - return p +elproto.pattern = elproto.toPattern = function (x, y, width, height) { + let p = make('pattern', getSomeDefs(this)) + if (x == null) { + x = this.getBBox() } - // SIERRA Element.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path. - // SIERRA Element.marker(): I suggest the method should accept default reference point values. Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where? Couldn't they also be assigned default values? - /*\ - * Element.marker + if (is(x, 'object') && 'x' in x) { + y = x.y + width = x.width + height = x.height + x = x.x + } + $(p.node, { + x: x, + y: y, + width: width, + height: height, + patternUnits: 'userSpaceOnUse', + id: p.id, + viewBox: [x, y, width, height].join(' ') + }) + p.node.appendChild(this.node) + return p +} +// SIERRA SnapElement.marker(): clarify what a reference point is. E.g., helps you offset the object from its edge such as when centering it over a path. +// SIERRA SnapElement.marker(): I suggest the method should accept default reference point values. Perhaps centered with (refX = width/2) and (refY = height/2)? Also, couldn't it assume the element's current _width_ and _height_? And please specify what _x_ and _y_ mean: offsets? If so, from where? Couldn't they also be assigned default values? +/*\ + * SnapElement.marker [ method ] ** * Creates a `` element from the current element @@ -638,51 +641,51 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - height (number) - refX (number) - refY (number) - = (Element) the `` element + = (SnapElement) the `` element * You can specify the marker later as an argument for `marker-start`, `marker-end`, `marker-mid`, and `marker` attributes. The `marker` attribute places the marker at every point along the path, and `marker-mid` places them at every point except the start and end. \*/ - // TODO add usage for markers - elproto.marker = function (x, y, width, height, refX, refY) { - var p = make('marker', getSomeDefs(this)) - if (x == null) { - x = this.getBBox() - } - if (is(x, 'object') && 'x' in x) { - y = x.y - width = x.width - height = x.height - refX = x.refX || x.cx - refY = x.refY || x.cy - x = x.x - } - $(p.node, { - viewBox: [x, y, width, height].join(' '), - markerWidth: width, - markerHeight: height, - orient: 'auto', - refX: refX || 0, - refY: refY || 0, - id: p.id - }) - p.node.appendChild(this.node) - return p +// TODO add usage for markers +elproto.marker = function (x, y, width, height, refX, refY) { + let p = make('marker', getSomeDefs(this)) + if (x == null) { + x = this.getBBox() } - var eldata = {} - /*\ - * Element.data + if (is(x, 'object') && 'x' in x) { + y = x.y + width = x.width + height = x.height + refX = x.refX || x.cx + refY = x.refY || x.cy + x = x.x + } + $(p.node, { + viewBox: [x, y, width, height].join(' '), + markerWidth: width, + markerHeight: height, + orient: 'auto', + refX: refX || 0, + refY: refY || 0, + id: p.id + }) + p.node.appendChild(this.node) + return p +} +let eldata = {} +/*\ + * SnapElement.data [ method ] ** * Adds or retrieves given value associated with given key. (Don’t confuse * with `data-` attributes) * - * See also @Element.removeData + * See also @SnapElement.removeData - key (string) key to store data - value (any) #optional value to store - = (object) @Element + = (object) @SnapElement * or, if value is not specified: = (any) value > Usage - | for (var i = 0, i < 5, i++) { + | for (let i = 0, i < 5, i++) { | paper.circle(10 + 15 * i, 10, 10) | .attr({fill: "#000"}) | .data("i", i) @@ -691,125 +694,119 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { | }); | } \*/ - elproto.data = function (key, value) { - var data = (eldata[this.id] = eldata[this.id] || {}) - if (arguments.length == 0) { - eve('snap.data.get.' + this.id, this, data, null) - return data - } - if (arguments.length == 1) { - if (Snap.is(key, 'object')) { - for (var i in key) - if (key[has](i)) { - this.data(i, key[i]) - } - return this - } - eve('snap.data.get.' + this.id, this, data[key], key) - return data[key] - } - data[key] = value - eve('snap.data.set.' + this.id, this, value, key) - return this +elproto.data = function (key, value) { + let data = (eldata[this.id] = eldata[this.id] || {}) + if (arguments.length == 0) { + eve('snap.data.get.' + this.id, this, data, null) + return data } - /*\ - * Element.removeData + if (arguments.length == 1) { + if (is(key, 'object')) { + for (let i in key) + if (key[has](i)) { + this.data(i, key[i]) + } + return this + } + eve('snap.data.get.' + this.id, this, data[key], key) + return data[key] + } + data[key] = value + eve('snap.data.set.' + this.id, this, value, key) + return this +} +/*\ + * SnapElement.removeData [ method ] ** * Removes value associated with an element by given key. * If key is not provided, removes all the data of the element. - key (string) #optional key - = (object) @Element + = (object) @SnapElement \*/ - elproto.removeData = function (key) { - if (key == null) { - eldata[this.id] = {} - } else { - eldata[this.id] && delete eldata[this.id][key] - } - return this +elproto.removeData = function (key) { + if (key == null) { + eldata[this.id] = {} + } else { + eldata[this.id] && delete eldata[this.id][key] } - /*\ - * Element.outerSVG + return this +} +/*\ + * SnapElement.outerSVG [ method ] ** * Returns SVG code for the element, equivalent to HTML's `outerHTML`. * - * See also @Element.innerSVG + * See also @SnapElement.innerSVG = (string) SVG code for the element \*/ - /*\ - * Element.toString +/*\ + * SnapElement.toString [ method ] ** - * See @Element.outerSVG + * See @SnapElement.outerSVG \*/ - elproto.outerSVG = elproto.toString = toString(1) - /*\ - * Element.innerSVG +elproto.outerSVG = elproto.toString = toString(1) +/*\ + * SnapElement.innerSVG [ method ] ** * Returns SVG code for the element's contents, equivalent to HTML's `innerHTML` = (string) SVG code for the element \*/ - elproto.innerSVG = toString() - function toString(type) { - return function () { - var res = type ? '<' + this.type : '', - attr = this.node.attributes, - chld = this.node.childNodes - if (type) { - for (var i = 0, ii = attr.length; i < ii; i++) { - res += - ' ' + attr[i].name + '="' + attr[i].value.replace(/"/g, '\\"') + '"' +elproto.innerSVG = toString() +function toString(type) { + return function () { + let res = type ? '<' + this.type : '', + attr = this.node.attributes, + chld = this.node.childNodes + if (type) { + for (let i = 0, ii = attr.length; i < ii; i++) { + res += + ' ' + attr[i].name + '="' + attr[i].value.replace(/"/g, '\\"') + '"' + } + } + if (chld.length) { + type && (res += '>') + for (i = 0, ii = chld.length; i < ii; i++) { + if (chld[i].nodeType == 3) { + res += chld[i].nodeValue + } else if (chld[i].nodeType == 1) { + res += wrap(chld[i]).toString() } } - if (chld.length) { - type && (res += '>') - for (i = 0, ii = chld.length; i < ii; i++) { - if (chld[i].nodeType == 3) { - res += chld[i].nodeValue - } else if (chld[i].nodeType == 1) { - res += wrap(chld[i]).toString() - } - } - type && (res += '') - } else { - type && (res += '/>') - } - return res + type && (res += '') + } else { + type && (res += '/>') } + return res } - elproto.toDataURL = function () { - if (window && window.btoa) { - var bb = this.getBBox(), - svg = Snap.format( - '{contents}', - { - x: +bb.x.toFixed(3), - y: +bb.y.toFixed(3), - width: +bb.width.toFixed(3), - height: +bb.height.toFixed(3), - contents: this.outerSVG() - } - ) - return ( - 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))) - ) - } +} +elproto.toDataURL = function () { + let bb = this.getBBox() + let { x, y, width, height } = { + x: +bb.x.toFixed(3), + y: +bb.y.toFixed(3), + width: +bb.width.toFixed(3), + height: +bb.height.toFixed(3) } - /*\ + let contents = this.outerSVG() + let svg = `${contents}` + + return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))) +} +/*\ * Fragment.select [ method ] ** - * See @Element.select + * See @SnapElement.select \*/ - Fragment.prototype.select = elproto.select - /*\ +Fragment.prototype.select = elproto.select +/*\ * Fragment.selectAll [ method ] ** - * See @Element.selectAll + * See @SnapElement.selectAll \*/ - Fragment.prototype.selectAll = elproto.selectAll -}) +Fragment.prototype.selectAll = elproto.selectAll diff --git a/src/equal.js b/src/equal.js index 4ffaca7..0a49fd9 100644 --- a/src/equal.js +++ b/src/equal.js @@ -12,216 +12,213 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' +import { is } from './utils.js' +import { SEPARATOR } from './lib/constants.js' +import { parseColor } from './lib/color.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - var names = {}, - reUnit = /[%a-z]+$/i, - Str = String - names.stroke = names.fill = 'colour' - function getEmpty(item) { - var l = item[0] - switch (l.toLowerCase()) { - case 't': - return [l, 0, 0] - case 'm': - return [l, 1, 0, 0, 1, 0, 0] - case 'r': - if (item.length == 4) { - return [l, 0, item[2], item[3]] - } else { - return [l, 0] - } - case 's': - if (item.length == 5) { - return [l, 1, 1, item[3], item[4]] - } else if (item.length == 3) { - return [l, 1, 1] - } else { - return [l, 1] - } - } - } - function equaliseTransform(t1, t2, getBBox) { - t1 = t1 || new Snap.Matrix() - t2 = t2 || new Snap.Matrix() - t1 = Snap.parseTransformString(t1.toTransformString()) || [] - t2 = Snap.parseTransformString(t2.toTransformString()) || [] - var maxlength = Math.max(t1.length, t2.length), - from = [], - to = [], - i = 0, - j, - jj, - tt1, - tt2 - for (; i < maxlength; i++) { - tt1 = t1[i] || getEmpty(t2[i]) - tt2 = t2[i] || getEmpty(tt1) - if ( - tt1[0] != tt2[0] || - (tt1[0].toLowerCase() == 'r' && - (tt1[2] != tt2[2] || tt1[3] != tt2[3])) || - (tt1[0].toLowerCase() == 's' && (tt1[3] != tt2[3] || tt1[4] != tt2[4])) - ) { - t1 = Snap._.transform2matrix(t1, getBBox()) - t2 = Snap._.transform2matrix(t2, getBBox()) - from = [['m', t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]] - to = [['m', t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]] - break - } - from[i] = [] - to[i] = [] - for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) { - j in tt1 && (from[i][j] = tt1[j]) - j in tt2 && (to[i][j] = tt2[j]) - } - } - return { - from: path2array(from), - to: path2array(to), - f: getPath(from) - } - } - function getNumber(val) { - return val - } - function getUnit(unit) { - return function (val) { - return +val.toFixed(3) + unit - } - } - function getViewBox(val) { - return val.join(' ') - } - function getColour(clr) { - return Snap.rgb(clr[0], clr[1], clr[2], clr[3]) - } - function getPath(path) { - var k = 0, - i, - ii, - j, - jj, - out, - a, - b = [] - for (i = 0, ii = path.length; i < ii; i++) { - out = '[' - a = ['"' + path[i][0] + '"'] - for (j = 1, jj = path[i].length; j < jj; j++) { - a[j] = 'val[' + k++ + ']' - } - out += a + ']' - b[i] = out - } - return Function('val', 'return Snap.path.toString.call([' + b + '])') - } - function path2array(path) { - var out = [] - for (var i = 0, ii = path.length; i < ii; i++) { - for (var j = 1, jj = path[i].length; j < jj; j++) { - out.push(path[i][j]) - } - } - return out - } - function isNumeric(obj) { - return isFinite(obj) - } - function arrayEqual(arr1, arr2) { - if (!Snap.is(arr1, 'array') || !Snap.is(arr2, 'array')) { - return false - } - return arr1.toString() == arr2.toString() - } - Element.prototype.equal = function (name, b) { - return eve('snap.util.equal', this, name, b).firstDefined() - } - eve.on('snap.util.equal', function (name, b) { - var A, - B, - a = Str(this.attr(name) || ''), - el = this - if (names[name] == 'colour') { - A = Snap.color(a) - B = Snap.color(b) - return { - from: [A.r, A.g, A.b, A.opacity], - to: [B.r, B.g, B.b, B.opacity], - f: getColour - } - } - if (name == 'viewBox') { - A = this.attr(name).vb.split(' ').map(Number) - B = b.split(' ').map(Number) - return { - from: A, - to: B, - f: getViewBox - } - } - if ( - name == 'transform' || - name == 'gradientTransform' || - name == 'patternTransform' - ) { - if (typeof b == 'string') { - b = Str(b).replace(/\.{3}|\u2026/g, a) - } - a = this.matrix - if (!Snap._.rgTransform.test(b)) { - b = Snap._.transform2matrix( - Snap._.svgTransform2string(b), - this.getBBox() - ) +let names = {}, + reUnit = /[%a-z]+$/i, + Str = String +names.stroke = names.fill = 'colour' +function getEmpty(item) { + let l = item[0] + switch (l.toLowerCase()) { + case 't': + return [l, 0, 0] + case 'm': + return [l, 1, 0, 0, 1, 0, 0] + case 'r': + if (item.length == 4) { + return [l, 0, item[2], item[3]] } else { - b = Snap._.transform2matrix(b, this.getBBox()) + return [l, 0] } - return equaliseTransform(a, b, function () { - return el.getBBox(1) - }) + case 's': + if (item.length == 5) { + return [l, 1, 1, item[3], item[4]] + } else if (item.length == 3) { + return [l, 1, 1] + } else { + return [l, 1] + } + } +} +function equaliseTransform(t1, t2, getBBox) { + t1 = t1 || new Snap.Matrix() + t2 = t2 || new Snap.Matrix() + t1 = Snap.parseTransformString(t1.toTransformString()) || [] + t2 = Snap.parseTransformString(t2.toTransformString()) || [] + let maxlength = Math.max(t1.length, t2.length), + from = [], + to = [], + i = 0, + j, + jj, + tt1, + tt2 + for (; i < maxlength; i++) { + tt1 = t1[i] || getEmpty(t2[i]) + tt2 = t2[i] || getEmpty(tt1) + if ( + tt1[0] != tt2[0] || + (tt1[0].toLowerCase() == 'r' && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) || + (tt1[0].toLowerCase() == 's' && (tt1[3] != tt2[3] || tt1[4] != tt2[4])) + ) { + t1 = Snap._.transform2matrix(t1, getBBox()) + t2 = Snap._.transform2matrix(t2, getBBox()) + from = [['m', t1.a, t1.b, t1.c, t1.d, t1.e, t1.f]] + to = [['m', t2.a, t2.b, t2.c, t2.d, t2.e, t2.f]] + break } - if (name == 'd' || name == 'path') { - A = Snap.path.toCubic(a, b) - return { - from: path2array(A[0]), - to: path2array(A[1]), - f: getPath(A[0]) - } + from[i] = [] + to[i] = [] + for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) { + j in tt1 && (from[i][j] = tt1[j]) + j in tt2 && (to[i][j] = tt2[j]) } - if (name == 'points') { - A = Str(a).split(Snap._.separator) - B = Str(b).split(Snap._.separator) - return { - from: A, - to: B, - f: function (val) { - return val - } - } + } + return { + from: path2array(from), + to: path2array(to), + f: getPath(from) + } +} +function getNumber(val) { + return val +} +function getUnit(unit) { + return function (val) { + return +val.toFixed(3) + unit + } +} +function getViewBox(val) { + return val.join(' ') +} +function getColour(clr) { + return Snap.rgb(clr[0], clr[1], clr[2], clr[3]) +} +function getPath(path) { + let k = 0, + i, + ii, + j, + jj, + out, + a, + b = [] + for (i = 0, ii = path.length; i < ii; i++) { + out = '[' + a = ['"' + path[i][0] + '"'] + for (j = 1, jj = path[i].length; j < jj; j++) { + a[j] = 'val[' + k++ + ']' } - if (isNumeric(a) && isNumeric(b)) { - return { - from: parseFloat(a), - to: parseFloat(b), - f: getNumber - } + out += a + ']' + b[i] = out + } + return Function('val', 'return Snap.path.toString.call([' + b + '])') +} +function path2array(path) { + let out = [] + for (let i = 0, ii = path.length; i < ii; i++) { + for (let j = 1, jj = path[i].length; j < jj; j++) { + out.push(path[i][j]) } - var aUnit = a.match(reUnit), - bUnit = Str(b).match(reUnit) - if (aUnit && arrayEqual(aUnit, bUnit)) { - return { - from: parseFloat(a), - to: parseFloat(b), - f: getUnit(aUnit) - } + } + return out +} +function isNumeric(obj) { + return isFinite(obj) +} +function arrayEqual(arr1, arr2) { + if (!is(arr1, 'array') || !is(arr2, 'array')) { + return false + } + return arr1.toString() == arr2.toString() +} +SnapElement.prototype.equal = function (name, b) { + return eve('snap.util.equal', this, name, b).firstDefined() +} +eve.on('snap.util.equal', function (name, b) { + let A, + B, + a = Str(this.attr(name) || ''), + el = this + if (names[name] == 'colour') { + A = parseColor(a) + B = parseColor(b) + return { + from: [A.r, A.g, A.b, A.opacity], + to: [B.r, B.g, B.b, B.opacity], + f: getColour + } + } + if (name == 'viewBox') { + A = this.attr(name).vb.split(' ').map(Number) + B = b.split(' ').map(Number) + return { + from: A, + to: B, + f: getViewBox + } + } + if ( + name == 'transform' || + name == 'gradientTransform' || + name == 'patternTransform' + ) { + if (typeof b == 'string') { + b = Str(b).replace(/\.{3}|\u2026/g, a) + } + a = this.matrix + if (!Snap._.rgTransform.test(b)) { + b = Snap._.transform2matrix(Snap._.svgTransform2string(b), this.getBBox()) } else { - return { - from: this.asPX(name), - to: this.asPX(name, b), - f: getNumber + b = Snap._.transform2matrix(b, this.getBBox()) + } + return equaliseTransform(a, b, function () { + return el.getBBox(1) + }) + } + if (name == 'd' || name == 'path') { + A = Snap.path.toCubic(a, b) + return { + from: path2array(A[0]), + to: path2array(A[1]), + f: getPath(A[0]) + } + } + if (name == 'points') { + A = Str(a).split(SEPARATOR) + B = Str(b).split(SEPARATOR) + return { + from: A, + to: B, + f: function (val) { + return val } } - }) + } + if (isNumeric(a) && isNumeric(b)) { + return { + from: parseFloat(a), + to: parseFloat(b), + f: getNumber + } + } + let aUnit = a.match(reUnit), + bUnit = Str(b).match(reUnit) + if (aUnit && arrayEqual(aUnit, bUnit)) { + return { + from: parseFloat(a), + to: parseFloat(b), + f: getUnit(aUnit) + } + } else { + return { + from: this.asPX(name), + to: this.asPX(name, b), + f: getNumber + } + } }) diff --git a/src/eve.js b/src/eve.js index 18e4ea8..fb7d7ed 100644 --- a/src/eve.js +++ b/src/eve.js @@ -17,7 +17,7 @@ // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ // └────────────────────────────────────────────────────────────┘ \\ -var version = '0.5.4', +let version = '0.5.4', has = 'hasOwnProperty', separator = /[\.\/]/, comaseparator = /\s*,\s*/, @@ -29,16 +29,16 @@ var version = '0.5.4', stop, events = { n: {} }, firstDefined = function () { - for (var i = 0, ii = this.length; i < ii; i++) { - if (typeof this[i] != 'undefined') { + for (let i = 0, ii = this.length; i < ii; i++) { + if (this[i] !== void 0) { return this[i] } } }, lastDefined = function () { - var i = this.length + let i = this.length while (--i) { - if (typeof this[i] != 'undefined') { + if (this[i] !== void 0) { return this[i] } } @@ -63,7 +63,7 @@ var version = '0.5.4', = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. \*/ eve = function (name, scope) { - var oldstop = stop, + let oldstop = stop, args = Array.prototype.slice.call(arguments, 2), listeners = eve.listeners(name), z = 0, @@ -76,7 +76,7 @@ var version = '0.5.4', out.lastDefined = lastDefined current_event = name stop = 0 - for (var i = 0, ii = listeners.length; i < ii; i++) + for (let i = 0; i < listeners.length; i++) if ('zIndex' in listeners[i]) { indexed.push(listeners[i].zIndex) if (listeners[i].zIndex < 0) { @@ -92,7 +92,7 @@ var version = '0.5.4', return out } } - for (i = 0; i < ii; i++) { + for (let i = 0; i < listeners.length; i++) { l = listeners[i] if ('zIndex' in l) { if (l.zIndex == indexed[z]) { @@ -135,7 +135,7 @@ eve._events = events = (array) array of event handlers \*/ eve.listeners = function (name) { - var names = isArray(name) ? name : name.split(separator), + let names = isArray(name) ? name : name.split(separator), e = events, item, items, @@ -213,17 +213,17 @@ eve.on = function (name, f) { if (typeof f != 'function') { return function () {} } - var names = isArray(name) + let names = isArray(name) ? isArray(name[0]) ? name : [name] : Str(name).split(comaseparator) - for (var i = 0, ii = names.length; i < ii; i++) { + for (let i = 0, ii = names.length; i < ii; i++) { ;(function (name) { - var names = isArray(name) ? name : Str(name).split(separator), + let names = isArray(name) ? name : Str(name).split(separator), e = events, exist - for (var i = 0, ii = names.length; i < ii; i++) { + for (let i = 0, ii = names.length; i < ii; i++) { e = e.n e = (e.hasOwnProperty(names[i]) && e[names[i]]) || @@ -245,8 +245,6 @@ eve.on = function (name, f) { } } /*\ - * eve.f - [ method ] ** * Returns function that will fire given event with optional arguments. * Arguments that will be passed to the result function will be also @@ -259,18 +257,12 @@ eve.on = function (name, f) { - varargs (…) and any other arguments = (function) possible event handler function \*/ -eve.f = function (event) { - var attrs = [].slice.call(arguments, 1) - return function () { - eve.apply( - null, - [event, null].concat(attrs).concat([].slice.call(arguments, 0)) - ) +eve.f = function (event, ...attrs) { + return function (...args) { + eve.apply(null, [event, null].concat(attrs).concat(args)) } } /*\ - * eve.stop - [ method ] ** * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. \*/ @@ -290,7 +282,7 @@ eve.stop = function () { = (boolean) `true`, if current event’s name contains `subname` \*/ eve.nt = function (subname) { - var cur = isArray(current_event) ? current_event.join('.') : current_event + let cur = isArray(current_event) ? current_event.join('.') : current_event if (subname) { return new RegExp('(?:\\.|\\/|^)' + subname + '(?:\\.|\\/|$)').test(cur) } @@ -329,19 +321,19 @@ eve.off = eve.unbind = function (name, f) { eve._events = events = { n: {} } return } - var names = isArray(name) + let names = isArray(name) ? isArray(name[0]) ? name : [name] : Str(name).split(comaseparator) if (names.length > 1) { - for (var i = 0, ii = names.length; i < ii; i++) { + for (let i = 0, ii = names.length; i < ii; i++) { eve.off(names[i], f) } return } names = isArray(name) ? name : Str(name).split(separator) - var e, + let e, key, splice, i, @@ -389,7 +381,7 @@ eve.off = eve.unbind = function (name, f) { } for (key in e.n) if (e.n[has](key) && e.n[key].f) { - var funcs = e.n[key].f + let funcs = e.n[key].f for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) { funcs.splice(j, 1) @@ -438,21 +430,11 @@ eve.off = eve.unbind = function (name, f) { = (function) same return function as @eve.on \*/ eve.once = function (name, f) { - var f2 = function () { + let f2 = function () { eve.off(name, f2) return f.apply(this, arguments) } return eve.on(name, f2) } -/*\ - * eve.version - [ property (string) ] - ** - * Current version of the library. - \*/ -eve.version = version -eve.toString = function () { - return 'You are running Eve ' + version -} export default eve diff --git a/src/filter.js b/src/filter.js index 3f24a8d..0691515 100644 --- a/src/filter.js +++ b/src/filter.js @@ -12,75 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' +import { $ } from './utils.js' +import { parseColor } from './lib/color.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - var elproto = Element.prototype, - pproto = Paper.prototype, - rgurl = /^\s*url\((.+)\)/, - Str = String, - $ = Snap._.$ - Snap.filter = {} - /*\ - * Paper.filter - [ method ] - ** - * Creates a `` element - ** - - filstr (string) SVG fragment of filter provided as a string - = (object) @Element - * Note: It is recommended to use filters embedded into the page inside an empty SVG element. - > Usage - | var f = paper.filter(''), - | c = paper.circle(10, 10, 10).attr({ - | filter: f - | }); - \*/ - pproto.filter = function (filstr) { - var paper = this - if (paper.type != 'svg') { - paper = paper.paper - } - var f = Snap.parse(Str(filstr)), - id = Snap._.id(), - width = paper.node.offsetWidth, - height = paper.node.offsetHeight, - filter = $('filter') - $(filter, { - id: id, - filterUnits: 'userSpaceOnUse' - }) - filter.appendChild(f.node) - paper.defs.appendChild(filter) - return new Element(filter) +let elproto = SnapElement.prototype, + rgurl = /^\s*url\((.+)\)/, + Str = String + +Snap.filter = {} + +eve.on('snap.util.getattr.filter', function () { + eve.stop() + let p = $(this.node, 'filter') + if (p) { + let match = Str(p).match(rgurl) + return match && Snap.select(match[1]) } - - eve.on('snap.util.getattr.filter', function () { +}) +eve.on('snap.util.attr.filter', function (value) { + if (value instanceof SnapElement && value.type == 'filter') { eve.stop() - var p = $(this.node, 'filter') - if (p) { - var match = Str(p).match(rgurl) - return match && Snap.select(match[1]) + let id = value.node.id + if (!id) { + $(value.node, { id: value.id }) + id = value.id } - }) - eve.on('snap.util.attr.filter', function (value) { - if (value instanceof Element && value.type == 'filter') { - eve.stop() - var id = value.node.id - if (!id) { - $(value.node, { id: value.id }) - id = value.id - } - $(this.node, { - filter: Snap.url(id) - }) - } - if (!value || value == 'none') { - eve.stop() - this.node.removeAttribute('filter') - } - }) - /*\ + $(this.node, { + filter: Snap.url(id) + }) + } + if (!value || value == 'none') { + eve.stop() + this.node.removeAttribute('filter') + } +}) +/*\ * Snap.filter.blur [ method ] ** @@ -90,24 +57,22 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - y (number) #optional amount of vertical blur, in pixels = (string) filter representation > Usage - | var f = paper.filter(Snap.filter.blur(5, 10)), + | let f = paper.filter(Snap.filter.blur(5, 10)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ - Snap.filter.blur = function (x, y) { - if (x == null) { - x = 2 - } - var def = y == null ? x : [x, y] - return Snap.format('', { - def: def - }) +Snap.filter.blur = function (x, y) { + if (x == null) { + x = 2 } - Snap.filter.blur.toString = function () { - return this() - } - /*\ + let def = y == null ? x : [x, y] + return `` +} +Snap.filter.blur.toString = function () { + return this() +} +/*\ * Snap.filter.shadow [ method ] ** @@ -129,52 +94,43 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - opacity (number) #optional `0..1` opacity of the shadow = (string) filter representation > Usage - | var f = paper.filter(Snap.filter.shadow(0, 2, .3)), + | let f = paper.filter(Snap.filter.shadow(0, 2, .3)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ - Snap.filter.shadow = function (dx, dy, blur, color, opacity) { - if (opacity == null) { - if (color == null) { - opacity = blur - blur = 4 - color = '#000' - } else { - opacity = color - color = blur - blur = 4 - } - } - if (blur == null) { +Snap.filter.shadow = function (dx, dy, blur, color, opacity) { + if (opacity == null) { + if (color == null) { + opacity = blur + blur = 4 + color = '#000' + } else { + opacity = color + color = blur blur = 4 } - if (opacity == null) { - opacity = 1 - } - if (dx == null) { - dx = 0 - dy = 2 - } - if (dy == null) { - dy = dx - } - color = Snap.color(color) - return Snap.format( - '', - { - color: color, - dx: dx, - dy: dy, - blur: blur, - opacity: opacity - } - ) } - Snap.filter.shadow.toString = function () { - return this() + if (blur == null) { + blur = 4 } - /*\ + if (opacity == null) { + opacity = 1 + } + if (dx == null) { + dx = 0 + dy = 2 + } + if (dy == null) { + dy = dx + } + color = parseColor(color) + return `` +} +Snap.filter.shadow.toString = function () { + return this() +} +/*\ * Snap.filter.grayscale [ method ] ** @@ -183,28 +139,26 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.grayscale = function (amount) { - if (amount == null) { - amount = 1 - } - return Snap.format( - '', - { - a: 0.2126 + 0.7874 * (1 - amount), - b: 0.7152 - 0.7152 * (1 - amount), - c: 0.0722 - 0.0722 * (1 - amount), - d: 0.2126 - 0.2126 * (1 - amount), - e: 0.7152 + 0.2848 * (1 - amount), - f: 0.0722 - 0.0722 * (1 - amount), - g: 0.2126 - 0.2126 * (1 - amount), - h: 0.0722 + 0.9278 * (1 - amount) - } - ) +Snap.filter.grayscale = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.grayscale.toString = function () { - return this() - } - /*\ + + let a = 0.2126 + 0.7874 * (1 - amount) + let b = 0.7152 - 0.7152 * (1 - amount) + let c = 0.0722 - 0.0722 * (1 - amount) + let d = 0.2126 - 0.2126 * (1 - amount) + let e = 0.7152 + 0.2848 * (1 - amount) + let f = 0.0722 - 0.0722 * (1 - amount) + let g = 0.2126 - 0.2126 * (1 - amount) + let h = 0.0722 + 0.9278 * (1 - amount) + + return `` +} +Snap.filter.grayscale.toString = function () { + return this() +} +/*\ * Snap.filter.sepia [ method ] ** @@ -213,29 +167,27 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.sepia = function (amount) { - if (amount == null) { - amount = 1 - } - return Snap.format( - '', - { - a: 0.393 + 0.607 * (1 - amount), - b: 0.769 - 0.769 * (1 - amount), - c: 0.189 - 0.189 * (1 - amount), - d: 0.349 - 0.349 * (1 - amount), - e: 0.686 + 0.314 * (1 - amount), - f: 0.168 - 0.168 * (1 - amount), - g: 0.272 - 0.272 * (1 - amount), - h: 0.534 - 0.534 * (1 - amount), - i: 0.131 + 0.869 * (1 - amount) - } - ) +Snap.filter.sepia = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.sepia.toString = function () { - return this() - } - /*\ + + let a = 0.393 + 0.607 * (1 - amount) + let b = 0.769 - 0.769 * (1 - amount) + let c = 0.189 - 0.189 * (1 - amount) + let d = 0.349 - 0.349 * (1 - amount) + let e = 0.686 + 0.314 * (1 - amount) + let f = 0.168 - 0.168 * (1 - amount) + let g = 0.272 - 0.272 * (1 - amount) + let h = 0.534 - 0.534 * (1 - amount) + let i = 0.131 + 0.869 * (1 - amount) + + return `` +} +Snap.filter.sepia.toString = function () { + return this() +} +/*\ * Snap.filter.saturate [ method ] ** @@ -244,18 +196,16 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.saturate = function (amount) { - if (amount == null) { - amount = 1 - } - return Snap.format('', { - amount: 1 - amount - }) +Snap.filter.saturate = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.saturate.toString = function () { - return this() - } - /*\ + return `` +} +Snap.filter.saturate.toString = function () { + return this() +} +/*\ * Snap.filter.hueRotate [ method ] ** @@ -264,16 +214,14 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - angle (number) angle of rotation = (string) filter representation \*/ - Snap.filter.hueRotate = function (angle) { - angle = angle || 0 - return Snap.format('', { - angle: angle - }) - } - Snap.filter.hueRotate.toString = function () { - return this() - } - /*\ +Snap.filter.hueRotate = function (angle) { + angle = angle || 0 + return `` +} +Snap.filter.hueRotate.toString = function () { + return this() +} +/*\ * Snap.filter.invert [ method ] ** @@ -282,23 +230,23 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.invert = function (amount) { - if (amount == null) { - amount = 1 - } - // - return Snap.format( - '', - { - amount: amount, - amount2: 1 - amount - } - ) +Snap.filter.invert = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.invert.toString = function () { - return this() - } - /*\ + // + return `` +} +Snap.filter.invert.toString = function () { + return this() +} +/*\ * Snap.filter.brightness [ method ] ** @@ -307,21 +255,16 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.brightness = function (amount) { - if (amount == null) { - amount = 1 - } - return Snap.format( - '', - { - amount: amount - } - ) +Snap.filter.brightness = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.brightness.toString = function () { - return this() - } - /*\ + return `` +} +Snap.filter.brightness.toString = function () { + return this() +} +/*\ * Snap.filter.contrast [ method ] ** @@ -330,19 +273,14 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - amount (number) amount of filter (`0..1`) = (string) filter representation \*/ - Snap.filter.contrast = function (amount) { - if (amount == null) { - amount = 1 - } - return Snap.format( - '', - { - amount: amount, - amount2: 0.5 - amount / 2 - } - ) +Snap.filter.contrast = function (amount) { + if (amount == null) { + amount = 1 } - Snap.filter.contrast.toString = function () { - return this() - } -}) + let amount2 = 0.5 - amount / 2 + return `` +} + +Snap.filter.contrast.toString = function () { + return this() +} diff --git a/src/index.js b/src/index.js index b644b82..026fe13 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,17 @@ import { Snap } from './svg.js' +import './paper.js' import './element.js' import './animation.js' import './matrix.js' import './attr.js' import './class.js' import './attradd.js' -import './paper.js' import './path.js' import './set.js' import './equal.js' import './mouse.js' import './filter.js' import './align.js' -import './colors.js' +// import './colors.js' export default Snap diff --git a/src/lib/color.js b/src/lib/color.js new file mode 100644 index 0000000..2a1c7b4 --- /dev/null +++ b/src/lib/color.js @@ -0,0 +1,444 @@ +/** + * {颜色处理} + * @author yutent + * @date 2024/03/06 13:05:09 + */ + +import { cacher, is } from '../utils.js' + +const hsrg = { hs: 1, rg: 1 } +const colourRegExp = + /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i + +function toHex(color) { + let i = + doc.getElementsByTagName('head')[0] || doc.getElementsByTagName('svg')[0], + red = 'rgb(255, 0, 0)' + toHex = cacher(function (color) { + if (color.toLowerCase() == 'red') { + return red + } + i.style.color = red + i.style.color = color + let out = doc.defaultView.getComputedStyle(i, E).getPropertyValue('color') + return out == red ? null : out + }) + return toHex(color) +} +function hsbtoString() { + return 'hsb(' + [this.h, this.s, this.b] + ')' +} +function hsltoString() { + return 'hsl(' + [this.h, this.s, this.l] + ')' +} +function rgbtoString() { + return this.opacity == 1 || this.opacity == null + ? this.hex + : 'rgba(' + [this.r, this.g, this.b, this.opacity] + ')' +} +function prepareRGB(r, g, b) { + if (g == null && is(r, 'object') && 'r' in r && 'g' in r && 'b' in r) { + b = r.b + g = r.g + r = r.r + } + if (g == null && is(r, string)) { + let clr = getRGB(r) + r = clr.r + g = clr.g + b = clr.b + } + if (r > 1 || g > 1 || b > 1) { + r /= 255 + g /= 255 + b /= 255 + } + + return [r, g, b] +} +function packageRGB(r, g, b, o) { + r = Math.round(r * 255) + g = Math.round(g * 255) + b = Math.round(b * 255) + let rgb = { + r: r, + g: g, + b: b, + opacity: is(o, 'finite') ? o : 1, + hex: rgb(r, g, b), + toString: rgbtoString + } + is(o, 'finite') && (rgb.opacity = o) + return rgb +} + +// Colour +/*\ + ** + * Parses color string as RGB object + - color (string) color string in one of the following formats: + #
    + #
  • Color name (red, green, cornflowerblue, etc)
  • + #
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • + #
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • + #
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • + #
  • rgba(•••, •••, •••, •••) — also with opacity
  • + #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • + #
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • + #
  • hsba(•••, •••, •••, •••) — also with opacity
  • + #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • + #
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • + #
  • hsla(•••, •••, •••, •••) — also with opacity
  • + #
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • + #
  • hsla(•••%, •••%, •••%, •••%) — also with opacity
  • + #
+ * Note that `%` can be used any time: `rgb(20%, 255, 50%)`. + = (object) RGB object in the following format: + o { + o r (number) red, + o g (number) green, + o b (number) blue, + o hex (string) color in HTML/CSS format: #••••••, + o error (boolean) true if string can't be parsed + o } +\*/ +export const getRGB = cacher(function (colour) { + if (!colour || !!((colour = String(colour)).indexOf('-') + 1)) { + return { + r: -1, + g: -1, + b: -1, + hex: 'none', + error: 1, + toString: rgbtoString + } + } + if (colour == 'none') { + return { r: -1, g: -1, b: -1, hex: 'none', toString: rgbtoString } + } + !( + hsrg.hasOwnProperty(colour.toLowerCase().slice(0, 2)) || + colour.charAt() == '#' + ) && (colour = toHex(colour)) + + if (!colour) { + return { + r: -1, + g: -1, + b: -1, + hex: 'none', + error: 1, + toString: rgbtoString + } + } + let res, + red, + green, + blue, + opacity, + t, + values, + rgb = colour.match(colourRegExp) + if (rgb) { + if (rgb[2]) { + blue = parseInt(rgb[2].substring(5), 16) + green = parseInt(rgb[2].substring(3, 5), 16) + red = parseInt(rgb[2].substring(1, 3), 16) + } + if (rgb[3]) { + blue = parseInt((t = rgb[3].charAt(3)) + t, 16) + green = parseInt((t = rgb[3].charAt(2)) + t, 16) + red = parseInt((t = rgb[3].charAt(1)) + t, 16) + } + if (rgb[4]) { + values = rgb[4].split(commaSpaces) + red = +values[0] + values[0].slice(-1) == '%' && (red *= 2.55) + green = +values[1] + values[1].slice(-1) == '%' && (green *= 2.55) + blue = +values[2] + values[2].slice(-1) == '%' && (blue *= 2.55) + rgb[1].toLowerCase().slice(0, 4) == 'rgba' && (opacity = +values[3]) + values[3] && values[3].slice(-1) == '%' && (opacity /= 100) + } + if (rgb[5]) { + values = rgb[5].split(commaSpaces) + red = +values[0] + values[0].slice(-1) == '%' && (red /= 100) + green = +values[1] + values[1].slice(-1) == '%' && (green /= 100) + blue = +values[2] + values[2].slice(-1) == '%' && (blue /= 100) + ;(values[0].slice(-3) == 'deg' || values[0].slice(-1) == '\xb0') && + (red /= 360) + rgb[1].toLowerCase().slice(0, 4) == 'hsba' && (opacity = +values[3]) + values[3] && values[3].slice(-1) == '%' && (opacity /= 100) + return hsb2rgb(red, green, blue, opacity) + } + if (rgb[6]) { + values = rgb[6].split(commaSpaces) + red = +values[0] + values[0].slice(-1) == '%' && (red /= 100) + green = +values[1] + values[1].slice(-1) == '%' && (green /= 100) + blue = +values[2] + values[2].slice(-1) == '%' && (blue /= 100) + ;(values[0].slice(-3) == 'deg' || values[0].slice(-1) == '\xb0') && + (red /= 360) + rgb[1].toLowerCase().slice(0, 4) == 'hsla' && (opacity = +values[3]) + values[3] && values[3].slice(-1) == '%' && (opacity /= 100) + return hsl2rgb(red, green, blue, opacity) + } + red = Math.min(Math.round(red), 255) + green = Math.min(Math.round(green), 255) + blue = Math.min(Math.round(blue), 255) + opacity = Math.min(Math.max(opacity, 0), 1) + rgb = { r: red, g: green, b: blue, toString: rgbtoString } + rgb.hex = + '#' + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1) + rgb.opacity = is(opacity, 'finite') ? opacity : 1 + return rgb + } + return { r: -1, g: -1, b: -1, hex: 'none', error: 1, toString: rgbtoString } +}) +/*\ + ** + * Converts HSB values to a hex representation of the color + - h (number) hue + - s (number) saturation + - b (number) value or brightness + = (string) hex representation of the color +\*/ +export const hsb = cacher(function (h, s, b) { + return hsb2rgb(h, s, b).hex +}) +/*\ + ** + * Converts HSL values to a hex representation of the color + - h (number) hue + - s (number) saturation + - l (number) luminosity + = (string) hex representation of the color +\*/ +export const hsl = cacher(function (h, s, l) { + return hsl2rgb(h, s, l).hex +}) +/*\ + ** + * Converts RGB values to a hex representation of the color + - r (number) red + - g (number) green + - b (number) blue + = (string) hex representation of the color +\*/ +export const rgb = cacher(function (r, g, b, o) { + if (is(o, 'finite')) { + let round = Math.round + return 'rgba(' + [round(r), round(g), round(b), +o.toFixed(2)] + ')' + } + return '#' + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) +}) + +/*\ + ** + * Parses the color string and returns an object featuring the color's component values + - clr (string) color string in one of the supported formats (see getRGB) + = (object) Combined RGB/HSB object in the following format: + o { + o r (number) red, + o g (number) green, + o b (number) blue, + o hex (string) color in HTML/CSS format: #••••••, + o error (boolean) `true` if string can't be parsed, + o h (number) hue, + o s (number) saturation, + o v (number) value (brightness), + o l (number) lightness + o } +\*/ +export const parseColor = function (clr) { + let rgb + if (is(clr, 'object') && 'h' in clr && 's' in clr && 'b' in clr) { + rgb = hsb2rgb(clr) + clr.r = rgb.r + clr.g = rgb.g + clr.b = rgb.b + clr.opacity = 1 + clr.hex = rgb.hex + } else if (is(clr, 'object') && 'h' in clr && 's' in clr && 'l' in clr) { + rgb = hsl2rgb(clr) + clr.r = rgb.r + clr.g = rgb.g + clr.b = rgb.b + clr.opacity = 1 + clr.hex = rgb.hex + } else { + if (is(clr, 'string')) { + clr = getRGB(clr) + } + if ( + is(clr, 'object') && + 'r' in clr && + 'g' in clr && + 'b' in clr && + !('error' in clr) + ) { + rgb = rgb2hsl(clr) + clr.h = rgb.h + clr.s = rgb.s + clr.l = rgb.l + rgb = rgb2hsb(clr) + clr.v = rgb.b + } else { + clr = { hex: 'none' } + clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1 + clr.error = 1 + } + } + clr.toString = rgbtoString + return clr +} +/*\ + ** + * Converts HSB values to an RGB object + - h (number) hue + - s (number) saturation + - v (number) value or brightness + = (object) RGB object in the following format: + o { + o r (number) red, + o g (number) green, + o b (number) blue, + o hex (string) color in HTML/CSS format: #•••••• + o } +\*/ +export const hsb2rgb = function (h, s, v, o) { + if (is(h, 'object') && 'h' in h && 's' in h && 'b' in h) { + v = h.b + s = h.s + o = h.o + h = h.h + } + h *= 360 + let R, G, B, X, C + h = (h % 360) / 60 + C = v * s + X = C * (1 - abs((h % 2) - 1)) + R = G = B = v - C + + h = ~~h + R += [C, X, 0, 0, X, C][h] + G += [X, C, C, X, 0, 0][h] + B += [0, 0, X, C, C, X][h] + return packageRGB(R, G, B, o) +} +/*\ + ** + * Converts HSL values to an RGB object + - h (number) hue + - s (number) saturation + - l (number) luminosity + = (object) RGB object in the following format: + o { + o r (number) red, + o g (number) green, + o b (number) blue, + o hex (string) color in HTML/CSS format: #•••••• + o } +\*/ +export const hsl2rgb = function (h, s, l, o) { + if (is(h, 'object') && 'h' in h && 's' in h && 'l' in h) { + l = h.l + s = h.s + h = h.h + } + if (h > 1 || s > 1 || l > 1) { + h /= 360 + s /= 100 + l /= 100 + } + h *= 360 + let R, G, B, X, C + h = (h % 360) / 60 + C = 2 * s * (l < 0.5 ? l : 1 - l) + X = C * (1 - abs((h % 2) - 1)) + R = G = B = l - C / 2 + + h = ~~h + R += [C, X, 0, 0, X, C][h] + G += [X, C, C, X, 0, 0][h] + B += [0, 0, X, C, C, X][h] + return packageRGB(R, G, B, o) +} +/*\ + ** + * Converts RGB values to an HSB object + - r (number) red + - g (number) green + - b (number) blue + = (object) HSB object in the following format: + o { + o h (number) hue, + o s (number) saturation, + o b (number) brightness + o } +\*/ +export const rgb2hsb = function (r, g, b) { + b = prepareRGB(r, g, b) + r = b[0] + g = b[1] + b = b[2] + + let H, S, V, C + V = Math.max(r, g, b) + C = V - Math.min(r, g, b) + H = + C == 0 + ? null + : V == r + ? (g - b) / C + : V == g + ? (b - r) / C + 2 + : (r - g) / C + 4 + H = (((H + 360) % 6) * 60) / 360 + S = C == 0 ? 0 : C / V + return { h: H, s: S, b: V, toString: hsbtoString } +} +/*\ + ** + * Converts RGB values to an HSL object + - r (number) red + - g (number) green + - b (number) blue + = (object) HSL object in the following format: + o { + o h (number) hue, + o s (number) saturation, + o l (number) luminosity + o } +\*/ +export const rgb2hsl = function (r, g, b) { + b = prepareRGB(r, g, b) + r = b[0] + g = b[1] + b = b[2] + + let H, S, L, M, m, C + M = Math.max(r, g, b) + m = Math.min(r, g, b) + C = M - m + H = + C == 0 + ? null + : M == r + ? (g - b) / C + : M == g + ? (b - r) / C + 2 + : (r - g) / C + 4 + H = (((H + 360) % 6) * 60) / 360 + L = (M + m) / 2 + S = C == 0 ? 0 : L < 0.5 ? C / (2 * L) : C / (2 - 2 * L) + return { h: H, s: S, l: L, toString: hsltoString } +} diff --git a/src/lib/constants.js b/src/lib/constants.js new file mode 100644 index 0000000..304a73d --- /dev/null +++ b/src/lib/constants.js @@ -0,0 +1,86 @@ +/** + * {一些常量} + * @author yutent + * @date 2024/03/06 16:25:01 + */ + +export const doc = document +export const win = window +export const xlink = 'http://www.w3.org/1999/xlink' +export const xmlns = 'http://www.w3.org/2000/svg' + +export const CSS_ATTR = { + 'alignment-baseline': 1, + 'baseline-shift': 1, + clip: 1, + 'clip-path': 1, + 'clip-rule': 1, + color: 1, + 'color-interpolation': 1, + 'color-interpolation-filters': 1, + 'color-profile': 1, + 'color-rendering': 1, + cursor: 1, + direction: 1, + display: 1, + 'dominant-baseline': 1, + 'enable-background': 1, + fill: 1, + 'fill-opacity': 1, + 'fill-rule': 1, + filter: 1, + 'flood-color': 1, + 'flood-opacity': 1, + font: 1, + 'font-family': 1, + 'font-size': 1, + 'font-size-adjust': 1, + 'font-stretch': 1, + 'font-style': 1, + 'font-variant': 1, + 'font-weight': 1, + 'glyph-orientation-horizontal': 1, + 'glyph-orientation-vertical': 1, + 'image-rendering': 1, + kerning: 1, + 'letter-spacing': 1, + 'lighting-color': 1, + marker: 1, + 'marker-end': 1, + 'marker-mid': 1, + 'marker-start': 1, + mask: 1, + opacity: 1, + overflow: 1, + 'pointer-events': 1, + 'shape-rendering': 1, + 'stop-color': 1, + 'stop-opacity': 1, + stroke: 1, + 'stroke-dasharray': 1, + 'stroke-dashoffset': 1, + 'stroke-linecap': 1, + 'stroke-linejoin': 1, + 'stroke-miterlimit': 1, + 'stroke-opacity': 1, + 'stroke-width': 1, + 'text-anchor': 1, + 'text-decoration': 1, + 'text-rendering': 1, + 'unicode-bidi': 1, + visibility: 1, + 'word-spacing': 1, + 'writing-mode': 1 +} + +export const ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i + +export const SEPARATOR = /[,\s]+/ +export const whitespace = /[\s]/g +export const commaSpaces = /[\s]*,[\s]*/ +export const bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/ +export const pathCommand = + /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi +export const T_COMMAND = + /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi +export const PATH_VALUES = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/gi diff --git a/src/lib/math.js b/src/lib/math.js new file mode 100644 index 0000000..f5a137e --- /dev/null +++ b/src/lib/math.js @@ -0,0 +1,91 @@ +/** + * {数学相关} + * @author yutent + * @date 2024/03/06 13:05:25 + */ + +/* -------------------------------------------------------- */ +/* --------------------- 数学方法 ---------------------- */ +/* -------------------------------------------------------- */ + +/** + * Returns an angle between two or three points + - x1 (number) x coord of first point + - y1 (number) y coord of first point + - x2 (number) x coord of second point + - y2 (number) y coord of second point + - x3 (number) #optional x coord of third point + - y3 (number) #optional y coord of third point + = (number) angle in degrees + */ +export function angle(x1, y1, x2, y2, x3, y3) { + if (x3 == null) { + let x = x1 - x2, + y = y1 - y2 + if (!x && !y) { + return 0 + } + return (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360 + } else { + return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3) + } +} + +export function rad(deg) { + return ((deg % 360) * Math.PI) / 180 +} + +export function deg(radius) { + return ((radius * 180) / Math.PI) % 360 +} + +export function sin(angle) { + return Math.sin(rad(angle)) +} + +export function tan(angle) { + return Math.tan(rad(angle)) +} + +export function cos(angle) { + return Math.cos(rad(angle)) +} + +export function asin(num) { + return deg(Math.asin(num)) +} + +export function acos(num) { + return deg(Math.acos(num)) +} + +export function atan(num) { + return deg(Math.atan(num)) +} + +export function atan2(x, y) { + return deg(Math.atan2(x, y)) +} + +/*\ + ** + * Returns distance between two points + - x1 (number) x coord of first point + - y1 (number) y coord of first point + - x2 (number) x coord of second point + - y2 (number) y coord of second point +\*/ +export function len(x1, y1, x2, y2) { + return Math.sqrt(len2(x1, y1, x2, y2)) +} +/*\ + ** + * Returns squared distance between two points + - x1 (number) x coord of first point + - y1 (number) y coord of first point + - x2 (number) x coord of second point + - y2 (number) y coord of second point +\*/ +export function len2(x1, y1, x2, y2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) +} diff --git a/src/matrix.js b/src/matrix.js index 3d59f58..b14b04e 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -11,40 +11,39 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Snap } from './svg.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var objectToString = Object.prototype.toString, - Str = String, - math = Math, - E = '' - function Matrix(a, b, c, d, e, f) { - if (b == null && objectToString.call(a) == '[object SVGMatrix]') { - this.a = a.a - this.b = a.b - this.c = a.c - this.d = a.d - this.e = a.e - this.f = a.f - return - } - if (a != null) { - this.a = +a - this.b = +b - this.c = +c - this.d = +d - this.e = +e - this.f = +f - } else { - this.a = 1 - this.b = 0 - this.c = 0 - this.d = 1 - this.e = 0 - this.f = 0 - } +import { Snap, SnapElement, Paper, Fragment } from './svg.js' +let objectToString = Object.prototype.toString, + Str = String, + math = Math, + E = '' +function Matrix(a, b, c, d, e, f) { + if (b == null && objectToString.call(a) == '[object SVGMatrix]') { + this.a = a.a + this.b = a.b + this.c = a.c + this.d = a.d + this.e = a.e + this.f = a.f + return } - ;(function (matrixproto) { - /*\ + if (a != null) { + this.a = +a + this.b = +b + this.c = +c + this.d = +d + this.e = +e + this.f = +f + } else { + this.a = 1 + this.b = 0 + this.c = 0 + this.d = 1 + this.e = 0 + this.f = 0 + } +} +;(function (matrixproto) { + /*\ * Matrix.add [ method ] ** @@ -58,22 +57,22 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { * or - matrix (object) @Matrix \*/ - matrixproto.add = function (a, b, c, d, e, f) { - if (a && a instanceof Matrix) { - return this.add(a.a, a.b, a.c, a.d, a.e, a.f) - } - var aNew = a * this.a + b * this.c, - bNew = a * this.b + b * this.d - this.e += e * this.a + f * this.c - this.f += e * this.b + f * this.d - this.c = c * this.a + d * this.c - this.d = c * this.b + d * this.d - - this.a = aNew - this.b = bNew - return this + matrixproto.add = function (a, b, c, d, e, f) { + if (a && a instanceof Matrix) { + return this.add(a.a, a.b, a.c, a.d, a.e, a.f) } - /*\ + let aNew = a * this.a + b * this.c, + bNew = a * this.b + b * this.d + this.e += e * this.a + f * this.c + this.f += e * this.b + f * this.d + this.c = c * this.a + d * this.c + this.d = c * this.b + d * this.d + + this.a = aNew + this.b = bNew + return this + } + /*\ * Matrix.multLeft [ method ] ** @@ -87,52 +86,52 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { * or - matrix (object) @Matrix \*/ - Matrix.prototype.multLeft = function (a, b, c, d, e, f) { - if (a && a instanceof Matrix) { - return this.multLeft(a.a, a.b, a.c, a.d, a.e, a.f) - } - var aNew = a * this.a + c * this.b, - cNew = a * this.c + c * this.d, - eNew = a * this.e + c * this.f + e - this.b = b * this.a + d * this.b - this.d = b * this.c + d * this.d - this.f = b * this.e + d * this.f + f - - this.a = aNew - this.c = cNew - this.e = eNew - return this + Matrix.prototype.multLeft = function (a, b, c, d, e, f) { + if (a && a instanceof Matrix) { + return this.multLeft(a.a, a.b, a.c, a.d, a.e, a.f) } - /*\ + let aNew = a * this.a + c * this.b, + cNew = a * this.c + c * this.d, + eNew = a * this.e + c * this.f + e + this.b = b * this.a + d * this.b + this.d = b * this.c + d * this.d + this.f = b * this.e + d * this.f + f + + this.a = aNew + this.c = cNew + this.e = eNew + return this + } + /*\ * Matrix.invert [ method ] ** * Returns an inverted version of the matrix = (object) @Matrix \*/ - matrixproto.invert = function () { - var me = this, - x = me.a * me.d - me.b * me.c - return new Matrix( - me.d / x, - -me.b / x, - -me.c / x, - me.a / x, - (me.c * me.f - me.d * me.e) / x, - (me.b * me.e - me.a * me.f) / x - ) - } - /*\ + matrixproto.invert = function () { + let me = this, + x = me.a * me.d - me.b * me.c + return new Matrix( + me.d / x, + -me.b / x, + -me.c / x, + me.a / x, + (me.c * me.f - me.d * me.e) / x, + (me.b * me.e - me.a * me.f) / x + ) + } + /*\ * Matrix.clone [ method ] ** * Returns a copy of the matrix = (object) @Matrix \*/ - matrixproto.clone = function () { - return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f) - } - /*\ + matrixproto.clone = function () { + return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f) + } + /*\ * Matrix.translate [ method ] ** @@ -140,12 +139,12 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - x (number) horizontal offset distance - y (number) vertical offset distance \*/ - matrixproto.translate = function (x, y) { - this.e += x * this.a + y * this.c - this.f += x * this.b + y * this.d - return this - } - /*\ + matrixproto.translate = function (x, y) { + this.e += x * this.a + y * this.c + this.f += x * this.b + y * this.d + return this + } + /*\ * Matrix.scale [ method ] ** @@ -156,17 +155,17 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - cy (number) #optional vertical origin point from which to scale * Default cx, cy is the middle point of the element. \*/ - matrixproto.scale = function (x, y, cx, cy) { - y == null && (y = x) - ;(cx || cy) && this.translate(cx, cy) - this.a *= x - this.b *= x - this.c *= y - this.d *= y - ;(cx || cy) && this.translate(-cx, -cy) - return this - } - /*\ + matrixproto.scale = function (x, y, cx, cy) { + y == null && (y = x) + ;(cx || cy) && this.translate(cx, cy) + this.a *= x + this.b *= x + this.c *= y + this.d *= y + ;(cx || cy) && this.translate(-cx, -cy) + return this + } + /*\ * Matrix.rotate [ method ] ** @@ -175,36 +174,36 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - x (number) horizontal origin point from which to rotate - y (number) vertical origin point from which to rotate \*/ - matrixproto.rotate = function (a, x, y) { - a = Snap.rad(a) - x = x || 0 - y = y || 0 - var cos = +math.cos(a).toFixed(9), - sin = +math.sin(a).toFixed(9) - this.add(cos, sin, -sin, cos, x, y) - return this.add(1, 0, 0, 1, -x, -y) - } - /*\ + matrixproto.rotate = function (a, x, y) { + a = Snap.rad(a) + x = x || 0 + y = y || 0 + let cos = +math.cos(a).toFixed(9), + sin = +math.sin(a).toFixed(9) + this.add(cos, sin, -sin, cos, x, y) + return this.add(1, 0, 0, 1, -x, -y) + } + /*\ * Matrix.skewX [ method ] ** * Skews the matrix along the x-axis - x (number) Angle to skew along the x-axis (in degrees). \*/ - matrixproto.skewX = function (x) { - return this.skew(x, 0) - } - /*\ + matrixproto.skewX = function (x) { + return this.skew(x, 0) + } + /*\ * Matrix.skewY [ method ] ** * Skews the matrix along the y-axis - y (number) Angle to skew along the y-axis (in degrees). \*/ - matrixproto.skewY = function (y) { - return this.skew(0, y) - } - /*\ + matrixproto.skewY = function (y) { + return this.skew(0, y) + } + /*\ * Matrix.skew [ method ] ** @@ -212,16 +211,16 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - y (number) Angle to skew along the y-axis (in degrees). - x (number) Angle to skew along the x-axis (in degrees). \*/ - matrixproto.skew = function (x, y) { - x = x || 0 - y = y || 0 - x = Snap.rad(x) - y = Snap.rad(y) - var c = math.tan(x).toFixed(9) - var b = math.tan(y).toFixed(9) - return this.add(1, b, c, 1, 0, 0) - } - /*\ + matrixproto.skew = function (x, y) { + x = x || 0 + y = y || 0 + x = Snap.rad(x) + y = Snap.rad(y) + let c = math.tan(x).toFixed(9) + let b = math.tan(y).toFixed(9) + return this.add(1, b, c, 1, 0, 0) + } + /*\ * Matrix.x [ method ] ** @@ -230,10 +229,10 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - y (number) = (number) x \*/ - matrixproto.x = function (x, y) { - return x * this.a + y * this.c + this.e - } - /*\ + matrixproto.x = function (x, y) { + return x * this.a + y * this.c + this.e + } + /*\ * Matrix.y [ method ] ** @@ -242,48 +241,48 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - y (number) = (number) y \*/ - matrixproto.y = function (x, y) { - return x * this.b + y * this.d + this.f - } - matrixproto.get = function (i) { - return +this[Str.fromCharCode(97 + i)].toFixed(4) - } - matrixproto.toString = function () { - return ( - 'matrix(' + - [ - this.get(0), - this.get(1), - this.get(2), - this.get(3), - this.get(4), - this.get(5) - ].join() + - ')' - ) - } - matrixproto.offset = function () { - return [this.e.toFixed(4), this.f.toFixed(4)] - } - function norm(a) { - return a[0] * a[0] + a[1] * a[1] - } - function normalize(a) { - var mag = math.sqrt(norm(a)) - a[0] && (a[0] /= mag) - a[1] && (a[1] /= mag) - } - /*\ + matrixproto.y = function (x, y) { + return x * this.b + y * this.d + this.f + } + matrixproto.get = function (i) { + return +this[Str.fromCharCode(97 + i)].toFixed(4) + } + matrixproto.toString = function () { + return ( + 'matrix(' + + [ + this.get(0), + this.get(1), + this.get(2), + this.get(3), + this.get(4), + this.get(5) + ].join() + + ')' + ) + } + matrixproto.offset = function () { + return [this.e.toFixed(4), this.f.toFixed(4)] + } + function norm(a) { + return a[0] * a[0] + a[1] * a[1] + } + function normalize(a) { + let mag = math.sqrt(norm(a)) + a[0] && (a[0] /= mag) + a[1] && (a[1] /= mag) + } + /*\ * Matrix.determinant [ method ] ** * Finds determinant of the given matrix. = (number) determinant \*/ - matrixproto.determinant = function () { - return this.a * this.d - this.b * this.c - } - /*\ + matrixproto.determinant = function () { + return this.a * this.d - this.b * this.c + } + /*\ * Matrix.split [ method ] ** @@ -297,100 +296,98 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { o rotate (number) rotation in deg o isSimple (boolean) could it be represented via simple transformations \*/ - matrixproto.split = function () { - var out = {} - // translation - out.dx = this.e - out.dy = this.f + matrixproto.split = function () { + let out = {} + // translation + out.dx = this.e + out.dy = this.f - // scale and shear - var row = [ - [this.a, this.b], - [this.c, this.d] - ] - out.scalex = math.sqrt(norm(row[0])) - normalize(row[0]) + // scale and shear + let row = [ + [this.a, this.b], + [this.c, this.d] + ] + out.scalex = math.sqrt(norm(row[0])) + normalize(row[0]) - out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1] - row[1] = [ - row[1][0] - row[0][0] * out.shear, - row[1][1] - row[0][1] * out.shear - ] + out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1] + row[1] = [ + row[1][0] - row[0][0] * out.shear, + row[1][1] - row[0][1] * out.shear + ] - out.scaley = math.sqrt(norm(row[1])) - normalize(row[1]) - out.shear /= out.scaley + out.scaley = math.sqrt(norm(row[1])) + normalize(row[1]) + out.shear /= out.scaley - if (this.determinant() < 0) { - out.scalex = -out.scalex - } - - // rotation - var sin = row[0][1], - cos = row[1][1] - if (cos < 0) { - out.rotate = Snap.deg(math.acos(cos)) - if (sin < 0) { - out.rotate = 360 - out.rotate - } - } else { - out.rotate = Snap.deg(math.asin(sin)) - } - - out.isSimple = - !+out.shear.toFixed(9) && - (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate) - out.isSuperSimple = - !+out.shear.toFixed(9) && - out.scalex.toFixed(9) == out.scaley.toFixed(9) && - !out.rotate - out.noRotation = !+out.shear.toFixed(9) && !out.rotate - return out + if (this.determinant() < 0) { + out.scalex = -out.scalex } - /*\ + + // rotation + let sin = row[0][1], + cos = row[1][1] + if (cos < 0) { + out.rotate = Snap.deg(math.acos(cos)) + if (sin < 0) { + out.rotate = 360 - out.rotate + } + } else { + out.rotate = Snap.deg(math.asin(sin)) + } + + out.isSimple = + !+out.shear.toFixed(9) && + (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate) + out.isSuperSimple = + !+out.shear.toFixed(9) && + out.scalex.toFixed(9) == out.scaley.toFixed(9) && + !out.rotate + out.noRotation = !+out.shear.toFixed(9) && !out.rotate + return out + } + /*\ * Matrix.toTransformString [ method ] ** * Returns transform string that represents given matrix = (string) transform string \*/ - matrixproto.toTransformString = function (shorter) { - var s = shorter || this.split() - if (!+s.shear.toFixed(9)) { - s.scalex = +s.scalex.toFixed(4) - s.scaley = +s.scaley.toFixed(4) - s.rotate = +s.rotate.toFixed(4) - return ( - (s.dx || s.dy ? 't' + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + - (s.rotate ? 'r' + [+s.rotate.toFixed(4), 0, 0] : E) + - (s.scalex != 1 || s.scaley != 1 - ? 's' + [s.scalex, s.scaley, 0, 0] - : E) - ) - } else { - return ( - 'm' + - [ - this.get(0), - this.get(1), - this.get(2), - this.get(3), - this.get(4), - this.get(5) - ] - ) - } + matrixproto.toTransformString = function (shorter) { + let s = shorter || this.split() + if (!+s.shear.toFixed(9)) { + s.scalex = +s.scalex.toFixed(4) + s.scaley = +s.scaley.toFixed(4) + s.rotate = +s.rotate.toFixed(4) + return ( + (s.dx || s.dy ? 't' + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + + (s.rotate ? 'r' + [+s.rotate.toFixed(4), 0, 0] : E) + + (s.scalex != 1 || s.scaley != 1 ? 's' + [s.scalex, s.scaley, 0, 0] : E) + ) + } else { + return ( + 'm' + + [ + this.get(0), + this.get(1), + this.get(2), + this.get(3), + this.get(4), + this.get(5) + ] + ) } - })(Matrix.prototype) - /*\ + } +})(Matrix.prototype) +/*\ * Snap.Matrix [ method ] ** * Matrix constructor, extend on your own risk. * To create matrices use @Snap.matrix. \*/ - Snap.Matrix = Matrix - /*\ +Snap.Matrix = Matrix +/*\ * Snap.matrix [ method ] ** @@ -407,7 +404,6 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - svgMatrix (SVGMatrix) = (object) @Matrix \*/ - Snap.matrix = function (a, b, c, d, e, f) { - return new Matrix(a, b, c, d, e, f) - } -}) +Snap.matrix = function (a, b, c, d, e, f) { + return new Matrix(a, b, c, d, e, f) +} diff --git a/src/mina.js b/src/mina.js index 8c18e53..029e51c 100644 --- a/src/mina.js +++ b/src/mina.js @@ -13,83 +13,59 @@ // limitations under the License. import eve from './eve.js' import { Snap } from './svg.js' +import { uuid } from './utils.js' -var animations = {}, +let animations = {}, requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function (callback) { - setTimeout(callback, 16, new Date().getTime()) - return true - }, + window.mozRequestAnimationFrame, requestID, - isArray = - Array.isArray || - function (a) { - return ( - a instanceof Array || - Object.prototype.toString.call(a) == '[object Array]' - ) - }, - idgen = 0, - idprefix = 'M' + (+new Date()).toString(36), - ID = function () { - return idprefix + (idgen++).toString(36) - }, + isArray = Array.isArray, diff = function (a, b, A, B) { if (isArray(a)) { res = [] - for (var i = 0, ii = a.length; i < ii; i++) { + for (let i = 0, ii = a.length; i < ii; i++) { res[i] = diff(a[i], b, A[i], B) } return res } - var dif = (A - a) / (B - b) + let dif = (A - a) / (B - b) return function (bb) { return a + dif * (bb - b) } }, - timer = - Date.now || - function () { - return +new Date() - }, + timer = Date.now, sta = function (val) { - var a = this + let a = this if (val == null) { return a.s } - var ds = a.s - val + let ds = a.s - val a.b += a.dur * ds a.B += a.dur * ds a.s = val }, speed = function (val) { - var a = this if (val == null) { - return a.spd + return this.spd } - a.spd = val + this.spd = val }, duration = function (val) { - var a = this if (val == null) { - return a.dur + return this.dur } - a.s = (a.s * val) / a.dur - a.dur = val + this.s = (this.s * val) / this.dur + this.dur = val }, stopit = function () { - var a = this - delete animations[a.id] - a.update() - eve('mina.stop.' + a.id, a) + delete animations[this.id] + this.update() + eve('mina.stop.' + this.id, a) }, pause = function () { - var a = this + let a = this if (a.pdif) { return } @@ -98,7 +74,7 @@ var animations = {}, a.pdif = a.get() - a.b }, resume = function () { - var a = this + let a = this if (!a.pdif) { return } @@ -108,11 +84,11 @@ var animations = {}, frame() }, update = function () { - var a = this, + let a = this, res if (isArray(a.start)) { res = [] - for (var j = 0, jj = a.start.length; j < jj; j++) { + for (let j = 0, jj = a.start.length; j < jj; j++) { res[j] = +a.start[j] + (a.end[j] - a.start[j]) * a.easing(a.s) } } else { @@ -130,10 +106,10 @@ var animations = {}, } return } - var len = 0 - for (var i in animations) + let len = 0 + for (let i in animations) if (animations.hasOwnProperty(i)) { - var a = animations[i], + let a = animations[i], b = a.get(), res len++ @@ -187,8 +163,8 @@ var animations = {}, o } \*/ mina = function (a, A, b, B, get, set, easing) { - var anim = { - id: ID(), + let anim = { + id: uuid('M'), start: a, end: A, b: b, @@ -207,7 +183,7 @@ var animations = {}, update: update } animations[anim.id] = anim - var len = 0, + let len = 0, i for (i in animations) if (animations.hasOwnProperty(i)) { @@ -289,7 +265,7 @@ mina.easeinout = function (n) { if (n == 0) { return 0 } - var q = 0.48 - n / 1.04, + let q = 0.48 - n / 1.04, Q = Math.sqrt(0.1734 + q * q), x = Q - q, X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), @@ -310,7 +286,7 @@ mina.backin = function (n) { if (n == 1) { return 1 } - var s = 1.70158 + let s = 1.70158 return n * n * ((s + 1) * n - s) } /*\ @@ -326,7 +302,7 @@ mina.backout = function (n) { return 0 } n = n - 1 - var s = 1.70158 + let s = 1.70158 return n * n * ((s + 1) * n + s) + 1 } /*\ @@ -354,7 +330,7 @@ mina.elastic = function (n) { = (number) output 0..1 \*/ mina.bounce = function (n) { - var s = 7.5625, + let s = 7.5625, p = 2.75, l if (n < 1 / p) { diff --git a/src/mouse.js b/src/mouse.js index a76b4bd..49e253d 100644 --- a/src/mouse.js +++ b/src/mouse.js @@ -12,389 +12,385 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper } from './svg.js' +import { is } from './utils.js' +import { doc, win } from './lib/constants.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - var elproto = Element.prototype, - has = 'hasOwnProperty', - supportsTouch = - 'ontouchstart' in window || - window.TouchEvent || - (window.DocumentTouch && document instanceof DocumentTouch), - events = [ - 'click', - 'dblclick', - 'mousedown', - 'mousemove', - 'mouseout', - 'mouseover', - 'mouseup', - 'touchstart', - 'touchmove', - 'touchend', - 'touchcancel' - ], - touchMap = { - mousedown: 'touchstart', - mousemove: 'touchmove', - mouseup: 'touchend' - }, - getScroll = function (xy, el) { - var name = xy == 'y' ? 'scrollTop' : 'scrollLeft', - doc = el && el.node ? el.node.ownerDocument : glob.doc - return doc[name in doc.documentElement ? 'documentElement' : 'body'][name] - }, - preventDefault = function () { - this.returnValue = false - }, - preventTouch = function () { - return this.originalEvent.preventDefault() - }, - stopPropagation = function () { - this.cancelBubble = true - }, - stopTouch = function () { - return this.originalEvent.stopPropagation() - }, - addEvent = function (obj, type, fn, element) { - var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, - f = function (e) { - var scrollY = getScroll('y', element), - scrollX = getScroll('x', element) - if (supportsTouch && touchMap[has](type)) { - for ( - var i = 0, ii = e.targetTouches && e.targetTouches.length; - i < ii; - i++ - ) { - if ( - e.targetTouches[i].target == obj || - obj.contains(e.targetTouches[i].target) - ) { - var olde = e - e = e.targetTouches[i] - e.originalEvent = olde - e.preventDefault = preventTouch - e.stopPropagation = stopTouch - break - } - } - } - var x = e.clientX + scrollX, - y = e.clientY + scrollY - return fn.call(element, e, x, y) - } - - if (type !== realName) { - obj.addEventListener(type, f, false) - } - - obj.addEventListener(realName, f, false) - - return function () { - if (type !== realName) { - obj.removeEventListener(type, f, false) - } - - obj.removeEventListener(realName, f, false) - return true - } - }, - drag = [], - dragMove = function (e) { - var x = e.clientX, - y = e.clientY, - scrollY = getScroll('y'), - scrollX = getScroll('x'), - dragi, - j = drag.length - while (j--) { - dragi = drag[j] - if (supportsTouch) { - var i = e.touches && e.touches.length, - touch - while (i--) { - touch = e.touches[i] +let elproto = SnapElement.prototype, + has = 'hasOwnProperty', + supportsTouch = + 'ontouchstart' in window || + window.TouchEvent || + (window.DocumentTouch && document instanceof DocumentTouch), + events = [ + 'click', + 'dblclick', + 'mousedown', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'touchstart', + 'touchmove', + 'touchend', + 'touchcancel' + ], + touchMap = { + mousedown: 'touchstart', + mousemove: 'touchmove', + mouseup: 'touchend' + }, + getScroll = function (xy, el) { + let name = xy == 'y' ? 'scrollTop' : 'scrollLeft', + doc = el && el.node ? el.node.ownerDocument : doc + return doc[name in doc.documentElement ? 'documentElement' : 'body'][name] + }, + preventDefault = function () { + this.returnValue = false + }, + preventTouch = function () { + return this.originalEvent.preventDefault() + }, + stopPropagation = function () { + this.cancelBubble = true + }, + stopTouch = function () { + return this.originalEvent.stopPropagation() + }, + addEvent = function (obj, type, fn, element) { + let realName = supportsTouch && touchMap[type] ? touchMap[type] : type, + f = function (e) { + let scrollY = getScroll('y', element), + scrollX = getScroll('x', element) + if (supportsTouch && touchMap[has](type)) { + for ( + let i = 0, ii = e.targetTouches && e.targetTouches.length; + i < ii; + i++ + ) { if ( - touch.identifier == dragi.el._drag.id || - dragi.el.node.contains(touch.target) + e.targetTouches[i].target == obj || + obj.contains(e.targetTouches[i].target) ) { - x = touch.clientX - y = touch.clientY - ;(e.originalEvent ? e.originalEvent : e).preventDefault() + let olde = e + e = e.targetTouches[i] + e.originalEvent = olde + e.preventDefault = preventTouch + e.stopPropagation = stopTouch break } } - } else { - e.preventDefault() } - var node = dragi.el.node, - o, - next = node.nextSibling, - parent = node.parentNode, - display = node.style.display - // glob.win.opera && parent.removeChild(node); - // node.style.display = "none"; - // o = dragi.el.paper.getElementByPoint(x, y); - // node.style.display = display; - // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); - // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o); - x += scrollX - y += scrollY - eve( - 'snap.drag.move.' + dragi.el.id, - dragi.move_scope || dragi.el, - x - dragi.el._drag.x, - y - dragi.el._drag.y, - x, - y, - e - ) + let x = e.clientX + scrollX, + y = e.clientY + scrollY + return fn.call(element, e, x, y) } - }, - dragUp = function (e) { - Snap.unmousemove(dragMove).unmouseup(dragUp) - var i = drag.length, - dragi - while (i--) { - dragi = drag[i] - dragi.el._drag = {} - eve( - 'snap.drag.end.' + dragi.el.id, - dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, - e - ) - eve.off('snap.drag.*.' + dragi.el.id) - } - drag = [] + + if (type !== realName) { + obj.addEventListener(type, f, false) } - /*\ - * Element.click + + obj.addEventListener(realName, f, false) + + return function () { + if (type !== realName) { + obj.removeEventListener(type, f, false) + } + + obj.removeEventListener(realName, f, false) + return true + } + }, + drag = [], + dragMove = function (e) { + let x = e.clientX, + y = e.clientY, + scrollY = getScroll('y'), + scrollX = getScroll('x'), + dragi, + j = drag.length + while (j--) { + dragi = drag[j] + if (supportsTouch) { + let i = e.touches && e.touches.length, + touch + while (i--) { + touch = e.touches[i] + if ( + touch.identifier == dragi.el._drag.id || + dragi.el.node.contains(touch.target) + ) { + x = touch.clientX + y = touch.clientY + ;(e.originalEvent ? e.originalEvent : e).preventDefault() + break + } + } + } else { + e.preventDefault() + } + let node = dragi.el.node, + o, + next = node.nextSibling, + parent = node.parentNode, + display = node.style.display + // win.opera && parent.removeChild(node); + // node.style.display = "none"; + // o = dragi.el.paper.getElementByPoint(x, y); + // node.style.display = display; + // win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node)); + // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o); + x += scrollX + y += scrollY + eve( + 'snap.drag.move.' + dragi.el.id, + dragi.move_scope || dragi.el, + x - dragi.el._drag.x, + y - dragi.el._drag.y, + x, + y, + e + ) + } + }, + dragUp = function (e) { + Snap.unmousemove(dragMove).unmouseup(dragUp) + let i = drag.length, + dragi + while (i--) { + dragi = drag[i] + dragi.el._drag = {} + eve( + 'snap.drag.end.' + dragi.el.id, + dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, + e + ) + eve.off('snap.drag.*.' + dragi.el.id) + } + drag = [] + } +/*\ + * SnapElement.click [ method ] ** * Adds a click event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unclick +/*\ + * SnapElement.unclick [ method ] ** * Removes a click event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.dblclick +/*\ + * SnapElement.dblclick [ method ] ** * Adds a double click event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.undblclick +/*\ + * SnapElement.undblclick [ method ] ** * Removes a double click event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.mousedown +/*\ + * SnapElement.mousedown [ method ] ** * Adds a mousedown event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unmousedown +/*\ + * SnapElement.unmousedown [ method ] ** * Removes a mousedown event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.mousemove +/*\ + * SnapElement.mousemove [ method ] ** * Adds a mousemove event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unmousemove +/*\ + * SnapElement.unmousemove [ method ] ** * Removes a mousemove event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.mouseout +/*\ + * SnapElement.mouseout [ method ] ** * Adds a mouseout event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unmouseout +/*\ + * SnapElement.unmouseout [ method ] ** * Removes a mouseout event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.mouseover +/*\ + * SnapElement.mouseover [ method ] ** * Adds a mouseover event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unmouseover +/*\ + * SnapElement.unmouseover [ method ] ** * Removes a mouseover event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.mouseup +/*\ + * SnapElement.mouseup [ method ] ** * Adds a mouseup event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.unmouseup +/*\ + * SnapElement.unmouseup [ method ] ** * Removes a mouseup event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.touchstart +/*\ + * SnapElement.touchstart [ method ] ** * Adds a touchstart event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.untouchstart +/*\ + * SnapElement.untouchstart [ method ] ** * Removes a touchstart event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.touchmove +/*\ + * SnapElement.touchmove [ method ] ** * Adds a touchmove event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.untouchmove +/*\ + * SnapElement.untouchmove [ method ] ** * Removes a touchmove event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.touchend +/*\ + * SnapElement.touchend [ method ] ** * Adds a touchend event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.untouchend +/*\ + * SnapElement.untouchend [ method ] ** * Removes a touchend event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.touchcancel +/*\ + * SnapElement.touchcancel [ method ] ** * Adds a touchcancel event handler to the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - /*\ - * Element.untouchcancel +/*\ + * SnapElement.untouchcancel [ method ] ** * Removes a touchcancel event handler from the element - handler (function) handler for the event - = (object) @Element + = (object) @SnapElement \*/ - for (var i = events.length; i--; ) { - ;(function (eventName) { - Snap[eventName] = elproto[eventName] = function (fn, scope) { - if (Snap.is(fn, 'function')) { - this.events = this.events || [] - this.events.push({ - name: eventName, - f: fn, - unbind: addEvent( - this.node || document, - eventName, - fn, - scope || this - ) - }) - } else { - for (var i = 0, ii = this.events.length; i < ii; i++) - if (this.events[i].name == eventName) { - try { - this.events[i].f.call(this) - } catch (e) {} - } - } - return this - } - Snap['un' + eventName] = elproto['un' + eventName] = function (fn) { - var events = this.events || [], - l = events.length - while (l--) - if (events[l].name == eventName && (events[l].f == fn || !fn)) { - events[l].unbind() - events.splice(l, 1) - !events.length && delete this.events - return this +for (let i = events.length; i--; ) { + ;(function (eventName) { + Snap[eventName] = elproto[eventName] = function (fn, scope) { + if (is(fn, 'function')) { + this.events = this.events || [] + this.events.push({ + name: eventName, + f: fn, + unbind: addEvent(this.node || document, eventName, fn, scope || this) + }) + } else { + for (let i = 0, ii = this.events.length; i < ii; i++) + if (this.events[i].name == eventName) { + try { + this.events[i].f.call(this) + } catch (e) {} } - return this } - })(events[i]) - } - /*\ - * Element.hover + return this + } + Snap['un' + eventName] = elproto['un' + eventName] = function (fn) { + let events = this.events || [], + l = events.length + while (l--) + if (events[l].name == eventName && (events[l].f == fn || !fn)) { + events[l].unbind() + events.splice(l, 1) + !events.length && delete this.events + return this + } + return this + } + })(events[i]) +} +/*\ + * SnapElement.hover [ method ] ** * Adds hover event handlers to the element @@ -402,30 +398,30 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - f_out (function) handler for hover out - icontext (object) #optional context for hover in handler - ocontext (object) #optional context for hover out handler - = (object) @Element + = (object) @SnapElement \*/ - elproto.hover = function (f_in, f_out, scope_in, scope_out) { - return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in) - } - /*\ - * Element.unhover +elproto.hover = function (f_in, f_out, scope_in, scope_out) { + return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in) +} +/*\ + * SnapElement.unhover [ method ] ** * Removes hover event handlers from the element - f_in (function) handler for hover in - f_out (function) handler for hover out - = (object) @Element + = (object) @SnapElement \*/ - elproto.unhover = function (f_in, f_out) { - return this.unmouseover(f_in).unmouseout(f_out) - } - var draggable = [] - // SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture. - // SIERRA Element.drag(): _x position of the mouse_: Where are the x/y values offset from? - // SIERRA Element.drag(): much of this member's doc appears to be duplicated for some reason. - // SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start. on start, drag.end. on end and drag.move. on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's ID? - /*\ - * Element.drag +elproto.unhover = function (f_in, f_out) { + return this.unmouseover(f_in).unmouseout(f_out) +} +let draggable = [] +// SIERRA unclear what _context_ refers to for starting, ending, moving the drag gesture. +// SIERRA SnapElement.drag(): _x position of the mouse_: Where are the x/y values offset from? +// SIERRA SnapElement.drag(): much of this member's doc appears to be duplicated for some reason. +// SIERRA Unclear about this sentence: _Additionally following drag events will be triggered: drag.start. on start, drag.end. on end and drag.move. on every move._ Is there a global _drag_ object to which you can assign handlers keyed by an element's id? +/*\ + * SnapElement.drag [ method ] ** * Adds event handlers for an element's drag gesture @@ -452,82 +448,81 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o event (object) DOM event object * End event and end handler are called in specified context or in context of the element with following parameters: o event (object) DOM event object - = (object) @Element + = (object) @SnapElement \*/ - elproto.drag = function ( - onmove, - onstart, - onend, - move_scope, - start_scope, - end_scope - ) { - var el = this - if (!arguments.length) { - var origTransform - return el.drag( - function (dx, dy) { - this.attr({ - transform: origTransform + (origTransform ? 'T' : 't') + [dx, dy] - }) - }, - function () { - origTransform = this.transform().local - } - ) - } - function start(e, x, y) { - ;(e.originalEvent || e).preventDefault() - el._drag.x = x - el._drag.y = y - el._drag.id = e.identifier - !drag.length && Snap.mousemove(dragMove).mouseup(dragUp) - drag.push({ - el: el, - move_scope: move_scope, - start_scope: start_scope, - end_scope: end_scope - }) - onstart && eve.on('snap.drag.start.' + el.id, onstart) - onmove && eve.on('snap.drag.move.' + el.id, onmove) - onend && eve.on('snap.drag.end.' + el.id, onend) - eve('snap.drag.start.' + el.id, start_scope || move_scope || el, x, y, e) - } - function init(e, x, y) { - eve('snap.draginit.' + el.id, el, e, x, y) - } - eve.on('snap.draginit.' + el.id, start) - el._drag = {} - draggable.push({ el: el, start: start, init: init }) - el.mousedown(init) - return el +elproto.drag = function ( + onmove, + onstart, + onend, + move_scope, + start_scope, + end_scope +) { + let el = this + if (!arguments.length) { + let origTransform + return el.drag( + function (dx, dy) { + this.attr({ + transform: origTransform + (origTransform ? 'T' : 't') + [dx, dy] + }) + }, + function () { + origTransform = this.transform().local + } + ) } - /* - * Element.onDragOver + function start(e, x, y) { + ;(e.originalEvent || e).preventDefault() + el._drag.x = x + el._drag.y = y + el._drag.id = e.identifier + !drag.length && Snap.mousemove(dragMove).mouseup(dragUp) + drag.push({ + el: el, + move_scope: move_scope, + start_scope: start_scope, + end_scope: end_scope + }) + onstart && eve.on('snap.drag.start.' + el.id, onstart) + onmove && eve.on('snap.drag.move.' + el.id, onmove) + onend && eve.on('snap.drag.end.' + el.id, onend) + eve('snap.drag.start.' + el.id, start_scope || move_scope || el, x, y, e) + } + function init(e, x, y) { + eve('snap.draginit.' + el.id, el, e, x, y) + } + eve.on('snap.draginit.' + el.id, start) + el._drag = {} + draggable.push({ el: el, start: start, init: init }) + el.mousedown(init) + return el +} +/* + * SnapElement.onDragOver [ method ] ** - * Shortcut to assign event handler for `drag.over.` event, where `id` is the element's `id` (see @Element.id) + * Shortcut to assign event handler for `drag.over.` event, where `id` is the element's `id` (see @SnapElement.id) - f (function) handler for event, first argument would be the element you are dragging over \*/ - // elproto.onDragOver = function (f) { - // f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id); - // }; - /*\ - * Element.undrag +// elproto.onDragOver = function (f) { +// f ? eve.on("snap.drag.over." + this.id, f) : eve.unbind("snap.drag.over." + this.id); +// }; +/*\ + * SnapElement.undrag [ method ] ** * Removes all drag event handlers from the given element \*/ - elproto.undrag = function () { - var i = draggable.length - while (i--) - if (draggable[i].el == this) { - this.unmousedown(draggable[i].init) - draggable.splice(i, 1) - eve.unbind('snap.drag.*.' + this.id) - eve.unbind('snap.draginit.' + this.id) - } - !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp) - return this - } -}) +elproto.undrag = function () { + let i = draggable.length + while (i--) + if (draggable[i].el == this) { + this.unmousedown(draggable[i].init) + draggable.splice(i, 1) + eve.unbind('snap.drag.*.' + this.id) + eve.unbind('snap.draginit.' + this.id) + } + !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp) + return this +} diff --git a/src/paper.js b/src/paper.js index 3caedb7..e1131a7 100644 --- a/src/paper.js +++ b/src/paper.js @@ -12,522 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment, make, wrap } from './svg.js' +import { $, is, uuid, preload } from './utils.js' +import { doc, win } from './lib/constants.js' -Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - var proto = Paper.prototype, - is = Snap.is - /*\ - * Paper.rect - [ method ] - * - * Draws a rectangle - ** - - x (number) x coordinate of the top left corner - - y (number) y coordinate of the top left corner - - width (number) width - - height (number) height - - rx (number) #optional horizontal radius for rounded corners, default is 0 - - ry (number) #optional vertical radius for rounded corners, default is rx or 0 - = (object) the `rect` element - ** - > Usage - | // regular rectangle - | var c = paper.rect(10, 10, 50, 50); - | // rectangle with rounded corners - | var c = paper.rect(40, 40, 50, 50, 10); - \*/ - proto.rect = function (x, y, w, h, rx, ry) { - var attr - if (ry == null) { - ry = rx - } - if (is(x, 'object') && x == '[object Object]') { - attr = x - } else if (x != null) { - attr = { - x: x, - y: y, - width: w, - height: h - } - if (rx != null) { - attr.rx = rx - attr.ry = ry - } - } - return this.el('rect', attr) - } - /*\ - * Paper.circle - [ method ] - ** - * Draws a circle - ** - - x (number) x coordinate of the centre - - y (number) y coordinate of the centre - - r (number) radius - = (object) the `circle` element - ** - > Usage - | var c = paper.circle(50, 50, 40); - \*/ - proto.circle = function (cx, cy, r) { - var attr - if (is(cx, 'object') && cx == '[object Object]') { - attr = cx - } else if (cx != null) { - attr = { - cx: cx, - cy: cy, - r: r - } - } - return this.el('circle', attr) - } - - var preload = (function () { - function onerror() { - this.parentNode.removeChild(this) - } - return function (src, f) { - var img = glob.doc.createElement('img'), - body = glob.doc.body - img.style.cssText = 'position:absolute;left:-9999em;top:-9999em' - img.onload = function () { - f.call(img) - img.onload = img.onerror = null - body.removeChild(img) - } - img.onerror = onerror - body.appendChild(img) - img.src = src - } - })() +let proto = Paper.prototype +// gradients +;(function () { + // gradients' helpers /*\ - * Paper.image - [ method ] - ** - * Places an image on the surface - ** - - src (string) URI of the source image - - x (number) x offset position - - y (number) y offset position - - width (number) width of the image - - height (number) height of the image - = (object) the `image` element - * or - = (object) Snap element object with type `image` - ** - > Usage - | var c = paper.image("apple.png", 10, 10, 80, 80); - \*/ - proto.image = function (src, x, y, width, height) { - var el = this.el('image') - if (is(src, 'object') && 'src' in src) { - el.attr(src) - } else if (src != null) { - var set = { - 'xlink:href': src, - preserveAspectRatio: 'none' - } - if (x != null && y != null) { - set.x = x - set.y = y - } - if (width != null && height != null) { - set.width = width - set.height = height - } else { - preload(src, function () { - Snap._.$(el.node, { - width: this.offsetWidth, - height: this.offsetHeight - }) - }) - } - Snap._.$(el.node, set) - } - return el - } - /*\ - * Paper.ellipse - [ method ] - ** - * Draws an ellipse - ** - - x (number) x coordinate of the centre - - y (number) y coordinate of the centre - - rx (number) horizontal radius - - ry (number) vertical radius - = (object) the `ellipse` element - ** - > Usage - | var c = paper.ellipse(50, 50, 40, 20); - \*/ - proto.ellipse = function (cx, cy, rx, ry) { - var attr - if (is(cx, 'object') && cx == '[object Object]') { - attr = cx - } else if (cx != null) { - attr = { - cx: cx, - cy: cy, - rx: rx, - ry: ry - } - } - return this.el('ellipse', attr) - } - // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier. - /*\ - * Paper.path - [ method ] - ** - * Creates a `` element using the given string as the path's definition - - pathString (string) #optional path string in SVG format - * Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example: - | "M10,20L30,40" - * This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates. - * - #

Here is short list of commands available, for more details see SVG path string format or article about path strings at MDN.

- # - # - # - # - # - # - # - # - # - # - # - #
CommandNameParameters
Mmoveto(x y)+
Zclosepath(none)
Llineto(x y)+
Hhorizontal linetox+
Vvertical linetoy+
Ccurveto(x1 y1 x2 y2 x y)+
Ssmooth curveto(x2 y2 x y)+
Qquadratic Bézier curveto(x1 y1 x y)+
Tsmooth quadratic Bézier curveto(x y)+
Aelliptical arc(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
RCatmull-Rom curveto*x1 y1 (x y)+
- * * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier. - * Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point. - > Usage - | var c = paper.path("M10 10L90 90"); - | // draw a diagonal line: - | // move to 10,10, line to 90,90 - \*/ - proto.path = function (d) { - var attr - if (is(d, 'object') && !is(d, 'array')) { - attr = d - } else if (d) { - attr = { d: d } - } - return this.el('path', attr) - } - /*\ - * Paper.g - [ method ] - ** - * Creates a group element - ** - - varargs (…) #optional elements to nest within the group - = (object) the `g` element - ** - > Usage - | var c1 = paper.circle(), - | c2 = paper.rect(), - | g = paper.g(c2, c1); // note that the order of elements is different - * or - | var c1 = paper.circle(), - | c2 = paper.rect(), - | g = paper.g(); - | g.add(c2, c1); - \*/ - /*\ - * Paper.group - [ method ] - ** - * See @Paper.g - \*/ - proto.group = proto.g = function (first) { - var attr, - el = this.el('g') - if (arguments.length == 1 && first && !first.type) { - el.attr(first) - } else if (arguments.length) { - el.add(Array.prototype.slice.call(arguments, 0)) - } - return el - } - /*\ - * Paper.svg - [ method ] - ** - * Creates a nested SVG element. - - x (number) @optional X of the element - - y (number) @optional Y of the element - - width (number) @optional width of the element - - height (number) @optional height of the element - - vbx (number) @optional viewbox X - - vby (number) @optional viewbox Y - - vbw (number) @optional viewbox width - - vbh (number) @optional viewbox height - ** - = (object) the `svg` element - ** - \*/ - proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) { - var attrs = {} - if (is(x, 'object') && y == null) { - attrs = x - } else { - if (x != null) { - attrs.x = x - } - if (y != null) { - attrs.y = y - } - if (width != null) { - attrs.width = width - } - if (height != null) { - attrs.height = height - } - if (vbx != null && vby != null && vbw != null && vbh != null) { - attrs.viewBox = [vbx, vby, vbw, vbh] - } - } - return this.el('svg', attrs) - } - /*\ - * Paper.mask - [ method ] - ** - * Equivalent in behaviour to @Paper.g, except it’s a mask. - ** - = (object) the `mask` element - ** - \*/ - proto.mask = function (first) { - var attr, - el = this.el('mask') - if (arguments.length == 1 && first && !first.type) { - el.attr(first) - } else if (arguments.length) { - el.add(Array.prototype.slice.call(arguments, 0)) - } - return el - } - /*\ - * Paper.ptrn - [ method ] - ** - * Equivalent in behaviour to @Paper.g, except it’s a pattern. - - x (number) @optional X of the element - - y (number) @optional Y of the element - - width (number) @optional width of the element - - height (number) @optional height of the element - - vbx (number) @optional viewbox X - - vby (number) @optional viewbox Y - - vbw (number) @optional viewbox width - - vbh (number) @optional viewbox height - ** - = (object) the `pattern` element - ** - \*/ - proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) { - if (is(x, 'object')) { - var attr = x - } else { - attr = { patternUnits: 'userSpaceOnUse' } - if (x) { - attr.x = x - } - if (y) { - attr.y = y - } - if (width != null) { - attr.width = width - } - if (height != null) { - attr.height = height - } - if (vx != null && vy != null && vw != null && vh != null) { - attr.viewBox = [vx, vy, vw, vh] - } else { - attr.viewBox = [x || 0, y || 0, width || 0, height || 0] - } - } - return this.el('pattern', attr) - } - /*\ - * Paper.use - [ method ] - ** - * Creates a element. - - id (string) @optional id of element to link - * or - - id (Element) @optional element to link - ** - = (object) the `use` element - ** - \*/ - proto.use = function (id) { - if (id != null) { - if (id instanceof Element) { - if (!id.attr('id')) { - id.attr({ id: Snap._.id(id) }) - } - id = id.attr('id') - } - if (String(id).charAt() == '#') { - id = id.substring(1) - } - return this.el('use', { 'xlink:href': '#' + id }) - } else { - return Element.prototype.use.call(this) - } - } - /*\ - * Paper.symbol - [ method ] - ** - * Creates a element. - - vbx (number) @optional viewbox X - - vby (number) @optional viewbox Y - - vbw (number) @optional viewbox width - - vbh (number) @optional viewbox height - = (object) the `symbol` element - ** - \*/ - proto.symbol = function (vx, vy, vw, vh) { - var attr = {} - if (vx != null && vy != null && vw != null && vh != null) { - attr.viewBox = [vx, vy, vw, vh] - } - - return this.el('symbol', attr) - } - /*\ - * Paper.text - [ method ] - ** - * Draws a text string - ** - - x (number) x coordinate position - - y (number) y coordinate position - - text (string|array) The text string to draw or array of strings to nest within separate `` elements - = (object) the `text` element - ** - > Usage - | var t1 = paper.text(50, 50, "Snap"); - | var t2 = paper.text(50, 50, ["S","n","a","p"]); - | // Text path usage - | t1.attr({textpath: "M10,10L100,100"}); - | // or - | var pth = paper.path("M10,10L100,100"); - | t1.attr({textpath: pth}); - \*/ - proto.text = function (x, y, text) { - var attr = {} - if (is(x, 'object')) { - attr = x - } else if (x != null) { - attr = { - x: x, - y: y, - text: text || '' - } - } - return this.el('text', attr) - } - /*\ - * Paper.line - [ method ] - ** - * Draws a line - ** - - x1 (number) x coordinate position of the start - - y1 (number) y coordinate position of the start - - x2 (number) x coordinate position of the end - - y2 (number) y coordinate position of the end - = (object) the `line` element - ** - > Usage - | var t1 = paper.line(50, 50, 100, 100); - \*/ - proto.line = function (x1, y1, x2, y2) { - var attr = {} - if (is(x1, 'object')) { - attr = x1 - } else if (x1 != null) { - attr = { - x1: x1, - x2: x2, - y1: y1, - y2: y2 - } - } - return this.el('line', attr) - } - /*\ - * Paper.polyline - [ method ] - ** - * Draws a polyline - ** - - points (array) array of points - * or - - varargs (…) points - = (object) the `polyline` element - ** - > Usage - | var p1 = paper.polyline([10, 10, 100, 100]); - | var p2 = paper.polyline(10, 10, 100, 100); - \*/ - proto.polyline = function (points) { - if (arguments.length > 1) { - points = Array.prototype.slice.call(arguments, 0) - } - var attr = {} - if (is(points, 'object') && !is(points, 'array')) { - attr = points - } else if (points != null) { - attr = { points: points } - } - return this.el('polyline', attr) - } - /*\ - * Paper.polygon - [ method ] - ** - * Draws a polygon. See @Paper.polyline - \*/ - proto.polygon = function (points) { - if (arguments.length > 1) { - points = Array.prototype.slice.call(arguments, 0) - } - var attr = {} - if (is(points, 'object') && !is(points, 'array')) { - attr = points - } else if (points != null) { - attr = { points: points } - } - return this.el('polygon', attr) - } - // gradients - ;(function () { - var $ = Snap._.$ - // gradients' helpers - /*\ - * Element.stops + * SnapElement.stops [ method ] ** * Only for gradients! * Returns array of gradient stops elements. = (array) the stops array. \*/ - function Gstops() { - return this.selectAll('stop') - } - /*\ - * Element.addStop + function Gstops() { + return this.selectAll('stop') + } + /*\ + * SnapElement.addStop [ method ] ** * Only for gradients! @@ -536,248 +42,157 @@ Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { - offset (number) stops offset 0..100 = (object) gradient element \*/ - function GaddStop(color, offset) { - var stop = $('stop'), - attr = { - offset: +offset + '%' - } - color = Snap.color(color) - attr['stop-color'] = color.hex - if (color.opacity < 1) { - attr['stop-opacity'] = color.opacity + function GaddStop(color, offset) { + let stop = $('stop'), + attr = { + offset: +offset + '%' } - $(stop, attr) - var stops = this.stops(), - inserted - for (var i = 0; i < stops.length; i++) { - var stopOffset = parseFloat(stops[i].attr('offset')) - if (stopOffset > offset) { - this.node.insertBefore(stop, stops[i].node) - inserted = true - break - } - } - if (!inserted) { - this.node.appendChild(stop) - } - return this + color = parseColor(color) + attr['stop-color'] = color.hex + if (color.opacity < 1) { + attr['stop-opacity'] = color.opacity } - function GgetBBox() { - if (this.type == 'linearGradient') { - var x1 = $(this.node, 'x1') || 0, - x2 = $(this.node, 'x2') || 1, - y1 = $(this.node, 'y1') || 0, - y2 = $(this.node, 'y2') || 0 - return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)) - } else { - var cx = this.node.cx || 0.5, - cy = this.node.cy || 0.5, - r = this.node.r || 0 - return Snap._.box(cx - r, cy - r, r * 2, r * 2) + $(stop, attr) + let stops = this.stops(), + inserted + for (let i = 0; i < stops.length; i++) { + let stopOffset = parseFloat(stops[i].attr('offset')) + if (stopOffset > offset) { + this.node.insertBefore(stop, stops[i].node) + inserted = true + break } } - /*\ - * Element.setStops + if (!inserted) { + this.node.appendChild(stop) + } + return this + } + function GgetBBox() { + if (this.type == 'linearGradient') { + let x1 = $(this.node, 'x1') || 0, + x2 = $(this.node, 'x2') || 1, + y1 = $(this.node, 'y1') || 0, + y2 = $(this.node, 'y2') || 0 + return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)) + } else { + let cx = this.node.cx || 0.5, + cy = this.node.cy || 0.5, + r = this.node.r || 0 + return Snap._.box(cx - r, cy - r, r * 2, r * 2) + } + } + /*\ + * SnapElement.setStops [ method ] ** * Only for gradients! * Updates stops of the gradient based on passed gradient descriptor. See @Ppaer.gradient - str (string) gradient descriptor part after `()`. = (object) gradient element - | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); + | let g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); | g.setStops("#fff-#000-#f00-#fc0"); \*/ - function GsetStops(str) { - var grad = str, - stops = this.stops() - if (typeof str == 'string') { - grad = eve( - 'snap.util.grad.parse', - null, - 'l(0,0,0,1)' + str - ).firstDefined().stops - } - if (!Snap.is(grad, 'array')) { - return - } - for (var i = 0; i < stops.length; i++) { - if (grad[i]) { - var color = Snap.color(grad[i].color), - attr = { offset: grad[i].offset + '%' } - attr['stop-color'] = color.hex - if (color.opacity < 1) { - attr['stop-opacity'] = color.opacity - } - stops[i].attr(attr) - } else { - stops[i].remove() - } - } - for (i = stops.length; i < grad.length; i++) { - this.addStop(grad[i].color, grad[i].offset) - } - return this + function GsetStops(str) { + let grad = str, + stops = this.stops() + if (typeof str == 'string') { + grad = eve( + 'snap.util.grad.parse', + null, + 'l(0,0,0,1)' + str + ).firstDefined().stops } - function gradient(defs, str) { - var grad = eve('snap.util.grad.parse', null, str).firstDefined(), - el - if (!grad) { - return null - } - grad.params.unshift(defs) - if (grad.type.toLowerCase() == 'l') { - el = gradientLinear.apply(0, grad.params) + if (!is(grad, 'array')) { + return + } + for (let i = 0; i < stops.length; i++) { + if (grad[i]) { + let color = parseColor(grad[i].color), + attr = { offset: grad[i].offset + '%' } + attr['stop-color'] = color.hex + if (color.opacity < 1) { + attr['stop-opacity'] = color.opacity + } + stops[i].attr(attr) } else { - el = gradientRadial.apply(0, grad.params) - } - if (grad.type != grad.type.toLowerCase()) { - $(el.node, { - gradientUnits: 'userSpaceOnUse' - }) - } - var stops = grad.stops, - len = stops.length - for (var i = 0; i < len; i++) { - var stop = stops[i] - el.addStop(stop.color, stop.offset) - } - return el - } - function gradientLinear(defs, x1, y1, x2, y2) { - var el = Snap._.make('linearGradient', defs) - el.stops = Gstops - el.addStop = GaddStop - el.getBBox = GgetBBox - el.setStops = GsetStops - if (x1 != null) { - $(el.node, { - x1: x1, - y1: y1, - x2: x2, - y2: y2 - }) - } - return el - } - function gradientRadial(defs, cx, cy, r, fx, fy) { - var el = Snap._.make('radialGradient', defs) - el.stops = Gstops - el.addStop = GaddStop - el.getBBox = GgetBBox - if (cx != null) { - $(el.node, { - cx: cx, - cy: cy, - r: r - }) - } - if (fx != null && fy != null) { - $(el.node, { - fx: fx, - fy: fy - }) - } - return el - } - /*\ - * Paper.gradient - [ method ] - ** - * Creates a gradient element - ** - - gradient (string) gradient descriptor - > Gradient Descriptor - * The gradient descriptor is an expression formatted as - * follows: `()`. The `` can be - * either linear or radial. The uppercase `L` or `R` letters - * indicate absolute coordinates offset from the SVG surface. - * Lowercase `l` or `r` letters indicate coordinates - * calculated relative to the element to which the gradient is - * applied. Coordinates specify a linear gradient vector as - * `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`, - * `r` and optional `fx`, `fy` specifying a focal point away - * from the center of the circle. Specify `` as a list - * of dash-separated CSS color values. Each color may be - * followed by a custom offset value, separated with a colon - * character. - > Examples - * Linear gradient, relative from top-left corner to bottom-right - * corner, from black through red to white: - | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); - * Linear gradient, absolute from (0, 0) to (100, 100), from black - * through red at 25% to white: - | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff"); - * Radial gradient, relative from the center of the element with radius - * half the width, from black to white: - | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff"); - * To apply the gradient: - | paper.circle(50, 50, 40).attr({ - | fill: g - | }); - = (object) the `gradient` element - \*/ - proto.gradient = function (str) { - return gradient(this.defs, str) - } - proto.gradientLinear = function (x1, y1, x2, y2) { - return gradientLinear(this.defs, x1, y1, x2, y2) - } - proto.gradientRadial = function (cx, cy, r, fx, fy) { - return gradientRadial(this.defs, cx, cy, r, fx, fy) - } - /*\ - * Paper.toString - [ method ] - ** - * Returns SVG code for the @Paper - = (string) SVG code for the @Paper - \*/ - proto.toString = function () { - var doc = this.node.ownerDocument, - f = doc.createDocumentFragment(), - d = doc.createElement('div'), - svg = this.node.cloneNode(true), - res - f.appendChild(d) - d.appendChild(svg) - Snap._.$(svg, { xmlns: 'http://www.w3.org/2000/svg' }) - res = d.innerHTML - f.removeChild(f.firstChild) - return res - } - /*\ - * Paper.toDataURL - [ method ] - ** - * Returns SVG code for the @Paper as Data URI string. - = (string) Data URI string - \*/ - proto.toDataURL = function () { - if (window && window.btoa) { - return ( - 'data:image/svg+xml;base64,' + - btoa(unescape(encodeURIComponent(this))) - ) + stops[i].remove() } } - /*\ - * Paper.clear - [ method ] - ** - * Removes all child nodes of the paper, except . - \*/ - proto.clear = function () { - var node = this.node.firstChild, - next - while (node) { - next = node.nextSibling - if (node.tagName != 'defs') { - node.parentNode.removeChild(node) - } else { - proto.clear.call({ node: node }) - } - node = next - } + for (i = stops.length; i < grad.length; i++) { + this.addStop(grad[i].color, grad[i].offset) } - })() -}) + return this + } + function gradient(defs, str) { + let grad = eve('snap.util.grad.parse', null, str).firstDefined(), + el + if (!grad) { + return null + } + grad.params.unshift(defs) + if (grad.type.toLowerCase() == 'l') { + el = gradientLinear.apply(0, grad.params) + } else { + el = gradientRadial.apply(0, grad.params) + } + if (grad.type != grad.type.toLowerCase()) { + $(el.node, { + gradientUnits: 'userSpaceOnUse' + }) + } + let stops = grad.stops, + len = stops.length + for (let i = 0; i < len; i++) { + let stop = stops[i] + el.addStop(stop.color, stop.offset) + } + return el + } + function gradientLinear(defs, x1, y1, x2, y2) { + let el = make('linearGradient', defs) + el.stops = Gstops + el.addStop = GaddStop + el.getBBox = GgetBBox + el.setStops = GsetStops + if (x1 != null) { + $(el.node, { + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }) + } + return el + } + function gradientRadial(defs, cx, cy, r, fx, fy) { + let el = make('radialGradient', defs) + el.stops = Gstops + el.addStop = GaddStop + el.getBBox = GgetBBox + if (cx != null) { + $(el.node, { + cx: cx, + cy: cy, + r: r + }) + } + if (fx != null && fy != null) { + $(el.node, { + fx: fx, + fy: fy + }) + } + return el + } + + proto.gradient = function (str) { + return gradient(this.defs, str) + } + proto.gradientLinear = function (x1, y1, x2, y2) { + return gradientLinear(this.defs, x1, y1, x2, y2) + } + proto.gradientRadial = function (cx, cy, r, fx, fy) { + return gradientRadial(this.defs, cx, cy, r, fx, fy) + } +})() diff --git a/src/path.js b/src/path.js index d9cf2bb..3af80b8 100644 --- a/src/path.js +++ b/src/path.js @@ -11,1287 +11,1268 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment, unit2px } from './svg.js' +import { is, clone, cacher } from './utils.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - var elproto = Element.prototype, - is = Snap.is, - clone = Snap._.clone, - has = 'hasOwnProperty', - p2s = /,?([a-z]),?/gi, - toFloat = parseFloat, - math = Math, - PI = math.PI, - mmin = math.min, - mmax = math.max, - pow = math.pow, - abs = math.abs - function paths(ps) { - var p = (paths.ps = paths.ps || {}) - if (p[ps]) { - p[ps].sleep = 100 - } else { - p[ps] = { - sleep: 100 +let elproto = SnapElement.prototype, + has = 'hasOwnProperty', + p2s = /,?([a-z]),?/gi, + toFloat = parseFloat, + math = Math, + PI = math.PI, + mmin = math.min, + mmax = math.max, + pow = math.pow, + abs = math.abs +function paths(ps) { + let p = (paths.ps = paths.ps || {}) + if (p[ps]) { + p[ps].sleep = 100 + } else { + p[ps] = { + sleep: 100 + } + } + setTimeout(function () { + for (let key in p) + if (p[has](key) && key != ps) { + p[key].sleep-- + !p[key].sleep && delete p[key] } - } - setTimeout(function () { - for (var key in p) - if (p[has](key) && key != ps) { - p[key].sleep-- - !p[key].sleep && delete p[key] - } - }) - return p[ps] + }) + return p[ps] +} +function box(x, y, width, height) { + if (x == null) { + x = y = width = height = 0 } - function box(x, y, width, height) { - if (x == null) { - x = y = width = height = 0 - } - if (y == null) { - y = x.y - width = x.width - height = x.height - x = x.x - } - return { - x: x, - y: y, - width: width, - w: width, - height: height, - h: height, - x2: x + width, - y2: y + height, - cx: x + width / 2, - cy: y + height / 2, - r1: math.min(width, height) / 2, - r2: math.max(width, height) / 2, - r0: math.sqrt(width * width + height * height) / 2, - path: rectPath(x, y, width, height), - vb: [x, y, width, height].join(' ') - } + if (y == null) { + y = x.y + width = x.width + height = x.height + x = x.x } - function toString() { - return this.join(',').replace(p2s, '$1') + return { + x: x, + y: y, + width: width, + w: width, + height: height, + h: height, + x2: x + width, + y2: y + height, + cx: x + width / 2, + cy: y + height / 2, + r1: math.min(width, height) / 2, + r2: math.max(width, height) / 2, + r0: math.sqrt(width * width + height * height) / 2, + path: rectPath(x, y, width, height), + vb: [x, y, width, height].join(' ') } - function pathClone(pathArray) { - var res = clone(pathArray) - res.toString = toString - return res - } - function getPointAtSegmentLength( - p1x, - p1y, - c1x, - c1y, - c2x, - c2y, - p2x, - p2y, - length - ) { - if (length == null) { - return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) - } else { - return findDotsAtSegment( - p1x, - p1y, - c1x, - c1y, - c2x, - c2y, - p2x, - p2y, - getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) - ) - } - } - function getLengthFactory(istotal, subpath) { - function O(val) { - return +(+val).toFixed(3) - } - return Snap._.cacher( - function (path, length, onlystart) { - if (path instanceof Element) { - path = path.attr('d') - } - path = path2curve(path) - var x, - y, - p, - l, - sp = '', - subpaths = {}, - point, - len = 0 - for (var i = 0, ii = path.length; i < ii; i++) { - p = path[i] - if (p[0] == 'M') { - x = +p[1] - y = +p[2] - } else { - l = getPointAtSegmentLength( - x, - y, - p[1], - p[2], - p[3], - p[4], - p[5], - p[6] - ) - if (len + l > length) { - if (subpath && !subpaths.start) { - point = getPointAtSegmentLength( - x, - y, - p[1], - p[2], - p[3], - p[4], - p[5], - p[6], - length - len - ) - sp += [ - 'C' + O(point.start.x), - O(point.start.y), - O(point.m.x), - O(point.m.y), - O(point.x), - O(point.y) - ] - if (onlystart) { - return sp - } - subpaths.start = sp - sp = [ - 'M' + O(point.x), - O(point.y) + 'C' + O(point.n.x), - O(point.n.y), - O(point.end.x), - O(point.end.y), - O(p[5]), - O(p[6]) - ].join() - len += l - x = +p[5] - y = +p[6] - continue - } - if (!istotal && !subpath) { - point = getPointAtSegmentLength( - x, - y, - p[1], - p[2], - p[3], - p[4], - p[5], - p[6], - length - len - ) - return point - } - } - len += l - x = +p[5] - y = +p[6] - } - sp += p.shift() + p - } - subpaths.end = sp - point = istotal - ? len - : subpath - ? subpaths - : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1) - return point - }, - null, - Snap._.clone +} +function toString() { + return this.join(',').replace(p2s, '$1') +} +function pathClone(pathArray) { + let res = clone(pathArray) + res.toString = toString + return res +} +function getPointAtSegmentLength( + p1x, + p1y, + c1x, + c1y, + c2x, + c2y, + p2x, + p2y, + length +) { + if (length == null) { + return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) + } else { + return findDotsAtSegment( + p1x, + p1y, + c1x, + c1y, + c2x, + c2y, + p2x, + p2y, + getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) ) } - var getTotalLength = getLengthFactory(1), - getPointAtLength = getLengthFactory(), - getSubpathsAtLength = getLengthFactory(0, 1) - function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { - var t1 = 1 - t, - t13 = pow(t1, 3), - t12 = pow(t1, 2), - t2 = t * t, - t3 = t2 * t, - x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, - y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, - mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), - my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), - nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), - ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), - ax = t1 * p1x + t * c1x, - ay = t1 * p1y + t * c1y, - cx = t1 * c2x + t * p2x, - cy = t1 * c2y + t * p2y, - alpha = 90 - (math.atan2(mx - nx, my - ny) * 180) / PI - // (mx > nx || my < ny) && (alpha += 180); - return { - x: x, - y: y, - m: { x: mx, y: my }, - n: { x: nx, y: ny }, - start: { x: ax, y: ay }, - end: { x: cx, y: cy }, - alpha: alpha - } +} +function getLengthFactory(istotal, subpath) { + function O(val) { + return +(+val).toFixed(3) } - function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { - if (!Snap.is(p1x, 'array')) { - p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y] - } - var bbox = curveDim.apply(null, p1x) - return box( - bbox.min.x, - bbox.min.y, - bbox.max.x - bbox.min.x, - bbox.max.y - bbox.min.y - ) - } - function isPointInsideBBox(bbox, x, y) { - return ( - x >= bbox.x && - x <= bbox.x + bbox.width && - y >= bbox.y && - y <= bbox.y + bbox.height - ) - } - function isBBoxIntersect(bbox1, bbox2) { - bbox1 = box(bbox1) - bbox2 = box(bbox2) - return ( - isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || - isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || - isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || - isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || - isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || - isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || - isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || - isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || - (((bbox1.x < bbox2.x2 && bbox1.x > bbox2.x) || - (bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)) && - ((bbox1.y < bbox2.y2 && bbox1.y > bbox2.y) || - (bbox2.y < bbox1.y2 && bbox2.y > bbox1.y))) - ) - } - function base3(t, p1, p2, p3, p4) { - var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, - t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3 - return t * t2 - 3 * p1 + 3 * p2 - } - function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { - if (z == null) { - z = 1 - } - z = z > 1 ? 1 : z < 0 ? 0 : z - var z2 = z / 2, - n = 12, - Tvalues = [ - -0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, - -0.9041, 0.9041, -0.9816, 0.9816 - ], - Cvalues = [ - 0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, - 0.1069, 0.0472, 0.0472 - ], - sum = 0 - for (var i = 0; i < n; i++) { - var ct = z2 * Tvalues[i] + z2, - xbase = base3(ct, x1, x2, x3, x4), - ybase = base3(ct, y1, y2, y3, y4), - comb = xbase * xbase + ybase * ybase - sum += Cvalues[i] * math.sqrt(comb) - } - return z2 * sum - } - function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { - if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { - return - } - var t = 1, - step = t / 2, - t2 = t - step, - l, - e = 0.01 - l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) - while (abs(l - ll) > e) { - step /= 2 - t2 += (l < ll ? 1 : -1) * step - l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) - } - return t2 - } - function intersect(x1, y1, x2, y2, x3, y3, x4, y4) { - if ( - mmax(x1, x2) < mmin(x3, x4) || - mmin(x1, x2) > mmax(x3, x4) || - mmax(y1, y2) < mmin(y3, y4) || - mmin(y1, y2) > mmax(y3, y4) - ) { - return - } - var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), - ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), - denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) - - if (!denominator) { - return - } - var px = nx / denominator, - py = ny / denominator, - px2 = +px.toFixed(2), - py2 = +py.toFixed(2) - if ( - px2 < +mmin(x1, x2).toFixed(2) || - px2 > +mmax(x1, x2).toFixed(2) || - px2 < +mmin(x3, x4).toFixed(2) || - px2 > +mmax(x3, x4).toFixed(2) || - py2 < +mmin(y1, y2).toFixed(2) || - py2 > +mmax(y1, y2).toFixed(2) || - py2 < +mmin(y3, y4).toFixed(2) || - py2 > +mmax(y3, y4).toFixed(2) - ) { - return - } - return { x: px, y: py } - } - function inter(bez1, bez2) { - return interHelper(bez1, bez2) - } - function interCount(bez1, bez2) { - return interHelper(bez1, bez2, 1) - } - function interHelper(bez1, bez2, justCount) { - var bbox1 = bezierBBox(bez1), - bbox2 = bezierBBox(bez2) - if (!isBBoxIntersect(bbox1, bbox2)) { - return justCount ? 0 : [] - } - var l1 = bezlen.apply(0, bez1), - l2 = bezlen.apply(0, bez2), - n1 = ~~(l1 / 8), - n2 = ~~(l2 / 8), - dots1 = [], - dots2 = [], - xy = {}, - res = justCount ? 0 : [] - for (var i = 0; i < n1 + 1; i++) { - var p = findDotsAtSegment.apply(0, bez1.concat(i / n1)) - dots1.push({ x: p.x, y: p.y, t: i / n1 }) - } - for (i = 0; i < n2 + 1; i++) { - p = findDotsAtSegment.apply(0, bez2.concat(i / n2)) - dots2.push({ x: p.x, y: p.y, t: i / n2 }) - } - for (i = 0; i < n1; i++) { - for (var j = 0; j < n2; j++) { - var di = dots1[i], - di1 = dots1[i + 1], - dj = dots2[j], - dj1 = dots2[j + 1], - ci = abs(di1.x - di.x) < 0.001 ? 'y' : 'x', - cj = abs(dj1.x - dj.x) < 0.001 ? 'y' : 'x', - is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y) - if (is) { - if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { - continue - } - xy[is.x.toFixed(4)] = is.y.toFixed(4) - var t1 = - di.t + - abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), - t2 = - dj.t + - abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t) - if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { - if (justCount) { - res++ - } else { - res.push({ - x: is.x, - y: is.y, - t1: t1, - t2: t2 - }) - } - } - } + return cacher( + function (path, length, onlystart) { + if (path instanceof SnapElement) { + path = path.attr('d') } - } - return res - } - function pathIntersection(path1, path2) { - return interPathHelper(path1, path2) - } - function pathIntersectionNumber(path1, path2) { - return interPathHelper(path1, path2, 1) - } - function interPathHelper(path1, path2, justCount) { - path1 = path2curve(path1) - path2 = path2curve(path2) - var x1, - y1, - x2, - y2, - x1m, - y1m, - x2m, - y2m, - bez1, - bez2, - res = justCount ? 0 : [] - for (var i = 0, ii = path1.length; i < ii; i++) { - var pi = path1[i] - if (pi[0] == 'M') { - x1 = x1m = pi[1] - y1 = y1m = pi[2] - } else { - if (pi[0] == 'C') { - bez1 = [x1, y1].concat(pi.slice(1)) - x1 = bez1[6] - y1 = bez1[7] + path = path2curve(path) + let x, + y, + p, + l, + sp = '', + subpaths = {}, + point, + len = 0 + for (let i = 0, ii = path.length; i < ii; i++) { + p = path[i] + if (p[0] == 'M') { + x = +p[1] + y = +p[2] } else { - bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m] - x1 = x1m - y1 = y1m - } - for (var j = 0, jj = path2.length; j < jj; j++) { - var pj = path2[j] - if (pj[0] == 'M') { - x2 = x2m = pj[1] - y2 = y2m = pj[2] - } else { - if (pj[0] == 'C') { - bez2 = [x2, y2].concat(pj.slice(1)) - x2 = bez2[6] - y2 = bez2[7] - } else { - bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m] - x2 = x2m - y2 = y2m - } - var intr = interHelper(bez1, bez2, justCount) - if (justCount) { - res += intr - } else { - for (var k = 0, kk = intr.length; k < kk; k++) { - intr[k].segment1 = i - intr[k].segment2 = j - intr[k].bez1 = bez1 - intr[k].bez2 = bez2 + l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]) + if (len + l > length) { + if (subpath && !subpaths.start) { + point = getPointAtSegmentLength( + x, + y, + p[1], + p[2], + p[3], + p[4], + p[5], + p[6], + length - len + ) + sp += [ + 'C' + O(point.start.x), + O(point.start.y), + O(point.m.x), + O(point.m.y), + O(point.x), + O(point.y) + ] + if (onlystart) { + return sp } - res = res.concat(intr) + subpaths.start = sp + sp = [ + 'M' + O(point.x), + O(point.y) + 'C' + O(point.n.x), + O(point.n.y), + O(point.end.x), + O(point.end.y), + O(p[5]), + O(p[6]) + ].join() + len += l + x = +p[5] + y = +p[6] + continue + } + if (!istotal && !subpath) { + point = getPointAtSegmentLength( + x, + y, + p[1], + p[2], + p[3], + p[4], + p[5], + p[6], + length - len + ) + return point } } + len += l + x = +p[5] + y = +p[6] } + sp += p.shift() + p } - } - return res + subpaths.end = sp + point = istotal + ? len + : subpath + ? subpaths + : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1) + return point + }, + null, + clone + ) +} +let getTotalLength = getLengthFactory(1), + getPointAtLength = getLengthFactory(), + getSubpathsAtLength = getLengthFactory(0, 1) +function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + let t1 = 1 - t, + t13 = pow(t1, 3), + t12 = pow(t1, 2), + t2 = t * t, + t3 = t2 * t, + x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, + y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y, + mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), + my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), + nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), + ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), + ax = t1 * p1x + t * c1x, + ay = t1 * p1y + t * c1y, + cx = t1 * c2x + t * p2x, + cy = t1 * c2y + t * p2y, + alpha = 90 - (math.atan2(mx - nx, my - ny) * 180) / PI + // (mx > nx || my < ny) && (alpha += 180); + return { + x: x, + y: y, + m: { x: mx, y: my }, + n: { x: nx, y: ny }, + start: { x: ax, y: ay }, + end: { x: cx, y: cy }, + alpha: alpha } - function isPointInsidePath(path, x, y) { - var bbox = pathBBox(path) - return ( - isPointInsideBBox(bbox, x, y) && - interPathHelper( - path, - [ - ['M', x, y], - ['H', bbox.x2 + 10] - ], - 1 - ) % - 2 == - 1 - ) +} +function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { + if (!is(p1x, 'array')) { + p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y] } - function pathBBox(path) { - var pth = paths(path) - if (pth.bbox) { - return clone(pth.bbox) - } - if (!path) { - return box() - } - path = path2curve(path) - var x = 0, - y = 0, - X = [], - Y = [], - p - for (var i = 0, ii = path.length; i < ii; i++) { - p = path[i] - if (p[0] == 'M') { - x = p[1] - y = p[2] - X.push(x) - Y.push(y) - } else { - var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]) - X = X.concat(dim.min.x, dim.max.x) - Y = Y.concat(dim.min.y, dim.max.y) - x = p[5] - y = p[6] - } - } - var xmin = mmin.apply(0, X), - ymin = mmin.apply(0, Y), - xmax = mmax.apply(0, X), - ymax = mmax.apply(0, Y), - bb = box(xmin, ymin, xmax - xmin, ymax - ymin) - pth.bbox = clone(bb) - return bb + let bbox = curveDim.apply(null, p1x) + return box( + bbox.min.x, + bbox.min.y, + bbox.max.x - bbox.min.x, + bbox.max.y - bbox.min.y + ) +} +function isPointInsideBBox(bbox, x, y) { + return ( + x >= bbox.x && + x <= bbox.x + bbox.width && + y >= bbox.y && + y <= bbox.y + bbox.height + ) +} +function isBBoxIntersect(bbox1, bbox2) { + bbox1 = box(bbox1) + bbox2 = box(bbox2) + return ( + isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || + isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || + isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || + isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || + isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || + isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || + isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || + isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || + (((bbox1.x < bbox2.x2 && bbox1.x > bbox2.x) || + (bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)) && + ((bbox1.y < bbox2.y2 && bbox1.y > bbox2.y) || + (bbox2.y < bbox1.y2 && bbox2.y > bbox1.y))) + ) +} +function base3(t, p1, p2, p3, p4) { + let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, + t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3 + return t * t2 - 3 * p1 + 3 * p2 +} +function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { + if (z == null) { + z = 1 } - function rectPath(x, y, w, h, r) { - if (r) { - return [ - ['M', +x + +r, y], - ['l', w - r * 2, 0], - ['a', r, r, 0, 0, 1, r, r], - ['l', 0, h - r * 2], - ['a', r, r, 0, 0, 1, -r, r], - ['l', r * 2 - w, 0], - ['a', r, r, 0, 0, 1, -r, -r], - ['l', 0, r * 2 - h], - ['a', r, r, 0, 0, 1, r, -r], - ['z'] - ] - } - var res = [['M', x, y], ['l', w, 0], ['l', 0, h], ['l', -w, 0], ['z']] - res.toString = toString - return res + z = z > 1 ? 1 : z < 0 ? 0 : z + let z2 = z / 2, + n = 12, + Tvalues = [ + -0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, + -0.9041, 0.9041, -0.9816, 0.9816 + ], + Cvalues = [ + 0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, + 0.1069, 0.0472, 0.0472 + ], + sum = 0 + for (let i = 0; i < n; i++) { + let ct = z2 * Tvalues[i] + z2, + xbase = base3(ct, x1, x2, x3, x4), + ybase = base3(ct, y1, y2, y3, y4), + comb = xbase * xbase + ybase * ybase + sum += Cvalues[i] * math.sqrt(comb) } - function ellipsePath(x, y, rx, ry, a) { - if (a == null && ry == null) { - ry = rx - } - x = +x - y = +y - rx = +rx - ry = +ry - if (a != null) { - var rad = Math.PI / 180, - x1 = x + rx * Math.cos(-ry * rad), - x2 = x + rx * Math.cos(-a * rad), - y1 = y + rx * Math.sin(-ry * rad), - y2 = y + rx * Math.sin(-a * rad), - res = [ - ['M', x1, y1], - ['A', rx, rx, 0, +(a - ry > 180), 0, x2, y2] - ] - } else { - res = [ - ['M', x, y], - ['m', 0, -ry], - ['a', rx, ry, 0, 1, 1, 0, 2 * ry], - ['a', rx, ry, 0, 1, 1, 0, -2 * ry], - ['z'] - ] - } - res.toString = toString - return res + return z2 * sum +} +function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { + if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { + return } - var unit2px = Snap._unit2px, - getPath = { - path: function (el) { - return el.attr('path') - }, - circle: function (el) { - var attr = unit2px(el) - return ellipsePath(attr.cx, attr.cy, attr.r) - }, - ellipse: function (el) { - var attr = unit2px(el) - return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry) - }, - rect: function (el) { - var attr = unit2px(el) - return rectPath( - attr.x || 0, - attr.y || 0, - attr.width, - attr.height, - attr.rx, - attr.ry - ) - }, - image: function (el) { - var attr = unit2px(el) - return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height) - }, - line: function (el) { - return ( - 'M' + - [el.attr('x1') || 0, el.attr('y1') || 0, el.attr('x2'), el.attr('y2')] - ) - }, - polyline: function (el) { - return 'M' + el.attr('points') - }, - polygon: function (el) { - return 'M' + el.attr('points') + 'z' - }, - deflt: function (el) { - var bbox = el.node.getBBox() - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height) - } - } - function pathToRelative(pathArray) { - var pth = paths(pathArray), - lowerCase = String.prototype.toLowerCase - if (pth.rel) { - return pathClone(pth.rel) - } - if ( - !Snap.is(pathArray, 'array') || - !Snap.is(pathArray && pathArray[0], 'array') - ) { - pathArray = Snap.parsePathString(pathArray) - } - var res = [], - x = 0, - y = 0, - mx = 0, - my = 0, - start = 0 - if (pathArray[0][0] == 'M') { - x = pathArray[0][1] - y = pathArray[0][2] - mx = x - my = y - start++ - res.push(['M', x, y]) - } - for (var i = start, ii = pathArray.length; i < ii; i++) { - var r = (res[i] = []), - pa = pathArray[i] - if (pa[0] != lowerCase.call(pa[0])) { - r[0] = lowerCase.call(pa[0]) - switch (r[0]) { - case 'a': - r[1] = pa[1] - r[2] = pa[2] - r[3] = pa[3] - r[4] = pa[4] - r[5] = pa[5] - r[6] = +(pa[6] - x).toFixed(3) - r[7] = +(pa[7] - y).toFixed(3) - break - case 'v': - r[1] = +(pa[1] - y).toFixed(3) - break - case 'm': - mx = pa[1] - my = pa[2] - default: - for (var j = 1, jj = pa.length; j < jj; j++) { - r[j] = +(pa[j] - (j % 2 ? x : y)).toFixed(3) - } - } - } else { - r = res[i] = [] - if (pa[0] == 'm') { - mx = pa[1] + x - my = pa[2] + y - } - for (var k = 0, kk = pa.length; k < kk; k++) { - res[i][k] = pa[k] - } - } - var len = res[i].length - switch (res[i][0]) { - case 'z': - x = mx - y = my - break - case 'h': - x += +res[i][len - 1] - break - case 'v': - y += +res[i][len - 1] - break - default: - x += +res[i][len - 2] - y += +res[i][len - 1] - } - } - res.toString = toString - pth.rel = pathClone(res) - return res + let t = 1, + step = t / 2, + t2 = t - step, + l, + e = 0.01 + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) + while (abs(l - ll) > e) { + step /= 2 + t2 += (l < ll ? 1 : -1) * step + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2) } - function pathToAbsolute(pathArray) { - var pth = paths(pathArray) - if (pth.abs) { - return pathClone(pth.abs) - } - if (!is(pathArray, 'array') || !is(pathArray && pathArray[0], 'array')) { - // rough assumption - pathArray = Snap.parsePathString(pathArray) - } - if (!pathArray || !pathArray.length) { - return [['M', 0, 0]] - } - var res = [], - x = 0, - y = 0, - mx = 0, - my = 0, - start = 0, - pa0 - if (pathArray[0][0] == 'M') { - x = +pathArray[0][1] - y = +pathArray[0][2] - mx = x - my = y - start++ - res[0] = ['M', x, y] - } - var crz = - pathArray.length == 3 && - pathArray[0][0] == 'M' && - pathArray[1][0].toUpperCase() == 'R' && - pathArray[2][0].toUpperCase() == 'Z' - for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { - res.push((r = [])) - pa = pathArray[i] - pa0 = pa[0] - if (pa0 != pa0.toUpperCase()) { - r[0] = pa0.toUpperCase() - switch (r[0]) { - case 'A': - r[1] = pa[1] - r[2] = pa[2] - r[3] = pa[3] - r[4] = pa[4] - r[5] = pa[5] - r[6] = +pa[6] + x - r[7] = +pa[7] + y - break - case 'V': - r[1] = +pa[1] + y - break - case 'H': - r[1] = +pa[1] + x - break - case 'R': - var dots = [x, y].concat(pa.slice(1)) - for (var j = 2, jj = dots.length; j < jj; j++) { - dots[j] = +dots[j] + x - dots[++j] = +dots[j] + y - } - res.pop() - res = res.concat(catmullRom2bezier(dots, crz)) - break - case 'O': - res.pop() - dots = ellipsePath(x, y, pa[1], pa[2]) - dots.push(dots[0]) - res = res.concat(dots) - break - case 'U': - res.pop() - res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) - r = ['U'].concat(res[res.length - 1].slice(-2)) - break - case 'M': - mx = +pa[1] + x - my = +pa[2] + y - default: - for (j = 1, jj = pa.length; j < jj; j++) { - r[j] = +pa[j] + (j % 2 ? x : y) - } - } - } else if (pa0 == 'R') { - dots = [x, y].concat(pa.slice(1)) - res.pop() - res = res.concat(catmullRom2bezier(dots, crz)) - r = ['R'].concat(pa.slice(-2)) - } else if (pa0 == 'O') { - res.pop() - dots = ellipsePath(x, y, pa[1], pa[2]) - dots.push(dots[0]) - res = res.concat(dots) - } else if (pa0 == 'U') { - res.pop() - res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) - r = ['U'].concat(res[res.length - 1].slice(-2)) - } else { - for (var k = 0, kk = pa.length; k < kk; k++) { - r[k] = pa[k] - } - } - pa0 = pa0.toUpperCase() - if (pa0 != 'O') { - switch (r[0]) { - case 'Z': - x = +mx - y = +my - break - case 'H': - x = r[1] - break - case 'V': - y = r[1] - break - case 'M': - mx = r[r.length - 2] - my = r[r.length - 1] - default: - x = r[r.length - 2] - y = r[r.length - 1] - } - } - } - res.toString = toString - pth.abs = pathClone(res) - return res - } - function l2c(x1, y1, x2, y2) { - return [x1, y1, x2, y2, x2, y2] - } - function q2c(x1, y1, ax, ay, x2, y2) { - var _13 = 1 / 3, - _23 = 2 / 3 - return [ - _13 * x1 + _23 * ax, - _13 * y1 + _23 * ay, - _13 * x2 + _23 * ax, - _13 * y2 + _23 * ay, - x2, - y2 - ] - } - function a2c( - x1, - y1, - rx, - ry, - angle, - large_arc_flag, - sweep_flag, - x2, - y2, - recursive + return t2 +} +function intersect(x1, y1, x2, y2, x3, y3, x4, y4) { + if ( + mmax(x1, x2) < mmin(x3, x4) || + mmin(x1, x2) > mmax(x3, x4) || + mmax(y1, y2) < mmin(y3, y4) || + mmin(y1, y2) > mmax(y3, y4) ) { - // for more information of where this math came from visit: - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - var _120 = (PI * 120) / 180, - rad = (PI / 180) * (+angle || 0), - res = [], - xy, - rotate = Snap._.cacher(function (x, y, rad) { - var X = x * math.cos(rad) - y * math.sin(rad), - Y = x * math.sin(rad) + y * math.cos(rad) - return { x: X, y: Y } - }) - if (!rx || !ry) { - return [x1, y1, x2, y2, x2, y2] - } - if (!recursive) { - xy = rotate(x1, y1, -rad) - x1 = xy.x - y1 = xy.y - xy = rotate(x2, y2, -rad) - x2 = xy.x - y2 = xy.y - var cos = math.cos((PI / 180) * angle), - sin = math.sin((PI / 180) * angle), - x = (x1 - x2) / 2, - y = (y1 - y2) / 2 - var h = (x * x) / (rx * rx) + (y * y) / (ry * ry) - if (h > 1) { - h = math.sqrt(h) - rx = h * rx - ry = h * ry - } - var rx2 = rx * rx, - ry2 = ry * ry, - k = - (large_arc_flag == sweep_flag ? -1 : 1) * - math.sqrt( - abs( - (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / - (rx2 * y * y + ry2 * x * x) - ) - ), - cx = (k * rx * y) / ry + (x1 + x2) / 2, - cy = (k * -ry * x) / rx + (y1 + y2) / 2, - f1 = math.asin(((y1 - cy) / ry).toFixed(9)), - f2 = math.asin(((y2 - cy) / ry).toFixed(9)) - - f1 = x1 < cx ? PI - f1 : f1 - f2 = x2 < cx ? PI - f2 : f2 - f1 < 0 && (f1 = PI * 2 + f1) - f2 < 0 && (f2 = PI * 2 + f2) - if (sweep_flag && f1 > f2) { - f1 = f1 - PI * 2 - } - if (!sweep_flag && f2 > f1) { - f2 = f2 - PI * 2 - } - } else { - f1 = recursive[0] - f2 = recursive[1] - cx = recursive[2] - cy = recursive[3] - } - var df = f2 - f1 - if (abs(df) > _120) { - var f2old = f2, - x2old = x2, - y2old = y2 - f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1) - x2 = cx + rx * math.cos(f2) - y2 = cy + ry * math.sin(f2) - res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [ - f2, - f2old, - cx, - cy - ]) - } - df = f2 - f1 - var c1 = math.cos(f1), - s1 = math.sin(f1), - c2 = math.cos(f2), - s2 = math.sin(f2), - t = math.tan(df / 4), - hx = (4 / 3) * rx * t, - hy = (4 / 3) * ry * t, - m1 = [x1, y1], - m2 = [x1 + hx * s1, y1 - hy * c1], - m3 = [x2 + hx * s2, y2 - hy * c2], - m4 = [x2, y2] - m2[0] = 2 * m1[0] - m2[0] - m2[1] = 2 * m1[1] - m2[1] - if (recursive) { - return [m2, m3, m4].concat(res) - } else { - res = [m2, m3, m4].concat(res).join().split(',') - var newres = [] - for (var i = 0, ii = res.length; i < ii; i++) { - newres[i] = - i % 2 - ? rotate(res[i - 1], res[i], rad).y - : rotate(res[i], res[i + 1], rad).x - } - return newres - } - } - function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { - var t1 = 1 - t - return { - x: - pow(t1, 3) * p1x + - pow(t1, 2) * 3 * t * c1x + - t1 * 3 * t * t * c2x + - pow(t, 3) * p2x, - y: - pow(t1, 3) * p1y + - pow(t1, 2) * 3 * t * c1y + - t1 * 3 * t * t * c2y + - pow(t, 3) * p2y - } + return } + let nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), + ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), + denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) - // Returns bounding box of cubic bezier curve. - // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - // Original version: NISHIO Hirokazu - // Modifications: https://github.com/timo22345 - function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { - var tvalues = [], - bounds = [[], []], - a, - b, - c, - t, - t1, - t2, - b2ac, - sqrtb2ac - for (var i = 0; i < 2; ++i) { - if (i == 0) { - b = 6 * x0 - 12 * x1 + 6 * x2 - a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3 - c = 3 * x1 - 3 * x0 - } else { - b = 6 * y0 - 12 * y1 + 6 * y2 - a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3 - c = 3 * y1 - 3 * y0 - } - if (abs(a) < 1e-12) { - if (abs(b) < 1e-12) { + if (!denominator) { + return + } + let px = nx / denominator, + py = ny / denominator, + px2 = +px.toFixed(2), + py2 = +py.toFixed(2) + if ( + px2 < +mmin(x1, x2).toFixed(2) || + px2 > +mmax(x1, x2).toFixed(2) || + px2 < +mmin(x3, x4).toFixed(2) || + px2 > +mmax(x3, x4).toFixed(2) || + py2 < +mmin(y1, y2).toFixed(2) || + py2 > +mmax(y1, y2).toFixed(2) || + py2 < +mmin(y3, y4).toFixed(2) || + py2 > +mmax(y3, y4).toFixed(2) + ) { + return + } + return { x: px, y: py } +} +function inter(bez1, bez2) { + return interHelper(bez1, bez2) +} +function interCount(bez1, bez2) { + return interHelper(bez1, bez2, 1) +} +function interHelper(bez1, bez2, justCount) { + let bbox1 = bezierBBox(bez1), + bbox2 = bezierBBox(bez2) + if (!isBBoxIntersect(bbox1, bbox2)) { + return justCount ? 0 : [] + } + let l1 = bezlen.apply(0, bez1), + l2 = bezlen.apply(0, bez2), + n1 = ~~(l1 / 8), + n2 = ~~(l2 / 8), + dots1 = [], + dots2 = [], + xy = {}, + res = justCount ? 0 : [] + for (let i = 0; i < n1 + 1; i++) { + let p = findDotsAtSegment.apply(0, bez1.concat(i / n1)) + dots1.push({ x: p.x, y: p.y, t: i / n1 }) + } + for (i = 0; i < n2 + 1; i++) { + p = findDotsAtSegment.apply(0, bez2.concat(i / n2)) + dots2.push({ x: p.x, y: p.y, t: i / n2 }) + } + for (i = 0; i < n1; i++) { + for (let j = 0; j < n2; j++) { + let di = dots1[i], + di1 = dots1[i + 1], + dj = dots2[j], + dj1 = dots2[j + 1], + ci = abs(di1.x - di.x) < 0.001 ? 'y' : 'x', + cj = abs(dj1.x - dj.x) < 0.001 ? 'y' : 'x', + is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y) + if (is) { + if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) { continue } - t = -c / b - if (0 < t && t < 1) { - tvalues.push(t) - } - continue - } - b2ac = b * b - 4 * c * a - sqrtb2ac = math.sqrt(b2ac) - if (b2ac < 0) { - continue - } - t1 = (-b + sqrtb2ac) / (2 * a) - if (0 < t1 && t1 < 1) { - tvalues.push(t1) - } - t2 = (-b - sqrtb2ac) / (2 * a) - if (0 < t2 && t2 < 1) { - tvalues.push(t2) - } - } - - var x, - y, - j = tvalues.length, - jlen = j, - mt - while (j--) { - t = tvalues[j] - mt = 1 - t - bounds[0][j] = - mt * mt * mt * x0 + - 3 * mt * mt * t * x1 + - 3 * mt * t * t * x2 + - t * t * t * x3 - bounds[1][j] = - mt * mt * mt * y0 + - 3 * mt * mt * t * y1 + - 3 * mt * t * t * y2 + - t * t * t * y3 - } - - bounds[0][jlen] = x0 - bounds[1][jlen] = y0 - bounds[0][jlen + 1] = x3 - bounds[1][jlen + 1] = y3 - bounds[0].length = bounds[1].length = jlen + 2 - - return { - min: { x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1]) }, - max: { x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1]) } - } - } - - function path2curve(path, path2) { - var pth = !path2 && paths(path) - if (!path2 && pth.curve) { - return pathClone(pth.curve) - } - var p = pathToAbsolute(path), - p2 = path2 && pathToAbsolute(path2), - attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, - attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, - processPath = function (path, d, pcom) { - var nx, ny - if (!path) { - return ['C', d.x, d.y, d.x, d.y, d.x, d.y] - } - !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null) - switch (path[0]) { - case 'M': - d.X = path[1] - d.Y = path[2] - break - case 'A': - path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))) - break - case 'S': - if (pcom == 'C' || pcom == 'S') { - // In "S" case we have to take into account, if the previous command is C/S. - nx = d.x * 2 - d.bx // And reflect the previous - ny = d.y * 2 - d.by // command's control point relative to the current point. - } else { - // or some else or nothing - nx = d.x - ny = d.y - } - path = ['C', nx, ny].concat(path.slice(1)) - break - case 'T': - if (pcom == 'Q' || pcom == 'T') { - // In "T" case we have to take into account, if the previous command is Q/T. - d.qx = d.x * 2 - d.qx // And make a reflection similar - d.qy = d.y * 2 - d.qy // to case "S". - } else { - // or something else or nothing - d.qx = d.x - d.qy = d.y - } - path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])) - break - case 'Q': - d.qx = path[1] - d.qy = path[2] - path = ['C'].concat( - q2c(d.x, d.y, path[1], path[2], path[3], path[4]) - ) - break - case 'L': - path = ['C'].concat(l2c(d.x, d.y, path[1], path[2])) - break - case 'H': - path = ['C'].concat(l2c(d.x, d.y, path[1], d.y)) - break - case 'V': - path = ['C'].concat(l2c(d.x, d.y, d.x, path[1])) - break - case 'Z': - path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y)) - break - } - return path - }, - fixArc = function (pp, i) { - if (pp[i].length > 7) { - pp[i].shift() - var pi = pp[i] - while (pi.length) { - pcoms1[i] = 'A' // if created multiple C:s, their original seg is saved - p2 && (pcoms2[i] = 'A') // the same as above - pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))) + xy[is.x.toFixed(4)] = is.y.toFixed(4) + let t1 = + di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), + t2 = + dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t) + if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) { + if (justCount) { + res++ + } else { + res.push({ + x: is.x, + y: is.y, + t1: t1, + t2: t2 + }) } - pp.splice(i, 1) - ii = mmax(p.length, (p2 && p2.length) || 0) } - }, - fixM = function (path1, path2, a1, a2, i) { - if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M') { - path2.splice(i, 0, ['M', a2.x, a2.y]) - a1.bx = 0 - a1.by = 0 - a1.x = path1[i][1] - a1.y = path1[i][2] - ii = mmax(p.length, (p2 && p2.length) || 0) - } - }, - pcoms1 = [], // path commands of original path p - pcoms2 = [], // path commands of original path p2 - pfirst = '', // temporary holder for original path command - pcom = '' // holder for previous path command of original path - for (var i = 0, ii = mmax(p.length, (p2 && p2.length) || 0); i < ii; i++) { - p[i] && (pfirst = p[i][0]) // save current path command - - if (pfirst != 'C') { - // C is not saved yet, because it may be result of conversion - pcoms1[i] = pfirst // Save current path command - i && (pcom = pcoms1[i - 1]) // Get previous path command pcom } - p[i] = processPath(p[i], attrs, pcom) // Previous path command is inputted to processPath - - if (pcoms1[i] != 'A' && pfirst == 'C') pcoms1[i] = 'C' // A is the only command - // which may produce multiple C:s - // so we have to make sure that C is also C in original path - - fixArc(p, i) // fixArc adds also the right amount of A:s to pcoms1 - - if (p2) { - // the same procedures is done to p2 - p2[i] && (pfirst = p2[i][0]) - if (pfirst != 'C') { - pcoms2[i] = pfirst - i && (pcom = pcoms2[i - 1]) - } - p2[i] = processPath(p2[i], attrs2, pcom) - - if (pcoms2[i] != 'A' && pfirst == 'C') { - pcoms2[i] = 'C' - } - - fixArc(p2, i) - } - fixM(p, p2, attrs, attrs2, i) - fixM(p2, p, attrs2, attrs, i) - var seg = p[i], - seg2 = p2 && p2[i], - seglen = seg.length, - seg2len = p2 && seg2.length - attrs.x = seg[seglen - 2] - attrs.y = seg[seglen - 1] - attrs.bx = toFloat(seg[seglen - 4]) || attrs.x - attrs.by = toFloat(seg[seglen - 3]) || attrs.y - attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x) - attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y) - attrs2.x = p2 && seg2[seg2len - 2] - attrs2.y = p2 && seg2[seg2len - 1] } - if (!p2) { - pth.curve = pathClone(p) - } - return p2 ? [p, p2] : p } - function mapPath(path, matrix) { - if (!matrix) { - return path - } - var x, y, i, j, ii, jj, pathi - path = path2curve(path) - for (i = 0, ii = path.length; i < ii; i++) { - pathi = path[i] - for (j = 1, jj = pathi.length; j < jj; j += 2) { - x = matrix.x(pathi[j], pathi[j + 1]) - y = matrix.y(pathi[j], pathi[j + 1]) - pathi[j] = x - pathi[j + 1] = y + return res +} +function pathIntersection(path1, path2) { + return interPathHelper(path1, path2) +} +function pathIntersectionNumber(path1, path2) { + return interPathHelper(path1, path2, 1) +} +function interPathHelper(path1, path2, justCount) { + path1 = path2curve(path1) + path2 = path2curve(path2) + let x1, + y1, + x2, + y2, + x1m, + y1m, + x2m, + y2m, + bez1, + bez2, + res = justCount ? 0 : [] + for (let i = 0, ii = path1.length; i < ii; i++) { + let pi = path1[i] + if (pi[0] == 'M') { + x1 = x1m = pi[1] + y1 = y1m = pi[2] + } else { + if (pi[0] == 'C') { + bez1 = [x1, y1].concat(pi.slice(1)) + x1 = bez1[6] + y1 = bez1[7] + } else { + bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m] + x1 = x1m + y1 = y1m + } + for (let j = 0, jj = path2.length; j < jj; j++) { + let pj = path2[j] + if (pj[0] == 'M') { + x2 = x2m = pj[1] + y2 = y2m = pj[2] + } else { + if (pj[0] == 'C') { + bez2 = [x2, y2].concat(pj.slice(1)) + x2 = bez2[6] + y2 = bez2[7] + } else { + bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m] + x2 = x2m + y2 = y2m + } + let intr = interHelper(bez1, bez2, justCount) + if (justCount) { + res += intr + } else { + for (let k = 0, kk = intr.length; k < kk; k++) { + intr[k].segment1 = i + intr[k].segment2 = j + intr[k].bez1 = bez1 + intr[k].bez2 = bez2 + } + res = res.concat(intr) + } + } } } + } + return res +} +function isPointInsidePath(path, x, y) { + let bbox = pathBBox(path) + return ( + isPointInsideBBox(bbox, x, y) && + interPathHelper( + path, + [ + ['M', x, y], + ['H', bbox.x2 + 10] + ], + 1 + ) % + 2 == + 1 + ) +} +function pathBBox(path) { + let pth = paths(path) + if (pth.bbox) { + return clone(pth.bbox) + } + if (!path) { + return box() + } + path = path2curve(path) + let x = 0, + y = 0, + X = [], + Y = [], + p + for (let i = 0, ii = path.length; i < ii; i++) { + p = path[i] + if (p[0] == 'M') { + x = p[1] + y = p[2] + X.push(x) + Y.push(y) + } else { + let dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]) + X = X.concat(dim.min.x, dim.max.x) + Y = Y.concat(dim.min.y, dim.max.y) + x = p[5] + y = p[6] + } + } + let xmin = mmin.apply(0, X), + ymin = mmin.apply(0, Y), + xmax = mmax.apply(0, X), + ymax = mmax.apply(0, Y), + bb = box(xmin, ymin, xmax - xmin, ymax - ymin) + pth.bbox = clone(bb) + return bb +} +function rectPath(x, y, w, h, r) { + if (r) { + return [ + ['M', +x + +r, y], + ['l', w - r * 2, 0], + ['a', r, r, 0, 0, 1, r, r], + ['l', 0, h - r * 2], + ['a', r, r, 0, 0, 1, -r, r], + ['l', r * 2 - w, 0], + ['a', r, r, 0, 0, 1, -r, -r], + ['l', 0, r * 2 - h], + ['a', r, r, 0, 0, 1, r, -r], + ['z'] + ] + } + let res = [['M', x, y], ['l', w, 0], ['l', 0, h], ['l', -w, 0], ['z']] + res.toString = toString + return res +} +function ellipsePath(x, y, rx, ry, a) { + if (a == null && ry == null) { + ry = rx + } + x = +x + y = +y + rx = +rx + ry = +ry + if (a != null) { + let rad = Math.PI / 180, + x1 = x + rx * Math.cos(-ry * rad), + x2 = x + rx * Math.cos(-a * rad), + y1 = y + rx * Math.sin(-ry * rad), + y2 = y + rx * Math.sin(-a * rad), + res = [ + ['M', x1, y1], + ['A', rx, rx, 0, +(a - ry > 180), 0, x2, y2] + ] + } else { + res = [ + ['M', x, y], + ['m', 0, -ry], + ['a', rx, ry, 0, 1, 1, 0, 2 * ry], + ['a', rx, ry, 0, 1, 1, 0, -2 * ry], + ['z'] + ] + } + res.toString = toString + return res +} +let getPath = { + path: function (el) { + return el.attr('path') + }, + circle: function (el) { + let attr = unit2px(el) + return ellipsePath(attr.cx, attr.cy, attr.r) + }, + ellipse: function (el) { + let attr = unit2px(el) + return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry) + }, + rect: function (el) { + let attr = unit2px(el) + return rectPath( + attr.x || 0, + attr.y || 0, + attr.width, + attr.height, + attr.rx, + attr.ry + ) + }, + image: function (el) { + let attr = unit2px(el) + return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height) + }, + line: function (el) { + return ( + 'M' + + [el.attr('x1') || 0, el.attr('y1') || 0, el.attr('x2'), el.attr('y2')] + ) + }, + polyline: function (el) { + return 'M' + el.attr('points') + }, + polygon: function (el) { + return 'M' + el.attr('points') + 'z' + }, + deflt: function (el) { + let bbox = el.node.getBBox() + return rectPath(bbox.x, bbox.y, bbox.width, bbox.height) + } +} +function pathToRelative(pathArray) { + let pth = paths(pathArray), + lowerCase = String.prototype.toLowerCase + if (pth.rel) { + return pathClone(pth.rel) + } + if (!is(pathArray, 'array') || !is(pathArray && pathArray[0], 'array')) { + pathArray = Snap.parsePathString(pathArray) + } + let res = [], + x = 0, + y = 0, + mx = 0, + my = 0, + start = 0 + if (pathArray[0][0] == 'M') { + x = pathArray[0][1] + y = pathArray[0][2] + mx = x + my = y + start++ + res.push(['M', x, y]) + } + for (let i = start, ii = pathArray.length; i < ii; i++) { + let r = (res[i] = []), + pa = pathArray[i] + if (pa[0] != lowerCase.call(pa[0])) { + r[0] = lowerCase.call(pa[0]) + switch (r[0]) { + case 'a': + r[1] = pa[1] + r[2] = pa[2] + r[3] = pa[3] + r[4] = pa[4] + r[5] = pa[5] + r[6] = +(pa[6] - x).toFixed(3) + r[7] = +(pa[7] - y).toFixed(3) + break + case 'v': + r[1] = +(pa[1] - y).toFixed(3) + break + case 'm': + mx = pa[1] + my = pa[2] + default: + for (let j = 1, jj = pa.length; j < jj; j++) { + r[j] = +(pa[j] - (j % 2 ? x : y)).toFixed(3) + } + } + } else { + r = res[i] = [] + if (pa[0] == 'm') { + mx = pa[1] + x + my = pa[2] + y + } + for (let k = 0, kk = pa.length; k < kk; k++) { + res[i][k] = pa[k] + } + } + let len = res[i].length + switch (res[i][0]) { + case 'z': + x = mx + y = my + break + case 'h': + x += +res[i][len - 1] + break + case 'v': + y += +res[i][len - 1] + break + default: + x += +res[i][len - 2] + y += +res[i][len - 1] + } + } + res.toString = toString + pth.rel = pathClone(res) + return res +} +function pathToAbsolute(pathArray) { + let pth = paths(pathArray) + if (pth.abs) { + return pathClone(pth.abs) + } + if (!is(pathArray, 'array') || !is(pathArray && pathArray[0], 'array')) { + // rough assumption + pathArray = Snap.parsePathString(pathArray) + } + if (!pathArray || !pathArray.length) { + return [['M', 0, 0]] + } + let res = [], + x = 0, + y = 0, + mx = 0, + my = 0, + start = 0, + pa0 + if (pathArray[0][0] == 'M') { + x = +pathArray[0][1] + y = +pathArray[0][2] + mx = x + my = y + start++ + res[0] = ['M', x, y] + } + let crz = + pathArray.length == 3 && + pathArray[0][0] == 'M' && + pathArray[1][0].toUpperCase() == 'R' && + pathArray[2][0].toUpperCase() == 'Z' + for (let r, pa, i = start, ii = pathArray.length; i < ii; i++) { + res.push((r = [])) + pa = pathArray[i] + pa0 = pa[0] + if (pa0 != pa0.toUpperCase()) { + r[0] = pa0.toUpperCase() + switch (r[0]) { + case 'A': + r[1] = pa[1] + r[2] = pa[2] + r[3] = pa[3] + r[4] = pa[4] + r[5] = pa[5] + r[6] = +pa[6] + x + r[7] = +pa[7] + y + break + case 'V': + r[1] = +pa[1] + y + break + case 'H': + r[1] = +pa[1] + x + break + case 'R': + let dots = [x, y].concat(pa.slice(1)) + for (let j = 2, jj = dots.length; j < jj; j++) { + dots[j] = +dots[j] + x + dots[++j] = +dots[j] + y + } + res.pop() + res = res.concat(catmullRom2bezier(dots, crz)) + break + case 'O': + res.pop() + dots = ellipsePath(x, y, pa[1], pa[2]) + dots.push(dots[0]) + res = res.concat(dots) + break + case 'U': + res.pop() + res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) + r = ['U'].concat(res[res.length - 1].slice(-2)) + break + case 'M': + mx = +pa[1] + x + my = +pa[2] + y + default: + for (j = 1, jj = pa.length; j < jj; j++) { + r[j] = +pa[j] + (j % 2 ? x : y) + } + } + } else if (pa0 == 'R') { + dots = [x, y].concat(pa.slice(1)) + res.pop() + res = res.concat(catmullRom2bezier(dots, crz)) + r = ['R'].concat(pa.slice(-2)) + } else if (pa0 == 'O') { + res.pop() + dots = ellipsePath(x, y, pa[1], pa[2]) + dots.push(dots[0]) + res = res.concat(dots) + } else if (pa0 == 'U') { + res.pop() + res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3])) + r = ['U'].concat(res[res.length - 1].slice(-2)) + } else { + for (let k = 0, kk = pa.length; k < kk; k++) { + r[k] = pa[k] + } + } + pa0 = pa0.toUpperCase() + if (pa0 != 'O') { + switch (r[0]) { + case 'Z': + x = +mx + y = +my + break + case 'H': + x = r[1] + break + case 'V': + y = r[1] + break + case 'M': + mx = r[r.length - 2] + my = r[r.length - 1] + default: + x = r[r.length - 2] + y = r[r.length - 1] + } + } + } + res.toString = toString + pth.abs = pathClone(res) + return res +} +function l2c(x1, y1, x2, y2) { + return [x1, y1, x2, y2, x2, y2] +} +function q2c(x1, y1, ax, ay, x2, y2) { + let _13 = 1 / 3, + _23 = 2 / 3 + return [ + _13 * x1 + _23 * ax, + _13 * y1 + _23 * ay, + _13 * x2 + _23 * ax, + _13 * y2 + _23 * ay, + x2, + y2 + ] +} +function a2c( + x1, + y1, + rx, + ry, + angle, + large_arc_flag, + sweep_flag, + x2, + y2, + recursive +) { + // for more information of where this math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + let _120 = (PI * 120) / 180, + rad = (PI / 180) * (+angle || 0), + res = [], + xy, + rotate = cacher(function (x, y, rad) { + let X = x * math.cos(rad) - y * math.sin(rad), + Y = x * math.sin(rad) + y * math.cos(rad) + return { x: X, y: Y } + }) + if (!rx || !ry) { + return [x1, y1, x2, y2, x2, y2] + } + if (!recursive) { + xy = rotate(x1, y1, -rad) + x1 = xy.x + y1 = xy.y + xy = rotate(x2, y2, -rad) + x2 = xy.x + y2 = xy.y + let cos = math.cos((PI / 180) * angle), + sin = math.sin((PI / 180) * angle), + x = (x1 - x2) / 2, + y = (y1 - y2) / 2 + let h = (x * x) / (rx * rx) + (y * y) / (ry * ry) + if (h > 1) { + h = math.sqrt(h) + rx = h * rx + ry = h * ry + } + let rx2 = rx * rx, + ry2 = ry * ry, + k = + (large_arc_flag == sweep_flag ? -1 : 1) * + math.sqrt( + abs( + (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / + (rx2 * y * y + ry2 * x * x) + ) + ), + cx = (k * rx * y) / ry + (x1 + x2) / 2, + cy = (k * -ry * x) / rx + (y1 + y2) / 2, + f1 = math.asin(((y1 - cy) / ry).toFixed(9)), + f2 = math.asin(((y2 - cy) / ry).toFixed(9)) + + f1 = x1 < cx ? PI - f1 : f1 + f2 = x2 < cx ? PI - f2 : f2 + f1 < 0 && (f1 = PI * 2 + f1) + f2 < 0 && (f2 = PI * 2 + f2) + if (sweep_flag && f1 > f2) { + f1 = f1 - PI * 2 + } + if (!sweep_flag && f2 > f1) { + f2 = f2 - PI * 2 + } + } else { + f1 = recursive[0] + f2 = recursive[1] + cx = recursive[2] + cy = recursive[3] + } + let df = f2 - f1 + if (abs(df) > _120) { + let f2old = f2, + x2old = x2, + y2old = y2 + f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1) + x2 = cx + rx * math.cos(f2) + y2 = cy + ry * math.sin(f2) + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [ + f2, + f2old, + cx, + cy + ]) + } + df = f2 - f1 + let c1 = math.cos(f1), + s1 = math.sin(f1), + c2 = math.cos(f2), + s2 = math.sin(f2), + t = math.tan(df / 4), + hx = (4 / 3) * rx * t, + hy = (4 / 3) * ry * t, + m1 = [x1, y1], + m2 = [x1 + hx * s1, y1 - hy * c1], + m3 = [x2 + hx * s2, y2 - hy * c2], + m4 = [x2, y2] + m2[0] = 2 * m1[0] - m2[0] + m2[1] = 2 * m1[1] - m2[1] + if (recursive) { + return [m2, m3, m4].concat(res) + } else { + res = [m2, m3, m4].concat(res).join().split(',') + let newres = [] + for (let i = 0, ii = res.length; i < ii; i++) { + newres[i] = + i % 2 + ? rotate(res[i - 1], res[i], rad).y + : rotate(res[i], res[i + 1], rad).x + } + return newres + } +} +function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + let t1 = 1 - t + return { + x: + pow(t1, 3) * p1x + + pow(t1, 2) * 3 * t * c1x + + t1 * 3 * t * t * c2x + + pow(t, 3) * p2x, + y: + pow(t1, 3) * p1y + + pow(t1, 2) * 3 * t * c1y + + t1 * 3 * t * t * c2y + + pow(t, 3) * p2y + } +} + +// Returns bounding box of cubic bezier curve. +// Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html +// Original version: NISHIO Hirokazu +// Modifications: https://github.com/timo22345 +function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { + let tvalues = [], + bounds = [[], []], + a, + b, + c, + t, + t1, + t2, + b2ac, + sqrtb2ac + for (let i = 0; i < 2; ++i) { + if (i == 0) { + b = 6 * x0 - 12 * x1 + 6 * x2 + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3 + c = 3 * x1 - 3 * x0 + } else { + b = 6 * y0 - 12 * y1 + 6 * y2 + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3 + c = 3 * y1 - 3 * y0 + } + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue + } + t = -c / b + if (0 < t && t < 1) { + tvalues.push(t) + } + continue + } + b2ac = b * b - 4 * c * a + sqrtb2ac = math.sqrt(b2ac) + if (b2ac < 0) { + continue + } + t1 = (-b + sqrtb2ac) / (2 * a) + if (0 < t1 && t1 < 1) { + tvalues.push(t1) + } + t2 = (-b - sqrtb2ac) / (2 * a) + if (0 < t2 && t2 < 1) { + tvalues.push(t2) + } + } + + let x, + y, + j = tvalues.length, + jlen = j, + mt + while (j--) { + t = tvalues[j] + mt = 1 - t + bounds[0][j] = + mt * mt * mt * x0 + + 3 * mt * mt * t * x1 + + 3 * mt * t * t * x2 + + t * t * t * x3 + bounds[1][j] = + mt * mt * mt * y0 + + 3 * mt * mt * t * y1 + + 3 * mt * t * t * y2 + + t * t * t * y3 + } + + bounds[0][jlen] = x0 + bounds[1][jlen] = y0 + bounds[0][jlen + 1] = x3 + bounds[1][jlen + 1] = y3 + bounds[0].length = bounds[1].length = jlen + 2 + + return { + min: { x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1]) }, + max: { x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1]) } + } +} + +function path2curve(path, path2) { + let pth = !path2 && paths(path) + if (!path2 && pth.curve) { + return pathClone(pth.curve) + } + let p = pathToAbsolute(path), + p2 = path2 && pathToAbsolute(path2), + attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + processPath = function (path, d, pcom) { + let nx, ny + if (!path) { + return ['C', d.x, d.y, d.x, d.y, d.x, d.y] + } + !(path[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null) + switch (path[0]) { + case 'M': + d.X = path[1] + d.Y = path[2] + break + case 'A': + path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))) + break + case 'S': + if (pcom == 'C' || pcom == 'S') { + // In "S" case we have to take into account, if the previous command is C/S. + nx = d.x * 2 - d.bx // And reflect the previous + ny = d.y * 2 - d.by // command's control point relative to the current point. + } else { + // or some else or nothing + nx = d.x + ny = d.y + } + path = ['C', nx, ny].concat(path.slice(1)) + break + case 'T': + if (pcom == 'Q' || pcom == 'T') { + // In "T" case we have to take into account, if the previous command is Q/T. + d.qx = d.x * 2 - d.qx // And make a reflection similar + d.qy = d.y * 2 - d.qy // to case "S". + } else { + // or something else or nothing + d.qx = d.x + d.qy = d.y + } + path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])) + break + case 'Q': + d.qx = path[1] + d.qy = path[2] + path = ['C'].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4])) + break + case 'L': + path = ['C'].concat(l2c(d.x, d.y, path[1], path[2])) + break + case 'H': + path = ['C'].concat(l2c(d.x, d.y, path[1], d.y)) + break + case 'V': + path = ['C'].concat(l2c(d.x, d.y, d.x, path[1])) + break + case 'Z': + path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y)) + break + } + return path + }, + fixArc = function (pp, i) { + if (pp[i].length > 7) { + pp[i].shift() + let pi = pp[i] + while (pi.length) { + pcoms1[i] = 'A' // if created multiple C:s, their original seg is saved + p2 && (pcoms2[i] = 'A') // the same as above + pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))) + } + pp.splice(i, 1) + ii = mmax(p.length, (p2 && p2.length) || 0) + } + }, + fixM = function (path1, path2, a1, a2, i) { + if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M') { + path2.splice(i, 0, ['M', a2.x, a2.y]) + a1.bx = 0 + a1.by = 0 + a1.x = path1[i][1] + a1.y = path1[i][2] + ii = mmax(p.length, (p2 && p2.length) || 0) + } + }, + pcoms1 = [], // path commands of original path p + pcoms2 = [], // path commands of original path p2 + pfirst = '', // temporary holder for original path command + pcom = '' // holder for previous path command of original path + for (let i = 0, ii = mmax(p.length, (p2 && p2.length) || 0); i < ii; i++) { + p[i] && (pfirst = p[i][0]) // save current path command + + if (pfirst != 'C') { + // C is not saved yet, because it may be result of conversion + pcoms1[i] = pfirst // Save current path command + i && (pcom = pcoms1[i - 1]) // Get previous path command pcom + } + p[i] = processPath(p[i], attrs, pcom) // Previous path command is inputted to processPath + + if (pcoms1[i] != 'A' && pfirst == 'C') pcoms1[i] = 'C' // A is the only command + // which may produce multiple C:s + // so we have to make sure that C is also C in original path + + fixArc(p, i) // fixArc adds also the right amount of A:s to pcoms1 + + if (p2) { + // the same procedures is done to p2 + p2[i] && (pfirst = p2[i][0]) + if (pfirst != 'C') { + pcoms2[i] = pfirst + i && (pcom = pcoms2[i - 1]) + } + p2[i] = processPath(p2[i], attrs2, pcom) + + if (pcoms2[i] != 'A' && pfirst == 'C') { + pcoms2[i] = 'C' + } + + fixArc(p2, i) + } + fixM(p, p2, attrs, attrs2, i) + fixM(p2, p, attrs2, attrs, i) + let seg = p[i], + seg2 = p2 && p2[i], + seglen = seg.length, + seg2len = p2 && seg2.length + attrs.x = seg[seglen - 2] + attrs.y = seg[seglen - 1] + attrs.bx = toFloat(seg[seglen - 4]) || attrs.x + attrs.by = toFloat(seg[seglen - 3]) || attrs.y + attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x) + attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y) + attrs2.x = p2 && seg2[seg2len - 2] + attrs2.y = p2 && seg2[seg2len - 1] + } + if (!p2) { + pth.curve = pathClone(p) + } + return p2 ? [p, p2] : p +} +function mapPath(path, matrix) { + if (!matrix) { return path } - - // http://schepers.cc/getting-to-the-point - function catmullRom2bezier(crp, z) { - var d = [] - for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { - var p = [ - { x: +crp[i - 2], y: +crp[i - 1] }, - { x: +crp[i], y: +crp[i + 1] }, - { x: +crp[i + 2], y: +crp[i + 3] }, - { x: +crp[i + 4], y: +crp[i + 5] } - ] - if (z) { - if (!i) { - p[0] = { x: +crp[iLen - 2], y: +crp[iLen - 1] } - } else if (iLen - 4 == i) { - p[3] = { x: +crp[0], y: +crp[1] } - } else if (iLen - 2 == i) { - p[2] = { x: +crp[0], y: +crp[1] } - p[3] = { x: +crp[2], y: +crp[3] } - } - } else { - if (iLen - 4 == i) { - p[3] = p[2] - } else if (!i) { - p[0] = { x: +crp[i], y: +crp[i + 1] } - } - } - d.push([ - 'C', - (-p[0].x + 6 * p[1].x + p[2].x) / 6, - (-p[0].y + 6 * p[1].y + p[2].y) / 6, - (p[1].x + 6 * p[2].x - p[3].x) / 6, - (p[1].y + 6 * p[2].y - p[3].y) / 6, - p[2].x, - p[2].y - ]) + let x, y, i, j, ii, jj, pathi + path = path2curve(path) + for (i = 0, ii = path.length; i < ii; i++) { + pathi = path[i] + for (j = 1, jj = pathi.length; j < jj; j += 2) { + x = matrix.x(pathi[j], pathi[j + 1]) + y = matrix.y(pathi[j], pathi[j + 1]) + pathi[j] = x + pathi[j + 1] = y } + } + return path +} - return d +// http://schepers.cc/getting-to-the-point +function catmullRom2bezier(crp, z) { + let d = [] + for (let i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { + let p = [ + { x: +crp[i - 2], y: +crp[i - 1] }, + { x: +crp[i], y: +crp[i + 1] }, + { x: +crp[i + 2], y: +crp[i + 3] }, + { x: +crp[i + 4], y: +crp[i + 5] } + ] + if (z) { + if (!i) { + p[0] = { x: +crp[iLen - 2], y: +crp[iLen - 1] } + } else if (iLen - 4 == i) { + p[3] = { x: +crp[0], y: +crp[1] } + } else if (iLen - 2 == i) { + p[2] = { x: +crp[0], y: +crp[1] } + p[3] = { x: +crp[2], y: +crp[3] } + } + } else { + if (iLen - 4 == i) { + p[3] = p[2] + } else if (!i) { + p[0] = { x: +crp[i], y: +crp[i + 1] } + } + } + d.push([ + 'C', + (-p[0].x + 6 * p[1].x + p[2].x) / 6, + (-p[0].y + 6 * p[1].y + p[2].y) / 6, + (p[1].x + 6 * p[2].x - p[3].x) / 6, + (p[1].y + 6 * p[2].y - p[3].y) / 6, + p[2].x, + p[2].y + ]) } - // export - Snap.path = paths + return d +} - /*\ +// export +Snap.path = paths + +/*\ * Snap.path.getTotalLength [ method ] ** @@ -1301,8 +1282,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { ** = (number) length \*/ - Snap.path.getTotalLength = getTotalLength - /*\ +Snap.path.getTotalLength = getTotalLength +/*\ * Snap.path.getPointAtLength [ method ] ** @@ -1318,8 +1299,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o alpha: (number) angle of derivative o } \*/ - Snap.path.getPointAtLength = getPointAtLength - /*\ +Snap.path.getPointAtLength = getPointAtLength +/*\ * Snap.path.getSubpath [ method ] ** @@ -1331,28 +1312,28 @@ Snap.plugin(function (Snap, Element, Paper, glob) { ** = (string) path string definition for the segment \*/ - Snap.path.getSubpath = function (path, from, to) { - if (this.getTotalLength(path) - to < 1e-6) { - return getSubpathsAtLength(path, from).end - } - var a = getSubpathsAtLength(path, to, 1) - return from ? getSubpathsAtLength(a, from).end : a +Snap.path.getSubpath = function (path, from, to) { + if (this.getTotalLength(path) - to < 1e-6) { + return getSubpathsAtLength(path, from).end } - /*\ - * Element.getTotalLength + let a = getSubpathsAtLength(path, to, 1) + return from ? getSubpathsAtLength(a, from).end : a +} +/*\ + * SnapElement.getTotalLength [ method ] ** * Returns the length of the path in pixels (only works for `path` elements) = (number) length \*/ - elproto.getTotalLength = function () { - if (this.node.getTotalLength) { - return this.node.getTotalLength() - } +elproto.getTotalLength = function () { + if (this.node.getTotalLength) { + return this.node.getTotalLength() } - // SIERRA Element.getPointAtLength()/Element.getTotalLength(): If a is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length? - /*\ - * Element.getPointAtLength +} +// SIERRA SnapElement.getPointAtLength()/SnapElement.getTotalLength(): If a is broken into different segments, is the jump distance to the new coordinates set by the _M_ or _m_ commands calculated as part of the path's total length? +/*\ + * SnapElement.getPointAtLength [ method ] ** * Returns coordinates of the point located at the given length on the given path (only works for `path` elements) @@ -1366,12 +1347,12 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o alpha: (number) angle of derivative o } \*/ - elproto.getPointAtLength = function (length) { - return getPointAtLength(this.attr('d'), length) - } - // SIERRA Element.getSubpath(): Similar to the problem for Element.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear. - /*\ - * Element.getSubpath +elproto.getPointAtLength = function (length) { + return getPointAtLength(this.attr('d'), length) +} +// SIERRA SnapElement.getSubpath(): Similar to the problem for SnapElement.getPointAtLength(). Unclear how this would work for a segmented path. Overall, the concept of _subpath_ and what I'm calling a _segment_ (series of non-_M_ or _Z_ commands) is unclear. +/*\ + * SnapElement.getSubpath [ method ] ** * Returns subpath of a given element from given start and end lengths (only works for `path` elements) @@ -1381,11 +1362,11 @@ Snap.plugin(function (Snap, Element, Paper, glob) { ** = (string) path string definition for the segment \*/ - elproto.getSubpath = function (from, to) { - return Snap.path.getSubpath(this.attr('d'), from, to) - } - Snap._.box = box - /*\ +elproto.getSubpath = function (from, to) { + return Snap.path.getSubpath(this.attr('d'), from, to) +} +Snap._.box = box +/*\ * Snap.path.findDotsAtSegment [ method ] ** @@ -1424,8 +1405,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o alpha: (number) angle of the curve derivative at the point o } \*/ - Snap.path.findDotsAtSegment = findDotsAtSegment - /*\ +Snap.path.findDotsAtSegment = findDotsAtSegment +/*\ * Snap.path.bezierBBox [ method ] ** @@ -1452,8 +1433,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o height: (number) height of the box o } \*/ - Snap.path.bezierBBox = bezierBBox - /*\ +Snap.path.bezierBBox = bezierBBox +/*\ * Snap.path.isPointInsideBBox [ method ] ** @@ -1465,55 +1446,55 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - y (string) y coordinate of the point = (boolean) `true` if point is inside \*/ - Snap.path.isPointInsideBBox = isPointInsideBBox - Snap.closest = function (x, y, X, Y) { - var r = 100, - b = box(x - r / 2, y - r / 2, r, r), - inside = [], - getter = X[0].hasOwnProperty('x') - ? function (i) { - return { - x: X[i].x, - y: X[i].y - } +Snap.path.isPointInsideBBox = isPointInsideBBox +Snap.closest = function (x, y, X, Y) { + let r = 100, + b = box(x - r / 2, y - r / 2, r, r), + inside = [], + getter = X[0].hasOwnProperty('x') + ? function (i) { + return { + x: X[i].x, + y: X[i].y } - : function (i) { - return { - x: X[i], - y: Y[i] - } - }, - found = 0 - while (r <= 1e6 && !found) { - for (var i = 0, ii = X.length; i < ii; i++) { - var xy = getter(i) - if (isPointInsideBBox(b, xy.x, xy.y)) { - found++ - inside.push(xy) - break } - } - if (!found) { - r *= 2 - b = box(x - r / 2, y - r / 2, r, r) + : function (i) { + return { + x: X[i], + y: Y[i] + } + }, + found = 0 + while (r <= 1e6 && !found) { + for (let i = 0, ii = X.length; i < ii; i++) { + let xy = getter(i) + if (isPointInsideBBox(b, xy.x, xy.y)) { + found++ + inside.push(xy) + break } } - if (r == 1e6) { - return + if (!found) { + r *= 2 + b = box(x - r / 2, y - r / 2, r, r) } - var len = Infinity, - res - for (i = 0, ii = inside.length; i < ii; i++) { - var l = Snap.len(x, y, inside[i].x, inside[i].y) - if (len > l) { - len = l - inside[i].len = l - res = inside[i] - } - } - return res } - /*\ + if (r == 1e6) { + return + } + let len = Infinity, + res + for (i = 0, ii = inside.length; i < ii; i++) { + let l = Snap.len(x, y, inside[i].x, inside[i].y) + if (len > l) { + len = l + inside[i].len = l + res = inside[i] + } + } + return res +} +/*\ * Snap.path.isBBoxIntersect [ method ] ** @@ -1524,8 +1505,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - bbox2 (string) second bounding box = (boolean) `true` if bounding boxes intersect \*/ - Snap.path.isBBoxIntersect = isBBoxIntersect - /*\ +Snap.path.isBBoxIntersect = isBBoxIntersect +/*\ * Snap.path.intersection [ method ] ** @@ -1548,9 +1529,9 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o } o ] \*/ - Snap.path.intersection = pathIntersection - Snap.path.intersectionNumber = pathIntersectionNumber - /*\ +Snap.path.intersection = pathIntersection +Snap.path.intersectionNumber = pathIntersectionNumber +/*\ * Snap.path.isPointInside [ method ] ** @@ -1564,8 +1545,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - y (number) y of the point = (boolean) `true` if point is inside the path \*/ - Snap.path.isPointInside = isPointInsidePath - /*\ +Snap.path.isPointInside = isPointInsidePath +/*\ * Snap.path.getBBox [ method ] ** @@ -1583,9 +1564,9 @@ Snap.plugin(function (Snap, Element, Paper, glob) { o height: (number) height of the box o } \*/ - Snap.path.getBBox = pathBBox - Snap.path.get = getPath - /*\ +Snap.path.getBBox = pathBBox +Snap.path.get = getPath +/*\ * Snap.path.toRelative [ method ] ** @@ -1595,8 +1576,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - path (string) path string = (array) path string \*/ - Snap.path.toRelative = pathToRelative - /*\ +Snap.path.toRelative = pathToRelative +/*\ * Snap.path.toAbsolute [ method ] ** @@ -1606,8 +1587,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - path (string) path string = (array) path string \*/ - Snap.path.toAbsolute = pathToAbsolute - /*\ +Snap.path.toAbsolute = pathToAbsolute +/*\ * Snap.path.toCubic [ method ] ** @@ -1617,8 +1598,8 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - pathString (string|array) path string or array of segments = (array) array of segments \*/ - Snap.path.toCubic = path2curve - /*\ +Snap.path.toCubic = path2curve +/*\ * Snap.path.map [ method ] ** @@ -1627,7 +1608,6 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - matrix (object) see @Matrix = (string) transformed path string \*/ - Snap.path.map = mapPath - Snap.path.toString = toString - Snap.path.clone = pathClone -}) +Snap.path.map = mapPath +Snap.path.toString = toString +Snap.path.clone = pathClone diff --git a/src/set.js b/src/set.js index 92f6f9d..530e078 100644 --- a/src/set.js +++ b/src/set.js @@ -12,60 +12,60 @@ // See the License for the specific language governing permissions and // limitations under the License. import eve from './eve.js' -import { Snap } from './svg.js' +import { Snap, SnapElement, Paper, Fragment } from './svg.js' import mina from './mina.js' +import { is } from './utils.js' -Snap.plugin(function (Snap, Element, Paper, glob) { - var mmax = Math.max, - mmin = Math.min +let mmax = Math.max, + mmin = Math.min - // Set - var Set = function (items) { - this.items = [] - this.bindings = {} - this.length = 0 - this.type = 'set' - if (items) { - for (var i = 0, ii = items.length; i < ii; i++) { - if (items[i]) { - this[this.items.length] = this.items[this.items.length] = items[i] - this.length++ - } +// Set +let Set = function (items) { + this.items = [] + this.bindings = {} + this.length = 0 + this.type = 'set' + if (items) { + for (let i = 0, ii = items.length; i < ii; i++) { + if (items[i]) { + this[this.items.length] = this.items[this.items.length] = items[i] + this.length++ } } - }, - setproto = Set.prototype - /*\ + } + }, + setproto = Set.prototype +/*\ * Set.push [ method ] ** * Adds each argument to the current set = (object) original element \*/ - setproto.push = function () { - var item, len - for (var i = 0, ii = arguments.length; i < ii; i++) { - item = arguments[i] - if (item) { - len = this.items.length - this[len] = this.items[len] = item - this.length++ - } +setproto.push = function () { + let item, len + for (let i = 0, ii = arguments.length; i < ii; i++) { + item = arguments[i] + if (item) { + len = this.items.length + this[len] = this.items[len] = item + this.length++ } - return this } - /*\ + return this +} +/*\ * Set.pop [ method ] ** * Removes last element and returns it = (object) element \*/ - setproto.pop = function () { - this.length && delete this[this.length--] - return this.items.pop() - } - /*\ +setproto.pop = function () { + this.length && delete this[this.length--] + return this.items.pop() +} +/*\ * Set.forEach [ method ] ** @@ -77,15 +77,15 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - thisArg (object) context object for the callback = (object) Set object \*/ - setproto.forEach = function (callback, thisArg) { - for (var i = 0, ii = this.items.length; i < ii; i++) { - if (callback.call(thisArg, this.items[i], i) === false) { - return this - } +setproto.forEach = function (callback, thisArg) { + for (let i = 0, ii = this.items.length; i < ii; i++) { + if (callback.call(thisArg, this.items[i], i) === false) { + return this } - return this } - /*\ + return this +} +/*\ * Set.animate [ method ] ** @@ -104,50 +104,50 @@ Snap.plugin(function (Snap, Element, Paper, glob) { | // or | // animate first element to radius 10, but second to radius 20 and in different time | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); - = (Element) the current element + = (SnapElement) the current element \*/ - setproto.animate = function (attrs, ms, easing, callback) { - if (typeof easing == 'function' && !easing.length) { - callback = easing - easing = mina.linear - } - if (attrs instanceof Snap._.Animation) { - callback = attrs.callback - easing = attrs.easing - ms = easing.dur - attrs = attrs.attr - } - var args = arguments - if (Snap.is(attrs, 'array') && Snap.is(args[args.length - 1], 'array')) { - var each = true - } - var begin, - handler = function () { - if (begin) { - this.b = begin - } else { - begin = this.b - } - }, - cb = 0, - set = this, - callbacker = - callback && - function () { - if (++cb == set.length) { - callback.call(this) - } - } - return this.forEach(function (el, i) { - eve.once('snap.animcreated.' + el.id, handler) - if (each) { - args[i] && el.animate.apply(el, args[i]) - } else { - el.animate(attrs, ms, easing, callbacker) - } - }) +setproto.animate = function (attrs, ms, easing, callback) { + if (typeof easing == 'function' && !easing.length) { + callback = easing + easing = mina.linear } - /*\ + if (attrs instanceof Snap._.Animation) { + callback = attrs.callback + easing = attrs.easing + ms = easing.dur + attrs = attrs.attr + } + let args = arguments + if (is(attrs, 'array') && is(args[args.length - 1], 'array')) { + let each = true + } + let begin, + handler = function () { + if (begin) { + this.b = begin + } else { + begin = this.b + } + }, + cb = 0, + set = this, + callbacker = + callback && + function () { + if (++cb == set.length) { + callback.call(this) + } + } + return this.forEach(function (el, i) { + eve.once('snap.animcreated.' + el.id, handler) + if (each) { + args[i] && el.animate.apply(el, args[i]) + } else { + el.animate(attrs, ms, easing, callbacker) + } + }) +} +/*\ * Set.remove [ method ] ** @@ -155,13 +155,13 @@ Snap.plugin(function (Snap, Element, Paper, glob) { * = (object) Set object \*/ - setproto.remove = function () { - while (this.length) { - this.pop().remove() - } - return this +setproto.remove = function () { + while (this.length) { + this.pop().remove() } - /*\ + return this +} +/*\ * Set.bind [ method ] ** @@ -173,59 +173,59 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - callback (function) function to run * or - attr (string) attribute name - - element (Element) specific element in the set to apply the attribute to + - element (SnapElement) specific element in the set to apply the attribute to * or - attr (string) attribute name - - element (Element) specific element in the set to apply the attribute to + - element (SnapElement) specific element in the set to apply the attribute to - eattr (string) attribute on the element to bind the attribute to = (object) Set object \*/ - setproto.bind = function (attr, a, b) { - var data = {} - if (typeof a == 'function') { - this.bindings[attr] = a - } else { - var aname = b || attr - this.bindings[attr] = function (v) { - data[aname] = v - a.attr(data) - } +setproto.bind = function (attr, a, b) { + let data = {} + if (typeof a == 'function') { + this.bindings[attr] = a + } else { + let aname = b || attr + this.bindings[attr] = function (v) { + data[aname] = v + a.attr(data) } - return this } - /*\ + return this +} +/*\ * Set.attr [ method ] ** - * Equivalent of @Element.attr. + * Equivalent of @SnapElement.attr. = (object) Set object \*/ - setproto.attr = function (value) { - var unbound = {} - for (var k in value) { - if (this.bindings[k]) { - this.bindings[k](value[k]) - } else { - unbound[k] = value[k] - } +setproto.attr = function (value) { + let unbound = {} + for (let k in value) { + if (this.bindings[k]) { + this.bindings[k](value[k]) + } else { + unbound[k] = value[k] } - for (var i = 0, ii = this.items.length; i < ii; i++) { - this.items[i].attr(unbound) - } - return this } - /*\ + for (let i = 0, ii = this.items.length; i < ii; i++) { + this.items[i].attr(unbound) + } + return this +} +/*\ * Set.clear [ method ] ** * Removes all elements from the set \*/ - setproto.clear = function () { - while (this.length) { - this.pop() - } +setproto.clear = function () { + while (this.length) { + this.pop() } - /*\ +} +/*\ * Set.splice [ method ] ** @@ -236,34 +236,34 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - insertion… (object) #optional elements to insert = (object) set elements that were deleted \*/ - setproto.splice = function (index, count, insertion) { - index = index < 0 ? mmax(this.length + index, 0) : index - count = mmax(0, mmin(this.length - index, count)) - var tail = [], - todel = [], - args = [], - i - for (i = 2; i < arguments.length; i++) { - args.push(arguments[i]) - } - for (i = 0; i < count; i++) { - todel.push(this[index + i]) - } - for (; i < this.length - index; i++) { - tail.push(this[index + i]) - } - var arglen = args.length - for (i = 0; i < arglen + tail.length; i++) { - this.items[index + i] = this[index + i] = - i < arglen ? args[i] : tail[i - arglen] - } - i = this.items.length = this.length -= count - arglen - while (this[i]) { - delete this[i++] - } - return new Set(todel) +setproto.splice = function (index, count, insertion) { + index = index < 0 ? mmax(this.length + index, 0) : index + count = mmax(0, mmin(this.length - index, count)) + let tail = [], + todel = [], + args = [], + i + for (i = 2; i < arguments.length; i++) { + args.push(arguments[i]) } - /*\ + for (i = 0; i < count; i++) { + todel.push(this[index + i]) + } + for (; i < this.length - index; i++) { + tail.push(this[index + i]) + } + let arglen = args.length + for (i = 0; i < arglen + tail.length; i++) { + this.items[index + i] = this[index + i] = + i < arglen ? args[i] : tail[i - arglen] + } + i = this.items.length = this.length -= count - arglen + while (this[i]) { + delete this[i++] + } + return new Set(todel) +} +/*\ * Set.exclude [ method ] ** @@ -272,15 +272,15 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - element (object) element to remove = (boolean) `true` if object was found and removed from the set \*/ - setproto.exclude = function (el) { - for (var i = 0, ii = this.length; i < ii; i++) - if (this[i] == el) { - this.splice(i, 1) - return true - } - return false - } - /*\ +setproto.exclude = function (el) { + for (let i = 0, ii = this.length; i < ii; i++) + if (this[i] == el) { + this.splice(i, 1) + return true + } + return false +} +/*\ * Set.insertAfter [ method ] ** @@ -289,49 +289,49 @@ Snap.plugin(function (Snap, Element, Paper, glob) { - element (object) set will be inserted after this element = (object) Set object \*/ - setproto.insertAfter = function (el) { - var i = this.items.length - while (i--) { - this.items[i].insertAfter(el) - } - return this +setproto.insertAfter = function (el) { + let i = this.items.length + while (i--) { + this.items[i].insertAfter(el) } - /*\ + return this +} +/*\ * Set.getBBox [ method ] ** - * Union of all bboxes of the set. See @Element.getBBox. - = (object) bounding box descriptor. See @Element.getBBox. + * Union of all bboxes of the set. See @SnapElement.getBBox. + = (object) bounding box descriptor. See @SnapElement.getBBox. \*/ - setproto.getBBox = function () { - var x = [], - y = [], - x2 = [], - y2 = [] - for (var i = this.items.length; i--; ) - if (!this.items[i].removed) { - var box = this.items[i].getBBox() - x.push(box.x) - y.push(box.y) - x2.push(box.x + box.width) - y2.push(box.y + box.height) - } - x = mmin.apply(0, x) - y = mmin.apply(0, y) - x2 = mmax.apply(0, x2) - y2 = mmax.apply(0, y2) - return { - x: x, - y: y, - x2: x2, - y2: y2, - width: x2 - x, - height: y2 - y, - cx: x + (x2 - x) / 2, - cy: y + (y2 - y) / 2 +setproto.getBBox = function () { + let x = [], + y = [], + x2 = [], + y2 = [] + for (let i = this.items.length; i--; ) + if (!this.items[i].removed) { + let box = this.items[i].getBBox() + x.push(box.x) + y.push(box.y) + x2.push(box.x + box.width) + y2.push(box.y + box.height) } + x = mmin.apply(0, x) + y = mmin.apply(0, y) + x2 = mmax.apply(0, x2) + y2 = mmax.apply(0, y2) + return { + x: x, + y: y, + x2: x2, + y2: y2, + width: x2 - x, + height: y2 - y, + cx: x + (x2 - x) / 2, + cy: y + (y2 - y) / 2 } - /*\ +} +/*\ * Set.insertAfter [ method ] ** @@ -339,41 +339,40 @@ Snap.plugin(function (Snap, Element, Paper, glob) { ** = (object) New Set object \*/ - setproto.clone = function (s) { - s = new Set() - for (var i = 0, ii = this.items.length; i < ii; i++) { - s.push(this.items[i].clone()) - } - return s +setproto.clone = function (s) { + s = new Set() + for (let i = 0, ii = this.items.length; i < ii; i++) { + s.push(this.items[i].clone()) } - setproto.toString = function () { - return 'Snap\u2018s set' - } - setproto.type = 'set' - // export - /*\ + return s +} +setproto.toString = function () { + return 'Snap\u2018s set' +} +setproto.type = 'set' +// export +/*\ * Snap.Set [ property ] ** * Set constructor. \*/ - Snap.Set = Set - /*\ +Snap.Set = Set +/*\ * Snap.set [ method ] ** * Creates a set and fills it with list of arguments. ** = (object) New Set object - | var r = paper.rect(0, 0, 10, 10), + | let r = paper.rect(0, 0, 10, 10), | s1 = Snap.set(), // empty set | s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set \*/ - Snap.set = function () { - var set = new Set() - if (arguments.length) { - set.push.apply(set, Array.prototype.slice.call(arguments, 0)) - } - return set +Snap.set = function () { + let set = new Set() + if (arguments.length) { + set.push.apply(set, Array.prototype.slice.call(arguments, 0)) } -}) + return set +} diff --git a/src/svg.js b/src/svg.js index 4708651..663fb5a 100644 --- a/src/svg.js +++ b/src/svg.js @@ -13,9 +13,68 @@ // limitations under the License. import eve from './eve.js' +import { + doc, + win, + CSS_ATTR, + xmlns, + T_COMMAND, + PATH_VALUES +} from './lib/constants.js' + +import { + $, + is, + uuid, + clone, + preload, + cacher, + jsonFiller, + extend +} from './utils.js' + +let has = 'hasOwnProperty', + math = Math, + abs = math.abs, + E = '', + S = ' ', + objectToString = Object.prototype.toString, + hub = {} + +export function make(name, parent) { + let res = $(name) + parent.appendChild(res) + let el = wrap(res) + return el +} + +export function wrap(dom) { + if (!dom) { + return dom + } + if (dom instanceof SnapElement || dom instanceof Fragment) { + return dom + } + if (dom.tagName.toLowerCase() === 'svg') { + return new Paper(dom) + } + if ( + dom.tagName && + dom.tagName.toLowerCase() == 'object' && + dom.type == 'image/svg+xml' + ) { + return new Paper(dom.contentDocument.getElementsByTagName('svg')[0]) + } + return new SnapElement(dom) +} + +export class Fragment { + constructor(frag) { + this.node = frag + } +} + /*\ - * Snap - [ method ] ** * Creates a drawing surface or wraps existing SVG element. ** @@ -27,424 +86,66 @@ import eve from './eve.js' - array (array) array of elements (will return set of elements) * or - query (string) CSS query selector - = (object) @Element + = (object) @SnapElement \*/ -export function Snap(w, h) { - if (w) { - if (w.nodeType) { - return wrap(w) - } - if (is(w, 'array') && Snap.set) { - return Snap.set.apply(Snap, w) - } - if (w instanceof Element) { - return w - } - if (h == null) { - try { - w = glob.doc.querySelector(String(w)) - return wrap(w) - } catch (e) { - return null - } - } - } - w = w == null ? '100%' : w - h = h == null ? '100%' : h - return new Paper(w, h) -} -Snap.toString = function () { - return 'Snap v' + this.version -} -Snap._ = {} -var glob = { - win: window, - doc: document -} -Snap._.glob = glob -var has = 'hasOwnProperty', - Str = String, - toFloat = parseFloat, - toInt = parseInt, - math = Math, - mmax = math.max, - mmin = math.min, - abs = math.abs, - pow = math.pow, - PI = math.PI, - round = math.round, - E = '', - S = ' ', - objectToString = Object.prototype.toString, - ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, - colourRegExp = - /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i, - bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, - separator = (Snap._.separator = /[,\s]+/), - whitespace = /[\s]/g, - commaSpaces = /[\s]*,[\s]*/, - hsrg = { hs: 1, rg: 1 }, - pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi, - tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/gi, - pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\s]*,?[\s]*/gi, - idgen = 0, - idprefix = 'S' + (+new Date()).toString(36), - ID = function (el) { - return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36) - }, - xlink = 'http://www.w3.org/1999/xlink', - xmlns = 'http://www.w3.org/2000/svg', - hub = {}, +export class Snap { + static _ = { $ } + /*\ - * Snap.url - [ method ] - ** - * Wraps path into `"url('')"`. - - value (string) path - = (string) wrapped path - \*/ - URL = (Snap.url = function (url) { - return "url('#" + url + "')" - }) - -function $(el, attr) { - if (attr) { - if (el == '#text') { - el = glob.doc.createTextNode(attr.text || attr['#text'] || '') + ** + * Parses SVG fragment and converts it into a @Fragment + ** +\*/ + static parse(svg) { + let f = doc.createDocumentFragment(), + full = true, + div = doc.createElement('div') + svg = String(svg) + if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { + svg = '' + svg + '' + full = false } - if (el == '#comment') { - el = glob.doc.createComment(attr.text || attr['#text'] || '') - } - if (typeof el == 'string') { - el = $(el) - } - if (typeof attr == 'string') { - if (el.nodeType == 1) { - if (attr.substring(0, 6) == 'xlink:') { - return el.getAttributeNS(xlink, attr.substring(6)) - } - if (attr.substring(0, 4) == 'xml:') { - return el.getAttributeNS(xmlns, attr.substring(4)) - } - return el.getAttribute(attr) - } else if (attr == 'text') { - return el.nodeValue + div.innerHTML = svg + svg = div.getElementsByTagName('svg')[0] + if (svg) { + if (full) { + f = svg } else { - return null + while (svg.firstChild) { + f.appendChild(svg.firstChild) + } } } - if (el.nodeType == 1) { - for (var key in attr) - if (attr[has](key)) { - var val = Str(attr[key]) - if (val) { - if (key.substring(0, 6) == 'xlink:') { - el.setAttributeNS(xlink, key.substring(6), val) - } else if (key.substring(0, 4) == 'xml:') { - el.setAttributeNS(xmlns, key.substring(4), val) - } else { - el.setAttribute(key, val) - } - } else { - el.removeAttribute(key) - } - } - } else if ('text' in attr) { - el.nodeValue = attr.text - } - } else { - el = glob.doc.createElementNS(xmlns, el) + return new Fragment(f) } - return el -} -Snap._.$ = $ -Snap._.id = ID -function getAttrs(el) { - var attrs = el.attributes, - name, - out = {} - for (var i = 0; i < attrs.length; i++) { - if (attrs[i].namespaceURI == xlink) { - name = 'xlink:' - } else { - name = '' - } - name += attrs[i].name - out[name] = attrs[i].textContent - } - return out -} -function is(o, type) { - type = Str.prototype.toLowerCase.call(type) - if (type == 'finite') { - return isFinite(o) - } - if ( - type == 'array' && - (o instanceof Array || (Array.isArray && Array.isArray(o))) - ) { - return true - } - return ( - (type == 'null' && o === null) || - (type == typeof o && o !== null) || - (type == 'object' && o === Object(o)) || - objectToString.call(o).slice(8, -1).toLowerCase() == type - ) -} -/*\ - * Snap.format - [ method ] - ** - * Replaces construction of type `{}` to the corresponding argument - ** - - token (string) string to format - - json (object) object which properties are used as a replacement - = (string) formatted string - > Usage - | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z" - | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { - | x: 10, - | y: 20, - | dim: { - | width: 40, - | height: 50, - | "negative width": -40 - | } - | })); -\*/ -Snap.format = (function () { - var tokenRegex = /\{([^\}]+)\}/g, - objNotationRegex = - /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties - replacer = function (all, key, obj) { - var res = obj - key.replace( - objNotationRegex, - function (all, name, quote, quotedName, isFunc) { - name = name || quotedName - if (res) { - if (name in res) { - res = res[name] - } - typeof res == 'function' && isFunc && (res = res()) - } - } - ) - res = (res == null || res == obj ? all : res) + '' - return res - } - return function (str, obj) { - return Str(str).replace(tokenRegex, function (all, key) { - return replacer(all, key, obj) - }) - } -})() -function clone(obj) { - if (typeof obj == 'function' || Object(obj) !== obj) { - return obj - } - var res = new obj.constructor() - for (var key in obj) - if (obj[has](key)) { - res[key] = clone(obj[key]) - } - return res -} -Snap._.clone = clone -function repush(array, item) { - for (var i = 0, ii = array.length; i < ii; i++) - if (array[i] === item) { - return array.push(array.splice(i, 1)[0]) - } -} -function cacher(f, scope, postprocessor) { - function newf() { - var arg = Array.prototype.slice.call(arguments, 0), - args = arg.join('\u2400'), - cache = (newf.cache = newf.cache || {}), - count = (newf.count = newf.count || []) - if (cache[has](args)) { - repush(count, args) - return postprocessor ? postprocessor(cache[args]) : cache[args] - } - count.length >= 1e3 && delete cache[count.shift()] - count.push(args) - cache[args] = f.apply(scope, arg) - return postprocessor ? postprocessor(cache[args]) : cache[args] - } - return newf -} -Snap._.cacher = cacher -function angle(x1, y1, x2, y2, x3, y3) { - if (x3 == null) { - var x = x1 - x2, - y = y1 - y2 - if (!x && !y) { - return 0 - } - return (180 + (math.atan2(-y, -x) * 180) / PI + 360) % 360 - } else { - return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3) - } -} -function rad(deg) { - return ((deg % 360) * PI) / 180 -} -function deg(rad) { - return ((rad * 180) / PI) % 360 -} -function x_y() { - return this.x + S + this.y -} -function x_y_w_h() { - return this.x + S + this.y + S + this.width + ' \xd7 ' + this.height -} -/*\ - * Snap.rad - [ method ] + /*\ ** - * Transform angle to radians - - deg (number) angle in degrees - = (number) angle in radians -\*/ -Snap.rad = rad -/*\ - * Snap.deg - [ method ] + * Creates a DOM fragment from a given list of elements or strings ** - * Transform angle to degrees - - rad (number) angle in radians - = (number) angle in degrees + - varargs (…) SVG string \*/ -Snap.deg = deg -/*\ - * Snap.sin - [ method ] - ** - * Equivalent to `Math.sin()` only works with degrees, not radians. - - angle (number) angle in degrees - = (number) sin -\*/ -Snap.sin = function (angle) { - return math.sin(Snap.rad(angle)) -} -/*\ - * Snap.tan - [ method ] - ** - * Equivalent to `Math.tan()` only works with degrees, not radians. - - angle (number) angle in degrees - = (number) tan -\*/ -Snap.tan = function (angle) { - return math.tan(Snap.rad(angle)) -} -/*\ - * Snap.cos - [ method ] - ** - * Equivalent to `Math.cos()` only works with degrees, not radians. - - angle (number) angle in degrees - = (number) cos -\*/ -Snap.cos = function (angle) { - return math.cos(Snap.rad(angle)) -} -/*\ - * Snap.asin - [ method ] - ** - * Equivalent to `Math.asin()` only works with degrees, not radians. - - num (number) value - = (number) asin in degrees -\*/ -Snap.asin = function (num) { - return Snap.deg(math.asin(num)) -} -/*\ - * Snap.acos - [ method ] - ** - * Equivalent to `Math.acos()` only works with degrees, not radians. - - num (number) value - = (number) acos in degrees -\*/ -Snap.acos = function (num) { - return Snap.deg(math.acos(num)) -} -/*\ - * Snap.atan - [ method ] - ** - * Equivalent to `Math.atan()` only works with degrees, not radians. - - num (number) value - = (number) atan in degrees -\*/ -Snap.atan = function (num) { - return Snap.deg(math.atan(num)) -} -/*\ - * Snap.atan2 - [ method ] - ** - * Equivalent to `Math.atan2()` only works with degrees, not radians. - - x (number) value - - y (number) value - = (number) atan2 in degrees -\*/ -Snap.atan2 = function (x, y) { - return Snap.deg(math.atan2(x, y)) -} -/*\ - * Snap.angle - [ method ] - ** - * Returns an angle between two or three points - - x1 (number) x coord of first point - - y1 (number) y coord of first point - - x2 (number) x coord of second point - - y2 (number) y coord of second point - - x3 (number) #optional x coord of third point - - y3 (number) #optional y coord of third point - = (number) angle in degrees -\*/ -Snap.angle = angle -/*\ - * Snap.len - [ method ] - ** - * Returns distance between two points - - x1 (number) x coord of first point - - y1 (number) y coord of first point - - x2 (number) x coord of second point - - y2 (number) y coord of second point - = (number) distance -\*/ -Snap.len = function (x1, y1, x2, y2) { - return Math.sqrt(Snap.len2(x1, y1, x2, y2)) -} -/*\ - * Snap.len2 - [ method ] - ** - * Returns squared distance between two points - - x1 (number) x coord of first point - - y1 (number) y coord of first point - - x2 (number) x coord of second point - - y2 (number) y coord of second point - = (number) distance -\*/ -Snap.len2 = function (x1, y1, x2, y2) { - return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) -} -/*\ - * Snap.closestPoint - [ method ] + static fragment(...args) { + let f = doc.createDocumentFragment() + for (let i = 0, ii = args.length; i < ii; i++) { + let item = args[i] + if (item.node && item.node.nodeType) { + f.appendChild(item.node) + } + if (item.nodeType) { + f.appendChild(item) + } + if (typeof item == 'string') { + f.appendChild(Snap.parse(item).node) + } + } + return new Fragment(f) + } + + /*\ ** * Returns closest point to a given one on a given path. - - path (Element) path element + - path (SnapElement) path element - x (number) x coord of a point - y (number) y coord of a point = (object) in format @@ -455,85 +156,79 @@ Snap.len2 = function (x1, y1, x2, y2) { distance (number) distance from the given point to the path } \*/ -// Copied from http://bl.ocks.org/mbostock/8027637 -Snap.closestPoint = function (path, x, y) { - function distance2(p) { - var dx = p.x - x, - dy = p.y - y - return dx * dx + dy * dy - } - var pathNode = path.node, - pathLength = pathNode.getTotalLength(), - precision = (pathLength / pathNode.pathSegList.numberOfItems) * 0.125, - best, - bestLength, - bestDistance = Infinity - - // linear scan for coarse approximation - for ( - var scan, scanLength = 0, scanDistance; - scanLength <= pathLength; - scanLength += precision - ) { - if ( - (scanDistance = distance2( - (scan = pathNode.getPointAtLength(scanLength)) - )) < bestDistance - ) { - best = scan - bestLength = scanLength - bestDistance = scanDistance + // Copied from http://bl.ocks.org/mbostock/8027637 + static closestPoint(path, x, y) { + function distance2(p) { + let dx = p.x - x, + dy = p.y - y + return dx * dx + dy * dy } - } + let pathNode = path.node, + pathLength = pathNode.getTotalLength(), + precision = (pathLength / pathNode.pathSegList.numberOfItems) * 0.125, + best, + bestLength, + bestDistance = Infinity - // binary search for precise estimate - precision *= 0.5 - while (precision > 0.5) { - var before, after, beforeLength, afterLength, beforeDistance, afterDistance - if ( - (beforeLength = bestLength - precision) >= 0 && - (beforeDistance = distance2( - (before = pathNode.getPointAtLength(beforeLength)) - )) < bestDistance + // linear scan for coarse approximation + for ( + let scan, scanLength = 0, scanDistance; + scanLength <= pathLength; + scanLength += precision ) { - best = before - bestLength = beforeLength - bestDistance = beforeDistance - } else if ( - (afterLength = bestLength + precision) <= pathLength && - (afterDistance = distance2( - (after = pathNode.getPointAtLength(afterLength)) - )) < bestDistance - ) { - best = after - bestLength = afterLength - bestDistance = afterDistance - } else { - precision *= 0.5 + if ( + (scanDistance = distance2( + (scan = pathNode.getPointAtLength(scanLength)) + )) < bestDistance + ) { + best = scan + bestLength = scanLength + bestDistance = scanDistance + } } + + // binary search for precise estimate + precision *= 0.5 + while (precision > 0.5) { + let before, + after, + beforeLength, + afterLength, + beforeDistance, + afterDistance + if ( + (beforeLength = bestLength - precision) >= 0 && + (beforeDistance = distance2( + (before = pathNode.getPointAtLength(beforeLength)) + )) < bestDistance + ) { + best = before + bestLength = beforeLength + bestDistance = beforeDistance + } else if ( + (afterLength = bestLength + precision) <= pathLength && + (afterDistance = distance2( + (after = pathNode.getPointAtLength(afterLength)) + )) < bestDistance + ) { + best = after + bestLength = afterLength + bestDistance = afterDistance + } else { + precision *= 0.5 + } + } + + best = { + x: best.x, + y: best.y, + length: bestLength, + distance: Math.sqrt(bestDistance) + } + return best } - best = { - x: best.x, - y: best.y, - length: bestLength, - distance: Math.sqrt(bestDistance) - } - return best -} -/*\ - * Snap.is - [ method ] - ** - * Handy replacement for the `typeof` operator - - o (…) any object or primitive - - type (string) name of the type, e.g., `string`, `function`, `number`, etc. - = (boolean) `true` if given value is of given type -\*/ -Snap.is = is -/*\ - * Snap.snapTo - [ method ] + /*\ ** * Snaps given value to given grid - values (array|number) given array of values or step of the grid @@ -541,483 +236,102 @@ Snap.is = is - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`. = (number) adjusted value \*/ -Snap.snapTo = function (values, value, tolerance) { - tolerance = is(tolerance, 'finite') ? tolerance : 10 - if (is(values, 'array')) { - var i = values.length - while (i--) - if (abs(values[i] - value) <= tolerance) { - return values[i] - } - } else { - values = +values - var rem = value % values - if (rem < tolerance) { - return value - rem - } - if (rem > values - tolerance) { - return value - rem + values - } - } - return value -} -// Colour -/*\ - * Snap.getRGB - [ method ] - ** - * Parses color string as RGB object - - color (string) color string in one of the following formats: - #
    - #
  • Color name (red, green, cornflowerblue, etc)
  • - #
  • #••• — shortened HTML color: (#000, #fc0, etc.)
  • - #
  • #•••••• — full length HTML color: (#000000, #bd2300)
  • - #
  • rgb(•••, •••, •••) — red, green and blue channels values: (rgb(200, 100, 0))
  • - #
  • rgba(•••, •••, •••, •••) — also with opacity
  • - #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (rgb(100%, 175%, 0%))
  • - #
  • rgba(•••%, •••%, •••%, •••%) — also with opacity
  • - #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (hsb(0.5, 0.25, 1))
  • - #
  • hsba(•••, •••, •••, •••) — also with opacity
  • - #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • - #
  • hsba(•••%, •••%, •••%, •••%) — also with opacity
  • - #
  • hsl(•••, •••, •••) — hue, saturation and luminosity values: (hsb(0.5, 0.25, 0.5))
  • - #
  • hsla(•••, •••, •••, •••) — also with opacity
  • - #
  • hsl(•••%, •••%, •••%) — same as above, but in %
  • - #
  • hsla(•••%, •••%, •••%, •••%) — also with opacity
  • - #
- * Note that `%` can be used any time: `rgb(20%, 255, 50%)`. - = (object) RGB object in the following format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) true if string can't be parsed - o } -\*/ -Snap.getRGB = cacher(function (colour) { - if (!colour || !!((colour = Str(colour)).indexOf('-') + 1)) { - return { - r: -1, - g: -1, - b: -1, - hex: 'none', - error: 1, - toString: rgbtoString - } - } - if (colour == 'none') { - return { r: -1, g: -1, b: -1, hex: 'none', toString: rgbtoString } - } - !( - hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == '#' - ) && (colour = toHex(colour)) - if (!colour) { - return { - r: -1, - g: -1, - b: -1, - hex: 'none', - error: 1, - toString: rgbtoString - } - } - var res, - red, - green, - blue, - opacity, - t, - values, - rgb = colour.match(colourRegExp) - if (rgb) { - if (rgb[2]) { - blue = toInt(rgb[2].substring(5), 16) - green = toInt(rgb[2].substring(3, 5), 16) - red = toInt(rgb[2].substring(1, 3), 16) - } - if (rgb[3]) { - blue = toInt((t = rgb[3].charAt(3)) + t, 16) - green = toInt((t = rgb[3].charAt(2)) + t, 16) - red = toInt((t = rgb[3].charAt(1)) + t, 16) - } - if (rgb[4]) { - values = rgb[4].split(commaSpaces) - red = toFloat(values[0]) - values[0].slice(-1) == '%' && (red *= 2.55) - green = toFloat(values[1]) - values[1].slice(-1) == '%' && (green *= 2.55) - blue = toFloat(values[2]) - values[2].slice(-1) == '%' && (blue *= 2.55) - rgb[1].toLowerCase().slice(0, 4) == 'rgba' && - (opacity = toFloat(values[3])) - values[3] && values[3].slice(-1) == '%' && (opacity /= 100) - } - if (rgb[5]) { - values = rgb[5].split(commaSpaces) - red = toFloat(values[0]) - values[0].slice(-1) == '%' && (red /= 100) - green = toFloat(values[1]) - values[1].slice(-1) == '%' && (green /= 100) - blue = toFloat(values[2]) - values[2].slice(-1) == '%' && (blue /= 100) - ;(values[0].slice(-3) == 'deg' || values[0].slice(-1) == '\xb0') && - (red /= 360) - rgb[1].toLowerCase().slice(0, 4) == 'hsba' && - (opacity = toFloat(values[3])) - values[3] && values[3].slice(-1) == '%' && (opacity /= 100) - return Snap.hsb2rgb(red, green, blue, opacity) - } - if (rgb[6]) { - values = rgb[6].split(commaSpaces) - red = toFloat(values[0]) - values[0].slice(-1) == '%' && (red /= 100) - green = toFloat(values[1]) - values[1].slice(-1) == '%' && (green /= 100) - blue = toFloat(values[2]) - values[2].slice(-1) == '%' && (blue /= 100) - ;(values[0].slice(-3) == 'deg' || values[0].slice(-1) == '\xb0') && - (red /= 360) - rgb[1].toLowerCase().slice(0, 4) == 'hsla' && - (opacity = toFloat(values[3])) - values[3] && values[3].slice(-1) == '%' && (opacity /= 100) - return Snap.hsl2rgb(red, green, blue, opacity) - } - red = mmin(math.round(red), 255) - green = mmin(math.round(green), 255) - blue = mmin(math.round(blue), 255) - opacity = mmin(mmax(opacity, 0), 1) - rgb = { r: red, g: green, b: blue, toString: rgbtoString } - rgb.hex = - '#' + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1) - rgb.opacity = is(opacity, 'finite') ? opacity : 1 - return rgb - } - return { r: -1, g: -1, b: -1, hex: 'none', error: 1, toString: rgbtoString } -}, Snap) -/*\ - * Snap.hsb - [ method ] - ** - * Converts HSB values to a hex representation of the color - - h (number) hue - - s (number) saturation - - b (number) value or brightness - = (string) hex representation of the color -\*/ -Snap.hsb = cacher(function (h, s, b) { - return Snap.hsb2rgb(h, s, b).hex -}) -/*\ - * Snap.hsl - [ method ] - ** - * Converts HSL values to a hex representation of the color - - h (number) hue - - s (number) saturation - - l (number) luminosity - = (string) hex representation of the color -\*/ -Snap.hsl = cacher(function (h, s, l) { - return Snap.hsl2rgb(h, s, l).hex -}) -/*\ - * Snap.rgb - [ method ] - ** - * Converts RGB values to a hex representation of the color - - r (number) red - - g (number) green - - b (number) blue - = (string) hex representation of the color -\*/ -Snap.rgb = cacher(function (r, g, b, o) { - if (is(o, 'finite')) { - var round = math.round - return 'rgba(' + [round(r), round(g), round(b), +o.toFixed(2)] + ')' - } - return '#' + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) -}) -var toHex = function (color) { - var i = - glob.doc.getElementsByTagName('head')[0] || - glob.doc.getElementsByTagName('svg')[0], - red = 'rgb(255, 0, 0)' - toHex = cacher(function (color) { - if (color.toLowerCase() == 'red') { - return red - } - i.style.color = red - i.style.color = color - var out = glob.doc.defaultView - .getComputedStyle(i, E) - .getPropertyValue('color') - return out == red ? null : out - }) - return toHex(color) - }, - hsbtoString = function () { - return 'hsb(' + [this.h, this.s, this.b] + ')' - }, - hsltoString = function () { - return 'hsl(' + [this.h, this.s, this.l] + ')' - }, - rgbtoString = function () { - return this.opacity == 1 || this.opacity == null - ? this.hex - : 'rgba(' + [this.r, this.g, this.b, this.opacity] + ')' - }, - prepareRGB = function (r, g, b) { - if (g == null && is(r, 'object') && 'r' in r && 'g' in r && 'b' in r) { - b = r.b - g = r.g - r = r.r - } - if (g == null && is(r, string)) { - var clr = Snap.getRGB(r) - r = clr.r - g = clr.g - b = clr.b - } - if (r > 1 || g > 1 || b > 1) { - r /= 255 - g /= 255 - b /= 255 - } - - return [r, g, b] - }, - packageRGB = function (r, g, b, o) { - r = math.round(r * 255) - g = math.round(g * 255) - b = math.round(b * 255) - var rgb = { - r: r, - g: g, - b: b, - opacity: is(o, 'finite') ? o : 1, - hex: Snap.rgb(r, g, b), - toString: rgbtoString - } - is(o, 'finite') && (rgb.opacity = o) - return rgb - } -/*\ - * Snap.color - [ method ] - ** - * Parses the color string and returns an object featuring the color's component values - - clr (string) color string in one of the supported formats (see @Snap.getRGB) - = (object) Combined RGB/HSB object in the following format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) `true` if string can't be parsed, - o h (number) hue, - o s (number) saturation, - o v (number) value (brightness), - o l (number) lightness - o } -\*/ -Snap.color = function (clr) { - var rgb - if (is(clr, 'object') && 'h' in clr && 's' in clr && 'b' in clr) { - rgb = Snap.hsb2rgb(clr) - clr.r = rgb.r - clr.g = rgb.g - clr.b = rgb.b - clr.opacity = 1 - clr.hex = rgb.hex - } else if (is(clr, 'object') && 'h' in clr && 's' in clr && 'l' in clr) { - rgb = Snap.hsl2rgb(clr) - clr.r = rgb.r - clr.g = rgb.g - clr.b = rgb.b - clr.opacity = 1 - clr.hex = rgb.hex - } else { - if (is(clr, 'string')) { - clr = Snap.getRGB(clr) - } - if ( - is(clr, 'object') && - 'r' in clr && - 'g' in clr && - 'b' in clr && - !('error' in clr) - ) { - rgb = Snap.rgb2hsl(clr) - clr.h = rgb.h - clr.s = rgb.s - clr.l = rgb.l - rgb = Snap.rgb2hsb(clr) - clr.v = rgb.b + static snapTo(values, value, tolerance) { + tolerance = is(tolerance, 'finite') ? tolerance : 10 + if (is(values, 'array')) { + let i = values.length + while (i--) + if (abs(values[i] - value) <= tolerance) { + return values[i] + } } else { - clr = { hex: 'none' } - clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1 - clr.error = 1 + values = +values + let rem = value % values + if (rem < tolerance) { + return value - rem + } + if (rem > values - tolerance) { + return value - rem + values + } } + return value } - clr.toString = rgbtoString - return clr -} -/*\ - * Snap.hsb2rgb - [ method ] + + /*\ ** - * Converts HSB values to an RGB object - - h (number) hue - - s (number) saturation - - v (number) value or brightness - = (object) RGB object in the following format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #•••••• - o } + * Wraps a DOM element specified by CSS selector as @SnapElement + - query (string) CSS selector of the element + = (SnapElement) the current element \*/ -Snap.hsb2rgb = function (h, s, v, o) { - if (is(h, 'object') && 'h' in h && 's' in h && 'b' in h) { - v = h.b - s = h.s - o = h.o - h = h.h + static select(query = '') { + query = query.replace(/([^\\]):/g, '$1\\:') + return wrap(doc.querySelector(query)) } - h *= 360 - var R, G, B, X, C - h = (h % 360) / 60 - C = v * s - X = C * (1 - abs((h % 2) - 1)) - R = G = B = v - C + /*\ - h = ~~h - R += [C, X, 0, 0, X, C][h] - G += [X, C, C, X, 0, 0][h] - B += [0, 0, X, C, C, X][h] - return packageRGB(R, G, B, o) -} -/*\ - * Snap.hsl2rgb - [ method ] ** - * Converts HSL values to an RGB object - - h (number) hue - - s (number) saturation - - l (number) luminosity - = (object) RGB object in the following format: - o { - o r (number) red, - o g (number) green, - o b (number) blue, - o hex (string) color in HTML/CSS format: #•••••• - o } + * Wraps DOM elements specified by CSS selector as set or array of @SnapElement + - query (string) CSS selector of the element + = (SnapElement) the current element \*/ -Snap.hsl2rgb = function (h, s, l, o) { - if (is(h, 'object') && 'h' in h && 's' in h && 'l' in h) { - l = h.l - s = h.s - h = h.h + static selectAll(query = '') { + let nodelist = doc.querySelectorAll(query), + set = (Snap.set || Array)() + for (let i = 0; i < nodelist.length; i++) { + set.push(wrap(nodelist[i])) + } + return set } - if (h > 1 || s > 1 || l > 1) { - h /= 360 - s /= 100 - l /= 100 + + /*\ + ** + * Returns you topmost element under given point. + ** + = (object) Snap element object + - x (number) x coordinate from the top left corner of the window + - y (number) y coordinate from the top left corner of the window + > Usage + | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); +\*/ + static getElementByPoint(x, y) { + let paper = this, + svg = paper.canvas, + target = doc.elementFromPoint(x, y) + + if (!target) { + return null + } + return wrap(target) } - h *= 360 - var R, G, B, X, C - h = (h % 360) / 60 - C = 2 * s * (l < 0.5 ? l : 1 - l) - X = C * (1 - abs((h % 2) - 1)) - R = G = B = l - C / 2 - h = ~~h - R += [C, X, 0, 0, X, C][h] - G += [X, C, C, X, 0, 0][h] - B += [0, 0, X, C, C, X][h] - return packageRGB(R, G, B, o) -} -/*\ - * Snap.rgb2hsb - [ method ] - ** - * Converts RGB values to an HSB object - - r (number) red - - g (number) green - - b (number) blue - = (object) HSB object in the following format: - o { - o h (number) hue, - o s (number) saturation, - o b (number) brightness - o } -\*/ -Snap.rgb2hsb = function (r, g, b) { - b = prepareRGB(r, g, b) - r = b[0] - g = b[1] - b = b[2] - - var H, S, V, C - V = mmax(r, g, b) - C = V - mmin(r, g, b) - H = - C == 0 - ? null - : V == r - ? (g - b) / C - : V == g - ? (b - r) / C + 2 - : (r - g) / C + 4 - H = (((H + 360) % 6) * 60) / 360 - S = C == 0 ? 0 : C / V - return { h: H, s: S, b: V, toString: hsbtoString } -} -/*\ - * Snap.rgb2hsl - [ method ] - ** - * Converts RGB values to an HSL object - - r (number) red - - g (number) green - - b (number) blue - = (object) HSL object in the following format: - o { - o h (number) hue, - o s (number) saturation, - o l (number) luminosity - o } -\*/ -Snap.rgb2hsl = function (r, g, b) { - b = prepareRGB(r, g, b) - r = b[0] - g = b[1] - b = b[2] - - var H, S, L, M, m, C - M = mmax(r, g, b) - m = mmin(r, g, b) - C = M - m - H = - C == 0 - ? null - : M == r - ? (g - b) / C - : M == g - ? (b - r) / C + 2 - : (r - g) / C + 4 - H = (((H + 360) % 6) * 60) / 360 - L = (M + m) / 2 - S = C == 0 ? 0 : L < 0.5 ? C / (2 * L) : C / (2 - 2 * L) - return { h: H, s: S, l: L, toString: hsltoString } + constructor(w, h) { + if (w) { + if (w.nodeType) { + return wrap(w) + } + if (is(w, 'array') && Snap.set) { + return Snap.set.apply(Snap, w) + } + if (w instanceof SnapElement) { + return w + } + if (h == null) { + try { + w = doc.querySelector(String(w)) + return wrap(w) + } catch (e) { + return null + } + } + } + w = w == null ? '100%' : w + h = h == null ? '100%' : h + return new Paper(w, h) + } } // Transformations /*\ - * Snap.parsePathString - [ method ] ** * Utility method ** @@ -1029,12 +343,12 @@ Snap.parsePathString = function (pathString) { if (!pathString) { return null } - var pth = Snap.path(pathString) + let pth = Snap.path(pathString) if (pth.arr) { return Snap.path.clone(pth.arr) } - var paramCounts = { + let paramCounts = { a: 7, c: 6, o: 2, @@ -1055,10 +369,10 @@ Snap.parsePathString = function (pathString) { data = Snap.path.clone(pathString) } if (!data.length) { - Str(pathString).replace(pathCommand, function (a, b, c) { - var params = [], + String(pathString).replace(pathCommand, function (a, b, c) { + let params = [], name = b.toLowerCase() - c.replace(pathValues, function (a, b) { + c.replace(PATH_VALUES, function (a, b) { b && params.push(+b) }) if (name == 'm' && params.length > 2) { @@ -1085,8 +399,6 @@ Snap.parsePathString = function (pathString) { return data } /*\ - * Snap.parseTransformString - [ method ] ** * Utility method ** @@ -1094,21 +406,21 @@ Snap.parsePathString = function (pathString) { - TString (string|array) transform string or array of transformations (in the last case it is returned straight away) = (array) array of transformations \*/ -var parseTransformString = (Snap.parseTransformString = function (TString) { +let parseTransformString = (Snap.parseTransformString = function (TString) { if (!TString) { return null } - var paramCounts = { r: 3, s: 4, t: 2, m: 6 }, + let paramCounts = { r: 3, s: 4, t: 2, m: 6 }, data = [] if (is(TString, 'array') && is(TString[0], 'array')) { // rough assumption data = Snap.path.clone(TString) } if (!data.length) { - Str(TString).replace(tCommand, function (a, b, c) { - var params = [], + String(TString).replace(T_COMMAND, function (a, b, c) { + let params = [], name = b.toLowerCase() - c.replace(pathValues, function (a, b) { + c.replace(PATH_VALUES, function (a, b) { b && params.push(+b) }) data.push([b].concat(params)) @@ -1118,7 +430,7 @@ var parseTransformString = (Snap.parseTransformString = function (TString) { return data }) function svgTransform2string(tstr) { - var res = [] + let res = [] tstr = tstr.replace( /(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { @@ -1150,14 +462,15 @@ function svgTransform2string(tstr) { } Snap._.svgTransform2string = svgTransform2string Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i + function transform2matrix(tstr, bbox) { - var tdata = parseTransformString(tstr), + let tdata = parseTransformString(tstr), m = new Snap.Matrix() if (tdata) { - for (var i = 0, ii = tdata.length; i < ii; i++) { - var t = tdata[i], + for (let i = 0, ii = tdata.length; i < ii; i++) { + let t = tdata[i], tlen = t.length, - command = Str(t[0]).toLowerCase(), + command = String(t[0]).toLowerCase(), absolute = t[0] != command, inver = absolute ? m.invert() : 0, x1, @@ -1218,42 +531,15 @@ function transform2matrix(tstr, bbox) { } return m } + Snap._.transform2matrix = transform2matrix -Snap._unit2px = unit2px -var contains = - glob.doc.contains || glob.doc.compareDocumentPosition - ? function (a, b) { - var adown = a.nodeType == 9 ? a.documentElement : a, - bup = b && b.parentNode - return ( - a == bup || - !!( - bup && - bup.nodeType == 1 && - (adown.contains - ? adown.contains(bup) - : a.compareDocumentPosition && - a.compareDocumentPosition(bup) & 16) - ) - ) - } - : function (a, b) { - if (b) { - while (b) { - b = b.parentNode - if (b == a) { - return true - } - } - } - return false - } -function getSomeDefs(el) { - var p = + +export function getSomeDefs(el) { + let p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || (el.node.parentNode && wrap(el.node.parentNode)) || Snap.select('svg') || - Snap(0, 0), + new Snap(0, 0), pdefs = p.select('defs'), defs = pdefs == null ? false : pdefs.node if (!defs) { @@ -1261,16 +547,16 @@ function getSomeDefs(el) { } return defs } + function getSomeSVG(el) { return ( (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || Snap.select('svg') ) } -Snap._.getSomeDefs = getSomeDefs -Snap._.getSomeSVG = getSomeSVG -function unit2px(el, name, value) { - var svg = getSomeSVG(el).node, + +export function unit2px(el, name, value) { + let svg = getSomeSVG(el).node, out = {}, mgr = svg.querySelector('.svg---mgr') if (!mgr) { @@ -1368,40 +654,12 @@ function unit2px(el, name, value) { svg.removeChild(mgr) return out } -/*\ - * Snap.select - [ method ] - ** - * Wraps a DOM element specified by CSS selector as @Element - - query (string) CSS selector of the element - = (Element) the current element -\*/ -Snap.select = function (query) { - query = Str(query).replace(/([^\\]):/g, '$1\\:') - return wrap(glob.doc.querySelector(query)) -} -/*\ - * Snap.selectAll - [ method ] - ** - * Wraps DOM elements specified by CSS selector as set or array of @Element - - query (string) CSS selector of the element - = (Element) the current element -\*/ -Snap.selectAll = function (query) { - var nodelist = glob.doc.querySelectorAll(query), - set = (Snap.set || Array)() - for (var i = 0; i < nodelist.length; i++) { - set.push(wrap(nodelist[i])) - } - return set -} function add2group(list) { if (!is(list, 'array')) { list = Array.prototype.slice.call(arguments, 0) } - var i = 0, + let i = 0, j = 0, node = this.node while (this[i]) delete this[i++] @@ -1414,7 +672,7 @@ function add2group(list) { node.appendChild(list[i].node) } } - var children = node.childNodes + let children = node.childNodes for (i = 0; i < children.length; i++) { this[j++] = wrap(children[i]) } @@ -1422,9 +680,9 @@ function add2group(list) { } // Hub garbage collector every 10s setInterval(function () { - for (var key in hub) + for (let key in hub) if (hub[has](key)) { - var el = hub[key], + let el = hub[key], node = el.node if ( (el.type != 'svg' && !node.ownerSVGElement) || @@ -1436,298 +694,641 @@ setInterval(function () { } } }, 1e4) -function Element(el) { - if (el.snap in hub) { - return hub[el.snap] - } - var svg - try { - svg = el.ownerSVGElement - } catch (e) {} - /*\ - * Element.node - [ property (object) ] - ** - * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. - > Usage - | // draw a circle at coordinate 10,10 with radius of 10 - | var c = paper.circle(10, 10, 10); - | c.node.onclick = function () { - | c.attr("fill", "red"); - | }; - \*/ - this.node = el - if (svg) { - this.paper = new Paper(svg) - } - /*\ - * Element.type - [ property (string) ] - ** - * SVG tag name of the given element. - \*/ - this.type = el.tagName || el.nodeName - var id = (this.id = ID(this)) - this.anims = {} - this._ = { - transform: [] - } - el.snap = id - hub[id] = this - if (this.type == 'g') { - this.add = add2group - } - if (this.type in { g: 1, mask: 1, pattern: 1, symbol: 1 }) { - for (var method in Paper.prototype) - if (Paper.prototype[has](method)) { - this[method] = Paper.prototype[method] + +export class Paper { + constructor(w, h) { + let res, desc, defs + if (w && w.tagName && w.tagName.toLowerCase() == 'svg') { + if (w.snap in hub) { + return hub[w.snap] } + let doc = w.ownerDocument + res = new SnapElement(w) + desc = w.getElementsByTagName('desc')[0] + defs = w.getElementsByTagName('defs')[0] + if (!desc) { + desc = $('desc') + desc.appendChild(doc.createTextNode('Created with Snap')) + res.node.appendChild(desc) + } + if (!defs) { + defs = $('defs') + res.node.appendChild(defs) + } + res.defs = defs + extend(res, Paper.prototype) + res.paper = res.root = res + } else { + res = make('svg', doc.body) + $(res.node, { + width: w, + height: h, + version: 1.1, + xmlns + }) + } + return res } -} -/*\ - * Element.attr - [ method ] + + /*\ ** - * Gets or sets given attributes of the element. + * Creates a nested SVG element. + - x (number) @optional X of the element + - y (number) @optional Y of the element + - width (number) @optional width of the element + - height (number) @optional height of the element + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + ** + = (object) the `svg` element ** - - params (object) contains key-value pairs of attributes you want to set - * or - - param (string) name of the attribute - = (Element) the current element - * or - = (string) value of attribute - > Usage - | el.attr({ - | fill: "#fc0", - | stroke: "#000", - | strokeWidth: 2, // CamelCase... - | "fill-opacity": 0.5, // or dash-separated names - | width: "*=2" // prefixed values - | }); - | console.log(el.attr("fill")); // #fc0 - * Prefixed values in format `"+=10"` supported. All four operations - * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` - * and `-`: `"+=2em"`. \*/ -Element.prototype.attr = function (params, value) { - var el = this, - node = el.node - if (!params) { - if (node.nodeType != 1) { - return { - text: node.nodeValue + svg(x, y, width, height, vbx, vby, vbw, vbh) { + let attrs = {} + if (is(x, 'object') && y == null) { + attrs = x + } else { + if (x != null) { + attrs.x = x + } + if (y != null) { + attrs.y = y + } + if (width != null) { + attrs.width = width + } + if (height != null) { + attrs.height = height + } + if (vbx != null && vby != null && vbw != null && vbh != null) { + attrs.viewBox = [vbx, vby, vbw, vbh] } } - var attr = node.attributes, - out = {} - for (var i = 0, ii = attr.length; i < ii; i++) { - out[attr[i].nodeName] = attr[i].nodeValue - } - return out + return this.el('svg', attrs) } - if (is(params, 'string')) { + + mask(first) { + let attr, + el = this.el('mask') + if (arguments.length == 1 && first && !first.type) { + el.attr(first) + } else if (arguments.length) { + el.add(Array.from(arguments)) + } + return el + } + + /*\ + ** + * Equivalent in behaviour to @Paper.g, except it’s a pattern. + - x (number) @optional X of the element + - y (number) @optional Y of the element + - width (number) @optional width of the element + - height (number) @optional height of the element + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + ** + = (object) the `pattern` element + ** + \*/ + ptrn(x, y, width, height, vx, vy, vw, vh) { + if (is(x, 'object')) { + let attr = x + } else { + attr = { patternUnits: 'userSpaceOnUse' } + if (x) { + attr.x = x + } + if (y) { + attr.y = y + } + if (width != null) { + attr.width = width + } + if (height != null) { + attr.height = height + } + if (vx != null && vy != null && vw != null && vh != null) { + attr.viewBox = [vx, vy, vw, vh] + } else { + attr.viewBox = [x || 0, y || 0, width || 0, height || 0] + } + } + return this.el('pattern', attr) + } + /*\ + ** + * Creates a element. + - id (string) @optional id of element to link + * or + - id (SnapElement) @optional element to link + ** + = (object) the `use` element + ** + \*/ + use(id) { + if (id != null) { + if (id instanceof SnapElement) { + if (!id.attr('id')) { + id.attr({ id: uuid(id.type + 'S') }) + } + id = id.attr('id') + } + if (String(id).charAt() == '#') { + id = id.substring(1) + } + return this.el('use', { 'xlink:href': '#' + id }) + } else { + return SnapElement.prototype.use.call(this) + } + } + /*\ + ** + * Creates a element. + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + = (object) the `symbol` element + ** + \*/ + symbol(vx, vy, vw, vh) { + let attr = {} + if (vx != null && vy != null && vw != null && vh != null) { + attr.viewBox = [vx, vy, vw, vh] + } + + return this.el('symbol', attr) + } + /*\ + ** + * Draws a text string + ** + - x (number) x coordinate position + - y (number) y coordinate position + - text (string|array) The text string to draw or array of strings to nest within separate `` elements + = (object) the `text` element + ** + > Usage + | let t1 = paper.text(50, 50, "Snap"); + | let t2 = paper.text(50, 50, ["S","n","a","p"]); + | // Text path usage + | t1.attr({textpath: "M10,10L100,100"}); + | // or + | let pth = paper.path("M10,10L100,100"); + | t1.attr({textpath: pth}); + \*/ + text(x, y, text) { + let attr = {} + if (is(x, 'object')) { + attr = x + } else if (x != null) { + attr = { + x: x, + y: y, + text: text || '' + } + } + return this.el('text', attr) + } + /*\ + ** + * Draws a line + ** + - x1 (number) x coordinate position of the start + - y1 (number) y coordinate position of the start + - x2 (number) x coordinate position of the end + - y2 (number) y coordinate position of the end + = (object) the `line` element + ** + > Usage + | let t1 = paper.line(50, 50, 100, 100); + \*/ + line(x1, y1, x2, y2) { + let attr = {} + if (is(x1, 'object')) { + attr = x1 + } else if (x1 != null) { + attr = { + x1: x1, + x2: x2, + y1: y1, + y2: y2 + } + } + return this.el('line', attr) + } + /*\ + ** + * Draws a polyline + ** + - points (array) array of points + * or + - varargs (…) points + = (object) the `polyline` element + ** + > Usage + | let p1 = paper.polyline([10, 10, 100, 100]); + | let p2 = paper.polyline(10, 10, 100, 100); + \*/ + polyline(points) { if (arguments.length > 1) { - var json = {} - json[params] = value - params = json - } else { - return eve('snap.util.getattr.' + params, el).firstDefined() + points = Array.from(arguments) } + let attr = {} + if (is(points, 'object') && !is(points, 'array')) { + attr = points + } else if (points != null) { + attr = { points: points } + } + return this.el('polyline', attr) } - for (var att in params) { - if (params[has](att)) { - eve('snap.util.attr.' + att, el, params[att]) + /*\ + ** + * Draws a polygon. See @Paper.polyline + \*/ + polygon(points) { + if (arguments.length > 1) { + points = Array.prototype.slice.call(arguments, 0) } + let attr = {} + if (is(points, 'object') && !is(points, 'array')) { + attr = points + } else if (points != null) { + attr = { points: points } + } + return this.el('polygon', attr) } - return el -} -/*\ - * Snap.parse - [ method ] - ** - * Parses SVG fragment and converts it into a @Fragment - ** - - svg (string) SVG string - = (Fragment) the @Fragment -\*/ -Snap.parse = function (svg) { - var f = glob.doc.createDocumentFragment(), - full = true, - div = glob.doc.createElement('div') - svg = Str(svg) - if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { - svg = '' + svg + '' - full = false - } - div.innerHTML = svg - svg = div.getElementsByTagName('svg')[0] - if (svg) { - if (full) { - f = svg - } else { - while (svg.firstChild) { - f.appendChild(svg.firstChild) - } - } - } - return new Fragment(f) -} -function Fragment(frag) { - this.node = frag -} -/*\ - * Snap.fragment - [ method ] - ** - * Creates a DOM fragment from a given list of elements or strings - ** - - varargs (…) SVG string - = (Fragment) the @Fragment -\*/ -Snap.fragment = function () { - var args = Array.prototype.slice.call(arguments, 0), - f = glob.doc.createDocumentFragment() - for (var i = 0, ii = args.length; i < ii; i++) { - var item = args[i] - if (item.node && item.node.nodeType) { - f.appendChild(item.node) - } - if (item.nodeType) { - f.appendChild(item) - } - if (typeof item == 'string') { - f.appendChild(Snap.parse(item).node) - } - } - return new Fragment(f) -} -function make(name, parent) { - var res = $(name) - parent.appendChild(res) - var el = wrap(res) - return el -} -function Paper(w, h) { - var res, - desc, - defs, - proto = Paper.prototype - if (w && w.tagName && w.tagName.toLowerCase() == 'svg') { - if (w.snap in hub) { - return hub[w.snap] - } - var doc = w.ownerDocument - res = new Element(w) - desc = w.getElementsByTagName('desc')[0] - defs = w.getElementsByTagName('defs')[0] - if (!desc) { - desc = $('desc') - desc.appendChild(doc.createTextNode('Created with Snap')) - res.node.appendChild(desc) - } - if (!defs) { - defs = $('defs') - res.node.appendChild(defs) - } - res.defs = defs - for (var key in proto) - if (proto[has](key)) { - res[key] = proto[key] - } - res.paper = res.root = res - } else { - res = make('svg', glob.doc.body) - $(res.node, { - height: h, - version: 1.1, - width: w, - xmlns: xmlns - }) - } - return res -} -function wrap(dom) { - if (!dom) { - return dom - } - if (dom instanceof Element || dom instanceof Fragment) { - return dom - } - if (dom.tagName && dom.tagName.toLowerCase() == 'svg') { - return new Paper(dom) - } - if ( - dom.tagName && - dom.tagName.toLowerCase() == 'object' && - dom.type == 'image/svg+xml' - ) { - return new Paper(dom.contentDocument.getElementsByTagName('svg')[0]) - } - return new Element(dom) -} - -Snap._.make = make -Snap._.wrap = wrap -/*\ - * Paper.el - [ method ] + /*\ ** * Creates an element on paper with a given name and no attributes ** - name (string) tag name - attr (object) attributes - = (Element) the current element + = (SnapElement) the current element > Usage - | var c = paper.circle(10, 10, 10); // is the same as... - | var c = paper.el("circle").attr({ + | let c = paper.circle(10, 10, 10); // is the same as... + | let c = paper.el("circle").attr({ | cx: 10, | cy: 10, | r: 10 | }); | // and the same as - | var c = paper.el("circle", { + | let c = paper.el("circle", { | cx: 10, | cy: 10, | r: 10 | }); \*/ -Paper.prototype.el = function (name, attr) { - var el = make(name, this.node) - attr && el.attr(attr) - return el -} -/*\ - * Element.children - [ method ] - ** - * Returns array of all the children of the element. - = (array) array of Elements -\*/ -Element.prototype.children = function () { - var out = [], - ch = this.node.childNodes - for (var i = 0, ii = ch.length; i < ii; i++) { - out[i] = Snap(ch[i]) + el(name, attr) { + let el = make(name, this.node) + attr && el.attr(attr) + return el } - return out -} -function jsonFiller(root, o) { - for (var i = 0, ii = root.length; i < ii; i++) { - var item = { - type: root[i].type, - attr: root[i].attr() - }, - children = root[i].children() - o.push(item) - if (children.length) { - jsonFiller(children, (item.childNodes = [])) + + /*\ + * + * Draws a rectangle + ** + - x (number) x coordinate of the top left corner + - y (number) y coordinate of the top left corner + - width (number) width + - height (number) height + - rx (number) #optional horizontal radius for rounded corners, default is 0 + - ry (number) #optional vertical radius for rounded corners, default is rx or 0 + = (object) the `rect` element + ** + > Usage + | // regular rectangle + | let c = paper.rect(10, 10, 50, 50); + | // rectangle with rounded corners + | let c = paper.rect(40, 40, 50, 50, 10); + \*/ + rect(x, y, w, h, rx, ry) { + let attr + if (ry == null) { + ry = rx + } + if (is(x, 'object') && x == '[object Object]') { + attr = x + } else if (x != null) { + attr = { + x: x, + y: y, + width: w, + height: h + } + if (rx != null) { + attr.rx = rx + attr.ry = ry + } + } + return this.el('rect', attr) + } + + /*\ + ** + * Draws a circle + ** + - x (number) x coordinate of the centre + - y (number) y coordinate of the centre + - r (number) radius + = (object) the `circle` element + ** + > Usage + | let c = paper.circle(50, 50, 40); + \*/ + circle(cx, cy, r) { + let attr + if (is(cx, 'object') && cx == '[object Object]') { + attr = cx + } else if (cx != null) { + attr = { + cx: cx, + cy: cy, + r: r + } + } + return this.el('circle', attr) + } + + /*\ + ** + * Places an image on the surface + ** + - src (string) URI of the source image + - x (number) x offset position + - y (number) y offset position + - width (number) width of the image + - height (number) height of the image + = (object) the `image` element + * or + = (object) Snap element object with type `image` + ** + > Usage + | let c = paper.image("apple.png", 10, 10, 80, 80); + \*/ + image(src, x, y, width, height) { + let el = this.el('image') + if (is(src, 'object') && 'src' in src) { + el.attr(src) + } else if (src != null) { + let set = { + 'xlink:href': src, + preserveAspectRatio: 'none' + } + if (x != null && y != null) { + set.x = x + set.y = y + } + if (width != null && height != null) { + set.width = width + set.height = height + } else { + preload(src, function () { + $(el.node, { + width: this.offsetWidth, + height: this.offsetHeight + }) + }) + } + $(el.node, set) + } + return el + } + + /*\ + ** + * Draws an ellipse + ** + - x (number) x coordinate of the centre + - y (number) y coordinate of the centre + - rx (number) horizontal radius + - ry (number) vertical radius + = (object) the `ellipse` element + ** + > Usage + | let c = paper.ellipse(50, 50, 40, 20); + \*/ + ellipse(cx, cy, rx, ry) { + let attr + if (is(cx, 'object') && cx == '[object Object]') { + attr = cx + } else if (cx != null) { + attr = { + cx: cx, + cy: cy, + rx: rx, + ry: ry + } + } + return this.el('ellipse', attr) + } + + path(d) { + let attr + if (is(d, 'object') && !is(d, 'array')) { + attr = d + } else if (d) { + attr = { d: d } + } + return this.el('path', attr) + } + + group(first) { + let attr, + el = this.el('g') + if (arguments.length == 1 && first && !first.type) { + el.attr(first) + } else if (arguments.length) { + el.add(Array.from(arguments)) + } + return el + } + + /*\ + ** + * Creates a `` element + ** + - filstr (string) SVG fragment of filter provided as a string + = (object) @SnapElement + * Note: It is recommended to use filters embedded into the page inside an empty SVG element. + > Usage + | let f = paper.filter(''), + | c = paper.circle(10, 10, 10).attr({ + | filter: f + | }); + \*/ + filter(filstr) { + let paper = this + if (paper.type !== 'svg') { + paper = paper.paper + } + let f = Snap.parse(String(filstr)), + id = uuid((this.type || '') + 'S'), + width = paper.node.offsetWidth, + height = paper.node.offsetHeight, + filter = $('filter') + $(filter, { + id: id, + filterUnits: 'userSpaceOnUse' + }) + filter.appendChild(f.node) + paper.defs.appendChild(filter) + return new SnapElement(filter) + } + + toString() { + let doc = this.node.ownerDocument, + f = doc.createDocumentFragment(), + d = doc.createElement('div'), + svg = this.node.cloneNode(true), + res + f.appendChild(d) + d.appendChild(svg) + $(svg, { xmlns: 'http://www.w3.org/2000/svg' }) + res = d.innerHTML + return res + } + + /*\ + + ** + * Removes all child nodes of the paper, except . + */ + clear() { + let node = this.node.firstChild, + next + while (node) { + next = node.nextSibling + if (node.tagName != 'defs') { + node.parentNode.removeChild(node) + } else { + this.clear.call({ node: node }) + } + node = next } } } -/*\ - * Element.toJSON - [ method ] + +export class SnapElement { + constructor(el) { + if (el.snap in hub) { + return hub[el.snap] + } + let svg + try { + svg = el.ownerSVGElement + } catch (e) {} + /*\ + ** + * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. + > Usage + | // draw a circle at coordinate 10,10 with radius of 10 + | let c = paper.circle(10, 10, 10); + | c.node.onclick = function () { + | c.attr("fill", "red"); + | }; + \*/ + this.node = el + if (svg) { + this.paper = new Paper(svg) + } + /*\ + * SnapElement.type + [ property (string) ] + ** + * SVG tag name of the given element. + \*/ + this.type = el.tagName || el.nodeName + let id = (this.id = uuid((this.type || '') + 'S')) + this.anims = {} + this._ = { + transform: [] + } + el.snap = id + hub[id] = this + if (this.type == 'g') { + this.add = add2group + } + if (this.type in { g: 1, mask: 1, pattern: 1, symbol: 1 }) { + extend(this, Paper.prototype) + } + } + + /* + ** + * Gets or sets given attributes of the element. + ** + - params (object) contains key-value pairs of attributes you want to set + * or + - param (string) name of the attribute + = (SnapElement) the current element + * or + = (string) value of attribute + > Usage + | el.attr({ + | fill: "#fc0", + | stroke: "#000", + | strokeWidth: 2, // CamelCase... + | "fill-opacity": 0.5, // or dash-separated names + | width: "*=2" // prefixed values + | }); + | console.log(el.attr("fill")); // #fc0 + * Prefixed values in format `"+=10"` supported. All four operations + * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` + * and `-`: `"+=2em"`. + */ + attr(params, value) { + let node = this.node + + if (!params) { + if (node.nodeType != 1) { + return { + text: node.nodeValue + } + } + let attr = node.attributes, + out = {} + for (let i = 0, ii = attr.length; i < ii; i++) { + out[attr[i].nodeName] = attr[i].nodeValue + } + + return out + } + if (is(params, 'string')) { + if (value !== void 0) { + params = { [params]: value } + } else { + return eve('snap.util.getattr.' + params, this).firstDefined() + } + } + for (let att in params) { + eve('snap.util.attr.' + att, this, params[att]) + } + return this + } + + /* + ** + * Returns array of all the children of the element. + = (array) array of Elements + */ + children() { + let out = [], + ch = this.node.childNodes + for (let i = 0, ii = ch.length; i < ii; i++) { + out[i] = new Snap(ch[i]) + } + return out + } + + /*\ ** * Returns object representation of the given element and all its children. = (object) in format @@ -1737,248 +1338,38 @@ function jsonFiller(root, o) { o childNodes (array) optional array of children in the same format o } \*/ -Element.prototype.toJSON = function () { - var out = [] - jsonFiller([this], out) - return out[0] + toJSON() { + let out = [] + jsonFiller([this], out) + return out[0] + } } + // default eve.on('snap.util.getattr', function () { - var att = eve.nt() - att = att.substring(att.lastIndexOf('.') + 1) - var css = att.replace(/[A-Z]/g, function (letter) { + let key = eve.nt().split('.').at(-1) + let css = key.replace(/[A-Z]/g, function (letter) { return '-' + letter.toLowerCase() }) - if (cssAttr[has](css)) { + if (CSS_ATTR[css]) { return this.node.ownerDocument.defaultView .getComputedStyle(this.node, null) .getPropertyValue(css) } else { - return $(this.node, att) + return $(this.node, key) } }) -var cssAttr = { - 'alignment-baseline': 0, - 'baseline-shift': 0, - clip: 0, - 'clip-path': 0, - 'clip-rule': 0, - color: 0, - 'color-interpolation': 0, - 'color-interpolation-filters': 0, - 'color-profile': 0, - 'color-rendering': 0, - cursor: 0, - direction: 0, - display: 0, - 'dominant-baseline': 0, - 'enable-background': 0, - fill: 0, - 'fill-opacity': 0, - 'fill-rule': 0, - filter: 0, - 'flood-color': 0, - 'flood-opacity': 0, - font: 0, - 'font-family': 0, - 'font-size': 0, - 'font-size-adjust': 0, - 'font-stretch': 0, - 'font-style': 0, - 'font-variant': 0, - 'font-weight': 0, - 'glyph-orientation-horizontal': 0, - 'glyph-orientation-vertical': 0, - 'image-rendering': 0, - kerning: 0, - 'letter-spacing': 0, - 'lighting-color': 0, - marker: 0, - 'marker-end': 0, - 'marker-mid': 0, - 'marker-start': 0, - mask: 0, - opacity: 0, - overflow: 0, - 'pointer-events': 0, - 'shape-rendering': 0, - 'stop-color': 0, - 'stop-opacity': 0, - stroke: 0, - 'stroke-dasharray': 0, - 'stroke-dashoffset': 0, - 'stroke-linecap': 0, - 'stroke-linejoin': 0, - 'stroke-miterlimit': 0, - 'stroke-opacity': 0, - 'stroke-width': 0, - 'text-anchor': 0, - 'text-decoration': 0, - 'text-rendering': 0, - 'unicode-bidi': 0, - visibility: 0, - 'word-spacing': 0, - 'writing-mode': 0 -} eve.on('snap.util.attr', function (value) { - var att = eve.nt(), - attr = {} - att = att.substring(att.lastIndexOf('.') + 1) - attr[att] = value - var style = att.replace(/-(\w)/gi, function (all, letter) { - return letter.toUpperCase() - }), - css = att.replace(/[A-Z]/g, function (letter) { - return '-' + letter.toLowerCase() - }) - if (cssAttr[has](css)) { - this.node.style[style] = value == null ? E : value + let key = eve.nt().split('.').at(-1) + let attr = { [key]: value } + + let css = key.replace(/[A-Z]/g, function (letter) { + return '-' + letter.toLowerCase() + }) + if (CSS_ATTR[css]) { + this.node.style[css] = value == null ? E : value } else { $(this.node, attr) } }) -;(function (proto) {})(Paper.prototype) - -// simple ajax -/*\ - * Snap.ajax - [ method ] - ** - * Simple implementation of Ajax - ** - - url (string) URL - - postData (object|string) data for post request - - callback (function) callback - - scope (object) #optional scope of callback - * or - - url (string) URL - - callback (function) callback - - scope (object) #optional scope of callback - = (XMLHttpRequest) the XMLHttpRequest object, just in case -\*/ -Snap.ajax = function (url, postData, callback, scope) { - var req = new XMLHttpRequest(), - id = ID() - if (req) { - if (is(postData, 'function')) { - scope = callback - callback = postData - postData = null - } else if (is(postData, 'object')) { - var pd = [] - for (var key in postData) - if (postData.hasOwnProperty(key)) { - pd.push( - encodeURIComponent(key) + '=' + encodeURIComponent(postData[key]) - ) - } - postData = pd.join('&') - } - req.open(postData ? 'POST' : 'GET', url, true) - if (postData) { - req.setRequestHeader('X-Requested-With', 'XMLHttpRequest') - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') - } - if (callback) { - eve.once('snap.ajax.' + id + '.0', callback) - eve.once('snap.ajax.' + id + '.200', callback) - eve.once('snap.ajax.' + id + '.304', callback) - } - req.onreadystatechange = function () { - if (req.readyState != 4) return - eve('snap.ajax.' + id + '.' + req.status, scope, req) - } - if (req.readyState == 4) { - return req - } - req.send(postData) - return req - } -} -/*\ - * Snap.load - [ method ] - ** - * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX) - ** - - url (string) URL - - callback (function) callback - - scope (object) #optional scope of callback -\*/ -Snap.load = function (url, callback, scope) { - Snap.ajax(url, function (req) { - var f = Snap.parse(req.responseText) - scope ? callback.call(scope, f) : callback(f) - }) -} -var getOffset = function (elem) { - var box = elem.getBoundingClientRect(), - doc = elem.ownerDocument, - body = doc.body, - docElem = doc.documentElement, - clientTop = docElem.clientTop || body.clientTop || 0, - clientLeft = docElem.clientLeft || body.clientLeft || 0, - top = - box.top + - (g.win.pageYOffset || docElem.scrollTop || body.scrollTop) - - clientTop, - left = - box.left + - (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - - clientLeft - return { - y: top, - x: left - } -} -/*\ - * Snap.getElementByPoint - [ method ] - ** - * Returns you topmost element under given point. - ** - = (object) Snap element object - - x (number) x coordinate from the top left corner of the window - - y (number) y coordinate from the top left corner of the window - > Usage - | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); -\*/ -Snap.getElementByPoint = function (x, y) { - var paper = this, - svg = paper.canvas, - target = glob.doc.elementFromPoint(x, y) - if (glob.win.opera && target.tagName == 'svg') { - var so = getOffset(target), - sr = target.createSVGRect() - sr.x = x - so.x - sr.y = y - so.y - sr.width = sr.height = 1 - var hits = target.getIntersectionList(sr, null) - if (hits.length) { - target = hits[hits.length - 1] - } - } - if (!target) { - return null - } - return wrap(target) -} -/*\ - * Snap.plugin - [ method ] - ** - * Let you write plugins. You pass in a function with five arguments, like this: - | Snap.plugin(function (Snap, Element, Paper, global, Fragment) { - | Snap.newmethod = function () {}; - | Element.prototype.newmethod = function () {}; - | Paper.prototype.newmethod = function () {}; - | }); - * Inside the function you have access to all main objects (and their - * prototypes). This allow you to extend anything you want. - ** - - f (function) your plugin body -\*/ -Snap.plugin = function (f) { - f(Snap, Element, Paper, glob, Fragment) -} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..470b6a2 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,154 @@ +/** + * {} + * @author yutent + * @date 2024/03/06 09:55:31 + */ + +import { xlink, xmlns, doc, win } from './lib/constants.js' + +export function uuid(prefix = '') { + return prefix + Math.random().toString(16).slice(-8) +} + +export function is(o, type) { + type = String(type).toLowerCase() + let _type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase() + if (type === 'finite') { + return isFinite(o) + } + if (type === 'array' && Array.isArray(o)) { + return true + } + return ( + (type === 'null' && o === null) || + (type === typeof o && o !== null) || + (type === 'object' && o === Object(o)) || + _type === type + ) +} + +export function $(el, attr) { + if (attr) { + if (el == '#text') { + el = doc.createTextNode(attr.text || attr['#text'] || '') + } + if (el == '#comment') { + el = doc.createComment(attr.text || attr['#text'] || '') + } + if (typeof el == 'string') { + el = $(el) + } + if (typeof attr == 'string') { + if (el.nodeType == 1) { + if (attr.slice(0, 6) == 'xlink:') { + return el.getAttributeNS(xlink, attr.slice(6)) + } + if (attr.slice(0, 4) == 'xml:') { + return el.getAttributeNS(xmlns, attr.slice(4)) + } + return el.getAttribute(attr) + } else if (attr == 'text') { + return el.nodeValue + } else { + return null + } + } + if (el.nodeType == 1) { + for (let key in attr) + if (attr.hasOwnProperty(key)) { + let val = String(attr[key]) + if (val) { + if (key.slice(0, 6) == 'xlink:') { + el.setAttributeNS(xlink, key.slice(6), val) + } else if (key.slice(0, 4) == 'xml:') { + el.setAttributeNS(xmlns, key.slice(4), val) + } else { + el.setAttribute(key, val) + } + } else { + el.removeAttribute(key) + } + } + } else if ('text' in attr) { + el.nodeValue = attr.text + } + } else { + el = doc.createElementNS(xmlns, el) + } + return el +} + +export function clone(obj) { + if (typeof obj == 'function' || Object(obj) !== obj) { + return obj + } + let res = new obj.constructor() + for (let key in obj) + if (obj.hasOwnProperty(key)) { + res[key] = clone(obj[key]) + } + return res +} + +export function preload(src, f) { + let img = new Image() + + img.onload = function () { + f.call(img) + } + img.src = src +} + +function repush(arr, item) { + let l = arr.length - 1 // 要减1, 最后如果本身在最后, 不用变 + for (let i = 0; i < l; i++) { + if (arr[i] === item) { + ;[a[i], a[l]] = [a[l], a[i]] + break + } + } +} + +export function cacher(fn, scope, postprocessor) { + function newf(...args) { + let key = args.join('\u2400'), + cache = (newf.cache = newf.cache || {}), + count = (newf.count = newf.count || []) + if (cache.hasOwnProperty(key)) { + repush(count, key) + return postprocessor ? postprocessor(cache[key]) : cache[key] + } + count.length >= 1e3 && delete cache[count.shift()] + count.push(key) + cache[key] = fn.apply(scope, args) + return postprocessor ? postprocessor(cache[key]) : cache[key] + } + return newf +} + +export function jsonFiller(root, o) { + for (let i = 0, ii = root.length; i < ii; i++) { + let item = { + type: root[i].type, + attr: root[i].attr() + }, + children = root[i].children() + o.push(item) + if (children.length) { + jsonFiller(children, (item.childNodes = [])) + } + } +} + +export function extend(origin, target) { + let methods = Object.getOwnPropertyNames(target).filter( + n => n !== 'constructor' + ) + for (let k of methods) { + Object.defineProperty(origin, k, { value: target[k] }) + } +} + +export function url(id) { + return "url('#" + id + "')" +}