From fe0c9bbfa65616d49b5ecbc348fa1e265ab0b7bb Mon Sep 17 00:00:00 2001 From: Godwin Date: Fri, 1 Jul 2016 16:16:57 -0700 Subject: [PATCH] Changed paragraph font to Queulat and started work on the schedule maker --- app/assets/fonts/queulat.eot | Bin 0 -> 38828 bytes app/assets/fonts/queulat.otf | Bin 0 -> 100968 bytes app/assets/fonts/queulat.svg | 6007 +++++ app/assets/fonts/queulat.ttf | Bin 0 -> 126932 bytes app/assets/fonts/queulat.woff | Bin 0 -> 61486 bytes app/assets/fonts/queulat.woff2 | Bin 0 -> 45062 bytes app/assets/stylesheets/_application.scss | 89 +- .../stylesheets/bumbleberry-settings.json | 8 +- app/controllers/conferences_controller.rb | 40 + app/helpers/application_helper.rb | 61 +- app/views/application/about.html.haml | 6 +- app/views/conferences/admin/_meals.html.haml | 2 +- .../conferences/admin/_schedule.html.haml | 17 + app/views/conferences/register.html.haml | 12 +- app/views/workshops/show.html.haml | 11 +- config/initializers/assets.rb | 2 +- config/locales/en.yml | 12 +- config/routes.rb | 1 + ...3219_add_workshop_blocks_to_conferences.rb | 5 + db/schema.rb | 3 +- vendor/assets/javascripts/html2canvas.js | 19279 ++++++++++++++++ 21 files changed, 25488 insertions(+), 67 deletions(-) create mode 100644 app/assets/fonts/queulat.eot create mode 100644 app/assets/fonts/queulat.otf create mode 100644 app/assets/fonts/queulat.svg create mode 100644 app/assets/fonts/queulat.ttf create mode 100644 app/assets/fonts/queulat.woff create mode 100644 app/assets/fonts/queulat.woff2 create mode 100644 db/migrate/20160630233219_add_workshop_blocks_to_conferences.rb create mode 100644 vendor/assets/javascripts/html2canvas.js diff --git a/app/assets/fonts/queulat.eot b/app/assets/fonts/queulat.eot new file mode 100644 index 0000000000000000000000000000000000000000..14cef1e2dd461a969d9a1128b309bbf04fa510d3 GIT binary patch literal 38828 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 literal 0 HcmV?d00001 diff --git a/app/assets/fonts/queulat.otf b/app/assets/fonts/queulat.otf new file mode 100644 index 0000000000000000000000000000000000000000..9c660106424734e5d63030b5875cef3794615976 GIT binary patch literal 100968 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ˑ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 +5bec@WwtQC=Wq!-S=T_@|-q7g~ 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 literal 0 HcmV?d00001 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 @@ + + + + +Created by FontForge 20150824 at Fri Jul 1 15:43:47 2016 + By Everything Fonts +Jorge Cisterna + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/fonts/queulat.ttf b/app/assets/fonts/queulat.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2d14435b9e40d373a9a8a7940b9752946299932b GIT binary patch literal 126932 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 literal 0 HcmV?d00001 diff --git a/app/assets/fonts/queulat.woff b/app/assets/fonts/queulat.woff new file mode 100644 index 0000000000000000000000000000000000000000..59b5de21fe1740d6b79bee3a858192cfbf2802ae GIT binary patch literal 61486 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 I^AEPG0AV6WQ2+n{ literal 0 HcmV?d00001 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( + '', + 'Created with Fabric.js ', fabric.version, '', + '', + fabric.createSVGFontFacesMarkup(this.getObjects()), + fabric.createSVGRefElementsMarkup(this), + '' + ); + }, + + /** + * @private + */ + _setSVGObjects: function(markup, reviver) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this.discardActiveGroup(); + } + for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + if (activeGroup) { + this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); + activeGroup.forEachObject(function(o) { + o.set('active', true); + }); + } + }, + + /** + * @private + */ + _setSVGBgOverlayImage: function(markup, property) { + if (this[property] && this[property].toSVG) { + markup.push(this[property].toSVG()); + } + }, + + /** + * @private + */ + _setSVGBgOverlayColor: function(markup, property) { + if (this[property] && this[property].source) { + markup.push( + '' + ); + } + else if (this[property] && property === 'overlayColor') { + markup.push( + '' + ); + } + }, + /* _TO_SVG_END_ */ + + /** + * Moves an object to the bottom of the stack of drawn objects + * @param {fabric.Object} object Object to send to back + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendToBack: function (object) { + removeFromArray(this._objects, object); + this._objects.unshift(object); + return this.renderAll && this.renderAll(); + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @param {fabric.Object} object Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringToFront: function (object) { + removeFromArray(this._objects, object); + this._objects.push(object); + return this.renderAll && this.renderAll(); + }, + + /** + * Moves an object down in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendBackwards: function (object, intersecting) { + var idx = this._objects.indexOf(object); + + // if object is not on the bottom of stack + if (idx !== 0) { + var newIdx = this._findNewLowerIndex(object, idx, intersecting); + + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + + /** + * @private + */ + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx; + + if (intersecting) { + newIdx = idx; + + // traverse down the stack looking for the nearest intersecting object + for (var i = idx - 1; i >= 0; --i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx - 1; + } + + return newIdx; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringForward: function (object, intersecting) { + var idx = this._objects.indexOf(object); + + // if object is not on top of stack (last item in an array) + if (idx !== this._objects.length - 1) { + var newIdx = this._findNewUpperIndex(object, idx, intersecting); + + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + + /** + * @private + */ + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx; + + if (intersecting) { + newIdx = idx; + + // traverse up the stack looking for the nearest intersecting object + for (var i = idx + 1; i < this._objects.length; ++i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx + 1; + } + + return newIdx; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Number} index Position to move to + * @return {fabric.Canvas} thisArg + * @chainable + */ + moveTo: function (object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderAll && this.renderAll(); + }, + + /** + * Clears a canvas element and removes all event listeners + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + this.clear(); + this.interactive && this.removeListeners(); + return this; + }, + + /** + * Returns a string representation of an instance + * @return {String} string representation of an instance + */ + toString: function () { + return '#'; + } + }); + + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + + extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { + + /** + * @static + * @type String + * @default + */ + EMPTY_JSON: '{"objects": [], "background": "white"}', + + /** + * Provides a way to check support of some of the canvas methods + * (either those of HTMLCanvasElement itself, or rendering context) + * + * @param {String} methodName Method to check support for; + * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" + * @return {Boolean | null} `true` if method is supported (or at least exists), + * `null` if canvas element or context can not be initialized + */ + supports: function (methodName) { + var el = fabric.util.createCanvasElement(); + + if (!el || !el.getContext) { + return null; + } + + var ctx = el.getContext('2d'); + if (!ctx) { + return null; + } + + switch (methodName) { + + case 'getImageData': + return typeof ctx.getImageData !== 'undefined'; + + case 'setLineDash': + return typeof ctx.setLineDash !== 'undefined'; + + case 'toDataURL': + return typeof el.toDataURL !== 'undefined'; + + case 'toDataURLWithQuality': + try { + el.toDataURL('image/jpeg', 0); + return true; + } + catch (e) { } + return false; + + default: + return null; + } + } + }); + + /** + * Returns JSON representation of canvas + * @function + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {String} JSON string + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} + * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} + * @example JSON without additional properties + * var json = canvas.toJSON(); + * @example JSON with additional properties included + * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); + * @example JSON without default values + * canvas.includeDefaultValues = false; + * var json = canvas.toJSON(); + */ + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; + +})(); + + +/** + * BaseBrush class + * @class fabric.BaseBrush + * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} + */ +fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { + + /** + * Color of a brush + * @type String + * @default + */ + color: 'rgb(0, 0, 0)', + + /** + * Width of a brush + * @type Number + * @default + */ + width: 1, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), + * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Line endings style of a brush (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'round', + + /** + * Corner style of a brush (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'round', + + /** + * Sets shadow of an object + * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") + * @return {fabric.Object} thisArg + * @chainable + */ + setShadow: function(options) { + this.shadow = new fabric.Shadow(options); + return this; + }, + + /** + * Sets brush styles + * @private + */ + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + }, + + /** + * Sets brush shadow styles + * @private + */ + _setShadow: function() { + if (!this.shadow) { + return; + } + + var ctx = this.canvas.contextTop; + + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; + }, + + /** + * Removes brush shadow styles + * @private + */ + _resetShadow: function() { + var ctx = this.canvas.contextTop; + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + } +}); + + +(function() { + + var utilMin = fabric.util.array.min, + utilMax = fabric.util.array.max; + + /** + * PencilBrush class + * @class fabric.PencilBrush + * @extends fabric.BaseBrush + */ + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.PencilBrush} Instance of a pencil brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this._points = [ ]; + }, + + /** + * Inovoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this._prepareForDrawing(pointer); + // capture coordinates immediately + // this allows to draw dots (when movement never occurs) + this._captureDrawingPath(pointer); + this._render(); + }, + + /** + * Inovoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this._captureDrawingPath(pointer); + // redraw curve + // clear top canvas + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + this._finalizeAndAddPath(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _prepareForDrawing: function(pointer) { + + var p = new fabric.Point(pointer.x, pointer.y); + + this._reset(); + this._addPoint(p); + + this.canvas.contextTop.moveTo(p.x, p.y); + }, + + /** + * @private + * @param {fabric.Point} point Point to be added to points array + */ + _addPoint: function(point) { + this._points.push(point); + }, + + /** + * Clear points array and set contextTop canvas style. + * @private + */ + _reset: function() { + this._points.length = 0; + + this._setBrushStyles(); + this._setShadow(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + this._addPoint(pointerPoint); + }, + + /** + * Draw a smooth path on the topCanvas using quadraticCurveTo + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform, + p1 = this._points[0], + p2 = this._points[1]; + + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.beginPath(); + + //if we only have 2 points in the path and they are the same + //it means that the user only clicked the canvas without moving the mouse + //then we should be drawing a dot. A path isn't drawn between two identical dots + //that's why we set them apart a bit + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + p1.x -= 0.5; + p2.x += 0.5; + } + ctx.moveTo(p1.x, p1.y); + + for (var i = 1, len = this._points.length; i < len; i++) { + // we pick the point between pi + 1 & pi + 2 as the + // end point and p1 as our control point. + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + // Draw last line as a straight line while + // we wait for the next point to be able to calculate + // the bezier control point + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + + /** + * Return an SVG path based on our captured points and their bounding box + * @private + */ + _getSVGPathData: function() { + this.box = this.getPathBoundingBox(this._points); + return this.convertPointsToSVGPath( + this._points, this.box.minX, this.box.minY); + }, + + /** + * Returns bounding box of a path based on given points + * @param {Array} points Array of points + * @return {Object} Object with minX, minY, maxX, maxY + */ + getPathBoundingBox: function(points) { + var xBounds = [], + yBounds = [], + p1 = points[0], + p2 = points[1], + startPoint = p1; + + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + // with startPoint, p1 as control point, midpoint as end point + xBounds.push(startPoint.x); + xBounds.push(midPoint.x); + yBounds.push(startPoint.y); + yBounds.push(midPoint.y); + + p1 = points[i]; + p2 = points[i + 1]; + startPoint = midPoint; + } + + xBounds.push(p1.x); + yBounds.push(p1.y); + + return { + minX: utilMin(xBounds), + minY: utilMin(yBounds), + maxX: utilMax(xBounds), + maxY: utilMax(yBounds) + }; + }, + + /** + * Converts points to SVG path + * @param {Array} points Array of points + * @param {Number} minX + * @param {Number} minY + * @return {String} SVG path + */ + convertPointsToSVGPath: function(points, minX, minY) { + var path = [], + p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), + p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + + path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); + p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); + if ((i + 1) < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); + } + } + path.push('L ', p1.x, ' ', p1.y, ' '); + return path; + }, + + /** + * Creates fabric.Path object to add on canvas + * @param {String} pathData Path data + * @return {fabric.Path} Path to add on canvas + */ + createPath: function(pathData) { + var path = new fabric.Path(pathData); + path.fill = null; + path.stroke = this.color; + path.strokeWidth = this.width; + path.strokeLineCap = this.strokeLineCap; + path.strokeLineJoin = this.strokeLineJoin; + + if (this.shadow) { + this.shadow.affectStroke = true; + path.setShadow(this.shadow); + } + + return path; + }, + + /** + * On mouseup after drawing the path on contextTop canvas + * we use the points captured to create an new fabric path object + * and add it to the fabric canvas. + */ + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + + var pathData = this._getSVGPathData().join(''); + if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { + // do not create 0 width/height paths, as they are + // rendered inconsistently across browsers + // Firefox 4, for example, renders a dot, + // whereas Chrome 10 renders nothing + this.canvas.renderAll(); + return; + } + + // set path origin coordinates based on our bounding box + var originLeft = this.box.minX + (this.box.maxX - this.box.minX) / 2, + originTop = this.box.minY + (this.box.maxY - this.box.minY) / 2; + + this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); + + var path = this.createPath(pathData); + path.set({ + left: originLeft, + top: originTop, + originX: 'center', + originY: 'center' + }); + + this.canvas.add(path); + path.setCoords(); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderAll(); + + // fire event 'path' created + this.canvas.fire('path:created', { path: path }); + } + }); +})(); + + +/** + * CircleBrush class + * @class fabric.CircleBrush + */ +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { + + /** + * Width of a brush + * @type Number + * @default + */ + width: 10, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.CircleBrush} Instance of a circle brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.points = [ ]; + }, + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + drawDot: function(pointer) { + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + ctx.fillStyle = point.fill; + ctx.beginPath(); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + }, + + /** + * Invoked on mouse down + */ + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this.drawDot(pointer); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var circles = [ ]; + + for (var i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); + + this.shadow && circle.setShadow(this.shadow); + + circles.push(circle); + } + var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; + + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + + /** + * @param {Object} pointer + * @return {fabric.Point} Just added pointer point + */ + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), + + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, + + circleColor = new fabric.Color(this.color) + .setAlpha(fabric.util.getRandomInt(0, 100) / 100) + .toRgba(); + + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + + this.points.push(pointerPoint); + + return pointerPoint; + } +}); + + +/** + * SprayBrush class + * @class fabric.SprayBrush + */ +fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { + + /** + * Width of a spray + * @type Number + * @default + */ + width: 10, + + /** + * Density of a spray (number of dots per chunk) + * @type Number + * @default + */ + density: 20, + + /** + * Width of spray dots + * @type Number + * @default + */ + dotWidth: 1, + + /** + * Width variance of spray dots + * @type Number + * @default + */ + dotWidthVariance: 1, + + /** + * Whether opacity of a dot should be random + * @type Boolean + * @default + */ + randomOpacity: false, + + /** + * Whether overlapping dots (rectangles) should be removed (for performance reasons) + * @type Boolean + * @default + */ + optimizeOverlapping: true, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.SprayBrush} Instance of a spray brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = [ ]; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var rects = [ ]; + + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: 'center', + originY: 'center', + fill: this.color + }); + + this.shadow && rect.setShadow(this.shadow); + rects.push(rect); + } + } + + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + + var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; + + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + + /** + * @private + * @param {Array} rects + */ + _getOptimizedRects: function(rects) { + + // avoid creating duplicate rects at the same coordinates + var uniqueRects = { }, key; + + for (var i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + '' + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = [ ]; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + + return uniqueRectsArray; + }, + + /** + * Renders brush + */ + render: function() { + var ctx = this.canvas.contextTop; + ctx.fillStyle = this.color; + + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { + var point = this.sprayChunkPoints[i]; + if (typeof point.opacity !== 'undefined') { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + + /** + * @param {Object} pointer + */ + addSprayChunk: function(pointer) { + this.sprayChunkPoints = [ ]; + + var x, y, width, radius = this.width / 2; + + for (var i = 0; i < this.density; i++) { + + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt( + // bottom clamp width to 1 + Math.max(1, this.dotWidth - this.dotWidthVariance), + this.dotWidth + this.dotWidthVariance); + } + else { + width = this.dotWidth; + } + + var point = new fabric.Point(x, y); + point.width = width; + + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + + this.sprayChunkPoints.push(point); + } + + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + + +/** + * PatternBrush class + * @class fabric.PatternBrush + * @extends fabric.BaseBrush + */ +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { + + getPatternSrc: function() { + + var dotWidth = 20, + dotDistance = 5, + patternCanvas = fabric.document.createElement('canvas'), + patternCtx = patternCanvas.getContext('2d'); + + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + + return patternCanvas; + }, + + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); + }, + + /** + * Creates "pattern" instance property + */ + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); + }, + + /** + * Sets brush styles + */ + _setBrushStyles: function() { + this.callSuper('_setBrushStyles'); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + + /** + * Creates path + */ + createPath: function(pathData) { + var path = this.callSuper('createPath', pathData); + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction() + }); + return path; + } +}); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately + * @param {Object} [options] Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} + * @example Generate jpeg dataURL with lower quality + * var dataURL = canvas.toDataURL({ + * format: 'jpeg', + * quality: 0.8 + * }); + * @example Generate cropped png dataURL (clipping of canvas) + * var dataURL = canvas.toDataURL({ + * format: 'png', + * left: 100, + * top: 100, + * width: 200, + * height: 200 + * }); + * @example Generate double scaled png dataURL + * var dataURL = canvas.toDataURL({ + * format: 'png', + * multiplier: 2 + * }); + */ + toDataURL: function (options) { + options || (options = { }); + + var format = options.format || 'png', + quality = options.quality || 1, + multiplier = options.multiplier || 1, + cropping = { + left: options.left, + top: options.top, + width: options.width, + height: options.height + }; + + if (multiplier !== 1) { + return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); + } + else { + return this.__toDataURL(format, quality, cropping); + } + }, + + /** + * @private + */ + __toDataURL: function(format, quality, cropping) { + + this.renderAll(true); + + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, + croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + + // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 + if (format === 'jpg') { + format = 'jpeg'; + } + + var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) + ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) + : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + + if (croppedCanvasEl) { + croppedCanvasEl = null; + } + + return data; + }, + + /** + * @private + */ + __getCroppedCanvas: function(canvasEl, cropping) { + + var croppedCanvasEl, + croppedCtx, + shouldCrop = 'left' in cropping || + 'top' in cropping || + 'width' in cropping || + 'height' in cropping; + + if (shouldCrop) { + + croppedCanvasEl = fabric.util.createCanvasElement(); + croppedCtx = croppedCanvasEl.getContext('2d'); + + croppedCanvasEl.width = cropping.width || this.width; + croppedCanvasEl.height = cropping.height || this.height; + + croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); + } + + return croppedCanvasEl; + }, + + /** + * @private + */ + __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { + + var origWidth = this.getWidth(), + origHeight = this.getHeight(), + scaledWidth = origWidth * multiplier, + scaledHeight = origHeight * multiplier, + activeObject = this.getActiveObject(), + activeGroup = this.getActiveGroup(), + + ctx = this.contextTop || this.contextContainer; + + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } + ctx.scale(multiplier, multiplier); + + if (cropping.left) { + cropping.left *= multiplier; + } + if (cropping.top) { + cropping.top *= multiplier; + } + if (cropping.width) { + cropping.width *= multiplier; + } + else if (multiplier < 1) { + cropping.width = scaledWidth; + } + if (cropping.height) { + cropping.height *= multiplier; + } + else if (multiplier < 1) { + cropping.height = scaledHeight; + } + + if (activeGroup) { + // not removing group due to complications with restoring it with correct state afterwords + this._tempRemoveBordersControlsFromGroup(activeGroup); + } + else if (activeObject && this.deactivateAll) { + this.deactivateAll(); + } + + this.renderAll(true); + + var data = this.__toDataURL(format, quality, cropping); + + // restoring width, height for `renderAll` to draw + // background properly (while context is scaled) + this.width = origWidth; + this.height = origHeight; + + ctx.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + + if (activeGroup) { + this._restoreBordersControlsOnGroup(activeGroup); + } + else if (activeObject && this.setActiveObject) { + this.setActiveObject(activeObject); + } + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + + return data; + }, + + /** + * Exports canvas element to a dataurl image (allowing to change image size via multiplier). + * @deprecated since 1.0.13 + * @param {String} format (png|jpeg) + * @param {Number} multiplier + * @param {Number} quality (0..1) + * @return {String} + */ + toDataURLWithMultiplier: function (format, multiplier, quality) { + return this.toDataURL({ + format: format, + multiplier: multiplier, + quality: quality + }); + }, + + /** + * @private + */ + _tempRemoveBordersControlsFromGroup: function(group) { + group.origHasControls = group.hasControls; + group.origBorderColor = group.borderColor; + + group.hasControls = true; + group.borderColor = 'rgba(0,0,0,0)'; + + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = 'rgba(0,0,0,0)'; + }); + }, + + /** + * @private + */ + _restoreBordersControlsOnGroup: function(group) { + group.hideControls = group.origHideControls; + group.borderColor = group.origBorderColor; + + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + } +}); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Populates canvas with data from the specified dataless JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} + * @deprecated since 1.2.2 + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} + */ + loadFromDatalessJSON: function (json, callback, reviver) { + return this.loadFromJSON(json, callback, reviver); + }, + + /** + * Populates canvas with data from the specified JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toJSON} + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} + * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} + * @example loadFromJSON + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); + * @example loadFromJSON with reviver + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { + * // `o` = json object + * // `object` = fabric.Object instance + * // ... do some stuff ... + * }); + */ + loadFromJSON: function (json, callback, reviver) { + if (!json) { + return; + } + + // serialize if it wasn't already + var serialized = (typeof json === 'string') + ? JSON.parse(json) + : json; + + this.clear(); + + var _this = this; + this._enlivenObjects(serialized.objects, function () { + _this._setBgOverlay(serialized, callback); + }, reviver); + + return this; + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + _setBgOverlay: function(serialized, callback) { + var _this = this, + loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; + } + + var cbIfLoaded = function () { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + _this.renderAll(); + callback && callback(); + } + }; + + this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); + + cbIfLoaded(); + }, + + /** + * @private + * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) + * @param {(Object|String)} value Value to set + * @param {Object} loaded Set loaded property to true if property is set + * @param {Object} callback Callback function to invoke after property is set + */ + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + + if (!value) { + loaded[property] = true; + return; + } + + if (property === 'backgroundImage' || property === 'overlayImage') { + fabric.Image.fromObject(value, function(img) { + _this[property] = img; + loaded[property] = true; + callback && callback(); + }); + } + else { + this['set' + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); + } + }, + + /** + * @private + * @param {Array} objects + * @param {Function} callback + * @param {Function} [reviver] + */ + _enlivenObjects: function (objects, callback, reviver) { + var _this = this; + + if (!objects || objects.length === 0) { + callback && callback(); + return; + } + + var renderOnAddRemove = this.renderOnAddRemove; + this.renderOnAddRemove = false; + + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + enlivenedObjects.forEach(function(obj, index) { + _this.insertAt(obj, index, true); + }); + + _this.renderOnAddRemove = renderOnAddRemove; + callback && callback(); + }, null, reviver); + }, + + /** + * @private + * @param {String} format + * @param {Function} callback + */ + _toDataURL: function (format, callback) { + this.clone(function (clone) { + callback(clone.toDataURL(format)); + }); + }, + + /** + * @private + * @param {String} format + * @param {Number} multiplier + * @param {Function} callback + */ + _toDataURLWithMultiplier: function (format, multiplier, callback) { + this.clone(function (clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + + /** + * Clones canvas instance + * @param {Object} [callback] Receives cloned instance as a first argument + * @param {Array} [properties] Array of properties to include in the cloned canvas and children + */ + clone: function (callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + + /** + * Clones canvas instance without cloning existing data. + * This essentially copies canvas dimensions, clipping properties, etc. + * but leaves data empty (so that you can populate it with your own) + * @param {Object} [callback] Receives cloned instance as a first argument + */ + cloneWithoutData: function(callback) { + var el = fabric.document.createElement('canvas'); + + el.width = this.getWidth(); + el.height = this.getHeight(); + + var clone = new fabric.Canvas(el); + clone.clipTo = this.clipTo; + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } + else { + callback && callback(clone); + } + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + toFixed = fabric.util.toFixed, + capitalize = fabric.util.string.capitalize, + degreesToRadians = fabric.util.degreesToRadians, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Object) { + return; + } + + /** + * Root object class from which all 2d shape classes inherit from + * @class fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} + * @see {@link fabric.Object#initialize} for constructor definition + * + * @fires added + * @fires removed + * + * @fires selected + * @fires modified + * @fires rotating + * @fires scaling + * @fires moving + * + * @fires mousedown + * @fires mouseup + */ + fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { + + /** + * Retrieves object's {@link fabric.Object#clipTo|clipping function} + * @method getClipTo + * @memberOf fabric.Object.prototype + * @return {Function} + */ + + /** + * Sets object's {@link fabric.Object#clipTo|clipping function} + * @method setClipTo + * @memberOf fabric.Object.prototype + * @param {Function} clipTo Clipping function + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} + * @method getTransformMatrix + * @memberOf fabric.Object.prototype + * @return {Array} transformMatrix + */ + + /** + * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} + * @method setTransformMatrix + * @memberOf fabric.Object.prototype + * @param {Array} transformMatrix + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#visible|visible} state + * @method getVisible + * @memberOf fabric.Object.prototype + * @return {Boolean} True if visible + */ + + /** + * Sets object's {@link fabric.Object#visible|visible} state + * @method setVisible + * @memberOf fabric.Object.prototype + * @param {Boolean} value visible value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#shadow|shadow} + * @method getShadow + * @memberOf fabric.Object.prototype + * @return {Object} Shadow instance + */ + + /** + * Retrieves object's {@link fabric.Object#stroke|stroke} + * @method getStroke + * @memberOf fabric.Object.prototype + * @return {String} stroke value + */ + + /** + * Sets object's {@link fabric.Object#stroke|stroke} + * @method setStroke + * @memberOf fabric.Object.prototype + * @param {String} value stroke value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} + * @method getStrokeWidth + * @memberOf fabric.Object.prototype + * @return {Number} strokeWidth value + */ + + /** + * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} + * @method setStrokeWidth + * @memberOf fabric.Object.prototype + * @param {Number} value strokeWidth value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#originX|originX} + * @method getOriginX + * @memberOf fabric.Object.prototype + * @return {String} originX value + */ + + /** + * Sets object's {@link fabric.Object#originX|originX} + * @method setOriginX + * @memberOf fabric.Object.prototype + * @param {String} value originX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#originY|originY} + * @method getOriginY + * @memberOf fabric.Object.prototype + * @return {String} originY value + */ + + /** + * Sets object's {@link fabric.Object#originY|originY} + * @method setOriginY + * @memberOf fabric.Object.prototype + * @param {String} value originY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#fill|fill} + * @method getFill + * @memberOf fabric.Object.prototype + * @return {String} Fill value + */ + + /** + * Sets object's {@link fabric.Object#fill|fill} + * @method setFill + * @memberOf fabric.Object.prototype + * @param {String} value Fill value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#opacity|opacity} + * @method getOpacity + * @memberOf fabric.Object.prototype + * @return {Number} Opacity value (0-1) + */ + + /** + * Sets object's {@link fabric.Object#opacity|opacity} + * @method setOpacity + * @memberOf fabric.Object.prototype + * @param {Number} value Opacity value (0-1) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) + * @method getAngle + * @memberOf fabric.Object.prototype + * @return {Number} + */ + + /** + * Sets object's {@link fabric.Object#angle|angle} + * @method setAngle + * @memberOf fabric.Object.prototype + * @param {Number} value Angle value (in degrees) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#top|top position} + * @method getTop + * @memberOf fabric.Object.prototype + * @return {Number} Top value (in pixels) + */ + + /** + * Sets object's {@link fabric.Object#top|top position} + * @method setTop + * @memberOf fabric.Object.prototype + * @param {Number} value Top value (in pixels) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#left|left position} + * @method getLeft + * @memberOf fabric.Object.prototype + * @return {Number} Left value (in pixels) + */ + + /** + * Sets object's {@link fabric.Object#left|left position} + * @method setLeft + * @memberOf fabric.Object.prototype + * @param {Number} value Left value (in pixels) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#scaleX|scaleX} value + * @method getScaleX + * @memberOf fabric.Object.prototype + * @return {Number} scaleX value + */ + + /** + * Sets object's {@link fabric.Object#scaleX|scaleX} value + * @method setScaleX + * @memberOf fabric.Object.prototype + * @param {Number} value scaleX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#scaleY|scaleY} value + * @method getScaleY + * @memberOf fabric.Object.prototype + * @return {Number} scaleY value + */ + + /** + * Sets object's {@link fabric.Object#scaleY|scaleY} value + * @method setScaleY + * @memberOf fabric.Object.prototype + * @param {Number} value scaleY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#flipX|flipX} value + * @method getFlipX + * @memberOf fabric.Object.prototype + * @return {Boolean} flipX value + */ + + /** + * Sets object's {@link fabric.Object#flipX|flipX} value + * @method setFlipX + * @memberOf fabric.Object.prototype + * @param {Boolean} value flipX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#flipY|flipY} value + * @method getFlipY + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value + */ + + /** + * Sets object's {@link fabric.Object#flipY|flipY} value + * @method setFlipY + * @memberOf fabric.Object.prototype + * @param {Boolean} value flipY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Type of an object (rect, circle, path, etc.) + * @type String + * @default + */ + type: 'object', + + /** + * Horizontal origin of transformation of an object (one of "left", "right", "center") + * @type String + * @default + */ + originX: 'left', + + /** + * Vertical origin of transformation of an object (one of "top", "bottom", "center") + * @type String + * @default + */ + originY: 'top', + + /** + * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} + * @type Number + * @default + */ + top: 0, + + /** + * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} + * @type Number + * @default + */ + left: 0, + + /** + * Object width + * @type Number + * @default + */ + width: 0, + + /** + * Object height + * @type Number + * @default + */ + height: 0, + + /** + * Object scale factor (horizontal) + * @type Number + * @default + */ + scaleX: 1, + + /** + * Object scale factor (vertical) + * @type Number + * @default + */ + scaleY: 1, + + /** + * When true, an object is rendered as flipped horizontally + * @type Boolean + * @default + */ + flipX: false, + + /** + * When true, an object is rendered as flipped vertically + * @type Boolean + * @default + */ + flipY: false, + + /** + * Opacity of an object + * @type Number + * @default + */ + opacity: 1, + + /** + * Angle of rotation of an object (in degrees) + * @type Number + * @default + */ + angle: 0, + + /** + * Size of object's controlling corners (in pixels) + * @type Number + * @default + */ + cornerSize: 12, + + /** + * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) + * @type Boolean + * @default + */ + transparentCorners: true, + + /** + * Default cursor value used when hovering over this object on canvas + * @type String + * @default + */ + hoverCursor: null, + + /** + * Padding between object and its controlling borders (in pixels) + * @type Number + * @default + */ + padding: 0, + + /** + * Color of controlling borders of an object (when it's active) + * @type String + * @default + */ + borderColor: 'rgba(102,153,255,0.75)', + + /** + * Color of controlling corners of an object (when it's active) + * @type String + * @default + */ + cornerColor: 'rgba(102,153,255,0.5)', + + /** + * When true, this object will use center point as the origin of transformation + * when being scaled via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, this object will use center point as the origin of transformation + * when being rotated via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: true, + + /** + * Color of object's fill + * @type String + * @default + */ + fill: 'rgb(0,0,0)', + + /** + * Fill rule used to fill an object + * @type String + * @default + */ + fillRule: 'source-over', + + /** + * Background color of an object. Only works with text objects at the moment. + * @type String + * @default + */ + backgroundColor: '', + + /** + * When defined, an object is rendered via stroke and this property specifies its color + * @type String + * @default + */ + stroke: null, + + /** + * Width of a stroke used to render this object + * @type Number + * @default + */ + strokeWidth: 1, + + /** + * Array specifying dash pattern of an object's stroke (stroke must be defined) + * @type Array + */ + strokeDashArray: null, + + /** + * Line endings style of an object's stroke (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'butt', + + /** + * Corner style of an object's stroke (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'miter', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke + * @type Number + * @default + */ + strokeMiterLimit: 10, + + /** + * Shadow object representing shadow of this shape + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Opacity of object's controlling borders when object is active and moving + * @type Number + * @default + */ + borderOpacityWhenMoving: 0.4, + + /** + * Scale factor of object's controlling borders + * @type Number + * @default + */ + borderScaleFactor: 1, + + /** + * Transform matrix (similar to SVG's transform matrix) + * @type Array + */ + transformMatrix: null, + + /** + * Minimum allowed scale value of an object + * @type Number + * @default + */ + minScaleLimit: 0.01, + + /** + * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). + * But events still fire on it. + * @type Boolean + * @default + */ + selectable: true, + + /** + * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 + * @type Boolean + * @default + */ + evented: true, + + /** + * When set to `false`, an object is not rendered on canvas + * @type Boolean + * @default + */ + visible: true, + + /** + * When set to `false`, object's controls are not displayed and can not be used to manipulate object + * @type Boolean + * @default + */ + hasControls: true, + + /** + * When set to `false`, object's controlling borders are not rendered + * @type Boolean + * @default + */ + hasBorders: true, + + /** + * When set to `false`, object's controlling rotating point will not be visible or selectable + * @type Boolean + * @default + */ + hasRotatingPoint: true, + + /** + * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) + * @type Number + * @default + */ + rotatingPointOffset: 40, + + /** + * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * When `false`, default object's values are not included in its serialization + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Function that determines clipping of an object (context is passed as a first argument) + * Note that context origin is at the object's center point (not left/top corner) + * @type Function + */ + clipTo: null, + + /** + * When `true`, object horizontal movement is locked + * @type Boolean + * @default + */ + lockMovementX: false, + + /** + * When `true`, object vertical movement is locked + * @type Boolean + * @default + */ + lockMovementY: false, + + /** + * When `true`, object rotation is locked + * @type Boolean + * @default + */ + lockRotation: false, + + /** + * When `true`, object horizontal scaling is locked + * @type Boolean + * @default + */ + lockScalingX: false, + + /** + * When `true`, object vertical scaling is locked + * @type Boolean + * @default + */ + lockScalingY: false, + + /** + * When `true`, object non-uniform scaling is locked + * @type Boolean + * @default + */ + lockUniScaling: false, + + /** + * When `true`, object cannot be flipped by scaling into negative values + * @type Boolean + * @default + */ + + lockScalingFlip: false, + /** + * List of properties to consider when checking if state + * of an object is changed (fabric.Object#hasStateChanged) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: ( + 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + + 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + + 'angle opacity fill fillRule shadow clipTo visible backgroundColor' + ).split(' '), + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initGradient: function(options) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { + this.set('fill', new fabric.Gradient(options.fill)); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initPattern: function(options) { + if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { + this.set('fill', new fabric.Pattern(options.fill)); + } + if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { + this.set('stroke', new fabric.Pattern(options.stroke)); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initClipping: function(options) { + if (!options.clipTo || typeof options.clipTo !== 'string') { + return; + } + + var functionBody = fabric.util.getFunctionBody(options.clipTo); + if (typeof functionBody !== 'undefined') { + this.clipTo = new Function('ctx', functionBody); + } + }, + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + this._initGradient(options); + this._initPattern(options); + this._initClipping(options); + }, + + /** + * Transforms context when rendering an object + * @param {CanvasRenderingContext2D} ctx Context + * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node + */ + transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } + ctx.globalAlpha = this.opacity; + + var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + ctx.scale( + this.scaleX * (this.flipX ? -1 : 1), + this.scaleY * (this.flipY ? -1 : 1) + ); + }, + + /** + * Returns an object representation of an instance + * @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) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; + + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /** + * Returns (dataless) object representation of an instance + * @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) { + // will be overwritten by subclasses + return this.toObject(propertiesToInclude); + }, + + /** + * @private + * @param {Object} object + */ + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; + + stateProperties.forEach(function(prop) { + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + }); + + return object; + }, + + /** + * Returns a string representation of an instance + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Basic getter + * @param {String} property Property name + * @return {Any} value of a property + */ + get: function(property) { + return this[property]; + }, + + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + if (typeof key === 'object') { + this._setObject(key); + } + else { + if (typeof value === 'function' && key !== 'clipTo') { + this._set(key, value(this.get(key))); + } + else { + this._set(key, value); + } + } + return this; + }, + + /** + * @private + * @param {String} key + * @param {Any} value + * @return {fabric.Object} thisArg + */ + _set: function(key, value) { + var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); + + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === 'scaleX' && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } + else if (key === 'scaleY' && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } + else if (key === 'width' || key === 'height') { + this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); + } + else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + + this[key] = value; + + return this; + }, + + /** + * Toggles specified property from `true` to `false` or from `false` to `true` + * @param {String} property Property to toggle + * @return {fabric.Object} thisArg + * @chainable + */ + toggle: function(property) { + var value = this.get(property); + if (typeof value === 'boolean') { + this.set(property, !value); + } + return this; + }, + + /** + * Sets sourcePath of an object + * @param {String} value Value to set sourcePath to + * @return {fabric.Object} thisArg + * @chainable + */ + setSourcePath: function(value) { + this.sourcePath = value; + return this; + }, + + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value // TODO + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) { + return this.canvas.viewportTransform; + } + return [1, 0, 0, 1, 0, 0]; + }, + + /** + * Renders an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + render: function(ctx, noTransform) { + // do not render if width/height are zeros or object is not visible + if (this.width === 0 || this.height === 0 || !this.visible) { + return; + } + + ctx.save(); + + //setup fill rule for current object + this._setupFillRule(ctx); + + this._transform(ctx, noTransform); + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + + if (this.group && this.group.type === 'path-group') { + ctx.translate(-this.group.width/2, -this.group.height/2); + var m = this.transformMatrix; + if (m) { + ctx.transform.apply(ctx, m); + } + } + ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + this._render(ctx, noTransform); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + this._restoreFillRule(ctx); + + ctx.restore(); + }, + + _transform: function(ctx, noTransform) { + var m = this.transformMatrix; + + if (m && !this.group) { + ctx.setTransform.apply(ctx, m); + } + if (!noTransform) { + this.transform(ctx); + } + }, + + _setStrokeStyles: function(ctx) { + if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; + } + }, + + _setFillStyles: function(ctx) { + if (this.fill) { + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) + : this.fill; + } + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + var vpt = this.getViewportTransform(); + + ctx.save(); + if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + this.drawBorders(ctx); + this.drawControls(ctx); + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _removeShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderFill: function(ctx) { + if (!this.fill) { + return; + } + + ctx.save(); + if (this.fill.toLive) { + ctx.translate( + -this.width / 2 + this.fill.offsetX || 0, + -this.height / 2 + this.fill.offsetY || 0); + } + if (this.fill.gradientTransform) { + var g = this.fill.gradientTransform; + ctx.transform.apply(ctx, g); + } + if (this.fillRule === 'destination-over') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } + ctx.restore(); + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderStroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + + ctx.save(); + if (this.strokeDashArray) { + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + + if (supportsLineDash) { + ctx.setLineDash(this.strokeDashArray); + this._stroke && this._stroke(ctx); + } + else { + this._renderDashedStroke && this._renderDashedStroke(ctx); + } + ctx.stroke(); + } + else { + if (this.stroke.gradientTransform) { + var g = this.stroke.gradientTransform; + ctx.transform.apply(ctx, g); + } + this._stroke ? this._stroke(ctx) : ctx.stroke(); + } + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Clones an instance + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {fabric.Object} clone of an instance + */ + clone: function(callback, propertiesToInclude) { + if (this.constructor.fromObject) { + return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + } + return new fabric.Object(this.toObject(propertiesToInclude)); + }, + + /** + * Creates an instance of fabric.Image out of an object + * @param {Function} callback callback, invoked with an instance as a first argument + * @return {fabric.Object} thisArg + */ + cloneAsImage: function(callback) { + var dataUrl = this.toDataURL(); + fabric.util.loadImage(dataUrl, function(img) { + if (callback) { + callback(new fabric.Image(img)); + } + }); + return this; + }, + + /** + * Converts an object into a data-url-like string + * @param {Object} options Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + */ + toDataURL: function(options) { + options || (options = { }); + + var el = fabric.util.createCanvasElement(), + boundingRect = this.getBoundingRect(); + + el.width = boundingRect.width; + el.height = boundingRect.height; + + fabric.util.wrapElement(el, 'div'); + var canvas = new fabric.Canvas(el); + + // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 + if (options.format === 'jpg') { + options.format = 'jpeg'; + } + + if (options.format === 'jpeg') { + canvas.backgroundColor = '#fff'; + } + + var origParams = { + active: this.get('active'), + left: this.getLeft(), + top: this.getTop() + }; + + this.set('active', false); + this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); + + var originalCanvas = this.canvas; + canvas.add(this); + var data = canvas.toDataURL(options); + + this.set(origParams).setCoords(); + this.canvas = originalCanvas; + + canvas.dispose(); + canvas = null; + + return data; + }, + + /** + * Returns true if specified type is identical to the type of an instance + * @param {String} type Type to check against + * @return {Boolean} + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 0; + }, + + /** + * Returns a JSON representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON + */ + toJSON: function(propertiesToInclude) { + // delegate, not alias + return this.toObject(propertiesToInclude); + }, + + /** + * Sets gradient (fill or stroke) of an object + * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 + * @param {String} property Property name 'stroke' or 'fill' + * @param {Object} [options] Options object + * @param {String} [options.type] Type of gradient 'radial' or 'linear' + * @param {Number} [options.x1=0] x-coordinate of start point + * @param {Number} [options.y1=0] y-coordinate of start point + * @param {Number} [options.x2=0] x-coordinate of end point + * @param {Number} [options.y2=0] y-coordinate of end point + * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) + * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) + * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} + * @example Set linear gradient + * object.setGradient('fill', { + * type: 'linear', + * x1: -object.width / 2, + * y1: 0, + * x2: object.width / 2, + * y2: 0, + * colorStops: { + * 0: 'red', + * 0.5: '#005555', + * 1: 'rgba(0,0,255,0.5)' + * } + * }); + * canvas.renderAll(); + * @example Set radial gradient + * object.setGradient('fill', { + * type: 'radial', + * x1: 0, + * y1: 0, + * x2: 0, + * y2: 0, + * r1: object.width / 2, + * r2: 10, + * colorStops: { + * 0: 'red', + * 0.5: '#005555', + * 1: 'rgba(0,0,255,0.5)' + * } + * }); + * canvas.renderAll(); + */ + setGradient: function(property, options) { + options || (options = { }); + + var gradient = { colorStops: [] }; + + gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); + gradient.coords = { + x1: options.x1, + y1: options.y1, + x2: options.x2, + y2: options.y2 + }; + + if (options.r1 || options.r2) { + gradient.coords.r1 = options.r1; + gradient.coords.r2 = options.r2; + } + + for (var position in options.colorStops) { + var color = new fabric.Color(options.colorStops[position]); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + + return this.set(property, fabric.Gradient.forObject(this, gradient)); + }, + + /** + * Sets pattern fill of an object + * @param {Object} options Options object + * @param {(String|HTMLImageElement)} options.source Pattern source + * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) + * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner + * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} + * @example Set pattern + * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { + * object.setPatternFill({ + * source: img, + * repeat: 'repeat' + * }); + * canvas.renderAll(); + * }); + */ + setPatternFill: function(options) { + return this.set('fill', new fabric.Pattern(options)); + }, + + /** + * Sets {@link fabric.Object#shadow|shadow} of an object + * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") + * @param {String} [options.color=rgb(0,0,0)] Shadow color + * @param {Number} [options.blur=0] Shadow blur + * @param {Number} [options.offsetX=0] Shadow horizontal offset + * @param {Number} [options.offsetY=0] Shadow vertical offset + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} + * @example Set shadow with string notation + * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); + * canvas.renderAll(); + * @example Set shadow with object notation + * object.setShadow({ + * color: 'red', + * blur: 10, + * offsetX: 20, + * offsetY: 20 + * }); + * canvas.renderAll(); + */ + setShadow: function(options) { + return this.set('shadow', options ? new fabric.Shadow(options) : null); + }, + + /** + * Sets "color" of an instance (alias of `set('fill', …)`) + * @param {String} color Color value + * @return {fabric.Object} thisArg + * @chainable + */ + setColor: function(color) { + this.set('fill', color); + return this; + }, + + /** + * Sets "angle" of an instance + * @param {Number} angle Angle value + * @return {fabric.Object} thisArg + * @chainable + */ + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + + /** + * Centers object horizontally on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerH: function () { + this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerV: function () { + this.canvas.centerObjectV(this); + return this; + }, + + /** + * Centers object vertically and horizontally on canvas to which is was added last + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + this.canvas.centerObject(this); + return this; + }, + + /** + * Removes object from canvas to which it was added last + * @return {fabric.Object} thisArg + * @chainable + */ + remove: function() { + this.canvas.remove(this); + return this; + }, + + /** + * Returns coordinates of a pointer relative to an object + * @param {Event} e Event to operate upon + * @param {Object} [pointer] Pointer to operate upon (instead of event) + * @return {Object} Coordinates of a pointer (x, y) + */ + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); + return { + x: pointer.x - objectLeftTop.x, + y: pointer.y - objectLeftTop.y + }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specifed using fillRule property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupFillRule: function (ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + + /** + * Restores previously saved canvas globalCompositeOperation after obeject rendering + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _restoreFillRule: function (ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } + } + }); + + fabric.util.createAccessors(fabric.Object); + + /** + * Alias for {@link fabric.Object.prototype.setAngle} + * @alias rotate -> setAngle + * @memberof fabric.Object + */ + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + + extend(fabric.Object.prototype, fabric.Observable); + + /** + * Defines the number of fraction digits to use when serializing object values. + * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. + * @static + * @memberof fabric.Object + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; + + /** + * Unique id used internally when creating SVG elements + * @static + * @memberof fabric.Object + * @type Number + */ + fabric.Object.__uid = 0; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Translates the coordinates from origin to center coordinates (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToCenterPoint: function(point, originX, originY) { + var cx = point.x, + cy = point.y, + strokeWidth = this.stroke ? this.strokeWidth : 0; + + if (originX === 'left') { + cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + + if (originY === 'top') { + cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + + // Apply the reverse rotation to the point (it's already scaled properly) + return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); + }, + + /** + * Translates the coordinates from center to origin coordinates (based on the object's dimensions) + * @param {fabric.Point} center The point which corresponds to center of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToOriginPoint: function(center, originX, originY) { + var x = center.x, + y = center.y, + strokeWidth = this.stroke ? this.strokeWidth : 0; + + // Get the point coordinates + if (originX === 'left') { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === 'top') { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + + // Apply the rotation to the point (it's already scaled properly) + return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); + }, + + /** + * Returns the real center coordinates of the object + * @return {fabric.Point} + */ + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + + /** + * Returns the coordinates of the object based on center coordinates + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @return {fabric.Point} + */ + // getOriginPoint: function(center) { + // return this.translateToOriginPoint(center, this.originX, this.originY); + // }, + + /** + * Returns the coordinates of the object as if it has a different origin + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + + /** + * Returns the point in local coordinates + * @param {fabric.Point} point The point relative to the global coordinate system + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), + strokeWidth = this.stroke ? this.strokeWidth : 0, + x, y; + + if (originX && originY) { + if (originX === 'left') { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else { + x = center.x; + } + + if (originY === 'top') { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else { + y = center.y; + } + } + else { + x = this.left; + y = this.top; + } + + return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) + .subtractEquals(new fabric.Point(x, y)); + }, + + /** + * Returns the point in global coordinates + * @param {fabric.Point} The point relative to the local coordinate system + * @return {fabric.Point} + */ + // toGlobalPoint: function(point) { + // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); + // }, + + /** + * Sets the position of the object taking into consideration the object's origin + * @param {fabric.Point} pos The new position of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {void} + */ + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); + + this.set('left', position.x); + this.set('top', position.y); + }, + + /** + * @param {String} to One of 'left', 'center', 'right' + */ + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), + hypotHalf = this.getWidth() / 2, + xHalf = Math.cos(angle) * hypotHalf, + yHalf = Math.sin(angle) * hypotHalf, + hypotFull = this.getWidth(), + xFull = Math.cos(angle) * hypotFull, + yFull = Math.sin(angle) * hypotFull; + + if (this.originX === 'center' && to === 'left' || + this.originX === 'right' && to === 'center') { + // move half left + this.left -= xHalf; + this.top -= yHalf; + } + else if (this.originX === 'left' && to === 'center' || + this.originX === 'center' && to === 'right') { + // move half right + this.left += xHalf; + this.top += yHalf; + } + else if (this.originX === 'left' && to === 'right') { + // move full right + this.left += xFull; + this.top += yFull; + } + else if (this.originX === 'right' && to === 'left') { + // move full left + this.left -= xFull; + this.top -= yFull; + } + + this.setCoords(); + this.originX = to; + }, + + /** + * Sets the origin/position of the object to it's center point + * @private + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * Resets the origin/position of the object to it's original origin + * @private + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + + /** + * @private + */ + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); + } + }); + +})(); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Object containing coordinates of object's controls + * @type Object + * @default + */ + oCoords: null, + + /** + * Checks if object intersects with an area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @return {Boolean} true if object intersects with an area formed by 2 points + */ + intersectsWithRect: function(pointTL, pointBR) { + var oCoords = this.oCoords, + tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br = new fabric.Point(oCoords.br.x, oCoords.br.y), + intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + pointTL, + pointBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object intersects with another object + * @param {Object} other Object to test + * @return {Boolean} true if object intersects with another object + */ + intersectsWithObject: function(other) { + // extracts coords + function getCoords(oCoords) { + return { + tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y), + tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y), + bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y), + br: new fabric.Point(oCoords.br.x, oCoords.br.y) + }; + } + var thisCoords = getCoords(this.oCoords), + otherCoords = getCoords(other.oCoords), + intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); + + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object is fully contained within area of another object + * @param {Object} other Object to test + * @return {Boolean} true if object is fully contained within area of another object + */ + isContainedWithinObject: function(other) { + var boundingRect = other.getBoundingRect(), + point1 = new fabric.Point(boundingRect.left, boundingRect.top), + point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); + + return this.isContainedWithinRect(point1, point2); + }, + + /** + * Checks if object is fully contained within area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @return {Boolean} true if object is fully contained within area formed by 2 points + */ + isContainedWithinRect: function(pointTL, pointBR) { + var boundingRect = this.getBoundingRect(); + + return ( + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y + ); + }, + + /** + * Checks if point is inside the object + * @param {fabric.Point} point Point to check against + * @return {Boolean} true if point is inside the object + */ + containsPoint: function(point) { + var lines = this._getImageLines(this.oCoords), + xPoints = this._findCrossPoints(point, lines); + + // if xPoints is odd then point is inside the object + return (xPoints !== 0 && xPoints % 2 === 1); + }, + + /** + * Method that returns an object with the object edges in it, given the coordinates of the corners + * @private + * @param {Object} oCoords Coordinates of the object corners + */ + _getImageLines: function(oCoords) { + return { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + }; + }, + + /** + * Helper method to determine how many cross points are between the 4 object edges + * and the horizontal line determined by a point on canvas + * @private + * @param {fabric.Point} point Point to check + * @param {Object} oCoords Coordinates of the object being evaluated + */ + _findCrossPoints: function(point, oCoords) { + var b1, b2, a1, a2, xi, yi, + xcount = 0, + iLine; + + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + // optimisation 1: line below point. no cross + if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { + continue; + } + // optimisation 2: line above point. no cross + if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { + xi = iLine.o.x; + yi = point.y; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + + xi = - (a1 - a2) / (b1 - b2); + yi = a1 + b1 * xi; + } + // dont count xi < point.x cases + if (xi >= point.x) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount === 2) { + break; + } + } + return xcount; + }, + + /** + * Returns width of an object's bounding rectangle + * @deprecated since 1.0.4 + * @return {Number} width value + */ + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + + /** + * Returns height of an object's bounding rectangle + * @deprecated since 1.0.4 + * @return {Number} height value + */ + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * @return {Object} Object with left, top, width, height properties + */ + getBoundingRect: function() { + this.oCoords || this.setCoords(); + + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], + minX = fabric.util.array.min(xCoords), + maxX = fabric.util.array.max(xCoords), + width = Math.abs(minX - maxX), + + yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], + minY = fabric.util.array.min(yCoords), + maxY = fabric.util.array.max(yCoords), + height = Math.abs(minY - maxY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns width of an object + * @return {Number} width value + */ + getWidth: function() { + return this.width * this.scaleX; + }, + + /** + * Returns height of an object + * @return {Number} height value + */ + getHeight: function() { + return this.height * this.scaleY; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @param {Number} value + * @return {Number} + */ + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } + else { + return this.minScaleLimit; + } + } + return value; + }, + + /** + * Scales an object (equally by x and y) + * @param {Number} value Scale factor + * @return {fabric.Object} thisArg + * @chainable + */ + scale: function(value) { + value = this._constrainScale(value); + + if (value < 0) { + this.flipX = !this.flipX; + this.flipY = !this.flipY; + value *= -1; + } + + this.scaleX = value; + this.scaleY = value; + this.setCoords(); + return this; + }, + + /** + * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New width value + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToWidth: function(value) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + + /** + * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New height value + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToHeight: function(value) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + + /** + * Sets corner position coordinates based on current angle, width and height + * @return {fabric.Object} thisArg + * @chainable + */ + setCoords: function() { + var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, + theta = degreesToRadians(this.angle), + vpt = this.getViewportTransform(), + f = function (p) { + return fabric.util.transformPoint(p, vpt); + }, + w = this.width, + h = this.height, + capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', + vLine = this.type === 'line' && this.width === 1, + hLine = this.type === 'line' && this.height === 1, + strokeW = (capped && hLine) || this.type !== 'line', + strokeH = (capped && vLine) || this.type !== 'line'; + + if (vLine) { + w = strokeWidth; + } + else if (hLine) { + h = strokeWidth; + } + if (strokeW) { + w += strokeWidth; + } + if (strokeH) { + h += strokeWidth; + } + this.currentWidth = w * this.scaleX; + this.currentHeight = h * this.scaleY; + + // If width is negative, make postive. Fixes path selection issue + if (this.currentWidth < 0) { + this.currentWidth = Math.abs(this.currentWidth); + } + + var _hypotenuse = Math.sqrt( + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)), + + _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), + + // offset added for rotate and scale actions + offsetX = Math.cos(_angle + theta) * _hypotenuse, + offsetY = Math.sin(_angle + theta) * _hypotenuse, + sinTh = Math.sin(theta), + cosTh = Math.cos(theta), + coords = this.getCenterPoint(), + wh = new fabric.Point(this.currentWidth, this.currentHeight), + _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), + _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), + _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), + _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), + tl = f(_tl), + tr = f(_tr), + br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), + bl = f(_bl), + ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), + mt = f(_mt), + mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), + mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), + mtr = f(new fabric.Point(_mt.x, _mt.y)), + + // padding + padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), + padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + + // debugging + + // setTimeout(function() { + // canvas.contextTop.fillStyle = 'green'; + // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3); + // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3); + // canvas.contextTop.fillRect(br.x, br.y, 3, 3); + // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3); + // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3); + // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3); + // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3); + // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3); + // }, 50); + + this.oCoords = { + // corners + tl: tl, tr: tr, br: br, bl: bl, + // middle + ml: ml, mt: mt, mr: mr, mb: mb, + // rotating point + mtr: mtr + }; + + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this._setCornerCoords && this._setCornerCoords(); + + return this; + } + }); +})(); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Moves an object to the bottom of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } + else { + this.canvas.sendToBack(this); + } + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } + else { + this.canvas.bringToFront(this); + } + return this; + }, + + /** + * Moves an object down in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } + else { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } + else { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {Number} index New position of object + * @return {fabric.Object} thisArg + * @chainable + */ + moveTo: function(index) { + if (this.group) { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } + else { + this.canvas.moveTo(this, index); + } + return this; + } +}); + + +/* _TO_SVG_START_ */ +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns styles-string for svg-export + * @return {String} + */ + getSvgStyles: function() { + + var fill = this.fill + ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) + : 'none', + fillRule = (this.fillRule === 'destination-over' ? 'evenodd' : this.fillRule), + stroke = this.stroke + ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) + : 'none', + + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', + + visibility = this.visible ? '' : ' visibility: hidden;', + filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + + return [ + 'stroke: ', stroke, '; ', + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + 'fill: ', fill, '; ', + 'fill-rule: ', fillRule, '; ', + 'opacity: ', opacity, ';', + filter, + visibility + ].join(''); + }, + + /** + * Returns transform-string for svg-export + * @return {String} + */ + getSvgTransform: function() { + if (this.group) { + return ''; + } + var toFixed = fabric.util.toFixed, + angle = this.getAngle(), + vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], + center = fabric.util.transformPoint(this.getCenterPoint(), vpt), + + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + translatePart = this.type === 'path-group' ? '' : 'translate(' + + toFixed(center.x, NUM_FRACTION_DIGITS) + + ' ' + + toFixed(center.y, NUM_FRACTION_DIGITS) + + ')', + + anglePart = angle !== 0 + ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') + : '', + + scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) + ? '' : + (' scale(' + + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + + ' ' + + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + + ')'), + + addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, + + flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', + + addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, + + flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; + + return [ + translatePart, anglePart, scalePart, flipXPart, flipYPart + ].join(''); + }, + + /** + * Returns transform-string for svg-export from the transform matrix of single elements + * @return {String} + */ + getSvgTransformMatrix: function() { + return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; + }, + + /** + * @private + */ + _createBaseSVGMarkup: function() { + var markup = [ ]; + + if (this.fill && this.fill.toLive) { + markup.push(this.fill.toSVG(this, false)); + } + if (this.stroke && this.stroke.toLive) { + markup.push(this.stroke.toSVG(this, false)); + } + if (this.shadow) { + markup.push(this.shadow.toSVG(this)); + } + return markup; + } +}); +/* _TO_SVG_END_ */ + + +/* + Depends on `stateProperties` +*/ +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns true if object state (one of its state properties) was changed + * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called + */ + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this.get(prop) !== this.originalState[prop]; + }, this); + }, + + /** + * Saves state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + saveState: function(options) { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + + if (options && options.stateProperties) { + options.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + } + + return this; + }, + + /** + * Setups state of an object + * @return {fabric.Object} thisArg + */ + setupState: function() { + this.originalState = { }; + this.saveState(); + + return this; + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Line) { + fabric.warn('fabric.Line is already defined'); + return; + } + + /** + * Line class + * @class fabric.Line + * @extends fabric.Object + * @see {@link fabric.Line#initialize} for constructor definition + */ + fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'line', + + /** + * x value or first line edge + * @type Number + * @default + */ + x1: 0, + + /** + * y value or first line edge + * @type Number + * @default + */ + y1: 0, + + /** + * x value or second line edge + * @type Number + * @default + */ + x2: 0, + + /** + * y value or second line edge + * @type Number + * @default + */ + y2: 0, + + /** + * Constructor + * @param {Array} [points] Array of points + * @param {Object} [options] Options object + * @return {fabric.Line} thisArg + */ + initialize: function(points, options) { + options = options || { }; + + if (!points) { + points = [0, 0, 0, 0]; + } + + this.callSuper('initialize', options); + + this.set('x1', points[0]); + this.set('y1', points[1]); + this.set('x2', points[2]); + this.set('y2', points[3]); + + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Object} [options] Options + */ + _setWidthHeight: function(options) { + options || (options = { }); + + this.width = Math.abs(this.x2 - this.x1) || 1; + this.height = Math.abs(this.y2 - this.y1) || 1; + + this.left = 'left' in options + ? options.left + : this._getLeftToOriginX(); + + this.top = 'top' in options + ? options.top + : this._getTopToOriginY(); + }, + + /** + * @private + * @param {String} key + * @param {Any} value + */ + _set: function(key, value) { + this[key] = value; + if (typeof coordProps[key] !== 'undefined') { + this._setWidthHeight(); + } + return this; + }, + + /** + * @private + * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. + */ + _getLeftToOriginX: makeEdgeToOriginGetter( + { // property names + origin: 'originX', + axis1: 'x1', + axis2: 'x2', + dimension: 'width' + }, + { // possible values of origin + nearest: 'left', + center: 'center', + farthest: 'right' + } + ), + + /** + * @private + * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. + */ + _getTopToOriginY: makeEdgeToOriginGetter( + { // property names + origin: 'originY', + axis1: 'y1', + axis2: 'y2', + dimension: 'height' + }, + { // possible values of origin + nearest: 'top', + center: 'center', + farthest: 'bottom' + } + ), + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + + if (noTransform) { + // Line coords are distances from left-top of canvas to origin of line. + // + // To render line in a path-group, we need to translate them to + // distances from center of path-group to center of line. + var cp = this.getCenterPoint(); + ctx.translate( + cp.x, + cp.y + ); + } + + if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { + + // move from center (of virtual box) to its left/top corner + // we can't assume x1, y1 is top left and x2, y2 is bottom right + var xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1; + + ctx.moveTo( + this.width === 1 ? 0 : (xMult * this.width / 2), + this.height === 1 ? 0 : (yMult * this.height / 2)); + + ctx.lineTo( + this.width === 1 ? 0 : (xMult * -1 * this.width / 2), + this.height === 1 ? 0 : (yMult * -1 * this.height / 2)); + } + + ctx.lineWidth = this.strokeWidth; + + // TODO: test this + // make sure setting "fill" changes color of a line + // (by copying fillStyle to strokeStyle, since line is stroked, not filled) + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var + xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1, + x = this.width === 1 ? 0 : xMult * this.width / 2, + y = this.height === 1 ? 0 : yMult * this.height / 2; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @methd toObject + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + x1: this.get('x1'), + y1: this.get('y1'), + x2: this.get('x2'), + y2: this.get('y2') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), addTranslate = ''; + if (!this.group) { + var x = - this.width / 2 - (this.x1 > this.x2 ? this.x2 : this.x1), + y = - this.height / 2 - (this.y1 > this.y2 ? this.y2 : this.y1); + addTranslate = 'translate(' + x + ', ' + y + ') '; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) + * @static + * @memberOf fabric.Line + * @see http://www.w3.org/TR/SVG/shapes.html#LineElement + */ + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); + + /** + * Returns fabric.Line instance from an SVG element + * @static + * @memberOf fabric.Line + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Line} instance of fabric.Line + */ + fabric.Line.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), + points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; + return new fabric.Line(points, extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Line instance from an object representation + * @static + * @memberOf fabric.Line + * @param {Object} object Object to create an instance from + * @return {fabric.Line} instance of fabric.Line + */ + fabric.Line.fromObject = function(object) { + var points = [object.x1, object.y1, object.x2, object.y2]; + return new fabric.Line(points, object); + }; + + /** + * Produces a function that calculates distance from canvas edge to Line origin. + */ + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, + axis1 = propertyNames.axis1, + axis2 = propertyNames.axis2, + dimension = propertyNames.dimension, + nearest = originValues.nearest, + center = originValues.center, + farthest = originValues.farthest; + + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + + } + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2, + extend = fabric.util.object.extend; + + if (fabric.Circle) { + fabric.warn('fabric.Circle is already defined.'); + return; + } + + /** + * Circle class + * @class fabric.Circle + * @extends fabric.Object + * @see {@link fabric.Circle#initialize} for constructor definition + */ + fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'circle', + + /** + * Radius of this circle + * @type Number + * @default + */ + radius: 0, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Circle} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + this.set('radius', options.radius || 0); + }, + + /** + * @private + * @param {String} key + * @param {Any} value + * @return {fabric.Circle} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + + if (key === 'radius') { + this.setRadius(value); + } + + return this; + }, + + /** + * Returns object representation of an instance + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + radius: this.get('radius') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0; + if (this.group) { + x = this.left + this.radius; + y = this.top + this.radius; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.arc(noTransform ? this.left + this.radius : 0, noTransform ? this.top + this.radius : 0, this.radius, 0, piBy2, false); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusX: function() { + return this.get('radius') * this.get('scaleX'); + }, + + /** + * Returns vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusY: function() { + return this.get('radius') * this.get('scaleY'); + }, + + /** + * Sets radius of an object (and updates width accordingly) + * @return {Number} + */ + setRadius: function(value) { + this.radius = value; + this.set('width', value * 2).set('height', value * 2); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) + * @static + * @memberOf fabric.Circle + * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement + */ + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); + + /** + * Returns {@link fabric.Circle} instance from an SVG element + * @static + * @memberOf fabric.Circle + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @throws {Error} If value of `r` attribute is missing or invalid + * @return {fabric.Circle} Instance of fabric.Circle + */ + fabric.Circle.fromElement = function(element, options) { + options || (options = { }); + + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + + if (!isValidRadius(parsedAttributes)) { + throw new Error('value of `r` attribute is required and can not be negative'); + } + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + var obj = new fabric.Circle(extend(parsedAttributes, options)); + + obj.left -= obj.radius; + obj.top -= obj.radius; + return obj; + }; + + /** + * @private + */ + function isValidRadius(attributes) { + return (('radius' in attributes) && (attributes.radius > 0)); + } + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Circle} instance from an object representation + * @static + * @memberOf fabric.Circle + * @param {Object} object Object to create an instance from + * @return {Object} Instance of fabric.Circle + */ + fabric.Circle.fromObject = function(object) { + return new fabric.Circle(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Triangle) { + fabric.warn('fabric.Triangle is already defined'); + return; + } + + /** + * Triangle class + * @class fabric.Triangle + * @extends fabric.Object + * @return {fabric.Triangle} thisArg + * @see {@link fabric.Triangle#initialize} for constructor definition + */ + fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'triangle', + + /** + * Constructor + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + + this.set('width', options.width || 100) + .set('height', options.height || 100); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); + ctx.closePath(); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), + widthBy2 = this.width / 2, + heightBy2 = this.height / 2, + points = [ + -widthBy2 + ' ' + heightBy2, + '0 ' + -heightBy2, + widthBy2 + ' ' + heightBy2 + ] + .join(','); + + markup.push( + '' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /** + * Returns fabric.Triangle instance from an object representation + * @static + * @memberOf fabric.Triangle + * @param {Object} object Object to create an instance from + * @return {Object} instance of Canvas.Triangle + */ + fabric.Triangle.fromObject = function(object) { + return new fabric.Triangle(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global){ + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2, + extend = fabric.util.object.extend; + + if (fabric.Ellipse) { + fabric.warn('fabric.Ellipse is already defined.'); + return; + } + + /** + * Ellipse class + * @class fabric.Ellipse + * @extends fabric.Object + * @return {fabric.Ellipse} thisArg + * @see {@link fabric.Ellipse#initialize} for constructor definition + */ + fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'ellipse', + + /** + * Horizontal radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical radius + * @type Number + * @default + */ + ry: 0, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Ellipse} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + + this.set('rx', options.rx || 0); + this.set('ry', options.ry || 0); + + this.set('width', this.get('rx') * 2); + this.set('height', this.get('ry') * 2); + }, + + /** + * Returns object representation of an instance + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + rx: this.get('rx'), + ry: this.get('ry') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0; + if (this.group) { + x = this.left + this.rx; + y = this.top + this.ry; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.save(); + ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); + ctx.arc(noTransform ? this.left + this.rx : 0, noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, this.rx, 0, piBy2, false); + ctx.restore(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) + * @static + * @memberOf fabric.Ellipse + * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); + + /** + * Returns {@link fabric.Ellipse} instance from an SVG element + * @static + * @memberOf fabric.Ellipse + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromElement = function(element, options) { + options || (options = { }); + + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); + + ellipse.top -= ellipse.ry; + ellipse.left -= ellipse.rx; + return ellipse; + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Ellipse} instance from an object representation + * @static + * @memberOf fabric.Ellipse + * @param {Object} object Object to create an instance from + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromObject = function(object) { + return new fabric.Ellipse(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + if (fabric.Rect) { + console.warn('fabric.Rect is already defined'); + return; + } + + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push('rx', 'ry', 'x', 'y'); + + /** + * Rectangle class + * @class fabric.Rect + * @extends fabric.Object + * @return {fabric.Rect} thisArg + * @see {@link fabric.Rect#initialize} for constructor definition + */ + fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { + + /** + * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: stateProperties, + + /** + * Type of an object + * @type String + * @default + */ + type: 'rect', + + /** + * Horizontal border radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical border radius + * @type Number + * @default + */ + ry: 0, + + /** + * Used to specify dash pattern for stroke on this object + * @type Array + */ + strokeDashArray: null, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + this._initRxRy(); + + }, + + /** + * Initializes rx/ry attributes + * @private + */ + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } + else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + + // optimize 1x1 case (used in spray brush) + if (this.width === 1 && this.height === 1) { + ctx.fillRect(0, 0, 1, 1); + return; + } + + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, + ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, + w = this.width, + h = this.height, + x = noTransform ? this.left : -this.width / 2, + y = noTransform ? this.top : -this.height / 2, + isRounded = rx !== 0 || ry !== 0, + k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; + + ctx.beginPath(); + + ctx.moveTo(x + rx, y); + + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + + ctx.closePath(); + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @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) { + var object = extend(this.callSuper('toObject', propertiesToInclude), { + rx: this.get('rx') || 0, + ry: this.get('ry') || 0 + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; + if (!this.group) { + x = -this.width / 2; + y = -this.height / 2; + } + markup.push( + '\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) + * @static + * @memberOf fabric.Rect + * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement + */ + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); + + /** + * Returns {@link fabric.Rect} instance from an SVG element + * @static + * @memberOf fabric.Rect + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Rect} Instance of fabric.Rect + */ + fabric.Rect.fromElement = function(element, options) { + if (!element) { + return null; + } + options = options || { }; + + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + return new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Rect} instance from an object representation + * @static + * @memberOf fabric.Rect + * @param {Object} object Object to create an instance from + * @return {Object} instance of fabric.Rect + */ + fabric.Rect.fromObject = function(object) { + return new fabric.Rect(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + toFixed = fabric.util.toFixed; + + if (fabric.Polyline) { + fabric.warn('fabric.Polyline is already defined'); + return; + } + + /** + * Polyline class + * @class fabric.Polyline + * @extends fabric.Object + * @see {@link fabric.Polyline#initialize} for constructor definition + */ + fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polyline', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + /** + * Constructor + * @param {Array} points Array of points (where each point is an object with x and y) + * @param {Object} [options] Options object + * @param {Boolean} [skipOffset] Whether points offsetting should be skipped + * @return {fabric.Polyline} thisArg + * @example + * var poly = new fabric.Polyline([ + * { x: 10, y: 10 }, + * { x: 50, y: 30 }, + * { x: 40, y: 70 }, + * { x: 60, y: 50 }, + * { x: 100, y: 150 }, + * { x: 40, y: 100 } + * ], { + * stroke: 'red', + * left: 100, + * top: 100 + * }); + */ + initialize: function(points, options) { + options = options || { }; + this.set('points', points); + this.callSuper('initialize', options); + this._calcDimensions(); + }, + + /** + * @private + */ + _calcDimensions: function() { + return fabric.Polygon.prototype._calcDimensions.call(this); + }, + + /** + * @private + */ + _applyPointOffset: function() { + return fabric.Polygon.prototype._applyPointOffset.call(this); + }, + + /** + * Returns object representation of an instance + * @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 fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var points = [], + markup = this._createBaseSVGMarkup(); + + for (var i = 0, len = this.points.length; i < len; i++) { + points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); + } + + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + + if (this._applyPointOffset) { + if (!(this.group && this.group.type === 'path-group')) { + this._applyPointOffset(); + } + this._applyPointOffset = null; + } + + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var p1, p2; + + ctx.beginPath(); + for (var i = 0, len = this.points.length; i < len; i++) { + p1 = this.points[i]; + p2 = this.points[i + 1] || p1; + fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); + } + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.get('points').length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) + * @static + * @memberOf fabric.Polyline + * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns fabric.Polyline instance from an SVG element + * @static + * @memberOf fabric.Polyline + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Polyline} Instance of fabric.Polyline + */ + fabric.Polyline.fromElement = function(element, options) { + if (!element) { + return null; + } + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES); + + if (points === null) { + return null; + } + + return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polyline instance from an object representation + * @static + * @memberOf fabric.Polyline + * @param {Object} object Object to create an instance from + * @return {fabric.Polyline} Instance of fabric.Polyline + */ + fabric.Polyline.fromObject = function(object) { + var points = object.points; + return new fabric.Polyline(points, object, true); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + min = fabric.util.array.min, + max = fabric.util.array.max, + toFixed = fabric.util.toFixed; + + if (fabric.Polygon) { + fabric.warn('fabric.Polygon is already defined'); + return; + } + + /** + * Polygon class + * @class fabric.Polygon + * @extends fabric.Object + * @see {@link fabric.Polygon#initialize} for constructor definition + */ + fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polygon', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + /** + * Constructor + * @param {Array} points Array of points + * @param {Object} [options] Options object + * @return {fabric.Polygon} thisArg + */ + initialize: function(points, options) { + options = options || { }; + this.points = points; + this.callSuper('initialize', options); + this._calcDimensions(); + }, + + /** + * @private + */ + _calcDimensions: function() { + + var points = this.points, + minX = min(points, 'x'), + minY = min(points, 'y'), + maxX = max(points, 'x'), + maxY = max(points, 'y'); + + this.width = (maxX - minX) || 1; + this.height = (maxY - minY) || 1; + + this.left = minX, + this.top = minY; + }, + + /** + * @private + */ + _applyPointOffset: function() { + // change points to offset polygon into a bounding box + // executed one time + this.points.forEach(function(p) { + p.x -= (this.left + this.width / 2); + p.y -= (this.top + this.height / 2); + }, this); + }, + + /** + * Returns object representation of an instance + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + points: this.points.concat() + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var points = [], + markup = this._createBaseSVGMarkup(); + + for (var i = 0, len = this.points.length; i < len; i++) { + points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' '); + } + + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + + if (this._applyPointOffset) { + if (!(this.group && this.group.type === 'path-group')) { + this._applyPointOffset(); + } + this._applyPointOffset = null; + } + + ctx.moveTo(this.points[0].x, this.points[0].y); + for (var i = 0, len = this.points.length; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x, point.y); + } + this._renderFill(ctx); + if (this.stroke || this.strokeDashArray) { + ctx.closePath(); + this._renderStroke(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var p1, p2; + + ctx.beginPath(); + for (var i = 0, len = this.points.length; i < len; i++) { + p1 = this.points[i]; + p2 = this.points[i + 1] || this.points[0]; + fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); + } + ctx.closePath(); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.points.length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) + * @static + * @memberOf fabric.Polygon + * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement + */ + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns {@link fabric.Polygon} instance from an SVG element + * @static + * @memberOf fabric.Polygon + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Polygon} Instance of fabric.Polygon + */ + fabric.Polygon.fromElement = function(element, options) { + if (!element) { + return null; + } + + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES); + + if (points === null) { + return null; + } + + return new fabric.Polygon(points, extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polygon instance from an object representation + * @static + * @memberOf fabric.Polygon + * @param {Object} object Object to create an instance from + * @return {fabric.Polygon} Instance of fabric.Polygon + */ + fabric.Polygon.fromObject = function(object) { + return new fabric.Polygon(object.points, object, true); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + min = fabric.util.array.min, + max = fabric.util.array.max, + extend = fabric.util.object.extend, + _toString = Object.prototype.toString, + drawArc = fabric.util.drawArc, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' + }; + + if (fabric.Path) { + fabric.warn('fabric.Path is already defined'); + return; + } + + /** + * @private + */ + function getX(item) { + if (item[0] === 'H') { + return item[1]; + } + return item[item.length - 2]; + } + + /** + * @private + */ + function getY(item) { + if (item[0] === 'V') { + return item[1]; + } + return item[item.length - 1]; + } + + /** + * Path class + * @class fabric.Path + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} + * @see {@link fabric.Path#initialize} for constructor definition + */ + fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path', + + /** + * Array of path points + * @type Array + * @default + */ + path: null, + + /** + * Constructor + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + * @return {fabric.Path} thisArg + */ + initialize: function(path, options) { + options = options || { }; + + this.setOptions(options); + + if (!path) { + throw new Error('`path` argument is required'); + } + + var fromArray = _toString.call(path) === '[object Array]'; + + this.path = fromArray + ? path + // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) + : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + + if (!this.path) { + return; + } + + if (!fromArray) { + this.path = this._parsePath(); + } + this._initializePath(options); + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initializePath: function (options) { + var isWidthSet = 'width' in options && options.width != null, + isHeightSet = 'height' in options && options.width != null, + isLeftSet = 'left' in options, + isTopSet = 'top' in options, + origLeft = isLeftSet ? this.left : 0, + origTop = isTopSet ? this.top : 0; + + if (!isWidthSet || !isHeightSet) { + extend(this, this._parseDimensions()); + if (isWidthSet) { + this.width = options.width; + } + if (isHeightSet) { + this.height = options.height; + } + } + else { //Set center location relative to given height/width if not specified + if (!isTopSet) { + this.top = this.height / 2; + } + if (!isLeftSet) { + this.left = this.width / 2; + } + } + this.pathOffset = this.pathOffset || + // Save top-left coords as offset + this._calculatePathOffset(origLeft, origTop); + }, + + /** + * @private + * @param {Number} origLeft Original left position + * @param {Number} origTop Original top position + */ + _calculatePathOffset: function (origLeft, origTop) { + return { + x: this.left - origLeft - (this.width / 2), + y: this.top - origTop - (this.height / 2) + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _render: function(ctx, noTransform) { + var current, // current instruction + previous = null, + subpathStartX = 0, + subpathStartY = 0, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + tempX, + tempY, + tempControlX, + tempControlY, + l = -((this.width / 2) + this.pathOffset.x), + t = -((this.height / 2) + this.pathOffset.y); + + if (noTransform) { + l += this.width / 2; + t += this.height / 2; + } + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'l': // lineto, relative + x += current[1]; + y += current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'h': // horizontal lineto, relative + x += current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'H': // horizontal lineto, absolute + x = current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'v': // vertical lineto, relative + y += current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'V': // verical lineto, absolute + y = current[1]; + ctx.lineTo(x + l, y + t); + break; + + case 'm': // moveTo, relative + x += current[1]; + y += current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'c': // bezierCurveTo, relative + tempX = x + current[5]; + tempY = y + current[6]; + controlX = x + current[3]; + controlY = y + current[4]; + ctx.bezierCurveTo( + x + current[1] + l, // x1 + y + current[2] + t, // y1 + controlX + l, // x2 + controlY + t, // y2 + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo( + current[1] + l, + current[2] + t, + controlX + l, + controlY + t, + x + l, + y + t + ); + break; + + case 's': // shorthand cubic bezierCurveTo, relative + + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + + // calculate reflection of previous control points + controlX = controlX ? (2 * x - controlX) : x; + controlY = controlY ? (2 * y - controlY) : y; + + ctx.bezierCurveTo( + controlX + l, + controlY + t, + x + current[1] + l, + y + current[2] + t, + tempX + l, + tempY + t + ); + // set control point to 2nd one of this command + // "... the first control point is assumed to be + // the reflection of the second control point on + // the previous command relative to the current point." + controlX = x + current[1]; + controlY = y + current[2]; + + x = tempX; + y = tempY; + break; + + case 'S': // shorthand cubic bezierCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.bezierCurveTo( + controlX + l, + controlY + t, + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + + // set control point to 2nd one of this command + // "... the first control point is assumed to be + // the reflection of the second control point on + // the previous command relative to the current point." + controlX = current[1]; + controlY = current[2]; + + break; + + case 'q': // quadraticCurveTo, relative + // transform to absolute x,y + tempX = x + current[3]; + tempY = y + current[4]; + + controlX = x + current[1]; + controlY = y + current[2]; + + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'Q': // quadraticCurveTo, absolute + tempX = current[3]; + tempY = current[4]; + + ctx.quadraticCurveTo( + current[1] + l, + current[2] + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = current[1]; + controlY = current[2]; + break; + + case 't': // shorthand quadraticCurveTo, relative + + // transform to absolute x,y + tempX = x + current[1]; + tempY = y + current[2]; + + if (previous[0].match(/[QqTt]/) === null) { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + else if (previous[0] === 't') { + // calculate reflection of previous control points for t + controlX = 2 * x - tempControlX; + controlY = 2 * y - tempControlY; + } + else if (previous[0] === 'q') { + // calculate reflection of previous control points for q + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + + tempControlX = controlX; + tempControlY = controlY; + + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + controlX = x + current[1]; + controlY = y + current[2]; + break; + + case 'T': + tempX = current[1]; + tempY = current[2]; + + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + ctx.quadraticCurveTo( + controlX + l, + controlY + t, + tempX + l, + tempY + t + ); + x = tempX; + y = tempY; + break; + + case 'a': + // TODO: optimize this + drawArc(ctx, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + x + l, + current[7] + y + t + ]); + x += current[6]; + y += current[7]; + break; + + case 'A': + // TODO: optimize this + drawArc(ctx, x + l, y + t, [ + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + l, + current[7] + t + ]); + x = current[6]; + y = current[7]; + break; + + case 'z': + case 'Z': + x = subpathStartX; + y = subpathStartY; + ctx.closePath(); + break; + } + previous = current; + } + }, + + /** + * Renders path on a specified context + * @param {CanvasRenderingContext2D} ctx context to render path on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + render: function(ctx, noTransform) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + if (noTransform) { + ctx.translate(-this.width/2, -this.height/2); + } + var m = this.transformMatrix; + + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (!noTransform) { + this.transform(ctx); + } + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + ctx.beginPath(); + ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; + this._render(ctx, noTransform); + this._renderFill(ctx); + this._renderStroke(ctx); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Returns string representation of an instance + * @return {String} string representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns object representation of an instance + * @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) { + var o = extend(this.callSuper('toObject', propertiesToInclude), { + path: this.path.map(function(item) { return item.slice() }), + pathOffset: this.pathOffset + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + if (this.transformMatrix) { + o.transformMatrix = this.transformMatrix; + } + return o; + }, + + /** + * Returns dataless object representation of an instance + * @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) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.path = this.sourcePath; + } + delete o.sourcePath; + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var chunks = [], + markup = this._createBaseSVGMarkup(); + + for (var i = 0, len = this.path.length; i < len; i++) { + chunks.push(this.path[i].join(' ')); + } + var path = chunks.join(' '); + + markup.push( + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns number representation of an instance complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.path.length; + }, + + /** + * @private + */ + _parsePath: function() { + var result = [ ], + coords = [ ], + currentPath, + parsed, + re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, + match, + coordsStr; + + for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { + currentPath = this.path[i]; + + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + + while ((match = re.exec(coordsStr))) { + coords.push(match[0]); + } + + coordsParsed = [ currentPath.charAt(0) ]; + + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + + var command = coordsParsed[0], + commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; + + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } + else { + result.push(coordsParsed); + } + } + + return result; + }, + + /** + * @private + */ + _parseDimensions: function() { + var aX = [], + aY = [], + previous = { }; + + this.path.forEach(function(item, i) { + this._getCoordsFromCommand(item, i, aX, aY, previous); + }, this); + + var minX = min(aX), + minY = min(aY), + maxX = max(aX), + maxY = max(aY), + deltaX = maxX - minX, + deltaY = maxY - minY, + + o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; + + return o; + }, + + _getCoordsFromCommand: function(item, i, aX, aY, previous) { + var isLowerCase = false; + + if (item[0] !== 'H') { + previous.x = (i === 0) ? getX(item) : getX(this.path[i - 1]); + } + if (item[0] !== 'V') { + previous.y = (i === 0) ? getY(item) : getY(this.path[i - 1]); + } + + // lowercased letter denotes relative position; + // transform to absolute + if (item[0] === item[0].toLowerCase()) { + isLowerCase = true; + } + + var xy = this._getXY(item, isLowerCase, previous), + val; + + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } + + val = parseInt(xy.y, 10); + if (!isNaN(val)) { + aY.push(val); + } + }, + + _getXY: function(item, isLowerCase, previous) { + + // last 2 items in an array of coordinates are the actualy x/y (except H/V), collect them + // TODO (kangax): support relative h/v commands + + var x = isLowerCase + ? previous.x + getX(item) + : item[0] === 'V' + ? previous.x + : getX(item), + + y = isLowerCase + ? previous.y + getY(item) + : item[0] === 'H' + ? previous.y + : getY(item); + + return { x: x, y: y }; + } + }); + + /** + * Creates an instance of fabric.Path from an object + * @static + * @memberOf fabric.Path + * @param {Object} object + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + */ + fabric.Path.fromObject = function(object, callback) { + if (typeof object.path === 'string') { + fabric.loadSVGFromURL(object.path, function (elements) { + var path = elements[0], + pathUrl = object.path; + + delete object.path; + + fabric.util.object.extend(path, object); + path.setSourcePath(pathUrl); + + callback(path); + }); + } + else { + callback(new fabric.Path(object.path, object)); + } + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) + * @static + * @memberOf fabric.Path + * @see http://www.w3.org/TR/SVG/paths.html#PathElement + */ + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); + + /** + * Creates an instance of fabric.Path from an SVG element + * @static + * @memberOf fabric.Path + * @param {SVGElement} element to parse + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + * @param {Object} [options] Options object + */ + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.Path + * @type Boolean + * @default + */ + fabric.Path.async = true; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + invoke = fabric.util.array.invoke, + parentToObject = fabric.Object.prototype.toObject; + + if (fabric.PathGroup) { + fabric.warn('fabric.PathGroup is already defined'); + return; + } + + /** + * Path group class + * @class fabric.PathGroup + * @extends fabric.Path + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} + * @see {@link fabric.PathGroup#initialize} for constructor definition + */ + fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path-group', + + /** + * Fill value + * @type String + * @default + */ + fill: '', + + /** + * Constructor + * @param {Array} paths + * @param {Object} [options] Options object + * @return {fabric.PathGroup} thisArg + */ + initialize: function(paths, options) { + + options = options || { }; + this.paths = paths || [ ]; + + for (var i = this.paths.length; i--; ) { + this.paths[i].group = this; + } + + this.setOptions(options); + + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + + this.setCoords(); + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * Renders this group on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render this instance on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + + var m = this.transformMatrix; + + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + this.transform(ctx); + + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + for (var i = 0, l = this.paths.length; i < l; ++i) { + this.paths[i].render(ctx, true); + } + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Sets certain property to a certain value + * @param {String} prop + * @param {Any} value + * @return {fabric.PathGroup} thisArg + */ + _set: function(prop, value) { + + if (prop === 'fill' && value && this.isSameColor()) { + var i = this.paths.length; + while (i--) { + this.paths[i]._set(prop, value); + } + } + + return this.callSuper('_set', prop, value); + }, + + /** + * Returns object representation of this path group + * @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) { + var o = extend(parentToObject.call(this, propertiesToInclude), { + paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + return o; + }, + + /** + * Returns dataless object representation of this path group + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} dataless object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.paths = this.sourcePath; + } + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var objects = this.getObjects(), + translatePart = 'translate(' + this.left + ' ' + this.top + ')', + markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + + for (var i = 0, len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns a string representation of this path group + * @return {String} string representation of an object + */ + toString: function() { + return '#'; + }, + + /** + * Returns true if all paths in this group are of same color + * @return {Boolean} true if all paths are of the same color (`fill`) + */ + isSameColor: function() { + var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); + return this.getObjects().every(function(path) { + return (path.get('fill') || '').toLowerCase() === firstPathFill; + }); + }, + + /** + * Returns number representation of object's complexity + * @return {Number} complexity + */ + complexity: function() { + return this.paths.reduce(function(total, path) { + return total + ((path && path.complexity) ? path.complexity() : 0); + }, 0); + }, + + /** + * Returns all paths in this path group + * @return {Array} array of path objects included in this path group + */ + getObjects: function() { + return this.paths; + } + }); + + /** + * Creates fabric.PathGroup instance from an object representation + * @static + * @memberOf fabric.PathGroup + * @param {Object} object Object to create an instance from + * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created + */ + fabric.PathGroup.fromObject = function(object, callback) { + if (typeof object.paths === 'string') { + fabric.loadSVGFromURL(object.paths, function (elements) { + + var pathUrl = object.paths; + delete object.paths; + + var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); + + callback(pathGroup); + }); + } + else { + fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { + delete object.paths; + callback(new fabric.PathGroup(enlivenedObjects, object)); + }); + } + }; + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.PathGroup + * @type Boolean + * @default + */ + fabric.PathGroup.async = true; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global){ + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + min = fabric.util.array.min, + max = fabric.util.array.max, + invoke = fabric.util.array.invoke; + + if (fabric.Group) { + return; + } + + // lock-related properties, for use in fabric.Group#get + // to enable locking behavior on group + // when one of its objects has lock-related properties set + var _lockProperties = { + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockUniScaling: true + }; + + /** + * Group class + * @class fabric.Group + * @extends fabric.Object + * @mixes fabric.Collection + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} + * @see {@link fabric.Group#initialize} for constructor definition + */ + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'group', + + /** + * Constructor + * @param {Object} objects Group objects + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(objects, options) { + options = options || { }; + + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + this.originalState = { }; + this.callSuper('initialize'); + + this._calcBounds(); + this._updateObjectsCoords(); + + if (options) { + extend(this, options); + } + this._setOpacityIfSame(); + + this.setCoords(); + this.saveCoords(); + }, + + /** + * @private + */ + _updateObjectsCoords: function() { + this.forEachObject(this._updateObjectCoords, this); + }, + + /** + * @private + */ + _updateObjectCoords: function(object) { + var objectLeft = object.getLeft(), + objectTop = object.getTop(); + + object.set({ + originalLeft: objectLeft, + originalTop: objectTop, + left: objectLeft - this.left, + top: objectTop - this.top + }); + + object.setCoords(); + + // do not display corners of objects enclosed in a group + object.__origHasControls = object.hasControls; + object.hasControls = false; + }, + + /** + * Returns string represenation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Adds an object to a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + addWithUpdate: function(object) { + this._restoreObjectsState(); + if (object) { + this._objects.push(object); + object.group = this; + } + // since _restoreObjectsState set objects inactive + this.forEachObject(this._setObjectActive, this); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + + /** + * @private + */ + _setObjectActive: function(object) { + object.set('active', true); + object.group = this; + }, + + /** + * Removes an object from a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + removeWithUpdate: function(object) { + this._moveFlippedObject(object); + this._restoreObjectsState(); + + // since _restoreObjectsState set objects inactive + this.forEachObject(this._setObjectActive, this); + + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + + return this; + }, + + /** + * @private + */ + _onObjectAdded: function(object) { + object.group = this; + }, + + /** + * @private + */ + _onObjectRemoved: function(object) { + delete object.group; + object.set('active', false); + }, + + /** + * Properties that are delegated to group objects when reading/writing + * @param {Object} delegatedProperties + */ + delegatedProperties: { + fill: true, + opacity: true, + fontFamily: true, + fontWeight: true, + fontSize: true, + fontStyle: true, + lineHeight: true, + textDecoration: true, + textAlign: true, + backgroundColor: true + }, + + /** + * @private + */ + _set: function(key, value) { + if (key in this.delegatedProperties) { + var i = this._objects.length; + this[key] = value; + while (i--) { + this._objects[i].set(key, value); + } + } + else { + this[key] = value; + } + }, + + /** + * Returns object representation of an instance + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + objects: invoke(this._objects, 'toObject', propertiesToInclude) + }); + }, + + /** + * Renders instance on a given context + * @param {CanvasRenderingContext2D} ctx context to render instance on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + this.clipTo && fabric.util.clipContext(this, ctx); + + // the array is now sorted in order of highest first, so start from end + for (var i = 0, len = this._objects.length; i < len; i++) { + this._renderObject(this._objects[i], ctx); + } + + this.clipTo && ctx.restore(); + + ctx.restore(); + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + this.callSuper('_renderControls', ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + + /** + * @private + */ + _renderObject: function(object, ctx) { + var originalHasRotatingPoint = object.hasRotatingPoint; + + // do not render if object is not visible + if (!object.visible) { + return; + } + + object.hasRotatingPoint = false; + + object.render(ctx); + + object.hasRotatingPoint = originalHasRotatingPoint; + }, + + /** + * Retores original state of each of group objects (original state is that which was before group was created). + * @private + * @return {fabric.Group} thisArg + * @chainable + */ + _restoreObjectsState: function() { + this._objects.forEach(this._restoreObjectState, this); + return this; + }, + + /** + * Moves a flipped object to the position where it's displayed + * @private + * @param {fabric.Object} object + * @return {fabric.Group} thisArg + */ + _moveFlippedObject: function(object) { + var oldOriginX = object.get('originX'), + oldOriginY = object.get('originY'), + center = object.getCenterPoint(); + + object.set({ + originX: 'center', + originY: 'center', + left: center.x, + top: center.y + }); + + this._toggleFlipping(object); + + var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); + + object.set({ + originX: oldOriginX, + originY: oldOriginY, + left: newOrigin.x, + top: newOrigin.y + }); + + return this; + }, + + /** + * @private + */ + _toggleFlipping: function(object) { + if (this.flipX) { + object.toggle('flipX'); + object.set('left', -object.get('left')); + object.setAngle(-object.getAngle()); + } + if (this.flipY) { + object.toggle('flipY'); + object.set('top', -object.get('top')); + object.setAngle(-object.getAngle()); + } + }, + + /** + * Restores original state of a specified object in group + * @private + * @param {fabric.Object} object + * @return {fabric.Group} thisArg + */ + _restoreObjectState: function(object) { + this._setObjectPosition(object); + + object.setCoords(); + object.hasControls = object.__origHasControls; + delete object.__origHasControls; + object.set('active', false); + object.setCoords(); + delete object.group; + + return this; + }, + + /** + * @private + */ + _setObjectPosition: function(object) { + var groupLeft = this.getLeft(), + groupTop = this.getTop(), + rotated = this._getRotatedLeftTop(object); + + object.set({ + angle: object.getAngle() + this.getAngle(), + left: groupLeft + rotated.left, + top: groupTop + rotated.top, + scaleX: object.get('scaleX') * this.get('scaleX'), + scaleY: object.get('scaleY') * this.get('scaleY') + }); + }, + + /** + * @private + */ + _getRotatedLeftTop: function(object) { + var groupAngle = this.getAngle() * (Math.PI / 180); + return { + left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + + Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), + + top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + + Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) + }; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + destroy: function() { + this._objects.forEach(this._moveFlippedObject, this); + return this._restoreObjectsState(); + }, + + /** + * Saves coordinates of this instance (to be used together with `hasMoved`) + * @saveCoords + * @return {fabric.Group} thisArg + * @chainable + */ + saveCoords: function() { + this._originalLeft = this.get('left'); + this._originalTop = this.get('top'); + return this; + }, + + /** + * Checks whether this group was moved (since `saveCoords` was called last) + * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called) + */ + hasMoved: function() { + return this._originalLeft !== this.get('left') || + this._originalTop !== this.get('top'); + }, + + /** + * Sets coordinates of all group objects + * @return {fabric.Group} thisArg + * @chainable + */ + setObjectsCoords: function() { + this.forEachObject(function(object) { + object.setCoords(); + }); + return this; + }, + + /** + * @private + */ + _setOpacityIfSame: function() { + var objects = this.getObjects(), + firstValue = objects[0] ? objects[0].get('opacity') : 1, + isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); + + if (isSameOpacity) { + this.opacity = firstValue; + } + }, + + /** + * @private + */ + _calcBounds: function(onlyWidthHeight) { + var aX = [], + aY = [], + o; + + for (var i = 0, len = this._objects.length; i < len; ++i) { + o = this._objects[i]; + o.setCoords(); + for (var prop in o.oCoords) { + aX.push(o.oCoords[prop].x); + aY.push(o.oCoords[prop].y); + } + } + + this.set(this._getBounds(aX, aY, onlyWidthHeight)); + }, + + /** + * @private + */ + _getBounds: function(aX, aY, onlyWidthHeight) { + var ivt = fabric.util.invertTransform(this.getViewportTransform()), + minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), + obj = { + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0 + }; + + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + + for (var i = 0, len = this._objects.length; i < len; i++) { + markup.push(this._objects[i].toSVG(reviver)); + } + + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns requested property + * @param {String} prop Property to get + * @return {Any} + */ + get: function(prop) { + if (prop in _lockProperties) { + if (this[prop]) { + return this[prop]; + } + else { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i][prop]) { + return true; + } + } + return false; + } + } + else { + if (prop in this.delegatedProperties) { + return this._objects[0] && this._objects[0].get(prop); + } + return this[prop]; + } + } + }); + + /** + * Returns {@link fabric.Group} instance from an object representation + * @static + * @memberOf fabric.Group + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an group instance is created + * @return {fabric.Group} An instance of fabric.Group + */ + fabric.Group.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.Group(enlivenedObjects, object)); + }); + }; + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.Group + * @type Boolean + * @default + */ + fabric.Group.async = true; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var extend = fabric.util.object.extend; + + if (!global.fabric) { + global.fabric = { }; + } + + if (global.fabric.Image) { + fabric.warn('fabric.Image is already defined.'); + return; + } + + /** + * Image class + * @class fabric.Image + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} + * @see {@link fabric.Image#initialize} for constructor definition + */ + fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'image', + + /** + * crossOrigin value (one of "", "anonymous", "allow-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @type String + * @default + */ + crossOrigin: '', + + /** + * Constructor + * @param {HTMLImageElement | String} element Image element + * @param {Object} [options] Options object + * @return {fabric.Image} thisArg + */ + initialize: function(element, options) { + options || (options = { }); + + this.filters = [ ]; + + this.callSuper('initialize', options); + + this._initElement(element, options); + this._initConfig(options); + + if (options.filters) { + this.filters = options.filters; + this.applyFilters(); + } + }, + + /** + * Returns image element which this instance if based on + * @return {HTMLImageElement} Image element + */ + getElement: function() { + return this._element; + }, + + /** + * Sets image element for this instance to a specified one. + * If filters defined they are applied to new image. + * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. + * @param {HTMLImageElement} element + * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated + * @return {fabric.Image} thisArg + * @chainable + */ + setElement: function(element, callback) { + this._element = element; + this._originalElement = element; + this._initConfig(); + + if (this.filters.length !== 0) { + this.applyFilters(callback); + } + + return this; + }, + + /** + * Sets crossOrigin value (on an instance and corresponding image element) + * @return {fabric.Image} thisArg + * @chainable + */ + setCrossOrigin: function(value) { + this.crossOrigin = value; + this._element.crossOrigin = value; + + return this; + }, + + /** + * Returns original size of an image + * @return {Object} Object with "width" and "height" properties + */ + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.width, + height: element.height + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _stroke: function(ctx) { + ctx.save(); + this._setStrokeStyles(ctx); + ctx.beginPath(); + ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); + ctx.closePath(); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; + + ctx.save(); + this._setStrokeStyles(ctx); + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @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 extend(this.callSuper('toObject', propertiesToInclude), { + src: this._originalElement.src || this._originalElement._src, + filters: this.filters.map(function(filterObj) { + return filterObj && filterObj.toObject(); + }), + crossOrigin: this.crossOrigin + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [], x = -this.width / 2, y = -this.height / 2; + if (this.group) { + x = this.left; + y = this.top; + } + markup.push( + '\n', + '\n' + ); + + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + markup.push( + '\n' + ); + this.fill = origFill; + } + + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns source of an image + * @return {String} Source of an image + */ + getSrc: function() { + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns a clone of an instance + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + */ + clone: function(callback, propertiesToInclude) { + this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + }, + + /** + * Applies filters assigned to this image (from "filters" array) + * @mthod applyFilters + * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated + * @return {fabric.Image} thisArg + * @chainable + */ + applyFilters: function(callback) { + + if (!this._originalElement) { + return; + } + + if (this.filters.length === 0) { + this._element = this._originalElement; + callback && callback(); + return; + } + + var imgEl = this._originalElement, + canvasEl = fabric.util.createCanvasElement(), + replacement = fabric.util.createImage(), + _this = this; + + canvasEl.width = imgEl.width; + canvasEl.height = imgEl.height; + + canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); + + this.filters.forEach(function(filter) { + filter && filter.applyTo(canvasEl); + }); + + /** @ignore */ + + replacement.width = imgEl.width; + replacement.height = imgEl.height; + + if (fabric.isLikelyNode) { + replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); + + // onload doesn't fire in some node versions, so we invoke callback manually + _this._element = replacement; + callback && callback(); + } + else { + replacement.onload = function() { + _this._element = replacement; + callback && callback(); + replacement.onload = canvasEl = imgEl = null; + }; + replacement.src = canvasEl.toDataURL('image/png'); + } + + return this; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + this._element && + ctx.drawImage( + this._element, + noTransform ? this.left : -this.width/2, + noTransform ? this.top : -this.height/2, + this.width, + this.height + ); + this._renderStroke(ctx); + }, + + /** + * @private + */ + _resetWidthHeight: function() { + var element = this.getElement(); + + this.set('width', element.width); + this.set('height', element.height); + }, + + /** + * The Image class's initialization method. This method is automatically + * called by the constructor. + * @private + * @param {HTMLImageElement|String} element The element representing the image + */ + _initElement: function(element) { + this.setElement(fabric.util.getById(element)); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initConfig: function(options) { + options || (options = { }); + this.setOptions(options); + this._setWidthHeight(options); + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } + }, + + /** + * @private + * @param {Object} object Object with filters property + * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created + */ + _initFilters: function(object, callback) { + if (object.filters && object.filters.length) { + fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, 'fabric.Image.filters'); + } + else { + callback && callback(); + } + }, + + /** + * @private + * @param {Object} [options] Object with width/height properties + */ + _setWidthHeight: function(options) { + this.width = 'width' in options + ? options.width + : (this.getElement() + ? this.getElement().width || 0 + : 0); + + this.height = 'height' in options + ? options.height + : (this.getElement() + ? this.getElement().height || 0 + : 0); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /** + * Default CSS class name for canvas + * @static + * @type String + * @default + */ + fabric.Image.CSS_CANVAS = 'canvas-img'; + + /** + * Alias for getSrc + * @static + */ + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + + /** + * Creates an instance of fabric.Image from its object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an image instance is created + */ + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + fabric.Image.prototype._initFilters.call(object, object, function(filters) { + object.filters = filters || [ ]; + var instance = new fabric.Image(img, object); + callback && callback(instance); + }); + }, null, object.crossOrigin); + }; + + /** + * Creates an instance of fabric.Image from an URL string + * @static + * @param {String} url URL to create an image from + * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) + * @param {Object} [imgOptions] Options object + */ + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img) { + callback(new fabric.Image(img, imgOptions)); + }, null, imgOptions && imgOptions.crossOrigin); + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) + * @static + * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} + */ + fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' ')); + + /** + * Returns {@link fabric.Image} instance from an SVG element + * @static + * @param {SVGElement} element Element to parse + * @param {Function} callback Callback to execute when fabric.Image object is created + * @param {Object} [options] Options object + * @return {fabric.Image} Instance of fabric.Image + */ + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + + fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, + extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Indicates that instances of this type are async + * @static + * @type Boolean + * @default + */ + fabric.Image.async = true; + + /** + * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 + * @static + * @type Number + * @default + */ + fabric.Image.pngCompression = 1; + +})(typeof exports !== 'undefined' ? exports : this); + + +/** + * @namespace fabric.Image.filters + * @memberOf fabric.Image + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + */ +fabric.Image.filters = fabric.Image.filters || { }; + +/** + * Root filter class from which all filter classes inherit from + * @class fabric.Image.filters.BaseFilter + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'BaseFilter', + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { type: this.type }; + }, + + /** + * Returns a JSON representation of an instance + * @return {Object} JSON + */ + toJSON: function() { + // delegate, not alias + return this.toObject(); + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Brightness filter class + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Brightness({ + * brightness: 200 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Brightness', + + /** + * Constructor + * @memberOf fabric.Image.filters.Brightness.prototype + * @param {Object} [options] Options object + * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) + */ + initialize: function(options) { + options = options || { }; + this.brightness = options.brightness || 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + brightness = this.brightness; + + for (var i = 0, len = data.length; i < len; i += 4) { + data[i] += brightness; + data[i + 1] += brightness; + data[i + 2] += brightness; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + brightness: this.brightness + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness + */ + fabric.Image.filters.Brightness.fromObject = function(object) { + return new fabric.Image.filters.Brightness(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Adapted from html5rocks article + * @class fabric.Image.filters.Convolute + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example Sharpen filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 0, -1, 0, + * -1, 5, -1, + * 0, -1, 0 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Blur filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Emboss filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Emboss filter with opaqueness + * var filter = new fabric.Image.filters.Convolute({ + * opaque: true, + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Convolute', + + /** + * Constructor + * @memberOf fabric.Image.filters.Convolute.prototype + * @param {Object} [options] Options object + * @param {Boolean} [options.opaque=false] Opaque value (true/false) + * @param {Array} [options.matrix] Filter matrix + */ + initialize: function(options) { + options = options || { }; + + this.opaque = options.opaque; + this.matrix = options.matrix || [ + 0, 0, 0, + 0, 1, 0, + 0, 0, 0 + ]; + + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext('2d'); + }, + + /** + * @private + */ + _createImageData: function(w, h) { + return this.tmpCtx.createImageData(w, h); + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + + var weights = this.matrix, + context = canvasEl.getContext('2d'), + pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + + side = Math.round(Math.sqrt(weights.length)), + halfSide = Math.floor(side/2), + src = pixels.data, + sw = pixels.width, + sh = pixels.height, + + // pad output by the convolution matrix + w = sw, + h = sh, + output = this._createImageData(w, h), + + dst = output.data, + + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0; + + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var sy = y, + sx = x, + dstOff = (y * w + x) * 4, + // calculate the weighed sum of the source image pixels that + // fall under the convolution matrix + r = 0, g = 0, b = 0, a = 0; + + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + + var scy = sy + cy - halfSide, + scx = sx + cx - halfSide; + + /* jshint maxdepth:5 */ + if (scy < 0 || scy > sh || scx < 0 || scx > sw) { + continue; + } + + var srcOff = (scy * sw + scx) * 4, + wt = weights[cy * side + cx]; + + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); + } + } + + context.putImageData(output, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + opaque: this.opaque, + matrix: this.matrix + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute + */ + fabric.Image.filters.Convolute.fromObject = function(object) { + return new fabric.Image.filters.Convolute(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * GradientTransparency filter class + * @class fabric.Image.filters.GradientTransparency + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.GradientTransparency({ + * threshold: 200 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'GradientTransparency', + + /** + * Constructor + * @memberOf fabric.Image.filters.GradientTransparency.prototype + * @param {Object} [options] Options object + * @param {Number} [options.threshold=100] Threshold value + */ + initialize: function(options) { + options = options || { }; + this.threshold = options.threshold || 100; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + threshold = this.threshold, + total = data.length; + + for (var i = 0, len = data.length; i < len; i += 4) { + data[i + 3] = threshold + 255 * (total - i) / total; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + threshold: this.threshold + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency + */ + fabric.Image.filters.GradientTransparency.fromObject = function(object) { + return new fabric.Image.filters.GradientTransparency(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Grayscale image filter class + * @class fabric.Image.filters.Grayscale + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Grayscale(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Grayscale', + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Grayscale.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + len = imageData.width * imageData.height * 4, + index = 0, + average; + + while (index < len) { + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + index += 4; + } + + context.putImageData(imageData, 0, 0); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale + */ + fabric.Image.filters.Grayscale.fromObject = function() { + return new fabric.Image.filters.Grayscale(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Invert filter class + * @class fabric.Image.filters.Invert + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Invert(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Invert', + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Invert.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i; + + for (i = 0; i < iLen; i+=4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + + context.putImageData(imageData, 0, 0); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert + */ + fabric.Image.filters.Invert.fromObject = function() { + return new fabric.Image.filters.Invert(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Mask filter class + * See http://resources.aleph-1.com/mask/ + * @class fabric.Image.filters.Mask + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition + */ + fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Mask', + + /** + * Constructor + * @memberOf fabric.Image.filters.Mask.prototype + * @param {Object} [options] Options object + * @param {fabric.Image} [options.mask] Mask image object + * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) + */ + initialize: function(options) { + options = options || { }; + + this.mask = options.mask; + this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + if (!this.mask) { + return; + } + + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + maskEl = this.mask.getElement(), + maskCanvasEl = fabric.util.createCanvasElement(), + channel = this.channel, + i, + iLen = imageData.width * imageData.height * 4; + + maskCanvasEl.width = maskEl.width; + maskCanvasEl.height = maskEl.height; + + maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); + + var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), + maskData = maskImageData.data; + + for (i = 0; i < iLen; i += 4) { + data[i + 3] = maskData[i + channel]; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + mask: this.mask.toObject(), + channel: this.channel + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when a mask filter instance is created + */ + fabric.Image.filters.Mask.fromObject = function(object, callback) { + fabric.util.loadImage(object.mask.src, function(img) { + object.mask = new fabric.Image(img, object.mask); + callback && callback(new fabric.Image.filters.Mask(object)); + }); + }; + + /** + * Indicates that instances of this type are async + * @static + * @type Boolean + * @default + */ + fabric.Image.filters.Mask.async = true; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Noise filter class + * @class fabric.Image.filters.Noise + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Noise({ + * noise: 700 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Noise', + + /** + * Constructor + * @memberOf fabric.Image.filters.Noise.prototype + * @param {Object} [options] Options object + * @param {Number} [options.noise=0] Noise value + */ + initialize: function(options) { + options = options || { }; + this.noise = options.noise || 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + noise = this.noise, rand; + + for (var i = 0, len = data.length; i < len; i += 4) { + + rand = (0.5 - Math.random()) * noise; + + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + noise: this.noise + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise + */ + fabric.Image.filters.Noise.fromObject = function(object) { + return new fabric.Image.filters.Noise(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Pixelate filter class + * @class fabric.Image.filters.Pixelate + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Pixelate({ + * blocksize: 8 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Pixelate', + + /** + * Constructor + * @memberOf fabric.Image.filters.Pixelate.prototype + * @param {Object} [options] Options object + * @param {Number} [options.blocksize=4] Blocksize for pixelate + */ + initialize: function(options) { + options = options || { }; + this.blocksize = options.blocksize || 4; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = imageData.height, + jLen = imageData.width, + index, i, j, r, g, b, a; + + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + + index = (i * 4) * jLen + (j * 4); + + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + + /* + blocksize: 4 + + [1,x,x,x,1] + [x,x,x,x,1] + [x,x,x,x,1] + [x,x,x,x,1] + [1,1,1,1,1] + */ + + for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { + for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { + index = (_i * 4) * jLen + (_j * 4); + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + blocksize: this.blocksize + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate + */ + fabric.Image.filters.Pixelate.fromObject = function(object) { + return new fabric.Image.filters.Pixelate(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Remove white filter class + * @class fabric.Image.filters.RemoveWhite + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.RemoveWhite({ + * threshold: 40, + * distance: 140 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'RemoveWhite', + + /** + * Constructor + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + * @param {Number} [options.threshold=30] Threshold value + * @param {Number} [options.distance=20] Distance value + */ + initialize: function(options) { + options = options || { }; + this.threshold = options.threshold || 30; + this.distance = options.distance || 20; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + threshold = this.threshold, + distance = this.distance, + limit = 255 - threshold, + abs = Math.abs, + r, g, b; + + for (var i = 0, len = data.length; i < len; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + if (r > limit && + g > limit && + b > limit && + abs(r - g) < distance && + abs(r - b) < distance && + abs(g - b) < distance + ) { + data[i + 3] = 1; + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + threshold: this.threshold, + distance: this.distance + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite + */ + fabric.Image.filters.RemoveWhite.fromObject = function(object) { + return new fabric.Image.filters.RemoveWhite(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Sepia filter class + * @class fabric.Image.filters.Sepia + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Sepia(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Sepia', + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Sepia.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, avg; + + for (i = 0; i < iLen; i+=4) { + avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2]; + data[i] = avg + 100; + data[i + 1] = avg + 50; + data[i + 2] = avg + 255; + } + + context.putImageData(imageData, 0, 0); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia + */ + fabric.Image.filters.Sepia.fromObject = function() { + return new fabric.Image.filters.Sepia(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Sepia2 filter class + * @class fabric.Image.filters.Sepia2 + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Sepia2(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Sepia2', + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Sepia.prototype + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, r, g, b; + + for (i = 0; i < iLen; i+=4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351; + data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203; + data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140; + } + + context.putImageData(imageData, 0, 0); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 + */ + fabric.Image.filters.Sepia2.fromObject = function() { + return new fabric.Image.filters.Sepia2(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Tint filter class + * Adapted from https://github.com/mezzoblue/PaintbrushJS + * @class fabric.Image.filters.Tint + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example Tint filter with hex color and opacity + * var filter = new fabric.Image.filters.Tint({ + * color: '#3513B0', + * opacity: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Tint filter with rgba color + * var filter = new fabric.Image.filters.Tint({ + * color: 'rgba(53, 21, 176, 0.5)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Tint', + + /** + * Constructor + * @memberOf fabric.Image.filters.Tint.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to tint the image with + * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + this.opacity = typeof options.opacity !== 'undefined' + ? options.opacity + : new fabric.Color(this.color).getAlpha(); + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + tintR, tintG, tintB, + r, g, b, alpha1, + source; + + source = new fabric.Color(this.color).getSource(); + + tintR = source[0] * this.opacity; + tintG = source[1] * this.opacity; + tintB = source[2] * this.opacity; + + alpha1 = 1 - this.opacity; + + for (i = 0; i < iLen; i+=4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + // alpha compositing + data[i] = tintR + r * alpha1; + data[i + 1] = tintG + g * alpha1; + data[i + 2] = tintB + b * alpha1; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color, + opacity: this.opacity + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint + */ + fabric.Image.filters.Tint.fromObject = function(object) { + return new fabric.Image.filters.Tint(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Multiply filter class + * Adapted from http://www.laurenscorijn.com/articles/colormath-basics + * @class fabric.Image.filters.Multiply + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example Multiply filter with hex color + * var filter = new fabric.Image.filters.Multiply({ + * color: '#F0F' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Multiply filter with rgb color + * var filter = new fabric.Image.filters.Multiply({ + * color: 'rgb(53, 21, 176)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Multiply', + + /** + * Constructor + * @memberOf fabric.Image.filters.Multiply.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to multiply the image pixels with + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + source; + + source = new fabric.Color(this.color).getSource(); + + for (i = 0; i < iLen; i+=4) { + data[i] *= source[0] / 255; + data[i + 1] *= source[1] / 255; + data[i + 2] *= source[2] / 255; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply + */ + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global){ + 'use strict'; + + var fabric = global.fabric; + + /** + * Color Blend filter class + * @class fabric.Image.filter.Blend + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.Blend({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.Blend({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Blend = fabric.util.createClass({ + type: 'Blend', + + initialize: function(options){ + options = options || {}; + this.color = options.color || '#000'; + this.image = options.image || false; + this.mode = options.mode || 'multiply'; + this.alpha = options.alpha || 1; + }, + + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + tr, tg, tb, + r, g, b, + source, + isImage = false; + + if (this.image) { + // Blend images + isImage = true; + + var _el = fabric.util.createCanvasElement(); + _el.width = this.image.width; + _el.height = this.image.height; + + var tmpCanvas = new fabric.StaticCanvas(_el); + tmpCanvas.add(this.image); + var context2 = tmpCanvas.getContext('2d'); + source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; + } + else { + // Blend color + source = new fabric.Color(this.color).getSource(); + + tr = source[0] * this.alpha; + tg = source[1] * this.alpha; + tb = source[2] * this.alpha; + } + + for (var i = 0, len = data.length; i < len; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + if (isImage) { + tr = source[i] * this.alpha; + tg = source[i + 1] * this.alpha; + tb = source[i + 2] * this.alpha; + } + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + break; + case 'screen': + data[i] = 1 - (1 - r) * (1 - tr); + data[i + 1] = 1 - (1 - g) * (1 - tg); + data[i + 2] = 1 - (1 - b) * (1 - tb); + break; + case 'add': + data[i] = Math.min(255, r + tr); + data[i + 1] = Math.min(255, g + tg); + data[i + 2] = Math.min(255, b + tb); + break; + case 'diff': + case 'difference': + data[i] = Math.abs(r - tr); + data[i + 1] = Math.abs(g - tg); + data[i + 2] = Math.abs(b - tb); + break; + case 'subtract': + var _r = r - tr, + _g = g - tg, + _b = b - tb; + + data[i] = (_r < 0) ? 0 : _r; + data[i + 1] = (_g < 0) ? 0 : _g; + data[i + 2] = (_b < 0) ? 0 : _b; + break; + case 'darken': + data[i] = Math.min(r, tr); + data[i + 1] = Math.min(g, tg); + data[i + 2] = Math.min(b, tb); + break; + case 'lighten': + data[i] = Math.max(r, tr); + data[i + 1] = Math.max(g, tg); + data[i + 2] = Math.max(b, tb); + break; + } + } + + context.putImageData(imageData, 0, 0); + } + }); + + fabric.Image.filters.Blend.fromObject = function(object) { + return new fabric.Image.filters.Blend(object); + }; +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Text) { + fabric.warn('fabric.Text is already defined'); + return; + } + + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push( + 'fontFamily', + 'fontWeight', + 'fontSize', + 'text', + 'textDecoration', + 'textAlign', + 'fontStyle', + 'lineHeight', + 'textBackgroundColor', + 'useNative', + 'path' + ); + + /** + * Text class + * @class fabric.Text + * @extends fabric.Object + * @return {fabric.Text} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} + * @see {@link fabric.Text#initialize} for constructor definition + */ + fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { + + /** + * Properties which when set cause object to change dimensions + * @type Object + * @private + */ + _dimensionAffectingProps: { + fontSize: true, + fontWeight: true, + fontFamily: true, + textDecoration: true, + fontStyle: true, + lineHeight: true, + stroke: true, + strokeWidth: true, + text: true + }, + + /** + * @private + */ + _reNewline: /\r?\n/, + + /** + * Retrieves object's fontSize + * @method getFontSize + * @memberOf fabric.Text.prototype + * @return {String} Font size (in pixels) + */ + + /** + * Sets object's fontSize + * @method setFontSize + * @memberOf fabric.Text.prototype + * @param {Number} fontSize Font size (in pixels) + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontWeight + * @method getFontWeight + * @memberOf fabric.Text.prototype + * @return {(String|Number)} Font weight + */ + + /** + * Sets object's fontWeight + * @method setFontWeight + * @memberOf fabric.Text.prototype + * @param {(Number|String)} fontWeight Font weight + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontFamily + * @method getFontFamily + * @memberOf fabric.Text.prototype + * @return {String} Font family + */ + + /** + * Sets object's fontFamily + * @method setFontFamily + * @memberOf fabric.Text.prototype + * @param {String} fontFamily Font family + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's text + * @method getText + * @memberOf fabric.Text.prototype + * @return {String} text + */ + + /** + * Sets object's text + * @method setText + * @memberOf fabric.Text.prototype + * @param {String} text Text + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textDecoration + * @method getTextDecoration + * @memberOf fabric.Text.prototype + * @return {String} Text decoration + */ + + /** + * Sets object's textDecoration + * @method setTextDecoration + * @memberOf fabric.Text.prototype + * @param {String} textDecoration Text decoration + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontStyle + * @method getFontStyle + * @memberOf fabric.Text.prototype + * @return {String} Font style + */ + + /** + * Sets object's fontStyle + * @method setFontStyle + * @memberOf fabric.Text.prototype + * @param {String} fontStyle Font style + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's lineHeight + * @method getLineHeight + * @memberOf fabric.Text.prototype + * @return {Number} Line height + */ + + /** + * Sets object's lineHeight + * @method setLineHeight + * @memberOf fabric.Text.prototype + * @param {Number} lineHeight Line height + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textAlign + * @method getTextAlign + * @memberOf fabric.Text.prototype + * @return {String} Text alignment + */ + + /** + * Sets object's textAlign + * @method setTextAlign + * @memberOf fabric.Text.prototype + * @param {String} textAlign Text alignment + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textBackgroundColor + * @method getTextBackgroundColor + * @memberOf fabric.Text.prototype + * @return {String} Text background color + */ + + /** + * Sets object's textBackgroundColor + * @method setTextBackgroundColor + * @memberOf fabric.Text.prototype + * @param {String} textBackgroundColor Text background color + * @return {fabric.Text} + * @chainable + */ + + /** + * Type of an object + * @type String + * @default + */ + type: 'text', + + /** + * Font size (in pixels) + * @type Number + * @default + */ + fontSize: 40, + + /** + * Font weight (e.g. bold, normal, 400, 600, 800) + * @type {(Number|String)} + * @default + */ + fontWeight: 'normal', + + /** + * Font family + * @type String + * @default + */ + fontFamily: 'Times New Roman', + + /** + * Text decoration Possible values: "", "underline", "overline" or "line-through". + * @type String + * @default + */ + textDecoration: '', + + /** + * Text alignment. Possible values: "left", "center", or "right". + * @type String + * @default + */ + textAlign: 'left', + + /** + * Font style . Possible values: "", "normal", "italic" or "oblique". + * @type String + * @default + */ + fontStyle: '', + + /** + * Line height + * @type Number + * @default + */ + lineHeight: 1.3, + + /** + * Background color of text lines + * @type String + * @default + */ + textBackgroundColor: '', + + /** + * URL of a font file, when using Cufon + * @type String | null + * @default + */ + path: null, + + /** + * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) + * @type Boolean + * @default + */ + useNative: true, + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: stateProperties, + + /** + * When defined, an object is rendered via stroke and this property specifies its color. + * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 + * @type String + * @default + */ + stroke: null, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + initialize: function(text, options) { + options = options || { }; + + this.text = text; + this.__skipDimension = true; + this.setOptions(options); + this.__skipDimension = false; + this._initDimensions(); + }, + + /** + * Renders text object on offscreen canvas, so that it would get dimensions + * @private + */ + _initDimensions: function() { + if (this.__skipDimension) { + return; + } + var canvasEl = fabric.util.createCanvasElement(); + this._render(canvasEl.getContext('2d')); + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of text object + */ + toString: function() { + return '#'; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + + if (typeof Cufon === 'undefined' || this.useNative === true) { + this._renderViaNative(ctx); + } + else { + this._renderViaCufon(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderViaNative: function(ctx) { + var textLines = this.text.split(this._reNewline); + + this._setTextStyles(ctx); + + this.width = this._getTextWidth(ctx, textLines); + this.height = this._getTextHeight(ctx, textLines); + + this.clipTo && fabric.util.clipContext(this, ctx); + + this._renderTextBackground(ctx, textLines); + this._translateForTextAlign(ctx); + this._renderText(ctx, textLines); + + if (this.textAlign !== 'left' && this.textAlign !== 'justify') { + ctx.restore(); + } + + this._renderTextDecoration(ctx, textLines); + this.clipTo && ctx.restore(); + + this._setBoundaries(ctx, textLines); + this._totalLineHeight = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderText: function(ctx, textLines) { + ctx.save(); + this._setShadow(ctx); + this._setupFillRule(ctx); + this._renderTextFill(ctx, textLines); + this._renderTextStroke(ctx, textLines); + this._restoreFillRule(ctx); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _translateForTextAlign: function(ctx) { + if (this.textAlign !== 'left' && this.textAlign !== 'justify') { + ctx.save(); + ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _setBoundaries: function(ctx, textLines) { + this._boundaries = [ ]; + + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); + + this._boundaries.push({ + height: this.fontSize * this.lineHeight, + width: lineWidth, + left: lineLeftOffset + }); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setTextStyles: function(ctx) { + this._setFillStyles(ctx); + this._setStrokeStyles(ctx); + ctx.textBaseline = 'alphabetic'; + if (!this.skipTextAlign) { + ctx.textAlign = this.textAlign; + } + ctx.font = this._getFontDeclaration(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Height of fabric.Text object + */ + _getTextHeight: function(ctx, textLines) { + return this.fontSize * textLines.length * this.lineHeight; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Maximum width of fabric.Text object + */ + _getTextWidth: function(ctx, textLines) { + var maxWidth = ctx.measureText(textLines[0] || '|').width; + + for (var i = 1, len = textLines.length; i < len; i++) { + var currentLineWidth = ctx.measureText(textLines[i]).width; + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + + /** + * @private + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} chars Chars to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + */ + _renderChars: function(method, ctx, chars, left, top) { + ctx[method](chars, left, top); + }, + + /** + * @private + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + * @param {Number} lineIndex Index of a line in a text + */ + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + // lift the line by quarter of fontSize + top -= this.fontSize / 4; + + // short-circuit + if (this.textAlign !== 'justify') { + this._renderChars(method, ctx, line, left, top, lineIndex); + return; + } + + var lineWidth = ctx.measureText(line).width, + totalWidth = this.width; + + if (totalWidth > lineWidth) { + // stretch the line + var words = line.split(/\s+/), + wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, + widthDiff = totalWidth - wordsWidth, + numSpaces = words.length - 1, + spaceWidth = widthDiff / numSpaces, + leftOffset = 0; + + for (var i = 0, len = words.length; i < len; i++) { + this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); + leftOffset += ctx.measureText(words[i]).width + spaceWidth; + } + } + else { + this._renderChars(method, ctx, line, left, top, lineIndex); + } + }, + + /** + * @private + * @return {Number} Left offset + */ + _getLeftOffset: function() { + if (fabric.isLikelyNode) { + return 0; + } + return -this.width / 2; + }, + + /** + * @private + * @return {Number} Top offset + */ + _getTopOffset: function() { + return -this.height / 2; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextFill: function(ctx, textLines) { + if (!this.fill && !this._skipFillStrokeCheck) { + return; + } + + this._boundaries = [ ]; + var lineHeights = 0; + + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + + this._renderTextLine( + 'fillText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + lineHeights, + i + ); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextStroke: function(ctx, textLines) { + if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { + return; + } + + var lineHeights = 0; + + ctx.save(); + if (this.strokeDashArray) { + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + supportsLineDash && ctx.setLineDash(this.strokeDashArray); + } + + ctx.beginPath(); + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + + this._renderTextLine( + 'strokeText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + lineHeights, + i + ); + } + ctx.closePath(); + ctx.restore(); + }, + + _getHeightOfLine: function() { + return this.fontSize * this.lineHeight; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextBackground: function(ctx, textLines) { + this._renderTextBoxBackground(ctx); + this._renderTextLinesBackground(ctx, textLines); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset(), + this.width, + this.height + ); + + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextLinesBackground: function(ctx, textLines) { + if (!this.textBackgroundColor) { + return; + } + + ctx.save(); + ctx.fillStyle = this.textBackgroundColor; + + for (var i = 0, len = textLines.length; i < len; i++) { + + if (textLines[i] !== '') { + + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + this._getLeftOffset() + lineLeftOffset, + this._getTopOffset() + (i * this.fontSize * this.lineHeight), + lineWidth, + this.fontSize * this.lineHeight + ); + } + } + ctx.restore(); + }, + + /** + * @private + * @param {Number} lineWidth Width of text line + * @return {Number} Line left offset + */ + _getLineLeftOffset: function(lineWidth) { + if (this.textAlign === 'center') { + return (this.width - lineWidth) / 2; + } + if (this.textAlign === 'right') { + return this.width - lineWidth; + } + return 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text line + * @return {Number} Line width + */ + _getLineWidth: function(ctx, line) { + return this.textAlign === 'justify' + ? this.width + : ctx.measureText(line).width; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextDecoration: function(ctx, textLines) { + if (!this.textDecoration) { + return; + } + + // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, + _this = this; + + /** @ignore */ + function renderLinesAtOffset(offset) { + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = _this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = _this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + _this._getLeftOffset() + lineLeftOffset, + ~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox), + lineWidth, + 1); + } + } + + if (this.textDecoration.indexOf('underline') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight); + } + if (this.textDecoration.indexOf('line-through') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); + } + if (this.textDecoration.indexOf('overline') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); + } + }, + + /** + * @private + */ + _getFontDeclaration: function() { + return [ + // node-canvas needs "weight style", while browsers need "style weight" + (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), + (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), + this.fontSize + 'px', + (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) + ].join(' '); + }, + + /** + * Renders text instance on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx, noTransform) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + this._transform(ctx, noTransform); + + var m = this.transformMatrix, + isInPathGroup = this.group && this.group.type === 'path-group'; + + if (isInPathGroup) { + ctx.translate(-this.group.width/2, -this.group.height/2); + } + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (isInPathGroup) { + ctx.translate(this.left, this.top); + } + this._render(ctx); + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @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) { + var object = extend(this.callSuper('toObject', propertiesToInclude), { + text: this.text, + fontSize: this.fontSize, + fontWeight: this.fontWeight, + fontFamily: this.fontFamily, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + textDecoration: this.textDecoration, + textAlign: this.textAlign, + path: this.path, + textBackgroundColor: this.textBackgroundColor, + useNative: this.useNative + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [ ], + textLines = this.text.split(this._reNewline), + offsets = this._getSVGLeftTopOffsets(textLines), + textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), + shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); + + // move top offset by an ascent + offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); + + this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + + /** + * @private + */ + _getSVGLeftTopOffsets: function(textLines) { + var lineTop = this.useNative + ? this.fontSize * this.lineHeight + : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), + + textLeft = -(this.width/2), + textTop = this.useNative + ? this.fontSize - 1 + : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight; + + return { + textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), + textTop: textTop + (this.group && this.group.type === 'path-group' ? this.top : 0), + lineTop: lineTop + }; + }, + + /** + * @private + */ + _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { + markup.push( + '\n', + textAndBg.textBgRects.join(''), + '', + shadowSpans.join(''), + textAndBg.textSpans.join(''), + '\n', + '\n' + ); + }, + + /** + * @private + * @param {Number} lineHeight + * @param {Array} textLines Array of all text lines + * @return {Array} + */ + _getSVGShadows: function(lineHeight, textLines) { + var shadowSpans = [], + i, len, + lineTopOffsetMultiplier = 1; + + if (!this.shadow || !this._boundaries) { + return shadowSpans; + } + + for (i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== '') { + var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; + shadowSpans.push( + '', + fabric.util.string.escapeXml(textLines[i]), + ''); + lineTopOffsetMultiplier = 1; + } + else { + // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier + // prevents empty tspans + lineTopOffsetMultiplier++; + } + } + + return shadowSpans; + }, + + /** + * @private + * @param {Number} lineHeight + * @param {Number} textLeftOffset Text left offset + * @param {Array} textLines Array of all text lines + * @return {Object} + */ + _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { + var textSpans = [ ], + textBgRects = [ ], + lineTopOffsetMultiplier = 1; + + // bounding-box background + this._setSVGBg(textBgRects); + + // text and text-background + for (var i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== '') { + this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); + lineTopOffsetMultiplier = 1; + } + else { + // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier + // prevents empty tspans + lineTopOffsetMultiplier++; + } + + if (!this.textBackgroundColor || !this._boundaries) { + continue; + } + + this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); + } + + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { + var lineLeftOffset = (this._boundaries && this._boundaries[i]) + ? toFixed(this._boundaries[i].left, 2) + : 0; + + textSpans.push( + ' elements since setting opacity + // on containing one doesn't work in Illustrator + this._getFillAttributes(this.fill), '>', + fabric.util.string.escapeXml(textLine), + '' + ); + }, + + _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { + textBgRects.push( + '\n'); + }, + + _setSVGBg: function(textBgRects) { + if (this.backgroundColor && this._boundaries) { + textBgRects.push( + ''); + } + }, + + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @private + * @param {Any} value + * @return {String} + */ + _getFillAttributes: function(value) { + var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + /* _TO_SVG_END_ */ + + /** + * Sets specified property to a specified value + * @param {String} key + * @param {Any} value + * @return {fabric.Text} thisArg + * @chainable + */ + _set: function(key, value) { + if (key === 'fontFamily' && this.path) { + this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); + } + this.callSuper('_set', key, value); + + if (key in this._dimensionAffectingProps) { + this._initDimensions(); + this.setCoords(); + } + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) + * @static + * @memberOf fabric.Text + * @see: http://www.w3.org/TR/SVG/text.html#TextElement + */ + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( + 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + + /** + * Returns fabric.Text instance from an SVG element (not yet implemented) + * @static + * @memberOf fabric.Text + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Text} Instance of fabric.Text + */ + fabric.Text.fromElement = function(element, options) { + if (!element) { + return null; + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); + options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + if (!options.originX) { + options.originX = 'left'; + } + + var text = new fabric.Text(element.textContent, options), + /* + Adjust positioning: + x/y attributes in SVG correspond to the bottom-left corner of text bounding box + top/left properties in Fabric correspond to center point of text bounding box + */ + offX = 0; + + if (text.originX === 'left') { + offX = text.getWidth() / 2; + } + if (text.originX === 'right') { + offX = -text.getWidth() / 2; + } + text.set({ + left: text.getLeft() + offX, + top: text.getTop() - text.getHeight() / 2 + }); + + return text; + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Text instance from an object representation + * @static + * @memberOf fabric.Text + * @param {Object} object Object to create an instance from + * @return {fabric.Text} Instance of fabric.Text + */ + fabric.Text.fromObject = function(object) { + return new fabric.Text(object.text, clone(object)); + }; + + fabric.util.createAccessors(fabric.Text); + +})(typeof exports !== 'undefined' ? exports : this); + + +}).call(this,_dereq_("buffer").Buffer) +},{"buffer":2,"canvas":1,"jsdom":1}]},{},[6])(6) +}); \ No newline at end of file