diff --git a/app/assets/fonts/queulat.eot b/app/assets/fonts/queulat.eot
new file mode 100644
index 0000000..14cef1e
Binary files /dev/null and b/app/assets/fonts/queulat.eot differ
diff --git a/app/assets/fonts/queulat.otf b/app/assets/fonts/queulat.otf
new file mode 100644
index 0000000..9c66010
Binary files /dev/null and b/app/assets/fonts/queulat.otf differ
diff --git a/app/assets/fonts/queulat.svg b/app/assets/fonts/queulat.svg
new file mode 100644
index 0000000..bb43512
--- /dev/null
+++ b/app/assets/fonts/queulat.svg
@@ -0,0 +1,6007 @@
+
+
+
diff --git a/app/assets/fonts/queulat.ttf b/app/assets/fonts/queulat.ttf
new file mode 100644
index 0000000..2d14435
Binary files /dev/null and b/app/assets/fonts/queulat.ttf differ
diff --git a/app/assets/fonts/queulat.woff b/app/assets/fonts/queulat.woff
new file mode 100644
index 0000000..59b5de2
Binary files /dev/null and b/app/assets/fonts/queulat.woff differ
diff --git a/app/assets/fonts/queulat.woff2 b/app/assets/fonts/queulat.woff2
new file mode 100644
index 0000000..03b1884
Binary files /dev/null and b/app/assets/fonts/queulat.woff2 differ
diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss
index 13e788d..35d4107 100644
--- a/app/assets/stylesheets/_application.scss
+++ b/app/assets/stylesheets/_application.scss
@@ -13,7 +13,7 @@ body {
padding-bottom: 20vw;
}
-h1, h2, h3, h4, h5, label, button, .button, dt, th, nav.sub-menu {
+h1, h2, h3, h4, h5, label, button, .button, dt, th, .table-th, nav.sub-menu {
@include font-family(secondary);
font-weight: normal;
}
@@ -23,6 +23,10 @@ h2 {
font-size: 6vw;
}
+h3 {
+ font-size: 4.5vw;
+}
+
h3.subtitle {
font-size: 1.5em;
}
@@ -70,32 +74,53 @@ a {
position: absolute !important;
}
-table {
+table, .table {
margin-bottom: 2em;
margin-left: 1em;
// background-color: #F8F8F8;
// width: 100%;
// @include default-box-shadow(top, 2, true);
- th, td {
+ th, td, .table-th, .table-td {
text-align: left;
padding: 0.25em 0.5em;
- border: 0.1em solid #EEE;
+ border: 0.1rem solid #EEE;
- &:first-child {
+ &.center {
+ text-align: center;
+ }
+
+ &.big {
+ font-size: 1.5em;
+ }
+
+ /*&:first-child {
padding-left: 1em;
}
&:last-child {
padding-right: 1em;
- }
+ }*/
}
- th {
+ th, .table-th {
background-color: #F8F8F8;
}
}
+.table {
+ display: table;
+ border-collapse: collapse;
+}
+
+.table-tr {
+ display: table-row;
+}
+
+.table-th, .table-td {
+ display: table-cell;
+}
+
button,
.button {
@include button;
@@ -451,6 +476,12 @@ input {
margin-bottom: 3em;
position: relative;
@include default-box-shadow(top);
+
+ td &,
+ .table-td & {
+ @include _(box-shadow, none);
+ margin: 0;
+ }
label {
display: block;
@@ -572,6 +603,14 @@ input {
display: inline-block;
}
+.check-box-field.small {
+ font-size: 0.75em;
+
+ &.small label {
+ font-size: 1.125em;
+ }
+}
+
.radio-button-field {
label {
width: 7em;
@@ -655,16 +694,27 @@ form {
}
}
-#main .columns th.form {
+#main .columns th.form,
+#main .columns .table-th.form {
display: none;
}
-#main .columns td.form {
+#main .columns td.form,
+#main .columns .table-td.form {
border: 0;
form {
margin: 0;
}
+
+ button {
+ display: block;
+ width: 100%;
+
+ + button {
+ margin-top: 0.5em;
+ }
+ }
}
fieldset {
@@ -1306,6 +1356,14 @@ ul.warnings li,
}
}
+#admin-schedule {
+ .workshop-blocks {
+ td, th {
+ vertical-align: top;
+ }
+ }
+}
+
#main {
position: relative;
background-color: $white;
@@ -2672,6 +2730,11 @@ html[data-lingua-franca-example="html"] {
border: 0.1rem solid #CCC;
cursor: pointer;
@include default-box-shadow(top, 1.5, false);
+
+ td &,
+ .table-td & {
+ @include _(box-shadow, none);
+ }
}
}
@@ -2779,8 +2842,12 @@ html[data-ontop] {
font-size: 2.25em;
}
+ h3 {
+ font-size: 1.4em;
+ }
+
p {
- font-size: 1.333em;
+ font-size: 1.25em;
}
form {
@@ -3089,7 +3156,7 @@ html[data-ontop] {
figure {
float: left;
width: 33%;
- margin: 0 1.5em 1.5em -1.5em;
+ margin: 0 1.5em 1em -1.5em;
}
}
}
diff --git a/app/assets/stylesheets/bumbleberry-settings.json b/app/assets/stylesheets/bumbleberry-settings.json
index bf65d65..8cec740 100644
--- a/app/assets/stylesheets/bumbleberry-settings.json
+++ b/app/assets/stylesheets/bumbleberry-settings.json
@@ -63,10 +63,10 @@
"font-loading-method": "http2",
"fonts": {
"primary": {
- "name": "SourceSansPro-Regular",
- "location": "SourceSansPro-Regular",
- "svg_id": "source_sans_proregular",
- "ttf_type": "otf",
+ "name": "queulat",
+ "location": "queulat",
+ "svg_id": "queulat",
+ "ttf_type": "ttf",
"fallback": ["Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", "sans-serif"]
},
"secondary": {
diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb
index d794472..2609d5c 100644
--- a/app/controllers/conferences_controller.rb
+++ b/app/controllers/conferences_controller.rb
@@ -809,6 +809,10 @@ class ConferencesController < ApplicationController
@day = nil
@time = nil
@length = 1.5
+ when :meals
+ @meals = Hash[@this_conference.meals.map{ |k, v| [k.to_i, v] }].sort.to_h
+ when :schedule
+ get_scheule_data
end
when :done
@amount = ((@registration.registration_fees_paid || 0) * 100).to_i.to_s.gsub(/^(.*)(\d\d)$/, '\1.\2')
@@ -816,6 +820,30 @@ class ConferencesController < ApplicationController
end
+ def get_scheule_data
+ @meals = Hash[@this_conference.meals.map{ |k, v| [k.to_i, v] }].sort.to_h
+ @events = Event.where(:conference_id => @this_conference.id).order(start_time: :asc)
+ @workshops = Workshop.where(:conference_id => @this_conference.id).order(start_time: :asc)
+ @locations = {}
+ @workshop_blocks = @this_conference.workshop_blocks || []
+ @workshop_blocks << {
+ 'time' => nil,
+ 'length' => 1.0,
+ 'days' => []
+ }
+ @workshops.each do |workshop|
+ if workshop.location_id
+ @locations[workshop.location_id] ||= workshop.location
+ end
+ end
+ @block_days = []
+ day = @this_conference.start_date
+ while day <= @this_conference.end_date
+ @block_days << day.wday
+ day += 1.day
+ end
+ end
+
def get_housing_data
@hosts = {}
@guests = {}
@@ -1063,6 +1091,18 @@ class ConferencesController < ApplicationController
return redirect_to administration_step_path(@this_conference.slug, :events)
end
+ when 'schedule'
+ case params[:button]
+ when 'save_block'
+ @this_conference.workshop_blocks ||= []
+ @this_conference.workshop_blocks[params[:workshop_block].to_i] = {
+ 'time' => params[:time],
+ 'length' => params[:time_span],
+ 'days' => params[:days].keys
+ }
+ @this_conference.save
+ return redirect_to administration_step_path(@this_conference.slug, :schedule)
+ end
end
do_404
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index b7ce4f6..bd111f9 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1077,6 +1077,26 @@ module ApplicationHelper
checkboxes(name, [true], value, label_key, options)
end
+ def unique_id(id)
+ id = id.to_s.gsub('[', '_').gsub(']', '')
+
+ @_ids ||= {}
+ @_ids[id] ||= 0
+
+ new_id = id
+
+ if @_ids[id] > 0
+ #puts " ====== #{id} - #{@_ids[id]} : #{@_ids[id] + 1} ====== "
+ new_id += "--#{@_ids[id]}"
+ end
+
+ puts " ====== #{id} = #{@_ids[id]} ====== "
+ @_ids[id] += 1
+ puts " ====== #{id} = #{@_ids[id]} ====== "
+
+ return new_id
+ end
+
def checkboxes(name, boxes, values, label_key, options = {})
html = ''
@@ -1084,42 +1104,54 @@ module ApplicationHelper
description_id = nil
if options[:heading].present?
- label_id ||= "#{name.to_s}-label"
+ label_id ||= unique_id("#{name.to_s}-label")
html += content_tag(:h3, _(options[:heading], :t), id: label_id)
end
if options[:help].present?
- description_id ||= "#{name.to_s}-desc"
+ description_id ||= unique_id("#{name.to_s}-desc")
html += content_tag(:div, _(options[:help], :s, 2), class: 'input-field-help', id: description_id)
end
boxes_html = ''
is_single = !values.is_a?(Array)
- values = values.present? ? values.map(&:to_s) : [] unless is_single
- boxes = boxes.map(&:to_s)
+ unless boxes.length > 0 && boxes.first.is_a?(Integer)
+ values = values.present? ? values.map(&:to_s) : [] unless is_single
+ boxes = boxes.map(&:to_s)
+ end
boxes.each do | box |
checked = (is_single ? values.present? : values.include?(box))
values -= [box] if checked && !is_single
id = nil
if options[:radiobuttons].present?
- id = "#{name.to_s}_#{box}"
- boxes_html += radio_button_tag(name, box, checked)
+ id = unique_id("#{name.to_s}_#{box}")
+ boxes_html += radio_button_tag(name, box, checked, id: id)
+ else
+ _name = (is_single ? name : "#{name.to_s}[#{box}]")
+ id = unique_id(_name)
+ boxes_html += check_box_tag(_name, 1, checked, data: { toggles: options[:toggles] }.compact, id: id)
+ end
+ if is_single
+ label = _(label_key.to_s)
+ elsif box.is_a?(Integer)
+ label = I18n.t(label_key.to_s)[box]
else
- id = (is_single ? name : "#{name.to_s}[#{box}]")
- boxes_html += check_box_tag(id, 1, checked, data: { toggles: options[:toggles] }.compact)
+ label = _("#{label_key.to_s}.#{box}")
end
- boxes_html += label_tag(id, _(is_single ? label_key.to_s : "#{label_key.to_s}.#{box}"))
+
+ boxes_html += label_tag(id, label)
end
if options[:other].present? && !is_single
id = nil
if options[:radiobuttons].present?
- id = "#{name.to_s}_other"
- boxes_html += radio_button_tag(name, :other, values.present?)
+ id = unique_id("#{name.to_s}_other")
+ boxes_html += radio_button_tag(name, :other, values.present?, id: id)
else
- id = "#{name.to_s}[other]"
- boxes_html += check_box_tag(id, 1, values.present?)
+ _name = "#{name.to_s}[other]"
+ id = unique_id(_name)
+ boxes_html += check_box_tag(_name, 1, values.present?, id: id)
end
boxes_html += label_tag id,
content_tag(:div,
@@ -1132,7 +1164,8 @@ module ApplicationHelper
'check-box-field',
'input-field',
options[:vertical] ? 'vertical' : nil,
- options[:inline] ? 'inline' : nil
+ options[:inline] ? 'inline' : nil,
+ options[:small] ? 'small' : nil
]).html_safe,
aria: {
labeledby: label_id,
diff --git a/app/views/application/about.html.haml b/app/views/application/about.html.haml
index ddb059a..7cfa000 100644
--- a/app/views/application/about.html.haml
+++ b/app/views/application/about.html.haml
@@ -5,8 +5,9 @@
%h2=_'articles.about_bikebike.headings.What_is_BikeBike', :t
%p=_'articles.about_bikebike.paragraphs.What_is_BikeBike', :p
- = columns(medium: 12) do
+ = columns(medium: 12, class: 'featured-image-container') do
%h3=_'articles.about_bikebike.headings.bicycle_project', :t
+ %figure{style: "background-image: url(#{image_path('columbus_people.jpg')})"}
%p=_'articles.about_bikebike.paragraphs.bicycle_project_paragraph', :p
%ul
- [:non_profit, :no_money, :education, :volunteer_run, :export_bikes, :low_cost, :recycle_parts].each do |term|
@@ -14,9 +15,8 @@
= columns(medium: 12) do
%h3=_'articles.about_bikebike.headings.Who_is_Invited', :t
%p=_'articles.about_bikebike.paragraphs.Who_is_Invited', :p
- = columns(medium: 12, class: 'featured-image-container') do
+ = columns(medium: 12) do
%h3=_'articles.about_bikebike.headings.Types_of_Workshops', :t
- %figure{style: "background-image: url(#{image_path('columbus_people.jpg')})"}
%p=_'articles.about_bikebike.paragraphs.Types_of_Workshops', :p
= columns(medium: 12) do
%h3=_'articles.about_bikebike.headings.Amenities', :t
diff --git a/app/views/conferences/admin/_meals.html.haml b/app/views/conferences/admin/_meals.html.haml
index ca5a0e8..a3ea8c6 100644
--- a/app/views/conferences/admin/_meals.html.haml
+++ b/app/views/conferences/admin/_meals.html.haml
@@ -7,7 +7,7 @@
%th=_'forms.labels.generic.day'
%th=_'forms.labels.generic.time'
%th.form
- - Hash[@this_conference.meals.map{ |k, v| [k.to_i, v] }].sort.to_h.each do | time, meal |
+ - @meals.each do | time, meal |
%tr
%th
=_!(meal['title'] || '')
diff --git a/app/views/conferences/admin/_schedule.html.haml b/app/views/conferences/admin/_schedule.html.haml
index e69de29..918b366 100644
--- a/app/views/conferences/admin/_schedule.html.haml
+++ b/app/views/conferences/admin/_schedule.html.haml
@@ -0,0 +1,17 @@
+.table.workshop-blocks
+ .table-tr
+ .table-th=_'forms.labels.generic.block_number'
+ .table-th=_'forms.labels.generic.time'
+ .table-th=_'forms.labels.generic.length'
+ .table-th=_'forms.labels.generic.days'
+ .table-th.form
+ - @workshop_blocks.each_with_index do | info, block |
+ = form_tag administration_update_path(@this_conference.slug, :schedule), class: 'table-tr' do
+ .table-th.center.big=(block + 1)
+ .table-td=time_select info['time'], small: true, label: false
+ .table-td=length_select info['length'], {small: true, label: false}, 0.5, 2
+ .table-td=checkboxes :days, @block_days, info['days'].map(&:to_i), 'date.day_names', vertical: true, small: true
+ .table-td.form
+ = hidden_field_tag :workshop_block, block
+ = button_tag :delete_block, value: :delete_block, class: [:small, :delete] if block == @workshop_blocks.length - 2
+ = button_tag (info['time'].present? ? :update_block : :add_block), value: :save_block, class: [:small, :add]
diff --git a/app/views/conferences/register.html.haml b/app/views/conferences/register.html.haml
index a3719d1..a88d7b3 100644
--- a/app/views/conferences/register.html.haml
+++ b/app/views/conferences/register.html.haml
@@ -1,16 +1,6 @@
= render :partial => 'page_header', :locals => {:page_key => 'Conference_Registration'}
= registration_step_menu
--#- if (steps = current_registration_steps(@registration))
--# = row id: 'registration-steps', class: 'flow-steps' do
--# = columns do
--# %ul
--# - current_registration_steps.each do | step |
--# - text = _"articles.conference_registration.headings.#{step[:name].to_s}"
--# %li{class: [step[:enabled] ? :enabled : nil, @register_template == step[:name] ? :current : nil]}
--# - if step[:enabled]
--# .step= link_to text, register_step_path(@this_conference.slug, step[:name])
--# - else
--# .step= text
+
- if @warnings.present?
= row class: 'warnings', tag: :ul do
- @warnings.each do | warning |
diff --git a/app/views/workshops/show.html.haml b/app/views/workshops/show.html.haml
index bb6ab2d..88bf3d2 100644
--- a/app/views/workshops/show.html.haml
+++ b/app/views/workshops/show.html.haml
@@ -1,15 +1,6 @@
= render 'conferences/page_header', :page_key => 'Conference_Registration'
-= row id: 'registration-steps', class: 'flow-steps' do
- = columns do
- %ul
- - current_registration_steps.each do | step |
- - text = _"articles.conference_registration.headings.#{step[:name].to_s}"
- %li{class: [step[:enabled] ? :enabled : nil, :workshops == step[:name] ? :current : nil]}
- - if step[:enabled]
- .step= link_to text, register_step_path(@this_conference.slug, step[:name])
- - else
- .step= text
+= registration_step_menu
%article
= render 'workshops/show', :workshop => @workshop, :translations_available_for_editing => @translations_available_for_editing, :preview => false
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 783bec4..79fd481 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -8,4 +8,4 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-Rails.application.config.assets.precompile += %w( user-mailer.css map.js pen.js editor.js markdown.js main.js housing.js favicon.ico )
+Rails.application.config.assets.precompile += %w( user-mailer.css map.js pen.js editor.js markdown.js html2canvas.js main.js housing.js favicon.ico )
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 438d8c5..5c8b64d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -5478,17 +5478,7 @@ en:
Total_Registrations: Total Registrations
about_bikebike:
paragraphs:
- bicycle_project_paragraph: 'Bike!Bike! 2015: An international reunion meant
- to be a swap-meet for experiences and ideas about community bike projects.
- From collectives that use the bicycle as an excuse to change society, economy
- and the environment. Non-profit groups that have a community bike shops,
- cooperatives and other projects that promote the use of the bicycle and
- that come together to turn their communities into a place where riding is
- easier, more inclusive, safer and more fun. The list below uses the criteria
- found in the old Bicycle Organization Organization Project for what constitutes
- a community bike shop. The bike project need not meet all these criteria.
- Rather, it is a general list of qualities which are common among many bicycle
- projects.'
+ bicycle_project_paragraph: 'From collectives that use the bicycle as an excuse to change society, economy and the environment. Non-profit groups that have a community bike shops, cooperatives and other projects that promote the use of the bicycle and that come together to turn their communities into a place where riding is easier, more inclusive, safer and more fun. The list below uses the criteria found in the old Bicycle Organization Organization Project for what constitutes a community bike shop. The bike project need not meet all these criteria. Rather, it is a general list of qualities which are common among many bicycle projects.'
Who_is_Invited: You don’t have to be an expert or belong to a huge group,
you just need to be willing to share what you know about organization, mechanics,
social impact, inequality alternatives, better access to bicycles or knowledge
diff --git a/config/routes.rb b/config/routes.rb
index 9240b19..c75ae6a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -52,6 +52,7 @@ BikeBike::Application.routes.draw do
get '/oauth/:provider' => 'oauths#oauth', :as => :auth_at_provider
post '/translator-request' => 'application#translator_request', :as => :translator_request
+ # patch '/capture_view' => 'application#capture_view'
post '/js_error' => 'application#js_error'
get '/error_403' => 'application#do_403'
get '/error_404' => 'application#error_404'
diff --git a/db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb b/db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb
new file mode 100644
index 0000000..e4d82b3
--- /dev/null
+++ b/db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb
@@ -0,0 +1,5 @@
+class AddWorkshopBlocksToConferences < ActiveRecord::Migration
+ def change
+ add_column :conferences, :workshop_blocks, :json
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1572812..ca8b4e8 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160622011811) do
+ActiveRecord::Schema.define(version: 20160630233219) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -127,6 +127,7 @@ ActiveRecord::Schema.define(version: 20160622011811) do
t.string "day_parts"
t.string "registration_status"
t.json "meals"
+ t.json "workshop_blocks"
end
create_table "delayed_jobs", force: :cascade do |t|
diff --git a/vendor/assets/javascripts/html2canvas.js b/vendor/assets/javascripts/html2canvas.js
new file mode 100644
index 0000000..ec9fb45
--- /dev/null
+++ b/vendor/assets/javascripts/html2canvas.js
@@ -0,0 +1,19279 @@
+/*
+ html2canvas 0.5.0-beta3
+ Copyright (c) 2016 Niklas von Hertzen
+
+ Released under License
+*/
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.html2canvas=e()}}(function(){var e;return function n(e,f,o){function d(t,l){if(!f[t]){if(!e[t]){var s="function"==typeof require&&require;if(!l&&s)return s(t,!0);if(i)return i(t,!0);var u=new Error("Cannot find module '"+t+"'");throw u.code="MODULE_NOT_FOUND",u}var a=f[t]={exports:{}};e[t][0].call(a.exports,function(n){var f=e[t][1][n];return d(f?f:n)},a,a.exports,n,e,f,o)}return f[t].exports}for(var i="function"==typeof require&&require,t=0;td;)n=e.charCodeAt(d++),n>=55296&&56319>=n&&i>d?(f=e.charCodeAt(d++),56320==(64512&f)?o.push(((1023&n)<<10)+(1023&f)+65536):(o.push(n),d--)):o.push(n);return o}function u(e){return t(e,function(e){var n="";return e>65535&&(e-=65536,n+=L(e>>>10&1023|55296),e=56320|1023&e),n+=L(e)}).join("")}function a(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:k}function p(e,n){return e+22+75*(26>e)-((0!=n)<<5)}function c(e,n,f){var o=0;for(e=f?K(e/B):e>>1,e+=K(e/n);e>J*z>>1;o+=k)e=K(e/J);return K(o+(J+1)*e/(e+A))}function y(e){var n,f,o,d,t,l,s,p,y,m,r=[],v=e.length,w=0,b=D,g=C;for(f=e.lastIndexOf(E),0>f&&(f=0),o=0;f>o;++o)e.charCodeAt(o)>=128&&i("not-basic"),r.push(e.charCodeAt(o));for(d=f>0?f+1:0;v>d;){for(t=w,l=1,s=k;d>=v&&i("invalid-input"),p=a(e.charCodeAt(d++)),(p>=k||p>K((j-w)/l))&&i("overflow"),w+=p*l,y=g>=s?q:s>=g+z?z:s-g,!(y>p);s+=k)m=k-y,l>K(j/m)&&i("overflow"),l*=m;n=r.length+1,g=c(w-t,n,0==t),K(w/n)>j-b&&i("overflow"),b+=K(w/n),w%=n,r.splice(w++,0,b)}return u(r)}function m(e){var n,f,o,d,t,l,u,a,y,m,r,v,w,b,g,h=[];for(e=s(e),v=e.length,n=D,f=0,t=C,l=0;v>l;++l)r=e[l],128>r&&h.push(L(r));for(o=d=h.length,d&&h.push(E);v>o;){for(u=j,l=0;v>l;++l)r=e[l],r>=n&&u>r&&(u=r);for(w=o+1,u-n>K((j-f)/w)&&i("overflow"),f+=(u-n)*w,n=u,l=0;v>l;++l)if(r=e[l],n>r&&++f>j&&i("overflow"),r==n){for(a=f,y=k;m=t>=y?q:y>=t+z?z:y-t,!(m>a);y+=k)g=a-m,b=k-m,h.push(L(p(m+g%b,0))),a=K(g/b);h.push(L(p(a,0))),t=c(f,w,o==d),f=0,++o}++f,++n}return h.join("")}function r(e){return l(e,function(e){return F.test(e)?y(e.slice(4).toLowerCase()):e})}function v(e){return l(e,function(e){return G.test(e)?"xn--"+m(e):e})}var w="object"==typeof o&&o,b="object"==typeof f&&f&&f.exports==w&&f,g="object"==typeof n&&n;(g.global===g||g.window===g)&&(d=g);var h,x,j=2147483647,k=36,q=1,z=26,A=38,B=700,C=72,D=128,E="-",F=/^xn--/,G=/[^ -~]/,H=/\x2E|\u3002|\uFF0E|\uFF61/g,I={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},J=k-q,K=Math.floor,L=String.fromCharCode;if(h={version:"1.2.4",ucs2:{decode:s,encode:u},decode:y,encode:m,toASCII:v,toUnicode:r},"function"==typeof e&&"object"==typeof e.amd&&e.amd)e("punycode",function(){return h});else if(w&&!w.nodeType)if(b)b.exports=h;else for(x in h)h.hasOwnProperty(x)&&(w[x]=h[x]);else d.punycode=h}(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(e,n){function f(e,n,f){!e.defaultView||n===e.defaultView.pageXOffset&&f===e.defaultView.pageYOffset||e.defaultView.scrollTo(n,f)}function o(e,n){try{n&&(n.width=e.width,n.height=e.height,n.getContext("2d").putImageData(e.getContext("2d").getImageData(0,0,e.width,e.height),0,0))}catch(f){t("Unable to copy canvas content from",e,f)}}function d(e,n){for(var f=3===e.nodeType?document.createTextNode(e.nodeValue):e.cloneNode(!1),i=e.firstChild;i;)(n===!0||1!==i.nodeType||"SCRIPT"!==i.nodeName)&&f.appendChild(d(i,n)),i=i.nextSibling;return 1===e.nodeType&&(f._scrollTop=e.scrollTop,f._scrollLeft=e.scrollLeft,"CANVAS"===e.nodeName?o(e,f):("TEXTAREA"===e.nodeName||"SELECT"===e.nodeName)&&(f.value=e.value)),f}function i(e){if(1===e.nodeType){e.scrollTop=e._scrollTop,e.scrollLeft=e._scrollLeft;for(var n=e.firstChild;n;)i(n),n=n.nextSibling}}var t=e("./log");n.exports=function(e,n,o,t,l,s,u){var a=d(e.documentElement,l.javascriptEnabled),p=n.createElement("iframe");return p.className="html2canvas-container",p.style.visibility="hidden",p.style.position="fixed",p.style.left="-10000px",p.style.top="0px",p.style.border="0",p.width=o,p.height=t,p.scrolling="no",n.body.appendChild(p),new Promise(function(n){var o=p.contentWindow.document;p.contentWindow.onload=p.onload=function(){var e=setInterval(function(){o.body.childNodes.length>0&&(i(o.documentElement),clearInterval(e),"view"===l.type&&(p.contentWindow.scrollTo(s,u),!/(iPad|iPhone|iPod)/g.test(navigator.userAgent)||p.contentWindow.scrollY===u&&p.contentWindow.scrollX===s||(o.documentElement.style.top=-u+"px",o.documentElement.style.left=-s+"px",o.documentElement.style.position="absolute")),n(p))},50)},o.open(),o.write(""),f(e,s,u),o.replaceChild(o.adoptNode(a),o.documentElement),o.close()})}},{"./log":13}],3:[function(e,n){function f(e){this.r=0,this.g=0,this.b=0,this.a=null;this.fromArray(e)||this.namedColor(e)||this.rgb(e)||this.rgba(e)||this.hex6(e)||this.hex3(e)}f.prototype.darken=function(e){var n=1-e;return new f([Math.round(this.r*n),Math.round(this.g*n),Math.round(this.b*n),this.a])},f.prototype.isTransparent=function(){return 0===this.a},f.prototype.isBlack=function(){return 0===this.r&&0===this.g&&0===this.b},f.prototype.fromArray=function(e){return Array.isArray(e)&&(this.r=Math.min(e[0],255),this.g=Math.min(e[1],255),this.b=Math.min(e[2],255),e.length>3&&(this.a=e[3])),Array.isArray(e)};var o=/^#([a-f0-9]{3})$/i;f.prototype.hex3=function(e){var n=null;return null!==(n=e.match(o))&&(this.r=parseInt(n[1][0]+n[1][0],16),this.g=parseInt(n[1][1]+n[1][1],16),this.b=parseInt(n[1][2]+n[1][2],16)),null!==n};var d=/^#([a-f0-9]{6})$/i;f.prototype.hex6=function(e){var n=null;return null!==(n=e.match(d))&&(this.r=parseInt(n[1].substring(0,2),16),this.g=parseInt(n[1].substring(2,4),16),this.b=parseInt(n[1].substring(4,6),16)),null!==n};var i=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;f.prototype.rgb=function(e){var n=null;return null!==(n=e.match(i))&&(this.r=Number(n[1]),this.g=Number(n[2]),this.b=Number(n[3])),null!==n};var t=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d?\.?\d+)\s*\)$/;f.prototype.rgba=function(e){var n=null;return null!==(n=e.match(t))&&(this.r=Number(n[1]),this.g=Number(n[2]),this.b=Number(n[3]),this.a=Number(n[4])),null!==n},f.prototype.toString=function(){return null!==this.a&&1!==this.a?"rgba("+[this.r,this.g,this.b,this.a].join(",")+")":"rgb("+[this.r,this.g,this.b].join(",")+")"},f.prototype.namedColor=function(e){e=e.toLowerCase();var n=l[e];if(n)this.r=n[0],this.g=n[1],this.b=n[2];else if("transparent"===e)return this.r=this.g=this.b=this.a=0,!0;return!!n},f.prototype.isColor=!0;var l={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};n.exports=f},{}],4:[function(n,f){function o(e,n){var f=j++;if(n=n||{},n.logging&&(v.options.logging=!0,v.options.start=Date.now()),n.async="undefined"==typeof n.async?!0:n.async,n.allowTaint="undefined"==typeof n.allowTaint?!1:n.allowTaint,n.removeContainer="undefined"==typeof n.removeContainer?!0:n.removeContainer,n.javascriptEnabled="undefined"==typeof n.javascriptEnabled?!1:n.javascriptEnabled,n.imageTimeout="undefined"==typeof n.imageTimeout?1e4:n.imageTimeout,n.renderer="function"==typeof n.renderer?n.renderer:c,n.strict=!!n.strict,"string"==typeof e){if("string"!=typeof n.proxy)return Promise.reject("Proxy must be used when rendering url");var o=null!=n.width?n.width:window.innerWidth,t=null!=n.height?n.height:window.innerHeight;return g(a(e),n.proxy,document,o,t,n).then(function(e){return i(e.contentWindow.document.documentElement,e,n,o,t)})}var l=(void 0===e?[document.documentElement]:e.length?e:[e])[0];return l.setAttribute(x+f,f),d(l.ownerDocument,n,l.ownerDocument.defaultView.innerWidth,l.ownerDocument.defaultView.innerHeight,f).then(function(e){return"function"==typeof n.onrendered&&(v("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas"),n.onrendered(e)),e})}function d(e,n,f,o,d){return b(e,e,f,o,n,e.defaultView.pageXOffset,e.defaultView.pageYOffset).then(function(t){v("Document cloned");var l=x+d,s="["+l+"='"+d+"']";e.querySelector(s).removeAttribute(l);var u=t.contentWindow,a=u.document.querySelector(s),p=Promise.resolve("function"==typeof n.onclone?n.onclone(u.document):!0);return p.then(function(){return i(a,t,n,f,o)})})}function i(e,n,f,o,d){var i=n.contentWindow,a=new p(i.document),c=new y(f,a),r=h(e),w="view"===f.type?o:s(i.document),b="view"===f.type?d:u(i.document),g=new f.renderer(w,b,c,f,document),x=new m(e,g,a,c,f);return x.ready.then(function(){v("Finished rendering");var o;return o="view"===f.type?l(g.canvas,{width:g.canvas.width,height:g.canvas.height,top:0,left:0,x:0,y:0}):e===i.document.body||e===i.document.documentElement||null!=f.canvas?g.canvas:l(g.canvas,{width:null!=f.width?f.width:r.width,height:null!=f.height?f.height:r.height,top:r.top,left:r.left,x:0,y:0}),t(n,f),o})}function t(e,n){n.removeContainer&&(e.parentNode.removeChild(e),v("Cleaned up container"))}function l(e,n){var f=document.createElement("canvas"),o=Math.min(e.width-1,Math.max(0,n.left)),d=Math.min(e.width,Math.max(1,n.left+n.width)),i=Math.min(e.height-1,Math.max(0,n.top)),t=Math.min(e.height,Math.max(1,n.top+n.height));f.width=n.width,f.height=n.height;var l=d-o,s=t-i;return v("Cropping canvas at:","left:",n.left,"top:",n.top,"width:",l,"height:",s),v("Resulting crop with width",n.width,"and height",n.height,"with x",o,"and y",i),f.getContext("2d").drawImage(e,o,i,l,s,n.x,n.y,l,s),f}function s(e){return Math.max(Math.max(e.body.scrollWidth,e.documentElement.scrollWidth),Math.max(e.body.offsetWidth,e.documentElement.offsetWidth),Math.max(e.body.clientWidth,e.documentElement.clientWidth))}function u(e){return Math.max(Math.max(e.body.scrollHeight,e.documentElement.scrollHeight),Math.max(e.body.offsetHeight,e.documentElement.offsetHeight),Math.max(e.body.clientHeight,e.documentElement.clientHeight))}function a(e){var n=document.createElement("a");return n.href=e,n.href=n.href,n}var p=n("./support"),c=n("./renderers/canvas"),y=n("./imageloader"),m=n("./nodeparser"),r=n("./nodecontainer"),v=n("./log"),w=n("./utils"),b=n("./clone"),g=n("./proxy").loadUrlDocument,h=w.getBounds,x="data-html2canvas-node",j=0;o.CanvasRenderer=c,o.NodeContainer=r,o.log=v,o.utils=w;var k="undefined"==typeof document||"function"!=typeof Object.create||"function"!=typeof document.createElement("canvas").getContext?function(){return Promise.reject("No canvas support")}:o;f.exports=k,"function"==typeof e&&e.amd&&e("html2canvas",[],function(){return k})},{"./clone":2,"./imageloader":11,"./log":13,"./nodecontainer":14,"./nodeparser":15,"./proxy":16,"./renderers/canvas":20,"./support":22,"./utils":26}],5:[function(e,n){function f(e){if(this.src=e,o("DummyImageContainer for",e),!this.promise||!this.image){o("Initiating DummyImageContainer"),f.prototype.image=new Image;var n=this.image;f.prototype.promise=new Promise(function(e,f){n.onload=e,n.onerror=f,n.src=d(),n.complete===!0&&e(n)})}}var o=e("./log"),d=e("./utils").smallImage;n.exports=f},{"./log":13,"./utils":26}],6:[function(e,n){function f(e,n){var f,d,i=document.createElement("div"),t=document.createElement("img"),l=document.createElement("span"),s="Hidden Text";i.style.visibility="hidden",i.style.fontFamily=e,i.style.fontSize=n,i.style.margin=0,i.style.padding=0,document.body.appendChild(i),t.src=o(),t.width=1,t.height=1,t.style.margin=0,t.style.padding=0,t.style.verticalAlign="baseline",l.style.fontFamily=e,l.style.fontSize=n,l.style.margin=0,l.style.padding=0,l.appendChild(document.createTextNode(s)),i.appendChild(l),i.appendChild(t),f=t.offsetTop-l.offsetTop+1,i.removeChild(l),i.appendChild(document.createTextNode(s)),i.style.lineHeight="normal",t.style.verticalAlign="super",d=t.offsetTop-i.offsetTop+1,document.body.removeChild(i),this.baseline=f,this.lineWidth=1,this.middle=d}var o=e("./utils").smallImage;n.exports=f},{"./utils":26}],7:[function(e,n){function f(){this.data={}}var o=e("./font");f.prototype.getMetrics=function(e,n){return void 0===this.data[e+"-"+n]&&(this.data[e+"-"+n]=new o(e,n)),this.data[e+"-"+n]},n.exports=f},{"./font":6}],8:[function(e,n){function f(n,f,o){this.image=null,this.src=n;var i=this,t=d(n);this.promise=(f?new Promise(function(e){"about:blank"===n.contentWindow.document.URL||null==n.contentWindow.document.documentElement?n.contentWindow.onload=n.onload=function(){e(n)}:e(n)}):this.proxyLoad(o.proxy,t,o)).then(function(n){var f=e("./core");return f(n.contentWindow.document.documentElement,{type:"view",width:n.width,height:n.height,proxy:o.proxy,javascriptEnabled:o.javascriptEnabled,removeContainer:o.removeContainer,allowTaint:o.allowTaint,imageTimeout:o.imageTimeout/2})}).then(function(e){return i.image=e})}var o=e("./utils"),d=o.getBounds,i=e("./proxy").loadUrlDocument;f.prototype.proxyLoad=function(e,n,f){var o=this.src;return i(o.src,e,o.ownerDocument,n.width,n.height,f)},n.exports=f},{"./core":4,"./proxy":16,"./utils":26}],9:[function(e,n){function f(e){this.src=e.value,this.colorStops=[],this.type=null,this.x0=.5,this.y0=.5,this.x1=.5,this.y1=.5,this.promise=Promise.resolve(!0)}f.TYPES={LINEAR:1,RADIAL:2},f.REGEXP_COLORSTOP=/^\s*(rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[0-9\.]+)?\s*\)|[a-z]{3,20}|#[a-f0-9]{3,6})(?:\s+(\d{1,3}(?:\.\d+)?)(%|px)?)?(?:\s|$)/i,n.exports=f},{}],10:[function(e,n){function f(e,n){this.src=e,this.image=new Image;var f=this;this.tainted=null,this.promise=new Promise(function(o,d){f.image.onload=o,f.image.onerror=d,n&&(f.image.crossOrigin="anonymous"),f.image.src=e,f.image.complete===!0&&o(f.image)})}n.exports=f},{}],11:[function(e,n){function f(e,n){this.link=null,this.options=e,this.support=n,this.origin=this.getOrigin(window.location.href)}var o=e("./log"),d=e("./imagecontainer"),i=e("./dummyimagecontainer"),t=e("./proxyimagecontainer"),l=e("./framecontainer"),s=e("./svgcontainer"),u=e("./svgnodecontainer"),a=e("./lineargradientcontainer"),p=e("./webkitgradientcontainer"),c=e("./utils").bind;f.prototype.findImages=function(e){var n=[];return e.reduce(function(e,n){switch(n.node.nodeName){case"IMG":return e.concat([{args:[n.node.src],method:"url"}]);case"svg":case"IFRAME":return e.concat([{args:[n.node],method:n.node.nodeName}])}return e},[]).forEach(this.addImage(n,this.loadImage),this),n},f.prototype.findBackgroundImage=function(e,n){return n.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(e,this.loadImage),this),e},f.prototype.addImage=function(e,n){return function(f){f.args.forEach(function(d){this.imageExists(e,d)||(e.splice(0,0,n.call(this,f)),o("Added image #"+e.length,"string"==typeof d?d.substring(0,100):d))},this)}},f.prototype.hasImageBackground=function(e){return"none"!==e.method},f.prototype.loadImage=function(e){if("url"===e.method){var n=e.args[0];return!this.isSVG(n)||this.support.svg||this.options.allowTaint?n.match(/data:image\/.*;base64,/i)?new d(n.replace(/url\(['"]{0,}|['"]{0,}\)$/gi,""),!1):this.isSameOrigin(n)||this.options.allowTaint===!0||this.isSVG(n)?new d(n,!1):this.support.cors&&!this.options.allowTaint&&this.options.useCORS?new d(n,!0):this.options.proxy?new t(n,this.options.proxy):new i(n):new s(n)}return"linear-gradient"===e.method?new a(e):"gradient"===e.method?new p(e):"svg"===e.method?new u(e.args[0],this.support.svg):"IFRAME"===e.method?new l(e.args[0],this.isSameOrigin(e.args[0].src),this.options):new i(e)},f.prototype.isSVG=function(e){return"svg"===e.substring(e.length-3).toLowerCase()||s.prototype.isInline(e)},f.prototype.imageExists=function(e,n){return e.some(function(e){return e.src===n})},f.prototype.isSameOrigin=function(e){return this.getOrigin(e)===this.origin},f.prototype.getOrigin=function(e){var n=this.link||(this.link=document.createElement("a"));return n.href=e,n.href=n.href,n.protocol+n.hostname+n.port},f.prototype.getPromise=function(e){return this.timeout(e,this.options.imageTimeout)["catch"](function(){var n=new i(e.src);return n.promise.then(function(n){e.image=n})})},f.prototype.get=function(e){var n=null;return this.images.some(function(f){return(n=f).src===e})?n:null},f.prototype.fetch=function(e){return this.images=e.reduce(c(this.findBackgroundImage,this),this.findImages(e)),this.images.forEach(function(e,n){e.promise.then(function(){o("Succesfully loaded image #"+(n+1),e)},function(f){o("Failed loading image #"+(n+1),e,f)})}),this.ready=Promise.all(this.images.map(this.getPromise,this)),o("Finished searching images"),this},f.prototype.timeout=function(e,n){var f,d=Promise.race([e.promise,new Promise(function(d,i){f=setTimeout(function(){o("Timed out loading image",e),i(e)},n)})]).then(function(e){return clearTimeout(f),e});return d["catch"](function(){clearTimeout(f)}),d},n.exports=f},{"./dummyimagecontainer":5,"./framecontainer":8,"./imagecontainer":10,"./lineargradientcontainer":12,"./log":13,"./proxyimagecontainer":17,"./svgcontainer":23,"./svgnodecontainer":24,"./utils":26,"./webkitgradientcontainer":27}],12:[function(e,n){function f(e){o.apply(this,arguments),this.type=o.TYPES.LINEAR;var n=f.REGEXP_DIRECTION.test(e.args[0])||!o.REGEXP_COLORSTOP.test(e.args[0]);n?e.args[0].split(/\s+/).reverse().forEach(function(e,n){switch(e){case"left":this.x0=0,this.x1=1;break;case"top":this.y0=0,this.y1=1;break;case"right":this.x0=1,this.x1=0;break;case"bottom":this.y0=1,this.y1=0;break;case"to":var f=this.y0,o=this.x0;this.y0=this.y1,this.x0=this.x1,this.x1=o,this.y1=f;break;case"center":break;default:var d=.01*parseFloat(e,10);if(isNaN(d))break;0===n?(this.y0=d,this.y1=1-this.y0):(this.x0=d,this.x1=1-this.x0)}},this):(this.y0=0,this.y1=1),this.colorStops=e.args.slice(n?1:0).map(function(e){var n=e.match(o.REGEXP_COLORSTOP),f=+n[2],i=0===f?"%":n[3];return{color:new d(n[1]),stop:"%"===i?f/100:null}}),null===this.colorStops[0].stop&&(this.colorStops[0].stop=0),null===this.colorStops[this.colorStops.length-1].stop&&(this.colorStops[this.colorStops.length-1].stop=1),this.colorStops.forEach(function(e,n){null===e.stop&&this.colorStops.slice(n).some(function(f,o){return null!==f.stop?(e.stop=(f.stop-this.colorStops[n-1].stop)/(o+1)+this.colorStops[n-1].stop,!0):!1},this)},this)}var o=e("./gradientcontainer"),d=e("./color");f.prototype=Object.create(o.prototype),f.REGEXP_DIRECTION=/^\s*(?:to|left|right|top|bottom|center|\d{1,3}(?:\.\d+)?%?)(?:\s|$)/i,n.exports=f},{"./color":3,"./gradientcontainer":9}],13:[function(e,n){var f=function(){f.options.logging&&window.console&&window.console.log&&Function.prototype.bind.call(window.console.log,window.console).apply(window.console,[Date.now()-f.options.start+"ms","html2canvas:"].concat([].slice.call(arguments,0)))};f.options={logging:!1},n.exports=f},{}],14:[function(e,n){function f(e,n){this.node=e,this.parent=n,this.stack=null,this.bounds=null,this.borders=null,this.clip=[],this.backgroundClip=[],this.offsetBounds=null,this.visible=null,this.computedStyles=null,this.colors={},this.styles={},this.backgroundImages=null,this.transformData=null,this.transformMatrix=null,this.isPseudoElement=!1,this.opacity=null}function o(e){var n=e.options[e.selectedIndex||0];return n?n.text||"":""}function d(e){if(e&&"matrix"===e[1])return e[2].split(",").map(function(e){return parseFloat(e.trim())});if(e&&"matrix3d"===e[1]){var n=e[2].split(",").map(function(e){return parseFloat(e.trim())});return[n[0],n[1],n[4],n[5],n[12],n[13]]}}function i(e){return-1!==e.toString().indexOf("%")}function t(e){return e.replace("px","")}function l(e){return parseFloat(e)}var s=e("./color"),u=e("./utils"),a=u.getBounds,p=u.parseBackgrounds,c=u.offsetBounds;f.prototype.cloneTo=function(e){e.visible=this.visible,e.borders=this.borders,e.bounds=this.bounds,e.clip=this.clip,e.backgroundClip=this.backgroundClip,e.computedStyles=this.computedStyles,e.styles=this.styles,e.backgroundImages=this.backgroundImages,e.opacity=this.opacity},f.prototype.getOpacity=function(){return null===this.opacity?this.opacity=this.cssFloat("opacity"):this.opacity},f.prototype.assignStack=function(e){this.stack=e,e.children.push(this)},f.prototype.isElementVisible=function(){return this.node.nodeType===Node.TEXT_NODE?this.parent.visible:"none"!==this.css("display")&&"hidden"!==this.css("visibility")&&!this.node.hasAttribute("data-html2canvas-ignore")&&("INPUT"!==this.node.nodeName||"hidden"!==this.node.getAttribute("type"))},f.prototype.css=function(e){return this.computedStyles||(this.computedStyles=this.isPseudoElement?this.parent.computedStyle(this.before?":before":":after"):this.computedStyle(null)),this.styles[e]||(this.styles[e]=this.computedStyles[e])},f.prototype.prefixedCss=function(e){var n=["webkit","moz","ms","o"],f=this.css(e);return void 0===f&&n.some(function(n){return f=this.css(n+e.substr(0,1).toUpperCase()+e.substr(1)),void 0!==f},this),void 0===f?null:f},f.prototype.computedStyle=function(e){return this.node.ownerDocument.defaultView.getComputedStyle(this.node,e)},f.prototype.cssInt=function(e){var n=parseInt(this.css(e),10);return isNaN(n)?0:n},f.prototype.color=function(e){return this.colors[e]||(this.colors[e]=new s(this.css(e)))},f.prototype.cssFloat=function(e){var n=parseFloat(this.css(e));return isNaN(n)?0:n},f.prototype.fontWeight=function(){var e=this.css("fontWeight");switch(parseInt(e,10)){case 401:e="bold";break;case 400:e="normal"}return e},f.prototype.parseClip=function(){var e=this.css("clip").match(this.CLIP);return e?{top:parseInt(e[1],10),right:parseInt(e[2],10),bottom:parseInt(e[3],10),left:parseInt(e[4],10)}:null},f.prototype.parseBackgroundImages=function(){return this.backgroundImages||(this.backgroundImages=p(this.css("backgroundImage")))},f.prototype.cssList=function(e,n){var f=(this.css(e)||"").split(",");return f=f[n||0]||f[0]||"auto",f=f.trim().split(" "),1===f.length&&(f=[f[0],i(f[0])?"auto":f[0]]),f},f.prototype.parseBackgroundSize=function(e,n,f){var o,d,t=this.cssList("backgroundSize",f);if(i(t[0]))o=e.width*parseFloat(t[0])/100;else{if(/contain|cover/.test(t[0])){var l=e.width/e.height,s=n.width/n.height;return s>l^"contain"===t[0]?{width:e.height*s,height:e.height}:{width:e.width,height:e.width/s}}o=parseInt(t[0],10)}return d="auto"===t[0]&&"auto"===t[1]?n.height:"auto"===t[1]?o/n.width*n.height:i(t[1])?e.height*parseFloat(t[1])/100:parseInt(t[1],10),"auto"===t[0]&&(o=d/n.height*n.width),{width:o,height:d}},f.prototype.parseBackgroundPosition=function(e,n,f,o){var d,t,l=this.cssList("backgroundPosition",f);return d=i(l[0])?(e.width-(o||n).width)*(parseFloat(l[0])/100):parseInt(l[0],10),t="auto"===l[1]?d/n.width*n.height:i(l[1])?(e.height-(o||n).height)*parseFloat(l[1])/100:parseInt(l[1],10),"auto"===l[0]&&(d=t/n.height*n.width),{left:d,top:t}},f.prototype.parseBackgroundRepeat=function(e){return this.cssList("backgroundRepeat",e)[0]},f.prototype.parseTextShadows=function(){var e=this.css("textShadow"),n=[];if(e&&"none"!==e)for(var f=e.match(this.TEXT_SHADOW_PROPERTY),o=0;f&&o0?(this.renderIndex=0,this.asyncRenderer(this.renderQueue,e)):e():(this.renderQueue.forEach(this.paint,this),e())},this))},this))}function o(e){return e.parent&&e.parent.clip.length}function d(e){return e.replace(/(\-[a-z])/g,function(e){return e.toUpperCase().replace("-","")})}function i(){}function t(e,n,f,o){return e.map(function(d,i){if(d.width>0){var t=n.left,l=n.top,s=n.width,u=n.height-e[2].width;switch(i){case 0:u=e[0].width,d.args=a({c1:[t,l],c2:[t+s,l],c3:[t+s-e[1].width,l+u],c4:[t+e[3].width,l+u]},o[0],o[1],f.topLeftOuter,f.topLeftInner,f.topRightOuter,f.topRightInner);break;case 1:t=n.left+n.width-e[1].width,s=e[1].width,d.args=a({c1:[t+s,l],c2:[t+s,l+u+e[2].width],c3:[t,l+u],c4:[t,l+e[0].width]},o[1],o[2],f.topRightOuter,f.topRightInner,f.bottomRightOuter,f.bottomRightInner);break;case 2:l=l+n.height-e[2].width,u=e[2].width,d.args=a({c1:[t+s,l+u],c2:[t,l+u],c3:[t+e[3].width,l],c4:[t+s-e[3].width,l]},o[2],o[3],f.bottomRightOuter,f.bottomRightInner,f.bottomLeftOuter,f.bottomLeftInner);break;case 3:s=e[3].width,d.args=a({c1:[t,l+u+e[2].width],c2:[t,l],c3:[t+s,l+e[0].width],c4:[t+s,l+u]},o[3],o[0],f.bottomLeftOuter,f.bottomLeftInner,f.topLeftOuter,f.topLeftInner)}}return d})}function l(e,n,f,o){var d=4*((Math.sqrt(2)-1)/3),i=f*d,t=o*d,l=e+f,s=n+o;return{topLeft:u({x:e,y:s},{x:e,y:s-t},{x:l-i,y:n},{x:l,y:n}),topRight:u({x:e,y:n},{x:e+i,y:n},{x:l,y:s-t},{x:l,y:s}),bottomRight:u({x:l,y:n},{x:l,y:n+t},{x:e+i,y:s},{x:e,y:s}),bottomLeft:u({x:l,y:s},{x:l-i,y:s},{x:e,y:n+t},{x:e,y:n})}}function s(e,n,f){var o=e.left,d=e.top,i=e.width,t=e.height,s=n[0][0]i+f[3].width?0:a-f[3].width,p-f[0].width).topRight.subdivide(.5),bottomRightOuter:l(o+b,d+w,c,y).bottomRight.subdivide(.5),bottomRightInner:l(o+Math.min(b,i-f[3].width),d+Math.min(w,t+f[0].width),Math.max(0,c-f[1].width),y-f[2].width).bottomRight.subdivide(.5),bottomLeftOuter:l(o,d+g,m,r).bottomLeft.subdivide(.5),bottomLeftInner:l(o+f[3].width,d+g,Math.max(0,m-f[3].width),r-f[2].width).bottomLeft.subdivide(.5)}
+}function u(e,n,f,o){var d=function(e,n,f){return{x:e.x+(n.x-e.x)*f,y:e.y+(n.y-e.y)*f}};return{start:e,startControl:n,endControl:f,end:o,subdivide:function(i){var t=d(e,n,i),l=d(n,f,i),s=d(f,o,i),a=d(t,l,i),p=d(l,s,i),c=d(a,p,i);return[u(e,t,a,c),u(c,p,s,o)]},curveTo:function(e){e.push(["bezierCurve",n.x,n.y,f.x,f.y,o.x,o.y])},curveToReversed:function(o){o.push(["bezierCurve",f.x,f.y,n.x,n.y,e.x,e.y])}}}function a(e,n,f,o,d,i,t){var l=[];return n[0]>0||n[1]>0?(l.push(["line",o[1].start.x,o[1].start.y]),o[1].curveTo(l)):l.push(["line",e.c1[0],e.c1[1]]),f[0]>0||f[1]>0?(l.push(["line",i[0].start.x,i[0].start.y]),i[0].curveTo(l),l.push(["line",t[0].end.x,t[0].end.y]),t[0].curveToReversed(l)):(l.push(["line",e.c2[0],e.c2[1]]),l.push(["line",e.c3[0],e.c3[1]])),n[0]>0||n[1]>0?(l.push(["line",d[1].end.x,d[1].end.y]),d[1].curveToReversed(l)):l.push(["line",e.c4[0],e.c4[1]]),l}function p(e,n,f,o,d,i,t){n[0]>0||n[1]>0?(e.push(["line",o[0].start.x,o[0].start.y]),o[0].curveTo(e),o[1].curveTo(e)):e.push(["line",i,t]),(f[0]>0||f[1]>0)&&e.push(["line",d[0].start.x,d[0].start.y])}function c(e){return e.cssInt("zIndex")<0}function y(e){return e.cssInt("zIndex")>0}function m(e){return 0===e.cssInt("zIndex")}function r(e){return-1!==["inline","inline-block","inline-table"].indexOf(e.css("display"))}function v(e){return e instanceof U}function w(e){return e.node.data.trim().length>0}function b(e){return/^(normal|none|0px)$/.test(e.parent.css("letterSpacing"))}function g(e){return["TopLeft","TopRight","BottomRight","BottomLeft"].map(function(n){var f=e.css("border"+n+"Radius"),o=f.split(" ");return o.length<=1&&(o[1]=o[0]),o.map(F)})}function h(e){return e.nodeType===Node.TEXT_NODE||e.nodeType===Node.ELEMENT_NODE}function x(e){var n=e.css("position"),f=-1!==["absolute","relative","fixed"].indexOf(n)?e.css("zIndex"):"auto";return"auto"!==f}function j(e){return"static"!==e.css("position")}function k(e){return"none"!==e.css("float")}function q(e){return-1!==["inline-block","inline-table"].indexOf(e.css("display"))}function z(e){var n=this;return function(){return!e.apply(n,arguments)}}function A(e){return e.node.nodeType===Node.ELEMENT_NODE}function B(e){return e.isPseudoElement===!0}function C(e){return e.node.nodeType===Node.TEXT_NODE}function D(e){return function(n,f){return n.cssInt("zIndex")+e.indexOf(n)/e.length-(f.cssInt("zIndex")+e.indexOf(f)/e.length)}}function E(e){return e.getOpacity()<1}function F(e){return parseInt(e,10)}function G(e){return e.width}function H(e){return e.node.nodeType!==Node.ELEMENT_NODE||-1===["SCRIPT","HEAD","TITLE","OBJECT","BR","OPTION"].indexOf(e.node.nodeName)}function I(e){return[].concat.apply([],e)}function J(e){var n=e.substr(0,1);return n===e.substr(e.length-1)&&n.match(/'|"/)?e.substr(1,e.length-2):e}function K(e){for(var n,f=[],o=0,d=!1;e.length;)L(e[o])===d?(n=e.splice(0,o),n.length&&f.push(O.ucs2.encode(n)),d=!d,o=0):o++,o>=e.length&&(n=e.splice(0,o),n.length&&f.push(O.ucs2.encode(n)));return f}function L(e){return-1!==[32,13,10,9,45].indexOf(e)}function M(e){return/[^\u0000-\u00ff]/.test(e)}var N=e("./log"),O=e("punycode"),P=e("./nodecontainer"),Q=e("./textcontainer"),R=e("./pseudoelementcontainer"),S=e("./fontmetrics"),T=e("./color"),U=e("./stackingcontext"),V=e("./utils"),W=V.bind,X=V.getBounds,Y=V.parseBackgrounds,Z=V.offsetBounds;f.prototype.calculateOverflowClips=function(){this.nodes.forEach(function(e){if(A(e)){B(e)&&e.appendToDOM(),e.borders=this.parseBorders(e);var n="hidden"===e.css("overflow")?[e.borders.clip]:[],f=e.parseClip();f&&-1!==["absolute","fixed"].indexOf(e.css("position"))&&n.push([["rect",e.bounds.left+f.left,e.bounds.top+f.top,f.right-f.left,f.bottom-f.top]]),e.clip=o(e)?e.parent.clip.concat(n):n,e.backgroundClip="hidden"!==e.css("overflow")?e.clip.concat([e.borders.clip]):e.clip,B(e)&&e.cleanDOM()}else C(e)&&(e.clip=o(e)?e.parent.clip:[]);B(e)||(e.bounds=null)},this)},f.prototype.asyncRenderer=function(e,n,f){f=f||Date.now(),this.paint(e[this.renderIndex++]),e.length===this.renderIndex?n():f+20>Date.now()?this.asyncRenderer(e,n,f):setTimeout(W(function(){this.asyncRenderer(e,n)},this),0)},f.prototype.createPseudoHideStyles=function(e){this.createStyles(e,"."+R.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+':before { content: "" !important; display: none !important; }.'+R.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER+':after { content: "" !important; display: none !important; }')},f.prototype.disableAnimations=function(e){this.createStyles(e,"* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; -webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}")},f.prototype.createStyles=function(e,n){var f=e.createElement("style");f.innerHTML=n,e.body.appendChild(f)},f.prototype.getPseudoElements=function(e){var n=[[e]];if(e.node.nodeType===Node.ELEMENT_NODE){var f=this.getPseudoElement(e,":before"),o=this.getPseudoElement(e,":after");f&&n.push(f),o&&n.push(o)}return I(n)},f.prototype.getPseudoElement=function(e,n){var f=e.computedStyle(n);if(!f||!f.content||"none"===f.content||"-moz-alt-content"===f.content||"none"===f.display)return null;for(var o=J(f.content),i="url"===o.substr(0,3),t=document.createElement(i?"img":"html2canvaspseudoelement"),l=new R(t,e,n),s=f.length-1;s>=0;s--){var u=d(f.item(s));t.style[u]=f[u]}if(t.className=R.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE+" "+R.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER,i)return t.src=Y(o)[0].args[0],[l];var a=document.createTextNode(o);return t.appendChild(a),[l,new Q(a,l)]},f.prototype.getChildren=function(e){return I([].filter.call(e.node.childNodes,h).map(function(n){var f=[n.nodeType===Node.TEXT_NODE?new Q(n,e):new P(n,e)].filter(H);return n.nodeType===Node.ELEMENT_NODE&&f.length&&"TEXTAREA"!==n.tagName?f[0].isElementVisible()?f.concat(this.getChildren(f[0])):[]:f},this))},f.prototype.newStackingContext=function(e,n){var f=new U(n,e.getOpacity(),e.node,e.parent);e.cloneTo(f);var o=n?f.getParentStack(this):f.parent.stack;o.contexts.push(f),e.stack=f},f.prototype.createStackingContexts=function(){this.nodes.forEach(function(e){A(e)&&(this.isRootElement(e)||E(e)||x(e)||this.isBodyWithTransparentRoot(e)||e.hasTransform())?this.newStackingContext(e,!0):A(e)&&(j(e)&&m(e)||q(e)||k(e))?this.newStackingContext(e,!1):e.assignStack(e.parent.stack)},this)},f.prototype.isBodyWithTransparentRoot=function(e){return"BODY"===e.node.nodeName&&e.parent.color("backgroundColor").isTransparent()},f.prototype.isRootElement=function(e){return null===e.parent},f.prototype.sortStackingContexts=function(e){e.contexts.sort(D(e.contexts.slice(0))),e.contexts.forEach(this.sortStackingContexts,this)},f.prototype.parseTextBounds=function(e){return function(n,f,o){if("none"!==e.parent.css("textDecoration").substr(0,4)||0!==n.trim().length){if(this.support.rangeBounds&&!e.parent.hasTransform()){var d=o.slice(0,f).join("").length;return this.getRangeBounds(e.node,d,n.length)}if(e.node&&"string"==typeof e.node.data){var i=e.node.splitText(n.length),t=this.getWrapperBounds(e.node,e.parent.hasTransform());return e.node=i,t}}else(!this.support.rangeBounds||e.parent.hasTransform())&&(e.node=e.node.splitText(n.length));return{}}},f.prototype.getWrapperBounds=function(e,n){var f=e.ownerDocument.createElement("html2canvaswrapper"),o=e.parentNode,d=e.cloneNode(!0);f.appendChild(e.cloneNode(!0)),o.replaceChild(f,e);var i=n?Z(f):X(f);return o.replaceChild(d,f),i},f.prototype.getRangeBounds=function(e,n,f){var o=this.range||(this.range=e.ownerDocument.createRange());return o.setStart(e,n),o.setEnd(e,n+f),o.getBoundingClientRect()},f.prototype.parse=function(e){var n=e.contexts.filter(c),f=e.children.filter(A),o=f.filter(z(k)),d=o.filter(z(j)).filter(z(r)),t=f.filter(z(j)).filter(k),l=o.filter(z(j)).filter(r),s=e.contexts.concat(o.filter(j)).filter(m),u=e.children.filter(C).filter(w),a=e.contexts.filter(y);n.concat(d).concat(t).concat(l).concat(s).concat(u).concat(a).forEach(function(e){this.renderQueue.push(e),v(e)&&(this.parse(e),this.renderQueue.push(new i))},this)},f.prototype.paint=function(e){try{e instanceof i?this.renderer.ctx.restore():C(e)?(B(e.parent)&&e.parent.appendToDOM(),this.paintText(e),B(e.parent)&&e.parent.cleanDOM()):this.paintNode(e)}catch(n){if(N(n),this.options.strict)throw n}},f.prototype.paintNode=function(e){v(e)&&(this.renderer.setOpacity(e.opacity),this.renderer.ctx.save(),e.hasTransform()&&this.renderer.setTransform(e.parseTransform())),"INPUT"===e.node.nodeName&&"checkbox"===e.node.type?this.paintCheckbox(e):"INPUT"===e.node.nodeName&&"radio"===e.node.type?this.paintRadio(e):this.paintElement(e)},f.prototype.paintElement=function(e){var n=e.parseBounds();this.renderer.clip(e.backgroundClip,function(){this.renderer.renderBackground(e,n,e.borders.borders.map(G))},this),this.renderer.clip(e.clip,function(){this.renderer.renderBorders(e.borders.borders)},this),this.renderer.clip(e.backgroundClip,function(){switch(e.node.nodeName){case"svg":case"IFRAME":var f=this.images.get(e.node);f?this.renderer.renderImage(e,n,e.borders,f):N("Error loading <"+e.node.nodeName+">",e.node);break;case"IMG":var o=this.images.get(e.node.src);o?this.renderer.renderImage(e,n,e.borders,o):N("Error loading ",e.node.src);break;case"CANVAS":this.renderer.renderImage(e,n,e.borders,{image:e.node});break;case"SELECT":case"INPUT":case"TEXTAREA":this.paintFormValue(e)}},this)},f.prototype.paintCheckbox=function(e){var n=e.parseBounds(),f=Math.min(n.width,n.height),o={width:f-1,height:f-1,top:n.top,left:n.left},d=[3,3],i=[d,d,d,d],l=[1,1,1,1].map(function(e){return{color:new T("#A5A5A5"),width:e}}),u=s(o,i,l);this.renderer.clip(e.backgroundClip,function(){this.renderer.rectangle(o.left+1,o.top+1,o.width-2,o.height-2,new T("#DEDEDE")),this.renderer.renderBorders(t(l,o,u,i)),e.node.checked&&(this.renderer.font(new T("#424242"),"normal","normal","bold",f-3+"px","arial"),this.renderer.text("✔",o.left+f/6,o.top+f-1))},this)},f.prototype.paintRadio=function(e){var n=e.parseBounds(),f=Math.min(n.width,n.height)-2;this.renderer.clip(e.backgroundClip,function(){this.renderer.circleStroke(n.left+1,n.top+1,f,new T("#DEDEDE"),1,new T("#A5A5A5")),e.node.checked&&this.renderer.circle(Math.ceil(n.left+f/4)+1,Math.ceil(n.top+f/4)+1,Math.floor(f/2),new T("#424242"))},this)},f.prototype.paintFormValue=function(e){var n=e.getValue();if(n.length>0){var f=e.node.ownerDocument,o=f.createElement("html2canvaswrapper"),d=["lineHeight","textAlign","fontFamily","fontWeight","fontSize","color","paddingLeft","paddingTop","paddingRight","paddingBottom","width","height","borderLeftStyle","borderTopStyle","borderLeftWidth","borderTopWidth","boxSizing","whiteSpace","wordWrap"];d.forEach(function(n){try{o.style[n]=e.css(n)}catch(f){N("html2canvas: Parse: Exception caught in renderFormValue: "+f.message)}});var i=e.parseBounds();o.style.position="fixed",o.style.left=i.left+"px",o.style.top=i.top+"px",o.textContent=n,f.body.appendChild(o),this.paintText(new Q(o.firstChild,e)),f.body.removeChild(o)}},f.prototype.paintText=function(e){e.applyTextTransform();var n=O.ucs2.decode(e.node.data),f=this.options.letterRendering&&!b(e)||M(e.node.data)?n.map(function(e){return O.ucs2.encode([e])}):K(n),o=e.parent.fontWeight(),d=e.parent.css("fontSize"),i=e.parent.css("fontFamily"),t=e.parent.parseTextShadows();this.renderer.font(e.parent.color("color"),e.parent.css("fontStyle"),e.parent.css("fontVariant"),o,d,i),t.length?this.renderer.fontShadow(t[0].color,t[0].offsetX,t[0].offsetY,t[0].blur):this.renderer.clearShadow(),this.renderer.clip(e.parent.clip,function(){f.map(this.parseTextBounds(e),this).forEach(function(n,o){n&&(this.renderer.text(f[o],n.left,n.bottom),this.renderTextDecoration(e.parent,n,this.fontMetrics.getMetrics(i,d)))},this)},this)},f.prototype.renderTextDecoration=function(e,n,f){switch(e.css("textDecoration").split(" ")[0]){case"underline":this.renderer.rectangle(n.left,Math.round(n.top+f.baseline+f.lineWidth),n.width,1,e.color("color"));break;case"overline":this.renderer.rectangle(n.left,Math.round(n.top),n.width,1,e.color("color"));break;case"line-through":this.renderer.rectangle(n.left,Math.ceil(n.top+f.middle+f.lineWidth),n.width,1,e.color("color"))}};var $={inset:[["darken",.6],["darken",.1],["darken",.1],["darken",.6]]};f.prototype.parseBorders=function(e){var n=e.parseBounds(),f=g(e),o=["Top","Right","Bottom","Left"].map(function(n,f){var o=e.css("border"+n+"Style"),d=e.color("border"+n+"Color");"inset"===o&&d.isBlack()&&(d=new T([255,255,255,d.a]));var i=$[o]?$[o][f]:null;return{width:e.cssInt("border"+n+"Width"),color:i?d[i[0]](i[1]):d,args:null}}),d=s(n,f,o);return{clip:this.parseBackgroundClip(e,d,o,f,n),borders:t(o,n,d,f)}},f.prototype.parseBackgroundClip=function(e,n,f,o,d){var i=e.css("backgroundClip"),t=[];switch(i){case"content-box":case"padding-box":p(t,o[0],o[1],n.topLeftInner,n.topRightInner,d.left+f[3].width,d.top+f[0].width),p(t,o[1],o[2],n.topRightInner,n.bottomRightInner,d.left+d.width-f[1].width,d.top+f[0].width),p(t,o[2],o[3],n.bottomRightInner,n.bottomLeftInner,d.left+d.width-f[1].width,d.top+d.height-f[2].width),p(t,o[3],o[0],n.bottomLeftInner,n.topLeftInner,d.left+f[3].width,d.top+d.height-f[2].width);break;default:p(t,o[0],o[1],n.topLeftOuter,n.topRightOuter,d.left,d.top),p(t,o[1],o[2],n.topRightOuter,n.bottomRightOuter,d.left+d.width,d.top),p(t,o[2],o[3],n.bottomRightOuter,n.bottomLeftOuter,d.left+d.width,d.top+d.height),p(t,o[3],o[0],n.bottomLeftOuter,n.topLeftOuter,d.left,d.top+d.height)}return t},n.exports=f},{"./color":3,"./fontmetrics":7,"./log":13,"./nodecontainer":14,"./pseudoelementcontainer":18,"./stackingcontext":21,"./textcontainer":25,"./utils":26,punycode:1}],16:[function(e,n,f){function o(e,n,f){var o="withCredentials"in new XMLHttpRequest;if(!n)return Promise.reject("No proxy configured");var d=t(o),s=l(n,e,d);return o?a(s):i(f,s,d).then(function(e){return m(e.content)})}function d(e,n,f){var o="crossOrigin"in new Image,d=t(o),s=l(n,e,d);return o?Promise.resolve(s):i(f,s,d).then(function(e){return"data:"+e.type+";base64,"+e.content})}function i(e,n,f){return new Promise(function(o,d){var i=e.createElement("script"),t=function(){delete window.html2canvas.proxy[f],e.body.removeChild(i)};window.html2canvas.proxy[f]=function(e){t(),o(e)},i.src=n,i.onerror=function(e){t(),d(e)},e.body.appendChild(i)})}function t(e){return e?"":"html2canvas_"+Date.now()+"_"+ ++r+"_"+Math.round(1e5*Math.random())}function l(e,n,f){return e+"?url="+encodeURIComponent(n)+(f.length?"&callback=html2canvas.proxy."+f:"")}function s(e){return function(n){var f,o=new DOMParser;try{f=o.parseFromString(n,"text/html")}catch(d){c("DOMParser not supported, falling back to createHTMLDocument"),f=document.implementation.createHTMLDocument("");try{f.open(),f.write(n),f.close()}catch(i){c("createHTMLDocument write not supported, falling back to document.body.innerHTML"),f.body.innerHTML=n}}var t=f.querySelector("base");if(!t||!t.href.host){var l=f.createElement("base");l.href=e,f.head.insertBefore(l,f.head.firstChild)}return f}}function u(e,n,f,d,i,t){return new o(e,n,window.document).then(s(e)).then(function(e){return y(e,f,d,i,t,0,0)})}var a=e("./xhr"),p=e("./utils"),c=e("./log"),y=e("./clone"),m=p.decode64,r=0;f.Proxy=o,f.ProxyURL=d,f.loadUrlDocument=u},{"./clone":2,"./log":13,"./utils":26,"./xhr":28}],17:[function(e,n){function f(e,n){var f=document.createElement("a");f.href=e,e=f.href,this.src=e,this.image=new Image;var d=this;this.promise=new Promise(function(f,i){d.image.crossOrigin="Anonymous",d.image.onload=f,d.image.onerror=i,new o(e,n,document).then(function(e){d.image.src=e})["catch"](i)})}var o=e("./proxy").ProxyURL;n.exports=f},{"./proxy":16}],18:[function(e,n){function f(e,n,f){o.call(this,e,n),this.isPseudoElement=!0,this.before=":before"===f}var o=e("./nodecontainer");f.prototype.cloneTo=function(e){f.prototype.cloneTo.call(this,e),e.isPseudoElement=!0,e.before=this.before},f.prototype=Object.create(o.prototype),f.prototype.appendToDOM=function(){this.before?this.parent.node.insertBefore(this.node,this.parent.node.firstChild):this.parent.node.appendChild(this.node),this.parent.node.className+=" "+this.getHideClass()},f.prototype.cleanDOM=function(){this.node.parentNode.removeChild(this.node),this.parent.node.className=this.parent.node.className.replace(this.getHideClass(),"")},f.prototype.getHideClass=function(){return this["PSEUDO_HIDE_ELEMENT_CLASS_"+(this.before?"BEFORE":"AFTER")]},f.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE="___html2canvas___pseudoelement_before",f.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER="___html2canvas___pseudoelement_after",n.exports=f},{"./nodecontainer":14}],19:[function(e,n){function f(e,n,f,o,d){this.width=e,this.height=n,this.images=f,this.options=o,this.document=d}var o=e("./log");f.prototype.renderImage=function(e,n,f,o){var d=e.cssInt("paddingLeft"),i=e.cssInt("paddingTop"),t=e.cssInt("paddingRight"),l=e.cssInt("paddingBottom"),s=f.borders,u=n.width-(s[1].width+s[3].width+d+t),a=n.height-(s[0].width+s[2].width+i+l);this.drawImage(o,0,0,o.image.width||u,o.image.height||a,n.left+d+s[3].width,n.top+i+s[0].width,u,a)},f.prototype.renderBackground=function(e,n,f){n.height>0&&n.width>0&&(this.renderBackgroundColor(e,n),this.renderBackgroundImage(e,n,f))},f.prototype.renderBackgroundColor=function(e,n){var f=e.color("backgroundColor");f.isTransparent()||this.rectangle(n.left,n.top,n.width,n.height,f)},f.prototype.renderBorders=function(e){e.forEach(this.renderBorder,this)},f.prototype.renderBorder=function(e){e.color.isTransparent()||null===e.args||this.drawShape(e.args,e.color)},f.prototype.renderBackgroundImage=function(e,n,f){var d=e.parseBackgroundImages();d.reverse().forEach(function(d,i,t){switch(d.method){case"url":var l=this.images.get(d.args[0]);l?this.renderBackgroundRepeating(e,n,l,t.length-(i+1),f):o("Error loading background-image",d.args[0]);break;case"linear-gradient":case"gradient":var s=this.images.get(d.value);s?this.renderBackgroundGradient(s,n,f):o("Error loading background-image",d.args[0]);break;case"none":break;default:o("Unknown background-image type",d.args[0])}},this)},f.prototype.renderBackgroundRepeating=function(e,n,f,o,d){var i=e.parseBackgroundSize(n,f.image,o),t=e.parseBackgroundPosition(n,f.image,o,i),l=e.parseBackgroundRepeat(o);switch(l){case"repeat-x":case"repeat no-repeat":this.backgroundRepeatShape(f,t,i,n,n.left+d[3],n.top+t.top+d[0],99999,i.height,d);break;case"repeat-y":case"no-repeat repeat":this.backgroundRepeatShape(f,t,i,n,n.left+t.left+d[3],n.top+d[0],i.width,99999,d);break;case"no-repeat":this.backgroundRepeatShape(f,t,i,n,n.left+t.left+d[3],n.top+t.top+d[0],i.width,i.height,d);break;default:this.renderBackgroundRepeat(f,t,i,{top:n.top,left:n.left},d[3],d[0])}},n.exports=f},{"./log":13}],20:[function(e,n){function f(e,n){d.apply(this,arguments),this.canvas=this.options.canvas||this.document.createElement("canvas"),this.options.canvas||(this.canvas.width=e,this.canvas.height=n),this.ctx=this.canvas.getContext("2d"),this.taintCtx=this.document.createElement("canvas").getContext("2d"),this.ctx.textBaseline="bottom",this.variables={},t("Initialized CanvasRenderer with size",e,"x",n)}function o(e){return e.length>0}var d=e("../renderer"),i=e("../lineargradientcontainer"),t=e("../log");f.prototype=Object.create(d.prototype),f.prototype.setFillStyle=function(e){return this.ctx.fillStyle="object"==typeof e&&e.isColor?e.toString():e,this.ctx},f.prototype.rectangle=function(e,n,f,o,d){this.setFillStyle(d).fillRect(e,n,f,o)},f.prototype.circle=function(e,n,f,o){this.setFillStyle(o),this.ctx.beginPath(),this.ctx.arc(e+f/2,n+f/2,f/2,0,2*Math.PI,!0),this.ctx.closePath(),this.ctx.fill()},f.prototype.circleStroke=function(e,n,f,o,d,i){this.circle(e,n,f,o),this.ctx.strokeStyle=i.toString(),this.ctx.stroke()},f.prototype.drawShape=function(e,n){this.shape(e),this.setFillStyle(n).fill()},f.prototype.taints=function(e){if(null===e.tainted){this.taintCtx.drawImage(e.image,0,0);try{this.taintCtx.getImageData(0,0,1,1),e.tainted=!1}catch(n){this.taintCtx=document.createElement("canvas").getContext("2d"),e.tainted=!0}}return e.tainted},f.prototype.drawImage=function(e,n,f,o,d,i,t,l,s){(!this.taints(e)||this.options.allowTaint)&&this.ctx.drawImage(e.image,n,f,o,d,i,t,l,s)},f.prototype.clip=function(e,n,f){this.ctx.save(),e.filter(o).forEach(function(e){this.shape(e).clip()},this),n.call(f),this.ctx.restore()},f.prototype.shape=function(e){return this.ctx.beginPath(),e.forEach(function(e,n){"rect"===e[0]?this.ctx.rect.apply(this.ctx,e.slice(1)):this.ctx[0===n?"moveTo":e[0]+"To"].apply(this.ctx,e.slice(1))},this),this.ctx.closePath(),this.ctx},f.prototype.font=function(e,n,f,o,d,i){this.setFillStyle(e).font=[n,f,o,d,i].join(" ").split(",")[0]},f.prototype.fontShadow=function(e,n,f,o){this.setVariable("shadowColor",e.toString()).setVariable("shadowOffsetY",n).setVariable("shadowOffsetX",f).setVariable("shadowBlur",o)},f.prototype.clearShadow=function(){this.setVariable("shadowColor","rgba(0,0,0,0)")},f.prototype.setOpacity=function(e){this.ctx.globalAlpha=e},f.prototype.setTransform=function(e){this.ctx.translate(e.origin[0],e.origin[1]),this.ctx.transform.apply(this.ctx,e.matrix),this.ctx.translate(-e.origin[0],-e.origin[1])},f.prototype.setVariable=function(e,n){return this.variables[e]!==n&&(this.variables[e]=this.ctx[e]=n),this},f.prototype.text=function(e,n,f){this.ctx.fillText(e,n,f)},f.prototype.backgroundRepeatShape=function(e,n,f,o,d,i,t,l,s){var u=[["line",Math.round(d),Math.round(i)],["line",Math.round(d+t),Math.round(i)],["line",Math.round(d+t),Math.round(l+i)],["line",Math.round(d),Math.round(l+i)]];this.clip([u],function(){this.renderBackgroundRepeat(e,n,f,o,s[3],s[0])},this)},f.prototype.renderBackgroundRepeat=function(e,n,f,o,d,i){var t=Math.round(o.left+n.left+d),l=Math.round(o.top+n.top+i);this.setFillStyle(this.ctx.createPattern(this.resizeImage(e,f),"repeat")),this.ctx.translate(t,l),this.ctx.fill(),this.ctx.translate(-t,-l)},f.prototype.renderBackgroundGradient=function(e,n){if(e instanceof i){var f=this.ctx.createLinearGradient(n.left+n.width*e.x0,n.top+n.height*e.y0,n.left+n.width*e.x1,n.top+n.height*e.y1);e.colorStops.forEach(function(e){f.addColorStop(e.stop,e.color.toString())}),this.rectangle(n.left,n.top,n.width,n.height,f)}},f.prototype.resizeImage=function(e,n){var f=e.image;if(f.width===n.width&&f.height===n.height)return f;var o,d=document.createElement("canvas");return d.width=n.width,d.height=n.height,o=d.getContext("2d"),o.drawImage(f,0,0,f.width,f.height,0,0,n.width,n.height),d},n.exports=f},{"../lineargradientcontainer":12,"../log":13,"../renderer":19}],21:[function(e,n){function f(e,n,f,d){o.call(this,f,d),this.ownStacking=e,this.contexts=[],this.children=[],this.opacity=(this.parent?this.parent.stack.opacity:1)*n}var o=e("./nodecontainer");f.prototype=Object.create(o.prototype),f.prototype.getParentStack=function(e){var n=this.parent?this.parent.stack:null;return n?n.ownStacking?n:n.getParentStack(e):e.stack},n.exports=f},{"./nodecontainer":14}],22:[function(e,n){function f(e){this.rangeBounds=this.testRangeBounds(e),this.cors=this.testCORS(),this.svg=this.testSVG()}f.prototype.testRangeBounds=function(e){var n,f,o,d,i=!1;return e.createRange&&(n=e.createRange(),n.getBoundingClientRect&&(f=e.createElement("boundtest"),f.style.height="123px",f.style.display="block",e.body.appendChild(f),n.selectNode(f),o=n.getBoundingClientRect(),d=o.height,123===d&&(i=!0),e.body.removeChild(f))),i},f.prototype.testCORS=function(){return"undefined"!=typeof(new Image).crossOrigin},f.prototype.testSVG=function(){var e=new Image,n=document.createElement("canvas"),f=n.getContext("2d");e.src="data:image/svg+xml,";try{f.drawImage(e,0,0),n.toDataURL()}catch(o){return!1}return!0},n.exports=f},{}],23:[function(e,n){function f(e){this.src=e,this.image=null;var n=this;this.promise=this.hasFabric().then(function(){return n.isInline(e)?Promise.resolve(n.inlineFormatting(e)):o(e)}).then(function(e){return new Promise(function(f){window.html2canvas.svg.fabric.loadSVGFromString(e,n.createCanvas.call(n,f))})})}var o=e("./xhr"),d=e("./utils").decode64;f.prototype.hasFabric=function(){return window.html2canvas.svg&&window.html2canvas.svg.fabric?Promise.resolve():Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg"))},f.prototype.inlineFormatting=function(e){return/^data:image\/svg\+xml;base64,/.test(e)?this.decode64(this.removeContentType(e)):this.removeContentType(e)},f.prototype.removeContentType=function(e){return e.replace(/^data:image\/svg\+xml(;base64)?,/,"")},f.prototype.isInline=function(e){return/^data:image\/svg\+xml/i.test(e)},f.prototype.createCanvas=function(e){var n=this;return function(f,o){var d=new window.html2canvas.svg.fabric.StaticCanvas("c");n.image=d.lowerCanvasEl,d.setWidth(o.width).setHeight(o.height).add(window.html2canvas.svg.fabric.util.groupSVGElements(f,o)).renderAll(),e(d.lowerCanvasEl)}},f.prototype.decode64=function(e){return"function"==typeof window.atob?window.atob(e):d(e)},n.exports=f},{"./utils":26,"./xhr":28}],24:[function(e,n){function f(e,n){this.src=e,this.image=null;var f=this;this.promise=n?new Promise(function(n,o){f.image=new Image,f.image.onload=n,f.image.onerror=o,f.image.src="data:image/svg+xml,"+(new XMLSerializer).serializeToString(e),f.image.complete===!0&&n(f.image)}):this.hasFabric().then(function(){return new Promise(function(n){window.html2canvas.svg.fabric.parseSVGDocument(e,f.createCanvas.call(f,n))})})}var o=e("./svgcontainer");f.prototype=Object.create(o.prototype),n.exports=f},{"./svgcontainer":23}],25:[function(e,n){function f(e,n){d.call(this,e,n)}function o(e,n,f){return e.length>0?n+f.toUpperCase():void 0}var d=e("./nodecontainer");f.prototype=Object.create(d.prototype),f.prototype.applyTextTransform=function(){this.node.data=this.transform(this.parent.css("textTransform"))},f.prototype.transform=function(e){var n=this.node.data;switch(e){case"lowercase":return n.toLowerCase();case"capitalize":return n.replace(/(^|\s|:|-|\(|\))([a-z])/g,o);case"uppercase":return n.toUpperCase();default:return n}},n.exports=f},{"./nodecontainer":14}],26:[function(e,n,f){f.smallImage=function(){return"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"},f.bind=function(e,n){return function(){return e.apply(n,arguments)}},f.decode64=function(e){var n,f,o,d,i,t,l,s,u="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=e.length,p="";for(n=0;a>n;n+=4)f=u.indexOf(e[n]),o=u.indexOf(e[n+1]),d=u.indexOf(e[n+2]),i=u.indexOf(e[n+3]),t=f<<2|o>>4,l=(15&o)<<4|d>>2,s=(3&d)<<6|i,p+=64===d?String.fromCharCode(t):64===i||-1===i?String.fromCharCode(t,l):String.fromCharCode(t,l,s);return p},f.getBounds=function(e){if(e.getBoundingClientRect){var n=e.getBoundingClientRect(),f=null==e.offsetWidth?n.width:e.offsetWidth;return{top:n.top,bottom:n.bottom||n.top+n.height,right:n.left+f,left:n.left,width:f,height:null==e.offsetHeight?n.height:e.offsetHeight}}return{}},f.offsetBounds=function(e){var n=e.offsetParent?f.offsetBounds(e.offsetParent):{top:0,left:0};return{top:e.offsetTop+n.top,bottom:e.offsetTop+e.offsetHeight+n.top,right:e.offsetLeft+n.left+e.offsetWidth,left:e.offsetLeft+n.left,width:e.offsetWidth,height:e.offsetHeight}},f.parseBackgrounds=function(e){var n,f,o,d,i,t,l,s=" \r\n ",u=[],a=0,p=0,c=function(){n&&('"'===f.substr(0,1)&&(f=f.substr(1,f.length-2)),f&&l.push(f),"-"===n.substr(0,1)&&(d=n.indexOf("-",1)+1)>0&&(o=n.substr(0,d),n=n.substr(d)),u.push({prefix:o,method:n.toLowerCase(),value:i,args:l,image:null})),l=[],n=o=f=i=""};return l=[],n=o=f=i="",e.split("").forEach(function(e){if(!(0===a&&s.indexOf(e)>-1)){switch(e){case'"':t?t===e&&(t=null):t=e;break;case"(":if(t)break;if(0===a)return a=1,void(i+=e);p++;break;case")":if(t)break;if(1===a){if(0===p)return a=0,i+=e,void c();p--}break;case",":if(t)break;if(0===a)return void c();if(1===a&&0===p&&!n.match(/^url$/i))return l.push(f),f="",void(i+=e)}i+=e,0===a?n+=e:f+=e}}),c(),u}},{}],27:[function(e,n){function f(e){o.apply(this,arguments),this.type="linear"===e.args[0]?o.TYPES.LINEAR:o.TYPES.RADIAL}var o=e("./gradientcontainer");f.prototype=Object.create(o.prototype),n.exports=f},{"./gradientcontainer":9}],28:[function(e,n){function f(e){return new Promise(function(n,f){var o=new XMLHttpRequest;o.open("GET",e),o.onload=function(){200===o.status?n(o.responseText):f(new Error(o.statusText))},o.onerror=function(){f(new Error("Network Error"))},o.send()})}n.exports=f},{}]},{},[4])(4)});
+/*
+ html2canvas 0.5.0-beta3
+ Copyright (c) 2016 Niklas von Hertzen
+
+ Released under License
+*/
+
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),(n.html2canvas||(n.html2canvas={})).svg=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
+ * @license MIT
+ */
+
+var base64 = _dereq_('base64-js')
+var ieee754 = _dereq_('ieee754')
+var isArray = _dereq_('is-array')
+
+exports.Buffer = Buffer
+exports.SlowBuffer = SlowBuffer
+exports.INSPECT_MAX_BYTES = 50
+Buffer.poolSize = 8192 // not used by this implementation
+
+var kMaxLength = 0x3fffffff
+var rootParent = {}
+
+/**
+ * If `Buffer.TYPED_ARRAY_SUPPORT`:
+ * === true Use Uint8Array implementation (fastest)
+ * === false Use Object implementation (most compatible, even IE6)
+ *
+ * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
+ * Opera 11.6+, iOS 4.2+.
+ *
+ * Note:
+ *
+ * - Implementation must support adding new properties to `Uint8Array` instances.
+ * Firefox 4-29 lacked support, fixed in Firefox 30+.
+ * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
+ *
+ * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
+ *
+ * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
+ * incorrect length in some situations.
+ *
+ * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they will
+ * get the Object implementation, which is slower but will work correctly.
+ */
+Buffer.TYPED_ARRAY_SUPPORT = (function () {
+ try {
+ var buf = new ArrayBuffer(0)
+ var arr = new Uint8Array(buf)
+ arr.foo = function () { return 42 }
+ return 42 === arr.foo() && // typed array instances can be augmented
+ typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
+ new Uint8Array(1).subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
+ } catch (e) {
+ return false
+ }
+})()
+
+/**
+ * Class: Buffer
+ * =============
+ *
+ * The Buffer constructor returns instances of `Uint8Array` that are augmented
+ * with function properties for all the node `Buffer` API functions. We use
+ * `Uint8Array` so that square bracket notation works as expected -- it returns
+ * a single octet.
+ *
+ * By augmenting the instances, we can avoid modifying the `Uint8Array`
+ * prototype.
+ */
+function Buffer (subject, encoding, noZero) {
+ if (!(this instanceof Buffer))
+ return new Buffer(subject, encoding, noZero)
+
+ var type = typeof subject
+
+ // Find the length
+ var length
+ if (type === 'number')
+ length = subject > 0 ? subject >>> 0 : 0
+ else if (type === 'string') {
+ length = Buffer.byteLength(subject, encoding)
+ } else if (type === 'object' && subject !== null) { // assume object is array-like
+ if (subject.type === 'Buffer' && isArray(subject.data))
+ subject = subject.data
+ length = +subject.length > 0 ? Math.floor(+subject.length) : 0
+ } else
+ throw new TypeError('must start with number, buffer, array or string')
+
+ if (length > kMaxLength)
+ throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
+ 'size: 0x' + kMaxLength.toString(16) + ' bytes')
+
+ var buf
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ // Preferred: Return an augmented `Uint8Array` instance for best performance
+ buf = Buffer._augment(new Uint8Array(length))
+ } else {
+ // Fallback: Return THIS instance of Buffer (created by `new`)
+ buf = this
+ buf.length = length
+ buf._isBuffer = true
+ }
+
+ var i
+ if (Buffer.TYPED_ARRAY_SUPPORT && typeof subject.byteLength === 'number') {
+ // Speed optimization -- use set if we're copying from a typed array
+ buf._set(subject)
+ } else if (isArrayish(subject)) {
+ // Treat array-ish objects as a byte array
+ if (Buffer.isBuffer(subject)) {
+ for (i = 0; i < length; i++)
+ buf[i] = subject.readUInt8(i)
+ } else {
+ for (i = 0; i < length; i++)
+ buf[i] = ((subject[i] % 256) + 256) % 256
+ }
+ } else if (type === 'string') {
+ buf.write(subject, 0, encoding)
+ } else if (type === 'number' && !Buffer.TYPED_ARRAY_SUPPORT && !noZero) {
+ for (i = 0; i < length; i++) {
+ buf[i] = 0
+ }
+ }
+
+ if (length > 0 && length <= Buffer.poolSize)
+ buf.parent = rootParent
+
+ return buf
+}
+
+function SlowBuffer(subject, encoding, noZero) {
+ if (!(this instanceof SlowBuffer))
+ return new SlowBuffer(subject, encoding, noZero)
+
+ var buf = new Buffer(subject, encoding, noZero)
+ delete buf.parent
+ return buf
+}
+
+Buffer.isBuffer = function (b) {
+ return !!(b != null && b._isBuffer)
+}
+
+Buffer.compare = function (a, b) {
+ if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b))
+ throw new TypeError('Arguments must be Buffers')
+
+ var x = a.length
+ var y = b.length
+ for (var i = 0, len = Math.min(x, y); i < len && a[i] === b[i]; i++) {}
+ if (i !== len) {
+ x = a[i]
+ y = b[i]
+ }
+ if (x < y) return -1
+ if (y < x) return 1
+ return 0
+}
+
+Buffer.isEncoding = function (encoding) {
+ switch (String(encoding).toLowerCase()) {
+ case 'hex':
+ case 'utf8':
+ case 'utf-8':
+ case 'ascii':
+ case 'binary':
+ case 'base64':
+ case 'raw':
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return true
+ default:
+ return false
+ }
+}
+
+Buffer.concat = function (list, totalLength) {
+ if (!isArray(list)) throw new TypeError('Usage: Buffer.concat(list[, length])')
+
+ if (list.length === 0) {
+ return new Buffer(0)
+ } else if (list.length === 1) {
+ return list[0]
+ }
+
+ var i
+ if (totalLength === undefined) {
+ totalLength = 0
+ for (i = 0; i < list.length; i++) {
+ totalLength += list[i].length
+ }
+ }
+
+ var buf = new Buffer(totalLength)
+ var pos = 0
+ for (i = 0; i < list.length; i++) {
+ var item = list[i]
+ item.copy(buf, pos)
+ pos += item.length
+ }
+ return buf
+}
+
+Buffer.byteLength = function (str, encoding) {
+ var ret
+ str = str + ''
+ switch (encoding || 'utf8') {
+ case 'ascii':
+ case 'binary':
+ case 'raw':
+ ret = str.length
+ break
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ ret = str.length * 2
+ break
+ case 'hex':
+ ret = str.length >>> 1
+ break
+ case 'utf8':
+ case 'utf-8':
+ ret = utf8ToBytes(str).length
+ break
+ case 'base64':
+ ret = base64ToBytes(str).length
+ break
+ default:
+ ret = str.length
+ }
+ return ret
+}
+
+// pre-set for values that may exist in the future
+Buffer.prototype.length = undefined
+Buffer.prototype.parent = undefined
+
+// toString(encoding, start=0, end=buffer.length)
+Buffer.prototype.toString = function (encoding, start, end) {
+ var loweredCase = false
+
+ start = start >>> 0
+ end = end === undefined || end === Infinity ? this.length : end >>> 0
+
+ if (!encoding) encoding = 'utf8'
+ if (start < 0) start = 0
+ if (end > this.length) end = this.length
+ if (end <= start) return ''
+
+ while (true) {
+ switch (encoding) {
+ case 'hex':
+ return hexSlice(this, start, end)
+
+ case 'utf8':
+ case 'utf-8':
+ return utf8Slice(this, start, end)
+
+ case 'ascii':
+ return asciiSlice(this, start, end)
+
+ case 'binary':
+ return binarySlice(this, start, end)
+
+ case 'base64':
+ return base64Slice(this, start, end)
+
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ return utf16leSlice(this, start, end)
+
+ default:
+ if (loweredCase)
+ throw new TypeError('Unknown encoding: ' + encoding)
+ encoding = (encoding + '').toLowerCase()
+ loweredCase = true
+ }
+ }
+}
+
+Buffer.prototype.equals = function (b) {
+ if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+ return Buffer.compare(this, b) === 0
+}
+
+Buffer.prototype.inspect = function () {
+ var str = ''
+ var max = exports.INSPECT_MAX_BYTES
+ if (this.length > 0) {
+ str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
+ if (this.length > max)
+ str += ' ... '
+ }
+ return ''
+}
+
+Buffer.prototype.compare = function (b) {
+ if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
+ return Buffer.compare(this, b)
+}
+
+// `get` will be removed in Node 0.13+
+Buffer.prototype.get = function (offset) {
+ console.log('.get() is deprecated. Access using array indexes instead.')
+ return this.readUInt8(offset)
+}
+
+// `set` will be removed in Node 0.13+
+Buffer.prototype.set = function (v, offset) {
+ console.log('.set() is deprecated. Access using array indexes instead.')
+ return this.writeUInt8(v, offset)
+}
+
+function hexWrite (buf, string, offset, length) {
+ offset = Number(offset) || 0
+ var remaining = buf.length - offset
+ if (!length) {
+ length = remaining
+ } else {
+ length = Number(length)
+ if (length > remaining) {
+ length = remaining
+ }
+ }
+
+ // must be an even number of digits
+ var strLen = string.length
+ if (strLen % 2 !== 0) throw new Error('Invalid hex string')
+
+ if (length > strLen / 2) {
+ length = strLen / 2
+ }
+ for (var i = 0; i < length; i++) {
+ var byte = parseInt(string.substr(i * 2, 2), 16)
+ if (isNaN(byte)) throw new Error('Invalid hex string')
+ buf[offset + i] = byte
+ }
+ return i
+}
+
+function utf8Write (buf, string, offset, length) {
+ var charsWritten = blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
+ return charsWritten
+}
+
+function asciiWrite (buf, string, offset, length) {
+ var charsWritten = blitBuffer(asciiToBytes(string), buf, offset, length)
+ return charsWritten
+}
+
+function binaryWrite (buf, string, offset, length) {
+ return asciiWrite(buf, string, offset, length)
+}
+
+function base64Write (buf, string, offset, length) {
+ var charsWritten = blitBuffer(base64ToBytes(string), buf, offset, length)
+ return charsWritten
+}
+
+function utf16leWrite (buf, string, offset, length) {
+ var charsWritten = blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length, 2)
+ return charsWritten
+}
+
+Buffer.prototype.write = function (string, offset, length, encoding) {
+ // Support both (string, offset, length, encoding)
+ // and the legacy (string, encoding, offset, length)
+ if (isFinite(offset)) {
+ if (!isFinite(length)) {
+ encoding = length
+ length = undefined
+ }
+ } else { // legacy
+ var swap = encoding
+ encoding = offset
+ offset = length
+ length = swap
+ }
+
+ offset = Number(offset) || 0
+
+ if (length < 0 || offset < 0 || offset > this.length)
+ throw new RangeError('attempt to write outside buffer bounds');
+
+ var remaining = this.length - offset
+ if (!length) {
+ length = remaining
+ } else {
+ length = Number(length)
+ if (length > remaining) {
+ length = remaining
+ }
+ }
+ encoding = String(encoding || 'utf8').toLowerCase()
+
+ var ret
+ switch (encoding) {
+ case 'hex':
+ ret = hexWrite(this, string, offset, length)
+ break
+ case 'utf8':
+ case 'utf-8':
+ ret = utf8Write(this, string, offset, length)
+ break
+ case 'ascii':
+ ret = asciiWrite(this, string, offset, length)
+ break
+ case 'binary':
+ ret = binaryWrite(this, string, offset, length)
+ break
+ case 'base64':
+ ret = base64Write(this, string, offset, length)
+ break
+ case 'ucs2':
+ case 'ucs-2':
+ case 'utf16le':
+ case 'utf-16le':
+ ret = utf16leWrite(this, string, offset, length)
+ break
+ default:
+ throw new TypeError('Unknown encoding: ' + encoding)
+ }
+ return ret
+}
+
+Buffer.prototype.toJSON = function () {
+ return {
+ type: 'Buffer',
+ data: Array.prototype.slice.call(this._arr || this, 0)
+ }
+}
+
+function base64Slice (buf, start, end) {
+ if (start === 0 && end === buf.length) {
+ return base64.fromByteArray(buf)
+ } else {
+ return base64.fromByteArray(buf.slice(start, end))
+ }
+}
+
+function utf8Slice (buf, start, end) {
+ var res = ''
+ var tmp = ''
+ end = Math.min(buf.length, end)
+
+ for (var i = start; i < end; i++) {
+ if (buf[i] <= 0x7F) {
+ res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
+ tmp = ''
+ } else {
+ tmp += '%' + buf[i].toString(16)
+ }
+ }
+
+ return res + decodeUtf8Char(tmp)
+}
+
+function asciiSlice (buf, start, end) {
+ var ret = ''
+ end = Math.min(buf.length, end)
+
+ for (var i = start; i < end; i++) {
+ ret += String.fromCharCode(buf[i] & 0x7F)
+ }
+ return ret
+}
+
+function binarySlice (buf, start, end) {
+ var ret = ''
+ end = Math.min(buf.length, end)
+
+ for (var i = start; i < end; i++) {
+ ret += String.fromCharCode(buf[i])
+ }
+ return ret
+}
+
+function hexSlice (buf, start, end) {
+ var len = buf.length
+
+ if (!start || start < 0) start = 0
+ if (!end || end < 0 || end > len) end = len
+
+ var out = ''
+ for (var i = start; i < end; i++) {
+ out += toHex(buf[i])
+ }
+ return out
+}
+
+function utf16leSlice (buf, start, end) {
+ var bytes = buf.slice(start, end)
+ var res = ''
+ for (var i = 0; i < bytes.length; i += 2) {
+ res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
+ }
+ return res
+}
+
+Buffer.prototype.slice = function (start, end) {
+ var len = this.length
+ start = ~~start
+ end = end === undefined ? len : ~~end
+
+ if (start < 0) {
+ start += len;
+ if (start < 0)
+ start = 0
+ } else if (start > len) {
+ start = len
+ }
+
+ if (end < 0) {
+ end += len
+ if (end < 0)
+ end = 0
+ } else if (end > len) {
+ end = len
+ }
+
+ if (end < start)
+ end = start
+
+ var newBuf
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ newBuf = Buffer._augment(this.subarray(start, end))
+ } else {
+ var sliceLen = end - start
+ newBuf = new Buffer(sliceLen, undefined, true)
+ for (var i = 0; i < sliceLen; i++) {
+ newBuf[i] = this[i + start]
+ }
+ }
+
+ if (newBuf.length)
+ newBuf.parent = this.parent || this
+
+ return newBuf
+}
+
+/*
+ * Need to make sure that buffer isn't trying to write out of bounds.
+ */
+function checkOffset (offset, ext, length) {
+ if ((offset % 1) !== 0 || offset < 0)
+ throw new RangeError('offset is not uint')
+ if (offset + ext > length)
+ throw new RangeError('Trying to access beyond buffer length')
+}
+
+Buffer.prototype.readUIntLE = function (offset, byteLength, noAssert) {
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkOffset(offset, byteLength, this.length)
+
+ var val = this[offset]
+ var mul = 1
+ var i = 0
+ while (++i < byteLength && (mul *= 0x100))
+ val += this[offset + i] * mul
+
+ return val
+}
+
+Buffer.prototype.readUIntBE = function (offset, byteLength, noAssert) {
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkOffset(offset, byteLength, this.length)
+
+ var val = this[offset + --byteLength]
+ var mul = 1
+ while (byteLength > 0 && (mul *= 0x100))
+ val += this[offset + --byteLength] * mul;
+
+ return val
+}
+
+Buffer.prototype.readUInt8 = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 1, this.length)
+ return this[offset]
+}
+
+Buffer.prototype.readUInt16LE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 2, this.length)
+ return this[offset] | (this[offset + 1] << 8)
+}
+
+Buffer.prototype.readUInt16BE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 2, this.length)
+ return (this[offset] << 8) | this[offset + 1]
+}
+
+Buffer.prototype.readUInt32LE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+
+ return ((this[offset]) |
+ (this[offset + 1] << 8) |
+ (this[offset + 2] << 16)) +
+ (this[offset + 3] * 0x1000000)
+}
+
+Buffer.prototype.readUInt32BE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+
+ return (this[offset] * 0x1000000) +
+ ((this[offset + 1] << 16) |
+ (this[offset + 2] << 8) |
+ this[offset + 3])
+}
+
+Buffer.prototype.readIntLE = function (offset, byteLength, noAssert) {
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkOffset(offset, byteLength, this.length)
+
+ var val = this[offset]
+ var mul = 1
+ var i = 0
+ while (++i < byteLength && (mul *= 0x100))
+ val += this[offset + i] * mul
+ mul *= 0x80
+
+ if (val >= mul)
+ val -= Math.pow(2, 8 * byteLength)
+
+ return val
+}
+
+Buffer.prototype.readIntBE = function (offset, byteLength, noAssert) {
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkOffset(offset, byteLength, this.length)
+
+ var i = byteLength
+ var mul = 1
+ var val = this[offset + --i]
+ while (i > 0 && (mul *= 0x100))
+ val += this[offset + --i] * mul
+ mul *= 0x80
+
+ if (val >= mul)
+ val -= Math.pow(2, 8 * byteLength)
+
+ return val
+}
+
+Buffer.prototype.readInt8 = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 1, this.length)
+ if (!(this[offset] & 0x80))
+ return (this[offset])
+ return ((0xff - this[offset] + 1) * -1)
+}
+
+Buffer.prototype.readInt16LE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 2, this.length)
+ var val = this[offset] | (this[offset + 1] << 8)
+ return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt16BE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 2, this.length)
+ var val = this[offset + 1] | (this[offset] << 8)
+ return (val & 0x8000) ? val | 0xFFFF0000 : val
+}
+
+Buffer.prototype.readInt32LE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+
+ return (this[offset]) |
+ (this[offset + 1] << 8) |
+ (this[offset + 2] << 16) |
+ (this[offset + 3] << 24)
+}
+
+Buffer.prototype.readInt32BE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+
+ return (this[offset] << 24) |
+ (this[offset + 1] << 16) |
+ (this[offset + 2] << 8) |
+ (this[offset + 3])
+}
+
+Buffer.prototype.readFloatLE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+ return ieee754.read(this, offset, true, 23, 4)
+}
+
+Buffer.prototype.readFloatBE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 4, this.length)
+ return ieee754.read(this, offset, false, 23, 4)
+}
+
+Buffer.prototype.readDoubleLE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 8, this.length)
+ return ieee754.read(this, offset, true, 52, 8)
+}
+
+Buffer.prototype.readDoubleBE = function (offset, noAssert) {
+ if (!noAssert)
+ checkOffset(offset, 8, this.length)
+ return ieee754.read(this, offset, false, 52, 8)
+}
+
+function checkInt (buf, value, offset, ext, max, min) {
+ if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')
+ if (value > max || value < min) throw new RangeError('value is out of bounds')
+ if (offset + ext > buf.length) throw new RangeError('index out of range')
+}
+
+Buffer.prototype.writeUIntLE = function (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+
+ var mul = 1
+ var i = 0
+ this[offset] = value & 0xFF
+ while (++i < byteLength && (mul *= 0x100))
+ this[offset + i] = (value / mul) >>> 0 & 0xFF
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeUIntBE = function (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ byteLength = byteLength >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+
+ var i = byteLength - 1
+ var mul = 1
+ this[offset + i] = value & 0xFF
+ while (--i >= 0 && (mul *= 0x100))
+ this[offset + i] = (value / mul) >>> 0 & 0xFF
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeUInt8 = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 1, 0xff, 0)
+ if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+ this[offset] = value
+ return offset + 1
+}
+
+function objectWriteUInt16 (buf, value, offset, littleEndian) {
+ if (value < 0) value = 0xffff + value + 1
+ for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
+ buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
+ (littleEndian ? i : 1 - i) * 8
+ }
+}
+
+Buffer.prototype.writeUInt16LE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 2, 0xffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = value
+ this[offset + 1] = (value >>> 8)
+ } else objectWriteUInt16(this, value, offset, true)
+ return offset + 2
+}
+
+Buffer.prototype.writeUInt16BE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 2, 0xffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 8)
+ this[offset + 1] = value
+ } else objectWriteUInt16(this, value, offset, false)
+ return offset + 2
+}
+
+function objectWriteUInt32 (buf, value, offset, littleEndian) {
+ if (value < 0) value = 0xffffffff + value + 1
+ for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {
+ buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
+ }
+}
+
+Buffer.prototype.writeUInt32LE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 4, 0xffffffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset + 3] = (value >>> 24)
+ this[offset + 2] = (value >>> 16)
+ this[offset + 1] = (value >>> 8)
+ this[offset] = value
+ } else objectWriteUInt32(this, value, offset, true)
+ return offset + 4
+}
+
+Buffer.prototype.writeUInt32BE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 4, 0xffffffff, 0)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 24)
+ this[offset + 1] = (value >>> 16)
+ this[offset + 2] = (value >>> 8)
+ this[offset + 3] = value
+ } else objectWriteUInt32(this, value, offset, false)
+ return offset + 4
+}
+
+Buffer.prototype.writeIntLE = function (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert) {
+ checkInt(this,
+ value,
+ offset,
+ byteLength,
+ Math.pow(2, 8 * byteLength - 1) - 1,
+ -Math.pow(2, 8 * byteLength - 1))
+ }
+
+ var i = 0
+ var mul = 1
+ var sub = value < 0 ? 1 : 0
+ this[offset] = value & 0xFF
+ while (++i < byteLength && (mul *= 0x100))
+ this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeIntBE = function (value, offset, byteLength, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert) {
+ checkInt(this,
+ value,
+ offset,
+ byteLength,
+ Math.pow(2, 8 * byteLength - 1) - 1,
+ -Math.pow(2, 8 * byteLength - 1))
+ }
+
+ var i = byteLength - 1
+ var mul = 1
+ var sub = value < 0 ? 1 : 0
+ this[offset + i] = value & 0xFF
+ while (--i >= 0 && (mul *= 0x100))
+ this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
+
+ return offset + byteLength
+}
+
+Buffer.prototype.writeInt8 = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 1, 0x7f, -0x80)
+ if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
+ if (value < 0) value = 0xff + value + 1
+ this[offset] = value
+ return offset + 1
+}
+
+Buffer.prototype.writeInt16LE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = value
+ this[offset + 1] = (value >>> 8)
+ } else objectWriteUInt16(this, value, offset, true)
+ return offset + 2
+}
+
+Buffer.prototype.writeInt16BE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 2, 0x7fff, -0x8000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 8)
+ this[offset + 1] = value
+ } else objectWriteUInt16(this, value, offset, false)
+ return offset + 2
+}
+
+Buffer.prototype.writeInt32LE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = value
+ this[offset + 1] = (value >>> 8)
+ this[offset + 2] = (value >>> 16)
+ this[offset + 3] = (value >>> 24)
+ } else objectWriteUInt32(this, value, offset, true)
+ return offset + 4
+}
+
+Buffer.prototype.writeInt32BE = function (value, offset, noAssert) {
+ value = +value
+ offset = offset >>> 0
+ if (!noAssert)
+ checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
+ if (value < 0) value = 0xffffffff + value + 1
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ this[offset] = (value >>> 24)
+ this[offset + 1] = (value >>> 16)
+ this[offset + 2] = (value >>> 8)
+ this[offset + 3] = value
+ } else objectWriteUInt32(this, value, offset, false)
+ return offset + 4
+}
+
+function checkIEEE754 (buf, value, offset, ext, max, min) {
+ if (value > max || value < min) throw new RangeError('value is out of bounds')
+ if (offset + ext > buf.length) throw new RangeError('index out of range')
+ if (offset < 0) throw new RangeError('index out of range')
+}
+
+function writeFloat (buf, value, offset, littleEndian, noAssert) {
+ if (!noAssert)
+ checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
+ ieee754.write(buf, value, offset, littleEndian, 23, 4)
+ return offset + 4
+}
+
+Buffer.prototype.writeFloatLE = function (value, offset, noAssert) {
+ return writeFloat(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeFloatBE = function (value, offset, noAssert) {
+ return writeFloat(this, value, offset, false, noAssert)
+}
+
+function writeDouble (buf, value, offset, littleEndian, noAssert) {
+ if (!noAssert)
+ checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
+ ieee754.write(buf, value, offset, littleEndian, 52, 8)
+ return offset + 8
+}
+
+Buffer.prototype.writeDoubleLE = function (value, offset, noAssert) {
+ return writeDouble(this, value, offset, true, noAssert)
+}
+
+Buffer.prototype.writeDoubleBE = function (value, offset, noAssert) {
+ return writeDouble(this, value, offset, false, noAssert)
+}
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function (target, target_start, start, end) {
+ var source = this
+
+ if (!start) start = 0
+ if (!end && end !== 0) end = this.length
+ if (target_start >= target.length) target_start = target.length
+ if (!target_start) target_start = 0
+ if (end > 0 && end < start) end = start
+
+ // Copy 0 bytes; we're done
+ if (end === start) return 0
+ if (target.length === 0 || source.length === 0) return 0
+
+ // Fatal error conditions
+ if (target_start < 0)
+ throw new RangeError('targetStart out of bounds')
+ if (start < 0 || start >= source.length) throw new RangeError('sourceStart out of bounds')
+ if (end < 0) throw new RangeError('sourceEnd out of bounds')
+
+ // Are we oob?
+ if (end > this.length)
+ end = this.length
+ if (target.length - target_start < end - start)
+ end = target.length - target_start + start
+
+ var len = end - start
+
+ if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
+ for (var i = 0; i < len; i++) {
+ target[i + target_start] = this[i + start]
+ }
+ } else {
+ target._set(this.subarray(start, start + len), target_start)
+ }
+
+ return len
+}
+
+// fill(value, start=0, end=buffer.length)
+Buffer.prototype.fill = function (value, start, end) {
+ if (!value) value = 0
+ if (!start) start = 0
+ if (!end) end = this.length
+
+ if (end < start) throw new RangeError('end < start')
+
+ // Fill 0 bytes; we're done
+ if (end === start) return
+ if (this.length === 0) return
+
+ if (start < 0 || start >= this.length) throw new RangeError('start out of bounds')
+ if (end < 0 || end > this.length) throw new RangeError('end out of bounds')
+
+ var i
+ if (typeof value === 'number') {
+ for (i = start; i < end; i++) {
+ this[i] = value
+ }
+ } else {
+ var bytes = utf8ToBytes(value.toString())
+ var len = bytes.length
+ for (i = start; i < end; i++) {
+ this[i] = bytes[i % len]
+ }
+ }
+
+ return this
+}
+
+/**
+ * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance.
+ * Added in Node 0.12. Only available in browsers that support ArrayBuffer.
+ */
+Buffer.prototype.toArrayBuffer = function () {
+ if (typeof Uint8Array !== 'undefined') {
+ if (Buffer.TYPED_ARRAY_SUPPORT) {
+ return (new Buffer(this)).buffer
+ } else {
+ var buf = new Uint8Array(this.length)
+ for (var i = 0, len = buf.length; i < len; i += 1) {
+ buf[i] = this[i]
+ }
+ return buf.buffer
+ }
+ } else {
+ throw new TypeError('Buffer.toArrayBuffer not supported in this browser')
+ }
+}
+
+// HELPER FUNCTIONS
+// ================
+
+var BP = Buffer.prototype
+
+/**
+ * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods
+ */
+Buffer._augment = function (arr) {
+ arr.constructor = Buffer
+ arr._isBuffer = true
+
+ // save reference to original Uint8Array get/set methods before overwriting
+ arr._get = arr.get
+ arr._set = arr.set
+
+ // deprecated, will be removed in node 0.13+
+ arr.get = BP.get
+ arr.set = BP.set
+
+ arr.write = BP.write
+ arr.toString = BP.toString
+ arr.toLocaleString = BP.toString
+ arr.toJSON = BP.toJSON
+ arr.equals = BP.equals
+ arr.compare = BP.compare
+ arr.copy = BP.copy
+ arr.slice = BP.slice
+ arr.readUIntLE = BP.readUIntLE
+ arr.readUIntBE = BP.readUIntBE
+ arr.readUInt8 = BP.readUInt8
+ arr.readUInt16LE = BP.readUInt16LE
+ arr.readUInt16BE = BP.readUInt16BE
+ arr.readUInt32LE = BP.readUInt32LE
+ arr.readUInt32BE = BP.readUInt32BE
+ arr.readIntLE = BP.readIntLE
+ arr.readIntBE = BP.readIntBE
+ arr.readInt8 = BP.readInt8
+ arr.readInt16LE = BP.readInt16LE
+ arr.readInt16BE = BP.readInt16BE
+ arr.readInt32LE = BP.readInt32LE
+ arr.readInt32BE = BP.readInt32BE
+ arr.readFloatLE = BP.readFloatLE
+ arr.readFloatBE = BP.readFloatBE
+ arr.readDoubleLE = BP.readDoubleLE
+ arr.readDoubleBE = BP.readDoubleBE
+ arr.writeUInt8 = BP.writeUInt8
+ arr.writeUIntLE = BP.writeUIntLE
+ arr.writeUIntBE = BP.writeUIntBE
+ arr.writeUInt16LE = BP.writeUInt16LE
+ arr.writeUInt16BE = BP.writeUInt16BE
+ arr.writeUInt32LE = BP.writeUInt32LE
+ arr.writeUInt32BE = BP.writeUInt32BE
+ arr.writeIntLE = BP.writeIntLE
+ arr.writeIntBE = BP.writeIntBE
+ arr.writeInt8 = BP.writeInt8
+ arr.writeInt16LE = BP.writeInt16LE
+ arr.writeInt16BE = BP.writeInt16BE
+ arr.writeInt32LE = BP.writeInt32LE
+ arr.writeInt32BE = BP.writeInt32BE
+ arr.writeFloatLE = BP.writeFloatLE
+ arr.writeFloatBE = BP.writeFloatBE
+ arr.writeDoubleLE = BP.writeDoubleLE
+ arr.writeDoubleBE = BP.writeDoubleBE
+ arr.fill = BP.fill
+ arr.inspect = BP.inspect
+ arr.toArrayBuffer = BP.toArrayBuffer
+
+ return arr
+}
+
+var INVALID_BASE64_RE = /[^+\/0-9A-z\-]/g
+
+function base64clean (str) {
+ // Node strips out invalid characters like \n and \t from the string, base64-js does not
+ str = stringtrim(str).replace(INVALID_BASE64_RE, '')
+ // Node converts strings with length < 2 to ''
+ if (str.length < 2) return ''
+ // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
+ while (str.length % 4 !== 0) {
+ str = str + '='
+ }
+ return str
+}
+
+function stringtrim (str) {
+ if (str.trim) return str.trim()
+ return str.replace(/^\s+|\s+$/g, '')
+}
+
+function isArrayish (subject) {
+ return isArray(subject) || Buffer.isBuffer(subject) ||
+ subject && typeof subject === 'object' &&
+ typeof subject.length === 'number'
+}
+
+function toHex (n) {
+ if (n < 16) return '0' + n.toString(16)
+ return n.toString(16)
+}
+
+function utf8ToBytes(string, units) {
+ var codePoint, length = string.length
+ var leadSurrogate = null
+ units = units || Infinity
+ var bytes = []
+ var i = 0
+
+ for (; i 0xD7FF && codePoint < 0xE000) {
+
+ // last char was a lead
+ if (leadSurrogate) {
+
+ // 2 leads in a row
+ if (codePoint < 0xDC00) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ leadSurrogate = codePoint
+ continue
+ }
+
+ // valid surrogate pair
+ else {
+ codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000
+ leadSurrogate = null
+ }
+ }
+
+ // no lead yet
+ else {
+
+ // unexpected trail
+ if (codePoint > 0xDBFF) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ }
+
+ // unpaired lead
+ else if (i + 1 === length) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ }
+
+ // valid lead
+ else {
+ leadSurrogate = codePoint
+ continue
+ }
+ }
+ }
+
+ // valid bmp char, but last char was a lead
+ else if (leadSurrogate) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ leadSurrogate = null
+ }
+
+ // encode utf8
+ if (codePoint < 0x80) {
+ if ((units -= 1) < 0) break
+ bytes.push(codePoint)
+ }
+ else if (codePoint < 0x800) {
+ if ((units -= 2) < 0) break
+ bytes.push(
+ codePoint >> 0x6 | 0xC0,
+ codePoint & 0x3F | 0x80
+ );
+ }
+ else if (codePoint < 0x10000) {
+ if ((units -= 3) < 0) break
+ bytes.push(
+ codePoint >> 0xC | 0xE0,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ );
+ }
+ else if (codePoint < 0x200000) {
+ if ((units -= 4) < 0) break
+ bytes.push(
+ codePoint >> 0x12 | 0xF0,
+ codePoint >> 0xC & 0x3F | 0x80,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ );
+ }
+ else {
+ throw new Error('Invalid code point')
+ }
+ }
+
+ return bytes
+}
+
+function asciiToBytes (str) {
+ var byteArray = []
+ for (var i = 0; i < str.length; i++) {
+ // Node's code seems to be doing this and not & 0x7F..
+ byteArray.push(str.charCodeAt(i) & 0xFF)
+ }
+ return byteArray
+}
+
+function utf16leToBytes (str, units) {
+ var c, hi, lo
+ var byteArray = []
+ for (var i = 0; i < str.length; i++) {
+
+ if ((units -= 2) < 0) break
+
+ c = str.charCodeAt(i)
+ hi = c >> 8
+ lo = c % 256
+ byteArray.push(lo)
+ byteArray.push(hi)
+ }
+
+ return byteArray
+}
+
+function base64ToBytes (str) {
+ return base64.toByteArray(base64clean(str))
+}
+
+function blitBuffer (src, dst, offset, length, unitSize) {
+ if (unitSize) length -= length % unitSize;
+ for (var i = 0; i < length; i++) {
+ if ((i + offset >= dst.length) || (i >= src.length))
+ break
+ dst[i + offset] = src[i]
+ }
+ return i
+}
+
+function decodeUtf8Char (str) {
+ try {
+ return decodeURIComponent(str)
+ } catch (err) {
+ return String.fromCharCode(0xFFFD) // UTF 8 invalid char
+ }
+}
+
+},{"base64-js":3,"ieee754":4,"is-array":5}],3:[function(_dereq_,module,exports){
+var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+;(function (exports) {
+ 'use strict';
+
+ var Arr = (typeof Uint8Array !== 'undefined')
+ ? Uint8Array
+ : Array
+
+ var PLUS = '+'.charCodeAt(0)
+ var SLASH = '/'.charCodeAt(0)
+ var NUMBER = '0'.charCodeAt(0)
+ var LOWER = 'a'.charCodeAt(0)
+ var UPPER = 'A'.charCodeAt(0)
+ var PLUS_URL_SAFE = '-'.charCodeAt(0)
+ var SLASH_URL_SAFE = '_'.charCodeAt(0)
+
+ function decode (elt) {
+ var code = elt.charCodeAt(0)
+ if (code === PLUS ||
+ code === PLUS_URL_SAFE)
+ return 62 // '+'
+ if (code === SLASH ||
+ code === SLASH_URL_SAFE)
+ return 63 // '/'
+ if (code < NUMBER)
+ return -1 //no match
+ if (code < NUMBER + 10)
+ return code - NUMBER + 26 + 26
+ if (code < UPPER + 26)
+ return code - UPPER
+ if (code < LOWER + 26)
+ return code - LOWER + 26
+ }
+
+ function b64ToByteArray (b64) {
+ var i, j, l, tmp, placeHolders, arr
+
+ if (b64.length % 4 > 0) {
+ throw new Error('Invalid string. Length must be a multiple of 4')
+ }
+
+ // the number of equal signs (place holders)
+ // if there are two placeholders, than the two characters before it
+ // represent one byte
+ // if there is only one, then the three characters before it represent 2 bytes
+ // this is just a cheap hack to not do indexOf twice
+ var len = b64.length
+ placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0
+
+ // base64 is 4/3 + up to two characters of the original data
+ arr = new Arr(b64.length * 3 / 4 - placeHolders)
+
+ // if there are placeholders, only get up to the last complete 4 chars
+ l = placeHolders > 0 ? b64.length - 4 : b64.length
+
+ var L = 0
+
+ function push (v) {
+ arr[L++] = v
+ }
+
+ for (i = 0, j = 0; i < l; i += 4, j += 3) {
+ tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
+ push((tmp & 0xFF0000) >> 16)
+ push((tmp & 0xFF00) >> 8)
+ push(tmp & 0xFF)
+ }
+
+ if (placeHolders === 2) {
+ tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
+ push(tmp & 0xFF)
+ } else if (placeHolders === 1) {
+ tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
+ push((tmp >> 8) & 0xFF)
+ push(tmp & 0xFF)
+ }
+
+ return arr
+ }
+
+ function uint8ToBase64 (uint8) {
+ var i,
+ extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
+ output = "",
+ temp, length
+
+ function encode (num) {
+ return lookup.charAt(num)
+ }
+
+ function tripletToBase64 (num) {
+ return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
+ }
+
+ // go through the array every three bytes, we'll deal with trailing stuff later
+ for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
+ temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
+ output += tripletToBase64(temp)
+ }
+
+ // pad the end with zeros, but make sure to not forget the extra bytes
+ switch (extraBytes) {
+ case 1:
+ temp = uint8[uint8.length - 1]
+ output += encode(temp >> 2)
+ output += encode((temp << 4) & 0x3F)
+ output += '=='
+ break
+ case 2:
+ temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
+ output += encode(temp >> 10)
+ output += encode((temp >> 4) & 0x3F)
+ output += encode((temp << 2) & 0x3F)
+ output += '='
+ break
+ }
+
+ return output
+ }
+
+ exports.toByteArray = b64ToByteArray
+ exports.fromByteArray = uint8ToBase64
+}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
+
+},{}],4:[function(_dereq_,module,exports){
+exports.read = function(buffer, offset, isLE, mLen, nBytes) {
+ var e, m,
+ eLen = nBytes * 8 - mLen - 1,
+ eMax = (1 << eLen) - 1,
+ eBias = eMax >> 1,
+ nBits = -7,
+ i = isLE ? (nBytes - 1) : 0,
+ d = isLE ? -1 : 1,
+ s = buffer[offset + i];
+
+ i += d;
+
+ e = s & ((1 << (-nBits)) - 1);
+ s >>= (-nBits);
+ nBits += eLen;
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8);
+
+ m = e & ((1 << (-nBits)) - 1);
+ e >>= (-nBits);
+ nBits += mLen;
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8);
+
+ if (e === 0) {
+ e = 1 - eBias;
+ } else if (e === eMax) {
+ return m ? NaN : ((s ? -1 : 1) * Infinity);
+ } else {
+ m = m + Math.pow(2, mLen);
+ e = e - eBias;
+ }
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+};
+
+exports.write = function(buffer, value, offset, isLE, mLen, nBytes) {
+ var e, m, c,
+ eLen = nBytes * 8 - mLen - 1,
+ eMax = (1 << eLen) - 1,
+ eBias = eMax >> 1,
+ rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0),
+ i = isLE ? 0 : (nBytes - 1),
+ d = isLE ? 1 : -1,
+ s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
+
+ value = Math.abs(value);
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0;
+ e = eMax;
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2);
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--;
+ c *= 2;
+ }
+ if (e + eBias >= 1) {
+ value += rt / c;
+ } else {
+ value += rt * Math.pow(2, 1 - eBias);
+ }
+ if (value * c >= 2) {
+ e++;
+ c /= 2;
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0;
+ e = eMax;
+ } else if (e + eBias >= 1) {
+ m = (value * c - 1) * Math.pow(2, mLen);
+ e = e + eBias;
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+ e = 0;
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8);
+
+ e = (e << mLen) | m;
+ eLen += mLen;
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8);
+
+ buffer[offset + i - d] |= s * 128;
+};
+
+},{}],5:[function(_dereq_,module,exports){
+
+/**
+ * isArray
+ */
+
+var isArray = Array.isArray;
+
+/**
+ * toString
+ */
+
+var str = Object.prototype.toString;
+
+/**
+ * Whether or not the given `val`
+ * is an array.
+ *
+ * example:
+ *
+ * isArray([]);
+ * // > true
+ * isArray(arguments);
+ * // > false
+ * isArray('');
+ * // > false
+ *
+ * @param {mixed} val
+ * @return {bool}
+ */
+
+module.exports = isArray || function (val) {
+ return !! val && '[object Array]' == str.call(val);
+};
+
+},{}],6:[function(_dereq_,module,exports){
+(function (Buffer){
+/* build: `node build.js modules=text,serialization,parser,gradient,pattern,shadow,freedrawing,image_filters,serialization no-es5-compat minifier=uglifyjs` */
+/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */
+
+var fabric = fabric || { version: "1.4.11" };
+if (typeof exports !== 'undefined') {
+ exports.fabric = fabric;
+}
+
+if (typeof document !== 'undefined' && typeof window !== 'undefined') {
+ fabric.document = document;
+ fabric.window = window;
+}
+else {
+ // assume we're running under node.js when document/window are not present
+ fabric.document = _dereq_("jsdom")
+ .jsdom("");
+
+ fabric.window = fabric.document.createWindow();
+}
+
+/**
+ * True when in environment that supports touch events
+ * @type boolean
+ */
+fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
+
+/**
+ * True when in environment that's probably Node.js
+ * @type boolean
+ */
+fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
+ typeof window === 'undefined';
+
+
+/**
+ * Attributes parsed from all SVG elements
+ * @type array
+ */
+fabric.SHARED_ATTRIBUTES = [
+ "display",
+ "transform",
+ "fill", "fill-opacity", "fill-rule",
+ "opacity",
+ "stroke", "stroke-dasharray", "stroke-linecap",
+ "stroke-linejoin", "stroke-miterlimit",
+ "stroke-opacity", "stroke-width"
+];
+
+/**
+ * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
+ */
+fabric.DPI = 96;
+
+
+/*!
+ * Copyright (c) 2009 Simo Kinnunen.
+ * Licensed under the MIT license.
+ */
+
+var Cufon = (function() {
+
+ /** @ignore */
+ var api = function() {
+ return api.replace.apply(null, arguments);
+ };
+
+ /** @ignore */
+ var DOM = api.DOM = {
+
+ ready: (function() {
+
+ var complete = false, readyStatus = { loaded: 1, complete: 1 };
+
+ var queue = [], /** @ignore */ perform = function() {
+ if (complete) return;
+ complete = true;
+ for (var fn; fn = queue.shift(); fn());
+ };
+
+ // Gecko, Opera, WebKit r26101+
+
+ if (fabric.document.addEventListener) {
+ fabric.document.addEventListener('DOMContentLoaded', perform, false);
+ fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages
+ }
+
+ // Old WebKit, Internet Explorer
+
+ if (!fabric.window.opera && fabric.document.readyState) (function() {
+ readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10);
+ })();
+
+ // Internet Explorer
+
+ if (fabric.document.readyState && fabric.document.createStyleSheet) (function() {
+ try {
+ fabric.document.body.doScroll('left');
+ perform();
+ }
+ catch (e) {
+ setTimeout(arguments.callee, 1);
+ }
+ })();
+
+ addEvent(fabric.window, 'load', perform); // Fallback
+
+ return function(listener) {
+ if (!arguments.length) perform();
+ else complete ? listener() : queue.push(listener);
+ };
+
+ })()
+
+ };
+
+ /** @ignore */
+ var CSS = api.CSS = /** @ignore */ {
+
+ /** @ignore */
+ Size: function(value, base) {
+
+ this.value = parseFloat(value);
+ this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
+
+ /** @ignore */
+ this.convert = function(value) {
+ return value / base * this.value;
+ };
+
+ /** @ignore */
+ this.convertFrom = function(value) {
+ return value / this.value * base;
+ };
+
+ /** @ignore */
+ this.toString = function() {
+ return this.value + this.unit;
+ };
+
+ },
+
+ /** @ignore */
+ getStyle: function(el) {
+ return new Style(el.style);
+ /*
+ var view = document.defaultView;
+ if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
+ if (el.currentStyle) return new Style(el.currentStyle);
+ return new Style(el.style);
+ */
+ },
+
+ quotedList: cached(function(value) {
+ // doesn't work properly with empty quoted strings (""), but
+ // it's not worth the extra code.
+ var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
+ while (match = re.exec(value)) list.push(match[3] || match[1]);
+ return list;
+ }),
+
+ ready: (function() {
+
+ var complete = false;
+
+ var queue = [], perform = function() {
+ complete = true;
+ for (var fn; fn = queue.shift(); fn());
+ };
+
+ // Safari 2 does not include ');
+
+ function getFontSizeInPixels(el, value) {
+ return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value);
+ }
+
+ // Original by Dead Edwards.
+ // Combined with getFontSizeInPixels it also works with relative units.
+ function getSizeInPixels(el, value) {
+ if (/px$/i.test(value)) return parseFloat(value);
+ var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
+ el.runtimeStyle.left = el.currentStyle.left;
+ el.style.left = value;
+ var result = el.style.pixelLeft;
+ el.style.left = style;
+ el.runtimeStyle.left = runtimeStyle;
+ return result;
+ }
+
+ return function(font, text, style, options, node, el, hasNext) {
+ var redraw = (text === null);
+
+ if (redraw) text = node.alt;
+
+ // @todo word-spacing, text-decoration
+
+ var viewBox = font.viewBox;
+
+ var size = style.computedFontSize ||
+ (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
+
+ var letterSpacing = style.computedLSpacing;
+
+ if (letterSpacing == undefined) {
+ letterSpacing = style.get('letterSpacing');
+ style.computedLSpacing = letterSpacing =
+ (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing));
+ }
+
+ var wrapper, canvas;
+
+ if (redraw) {
+ wrapper = node;
+ canvas = node.firstChild;
+ }
+ else {
+ wrapper = fabric.document.createElement('span');
+ wrapper.className = 'cufon cufon-vml';
+ wrapper.alt = text;
+
+ canvas = fabric.document.createElement('span');
+ canvas.className = 'cufon-vml-canvas';
+ wrapper.appendChild(canvas);
+
+ if (options.printable) {
+ var print = fabric.document.createElement('span');
+ print.className = 'cufon-alt';
+ print.appendChild(fabric.document.createTextNode(text));
+ wrapper.appendChild(print);
+ }
+
+ // ie6, for some reason, has trouble rendering the last VML element in the document.
+ // we can work around this by injecting a dummy element where needed.
+ // @todo find a better solution
+ if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape'));
+ }
+
+ var wStyle = wrapper.style;
+ var cStyle = canvas.style;
+
+ var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
+ var roundingFactor = roundedHeight / height;
+ var minX = viewBox.minX, minY = viewBox.minY;
+
+ cStyle.height = roundedHeight;
+ cStyle.top = Math.round(size.convert(minY - font.ascent));
+ cStyle.left = Math.round(size.convert(minX));
+
+ wStyle.height = size.convert(font.height) + 'px';
+
+ var textDecoration = Cufon.getTextDecoration(options);
+
+ var color = style.get('color');
+
+ var chars = Cufon.CSS.textTransform(text, style).split('');
+
+ var width = 0, offsetX = 0, advance = null;
+
+ var glyph, shape, shadows = options.textShadow;
+
+ // pre-calculate width
+ for (var i = 0, k = 0, l = chars.length; i < l; ++i) {
+ glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing;
+ }
+
+ if (advance === null) return null;
+
+ var fullWidth = -minX + width + (viewBox.width - advance);
+
+ var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth);
+
+ var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
+ var stretch = 'r' + coordSize + 'nsnf';
+
+ for (i = 0; i < l; ++i) {
+
+ glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+
+ if (redraw) {
+ // some glyphs may be missing so we can't use i
+ shape = canvas.childNodes[k];
+ if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow
+ }
+ else {
+ shape = fabric.document.createElement('cvml:shape');
+ canvas.appendChild(shape);
+ }
+
+ shape.stroked = 'f';
+ shape.coordsize = coordSize;
+ shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
+ shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
+ shape.fillcolor = color;
+
+ // it's important to not set top/left or IE8 will grind to a halt
+ var sStyle = shape.style;
+ sStyle.width = roundedShapeWidth;
+ sStyle.height = roundedHeight;
+
+ if (shadows) {
+ // due to the limitations of the VML shadow element there
+ // can only be two visible shadows. opacity is shared
+ // for all shadows.
+ var shadow1 = shadows[0], shadow2 = shadows[1];
+ var color1 = Cufon.CSS.color(shadow1.color), color2;
+ var shadow = fabric.document.createElement('cvml:shadow');
+ shadow.on = 't';
+ shadow.color = color1.color;
+ shadow.offset = shadow1.offX + ',' + shadow1.offY;
+ if (shadow2) {
+ color2 = Cufon.CSS.color(shadow2.color);
+ shadow.type = 'double';
+ shadow.color2 = color2.color;
+ shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
+ }
+ shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
+ shape.appendChild(shadow);
+ }
+
+ offsetX += ~~(glyph.w || font.w) + letterSpacing;
+
+ ++k;
+
+ }
+
+ wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0);
+
+ return wrapper;
+
+ };
+
+})());
+
+Cufon.getTextDecoration = function(options) {
+ return {
+ underline: options.textDecoration === 'underline',
+ overline: options.textDecoration === 'overline',
+ 'line-through': options.textDecoration === 'line-through'
+ };
+};
+
+if (typeof exports != 'undefined') {
+ exports.Cufon = Cufon;
+}
+
+
+/*
+ json2.js
+ 2014-02-04
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== 'object') {
+ JSON = {};
+}
+
+(function () {
+ 'use strict';
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function () {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z'
+ : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function () {
+ return this.valueOf();
+ };
+ }
+
+ var cx,
+ escapable,
+ gap,
+ indent,
+ meta,
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string'
+ ? c
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0
+ ? '[]'
+ : gap
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+ : '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === 'string') {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0
+ ? '{}'
+ : gap
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+ : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function'
+ ? walk({'': j}, '')
+ : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
+
+
+(function(){
+
+ /**
+ * @private
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ function _removeEventListener(eventName, handler) {
+ if (!this.__eventListeners[eventName]) {
+ return;
+ }
+
+ if (handler) {
+ fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
+ }
+ else {
+ this.__eventListeners[eventName].length = 0;
+ }
+ }
+
+ /**
+ * Observes specified event
+ * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
+ * @memberOf fabric.Observable
+ * @alias on
+ * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
+ * @param {Function} handler Function that receives a notification when an event of the specified type occurs
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function observe(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = { };
+ }
+ // one object with key/value pairs was passed
+ if (arguments.length === 1) {
+ for (var prop in eventName) {
+ this.on(prop, eventName[prop]);
+ }
+ }
+ else {
+ if (!this.__eventListeners[eventName]) {
+ this.__eventListeners[eventName] = [ ];
+ }
+ this.__eventListeners[eventName].push(handler);
+ }
+ return this;
+ }
+
+ /**
+ * Stops event observing for a particular event handler. Calling this method
+ * without arguments removes all handlers for all events
+ * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
+ * @memberOf fabric.Observable
+ * @alias off
+ * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
+ * @param {Function} handler Function to be deleted from EventListeners
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function stopObserving(eventName, handler) {
+ if (!this.__eventListeners) {
+ return;
+ }
+
+ // remove all key/value pairs (event name -> event handler)
+ if (arguments.length === 0) {
+ this.__eventListeners = { };
+ }
+ // one object with key/value pairs was passed
+ else if (arguments.length === 1 && typeof arguments[0] === 'object') {
+ for (var prop in eventName) {
+ _removeEventListener.call(this, prop, eventName[prop]);
+ }
+ }
+ else {
+ _removeEventListener.call(this, eventName, handler);
+ }
+ return this;
+ }
+
+ /**
+ * Fires event with an optional options object
+ * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
+ * @memberOf fabric.Observable
+ * @alias trigger
+ * @param {String} eventName Event name to fire
+ * @param {Object} [options] Options object
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function fire(eventName, options) {
+ if (!this.__eventListeners) {
+ return;
+ }
+
+ var listenersForEvent = this.__eventListeners[eventName];
+ if (!listenersForEvent) {
+ return;
+ }
+
+ for (var i = 0, len = listenersForEvent.length; i < len; i++) {
+ // avoiding try/catch for perf. reasons
+ listenersForEvent[i].call(this, options || { });
+ }
+ return this;
+ }
+
+ /**
+ * @namespace fabric.Observable
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events}
+ * @see {@link http://fabricjs.com/events/|Events demo}
+ */
+ fabric.Observable = {
+ observe: observe,
+ stopObserving: stopObserving,
+ fire: fire,
+
+ on: observe,
+ off: stopObserving,
+ trigger: fire
+ };
+})();
+
+
+/**
+ * @namespace fabric.Collection
+ */
+fabric.Collection = {
+
+ /**
+ * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * Objects should be instances of (or inherit from) fabric.Object
+ * @param {...fabric.Object} object Zero or more fabric instances
+ * @return {Self} thisArg
+ */
+ add: function () {
+ this._objects.push.apply(this._objects, arguments);
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ this._onObjectAdded(arguments[i]);
+ }
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * An object should be an instance of (or inherit from) fabric.Object
+ * @param {Object} object Object to insert
+ * @param {Number} index Index to insert object at
+ * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
+ * @return {Self} thisArg
+ * @chainable
+ */
+ insertAt: function (object, index, nonSplicing) {
+ var objects = this.getObjects();
+ if (nonSplicing) {
+ objects[index] = object;
+ }
+ else {
+ objects.splice(index, 0, object);
+ }
+ this._onObjectAdded(object);
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
+ * @param {...fabric.Object} object Zero or more fabric instances
+ * @return {Self} thisArg
+ * @chainable
+ */
+ remove: function() {
+ var objects = this.getObjects(),
+ index;
+
+ for (var i = 0, length = arguments.length; i < length; i++) {
+ index = objects.indexOf(arguments[i]);
+
+ // only call onObjectRemoved if an object was actually removed
+ if (index !== -1) {
+ objects.splice(index, 1);
+ this._onObjectRemoved(arguments[i]);
+ }
+ }
+
+ this.renderOnAddRemove && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Executes given function for each object in this group
+ * @param {Function} callback
+ * Callback invoked with current object as first argument,
+ * index - as second and an array of all objects - as third.
+ * Iteration happens in reverse order (for performance reasons).
+ * Callback is invoked in a context of Global Object (e.g. `window`)
+ * when no `context` argument is given
+ *
+ * @param {Object} context Context (aka thisObject)
+ * @return {Self} thisArg
+ */
+ forEachObject: function(callback, context) {
+ var objects = this.getObjects(),
+ i = objects.length;
+ while (i--) {
+ callback.call(context, objects[i], i, objects);
+ }
+ return this;
+ },
+
+ /**
+ * Returns an array of children objects of this instance
+ * Type parameter introduced in 1.3.10
+ * @param {String} [type] When specified, only objects of this type are returned
+ * @return {Array}
+ */
+ getObjects: function(type) {
+ if (typeof type === 'undefined') {
+ return this._objects;
+ }
+ return this._objects.filter(function(o) {
+ return o.type === type;
+ });
+ },
+
+ /**
+ * Returns object at specified index
+ * @param {Number} index
+ * @return {Self} thisArg
+ */
+ item: function (index) {
+ return this.getObjects()[index];
+ },
+
+ /**
+ * Returns true if collection contains no objects
+ * @return {Boolean} true if collection is empty
+ */
+ isEmpty: function () {
+ return this.getObjects().length === 0;
+ },
+
+ /**
+ * Returns a size of a collection (i.e: length of an array containing its objects)
+ * @return {Number} Collection size
+ */
+ size: function() {
+ return this.getObjects().length;
+ },
+
+ /**
+ * Returns true if collection contains an object
+ * @param {Object} object Object to check against
+ * @return {Boolean} `true` if collection contains an object
+ */
+ contains: function(object) {
+ return this.getObjects().indexOf(object) > -1;
+ },
+
+ /**
+ * Returns number representation of a collection complexity
+ * @return {Number} complexity
+ */
+ complexity: function () {
+ return this.getObjects().reduce(function (memo, current) {
+ memo += current.complexity ? current.complexity() : 0;
+ return memo;
+ }, 0);
+ }
+};
+
+
+(function(global) {
+
+ var sqrt = Math.sqrt,
+ atan2 = Math.atan2,
+ PiBy180 = Math.PI / 180;
+
+ /**
+ * @namespace fabric.util
+ */
+ fabric.util = {
+
+ /**
+ * Removes value from an array.
+ * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} array
+ * @param {Any} value
+ * @return {Array} original array
+ */
+ removeFromArray: function(array, value) {
+ var idx = array.indexOf(value);
+ if (idx !== -1) {
+ array.splice(idx, 1);
+ }
+ return array;
+ },
+
+ /**
+ * Returns random number between 2 specified ones.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} min lower limit
+ * @param {Number} max upper limit
+ * @return {Number} random value (between min and max)
+ */
+ getRandomInt: function(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ },
+
+ /**
+ * Transforms degrees to radians.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} degrees value in degrees
+ * @return {Number} value in radians
+ */
+ degreesToRadians: function(degrees) {
+ return degrees * PiBy180;
+ },
+
+ /**
+ * Transforms radians to degrees.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number} radians value in radians
+ * @return {Number} value in degrees
+ */
+ radiansToDegrees: function(radians) {
+ return radians / PiBy180;
+ },
+
+ /**
+ * Rotates `point` around `origin` with `radians`
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Point} point The point to rotate
+ * @param {fabric.Point} origin The origin of the rotation
+ * @param {Number} radians The radians of the angle for the rotation
+ * @return {fabric.Point} The new rotated point
+ */
+ rotatePoint: function(point, origin, radians) {
+ var sin = Math.sin(radians),
+ cos = Math.cos(radians);
+
+ point.subtractEquals(origin);
+
+ var rx = point.x * cos - point.y * sin,
+ ry = point.x * sin + point.y * cos;
+
+ return new fabric.Point(rx, ry).addEquals(origin);
+ },
+
+ /**
+ * Apply transform t to point p
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Point} p The point to transform
+ * @param {Array} t The transform
+ * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
+ * @return {fabric.Point} The transformed point
+ */
+ transformPoint: function(p, t, ignoreOffset) {
+ if (ignoreOffset) {
+ return new fabric.Point(
+ t[0] * p.x + t[1] * p.y,
+ t[2] * p.x + t[3] * p.y
+ );
+ }
+ return new fabric.Point(
+ t[0] * p.x + t[1] * p.y + t[4],
+ t[2] * p.x + t[3] * p.y + t[5]
+ );
+ },
+
+ /**
+ * Invert transformation t
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} t The transform
+ * @return {Array} The inverted transform
+ */
+ invertTransform: function(t) {
+ var r = t.slice(),
+ a = 1 / (t[0] * t[3] - t[1] * t[2]);
+ r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];
+ var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r);
+ r[4] = -o.x;
+ r[5] = -o.y;
+ return r;
+ },
+
+ /**
+ * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
+ * @static
+ * @memberOf fabric.util
+ * @param {Number|String} number number to operate on
+ * @param {Number} fractionDigits number of fraction digits to "leave"
+ * @return {Number}
+ */
+ toFixed: function(number, fractionDigits) {
+ return parseFloat(Number(number).toFixed(fractionDigits));
+ },
+
+ /**
+ * Converts from attribute value to pixel value if applicable.
+ * Returns converted pixels or original value not converted.
+ * @param {Number|String} value number to operate on
+ * @return {Number|String}
+ */
+ parseUnit: function(value) {
+ var unit = /\D{0,2}$/.exec(value),
+ number = parseFloat(value);
+
+ switch (unit[0]) {
+ case 'mm':
+ return number * fabric.DPI / 25.4;
+
+ case 'cm':
+ return number * fabric.DPI / 2.54;
+
+ case 'in':
+ return number * fabric.DPI;
+
+ case 'pt':
+ return number * fabric.DPI / 72; // or * 4 / 3
+
+ case 'pc':
+ return number * fabric.DPI / 72 * 12; // or * 16
+
+ default:
+ return number;
+ }
+ },
+
+ /**
+ * Function which always returns `false`.
+ * @static
+ * @memberOf fabric.util
+ * @return {Boolean}
+ */
+ falseFunction: function() {
+ return false;
+ },
+
+ /**
+ * Returns klass "Class" object of given namespace
+ * @memberOf fabric.util
+ * @param {String} type Type of object (eg. 'circle')
+ * @param {String} namespace Namespace to get klass "Class" object from
+ * @return {Object} klass "Class"
+ */
+ getKlass: function(type, namespace) {
+ // capitalize first letter only
+ type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
+ return fabric.util.resolveNamespace(namespace)[type];
+ },
+
+ /**
+ * Returns object of given namespace
+ * @memberOf fabric.util
+ * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
+ * @return {Object} Object for given namespace (default fabric)
+ */
+ resolveNamespace: function(namespace) {
+ if (!namespace) {
+ return fabric;
+ }
+
+ var parts = namespace.split('.'),
+ len = parts.length,
+ obj = global || fabric.window;
+
+ for (var i = 0; i < len; ++i) {
+ obj = obj[parts[i]];
+ }
+
+ return obj;
+ },
+
+ /**
+ * Loads image element from given url and passes it to a callback
+ * @memberOf fabric.util
+ * @param {String} url URL representing an image
+ * @param {Function} callback Callback; invoked with loaded image
+ * @param {Any} [context] Context to invoke callback in
+ * @param {Object} [crossOrigin] crossOrigin value to set image element to
+ */
+ loadImage: function(url, callback, context, crossOrigin) {
+ if (!url) {
+ callback && callback.call(context, url);
+ return;
+ }
+
+ var img = fabric.util.createImage();
+
+ /** @ignore */
+ img.onload = function () {
+ callback && callback.call(context, img);
+ img = img.onload = img.onerror = null;
+ };
+
+ /** @ignore */
+ img.onerror = function() {
+ fabric.log('Error loading ' + img.src);
+ callback && callback.call(context, null, true);
+ img = img.onload = img.onerror = null;
+ };
+
+ // data-urls appear to be buggy with crossOrigin
+ // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
+ // see https://code.google.com/p/chromium/issues/detail?id=315152
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=935069
+ if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') {
+ img.crossOrigin = crossOrigin;
+ }
+
+ img.src = url;
+ },
+
+ /**
+ * Creates corresponding fabric instances from their object representations
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} objects Objects to enliven
+ * @param {Function} callback Callback to invoke when all objects are created
+ * @param {String} namespace Namespace to get klass "Class" object from
+ * @param {Function} reviver Method for further parsing of object elements,
+ * called after each fabric object created.
+ */
+ enlivenObjects: function(objects, callback, namespace, reviver) {
+ objects = objects || [ ];
+
+ function onLoaded() {
+ if (++numLoadedObjects === numTotalObjects) {
+ callback && callback(enlivenedObjects);
+ }
+ }
+
+ var enlivenedObjects = [ ],
+ numLoadedObjects = 0,
+ numTotalObjects = objects.length;
+
+ if (!numTotalObjects) {
+ callback && callback(enlivenedObjects);
+ return;
+ }
+
+ objects.forEach(function (o, index) {
+ // if sparse array
+ if (!o || !o.type) {
+ onLoaded();
+ return;
+ }
+ var klass = fabric.util.getKlass(o.type, namespace);
+ if (klass.async) {
+ klass.fromObject(o, function (obj, error) {
+ if (!error) {
+ enlivenedObjects[index] = obj;
+ reviver && reviver(o, enlivenedObjects[index]);
+ }
+ onLoaded();
+ });
+ }
+ else {
+ enlivenedObjects[index] = klass.fromObject(o);
+ reviver && reviver(o, enlivenedObjects[index]);
+ onLoaded();
+ }
+ });
+ },
+
+ /**
+ * Groups SVG elements (usually those retrieved from SVG document)
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} elements SVG elements to group
+ * @param {Object} [options] Options object
+ * @return {fabric.Object|fabric.PathGroup}
+ */
+ groupSVGElements: function(elements, options, path) {
+ var object;
+
+ object = new fabric.PathGroup(elements, options);
+
+ if (typeof path !== 'undefined') {
+ object.setSourcePath(path);
+ }
+ return object;
+ },
+
+ /**
+ * Populates an object with properties of another object
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} source Source object
+ * @param {Object} destination Destination object
+ * @return {Array} properties Propertie names to include
+ */
+ populateWithProperties: function(source, destination, properties) {
+ if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
+ for (var i = 0, len = properties.length; i < len; i++) {
+ if (properties[i] in source) {
+ destination[properties[i]] = source[properties[i]];
+ }
+ }
+ }
+ },
+
+ /**
+ * Draws a dashed line between two points
+ *
+ * This method is used to draw dashed line around selection area.
+ * See dotted stroke in canvas
+ *
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x start x coordinate
+ * @param {Number} y start y coordinate
+ * @param {Number} x2 end x coordinate
+ * @param {Number} y2 end y coordinate
+ * @param {Array} da dash array pattern
+ */
+ drawDashedLine: function(ctx, x, y, x2, y2, da) {
+ var dx = x2 - x,
+ dy = y2 - y,
+ len = sqrt(dx * dx + dy * dy),
+ rot = atan2(dy, dx),
+ dc = da.length,
+ di = 0,
+ draw = true;
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.moveTo(0, 0);
+ ctx.rotate(rot);
+
+ x = 0;
+ while (len > x) {
+ x += da[di++ % dc];
+ if (x > len) {
+ x = len;
+ }
+ ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
+ draw = !draw;
+ }
+
+ ctx.restore();
+ },
+
+ /**
+ * Creates canvas element and initializes it via excanvas if necessary
+ * @static
+ * @memberOf fabric.util
+ * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
+ * when not given, element is created implicitly
+ * @return {CanvasElement} initialized canvas element
+ */
+ createCanvasElement: function(canvasEl) {
+ canvasEl || (canvasEl = fabric.document.createElement('canvas'));
+ //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+ //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
+ return canvasEl;
+ },
+
+ /**
+ * Creates image element (works on client and node)
+ * @static
+ * @memberOf fabric.util
+ * @return {HTMLImageElement} HTML image element
+ */
+ createImage: function() {
+ return fabric.isLikelyNode
+ ? new (_dereq_('canvas').Image)()
+ : fabric.document.createElement('img');
+ },
+
+ /**
+ * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
+ * @static
+ * @memberOf fabric.util
+ * @param {Object} klass "Class" to create accessors for
+ */
+ createAccessors: function(klass) {
+ var proto = klass.prototype;
+
+ for (var i = proto.stateProperties.length; i--; ) {
+
+ var propName = proto.stateProperties[i],
+ capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
+ setterName = 'set' + capitalizedPropName,
+ getterName = 'get' + capitalizedPropName;
+
+ // using `new Function` for better introspection
+ if (!proto[getterName]) {
+ proto[getterName] = (function(property) {
+ return new Function('return this.get("' + property + '")');
+ })(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = (function(property) {
+ return new Function('value', 'return this.set("' + property + '", value)');
+ })(propName);
+ }
+ }
+ },
+
+ /**
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Object} receiver Object implementing `clipTo` method
+ * @param {CanvasRenderingContext2D} ctx Context to clip
+ */
+ clipContext: function(receiver, ctx) {
+ ctx.save();
+ ctx.beginPath();
+ receiver.clipTo(ctx);
+ ctx.clip();
+ },
+
+ /**
+ * Multiply matrix A by matrix B to nest transformations
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} matrixA First transformMatrix
+ * @param {Array} matrixB Second transformMatrix
+ * @return {Array} The product of the two transform matrices
+ */
+ multiplyTransformMatrices: function(matrixA, matrixB) {
+ // Matrix multiply matrixA * matrixB
+ var a = [
+ [matrixA[0], matrixA[2], matrixA[4]],
+ [matrixA[1], matrixA[3], matrixA[5]],
+ [0, 0, 1 ]
+ ],
+
+ b = [
+ [matrixB[0], matrixB[2], matrixB[4]],
+ [matrixB[1], matrixB[3], matrixB[5]],
+ [0, 0, 1 ]
+ ],
+
+ result = [];
+
+ for (var r = 0; r < 3; r++) {
+ result[r] = [];
+ for (var c = 0; c < 3; c++) {
+ var sum = 0;
+ for (var k = 0; k < 3; k++) {
+ sum += a[r][k] * b[k][c];
+ }
+
+ result[r][c] = sum;
+ }
+ }
+
+ return [
+ result[0][0],
+ result[1][0],
+ result[0][1],
+ result[1][1],
+ result[0][2],
+ result[1][2]
+ ];
+ },
+
+ /**
+ * Returns string representation of function body
+ * @param {Function} fn Function to get body of
+ * @return {String} Function body
+ */
+ getFunctionBody: function(fn) {
+ return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
+ },
+
+ /**
+ * Returns true if context has transparent pixel
+ * at specified location (taking tolerance into account)
+ * @param {CanvasRenderingContext2D} ctx context
+ * @param {Number} x x coordinate
+ * @param {Number} y y coordinate
+ * @param {Number} tolerance Tolerance
+ */
+ isTransparent: function(ctx, x, y, tolerance) {
+
+ // If tolerance is > 0 adjust start coords to take into account.
+ // If moves off Canvas fix to 0
+ if (tolerance > 0) {
+ if (x > tolerance) {
+ x -= tolerance;
+ }
+ else {
+ x = 0;
+ }
+ if (y > tolerance) {
+ y -= tolerance;
+ }
+ else {
+ y = 0;
+ }
+ }
+
+ var _isTransparent = true,
+ imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1);
+
+ // Split image data - for tolerance > 1, pixelDataSize = 4;
+ for (var i = 3, l = imageData.data.length; i < l; i += 4) {
+ var temp = imageData.data[i];
+ _isTransparent = temp <= 0;
+ if (_isTransparent === false) {
+ break; // Stop if colour found
+ }
+ }
+
+ imageData = null;
+
+ return _isTransparent;
+ }
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function() {
+
+ var arcToSegmentsCache = { },
+ segmentToBezierCache = { },
+ _join = Array.prototype.join;
+
+ /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
+ * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
+ * http://mozilla.org/MPL/2.0/
+ */
+ function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
+ var argsString = _join.call(arguments);
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
+
+ var PI = Math.PI, th = rotateX * (PI / 180),
+ sinTh = Math.sin(th),
+ cosTh = Math.cos(th),
+ fromX = 0, fromY = 0;
+
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+
+ var px = -cosTh * toX - sinTh * toY,
+ py = -cosTh * toY + sinTh * toX,
+ rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
+ pl = 4 * rx2 * ry2 - rx2 * py2 - ry2 * px2,
+ root = 0;
+
+ if (pl < 0) {
+ var s = Math.sqrt(1 - 0.25 * pl/(rx2 * ry2));
+ rx *= s;
+ ry *= s;
+ }
+ else {
+ root = (large === sweep ? -0.5 : 0.5) *
+ Math.sqrt( pl /(rx2 * py2 + ry2 * px2));
+ }
+
+ var cx = root * rx * py / ry,
+ cy = -root * ry * px / rx,
+ cx1 = cosTh * cx - sinTh * cy + toX / 2,
+ cy1 = sinTh * cx + cosTh * cy + toY / 2,
+ mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
+ dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
+
+ if (sweep === 0 && dtheta > 0) {
+ dtheta -= 2 * PI;
+ }
+ else if (sweep === 1 && dtheta < 0) {
+ dtheta += 2 * PI;
+ }
+
+ // Convert into cubic bezier segments <= 90deg
+ var segments = Math.ceil(Math.abs(dtheta / (PI * 0.5))),
+ result = [], mDelta = dtheta / segments,
+ mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
+ th3 = mTheta + mDelta;
+
+ for (var i = 0; i < segments; i++) {
+ result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
+ fromX = result[i][4];
+ fromY = result[i][5];
+ mTheta += mDelta;
+ th3 += mDelta;
+ }
+ arcToSegmentsCache[argsString] = result;
+ return result;
+ }
+
+ function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
+ var argsString2 = _join.call(arguments);
+ if (segmentToBezierCache[argsString2]) {
+ return segmentToBezierCache[argsString2];
+ }
+
+ var costh2 = Math.cos(th2),
+ sinth2 = Math.sin(th2),
+ costh3 = Math.cos(th3),
+ sinth3 = Math.sin(th3),
+ toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
+ toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
+ cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2),
+ cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2),
+ cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
+ cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
+
+ segmentToBezierCache[argsString2] = [
+ cp1X, cp1Y,
+ cp2X, cp2Y,
+ toX, toY
+ ];
+ return segmentToBezierCache[argsString2];
+ }
+
+ /*
+ * Private
+ */
+ function calcVectorAngle(ux, uy, vx, vy) {
+ var ta = Math.atan2(uy, ux),
+ tb = Math.atan2(vy, vx);
+ if (tb >= ta) {
+ return tb - ta;
+ }
+ else {
+ return 2 * Math.PI - (ta - tb);
+ }
+ }
+
+ /**
+ * Draws arc
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {Number} fx
+ * @param {Number} fy
+ * @param {Array} coords
+ */
+ fabric.util.drawArc = function(ctx, fx, fy, coords) {
+ var rx = coords[0],
+ ry = coords[1],
+ rot = coords[2],
+ large = coords[3],
+ sweep = coords[4],
+ tx = coords[5],
+ ty = coords[6],
+ segs = [[ ], [ ], [ ], [ ]],
+ segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
+
+ for (var i = 0, len = segsNorm.length; i < len; i++) {
+ segs[i][0] = segsNorm[i][0] + fx;
+ segs[i][1] = segsNorm[i][1] + fy;
+ segs[i][2] = segsNorm[i][2] + fx;
+ segs[i][3] = segsNorm[i][3] + fy;
+ segs[i][4] = segsNorm[i][4] + fx;
+ segs[i][5] = segsNorm[i][5] + fy;
+ ctx.bezierCurveTo.apply(ctx, segs[i]);
+ }
+ };
+})();
+
+
+(function() {
+
+ var slice = Array.prototype.slice;
+
+
+
+ /**
+ * Invokes method on all items in a given array
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} method Name of a method to invoke
+ * @return {Array}
+ */
+ function invoke(array, method) {
+ var args = slice.call(arguments, 2), result = [ ];
+ for (var i = 0, len = array.length; i < len; i++) {
+ result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Finds maximum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {Any}
+ */
+ function max(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 >= value2;
+ });
+ }
+
+ /**
+ * Finds minimum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {Any}
+ */
+ function min(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 < value2;
+ });
+ }
+
+ /**
+ * @private
+ */
+ function find(array, byProperty, condition) {
+ if (!array || array.length === 0) {
+ return;
+ }
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (condition(array[i][byProperty], result)) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (condition(array[i], result)) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @namespace fabric.util.array
+ */
+ fabric.util.array = {
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+
+})();
+
+
+(function(){
+
+ /**
+ * Copies all enumerable properties of one object to another
+ * @memberOf fabric.util.object
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ * @return {Object}
+ */
+ function extend(destination, source) {
+ // JScript DontEnum bug is not taken care of
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+ }
+
+ /**
+ * Creates an empty object and copies all enumerable properties of another object to it
+ * @memberOf fabric.util.object
+ * @param {Object} object Object to clone
+ * @return {Object}
+ */
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ /** @namespace fabric.util.object */
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
+ };
+
+})();
+
+
+(function() {
+
+
+
+ /**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to camelize
+ * @return {String} Camelized version of a string
+ */
+ function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : '';
+ });
+ }
+
+ /**
+ * Capitalizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
+ * and other letters stay untouched, if false first letter is capitalized
+ * and other letters are converted to lowercase.
+ * @return {String} Capitalized version of a string
+ */
+ function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() +
+ (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+ }
+
+ /**
+ * Escapes XML in a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to escape
+ * @return {String} Escaped version of a string
+ */
+ function escapeXml(string) {
+ return string.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>');
+ }
+
+ /**
+ * String utilities
+ * @namespace fabric.util.string
+ */
+ fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml
+ };
+}());
+
+
+
+
+
+(function() {
+
+ var slice = Array.prototype.slice, emptyFunction = function() { },
+
+ IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') {
+ return false;
+ }
+ }
+ return true;
+ })(),
+
+ /** @ignore */
+ addMethods = function(klass, source, parent) {
+ for (var property in source) {
+
+ if (property in klass.prototype &&
+ typeof klass.prototype[property] === 'function' &&
+ (source[property] + '').indexOf('callSuper') > -1) {
+
+ klass.prototype[property] = (function(property) {
+ return function() {
+
+ var superclass = this.constructor.superclass;
+ this.constructor.superclass = parent;
+ var returnValue = source[property].apply(this, arguments);
+ this.constructor.superclass = superclass;
+
+ if (property !== 'initialize') {
+ return returnValue;
+ }
+ };
+ })(property);
+ }
+ else {
+ klass.prototype[property] = source[property];
+ }
+
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString !== Object.prototype.toString) {
+ klass.prototype.toString = source.toString;
+ }
+ if (source.valueOf !== Object.prototype.valueOf) {
+ klass.prototype.valueOf = source.valueOf;
+ }
+ }
+ }
+ };
+
+ function Subclass() { }
+
+ function callSuper(methodName) {
+ var fn = this.constructor.superclass.prototype[methodName];
+ return (arguments.length > 1)
+ ? fn.apply(this, slice.call(arguments, 1))
+ : fn.call(this);
+ }
+
+ /**
+ * Helper for creation of "classes".
+ * @memberOf fabric.util
+ * @param {Function} [parent] optional "Class" to inherit from
+ * @param {Object} [properties] Properties shared by all instances of this class
+ * (be careful modifying objects defined here as this would affect all instances)
+ */
+ function createClass() {
+ var parent = null,
+ properties = slice.call(arguments, 0);
+
+ if (typeof properties[0] === 'function') {
+ parent = properties.shift();
+ }
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ klass.superclass = parent;
+ klass.subclasses = [ ];
+
+ if (parent) {
+ Subclass.prototype = parent.prototype;
+ klass.prototype = new Subclass();
+ parent.subclasses.push(klass);
+ }
+ for (var i = 0, length = properties.length; i < length; i++) {
+ addMethods(klass, properties[i], parent);
+ }
+ if (!klass.prototype.initialize) {
+ klass.prototype.initialize = emptyFunction;
+ }
+ klass.prototype.constructor = klass;
+ klass.prototype.callSuper = callSuper;
+ return klass;
+ }
+
+ fabric.util.createClass = createClass;
+})();
+
+
+(function () {
+
+ var unknown = 'unknown';
+
+ /* EVENT HANDLING */
+
+ function areHostMethods(object) {
+ var methodNames = Array.prototype.slice.call(arguments, 1),
+ t, i, len = methodNames.length;
+ for (i = 0; i < len; i++) {
+ t = typeof object[methodNames[i]];
+ if (!(/^(?:function|object|unknown)$/).test(t)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @ignore */
+ var getElement,
+ setElement,
+ getUniqueId = (function () {
+ var uid = 0;
+ return function (element) {
+ return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
+ };
+ })();
+
+ (function () {
+ var elements = { };
+ /** @ignore */
+ getElement = function (uid) {
+ return elements[uid];
+ };
+ /** @ignore */
+ setElement = function (uid, element) {
+ elements[uid] = element;
+ };
+ })();
+
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+
+ function createWrappedHandler(uid, handler) {
+ return function (e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+
+ function createDispatcher(uid, eventName) {
+ return function (e) {
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ handlersForEvent[i].call(this, e || fabric.window.event);
+ }
+ }
+ };
+ }
+
+ var shouldUseAddListenerRemoveListener = (
+ areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
+ areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
+
+ shouldUseAttachEventDetachEvent = (
+ areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
+ areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
+
+ // IE branch
+ listeners = { },
+
+ // DOM L0 branch
+ handlers = { },
+
+ addListener, removeListener;
+
+ if (shouldUseAddListenerRemoveListener) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ element.addEventListener(eventName, handler, false);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ element.removeEventListener(eventName, handler, false);
+ };
+ }
+
+ else if (shouldUseAttachEventDetachEvent) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ setElement(uid, element);
+ if (!listeners[uid]) {
+ listeners[uid] = { };
+ }
+ if (!listeners[uid][eventName]) {
+ listeners[uid][eventName] = [ ];
+
+ }
+ var listener = createListener(uid, handler);
+ listeners[uid][eventName].push(listener);
+ element.attachEvent('on' + eventName, listener.wrappedHandler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element), listener;
+ if (listeners[uid] && listeners[uid][eventName]) {
+ for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
+ listener = listeners[uid][eventName][i];
+ if (listener && listener.handler === handler) {
+ element.detachEvent('on' + eventName, listener.wrappedHandler);
+ listeners[uid][eventName][i] = null;
+ }
+ }
+ }
+ };
+ }
+ else {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (!handlers[uid]) {
+ handlers[uid] = { };
+ }
+ if (!handlers[uid][eventName]) {
+ handlers[uid][eventName] = [ ];
+ var existingHandler = element['on' + eventName];
+ if (existingHandler) {
+ handlers[uid][eventName].push(existingHandler);
+ }
+ element['on' + eventName] = createDispatcher(uid, eventName);
+ }
+ handlers[uid][eventName].push(handler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ if (handlersForEvent[i] === handler) {
+ handlersForEvent.splice(i, 1);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds an event listener to an element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.addListener = addListener;
+
+ /**
+ * Removes an event listener from an element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.removeListener = removeListener;
+
+ /**
+ * Cross-browser wrapper for getting event's coordinates
+ * @memberOf fabric.util
+ * @param {Event} event Event object
+ * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn
+ */
+ function getPointer(event, upperCanvasEl) {
+ event || (event = fabric.window.event);
+
+ var element = event.target ||
+ (typeof event.srcElement !== unknown ? event.srcElement : null),
+
+ scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl);
+
+ return {
+ x: pointerX(event) + scroll.left,
+ y: pointerY(event) + scroll.top
+ };
+ }
+
+ var pointerX = function(event) {
+ // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
+ // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
+ // need to investigate later
+ return (typeof event.clientX !== unknown ? event.clientX : 0);
+ },
+
+ pointerY = function(event) {
+ return (typeof event.clientY !== unknown ? event.clientY : 0);
+ };
+
+ function _getPointer(event, pageProp, clientProp) {
+ var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
+
+ return (event[touchProp] && event[touchProp][0]
+ ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
+ || event[clientProp]
+ : event[clientProp]);
+ }
+
+ if (fabric.isTouchSupported) {
+ pointerX = function(event) {
+ return _getPointer(event, 'pageX', 'clientX');
+ };
+ pointerY = function(event) {
+ return _getPointer(event, 'pageY', 'clientY');
+ };
+ }
+
+ fabric.util.getPointer = getPointer;
+
+ fabric.util.object.extend(fabric.util, fabric.Observable);
+
+})();
+
+
+(function () {
+
+ /**
+ * Cross-browser wrapper for setting element's style
+ * @memberOf fabric.util
+ * @param {HTMLElement} element
+ * @param {Object} styles
+ * @return {HTMLElement} Element that was passed as a first argument
+ */
+ function setStyle(element, styles) {
+ var elementStyle = element.style;
+ if (!elementStyle) {
+ return element;
+ }
+ if (typeof styles === 'string') {
+ element.style.cssText += ';' + styles;
+ return styles.indexOf('opacity') > -1
+ ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
+ : element;
+ }
+ for (var property in styles) {
+ if (property === 'opacity') {
+ setOpacity(element, styles[property]);
+ }
+ else {
+ var normalizedProperty = (property === 'float' || property === 'cssFloat')
+ ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
+ : property;
+ elementStyle[normalizedProperty] = styles[property];
+ }
+ }
+ return element;
+ }
+
+ var parseEl = fabric.document.createElement('div'),
+ supportsOpacity = typeof parseEl.style.opacity === 'string',
+ supportsFilters = typeof parseEl.style.filter === 'string',
+ reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
+
+ /** @ignore */
+ setOpacity = function (element) { return element; };
+
+ if (supportsOpacity) {
+ /** @ignore */
+ setOpacity = function(element, value) {
+ element.style.opacity = value;
+ return element;
+ };
+ }
+ else if (supportsFilters) {
+ /** @ignore */
+ setOpacity = function(element, value) {
+ var es = element.style;
+ if (element.currentStyle && !element.currentStyle.hasLayout) {
+ es.zoom = 1;
+ }
+ if (reOpacity.test(es.filter)) {
+ value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
+ es.filter = es.filter.replace(reOpacity, value);
+ }
+ else {
+ es.filter += ' alpha(opacity=' + (value * 100) + ')';
+ }
+ return element;
+ };
+ }
+
+ fabric.util.setStyle = setStyle;
+
+})();
+
+
+(function() {
+
+ var _slice = Array.prototype.slice;
+
+ /**
+ * Takes id and returns an element with that id (if one exists in a document)
+ * @memberOf fabric.util
+ * @param {String|HTMLElement} id
+ * @return {HTMLElement|null}
+ */
+ function getById(id) {
+ return typeof id === 'string' ? fabric.document.getElementById(id) : id;
+ }
+
+ var sliceCanConvertNodelists,
+ /**
+ * Converts an array-like object (e.g. arguments or NodeList) to an array
+ * @memberOf fabric.util
+ * @param {Object} arrayLike
+ * @return {Array}
+ */
+ toArray = function(arrayLike) {
+ return _slice.call(arrayLike, 0);
+ };
+
+ try {
+ sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
+ }
+ catch (err) { }
+
+ if (!sliceCanConvertNodelists) {
+ toArray = function(arrayLike) {
+ var arr = new Array(arrayLike.length), i = arrayLike.length;
+ while (i--) {
+ arr[i] = arrayLike[i];
+ }
+ return arr;
+ };
+ }
+
+ /**
+ * Creates specified element with specified attributes
+ * @memberOf fabric.util
+ * @param {String} tagName Type of an element to create
+ * @param {Object} [attributes] Attributes to set on an element
+ * @return {HTMLElement} Newly created element
+ */
+ function makeElement(tagName, attributes) {
+ var el = fabric.document.createElement(tagName);
+ for (var prop in attributes) {
+ if (prop === 'class') {
+ el.className = attributes[prop];
+ }
+ else if (prop === 'for') {
+ el.htmlFor = attributes[prop];
+ }
+ else {
+ el.setAttribute(prop, attributes[prop]);
+ }
+ }
+ return el;
+ }
+
+ /**
+ * Adds class to an element
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to add class to
+ * @param {String} className Class to add to an element
+ */
+ function addClass(element, className) {
+ if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
+ element.className += (element.className ? ' ' : '') + className;
+ }
+ }
+
+ /**
+ * Wraps element with another element
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to wrap
+ * @param {HTMLElement|String} wrapper Element to wrap with
+ * @param {Object} [attributes] Attributes to set on a wrapper
+ * @return {HTMLElement} wrapper
+ */
+ function wrapElement(element, wrapper, attributes) {
+ if (typeof wrapper === 'string') {
+ wrapper = makeElement(wrapper, attributes);
+ }
+ if (element.parentNode) {
+ element.parentNode.replaceChild(wrapper, element);
+ }
+ wrapper.appendChild(element);
+ return wrapper;
+ }
+
+ /**
+ * Returns element scroll offsets
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to operate on
+ * @param {HTMLElement} upperCanvasEl Upper canvas element
+ * @return {Object} Object with left/top values
+ */
+ function getScrollLeftTop(element, upperCanvasEl) {
+
+ var firstFixedAncestor,
+ origElement,
+ left = 0,
+ top = 0,
+ docElement = fabric.document.documentElement,
+ body = fabric.document.body || {
+ scrollLeft: 0, scrollTop: 0
+ };
+
+ origElement = element;
+
+ while (element && element.parentNode && !firstFixedAncestor) {
+
+ element = element.parentNode;
+
+ if (element !== fabric.document &&
+ fabric.util.getElementStyle(element, 'position') === 'fixed') {
+ firstFixedAncestor = element;
+ }
+
+ if (element !== fabric.document &&
+ origElement !== upperCanvasEl &&
+ fabric.util.getElementStyle(element, 'position') === 'absolute') {
+ left = 0;
+ top = 0;
+ }
+ else if (element === fabric.document) {
+ left = body.scrollLeft || docElement.scrollLeft || 0;
+ top = body.scrollTop || docElement.scrollTop || 0;
+ }
+ else {
+ left += element.scrollLeft || 0;
+ top += element.scrollTop || 0;
+ }
+ }
+
+ return { left: left, top: top };
+ }
+
+ /**
+ * Returns offset for a given element
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to get offset for
+ * @return {Object} Object with "left" and "top" properties
+ */
+ function getElementOffset(element) {
+ var docElem,
+ doc = element && element.ownerDocument,
+ box = { left: 0, top: 0 },
+ offset = { left: 0, top: 0 },
+ scrollLeftTop,
+ offsetAttributes = {
+ borderLeftWidth: 'left',
+ borderTopWidth: 'top',
+ paddingLeft: 'left',
+ paddingTop: 'top'
+ };
+
+ if (!doc) {
+ return { left: 0, top: 0 };
+ }
+
+ for (var attr in offsetAttributes) {
+ offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
+ }
+
+ docElem = doc.documentElement;
+ if ( typeof element.getBoundingClientRect !== 'undefined' ) {
+ box = element.getBoundingClientRect();
+ }
+
+ scrollLeftTop = fabric.util.getScrollLeftTop(element, null);
+
+ return {
+ left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
+ top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
+ };
+ }
+
+ /**
+ * Returns style attribute value of a given element
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to get style attribute for
+ * @param {String} attr Style attribute to get for element
+ * @return {String} Style attribute value of the given element.
+ */
+ var getElementStyle;
+ if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
+ getElementStyle = function(element, attr) {
+ return fabric.document.defaultView.getComputedStyle(element, null)[attr];
+ };
+ }
+ else {
+ getElementStyle = function(element, attr) {
+ var value = element.style[attr];
+ if (!value && element.currentStyle) {
+ value = element.currentStyle[attr];
+ }
+ return value;
+ };
+ }
+
+ (function () {
+ var style = fabric.document.documentElement.style,
+ selectProp = 'userSelect' in style
+ ? 'userSelect'
+ : 'MozUserSelect' in style
+ ? 'MozUserSelect'
+ : 'WebkitUserSelect' in style
+ ? 'WebkitUserSelect'
+ : 'KhtmlUserSelect' in style
+ ? 'KhtmlUserSelect'
+ : '';
+
+ /**
+ * Makes element unselectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make unselectable
+ * @return {HTMLElement} Element that was passed in
+ */
+ function makeElementUnselectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = fabric.util.falseFunction;
+ }
+ if (selectProp) {
+ element.style[selectProp] = 'none';
+ }
+ else if (typeof element.unselectable === 'string') {
+ element.unselectable = 'on';
+ }
+ return element;
+ }
+
+ /**
+ * Makes element selectable
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to make selectable
+ * @return {HTMLElement} Element that was passed in
+ */
+ function makeElementSelectable(element) {
+ if (typeof element.onselectstart !== 'undefined') {
+ element.onselectstart = null;
+ }
+ if (selectProp) {
+ element.style[selectProp] = '';
+ }
+ else if (typeof element.unselectable === 'string') {
+ element.unselectable = '';
+ }
+ return element;
+ }
+
+ fabric.util.makeElementUnselectable = makeElementUnselectable;
+ fabric.util.makeElementSelectable = makeElementSelectable;
+ })();
+
+ (function() {
+
+ /**
+ * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
+ * @memberOf fabric.util
+ * @param {String} url URL of a script to load
+ * @param {Function} callback Callback to execute when script is finished loading
+ */
+ function getScript(url, callback) {
+ var headEl = fabric.document.getElementsByTagName('head')[0],
+ scriptEl = fabric.document.createElement('script'),
+ loading = true;
+
+ /** @ignore */
+ scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
+ if (loading) {
+ if (typeof this.readyState === 'string' &&
+ this.readyState !== 'loaded' &&
+ this.readyState !== 'complete') {
+ return;
+ }
+ loading = false;
+ callback(e || fabric.window.event);
+ scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
+ }
+ };
+ scriptEl.src = url;
+ headEl.appendChild(scriptEl);
+ // causes issue in Opera
+ // headEl.removeChild(scriptEl);
+ }
+
+ fabric.util.getScript = getScript;
+ })();
+
+ fabric.util.getById = getById;
+ fabric.util.toArray = toArray;
+ fabric.util.makeElement = makeElement;
+ fabric.util.addClass = addClass;
+ fabric.util.wrapElement = wrapElement;
+ fabric.util.getScrollLeftTop = getScrollLeftTop;
+ fabric.util.getElementOffset = getElementOffset;
+ fabric.util.getElementStyle = getElementStyle;
+
+})();
+
+
+(function(){
+
+ function addParamToUrl(url, param) {
+ return url + (/\?/.test(url) ? '&' : '?') + param;
+ }
+
+ var makeXHR = (function() {
+ var factories = [
+ function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
+ function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
+ function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },
+ function() { return new XMLHttpRequest(); }
+ ];
+ for (var i = factories.length; i--; ) {
+ try {
+ var req = factories[i]();
+ if (req) {
+ return factories[i];
+ }
+ }
+ catch (err) { }
+ }
+ })();
+
+ function emptyFn() { }
+
+ /**
+ * Cross-browser abstraction for sending XMLHttpRequest
+ * @memberOf fabric.util
+ * @param {String} url URL to send XMLHttpRequest to
+ * @param {Object} [options] Options object
+ * @param {String} [options.method="GET"]
+ * @param {Function} options.onComplete Callback to invoke when request is completed
+ * @return {XMLHttpRequest} request
+ */
+ function request(url, options) {
+
+ options || (options = { });
+
+ var method = options.method ? options.method.toUpperCase() : 'GET',
+ onComplete = options.onComplete || function() { },
+ xhr = makeXHR(),
+ body;
+
+ /** @ignore */
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ onComplete(xhr);
+ xhr.onreadystatechange = emptyFn;
+ }
+ };
+
+ if (method === 'GET') {
+ body = null;
+ if (typeof options.parameters === 'string') {
+ url = addParamToUrl(url, options.parameters);
+ }
+ }
+
+ xhr.open(method, url, true);
+
+ if (method === 'POST' || method === 'PUT') {
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ }
+
+ xhr.send(body);
+ return xhr;
+ }
+
+ fabric.util.request = request;
+})();
+
+
+/**
+ * Wrapper around `console.log` (when available)
+ * @param {Any} [values] Values to log
+ */
+fabric.log = function() { };
+
+/**
+ * Wrapper around `console.warn` (when available)
+ * @param {Any} [values] Values to log as a warning
+ */
+fabric.warn = function() { };
+
+if (typeof console !== 'undefined') {
+ ['log', 'warn'].forEach(function(methodName) {
+ if (typeof console[methodName] !== 'undefined' && console[methodName].apply) {
+ fabric[methodName] = function() {
+ return console[methodName].apply(console, arguments);
+ };
+ }
+ });
+}
+
+
+(function(global) {
+
+ 'use strict';
+
+ /**
+ * @name fabric
+ * @namespace
+ */
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ capitalize = fabric.util.string.capitalize,
+ clone = fabric.util.object.clone,
+ toFixed = fabric.util.toFixed,
+ parseUnit = fabric.util.parseUnit,
+ multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
+
+ attributesMap = {
+ cx: 'left',
+ x: 'left',
+ r: 'radius',
+ cy: 'top',
+ y: 'top',
+ display: 'visible',
+ visibility: 'visible',
+ transform: 'transformMatrix',
+ 'fill-opacity': 'fillOpacity',
+ 'fill-rule': 'fillRule',
+ 'font-family': 'fontFamily',
+ 'font-size': 'fontSize',
+ 'font-style': 'fontStyle',
+ 'font-weight': 'fontWeight',
+ 'stroke-dasharray': 'strokeDashArray',
+ 'stroke-linecap': 'strokeLineCap',
+ 'stroke-linejoin': 'strokeLineJoin',
+ 'stroke-miterlimit': 'strokeMiterLimit',
+ 'stroke-opacity': 'strokeOpacity',
+ 'stroke-width': 'strokeWidth',
+ 'text-decoration': 'textDecoration',
+ 'text-anchor': 'originX'
+ },
+
+ colorAttributes = {
+ stroke: 'strokeOpacity',
+ fill: 'fillOpacity'
+ };
+
+ function normalizeAttr(attr) {
+ // transform attribute names
+ if (attr in attributesMap) {
+ return attributesMap[attr];
+ }
+ return attr;
+ }
+
+ function normalizeValue(attr, value, parentAttributes) {
+ var isArray = Object.prototype.toString.call(value) === '[object Array]',
+ parsed;
+
+ if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
+ value = '';
+ }
+ else if (attr === 'fillRule') {
+ value = (value === 'evenodd') ? 'destination-over' : value;
+ }
+ else if (attr === 'strokeDashArray') {
+ value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
+ return parseInt(n);
+ });
+ }
+ else if (attr === 'transformMatrix') {
+ if (parentAttributes && parentAttributes.transformMatrix) {
+ value = multiplyTransformMatrices(
+ parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
+ }
+ else {
+ value = fabric.parseTransformAttribute(value);
+ }
+ }
+ else if (attr === 'visible') {
+ value = (value === 'none' || value === 'hidden') ? false : true;
+ // display=none on parent element always takes precedence over child element
+ if (parentAttributes && parentAttributes.visible === false) {
+ value = false;
+ }
+ }
+ else if (attr === 'originX' /* text-anchor */) {
+ value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
+ }
+ else {
+ parsed = isArray ? value.map(parseUnit) : parseUnit(value);
+ }
+
+ return (!isArray && isNaN(parsed) ? value : parsed);
+ }
+
+ /**
+ * @private
+ * @param {Object} attributes Array of attributes to parse
+ */
+ function _setStrokeFillOpacity(attributes) {
+ for (var attr in colorAttributes) {
+
+ if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') {
+ continue;
+ }
+
+ if (attributes[attr].indexOf('url(') === 0) {
+ continue;
+ }
+
+ var color = new fabric.Color(attributes[attr]);
+ attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
+ }
+ return attributes;
+ }
+
+ /**
+ * Parses "transform" attribute, returning an array of values
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {String} attributeValue String containing attribute value
+ * @return {Array} Array of 6 elements representing transformation matrix
+ */
+ fabric.parseTransformAttribute = (function() {
+ function rotateMatrix(matrix, args) {
+ var angle = args[0];
+
+ matrix[0] = Math.cos(angle);
+ matrix[1] = Math.sin(angle);
+ matrix[2] = -Math.sin(angle);
+ matrix[3] = Math.cos(angle);
+ }
+
+ function scaleMatrix(matrix, args) {
+ var multiplierX = args[0],
+ multiplierY = (args.length === 2) ? args[1] : args[0];
+
+ matrix[0] = multiplierX;
+ matrix[3] = multiplierY;
+ }
+
+ function skewXMatrix(matrix, args) {
+ matrix[2] = args[0];
+ }
+
+ function skewYMatrix(matrix, args) {
+ matrix[1] = args[0];
+ }
+
+ function translateMatrix(matrix, args) {
+ matrix[4] = args[0];
+ if (args.length === 2) {
+ matrix[5] = args[1];
+ }
+ }
+
+ // identity matrix
+ var iMatrix = [
+ 1, // a
+ 0, // b
+ 0, // c
+ 1, // d
+ 0, // e
+ 0 // f
+ ],
+
+ // == begin transform regexp
+ number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
+
+ commaWsp = '(?:\\s+,?\\s*|,\\s*)',
+
+ skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
+
+ skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
+
+ rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
+ commaWsp + '(' + number + ')' +
+ commaWsp + '(' + number + '))?\\s*\\))',
+
+ scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
+ commaWsp + '(' + number + '))?\\s*\\))',
+
+ translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
+ commaWsp + '(' + number + '))?\\s*\\))',
+
+ matrix = '(?:(matrix)\\s*\\(\\s*' +
+ '(' + number + ')' + commaWsp +
+ '(' + number + ')' + commaWsp +
+ '(' + number + ')' + commaWsp +
+ '(' + number + ')' + commaWsp +
+ '(' + number + ')' + commaWsp +
+ '(' + number + ')' +
+ '\\s*\\))',
+
+ transform = '(?:' +
+ matrix + '|' +
+ translate + '|' +
+ scale + '|' +
+ rotate + '|' +
+ skewX + '|' +
+ skewY +
+ ')',
+
+ transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')',
+
+ transformList = '^\\s*(?:' + transforms + '?)\\s*$',
+
+ // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ reTransformList = new RegExp(transformList),
+ // == end transform regexp
+
+ reTransform = new RegExp(transform, 'g');
+
+ return function(attributeValue) {
+
+ // start with identity matrix
+ var matrix = iMatrix.concat(),
+ matrices = [ ];
+
+ // return if no argument was given or
+ // an argument does not match transform attribute regexp
+ if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
+ return matrix;
+ }
+
+ attributeValue.replace(reTransform, function(match) {
+
+ var m = new RegExp(transform).exec(match).filter(function (match) {
+ return (match !== '' && match != null);
+ }),
+ operation = m[1],
+ args = m.slice(2).map(parseFloat);
+
+ switch (operation) {
+ case 'translate':
+ translateMatrix(matrix, args);
+ break;
+ case 'rotate':
+ args[0] = fabric.util.degreesToRadians(args[0]);
+ rotateMatrix(matrix, args);
+ break;
+ case 'scale':
+ scaleMatrix(matrix, args);
+ break;
+ case 'skewX':
+ skewXMatrix(matrix, args);
+ break;
+ case 'skewY':
+ skewYMatrix(matrix, args);
+ break;
+ case 'matrix':
+ matrix = args;
+ break;
+ }
+
+ // snapshot current matrix into matrices array
+ matrices.push(matrix.concat());
+ // reset
+ matrix = iMatrix.concat();
+ });
+
+ var combinedMatrix = matrices[0];
+ while (matrices.length > 1) {
+ matrices.shift();
+ combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
+ }
+ return combinedMatrix;
+ };
+ })();
+
+ function parseFontDeclaration(value, oStyle) {
+
+ // TODO: support non-px font size
+ var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);
+
+ if (!match) {
+ return;
+ }
+
+ var fontStyle = match[1],
+ // font variant is not used
+ // fontVariant = match[2],
+ fontWeight = match[3],
+ fontSize = match[4],
+ lineHeight = match[5],
+ fontFamily = match[6];
+
+ if (fontStyle) {
+ oStyle.fontStyle = fontStyle;
+ }
+ if (fontWeight) {
+ oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
+ }
+ if (fontSize) {
+ oStyle.fontSize = parseFloat(fontSize);
+ }
+ if (fontFamily) {
+ oStyle.fontFamily = fontFamily;
+ }
+ if (lineHeight) {
+ oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
+ }
+ }
+
+ /**
+ * @private
+ */
+ function parseStyleString(style, oStyle) {
+ var attr, value;
+ style.replace(/;$/, '').split(';').forEach(function (chunk) {
+ var pair = chunk.split(':');
+
+ attr = normalizeAttr(pair[0].trim().toLowerCase());
+ value = normalizeValue(attr, pair[1].trim());
+
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
+ }
+ else {
+ oStyle[attr] = value;
+ }
+ });
+ }
+
+ /**
+ * @private
+ */
+ function parseStyleObject(style, oStyle) {
+ var attr, value;
+ for (var prop in style) {
+ if (typeof style[prop] === 'undefined') {
+ continue;
+ }
+
+ attr = normalizeAttr(prop.toLowerCase());
+ value = normalizeValue(attr, style[prop]);
+
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
+ }
+ else {
+ oStyle[attr] = value;
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ function getGlobalStylesForElement(element) {
+ var styles = { };
+
+ for (var rule in fabric.cssRules) {
+ if (elementMatchesRule(element, rule.split(' '))) {
+ for (var property in fabric.cssRules[rule]) {
+ styles[property] = fabric.cssRules[rule][property];
+ }
+ }
+ }
+ return styles;
+ }
+
+ /**
+ * @private
+ */
+ function elementMatchesRule(element, selectors) {
+ var firstMatching, parentMatching = true;
+ //start from rightmost selector.
+ firstMatching = selectorMatches(element, selectors.pop());
+ if (firstMatching && selectors.length) {
+ parentMatching = doesSomeParentMatch(element, selectors);
+ }
+ return firstMatching && parentMatching && (selectors.length === 0);
+ }
+
+ function doesSomeParentMatch(element, selectors) {
+ var selector, parentMatching = true;
+ while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
+ if (parentMatching) {
+ selector = selectors.pop();
+ }
+ element = element.parentNode;
+ parentMatching = selectorMatches(element, selector);
+ }
+ return selectors.length === 0;
+ }
+ /**
+ * @private
+ */
+ function selectorMatches(element, selector) {
+ var nodeName = element.nodeName,
+ classNames = element.getAttribute('class'),
+ id = element.getAttribute('id'), matcher;
+ // i check if a selector matches slicing away part from it.
+ // if i get empty string i should match
+ matcher = new RegExp('^' + nodeName, 'i');
+ selector = selector.replace(matcher, '');
+ if (id && selector.length) {
+ matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
+ selector = selector.replace(matcher, '');
+ }
+ if (classNames && selector.length) {
+ classNames = classNames.split(' ');
+ for (var i = classNames.length; i--;) {
+ matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
+ selector = selector.replace(matcher, '');
+ }
+ }
+ return selector.length === 0;
+ }
+
+ /**
+ * @private
+ */
+ function parseUseDirectives(doc) {
+ var nodelist = doc.getElementsByTagName('use');
+ while (nodelist.length) {
+ var el = nodelist[0],
+ xlink = el.getAttribute('xlink:href').substr(1),
+ x = el.getAttribute('x') || 0,
+ y = el.getAttribute('y') || 0,
+ el2 = doc.getElementById(xlink).cloneNode(true),
+ currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
+ parentNode;
+
+ for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
+ var attr = attrs.item(j);
+ if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
+ continue;
+ }
+
+ if (attr.nodeName === 'transform') {
+ currentTrans = currentTrans + ' ' + attr.nodeValue;
+ }
+ else {
+ el2.setAttribute(attr.nodeName, attr.nodeValue);
+ }
+ }
+
+ el2.setAttribute('transform', currentTrans);
+ el2.removeAttribute('id');
+ parentNode = el.parentNode;
+ parentNode.replaceChild(el2, el);
+ }
+ }
+
+ /**
+ * Add a element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements
+ */
+ function addSvgTransform(doc, matrix) {
+ matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]);
+ if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) {
+ return;
+ }
+ // default is to preserve aspect ratio
+ // preserveAspectRatio attribute to be implemented
+ var el = doc.ownerDocument.createElement('g');
+ while (doc.firstChild != null) {
+ el.appendChild(doc.firstChild);
+ }
+ el.setAttribute('transform','matrix(' + matrix[0] + ' ' + matrix[1] + ' ' + matrix[2] + ' ' + matrix[3] + ' ' + matrix[4] + ' ' + matrix[5] + ')');
+ doc.appendChild(el);
+ }
+
+ /**
+ * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ fabric.parseSVGDocument = (function() {
+
+ var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
+
+ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ // \d doesn't quite cut it (as we need to match an actual float number)
+
+ // matches, e.g.: +14.56e-12, etc.
+ reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)',
+
+ reViewBoxAttrValue = new RegExp(
+ '^' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*' +
+ '$'
+ );
+
+ function hasAncestorWithNodeName(element, nodeName) {
+ while (element && (element = element.parentNode)) {
+ if (nodeName.test(element.nodeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return function(doc, callback, reviver) {
+ if (!doc) {
+ return;
+ }
+ var startTime = new Date();
+
+ parseUseDirectives(doc);
+ /* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute
+ * as per spec, width and height attributes are to be considered
+ * 100% if no value is specified.
+ */
+ var viewBoxAttr = doc.getAttribute('viewBox'),
+ widthAttr = parseUnit(doc.getAttribute('width') || '100%'),
+ heightAttr = parseUnit(doc.getAttribute('height') || '100%'),
+ viewBoxWidth,
+ viewBoxHeight;
+
+ if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
+ var minX = parseFloat(viewBoxAttr[1]),
+ minY = parseFloat(viewBoxAttr[2]),
+ scaleX = 1, scaleY = 1;
+ viewBoxWidth = parseFloat(viewBoxAttr[3]);
+ viewBoxHeight = parseFloat(viewBoxAttr[4]);
+ if (widthAttr && widthAttr !== viewBoxWidth ) {
+ scaleX = widthAttr / viewBoxWidth;
+ }
+ if (heightAttr && heightAttr !== viewBoxHeight) {
+ scaleY = heightAttr / viewBoxHeight;
+ }
+ addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]);
+ }
+
+ var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
+
+ if (descendants.length === 0 && fabric.isLikelyNode) {
+ // we're likely in node, where "o3-xml" library fails to gEBTN("*")
+ // https://github.com/ajaxorg/node-o3-xml/issues/21
+ descendants = doc.selectNodes('//*[name(.)!="svg"]');
+ var arr = [ ];
+ for (var i = 0, len = descendants.length; i < len; i++) {
+ arr[i] = descendants[i];
+ }
+ descendants = arr;
+ }
+
+ var elements = descendants.filter(function(el) {
+ return reAllowedSVGTagNames.test(el.tagName) &&
+ !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
+ });
+
+ if (!elements || (elements && !elements.length)) {
+ callback && callback([], {});
+ return;
+ }
+
+ var options = {
+ width: widthAttr ? widthAttr : viewBoxWidth,
+ height: heightAttr ? heightAttr : viewBoxHeight,
+ widthAttr: widthAttr,
+ heightAttr: heightAttr
+ };
+
+ fabric.gradientDefs = fabric.getGradientDefs(doc);
+ fabric.cssRules = fabric.getCSSRules(doc);
+ // Precedence of rules: style > class > attribute
+
+ fabric.parseElements(elements, function(instances) {
+ fabric.documentParsingTime = new Date() - startTime;
+ if (callback) {
+ callback(instances, options);
+ }
+ }, clone(options), reviver);
+ };
+ })();
+
+ /**
+ * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
+ * @namespace
+ */
+ var svgCache = {
+
+ /**
+ * @param {String} name
+ * @param {Function} callback
+ */
+ has: function (name, callback) {
+ callback(false);
+ },
+
+ get: function () {
+ /* NOOP */
+ },
+
+ set: function () {
+ /* NOOP */
+ }
+ };
+
+ /**
+ * @private
+ */
+ function _enlivenCachedObject(cachedObject) {
+
+ var objects = cachedObject.objects,
+ options = cachedObject.options;
+
+ objects = objects.map(function (o) {
+ return fabric[capitalize(o.type)].fromObject(o);
+ });
+
+ return ({ objects: objects, options: options });
+ }
+
+ /**
+ * @private
+ */
+ function _createSVGPattern(markup, canvas, property) {
+ if (canvas[property] && canvas[property].toSVG) {
+ markup.push(
+ '',
+ ''
+ );
+ }
+ }
+
+ extend(fabric, {
+
+ /**
+ * Parses an SVG document, returning all of the gradient declarations found in it
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
+ */
+ getGradientDefs: function(doc) {
+ var linearGradientEls = doc.getElementsByTagName('linearGradient'),
+ radialGradientEls = doc.getElementsByTagName('radialGradient'),
+ el, i, j = 0, id, xlink, elList = [ ],
+ gradientDefs = { }, idsToXlinkMap = { };
+
+ elList.length = linearGradientEls.length + radialGradientEls.length;
+ i = linearGradientEls.length;
+ while (i--) {
+ elList[j++] = linearGradientEls[i];
+ }
+ i = radialGradientEls.length;
+ while (i--) {
+ elList[j++] = radialGradientEls[i];
+ }
+
+ while (j--) {
+ el = elList[j];
+ xlink = el.getAttribute('xlink:href');
+ id = el.getAttribute('id');
+ if (xlink) {
+ idsToXlinkMap[id] = xlink.substr(1);
+ }
+ gradientDefs[id] = el;
+ }
+
+ for (id in idsToXlinkMap) {
+ var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
+ el = gradientDefs[id];
+ while (el2.firstChild) {
+ el.appendChild(el2.firstChild);
+ }
+ }
+ return gradientDefs;
+ },
+
+ /**
+ * Returns an object of attributes' name/value, given element and an array of attribute names;
+ * Parses parent "g" nodes recursively upwards.
+ * @static
+ * @memberOf fabric
+ * @param {DOMElement} element Element to parse
+ * @param {Array} attributes Array of attributes to parse
+ * @return {Object} object containing parsed attributes' names/values
+ */
+ parseAttributes: function(element, attributes) {
+
+ if (!element) {
+ return;
+ }
+
+ var value,
+ parentAttributes = { };
+
+ // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
+ if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {
+ parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
+ }
+
+ var ownAttributes = attributes.reduce(function(memo, attr) {
+ value = element.getAttribute(attr);
+ if (value) {
+ attr = normalizeAttr(attr);
+ value = normalizeValue(attr, value, parentAttributes);
+
+ memo[attr] = value;
+ }
+ return memo;
+ }, { });
+
+ // add values parsed from style, which take precedence over attributes
+ // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
+ ownAttributes = extend(ownAttributes,
+ extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
+
+ return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
+ },
+
+ /**
+ * Transforms an array of svg elements to corresponding fabric.* instances
+ * @static
+ * @memberOf fabric
+ * @param {Array} elements Array of elements to parse
+ * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
+ * @param {Object} [options] Options object
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ parseElements: function(elements, callback, options, reviver) {
+ new fabric.ElementsParser(elements, callback, options, reviver).parse();
+ },
+
+ /**
+ * Parses "style" attribute, retuning an object with values
+ * @static
+ * @memberOf fabric
+ * @param {SVGElement} element Element to parse
+ * @return {Object} Objects with values parsed from style attribute of an element
+ */
+ parseStyleAttribute: function(element) {
+ var oStyle = { },
+ style = element.getAttribute('style');
+
+ if (!style) {
+ return oStyle;
+ }
+
+ if (typeof style === 'string') {
+ parseStyleString(style, oStyle);
+ }
+ else {
+ parseStyleObject(style, oStyle);
+ }
+
+ return oStyle;
+ },
+
+ /**
+ * Parses "points" attribute, returning an array of values
+ * @static
+ * @memberOf fabric
+ * @param {String} points points attribute string
+ * @return {Array} array of points
+ */
+ parsePointsAttribute: function(points) {
+
+ // points attribute is required and must not be empty
+ if (!points) {
+ return null;
+ }
+
+ // replace commas with whitespace and remove bookending whitespace
+ points = points.replace(/,/g, ' ').trim();
+
+ points = points.split(/\s+/);
+ var parsedPoints = [ ], i, len;
+
+ i = 0;
+ len = points.length;
+ for (; i < len; i+=2) {
+ parsedPoints.push({
+ x: parseFloat(points[i]),
+ y: parseFloat(points[i + 1])
+ });
+ }
+
+ // odd number of points is an error
+ // if (parsedPoints.length % 2 !== 0) {
+ // return null;
+ // }
+
+ return parsedPoints;
+ },
+
+ /**
+ * Returns CSS rules for a given SVG document
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} CSS rules of this document
+ */
+ getCSSRules: function(doc) {
+ var styles = doc.getElementsByTagName('style'),
+ allRules = { }, rules;
+
+ // very crude parsing of style contents
+ for (var i = 0, len = styles.length; i < len; i++) {
+ var styleContents = styles[0].textContent;
+
+ // remove comments
+ styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
+
+ rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
+ rules = rules.map(function(rule) { return rule.trim(); });
+
+ rules.forEach(function(rule) {
+
+ var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
+ ruleObj = { }, declaration = match[2].trim(),
+ propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+
+ for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/),
+ property = normalizeAttr(pair[0]),
+ value = normalizeValue(property,pair[1],pair[0]);
+ ruleObj[property] = value;
+ }
+ rule = match[1];
+ rule.split(',').forEach(function(_rule) {
+ allRules[_rule.trim()] = fabric.util.object.clone(ruleObj);
+ });
+ });
+ }
+ return allRules;
+ },
+
+ /**
+ * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
+ * @memberof fabric
+ * @param {String} url
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromURL: function(url, callback, reviver) {
+
+ url = url.replace(/^\n\s*/, '').trim();
+ svgCache.has(url, function (hasUrl) {
+ if (hasUrl) {
+ svgCache.get(url, function (value) {
+ var enlivedRecord = _enlivenCachedObject(value);
+ callback(enlivedRecord.objects, enlivedRecord.options);
+ });
+ }
+ else {
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
+ });
+ }
+ });
+
+ function onComplete(r) {
+
+ var xml = r.responseXML;
+ if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.async = 'false';
+ //IE chokes on DOCTYPE
+ xml.loadXML(r.responseText.replace(//i,''));
+ }
+ if (!xml || !xml.documentElement) {
+ return;
+ }
+
+ fabric.parseSVGDocument(xml.documentElement, function (results, options) {
+ svgCache.set(url, {
+ objects: fabric.util.array.invoke(results, 'toObject'),
+ options: options
+ });
+ callback(results, options);
+ }, reviver);
+ }
+ },
+
+ /**
+ * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
+ * @memberof fabric
+ * @param {String} string
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromString: function(string, callback, reviver) {
+ string = string.trim();
+ var doc;
+ if (typeof DOMParser !== 'undefined') {
+ var parser = new DOMParser();
+ if (parser && parser.parseFromString) {
+ doc = parser.parseFromString(string, 'text/xml');
+ }
+ }
+ else if (fabric.window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ //IE chokes on DOCTYPE
+ doc.loadXML(string.replace(//i,''));
+ }
+
+ fabric.parseSVGDocument(doc.documentElement, function (results, options) {
+ callback(results, options);
+ }, reviver);
+ },
+
+ /**
+ * Creates markup containing SVG font faces
+ * @param {Array} objects Array of fabric objects
+ * @return {String}
+ */
+ createSVGFontFacesMarkup: function(objects) {
+ var markup = '';
+
+ for (var i = 0, len = objects.length; i < len; i++) {
+ if (objects[i].type !== 'text' || !objects[i].path) {
+ continue;
+ }
+
+ markup += [
+ //jscs:disable validateIndentation
+ '@font-face {',
+ 'font-family: ', objects[i].fontFamily, '; ',
+ 'src: url(\'', objects[i].path, '\')',
+ '}'
+ //jscs:enable validateIndentation
+ ].join('');
+ }
+
+ if (markup) {
+ markup = [
+ //jscs:disable validateIndentation
+ ''
+ //jscs:enable validateIndentation
+ ].join('');
+ }
+
+ return markup;
+ },
+
+ /**
+ * Creates markup containing SVG referenced elements like patterns, gradients etc.
+ * @param {fabric.Canvas} canvas instance of fabric.Canvas
+ * @return {String}
+ */
+ createSVGRefElementsMarkup: function(canvas) {
+ var markup = [ ];
+
+ _createSVGPattern(markup, canvas, 'backgroundColor');
+ _createSVGPattern(markup, canvas, 'overlayColor');
+
+ return markup.join('');
+ }
+ });
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+fabric.ElementsParser = function(elements, callback, options, reviver) {
+ this.elements = elements;
+ this.callback = callback;
+ this.options = options;
+ this.reviver = reviver;
+};
+
+fabric.ElementsParser.prototype.parse = function() {
+ this.instances = new Array(this.elements.length);
+ this.numElements = this.elements.length;
+
+ this.createObjects();
+};
+
+fabric.ElementsParser.prototype.createObjects = function() {
+ for (var i = 0, len = this.elements.length; i < len; i++) {
+ (function(_this, i) {
+ setTimeout(function() {
+ _this.createObject(_this.elements[i], i);
+ }, 0);
+ })(this, i);
+ }
+};
+
+fabric.ElementsParser.prototype.createObject = function(el, index) {
+ var klass = fabric[fabric.util.string.capitalize(el.tagName)];
+ if (klass && klass.fromElement) {
+ try {
+ this._createObject(klass, el, index);
+ }
+ catch (err) {
+ fabric.log(err);
+ }
+ }
+ else {
+ this.checkIfDone();
+ }
+};
+
+fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
+ if (klass.async) {
+ klass.fromElement(el, this.createCallback(index, el), this.options);
+ }
+ else {
+ var obj = klass.fromElement(el, this.options);
+ this.resolveGradient(obj, 'fill');
+ this.resolveGradient(obj, 'stroke');
+ this.reviver && this.reviver(el, obj);
+ this.instances[index] = obj;
+ this.checkIfDone();
+ }
+};
+
+fabric.ElementsParser.prototype.createCallback = function(index, el) {
+ var _this = this;
+ return function(obj) {
+ _this.resolveGradient(obj, 'fill');
+ _this.resolveGradient(obj, 'stroke');
+ _this.reviver && _this.reviver(el, obj);
+ _this.instances[index] = obj;
+ _this.checkIfDone();
+ };
+};
+
+fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
+
+ var instanceFillValue = obj.get(property);
+ if (!(/^url\(/).test(instanceFillValue)) {
+ return;
+ }
+ var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
+ if (fabric.gradientDefs[gradientId]) {
+ obj.set(property,
+ fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], obj));
+ }
+};
+
+fabric.ElementsParser.prototype.checkIfDone = function() {
+ if (--this.numElements === 0) {
+ this.instances = this.instances.filter(function(el) {
+ return el != null;
+ });
+ this.callback(this.instances);
+ }
+};
+
+
+(function(global) {
+
+ 'use strict';
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Point) {
+ fabric.warn('fabric.Point is already defined');
+ return;
+ }
+
+ fabric.Point = Point;
+
+ /**
+ * Point class
+ * @class fabric.Point
+ * @memberOf fabric
+ * @constructor
+ * @param {Number} x
+ * @param {Number} y
+ * @return {fabric.Point} thisArg
+ */
+ function Point(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ Point.prototype = /** @lends fabric.Point.prototype */ {
+
+ constructor: Point,
+
+ /**
+ * Adds another point to this one and returns another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} new Point instance with added values
+ */
+ add: function (that) {
+ return new Point(this.x + that.x, this.y + that.y);
+ },
+
+ /**
+ * Adds another point to this one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ addEquals: function (that) {
+ this.x += that.x;
+ this.y += that.y;
+ return this;
+ },
+
+ /**
+ * Adds value to this point and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point} new Point with added value
+ */
+ scalarAdd: function (scalar) {
+ return new Point(this.x + scalar, this.y + scalar);
+ },
+
+ /**
+ * Adds value to this point
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ scalarAddEquals: function (scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
+ },
+
+ /**
+ * Subtracts another point from this point and returns a new one
+ * @param {fabric.Point} that
+ * @return {fabric.Point} new Point object with subtracted values
+ */
+ subtract: function (that) {
+ return new Point(this.x - that.x, this.y - that.y);
+ },
+
+ /**
+ * Subtracts another point from this point
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ subtractEquals: function (that) {
+ this.x -= that.x;
+ this.y -= that.y;
+ return this;
+ },
+
+ /**
+ * Subtracts value from this point and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ scalarSubtract: function (scalar) {
+ return new Point(this.x - scalar, this.y - scalar);
+ },
+
+ /**
+ * Subtracts value from this point
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ scalarSubtractEquals: function (scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
+ },
+
+ /**
+ * Miltiplies this point by a value and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ multiply: function (scalar) {
+ return new Point(this.x * scalar, this.y * scalar);
+ },
+
+ /**
+ * Miltiplies this point by a value
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ multiplyEquals: function (scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+
+ /**
+ * Divides this point by a value and returns a new one
+ * @param {Number} scalar
+ * @return {fabric.Point}
+ */
+ divide: function (scalar) {
+ return new Point(this.x / scalar, this.y / scalar);
+ },
+
+ /**
+ * Divides this point by a value
+ * @param {Number} scalar
+ * @return {fabric.Point} thisArg
+ */
+ divideEquals: function (scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+
+ /**
+ * Returns true if this point is equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ eq: function (that) {
+ return (this.x === that.x && this.y === that.y);
+ },
+
+ /**
+ * Returns true if this point is less than another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ lt: function (that) {
+ return (this.x < that.x && this.y < that.y);
+ },
+
+ /**
+ * Returns true if this point is less than or equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ lte: function (that) {
+ return (this.x <= that.x && this.y <= that.y);
+ },
+
+ /**
+
+ * Returns true if this point is greater another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ gt: function (that) {
+ return (this.x > that.x && this.y > that.y);
+ },
+
+ /**
+ * Returns true if this point is greater than or equal to another one
+ * @param {fabric.Point} that
+ * @return {Boolean}
+ */
+ gte: function (that) {
+ return (this.x >= that.x && this.y >= that.y);
+ },
+
+ /**
+ * Returns new point which is the result of linear interpolation with this one and another one
+ * @param {fabric.Point} that
+ * @param {Number} t
+ * @return {fabric.Point}
+ */
+ lerp: function (that, t) {
+ return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
+ },
+
+ /**
+ * Returns distance from this point and another one
+ * @param {fabric.Point} that
+ * @return {Number}
+ */
+ distanceFrom: function (that) {
+ var dx = this.x - that.x,
+ dy = this.y - that.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ /**
+ * Returns the point between this point and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ midPointFrom: function (that) {
+ return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
+ },
+
+ /**
+ * Returns a new point which is the min of this and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ min: function (that) {
+ return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
+ },
+
+ /**
+ * Returns a new point which is the max of this and another one
+ * @param {fabric.Point} that
+ * @return {fabric.Point}
+ */
+ max: function (that) {
+ return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
+ },
+
+ /**
+ * Returns string representation of this point
+ * @return {String}
+ */
+ toString: function () {
+ return this.x + ',' + this.y;
+ },
+
+ /**
+ * Sets x/y of this point
+ * @param {Number} x
+ * @return {Number} y
+ */
+ setXY: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+
+ /**
+ * Sets x/y of this point from another point
+ * @param {fabric.Point} that
+ */
+ setFromPoint: function (that) {
+ this.x = that.x;
+ this.y = that.y;
+ },
+
+ /**
+ * Swaps x/y of this point and another point
+ * @param {fabric.Point} that
+ */
+ swap: function (that) {
+ var x = this.x,
+ y = this.y;
+ this.x = that.x;
+ this.y = that.y;
+ that.x = x;
+ that.y = y;
+ }
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function(global) {
+
+ 'use strict';
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Intersection) {
+ fabric.warn('fabric.Intersection is already defined');
+ return;
+ }
+
+ /**
+ * Intersection class
+ * @class fabric.Intersection
+ * @memberOf fabric
+ * @constructor
+ */
+ function Intersection(status) {
+ this.status = status;
+ this.points = [];
+ }
+
+ fabric.Intersection = Intersection;
+
+ fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
+
+ /**
+ * Appends a point to intersection
+ * @param {fabric.Point} point
+ */
+ appendPoint: function (point) {
+ this.points.push(point);
+ },
+
+ /**
+ * Appends points to intersection
+ * @param {Array} points
+ */
+ appendPoints: function (points) {
+ this.points = this.points.concat(points);
+ }
+ };
+
+ /**
+ * Checks if one line intersects another
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {fabric.Point} b1
+ * @param {fabric.Point} b2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
+ var result,
+ uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
+ ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
+ uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (uB !== 0) {
+ var ua = uaT / uB,
+ ub = ubT / uB;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection('Intersection');
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection();
+ }
+ }
+ else {
+ if (uaT === 0 || ubT === 0) {
+ result = new Intersection('Coincident');
+ }
+ else {
+ result = new Intersection('Parallel');
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Checks if line intersects polygon
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {Array} points
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection(),
+ length = points.length;
+
+ for (var i = 0; i < length; i++) {
+ var b1 = points[i],
+ b2 = points[(i + 1) % length],
+ inter = Intersection.intersectLineLine(a1, a2, b1, b2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = 'Intersection';
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects another polygon
+ * @static
+ * @param {Array} points1
+ * @param {Array} points2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection(),
+ length = points1.length;
+
+ for (var i = 0; i < length; i++) {
+ var a1 = points1[i],
+ a2 = points1[(i + 1) % length],
+ inter = Intersection.intersectLinePolygon(a1, a2, points2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = 'Intersection';
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects rectangle
+ * @static
+ * @param {Array} points
+ * @param {Number} r1
+ * @param {Number} r2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
+ var min = r1.min(r2),
+ max = r1.max(r2),
+ topRight = new fabric.Point(max.x, min.y),
+ bottomLeft = new fabric.Point(min.x, max.y),
+ inter1 = Intersection.intersectLinePolygon(min, topRight, points),
+ inter2 = Intersection.intersectLinePolygon(topRight, max, points),
+ inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
+ inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
+ result = new Intersection();
+
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+
+ if (result.points.length > 0) {
+ result.status = 'Intersection';
+ }
+ return result;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function(global) {
+
+ 'use strict';
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Color) {
+ fabric.warn('fabric.Color is already defined.');
+ return;
+ }
+
+ /**
+ * Color class
+ * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
+ * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
+ *
+ * @class fabric.Color
+ * @param {String} color optional in hex or rgb(a) format
+ * @return {fabric.Color} thisArg
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
+ */
+ function Color(color) {
+ if (!color) {
+ this.setSource([0, 0, 0, 1]);
+ }
+ else {
+ this._tryParsingColor(color);
+ }
+ }
+
+ fabric.Color = Color;
+
+ fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
+
+ /**
+ * @private
+ * @param {String|Array} color Color value to parse
+ */
+ _tryParsingColor: function(color) {
+ var source;
+
+ if (color in Color.colorNameMap) {
+ color = Color.colorNameMap[color];
+ }
+
+ if (color === 'transparent') {
+ this.setSource([255,255,255,0]);
+ return;
+ }
+
+ source = Color.sourceFromHex(color);
+
+ if (!source) {
+ source = Color.sourceFromRgb(color);
+ }
+ if (!source) {
+ source = Color.sourceFromHsl(color);
+ }
+ if (source) {
+ this.setSource(source);
+ }
+ },
+
+ /**
+ * Adapted from https://github.com/mjijackson
+ * @private
+ * @param {Number} r Red color value
+ * @param {Number} g Green color value
+ * @param {Number} b Blue color value
+ * @return {Array} Hsl color
+ */
+ _rgbToHsl: function(r, g, b) {
+ r /= 255, g /= 255, b /= 255;
+
+ var h, s, l,
+ max = fabric.util.array.max([r, g, b]),
+ min = fabric.util.array.min([r, g, b]);
+
+ l = (max + min) / 2;
+
+ if (max === min) {
+ h = s = 0; // achromatic
+ }
+ else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+
+ return [
+ Math.round(h * 360),
+ Math.round(s * 100),
+ Math.round(l * 100)
+ ];
+ },
+
+ /**
+ * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @return {Array}
+ */
+ getSource: function() {
+ return this._source;
+ },
+
+ /**
+ * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @param {Array} source
+ */
+ setSource: function(source) {
+ this._source = source;
+ },
+
+ /**
+ * Returns color represenation in RGB format
+ * @return {String} ex: rgb(0-255,0-255,0-255)
+ */
+ toRgb: function() {
+ var source = this.getSource();
+ return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
+ },
+
+ /**
+ * Returns color represenation in RGBA format
+ * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
+ */
+ toRgba: function() {
+ var source = this.getSource();
+ return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
+ },
+
+ /**
+ * Returns color represenation in HSL format
+ * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
+ */
+ toHsl: function() {
+ var source = this.getSource(),
+ hsl = this._rgbToHsl(source[0], source[1], source[2]);
+
+ return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
+ },
+
+ /**
+ * Returns color represenation in HSLA format
+ * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
+ */
+ toHsla: function() {
+ var source = this.getSource(),
+ hsl = this._rgbToHsl(source[0], source[1], source[2]);
+
+ return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
+ },
+
+ /**
+ * Returns color represenation in HEX format
+ * @return {String} ex: FF5555
+ */
+ toHex: function() {
+ var source = this.getSource(), r, g, b;
+
+ r = source[0].toString(16);
+ r = (r.length === 1) ? ('0' + r) : r;
+
+ g = source[1].toString(16);
+ g = (g.length === 1) ? ('0' + g) : g;
+
+ b = source[2].toString(16);
+ b = (b.length === 1) ? ('0' + b) : b;
+
+ return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
+ },
+
+ /**
+ * Gets value of alpha channel for this color
+ * @return {Number} 0-1
+ */
+ getAlpha: function() {
+ return this.getSource()[3];
+ },
+
+ /**
+ * Sets value of alpha channel for this color
+ * @param {Number} alpha Alpha value 0-1
+ * @return {fabric.Color} thisArg
+ */
+ setAlpha: function(alpha) {
+ var source = this.getSource();
+ source[3] = alpha;
+ this.setSource(source);
+ return this;
+ },
+
+ /**
+ * Transforms color to its grayscale representation
+ * @return {fabric.Color} thisArg
+ */
+ toGrayscale: function() {
+ var source = this.getSource(),
+ average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
+ currentAlpha = source[3];
+ this.setSource([average, average, average, currentAlpha]);
+ return this;
+ },
+
+ /**
+ * Transforms color to its black and white representation
+ * @param {Number} threshold
+ * @return {fabric.Color} thisArg
+ */
+ toBlackWhite: function(threshold) {
+ var source = this.getSource(),
+ average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
+ currentAlpha = source[3];
+
+ threshold = threshold || 127;
+
+ average = (Number(average) < Number(threshold)) ? 0 : 255;
+ this.setSource([average, average, average, currentAlpha]);
+ return this;
+ },
+
+ /**
+ * Overlays color with another color
+ * @param {String|fabric.Color} otherColor
+ * @return {fabric.Color} thisArg
+ */
+ overlayWith: function(otherColor) {
+ if (!(otherColor instanceof Color)) {
+ otherColor = new Color(otherColor);
+ }
+
+ var result = [],
+ alpha = this.getAlpha(),
+ otherAlpha = 0.5,
+ source = this.getSource(),
+ otherSource = otherColor.getSource();
+
+ for (var i = 0; i < 3; i++) {
+ result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
+ }
+
+ result[3] = alpha;
+ this.setSource(result);
+ return this;
+ }
+ };
+
+ /**
+ * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+
+ /**
+ * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
+
+ /**
+ * Regex matching color in HEX format (ex: #FF5555, 010155, aff)
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ */
+ fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
+
+ /**
+ * Map of the 17 basic color names with HEX code
+ * @static
+ * @field
+ * @memberOf fabric.Color
+ * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
+ */
+ fabric.Color.colorNameMap = {
+ aqua: '#00FFFF',
+ black: '#000000',
+ blue: '#0000FF',
+ fuchsia: '#FF00FF',
+ gray: '#808080',
+ green: '#008000',
+ lime: '#00FF00',
+ maroon: '#800000',
+ navy: '#000080',
+ olive: '#808000',
+ orange: '#FFA500',
+ purple: '#800080',
+ red: '#FF0000',
+ silver: '#C0C0C0',
+ teal: '#008080',
+ white: '#FFFFFF',
+ yellow: '#FFFF00'
+ };
+
+ /**
+ * @private
+ * @param {Number} p
+ * @param {Number} q
+ * @param {Number} t
+ * @return {Number}
+ */
+ function hue2rgb(p, q, t){
+ if (t < 0) {
+ t += 1;
+ }
+ if (t > 1) {
+ t -= 1;
+ }
+ if (t < 1/6) {
+ return p + (q - p) * 6 * t;
+ }
+ if (t < 1/2) {
+ return q;
+ }
+ if (t < 2/3) {
+ return p + (q - p) * (2/3 - t) * 6;
+ }
+ return p;
+ }
+
+ /**
+ * Returns new color object, when given a color in RGB format
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgb = function(color) {
+ return Color.fromSource(Color.sourceFromRgb(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromRgb = function(color) {
+ var match = color.match(Color.reRGBa);
+ if (match) {
+ var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
+ g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
+ b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
+
+ return [
+ parseInt(r, 10),
+ parseInt(g, 10),
+ parseInt(b, 10),
+ match[4] ? parseFloat(match[4]) : 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given a color in RGBA format
+ * @static
+ * @function
+ * @memberOf fabric.Color
+ * @param {String} color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgba = Color.fromRgb;
+
+ /**
+ * Returns new color object, when given a color in HSL format
+ * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
+ * @memberOf fabric.Color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHsl = function(color) {
+ return Color.fromSource(Color.sourceFromHsl(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
+ * Adapted from https://github.com/mjijackson
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
+ * @return {Array} source
+ * @see http://http://www.w3.org/TR/css3-color/#hsl-color
+ */
+ fabric.Color.sourceFromHsl = function(color) {
+ var match = color.match(Color.reHSLa);
+ if (!match) {
+ return;
+ }
+
+ var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
+ s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
+ l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
+ r, g, b;
+
+ if (s === 0) {
+ r = g = b = l;
+ }
+ else {
+ var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
+ p = l * 2 - q;
+
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return [
+ Math.round(r * 255),
+ Math.round(g * 255),
+ Math.round(b * 255),
+ match[4] ? parseFloat(match[4]) : 1
+ ];
+ };
+
+ /**
+ * Returns new color object, when given a color in HSLA format
+ * @static
+ * @function
+ * @memberOf fabric.Color
+ * @param {String} color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHsla = Color.fromHsl;
+
+ /**
+ * Returns new color object, when given a color in HEX format
+ * @static
+ * @memberOf fabric.Color
+ * @param {String} color Color value ex: FF5555
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHex = function(color) {
+ return Color.fromSource(Color.sourceFromHex(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
+ * @static
+ * @memberOf fabric.Color
+ * @param {String} color ex: FF5555
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromHex = function(color) {
+ if (color.match(Color.reHex)) {
+ var value = color.slice(color.indexOf('#') + 1),
+ isShortNotation = (value.length === 3),
+ r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
+ g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
+ b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6);
+
+ return [
+ parseInt(r, 16),
+ parseInt(g, 16),
+ parseInt(b, 16),
+ 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
+ * @static
+ * @memberOf fabric.Color
+ * @param {Array} source
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromSource = function(source) {
+ var oColor = new Color();
+ oColor.setSource(source);
+ return oColor;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function() {
+
+ /* _FROM_SVG_START_ */
+ function getColorStop(el) {
+ var style = el.getAttribute('style'),
+ offset = el.getAttribute('offset'),
+ color, colorAlpha, opacity;
+
+ // convert percents to absolute values
+ offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
+ offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
+ if (style) {
+ var keyValuePairs = style.split(/\s*;\s*/);
+
+ if (keyValuePairs[keyValuePairs.length - 1] === '') {
+ keyValuePairs.pop();
+ }
+
+ for (var i = keyValuePairs.length; i--; ) {
+
+ var split = keyValuePairs[i].split(/\s*:\s*/),
+ key = split[0].trim(),
+ value = split[1].trim();
+
+ if (key === 'stop-color') {
+ color = value;
+ }
+ else if (key === 'stop-opacity') {
+ opacity = value;
+ }
+ }
+ }
+
+ if (!color) {
+ color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
+ }
+ if (!opacity) {
+ opacity = el.getAttribute('stop-opacity');
+ }
+
+ color = new fabric.Color(color);
+ colorAlpha = color.getAlpha();
+ opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
+ opacity *= colorAlpha;
+
+ return {
+ offset: offset,
+ color: color.toRgb(),
+ opacity: opacity
+ };
+ }
+
+ function getLinearCoords(el) {
+ return {
+ x1: el.getAttribute('x1') || 0,
+ y1: el.getAttribute('y1') || 0,
+ x2: el.getAttribute('x2') || '100%',
+ y2: el.getAttribute('y2') || 0
+ };
+ }
+
+ function getRadialCoords(el) {
+ return {
+ x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
+ y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
+ r1: 0,
+ x2: el.getAttribute('cx') || '50%',
+ y2: el.getAttribute('cy') || '50%',
+ r2: el.getAttribute('r') || '50%'
+ };
+ }
+ /* _FROM_SVG_END_ */
+
+ /**
+ * Gradient class
+ * @class fabric.Gradient
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}
+ * @see {@link fabric.Gradient#initialize} for constructor definition
+ */
+ fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
+
+ /**
+ * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
+ * @type Number
+ * @default 0
+ */
+ offsetX: 0,
+
+ /**
+ * Vertical offset for aligning gradients coming from SVG when outside pathgroups
+ * @type Number
+ * @default 0
+ */
+ offsetY: 0,
+
+ /**
+ * Constructor
+ * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
+ * @return {fabric.Gradient} thisArg
+ */
+ initialize: function(options) {
+ options || (options = { });
+
+ var coords = { };
+
+ this.id = fabric.Object.__uid++;
+ this.type = options.type || 'linear';
+
+ coords = {
+ x1: options.coords.x1 || 0,
+ y1: options.coords.y1 || 0,
+ x2: options.coords.x2 || 0,
+ y2: options.coords.y2 || 0
+ };
+
+ if (this.type === 'radial') {
+ coords.r1 = options.coords.r1 || 0;
+ coords.r2 = options.coords.r2 || 0;
+ }
+ this.coords = coords;
+ this.colorStops = options.colorStops.slice();
+ if (options.gradientTransform) {
+ this.gradientTransform = options.gradientTransform;
+ }
+ this.offsetX = options.offsetX || this.offsetX;
+ this.offsetY = options.offsetY || this.offsetY;
+ },
+
+ /**
+ * Adds another colorStop
+ * @param {Object} colorStop Object with offset and color
+ * @return {fabric.Gradient} thisArg
+ */
+ addColorStop: function(colorStop) {
+ for (var position in colorStop) {
+ var color = new fabric.Color(colorStop[position]);
+ this.colorStops.push({
+ offset: position,
+ color: color.toRgb(),
+ opacity: color.getAlpha()
+ });
+ }
+ return this;
+ },
+
+ /**
+ * Returns object representation of a gradient
+ * @return {Object}
+ */
+ toObject: function() {
+ return {
+ type: this.type,
+ coords: this.coords,
+ colorStops: this.colorStops,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of an gradient
+ * @param {Object} object Object to create a gradient for
+ * @param {Boolean} normalize Whether coords should be normalized
+ * @return {String} SVG representation of an gradient (linear/radial)
+ */
+ toSVG: function(object) {
+ var coords = fabric.util.object.clone(this.coords),
+ markup, commonAttributes;
+
+ // colorStops must be sorted ascending
+ this.colorStops.sort(function(a, b) {
+ return a.offset - b.offset;
+ });
+
+ if (!(object.group && object.group.type === 'path-group')) {
+ for (var prop in coords) {
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
+ coords[prop] += this.offsetX - object.width / 2;
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ coords[prop] += this.offsetY - object.height / 2;
+ }
+ }
+ }
+
+ commonAttributes = 'id="SVGID_' + this.id +
+ '" gradientUnits="userSpaceOnUse"';
+ if (this.gradientTransform) {
+ commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
+ }
+ if (this.type === 'linear') {
+ markup = [
+ //jscs:disable validateIndentation
+ '\n'
+ //jscs:enable validateIndentation
+ ];
+ }
+ else if (this.type === 'radial') {
+ markup = [
+ //jscs:disable validateIndentation
+ '\n'
+ //jscs:enable validateIndentation
+ ];
+ }
+
+ for (var i = 0; i < this.colorStops.length; i++) {
+ markup.push(
+ //jscs:disable validateIndentation
+ '\n'
+ //jscs:enable validateIndentation
+ );
+ }
+
+ markup.push((this.type === 'linear' ? '\n' : '\n'));
+
+ return markup.join('');
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns an instance of CanvasGradient
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @return {CanvasGradient}
+ */
+ toLive: function(ctx) {
+ var gradient;
+
+ if (!this.type) {
+ return;
+ }
+
+ if (this.type === 'linear') {
+ gradient = ctx.createLinearGradient(
+ this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2);
+ }
+ else if (this.type === 'radial') {
+ gradient = ctx.createRadialGradient(
+ this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2);
+ }
+
+ for (var i = 0, len = this.colorStops.length; i < len; i++) {
+ var color = this.colorStops[i].color,
+ opacity = this.colorStops[i].opacity,
+ offset = this.colorStops[i].offset;
+
+ if (typeof opacity !== 'undefined') {
+ color = new fabric.Color(color).setAlpha(opacity).toRgba();
+ }
+ gradient.addColorStop(parseFloat(offset), color);
+ }
+
+ return gradient;
+ }
+ });
+
+ fabric.util.object.extend(fabric.Gradient, {
+
+ /* _FROM_SVG_START_ */
+ /**
+ * Returns {@link fabric.Gradient} instance from an SVG element
+ * @static
+ * @memberof fabric.Gradient
+ * @param {SVGGradientElement} el SVG gradient element
+ * @param {fabric.Object} instance
+ * @return {fabric.Gradient} Gradient instance
+ * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
+ * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
+ */
+ fromElement: function(el, instance) {
+
+ /**
+ * @example:
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * OR
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+
+ var colorStopEls = el.getElementsByTagName('stop'),
+ type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
+ gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
+ gradientTransform = el.getAttribute('gradientTransform'),
+ colorStops = [],
+ coords = { }, ellipseMatrix;
+
+ if (type === 'linear') {
+ coords = getLinearCoords(el);
+ }
+ else if (type === 'radial') {
+ coords = getRadialCoords(el);
+ }
+
+ for (var i = colorStopEls.length; i--; ) {
+ colorStops.push(getColorStop(colorStopEls[i]));
+ }
+
+ ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
+
+ var gradient = new fabric.Gradient({
+ type: type,
+ coords: coords,
+ colorStops: colorStops,
+ offsetX: -instance.left,
+ offsetY: -instance.top
+ });
+
+ if (gradientTransform || ellipseMatrix !== '') {
+ gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
+ }
+ return gradient;
+ },
+ /* _FROM_SVG_END_ */
+
+ /**
+ * Returns {@link fabric.Gradient} instance from its object representation
+ * @static
+ * @memberof fabric.Gradient
+ * @param {Object} obj
+ * @param {Object} [options] Options object
+ */
+ forObject: function(obj, options) {
+ options || (options = { });
+ _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
+ return new fabric.Gradient(options);
+ }
+ });
+
+ /**
+ * @private
+ */
+ function _convertPercentUnitsToValues(object, options, gradientUnits) {
+ var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
+ for (var prop in options) {
+ propValue = parseFloat(options[prop], 10);
+ if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
+ multFactor = 0.01;
+ }
+ else {
+ multFactor = 1;
+ }
+ if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
+ multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
+ addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
+ addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
+ }
+ options[prop] = propValue * multFactor + addFactor;
+ }
+ if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) {
+ var scaleFactor = object.ry/object.rx;
+ ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
+ if (options.y1) {
+ options.y1 /= scaleFactor;
+ }
+ if (options.y2) {
+ options.y2 /= scaleFactor;
+ }
+ }
+ return ellipseMatrix;
+ }
+})();
+
+
+/**
+ * Pattern class
+ * @class fabric.Pattern
+ * @see {@link http://fabricjs.com/patterns/|Pattern demo}
+ * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}
+ * @see {@link fabric.Pattern#initialize} for constructor definition
+ */
+fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
+
+ /**
+ * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
+ * @type String
+ * @default
+ */
+ repeat: 'repeat',
+
+ /**
+ * Pattern horizontal offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
+
+ /**
+ * Pattern vertical offset from object's left/top corner
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Constructor
+ * @param {Object} [options] Options object
+ * @return {fabric.Pattern} thisArg
+ */
+ initialize: function(options) {
+ options || (options = { });
+
+ this.id = fabric.Object.__uid++;
+
+ if (options.source) {
+ if (typeof options.source === 'string') {
+ // function string
+ if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
+ this.source = new Function(fabric.util.getFunctionBody(options.source));
+ }
+ else {
+ // img src string
+ var _this = this;
+ this.source = fabric.util.createImage();
+ fabric.util.loadImage(options.source, function(img) {
+ _this.source = img;
+ });
+ }
+ }
+ else {
+ // img element
+ this.source = options.source;
+ }
+ }
+ if (options.repeat) {
+ this.repeat = options.repeat;
+ }
+ if (options.offsetX) {
+ this.offsetX = options.offsetX;
+ }
+ if (options.offsetY) {
+ this.offsetY = options.offsetY;
+ }
+ },
+
+ /**
+ * Returns object representation of a pattern
+ * @return {Object} Object representation of a pattern instance
+ */
+ toObject: function() {
+
+ var source;
+
+ // callback
+ if (typeof this.source === 'function') {
+ source = String(this.source);
+ }
+ // element
+ else if (typeof this.source.src === 'string') {
+ source = this.source.src;
+ }
+
+ return {
+ source: source,
+ repeat: this.repeat,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of a pattern
+ * @param {fabric.Object} object
+ * @return {String} SVG representation of a pattern
+ */
+ toSVG: function(object) {
+ var patternSource = typeof this.source === 'function' ? this.source() : this.source,
+ patternWidth = patternSource.width / object.getWidth(),
+ patternHeight = patternSource.height / object.getHeight(),
+ patternImgSrc = '';
+
+ if (patternSource.src) {
+ patternImgSrc = patternSource.src;
+ }
+ else if (patternSource.toDataURL) {
+ patternImgSrc = patternSource.toDataURL();
+ }
+
+ return '' +
+ '' +
+ '';
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns an instance of CanvasPattern
+ * @param {CanvasRenderingContext2D} ctx Context to create pattern
+ * @return {CanvasPattern}
+ */
+ toLive: function(ctx) {
+ var source = typeof this.source === 'function'
+ ? this.source()
+ : this.source;
+
+ // if the image failed to load, return, and allow rest to continue loading
+ if (!source) {
+ return '';
+ }
+
+ // if an image
+ if (typeof source.src !== 'undefined') {
+ if (!source.complete) {
+ return '';
+ }
+ if (source.naturalWidth === 0 || source.naturalHeight === 0) {
+ return '';
+ }
+ }
+ return ctx.createPattern(source, this.repeat);
+ }
+});
+
+
+(function(global) {
+
+ 'use strict';
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Shadow) {
+ fabric.warn('fabric.Shadow is already defined.');
+ return;
+ }
+
+ /**
+ * Shadow class
+ * @class fabric.Shadow
+ * @see {@link http://fabricjs.com/shadows/|Shadow demo}
+ * @see {@link fabric.Shadow#initialize} for constructor definition
+ */
+ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
+
+ /**
+ * Shadow color
+ * @type String
+ * @default
+ */
+ color: 'rgb(0,0,0)',
+
+ /**
+ * Shadow blur
+ * @type Number
+ */
+ blur: 0,
+
+ /**
+ * Shadow horizontal offset
+ * @type Number
+ * @default
+ */
+ offsetX: 0,
+
+ /**
+ * Shadow vertical offset
+ * @type Number
+ * @default
+ */
+ offsetY: 0,
+
+ /**
+ * Whether the shadow should affect stroke operations
+ * @type Boolean
+ * @default
+ */
+ affectStroke: false,
+
+ /**
+ * Indicates whether toObject should include default values
+ * @type Boolean
+ * @default
+ */
+ includeDefaultValues: true,
+
+ /**
+ * Constructor
+ * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
+ * @return {fabric.Shadow} thisArg
+ */
+ initialize: function(options) {
+
+ if (typeof options === 'string') {
+ options = this._parseShadow(options);
+ }
+
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+
+ this.id = fabric.Object.__uid++;
+ },
+
+ /**
+ * @private
+ * @param {String} shadow Shadow value to parse
+ * @return {Object} Shadow object with color, offsetX, offsetY and blur
+ */
+ _parseShadow: function(shadow) {
+ var shadowStr = shadow.trim(),
+ offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ],
+ color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';
+
+ return {
+ color: color.trim(),
+ offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
+ offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
+ blur: parseInt(offsetsAndBlur[3], 10) || 0
+ };
+ },
+
+ /**
+ * Returns a string representation of an instance
+ * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
+ * @return {String} Returns CSS3 text-shadow declaration
+ */
+ toString: function() {
+ return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * Returns SVG representation of a shadow
+ * @param {fabric.Object} object
+ * @return {String} SVG representation of a shadow
+ */
+ toSVG: function(object) {
+ var mode = 'SourceAlpha';
+
+ if (object && (object.fill === this.color || object.stroke === this.color)) {
+ mode = 'SourceGraphic';
+ }
+
+ return (
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '');
+ },
+ /* _TO_SVG_END_ */
+
+ /**
+ * Returns object representation of a shadow
+ * @return {Object} Object representation of a shadow instance
+ */
+ toObject: function() {
+ if (this.includeDefaultValues) {
+ return {
+ color: this.color,
+ blur: this.blur,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ }
+ var obj = { }, proto = fabric.Shadow.prototype;
+ if (this.color !== proto.color) {
+ obj.color = this.color;
+ }
+ if (this.blur !== proto.blur) {
+ obj.blur = this.blur;
+ }
+ if (this.offsetX !== proto.offsetX) {
+ obj.offsetX = this.offsetX;
+ }
+ if (this.offsetY !== proto.offsetY) {
+ obj.offsetY = this.offsetY;
+ }
+ return obj;
+ }
+ });
+
+ /**
+ * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
+ * @static
+ * @field
+ * @memberOf fabric.Shadow
+ */
+ fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
+
+})(typeof exports !== 'undefined' ? exports : this);
+
+
+(function () {
+
+ 'use strict';
+
+ if (fabric.StaticCanvas) {
+ fabric.warn('fabric.StaticCanvas is already defined.');
+ return;
+ }
+
+ // aliases for faster resolution
+ var extend = fabric.util.object.extend,
+ getElementOffset = fabric.util.getElementOffset,
+ removeFromArray = fabric.util.removeFromArray,
+
+ CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
+
+ /**
+ * Static canvas class
+ * @class fabric.StaticCanvas
+ * @mixes fabric.Collection
+ * @mixes fabric.Observable
+ * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo}
+ * @see {@link fabric.StaticCanvas#initialize} for constructor definition
+ * @fires before:render
+ * @fires after:render
+ * @fires canvas:cleared
+ * @fires object:added
+ * @fires object:removed
+ */
+ fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ {
+
+ /**
+ * Constructor
+ * @param {HTMLElement | String} el <canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ * @return {Object} thisArg
+ */
+ initialize: function(el, options) {
+ options || (options = { });
+
+ this._initStatic(el, options);
+ fabric.StaticCanvas.activeInstance = this;
+ },
+
+ /**
+ * Background color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
+ * @type {(String|fabric.Pattern)}
+ * @default
+ */
+ backgroundColor: '',
+
+ /**
+ * Background image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
+ * Backwards incompatibility note: The "backgroundImageOpacity"
+ * and "backgroundImageStretch" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
+ * @type fabric.Image
+ * @default
+ */
+ backgroundImage: null,
+
+ /**
+ * Overlay color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
+ * @since 1.3.9
+ * @type {(String|fabric.Pattern)}
+ * @default
+ */
+ overlayColor: '',
+
+ /**
+ * Overlay image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
+ * Backwards incompatibility note: The "overlayImageLeft"
+ * and "overlayImageTop" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
+ * @type fabric.Image
+ * @default
+ */
+ overlayImage: null,
+
+ /**
+ * Indicates whether toObject/toDatalessObject should include default values
+ * @type Boolean
+ * @default
+ */
+ includeDefaultValues: true,
+
+ /**
+ * Indicates whether objects' state should be saved
+ * @type Boolean
+ * @default
+ */
+ stateful: true,
+
+ /**
+ * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
+ * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
+ * (followed by a manual rendering after addition/deletion)
+ * @type Boolean
+ * @default
+ */
+ renderOnAddRemove: true,
+
+ /**
+ * Function that determines clipping of entire canvas area
+ * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
+ * @type Function
+ * @default
+ */
+ clipTo: null,
+
+ /**
+ * Indicates whether object controls (borders/controls) are rendered above overlay image
+ * @type Boolean
+ * @default
+ */
+ controlsAboveOverlay: false,
+
+ /**
+ * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
+ * @type Boolean
+ * @default
+ */
+ allowTouchScrolling: false,
+
+ /**
+ * Indicates whether this canvas will use image smoothing, this is on by default in browsers
+ * @type Boolean
+ * @default
+ */
+ imageSmoothingEnabled: true,
+
+ /**
+ * The transformation (in the format of Canvas transform) which focuses the viewport
+ * @type Array
+ * @default
+ */
+ viewportTransform: [1, 0, 0, 1, 0, 0],
+
+ /**
+ * Callback; invoked right before object is about to be scaled/rotated
+ */
+ onBeforeScaleRotate: function () {
+ /* NOOP */
+ },
+
+ /**
+ * @private
+ * @param {HTMLElement | String} el <canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ */
+ _initStatic: function(el, options) {
+ this._objects = [];
+
+ this._createLowerCanvas(el);
+ this._initOptions(options);
+ this._setImageSmoothing();
+
+ if (options.overlayImage) {
+ this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundImage) {
+ this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundColor) {
+ this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
+ }
+ if (options.overlayColor) {
+ this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
+ }
+ this.calcOffset();
+ },
+
+ /**
+ * Calculates canvas element offset relative to the document
+ * This method is also attached as "resize" event handler of window
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ calcOffset: function () {
+ this._offset = getElementOffset(this.lowerCanvasEl);
+ return this;
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
+ * @param {Function} callback callback to invoke when image is loaded and set as an overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
+ * @example Normal overlayImage with left/top = 0
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example overlayImage with different properties
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched overlayImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched overlayImage #2 - width/height correspond to canvas width/height
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ */
+ setOverlayImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('overlayImage', image, callback, options);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
+ * @example Normal backgroundImage with left/top = 0
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example backgroundImage with different properties
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ */
+ setBackgroundImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('backgroundImage', image, callback, options);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
+ * @example Normal overlayColor - color value
+ * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor with repeat and offset
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setOverlayColor: function(overlayColor, callback) {
+ return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
+ },
+
+ /**
+ * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
+ * @example Normal backgroundColor - color value
+ * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor with repeat and offset
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setBackgroundColor: function(backgroundColor, callback) {
+ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
+ },
+
+ /**
+ * @private
+ * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}
+ */
+ _setImageSmoothing: function(){
+ var ctx = this.getContext();
+
+ ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
+ ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled;
+ ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled;
+ ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled;
+ ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled;
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
+ * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
+ * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
+ */
+ __setBgOverlayImage: function(property, image, callback, options) {
+ if (typeof image === 'string') {
+ fabric.util.loadImage(image, function(img) {
+ this[property] = new fabric.Image(img, options);
+ callback && callback();
+ }, this);
+ }
+ else {
+ this[property] = image;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
+ * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
+ * @param {(Object|String|null)} color Object with pattern information, color value or null
+ * @param {Function} [callback] Callback is invoked when color is set
+ */
+ __setBgOverlayColor: function(property, color, callback) {
+ if (color && color.source) {
+ var _this = this;
+ fabric.util.loadImage(color.source, function(img) {
+ _this[property] = new fabric.Pattern({
+ source: img,
+ repeat: color.repeat,
+ offsetX: color.offsetX,
+ offsetY: color.offsetY
+ });
+ callback && callback();
+ });
+ }
+ else {
+ this[property] = color;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ _createCanvasElement: function() {
+ var element = fabric.document.createElement('canvas');
+ if (!element.style) {
+ element.style = { };
+ }
+ if (!element) {
+ throw CANVAS_INIT_ERROR;
+ }
+ this._initCanvasElement(element);
+ return element;
+ },
+
+ /**
+ * @private
+ * @param {HTMLElement} element
+ */
+ _initCanvasElement: function(element) {
+ fabric.util.createCanvasElement(element);
+
+ if (typeof element.getContext === 'undefined') {
+ throw CANVAS_INIT_ERROR;
+ }
+ },
+
+ /**
+ * @private
+ * @param {Object} [options] Options object
+ */
+ _initOptions: function (options) {
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+
+ this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
+ this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
+
+ if (!this.lowerCanvasEl.style) {
+ return;
+ }
+
+ this.lowerCanvasEl.width = this.width;
+ this.lowerCanvasEl.height = this.height;
+
+ this.lowerCanvasEl.style.width = this.width + 'px';
+ this.lowerCanvasEl.style.height = this.height + 'px';
+
+ this.viewportTransform = this.viewportTransform.slice();
+ },
+
+ /**
+ * Creates a bottom canvas
+ * @private
+ * @param {HTMLElement} [canvasEl]
+ */
+ _createLowerCanvas: function (canvasEl) {
+ this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
+ this._initCanvasElement(this.lowerCanvasEl);
+
+ fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
+
+ if (this.interactive) {
+ this._applyCanvasStyle(this.lowerCanvasEl);
+ }
+
+ this.contextContainer = this.lowerCanvasEl.getContext('2d');
+ },
+
+ /**
+ * Returns canvas width (in px)
+ * @return {Number}
+ */
+ getWidth: function () {
+ return this.width;
+ },
+
+ /**
+ * Returns canvas height (in px)
+ * @return {Number}
+ */
+ getHeight: function () {
+ return this.height;
+ },
+
+ /**
+ * Sets width of this canvas instance
+ * @param {Number|String} value Value to set width to
+ * @param {Object} [options] Options object
+ * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
+ * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setWidth: function (value, options) {
+ return this.setDimensions({ width: value }, options);
+ },
+
+ /**
+ * Sets height of this canvas instance
+ * @param {Number|String} value Value to set height to
+ * @param {Object} [options] Options object
+ * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
+ * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setHeight: function (value, options) {
+ return this.setDimensions({ height: value }, options);
+ },
+
+ /**
+ * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)
+ * @param {Object} dimensions Object with width/height properties
+ * @param {Number|String} [dimensions.width] Width of canvas element
+ * @param {Number|String} [dimensions.height] Height of canvas element
+ * @param {Object} [options] Options object
+ * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
+ * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setDimensions: function (dimensions, options) {
+ var cssValue;
+
+ options = options || {};
+
+ for (var prop in dimensions) {
+ cssValue = dimensions[prop];
+
+ if (!options.cssOnly) {
+ this._setBackstoreDimension(prop, dimensions[prop]);
+ cssValue += 'px';
+ }
+
+ if (!options.backstoreOnly) {
+ this._setCssDimension(prop, cssValue);
+ }
+ }
+
+ if (!options.cssOnly) {
+ this.renderAll();
+ }
+
+ this.calcOffset();
+
+ return this;
+ },
+
+ /**
+ * Helper for setting width/height
+ * @private
+ * @param {String} prop property (width|height)
+ * @param {Number} value value to set property to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ _setBackstoreDimension: function (prop, value) {
+ this.lowerCanvasEl[prop] = value;
+
+ if (this.upperCanvasEl) {
+ this.upperCanvasEl[prop] = value;
+ }
+
+ if (this.cacheCanvasEl) {
+ this.cacheCanvasEl[prop] = value;
+ }
+
+ this[prop] = value;
+
+ return this;
+ },
+
+ /**
+ * Helper for setting css width/height
+ * @private
+ * @param {String} prop property (width|height)
+ * @param {String} value value to set property to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ _setCssDimension: function (prop, value) {
+ this.lowerCanvasEl.style[prop] = value;
+
+ if (this.upperCanvasEl) {
+ this.upperCanvasEl.style[prop] = value;
+ }
+
+ if (this.wrapperEl) {
+ this.wrapperEl.style[prop] = value;
+ }
+
+ return this;
+ },
+
+ /**
+ * Returns canvas zoom level
+ * @return {Number}
+ */
+ getZoom: function () {
+ return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
+ },
+
+ /**
+ * Sets viewport transform of this canvas instance
+ * @param {Array} vpt the transform in the form of context.transform
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setViewportTransform: function (vpt) {
+ this.viewportTransform = vpt;
+ this.renderAll();
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ this._objects[i].setCoords();
+ }
+ return this;
+ },
+
+ /**
+ * Sets zoom level of this canvas instance, zoom centered around point
+ * @param {fabric.Point} point to zoom with respect to
+ * @param {Number} value to set zoom to, less than 1 zooms out
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ zoomToPoint: function (point, value) {
+ // TODO: just change the scale, preserve other transformations
+ var before = point;
+ point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform));
+ this.viewportTransform[0] = value;
+ this.viewportTransform[3] = value;
+ var after = fabric.util.transformPoint(point, this.viewportTransform);
+ this.viewportTransform[4] += before.x - after.x;
+ this.viewportTransform[5] += before.y - after.y;
+ this.renderAll();
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ this._objects[i].setCoords();
+ }
+ return this;
+ },
+
+ /**
+ * Sets zoom level of this canvas instance
+ * @param {Number} value to set zoom to, less than 1 zooms out
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setZoom: function (value) {
+ this.zoomToPoint(new fabric.Point(0, 0), value);
+ return this;
+ },
+
+ /**
+ * Pan viewport so as to place point at top left corner of canvas
+ * @param {fabric.Point} point to move to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ absolutePan: function (point) {
+ this.viewportTransform[4] = -point.x;
+ this.viewportTransform[5] = -point.y;
+ this.renderAll();
+ for (var i = 0, len = this._objects.length; i < len; i++) {
+ this._objects[i].setCoords();
+ }
+ return this;
+ },
+
+ /**
+ * Pans viewpoint relatively
+ * @param {fabric.Point} point (position vector) to move by
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ relativePan: function (point) {
+ return this.absolutePan(new fabric.Point(
+ -point.x - this.viewportTransform[4],
+ -point.y - this.viewportTransform[5]
+ ));
+ },
+
+ /**
+ * Returns <canvas> element corresponding to this instance
+ * @return {HTMLCanvasElement}
+ */
+ getElement: function () {
+ return this.lowerCanvasEl;
+ },
+
+ /**
+ * Returns currently selected object, if any
+ * @return {fabric.Object}
+ */
+ getActiveObject: function() {
+ return null;
+ },
+
+ /**
+ * Returns currently selected group of object, if any
+ * @return {fabric.Group}
+ */
+ getActiveGroup: function() {
+ return null;
+ },
+
+ /**
+ * Given a context, renders an object on that context
+ * @param {CanvasRenderingContext2D} ctx Context to render object on
+ * @param {fabric.Object} object Object to render
+ * @private
+ */
+ _draw: function (ctx, object) {
+ if (!object) {
+ return;
+ }
+
+ ctx.save();
+ var v = this.viewportTransform;
+ ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
+ object.render(ctx);
+ ctx.restore();
+ if (!this.controlsAboveOverlay) {
+ object._renderControls(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} obj Object that was added
+ */
+ _onObjectAdded: function(obj) {
+ this.stateful && obj.setupState();
+ obj.canvas = this;
+ obj.setCoords();
+ this.fire('object:added', { target: obj });
+ obj.fire('added');
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} obj Object that was removed
+ */
+ _onObjectRemoved: function(obj) {
+ // removing active object should fire "selection:cleared" events
+ if (this.getActiveObject() === obj) {
+ this.fire('before:selection:cleared', { target: obj });
+ this._discardActiveObject();
+ this.fire('selection:cleared');
+ }
+
+ this.fire('object:removed', { target: obj });
+ obj.fire('removed');
+ },
+
+ /**
+ * Clears specified context of canvas element
+ * @param {CanvasRenderingContext2D} ctx Context to clear
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clearContext: function(ctx) {
+ ctx.clearRect(0, 0, this.width, this.height);
+ return this;
+ },
+
+ /**
+ * Returns context of canvas where objects are drawn
+ * @return {CanvasRenderingContext2D}
+ */
+ getContext: function () {
+ return this.contextContainer;
+ },
+
+ /**
+ * Clears all contexts (background, main, top) of an instance
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clear: function () {
+ this._objects.length = 0;
+ if (this.discardActiveGroup) {
+ this.discardActiveGroup();
+ }
+ if (this.discardActiveObject) {
+ this.discardActiveObject();
+ }
+ this.clearContext(this.contextContainer);
+ if (this.contextTop) {
+ this.clearContext(this.contextTop);
+ }
+ this.fire('canvas:cleared');
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Renders both the top canvas and the secondary container canvas.
+ * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ renderAll: function (allOnTop) {
+ var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'],
+ activeGroup = this.getActiveGroup();
+
+ if (this.contextTop && this.selection && !this._groupSelector) {
+ this.clearContext(this.contextTop);
+ }
+
+ if (!allOnTop) {
+ this.clearContext(canvasToDrawOn);
+ }
+
+ this.fire('before:render');
+
+ if (this.clipTo) {
+ fabric.util.clipContext(this, canvasToDrawOn);
+ }
+
+ this._renderBackground(canvasToDrawOn);
+ this._renderObjects(canvasToDrawOn, activeGroup);
+ this._renderActiveGroup(canvasToDrawOn, activeGroup);
+
+ if (this.clipTo) {
+ canvasToDrawOn.restore();
+ }
+
+ this._renderOverlay(canvasToDrawOn);
+
+ if (this.controlsAboveOverlay && this.interactive) {
+ this.drawControls(canvasToDrawOn);
+ }
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderObjects: function(ctx, activeGroup) {
+ var i, length;
+
+ // fast path
+ if (!activeGroup) {
+ for (i = 0, length = this._objects.length; i < length; ++i) {
+ this._draw(ctx, this._objects[i]);
+ }
+ }
+ else {
+ for (i = 0, length = this._objects.length; i < length; ++i) {
+ if (this._objects[i] && !activeGroup.contains(this._objects[i])) {
+ this._draw(ctx, this._objects[i]);
+ }
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderActiveGroup: function(ctx, activeGroup) {
+
+ // delegate rendering to group selection (if one exists)
+ if (activeGroup) {
+
+ //Store objects in group preserving order, then replace
+ var sortedObjects = [];
+ this.forEachObject(function (object) {
+ if (activeGroup.contains(object)) {
+ sortedObjects.push(object);
+ }
+ });
+ activeGroup._set('objects', sortedObjects);
+ this._draw(ctx, activeGroup);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderBackground: function(ctx) {
+ if (this.backgroundColor) {
+ ctx.fillStyle = this.backgroundColor.toLive
+ ? this.backgroundColor.toLive(ctx)
+ : this.backgroundColor;
+
+ ctx.fillRect(
+ this.backgroundColor.offsetX || 0,
+ this.backgroundColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.backgroundImage) {
+ this._draw(ctx, this.backgroundImage);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderOverlay: function(ctx) {
+ if (this.overlayColor) {
+ ctx.fillStyle = this.overlayColor.toLive
+ ? this.overlayColor.toLive(ctx)
+ : this.overlayColor;
+
+ ctx.fillRect(
+ this.overlayColor.offsetX || 0,
+ this.overlayColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.overlayImage) {
+ this._draw(ctx, this.overlayImage);
+ }
+ },
+
+ /**
+ * Method to render only the top canvas.
+ * Also used to render the group selection box.
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ renderTop: function () {
+ var ctx = this.contextTop || this.contextContainer;
+ this.clearContext(ctx);
+
+ // we render the top context - last object
+ if (this.selection && this._groupSelector) {
+ this._drawSelection();
+ }
+
+ // delegate rendering to group selection if one exists
+ // used for drawing selection borders/controls
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.render(ctx);
+ }
+
+ this._renderOverlay(ctx);
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * Returns coordinates of a center of canvas.
+ * Returned value is an object with top and left properties
+ * @return {Object} object with "top" and "left" number values
+ */
+ getCenter: function () {
+ return {
+ top: this.getHeight() / 2,
+ left: this.getWidth() / 2
+ };
+ },
+
+ /**
+ * Centers object horizontally.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center horizontally
+ * @return {fabric.Canvas} thisArg
+ */
+ centerObjectH: function (object) {
+ this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center vertically
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObjectV: function (object) {
+ this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically and horizontally.
+ * You might need to call `setCoords` on an object after centering, to update controls area.
+ * @param {fabric.Object} object Object to center vertically and horizontally
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObject: function(object) {
+ var center = this.getCenter();
+
+ this._centerObject(object, new fabric.Point(center.left, center.top));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} object Object to center
+ * @param {fabric.Point} center Center point
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ _centerObject: function(object, center) {
+ object.setPositionByOrigin(center, 'center', 'center');
+ return this;
+ },
+
+ /**
+ * Returs dataless JSON representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {String} json string
+ */
+ toDatalessJSON: function (propertiesToInclude) {
+ return this.toDatalessObject(propertiesToInclude);
+ },
+
+ /**
+ * Returns object representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {Object} object representation of an instance
+ */
+ toObject: function (propertiesToInclude) {
+ return this._toObjectMethod('toObject', propertiesToInclude);
+ },
+
+ /**
+ * Returns dataless object representation of canvas
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
+ * @return {Object} object representation of an instance
+ */
+ toDatalessObject: function (propertiesToInclude) {
+ return this._toObjectMethod('toDatalessObject', propertiesToInclude);
+ },
+
+ /**
+ * @private
+ */
+ _toObjectMethod: function (methodName, propertiesToInclude) {
+
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ this.discardActiveGroup();
+ }
+
+ var data = {
+ objects: this._toObjects(methodName, propertiesToInclude)
+ };
+
+ extend(data, this.__serializeBgOverlay());
+
+ fabric.util.populateWithProperties(this, data, propertiesToInclude);
+
+ if (activeGroup) {
+ this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), {
+ originX: 'center',
+ originY: 'center'
+ }));
+ activeGroup.forEachObject(function(o) {
+ o.set('active', true);
+ });
+
+ if (this._currentTransform) {
+ this._currentTransform.target = this.getActiveGroup();
+ }
+ }
+
+ return data;
+ },
+
+ /**
+ * @private
+ */
+ _toObjects: function(methodName, propertiesToInclude) {
+ return this.getObjects().map(function(instance) {
+ return this._toObject(instance, methodName, propertiesToInclude);
+ }, this);
+ },
+
+ /**
+ * @private
+ */
+ _toObject: function(instance, methodName, propertiesToInclude) {
+ var originalValue;
+
+ if (!this.includeDefaultValues) {
+ originalValue = instance.includeDefaultValues;
+ instance.includeDefaultValues = false;
+ }
+ var object = instance[methodName](propertiesToInclude);
+ if (!this.includeDefaultValues) {
+ instance.includeDefaultValues = originalValue;
+ }
+ return object;
+ },
+
+ /**
+ * @private
+ */
+ __serializeBgOverlay: function() {
+ var data = {
+ background: (this.backgroundColor && this.backgroundColor.toObject)
+ ? this.backgroundColor.toObject()
+ : this.backgroundColor
+ };
+
+ if (this.overlayColor) {
+ data.overlay = this.overlayColor.toObject
+ ? this.overlayColor.toObject()
+ : this.overlayColor;
+ }
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.toObject();
+ }
+ if (this.overlayImage) {
+ data.overlayImage = this.overlayImage.toObject();
+ }
+
+ return data;
+ },
+
+ /* _TO_SVG_START_ */
+ /**
+ * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,
+ * a zoomed canvas will then produce zoomed SVG output.
+ * @type Boolean
+ * @default
+ */
+ svgViewportTransformation: true,
+
+ /**
+ * Returns SVG representation of canvas
+ * @function
+ * @param {Object} [options] Options object for SVG output
+ * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
+ * @param {Object} [options.viewBox] SVG viewbox object
+ * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
+ * @param {Number} [options.viewBox.y] y-coordinate of viewbox
+ * @param {Number} [options.viewBox.width] Width of viewbox
+ * @param {Number} [options.viewBox.height] Height of viewbox
+ * @param {String} [options.encoding=UTF-8] Encoding of SVG output
+ * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
+ * @return {String} SVG string
+ * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
+ * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
+ * @example Normal SVG output
+ * var svg = canvas.toSVG();
+ * @example SVG output without preamble (without <?xml ../>)
+ * var svg = canvas.toSVG({suppressPreamble: true});
+ * @example SVG output with viewBox attribute
+ * var svg = canvas.toSVG({
+ * viewBox: {
+ * x: 100,
+ * y: 100,
+ * width: 200,
+ * height: 300
+ * }
+ * });
+ * @example SVG output with different encoding (default: UTF-8)
+ * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
+ * @example Modify SVG output with reviver function
+ * var svg = canvas.toSVG(null, function(svg) {
+ * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
+ * });
+ */
+ toSVG: function(options, reviver) {
+ options || (options = { });
+
+ var markup = [];
+
+ this._setSVGPreamble(markup, options);
+ this._setSVGHeader(markup, options);
+
+ this._setSVGBgOverlayColor(markup, 'backgroundColor');
+ this._setSVGBgOverlayImage(markup, 'backgroundImage');
+
+ this._setSVGObjects(markup, reviver);
+
+ this._setSVGBgOverlayColor(markup, 'overlayColor');
+ this._setSVGBgOverlayImage(markup, 'overlayImage');
+
+ markup.push('');
+
+ return markup.join('');
+ },
+
+ /**
+ * @private
+ */
+ _setSVGPreamble: function(markup, options) {
+ if (!options.suppressPreamble) {
+ markup.push(
+ '',
+ '\n'
+ );
+ }
+ },
+
+ /**
+ * @private
+ */
+ _setSVGHeader: function(markup, options) {
+ var width, height, vpt;
+
+ if (options.viewBox) {
+ width = options.viewBox.width;
+ height = options.viewBox.height;
+ }
+ else {
+ width = this.width;
+ height = this.height;
+ if (!this.svgViewportTransformation) {
+ vpt = this.viewportTransform;
+ width /= vpt[0];
+ height /= vpt[3];
+ }
+ }
+
+ markup.push(
+ '