(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["BezierArticle"] = factory(); else root["BezierArticle"] = factory(); })(window, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 140); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var Locale = __webpack_require__(11); var locale = new Locale(); module.exports = function generateBase(page, handler) { // the basic class just has a title and basic content. var ComponentClass = { getDefaultProps: function getDefaultProps() { return { page: page, title: locale.getTitle(page), handler: handler }; }, render: function render() { return locale.getContent(page, this); } }; // if the content requires code bindings, ensure those exist: if (handler) { Object.keys(handler).forEach(function (key) { ComponentClass[key] = handler[key]; }); } // then build the actual React class return React.createClass(ComponentClass); }; /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* WEBPACK VAR INJECTION */(function(process) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "version", function() { return version; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DOM", function() { return DOM; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Children", function() { return Children; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "render", function() { return render$1; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "createClass", function() { return createClass; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "createFactory", function() { return createFactory; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "createElement", function() { return createElement; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "cloneElement", function() { return cloneElement$1; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isValidElement", function() { return isValidElement; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findDOMNode", function() { return findDOMNode; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unmountComponentAtNode", function() { return unmountComponentAtNode; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Component", function() { return Component$1; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PureComponent", function() { return PureComponent; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unstable_renderSubtreeIntoContainer", function() { return renderSubtreeIntoContainer; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return extend; }); /* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_0__); /* harmony reexport (default from non-harmony) */ __webpack_require__.d(__webpack_exports__, "PropTypes", function() { return prop_types__WEBPACK_IMPORTED_MODULE_0___default.a; }); /* harmony import */ var preact__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); var version = '15.1.0'; // trick libraries to think we are react var ELEMENTS = 'a abbr address area article aside audio b base bdi bdo big blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td textarea tfoot th thead time title tr track u ul var video wbr circle clipPath defs ellipse g image line linearGradient mask path pattern polygon polyline radialGradient rect stop svg text tspan'.split(' '); var REACT_ELEMENT_TYPE = (typeof Symbol!=='undefined' && Symbol.for && Symbol.for('react.element')) || 0xeac7; var COMPONENT_WRAPPER_KEY = (typeof Symbol!=='undefined' && Symbol.for) ? Symbol.for('__preactCompatWrapper') : '__preactCompatWrapper'; // don't autobind these methods since they already have guaranteed context. var AUTOBIND_BLACKLIST = { constructor: 1, render: 1, shouldComponentUpdate: 1, componentWillReceiveProps: 1, componentWillUpdate: 1, componentDidUpdate: 1, componentWillMount: 1, componentDidMount: 1, componentWillUnmount: 1, componentDidUnmount: 1 }; var CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip|color|fill|flood|font|glyph|horiz|marker|overline|paint|stop|strikethrough|stroke|text|underline|unicode|units|v|vector|vert|word|writing|x)[A-Z]/; var BYPASS_HOOK = {}; /*global process*/ var DEV = typeof process==='undefined' || !process.env || "production"!=='production'; // a component that renders nothing. Used to replace components for unmountComponentAtNode. function EmptyComponent() { return null; } // make react think we're react. var VNode = Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* h */ "c"])('a', null).constructor; VNode.prototype.$$typeof = REACT_ELEMENT_TYPE; VNode.prototype.preactCompatUpgraded = false; VNode.prototype.preactCompatNormalized = false; Object.defineProperty(VNode.prototype, 'type', { get: function() { return this.nodeName; }, set: function(v) { this.nodeName = v; }, configurable:true }); Object.defineProperty(VNode.prototype, 'props', { get: function() { return this.attributes; }, set: function(v) { this.attributes = v; }, configurable:true }); var oldEventHook = preact__WEBPACK_IMPORTED_MODULE_1__[/* options */ "d"].event; preact__WEBPACK_IMPORTED_MODULE_1__[/* options */ "d"].event = function (e) { if (oldEventHook) { e = oldEventHook(e); } e.persist = Object; e.nativeEvent = e; return e; }; var oldVnodeHook = preact__WEBPACK_IMPORTED_MODULE_1__[/* options */ "d"].vnode; preact__WEBPACK_IMPORTED_MODULE_1__[/* options */ "d"].vnode = function (vnode) { if (!vnode.preactCompatUpgraded) { vnode.preactCompatUpgraded = true; var tag = vnode.nodeName, attrs = vnode.attributes = extend({}, vnode.attributes); if (typeof tag==='function') { if (tag[COMPONENT_WRAPPER_KEY]===true || (tag.prototype && 'isReactComponent' in tag.prototype)) { if (vnode.children && String(vnode.children)==='') { vnode.children = undefined; } if (vnode.children) { attrs.children = vnode.children; } if (!vnode.preactCompatNormalized) { normalizeVNode(vnode); } handleComponentVNode(vnode); } } else { if (vnode.children && String(vnode.children)==='') { vnode.children = undefined; } if (vnode.children) { attrs.children = vnode.children; } if (attrs.defaultValue) { if (!attrs.value && attrs.value!==0) { attrs.value = attrs.defaultValue; } delete attrs.defaultValue; } handleElementVNode(vnode, attrs); } } if (oldVnodeHook) { oldVnodeHook(vnode); } }; function handleComponentVNode(vnode) { var tag = vnode.nodeName, a = vnode.attributes; vnode.attributes = {}; if (tag.defaultProps) { extend(vnode.attributes, tag.defaultProps); } if (a) { extend(vnode.attributes, a); } } function handleElementVNode(vnode, a) { var shouldSanitize, attrs, i; if (a) { for (i in a) { if ((shouldSanitize = CAMEL_PROPS.test(i))) { break; } } if (shouldSanitize) { attrs = vnode.attributes = {}; for (i in a) { if (a.hasOwnProperty(i)) { attrs[ CAMEL_PROPS.test(i) ? i.replace(/([A-Z0-9])/, '-$1').toLowerCase() : i ] = a[i]; } } } } } // proxy render() since React returns a Component reference. function render$1(vnode, parent, callback) { var prev = parent && parent._preactCompatRendered && parent._preactCompatRendered.base; // ignore impossible previous renders if (prev && prev.parentNode!==parent) { prev = null; } // default to first Element child if (!prev && parent) { prev = parent.firstElementChild; } // remove unaffected siblings for (var i=parent.childNodes.length; i--; ) { if (parent.childNodes[i]!==prev) { parent.removeChild(parent.childNodes[i]); } } var out = Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* render */ "e"])(vnode, parent, prev); if (parent) { parent._preactCompatRendered = out && (out._component || { base: out }); } if (typeof callback==='function') { callback(); } return out && out._component || out; } var ContextProvider = function () {}; ContextProvider.prototype.getChildContext = function () { return this.props.context; }; ContextProvider.prototype.render = function (props) { return props.children[0]; }; function renderSubtreeIntoContainer(parentComponent, vnode, container, callback) { var wrap = Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* h */ "c"])(ContextProvider, { context: parentComponent.context }, vnode); var renderContainer = render$1(wrap, container); var component = renderContainer._component || renderContainer.base; if (callback) { callback.call(component, renderContainer); } return component; } function unmountComponentAtNode(container) { var existing = container._preactCompatRendered && container._preactCompatRendered.base; if (existing && existing.parentNode===container) { Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* render */ "e"])(Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* h */ "c"])(EmptyComponent), container, existing); return true; } return false; } var ARR = []; // This API is completely unnecessary for Preact, so it's basically passthrough. var Children = { map: function(children, fn, ctx) { if (children == null) { return null; } children = Children.toArray(children); if (ctx && ctx!==children) { fn = fn.bind(ctx); } return children.map(fn); }, forEach: function(children, fn, ctx) { if (children == null) { return null; } children = Children.toArray(children); if (ctx && ctx!==children) { fn = fn.bind(ctx); } children.forEach(fn); }, count: function(children) { return children && children.length || 0; }, only: function(children) { children = Children.toArray(children); if (children.length!==1) { throw new Error('Children.only() expects only one child.'); } return children[0]; }, toArray: function(children) { if (children == null) { return []; } return ARR.concat(children); } }; /** Track current render() component for ref assignment */ var currentComponent; function createFactory(type) { return createElement.bind(null, type); } var DOM = {}; for (var i=ELEMENTS.length; i--; ) { DOM[ELEMENTS[i]] = createFactory(ELEMENTS[i]); } function upgradeToVNodes(arr, offset) { for (var i=offset || 0; i 0 ) children[ len ] = arguments[ len + 2 ]; if (!isValidElement(element)) { return element; } var elementProps = element.attributes || element.props; var node = Object(preact__WEBPACK_IMPORTED_MODULE_1__[/* h */ "c"])( element.nodeName || element.type, extend({}, elementProps), element.children || elementProps && elementProps.children ); // Only provide the 3rd argument if needed. // Arguments 3+ overwrite element.children in preactCloneElement var cloneArgs = [node, props]; if (children && children.length) { cloneArgs.push(children); } else if (props && props.children) { cloneArgs.push(props.children); } return normalizeVNode(preact__WEBPACK_IMPORTED_MODULE_1__[/* cloneElement */ "b"].apply(void 0, cloneArgs)); } function isValidElement(element) { return element && ((element instanceof VNode) || element.$$typeof===REACT_ELEMENT_TYPE); } function createStringRefProxy(name, component) { return component._refProxies[name] || (component._refProxies[name] = function (resolved) { if (component && component.refs) { component.refs[name] = resolved; if (resolved===null) { delete component._refProxies[name]; component = null; } } }); } function applyEventNormalization(ref) { var nodeName = ref.nodeName; var attributes = ref.attributes; if (!attributes || typeof nodeName!=='string') { return; } var props = {}; for (var i in attributes) { props[i.toLowerCase()] = i; } if (props.ondoubleclick) { attributes.ondblclick = attributes[props.ondoubleclick]; delete attributes[props.ondoubleclick]; } // for *textual inputs* (incl textarea), normalize `onChange` -> `onInput`: if (props.onchange && (nodeName==='textarea' || (nodeName.toLowerCase()==='input' && !/^fil|che|rad/i.test(attributes.type)))) { var normalized = props.oninput || 'oninput'; if (!attributes[normalized]) { attributes[normalized] = multihook([attributes[normalized], attributes[props.onchange]]); delete attributes[props.onchange]; } } } function applyClassName(vnode) { var a = vnode.attributes || (vnode.attributes = {}); classNameDescriptor.enumerable = 'className' in a; if (a.className) { a.class = a.className; } Object.defineProperty(a, 'className', classNameDescriptor); } var classNameDescriptor = { configurable: true, get: function() { return this.class; }, set: function(v) { this.class = v; } }; function extend(base, props) { var arguments$1 = arguments; for (var i=1, obj = (void 0); iHello!` * * can be constructed using this function as: * * `h('div', { id: 'foo', name : 'bar' }, 'Hello!');` * * @param {string} nodeName An element name. Ex: `div`, `a`, `span`, etc. * @param {Object} attributes Any attributes/props to set on the created element. * @param rest Additional arguments are taken to be children to append. Can be infinitely nested Arrays. * * @public */ function h(nodeName, attributes) { var children = EMPTY_CHILDREN, lastSimple, child, simple, i; for (i = arguments.length; i-- > 2;) { stack.push(arguments[i]); } if (attributes && attributes.children != null) { if (!stack.length) stack.push(attributes.children); delete attributes.children; } while (stack.length) { if ((child = stack.pop()) && child.pop !== undefined) { for (i = child.length; i--;) { stack.push(child[i]); } } else { if (typeof child === 'boolean') child = null; if (simple = typeof nodeName !== 'function') { if (child == null) child = '';else if (typeof child === 'number') child = String(child);else if (typeof child !== 'string') simple = false; } if (simple && lastSimple) { children[children.length - 1] += child; } else if (children === EMPTY_CHILDREN) { children = [child]; } else { children.push(child); } lastSimple = simple; } } var p = new VNode(); p.nodeName = nodeName; p.children = children; p.attributes = attributes == null ? undefined : attributes; p.key = attributes == null ? undefined : attributes.key; // if a "vnode hook" is defined, pass every created VNode to it if (options.vnode !== undefined) options.vnode(p); return p; } /** * Copy all properties from `props` onto `obj`. * @param {Object} obj Object onto which properties should be copied. * @param {Object} props Object from which to copy properties. * @returns obj * @private */ function extend(obj, props) { for (var i in props) { obj[i] = props[i]; }return obj; } /** * Call a function asynchronously, as soon as possible. Makes * use of HTML Promise to schedule the callback if available, * otherwise falling back to `setTimeout` (mainly for IE<11). * * @param {Function} callback */ var defer = typeof Promise == 'function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout; /** * Clones the given VNode, optionally adding attributes/props and replacing its children. * @param {VNode} vnode The virtual DOM element to clone * @param {Object} props Attributes/props to add when cloning * @param {VNode} rest Any additional arguments will be used as replacement children. */ function cloneElement(vnode, props) { return h(vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children); } // DOM properties that should NOT have "px" added when numeric var IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i; /** Managed queue of dirty components to be re-rendered */ var items = []; function enqueueRender(component) { if (!component._dirty && (component._dirty = true) && items.push(component) == 1) { (options.debounceRendering || defer)(rerender); } } function rerender() { var p, list = items; items = []; while (p = list.pop()) { if (p._dirty) renderComponent(p); } } /** * Check if two nodes are equivalent. * * @param {Node} node DOM Node to compare * @param {VNode} vnode Virtual DOM node to compare * @param {boolean} [hydrating=false] If true, ignores component constructors when comparing. * @private */ function isSameNodeType(node, vnode, hydrating) { if (typeof vnode === 'string' || typeof vnode === 'number') { return node.splitText !== undefined; } if (typeof vnode.nodeName === 'string') { return !node._componentConstructor && isNamedNode(node, vnode.nodeName); } return hydrating || node._componentConstructor === vnode.nodeName; } /** * Check if an Element has a given nodeName, case-insensitively. * * @param {Element} node A DOM Element to inspect the name of. * @param {String} nodeName Unnormalized name to compare against. */ function isNamedNode(node, nodeName) { return node.normalizedNodeName === nodeName || node.nodeName.toLowerCase() === nodeName.toLowerCase(); } /** * Reconstruct Component-style `props` from a VNode. * Ensures default/fallback values from `defaultProps`: * Own-properties of `defaultProps` not present in `vnode.attributes` are added. * * @param {VNode} vnode * @returns {Object} props */ function getNodeProps(vnode) { var props = extend({}, vnode.attributes); props.children = vnode.children; var defaultProps = vnode.nodeName.defaultProps; if (defaultProps !== undefined) { for (var i in defaultProps) { if (props[i] === undefined) { props[i] = defaultProps[i]; } } } return props; } /** Create an element with the given nodeName. * @param {String} nodeName * @param {Boolean} [isSvg=false] If `true`, creates an element within the SVG namespace. * @returns {Element} node */ function createNode(nodeName, isSvg) { var node = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName); node.normalizedNodeName = nodeName; return node; } /** Remove a child node from its parent if attached. * @param {Element} node The node to remove */ function removeNode(node) { var parentNode = node.parentNode; if (parentNode) parentNode.removeChild(node); } /** Set a named attribute on the given Node, with special behavior for some names and event handlers. * If `value` is `null`, the attribute/handler will be removed. * @param {Element} node An element to mutate * @param {string} name The name/key to set, such as an event or attribute name * @param {any} old The last value that was set for this name/node pair * @param {any} value An attribute value, such as a function to be used as an event handler * @param {Boolean} isSvg Are we currently diffing inside an svg? * @private */ function setAccessor(node, name, old, value, isSvg) { if (name === 'className') name = 'class'; if (name === 'key') { // ignore } else if (name === 'ref') { if (old) old(null); if (value) value(node); } else if (name === 'class' && !isSvg) { node.className = value || ''; } else if (name === 'style') { if (!value || typeof value === 'string' || typeof old === 'string') { node.style.cssText = value || ''; } if (value && typeof value === 'object') { if (typeof old !== 'string') { for (var i in old) { if (!(i in value)) node.style[i] = ''; } } for (var i in value) { node.style[i] = typeof value[i] === 'number' && IS_NON_DIMENSIONAL.test(i) === false ? value[i] + 'px' : value[i]; } } } else if (name === 'dangerouslySetInnerHTML') { if (value) node.innerHTML = value.__html || ''; } else if (name[0] == 'o' && name[1] == 'n') { var useCapture = name !== (name = name.replace(/Capture$/, '')); name = name.toLowerCase().substring(2); if (value) { if (!old) node.addEventListener(name, eventProxy, useCapture); } else { node.removeEventListener(name, eventProxy, useCapture); } (node._listeners || (node._listeners = {}))[name] = value; } else if (name !== 'list' && name !== 'type' && !isSvg && name in node) { setProperty(node, name, value == null ? '' : value); if (value == null || value === false) node.removeAttribute(name); } else { var ns = isSvg && name !== (name = name.replace(/^xlink:?/, '')); if (value == null || value === false) { if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());else node.removeAttribute(name); } else if (typeof value !== 'function') { if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);else node.setAttribute(name, value); } } } /** Attempt to set a DOM property to the given value. * IE & FF throw for certain property-value combinations. */ function setProperty(node, name, value) { try { node[name] = value; } catch (e) {} } /** Proxy an event to hooked event handlers * @private */ function eventProxy(e) { return this._listeners[e.type](options.event && options.event(e) || e); } /** Queue of components that have been mounted and are awaiting componentDidMount */ var mounts = []; /** Diff recursion count, used to track the end of the diff cycle. */ var diffLevel = 0; /** Global flag indicating if the diff is currently within an SVG */ var isSvgMode = false; /** Global flag indicating if the diff is performing hydration */ var hydrating = false; /** Invoke queued componentDidMount lifecycle methods */ function flushMounts() { var c; while (c = mounts.pop()) { if (options.afterMount) options.afterMount(c); if (c.componentDidMount) c.componentDidMount(); } } /** Apply differences in a given vnode (and it's deep children) to a real DOM Node. * @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode` * @param {VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure * @returns {Element} dom The created/mutated element * @private */ function diff(dom, vnode, context, mountAll, parent, componentRoot) { // diffLevel having been 0 here indicates initial entry into the diff (not a subdiff) if (!diffLevel++) { // when first starting the diff, check if we're diffing an SVG or within an SVG isSvgMode = parent != null && parent.ownerSVGElement !== undefined; // hydration is indicated by the existing element to be diffed not having a prop cache hydrating = dom != null && !('__preactattr_' in dom); } var ret = idiff(dom, vnode, context, mountAll, componentRoot); // append the element if its a new parent if (parent && ret.parentNode !== parent) parent.appendChild(ret); // diffLevel being reduced to 0 means we're exiting the diff if (! --diffLevel) { hydrating = false; // invoke queued componentDidMount lifecycle methods if (!componentRoot) flushMounts(); } return ret; } /** Internals of `diff()`, separated to allow bypassing diffLevel / mount flushing. */ function idiff(dom, vnode, context, mountAll, componentRoot) { var out = dom, prevSvgMode = isSvgMode; // empty values (null, undefined, booleans) render as empty Text nodes if (vnode == null || typeof vnode === 'boolean') vnode = ''; // Fast case: Strings & Numbers create/update Text nodes. if (typeof vnode === 'string' || typeof vnode === 'number') { // update if it's already a Text node: if (dom && dom.splitText !== undefined && dom.parentNode && (!dom._component || componentRoot)) { /* istanbul ignore if */ /* Browser quirk that can't be covered: https://github.com/developit/preact/commit/fd4f21f5c45dfd75151bd27b4c217d8003aa5eb9 */ if (dom.nodeValue != vnode) { dom.nodeValue = vnode; } } else { // it wasn't a Text node: replace it with one and recycle the old Element out = document.createTextNode(vnode); if (dom) { if (dom.parentNode) dom.parentNode.replaceChild(out, dom); recollectNodeTree(dom, true); } } out['__preactattr_'] = true; return out; } // If the VNode represents a Component, perform a component diff: var vnodeName = vnode.nodeName; if (typeof vnodeName === 'function') { return buildComponentFromVNode(dom, vnode, context, mountAll); } // Tracks entering and exiting SVG namespace when descending through the tree. isSvgMode = vnodeName === 'svg' ? true : vnodeName === 'foreignObject' ? false : isSvgMode; // If there's no existing element or it's the wrong type, create a new one: vnodeName = String(vnodeName); if (!dom || !isNamedNode(dom, vnodeName)) { out = createNode(vnodeName, isSvgMode); if (dom) { // move children into the replacement node while (dom.firstChild) { out.appendChild(dom.firstChild); } // if the previous Element was mounted into the DOM, replace it inline if (dom.parentNode) dom.parentNode.replaceChild(out, dom); // recycle the old element (skips non-Element node types) recollectNodeTree(dom, true); } } var fc = out.firstChild, props = out['__preactattr_'], vchildren = vnode.children; if (props == null) { props = out['__preactattr_'] = {}; for (var a = out.attributes, i = a.length; i--;) { props[a[i].name] = a[i].value; } } // Optimization: fast-path for elements containing a single TextNode: if (!hydrating && vchildren && vchildren.length === 1 && typeof vchildren[0] === 'string' && fc != null && fc.splitText !== undefined && fc.nextSibling == null) { if (fc.nodeValue != vchildren[0]) { fc.nodeValue = vchildren[0]; } } // otherwise, if there are existing or new children, diff them: else if (vchildren && vchildren.length || fc != null) { innerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML != null); } // Apply attributes/props from VNode to the DOM Element: diffAttributes(out, vnode.attributes, props); // restore previous SVG mode: (in case we're exiting an SVG namespace) isSvgMode = prevSvgMode; return out; } /** Apply child and attribute changes between a VNode and a DOM Node to the DOM. * @param {Element} dom Element whose children should be compared & mutated * @param {Array} vchildren Array of VNodes to compare to `dom.childNodes` * @param {Object} context Implicitly descendant context object (from most recent `getChildContext()`) * @param {Boolean} mountAll * @param {Boolean} isHydrating If `true`, consumes externally created elements similar to hydration */ function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) { var originalChildren = dom.childNodes, children = [], keyed = {}, keyedLen = 0, min = 0, len = originalChildren.length, childrenLen = 0, vlen = vchildren ? vchildren.length : 0, j, c, f, vchild, child; // Build up a map of keyed children and an Array of unkeyed children: if (len !== 0) { for (var i = 0; i < len; i++) { var _child = originalChildren[i], props = _child['__preactattr_'], key = vlen && props ? _child._component ? _child._component.__key : props.key : null; if (key != null) { keyedLen++; keyed[key] = _child; } else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) { children[childrenLen++] = _child; } } } if (vlen !== 0) { for (var i = 0; i < vlen; i++) { vchild = vchildren[i]; child = null; // attempt to find a node based on key matching var key = vchild.key; if (key != null) { if (keyedLen && keyed[key] !== undefined) { child = keyed[key]; keyed[key] = undefined; keyedLen--; } } // attempt to pluck a node of the same type from the existing children else if (!child && min < childrenLen) { for (j = min; j < childrenLen; j++) { if (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) { child = c; children[j] = undefined; if (j === childrenLen - 1) childrenLen--; if (j === min) min++; break; } } } // morph the matched/found/created DOM child to match vchild (deep) child = idiff(child, vchild, context, mountAll); f = originalChildren[i]; if (child && child !== dom && child !== f) { if (f == null) { dom.appendChild(child); } else if (child === f.nextSibling) { removeNode(f); } else { dom.insertBefore(child, f); } } } } // remove unused keyed children: if (keyedLen) { for (var i in keyed) { if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false); } } // remove orphaned unkeyed children: while (min <= childrenLen) { if ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false); } } /** Recursively recycle (or just unmount) a node and its descendants. * @param {Node} node DOM node to start unmount/removal from * @param {Boolean} [unmountOnly=false] If `true`, only triggers unmount lifecycle, skips removal */ function recollectNodeTree(node, unmountOnly) { var component = node._component; if (component) { // if node is owned by a Component, unmount that component (ends up recursing back here) unmountComponent(component); } else { // If the node's VNode had a ref function, invoke it with null here. // (this is part of the React spec, and smart for unsetting references) if (node['__preactattr_'] != null && node['__preactattr_'].ref) node['__preactattr_'].ref(null); if (unmountOnly === false || node['__preactattr_'] == null) { removeNode(node); } removeChildren(node); } } /** Recollect/unmount all children. * - we use .lastChild here because it causes less reflow than .firstChild * - it's also cheaper than accessing the .childNodes Live NodeList */ function removeChildren(node) { node = node.lastChild; while (node) { var next = node.previousSibling; recollectNodeTree(node, true); node = next; } } /** Apply differences in attributes from a VNode to the given DOM Element. * @param {Element} dom Element with attributes to diff `attrs` against * @param {Object} attrs The desired end-state key-value attribute pairs * @param {Object} old Current/previous attributes (from previous VNode or element's prop cache) */ function diffAttributes(dom, attrs, old) { var name; // remove attributes no longer present on the vnode by setting them to undefined for (name in old) { if (!(attrs && attrs[name] != null) && old[name] != null) { setAccessor(dom, name, old[name], old[name] = undefined, isSvgMode); } } // add new & update changed attributes for (name in attrs) { if (name !== 'children' && name !== 'innerHTML' && (!(name in old) || attrs[name] !== (name === 'value' || name === 'checked' ? dom[name] : old[name]))) { setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode); } } } /** Retains a pool of Components for re-use, keyed on component name. * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding. * @private */ var components = {}; /** Reclaim a component for later re-use by the recycler. */ function collectComponent(component) { var name = component.constructor.name; (components[name] || (components[name] = [])).push(component); } /** Create a component. Normalizes differences between PFC's and classful Components. */ function createComponent(Ctor, props, context) { var list = components[Ctor.name], inst; if (Ctor.prototype && Ctor.prototype.render) { inst = new Ctor(props, context); Component.call(inst, props, context); } else { inst = new Component(props, context); inst.constructor = Ctor; inst.render = doRender; } if (list) { for (var i = list.length; i--;) { if (list[i].constructor === Ctor) { inst.nextBase = list[i].nextBase; list.splice(i, 1); break; } } } return inst; } /** The `.render()` method for a PFC backing instance. */ function doRender(props, state, context) { return this.constructor(props, context); } /** Set a component's `props` (generally derived from JSX attributes). * @param {Object} props * @param {Object} [opts] * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering. * @param {boolean} [opts.render=true] If `false`, no render will be triggered. */ function setComponentProps(component, props, opts, context, mountAll) { if (component._disable) return; component._disable = true; if (component.__ref = props.ref) delete props.ref; if (component.__key = props.key) delete props.key; if (!component.base || mountAll) { if (component.componentWillMount) component.componentWillMount(); } else if (component.componentWillReceiveProps) { component.componentWillReceiveProps(props, context); } if (context && context !== component.context) { if (!component.prevContext) component.prevContext = component.context; component.context = context; } if (!component.prevProps) component.prevProps = component.props; component.props = props; component._disable = false; if (opts !== 0) { if (opts === 1 || options.syncComponentUpdates !== false || !component.base) { renderComponent(component, 1, mountAll); } else { enqueueRender(component); } } if (component.__ref) component.__ref(component); } /** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. * @param {Component} component * @param {Object} [opts] * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one. * @private */ function renderComponent(component, opts, mountAll, isChild) { if (component._disable) return; var props = component.props, state = component.state, context = component.context, previousProps = component.prevProps || props, previousState = component.prevState || state, previousContext = component.prevContext || context, isUpdate = component.base, nextBase = component.nextBase, initialBase = isUpdate || nextBase, initialChildComponent = component._component, skip = false, rendered, inst, cbase; // if updating if (isUpdate) { component.props = previousProps; component.state = previousState; component.context = previousContext; if (opts !== 2 && component.shouldComponentUpdate && component.shouldComponentUpdate(props, state, context) === false) { skip = true; } else if (component.componentWillUpdate) { component.componentWillUpdate(props, state, context); } component.props = props; component.state = state; component.context = context; } component.prevProps = component.prevState = component.prevContext = component.nextBase = null; component._dirty = false; if (!skip) { rendered = component.render(props, state, context); // context to pass to the child, can be updated via (grand-)parent component if (component.getChildContext) { context = extend(extend({}, context), component.getChildContext()); } var childComponent = rendered && rendered.nodeName, toUnmount, base; if (typeof childComponent === 'function') { // set up high order component link var childProps = getNodeProps(rendered); inst = initialChildComponent; if (inst && inst.constructor === childComponent && childProps.key == inst.__key) { setComponentProps(inst, childProps, 1, context, false); } else { toUnmount = inst; component._component = inst = createComponent(childComponent, childProps, context); inst.nextBase = inst.nextBase || nextBase; inst._parentComponent = component; setComponentProps(inst, childProps, 0, context, false); renderComponent(inst, 1, mountAll, true); } base = inst.base; } else { cbase = initialBase; // destroy high order component link toUnmount = initialChildComponent; if (toUnmount) { cbase = component._component = null; } if (initialBase || opts === 1) { if (cbase) cbase._component = null; base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); } } if (initialBase && base !== initialBase && inst !== initialChildComponent) { var baseParent = initialBase.parentNode; if (baseParent && base !== baseParent) { baseParent.replaceChild(base, initialBase); if (!toUnmount) { initialBase._component = null; recollectNodeTree(initialBase, false); } } } if (toUnmount) { unmountComponent(toUnmount); } component.base = base; if (base && !isChild) { var componentRef = component, t = component; while (t = t._parentComponent) { (componentRef = t).base = base; } base._component = componentRef; base._componentConstructor = componentRef.constructor; } } if (!isUpdate || mountAll) { mounts.unshift(component); } else if (!skip) { // Ensure that pending componentDidMount() hooks of child components // are called before the componentDidUpdate() hook in the parent. // Note: disabled as it causes duplicate hooks, see https://github.com/developit/preact/issues/750 // flushMounts(); if (component.componentDidUpdate) { component.componentDidUpdate(previousProps, previousState, previousContext); } if (options.afterUpdate) options.afterUpdate(component); } if (component._renderCallbacks != null) { while (component._renderCallbacks.length) { component._renderCallbacks.pop().call(component); } } if (!diffLevel && !isChild) flushMounts(); } /** Apply the Component referenced by a VNode to the DOM. * @param {Element} dom The DOM node to mutate * @param {VNode} vnode A Component-referencing VNode * @returns {Element} dom The created/mutated element * @private */ function buildComponentFromVNode(dom, vnode, context, mountAll) { var c = dom && dom._component, originalComponent = c, oldDom = dom, isDirectOwner = c && dom._componentConstructor === vnode.nodeName, isOwner = isDirectOwner, props = getNodeProps(vnode); while (c && !isOwner && (c = c._parentComponent)) { isOwner = c.constructor === vnode.nodeName; } if (c && isOwner && (!mountAll || c._component)) { setComponentProps(c, props, 3, context, mountAll); dom = c.base; } else { if (originalComponent && !isDirectOwner) { unmountComponent(originalComponent); dom = oldDom = null; } c = createComponent(vnode.nodeName, props, context); if (dom && !c.nextBase) { c.nextBase = dom; // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: oldDom = null; } setComponentProps(c, props, 1, context, mountAll); dom = c.base; if (oldDom && dom !== oldDom) { oldDom._component = null; recollectNodeTree(oldDom, false); } } return dom; } /** Remove a component from the DOM and recycle it. * @param {Component} component The Component instance to unmount * @private */ function unmountComponent(component) { if (options.beforeUnmount) options.beforeUnmount(component); var base = component.base; component._disable = true; if (component.componentWillUnmount) component.componentWillUnmount(); component.base = null; // recursively tear down & recollect high-order component children: var inner = component._component; if (inner) { unmountComponent(inner); } else if (base) { if (base['__preactattr_'] && base['__preactattr_'].ref) base['__preactattr_'].ref(null); component.nextBase = base; removeNode(base); collectComponent(component); removeChildren(base); } if (component.__ref) component.__ref(null); } /** Base Component class. * Provides `setState()` and `forceUpdate()`, which trigger rendering. * @public * * @example * class MyFoo extends Component { * render(props, state) { * return
; * } * } */ function Component(props, context) { this._dirty = true; /** @public * @type {object} */ this.context = context; /** @public * @type {object} */ this.props = props; /** @public * @type {object} */ this.state = this.state || {}; } extend(Component.prototype, { /** Returns a `boolean` indicating if the component should re-render when receiving the given `props` and `state`. * @param {object} nextProps * @param {object} nextState * @param {object} nextContext * @returns {Boolean} should the component re-render * @name shouldComponentUpdate * @function */ /** Update component state by copying properties from `state` to `this.state`. * @param {object} state A hash of state properties to update with new values * @param {function} callback A function to be called once component state is updated */ setState: function setState(state, callback) { var s = this.state; if (!this.prevState) this.prevState = extend({}, s); extend(s, typeof state === 'function' ? state(s, this.props) : state); if (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback); enqueueRender(this); }, /** Immediately perform a synchronous re-render of the component. * @param {function} callback A function to be called after component is re-rendered. * @private */ forceUpdate: function forceUpdate(callback) { if (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback); renderComponent(this, 2); }, /** Accepts `props` and `state`, and returns a new Virtual DOM tree to build. * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). * @param {object} props Props (eg: JSX attributes) received from parent element/component * @param {object} state The component's current state * @param {object} context Context object (if a parent component has provided context) * @returns VNode */ render: function render() {} }); /** Render JSX into a `parent` Element. * @param {VNode} vnode A (JSX) VNode to render * @param {Element} parent DOM element to render into * @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge` * @public * * @example * // render a div into : * render(
hello!
, document.body); * * @example * // render a "Thing" component into #foo: * const Thing = ({ name }) => { name }; * render(, document.querySelector('#foo')); */ function render(vnode, parent, merge) { return diff(merge, vnode, {}, false, parent, false); } var preact = { h: h, createElement: h, cloneElement: cloneElement, Component: Component, render: render, rerender: rerender, options: options }; /* unused harmony default export */ var _unused_webpack_default_export = (preact); //# sourceMappingURL=preact.esm.js.map /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var React = __webpack_require__(1); var noop = __webpack_require__(101); module.exports = function (Component) { var options = Component.keyHandlingOptions, propName = options.propName || "", values = options.values || {}, controller = options.controller || noop, defaultProps = Component.defaultProps, getDefaultProps = Component.getDefaultProps, ref = "wrappedComponent"; return React.createClass({ values: values, defaultProps: defaultProps, getDefaultProps: getDefaultProps, onKeyDown: function onKeyDown(event, api) { var v = this.values[event.keyCode]; if (v) { event.preventDefault(); if (typeof v === "function") { v(api); } else { api[propName] += v; controller(api); } } }, getComponent: function getComponent() { var wrappedComponent = this.refs[ref]; if (wrappedComponent.getComponent) { return wrappedComponent.getComponent(); } return wrappedComponent; }, render: function render() { return React.createElement(Component, _extends({}, this.props, { onKeyDown: this.onKeyDown, ref: ref })); } }); }; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ if (false) { var throwOnDirectAccess, isValidElement, REACT_ELEMENT_TYPE; } else { // By explicitly using `prop-types` you are opting into new production behavior. // http://fb.me/prop-types-in-prod module.exports = __webpack_require__(137)(); } /***/ }), /* 5 */ /***/ (function(module, exports) { module.exports = function transpose(M) { return M[0].map((col, i) => M.map(row => row[i])); }; /***/ }), /* 6 */ /***/ (function(module, exports) { // Copied from http://blog.acipo.com/matrix-inversion-in-javascript/ // Returns the inverse of matrix `M`. module.exports = function matrix_invert(M) { // I use Guassian Elimination to calculate the inverse: // (1) 'augment' the matrix (left) by the identity (on the right) // (2) Turn the matrix on the left into the identity by elemetry row ops // (3) The matrix on the right is the inverse (was the identity matrix) // There are 3 elemtary row ops: (I combine b and c in my code) // (a) Swap 2 rows // (b) Multiply a row by a scalar // (c) Add 2 rows //if the matrix isn't square: exit (error) if (M.length !== M[0].length) { console.log('not square'); return; } //create the identity matrix (I), and a copy (C) of the original var i = 0, ii = 0, j = 0, dim = M.length, e = 0, t = 0; var I = [], C = []; for (i = 0; i < dim; i += 1) { // Create the row I[I.length] = []; C[C.length] = []; for (j = 0; j < dim; j += 1) { //if we're on the diagonal, put a 1 (for identity) if (i == j) { I[i][j] = 1; } else { I[i][j] = 0; } // Also, make the copy of the original C[i][j] = M[i][j]; } } // Perform elementary row operations for (i = 0; i < dim; i += 1) { // get the element e on the diagonal e = C[i][i]; // if we have a 0 on the diagonal (we'll need to swap with a lower row) if (e == 0) { //look through every row below the i'th row for (ii = i + 1; ii < dim; ii += 1) { //if the ii'th row has a non-0 in the i'th col if (C[ii][i] != 0) { //it would make the diagonal have a non-0 so swap it for (j = 0; j < dim; j++) { e = C[i][j]; //temp store i'th row C[i][j] = C[ii][j]; //replace i'th row by ii'th C[ii][j] = e; //repace ii'th by temp e = I[i][j]; //temp store i'th row I[i][j] = I[ii][j]; //replace i'th row by ii'th I[ii][j] = e; //repace ii'th by temp } //don't bother checking other rows since we've swapped break; } } //get the new diagonal e = C[i][i]; //if it's still 0, not invertable (error) if (e == 0) { return; } } // Scale this row down by e (so we have a 1 on the diagonal) for (j = 0; j < dim; j++) { C[i][j] = C[i][j] / e; //apply to original matrix I[i][j] = I[i][j] / e; //apply to identity } // Subtract this row (scaled appropriately for each row) from ALL of // the other rows so that there will be 0's in this column in the // rows above and below this one for (ii = 0; ii < dim; ii++) { // Only apply to other rows (we want a 1 on the diagonal) if (ii == i) { continue; } // We want to change this element to 0 e = C[ii][i]; // Subtract (the row above(or below) scaled by e) from (the // current row) but start at the i'th column and assume all the // stuff left of diagonal is 0 (which it should be if we made this // algorithm correctly) for (j = 0; j < dim; j++) { C[ii][j] -= e * C[i][j]; //apply to original matrix I[ii][j] -= e * I[i][j]; //apply to identity } } } //we've done all operations, C should be the identity //matrix I should be the inverse: return I; }; /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /** * This is an ordered list of all sections used in the Bezier primer. * * The ordering you see here reflects the ordering in which sections * are present on the Primer page: do not change them unless there is * a REALLY good reason to =) * */ module.exports = { preface: __webpack_require__(108), // the basic topic(s) introduction(s) introduction: __webpack_require__(107), whatis: __webpack_require__(105), explanation: __webpack_require__(103), control: __webpack_require__(100), weightcontrol: __webpack_require__(98), extended: __webpack_require__(96), // basic operations matrix: __webpack_require__(94), decasteljau: __webpack_require__(93), flattening: __webpack_require__(91), splitting: __webpack_require__(89), matrixsplit: __webpack_require__(87), reordering: __webpack_require__(86), // information that can be obtained through analysis derivatives: __webpack_require__(83), pointvectors: __webpack_require__(82), pointvectors3d: __webpack_require__(80), components: __webpack_require__(78), extremities: __webpack_require__(76), boundingbox: __webpack_require__(74), aligning: __webpack_require__(72), tightbounds: __webpack_require__(70), inflections: __webpack_require__(68), canonical: __webpack_require__(66), yforx: __webpack_require__(64), // accurate arc length is hard, yo arclength: __webpack_require__(62), arclengthapprox: __webpack_require__(60), curvature: __webpack_require__(58), tracing: __webpack_require__(56), // curve intersections intersections: __webpack_require__(54), curveintersection: __webpack_require__(52), // curve manipulation abc: __webpack_require__(50), moulding: __webpack_require__(48), pointcurves: __webpack_require__(46), curvefitting: __webpack_require__(44), // A quick foray into Catmull-Rom splines catmullconv: __webpack_require__(41), catmullmoulding: __webpack_require__(40), // "things made of more than on curve" polybezier: __webpack_require__(38), shapes: __webpack_require__(36), //drawing: require("./drawing"), // curve offsetting projections: __webpack_require__(34), offsetting: __webpack_require__(32), graduatedoffset: __webpack_require__(30), // circle and arc approximation circles: __webpack_require__(28), circles_cubic: __webpack_require__(26), arcapproximation: __webpack_require__(24), // A quick foray in to B-Spline land bsplines: __webpack_require__(22), // comments come last for obvious reasons comments: __webpack_require__(14) }; /***/ }), /* 8 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var hashResolver = __webpack_require__(115); var SectionHeader = React.createClass({ displayName: "SectionHeader", statics: { locale: "" }, render: function render() { var locale = SectionHeader.locale; if (typeof window !== "undefined" && window.location.toString().indexOf(locale) === -1) { locale = ""; } var fragmentid = (locale ? "./" + locale + "/" : ".") + "#" + this.props.name; return React.createElement( "h2", { id: this.props.name, "data-num": this.props.number }, React.createElement( "a", { href: fragmentid }, this.props.title ) ); }, componentDidMount: function componentDidMount() { hashResolver(); } }); module.exports = SectionHeader; /***/ }), /* 9 */ /***/ (function(module, exports, __webpack_require__) { (function() { "use strict"; // math-inlining. var abs = Math.abs, cos = Math.cos, sin = Math.sin, acos = Math.acos, atan2 = Math.atan2, sqrt = Math.sqrt, pow = Math.pow, // cube root function yielding real roots crt = function(v) { return v < 0 ? -pow(-v, 1 / 3) : pow(v, 1 / 3); }, // trig constants pi = Math.PI, tau = 2 * pi, quart = pi / 2, // float precision significant decimal epsilon = 0.000001, // extremas used in bbox calculation and similar algorithms nMax = Number.MAX_SAFE_INTEGER || 9007199254740991, nMin = Number.MIN_SAFE_INTEGER || -9007199254740991, // a zero coordinate, which is surprisingly useful ZERO = { x: 0, y: 0, z: 0 }; // Bezier utility functions var utils = { // Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x)) Tvalues: [ -0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909, -0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404, -0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864, -0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524, -0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107, -0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696, -0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232, -0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761, -0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873, -0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548, -0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411, -0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745 ], // Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article) Cvalues: [ 0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517, 0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264, 0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607, 0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622, 0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946, 0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279, 0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185, 0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193, 0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412, 0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593, 0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491, 0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759 ], arcfn: function(t, derivativeFn) { var d = derivativeFn(t); var l = d.x * d.x + d.y * d.y; if (typeof d.z !== "undefined") { l += d.z * d.z; } return sqrt(l); }, compute: function(t, points, _3d) { // shortcuts if (t === 0) { return points[0]; } let order = points.length-1; if (t === 1) { return points[order]; } var p = points; var mt = 1 - t; // constant? if (order === 0) { return points[0]; } // linear? if (order === 1) { ret = { x: mt * p[0].x + t * p[1].x, y: mt * p[0].y + t * p[1].y }; if (_3d) { ret.z = mt * p[0].z + t * p[1].z; } return ret; } // quadratic/cubic curve? if (order < 4) { var mt2 = mt * mt, t2 = t * t, a, b, c, d = 0; if (order === 2) { p = [p[0], p[1], p[2], ZERO]; a = mt2; b = mt * t * 2; c = t2; } else if (order === 3) { a = mt2 * mt; b = mt2 * t * 3; c = mt * t2 * 3; d = t * t2; } var ret = { x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x, y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y }; if (_3d) { ret.z = a * p[0].z + b * p[1].z + c * p[2].z + d * p[3].z; } return ret; } // higher order curves: use de Casteljau's computation var dCpts = JSON.parse(JSON.stringify(points)); while (dCpts.length > 1) { for (var i = 0; i < dCpts.length - 1; i++) { dCpts[i] = { x: dCpts[i].x + (dCpts[i + 1].x - dCpts[i].x) * t, y: dCpts[i].y + (dCpts[i + 1].y - dCpts[i].y) * t }; if (typeof dCpts[i].z !== "undefined") { dCpts[i] = dCpts[i].z + (dCpts[i + 1].z - dCpts[i].z) * t; } } dCpts.splice(dCpts.length - 1, 1); } return dCpts[0]; }, computeWithRatios: function (t, points, ratios, _3d) { var mt = 1 - t, r = ratios, p = points, d; var f1 = r[0], f2 = r[1], f3 = r[2], f4 = r[3]; // spec for linear f1 *= mt; f2 *= t; if (p.length === 2) { d = f1 + f2; return { x: (f1 * p[0].x + f2 * p[1].x)/d, y: (f1 * p[0].y + f2 * p[1].y)/d, z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z)/d }; } // upgrade to quadratic f1 *= mt; f2 *= 2 * mt; f3 *= t * t; if (p.length === 3) { d = f1 + f2 + f3; return { x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x)/d, y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y)/d, z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z)/d }; } // upgrade to cubic f1 *= mt; f2 *= 1.5 * mt; f3 *= 3 * mt; f4 *= t * t * t; if (p.length === 4) { d = f1 + f2 + f3 + f4; return { x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x + f4 * p[3].x)/d, y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y + f4 * p[3].y)/d, z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z + f4 * p[3].z)/d }; } }, derive: function (points, _3d) { var dpoints = []; for (var p = points, d = p.length, c = d - 1; d > 1; d--, c--) { var list = []; for (var j = 0, dpt; j < c; j++) { dpt = { x: c * (p[j + 1].x - p[j].x), y: c * (p[j + 1].y - p[j].y) }; if (_3d) { dpt.z = c * (p[j + 1].z - p[j].z); } list.push(dpt); } dpoints.push(list); p = list; } return dpoints; }, between: function(v, m, M) { return ( (m <= v && v <= M) || utils.approximately(v, m) || utils.approximately(v, M) ); }, approximately: function(a, b, precision) { return abs(a - b) <= (precision || epsilon); }, length: function(derivativeFn) { var z = 0.5, sum = 0, len = utils.Tvalues.length, i, t; for (i = 0; i < len; i++) { t = z * utils.Tvalues[i] + z; sum += utils.Cvalues[i] * utils.arcfn(t, derivativeFn); } return z * sum; }, map: function(v, ds, de, ts, te) { var d1 = de - ds, d2 = te - ts, v2 = v - ds, r = v2 / d1; return ts + d2 * r; }, lerp: function(r, v1, v2) { var ret = { x: v1.x + r * (v2.x - v1.x), y: v1.y + r * (v2.y - v1.y) }; if (!!v1.z && !!v2.z) { ret.z = v1.z + r * (v2.z - v1.z); } return ret; }, pointToString: function(p) { var s = p.x + "/" + p.y; if (typeof p.z !== "undefined") { s += "/" + p.z; } return s; }, pointsToString: function(points) { return "[" + points.map(utils.pointToString).join(", ") + "]"; }, copy: function(obj) { return JSON.parse(JSON.stringify(obj)); }, angle: function(o, v1, v2) { var dx1 = v1.x - o.x, dy1 = v1.y - o.y, dx2 = v2.x - o.x, dy2 = v2.y - o.y, cross = dx1 * dy2 - dy1 * dx2, dot = dx1 * dx2 + dy1 * dy2; return atan2(cross, dot); }, // round as string, to avoid rounding errors round: function(v, d) { var s = "" + v; var pos = s.indexOf("."); return parseFloat(s.substring(0, pos + 1 + d)); }, dist: function(p1, p2) { var dx = p1.x - p2.x, dy = p1.y - p2.y; return sqrt(dx * dx + dy * dy); }, closest: function(LUT, point) { var mdist = pow(2, 63), mpos, d; LUT.forEach(function(p, idx) { d = utils.dist(point, p); if (d < mdist) { mdist = d; mpos = idx; } }); return { mdist: mdist, mpos: mpos }; }, abcratio: function(t, n) { // see ratio(t) note on http://pomax.github.io/bezierinfo/#abc if (n !== 2 && n !== 3) { return false; } if (typeof t === "undefined") { t = 0.5; } else if (t === 0 || t === 1) { return t; } var bottom = pow(t, n) + pow(1 - t, n), top = bottom - 1; return abs(top / bottom); }, projectionratio: function(t, n) { // see u(t) note on http://pomax.github.io/bezierinfo/#abc if (n !== 2 && n !== 3) { return false; } if (typeof t === "undefined") { t = 0.5; } else if (t === 0 || t === 1) { return t; } var top = pow(1 - t, n), bottom = pow(t, n) + top; return top / bottom; }, lli8: function(x1, y1, x2, y2, x3, y3, x4, y4) { 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), d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); if (d == 0) { return false; } return { x: nx / d, y: ny / d }; }, lli4: function(p1, p2, p3, p4) { var x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y; return utils.lli8(x1, y1, x2, y2, x3, y3, x4, y4); }, lli: function(v1, v2) { return utils.lli4(v1, v1.c, v2, v2.c); }, makeline: function(p1, p2) { var Bezier = __webpack_require__(10); var x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, dx = (x2 - x1) / 3, dy = (y2 - y1) / 3; return new Bezier( x1, y1, x1 + dx, y1 + dy, x1 + 2 * dx, y1 + 2 * dy, x2, y2 ); }, findbbox: function(sections) { var mx = nMax, my = nMax, MX = nMin, MY = nMin; sections.forEach(function(s) { var bbox = s.bbox(); if (mx > bbox.x.min) mx = bbox.x.min; if (my > bbox.y.min) my = bbox.y.min; if (MX < bbox.x.max) MX = bbox.x.max; if (MY < bbox.y.max) MY = bbox.y.max; }); return { x: { min: mx, mid: (mx + MX) / 2, max: MX, size: MX - mx }, y: { min: my, mid: (my + MY) / 2, max: MY, size: MY - my } }; }, shapeintersections: function( s1, bbox1, s2, bbox2, curveIntersectionThreshold ) { if (!utils.bboxoverlap(bbox1, bbox2)) return []; var intersections = []; var a1 = [s1.startcap, s1.forward, s1.back, s1.endcap]; var a2 = [s2.startcap, s2.forward, s2.back, s2.endcap]; a1.forEach(function(l1) { if (l1.virtual) return; a2.forEach(function(l2) { if (l2.virtual) return; var iss = l1.intersects(l2, curveIntersectionThreshold); if (iss.length > 0) { iss.c1 = l1; iss.c2 = l2; iss.s1 = s1; iss.s2 = s2; intersections.push(iss); } }); }); return intersections; }, makeshape: function(forward, back, curveIntersectionThreshold) { var bpl = back.points.length; var fpl = forward.points.length; var start = utils.makeline(back.points[bpl - 1], forward.points[0]); var end = utils.makeline(forward.points[fpl - 1], back.points[0]); var shape = { startcap: start, forward: forward, back: back, endcap: end, bbox: utils.findbbox([start, forward, back, end]) }; var self = utils; shape.intersections = function(s2) { return self.shapeintersections( shape, shape.bbox, s2, s2.bbox, curveIntersectionThreshold ); }; return shape; }, getminmax: function(curve, d, list) { if (!list) return { min: 0, max: 0 }; var min = nMax, max = nMin, t, c; if (list.indexOf(0) === -1) { list = [0].concat(list); } if (list.indexOf(1) === -1) { list.push(1); } for (var i = 0, len = list.length; i < len; i++) { t = list[i]; c = curve.get(t); if (c[d] < min) { min = c[d]; } if (c[d] > max) { max = c[d]; } } return { min: min, mid: (min + max) / 2, max: max, size: max - min }; }, align: function(points, line) { var tx = line.p1.x, ty = line.p1.y, a = -atan2(line.p2.y - ty, line.p2.x - tx), d = function(v) { return { x: (v.x - tx) * cos(a) - (v.y - ty) * sin(a), y: (v.x - tx) * sin(a) + (v.y - ty) * cos(a) }; }; return points.map(d); }, roots: function(points, line) { line = line || { p1: { x: 0, y: 0 }, p2: { x: 1, y: 0 } }; var order = points.length - 1; var p = utils.align(points, line); var reduce = function(t) { return 0 <= t && t <= 1; }; if (order === 2) { var a = p[0].y, b = p[1].y, c = p[2].y, d = a - 2 * b + c; if (d !== 0) { var m1 = -sqrt(b * b - a * c), m2 = -a + b, v1 = -(m1 + m2) / d, v2 = -(-m1 + m2) / d; return [v1, v2].filter(reduce); } else if (b !== c && d === 0) { return [(2*b - c)/(2*b - 2*c)].filter(reduce); } return []; } // see http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm var pa = p[0].y, pb = p[1].y, pc = p[2].y, pd = p[3].y, d = -pa + 3 * pb - 3 * pc + pd, a = 3 * pa - 6 * pb + 3 * pc, b = -3 * pa + 3 * pb, c = pa; if (utils.approximately(d, 0)) { // this is not a cubic curve. if (utils.approximately(a, 0)) { // in fact, this is not a quadratic curve either. if (utils.approximately(b, 0)) { // in fact in fact, there are no solutions. return []; } // linear solution: return [-c / b].filter(reduce); } // quadratic solution: var q = sqrt(b * b - 4 * a * c), a2 = 2 * a; return [(q - b) / a2, (-b - q) / a2].filter(reduce); } // at this point, we know we need a cubic solution: a /= d; b /= d; c /= d; var p = (3 * b - a * a) / 3, p3 = p / 3, q = (2 * a * a * a - 9 * a * b + 27 * c) / 27, q2 = q / 2, discriminant = q2 * q2 + p3 * p3 * p3, u1, v1, x1, x2, x3; if (discriminant < 0) { var mp3 = -p / 3, mp33 = mp3 * mp3 * mp3, r = sqrt(mp33), t = -q / (2 * r), cosphi = t < -1 ? -1 : t > 1 ? 1 : t, phi = acos(cosphi), crtr = crt(r), t1 = 2 * crtr; x1 = t1 * cos(phi / 3) - a / 3; x2 = t1 * cos((phi + tau) / 3) - a / 3; x3 = t1 * cos((phi + 2 * tau) / 3) - a / 3; return [x1, x2, x3].filter(reduce); } else if (discriminant === 0) { u1 = q2 < 0 ? crt(-q2) : -crt(q2); x1 = 2 * u1 - a / 3; x2 = -u1 - a / 3; return [x1, x2].filter(reduce); } else { var sd = sqrt(discriminant); u1 = crt(-q2 + sd); v1 = crt(q2 + sd); return [u1 - v1 - a / 3].filter(reduce); } }, droots: function(p) { // quadratic roots are easy if (p.length === 3) { var a = p[0], b = p[1], c = p[2], d = a - 2 * b + c; if (d !== 0) { var m1 = -sqrt(b * b - a * c), m2 = -a + b, v1 = -(m1 + m2) / d, v2 = -(-m1 + m2) / d; return [v1, v2]; } else if (b !== c && d === 0) { return [(2 * b - c) / (2 * (b - c))]; } return []; } // linear roots are even easier if (p.length === 2) { var a = p[0], b = p[1]; if (a !== b) { return [a / (a - b)]; } return []; } }, curvature: function(t, points, _3d, kOnly) { var dpoints = utils.derive(points); var d1 = dpoints[0]; var d2 = dpoints[1]; var num, dnm, adk, dk, k=0, r=0; // // We're using the following formula for curvature: // // x'y" - y'x" // k(t) = ------------------ // (x'² + y'²)^(2/3) // // from https://en.wikipedia.org/wiki/Radius_of_curvature#Definition // // With it corresponding 3D counterpart: // // sqrt( (y'z" - y"z')² + (z'x" - z"x')² + (x'y" - x"y')²) // k(t) = ------------------------------------------------------- // (x'² + y'² + z'²)^(2/3) // var d = utils.compute(t, d1); var dd = utils.compute(t, d2); var qdsum = d.x*d.x + d.y*d.y; if (_3d) { num = sqrt( pow(d.y*dd.z - dd.y*d.z, 2) + pow(d.z*dd.x - dd.z*d.x, 2) + pow(d.x*dd.y - dd.x*d.y, 2) ); dnm = pow(qdsum + d.z*d.z, 2/3); } else { num = d.x*dd.y - d.y*dd.x; dnm = pow(qdsum, 2/3); } if (num === 0 || dnm === 0) { return { k:0, r:0 }; } k = num/dnm; r = dnm/num; // We're also computing the derivative of kappa, because // there is value in knowing the rate of change for the // curvature along the curve. And we're just going to // ballpark it based on an epsilon. if (!kOnly) { // compute k'(t) based on the interval before, and after it, // to at least try to not introduce forward/backward pass bias. var pk = utils.curvature(t-0.001, points, _3d, true).k; var nk = utils.curvature(t+0.001, points, _3d, true).k; dk = ((nk-k) + (k-pk))/2; adk = (abs(nk-k) + abs(k-pk))/2; } return { k: k, r: r, dk: dk, adk:adk, }; }, inflections: function(points) { if (points.length < 4) return []; // FIXME: TODO: add in inflection abstraction for quartic+ curves? var p = utils.align(points, { p1: points[0], p2: points.slice(-1)[0] }), a = p[2].x * p[1].y, b = p[3].x * p[1].y, c = p[1].x * p[2].y, d = p[3].x * p[2].y, v1 = 18 * (-3 * a + 2 * b + 3 * c - d), v2 = 18 * (3 * a - b - 3 * c), v3 = 18 * (c - a); if (utils.approximately(v1, 0)) { if (!utils.approximately(v2, 0)) { var t = -v3 / v2; if (0 <= t && t <= 1) return [t]; } return []; } var trm = v2 * v2 - 4 * v1 * v3, sq = Math.sqrt(trm), d = 2 * v1; if (utils.approximately(d, 0)) return []; return [(sq - v2) / d, -(v2 + sq) / d].filter(function(r) { return 0 <= r && r <= 1; }); }, bboxoverlap: function(b1, b2) { var dims = ["x", "y"], len = dims.length, i, dim, l, t, d; for (i = 0; i < len; i++) { dim = dims[i]; l = b1[dim].mid; t = b2[dim].mid; d = (b1[dim].size + b2[dim].size) / 2; if (abs(l - t) >= d) return false; } return true; }, expandbox: function(bbox, _bbox) { if (_bbox.x.min < bbox.x.min) { bbox.x.min = _bbox.x.min; } if (_bbox.y.min < bbox.y.min) { bbox.y.min = _bbox.y.min; } if (_bbox.z && _bbox.z.min < bbox.z.min) { bbox.z.min = _bbox.z.min; } if (_bbox.x.max > bbox.x.max) { bbox.x.max = _bbox.x.max; } if (_bbox.y.max > bbox.y.max) { bbox.y.max = _bbox.y.max; } if (_bbox.z && _bbox.z.max > bbox.z.max) { bbox.z.max = _bbox.z.max; } bbox.x.mid = (bbox.x.min + bbox.x.max) / 2; bbox.y.mid = (bbox.y.min + bbox.y.max) / 2; if (bbox.z) { bbox.z.mid = (bbox.z.min + bbox.z.max) / 2; } bbox.x.size = bbox.x.max - bbox.x.min; bbox.y.size = bbox.y.max - bbox.y.min; if (bbox.z) { bbox.z.size = bbox.z.max - bbox.z.min; } }, pairiteration: function(c1, c2, curveIntersectionThreshold) { var c1b = c1.bbox(), c2b = c2.bbox(), r = 100000, threshold = curveIntersectionThreshold || 0.5; if ( c1b.x.size + c1b.y.size < threshold && c2b.x.size + c2b.y.size < threshold ) { return [ ((r * (c1._t1 + c1._t2) / 2) | 0) / r + "/" + ((r * (c2._t1 + c2._t2) / 2) | 0) / r ]; } var cc1 = c1.split(0.5), cc2 = c2.split(0.5), pairs = [ { left: cc1.left, right: cc2.left }, { left: cc1.left, right: cc2.right }, { left: cc1.right, right: cc2.right }, { left: cc1.right, right: cc2.left } ]; pairs = pairs.filter(function(pair) { return utils.bboxoverlap(pair.left.bbox(), pair.right.bbox()); }); var results = []; if (pairs.length === 0) return results; pairs.forEach(function(pair) { results = results.concat( utils.pairiteration(pair.left, pair.right, threshold) ); }); results = results.filter(function(v, i) { return results.indexOf(v) === i; }); return results; }, getccenter: function(p1, p2, p3) { var dx1 = p2.x - p1.x, dy1 = p2.y - p1.y, dx2 = p3.x - p2.x, dy2 = p3.y - p2.y; var dx1p = dx1 * cos(quart) - dy1 * sin(quart), dy1p = dx1 * sin(quart) + dy1 * cos(quart), dx2p = dx2 * cos(quart) - dy2 * sin(quart), dy2p = dx2 * sin(quart) + dy2 * cos(quart); // chord midpoints var mx1 = (p1.x + p2.x) / 2, my1 = (p1.y + p2.y) / 2, mx2 = (p2.x + p3.x) / 2, my2 = (p2.y + p3.y) / 2; // midpoint offsets var mx1n = mx1 + dx1p, my1n = my1 + dy1p, mx2n = mx2 + dx2p, my2n = my2 + dy2p; // intersection of these lines: var arc = utils.lli8(mx1, my1, mx1n, my1n, mx2, my2, mx2n, my2n), r = utils.dist(arc, p1), // arc start/end values, over mid point: s = atan2(p1.y - arc.y, p1.x - arc.x), m = atan2(p2.y - arc.y, p2.x - arc.x), e = atan2(p3.y - arc.y, p3.x - arc.x), _; // determine arc direction (cw/ccw correction) if (s < e) { // if s m || m > e) { s += tau; } if (s > e) { _ = e; e = s; s = _; } } else { // if e 4) { if (arguments.length !== 1) { throw new Error( "Only new Bezier(point[]) is accepted for 4th and higher order curves" ); } higher = true; } } else { if (len !== 6 && len !== 8 && len !== 9 && len !== 12) { if (arguments.length !== 1) { throw new Error( "Only new Bezier(point[]) is accepted for 4th and higher order curves" ); } } } var _3d = (!higher && (len === 9 || len === 12)) || (coords && coords[0] && typeof coords[0].z !== "undefined"); this._3d = _3d; var points = []; for (var idx = 0, step = _3d ? 3 : 2; idx < len; idx += step) { var point = { x: args[idx], y: args[idx + 1] }; if (_3d) { point.z = args[idx + 2]; } points.push(point); } this.order = points.length - 1; this.points = points; var dims = ["x", "y"]; if (_3d) dims.push("z"); this.dims = dims; this.dimlen = dims.length; (function(curve) { var order = curve.order; var points = curve.points; var a = utils.align(points, { p1: points[0], p2: points[order] }); for (var i = 0; i < a.length; i++) { if (abs(a[i].y) > 0.0001) { curve._linear = false; return; } } curve._linear = true; })(this); this._t1 = 0; this._t2 = 1; this.update(); }; var svgToBeziers = __webpack_require__(117); /** * turn an svg d attribute into a sequence of Bezier segments. */ Bezier.SVGtoBeziers = function(d) { return svgToBeziers(Bezier, d); }; function getABC(n, S, B, E, t) { if (typeof t === "undefined") { t = 0.5; } var u = utils.projectionratio(t, n), um = 1 - u, C = { x: u * S.x + um * E.x, y: u * S.y + um * E.y }, s = utils.abcratio(t, n), A = { x: B.x + (B.x - C.x) / s, y: B.y + (B.y - C.y) / s }; return { A: A, B: B, C: C }; } Bezier.quadraticFromPoints = function(p1, p2, p3, t) { if (typeof t === "undefined") { t = 0.5; } // shortcuts, although they're really dumb if (t === 0) { return new Bezier(p2, p2, p3); } if (t === 1) { return new Bezier(p1, p2, p2); } // real fitting. var abc = getABC(2, p1, p2, p3, t); return new Bezier(p1, abc.A, p3); }; Bezier.cubicFromPoints = function(S, B, E, t, d1) { if (typeof t === "undefined") { t = 0.5; } var abc = getABC(3, S, B, E, t); if (typeof d1 === "undefined") { d1 = utils.dist(B, abc.C); } var d2 = d1 * (1 - t) / t; var selen = utils.dist(S, E), lx = (E.x - S.x) / selen, ly = (E.y - S.y) / selen, bx1 = d1 * lx, by1 = d1 * ly, bx2 = d2 * lx, by2 = d2 * ly; // derivation of new hull coordinates var e1 = { x: B.x - bx1, y: B.y - by1 }, e2 = { x: B.x + bx2, y: B.y + by2 }, A = abc.A, v1 = { x: A.x + (e1.x - A.x) / (1 - t), y: A.y + (e1.y - A.y) / (1 - t) }, v2 = { x: A.x + (e2.x - A.x) / t, y: A.y + (e2.y - A.y) / t }, nc1 = { x: S.x + (v1.x - S.x) / t, y: S.y + (v1.y - S.y) / t }, nc2 = { x: E.x + (v2.x - E.x) / (1 - t), y: E.y + (v2.y - E.y) / (1 - t) }; // ...done return new Bezier(S, nc1, nc2, E); }; var getUtils = function() { return utils; }; Bezier.getUtils = getUtils; Bezier.PolyBezier = PolyBezier; Bezier.prototype = { getUtils: getUtils, valueOf: function() { return this.toString(); }, toString: function() { return utils.pointsToString(this.points); }, toSVG: function(relative) { if (this._3d) return false; var p = this.points, x = p[0].x, y = p[0].y, s = ["M", x, y, this.order === 2 ? "Q" : "C"]; for (var i = 1, last = p.length; i < last; i++) { s.push(p[i].x); s.push(p[i].y); } return s.join(" "); }, setRatios: function(ratios) { if (ratios.length !== this.points.length) { throw new Error("incorrect number of ratio values"); } this.ratios = ratios; this._lut = []; // invalidate any precomputed LUT }, update: function() { // invalidate any precomputed LUT this._lut = []; this.dpoints = utils.derive(this.points, this._3d); this.computedirection(); }, computedirection: function() { var points = this.points; var angle = utils.angle(points[0], points[this.order], points[1]); this.clockwise = angle > 0; }, length: function() { return utils.length(this.derivative.bind(this)); }, _lut: [], getLUT: function(steps) { steps = steps || 100; if (this._lut.length === steps) { return this._lut; } this._lut = []; // We want a range from 0 to 1 inclusive, so // we decrement and then use <= rather than <: steps--; for (var t = 0; t <= steps; t++) { this._lut.push(this.compute(t / steps)); } return this._lut; }, on: function(point, error) { error = error || 5; var lut = this.getLUT(), hits = [], c, t = 0; for (var i = 0; i < lut.length; i++) { c = lut[i]; if (utils.dist(c, point) < error) { hits.push(c); t += i / lut.length; } } if (!hits.length) return false; return (t /= hits.length); }, project: function(point) { // step 1: coarse check var LUT = this.getLUT(), l = LUT.length - 1, closest = utils.closest(LUT, point), mdist = closest.mdist, mpos = closest.mpos; if (mpos === 0 || mpos === l) { var t = mpos / l, pt = this.compute(t); pt.t = t; pt.d = mdist; return pt; } // step 2: fine check var ft, t, p, d, t1 = (mpos - 1) / l, t2 = (mpos + 1) / l, step = 0.1 / l; mdist += 1; for (t = t1, ft = t; t < t2 + step; t += step) { p = this.compute(t); d = utils.dist(point, p); if (d < mdist) { mdist = d; ft = t; } } p = this.compute(ft); p.t = ft; p.d = mdist; return p; }, get: function(t) { return this.compute(t); }, point: function(idx) { return this.points[idx]; }, compute: function(t) { if (this.ratios) return utils.computeWithRatios(t, this.points, this.ratios, this._3d); return utils.compute(t, this.points, this._3d, this.ratios); }, raise: function() { var p = this.points, np = [p[0]], i, k = p.length, pi, pim; for (var i = 1; i < k; i++) { pi = p[i]; pim = p[i - 1]; np[i] = { x: (k - i) / k * pi.x + i / k * pim.x, y: (k - i) / k * pi.y + i / k * pim.y }; } np[k] = p[k - 1]; return new Bezier(np); }, derivative: function(t) { var mt = 1 - t, a, b, c = 0, p = this.dpoints[0]; if (this.order === 2) { p = [p[0], p[1], ZERO]; a = mt; b = t; } if (this.order === 3) { a = mt * mt; b = mt * t * 2; c = t * t; } var ret = { x: a * p[0].x + b * p[1].x + c * p[2].x, y: a * p[0].y + b * p[1].y + c * p[2].y }; if (this._3d) { ret.z = a * p[0].z + b * p[1].z + c * p[2].z; } return ret; }, curvature: function(t) { return utils.curvature(t, this.points, this._3d); }, inflections: function() { return utils.inflections(this.points); }, normal: function(t) { return this._3d ? this.__normal3(t) : this.__normal2(t); }, __normal2: function(t) { var d = this.derivative(t); var q = sqrt(d.x * d.x + d.y * d.y); return { x: -d.y / q, y: d.x / q }; }, __normal3: function(t) { // see http://stackoverflow.com/questions/25453159 var r1 = this.derivative(t), r2 = this.derivative(t + 0.01), q1 = sqrt(r1.x * r1.x + r1.y * r1.y + r1.z * r1.z), q2 = sqrt(r2.x * r2.x + r2.y * r2.y + r2.z * r2.z); r1.x /= q1; r1.y /= q1; r1.z /= q1; r2.x /= q2; r2.y /= q2; r2.z /= q2; // cross product var c = { x: r2.y * r1.z - r2.z * r1.y, y: r2.z * r1.x - r2.x * r1.z, z: r2.x * r1.y - r2.y * r1.x }; var m = sqrt(c.x * c.x + c.y * c.y + c.z * c.z); c.x /= m; c.y /= m; c.z /= m; // rotation matrix var R = [ c.x * c.x, c.x * c.y - c.z, c.x * c.z + c.y, c.x * c.y + c.z, c.y * c.y, c.y * c.z - c.x, c.x * c.z - c.y, c.y * c.z + c.x, c.z * c.z ]; // normal vector: var n = { x: R[0] * r1.x + R[1] * r1.y + R[2] * r1.z, y: R[3] * r1.x + R[4] * r1.y + R[5] * r1.z, z: R[6] * r1.x + R[7] * r1.y + R[8] * r1.z }; return n; }, hull: function(t) { var p = this.points, _p = [], pt, q = [], idx = 0, i = 0, l = 0; q[idx++] = p[0]; q[idx++] = p[1]; q[idx++] = p[2]; if (this.order === 3) { q[idx++] = p[3]; } // we lerp between all points at each iteration, until we have 1 point left. while (p.length > 1) { _p = []; for (i = 0, l = p.length - 1; i < l; i++) { pt = utils.lerp(t, p[i], p[i + 1]); q[idx++] = pt; _p.push(pt); } p = _p; } return q; }, split: function(t1, t2) { // shortcuts if (t1 === 0 && !!t2) { return this.split(t2).left; } if (t2 === 1) { return this.split(t1).right; } // no shortcut: use "de Casteljau" iteration. var q = this.hull(t1); var result = { left: this.order === 2 ? new Bezier([q[0], q[3], q[5]]) : new Bezier([q[0], q[4], q[7], q[9]]), right: this.order === 2 ? new Bezier([q[5], q[4], q[2]]) : new Bezier([q[9], q[8], q[6], q[3]]), span: q }; // make sure we bind _t1/_t2 information! result.left._t1 = utils.map(0, 0, 1, this._t1, this._t2); result.left._t2 = utils.map(t1, 0, 1, this._t1, this._t2); result.right._t1 = utils.map(t1, 0, 1, this._t1, this._t2); result.right._t2 = utils.map(1, 0, 1, this._t1, this._t2); // if we have no t2, we're done if (!t2) { return result; } // if we have a t2, split again: t2 = utils.map(t2, t1, 1, 0, 1); var subsplit = result.right.split(t2); return subsplit.left; }, extrema: function() { var dims = this.dims, result = {}, roots = [], p, mfn; dims.forEach( function(dim) { mfn = function(v) { return v[dim]; }; p = this.dpoints[0].map(mfn); result[dim] = utils.droots(p); if (this.order === 3) { p = this.dpoints[1].map(mfn); result[dim] = result[dim].concat(utils.droots(p)); } result[dim] = result[dim].filter(function(t) { return t >= 0 && t <= 1; }); roots = roots.concat(result[dim].sort(utils.numberSort)); }.bind(this) ); roots = roots.sort(utils.numberSort).filter(function(v, idx) { return roots.indexOf(v) === idx; }); result.values = roots; return result; }, bbox: function() { var extrema = this.extrema(), result = {}; this.dims.forEach( function(d) { result[d] = utils.getminmax(this, d, extrema[d]); }.bind(this) ); return result; }, overlaps: function(curve) { var lbbox = this.bbox(), tbbox = curve.bbox(); return utils.bboxoverlap(lbbox, tbbox); }, offset: function(t, d) { if (typeof d !== "undefined") { var c = this.get(t); var n = this.normal(t); var ret = { c: c, n: n, x: c.x + n.x * d, y: c.y + n.y * d }; if (this._3d) { ret.z = c.z + n.z * d; } return ret; } if (this._linear) { var nv = this.normal(0); var coords = this.points.map(function(p) { var ret = { x: p.x + t * nv.x, y: p.y + t * nv.y }; if (p.z && n.z) { ret.z = p.z + t * nv.z; } return ret; }); return [new Bezier(coords)]; } var reduced = this.reduce(); return reduced.map(function(s) { return s.scale(t); }); }, simple: function() { if (this.order === 3) { var a1 = utils.angle(this.points[0], this.points[3], this.points[1]); var a2 = utils.angle(this.points[0], this.points[3], this.points[2]); if ((a1 > 0 && a2 < 0) || (a1 < 0 && a2 > 0)) return false; } var n1 = this.normal(0); var n2 = this.normal(1); var s = n1.x * n2.x + n1.y * n2.y; if (this._3d) { s += n1.z * n2.z; } var angle = abs(acos(s)); return angle < pi / 3; }, reduce: function() { var i, t1 = 0, t2 = 0, step = 0.01, segment, pass1 = [], pass2 = []; // first pass: split on extrema var extrema = this.extrema().values; if (extrema.indexOf(0) === -1) { extrema = [0].concat(extrema); } if (extrema.indexOf(1) === -1) { extrema.push(1); } for (t1 = extrema[0], i = 1; i < extrema.length; i++) { t2 = extrema[i]; segment = this.split(t1, t2); segment._t1 = t1; segment._t2 = t2; pass1.push(segment); t1 = t2; } // second pass: further reduce these segments to simple segments pass1.forEach(function(p1) { t1 = 0; t2 = 0; while (t2 <= 1) { for (t2 = t1 + step; t2 <= 1 + step; t2 += step) { segment = p1.split(t1, t2); if (!segment.simple()) { t2 -= step; if (abs(t1 - t2) < step) { // we can never form a reduction return []; } segment = p1.split(t1, t2); segment._t1 = utils.map(t1, 0, 1, p1._t1, p1._t2); segment._t2 = utils.map(t2, 0, 1, p1._t1, p1._t2); pass2.push(segment); t1 = t2; break; } } } if (t1 < 1) { segment = p1.split(t1, 1); segment._t1 = utils.map(t1, 0, 1, p1._t1, p1._t2); segment._t2 = p1._t2; pass2.push(segment); } }); return pass2; }, scale: function(d) { var order = this.order; var distanceFn = false; if (typeof d === "function") { distanceFn = d; } if (distanceFn && order === 2) { return this.raise().scale(distanceFn); } // TODO: add special handling for degenerate (=linear) curves. var clockwise = this.clockwise; var r1 = distanceFn ? distanceFn(0) : d; var r2 = distanceFn ? distanceFn(1) : d; var v = [this.offset(0, 10), this.offset(1, 10)]; var o = utils.lli4(v[0], v[0].c, v[1], v[1].c); if (!o) { throw new Error("cannot scale this curve. Try reducing it first."); } // move all points by distance 'd' wrt the origin 'o' var points = this.points, np = []; // move end points by fixed distance along normal. [0, 1].forEach( function(t) { var p = (np[t * order] = utils.copy(points[t * order])); p.x += (t ? r2 : r1) * v[t].n.x; p.y += (t ? r2 : r1) * v[t].n.y; }.bind(this) ); if (!distanceFn) { // move control points to lie on the intersection of the offset // derivative vector, and the origin-through-control vector [0, 1].forEach( function(t) { if (this.order === 2 && !!t) return; var p = np[t * order]; var d = this.derivative(t); var p2 = { x: p.x + d.x, y: p.y + d.y }; np[t + 1] = utils.lli4(p, p2, o, points[t + 1]); }.bind(this) ); return new Bezier(np); } // move control points by "however much necessary to // ensure the correct tangent to endpoint". [0, 1].forEach( function(t) { if (this.order === 2 && !!t) return; var p = points[t + 1]; var ov = { x: p.x - o.x, y: p.y - o.y }; var rc = distanceFn ? distanceFn((t + 1) / order) : d; if (distanceFn && !clockwise) rc = -rc; var m = sqrt(ov.x * ov.x + ov.y * ov.y); ov.x /= m; ov.y /= m; np[t + 1] = { x: p.x + rc * ov.x, y: p.y + rc * ov.y }; }.bind(this) ); return new Bezier(np); }, outline: function(d1, d2, d3, d4) { d2 = typeof d2 === "undefined" ? d1 : d2; var reduced = this.reduce(), len = reduced.length, fcurves = [], bcurves = [], p, alen = 0, tlen = this.length(); var graduated = typeof d3 !== "undefined" && typeof d4 !== "undefined"; function linearDistanceFunction(s, e, tlen, alen, slen) { return function(v) { var f1 = alen / tlen, f2 = (alen + slen) / tlen, d = e - s; return utils.map(v, 0, 1, s + f1 * d, s + f2 * d); }; } // form curve oulines reduced.forEach(function(segment) { slen = segment.length(); if (graduated) { fcurves.push( segment.scale(linearDistanceFunction(d1, d3, tlen, alen, slen)) ); bcurves.push( segment.scale(linearDistanceFunction(-d2, -d4, tlen, alen, slen)) ); } else { fcurves.push(segment.scale(d1)); bcurves.push(segment.scale(-d2)); } alen += slen; }); // reverse the "return" outline bcurves = bcurves .map(function(s) { p = s.points; if (p[3]) { s.points = [p[3], p[2], p[1], p[0]]; } else { s.points = [p[2], p[1], p[0]]; } return s; }) .reverse(); // form the endcaps as lines var fs = fcurves[0].points[0], fe = fcurves[len - 1].points[fcurves[len - 1].points.length - 1], bs = bcurves[len - 1].points[bcurves[len - 1].points.length - 1], be = bcurves[0].points[0], ls = utils.makeline(bs, fs), le = utils.makeline(fe, be), segments = [ls] .concat(fcurves) .concat([le]) .concat(bcurves), slen = segments.length; return new PolyBezier(segments); }, outlineshapes: function(d1, d2, curveIntersectionThreshold) { d2 = d2 || d1; var outline = this.outline(d1, d2).curves; var shapes = []; for (var i = 1, len = outline.length; i < len / 2; i++) { var shape = utils.makeshape( outline[i], outline[len - i], curveIntersectionThreshold ); shape.startcap.virtual = i > 1; shape.endcap.virtual = i < len / 2 - 1; shapes.push(shape); } return shapes; }, intersects: function(curve, curveIntersectionThreshold) { if (!curve) return this.selfintersects(curveIntersectionThreshold); if (curve.p1 && curve.p2) { return this.lineIntersects(curve); } if (curve instanceof Bezier) { curve = curve.reduce(); } return this.curveintersects( this.reduce(), curve, curveIntersectionThreshold ); }, lineIntersects: function(line) { var mx = min(line.p1.x, line.p2.x), my = min(line.p1.y, line.p2.y), MX = max(line.p1.x, line.p2.x), MY = max(line.p1.y, line.p2.y), self = this; return utils.roots(this.points, line).filter(function(t) { var p = self.get(t); return utils.between(p.x, mx, MX) && utils.between(p.y, my, MY); }); }, selfintersects: function(curveIntersectionThreshold) { var reduced = this.reduce(); // "simple" curves cannot intersect with their direct // neighbour, so for each segment X we check whether // it intersects [0:x-2][x+2:last]. var i, len = reduced.length - 2, results = [], result, left, right; for (i = 0; i < len; i++) { left = reduced.slice(i, i + 1); right = reduced.slice(i + 2); result = this.curveintersects(left, right, curveIntersectionThreshold); results = results.concat(result); } return results; }, curveintersects: function(c1, c2, curveIntersectionThreshold) { var pairs = []; // step 1: pair off any overlapping segments c1.forEach(function(l) { c2.forEach(function(r) { if (l.overlaps(r)) { pairs.push({ left: l, right: r }); } }); }); // step 2: for each pairing, run through the convergence algorithm. var intersections = []; pairs.forEach(function(pair) { var result = utils.pairiteration( pair.left, pair.right, curveIntersectionThreshold ); if (result.length > 0) { intersections = intersections.concat(result); } }); return intersections; }, arcs: function(errorThreshold) { errorThreshold = errorThreshold || 0.5; var circles = []; return this._iterate(errorThreshold, circles); }, _error: function(pc, np1, s, e) { var q = (e - s) / 4, c1 = this.get(s + q), c2 = this.get(e - q), ref = utils.dist(pc, np1), d1 = utils.dist(pc, c1), d2 = utils.dist(pc, c2); return abs(d1 - ref) + abs(d2 - ref); }, _iterate: function(errorThreshold, circles) { var t_s = 0, t_e = 1, safety; // we do a binary search to find the "good `t` closest to no-longer-good" do { safety = 0; // step 1: start with the maximum possible arc t_e = 1; // points: var np1 = this.get(t_s), np2, np3, arc, prev_arc; // booleans: var curr_good = false, prev_good = false, done; // numbers: var t_m = t_e, prev_e = 1, step = 0; // step 2: find the best possible arc do { prev_good = curr_good; prev_arc = arc; t_m = (t_s + t_e) / 2; step++; np2 = this.get(t_m); np3 = this.get(t_e); arc = utils.getccenter(np1, np2, np3); //also save the t values arc.interval = { start: t_s, end: t_e }; var error = this._error(arc, np1, t_s, t_e); curr_good = error <= errorThreshold; done = prev_good && !curr_good; if (!done) prev_e = t_e; // this arc is fine: we can move 'e' up to see if we can find a wider arc if (curr_good) { // if e is already at max, then we're done for this arc. if (t_e >= 1) { // make sure we cap at t=1 arc.interval.end = prev_e = 1; prev_arc = arc; // if we capped the arc segment to t=1 we also need to make sure that // the arc's end angle is correct with respect to the bezier end point. if (t_e > 1) { var d = { x: arc.x + arc.r * cos(arc.e), y: arc.y + arc.r * sin(arc.e) }; arc.e += utils.angle({ x: arc.x, y: arc.y }, d, this.get(1)); } break; } // if not, move it up by half the iteration distance t_e = t_e + (t_e - t_s) / 2; } else { // this is a bad arc: we need to move 'e' down to find a good arc t_e = t_m; } } while (!done && safety++ < 100); if (safety >= 100) { break; } // console.log("L835: [F] arc found", t_s, prev_e, prev_arc.x, prev_arc.y, prev_arc.s, prev_arc.e); prev_arc = prev_arc ? prev_arc : arc; circles.push(prev_arc); t_s = prev_e; } while (t_e < 1); return circles; } }; module.exports = Bezier; })(); /***/ }), /* 11 */ /***/ (function(module, exports, __webpack_require__) { var data = __webpack_require__(127); var Locale = function() { this.data = {}; this.data = data; }; Locale.prototype = { getSectionLocale: function(key) { return this.data[key].locale; }, getContent: function(key, handler) { return this.data[key].getContent(handler); }, getTitle: function(key) { return this.data[key].title; } }; module.exports = Locale; /***/ }), /* 12 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var Footer = React.createClass({ displayName: "Footer", render: function render() { var copyright = "© 2011-2018"; return React.createElement( "div", null, React.createElement( "footer", { className: "copyright" }, "This article is ", copyright, " to me, Mike \"Pomax\" Kamermans, but the text, code, and images are ", React.createElement( "a", { href: "https://github.com/Pomax/bezierinfo/tree/master/LICENSE.md" }, "almost no rights reserved" ), ". Go do something cool with it!" ), React.createElement( "footer", { className: "print copyright" }, "Content printed from https://pomax.github.io/bezierinfo, ", copyright, " Mike \"Pomax\" Kamermans." ) ); } }); module.exports = Footer; /***/ }), /* 13 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /** * We REALLY don't want disqus to load unless the user * is actually looking at the comments section, because it * tacks on 2.5+ MB in network transfers... */ module.exports = { componentDidMount: function componentDidMount() { if (typeof document === "undefined") { return this.silence(); } this.heading = document.getElementById(this.props.page); document.addEventListener("scroll", this.scrollHandler, { passive: true }); }, scrollHandler: function scrollHandler(evt) { var bbox = this.heading.getBoundingClientRect(); var top = bbox.top; var limit = window.innerHeight; if (top < limit) { this.loadDisqus(); } }, loadDisqus: function loadDisqus() { var script = document.createElement("script"); script.src = "lib/site/disqus.js"; script.async = true; document.head.appendChild(script); this.silence(); this.unlisten(); }, silence: function silence() { this.loadDisqus = function () {}; }, unlisten: function unlisten() { document.removeEventListener("scroll", this.scrollHandler); } }; /***/ }), /* 14 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(13); var generateBase = __webpack_require__(0); module.exports = generateBase("comments", handler); /***/ }), /* 15 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { degree: 3, activeDistance: 9, weights: [], setup: function setup() { this.size(400, 400); var TAU = Math.PI * 2; var r = this.width / 3; for (var i = 0; i < 6; i++) { this.points.push({ x: this.width / 2 + r * Math.cos(i / 6 * TAU), y: this.height / 2 + r * Math.sin(i / 6 * TAU) }); } this.points = this.points.concat(this.points.slice(0, 3)); this.closed = this.degree; this.knots = this.formKnots(this.points); this.weights = this.formWeights(this.points); if (this.props.controller) { this.props.controller(this, this.knots, this.weights, this.closed); } this.draw(); }, draw: function draw() { var _this = this; this.clear(); this.grid(25); var p = this.points[0]; this.points.forEach(function (n) { _this.stroke(200); _this.line(n.x, n.y, p.x, p.y); p = n; _this.stroke(0); _this.circle(p.x, p.y, 4); }); this.drawSplineData(); }, drawSplineData: function drawSplineData() { if (this.points.length <= this.degree) return; var mapped = this.points.map(function (p) { return [p.x, p.y]; }); this.drawCurve(mapped); this.drawKnots(mapped); } }; /***/ }), /* 16 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { degree: 3, activeDistance: 9, setup: function setup() { this.size(400, 400); var TAU = Math.PI * 2; for (var i = 0; i < TAU; i += TAU / 10) { this.points.push({ x: this.width / 2 + 100 * Math.cos(i), y: this.height / 2 + 100 * Math.sin(i) }); } this.knots = this.formKnots(this.points, true); if (this.props.controller) { this.props.controller(this, this.knots); } this.draw(); }, draw: function draw() { var _this = this; this.clear(); this.grid(25); var p = this.points[0]; this.points.forEach(function (n) { _this.stroke(200); _this.line(n.x, n.y, p.x, p.y); p = n; _this.stroke(0); _this.circle(p.x, p.y, 4); }); this.drawSplineData(); }, drawSplineData: function drawSplineData() { if (this.points.length <= this.degree) return; var mapped = this.points.map(function (p) { return [p.x, p.y]; }); this.drawCurve(mapped); this.drawKnots(mapped); } }; /***/ }), /* 17 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { degree: 3, activeDistance: 9, setup: function setup() { this.size(400, 400); var TAU = Math.PI * 2; for (var i = 0; i < TAU; i += TAU / 9) { this.points.push({ x: this.width / 2 + 100 * Math.cos(i), y: this.height / 2 + 100 * Math.sin(i) }); } this.knots = this.formKnots(this.points); var m = Math.round(this.points.length / 2) | 0; this.knots[m + 0] = this.knots[m]; this.knots[m + 1] = this.knots[m]; this.knots[m + 2] = this.knots[m]; for (var _i = m + 3; _i < this.knots.length; _i++) { this.knots[_i] = this.knots[_i - 1] + 1; } if (this.props.controller) { this.props.controller(this, this.knots); } this.draw(); }, draw: function draw() { var _this = this; this.clear(); this.grid(25); var p = this.points[0]; this.points.forEach(function (n) { _this.stroke(200); _this.line(n.x, n.y, p.x, p.y); p = n; _this.stroke(0); _this.circle(p.x, p.y, 4); }); this.drawSplineData(); }, drawSplineData: function drawSplineData() { if (this.points.length <= this.degree) return; var mapped = this.points.map(function (p) { return [p.x, p.y]; }); this.drawCurve(mapped); this.drawKnots(mapped); } }; /***/ }), /* 18 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { degree: 3, activeDistance: 9, setup: function setup() { this.size(400, 400); var TAU = Math.PI * 2; for (var i = 0; i < TAU; i += TAU / 10) { this.points.push({ x: this.width / 2 + 100 * Math.cos(i), y: this.height / 2 + 100 * Math.sin(i) }); } this.knots = this.formKnots(this.points); if (this.props.controller) { this.props.controller(this, this.knots); } this.draw(); }, draw: function draw() { var _this = this; this.clear(); this.grid(25); var p = this.points[0]; this.points.forEach(function (n) { _this.stroke(200); _this.line(n.x, n.y, p.x, p.y); p = n; _this.stroke(0); _this.circle(p.x, p.y, 4); }); this.drawSplineData(); }, drawSplineData: function drawSplineData() { if (this.points.length <= this.degree) return; var mapped = this.points.map(function (p) { return [p.x, p.y]; }); this.drawCurve(mapped); this.drawKnots(mapped); } }; /***/ }), /* 19 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var colors = ['#C00', '#CC0', '#0C0', '#0CC', '#00C', '#C0C', '#600', '#660', '#060', '#066', '#006', '#606']; module.exports = { degree: 3, activeDistance: 9, cache: { N: [] }, setup: function setup() { this.size(600, 300); this.points = [{ x: 0, y: 0 }, { x: 100, y: -100 }, { x: 200, y: 100 }, { x: 300, y: -100 }, { x: 400, y: 100 }, { x: 500, y: 0 }]; this.knots = this.formKnots(this.points); if (this.props.controller) { this.props.controller(this, this.knots); } this.draw(); }, draw: function draw() { this.clear(); var pad = 25; this.grid(pad); this.stroke(0); this.line(pad, 0, pad, this.height); var y = this.height - pad; this.line(0, y, this.width, y); var k = this.degree; var n = this.points.length || 4; for (var i = 0; i < n + 1 + k; i++) { this.drawN(i, k, pad, (this.width - pad) / (2 * (n + 2)), this.height - 2 * pad); } }, drawN: function drawN(i, k, pad, w, h) { this.stroke(colors[i]); var knots = this.knots; this.beginPath(); for (var start = i - 1, t = start, step = 0.1, end = i + k + 1; t < end; t += step) { var x = pad + i * w + t * w; var y = this.height - pad - this.N(i, k, t) * h; this.vertex(x, y); } this.endPath(); }, N: function N(i, k, t) { var t_i = this.knots[i]; var t_i1 = this.knots[i + 1]; var t_ik1 = this.knots[i + k - 1]; var t_ik = this.knots[i + k]; if (k === 1) { return t_i <= t && t <= t_i1 ? 1 : 0; } var n1 = t - t_i; var d1 = t_ik1 - t_i; var a1 = d1 === 0 ? 0 : n1 / d1; var n2 = t_ik - t; var d2 = t_ik - t_i1; var a2 = d2 === 0 ? 0 : n2 / d2; var N1 = 0; if (a1 !== 0) { var n1v = this.ensureN(i, k - 1, t); N1 = n1v === undefined ? this.N(i, k - 1, t) : n1v; } var N2 = 0; if (a2 !== 0) { var n2v = this.ensureN(i + 1, k - 1, t); N2 = n2v === undefined ? this.N(i + 1, k - 1, t) : n2v; } this.cacheN(i, k, t, a1 * N1 + a2 * N2); return this.cache.N[i][k][t]; }, ensureN: function ensureN(i, k, t) { if (!this.cache.N) { this.cache.N = []; } var N = this.cache.N; if (!N[i]) { N[i] = []; } if (!N[i][k]) { N[i][k] = []; } return N[i][k][t]; }, cacheN: function cacheN(i, k, t, value) { this.ensureN(i, k, t); this.cache.N[i][k][t] = value; } }; /***/ }), /* 20 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { degree: 3, activeDistance: 9, setup: function setup() { this.size(600, 300); this.draw(); }, draw: function draw() { var _this = this; this.clear(); this.grid(25); var p = this.points[0]; this.points.forEach(function (n) { _this.stroke(200); _this.line(n.x, n.y, p.x, p.y); p = n; _this.stroke(0); _this.circle(p.x, p.y, 4); }); this.drawSplineData(); }, drawSplineData: function drawSplineData() { if (this.points.length <= this.degree) return; var mapped = this.points.map(function (p) { return [p.x, p.y]; }); this.drawCurve(mapped); this.drawKnots(mapped); } }; /***/ }), /* 21 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { basicSketch: __webpack_require__(20), interpolationGraph: __webpack_require__(19), uniformBSpline: __webpack_require__(18), centerCutBSpline: __webpack_require__(17), openUniformBSpline: __webpack_require__(16), rationalUniformBSpline: __webpack_require__(15), bindKnots: function bindKnots(owner, knots, ref) { this.refs[ref].bindKnots(owner, knots); }, bindWeights: function bindWeights(owner, weights, closed, ref) { this.refs[ref].bindWeights(owner, weights, closed); } }; /***/ }), /* 22 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(21); var generateBase = __webpack_require__(0); module.exports = generateBase("bsplines", handler); /***/ }), /* 23 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var atan2 = Math.atan2, PI = Math.PI, TAU = 2 * PI, cos = Math.cos, sin = Math.sin; module.exports = { // These are functions that can be called "From the page", // rather than being internal to the sketch. This is useful // for making on-page controls hook into the sketch code. statics: { keyHandlingOptions: { propName: "error", values: { "38": 0.1, // up arrow "40": -0.1 // down arrow }, controller: function controller(api) { if (api.error < 0.1) { api.error = 0.1; } } } }, /** * Setup up a skeleton curve that, when using its * points for a B-spline, can form a circle. */ setupCircle: function setupCircle(api) { var curve = new api.Bezier(70, 70, 140, 40, 240, 130); api.setCurve(curve); }, /** * Set up the default quadratic curve. */ setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, /** * Set up the default cubic curve. */ setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.error = 0.5; }, /** * Given three points, find the (only!) circle * that passes through all three points, based * on the fact that the perpendiculars of the * chords between the points all cross each * other at the center of that circle. */ getCCenter: function getCCenter(api, p1, p2, p3) { // deltas var dx1 = p2.x - p1.x, dy1 = p2.y - p1.y, dx2 = p3.x - p2.x, dy2 = p3.y - p2.y; // perpendiculars (quarter circle turned) var dx1p = dx1 * cos(PI / 2) - dy1 * sin(PI / 2), dy1p = dx1 * sin(PI / 2) + dy1 * cos(PI / 2), dx2p = dx2 * cos(PI / 2) - dy2 * sin(PI / 2), dy2p = dx2 * sin(PI / 2) + dy2 * cos(PI / 2); // chord midpoints var mx1 = (p1.x + p2.x) / 2, my1 = (p1.y + p2.y) / 2, mx2 = (p2.x + p3.x) / 2, my2 = (p2.y + p3.y) / 2; // midpoint offsets var mx1n = mx1 + dx1p, my1n = my1 + dy1p, mx2n = mx2 + dx2p, my2n = my2 + dy2p; // intersection of these lines: var i = api.utils.lli8(mx1, my1, mx1n, my1n, mx2, my2, mx2n, my2n); var r = api.utils.dist(i, p1); // arc start/end values, over mid point var s = atan2(p1.y - i.y, p1.x - i.x), m = atan2(p2.y - i.y, p2.x - i.x), e = atan2(p3.y - i.y, p3.x - i.x); // determine arc direction (cw/ccw correction) var __; if (s < e) { if (s > m || m > e) { s += TAU; } if (s > e) { __ = e;e = s;s = __; } } else { if (e < m && m < s) { __ = e;e = s;s = __; } else { e += TAU; } } // assign and done. i.s = s; i.e = e; i.r = r; return i; }, /** * Draw the circle-computation sketch */ drawCircle: function drawCircle(api, curve) { api.reset(); var pts = curve.points; // get center var C = this.getCCenter(api, pts[0], pts[1], pts[2]); // outer circle api.setColor("grey"); api.drawCircle(C, api.utils.dist(C, pts[0])); // controllable points api.setColor("black"); pts.forEach(function (p) { return api.drawCircle(p, 3); }); // chords and perpendicular lines var m; api.setColor("blue"); api.drawLine(pts[0], pts[1]); m = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }; api.drawLine(m, { x: C.x + (C.x - m.x), y: C.y + (C.y - m.y) }); api.setColor("red"); api.drawLine(pts[1], pts[2]); m = { x: (pts[1].x + pts[2].x) / 2, y: (pts[1].y + pts[2].y) / 2 }; api.drawLine(m, { x: C.x + (C.x - m.x), y: C.y + (C.y - m.y) }); api.setColor("green"); api.drawLine(pts[2], pts[0]); m = { x: (pts[2].x + pts[0].x) / 2, y: (pts[2].y + pts[0].y) / 2 }; api.drawLine(m, { x: C.x + (C.x - m.x), y: C.y + (C.y - m.y) }); // center api.setColor("black"); api.drawPoint(C); api.setFill("black"); api.text("Intersection point", C, { x: -25, y: 10 }); }, /** * Draw a single arc being fit to a Bezier curve, * to show off the general application. */ drawSingleArc: function drawSingleArc(api, curve) { api.reset(); var arcs = curve.arcs(api.error); api.drawSkeleton(curve); api.drawCurve(curve); var a = arcs[0]; api.setColor("red"); api.setFill("rgba(255,0,0,0.2)"); api.debug = true; api.drawArc(a); api.setFill("black"); api.text("Arc approximation with total error " + api.utils.round(api.error, 1), { x: 10, y: 15 }); }, /** * Draw an arc approximation for an entire Bezier curve. */ drawArcs: function drawArcs(api, curve) { api.reset(); var arcs = curve.arcs(api.error); api.drawSkeleton(curve); api.drawCurve(curve); arcs.forEach(function (a) { api.setRandomColor(0.3); api.setFill(api.getColor()); api.drawArc(a); }); api.setFill("black"); api.text("Arc approximation with total error " + api.utils.round(api.error, 1) + " per arc segment", { x: 10, y: 15 }); } }; /***/ }), /* 24 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(23); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("arcapproximation", handler)); /***/ }), /* 25 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var sin = Math.sin, cos = Math.cos, tan = Math.tan; module.exports = { setup: function setup(api) { api.setSize(400, 400); api.w = api.getPanelWidth(); api.h = api.getPanelHeight(); api.pad = 80; api.r = api.w / 2 - api.pad; api.mousePt = false; api.angle = 0; var spt = { x: api.w - api.pad, y: api.h / 2 }; api.setCurve(new api.Bezier(spt, spt, spt, spt)); }, guessCurve: function guessCurve(S, B, E) { var C = { x: (S.x + E.x) / 2, y: (S.y + E.y) / 2 }, A = { x: B.x + (B.x - C.x) / 3, // cubic ratio at t=0.5 is 1/3 y: B.y + (B.y - C.y) / 3 }, bx = (E.x - S.x) / 4, by = (E.y - S.y) / 4, e1 = { x: B.x - bx, y: B.y - by }, e2 = { x: B.x + bx, y: B.y + by }, v1 = { x: A.x + (e1.x - A.x) * 2, y: A.y + (e1.y - A.y) * 2 }, v2 = { x: A.x + (e2.x - A.x) * 2, y: A.y + (e2.y - A.y) * 2 }, nc1 = { x: S.x + (v1.x - S.x) * 2, y: S.y + (v1.y - S.y) * 2 }, nc2 = { x: E.x + (v2.x - E.x) * 2, y: E.y + (v2.y - E.y) * 2 }; return [nc1, nc2]; }, draw: function draw(api, curve) { api.reset(); api.setColor("lightgrey"); api.drawGrid(1, 1); api.setColor("rgba(255,0,0,0.4)"); api.drawCircle({ x: api.w / 2, y: api.h / 2 }, api.r); api.setColor("transparent"); api.setFill("rgba(100,255,100,0.4)"); var p = { x: api.w / 2, y: api.h / 2, r: api.r, s: api.angle < 0 ? api.angle : 0, e: api.angle < 0 ? 0 : api.angle }; api.drawArc(p); // guessed curve var B = { x: api.w / 2 + api.r * cos(api.angle / 2), y: api.w / 2 + api.r * sin(api.angle / 2) }; var S = curve.points[0], E = curve.points[3], nc = this.guessCurve(S, B, E); var guess = new api.Bezier([S, nc[0], nc[1], E]); api.setColor("rgb(140,140,255)"); api.drawLine(guess.points[0], guess.points[1]); api.drawLine(guess.points[1], guess.points[2]); api.drawLine(guess.points[2], guess.points[3]); api.setColor("blue"); api.drawCurve(guess); api.drawCircle(guess.points[1], 3); api.drawCircle(guess.points[2], 3); // real curve api.drawSkeleton(curve); api.setColor("black"); api.drawLine(curve.points[1], curve.points[2]); api.drawCurve(curve); }, onMouseMove: function onMouseMove(evt, api) { var x = evt.offsetX - api.w / 2, y = evt.offsetY - api.h / 2; if (x > api.w / 2) return; var angle = Math.atan2(y, x); if (angle < 0) { angle = 2 * Math.PI + angle; } var pts = api.curve.points; // new control 1 var r = api.r, f = 4 * tan(angle / 4) / 3; pts[1] = { x: api.w / 2 + r, y: api.w / 2 + r * f }; // new control 2 pts[2] = { x: api.w / 2 + api.r * (cos(angle) + f * sin(angle)), y: api.w / 2 + api.r * (sin(angle) - f * cos(angle)) }; // new endpoint pts[3] = { x: api.w / 2 + api.r * cos(angle), y: api.w / 2 + api.r * sin(angle) }; api.setCurve(new api.Bezier(pts)); api.angle = angle; }, drawCircle: function drawCircle(api) { api.setSize(325, 325); api.reset(); var w = api.getPanelWidth(), h = api.getPanelHeight(), pad = 60, r = w / 2 - pad, k = 0.55228, offset = { x: -pad / 2, y: -pad / 4 }; var curve = new api.Bezier([{ x: w / 2 + r, y: h / 2 }, { x: w / 2 + r, y: h / 2 + k * r }, { x: w / 2 + k * r, y: h / 2 + r }, { x: w / 2, y: h / 2 + r }]); api.setColor("lightgrey"); api.drawLine({ x: 0, y: h / 2 }, { x: w + pad, y: h / 2 }, offset); api.drawLine({ x: w / 2, y: 0 }, { x: w / 2, y: h + pad }, offset); var pts = curve.points; api.setColor("red"); api.drawPoint(pts[0], offset); api.drawPoint(pts[1], offset); api.drawPoint(pts[2], offset); api.drawPoint(pts[3], offset); api.drawCurve(curve, offset); api.setColor("rgb(255,160,160)"); api.drawLine(pts[0], pts[1], offset); api.drawLine(pts[1], pts[2], offset); api.drawLine(pts[2], pts[3], offset); api.setFill("red"); api.text(pts[0].x - w / 2 + "," + (pts[0].y - h / 2), { x: pts[0].x + 7, y: pts[0].y + 3 }, offset); api.text(pts[1].x - w / 2 + "," + (pts[1].y - h / 2), { x: pts[1].x + 7, y: pts[1].y + 3 }, offset); api.text(pts[2].x - w / 2 + "," + (pts[2].y - h / 2), { x: pts[2].x + 7, y: pts[2].y + 7 }, offset); api.text(pts[3].x - w / 2 + "," + (pts[3].y - h / 2), { x: pts[3].x, y: pts[3].y + 13 }, offset); pts.forEach(function (p) { p.x = -(p.x - w); }); api.setColor("blue"); api.drawCurve(curve, offset); api.drawLine(pts[2], pts[3], offset); api.drawPoint(pts[2], offset); api.setFill("blue"); api.text("reflected", { x: pts[2].x - pad / 2, y: pts[2].y + 13 }, offset); api.setColor("rgb(200,200,255)"); api.drawLine(pts[1], pts[0], offset); api.drawPoint(pts[1], offset); pts.forEach(function (p) { p.y = -(p.y - h); }); api.setColor("green"); api.drawCurve(curve, offset); pts.forEach(function (p) { p.x = -(p.x - w); }); api.setColor("purple"); api.drawCurve(curve, offset); api.drawLine(pts[1], pts[0], offset); api.drawPoint(pts[1], offset); api.setFill("purple"); api.text("reflected", { x: pts[1].x + 10, y: pts[1].y + 3 }, offset); api.setColor("rgb(200,200,255)"); api.drawLine(pts[2], pts[3], offset); api.drawPoint(pts[2], offset); api.setColor("black"); api.setFill("black"); api.drawLine({ x: w / 2, y: h / 2 }, { x: w / 2 + r - 2, y: h / 2 }, offset); api.drawLine({ x: w / 2, y: h / 2 }, { x: w / 2, y: h / 2 + r - 2 }, offset); api.text("r = " + r, { x: w / 2 + r / 3, y: h / 2 + 10 }, offset); } }; /***/ }), /* 26 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(25); var generateBase = __webpack_require__(0); module.exports = generateBase("circles_cubic", handler); /***/ }), /* 27 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var sin = Math.sin, cos = Math.cos; module.exports = { setup: function setup(api) { api.w = api.getPanelWidth(); api.h = api.getPanelHeight(); api.pad = 20; api.r = api.w / 2 - api.pad; api.mousePt = false; api.angle = 0; var spt = { x: api.w - api.pad, y: api.h / 2 }; api.setCurve(new api.Bezier(spt, spt, spt)); }, draw: function draw(api, curve) { api.reset(); api.setColor("lightgrey"); api.drawGrid(1, 1); api.setColor("red"); api.drawCircle({ x: api.w / 2, y: api.h / 2 }, api.r); api.setColor("transparent"); api.setFill("rgba(100,255,100,0.4)"); var p = { x: api.w / 2, y: api.h / 2, r: api.r, s: api.angle < 0 ? api.angle : 0, e: api.angle < 0 ? 0 : api.angle }; api.drawArc(p); api.setColor("black"); api.drawSkeleton(curve); api.drawCurve(curve); }, onMouseMove: function onMouseMove(evt, api) { var x = evt.offsetX - api.w / 2, y = evt.offsetY - api.h / 2; var angle = Math.atan2(y, x); var pts = api.curve.points; // new control var r = api.r, b = (cos(angle) - 1) / sin(angle); pts[1] = { x: api.w / 2 + r * (cos(angle) - b * sin(angle)), y: api.w / 2 + r * (sin(angle) + b * cos(angle)) }; // new endpoint pts[2] = { x: api.w / 2 + api.r * cos(angle), y: api.w / 2 + api.r * sin(angle) }; api.setCurve(new api.Bezier(pts)); api.angle = angle; } }; /***/ }), /* 28 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(27); var generateBase = __webpack_require__(0); module.exports = generateBase("circles", handler); /***/ }), /* 29 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "distance", values: { "38": 1, // up arrow "40": -1 // down arrow } } }, setup: function setup(api, curve) { api.setCurve(curve); api.distance = 20; }, setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); this.setup(api, curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); this.setup(api, curve); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); api.setColor("blue"); var outline = curve.outline(0, 0, api.distance, api.distance); outline.curves.forEach(function (c) { return api.drawCurve(c); }); } }; /***/ }), /* 30 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(29); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("graduatedoffset", handler)); /***/ }), /* 31 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "distance", values: { "38": 1, // up arrow "40": -1 // down arrow } } }, setup: function setup(api, curve) { api.setCurve(curve); api.distance = 20; }, setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); this.setup(api, curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); this.setup(api, curve); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); var reduced = curve.reduce(); reduced.forEach(function (c) { api.setRandomColor(); api.drawCurve(c); api.drawCircle(c.points[0], 1); }); var last = reduced.slice(-1)[0]; api.drawPoint(last.points[3] || last.points[2]); api.setColor("red"); var offset = curve.offset(api.distance); offset.forEach(function (c) { api.drawPoint(c.points[0]); api.drawCurve(c); }); last = offset.slice(-1)[0]; api.drawPoint(last.points[3] || last.points[2]); api.setColor("blue"); offset = curve.offset(-api.distance); offset.forEach(function (c) { api.drawPoint(c.points[0]); api.drawCurve(c); }); last = offset.slice(-1)[0]; api.drawPoint(last.points[3] || last.points[2]); } }; /***/ }), /* 32 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(31); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("offsetting", handler)); /***/ }), /* 33 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setup: function setup(api) { api.setSize(320, 320); var curve = new api.Bezier([{ x: 248, y: 188 }, { x: 218, y: 294 }, { x: 45, y: 290 }, { x: 12, y: 236 }, { x: 14, y: 82 }, { x: 186, y: 177 }, { x: 221, y: 90 }, { x: 18, y: 156 }, { x: 34, y: 57 }, { x: 198, y: 18 }]); api.setCurve(curve); api._lut = curve.getLUT(); }, findClosest: function findClosest(LUT, p, dist) { var i, end = LUT.length, d, dd = dist(LUT[0], p), f = 0; for (i = 1; i < end; i++) { d = dist(LUT[i], p); if (d < dd) { f = i;dd = d; } } return f / (end - 1); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); if (api.mousePt) { api.setColor("red"); api.setFill("red"); api.drawCircle(api.mousePt, 3); // naive t value var t = this.findClosest(api._lut, api.mousePt, api.utils.dist); // no real point in refining for illustration purposes var p = curve.get(t); api.drawLine(p, api.mousePt); api.drawCircle(p, 3); api.text("t = " + api.utils.round(t, 2), p, { x: 10, y: 3 }); } }, onMouseMove: function onMouseMove(evt, api) { api.mousePt = { x: evt.offsetX, y: evt.offsetY }; api._lut = api.curve.getLUT(); } }; /***/ }), /* 34 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(33); var generateBase = __webpack_require__(0); module.exports = generateBase("projections", handler); /***/ }), /* 35 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var modes; module.exports = { getInitialState: function getInitialState() { modes = this.modes = ["unite", "intersect", "exclude", "subtract"]; return { mode: modes[0] }; }, setMode: function setMode(mode) { this.setState({ mode: mode }); }, formPath: function formPath(api, mx, my, w, h) { mx = mx || 0; my = my || 0; var unit = 30; var unit2 = unit / 2; w = w || 8 * unit; h = h || 4 * unit; var w2 = w / 2; var h2 = h / 2; var ow3 = w2 / 3; var oh3 = h2 / 3; var Paper = api.Paper; var Path = Paper.Path; var Point = Paper.Point; var path = new Path(); path.moveTo(new Point(mx - w2 + unit * 2, my - h2)); path.cubicCurveTo(new Point(mx - w2 + unit2, my - h2 + unit2), new Point(mx - w2 + unit2, my + h2 - unit2), new Point(mx - w2 + unit * 2, my + h2)); path.cubicCurveTo(new Point(mx - ow3, my + oh3), new Point(mx + ow3, my + oh3), new Point(mx + w2 - unit * 2, my + h2)); path.cubicCurveTo(new Point(mx + w2 - unit2, my + h2 - unit2), new Point(mx + w2 - unit2, my - h2 + unit2), new Point(mx + w2 - unit * 2, my - h2)); path.cubicCurveTo(new Point(mx + ow3, my - oh3), new Point(mx - ow3, my - oh3), new Point(mx - w2 + unit * 2, my - h2)); path.closePath(true); path.strokeColor = "rgb(100,100,255)"; return path; }, setup: function setup(api) { var dim = api.getPanelWidth(); var pad = 40; var cx = dim / 2; var cy = dim / 2; api.c1 = this.formPath(api, cx, cy); cx += pad; cy += pad; api.c2 = this.formPath(api, cx, cy); this.state.mode = modes[0]; }, onMouseMove: function onMouseMove(evt, api) { var cx = evt.offsetX; var cy = evt.offsetY; api.c2.position = { x: cx, y: cy }; }, draw: function draw(api) { if (api.c3) { api.c3.remove(); } var c1 = api.c1, c2 = api.c2, fn = c1[this.state.mode].bind(c1), c3 = api.c3 = fn(c2); c3.strokeColor = "red"; c3.fillColor = "rgba(255,100,100,0.4)"; api.Paper.view.draw(); } }; /***/ }), /* 36 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(35); var generateBase = __webpack_require__(0); module.exports = generateBase("shapes", handler); /***/ }), /* 37 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var atan2 = Math.atan2, sqrt = Math.sqrt, sin = Math.sin, cos = Math.cos; module.exports = { setupQuadratic: function setupQuadratic(api) { var w = api.getPanelWidth(), h = api.getPanelHeight(), cx = w / 2, cy = h / 2, pad = 40, pts = [ // first curve: { x: cx, y: pad }, { x: w - pad, y: pad }, { x: w - pad, y: cy }, // subsequent curve { x: w - pad, y: h - pad }, { x: cx, y: h - pad }, // subsequent curve { x: pad, y: h - pad }, { x: pad, y: cy }, // final curve control point { x: pad, y: pad }]; api.lpts = pts; }, setupCubic: function setupCubic(api) { var w = api.getPanelWidth(), h = api.getPanelHeight(), cx = w / 2, cy = h / 2, pad = 40, r = (w - 2 * pad) / 2, k = 0.55228, kr = k * r, pts = [ // first curve: { x: cx, y: pad }, { x: cx + kr, y: pad }, { x: w - pad, y: cy - kr }, { x: w - pad, y: cy }, // subsequent curve { x: w - pad, y: cy + kr }, { x: cx + kr, y: h - pad }, { x: cx, y: h - pad }, // subsequent curve { x: cx - kr, y: h - pad }, { x: pad, y: cy + kr }, { x: pad, y: cy }, // final curve control point { x: pad, y: cy - kr }, { x: cx - kr, y: pad }]; api.lpts = pts; }, movePointsQuadraticLD: function movePointsQuadraticLD(api, i) { // ...we need to move _everything_ var anchor, fixed, toMove; for (var p = 1; p < 4; p++) { anchor = i + (2 * p - 2) + api.lpts.length; anchor = api.lpts[anchor % api.lpts.length]; fixed = i + (2 * p - 1); fixed = api.lpts[fixed % api.lpts.length]; toMove = i + 2 * p; toMove = api.lpts[toMove % api.lpts.length]; toMove.x = fixed.x + (fixed.x - anchor.x); toMove.y = fixed.y + (fixed.y - anchor.y); } // then, the furthest point cannot be computed properly! toMove = i + 6; toMove = api.lpts[toMove % api.lpts.length]; api.problem = toMove; }, movePointsCubicLD: function movePointsCubicLD(api, i) { var toMove, fixed; if (i % 3 === 1) { fixed = i - 1; fixed += fixed < 0 ? api.lpts.length : 0; toMove = i - 2; toMove += toMove < 0 ? api.lpts.length : 0; } else { fixed = (i + 1) % api.lpts.length; toMove = (i + 2) % api.lpts.length; } fixed = api.lpts[fixed]; toMove = api.lpts[toMove]; toMove.x = fixed.x + (fixed.x - api.mp.x); toMove.y = fixed.y + (fixed.y - api.mp.y); }, linkDerivatives: function linkDerivatives(evt, api) { if (api.mp) { var quad = api.lpts.length === 8; var i = api.mp_idx; if (quad) { if (i % 2 !== 0) { this.movePointsQuadraticLD(api, i); } } else { if (i % 3 !== 0) { this.movePointsCubicLD(api, i); } } } }, movePointsQuadraticDirOnly: function movePointsQuadraticDirOnly(api, i) { // ...we need to move _everything_ ...again var anchor, fixed, toMove; // move left and right [-1, 1].forEach(function (v) { anchor = api.mp; fixed = i + v + api.lpts.length; fixed = api.lpts[fixed % api.lpts.length]; toMove = i + 2 * v + api.lpts.length; toMove = api.lpts[toMove % api.lpts.length]; var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x), dx = toMove.x - fixed.x, dy = toMove.y - fixed.y, d = sqrt(dx * dx + dy * dy); toMove.x = fixed.x + d * cos(a); toMove.y = fixed.y + d * sin(a); }); // then, the furthest point cannot be computed properly! toMove = i + 4; toMove = api.lpts[toMove % api.lpts.length]; api.problem = toMove; }, movePointsCubicDirOnly: function movePointsCubicDirOnly(api, i) { var toMove, fixed; if (i % 3 === 1) { fixed = i - 1; fixed += fixed < 0 ? api.lpts.length : 0; toMove = i - 2; toMove += toMove < 0 ? api.lpts.length : 0; } else { fixed = (i + 1) % api.lpts.length; toMove = (i + 2) % api.lpts.length; } fixed = api.lpts[fixed]; toMove = api.lpts[toMove]; var a = atan2(fixed.y - api.mp.y, fixed.x - api.mp.x), dx = toMove.x - fixed.x, dy = toMove.y - fixed.y, d = sqrt(dx * dx + dy * dy); toMove.x = fixed.x + d * cos(a); toMove.y = fixed.y + d * sin(a); }, linkDirection: function linkDirection(evt, api) { if (api.mp) { var quad = api.lpts.length === 8; var i = api.mp_idx; if (quad) { if (i % 2 !== 0) { this.movePointsQuadraticDirOnly(api, i); } } else { if (i % 3 !== 0) { this.movePointsCubicDirOnly(api, i); } } } }, bufferPoints: function bufferPoints(evt, api) { api.bpts = JSON.parse(JSON.stringify(api.lpts)); }, moveQuadraticPoint: function moveQuadraticPoint(api, i) { this.moveCubicPoint(api, i); // then move the other control points [-1, 1].forEach(function (v) { var anchor = i - v + api.lpts.length; anchor = api.lpts[anchor % api.lpts.length]; var fixed = i - 2 * v + api.lpts.length; fixed = api.lpts[fixed % api.lpts.length]; var toMove = i - 3 * v + api.lpts.length; toMove = api.lpts[toMove % api.lpts.length]; var a = atan2(fixed.y - anchor.y, fixed.x - anchor.x), dx = toMove.x - fixed.x, dy = toMove.y - fixed.y, d = sqrt(dx * dx + dy * dy); toMove.x = fixed.x + d * cos(a); toMove.y = fixed.y + d * sin(a); }); // then signal a problem var toMove = i + 4; toMove = api.lpts[toMove % api.lpts.length]; api.problem = toMove; }, moveCubicPoint: function moveCubicPoint(api, i) { var op = api.bpts[i], np = api.lpts[i], dx = np.x - op.x, dy = np.y - op.y, len = api.lpts.length, l = i - 1 + len, r = i + 1, // original left and right ol = api.bpts[l % len], or = api.bpts[r % len], // current left and right nl = api.lpts[l % len], nr = api.lpts[r % len]; // update current left nl.x = ol.x + dx; nl.y = ol.y + dy; // update current right nr.x = or.x + dx; nr.y = or.y + dy; return { x: dx, y: dy }; }, modelCurve: function modelCurve(evt, api) { if (api.mp) { var quad = api.lpts.length === 8; var i = api.mp_idx; if (quad) { if (i % 2 !== 0) { this.movePointsQuadraticDirOnly(api, i); } else { this.moveQuadraticPoint(api, i); } } else { if (i % 3 !== 0) { this.movePointsCubicDirOnly(api, i); } else { this.moveCubicPoint(api, i); } } } }, draw: function draw(api, curves) { api.reset(); var pts = api.lpts; var quad = pts.length === 8; var c1 = quad ? new api.Bezier(pts[0], pts[1], pts[2]) : new api.Bezier(pts[0], pts[1], pts[2], pts[3]); api.drawSkeleton(c1, false, true); api.drawCurve(c1); var c2 = quad ? new api.Bezier(pts[2], pts[3], pts[4]) : new api.Bezier(pts[3], pts[4], pts[5], pts[6]); api.drawSkeleton(c2, false, true); api.drawCurve(c2); var c3 = quad ? new api.Bezier(pts[4], pts[5], pts[6]) : new api.Bezier(pts[6], pts[7], pts[8], pts[9]); api.drawSkeleton(c3, false, true); api.drawCurve(c3); var c4 = quad ? new api.Bezier(pts[6], pts[7], pts[0]) : new api.Bezier(pts[9], pts[10], pts[11], pts[0]); api.drawSkeleton(c4, false, true); api.drawCurve(c4); if (api.problem) { api.setColor("red"); api.drawCircle(api.problem, 5); } } }; /***/ }), /* 38 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(37); var generateBase = __webpack_require__(0); module.exports = generateBase("polybezier", handler); /***/ }), /* 39 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "distance", values: { "38": 1, // up arrow "40": -1 // down arrow } } }, setup: function setup(api) { api.setPanelCount(3); api.lpts = [{ x: 56, y: 153 }, { x: 144, y: 83 }, { x: 188, y: 185 }]; api.distance = 0; }, convert: function convert(p1, p2, p3, p4) { var t = 0.5; return [p2, { x: p2.x + (p3.x - p1.x) / (6 * t), y: p2.y + (p3.y - p1.y) / (6 * t) }, { x: p3.x - (p4.x - p2.x) / (6 * t), y: p3.y - (p4.y - p2.y) / (6 * t) }, p3]; }, draw: function draw(api) { api.reset(); api.setColor("lightblue"); api.drawGrid(10, 10); var pts = api.lpts; api.setColor("black"); api.setFill("black"); pts.forEach(function (p, pos) { api.drawCircle(p, 3); api.text("point " + (pos + 1), p, { x: 10, y: 7 }); }); var w = api.getPanelWidth(); var h = api.getPanelHeight(); var offset = { x: w, y: 0 }; api.setColor("lightblue"); api.drawGrid(10, 10, offset); api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); pts.forEach(function (p, pos) { api.drawCircle(p, 3, offset); }); var p1 = pts[0], p2 = pts[1], p3 = pts[2]; var dx = p3.x - p1.x, dy = p3.y - p1.y, m = Math.sqrt(dx * dx + dy * dy); dx /= m; dy /= m; api.drawLine(p1, p3, offset); var p0 = { x: p1.x + (p3.x - p2.x) - api.distance * dx, y: p1.y + (p3.y - p2.y) - api.distance * dy }; var p4 = { x: p1.x + (p3.x - p2.x) + api.distance * dx, y: p1.y + (p3.y - p2.y) + api.distance * dy }; var center = api.utils.lli4(p1, p3, p2, { x: (p0.x + p4.x) / 2, y: (p0.y + p4.y) / 2 }); api.setColor("blue"); api.drawCircle(center, 3, offset); api.drawLine(pts[1], center, offset); api.setColor("#666"); api.drawLine(center, p0, offset); api.drawLine(center, p4, offset); api.setFill("blue"); api.text("p0", p0, { x: -20 + offset.x, y: offset.y + 2 }); api.text("p4", p4, { x: +10 + offset.x, y: offset.y + 2 }); // virtual point p0 api.setColor("red"); api.drawCircle(p0, 3, offset); api.drawLine(p2, p0, offset); api.drawLine(p1, { x: p1.x + (p2.x - p0.x) / 5, y: p1.y + (p2.y - p0.y) / 5 }, offset); // virtual point p4 api.setColor("#00FF00"); api.drawCircle(p4, 3, offset); api.drawLine(p2, p4, offset); api.drawLine(p3, { x: p3.x + (p4.x - p2.x) / 5, y: p3.y + (p4.y - p2.y) / 5 }, offset); // Catmull-Rom curve for p0-p1-p2-p3-p4 var c1 = new api.Bezier(this.convert(p0, p1, p2, p3)), c2 = new api.Bezier(this.convert(p1, p2, p3, p4)); api.setColor("lightgrey"); api.drawCurve(c1, offset); api.drawCurve(c2, offset); offset.x += w; api.setColor("lightblue"); api.drawGrid(10, 10, offset); api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawCurve(c1, offset); api.drawCurve(c2, offset); api.drawPoints(c1.points, offset); api.drawPoints(c2.points, offset); api.setColor("lightgrey"); api.drawLine(c1.points[0], c1.points[1], offset); api.drawLine(c1.points[2], c2.points[1], offset); api.drawLine(c2.points[2], c2.points[3], offset); } }; /***/ }), /* 40 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(39); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("catmullmoulding", handler)); /***/ }), /* 41 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var generateBase = __webpack_require__(0); module.exports = generateBase("catmullconv"); /***/ }), /* 42 */ /***/ (function(module, exports, __webpack_require__) { var invert = __webpack_require__(6); var matrices = []; const POLYGONAL = 'polygonal', EQUIDISTANT = 'equidistant'; var binomialCoefficients = [[1],[1,1]]; function binomial(n,k) { if (n===0) return 1; var lut = binomialCoefficients; while(n >= lut.length) { var s = lut.length; var nextRow = [1]; for(var i=1,prev=s-1; i Mt.push([])); M.forEach((row,r) => row.forEach((v,c) => Mt[c][r] = v)); return Mt; } function row(M,i) { return M[i]; } function col(M,i) { var col = []; for(var r=0, l=M.length; r a + _col[i]*v; M[r][c] = _row.reduce(reducer, 0); } } return M; } function getValueColumn(P, prop) { var col = []; P.forEach(v => col.push([v[prop]])); return col; } function computeBasisMatrix(n) { /* We can form any basis matrix using a generative approach: - it's an M = (n x n) matrix - it's a lower triangular matrix: all the entries above the main diagonal are zero - the main diagonal consists of the binomial coefficients for n - all entries are symmetric about the antidiagonal. What's more, if we number rows and columns starting at 0, then the value at position M[r,c], with row=r and column=c, can be expressed as: M[r,c] = (r choose c) * M[r,r] * S, where S = 1 if r+c is even, or -1 otherwise That is: the values in column c are directly computed off of the binomial coefficients on the main diagonal, through multiplication by a binomial based on matrix position, with the sign of the value also determined by matrix position. This is actually very easy to write out in code: */ // form the square matrix, and set it to all zeroes var M = [], i = n; while (i--) { M[i] = "0".repeat(n).split('').map(v => parseInt(v)); } // populate the main diagonal var k = n - 1; for (i=0; i { S[i] = v/len; }); return S; } computeTimeValues[EQUIDISTANT] = function computeEquidistantTimeValues(P, n) { return '0'.repeat(n).split('').map((_,i) =>i/(n-1)); } function raiseRowPower(row, i) { return row.map(v => Math.pow(v,i)); } function formTMatrix(S, n) { n = n || S.length; var Tp = []; // it's easier to generate the transposed matrix: for(var i=0; i 2) { curve = this.fitCurve(api); } if (curve) { api.drawCurve(curve); api.drawSkeleton(curve); } api.drawPoints(this.points); if (!this.customTimeValues) { api.setFill(0); api.text("using " + fit.modes[this.mode] + " t values", { x: 5, y: 10 }); } }, processTimeUpdate: function processTimeUpdate(sliderid, timeValues) { var api = this.api; this.customTimeValues = true; this.fitCurve(api, timeValues); api.redraw(); }, fitCurve: function fitCurve(api, timeValues) { var bestFitData = fit(this.points, timeValues || this.mode), x = bestFitData.C.x, y = bestFitData.C.y, bpoints = []; x.forEach(function (r, i) { bpoints.push({ x: r[0], y: y[i][0] }); }); var curve = new api.Bezier(bpoints); api.setCurve(curve); this.curveset = true; this.sliders.setOptions(bestFitData.S); return curve; }, onClick: function onClick(evt, api) { this.curveset = false; this.points.push({ x: api.mx, y: api.my }); api.redraw(); } }; /***/ }), /* 44 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(43); var generateBase = __webpack_require__(0); module.exports = generateBase("curvefitting", handler); /***/ }), /* 45 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var abs = Math.abs; module.exports = { setup: function setup(api) { api.lpts = [{ x: 56, y: 153 }, { x: 144, y: 83 }, { x: 188, y: 185 }]; }, onClick: function onClick(evt, api) { if (api.lpts.length == 3) { api.lpts = []; } api.lpts.push({ x: evt.offsetX, y: evt.offsetY }); api.redraw(); }, getQRatio: function getQRatio(t) { var t2 = 2 * t, top = t2 * t - t2, bottom = top + 1; return abs(top / bottom); }, getCRatio: function getCRatio(t) { var mt = 1 - t, t3 = t * t * t, mt3 = mt * mt * mt, bottom = t3 + mt3, top = bottom - 1; return abs(top / bottom); }, drawQuadratic: function drawQuadratic(api, curve) { var labels = ["start", "t=0.5", "end"]; api.reset(); api.setColor("lightblue"); api.drawGrid(10, 10); api.setFill("black"); api.setColor("black"); api.lpts.forEach(function (p, i) { api.drawCircle(p, 3); api.text(labels[i], p, { x: 5, y: 2 }); }); if (api.lpts.length === 3) { var S = api.lpts[0], E = api.lpts[2], B = api.lpts[1], C = { x: (S.x + E.x) / 2, y: (S.y + E.y) / 2 }; api.setColor("blue"); api.drawLine(S, E); api.drawLine(B, C); api.drawCircle(C, 3); var ratio = this.getQRatio(0.5), A = { x: B.x + (B.x - C.x) / ratio, y: B.y + (B.y - C.y) / ratio }; curve = new api.Bezier([S, A, E]); api.setColor("lightgrey"); api.drawLine(A, B); api.drawLine(A, S); api.drawLine(A, E); api.setColor("black"); api.drawCircle(A, 1); api.drawCurve(curve); } }, drawCubic: function drawCubic(api, curve) { var labels = ["start", "t=0.5", "end"]; api.reset(); api.setFill("black"); api.setColor("black"); api.lpts.forEach(function (p, i) { api.drawCircle(p, 3); api.text(labels[i], p, { x: 5, y: 2 }); }); api.setColor("lightblue"); api.drawGrid(10, 10); if (api.lpts.length === 3) { var S = api.lpts[0], E = api.lpts[2], B = api.lpts[1], C = { x: (S.x + E.x) / 2, y: (S.y + E.y) / 2 }; api.setColor("blue"); api.drawLine(S, E); api.drawLine(B, C); api.drawCircle(C, 1); var ratio = this.getCRatio(0.5), A = { x: B.x + (B.x - C.x) / ratio, y: B.y + (B.y - C.y) / ratio }, selen = api.utils.dist(S, E), bclen_min = selen / 8, bclen = api.utils.dist(B, C), aesthetics = 4, be12dist = bclen_min + bclen / aesthetics, bx = be12dist * (E.x - S.x) / selen, by = be12dist * (E.y - S.y) / selen, e1 = { x: B.x - bx, y: B.y - by }, e2 = { x: B.x + bx, y: B.y + by }, v1 = { x: A.x + (e1.x - A.x) * 2, y: A.y + (e1.y - A.y) * 2 }, v2 = { x: A.x + (e2.x - A.x) * 2, y: A.y + (e2.y - A.y) * 2 }, nc1 = { x: S.x + (v1.x - S.x) * 2, y: S.y + (v1.y - S.y) * 2 }, nc2 = { x: E.x + (v2.x - E.x) * 2, y: E.y + (v2.y - E.y) * 2 }; curve = new api.Bezier([S, nc1, nc2, E]); api.drawLine(e1, e2); api.setColor("lightgrey"); api.drawLine(A, C); api.drawLine(A, v1); api.drawLine(A, v2); api.drawLine(S, nc1); api.drawLine(E, nc2); api.drawLine(nc1, nc2); api.setColor("black"); api.drawCircle(A, 1); api.drawCircle(nc1, 1); api.drawCircle(nc2, 1); api.drawCurve(curve); } } }; /***/ }), /* 46 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(45); var generateBase = __webpack_require__(0); module.exports = generateBase("pointcurves", handler); /***/ }), /* 47 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var abs = Math.abs; module.exports = { setupQuadratic: function setupQuadratic(api) { api.setPanelCount(3); var curve = api.getDefaultQuadratic(); curve.points[2].x -= 30; api.setCurve(curve); }, setupCubic: function setupCubic(api) { api.setPanelCount(3); var curve = new api.Bezier([100, 230, 30, 160, 200, 50, 210, 160]); curve.points[2].y -= 20; api.setCurve(curve); api.lut = curve.getLUT(100); }, saveCurve: function saveCurve(evt, api) { if (!api.t) return; if (!api.newcurve) return; api.setCurve(api.newcurve); api.t = false; api.redraw(); }, findTValue: function findTValue(evt, api) { var t = api.curve.on({ x: evt.offsetX, y: evt.offsetY }, 7); if (t < 0.05 || t > 0.95) return false; return t; }, markQB: function markQB(evt, api) { api.t = this.findTValue(evt, api); if (api.t) { var t = api.t, t2 = 2 * t, top = t2 * t - t2, bottom = top + 1, ratio = abs(top / bottom), curve = api.curve, A = api.A = curve.points[1], B = api.B = curve.get(t); api.C = api.utils.lli4(A, B, curve.points[0], curve.points[2]); api.ratio = ratio; this.dragQB(evt, api); } }, markCB: function markCB(evt, api) { api.t = this.findTValue(evt, api); if (api.t) { var t = api.t, mt = 1 - t, t3 = t * t * t, mt3 = mt * mt * mt, bottom = t3 + mt3, top = bottom - 1, ratio = abs(top / bottom), curve = api.curve, hull = curve.hull(t), A = api.A = hull[5], B = api.B = curve.get(t); api.db = curve.derivative(t); api.C = api.utils.lli4(A, B, curve.points[0], curve.points[3]); api.ratio = ratio; this.dragCB(evt, api); } }, drag: function drag(evt, api) { if (!api.t) return; var newB = api.newB = { x: evt.offsetX, y: evt.offsetY }; // now that we know A, B, C and the AB:BC ratio, we can compute the new A' based on the desired B' api.newA = { x: newB.x - (api.C.x - newB.x) / api.ratio, y: newB.y - (api.C.y - newB.y) / api.ratio }; }, dragQB: function dragQB(evt, api) { if (!api.t) return; this.drag(evt, api); api.update = [api.newA]; }, dragCB: function dragCB(evt, api) { if (!api.t) return; this.drag(evt, api); // preserve struts for B when repositioning var curve = api.curve, hull = curve.hull(api.t), B = api.B, Bl = hull[7], Br = hull[8], dbl = { x: Bl.x - B.x, y: Bl.y - B.y }, dbr = { x: Br.x - B.x, y: Br.y - B.y }, pts = curve.points, // find new point on s--c1 p1 = { x: api.newB.x + dbl.x, y: api.newB.y + dbl.y }, sc1 = { x: api.newA.x - (api.newA.x - p1.x) / (1 - api.t), y: api.newA.y - (api.newA.y - p1.y) / (1 - api.t) }, // find new point on c2--e p2 = { x: api.newB.x + dbr.x, y: api.newB.y + dbr.y }, sc2 = { x: api.newA.x + (p2.x - api.newA.x) / api.t, y: api.newA.y + (p2.y - api.newA.y) / api.t }, // construct new c1` based on the fact that s--sc1 is s--c1 * t nc1 = { x: pts[0].x + (sc1.x - pts[0].x) / api.t, y: pts[0].y + (sc1.y - pts[0].y) / api.t }, // construct new c2` based on the fact that e--sc2 is e--c2 * (1-t) nc2 = { x: pts[3].x - (pts[3].x - sc2.x) / (1 - api.t), y: pts[3].y - (pts[3].y - sc2.y) / (1 - api.t) }; api.p1 = p1; api.p2 = p2; api.sc1 = sc1; api.sc2 = sc2; api.nc1 = nc1; api.nc2 = nc2; api.update = [nc1, nc2]; }, drawMould: function drawMould(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var w = api.getPanelWidth(), h = api.getPanelHeight(), offset = { x: w, y: 0 }, round = api.utils.round; api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawLine({ x: w, y: 0 }, { x: w, y: h }, offset); if (api.t && api.update) { api.drawCircle(curve.get(api.t), 3); api.npts = [curve.points[0]].concat(api.update).concat([curve.points.slice(-1)[0]]); api.newcurve = new api.Bezier(api.npts); api.setColor("lightgrey"); api.drawCurve(api.newcurve); var newhull = api.drawHull(api.newcurve, api.t, offset); api.drawLine(api.npts[0], api.npts.slice(-1)[0], offset); api.drawLine(api.newA, api.newB, offset); api.setColor("grey"); api.drawCircle(api.newA, 3, offset); api.setColor("blue"); api.drawCircle(api.B, 3, offset); api.drawCircle(api.C, 3, offset); api.drawCircle(api.newB, 3, offset); api.drawLine(api.B, api.C, offset); api.drawLine(api.newB, api.C, offset); api.setFill("black"); api.text("A'", api.newA, { x: offset.x + 7, y: offset.y + 1 }); api.text("start", curve.get(0), { x: offset.x + 7, y: offset.y + 1 }); api.text("end", curve.get(1), { x: offset.x + 7, y: offset.y + 1 }); api.setFill("blue"); api.text("B'", api.newB, { x: offset.x + 7, y: offset.y + 1 }); api.text("B, at t = " + round(api.t, 2), api.B, { x: offset.x + 7, y: offset.y + 1 }); api.text("C", api.C, { x: offset.x + 7, y: offset.y + 1 }); if (curve.order === 3) { var hull = curve.hull(api.t); api.drawLine(hull[7], hull[8], offset); api.drawLine(newhull[7], newhull[8], offset); api.drawCircle(newhull[7], 3, offset); api.drawCircle(newhull[8], 3, offset); api.text("e1", newhull[7], { x: offset.x + 7, y: offset.y + 1 }); api.text("e2", newhull[8], { x: offset.x + 7, y: offset.y + 1 }); } offset.x += w; api.setColor("lightgrey"); api.drawSkeleton(api.newcurve, offset); api.setColor("black"); api.drawCurve(api.newcurve, offset); } else { offset.x += w; api.drawCurve(curve, offset); } } }; /***/ }), /* 48 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(47); var generateBase = __webpack_require__(0); module.exports = generateBase("moulding", handler); /***/ }), /* 49 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { // ============== first sketch set ===================== /** * The entry point for the quadratic curve example */ setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); curve.points[0].y -= 10; api.setCurve(curve); }, /** * The entry point for the cubic curve example */ setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); curve.points[2].y -= 20; api.setCurve(curve); api.lut = curve.getLUT(100); }, /** * When someone clicks a graphic, find the associated * on-curve t value and redraw with that new knowledge. */ onClick: function onClick(evt, api) { api.t = api.curve.on({ x: evt.offsetX, y: evt.offsetY }, 7); if (api.t < 0.05 || api.t > 0.95) api.t = false; api.redraw(); }, /** * The master draw function for the "projection" sketches */ draw: function draw(api, curve) { // draw the basic curve and curve control points api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); api.setColor("black"); if (!api.t) return; // draw the user-clicked on-curve point api.drawCircle(api.curve.get(api.t), 3); api.setColor("lightgrey"); var utils = api.utils; // find the A/B/C values as described in the section text var hull = api.drawHull(curve, api.t); var A, B, C; if (hull.length === 6) { A = curve.points[1]; B = hull[5]; C = utils.lli4(A, B, curve.points[0], curve.points[2]); api.setColor("lightgrey"); api.drawLine(curve.points[0], curve.points[2]); } else if (hull.length === 10) { A = hull[5]; B = hull[9]; C = utils.lli4(A, B, curve.points[0], curve.points[3]); api.setColor("lightgrey"); api.drawLine(curve.points[0], curve.points[3]); } // show the lines between the A/B/C values api.setColor("#00FF00"); api.drawLine(A, B); api.setColor("red"); api.drawLine(B, C); api.setColor("black"); api.drawCircle(C, 3); // with their associated labels api.setFill("black"); api.text("A", { x: 10 + A.x, y: A.y }); api.text("B (t = " + api.utils.round(api.t, 2) + ")", { x: 10 + B.x, y: B.y }); api.text("C", { x: 10 + C.x, y: C.y }); // and show the distance ratio, which we see does not change irrespective of whether A/B/C change. var d1 = utils.dist(A, B); var d2 = utils.dist(B, C); var ratio = d1 / d2; var h = api.getPanelHeight(); api.text("d1 (A-B): " + utils.round(d1, 2) + ", d2 (B-C): " + utils.round(d2, 2) + ", ratio (d1/d2): " + utils.round(ratio, 4), { x: 10, y: h - 7 }); }, // ============== second sketch set ===================== /** * on mouse move, fix the t value for drawing based on the * cursor position over the sketch. All the way on the left * is t=0, all the way on the right is t=1, with a linear * interpolation for anything in between. */ setCT: function setCT(evt, api) { api.t = evt.offsetX / api.getPanelWidth(); }, /** * Draw the quadratic C(t) values */ drawQCT: function drawQCT(api) { api.u = api.u || function (t) { var top = (t - 1) * (t - 1), bottom = 2 * t * t - 2 * t + 1; return top / bottom; }; this.drawCTgraph(api); }, /** * Draw the cubic C(t) values */ drawCCT: function drawCCT(api) { api.u = api.u || function (t) { var top = (1 - t) * (1 - t) * (1 - t), bottom = t * t * t + top; return top / bottom; }; this.drawCTgraph(api); }, /** * Draw a C(t) curve */ drawCTgraph: function drawCTgraph(api) { api.reset(); var w = api.getPanelWidth(); var pad = 20; var fwh = w - 2 * pad; // draw some axes api.setColor("black"); api.drawAxes(pad, "t", 0, 1, "u", 0, 1); // draw the C(t) function using an // indirection function that takes a // t value and spits out the C(t) value // as a point coordinate. api.setColor("blue"); var uPoint = function uPoint(t) { var value = api.u(t), res = { x: pad + t * fwh, y: pad + value * fwh }; return res; }; api.drawFunction(uPoint); // if the cursor is (or was ever) over this // graphic, draw the "crosshair" that pinpoints // where in the function the associated t/C(t) // coordinate is. if (api.t) { var v = api.u(api.t), v1 = api.utils.round(v, 3), v2 = api.utils.round(1 - v, 3), up = uPoint(api.t); api.drawLine({ x: up.x, y: pad }, up); api.drawLine({ x: pad, y: up.y }, up); api.drawCircle(up, 3); // with some handy text that shows the actual computed values api.setFill("blue"); api.text(" t = " + api.utils.round(api.t, 3), { x: up.x + 10, y: up.y - 7 }); api.text("u(t) = " + api.utils.round(v, 3), { x: up.x + 10, y: up.y + 7 }); api.setFill("black"); api.text("C = " + v1 + " * start + " + v2 + " * end", { x: w / 2 - pad, y: pad + fwh }); } } }; /***/ }), /* 50 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(49); var generateBase = __webpack_require__(0); module.exports = generateBase("abc", handler); /***/ }), /* 51 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var abs = Math.abs; module.exports = { setup: function setup(api) { this.api = api; api.setPanelCount(3); var curve1 = new api.Bezier(10, 100, 90, 30, 40, 140, 220, 220); var curve2 = new api.Bezier(5, 150, 180, 20, 80, 250, 210, 190); api.setCurve(curve1, curve2); this.pairReset(); }, pairReset: function pairReset() { this.prevstep = 0; this.step = 0; }, draw: function draw(api, curves) { var _this = this; api.reset(); var offset = { x: 0, y: 0 }; curves.forEach(function (curve) { api.drawSkeleton(curve); api.drawCurve(curve); }); // next panel: iterations var w = api.getPanelWidth(); var h = api.getPanelHeight(); offset.x += w; api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); if (this.step === 0) { this.pairs = [{ c1: curves[0], c2: curves[1] }]; } if (this.step !== this.prevstep) { var pairs = this.pairs; this.pairs = []; this.finals = []; pairs.forEach(function (p) { if (p.c1.length() < 0.6 && p.c2.length() < 0.6) { return _this.finals.push(p); } var s1 = p.c1.split(0.5); api.setColor("black"); api.drawCurve(p.c1, offset); api.setColor("red"); api.drawbbox(s1.left.bbox(), offset); api.drawbbox(s1.right.bbox(), offset); var s2 = p.c2.split(0.5); api.setColor("black"); api.drawCurve(p.c2, offset); api.setColor("blue"); api.drawbbox(s2.left.bbox(), offset); api.drawbbox(s2.right.bbox(), offset); if (s1.left.overlaps(s2.left)) { _this.pairs.push({ c1: s1.left, c2: s2.left }); } if (s1.left.overlaps(s2.right)) { _this.pairs.push({ c1: s1.left, c2: s2.right }); } if (s1.right.overlaps(s2.left)) { _this.pairs.push({ c1: s1.right, c2: s2.left }); } if (s1.right.overlaps(s2.right)) { _this.pairs.push({ c1: s1.right, c2: s2.right }); } }); this.prevstep = this.step; } else { this.pairs.forEach(function (p) { api.setColor("black"); api.drawCurve(p.c1, offset); api.drawCurve(p.c2, offset); api.setColor("red"); api.drawbbox(p.c1.bbox(), offset); api.setColor("blue"); api.drawbbox(p.c2.bbox(), offset); }); } if (this.pairs.length === 0) { this.pairReset(); this.draw(api, curves); } // next panel: results offset.x += w; api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); // get intersections as coordinates var results = curves[0].intersects(curves[1]).map(function (s) { var tvals = s.split('/').map(function (v) { return parseFloat(v); }); return { t1: tvals[0], t2: tvals[1] }; }); // filter out likely duplicates var curr = results[0], _, i, same = function same(a, b) { return abs(a.t1 - b.t1) < 0.01 && abs(a.t2 - b.t2) < 0.01; }; for (i = 1; i < results.length; i++) { _ = results[i]; if (same(curr, _)) { results.splice(i--, 1); } else { curr = _; } } api.setColor("lightblue"); api.drawCurve(curves[0], offset); api.drawCurve(curves[1], offset); api.setColor("blue"); results.forEach(function (tvals) { api.drawCircle(curves[0].get(tvals.t1), 3, offset); }); }, stepUp: function stepUp() { this.step++; this.api.redraw(); } }; /***/ }), /* 52 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(51); var generateBase = __webpack_require__(0); module.exports = generateBase("curveintersection", handler); /***/ }), /* 53 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var min = Math.min, max = Math.max; module.exports = { setupLines: function setupLines(api) { var curve1 = new api.Bezier([50, 50, 150, 110]); var curve2 = new api.Bezier([50, 250, 170, 170]); api.setCurve(curve1, curve2); }, drawLineIntersection: function drawLineIntersection(api, curves) { api.reset(); var lli = api.utils.lli4; var p = lli(curves[0].points[0], curves[0].points[1], curves[1].points[0], curves[1].points[1]); var mark = 0; curves.forEach(function (curve) { api.drawSkeleton(curve); api.setColor("black"); if (p) { var pts = curve.points, mx = min(pts[0].x, pts[1].x), my = min(pts[0].y, pts[1].y), Mx = max(pts[0].x, pts[1].x), My = max(pts[0].y, pts[1].y); if (mx <= p.x && my <= p.y && Mx >= p.x && My >= p.y) { api.setColor("#00FF00"); mark++; } } api.drawCurve(curve); }); if (p) { api.setColor(mark < 2 ? "red" : "#00FF00"); api.drawCircle(p, 3); } }, setupQuadratic: function setupQuadratic(api) { var curve1 = api.getDefaultQuadratic(); var curve2 = new api.Bezier([15, 250, 220, 20]); api.setCurve(curve1, curve2); }, setupCubic: function setupCubic(api) { var curve1 = new api.Bezier([100, 240, 30, 60, 210, 230, 160, 30]); var curve2 = new api.Bezier([25, 260, 230, 20]); api.setCurve(curve1, curve2); }, draw: function draw(api, curves) { api.reset(); curves.forEach(function (curve) { api.drawSkeleton(curve); api.drawCurve(curve); }); var utils = api.utils; var line = { p1: curves[1].points[0], p2: curves[1].points[1] }; var acpts = utils.align(curves[0].points, line); var nB = new api.Bezier(acpts); var roots = utils.roots(nB.points); roots.forEach(function (t) { var p = curves[0].get(t); api.drawCircle(p, 3); api.text("t = " + t, { x: p.x + 5, y: p.y + 10 }); }); } }; /***/ }), /* 54 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(53); var generateBase = __webpack_require__(0); module.exports = generateBase("intersections", handler); /***/ }), /* 55 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "steps", values: { "38": 1, // up arrow "40": -1 // down arrow }, controller: function controller(api) { if (api.steps < 1) { api.steps = 1; } } } }, setup: function setup(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.steps = 8; }, generate: function generate(api, curve, offset, pad, fwh) { offset.x += pad; offset.y += pad; var len = curve.length(); var pts = [{ x: 0, y: 0, d: 0 }]; for (var v = 1, t, d; v <= 100; v++) { t = v / 100; d = curve.split(t).left.length(); pts.push({ x: api.utils.map(t, 0, 1, 0, fwh), y: api.utils.map(d, 0, len, 0, fwh), d: d, t: t }); } return pts; }, draw: function draw(api, curve, offset) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var len = curve.length(); var w = api.getPanelWidth(); var h = api.getPanelHeight(); var pad = 20; var fwh = w - 2 * pad; offset.x += w; api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawAxes(pad, "t", 0, 1, "d", 0, len, offset); return this.generate(api, curve, offset, pad, fwh); }, plotOnly: function plotOnly(api, curve) { api.setPanelCount(2); var offset = { x: 0, y: 0 }; var pts = this.draw(api, curve, offset); for (var i = 0; i < pts.length - 1; i++) { api.drawLine(pts[i], pts[i + 1], offset); } }, drawColoured: function drawColoured(api, curve) { api.setPanelCount(3); var w = api.getPanelWidth(); var h = api.getPanelHeight(); var pad = 20; var fwh = w - 2 * pad; var offset = { x: 0, y: 0 }; var len = curve.length(); var pts = this.draw(api, curve, offset); var s = api.steps, i, p, ts = []; for (i = 0; i <= s; i++) { var target = i * len / s; // find the t nearest our target distance for (p = 0; p < pts.length; p++) { if (pts[p].d > target) { p--; break; } } if (p < 0) p = 0; if (p === pts.length) p = pts.length - 1; ts.push(pts[p]); } for (i = 0; i < pts.length - 1; i++) { api.drawLine(pts[i], pts[i + 1], offset); } ts.forEach(function (p) { var pt = { x: api.utils.map(p.t, 0, 1, 0, fwh), y: 0 }; var pd = { x: 0, y: api.utils.map(p.d, 0, len, 0, fwh) }; api.setColor("black"); api.drawCircle(pt, 3, offset); api.drawCircle(pd, 3, offset); api.setColor("lightgrey"); api.drawLine(pt, { x: pt.x, y: pd.y }, offset); api.drawLine(pd, { x: pt.x, y: pd.y }, offset); }); offset = { x: 2 * w, y: 0 }; api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); var idx = 0, colors = ["rgb(240,0,200)", "rgb(0,40,200)"]; api.setColor(colors[idx]); var p0 = curve.get(pts[0].t), p1; api.drawCircle(curve.get(0), 4, offset); for (i = 1, p1; i < pts.length; i++) { p1 = curve.get(pts[i].t); api.drawLine(p0, p1, offset); if (ts.indexOf(pts[i]) !== -1) { api.setColor(colors[++idx % colors.length]); api.drawCircle(p1, 4, offset); } p0 = p1; } } }; /***/ }), /* 56 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(55); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("tracing", handler)); /***/ }), /* 57 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setup: function setup(api) { var d = api.defaultWidth; api.setSize(d * 3, api.defaultHeight); // Set up two curves with identical form, but different functions: var q = this.q = new api.Bezier(115, 250, 10, 35, 190, 45); var c = this.c = q.raise(); q.points.forEach(function (p) { return p.x += d / 2; }); c.points.forEach(function (p) { return p.x += 3 * d / 2; }); // And "fake" a master curve that we'll never draw, but which // will allow us to move interact with the curve points. api.setCurve({ points: q.points.concat(c.points) }); }, updateCurves: function updateCurves(api, curve) { // update the quadratic and cubic curves by grabbing // whatever the points in our "fake" master curve are var q = this.q; q.points = curve.points.slice(0, 3); q.update(); var c = this.c; c.points = curve.points.slice(3, 7); c.update(); }, drawCurvature: function drawCurvature(api, curve, omni) { api.drawSkeleton(curve); api.drawCurve(curve); var s, t, p, n, c, ox, oy; for (s = 0; s < 256; s++) { // Draw the curvature as a coloured line at the // current point, along the normal. api.setColor('rgba(255,127,' + s + ',0.6)'); t = s / 255; p = curve.get(t); n = curve.normal(t); c = curve.curvature(t); ox = c.k * n.x; oy = c.k * n.y; api.drawLine(p, { x: p.x + ox, y: p.y + oy }); // And if requested, also draw it along the anti-normal. if (omni) { api.setColor('rgba(' + s + ',127,255,0.6)'); api.drawLine(p, { x: p.x - ox, y: p.y - oy }); } } }, proxyDraw: function proxyDraw(api, curve, omni) { var _this = this; api.reset(); this.updateCurves(api, curve); [this.q, this.c].forEach(function (curve) { return _this.drawCurvature(api, curve, omni); }); }, draw: function draw(api, curve) { this.proxyDraw(api, curve); }, drawOmni: function drawOmni(api, curve) { this.proxyDraw(api, curve, true); } }; /***/ }), /* 58 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(57); var generateBase = __webpack_require__(0); module.exports = generateBase("curvature", handler); /***/ }), /* 59 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { // These are functions that can be called "From the page", // rather than being internal to the sketch. This is useful // for making on-page controls hook into the sketch code. statics: { keyHandlingOptions: { propName: "steps", values: { "38": 1, // up arrow "40": -1 // down arrow }, controller: function controller(api) { if (api.steps < 1) { api.steps = 1; } } } }, /** * Set up the default quadratic curve. */ setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); api.steps = 10; }, /** * Set up the default cubic curve. */ setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.steps = 16; }, /** * Draw a curve and its polygon-approximation, * showing the "true" length of the curve vs. the * length based on tallying up the polygon sections. */ draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); var pts = curve.getLUT(api.steps); var step = 1 / api.steps; var p0 = curve.points[0], pc; for (var t = step; t < 1.0 + step; t += step) { pc = curve.get(Math.min(t, 1)); api.setColor("red"); api.drawLine(p0, pc); p0 = pc; } var len = curve.length(); var alen = 0; for (var i = 0, p1, dx, dy; i < pts.length - 1; i++) { p0 = pts[i]; p1 = pts[i + 1]; dx = p1.x - p0.x; dy = p1.y - p0.y; alen += Math.sqrt(dx * dx + dy * dy); } alen = (100 * alen | 0) / 100; len = (100 * len | 0) / 100; api.text("Approximate length, " + api.steps + " steps: " + alen + " (true: " + len + ")", { x: 10, y: 15 }); } }; /***/ }), /* 60 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(59); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("arclengthapprox", handler)); /***/ }), /* 61 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var sin = Math.sin; var tau = Math.PI * 2; module.exports = { /** * Set up a sinusoid generating function, * which we'll use to draw the "progressively * better looking" integral approximations. */ setup: function setup(api) { var w = api.getPanelWidth(); var h = api.getPanelHeight(); var generator; if (!this.generator) { generator = function generator(v, scale) { scale = scale || 1; return { x: v * w / tau, y: scale * sin(v) }; }; generator.start = 0; generator.end = tau; generator.step = 0.1; generator.scale = h / 3; this.generator = generator; } }, /** * Draw the generator's sine function: */ drawSine: function drawSine(api, dheight) { var w = api.getPanelWidth(); var h = api.getPanelHeight(); var generator = this.generator; generator.dheight = dheight; api.setColor("black"); api.drawLine({ x: 0, y: h / 2 }, { x: w, y: h / 2 }); api.drawFunction(generator, { x: 0, y: h / 2 }); }, /** * Draw the sliced between the sine curve and * the x-axis, with a variable number of steps so * we can show the approximation becoming better * and better as we increase the step count. */ drawSlices: function drawSlices(api, steps) { var w = api.getPanelWidth(); var h = api.getPanelHeight(); var f = w / tau; var area = 0; var c = steps <= 25 ? 1 : 0; api.reset(); api.setColor("transparent"); api.setFill("rgba(150,150,255, 0.4)"); for (var step = tau / steps, i = step / 2, v, p1, p2; i < tau + step / 2; i += step) { v = this.generator(i); // draw a rectangular strip between the curve and the x-axis: p1 = { x: v.x - f * step / 2 + c, y: 0 }; p2 = { x: v.x + f * step / 2 - c, y: v.y * this.generator.scale }; if (!c) { api.setFill("rgba(150,150,255," + (0.4 + 0.3 * Math.random()) + ")"); } api.drawRect(p1, p2, { x: 0, y: h / 2 }); // and keep track of the (much simpler to compute) approximated area under the curve so far: area += step * Math.abs(v.y * this.generator.scale); } api.setFill("black"); var trueArea = (100 * 4 * h / 3 | 0) / 100; var currArea = (100 * area | 0) / 100; api.text("Approximating with " + steps + " strips (true area: " + trueArea + "): " + currArea, { x: 10, y: h - 15 }); }, /** * Draw the sine curve, with a 10 slice approximation: */ drawCoarseIntegral: function drawCoarseIntegral(api) { api.reset(); this.drawSlices(api, 10); this.drawSine(api); }, /** * Draw the sine curve, with a 24 slice approximation: */ drawFineIntegral: function drawFineIntegral(api) { api.reset(); this.drawSlices(api, 24); this.drawSine(api); }, /** * Draw the sine curve, with a 99 slice approximation: */ drawSuperFineIntegral: function drawSuperFineIntegral(api) { api.reset(); this.drawSlices(api, 99); this.drawSine(api); }, /** * Set up a default cubic curve for which we'll be determining * its length, using the iterative integral approach: */ setupCurve: function setupCurve(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, /** * Draw our curve, and show its computed length: */ drawCurve: function drawCurve(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var len = curve.length(); api.setFill("black"); api.text("Curve length: " + len + " pixels", { x: 10, y: 15 }); } }; /***/ }), /* 62 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(61); var generateBase = __webpack_require__(0); module.exports = generateBase("arclength", handler); /***/ }), /* 63 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var sketch = { getCurve: function getCurve(api) { if (!sketch.curve) { sketch.curve = new api.Bezier(20, 250, 30, 20, 200, 250, 250, 20); } return sketch.curve; }, onMouseMove: function onMouseMove(evt, api) { api.redraw(); }, tforx: { setup: function setup(api) { api.setPanelCount(2); api.setCurve(sketch.getCurve(api)); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var w = api.defaultWidth; var h = api.defaultHeight; var bbox = curve.bbox(); var x = api.mx; if (bbox.x.min < x && x < bbox.x.max) { api.setColor("red"); api.drawLine({ x: x, y: 0 }, { x: x, y: h }); api.text("x=" + (x | 0), { x: x + 5, y: h - 30 }); } api.setColor("black"); api.drawLine({ x: w, y: 0 }, { x: w, y: h }); api.setOffset({ x: w, y: 0 }); // draw x = t(x) api.drawLine({ x: 0, y: h - 20 }, { x: w, y: h - 20 }); api.text('0', { x: 10, y: h - 10 }); api.text('⅓', { x: 10 + (w - 10) / 3, y: h - 10 }); api.text('⅔', { x: 10 + 2 * (w - 10) / 3, y: h - 10 }); api.text('1', { x: w - 10, y: h - 10 }); var p = void 0, s = { x: 0, y: h - curve.get(0).x }; for (var step = 0.05, t = step; t < 1 + step; t += step) { p = { x: t * w, y: h - curve.get(t).x }; api.drawLine(s, p); s = p; } api.setColor("black"); api.text("↑\nx", { x: 10, y: h / 2 }); api.text("t →", { x: w / 2, y: h - 10 }); if (bbox.x.min < x && x < bbox.x.max) { api.setColor("red"); api.drawLine({ x: 0, y: h - x }, { x: w, y: h - x }); } } }, yforx: { setup: function setup(api) { api.setCurve(sketch.getCurve(api)); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var w = api.defaultWidth; var h = api.defaultHeight; var bbox = curve.bbox(); var x = api.mx; if (bbox.x.min < x && x < bbox.x.max) { api.setColor("red"); // The root finder is based on normal x/y coordinates, // so we can "trick" it by giving it "t" values as x // values, and "x" values as y values. Since it won't // even look at the x dimension, we can also just leave it. var roots = api.utils.roots(curve.points.map(function (v) { return { x: v.x, y: v.x - x }; })); roots = roots.filter(function (t) { return t >= 0 && t <= 1.0; }); var t = roots[0]; var p = curve.get(t); api.drawLine({ x: p.x, y: p.y }, { x: p.x, y: h }); api.drawLine({ x: p.x, y: p.y }, { x: 0, y: p.y }); api.text("y=" + (p.y | 0), { x: p.x / 2, y: p.y - 5 }); api.text("x=" + (p.x | 0), { x: x + 5, y: h - (h - p.y) / 2 }); api.text("t=" + (t * 100 | 0) / 100, { x: x + 15, y: p.y }); } } } }; module.exports = sketch; /***/ }), /* 64 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(63); var generateBase = __webpack_require__(0); module.exports = generateBase("yforx", handler); /***/ }), /* 65 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setup: function setup(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.reset(); api._map_loaded = false; }, draw: function draw(api, curve) { var w = 400, h = w, unit = this.unit, center = { x: w / 2, y: h / 2 }; api.setSize(w, h); api.setPanelCount(2); api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); api.offset.x += 400; if (api._map_loaded) { api.image(api._map_image); } else { setTimeout(function () { this.drawBase(api, curve); this.draw(api, curve); }.bind(this), 100); } api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }); var npts = [{ x: 0, y: 0 }, { x: 0, y: unit }, { x: unit, y: unit }, this.forwardTransform(curve.points, unit)]; var canonical = new api.Bezier(npts); api.setColor("blue"); api.drawCurve(canonical, center); api.drawCircle(npts[3], 3, center); }, forwardTransform: function forwardTransform(pts, s) { s = s || 1; var p1 = pts[0], p2 = pts[1], p3 = pts[2], p4 = pts[3]; var xn = -p1.x + p4.x - (-p1.x + p2.x) * (-p1.y + p4.y) / (-p1.y + p2.y); var xd = -p1.x + p3.x - (-p1.x + p2.x) * (-p1.y + p3.y) / (-p1.y + p2.y); var np4x = s * xn / xd; var yt1 = s * (-p1.y + p4.y) / (-p1.y + p2.y); var yt2 = s - s * (-p1.y + p3.y) / (-p1.y + p2.y); var yp = yt2 * xn / xd; var np4y = yt1 + yp; return { x: np4x, y: np4y }; }, drawBase: function drawBase(api, curve) { api.reset(); var w = 400, h = w, unit = this.unit = w / 5, center = { x: w / 2, y: h / 2 }; api.setSize(w, h); // axes + gridlines api.setColor("lightgrey"); for (var x = 0; x < w; x += unit / 2) { api.drawLine({ x: x, y: 0 }, { x: x, y: h }); } for (var y = 0; y < h; y += unit / 2) { api.drawLine({ x: 0, y: y }, { x: w, y: y }); } api.setColor("black"); api.drawLine({ x: w / 2, y: 0 }, { x: w / 2, y: h }); api.drawLine({ x: 0, y: h / 2 }, { x: w, y: h / 2 }); // Inflection border: api.setColor("green"); api.drawLine({ x: -w / 2, y: unit }, { x: w / 2, y: unit }, center); // the three stable points api.setColor("black"); api.setFill("black"); api.drawCircle({ x: 0, y: 0 }, 4, center); api.text("(0,0)", { x: 5 + center.x, y: 15 + center.y }); api.drawCircle({ x: 0, y: unit }, 4, center); api.text("(0,1)", { x: 5 + center.x, y: unit + 15 + center.y }); api.drawCircle({ x: unit, y: unit }, 4, center); api.text("(1,1)", { x: unit + 5 + center.x, y: unit + 15 + center.y }); // cusp parabola: api.setWeight(1.5); api.setColor("#FF0000"); api.setFill(api.getColor()); var pts = []; var px = 1, py = 1; for (x = -10; x <= 1; x += 0.01) { y = (-x * x + 2 * x + 3) / 4; if (x > -10) { pts.push({ x: unit * px, y: unit * py }); api.drawLine({ x: unit * px, y: unit * py }, { x: unit * x, y: unit * y }, center); } px = x; py = y; } pts.push({ x: unit * px, y: unit * py }); api.text("Curve form has cusp →", { x: w / 2 - unit * 2, y: h / 2 + unit / 2.5 }); // loop/arch transition boundary, elliptical section api.setColor("#FF00FF"); api.setFill(api.getColor()); var sqrt = Math.sqrt; for (x = 1; x >= 0; x -= 0.005) { pts.push({ x: unit * px, y: unit * py }); y = 0.5 * (sqrt(3) * sqrt(4 * x - x * x) - x); api.drawLine({ x: unit * px, y: unit * py }, { x: unit * x, y: unit * y }, center); px = x; py = y; } pts.push({ x: unit * px, y: unit * py }); api.text("← Curve forms a loop at t = 1", { x: w / 2 + unit / 4, y: h / 2 + unit / 1.5 }); // loop/arch transition boundary, parabolic section api.setColor("#3300FF"); api.setFill(api.getColor()); for (x = 0; x > -w; x -= 0.01) { pts.push({ x: unit * px, y: unit * py }); y = (-x * x + 3 * x) / 3; api.drawLine({ x: unit * px, y: unit * py }, { x: unit * x, y: unit * y }, center); px = x; py = y; } pts.push({ x: unit * px, y: unit * py }); api.text("← Curve forms a loop at t = 0", { x: w / 2 - unit + 10, y: h / 2 - unit * 1.25 }); // shape fill api.setColor("transparent"); api.setFill("rgba(255,120,100,0.2)"); api.drawPath(pts, center); pts = [{ x: -w / 2, y: unit }, { x: w / 2, y: unit }, { x: w / 2, y: h }, { x: -w / 2, y: h }]; api.setFill("rgba(0,200,0,0.2)"); api.drawPath(pts, center); // further labels api.setColor("black"); api.setFill(api.getColor()); api.text("← Curve form has one inflection →", { x: w / 2 - unit, y: h / 2 + unit * 1.75 }); api.text("← Plain curve ↕", { x: w / 2 + unit / 2, y: h / 6 }); api.text("↕ Double inflection", { x: 10, y: h / 2 - 10 }); api._map_image = api.toImage(); api._map_loaded = true; } }; /***/ }), /* 66 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(65); var generateBase = __webpack_require__(0); module.exports = generateBase("canonical", handler); /***/ }), /* 67 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupCubic: function setupCubic(api) { var curve = new api.Bezier(135, 25, 25, 135, 215, 75, 215, 240); api.setCurve(curve); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); api.setColor("red"); curve.inflections().forEach(function (t) { api.drawCircle(curve.get(t), 5); }); } }; /***/ }), /* 68 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(67); var generateBase = __webpack_require__(0); module.exports = generateBase("inflections", handler); /***/ }), /* 69 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, align: function align(points, line) { var tx = line.p1.x, ty = line.p1.y, a = -Math.atan2(line.p2.y - ty, line.p2.x - tx), cos = Math.cos, sin = Math.sin, d = function d(v) { return { x: (v.x - tx) * cos(a) - (v.y - ty) * sin(a), y: (v.x - tx) * sin(a) + (v.y - ty) * cos(a), a: a }; }; return points.map(d); }, // FIXME: I'm not satisfied with needing to turn a bbox[] into a point[], // this needs a bezier.js solution, really, with a call curve.tightbbox() transpose: function transpose(points, angle, offset) { var tx = offset.x, ty = offset.y, cos = Math.cos, sin = Math.sin, v = [points.x.min, points.y.min, points.x.max, points.y.max]; return [{ x: v[0], y: v[1] }, { x: v[2], y: v[1] }, { x: v[2], y: v[3] }, { x: v[0], y: v[3] }].map(function (p) { var x = p.x, y = p.y; return { x: x * cos(angle) - y * sin(angle) + tx, y: x * sin(angle) + y * cos(angle) + ty }; }); }, draw: function draw(api, curve) { api.reset(); var pts = curve.points; var line = { p1: pts[0], p2: pts[pts.length - 1] }; var apts = this.align(pts, line); var angle = -apts[0].a; var aligned = new api.Bezier(apts); var bbox = aligned.bbox(); var tpts = this.transpose(bbox, angle, pts[0]); api.setColor("#00FF00"); api.drawLine(tpts[0], tpts[1]); api.drawLine(tpts[1], tpts[2]); api.drawLine(tpts[2], tpts[3]); api.drawLine(tpts[3], tpts[0]); api.setColor("black"); api.drawSkeleton(curve); api.drawCurve(curve); } }; /***/ }), /* 70 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(69); var generateBase = __webpack_require__(0); module.exports = generateBase("tightbounds", handler); /***/ }), /* 71 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { /** * Setup function for a default quadratic curve. */ setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, /** * Setup function for a default cubic curve. */ setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, /** * A coordinate rotation function that rotates and * translates the curve, such that the first coordinate * of the curve is (0,0) and the last coordinate is (..., 0) */ align: function align(points, line) { var tx = line.p1.x, ty = line.p1.y, // The atan2 function is so important to computing // that most CPUs have a dedicated implementation // at the hardware level for it. a = -Math.atan2(line.p2.y - ty, line.p2.x - tx), cos = Math.cos, sin = Math.sin, d = function d(v) { return { x: (v.x - tx) * cos(a) - (v.y - ty) * sin(a), y: (v.x - tx) * sin(a) + (v.y - ty) * cos(a) }; }; return points.map(d); }, /** * Draw a curve and its aligned counterpart * side by side across two panels. */ draw: function draw(api, curve) { api.setPanelCount(2); api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var pts = curve.points; var line = { p1: pts[0], p2: pts[pts.length - 1] }; var apts = this.align(pts, line); var aligned = new api.Bezier(apts); var w = api.getPanelWidth(); var h = api.getPanelHeight(); var offset = { x: w, y: 0 }; api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); offset.x += w / 4; offset.y += h / 2; api.setColor("grey"); api.drawLine({ x: 0, y: -h / 2 }, { x: 0, y: h / 2 }, offset); api.drawLine({ x: -w / 4, y: 0 }, { x: w, y: 0 }, offset); api.setFill("grey"); api.setColor("black"); api.drawSkeleton(aligned, offset); api.drawCurve(aligned, offset); } }; /***/ }), /* 72 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(71); var generateBase = __webpack_require__(0); module.exports = generateBase("aligning", handler); /***/ }), /* 73 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, draw: function draw(api, curve) { api.reset(); api.setColor("#00FF00"); api.drawbbox(curve.bbox()); api.setColor("black"); api.drawSkeleton(curve); api.drawCurve(curve); api.setColor("red"); curve.extrema().values.forEach(function (t) { api.drawCircle(curve.get(t), 3); }); } }; /***/ }), /* 74 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(73); var generateBase = __webpack_require__(0); module.exports = generateBase("boundingbox", handler); /***/ }), /* 75 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); curve.points[2].x = 210; api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, draw: function draw(api, curve) { api.setPanelCount(3); api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var tf = curve.order + 1, pad = 20, pts = curve.points, w = api.getPanelWidth(), h = api.getPanelHeight(), offset = { x: w, y: 0 }; var x_pts = JSON.parse(JSON.stringify(pts)).map(function (p, t) { return { x: w * t / tf, y: p.x }; }); api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawAxes(pad, "t", 0, 1, "x", 0, w, offset); offset.x += pad; var xcurve = new api.Bezier(x_pts); api.drawCurve(xcurve, offset); api.setColor("red"); xcurve.extrema().y.forEach(function (t) { var p = xcurve.get(t); api.drawCircle(p, 3, offset); }); offset.x += w - pad; var y_pts = JSON.parse(JSON.stringify(pts)).map(function (p, t) { return { x: w * t / tf, y: p.y }; }); api.setColor("black"); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawAxes(pad, "t", 0, 1, "y", 0, w, offset); offset.x += pad; var ycurve = new api.Bezier(y_pts); api.drawCurve(ycurve, offset); api.setColor("red"); ycurve.extrema().y.forEach(function (t) { var p = ycurve.get(t); api.drawCircle(p, 3, offset); }); } }; /***/ }), /* 76 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(75); var generateBase = __webpack_require__(0); module.exports = generateBase("extremities", handler); /***/ }), /* 77 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); curve.points[2].x = 210; api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, draw: function draw(api, curve) { api.setPanelCount(3); api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var tf = curve.order, pad = 20, pts = curve.points, w = api.getPanelWidth(), wp = w - 2 * pad, h = api.getPanelHeight(), offset = { x: w, y: 0 }; var x_pts = JSON.parse(JSON.stringify(pts)).map(function (p, t) { return { x: wp * t / tf, y: p.x }; }); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawAxes(pad, "t", 0, 1, "x", 0, w, offset); offset.x += pad; api.drawCurve(new api.Bezier(x_pts), offset); offset.x += w - pad; var y_pts = JSON.parse(JSON.stringify(pts)).map(function (p, t) { return { x: wp * t / tf, y: p.y }; }); api.drawLine({ x: 0, y: 0 }, { x: 0, y: h }, offset); api.drawAxes(pad, "t", 0, 1, "y", 0, w, offset); offset.x += pad; api.drawCurve(new api.Bezier(y_pts), offset); } }; /***/ }), /* 78 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(77); var generateBase = __webpack_require__(0); module.exports = generateBase("components", handler); /***/ }), /* 79 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var vectorOffset; var normalsOffset; var SHADOW_ALPHA = 0.2; var SHOW_PROJECTIONS = true; function normalize(v) { var d = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); return { x: v.x / d, y: v.y / d, z: v.z / d }; } function vdot(v1, v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } function vscale(v1, s) { return { x: s * v1.x, y: s * v1.y, z: s * v1.z }; } function vplus(v1, v2) { return { x: v1.x + v2.x, y: v1.y + v2.y, z: v1.z + v2.z }; } function vminus(v1, v2) { return { x: v1.x - v2.x, y: v1.y - v2.y, z: v1.z - v2.z }; } function vcross(v1, v2) { return { x: v1.y * v2.z - v1.z * v2.y, y: v1.z * v2.x - v1.x * v2.z, z: v1.x * v2.y - v1.y * v2.x }; } function vlerp(t, v1, v2) { return { x: (1 - t) * v1.x + t * v2.x, y: (1 - t) * v1.y + t * v2.y, z: (1 - t) * v1.z + t * v2.z }; } module.exports = { setup: function setup(api) { vectorOffset = { x: 2 * api.getPanelWidth() / 5, y: 4 * api.getPanelHeight() / 5 }; api.setSize(1.25 * api.getPanelWidth(), api.getPanelHeight()); }, drawCube: function drawCube(api) { var prj = function prj(p) { return api.project(p, vectorOffset); }; var cube = [{ x: 0, y: 0, z: 0 }, { x: 200, y: 0, z: 0 }, { x: 200, y: 200, z: 0 }, { x: 0, y: 200, z: 0 }, { x: 0, y: 0, z: 200 }, { x: 200, y: 0, z: 200 }, { x: 200, y: 200, z: 200 }, { x: 0, y: 200, z: 200 }].map(function (p) { return prj(p); }); // "most of the cube" api.setColor("grey"); api.drawLine(cube[1], cube[2]); api.drawLine(cube[2], cube[3]); api.drawLine(cube[1], cube[5]); api.drawLine(cube[2], cube[6]); api.drawLine(cube[3], cube[7]); api.drawLine(cube[4], cube[5]); api.drawLine(cube[5], cube[6]); api.drawLine(cube[6], cube[7]); api.drawLine(cube[7], cube[4]); // x axis api.setColor("blue"); api.drawLine(cube[0], cube[1]); // y axis api.setColor("red"); api.drawLine(cube[3], cube[0]); // z axis api.setColor("green"); api.drawLine(cube[0], cube[4]); }, drawCurve: function drawCurve(api, curvepoints, project) { var prj = function prj(p) { return api.project(p, vectorOffset); }, curve2d = curvepoints.map(function (p) { return prj(p); }), points; if (project) { // projections api.setColor("rgba(0,0,0," + SHADOW_ALPHA + ")"); api.drawCurve({ points: curvepoints.map(function (p) { return api.projectXY(p, vectorOffset); }) }); api.drawCurve({ points: curvepoints.map(function (p) { return api.projectYZ(p, vectorOffset); }) }); api.drawCurve({ points: curvepoints.map(function (p) { return api.projectXZ(p, vectorOffset); }) }); } // control lines api.setColor("#333"); api.drawLine(curve2d[0], curve2d[1]); api.drawCircle(curve2d[1], 3); api.drawCircle(curve2d[2], 3); api.drawLine(curve2d[2], curve2d[3]); // main curve api.setColor("black"); api.drawCircle(curve2d[0], 3); api.drawCircle(curve2d[3], 3); var curve = new api.Bezier(curve2d); api.drawCurve({ points: curve2d }); }, getFrenetVectors: function getFrenetVectors(t, curve, d1curve) { var o = curve.get(t), // get the normalized tangent dt = d1curve.get(t), // and then let's work in the change in tangent ddt = d1curve.derivative(t), b = normalize(vplus(dt, ddt)), // compute the normalized axis of rotation r = normalize(vcross(b, dt)), // compute the normal n = normalize(vcross(r, dt)); return { o: o, dt: dt, r: r, n: n }; }, lerpVectors: function lerpVectors(t, v1, v2) { var v = {}; ['o', 'dt', 'r', 'n'].forEach(function (p) { v[p] = vlerp(t, v1[p], v2[p]); }); return v; }, generateRMF: function generateRMF(curve, d1curve) { var frames = [], step = 0.05; frames.push(this.getFrenetVectors(0, curve, d1curve)); for (var t0 = 0; t0 <= 1; t0 += step) { var x0 = frames.slice(-1)[0], t1 = t0 + step, x1 = { o: curve.get(t1), dt: d1curve.get(t1) }, v1 = vminus(x1.o, x0.o), c1 = vdot(v1, v1), riL = vminus(x0.r, vscale(v1, 2 / c1 * vdot(v1, x0.r))), tiL = vminus(x0.dt, vscale(v1, 2 / c1 * vdot(v1, x0.dt))), v2 = vminus(x1.dt, tiL), c2 = vdot(v2, v2); x1.r = vminus(riL, vscale(v2, 2 / c2 * vdot(v2, riL))); x1.n = vcross(x1.r, x1.dt); frames.push(x1); } return frames; }, getRMF: function getRMF(t, curve, d1curve) { if (!this.rmf_LUT) { this.rmf_LUT = this.generateRMF(curve, d1curve); } // find integer index var l = this.rmf_LUT.length; var i = t * l; if (i != (i | 0)) { // no intenger index: interpolate values? i = i | 0; if (i === l - 1) return this.rmf_LUT[i - 1]; var j = i + 1, ti = i / l, tj = j / l; t = (t - ti) / (tj - ti); return this.lerpVectors(t, this.rmf_LUT[i], this.rmf_LUT[j]); } return this.rmf_LUT[i]; }, drawVector: function drawVector(api, from, to, len, r, g, b) { var prj = function prj(p) { return api.project(p, vectorOffset); }; to = normalize(to); to = { x: from.x + len * to.x, y: from.y + len * to.y, z: from.z + len * to.z }; api.setColor("rgba(" + r + "," + g + "," + b + ",1)"); // draw the actual vector api.drawLine(prj(from), prj(to)); }, drawFrenetVectors: function drawFrenetVectors(api) { api.reset(); var prj = function prj(p) { return api.project(p, vectorOffset); }; this.drawCube(api); var curvepoints = [{ x: 120, y: 0, z: 0 }, { x: 120, y: 220, z: 0 }, { x: 30, y: 0, z: 30 }, { x: 0, y: 0, z: 200 }]; this.drawCurve(api, curvepoints, SHOW_PROJECTIONS); // let's mark t var curve = new api.Bezier(curvepoints); var d1curve = new api.Bezier(curve.dpoints[0]); var t = Math.max(api.hover.x ? api.hover.x / api.getPanelWidth() : 0, 0); var mt = curve.get(t); api.drawCircle(prj(mt), 3); // draw the tangent, rotational axis, and normal var vectors = this.getFrenetVectors(t, curve, d1curve); this.drawVector(api, mt, vectors.dt, 40, 0, 200, 0); this.drawVector(api, mt, vectors.r, 40, 0, 0, 200); this.drawVector(api, mt, vectors.n, 40, 200, 0, 0); }, drawRMFNormals: function drawRMFNormals(api) { api.reset(); var prj = function prj(p) { return api.project(p, vectorOffset); }; this.drawCube(api); var curvepoints = [{ x: 120, y: 0, z: 0 }, { x: 120, y: 220, z: 0 }, { x: 30, y: 0, z: 30 }, { x: 0, y: 0, z: 200 }]; this.drawCurve(api, curvepoints, SHOW_PROJECTIONS); // let's mark t var curve = new api.Bezier(curvepoints); var d1curve = new api.Bezier(curve.dpoints[0]); var t = Math.max(api.hover.x ? api.hover.x / api.getPanelWidth() : 0, 0); var mt = curve.get(t); api.drawCircle(prj(mt), 3); // draw the tangent, rotational axis, and normal var vectors = this.getRMF(t, curve, d1curve); this.drawVector(api, mt, vectors.dt, 40, 0, 200, 0); this.drawVector(api, mt, vectors.r, 40, 0, 0, 200); this.drawVector(api, mt, vectors.n, 40, 200, 0, 0); } }; /***/ }), /* 80 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(79); var generateBase = __webpack_require__(0); module.exports = generateBase("pointvectors3d", handler); /***/ }), /* 81 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); var i, t, p, tg, n, m, nd = 20; for (i = 0; i <= 10; i++) { t = i / 10.0; p = curve.get(t); tg = curve.derivative(t); m = Math.sqrt(tg.x * tg.x + tg.y * tg.y); tg = { x: tg.x / m, y: tg.y / m }; n = curve.normal(t); api.setColor("blue"); api.drawLine(p, { x: p.x + tg.x * nd, y: p.y + tg.y * nd }); api.setColor("red"); api.drawLine(p, { x: p.x + n.x * nd, y: p.y + n.y * nd }); api.setColor("black"); api.drawCircle(p, 3); } } }; /***/ }), /* 82 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(81); var generateBase = __webpack_require__(0); module.exports = generateBase("pointvectors", handler); /***/ }), /* 83 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var generateBase = __webpack_require__(0); module.exports = generateBase("derivatives"); /***/ }), /* 84 */ /***/ (function(module, exports, __webpack_require__) { var transpose = __webpack_require__(5); module.exports = function multiply(m1, m2) { var M = []; var m2t = transpose(m2); m1.forEach( (row, r) => { M[r] = []; m2t.forEach( (col, c) => { M[r][c] = row.map( (v,i) => col[i] * v).reduce( (a,v) => a+v, 0 ); }); }); return M; }; /***/ }), /* 85 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var invert = __webpack_require__(6); var multiply = __webpack_require__(84); var transpose = __webpack_require__(5); var Reordering = { statics: { keyHandlingOptions: { values: { "38": function _(api) { api.setCurve(api.curve.raise()); api.redraw(); }, "40": function _(api) { api.setCurve(Reordering.lower(api)); api.redraw(); } } } }, // Based on http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/ lower: function lower(api) { var curve = api.curve, pts = curve.points, k = pts.length, M = [], n = k - 1, i; // build M, which will be (k) rows by (k-1) columns for (i = 0; i < k; i++) { M[i] = new Array(k - 1).fill(0); if (i === 0) { M[i][0] = 1; } else if (i === n) { M[i][i - 1] = 1; } else { M[i][i - 1] = i / k; M[i][i] = 1 - M[i][i - 1]; } } // then, apply our matrix operations: var Mt = transpose(M); var Mc = multiply(Mt, M); var Mi = invert(Mc); if (!Mi) { console.error('MtM has no inverse?'); return curve; } var V = multiply(Mi, Mt); // And then we map our k-order list of coordinates // to an n-order list of coordinates, instead: var x = pts.map(function (p) { return [p.x]; }); var nx = multiply(V, x); var y = pts.map(function (p) { return [p.y]; }); var ny = multiply(V, y); var npts = nx.map(function (x, i) { return { x: x[0], y: ny[i][0] }; }); return new api.Bezier(npts); }, getInitialState: function getInitialState() { return { order: 0 }; }, setup: function setup(api) { var points = []; var w = api.getPanelWidth(), h = api.getPanelHeight(); for (var i = 0; i < 10; i++) { points.push({ x: w / 2 + Math.random() * 20 + Math.cos(Math.PI * 2 * i / 10) * (w / 2 - 40), y: h / 2 + Math.random() * 20 + Math.sin(Math.PI * 2 * i / 10) * (h / 2 - 40) }); } var curve = new api.Bezier(points); api.setCurve(curve); }, draw: function draw(api, curve) { api.reset(); var pts = curve.points; this.setState({ order: pts.length }); var p0 = pts[0]; // we can't "just draw" this curve, since it'll be an arbitrary order, // And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm: for (var t = 0; t <= 1; t += 0.01) { var q = JSON.parse(JSON.stringify(pts)); while (q.length > 1) { for (var i = 0; i < q.length - 1; i++) { q[i] = { x: q[i].x + (q[i + 1].x - q[i].x) * t, y: q[i].y + (q[i + 1].y - q[i].y) * t }; } q.splice(q.length - 1, 1); } api.drawLine(p0, q[0]); p0 = q[0]; } p0 = pts[0]; api.setColor("black"); api.drawCircle(p0, 3); pts.forEach(function (p) { if (p === p0) return; api.setColor("#DDD"); api.drawLine(p0, p); api.setColor("black"); api.drawCircle(p, 3); p0 = p; }); }, getOrder: function getOrder() { var order = this.state.order; if (order % 10 === 1 && order !== 11) { order += "st"; } else if (order % 10 === 2 && order !== 12) { order += "nd"; } else if (order % 10 === 3 && order !== 13) { order += "rd"; } else { order += "th"; } return order; }, onMouseMove: function onMouseMove(evt, api) { api.redraw(); } }; module.exports = Reordering; /***/ }), /* 86 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(85); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("reordering", handler)); /***/ }), /* 87 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var generateBase = __webpack_require__(0); module.exports = generateBase("matrixsplit"); /***/ }), /* 88 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.forward = true; }, drawSplit: function drawSplit(api, curve) { api.setPanelCount(2); api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); var offset = { x: 0, y: 0 }; var t = 0.5; var pt = curve.get(0.5); var split = curve.split(t); api.drawCurve(split.left); api.drawCurve(split.right); api.setColor("red"); api.drawCircle(pt, 3); api.setColor("black"); offset.x = api.getPanelWidth(); api.drawLine({ x: 0, y: 0 }, { x: 0, y: api.getPanelHeight() }, offset); api.setColor("lightgrey"); api.drawCurve(curve, offset); api.drawCircle(pt, 4); offset.x -= 20; offset.y -= 20; api.drawSkeleton(split.left, offset, true); api.drawCurve(split.left, offset); offset.x += 40; offset.y += 40; api.drawSkeleton(split.right, offset, true); api.drawCurve(split.right, offset); }, drawAnimated: function drawAnimated(api, curve) { api.setPanelCount(3); api.reset(); var frame = api.getFrame(); var interval = 5 * api.getPlayInterval(); var t = frame % interval / interval; var forward = frame % (2 * interval) < interval; if (forward) { t = t % 1; } else { t = 1 - t % 1; } var offset = { x: 0, y: 0 }; api.setColor("lightblue"); api.drawHull(curve, t); api.drawSkeleton(curve); api.drawCurve(curve); var pt = curve.get(t); api.drawCircle(pt, 4); api.setColor("black"); offset.x += api.getPanelWidth(); api.drawLine({ x: 0, y: 0 }, { x: 0, y: api.getPanelHeight() }, offset); var split = curve.split(t); api.setColor("lightgrey"); api.drawCurve(curve, offset); api.drawHull(curve, t, offset); api.setColor("black"); api.drawCurve(split.left, offset); api.drawPoints(split.left.points, offset); api.setFill("black"); api.text("Left side of curve split at t = " + (100 * t | 0) / 100, { x: 10 + offset.x, y: 15 + offset.y }); offset.x += api.getPanelWidth(); api.drawLine({ x: 0, y: 0 }, { x: 0, y: api.getPanelHeight() }, offset); api.setColor("lightgrey"); api.drawCurve(curve, offset); api.drawHull(curve, t, offset); api.setColor("black"); api.drawCurve(split.right, offset); api.drawPoints(split.right.points, offset); api.setFill("black"); api.text("Right side of curve split at t = " + (100 * t | 0) / 100, { x: 10 + offset.x, y: 15 + offset.y }); }, togglePlay: function togglePlay(evt, api) { if (api.playing) { api.pause(); } else { api.play(); } } }; /***/ }), /* 89 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(88); var generateBase = __webpack_require__(0); module.exports = generateBase("splitting", handler); /***/ }), /* 90 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "steps", values: { "38": 1, // up arrow "40": -1 // down arrow }, controller: function controller(api) { if (api.steps < 1) { api.steps = 1; } } } }, setupQuadratic: function setupQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); api.steps = 3; }, setupCubic: function setupCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); api.steps = 5; }, drawFlattened: function drawFlattened(api, curve) { api.reset(); api.setColor("#DDD"); api.drawSkeleton(curve); api.setColor("#DDD"); api.drawCurve(curve); var step = 1 / api.steps; var p0 = curve.points[0], pc; for (var t = step; t < 1.0 + step; t += step) { pc = curve.get(Math.min(t, 1)); api.setColor("red"); api.drawLine(p0, pc); p0 = pc; } api.setFill("black"); api.text("Curve approximation using " + api.steps + " segments", { x: 10, y: 15 }); }, values: { "38": 1, // up arrow "40": -1 // down arrow }, onKeyDown: function onKeyDown(e, api) { var v = this.values[e.keyCode]; if (v) { e.preventDefault(); api.steps += v; if (api.steps < 1) { api.steps = 1; } } } }; /***/ }), /* 91 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(90); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("flattening", handler)); /***/ }), /* 92 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setup: function setup(api) { var points = [{ x: 90, y: 110 }, { x: 25, y: 40 }, { x: 230, y: 40 }, { x: 150, y: 240 }]; api.setCurve(new api.Bezier(points)); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); if (api.hover) { api.setColor("rgb(200,100,100)"); var dim = api.getPanelWidth(); var t = api.hover.x / dim; var hull = api.drawHull(curve, t); for (var i = 4; i <= 8; i++) { api.drawCircle(hull[i], 3); } var p = curve.get(t); api.drawCircle(p, 5); api.setFill("black"); api.drawCircle(p, 3); var perc = t * 100 | 0; t = perc / 100; api.text("Sequential interpolation for " + perc + "% (t=" + t + ")", { x: 10, y: 15 }); } } }; /***/ }), /* 93 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(92); var generateBase = __webpack_require__(0); module.exports = generateBase("decasteljau", handler); /***/ }), /* 94 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var generateBase = __webpack_require__(0); module.exports = generateBase("matrix"); /***/ }), /* 95 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setupQuadratic: function setupQuadratic(api) { var curve = new api.Bezier(70, 155, 20, 110, 100, 75); api.setCurve(curve); }, setupCubic: function setupCubic(api) { var curve = new api.Bezier(60, 105, 75, 30, 215, 115, 140, 160); api.setCurve(curve); }, draw: function draw(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); api.setColor("lightgrey"); var t, step = 0.05, min = -10; var pt = curve.get(min - step), pn; for (t = min; t <= step; t += step) { pn = curve.get(t); api.drawLine(pt, pn); pt = pn; } pt = curve.get(1); var max = 10; for (t = 1 + step; t <= max; t += step) { pn = curve.get(t); api.drawLine(pt, pn); pt = pn; } } }; /***/ }), /* 96 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(95); var generateBase = __webpack_require__(0); module.exports = generateBase("extended", handler); /***/ }), /* 97 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var ratios; module.exports = { drawCubic: function drawCubic(api) { var curve = new api.Bezier(120, 160, 35, 200, 220, 260, 220, 40); api.setCurve(curve); }, drawCurve: function drawCurve(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); }, setRatio: function setRatio(api, values) { ratios = values; this.update(api); }, changeRatio: function changeRatio(api, value, pos) { ratios[pos] = parseFloat(value) || 0.00001; this.update(api); }, update: function update(api) { api.curve.setRatios(ratios.slice()); this.drawCurve(api, api.curve); }, // ====== drawConic: function drawConic(api) { api.setSize(1.25 * api.getPanelWidth(), api.getPanelHeight()); var vectorOffset = { x: 2.5 / 5 * api.getPanelWidth(), y: 4 / 5 * api.getPanelHeight() }; var prj = this.prj = function (p) { return api.project(p, vectorOffset); }; var r = this.r = 60; var h = this.h = 160; this.cone = []; this.density = 50; for (var i = 0, e = this.density; i <= e; i++) { var cx = r * Math.cos(Math.PI * 2 * i / e); var cy = r * Math.sin(Math.PI * 2 * i / e); this.cone.push({ x: cx, y: cy, z: 0 }, { x: 0, y: 0, z: h }); } this.cone = this.cone.map(function (p) { return prj(p); }); }, drawCone: function drawCone(api) { api.reset(); var i, c = this.cone; var center = this.prj({ x: 0, y: 0, z: 0 }); api.setColor("blue"); api.drawLine(center, this.prj({ x: 400, y: 0, z: 0 })); api.drawLine(center, this.prj({ x: 0, y: 400, z: 0 })); api.drawLine(center, this.prj({ x: 0, y: 0, z: 400 })); api.setColor("red"); api.drawLine(center, this.prj({ x: -400, y: 0, z: 0 })); api.drawLine(center, this.prj({ x: 0, y: -400, z: 0 })); api.drawLine(center, this.prj({ x: 0, y: 0, z: -400 })); // cone api.setColor("rgba(200,100,200,0.6)"); for (i = 0; i < c.length - 2; i++) { api.drawLine(c[i], c[i + 1]); api.drawLine(c[i + 1], c[i + 2]); api.drawLine(c[i], c[i + 2]); } var slice = 4 * (api.mx / api.getPanelWidth()) - 2; this.drawParabola(api, slice * this.r); }, drawParabola: function drawParabola(api, oy) { var x = this.r * Math.sin(Math.acos(oy / this.r)); var y = this.r * (this.r - oy) / this.r; var h = this.h * (this.r - oy) / this.r; // plane api.setColor("green"); api.drawLine(this.prj({ x: -this.r, y: oy, z: 0 }), this.prj({ x: this.r, y: oy, z: 0 })); api.drawLine(this.prj({ x: this.r, y: oy, z: 0 }), this.prj({ x: this.r, y: oy + this.r, z: this.h })); api.drawLine(this.prj({ x: this.r, y: oy + this.r, z: this.h }), this.prj({ x: -this.r, y: oy + this.r, z: this.h })); api.drawLine(this.prj({ x: -this.r, y: oy + this.r, z: this.h }), this.prj({ x: -this.r, y: oy, z: 0 })); // conic angle api.drawLine(this.prj({ x: 0, y: -2 * this.r, z: -this.h }), this.prj({ x: 0, y: this.r, z: 2 * this.h })); var coords = [{ x: x, y: oy, z: 0 }, { x: 0, y: oy + y, z: h }, { x: -x, y: oy, z: 0 }]; // parabola var quad = new api.Bezier(coords[0], coords[1], coords[2]); api.setColor("black"); var lut = quad.getLUT(21).map(this.prj); for (var i = 0; i < lut.length - 1; i++) { api.drawLine(lut[i], lut[i + 1]); } var ry = Math.sqrt(coords[1].y * coords[1].y + coords[1].z * coords[1].z); var mp = { x: coords[1].x, y: ry, z: 0 }; var quad2 = new api.Bezier({ x: coords[0].x, y: coords[0].y, z: 0 }, { x: coords[1].x, y: coords[1].y, z: 0 }, { x: coords[2].x, y: coords[2].y, z: 0 }); quad2.setRatios([1, 1, 1]); api.setColor("purple"); var lut2 = quad2.getLUT(21).map(this.prj); for (var j = 0; j < lut2.length - 1; j++) { api.drawLine(lut2[j], lut2[j + 1]); } } }; /***/ }), /* 98 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(97); var generateBase = __webpack_require__(0); module.exports = generateBase("weightcontrol", handler); /***/ }), /* 99 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { drawCubic: function drawCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, drawCurve: function drawCurve(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); }, drawFunction: function drawFunction(api, label, where, generator) { api.setRandomColor(); api.drawFunction(generator); api.setFill(api.getColor()); if (label) api.text(label, where); }, drawLerpBox: function drawLerpBox(api, dim, pad, p) { api.noColor(); api.setFill("rgba(0,0,100,0.2)"); var p1 = { x: p.x - 5, y: pad }, p2 = { x: p.x + 5, y: dim }; api.drawRect(p1, p2); api.setColor("black"); }, drawLerpPoint: function drawLerpPoint(api, tf, pad, fwh, p) { p.y = pad + tf * fwh; api.drawCircle(p, 3); api.setFill("black"); api.text((tf * 10000 | 0) / 100 + "%", { x: p.x + 10, y: p.y + 4 }); api.noFill(); }, drawQuadraticLerp: function drawQuadraticLerp(api) { api.reset(); var dim = api.getPanelWidth(), pad = 20, fwh = dim - pad * 2; api.drawAxes(pad, "t", 0, 1, "S", "0%", "100%"); var p = api.hover; if (p && p.x >= pad && p.x <= dim - pad) { this.drawLerpBox(api, dim, pad, p); var t = (p.x - pad) / fwh; this.drawLerpPoint(api, (1 - t) * (1 - t), pad, fwh, p); this.drawLerpPoint(api, 2 * (1 - t) * t, pad, fwh, p); this.drawLerpPoint(api, t * t, pad, fwh, p); } this.drawFunction(api, "first term", { x: pad * 2, y: fwh }, function (t) { return { x: pad + t * fwh, y: pad + fwh * (1 - t) * (1 - t) }; }); this.drawFunction(api, "second term", { x: dim / 2 - 1.5 * pad, y: dim / 2 + pad }, function (t) { return { x: pad + t * fwh, y: pad + fwh * 2 * (1 - t) * t }; }); this.drawFunction(api, "third term", { x: fwh - pad * 2.5, y: fwh }, function (t) { return { x: pad + t * fwh, y: pad + fwh * t * t }; }); }, drawCubicLerp: function drawCubicLerp(api) { api.reset(); var dim = api.getPanelWidth(), pad = 20, fwh = dim - pad * 2; api.drawAxes(pad, "t", 0, 1, "S", "0%", "100%"); var p = api.hover; if (p && p.x >= pad && p.x <= dim - pad) { this.drawLerpBox(api, dim, pad, p); var t = (p.x - pad) / fwh; this.drawLerpPoint(api, (1 - t) * (1 - t) * (1 - t), pad, fwh, p); this.drawLerpPoint(api, 3 * (1 - t) * (1 - t) * t, pad, fwh, p); this.drawLerpPoint(api, 3 * (1 - t) * t * t, pad, fwh, p); this.drawLerpPoint(api, t * t * t, pad, fwh, p); } this.drawFunction(api, "first term", { x: pad * 2, y: fwh }, function (t) { return { x: pad + t * fwh, y: pad + fwh * (1 - t) * (1 - t) * (1 - t) }; }); this.drawFunction(api, "second term", { x: dim / 2 - 4 * pad, y: dim / 2 }, function (t) { return { x: pad + t * fwh, y: pad + fwh * 3 * (1 - t) * (1 - t) * t }; }); this.drawFunction(api, "third term", { x: dim / 2 + 2 * pad, y: dim / 2 }, function (t) { return { x: pad + t * fwh, y: pad + fwh * 3 * (1 - t) * t * t }; }); this.drawFunction(api, "fourth term", { x: fwh - pad * 2.5, y: fwh }, function (t) { return { x: pad + t * fwh, y: pad + fwh * t * t * t }; }); }, draw15thLerp: function draw15thLerp(api) { api.reset(); var dim = api.getPanelWidth(), pad = 20, fwh = dim - pad * 2; api.drawAxes(pad, "t", 0, 1, "S", "0%", "100%"); var factors = [1, 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, 105, 15, 1]; var p = api.hover, n; if (p && p.x >= pad && p.x <= dim - pad) { this.drawLerpBox(api, dim, pad, p); for (n = 0; n <= 15; n++) { var t = (p.x - pad) / fwh, tf = factors[n] * Math.pow(1 - t, 15 - n) * Math.pow(t, n); this.drawLerpPoint(api, tf, pad, fwh, p); } } for (n = 0; n <= 15; n++) { var label = false, position = false; if (n === 0) { label = "first term"; position = { x: pad + 5, y: fwh }; } if (n === 15) { label = "last term"; position = { x: dim - 3.5 * pad, y: fwh }; } this.drawFunction(api, label, position, function (t) { return { x: pad + t * fwh, y: pad + fwh * factors[n] * Math.pow(1 - t, 15 - n) * Math.pow(t, n) }; }); } } }; /***/ }), /* 100 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(99); var generateBase = __webpack_require__(0); module.exports = generateBase("control", handler); /***/ }), /* 101 */ /***/ (function(module, exports) { module.exports = function(){}; /***/ }), /* 102 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { statics: { keyHandlingOptions: { propName: "step", values: { "38": 0.1, // up arrow "40": -0.1 // down arrow }, controller: function controller(api) { if (api.step < 0.1) { api.step = 0.1; } } } }, setup: function setup(api) { api.step = 5; }, draw: function draw(api, curve) { var dim = api.getPanelWidth(), w = dim, h = dim, w2 = w / 2, h2 = h / 2, w4 = w2 / 2, h4 = h2 / 2; api.reset(); api.setColor("black"); api.drawLine({ x: 0, y: h2 }, { x: w, y: h2 }); api.drawLine({ x: w2, y: 0 }, { x: w2, y: h }); var offset = { x: w2, y: h2 }; for (var t = 0, p; t <= api.step; t += 0.1) { p = { x: w4 * Math.cos(t), y: h4 * Math.sin(t) }; api.drawPoint(p, offset); var modulo = t % 1; if (modulo < 0.05 || modulo > 0.95) { api.text("t = " + Math.round(t), { x: offset.x + 1.25 * w4 * Math.cos(t) - 10, y: offset.y + 1.25 * h4 * Math.sin(t) + 5 }); api.drawCircle(p, 2, offset); } } } }; /***/ }), /* 103 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(102); var generateBase = __webpack_require__(0); var keyHandling = __webpack_require__(3); module.exports = keyHandling(generateBase("explanation", handler)); /***/ }), /* 104 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { setup: function setup(api) { api.setPanelCount(3); var curve = api.getDefaultQuadratic(); api.setCurve(curve); api.step = 25; }, draw: function draw(api, curve) { var dim = api.getPanelWidth(), pts = curve.points, p1 = pts[0], p2 = pts[1], p3 = pts[2], p1e, p2e, m, t, i, offset = { x: 0, y: 0 }, d, v, tvp; api.reset(); api.setColor("black"); api.setFill("black"); api.drawSkeleton(curve, offset); api.text("First linear interpolation at " + api.step + "% steps", { x: 5, y: 15 }, offset); offset.x += dim; api.drawLine({ x: 0, y: 0 }, { x: 0, y: this.dim }, offset); api.drawSkeleton(curve, offset); api.text("Second interpolation at " + api.step + "% steps", { x: 5, y: 15 }, offset); offset.x += dim; api.drawLine({ x: 0, y: 0 }, { x: 0, y: this.dim }, offset); api.drawSkeleton(curve, offset); api.text("Curve points generated this way", { x: 5, y: 15 }, offset); api.setColor("lightgrey"); for (t = 1, d = 20, v, tvp; t < d; t++) { v = t / d; tvp = curve.get(v); api.drawCircle(tvp, 2, offset); } for (i = 3 * api.step; i > 0; i -= api.step) { t = i / 100; if (t > 1) continue; api.setRandomColor(); p1e = { x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y) }; p2e = { x: p2.x + t * (p3.x - p2.x), y: p2.y + t * (p3.y - p2.y) }; m = { x: p1e.x + t * (p2e.x - p1e.x), y: p1e.y + t * (p2e.y - p1e.y) }; offset = { x: 0, y: 0 }; api.drawCircle(p1e, 3, offset); api.drawCircle(p2e, 3, offset); api.setWeight(0.5); api.drawLine(p1e, p2e, offset); api.setWeight(1.5); api.drawLine(p1, p1e, offset); api.drawLine(p2, p2e, offset); api.setWeight(1); offset.x += dim; api.drawCircle(p1e, 3, offset); api.drawCircle(p2e, 3, offset); api.setWeight(0.5); api.drawLine(p1e, p2e, offset); api.setWeight(1.5); api.drawLine(p1e, m, offset); api.setWeight(1); api.drawCircle(m, 3, offset); offset.x += dim; api.drawCircle(m, 3, offset); api.text(i + "%, or t = " + api.utils.round(t, 2), { x: m.x + 10 + offset.x, y: m.y + 10 + offset.y }); } }, values: { "38": 1, // up arrow "40": -1 // down arrow }, onKeyDown: function onKeyDown(e, api) { var v = this.values[e.keyCode]; if (v) { e.preventDefault(); api.step += v; if (api.step < 1) { api.step = 1; } } } }; /***/ }), /* 105 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(104); var generateBase = __webpack_require__(0); module.exports = generateBase("whatis", handler); /***/ }), /* 106 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = { drawQuadratic: function drawQuadratic(api) { var curve = api.getDefaultQuadratic(); api.setCurve(curve); }, drawCubic: function drawCubic(api) { var curve = api.getDefaultCubic(); api.setCurve(curve); }, drawCurve: function drawCurve(api, curve) { api.reset(); api.drawSkeleton(curve); api.drawCurve(curve); } }; /***/ }), /* 107 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var handler = __webpack_require__(106); var generateBase = __webpack_require__(0); module.exports = generateBase("introduction", handler); /***/ }), /* 108 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var generateBase = __webpack_require__(0); module.exports = generateBase("preface"); /***/ }), /* 109 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var sections = __webpack_require__(7); var sectionPages = Object.keys(sections); var SectionHeader = __webpack_require__(8); var Navigation = React.createClass({ displayName: "Navigation", generateNavItem: function generateNavItem(name, entry) { var Type = sections[name]; var title = Type.defaultProps.title; var locale = SectionHeader.locale; if (typeof window !== "undefined" && window.location.toString().indexOf(locale) === -1) { locale = ''; } var fragmentid = (locale ? './' + locale + '/' : '.') + "#" + name; var link = React.createElement( "a", { href: fragmentid }, title ); var last = sectionPages.length - 1; if (entry === last) entry = null; return React.createElement( "li", { key: name, "data-number": entry }, link ); }, generateNav: function generateNav() { return React.createElement( "div", { ref: "navigation" }, React.createElement( "navigation", null, React.createElement( "ul", { className: "navigation" }, sectionPages.map(this.generateNavItem) ) ) ); }, render: function render() { return this.generateNav(); } }); module.exports = Navigation; /***/ }), /* 110 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var React = __webpack_require__(1); var SliderSet = function (_React$Component) { _inherits(SliderSet, _React$Component); function SliderSet(props) { _classCallCheck(this, SliderSet); var _this = _possibleConstructorReturn(this, (SliderSet.__proto__ || Object.getPrototypeOf(SliderSet)).call(this, props)); _this.options = props.options || []; return _this; } _createClass(SliderSet, [{ key: "render", value: function render(props) { var _this2 = this; props = props || {}; var min = props.min || 0; var max = props.max || 1; var step = props.step || (max - min) / 100; var rows = this.options; var sliders = rows.map(function (v, i) { return React.createElement( "div", null, React.createElement( "label", null, "t", React.createElement( "sub", null, i ) ), React.createElement("input", { type: "range", key: "row" + i, min: min, max: max, defaultValue: v, step: step, onChange: function onChange(e) { _this2.options[i] = e.target.value; props.onChange(i, _this2.options); } }), React.createElement( "span", null, parseFloat(v).toFixed(2) ) ); }); return React.createElement( "div", null, sliders ); } }, { key: "setOptions", value: function setOptions(options, labels) { this.options = options; this.forceUpdate(); } }]); return SliderSet; }(React.Component); module.exports = SliderSet; /***/ }), /* 111 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var WeightController = React.createClass({ displayName: 'WeightController', getInitialState: function getInitialState() { return { owner: false, weights: [], closed: false }; }, bindWeights: function bindWeights(owner, weights, closed) { this.setState({ owner: owner, weights: weights, closed: closed }); }, render: function render() { var _this = this; var type = 'range'; var min = 0; var max = this.state.weights.length; var step = 1; var overlap = this.state.closed; var baselength = this.state.weights.length; if (overlap !== false) { baselength -= overlap; } return React.createElement( 'section', { className: 'knot-controls' }, React.createElement( 'h2', null, 'weight values' ), this.state.weights.map(function (value, position) { if (overlap && position >= baselength) { return null; } var props = { type: type, min: min, max: max, step: step, value: value, onChange: function onChange(e) { var k = _this.state.weights; k[position] = e.target.value; if (overlap && position < overlap) { k[position + baselength] = e.target.value; } _this.setState({ weights: k }, function () { _this.state.owner.redraw(); }); } }; return React.createElement( 'div', { key: 'knot' + position }, min, React.createElement('input', props), max, ' (= ', value, ')' ); }) ); } }); module.exports = WeightController; /***/ }), /* 112 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var React = __webpack_require__(1); var KnotController = React.createClass({ displayName: 'KnotController', getInitialState: function getInitialState() { return { owner: false, knots: [] }; }, bindKnots: function bindKnots(owner, knots) { this.setState({ owner: owner, knots: knots }); }, render: function render() { var _this = this; var type = 'range'; var min = 0; var max = this.state.knots.length; var step = 1; return React.createElement( 'section', { className: 'knot-controls' }, React.createElement( 'h2', null, 'knot values' ), this.state.knots.map(function (value, position) { var props = { type: type, min: min, max: max, step: step, value: value, onChange: function onChange(e) { var k = _this.state.knots; k[position] = e.target.value; _this.setState({ knots: k }, function () { _this.state.owner.redraw(); }); } }; return React.createElement( 'div', { key: 'knot' + position }, min, React.createElement('input', props), max, ' (= ', value, ')' ); }) ); } }); module.exports = KnotController; /***/ }), /* 113 */ /***/ (function(module, exports) { // https://github.com/thibauts/b-spline module.exports = function interpolate(t, degree, points, knots, weights, result, scaled) { var i,j,s,l; // function-scoped iteration variables var n = points.length; // points count var d = points[0].length; // point dimensionality if(degree < 1) throw new Error('degree must be at least 1 (linear)'); if(degree > (n-1)) throw new Error('degree must be less than or equal to point count - 1'); if(!weights) { // build weight vector of length [n] weights = []; for(i=0; i high) throw new Error('out of bounds'); // find s (the spline segment) for the [t] value provided for(s=domain[0]; s= knots[s] && t <= knots[s+1]) { break; } } // convert points to homogeneous coordinates var v = []; for(i=0; is-degree-1+l; i--) { alpha = (t - knots[i]) / (knots[i+degree+1-l] - knots[i]); // interpolate each component for(j=0; j 0 ? this.weights : false; ctx.beginPath(); var p = interpolate(0, this.degree, points, this.knots, weights); ctx.moveTo(p[0], p[1]); for (var t = 0.01; t < 1; t += 0.01) { p = interpolate(t, this.degree, points, this.knots, weights); ctx.lineTo(p[0], p[1]); } p = interpolate(1, this.degree, points, this.knots, weights); ctx.lineTo(p[0], p[1]); ctx.stroke(); ctx.closePath(); }, drawKnots: function drawKnots(points) { var _this2 = this; var knots = this.knots; var weights = this.weights.length > 0 ? this.weights : false; knots.forEach(function (knot, i) { if (i < _this2.degree) return; if (i > knots.length - 1 - _this2.degree) return; var p = interpolate(knot, _this2.degree, points, knots, weights, false, true); _this2.circle(p[0], p[1], 3); }); }, // FIXME: TODO: these do not seem correct using uniform knots drawNodes: function drawNodes(points) { var _this3 = this; var i = 0, p; this.stroke(150); this.nodes.forEach(function (node, i) { try { p = interpolate(node, _this3.degree, points, _this3.knots, false, false, true); _this3.line(p[0], p[1], points[i][0], points[i++][1]); } catch (e) { console.error(e); } }); }, // FIXME: this doesn't work with a degree change formKnots: function formKnots(points, open) { open = open === true ? true : false; if (!open) return this.formUniformKnots(points); var l = points.length, knots = [], m = l - this.degree, i; // form the open-uniform knot vector for (i = 1; i < l - this.degree; i++) { knots.push(i + this.degree); } // add [degree] zeroes at the front for (i = 0; i <= this.degree; i++) { knots = [this.degree].concat(knots); } // add [degree] max-values to the back for (i = 0; i <= this.degree; i++) { knots.push(m + this.degree); } return knots; }, formUniformKnots: function formUniformKnots(points) { var knots = []; for (var i = this.points.length + this.degree; i >= 0; i--) { knots.push(i); } return knots.reverse(); }, formNodes: function formNodes(knots, points) { var domain = [this.degree, knots.length - 1 - this.degree]; var nodes = [], node, k, offset; for (k = 0; k < this.points.length; k++) { node = 0; for (offset = 1; offset <= this.degree; offset++) { node += knots[k + offset]; } node /= this.degree; if (node < knots[domain[0]]) continue; if (node > knots[domain[1]]) continue; nodes.push(node); } return nodes; }, formWeights: function formWeights(points) { var weights = []; points.forEach(function (p) { return weights.push(1); }); return weights; }, setDegree: function setDegree(d) { this.degree += d; this.knots = this.formKnots(this.points); this.nodes = this.formNodes(this.knots, this.points); }, near: function near(p, x, y) { var dx = p.x - x, dy = p.y - y, d = Math.sqrt(dx * dx + dy * dy); return d < this.activeDistance; }, getCurrentPoint: function getCurrentPoint(x, y) { for (var i = 0; i < this.points.length; i++) { if (this.near(this.points[i], x, y)) { return this.points[i]; } } }, // Interaction stuffs keyDown: function keyDown() { if (this.key === 'ArrowUp') { this.setDegree(1); } if (this.key === 'ArrowDown') { if (this.degree > 1) { this.setDegree(-1); } } this.redraw(); }, keyUp: function keyUp() { // ... do nothing? }, keyPressed: function keyPressed() { // ... do nothing? }, mouseDown: function mouseDown() { this.isMouseDown = true; this.cp = this.getCurrentPoint(this.mouseX, this.mouseY); if (!this.cp) { this.points.push({ x: this.mouseX, y: this.mouseY }); this.knots = this.formKnots(this.points); this.nodes = this.formNodes(this.knots, this.points); } this.redraw(); }, mouseUp: function mouseUp() { this.isMouseDown = false; this.cp = false; this.redraw(); }, mouseDrag: function mouseDrag() { if (this.cp) { this.cp.x = this.mouseX; this.cp.y = this.mouseY; this.redraw(); } }, mouseMove: function mouseMove() { // ... do nothing? }, scrolled: function scrolled(direction) { this.cp = this.getCurrentPoint(this.mouseX, this.mouseY); if (!this.cp) return; // base case var pos = this.points.indexOf(this.cp); if (this.weights.length > pos) { this.weights[pos] += direction * 0.1; if (this.weights[pos] < 0) { this.weights[pos] = 0; } } // possible multiplicity due to "closed" curves pos = this.points.indexOf(this.cp, pos + 1); if (pos !== -1 && this.weights.length > pos) { this.weights[pos] += direction * 0.1; if (this.weights[pos] < 0) { this.weights[pos] = 0; } } this.redraw(); }, // keyboard events setKeyboardValues: function setKeyboardValues(e) { if (!e.ctrlKey && !e.metaKey && !e.altKey) { e.preventDefault(); } this.key = e.key; this.keyCode = e.code; }, // mouse events setMouseValues: function setMouseValues(e) { var brect = this.cvs.getBoundingClientRect(); this.mouseX = e.clientX - brect.left; this.mouseY = e.clientY - brect.top; }, // API stuffs size: function size(w, h) { this.width = w | 0; this.height = (h || w) | 0; this.cvs.width = this.width; this.cvs.height = this.height; this.cvs.style.width = this.width + "px"; this.cvs.style.height = this.height + "px"; this.ctx = this.cvs.getContext("2d"); }, redraw: function redraw() { this.draw(); }, clear: function clear() { this.ctx.clearRect(0, 0, this.width, this.height); }, grid: function grid(spacing) { spacing = ((spacing || 10) | 0) + 0.5; this.stroke(200, 200, 220); for (var x = spacing; x < this.width - 1; x += spacing) { this.line(x, 0, x, this.height); } for (var y = spacing; y < this.height - 1; y += spacing) { this.line(0, y, this.width, y); } }, circle: function circle(x, y, r) { var hr = r / 2; var ctx = this.ctx; ctx.beginPath(); ctx.moveTo(x, y); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.stroke(); ctx.closePath(); }, line: function line(a, b, c, d) { var ctx = this.ctx; ctx.beginPath(); ctx.moveTo(a, b); ctx.lineTo(c, d); ctx.stroke(); ctx.closePath(); }, stroke: function stroke(r, g, b, a) { if (typeof r === "string") { return this.ctx.strokeStyle = r; } if (g === undefined) { g = r;b = r; } if (a === undefined) { a = 1; } this.ctx.strokeStyle = this.rgba(r, g, b, a); }, noStroke: function noStroke() { this.ctx.strokeStyle = "none"; }, fill: function fill(r, g, b, a) { if (typeof r === "string") { return this.ctx.fillStyle = r; } if (g === undefined) { g = r;b = r; } if (a === undefined) { a = 1; } this.ctx.fillStyle = this.rgba(r, g, b, a); }, noFill: function noFill() { this.ctx.fillStyle = "none"; }, rgba: function rgba(r, g, b, a) { return "rgba(" + r + "," + g + "," + b + "," + a + ")"; }, beginPath: function beginPath() { this.ctx.beginPath();this.bp = true; }, vertex: function vertex(x, y) { if (!this.bp) { return this.ctx.lineTo(x, y); } this.ctx.moveTo(x, y); this.bp = false; }, endPath: function endPath() { this.ctx.stroke(); this.ctx.closePath(); } }); module.exports = BSplineGraphic; /***/ }), /* 115 */ /***/ (function(module, exports) { function scrollToHash() { window.location = window.location.hash; } module.exports = function() { if (typeof window === "undefined") return; if (window.location) { var h = window.location.hash; if (h) { // is this a comment hash, or a section hash? if (h.match(/comment-\d+/)) { var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; div = document.getElementById("disqus_thread"); var mObject = new MutationObserver(function(changes, observer) { var limit = 10; (function waitForCommentToLoad() { if (document.querySelector(h)) { return scrollToHash(); } if (limit-- < 0) return; setTimeout(waitForCommentToLoad, 200); })(); mObject.disconnect(); }); mObject.observe(div, { childList: true, subtree: true }); // kick off disqus loading comments = document.getElementById("comments"); comments.scrollIntoView(); } else { scrollToHash(); } } } }; /***/ }), /* 116 */ /***/ (function(module, exports) { /** * Normalise an SVG path to absolute coordinates * and full commands, rather than relative coordinates * and/or shortcut commands. */ function normalizePath(d) { // preprocess "d" so that we have spaces between values d = d .replace(/,/g, " ") // replace commas with spaces .replace(/-/g, " - ") // add spacing around minus signs .replace(/-\s+/g, "-") // remove spacing to the right of minus signs. .replace(/([a-zA-Z])/g, " $1 "); // set up the variables used in this function var instructions = d.replace(/([a-zA-Z])\s?/g, "|$1").split("|"), instructionLength = instructions.length, i, instruction, op, lop, args = [], alen, a, sx = 0, sy = 0, x = 0, y = 0, cx = 0, cy = 0, cx2 = 0, cy2 = 0, normalized = ""; // we run through the instruction list starting at 1, not 0, // because we split up "|M x y ...." so the first element will // always be an empty string. By design. for (i = 1; i < instructionLength; i++) { // which instruction is this? instruction = instructions[i]; op = instruction.substring(0, 1); lop = op.toLowerCase(); // what are the arguments? note that we need to convert // all strings into numbers, or + will do silly things. args = instruction .replace(op, "") .trim() .split(" "); args = args .filter(function(v) { return v !== ""; }) .map(parseFloat); alen = args.length; // we could use a switch, but elaborate code in a "case" with // fallthrough is just horrid to read. So let's use ifthen // statements instead. // moveto command (plus possible lineto) if (lop === "m") { normalized += "M "; if (op === "m") { x += args[0]; y += args[1]; } else { x = args[0]; y = args[1]; } // records start position, for dealing // with the shape close operator ('Z') sx = x; sy = y; normalized += x + " " + y + " "; if (alen > 2) { for (a = 0; a < alen; a += 2) { if (op === "m") { x += args[a]; y += args[a + 1]; } else { x = args[a]; y = args[a + 1]; } normalized += ["L",x,y,''].join(" "); } } } else if (lop === "l") { // lineto commands for (a = 0; a < alen; a += 2) { if (op === "l") { x += args[a]; y += args[a + 1]; } else { x = args[a]; y = args[a + 1]; } normalized += ["L",x,y,''].join(" "); } } else if (lop === "h") { for (a = 0; a < alen; a++) { if (op === "h") { x += args[a]; } else { x = args[a]; } normalized += ["L",x,y,''].join(" "); } } else if (lop === "v") { for (a = 0; a < alen; a++) { if (op === "v") { y += args[a]; } else { y = args[a]; } normalized += ["L",x,y,''].join(" "); } } else if (lop === "q") { // quadratic curveto commands for (a = 0; a < alen; a += 4) { if (op === "q") { cx = x + args[a]; cy = y + args[a + 1]; x += args[a + 2]; y += args[a + 3]; } else { cx = args[a]; cy = args[a + 1]; x = args[a + 2]; y = args[a + 3]; } normalized += ["Q",cx,cy,x,y,''].join(" "); } } else if (lop === "t") { for (a = 0; a < alen; a += 2) { // reflect previous cx/cy over x/y cx = x + (x - cx); cy = y + (y - cy); // then get real end point if (op === "t") { x += args[a]; y += args[a + 1]; } else { x = args[a]; y = args[a + 1]; } normalized += ["Q",cx,cy,x,y,''].join(" "); } } else if (lop === "c") { // cubic curveto commands for (a = 0; a < alen; a += 6) { if (op === "c") { cx = x + args[a]; cy = y + args[a + 1]; cx2 = x + args[a + 2]; cy2 = y + args[a + 3]; x += args[a + 4]; y += args[a + 5]; } else { cx = args[a]; cy = args[a + 1]; cx2 = args[a + 2]; cy2 = args[a + 3]; x = args[a + 4]; y = args[a + 5]; } normalized += ["C",cx,cy,cx2,cy2,x,y,''].join(" "); } } else if (lop === "s") { for (a = 0; a < alen; a += 4) { // reflect previous cx2/cy2 over x/y cx = x + (x - cx2); cy = y + (y - cy2); // then get real control and end point if (op === "s") { cx2 = x + args[a]; cy2 = y + args[a + 1]; x += args[a + 2]; y += args[a + 3]; } else { cx2 = args[a]; cy2 = args[a + 1]; x = args[a + 2]; y = args[a + 3]; } normalized +=["C",cx,cy,cx2,cy2,x,y,''].join(" "); } } else if (lop === "z") { normalized += "Z "; // not unimportant: path closing changes the current x/y coordinate x = sx; y = sy; } } return normalized.trim(); } module.exports = normalizePath; /***/ }), /* 117 */ /***/ (function(module, exports, __webpack_require__) { var normalise = __webpack_require__(116); var M = { x: false, y: false }; function makeBezier(Bezier, term, values) { if (term === 'Z') return; if (term === 'M') { M = {x: values[0], y: values[1]}; return; } // ES7: new Bezier(M.x, M.y, ...values) var cvalues = [false, M.x, M.y].concat(values); var PreboundConstructor = Bezier.bind.apply(Bezier, cvalues) var curve = new PreboundConstructor(); var last = values.slice(-2); M = { x : last[0], y: last[1] }; return curve; } function convertPath(Bezier, d) { var terms = normalise(d).split(" "), term, matcher = new RegExp("[MLCQZ]", ""), segment, values, segments = [], ARGS = { "C": 6, "Q": 4, "L": 2, "M": 2}; while (terms.length) { term = terms.splice(0,1)[0]; if (matcher.test(term)) { values = terms.splice(0, ARGS[term]).map(parseFloat); segment = makeBezier(Bezier, term, values); if (segment) segments.push(segment); } } return new Bezier.PolyBezier(segments); } module.exports = convertPath; /***/ }), /* 118 */ /***/ (function(module, exports, __webpack_require__) { (function() { "use strict"; var utils = __webpack_require__(9); /** * Poly Bezier * @param {[type]} curves [description] */ var PolyBezier = function(curves) { this.curves = []; this._3d = false; if (!!curves) { this.curves = curves; this._3d = this.curves[0]._3d; } }; PolyBezier.prototype = { valueOf: function() { return this.toString(); }, toString: function() { return ( "[" + this.curves .map(function(curve) { return utils.pointsToString(curve.points); }) .join(", ") + "]" ); }, addCurve: function(curve) { this.curves.push(curve); this._3d = this._3d || curve._3d; }, length: function() { return this.curves .map(function(v) { return v.length(); }) .reduce(function(a, b) { return a + b; }); }, curve: function(idx) { return this.curves[idx]; }, bbox: function() { var c = this.curves; var bbox = c[0].bbox(); for (var i = 1; i < c.length; i++) { utils.expandbox(bbox, c[i].bbox()); } return bbox; }, offset: function(d) { var offset = []; this.curves.forEach(function(v) { offset = offset.concat(v.offset(d)); }); return new PolyBezier(offset); } }; module.exports = PolyBezier; })(); /***/ }), /* 119 */ /***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(10); /***/ }), /* 120 */ /***/ (function(module, exports) { module.exports = function(module) { if (!module.webpackPolyfill) { module.deprecate = function() {}; module.paths = []; // module.parent = undefined by default if (!module.children) module.children = []; Object.defineProperty(module, "loaded", { enumerable: true, get: function() { return module.l; } }); Object.defineProperty(module, "id", { enumerable: true, get: function() { return module.i; } }); module.webpackPolyfill = 1; } return module; }; /***/ }), /* 121 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__; /** * @license * * chroma.js - JavaScript library for color conversions * * Copyright (c) 2011-2017, Gregor Aisch * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The name Gregor Aisch may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ (function() { var Color, DEG2RAD, LAB_CONSTANTS, PI, PITHIRD, RAD2DEG, TWOPI, _average_lrgb, _guess_formats, _guess_formats_sorted, _input, _interpolators, abs, atan2, bezier, blend, blend_f, brewer, burn, chroma, clip_rgb, cmyk2rgb, colors, cos, css2rgb, darken, dodge, each, floor, hcg2rgb, hex2rgb, hsi2rgb, hsl2css, hsl2rgb, hsv2rgb, interpolate, interpolate_hsx, interpolate_lab, interpolate_lrgb, interpolate_num, interpolate_rgb, lab2lch, lab2rgb, lab_xyz, lch2lab, lch2rgb, lighten, limit, log, luminance_x, m, max, multiply, normal, num2rgb, overlay, pow, rgb2cmyk, rgb2css, rgb2hcg, rgb2hex, rgb2hsi, rgb2hsl, rgb2hsv, rgb2lab, rgb2lch, rgb2luminance, rgb2num, rgb2temperature, rgb2xyz, rgb_xyz, rnd, root, round, screen, sin, sqrt, temperature2rgb, type, unpack, w3cx11, xyz_lab, xyz_rgb, slice = [].slice; type = (function() { /* for browser-safe type checking+ ported from jQuery's $.type */ var classToType, len, name, o, ref; classToType = {}; ref = "Boolean Number String Function Array Date RegExp Undefined Null".split(" "); for (o = 0, len = ref.length; o < len; o++) { name = ref[o]; classToType["[object " + name + "]"] = name.toLowerCase(); } return function(obj) { var strType; strType = Object.prototype.toString.call(obj); return classToType[strType] || "object"; }; })(); limit = function(x, min, max) { if (min == null) { min = 0; } if (max == null) { max = 1; } if (x < min) { x = min; } if (x > max) { x = max; } return x; }; unpack = function(args) { if (args.length >= 3) { return Array.prototype.slice.call(args); } else { return args[0]; } }; clip_rgb = function(rgb) { var i, o; rgb._clipped = false; rgb._unclipped = rgb.slice(0); for (i = o = 0; o < 3; i = ++o) { if (i < 3) { if (rgb[i] < 0 || rgb[i] > 255) { rgb._clipped = true; } if (rgb[i] < 0) { rgb[i] = 0; } if (rgb[i] > 255) { rgb[i] = 255; } } else if (i === 3) { if (rgb[i] < 0) { rgb[i] = 0; } if (rgb[i] > 1) { rgb[i] = 1; } } } if (!rgb._clipped) { delete rgb._unclipped; } return rgb; }; PI = Math.PI, round = Math.round, cos = Math.cos, floor = Math.floor, pow = Math.pow, log = Math.log, sin = Math.sin, sqrt = Math.sqrt, atan2 = Math.atan2, max = Math.max, abs = Math.abs; TWOPI = PI * 2; PITHIRD = PI / 3; DEG2RAD = PI / 180; RAD2DEG = 180 / PI; chroma = function() { if (arguments[0] instanceof Color) { return arguments[0]; } return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, arguments, function(){}); }; chroma["default"] = chroma; _interpolators = []; if ((typeof module !== "undefined" && module !== null) && (module.exports != null)) { module.exports = chroma; } if (true) { !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = (function() { return chroma; }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else {} chroma.version = '1.3.7'; _input = {}; _guess_formats = []; _guess_formats_sorted = false; Color = (function() { function Color() { var arg, args, chk, len, len1, me, mode, o, w; me = this; args = []; for (o = 0, len = arguments.length; o < len; o++) { arg = arguments[o]; if (arg != null) { args.push(arg); } } if (args.length > 1) { mode = args[args.length - 1]; } if (_input[mode] != null) { me._rgb = clip_rgb(_input[mode](unpack(args.slice(0, -1)))); } else { if (!_guess_formats_sorted) { _guess_formats = _guess_formats.sort(function(a, b) { return b.p - a.p; }); _guess_formats_sorted = true; } for (w = 0, len1 = _guess_formats.length; w < len1; w++) { chk = _guess_formats[w]; mode = chk.test.apply(chk, args); if (mode) { break; } } if (mode) { me._rgb = clip_rgb(_input[mode].apply(_input, args)); } } if (me._rgb == null) { console.warn('unknown format: ' + args); } if (me._rgb == null) { me._rgb = [0, 0, 0]; } if (me._rgb.length === 3) { me._rgb.push(1); } } Color.prototype.toString = function() { return this.hex(); }; Color.prototype.clone = function() { return chroma(me._rgb); }; return Color; })(); chroma._input = _input; /** ColorBrewer colors for chroma.js Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. 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. @preserve */ chroma.brewer = brewer = { OrRd: ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#ef6548', '#d7301f', '#b30000', '#7f0000'], PuBu: ['#fff7fb', '#ece7f2', '#d0d1e6', '#a6bddb', '#74a9cf', '#3690c0', '#0570b0', '#045a8d', '#023858'], BuPu: ['#f7fcfd', '#e0ecf4', '#bfd3e6', '#9ebcda', '#8c96c6', '#8c6bb1', '#88419d', '#810f7c', '#4d004b'], Oranges: ['#fff5eb', '#fee6ce', '#fdd0a2', '#fdae6b', '#fd8d3c', '#f16913', '#d94801', '#a63603', '#7f2704'], BuGn: ['#f7fcfd', '#e5f5f9', '#ccece6', '#99d8c9', '#66c2a4', '#41ae76', '#238b45', '#006d2c', '#00441b'], YlOrBr: ['#ffffe5', '#fff7bc', '#fee391', '#fec44f', '#fe9929', '#ec7014', '#cc4c02', '#993404', '#662506'], YlGn: ['#ffffe5', '#f7fcb9', '#d9f0a3', '#addd8e', '#78c679', '#41ab5d', '#238443', '#006837', '#004529'], Reds: ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#a50f15', '#67000d'], RdPu: ['#fff7f3', '#fde0dd', '#fcc5c0', '#fa9fb5', '#f768a1', '#dd3497', '#ae017e', '#7a0177', '#49006a'], Greens: ['#f7fcf5', '#e5f5e0', '#c7e9c0', '#a1d99b', '#74c476', '#41ab5d', '#238b45', '#006d2c', '#00441b'], YlGnBu: ['#ffffd9', '#edf8b1', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8', '#253494', '#081d58'], Purples: ['#fcfbfd', '#efedf5', '#dadaeb', '#bcbddc', '#9e9ac8', '#807dba', '#6a51a3', '#54278f', '#3f007d'], GnBu: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#2b8cbe', '#0868ac', '#084081'], Greys: ['#ffffff', '#f0f0f0', '#d9d9d9', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'], YlOrRd: ['#ffffcc', '#ffeda0', '#fed976', '#feb24c', '#fd8d3c', '#fc4e2a', '#e31a1c', '#bd0026', '#800026'], PuRd: ['#f7f4f9', '#e7e1ef', '#d4b9da', '#c994c7', '#df65b0', '#e7298a', '#ce1256', '#980043', '#67001f'], Blues: ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c', '#08306b'], PuBuGn: ['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636'], Viridis: ['#440154', '#482777', '#3f4a8a', '#31678e', '#26838f', '#1f9d8a', '#6cce5a', '#b6de2b', '#fee825'], Spectral: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'], RdYlGn: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850', '#006837'], RdBu: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de', '#4393c3', '#2166ac', '#053061'], PiYG: ['#8e0152', '#c51b7d', '#de77ae', '#f1b6da', '#fde0ef', '#f7f7f7', '#e6f5d0', '#b8e186', '#7fbc41', '#4d9221', '#276419'], PRGn: ['#40004b', '#762a83', '#9970ab', '#c2a5cf', '#e7d4e8', '#f7f7f7', '#d9f0d3', '#a6dba0', '#5aae61', '#1b7837', '#00441b'], RdYlBu: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee090', '#ffffbf', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'], BrBG: ['#543005', '#8c510a', '#bf812d', '#dfc27d', '#f6e8c3', '#f5f5f5', '#c7eae5', '#80cdc1', '#35978f', '#01665e', '#003c30'], RdGy: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#ffffff', '#e0e0e0', '#bababa', '#878787', '#4d4d4d', '#1a1a1a'], PuOr: ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'], Set2: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'], Accent: ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'], Set1: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf', '#999999'], Set3: ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'], Dark2: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666'], Paired: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', '#ffff99', '#b15928'], Pastel2: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae', '#f1e2cc', '#cccccc'], Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2'] }; (function() { var key, results; results = []; for (key in brewer) { results.push(brewer[key.toLowerCase()] = brewer[key]); } return results; })(); /** X11 color names http://www.w3.org/TR/css3-color/#svg-color */ w3cx11 = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflower: '#6495ed', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkgrey: '#a9a9a9', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', green: '#008000', greenyellow: '#adff2f', grey: '#808080', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', laserlemon: '#ffff54', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrod: '#fafad2', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', maroon2: '#7f0000', maroon3: '#b03060', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', purple2: '#7f007f', purple3: '#a020f0', rebeccapurple: '#663399', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32' }; chroma.colors = colors = w3cx11; lab2rgb = function() { var a, args, b, g, l, r, x, y, z; args = unpack(arguments); l = args[0], a = args[1], b = args[2]; y = (l + 16) / 116; x = isNaN(a) ? y : y + a / 500; z = isNaN(b) ? y : y - b / 200; y = LAB_CONSTANTS.Yn * lab_xyz(y); x = LAB_CONSTANTS.Xn * lab_xyz(x); z = LAB_CONSTANTS.Zn * lab_xyz(z); r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z); b = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z); return [r, g, b, args.length > 3 ? args[3] : 1]; }; xyz_rgb = function(r) { return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * pow(r, 1 / 2.4) - 0.055); }; lab_xyz = function(t) { if (t > LAB_CONSTANTS.t1) { return t * t * t; } else { return LAB_CONSTANTS.t2 * (t - LAB_CONSTANTS.t0); } }; LAB_CONSTANTS = { Kn: 18, Xn: 0.950470, Yn: 1, Zn: 1.088830, t0: 0.137931034, t1: 0.206896552, t2: 0.12841855, t3: 0.008856452 }; rgb2lab = function() { var b, g, r, ref, ref1, x, y, z; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; ref1 = rgb2xyz(r, g, b), x = ref1[0], y = ref1[1], z = ref1[2]; return [116 * y - 16, 500 * (x - y), 200 * (y - z)]; }; rgb_xyz = function(r) { if ((r /= 255) <= 0.04045) { return r / 12.92; } else { return pow((r + 0.055) / 1.055, 2.4); } }; xyz_lab = function(t) { if (t > LAB_CONSTANTS.t3) { return pow(t, 1 / 3); } else { return t / LAB_CONSTANTS.t2 + LAB_CONSTANTS.t0; } }; rgb2xyz = function() { var b, g, r, ref, x, y, z; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; r = rgb_xyz(r); g = rgb_xyz(g); b = rgb_xyz(b); x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LAB_CONSTANTS.Xn); y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LAB_CONSTANTS.Yn); z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LAB_CONSTANTS.Zn); return [x, y, z]; }; chroma.lab = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['lab']), function(){}); }; _input.lab = lab2rgb; Color.prototype.lab = function() { return rgb2lab(this._rgb); }; bezier = function(colors) { var I, I0, I1, c, lab0, lab1, lab2, lab3, ref, ref1, ref2; colors = (function() { var len, o, results; results = []; for (o = 0, len = colors.length; o < len; o++) { c = colors[o]; results.push(chroma(c)); } return results; })(); if (colors.length === 2) { ref = (function() { var len, o, results; results = []; for (o = 0, len = colors.length; o < len; o++) { c = colors[o]; results.push(c.lab()); } return results; })(), lab0 = ref[0], lab1 = ref[1]; I = function(t) { var i, lab; lab = (function() { var o, results; results = []; for (i = o = 0; o <= 2; i = ++o) { results.push(lab0[i] + t * (lab1[i] - lab0[i])); } return results; })(); return chroma.lab.apply(chroma, lab); }; } else if (colors.length === 3) { ref1 = (function() { var len, o, results; results = []; for (o = 0, len = colors.length; o < len; o++) { c = colors[o]; results.push(c.lab()); } return results; })(), lab0 = ref1[0], lab1 = ref1[1], lab2 = ref1[2]; I = function(t) { var i, lab; lab = (function() { var o, results; results = []; for (i = o = 0; o <= 2; i = ++o) { results.push((1 - t) * (1 - t) * lab0[i] + 2 * (1 - t) * t * lab1[i] + t * t * lab2[i]); } return results; })(); return chroma.lab.apply(chroma, lab); }; } else if (colors.length === 4) { ref2 = (function() { var len, o, results; results = []; for (o = 0, len = colors.length; o < len; o++) { c = colors[o]; results.push(c.lab()); } return results; })(), lab0 = ref2[0], lab1 = ref2[1], lab2 = ref2[2], lab3 = ref2[3]; I = function(t) { var i, lab; lab = (function() { var o, results; results = []; for (i = o = 0; o <= 2; i = ++o) { results.push((1 - t) * (1 - t) * (1 - t) * lab0[i] + 3 * (1 - t) * (1 - t) * t * lab1[i] + 3 * (1 - t) * t * t * lab2[i] + t * t * t * lab3[i]); } return results; })(); return chroma.lab.apply(chroma, lab); }; } else if (colors.length === 5) { I0 = bezier(colors.slice(0, 3)); I1 = bezier(colors.slice(2, 5)); I = function(t) { if (t < 0.5) { return I0(t * 2); } else { return I1((t - 0.5) * 2); } }; } return I; }; chroma.bezier = function(colors) { var f; f = bezier(colors); f.scale = function() { return chroma.scale(f); }; return f; }; /* chroma.js Copyright (c) 2011-2013, Gregor Aisch All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name Gregor Aisch may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @source: https://github.com/gka/chroma.js */ chroma.cubehelix = function(start, rotations, hue, gamma, lightness) { var dh, dl, f; if (start == null) { start = 300; } if (rotations == null) { rotations = -1.5; } if (hue == null) { hue = 1; } if (gamma == null) { gamma = 1; } if (lightness == null) { lightness = [0, 1]; } dh = 0; if (type(lightness) === 'array') { dl = lightness[1] - lightness[0]; } else { dl = 0; lightness = [lightness, lightness]; } f = function(fract) { var a, amp, b, cos_a, g, h, l, r, sin_a; a = TWOPI * ((start + 120) / 360 + rotations * fract); l = pow(lightness[0] + dl * fract, gamma); h = dh !== 0 ? hue[0] + fract * dh : hue; amp = h * l * (1 - l) / 2; cos_a = cos(a); sin_a = sin(a); r = l + amp * (-0.14861 * cos_a + 1.78277 * sin_a); g = l + amp * (-0.29227 * cos_a - 0.90649 * sin_a); b = l + amp * (+1.97294 * cos_a); return chroma(clip_rgb([r * 255, g * 255, b * 255])); }; f.start = function(s) { if (s == null) { return start; } start = s; return f; }; f.rotations = function(r) { if (r == null) { return rotations; } rotations = r; return f; }; f.gamma = function(g) { if (g == null) { return gamma; } gamma = g; return f; }; f.hue = function(h) { if (h == null) { return hue; } hue = h; if (type(hue) === 'array') { dh = hue[1] - hue[0]; if (dh === 0) { hue = hue[1]; } } else { dh = 0; } return f; }; f.lightness = function(h) { if (h == null) { return lightness; } if (type(h) === 'array') { lightness = h; dl = h[1] - h[0]; } else { lightness = [h, h]; dl = 0; } return f; }; f.scale = function() { return chroma.scale(f); }; f.hue(hue); return f; }; chroma.random = function() { var code, digits, i, o; digits = '0123456789abcdef'; code = '#'; for (i = o = 0; o < 6; i = ++o) { code += digits.charAt(floor(Math.random() * 16)); } return new Color(code); }; _interpolators = []; interpolate = function(col1, col2, f, m) { var interpol, len, o, res; if (f == null) { f = 0.5; } if (m == null) { m = 'rgb'; } /* interpolates between colors f = 0 --> me f = 1 --> col */ if (type(col1) !== 'object') { col1 = chroma(col1); } if (type(col2) !== 'object') { col2 = chroma(col2); } for (o = 0, len = _interpolators.length; o < len; o++) { interpol = _interpolators[o]; if (m === interpol[0]) { res = interpol[1](col1, col2, f, m); break; } } if (res == null) { throw "color mode " + m + " is not supported"; } return res.alpha(col1.alpha() + f * (col2.alpha() - col1.alpha())); }; chroma.interpolate = interpolate; Color.prototype.interpolate = function(col2, f, m) { return interpolate(this, col2, f, m); }; chroma.mix = interpolate; Color.prototype.mix = Color.prototype.interpolate; _input.rgb = function() { var k, ref, results, v; ref = unpack(arguments); results = []; for (k in ref) { v = ref[k]; results.push(v); } return results; }; chroma.rgb = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['rgb']), function(){}); }; Color.prototype.rgb = function(round) { if (round == null) { round = true; } if (round) { return this._rgb.map(Math.round).slice(0, 3); } else { return this._rgb.slice(0, 3); } }; Color.prototype.rgba = function(round) { if (round == null) { round = true; } if (!round) { return this._rgb.slice(0); } return [Math.round(this._rgb[0]), Math.round(this._rgb[1]), Math.round(this._rgb[2]), this._rgb[3]]; }; _guess_formats.push({ p: 3, test: function(n) { var a; a = unpack(arguments); if (type(a) === 'array' && a.length === 3) { return 'rgb'; } if (a.length === 4 && type(a[3]) === "number" && a[3] >= 0 && a[3] <= 1) { return 'rgb'; } } }); _input.lrgb = _input.rgb; interpolate_lrgb = function(col1, col2, f, m) { var xyz0, xyz1; xyz0 = col1._rgb; xyz1 = col2._rgb; return new Color(sqrt(pow(xyz0[0], 2) * (1 - f) + pow(xyz1[0], 2) * f), sqrt(pow(xyz0[1], 2) * (1 - f) + pow(xyz1[1], 2) * f), sqrt(pow(xyz0[2], 2) * (1 - f) + pow(xyz1[2], 2) * f), m); }; _average_lrgb = function(colors) { var col, f, len, o, rgb, xyz; f = 1 / colors.length; xyz = [0, 0, 0, 0]; for (o = 0, len = colors.length; o < len; o++) { col = colors[o]; rgb = col._rgb; xyz[0] += pow(rgb[0], 2) * f; xyz[1] += pow(rgb[1], 2) * f; xyz[2] += pow(rgb[2], 2) * f; xyz[3] += rgb[3] * f; } xyz[0] = sqrt(xyz[0]); xyz[1] = sqrt(xyz[1]); xyz[2] = sqrt(xyz[2]); return new Color(xyz); }; _interpolators.push(['lrgb', interpolate_lrgb]); chroma.average = function(colors, mode) { var A, alpha, c, cnt, dx, dy, first, i, l, len, o, xyz, xyz2; if (mode == null) { mode = 'rgb'; } l = colors.length; colors = colors.map(function(c) { return chroma(c); }); first = colors.splice(0, 1)[0]; if (mode === 'lrgb') { return _average_lrgb(colors); } xyz = first.get(mode); cnt = []; dx = 0; dy = 0; for (i in xyz) { xyz[i] = xyz[i] || 0; cnt.push(isNaN(xyz[i]) ? 0 : 1); if (mode.charAt(i) === 'h' && !isNaN(xyz[i])) { A = xyz[i] / 180 * PI; dx += cos(A); dy += sin(A); } } alpha = first.alpha(); for (o = 0, len = colors.length; o < len; o++) { c = colors[o]; xyz2 = c.get(mode); alpha += c.alpha(); for (i in xyz) { if (!isNaN(xyz2[i])) { cnt[i] += 1; if (mode.charAt(i) === 'h') { A = xyz2[i] / 180 * PI; dx += cos(A); dy += sin(A); } else { xyz[i] += xyz2[i]; } } } } for (i in xyz) { if (mode.charAt(i) === 'h') { A = atan2(dy / cnt[i], dx / cnt[i]) / PI * 180; while (A < 0) { A += 360; } while (A >= 360) { A -= 360; } xyz[i] = A; } else { xyz[i] = xyz[i] / cnt[i]; } } return chroma(xyz, mode).alpha(alpha / l); }; hex2rgb = function(hex) { var a, b, g, r, rgb, u; if (hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) { if (hex.length === 4 || hex.length === 7) { hex = hex.substr(1); } if (hex.length === 3) { hex = hex.split(""); hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } u = parseInt(hex, 16); r = u >> 16; g = u >> 8 & 0xFF; b = u & 0xFF; return [r, g, b, 1]; } if (hex.match(/^#?([A-Fa-f0-9]{8})$/)) { if (hex.length === 9) { hex = hex.substr(1); } u = parseInt(hex, 16); r = u >> 24 & 0xFF; g = u >> 16 & 0xFF; b = u >> 8 & 0xFF; a = round((u & 0xFF) / 0xFF * 100) / 100; return [r, g, b, a]; } if ((_input.css != null) && (rgb = _input.css(hex))) { return rgb; } throw "unknown color: " + hex; }; rgb2hex = function(channels, mode) { var a, b, g, hxa, r, str, u; if (mode == null) { mode = 'rgb'; } r = channels[0], g = channels[1], b = channels[2], a = channels[3]; r = Math.round(r); g = Math.round(g); b = Math.round(b); u = r << 16 | g << 8 | b; str = "000000" + u.toString(16); str = str.substr(str.length - 6); hxa = '0' + round(a * 255).toString(16); hxa = hxa.substr(hxa.length - 2); return "#" + (function() { switch (mode.toLowerCase()) { case 'rgba': return str + hxa; case 'argb': return hxa + str; default: return str; } })(); }; _input.hex = function(h) { return hex2rgb(h); }; chroma.hex = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['hex']), function(){}); }; Color.prototype.hex = function(mode) { if (mode == null) { mode = 'rgb'; } return rgb2hex(this._rgb, mode); }; _guess_formats.push({ p: 4, test: function(n) { if (arguments.length === 1 && type(n) === "string") { return 'hex'; } } }); hsl2rgb = function() { var args, b, c, g, h, i, l, o, r, ref, s, t1, t2, t3; args = unpack(arguments); h = args[0], s = args[1], l = args[2]; if (s === 0) { r = g = b = l * 255; } else { t3 = [0, 0, 0]; c = [0, 0, 0]; t2 = l < 0.5 ? l * (1 + s) : l + s - l * s; t1 = 2 * l - t2; h /= 360; t3[0] = h + 1 / 3; t3[1] = h; t3[2] = h - 1 / 3; for (i = o = 0; o <= 2; i = ++o) { if (t3[i] < 0) { t3[i] += 1; } if (t3[i] > 1) { t3[i] -= 1; } if (6 * t3[i] < 1) { c[i] = t1 + (t2 - t1) * 6 * t3[i]; } else if (2 * t3[i] < 1) { c[i] = t2; } else if (3 * t3[i] < 2) { c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6; } else { c[i] = t1; } } ref = [round(c[0] * 255), round(c[1] * 255), round(c[2] * 255)], r = ref[0], g = ref[1], b = ref[2]; } if (args.length > 3) { return [r, g, b, args[3]]; } else { return [r, g, b]; } }; rgb2hsl = function(r, g, b) { var h, l, min, ref, s; if (r !== void 0 && r.length >= 3) { ref = r, r = ref[0], g = ref[1], b = ref[2]; } r /= 255; g /= 255; b /= 255; min = Math.min(r, g, b); max = Math.max(r, g, b); l = (max + min) / 2; if (max === min) { s = 0; h = Number.NaN; } else { s = l < 0.5 ? (max - min) / (max + min) : (max - min) / (2 - max - min); } if (r === max) { h = (g - b) / (max - min); } else if (g === max) { h = 2 + (b - r) / (max - min); } else if (b === max) { h = 4 + (r - g) / (max - min); } h *= 60; if (h < 0) { h += 360; } return [h, s, l]; }; chroma.hsl = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['hsl']), function(){}); }; _input.hsl = hsl2rgb; Color.prototype.hsl = function() { return rgb2hsl(this._rgb); }; hsv2rgb = function() { var args, b, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, s, t, v; args = unpack(arguments); h = args[0], s = args[1], v = args[2]; v *= 255; if (s === 0) { r = g = b = v; } else { if (h === 360) { h = 0; } if (h > 360) { h -= 360; } if (h < 0) { h += 360; } h /= 60; i = floor(h); f = h - i; p = v * (1 - s); q = v * (1 - s * f); t = v * (1 - s * (1 - f)); switch (i) { case 0: ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2]; break; case 1: ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2]; break; case 2: ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2]; break; case 3: ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2]; break; case 4: ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2]; break; case 5: ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2]; } } return [r, g, b, args.length > 3 ? args[3] : 1]; }; rgb2hsv = function() { var b, delta, g, h, min, r, ref, s, v; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; min = Math.min(r, g, b); max = Math.max(r, g, b); delta = max - min; v = max / 255.0; if (max === 0) { h = Number.NaN; s = 0; } else { s = delta / max; if (r === max) { h = (g - b) / delta; } if (g === max) { h = 2 + (b - r) / delta; } if (b === max) { h = 4 + (r - g) / delta; } h *= 60; if (h < 0) { h += 360; } } return [h, s, v]; }; chroma.hsv = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['hsv']), function(){}); }; _input.hsv = hsv2rgb; Color.prototype.hsv = function() { return rgb2hsv(this._rgb); }; num2rgb = function(num) { var b, g, r; if (type(num) === "number" && num >= 0 && num <= 0xFFFFFF) { r = num >> 16; g = (num >> 8) & 0xFF; b = num & 0xFF; return [r, g, b, 1]; } console.warn("unknown num color: " + num); return [0, 0, 0, 1]; }; rgb2num = function() { var b, g, r, ref; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; return (r << 16) + (g << 8) + b; }; chroma.num = function(num) { return new Color(num, 'num'); }; Color.prototype.num = function(mode) { if (mode == null) { mode = 'rgb'; } return rgb2num(this._rgb, mode); }; _input.num = num2rgb; _guess_formats.push({ p: 1, test: function(n) { if (arguments.length === 1 && type(n) === "number" && n >= 0 && n <= 0xFFFFFF) { return 'num'; } } }); hcg2rgb = function() { var _c, _g, args, b, c, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, t, v; args = unpack(arguments); h = args[0], c = args[1], _g = args[2]; c = c / 100; g = g / 100 * 255; _c = c * 255; if (c === 0) { r = g = b = _g; } else { if (h === 360) { h = 0; } if (h > 360) { h -= 360; } if (h < 0) { h += 360; } h /= 60; i = floor(h); f = h - i; p = _g * (1 - c); q = p + _c * (1 - f); t = p + _c * f; v = p + _c; switch (i) { case 0: ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2]; break; case 1: ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2]; break; case 2: ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2]; break; case 3: ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2]; break; case 4: ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2]; break; case 5: ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2]; } } return [r, g, b, args.length > 3 ? args[3] : 1]; }; rgb2hcg = function() { var _g, b, c, delta, g, h, min, r, ref; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; min = Math.min(r, g, b); max = Math.max(r, g, b); delta = max - min; c = delta * 100 / 255; _g = min / (255 - delta) * 100; if (delta === 0) { h = Number.NaN; } else { if (r === max) { h = (g - b) / delta; } if (g === max) { h = 2 + (b - r) / delta; } if (b === max) { h = 4 + (r - g) / delta; } h *= 60; if (h < 0) { h += 360; } } return [h, c, _g]; }; chroma.hcg = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['hcg']), function(){}); }; _input.hcg = hcg2rgb; Color.prototype.hcg = function() { return rgb2hcg(this._rgb); }; css2rgb = function(css) { var aa, ab, hsl, i, m, o, rgb, w; css = css.toLowerCase(); if ((chroma.colors != null) && chroma.colors[css]) { return hex2rgb(chroma.colors[css]); } if (m = css.match(/rgb\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*\)/)) { rgb = m.slice(1, 4); for (i = o = 0; o <= 2; i = ++o) { rgb[i] = +rgb[i]; } rgb[3] = 1; } else if (m = css.match(/rgba\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*,\s*([01]|[01]?\.\d+)\)/)) { rgb = m.slice(1, 5); for (i = w = 0; w <= 3; i = ++w) { rgb[i] = +rgb[i]; } } else if (m = css.match(/rgb\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) { rgb = m.slice(1, 4); for (i = aa = 0; aa <= 2; i = ++aa) { rgb[i] = round(rgb[i] * 2.55); } rgb[3] = 1; } else if (m = css.match(/rgba\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) { rgb = m.slice(1, 5); for (i = ab = 0; ab <= 2; i = ++ab) { rgb[i] = round(rgb[i] * 2.55); } rgb[3] = +rgb[3]; } else if (m = css.match(/hsl\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) { hsl = m.slice(1, 4); hsl[1] *= 0.01; hsl[2] *= 0.01; rgb = hsl2rgb(hsl); rgb[3] = 1; } else if (m = css.match(/hsla\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) { hsl = m.slice(1, 4); hsl[1] *= 0.01; hsl[2] *= 0.01; rgb = hsl2rgb(hsl); rgb[3] = +m[4]; } return rgb; }; rgb2css = function(rgba) { var mode; mode = rgba[3] < 1 ? 'rgba' : 'rgb'; if (mode === 'rgb') { return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ')'; } else if (mode === 'rgba') { return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ',' + rgba[3] + ')'; } else { } }; rnd = function(a) { return round(a * 100) / 100; }; hsl2css = function(hsl, alpha) { var mode; mode = alpha < 1 ? 'hsla' : 'hsl'; hsl[0] = rnd(hsl[0] || 0); hsl[1] = rnd(hsl[1] * 100) + '%'; hsl[2] = rnd(hsl[2] * 100) + '%'; if (mode === 'hsla') { hsl[3] = alpha; } return mode + '(' + hsl.join(',') + ')'; }; _input.css = function(h) { return css2rgb(h); }; chroma.css = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['css']), function(){}); }; Color.prototype.css = function(mode) { if (mode == null) { mode = 'rgb'; } if (mode.slice(0, 3) === 'rgb') { return rgb2css(this._rgb); } else if (mode.slice(0, 3) === 'hsl') { return hsl2css(this.hsl(), this.alpha()); } }; _input.named = function(name) { return hex2rgb(w3cx11[name]); }; _guess_formats.push({ p: 5, test: function(n) { if (arguments.length === 1 && (w3cx11[n] != null)) { return 'named'; } } }); Color.prototype.name = function(n) { var h, k; if (arguments.length) { if (w3cx11[n]) { this._rgb = hex2rgb(w3cx11[n]); } this._rgb[3] = 1; this; } h = this.hex(); for (k in w3cx11) { if (h === w3cx11[k]) { return k; } } return h; }; lch2lab = function() { /* Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel. These formulas were invented by David Dalrymple to obtain maximum contrast without going out of gamut if the parameters are in the range 0-1. A saturation multiplier was added by Gregor Aisch */ var c, h, l, ref; ref = unpack(arguments), l = ref[0], c = ref[1], h = ref[2]; h = h * DEG2RAD; return [l, cos(h) * c, sin(h) * c]; }; lch2rgb = function() { var L, a, args, b, c, g, h, l, r, ref, ref1; args = unpack(arguments); l = args[0], c = args[1], h = args[2]; ref = lch2lab(l, c, h), L = ref[0], a = ref[1], b = ref[2]; ref1 = lab2rgb(L, a, b), r = ref1[0], g = ref1[1], b = ref1[2]; return [r, g, b, args.length > 3 ? args[3] : 1]; }; lab2lch = function() { var a, b, c, h, l, ref; ref = unpack(arguments), l = ref[0], a = ref[1], b = ref[2]; c = sqrt(a * a + b * b); h = (atan2(b, a) * RAD2DEG + 360) % 360; if (round(c * 10000) === 0) { h = Number.NaN; } return [l, c, h]; }; rgb2lch = function() { var a, b, g, l, r, ref, ref1; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; ref1 = rgb2lab(r, g, b), l = ref1[0], a = ref1[1], b = ref1[2]; return lab2lch(l, a, b); }; chroma.lch = function() { var args; args = unpack(arguments); return new Color(args, 'lch'); }; chroma.hcl = function() { var args; args = unpack(arguments); return new Color(args, 'hcl'); }; _input.lch = lch2rgb; _input.hcl = function() { var c, h, l, ref; ref = unpack(arguments), h = ref[0], c = ref[1], l = ref[2]; return lch2rgb([l, c, h]); }; Color.prototype.lch = function() { return rgb2lch(this._rgb); }; Color.prototype.hcl = function() { return rgb2lch(this._rgb).reverse(); }; rgb2cmyk = function(mode) { var b, c, f, g, k, m, r, ref, y; if (mode == null) { mode = 'rgb'; } ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; r = r / 255; g = g / 255; b = b / 255; k = 1 - Math.max(r, Math.max(g, b)); f = k < 1 ? 1 / (1 - k) : 0; c = (1 - r - k) * f; m = (1 - g - k) * f; y = (1 - b - k) * f; return [c, m, y, k]; }; cmyk2rgb = function() { var alpha, args, b, c, g, k, m, r, y; args = unpack(arguments); c = args[0], m = args[1], y = args[2], k = args[3]; alpha = args.length > 4 ? args[4] : 1; if (k === 1) { return [0, 0, 0, alpha]; } r = c >= 1 ? 0 : 255 * (1 - c) * (1 - k); g = m >= 1 ? 0 : 255 * (1 - m) * (1 - k); b = y >= 1 ? 0 : 255 * (1 - y) * (1 - k); return [r, g, b, alpha]; }; _input.cmyk = function() { return cmyk2rgb(unpack(arguments)); }; chroma.cmyk = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['cmyk']), function(){}); }; Color.prototype.cmyk = function() { return rgb2cmyk(this._rgb); }; _input.gl = function() { var i, k, o, rgb, v; rgb = (function() { var ref, results; ref = unpack(arguments); results = []; for (k in ref) { v = ref[k]; results.push(v); } return results; }).apply(this, arguments); for (i = o = 0; o <= 2; i = ++o) { rgb[i] *= 255; } return rgb; }; chroma.gl = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['gl']), function(){}); }; Color.prototype.gl = function() { var rgb; rgb = this._rgb; return [rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, rgb[3]]; }; rgb2luminance = function(r, g, b) { var ref; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; r = luminance_x(r); g = luminance_x(g); b = luminance_x(b); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; luminance_x = function(x) { x /= 255; if (x <= 0.03928) { return x / 12.92; } else { return pow((x + 0.055) / 1.055, 2.4); } }; interpolate_rgb = function(col1, col2, f, m) { var xyz0, xyz1; xyz0 = col1._rgb; xyz1 = col2._rgb; return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m); }; _interpolators.push(['rgb', interpolate_rgb]); Color.prototype.luminance = function(lum, mode) { var cur_lum, eps, max_iter, rgba, test; if (mode == null) { mode = 'rgb'; } if (!arguments.length) { return rgb2luminance(this._rgb); } rgba = this._rgb; if (lum === 0) { rgba = [0, 0, 0, this._rgb[3]]; } else if (lum === 1) { rgba = [255, 255, 255, this[3]]; } else { cur_lum = rgb2luminance(this._rgb); eps = 1e-7; max_iter = 20; test = function(l, h) { var lm, m; m = l.interpolate(h, 0.5, mode); lm = m.luminance(); if (Math.abs(lum - lm) < eps || !max_iter--) { return m; } if (lm > lum) { return test(l, m); } return test(m, h); }; if (cur_lum > lum) { rgba = test(chroma('black'), this).rgba(); } else { rgba = test(this, chroma('white')).rgba(); } } return chroma(rgba).alpha(this.alpha()); }; temperature2rgb = function(kelvin) { var b, g, r, temp; temp = kelvin / 100; if (temp < 66) { r = 255; g = -155.25485562709179 - 0.44596950469579133 * (g = temp - 2) + 104.49216199393888 * log(g); b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp - 10) + 115.67994401066147 * log(b); } else { r = 351.97690566805693 + 0.114206453784165 * (r = temp - 55) - 40.25366309332127 * log(r); g = 325.4494125711974 + 0.07943456536662342 * (g = temp - 50) - 28.0852963507957 * log(g); b = 255; } return [r, g, b]; }; rgb2temperature = function() { var b, eps, g, maxTemp, minTemp, r, ref, rgb, temp; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; minTemp = 1000; maxTemp = 40000; eps = 0.4; while (maxTemp - minTemp > eps) { temp = (maxTemp + minTemp) * 0.5; rgb = temperature2rgb(temp); if ((rgb[2] / rgb[0]) >= (b / r)) { maxTemp = temp; } else { minTemp = temp; } } return round(temp); }; chroma.temperature = chroma.kelvin = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['temperature']), function(){}); }; _input.temperature = _input.kelvin = _input.K = temperature2rgb; Color.prototype.temperature = function() { return rgb2temperature(this._rgb); }; Color.prototype.kelvin = Color.prototype.temperature; chroma.contrast = function(a, b) { var l1, l2, ref, ref1; if ((ref = type(a)) === 'string' || ref === 'number') { a = new Color(a); } if ((ref1 = type(b)) === 'string' || ref1 === 'number') { b = new Color(b); } l1 = a.luminance(); l2 = b.luminance(); if (l1 > l2) { return (l1 + 0.05) / (l2 + 0.05); } else { return (l2 + 0.05) / (l1 + 0.05); } }; chroma.distance = function(a, b, mode) { var d, i, l1, l2, ref, ref1, sum_sq; if (mode == null) { mode = 'lab'; } if ((ref = type(a)) === 'string' || ref === 'number') { a = new Color(a); } if ((ref1 = type(b)) === 'string' || ref1 === 'number') { b = new Color(b); } l1 = a.get(mode); l2 = b.get(mode); sum_sq = 0; for (i in l1) { d = (l1[i] || 0) - (l2[i] || 0); sum_sq += d * d; } return Math.sqrt(sum_sq); }; chroma.deltaE = function(a, b, L, C) { var L1, L2, a1, a2, b1, b2, c1, c2, c4, dH2, delA, delB, delC, delL, f, h1, ref, ref1, ref2, ref3, sc, sh, sl, t, v1, v2, v3; if (L == null) { L = 1; } if (C == null) { C = 1; } if ((ref = type(a)) === 'string' || ref === 'number') { a = new Color(a); } if ((ref1 = type(b)) === 'string' || ref1 === 'number') { b = new Color(b); } ref2 = a.lab(), L1 = ref2[0], a1 = ref2[1], b1 = ref2[2]; ref3 = b.lab(), L2 = ref3[0], a2 = ref3[1], b2 = ref3[2]; c1 = sqrt(a1 * a1 + b1 * b1); c2 = sqrt(a2 * a2 + b2 * b2); sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + 0.01765 * L1); sc = (0.0638 * c1) / (1.0 + 0.0131 * c1) + 0.638; h1 = c1 < 0.000001 ? 0.0 : (atan2(b1, a1) * 180.0) / PI; while (h1 < 0) { h1 += 360; } while (h1 >= 360) { h1 -= 360; } t = (h1 >= 164.0) && (h1 <= 345.0) ? 0.56 + abs(0.2 * cos((PI * (h1 + 168.0)) / 180.0)) : 0.36 + abs(0.4 * cos((PI * (h1 + 35.0)) / 180.0)); c4 = c1 * c1 * c1 * c1; f = sqrt(c4 / (c4 + 1900.0)); sh = sc * (f * t + 1.0 - f); delL = L1 - L2; delC = c1 - c2; delA = a1 - a2; delB = b1 - b2; dH2 = delA * delA + delB * delB - delC * delC; v1 = delL / (L * sl); v2 = delC / (C * sc); v3 = sh; return sqrt(v1 * v1 + v2 * v2 + (dH2 / (v3 * v3))); }; Color.prototype.get = function(modechan) { var channel, i, me, mode, ref, src; me = this; ref = modechan.split('.'), mode = ref[0], channel = ref[1]; src = me[mode](); if (channel) { i = mode.indexOf(channel); if (i > -1) { return src[i]; } else { return console.warn('unknown channel ' + channel + ' in mode ' + mode); } } else { return src; } }; Color.prototype.set = function(modechan, value) { var channel, i, me, mode, ref, src; me = this; ref = modechan.split('.'), mode = ref[0], channel = ref[1]; if (channel) { src = me[mode](); i = mode.indexOf(channel); if (i > -1) { if (type(value) === 'string') { switch (value.charAt(0)) { case '+': src[i] += +value; break; case '-': src[i] += +value; break; case '*': src[i] *= +(value.substr(1)); break; case '/': src[i] /= +(value.substr(1)); break; default: src[i] = +value; } } else { src[i] = value; } } else { console.warn('unknown channel ' + channel + ' in mode ' + mode); } } else { src = value; } return chroma(src, mode).alpha(me.alpha()); }; Color.prototype.clipped = function() { return this._rgb._clipped || false; }; Color.prototype.alpha = function(a) { if (arguments.length) { return chroma.rgb([this._rgb[0], this._rgb[1], this._rgb[2], a]); } return this._rgb[3]; }; Color.prototype.darken = function(amount) { var lab, me; if (amount == null) { amount = 1; } me = this; lab = me.lab(); lab[0] -= LAB_CONSTANTS.Kn * amount; return chroma.lab(lab).alpha(me.alpha()); }; Color.prototype.brighten = function(amount) { if (amount == null) { amount = 1; } return this.darken(-amount); }; Color.prototype.darker = Color.prototype.darken; Color.prototype.brighter = Color.prototype.brighten; Color.prototype.saturate = function(amount) { var lch, me; if (amount == null) { amount = 1; } me = this; lch = me.lch(); lch[1] += amount * LAB_CONSTANTS.Kn; if (lch[1] < 0) { lch[1] = 0; } return chroma.lch(lch).alpha(me.alpha()); }; Color.prototype.desaturate = function(amount) { if (amount == null) { amount = 1; } return this.saturate(-amount); }; Color.prototype.premultiply = function() { var a, rgb; rgb = this.rgb(); a = this.alpha(); return chroma(rgb[0] * a, rgb[1] * a, rgb[2] * a, a); }; blend = function(bottom, top, mode) { if (!blend[mode]) { throw 'unknown blend mode ' + mode; } return blend[mode](bottom, top); }; blend_f = function(f) { return function(bottom, top) { var c0, c1; c0 = chroma(top).rgb(); c1 = chroma(bottom).rgb(); return chroma(f(c0, c1), 'rgb'); }; }; each = function(f) { return function(c0, c1) { var i, o, out; out = []; for (i = o = 0; o <= 3; i = ++o) { out[i] = f(c0[i], c1[i]); } return out; }; }; normal = function(a, b) { return a; }; multiply = function(a, b) { return a * b / 255; }; darken = function(a, b) { if (a > b) { return b; } else { return a; } }; lighten = function(a, b) { if (a > b) { return a; } else { return b; } }; screen = function(a, b) { return 255 * (1 - (1 - a / 255) * (1 - b / 255)); }; overlay = function(a, b) { if (b < 128) { return 2 * a * b / 255; } else { return 255 * (1 - 2 * (1 - a / 255) * (1 - b / 255)); } }; burn = function(a, b) { return 255 * (1 - (1 - b / 255) / (a / 255)); }; dodge = function(a, b) { if (a === 255) { return 255; } a = 255 * (b / 255) / (1 - a / 255); if (a > 255) { return 255; } else { return a; } }; blend.normal = blend_f(each(normal)); blend.multiply = blend_f(each(multiply)); blend.screen = blend_f(each(screen)); blend.overlay = blend_f(each(overlay)); blend.darken = blend_f(each(darken)); blend.lighten = blend_f(each(lighten)); blend.dodge = blend_f(each(dodge)); blend.burn = blend_f(each(burn)); chroma.blend = blend; chroma.analyze = function(data) { var len, o, r, val; r = { min: Number.MAX_VALUE, max: Number.MAX_VALUE * -1, sum: 0, values: [], count: 0 }; for (o = 0, len = data.length; o < len; o++) { val = data[o]; if ((val != null) && !isNaN(val)) { r.values.push(val); r.sum += val; if (val < r.min) { r.min = val; } if (val > r.max) { r.max = val; } r.count += 1; } } r.domain = [r.min, r.max]; r.limits = function(mode, num) { return chroma.limits(r, mode, num); }; return r; }; chroma.scale = function(colors, positions) { var _classes, _colorCache, _colors, _correctLightness, _domain, _fixed, _gamma, _max, _min, _mode, _nacol, _out, _padding, _pos, _spread, _useCache, classifyValue, f, getClass, getColor, resetCache, setColors, tmap; _mode = 'rgb'; _nacol = chroma('#ccc'); _spread = 0; _fixed = false; _domain = [0, 1]; _pos = []; _padding = [0, 0]; _classes = false; _colors = []; _out = false; _min = 0; _max = 1; _correctLightness = false; _colorCache = {}; _useCache = true; _gamma = 1; setColors = function(colors) { var c, col, o, ref, ref1, w; if (colors == null) { colors = ['#fff', '#000']; } if ((colors != null) && type(colors) === 'string' && (chroma.brewer != null)) { colors = chroma.brewer[colors] || chroma.brewer[colors.toLowerCase()] || colors; } if (type(colors) === 'array') { colors = colors.slice(0); for (c = o = 0, ref = colors.length - 1; 0 <= ref ? o <= ref : o >= ref; c = 0 <= ref ? ++o : --o) { col = colors[c]; if (type(col) === "string") { colors[c] = chroma(col); } } _pos.length = 0; for (c = w = 0, ref1 = colors.length - 1; 0 <= ref1 ? w <= ref1 : w >= ref1; c = 0 <= ref1 ? ++w : --w) { _pos.push(c / (colors.length - 1)); } } resetCache(); return _colors = colors; }; getClass = function(value) { var i, n; if (_classes != null) { n = _classes.length - 1; i = 0; while (i < n && value >= _classes[i]) { i++; } return i - 1; } return 0; }; tmap = function(t) { return t; }; classifyValue = function(value) { var i, maxc, minc, n, val; val = value; if (_classes.length > 2) { n = _classes.length - 1; i = getClass(value); minc = _classes[0] + (_classes[1] - _classes[0]) * (0 + _spread * 0.5); maxc = _classes[n - 1] + (_classes[n] - _classes[n - 1]) * (1 - _spread * 0.5); val = _min + ((_classes[i] + (_classes[i + 1] - _classes[i]) * 0.5 - minc) / (maxc - minc)) * (_max - _min); } return val; }; getColor = function(val, bypassMap) { var c, col, i, k, o, p, ref, t; if (bypassMap == null) { bypassMap = false; } if (isNaN(val)) { return _nacol; } if (!bypassMap) { if (_classes && _classes.length > 2) { c = getClass(val); t = c / (_classes.length - 2); } else if (_max !== _min) { t = (val - _min) / (_max - _min); } else { t = 1; } } else { t = val; } if (!bypassMap) { t = tmap(t); } if (_gamma !== 1) { t = pow(t, _gamma); } t = _padding[0] + (t * (1 - _padding[0] - _padding[1])); t = Math.min(1, Math.max(0, t)); k = Math.floor(t * 10000); if (_useCache && _colorCache[k]) { col = _colorCache[k]; } else { if (type(_colors) === 'array') { for (i = o = 0, ref = _pos.length - 1; 0 <= ref ? o <= ref : o >= ref; i = 0 <= ref ? ++o : --o) { p = _pos[i]; if (t <= p) { col = _colors[i]; break; } if (t >= p && i === _pos.length - 1) { col = _colors[i]; break; } if (t > p && t < _pos[i + 1]) { t = (t - p) / (_pos[i + 1] - p); col = chroma.interpolate(_colors[i], _colors[i + 1], t, _mode); break; } } } else if (type(_colors) === 'function') { col = _colors(t); } if (_useCache) { _colorCache[k] = col; } } return col; }; resetCache = function() { return _colorCache = {}; }; setColors(colors); f = function(v) { var c; c = chroma(getColor(v)); if (_out && c[_out]) { return c[_out](); } else { return c; } }; f.classes = function(classes) { var d; if (classes != null) { if (type(classes) === 'array') { _classes = classes; _domain = [classes[0], classes[classes.length - 1]]; } else { d = chroma.analyze(_domain); if (classes === 0) { _classes = [d.min, d.max]; } else { _classes = chroma.limits(d, 'e', classes); } } return f; } return _classes; }; f.domain = function(domain) { var c, d, k, len, o, ref, w; if (!arguments.length) { return _domain; } _min = domain[0]; _max = domain[domain.length - 1]; _pos = []; k = _colors.length; if (domain.length === k && _min !== _max) { for (o = 0, len = domain.length; o < len; o++) { d = domain[o]; _pos.push((d - _min) / (_max - _min)); } } else { for (c = w = 0, ref = k - 1; 0 <= ref ? w <= ref : w >= ref; c = 0 <= ref ? ++w : --w) { _pos.push(c / (k - 1)); } } _domain = [_min, _max]; return f; }; f.mode = function(_m) { if (!arguments.length) { return _mode; } _mode = _m; resetCache(); return f; }; f.range = function(colors, _pos) { setColors(colors, _pos); return f; }; f.out = function(_o) { _out = _o; return f; }; f.spread = function(val) { if (!arguments.length) { return _spread; } _spread = val; return f; }; f.correctLightness = function(v) { if (v == null) { v = true; } _correctLightness = v; resetCache(); if (_correctLightness) { tmap = function(t) { var L0, L1, L_actual, L_diff, L_ideal, max_iter, pol, t0, t1; L0 = getColor(0, true).lab()[0]; L1 = getColor(1, true).lab()[0]; pol = L0 > L1; L_actual = getColor(t, true).lab()[0]; L_ideal = L0 + (L1 - L0) * t; L_diff = L_actual - L_ideal; t0 = 0; t1 = 1; max_iter = 20; while (Math.abs(L_diff) > 1e-2 && max_iter-- > 0) { (function() { if (pol) { L_diff *= -1; } if (L_diff < 0) { t0 = t; t += (t1 - t) * 0.5; } else { t1 = t; t += (t0 - t) * 0.5; } L_actual = getColor(t, true).lab()[0]; return L_diff = L_actual - L_ideal; })(); } return t; }; } else { tmap = function(t) { return t; }; } return f; }; f.padding = function(p) { if (p != null) { if (type(p) === 'number') { p = [p, p]; } _padding = p; return f; } else { return _padding; } }; f.colors = function(numColors, out) { var dd, dm, i, o, ref, result, results, samples, w; if (arguments.length < 2) { out = 'hex'; } result = []; if (arguments.length === 0) { result = _colors.slice(0); } else if (numColors === 1) { result = [f(0.5)]; } else if (numColors > 1) { dm = _domain[0]; dd = _domain[1] - dm; result = (function() { results = []; for (var o = 0; 0 <= numColors ? o < numColors : o > numColors; 0 <= numColors ? o++ : o--){ results.push(o); } return results; }).apply(this).map(function(i) { return f(dm + i / (numColors - 1) * dd); }); } else { colors = []; samples = []; if (_classes && _classes.length > 2) { for (i = w = 1, ref = _classes.length; 1 <= ref ? w < ref : w > ref; i = 1 <= ref ? ++w : --w) { samples.push((_classes[i - 1] + _classes[i]) * 0.5); } } else { samples = _domain; } result = samples.map(function(v) { return f(v); }); } if (chroma[out]) { result = result.map(function(c) { return c[out](); }); } return result; }; f.cache = function(c) { if (c != null) { _useCache = c; return f; } else { return _useCache; } }; f.gamma = function(g) { if (g != null) { _gamma = g; return f; } else { return _gamma; } }; return f; }; if (chroma.scales == null) { chroma.scales = {}; } chroma.scales.cool = function() { return chroma.scale([chroma.hsl(180, 1, .9), chroma.hsl(250, .7, .4)]); }; chroma.scales.hot = function() { return chroma.scale(['#000', '#f00', '#ff0', '#fff'], [0, .25, .75, 1]).mode('rgb'); }; chroma.analyze = function(data, key, filter) { var add, k, len, o, r, val, visit; r = { min: Number.MAX_VALUE, max: Number.MAX_VALUE * -1, sum: 0, values: [], count: 0 }; if (filter == null) { filter = function() { return true; }; } add = function(val) { if ((val != null) && !isNaN(val)) { r.values.push(val); r.sum += val; if (val < r.min) { r.min = val; } if (val > r.max) { r.max = val; } r.count += 1; } }; visit = function(val, k) { if (filter(val, k)) { if ((key != null) && type(key) === 'function') { return add(key(val)); } else if ((key != null) && type(key) === 'string' || type(key) === 'number') { return add(val[key]); } else { return add(val); } } }; if (type(data) === 'array') { for (o = 0, len = data.length; o < len; o++) { val = data[o]; visit(val); } } else { for (k in data) { val = data[k]; visit(val, k); } } r.domain = [r.min, r.max]; r.limits = function(mode, num) { return chroma.limits(r, mode, num); }; return r; }; chroma.limits = function(data, mode, num) { var aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, assignments, best, centroids, cluster, clusterSizes, dist, i, j, kClusters, limits, max_log, min, min_log, mindist, n, nb_iters, newCentroids, o, p, pb, pr, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, repeat, sum, tmpKMeansBreaks, v, value, values, w; if (mode == null) { mode = 'equal'; } if (num == null) { num = 7; } if (type(data) === 'array') { data = chroma.analyze(data); } min = data.min; max = data.max; sum = data.sum; values = data.values.sort(function(a, b) { return a - b; }); if (num === 1) { return [min, max]; } limits = []; if (mode.substr(0, 1) === 'c') { limits.push(min); limits.push(max); } if (mode.substr(0, 1) === 'e') { limits.push(min); for (i = o = 1, ref = num - 1; 1 <= ref ? o <= ref : o >= ref; i = 1 <= ref ? ++o : --o) { limits.push(min + (i / num) * (max - min)); } limits.push(max); } else if (mode.substr(0, 1) === 'l') { if (min <= 0) { throw 'Logarithmic scales are only possible for values > 0'; } min_log = Math.LOG10E * log(min); max_log = Math.LOG10E * log(max); limits.push(min); for (i = w = 1, ref1 = num - 1; 1 <= ref1 ? w <= ref1 : w >= ref1; i = 1 <= ref1 ? ++w : --w) { limits.push(pow(10, min_log + (i / num) * (max_log - min_log))); } limits.push(max); } else if (mode.substr(0, 1) === 'q') { limits.push(min); for (i = aa = 1, ref2 = num - 1; 1 <= ref2 ? aa <= ref2 : aa >= ref2; i = 1 <= ref2 ? ++aa : --aa) { p = (values.length - 1) * i / num; pb = floor(p); if (pb === p) { limits.push(values[pb]); } else { pr = p - pb; limits.push(values[pb] * (1 - pr) + values[pb + 1] * pr); } } limits.push(max); } else if (mode.substr(0, 1) === 'k') { /* implementation based on http://code.google.com/p/figue/source/browse/trunk/figue.js#336 simplified for 1-d input values */ n = values.length; assignments = new Array(n); clusterSizes = new Array(num); repeat = true; nb_iters = 0; centroids = null; centroids = []; centroids.push(min); for (i = ab = 1, ref3 = num - 1; 1 <= ref3 ? ab <= ref3 : ab >= ref3; i = 1 <= ref3 ? ++ab : --ab) { centroids.push(min + (i / num) * (max - min)); } centroids.push(max); while (repeat) { for (j = ac = 0, ref4 = num - 1; 0 <= ref4 ? ac <= ref4 : ac >= ref4; j = 0 <= ref4 ? ++ac : --ac) { clusterSizes[j] = 0; } for (i = ad = 0, ref5 = n - 1; 0 <= ref5 ? ad <= ref5 : ad >= ref5; i = 0 <= ref5 ? ++ad : --ad) { value = values[i]; mindist = Number.MAX_VALUE; for (j = ae = 0, ref6 = num - 1; 0 <= ref6 ? ae <= ref6 : ae >= ref6; j = 0 <= ref6 ? ++ae : --ae) { dist = abs(centroids[j] - value); if (dist < mindist) { mindist = dist; best = j; } } clusterSizes[best]++; assignments[i] = best; } newCentroids = new Array(num); for (j = af = 0, ref7 = num - 1; 0 <= ref7 ? af <= ref7 : af >= ref7; j = 0 <= ref7 ? ++af : --af) { newCentroids[j] = null; } for (i = ag = 0, ref8 = n - 1; 0 <= ref8 ? ag <= ref8 : ag >= ref8; i = 0 <= ref8 ? ++ag : --ag) { cluster = assignments[i]; if (newCentroids[cluster] === null) { newCentroids[cluster] = values[i]; } else { newCentroids[cluster] += values[i]; } } for (j = ah = 0, ref9 = num - 1; 0 <= ref9 ? ah <= ref9 : ah >= ref9; j = 0 <= ref9 ? ++ah : --ah) { newCentroids[j] *= 1 / clusterSizes[j]; } repeat = false; for (j = ai = 0, ref10 = num - 1; 0 <= ref10 ? ai <= ref10 : ai >= ref10; j = 0 <= ref10 ? ++ai : --ai) { if (newCentroids[j] !== centroids[i]) { repeat = true; break; } } centroids = newCentroids; nb_iters++; if (nb_iters > 200) { repeat = false; } } kClusters = {}; for (j = aj = 0, ref11 = num - 1; 0 <= ref11 ? aj <= ref11 : aj >= ref11; j = 0 <= ref11 ? ++aj : --aj) { kClusters[j] = []; } for (i = ak = 0, ref12 = n - 1; 0 <= ref12 ? ak <= ref12 : ak >= ref12; i = 0 <= ref12 ? ++ak : --ak) { cluster = assignments[i]; kClusters[cluster].push(values[i]); } tmpKMeansBreaks = []; for (j = al = 0, ref13 = num - 1; 0 <= ref13 ? al <= ref13 : al >= ref13; j = 0 <= ref13 ? ++al : --al) { tmpKMeansBreaks.push(kClusters[j][0]); tmpKMeansBreaks.push(kClusters[j][kClusters[j].length - 1]); } tmpKMeansBreaks = tmpKMeansBreaks.sort(function(a, b) { return a - b; }); limits.push(tmpKMeansBreaks[0]); for (i = am = 1, ref14 = tmpKMeansBreaks.length - 1; am <= ref14; i = am += 2) { v = tmpKMeansBreaks[i]; if (!isNaN(v) && limits.indexOf(v) === -1) { limits.push(v); } } } return limits; }; hsi2rgb = function(h, s, i) { /* borrowed from here: http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp */ var args, b, g, r; args = unpack(arguments); h = args[0], s = args[1], i = args[2]; if (isNaN(h)) { h = 0; } h /= 360; if (h < 1 / 3) { b = (1 - s) / 3; r = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3; g = 1 - (b + r); } else if (h < 2 / 3) { h -= 1 / 3; r = (1 - s) / 3; g = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3; b = 1 - (r + g); } else { h -= 2 / 3; g = (1 - s) / 3; b = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3; r = 1 - (g + b); } r = limit(i * r * 3); g = limit(i * g * 3); b = limit(i * b * 3); return [r * 255, g * 255, b * 255, args.length > 3 ? args[3] : 1]; }; rgb2hsi = function() { /* borrowed from here: http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp */ var b, g, h, i, min, r, ref, s; ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2]; TWOPI = Math.PI * 2; r /= 255; g /= 255; b /= 255; min = Math.min(r, g, b); i = (r + g + b) / 3; s = 1 - min / i; if (s === 0) { h = 0; } else { h = ((r - g) + (r - b)) / 2; h /= Math.sqrt((r - g) * (r - g) + (r - b) * (g - b)); h = Math.acos(h); if (b > g) { h = TWOPI - h; } h /= TWOPI; } return [h * 360, s, i]; }; chroma.hsi = function() { return (function(func, args, ctor) { ctor.prototype = func.prototype; var child = new ctor, result = func.apply(child, args); return Object(result) === result ? result : child; })(Color, slice.call(arguments).concat(['hsi']), function(){}); }; _input.hsi = hsi2rgb; Color.prototype.hsi = function() { return rgb2hsi(this._rgb); }; interpolate_hsx = function(col1, col2, f, m) { var dh, hue, hue0, hue1, lbv, lbv0, lbv1, res, sat, sat0, sat1, xyz0, xyz1; if (m === 'hsl') { xyz0 = col1.hsl(); xyz1 = col2.hsl(); } else if (m === 'hsv') { xyz0 = col1.hsv(); xyz1 = col2.hsv(); } else if (m === 'hcg') { xyz0 = col1.hcg(); xyz1 = col2.hcg(); } else if (m === 'hsi') { xyz0 = col1.hsi(); xyz1 = col2.hsi(); } else if (m === 'lch' || m === 'hcl') { m = 'hcl'; xyz0 = col1.hcl(); xyz1 = col2.hcl(); } if (m.substr(0, 1) === 'h') { hue0 = xyz0[0], sat0 = xyz0[1], lbv0 = xyz0[2]; hue1 = xyz1[0], sat1 = xyz1[1], lbv1 = xyz1[2]; } if (!isNaN(hue0) && !isNaN(hue1)) { if (hue1 > hue0 && hue1 - hue0 > 180) { dh = hue1 - (hue0 + 360); } else if (hue1 < hue0 && hue0 - hue1 > 180) { dh = hue1 + 360 - hue0; } else { dh = hue1 - hue0; } hue = hue0 + f * dh; } else if (!isNaN(hue0)) { hue = hue0; if ((lbv1 === 1 || lbv1 === 0) && m !== 'hsv') { sat = sat0; } } else if (!isNaN(hue1)) { hue = hue1; if ((lbv0 === 1 || lbv0 === 0) && m !== 'hsv') { sat = sat1; } } else { hue = Number.NaN; } if (sat == null) { sat = sat0 + f * (sat1 - sat0); } lbv = lbv0 + f * (lbv1 - lbv0); return res = chroma[m](hue, sat, lbv); }; _interpolators = _interpolators.concat((function() { var len, o, ref, results; ref = ['hsv', 'hsl', 'hsi', 'hcl', 'lch', 'hcg']; results = []; for (o = 0, len = ref.length; o < len; o++) { m = ref[o]; results.push([m, interpolate_hsx]); } return results; })()); interpolate_num = function(col1, col2, f, m) { var n1, n2; n1 = col1.num(); n2 = col2.num(); return chroma.num(n1 + (n2 - n1) * f, 'num'); }; _interpolators.push(['num', interpolate_num]); interpolate_lab = function(col1, col2, f, m) { var res, xyz0, xyz1; xyz0 = col1.lab(); xyz1 = col2.lab(); return res = new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m); }; _interpolators.push(['lab', interpolate_lab]); }).call(this); /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(120)(module))) /***/ }), /* 122 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var hasWindow = typeof window !== "undefined"; var chroma = hasWindow && window.chroma ? window.chroma : __webpack_require__(121); var Bezier = hasWindow && window.Bezier ? window.Bezier : __webpack_require__(119); var sin = Math.sin; var cos = Math.cos; var PI = Math.PI; var API = { Paper: false, defaultWidth: 275, defaultHeight: 275, panelCount: 1, Bezier: Bezier, utils: Bezier.getUtils(), curve: false, mx: 0, my: 0, hover: { x: 0, y: 0 }, cx: 0, cy: 0, mp: false, offset: { x: 0, y: 0 }, lpts: [], colorSeed: 0, playing: false, frame: 0, playinterval: 33, animate: function animate() { if (this.playing) { this.frame++; // requestAnimationFrame is spectacularly too fast setTimeout(this.animate, this.playinterval); this.props.draw(this, this.curve); } }, getFrame: function getFrame() { return this.frame; }, getPlayInterval: function getPlayInterval() { return this.playinterval; }, play: function play() { this.playing = true;this.animate(); }, pause: function pause() { this.playing = false; }, redraw: function redraw() { if (this.props.draw) { this.props.draw(this, this.curve); } }, mouseDown: function mouseDown(evt) { var _this = this; this.mx = evt.offsetX; this.my = evt.offsetY; this.movingPoint = false; this.dragging = false; this.down = true; this.lpts.forEach(function (p, idx) { if (Math.abs(_this.mx - p.x) < 10 && Math.abs(_this.my - p.y) < 10) { _this.movingPoint = true; _this.mp = p; _this.mp_idx = idx; _this.cx = p.x; _this.cy = p.y; } }); if (this.props.onMouseDown) { this.props.onMouseDown(evt, this); } if ('setCapture' in evt.target) { evt.target.setCapture(); } }, mouseMove: function mouseMove(evt) { if (!this.props.static) { if (this.down) { this.dragging = true; } var found = false; this.lpts.forEach(function (p) { var mx = evt.offsetX; var my = evt.offsetY; if (Math.abs(mx - p.x) < 10 && Math.abs(my - p.y) < 10) { found = found || true; } }); this.cvs.style.cursor = found ? "pointer" : "default"; this.hover = { x: evt.offsetX, y: evt.offsetY }; if (this.movingPoint) { this.ox = evt.offsetX - this.mx; this.oy = evt.offsetY - this.my; this.mp.x = Math.max(0, Math.min(this.defaultWidth, this.cx + this.ox)); this.mp.y = Math.max(0, Math.min(this.defaultHeight, this.cy + this.oy)); if (this.curve.forEach) { for (var i = 0, c, _pts; i < this.curve.length; i++) { c = this.curve[i]; _pts = c.points; if (_pts.indexOf(this.mp) > -1) { c.update(); break; } } } else if (this.curve && this.curve.update) { this.curve.update(); } } else { this.mx = evt.offsetX; this.my = evt.offsetY; } } if (this.props.onMouseMove) { this.props.onMouseMove(evt, this); } if (this.dragging && this.props.onMouseDrag) { this.props.onMouseDrag(evt, this); } if (!this.props.static && !this.playing && this.props.draw) { this.props.draw(this, this.curve); } }, mouseUp: function mouseUp(evt) { this.down = false; if (!this.movingPoint) { if (this.props.onMouseUp) { this.props.onMouseUp(evt, this); } return; } this.movingPoint = false; this.mp = false; if (this.props.onMouseUp) { this.props.onMouseUp(evt, this); } }, onClick: function onClick(evt) { this.mx = evt.offsetX; this.my = evt.offsetY; if (!this.dragging && this.props.onClick) { this.props.onClick(evt, this); } }, onKeyUp: function onKeyUp(evt) { if (this.props.onKeyUp) { this.props.onKeyUp(evt, this); if (!this.playing && this.props.draw) { this.props.draw(this, this.curve); } } }, onKeyDown: function onKeyDown(evt) { if (this.props.onKeyDown) { this.props.onKeyDown(evt, this); if (!this.playing && this.props.draw) { this.props.draw(this, this.curve); } } }, onKeyPress: function onKeyPress(evt) { if (this.props.onKeyPress) { this.props.onKeyPress(evt, this); if (!this.playing && this.props.draw) { this.props.draw(this, this.curve); } } }, /** * API for curve drawing. */ reset: function reset() { this.refs.canvas.width = this.refs.canvas.width; this.ctx.strokeStyle = "black"; this.ctx.lineWidth = 1; this.ctx.fillStyle = "none"; var dpr = this.getPixelRatio(); this.ctx.scale(dpr, dpr); this.offset = { x: 0, y: 0 }; this.colorSeed = 0; }, setSize: function setSize(w, h) { this.defaultWidth = w; this.defaultHeight = h; var cvs = this.refs.canvas; cvs.style.width = this.panelCount * w + "px"; cvs.style.height = h + "px"; var dpr = this.getPixelRatio(); cvs.width = this.panelCount * w * dpr; cvs.height = h * dpr; this.ctx.scale(dpr, dpr); }, setCurves: function setCurves(c) { this.setCurve(c); }, setCurve: function setCurve(c) { if (!c) { this.curve = false; this.lpts = []; return; } var pts = []; c = c instanceof Array ? c : Array.from(arguments); c.forEach(function (nc) { pts = pts.concat(nc.points); }); this.curve = c.length === 1 ? c[0] : c; this.lpts = pts; }, getPanelWidth: function getPanelWidth() { return this.defaultWidth; }, getPanelHeight: function getPanelHeight() { return this.defaultHeight; }, getDefaultQuadratic: function getDefaultQuadratic() { return new this.Bezier(70, 250, 20, 110, 250, 60); }, getDefaultCubic: function getDefaultCubic() { return new this.Bezier(120, 160, 35, 200, 220, 260, 220, 40); }, getDefault3DCubic: function getDefault3DCubic() { return new this.Bezier(120, 0, 0, 120, 120, 30, 0, 120, 100, 0, 0, 200); }, getPixelRatio: function getPixelRatio() { return window.devicePixelRatio || 1; }, toImage: function toImage() { var dataURL = this.refs.canvas.toDataURL(); var img = new Image(); img.src = dataURL; img.devicePixelRatio = this.getPixelRatio(); return img; }, setPanelCount: function setPanelCount(c) { this.panelCount = c; var cvs = this.refs.canvas; cvs.width = c * this.defaultWidth * this.getPixelRatio(); cvs.style.width = c * this.defaultWidth + "px"; }, setOffset: function setOffset(f) { this.offset = f; }, setColor: function setColor(c) { this.ctx.strokeStyle = c; }, getColor: function getColor() { return this.ctx.strokeStyle || "black"; }, setWeight: function setWeight(c) { this.ctx.lineWidth = c; }, noColor: function noColor(c) { this.ctx.strokeStyle = "transparent"; }, setRandomColor: function setRandomColor(a) { a = typeof a === "undefined" ? 1 : a; var h = this.colorSeed % 360, s = 1.0, l = 0.34; this.colorSeed += 87; this.ctx.strokeStyle = chroma.hsl(h, s, l).alpha(a).css(); }, setRandomFill: function setRandomFill(a) { a = typeof a === "undefined" ? 1 : a; var h = this.colorSeed % 360, s = 1.0, l = 0.34; this.colorSeed += 87; this.ctx.fillStyle = chroma.hsl(h, s, l).alpha(a).css(); }, setFill: function setFill(c) { this.ctx.fillStyle = c; }, getFill: function getFill() { return this.ctx.fillStyle || "transparent"; }, noFill: function noFill() { this.ctx.fillStyle = "transparent"; }, // cabinet projection is good enough project: function project(p, offset, phi) { offset = offset || { x: 0, y: 0 }; phi = phi || -PI / 6; // what they rarely tell you: if you want // z to "go up", x "come out of the screen" // and y to be the "left/right", we need this: var x = p.y, y = -p.z, z = -p.x; return { x: offset.x + x + z / 2 * cos(phi), y: offset.y + y + z / 2 * sin(phi) }; }, projectXY: function projectXY(p, offset, phi) { return API.project({ x: p.x, y: p.y, z: 0 }, offset, phi); }, projectXZ: function projectXZ(p, offset, phi) { return API.project({ x: p.x, y: 0, z: p.z }, offset, phi); }, projectYZ: function projectYZ(p, offset, phi) { return API.project({ x: 0, y: p.y, z: p.z }, offset, phi); }, drawSkeleton: function drawSkeleton(curve, offset, nocoords) { offset = offset || { x: 0, y: 0 }; var pts = curve.points; if (pts.length > 2) { this.ctx.strokeStyle = "lightgrey"; this.drawLine(pts[0], pts[1], offset); var last = pts.length - 2; for (var i = 1; i < last; i++) { this.drawLine(pts[i], pts[i + 1], offset); } this.drawLine(pts[last], pts[last + 1], offset); } this.ctx.strokeStyle = "black"; this.drawPoints(pts, offset); if (!nocoords) { this.drawCoordinates(curve, offset); } }, drawGrid: function drawGrid(xcount, ycount, offset) { var w = this.defaultWidth, h = this.defaultHeight, xstep = w / xcount, ox = xstep / 2, ystep = h / ycount, oy = ystep / 2, x, xv, y, yv, p1, p2; for (x = 0; x < xcount; x++) { xv = ox + x * xstep; p1 = { x: xv, y: 0 }; p2 = { x: xv, y: h }; this.drawLine(p1, p2, offset); } for (y = 0; y < ycount; y++) { yv = oy + y * ystep; p1 = { x: 0, y: yv }; p2 = { x: w, y: yv }; this.drawLine(p1, p2, offset); } }, drawHull: function drawHull(curve, t, offset) { var hull = curve instanceof Array ? curve : curve.hull(t); if (hull.length === 6) { this.drawLine(hull[0], hull[1], offset); this.drawLine(hull[1], hull[2], offset); this.drawLine(hull[3], hull[4], offset); } else { this.drawLine(hull[0], hull[1], offset); this.drawLine(hull[1], hull[2], offset); this.drawLine(hull[2], hull[3], offset); this.drawLine(hull[4], hull[5], offset); this.drawLine(hull[5], hull[6], offset); this.drawLine(hull[7], hull[8], offset); } return hull; }, drawCoordinates: function drawCoordinates(curve, offset) { var _this2 = this; offset = offset || { x: 0, y: 0 }; var pts = curve.points; this.setFill("black"); pts.forEach(function (p) { var txt = "(" + (p.x | 0) + "," + (p.y | 0) + ")"; var x = p.x + offset.x + 5; var y = p.y + offset.y + 10; _this2.text(txt, { x: x, y: y }); }); }, drawFunction: function drawFunction(generator, offset) { var start = generator.start || 0, p0 = generator(start), end = generator.end || 1, plast = generator(end), step = generator.step || 0.01, scale = generator.scale || 1, p, t; for (t = step; t < end; t += step) { p = generator(t, scale); this.drawLine(p0, p, offset); p0 = p; } this.drawLine(p, plast, offset); }, drawCurve: function drawCurve(curve, offset) { var _this3 = this; offset = offset || { x: 0, y: 0 }; var p = curve.points; if (curve.getLUT) { var points = curve.getLUT(100); var p0 = points[0]; points.forEach(function (p1, i) { if (!i) return; _this3.drawLine(p0, p1, offset); p0 = p1; }); return; } var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); this.ctx.moveTo(p[0].x + ox, p[0].y + oy); if (p.length === 3) { this.ctx.quadraticCurveTo(p[1].x + ox, p[1].y + oy, p[2].x + ox, p[2].y + oy); } else if (p.length === 4) { this.ctx.bezierCurveTo(p[1].x + ox, p[1].y + oy, p[2].x + ox, p[2].y + oy, p[3].x + ox, p[3].y + oy); } this.ctx.stroke(); this.ctx.closePath(); }, drawLine: function drawLine(p1, p2, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); this.ctx.moveTo(p1.x + ox, p1.y + oy); this.ctx.lineTo(p2.x + ox, p2.y + oy); this.ctx.stroke(); }, drawPoint: function drawPoint(p, offset) { this.drawCircle(p, 1, offset); }, drawPoints: function drawPoints(points, offset) { offset = offset || { x: 0, y: 0 }; points.forEach(function (p) { this.drawCircle(p, offset.x !== 0 || offset.y !== 0 ? 1.5 : 3, offset); }.bind(this)); }, drawArc: function drawArc(p, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); this.ctx.moveTo(p.x + ox, p.y + oy); this.ctx.arc(p.x + ox, p.y + oy, p.r, p.s, p.e); this.ctx.lineTo(p.x + ox, p.y + oy); this.ctx.fill(); this.ctx.stroke(); }, drawCircle: function drawCircle(p, r, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); this.ctx.arc(p.x + ox, p.y + oy, r, 0, 2 * Math.PI); this.ctx.stroke(); }, drawbbox: function drawbbox(bbox, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); this.ctx.moveTo(bbox.x.min + ox, bbox.y.min + oy); this.ctx.lineTo(bbox.x.min + ox, bbox.y.max + oy); this.ctx.lineTo(bbox.x.max + ox, bbox.y.max + oy); this.ctx.lineTo(bbox.x.max + ox, bbox.y.min + oy); this.ctx.closePath(); this.ctx.stroke(); }, drawRect: function drawRect(p1, p2, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; var x = p1.x + ox, y = p1.y + oy, w = p2.x - p1.x, h = p2.y - p1.y; this.ctx.beginPath(); this.ctx.moveTo(x, y); this.ctx.lineTo(x + w, y); this.ctx.lineTo(x + w, y + h); this.ctx.lineTo(x, y + h); this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); }, drawPath: function drawPath(path, offset) { var _this4 = this; offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; this.ctx.beginPath(); path.forEach(function (p, idx) { if (idx === 0) { return _this4.ctx.moveTo(p.x + ox, p.y + oy); } _this4.ctx.lineTo(p.x + ox, p.y + oy); }); if (closed) this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); }, drawShape: function drawShape(shape, offset) { offset = offset || { x: 0, y: 0 }; var ox = offset.x + this.offset.x; var oy = offset.y + this.offset.y; var order = shape.forward.points.length - 1; this.ctx.beginPath(); this.ctx.moveTo(ox + shape.startcap.points[0].x, oy + shape.startcap.points[0].y); this.ctx.lineTo(ox + shape.startcap.points[3].x, oy + shape.startcap.points[3].y); if (order === 3) { this.ctx.bezierCurveTo(ox + shape.forward.points[1].x, oy + shape.forward.points[1].y, ox + shape.forward.points[2].x, oy + shape.forward.points[2].y, ox + shape.forward.points[3].x, oy + shape.forward.points[3].y); } else { this.ctx.quadraticCurveTo(ox + shape.forward.points[1].x, oy + shape.forward.points[1].y, ox + shape.forward.points[2].x, oy + shape.forward.points[2].y); } this.ctx.lineTo(ox + shape.endcap.points[3].x, oy + shape.endcap.points[3].y); if (order === 3) { this.ctx.bezierCurveTo(ox + shape.back.points[1].x, oy + shape.back.points[1].y, ox + shape.back.points[2].x, oy + shape.back.points[2].y, ox + shape.back.points[3].x, oy + shape.back.points[3].y); } else { this.ctx.quadraticCurveTo(ox + shape.back.points[1].x, oy + shape.back.points[1].y, ox + shape.back.points[2].x, oy + shape.back.points[2].y); } this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); }, text: function text(_text, coord, offset) { offset = offset || { x: 0, y: 0 }; if (this.offset) { offset.x += this.offset.x; offset.y += this.offset.y; } this.ctx.fillText(_text, coord.x + offset.x, coord.y + offset.y); }, image: function image(_image, offset) { var _this5 = this; offset = offset || { x: 0, y: 0 }; if (this.offset) { offset.x += this.offset.x; offset.y += this.offset.y; } var dpr = _image.devicePixelRatio || 1; if (_image.loaded) { this.ctx.drawImage(_image, offset.x, offset.y, _image.width / dpr, _image.height / dpr); } else { _image.onload = function () { _image.loaded = true; _this5.ctx.drawImage(_image, offset.x, offset.y, _image.width / dpr, _image.height / dpr); }; } }, drawAxes: function drawAxes(pad, xlabel, xs, xe, ylabel, ys, ye, offset) { offset = offset || { x: 0, y: 0 }; var dim = this.getPanelWidth(); this.drawLine({ x: pad, y: pad }, { x: dim - pad, y: pad }, offset); this.drawLine({ x: pad, y: pad }, { x: pad, y: dim - pad }, offset); this.setFill("black"); this.text(xlabel + " →", { x: offset.x + dim / 2, y: offset.y + 15 }); this.text(xs, { x: offset.x + pad, y: offset.y + 15 }); this.text(xe, { x: offset.x + dim - pad, y: offset.y + 15 }); this.text(ylabel, { x: offset.x + 5, y: offset.y + dim / 2 - pad }); this.text("↓", { x: offset.x + 5, y: offset.y + dim / 2 }); this.text(ys, { x: offset.x + 4, y: offset.y + pad + 5 }); this.text(ye, { x: offset.x + 2, y: offset.y + dim - pad + 10 }); } }; if (hasWindow) { window["Bezier Graphics API"] = API; } if (true) { module.exports = API; } /***/ }), /* 123 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), /* 124 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), /* 125 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! * Paper.js v0.11.5 - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey * http://scratchdisk.com/ & http://jonathanpuckey.com/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. * * Date: Thu Oct 5 16:16:29 2017 +0200 * *** * * Straps.js - Class inheritance library with support for bean-style accessors * * Copyright (c) 2006 - 2016 Juerg Lehni * http://scratchdisk.com/ * * Distributed under the MIT license. * *** * * Acorn.js * http://marijnhaverbeke.nl/acorn/ * * Acorn is a tiny, fast JavaScript parser written in JavaScript, * created by Marijn Haverbeke and released under an MIT license. * */ var paper = function(self, undefined) { self = self || __webpack_require__(124); var window = self.window, document = self.document; var Base = new function() { var hidden = /^(statics|enumerable|beans|preserve)$/, array = [], slice = array.slice, create = Object.create, describe = Object.getOwnPropertyDescriptor, define = Object.defineProperty, forEach = array.forEach || function(iter, bind) { for (var i = 0, l = this.length; i < l; i++) { iter.call(bind, this[i], i, this); } }, forIn = function(iter, bind) { for (var i in this) { if (this.hasOwnProperty(i)) iter.call(bind, this[i], i, this); } }, set = Object.assign || function(dst) { for (var i = 1, l = arguments.length; i < l; i++) { var src = arguments[i]; for (var key in src) { if (src.hasOwnProperty(key)) dst[key] = src[key]; } } return dst; }, each = function(obj, iter, bind) { if (obj) { var desc = describe(obj, 'length'); (desc && typeof desc.value === 'number' ? forEach : forIn) .call(obj, iter, bind = bind || obj); } return bind; }; function inject(dest, src, enumerable, beans, preserve) { var beansNames = {}; function field(name, val) { val = val || (val = describe(src, name)) && (val.get ? val : val.value); if (typeof val === 'string' && val[0] === '#') val = dest[val.substring(1)] || val; var isFunc = typeof val === 'function', res = val, prev = preserve || isFunc && !val.base ? (val && val.get ? name in dest : dest[name]) : null, bean; if (!preserve || !prev) { if (isFunc && prev) val.base = prev; if (isFunc && beans !== false && (bean = name.match(/^([gs]et|is)(([A-Z])(.*))$/))) beansNames[bean[3].toLowerCase() + bean[4]] = bean[2]; if (!res || isFunc || !res.get || typeof res.get !== 'function' || !Base.isPlainObject(res)) { res = { value: res, writable: true }; } if ((describe(dest, name) || { configurable: true }).configurable) { res.configurable = true; res.enumerable = enumerable != null ? enumerable : !bean; } define(dest, name, res); } } if (src) { for (var name in src) { if (src.hasOwnProperty(name) && !hidden.test(name)) field(name); } for (var name in beansNames) { var part = beansNames[name], set = dest['set' + part], get = dest['get' + part] || set && dest['is' + part]; if (get && (beans === true || get.length === 0)) field(name, { get: get, set: set }); } } return dest; } function Base() { for (var i = 0, l = arguments.length; i < l; i++) { var src = arguments[i]; if (src) set(this, src); } return this; } return inject(Base, { inject: function(src) { if (src) { var statics = src.statics === true ? src : src.statics, beans = src.beans, preserve = src.preserve; if (statics !== src) inject(this.prototype, src, src.enumerable, beans, preserve); inject(this, statics, null, beans, preserve); } for (var i = 1, l = arguments.length; i < l; i++) this.inject(arguments[i]); return this; }, extend: function() { var base = this, ctor, proto; for (var i = 0, obj, l = arguments.length; i < l && !(ctor && proto); i++) { obj = arguments[i]; ctor = ctor || obj.initialize; proto = proto || obj.prototype; } ctor = ctor || function() { base.apply(this, arguments); }; proto = ctor.prototype = proto || create(this.prototype); define(proto, 'constructor', { value: ctor, writable: true, configurable: true }); inject(ctor, this); if (arguments.length) this.inject.apply(ctor, arguments); ctor.base = base; return ctor; } }).inject({ enumerable: false, initialize: Base, set: Base, inject: function() { for (var i = 0, l = arguments.length; i < l; i++) { var src = arguments[i]; if (src) { inject(this, src, src.enumerable, src.beans, src.preserve); } } return this; }, extend: function() { var res = create(this); return res.inject.apply(res, arguments); }, each: function(iter, bind) { return each(this, iter, bind); }, clone: function() { return new this.constructor(this); }, statics: { set: set, each: each, create: create, define: define, describe: describe, clone: function(obj) { return set(new obj.constructor(), obj); }, isPlainObject: function(obj) { var ctor = obj != null && obj.constructor; return ctor && (ctor === Object || ctor === Base || ctor.name === 'Object'); }, pick: function(a, b) { return a !== undefined ? a : b; }, slice: function(list, begin, end) { return slice.call(list, begin, end); } } }); }; if (true) module.exports = Base; Base.inject({ enumerable: false, toString: function() { return this._id != null ? (this._class || 'Object') + (this._name ? " '" + this._name + "'" : ' @' + this._id) : '{ ' + Base.each(this, function(value, key) { if (!/^_/.test(key)) { var type = typeof value; this.push(key + ': ' + (type === 'number' ? Formatter.instance.number(value) : type === 'string' ? "'" + value + "'" : value)); } }, []).join(', ') + ' }'; }, getClassName: function() { return this._class || ''; }, importJSON: function(json) { return Base.importJSON(json, this); }, exportJSON: function(options) { return Base.exportJSON(this, options); }, toJSON: function() { return Base.serialize(this); }, set: function(props, exclude) { if (props) Base.filter(this, props, exclude, this._prioritize); return this; } }, { beans: false, statics: { exports: {}, extend: function extend() { var res = extend.base.apply(this, arguments), name = res.prototype._class; if (name && !Base.exports[name]) Base.exports[name] = res; return res; }, equals: function(obj1, obj2) { if (obj1 === obj2) return true; if (obj1 && obj1.equals) return obj1.equals(obj2); if (obj2 && obj2.equals) return obj2.equals(obj1); if (obj1 && obj2 && typeof obj1 === 'object' && typeof obj2 === 'object') { if (Array.isArray(obj1) && Array.isArray(obj2)) { var length = obj1.length; if (length !== obj2.length) return false; while (length--) { if (!Base.equals(obj1[length], obj2[length])) return false; } } else { var keys = Object.keys(obj1), length = keys.length; if (length !== Object.keys(obj2).length) return false; while (length--) { var key = keys[length]; if (!(obj2.hasOwnProperty(key) && Base.equals(obj1[key], obj2[key]))) return false; } } return true; } return false; }, read: function(list, start, options, amount) { if (this === Base) { var value = this.peek(list, start); list.__index++; return value; } var proto = this.prototype, readIndex = proto._readIndex, begin = start || readIndex && list.__index || 0, length = list.length, obj = list[begin]; amount = amount || length - begin; if (obj instanceof this || options && options.readNull && obj == null && amount <= 1) { if (readIndex) list.__index = begin + 1; return obj && options && options.clone ? obj.clone() : obj; } obj = Base.create(proto); if (readIndex) obj.__read = true; obj = obj.initialize.apply(obj, begin > 0 || begin + amount < length ? Base.slice(list, begin, begin + amount) : list) || obj; if (readIndex) { list.__index = begin + obj.__read; var filtered = obj.__filtered; if (filtered) { list.__filtered = filtered; obj.__filtered = undefined; } obj.__read = undefined; } return obj; }, peek: function(list, start) { return list[list.__index = start || list.__index || 0]; }, remain: function(list) { return list.length - (list.__index || 0); }, readList: function(list, start, options, amount) { var res = [], entry, begin = start || 0, end = amount ? begin + amount : list.length; for (var i = begin; i < end; i++) { res.push(Array.isArray(entry = list[i]) ? this.read(entry, 0, options) : this.read(list, i, options, 1)); } return res; }, readNamed: function(list, name, start, options, amount) { var value = this.getNamed(list, name), hasObject = value !== undefined; if (hasObject) { var filtered = list.__filtered; if (!filtered) { filtered = list.__filtered = Base.create(list[0]); filtered.__unfiltered = list[0]; } filtered[name] = undefined; } var l = hasObject ? [value] : list, res = this.read(l, start, options, amount); return res; }, getNamed: function(list, name) { var arg = list[0]; if (list._hasObject === undefined) list._hasObject = list.length === 1 && Base.isPlainObject(arg); if (list._hasObject) return name ? arg[name] : list.__filtered || arg; }, hasNamed: function(list, name) { return !!this.getNamed(list, name); }, filter: function(dest, source, exclude, prioritize) { var processed; function handleKey(key) { if (!(exclude && key in exclude) && !(processed && key in processed)) { var value = source[key]; if (value !== undefined) dest[key] = value; } } if (prioritize) { var keys = {}; for (var i = 0, key, l = prioritize.length; i < l; i++) { if ((key = prioritize[i]) in source) { handleKey(key); keys[key] = true; } } processed = keys; } Object.keys(source.__unfiltered || source).forEach(handleKey); return dest; }, isPlainValue: function(obj, asString) { return Base.isPlainObject(obj) || Array.isArray(obj) || asString && typeof obj === 'string'; }, serialize: function(obj, options, compact, dictionary) { options = options || {}; var isRoot = !dictionary, res; if (isRoot) { options.formatter = new Formatter(options.precision); dictionary = { length: 0, definitions: {}, references: {}, add: function(item, create) { var id = '#' + item._id, ref = this.references[id]; if (!ref) { this.length++; var res = create.call(item), name = item._class; if (name && res[0] !== name) res.unshift(name); this.definitions[id] = res; ref = this.references[id] = [id]; } return ref; } }; } if (obj && obj._serialize) { res = obj._serialize(options, dictionary); var name = obj._class; if (name && !obj._compactSerialize && (isRoot || !compact) && res[0] !== name) { res.unshift(name); } } else if (Array.isArray(obj)) { res = []; for (var i = 0, l = obj.length; i < l; i++) res[i] = Base.serialize(obj[i], options, compact, dictionary); } else if (Base.isPlainObject(obj)) { res = {}; var keys = Object.keys(obj); for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; res[key] = Base.serialize(obj[key], options, compact, dictionary); } } else if (typeof obj === 'number') { res = options.formatter.number(obj, options.precision); } else { res = obj; } return isRoot && dictionary.length > 0 ? [['dictionary', dictionary.definitions], res] : res; }, deserialize: function(json, create, _data, _setDictionary, _isRoot) { var res = json, isFirst = !_data, hasDictionary = isFirst && json && json.length && json[0][0] === 'dictionary'; _data = _data || {}; if (Array.isArray(json)) { var type = json[0], isDictionary = type === 'dictionary'; if (json.length == 1 && /^#/.test(type)) { return _data.dictionary[type]; } type = Base.exports[type]; res = []; for (var i = type ? 1 : 0, l = json.length; i < l; i++) { res.push(Base.deserialize(json[i], create, _data, isDictionary, hasDictionary)); } if (type) { var args = res; if (create) { res = create(type, args, isFirst || _isRoot); } else { res = Base.create(type.prototype); type.apply(res, args); } } } else if (Base.isPlainObject(json)) { res = {}; if (_setDictionary) _data.dictionary = res; for (var key in json) res[key] = Base.deserialize(json[key], create, _data); } return hasDictionary ? res[1] : res; }, exportJSON: function(obj, options) { var json = Base.serialize(obj, options); return options && options.asString == false ? json : JSON.stringify(json); }, importJSON: function(json, target) { return Base.deserialize( typeof json === 'string' ? JSON.parse(json) : json, function(ctor, args, isRoot) { var useTarget = isRoot && target && target.constructor === ctor, obj = useTarget ? target : Base.create(ctor.prototype); if (args.length === 1 && obj instanceof Item && (useTarget || !(obj instanceof Layer))) { var arg = args[0]; if (Base.isPlainObject(arg)) arg.insert = false; } (useTarget ? obj.set : ctor).apply(obj, args); if (useTarget) target = null; return obj; }); }, splice: function(list, items, index, remove) { var amount = items && items.length, append = index === undefined; index = append ? list.length : index; if (index > list.length) index = list.length; for (var i = 0; i < amount; i++) items[i]._index = index + i; if (append) { list.push.apply(list, items); return []; } else { var args = [index, remove]; if (items) args.push.apply(args, items); var removed = list.splice.apply(list, args); for (var i = 0, l = removed.length; i < l; i++) removed[i]._index = undefined; for (var i = index + amount, l = list.length; i < l; i++) list[i]._index = i; return removed; } }, capitalize: function(str) { return str.replace(/\b[a-z]/g, function(match) { return match.toUpperCase(); }); }, camelize: function(str) { return str.replace(/-(.)/g, function(match, chr) { return chr.toUpperCase(); }); }, hyphenate: function(str) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); } }}); var Emitter = { on: function(type, func) { if (typeof type !== 'string') { Base.each(type, function(value, key) { this.on(key, value); }, this); } else { var types = this._eventTypes, entry = types && types[type], handlers = this._callbacks = this._callbacks || {}; handlers = handlers[type] = handlers[type] || []; if (handlers.indexOf(func) === -1) { handlers.push(func); if (entry && entry.install && handlers.length === 1) entry.install.call(this, type); } } return this; }, off: function(type, func) { if (typeof type !== 'string') { Base.each(type, function(value, key) { this.off(key, value); }, this); return; } var types = this._eventTypes, entry = types && types[type], handlers = this._callbacks && this._callbacks[type], index; if (handlers) { if (!func || (index = handlers.indexOf(func)) !== -1 && handlers.length === 1) { if (entry && entry.uninstall) entry.uninstall.call(this, type); delete this._callbacks[type]; } else if (index !== -1) { handlers.splice(index, 1); } } return this; }, once: function(type, func) { return this.on(type, function() { func.apply(this, arguments); this.off(type, func); }); }, emit: function(type, event) { var handlers = this._callbacks && this._callbacks[type]; if (!handlers) return false; var args = Base.slice(arguments, 1), setTarget = event && event.target && !event.currentTarget; handlers = handlers.slice(); if (setTarget) event.currentTarget = this; for (var i = 0, l = handlers.length; i < l; i++) { if (handlers[i].apply(this, args) == false) { if (event && event.stop) event.stop(); break; } } if (setTarget) delete event.currentTarget; return true; }, responds: function(type) { return !!(this._callbacks && this._callbacks[type]); }, attach: '#on', detach: '#off', fire: '#emit', _installEvents: function(install) { var types = this._eventTypes, handlers = this._callbacks, key = install ? 'install' : 'uninstall'; if (types) { for (var type in handlers) { if (handlers[type].length > 0) { var entry = types[type], func = entry && entry[key]; if (func) func.call(this, type); } } } }, statics: { inject: function inject(src) { var events = src._events; if (events) { var types = {}; Base.each(events, function(entry, key) { var isString = typeof entry === 'string', name = isString ? entry : key, part = Base.capitalize(name), type = name.substring(2).toLowerCase(); types[type] = isString ? {} : entry; name = '_' + name; src['get' + part] = function() { return this[name]; }; src['set' + part] = function(func) { var prev = this[name]; if (prev) this.off(type, prev); if (func) this.on(type, func); this[name] = func; }; }); src._eventTypes = types; } return inject.base.apply(this, arguments); } } }; var PaperScope = Base.extend({ _class: 'PaperScope', initialize: function PaperScope() { paper = this; this.settings = new Base({ applyMatrix: true, insertItems: true, handleSize: 4, hitTolerance: 0 }); this.project = null; this.projects = []; this.tools = []; this._id = PaperScope._id++; PaperScope._scopes[this._id] = this; var proto = PaperScope.prototype; if (!this.support) { var ctx = CanvasProvider.getContext(1, 1) || {}; proto.support = { nativeDash: 'setLineDash' in ctx || 'mozDash' in ctx, nativeBlendModes: BlendMode.nativeModes }; CanvasProvider.release(ctx); } if (!this.agent) { var user = self.navigator.userAgent.toLowerCase(), os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0], platform = os === 'darwin' ? 'mac' : os, agent = proto.agent = proto.browser = { platform: platform }; if (platform) agent[platform] = true; user.replace( /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, function(match, n, v1, v2, rv) { if (!agent.chrome) { var v = n === 'opera' ? v2 : /^(node|trident)$/.test(n) ? rv : v1; agent.version = v; agent.versionNumber = parseFloat(v); n = n === 'trident' ? 'msie' : n; agent.name = n; agent[n] = true; } } ); if (agent.chrome) delete agent.webkit; if (agent.atom) delete agent.chrome; } }, version: "0.11.5", getView: function() { var project = this.project; return project && project._view; }, getPaper: function() { return this; }, execute: function(code, options) { paper.PaperScript.execute(code, this, options); View.updateFocus(); }, install: function(scope) { var that = this; Base.each(['project', 'view', 'tool'], function(key) { Base.define(scope, key, { configurable: true, get: function() { return that[key]; } }); }); for (var key in this) if (!/^_/.test(key) && this[key]) scope[key] = this[key]; }, setup: function(element) { paper = this; this.project = new Project(element); return this; }, createCanvas: function(width, height) { return CanvasProvider.getCanvas(width, height); }, activate: function() { paper = this; }, clear: function() { var projects = this.projects, tools = this.tools; for (var i = projects.length - 1; i >= 0; i--) projects[i].remove(); for (var i = tools.length - 1; i >= 0; i--) tools[i].remove(); }, remove: function() { this.clear(); delete PaperScope._scopes[this._id]; }, statics: new function() { function handleAttribute(name) { name += 'Attribute'; return function(el, attr) { return el[name](attr) || el[name]('data-paper-' + attr); }; } return { _scopes: {}, _id: 0, get: function(id) { return this._scopes[id] || null; }, getAttribute: handleAttribute('get'), hasAttribute: handleAttribute('has') }; } }); var PaperScopeItem = Base.extend(Emitter, { initialize: function(activate) { this._scope = paper; this._index = this._scope[this._list].push(this) - 1; if (activate || !this._scope[this._reference]) this.activate(); }, activate: function() { if (!this._scope) return false; var prev = this._scope[this._reference]; if (prev && prev !== this) prev.emit('deactivate'); this._scope[this._reference] = this; this.emit('activate', prev); return true; }, isActive: function() { return this._scope[this._reference] === this; }, remove: function() { if (this._index == null) return false; Base.splice(this._scope[this._list], null, this._index, 1); if (this._scope[this._reference] == this) this._scope[this._reference] = null; this._scope = null; return true; }, getView: function() { return this._scope.getView(); } }); var Formatter = Base.extend({ initialize: function(precision) { this.precision = Base.pick(precision, 5); this.multiplier = Math.pow(10, this.precision); }, number: function(val) { return this.precision < 16 ? Math.round(val * this.multiplier) / this.multiplier : val; }, pair: function(val1, val2, separator) { return this.number(val1) + (separator || ',') + this.number(val2); }, point: function(val, separator) { return this.number(val.x) + (separator || ',') + this.number(val.y); }, size: function(val, separator) { return this.number(val.width) + (separator || ',') + this.number(val.height); }, rectangle: function(val, separator) { return this.point(val, separator) + (separator || ',') + this.size(val, separator); } }); Formatter.instance = new Formatter(); var Numerical = new function() { var abscissas = [ [ 0.5773502691896257645091488], [0,0.7745966692414833770358531], [ 0.3399810435848562648026658,0.8611363115940525752239465], [0,0.5384693101056830910363144,0.9061798459386639927976269], [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016], [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897], [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609], [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762], [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640], [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380], [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491], [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294], [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973], [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657], [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542] ]; var weights = [ [1], [0.8888888888888888888888889,0.5555555555555555555555556], [0.6521451548625461426269361,0.3478548451374538573730639], [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640], [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961], [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114], [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314], [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922], [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688], [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537], [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160], [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216], [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329], [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284], [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806] ]; var abs = Math.abs, sqrt = Math.sqrt, pow = Math.pow, log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }, EPSILON = 1e-12, MACHINE_EPSILON = 1.12e-16; function clamp(value, min, max) { return value < min ? min : value > max ? max : value; } function getDiscriminant(a, b, c) { function split(v) { var x = v * 134217729, y = v - x, hi = y + x, lo = v - hi; return [hi, lo]; } var D = b * b - a * c, E = b * b + a * c; if (abs(D) * 3 < E) { var ad = split(a), bd = split(b), cd = split(c), p = b * b, dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1], q = a * c, dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0]) + ad[1] * cd[1]; D = (p - q) + (dp - dq); } return D; } function getNormalizationFactor() { var norm = Math.max.apply(Math, arguments); return norm && (norm < 1e-8 || norm > 1e8) ? pow(2, -Math.round(log2(norm))) : 0; } return { EPSILON: EPSILON, MACHINE_EPSILON: MACHINE_EPSILON, CURVETIME_EPSILON: 1e-8, GEOMETRIC_EPSILON: 1e-7, TRIGONOMETRIC_EPSILON: 1e-8, KAPPA: 4 * (sqrt(2) - 1) / 3, isZero: function(val) { return val >= -EPSILON && val <= EPSILON; }, clamp: clamp, integrate: function(f, a, b, n) { var x = abscissas[n - 2], w = weights[n - 2], A = (b - a) * 0.5, B = A + a, i = 0, m = (n + 1) >> 1, sum = n & 1 ? w[i++] * f(B) : 0; while (i < m) { var Ax = A * x[i]; sum += w[i++] * (f(B + Ax) + f(B - Ax)); } return A * sum; }, findRoot: function(f, df, x, a, b, n, tolerance) { for (var i = 0; i < n; i++) { var fx = f(x), dx = fx / df(x), nx = x - dx; if (abs(dx) < tolerance) { x = nx; break; } if (fx > 0) { b = x; x = nx <= a ? (a + b) * 0.5 : nx; } else { a = x; x = nx >= b ? (a + b) * 0.5 : nx; } } return clamp(x, a, b); }, solveQuadratic: function(a, b, c, roots, min, max) { var x1, x2 = Infinity; if (abs(a) < EPSILON) { if (abs(b) < EPSILON) return abs(c) < EPSILON ? -1 : 0; x1 = -c / b; } else { b *= -0.5; var D = getDiscriminant(a, b, c); if (D && abs(D) < MACHINE_EPSILON) { var f = getNormalizationFactor(abs(a), abs(b), abs(c)); if (f) { a *= f; b *= f; c *= f; D = getDiscriminant(a, b, c); } } if (D >= -MACHINE_EPSILON) { var Q = D < 0 ? 0 : sqrt(D), R = b + (b < 0 ? -Q : Q); if (R === 0) { x1 = c / a; x2 = -x1; } else { x1 = R / a; x2 = c / R; } } } var count = 0, boundless = min == null, minB = min - EPSILON, maxB = max + EPSILON; if (isFinite(x1) && (boundless || x1 > minB && x1 < maxB)) roots[count++] = boundless ? x1 : clamp(x1, min, max); if (x2 !== x1 && isFinite(x2) && (boundless || x2 > minB && x2 < maxB)) roots[count++] = boundless ? x2 : clamp(x2, min, max); return count; }, solveCubic: function(a, b, c, d, roots, min, max) { var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)), x, b1, c2, qd, q; if (f) { a *= f; b *= f; c *= f; d *= f; } function evaluate(x0) { x = x0; var tmp = a * x; b1 = tmp + b; c2 = b1 * x + c; qd = (tmp + b1) * x + c2; q = c2 * x + d; } if (abs(a) < EPSILON) { a = b; b1 = c; c2 = d; x = Infinity; } else if (abs(d) < EPSILON) { b1 = b; c2 = c; x = 0; } else { evaluate(-(b / a) / 3); var t = q / a, r = pow(abs(t), 1/3), s = t < 0 ? -1 : 1, td = -qd / a, rd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r, x0 = x - s * rd; if (x0 !== x) { do { evaluate(x0); x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON); } while (s * x0 > s * x); if (abs(a) * x * x > abs(d / x)) { c2 = -d / x; b1 = (c2 - c) / x; } } } var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max), boundless = min == null; if (isFinite(x) && (count === 0 || count > 0 && x !== roots[0] && x !== roots[1]) && (boundless || x > min - EPSILON && x < max + EPSILON)) roots[count++] = boundless ? x : clamp(x, min, max); return count; } }; }; var UID = { _id: 1, _pools: {}, get: function(name) { if (name) { var pool = this._pools[name]; if (!pool) pool = this._pools[name] = { _id: 1 }; return pool._id++; } else { return this._id++; } } }; var Point = Base.extend({ _class: 'Point', _readIndex: true, initialize: function Point(arg0, arg1) { var type = typeof arg0, reading = this.__read, read = 0; if (type === 'number') { var hasY = typeof arg1 === 'number'; this._set(arg0, hasY ? arg1 : arg0); if (reading) read = hasY ? 2 : 1; } else if (type === 'undefined' || arg0 === null) { this._set(0, 0); if (reading) read = arg0 === null ? 1 : 0; } else { var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0; read = 1; if (Array.isArray(obj)) { this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0])); } else if ('x' in obj) { this._set(obj.x || 0, obj.y || 0); } else if ('width' in obj) { this._set(obj.width || 0, obj.height || 0); } else if ('angle' in obj) { this._set(obj.length || 0, 0); this.setAngle(obj.angle || 0); } else { this._set(0, 0); read = 0; } } if (reading) this.__read = read; return this; }, set: '#initialize', _set: function(x, y) { this.x = x; this.y = y; return this; }, equals: function(point) { return this === point || point && (this.x === point.x && this.y === point.y || Array.isArray(point) && this.x === point[0] && this.y === point[1]) || false; }, clone: function() { return new Point(this.x, this.y); }, toString: function() { var f = Formatter.instance; return '{ x: ' + f.number(this.x) + ', y: ' + f.number(this.y) + ' }'; }, _serialize: function(options) { var f = options.formatter; return [f.number(this.x), f.number(this.y)]; }, getLength: function() { return Math.sqrt(this.x * this.x + this.y * this.y); }, setLength: function(length) { if (this.isZero()) { var angle = this._angle || 0; this._set( Math.cos(angle) * length, Math.sin(angle) * length ); } else { var scale = length / this.getLength(); if (Numerical.isZero(scale)) this.getAngle(); this._set( this.x * scale, this.y * scale ); } }, getAngle: function() { return this.getAngleInRadians.apply(this, arguments) * 180 / Math.PI; }, setAngle: function(angle) { this.setAngleInRadians.call(this, angle * Math.PI / 180); }, getAngleInDegrees: '#getAngle', setAngleInDegrees: '#setAngle', getAngleInRadians: function() { if (!arguments.length) { return this.isZero() ? this._angle || 0 : this._angle = Math.atan2(this.y, this.x); } else { var point = Point.read(arguments), div = this.getLength() * point.getLength(); if (Numerical.isZero(div)) { return NaN; } else { var a = this.dot(point) / div; return Math.acos(a < -1 ? -1 : a > 1 ? 1 : a); } } }, setAngleInRadians: function(angle) { this._angle = angle; if (!this.isZero()) { var length = this.getLength(); this._set( Math.cos(angle) * length, Math.sin(angle) * length ); } }, getQuadrant: function() { return this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3; } }, { beans: false, getDirectedAngle: function() { var point = Point.read(arguments); return Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI; }, getDistance: function() { var point = Point.read(arguments), x = point.x - this.x, y = point.y - this.y, d = x * x + y * y, squared = Base.read(arguments); return squared ? d : Math.sqrt(d); }, normalize: function(length) { if (length === undefined) length = 1; var current = this.getLength(), scale = current !== 0 ? length / current : 0, point = new Point(this.x * scale, this.y * scale); if (scale >= 0) point._angle = this._angle; return point; }, rotate: function(angle, center) { if (angle === 0) return this.clone(); angle = angle * Math.PI / 180; var point = center ? this.subtract(center) : this, sin = Math.sin(angle), cos = Math.cos(angle); point = new Point( point.x * cos - point.y * sin, point.x * sin + point.y * cos ); return center ? point.add(center) : point; }, transform: function(matrix) { return matrix ? matrix._transformPoint(this) : this; }, add: function() { var point = Point.read(arguments); return new Point(this.x + point.x, this.y + point.y); }, subtract: function() { var point = Point.read(arguments); return new Point(this.x - point.x, this.y - point.y); }, multiply: function() { var point = Point.read(arguments); return new Point(this.x * point.x, this.y * point.y); }, divide: function() { var point = Point.read(arguments); return new Point(this.x / point.x, this.y / point.y); }, modulo: function() { var point = Point.read(arguments); return new Point(this.x % point.x, this.y % point.y); }, negate: function() { return new Point(-this.x, -this.y); }, isInside: function() { return Rectangle.read(arguments).contains(this); }, isClose: function() { var point = Point.read(arguments), tolerance = Base.read(arguments); return this.getDistance(point) <= tolerance; }, isCollinear: function() { var point = Point.read(arguments); return Point.isCollinear(this.x, this.y, point.x, point.y); }, isColinear: '#isCollinear', isOrthogonal: function() { var point = Point.read(arguments); return Point.isOrthogonal(this.x, this.y, point.x, point.y); }, isZero: function() { var isZero = Numerical.isZero; return isZero(this.x) && isZero(this.y); }, isNaN: function() { return isNaN(this.x) || isNaN(this.y); }, isInQuadrant: function(q) { return this.x * (q > 1 && q < 4 ? -1 : 1) >= 0 && this.y * (q > 2 ? -1 : 1) >= 0; }, dot: function() { var point = Point.read(arguments); return this.x * point.x + this.y * point.y; }, cross: function() { var point = Point.read(arguments); return this.x * point.y - this.y * point.x; }, project: function() { var point = Point.read(arguments), scale = point.isZero() ? 0 : this.dot(point) / point.dot(point); return new Point( point.x * scale, point.y * scale ); }, statics: { min: function() { var point1 = Point.read(arguments), point2 = Point.read(arguments); return new Point( Math.min(point1.x, point2.x), Math.min(point1.y, point2.y) ); }, max: function() { var point1 = Point.read(arguments), point2 = Point.read(arguments); return new Point( Math.max(point1.x, point2.x), Math.max(point1.y, point2.y) ); }, random: function() { return new Point(Math.random(), Math.random()); }, isCollinear: function(x1, y1, x2, y2) { return Math.abs(x1 * y2 - y1 * x2) <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) * 1e-8; }, isOrthogonal: function(x1, y1, x2, y2) { return Math.abs(x1 * x2 + y1 * y2) <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) * 1e-8; } } }, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) { var op = Math[key]; this[key] = function() { return new Point(op(this.x), op(this.y)); }; }, {})); var LinkedPoint = Point.extend({ initialize: function Point(x, y, owner, setter) { this._x = x; this._y = y; this._owner = owner; this._setter = setter; }, _set: function(x, y, _dontNotify) { this._x = x; this._y = y; if (!_dontNotify) this._owner[this._setter](this); return this; }, getX: function() { return this._x; }, setX: function(x) { this._x = x; this._owner[this._setter](this); }, getY: function() { return this._y; }, setY: function(y) { this._y = y; this._owner[this._setter](this); }, isSelected: function() { return !!(this._owner._selection & this._getSelection()); }, setSelected: function(selected) { this._owner._changeSelection(this._getSelection(), selected); }, _getSelection: function() { return this._setter === 'setPosition' ? 4 : 0; } }); var Size = Base.extend({ _class: 'Size', _readIndex: true, initialize: function Size(arg0, arg1) { var type = typeof arg0, reading = this.__read, read = 0; if (type === 'number') { var hasHeight = typeof arg1 === 'number'; this._set(arg0, hasHeight ? arg1 : arg0); if (reading) read = hasHeight ? 2 : 1; } else if (type === 'undefined' || arg0 === null) { this._set(0, 0); if (reading) read = arg0 === null ? 1 : 0; } else { var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0; read = 1; if (Array.isArray(obj)) { this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0])); } else if ('width' in obj) { this._set(obj.width || 0, obj.height || 0); } else if ('x' in obj) { this._set(obj.x || 0, obj.y || 0); } else { this._set(0, 0); read = 0; } } if (reading) this.__read = read; return this; }, set: '#initialize', _set: function(width, height) { this.width = width; this.height = height; return this; }, equals: function(size) { return size === this || size && (this.width === size.width && this.height === size.height || Array.isArray(size) && this.width === size[0] && this.height === size[1]) || false; }, clone: function() { return new Size(this.width, this.height); }, toString: function() { var f = Formatter.instance; return '{ width: ' + f.number(this.width) + ', height: ' + f.number(this.height) + ' }'; }, _serialize: function(options) { var f = options.formatter; return [f.number(this.width), f.number(this.height)]; }, add: function() { var size = Size.read(arguments); return new Size(this.width + size.width, this.height + size.height); }, subtract: function() { var size = Size.read(arguments); return new Size(this.width - size.width, this.height - size.height); }, multiply: function() { var size = Size.read(arguments); return new Size(this.width * size.width, this.height * size.height); }, divide: function() { var size = Size.read(arguments); return new Size(this.width / size.width, this.height / size.height); }, modulo: function() { var size = Size.read(arguments); return new Size(this.width % size.width, this.height % size.height); }, negate: function() { return new Size(-this.width, -this.height); }, isZero: function() { var isZero = Numerical.isZero; return isZero(this.width) && isZero(this.height); }, isNaN: function() { return isNaN(this.width) || isNaN(this.height); }, statics: { min: function(size1, size2) { return new Size( Math.min(size1.width, size2.width), Math.min(size1.height, size2.height)); }, max: function(size1, size2) { return new Size( Math.max(size1.width, size2.width), Math.max(size1.height, size2.height)); }, random: function() { return new Size(Math.random(), Math.random()); } } }, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) { var op = Math[key]; this[key] = function() { return new Size(op(this.width), op(this.height)); }; }, {})); var LinkedSize = Size.extend({ initialize: function Size(width, height, owner, setter) { this._width = width; this._height = height; this._owner = owner; this._setter = setter; }, _set: function(width, height, _dontNotify) { this._width = width; this._height = height; if (!_dontNotify) this._owner[this._setter](this); return this; }, getWidth: function() { return this._width; }, setWidth: function(width) { this._width = width; this._owner[this._setter](this); }, getHeight: function() { return this._height; }, setHeight: function(height) { this._height = height; this._owner[this._setter](this); } }); var Rectangle = Base.extend({ _class: 'Rectangle', _readIndex: true, beans: true, initialize: function Rectangle(arg0, arg1, arg2, arg3) { var type = typeof arg0, read; if (type === 'number') { this._set(arg0, arg1, arg2, arg3); read = 4; } else if (type === 'undefined' || arg0 === null) { this._set(0, 0, 0, 0); read = arg0 === null ? 1 : 0; } else if (arguments.length === 1) { if (Array.isArray(arg0)) { this._set.apply(this, arg0); read = 1; } else if (arg0.x !== undefined || arg0.width !== undefined) { this._set(arg0.x || 0, arg0.y || 0, arg0.width || 0, arg0.height || 0); read = 1; } else if (arg0.from === undefined && arg0.to === undefined) { this._set(0, 0, 0, 0); Base.filter(this, arg0); read = 1; } } if (read === undefined) { var frm = Point.readNamed(arguments, 'from'), next = Base.peek(arguments), x = frm.x, y = frm.y, width, height; if (next && next.x !== undefined || Base.hasNamed(arguments, 'to')) { var to = Point.readNamed(arguments, 'to'); width = to.x - x; height = to.y - y; if (width < 0) { x = to.x; width = -width; } if (height < 0) { y = to.y; height = -height; } } else { var size = Size.read(arguments); width = size.width; height = size.height; } this._set(x, y, width, height); read = arguments.__index; var filtered = arguments.__filtered; if (filtered) this.__filtered = filtered; } if (this.__read) this.__read = read; return this; }, set: '#initialize', _set: function(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; return this; }, clone: function() { return new Rectangle(this.x, this.y, this.width, this.height); }, equals: function(rect) { var rt = Base.isPlainValue(rect) ? Rectangle.read(arguments) : rect; return rt === this || rt && this.x === rt.x && this.y === rt.y && this.width === rt.width && this.height === rt.height || false; }, toString: function() { var f = Formatter.instance; return '{ x: ' + f.number(this.x) + ', y: ' + f.number(this.y) + ', width: ' + f.number(this.width) + ', height: ' + f.number(this.height) + ' }'; }, _serialize: function(options) { var f = options.formatter; return [f.number(this.x), f.number(this.y), f.number(this.width), f.number(this.height)]; }, getPoint: function(_dontLink) { var ctor = _dontLink ? Point : LinkedPoint; return new ctor(this.x, this.y, this, 'setPoint'); }, setPoint: function() { var point = Point.read(arguments); this.x = point.x; this.y = point.y; }, getSize: function(_dontLink) { var ctor = _dontLink ? Size : LinkedSize; return new ctor(this.width, this.height, this, 'setSize'); }, _fw: 1, _fh: 1, setSize: function() { var size = Size.read(arguments), sx = this._sx, sy = this._sy, w = size.width, h = size.height; if (sx) { this.x += (this.width - w) * sx; } if (sy) { this.y += (this.height - h) * sy; } this.width = w; this.height = h; this._fw = this._fh = 1; }, getLeft: function() { return this.x; }, setLeft: function(left) { if (!this._fw) { var amount = left - this.x; this.width -= this._sx === 0.5 ? amount * 2 : amount; } this.x = left; this._sx = this._fw = 0; }, getTop: function() { return this.y; }, setTop: function(top) { if (!this._fh) { var amount = top - this.y; this.height -= this._sy === 0.5 ? amount * 2 : amount; } this.y = top; this._sy = this._fh = 0; }, getRight: function() { return this.x + this.width; }, setRight: function(right) { if (!this._fw) { var amount = right - this.x; this.width = this._sx === 0.5 ? amount * 2 : amount; } this.x = right - this.width; this._sx = 1; this._fw = 0; }, getBottom: function() { return this.y + this.height; }, setBottom: function(bottom) { if (!this._fh) { var amount = bottom - this.y; this.height = this._sy === 0.5 ? amount * 2 : amount; } this.y = bottom - this.height; this._sy = 1; this._fh = 0; }, getCenterX: function() { return this.x + this.width / 2; }, setCenterX: function(x) { if (this._fw || this._sx === 0.5) { this.x = x - this.width / 2; } else { if (this._sx) { this.x += (x - this.x) * 2 * this._sx; } this.width = (x - this.x) * 2; } this._sx = 0.5; this._fw = 0; }, getCenterY: function() { return this.y + this.height / 2; }, setCenterY: function(y) { if (this._fh || this._sy === 0.5) { this.y = y - this.height / 2; } else { if (this._sy) { this.y += (y - this.y) * 2 * this._sy; } this.height = (y - this.y) * 2; } this._sy = 0.5; this._fh = 0; }, getCenter: function(_dontLink) { var ctor = _dontLink ? Point : LinkedPoint; return new ctor(this.getCenterX(), this.getCenterY(), this, 'setCenter'); }, setCenter: function() { var point = Point.read(arguments); this.setCenterX(point.x); this.setCenterY(point.y); return this; }, getArea: function() { return this.width * this.height; }, isEmpty: function() { return this.width === 0 || this.height === 0; }, contains: function(arg) { return arg && arg.width !== undefined || (Array.isArray(arg) ? arg : arguments).length === 4 ? this._containsRectangle(Rectangle.read(arguments)) : this._containsPoint(Point.read(arguments)); }, _containsPoint: function(point) { var x = point.x, y = point.y; return x >= this.x && y >= this.y && x <= this.x + this.width && y <= this.y + this.height; }, _containsRectangle: function(rect) { var x = rect.x, y = rect.y; return x >= this.x && y >= this.y && x + rect.width <= this.x + this.width && y + rect.height <= this.y + this.height; }, intersects: function() { var rect = Rectangle.read(arguments), epsilon = Base.read(arguments) || 0; return rect.x + rect.width > this.x - epsilon && rect.y + rect.height > this.y - epsilon && rect.x < this.x + this.width + epsilon && rect.y < this.y + this.height + epsilon; }, intersect: function() { var rect = Rectangle.read(arguments), x1 = Math.max(this.x, rect.x), y1 = Math.max(this.y, rect.y), x2 = Math.min(this.x + this.width, rect.x + rect.width), y2 = Math.min(this.y + this.height, rect.y + rect.height); return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, unite: function() { var rect = Rectangle.read(arguments), x1 = Math.min(this.x, rect.x), y1 = Math.min(this.y, rect.y), x2 = Math.max(this.x + this.width, rect.x + rect.width), y2 = Math.max(this.y + this.height, rect.y + rect.height); return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, include: function() { var point = Point.read(arguments); var x1 = Math.min(this.x, point.x), y1 = Math.min(this.y, point.y), x2 = Math.max(this.x + this.width, point.x), y2 = Math.max(this.y + this.height, point.y); return new Rectangle(x1, y1, x2 - x1, y2 - y1); }, expand: function() { var amount = Size.read(arguments), hor = amount.width, ver = amount.height; return new Rectangle(this.x - hor / 2, this.y - ver / 2, this.width + hor, this.height + ver); }, scale: function(hor, ver) { return this.expand(this.width * hor - this.width, this.height * (ver === undefined ? hor : ver) - this.height); } }, Base.each([ ['Top', 'Left'], ['Top', 'Right'], ['Bottom', 'Left'], ['Bottom', 'Right'], ['Left', 'Center'], ['Top', 'Center'], ['Right', 'Center'], ['Bottom', 'Center'] ], function(parts, index) { var part = parts.join(''), xFirst = /^[RL]/.test(part); if (index >= 4) parts[1] += xFirst ? 'Y' : 'X'; var x = parts[xFirst ? 0 : 1], y = parts[xFirst ? 1 : 0], getX = 'get' + x, getY = 'get' + y, setX = 'set' + x, setY = 'set' + y, get = 'get' + part, set = 'set' + part; this[get] = function(_dontLink) { var ctor = _dontLink ? Point : LinkedPoint; return new ctor(this[getX](), this[getY](), this, set); }; this[set] = function() { var point = Point.read(arguments); this[setX](point.x); this[setY](point.y); }; }, { beans: true } )); var LinkedRectangle = Rectangle.extend({ initialize: function Rectangle(x, y, width, height, owner, setter) { this._set(x, y, width, height, true); this._owner = owner; this._setter = setter; }, _set: function(x, y, width, height, _dontNotify) { this._x = x; this._y = y; this._width = width; this._height = height; if (!_dontNotify) this._owner[this._setter](this); return this; } }, new function() { var proto = Rectangle.prototype; return Base.each(['x', 'y', 'width', 'height'], function(key) { var part = Base.capitalize(key), internal = '_' + key; this['get' + part] = function() { return this[internal]; }; this['set' + part] = function(value) { this[internal] = value; if (!this._dontNotify) this._owner[this._setter](this); }; }, Base.each(['Point', 'Size', 'Center', 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY', 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], function(key) { var name = 'set' + key; this[name] = function() { this._dontNotify = true; proto[name].apply(this, arguments); this._dontNotify = false; this._owner[this._setter](this); }; }, { isSelected: function() { return !!(this._owner._selection & 2); }, setSelected: function(selected) { var owner = this._owner; if (owner._changeSelection) { owner._changeSelection(2, selected); } } }) ); }); var Matrix = Base.extend({ _class: 'Matrix', initialize: function Matrix(arg, _dontNotify) { var count = arguments.length, ok = true; if (count >= 6) { this._set.apply(this, arguments); } else if (count === 1 || count === 2) { if (arg instanceof Matrix) { this._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty, _dontNotify); } else if (Array.isArray(arg)) { this._set.apply(this, _dontNotify ? arg.concat([_dontNotify]) : arg); } else { ok = false; } } else if (!count) { this.reset(); } else { ok = false; } if (!ok) { throw new Error('Unsupported matrix parameters'); } return this; }, set: '#initialize', _set: function(a, b, c, d, tx, ty, _dontNotify) { this._a = a; this._b = b; this._c = c; this._d = d; this._tx = tx; this._ty = ty; if (!_dontNotify) this._changed(); return this; }, _serialize: function(options, dictionary) { return Base.serialize(this.getValues(), options, true, dictionary); }, _changed: function() { var owner = this._owner; if (owner) { if (owner._applyMatrix) { owner.transform(null, true); } else { owner._changed(9); } } }, clone: function() { return new Matrix(this._a, this._b, this._c, this._d, this._tx, this._ty); }, equals: function(mx) { return mx === this || mx && this._a === mx._a && this._b === mx._b && this._c === mx._c && this._d === mx._d && this._tx === mx._tx && this._ty === mx._ty; }, toString: function() { var f = Formatter.instance; return '[[' + [f.number(this._a), f.number(this._c), f.number(this._tx)].join(', ') + '], [' + [f.number(this._b), f.number(this._d), f.number(this._ty)].join(', ') + ']]'; }, reset: function(_dontNotify) { this._a = this._d = 1; this._b = this._c = this._tx = this._ty = 0; if (!_dontNotify) this._changed(); return this; }, apply: function(recursively, _setApplyMatrix) { var owner = this._owner; if (owner) { owner.transform(null, true, Base.pick(recursively, true), _setApplyMatrix); return this.isIdentity(); } return false; }, translate: function() { var point = Point.read(arguments), x = point.x, y = point.y; this._tx += x * this._a + y * this._c; this._ty += x * this._b + y * this._d; this._changed(); return this; }, scale: function() { var scale = Point.read(arguments), center = Point.read(arguments, 0, { readNull: true }); if (center) this.translate(center); this._a *= scale.x; this._b *= scale.x; this._c *= scale.y; this._d *= scale.y; if (center) this.translate(center.negate()); this._changed(); return this; }, rotate: function(angle ) { angle *= Math.PI / 180; var center = Point.read(arguments, 1), x = center.x, y = center.y, cos = Math.cos(angle), sin = Math.sin(angle), tx = x - x * cos + y * sin, ty = y - x * sin - y * cos, a = this._a, b = this._b, c = this._c, d = this._d; this._a = cos * a + sin * c; this._b = cos * b + sin * d; this._c = -sin * a + cos * c; this._d = -sin * b + cos * d; this._tx += tx * a + ty * c; this._ty += tx * b + ty * d; this._changed(); return this; }, shear: function() { var shear = Point.read(arguments), center = Point.read(arguments, 0, { readNull: true }); if (center) this.translate(center); var a = this._a, b = this._b; this._a += shear.y * this._c; this._b += shear.y * this._d; this._c += shear.x * a; this._d += shear.x * b; if (center) this.translate(center.negate()); this._changed(); return this; }, skew: function() { var skew = Point.read(arguments), center = Point.read(arguments, 0, { readNull: true }), toRadians = Math.PI / 180, shear = new Point(Math.tan(skew.x * toRadians), Math.tan(skew.y * toRadians)); return this.shear(shear, center); }, append: function(mx, _dontNotify) { if (mx) { var a1 = this._a, b1 = this._b, c1 = this._c, d1 = this._d, a2 = mx._a, b2 = mx._c, c2 = mx._b, d2 = mx._d, tx2 = mx._tx, ty2 = mx._ty; this._a = a2 * a1 + c2 * c1; this._c = b2 * a1 + d2 * c1; this._b = a2 * b1 + c2 * d1; this._d = b2 * b1 + d2 * d1; this._tx += tx2 * a1 + ty2 * c1; this._ty += tx2 * b1 + ty2 * d1; if (!_dontNotify) this._changed(); } return this; }, prepend: function(mx, _dontNotify) { if (mx) { var a1 = this._a, b1 = this._b, c1 = this._c, d1 = this._d, tx1 = this._tx, ty1 = this._ty, a2 = mx._a, b2 = mx._c, c2 = mx._b, d2 = mx._d, tx2 = mx._tx, ty2 = mx._ty; this._a = a2 * a1 + b2 * b1; this._c = a2 * c1 + b2 * d1; this._b = c2 * a1 + d2 * b1; this._d = c2 * c1 + d2 * d1; this._tx = a2 * tx1 + b2 * ty1 + tx2; this._ty = c2 * tx1 + d2 * ty1 + ty2; if (!_dontNotify) this._changed(); } return this; }, appended: function(mx) { return this.clone().append(mx); }, prepended: function(mx) { return this.clone().prepend(mx); }, invert: function() { var a = this._a, b = this._b, c = this._c, d = this._d, tx = this._tx, ty = this._ty, det = a * d - b * c, res = null; if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) { this._a = d / det; this._b = -b / det; this._c = -c / det; this._d = a / det; this._tx = (c * ty - d * tx) / det; this._ty = (b * tx - a * ty) / det; res = this; } return res; }, inverted: function() { return this.clone().invert(); }, concatenate: '#append', preConcatenate: '#prepend', chain: '#appended', _shiftless: function() { return new Matrix(this._a, this._b, this._c, this._d, 0, 0); }, _orNullIfIdentity: function() { return this.isIdentity() ? null : this; }, isIdentity: function() { return this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1 && this._tx === 0 && this._ty === 0; }, isInvertible: function() { var det = this._a * this._d - this._c * this._b; return det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty); }, isSingular: function() { return !this.isInvertible(); }, transform: function( src, dst, count) { return arguments.length < 3 ? this._transformPoint(Point.read(arguments)) : this._transformCoordinates(src, dst, count); }, _transformPoint: function(point, dest, _dontNotify) { var x = point.x, y = point.y; if (!dest) dest = new Point(); return dest._set( x * this._a + y * this._c + this._tx, x * this._b + y * this._d + this._ty, _dontNotify); }, _transformCoordinates: function(src, dst, count) { for (var i = 0, max = 2 * count; i < max; i += 2) { var x = src[i], y = src[i + 1]; dst[i] = x * this._a + y * this._c + this._tx; dst[i + 1] = x * this._b + y * this._d + this._ty; } return dst; }, _transformCorners: function(rect) { var x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height, coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ]; return this._transformCoordinates(coords, coords, 4); }, _transformBounds: function(bounds, dest, _dontNotify) { var coords = this._transformCorners(bounds), min = coords.slice(0, 2), max = min.slice(); for (var i = 2; i < 8; i++) { var val = coords[i], j = i & 1; if (val < min[j]) { min[j] = val; } else if (val > max[j]) { max[j] = val; } } if (!dest) dest = new Rectangle(); return dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1], _dontNotify); }, inverseTransform: function() { return this._inverseTransform(Point.read(arguments)); }, _inverseTransform: function(point, dest, _dontNotify) { var a = this._a, b = this._b, c = this._c, d = this._d, tx = this._tx, ty = this._ty, det = a * d - b * c, res = null; if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) { var x = point.x - this._tx, y = point.y - this._ty; if (!dest) dest = new Point(); res = dest._set( (x * d - y * c) / det, (y * a - x * b) / det, _dontNotify); } return res; }, decompose: function() { var a = this._a, b = this._b, c = this._c, d = this._d, det = a * d - b * c, sqrt = Math.sqrt, atan2 = Math.atan2, degrees = 180 / Math.PI, rotate, scale, skew; if (a !== 0 || b !== 0) { var r = sqrt(a * a + b * b); rotate = Math.acos(a / r) * (b > 0 ? 1 : -1); scale = [r, det / r]; skew = [atan2(a * c + b * d, r * r), 0]; } else if (c !== 0 || d !== 0) { var s = sqrt(c * c + d * d); rotate = Math.asin(c / s) * (d > 0 ? 1 : -1); scale = [det / s, s]; skew = [0, atan2(a * c + b * d, s * s)]; } else { rotate = 0; skew = scale = [0, 0]; } return { translation: this.getTranslation(), rotation: rotate * degrees, scaling: new Point(scale), skewing: new Point(skew[0] * degrees, skew[1] * degrees) }; }, getValues: function() { return [ this._a, this._b, this._c, this._d, this._tx, this._ty ]; }, getTranslation: function() { return new Point(this._tx, this._ty); }, getScaling: function() { return (this.decompose() || {}).scaling; }, getRotation: function() { return (this.decompose() || {}).rotation; }, applyToContext: function(ctx) { if (!this.isIdentity()) { ctx.transform(this._a, this._b, this._c, this._d, this._tx, this._ty); } } }, Base.each(['a', 'b', 'c', 'd', 'tx', 'ty'], function(key) { var part = Base.capitalize(key), prop = '_' + key; this['get' + part] = function() { return this[prop]; }; this['set' + part] = function(value) { this[prop] = value; this._changed(); }; }, {})); var Line = Base.extend({ _class: 'Line', initialize: function Line(arg0, arg1, arg2, arg3, arg4) { var asVector = false; if (arguments.length >= 4) { this._px = arg0; this._py = arg1; this._vx = arg2; this._vy = arg3; asVector = arg4; } else { this._px = arg0.x; this._py = arg0.y; this._vx = arg1.x; this._vy = arg1.y; asVector = arg2; } if (!asVector) { this._vx -= this._px; this._vy -= this._py; } }, getPoint: function() { return new Point(this._px, this._py); }, getVector: function() { return new Point(this._vx, this._vy); }, getLength: function() { return this.getVector().getLength(); }, intersect: function(line, isInfinite) { return Line.intersect( this._px, this._py, this._vx, this._vy, line._px, line._py, line._vx, line._vy, true, isInfinite); }, getSide: function(point, isInfinite) { return Line.getSide( this._px, this._py, this._vx, this._vy, point.x, point.y, true, isInfinite); }, getDistance: function(point) { return Math.abs(this.getSignedDistance(point)); }, getSignedDistance: function(point) { return Line.getSignedDistance(this._px, this._py, this._vx, this._vy, point.x, point.y, true); }, isCollinear: function(line) { return Point.isCollinear(this._vx, this._vy, line._vx, line._vy); }, isOrthogonal: function(line) { return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy); }, statics: { intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector, isInfinite) { if (!asVector) { v1x -= p1x; v1y -= p1y; v2x -= p2x; v2y -= p2y; } var cross = v1x * v2y - v1y * v2x; if (!Numerical.isZero(cross)) { var dx = p1x - p2x, dy = p1y - p2y, u1 = (v2x * dy - v2y * dx) / cross, u2 = (v1x * dy - v1y * dx) / cross, epsilon = 1e-12, uMin = -epsilon, uMax = 1 + epsilon; if (isInfinite || uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) { if (!isInfinite) { u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1; } return new Point( p1x + u1 * v1x, p1y + u1 * v1y); } } }, getSide: function(px, py, vx, vy, x, y, asVector, isInfinite) { if (!asVector) { vx -= px; vy -= py; } var v2x = x - px, v2y = y - py, ccw = v2x * vy - v2y * vx; if (!isInfinite && Numerical.isZero(ccw)) { ccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy); if (ccw >= 0 && ccw <= 1) ccw = 0; } return ccw < 0 ? -1 : ccw > 0 ? 1 : 0; }, getSignedDistance: function(px, py, vx, vy, x, y, asVector) { if (!asVector) { vx -= px; vy -= py; } return vx === 0 ? vy > 0 ? x - px : px - x : vy === 0 ? vx < 0 ? y - py : py - y : ((x-px) * vy - (y-py) * vx) / Math.sqrt(vx * vx + vy * vy); }, getDistance: function(px, py, vx, vy, x, y, asVector) { return Math.abs( Line.getSignedDistance(px, py, vx, vy, x, y, asVector)); } } }); var Project = PaperScopeItem.extend({ _class: 'Project', _list: 'projects', _reference: 'project', _compactSerialize: true, initialize: function Project(element) { PaperScopeItem.call(this, true); this._children = []; this._namedChildren = {}; this._activeLayer = null; this._currentStyle = new Style(null, null, this); this._view = View.create(this, element || CanvasProvider.getCanvas(1, 1)); this._selectionItems = {}; this._selectionCount = 0; this._updateVersion = 0; }, _serialize: function(options, dictionary) { return Base.serialize(this._children, options, true, dictionary); }, _changed: function(flags, item) { if (flags & 1) { var view = this._view; if (view) { view._needsUpdate = true; if (!view._requested && view._autoUpdate) view.requestUpdate(); } } var changes = this._changes; if (changes && item) { var changesById = this._changesById, id = item._id, entry = changesById[id]; if (entry) { entry.flags |= flags; } else { changes.push(changesById[id] = { item: item, flags: flags }); } } }, clear: function() { var children = this._children; for (var i = children.length - 1; i >= 0; i--) children[i].remove(); }, isEmpty: function() { return !this._children.length; }, remove: function remove() { if (!remove.base.call(this)) return false; if (this._view) this._view.remove(); return true; }, getView: function() { return this._view; }, getCurrentStyle: function() { return this._currentStyle; }, setCurrentStyle: function(style) { this._currentStyle.set(style); }, getIndex: function() { return this._index; }, getOptions: function() { return this._scope.settings; }, getLayers: function() { return this._children; }, getActiveLayer: function() { return this._activeLayer || new Layer({ project: this, insert: true }); }, getSymbolDefinitions: function() { var definitions = [], ids = {}; this.getItems({ class: SymbolItem, match: function(item) { var definition = item._definition, id = definition._id; if (!ids[id]) { ids[id] = true; definitions.push(definition); } return false; } }); return definitions; }, getSymbols: 'getSymbolDefinitions', getSelectedItems: function() { var selectionItems = this._selectionItems, items = []; for (var id in selectionItems) { var item = selectionItems[id], selection = item._selection; if ((selection & 1) && item.isInserted()) { items.push(item); } else if (!selection) { this._updateSelection(item); } } return items; }, _updateSelection: function(item) { var id = item._id, selectionItems = this._selectionItems; if (item._selection) { if (selectionItems[id] !== item) { this._selectionCount++; selectionItems[id] = item; } } else if (selectionItems[id] === item) { this._selectionCount--; delete selectionItems[id]; } }, selectAll: function() { var children = this._children; for (var i = 0, l = children.length; i < l; i++) children[i].setFullySelected(true); }, deselectAll: function() { var selectionItems = this._selectionItems; for (var i in selectionItems) selectionItems[i].setFullySelected(false); }, addLayer: function(layer) { return this.insertLayer(undefined, layer); }, insertLayer: function(index, layer) { if (layer instanceof Layer) { layer._remove(false, true); Base.splice(this._children, [layer], index, 0); layer._setProject(this, true); var name = layer._name; if (name) layer.setName(name); if (this._changes) layer._changed(5); if (!this._activeLayer) this._activeLayer = layer; } else { layer = null; } return layer; }, _insertItem: function(index, item, _created) { item = this.insertLayer(index, item) || (this._activeLayer || this._insertItem(undefined, new Layer(Item.NO_INSERT), true)) .insertChild(index, item); if (_created && item.activate) item.activate(); return item; }, getItems: function(options) { return Item._getItems(this, options); }, getItem: function(options) { return Item._getItems(this, options, null, null, true)[0] || null; }, importJSON: function(json) { this.activate(); var layer = this._activeLayer; return Base.importJSON(json, layer && layer.isEmpty() && layer); }, removeOn: function(type) { var sets = this._removeSets; if (sets) { if (type === 'mouseup') sets.mousedrag = null; var set = sets[type]; if (set) { for (var id in set) { var item = set[id]; for (var key in sets) { var other = sets[key]; if (other && other != set) delete other[item._id]; } item.remove(); } sets[type] = null; } } }, draw: function(ctx, matrix, pixelRatio) { this._updateVersion++; ctx.save(); matrix.applyToContext(ctx); var children = this._children, param = new Base({ offset: new Point(0, 0), pixelRatio: pixelRatio, viewMatrix: matrix.isIdentity() ? null : matrix, matrices: [new Matrix()], updateMatrix: true }); for (var i = 0, l = children.length; i < l; i++) { children[i].draw(ctx, param); } ctx.restore(); if (this._selectionCount > 0) { ctx.save(); ctx.strokeWidth = 1; var items = this._selectionItems, size = this._scope.settings.handleSize, version = this._updateVersion; for (var id in items) { items[id]._drawSelection(ctx, matrix, size, items, version); } ctx.restore(); } } }); var Item = Base.extend(Emitter, { statics: { extend: function extend(src) { if (src._serializeFields) src._serializeFields = Base.set({}, this.prototype._serializeFields, src._serializeFields); return extend.base.apply(this, arguments); }, NO_INSERT: { insert: false } }, _class: 'Item', _name: null, _applyMatrix: true, _canApplyMatrix: true, _canScaleStroke: false, _pivot: null, _visible: true, _blendMode: 'normal', _opacity: 1, _locked: false, _guide: false, _clipMask: false, _selection: 0, _selectBounds: true, _selectChildren: false, _serializeFields: { name: null, applyMatrix: null, matrix: new Matrix(), pivot: null, visible: true, blendMode: 'normal', opacity: 1, locked: false, guide: false, clipMask: false, selected: false, data: {} }, _prioritize: ['applyMatrix'] }, new function() { var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave']; return Base.each(handlers, function(name) { this._events[name] = { install: function(type) { this.getView()._countItemEvent(type, 1); }, uninstall: function(type) { this.getView()._countItemEvent(type, -1); } }; }, { _events: { onFrame: { install: function() { this.getView()._animateItem(this, true); }, uninstall: function() { this.getView()._animateItem(this, false); } }, onLoad: {}, onError: {} }, statics: { _itemHandlers: handlers } } ); }, { initialize: function Item() { }, _initialize: function(props, point) { var hasProps = props && Base.isPlainObject(props), internal = hasProps && props.internal === true, matrix = this._matrix = new Matrix(), project = hasProps && props.project || paper.project, settings = paper.settings; this._id = internal ? null : UID.get(); this._parent = this._index = null; this._applyMatrix = this._canApplyMatrix && settings.applyMatrix; if (point) matrix.translate(point); matrix._owner = this; this._style = new Style(project._currentStyle, this, project); if (internal || hasProps && props.insert == false || !settings.insertItems && !(hasProps && props.insert === true)) { this._setProject(project); } else { (hasProps && props.parent || project) ._insertItem(undefined, this, true); } if (hasProps && props !== Item.NO_INSERT) { this.set(props, { internal: true, insert: true, project: true, parent: true }); } return hasProps; }, _serialize: function(options, dictionary) { var props = {}, that = this; function serialize(fields) { for (var key in fields) { var value = that[key]; if (!Base.equals(value, key === 'leading' ? fields.fontSize * 1.2 : fields[key])) { props[key] = Base.serialize(value, options, key !== 'data', dictionary); } } } serialize(this._serializeFields); if (!(this instanceof Group)) serialize(this._style._defaults); return [ this._class, props ]; }, _changed: function(flags) { var symbol = this._symbol, cacheParent = this._parent || symbol, project = this._project; if (flags & 8) { this._bounds = this._position = this._decomposed = this._globalMatrix = undefined; } if (cacheParent && (flags & 40)) { Item._clearBoundsCache(cacheParent); } if (flags & 2) { Item._clearBoundsCache(this); } if (project) project._changed(flags, this); if (symbol) symbol._changed(flags); }, getId: function() { return this._id; }, getName: function() { return this._name; }, setName: function(name) { if (this._name) this._removeNamed(); if (name === (+name) + '') throw new Error( 'Names consisting only of numbers are not supported.'); var owner = this._getOwner(); if (name && owner) { var children = owner._children, namedChildren = owner._namedChildren; (namedChildren[name] = namedChildren[name] || []).push(this); if (!(name in children)) children[name] = this; } this._name = name || undefined; this._changed(128); }, getStyle: function() { return this._style; }, setStyle: function(style) { this.getStyle().set(style); } }, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'], function(name) { var part = Base.capitalize(name), key = '_' + name, flags = { locked: 128, visible: 137 }; this['get' + part] = function() { return this[key]; }; this['set' + part] = function(value) { if (value != this[key]) { this[key] = value; this._changed(flags[name] || 129); } }; }, {}), { beans: true, getSelection: function() { return this._selection; }, setSelection: function(selection) { if (selection !== this._selection) { this._selection = selection; var project = this._project; if (project) { project._updateSelection(this); this._changed(129); } } }, _changeSelection: function(flag, selected) { var selection = this._selection; this.setSelection(selected ? selection | flag : selection & ~flag); }, isSelected: function() { if (this._selectChildren) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) if (children[i].isSelected()) return true; } return !!(this._selection & 1); }, setSelected: function(selected) { if (this._selectChildren) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) children[i].setSelected(selected); } this._changeSelection(1, selected); }, isFullySelected: function() { var children = this._children, selected = !!(this._selection & 1); if (children && selected) { for (var i = 0, l = children.length; i < l; i++) if (!children[i].isFullySelected()) return false; return true; } return selected; }, setFullySelected: function(selected) { var children = this._children; if (children) { for (var i = 0, l = children.length; i < l; i++) children[i].setFullySelected(selected); } this._changeSelection(1, selected); }, isClipMask: function() { return this._clipMask; }, setClipMask: function(clipMask) { if (this._clipMask != (clipMask = !!clipMask)) { this._clipMask = clipMask; if (clipMask) { this.setFillColor(null); this.setStrokeColor(null); } this._changed(129); if (this._parent) this._parent._changed(1024); } }, getData: function() { if (!this._data) this._data = {}; return this._data; }, setData: function(data) { this._data = data; }, getPosition: function(_dontLink) { var position = this._position, ctor = _dontLink ? Point : LinkedPoint; if (!position) { var pivot = this._pivot; position = this._position = pivot ? this._matrix._transformPoint(pivot) : this.getBounds().getCenter(true); } return new ctor(position.x, position.y, this, 'setPosition'); }, setPosition: function() { this.translate(Point.read(arguments).subtract(this.getPosition(true))); }, getPivot: function() { var pivot = this._pivot; return pivot ? new LinkedPoint(pivot.x, pivot.y, this, 'setPivot') : null; }, setPivot: function() { this._pivot = Point.read(arguments, 0, { clone: true, readNull: true }); this._position = undefined; } }, Base.each({ getStrokeBounds: { stroke: true }, getHandleBounds: { handle: true }, getInternalBounds: { internal: true } }, function(options, key) { this[key] = function(matrix) { return this.getBounds(matrix, options); }; }, { beans: true, getBounds: function(matrix, options) { var hasMatrix = options || matrix instanceof Matrix, opts = Base.set({}, hasMatrix ? options : matrix, this._boundsOptions); if (!opts.stroke || this.getStrokeScaling()) opts.cacheItem = this; var rect = this._getCachedBounds(hasMatrix && matrix, opts).rect; return !arguments.length ? new LinkedRectangle(rect.x, rect.y, rect.width, rect.height, this, 'setBounds') : rect; }, setBounds: function() { var rect = Rectangle.read(arguments), bounds = this.getBounds(), _matrix = this._matrix, matrix = new Matrix(), center = rect.getCenter(); matrix.translate(center); if (rect.width != bounds.width || rect.height != bounds.height) { if (!_matrix.isInvertible()) { _matrix.set(_matrix._backup || new Matrix().translate(_matrix.getTranslation())); bounds = this.getBounds(); } matrix.scale( bounds.width !== 0 ? rect.width / bounds.width : 0, bounds.height !== 0 ? rect.height / bounds.height : 0); } center = bounds.getCenter(); matrix.translate(-center.x, -center.y); this.transform(matrix); }, _getBounds: function(matrix, options) { var children = this._children; if (!children || !children.length) return new Rectangle(); Item._updateBoundsCache(this, options.cacheItem); return Item._getBounds(children, matrix, options); }, _getBoundsCacheKey: function(options, internal) { return [ options.stroke ? 1 : 0, options.handle ? 1 : 0, internal ? 1 : 0 ].join(''); }, _getCachedBounds: function(matrix, options, noInternal) { matrix = matrix && matrix._orNullIfIdentity(); var internal = options.internal && !noInternal, cacheItem = options.cacheItem, _matrix = internal ? null : this._matrix._orNullIfIdentity(), cacheKey = cacheItem && (!matrix || matrix.equals(_matrix)) && this._getBoundsCacheKey(options, internal), bounds = this._bounds; Item._updateBoundsCache(this._parent || this._symbol, cacheItem); if (cacheKey && bounds && cacheKey in bounds) { var cached = bounds[cacheKey]; return { rect: cached.rect.clone(), nonscaling: cached.nonscaling }; } var res = this._getBounds(matrix || _matrix, options), rect = res.rect || res, style = this._style, nonscaling = res.nonscaling || style.hasStroke() && !style.getStrokeScaling(); if (cacheKey) { if (!bounds) { this._bounds = bounds = {}; } var cached = bounds[cacheKey] = { rect: rect.clone(), nonscaling: nonscaling, internal: internal }; } return { rect: rect, nonscaling: nonscaling }; }, _getStrokeMatrix: function(matrix, options) { var parent = this.getStrokeScaling() ? null : options && options.internal ? this : this._parent || this._symbol && this._symbol._item, mx = parent ? parent.getViewMatrix().invert() : matrix; return mx && mx._shiftless(); }, statics: { _updateBoundsCache: function(parent, item) { if (parent && item) { var id = item._id, ref = parent._boundsCache = parent._boundsCache || { ids: {}, list: [] }; if (!ref.ids[id]) { ref.list.push(item); ref.ids[id] = item; } } }, _clearBoundsCache: function(item) { var cache = item._boundsCache; if (cache) { item._bounds = item._position = item._boundsCache = undefined; for (var i = 0, list = cache.list, l = list.length; i < l; i++){ var other = list[i]; if (other !== item) { other._bounds = other._position = undefined; if (other._boundsCache) Item._clearBoundsCache(other); } } } }, _getBounds: function(items, matrix, options) { var x1 = Infinity, x2 = -x1, y1 = x1, y2 = x2, nonscaling = false; options = options || {}; for (var i = 0, l = items.length; i < l; i++) { var item = items[i]; if (item._visible && !item.isEmpty()) { var bounds = item._getCachedBounds( matrix && matrix.appended(item._matrix), options, true), rect = bounds.rect; x1 = Math.min(rect.x, x1); y1 = Math.min(rect.y, y1); x2 = Math.max(rect.x + rect.width, x2); y2 = Math.max(rect.y + rect.height, y2); if (bounds.nonscaling) nonscaling = true; } } return { rect: isFinite(x1) ? new Rectangle(x1, y1, x2 - x1, y2 - y1) : new Rectangle(), nonscaling: nonscaling }; } } }), { beans: true, _decompose: function() { return this._applyMatrix ? null : this._decomposed || (this._decomposed = this._matrix.decompose()); }, getRotation: function() { var decomposed = this._decompose(); return decomposed ? decomposed.rotation : 0; }, setRotation: function(rotation) { var current = this.getRotation(); if (current != null && rotation != null) { var decomposed = this._decomposed; this.rotate(rotation - current); if (decomposed) { decomposed.rotation = rotation; this._decomposed = decomposed; } } }, getScaling: function() { var decomposed = this._decompose(), s = decomposed && decomposed.scaling; return new LinkedPoint(s ? s.x : 1, s ? s.y : 1, this, 'setScaling'); }, setScaling: function() { var current = this.getScaling(), scaling = Point.read(arguments, 0, { clone: true, readNull: true }); if (current && scaling && !current.equals(scaling)) { var rotation = this.getRotation(), decomposed = this._decomposed, matrix = new Matrix(), center = this.getPosition(true); matrix.translate(center); if (rotation) matrix.rotate(rotation); matrix.scale(scaling.x / current.x, scaling.y / current.y); if (rotation) matrix.rotate(-rotation); matrix.translate(center.negate()); this.transform(matrix); if (decomposed) { decomposed.scaling = scaling; this._decomposed = decomposed; } } }, getMatrix: function() { return this._matrix; }, setMatrix: function() { var matrix = this._matrix; matrix.initialize.apply(matrix, arguments); }, getGlobalMatrix: function(_dontClone) { var matrix = this._globalMatrix, updateVersion = this._project._updateVersion; if (matrix && matrix._updateVersion !== updateVersion) matrix = null; if (!matrix) { matrix = this._globalMatrix = this._matrix.clone(); var parent = this._parent; if (parent) matrix.prepend(parent.getGlobalMatrix(true)); matrix._updateVersion = updateVersion; } return _dontClone ? matrix : matrix.clone(); }, getViewMatrix: function() { return this.getGlobalMatrix().prepend(this.getView()._matrix); }, getApplyMatrix: function() { return this._applyMatrix; }, setApplyMatrix: function(apply) { if (this._applyMatrix = this._canApplyMatrix && !!apply) this.transform(null, true); }, getTransformContent: '#getApplyMatrix', setTransformContent: '#setApplyMatrix', }, { getProject: function() { return this._project; }, _setProject: function(project, installEvents) { if (this._project !== project) { if (this._project) this._installEvents(false); this._project = project; var children = this._children; for (var i = 0, l = children && children.length; i < l; i++) children[i]._setProject(project); installEvents = true; } if (installEvents) this._installEvents(true); }, getView: function() { return this._project._view; }, _installEvents: function _installEvents(install) { _installEvents.base.call(this, install); var children = this._children; for (var i = 0, l = children && children.length; i < l; i++) children[i]._installEvents(install); }, getLayer: function() { var parent = this; while (parent = parent._parent) { if (parent instanceof Layer) return parent; } return null; }, getParent: function() { return this._parent; }, setParent: function(item) { return item.addChild(this); }, _getOwner: '#getParent', getChildren: function() { return this._children; }, setChildren: function(items) { this.removeChildren(); this.addChildren(items); }, getFirstChild: function() { return this._children && this._children[0] || null; }, getLastChild: function() { return this._children && this._children[this._children.length - 1] || null; }, getNextSibling: function() { var owner = this._getOwner(); return owner && owner._children[this._index + 1] || null; }, getPreviousSibling: function() { var owner = this._getOwner(); return owner && owner._children[this._index - 1] || null; }, getIndex: function() { return this._index; }, equals: function(item) { return item === this || item && this._class === item._class && this._style.equals(item._style) && this._matrix.equals(item._matrix) && this._locked === item._locked && this._visible === item._visible && this._blendMode === item._blendMode && this._opacity === item._opacity && this._clipMask === item._clipMask && this._guide === item._guide && this._equals(item) || false; }, _equals: function(item) { return Base.equals(this._children, item._children); }, clone: function(options) { var copy = new this.constructor(Item.NO_INSERT), children = this._children, insert = Base.pick(options ? options.insert : undefined, options === undefined || options === true), deep = Base.pick(options ? options.deep : undefined, true); if (children) copy.copyAttributes(this); if (!children || deep) copy.copyContent(this); if (!children) copy.copyAttributes(this); if (insert) copy.insertAbove(this); var name = this._name, parent = this._parent; if (name && parent) { var children = parent._children, orig = name, i = 1; while (children[name]) name = orig + ' ' + (i++); if (name !== orig) copy.setName(name); } return copy; }, copyContent: function(source) { var children = source._children; for (var i = 0, l = children && children.length; i < l; i++) { this.addChild(children[i].clone(false), true); } }, copyAttributes: function(source, excludeMatrix) { this.setStyle(source._style); var keys = ['_locked', '_visible', '_blendMode', '_opacity', '_clipMask', '_guide']; for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; if (source.hasOwnProperty(key)) this[key] = source[key]; } if (!excludeMatrix) this._matrix.set(source._matrix, true); this.setApplyMatrix(source._applyMatrix); this.setPivot(source._pivot); this.setSelection(source._selection); var data = source._data, name = source._name; this._data = data ? Base.clone(data) : null; if (name) this.setName(name); }, rasterize: function(resolution, insert) { var bounds = this.getStrokeBounds(), scale = (resolution || this.getView().getResolution()) / 72, topLeft = bounds.getTopLeft().floor(), bottomRight = bounds.getBottomRight().ceil(), size = new Size(bottomRight.subtract(topLeft)), raster = new Raster(Item.NO_INSERT); if (!size.isZero()) { var canvas = CanvasProvider.getCanvas(size.multiply(scale)), ctx = canvas.getContext('2d'), matrix = new Matrix().scale(scale).translate(topLeft.negate()); ctx.save(); matrix.applyToContext(ctx); this.draw(ctx, new Base({ matrices: [matrix] })); ctx.restore(); raster.setCanvas(canvas); } raster.transform(new Matrix().translate(topLeft.add(size.divide(2))) .scale(1 / scale)); if (insert === undefined || insert) raster.insertAbove(this); return raster; }, contains: function() { return !!this._contains( this._matrix._inverseTransform(Point.read(arguments))); }, _contains: function(point) { var children = this._children; if (children) { for (var i = children.length - 1; i >= 0; i--) { if (children[i].contains(point)) return true; } return false; } return point.isInside(this.getInternalBounds()); }, isInside: function() { return Rectangle.read(arguments).contains(this.getBounds()); }, _asPathItem: function() { return new Path.Rectangle({ rectangle: this.getInternalBounds(), matrix: this._matrix, insert: false, }); }, intersects: function(item, _matrix) { if (!(item instanceof Item)) return false; return this._asPathItem().getIntersections(item._asPathItem(), null, _matrix, true).length > 0; } }, new function() { function hitTest() { return this._hitTest( Point.read(arguments), HitResult.getOptions(arguments)); } function hitTestAll() { var point = Point.read(arguments), options = HitResult.getOptions(arguments), all = []; this._hitTest(point, Base.set({ all: all }, options)); return all; } function hitTestChildren(point, options, viewMatrix, _exclude) { var children = this._children; if (children) { for (var i = children.length - 1; i >= 0; i--) { var child = children[i]; var res = child !== _exclude && child._hitTest(point, options, viewMatrix); if (res && !options.all) return res; } } return null; } Project.inject({ hitTest: hitTest, hitTestAll: hitTestAll, _hitTest: hitTestChildren }); return { hitTest: hitTest, hitTestAll: hitTestAll, _hitTestChildren: hitTestChildren, }; }, { _hitTest: function(point, options, parentViewMatrix) { if (this._locked || !this._visible || this._guide && !options.guides || this.isEmpty()) { return null; } var matrix = this._matrix, viewMatrix = parentViewMatrix ? parentViewMatrix.appended(matrix) : this.getGlobalMatrix().prepend(this.getView()._matrix), tolerance = Math.max(options.tolerance, 1e-12), tolerancePadding = options._tolerancePadding = new Size( Path._getStrokePadding(tolerance, matrix._shiftless().invert())); point = matrix._inverseTransform(point); if (!point || !this._children && !this.getBounds({ internal: true, stroke: true, handle: true }) .expand(tolerancePadding.multiply(2))._containsPoint(point)) { return null; } var checkSelf = !(options.guides && !this._guide || options.selected && !this.isSelected() || options.type && options.type !== Base.hyphenate(this._class) || options.class && !(this instanceof options.class)), match = options.match, that = this, bounds, res; function filter(hit) { if (hit && match && !match(hit)) hit = null; if (hit && options.all) options.all.push(hit); return hit; } function checkPoint(type, part) { var pt = part ? bounds['get' + part]() : that.getPosition(); if (point.subtract(pt).divide(tolerancePadding).length <= 1) { return new HitResult(type, that, { name: part ? Base.hyphenate(part) : type, point: pt }); } } var checkPosition = options.position, checkCenter = options.center, checkBounds = options.bounds; if (checkSelf && this._parent && (checkPosition || checkCenter || checkBounds)) { if (checkCenter || checkBounds) { bounds = this.getInternalBounds(); } res = checkPosition && checkPoint('position') || checkCenter && checkPoint('center', 'Center'); if (!res && checkBounds) { var points = [ 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter' ]; for (var i = 0; i < 8 && !res; i++) { res = checkPoint('bounds', points[i]); } } res = filter(res); } if (!res) { res = this._hitTestChildren(point, options, viewMatrix) || checkSelf && filter(this._hitTestSelf(point, options, viewMatrix, this.getStrokeScaling() ? null : viewMatrix._shiftless().invert())) || null; } if (res && res.point) { res.point = matrix.transform(res.point); } return res; }, _hitTestSelf: function(point, options) { if (options.fill && this.hasFill() && this._contains(point)) return new HitResult('fill', this); }, matches: function(name, compare) { function matchObject(obj1, obj2) { for (var i in obj1) { if (obj1.hasOwnProperty(i)) { var val1 = obj1[i], val2 = obj2[i]; if (Base.isPlainObject(val1) && Base.isPlainObject(val2)) { if (!matchObject(val1, val2)) return false; } else if (!Base.equals(val1, val2)) { return false; } } } return true; } var type = typeof name; if (type === 'object') { for (var key in name) { if (name.hasOwnProperty(key) && !this.matches(key, name[key])) return false; } return true; } else if (type === 'function') { return name(this); } else if (name === 'match') { return compare(this); } else { var value = /^(empty|editable)$/.test(name) ? this['is' + Base.capitalize(name)]() : name === 'type' ? Base.hyphenate(this._class) : this[name]; if (name === 'class') { if (typeof compare === 'function') return this instanceof compare; value = this._class; } if (typeof compare === 'function') { return !!compare(value); } else if (compare) { if (compare.test) { return compare.test(value); } else if (Base.isPlainObject(compare)) { return matchObject(compare, value); } } return Base.equals(value, compare); } }, getItems: function(options) { return Item._getItems(this, options, this._matrix); }, getItem: function(options) { return Item._getItems(this, options, this._matrix, null, true)[0] || null; }, statics: { _getItems: function _getItems(item, options, matrix, param, firstOnly) { if (!param) { var obj = typeof options === 'object' && options, overlapping = obj && obj.overlapping, inside = obj && obj.inside, bounds = overlapping || inside, rect = bounds && Rectangle.read([bounds]); param = { items: [], recursive: obj && obj.recursive !== false, inside: !!inside, overlapping: !!overlapping, rect: rect, path: overlapping && new Path.Rectangle({ rectangle: rect, insert: false }) }; if (obj) { options = Base.filter({}, options, { recursive: true, inside: true, overlapping: true }); } } var children = item._children, items = param.items, rect = param.rect; matrix = rect && (matrix || new Matrix()); for (var i = 0, l = children && children.length; i < l; i++) { var child = children[i], childMatrix = matrix && matrix.appended(child._matrix), add = true; if (rect) { var bounds = child.getBounds(childMatrix); if (!rect.intersects(bounds)) continue; if (!(rect.contains(bounds) || param.overlapping && (bounds.contains(rect) || param.path.intersects(child, childMatrix)))) add = false; } if (add && child.matches(options)) { items.push(child); if (firstOnly) break; } if (param.recursive !== false) { _getItems(child, options, childMatrix, param, firstOnly); } if (firstOnly && items.length > 0) break; } return items; } } }, { importJSON: function(json) { var res = Base.importJSON(json, this); return res !== this ? this.addChild(res) : res; }, addChild: function(item) { return this.insertChild(undefined, item); }, insertChild: function(index, item) { var res = item ? this.insertChildren(index, [item]) : null; return res && res[0]; }, addChildren: function(items) { return this.insertChildren(this._children.length, items); }, insertChildren: function(index, items) { var children = this._children; if (children && items && items.length > 0) { items = Base.slice(items); var inserted = {}; for (var i = items.length - 1; i >= 0; i--) { var item = items[i], id = item && item._id; if (!item || inserted[id]) { items.splice(i, 1); } else { item._remove(false, true); inserted[id] = true; } } Base.splice(children, items, index, 0); var project = this._project, notifySelf = project._changes; for (var i = 0, l = items.length; i < l; i++) { var item = items[i], name = item._name; item._parent = this; item._setProject(project, true); if (name) item.setName(name); if (notifySelf) item._changed(5); } this._changed(11); } else { items = null; } return items; }, _insertItem: '#insertChild', _insertAt: function(item, offset) { var owner = item && item._getOwner(), res = item !== this && owner ? this : null; if (res) { res._remove(false, true); owner._insertItem(item._index + offset, res); } return res; }, insertAbove: function(item) { return this._insertAt(item, 1); }, insertBelow: function(item) { return this._insertAt(item, 0); }, sendToBack: function() { var owner = this._getOwner(); return owner ? owner._insertItem(0, this) : null; }, bringToFront: function() { var owner = this._getOwner(); return owner ? owner._insertItem(undefined, this) : null; }, appendTop: '#addChild', appendBottom: function(item) { return this.insertChild(0, item); }, moveAbove: '#insertAbove', moveBelow: '#insertBelow', addTo: function(owner) { return owner._insertItem(undefined, this); }, copyTo: function(owner) { return this.clone(false).addTo(owner); }, reduce: function(options) { var children = this._children; if (children && children.length === 1) { var child = children[0].reduce(options); if (this._parent) { child.insertAbove(this); this.remove(); } else { child.remove(); } return child; } return this; }, _removeNamed: function() { var owner = this._getOwner(); if (owner) { var children = owner._children, namedChildren = owner._namedChildren, name = this._name, namedArray = namedChildren[name], index = namedArray ? namedArray.indexOf(this) : -1; if (index !== -1) { if (children[name] == this) delete children[name]; namedArray.splice(index, 1); if (namedArray.length) { children[name] = namedArray[0]; } else { delete namedChildren[name]; } } } }, _remove: function(notifySelf, notifyParent) { var owner = this._getOwner(), project = this._project, index = this._index; if (owner) { if (this._name) this._removeNamed(); if (index != null) { if (project._activeLayer === this) project._activeLayer = this.getNextSibling() || this.getPreviousSibling(); Base.splice(owner._children, null, index, 1); } this._installEvents(false); if (notifySelf && project._changes) this._changed(5); if (notifyParent) owner._changed(11, this); this._parent = null; return true; } return false; }, remove: function() { return this._remove(true, true); }, replaceWith: function(item) { var ok = item && item.insertBelow(this); if (ok) this.remove(); return ok; }, removeChildren: function(start, end) { if (!this._children) return null; start = start || 0; end = Base.pick(end, this._children.length); var removed = Base.splice(this._children, null, start, end - start); for (var i = removed.length - 1; i >= 0; i--) { removed[i]._remove(true, false); } if (removed.length > 0) this._changed(11); return removed; }, clear: '#removeChildren', reverseChildren: function() { if (this._children) { this._children.reverse(); for (var i = 0, l = this._children.length; i < l; i++) this._children[i]._index = i; this._changed(11); } }, isEmpty: function() { var children = this._children; return !children || !children.length; }, isEditable: function() { var item = this; while (item) { if (!item._visible || item._locked) return false; item = item._parent; } return true; }, hasFill: function() { return this.getStyle().hasFill(); }, hasStroke: function() { return this.getStyle().hasStroke(); }, hasShadow: function() { return this.getStyle().hasShadow(); }, _getOrder: function(item) { function getList(item) { var list = []; do { list.unshift(item); } while (item = item._parent); return list; } var list1 = getList(this), list2 = getList(item); for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) { if (list1[i] != list2[i]) { return list1[i]._index < list2[i]._index ? 1 : -1; } } return 0; }, hasChildren: function() { return this._children && this._children.length > 0; }, isInserted: function() { return this._parent ? this._parent.isInserted() : false; }, isAbove: function(item) { return this._getOrder(item) === -1; }, isBelow: function(item) { return this._getOrder(item) === 1; }, isParent: function(item) { return this._parent === item; }, isChild: function(item) { return item && item._parent === this; }, isDescendant: function(item) { var parent = this; while (parent = parent._parent) { if (parent === item) return true; } return false; }, isAncestor: function(item) { return item ? item.isDescendant(this) : false; }, isSibling: function(item) { return this._parent === item._parent; }, isGroupedWith: function(item) { var parent = this._parent; while (parent) { if (parent._parent && /^(Group|Layer|CompoundPath)$/.test(parent._class) && item.isDescendant(parent)) return true; parent = parent._parent; } return false; }, }, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { var rotate = key === 'rotate'; this[key] = function() { var value = (rotate ? Base : Point).read(arguments), center = Point.read(arguments, 0, { readNull: true }); return this.transform(new Matrix()[key](value, center || this.getPosition(true))); }; }, { translate: function() { var mx = new Matrix(); return this.transform(mx.translate.apply(mx, arguments)); }, transform: function(matrix, _applyMatrix, _applyRecursively, _setApplyMatrix) { var _matrix = this._matrix, transform = matrix && !matrix.isIdentity(), applyMatrix = (_applyMatrix || this._applyMatrix) && ((!_matrix.isIdentity() || transform) || _applyMatrix && _applyRecursively && this._children); if (!transform && !applyMatrix) return this; if (transform) { if (!matrix.isInvertible() && _matrix.isInvertible()) _matrix._backup = _matrix.getValues(); _matrix.prepend(matrix, true); var style = this._style, fillColor = style.getFillColor(true), strokeColor = style.getStrokeColor(true); if (fillColor) fillColor.transform(matrix); if (strokeColor) strokeColor.transform(matrix); } if (applyMatrix && (applyMatrix = this._transformContent(_matrix, _applyRecursively, _setApplyMatrix))) { var pivot = this._pivot; if (pivot) _matrix._transformPoint(pivot, pivot, true); _matrix.reset(true); if (_setApplyMatrix && this._canApplyMatrix) this._applyMatrix = true; } var bounds = this._bounds, position = this._position; if (transform || applyMatrix) { this._changed(9); } var decomp = transform && bounds && matrix.decompose(); if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) { for (var key in bounds) { var cache = bounds[key]; if (cache.nonscaling) { delete bounds[key]; } else if (applyMatrix || !cache.internal) { var rect = cache.rect; matrix._transformBounds(rect, rect); } } this._bounds = bounds; var cached = bounds[this._getBoundsCacheKey( this._boundsOptions || {})]; if (cached) { this._position = cached.rect.getCenter(true); } } else if (transform && position && this._pivot) { this._position = matrix._transformPoint(position, position); } return this; }, _transformContent: function(matrix, applyRecursively, setApplyMatrix) { var children = this._children; if (children) { for (var i = 0, l = children.length; i < l; i++) children[i].transform(matrix, true, applyRecursively, setApplyMatrix); return true; } }, globalToLocal: function() { return this.getGlobalMatrix(true)._inverseTransform( Point.read(arguments)); }, localToGlobal: function() { return this.getGlobalMatrix(true)._transformPoint( Point.read(arguments)); }, parentToLocal: function() { return this._matrix._inverseTransform(Point.read(arguments)); }, localToParent: function() { return this._matrix._transformPoint(Point.read(arguments)); }, fitBounds: function(rectangle, fill) { rectangle = Rectangle.read(arguments); var bounds = this.getBounds(), itemRatio = bounds.height / bounds.width, rectRatio = rectangle.height / rectangle.width, scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio) ? rectangle.width / bounds.width : rectangle.height / bounds.height, newBounds = new Rectangle(new Point(), new Size(bounds.width * scale, bounds.height * scale)); newBounds.setCenter(rectangle.getCenter()); this.setBounds(newBounds); } }), { _setStyles: function(ctx, param, viewMatrix) { var style = this._style, matrix = this._matrix; if (style.hasFill()) { ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix); } if (style.hasStroke()) { ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix); ctx.lineWidth = style.getStrokeWidth(); var strokeJoin = style.getStrokeJoin(), strokeCap = style.getStrokeCap(), miterLimit = style.getMiterLimit(); if (strokeJoin) ctx.lineJoin = strokeJoin; if (strokeCap) ctx.lineCap = strokeCap; if (miterLimit) ctx.miterLimit = miterLimit; if (paper.support.nativeDash) { var dashArray = style.getDashArray(), dashOffset = style.getDashOffset(); if (dashArray && dashArray.length) { if ('setLineDash' in ctx) { ctx.setLineDash(dashArray); ctx.lineDashOffset = dashOffset; } else { ctx.mozDash = dashArray; ctx.mozDashOffset = dashOffset; } } } } if (style.hasShadow()) { var pixelRatio = param.pixelRatio || 1, mx = viewMatrix._shiftless().prepend( new Matrix().scale(pixelRatio, pixelRatio)), blur = mx.transform(new Point(style.getShadowBlur(), 0)), offset = mx.transform(this.getShadowOffset()); ctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx); ctx.shadowBlur = blur.getLength(); ctx.shadowOffsetX = offset.x; ctx.shadowOffsetY = offset.y; } }, draw: function(ctx, param, parentStrokeMatrix) { var updateVersion = this._updateVersion = this._project._updateVersion; if (!this._visible || this._opacity === 0) return; var matrices = param.matrices, viewMatrix = param.viewMatrix, matrix = this._matrix, globalMatrix = matrices[matrices.length - 1].appended(matrix); if (!globalMatrix.isInvertible()) return; viewMatrix = viewMatrix ? viewMatrix.appended(globalMatrix) : globalMatrix; matrices.push(globalMatrix); if (param.updateMatrix) { globalMatrix._updateVersion = updateVersion; this._globalMatrix = globalMatrix; } var blendMode = this._blendMode, opacity = this._opacity, normalBlend = blendMode === 'normal', nativeBlend = BlendMode.nativeModes[blendMode], direct = normalBlend && opacity === 1 || param.dontStart || param.clip || (nativeBlend || normalBlend && opacity < 1) && this._canComposite(), pixelRatio = param.pixelRatio || 1, mainCtx, itemOffset, prevOffset; if (!direct) { var bounds = this.getStrokeBounds(viewMatrix); if (!bounds.width || !bounds.height) return; prevOffset = param.offset; itemOffset = param.offset = bounds.getTopLeft().floor(); mainCtx = ctx; ctx = CanvasProvider.getContext(bounds.getSize().ceil().add(1) .multiply(pixelRatio)); if (pixelRatio !== 1) ctx.scale(pixelRatio, pixelRatio); } ctx.save(); var strokeMatrix = parentStrokeMatrix ? parentStrokeMatrix.appended(matrix) : this._canScaleStroke && !this.getStrokeScaling(true) && viewMatrix, clip = !direct && param.clipItem, transform = !strokeMatrix || clip; if (direct) { ctx.globalAlpha = opacity; if (nativeBlend) ctx.globalCompositeOperation = blendMode; } else if (transform) { ctx.translate(-itemOffset.x, -itemOffset.y); } if (transform) { (direct ? matrix : viewMatrix).applyToContext(ctx); } if (clip) { param.clipItem.draw(ctx, param.extend({ clip: true })); } if (strokeMatrix) { ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); var offset = param.offset; if (offset) ctx.translate(-offset.x, -offset.y); } this._draw(ctx, param, viewMatrix, strokeMatrix); ctx.restore(); matrices.pop(); if (param.clip && !param.dontFinish) ctx.clip(); if (!direct) { BlendMode.process(blendMode, ctx, mainCtx, opacity, itemOffset.subtract(prevOffset).multiply(pixelRatio)); CanvasProvider.release(ctx); param.offset = prevOffset; } }, _isUpdated: function(updateVersion) { var parent = this._parent; if (parent instanceof CompoundPath) return parent._isUpdated(updateVersion); var updated = this._updateVersion === updateVersion; if (!updated && parent && parent._visible && parent._isUpdated(updateVersion)) { this._updateVersion = updateVersion; updated = true; } return updated; }, _drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) { var selection = this._selection, itemSelected = selection & 1, boundsSelected = selection & 2 || itemSelected && this._selectBounds, positionSelected = selection & 4; if (!this._drawSelected) itemSelected = false; if ((itemSelected || boundsSelected || positionSelected) && this._isUpdated(updateVersion)) { var layer, color = this.getSelectedColor(true) || (layer = this.getLayer()) && layer.getSelectedColor(true), mx = matrix.appended(this.getGlobalMatrix(true)), half = size / 2; ctx.strokeStyle = ctx.fillStyle = color ? color.toCanvasStyle(ctx) : '#009dec'; if (itemSelected) this._drawSelected(ctx, mx, selectionItems); if (positionSelected) { var point = this.getPosition(true), x = point.x, y = point.y; ctx.beginPath(); ctx.arc(x, y, half, 0, Math.PI * 2, true); ctx.stroke(); var deltas = [[0, -1], [1, 0], [0, 1], [-1, 0]], start = half, end = size + 1; for (var i = 0; i < 4; i++) { var delta = deltas[i], dx = delta[0], dy = delta[1]; ctx.moveTo(x + dx * start, y + dy * start); ctx.lineTo(x + dx * end, y + dy * end); ctx.stroke(); } } if (boundsSelected) { var coords = mx._transformCorners(this.getInternalBounds()); ctx.beginPath(); for (var i = 0; i < 8; i++) { ctx[!i ? 'moveTo' : 'lineTo'](coords[i], coords[++i]); } ctx.closePath(); ctx.stroke(); for (var i = 0; i < 8; i++) { ctx.fillRect(coords[i] - half, coords[++i] - half, size, size); } } } }, _canComposite: function() { return false; } }, Base.each(['down', 'drag', 'up', 'move'], function(key) { this['removeOn' + Base.capitalize(key)] = function() { var hash = {}; hash[key] = true; return this.removeOn(hash); }; }, { removeOn: function(obj) { for (var name in obj) { if (obj[name]) { var key = 'mouse' + name, project = this._project, sets = project._removeSets = project._removeSets || {}; sets[key] = sets[key] || {}; sets[key][this._id] = this; } } return this; } })); var Group = Item.extend({ _class: 'Group', _selectBounds: false, _selectChildren: true, _serializeFields: { children: [] }, initialize: function Group(arg) { this._children = []; this._namedChildren = {}; if (!this._initialize(arg)) this.addChildren(Array.isArray(arg) ? arg : arguments); }, _changed: function _changed(flags) { _changed.base.call(this, flags); if (flags & 1026) { this._clipItem = undefined; } }, _getClipItem: function() { var clipItem = this._clipItem; if (clipItem === undefined) { clipItem = null; var children = this._children; for (var i = 0, l = children.length; i < l; i++) { if (children[i]._clipMask) { clipItem = children[i]; break; } } this._clipItem = clipItem; } return clipItem; }, isClipped: function() { return !!this._getClipItem(); }, setClipped: function(clipped) { var child = this.getFirstChild(); if (child) child.setClipMask(clipped); }, _getBounds: function _getBounds(matrix, options) { var clipItem = this._getClipItem(); return clipItem ? clipItem._getCachedBounds( matrix && matrix.appended(clipItem._matrix), Base.set({}, options, { stroke: false })) : _getBounds.base.call(this, matrix, options); }, _hitTestChildren: function _hitTestChildren(point, options, viewMatrix) { var clipItem = this._getClipItem(); return (!clipItem || clipItem.contains(point)) && _hitTestChildren.base.call(this, point, options, viewMatrix, clipItem); }, _draw: function(ctx, param) { var clip = param.clip, clipItem = !clip && this._getClipItem(); param = param.extend({ clipItem: clipItem, clip: false }); if (clip) { ctx.beginPath(); param.dontStart = param.dontFinish = true; } else if (clipItem) { clipItem.draw(ctx, param.extend({ clip: true })); } var children = this._children; for (var i = 0, l = children.length; i < l; i++) { var item = children[i]; if (item !== clipItem) item.draw(ctx, param); } } }); var Layer = Group.extend({ _class: 'Layer', initialize: function Layer() { Group.apply(this, arguments); }, _getOwner: function() { return this._parent || this._index != null && this._project; }, isInserted: function isInserted() { return this._parent ? isInserted.base.call(this) : this._index != null; }, activate: function() { this._project._activeLayer = this; }, _hitTestSelf: function() { } }); var Shape = Item.extend({ _class: 'Shape', _applyMatrix: false, _canApplyMatrix: false, _canScaleStroke: true, _serializeFields: { type: null, size: null, radius: null }, initialize: function Shape(props, point) { this._initialize(props, point); }, _equals: function(item) { return this._type === item._type && this._size.equals(item._size) && Base.equals(this._radius, item._radius); }, copyContent: function(source) { this.setType(source._type); this.setSize(source._size); this.setRadius(source._radius); }, getType: function() { return this._type; }, setType: function(type) { this._type = type; }, getShape: '#getType', setShape: '#setType', getSize: function() { var size = this._size; return new LinkedSize(size.width, size.height, this, 'setSize'); }, setSize: function() { var size = Size.read(arguments); if (!this._size) { this._size = size.clone(); } else if (!this._size.equals(size)) { var type = this._type, width = size.width, height = size.height; if (type === 'rectangle') { this._radius.set(Size.min(this._radius, size.divide(2))); } else if (type === 'circle') { width = height = (width + height) / 2; this._radius = width / 2; } else if (type === 'ellipse') { this._radius._set(width / 2, height / 2); } this._size._set(width, height); this._changed(9); } }, getRadius: function() { var rad = this._radius; return this._type === 'circle' ? rad : new LinkedSize(rad.width, rad.height, this, 'setRadius'); }, setRadius: function(radius) { var type = this._type; if (type === 'circle') { if (radius === this._radius) return; var size = radius * 2; this._radius = radius; this._size._set(size, size); } else { radius = Size.read(arguments); if (!this._radius) { this._radius = radius.clone(); } else { if (this._radius.equals(radius)) return; this._radius.set(radius); if (type === 'rectangle') { var size = Size.max(this._size, radius.multiply(2)); this._size.set(size); } else if (type === 'ellipse') { this._size._set(radius.width * 2, radius.height * 2); } } } this._changed(9); }, isEmpty: function() { return false; }, toPath: function(insert) { var path = new Path[Base.capitalize(this._type)]({ center: new Point(), size: this._size, radius: this._radius, insert: false }); path.copyAttributes(this); if (paper.settings.applyMatrix) path.setApplyMatrix(true); if (insert === undefined || insert) path.insertAbove(this); return path; }, toShape: '#clone', _asPathItem: function() { return this.toPath(false); }, _draw: function(ctx, param, viewMatrix, strokeMatrix) { var style = this._style, hasFill = style.hasFill(), hasStroke = style.hasStroke(), dontPaint = param.dontFinish || param.clip, untransformed = !strokeMatrix; if (hasFill || hasStroke || dontPaint) { var type = this._type, radius = this._radius, isCircle = type === 'circle'; if (!param.dontStart) ctx.beginPath(); if (untransformed && isCircle) { ctx.arc(0, 0, radius, 0, Math.PI * 2, true); } else { var rx = isCircle ? radius : radius.width, ry = isCircle ? radius : radius.height, size = this._size, width = size.width, height = size.height; if (untransformed && type === 'rectangle' && rx === 0 && ry === 0) { ctx.rect(-width / 2, -height / 2, width, height); } else { var x = width / 2, y = height / 2, kappa = 1 - 0.5522847498307936, cx = rx * kappa, cy = ry * kappa, c = [ -x, -y + ry, -x, -y + cy, -x + cx, -y, -x + rx, -y, x - rx, -y, x - cx, -y, x, -y + cy, x, -y + ry, x, y - ry, x, y - cy, x - cx, y, x - rx, y, -x + rx, y, -x + cx, y, -x, y - cy, -x, y - ry ]; if (strokeMatrix) strokeMatrix.transform(c, c, 32); ctx.moveTo(c[0], c[1]); ctx.bezierCurveTo(c[2], c[3], c[4], c[5], c[6], c[7]); if (x !== rx) ctx.lineTo(c[8], c[9]); ctx.bezierCurveTo(c[10], c[11], c[12], c[13], c[14], c[15]); if (y !== ry) ctx.lineTo(c[16], c[17]); ctx.bezierCurveTo(c[18], c[19], c[20], c[21], c[22], c[23]); if (x !== rx) ctx.lineTo(c[24], c[25]); ctx.bezierCurveTo(c[26], c[27], c[28], c[29], c[30], c[31]); } } ctx.closePath(); } if (!dontPaint && (hasFill || hasStroke)) { this._setStyles(ctx, param, viewMatrix); if (hasFill) { ctx.fill(style.getFillRule()); ctx.shadowColor = 'rgba(0,0,0,0)'; } if (hasStroke) ctx.stroke(); } }, _canComposite: function() { return !(this.hasFill() && this.hasStroke()); }, _getBounds: function(matrix, options) { var rect = new Rectangle(this._size).setCenter(0, 0), style = this._style, strokeWidth = options.stroke && style.hasStroke() && style.getStrokeWidth(); if (matrix) rect = matrix._transformBounds(rect); return strokeWidth ? rect.expand(Path._getStrokePadding(strokeWidth, this._getStrokeMatrix(matrix, options))) : rect; } }, new function() { function getCornerCenter(that, point, expand) { var radius = that._radius; if (!radius.isZero()) { var halfSize = that._size.divide(2); for (var q = 1; q <= 4; q++) { var dir = new Point(q > 1 && q < 4 ? -1 : 1, q > 2 ? -1 : 1), corner = dir.multiply(halfSize), center = corner.subtract(dir.multiply(radius)), rect = new Rectangle( expand ? corner.add(dir.multiply(expand)) : corner, center); if (rect.contains(point)) return { point: center, quadrant: q }; } } } function isOnEllipseStroke(point, radius, padding, quadrant) { var vector = point.divide(radius); return (!quadrant || vector.isInQuadrant(quadrant)) && vector.subtract(vector.normalize()).multiply(radius) .divide(padding).length <= 1; } return { _contains: function _contains(point) { if (this._type === 'rectangle') { var center = getCornerCenter(this, point); return center ? point.subtract(center.point).divide(this._radius) .getLength() <= 1 : _contains.base.call(this, point); } else { return point.divide(this.size).getLength() <= 0.5; } }, _hitTestSelf: function _hitTestSelf(point, options, viewMatrix, strokeMatrix) { var hit = false, style = this._style, hitStroke = options.stroke && style.hasStroke(), hitFill = options.fill && style.hasFill(); if (hitStroke || hitFill) { var type = this._type, radius = this._radius, strokeRadius = hitStroke ? style.getStrokeWidth() / 2 : 0, strokePadding = options._tolerancePadding.add( Path._getStrokePadding(strokeRadius, !style.getStrokeScaling() && strokeMatrix)); if (type === 'rectangle') { var padding = strokePadding.multiply(2), center = getCornerCenter(this, point, padding); if (center) { hit = isOnEllipseStroke(point.subtract(center.point), radius, strokePadding, center.quadrant); } else { var rect = new Rectangle(this._size).setCenter(0, 0), outer = rect.expand(padding), inner = rect.expand(padding.negate()); hit = outer._containsPoint(point) && !inner._containsPoint(point); } } else { hit = isOnEllipseStroke(point, radius, strokePadding); } } return hit ? new HitResult(hitStroke ? 'stroke' : 'fill', this) : _hitTestSelf.base.apply(this, arguments); } }; }, { statics: new function() { function createShape(type, point, size, radius, args) { var item = new Shape(Base.getNamed(args), point); item._type = type; item._size = size; item._radius = radius; return item; } return { Circle: function() { var center = Point.readNamed(arguments, 'center'), radius = Base.readNamed(arguments, 'radius'); return createShape('circle', center, new Size(radius * 2), radius, arguments); }, Rectangle: function() { var rect = Rectangle.readNamed(arguments, 'rectangle'), radius = Size.min(Size.readNamed(arguments, 'radius'), rect.getSize(true).divide(2)); return createShape('rectangle', rect.getCenter(true), rect.getSize(true), radius, arguments); }, Ellipse: function() { var ellipse = Shape._readEllipse(arguments), radius = ellipse.radius; return createShape('ellipse', ellipse.center, radius.multiply(2), radius, arguments); }, _readEllipse: function(args) { var center, radius; if (Base.hasNamed(args, 'radius')) { center = Point.readNamed(args, 'center'); radius = Size.readNamed(args, 'radius'); } else { var rect = Rectangle.readNamed(args, 'rectangle'); center = rect.getCenter(true); radius = rect.getSize(true).divide(2); } return { center: center, radius: radius }; } }; }}); var Raster = Item.extend({ _class: 'Raster', _applyMatrix: false, _canApplyMatrix: false, _boundsOptions: { stroke: false, handle: false }, _serializeFields: { crossOrigin: null, source: null }, _prioritize: ['crossOrigin'], initialize: function Raster(object, position) { if (!this._initialize(object, position !== undefined && Point.read(arguments, 1))) { var image = typeof object === 'string' ? document.getElementById(object) : object; if (image) { this.setImage(image); } else { this.setSource(object); } } if (!this._size) { this._size = new Size(); this._loaded = false; } }, _equals: function(item) { return this.getSource() === item.getSource(); }, copyContent: function(source) { var image = source._image, canvas = source._canvas; if (image) { this._setImage(image); } else if (canvas) { var copyCanvas = CanvasProvider.getCanvas(source._size); copyCanvas.getContext('2d').drawImage(canvas, 0, 0); this._setImage(copyCanvas); } this._crossOrigin = source._crossOrigin; }, getSize: function() { var size = this._size; return new LinkedSize(size ? size.width : 0, size ? size.height : 0, this, 'setSize'); }, setSize: function() { var size = Size.read(arguments); if (!size.equals(this._size)) { if (size.width > 0 && size.height > 0) { var element = this.getElement(); this._setImage(CanvasProvider.getCanvas(size)); if (element) this.getContext(true).drawImage(element, 0, 0, size.width, size.height); } else { if (this._canvas) CanvasProvider.release(this._canvas); this._size = size.clone(); } } }, getWidth: function() { return this._size ? this._size.width : 0; }, setWidth: function(width) { this.setSize(width, this.getHeight()); }, getHeight: function() { return this._size ? this._size.height : 0; }, setHeight: function(height) { this.setSize(this.getWidth(), height); }, getLoaded: function() { return this._loaded; }, isEmpty: function() { var size = this._size; return !size || size.width === 0 && size.height === 0; }, getResolution: function() { var matrix = this._matrix, orig = new Point(0, 0).transform(matrix), u = new Point(1, 0).transform(matrix).subtract(orig), v = new Point(0, 1).transform(matrix).subtract(orig); return new Size( 72 / u.getLength(), 72 / v.getLength() ); }, getPpi: '#getResolution', getImage: function() { return this._image; }, setImage: function(image) { var that = this; function emit(event) { var view = that.getView(), type = event && event.type || 'load'; if (view && that.responds(type)) { paper = view._scope; that.emit(type, new Event(event)); } } this._setImage(image); if (this._loaded) { setTimeout(emit, 0); } else if (image) { DomEvent.add(image, { load: function(event) { that._setImage(image); emit(event); }, error: emit }); } }, _setImage: function(image) { if (this._canvas) CanvasProvider.release(this._canvas); if (image && image.getContext) { this._image = null; this._canvas = image; this._loaded = true; } else { this._image = image; this._canvas = null; this._loaded = !!(image && image.src && image.complete); } this._size = new Size( image ? image.naturalWidth || image.width : 0, image ? image.naturalHeight || image.height : 0); this._context = null; this._changed(521); }, getCanvas: function() { if (!this._canvas) { var ctx = CanvasProvider.getContext(this._size); try { if (this._image) ctx.drawImage(this._image, 0, 0); this._canvas = ctx.canvas; } catch (e) { CanvasProvider.release(ctx); } } return this._canvas; }, setCanvas: '#setImage', getContext: function(modify) { if (!this._context) this._context = this.getCanvas().getContext('2d'); if (modify) { this._image = null; this._changed(513); } return this._context; }, setContext: function(context) { this._context = context; }, getSource: function() { var image = this._image; return image && image.src || this.toDataURL(); }, setSource: function(src) { var image = new self.Image(), crossOrigin = this._crossOrigin; if (crossOrigin) image.crossOrigin = crossOrigin; image.src = src; this.setImage(image); }, getCrossOrigin: function() { var image = this._image; return image && image.crossOrigin || this._crossOrigin || ''; }, setCrossOrigin: function(crossOrigin) { this._crossOrigin = crossOrigin; var image = this._image; if (image) image.crossOrigin = crossOrigin; }, getElement: function() { return this._canvas || this._loaded && this._image; } }, { beans: false, getSubCanvas: function() { var rect = Rectangle.read(arguments), ctx = CanvasProvider.getContext(rect.getSize()); ctx.drawImage(this.getCanvas(), rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height); return ctx.canvas; }, getSubRaster: function() { var rect = Rectangle.read(arguments), raster = new Raster(Item.NO_INSERT); raster._setImage(this.getSubCanvas(rect)); raster.translate(rect.getCenter().subtract(this.getSize().divide(2))); raster._matrix.prepend(this._matrix); raster.insertAbove(this); return raster; }, toDataURL: function() { var image = this._image, src = image && image.src; if (/^data:/.test(src)) return src; var canvas = this.getCanvas(); return canvas ? canvas.toDataURL.apply(canvas, arguments) : null; }, drawImage: function(image ) { var point = Point.read(arguments, 1); this.getContext(true).drawImage(image, point.x, point.y); }, getAverageColor: function(object) { var bounds, path; if (!object) { bounds = this.getBounds(); } else if (object instanceof PathItem) { path = object; bounds = object.getBounds(); } else if (typeof object === 'object') { if ('width' in object) { bounds = new Rectangle(object); } else if ('x' in object) { bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1); } } if (!bounds) return null; var sampleSize = 32, width = Math.min(bounds.width, sampleSize), height = Math.min(bounds.height, sampleSize); var ctx = Raster._sampleContext; if (!ctx) { ctx = Raster._sampleContext = CanvasProvider.getContext( new Size(sampleSize)); } else { ctx.clearRect(0, 0, sampleSize + 1, sampleSize + 1); } ctx.save(); var matrix = new Matrix() .scale(width / bounds.width, height / bounds.height) .translate(-bounds.x, -bounds.y); matrix.applyToContext(ctx); if (path) path.draw(ctx, new Base({ clip: true, matrices: [matrix] })); this._matrix.applyToContext(ctx); var element = this.getElement(), size = this._size; if (element) ctx.drawImage(element, -size.width / 2, -size.height / 2); ctx.restore(); var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width), Math.ceil(height)).data, channels = [0, 0, 0], total = 0; for (var i = 0, l = pixels.length; i < l; i += 4) { var alpha = pixels[i + 3]; total += alpha; alpha /= 255; channels[0] += pixels[i] * alpha; channels[1] += pixels[i + 1] * alpha; channels[2] += pixels[i + 2] * alpha; } for (var i = 0; i < 3; i++) channels[i] /= total; return total ? Color.read(channels) : null; }, getPixel: function() { var point = Point.read(arguments); var data = this.getContext().getImageData(point.x, point.y, 1, 1).data; return new Color('rgb', [data[0] / 255, data[1] / 255, data[2] / 255], data[3] / 255); }, setPixel: function() { var point = Point.read(arguments), color = Color.read(arguments), components = color._convert('rgb'), alpha = color._alpha, ctx = this.getContext(true), imageData = ctx.createImageData(1, 1), data = imageData.data; data[0] = components[0] * 255; data[1] = components[1] * 255; data[2] = components[2] * 255; data[3] = alpha != null ? alpha * 255 : 255; ctx.putImageData(imageData, point.x, point.y); }, createImageData: function() { var size = Size.read(arguments); return this.getContext().createImageData(size.width, size.height); }, getImageData: function() { var rect = Rectangle.read(arguments); if (rect.isEmpty()) rect = new Rectangle(this._size); return this.getContext().getImageData(rect.x, rect.y, rect.width, rect.height); }, setImageData: function(data ) { var point = Point.read(arguments, 1); this.getContext(true).putImageData(data, point.x, point.y); }, _getBounds: function(matrix, options) { var rect = new Rectangle(this._size).setCenter(0, 0); return matrix ? matrix._transformBounds(rect) : rect; }, _hitTestSelf: function(point) { if (this._contains(point)) { var that = this; return new HitResult('pixel', that, { offset: point.add(that._size.divide(2)).round(), color: { get: function() { return that.getPixel(this.offset); } } }); } }, _draw: function(ctx) { var element = this.getElement(); if (element) { ctx.globalAlpha = this._opacity; ctx.drawImage(element, -this._size.width / 2, -this._size.height / 2); } }, _canComposite: function() { return true; } }); var SymbolItem = Item.extend({ _class: 'SymbolItem', _applyMatrix: false, _canApplyMatrix: false, _boundsOptions: { stroke: true }, _serializeFields: { symbol: null }, initialize: function SymbolItem(arg0, arg1) { if (!this._initialize(arg0, arg1 !== undefined && Point.read(arguments, 1))) this.setDefinition(arg0 instanceof SymbolDefinition ? arg0 : new SymbolDefinition(arg0)); }, _equals: function(item) { return this._definition === item._definition; }, copyContent: function(source) { this.setDefinition(source._definition); }, getDefinition: function() { return this._definition; }, setDefinition: function(definition) { this._definition = definition; this._changed(9); }, getSymbol: '#getDefinition', setSymbol: '#setDefinition', isEmpty: function() { return this._definition._item.isEmpty(); }, _getBounds: function(matrix, options) { var item = this._definition._item; return item._getCachedBounds(item._matrix.prepended(matrix), options); }, _hitTestSelf: function(point, options, viewMatrix) { var res = this._definition._item._hitTest(point, options, viewMatrix); if (res) res.item = this; return res; }, _draw: function(ctx, param) { this._definition._item.draw(ctx, param); } }); var SymbolDefinition = Base.extend({ _class: 'SymbolDefinition', initialize: function SymbolDefinition(item, dontCenter) { this._id = UID.get(); this.project = paper.project; if (item) this.setItem(item, dontCenter); }, _serialize: function(options, dictionary) { return dictionary.add(this, function() { return Base.serialize([this._class, this._item], options, false, dictionary); }); }, _changed: function(flags) { if (flags & 8) Item._clearBoundsCache(this); if (flags & 1) this.project._changed(flags); }, getItem: function() { return this._item; }, setItem: function(item, _dontCenter) { if (item._symbol) item = item.clone(); if (this._item) this._item._symbol = null; this._item = item; item.remove(); item.setSelected(false); if (!_dontCenter) item.setPosition(new Point()); item._symbol = this; this._changed(9); }, getDefinition: '#getItem', setDefinition: '#setItem', place: function(position) { return new SymbolItem(this, position); }, clone: function() { return new SymbolDefinition(this._item.clone(false)); }, equals: function(symbol) { return symbol === this || symbol && this._item.equals(symbol._item) || false; } }); var HitResult = Base.extend({ _class: 'HitResult', initialize: function HitResult(type, item, values) { this.type = type; this.item = item; if (values) this.inject(values); }, statics: { getOptions: function(args) { var options = args && Base.read(args); return Base.set({ type: null, tolerance: paper.settings.hitTolerance, fill: !options, stroke: !options, segments: !options, handles: false, ends: false, position: false, center: false, bounds: false, guides: false, selected: false }, options); } } }); var Segment = Base.extend({ _class: 'Segment', beans: true, _selection: 0, initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) { var count = arguments.length, point, handleIn, handleOut, selection; if (count > 0) { if (arg0 == null || typeof arg0 === 'object') { if (count === 1 && arg0 && 'point' in arg0) { point = arg0.point; handleIn = arg0.handleIn; handleOut = arg0.handleOut; selection = arg0.selection; } else { point = arg0; handleIn = arg1; handleOut = arg2; selection = arg3; } } else { point = [ arg0, arg1 ]; handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null; handleOut = arg4 !== undefined ? [ arg4, arg5 ] : null; } } new SegmentPoint(point, this, '_point'); new SegmentPoint(handleIn, this, '_handleIn'); new SegmentPoint(handleOut, this, '_handleOut'); if (selection) this.setSelection(selection); }, _serialize: function(options, dictionary) { var point = this._point, selection = this._selection, obj = selection || this.hasHandles() ? [point, this._handleIn, this._handleOut] : point; if (selection) obj.push(selection); return Base.serialize(obj, options, true, dictionary); }, _changed: function(point) { var path = this._path; if (!path) return; var curves = path._curves, index = this._index, curve; if (curves) { if ((!point || point === this._point || point === this._handleIn) && (curve = index > 0 ? curves[index - 1] : path._closed ? curves[curves.length - 1] : null)) curve._changed(); if ((!point || point === this._point || point === this._handleOut) && (curve = curves[index])) curve._changed(); } path._changed(25); }, getPoint: function() { return this._point; }, setPoint: function() { this._point.set(Point.read(arguments)); }, getHandleIn: function() { return this._handleIn; }, setHandleIn: function() { this._handleIn.set(Point.read(arguments)); }, getHandleOut: function() { return this._handleOut; }, setHandleOut: function() { this._handleOut.set(Point.read(arguments)); }, hasHandles: function() { return !this._handleIn.isZero() || !this._handleOut.isZero(); }, isSmooth: function() { var handleIn = this._handleIn, handleOut = this._handleOut; return !handleIn.isZero() && !handleOut.isZero() && handleIn.isCollinear(handleOut); }, clearHandles: function() { this._handleIn._set(0, 0); this._handleOut._set(0, 0); }, getSelection: function() { return this._selection; }, setSelection: function(selection) { var oldSelection = this._selection, path = this._path; this._selection = selection = selection || 0; if (path && selection !== oldSelection) { path._updateSelection(this, oldSelection, selection); path._changed(129); } }, _changeSelection: function(flag, selected) { var selection = this._selection; this.setSelection(selected ? selection | flag : selection & ~flag); }, isSelected: function() { return !!(this._selection & 7); }, setSelected: function(selected) { this._changeSelection(7, selected); }, getIndex: function() { return this._index !== undefined ? this._index : null; }, getPath: function() { return this._path || null; }, getCurve: function() { var path = this._path, index = this._index; if (path) { if (index > 0 && !path._closed && index === path._segments.length - 1) index--; return path.getCurves()[index] || null; } return null; }, getLocation: function() { var curve = this.getCurve(); return curve ? new CurveLocation(curve, this === curve._segment1 ? 0 : 1) : null; }, getNext: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index + 1] || this._path._closed && segments[0]) || null; }, smooth: function(options, _first, _last) { var opts = options || {}, type = opts.type, factor = opts.factor, prev = this.getPrevious(), next = this.getNext(), p0 = (prev || this)._point, p1 = this._point, p2 = (next || this)._point, d1 = p0.getDistance(p1), d2 = p1.getDistance(p2); if (!type || type === 'catmull-rom') { var a = factor === undefined ? 0.5 : factor, d1_a = Math.pow(d1, a), d1_2a = d1_a * d1_a, d2_a = Math.pow(d2, a), d2_2a = d2_a * d2_a; if (!_first && prev) { var A = 2 * d2_2a + 3 * d2_a * d1_a + d1_2a, N = 3 * d2_a * (d2_a + d1_a); this.setHandleIn(N !== 0 ? new Point( (d2_2a * p0._x + A * p1._x - d1_2a * p2._x) / N - p1._x, (d2_2a * p0._y + A * p1._y - d1_2a * p2._y) / N - p1._y) : new Point()); } if (!_last && next) { var A = 2 * d1_2a + 3 * d1_a * d2_a + d2_2a, N = 3 * d1_a * (d1_a + d2_a); this.setHandleOut(N !== 0 ? new Point( (d1_2a * p2._x + A * p1._x - d2_2a * p0._x) / N - p1._x, (d1_2a * p2._y + A * p1._y - d2_2a * p0._y) / N - p1._y) : new Point()); } } else if (type === 'geometric') { if (prev && next) { var vector = p0.subtract(p2), t = factor === undefined ? 0.4 : factor, k = t * d1 / (d1 + d2); if (!_first) this.setHandleIn(vector.multiply(k)); if (!_last) this.setHandleOut(vector.multiply(k - t)); } } else { throw new Error('Smoothing method \'' + type + '\' not supported.'); } }, getPrevious: function() { var segments = this._path && this._path._segments; return segments && (segments[this._index - 1] || this._path._closed && segments[segments.length - 1]) || null; }, isFirst: function() { return !this._index; }, isLast: function() { var path = this._path; return path && this._index === path._segments.length - 1 || false; }, reverse: function() { var handleIn = this._handleIn, handleOut = this._handleOut, tmp = handleIn.clone(); handleIn.set(handleOut); handleOut.set(tmp); }, reversed: function() { return new Segment(this._point, this._handleOut, this._handleIn); }, remove: function() { return this._path ? !!this._path.removeSegment(this._index) : false; }, clone: function() { return new Segment(this._point, this._handleIn, this._handleOut); }, equals: function(segment) { return segment === this || segment && this._class === segment._class && this._point.equals(segment._point) && this._handleIn.equals(segment._handleIn) && this._handleOut.equals(segment._handleOut) || false; }, toString: function() { var parts = [ 'point: ' + this._point ]; if (!this._handleIn.isZero()) parts.push('handleIn: ' + this._handleIn); if (!this._handleOut.isZero()) parts.push('handleOut: ' + this._handleOut); return '{ ' + parts.join(', ') + ' }'; }, transform: function(matrix) { this._transformCoordinates(matrix, new Array(6), true); this._changed(); }, interpolate: function(from, to, factor) { var u = 1 - factor, v = factor, point1 = from._point, point2 = to._point, handleIn1 = from._handleIn, handleIn2 = to._handleIn, handleOut2 = to._handleOut, handleOut1 = from._handleOut; this._point._set( u * point1._x + v * point2._x, u * point1._y + v * point2._y, true); this._handleIn._set( u * handleIn1._x + v * handleIn2._x, u * handleIn1._y + v * handleIn2._y, true); this._handleOut._set( u * handleOut1._x + v * handleOut2._x, u * handleOut1._y + v * handleOut2._y, true); this._changed(); }, _transformCoordinates: function(matrix, coords, change) { var point = this._point, handleIn = !change || !this._handleIn.isZero() ? this._handleIn : null, handleOut = !change || !this._handleOut.isZero() ? this._handleOut : null, x = point._x, y = point._y, i = 2; coords[0] = x; coords[1] = y; if (handleIn) { coords[i++] = handleIn._x + x; coords[i++] = handleIn._y + y; } if (handleOut) { coords[i++] = handleOut._x + x; coords[i++] = handleOut._y + y; } if (matrix) { matrix._transformCoordinates(coords, coords, i / 2); x = coords[0]; y = coords[1]; if (change) { point._x = x; point._y = y; i = 2; if (handleIn) { handleIn._x = coords[i++] - x; handleIn._y = coords[i++] - y; } if (handleOut) { handleOut._x = coords[i++] - x; handleOut._y = coords[i++] - y; } } else { if (!handleIn) { coords[i++] = x; coords[i++] = y; } if (!handleOut) { coords[i++] = x; coords[i++] = y; } } } return coords; } }); var SegmentPoint = Point.extend({ initialize: function SegmentPoint(point, owner, key) { var x, y, selected; if (!point) { x = y = 0; } else if ((x = point[0]) !== undefined) { y = point[1]; } else { var pt = point; if ((x = pt.x) === undefined) { pt = Point.read(arguments); x = pt.x; } y = pt.y; selected = pt.selected; } this._x = x; this._y = y; this._owner = owner; owner[key] = this; if (selected) this.setSelected(true); }, _set: function(x, y) { this._x = x; this._y = y; this._owner._changed(this); return this; }, getX: function() { return this._x; }, setX: function(x) { this._x = x; this._owner._changed(this); }, getY: function() { return this._y; }, setY: function(y) { this._y = y; this._owner._changed(this); }, isZero: function() { var isZero = Numerical.isZero; return isZero(this._x) && isZero(this._y); }, isSelected: function() { return !!(this._owner._selection & this._getSelection()); }, setSelected: function(selected) { this._owner._changeSelection(this._getSelection(), selected); }, _getSelection: function() { var owner = this._owner; return this === owner._point ? 1 : this === owner._handleIn ? 2 : this === owner._handleOut ? 4 : 0; } }); var Curve = Base.extend({ _class: 'Curve', beans: true, initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { var count = arguments.length, seg1, seg2, point1, point2, handle1, handle2; if (count === 3) { this._path = arg0; seg1 = arg1; seg2 = arg2; } else if (!count) { seg1 = new Segment(); seg2 = new Segment(); } else if (count === 1) { if ('segment1' in arg0) { seg1 = new Segment(arg0.segment1); seg2 = new Segment(arg0.segment2); } else if ('point1' in arg0) { point1 = arg0.point1; handle1 = arg0.handle1; handle2 = arg0.handle2; point2 = arg0.point2; } else if (Array.isArray(arg0)) { point1 = [arg0[0], arg0[1]]; point2 = [arg0[6], arg0[7]]; handle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]]; handle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]]; } } else if (count === 2) { seg1 = new Segment(arg0); seg2 = new Segment(arg1); } else if (count === 4) { point1 = arg0; handle1 = arg1; handle2 = arg2; point2 = arg3; } else if (count === 8) { point1 = [arg0, arg1]; point2 = [arg6, arg7]; handle1 = [arg2 - arg0, arg3 - arg1]; handle2 = [arg4 - arg6, arg5 - arg7]; } this._segment1 = seg1 || new Segment(point1, null, handle1); this._segment2 = seg2 || new Segment(point2, handle2, null); }, _serialize: function(options, dictionary) { return Base.serialize(this.hasHandles() ? [this.getPoint1(), this.getHandle1(), this.getHandle2(), this.getPoint2()] : [this.getPoint1(), this.getPoint2()], options, true, dictionary); }, _changed: function() { this._length = this._bounds = undefined; }, clone: function() { return new Curve(this._segment1, this._segment2); }, toString: function() { var parts = [ 'point1: ' + this._segment1._point ]; if (!this._segment1._handleOut.isZero()) parts.push('handle1: ' + this._segment1._handleOut); if (!this._segment2._handleIn.isZero()) parts.push('handle2: ' + this._segment2._handleIn); parts.push('point2: ' + this._segment2._point); return '{ ' + parts.join(', ') + ' }'; }, classify: function() { return Curve.classify(this.getValues()); }, remove: function() { var removed = false; if (this._path) { var segment2 = this._segment2, handleOut = segment2._handleOut; removed = segment2.remove(); if (removed) this._segment1._handleOut.set(handleOut); } return removed; }, getPoint1: function() { return this._segment1._point; }, setPoint1: function() { this._segment1._point.set(Point.read(arguments)); }, getPoint2: function() { return this._segment2._point; }, setPoint2: function() { this._segment2._point.set(Point.read(arguments)); }, getHandle1: function() { return this._segment1._handleOut; }, setHandle1: function() { this._segment1._handleOut.set(Point.read(arguments)); }, getHandle2: function() { return this._segment2._handleIn; }, setHandle2: function() { this._segment2._handleIn.set(Point.read(arguments)); }, getSegment1: function() { return this._segment1; }, getSegment2: function() { return this._segment2; }, getPath: function() { return this._path; }, getIndex: function() { return this._segment1._index; }, getNext: function() { var curves = this._path && this._path._curves; return curves && (curves[this._segment1._index + 1] || this._path._closed && curves[0]) || null; }, getPrevious: function() { var curves = this._path && this._path._curves; return curves && (curves[this._segment1._index - 1] || this._path._closed && curves[curves.length - 1]) || null; }, isFirst: function() { return !this._segment1._index; }, isLast: function() { var path = this._path; return path && this._segment1._index === path._curves.length - 1 || false; }, isSelected: function() { return this.getPoint1().isSelected() && this.getHandle1().isSelected() && this.getHandle2().isSelected() && this.getPoint2().isSelected(); }, setSelected: function(selected) { this.getPoint1().setSelected(selected); this.getHandle1().setSelected(selected); this.getHandle2().setSelected(selected); this.getPoint2().setSelected(selected); }, getValues: function(matrix) { return Curve.getValues(this._segment1, this._segment2, matrix); }, getPoints: function() { var coords = this.getValues(), points = []; for (var i = 0; i < 8; i += 2) points.push(new Point(coords[i], coords[i + 1])); return points; } }, { getLength: function() { if (this._length == null) this._length = Curve.getLength(this.getValues(), 0, 1); return this._length; }, getArea: function() { return Curve.getArea(this.getValues()); }, getLine: function() { return new Line(this._segment1._point, this._segment2._point); }, getPart: function(from, to) { return new Curve(Curve.getPart(this.getValues(), from, to)); }, getPartLength: function(from, to) { return Curve.getLength(this.getValues(), from, to); }, divideAt: function(location) { return this.divideAtTime(location && location.curve === this ? location.time : this.getTimeAt(location)); }, divideAtTime: function(time, _setHandles) { var tMin = 1e-8, tMax = 1 - tMin, res = null; if (time >= tMin && time <= tMax) { var parts = Curve.subdivide(this.getValues(), time), left = parts[0], right = parts[1], setHandles = _setHandles || this.hasHandles(), seg1 = this._segment1, seg2 = this._segment2, path = this._path; if (setHandles) { seg1._handleOut._set(left[2] - left[0], left[3] - left[1]); seg2._handleIn._set(right[4] - right[6],right[5] - right[7]); } var x = left[6], y = left[7], segment = new Segment(new Point(x, y), setHandles && new Point(left[4] - x, left[5] - y), setHandles && new Point(right[2] - x, right[3] - y)); if (path) { path.insert(seg1._index + 1, segment); res = this.getNext(); } else { this._segment2 = segment; this._changed(); res = new Curve(segment, seg2); } } return res; }, splitAt: function(location) { var path = this._path; return path ? path.splitAt(location) : null; }, splitAtTime: function(time) { return this.splitAt(this.getLocationAtTime(time)); }, divide: function(offset, isTime) { return this.divideAtTime(offset === undefined ? 0.5 : isTime ? offset : this.getTimeAt(offset)); }, split: function(offset, isTime) { return this.splitAtTime(offset === undefined ? 0.5 : isTime ? offset : this.getTimeAt(offset)); }, reversed: function() { return new Curve(this._segment2.reversed(), this._segment1.reversed()); }, clearHandles: function() { this._segment1._handleOut._set(0, 0); this._segment2._handleIn._set(0, 0); }, statics: { getValues: function(segment1, segment2, matrix, straight) { var p1 = segment1._point, h1 = segment1._handleOut, h2 = segment2._handleIn, p2 = segment2._point, x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y, values = straight ? [ x1, y1, x1, y1, x2, y2, x2, y2 ] : [ x1, y1, x1 + h1._x, y1 + h1._y, x2 + h2._x, y2 + h2._y, x2, y2 ]; if (matrix) matrix._transformCoordinates(values, values, 4); return values; }, subdivide: function(v, t) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7]; if (t === undefined) t = 0.5; var u = 1 - t, x4 = u * x0 + t * x1, y4 = u * y0 + t * y1, x5 = u * x1 + t * x2, y5 = u * y1 + t * y2, x6 = u * x2 + t * x3, y6 = u * y2 + t * y3, x7 = u * x4 + t * x5, y7 = u * y4 + t * y5, x8 = u * x5 + t * x6, y8 = u * y5 + t * y6, x9 = u * x7 + t * x8, y9 = u * y7 + t * y8; return [ [x0, y0, x4, y4, x7, y7, x9, y9], [x9, y9, x8, y8, x6, y6, x3, y3] ]; }, getMonoCurves: function(v, dir) { var curves = [], io = dir ? 0 : 1, o0 = v[io + 0], o1 = v[io + 2], o2 = v[io + 4], o3 = v[io + 6]; if ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3) || Curve.isStraight(v)) { curves.push(v); } else { var a = 3 * (o1 - o2) - o0 + o3, b = 2 * (o0 + o2) - 4 * o1, c = o1 - o0, tMin = 1e-8, tMax = 1 - tMin, roots = [], n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax); if (!n) { curves.push(v); } else { roots.sort(); var t = roots[0], parts = Curve.subdivide(v, t); curves.push(parts[0]); if (n > 1) { t = (roots[1] - t) / (1 - t); parts = Curve.subdivide(parts[1], t); curves.push(parts[0]); } curves.push(parts[1]); } } return curves; }, solveCubic: function (v, coord, val, roots, min, max) { var v0 = v[coord], v1 = v[coord + 2], v2 = v[coord + 4], v3 = v[coord + 6], res = 0; if ( !(v0 < val && v3 < val && v1 < val && v2 < val || v0 > val && v3 > val && v1 > val && v2 > val)) { var c = 3 * (v1 - v0), b = 3 * (v2 - v1) - c, a = v3 - v0 - c - b; res = Numerical.solveCubic(a, b, c, v0 - val, roots, min, max); } return res; }, getTimeOf: function(v, point) { var p0 = new Point(v[0], v[1]), p3 = new Point(v[6], v[7]), epsilon = 1e-12, geomEpsilon = 1e-7, t = point.isClose(p0, epsilon) ? 0 : point.isClose(p3, epsilon) ? 1 : null; if (t === null) { var coords = [point.x, point.y], roots = []; for (var c = 0; c < 2; c++) { var count = Curve.solveCubic(v, c, coords[c], roots, 0, 1); for (var i = 0; i < count; i++) { var u = roots[i]; if (point.isClose(Curve.getPoint(v, u), geomEpsilon)) return u; } } } return point.isClose(p0, geomEpsilon) ? 0 : point.isClose(p3, geomEpsilon) ? 1 : null; }, getNearestTime: function(v, point) { if (Curve.isStraight(v)) { var x0 = v[0], y0 = v[1], x3 = v[6], y3 = v[7], vx = x3 - x0, vy = y3 - y0, det = vx * vx + vy * vy; if (det === 0) return 0; var u = ((point.x - x0) * vx + (point.y - y0) * vy) / det; return u < 1e-12 ? 0 : u > 0.999999999999 ? 1 : Curve.getTimeOf(v, new Point(x0 + u * vx, y0 + u * vy)); } var count = 100, minDist = Infinity, minT = 0; function refine(t) { if (t >= 0 && t <= 1) { var dist = point.getDistance(Curve.getPoint(v, t), true); if (dist < minDist) { minDist = dist; minT = t; return true; } } } for (var i = 0; i <= count; i++) refine(i / count); var step = 1 / (count * 2); while (step > 1e-8) { if (!refine(minT - step) && !refine(minT + step)) step /= 2; } return minT; }, getPart: function(v, from, to) { var flip = from > to; if (flip) { var tmp = from; from = to; to = tmp; } if (from > 0) v = Curve.subdivide(v, from)[1]; if (to < 1) v = Curve.subdivide(v, (to - from) / (1 - from))[0]; return flip ? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]] : v; }, isFlatEnough: function(v, flatness) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7], ux = 3 * x1 - 2 * x0 - x3, uy = 3 * y1 - 2 * y0 - y3, vx = 3 * x2 - 2 * x3 - x0, vy = 3 * y2 - 2 * y3 - y0; return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy) <= 16 * flatness * flatness; }, getArea: function(v) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7]; return 3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2) + y1 * (x0 - x2) - x1 * (y0 - y2) + y3 * (x2 + x0 / 3) - x3 * (y2 + y0 / 3)) / 20; }, getBounds: function(v) { var min = v.slice(0, 2), max = min.slice(), roots = [0, 0]; for (var i = 0; i < 2; i++) Curve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6], i, 0, min, max, roots); return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]); }, _addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) { function add(value, padding) { var left = value - padding, right = value + padding; if (left < min[coord]) min[coord] = left; if (right > max[coord]) max[coord] = right; } padding /= 2; var minPad = min[coord] - padding, maxPad = max[coord] + padding; if ( v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad || v0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) { if (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) { add(v0, padding); add(v3, padding); } else { var a = 3 * (v1 - v2) - v0 + v3, b = 2 * (v0 + v2) - 4 * v1, c = v1 - v0, count = Numerical.solveQuadratic(a, b, c, roots), tMin = 1e-8, tMax = 1 - tMin; add(v3, 0); for (var i = 0; i < count; i++) { var t = roots[i], u = 1 - t; if (tMin <= t && t <= tMax) add(u * u * u * v0 + 3 * u * u * t * v1 + 3 * u * t * t * v2 + t * t * t * v3, padding); } } } } }}, Base.each( ['getBounds', 'getStrokeBounds', 'getHandleBounds'], function(name) { this[name] = function() { if (!this._bounds) this._bounds = {}; var bounds = this._bounds[name]; if (!bounds) { bounds = this._bounds[name] = Path[name]( [this._segment1, this._segment2], false, this._path); } return bounds.clone(); }; }, { }), Base.each({ isStraight: function(p1, h1, h2, p2) { if (h1.isZero() && h2.isZero()) { return true; } else { var v = p2.subtract(p1); if (v.isZero()) { return false; } else if (v.isCollinear(h1) && v.isCollinear(h2)) { var l = new Line(p1, p2), epsilon = 1e-7; if (l.getDistance(p1.add(h1)) < epsilon && l.getDistance(p2.add(h2)) < epsilon) { var div = v.dot(v), s1 = v.dot(h1) / div, s2 = v.dot(h2) / div; return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1; } } } return false; }, isLinear: function(p1, h1, h2, p2) { var third = p2.subtract(p1).divide(3); return h1.equals(third) && h2.negate().equals(third); } }, function(test, name) { this[name] = function(epsilon) { var seg1 = this._segment1, seg2 = this._segment2; return test(seg1._point, seg1._handleOut, seg2._handleIn, seg2._point, epsilon); }; this.statics[name] = function(v, epsilon) { var x0 = v[0], y0 = v[1], x3 = v[6], y3 = v[7]; return test( new Point(x0, y0), new Point(v[2] - x0, v[3] - y0), new Point(v[4] - x3, v[5] - y3), new Point(x3, y3), epsilon); }; }, { statics: {}, hasHandles: function() { return !this._segment1._handleOut.isZero() || !this._segment2._handleIn.isZero(); }, hasLength: function(epsilon) { return (!this.getPoint1().equals(this.getPoint2()) || this.hasHandles()) && this.getLength() > (epsilon || 0); }, isCollinear: function(curve) { return curve && this.isStraight() && curve.isStraight() && this.getLine().isCollinear(curve.getLine()); }, isHorizontal: function() { return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).y) < 1e-8; }, isVertical: function() { return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).x) < 1e-8; } }), { beans: false, getLocationAt: function(offset, _isTime) { return this.getLocationAtTime( _isTime ? offset : this.getTimeAt(offset)); }, getLocationAtTime: function(t) { return t != null && t >= 0 && t <= 1 ? new CurveLocation(this, t) : null; }, getTimeAt: function(offset, start) { return Curve.getTimeAt(this.getValues(), offset, start); }, getParameterAt: '#getTimeAt', getOffsetAtTime: function(t) { return this.getPartLength(0, t); }, getLocationOf: function() { return this.getLocationAtTime(this.getTimeOf(Point.read(arguments))); }, getOffsetOf: function() { var loc = this.getLocationOf.apply(this, arguments); return loc ? loc.getOffset() : null; }, getTimeOf: function() { return Curve.getTimeOf(this.getValues(), Point.read(arguments)); }, getParameterOf: '#getTimeOf', getNearestLocation: function() { var point = Point.read(arguments), values = this.getValues(), t = Curve.getNearestTime(values, point), pt = Curve.getPoint(values, t); return new CurveLocation(this, t, pt, null, point.getDistance(pt)); }, getNearestPoint: function() { var loc = this.getNearestLocation.apply(this, arguments); return loc ? loc.getPoint() : loc; } }, new function() { var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent', 'getWeightedNormal', 'getCurvature']; return Base.each(methods, function(name) { this[name + 'At'] = function(location, _isTime) { var values = this.getValues(); return Curve[name](values, _isTime ? location : Curve.getTimeAt(values, location)); }; this[name + 'AtTime'] = function(time) { return Curve[name](this.getValues(), time); }; }, { statics: { _evaluateMethods: methods } } ); }, new function() { function getLengthIntegrand(v) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7], ax = 9 * (x1 - x2) + 3 * (x3 - x0), bx = 6 * (x0 + x2) - 12 * x1, cx = 3 * (x1 - x0), ay = 9 * (y1 - y2) + 3 * (y3 - y0), by = 6 * (y0 + y2) - 12 * y1, cy = 3 * (y1 - y0); return function(t) { var dx = (ax * t + bx) * t + cx, dy = (ay * t + by) * t + cy; return Math.sqrt(dx * dx + dy * dy); }; } function getIterations(a, b) { return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32))); } function evaluate(v, t, type, normalized) { if (t == null || t < 0 || t > 1) return null; var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7], isZero = Numerical.isZero; if (isZero(x1 - x0) && isZero(y1 - y0)) { x1 = x0; y1 = y0; } if (isZero(x2 - x3) && isZero(y2 - y3)) { x2 = x3; y2 = y3; } var cx = 3 * (x1 - x0), bx = 3 * (x2 - x1) - cx, ax = x3 - x0 - cx - bx, cy = 3 * (y1 - y0), by = 3 * (y2 - y1) - cy, ay = y3 - y0 - cy - by, x, y; if (type === 0) { x = t === 0 ? x0 : t === 1 ? x3 : ((ax * t + bx) * t + cx) * t + x0; y = t === 0 ? y0 : t === 1 ? y3 : ((ay * t + by) * t + cy) * t + y0; } else { var tMin = 1e-8, tMax = 1 - tMin; if (t < tMin) { x = cx; y = cy; } else if (t > tMax) { x = 3 * (x3 - x2); y = 3 * (y3 - y2); } else { x = (3 * ax * t + 2 * bx) * t + cx; y = (3 * ay * t + 2 * by) * t + cy; } if (normalized) { if (x === 0 && y === 0 && (t < tMin || t > tMax)) { x = x2 - x1; y = y2 - y1; } var len = Math.sqrt(x * x + y * y); if (len) { x /= len; y /= len; } } if (type === 3) { var x2 = 6 * ax * t + 2 * bx, y2 = 6 * ay * t + 2 * by, d = Math.pow(x * x + y * y, 3 / 2); x = d !== 0 ? (x * y2 - y * x2) / d : 0; y = 0; } } return type === 2 ? new Point(y, -x) : new Point(x, y); } return { statics: { classify: function(v) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7], a1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2, a2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3, a3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0, d3 = 3 * a3, d2 = d3 - a2, d1 = d2 - a2 + a1, l = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3), s = l !== 0 ? 1 / l : 0, isZero = Numerical.isZero, serpentine = 'serpentine'; d1 *= s; d2 *= s; d3 *= s; function type(type, t1, t2) { var hasRoots = t1 !== undefined, t1Ok = hasRoots && t1 > 0 && t1 < 1, t2Ok = hasRoots && t2 > 0 && t2 < 1; if (hasRoots && (!(t1Ok || t2Ok) || type === 'loop' && !(t1Ok && t2Ok))) { type = 'arch'; t1Ok = t2Ok = false; } return { type: type, roots: t1Ok || t2Ok ? t1Ok && t2Ok ? t1 < t2 ? [t1, t2] : [t2, t1] : [t1Ok ? t1 : t2] : null }; } if (isZero(d1)) { return isZero(d2) ? type(isZero(d3) ? 'line' : 'quadratic') : type(serpentine, d3 / (3 * d2)); } var d = 3 * d2 * d2 - 4 * d1 * d3; if (isZero(d)) { return type('cusp', d2 / (2 * d1)); } var f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d), f2 = 2 * d1; return type(d > 0 ? serpentine : 'loop', (d2 + f1) / f2, (d2 - f1) / f2); }, getLength: function(v, a, b, ds) { if (a === undefined) a = 0; if (b === undefined) b = 1; if (Curve.isStraight(v)) { var c = v; if (b < 1) { c = Curve.subdivide(c, b)[0]; a /= b; } if (a > 0) { c = Curve.subdivide(c, a)[1]; } var dx = c[6] - c[0], dy = c[7] - c[1]; return Math.sqrt(dx * dx + dy * dy); } return Numerical.integrate(ds || getLengthIntegrand(v), a, b, getIterations(a, b)); }, getTimeAt: function(v, offset, start) { if (start === undefined) start = offset < 0 ? 1 : 0; if (offset === 0) return start; var abs = Math.abs, epsilon = 1e-12, forward = offset > 0, a = forward ? start : 0, b = forward ? 1 : start, ds = getLengthIntegrand(v), rangeLength = Curve.getLength(v, a, b, ds), diff = abs(offset) - rangeLength; if (abs(diff) < epsilon) { return forward ? b : a; } else if (diff > epsilon) { return null; } var guess = offset / rangeLength, length = 0; function f(t) { length += Numerical.integrate(ds, start, t, getIterations(start, t)); start = t; return length - offset; } return Numerical.findRoot(f, ds, start + guess, a, b, 32, 1e-12); }, getPoint: function(v, t) { return evaluate(v, t, 0, false); }, getTangent: function(v, t) { return evaluate(v, t, 1, true); }, getWeightedTangent: function(v, t) { return evaluate(v, t, 1, false); }, getNormal: function(v, t) { return evaluate(v, t, 2, true); }, getWeightedNormal: function(v, t) { return evaluate(v, t, 2, false); }, getCurvature: function(v, t) { return evaluate(v, t, 3, false).x; }, getPeaks: function(v) { var x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7], ax = -x0 + 3 * x1 - 3 * x2 + x3, bx = 3 * x0 - 6 * x1 + 3 * x2, cx = -3 * x0 + 3 * x1, ay = -y0 + 3 * y1 - 3 * y2 + y3, by = 3 * y0 - 6 * y1 + 3 * y2, cy = -3 * y0 + 3 * y1, tMin = 1e-8, tMax = 1 - tMin, roots = []; Numerical.solveCubic( 9 * (ax * ax + ay * ay), 9 * (ax * bx + by * ay), 2 * (bx * bx + by * by) + 3 * (cx * ax + cy * ay), (cx * bx + by * cy), roots, tMin, tMax); return roots.sort(); } }}; }, new function() { function addLocation(locations, include, c1, t1, c2, t2, overlap) { var excludeStart = !overlap && c1.getPrevious() === c2, excludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2, tMin = 1e-8, tMax = 1 - tMin; if (t1 !== null && t1 >= (excludeStart ? tMin : 0) && t1 <= (excludeEnd ? tMax : 1)) { if (t2 !== null && t2 >= (excludeEnd ? tMin : 0) && t2 <= (excludeStart ? tMax : 1)) { var loc1 = new CurveLocation(c1, t1, null, overlap), loc2 = new CurveLocation(c2, t2, null, overlap); loc1._intersection = loc2; loc2._intersection = loc1; if (!include || include(loc1)) { CurveLocation.insert(locations, loc1, true); } } } } function addCurveIntersections(v1, v2, c1, c2, locations, include, flip, recursion, calls, tMin, tMax, uMin, uMax) { if (++calls >= 4096 || ++recursion >= 40) return calls; var fatLineEpsilon = 1e-9, q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7], getSignedDistance = Line.getSignedDistance, d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]), d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]), factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, dMin = factor * Math.min(0, d1, d2), dMax = factor * Math.max(0, d1, d2), dp0 = getSignedDistance(q0x, q0y, q3x, q3y, v1[0], v1[1]), dp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]), dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]), dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]), hull = getConvexHull(dp0, dp1, dp2, dp3), top = hull[0], bottom = hull[1], tMinClip, tMaxClip; if (d1 === 0 && d2 === 0 && dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0 || (tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null || (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(), dMin, dMax)) == null) return calls; var tMinNew = tMin + (tMax - tMin) * tMinClip, tMaxNew = tMin + (tMax - tMin) * tMaxClip; if (Math.max(uMax - uMin, tMaxNew - tMinNew) < fatLineEpsilon) { var t = (tMinNew + tMaxNew) / 2, u = (uMin + uMax) / 2; addLocation(locations, include, flip ? c2 : c1, flip ? u : t, flip ? c1 : c2, flip ? t : u); } else { v1 = Curve.getPart(v1, tMinClip, tMaxClip); if (tMaxClip - tMinClip > 0.8) { if (tMaxNew - tMinNew > uMax - uMin) { var parts = Curve.subdivide(v1, 0.5), t = (tMinNew + tMaxNew) / 2; calls = addCurveIntersections( v2, parts[0], c2, c1, locations, include, !flip, recursion, calls, uMin, uMax, tMinNew, t); calls = addCurveIntersections( v2, parts[1], c2, c1, locations, include, !flip, recursion, calls, uMin, uMax, t, tMaxNew); } else { var parts = Curve.subdivide(v2, 0.5), u = (uMin + uMax) / 2; calls = addCurveIntersections( parts[0], v1, c2, c1, locations, include, !flip, recursion, calls, uMin, u, tMinNew, tMaxNew); calls = addCurveIntersections( parts[1], v1, c2, c1, locations, include, !flip, recursion, calls, u, uMax, tMinNew, tMaxNew); } } else { if (uMax - uMin >= fatLineEpsilon) { calls = addCurveIntersections( v2, v1, c2, c1, locations, include, !flip, recursion, calls, uMin, uMax, tMinNew, tMaxNew); } else { calls = addCurveIntersections( v1, v2, c1, c2, locations, include, flip, recursion, calls, tMinNew, tMaxNew, uMin, uMax); } } } return calls; } function getConvexHull(dq0, dq1, dq2, dq3) { var p0 = [ 0, dq0 ], p1 = [ 1 / 3, dq1 ], p2 = [ 2 / 3, dq2 ], p3 = [ 1, dq3 ], dist1 = dq1 - (2 * dq0 + dq3) / 3, dist2 = dq2 - (dq0 + 2 * dq3) / 3, hull; if (dist1 * dist2 < 0) { hull = [[p0, p1, p3], [p0, p2, p3]]; } else { var distRatio = dist1 / dist2; hull = [ distRatio >= 2 ? [p0, p1, p3] : distRatio <= 0.5 ? [p0, p2, p3] : [p0, p1, p2, p3], [p0, p3] ]; } return (dist1 || dist2) < 0 ? hull.reverse() : hull; } function clipConvexHull(hullTop, hullBottom, dMin, dMax) { if (hullTop[0][1] < dMin) { return clipConvexHullPart(hullTop, true, dMin); } else if (hullBottom[0][1] > dMax) { return clipConvexHullPart(hullBottom, false, dMax); } else { return hullTop[0][0]; } } function clipConvexHullPart(part, top, threshold) { var px = part[0][0], py = part[0][1]; for (var i = 1, l = part.length; i < l; i++) { var qx = part[i][0], qy = part[i][1]; if (top ? qy >= threshold : qy <= threshold) { return qy === threshold ? qx : px + (threshold - py) * (qx - px) / (qy - py); } px = qx; py = qy; } return null; } function getCurveLineIntersections(v, px, py, vx, vy) { var isZero = Numerical.isZero; if (isZero(vx) && isZero(vy)) { var t = Curve.getTimeOf(v, new Point(px, py)); return t === null ? [] : [t]; } var angle = Math.atan2(-vy, vx), sin = Math.sin(angle), cos = Math.cos(angle), rv = [], roots = []; for (var i = 0; i < 8; i += 2) { var x = v[i] - px, y = v[i + 1] - py; rv.push( x * cos - y * sin, x * sin + y * cos); } Curve.solveCubic(rv, 1, 0, roots, 0, 1); return roots; } function addCurveLineIntersections(v1, v2, c1, c2, locations, include, flip) { var x1 = v2[0], y1 = v2[1], x2 = v2[6], y2 = v2[7], roots = getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1); for (var i = 0, l = roots.length; i < l; i++) { var t1 = roots[i], p1 = Curve.getPoint(v1, t1), t2 = Curve.getTimeOf(v2, p1); if (t2 !== null) { addLocation(locations, include, flip ? c2 : c1, flip ? t2 : t1, flip ? c1 : c2, flip ? t1 : t2); } } } function addLineIntersection(v1, v2, c1, c2, locations, include) { var pt = Line.intersect( v1[0], v1[1], v1[6], v1[7], v2[0], v2[1], v2[6], v2[7]); if (pt) { addLocation(locations, include, c1, Curve.getTimeOf(v1, pt), c2, Curve.getTimeOf(v2, pt)); } } function getCurveIntersections(v1, v2, c1, c2, locations, include) { var epsilon = 1e-12, min = Math.min, max = Math.max; if (max(v1[0], v1[2], v1[4], v1[6]) + epsilon > min(v2[0], v2[2], v2[4], v2[6]) && min(v1[0], v1[2], v1[4], v1[6]) - epsilon < max(v2[0], v2[2], v2[4], v2[6]) && max(v1[1], v1[3], v1[5], v1[7]) + epsilon > min(v2[1], v2[3], v2[5], v2[7]) && min(v1[1], v1[3], v1[5], v1[7]) - epsilon < max(v2[1], v2[3], v2[5], v2[7])) { var overlaps = getOverlaps(v1, v2); if (overlaps) { for (var i = 0; i < 2; i++) { var overlap = overlaps[i]; addLocation(locations, include, c1, overlap[0], c2, overlap[1], true); } } else { var straight1 = Curve.isStraight(v1), straight2 = Curve.isStraight(v2), straight = straight1 && straight2, flip = straight1 && !straight2, before = locations.length; (straight ? addLineIntersection : straight1 || straight2 ? addCurveLineIntersections : addCurveIntersections)( flip ? v2 : v1, flip ? v1 : v2, flip ? c2 : c1, flip ? c1 : c2, locations, include, flip, 0, 0, 0, 1, 0, 1); if (!straight || locations.length === before) { for (var i = 0; i < 4; i++) { var t1 = i >> 1, t2 = i & 1, i1 = t1 * 6, i2 = t2 * 6, p1 = new Point(v1[i1], v1[i1 + 1]), p2 = new Point(v2[i2], v2[i2 + 1]); if (p1.isClose(p2, epsilon)) { addLocation(locations, include, c1, t1, c2, t2); } } } } } return locations; } function getLoopIntersection(v1, c1, locations, include) { var info = Curve.classify(v1); if (info.type === 'loop') { var roots = info.roots; addLocation(locations, include, c1, roots[0], c1, roots[1]); } return locations; } function getIntersections(curves1, curves2, include, matrix1, matrix2, _returnFirst) { var self = !curves2; if (self) curves2 = curves1; var length1 = curves1.length, length2 = curves2.length, values2 = [], arrays = [], locations, current; for (var i = 0; i < length2; i++) values2[i] = curves2[i].getValues(matrix2); for (var i = 0; i < length1; i++) { var curve1 = curves1[i], values1 = self ? values2[i] : curve1.getValues(matrix1), path1 = curve1.getPath(); if (path1 !== current) { current = path1; locations = []; arrays.push(locations); } if (self) { getLoopIntersection(values1, curve1, locations, include); } for (var j = self ? i + 1 : 0; j < length2; j++) { if (_returnFirst && locations.length) return locations; getCurveIntersections(values1, values2[j], curve1, curves2[j], locations, include); } } locations = []; for (var i = 0, l = arrays.length; i < l; i++) { locations.push.apply(locations, arrays[i]); } return locations; } function getOverlaps(v1, v2) { function getSquaredLineLength(v) { var x = v[6] - v[0], y = v[7] - v[1]; return x * x + y * y; } var abs = Math.abs, getDistance = Line.getDistance, timeEpsilon = 1e-8, geomEpsilon = 1e-7, straight1 = Curve.isStraight(v1), straight2 = Curve.isStraight(v2), straightBoth = straight1 && straight2, flip = getSquaredLineLength(v1) < getSquaredLineLength(v2), l1 = flip ? v2 : v1, l2 = flip ? v1 : v2, px = l1[0], py = l1[1], vx = l1[6] - px, vy = l1[7] - py; if (getDistance(px, py, vx, vy, l2[0], l2[1], true) < geomEpsilon && getDistance(px, py, vx, vy, l2[6], l2[7], true) < geomEpsilon) { if (!straightBoth && getDistance(px, py, vx, vy, l1[2], l1[3], true) < geomEpsilon && getDistance(px, py, vx, vy, l1[4], l1[5], true) < geomEpsilon && getDistance(px, py, vx, vy, l2[2], l2[3], true) < geomEpsilon && getDistance(px, py, vx, vy, l2[4], l2[5], true) < geomEpsilon) { straight1 = straight2 = straightBoth = true; } } else if (straightBoth) { return null; } if (straight1 ^ straight2) { return null; } var v = [v1, v2], pairs = []; for (var i = 0; i < 4 && pairs.length < 2; i++) { var i1 = i & 1, i2 = i1 ^ 1, t1 = i >> 1, t2 = Curve.getTimeOf(v[i1], new Point( v[i2][t1 ? 6 : 0], v[i2][t1 ? 7 : 1])); if (t2 != null) { var pair = i1 ? [t1, t2] : [t2, t1]; if (!pairs.length || abs(pair[0] - pairs[0][0]) > timeEpsilon && abs(pair[1] - pairs[0][1]) > timeEpsilon) { pairs.push(pair); } } if (i > 2 && !pairs.length) break; } if (pairs.length !== 2) { pairs = null; } else if (!straightBoth) { var o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]), o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]); if (abs(o2[2] - o1[2]) > geomEpsilon || abs(o2[3] - o1[3]) > geomEpsilon || abs(o2[4] - o1[4]) > geomEpsilon || abs(o2[5] - o1[5]) > geomEpsilon) pairs = null; } return pairs; } return { getIntersections: function(curve) { var v1 = this.getValues(), v2 = curve && curve !== this && curve.getValues(); return v2 ? getCurveIntersections(v1, v2, this, curve, []) : getLoopIntersection(v1, this, []); }, statics: { getOverlaps: getOverlaps, getIntersections: getIntersections, getCurveLineIntersections: getCurveLineIntersections } }; }); var CurveLocation = Base.extend({ _class: 'CurveLocation', initialize: function CurveLocation(curve, time, point, _overlap, _distance) { if (time >= 0.99999999) { var next = curve.getNext(); if (next) { time = 0; curve = next; } } this._setCurve(curve); this._time = time; this._point = point || curve.getPointAtTime(time); this._overlap = _overlap; this._distance = _distance; this._intersection = this._next = this._previous = null; }, _setCurve: function(curve) { var path = curve._path; this._path = path; this._version = path ? path._version : 0; this._curve = curve; this._segment = null; this._segment1 = curve._segment1; this._segment2 = curve._segment2; }, _setSegment: function(segment) { this._setCurve(segment.getCurve()); this._segment = segment; this._time = segment === this._segment1 ? 0 : 1; this._point = segment._point.clone(); }, getSegment: function() { var segment = this._segment; if (!segment) { var curve = this.getCurve(), time = this.getTime(); if (time === 0) { segment = curve._segment1; } else if (time === 1) { segment = curve._segment2; } else if (time != null) { segment = curve.getPartLength(0, time) < curve.getPartLength(time, 1) ? curve._segment1 : curve._segment2; } this._segment = segment; } return segment; }, getCurve: function() { var path = this._path, that = this; if (path && path._version !== this._version) { this._time = this._offset = this._curveOffset = this._curve = null; } function trySegment(segment) { var curve = segment && segment.getCurve(); if (curve && (that._time = curve.getTimeOf(that._point)) != null) { that._setCurve(curve); return curve; } } return this._curve || trySegment(this._segment) || trySegment(this._segment1) || trySegment(this._segment2.getPrevious()); }, getPath: function() { var curve = this.getCurve(); return curve && curve._path; }, getIndex: function() { var curve = this.getCurve(); return curve && curve.getIndex(); }, getTime: function() { var curve = this.getCurve(), time = this._time; return curve && time == null ? this._time = curve.getTimeOf(this._point) : time; }, getParameter: '#getTime', getPoint: function() { return this._point; }, getOffset: function() { var offset = this._offset; if (offset == null) { offset = 0; var path = this.getPath(), index = this.getIndex(); if (path && index != null) { var curves = path.getCurves(); for (var i = 0; i < index; i++) offset += curves[i].getLength(); } this._offset = offset += this.getCurveOffset(); } return offset; }, getCurveOffset: function() { var offset = this._curveOffset; if (offset == null) { var curve = this.getCurve(), time = this.getTime(); this._curveOffset = offset = time != null && curve && curve.getPartLength(0, time); } return offset; }, getIntersection: function() { return this._intersection; }, getDistance: function() { return this._distance; }, divide: function() { var curve = this.getCurve(), res = curve && curve.divideAtTime(this.getTime()); if (res) { this._setSegment(res._segment1); } return res; }, split: function() { var curve = this.getCurve(), path = curve._path, res = curve && curve.splitAtTime(this.getTime()); if (res) { this._setSegment(path.getLastSegment()); } return res; }, equals: function(loc, _ignoreOther) { var res = this === loc; if (!res && loc instanceof CurveLocation) { var c1 = this.getCurve(), c2 = loc.getCurve(), p1 = c1._path, p2 = c2._path; if (p1 === p2) { var abs = Math.abs, epsilon = 1e-7, diff = abs(this.getOffset() - loc.getOffset()), i1 = !_ignoreOther && this._intersection, i2 = !_ignoreOther && loc._intersection; res = (diff < epsilon || p1 && abs(p1.getLength() - diff) < epsilon) && (!i1 && !i2 || i1 && i2 && i1.equals(i2, true)); } } return res; }, toString: function() { var parts = [], point = this.getPoint(), f = Formatter.instance; if (point) parts.push('point: ' + point); var index = this.getIndex(); if (index != null) parts.push('index: ' + index); var time = this.getTime(); if (time != null) parts.push('time: ' + f.number(time)); if (this._distance != null) parts.push('distance: ' + f.number(this._distance)); return '{ ' + parts.join(', ') + ' }'; }, isTouching: function() { var inter = this._intersection; if (inter && this.getTangent().isCollinear(inter.getTangent())) { var curve1 = this.getCurve(), curve2 = inter.getCurve(); return !(curve1.isStraight() && curve2.isStraight() && curve1.getLine().intersect(curve2.getLine())); } return false; }, isCrossing: function() { var inter = this._intersection; if (!inter) return false; var t1 = this.getTime(), t2 = inter.getTime(), tMin = 1e-8, tMax = 1 - tMin, t1Inside = t1 >= tMin && t1 <= tMax, t2Inside = t2 >= tMin && t2 <= tMax; if (t1Inside && t2Inside) return !this.isTouching(); var c2 = this.getCurve(), c1 = t1 < tMin ? c2.getPrevious() : c2, c4 = inter.getCurve(), c3 = t2 < tMin ? c4.getPrevious() : c4; if (t1 > tMax) c2 = c2.getNext(); if (t2 > tMax) c4 = c4.getNext(); if (!c1 || !c2 || !c3 || !c4) return false; var offsets = []; function addOffsets(curve, end) { var v = curve.getValues(), roots = Curve.classify(v).roots || Curve.getPeaks(v), count = roots.length, t = end && count > 1 ? roots[count - 1] : count > 0 ? roots[0] : 0.5; offsets.push(Curve.getLength(v, end ? t : 0, end ? 1 : t) / 2); } function isInRange(angle, min, max) { return min < max ? angle > min && angle < max : angle > min || angle < max; } if (!t1Inside) { addOffsets(c1, true); addOffsets(c2, false); } if (!t2Inside) { addOffsets(c3, true); addOffsets(c4, false); } var pt = this.getPoint(), offset = Math.min.apply(Math, offsets), v2 = t1Inside ? c2.getTangentAtTime(t1) : c2.getPointAt(offset).subtract(pt), v1 = t1Inside ? v2.negate() : c1.getPointAt(-offset).subtract(pt), v4 = t2Inside ? c4.getTangentAtTime(t2) : c4.getPointAt(offset).subtract(pt), v3 = t2Inside ? v4.negate() : c3.getPointAt(-offset).subtract(pt), a1 = v1.getAngle(), a2 = v2.getAngle(), a3 = v3.getAngle(), a4 = v4.getAngle(); return !!(t1Inside ? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) && (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3)) : (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) && (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1))); }, hasOverlap: function() { return !!this._overlap; } }, Base.each(Curve._evaluateMethods, function(name) { var get = name + 'At'; this[name] = function() { var curve = this.getCurve(), time = this.getTime(); return time != null && curve && curve[get](time, true); }; }, { preserve: true }), new function() { function insert(locations, loc, merge) { var length = locations.length, l = 0, r = length - 1; function search(index, dir) { for (var i = index + dir; i >= -1 && i <= length; i += dir) { var loc2 = locations[((i % length) + length) % length]; if (!loc.getPoint().isClose(loc2.getPoint(), 1e-7)) break; if (loc.equals(loc2)) return loc2; } return null; } while (l <= r) { var m = (l + r) >>> 1, loc2 = locations[m], found; if (merge && (found = loc.equals(loc2) ? loc2 : (search(m, -1) || search(m, 1)))) { if (loc._overlap) { found._overlap = found._intersection._overlap = true; } return found; } var path1 = loc.getPath(), path2 = loc2.getPath(), diff = path1 !== path2 ? path1._id - path2._id : (loc.getIndex() + loc.getTime()) - (loc2.getIndex() + loc2.getTime()); if (diff < 0) { r = m - 1; } else { l = m + 1; } } locations.splice(l, 0, loc); return loc; } return { statics: { insert: insert, expand: function(locations) { var expanded = locations.slice(); for (var i = locations.length - 1; i >= 0; i--) { insert(expanded, locations[i]._intersection, false); } return expanded; } }}; }); var PathItem = Item.extend({ _class: 'PathItem', _selectBounds: false, _canScaleStroke: true, beans: true, initialize: function PathItem() { }, statics: { create: function(arg) { var data, segments, compound; if (Base.isPlainObject(arg)) { segments = arg.segments; data = arg.pathData; } else if (Array.isArray(arg)) { segments = arg; } else if (typeof arg === 'string') { data = arg; } if (segments) { var first = segments[0]; compound = first && Array.isArray(first[0]); } else if (data) { compound = (data.match(/m/gi) || []).length > 1 || /z\s*\S+/i.test(data); } var ctor = compound ? CompoundPath : Path; return new ctor(arg); } }, _asPathItem: function() { return this; }, isClockwise: function() { return this.getArea() >= 0; }, setClockwise: function(clockwise) { if (this.isClockwise() != (clockwise = !!clockwise)) this.reverse(); }, setPathData: function(data) { var parts = data && data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig), coords, relative = false, previous, control, current = new Point(), start = new Point(); function getCoord(index, coord) { var val = +coords[index]; if (relative) val += current[coord]; return val; } function getPoint(index) { return new Point( getCoord(index, 'x'), getCoord(index + 1, 'y') ); } this.clear(); for (var i = 0, l = parts && parts.length; i < l; i++) { var part = parts[i], command = part[0], lower = command.toLowerCase(); coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); var length = coords && coords.length; relative = command === lower; if (previous === 'z' && !/[mz]/.test(lower)) this.moveTo(current); switch (lower) { case 'm': case 'l': var move = lower === 'm'; for (var j = 0; j < length; j += 2) { this[move ? 'moveTo' : 'lineTo'](current = getPoint(j)); if (move) { start = current; move = false; } } control = current; break; case 'h': case 'v': var coord = lower === 'h' ? 'x' : 'y'; current = current.clone(); for (var j = 0; j < length; j++) { current[coord] = getCoord(j, coord); this.lineTo(current); } control = current; break; case 'c': for (var j = 0; j < length; j += 6) { this.cubicCurveTo( getPoint(j), control = getPoint(j + 2), current = getPoint(j + 4)); } break; case 's': for (var j = 0; j < length; j += 4) { this.cubicCurveTo( /[cs]/.test(previous) ? current.multiply(2).subtract(control) : current, control = getPoint(j), current = getPoint(j + 2)); previous = lower; } break; case 'q': for (var j = 0; j < length; j += 4) { this.quadraticCurveTo( control = getPoint(j), current = getPoint(j + 2)); } break; case 't': for (var j = 0; j < length; j += 2) { this.quadraticCurveTo( control = (/[qt]/.test(previous) ? current.multiply(2).subtract(control) : current), current = getPoint(j)); previous = lower; } break; case 'a': for (var j = 0; j < length; j += 7) { this.arcTo(current = getPoint(j + 5), new Size(+coords[j], +coords[j + 1]), +coords[j + 2], +coords[j + 4], +coords[j + 3]); } break; case 'z': this.closePath(1e-12); current = start; break; } previous = lower; } }, _canComposite: function() { return !(this.hasFill() && this.hasStroke()); }, _contains: function(point) { var winding = point.isInside( this.getBounds({ internal: true, handle: true })) ? this._getWinding(point) : {}; return winding.onPath || !!(this.getFillRule() === 'evenodd' ? winding.windingL & 1 || winding.windingR & 1 : winding.winding); }, getIntersections: function(path, include, _matrix, _returnFirst) { var self = this === path || !path, matrix1 = this._matrix._orNullIfIdentity(), matrix2 = self ? matrix1 : (_matrix || path._matrix)._orNullIfIdentity(); return self || this.getBounds(matrix1).intersects( path.getBounds(matrix2), 1e-12) ? Curve.getIntersections( this.getCurves(), !self && path.getCurves(), include, matrix1, matrix2, _returnFirst) : []; }, getCrossings: function(path) { return this.getIntersections(path, function(inter) { return inter.hasOverlap() || inter.isCrossing(); }); }, getNearestLocation: function() { var point = Point.read(arguments), curves = this.getCurves(), minDist = Infinity, minLoc = null; for (var i = 0, l = curves.length; i < l; i++) { var loc = curves[i].getNearestLocation(point); if (loc._distance < minDist) { minDist = loc._distance; minLoc = loc; } } return minLoc; }, getNearestPoint: function() { var loc = this.getNearestLocation.apply(this, arguments); return loc ? loc.getPoint() : loc; }, interpolate: function(from, to, factor) { var isPath = !this._children, name = isPath ? '_segments' : '_children', itemsFrom = from[name], itemsTo = to[name], items = this[name]; if (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) { throw new Error('Invalid operands in interpolate() call: ' + from + ', ' + to); } var current = items.length, length = itemsTo.length; if (current < length) { var ctor = isPath ? Segment : Path; for (var i = current; i < length; i++) { this.add(new ctor()); } } else if (current > length) { this[isPath ? 'removeSegments' : 'removeChildren'](length, current); } for (var i = 0; i < length; i++) { items[i].interpolate(itemsFrom[i], itemsTo[i], factor); } if (isPath) { this.setClosed(from._closed); this._changed(9); } }, compare: function(path) { var ok = false; if (path) { var paths1 = this._children || [this], paths2 = path._children ? path._children.slice() : [path], length1 = paths1.length, length2 = paths2.length, matched = [], count = 0; ok = true; for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) { var path1 = paths1[i1]; ok = false; for (var i2 = length2 - 1; i2 >= 0 && !ok; i2--) { if (path1.compare(paths2[i2])) { if (!matched[i2]) { matched[i2] = true; count++; } ok = true; } } } ok = ok && count === length2; } return ok; }, }); var Path = PathItem.extend({ _class: 'Path', _serializeFields: { segments: [], closed: false }, initialize: function Path(arg) { this._closed = false; this._segments = []; this._version = 0; var segments = Array.isArray(arg) ? typeof arg[0] === 'object' ? arg : arguments : arg && (arg.size === undefined && (arg.x !== undefined || arg.point !== undefined)) ? arguments : null; if (segments && segments.length > 0) { this.setSegments(segments); } else { this._curves = undefined; this._segmentSelection = 0; if (!segments && typeof arg === 'string') { this.setPathData(arg); arg = null; } } this._initialize(!segments && arg); }, _equals: function(item) { return this._closed === item._closed && Base.equals(this._segments, item._segments); }, copyContent: function(source) { this.setSegments(source._segments); this._closed = source._closed; }, _changed: function _changed(flags) { _changed.base.call(this, flags); if (flags & 8) { this._length = this._area = undefined; if (flags & 16) { this._version++; } else if (this._curves) { for (var i = 0, l = this._curves.length; i < l; i++) this._curves[i]._changed(); } } else if (flags & 32) { this._bounds = undefined; } }, getStyle: function() { var parent = this._parent; return (parent instanceof CompoundPath ? parent : this)._style; }, getSegments: function() { return this._segments; }, setSegments: function(segments) { var fullySelected = this.isFullySelected(), length = segments && segments.length; this._segments.length = 0; this._segmentSelection = 0; this._curves = undefined; if (length) { var last = segments[length - 1]; if (typeof last === 'boolean') { this.setClosed(last); length--; } this._add(Segment.readList(segments, 0, {}, length)); } if (fullySelected) this.setFullySelected(true); }, getFirstSegment: function() { return this._segments[0]; }, getLastSegment: function() { return this._segments[this._segments.length - 1]; }, getCurves: function() { var curves = this._curves, segments = this._segments; if (!curves) { var length = this._countCurves(); curves = this._curves = new Array(length); for (var i = 0; i < length; i++) curves[i] = new Curve(this, segments[i], segments[i + 1] || segments[0]); } return curves; }, getFirstCurve: function() { return this.getCurves()[0]; }, getLastCurve: function() { var curves = this.getCurves(); return curves[curves.length - 1]; }, isClosed: function() { return this._closed; }, setClosed: function(closed) { if (this._closed != (closed = !!closed)) { this._closed = closed; if (this._curves) { var length = this._curves.length = this._countCurves(); if (closed) this._curves[length - 1] = new Curve(this, this._segments[length - 1], this._segments[0]); } this._changed(25); } } }, { beans: true, getPathData: function(_matrix, _precision) { var segments = this._segments, length = segments.length, f = new Formatter(_precision), coords = new Array(6), first = true, curX, curY, prevX, prevY, inX, inY, outX, outY, parts = []; function addSegment(segment, skipLine) { segment._transformCoordinates(_matrix, coords); curX = coords[0]; curY = coords[1]; if (first) { parts.push('M' + f.pair(curX, curY)); first = false; } else { inX = coords[2]; inY = coords[3]; if (inX === curX && inY === curY && outX === prevX && outY === prevY) { if (!skipLine) { var dx = curX - prevX, dy = curY - prevY; parts.push( dx === 0 ? 'v' + f.number(dy) : dy === 0 ? 'h' + f.number(dx) : 'l' + f.pair(dx, dy)); } } else { parts.push('c' + f.pair(outX - prevX, outY - prevY) + ' ' + f.pair( inX - prevX, inY - prevY) + ' ' + f.pair(curX - prevX, curY - prevY)); } } prevX = curX; prevY = curY; outX = coords[4]; outY = coords[5]; } if (!length) return ''; for (var i = 0; i < length; i++) addSegment(segments[i]); if (this._closed && length > 0) { addSegment(segments[0], true); parts.push('z'); } return parts.join(''); }, isEmpty: function() { return !this._segments.length; }, _transformContent: function(matrix) { var segments = this._segments, coords = new Array(6); for (var i = 0, l = segments.length; i < l; i++) segments[i]._transformCoordinates(matrix, coords, true); return true; }, _add: function(segs, index) { var segments = this._segments, curves = this._curves, amount = segs.length, append = index == null, index = append ? segments.length : index; for (var i = 0; i < amount; i++) { var segment = segs[i]; if (segment._path) segment = segs[i] = segment.clone(); segment._path = this; segment._index = index + i; if (segment._selection) this._updateSelection(segment, 0, segment._selection); } if (append) { segments.push.apply(segments, segs); } else { segments.splice.apply(segments, [index, 0].concat(segs)); for (var i = index + amount, l = segments.length; i < l; i++) segments[i]._index = i; } if (curves) { var total = this._countCurves(), start = index > 0 && index + amount - 1 === total ? index - 1 : index, insert = start, end = Math.min(start + amount, total); if (segs._curves) { curves.splice.apply(curves, [start, 0].concat(segs._curves)); insert += segs._curves.length; } for (var i = insert; i < end; i++) curves.splice(i, 0, new Curve(this, null, null)); this._adjustCurves(start, end); } this._changed(25); return segs; }, _adjustCurves: function(start, end) { var segments = this._segments, curves = this._curves, curve; for (var i = start; i < end; i++) { curve = curves[i]; curve._path = this; curve._segment1 = segments[i]; curve._segment2 = segments[i + 1] || segments[0]; curve._changed(); } if (curve = curves[this._closed && !start ? segments.length - 1 : start - 1]) { curve._segment2 = segments[start] || segments[0]; curve._changed(); } if (curve = curves[end]) { curve._segment1 = segments[end]; curve._changed(); } }, _countCurves: function() { var length = this._segments.length; return !this._closed && length > 0 ? length - 1 : length; }, add: function(segment1 ) { return arguments.length > 1 && typeof segment1 !== 'number' ? this._add(Segment.readList(arguments)) : this._add([ Segment.read(arguments) ])[0]; }, insert: function(index, segment1 ) { return arguments.length > 2 && typeof segment1 !== 'number' ? this._add(Segment.readList(arguments, 1), index) : this._add([ Segment.read(arguments, 1) ], index)[0]; }, addSegment: function() { return this._add([ Segment.read(arguments) ])[0]; }, insertSegment: function(index ) { return this._add([ Segment.read(arguments, 1) ], index)[0]; }, addSegments: function(segments) { return this._add(Segment.readList(segments)); }, insertSegments: function(index, segments) { return this._add(Segment.readList(segments), index); }, removeSegment: function(index) { return this.removeSegments(index, index + 1)[0] || null; }, removeSegments: function(start, end, _includeCurves) { start = start || 0; end = Base.pick(end, this._segments.length); var segments = this._segments, curves = this._curves, count = segments.length, removed = segments.splice(start, end - start), amount = removed.length; if (!amount) return removed; for (var i = 0; i < amount; i++) { var segment = removed[i]; if (segment._selection) this._updateSelection(segment, segment._selection, 0); segment._index = segment._path = null; } for (var i = start, l = segments.length; i < l; i++) segments[i]._index = i; if (curves) { var index = start > 0 && end === count + (this._closed ? 1 : 0) ? start - 1 : start, curves = curves.splice(index, amount); for (var i = curves.length - 1; i >= 0; i--) curves[i]._path = null; if (_includeCurves) removed._curves = curves.slice(1); this._adjustCurves(index, index); } this._changed(25); return removed; }, clear: '#removeSegments', hasHandles: function() { var segments = this._segments; for (var i = 0, l = segments.length; i < l; i++) { if (segments[i].hasHandles()) return true; } return false; }, clearHandles: function() { var segments = this._segments; for (var i = 0, l = segments.length; i < l; i++) segments[i].clearHandles(); }, getLength: function() { if (this._length == null) { var curves = this.getCurves(), length = 0; for (var i = 0, l = curves.length; i < l; i++) length += curves[i].getLength(); this._length = length; } return this._length; }, getArea: function() { var area = this._area; if (area == null) { var segments = this._segments, closed = this._closed; area = 0; for (var i = 0, l = segments.length; i < l; i++) { var last = i + 1 === l; area += Curve.getArea(Curve.getValues( segments[i], segments[last ? 0 : i + 1], null, last && !closed)); } this._area = area; } return area; }, isFullySelected: function() { var length = this._segments.length; return this.isSelected() && length > 0 && this._segmentSelection === length * 7; }, setFullySelected: function(selected) { if (selected) this._selectSegments(true); this.setSelected(selected); }, setSelection: function setSelection(selection) { if (!(selection & 1)) this._selectSegments(false); setSelection.base.call(this, selection); }, _selectSegments: function(selected) { var segments = this._segments, length = segments.length, selection = selected ? 7 : 0; this._segmentSelection = selection * length; for (var i = 0; i < length; i++) segments[i]._selection = selection; }, _updateSelection: function(segment, oldSelection, newSelection) { segment._selection = newSelection; var selection = this._segmentSelection += newSelection - oldSelection; if (selection > 0) this.setSelected(true); }, divideAt: function(location) { var loc = this.getLocationAt(location), curve; return loc && (curve = loc.getCurve().divideAt(loc.getCurveOffset())) ? curve._segment1 : null; }, splitAt: function(location) { var loc = this.getLocationAt(location), index = loc && loc.index, time = loc && loc.time, tMin = 1e-8, tMax = 1 - tMin; if (time > tMax) { index++; time = 0; } var curves = this.getCurves(); if (index >= 0 && index < curves.length) { if (time >= tMin) { curves[index++].divideAtTime(time); } var segs = this.removeSegments(index, this._segments.length, true), path; if (this._closed) { this.setClosed(false); path = this; } else { path = new Path(Item.NO_INSERT); path.insertAbove(this); path.copyAttributes(this); } path._add(segs, 0); this.addSegment(segs[0]); return path; } return null; }, split: function(index, time) { var curve, location = time === undefined ? index : (curve = this.getCurves()[index]) && curve.getLocationAtTime(time); return location != null ? this.splitAt(location) : null; }, join: function(path, tolerance) { var epsilon = tolerance || 0; if (path && path !== this) { var segments = path._segments, last1 = this.getLastSegment(), last2 = path.getLastSegment(); if (!last2) return this; if (last1 && last1._point.isClose(last2._point, epsilon)) path.reverse(); var first2 = path.getFirstSegment(); if (last1 && last1._point.isClose(first2._point, epsilon)) { last1.setHandleOut(first2._handleOut); this._add(segments.slice(1)); } else { var first1 = this.getFirstSegment(); if (first1 && first1._point.isClose(first2._point, epsilon)) path.reverse(); last2 = path.getLastSegment(); if (first1 && first1._point.isClose(last2._point, epsilon)) { first1.setHandleIn(last2._handleIn); this._add(segments.slice(0, segments.length - 1), 0); } else { this._add(segments.slice()); } } if (path._closed) this._add([segments[0]]); path.remove(); } var first = this.getFirstSegment(), last = this.getLastSegment(); if (first !== last && first._point.isClose(last._point, epsilon)) { first.setHandleIn(last._handleIn); last.remove(); this.setClosed(true); } return this; }, reduce: function(options) { var curves = this.getCurves(), simplify = options && options.simplify, tolerance = simplify ? 1e-7 : 0; for (var i = curves.length - 1; i >= 0; i--) { var curve = curves[i]; if (!curve.hasHandles() && (!curve.hasLength(tolerance) || simplify && curve.isCollinear(curve.getNext()))) curve.remove(); } return this; }, reverse: function() { this._segments.reverse(); for (var i = 0, l = this._segments.length; i < l; i++) { var segment = this._segments[i]; var handleIn = segment._handleIn; segment._handleIn = segment._handleOut; segment._handleOut = handleIn; segment._index = i; } this._curves = null; this._changed(9); }, flatten: function(flatness) { var flattener = new PathFlattener(this, flatness || 0.25, 256, true), parts = flattener.parts, length = parts.length, segments = []; for (var i = 0; i < length; i++) { segments.push(new Segment(parts[i].curve.slice(0, 2))); } if (!this._closed && length > 0) { segments.push(new Segment(parts[length - 1].curve.slice(6))); } this.setSegments(segments); }, simplify: function(tolerance) { var segments = new PathFitter(this).fit(tolerance || 2.5); if (segments) this.setSegments(segments); return !!segments; }, smooth: function(options) { var that = this, opts = options || {}, type = opts.type || 'asymmetric', segments = this._segments, length = segments.length, closed = this._closed; function getIndex(value, _default) { var index = value && value.index; if (index != null) { var path = value.path; if (path && path !== that) throw new Error(value._class + ' ' + index + ' of ' + path + ' is not part of ' + that); if (_default && value instanceof Curve) index++; } else { index = typeof value === 'number' ? value : _default; } return Math.min(index < 0 && closed ? index % length : index < 0 ? index + length : index, length - 1); } var loop = closed && opts.from === undefined && opts.to === undefined, from = getIndex(opts.from, 0), to = getIndex(opts.to, length - 1); if (from > to) { if (closed) { from -= length; } else { var tmp = from; from = to; to = tmp; } } if (/^(?:asymmetric|continuous)$/.test(type)) { var asymmetric = type === 'asymmetric', min = Math.min, amount = to - from + 1, n = amount - 1, padding = loop ? min(amount, 4) : 1, paddingLeft = padding, paddingRight = padding, knots = []; if (!closed) { paddingLeft = min(1, from); paddingRight = min(1, length - to - 1); } n += paddingLeft + paddingRight; if (n <= 1) return; for (var i = 0, j = from - paddingLeft; i <= n; i++, j++) { knots[i] = segments[(j < 0 ? j + length : j) % length]._point; } var x = knots[0]._x + 2 * knots[1]._x, y = knots[0]._y + 2 * knots[1]._y, f = 2, n_1 = n - 1, rx = [x], ry = [y], rf = [f], px = [], py = []; for (var i = 1; i < n; i++) { var internal = i < n_1, a = internal ? 1 : asymmetric ? 1 : 2, b = internal ? 4 : asymmetric ? 2 : 7, u = internal ? 4 : asymmetric ? 3 : 8, v = internal ? 2 : asymmetric ? 0 : 1, m = a / f; f = rf[i] = b - m; x = rx[i] = u * knots[i]._x + v * knots[i + 1]._x - m * x; y = ry[i] = u * knots[i]._y + v * knots[i + 1]._y - m * y; } px[n_1] = rx[n_1] / rf[n_1]; py[n_1] = ry[n_1] / rf[n_1]; for (var i = n - 2; i >= 0; i--) { px[i] = (rx[i] - px[i + 1]) / rf[i]; py[i] = (ry[i] - py[i + 1]) / rf[i]; } px[n] = (3 * knots[n]._x - px[n_1]) / 2; py[n] = (3 * knots[n]._y - py[n_1]) / 2; for (var i = paddingLeft, max = n - paddingRight, j = from; i <= max; i++, j++) { var segment = segments[j < 0 ? j + length : j], pt = segment._point, hx = px[i] - pt._x, hy = py[i] - pt._y; if (loop || i < max) segment.setHandleOut(hx, hy); if (loop || i > paddingLeft) segment.setHandleIn(-hx, -hy); } } else { for (var i = from; i <= to; i++) { segments[i < 0 ? i + length : i].smooth(opts, !loop && i === from, !loop && i === to); } } }, toShape: function(insert) { if (!this._closed) return null; var segments = this._segments, type, size, radius, topCenter; function isCollinear(i, j) { var seg1 = segments[i], seg2 = seg1.getNext(), seg3 = segments[j], seg4 = seg3.getNext(); return seg1._handleOut.isZero() && seg2._handleIn.isZero() && seg3._handleOut.isZero() && seg4._handleIn.isZero() && seg2._point.subtract(seg1._point).isCollinear( seg4._point.subtract(seg3._point)); } function isOrthogonal(i) { var seg2 = segments[i], seg1 = seg2.getPrevious(), seg3 = seg2.getNext(); return seg1._handleOut.isZero() && seg2._handleIn.isZero() && seg2._handleOut.isZero() && seg3._handleIn.isZero() && seg2._point.subtract(seg1._point).isOrthogonal( seg3._point.subtract(seg2._point)); } function isArc(i) { var seg1 = segments[i], seg2 = seg1.getNext(), handle1 = seg1._handleOut, handle2 = seg2._handleIn, kappa = 0.5522847498307936; if (handle1.isOrthogonal(handle2)) { var pt1 = seg1._point, pt2 = seg2._point, corner = new Line(pt1, handle1, true).intersect( new Line(pt2, handle2, true), true); return corner && Numerical.isZero(handle1.getLength() / corner.subtract(pt1).getLength() - kappa) && Numerical.isZero(handle2.getLength() / corner.subtract(pt2).getLength() - kappa); } return false; } function getDistance(i, j) { return segments[i]._point.getDistance(segments[j]._point); } if (!this.hasHandles() && segments.length === 4 && isCollinear(0, 2) && isCollinear(1, 3) && isOrthogonal(1)) { type = Shape.Rectangle; size = new Size(getDistance(0, 3), getDistance(0, 1)); topCenter = segments[1]._point.add(segments[2]._point).divide(2); } else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4) && isArc(6) && isCollinear(1, 5) && isCollinear(3, 7)) { type = Shape.Rectangle; size = new Size(getDistance(1, 6), getDistance(0, 3)); radius = size.subtract(new Size(getDistance(0, 7), getDistance(1, 2))).divide(2); topCenter = segments[3]._point.add(segments[4]._point).divide(2); } else if (segments.length === 4 && isArc(0) && isArc(1) && isArc(2) && isArc(3)) { if (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) { type = Shape.Circle; radius = getDistance(0, 2) / 2; } else { type = Shape.Ellipse; radius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2); } topCenter = segments[1]._point; } if (type) { var center = this.getPosition(true), shape = new type({ center: center, size: size, radius: radius, insert: false }); shape.copyAttributes(this, true); shape._matrix.prepend(this._matrix); shape.rotate(topCenter.subtract(center).getAngle() + 90); if (insert === undefined || insert) shape.insertAbove(this); return shape; } return null; }, toPath: '#clone', compare: function compare(path) { if (!path || path instanceof CompoundPath) return compare.base.call(this, path); var curves1 = this.getCurves(), curves2 = path.getCurves(), length1 = curves1.length, length2 = curves2.length; if (!length1 || !length2) { return length1 == length2; } var v1 = curves1[0].getValues(), values2 = [], pos1 = 0, pos2, end1 = 0, end2; for (var i = 0; i < length2; i++) { var v2 = curves2[i].getValues(); values2.push(v2); var overlaps = Curve.getOverlaps(v1, v2); if (overlaps) { pos2 = !i && overlaps[0][0] > 0 ? length2 - 1 : i; end2 = overlaps[0][1]; break; } } var abs = Math.abs, epsilon = 1e-8, v2 = values2[pos2], start2; while (v1 && v2) { var overlaps = Curve.getOverlaps(v1, v2); if (overlaps) { var t1 = overlaps[0][0]; if (abs(t1 - end1) < epsilon) { end1 = overlaps[1][0]; if (end1 === 1) { v1 = ++pos1 < length1 ? curves1[pos1].getValues() : null; end1 = 0; } var t2 = overlaps[0][1]; if (abs(t2 - end2) < epsilon) { if (!start2) start2 = [pos2, t2]; end2 = overlaps[1][1]; if (end2 === 1) { if (++pos2 >= length2) pos2 = 0; v2 = values2[pos2] || curves2[pos2].getValues(); end2 = 0; } if (!v1) { return start2[0] === pos2 && start2[1] === end2; } continue; } } } break; } return false; }, _hitTestSelf: function(point, options, viewMatrix, strokeMatrix) { var that = this, style = this.getStyle(), segments = this._segments, numSegments = segments.length, closed = this._closed, tolerancePadding = options._tolerancePadding, strokePadding = tolerancePadding, join, cap, miterLimit, area, loc, res, hitStroke = options.stroke && style.hasStroke(), hitFill = options.fill && style.hasFill(), hitCurves = options.curves, strokeRadius = hitStroke ? style.getStrokeWidth() / 2 : hitFill && options.tolerance > 0 || hitCurves ? 0 : null; if (strokeRadius !== null) { if (strokeRadius > 0) { join = style.getStrokeJoin(); cap = style.getStrokeCap(); miterLimit = style.getMiterLimit(); strokePadding = strokePadding.add( Path._getStrokePadding(strokeRadius, strokeMatrix)); } else { join = cap = 'round'; } } function isCloseEnough(pt, padding) { return point.subtract(pt).divide(padding).length <= 1; } function checkSegmentPoint(seg, pt, name) { if (!options.selected || pt.isSelected()) { var anchor = seg._point; if (pt !== anchor) pt = pt.add(anchor); if (isCloseEnough(pt, strokePadding)) { return new HitResult(name, that, { segment: seg, point: pt }); } } } function checkSegmentPoints(seg, ends) { return (ends || options.segments) && checkSegmentPoint(seg, seg._point, 'segment') || (!ends && options.handles) && ( checkSegmentPoint(seg, seg._handleIn, 'handle-in') || checkSegmentPoint(seg, seg._handleOut, 'handle-out')); } function addToArea(point) { area.add(point); } function checkSegmentStroke(segment) { var isJoin = closed || segment._index > 0 && segment._index < numSegments - 1; if ((isJoin ? join : cap) === 'round') { return isCloseEnough(segment._point, strokePadding); } else { area = new Path({ internal: true, closed: true }); if (isJoin) { if (!segment.isSmooth()) { Path._addBevelJoin(segment, join, strokeRadius, miterLimit, null, strokeMatrix, addToArea, true); } } else if (cap === 'square') { Path._addSquareCap(segment, cap, strokeRadius, null, strokeMatrix, addToArea, true); } if (!area.isEmpty()) { var loc; return area.contains(point) || (loc = area.getNearestLocation(point)) && isCloseEnough(loc.getPoint(), tolerancePadding); } } } if (options.ends && !options.segments && !closed) { if (res = checkSegmentPoints(segments[0], true) || checkSegmentPoints(segments[numSegments - 1], true)) return res; } else if (options.segments || options.handles) { for (var i = 0; i < numSegments; i++) if (res = checkSegmentPoints(segments[i])) return res; } if (strokeRadius !== null) { loc = this.getNearestLocation(point); if (loc) { var time = loc.getTime(); if (time === 0 || time === 1 && numSegments > 1) { if (!checkSegmentStroke(loc.getSegment())) loc = null; } else if (!isCloseEnough(loc.getPoint(), strokePadding)) { loc = null; } } if (!loc && join === 'miter' && numSegments > 1) { for (var i = 0; i < numSegments; i++) { var segment = segments[i]; if (point.getDistance(segment._point) <= miterLimit * strokeRadius && checkSegmentStroke(segment)) { loc = segment.getLocation(); break; } } } } return !loc && hitFill && this._contains(point) || loc && !hitStroke && !hitCurves ? new HitResult('fill', this) : loc ? new HitResult(hitStroke ? 'stroke' : 'curve', this, { location: loc, point: loc.getPoint() }) : null; } }, Base.each(Curve._evaluateMethods, function(name) { this[name + 'At'] = function(offset) { var loc = this.getLocationAt(offset); return loc && loc[name](); }; }, { beans: false, getLocationOf: function() { var point = Point.read(arguments), curves = this.getCurves(); for (var i = 0, l = curves.length; i < l; i++) { var loc = curves[i].getLocationOf(point); if (loc) return loc; } return null; }, getOffsetOf: function() { var loc = this.getLocationOf.apply(this, arguments); return loc ? loc.getOffset() : null; }, getLocationAt: function(offset) { if (typeof offset === 'number') { var curves = this.getCurves(), length = 0; for (var i = 0, l = curves.length; i < l; i++) { var start = length, curve = curves[i]; length += curve.getLength(); if (length > offset) { return curve.getLocationAt(offset - start); } } if (curves.length > 0 && offset <= this.getLength()) { return new CurveLocation(curves[curves.length - 1], 1); } } else if (offset && offset.getPath && offset.getPath() === this) { return offset; } return null; } }), new function() { function drawHandles(ctx, segments, matrix, size) { var half = size / 2, coords = new Array(6), pX, pY; function drawHandle(index) { var hX = coords[index], hY = coords[index + 1]; if (pX != hX || pY != hY) { ctx.beginPath(); ctx.moveTo(pX, pY); ctx.lineTo(hX, hY); ctx.stroke(); ctx.beginPath(); ctx.arc(hX, hY, half, 0, Math.PI * 2, true); ctx.fill(); } } for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], selection = segment._selection; segment._transformCoordinates(matrix, coords); pX = coords[0]; pY = coords[1]; if (selection & 2) drawHandle(2); if (selection & 4) drawHandle(4); ctx.fillRect(pX - half, pY - half, size, size); if (!(selection & 1)) { var fillStyle = ctx.fillStyle; ctx.fillStyle = '#ffffff'; ctx.fillRect(pX - half + 1, pY - half + 1, size - 2, size - 2); ctx.fillStyle = fillStyle; } } } function drawSegments(ctx, path, matrix) { var segments = path._segments, length = segments.length, coords = new Array(6), first = true, curX, curY, prevX, prevY, inX, inY, outX, outY; function drawSegment(segment) { if (matrix) { segment._transformCoordinates(matrix, coords); curX = coords[0]; curY = coords[1]; } else { var point = segment._point; curX = point._x; curY = point._y; } if (first) { ctx.moveTo(curX, curY); first = false; } else { if (matrix) { inX = coords[2]; inY = coords[3]; } else { var handle = segment._handleIn; inX = curX + handle._x; inY = curY + handle._y; } if (inX === curX && inY === curY && outX === prevX && outY === prevY) { ctx.lineTo(curX, curY); } else { ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY); } } prevX = curX; prevY = curY; if (matrix) { outX = coords[4]; outY = coords[5]; } else { var handle = segment._handleOut; outX = prevX + handle._x; outY = prevY + handle._y; } } for (var i = 0; i < length; i++) drawSegment(segments[i]); if (path._closed && length > 0) drawSegment(segments[0]); } return { _draw: function(ctx, param, viewMatrix, strokeMatrix) { var dontStart = param.dontStart, dontPaint = param.dontFinish || param.clip, style = this.getStyle(), hasFill = style.hasFill(), hasStroke = style.hasStroke(), dashArray = style.getDashArray(), dashLength = !paper.support.nativeDash && hasStroke && dashArray && dashArray.length; if (!dontStart) ctx.beginPath(); if (hasFill || hasStroke && !dashLength || dontPaint) { drawSegments(ctx, this, strokeMatrix); if (this._closed) ctx.closePath(); } function getOffset(i) { return dashArray[((i % dashLength) + dashLength) % dashLength]; } if (!dontPaint && (hasFill || hasStroke)) { this._setStyles(ctx, param, viewMatrix); if (hasFill) { ctx.fill(style.getFillRule()); ctx.shadowColor = 'rgba(0,0,0,0)'; } if (hasStroke) { if (dashLength) { if (!dontStart) ctx.beginPath(); var flattener = new PathFlattener(this, 0.25, 32, false, strokeMatrix), length = flattener.length, from = -style.getDashOffset(), to, i = 0; from = from % length; while (from > 0) { from -= getOffset(i--) + getOffset(i--); } while (from < length) { to = from + getOffset(i++); if (from > 0 || to > 0) flattener.drawPart(ctx, Math.max(from, 0), Math.max(to, 0)); from = to + getOffset(i++); } } ctx.stroke(); } } }, _drawSelected: function(ctx, matrix) { ctx.beginPath(); drawSegments(ctx, this, matrix); ctx.stroke(); drawHandles(ctx, this._segments, matrix, paper.settings.handleSize); } }; }, new function() { function getCurrentSegment(that) { var segments = that._segments; if (!segments.length) throw new Error('Use a moveTo() command first'); return segments[segments.length - 1]; } return { moveTo: function() { var segments = this._segments; if (segments.length === 1) this.removeSegment(0); if (!segments.length) this._add([ new Segment(Point.read(arguments)) ]); }, moveBy: function() { throw new Error('moveBy() is unsupported on Path items.'); }, lineTo: function() { this._add([ new Segment(Point.read(arguments)) ]); }, cubicCurveTo: function() { var handle1 = Point.read(arguments), handle2 = Point.read(arguments), to = Point.read(arguments), current = getCurrentSegment(this); current.setHandleOut(handle1.subtract(current._point)); this._add([ new Segment(to, handle2.subtract(to)) ]); }, quadraticCurveTo: function() { var handle = Point.read(arguments), to = Point.read(arguments), current = getCurrentSegment(this)._point; this.cubicCurveTo( handle.add(current.subtract(handle).multiply(1 / 3)), handle.add(to.subtract(handle).multiply(1 / 3)), to ); }, curveTo: function() { var through = Point.read(arguments), to = Point.read(arguments), t = Base.pick(Base.read(arguments), 0.5), t1 = 1 - t, current = getCurrentSegment(this)._point, handle = through.subtract(current.multiply(t1 * t1)) .subtract(to.multiply(t * t)).divide(2 * t * t1); if (handle.isNaN()) throw new Error( 'Cannot put a curve through points with parameter = ' + t); this.quadraticCurveTo(handle, to); }, arcTo: function() { var abs = Math.abs, sqrt = Math.sqrt, current = getCurrentSegment(this), from = current._point, to = Point.read(arguments), through, peek = Base.peek(arguments), clockwise = Base.pick(peek, true), center, extent, vector, matrix; if (typeof clockwise === 'boolean') { var middle = from.add(to).divide(2), through = middle.add(middle.subtract(from).rotate( clockwise ? -90 : 90)); } else if (Base.remain(arguments) <= 2) { through = to; to = Point.read(arguments); } else { var radius = Size.read(arguments), isZero = Numerical.isZero; if (isZero(radius.width) || isZero(radius.height)) return this.lineTo(to); var rotation = Base.read(arguments), clockwise = !!Base.read(arguments), large = !!Base.read(arguments), middle = from.add(to).divide(2), pt = from.subtract(middle).rotate(-rotation), x = pt.x, y = pt.y, rx = abs(radius.width), ry = abs(radius.height), rxSq = rx * rx, rySq = ry * ry, xSq = x * x, ySq = y * y; var factor = sqrt(xSq / rxSq + ySq / rySq); if (factor > 1) { rx *= factor; ry *= factor; rxSq = rx * rx; rySq = ry * ry; } factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) / (rxSq * ySq + rySq * xSq); if (abs(factor) < 1e-12) factor = 0; if (factor < 0) throw new Error( 'Cannot create an arc with the given arguments'); center = new Point(rx * y / ry, -ry * x / rx) .multiply((large === clockwise ? -1 : 1) * sqrt(factor)) .rotate(rotation).add(middle); matrix = new Matrix().translate(center).rotate(rotation) .scale(rx, ry); vector = matrix._inverseTransform(from); extent = vector.getDirectedAngle(matrix._inverseTransform(to)); if (!clockwise && extent > 0) extent -= 360; else if (clockwise && extent < 0) extent += 360; } if (through) { var l1 = new Line(from.add(through).divide(2), through.subtract(from).rotate(90), true), l2 = new Line(through.add(to).divide(2), to.subtract(through).rotate(90), true), line = new Line(from, to), throughSide = line.getSide(through); center = l1.intersect(l2, true); if (!center) { if (!throughSide) return this.lineTo(to); throw new Error( 'Cannot create an arc with the given arguments'); } vector = from.subtract(center); extent = vector.getDirectedAngle(to.subtract(center)); var centerSide = line.getSide(center); if (centerSide === 0) { extent = throughSide * abs(extent); } else if (throughSide === centerSide) { extent += extent < 0 ? 360 : -360; } } var epsilon = 1e-7, ext = abs(extent), count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90), inc = extent / count, half = inc * Math.PI / 360, z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), segments = []; for (var i = 0; i <= count; i++) { var pt = to, out = null; if (i < count) { out = vector.rotate(90).multiply(z); if (matrix) { pt = matrix._transformPoint(vector); out = matrix._transformPoint(vector.add(out)) .subtract(pt); } else { pt = center.add(vector); } } if (!i) { current.setHandleOut(out); } else { var _in = vector.rotate(-90).multiply(z); if (matrix) { _in = matrix._transformPoint(vector.add(_in)) .subtract(pt); } segments.push(new Segment(pt, _in, out)); } vector = vector.rotate(inc); } this._add(segments); }, lineBy: function() { var to = Point.read(arguments), current = getCurrentSegment(this)._point; this.lineTo(current.add(to)); }, curveBy: function() { var through = Point.read(arguments), to = Point.read(arguments), parameter = Base.read(arguments), current = getCurrentSegment(this)._point; this.curveTo(current.add(through), current.add(to), parameter); }, cubicCurveBy: function() { var handle1 = Point.read(arguments), handle2 = Point.read(arguments), to = Point.read(arguments), current = getCurrentSegment(this)._point; this.cubicCurveTo(current.add(handle1), current.add(handle2), current.add(to)); }, quadraticCurveBy: function() { var handle = Point.read(arguments), to = Point.read(arguments), current = getCurrentSegment(this)._point; this.quadraticCurveTo(current.add(handle), current.add(to)); }, arcBy: function() { var current = getCurrentSegment(this)._point, point = current.add(Point.read(arguments)), clockwise = Base.pick(Base.peek(arguments), true); if (typeof clockwise === 'boolean') { this.arcTo(point, clockwise); } else { this.arcTo(point, current.add(Point.read(arguments))); } }, closePath: function(tolerance) { this.setClosed(true); this.join(this, tolerance); } }; }, { _getBounds: function(matrix, options) { var method = options.handle ? 'getHandleBounds' : options.stroke ? 'getStrokeBounds' : 'getBounds'; return Path[method](this._segments, this._closed, this, matrix, options); }, statics: { getBounds: function(segments, closed, path, matrix, options, strokePadding) { var first = segments[0]; if (!first) return new Rectangle(); var coords = new Array(6), prevCoords = first._transformCoordinates(matrix, new Array(6)), min = prevCoords.slice(0, 2), max = min.slice(), roots = new Array(2); function processSegment(segment) { segment._transformCoordinates(matrix, coords); for (var i = 0; i < 2; i++) { Curve._addBounds( prevCoords[i], prevCoords[i + 4], coords[i + 2], coords[i], i, strokePadding ? strokePadding[i] : 0, min, max, roots); } var tmp = prevCoords; prevCoords = coords; coords = tmp; } for (var i = 1, l = segments.length; i < l; i++) processSegment(segments[i]); if (closed) processSegment(first); return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]); }, getStrokeBounds: function(segments, closed, path, matrix, options) { var style = path.getStyle(), stroke = style.hasStroke(), strokeWidth = style.getStrokeWidth(), strokeMatrix = stroke && path._getStrokeMatrix(matrix, options), strokePadding = stroke && Path._getStrokePadding(strokeWidth, strokeMatrix), bounds = Path.getBounds(segments, closed, path, matrix, options, strokePadding); if (!stroke) return bounds; var strokeRadius = strokeWidth / 2, join = style.getStrokeJoin(), cap = style.getStrokeCap(), miterLimit = style.getMiterLimit(), joinBounds = new Rectangle(new Size(strokePadding)); function addPoint(point) { bounds = bounds.include(point); } function addRound(segment) { bounds = bounds.unite( joinBounds.setCenter(segment._point.transform(matrix))); } function addJoin(segment, join) { if (join === 'round' || segment.isSmooth()) { addRound(segment); } else { Path._addBevelJoin(segment, join, strokeRadius, miterLimit, matrix, strokeMatrix, addPoint); } } function addCap(segment, cap) { if (cap === 'round') { addRound(segment); } else { Path._addSquareCap(segment, cap, strokeRadius, matrix, strokeMatrix, addPoint); } } var length = segments.length - (closed ? 0 : 1); for (var i = 1; i < length; i++) addJoin(segments[i], join); if (closed) { addJoin(segments[0], join); } else if (length > 0) { addCap(segments[0], cap); addCap(segments[segments.length - 1], cap); } return bounds; }, _getStrokePadding: function(radius, matrix) { if (!matrix) return [radius, radius]; var hor = new Point(radius, 0).transform(matrix), ver = new Point(0, radius).transform(matrix), phi = hor.getAngleInRadians(), a = hor.getLength(), b = ver.getLength(); var sin = Math.sin(phi), cos = Math.cos(phi), tan = Math.tan(phi), tx = Math.atan2(b * tan, a), ty = Math.atan2(b, tan * a); return [Math.abs(a * Math.cos(tx) * cos + b * Math.sin(tx) * sin), Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)]; }, _addBevelJoin: function(segment, join, radius, miterLimit, matrix, strokeMatrix, addPoint, isArea) { var curve2 = segment.getCurve(), curve1 = curve2.getPrevious(), point = curve2.getPoint1().transform(matrix), normal1 = curve1.getNormalAtTime(1).multiply(radius) .transform(strokeMatrix), normal2 = curve2.getNormalAtTime(0).multiply(radius) .transform(strokeMatrix); if (normal1.getDirectedAngle(normal2) < 0) { normal1 = normal1.negate(); normal2 = normal2.negate(); } if (isArea) addPoint(point); addPoint(point.add(normal1)); if (join === 'miter') { var corner = new Line(point.add(normal1), new Point(-normal1.y, normal1.x), true ).intersect(new Line(point.add(normal2), new Point(-normal2.y, normal2.x), true ), true); if (corner && point.getDistance(corner) <= miterLimit * radius) { addPoint(corner); } } addPoint(point.add(normal2)); }, _addSquareCap: function(segment, cap, radius, matrix, strokeMatrix, addPoint, isArea) { var point = segment._point.transform(matrix), loc = segment.getLocation(), normal = loc.getNormal() .multiply(loc.getTime() === 0 ? radius : -radius) .transform(strokeMatrix); if (cap === 'square') { if (isArea) { addPoint(point.subtract(normal)); addPoint(point.add(normal)); } point = point.add(normal.rotate(-90)); } addPoint(point.add(normal)); addPoint(point.subtract(normal)); }, getHandleBounds: function(segments, closed, path, matrix, options) { var style = path.getStyle(), stroke = options.stroke && style.hasStroke(), strokePadding, joinPadding; if (stroke) { var strokeMatrix = path._getStrokeMatrix(matrix, options), strokeRadius = style.getStrokeWidth() / 2, joinRadius = strokeRadius; if (style.getStrokeJoin() === 'miter') joinRadius = strokeRadius * style.getMiterLimit(); if (style.getStrokeCap() === 'square') joinRadius = Math.max(joinRadius, strokeRadius * Math.SQRT2); strokePadding = Path._getStrokePadding(strokeRadius, strokeMatrix); joinPadding = Path._getStrokePadding(joinRadius, strokeMatrix); } var coords = new Array(6), x1 = Infinity, x2 = -x1, y1 = x1, y2 = x2; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i]; segment._transformCoordinates(matrix, coords); for (var j = 0; j < 6; j += 2) { var padding = !j ? joinPadding : strokePadding, paddingX = padding ? padding[0] : 0, paddingY = padding ? padding[1] : 0, x = coords[j], y = coords[j + 1], xn = x - paddingX, xx = x + paddingX, yn = y - paddingY, yx = y + paddingY; if (xn < x1) x1 = xn; if (xx > x2) x2 = xx; if (yn < y1) y1 = yn; if (yx > y2) y2 = yx; } } return new Rectangle(x1, y1, x2 - x1, y2 - y1); } }}); Path.inject({ statics: new function() { var kappa = 0.5522847498307936, ellipseSegments = [ new Segment([-1, 0], [0, kappa ], [0, -kappa]), new Segment([0, -1], [-kappa, 0], [kappa, 0 ]), new Segment([1, 0], [0, -kappa], [0, kappa ]), new Segment([0, 1], [kappa, 0 ], [-kappa, 0]) ]; function createPath(segments, closed, args) { var props = Base.getNamed(args), path = new Path(props && props.insert == false && Item.NO_INSERT); path._add(segments); path._closed = closed; return path.set(props, { insert: true }); } function createEllipse(center, radius, args) { var segments = new Array(4); for (var i = 0; i < 4; i++) { var segment = ellipseSegments[i]; segments[i] = new Segment( segment._point.multiply(radius).add(center), segment._handleIn.multiply(radius), segment._handleOut.multiply(radius) ); } return createPath(segments, true, args); } return { Line: function() { return createPath([ new Segment(Point.readNamed(arguments, 'from')), new Segment(Point.readNamed(arguments, 'to')) ], false, arguments); }, Circle: function() { var center = Point.readNamed(arguments, 'center'), radius = Base.readNamed(arguments, 'radius'); return createEllipse(center, new Size(radius), arguments); }, Rectangle: function() { var rect = Rectangle.readNamed(arguments, 'rectangle'), radius = Size.readNamed(arguments, 'radius', 0, { readNull: true }), bl = rect.getBottomLeft(true), tl = rect.getTopLeft(true), tr = rect.getTopRight(true), br = rect.getBottomRight(true), segments; if (!radius || radius.isZero()) { segments = [ new Segment(bl), new Segment(tl), new Segment(tr), new Segment(br) ]; } else { radius = Size.min(radius, rect.getSize(true).divide(2)); var rx = radius.width, ry = radius.height, hx = rx * kappa, hy = ry * kappa; segments = [ new Segment(bl.add(rx, 0), null, [-hx, 0]), new Segment(bl.subtract(0, ry), [0, hy]), new Segment(tl.add(0, ry), null, [0, -hy]), new Segment(tl.add(rx, 0), [-hx, 0], null), new Segment(tr.subtract(rx, 0), null, [hx, 0]), new Segment(tr.add(0, ry), [0, -hy], null), new Segment(br.subtract(0, ry), null, [0, hy]), new Segment(br.subtract(rx, 0), [hx, 0]) ]; } return createPath(segments, true, arguments); }, RoundRectangle: '#Rectangle', Ellipse: function() { var ellipse = Shape._readEllipse(arguments); return createEllipse(ellipse.center, ellipse.radius, arguments); }, Oval: '#Ellipse', Arc: function() { var from = Point.readNamed(arguments, 'from'), through = Point.readNamed(arguments, 'through'), to = Point.readNamed(arguments, 'to'), props = Base.getNamed(arguments), path = new Path(props && props.insert == false && Item.NO_INSERT); path.moveTo(from); path.arcTo(through, to); return path.set(props); }, RegularPolygon: function() { var center = Point.readNamed(arguments, 'center'), sides = Base.readNamed(arguments, 'sides'), radius = Base.readNamed(arguments, 'radius'), step = 360 / sides, three = sides % 3 === 0, vector = new Point(0, three ? -radius : radius), offset = three ? -1 : 0.5, segments = new Array(sides); for (var i = 0; i < sides; i++) segments[i] = new Segment(center.add( vector.rotate((i + offset) * step))); return createPath(segments, true, arguments); }, Star: function() { var center = Point.readNamed(arguments, 'center'), points = Base.readNamed(arguments, 'points') * 2, radius1 = Base.readNamed(arguments, 'radius1'), radius2 = Base.readNamed(arguments, 'radius2'), step = 360 / points, vector = new Point(0, -1), segments = new Array(points); for (var i = 0; i < points; i++) segments[i] = new Segment(center.add(vector.rotate(step * i) .multiply(i % 2 ? radius2 : radius1))); return createPath(segments, true, arguments); } }; }}); var CompoundPath = PathItem.extend({ _class: 'CompoundPath', _serializeFields: { children: [] }, beans: true, initialize: function CompoundPath(arg) { this._children = []; this._namedChildren = {}; if (!this._initialize(arg)) { if (typeof arg === 'string') { this.setPathData(arg); } else { this.addChildren(Array.isArray(arg) ? arg : arguments); } } }, insertChildren: function insertChildren(index, items) { var list = items, first = list[0]; if (first && typeof first[0] === 'number') list = [list]; for (var i = items.length - 1; i >= 0; i--) { var item = list[i]; if (list === items && !(item instanceof Path)) list = Base.slice(list); if (Array.isArray(item)) { list[i] = new Path({ segments: item, insert: false }); } else if (item instanceof CompoundPath) { list.splice.apply(list, [i, 1].concat(item.removeChildren())); item.remove(); } } return insertChildren.base.call(this, index, list); }, reduce: function reduce(options) { var children = this._children; for (var i = children.length - 1; i >= 0; i--) { var path = children[i].reduce(options); if (path.isEmpty()) path.remove(); } if (!children.length) { var path = new Path(Item.NO_INSERT); path.copyAttributes(this); path.insertAbove(this); this.remove(); return path; } return reduce.base.call(this); }, isClosed: function() { var children = this._children; for (var i = 0, l = children.length; i < l; i++) { if (!children[i]._closed) return false; } return true; }, setClosed: function(closed) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) { children[i].setClosed(closed); } }, getFirstSegment: function() { var first = this.getFirstChild(); return first && first.getFirstSegment(); }, getLastSegment: function() { var last = this.getLastChild(); return last && last.getLastSegment(); }, getCurves: function() { var children = this._children, curves = []; for (var i = 0, l = children.length; i < l; i++) curves.push.apply(curves, children[i].getCurves()); return curves; }, getFirstCurve: function() { var first = this.getFirstChild(); return first && first.getFirstCurve(); }, getLastCurve: function() { var last = this.getLastChild(); return last && last.getLastCurve(); }, getArea: function() { var children = this._children, area = 0; for (var i = 0, l = children.length; i < l; i++) area += children[i].getArea(); return area; }, getLength: function() { var children = this._children, length = 0; for (var i = 0, l = children.length; i < l; i++) length += children[i].getLength(); return length; }, getPathData: function(_matrix, _precision) { var children = this._children, paths = []; for (var i = 0, l = children.length; i < l; i++) { var child = children[i], mx = child._matrix; paths.push(child.getPathData(_matrix && !mx.isIdentity() ? _matrix.appended(mx) : _matrix, _precision)); } return paths.join(''); }, _hitTestChildren: function _hitTestChildren(point, options, viewMatrix) { return _hitTestChildren.base.call(this, point, options.class === Path || options.type === 'path' ? options : Base.set({}, options, { fill: false }), viewMatrix); }, _draw: function(ctx, param, viewMatrix, strokeMatrix) { var children = this._children; if (!children.length) return; param = param.extend({ dontStart: true, dontFinish: true }); ctx.beginPath(); for (var i = 0, l = children.length; i < l; i++) children[i].draw(ctx, param, strokeMatrix); if (!param.clip) { this._setStyles(ctx, param, viewMatrix); var style = this._style; if (style.hasFill()) { ctx.fill(style.getFillRule()); ctx.shadowColor = 'rgba(0,0,0,0)'; } if (style.hasStroke()) ctx.stroke(); } }, _drawSelected: function(ctx, matrix, selectionItems) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) { var child = children[i], mx = child._matrix; if (!selectionItems[child._id]) { child._drawSelected(ctx, mx.isIdentity() ? matrix : matrix.appended(mx)); } } } }, new function() { function getCurrentPath(that, check) { var children = that._children; if (check && !children.length) throw new Error('Use a moveTo() command first'); return children[children.length - 1]; } return Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', 'arcTo', 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', 'arcBy'], function(key) { this[key] = function() { var path = getCurrentPath(this, true); path[key].apply(path, arguments); }; }, { moveTo: function() { var current = getCurrentPath(this), path = current && current.isEmpty() ? current : new Path(Item.NO_INSERT); if (path !== current) this.addChild(path); path.moveTo.apply(path, arguments); }, moveBy: function() { var current = getCurrentPath(this, true), last = current && current.getLastSegment(), point = Point.read(arguments); this.moveTo(last ? point.add(last._point) : point); }, closePath: function(tolerance) { getCurrentPath(this, true).closePath(tolerance); } } ); }, Base.each(['reverse', 'flatten', 'simplify', 'smooth'], function(key) { this[key] = function(param) { var children = this._children, res; for (var i = 0, l = children.length; i < l; i++) { res = children[i][key](param) || res; } return res; }; }, {})); PathItem.inject(new function() { var min = Math.min, max = Math.max, abs = Math.abs, operators = { unite: { '1': true, '2': true }, intersect: { '2': true }, subtract: { '1': true }, exclude: { '1': true, '-1': true } }; function preparePath(path, resolve) { var res = path.clone(false).reduce({ simplify: true }) .transform(null, true, true); return resolve ? res.resolveCrossings().reorient( res.getFillRule() === 'nonzero', true) : res; } function createResult(paths, simplify, path1, path2, options) { var result = new CompoundPath(Item.NO_INSERT); result.addChildren(paths, true); result = result.reduce({ simplify: simplify }); if (!(options && options.insert == false)) { result.insertAbove(path2 && path1.isSibling(path2) && path1.getIndex() < path2.getIndex() ? path2 : path1); } result.copyAttributes(path1, true); return result; } function traceBoolean(path1, path2, operation, options) { if (options && (options.trace == false || options.stroke) && /^(subtract|intersect)$/.test(operation)) return splitBoolean(path1, path2, operation); var _path1 = preparePath(path1, true), _path2 = path2 && path1 !== path2 && preparePath(path2, true), operator = operators[operation]; operator[operation] = true; if (_path2 && (operator.subtract || operator.exclude) ^ (_path2.isClockwise() ^ _path1.isClockwise())) _path2.reverse(); var crossings = divideLocations( CurveLocation.expand(_path1.getCrossings(_path2))), paths1 = _path1._children || [_path1], paths2 = _path2 && (_path2._children || [_path2]), segments = [], curves = [], paths; function collect(paths) { for (var i = 0, l = paths.length; i < l; i++) { var path = paths[i]; segments.push.apply(segments, path._segments); curves.push.apply(curves, path.getCurves()); path._overlapsOnly = true; } } if (crossings.length) { collect(paths1); if (paths2) collect(paths2); for (var i = 0, l = crossings.length; i < l; i++) { propagateWinding(crossings[i]._segment, _path1, _path2, curves, operator); } for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], inter = segment._intersection; if (!segment._winding) { propagateWinding(segment, _path1, _path2, curves, operator); } if (!(inter && inter._overlap)) segment._path._overlapsOnly = false; } paths = tracePaths(segments, operator); } else { paths = reorientPaths( paths2 ? paths1.concat(paths2) : paths1.slice(), function(w) { return !!operator[w]; }); } return createResult(paths, true, path1, path2, options); } function splitBoolean(path1, path2, operation) { var _path1 = preparePath(path1), _path2 = preparePath(path2), crossings = _path1.getCrossings(_path2), subtract = operation === 'subtract', divide = operation === 'divide', added = {}, paths = []; function addPath(path) { if (!added[path._id] && (divide || _path2.contains(path.getPointAt(path.getLength() / 2)) ^ subtract)) { paths.unshift(path); return added[path._id] = true; } } for (var i = crossings.length - 1; i >= 0; i--) { var path = crossings[i].split(); if (path) { if (addPath(path)) path.getFirstSegment().setHandleIn(0, 0); _path1.getLastSegment().setHandleOut(0, 0); } } addPath(_path1); return createResult(paths, false, path1, path2); } function linkIntersections(from, to) { var prev = from; while (prev) { if (prev === to) return; prev = prev._previous; } while (from._next && from._next !== to) from = from._next; if (!from._next) { while (to._previous) to = to._previous; from._next = to; to._previous = from; } } function clearCurveHandles(curves) { for (var i = curves.length - 1; i >= 0; i--) curves[i].clearHandles(); } function reorientPaths(paths, isInside, clockwise) { var length = paths && paths.length; if (length) { var lookup = Base.each(paths, function (path, i) { this[path._id] = { container: null, winding: path.isClockwise() ? 1 : -1, index: i }; }, {}), sorted = paths.slice().sort(function (a, b) { return abs(b.getArea()) - abs(a.getArea()); }), first = sorted[0]; if (clockwise == null) clockwise = first.isClockwise(); for (var i = 0; i < length; i++) { var path1 = sorted[i], entry1 = lookup[path1._id], point = path1.getInteriorPoint(), containerWinding = 0; for (var j = i - 1; j >= 0; j--) { var path2 = sorted[j]; if (path2.contains(point)) { var entry2 = lookup[path2._id]; containerWinding = entry2.winding; entry1.winding += containerWinding; entry1.container = entry2.exclude ? entry2.container : path2; break; } } if (isInside(entry1.winding) === isInside(containerWinding)) { entry1.exclude = true; paths[entry1.index] = null; } else { var container = entry1.container; path1.setClockwise(container ? !container.isClockwise() : clockwise); } } } return paths; } function divideLocations(locations, include, clearLater) { var results = include && [], tMin = 1e-8, tMax = 1 - tMin, clearHandles = false, clearCurves = clearLater || [], clearLookup = clearLater && {}, renormalizeLocs, prevCurve, prevTime; function getId(curve) { return curve._path._id + '.' + curve._segment1._index; } for (var i = (clearLater && clearLater.length) - 1; i >= 0; i--) { var curve = clearLater[i]; if (curve._path) clearLookup[getId(curve)] = true; } for (var i = locations.length - 1; i >= 0; i--) { var loc = locations[i], time = loc._time, origTime = time, exclude = include && !include(loc), curve = loc._curve, segment; if (curve) { if (curve !== prevCurve) { clearHandles = !curve.hasHandles() || clearLookup && clearLookup[getId(curve)]; renormalizeLocs = []; prevTime = null; prevCurve = curve; } else if (prevTime >= tMin) { time /= prevTime; } } if (exclude) { if (renormalizeLocs) renormalizeLocs.push(loc); continue; } else if (include) { results.unshift(loc); } prevTime = origTime; if (time < tMin) { segment = curve._segment1; } else if (time > tMax) { segment = curve._segment2; } else { var newCurve = curve.divideAtTime(time, true); if (clearHandles) clearCurves.push(curve, newCurve); segment = newCurve._segment1; for (var j = renormalizeLocs.length - 1; j >= 0; j--) { var l = renormalizeLocs[j]; l._time = (l._time - time) / (1 - time); } } loc._setSegment(segment); var inter = segment._intersection, dest = loc._intersection; if (inter) { linkIntersections(inter, dest); var other = inter; while (other) { linkIntersections(other._intersection, inter); other = other._next; } } else { segment._intersection = dest; } } if (!clearLater) clearCurveHandles(clearCurves); return results || locations; } function getWinding(point, curves, dir, closed, dontFlip) { var ia = dir ? 1 : 0, io = ia ^ 1, pv = [point.x, point.y], pa = pv[ia], po = pv[io], windingEpsilon = 1e-9, qualityEpsilon = 1e-6, paL = pa - windingEpsilon, paR = pa + windingEpsilon, windingL = 0, windingR = 0, pathWindingL = 0, pathWindingR = 0, onPath = false, onAnyPath = false, quality = 1, roots = [], vPrev, vClose; function addWinding(v) { var o0 = v[io + 0], o3 = v[io + 6]; if (po < min(o0, o3) || po > max(o0, o3)) { return; } var a0 = v[ia + 0], a1 = v[ia + 2], a2 = v[ia + 4], a3 = v[ia + 6]; if (o0 === o3) { if (a0 < paR && a3 > paL || a3 < paR && a0 > paL) { onPath = true; } return; } var t = po === o0 ? 0 : po === o3 ? 1 : paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3) ? 1 : Curve.solveCubic(v, io, po, roots, 0, 1) > 0 ? roots[0] : 1, a = t === 0 ? a0 : t === 1 ? a3 : Curve.getPoint(v, t)[dir ? 'y' : 'x'], winding = o0 > o3 ? 1 : -1, windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1, a3Prev = vPrev[ia + 6]; if (po !== o0) { if (a < paL) { pathWindingL += winding; } else if (a > paR) { pathWindingR += winding; } else { onPath = true; } if (a > pa - qualityEpsilon && a < pa + qualityEpsilon) quality /= 2; } else { if (winding !== windingPrev) { if (a0 < paL) { pathWindingL += winding; } else if (a0 > paR) { pathWindingR += winding; } } else if (a0 != a3Prev) { if (a3Prev < paR && a > paR) { pathWindingR += winding; onPath = true; } else if (a3Prev > paL && a < paL) { pathWindingL += winding; onPath = true; } } quality = 0; } vPrev = v; return !dontFlip && a > paL && a < paR && Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0 && getWinding(point, curves, !dir, closed, true); } function handleCurve(v) { var o0 = v[io + 0], o1 = v[io + 2], o2 = v[io + 4], o3 = v[io + 6]; if (po <= max(o0, o1, o2, o3) && po >= min(o0, o1, o2, o3)) { var a0 = v[ia + 0], a1 = v[ia + 2], a2 = v[ia + 4], a3 = v[ia + 6], monoCurves = paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3) ? [v] : Curve.getMonoCurves(v, dir), res; for (var i = 0, l = monoCurves.length; i < l; i++) { if (res = addWinding(monoCurves[i])) return res; } } } for (var i = 0, l = curves.length; i < l; i++) { var curve = curves[i], path = curve._path, v = curve.getValues(), res; if (!i || curves[i - 1]._path !== path) { vPrev = null; if (!path._closed) { vClose = Curve.getValues( path.getLastCurve().getSegment2(), curve.getSegment1(), null, !closed); if (vClose[io] !== vClose[io + 6]) { vPrev = vClose; } } if (!vPrev) { vPrev = v; var prev = path.getLastCurve(); while (prev && prev !== curve) { var v2 = prev.getValues(); if (v2[io] !== v2[io + 6]) { vPrev = v2; break; } prev = prev.getPrevious(); } } } if (res = handleCurve(v)) return res; if (i + 1 === l || curves[i + 1]._path !== path) { if (vClose && (res = handleCurve(vClose))) return res; if (onPath && !pathWindingL && !pathWindingR) { pathWindingL = pathWindingR = path.isClockwise(closed) ^ dir ? 1 : -1; } windingL += pathWindingL; windingR += pathWindingR; pathWindingL = pathWindingR = 0; if (onPath) { onAnyPath = true; onPath = false; } vClose = null; } } windingL = abs(windingL); windingR = abs(windingR); return { winding: max(windingL, windingR), windingL: windingL, windingR: windingR, quality: quality, onPath: onAnyPath }; } function propagateWinding(segment, path1, path2, curves, operator) { var chain = [], start = segment, totalLength = 0, winding; do { var curve = segment.getCurve(), length = curve.getLength(); chain.push({ segment: segment, curve: curve, length: length }); totalLength += length; segment = segment.getNext(); } while (segment && !segment._intersection && segment !== start); var offsets = [0.5, 0.25, 0.75], winding = { winding: 0, quality: -1 }, tMin = 1e-8, tMax = 1 - tMin; for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) { var length = totalLength * offsets[i]; for (var j = 0, l = chain.length; j < l; j++) { var entry = chain[j], curveLength = entry.length; if (length <= curveLength) { var curve = entry.curve, path = curve._path, parent = path._parent, operand = parent instanceof CompoundPath ? parent : path, t = Numerical.clamp(curve.getTimeAt(length), tMin, tMax), pt = curve.getPointAtTime(t), dir = abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2; var wind = !(operator.subtract && path2 && ( operand === path1 && path2._getWinding(pt, dir, true).winding || operand === path2 && !path1._getWinding(pt, dir, true).winding)) ? getWinding(pt, curves, dir, true) : { winding: 0, quality: 1 }; if (wind.quality > winding.quality) winding = wind; break; } length -= curveLength; } } for (var j = chain.length - 1; j >= 0; j--) { chain[j].segment._winding = winding; } } function tracePaths(segments, operator) { var paths = [], starts; function isValid(seg) { var winding; return !!(seg && !seg._visited && (!operator || operator[(winding = seg._winding || {}).winding] && !(operator.unite && winding.winding === 2 && winding.windingL && winding.windingR))); } function isStart(seg) { if (seg) { for (var i = 0, l = starts.length; i < l; i++) { if (seg === starts[i]) return true; } } return false; } function visitPath(path) { var segments = path._segments; for (var i = 0, l = segments.length; i < l; i++) { segments[i]._visited = true; } } function getCrossingSegments(segment, collectStarts) { var inter = segment._intersection, start = inter, crossings = []; if (collectStarts) starts = [segment]; function collect(inter, end) { while (inter && inter !== end) { var other = inter._segment, path = other && other._path; if (path) { var next = other.getNext() || path.getFirstSegment(), nextInter = next._intersection; if (other !== segment && (isStart(other) || isStart(next) || next && (isValid(other) && (isValid(next) || nextInter && isValid(nextInter._segment)))) ) { crossings.push(other); } if (collectStarts) starts.push(other); } inter = inter._next; } } if (inter) { collect(inter); while (inter && inter._prev) inter = inter._prev; collect(inter, start); } return crossings; } segments.sort(function(seg1, seg2) { var inter1 = seg1._intersection, inter2 = seg2._intersection, over1 = !!(inter1 && inter1._overlap), over2 = !!(inter2 && inter2._overlap), path1 = seg1._path, path2 = seg2._path; return over1 ^ over2 ? over1 ? 1 : -1 : !inter1 ^ !inter2 ? inter1 ? 1 : -1 : path1 !== path2 ? path1._id - path2._id : seg1._index - seg2._index; }); for (var i = 0, l = segments.length; i < l; i++) { var seg = segments[i], valid = isValid(seg), path = null, finished = false, closed = true, branches = [], branch, visited, handleIn; if (valid && seg._path._overlapsOnly) { var path1 = seg._path, path2 = seg._intersection._segment._path; if (path1.compare(path2)) { if (path1.getArea()) paths.push(path1.clone(false)); visitPath(path1); visitPath(path2); valid = false; } } while (valid) { var first = !path, crossings = getCrossingSegments(seg, first), other = crossings.shift(), finished = !first && (isStart(seg) || isStart(other)), cross = !finished && other; if (first) { path = new Path(Item.NO_INSERT); branch = null; } if (finished) { if (seg.isFirst() || seg.isLast()) closed = seg._path._closed; seg._visited = true; break; } if (cross && branch) { branches.push(branch); branch = null; } if (!branch) { if (cross) crossings.push(seg); branch = { start: path._segments.length, crossings: crossings, visited: visited = [], handleIn: handleIn }; } if (cross) seg = other; if (!isValid(seg)) { path.removeSegments(branch.start); for (var j = 0, k = visited.length; j < k; j++) { visited[j]._visited = false; } visited.length = 0; do { seg = branch && branch.crossings.shift(); if (!seg || !seg._path) { seg = null; branch = branches.pop(); if (branch) { visited = branch.visited; handleIn = branch.handleIn; } } } while (branch && !isValid(seg)); if (!seg) break; } var next = seg.getNext(); path.add(new Segment(seg._point, handleIn, next && seg._handleOut)); seg._visited = true; visited.push(seg); seg = next || seg._path.getFirstSegment(); handleIn = next && next._handleIn; } if (finished) { if (closed) { path.getFirstSegment().setHandleIn(handleIn); path.setClosed(closed); } if (path.getArea() !== 0) { paths.push(path); } } } return paths; } return { _getWinding: function(point, dir, closed) { return getWinding(point, this.getCurves(), dir, closed); }, unite: function(path, options) { return traceBoolean(this, path, 'unite', options); }, intersect: function(path, options) { return traceBoolean(this, path, 'intersect', options); }, subtract: function(path, options) { return traceBoolean(this, path, 'subtract', options); }, exclude: function(path, options) { return traceBoolean(this, path, 'exclude', options); }, divide: function(path, options) { return options && (options.trace == false || options.stroke) ? splitBoolean(this, path, 'divide') : createResult([ this.subtract(path, options), this.intersect(path, options) ], true, this, path, options); }, resolveCrossings: function() { var children = this._children, paths = children || [this]; function hasOverlap(seg, path) { var inter = seg && seg._intersection; return inter && inter._overlap && inter._path === path; } var hasOverlaps = false, hasCrossings = false, intersections = this.getIntersections(null, function(inter) { return inter.hasOverlap() && (hasOverlaps = true) || inter.isCrossing() && (hasCrossings = true); }), clearCurves = hasOverlaps && hasCrossings && []; intersections = CurveLocation.expand(intersections); if (hasOverlaps) { var overlaps = divideLocations(intersections, function(inter) { return inter.hasOverlap(); }, clearCurves); for (var i = overlaps.length - 1; i >= 0; i--) { var overlap = overlaps[i], path = overlap._path, seg = overlap._segment, prev = seg.getPrevious(), next = seg.getNext(); if (hasOverlap(prev, path) && hasOverlap(next, path)) { seg.remove(); prev._handleOut._set(0, 0); next._handleIn._set(0, 0); if (prev !== seg && !prev.getCurve().hasLength()) { next._handleIn.set(prev._handleIn); prev.remove(); } } } } if (hasCrossings) { divideLocations(intersections, hasOverlaps && function(inter) { var curve1 = inter.getCurve(), seg1 = inter.getSegment(), other = inter._intersection, curve2 = other._curve, seg2 = other._segment; if (curve1 && curve2 && curve1._path && curve2._path) return true; if (seg1) seg1._intersection = null; if (seg2) seg2._intersection = null; }, clearCurves); if (clearCurves) clearCurveHandles(clearCurves); paths = tracePaths(Base.each(paths, function(path) { this.push.apply(this, path._segments); }, [])); } var length = paths.length, item; if (length > 1 && children) { if (paths !== children) this.setChildren(paths); item = this; } else if (length === 1 && !children) { if (paths[0] !== this) this.setSegments(paths[0].removeSegments()); item = this; } if (!item) { item = new CompoundPath(Item.NO_INSERT); item.addChildren(paths); item = item.reduce(); item.copyAttributes(this); this.replaceWith(item); } return item; }, reorient: function(nonZero, clockwise) { var children = this._children; if (children && children.length) { this.setChildren(reorientPaths(this.removeChildren(), function(w) { return !!(nonZero ? w : w & 1); }, clockwise)); } else if (clockwise !== undefined) { this.setClockwise(clockwise); } return this; }, getInteriorPoint: function() { var bounds = this.getBounds(), point = bounds.getCenter(true); if (!this.contains(point)) { var curves = this.getCurves(), y = point.y, intercepts = [], roots = []; for (var i = 0, l = curves.length; i < l; i++) { var v = curves[i].getValues(), o0 = v[1], o1 = v[3], o2 = v[5], o3 = v[7]; if (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) { var monoCurves = Curve.getMonoCurves(v); for (var j = 0, m = monoCurves.length; j < m; j++) { var mv = monoCurves[j], mo0 = mv[1], mo3 = mv[7]; if ((mo0 !== mo3) && (y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){ var x = y === mo0 ? mv[0] : y === mo3 ? mv[6] : Curve.solveCubic(mv, 1, y, roots, 0, 1) === 1 ? Curve.getPoint(mv, roots[0]).x : (mv[0] + mv[6]) / 2; intercepts.push(x); } } } } if (intercepts.length > 1) { intercepts.sort(function(a, b) { return a - b; }); point.x = (intercepts[0] + intercepts[1]) / 2; } } return point; } }; }); var PathFlattener = Base.extend({ _class: 'PathFlattener', initialize: function(path, flatness, maxRecursion, ignoreStraight, matrix) { var curves = [], parts = [], length = 0, minSpan = 1 / (maxRecursion || 32), segments = path._segments, segment1 = segments[0], segment2; function addCurve(segment1, segment2) { var curve = Curve.getValues(segment1, segment2, matrix); curves.push(curve); computeParts(curve, segment1._index, 0, 1); } function computeParts(curve, index, t1, t2) { if ((t2 - t1) > minSpan && !(ignoreStraight && Curve.isStraight(curve)) && !Curve.isFlatEnough(curve, flatness || 0.25)) { var halves = Curve.subdivide(curve, 0.5), tMid = (t1 + t2) / 2; computeParts(halves[0], index, t1, tMid); computeParts(halves[1], index, tMid, t2); } else { var dx = curve[6] - curve[0], dy = curve[7] - curve[1], dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { length += dist; parts.push({ offset: length, curve: curve, index: index, time: t2, }); } } } for (var i = 1, l = segments.length; i < l; i++) { segment2 = segments[i]; addCurve(segment1, segment2); segment1 = segment2; } if (path._closed) addCurve(segment2, segments[0]); this.curves = curves; this.parts = parts; this.length = length; this.index = 0; }, _get: function(offset) { var parts = this.parts, length = parts.length, start, i, j = this.index; for (;;) { i = j; if (!j || parts[--j].offset < offset) break; } for (; i < length; i++) { var part = parts[i]; if (part.offset >= offset) { this.index = i; var prev = parts[i - 1], prevTime = prev && prev.index === part.index ? prev.time : 0, prevOffset = prev ? prev.offset : 0; return { index: part.index, time: prevTime + (part.time - prevTime) * (offset - prevOffset) / (part.offset - prevOffset) }; } } return { index: parts[length - 1].index, time: 1 }; }, drawPart: function(ctx, from, to) { var start = this._get(from), end = this._get(to); for (var i = start.index, l = end.index; i <= l; i++) { var curve = Curve.getPart(this.curves[i], i === start.index ? start.time : 0, i === end.index ? end.time : 1); if (i === start.index) ctx.moveTo(curve[0], curve[1]); ctx.bezierCurveTo.apply(ctx, curve.slice(2)); } } }, Base.each(Curve._evaluateMethods, function(name) { this[name + 'At'] = function(offset) { var param = this._get(offset); return Curve[name](this.curves[param.index], param.time); }; }, {}) ); var PathFitter = Base.extend({ initialize: function(path) { var points = this.points = [], segments = path._segments, closed = path._closed; for (var i = 0, prev, l = segments.length; i < l; i++) { var point = segments[i].point; if (!prev || !prev.equals(point)) { points.push(prev = point.clone()); } } if (closed) { points.unshift(points[points.length - 1]); points.push(points[1]); } this.closed = closed; }, fit: function(error) { var points = this.points, length = points.length, segments = null; if (length > 0) { segments = [new Segment(points[0])]; if (length > 1) { this.fitCubic(segments, error, 0, length - 1, points[1].subtract(points[0]), points[length - 2].subtract(points[length - 1])); if (this.closed) { segments.shift(); segments.pop(); } } } return segments; }, fitCubic: function(segments, error, first, last, tan1, tan2) { var points = this.points; if (last - first === 1) { var pt1 = points[first], pt2 = points[last], dist = pt1.getDistance(pt2) / 3; this.addCurve(segments, [pt1, pt1.add(tan1.normalize(dist)), pt2.add(tan2.normalize(dist)), pt2]); return; } var uPrime = this.chordLengthParameterize(first, last), maxError = Math.max(error, error * error), split, parametersInOrder = true; for (var i = 0; i <= 4; i++) { var curve = this.generateBezier(first, last, uPrime, tan1, tan2); var max = this.findMaxError(first, last, curve, uPrime); if (max.error < error && parametersInOrder) { this.addCurve(segments, curve); return; } split = max.index; if (max.error >= maxError) break; parametersInOrder = this.reparameterize(first, last, uPrime, curve); maxError = max.error; } var tanCenter = points[split - 1].subtract(points[split + 1]); this.fitCubic(segments, error, first, split, tan1, tanCenter); this.fitCubic(segments, error, split, last, tanCenter.negate(), tan2); }, addCurve: function(segments, curve) { var prev = segments[segments.length - 1]; prev.setHandleOut(curve[1].subtract(curve[0])); segments.push(new Segment(curve[3], curve[2].subtract(curve[3]))); }, generateBezier: function(first, last, uPrime, tan1, tan2) { var epsilon = 1e-12, abs = Math.abs, points = this.points, pt1 = points[first], pt2 = points[last], C = [[0, 0], [0, 0]], X = [0, 0]; for (var i = 0, l = last - first + 1; i < l; i++) { var u = uPrime[i], t = 1 - u, b = 3 * u * t, b0 = t * t * t, b1 = b * t, b2 = b * u, b3 = u * u * u, a1 = tan1.normalize(b1), a2 = tan2.normalize(b2), tmp = points[first + i] .subtract(pt1.multiply(b0 + b1)) .subtract(pt2.multiply(b2 + b3)); C[0][0] += a1.dot(a1); C[0][1] += a1.dot(a2); C[1][0] = C[0][1]; C[1][1] += a2.dot(a2); X[0] += a1.dot(tmp); X[1] += a2.dot(tmp); } var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1], alpha1, alpha2; if (abs(detC0C1) > epsilon) { var detC0X = C[0][0] * X[1] - C[1][0] * X[0], detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; alpha1 = detXC1 / detC0C1; alpha2 = detC0X / detC0C1; } else { var c0 = C[0][0] + C[0][1], c1 = C[1][0] + C[1][1]; alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 : abs(c1) > epsilon ? X[1] / c1 : 0; } var segLength = pt2.getDistance(pt1), eps = epsilon * segLength, handle1, handle2; if (alpha1 < eps || alpha2 < eps) { alpha1 = alpha2 = segLength / 3; } else { var line = pt2.subtract(pt1); handle1 = tan1.normalize(alpha1); handle2 = tan2.normalize(alpha2); if (handle1.dot(line) - handle2.dot(line) > segLength * segLength) { alpha1 = alpha2 = segLength / 3; handle1 = handle2 = null; } } return [pt1, pt1.add(handle1 || tan1.normalize(alpha1)), pt2.add(handle2 || tan2.normalize(alpha2)), pt2]; }, reparameterize: function(first, last, u, curve) { for (var i = first; i <= last; i++) { u[i - first] = this.findRoot(curve, this.points[i], u[i - first]); } for (var i = 1, l = u.length; i < l; i++) { if (u[i] <= u[i - 1]) return false; } return true; }, findRoot: function(curve, point, u) { var curve1 = [], curve2 = []; for (var i = 0; i <= 2; i++) { curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3); } for (var i = 0; i <= 1; i++) { curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2); } var pt = this.evaluate(3, curve, u), pt1 = this.evaluate(2, curve1, u), pt2 = this.evaluate(1, curve2, u), diff = pt.subtract(point), df = pt1.dot(pt1) + diff.dot(pt2); return Numerical.isZero(df) ? u : u - diff.dot(pt1) / df; }, evaluate: function(degree, curve, t) { var tmp = curve.slice(); for (var i = 1; i <= degree; i++) { for (var j = 0; j <= degree - i; j++) { tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t)); } } return tmp[0]; }, chordLengthParameterize: function(first, last) { var u = [0]; for (var i = first + 1; i <= last; i++) { u[i - first] = u[i - first - 1] + this.points[i].getDistance(this.points[i - 1]); } for (var i = 1, m = last - first; i <= m; i++) { u[i] /= u[m]; } return u; }, findMaxError: function(first, last, curve, u) { var index = Math.floor((last - first + 1) / 2), maxDist = 0; for (var i = first + 1; i < last; i++) { var P = this.evaluate(3, curve, u[i - first]); var v = P.subtract(this.points[i]); var dist = v.x * v.x + v.y * v.y; if (dist >= maxDist) { maxDist = dist; index = i; } } return { error: maxDist, index: index }; } }); var TextItem = Item.extend({ _class: 'TextItem', _applyMatrix: false, _canApplyMatrix: false, _serializeFields: { content: null }, _boundsOptions: { stroke: false, handle: false }, initialize: function TextItem(arg) { this._content = ''; this._lines = []; var hasProps = arg && Base.isPlainObject(arg) && arg.x === undefined && arg.y === undefined; this._initialize(hasProps && arg, !hasProps && Point.read(arguments)); }, _equals: function(item) { return this._content === item._content; }, copyContent: function(source) { this.setContent(source._content); }, getContent: function() { return this._content; }, setContent: function(content) { this._content = '' + content; this._lines = this._content.split(/\r\n|\n|\r/mg); this._changed(265); }, isEmpty: function() { return !this._content; }, getCharacterStyle: '#getStyle', setCharacterStyle: '#setStyle', getParagraphStyle: '#getStyle', setParagraphStyle: '#setStyle' }); var PointText = TextItem.extend({ _class: 'PointText', initialize: function PointText() { TextItem.apply(this, arguments); }, getPoint: function() { var point = this._matrix.getTranslation(); return new LinkedPoint(point.x, point.y, this, 'setPoint'); }, setPoint: function() { var point = Point.read(arguments); this.translate(point.subtract(this._matrix.getTranslation())); }, _draw: function(ctx, param, viewMatrix) { if (!this._content) return; this._setStyles(ctx, param, viewMatrix); var lines = this._lines, style = this._style, hasFill = style.hasFill(), hasStroke = style.hasStroke(), leading = style.getLeading(), shadowColor = ctx.shadowColor; ctx.font = style.getFontStyle(); ctx.textAlign = style.getJustification(); for (var i = 0, l = lines.length; i < l; i++) { ctx.shadowColor = shadowColor; var line = lines[i]; if (hasFill) { ctx.fillText(line, 0, 0); ctx.shadowColor = 'rgba(0,0,0,0)'; } if (hasStroke) ctx.strokeText(line, 0, 0); ctx.translate(0, leading); } }, _getBounds: function(matrix, options) { var style = this._style, lines = this._lines, numLines = lines.length, justification = style.getJustification(), leading = style.getLeading(), width = this.getView().getTextWidth(style.getFontStyle(), lines), x = 0; if (justification !== 'left') x -= width / (justification === 'center' ? 2: 1); var rect = new Rectangle(x, numLines ? - 0.75 * leading : 0, width, numLines * leading); return matrix ? matrix._transformBounds(rect, rect) : rect; } }); var Color = Base.extend(new function() { var types = { gray: ['gray'], rgb: ['red', 'green', 'blue'], hsb: ['hue', 'saturation', 'brightness'], hsl: ['hue', 'saturation', 'lightness'], gradient: ['gradient', 'origin', 'destination', 'highlight'] }; var componentParsers = {}, colorCache = {}, colorCtx; function fromCSS(string) { var match = string.match(/^#(\w{1,2})(\w{1,2})(\w{1,2})$/), components; if (match) { components = [0, 0, 0]; for (var i = 0; i < 3; i++) { var value = match[i + 1]; components[i] = parseInt(value.length == 1 ? value + value : value, 16) / 255; } } else if (match = string.match(/^rgba?\((.*)\)$/)) { components = match[1].split(','); for (var i = 0, l = components.length; i < l; i++) { var value = +components[i]; components[i] = i < 3 ? value / 255 : value; } } else if (window) { var cached = colorCache[string]; if (!cached) { if (!colorCtx) { colorCtx = CanvasProvider.getContext(1, 1); colorCtx.globalCompositeOperation = 'copy'; } colorCtx.fillStyle = 'rgba(0,0,0,0)'; colorCtx.fillStyle = string; colorCtx.fillRect(0, 0, 1, 1); var data = colorCtx.getImageData(0, 0, 1, 1).data; cached = colorCache[string] = [ data[0] / 255, data[1] / 255, data[2] / 255 ]; } components = cached.slice(); } else { components = [0, 0, 0]; } return components; } var hsbIndices = [ [0, 3, 1], [2, 0, 1], [1, 0, 3], [1, 2, 0], [3, 1, 0], [0, 1, 2] ]; var converters = { 'rgb-hsb': function(r, g, b) { var max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min, h = delta === 0 ? 0 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 : (r - g) / delta + 4) * 60; return [h, max === 0 ? 0 : delta / max, max]; }, 'hsb-rgb': function(h, s, b) { h = (((h / 60) % 6) + 6) % 6; var i = Math.floor(h), f = h - i, i = hsbIndices[i], v = [ b, b * (1 - s), b * (1 - s * f), b * (1 - s * (1 - f)) ]; return [v[i[0]], v[i[1]], v[i[2]]]; }, 'rgb-hsl': function(r, g, b) { var max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min, achromatic = delta === 0, h = achromatic ? 0 : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) : max == g ? (b - r) / delta + 2 : (r - g) / delta + 4) * 60, l = (max + min) / 2, s = achromatic ? 0 : l < 0.5 ? delta / (max + min) : delta / (2 - max - min); return [h, s, l]; }, 'hsl-rgb': function(h, s, l) { h = (((h / 360) % 1) + 1) % 1; if (s === 0) return [l, l, l]; var t3s = [ h + 1 / 3, h, h - 1 / 3 ], t2 = l < 0.5 ? l * (1 + s) : l + s - l * s, t1 = 2 * l - t2, c = []; for (var i = 0; i < 3; i++) { var t3 = t3s[i]; if (t3 < 0) t3 += 1; if (t3 > 1) t3 -= 1; c[i] = 6 * t3 < 1 ? t1 + (t2 - t1) * 6 * t3 : 2 * t3 < 1 ? t2 : 3 * t3 < 2 ? t1 + (t2 - t1) * ((2 / 3) - t3) * 6 : t1; } return c; }, 'rgb-gray': function(r, g, b) { return [r * 0.2989 + g * 0.587 + b * 0.114]; }, 'gray-rgb': function(g) { return [g, g, g]; }, 'gray-hsb': function(g) { return [0, 0, g]; }, 'gray-hsl': function(g) { return [0, 0, g]; }, 'gradient-rgb': function() { return []; }, 'rgb-gradient': function() { return []; } }; return Base.each(types, function(properties, type) { componentParsers[type] = []; Base.each(properties, function(name, index) { var part = Base.capitalize(name), hasOverlap = /^(hue|saturation)$/.test(name), parser = componentParsers[type][index] = name === 'gradient' ? function(value) { var current = this._components[0]; value = Gradient.read(Array.isArray(value) ? value : arguments, 0, { readNull: true }); if (current !== value) { if (current) current._removeOwner(this); if (value) value._addOwner(this); } return value; } : type === 'gradient' ? function() { return Point.read(arguments, 0, { readNull: name === 'highlight', clone: true }); } : function(value) { return value == null || isNaN(value) ? 0 : value; }; this['get' + part] = function() { return this._type === type || hasOverlap && /^hs[bl]$/.test(this._type) ? this._components[index] : this._convert(type)[index]; }; this['set' + part] = function(value) { if (this._type !== type && !(hasOverlap && /^hs[bl]$/.test(this._type))) { this._components = this._convert(type); this._properties = types[type]; this._type = type; } this._components[index] = parser.call(this, value); this._changed(); }; }, this); }, { _class: 'Color', _readIndex: true, initialize: function Color(arg) { var args = arguments, reading = this.__read, read = 0, type, components, alpha, values; if (Array.isArray(arg)) { args = arg; arg = args[0]; } var argType = arg != null && typeof arg; if (argType === 'string' && arg in types) { type = arg; arg = args[1]; if (Array.isArray(arg)) { components = arg; alpha = args[2]; } else { if (reading) read = 1; args = Base.slice(args, 1); argType = typeof arg; } } if (!components) { values = argType === 'number' ? args : argType === 'object' && arg.length != null ? arg : null; if (values) { if (!type) type = values.length >= 3 ? 'rgb' : 'gray'; var length = types[type].length; alpha = values[length]; if (reading) { read += values === arguments ? length + (alpha != null ? 1 : 0) : 1; } if (values.length > length) values = Base.slice(values, 0, length); } else if (argType === 'string') { type = 'rgb'; components = fromCSS(arg); if (components.length === 4) { alpha = components[3]; components.length--; } } else if (argType === 'object') { if (arg.constructor === Color) { type = arg._type; components = arg._components.slice(); alpha = arg._alpha; if (type === 'gradient') { for (var i = 1, l = components.length; i < l; i++) { var point = components[i]; if (point) components[i] = point.clone(); } } } else if (arg.constructor === Gradient) { type = 'gradient'; values = args; } else { type = 'hue' in arg ? 'lightness' in arg ? 'hsl' : 'hsb' : 'gradient' in arg || 'stops' in arg || 'radial' in arg ? 'gradient' : 'gray' in arg ? 'gray' : 'rgb'; var properties = types[type], parsers = componentParsers[type]; this._components = components = []; for (var i = 0, l = properties.length; i < l; i++) { var value = arg[properties[i]]; if (value == null && !i && type === 'gradient' && 'stops' in arg) { value = { stops: arg.stops, radial: arg.radial }; } value = parsers[i].call(this, value); if (value != null) components[i] = value; } alpha = arg.alpha; } } if (reading && type) read = 1; } this._type = type || 'rgb'; if (!components) { this._components = components = []; var parsers = componentParsers[this._type]; for (var i = 0, l = parsers.length; i < l; i++) { var value = parsers[i].call(this, values && values[i]); if (value != null) components[i] = value; } } this._components = components; this._properties = types[this._type]; this._alpha = alpha; if (reading) this.__read = read; return this; }, set: '#initialize', _serialize: function(options, dictionary) { var components = this.getComponents(); return Base.serialize( /^(gray|rgb)$/.test(this._type) ? components : [this._type].concat(components), options, true, dictionary); }, _changed: function() { this._canvasStyle = null; if (this._owner) this._owner._changed(65); }, _convert: function(type) { var converter; return this._type === type ? this._components.slice() : (converter = converters[this._type + '-' + type]) ? converter.apply(this, this._components) : converters['rgb-' + type].apply(this, converters[this._type + '-rgb'].apply(this, this._components)); }, convert: function(type) { return new Color(type, this._convert(type), this._alpha); }, getType: function() { return this._type; }, setType: function(type) { this._components = this._convert(type); this._properties = types[type]; this._type = type; }, getComponents: function() { var components = this._components.slice(); if (this._alpha != null) components.push(this._alpha); return components; }, getAlpha: function() { return this._alpha != null ? this._alpha : 1; }, setAlpha: function(alpha) { this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1); this._changed(); }, hasAlpha: function() { return this._alpha != null; }, equals: function(color) { var col = Base.isPlainValue(color, true) ? Color.read(arguments) : color; return col === this || col && this._class === col._class && this._type === col._type && this.getAlpha() === col.getAlpha() && Base.equals(this._components, col._components) || false; }, toString: function() { var properties = this._properties, parts = [], isGradient = this._type === 'gradient', f = Formatter.instance; for (var i = 0, l = properties.length; i < l; i++) { var value = this._components[i]; if (value != null) parts.push(properties[i] + ': ' + (isGradient ? value : f.number(value))); } if (this._alpha != null) parts.push('alpha: ' + f.number(this._alpha)); return '{ ' + parts.join(', ') + ' }'; }, toCSS: function(hex) { var components = this._convert('rgb'), alpha = hex || this._alpha == null ? 1 : this._alpha; function convert(val) { return Math.round((val < 0 ? 0 : val > 1 ? 1 : val) * 255); } components = [ convert(components[0]), convert(components[1]), convert(components[2]) ]; if (alpha < 1) components.push(alpha < 0 ? 0 : alpha); return hex ? '#' + ((1 << 24) + (components[0] << 16) + (components[1] << 8) + components[2]).toString(16).slice(1) : (components.length == 4 ? 'rgba(' : 'rgb(') + components.join(',') + ')'; }, toCanvasStyle: function(ctx, matrix) { if (this._canvasStyle) return this._canvasStyle; if (this._type !== 'gradient') return this._canvasStyle = this.toCSS(); var components = this._components, gradient = components[0], stops = gradient._stops, origin = components[1], destination = components[2], highlight = components[3], inverse = matrix && matrix.inverted(), canvasGradient; if (inverse) { origin = inverse._transformPoint(origin); destination = inverse._transformPoint(destination); if (highlight) highlight = inverse._transformPoint(highlight); } if (gradient._radial) { var radius = destination.getDistance(origin); if (highlight) { var vector = highlight.subtract(origin); if (vector.getLength() > radius) highlight = origin.add(vector.normalize(radius - 0.1)); } var start = highlight || origin; canvasGradient = ctx.createRadialGradient(start.x, start.y, 0, origin.x, origin.y, radius); } else { canvasGradient = ctx.createLinearGradient(origin.x, origin.y, destination.x, destination.y); } for (var i = 0, l = stops.length; i < l; i++) { var stop = stops[i], offset = stop._offset; canvasGradient.addColorStop( offset == null ? i / (l - 1) : offset, stop._color.toCanvasStyle()); } return this._canvasStyle = canvasGradient; }, transform: function(matrix) { if (this._type === 'gradient') { var components = this._components; for (var i = 1, l = components.length; i < l; i++) { var point = components[i]; matrix._transformPoint(point, point, true); } this._changed(); } }, statics: { _types: types, random: function() { var random = Math.random; return new Color(random(), random(), random()); } } }); }, new function() { var operators = { add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; }, multiply: function(a, b) { return a * b; }, divide: function(a, b) { return a / b; } }; return Base.each(operators, function(operator, name) { this[name] = function(color) { color = Color.read(arguments); var type = this._type, components1 = this._components, components2 = color._convert(type); for (var i = 0, l = components1.length; i < l; i++) components2[i] = operator(components1[i], components2[i]); return new Color(type, components2, this._alpha != null ? operator(this._alpha, color.getAlpha()) : null); }; }, { }); }); var Gradient = Base.extend({ _class: 'Gradient', initialize: function Gradient(stops, radial) { this._id = UID.get(); if (stops && Base.isPlainObject(stops)) { this.set(stops); stops = radial = null; } if (this._stops == null) { this.setStops(stops || ['white', 'black']); } if (this._radial == null) { this.setRadial(typeof radial === 'string' && radial === 'radial' || radial || false); } }, _serialize: function(options, dictionary) { return dictionary.add(this, function() { return Base.serialize([this._stops, this._radial], options, true, dictionary); }); }, _changed: function() { for (var i = 0, l = this._owners && this._owners.length; i < l; i++) { this._owners[i]._changed(); } }, _addOwner: function(color) { if (!this._owners) this._owners = []; this._owners.push(color); }, _removeOwner: function(color) { var index = this._owners ? this._owners.indexOf(color) : -1; if (index != -1) { this._owners.splice(index, 1); if (!this._owners.length) this._owners = undefined; } }, clone: function() { var stops = []; for (var i = 0, l = this._stops.length; i < l; i++) { stops[i] = this._stops[i].clone(); } return new Gradient(stops, this._radial); }, getStops: function() { return this._stops; }, setStops: function(stops) { if (stops.length < 2) { throw new Error( 'Gradient stop list needs to contain at least two stops.'); } var _stops = this._stops; if (_stops) { for (var i = 0, l = _stops.length; i < l; i++) _stops[i]._owner = undefined; } _stops = this._stops = GradientStop.readList(stops, 0, { clone: true }); for (var i = 0, l = _stops.length; i < l; i++) _stops[i]._owner = this; this._changed(); }, getRadial: function() { return this._radial; }, setRadial: function(radial) { this._radial = radial; this._changed(); }, equals: function(gradient) { if (gradient === this) return true; if (gradient && this._class === gradient._class) { var stops1 = this._stops, stops2 = gradient._stops, length = stops1.length; if (length === stops2.length) { for (var i = 0; i < length; i++) { if (!stops1[i].equals(stops2[i])) return false; } return true; } } return false; } }); var GradientStop = Base.extend({ _class: 'GradientStop', initialize: function GradientStop(arg0, arg1) { var color = arg0, offset = arg1; if (typeof arg0 === 'object' && arg1 === undefined) { if (Array.isArray(arg0) && typeof arg0[0] !== 'number') { color = arg0[0]; offset = arg0[1]; } else if ('color' in arg0 || 'offset' in arg0 || 'rampPoint' in arg0) { color = arg0.color; offset = arg0.offset || arg0.rampPoint || 0; } } this.setColor(color); this.setOffset(offset); }, clone: function() { return new GradientStop(this._color.clone(), this._offset); }, _serialize: function(options, dictionary) { var color = this._color, offset = this._offset; return Base.serialize(offset == null ? [color] : [color, offset], options, true, dictionary); }, _changed: function() { if (this._owner) this._owner._changed(65); }, getOffset: function() { return this._offset; }, setOffset: function(offset) { this._offset = offset; this._changed(); }, getRampPoint: '#getOffset', setRampPoint: '#setOffset', getColor: function() { return this._color; }, setColor: function() { var color = Color.read(arguments, 0, { clone: true }); if (color) color._owner = this; this._color = color; this._changed(); }, equals: function(stop) { return stop === this || stop && this._class === stop._class && this._color.equals(stop._color) && this._offset == stop._offset || false; } }); var Style = Base.extend(new function() { var itemDefaults = { fillColor: null, fillRule: 'nonzero', strokeColor: null, strokeWidth: 1, strokeCap: 'butt', strokeJoin: 'miter', strokeScaling: true, miterLimit: 10, dashOffset: 0, dashArray: [], shadowColor: null, shadowBlur: 0, shadowOffset: new Point(), selectedColor: null }, groupDefaults = Base.set({}, itemDefaults, { fontFamily: 'sans-serif', fontWeight: 'normal', fontSize: 12, leading: null, justification: 'left' }), textDefaults = Base.set({}, groupDefaults, { fillColor: new Color() }), flags = { strokeWidth: 97, strokeCap: 97, strokeJoin: 97, strokeScaling: 105, miterLimit: 97, fontFamily: 9, fontWeight: 9, fontSize: 9, font: 9, leading: 9, justification: 9 }, item = { beans: true }, fields = { _class: 'Style', beans: true, initialize: function Style(style, _owner, _project) { this._values = {}; this._owner = _owner; this._project = _owner && _owner._project || _project || paper.project; this._defaults = !_owner || _owner instanceof Group ? groupDefaults : _owner instanceof TextItem ? textDefaults : itemDefaults; if (style) this.set(style); } }; Base.each(groupDefaults, function(value, key) { var isColor = /Color$/.test(key), isPoint = key === 'shadowOffset', part = Base.capitalize(key), flag = flags[key], set = 'set' + part, get = 'get' + part; fields[set] = function(value) { var owner = this._owner, children = owner && owner._children; if (children && children.length > 0 && !(owner instanceof CompoundPath)) { for (var i = 0, l = children.length; i < l; i++) children[i]._style[set](value); } else if (key in this._defaults) { var old = this._values[key]; if (old !== value) { if (isColor) { if (old && old._owner !== undefined) old._owner = undefined; if (value && value.constructor === Color) { if (value._owner) value = value.clone(); value._owner = owner; } } this._values[key] = value; if (owner) owner._changed(flag || 65); } } }; fields[get] = function(_dontMerge) { var owner = this._owner, children = owner && owner._children, value; if (key in this._defaults && (!children || !children.length || _dontMerge || owner instanceof CompoundPath)) { var value = this._values[key]; if (value === undefined) { value = this._defaults[key]; if (value && value.clone) value = value.clone(); } else { var ctor = isColor ? Color : isPoint ? Point : null; if (ctor && !(value && value.constructor === ctor)) { this._values[key] = value = ctor.read([value], 0, { readNull: true, clone: true }); if (value && isColor) value._owner = owner; } } } else if (children) { for (var i = 0, l = children.length; i < l; i++) { var childValue = children[i]._style[get](); if (!i) { value = childValue; } else if (!Base.equals(value, childValue)) { return undefined; } } } return value; }; item[get] = function(_dontMerge) { return this._style[get](_dontMerge); }; item[set] = function(value) { this._style[set](value); }; }); Base.each({ Font: 'FontFamily', WindingRule: 'FillRule' }, function(value, key) { var get = 'get' + key, set = 'set' + key; fields[get] = item[get] = '#get' + value; fields[set] = item[set] = '#set' + value; }); Item.inject(item); return fields; }, { set: function(style) { var isStyle = style instanceof Style, values = isStyle ? style._values : style; if (values) { for (var key in values) { if (key in this._defaults) { var value = values[key]; this[key] = value && isStyle && value.clone ? value.clone() : value; } } } }, equals: function(style) { function compare(style1, style2, secondary) { var values1 = style1._values, values2 = style2._values, defaults2 = style2._defaults; for (var key in values1) { var value1 = values1[key], value2 = values2[key]; if (!(secondary && key in values2) && !Base.equals(value1, value2 === undefined ? defaults2[key] : value2)) return false; } return true; } return style === this || style && this._class === style._class && compare(this, style) && compare(style, this, true) || false; }, hasFill: function() { var color = this.getFillColor(); return !!color && color.alpha > 0; }, hasStroke: function() { var color = this.getStrokeColor(); return !!color && color.alpha > 0 && this.getStrokeWidth() > 0; }, hasShadow: function() { var color = this.getShadowColor(); return !!color && color.alpha > 0 && (this.getShadowBlur() > 0 || !this.getShadowOffset().isZero()); }, getView: function() { return this._project._view; }, getFontStyle: function() { var fontSize = this.getFontSize(); return this.getFontWeight() + ' ' + fontSize + (/[a-z]/i.test(fontSize + '') ? ' ' : 'px ') + this.getFontFamily(); }, getFont: '#getFontFamily', setFont: '#setFontFamily', getLeading: function getLeading() { var leading = getLeading.base.call(this), fontSize = this.getFontSize(); if (/pt|em|%|px/.test(fontSize)) fontSize = this.getView().getPixelSize(fontSize); return leading != null ? leading : fontSize * 1.2; } }); var DomElement = new function() { function handlePrefix(el, name, set, value) { var prefixes = ['', 'webkit', 'moz', 'Moz', 'ms', 'o'], suffix = name[0].toUpperCase() + name.substring(1); for (var i = 0; i < 6; i++) { var prefix = prefixes[i], key = prefix ? prefix + suffix : name; if (key in el) { if (set) { el[key] = value; } else { return el[key]; } break; } } } return { getStyles: function(el) { var doc = el && el.nodeType !== 9 ? el.ownerDocument : el, view = doc && doc.defaultView; return view && view.getComputedStyle(el, ''); }, getBounds: function(el, viewport) { var doc = el.ownerDocument, body = doc.body, html = doc.documentElement, rect; try { rect = el.getBoundingClientRect(); } catch (e) { rect = { left: 0, top: 0, width: 0, height: 0 }; } var x = rect.left - (html.clientLeft || body.clientLeft || 0), y = rect.top - (html.clientTop || body.clientTop || 0); if (!viewport) { var view = doc.defaultView; x += view.pageXOffset || html.scrollLeft || body.scrollLeft; y += view.pageYOffset || html.scrollTop || body.scrollTop; } return new Rectangle(x, y, rect.width, rect.height); }, getViewportBounds: function(el) { var doc = el.ownerDocument, view = doc.defaultView, html = doc.documentElement; return new Rectangle(0, 0, view.innerWidth || html.clientWidth, view.innerHeight || html.clientHeight ); }, getOffset: function(el, viewport) { return DomElement.getBounds(el, viewport).getPoint(); }, getSize: function(el) { return DomElement.getBounds(el, true).getSize(); }, isInvisible: function(el) { return DomElement.getSize(el).equals(new Size(0, 0)); }, isInView: function(el) { return !DomElement.isInvisible(el) && DomElement.getViewportBounds(el).intersects( DomElement.getBounds(el, true)); }, isInserted: function(el) { return document.body.contains(el); }, getPrefixed: function(el, name) { return el && handlePrefix(el, name); }, setPrefixed: function(el, name, value) { if (typeof name === 'object') { for (var key in name) handlePrefix(el, key, true, name[key]); } else { handlePrefix(el, name, true, value); } } }; }; var DomEvent = { add: function(el, events) { if (el) { for (var type in events) { var func = events[type], parts = type.split(/[\s,]+/g); for (var i = 0, l = parts.length; i < l; i++) el.addEventListener(parts[i], func, false); } } }, remove: function(el, events) { if (el) { for (var type in events) { var func = events[type], parts = type.split(/[\s,]+/g); for (var i = 0, l = parts.length; i < l; i++) el.removeEventListener(parts[i], func, false); } } }, getPoint: function(event) { var pos = event.targetTouches ? event.targetTouches.length ? event.targetTouches[0] : event.changedTouches[0] : event; return new Point( pos.pageX || pos.clientX + document.documentElement.scrollLeft, pos.pageY || pos.clientY + document.documentElement.scrollTop ); }, getTarget: function(event) { return event.target || event.srcElement; }, getRelatedTarget: function(event) { return event.relatedTarget || event.toElement; }, getOffset: function(event, target) { return DomEvent.getPoint(event).subtract(DomElement.getOffset( target || DomEvent.getTarget(event))); } }; DomEvent.requestAnimationFrame = new function() { var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), requested = false, callbacks = [], timer; function handleCallbacks() { var functions = callbacks; callbacks = []; for (var i = 0, l = functions.length; i < l; i++) functions[i](); requested = nativeRequest && callbacks.length; if (requested) nativeRequest(handleCallbacks); } return function(callback) { callbacks.push(callback); if (nativeRequest) { if (!requested) { nativeRequest(handleCallbacks); requested = true; } } else if (!timer) { timer = setInterval(handleCallbacks, 1000 / 60); } }; }; var View = Base.extend(Emitter, { _class: 'View', initialize: function View(project, element) { function getSize(name) { return element[name] || parseInt(element.getAttribute(name), 10); } function getCanvasSize() { var size = DomElement.getSize(element); return size.isNaN() || size.isZero() ? new Size(getSize('width'), getSize('height')) : size; } var size; if (window && element) { this._id = element.getAttribute('id'); if (this._id == null) element.setAttribute('id', this._id = 'view-' + View._id++); DomEvent.add(element, this._viewEvents); var none = 'none'; DomElement.setPrefixed(element.style, { userDrag: none, userSelect: none, touchCallout: none, contentZooming: none, tapHighlightColor: 'rgba(0,0,0,0)' }); if (PaperScope.hasAttribute(element, 'resize')) { var that = this; DomEvent.add(window, this._windowEvents = { resize: function() { that.setViewSize(getCanvasSize()); } }); } size = getCanvasSize(); if (PaperScope.hasAttribute(element, 'stats') && typeof Stats !== 'undefined') { this._stats = new Stats(); var stats = this._stats.domElement, style = stats.style, offset = DomElement.getOffset(element); style.position = 'absolute'; style.left = offset.x + 'px'; style.top = offset.y + 'px'; document.body.appendChild(stats); } } else { size = new Size(element); element = null; } this._project = project; this._scope = project._scope; this._element = element; if (!this._pixelRatio) this._pixelRatio = window && window.devicePixelRatio || 1; this._setElementSize(size.width, size.height); this._viewSize = size; View._views.push(this); View._viewsById[this._id] = this; (this._matrix = new Matrix())._owner = this; if (!View._focused) View._focused = this; this._frameItems = {}; this._frameItemCount = 0; this._itemEvents = { native: {}, virtual: {} }; this._autoUpdate = !paper.agent.node; this._needsUpdate = false; }, remove: function() { if (!this._project) return false; if (View._focused === this) View._focused = null; View._views.splice(View._views.indexOf(this), 1); delete View._viewsById[this._id]; var project = this._project; if (project._view === this) project._view = null; DomEvent.remove(this._element, this._viewEvents); DomEvent.remove(window, this._windowEvents); this._element = this._project = null; this.off('frame'); this._animate = false; this._frameItems = {}; return true; }, _events: Base.each( Item._itemHandlers.concat(['onResize', 'onKeyDown', 'onKeyUp']), function(name) { this[name] = {}; }, { onFrame: { install: function() { this.play(); }, uninstall: function() { this.pause(); } } } ), _animate: false, _time: 0, _count: 0, getAutoUpdate: function() { return this._autoUpdate; }, setAutoUpdate: function(autoUpdate) { this._autoUpdate = autoUpdate; if (autoUpdate) this.requestUpdate(); }, update: function() { }, draw: function() { this.update(); }, requestUpdate: function() { if (!this._requested) { var that = this; DomEvent.requestAnimationFrame(function() { that._requested = false; if (that._animate) { that.requestUpdate(); var element = that._element; if ((!DomElement.getPrefixed(document, 'hidden') || PaperScope.getAttribute(element, 'keepalive') === 'true') && DomElement.isInView(element)) { that._handleFrame(); } } if (that._autoUpdate) that.update(); }); this._requested = true; } }, play: function() { this._animate = true; this.requestUpdate(); }, pause: function() { this._animate = false; }, _handleFrame: function() { paper = this._scope; var now = Date.now() / 1000, delta = this._last ? now - this._last : 0; this._last = now; this.emit('frame', new Base({ delta: delta, time: this._time += delta, count: this._count++ })); if (this._stats) this._stats.update(); }, _animateItem: function(item, animate) { var items = this._frameItems; if (animate) { items[item._id] = { item: item, time: 0, count: 0 }; if (++this._frameItemCount === 1) this.on('frame', this._handleFrameItems); } else { delete items[item._id]; if (--this._frameItemCount === 0) { this.off('frame', this._handleFrameItems); } } }, _handleFrameItems: function(event) { for (var i in this._frameItems) { var entry = this._frameItems[i]; entry.item.emit('frame', new Base(event, { time: entry.time += event.delta, count: entry.count++ })); } }, _changed: function() { this._project._changed(2049); this._bounds = this._decomposed = undefined; }, getElement: function() { return this._element; }, getPixelRatio: function() { return this._pixelRatio; }, getResolution: function() { return this._pixelRatio * 72; }, getViewSize: function() { var size = this._viewSize; return new LinkedSize(size.width, size.height, this, 'setViewSize'); }, setViewSize: function() { var size = Size.read(arguments), delta = size.subtract(this._viewSize); if (delta.isZero()) return; this._setElementSize(size.width, size.height); this._viewSize.set(size); this._changed(); this.emit('resize', { size: size, delta: delta }); if (this._autoUpdate) { this.update(); } }, _setElementSize: function(width, height) { var element = this._element; if (element) { if (element.width !== width) element.width = width; if (element.height !== height) element.height = height; } }, getBounds: function() { if (!this._bounds) this._bounds = this._matrix.inverted()._transformBounds( new Rectangle(new Point(), this._viewSize)); return this._bounds; }, getSize: function() { return this.getBounds().getSize(); }, isVisible: function() { return DomElement.isInView(this._element); }, isInserted: function() { return DomElement.isInserted(this._element); }, getPixelSize: function(size) { var element = this._element, pixels; if (element) { var parent = element.parentNode, temp = document.createElement('div'); temp.style.fontSize = size; parent.appendChild(temp); pixels = parseFloat(DomElement.getStyles(temp).fontSize); parent.removeChild(temp); } else { pixels = parseFloat(pixels); } return pixels; }, getTextWidth: function(font, lines) { return 0; } }, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { var rotate = key === 'rotate'; this[key] = function() { var value = (rotate ? Base : Point).read(arguments), center = Point.read(arguments, 0, { readNull: true }); return this.transform(new Matrix()[key](value, center || this.getCenter(true))); }; }, { _decompose: function() { return this._decomposed || (this._decomposed = this._matrix.decompose()); }, translate: function() { var mx = new Matrix(); return this.transform(mx.translate.apply(mx, arguments)); }, getCenter: function() { return this.getBounds().getCenter(); }, setCenter: function() { var center = Point.read(arguments); this.translate(this.getCenter().subtract(center)); }, getZoom: function() { var decomposed = this._decompose(), scaling = decomposed && decomposed.scaling; return scaling ? (scaling.x + scaling.y) / 2 : 0; }, setZoom: function(zoom) { this.transform(new Matrix().scale(zoom / this.getZoom(), this.getCenter())); }, getRotation: function() { var decomposed = this._decompose(); return decomposed && decomposed.rotation; }, setRotation: function(rotation) { var current = this.getRotation(); if (current != null && rotation != null) { this.rotate(rotation - current); } }, getScaling: function() { var decomposed = this._decompose(), scaling = decomposed && decomposed.scaling; return scaling ? new LinkedPoint(scaling.x, scaling.y, this, 'setScaling') : undefined; }, setScaling: function() { var current = this.getScaling(), scaling = Point.read(arguments, 0, { clone: true, readNull: true }); if (current && scaling) { this.scale(scaling.x / current.x, scaling.y / current.y); } }, getMatrix: function() { return this._matrix; }, setMatrix: function() { var matrix = this._matrix; matrix.initialize.apply(matrix, arguments); }, transform: function(matrix) { this._matrix.append(matrix); }, scrollBy: function() { this.translate(Point.read(arguments).negate()); } }), { projectToView: function() { return this._matrix._transformPoint(Point.read(arguments)); }, viewToProject: function() { return this._matrix._inverseTransform(Point.read(arguments)); }, getEventPoint: function(event) { return this.viewToProject(DomEvent.getOffset(event, this._element)); }, }, { statics: { _views: [], _viewsById: {}, _id: 0, create: function(project, element) { if (document && typeof element === 'string') element = document.getElementById(element); var ctor = window ? CanvasView : View; return new ctor(project, element); } } }, new function() { if (!window) return; var prevFocus, tempFocus, dragging = false, mouseDown = false; function getView(event) { var target = DomEvent.getTarget(event); return target.getAttribute && View._viewsById[ target.getAttribute('id')]; } function updateFocus() { var view = View._focused; if (!view || !view.isVisible()) { for (var i = 0, l = View._views.length; i < l; i++) { if ((view = View._views[i]).isVisible()) { View._focused = tempFocus = view; break; } } } } function handleMouseMove(view, event, point) { view._handleMouseEvent('mousemove', event, point); } var navigator = window.navigator, mousedown, mousemove, mouseup; if (navigator.pointerEnabled || navigator.msPointerEnabled) { mousedown = 'pointerdown MSPointerDown'; mousemove = 'pointermove MSPointerMove'; mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel'; } else { mousedown = 'touchstart'; mousemove = 'touchmove'; mouseup = 'touchend touchcancel'; if (!('ontouchstart' in window && navigator.userAgent.match( /mobile|tablet|ip(ad|hone|od)|android|silk/i))) { mousedown += ' mousedown'; mousemove += ' mousemove'; mouseup += ' mouseup'; } } var viewEvents = {}, docEvents = { mouseout: function(event) { var view = View._focused, target = DomEvent.getRelatedTarget(event); if (view && (!target || target.nodeName === 'HTML')) { var offset = DomEvent.getOffset(event, view._element), x = offset.x, abs = Math.abs, ax = abs(x), max = 1 << 25, diff = ax - max; offset.x = abs(diff) < ax ? diff * (x < 0 ? -1 : 1) : x; handleMouseMove(view, event, view.viewToProject(offset)); } }, scroll: updateFocus }; viewEvents[mousedown] = function(event) { var view = View._focused = getView(event); if (!dragging) { dragging = true; view._handleMouseEvent('mousedown', event); } }; docEvents[mousemove] = function(event) { var view = View._focused; if (!mouseDown) { var target = getView(event); if (target) { if (view !== target) { if (view) handleMouseMove(view, event); if (!prevFocus) prevFocus = view; view = View._focused = tempFocus = target; } } else if (tempFocus && tempFocus === view) { if (prevFocus && !prevFocus.isInserted()) prevFocus = null; view = View._focused = prevFocus; prevFocus = null; updateFocus(); } } if (view) handleMouseMove(view, event); }; docEvents[mousedown] = function() { mouseDown = true; }; docEvents[mouseup] = function(event) { var view = View._focused; if (view && dragging) view._handleMouseEvent('mouseup', event); mouseDown = dragging = false; }; DomEvent.add(document, docEvents); DomEvent.add(window, { load: updateFocus }); var called = false, prevented = false, fallbacks = { doubleclick: 'click', mousedrag: 'mousemove' }, wasInView = false, overView, downPoint, lastPoint, downItem, overItem, dragItem, clickItem, clickTime, dblClick; function emitMouseEvent(obj, target, type, event, point, prevPoint, stopItem) { var stopped = false, mouseEvent; function emit(obj, type) { if (obj.responds(type)) { if (!mouseEvent) { mouseEvent = new MouseEvent(type, event, point, target || obj, prevPoint ? point.subtract(prevPoint) : null); } if (obj.emit(type, mouseEvent)) { called = true; if (mouseEvent.prevented) prevented = true; if (mouseEvent.stopped) return stopped = true; } } else { var fallback = fallbacks[type]; if (fallback) return emit(obj, fallback); } } while (obj && obj !== stopItem) { if (emit(obj, type)) break; obj = obj._parent; } return stopped; } function emitMouseEvents(view, hitItem, type, event, point, prevPoint) { view._project.removeOn(type); prevented = called = false; return (dragItem && emitMouseEvent(dragItem, null, type, event, point, prevPoint) || hitItem && hitItem !== dragItem && !hitItem.isDescendant(dragItem) && emitMouseEvent(hitItem, null, type, event, point, prevPoint, dragItem) || emitMouseEvent(view, dragItem || hitItem || view, type, event, point, prevPoint)); } var itemEventsMap = { mousedown: { mousedown: 1, mousedrag: 1, click: 1, doubleclick: 1 }, mouseup: { mouseup: 1, mousedrag: 1, click: 1, doubleclick: 1 }, mousemove: { mousedrag: 1, mousemove: 1, mouseenter: 1, mouseleave: 1 } }; return { _viewEvents: viewEvents, _handleMouseEvent: function(type, event, point) { var itemEvents = this._itemEvents, hitItems = itemEvents.native[type], nativeMove = type === 'mousemove', tool = this._scope.tool, view = this; function responds(type) { return itemEvents.virtual[type] || view.responds(type) || tool && tool.responds(type); } if (nativeMove && dragging && responds('mousedrag')) type = 'mousedrag'; if (!point) point = this.getEventPoint(event); var inView = this.getBounds().contains(point), hit = hitItems && inView && view._project.hitTest(point, { tolerance: 0, fill: true, stroke: true }), hitItem = hit && hit.item || null, handle = false, mouse = {}; mouse[type.substr(5)] = true; if (hitItems && hitItem !== overItem) { if (overItem) { emitMouseEvent(overItem, null, 'mouseleave', event, point); } if (hitItem) { emitMouseEvent(hitItem, null, 'mouseenter', event, point); } overItem = hitItem; } if (wasInView ^ inView) { emitMouseEvent(this, null, inView ? 'mouseenter' : 'mouseleave', event, point); overView = inView ? this : null; handle = true; } if ((inView || mouse.drag) && !point.equals(lastPoint)) { emitMouseEvents(this, hitItem, nativeMove ? type : 'mousemove', event, point, lastPoint); handle = true; } wasInView = inView; if (mouse.down && inView || mouse.up && downPoint) { emitMouseEvents(this, hitItem, type, event, point, downPoint); if (mouse.down) { dblClick = hitItem === clickItem && (Date.now() - clickTime < 300); downItem = clickItem = hitItem; if (!prevented && hitItem) { var item = hitItem; while (item && !item.responds('mousedrag')) item = item._parent; if (item) dragItem = hitItem; } downPoint = point; } else if (mouse.up) { if (!prevented && hitItem === downItem) { clickTime = Date.now(); emitMouseEvents(this, hitItem, dblClick ? 'doubleclick' : 'click', event, point, downPoint); dblClick = false; } downItem = dragItem = null; } wasInView = false; handle = true; } lastPoint = point; if (handle && tool) { called = tool._handleMouseEvent(type, event, point, mouse) || called; } if (called && !mouse.move || mouse.down && responds('mouseup')) event.preventDefault(); }, _handleKeyEvent: function(type, event, key, character) { var scope = this._scope, tool = scope.tool, keyEvent; function emit(obj) { if (obj.responds(type)) { paper = scope; obj.emit(type, keyEvent = keyEvent || new KeyEvent(type, event, key, character)); } } if (this.isVisible()) { emit(this); if (tool && tool.responds(type)) emit(tool); } }, _countItemEvent: function(type, sign) { var itemEvents = this._itemEvents, native = itemEvents.native, virtual = itemEvents.virtual; for (var key in itemEventsMap) { native[key] = (native[key] || 0) + (itemEventsMap[key][type] || 0) * sign; } virtual[type] = (virtual[type] || 0) + sign; }, statics: { updateFocus: updateFocus } }; }); var CanvasView = View.extend({ _class: 'CanvasView', initialize: function CanvasView(project, canvas) { if (!(canvas instanceof window.HTMLCanvasElement)) { var size = Size.read(arguments, 1); if (size.isZero()) throw new Error( 'Cannot create CanvasView with the provided argument: ' + Base.slice(arguments, 1)); canvas = CanvasProvider.getCanvas(size); } var ctx = this._context = canvas.getContext('2d'); ctx.save(); this._pixelRatio = 1; if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) { var deviceRatio = window.devicePixelRatio || 1, backingStoreRatio = DomElement.getPrefixed(ctx, 'backingStorePixelRatio') || 1; this._pixelRatio = deviceRatio / backingStoreRatio; } View.call(this, project, canvas); this._needsUpdate = true; }, remove: function remove() { this._context.restore(); return remove.base.call(this); }, _setElementSize: function _setElementSize(width, height) { var pixelRatio = this._pixelRatio; _setElementSize.base.call(this, width * pixelRatio, height * pixelRatio); if (pixelRatio !== 1) { var element = this._element, ctx = this._context; if (!PaperScope.hasAttribute(element, 'resize')) { var style = element.style; style.width = width + 'px'; style.height = height + 'px'; } ctx.restore(); ctx.save(); ctx.scale(pixelRatio, pixelRatio); } }, getPixelSize: function getPixelSize(size) { var agent = paper.agent, pixels; if (agent && agent.firefox) { pixels = getPixelSize.base.call(this, size); } else { var ctx = this._context, prevFont = ctx.font; ctx.font = size + ' serif'; pixels = parseFloat(ctx.font); ctx.font = prevFont; } return pixels; }, getTextWidth: function(font, lines) { var ctx = this._context, prevFont = ctx.font, width = 0; ctx.font = font; for (var i = 0, l = lines.length; i < l; i++) width = Math.max(width, ctx.measureText(lines[i]).width); ctx.font = prevFont; return width; }, update: function() { if (!this._needsUpdate) return false; var project = this._project, ctx = this._context, size = this._viewSize; ctx.clearRect(0, 0, size.width + 1, size.height + 1); if (project) project.draw(ctx, this._matrix, this._pixelRatio); this._needsUpdate = false; return true; } }); var Event = Base.extend({ _class: 'Event', initialize: function Event(event) { this.event = event; this.type = event && event.type; }, prevented: false, stopped: false, preventDefault: function() { this.prevented = true; this.event.preventDefault(); }, stopPropagation: function() { this.stopped = true; this.event.stopPropagation(); }, stop: function() { this.stopPropagation(); this.preventDefault(); }, getTimeStamp: function() { return this.event.timeStamp; }, getModifiers: function() { return Key.modifiers; } }); var KeyEvent = Event.extend({ _class: 'KeyEvent', initialize: function KeyEvent(type, event, key, character) { this.type = type; this.event = event; this.key = key; this.character = character; }, toString: function() { return "{ type: '" + this.type + "', key: '" + this.key + "', character: '" + this.character + "', modifiers: " + this.getModifiers() + " }"; } }); var Key = new function() { var keyLookup = { '\t': 'tab', ' ': 'space', '\b': 'backspace', '\x7f': 'delete', 'Spacebar': 'space', 'Del': 'delete', 'Win': 'meta', 'Esc': 'escape' }, charLookup = { 'tab': '\t', 'space': ' ', 'enter': '\r' }, keyMap = {}, charMap = {}, metaFixMap, downKey, modifiers = new Base({ shift: false, control: false, alt: false, meta: false, capsLock: false, space: false }).inject({ option: { get: function() { return this.alt; } }, command: { get: function() { var agent = paper && paper.agent; return agent && agent.mac ? this.meta : this.control; } } }); function getKey(event) { var key = event.key || event.keyIdentifier; key = /^U\+/.test(key) ? String.fromCharCode(parseInt(key.substr(2), 16)) : /^Arrow[A-Z]/.test(key) ? key.substr(5) : key === 'Unidentified' || key === undefined ? String.fromCharCode(event.keyCode) : key; return keyLookup[key] || (key.length > 1 ? Base.hyphenate(key) : key.toLowerCase()); } function handleKey(down, key, character, event) { var type = down ? 'keydown' : 'keyup', view = View._focused, name; keyMap[key] = down; if (down) { charMap[key] = character; } else { delete charMap[key]; } if (key.length > 1 && (name = Base.camelize(key)) in modifiers) { modifiers[name] = down; var agent = paper && paper.agent; if (name === 'meta' && agent && agent.mac) { if (down) { metaFixMap = {}; } else { for (var k in metaFixMap) { if (k in charMap) handleKey(false, k, metaFixMap[k], event); } metaFixMap = null; } } } else if (down && metaFixMap) { metaFixMap[key] = character; } if (view) { view._handleKeyEvent(down ? 'keydown' : 'keyup', event, key, character); } } DomEvent.add(document, { keydown: function(event) { var key = getKey(event), agent = paper && paper.agent; if (key.length > 1 || agent && (agent.chrome && (event.altKey || agent.mac && event.metaKey || !agent.mac && event.ctrlKey))) { handleKey(true, key, charLookup[key] || (key.length > 1 ? '' : key), event); } else { downKey = key; } }, keypress: function(event) { if (downKey) { var key = getKey(event), code = event.charCode, character = code >= 32 ? String.fromCharCode(code) : key.length > 1 ? '' : key; if (key !== downKey) { key = character.toLowerCase(); } handleKey(true, key, character, event); downKey = null; } }, keyup: function(event) { var key = getKey(event); if (key in charMap) handleKey(false, key, charMap[key], event); } }); DomEvent.add(window, { blur: function(event) { for (var key in charMap) handleKey(false, key, charMap[key], event); } }); return { modifiers: modifiers, isDown: function(key) { return !!keyMap[key]; } }; }; var MouseEvent = Event.extend({ _class: 'MouseEvent', initialize: function MouseEvent(type, event, point, target, delta) { this.type = type; this.event = event; this.point = point; this.target = target; this.delta = delta; }, toString: function() { return "{ type: '" + this.type + "', point: " + this.point + ', target: ' + this.target + (this.delta ? ', delta: ' + this.delta : '') + ', modifiers: ' + this.getModifiers() + ' }'; } }); var ToolEvent = Event.extend({ _class: 'ToolEvent', _item: null, initialize: function ToolEvent(tool, type, event) { this.tool = tool; this.type = type; this.event = event; }, _choosePoint: function(point, toolPoint) { return point ? point : toolPoint ? toolPoint.clone() : null; }, getPoint: function() { return this._choosePoint(this._point, this.tool._point); }, setPoint: function(point) { this._point = point; }, getLastPoint: function() { return this._choosePoint(this._lastPoint, this.tool._lastPoint); }, setLastPoint: function(lastPoint) { this._lastPoint = lastPoint; }, getDownPoint: function() { return this._choosePoint(this._downPoint, this.tool._downPoint); }, setDownPoint: function(downPoint) { this._downPoint = downPoint; }, getMiddlePoint: function() { if (!this._middlePoint && this.tool._lastPoint) { return this.tool._point.add(this.tool._lastPoint).divide(2); } return this._middlePoint; }, setMiddlePoint: function(middlePoint) { this._middlePoint = middlePoint; }, getDelta: function() { return !this._delta && this.tool._lastPoint ? this.tool._point.subtract(this.tool._lastPoint) : this._delta; }, setDelta: function(delta) { this._delta = delta; }, getCount: function() { return this.tool[/^mouse(down|up)$/.test(this.type) ? '_downCount' : '_moveCount']; }, setCount: function(count) { this.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count'] = count; }, getItem: function() { if (!this._item) { var result = this.tool._scope.project.hitTest(this.getPoint()); if (result) { var item = result.item, parent = item._parent; while (/^(Group|CompoundPath)$/.test(parent._class)) { item = parent; parent = parent._parent; } this._item = item; } } return this._item; }, setItem: function(item) { this._item = item; }, toString: function() { return '{ type: ' + this.type + ', point: ' + this.getPoint() + ', count: ' + this.getCount() + ', modifiers: ' + this.getModifiers() + ' }'; } }); var Tool = PaperScopeItem.extend({ _class: 'Tool', _list: 'tools', _reference: 'tool', _events: ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', 'onActivate', 'onDeactivate', 'onEditOptions', 'onKeyDown', 'onKeyUp'], initialize: function Tool(props) { PaperScopeItem.call(this); this._moveCount = -1; this._downCount = -1; this.set(props); }, getMinDistance: function() { return this._minDistance; }, setMinDistance: function(minDistance) { this._minDistance = minDistance; if (minDistance != null && this._maxDistance != null && minDistance > this._maxDistance) { this._maxDistance = minDistance; } }, getMaxDistance: function() { return this._maxDistance; }, setMaxDistance: function(maxDistance) { this._maxDistance = maxDistance; if (this._minDistance != null && maxDistance != null && maxDistance < this._minDistance) { this._minDistance = maxDistance; } }, getFixedDistance: function() { return this._minDistance == this._maxDistance ? this._minDistance : null; }, setFixedDistance: function(distance) { this._minDistance = this._maxDistance = distance; }, _handleMouseEvent: function(type, event, point, mouse) { paper = this._scope; if (mouse.drag && !this.responds(type)) type = 'mousemove'; var move = mouse.move || mouse.drag, responds = this.responds(type), minDistance = this.minDistance, maxDistance = this.maxDistance, called = false, tool = this; function update(minDistance, maxDistance) { var pt = point, toolPoint = move ? tool._point : (tool._downPoint || pt); if (move) { if (tool._moveCount && pt.equals(toolPoint)) { return false; } if (toolPoint && (minDistance != null || maxDistance != null)) { var vector = pt.subtract(toolPoint), distance = vector.getLength(); if (distance < (minDistance || 0)) return false; if (maxDistance) { pt = toolPoint.add(vector.normalize( Math.min(distance, maxDistance))); } } tool._moveCount++; } tool._point = pt; tool._lastPoint = toolPoint || pt; if (mouse.down) { tool._moveCount = -1; tool._downPoint = pt; tool._downCount++; } return true; } function emit() { if (responds) { called = tool.emit(type, new ToolEvent(tool, type, event)) || called; } } if (mouse.down) { update(); emit(); } else if (mouse.up) { update(null, maxDistance); emit(); } else if (responds) { while (update(minDistance, maxDistance)) emit(); } return called; } }); var Http = { request: function(options) { var xhr = new self.XMLHttpRequest(); xhr.open((options.method || 'get').toUpperCase(), options.url, Base.pick(options.async, true)); if (options.mimeType) xhr.overrideMimeType(options.mimeType); xhr.onload = function() { var status = xhr.status; if (status === 0 || status === 200) { if (options.onLoad) { options.onLoad.call(xhr, xhr.responseText); } } else { xhr.onerror(); } }; xhr.onerror = function() { var status = xhr.status, message = 'Could not load "' + options.url + '" (Status: ' + status + ')'; if (options.onError) { options.onError(message, status); } else { throw new Error(message); } }; return xhr.send(null); } }; var CanvasProvider = { canvases: [], getCanvas: function(width, height) { if (!window) return null; var canvas, clear = true; if (typeof width === 'object') { height = width.height; width = width.width; } if (this.canvases.length) { canvas = this.canvases.pop(); } else { canvas = document.createElement('canvas'); clear = false; } var ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Canvas ' + canvas + ' is unable to provide a 2D context.'); } if (canvas.width === width && canvas.height === height) { if (clear) ctx.clearRect(0, 0, width + 1, height + 1); } else { canvas.width = width; canvas.height = height; } ctx.save(); return canvas; }, getContext: function(width, height) { var canvas = this.getCanvas(width, height); return canvas ? canvas.getContext('2d') : null; }, release: function(obj) { var canvas = obj && obj.canvas ? obj.canvas : obj; if (canvas && canvas.getContext) { canvas.getContext('2d').restore(); this.canvases.push(canvas); } } }; var BlendMode = new function() { var min = Math.min, max = Math.max, abs = Math.abs, sr, sg, sb, sa, br, bg, bb, ba, dr, dg, db; function getLum(r, g, b) { return 0.2989 * r + 0.587 * g + 0.114 * b; } function setLum(r, g, b, l) { var d = l - getLum(r, g, b); dr = r + d; dg = g + d; db = b + d; var l = getLum(dr, dg, db), mn = min(dr, dg, db), mx = max(dr, dg, db); if (mn < 0) { var lmn = l - mn; dr = l + (dr - l) * l / lmn; dg = l + (dg - l) * l / lmn; db = l + (db - l) * l / lmn; } if (mx > 255) { var ln = 255 - l, mxl = mx - l; dr = l + (dr - l) * ln / mxl; dg = l + (dg - l) * ln / mxl; db = l + (db - l) * ln / mxl; } } function getSat(r, g, b) { return max(r, g, b) - min(r, g, b); } function setSat(r, g, b, s) { var col = [r, g, b], mx = max(r, g, b), mn = min(r, g, b), md; mn = mn === r ? 0 : mn === g ? 1 : 2; mx = mx === r ? 0 : mx === g ? 1 : 2; md = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0; if (col[mx] > col[mn]) { col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]); col[mx] = s; } else { col[md] = col[mx] = 0; } col[mn] = 0; dr = col[0]; dg = col[1]; db = col[2]; } var modes = { multiply: function() { dr = br * sr / 255; dg = bg * sg / 255; db = bb * sb / 255; }, screen: function() { dr = br + sr - (br * sr / 255); dg = bg + sg - (bg * sg / 255); db = bb + sb - (bb * sb / 255); }, overlay: function() { dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255; dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255; db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255; }, 'soft-light': function() { var t = sr * br / 255; dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255; t = sg * bg / 255; dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255; t = sb * bb / 255; db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255; }, 'hard-light': function() { dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255; dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255; db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255; }, 'color-dodge': function() { dr = br === 0 ? 0 : sr === 255 ? 255 : min(255, 255 * br / (255 - sr)); dg = bg === 0 ? 0 : sg === 255 ? 255 : min(255, 255 * bg / (255 - sg)); db = bb === 0 ? 0 : sb === 255 ? 255 : min(255, 255 * bb / (255 - sb)); }, 'color-burn': function() { dr = br === 255 ? 255 : sr === 0 ? 0 : max(0, 255 - (255 - br) * 255 / sr); dg = bg === 255 ? 255 : sg === 0 ? 0 : max(0, 255 - (255 - bg) * 255 / sg); db = bb === 255 ? 255 : sb === 0 ? 0 : max(0, 255 - (255 - bb) * 255 / sb); }, darken: function() { dr = br < sr ? br : sr; dg = bg < sg ? bg : sg; db = bb < sb ? bb : sb; }, lighten: function() { dr = br > sr ? br : sr; dg = bg > sg ? bg : sg; db = bb > sb ? bb : sb; }, difference: function() { dr = br - sr; if (dr < 0) dr = -dr; dg = bg - sg; if (dg < 0) dg = -dg; db = bb - sb; if (db < 0) db = -db; }, exclusion: function() { dr = br + sr * (255 - br - br) / 255; dg = bg + sg * (255 - bg - bg) / 255; db = bb + sb * (255 - bb - bb) / 255; }, hue: function() { setSat(sr, sg, sb, getSat(br, bg, bb)); setLum(dr, dg, db, getLum(br, bg, bb)); }, saturation: function() { setSat(br, bg, bb, getSat(sr, sg, sb)); setLum(dr, dg, db, getLum(br, bg, bb)); }, luminosity: function() { setLum(br, bg, bb, getLum(sr, sg, sb)); }, color: function() { setLum(sr, sg, sb, getLum(br, bg, bb)); }, add: function() { dr = min(br + sr, 255); dg = min(bg + sg, 255); db = min(bb + sb, 255); }, subtract: function() { dr = max(br - sr, 0); dg = max(bg - sg, 0); db = max(bb - sb, 0); }, average: function() { dr = (br + sr) / 2; dg = (bg + sg) / 2; db = (bb + sb) / 2; }, negation: function() { dr = 255 - abs(255 - sr - br); dg = 255 - abs(255 - sg - bg); db = 255 - abs(255 - sb - bb); } }; var nativeModes = this.nativeModes = Base.each([ 'source-over', 'source-in', 'source-out', 'source-atop', 'destination-over', 'destination-in', 'destination-out', 'destination-atop', 'lighter', 'darker', 'copy', 'xor' ], function(mode) { this[mode] = true; }, {}); var ctx = CanvasProvider.getContext(1, 1); if (ctx) { Base.each(modes, function(func, mode) { var darken = mode === 'darken', ok = false; ctx.save(); try { ctx.fillStyle = darken ? '#300' : '#a00'; ctx.fillRect(0, 0, 1, 1); ctx.globalCompositeOperation = mode; if (ctx.globalCompositeOperation === mode) { ctx.fillStyle = darken ? '#a00' : '#300'; ctx.fillRect(0, 0, 1, 1); ok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken ? 170 : 51; } } catch (e) {} ctx.restore(); nativeModes[mode] = ok; }); CanvasProvider.release(ctx); } this.process = function(mode, srcContext, dstContext, alpha, offset) { var srcCanvas = srcContext.canvas, normal = mode === 'normal'; if (normal || nativeModes[mode]) { dstContext.save(); dstContext.setTransform(1, 0, 0, 1, 0, 0); dstContext.globalAlpha = alpha; if (!normal) dstContext.globalCompositeOperation = mode; dstContext.drawImage(srcCanvas, offset.x, offset.y); dstContext.restore(); } else { var process = modes[mode]; if (!process) return; var dstData = dstContext.getImageData(offset.x, offset.y, srcCanvas.width, srcCanvas.height), dst = dstData.data, src = srcContext.getImageData(0, 0, srcCanvas.width, srcCanvas.height).data; for (var i = 0, l = dst.length; i < l; i += 4) { sr = src[i]; br = dst[i]; sg = src[i + 1]; bg = dst[i + 1]; sb = src[i + 2]; bb = dst[i + 2]; sa = src[i + 3]; ba = dst[i + 3]; process(); var a1 = sa * alpha / 255, a2 = 1 - a1; dst[i] = a1 * dr + a2 * br; dst[i + 1] = a1 * dg + a2 * bg; dst[i + 2] = a1 * db + a2 * bb; dst[i + 3] = sa * alpha + a2 * ba; } dstContext.putImageData(dstData, offset.x, offset.y); } }; }; var SvgElement = new function() { var svg = 'http://www.w3.org/2000/svg', xmlns = 'http://www.w3.org/2000/xmlns', xlink = 'http://www.w3.org/1999/xlink', attributeNamespace = { href: xlink, xlink: xmlns, xmlns: xmlns + '/', 'xmlns:xlink': xmlns + '/' }; function create(tag, attributes, formatter) { return set(document.createElementNS(svg, tag), attributes, formatter); } function get(node, name) { var namespace = attributeNamespace[name], value = namespace ? node.getAttributeNS(namespace, name) : node.getAttribute(name); return value === 'null' ? null : value; } function set(node, attributes, formatter) { for (var name in attributes) { var value = attributes[name], namespace = attributeNamespace[name]; if (typeof value === 'number' && formatter) value = formatter.number(value); if (namespace) { node.setAttributeNS(namespace, name, value); } else { node.setAttribute(name, value); } } return node; } return { svg: svg, xmlns: xmlns, xlink: xlink, create: create, get: get, set: set }; }; var SvgStyles = Base.each({ fillColor: ['fill', 'color'], fillRule: ['fill-rule', 'string'], strokeColor: ['stroke', 'color'], strokeWidth: ['stroke-width', 'number'], strokeCap: ['stroke-linecap', 'string'], strokeJoin: ['stroke-linejoin', 'string'], strokeScaling: ['vector-effect', 'lookup', { true: 'none', false: 'non-scaling-stroke' }, function(item, value) { return !value && (item instanceof PathItem || item instanceof Shape || item instanceof TextItem); }], miterLimit: ['stroke-miterlimit', 'number'], dashArray: ['stroke-dasharray', 'array'], dashOffset: ['stroke-dashoffset', 'number'], fontFamily: ['font-family', 'string'], fontWeight: ['font-weight', 'string'], fontSize: ['font-size', 'number'], justification: ['text-anchor', 'lookup', { left: 'start', center: 'middle', right: 'end' }], opacity: ['opacity', 'number'], blendMode: ['mix-blend-mode', 'style'] }, function(entry, key) { var part = Base.capitalize(key), lookup = entry[2]; this[key] = { type: entry[1], property: key, attribute: entry[0], toSVG: lookup, fromSVG: lookup && Base.each(lookup, function(value, name) { this[value] = name; }, {}), exportFilter: entry[3], get: 'get' + part, set: 'set' + part }; }, {}); new function() { var formatter; function getTransform(matrix, coordinates, center) { var attrs = new Base(), trans = matrix.getTranslation(); if (coordinates) { matrix = matrix._shiftless(); var point = matrix._inverseTransform(trans); attrs[center ? 'cx' : 'x'] = point.x; attrs[center ? 'cy' : 'y'] = point.y; trans = null; } if (!matrix.isIdentity()) { var decomposed = matrix.decompose(); if (decomposed) { var parts = [], angle = decomposed.rotation, scale = decomposed.scaling, skew = decomposed.skewing; if (trans && !trans.isZero()) parts.push('translate(' + formatter.point(trans) + ')'); if (angle) parts.push('rotate(' + formatter.number(angle) + ')'); if (!Numerical.isZero(scale.x - 1) || !Numerical.isZero(scale.y - 1)) parts.push('scale(' + formatter.point(scale) +')'); if (skew.x) parts.push('skewX(' + formatter.number(skew.x) + ')'); if (skew.y) parts.push('skewY(' + formatter.number(skew.y) + ')'); attrs.transform = parts.join(' '); } else { attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')'; } } return attrs; } function exportGroup(item, options) { var attrs = getTransform(item._matrix), children = item._children; var node = SvgElement.create('g', attrs, formatter); for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; var childNode = exportSVG(child, options); if (childNode) { if (child.isClipMask()) { var clip = SvgElement.create('clipPath'); clip.appendChild(childNode); setDefinition(child, clip, 'clip'); SvgElement.set(node, { 'clip-path': 'url(#' + clip.id + ')' }); } else { node.appendChild(childNode); } } } return node; } function exportRaster(item, options) { var attrs = getTransform(item._matrix, true), size = item.getSize(), image = item.getImage(); attrs.x -= size.width / 2; attrs.y -= size.height / 2; attrs.width = size.width; attrs.height = size.height; attrs.href = options.embedImages == false && image && image.src || item.toDataURL(); return SvgElement.create('image', attrs, formatter); } function exportPath(item, options) { var matchShapes = options.matchShapes; if (matchShapes) { var shape = item.toShape(false); if (shape) return exportShape(shape, options); } var segments = item._segments, length = segments.length, type, attrs = getTransform(item._matrix); if (matchShapes && length >= 2 && !item.hasHandles()) { if (length > 2) { type = item._closed ? 'polygon' : 'polyline'; var parts = []; for (var i = 0; i < length; i++) { parts.push(formatter.point(segments[i]._point)); } attrs.points = parts.join(' '); } else { type = 'line'; var start = segments[0]._point, end = segments[1]._point; attrs.set({ x1: start.x, y1: start.y, x2: end.x, y2: end.y }); } } else { type = 'path'; attrs.d = item.getPathData(null, options.precision); } return SvgElement.create(type, attrs, formatter); } function exportShape(item) { var type = item._type, radius = item._radius, attrs = getTransform(item._matrix, true, type !== 'rectangle'); if (type === 'rectangle') { type = 'rect'; var size = item._size, width = size.width, height = size.height; attrs.x -= width / 2; attrs.y -= height / 2; attrs.width = width; attrs.height = height; if (radius.isZero()) radius = null; } if (radius) { if (type === 'circle') { attrs.r = radius; } else { attrs.rx = radius.width; attrs.ry = radius.height; } } return SvgElement.create(type, attrs, formatter); } function exportCompoundPath(item, options) { var attrs = getTransform(item._matrix); var data = item.getPathData(null, options.precision); if (data) attrs.d = data; return SvgElement.create('path', attrs, formatter); } function exportSymbolItem(item, options) { var attrs = getTransform(item._matrix, true), definition = item._definition, node = getDefinition(definition, 'symbol'), definitionItem = definition._item, bounds = definitionItem.getBounds(); if (!node) { node = SvgElement.create('symbol', { viewBox: formatter.rectangle(bounds) }); node.appendChild(exportSVG(definitionItem, options)); setDefinition(definition, node, 'symbol'); } attrs.href = '#' + node.id; attrs.x += bounds.x; attrs.y += bounds.y; attrs.width = bounds.width; attrs.height = bounds.height; attrs.overflow = 'visible'; return SvgElement.create('use', attrs, formatter); } function exportGradient(color) { var gradientNode = getDefinition(color, 'color'); if (!gradientNode) { var gradient = color.getGradient(), radial = gradient._radial, origin = color.getOrigin(), destination = color.getDestination(), attrs; if (radial) { attrs = { cx: origin.x, cy: origin.y, r: origin.getDistance(destination) }; var highlight = color.getHighlight(); if (highlight) { attrs.fx = highlight.x; attrs.fy = highlight.y; } } else { attrs = { x1: origin.x, y1: origin.y, x2: destination.x, y2: destination.y }; } attrs.gradientUnits = 'userSpaceOnUse'; gradientNode = SvgElement.create((radial ? 'radial' : 'linear') + 'Gradient', attrs, formatter); var stops = gradient._stops; for (var i = 0, l = stops.length; i < l; i++) { var stop = stops[i], stopColor = stop._color, alpha = stopColor.getAlpha(), offset = stop._offset; attrs = { offset: offset == null ? i / (l - 1) : offset }; if (stopColor) attrs['stop-color'] = stopColor.toCSS(true); if (alpha < 1) attrs['stop-opacity'] = alpha; gradientNode.appendChild( SvgElement.create('stop', attrs, formatter)); } setDefinition(color, gradientNode, 'color'); } return 'url(#' + gradientNode.id + ')'; } function exportText(item) { var node = SvgElement.create('text', getTransform(item._matrix, true), formatter); node.textContent = item._content; return node; } var exporters = { Group: exportGroup, Layer: exportGroup, Raster: exportRaster, Path: exportPath, Shape: exportShape, CompoundPath: exportCompoundPath, SymbolItem: exportSymbolItem, PointText: exportText }; function applyStyle(item, node, isRoot) { var attrs = {}, parent = !isRoot && item.getParent(), style = []; if (item._name != null) attrs.id = item._name; Base.each(SvgStyles, function(entry) { var get = entry.get, type = entry.type, value = item[get](); if (entry.exportFilter ? entry.exportFilter(item, value) : !parent || !Base.equals(parent[get](), value)) { if (type === 'color' && value != null) { var alpha = value.getAlpha(); if (alpha < 1) attrs[entry.attribute + '-opacity'] = alpha; } if (type === 'style') { style.push(entry.attribute + ': ' + value); } else { attrs[entry.attribute] = value == null ? 'none' : type === 'color' ? value.gradient ? exportGradient(value, item) : value.toCSS(true) : type === 'array' ? value.join(',') : type === 'lookup' ? entry.toSVG[value] : value; } } }); if (style.length) attrs.style = style.join(';'); if (attrs.opacity === 1) delete attrs.opacity; if (!item._visible) attrs.visibility = 'hidden'; return SvgElement.set(node, attrs, formatter); } var definitions; function getDefinition(item, type) { if (!definitions) definitions = { ids: {}, svgs: {} }; return item && definitions.svgs[type + '-' + (item._id || item.__id || (item.__id = UID.get('svg')))]; } function setDefinition(item, node, type) { if (!definitions) getDefinition(); var typeId = definitions.ids[type] = (definitions.ids[type] || 0) + 1; node.id = type + '-' + typeId; definitions.svgs[type + '-' + (item._id || item.__id)] = node; } function exportDefinitions(node, options) { var svg = node, defs = null; if (definitions) { svg = node.nodeName.toLowerCase() === 'svg' && node; for (var i in definitions.svgs) { if (!defs) { if (!svg) { svg = SvgElement.create('svg'); svg.appendChild(node); } defs = svg.insertBefore(SvgElement.create('defs'), svg.firstChild); } defs.appendChild(definitions.svgs[i]); } definitions = null; } return options.asString ? new self.XMLSerializer().serializeToString(svg) : svg; } function exportSVG(item, options, isRoot) { var exporter = exporters[item._class], node = exporter && exporter(item, options); if (node) { var onExport = options.onExport; if (onExport) node = onExport(item, node, options) || node; var data = JSON.stringify(item._data); if (data && data !== '{}' && data !== 'null') node.setAttribute('data-paper-data', data); } return node && applyStyle(item, node, isRoot); } function setOptions(options) { if (!options) options = {}; formatter = new Formatter(options.precision); return options; } Item.inject({ exportSVG: function(options) { options = setOptions(options); return exportDefinitions(exportSVG(this, options, true), options); } }); Project.inject({ exportSVG: function(options) { options = setOptions(options); var children = this._children, view = this.getView(), bounds = Base.pick(options.bounds, 'view'), mx = options.matrix || bounds === 'view' && view._matrix, matrix = mx && Matrix.read([mx]), rect = bounds === 'view' ? new Rectangle([0, 0], view.getViewSize()) : bounds === 'content' ? Item._getBounds(children, matrix, { stroke: true }) .rect : Rectangle.read([bounds], 0, { readNull: true }), attrs = { version: '1.1', xmlns: SvgElement.svg, 'xmlns:xlink': SvgElement.xlink, }; if (rect) { attrs.width = rect.width; attrs.height = rect.height; if (rect.x || rect.y) attrs.viewBox = formatter.rectangle(rect); } var node = SvgElement.create('svg', attrs, formatter), parent = node; if (matrix && !matrix.isIdentity()) { parent = node.appendChild(SvgElement.create('g', getTransform(matrix), formatter)); } for (var i = 0, l = children.length; i < l; i++) { parent.appendChild(exportSVG(children[i], options, true)); } return exportDefinitions(node, options); } }); }; new function() { var definitions = {}, rootSize; function getValue(node, name, isString, allowNull, allowPercent) { var value = SvgElement.get(node, name), res = value == null ? allowNull ? null : isString ? '' : 0 : isString ? value : parseFloat(value); return /%\s*$/.test(value) ? (res / 100) * (allowPercent ? 1 : rootSize[/x|^width/.test(name) ? 'width' : 'height']) : res; } function getPoint(node, x, y, allowNull, allowPercent) { x = getValue(node, x || 'x', false, allowNull, allowPercent); y = getValue(node, y || 'y', false, allowNull, allowPercent); return allowNull && (x == null || y == null) ? null : new Point(x, y); } function getSize(node, w, h, allowNull, allowPercent) { w = getValue(node, w || 'width', false, allowNull, allowPercent); h = getValue(node, h || 'height', false, allowNull, allowPercent); return allowNull && (w == null || h == null) ? null : new Size(w, h); } function convertValue(value, type, lookup) { return value === 'none' ? null : type === 'number' ? parseFloat(value) : type === 'array' ? value ? value.split(/[\s,]+/g).map(parseFloat) : [] : type === 'color' ? getDefinition(value) || value : type === 'lookup' ? lookup[value] : value; } function importGroup(node, type, options, isRoot) { var nodes = node.childNodes, isClip = type === 'clippath', isDefs = type === 'defs', item = new Group(), project = item._project, currentStyle = project._currentStyle, children = []; if (!isClip && !isDefs) { item = applyAttributes(item, node, isRoot); project._currentStyle = item._style.clone(); } if (isRoot) { var defs = node.querySelectorAll('defs'); for (var i = 0, l = defs.length; i < l; i++) { importNode(defs[i], options, false); } } for (var i = 0, l = nodes.length; i < l; i++) { var childNode = nodes[i], child; if (childNode.nodeType === 1 && !/^defs$/i.test(childNode.nodeName) && (child = importNode(childNode, options, false)) && !(child instanceof SymbolDefinition)) children.push(child); } item.addChildren(children); if (isClip) item = applyAttributes(item.reduce(), node, isRoot); project._currentStyle = currentStyle; if (isClip || isDefs) { item.remove(); item = null; } return item; } function importPoly(node, type) { var coords = node.getAttribute('points').match( /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g), points = []; for (var i = 0, l = coords.length; i < l; i += 2) points.push(new Point( parseFloat(coords[i]), parseFloat(coords[i + 1]))); var path = new Path(points); if (type === 'polygon') path.closePath(); return path; } function importPath(node) { return PathItem.create(node.getAttribute('d')); } function importGradient(node, type) { var id = (getValue(node, 'href', true) || '').substring(1), radial = type === 'radialgradient', gradient; if (id) { gradient = definitions[id].getGradient(); if (gradient._radial ^ radial) { gradient = gradient.clone(); gradient._radial = radial; } } else { var nodes = node.childNodes, stops = []; for (var i = 0, l = nodes.length; i < l; i++) { var child = nodes[i]; if (child.nodeType === 1) stops.push(applyAttributes(new GradientStop(), child)); } gradient = new Gradient(stops, radial); } var origin, destination, highlight, scaleToBounds = getValue(node, 'gradientUnits', true) !== 'userSpaceOnUse'; if (radial) { origin = getPoint(node, 'cx', 'cy', false, scaleToBounds); destination = origin.add( getValue(node, 'r', false, false, scaleToBounds), 0); highlight = getPoint(node, 'fx', 'fy', true, scaleToBounds); } else { origin = getPoint(node, 'x1', 'y1', false, scaleToBounds); destination = getPoint(node, 'x2', 'y2', false, scaleToBounds); } var color = applyAttributes( new Color(gradient, origin, destination, highlight), node); color._scaleToBounds = scaleToBounds; return null; } var importers = { '#document': function (node, type, options, isRoot) { var nodes = node.childNodes; for (var i = 0, l = nodes.length; i < l; i++) { var child = nodes[i]; if (child.nodeType === 1) return importNode(child, options, isRoot); } }, g: importGroup, svg: importGroup, clippath: importGroup, polygon: importPoly, polyline: importPoly, path: importPath, lineargradient: importGradient, radialgradient: importGradient, image: function (node) { var raster = new Raster(getValue(node, 'href', true)); raster.on('load', function() { var size = getSize(node); this.setSize(size); var center = this._matrix._transformPoint( getPoint(node).add(size.divide(2))); this.translate(center); }); return raster; }, symbol: function(node, type, options, isRoot) { return new SymbolDefinition( importGroup(node, type, options, isRoot), true); }, defs: importGroup, use: function(node) { var id = (getValue(node, 'href', true) || '').substring(1), definition = definitions[id], point = getPoint(node); return definition ? definition instanceof SymbolDefinition ? definition.place(point) : definition.clone().translate(point) : null; }, circle: function(node) { return new Shape.Circle( getPoint(node, 'cx', 'cy'), getValue(node, 'r')); }, ellipse: function(node) { return new Shape.Ellipse({ center: getPoint(node, 'cx', 'cy'), radius: getSize(node, 'rx', 'ry') }); }, rect: function(node) { return new Shape.Rectangle(new Rectangle( getPoint(node), getSize(node) ), getSize(node, 'rx', 'ry')); }, line: function(node) { return new Path.Line( getPoint(node, 'x1', 'y1'), getPoint(node, 'x2', 'y2')); }, text: function(node) { var text = new PointText(getPoint(node).add( getPoint(node, 'dx', 'dy'))); text.setContent(node.textContent.trim() || ''); return text; } }; function applyTransform(item, value, name, node) { if (item.transform) { var transforms = (node.getAttribute(name) || '').split(/\)\s*/g), matrix = new Matrix(); for (var i = 0, l = transforms.length; i < l; i++) { var transform = transforms[i]; if (!transform) break; var parts = transform.split(/\(\s*/), command = parts[0], v = parts[1].split(/[\s,]+/g); for (var j = 0, m = v.length; j < m; j++) v[j] = parseFloat(v[j]); switch (command) { case 'matrix': matrix.append( new Matrix(v[0], v[1], v[2], v[3], v[4], v[5])); break; case 'rotate': matrix.rotate(v[0], v[1], v[2]); break; case 'translate': matrix.translate(v[0], v[1]); break; case 'scale': matrix.scale(v); break; case 'skewX': matrix.skew(v[0], 0); break; case 'skewY': matrix.skew(0, v[0]); break; } } item.transform(matrix); } } function applyOpacity(item, value, name) { var key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor', color = item[key] && item[key](); if (color) color.setAlpha(parseFloat(value)); } var attributes = Base.set(Base.each(SvgStyles, function(entry) { this[entry.attribute] = function(item, value) { if (item[entry.set]) { item[entry.set](convertValue(value, entry.type, entry.fromSVG)); if (entry.type === 'color') { var color = item[entry.get](); if (color) { if (color._scaleToBounds) { var bounds = item.getBounds(); color.transform(new Matrix() .translate(bounds.getPoint()) .scale(bounds.getSize())); } } } } }; }, {}), { id: function(item, value) { definitions[value] = item; if (item.setName) item.setName(value); }, 'clip-path': function(item, value) { var clip = getDefinition(value); if (clip) { clip = clip.clone(); clip.setClipMask(true); if (item instanceof Group) { item.insertChild(0, clip); } else { return new Group(clip, item); } } }, gradientTransform: applyTransform, transform: applyTransform, 'fill-opacity': applyOpacity, 'stroke-opacity': applyOpacity, visibility: function(item, value) { if (item.setVisible) item.setVisible(value === 'visible'); }, display: function(item, value) { if (item.setVisible) item.setVisible(value !== null); }, 'stop-color': function(item, value) { if (item.setColor) item.setColor(value); }, 'stop-opacity': function(item, value) { if (item._color) item._color.setAlpha(parseFloat(value)); }, offset: function(item, value) { if (item.setOffset) { var percent = value.match(/(.*)%$/); item.setOffset(percent ? percent[1] / 100 : parseFloat(value)); } }, viewBox: function(item, value, name, node, styles) { var rect = new Rectangle(convertValue(value, 'array')), size = getSize(node, null, null, true), group, matrix; if (item instanceof Group) { var scale = size ? size.divide(rect.getSize()) : 1, matrix = new Matrix().scale(scale) .translate(rect.getPoint().negate()); group = item; } else if (item instanceof SymbolDefinition) { if (size) rect.setSize(size); group = item._item; } if (group) { if (getAttribute(node, 'overflow', styles) !== 'visible') { var clip = new Shape.Rectangle(rect); clip.setClipMask(true); group.addChild(clip); } if (matrix) group.transform(matrix); } } }); function getAttribute(node, name, styles) { var attr = node.attributes[name], value = attr && attr.value; if (!value) { var style = Base.camelize(name); value = node.style[style]; if (!value && styles.node[style] !== styles.parent[style]) value = styles.node[style]; } return !value ? undefined : value === 'none' ? null : value; } function applyAttributes(item, node, isRoot) { if (node.style) { var parent = node.parentNode, styles = { node: DomElement.getStyles(node) || {}, parent: !isRoot && !/^defs$/i.test(parent.tagName) && DomElement.getStyles(parent) || {} }; Base.each(attributes, function(apply, name) { var value = getAttribute(node, name, styles); item = value !== undefined && apply(item, value, name, node, styles) || item; }); } return item; } function getDefinition(value) { var match = value && value.match(/\((?:["'#]*)([^"')]+)/), name = match && match[1], res = name && definitions[window ? name.replace(window.location.href.split('#')[0] + '#', '') : name]; if (res && res._scaleToBounds) { res = res.clone(); res._scaleToBounds = true; } return res; } function importNode(node, options, isRoot) { var type = node.nodeName.toLowerCase(), isElement = type !== '#document', body = document.body, container, parent, next; if (isRoot && isElement) { rootSize = paper.getView().getSize(); rootSize = getSize(node, null, null, true) || rootSize; container = SvgElement.create('svg', { style: 'stroke-width: 1px; stroke-miterlimit: 10' }); parent = node.parentNode; next = node.nextSibling; container.appendChild(node); body.appendChild(container); } var settings = paper.settings, applyMatrix = settings.applyMatrix, insertItems = settings.insertItems; settings.applyMatrix = false; settings.insertItems = false; var importer = importers[type], item = importer && importer(node, type, options, isRoot) || null; settings.insertItems = insertItems; settings.applyMatrix = applyMatrix; if (item) { if (isElement && !(item instanceof Group)) item = applyAttributes(item, node, isRoot); var onImport = options.onImport, data = isElement && node.getAttribute('data-paper-data'); if (onImport) item = onImport(node, item, options) || item; if (options.expandShapes && item instanceof Shape) { item.remove(); item = item.toPath(); } if (data) item._data = JSON.parse(data); } if (container) { body.removeChild(container); if (parent) { if (next) { parent.insertBefore(node, next); } else { parent.appendChild(node); } } } if (isRoot) { definitions = {}; if (item && Base.pick(options.applyMatrix, applyMatrix)) item.matrix.apply(true, true); } return item; } function importSVG(source, options, owner) { if (!source) return null; options = typeof options === 'function' ? { onLoad: options } : options || {}; var scope = paper, item = null; function onLoad(svg) { try { var node = typeof svg === 'object' ? svg : new self.DOMParser() .parseFromString(svg, 'image/svg+xml'); if (!node.nodeName) { node = null; throw new Error('Unsupported SVG source: ' + source); } paper = scope; item = importNode(node, options, true); if (!options || options.insert !== false) { owner._insertItem(undefined, item); } var onLoad = options.onLoad; if (onLoad) onLoad(item, svg); } catch (e) { onError(e); } } function onError(message, status) { var onError = options.onError; if (onError) { onError(message, status); } else { throw new Error(message); } } if (typeof source === 'string' && !/^.* and ending at the value listed on top of the \u03A3)." ), React.createElement( "div", { className: "howtocode" }, React.createElement( "h3", { id: "how-to-implement-the-basis-function" }, "How to implement the basis function" ), React.createElement( "p", null, "We could naively implement the basis function as a mathematical construct, using the function as our guide, like this:" ), React.createElement( "pre", null, "function Bezier(n,t):\n sum = 0\n for(k=0; k= lut.length):\n s = lut.length\n nextRow = new array(size=s+1)\n nextRow[0] = 1\n for(i=1, prev=s-1; i