diff options
author | Ulrich-Matthias Schäfer <ulima.ums@googlemail.com> | 2018-12-01 15:57:06 +0100 |
---|---|---|
committer | Ulrich-Matthias Schäfer <ulima.ums@googlemail.com> | 2018-12-01 15:57:06 +0100 |
commit | 9546418c5ed9b1876132b43dff1ae690c3ec4e24 (patch) | |
tree | e291fe6c2443aeaaef99edf5fbc5cd1af336f43e /src | |
parent | dbe9c376fd76dd4e4777281888f4092e38512c18 (diff) | |
parent | 947964e4a257b1b1fcc9b115a218a9d8dde7513f (diff) | |
download | svg.js-9546418c5ed9b1876132b43dff1ae690c3ec4e24.tar.gz svg.js-9546418c5ed9b1876132b43dff1ae690c3ec4e24.zip |
Merge branch '790-color-spaces' into 3.0.0
Diffstat (limited to 'src')
29 files changed, 567 insertions, 230 deletions
diff --git a/src/animation/Animator.js b/src/animation/Animator.js index 0119cba..2786602 100644 --- a/src/animation/Animator.js +++ b/src/animation/Animator.js @@ -74,7 +74,9 @@ const Animator = { nextFrame.run() } - Animator.transforms.forEach(function (el) { el() }) + Animator.transforms.forEach(function (el) { + el() + }) // If we have remaining timeouts or frames, draw until we don't anymore Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() diff --git a/src/animation/Controller.js b/src/animation/Controller.js index cee7115..6cf58cd 100644 --- a/src/animation/Controller.js +++ b/src/animation/Controller.js @@ -17,10 +17,18 @@ function makeSetterGetter (k, f) { } export let easing = { - '-': function (pos) { return pos }, - '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 }, - '>': function (pos) { return Math.sin(pos * Math.PI / 2) }, - '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }, + '-': function (pos) { + return pos + }, + '<>': function (pos) { + return -Math.cos(pos * Math.PI) / 2 + 0.5 + }, + '>': function (pos) { + return Math.sin(pos * Math.PI / 2) + }, + '<': function (pos) { + return -Math.cos(pos * Math.PI / 2) + 1 + }, bezier: function (x1, y1, x2, y2) { // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo return function (t) { @@ -85,7 +93,9 @@ export let easing = { } export class Stepper { - done () { return false } + done () { + return false + } } /*** @@ -166,9 +176,9 @@ export class Spring extends Controller { // Apply the control to get the new position and store it var acceleration = -this.d * velocity - this.k * (current - target) - var newPosition = current + - velocity * dt + - acceleration * dt * dt / 2 + var newPosition = current + + velocity * dt + + acceleration * dt * dt / 2 // Store the velocity c.velocity = velocity + acceleration * dt diff --git a/src/animation/Morphable.js b/src/animation/Morphable.js index 4d52066..56ffe95 100644 --- a/src/animation/Morphable.js +++ b/src/animation/Morphable.js @@ -80,10 +80,17 @@ export default class Morphable { } } - var result = (new this._type(value)).toArray() + var result = (new this._type(value)) + if (this._type === Color) { + result = this._to ? result[this._to[4]]() + : this._from ? result[this._from[4]]() + : result + } + result = result.toArray() + this._morphObj = this._morphObj || new this._type() - this._context = this._context || - Array.apply(null, Array(result.length)).map(Object) + this._context = this._context + || Array.apply(null, Array(result.length)).map(Object) return result } @@ -129,7 +136,7 @@ export class NonMorphable { } toArray () { - return [this.value] + return [ this.value ] } } diff --git a/src/animation/Runner.js b/src/animation/Runner.js index 125c4c5..3af5823 100644 --- a/src/animation/Runner.js +++ b/src/animation/Runner.js @@ -230,7 +230,7 @@ export default class Runner extends EventTarget { var endTime = t * (w + d) - w position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) - : Math.round(f(endTime - 1e-5)) + : Math.round(f(endTime - 1e-5)) return position } @@ -355,7 +355,7 @@ export default class Runner extends EventTarget { // which has access to the outer scope if (this._history[method].caller.retarget) { this._history[method].caller.retarget(target) - // for everything else a simple morpher change is sufficient + // for everything else a simple morpher change is sufficient } else { this._history[method].morpher.to(target) } @@ -470,7 +470,7 @@ class FakeRunner { clearTransformsFromQueue () { } } -extend([Runner, FakeRunner], { +extend([ Runner, FakeRunner ], { mergeWith (runner) { return new FakeRunner( runner.transforms.lmultiply(this.transforms), @@ -729,7 +729,7 @@ extend(Runner, { let { x, y } = new Point(origin).transform(element._currentTransform(this)) - let target = new Matrix({ ...transforms, origin: [x, y] }) + let target = new Matrix({ ...transforms, origin: [ x, y ] }) let start = this._isDeclarative && current ? current : startTransform @@ -743,7 +743,7 @@ extend(Runner, { const rCurrent = start.rotate // Figure out the shortest path to rotate directly - const possibilities = [rTarget - 360, rTarget, rTarget + 360] + const possibilities = [ rTarget - 360, rTarget, rTarget + 360 ] const distances = possibilities.map(a => Math.abs(a - rCurrent)) const shortest = Math.min(...distances) const index = distances.indexOf(shortest) @@ -775,8 +775,8 @@ extend(Runner, { function retarget (newTransforms) { // only get a new origin if it changed since the last call if ( - (newTransforms.origin || 'center').toString() !== - (transforms.origin || 'center').toString() + (newTransforms.origin || 'center').toString() + !== (transforms.origin || 'center').toString() ) { origin = getOrigin(transforms, element) } @@ -911,7 +911,7 @@ extend(Runner, { plot (a, b, c, d) { // Lines can be plotted with 4 arguments if (arguments.length === 4) { - return this.plot([a, b, c, d]) + return this.plot([ a, b, c, d ]) } var morpher = this._element.MorphArray().to(a) diff --git a/src/elements/A.js b/src/elements/A.js index 722deed..ef047a2 100644 --- a/src/elements/A.js +++ b/src/elements/A.js @@ -31,7 +31,9 @@ registerMethods({ linkTo: function (url) { var link = new A() - if (typeof url === 'function') { url.call(link, link) } else { + if (typeof url === 'function') { + url.call(link, link) + } else { link.to(url) } diff --git a/src/elements/Defs.js b/src/elements/Defs.js index 2826611..6b486ca 100644 --- a/src/elements/Defs.js +++ b/src/elements/Defs.js @@ -6,8 +6,12 @@ export default class Defs extends Container { super(nodeOrNew('defs', node), node) } - flatten () { return this } - ungroup () { return this } + flatten () { + return this + } + ungroup () { + return this + } } register(Defs) diff --git a/src/elements/Dom.js b/src/elements/Dom.js index fa4ed08..ff33d46 100644 --- a/src/elements/Dom.js +++ b/src/elements/Dom.js @@ -79,7 +79,7 @@ export default class Dom extends EventTarget { var i, il for (i = 0, il = children.length; i < il; i++) { - block.apply(children[i], [i, children]) + block.apply(children[i], [ i, children ]) if (deep) { children[i].each(block, deep) @@ -260,7 +260,7 @@ export default class Dom extends EventTarget { if (result === false) { this.remove() - // If modifier returns new node, use it + // If modifier returns new node, use it } else if (result && this !== _this) { this.replace(_this) } diff --git a/src/elements/Element.js b/src/elements/Element.js index 91aa3e0..594daa1 100644 --- a/src/elements/Element.js +++ b/src/elements/Element.js @@ -74,10 +74,10 @@ export default class Element extends Dom { inside (x, y) { let box = this.bbox() - return x > box.x && - y > box.y && - x < box.x + box.width && - y < box.y + box.height + return x > box.x + && y > box.y + && x < box.x + box.width + && y < box.y + box.height } // Move element to given x and y values @@ -92,9 +92,9 @@ export default class Element extends Dom { let parent = this while ( - (parent = parent.parent()) && - parent.node !== until.node && - parent.node !== globals.document + (parent = parent.parent()) + && parent.node !== until.node + && parent.node !== globals.document ) { parents.push(parent) } diff --git a/src/elements/Line.js b/src/elements/Line.js index 99e7497..edf10e7 100644 --- a/src/elements/Line.js +++ b/src/elements/Line.js @@ -59,7 +59,7 @@ registerMethods({ // x1 is not necessarily a number, it can also be an array, a string and a PointArray return Line.prototype.plot.apply( this.put(new Line()) - , args[0] != null ? args : [0, 0, 0, 0] + , args[0] != null ? args : [ 0, 0, 0, 0 ] ) }) } diff --git a/src/elements/Marker.js b/src/elements/Marker.js index d40d13b..238f559 100644 --- a/src/elements/Marker.js +++ b/src/elements/Marker.js @@ -29,7 +29,9 @@ export default class Marker extends Container { this.clear() // invoke passed block - if (typeof block === 'function') { block.call(this, this) } + if (typeof block === 'function') { + block.call(this, this) + } return this } @@ -62,7 +64,7 @@ registerMethods({ marker: { // Create and attach markers marker (marker, width, height, block) { - var attr = ['marker'] + var attr = [ 'marker' ] // Build attribute name if (marker !== 'all') attr.push(marker) diff --git a/src/elements/Svg.js b/src/elements/Svg.js index 6172454..ab7d89f 100644 --- a/src/elements/Svg.js +++ b/src/elements/Svg.js @@ -17,9 +17,9 @@ export default class Svg extends Container { } isRoot () { - return !this.node.parentNode || - !(this.node.parentNode instanceof globals.window.SVGElement) || - this.node.parentNode.nodeName === '#document' + return !this.node.parentNode + || !(this.node.parentNode instanceof globals.window.SVGElement) + || this.node.parentNode.nodeName === '#document' } // Check if this is a root svg @@ -42,8 +42,8 @@ export default class Svg extends Container { defs () { if (!this.isRoot()) return this.root().defs() - return adopt(this.node.getElementsByTagName('defs')[0]) || - this.put(new Defs()) + return adopt(this.node.getElementsByTagName('defs')[0]) + || this.put(new Defs()) } // custom parent method diff --git a/src/modules/core/attr.js b/src/modules/core/attr.js index f90dcb9..79dd0d7 100644 --- a/src/modules/core/attr.js +++ b/src/modules/core/attr.js @@ -41,7 +41,7 @@ export default function attr (attr, val, ns) { val = this.node.getAttribute(attr) return val == null ? defaults[attr] : isNumber.test(val) ? parseFloat(val) - : val + : val } else { // Loop through hooks and execute them to convert value val = hooks.reduce((_val, hook) => { diff --git a/src/modules/core/event.js b/src/modules/core/event.js index a52a744..507e91f 100644 --- a/src/modules/core/event.js +++ b/src/modules/core/event.js @@ -79,7 +79,9 @@ export function off (node, events, listener, options) { } else if (ev && ns) { // remove all listeners for a namespaced event if (bag[ev] && bag[ev][ns]) { - for (l in bag[ev][ns]) { off(n, [ev, ns].join('.'), l) } + for (l in bag[ev][ns]) { + off(n, [ ev, ns ].join('.'), l) + } delete bag[ev][ns] } @@ -87,19 +89,25 @@ export function off (node, events, listener, options) { // remove all listeners for a specific namespace for (event in bag) { for (namespace in bag[event]) { - if (ns === namespace) { off(n, [event, ns].join('.')) } + if (ns === namespace) { + off(n, [ event, ns ].join('.')) + } } } } else if (ev) { // remove all listeners for the event if (bag[ev]) { - for (namespace in bag[ev]) { off(n, [ev, namespace].join('.')) } + for (namespace in bag[ev]) { + off(n, [ ev, namespace ].join('.')) + } delete bag[ev] } } else { // remove all listeners on a given node - for (event in bag) { off(n, event) } + for (event in bag) { + off(n, event) + } clearEvents(node) } diff --git a/src/modules/core/poly.js b/src/modules/core/poly.js index ad12020..f23b70b 100644 --- a/src/modules/core/poly.js +++ b/src/modules/core/poly.js @@ -10,7 +10,7 @@ export function array () { export function plot (p) { return (p == null) ? this.array() : this.clear().attr('points', typeof p === 'string' ? p - : (this._array = new PointArray(p))) + : (this._array = new PointArray(p))) } // Clear array cache diff --git a/src/modules/optional/css.js b/src/modules/optional/css.js index babee7a..2c97f3e 100644 --- a/src/modules/optional/css.js +++ b/src/modules/optional/css.js @@ -8,7 +8,9 @@ export function css (style, val) { if (arguments.length === 0) { // get full style as object this.node.style.cssText.split(/\s*;\s*/) - .filter(function (el) { return !!el.length }) + .filter(function (el) { + return !!el.length + }) .forEach(function (el) { let t = el.split(/\s*:\s*/) ret[t[0]] = t[1] @@ -35,16 +37,16 @@ export function css (style, val) { if (typeof style === 'object') { for (let name in style) { // set empty string if null/undefined/'' was given - this.node.style[camelCase(name)] = - (style[name] == null || isBlank.test(style[name])) ? '' : style[name] + this.node.style[camelCase(name)] + = (style[name] == null || isBlank.test(style[name])) ? '' : style[name] } } } // set style for property if (arguments.length === 2) { - this.node.style[camelCase(style)] = - (val == null || isBlank.test(val)) ? '' : val + this.node.style[camelCase(style)] + = (val == null || isBlank.test(val)) ? '' : val } return this diff --git a/src/modules/optional/data.js b/src/modules/optional/data.js index 341d129..4c163c0 100644 --- a/src/modules/optional/data.js +++ b/src/modules/optional/data.js @@ -15,8 +15,8 @@ export function data (a, v, r) { } else { this.attr('data-' + a, v === null ? null - : r === true || typeof v === 'string' || typeof v === 'number' ? v - : JSON.stringify(v) + : r === true || typeof v === 'string' || typeof v === 'number' ? v + : JSON.stringify(v) ) } diff --git a/src/modules/optional/sugar.js b/src/modules/optional/sugar.js index 6001631..18f3e78 100644 --- a/src/modules/optional/sugar.js +++ b/src/modules/optional/sugar.js @@ -8,15 +8,15 @@ import SVGNumber from '../../types/SVGNumber.js' // Define list of available attributes for stroke and fill var sugar = { - stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'], - fill: ['color', 'opacity', 'rule'], + stroke: [ 'color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset' ], + fill: [ 'color', 'opacity', 'rule' ], prefix: function (t, a) { return a === 'color' ? t : t + '-' + a } } // Add sugar for fill and stroke -;['fill', 'stroke'].forEach(function (m) { +;[ 'fill', 'stroke' ].forEach(function (m) { var extension = {} var i @@ -24,7 +24,7 @@ var sugar = { if (typeof o === 'undefined') { return this.attr(m) } - if (typeof o === 'string' || Color.isRgb(o) || (o instanceof Element)) { + if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || (o instanceof Element)) { this.attr(m, o) } else { // set all attributes from sugar.fill and sugar.stroke list @@ -38,10 +38,10 @@ var sugar = { return this } - registerMethods(['Shape', 'Runner'], extension) + registerMethods([ 'Shape', 'Runner' ], extension) }) -registerMethods(['Element', 'Runner'], { +registerMethods([ 'Element', 'Runner' ], { // Let the user set the matrix directly matrix: function (mat, b, c, d, e, f) { // Act as a getter @@ -62,7 +62,7 @@ registerMethods(['Element', 'Runner'], { skew: function (x, y, cx, cy) { return arguments.length === 1 || arguments.length === 3 ? this.transform({ skew: x, ox: y, oy: cx }, true) - : this.transform({ skew: [x, y], ox: cx, oy: cy }, true) + : this.transform({ skew: [ x, y ], ox: cx, oy: cy }, true) }, shear: function (lam, cx, cy) { @@ -73,29 +73,29 @@ registerMethods(['Element', 'Runner'], { scale: function (x, y, cx, cy) { return arguments.length === 1 || arguments.length === 3 ? this.transform({ scale: x, ox: y, oy: cx }, true) - : this.transform({ scale: [x, y], ox: cx, oy: cy }, true) + : this.transform({ scale: [ x, y ], ox: cx, oy: cy }, true) }, // Map translate to transform translate: function (x, y) { - return this.transform({ translate: [x, y] }, true) + return this.transform({ translate: [ x, y ] }, true) }, // Map relative translations to transform relative: function (x, y) { - return this.transform({ relative: [x, y] }, true) + return this.transform({ relative: [ x, y ] }, true) }, // Map flip to transform flip: function (direction, around) { var directionString = typeof direction === 'string' ? direction : isFinite(direction) ? 'both' - : 'both' - var origin = (direction === 'both' && isFinite(around)) ? [around, around] - : (direction === 'x') ? [around, 0] - : (direction === 'y') ? [0, around] - : isFinite(direction) ? [direction, direction] - : [0, 0] + : 'both' + var origin = (direction === 'both' && isFinite(around)) ? [ around, around ] + : (direction === 'x') ? [ around, 0 ] + : (direction === 'y') ? [ 0, around ] + : isFinite(direction) ? [ direction, direction ] + : [ 0, 0 ] this.transform({ flip: directionString, origin: origin }, true) }, @@ -143,7 +143,7 @@ registerMethods('Path', { } }) -registerMethods(['Element', 'Runner'], { +registerMethods([ 'Element', 'Runner' ], { // Set font font: function (a, v) { if (typeof a === 'object') { diff --git a/src/modules/optional/transform.js b/src/modules/optional/transform.js index b8f4c74..0d0c7e3 100644 --- a/src/modules/optional/transform.js +++ b/src/modules/optional/transform.js @@ -15,9 +15,11 @@ export function matrixify () { .split(transforms).slice(0, -1).map(function (str) { // generate key => value pairs var kv = str.trim().split('(') - return [kv[0], + return [ kv[0], kv[1].split(delimiter) - .map(function (str) { return parseFloat(str) }) + .map(function (str) { + return parseFloat(str) + }) ] }) .reverse() diff --git a/src/types/Box.js b/src/types/Box.js index a08ad67..d2ddcdd 100644 --- a/src/types/Box.js +++ b/src/types/Box.js @@ -24,13 +24,13 @@ export default class Box { } init (source) { - var base = [0, 0, 0, 0] + var base = [ 0, 0, 0, 0 ] source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source - : typeof source === 'object' ? [source.left != null ? source.left - : source.x, source.top != null ? source.top : source.y, source.width, source.height] - : arguments.length === 4 ? [].slice.call(arguments) - : base + : typeof source === 'object' ? [ source.left != null ? source.left + : source.x, source.top != null ? source.top : source.y, source.width, source.height ] + : arguments.length === 4 ? [].slice.call(arguments) + : base this.x = source[0] || 0 this.y = source[1] || 0 @@ -96,7 +96,7 @@ export default class Box { } toArray () { - return [this.x, this.y, this.width, this.height] + return [ this.x, this.y, this.width, this.height ] } isNulled () { diff --git a/src/types/Color.js b/src/types/Color.js index a96958b..a1329aa 100644 --- a/src/types/Color.js +++ b/src/types/Color.js @@ -1,36 +1,7 @@ -/* - -Color { - constructor (a, b, c, space) { - space: 'hsl' - a: 30 - b: 20 - c: 10 - }, - - toRgb () { return new Color in rgb space } - toHsl () { return new Color in hsl space } - toLab () { return new Color in lab space } - - toArray () { [space, a, b, c] } - fromArray () { convert it back } -} - -// Conversions aren't always exact because of monitor profiles etc... -new Color(h, s, l, 'hsl') !== new Color(r, g, b).hsl() -new Color(100, 100, 100, [space]) -new Color('hsl(30, 20, 10)') - -// Sugar -SVG.rgb(30, 20, 50).lab() -SVG.hsl() -SVG.lab('rgb(100, 100, 100)') -*/ import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js' -// Ensure to six-based hex -function fullHex (hex) { +function sixDigitHex (hex) { return hex.length === 4 ? [ '#', hex.substring(1, 2), hex.substring(1, 2), @@ -40,93 +11,412 @@ function fullHex (hex) { : hex } -// Component to hex value -function compToHex (comp) { - var hex = comp.toString(16) +function componentHex (component) { + const integer = Math.round(component) + const bounded = Math.max(0, Math.min(255, integer)) + const hex = bounded.toString(16) return hex.length === 1 ? '0' + hex : hex } +function is (object, space) { + for (let i = space.length; i--;) { + if (object[space[i]] == null) { + return false + } + } + return true +} + +function getParameters (a, b) { + const params = is(a, 'rgb') ? { _a: a.r, _b: a.g, _c: a.b, space: 'rgb' } + : is(a, 'xyz') ? { _a: a.x, _b: a.y, _c: a.z, _d: 0, space: 'xyz' } + : is(a, 'hsl') ? { _a: a.h, _b: a.s, _c: a.l, _d: 0, space: 'hsl' } + : is(a, 'lab') ? { _a: a.l, _b: a.a, _c: a.b, _d: 0, space: 'lab' } + : is(a, 'lch') ? { _a: a.l, _b: a.c, _c: a.h, _d: 0, space: 'lch' } + : is(a, 'cmyk') ? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' } + : { _a: 0, _b: 0, _c: 0, space: 'rgb' } + + params.space = b || params.space + return params +} + +function cieSpace (space) { + if (space === 'lab' || space === 'xyz' || space === 'lch') { + return true + } else { + return false + } +} + +function hueToRgb (p, q, t) { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1 / 6) return p + (q - p) * 6 * t + if (t < 1 / 2) return q + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 + return p +} + export default class Color { - constructor (...args) { - this.init(...args) - } - - init (color, g, b) { - let match - - // initialize defaults - this.r = 0 - this.g = 0 - this.b = 0 - - if (!color) return - - // parse color - if (typeof color === 'string') { - if (isRgb.test(color)) { - // get rgb values - match = rgb.exec(color.replace(whitespace, '')) - - // parse numeric values - this.r = parseInt(match[1]) - this.g = parseInt(match[2]) - this.b = parseInt(match[3]) - } else if (isHex.test(color)) { - // get hex values - match = hex.exec(fullHex(color)) - - // parse numeric values - this.r = parseInt(match[1], 16) - this.g = parseInt(match[2], 16) - this.b = parseInt(match[3], 16) + constructor (...inputs) { + this.init(...inputs) + } + + init (a = 0, b = 0, c = 0, d = 0, space = 'rgb') { + // Reset all values in case the init function is rerun with new color space + if (this.space) { + for (let component in this.space) { + delete this[this.space[component]] } - } else if (Array.isArray(color)) { - this.r = color[0] - this.g = color[1] - this.b = color[2] - } else if (typeof color === 'object') { - this.r = color.r - this.g = color.g - this.b = color.b - } else if (arguments.length === 3) { - this.r = color - this.g = g - this.b = b } - return this + if (typeof a === 'number') { + // Allow for the case that we don't need d... + space = typeof d === 'string' ? d : space + d = typeof d === 'string' ? 0 : d + + // Assign the values straight to the color + Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space }) + // If the user gave us an array, make the color from it + } else if (a instanceof Array) { + this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb' + Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] || 0 }) + } else if (a instanceof Object) { + // Set the object up and assign its values directly + const values = getParameters(a, b) + Object.assign(this, values) + } else if (typeof a === 'string') { + if (isRgb.test(a)) { + const noWhitespace = a.replace(whitespace, '') + const [ _a, _b, _c ] = rgb.exec(noWhitespace) + .slice(1, 4).map(v => parseInt(v)) + Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' }) + } else if (isHex.test(a)) { + const hexParse = v => parseInt(v, 16) + const [ , _a, _b, _c ] = hex.exec(sixDigitHex(a)).map(hexParse) + Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' }) + } else throw Error(`Unsupported string format, can't construct Color`) + } + + // Now add the components as a convenience + const { _a, _b, _c, _d } = this + const components = this.space === 'rgb' ? { r: _a, g: _b, b: _c } + : this.space === 'xyz' ? { x: _a, y: _b, z: _c } + : this.space === 'hsl' ? { h: _a, s: _b, l: _c } + : this.space === 'lab' ? { l: _a, a: _b, b: _c } + : this.space === 'lch' ? { l: _a, c: _b, h: _c } + : this.space === 'cmyk' ? { c: _a, m: _b, y: _c, k: _d } + : {} + Object.assign(this, components) } - // Default to hex conversion - toString () { - return this.toHex() + /* + Conversion Methods + */ + + rgb () { + if (this.space === 'rgb') { + return this + } else if (cieSpace(this.space)) { + // Convert to the xyz color space + let { x, y, z } = this + if (this.space === 'lab' || this.space === 'lch') { + // Get the values in the lab space + let { l, a, b } = this + if (this.space === 'lch') { + let { c, h } = this + const dToR = Math.PI / 180 + a = c * Math.cos(dToR * h) + b = c * Math.sin(dToR * h) + } + + // Undo the nonlinear function + const yL = (l + 16) / 116 + const xL = a / 500 + yL + const zL = yL - b / 200 + + // Get the xyz values + const ct = 16 / 116 + const mx = 0.008856 + const nm = 7.787 + x = 0.95047 * ((xL ** 3 > mx) ? xL ** 3 : (xL - ct) / nm) + y = 1.00000 * ((yL ** 3 > mx) ? yL ** 3 : (yL - ct) / nm) + z = 1.08883 * ((zL ** 3 > mx) ? zL ** 3 : (zL - ct) / nm) + } + + // Convert xyz to unbounded rgb values + const rU = x * 3.2406 + y * -1.5372 + z * -0.4986 + const gU = x * -0.9689 + y * 1.8758 + z * 0.0415 + const bU = x * 0.0557 + y * -0.2040 + z * 1.0570 + + // Convert the values to true rgb values + let pow = Math.pow + let bd = 0.0031308 + const r = (rU > bd) ? (1.055 * pow(rU, 1 / 2.4) - 0.055) : 12.92 * rU + const g = (gU > bd) ? (1.055 * pow(gU, 1 / 2.4) - 0.055) : 12.92 * gU + const b = (bU > bd) ? (1.055 * pow(bU, 1 / 2.4) - 0.055) : 12.92 * bU + + // Make and return the color + const color = new Color(255 * r, 255 * g, 255 * b) + return color + } else if (this.space === 'hsl') { + // https://bgrins.github.io/TinyColor/docs/tinycolor.html + // Get the current hsl values + let { h, s, l } = this + h /= 360 + s /= 100 + l /= 100 + + // If we are grey, then just make the color directly + if (s === 0) { + l *= 255 + let color = new Color(l, l, l) + return color + } + + // TODO I have no idea what this does :D If you figure it out, tell me! + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + + // Get the rgb values + const r = 255 * hueToRgb(p, q, h + 1 / 3) + const g = 255 * hueToRgb(p, q, h) + const b = 255 * hueToRgb(p, q, h - 1 / 3) + + // Make a new color + const color = new Color(r, g, b) + return color + } else if (this.space === 'cmyk') { + // https://gist.github.com/felipesabino/5066336 + // Get the normalised cmyk values + const { c, m, y, k } = this + + // Get the rgb values + const r = 255 * (1 - Math.min(1, c * (1 - k) + k)) + const g = 255 * (1 - Math.min(1, m * (1 - k) + k)) + const b = 255 * (1 - Math.min(1, y * (1 - k) + k)) + + // Form the color and return it + const color = new Color(r, g, b) + return color + } else { + return this + } } - toArray () { - return [this.r, this.g, this.b] + lab () { + // Get the xyz color + const { x, y, z } = this.xyz() + + // Get the lab components + const l = (116 * y) - 16 + const a = 500 * (x - y) + const b = 200 * (y - z) + + // Construct and return a new color + const color = new Color(l, a, b, 'lab') + return color + } + + xyz () { + + // Normalise the red, green and blue values + const { _a: r255, _b: g255, _c: b255 } = this.rgb() + const [ r, g, b ] = [ r255, g255, b255 ].map(v => v / 255) + + // Convert to the lab rgb space + const rL = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92 + const gL = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92 + const bL = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92 + + // Convert to the xyz color space without bounding the values + const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047 + const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000 + const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883 + + // Get the proper xyz values by applying the bounding + const x = (xU > 0.008856) ? Math.pow(xU, 1 / 3) : (7.787 * xU) + 16 / 116 + const y = (yU > 0.008856) ? Math.pow(yU, 1 / 3) : (7.787 * yU) + 16 / 116 + const z = (zU > 0.008856) ? Math.pow(zU, 1 / 3) : (7.787 * zU) + 16 / 116 + + // Make and return the color + const color = new Color(x, y, z, 'xyz') + return color + } + + lch () { + + // Get the lab color directly + const { l, a, b } = this.lab() + + // Get the chromaticity and the hue using polar coordinates + const c = Math.sqrt(a ** 2 + b ** 2) + let h = 180 * Math.atan2(b, a) / Math.PI + if (h < 0) { + h *= -1 + h = 360 - h + } + + // Make a new color and return it + const color = new Color(l, c, h, 'lch') + return color + } + + hsl () { + + // Get the rgb values + const { _a, _b, _c } = this.rgb() + const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255) + + // Find the maximum and minimum values to get the lightness + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const l = (max + min) / 2 + + // If the r, g, v values are identical then we are grey + const isGrey = max === min + + // Calculate the hue and saturation + const delta = max - min + const s = isGrey ? 0 + : l > 0.5 ? delta / (2 - max - min) + : delta / (max + min) + const h = isGrey ? 0 + : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 + : max === g ? ((b - r) / delta + 2) / 6 + : max === b ? ((r - g) / delta + 4) / 6 + : 0 + + // Construct and return the new color + const color = new Color(360 * h, 100 * s, 100 * l, 'hsl') + return color + } + + cmyk () { + + // Get the rgb values for the current color + const { _a, _b, _c } = this.rgb() + const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255) + + // Get the cmyk values in an unbounded format + const k = Math.min(1 - r, 1 - g, 1 - b) + + if (k === 1) { + // Catch the black case + return new Color(0, 0, 0, 1, 'cmyk') + } + + const c = (1 - r - k) / (1 - k) + const m = (1 - g - k) / (1 - k) + const y = (1 - b - k) / (1 - k) + + // Construct the new color + const color = new Color(c, m, y, k, 'cmyk') + return color + } + + /* + Input and Output methods + */ + + _clamped () { + let { _a, _b, _c } = this.rgb() + let { max, min, round } = Math + let format = v => max(0, min(round(v), 255)) + return [ _a, _b, _c ].map(format) } - // Build hex value toHex () { - return '#' + - compToHex(Math.round(this.r)) + - compToHex(Math.round(this.g)) + - compToHex(Math.round(this.b)) + let [ r, g, b ] = this._clamped().map(componentHex) + return `#${r}${g}${b}` + } + + toString () { + return this.toHex() } - // Build rgb value toRgb () { - return 'rgb(' + [this.r, this.g, this.b].join() + ')' + let [ rV, gV, bV ] = this._clamped() + let string = `rgb(${rV},${gV},${bV})` + return string + } + + toArray () { + let { _a, _b, _c, _d, space } = this + return [ _a, _b, _c, _d, space ] } - // Calculate true brightness - brightness () { - return (this.r / 255 * 0.30) + - (this.g / 255 * 0.59) + - (this.b / 255 * 0.11) + /* + Generating random colors + */ + + static random (mode = 'vibrant', t, u) { + + // Get the math modules + const { random, round, sin, PI: pi } = Math + + // Run the correct generator + if (mode === 'vibrant') { + + const l = (81 - 57) * random() + 57 + const c = (83 - 45) * random() + 45 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + + } else if (mode === 'sine') { + + t = t == null ? random() : t + const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150) + const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200) + const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150) + const color = new Color(r, g, b) + return color + + } else if (mode === 'pastel') { + + const l = (94 - 86) * random() + 86 + const c = (26 - 9) * random() + 9 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + + } else if (mode === 'dark') { + + const l = 10 + 10 * random() + const c = (125 - 75) * random() + 86 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + + } else if (mode === 'rgb') { + + const r = 255 * random() + const g = 255 * random() + const b = 255 * random() + const color = new Color(r, g, b) + return color + + } else if (mode === 'lab') { + + const l = 100 * random() + const a = 256 * random() - 128 + const b = 256 * random() - 128 + const color = new Color(l, a, b, 'lab') + return color + + } else if (mode === 'grey') { + + const grey = 255 * random() + const color = new Color(grey, grey, grey) + return color + + } } - // Testers + /* + Constructing colors + */ // Test if given value is a color string static test (color) { @@ -136,9 +426,9 @@ export default class Color { // Test if given value is a rgb object static isRgb (color) { - return color && typeof color.r === 'number' && - typeof color.g === 'number' && - typeof color.b === 'number' + return color && typeof color.r === 'number' + && typeof color.g === 'number' + && typeof color.b === 'number' } // Test if given value is a color diff --git a/src/types/List.js b/src/types/List.js index 8bd3985..ccdf11d 100644 --- a/src/types/List.js +++ b/src/types/List.js @@ -13,9 +13,13 @@ export default List extend(List, { each (fnOrMethodName, ...args) { if (typeof fnOrMethodName === 'function') { - this.forEach((el) => { fnOrMethodName.call(el, el) }) + this.forEach((el) => { + fnOrMethodName.call(el, el) + }) } else { - return this.map(el => { return el[fnOrMethodName](...args) }) + return this.map(el => { + return el[fnOrMethodName](...args) + }) } return this diff --git a/src/types/Matrix.js b/src/types/Matrix.js index a1eb317..102192b 100644 --- a/src/types/Matrix.js +++ b/src/types/Matrix.js @@ -14,16 +14,16 @@ export default class Matrix { // Initialize init (source) { - var base = Matrix.fromArray([1, 0, 0, 1, 0, 0]) + var base = Matrix.fromArray([ 1, 0, 0, 1, 0, 0 ]) // ensure source as object source = source instanceof Element ? source.matrixify() : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) - : Array.isArray(source) ? Matrix.fromArray(source) - : (typeof source === 'object' && Matrix.isMatrixLike(source)) ? source - : (typeof source === 'object') ? new Matrix().transform(source) - : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) - : base + : Array.isArray(source) ? Matrix.fromArray(source) + : (typeof source === 'object' && Matrix.isMatrixLike(source)) ? source + : (typeof source === 'object') ? new Matrix().transform(source) + : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) + : base // Merge the source matrix with the base matrix this.a = source.a != null ? source.a : base.a @@ -295,7 +295,7 @@ export default class Matrix { flipO (axis, around) { return axis === 'x' ? this.scaleO(-1, 1, around, 0) : axis === 'y' ? this.scaleO(1, -1, 0, around) - : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point + : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point } // Shear matrix @@ -377,9 +377,9 @@ export default class Matrix { // Check if two matrices are equal equals (other) { var comp = new Matrix(other) - return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && - closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && - closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f) + return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) + && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) + && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f) } // Convert matrix to string @@ -388,7 +388,7 @@ export default class Matrix { } toArray () { - return [this.a, this.b, this.c, this.d, this.e, this.f] + return [ this.a, this.b, this.c, this.d, this.e, this.f ] } valueOf () { @@ -408,12 +408,12 @@ export default class Matrix { static isMatrixLike (o) { return ( - o.a != null || - o.b != null || - o.c != null || - o.d != null || - o.e != null || - o.f != null + o.a != null + || o.b != null + || o.c != null + || o.d != null + || o.e != null + || o.f != null ) } @@ -424,20 +424,20 @@ export default class Matrix { var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1 var skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew - : isFinite(o.skewX) ? o.skewX - : 0 + : isFinite(o.skewX) ? o.skewX + : 0 var skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew - : isFinite(o.skewY) ? o.skewY - : 0 + : isFinite(o.skewY) ? o.skewY + : 0 var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX - : isFinite(o.scaleX) ? o.scaleX * flipX - : flipX + : isFinite(o.scaleX) ? o.scaleX * flipX + : flipX var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY - : isFinite(o.scaleY) ? o.scaleY * flipY - : flipY + : isFinite(o.scaleY) ? o.scaleY * flipY + : flipY var shear = o.shear || 0 var theta = o.rotate || o.theta || 0 var origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY) diff --git a/src/types/PathArray.js b/src/types/PathArray.js index 989cd8f..764d05c 100644 --- a/src/types/PathArray.js +++ b/src/types/PathArray.js @@ -61,50 +61,50 @@ const pathHandlers = { p.x = p0.x = c[0] p.y = p0.y = c[1] - return ['M', p.x, p.y] + return [ 'M', p.x, p.y ] }, L: function (c, p) { p.x = c[0] p.y = c[1] - return ['L', c[0], c[1]] + return [ 'L', c[0], c[1] ] }, H: function (c, p) { p.x = c[0] - return ['H', c[0]] + return [ 'H', c[0] ] }, V: function (c, p) { p.y = c[0] - return ['V', c[0]] + return [ 'V', c[0] ] }, C: function (c, p) { p.x = c[4] p.y = c[5] - return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] + return [ 'C', c[0], c[1], c[2], c[3], c[4], c[5] ] }, S: function (c, p) { p.x = c[2] p.y = c[3] - return ['S', c[0], c[1], c[2], c[3]] + return [ 'S', c[0], c[1], c[2], c[3] ] }, Q: function (c, p) { p.x = c[2] p.y = c[3] - return ['Q', c[0], c[1], c[2], c[3]] + return [ 'Q', c[0], c[1], c[2], c[3] ] }, T: function (c, p) { p.x = c[0] p.y = c[1] - return ['T', c[0], c[1]] + return [ 'T', c[0], c[1] ] }, Z: function (c, p, p0) { p.x = p0.x p.y = p0.y - return ['Z'] + return [ 'Z' ] }, A: function (c, p) { p.x = c[5] p.y = c[6] - return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] + return [ 'A', c[0], c[1], c[2], c[3], c[4], c[5], c[6] ] } } @@ -258,7 +258,7 @@ extend(PathArray, { // Animate has specified in the SVG spec // See: https://www.w3.org/TR/SVG11/paths.html#PathElement for (i = 0, il = sourceArray.length; i < il; i++) { - array[i] = [sourceArray[i][0]] + array[i] = [ sourceArray[i][0] ] for (j = 1, jl = sourceArray[i].length; j < jl; j++) { array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos } @@ -279,7 +279,7 @@ extend(PathArray, { }, // Absolutize and parse path to array - parse (array = [['M', 0, 0]]) { + parse (array = [ [ 'M', 0, 0 ] ]) { // if it's already a patharray, no need to parse it if (array instanceof PathArray) return array @@ -312,7 +312,7 @@ extend(PathArray, { if (isPathLetter.test(array[index])) { s = array[index] ++index - // If last letter was a move command and we got no new, it defaults to [L]ine + // If last letter was a move command and we got no new, it defaults to [L]ine } else if (s === 'M') { s = 'L' } else if (s === 'm') { diff --git a/src/types/Point.js b/src/types/Point.js index 27d81ea..f1c85a1 100644 --- a/src/types/Point.js +++ b/src/types/Point.js @@ -11,7 +11,7 @@ export default class Point { // ensure source as object source = Array.isArray(x) ? { x: x[0], y: x[1] } : typeof x === 'object' ? { x: x.x, y: x.y } - : { x: x, y: y } + : { x: x, y: y } // merge source this.x = source.x == null ? base.x : source.x @@ -36,7 +36,7 @@ export default class Point { } toArray () { - return [this.x, this.y] + return [ this.x, this.y ] } } diff --git a/src/types/PointArray.js b/src/types/PointArray.js index b246b2f..9e7406d 100644 --- a/src/types/PointArray.js +++ b/src/types/PointArray.js @@ -45,7 +45,7 @@ extend(PointArray, { }, // Parse point string and flat array - parse (array = [[0, 0]]) { + parse (array = [ [ 0, 0 ] ]) { var points = [] // if it is an array @@ -82,7 +82,7 @@ extend(PointArray, { // move every point if (!isNaN(x) && !isNaN(y)) { for (var i = this.length - 1; i >= 0; i--) { - this[i] = [this[i][0] + x, this[i][1] + y] + this[i] = [ this[i][0] + x, this[i][1] + y ] } } diff --git a/src/types/SVGNumber.js b/src/types/SVGNumber.js index ea21cbd..a152667 100644 --- a/src/types/SVGNumber.js +++ b/src/types/SVGNumber.js @@ -27,7 +27,9 @@ export default class SVGNumber { this.value = parseFloat(unit[1]) // normalize - if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') { + if (unit[5] === '%') { + this.value /= 100 + } else if (unit[5] === 's') { this.value *= 1000 } @@ -47,7 +49,7 @@ export default class SVGNumber { toString () { return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 - : this.value + : this.value ) + this.unit } @@ -56,7 +58,7 @@ export default class SVGNumber { } toArray () { - return [this.value, this.unit] + return [ this.value, this.unit ] } valueOf () { diff --git a/src/utils/adopter.js b/src/utils/adopter.js index 4f50ca0..3e86d8a 100644 --- a/src/utils/adopter.js +++ b/src/utils/adopter.js @@ -110,7 +110,7 @@ export function assignNewId (node) { export function extend (modules, methods, attrCheck) { var key, i - modules = Array.isArray(modules) ? modules : [modules] + modules = Array.isArray(modules) ? modules : [ modules ] for (i = modules.length - 1; i >= 0; i--) { for (key in methods) { diff --git a/src/utils/methods.js b/src/utils/methods.js index bf30a1e..bc05a70 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -25,7 +25,7 @@ export function getMethodsFor (name) { } export function getMethodNames () { - return [...new Set(names)] + return [ ...new Set(names) ] } export function addMethodNames (_names) { diff --git a/src/utils/utils.js b/src/utils/utils.js index 64c0eed..3bac0de 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -18,7 +18,9 @@ export function filter (array, block) { var result = [] for (i = 0; i < il; i++) { - if (block(array[i])) { result.push(array[i]) } + if (block(array[i])) { + result.push(array[i]) + } } return result @@ -85,10 +87,10 @@ export function getOrigin (o, element) { // Calculate the transformed x and y coordinates let bx = string.includes('left') ? x : string.includes('right') ? x + width - : x + width / 2 + : x + width / 2 let by = string.includes('top') ? y : string.includes('bottom') ? y + height - : y + height / 2 + : y + height / 2 // Set the bounds eg : "bottom-left", "Top right", "middle" etc... ox = o.ox != null ? o.ox : bx |