summaryrefslogtreecommitdiffstats
path: root/debian/missing-sources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:29 +0000
commitdf4528d6668ab18e40584fe540355bdfba0fb6dd (patch)
treef1bfdcb6485edff853369405dd5423c662c47345 /debian/missing-sources
parentAdding upstream version 14.2.21. (diff)
downloadceph-debian.tar.xz
ceph-debian.zip
Adding debian version 14.2.21-1.debian/14.2.21-1debian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/missing-sources')
-rw-r--r--debian/missing-sources/bootstrap.js2377
-rw-r--r--debian/missing-sources/two.js10244
2 files changed, 12621 insertions, 0 deletions
diff --git a/debian/missing-sources/bootstrap.js b/debian/missing-sources/bootstrap.js
new file mode 100644
index 00000000..8a2e99a5
--- /dev/null
+++ b/debian/missing-sources/bootstrap.js
@@ -0,0 +1,2377 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under the MIT license
+ */
+
+if (typeof jQuery === 'undefined') {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+ 'use strict';
+ var version = $.fn.jquery.split(' ')[0].split('.')
+ if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
+ throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
+ }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.7
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap')
+
+ var transEndEventNames = {
+ WebkitTransition : 'webkitTransitionEnd',
+ MozTransition : 'transitionend',
+ OTransition : 'oTransitionEnd otransitionend',
+ transition : 'transitionend'
+ }
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] }
+ }
+ }
+
+ return false // explicit for ie8 ( ._.)
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function (duration) {
+ var called = false
+ var $el = this
+ $(this).one('bsTransitionEnd', function () { called = true })
+ var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+ setTimeout(callback, duration)
+ return this
+ }
+
+ $(function () {
+ $.support.transition = transitionEnd()
+
+ if (!$.support.transition) return
+
+ $.event.special.bsTransitionEnd = {
+ bindType: $.support.transition.end,
+ delegateType: $.support.transition.end,
+ handle: function (e) {
+ if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+ }
+ }
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.7
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // ALERT CLASS DEFINITION
+ // ======================
+
+ var dismiss = '[data-dismiss="alert"]'
+ var Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.VERSION = '3.3.7'
+
+ Alert.TRANSITION_DURATION = 150
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = $(selector === '#' ? [] : selector)
+
+ if (e) e.preventDefault()
+
+ if (!$parent.length) {
+ $parent = $this.closest('.alert')
+ }
+
+ $parent.trigger(e = $.Event('close.bs.alert'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ // detach from parent, fire event then clean up data
+ $parent.detach().trigger('closed.bs.alert').remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent
+ .one('bsTransitionEnd', removeElement)
+ .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+ removeElement()
+ }
+
+
+ // ALERT PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.alert')
+
+ if (!data) $this.data('bs.alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.alert
+
+ $.fn.alert = Plugin
+ $.fn.alert.Constructor = Alert
+
+
+ // ALERT NO CONFLICT
+ // =================
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ // ALERT DATA-API
+ // ==============
+
+ $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.7
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // BUTTON PUBLIC CLASS DEFINITION
+ // ==============================
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Button.DEFAULTS, options)
+ this.isLoading = false
+ }
+
+ Button.VERSION = '3.3.7'
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ var $el = this.$element
+ var val = $el.is('input') ? 'val' : 'html'
+ var data = $el.data()
+
+ state += 'Text'
+
+ if (data.resetText == null) $el.data('resetText', $el[val]())
+
+ // push to event loop to allow forms to submit
+ setTimeout($.proxy(function () {
+ $el[val](data[state] == null ? this.options[state] : data[state])
+
+ if (state == 'loadingText') {
+ this.isLoading = true
+ $el.addClass(d).attr(d, d).prop(d, true)
+ } else if (this.isLoading) {
+ this.isLoading = false
+ $el.removeClass(d).removeAttr(d).prop(d, false)
+ }
+ }, this), 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var changed = true
+ var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+ if ($parent.length) {
+ var $input = this.$element.find('input')
+ if ($input.prop('type') == 'radio') {
+ if ($input.prop('checked')) changed = false
+ $parent.find('.active').removeClass('active')
+ this.$element.addClass('active')
+ } else if ($input.prop('type') == 'checkbox') {
+ if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
+ this.$element.toggleClass('active')
+ }
+ $input.prop('checked', this.$element.hasClass('active'))
+ if (changed) $input.trigger('change')
+ } else {
+ this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+ this.$element.toggleClass('active')
+ }
+ }
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.button')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ var old = $.fn.button
+
+ $.fn.button = Plugin
+ $.fn.button.Constructor = Button
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(document)
+ .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ var $btn = $(e.target).closest('.btn')
+ Plugin.call($btn, 'toggle')
+ if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
+ // Prevent double click on radios, and the double selections (so cancellation) on checkboxes
+ e.preventDefault()
+ // The target component still receive the focus
+ if ($btn.is('input,button')) $btn.trigger('focus')
+ else $btn.find('input:visible,button:visible').first().trigger('focus')
+ }
+ })
+ .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.7
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // CAROUSEL CLASS DEFINITION
+ // =========================
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.paused = null
+ this.sliding = null
+ this.interval = null
+ this.$active = null
+ this.$items = null
+
+ this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+ this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+ .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+ .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+ }
+
+ Carousel.VERSION = '3.3.7'
+
+ Carousel.TRANSITION_DURATION = 600
+
+ Carousel.DEFAULTS = {
+ interval: 5000,
+ pause: 'hover',
+ wrap: true,
+ keyboard: true
+ }
+
+ Carousel.prototype.keydown = function (e) {
+ if (/input|textarea/i.test(e.target.tagName)) return
+ switch (e.which) {
+ case 37: this.prev(); break
+ case 39: this.next(); break
+ default: return
+ }
+
+ e.preventDefault()
+ }
+
+ Carousel.prototype.cycle = function (e) {
+ e || (this.paused = false)
+
+ this.interval && clearInterval(this.interval)
+
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+ return this
+ }
+
+ Carousel.prototype.getItemIndex = function (item) {
+ this.$items = item.parent().children('.item')
+ return this.$items.index(item || this.$active)
+ }
+
+ Carousel.prototype.getItemForDirection = function (direction, active) {
+ var activeIndex = this.getItemIndex(active)
+ var willWrap = (direction == 'prev' && activeIndex === 0)
+ || (direction == 'next' && activeIndex == (this.$items.length - 1))
+ if (willWrap && !this.options.wrap) return active
+ var delta = direction == 'prev' ? -1 : 1
+ var itemIndex = (activeIndex + delta) % this.$items.length
+ return this.$items.eq(itemIndex)
+ }
+
+ Carousel.prototype.to = function (pos) {
+ var that = this
+ var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+ if (activeIndex == pos) return this.pause().cycle()
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+ }
+
+ Carousel.prototype.pause = function (e) {
+ e || (this.paused = true)
+
+ if (this.$element.find('.next, .prev').length && $.support.transition) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+
+ this.interval = clearInterval(this.interval)
+
+ return this
+ }
+
+ Carousel.prototype.next = function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ Carousel.prototype.prev = function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ Carousel.prototype.slide = function (type, next) {
+ var $active = this.$element.find('.item.active')
+ var $next = next || this.getItemForDirection(type, $active)
+ var isCycling = this.interval
+ var direction = type == 'next' ? 'left' : 'right'
+ var that = this
+
+ if ($next.hasClass('active')) return (this.sliding = false)
+
+ var relatedTarget = $next[0]
+ var slideEvent = $.Event('slide.bs.carousel', {
+ relatedTarget: relatedTarget,
+ direction: direction
+ })
+ this.$element.trigger(slideEvent)
+ if (slideEvent.isDefaultPrevented()) return
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+ $nextIndicator && $nextIndicator.addClass('active')
+ }
+
+ var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ $active
+ .one('bsTransitionEnd', function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () {
+ that.$element.trigger(slidEvent)
+ }, 0)
+ })
+ .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+ } else {
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger(slidEvent)
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+
+ // CAROUSEL PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.carousel')
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ var action = typeof option == 'string' ? option : options.slide
+
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = Plugin
+ $.fn.carousel.Constructor = Carousel
+
+
+ // CAROUSEL NO CONFLICT
+ // ====================
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+
+ // CAROUSEL DATA-API
+ // =================
+
+ var clickHandler = function (e) {
+ var href
+ var $this = $(this)
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+ if (!$target.hasClass('carousel')) return
+ var options = $.extend({}, $target.data(), $this.data())
+ var slideIndex = $this.attr('data-slide-to')
+ if (slideIndex) options.interval = false
+
+ Plugin.call($target, options)
+
+ if (slideIndex) {
+ $target.data('bs.carousel').to(slideIndex)
+ }
+
+ e.preventDefault()
+ }
+
+ $(document)
+ .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+ .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+ $(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ Plugin.call($carousel, $carousel.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.7
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+/* jshint latedef: false */
+
++function ($) {
+ 'use strict';
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Collapse.DEFAULTS, options)
+ this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
+ '[data-toggle="collapse"][data-target="#' + element.id + '"]')
+ this.transitioning = null
+
+ if (this.options.parent) {
+ this.$parent = this.getParent()
+ } else {
+ this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+ }
+
+ if (this.options.toggle) this.toggle()
+ }
+
+ Collapse.VERSION = '3.3.7'
+
+ Collapse.TRANSITION_DURATION = 350
+
+ Collapse.DEFAULTS = {
+ toggle: true
+ }
+
+ Collapse.prototype.dimension = function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ Collapse.prototype.show = function () {
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ var activesData
+ var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+ if (actives && actives.length) {
+ activesData = actives.data('bs.collapse')
+ if (activesData && activesData.transitioning) return
+ }
+
+ var startEvent = $.Event('show.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ if (actives && actives.length) {
+ Plugin.call(actives, 'hide')
+ activesData || actives.data('bs.collapse', null)
+ }
+
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')[dimension](0)
+ .attr('aria-expanded', true)
+
+ this.$trigger
+ .removeClass('collapsed')
+ .attr('aria-expanded', true)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse in')[dimension]('')
+ this.transitioning = 0
+ this.$element
+ .trigger('shown.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+ this.$element
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+ }
+
+ Collapse.prototype.hide = function () {
+ if (this.transitioning || !this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('hide.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var dimension = this.dimension()
+
+ this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse in')
+ .attr('aria-expanded', false)
+
+ this.$trigger
+ .addClass('collapsed')
+ .attr('aria-expanded', false)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.transitioning = 0
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse')
+ .trigger('hidden.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ this.$element
+ [dimension](0)
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+ }
+
+ Collapse.prototype.toggle = function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ Collapse.prototype.getParent = function () {
+ return $(this.options.parent)
+ .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+ .each($.proxy(function (i, element) {
+ var $element = $(element)
+ this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+ }, this))
+ .end()
+ }
+
+ Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+ var isOpen = $element.hasClass('in')
+
+ $element.attr('aria-expanded', isOpen)
+ $trigger
+ .toggleClass('collapsed', !isOpen)
+ .attr('aria-expanded', isOpen)
+ }
+
+ function getTargetFromTrigger($trigger) {
+ var href
+ var target = $trigger.attr('data-target')
+ || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+ return $(target)
+ }
+
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.collapse')
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = Plugin
+ $.fn.collapse.Constructor = Collapse
+
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+ var $this = $(this)
+
+ if (!$this.attr('data-target')) e.preventDefault()
+
+ var $target = getTargetFromTrigger($this)
+ var data = $target.data('bs.collapse')
+ var option = data ? 'toggle' : $this.data()
+
+ Plugin.call($target, option)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.7
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.3.7'
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+ })
+ }
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $(document.createElement('div'))
+ .addClass('dropdown-backdrop')
+ .insertAfter($(this))
+ .on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this
+ .trigger('focus')
+ .attr('aria-expanded', 'true')
+
+ $parent
+ .toggleClass('open')
+ .trigger($.Event('shown.bs.dropdown', relatedTarget))
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if (!isActive && e.which != 27 || isActive && e.which == 27) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.disabled):visible a'
+ var $items = $parent.find('.dropdown-menu' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index(e.target)
+
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.7
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // MODAL CLASS DEFINITION
+ // ======================
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$body = $(document.body)
+ this.$element = $(element)
+ this.$dialog = this.$element.find('.modal-dialog')
+ this.$backdrop = null
+ this.isShown = null
+ this.originalBodyPad = null
+ this.scrollbarWidth = 0
+ this.ignoreBackdropClick = false
+
+ if (this.options.remote) {
+ this.$element
+ .find('.modal-content')
+ .load(this.options.remote, $.proxy(function () {
+ this.$element.trigger('loaded.bs.modal')
+ }, this))
+ }
+ }
+
+ Modal.VERSION = '3.3.7'
+
+ Modal.TRANSITION_DURATION = 300
+ Modal.BACKDROP_TRANSITION_DURATION = 150
+
+ Modal.DEFAULTS = {
+ backdrop: true,
+ keyboard: true,
+ show: true
+ }
+
+ Modal.prototype.toggle = function (_relatedTarget) {
+ return this.isShown ? this.hide() : this.show(_relatedTarget)
+ }
+
+ Modal.prototype.show = function (_relatedTarget) {
+ var that = this
+ var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.checkScrollbar()
+ this.setScrollbar()
+ this.$body.addClass('modal-open')
+
+ this.escape()
+ this.resize()
+
+ this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+ this.$dialog.on('mousedown.dismiss.bs.modal', function () {
+ that.$element.one('mouseup.dismiss.bs.modal', function (e) {
+ if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
+ })
+ })
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(that.$body) // don't move modals dom position
+ }
+
+ that.$element
+ .show()
+ .scrollTop(0)
+
+ that.adjustDialog()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element.addClass('in')
+
+ that.enforceFocus()
+
+ var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+ transition ?
+ that.$dialog // wait for modal to slide in
+ .one('bsTransitionEnd', function () {
+ that.$element.trigger('focus').trigger(e)
+ })
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ that.$element.trigger('focus').trigger(e)
+ })
+ }
+
+ Modal.prototype.hide = function (e) {
+ if (e) e.preventDefault()
+
+ e = $.Event('hide.bs.modal')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+ this.resize()
+
+ $(document).off('focusin.bs.modal')
+
+ this.$element
+ .removeClass('in')
+ .off('click.dismiss.bs.modal')
+ .off('mouseup.dismiss.bs.modal')
+
+ this.$dialog.off('mousedown.dismiss.bs.modal')
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$element
+ .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+ .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+ this.hideModal()
+ }
+
+ Modal.prototype.enforceFocus = function () {
+ $(document)
+ .off('focusin.bs.modal') // guard against infinite focus loop
+ .on('focusin.bs.modal', $.proxy(function (e) {
+ if (document !== e.target &&
+ this.$element[0] !== e.target &&
+ !this.$element.has(e.target).length) {
+ this.$element.trigger('focus')
+ }
+ }, this))
+ }
+
+ Modal.prototype.escape = function () {
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+ e.which == 27 && this.hide()
+ }, this))
+ } else if (!this.isShown) {
+ this.$element.off('keydown.dismiss.bs.modal')
+ }
+ }
+
+ Modal.prototype.resize = function () {
+ if (this.isShown) {
+ $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+ } else {
+ $(window).off('resize.bs.modal')
+ }
+ }
+
+ Modal.prototype.hideModal = function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.$body.removeClass('modal-open')
+ that.resetAdjustments()
+ that.resetScrollbar()
+ that.$element.trigger('hidden.bs.modal')
+ })
+ }
+
+ Modal.prototype.removeBackdrop = function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ Modal.prototype.backdrop = function (callback) {
+ var that = this
+ var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $(document.createElement('div'))
+ .addClass('modal-backdrop ' + animate)
+ .appendTo(this.$body)
+
+ this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+ if (this.ignoreBackdropClick) {
+ this.ignoreBackdropClick = false
+ return
+ }
+ if (e.target !== e.currentTarget) return
+ this.options.backdrop == 'static'
+ ? this.$element[0].focus()
+ : this.hide()
+ }, this))
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop
+ .one('bsTransitionEnd', callback)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ var callbackRemove = function () {
+ that.removeBackdrop()
+ callback && callback()
+ }
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$backdrop
+ .one('bsTransitionEnd', callbackRemove)
+ .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+ callbackRemove()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+
+ // these following methods are used to handle overflowing modals
+
+ Modal.prototype.handleUpdate = function () {
+ this.adjustDialog()
+ }
+
+ Modal.prototype.adjustDialog = function () {
+ var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+ this.$element.css({
+ paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+ paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+ })
+ }
+
+ Modal.prototype.resetAdjustments = function () {
+ this.$element.css({
+ paddingLeft: '',
+ paddingRight: ''
+ })
+ }
+
+ Modal.prototype.checkScrollbar = function () {
+ var fullWindowWidth = window.innerWidth
+ if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
+ var documentElementRect = document.documentElement.getBoundingClientRect()
+ fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
+ }
+ this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
+ this.scrollbarWidth = this.measureScrollbar()
+ }
+
+ Modal.prototype.setScrollbar = function () {
+ var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+ this.originalBodyPad = document.body.style.paddingRight || ''
+ if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+ }
+
+ Modal.prototype.resetScrollbar = function () {
+ this.$body.css('padding-right', this.originalBodyPad)
+ }
+
+ Modal.prototype.measureScrollbar = function () { // thx walsh
+ var scrollDiv = document.createElement('div')
+ scrollDiv.className = 'modal-scrollbar-measure'
+ this.$body.append(scrollDiv)
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+ this.$body[0].removeChild(scrollDiv)
+ return scrollbarWidth
+ }
+
+
+ // MODAL PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option, _relatedTarget) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.modal')
+ var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option](_relatedTarget)
+ else if (options.show) data.show(_relatedTarget)
+ })
+ }
+
+ var old = $.fn.modal
+
+ $.fn.modal = Plugin
+ $.fn.modal.Constructor = Modal
+
+
+ // MODAL NO CONFLICT
+ // =================
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ // MODAL DATA-API
+ // ==============
+
+ $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ var href = $this.attr('href')
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+ var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ if ($this.is('a')) e.preventDefault()
+
+ $target.one('show.bs.modal', function (showEvent) {
+ if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+ $target.one('hidden.bs.modal', function () {
+ $this.is(':visible') && $this.trigger('focus')
+ })
+ })
+ Plugin.call($target, option, this)
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.7
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TOOLTIP PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Tooltip = function (element, options) {
+ this.type = null
+ this.options = null
+ this.enabled = null
+ this.timeout = null
+ this.hoverState = null
+ this.$element = null
+ this.inState = null
+
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.VERSION = '3.3.7'
+
+ Tooltip.TRANSITION_DURATION = 150
+
+ Tooltip.DEFAULTS = {
+ animation: true,
+ placement: 'top',
+ selector: false,
+ template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ trigger: 'hover focus',
+ title: '',
+ delay: 0,
+ html: false,
+ container: false,
+ viewport: {
+ selector: 'body',
+ padding: 0
+ }
+ }
+
+ Tooltip.prototype.init = function (type, element, options) {
+ this.enabled = true
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
+ this.inState = { click: false, hover: false, focus: false }
+
+ if (this.$element[0] instanceof document.constructor && !this.options.selector) {
+ throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
+ }
+
+ var triggers = this.options.trigger.split(' ')
+
+ for (var i = triggers.length; i--;) {
+ var trigger = triggers[i]
+
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ Tooltip.prototype.getDefaults = function () {
+ return Tooltip.DEFAULTS
+ }
+
+ Tooltip.prototype.getOptions = function (options) {
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay,
+ hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ Tooltip.prototype.getDelegateOptions = function () {
+ var options = {}
+ var defaults = this.getDefaults()
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ })
+
+ return options
+ }
+
+ Tooltip.prototype.enter = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ if (obj instanceof $.Event) {
+ self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
+ }
+
+ if (self.tip().hasClass('in') || self.hoverState == 'in') {
+ self.hoverState = 'in'
+ return
+ }
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'in'
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ Tooltip.prototype.isInStateTrue = function () {
+ for (var key in this.inState) {
+ if (this.inState[key]) return true
+ }
+
+ return false
+ }
+
+ Tooltip.prototype.leave = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget).data('bs.' + this.type)
+
+ if (!self) {
+ self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+ $(obj.currentTarget).data('bs.' + this.type, self)
+ }
+
+ if (obj instanceof $.Event) {
+ self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
+ }
+
+ if (self.isInStateTrue()) return
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'out'
+
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ Tooltip.prototype.show = function () {
+ var e = $.Event('show.bs.' + this.type)
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+
+ var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+ if (e.isDefaultPrevented() || !inDom) return
+ var that = this
+
+ var $tip = this.tip()
+
+ var tipId = this.getUID(this.type)
+
+ this.setContent()
+ $tip.attr('id', tipId)
+ this.$element.attr('aria-describedby', tipId)
+
+ if (this.options.animation) $tip.addClass('fade')
+
+ var placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ var autoToken = /\s?auto?\s?/i
+ var autoPlace = autoToken.test(placement)
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .addClass(placement)
+ .data('bs.' + this.type, this)
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+ this.$element.trigger('inserted.bs.' + this.type)
+
+ var pos = this.getPosition()
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (autoPlace) {
+ var orgPlacement = placement
+ var viewportDim = this.getPosition(this.$viewport)
+
+ placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
+ placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
+ placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
+ placement
+
+ $tip
+ .removeClass(orgPlacement)
+ .addClass(placement)
+ }
+
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+ this.applyPlacement(calculatedOffset, placement)
+
+ var complete = function () {
+ var prevHoverState = that.hoverState
+ that.$element.trigger('shown.bs.' + that.type)
+ that.hoverState = null
+
+ if (prevHoverState == 'out') that.leave(that)
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+ }
+ }
+
+ Tooltip.prototype.applyPlacement = function (offset, placement) {
+ var $tip = this.tip()
+ var width = $tip[0].offsetWidth
+ var height = $tip[0].offsetHeight
+
+ // manually read margins because getBoundingClientRect includes difference
+ var marginTop = parseInt($tip.css('margin-top'), 10)
+ var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+ // we must check for NaN for ie 8/9
+ if (isNaN(marginTop)) marginTop = 0
+ if (isNaN(marginLeft)) marginLeft = 0
+
+ offset.top += marginTop
+ offset.left += marginLeft
+
+ // $.fn.offset doesn't round pixel values
+ // so we use setOffset directly with our own function B-0
+ $.offset.setOffset($tip[0], $.extend({
+ using: function (props) {
+ $tip.css({
+ top: Math.round(props.top),
+ left: Math.round(props.left)
+ })
+ }
+ }, offset), 0)
+
+ $tip.addClass('in')
+
+ // check to see if placing tip in new offset caused the tip to resize itself
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ offset.top = offset.top + height - actualHeight
+ }
+
+ var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+ if (delta.left) offset.left += delta.left
+ else offset.top += delta.top
+
+ var isVertical = /top|bottom/.test(placement)
+ var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+ var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+ $tip.offset(offset)
+ this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+ }
+
+ Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
+ this.arrow()
+ .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+ .css(isVertical ? 'top' : 'left', '')
+ }
+
+ Tooltip.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ Tooltip.prototype.hide = function (callback) {
+ var that = this
+ var $tip = $(this.$tip)
+ var e = $.Event('hide.bs.' + this.type)
+
+ function complete() {
+ if (that.hoverState != 'in') $tip.detach()
+ if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
+ that.$element
+ .removeAttr('aria-describedby')
+ .trigger('hidden.bs.' + that.type)
+ }
+ callback && callback()
+ }
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ $.support.transition && $tip.hasClass('fade') ?
+ $tip
+ .one('bsTransitionEnd', complete)
+ .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+ complete()
+
+ this.hoverState = null
+
+ return this
+ }
+
+ Tooltip.prototype.fixTitle = function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ Tooltip.prototype.hasContent = function () {
+ return this.getTitle()
+ }
+
+ Tooltip.prototype.getPosition = function ($element) {
+ $element = $element || this.$element
+
+ var el = $element[0]
+ var isBody = el.tagName == 'BODY'
+
+ var elRect = el.getBoundingClientRect()
+ if (elRect.width == null) {
+ // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+ elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+ }
+ var isSvg = window.SVGElement && el instanceof window.SVGElement
+ // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
+ // See https://github.com/twbs/bootstrap/issues/20280
+ var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
+ var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+ var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+ return $.extend({}, elRect, scroll, outerDims, elOffset)
+ }
+
+ Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+ }
+
+ Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+ var delta = { top: 0, left: 0 }
+ if (!this.$viewport) return delta
+
+ var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+ var viewportDimensions = this.getPosition(this.$viewport)
+
+ if (/right|left/.test(placement)) {
+ var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
+ var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+ if (topEdgeOffset < viewportDimensions.top) { // top overflow
+ delta.top = viewportDimensions.top - topEdgeOffset
+ } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+ delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+ }
+ } else {
+ var leftEdgeOffset = pos.left - viewportPadding
+ var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+ if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+ delta.left = viewportDimensions.left - leftEdgeOffset
+ } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
+ delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+ }
+ }
+
+ return delta
+ }
+
+ Tooltip.prototype.getTitle = function () {
+ var title
+ var $e = this.$element
+ var o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ Tooltip.prototype.getUID = function (prefix) {
+ do prefix += ~~(Math.random() * 1000000)
+ while (document.getElementById(prefix))
+ return prefix
+ }
+
+ Tooltip.prototype.tip = function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ if (this.$tip.length != 1) {
+ throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
+ }
+ }
+ return this.$tip
+ }
+
+ Tooltip.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+ }
+
+ Tooltip.prototype.enable = function () {
+ this.enabled = true
+ }
+
+ Tooltip.prototype.disable = function () {
+ this.enabled = false
+ }
+
+ Tooltip.prototype.toggleEnabled = function () {
+ this.enabled = !this.enabled
+ }
+
+ Tooltip.prototype.toggle = function (e) {
+ var self = this
+ if (e) {
+ self = $(e.currentTarget).data('bs.' + this.type)
+ if (!self) {
+ self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+ $(e.currentTarget).data('bs.' + this.type, self)
+ }
+ }
+
+ if (e) {
+ self.inState.click = !self.inState.click
+ if (self.isInStateTrue()) self.enter(self)
+ else self.leave(self)
+ } else {
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+ }
+ }
+
+ Tooltip.prototype.destroy = function () {
+ var that = this
+ clearTimeout(this.timeout)
+ this.hide(function () {
+ that.$element.off('.' + that.type).removeData('bs.' + that.type)
+ if (that.$tip) {
+ that.$tip.detach()
+ }
+ that.$tip = null
+ that.$arrow = null
+ that.$viewport = null
+ that.$element = null
+ })
+ }
+
+
+ // TOOLTIP PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tooltip')
+ var options = typeof option == 'object' && option
+
+ if (!data && /destroy|hide/.test(option)) return
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = Plugin
+ $.fn.tooltip.Constructor = Tooltip
+
+
+ // TOOLTIP NO CONFLICT
+ // ===================
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.7
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.VERSION = '3.3.7'
+
+ Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right',
+ trigger: 'click',
+ content: '',
+ template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+ this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+ ](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data && /destroy|hide/.test(option)) return
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.popover
+
+ $.fn.popover = Plugin
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.7
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // SCROLLSPY CLASS DEFINITION
+ // ==========================
+
+ function ScrollSpy(element, options) {
+ this.$body = $(document.body)
+ this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
+ this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
+ this.selector = (this.options.target || '') + ' .nav li > a'
+ this.offsets = []
+ this.targets = []
+ this.activeTarget = null
+ this.scrollHeight = 0
+
+ this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.VERSION = '3.3.7'
+
+ ScrollSpy.DEFAULTS = {
+ offset: 10
+ }
+
+ ScrollSpy.prototype.getScrollHeight = function () {
+ return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+ }
+
+ ScrollSpy.prototype.refresh = function () {
+ var that = this
+ var offsetMethod = 'offset'
+ var offsetBase = 0
+
+ this.offsets = []
+ this.targets = []
+ this.scrollHeight = this.getScrollHeight()
+
+ if (!$.isWindow(this.$scrollElement[0])) {
+ offsetMethod = 'position'
+ offsetBase = this.$scrollElement.scrollTop()
+ }
+
+ this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ var href = $el.data('target') || $el.attr('href')
+ var $href = /^#./.test(href) && $(href)
+
+ return ($href
+ && $href.length
+ && $href.is(':visible')
+ && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ that.offsets.push(this[0])
+ that.targets.push(this[1])
+ })
+ }
+
+ ScrollSpy.prototype.process = function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ var scrollHeight = this.getScrollHeight()
+ var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
+ var offsets = this.offsets
+ var targets = this.targets
+ var activeTarget = this.activeTarget
+ var i
+
+ if (this.scrollHeight != scrollHeight) {
+ this.refresh()
+ }
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+ }
+
+ if (activeTarget && scrollTop < offsets[0]) {
+ this.activeTarget = null
+ return this.clear()
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
+ && this.activate(targets[i])
+ }
+ }
+
+ ScrollSpy.prototype.activate = function (target) {
+ this.activeTarget = target
+
+ this.clear()
+
+ var selector = this.selector +
+ '[data-target="' + target + '"],' +
+ this.selector + '[href="' + target + '"]'
+
+ var active = $(selector)
+ .parents('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active
+ .closest('li.dropdown')
+ .addClass('active')
+ }
+
+ active.trigger('activate.bs.scrollspy')
+ }
+
+ ScrollSpy.prototype.clear = function () {
+ $(this.selector)
+ .parentsUntil(this.options.target, '.active')
+ .removeClass('active')
+ }
+
+
+ // SCROLLSPY PLUGIN DEFINITION
+ // ===========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.scrollspy')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = Plugin
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+
+ // SCROLLSPY NO CONFLICT
+ // =====================
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ // SCROLLSPY DATA-API
+ // ==================
+
+ $(window).on('load.bs.scrollspy.data-api', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ Plugin.call($spy, $spy.data())
+ })
+ })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.7
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ // jscs:disable requireDollarBeforejQueryAssignment
+ this.element = $(element)
+ // jscs:enable requireDollarBeforejQueryAssignment
+ }
+
+ Tab.VERSION = '3.3.7'
+
+ Tab.TRANSITION_DURATION = 150
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.data('target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var $previous = $ul.find('.active:last a')
+ var hideEvent = $.Event('hide.bs.tab', {
+ relatedTarget: $this[0]
+ })
+ var showEvent = $.Event('show.bs.tab', {
+ relatedTarget: $previous[0]
+ })
+
+ $previous.trigger(hideEvent)
+ $this.trigger(showEvent)
+
+ if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+ var $target = $(selector)
+
+ this.activate($this.closest('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $previous.trigger({
+ type: 'hidden.bs.tab',
+ relatedTarget: $this[0]
+ })
+ $this.trigger({
+ type: 'shown.bs.tab',
+ relatedTarget: $previous[0]
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', false)
+
+ element
+ .addClass('active')
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu').length) {
+ element
+ .closest('li.dropdown')
+ .addClass('active')
+ .end()
+ .find('[data-toggle="tab"]')
+ .attr('aria-expanded', true)
+ }
+
+ callback && callback()
+ }
+
+ $active.length && transition ?
+ $active
+ .one('bsTransitionEnd', next)
+ .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.tab
+
+ $.fn.tab = Plugin
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ var clickHandler = function (e) {
+ e.preventDefault()
+ Plugin.call($(this), 'show')
+ }
+
+ $(document)
+ .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+ .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.7
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // AFFIX CLASS DEFINITION
+ // ======================
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, Affix.DEFAULTS, options)
+
+ this.$target = $(this.options.target)
+ .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
+
+ this.$element = $(element)
+ this.affixed = null
+ this.unpin = null
+ this.pinnedOffset = null
+
+ this.checkPosition()
+ }
+
+ Affix.VERSION = '3.3.7'
+
+ Affix.RESET = 'affix affix-top affix-bottom'
+
+ Affix.DEFAULTS = {
+ offset: 0,
+ target: window
+ }
+
+ Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ var targetHeight = this.$target.height()
+
+ if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+ if (this.affixed == 'bottom') {
+ if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+ return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+ }
+
+ var initializing = this.affixed == null
+ var colliderTop = initializing ? scrollTop : position.top
+ var colliderHeight = initializing ? targetHeight : height
+
+ if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+ if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+ return false
+ }
+
+ Affix.prototype.getPinnedOffset = function () {
+ if (this.pinnedOffset) return this.pinnedOffset
+ this.$element.removeClass(Affix.RESET).addClass('affix')
+ var scrollTop = this.$target.scrollTop()
+ var position = this.$element.offset()
+ return (this.pinnedOffset = position.top - scrollTop)
+ }
+
+ Affix.prototype.checkPositionWithEventLoop = function () {
+ setTimeout($.proxy(this.checkPosition, this), 1)
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var height = this.$element.height()
+ var offset = this.options.offset
+ var offsetTop = offset.top
+ var offsetBottom = offset.bottom
+ var scrollHeight = Math.max($(document).height(), $(document.body).height())
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+ var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+ if (this.affixed != affix) {
+ if (this.unpin != null) this.$element.css('top', '')
+
+ var affixType = 'affix' + (affix ? '-' + affix : '')
+ var e = $.Event(affixType + '.bs.affix')
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+ this.$element
+ .removeClass(Affix.RESET)
+ .addClass(affixType)
+ .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+ }
+
+ if (affix == 'bottom') {
+ this.$element.offset({
+ top: scrollHeight - height - offsetBottom
+ })
+ }
+ }
+
+
+ // AFFIX PLUGIN DEFINITION
+ // =======================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.affix')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ var old = $.fn.affix
+
+ $.fn.affix = Plugin
+ $.fn.affix.Constructor = Affix
+
+
+ // AFFIX NO CONFLICT
+ // =================
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ // AFFIX DATA-API
+ // ==============
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ var data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+ if (data.offsetTop != null) data.offset.top = data.offsetTop
+
+ Plugin.call($spy, data)
+ })
+ })
+
+}(jQuery);
diff --git a/debian/missing-sources/two.js b/debian/missing-sources/two.js
new file mode 100644
index 00000000..859d3b41
--- /dev/null
+++ b/debian/missing-sources/two.js
@@ -0,0 +1,10244 @@
+/**
+ * two.js
+ * a two-dimensional drawing api meant for modern browsers. It is renderer
+ * agnostic enabling the same api for rendering in multiple contexts: webgl,
+ * canvas2d, and svg.
+ *
+ * Copyright (c) 2012 - 2017 jonobr1 / http://jonobr1.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+this.Two = (function(previousTwo) {
+
+ var root = typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : null;
+ var toString = Object.prototype.toString;
+ var _ = {
+ // http://underscorejs.org/ • 1.8.3
+ _indexAmount: 0,
+ natural: {
+ slice: Array.prototype.slice,
+ indexOf: Array.prototype.indexOf,
+ keys: Object.keys,
+ bind: Function.prototype.bind,
+ create: Object.create
+ },
+ identity: function(value) {
+ return value;
+ },
+ isArguments: function(obj) {
+ return toString.call(obj) === '[object Arguments]';
+ },
+ isFunction: function(obj) {
+ return toString.call(obj) === '[object Function]';
+ },
+ isString: function(obj) {
+ return toString.call(obj) === '[object String]';
+ },
+ isNumber: function(obj) {
+ return toString.call(obj) === '[object Number]';
+ },
+ isDate: function(obj) {
+ return toString.call(obj) === '[object Date]';
+ },
+ isRegExp: function(obj) {
+ return toString.call(obj) === '[object RegExp]';
+ },
+ isError: function(obj) {
+ return toString.call(obj) === '[object Error]';
+ },
+ isFinite: function(obj) {
+ return isFinite(obj) && !isNaN(parseFloat(obj));
+ },
+ isNaN: function(obj) {
+ return _.isNumber(obj) && obj !== +obj;
+ },
+ isBoolean: function(obj) {
+ return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+ },
+ isNull: function(obj) {
+ return obj === null;
+ },
+ isUndefined: function(obj) {
+ return obj === void 0;
+ },
+ isEmpty: function(obj) {
+ if (obj == null) return true;
+ if (isArrayLike && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+ return _.keys(obj).length === 0;
+ },
+ isElement: function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ },
+ isArray: Array.isArray || function(obj) {
+ return toString.call(obj) === '[object Array]';
+ },
+ isObject: function(obj) {
+ var type = typeof obj;
+ return type === 'function' || type === 'object' && !!obj;
+ },
+ toArray: function(obj) {
+ if (!obj) {
+ return [];
+ }
+ if (_.isArray(obj)) {
+ return slice.call(obj);
+ }
+ if (isArrayLike(obj)) {
+ return _.map(obj, _.identity);
+ }
+ return _.values(obj);
+ },
+ range: function(start, stop, step) {
+ if (stop == null) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = step || 1;
+
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
+ var range = Array(length);
+
+ for (var idx = 0; idx < length; idx++, start += step) {
+ range[idx] = start;
+ }
+
+ return range;
+ },
+ indexOf: function(list, item) {
+ if (!!_.natural.indexOf) {
+ return _.natural.indexOf.call(list, item);
+ }
+ for (var i = 0; i < list.length; i++) {
+ if (list[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ has: function(obj, key) {
+ return obj != null && hasOwnProperty.call(obj, key);
+ },
+ bind: function(func, ctx) {
+ var natural = _.natural.bind;
+ if (natural && func.bind === natural) {
+ return natural.apply(func, slice.call(arguments, 1));
+ }
+ var args = slice.call(arguments, 2);
+ return function() {
+ func.apply(ctx, args);
+ };
+ },
+ extend: function(base) {
+ var sources = slice.call(arguments, 1);
+ for (var i = 0; i < sources.length; i++) {
+ var obj = sources[i];
+ for (var k in obj) {
+ base[k] = obj[k];
+ }
+ }
+ return base;
+ },
+ defaults: function(base) {
+ var sources = slice.call(arguments, 1);
+ for (var i = 0; i < sources.length; i++) {
+ var obj = sources[i];
+ for (var k in obj) {
+ if (base[k] === void 0) {
+ base[k] = obj[k];
+ }
+ }
+ }
+ return base;
+ },
+ keys: function(obj) {
+ if (!_.isObject(obj)) {
+ return [];
+ }
+ if (_.natural.keys) {
+ return _.natural.keys(obj);
+ }
+ var keys = [];
+ for (var k in obj) {
+ if (_.has(obj, k)) {
+ keys.push(k);
+ }
+ }
+ return keys;
+ },
+ values: function(obj) {
+ var keys = _.keys(obj);
+ var values = [];
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
+ values.push(obj[k]);
+ }
+ return values;
+ },
+ each: function(obj, iteratee, context) {
+ var ctx = context || this;
+ var keys = !isArrayLike(obj) && _.keys(obj);
+ var length = (keys || obj).length;
+ for (var i = 0; i < length; i++) {
+ var k = keys ? keys[i] : i;
+ iteratee.call(ctx, obj[k], k, obj);
+ }
+ return obj;
+ },
+ map: function(obj, iteratee, context) {
+ var ctx = context || this;
+ var keys = !isArrayLike(obj) && _.keys(obj);
+ var length = (keys || obj).length;
+ var result = [];
+ for (var i = 0; i < length; i++) {
+ var k = keys ? keys[i] : i;
+ result[i] = iteratee.call(ctx, obj[k], k, obj);
+ }
+ return result;
+ },
+ once: function(func) {
+ var init = false;
+ return function() {
+ if (!!init) {
+ return func;
+ }
+ init = true;
+ return func.apply(this, arguments);
+ }
+ },
+ after: function(times, func) {
+ return function() {
+ while (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ }
+ },
+ uniqueId: function(prefix) {
+ var id = ++_._indexAmount + '';
+ return prefix ? prefix + id : id;
+ }
+ };
+
+ /**
+ * Constants
+ */
+
+ var sin = Math.sin,
+ cos = Math.cos,
+ atan2 = Math.atan2,
+ sqrt = Math.sqrt,
+ round = Math.round,
+ abs = Math.abs,
+ PI = Math.PI,
+ TWO_PI = PI * 2,
+ HALF_PI = PI / 2,
+ pow = Math.pow,
+ min = Math.min,
+ max = Math.max;
+
+ /**
+ * Localized variables
+ */
+
+ var count = 0;
+ var slice = _.natural.slice;
+ var perf = ((root.performance && root.performance.now) ? root.performance : Date);
+ var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+ var getLength = function(obj) {
+ return obj == null ? void 0 : obj['length'];
+ };
+ var isArrayLike = function(collection) {
+ var length = getLength(collection);
+ return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+ };
+
+ /**
+ * Cross browser dom events.
+ */
+ var dom = {
+
+ temp: (root.document ? root.document.createElement('div') : {}),
+
+ hasEventListeners: _.isFunction(root.addEventListener),
+
+ bind: function(elem, event, func, bool) {
+ if (this.hasEventListeners) {
+ elem.addEventListener(event, func, !!bool);
+ } else {
+ elem.attachEvent('on' + event, func);
+ }
+ return dom;
+ },
+
+ unbind: function(elem, event, func, bool) {
+ if (dom.hasEventListeners) {
+ elem.removeEventListeners(event, func, !!bool);
+ } else {
+ elem.detachEvent('on' + event, func);
+ }
+ return dom;
+ },
+
+ getRequestAnimationFrame: function() {
+
+ var lastTime = 0;
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
+ var request = root.requestAnimationFrame, cancel;
+
+ if(!request) {
+ for (var i = 0; i < vendors.length; i++) {
+ request = root[vendors[i] + 'RequestAnimationFrame'] || request;
+ cancel = root[vendors[i] + 'CancelAnimationFrame']
+ || root[vendors[i] + 'CancelRequestAnimationFrame'] || cancel;
+ }
+
+ request = request || function(callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = root.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ // cancel = cancel || function(id) {
+ // clearTimeout(id);
+ // };
+ }
+
+ request.init = _.once(loop);
+
+ return request;
+
+ }
+
+ };
+
+ /**
+ * @class
+ */
+ var Two = root.Two = function(options) {
+
+ // Determine what Renderer to use and setup a scene.
+
+ var params = _.defaults(options || {}, {
+ fullscreen: false,
+ width: 640,
+ height: 480,
+ type: Two.Types.svg,
+ autostart: false
+ });
+
+ _.each(params, function(v, k) {
+ if (k === 'fullscreen' || k === 'autostart') {
+ return;
+ }
+ this[k] = v;
+ }, this);
+
+ // Specified domElement overrides type declaration only if the element does not support declared renderer type.
+ if (_.isElement(params.domElement)) {
+ var tagName = params.domElement.tagName.toLowerCase();
+ // TODO: Reconsider this if statement's logic.
+ if (!/^(CanvasRenderer-canvas|WebGLRenderer-canvas|SVGRenderer-svg)$/.test(this.type+'-'+tagName)) {
+ this.type = Two.Types[tagName];
+ }
+ }
+
+ this.renderer = new Two[this.type](this);
+ Two.Utils.setPlaying.call(this, params.autostart);
+ this.frameCount = 0;
+
+ if (params.fullscreen) {
+
+ var fitted = _.bind(fitToWindow, this);
+ _.extend(document.body.style, {
+ overflow: 'hidden',
+ margin: 0,
+ padding: 0,
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ position: 'fixed'
+ });
+ _.extend(this.renderer.domElement.style, {
+ display: 'block',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ position: 'fixed'
+ });
+ dom.bind(root, 'resize', fitted);
+ fitted();
+
+
+ } else if (!_.isElement(params.domElement)) {
+
+ this.renderer.setSize(params.width, params.height, this.ratio);
+ this.width = params.width;
+ this.height = params.height;
+
+ }
+
+ this.scene = this.renderer.scene;
+
+ Two.Instances.push(this);
+ raf.init();
+
+ };
+
+ _.extend(Two, {
+
+ /**
+ * Access to root in other files.
+ */
+
+ root: root,
+
+ /**
+ * Primitive
+ */
+
+ Array: root.Float32Array || Array,
+
+ Types: {
+ webgl: 'WebGLRenderer',
+ svg: 'SVGRenderer',
+ canvas: 'CanvasRenderer'
+ },
+
+ Version: 'v0.7.0',
+
+ Identifier: 'two_',
+
+ Properties: {
+ hierarchy: 'hierarchy',
+ demotion: 'demotion'
+ },
+
+ Events: {
+ play: 'play',
+ pause: 'pause',
+ update: 'update',
+ render: 'render',
+ resize: 'resize',
+ change: 'change',
+ remove: 'remove',
+ insert: 'insert',
+ order: 'order',
+ load: 'load'
+ },
+
+ Commands: {
+ move: 'M',
+ line: 'L',
+ curve: 'C',
+ close: 'Z'
+ },
+
+ Resolution: 8,
+
+ Instances: [],
+
+ noConflict: function() {
+ root.Two = previousTwo;
+ return this;
+ },
+
+ uniqueId: function() {
+ var id = count;
+ count++;
+ return id;
+ },
+
+ Utils: _.extend(_, {
+
+ performance: perf,
+
+ defineProperty: function(property) {
+
+ var object = this;
+ var secret = '_' + property;
+ var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+ Object.defineProperty(object, property, {
+ enumerable: true,
+ get: function() {
+ return this[secret];
+ },
+ set: function(v) {
+ this[secret] = v;
+ this[flag] = true;
+ }
+ });
+
+ },
+
+ /**
+ * Release an arbitrary class' events from the two.js corpus and recurse
+ * through its children and or vertices.
+ */
+ release: function(obj) {
+
+ if (!_.isObject(obj)) {
+ return;
+ }
+
+ if (_.isFunction(obj.unbind)) {
+ obj.unbind();
+ }
+
+ if (obj.vertices) {
+ if (_.isFunction(obj.vertices.unbind)) {
+ obj.vertices.unbind();
+ }
+ _.each(obj.vertices, function(v) {
+ if (_.isFunction(v.unbind)) {
+ v.unbind();
+ }
+ });
+ }
+
+ if (obj.children) {
+ _.each(obj.children, function(obj) {
+ Two.Utils.release(obj);
+ });
+ }
+
+ },
+
+ xhr: function(path, callback) {
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', path);
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4 && xhr.status === 200) {
+ callback(xhr.responseText);
+ }
+ };
+
+ xhr.send();
+ return xhr;
+
+ },
+
+ Curve: {
+
+ CollinearityEpsilon: pow(10, -30),
+
+ RecursionLimit: 16,
+
+ CuspLimit: 0,
+
+ Tolerance: {
+ distance: 0.25,
+ angle: 0,
+ epsilon: 0.01
+ },
+
+ // Lookup tables for abscissas and weights with values for n = 2 .. 16.
+ // As values are symmetric, only store half of them and adapt algorithm
+ // to factor in symmetry.
+ 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]
+ ],
+
+ 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]
+ ]
+
+ },
+
+ /**
+ * Account for high dpi rendering.
+ * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
+ */
+
+ devicePixelRatio: root.devicePixelRatio || 1,
+
+ getBackingStoreRatio: function(ctx) {
+ return ctx.webkitBackingStorePixelRatio ||
+ ctx.mozBackingStorePixelRatio ||
+ ctx.msBackingStorePixelRatio ||
+ ctx.oBackingStorePixelRatio ||
+ ctx.backingStorePixelRatio || 1;
+ },
+
+ getRatio: function(ctx) {
+ return Two.Utils.devicePixelRatio / getBackingStoreRatio(ctx);
+ },
+
+ /**
+ * Properly defer play calling until after all objects
+ * have been updated with their newest styles.
+ */
+ setPlaying: function(b) {
+
+ this.playing = !!b;
+ return this;
+
+ },
+
+ /**
+ * Return the computed matrix of a nested object.
+ * TODO: Optimize traversal.
+ */
+ getComputedMatrix: function(object, matrix) {
+
+ matrix = (matrix && matrix.identity()) || new Two.Matrix();
+ var parent = object, matrices = [];
+
+ while (parent && parent._matrix) {
+ matrices.push(parent._matrix);
+ parent = parent.parent;
+ }
+
+ matrices.reverse();
+
+ _.each(matrices, function(m) {
+
+ var e = m.elements;
+ matrix.multiply(
+ e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]);
+
+ });
+
+ return matrix;
+
+ },
+
+ deltaTransformPoint: function(matrix, x, y) {
+
+ var dx = x * matrix.a + y * matrix.c + 0;
+ var dy = x * matrix.b + y * matrix.d + 0;
+
+ return new Two.Vector(dx, dy);
+
+ },
+
+ /**
+ * https://gist.github.com/2052247
+ */
+ decomposeMatrix: function(matrix) {
+
+ // calculate delta transform point
+ var px = Two.Utils.deltaTransformPoint(matrix, 0, 1);
+ var py = Two.Utils.deltaTransformPoint(matrix, 1, 0);
+
+ // calculate skew
+ var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
+ var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
+
+ return {
+ translateX: matrix.e,
+ translateY: matrix.f,
+ scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
+ scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
+ skewX: skewX,
+ skewY: skewY,
+ rotation: skewX // rotation is the same as skew x
+ };
+
+ },
+
+ /**
+ * Walk through item properties and pick the ones of interest.
+ * Will try to resolve styles applied via CSS
+ *
+ * TODO: Reverse calculate `Two.Gradient`s for fill / stroke
+ * of any given path.
+ */
+ applySvgAttributes: function(node, elem) {
+
+ var attributes = {}, styles = {}, i, key, value, attr;
+
+ // Not available in non browser environments
+ if (getComputedStyle) {
+ // Convert CSSStyleDeclaration to a normal object
+ var computedStyles = getComputedStyle(node);
+ i = computedStyles.length;
+
+ while (i--) {
+ key = computedStyles[i];
+ value = computedStyles[key];
+ // Gecko returns undefined for unset properties
+ // Webkit returns the default value
+ if (value !== undefined) {
+ styles[key] = value;
+ }
+ }
+ }
+
+ // Convert NodeMap to a normal object
+ i = node.attributes.length;
+ while (i--) {
+ attr = node.attributes[i];
+ attributes[attr.nodeName] = attr.value;
+ }
+
+ // Getting the correct opacity is a bit tricky, since SVG path elements don't
+ // support opacity as an attribute, but you can apply it via CSS.
+ // So we take the opacity and set (stroke/fill)-opacity to the same value.
+ if (!_.isUndefined(styles.opacity)) {
+ styles['stroke-opacity'] = styles.opacity;
+ styles['fill-opacity'] = styles.opacity;
+ }
+
+ // Merge attributes and applied styles (attributes take precedence)
+ _.extend(styles, attributes);
+
+ // Similarly visibility is influenced by the value of both display and visibility.
+ // Calculate a unified value here which defaults to `true`.
+ styles.visible = !(_.isUndefined(styles.display) && styles.display === 'none')
+ || (_.isUndefined(styles.visibility) && styles.visibility === 'hidden');
+
+ // Now iterate the whole thing
+ for (key in styles) {
+ value = styles[key];
+
+ switch (key) {
+ case 'transform':
+ // TODO: Check this out https://github.com/paperjs/paper.js/blob/master/src/svg/SVGImport.js#L313
+ if (value === 'none') break;
+ var m = node.getCTM ? node.getCTM() : null;
+
+ // Might happen when transform string is empty or not valid.
+ if (m === null) break;
+
+ // // Option 1: edit the underlying matrix and don't force an auto calc.
+ // var m = node.getCTM();
+ // elem._matrix.manual = true;
+ // elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
+
+ // Option 2: Decompose and infer Two.js related properties.
+ var transforms = Two.Utils.decomposeMatrix(node.getCTM());
+
+ elem.translation.set(transforms.translateX, transforms.translateY);
+ elem.rotation = transforms.rotation;
+ // Warning: Two.js elements only support uniform scalars...
+ elem.scale = transforms.scaleX;
+
+ var x = parseFloat((styles.x + '').replace('px'));
+ var y = parseFloat((styles.y + '').replace('px'));
+
+ // Override based on attributes.
+ if (x) {
+ elem.translation.x = x;
+ }
+
+ if (y) {
+ elem.translation.y = y;
+ }
+
+ break;
+ case 'visible':
+ elem.visible = value;
+ break;
+ case 'stroke-linecap':
+ elem.cap = value;
+ break;
+ case 'stroke-linejoin':
+ elem.join = value;
+ break;
+ case 'stroke-miterlimit':
+ elem.miter = value;
+ break;
+ case 'stroke-width':
+ elem.linewidth = parseFloat(value);
+ break;
+ case 'stroke-opacity':
+ case 'fill-opacity':
+ case 'opacity':
+ elem.opacity = parseFloat(value);
+ break;
+ case 'fill':
+ case 'stroke':
+ if (/url\(\#.*\)/i.test(value)) {
+ elem[key] = this.getById(
+ value.replace(/url\(\#(.*)\)/i, '$1'));
+ } else {
+ elem[key] = (value === 'none') ? 'transparent' : value;
+ }
+ break;
+ case 'id':
+ elem.id = value;
+ break;
+ case 'class':
+ elem.classList = value.split(' ');
+ break;
+ }
+ }
+
+ return elem;
+
+ },
+
+ /**
+ * Read any number of SVG node types and create Two equivalents of them.
+ */
+ read: {
+
+ svg: function() {
+ return Two.Utils.read.g.apply(this, arguments);
+ },
+
+ g: function(node) {
+
+ var group = new Two.Group();
+
+ // Switched up order to inherit more specific styles
+ Two.Utils.applySvgAttributes.call(this, node, group);
+
+ for (var i = 0, l = node.childNodes.length; i < l; i++) {
+ var n = node.childNodes[i];
+ var tag = n.nodeName;
+ if (!tag) return;
+
+ var tagName = tag.replace(/svg\:/ig, '').toLowerCase();
+
+ if (tagName in Two.Utils.read) {
+ var o = Two.Utils.read[tagName].call(group, n);
+ group.add(o);
+ }
+ }
+
+ return group;
+
+ },
+
+ polygon: function(node, open) {
+
+ var points = node.getAttribute('points');
+
+ var verts = [];
+ points.replace(/(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g, function(match, p1, p2) {
+ verts.push(new Two.Anchor(parseFloat(p1), parseFloat(p2)));
+ });
+
+ var poly = new Two.Path(verts, !open).noStroke();
+ poly.fill = 'black';
+
+ return Two.Utils.applySvgAttributes.call(this, node, poly);
+
+ },
+
+ polyline: function(node) {
+ return Two.Utils.read.polygon.call(this, node, true);
+ },
+
+ path: function(node) {
+
+ var path = node.getAttribute('d');
+
+ // Create a Two.Path from the paths.
+
+ var coord = new Two.Anchor();
+ var control, coords;
+ var closed = false, relative = false;
+ var commands = path.match(/[a-df-z][^a-df-z]*/ig);
+ var last = commands.length - 1;
+
+ // Split up polybeziers
+
+ _.each(commands.slice(0), function(command, i) {
+
+ var type = command[0];
+ var lower = type.toLowerCase();
+ var items = command.slice(1).trim().split(/[\s,]+|(?=\s?[+\-])/);
+ var pre, post, result = [], bin;
+
+ if (i <= 0) {
+ commands = [];
+ }
+
+ switch (lower) {
+ case 'h':
+ case 'v':
+ if (items.length > 1) {
+ bin = 1;
+ }
+ break;
+ case 'm':
+ case 'l':
+ case 't':
+ if (items.length > 2) {
+ bin = 2;
+ }
+ break;
+ case 's':
+ case 'q':
+ if (items.length > 4) {
+ bin = 4;
+ }
+ break;
+ case 'c':
+ if (items.length > 6) {
+ bin = 6;
+ }
+ break;
+ case 'a':
+ // TODO: Handle Ellipses
+ break;
+ }
+
+ if (bin) {
+
+ for (var j = 0, l = items.length, times = 0; j < l; j+=bin) {
+
+ var ct = type;
+ if (times > 0) {
+
+ switch (type) {
+ case 'm':
+ ct = 'l';
+ break;
+ case 'M':
+ ct = 'L';
+ break;
+ }
+
+ }
+
+ result.push([ct].concat(items.slice(j, j + bin)).join(' '));
+ times++;
+
+ }
+
+ commands = Array.prototype.concat.apply(commands, result);
+
+ } else {
+
+ commands.push(command);
+
+ }
+
+ });
+
+ // Create the vertices for our Two.Path
+
+ var points = [];
+ _.each(commands, function(command, i) {
+
+ var result, x, y;
+ var type = command[0];
+ var lower = type.toLowerCase();
+
+ coords = command.slice(1).trim();
+ coords = coords.replace(/(-?\d+(?:\.\d*)?)[eE]([+\-]?\d+)/g, function(match, n1, n2) {
+ return parseFloat(n1) * pow(10, n2);
+ });
+ coords = coords.split(/[\s,]+|(?=\s?[+\-])/);
+ relative = type === lower;
+
+ var x1, y1, x2, y2, x3, y3, x4, y4, reflection;
+
+ switch (lower) {
+
+ case 'z':
+ if (i >= last) {
+ closed = true;
+ } else {
+ x = coord.x;
+ y = coord.y;
+ result = new Two.Anchor(
+ x, y,
+ undefined, undefined,
+ undefined, undefined,
+ Two.Commands.close
+ );
+ }
+ break;
+
+ case 'm':
+ case 'l':
+
+ x = parseFloat(coords[0]);
+ y = parseFloat(coords[1]);
+
+ result = new Two.Anchor(
+ x, y,
+ undefined, undefined,
+ undefined, undefined,
+ lower === 'm' ? Two.Commands.move : Two.Commands.line
+ );
+
+ if (relative) {
+ result.addSelf(coord);
+ }
+
+ // result.controls.left.copy(result);
+ // result.controls.right.copy(result);
+
+ coord = result;
+ break;
+
+ case 'h':
+ case 'v':
+
+ var a = lower === 'h' ? 'x' : 'y';
+ var b = a === 'x' ? 'y' : 'x';
+
+ result = new Two.Anchor(
+ undefined, undefined,
+ undefined, undefined,
+ undefined, undefined,
+ Two.Commands.line
+ );
+ result[a] = parseFloat(coords[0]);
+ result[b] = coord[b];
+
+ if (relative) {
+ result[a] += coord[a];
+ }
+
+ // result.controls.left.copy(result);
+ // result.controls.right.copy(result);
+
+ coord = result;
+ break;
+
+ case 'c':
+ case 's':
+
+ x1 = coord.x;
+ y1 = coord.y;
+
+ if (!control) {
+ control = new Two.Vector();//.copy(coord);
+ }
+
+ if (lower === 'c') {
+
+ x2 = parseFloat(coords[0]);
+ y2 = parseFloat(coords[1]);
+ x3 = parseFloat(coords[2]);
+ y3 = parseFloat(coords[3]);
+ x4 = parseFloat(coords[4]);
+ y4 = parseFloat(coords[5]);
+
+ } else {
+
+ // Calculate reflection control point for proper x2, y2
+ // inclusion.
+
+ reflection = getReflection(coord, control, relative);
+
+ x2 = reflection.x;
+ y2 = reflection.y;
+ x3 = parseFloat(coords[0]);
+ y3 = parseFloat(coords[1]);
+ x4 = parseFloat(coords[2]);
+ y4 = parseFloat(coords[3]);
+
+ }
+
+ if (relative) {
+ x2 += x1;
+ y2 += y1;
+ x3 += x1;
+ y3 += y1;
+ x4 += x1;
+ y4 += y1;
+ }
+
+ if (!_.isObject(coord.controls)) {
+ Two.Anchor.AppendCurveProperties(coord);
+ }
+
+ coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+ result = new Two.Anchor(
+ x4, y4,
+ x3 - x4, y3 - y4,
+ undefined, undefined,
+ Two.Commands.curve
+ );
+
+ coord = result;
+ control = result.controls.left;
+
+ break;
+
+ case 't':
+ case 'q':
+
+ x1 = coord.x;
+ y1 = coord.y;
+
+ if (!control) {
+ control = new Two.Vector();//.copy(coord);
+ }
+
+ if (control.isZero()) {
+ x2 = x1;
+ y2 = y1;
+ } else {
+ x2 = control.x;
+ y1 = control.y;
+ }
+
+ if (lower === 'q') {
+
+ x3 = parseFloat(coords[0]);
+ y3 = parseFloat(coords[1]);
+ x4 = parseFloat(coords[1]);
+ y4 = parseFloat(coords[2]);
+
+ } else {
+
+ reflection = getReflection(coord, control, relative);
+
+ x3 = reflection.x;
+ y3 = reflection.y;
+ x4 = parseFloat(coords[0]);
+ y4 = parseFloat(coords[1]);
+
+ }
+
+ if (relative) {
+ x2 += x1;
+ y2 += y1;
+ x3 += x1;
+ y3 += y1;
+ x4 += x1;
+ y4 += y1;
+ }
+
+ if (!_.isObject(coord.controls)) {
+ Two.Anchor.AppendCurveProperties(coord);
+ }
+
+ coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+ result = new Two.Anchor(
+ x4, y4,
+ x3 - x4, y3 - y4,
+ undefined, undefined,
+ Two.Commands.curve
+ );
+
+ coord = result;
+ control = result.controls.left;
+
+ break;
+
+ case 'a':
+
+ // throw new Two.Utils.Error('not yet able to interpret Elliptical Arcs.');
+ x1 = coord.x;
+ y1 = coord.y;
+
+ var rx = parseFloat(coords[0]);
+ var ry = parseFloat(coords[1]);
+ var xAxisRotation = parseFloat(coords[2]) * Math.PI / 180;
+ var largeArcFlag = parseFloat(coords[3]);
+ var sweepFlag = parseFloat(coords[4]);
+
+ x4 = parseFloat(coords[5]);
+ y4 = parseFloat(coords[6]);
+
+ if (relative) {
+ x4 += x1;
+ y4 += y1;
+ }
+
+ // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+
+ // Calculate midpoint mx my
+ var mx = (x4 - x1) / 2;
+ var my = (y4 - y1) / 2;
+
+ // Calculate x1' y1' F.6.5.1
+ var _x = mx * Math.cos(xAxisRotation) + my * Math.sin(xAxisRotation);
+ var _y = - mx * Math.sin(xAxisRotation) + my * Math.cos(xAxisRotation);
+
+ var rx2 = rx * rx;
+ var ry2 = ry * ry;
+ var _x2 = _x * _x;
+ var _y2 = _y * _y;
+
+ // adjust radii
+ var l = _x2 / rx2 + _y2 / ry2;
+ if (l > 1) {
+ rx *= Math.sqrt(l);
+ ry *= Math.sqrt(l);
+ }
+
+ var amp = Math.sqrt((rx2 * ry2 - rx2 * _y2 - ry2 * _x2) / (rx2 * _y2 + ry2 * _x2));
+
+ if (_.isNaN(amp)) {
+ amp = 0;
+ } else if (largeArcFlag != sweepFlag && amp > 0) {
+ amp *= -1;
+ }
+
+ // Calculate cx' cy' F.6.5.2
+ var _cx = amp * rx * _y / ry;
+ var _cy = - amp * ry * _x / rx;
+
+ // Calculate cx cy F.6.5.3
+ var cx = _cx * Math.cos(xAxisRotation) - _cy * Math.sin(xAxisRotation) + (x1 + x4) / 2;
+ var cy = _cx * Math.sin(xAxisRotation) + _cy * Math.cos(xAxisRotation) + (y1 + y4) / 2;
+
+ // vector magnitude
+ var m = function(v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)); }
+ // ratio between two vectors
+ var r = function(u, v) { return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v)) }
+ // angle between two vectors
+ var a = function(u, v) { return (u[0] * v[1] < u[1] * v[0] ? - 1 : 1) * Math.acos(r(u,v)); }
+
+ // Calculate theta1 and delta theta F.6.5.4 + F.6.5.5
+ var t1 = a([1, 0], [(_x - _cx) / rx, (_y - _cy) / ry]);
+ var u = [(_x - _cx) / rx, (_y - _cy) / ry];
+ var v = [( - _x - _cx) / rx, ( - _y - _cy) / ry];
+ var dt = a(u, v);
+
+ if (r(u, v) <= -1) dt = Math.PI;
+ if (r(u, v) >= 1) dt = 0;
+
+ // F.6.5.6
+ if (largeArcFlag) {
+ dt = mod(dt, Math.PI * 2);
+ }
+
+ if (sweepFlag && dt > 0) {
+ dt -= Math.PI * 2;
+ }
+
+ var length = Two.Resolution;
+
+ // Save a projection of our rotation and translation to apply
+ // to the set of points.
+ var projection = new Two.Matrix()
+ .translate(cx, cy)
+ .rotate(xAxisRotation);
+
+ // Create a resulting array of Two.Anchor's to export to the
+ // the path.
+ result = _.map(_.range(length), function(i) {
+ var pct = 1 - (i / (length - 1));
+ var theta = pct * dt + t1;
+ var x = rx * Math.cos(theta);
+ var y = ry * Math.sin(theta);
+ var projected = projection.multiply(x, y, 1);
+ return new Two.Anchor(projected.x, projected.y, false, false, false, false, Two.Commands.line);;
+ });
+
+ result.push(new Two.Anchor(x4, y4, false, false, false, false, Two.Commands.line));
+
+ coord = result[result.length - 1];
+ control = coord.controls.left;
+
+ break;
+
+ }
+
+ if (result) {
+ if (_.isArray(result)) {
+ points = points.concat(result);
+ } else {
+ points.push(result);
+ }
+ }
+
+ });
+
+ if (points.length <= 1) {
+ return;
+ }
+
+ var path = new Two.Path(points, closed, undefined, true).noStroke();
+ path.fill = 'black';
+
+ var rect = path.getBoundingClientRect(true);
+
+ // Center objects to stay consistent
+ // with the rest of the Two.js API.
+ rect.centroid = {
+ x: rect.left + rect.width / 2,
+ y: rect.top + rect.height / 2
+ };
+
+ _.each(path.vertices, function(v) {
+ v.subSelf(rect.centroid);
+ });
+
+ path.translation.addSelf(rect.centroid);
+
+ return Two.Utils.applySvgAttributes.call(this, node, path);
+
+ },
+
+ circle: function(node) {
+
+ var x = parseFloat(node.getAttribute('cx'));
+ var y = parseFloat(node.getAttribute('cy'));
+ var r = parseFloat(node.getAttribute('r'));
+
+ var circle = new Two.Circle(x, y, r).noStroke();
+ circle.fill = 'black';
+
+ return Two.Utils.applySvgAttributes.call(this, node, circle);
+
+ },
+
+ ellipse: function(node) {
+
+ var x = parseFloat(node.getAttribute('cx'));
+ var y = parseFloat(node.getAttribute('cy'));
+ var width = parseFloat(node.getAttribute('rx'));
+ var height = parseFloat(node.getAttribute('ry'));
+
+ var ellipse = new Two.Ellipse(x, y, width, height).noStroke();
+ ellipse.fill = 'black';
+
+ return Two.Utils.applySvgAttributes.call(this, node, ellipse);
+
+ },
+
+ rect: function(node) {
+
+ var x = parseFloat(node.getAttribute('x')) || 0;
+ var y = parseFloat(node.getAttribute('y')) || 0;
+ var width = parseFloat(node.getAttribute('width'));
+ var height = parseFloat(node.getAttribute('height'));
+
+ var w2 = width / 2;
+ var h2 = height / 2;
+
+ var rect = new Two.Rectangle(x + w2, y + h2, width, height)
+ .noStroke();
+ rect.fill = 'black';
+
+ return Two.Utils.applySvgAttributes.call(this, node, rect);
+
+ },
+
+ line: function(node) {
+
+ var x1 = parseFloat(node.getAttribute('x1'));
+ var y1 = parseFloat(node.getAttribute('y1'));
+ var x2 = parseFloat(node.getAttribute('x2'));
+ var y2 = parseFloat(node.getAttribute('y2'));
+
+ var line = new Two.Line(x1, y1, x2, y2).noFill();
+
+ return Two.Utils.applySvgAttributes.call(this, node, line);
+
+ },
+
+ lineargradient: function(node) {
+
+ var x1 = parseFloat(node.getAttribute('x1'));
+ var y1 = parseFloat(node.getAttribute('y1'));
+ var x2 = parseFloat(node.getAttribute('x2'));
+ var y2 = parseFloat(node.getAttribute('y2'));
+
+ var ox = (x2 + x1) / 2;
+ var oy = (y2 + y1) / 2;
+
+ var stops = [];
+ for (var i = 0; i < node.children.length; i++) {
+
+ var child = node.children[i];
+
+ var offset = parseFloat(child.getAttribute('offset'));
+ var color = child.getAttribute('stop-color');
+ var opacity = child.getAttribute('stop-opacity');
+ var style = child.getAttribute('style');
+
+ if (_.isNull(color)) {
+ var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
+ color = matches && matches.length > 1 ? matches[1] : undefined;
+ }
+
+ if (_.isNull(opacity)) {
+ var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
+ opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+ }
+
+ stops.push(new Two.Gradient.Stop(offset, color, opacity));
+
+ }
+
+ var gradient = new Two.LinearGradient(x1 - ox, y1 - oy, x2 - ox,
+ y2 - oy, stops);
+
+ return Two.Utils.applySvgAttributes.call(this, node, gradient);
+
+ },
+
+ radialgradient: function(node) {
+
+ var cx = parseFloat(node.getAttribute('cx')) || 0;
+ var cy = parseFloat(node.getAttribute('cy')) || 0;
+ var r = parseFloat(node.getAttribute('r'));
+
+ var fx = parseFloat(node.getAttribute('fx'));
+ var fy = parseFloat(node.getAttribute('fy'));
+
+ if (_.isNaN(fx)) {
+ fx = cx;
+ }
+
+ if (_.isNaN(fy)) {
+ fy = cy;
+ }
+
+ var ox = Math.abs(cx + fx) / 2;
+ var oy = Math.abs(cy + fy) / 2;
+
+ var stops = [];
+ for (var i = 0; i < node.children.length; i++) {
+
+ var child = node.children[i];
+
+ var offset = parseFloat(child.getAttribute('offset'));
+ var color = child.getAttribute('stop-color');
+ var opacity = child.getAttribute('stop-opacity');
+ var style = child.getAttribute('style');
+
+ if (_.isNull(color)) {
+ var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
+ color = matches && matches.length > 1 ? matches[1] : undefined;
+ }
+
+ if (_.isNull(opacity)) {
+ var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
+ opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+ }
+
+ stops.push(new Two.Gradient.Stop(offset, color, opacity));
+
+ }
+
+ var gradient = new Two.RadialGradient(cx - ox, cy - oy, r,
+ stops, fx - ox, fy - oy);
+
+ return Two.Utils.applySvgAttributes.call(this, node, gradient);
+
+ }
+
+ },
+
+ /**
+ * Given 2 points (a, b) and corresponding control point for each
+ * return an array of points that represent points plotted along
+ * the curve. Number points determined by limit.
+ */
+ subdivide: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+ limit = limit || Two.Utils.Curve.RecursionLimit;
+ var amount = limit + 1;
+
+ // TODO: Issue 73
+ // Don't recurse if the end points are identical
+ if (x1 === x4 && y1 === y4) {
+ return [new Two.Anchor(x4, y4)];
+ }
+
+ return _.map(_.range(0, amount), function(i) {
+
+ var t = i / amount;
+ var x = getPointOnCubicBezier(t, x1, x2, x3, x4);
+ var y = getPointOnCubicBezier(t, y1, y2, y3, y4);
+
+ return new Two.Anchor(x, y);
+
+ });
+
+ },
+
+ getPointOnCubicBezier: function(t, a, b, c, d) {
+ var k = 1 - t;
+ return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) +
+ (t * t * t * d);
+ },
+
+ /**
+ * Given 2 points (a, b) and corresponding control point for each
+ * return a float that represents the length of the curve using
+ * Gauss-Legendre algorithm. Limit iterations of calculation by `limit`.
+ */
+ getCurveLength: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+ // TODO: Better / fuzzier equality check
+ // Linear calculation
+ if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) {
+ var dx = x4 - x1;
+ var dy = y4 - y1;
+ return sqrt(dx * dx + dy * dy);
+ }
+
+ // Calculate the coefficients of a Bezier derivative.
+ var ax = 9 * (x2 - x3) + 3 * (x4 - x1),
+ bx = 6 * (x1 + x3) - 12 * x2,
+ cx = 3 * (x2 - x1),
+
+ ay = 9 * (y2 - y3) + 3 * (y4 - y1),
+ by = 6 * (y1 + y3) - 12 * y2,
+ cy = 3 * (y2 - y1);
+
+ var integrand = function(t) {
+ // Calculate quadratic equations of derivatives for x and y
+ var dx = (ax * t + bx) * t + cx,
+ dy = (ay * t + by) * t + cy;
+ return sqrt(dx * dx + dy * dy);
+ };
+
+ return integrate(
+ integrand, 0, 1, limit || Two.Utils.Curve.RecursionLimit
+ );
+
+ },
+
+ /**
+ * Integration for `getCurveLength` calculations. Referenced from
+ * Paper.js: https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101
+ */
+ integrate: function(f, a, b, n) {
+ var x = Two.Utils.Curve.abscissas[n - 2],
+ w = Two.Utils.Curve.weights[n - 2],
+ A = 0.5 * (b - a),
+ B = A + a,
+ i = 0,
+ m = (n + 1) >> 1,
+ sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n
+ while (i < m) {
+ var Ax = A * x[i];
+ sum += w[i++] * (f(B + Ax) + f(B - Ax));
+ }
+ return A * sum;
+ },
+
+ /**
+ * Creates a set of points that have u, v values for anchor positions
+ */
+ getCurveFromPoints: function(points, closed) {
+
+ var l = points.length, last = l - 1;
+
+ for (var i = 0; i < l; i++) {
+
+ var point = points[i];
+
+ if (!_.isObject(point.controls)) {
+ Two.Anchor.AppendCurveProperties(point);
+ }
+
+ var prev = closed ? mod(i - 1, l) : max(i - 1, 0);
+ var next = closed ? mod(i + 1, l) : min(i + 1, last);
+
+ var a = points[prev];
+ var b = point;
+ var c = points[next];
+ getControlPoints(a, b, c);
+
+ b._command = i === 0 ? Two.Commands.move : Two.Commands.curve;
+
+ b.controls.left.x = _.isNumber(b.controls.left.x) ? b.controls.left.x : b.x;
+ b.controls.left.y = _.isNumber(b.controls.left.y) ? b.controls.left.y : b.y;
+
+ b.controls.right.x = _.isNumber(b.controls.right.x) ? b.controls.right.x : b.x;
+ b.controls.right.y = _.isNumber(b.controls.right.y) ? b.controls.right.y : b.y;
+
+ }
+
+ },
+
+ /**
+ * Given three coordinates return the control points for the middle, b,
+ * vertex.
+ */
+ getControlPoints: function(a, b, c) {
+
+ var a1 = angleBetween(a, b);
+ var a2 = angleBetween(c, b);
+
+ var d1 = distanceBetween(a, b);
+ var d2 = distanceBetween(c, b);
+
+ var mid = (a1 + a2) / 2;
+
+ // So we know which angle corresponds to which side.
+
+ b.u = _.isObject(b.controls.left) ? b.controls.left : new Two.Vector(0, 0);
+ b.v = _.isObject(b.controls.right) ? b.controls.right : new Two.Vector(0, 0);
+
+ // TODO: Issue 73
+ if (d1 < 0.0001 || d2 < 0.0001) {
+ if (!b._relative) {
+ b.controls.left.copy(b);
+ b.controls.right.copy(b);
+ }
+ return b;
+ }
+
+ d1 *= 0.33; // Why 0.33?
+ d2 *= 0.33;
+
+ if (a2 < a1) {
+ mid += HALF_PI;
+ } else {
+ mid -= HALF_PI;
+ }
+
+ b.controls.left.x = cos(mid) * d1;
+ b.controls.left.y = sin(mid) * d1;
+
+ mid -= PI;
+
+ b.controls.right.x = cos(mid) * d2;
+ b.controls.right.y = sin(mid) * d2;
+
+ if (!b._relative) {
+ b.controls.left.x += b.x;
+ b.controls.left.y += b.y;
+ b.controls.right.x += b.x;
+ b.controls.right.y += b.y;
+ }
+
+ return b;
+
+ },
+
+ /**
+ * Get the reflection of a point "b" about point "a". Where "a" is in
+ * absolute space and "b" is relative to "a".
+ *
+ * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
+ */
+ getReflection: function(a, b, relative) {
+
+ return new Two.Vector(
+ 2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
+ 2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
+ );
+
+ },
+
+ getAnchorsFromArcData: function(center, xAxisRotation, rx, ry, ts, td, ccw) {
+
+ var matrix = new Two.Matrix()
+ .translate(center.x, center.y)
+ .rotate(xAxisRotation);
+
+ var l = Two.Resolution;
+
+ return _.map(_.range(l), function(i) {
+
+ var pct = (i + 1) / l;
+ if (!!ccw) {
+ pct = 1 - pct;
+ }
+
+ var theta = pct * td + ts;
+ var x = rx * Math.cos(theta);
+ var y = ry * Math.sin(theta);
+
+ // x += center.x;
+ // y += center.y;
+
+ var anchor = new Two.Anchor(x, y);
+ Two.Anchor.AppendCurveProperties(anchor);
+ anchor.command = Two.Commands.line;
+
+ // TODO: Calculate control points here...
+
+ return anchor;
+
+ });
+
+ },
+
+ ratioBetween: function(A, B) {
+
+ return (A.x * B.x + A.y * B.y) / (A.length() * B.length());
+
+ },
+
+ angleBetween: function(A, B) {
+
+ var dx, dy;
+
+ if (arguments.length >= 4) {
+
+ dx = arguments[0] - arguments[2];
+ dy = arguments[1] - arguments[3];
+
+ return atan2(dy, dx);
+
+ }
+
+ dx = A.x - B.x;
+ dy = A.y - B.y;
+
+ return atan2(dy, dx);
+
+ },
+
+ distanceBetweenSquared: function(p1, p2) {
+
+ var dx = p1.x - p2.x;
+ var dy = p1.y - p2.y;
+
+ return dx * dx + dy * dy;
+
+ },
+
+ distanceBetween: function(p1, p2) {
+
+ return sqrt(distanceBetweenSquared(p1, p2));
+
+ },
+
+ lerp: function(a, b, t) {
+ return t * (b - a) + a;
+ },
+
+ // A pretty fast toFixed(3) alternative
+ // See http://jsperf.com/parsefloat-tofixed-vs-math-round/18
+ toFixed: function(v) {
+ return Math.floor(v * 1000) / 1000;
+ },
+
+ mod: function(v, l) {
+
+ while (v < 0) {
+ v += l;
+ }
+
+ return v % l;
+
+ },
+
+ /**
+ * Array like collection that triggers inserted and removed events
+ * removed : pop / shift / splice
+ * inserted : push / unshift / splice (with > 2 arguments)
+ */
+ Collection: function() {
+
+ Array.call(this);
+
+ if (arguments.length > 1) {
+ Array.prototype.push.apply(this, arguments);
+ } else if (arguments[0] && Array.isArray(arguments[0])) {
+ Array.prototype.push.apply(this, arguments[0]);
+ }
+
+ },
+
+ // Custom Error Throwing for Two.js
+
+ Error: function(message) {
+ this.name = 'two.js';
+ this.message = message;
+ },
+
+ Events: {
+
+ on: function(name, callback) {
+
+ this._events || (this._events = {});
+ var list = this._events[name] || (this._events[name] = []);
+
+ list.push(callback);
+
+ return this;
+
+ },
+
+ off: function(name, callback) {
+
+ if (!this._events) {
+ return this;
+ }
+ if (!name && !callback) {
+ this._events = {};
+ return this;
+ }
+
+ var names = name ? [name] : _.keys(this._events);
+ for (var i = 0, l = names.length; i < l; i++) {
+
+ var name = names[i];
+ var list = this._events[name];
+
+ if (!!list) {
+ var events = [];
+ if (callback) {
+ for (var j = 0, k = list.length; j < k; j++) {
+ var ev = list[j];
+ ev = ev.callback ? ev.callback : ev;
+ if (callback && callback !== ev) {
+ events.push(ev);
+ }
+ }
+ }
+ this._events[name] = events;
+ }
+ }
+
+ return this;
+ },
+
+ trigger: function(name) {
+ if (!this._events) return this;
+ var args = slice.call(arguments, 1);
+ var events = this._events[name];
+ if (events) trigger(this, events, args);
+ return this;
+ },
+
+ listen: function (obj, name, callback) {
+
+ var bound = this;
+
+ if (obj) {
+ var ev = function () {
+ callback.apply(bound, arguments);
+ };
+
+ // add references about the object that assigned this listener
+ ev.obj = obj;
+ ev.name = name;
+ ev.callback = callback;
+
+ obj.on(name, ev);
+ }
+
+ return this;
+
+ },
+
+ ignore: function (obj, name, callback) {
+
+ obj.off(name, callback);
+
+ return this;
+
+ }
+
+ }
+
+ })
+
+ });
+
+ Two.Utils.Events.bind = Two.Utils.Events.on;
+ Two.Utils.Events.unbind = Two.Utils.Events.off;
+
+ var trigger = function(obj, events, args) {
+ var method;
+ switch (args.length) {
+ case 0:
+ method = function(i) {
+ events[i].call(obj, args[0]);
+ };
+ break;
+ case 1:
+ method = function(i) {
+ events[i].call(obj, args[0], args[1]);
+ };
+ break;
+ case 2:
+ method = function(i) {
+ events[i].call(obj, args[0], args[1], args[2]);
+ };
+ break;
+ case 3:
+ method = function(i) {
+ events[i].call(obj, args[0], args[1], args[2], args[3]);
+ };
+ break;
+ default:
+ method = function(i) {
+ events[i].apply(obj, args);
+ };
+ }
+ for (var i = 0; i < events.length; i++) {
+ method(i);
+ }
+ };
+
+ Two.Utils.Error.prototype = new Error();
+ Two.Utils.Error.prototype.constructor = Two.Utils.Error;
+
+ Two.Utils.Collection.prototype = new Array();
+ Two.Utils.Collection.prototype.constructor = Two.Utils.Collection;
+
+ _.extend(Two.Utils.Collection.prototype, Two.Utils.Events, {
+
+ pop: function() {
+ var popped = Array.prototype.pop.apply(this, arguments);
+ this.trigger(Two.Events.remove, [popped]);
+ return popped;
+ },
+
+ shift: function() {
+ var shifted = Array.prototype.shift.apply(this, arguments);
+ this.trigger(Two.Events.remove, [shifted]);
+ return shifted;
+ },
+
+ push: function() {
+ var pushed = Array.prototype.push.apply(this, arguments);
+ this.trigger(Two.Events.insert, arguments);
+ return pushed;
+ },
+
+ unshift: function() {
+ var unshifted = Array.prototype.unshift.apply(this, arguments);
+ this.trigger(Two.Events.insert, arguments);
+ return unshifted;
+ },
+
+ splice: function() {
+ var spliced = Array.prototype.splice.apply(this, arguments);
+ var inserted;
+
+ this.trigger(Two.Events.remove, spliced);
+
+ if (arguments.length > 2) {
+ inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
+ this.trigger(Two.Events.insert, inserted);
+ this.trigger(Two.Events.order);
+ }
+ return spliced;
+ },
+
+ sort: function() {
+ Array.prototype.sort.apply(this, arguments);
+ this.trigger(Two.Events.order);
+ return this;
+ },
+
+ reverse: function() {
+ Array.prototype.reverse.apply(this, arguments);
+ this.trigger(Two.Events.order);
+ return this;
+ }
+
+ });
+
+ // Localize utils
+
+ var distanceBetween = Two.Utils.distanceBetween,
+ getAnchorsFromArcData = Two.Utils.getAnchorsFromArcData,
+ distanceBetweenSquared = Two.Utils.distanceBetweenSquared,
+ ratioBetween = Two.Utils.ratioBetween,
+ angleBetween = Two.Utils.angleBetween,
+ getControlPoints = Two.Utils.getControlPoints,
+ getCurveFromPoints = Two.Utils.getCurveFromPoints,
+ solveSegmentIntersection = Two.Utils.solveSegmentIntersection,
+ decoupleShapes = Two.Utils.decoupleShapes,
+ mod = Two.Utils.mod,
+ getBackingStoreRatio = Two.Utils.getBackingStoreRatio,
+ getPointOnCubicBezier = Two.Utils.getPointOnCubicBezier,
+ getCurveLength = Two.Utils.getCurveLength,
+ integrate = Two.Utils.integrate,
+ getReflection = Two.Utils.getReflection;
+
+ _.extend(Two.prototype, Two.Utils.Events, {
+
+ appendTo: function(elem) {
+
+ elem.appendChild(this.renderer.domElement);
+ return this;
+
+ },
+
+ play: function() {
+
+ Two.Utils.setPlaying.call(this, true);
+ return this.trigger(Two.Events.play);
+
+ },
+
+ pause: function() {
+
+ this.playing = false;
+ return this.trigger(Two.Events.pause);
+
+ },
+
+ /**
+ * Update positions and calculations in one pass before rendering.
+ */
+ update: function() {
+
+ var animated = !!this._lastFrame;
+ var now = perf.now();
+
+ this.frameCount++;
+
+ if (animated) {
+ this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
+ }
+ this._lastFrame = now;
+
+ var width = this.width;
+ var height = this.height;
+ var renderer = this.renderer;
+
+ // Update width / height for the renderer
+ if (width !== renderer.width || height !== renderer.height) {
+ renderer.setSize(width, height, this.ratio);
+ }
+
+ this.trigger(Two.Events.update, this.frameCount, this.timeDelta);
+
+ return this.render();
+
+ },
+
+ /**
+ * Render all drawable - visible objects of the scene.
+ */
+ render: function() {
+
+ this.renderer.render();
+ return this.trigger(Two.Events.render, this.frameCount);
+
+ },
+
+ /**
+ * Convenience Methods
+ */
+
+ add: function(o) {
+
+ var objects = o;
+ if (!(objects instanceof Array)) {
+ objects = _.toArray(arguments);
+ }
+
+ this.scene.add(objects);
+ return this;
+
+ },
+
+ remove: function(o) {
+
+ var objects = o;
+ if (!(objects instanceof Array)) {
+ objects = _.toArray(arguments);
+ }
+
+ this.scene.remove(objects);
+
+ return this;
+
+ },
+
+ clear: function() {
+
+ this.scene.remove(_.toArray(this.scene.children));
+ return this;
+
+ },
+
+ makeLine: function(x1, y1, x2, y2) {
+
+ var line = new Two.Line(x1, y1, x2, y2);
+ this.scene.add(line);
+
+ return line;
+
+ },
+
+ makeRectangle: function(x, y, width, height) {
+
+ var rect = new Two.Rectangle(x, y, width, height);
+ this.scene.add(rect);
+
+ return rect;
+
+ },
+
+ makeRoundedRectangle: function(x, y, width, height, sides) {
+
+ var rect = new Two.RoundedRectangle(x, y, width, height, sides);
+ this.scene.add(rect);
+
+ return rect;
+
+ },
+
+ makeCircle: function(ox, oy, r) {
+
+ var circle = new Two.Circle(ox, oy, r);
+ this.scene.add(circle);
+
+ return circle;
+
+ },
+
+ makeEllipse: function(ox, oy, rx, ry) {
+
+ var ellipse = new Two.Ellipse(ox, oy, rx, ry);
+ this.scene.add(ellipse);
+
+ return ellipse;
+
+ },
+
+ makeStar: function(ox, oy, or, ir, sides) {
+
+ var star = new Two.Star(ox, oy, or, ir, sides);
+ this.scene.add(star);
+
+ return star;
+
+ },
+
+ makeCurve: function(p) {
+
+ var l = arguments.length, points = p;
+ if (!_.isArray(p)) {
+ points = [];
+ for (var i = 0; i < l; i+=2) {
+ var x = arguments[i];
+ if (!_.isNumber(x)) {
+ break;
+ }
+ var y = arguments[i + 1];
+ points.push(new Two.Anchor(x, y));
+ }
+ }
+
+ var last = arguments[l - 1];
+ var curve = new Two.Path(points, !(_.isBoolean(last) ? last : undefined), true);
+ var rect = curve.getBoundingClientRect();
+ curve.center().translation
+ .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+
+ this.scene.add(curve);
+
+ return curve;
+
+ },
+
+ makePolygon: function(ox, oy, r, sides) {
+
+ var poly = new Two.Polygon(ox, oy, r, sides);
+ this.scene.add(poly);
+
+ return poly;
+
+ },
+
+ /*
+ * Make an Arc Segment
+ */
+
+ makeArcSegment: function(ox, oy, ir, or, sa, ea, res) {
+ var arcSegment = new Two.ArcSegment(ox, oy, ir, or, sa, ea, res);
+ this.scene.add(arcSegment);
+ return arcSegment;
+ },
+
+ /**
+ * Convenience method to make and draw a Two.Path.
+ */
+ makePath: function(p) {
+
+ var l = arguments.length, points = p;
+ if (!_.isArray(p)) {
+ points = [];
+ for (var i = 0; i < l; i+=2) {
+ var x = arguments[i];
+ if (!_.isNumber(x)) {
+ break;
+ }
+ var y = arguments[i + 1];
+ points.push(new Two.Anchor(x, y));
+ }
+ }
+
+ var last = arguments[l - 1];
+ var path = new Two.Path(points, !(_.isBoolean(last) ? last : undefined));
+ var rect = path.getBoundingClientRect();
+ path.center().translation
+ .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+
+ this.scene.add(path);
+
+ return path;
+
+ },
+
+ /**
+ * Convenience method to make and add a Two.Text.
+ */
+ makeText: function(message, x, y, styles) {
+ var text = new Two.Text(message, x, y, styles);
+ this.add(text);
+ return text;
+ },
+
+ /**
+ * Convenience method to make and add a Two.LinearGradient.
+ */
+ makeLinearGradient: function(x1, y1, x2, y2 /* stops */) {
+
+ var stops = slice.call(arguments, 4);
+ var gradient = new Two.LinearGradient(x1, y1, x2, y2, stops);
+
+ this.add(gradient);
+
+ return gradient;
+
+ },
+
+ /**
+ * Convenience method to make and add a Two.RadialGradient.
+ */
+ makeRadialGradient: function(x1, y1, r /* stops */) {
+
+ var stops = slice.call(arguments, 3);
+ var gradient = new Two.RadialGradient(x1, y1, r, stops);
+
+ this.add(gradient);
+
+ return gradient;
+
+ },
+
+ makeSprite: function(path, x, y, cols, rows, frameRate, autostart) {
+
+ var sprite = new Two.Sprite(path, x, y, cols, rows, frameRate);
+ if (!!autostart) {
+ sprite.play();
+ }
+ this.add(sprite);
+
+ return sprite;
+
+ },
+
+ makeImageSequence: function(paths, x, y, frameRate, autostart) {
+
+ var imageSequence = new Two.ImageSequence(paths, x, y, frameRate);
+ if (!!autostart) {
+ imageSequence.play();
+ }
+ this.add(imageSequence);
+
+ return imageSequence;
+
+ },
+
+ makeTexture: function(path, callback) {
+
+ var texture = new Two.Texture(path, callback);
+ return texture;
+
+ },
+
+ makeGroup: function(o) {
+
+ var objects = o;
+ if (!(objects instanceof Array)) {
+ objects = _.toArray(arguments);
+ }
+
+ var group = new Two.Group();
+ this.scene.add(group);
+ group.add(objects);
+
+ return group;
+
+ },
+
+ /**
+ * Interpret an SVG Node and add it to this instance's scene. The
+ * distinction should be made that this doesn't `import` svg's, it solely
+ * interprets them into something compatible for Two.js — this is slightly
+ * different than a direct transcription.
+ *
+ * @param {Object} svgNode - The node to be parsed
+ * @param {Boolean} shallow - Don't create a top-most group but
+ * append all contents directly
+ */
+ interpret: function(svgNode, shallow) {
+
+ var tag = svgNode.tagName.toLowerCase();
+
+ if (!(tag in Two.Utils.read)) {
+ return null;
+ }
+
+ var node = Two.Utils.read[tag].call(this, svgNode);
+
+ if (shallow && node instanceof Two.Group) {
+ this.add(node.children);
+ } else {
+ this.add(node);
+ }
+
+ return node;
+
+ },
+
+ /**
+ * Load an SVG file / text and interpret.
+ */
+ load: function(text, callback) {
+
+ var nodes = [], elem, i;
+
+ if (/.*\.svg/ig.test(text)) {
+
+ Two.Utils.xhr(text, _.bind(function(data) {
+
+ dom.temp.innerHTML = data;
+ for (i = 0; i < dom.temp.children.length; i++) {
+ elem = dom.temp.children[i];
+ nodes.push(this.interpret(elem));
+ }
+
+ callback(nodes.length <= 1 ? nodes[0] : nodes,
+ dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
+
+ }, this));
+
+ return this;
+
+ }
+
+ dom.temp.innerHTML = text;
+ for (i = 0; i < dom.temp.children.length; i++) {
+ elem = dom.temp.children[i];
+ nodes.push(this.interpret(elem));
+ }
+
+ callback(nodes.length <= 1 ? nodes[0] : nodes,
+ dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
+
+ return this;
+
+ }
+
+ });
+
+ function fitToWindow() {
+
+ var wr = document.body.getBoundingClientRect();
+
+ var width = this.width = wr.width;
+ var height = this.height = wr.height;
+
+ this.renderer.setSize(width, height, this.ratio);
+ this.trigger(Two.Events.resize, width, height);
+
+ }
+
+ // Request Animation Frame
+
+ var raf = dom.getRequestAnimationFrame();
+
+ function loop() {
+
+ raf(loop);
+
+ for (var i = 0; i < Two.Instances.length; i++) {
+ var t = Two.Instances[i];
+ if (t.playing) {
+ t.update();
+ }
+ }
+
+ }
+
+ if (typeof define === 'function' && define.amd) {
+ define('two', [], function() {
+ return Two;
+ });
+ } else if (typeof module != 'undefined' && module.exports) {
+ module.exports = Two;
+ }
+
+ return Two;
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var Registry = Two.Registry = function() {
+
+ this.map = {};
+
+ };
+
+ _.extend(Registry, {
+
+ });
+
+ _.extend(Registry.prototype, {
+
+ add: function(id, obj) {
+ this.map[id] = obj;
+ return this;
+ },
+
+ remove: function(id) {
+ delete this.map[id];
+ return this;
+ },
+
+ get: function(id) {
+ return this.map[id];
+ },
+
+ contains: function(id) {
+ return id in this.map;
+ }
+
+ });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var Vector = Two.Vector = function(x, y) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+
+ };
+
+ _.extend(Vector, {
+
+ zero: new Two.Vector()
+
+ });
+
+ _.extend(Vector.prototype, Two.Utils.Events, {
+
+ set: function(x, y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ },
+
+ copy: function(v) {
+ this.x = v.x;
+ this.y = v.y;
+ return this;
+ },
+
+ clear: function() {
+ this.x = 0;
+ this.y = 0;
+ return this;
+ },
+
+ clone: function() {
+ return new Vector(this.x, this.y);
+ },
+
+ add: function(v1, v2) {
+ this.x = v1.x + v2.x;
+ this.y = v1.y + v2.y;
+ return this;
+ },
+
+ addSelf: function(v) {
+ this.x += v.x;
+ this.y += v.y;
+ return this;
+ },
+
+ sub: function(v1, v2) {
+ this.x = v1.x - v2.x;
+ this.y = v1.y - v2.y;
+ return this;
+ },
+
+ subSelf: function(v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ return this;
+ },
+
+ multiplySelf: function(v) {
+ this.x *= v.x;
+ this.y *= v.y;
+ return this;
+ },
+
+ multiplyScalar: function(s) {
+ this.x *= s;
+ this.y *= s;
+ return this;
+ },
+
+ divideScalar: function(s) {
+ if (s) {
+ this.x /= s;
+ this.y /= s;
+ } else {
+ this.set(0, 0);
+ }
+ return this;
+ },
+
+ negate: function() {
+ return this.multiplyScalar(-1);
+ },
+
+ dot: function(v) {
+ return this.x * v.x + this.y * v.y;
+ },
+
+ lengthSquared: function() {
+ return this.x * this.x + this.y * this.y;
+ },
+
+ length: function() {
+ return Math.sqrt(this.lengthSquared());
+ },
+
+ normalize: function() {
+ return this.divideScalar(this.length());
+ },
+
+ distanceTo: function(v) {
+ return Math.sqrt(this.distanceToSquared(v));
+ },
+
+ distanceToSquared: function(v) {
+ var dx = this.x - v.x,
+ dy = this.y - v.y;
+ return dx * dx + dy * dy;
+ },
+
+ setLength: function(l) {
+ return this.normalize().multiplyScalar(l);
+ },
+
+ equals: function(v, eps) {
+ eps = (typeof eps === 'undefined') ? 0.0001 : eps;
+ return (this.distanceTo(v) < eps);
+ },
+
+ lerp: function(v, t) {
+ var x = (v.x - this.x) * t + this.x;
+ var y = (v.y - this.y) * t + this.y;
+ return this.set(x, y);
+ },
+
+ isZero: function(eps) {
+ eps = (typeof eps === 'undefined') ? 0.0001 : eps;
+ return (this.length() < eps);
+ },
+
+ toString: function() {
+ return this.x + ', ' + this.y;
+ },
+
+ toObject: function() {
+ return { x: this.x, y: this.y };
+ },
+
+ rotate: function (radians) {
+ var cos = Math.cos(radians);
+ var sin = Math.sin(radians);
+ this.x = this.x * cos - this.y * sin;
+ this.y = this.x * sin + this.y * cos;
+ return this;
+ }
+
+ });
+
+ var BoundProto = {
+
+ set: function(x, y) {
+ this._x = x;
+ this._y = y;
+ return this.trigger(Two.Events.change);
+ },
+
+ copy: function(v) {
+ this._x = v.x;
+ this._y = v.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ clear: function() {
+ this._x = 0;
+ this._y = 0;
+ return this.trigger(Two.Events.change);
+ },
+
+ clone: function() {
+ return new Vector(this._x, this._y);
+ },
+
+ add: function(v1, v2) {
+ this._x = v1.x + v2.x;
+ this._y = v1.y + v2.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ addSelf: function(v) {
+ this._x += v.x;
+ this._y += v.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ sub: function(v1, v2) {
+ this._x = v1.x - v2.x;
+ this._y = v1.y - v2.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ subSelf: function(v) {
+ this._x -= v.x;
+ this._y -= v.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ multiplySelf: function(v) {
+ this._x *= v.x;
+ this._y *= v.y;
+ return this.trigger(Two.Events.change);
+ },
+
+ multiplyScalar: function(s) {
+ this._x *= s;
+ this._y *= s;
+ return this.trigger(Two.Events.change);
+ },
+
+ divideScalar: function(s) {
+ if (s) {
+ this._x /= s;
+ this._y /= s;
+ return this.trigger(Two.Events.change);
+ }
+ return this.clear();
+ },
+
+ negate: function() {
+ return this.multiplyScalar(-1);
+ },
+
+ dot: function(v) {
+ return this._x * v.x + this._y * v.y;
+ },
+
+ lengthSquared: function() {
+ return this._x * this._x + this._y * this._y;
+ },
+
+ length: function() {
+ return Math.sqrt(this.lengthSquared());
+ },
+
+ normalize: function() {
+ return this.divideScalar(this.length());
+ },
+
+ distanceTo: function(v) {
+ return Math.sqrt(this.distanceToSquared(v));
+ },
+
+ distanceToSquared: function(v) {
+ var dx = this._x - v.x,
+ dy = this._y - v.y;
+ return dx * dx + dy * dy;
+ },
+
+ setLength: function(l) {
+ return this.normalize().multiplyScalar(l);
+ },
+
+ equals: function(v, eps) {
+ eps = (typeof eps === 'undefined') ? 0.0001 : eps;
+ return (this.distanceTo(v) < eps);
+ },
+
+ lerp: function(v, t) {
+ var x = (v.x - this._x) * t + this._x;
+ var y = (v.y - this._y) * t + this._y;
+ return this.set(x, y);
+ },
+
+ isZero: function(eps) {
+ eps = (typeof eps === 'undefined') ? 0.0001 : eps;
+ return (this.length() < eps);
+ },
+
+ toString: function() {
+ return this._x + ', ' + this._y;
+ },
+
+ toObject: function() {
+ return { x: this._x, y: this._y };
+ },
+
+ rotate: function (radians) {
+ var cos = Math.cos(radians);
+ var sin = Math.sin(radians);
+ this._x = this._x * cos - this._y * sin;
+ this._y = this._x * sin + this._y * cos;
+ return this;
+ }
+
+ };
+
+ var xgs = {
+ enumerable: true,
+ get: function() {
+ return this._x;
+ },
+ set: function(v) {
+ this._x = v;
+ this.trigger(Two.Events.change, 'x');
+ }
+ };
+
+ var ygs = {
+ enumerable: true,
+ get: function() {
+ return this._y;
+ },
+ set: function(v) {
+ this._y = v;
+ this.trigger(Two.Events.change, 'y');
+ }
+ };
+
+ /**
+ * Override Backbone bind / on in order to add properly broadcasting.
+ * This allows Two.Vector to not broadcast events unless event listeners
+ * are explicity bound to it.
+ */
+
+ Two.Vector.prototype.bind = Two.Vector.prototype.on = function() {
+
+ if (!this._bound) {
+ this._x = this.x;
+ this._y = this.y;
+ Object.defineProperty(this, 'x', xgs);
+ Object.defineProperty(this, 'y', ygs);
+ _.extend(this, BoundProto);
+ this._bound = true; // Reserved for event initialization check
+ }
+
+ Two.Utils.Events.bind.apply(this, arguments);
+
+ return this;
+
+ };
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ // Localized variables
+ var commands = Two.Commands;
+ var _ = Two.Utils;
+
+ /**
+ * An object that holds 3 `Two.Vector`s, the anchor point and its
+ * corresponding handles: `left` and `right`.
+ */
+ var Anchor = Two.Anchor = function(x, y, ux, uy, vx, vy, command) {
+
+ Two.Vector.call(this, x, y);
+
+ this._broadcast = _.bind(function() {
+ this.trigger(Two.Events.change);
+ }, this);
+
+ this._command = command || commands.move;
+ this._relative = true;
+
+ if (!command) {
+ return this;
+ }
+
+ Anchor.AppendCurveProperties(this);
+
+ if (_.isNumber(ux)) {
+ this.controls.left.x = ux;
+ }
+ if (_.isNumber(uy)) {
+ this.controls.left.y = uy;
+ }
+ if (_.isNumber(vx)) {
+ this.controls.right.x = vx;
+ }
+ if (_.isNumber(vy)) {
+ this.controls.right.y = vy;
+ }
+
+ };
+
+ _.extend(Anchor, {
+
+ AppendCurveProperties: function(anchor) {
+ anchor.controls = {
+ left: new Two.Vector(0, 0),
+ right: new Two.Vector(0, 0)
+ };
+ }
+
+ });
+
+ var AnchorProto = {
+
+ listen: function() {
+
+ if (!_.isObject(this.controls)) {
+ Anchor.AppendCurveProperties(this);
+ }
+
+ this.controls.left.bind(Two.Events.change, this._broadcast);
+ this.controls.right.bind(Two.Events.change, this._broadcast);
+
+ return this;
+
+ },
+
+ ignore: function() {
+
+ this.controls.left.unbind(Two.Events.change, this._broadcast);
+ this.controls.right.unbind(Two.Events.change, this._broadcast);
+
+ return this;
+
+ },
+
+ clone: function() {
+
+ var controls = this.controls;
+
+ var clone = new Two.Anchor(
+ this.x,
+ this.y,
+ controls && controls.left.x,
+ controls && controls.left.y,
+ controls && controls.right.x,
+ controls && controls.right.y,
+ this.command
+ );
+ clone.relative = this._relative;
+ return clone;
+
+ },
+
+ toObject: function() {
+ var o = {
+ x: this.x,
+ y: this.y
+ };
+ if (this._command) {
+ o.command = this._command;
+ }
+ if (this._relative) {
+ o.relative = this._relative;
+ }
+ if (this.controls) {
+ o.controls = {
+ left: this.controls.left.toObject(),
+ right: this.controls.right.toObject()
+ };
+ }
+ return o;
+ },
+
+ toString: function() {
+ if (!this.controls) {
+ return [this._x, this._y].join(', ');
+ }
+ return [this._x, this._y, this.controls.left.x, this.controls.left.y,
+ this.controls.right.x, this.controls.right.y].join(', ');
+ }
+
+ };
+
+ Object.defineProperty(Anchor.prototype, 'command', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._command;
+ },
+
+ set: function(c) {
+ this._command = c;
+ if (this._command === commands.curve && !_.isObject(this.controls)) {
+ Anchor.AppendCurveProperties(this);
+ }
+ return this.trigger(Two.Events.change);
+ }
+
+ });
+
+ Object.defineProperty(Anchor.prototype, 'relative', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._relative;
+ },
+
+ set: function(b) {
+ if (this._relative == b) {
+ return this;
+ }
+ this._relative = !!b;
+ return this.trigger(Two.Events.change);
+ }
+
+ });
+
+ _.extend(Anchor.prototype, Two.Vector.prototype, AnchorProto);
+
+ // Make it possible to bind and still have the Anchor specific
+ // inheritance from Two.Vector
+ Two.Anchor.prototype.bind = Two.Anchor.prototype.on = function() {
+ Two.Vector.prototype.bind.apply(this, arguments);
+ _.extend(this, AnchorProto);
+ };
+
+ Two.Anchor.prototype.unbind = Two.Anchor.prototype.off = function() {
+ Two.Vector.prototype.unbind.apply(this, arguments);
+ _.extend(this, AnchorProto);
+ };
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ /**
+ * Constants
+ */
+ var cos = Math.cos, sin = Math.sin, tan = Math.tan;
+ var _ = Two.Utils;
+
+ /**
+ * Two.Matrix contains an array of elements that represent
+ * the two dimensional 3 x 3 matrix as illustrated below:
+ *
+ * =====
+ * a b c
+ * d e f
+ * g h i // this row is not really used in 2d transformations
+ * =====
+ *
+ * String order is for transform strings: a, d, b, e, c, f
+ *
+ * @class
+ */
+ var Matrix = Two.Matrix = function(a, b, c, d, e, f) {
+
+ this.elements = new Two.Array(9);
+
+ var elements = a;
+ if (!_.isArray(elements)) {
+ elements = _.toArray(arguments);
+ }
+
+ // initialize the elements with default values.
+
+ this.identity().set(elements);
+
+ };
+
+ _.extend(Matrix, {
+
+ Identity: [
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+ ],
+
+ /**
+ * Multiply two matrix 3x3 arrays
+ */
+ Multiply: function(A, B, C) {
+
+ if (B.length <= 3) { // Multiply Vector
+
+ var x, y, z, e = A;
+
+ var a = B[0] || 0,
+ b = B[1] || 0,
+ c = B[2] || 0;
+
+ // Go down rows first
+ // a, d, g, b, e, h, c, f, i
+
+ x = e[0] * a + e[1] * b + e[2] * c;
+ y = e[3] * a + e[4] * b + e[5] * c;
+ z = e[6] * a + e[7] * b + e[8] * c;
+
+ return { x: x, y: y, z: z };
+
+ }
+
+ var A0 = A[0], A1 = A[1], A2 = A[2];
+ var A3 = A[3], A4 = A[4], A5 = A[5];
+ var A6 = A[6], A7 = A[7], A8 = A[8];
+
+ var B0 = B[0], B1 = B[1], B2 = B[2];
+ var B3 = B[3], B4 = B[4], B5 = B[5];
+ var B6 = B[6], B7 = B[7], B8 = B[8];
+
+ C = C || new Two.Array(9);
+
+ C[0] = A0 * B0 + A1 * B3 + A2 * B6;
+ C[1] = A0 * B1 + A1 * B4 + A2 * B7;
+ C[2] = A0 * B2 + A1 * B5 + A2 * B8;
+ C[3] = A3 * B0 + A4 * B3 + A5 * B6;
+ C[4] = A3 * B1 + A4 * B4 + A5 * B7;
+ C[5] = A3 * B2 + A4 * B5 + A5 * B8;
+ C[6] = A6 * B0 + A7 * B3 + A8 * B6;
+ C[7] = A6 * B1 + A7 * B4 + A8 * B7;
+ C[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+ return C;
+
+ }
+
+ });
+
+ _.extend(Matrix.prototype, Two.Utils.Events, {
+
+ /**
+ * Takes an array of elements or the arguments list itself to
+ * set and update the current matrix's elements. Only updates
+ * specified values.
+ */
+ set: function(a) {
+
+ var elements = a;
+ if (!_.isArray(elements)) {
+ elements = _.toArray(arguments);
+ }
+
+ _.extend(this.elements, elements);
+
+ return this.trigger(Two.Events.change);
+
+ },
+
+ /**
+ * Turn matrix to identity, like resetting.
+ */
+ identity: function() {
+
+ this.set(Matrix.Identity);
+
+ return this;
+
+ },
+
+ /**
+ * Multiply scalar or multiply by another matrix.
+ */
+ multiply: function(a, b, c, d, e, f, g, h, i) {
+
+ var elements = arguments, l = elements.length;
+
+ // Multiply scalar
+
+ if (l <= 1) {
+
+ _.each(this.elements, function(v, i) {
+ this.elements[i] = v * a;
+ }, this);
+
+ return this.trigger(Two.Events.change);
+
+ }
+
+ if (l <= 3) { // Multiply Vector
+
+ var x, y, z;
+ a = a || 0;
+ b = b || 0;
+ c = c || 0;
+ e = this.elements;
+
+ // Go down rows first
+ // a, d, g, b, e, h, c, f, i
+
+ x = e[0] * a + e[1] * b + e[2] * c;
+ y = e[3] * a + e[4] * b + e[5] * c;
+ z = e[6] * a + e[7] * b + e[8] * c;
+
+ return { x: x, y: y, z: z };
+
+ }
+
+ // Multiple matrix
+
+ var A = this.elements;
+ var B = elements;
+
+ var A0 = A[0], A1 = A[1], A2 = A[2];
+ var A3 = A[3], A4 = A[4], A5 = A[5];
+ var A6 = A[6], A7 = A[7], A8 = A[8];
+
+ var B0 = B[0], B1 = B[1], B2 = B[2];
+ var B3 = B[3], B4 = B[4], B5 = B[5];
+ var B6 = B[6], B7 = B[7], B8 = B[8];
+
+ this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6;
+ this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7;
+ this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8;
+
+ this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6;
+ this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7;
+ this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8;
+
+ this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6;
+ this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7;
+ this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+ return this.trigger(Two.Events.change);
+
+ },
+
+ inverse: function(out) {
+
+ var a = this.elements;
+ out = out || new Two.Matrix();
+
+ var a00 = a[0], a01 = a[1], a02 = a[2];
+ var a10 = a[3], a11 = a[4], a12 = a[5];
+ var a20 = a[6], a21 = a[7], a22 = a[8];
+
+ var b01 = a22 * a11 - a12 * a21;
+ var b11 = -a22 * a10 + a12 * a20;
+ var b21 = a21 * a10 - a11 * a20;
+
+ // Calculate the determinant
+ var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+ if (!det) {
+ return null;
+ }
+
+ det = 1.0 / det;
+
+ out.elements[0] = b01 * det;
+ out.elements[1] = (-a22 * a01 + a02 * a21) * det;
+ out.elements[2] = (a12 * a01 - a02 * a11) * det;
+ out.elements[3] = b11 * det;
+ out.elements[4] = (a22 * a00 - a02 * a20) * det;
+ out.elements[5] = (-a12 * a00 + a02 * a10) * det;
+ out.elements[6] = b21 * det;
+ out.elements[7] = (-a21 * a00 + a01 * a20) * det;
+ out.elements[8] = (a11 * a00 - a01 * a10) * det;
+
+ return out;
+
+ },
+
+ /**
+ * Set a scalar onto the matrix.
+ */
+ scale: function(sx, sy) {
+
+ var l = arguments.length;
+ if (l <= 1) {
+ sy = sx;
+ }
+
+ return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1);
+
+ },
+
+ /**
+ * Rotate the matrix.
+ */
+ rotate: function(radians) {
+
+ var c = cos(radians);
+ var s = sin(radians);
+
+ return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
+
+ },
+
+ /**
+ * Translate the matrix.
+ */
+ translate: function(x, y) {
+
+ return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
+
+ },
+
+ /*
+ * Skew the matrix by an angle in the x axis direction.
+ */
+ skewX: function(radians) {
+
+ var a = tan(radians);
+
+ return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
+
+ },
+
+ /*
+ * Skew the matrix by an angle in the y axis direction.
+ */
+ skewY: function(radians) {
+
+ var a = tan(radians);
+
+ return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
+
+ },
+
+ /**
+ * Create a transform string to be used with rendering apis.
+ */
+ toString: function(fullMatrix) {
+ var temp = [];
+
+ this.toArray(fullMatrix, temp);
+
+ return temp.join(' ');
+
+ },
+
+ /**
+ * Create a transform array to be used with rendering apis.
+ */
+ toArray: function(fullMatrix, output) {
+
+ var elements = this.elements;
+ var hasOutput = !!output;
+
+ var a = parseFloat(elements[0].toFixed(3));
+ var b = parseFloat(elements[1].toFixed(3));
+ var c = parseFloat(elements[2].toFixed(3));
+ var d = parseFloat(elements[3].toFixed(3));
+ var e = parseFloat(elements[4].toFixed(3));
+ var f = parseFloat(elements[5].toFixed(3));
+
+ if (!!fullMatrix) {
+
+ var g = parseFloat(elements[6].toFixed(3));
+ var h = parseFloat(elements[7].toFixed(3));
+ var i = parseFloat(elements[8].toFixed(3));
+
+ if (hasOutput) {
+ output[0] = a;
+ output[1] = d;
+ output[2] = g;
+ output[3] = b;
+ output[4] = e;
+ output[5] = h;
+ output[6] = c;
+ output[7] = f;
+ output[8] = i;
+ return;
+ }
+
+ return [
+ a, d, g, b, e, h, c, f, i
+ ];
+ }
+
+ if (hasOutput) {
+ output[0] = a;
+ output[1] = d;
+ output[2] = b;
+ output[3] = e;
+ output[4] = c;
+ output[5] = f;
+ return;
+ }
+
+ return [
+ a, d, b, e, c, f // Specific format see LN:19
+ ];
+
+ },
+
+ /**
+ * Clone the current matrix.
+ */
+ clone: function() {
+ var a, b, c, d, e, f, g, h, i;
+
+ a = this.elements[0];
+ b = this.elements[1];
+ c = this.elements[2];
+ d = this.elements[3];
+ e = this.elements[4];
+ f = this.elements[5];
+ g = this.elements[6];
+ h = this.elements[7];
+ i = this.elements[8];
+
+ return new Two.Matrix(a, b, c, d, e, f, g, h, i);
+
+ }
+
+ });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ // Localize variables
+ var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
+ var _ = Two.Utils;
+
+ var svg = {
+
+ version: 1.1,
+
+ ns: 'http://www.w3.org/2000/svg',
+ xlink: 'http://www.w3.org/1999/xlink',
+
+ alignments: {
+ left: 'start',
+ center: 'middle',
+ right: 'end'
+ },
+
+ /**
+ * Create an svg namespaced element.
+ */
+ createElement: function(name, attrs) {
+ var tag = name;
+ var elem = document.createElementNS(svg.ns, tag);
+ if (tag === 'svg') {
+ attrs = _.defaults(attrs || {}, {
+ version: svg.version
+ });
+ }
+ if (!_.isEmpty(attrs)) {
+ svg.setAttributes(elem, attrs);
+ }
+ return elem;
+ },
+
+ /**
+ * Add attributes from an svg element.
+ */
+ setAttributes: function(elem, attrs) {
+ var keys = Object.keys(attrs);
+ for (var i = 0; i < keys.length; i++) {
+ if (/href/.test(keys[i])) {
+ elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]);
+ } else {
+ elem.setAttribute(keys[i], attrs[keys[i]]);
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Remove attributes from an svg element.
+ */
+ removeAttributes: function(elem, attrs) {
+ for (var key in attrs) {
+ elem.removeAttribute(key);
+ }
+ return this;
+ },
+
+ /**
+ * Turn a set of vertices into a string for the d property of a path
+ * element. It is imperative that the string collation is as fast as
+ * possible, because this call will be happening multiple times a
+ * second.
+ */
+ toString: function(points, closed) {
+
+ var l = points.length,
+ last = l - 1,
+ d, // The elusive last Two.Commands.move point
+ ret = '';
+
+ for (var i = 0; i < l; i++) {
+ var b = points[i];
+ var command;
+ var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0);
+ var next = closed ? mod(i + 1, l) : Math.min(i + 1, last);
+
+ var a = points[prev];
+ var c = points[next];
+
+ var vx, vy, ux, uy, ar, bl, br, cl;
+
+ // Access x and y directly,
+ // bypassing the getter
+ var x = toFixed(b._x);
+ var y = toFixed(b._y);
+
+ switch (b._command) {
+
+ case Two.Commands.close:
+ command = Two.Commands.close;
+ break;
+
+ case Two.Commands.curve:
+
+ ar = (a.controls && a.controls.right) || Two.Vector.zero;
+ bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+ if (a._relative) {
+ vx = toFixed((ar.x + a.x));
+ vy = toFixed((ar.y + a.y));
+ } else {
+ vx = toFixed(ar.x);
+ vy = toFixed(ar.y);
+ }
+
+ if (b._relative) {
+ ux = toFixed((bl.x + b.x));
+ uy = toFixed((bl.y + b.y));
+ } else {
+ ux = toFixed(bl.x);
+ uy = toFixed(bl.y);
+ }
+
+ command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) +
+ ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+ break;
+
+ case Two.Commands.move:
+ d = b;
+ command = Two.Commands.move + ' ' + x + ' ' + y;
+ break;
+
+ default:
+ command = b._command + ' ' + x + ' ' + y;
+
+ }
+
+ // Add a final point and close it off
+
+ if (i >= last && closed) {
+
+ if (b._command === Two.Commands.curve) {
+
+ // Make sure we close to the most previous Two.Commands.move
+ c = d;
+
+ br = (b.controls && b.controls.right) || b;
+ cl = (c.controls && c.controls.left) || c;
+
+ if (b._relative) {
+ vx = toFixed((br.x + b.x));
+ vy = toFixed((br.y + b.y));
+ } else {
+ vx = toFixed(br.x);
+ vy = toFixed(br.y);
+ }
+
+ if (c._relative) {
+ ux = toFixed((cl.x + c.x));
+ uy = toFixed((cl.y + c.y));
+ } else {
+ ux = toFixed(cl.x);
+ uy = toFixed(cl.y);
+ }
+
+ x = toFixed(c.x);
+ y = toFixed(c.y);
+
+ command +=
+ ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+ }
+
+ command += ' Z';
+
+ }
+
+ ret += command + ' ';
+
+ }
+
+ return ret;
+
+ },
+
+ getClip: function(shape) {
+
+ var clip = shape._renderer.clip;
+
+ if (!clip) {
+
+ var root = shape;
+
+ while (root.parent) {
+ root = root.parent;
+ }
+
+ clip = shape._renderer.clip = svg.createElement('clipPath');
+ root.defs.appendChild(clip);
+
+ }
+
+ return clip;
+
+ },
+
+ group: {
+
+ // TODO: Can speed up.
+ // TODO: How does this effect a f
+ appendChild: function(object) {
+
+ var elem = object._renderer.elem;
+
+ if (!elem) {
+ return;
+ }
+
+ var tag = elem.nodeName;
+
+ if (!tag || /(radial|linear)gradient/i.test(tag) || object._clip) {
+ return;
+ }
+
+ this.elem.appendChild(elem);
+
+ },
+
+ removeChild: function(object) {
+
+ var elem = object._renderer.elem;
+
+ if (!elem || elem.parentNode != this.elem) {
+ return;
+ }
+
+ var tag = elem.nodeName;
+
+ if (!tag) {
+ return;
+ }
+
+ // Defer subtractions while clipping.
+ if (object._clip) {
+ return;
+ }
+
+ this.elem.removeChild(elem);
+
+ },
+
+ orderChild: function(object) {
+ this.elem.appendChild(object._renderer.elem);
+ },
+
+ renderChild: function(child) {
+ svg[child._renderer.type].render.call(child, this);
+ },
+
+ render: function(domElement) {
+
+ this._update();
+
+ // Shortcut for hidden objects.
+ // Doesn't reset the flags, so changes are stored and
+ // applied once the object is visible again
+ if (this._opacity === 0 && !this._flagOpacity) {
+ return this;
+ }
+
+ if (!this._renderer.elem) {
+ this._renderer.elem = svg.createElement('g', {
+ id: this.id
+ });
+ domElement.appendChild(this._renderer.elem);
+ }
+
+ // _Update styles for the <g>
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+ var context = {
+ domElement: domElement,
+ elem: this._renderer.elem
+ };
+
+ if (flagMatrix) {
+ this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')');
+ }
+
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ svg[child._renderer.type].render.call(child, domElement);
+ }
+
+ if (this._flagOpacity) {
+ this._renderer.elem.setAttribute('opacity', this._opacity);
+ }
+
+ if (this._flagAdditions) {
+ this.additions.forEach(svg.group.appendChild, context);
+ }
+
+ if (this._flagSubtractions) {
+ this.subtractions.forEach(svg.group.removeChild, context);
+ }
+
+ if (this._flagOrder) {
+ this.children.forEach(svg.group.orderChild, context);
+ }
+
+ /**
+ * Commented two-way functionality of clips / masks with groups and
+ * polygons. Uncomment when this bug is fixed:
+ * https://code.google.com/p/chromium/issues/detail?id=370951
+ */
+
+ // if (this._flagClip) {
+
+ // clip = svg.getClip(this);
+ // elem = this._renderer.elem;
+
+ // if (this._clip) {
+ // elem.removeAttribute('id');
+ // clip.setAttribute('id', this.id);
+ // clip.appendChild(elem);
+ // } else {
+ // clip.removeAttribute('id');
+ // elem.setAttribute('id', this.id);
+ // this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+ // }
+
+ // }
+
+ if (this._flagMask) {
+ if (this._mask) {
+ this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+ } else {
+ this._renderer.elem.removeAttribute('clip-path');
+ }
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ path: {
+
+ render: function(domElement) {
+
+ this._update();
+
+ // Shortcut for hidden objects.
+ // Doesn't reset the flags, so changes are stored and
+ // applied once the object is visible again
+ if (this._opacity === 0 && !this._flagOpacity) {
+ return this;
+ }
+
+ // Collect any attribute that needs to be changed here
+ var changed = {};
+
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+ if (flagMatrix) {
+ changed.transform = 'matrix(' + this._matrix.toString() + ')';
+ }
+
+ if (this._flagVertices) {
+ var vertices = svg.toString(this._vertices, this._closed);
+ changed.d = vertices;
+ }
+
+ if (this._fill && this._fill._renderer) {
+ this._fill._update();
+ svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
+ }
+
+ if (this._flagFill) {
+ changed.fill = this._fill && this._fill.id
+ ? 'url(#' + this._fill.id + ')' : this._fill;
+ }
+
+ if (this._stroke && this._stroke._renderer) {
+ this._stroke._update();
+ svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
+ }
+
+ if (this._flagStroke) {
+ changed.stroke = this._stroke && this._stroke.id
+ ? 'url(#' + this._stroke.id + ')' : this._stroke;
+ }
+
+ if (this._flagLinewidth) {
+ changed['stroke-width'] = this._linewidth;
+ }
+
+ if (this._flagOpacity) {
+ changed['stroke-opacity'] = this._opacity;
+ changed['fill-opacity'] = this._opacity;
+ }
+
+ if (this._flagVisible) {
+ changed.visibility = this._visible ? 'visible' : 'hidden';
+ }
+
+ if (this._flagCap) {
+ changed['stroke-linecap'] = this._cap;
+ }
+
+ if (this._flagJoin) {
+ changed['stroke-linejoin'] = this._join;
+ }
+
+ if (this._flagMiter) {
+ changed['stroke-miterlimit'] = this._miter;
+ }
+
+ // If there is no attached DOM element yet,
+ // create it with all necessary attributes.
+ if (!this._renderer.elem) {
+
+ changed.id = this.id;
+ this._renderer.elem = svg.createElement('path', changed);
+ domElement.appendChild(this._renderer.elem);
+
+ // Otherwise apply all pending attributes
+ } else {
+ svg.setAttributes(this._renderer.elem, changed);
+ }
+
+ if (this._flagClip) {
+
+ var clip = svg.getClip(this);
+ var elem = this._renderer.elem;
+
+ if (this._clip) {
+ elem.removeAttribute('id');
+ clip.setAttribute('id', this.id);
+ clip.appendChild(elem);
+ } else {
+ clip.removeAttribute('id');
+ elem.setAttribute('id', this.id);
+ this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+ }
+
+ }
+
+ /**
+ * Commented two-way functionality of clips / masks with groups and
+ * polygons. Uncomment when this bug is fixed:
+ * https://code.google.com/p/chromium/issues/detail?id=370951
+ */
+
+ // if (this._flagMask) {
+ // if (this._mask) {
+ // elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+ // } else {
+ // elem.removeAttribute('clip-path');
+ // }
+ // }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ text: {
+
+ render: function(domElement) {
+
+ this._update();
+
+ var changed = {};
+
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+ if (flagMatrix) {
+ changed.transform = 'matrix(' + this._matrix.toString() + ')';
+ }
+
+ if (this._flagFamily) {
+ changed['font-family'] = this._family;
+ }
+ if (this._flagSize) {
+ changed['font-size'] = this._size;
+ }
+ if (this._flagLeading) {
+ changed['line-height'] = this._leading;
+ }
+ if (this._flagAlignment) {
+ changed['text-anchor'] = svg.alignments[this._alignment] || this._alignment;
+ }
+ if (this._flagBaseline) {
+ changed['alignment-baseline'] = changed['dominant-baseline'] = this._baseline;
+ }
+ if (this._flagStyle) {
+ changed['font-style'] = this._style;
+ }
+ if (this._flagWeight) {
+ changed['font-weight'] = this._weight;
+ }
+ if (this._flagDecoration) {
+ changed['text-decoration'] = this._decoration;
+ }
+ if (this._fill && this._fill._renderer) {
+ this._fill._update();
+ svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
+ }
+ if (this._flagFill) {
+ changed.fill = this._fill && this._fill.id
+ ? 'url(#' + this._fill.id + ')' : this._fill;
+ }
+ if (this._stroke && this._stroke._renderer) {
+ this._stroke._update();
+ svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
+ }
+ if (this._flagStroke) {
+ changed.stroke = this._stroke && this._stroke.id
+ ? 'url(#' + this._stroke.id + ')' : this._stroke;
+ }
+ if (this._flagLinewidth) {
+ changed['stroke-width'] = this._linewidth;
+ }
+ if (this._flagOpacity) {
+ changed.opacity = this._opacity;
+ }
+ if (this._flagVisible) {
+ changed.visibility = this._visible ? 'visible' : 'hidden';
+ }
+
+ if (!this._renderer.elem) {
+
+ changed.id = this.id;
+
+ this._renderer.elem = svg.createElement('text', changed);
+ domElement.defs.appendChild(this._renderer.elem);
+
+ } else {
+
+ svg.setAttributes(this._renderer.elem, changed);
+
+ }
+
+ if (this._flagClip) {
+
+ var clip = svg.getClip(this);
+ var elem = this._renderer.elem;
+
+ if (this._clip) {
+ elem.removeAttribute('id');
+ clip.setAttribute('id', this.id);
+ clip.appendChild(elem);
+ } else {
+ clip.removeAttribute('id');
+ elem.setAttribute('id', this.id);
+ this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
+ }
+
+ }
+
+ if (this._flagValue) {
+ this._renderer.elem.textContent = this._value;
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'linear-gradient': {
+
+ render: function(domElement, silent) {
+
+ if (!silent) {
+ this._update();
+ }
+
+ var changed = {};
+
+ if (this._flagEndPoints) {
+ changed.x1 = this.left._x;
+ changed.y1 = this.left._y;
+ changed.x2 = this.right._x;
+ changed.y2 = this.right._y;
+ }
+
+ if (this._flagSpread) {
+ changed.spreadMethod = this._spread;
+ }
+
+ // If there is no attached DOM element yet,
+ // create it with all necessary attributes.
+ if (!this._renderer.elem) {
+
+ changed.id = this.id;
+ changed.gradientUnits = 'userSpaceOnUse';
+ this._renderer.elem = svg.createElement('linearGradient', changed);
+ domElement.defs.appendChild(this._renderer.elem);
+
+ // Otherwise apply all pending attributes
+ } else {
+
+ svg.setAttributes(this._renderer.elem, changed);
+
+ }
+
+ if (this._flagStops) {
+
+ var lengthChanged = this._renderer.elem.childNodes.length
+ !== this.stops.length;
+
+ if (lengthChanged) {
+ this._renderer.elem.childNodes.length = 0;
+ }
+
+ for (var i = 0; i < this.stops.length; i++) {
+
+ var stop = this.stops[i];
+ var attrs = {};
+
+ if (stop._flagOffset) {
+ attrs.offset = 100 * stop._offset + '%';
+ }
+ if (stop._flagColor) {
+ attrs['stop-color'] = stop._color;
+ }
+ if (stop._flagOpacity) {
+ attrs['stop-opacity'] = stop._opacity;
+ }
+
+ if (!stop._renderer.elem) {
+ stop._renderer.elem = svg.createElement('stop', attrs);
+ } else {
+ svg.setAttributes(stop._renderer.elem, attrs);
+ }
+
+ if (lengthChanged) {
+ this._renderer.elem.appendChild(stop._renderer.elem);
+ }
+ stop.flagReset();
+
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'radial-gradient': {
+
+ render: function(domElement, silent) {
+
+ if (!silent) {
+ this._update();
+ }
+
+ var changed = {};
+
+ if (this._flagCenter) {
+ changed.cx = this.center._x;
+ changed.cy = this.center._y;
+ }
+ if (this._flagFocal) {
+ changed.fx = this.focal._x;
+ changed.fy = this.focal._y;
+ }
+
+ if (this._flagRadius) {
+ changed.r = this._radius;
+ }
+
+ if (this._flagSpread) {
+ changed.spreadMethod = this._spread;
+ }
+
+ // If there is no attached DOM element yet,
+ // create it with all necessary attributes.
+ if (!this._renderer.elem) {
+
+ changed.id = this.id;
+ changed.gradientUnits = 'userSpaceOnUse';
+ this._renderer.elem = svg.createElement('radialGradient', changed);
+ domElement.defs.appendChild(this._renderer.elem);
+
+ // Otherwise apply all pending attributes
+ } else {
+
+ svg.setAttributes(this._renderer.elem, changed);
+
+ }
+
+ if (this._flagStops) {
+
+ var lengthChanged = this._renderer.elem.childNodes.length
+ !== this.stops.length;
+
+ if (lengthChanged) {
+ this._renderer.elem.childNodes.length = 0;
+ }
+
+ for (var i = 0; i < this.stops.length; i++) {
+
+ var stop = this.stops[i];
+ var attrs = {};
+
+ if (stop._flagOffset) {
+ attrs.offset = 100 * stop._offset + '%';
+ }
+ if (stop._flagColor) {
+ attrs['stop-color'] = stop._color;
+ }
+ if (stop._flagOpacity) {
+ attrs['stop-opacity'] = stop._opacity;
+ }
+
+ if (!stop._renderer.elem) {
+ stop._renderer.elem = svg.createElement('stop', attrs);
+ } else {
+ svg.setAttributes(stop._renderer.elem, attrs);
+ }
+
+ if (lengthChanged) {
+ this._renderer.elem.appendChild(stop._renderer.elem);
+ }
+ stop.flagReset();
+
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ texture: {
+
+ render: function(domElement, silent) {
+
+ if (!silent) {
+ this._update();
+ }
+
+ var changed = {};
+ var styles = { x: 0, y: 0 };
+ var image = this.image;
+
+ if (this._flagLoaded && this.loaded) {
+
+ switch (image.nodeName.toLowerCase()) {
+
+ case 'canvas':
+ styles.href = styles['xlink:href'] = image.toDataURL('image/png');
+ break;
+ case 'img':
+ case 'image':
+ styles.href = styles['xlink:href'] = this.src;
+ break;
+
+ }
+
+ }
+
+ if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+ changed.x = this._offset.x;
+ changed.y = this._offset.y;
+
+ if (image) {
+
+ changed.x -= image.width / 2;
+ changed.y -= image.height / 2;
+
+ if (this._scale instanceof Two.Vector) {
+ changed.x *= this._scale.x;
+ changed.y *= this._scale.y;
+ } else {
+ changed.x *= this._scale;
+ changed.y *= this._scale;
+ }
+ }
+
+ if (changed.x > 0) {
+ changed.x *= - 1;
+ }
+ if (changed.y > 0) {
+ changed.y *= - 1;
+ }
+
+ }
+
+ if (this._flagScale || this._flagLoaded || this._flagRepeat) {
+
+ changed.width = 0;
+ changed.height = 0;
+
+ if (image) {
+
+ styles.width = changed.width = image.width;
+ styles.height = changed.height = image.height;
+
+ // TODO: Hack / Bandaid
+ switch (this._repeat) {
+ case 'no-repeat':
+ changed.width += 1;
+ changed.height += 1;
+ break;
+ }
+
+ if (this._scale instanceof Two.Vector) {
+ changed.width *= this._scale.x;
+ changed.height *= this._scale.y;
+ } else {
+ changed.width *= this._scale;
+ changed.height *= this._scale;
+ }
+ }
+
+ }
+
+ if (this._flagScale || this._flagLoaded) {
+ if (!this._renderer.image) {
+ this._renderer.image = svg.createElement('image', styles);
+ } else if (!_.isEmpty(styles)) {
+ svg.setAttributes(this._renderer.image, styles);
+ }
+ }
+
+ if (!this._renderer.elem) {
+
+ changed.id = this.id;
+ changed.patternUnits = 'userSpaceOnUse';
+ this._renderer.elem = svg.createElement('pattern', changed);
+ domElement.defs.appendChild(this._renderer.elem);
+
+ } else if (!_.isEmpty(changed)) {
+
+ svg.setAttributes(this._renderer.elem, changed);
+
+ }
+
+ if (this._renderer.elem && this._renderer.image && !this._renderer.appended) {
+ this._renderer.elem.appendChild(this._renderer.image);
+ this._renderer.appended = true;
+ }
+
+ return this.flagReset();
+
+ }
+
+ }
+
+ };
+
+ /**
+ * @class
+ */
+ var Renderer = Two[Two.Types.svg] = function(params) {
+
+ this.domElement = params.domElement || svg.createElement('svg');
+
+ this.scene = new Two.Group();
+ this.scene.parent = this;
+
+ this.defs = svg.createElement('defs');
+ this.domElement.appendChild(this.defs);
+ this.domElement.defs = this.defs;
+ this.domElement.style.overflow = 'hidden';
+
+ };
+
+ _.extend(Renderer, {
+
+ Utils: svg
+
+ });
+
+ _.extend(Renderer.prototype, Two.Utils.Events, {
+
+ setSize: function(width, height) {
+
+ this.width = width;
+ this.height = height;
+
+ svg.setAttributes(this.domElement, {
+ width: width,
+ height: height
+ });
+
+ return this;
+
+ },
+
+ render: function() {
+
+ svg.group.render.call(this.scene, this.domElement);
+
+ return this;
+
+ }
+
+ });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ /**
+ * Constants
+ */
+ var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
+ var getRatio = Two.Utils.getRatio;
+ var _ = Two.Utils;
+
+ // Returns true if this is a non-transforming matrix
+ var isDefaultMatrix = function (m) {
+ return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0);
+ };
+
+ var canvas = {
+
+ isHidden: /(none|transparent)/i,
+
+ alignments: {
+ left: 'start',
+ middle: 'center',
+ right: 'end'
+ },
+
+ shim: function(elem) {
+ elem.tagName = 'canvas';
+ elem.nodeType = 1;
+ return elem;
+ },
+
+ group: {
+
+ renderChild: function(child) {
+ canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
+ },
+
+ render: function(ctx) {
+
+ // TODO: Add a check here to only invoke _update if need be.
+ this._update();
+
+ var matrix = this._matrix.elements;
+ var parent = this.parent;
+ this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+ var defaultMatrix = isDefaultMatrix(matrix);
+
+ var mask = this._mask;
+ // var clip = this._clip;
+
+ if (!this._renderer.context) {
+ this._renderer.context = {};
+ }
+
+ this._renderer.context.ctx = ctx;
+ // this._renderer.context.clip = clip;
+
+ if (!defaultMatrix) {
+ ctx.save();
+ ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+ }
+
+ if (mask) {
+ canvas[mask._renderer.type].render.call(mask, ctx, true);
+ }
+
+ if (this.opacity > 0 && this.scale !== 0) {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ canvas[child._renderer.type].render.call(child, ctx);
+ }
+ }
+
+ if (!defaultMatrix) {
+ ctx.restore();
+ }
+
+ /**
+ * Commented two-way functionality of clips / masks with groups and
+ * polygons. Uncomment when this bug is fixed:
+ * https://code.google.com/p/chromium/issues/detail?id=370951
+ */
+
+ // if (clip) {
+ // ctx.clip();
+ // }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ path: {
+
+ render: function(ctx, forced, parentClipped) {
+
+ var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter,
+ closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy,
+ ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset;
+
+ // TODO: Add a check here to only invoke _update if need be.
+ this._update();
+
+ matrix = this._matrix.elements;
+ stroke = this._stroke;
+ linewidth = this._linewidth;
+ fill = this._fill;
+ opacity = this._opacity * this.parent._renderer.opacity;
+ visible = this._visible;
+ cap = this._cap;
+ join = this._join;
+ miter = this._miter;
+ closed = this._closed;
+ commands = this._vertices; // Commands
+ length = commands.length;
+ last = length - 1;
+ defaultMatrix = isDefaultMatrix(matrix);
+
+ // mask = this._mask;
+ clip = this._clip;
+
+ if (!forced && (!visible || clip)) {
+ return this;
+ }
+
+ // Transform
+ if (!defaultMatrix) {
+ ctx.save();
+ ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+ }
+
+ /**
+ * Commented two-way functionality of clips / masks with groups and
+ * polygons. Uncomment when this bug is fixed:
+ * https://code.google.com/p/chromium/issues/detail?id=370951
+ */
+
+ // if (mask) {
+ // canvas[mask._renderer.type].render.call(mask, ctx, true);
+ // }
+
+ // Styles
+ if (fill) {
+ if (_.isString(fill)) {
+ ctx.fillStyle = fill;
+ } else {
+ canvas[fill._renderer.type].render.call(fill, ctx);
+ ctx.fillStyle = fill._renderer.effect;
+ }
+ }
+ if (stroke) {
+ if (_.isString(stroke)) {
+ ctx.strokeStyle = stroke;
+ } else {
+ canvas[stroke._renderer.type].render.call(stroke, ctx);
+ ctx.strokeStyle = stroke._renderer.effect;
+ }
+ }
+ if (linewidth) {
+ ctx.lineWidth = linewidth;
+ }
+ if (miter) {
+ ctx.miterLimit = miter;
+ }
+ if (join) {
+ ctx.lineJoin = join;
+ }
+ if (cap) {
+ ctx.lineCap = cap;
+ }
+ if (_.isNumber(opacity)) {
+ ctx.globalAlpha = opacity;
+ }
+
+ ctx.beginPath();
+
+ for (var i = 0; i < commands.length; i++) {
+
+ b = commands[i];
+
+ x = toFixed(b._x);
+ y = toFixed(b._y);
+
+ switch (b._command) {
+
+ case Two.Commands.close:
+ ctx.closePath();
+ break;
+
+ case Two.Commands.curve:
+
+ prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+ next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+ a = commands[prev];
+ c = commands[next];
+ ar = (a.controls && a.controls.right) || Two.Vector.zero;
+ bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+ if (a._relative) {
+ vx = (ar.x + toFixed(a._x));
+ vy = (ar.y + toFixed(a._y));
+ } else {
+ vx = toFixed(ar.x);
+ vy = toFixed(ar.y);
+ }
+
+ if (b._relative) {
+ ux = (bl.x + toFixed(b._x));
+ uy = (bl.y + toFixed(b._y));
+ } else {
+ ux = toFixed(bl.x);
+ uy = toFixed(bl.y);
+ }
+
+ ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+ if (i >= last && closed) {
+
+ c = d;
+
+ br = (b.controls && b.controls.right) || Two.Vector.zero;
+ cl = (c.controls && c.controls.left) || Two.Vector.zero;
+
+ if (b._relative) {
+ vx = (br.x + toFixed(b._x));
+ vy = (br.y + toFixed(b._y));
+ } else {
+ vx = toFixed(br.x);
+ vy = toFixed(br.y);
+ }
+
+ if (c._relative) {
+ ux = (cl.x + toFixed(c._x));
+ uy = (cl.y + toFixed(c._y));
+ } else {
+ ux = toFixed(cl.x);
+ uy = toFixed(cl.y);
+ }
+
+ x = toFixed(c._x);
+ y = toFixed(c._y);
+
+ ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+ }
+
+ break;
+
+ case Two.Commands.line:
+ ctx.lineTo(x, y);
+ break;
+
+ case Two.Commands.move:
+ d = b;
+ ctx.moveTo(x, y);
+ break;
+
+ }
+ }
+
+ // Loose ends
+
+ if (closed) {
+ ctx.closePath();
+ }
+
+ if (!clip && !parentClipped) {
+ if (!canvas.isHidden.test(fill)) {
+ isOffset = fill._renderer && fill._renderer.offset
+ if (isOffset) {
+ ctx.save();
+ ctx.translate(
+ - fill._renderer.offset.x, - fill._renderer.offset.y);
+ ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+ }
+ ctx.fill();
+ if (isOffset) {
+ ctx.restore();
+ }
+ }
+ if (!canvas.isHidden.test(stroke)) {
+ isOffset = stroke._renderer && stroke._renderer.offset;
+ if (isOffset) {
+ ctx.save();
+ ctx.translate(
+ - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+ ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+ ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+ }
+ ctx.stroke();
+ if (isOffset) {
+ ctx.restore();
+ }
+ }
+ }
+
+ if (!defaultMatrix) {
+ ctx.restore();
+ }
+
+ if (clip && !parentClipped) {
+ ctx.clip();
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ text: {
+
+ render: function(ctx, forced, parentClipped) {
+
+ // TODO: Add a check here to only invoke _update if need be.
+ this._update();
+
+ var matrix = this._matrix.elements;
+ var stroke = this._stroke;
+ var linewidth = this._linewidth;
+ var fill = this._fill;
+ var opacity = this._opacity * this.parent._renderer.opacity;
+ var visible = this._visible;
+ var defaultMatrix = isDefaultMatrix(matrix);
+ var isOffset = fill._renderer && fill._renderer.offset
+ && stroke._renderer && stroke._renderer.offset;
+
+ var a, b, c, d, e, sx, sy;
+
+ // mask = this._mask;
+ var clip = this._clip;
+
+ if (!forced && (!visible || clip)) {
+ return this;
+ }
+
+ // Transform
+ if (!defaultMatrix) {
+ ctx.save();
+ ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+ }
+
+ /**
+ * Commented two-way functionality of clips / masks with groups and
+ * polygons. Uncomment when this bug is fixed:
+ * https://code.google.com/p/chromium/issues/detail?id=370951
+ */
+
+ // if (mask) {
+ // canvas[mask._renderer.type].render.call(mask, ctx, true);
+ // }
+
+ if (!isOffset) {
+ ctx.font = [this._style, this._weight, this._size + 'px/' +
+ this._leading + 'px', this._family].join(' ');
+ }
+
+ ctx.textAlign = canvas.alignments[this._alignment] || this._alignment;
+ ctx.textBaseline = this._baseline;
+
+ // Styles
+ if (fill) {
+ if (_.isString(fill)) {
+ ctx.fillStyle = fill;
+ } else {
+ canvas[fill._renderer.type].render.call(fill, ctx);
+ ctx.fillStyle = fill._renderer.effect;
+ }
+ }
+ if (stroke) {
+ if (_.isString(stroke)) {
+ ctx.strokeStyle = stroke;
+ } else {
+ canvas[stroke._renderer.type].render.call(stroke, ctx);
+ ctx.strokeStyle = stroke._renderer.effect;
+ }
+ }
+ if (linewidth) {
+ ctx.lineWidth = linewidth;
+ }
+ if (_.isNumber(opacity)) {
+ ctx.globalAlpha = opacity;
+ }
+
+ if (!clip && !parentClipped) {
+
+ if (!canvas.isHidden.test(fill)) {
+
+ if (fill._renderer && fill._renderer.offset) {
+
+ sx = toFixed(fill._renderer.scale.x);
+ sy = toFixed(fill._renderer.scale.y);
+
+ ctx.save();
+ ctx.translate( - toFixed(fill._renderer.offset.x),
+ - toFixed(fill._renderer.offset.y));
+ ctx.scale(sx, sy);
+
+ a = this._size / fill._renderer.scale.y;
+ b = this._leading / fill._renderer.scale.y;
+ ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
+ toFixed(b) + 'px', this._family].join(' ');
+
+ c = fill._renderer.offset.x / fill._renderer.scale.x;
+ d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+ ctx.fillText(this.value, toFixed(c), toFixed(d));
+ ctx.restore();
+
+ } else {
+ ctx.fillText(this.value, 0, 0);
+ }
+
+ }
+
+ if (!canvas.isHidden.test(stroke)) {
+
+ if (stroke._renderer && stroke._renderer.offset) {
+
+ sx = toFixed(stroke._renderer.scale.x);
+ sy = toFixed(stroke._renderer.scale.y);
+
+ ctx.save();
+ ctx.translate(- toFixed(stroke._renderer.offset.x),
+ - toFixed(stroke._renderer.offset.y));
+ ctx.scale(sx, sy);
+
+ a = this._size / stroke._renderer.scale.y;
+ b = this._leading / stroke._renderer.scale.y;
+ ctx.font = [this._style, this._weight, toFixed(a) + 'px/',
+ toFixed(b) + 'px', this._family].join(' ');
+
+ c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+ d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+ e = linewidth / stroke._renderer.scale.x;
+
+ ctx.lineWidth = toFixed(e);
+ ctx.strokeText(this.value, toFixed(c), toFixed(d));
+ ctx.restore();
+
+ } else {
+ ctx.strokeText(this.value, 0, 0);
+ }
+ }
+ }
+
+ if (!defaultMatrix) {
+ ctx.restore();
+ }
+
+ // TODO: Test for text
+ if (clip && !parentClipped) {
+ ctx.clip();
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'linear-gradient': {
+
+ render: function(ctx) {
+
+ this._update();
+
+ if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+ this._renderer.effect = ctx.createLinearGradient(
+ this.left._x, this.left._y,
+ this.right._x, this.right._y
+ );
+
+ for (var i = 0; i < this.stops.length; i++) {
+ var stop = this.stops[i];
+ this._renderer.effect.addColorStop(stop._offset, stop._color);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'radial-gradient': {
+
+ render: function(ctx) {
+
+ this._update();
+
+ if (!this._renderer.effect || this._flagCenter || this._flagFocal
+ || this._flagRadius || this._flagStops) {
+
+ this._renderer.effect = ctx.createRadialGradient(
+ this.center._x, this.center._y, 0,
+ this.focal._x, this.focal._y, this._radius
+ );
+
+ for (var i = 0; i < this.stops.length; i++) {
+ var stop = this.stops[i];
+ this._renderer.effect.addColorStop(stop._offset, stop._color);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ texture: {
+
+ render: function(ctx) {
+
+ this._update();
+
+ var image = this.image;
+ var repeat;
+
+ if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+ this._renderer.effect = ctx.createPattern(this.image, this._repeat);
+ }
+
+ if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+ if (!(this._renderer.offset instanceof Two.Vector)) {
+ this._renderer.offset = new Two.Vector();
+ }
+
+ this._renderer.offset.x = - this._offset.x;
+ this._renderer.offset.y = - this._offset.y;
+
+ if (image) {
+
+ this._renderer.offset.x += image.width / 2;
+ this._renderer.offset.y += image.height / 2;
+
+ if (this._scale instanceof Two.Vector) {
+ this._renderer.offset.x *= this._scale.x;
+ this._renderer.offset.y *= this._scale.y;
+ } else {
+ this._renderer.offset.x *= this._scale;
+ this._renderer.offset.y *= this._scale;
+ }
+ }
+
+ }
+
+ if (this._flagScale || this._flagLoaded) {
+
+ if (!(this._renderer.scale instanceof Two.Vector)) {
+ this._renderer.scale = new Two.Vector();
+ }
+
+ if (this._scale instanceof Two.Vector) {
+ this._renderer.scale.copy(this._scale);
+ } else {
+ this._renderer.scale.set(this._scale, this._scale);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ }
+
+ };
+
+ var Renderer = Two[Two.Types.canvas] = function(params) {
+ // Smoothing property. Defaults to true
+ // Set it to false when working with pixel art.
+ // false can lead to better performance, since it would use a cheaper interpolation algorithm.
+ // It might not make a big difference on GPU backed canvases.
+ var smoothing = (params.smoothing !== false);
+ this.domElement = params.domElement || document.createElement('canvas');
+ this.ctx = this.domElement.getContext('2d');
+ this.overdraw = params.overdraw || false;
+
+ if (!_.isUndefined(this.ctx.imageSmoothingEnabled)) {
+ this.ctx.imageSmoothingEnabled = smoothing;
+ }
+
+ // Everything drawn on the canvas needs to be added to the scene.
+ this.scene = new Two.Group();
+ this.scene.parent = this;
+ };
+
+
+ _.extend(Renderer, {
+
+ Utils: canvas
+
+ });
+
+ _.extend(Renderer.prototype, Two.Utils.Events, {
+
+ setSize: function(width, height, ratio) {
+
+ this.width = width;
+ this.height = height;
+
+ this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
+
+ this.domElement.width = width * this.ratio;
+ this.domElement.height = height * this.ratio;
+
+ if (this.domElement.style) {
+ _.extend(this.domElement.style, {
+ width: width + 'px',
+ height: height + 'px'
+ });
+ }
+
+ return this;
+
+ },
+
+ render: function() {
+
+ var isOne = this.ratio === 1;
+
+ if (!isOne) {
+ this.ctx.save();
+ this.ctx.scale(this.ratio, this.ratio);
+ }
+
+ if (!this.overdraw) {
+ this.ctx.clearRect(0, 0, this.width, this.height);
+ }
+
+ canvas.group.render.call(this.scene, this.ctx);
+
+ if (!isOne) {
+ this.ctx.restore();
+ }
+
+ return this;
+
+ }
+
+ });
+
+ function resetTransform(ctx) {
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ /**
+ * Constants
+ */
+
+ var root = Two.root,
+ multiplyMatrix = Two.Matrix.Multiply,
+ mod = Two.Utils.mod,
+ identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
+ transformation = new Two.Array(9),
+ getRatio = Two.Utils.getRatio,
+ getComputedMatrix = Two.Utils.getComputedMatrix,
+ toFixed = Two.Utils.toFixed,
+ _ = Two.Utils;
+
+ var webgl = {
+
+ isHidden: /(none|transparent)/i,
+
+ canvas: (root.document ? root.document.createElement('canvas') : { getContext: _.identity }),
+
+ alignments: {
+ left: 'start',
+ middle: 'center',
+ right: 'end'
+ },
+
+ matrix: new Two.Matrix(),
+
+ uv: new Two.Array([
+ 0, 0,
+ 1, 0,
+ 0, 1,
+ 0, 1,
+ 1, 0,
+ 1, 1
+ ]),
+
+ group: {
+
+ removeChild: function(child, gl) {
+ if (child.children) {
+ for (var i = 0; i < child.children.length; i++) {
+ webgl.group.removeChild(child.children[i], gl);
+ }
+ return;
+ }
+ // Deallocate texture to free up gl memory.
+ gl.deleteTexture(child._renderer.texture);
+ delete child._renderer.texture;
+ },
+
+ renderChild: function(child) {
+ webgl[child._renderer.type].render.call(child, this.gl, this.program);
+ },
+
+ render: function(gl, program) {
+
+ this._update();
+
+ var parent = this.parent;
+ var flagParentMatrix = (parent._matrix && parent._matrix.manual) || parent._flagMatrix;
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+
+ if (flagParentMatrix || flagMatrix) {
+
+ if (!this._renderer.matrix) {
+ this._renderer.matrix = new Two.Array(9);
+ }
+
+ // Reduce amount of object / array creation / deletion
+ this._matrix.toArray(true, transformation);
+
+ multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+ this._renderer.scale = this._scale * parent._renderer.scale;
+
+ if (flagParentMatrix) {
+ this._flagMatrix = true;
+ }
+
+ }
+
+ if (this._mask) {
+
+ gl.enable(gl.STENCIL_TEST);
+ gl.stencilFunc(gl.ALWAYS, 1, 1);
+
+ gl.colorMask(false, false, false, true);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
+
+ webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+ gl.colorMask(true, true, true, true);
+ gl.stencilFunc(gl.NOTEQUAL, 0, 1);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+
+ }
+
+ this._flagOpacity = parent._flagOpacity || this._flagOpacity;
+
+ this._renderer.opacity = this._opacity
+ * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+ if (this._flagSubtractions) {
+ for (var i = 0; i < this.subtractions.length; i++) {
+ webgl.group.removeChild(this.subtractions[i], gl);
+ }
+ }
+
+ this.children.forEach(webgl.group.renderChild, {
+ gl: gl,
+ program: program
+ });
+
+ if (this._mask) {
+
+ gl.colorMask(false, false, false, false);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
+
+ webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+ gl.colorMask(true, true, true, true);
+ gl.stencilFunc(gl.NOTEQUAL, 0, 1);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+
+ gl.disable(gl.STENCIL_TEST);
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ path: {
+
+ updateCanvas: function(elem) {
+
+ var next, prev, a, c, ux, uy, vx, vy, ar, bl, br, cl, x, y;
+ var isOffset;
+
+ var commands = elem._vertices;
+ var canvas = this.canvas;
+ var ctx = this.ctx;
+
+ // Styles
+ var scale = elem._renderer.scale;
+ var stroke = elem._stroke;
+ var linewidth = elem._linewidth;
+ var fill = elem._fill;
+ var opacity = elem._renderer.opacity || elem._opacity;
+ var cap = elem._cap;
+ var join = elem._join;
+ var miter = elem._miter;
+ var closed = elem._closed;
+ var length = commands.length;
+ var last = length - 1;
+
+ canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
+ canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
+
+ var centroid = elem._renderer.rect.centroid;
+ var cx = centroid.x;
+ var cy = centroid.y;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (fill) {
+ if (_.isString(fill)) {
+ ctx.fillStyle = fill;
+ } else {
+ webgl[fill._renderer.type].render.call(fill, ctx, elem);
+ ctx.fillStyle = fill._renderer.effect;
+ }
+ }
+ if (stroke) {
+ if (_.isString(stroke)) {
+ ctx.strokeStyle = stroke;
+ } else {
+ webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+ ctx.strokeStyle = stroke._renderer.effect;
+ }
+ }
+ if (linewidth) {
+ ctx.lineWidth = linewidth;
+ }
+ if (miter) {
+ ctx.miterLimit = miter;
+ }
+ if (join) {
+ ctx.lineJoin = join;
+ }
+ if (cap) {
+ ctx.lineCap = cap;
+ }
+ if (_.isNumber(opacity)) {
+ ctx.globalAlpha = opacity;
+ }
+
+ var d;
+ ctx.save();
+ ctx.scale(scale, scale);
+ ctx.translate(cx, cy);
+
+ ctx.beginPath();
+ for (var i = 0; i < commands.length; i++) {
+
+ b = commands[i];
+
+ x = toFixed(b._x);
+ y = toFixed(b._y);
+
+ switch (b._command) {
+
+ case Two.Commands.close:
+ ctx.closePath();
+ break;
+
+ case Two.Commands.curve:
+
+ prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+ next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+ a = commands[prev];
+ c = commands[next];
+ ar = (a.controls && a.controls.right) || Two.Vector.zero;
+ bl = (b.controls && b.controls.left) || Two.Vector.zero;
+
+ if (a._relative) {
+ vx = toFixed((ar.x + a._x));
+ vy = toFixed((ar.y + a._y));
+ } else {
+ vx = toFixed(ar.x);
+ vy = toFixed(ar.y);
+ }
+
+ if (b._relative) {
+ ux = toFixed((bl.x + b._x));
+ uy = toFixed((bl.y + b._y));
+ } else {
+ ux = toFixed(bl.x);
+ uy = toFixed(bl.y);
+ }
+
+ ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+ if (i >= last && closed) {
+
+ c = d;
+
+ br = (b.controls && b.controls.right) || Two.Vector.zero;
+ cl = (c.controls && c.controls.left) || Two.Vector.zero;
+
+ if (b._relative) {
+ vx = toFixed((br.x + b._x));
+ vy = toFixed((br.y + b._y));
+ } else {
+ vx = toFixed(br.x);
+ vy = toFixed(br.y);
+ }
+
+ if (c._relative) {
+ ux = toFixed((cl.x + c._x));
+ uy = toFixed((cl.y + c._y));
+ } else {
+ ux = toFixed(cl.x);
+ uy = toFixed(cl.y);
+ }
+
+ x = toFixed(c._x);
+ y = toFixed(c._y);
+
+ ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+ }
+
+ break;
+
+ case Two.Commands.line:
+ ctx.lineTo(x, y);
+ break;
+
+ case Two.Commands.move:
+ d = b;
+ ctx.moveTo(x, y);
+ break;
+
+ }
+
+ }
+
+ // Loose ends
+
+ if (closed) {
+ ctx.closePath();
+ }
+
+ if (!webgl.isHidden.test(fill)) {
+ isOffset = fill._renderer && fill._renderer.offset
+ if (isOffset) {
+ ctx.save();
+ ctx.translate(
+ - fill._renderer.offset.x, - fill._renderer.offset.y);
+ ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+ }
+ ctx.fill();
+ if (isOffset) {
+ ctx.restore();
+ }
+ }
+
+ if (!webgl.isHidden.test(stroke)) {
+ isOffset = stroke._renderer && stroke._renderer.offset;
+ if (isOffset) {
+ ctx.save();
+ ctx.translate(
+ - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+ ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+ ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+ }
+ ctx.stroke();
+ if (isOffset) {
+ ctx.restore();
+ }
+ }
+
+ ctx.restore();
+
+ },
+
+ /**
+ * Returns the rect of a set of verts. Typically takes vertices that are
+ * "centered" around 0 and returns them to be anchored upper-left.
+ */
+ getBoundingClientRect: function(vertices, border, rect) {
+
+ var left = Infinity, right = -Infinity,
+ top = Infinity, bottom = -Infinity,
+ width, height;
+
+ vertices.forEach(function(v) {
+
+ var x = v.x, y = v.y, controls = v.controls;
+ var a, b, c, d, cl, cr;
+
+ top = Math.min(y, top);
+ left = Math.min(x, left);
+ right = Math.max(x, right);
+ bottom = Math.max(y, bottom);
+
+ if (!v.controls) {
+ return;
+ }
+
+ cl = controls.left;
+ cr = controls.right;
+
+ if (!cl || !cr) {
+ return;
+ }
+
+ a = v._relative ? cl.x + x : cl.x;
+ b = v._relative ? cl.y + y : cl.y;
+ c = v._relative ? cr.x + x : cr.x;
+ d = v._relative ? cr.y + y : cr.y;
+
+ if (!a || !b || !c || !d) {
+ return;
+ }
+
+ top = Math.min(b, d, top);
+ left = Math.min(a, c, left);
+ right = Math.max(a, c, right);
+ bottom = Math.max(b, d, bottom);
+
+ });
+
+ // Expand borders
+
+ if (_.isNumber(border)) {
+ top -= border;
+ left -= border;
+ right += border;
+ bottom += border;
+ }
+
+ width = right - left;
+ height = bottom - top;
+
+ rect.top = top;
+ rect.left = left;
+ rect.right = right;
+ rect.bottom = bottom;
+ rect.width = width;
+ rect.height = height;
+
+ if (!rect.centroid) {
+ rect.centroid = {};
+ }
+
+ rect.centroid.x = - left;
+ rect.centroid.y = - top;
+
+ },
+
+ render: function(gl, program, forcedParent) {
+
+ if (!this._visible || !this._opacity) {
+ return this;
+ }
+
+ this._update();
+
+ // Calculate what changed
+
+ var parent = this.parent;
+ var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+ var flagTexture = this._flagVertices || this._flagFill
+ || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+ || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+ || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagOffset || this._fill._flagScale))
+ || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+ || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+ || (this._stroke instanceof Two.Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagOffset || this._fill._flagScale))
+ || this._flagStroke || this._flagLinewidth || this._flagOpacity
+ || parent._flagOpacity || this._flagVisible || this._flagCap
+ || this._flagJoin || this._flagMiter || this._flagScale
+ || !this._renderer.texture;
+
+ if (flagParentMatrix || flagMatrix) {
+
+ if (!this._renderer.matrix) {
+ this._renderer.matrix = new Two.Array(9);
+ }
+
+ // Reduce amount of object / array creation / deletion
+
+ this._matrix.toArray(true, transformation);
+
+ multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+ this._renderer.scale = this._scale * parent._renderer.scale;
+
+ }
+
+ if (flagTexture) {
+
+ if (!this._renderer.rect) {
+ this._renderer.rect = {};
+ }
+
+ if (!this._renderer.triangles) {
+ this._renderer.triangles = new Two.Array(12);
+ }
+
+ this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+ webgl.path.getBoundingClientRect(this._vertices, this._linewidth, this._renderer.rect);
+ webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
+
+ webgl.updateBuffer.call(webgl, gl, this, program);
+ webgl.updateTexture.call(webgl, gl, this);
+
+ }
+
+ // if (this._mask) {
+ // webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
+ // }
+
+ if (this._clip && !forcedParent) {
+ return;
+ }
+
+ // Draw Texture
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
+
+ gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
+
+ gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+
+ // Draw Rect
+
+ gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
+
+ gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ text: {
+
+ updateCanvas: function(elem) {
+
+ var canvas = this.canvas;
+ var ctx = this.ctx;
+
+ // Styles
+ var scale = elem._renderer.scale;
+ var stroke = elem._stroke;
+ var linewidth = elem._linewidth * scale;
+ var fill = elem._fill;
+ var opacity = elem._renderer.opacity || elem._opacity;
+
+ canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
+ canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
+
+ var centroid = elem._renderer.rect.centroid;
+ var cx = centroid.x;
+ var cy = centroid.y;
+
+ var a, b, c, d, e, sx, sy;
+ var isOffset = fill._renderer && fill._renderer.offset
+ && stroke._renderer && stroke._renderer.offset;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (!isOffset) {
+ ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
+ elem._leading + 'px', elem._family].join(' ');
+ }
+
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ // Styles
+ if (fill) {
+ if (_.isString(fill)) {
+ ctx.fillStyle = fill;
+ } else {
+ webgl[fill._renderer.type].render.call(fill, ctx, elem);
+ ctx.fillStyle = fill._renderer.effect;
+ }
+ }
+ if (stroke) {
+ if (_.isString(stroke)) {
+ ctx.strokeStyle = stroke;
+ } else {
+ webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+ ctx.strokeStyle = stroke._renderer.effect;
+ }
+ }
+ if (linewidth) {
+ ctx.lineWidth = linewidth;
+ }
+ if (_.isNumber(opacity)) {
+ ctx.globalAlpha = opacity;
+ }
+
+ ctx.save();
+ ctx.scale(scale, scale);
+ ctx.translate(cx, cy);
+
+ if (!webgl.isHidden.test(fill)) {
+
+ if (fill._renderer && fill._renderer.offset) {
+
+ sx = toFixed(fill._renderer.scale.x);
+ sy = toFixed(fill._renderer.scale.y);
+
+ ctx.save();
+ ctx.translate( - toFixed(fill._renderer.offset.x),
+ - toFixed(fill._renderer.offset.y));
+ ctx.scale(sx, sy);
+
+ a = elem._size / fill._renderer.scale.y;
+ b = elem._leading / fill._renderer.scale.y;
+ ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
+ toFixed(b) + 'px', elem._family].join(' ');
+
+ c = fill._renderer.offset.x / fill._renderer.scale.x;
+ d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+ ctx.fillText(elem.value, toFixed(c), toFixed(d));
+ ctx.restore();
+
+ } else {
+ ctx.fillText(elem.value, 0, 0);
+ }
+
+ }
+
+ if (!webgl.isHidden.test(stroke)) {
+
+ if (stroke._renderer && stroke._renderer.offset) {
+
+ sx = toFixed(stroke._renderer.scale.x);
+ sy = toFixed(stroke._renderer.scale.y);
+
+ ctx.save();
+ ctx.translate(- toFixed(stroke._renderer.offset.x),
+ - toFixed(stroke._renderer.offset.y));
+ ctx.scale(sx, sy);
+
+ a = elem._size / stroke._renderer.scale.y;
+ b = elem._leading / stroke._renderer.scale.y;
+ ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
+ toFixed(b) + 'px', elem._family].join(' ');
+
+ c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+ d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+ e = linewidth / stroke._renderer.scale.x;
+
+ ctx.lineWidth = toFixed(e);
+ ctx.strokeText(elem.value, toFixed(c), toFixed(d));
+ ctx.restore();
+
+ } else {
+ ctx.strokeText(elem.value, 0, 0);
+ }
+
+ }
+
+ ctx.restore();
+
+ },
+
+ getBoundingClientRect: function(elem, rect) {
+
+ var ctx = webgl.ctx;
+
+ ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
+ elem._leading + 'px', elem._family].join(' ');
+
+ ctx.textAlign = 'center';
+ ctx.textBaseline = elem._baseline;
+
+ // TODO: Estimate this better
+ var width = ctx.measureText(elem._value).width;
+ var height = Math.max(elem._size || elem._leading);
+
+ if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
+ // width += this._linewidth; // TODO: Not sure if the `measure` calcs this.
+ height += this._linewidth;
+ }
+
+ var w = width / 2;
+ var h = height / 2;
+
+ switch (webgl.alignments[elem._alignment] || elem._alignment) {
+
+ case webgl.alignments.left:
+ rect.left = 0;
+ rect.right = width;
+ break;
+ case webgl.alignments.right:
+ rect.left = - width;
+ rect.right = 0;
+ break;
+ default:
+ rect.left = - w;
+ rect.right = w;
+ }
+
+ // TODO: Gradients aren't inherited...
+ switch (elem._baseline) {
+ case 'bottom':
+ rect.top = - height;
+ rect.bottom = 0;
+ break;
+ case 'top':
+ rect.top = 0;
+ rect.bottom = height;
+ break;
+ default:
+ rect.top = - h;
+ rect.bottom = h;
+ }
+
+ rect.width = width;
+ rect.height = height;
+
+ if (!rect.centroid) {
+ rect.centroid = {};
+ }
+
+ // TODO:
+ rect.centroid.x = w;
+ rect.centroid.y = h;
+
+ },
+
+ render: function(gl, program, forcedParent) {
+
+ if (!this._visible || !this._opacity) {
+ return this;
+ }
+
+ this._update();
+
+ // Calculate what changed
+
+ var parent = this.parent;
+ var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+ var flagMatrix = this._matrix.manual || this._flagMatrix;
+ var flagTexture = this._flagVertices || this._flagFill
+ || (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+ || (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+ || (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded))
+ || (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+ || (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+ || (this._texture instanceof Two.Texture && (this._texture._flagLoaded && this._texture.loaded))
+ || this._flagStroke || this._flagLinewidth || this._flagOpacity
+ || parent._flagOpacity || this._flagVisible || this._flagScale
+ || this._flagValue || this._flagFamily || this._flagSize
+ || this._flagLeading || this._flagAlignment || this._flagBaseline
+ || this._flagStyle || this._flagWeight || this._flagDecoration
+ || !this._renderer.texture;
+
+ if (flagParentMatrix || flagMatrix) {
+
+ if (!this._renderer.matrix) {
+ this._renderer.matrix = new Two.Array(9);
+ }
+
+ // Reduce amount of object / array creation / deletion
+
+ this._matrix.toArray(true, transformation);
+
+ multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+ this._renderer.scale = this._scale * parent._renderer.scale;
+
+ }
+
+ if (flagTexture) {
+
+ if (!this._renderer.rect) {
+ this._renderer.rect = {};
+ }
+
+ if (!this._renderer.triangles) {
+ this._renderer.triangles = new Two.Array(12);
+ }
+
+ this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+ webgl.text.getBoundingClientRect(this, this._renderer.rect);
+ webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
+
+ webgl.updateBuffer.call(webgl, gl, this, program);
+ webgl.updateTexture.call(webgl, gl, this);
+
+ }
+
+ // if (this._mask) {
+ // webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
+ // }
+
+ if (this._clip && !forcedParent) {
+ return;
+ }
+
+ // Draw Texture
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
+
+ gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
+
+ gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+
+ // Draw Rect
+
+ gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
+
+ gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'linear-gradient': {
+
+ render: function(ctx, elem) {
+
+ if (!ctx.canvas.getContext('2d')) {
+ return;
+ }
+
+ this._update();
+
+ if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+ this._renderer.effect = ctx.createLinearGradient(
+ this.left._x, this.left._y,
+ this.right._x, this.right._y
+ );
+
+ for (var i = 0; i < this.stops.length; i++) {
+ var stop = this.stops[i];
+ this._renderer.effect.addColorStop(stop._offset, stop._color);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ 'radial-gradient': {
+
+ render: function(ctx, elem) {
+
+ if (!ctx.canvas.getContext('2d')) {
+ return;
+ }
+
+ this._update();
+
+ if (!this._renderer.effect || this._flagCenter || this._flagFocal
+ || this._flagRadius || this._flagStops) {
+
+ this._renderer.effect = ctx.createRadialGradient(
+ this.center._x, this.center._y, 0,
+ this.focal._x, this.focal._y, this._radius
+ );
+
+ for (var i = 0; i < this.stops.length; i++) {
+ var stop = this.stops[i];
+ this._renderer.effect.addColorStop(stop._offset, stop._color);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ texture: {
+
+ render: function(ctx, elem) {
+
+ if (!ctx.canvas.getContext('2d')) {
+ return;
+ }
+
+ this._update();
+
+ var image = this.image;
+ var repeat;
+
+ if (!this._renderer.effect || ((this._flagLoaded || this._flagRepeat) && this.loaded)) {
+ this._renderer.effect = ctx.createPattern(image, this._repeat);
+ }
+
+ if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+ if (!(this._renderer.offset instanceof Two.Vector)) {
+ this._renderer.offset = new Two.Vector();
+ }
+
+ this._renderer.offset.x = this._offset.x;
+ this._renderer.offset.y = this._offset.y;
+
+ if (image) {
+
+ this._renderer.offset.x -= image.width / 2;
+ this._renderer.offset.y += image.height / 2;
+
+ if (this._scale instanceof Two.Vector) {
+ this._renderer.offset.x *= this._scale.x;
+ this._renderer.offset.y *= this._scale.y;
+ } else {
+ this._renderer.offset.x *= this._scale;
+ this._renderer.offset.y *= this._scale;
+ }
+ }
+
+ }
+
+ if (this._flagScale || this._flagLoaded) {
+
+ if (!(this._renderer.scale instanceof Two.Vector)) {
+ this._renderer.scale = new Two.Vector();
+ }
+
+ if (this._scale instanceof Two.Vector) {
+ this._renderer.scale.copy(this._scale);
+ } else {
+ this._renderer.scale.set(this._scale, this._scale);
+ }
+
+ }
+
+ return this.flagReset();
+
+ }
+
+ },
+
+ getTriangles: function(rect, triangles) {
+
+ var top = rect.top,
+ left = rect.left,
+ right = rect.right,
+ bottom = rect.bottom;
+
+ // First Triangle
+
+ triangles[0] = left;
+ triangles[1] = top;
+
+ triangles[2] = right;
+ triangles[3] = top;
+
+ triangles[4] = left;
+ triangles[5] = bottom;
+
+ // Second Triangle
+
+ triangles[6] = left;
+ triangles[7] = bottom;
+
+ triangles[8] = right;
+ triangles[9] = top;
+
+ triangles[10] = right;
+ triangles[11] = bottom;
+
+ },
+
+ updateTexture: function(gl, elem) {
+
+ this[elem._renderer.type].updateCanvas.call(webgl, elem);
+
+ if (elem._renderer.texture) {
+ gl.deleteTexture(elem._renderer.texture);
+ }
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
+
+ // TODO: Is this necessary every time or can we do once?
+ // TODO: Create a registry for textures
+ elem._renderer.texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, elem._renderer.texture);
+
+ // Set the parameters so we can render any size image.
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+
+ if (this.canvas.width <= 0 || this.canvas.height <= 0) {
+ return;
+ }
+
+ // Upload the image into the texture.
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas);
+
+ },
+
+ updateBuffer: function(gl, elem, program) {
+
+ if (_.isObject(elem._renderer.buffer)) {
+ gl.deleteBuffer(elem._renderer.buffer);
+ }
+
+ elem._renderer.buffer = gl.createBuffer();
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.buffer);
+ gl.enableVertexAttribArray(program.position);
+
+ gl.bufferData(gl.ARRAY_BUFFER, elem._renderer.triangles, gl.STATIC_DRAW);
+
+ if (_.isObject(elem._renderer.textureCoordsBuffer)) {
+ gl.deleteBuffer(elem._renderer.textureCoordsBuffer);
+ }
+
+ elem._renderer.textureCoordsBuffer = gl.createBuffer();
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
+ gl.enableVertexAttribArray(program.textureCoords);
+
+ gl.bufferData(gl.ARRAY_BUFFER, this.uv, gl.STATIC_DRAW);
+
+ },
+
+ program: {
+
+ create: function(gl, shaders) {
+ var program, linked, error;
+ program = gl.createProgram();
+ _.each(shaders, function(s) {
+ gl.attachShader(program, s);
+ });
+
+ gl.linkProgram(program);
+ linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+ if (!linked) {
+ error = gl.getProgramInfoLog(program);
+ gl.deleteProgram(program);
+ throw new Two.Utils.Error('unable to link program: ' + error);
+ }
+
+ return program;
+
+ }
+
+ },
+
+ shaders: {
+
+ create: function(gl, source, type) {
+ var shader, compiled, error;
+ shader = gl.createShader(gl[type]);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+
+ compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+ if (!compiled) {
+ error = gl.getShaderInfoLog(shader);
+ gl.deleteShader(shader);
+ throw new Two.Utils.Error('unable to compile shader ' + shader + ': ' + error);
+ }
+
+ return shader;
+
+ },
+
+ types: {
+ vertex: 'VERTEX_SHADER',
+ fragment: 'FRAGMENT_SHADER'
+ },
+
+ vertex: [
+ 'attribute vec2 a_position;',
+ 'attribute vec2 a_textureCoords;',
+ '',
+ 'uniform mat3 u_matrix;',
+ 'uniform vec2 u_resolution;',
+ '',
+ 'varying vec2 v_textureCoords;',
+ '',
+ 'void main() {',
+ ' vec2 projected = (u_matrix * vec3(a_position, 1.0)).xy;',
+ ' vec2 normal = projected / u_resolution;',
+ ' vec2 clipspace = (normal * 2.0) - 1.0;',
+ '',
+ ' gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);',
+ ' v_textureCoords = a_textureCoords;',
+ '}'
+ ].join('\n'),
+
+ fragment: [
+ 'precision mediump float;',
+ '',
+ 'uniform sampler2D u_image;',
+ 'varying vec2 v_textureCoords;',
+ '',
+ 'void main() {',
+ ' gl_FragColor = texture2D(u_image, v_textureCoords);',
+ '}'
+ ].join('\n')
+
+ },
+
+ TextureRegistry: new Two.Registry()
+
+ };
+
+ webgl.ctx = webgl.canvas.getContext('2d');
+
+ var Renderer = Two[Two.Types.webgl] = function(options) {
+
+ var params, gl, vs, fs;
+ this.domElement = options.domElement || document.createElement('canvas');
+
+ // Everything drawn on the canvas needs to come from the stage.
+ this.scene = new Two.Group();
+ this.scene.parent = this;
+
+ this._renderer = {
+ matrix: new Two.Array(identity),
+ scale: 1,
+ opacity: 1
+ };
+ this._flagMatrix = true;
+
+ // http://games.greggman.com/game/webgl-and-alpha/
+ // http://www.khronos.org/registry/webgl/specs/latest/#5.2
+ params = _.defaults(options || {}, {
+ antialias: false,
+ alpha: true,
+ premultipliedAlpha: true,
+ stencil: true,
+ preserveDrawingBuffer: true,
+ overdraw: false
+ });
+
+ this.overdraw = params.overdraw;
+
+ gl = this.ctx = this.domElement.getContext('webgl', params) ||
+ this.domElement.getContext('experimental-webgl', params);
+
+ if (!this.ctx) {
+ throw new Two.Utils.Error(
+ 'unable to create a webgl context. Try using another renderer.');
+ }
+
+ // Compile Base Shaders to draw in pixel space.
+ vs = webgl.shaders.create(
+ gl, webgl.shaders.vertex, webgl.shaders.types.vertex);
+ fs = webgl.shaders.create(
+ gl, webgl.shaders.fragment, webgl.shaders.types.fragment);
+
+ this.program = webgl.program.create(gl, [vs, fs]);
+ gl.useProgram(this.program);
+
+ // Create and bind the drawing buffer
+
+ // look up where the vertex data needs to go.
+ this.program.position = gl.getAttribLocation(this.program, 'a_position');
+ this.program.matrix = gl.getUniformLocation(this.program, 'u_matrix');
+ this.program.textureCoords = gl.getAttribLocation(this.program, 'a_textureCoords');
+
+ // Copied from Three.js WebGLRenderer
+ gl.disable(gl.DEPTH_TEST);
+
+ // Setup some initial statements of the gl context
+ gl.enable(gl.BLEND);
+
+ // https://code.google.com/p/chromium/issues/detail?id=316393
+ // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, gl.TRUE);
+
+ gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
+ gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
+
+ };
+
+ _.extend(Renderer, {
+
+ Utils: webgl
+
+ });
+
+ _.extend(Renderer.prototype, Two.Utils.Events, {
+
+ setSize: function(width, height, ratio) {
+
+ this.width = width;
+ this.height = height;
+
+ this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
+
+ this.domElement.width = width * this.ratio;
+ this.domElement.height = height * this.ratio;
+
+ _.extend(this.domElement.style, {
+ width: width + 'px',
+ height: height + 'px'
+ });
+
+ width *= this.ratio;
+ height *= this.ratio;
+
+ // Set for this.stage parent scaling to account for HDPI
+ this._renderer.matrix[0] = this._renderer.matrix[4] = this._renderer.scale = this.ratio;
+
+ this._flagMatrix = true;
+
+ this.ctx.viewport(0, 0, width, height);
+
+ var resolutionLocation = this.ctx.getUniformLocation(
+ this.program, 'u_resolution');
+ this.ctx.uniform2f(resolutionLocation, width, height);
+
+ return this;
+
+ },
+
+ render: function() {
+
+ var gl = this.ctx;
+
+ if (!this.overdraw) {
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ }
+
+ webgl.group.render.call(this.scene, gl, this.program);
+ this._flagMatrix = false;
+
+ return this;
+
+ }
+
+ });
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var Shape = Two.Shape = function() {
+
+ // Private object for renderer specific variables.
+ this._renderer = {};
+ this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this);
+ this.isShape = true;
+
+ this.id = Two.Identifier + Two.uniqueId();
+ this.classList = [];
+
+ // Define matrix properties which all inherited
+ // objects of Shape have.
+
+ this._matrix = new Two.Matrix();
+
+ this.translation = new Two.Vector();
+ this.rotation = 0;
+ this.scale = 1;
+
+ };
+
+ _.extend(Shape, {
+
+ FlagMatrix: function() {
+ this._flagMatrix = true;
+ },
+
+ MakeObservable: function(object) {
+
+ Object.defineProperty(object, 'translation', {
+ enumerable: true,
+ get: function() {
+ return this._translation;
+ },
+ set: function(v) {
+ if (this._translation) {
+ this._translation.unbind(Two.Events.change, this._renderer.flagMatrix);
+ }
+ this._translation = v;
+ this._translation.bind(Two.Events.change, this._renderer.flagMatrix);
+ Shape.FlagMatrix.call(this);
+ }
+ });
+
+ Object.defineProperty(object, 'rotation', {
+ enumerable: true,
+ get: function() {
+ return this._rotation;
+ },
+ set: function(v) {
+ this._rotation = v;
+ this._flagMatrix = true;
+ }
+ });
+
+ Object.defineProperty(object, 'scale', {
+ enumerable: true,
+ get: function() {
+ return this._scale;
+ },
+ set: function(v) {
+
+ if (this._scale instanceof Two.Vector) {
+ this._scale.unbind(Two.Events.change, this._renderer.flagMatrix);
+ }
+
+ this._scale = v;
+
+ if (this._scale instanceof Two.Vector) {
+ this._scale.bind(Two.Events.change, this._renderer.flagMatrix);
+ }
+
+ this._flagMatrix = true;
+ this._flagScale = true;
+
+ }
+ });
+
+ }
+
+ });
+
+ _.extend(Shape.prototype, Two.Utils.Events, {
+
+ // Flags
+
+ _flagMatrix: true,
+ _flagScale: false,
+
+ // _flagMask: false,
+ // _flagClip: false,
+
+ // Underlying Properties
+
+ _rotation: 0,
+ _scale: 1,
+ _translation: null,
+
+ // _mask: null,
+ // _clip: false,
+
+ addTo: function(group) {
+ group.add(this);
+ return this;
+ },
+
+ clone: function() {
+ var clone = new Shape();
+ clone.translation.copy(this.translation);
+ clone.rotation = this.rotation;
+ clone.scale = this.scale;
+ _.each(Shape.Properties, function(k) {
+ clone[k] = this[k];
+ }, this);
+ return clone._update();
+ },
+
+ /**
+ * To be called before render that calculates and collates all information
+ * to be as up-to-date as possible for the render. Called once a frame.
+ */
+ _update: function(deep) {
+
+ if (!this._matrix.manual && this._flagMatrix) {
+
+ this._matrix
+ .identity()
+ .translate(this.translation.x, this.translation.y);
+
+ if (this._scale instanceof Two.Vector) {
+ this._matrix.scale(this._scale.x, this._scale.y);
+ } else {
+ this._matrix.scale(this._scale);
+ }
+
+ this._matrix.rotate(this.rotation);
+
+ }
+
+ if (deep) {
+ // Bubble up to parents mainly for `getBoundingClientRect` method.
+ if (this.parent && this.parent._update) {
+ this.parent._update();
+ }
+ }
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagMatrix = this._flagScale = false;
+
+ return this;
+
+ }
+
+ });
+
+ Shape.MakeObservable(Shape.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ /**
+ * Constants
+ */
+
+ var min = Math.min, max = Math.max, round = Math.round,
+ getComputedMatrix = Two.Utils.getComputedMatrix;
+
+ var commands = {};
+ var _ = Two.Utils;
+
+ _.each(Two.Commands, function(v, k) {
+ commands[k] = new RegExp(v);
+ });
+
+ var Path = Two.Path = function(vertices, closed, curved, manual) {
+
+ Two.Shape.call(this);
+
+ this._renderer.type = 'path';
+ this._renderer.flagVertices = _.bind(Path.FlagVertices, this);
+ this._renderer.bindVertices = _.bind(Path.BindVertices, this);
+ this._renderer.unbindVertices = _.bind(Path.UnbindVertices, this);
+
+ this._renderer.flagFill = _.bind(Path.FlagFill, this);
+ this._renderer.flagStroke = _.bind(Path.FlagStroke, this);
+
+ this._closed = !!closed;
+ this._curved = !!curved;
+
+ this.beginning = 0;
+ this.ending = 1;
+
+ // Style properties
+
+ this.fill = '#fff';
+ this.stroke = '#000';
+ this.linewidth = 1.0;
+ this.opacity = 1.0;
+ this.visible = true;
+
+ this.cap = 'butt'; // Default of Adobe Illustrator
+ this.join = 'miter'; // Default of Adobe Illustrator
+ this.miter = 4; // Default of Adobe Illustrator
+
+ this._vertices = [];
+ this.vertices = vertices;
+
+ // Determines whether or not two.js should calculate curves, lines, and
+ // commands automatically for you or to let the developer manipulate them
+ // for themselves.
+ this.automatic = !manual;
+
+ };
+
+ _.extend(Path, {
+
+ Properties: [
+ 'fill',
+ 'stroke',
+ 'linewidth',
+ 'opacity',
+ 'visible',
+ 'cap',
+ 'join',
+ 'miter',
+
+ 'closed',
+ 'curved',
+ 'automatic',
+ 'beginning',
+ 'ending'
+ ],
+
+ FlagVertices: function() {
+ this._flagVertices = true;
+ this._flagLength = true;
+ },
+
+ BindVertices: function(items) {
+
+ // This function is called a lot
+ // when importing a large SVG
+ var i = items.length;
+ while (i--) {
+ items[i].bind(Two.Events.change, this._renderer.flagVertices);
+ }
+
+ this._renderer.flagVertices();
+
+ },
+
+ UnbindVertices: function(items) {
+
+ var i = items.length;
+ while (i--) {
+ items[i].unbind(Two.Events.change, this._renderer.flagVertices);
+ }
+
+ this._renderer.flagVertices();
+
+ },
+
+ FlagFill: function() {
+ this._flagFill = true;
+ },
+
+ FlagStroke: function() {
+ this._flagStroke = true;
+ },
+
+ MakeObservable: function(object) {
+
+ Two.Shape.MakeObservable(object);
+
+ // Only the 6 defined properties are flagged like this. The subsequent
+ // properties behave differently and need to be hand written.
+ _.each(Path.Properties.slice(2, 8), Two.Utils.defineProperty, object);
+
+ Object.defineProperty(object, 'fill', {
+ enumerable: true,
+ get: function() {
+ return this._fill;
+ },
+ set: function(f) {
+
+ if (this._fill instanceof Two.Gradient
+ || this._fill instanceof Two.LinearGradient
+ || this._fill instanceof Two.RadialGradient
+ || this._fill instanceof Two.Texture) {
+ this._fill.unbind(Two.Events.change, this._renderer.flagFill);
+ }
+
+ this._fill = f;
+ this._flagFill = true;
+
+ if (this._fill instanceof Two.Gradient
+ || this._fill instanceof Two.LinearGradient
+ || this._fill instanceof Two.RadialGradient
+ || this._fill instanceof Two.Texture) {
+ this._fill.bind(Two.Events.change, this._renderer.flagFill);
+ }
+
+ }
+ });
+
+ Object.defineProperty(object, 'stroke', {
+ enumerable: true,
+ get: function() {
+ return this._stroke;
+ },
+ set: function(f) {
+
+ if (this._stroke instanceof Two.Gradient
+ || this._stroke instanceof Two.LinearGradient
+ || this._stroke instanceof Two.RadialGradient
+ || this._stroke instanceof Two.Texture) {
+ this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
+ }
+
+ this._stroke = f;
+ this._flagStroke = true;
+
+ if (this._stroke instanceof Two.Gradient
+ || this._stroke instanceof Two.LinearGradient
+ || this._stroke instanceof Two.RadialGradient
+ || this._stroke instanceof Two.Texture) {
+ this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
+ }
+
+ }
+ });
+
+ Object.defineProperty(object, 'length', {
+ get: function() {
+ if (this._flagLength) {
+ this._updateLength();
+ }
+ return this._length;
+ }
+ });
+
+ Object.defineProperty(object, 'closed', {
+ enumerable: true,
+ get: function() {
+ return this._closed;
+ },
+ set: function(v) {
+ this._closed = !!v;
+ this._flagVertices = true;
+ }
+ });
+
+ Object.defineProperty(object, 'curved', {
+ enumerable: true,
+ get: function() {
+ return this._curved;
+ },
+ set: function(v) {
+ this._curved = !!v;
+ this._flagVertices = true;
+ }
+ });
+
+ Object.defineProperty(object, 'automatic', {
+ enumerable: true,
+ get: function() {
+ return this._automatic;
+ },
+ set: function(v) {
+ if (v === this._automatic) {
+ return;
+ }
+ this._automatic = !!v;
+ var method = this._automatic ? 'ignore' : 'listen';
+ _.each(this.vertices, function(v) {
+ v[method]();
+ });
+ }
+ });
+
+ Object.defineProperty(object, 'beginning', {
+ enumerable: true,
+ get: function() {
+ return this._beginning;
+ },
+ set: function(v) {
+ this._beginning = v;
+ this._flagVertices = true;
+ }
+ });
+
+ Object.defineProperty(object, 'ending', {
+ enumerable: true,
+ get: function() {
+ return this._ending;
+ },
+ set: function(v) {
+ this._ending = v;
+ this._flagVertices = true;
+ }
+ });
+
+ Object.defineProperty(object, 'vertices', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._collection;
+ },
+
+ set: function(vertices) {
+
+ var updateVertices = this._renderer.flagVertices;
+ var bindVertices = this._renderer.bindVertices;
+ var unbindVertices = this._renderer.unbindVertices;
+
+ // Remove previous listeners
+ if (this._collection) {
+ this._collection
+ .unbind(Two.Events.insert, bindVertices)
+ .unbind(Two.Events.remove, unbindVertices);
+ }
+
+ // Create new Collection with copy of vertices
+ this._collection = new Two.Utils.Collection((vertices || []).slice(0));
+
+ // Listen for Collection changes and bind / unbind
+ this._collection
+ .bind(Two.Events.insert, bindVertices)
+ .bind(Two.Events.remove, unbindVertices);
+
+ // Bind Initial Vertices
+ bindVertices(this._collection);
+
+ }
+
+ });
+
+ Object.defineProperty(object, 'clip', {
+ enumerable: true,
+ get: function() {
+ return this._clip;
+ },
+ set: function(v) {
+ this._clip = v;
+ this._flagClip = true;
+ }
+ });
+
+ }
+
+ });
+
+ _.extend(Path.prototype, Two.Shape.prototype, {
+
+ // Flags
+ // http://en.wikipedia.org/wiki/Flag
+
+ _flagVertices: true,
+ _flagLength: true,
+
+ _flagFill: true,
+ _flagStroke: true,
+ _flagLinewidth: true,
+ _flagOpacity: true,
+ _flagVisible: true,
+
+ _flagCap: true,
+ _flagJoin: true,
+ _flagMiter: true,
+
+ _flagClip: false,
+
+ // Underlying Properties
+
+ _length: 0,
+
+ _fill: '#fff',
+ _stroke: '#000',
+ _linewidth: 1.0,
+ _opacity: 1.0,
+ _visible: true,
+
+ _cap: 'round',
+ _join: 'round',
+ _miter: 4,
+
+ _closed: true,
+ _curved: false,
+ _automatic: true,
+ _beginning: 0,
+ _ending: 1.0,
+
+ _clip: false,
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var points = _.map(this.vertices, function(v) {
+ return v.clone();
+ });
+
+ var clone = new Path(points, this.closed, this.curved, !this.automatic);
+
+ _.each(Two.Path.Properties, function(k) {
+ clone[k] = this[k];
+ }, this);
+
+ clone.translation.copy(this.translation);
+ clone.rotation = this.rotation;
+ clone.scale = this.scale;
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = {
+ vertices: _.map(this.vertices, function(v) {
+ return v.toObject();
+ })
+ };
+
+ _.each(Two.Shape.Properties, function(k) {
+ result[k] = this[k];
+ }, this);
+
+ result.translation = this.translation.toObject;
+ result.rotation = this.rotation;
+ result.scale = this.scale;
+
+ return result;
+
+ },
+
+ noFill: function() {
+ this.fill = 'transparent';
+ return this;
+ },
+
+ noStroke: function() {
+ this.stroke = 'transparent';
+ return this;
+ },
+
+ /**
+ * Orient the vertices of the shape to the upper lefthand
+ * corner of the path.
+ */
+ corner: function() {
+
+ var rect = this.getBoundingClientRect(true);
+
+ rect.centroid = {
+ x: rect.left + rect.width / 2,
+ y: rect.top + rect.height / 2
+ };
+
+ _.each(this.vertices, function(v) {
+ v.addSelf(rect.centroid);
+ });
+
+ return this;
+
+ },
+
+ /**
+ * Orient the vertices of the shape to the center of the
+ * path.
+ */
+ center: function() {
+
+ var rect = this.getBoundingClientRect(true);
+
+ rect.centroid = {
+ x: rect.left + rect.width / 2,
+ y: rect.top + rect.height / 2
+ };
+
+ _.each(this.vertices, function(v) {
+ v.subSelf(rect.centroid);
+ });
+
+ // this.translation.addSelf(rect.centroid);
+
+ return this;
+
+ },
+
+ /**
+ * Remove self from the scene / parent.
+ */
+ remove: function() {
+
+ if (!this.parent) {
+ return this;
+ }
+
+ this.parent.remove(this);
+
+ return this;
+
+ },
+
+ /**
+ * Return an object with top, left, right, bottom, width, and height
+ * parameters of the group.
+ */
+ getBoundingClientRect: function(shallow) {
+ var matrix, border, l, x, y, i, v;
+
+ var left = Infinity, right = -Infinity,
+ top = Infinity, bottom = -Infinity;
+
+ // TODO: Update this to not __always__ update. Just when it needs to.
+ this._update(true);
+
+ matrix = !!shallow ? this._matrix : getComputedMatrix(this);
+
+ border = this.linewidth / 2;
+ l = this._vertices.length;
+
+ if (l <= 0) {
+ v = matrix.multiply(0, 0, 1);
+ return {
+ top: v.y,
+ left: v.x,
+ right: v.x,
+ bottom: v.y,
+ width: 0,
+ height: 0
+ };
+ }
+
+ for (i = 0; i < l; i++) {
+ v = this._vertices[i];
+
+ x = v.x;
+ y = v.y;
+
+ v = matrix.multiply(x, y, 1);
+ top = min(v.y - border, top);
+ left = min(v.x - border, left);
+ right = max(v.x + border, right);
+ bottom = max(v.y + border, bottom);
+ }
+
+ return {
+ top: top,
+ left: left,
+ right: right,
+ bottom: bottom,
+ width: right - left,
+ height: bottom - top
+ };
+
+ },
+
+ /**
+ * Given a float `t` from 0 to 1, return a point or assign a passed `obj`'s
+ * coordinates to that percentage on this Two.Path's curve.
+ */
+ getPointAt: function(t, obj) {
+ var ia, ib;
+ var x, x1, x2, x3, x4, y, y1, y2, y3, y4, left, right;
+ var target = this.length * Math.min(Math.max(t, 0), 1);
+ var length = this.vertices.length;
+ var last = length - 1;
+
+ var a = null;
+ var b = null;
+
+ for (var i = 0, l = this._lengths.length, sum = 0; i < l; i++) {
+
+ if (sum + this._lengths[i] >= target) {
+
+ if (this._closed) {
+ ia = Two.Utils.mod(i, length);
+ ib = Two.Utils.mod(i - 1, length);
+ if (i === 0) {
+ ia = ib;
+ ib = i;
+ }
+ } else {
+ ia = i;
+ ib = Math.min(Math.max(i - 1, 0), last);
+ }
+
+ a = this.vertices[ia];
+ b = this.vertices[ib];
+ target -= sum;
+ if (this._lengths[i] !== 0) {
+ t = target / this._lengths[i];
+ }
+
+ break;
+
+ }
+
+ sum += this._lengths[i];
+
+ }
+
+ // console.log(sum, a.command, b.command);
+
+ if (_.isNull(a) || _.isNull(b)) {
+ return null;
+ }
+
+ right = b.controls && b.controls.right;
+ left = a.controls && a.controls.left;
+
+ x1 = b.x;
+ y1 = b.y;
+ x2 = (right || b).x;
+ y2 = (right || b).y;
+ x3 = (left || a).x;
+ y3 = (left || a).y;
+ x4 = a.x;
+ y4 = a.y;
+
+ if (right && b._relative) {
+ x2 += b.x;
+ y2 += b.y;
+ }
+
+ if (left && a._relative) {
+ x3 += a.x;
+ y3 += a.y;
+ }
+
+ x = Two.Utils.getPointOnCubicBezier(t, x1, x2, x3, x4);
+ y = Two.Utils.getPointOnCubicBezier(t, y1, y2, y3, y4);
+
+ if (_.isObject(obj)) {
+ obj.x = x;
+ obj.y = y;
+ return obj;
+ }
+
+ return new Two.Vector(x, y);
+
+ },
+
+ /**
+ * Based on closed / curved and sorting of vertices plot where all points
+ * should be and where the respective handles should be too.
+ */
+ plot: function() {
+
+ if (this.curved) {
+ Two.Utils.getCurveFromPoints(this._vertices, this.closed);
+ return this;
+ }
+
+ for (var i = 0; i < this._vertices.length; i++) {
+ this._vertices[i]._command = i === 0 ? Two.Commands.move : Two.Commands.line;
+ }
+
+ return this;
+
+ },
+
+ subdivide: function(limit) {
+ //TODO: DRYness (function below)
+ this._update();
+
+ var last = this.vertices.length - 1;
+ var b = this.vertices[last];
+ var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
+ var points = [];
+ _.each(this.vertices, function(a, i) {
+
+ if (i <= 0 && !closed) {
+ b = a;
+ return;
+ }
+
+ if (a.command === Two.Commands.move) {
+ points.push(new Two.Anchor(b.x, b.y));
+ if (i > 0) {
+ points[points.length - 1].command = Two.Commands.line;
+ }
+ b = a;
+ return;
+ }
+
+ var verts = getSubdivisions(a, b, limit);
+ points = points.concat(verts);
+
+ // Assign commands to all the verts
+ _.each(verts, function(v, i) {
+ if (i <= 0 && b.command === Two.Commands.move) {
+ v.command = Two.Commands.move;
+ } else {
+ v.command = Two.Commands.line;
+ }
+ });
+
+ if (i >= last) {
+
+ // TODO: Add check if the two vectors in question are the same values.
+ if (this._closed && this._automatic) {
+
+ b = a;
+
+ verts = getSubdivisions(a, b, limit);
+ points = points.concat(verts);
+
+ // Assign commands to all the verts
+ _.each(verts, function(v, i) {
+ if (i <= 0 && b.command === Two.Commands.move) {
+ v.command = Two.Commands.move;
+ } else {
+ v.command = Two.Commands.line;
+ }
+ });
+
+ } else if (closed) {
+ points.push(new Two.Anchor(a.x, a.y));
+ }
+
+ points[points.length - 1].command = closed ? Two.Commands.close : Two.Commands.line;
+
+ }
+
+ b = a;
+
+ }, this);
+
+ this._automatic = false;
+ this._curved = false;
+ this.vertices = points;
+
+ return this;
+
+ },
+
+ _updateLength: function(limit) {
+ //TODO: DRYness (function above)
+ this._update();
+
+ var length = this.vertices.length;
+ var last = length - 1;
+ var b = this.vertices[last];
+ var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
+ var sum = 0;
+
+ if (_.isUndefined(this._lengths)) {
+ this._lengths = [];
+ }
+
+ _.each(this.vertices, function(a, i) {
+
+ if ((i <= 0 && !closed) || a.command === Two.Commands.move) {
+ b = a;
+ this._lengths[i] = 0;
+ return;
+ }
+
+ this._lengths[i] = getCurveLength(a, b, limit);
+ sum += this._lengths[i];
+
+ if (i >= last && closed) {
+
+ b = this.vertices[(i + 1) % length];
+
+ this._lengths[i + 1] = getCurveLength(a, b, limit);
+ sum += this._lengths[i + 1];
+
+ }
+
+ b = a;
+
+ }, this);
+
+ this._length = sum;
+
+ return this;
+
+ },
+
+ _update: function() {
+
+ if (this._flagVertices) {
+
+ var l = this.vertices.length;
+ var last = l - 1, v;
+
+ // TODO: Should clamp this so that `ia` and `ib`
+ // cannot select non-verts.
+ var ia = round((this._beginning) * last);
+ var ib = round((this._ending) * last);
+
+ this._vertices.length = 0;
+
+ for (var i = ia; i < ib + 1; i++) {
+ v = this.vertices[i];
+ this._vertices.push(v);
+ }
+
+ if (this._automatic) {
+ this.plot();
+ }
+
+ }
+
+ Two.Shape.prototype._update.apply(this, arguments);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagVertices = this._flagFill = this._flagStroke =
+ this._flagLinewidth = this._flagOpacity = this._flagVisible =
+ this._flagCap = this._flagJoin = this._flagMiter =
+ this._flagClip = false;
+
+ Two.Shape.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Path.MakeObservable(Path.prototype);
+
+ /**
+ * Utility functions
+ */
+
+ function getCurveLength(a, b, limit) {
+ // TODO: DRYness
+ var x1, x2, x3, x4, y1, y2, y3, y4;
+
+ var right = b.controls && b.controls.right;
+ var left = a.controls && a.controls.left;
+
+ x1 = b.x;
+ y1 = b.y;
+ x2 = (right || b).x;
+ y2 = (right || b).y;
+ x3 = (left || a).x;
+ y3 = (left || a).y;
+ x4 = a.x;
+ y4 = a.y;
+
+ if (right && b._relative) {
+ x2 += b.x;
+ y2 += b.y;
+ }
+
+ if (left && a._relative) {
+ x3 += a.x;
+ y3 += a.y;
+ }
+
+ return Two.Utils.getCurveLength(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+ }
+
+ function getSubdivisions(a, b, limit) {
+ // TODO: DRYness
+ var x1, x2, x3, x4, y1, y2, y3, y4;
+
+ var right = b.controls && b.controls.right;
+ var left = a.controls && a.controls.left;
+
+ x1 = b.x;
+ y1 = b.y;
+ x2 = (right || b).x;
+ y2 = (right || b).y;
+ x3 = (left || a).x;
+ y3 = (left || a).y;
+ x4 = a.x;
+ y4 = a.y;
+
+ if (right && b._relative) {
+ x2 += b.x;
+ y2 += b.y;
+ }
+
+ if (left && a._relative) {
+ x3 += a.x;
+ y3 += a.y;
+ }
+
+ return Two.Utils.subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+ }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path;
+ var _ = Two.Utils;
+
+ var Line = Two.Line = function(x1, y1, x2, y2) {
+
+ var width = x2 - x1;
+ var height = y2 - y1;
+
+ var w2 = width / 2;
+ var h2 = height / 2;
+
+ Path.call(this, [
+ new Two.Anchor(- w2, - h2),
+ new Two.Anchor(w2, h2)
+ ]);
+
+ this.translation.set(x1 + w2, y1 + h2);
+
+ };
+
+ _.extend(Line.prototype, Path.prototype);
+
+ Path.MakeObservable(Line.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path;
+ var _ = Two.Utils;
+
+ var Rectangle = Two.Rectangle = function(x, y, width, height) {
+
+ Path.call(this, [
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor()
+ ], true);
+
+ this.width = width;
+ this.height = height;
+ this._update();
+
+ this.translation.set(x, y);
+
+ };
+
+ _.extend(Rectangle, {
+
+ Properties: ['width', 'height'],
+
+ MakeObservable: function(obj) {
+ Path.MakeObservable(obj);
+ _.each(Rectangle.Properties, Two.Utils.defineProperty, obj);
+ }
+
+ });
+
+ _.extend(Rectangle.prototype, Path.prototype, {
+
+ _width: 0,
+ _height: 0,
+
+ _flagWidth: 0,
+ _flagHeight: 0,
+
+ _update: function() {
+
+ if (this._flagWidth || this._flagHeight) {
+
+ var xr = this._width / 2;
+ var yr = this._height / 2;
+
+ this.vertices[0].set(-xr, -yr);
+ this.vertices[1].set(xr, -yr);
+ this.vertices[2].set(xr, yr);
+ this.vertices[3].set(-xr, yr);
+
+ }
+
+ Path.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagWidth = this._flagHeight = false;
+ Path.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Rectangle.MakeObservable(Rectangle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+ var _ = Two.Utils;
+
+ var Ellipse = Two.Ellipse = function(ox, oy, rx, ry) {
+
+ if (!_.isNumber(ry)) {
+ ry = rx;
+ }
+
+ var amount = Two.Resolution;
+
+ var points = _.map(_.range(amount), function(i) {
+ return new Two.Anchor();
+ }, this);
+
+ Path.call(this, points, true, true);
+
+ this.width = rx * 2;
+ this.height = ry * 2;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ };
+
+ _.extend(Ellipse, {
+
+ Properties: ['width', 'height'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(Ellipse.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(Ellipse.prototype, Path.prototype, {
+
+ _width: 0,
+ _height: 0,
+
+ _flagWidth: false,
+ _flagHeight: false,
+
+ _update: function() {
+
+ if (this._flagWidth || this._flagHeight) {
+ for (var i = 0, l = this.vertices.length; i < l; i++) {
+ var pct = i / l;
+ var theta = pct * TWO_PI;
+ var x = this._width * cos(theta) / 2;
+ var y = this._height * sin(theta) / 2;
+ this.vertices[i].set(x, y);
+ }
+ }
+
+ Path.prototype._update.call(this);
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagWidth = this._flagHeight = false;
+
+ Path.prototype.flagReset.call(this);
+ return this;
+
+ }
+
+ });
+
+ Ellipse.MakeObservable(Ellipse.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+ var _ = Two.Utils;
+
+ var Circle = Two.Circle = function(ox, oy, r) {
+
+ var amount = Two.Resolution;
+
+ var points = _.map(_.range(amount), function(i) {
+ return new Two.Anchor();
+ }, this);
+
+ Path.call(this, points, true, true);
+
+ this.radius = r;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ };
+
+ _.extend(Circle, {
+
+ Properties: ['radius'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(Circle.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(Circle.prototype, Path.prototype, {
+
+ _radius: 0,
+ _flagRadius: false,
+
+ _update: function() {
+
+ if (this._flagRadius) {
+ for (var i = 0, l = this.vertices.length; i < l; i++) {
+ var pct = i / l;
+ var theta = pct * TWO_PI;
+ var x = this._radius * cos(theta);
+ var y = this._radius * sin(theta);
+ this.vertices[i].set(x, y);
+ }
+ }
+
+ Path.prototype._update.call(this);
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagRadius = false;
+
+ Path.prototype.flagReset.call(this);
+ return this;
+
+ }
+
+ });
+
+ Circle.MakeObservable(Circle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+ var _ = Two.Utils;
+
+ var Polygon = Two.Polygon = function(ox, oy, r, sides) {
+
+ sides = Math.max(sides || 0, 3);
+
+ var points = _.map(_.range(sides), function(i) {
+ return new Two.Anchor();
+ });
+
+ Path.call(this, points, true);
+
+ this.width = r * 2;
+ this.height = r * 2;
+ this.sides = sides;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ };
+
+ _.extend(Polygon, {
+
+ Properties: ['width', 'height', 'sides'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(Polygon.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(Polygon.prototype, Path.prototype, {
+
+ _width: 0,
+ _height: 0,
+ _sides: 0,
+
+ _flagWidth: false,
+ _flagHeight: false,
+ _flagSides: false,
+
+ _update: function() {
+
+ if (this._flagWidth || this._flagHeight || this._flagSides) {
+
+ var sides = this._sides;
+ var amount = this.vertices.length;
+
+ if (amount > sides) {
+ this.vertices.splice(sides - 1, amount - sides);
+ }
+
+ for (var i = 0; i < sides; i++) {
+
+ var pct = (i + 0.5) / sides;
+ var theta = TWO_PI * pct + Math.PI / 2;
+ var x = this._width * cos(theta);
+ var y = this._height * sin(theta);
+
+ if (i >= amount) {
+ this.vertices.push(new Two.Anchor(x, y));
+ } else {
+ this.vertices[i].set(x, y);
+ }
+
+ }
+
+ }
+
+ Path.prototype._update.call(this);
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagWidth = this._flagHeight = this._flagSides = false;
+ Path.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Polygon.MakeObservable(Polygon.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path, PI = Math.PI, TWO_PI = Math.PI * 2, HALF_PI = Math.PI / 2,
+ cos = Math.cos, sin = Math.sin, abs = Math.abs, _ = Two.Utils;
+
+ var ArcSegment = Two.ArcSegment = function(ox, oy, ir, or, sa, ea, res) {
+
+ var points = _.map(_.range(res || (Two.Resolution * 3)), function() {
+ return new Two.Anchor();
+ });
+
+ Path.call(this, points, false, false, true);
+
+ this.innerRadius = ir;
+ this.outerRadius = or;
+
+ this.startAngle = sa;
+ this.endAngle = ea;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ }
+
+ _.extend(ArcSegment, {
+
+ Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(ArcSegment.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(ArcSegment.prototype, Path.prototype, {
+
+ _flagStartAngle: false,
+ _flagEndAngle: false,
+ _flagInnerRadius: false,
+ _flagOuterRadius: false,
+
+ _startAngle: 0,
+ _endAngle: TWO_PI,
+ _innerRadius: 0,
+ _outerRadius: 0,
+
+ _update: function() {
+
+ if (this._flagStartAngle || this._flagEndAngle || this._flagInnerRadius
+ || this._flagOuterRadius) {
+
+ var sa = this._startAngle;
+ var ea = this._endAngle;
+
+ var ir = this._innerRadius;
+ var or = this._outerRadius;
+
+ var connected = mod(sa, TWO_PI) === mod(ea, TWO_PI);
+ var punctured = ir > 0;
+
+ var vertices = this.vertices;
+ var length = (punctured ? vertices.length / 2 : vertices.length);
+ var command, id = 0;
+
+ if (connected) {
+ length--;
+ } else if (!punctured) {
+ length -= 2;
+ }
+
+ /**
+ * Outer Circle
+ */
+ for (var i = 0, last = length - 1; i < length; i++) {
+
+ var pct = i / last;
+ var v = vertices[id];
+ var theta = pct * (ea - sa) + sa;
+ var step = (ea - sa) / length;
+
+ var x = or * Math.cos(theta);
+ var y = or * Math.sin(theta);
+
+ switch (i) {
+ case 0:
+ command = Two.Commands.move;
+ break;
+ default:
+ command = Two.Commands.curve;
+ }
+
+ v.command = command;
+ v.x = x;
+ v.y = y;
+ v.controls.left.clear();
+ v.controls.right.clear();
+
+ if (v.command === Two.Commands.curve) {
+ var amp = or * step / Math.PI;
+ v.controls.left.x = amp * Math.cos(theta - HALF_PI);
+ v.controls.left.y = amp * Math.sin(theta - HALF_PI);
+ v.controls.right.x = amp * Math.cos(theta + HALF_PI);
+ v.controls.right.y = amp * Math.sin(theta + HALF_PI);
+ if (i === 1) {
+ v.controls.left.multiplyScalar(2);
+ }
+ if (i === last) {
+ v.controls.right.multiplyScalar(2);
+ }
+ }
+
+ id++;
+
+ }
+
+ if (punctured) {
+
+ if (connected) {
+ vertices[id].command = Two.Commands.close;
+ id++;
+ } else {
+ length--;
+ last = length - 1;
+ }
+
+ /**
+ * Inner Circle
+ */
+ for (i = 0; i < length; i++) {
+
+ pct = i / last;
+ v = vertices[id];
+ theta = (1 - pct) * (ea - sa) + sa;
+ step = (ea - sa) / length;
+
+ x = ir * Math.cos(theta);
+ y = ir * Math.sin(theta);
+ command = Two.Commands.curve;
+ if (i <= 0) {
+ command = connected ? Two.Commands.move : Two.Commands.line;
+ }
+
+ v.command = command;
+ v.x = x;
+ v.y = y;
+ v.controls.left.clear();
+ v.controls.right.clear();
+
+ if (v.command === Two.Commands.curve) {
+ amp = ir * step / Math.PI;
+ v.controls.left.x = amp * Math.cos(theta + HALF_PI);
+ v.controls.left.y = amp * Math.sin(theta + HALF_PI);
+ v.controls.right.x = amp * Math.cos(theta - HALF_PI);
+ v.controls.right.y = amp * Math.sin(theta - HALF_PI);
+ if (i === 1) {
+ v.controls.left.multiplyScalar(2);
+ }
+ if (i === last) {
+ v.controls.right.multiplyScalar(2);
+ }
+ }
+
+ id++;
+
+ }
+
+ } else if (!connected) {
+
+ vertices[id].command = Two.Commands.line;
+ vertices[id].x = 0;
+ vertices[id].y = 0;
+ id++;
+
+ }
+
+ /**
+ * Final Point
+ */
+ vertices[id].command = Two.Commands.close;
+
+ }
+
+ Path.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ Path.prototype.flagReset.call(this);
+
+ this._flagStartAngle = this._flagEndAngle
+ = this._flagInnerRadius = this._flagOuterRadius = false;
+
+ return this;
+
+ }
+
+ });
+
+ ArcSegment.MakeObservable(ArcSegment.prototype);
+
+ function mod(v, l) {
+ while (v < 0) {
+ v += l;
+ }
+ return v % l;
+ }
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+ var _ = Two.Utils;
+
+ var Star = Two.Star = function(ox, oy, or, ir, sides) {
+
+ if (!_.isNumber(ir)) {
+ ir = or / 2;
+ }
+
+ if (!_.isNumber(sides) || sides <= 0) {
+ sides = 5;
+ }
+
+ var length = sides * 2;
+
+ var points = _.map(_.range(length), function(i) {
+ return new Two.Anchor();
+ });
+
+ Path.call(this, points, true);
+
+ this.innerRadius = ir;
+ this.outerRadius = or;
+ this.sides = sides;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ };
+
+ _.extend(Star, {
+
+ Properties: ['innerRadius', 'outerRadius', 'sides'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(Star.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(Star.prototype, Path.prototype, {
+
+ _innerRadius: 0,
+ _outerRadius: 0,
+ _sides: 0,
+
+ _flagInnerRadius: false,
+ _flagOuterRadius: false,
+ _flagSides: false,
+
+ _update: function() {
+
+ if (this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
+
+ var sides = this._sides * 2;
+ var amount = this.vertices.length;
+
+ if (amount > sides) {
+ this.vertices.splice(sides - 1, amount - sides);
+ }
+
+ for (var i = 0; i < sides; i++) {
+
+ var pct = (i + 0.5) / sides;
+ var theta = TWO_PI * pct;
+ var r = (i % 2 ? this._innerRadius : this._outerRadius);
+ var x = r * cos(theta);
+ var y = r * sin(theta);
+
+ if (i >= amount) {
+ this.vertices.push(new Two.Anchor(x, y));
+ } else {
+ this.vertices[i].set(x, y);
+ }
+
+ }
+
+ }
+
+ Path.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
+ Path.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Star.MakeObservable(Star.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var Path = Two.Path;
+ var _ = Two.Utils;
+
+ var RoundedRectangle = Two.RoundedRectangle = function(ox, oy, width, height, radius) {
+
+ if (!_.isNumber(radius)) {
+ radius = Math.floor(Math.min(width, height) / 12);
+ }
+
+ var amount = 10;
+
+ var points = _.map(_.range(amount), function(i) {
+ return new Two.Anchor(0, 0, 0, 0, 0, 0,
+ i === 0 ? Two.Commands.move : Two.Commands.curve);
+ });
+
+ points[points.length - 1].command = Two.Commands.close;
+
+ Path.call(this, points, false, false, true);
+
+ this.width = width;
+ this.height = height;
+ this.radius = radius;
+
+ this._update();
+ this.translation.set(ox, oy);
+
+ };
+
+ _.extend(RoundedRectangle, {
+
+ Properties: ['width', 'height', 'radius'],
+
+ MakeObservable: function(obj) {
+
+ Path.MakeObservable(obj);
+ _.each(RoundedRectangle.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ });
+
+ _.extend(RoundedRectangle.prototype, Path.prototype, {
+
+ _width: 0,
+ _height: 0,
+ _radius: 0,
+
+ _flagWidth: false,
+ _flagHeight: false,
+ _flagRadius: false,
+
+ _update: function() {
+
+ if (this._flagWidth || this._flagHeight || this._flagRadius) {
+
+ var width = this._width;
+ var height = this._height;
+ var radius = Math.min(Math.max(this._radius, 0),
+ Math.min(width, height));
+
+ var v;
+ var w = width / 2;
+ var h = height / 2;
+
+ v = this.vertices[0];
+ v.x = - (w - radius);
+ v.y = - h;
+
+ // Upper Right Corner
+
+ v = this.vertices[1];
+ v.x = (w - radius);
+ v.y = - h;
+ v.controls.left.clear();
+ v.controls.right.x = radius;
+ v.controls.right.y = 0;
+
+ v = this.vertices[2];
+ v.x = w;
+ v.y = - (h - radius);
+ v.controls.right.clear();
+ v.controls.left.clear();
+
+ // Bottom Right Corner
+
+ v = this.vertices[3];
+ v.x = w;
+ v.y = (h - radius);
+ v.controls.left.clear();
+ v.controls.right.x = 0;
+ v.controls.right.y = radius;
+
+ v = this.vertices[4];
+ v.x = (w - radius);
+ v.y = h;
+ v.controls.right.clear();
+ v.controls.left.clear();
+
+ // Bottom Left Corner
+
+ v = this.vertices[5];
+ v.x = - (w - radius);
+ v.y = h;
+ v.controls.left.clear();
+ v.controls.right.x = - radius;
+ v.controls.right.y = 0;
+
+ v = this.vertices[6];
+ v.x = - w;
+ v.y = (h - radius);
+ v.controls.left.clear();
+ v.controls.right.clear();
+
+ // Upper Left Corner
+
+ v = this.vertices[7];
+ v.x = - w;
+ v.y = - (h - radius);
+ v.controls.left.clear();
+ v.controls.right.x = 0;
+ v.controls.right.y = - radius;
+
+ v = this.vertices[8];
+ v.x = - (w - radius);
+ v.y = - h;
+ v.controls.left.clear();
+ v.controls.right.clear();
+
+ v = this.vertices[9];
+ v.copy(this.vertices[8]);
+
+ }
+
+ Path.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagWidth = this._flagHeight = this._flagRadius = false;
+ Path.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var root = Two.root;
+ var getComputedMatrix = Two.Utils.getComputedMatrix;
+ var _ = Two.Utils;
+
+ var canvas = (root.document ? root.document.createElement('canvas') : { getContext: _.identity });
+ var ctx = canvas.getContext('2d');
+
+ var Text = Two.Text = function(message, x, y, styles) {
+
+ Two.Shape.call(this);
+
+ this._renderer.type = 'text';
+ this._renderer.flagFill = _.bind(Text.FlagFill, this);
+ this._renderer.flagStroke = _.bind(Text.FlagStroke, this);
+
+ this.value = message;
+
+ if (_.isNumber(x)) {
+ this.translation.x = x;
+ }
+ if (_.isNumber(y)) {
+ this.translation.y = y;
+ }
+
+ if (!_.isObject(styles)) {
+ return this;
+ }
+
+ _.each(Two.Text.Properties, function(property) {
+
+ if (property in styles) {
+ this[property] = styles[property];
+ }
+
+ }, this);
+
+ };
+
+ _.extend(Two.Text, {
+
+ Properties: [
+ 'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
+ 'weight', 'decoration', 'baseline', 'opacity', 'visible', 'fill', 'stroke'
+ ],
+
+ FlagFill: function() {
+ this._flagFill = true;
+ },
+
+ FlagStroke: function() {
+ this._flagStroke = true;
+ },
+
+ MakeObservable: function(object) {
+
+ Two.Shape.MakeObservable(object);
+
+ _.each(Two.Text.Properties.slice(0, 12), Two.Utils.defineProperty, object);
+
+ Object.defineProperty(object, 'fill', {
+ enumerable: true,
+ get: function() {
+ return this._fill;
+ },
+ set: function(f) {
+
+ if (this._fill instanceof Two.Gradient
+ || this._fill instanceof Two.LinearGradient
+ || this._fill instanceof Two.RadialGradient
+ || this._fill instanceof Two.Texture) {
+ this._fill.unbind(Two.Events.change, this._renderer.flagFill);
+ }
+
+ this._fill = f;
+ this._flagFill = true;
+
+ if (this._fill instanceof Two.Gradient
+ || this._fill instanceof Two.LinearGradient
+ || this._fill instanceof Two.RadialGradient
+ || this._fill instanceof Two.Texture) {
+ this._fill.bind(Two.Events.change, this._renderer.flagFill);
+ }
+
+ }
+ });
+
+ Object.defineProperty(object, 'stroke', {
+ enumerable: true,
+ get: function() {
+ return this._stroke;
+ },
+ set: function(f) {
+
+ if (this._stroke instanceof Two.Gradient
+ || this._stroke instanceof Two.LinearGradient
+ || this._stroke instanceof Two.RadialGradient
+ || this._stroke instanceof Two.Texture) {
+ this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
+ }
+
+ this._stroke = f;
+ this._flagStroke = true;
+
+ if (this._stroke instanceof Two.Gradient
+ || this._stroke instanceof Two.LinearGradient
+ || this._stroke instanceof Two.RadialGradient
+ || this._stroke instanceof Two.Texture) {
+ this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
+ }
+
+ }
+ });
+
+ Object.defineProperty(object, 'clip', {
+ enumerable: true,
+ get: function() {
+ return this._clip;
+ },
+ set: function(v) {
+ this._clip = v;
+ this._flagClip = true;
+ }
+ });
+
+ }
+
+ });
+
+ _.extend(Two.Text.prototype, Two.Shape.prototype, {
+
+ // Flags
+ // http://en.wikipedia.org/wiki/Flag
+
+ _flagValue: true,
+ _flagFamily: true,
+ _flagSize: true,
+ _flagLeading: true,
+ _flagAlignment: true,
+ _flagBaseline: true,
+ _flagStyle: true,
+ _flagWeight: true,
+ _flagDecoration: true,
+
+ _flagFill: true,
+ _flagStroke: true,
+ _flagLinewidth: true,
+ _flagOpacity: true,
+ _flagVisible: true,
+
+ _flagClip: false,
+
+ // Underlying Properties
+
+ _value: '',
+ _family: 'sans-serif',
+ _size: 13,
+ _leading: 17,
+ _alignment: 'center',
+ _baseline: 'middle',
+ _style: 'normal',
+ _weight: 500,
+ _decoration: 'none',
+
+ _fill: '#000',
+ _stroke: 'transparent',
+ _linewidth: 1,
+ _opacity: 1,
+ _visible: true,
+
+ _clip: false,
+
+ remove: function() {
+
+ if (!this.parent) {
+ return this;
+ }
+
+ this.parent.remove(this);
+
+ return this;
+
+ },
+
+ clone: function(parent) {
+
+ var parent = parent || this.parent;
+
+ var clone = new Two.Text(this.value);
+ clone.translation.copy(this.translation);
+ clone.rotation = this.rotation;
+ clone.scale = this.scale;
+
+ _.each(Two.Text.Properties, function(property) {
+ clone[property] = this[property];
+ }, this);
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = {
+ translation: this.translation.toObject(),
+ rotation: this.rotation,
+ scale: this.scale
+ };
+
+ _.each(Two.Text.Properties, function(property) {
+ result[property] = this[property];
+ }, this);
+
+ return result;
+
+ },
+
+ noStroke: function() {
+ this.stroke = 'transparent';
+ return this;
+ },
+
+ noFill: function() {
+ this.fill = 'transparent';
+ return this;
+ },
+
+ /**
+ * A shim to not break `getBoundingClientRect` calls. TODO: Implement a
+ * way to calculate proper bounding boxes of `Two.Text`.
+ */
+ getBoundingClientRect: function(shallow) {
+
+ var matrix, border, l, x, y, i, v;
+
+ var left = Infinity, right = -Infinity,
+ top = Infinity, bottom = -Infinity;
+
+ // TODO: Update this to not __always__ update. Just when it needs to.
+ this._update(true);
+
+ matrix = !!shallow ? this._matrix : getComputedMatrix(this);
+
+ v = matrix.multiply(0, 0, 1);
+
+ return {
+ top: v.x,
+ left: v.y,
+ right: v.x,
+ bottom: v.y,
+ width: 0,
+ height: 0
+ };
+
+ },
+
+ flagReset: function() {
+
+ this._flagValue = this._flagFamily = this._flagSize =
+ this._flagLeading = this._flagAlignment = this._flagFill =
+ this._flagStroke = this._flagLinewidth = this._flagOpaicty =
+ this._flagVisible = this._flagClip = this._flagDecoration =
+ this._flagBaseline = false;
+
+ Two.Shape.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Two.Text.MakeObservable(Two.Text.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var Stop = Two.Stop = function(offset, color, opacity) {
+
+ this._renderer = {};
+ this._renderer.type = 'stop';
+
+ this.offset = _.isNumber(offset) ? offset
+ : Stop.Index <= 0 ? 0 : 1;
+
+ this.opacity = _.isNumber(opacity) ? opacity : 1;
+
+ this.color = _.isString(color) ? color
+ : Stop.Index <= 0 ? '#fff' : '#000';
+
+ Stop.Index = (Stop.Index + 1) % 2;
+
+ };
+
+ _.extend(Stop, {
+
+ Index: 0,
+
+ Properties: [
+ 'offset',
+ 'opacity',
+ 'color'
+ ],
+
+ MakeObservable: function(object) {
+
+ _.each(Stop.Properties, function(property) {
+
+ var object = this;
+ var secret = '_' + property;
+ var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+ Object.defineProperty(object, property, {
+ enumerable: true,
+ get: function() {
+ return this[secret];
+ },
+ set: function(v) {
+ this[secret] = v;
+ this[flag] = true;
+ if (this.parent) {
+ this.parent._flagStops = true;
+ }
+ }
+ });
+
+ }, object);
+
+ }
+
+ });
+
+ _.extend(Stop.prototype, Two.Utils.Events, {
+
+ clone: function() {
+
+ var clone = new Stop();
+
+ _.each(Stop.Properties, function(property) {
+ clone[property] = this[property];
+ }, this);
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = {};
+
+ _.each(Stop.Properties, function(k) {
+ result[k] = this[k];
+ }, this);
+
+ return result;
+
+ },
+
+ flagReset: function() {
+
+ this._flagOffset = this._flagColor = this._flagOpacity = false;
+
+ return this;
+
+ }
+
+ });
+
+ Stop.MakeObservable(Stop.prototype);
+
+ var Gradient = Two.Gradient = function(stops) {
+
+ this._renderer = {};
+ this._renderer.type = 'gradient';
+
+ this.id = Two.Identifier + Two.uniqueId();
+ this.classList = [];
+
+ this._renderer.flagStops = _.bind(Gradient.FlagStops, this);
+ this._renderer.bindStops = _.bind(Gradient.BindStops, this);
+ this._renderer.unbindStops = _.bind(Gradient.UnbindStops, this);
+
+ this.spread = 'pad';
+
+ this.stops = stops;
+
+ };
+
+ _.extend(Gradient, {
+
+ Stop: Stop,
+
+ Properties: [
+ 'spread'
+ ],
+
+ MakeObservable: function(object) {
+
+ _.each(Gradient.Properties, Two.Utils.defineProperty, object);
+
+ Object.defineProperty(object, 'stops', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._stops;
+ },
+
+ set: function(stops) {
+
+ var updateStops = this._renderer.flagStops;
+ var bindStops = this._renderer.bindStops;
+ var unbindStops = this._renderer.unbindStops;
+
+ // Remove previous listeners
+ if (this._stops) {
+ this._stops
+ .unbind(Two.Events.insert, bindStops)
+ .unbind(Two.Events.remove, unbindStops);
+ }
+
+ // Create new Collection with copy of Stops
+ this._stops = new Two.Utils.Collection((stops || []).slice(0));
+
+ // Listen for Collection changes and bind / unbind
+ this._stops
+ .bind(Two.Events.insert, bindStops)
+ .bind(Two.Events.remove, unbindStops);
+
+ // Bind Initial Stops
+ bindStops(this._stops);
+
+ }
+
+ });
+
+ },
+
+ FlagStops: function() {
+ this._flagStops = true;
+ },
+
+ BindStops: function(items) {
+
+ // This function is called a lot
+ // when importing a large SVG
+ var i = items.length;
+ while(i--) {
+ items[i].bind(Two.Events.change, this._renderer.flagStops);
+ items[i].parent = this;
+ }
+
+ this._renderer.flagStops();
+
+ },
+
+ UnbindStops: function(items) {
+
+ var i = items.length;
+ while(i--) {
+ items[i].unbind(Two.Events.change, this._renderer.flagStops);
+ delete items[i].parent;
+ }
+
+ this._renderer.flagStops();
+
+ }
+
+ });
+
+ _.extend(Gradient.prototype, Two.Utils.Events, {
+
+ _flagStops: false,
+ _flagSpread: false,
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var stops = _.map(this.stops, function(s) {
+ return s.clone();
+ });
+
+ var clone = new Gradient(stops);
+
+ _.each(Two.Gradient.Properties, function(k) {
+ clone[k] = this[k];
+ }, this);
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = {
+ stops: _.map(this.stops, function(s) {
+ return s.toObject();
+ })
+ };
+
+ _.each(Gradient.Properties, function(k) {
+ result[k] = this[k];
+ }, this);
+
+ return result;
+
+ },
+
+ _update: function() {
+
+ if (this._flagSpread || this._flagStops) {
+ this.trigger(Two.Events.change);
+ }
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagSpread = this._flagStops = false;
+
+ return this;
+
+ }
+
+ });
+
+ Gradient.MakeObservable(Gradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var LinearGradient = Two.LinearGradient = function(x1, y1, x2, y2, stops) {
+
+ Two.Gradient.call(this, stops);
+
+ this._renderer.type = 'linear-gradient';
+
+ var flagEndPoints = _.bind(LinearGradient.FlagEndPoints, this);
+ this.left = new Two.Vector().bind(Two.Events.change, flagEndPoints);
+ this.right = new Two.Vector().bind(Two.Events.change, flagEndPoints);
+
+ if (_.isNumber(x1)) {
+ this.left.x = x1;
+ }
+ if (_.isNumber(y1)) {
+ this.left.y = y1;
+ }
+ if (_.isNumber(x2)) {
+ this.right.x = x2;
+ }
+ if (_.isNumber(y2)) {
+ this.right.y = y2;
+ }
+
+ };
+
+ _.extend(LinearGradient, {
+
+ Stop: Two.Gradient.Stop,
+
+ MakeObservable: function(object) {
+ Two.Gradient.MakeObservable(object);
+ },
+
+ FlagEndPoints: function() {
+ this._flagEndPoints = true;
+ }
+
+ });
+
+ _.extend(LinearGradient.prototype, Two.Gradient.prototype, {
+
+ _flagEndPoints: false,
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var stops = _.map(this.stops, function(stop) {
+ return stop.clone();
+ });
+
+ var clone = new LinearGradient(this.left._x, this.left._y,
+ this.right._x, this.right._y, stops);
+
+ _.each(Two.Gradient.Properties, function(k) {
+ clone[k] = this[k];
+ }, this);
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = Two.Gradient.prototype.toObject.call(this);
+
+ result.left = this.left.toObject();
+ result.right = this.right.toObject();
+
+ return result;
+
+ },
+
+ _update: function() {
+
+ if (this._flagEndPoints || this._flagSpread || this._flagStops) {
+ this.trigger(Two.Events.change);
+ }
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagEndPoints = false;
+
+ Two.Gradient.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ LinearGradient.MakeObservable(LinearGradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+
+ var RadialGradient = Two.RadialGradient = function(cx, cy, r, stops, fx, fy) {
+
+ Two.Gradient.call(this, stops);
+
+ this._renderer.type = 'radial-gradient';
+
+ this.center = new Two.Vector()
+ .bind(Two.Events.change, _.bind(function() {
+ this._flagCenter = true;
+ }, this));
+
+ this.radius = _.isNumber(r) ? r : 20;
+
+ this.focal = new Two.Vector()
+ .bind(Two.Events.change, _.bind(function() {
+ this._flagFocal = true;
+ }, this));
+
+ if (_.isNumber(cx)) {
+ this.center.x = cx;
+ }
+ if (_.isNumber(cy)) {
+ this.center.y = cy;
+ }
+
+ this.focal.copy(this.center);
+
+ if (_.isNumber(fx)) {
+ this.focal.x = fx;
+ }
+ if (_.isNumber(fy)) {
+ this.focal.y = fy;
+ }
+
+ };
+
+ _.extend(RadialGradient, {
+
+ Stop: Two.Gradient.Stop,
+
+ Properties: [
+ 'radius'
+ ],
+
+ MakeObservable: function(object) {
+
+ Two.Gradient.MakeObservable(object);
+
+ _.each(RadialGradient.Properties, Two.Utils.defineProperty, object);
+
+ }
+
+ });
+
+ _.extend(RadialGradient.prototype, Two.Gradient.prototype, {
+
+ _flagRadius: false,
+ _flagCenter: false,
+ _flagFocal: false,
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var stops = _.map(this.stops, function(stop) {
+ return stop.clone();
+ });
+
+ var clone = new RadialGradient(this.center._x, this.center._y,
+ this._radius, stops, this.focal._x, this.focal._y);
+
+ _.each(Two.Gradient.Properties.concat(RadialGradient.Properties), function(k) {
+ clone[k] = this[k];
+ }, this);
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ toObject: function() {
+
+ var result = Two.Gradient.prototype.toObject.call(this);
+
+ _.each(RadialGradient.Properties, function(k) {
+ result[k] = this[k];
+ }, this);
+
+ result.center = this.center.toObject();
+ result.focal = this.focal.toObject();
+
+ return result;
+
+ },
+
+ _update: function() {
+
+ if (this._flagRadius || this._flatCenter || this._flagFocal
+ || this._flagSpread || this._flagStops) {
+ this.trigger(Two.Events.change);
+ }
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagRadius = this._flagCenter = this._flagFocal = false;
+
+ Two.Gradient.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ RadialGradient.MakeObservable(RadialGradient.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+ var anchor;
+ var regex = {
+ video: /\.(mp4|webm)$/i,
+ image: /\.(jpe?g|png|gif|tiff)$/i
+ };
+
+ if (this.document) {
+ anchor = document.createElement('a');
+ }
+
+ var Texture = Two.Texture = function(src, callback) {
+
+ this._renderer = {};
+ this._renderer.type = 'texture';
+ this._renderer.flagOffset = _.bind(Texture.FlagOffset, this);
+ this._renderer.flagScale = _.bind(Texture.FlagScale, this);
+
+ this.id = Two.Identifier + Two.uniqueId();
+ this.classList = [];
+
+ this.offset = new Two.Vector();
+
+ if (_.isFunction(callback)) {
+ var loaded = _.bind(function() {
+ this.unbind(Two.Events.load, loaded);
+ if (_.isFunction(callback)) {
+ callback();
+ }
+ }, this);
+ this.bind(Two.Events.load, loaded);
+ }
+
+ if (_.isString(src)) {
+ this.src = src;
+ } else if (_.isElement(src)) {
+ this.image = src;
+ }
+
+ this._update();
+
+ };
+
+ _.extend(Texture, {
+
+ Properties: [
+ 'src',
+ 'loaded',
+ 'repeat'
+ ],
+
+ ImageRegistry: new Two.Registry(),
+
+ getAbsoluteURL: function(path) {
+ if (!anchor) {
+ // TODO: Fix for headless environment
+ return path;
+ }
+ anchor.href = path;
+ return anchor.href;
+ },
+
+ getImage: function(src) {
+
+ var absoluteSrc = Texture.getAbsoluteURL(src);
+
+ if (Texture.ImageRegistry.contains(absoluteSrc)) {
+ return Texture.ImageRegistry.get(absoluteSrc);
+ }
+
+ var image;
+
+ if (regex.video.test(absoluteSrc)) {
+ image = document.createElement('video');
+ } else {
+ image = document.createElement('img');
+ }
+
+ image.crossOrigin = 'anonymous';
+
+ return image;
+
+ },
+
+ Register: {
+ canvas: function(texture, callback) {
+ texture._src = '#' + texture.id;
+ Texture.ImageRegistry.add(texture.src, texture.image);
+ if (_.isFunction(callback)) {
+ callback();
+ }
+ },
+ img: function(texture, callback) {
+
+ var loaded = function(e) {
+ texture.image.removeEventListener('load', loaded, false);
+ texture.image.removeEventListener('error', error, false);
+ if (_.isFunction(callback)) {
+ callback();
+ }
+ };
+ var error = function(e) {
+ texture.image.removeEventListener('load', loaded, false);
+ texture.image.removeEventListener('error', error, false);
+ throw new Two.Utils.Error('unable to load ' + texture.src);
+ };
+
+ if (_.isNumber(texture.image.width) && texture.image.width > 0
+ && _.isNumber(texture.image.height) && texture.image.height > 0) {
+ loaded();
+ } else {
+ texture.image.addEventListener('load', loaded, false);
+ texture.image.addEventListener('error', error, false);
+ }
+
+ texture._src = Texture.getAbsoluteURL(texture._src);
+
+ if (texture.image && texture.image.getAttribute('two-src')) {
+ return;
+ }
+
+ texture.image.setAttribute('two-src', texture.src);
+ Texture.ImageRegistry.add(texture.src, texture.image);
+ texture.image.src = texture.src;
+
+ },
+ video: function(texture, callback) {
+
+ var loaded = function(e) {
+ texture.image.removeEventListener('load', loaded, false);
+ texture.image.removeEventListener('error', error, false);
+ texture.image.width = texture.image.videoWidth;
+ texture.image.height = texture.image.videoHeight;
+ texture.image.play();
+ if (_.isFunction(callback)) {
+ callback();
+ }
+ };
+ var error = function(e) {
+ texture.image.removeEventListener('load', loaded, false);
+ texture.image.removeEventListener('error', error, false);
+ throw new Two.Utils.Error('unable to load ' + texture.src);
+ };
+
+ texture._src = Texture.getAbsoluteURL(texture._src);
+ texture.image.addEventListener('canplaythrough', loaded, false);
+ texture.image.addEventListener('error', error, false);
+
+ if (texture.image && texture.image.getAttribute('two-src')) {
+ return;
+ }
+
+ texture.image.setAttribute('two-src', texture.src);
+ Texture.ImageRegistry.add(texture.src, texture.image);
+ texture.image.src = texture.src;
+ texture.image.loop = true;
+ texture.image.load();
+
+ }
+ },
+
+ load: function(texture, callback) {
+
+ var src = texture.src;
+ var image = texture.image;
+ var tag = image && image.nodeName.toLowerCase();
+
+ if (texture._flagImage) {
+ if (/canvas/i.test(tag)) {
+ Texture.Register.canvas(texture, callback);
+ } else {
+ texture._src = image.getAttribute('two-src') || image.src;
+ Texture.Register[tag](texture, callback);
+ }
+ }
+
+ if (texture._flagSrc) {
+ if (!image) {
+ texture.image = Texture.getImage(texture.src);
+ }
+ tag = texture.image.nodeName.toLowerCase();
+ Texture.Register[tag](texture, callback);
+ }
+
+ },
+
+ FlagOffset: function() {
+ this._flagOffset = true;
+ },
+
+ FlagScale: function() {
+ this._flagScale = true;
+ },
+
+ MakeObservable: function(object) {
+
+ _.each(Texture.Properties, Two.Utils.defineProperty, object);
+
+ Object.defineProperty(object, 'image', {
+ enumerable: true,
+ get: function() {
+ return this._image;
+ },
+ set: function(image) {
+
+ var tag = image && image.nodeName.toLowerCase();
+ var index;
+
+ switch (tag) {
+ case 'canvas':
+ index = '#' + image.id;
+ break;
+ default:
+ index = image.src;
+ }
+
+ if (Texture.ImageRegistry.contains(index)) {
+ this._image = Texture.ImageRegistry.get(image.src);
+ } else {
+ this._image = image;
+ }
+
+ this._flagImage = true;
+
+ }
+
+ });
+
+ Object.defineProperty(object, 'offset', {
+ enumerable: true,
+ get: function() {
+ return this._offset;
+ },
+ set: function(v) {
+ if (this._offset) {
+ this._offset.unbind(Two.Events.change, this._renderer.flagOffset);
+ }
+ this._offset = v;
+ this._offset.bind(Two.Events.change, this._renderer.flagOffset);
+ this._flagOffset = true;
+ }
+ });
+
+ Object.defineProperty(object, 'scale', {
+ enumerable: true,
+ get: function() {
+ return this._scale;
+ },
+ set: function(v) {
+
+ if (this._scale instanceof Two.Vector) {
+ this._scale.unbind(Two.Events.change, this._renderer.flagScale);
+ }
+
+ this._scale = v;
+
+ if (this._scale instanceof Two.Vector) {
+ this._scale.bind(Two.Events.change, this._renderer.flagScale);
+ }
+
+ this._flagScale = true;
+
+ }
+ });
+
+ }
+
+ });
+
+ _.extend(Texture.prototype, Two.Utils.Events, Two.Shape.prototype, {
+
+ _flagSrc: false,
+ _flagImage: false,
+ _flagVideo: false,
+ _flagLoaded: false,
+ _flagRepeat: false,
+
+ _flagOffset: false,
+ _flagScale: false,
+
+ _src: '',
+ _image: null,
+ _loaded: false,
+ _repeat: 'no-repeat',
+
+ _scale: 1,
+ _offset: null,
+
+ clone: function() {
+ return new Texture(this.src);
+ },
+
+ toObject: function() {
+ return {
+ src: this.src,
+ image: this.image
+ }
+ },
+
+ _update: function() {
+
+ if (this._flagSrc || this._flagImage || this._flagVideo) {
+
+ this.trigger(Two.Events.change);
+
+ if (this._flagSrc || this._flagImage) {
+ this.loaded = false;
+ Texture.load(this, _.bind(function() {
+ this.loaded = true;
+ this
+ .trigger(Two.Events.change)
+ .trigger(Two.Events.load);
+ }, this));
+ }
+
+ }
+
+ if (this._image && this._image.readyState >= 4) {
+ this._flagVideo = true;
+ }
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagSrc = this._flagImage = this._flagLoaded
+ = this._flagVideo = this._flagScale = this._flagOffset = false;
+
+ return this;
+
+ }
+
+ });
+
+ Texture.MakeObservable(Texture.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+ var Path = Two.Path;
+ var Rectangle = Two.Rectangle;
+
+ var Sprite = Two.Sprite = function(path, ox, oy, cols, rows, frameRate) {
+
+ Path.call(this, [
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor()
+ ], true);
+
+ this.noStroke();
+ this.noFill();
+
+ if (path instanceof Two.Texture) {
+ this.texture = path;
+ } else if (_.isString(path)) {
+ this.texture = new Two.Texture(path);
+ }
+
+ this._update();
+ this.translation.set(ox || 0, oy || 0);
+
+ if (_.isNumber(cols)) {
+ this.columns = cols;
+ }
+ if (_.isNumber(rows)) {
+ this.rows = rows;
+ }
+ if (_.isNumber(frameRate)) {
+ this.frameRate = frameRate;
+ }
+
+ };
+
+ _.extend(Sprite, {
+
+ Properties: [
+ 'texture', 'columns', 'rows', 'frameRate', 'index'
+ ],
+
+ MakeObservable: function(obj) {
+
+ Rectangle.MakeObservable(obj);
+ _.each(Sprite.Properties, Two.Utils.defineProperty, obj);
+
+ }
+
+ })
+
+ _.extend(Sprite.prototype, Rectangle.prototype, {
+
+ _flagTexture: false,
+ _flagColumns: false,
+ _flagRows: false,
+ _flagFrameRate: false,
+ flagIndex: false,
+
+ // Private variables
+ _amount: 1,
+ _duration: 0,
+ _startTime: 0,
+ _playing: false,
+ _firstFrame: 0,
+ _lastFrame: 0,
+ _loop: true,
+
+ // Exposed through getter-setter
+ _texture: null,
+ _columns: 1,
+ _rows: 1,
+ _frameRate: 0,
+ _index: 0,
+
+ play: function(firstFrame, lastFrame, onLastFrame) {
+
+ this._playing = true;
+ this._firstFrame = 0;
+ this._lastFrame = this.amount - 1;
+ this._startTime = _.performance.now();
+
+ if (_.isNumber(firstFrame)) {
+ this._firstFrame = firstFrame;
+ }
+ if (_.isNumber(lastFrame)) {
+ this._lastFrame = lastFrame;
+ }
+ if (_.isFunction(onLastFrame)) {
+ this._onLastFrame = onLastFrame;
+ } else {
+ delete this._onLastFrame;
+ }
+
+ if (this._index !== this._firstFrame) {
+ this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
+ / this._frameRate;
+ }
+
+ return this;
+
+ },
+
+ pause: function() {
+
+ this._playing = false;
+ return this;
+
+ },
+
+ stop: function() {
+
+ this._playing = false;
+ this._index = 0;
+
+ return this;
+
+ },
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var clone = new Sprite(
+ this.texture, this.translation.x, this.translation.y,
+ this.columns, this.rows, this.frameRate
+ );
+
+ if (this.playing) {
+ clone.play(this._firstFrame, this._lastFrame);
+ clone._loop = this._loop;
+ }
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ _update: function() {
+
+ var effect = this._texture;
+ var cols = this._columns;
+ var rows = this._rows;
+
+ var width, height, elapsed, amount, duration;
+ var index, iw, ih, isRange, frames;
+
+ if (this._flagColumns || this._flagRows) {
+ this._amount = this._columns * this._rows;
+ }
+
+ if (this._flagFrameRate) {
+ this._duration = 1000 * this._amount / this._frameRate;
+ }
+
+ if (this._flagTexture) {
+ this.fill = this._texture;
+ }
+
+ if (this._texture.loaded) {
+
+ iw = effect.image.width;
+ ih = effect.image.height;
+
+ width = iw / cols;
+ height = ih / rows;
+ amount = this._amount;
+
+ if (this.width !== width) {
+ this.width = width;
+ }
+ if (this.height !== height) {
+ this.height = height;
+ }
+
+ if (this._playing && this._frameRate > 0) {
+
+ if (_.isNaN(this._lastFrame)) {
+ this._lastFrame = amount - 1;
+ }
+
+ // TODO: Offload perf logic to instance of `Two`.
+ elapsed = _.performance.now() - this._startTime;
+ frames = this._lastFrame + 1;
+ duration = 1000 * (frames - this._firstFrame) / this._frameRate;
+
+ if (this._loop) {
+ elapsed = elapsed % duration;
+ } else {
+ elapsed = Math.min(elapsed, duration);
+ }
+
+ index = _.lerp(this._firstFrame, frames, elapsed / duration);
+ index = Math.floor(index);
+
+ if (index !== this._index) {
+ this._index = index;
+ if (index >= this._lastFrame - 1 && this._onLastFrame) {
+ this._onLastFrame(); // Shortcut for chainable sprite animations
+ }
+ }
+
+ }
+
+ var col = this._index % cols;
+ var row = Math.floor(this._index / cols);
+
+ var ox = - width * col + (iw - width) / 2;
+ var oy = - height * row + (ih - height) / 2;
+
+ // TODO: Improve performance
+ if (ox !== effect.offset.x) {
+ effect.offset.x = ox;
+ }
+ if (oy !== effect.offset.y) {
+ effect.offset.y = oy;
+ }
+
+ }
+
+ Rectangle.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagTexture = this._flagColumns = this._flagRows
+ = this._flagFrameRate = false;
+
+ Rectangle.prototype.flagReset.call(this);
+
+ return this;
+ }
+
+
+ });
+
+ Sprite.MakeObservable(Sprite.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ var _ = Two.Utils;
+ var Path = Two.Path;
+ var Rectangle = Two.Rectangle;
+
+ var ImageSequence = Two.ImageSequence = function(paths, ox, oy, frameRate) {
+
+ Path.call(this, [
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor(),
+ new Two.Anchor()
+ ], true);
+
+ this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this);
+ this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this);
+ this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this);
+
+ this.noStroke();
+ this.noFill();
+
+ this.textures = _.map(paths, ImageSequence.GenerateTexture, this);
+
+ this._update();
+ this.translation.set(ox || 0, oy || 0);
+
+ if (_.isNumber(frameRate)) {
+ this.frameRate = frameRate;
+ } else {
+ this.frameRate = ImageSequence.DefaultFrameRate;
+ }
+
+ };
+
+ _.extend(ImageSequence, {
+
+ Properties: [
+ 'frameRate',
+ 'index'
+ ],
+
+ DefaultFrameRate: 30,
+
+ FlagTextures: function() {
+ this._flagTextures = true;
+ },
+
+ BindTextures: function(items) {
+
+ var i = items.length;
+ while (i--) {
+ items[i].bind(Two.Events.change, this._renderer.flagTextures);
+ }
+
+ this._renderer.flagTextures();
+
+ },
+
+ UnbindTextures: function(items) {
+
+ var i = items.length;
+ while (i--) {
+ items[i].unbind(Two.Events.change, this._renderer.flagTextures);
+ }
+
+ this._renderer.flagTextures();
+
+ },
+
+ MakeObservable: function(obj) {
+
+ Rectangle.MakeObservable(obj);
+ _.each(ImageSequence.Properties, Two.Utils.defineProperty, obj);
+
+ Object.defineProperty(obj, 'textures', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._textures;
+ },
+
+ set: function(textures) {
+
+ var updateTextures = this._renderer.flagTextures;
+ var bindTextures = this._renderer.bindTextures;
+ var unbindTextures = this._renderer.unbindTextures;
+
+ // Remove previous listeners
+ if (this._textures) {
+ this._textures
+ .unbind(Two.Events.insert, bindTextures)
+ .unbind(Two.Events.remove, unbindTextures);
+ }
+
+ // Create new Collection with copy of vertices
+ this._textures = new Two.Utils.Collection((textures || []).slice(0));
+
+ // Listen for Collection changes and bind / unbind
+ this._textures
+ .bind(Two.Events.insert, bindTextures)
+ .bind(Two.Events.remove, unbindTextures);
+
+ // Bind Initial Textures
+ bindTextures(this._textures);
+
+ }
+
+ });
+
+ },
+
+ GenerateTexture: function(obj) {
+ if (obj instanceof Two.Texture) {
+ return obj;
+ } else if (_.isString(obj)) {
+ return new Two.Texture(obj);
+ }
+ }
+
+ });
+
+ _.extend(ImageSequence.prototype, Rectangle.prototype, {
+
+ _flagTextures: false,
+ _flagFrameRate: false,
+ _flagIndex: false,
+
+ // Private variables
+ _amount: 1,
+ _duration: 0,
+ _index: 0,
+ _startTime: 0,
+ _playing: false,
+ _firstFrame: 0,
+ _lastFrame: 0,
+ _loop: true,
+
+ // Exposed through getter-setter
+ _textures: null,
+ _frameRate: 0,
+
+ play: function(firstFrame, lastFrame, onLastFrame) {
+
+ this._playing = true;
+ this._firstFrame = 0;
+ this._lastFrame = this.amount - 1;
+ this._startTime = _.performance.now();
+
+ if (_.isNumber(firstFrame)) {
+ this._firstFrame = firstFrame;
+ }
+ if (_.isNumber(lastFrame)) {
+ this._lastFrame = lastFrame;
+ }
+ if (_.isFunction(onLastFrame)) {
+ this._onLastFrame = onLastFrame;
+ } else {
+ delete this._onLastFrame;
+ }
+
+ if (this._index !== this._firstFrame) {
+ this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
+ / this._frameRate;
+ }
+
+ return this;
+
+ },
+
+ pause: function() {
+
+ this._playing = false;
+ return this;
+
+ },
+
+ stop: function() {
+
+ this._playing = false;
+ this._index = 0;
+
+ return this;
+
+ },
+
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var clone = new ImageSequence(this.textures, this.translation.x,
+ this.translation.y, this.frameRate)
+
+ clone._loop = this._loop;
+
+ if (this._playing) {
+ clone.play();
+ }
+
+ if (parent) {
+ parent.add(clone);
+ }
+
+ return clone;
+
+ },
+
+ _update: function() {
+
+ var effects = this._textures;
+ var width, height, elapsed, amount, duration, texture;
+ var index, frames;
+
+ if (this._flagTextures) {
+ this._amount = effects.length;
+ }
+
+ if (this._flagFrameRate) {
+ this._duration = 1000 * this._amount / this._frameRate;
+ }
+
+ if (this._playing && this._frameRate > 0) {
+
+ amount = this._amount;
+
+ if (_.isNaN(this._lastFrame)) {
+ this._lastFrame = amount - 1;
+ }
+
+ // TODO: Offload perf logic to instance of `Two`.
+ elapsed = _.performance.now() - this._startTime;
+ frames = this._lastFrame + 1;
+ duration = 1000 * (frames - this._firstFrame) / this._frameRate;
+
+ if (this._loop) {
+ elapsed = elapsed % duration;
+ } else {
+ elapsed = Math.min(elapsed, duration);
+ }
+
+ index = _.lerp(this._firstFrame, frames, elapsed / duration);
+ index = Math.floor(index);
+
+ if (index !== this._index) {
+
+ this._index = index;
+ texture = effects[this._index];
+
+ if (texture.loaded) {
+
+ width = texture.image.width;
+ height = texture.image.height;
+
+ if (this.width !== width) {
+ this.width = width;
+ }
+ if (this.height !== height) {
+ this.height = height;
+ }
+
+ this.fill = texture;
+
+ if (index >= this._lastFrame - 1 && this._onLastFrame) {
+ this._onLastFrame(); // Shortcut for chainable sprite animations
+ }
+
+ }
+
+ }
+
+ } else if (this._flagIndex || !(this.fill instanceof Two.Texture)) {
+
+ texture = effects[this._index];
+
+ if (texture.loaded) {
+
+ width = texture.image.width;
+ height = texture.image.height;
+
+ if (this.width !== width) {
+ this.width = width;
+ }
+ if (this.height !== height) {
+ this.height = height;
+ }
+
+ }
+
+ this.fill = texture;
+
+ }
+
+ Rectangle.prototype._update.call(this);
+
+ return this;
+
+ },
+
+ flagReset: function() {
+
+ this._flagTextures = this._flagFrameRate = false;
+ Rectangle.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ ImageSequence.MakeObservable(ImageSequence.prototype);
+
+})((typeof global !== 'undefined' ? global : this).Two);
+
+(function(Two) {
+
+ /**
+ * Constants
+ */
+ var min = Math.min, max = Math.max;
+ var _ = Two.Utils;
+
+ /**
+ * A children collection which is accesible both by index and by object id
+ * @constructor
+ */
+ var Children = function() {
+
+ Two.Utils.Collection.apply(this, arguments);
+
+ Object.defineProperty(this, '_events', {
+ value : {},
+ enumerable: false
+ });
+
+ this.ids = {};
+
+ this.on(Two.Events.insert, this.attach);
+ this.on(Two.Events.remove, this.detach);
+ Children.prototype.attach.apply(this, arguments);
+
+ };
+
+ Children.prototype = new Two.Utils.Collection();
+ Children.prototype.constructor = Children;
+
+ _.extend(Children.prototype, {
+
+ attach: function(children) {
+ for (var i = 0; i < children.length; i++) {
+ this.ids[children[i].id] = children[i];
+ }
+ return this;
+ },
+
+ detach: function(children) {
+ for (var i = 0; i < children.length; i++) {
+ delete this.ids[children[i].id];
+ }
+ return this;
+ }
+
+ });
+
+ var Group = Two.Group = function() {
+
+ Two.Shape.call(this, true);
+
+ this._renderer.type = 'group';
+
+ this.additions = [];
+ this.subtractions = [];
+
+ this.children = arguments;
+
+ };
+
+ _.extend(Group, {
+
+ Children: Children,
+
+ InsertChildren: function(children) {
+ for (var i = 0; i < children.length; i++) {
+ replaceParent.call(this, children[i], this);
+ }
+ },
+
+ RemoveChildren: function(children) {
+ for (var i = 0; i < children.length; i++) {
+ replaceParent.call(this, children[i]);
+ }
+ },
+
+ OrderChildren: function(children) {
+ this._flagOrder = true;
+ },
+
+ MakeObservable: function(object) {
+
+ var properties = Two.Path.Properties.slice(0);
+ var oi = _.indexOf(properties, 'opacity');
+
+ if (oi >= 0) {
+
+ properties.splice(oi, 1);
+
+ Object.defineProperty(object, 'opacity', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._opacity;
+ },
+
+ set: function(v) {
+ // Only set flag if there is an actual difference
+ this._flagOpacity = (this._opacity != v);
+ this._opacity = v;
+ }
+
+ });
+
+ }
+
+ Two.Shape.MakeObservable(object);
+ Group.MakeGetterSetters(object, properties);
+
+ Object.defineProperty(object, 'children', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._children;
+ },
+
+ set: function(children) {
+
+ var insertChildren = _.bind(Group.InsertChildren, this);
+ var removeChildren = _.bind(Group.RemoveChildren, this);
+ var orderChildren = _.bind(Group.OrderChildren, this);
+
+ if (this._children) {
+ this._children.unbind();
+ }
+
+ this._children = new Children(children);
+ this._children.bind(Two.Events.insert, insertChildren);
+ this._children.bind(Two.Events.remove, removeChildren);
+ this._children.bind(Two.Events.order, orderChildren);
+
+ }
+
+ });
+
+ Object.defineProperty(object, 'mask', {
+
+ enumerable: true,
+
+ get: function() {
+ return this._mask;
+ },
+
+ set: function(v) {
+ this._mask = v;
+ this._flagMask = true;
+ if (!v.clip) {
+ v.clip = true;
+ }
+ }
+
+ });
+
+ },
+
+ MakeGetterSetters: function(group, properties) {
+
+ if (!_.isArray(properties)) {
+ properties = [properties];
+ }
+
+ _.each(properties, function(k) {
+ Group.MakeGetterSetter(group, k);
+ });
+
+ },
+
+ MakeGetterSetter: function(group, k) {
+
+ var secret = '_' + k;
+
+ Object.defineProperty(group, k, {
+
+ enumerable: true,
+
+ get: function() {
+ return this[secret];
+ },
+
+ set: function(v) {
+ this[secret] = v;
+ _.each(this.children, function(child) { // Trickle down styles
+ child[k] = v;
+ });
+ }
+
+ });
+
+ }
+
+ });
+
+ _.extend(Group.prototype, Two.Shape.prototype, {
+
+ // Flags
+ // http://en.wikipedia.org/wiki/Flag
+
+ _flagAdditions: false,
+ _flagSubtractions: false,
+ _flagOrder: false,
+ _flagOpacity: true,
+
+ _flagMask: false,
+
+ // Underlying Properties
+
+ _fill: '#fff',
+ _stroke: '#000',
+ _linewidth: 1.0,
+ _opacity: 1.0,
+ _visible: true,
+
+ _cap: 'round',
+ _join: 'round',
+ _miter: 4,
+
+ _closed: true,
+ _curved: false,
+ _automatic: true,
+ _beginning: 0,
+ _ending: 1.0,
+
+ _mask: null,
+
+ /**
+ * TODO: Group has a gotcha in that it's at the moment required to be bound to
+ * an instance of two in order to add elements correctly. This needs to
+ * be rethought and fixed.
+ */
+ clone: function(parent) {
+
+ parent = parent || this.parent;
+
+ var group = new Group();
+ var children = _.map(this.children, function(child) {
+ return child.clone(group);
+ });
+
+ group.add(children);
+
+ group.opacity = this.opacity;
+
+ if (this.mask) {
+ group.mask = this.mask;
+ }
+
+ group.translation.copy(this.translation);
+ group.rotation = this.rotation;
+ group.scale = this.scale;
+
+ if (parent) {
+ parent.add(group);
+ }
+
+ return group;
+
+ },
+
+ /**
+ * Export the data from the instance of Two.Group into a plain JavaScript
+ * object. This also makes all children plain JavaScript objects. Great
+ * for turning into JSON and storing in a database.
+ */
+ toObject: function() {
+
+ var result = {
+ children: [],
+ translation: this.translation.toObject(),
+ rotation: this.rotation,
+ scale: this.scale,
+ opacity: this.opacity,
+ mask: (this.mask ? this.mask.toObject() : null)
+ };
+
+ _.each(this.children, function(child, i) {
+ result.children[i] = child.toObject();
+ }, this);
+
+ return result;
+
+ },
+
+ /**
+ * Anchor all children to the upper left hand corner
+ * of the group.
+ */
+ corner: function() {
+
+ var rect = this.getBoundingClientRect(true),
+ corner = { x: rect.left, y: rect.top };
+
+ this.children.forEach(function(child) {
+ child.translation.subSelf(corner);
+ });
+
+ return this;
+
+ },
+
+ /**
+ * Anchors all children around the center of the group,
+ * effectively placing the shape around the unit circle.
+ */
+ center: function() {
+
+ var rect = this.getBoundingClientRect(true);
+
+ rect.centroid = {
+ x: rect.left + rect.width / 2,
+ y: rect.top + rect.height / 2
+ };
+
+ this.children.forEach(function(child) {
+ if (child.isShape) {
+ child.translation.subSelf(rect.centroid);
+ }
+ });
+
+ // this.translation.copy(rect.centroid);
+
+ return this;
+
+ },
+
+ /**
+ * Recursively search for id. Returns the first element found.
+ * Returns null if none found.
+ */
+ getById: function (id) {
+ var search = function (node, id) {
+ if (node.id === id) {
+ return node;
+ } else if (node.children) {
+ var i = node.children.length;
+ while (i--) {
+ var found = search(node.children[i], id);
+ if (found) return found;
+ }
+ }
+
+ };
+ return search(this, id) || null;
+ },
+
+ /**
+ * Recursively search for classes. Returns an array of matching elements.
+ * Empty array if none found.
+ */
+ getByClassName: function (cl) {
+ var found = [];
+ var search = function (node, cl) {
+ if (node.classList.indexOf(cl) != -1) {
+ found.push(node);
+ } else if (node.children) {
+ node.children.forEach(function (child) {
+ search(child, cl);
+ });
+ }
+ return found;
+ };
+ return search(this, cl);
+ },
+
+ /**
+ * Recursively search for children of a specific type,
+ * e.g. Two.Polygon. Pass a reference to this type as the param.
+ * Returns an empty array if none found.
+ */
+ getByType: function(type) {
+ var found = [];
+ var search = function (node, type) {
+ for (var id in node.children) {
+ if (node.children[id] instanceof type) {
+ found.push(node.children[id]);
+ } else if (node.children[id] instanceof Two.Group) {
+ search(node.children[id], type);
+ }
+ }
+ return found;
+ };
+ return search(this, type);
+ },
+
+ /**
+ * Add objects to the group.
+ */
+ add: function(objects) {
+
+ // Allow to pass multiple objects either as array or as multiple arguments
+ // If it's an array also create copy of it in case we're getting passed
+ // a childrens array directly.
+ if (!(objects instanceof Array)) {
+ objects = _.toArray(arguments);
+ } else {
+ objects = objects.slice();
+ }
+
+ // Add the objects
+ for (var i = 0; i < objects.length; i++) {
+ if (!(objects[i] && objects[i].id)) continue;
+ this.children.push(objects[i]);
+ }
+
+ return this;
+
+ },
+
+ /**
+ * Remove objects from the group.
+ */
+ remove: function(objects) {
+
+ var l = arguments.length,
+ grandparent = this.parent;
+
+ // Allow to call remove without arguments
+ // This will detach the object from the scene.
+ if (l <= 0 && grandparent) {
+ grandparent.remove(this);
+ return this;
+ }
+
+ // Allow to pass multiple objects either as array or as multiple arguments
+ // If it's an array also create copy of it in case we're getting passed
+ // a childrens array directly.
+ if (!(objects instanceof Array)) {
+ objects = _.toArray(arguments);
+ } else {
+ objects = objects.slice();
+ }
+
+ // Remove the objects
+ for (var i = 0; i < objects.length; i++) {
+ if (!objects[i] || !(this.children.ids[objects[i].id])) continue;
+ this.children.splice(_.indexOf(this.children, objects[i]), 1);
+ }
+
+ return this;
+
+ },
+
+ /**
+ * Return an object with top, left, right, bottom, width, and height
+ * parameters of the group.
+ */
+ getBoundingClientRect: function(shallow) {
+ var rect;
+
+ // TODO: Update this to not __always__ update. Just when it needs to.
+ this._update(true);
+
+ // Variables need to be defined here, because of nested nature of groups.
+ var left = Infinity, right = -Infinity,
+ top = Infinity, bottom = -Infinity;
+
+ this.children.forEach(function(child) {
+
+ if (/(linear-gradient|radial-gradient|gradient)/.test(child._renderer.type)) {
+ return;
+ }
+
+ rect = child.getBoundingClientRect(shallow);
+
+ if (!_.isNumber(rect.top) || !_.isNumber(rect.left) ||
+ !_.isNumber(rect.right) || !_.isNumber(rect.bottom)) {
+ return;
+ }
+
+ top = min(rect.top, top);
+ left = min(rect.left, left);
+ right = max(rect.right, right);
+ bottom = max(rect.bottom, bottom);
+
+ }, this);
+
+ return {
+ top: top,
+ left: left,
+ right: right,
+ bottom: bottom,
+ width: right - left,
+ height: bottom - top
+ };
+
+ },
+
+ /**
+ * Trickle down of noFill
+ */
+ noFill: function() {
+ this.children.forEach(function(child) {
+ child.noFill();
+ });
+ return this;
+ },
+
+ /**
+ * Trickle down of noStroke
+ */
+ noStroke: function() {
+ this.children.forEach(function(child) {
+ child.noStroke();
+ });
+ return this;
+ },
+
+ /**
+ * Trickle down subdivide
+ */
+ subdivide: function() {
+ var args = arguments;
+ this.children.forEach(function(child) {
+ child.subdivide.apply(child, args);
+ });
+ return this;
+ },
+
+ flagReset: function() {
+
+ if (this._flagAdditions) {
+ this.additions.length = 0;
+ this._flagAdditions = false;
+ }
+
+ if (this._flagSubtractions) {
+ this.subtractions.length = 0;
+ this._flagSubtractions = false;
+ }
+
+ this._flagOrder = this._flagMask = this._flagOpacity = false;
+
+ Two.Shape.prototype.flagReset.call(this);
+
+ return this;
+
+ }
+
+ });
+
+ Group.MakeObservable(Group.prototype);
+
+ /**
+ * Helper function used to sync parent-child relationship within the
+ * `Two.Group.children` object.
+ *
+ * Set the parent of the passed object to another object
+ * and updates parent-child relationships
+ * Calling with one arguments will simply remove the parenting
+ */
+ function replaceParent(child, newParent) {
+
+ var parent = child.parent;
+ var index;
+
+ if (parent === newParent) {
+ this.additions.push(child);
+ this._flagAdditions = true;
+ return;
+ }
+
+ if (parent && parent.children.ids[child.id]) {
+
+ index = _.indexOf(parent.children, child);
+ parent.children.splice(index, 1);
+
+ // If we're passing from one parent to another...
+ index = _.indexOf(parent.additions, child);
+
+ if (index >= 0) {
+ parent.additions.splice(index, 1);
+ } else {
+ parent.subtractions.push(child);
+ parent._flagSubtractions = true;
+ }
+
+ }
+
+ if (newParent) {
+ child.parent = newParent;
+ this.additions.push(child);
+ this._flagAdditions = true;
+ return;
+ }
+
+ // If we're passing from one parent to another...
+ index = _.indexOf(this.additions, child);
+
+ if (index >= 0) {
+ this.additions.splice(index, 1);
+ } else {
+ this.subtractions.push(child);
+ this._flagSubtractions = true;
+ }
+
+ delete child.parent;
+
+ }
+
+})((typeof global !== 'undefined' ? global : this).Two);