diff --git a/cs1811.css b/cs1811.css
new file mode 100644
index 0000000000000000000000000000000000000000..05306eba968b006a5596a952e16ce3ee74d3c904
--- /dev/null
+++ b/cs1811.css
@@ -0,0 +1,158 @@
+/**
+ * Custom styling for CS1811 files
+ */
+
+/**
+ * Aesthetics
+ */
+
+.step {
+    font-family: Sans;
+}
+
+.step.concept {
+    border-style: solid;
+    border-width: medium;
+    border-color: lightgray;
+}
+
+.concept h3:not(.rowtitle) {
+    border-bottom-style: solid;
+    border-bottom-width: medium;
+    border-bottom-color: #eb641e;
+    margin-bottom: 2ex;
+}
+
+h3.side-heading {
+    text-align: left;
+    padding-top: 2ex;
+    padding-bottom: 2ex;
+}
+
+img {
+    width: 70%;
+    display: block;
+    margin: auto;
+}
+
+p {
+    padding: .75ex;
+}
+
+ul {
+    list-style: none;
+    margin-left: 2ex;
+}
+
+li::before {
+    content: "○ "; 
+    color: #00a69e;
+}
+
+td {
+    vertical-align: middle;
+}
+
+.narrow {
+    width: 0px;
+    margin: 0px;
+    padding: 0px;
+}
+
+/**
+ * Add features to flides
+ */
+
+/**
+ * visible-after class so that steps don't get greyed out after they
+ * are shown
+ */
+.step.past {
+     opacity: 1;
+}
+
+/**
+ * Invisible slide steps (contain answers to questions
+ */
+.slide_step.slide_step_hidden.invisible { 
+     opacity: 0;
+}
+
+code {
+    font-family: monospace;
+    font-size: 80%;
+    background-color: #efefef;
+}
+
+pre {
+    margin-left: 5%;
+    width: 90%;
+    border: 1pt dashed;
+    background-color: #efefef;
+}
+
+blockquote {
+    padding-left: 3em;
+    padding-right: 3em;
+    font-style: italic;
+}
+
+/**
+ * Chapter 1
+ */
+
+table.viscomp td:first-child {
+    text-align:right;
+    padding-right:1em;
+}
+
+table.viscomp td:nth-child(2) {
+    width:5em;
+}
+
+table.viscomp td:last-child {
+    text-align:left;
+    padding-left:1em;
+}
+
+tr.result {
+    border-top: 1px dashed black;
+}
+
+table.numgrid td {
+    border: 1px solid black;
+    text-align: center;
+}
+
+td.arrlabel {
+    text-align: right;
+    padding-right: 3em;
+}
+
+td.arrbox {
+    border: 1px solid black;
+    text-align: center;
+}
+
+td.arridx {
+    text-align: center;
+}
+
+p.attribution {
+    text-align: right;
+    font-size: 33%;
+}
+
+p.copyright {
+    padding-top: 20%;
+    padding-left: 50%;
+    text-alight: right;
+    font-size: 33%;
+    width: 39em;
+}
+
+div.overview {
+    z-index: -100;
+}
+
+
diff --git a/lib/slide_helper.rb b/lib/slide_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..801a1f2c4d8ca0b0945d49b6eee136d241cdb405
--- /dev/null
+++ b/lib/slide_helper.rb
@@ -0,0 +1,470 @@
+# Commands
+#
+# Slides args appear on !SLIDE line. E.g.
+#
+#   !SLIDE_HERE myclass
+#
+# creates a slide in current pos with class myclass (in the HTML)
+#
+# Use scale to set zoom, this will be applied after next slide created.
+# First arg is movement scale, second is size of slides. Scale relative
+# does it wrt to current zoom
+#
+# !SCALE .5 .3
+# !SCALE_RELATIVE .5 .3
+#
+# Set next slides to .5 size and zoom in
+#
+# !SLIDE_HERE args
+#
+# Create a new slide in the current position (first slide).
+# SLIDE_SCALE_LEFT allows the distance left to be scaled (e.g. half
+# normal distance)
+#
+# !SLIDE_LEFT args
+# !SLIDE_DOWN args
+# !SLIDE_SCALE_LEFT .5 args
+#
+# Move one slide unit left and create a new slide
+#
+# !SLIDE_RELATIVE rx ry
+#
+# Useful for making slides within slides, e.g.
+#
+#   !SCALE .25
+#   !SLIDE_RELATIVE .25 -.25
+#
+# will make a 1/4 size sub-slide a quarter of the way from the top/left
+#
+# !VSPACE .2
+#
+# Add vspace of .2 of the slide height
+#
+# !PUSH
+#
+# Save current position
+#
+# !POP
+#
+# Return to last saved position
+#
+# To make rows of slides with titles, yoffset is optional and used when
+# the sliderows have material before them
+#
+# !START_SLIDEROWS 2 <yoffset>
+# !ROWTITLE title1
+# !ROWTITLE title2
+# !START_SLIDEROW 3
+# !SLIDE_HERE
+# slide 1
+# !SLIDE_LEFT
+# slide 2
+# !SLIDE_LEFT
+# slide 3
+# !END_SLIDEROW
+# !START_SLIDEROW 3
+# ...
+# !END_SLIDEROW
+# !END_SLIDEROWS
+#
+# To make a slide step
+#
+# appears as normal
+# {% slide_step %}
+# appears later!
+# {% end %}
+# appears as normal
+#
+# To add highlighted code
+#
+# {% embed_code ruby %}
+#   def blah()
+#
+#   end
+# {% end %}
+#
+# Embedded code will be surrounded by <div class="code_block"> for your styling.
+#
+# Images can be added with ![](path/to/image) but HTML can be used for
+# more control
+#
+# Repl.it can be embedded with plus optional height (in HTML format)
+#
+# !REPL 500px @matthewhague/EmptyPythonRepl
+#
+# Similar for general iframes
+#
+# !IFRAME 500px <url>
+#
+# Import a whole file verbatim
+#
+# !IMPORT <filename>
+#
+# Bookmark and return to a location
+#
+# !BOOKMARK <label>
+# !GOTO <label>
+
+require 'pygments'
+
+module SlideHelper
+    @@HUNIT = 1000.0
+    @@VUNIT = 800.0
+    @@OVERVIEW_VUNIT = 9 * @@HUNIT / 16
+
+    @@max_x = 0
+    @@min_x = 0
+    @@max_y = 0
+    @@min_y = 0
+
+    @@next_iframe_id = 0
+
+    class SlidePosition
+        attr_accessor :x, :y,
+                      :mov_scale, :slide_scale,
+                      :next_mov_scale, :next_slide_scale
+        def initialize()
+            @x = 0
+            @y = 0
+            @mov_scale = 1
+            @slide_scale = 1
+            @next_mov_scale = 1
+            @next_slide_scale = 1
+        end
+    end
+
+    @@pos = SlidePosition.new
+    @@stack = []
+    @@bookmarks = { }
+
+    class SlideRowsData
+        attr_accessor :num, :cur_row, :yoffset
+        def initialize(num_rows = 0, yoffset = 0)
+            @num = num_rows
+            @cur_row = 0
+            @yoffset = yoffset
+        end
+    end
+
+    @@cur_sliderows = SlideRowsData.new
+    @@sliderows_stack = []
+
+    def slide_step(opts={}, &blk)
+        text = capture_erb(&blk)
+
+        before = "<div class='step inner'>\n"
+        after  = "</div>\n"
+
+        html  = ""
+        html << guard_block(before)
+        html << markdown_to_html(text)
+        html << guard_block(after)
+
+        concat_erb(html, blk.binding)
+    end
+
+    def embed_code(opts="java", &blk)
+        code = capture_erb(&blk)
+
+        language = opts
+
+        before = "<div class='code_block'>\n"
+        after  = "</div>\n"
+        code = Pygments.highlight(code, {
+          :lexer => language,
+          :options => { :noclasses => true }
+        })
+
+        html  = ""
+        html << guard_block(before)
+        html << guard_block(code)
+        html << guard_block(after)
+
+        concat_erb(html, blk.binding)
+    end
+
+
+    def impress_demote_headers(input)
+        input.gsub(/^#/, "###")
+    end
+
+    def impress_layout_slides(input)
+        output = ""
+        input.each_line { |line|
+            cmd, args = impress_break_line(line)
+            case line.split.first
+            when '!SCALE'
+                output += impress_scale(args) + "\n"
+            when '!SCALE_RELATIVE'
+                output += impress_scale_relative(args) + "\n"
+            when '!SLIDE_HERE'
+                output += impress_make_slide(args) + "\n"
+            when '!SLIDE_LEFT'
+                output += impress_slide_left(args) + "\n"
+            when '!SLIDE_DOWN'
+                output += impress_slide_down(args) + "\n"
+            when '!SLIDE_SCALE_LEFT'
+                output += impress_slide_scale_left(args) + "\n"
+            when '!SLIDE_RELATIVE'
+                output += impress_slide_relative(args) + "\n"
+            when '!VSPACE'
+                output += impress_vspace(args) + "\n"
+            when '!PUSH'
+                output += impress_push(args) + "\n"
+            when '!POP'
+                output += impress_pop(args) + "\n"
+            when '!START_SLIDEROWS'
+                output += impress_start_sliderows(args) + "\n"
+            when '!END_SLIDEROWS'
+                output += impress_end_sliderows(args) + "\n"
+            when '!ROWTITLE'
+                output += impress_row_title(args) + "\n"
+            when '!START_SLIDEROW'
+                output += impress_start_sliderow(args) + "\n"
+            when '!END_SLIDEROW'
+                output += impress_end_sliderow(args) + "\n"
+            when '!OVERVIEW'
+                output += impress_overview(args) + "\n"
+            when '!REPL'
+                output += impress_repl(args) + "\n"
+            when '!IFRAME'
+                output += impress_iframe(args) + "\n"
+            when '!IMPORT'
+                output += impress_import(args) + "\n"
+            when '!BOOKMARK'
+                output += impress_bookmark(args) + "\n"
+            when '!GOTO'
+                output += impress_goto(args) + "\n"
+            else
+                output += line
+            end
+        }
+
+        output
+    end
+
+    def impress_push(args = "")
+        @@stack.push(@@pos.clone)
+        ''
+    end
+
+    def impress_pop(args = "")
+        @@pos = @@stack.pop()
+        "!SLIDE unclickable x=#{@@pos.x} y=#{@@pos.y} scale=#{@@pos.slide_scale} #{args}"
+    end
+
+    def impress_bookmark(args)
+      @@bookmarks[args] = @@pos.clone
+      ''
+    end
+
+    def impress_goto(args)
+        @@pos = @@bookmarks[args]
+        "!SLIDE unclickable x=#{@@pos.x} y=#{@@pos.y} scale=#{@@pos.slide_scale} #{args}"
+    end
+
+    def impress_scale(args)
+        argv = args.split
+        @@pos.next_mov_scale = argv[0].to_f
+        @@pos.next_slide_scale = argv[1].to_f
+        ""
+    end
+
+    def impress_scale_relative(args)
+        argv = args.split
+        @@pos.next_mov_scale *= argv[0].to_f
+        @@pos.next_slide_scale *= argv[1].to_f
+        ""
+    end
+
+    def impress_slide_left(args)
+        @@pos.x += impress_horiz_mov_unit()
+        impress_make_slide(args)
+    end
+
+    def impress_slide_down(args)
+        @@pos.y += impress_vert_mov_unit()
+        impress_make_slide(args)
+    end
+
+    def impress_slide_scale_left(args)
+        argv = args.split
+        scale = argv[0].to_f
+        rem_args = argv[1..-1].join(' ')
+        @@pos.x += scale * impress_horiz_mov_unit()
+        impress_make_slide(rem_args)
+    end
+
+    def impress_slide_relative(args)
+        argv = args.split
+        rx, ry = argv[0..1].map {|v| v.to_f}
+        # move relative to current slide (not movement unit)
+        x_delta = (rx - 0.5) * impress_horiz_slide_unit
+        y_delta = (ry - 0.5) * impress_vert_slide_unit
+        @@pos.x += x_delta
+        @@pos.y += y_delta
+        impress_make_slide(argv[2..-1].join(' '))
+    end
+
+    def impress_make_slide(args)
+        @@pos.mov_scale = @@pos.next_mov_scale
+        @@pos.slide_scale = @@pos.next_slide_scale
+
+        @@max_x = [@@max_x, @@pos.x].max
+        @@min_x = [@@min_x, @@pos.x].min
+        @@max_y = [@@max_y, @@pos.y].max
+        @@min_y = [@@min_y, @@pos.y].min
+
+        "!SLIDE #{args} x=#{@@pos.x} y=#{@@pos.y} scale=#{@@pos.slide_scale}"
+    end
+
+    def impress_overview(args)
+        mid_x = (@@max_x + @@min_x) / 2
+        mid_y = (@@max_y + @@min_y) / 2
+        scale_x = (@@max_x - @@min_x) / @@HUNIT
+        scale_y = (@@max_y - @@min_y) / @@OVERVIEW_VUNIT
+        scale = [scale_x, scale_y].max
+        "!SLIDE overview #{args} x=#{mid_x} y=#{mid_y} scale=#{scale}"
+    end
+
+    def impress_vspace(args)
+        pix = args.to_f * impress_vert_slide_unit
+        "<p style='height: #{pix}px'/>"
+    end
+
+    def impress_start_sliderows(args)
+        @@sliderows_stack.push(@@cur_sliderows.clone)
+
+        argv = args.split
+        num_rows = argv[0].to_i
+        yoffset = 0
+        if argv.length > 1
+            yoffset = argv[1].to_f
+        end
+
+        @@cur_sliderows = SlideRowsData.new(num_rows, yoffset)
+        ""
+    end
+
+    def impress_end_sliderows(args)
+        @@cur_sliderows = @@sliderows_stack.pop
+        ""
+    end
+
+    def impress_row_title(args)
+        offset_factor = (1 - @@cur_sliderows.yoffset)
+        # no row takes up more than 1/3 of slide space
+        effective_rows = [3,@@cur_sliderows.num].max
+        vspace = 0.9 / effective_rows * @@VUNIT * offset_factor
+        "<h3 class='rowtitle' style='height: #{vspace}px'>#{args}&nbsp;</h3>"
+    end
+
+    def impress_start_sliderow(args)
+        argv = args.split
+        num_slides = argv[0].to_i
+        slide_scale_factor = 1
+        if (argv.length > 1)
+            slide_scale_factor = argv[1].to_f
+        end
+
+        hscale = 1.0 / (num_slides + 1)
+        vscale = 0.9 / @@cur_sliderows.num
+
+        mov_scale = [hscale, vscale].min * @@pos.slide_scale
+
+        slide_vscale = 0.5 * vscale
+        slide_scale = slide_scale_factor * [hscale, slide_vscale].min * @@pos.slide_scale
+
+        # do this before messing with pos x and y
+        output = impress_push + "\n" + impress_scale("#{mov_scale} #{slide_scale}")
+
+        x_delta = (hscale - 0.5) * impress_horiz_slide_unit
+        fudged_yoffset = @@cur_sliderows.yoffset
+        if fudged_yoffset > 0
+            fudged_yoffset -= 0.4
+        end
+        y_baseline = (fudged_yoffset - 0.5) * impress_vert_slide_unit
+        y_offset = (@@cur_sliderows.cur_row + 0.7) * vscale * impress_vert_slide_unit
+        y_delta = y_baseline + y_offset
+        @@pos.x += x_delta
+        @@pos.y += y_delta
+
+        @@cur_sliderows.cur_row += 1
+
+        output
+    end
+
+    def impress_end_sliderow(args)
+        impress_pop(args)
+    end
+
+    def impress_horiz_mov_unit()
+        @@HUNIT * @@pos.mov_scale
+    end
+
+    def impress_vert_mov_unit()
+        @@VUNIT * @@pos.mov_scale
+    end
+
+    def impress_horiz_slide_unit()
+        @@HUNIT * @@pos.slide_scale
+    end
+
+    def impress_vert_slide_unit()
+        @@VUNIT * @@pos.slide_scale
+    end
+
+    def impress_break_line(line)
+        sline = line.split
+        cmd = sline[0]
+        args = ""
+        if sline.length > 1
+            args = sline[1..-1].join(" ")
+        end
+        [cmd, args]
+    end
+
+    def impress_repl(args)
+        argv = args.split
+        name = argv[-1]
+        argv[-1] = "https://repl.it/#{name}?lite=true"
+        impress_iframe(argv.join(" "))
+    end
+
+    def impress_iframe(args)
+        argv = args.split
+        url = ""
+        height = ""
+        if argv.length == 1
+            height = '500px'
+            url = argv[0]
+        else
+            height = argv[0]
+            url = argv[1]
+        end
+
+        container_id = "iframecontainer#{@@next_iframe_id}"
+        iframe_id = "iframe#{@@next_iframe_id}"
+        @@next_iframe_id += 1
+
+        # apologies to coding decency..
+        onclick_js = "document.getElementById(\"#{iframe_id}\").style = \"position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:95vw;height:95vh;border:1px dashed black\";"
+        onclick_js += "document.getElementById(\"#{container_id}\").style = \"position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:100vw;height:100vh;background:#efefefaa;cursor:pointer\""
+        onoff_js = "document.getElementById(\"#{iframe_id}\").style = \"width:100%\";"
+        onoff_js += "document.getElementById(\"#{container_id}\").style = \"\""
+
+        ("<div onclick='#{onoff_js}' id='#{container_id}'>" +
+         "<iframe id='#{iframe_id}' onload='blur()' frameborder='0' width='100%' height='#{height}' src='#{url}'></iframe>" +
+         "</div>" +
+         "<p style='width:100%;text-align:right;font-size:75%;cursor:pointer' onclick='#{onclick_js}'>↗</p>")
+    end
+
+    def impress_import(args)
+      # not memory efficient
+      impress_layout_slides(File.open(args, 'r').read)
+    end
+end
+
+class Slideshow::Gen
+    include SlideHelper
+end
diff --git a/slideshow.yml b/slideshow.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7354e8f7a7df3fc46b6efb8d4ddbbac4f9c30bd
--- /dev/null
+++ b/slideshow.yml
@@ -0,0 +1,11 @@
+filters:
+  - impress_layout_slides
+  - impress_demote_headers
+  - skip_end_directive
+  - directives_bang_style_to_percent_style
+  - directives_percent_style
+  - comments-percent-style
+  - leading-headers
+  - erb-django-style
+  - erb-rename-helper-hack
+  - erb
diff --git a/templates/impress.cs/apple-touch-icon.png b/templates/impress.cs/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6100163e4d3fefb9712ac3cd40734d5f7cf6943
Binary files /dev/null and b/templates/impress.cs/apple-touch-icon.png differ
diff --git a/templates/impress.cs/css/style.css b/templates/impress.cs/css/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..df128d6aa705cf61f54ef62d141949dae852a5db
--- /dev/null
+++ b/templates/impress.cs/css/style.css
@@ -0,0 +1,311 @@
+/** COPIED FROM impress **/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    font-size: 100%;
+    font: inherit;
+    vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+    display: block;
+}
+body {
+    line-height: 1;
+}
+ol, ul {
+    list-style: none;
+}
+blockquote, q {
+    quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+    content: '';
+    content: none;
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
+
+body {
+    font-family: Sans;
+    min-height: 740px;
+}
+
+h1,h2,h3,h4 {
+    text-align: center;
+}
+
+b, strong { font-weight: bold }
+i, em { font-style: italic }
+
+a {
+    color: inherit;
+    text-decoration: none;
+    padding: 0 0.1em;
+    background: rgba(255,255,255,0.5);
+    text-shadow: -1px -1px 2px rgba(100,100,100,0.9);
+    border-radius: 0.2em;
+
+    -webkit-transition: 0.5s;
+    -moz-transition:    0.5s;
+    -ms-transition:     0.5s;
+    -o-transition:      0.5s;
+    transition:         0.5s;
+}
+
+a:hover,
+a:focus {
+    background: rgba(255,255,255,1);
+    text-shadow: -1px -1px 2px rgba(100,100,100,0.5);
+}
+
+.step:not(.notransform):not(.inner) {
+    position: relative;
+    width: 900px;
+    padding: 40px;
+    margin: 0;
+
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing:    border-box;
+    -ms-box-sizing:     border-box;
+    -o-box-sizing:      border-box;
+    box-sizing:         border-box;
+
+    font-size: 27px;
+    line-height: 1.5;
+
+    -webkit-transition: opacity .1s;
+    -moz-transition:    opacity .1s;
+    -ms-transition:     opacity .1s;
+    -o-transition:      opacity .1s;
+    transition:         opacity .1s;
+}
+
+h1 {
+    font-size: 200px;
+}
+
+h2 {
+    font-size: 100px;
+    line-height: 1.1;
+}
+
+h3 {
+    font-size: 40px;
+}
+
+h4 {
+    font-size: 30px;
+}
+
+img {
+    width: 100%;
+}
+
+ul {
+    display: block;
+    list-style-type: disc;
+}
+
+table {
+    width: 100%;
+}
+
+.slides {
+    padding: 0;
+}
+
+/*
+	Slides specific part
+*/
+
+#impress-overview { 
+    display: none 
+}
+
+#impress { 
+    pointer-events: auto 
+}
+
+/*
+ opacity issues for animations (token)
+*/
+
+.token {
+    opacity: 0;
+}
+
+.token_invisible_past {
+    opacity: 0;
+}
+
+.token_invisible_future {
+    opacity: 0;
+}
+
+.token_visible {
+    opacity: 1;
+}
+
+/*
+ opacity issues for steps
+*/
+
+.step[data-fixed="1"]:not(.notransform) {
+     opacity: 1;
+}
+
+.step.active:not(.notransform) {
+     opacity: 1;
+}
+
+.step:not(.notransform) {
+     opacity: .1;
+}
+
+/*
+ opacity issues for slide_steps
+*/
+
+.slide_step.slide_step_visible { 
+     opacity: 1;
+}
+
+.slide_step.slide_step_hidden { 
+     opacity: .1;
+}
+
+.slide_step { 
+     opacity: .1;
+}
+
+/*
+ opacity issues for overview
+*/
+
+.impress-on-overview_intermediate .step.past {
+    opacity: 1;
+}
+
+.impress-on-overview_intermediate .slide_step { 
+     opacity: 1;
+}
+
+.impress-on-overview .step {
+    opacity: 1;
+    cursor: pointer;
+}
+
+.impress-on-overview .slide_step {
+    opacity: 1;
+}
+
+.impress-on-slide_return .step.past {
+    opacity: 1;
+}
+
+.impress-on-slide_return .slide_step { 
+     opacity: 1;
+}
+
+/**
+ * Because slide step isn't supported by impress
+ */
+.slide_step:hover {
+    opacity: 1
+}
+
+/** ADDED BY MATT **/
+
+#fabric-canvas-div {
+    margin: auto;
+    width: 2000;
+}
+
+#fabric-panel {
+    float: right;
+    padding-right: 10px;
+}
+
+#fabric-panel .fabric-subpanel {
+    display: inline-block;
+}
+
+#fabric-panel .fabric-button {
+    width: 10px;
+    height: 10px;
+    display: inline-block;
+    z-index: 101;
+    transform: translate3d(0px, 0px, 99999999px);
+}
+
+#fabric-show::before { content: "<"; }
+#fabric-hide::before { content: ">"; }
+
+#fabric-sel { border: 1px dashed; }
+#fabric-red { background-color: red; }
+#fabric-blue { background-color: blue; }
+#fabric-black { background-color: black; }
+#fabric-delete::before { content: "🗑"; }
+#fabric-clear::before { content: "↻"; }
+
+#start-presentation {
+    cursor: pointer;
+    width: 15em;
+    text-align: center;
+    background: #eb641e;
+    font-weight: bold;
+    font-size: 125%;
+    padding: .5em;
+    color: #202a30;
+}
+
+#start-presentation::before {
+    content: "▶️";
+}
+
+.step:not(.future) {
+     opacity: 1;
+}
+
+
+@media screen and (min-width: 1000px) {
+    nav .toc {
+        position:absolute;
+        top:0px;
+        right: 0px;
+        z-index: 99;
+    }
+}
+
+nav .toc {
+    width:30ex;
+    border: 1px dashed gray;
+    padding: 3ex;
+    background: white;
+}
+
+nav .toc h3 {
+    font-size: 20px;
+    padding-bottom: 1ex;
+}
diff --git a/templates/impress.cs/impress.cs.txt b/templates/impress.cs/impress.cs.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dba6bc9e535ed7de948677bc1e289d1c1e64770a
--- /dev/null
+++ b/templates/impress.cs/impress.cs.txt
@@ -0,0 +1,28 @@
+#####################
+# Sample Slide Show (S9) template package manifest using impress.js
+# - impress.js by Bartek Szopka (aka bartaz); see https://github.com/impress/impress.js 
+#
+# Change as desired/needed
+#
+# Configured to use the following headers in slides.html.erb:
+#
+# author: Your Name Here
+# title: Your Slide Show Title Here
+#
+# Questions? Comments?
+# Send them along to the wwwmake forum/mailing list.
+# http://groups.google.com/group/wwwmake
+
+
+__file__.html slides.html
+
+# impress.js core machinery
+
+css/style.css
+js/impress.js
+js/fabric.js
+js/slide-drawing.js
+
+# graphics
+
+apple-touch-icon.png
diff --git a/templates/impress.cs/js/fabric.js b/templates/impress.cs/js/fabric.js
new file mode 100644
index 0000000000000000000000000000000000000000..da3e19750030ff02dd397d8d9c48a9b70da349aa
--- /dev/null
+++ b/templates/impress.cs/js/fabric.js
@@ -0,0 +1 @@
+var fabric=fabric||{version:"3.2.0"};if("undefined"!=typeof exports?exports.fabric=fabric:"function"==typeof define&&define.amd&&define([],function(){return fabric}),"undefined"!=typeof document&&"undefined"!=typeof window)document instanceof("undefined"!=typeof HTMLDocument?HTMLDocument:Document)?fabric.document=document:fabric.document=document.implementation.createHTMLDocument(""),fabric.window=window;else{var jsdom=require("jsdom"),virtualWindow=new jsdom.JSDOM(decodeURIComponent("%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E"),{features:{FetchExternalResources:["img"]},resources:"usable"}).window;fabric.document=virtualWindow.document,fabric.jsdomImplForWrapper=require("jsdom/lib/jsdom/living/generated/utils").implForWrapper,fabric.nodeCanvas=require("jsdom/lib/jsdom/utils").Canvas,fabric.window=virtualWindow,DOMParser=fabric.window.DOMParser}function resizeCanvasIfNeeded(t){var e=t.targetCanvas,i=e.width,r=e.height,n=t.destinationWidth,s=t.destinationHeight;i===n&&r===s||(e.width=n,e.height=s)}function copyGLTo2DDrawImage(t,e){var i=t.canvas,r=e.targetCanvas,n=r.getContext("2d");n.translate(0,r.height),n.scale(1,-1);var s=i.height-r.height;n.drawImage(i,0,s,r.width,r.height,0,0,r.width,r.height)}function copyGLTo2DPutImageData(t,e){var i=e.targetCanvas.getContext("2d"),r=e.destinationWidth,n=e.destinationHeight,s=r*n*4,o=new Uint8Array(this.imageBuffer,0,s),a=new Uint8ClampedArray(this.imageBuffer,0,s);t.readPixels(0,0,r,n,t.RGBA,t.UNSIGNED_BYTE,o);var h=new ImageData(a,r,n);i.putImageData(h,0,0)}fabric.isTouchSupported="ontouchstart"in fabric.window||"ontouchstart"in fabric.document||fabric.window&&fabric.window.navigator&&0<fabric.window.navigator.maxTouchPoints,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-dashoffset","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","id","paint-order","vector-effect","instantiated_by_use","clip-path"],fabric.DPI=96,fabric.reNum="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)",fabric.fontPaths={},fabric.iMatrix=[1,0,0,1,0,0],fabric.perfLimitSizeTotal=2097152,fabric.maxCacheSideLimit=4096,fabric.minCacheSideLimit=256,fabric.charWidthsCache={},fabric.textureSize=2048,fabric.disableStyleCopyPaste=!1,fabric.enableGLFiltering=!0,fabric.devicePixelRatio=fabric.window.devicePixelRatio||fabric.window.webkitDevicePixelRatio||fabric.window.mozDevicePixelRatio||1,fabric.browserShadowBlurConstant=1,fabric.arcToSegmentsCache={},fabric.boundsOfCurveCache={},fabric.cachesBoundsOfCurve=!0,fabric.forceGLPutImageData=!1,fabric.initFilterBackend=function(){return fabric.enableGLFiltering&&fabric.isWebglSupported&&fabric.isWebglSupported(fabric.textureSize)?(console.log("max texture size: "+fabric.maxTextureSize),new fabric.WebglFilterBackend({tileSize:fabric.textureSize})):fabric.Canvas2dFilterBackend?new fabric.Canvas2dFilterBackend:void 0},"undefined"!=typeof document&&"undefined"!=typeof window&&(window.fabric=fabric),function(){function r(t,e){if(this.__eventListeners[t]){var i=this.__eventListeners[t];e?i[i.indexOf(e)]=!1:fabric.util.array.fill(i,!1)}}function t(t,e){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var i in t)this.on(i,t[i]);else this.__eventListeners[t]||(this.__eventListeners[t]=[]),this.__eventListeners[t].push(e);return this}function e(t,e){if(!this.__eventListeners)return this;if(0===arguments.length)for(t in this.__eventListeners)r.call(this,t);else if(1===arguments.length&&"object"==typeof t)for(var i in t)r.call(this,i,t[i]);else r.call(this,t,e);return this}function i(t,e){if(!this.__eventListeners)return this;var i=this.__eventListeners[t];if(!i)return this;for(var r=0,n=i.length;r<n;r++)i[r]&&i[r].call(this,e||{});return this.__eventListeners[t]=i.filter(function(t){return!1!==t}),this}fabric.Observable={observe:t,stopObserving:e,fire:i,on:t,off:e,trigger:i}}(),fabric.Collection={_objects:[],add:function(){if(this._objects.push.apply(this._objects,arguments),this._onObjectAdded)for(var t=0,e=arguments.length;t<e;t++)this._onObjectAdded(arguments[t]);return this.renderOnAddRemove&&this.requestRenderAll(),this},insertAt:function(t,e,i){var r=this._objects;return i?r[e]=t:r.splice(e,0,t),this._onObjectAdded&&this._onObjectAdded(t),this.renderOnAddRemove&&this.requestRenderAll(),this},remove:function(){for(var t,e=this._objects,i=!1,r=0,n=arguments.length;r<n;r++)-1!==(t=e.indexOf(arguments[r]))&&(i=!0,e.splice(t,1),this._onObjectRemoved&&this._onObjectRemoved(arguments[r]));return this.renderOnAddRemove&&i&&this.requestRenderAll(),this},forEachObject:function(t,e){for(var i=this.getObjects(),r=0,n=i.length;r<n;r++)t.call(e,i[r],r,i);return this},getObjects:function(e){return void 0===e?this._objects.concat():this._objects.filter(function(t){return t.type===e})},item:function(t){return this._objects[t]},isEmpty:function(){return 0===this._objects.length},size:function(){return this._objects.length},contains:function(t){return-1<this._objects.indexOf(t)},complexity:function(){return this._objects.reduce(function(t,e){return t+=e.complexity?e.complexity():0},0)}},fabric.CommonMethods={_setOptions:function(t){for(var e in t)this.set(e,t[e])},_initGradient:function(t,e){!t||!t.colorStops||t instanceof fabric.Gradient||this.set(e,new fabric.Gradient(t))},_initPattern:function(t,e,i){!t||!t.source||t instanceof fabric.Pattern?i&&i():this.set(e,new fabric.Pattern(t,i))},_initClipping:function(t){if(t.clipTo&&"string"==typeof t.clipTo){var e=fabric.util.getFunctionBody(t.clipTo);void 0!==e&&(this.clipTo=new Function("ctx",e))}},_setObject:function(t){for(var e in t)this._set(e,t[e])},set:function(t,e){return"object"==typeof t?this._setObject(t):"function"==typeof e&&"clipTo"!==t?this._set(t,e(this.get(t))):this._set(t,e),this},_set:function(t,e){this[t]=e},toggle:function(t){var e=this.get(t);return"boolean"==typeof e&&this.set(t,!e),this},get:function(t){return this[t]}},function(s){var d=Math.sqrt,g=Math.atan2,o=Math.pow,a=Math.abs,h=Math.PI/180,i=Math.PI/2;fabric.util={cos:function(t){if(0===t)return 1;switch(t<0&&(t=-t),t/i){case 1:case 3:return 0;case 2:return-1}return Math.cos(t)},sin:function(t){if(0===t)return 0;var e=1;switch(t<0&&(e=-1),t/i){case 1:return e;case 2:return 0;case 3:return-e}return Math.sin(t)},removeFromArray:function(t,e){var i=t.indexOf(e);return-1!==i&&t.splice(i,1),t},getRandomInt:function(t,e){return Math.floor(Math.random()*(e-t+1))+t},degreesToRadians:function(t){return t*h},radiansToDegrees:function(t){return t/h},rotatePoint:function(t,e,i){t.subtractEquals(e);var r=fabric.util.rotateVector(t,i);return new fabric.Point(r.x,r.y).addEquals(e)},rotateVector:function(t,e){var i=fabric.util.sin(e),r=fabric.util.cos(e);return{x:t.x*r-t.y*i,y:t.x*i+t.y*r}},transformPoint:function(t,e,i){return i?new fabric.Point(e[0]*t.x+e[2]*t.y,e[1]*t.x+e[3]*t.y):new fabric.Point(e[0]*t.x+e[2]*t.y+e[4],e[1]*t.x+e[3]*t.y+e[5])},makeBoundingBoxFromPoints:function(t){var e=[t[0].x,t[1].x,t[2].x,t[3].x],i=fabric.util.array.min(e),r=fabric.util.array.max(e)-i,n=[t[0].y,t[1].y,t[2].y,t[3].y],s=fabric.util.array.min(n);return{left:i,top:s,width:r,height:fabric.util.array.max(n)-s}},invertTransform:function(t){var e=1/(t[0]*t[3]-t[1]*t[2]),i=[e*t[3],-e*t[1],-e*t[2],e*t[0]],r=fabric.util.transformPoint({x:t[4],y:t[5]},i,!0);return i[4]=-r.x,i[5]=-r.y,i},toFixed:function(t,e){return parseFloat(Number(t).toFixed(e))},parseUnit:function(t,e){var i=/\D{0,2}$/.exec(t),r=parseFloat(t);switch(e||(e=fabric.Text.DEFAULT_SVG_FONT_SIZE),i[0]){case"mm":return r*fabric.DPI/25.4;case"cm":return r*fabric.DPI/2.54;case"in":return r*fabric.DPI;case"pt":return r*fabric.DPI/72;case"pc":return r*fabric.DPI/72*12;case"em":return r*e;default:return r}},falseFunction:function(){return!1},getKlass:function(t,e){return t=fabric.util.string.camelize(t.charAt(0).toUpperCase()+t.slice(1)),fabric.util.resolveNamespace(e)[t]},getSvgAttributes:function(t){var e=["instantiated_by_use","style","id","class"];switch(t){case"linearGradient":e=e.concat(["x1","y1","x2","y2","gradientUnits","gradientTransform"]);break;case"radialGradient":e=e.concat(["gradientUnits","gradientTransform","cx","cy","r","fx","fy","fr"]);break;case"stop":e=e.concat(["offset","stop-color","stop-opacity"])}return e},resolveNamespace:function(t){if(!t)return fabric;var e,i=t.split("."),r=i.length,n=s||fabric.window;for(e=0;e<r;++e)n=n[i[e]];return n},loadImage:function(t,e,i,r){if(t){var n=fabric.util.createImage(),s=function(){e&&e.call(i,n),n=n.onload=n.onerror=null};n.onload=s,n.onerror=function(){fabric.log("Error loading "+n.src),e&&e.call(i,null,!0),n=n.onload=n.onerror=null},0!==t.indexOf("data")&&r&&(n.crossOrigin=r),"data:image/svg"===t.substring(0,14)&&(n.onload=null,fabric.util.loadImageInDom(n,s)),n.src=t}else e&&e.call(i,t)},loadImageInDom:function(t,e){var i=fabric.document.createElement("div");i.style.width=i.style.height="1px",i.style.left=i.style.top="-100%",i.style.position="absolute",i.appendChild(t),fabric.document.querySelector("body").appendChild(i),t.onload=function(){e(),i.parentNode.removeChild(i),i=null}},enlivenObjects:function(t,e,n,s){var o=[],i=0,r=(t=t||[]).length;function a(){++i===r&&e&&e(o.filter(function(t){return t}))}r?t.forEach(function(i,r){i&&i.type?fabric.util.getKlass(i.type,n).fromObject(i,function(t,e){e||(o[r]=t),s&&s(i,t,e),a()}):a()}):e&&e(o)},enlivenPatterns:function(t,e){function i(){++n===s&&e&&e(r)}var r=[],n=0,s=(t=t||[]).length;s?t.forEach(function(t,e){t&&t.source?new fabric.Pattern(t,function(t){r[e]=t,i()}):(r[e]=t,i())}):e&&e(r)},groupSVGElements:function(t,e,i){var r;return t&&1===t.length?t[0]:(e&&(e.width&&e.height?e.centerPoint={x:e.width/2,y:e.height/2}:(delete e.width,delete e.height)),r=new fabric.Group(t,e),void 0!==i&&(r.sourcePath=i),r)},populateWithProperties:function(t,e,i){if(i&&"[object Array]"===Object.prototype.toString.call(i))for(var r=0,n=i.length;r<n;r++)i[r]in t&&(e[i[r]]=t[i[r]])},drawDashedLine:function(t,e,i,r,n,s){var o=r-e,a=n-i,h=d(o*o+a*a),c=g(a,o),l=s.length,u=0,f=!0;for(t.save(),t.translate(e,i),t.moveTo(0,0),t.rotate(c),e=0;e<h;)h<(e+=s[u++%l])&&(e=h),t[f?"lineTo":"moveTo"](e,0),f=!f;t.restore()},createCanvasElement:function(){return fabric.document.createElement("canvas")},copyCanvasElement:function(t){var e=fabric.util.createCanvasElement();return e.width=t.width,e.height=t.height,e.getContext("2d").drawImage(t,0,0),e},toDataURL:function(t,e,i){return t.toDataURL("image/"+e,i)},createImage:function(){return fabric.document.createElement("img")},clipContext:function(t,e){e.save(),e.beginPath(),t.clipTo(e),e.clip()},multiplyTransformMatrices:function(t,e,i){return[t[0]*e[0]+t[2]*e[1],t[1]*e[0]+t[3]*e[1],t[0]*e[2]+t[2]*e[3],t[1]*e[2]+t[3]*e[3],i?0:t[0]*e[4]+t[2]*e[5]+t[4],i?0:t[1]*e[4]+t[3]*e[5]+t[5]]},qrDecompose:function(t){var e=g(t[1],t[0]),i=o(t[0],2)+o(t[1],2),r=d(i),n=(t[0]*t[3]-t[2]*t[1])/r,s=g(t[0]*t[2]+t[1]*t[3],i);return{angle:e/h,scaleX:r,scaleY:n,skewX:s/h,skewY:0,translateX:t[4],translateY:t[5]}},customTransformMatrix:function(t,e,i){var r=[1,0,a(Math.tan(i*h)),1],n=[a(t),0,0,a(e)];return fabric.util.multiplyTransformMatrices(n,r,!0)},resetObjectTransform:function(t){t.scaleX=1,t.scaleY=1,t.skewX=0,t.skewY=0,t.flipX=!1,t.flipY=!1,t.rotate(0)},saveObjectTransform:function(t){return{scaleX:t.scaleX,scaleY:t.scaleY,skewX:t.skewX,skewY:t.skewY,angle:t.angle,left:t.left,flipX:t.flipX,flipY:t.flipY,top:t.top}},getFunctionBody:function(t){return(String(t).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(t,e,i,r){0<r&&(r<e?e-=r:e=0,r<i?i-=r:i=0);var n,s=!0,o=t.getImageData(e,i,2*r||1,2*r||1),a=o.data.length;for(n=3;n<a&&!1!==(s=o.data[n]<=0);n+=4);return o=null,s},parsePreserveAspectRatioAttribute:function(t){var e,i="meet",r=t.split(" ");return r&&r.length&&("meet"!==(i=r.pop())&&"slice"!==i?(e=i,i="meet"):r.length&&(e=r.pop())),{meetOrSlice:i,alignX:"none"!==e?e.slice(1,4):"none",alignY:"none"!==e?e.slice(5,8):"none"}},clearFabricFontCache:function(t){(t=(t||"").toLowerCase())?fabric.charWidthsCache[t]&&delete fabric.charWidthsCache[t]:fabric.charWidthsCache={}},limitDimsByArea:function(t,e){var i=Math.sqrt(e*t),r=Math.floor(e/i);return{x:Math.floor(i),y:r}},capValue:function(t,e,i){return Math.max(t,Math.min(e,i))},findScaleToFit:function(t,e){return Math.min(e.width/t.width,e.height/t.height)},findScaleToCover:function(t,e){return Math.max(e.width/t.width,e.height/t.height)},matrixToSVG:function(t){return"matrix("+t.map(function(t){return fabric.util.toFixed(t,fabric.Object.NUM_FRACTION_DIGITS)}).join(" ")+")"}}}("undefined"!=typeof exports?exports:this),function(){var Z=Array.prototype.join;function v(t,e,i,r,n,s,o){var a=Z.call(arguments);if(fabric.arcToSegmentsCache[a])return fabric.arcToSegmentsCache[a];var h=Math.PI,c=o*h/180,l=fabric.util.sin(c),u=fabric.util.cos(c),f=0,d=0,g=-u*t*.5-l*e*.5,p=-u*e*.5+l*t*.5,v=(i=Math.abs(i))*i,m=(r=Math.abs(r))*r,b=p*p,_=g*g,y=v*m-v*b-m*_,x=0;if(y<0){var C=Math.sqrt(1-y/(v*m));i*=C,r*=C}else x=(n===s?-1:1)*Math.sqrt(y/(v*b+m*_));var S=x*i*p/r,T=-x*r*g/i,w=u*S-l*T+.5*t,O=l*S+u*T+.5*e,k=Q(1,0,(g-S)/i,(p-T)/r),P=Q((g-S)/i,(p-T)/r,(-g-S)/i,(-p-T)/r);0===s&&0<P?P-=2*h:1===s&&P<0&&(P+=2*h);for(var D,E,j,A,M,F,I,L,B,R,X,W,Y,G,z,U,V,N=Math.ceil(Math.abs(P/h*2)),H=[],q=P/N,K=8/3*Math.sin(q/4)*Math.sin(q/4)/Math.sin(q/2),J=k+q,$=0;$<N;$++)H[$]=(D=k,E=J,j=u,A=l,M=i,F=r,I=w,L=O,B=K,R=f,X=d,void 0,W=fabric.util.cos(D),Y=fabric.util.sin(D),G=fabric.util.cos(E),z=fabric.util.sin(E),[R+B*(-j*M*Y-A*F*W),X+B*(-A*M*Y+j*F*W),(U=j*M*G-A*F*z+I)+B*(j*M*z+A*F*G),(V=A*M*G+j*F*z+L)+B*(A*M*z-j*F*G),U,V]),f=H[$][4],d=H[$][5],k=J,J+=q;return fabric.arcToSegmentsCache[a]=H}function Q(t,e,i,r){var n=Math.atan2(e,t),s=Math.atan2(r,i);return n<=s?s-n:2*Math.PI-(n-s)}function m(t,e,i,r,n,s,o,a){var h;if(fabric.cachesBoundsOfCurve&&(h=Z.call(arguments),fabric.boundsOfCurveCache[h]))return fabric.boundsOfCurveCache[h];var c,l,u,f,d,g,p,v,m=Math.sqrt,b=Math.min,_=Math.max,y=Math.abs,x=[],C=[[],[]];l=6*t-12*i+6*n,c=-3*t+9*i-9*n+3*o,u=3*i-3*t;for(var S=0;S<2;++S)if(0<S&&(l=6*e-12*r+6*s,c=-3*e+9*r-9*s+3*a,u=3*r-3*e),y(c)<1e-12){if(y(l)<1e-12)continue;0<(f=-u/l)&&f<1&&x.push(f)}else(p=l*l-4*u*c)<0||(0<(d=(-l+(v=m(p)))/(2*c))&&d<1&&x.push(d),0<(g=(-l-v)/(2*c))&&g<1&&x.push(g));for(var T,w,O,k=x.length,P=k;k--;)T=(O=1-(f=x[k]))*O*O*t+3*O*O*f*i+3*O*f*f*n+f*f*f*o,C[0][k]=T,w=O*O*O*e+3*O*O*f*r+3*O*f*f*s+f*f*f*a,C[1][k]=w;C[0][P]=t,C[1][P]=e,C[0][P+1]=o,C[1][P+1]=a;var D=[{x:b.apply(null,C[0]),y:b.apply(null,C[1])},{x:_.apply(null,C[0]),y:_.apply(null,C[1])}];return fabric.cachesBoundsOfCurve&&(fabric.boundsOfCurveCache[h]=D),D}fabric.util.drawArc=function(t,e,i,r){for(var n=r[0],s=r[1],o=r[2],a=r[3],h=r[4],c=[[],[],[],[]],l=v(r[5]-e,r[6]-i,n,s,a,h,o),u=0,f=l.length;u<f;u++)c[u][0]=l[u][0]+e,c[u][1]=l[u][1]+i,c[u][2]=l[u][2]+e,c[u][3]=l[u][3]+i,c[u][4]=l[u][4]+e,c[u][5]=l[u][5]+i,t.bezierCurveTo.apply(t,c[u])},fabric.util.getBoundsOfArc=function(t,e,i,r,n,s,o,a,h){for(var c,l=0,u=0,f=[],d=v(a-t,h-e,i,r,s,o,n),g=0,p=d.length;g<p;g++)c=m(l,u,d[g][0],d[g][1],d[g][2],d[g][3],d[g][4],d[g][5]),f.push({x:c[0].x+t,y:c[0].y+e}),f.push({x:c[1].x+t,y:c[1].y+e}),l=d[g][4],u=d[g][5];return f},fabric.util.getBoundsOfCurve=m}(),function(){var o=Array.prototype.slice;function i(t,e,i){if(t&&0!==t.length){var r=t.length-1,n=e?t[r][e]:t[r];if(e)for(;r--;)i(t[r][e],n)&&(n=t[r][e]);else for(;r--;)i(t[r],n)&&(n=t[r]);return n}}fabric.util.array={fill:function(t,e){for(var i=t.length;i--;)t[i]=e;return t},invoke:function(t,e){for(var i=o.call(arguments,2),r=[],n=0,s=t.length;n<s;n++)r[n]=i.length?t[n][e].apply(t[n],i):t[n][e].call(t[n]);return r},min:function(t,e){return i(t,e,function(t,e){return t<e})},max:function(t,e){return i(t,e,function(t,e){return e<=t})}}}(),function(){function o(t,e,i){if(i)if(!fabric.isLikelyNode&&e instanceof Element)t=e;else if(e instanceof Array){t=[];for(var r=0,n=e.length;r<n;r++)t[r]=o({},e[r],i)}else if(e&&"object"==typeof e)for(var s in e)"canvas"===s?t[s]=o({},e[s]):e.hasOwnProperty(s)&&(t[s]=o({},e[s],i));else t=e;else for(var s in e)t[s]=e[s];return t}fabric.util.object={extend:o,clone:function(t,e){return o({},t,e)}},fabric.util.object.extend(fabric.util,fabric.Observable)}(),function(){function n(t,e){var i=t.charCodeAt(e);if(isNaN(i))return"";if(i<55296||57343<i)return t.charAt(e);if(55296<=i&&i<=56319){if(t.length<=e+1)throw"High surrogate without following low surrogate";var r=t.charCodeAt(e+1);if(r<56320||57343<r)throw"High surrogate without following low surrogate";return t.charAt(e)+t.charAt(e+1)}if(0===e)throw"Low surrogate without preceding high surrogate";var n=t.charCodeAt(e-1);if(n<55296||56319<n)throw"Low surrogate without preceding high surrogate";return!1}fabric.util.string={camelize:function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},capitalize:function(t,e){return t.charAt(0).toUpperCase()+(e?t.slice(1):t.slice(1).toLowerCase())},escapeXml:function(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&apos;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},graphemeSplit:function(t){var e,i=0,r=[];for(i=0;i<t.length;i++)!1!==(e=n(t,i))&&r.push(e);return r}}}(),function(){var s=Array.prototype.slice,o=function(){},i=function(){for(var t in{toString:1})if("toString"===t)return!1;return!0}(),a=function(t,r,n){for(var e in r)e in t.prototype&&"function"==typeof t.prototype[e]&&-1<(r[e]+"").indexOf("callSuper")?t.prototype[e]=function(i){return function(){var t=this.constructor.superclass;this.constructor.superclass=n;var e=r[i].apply(this,arguments);if(this.constructor.superclass=t,"initialize"!==i)return e}}(e):t.prototype[e]=r[e],i&&(r.toString!==Object.prototype.toString&&(t.prototype.toString=r.toString),r.valueOf!==Object.prototype.valueOf&&(t.prototype.valueOf=r.valueOf))};function h(){}function c(t){for(var e=null,i=this;i.constructor.superclass;){var r=i.constructor.superclass.prototype[t];if(i[t]!==r){e=r;break}i=i.constructor.superclass.prototype}return e?1<arguments.length?e.apply(this,s.call(arguments,1)):e.call(this):console.log("tried to callSuper "+t+", method not found in prototype chain",this)}fabric.util.createClass=function(){var t=null,e=s.call(arguments,0);function i(){this.initialize.apply(this,arguments)}"function"==typeof e[0]&&(t=e.shift()),i.superclass=t,i.subclasses=[],t&&(h.prototype=t.prototype,i.prototype=new h,t.subclasses.push(i));for(var r=0,n=e.length;r<n;r++)a(i,e[r],t);return i.prototype.initialize||(i.prototype.initialize=o),(i.prototype.constructor=i).prototype.callSuper=c,i}}(),function(){function t(t){var e,i,r=Array.prototype.slice.call(arguments,1),n=r.length;for(i=0;i<n;i++)if(e=typeof t[r[i]],!/^(?:function|object|unknown)$/.test(e))return!1;return!0}var n,s,e,i,a=(e=0,function(t){return t.__uniqueID||(t.__uniqueID="uniqueID__"+e++)});function o(t,e){return{handler:e,wrappedHandler:(i=t,r=e,function(t){r.call(n(i),t||fabric.window.event)})};var i,r}i={},n=function(t){return i[t]},s=function(t,e){i[t]=e};var r,h,c=t(fabric.document.documentElement,"addEventListener","removeEventListener")&&t(fabric.window,"addEventListener","removeEventListener"),l=t(fabric.document.documentElement,"attachEvent","detachEvent")&&t(fabric.window,"attachEvent","detachEvent"),u={},f={};c?(r=function(t,e,i,r){t&&t.addEventListener(e,i,!l&&r)},h=function(t,e,i,r){t&&t.removeEventListener(e,i,!l&&r)}):l?(r=function(t,e,i){if(t){var r=a(t);s(r,t),u[r]||(u[r]={}),u[r][e]||(u[r][e]=[]);var n=o(r,i);u[r][e].push(n),t.attachEvent("on"+e,n.wrappedHandler)}},h=function(t,e,i){if(t){var r,n=a(t);if(u[n]&&u[n][e])for(var s=0,o=u[n][e].length;s<o;s++)(r=u[n][e][s])&&r.handler===i&&(t.detachEvent("on"+e,r.wrappedHandler),u[n][e][s]=null)}}):(r=function(t,e,i){if(t){var n,s,r=a(t);if(f[r]||(f[r]={}),!f[r][e]){f[r][e]=[];var o=t["on"+e];o&&f[r][e].push(o),t["on"+e]=(n=r,s=e,function(t){if(f[n]&&f[n][s])for(var e=f[n][s],i=0,r=e.length;i<r;i++)e[i].call(this,t||fabric.window.event)})}f[r][e].push(i)}},h=function(t,e,i){if(t){var r=a(t);if(f[r]&&f[r][e])for(var n=f[r][e],s=0,o=n.length;s<o;s++)n[s]===i&&n.splice(s,1)}}),fabric.util.addListener=r,fabric.util.removeListener=h;var d=function(t){return t.clientX},g=function(t){return t.clientY};function p(t,e,i){var r,n=t["touchend"===t.type?"changedTouches":"touches"];return n&&n[0]&&(r=n[0][i]),void 0===r&&(r=t[i]),r}fabric.isTouchSupported&&(d=function(t){return p(t,0,"clientX")},g=function(t){return p(t,0,"clientY")}),fabric.util.getPointer=function(t){t||(t=fabric.window.event);var e=t.target||("unknown"!=typeof t.srcElement?t.srcElement:null),i=fabric.util.getScrollLeftTop(e);return{x:d(t)+i.left,y:g(t)+i.top}}}(),function(){var t=fabric.document.createElement("div"),e="string"==typeof t.style.opacity,i="string"==typeof t.style.filter,r=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,n=function(t){return t};e?n=function(t,e){return t.style.opacity=e,t}:i&&(n=function(t,e){var i=t.style;return t.currentStyle&&!t.currentStyle.hasLayout&&(i.zoom=1),r.test(i.filter)?(e=.9999<=e?"":"alpha(opacity="+100*e+")",i.filter=i.filter.replace(r,e)):i.filter+=" alpha(opacity="+100*e+")",t}),fabric.util.setStyle=function(t,e){var i=t.style;if(!i)return t;if("string"==typeof e)return t.style.cssText+=";"+e,-1<e.indexOf("opacity")?n(t,e.match(/opacity:\s*(\d?\.?\d*)/)[1]):t;for(var r in e)"opacity"===r?n(t,e[r]):i["float"===r||"cssFloat"===r?void 0===i.styleFloat?"cssFloat":"styleFloat":r]=e[r];return t}}(),function(){var e=Array.prototype.slice;var t,h,i,r,n=function(t){return e.call(t,0)};try{t=n(fabric.document.childNodes)instanceof Array}catch(t){}function s(t,e){var i=fabric.document.createElement(t);for(var r in e)"class"===r?i.className=e[r]:"for"===r?i.htmlFor=e[r]:i.setAttribute(r,e[r]);return i}function c(t){for(var e=0,i=0,r=fabric.document.documentElement,n=fabric.document.body||{scrollLeft:0,scrollTop:0};t&&(t.parentNode||t.host)&&((t=t.parentNode||t.host)===fabric.document?(e=n.scrollLeft||r.scrollLeft||0,i=n.scrollTop||r.scrollTop||0):(e+=t.scrollLeft||0,i+=t.scrollTop||0),1!==t.nodeType||"fixed"!==t.style.position););return{left:e,top:i}}t||(n=function(t){for(var e=new Array(t.length),i=t.length;i--;)e[i]=t[i];return e}),h=fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?function(t,e){var i=fabric.document.defaultView.getComputedStyle(t,null);return i?i[e]:void 0}:function(t,e){var i=t.style[e];return!i&&t.currentStyle&&(i=t.currentStyle[e]),i},i=fabric.document.documentElement.style,r="userSelect"in i?"userSelect":"MozUserSelect"in i?"MozUserSelect":"WebkitUserSelect"in i?"WebkitUserSelect":"KhtmlUserSelect"in i?"KhtmlUserSelect":"",fabric.util.makeElementUnselectable=function(t){return void 0!==t.onselectstart&&(t.onselectstart=fabric.util.falseFunction),r?t.style[r]="none":"string"==typeof t.unselectable&&(t.unselectable="on"),t},fabric.util.makeElementSelectable=function(t){return void 0!==t.onselectstart&&(t.onselectstart=null),r?t.style[r]="":"string"==typeof t.unselectable&&(t.unselectable=""),t},fabric.util.getScript=function(t,e){var i=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),n=!0;r.onload=r.onreadystatechange=function(t){if(n){if("string"==typeof this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)return;n=!1,e(t||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=t,i.appendChild(r)},fabric.util.getById=function(t){return"string"==typeof t?fabric.document.getElementById(t):t},fabric.util.toArray=n,fabric.util.makeElement=s,fabric.util.addClass=function(t,e){t&&-1===(" "+t.className+" ").indexOf(" "+e+" ")&&(t.className+=(t.className?" ":"")+e)},fabric.util.wrapElement=function(t,e,i){return"string"==typeof e&&(e=s(e,i)),t.parentNode&&t.parentNode.replaceChild(e,t),e.appendChild(t),e},fabric.util.getScrollLeftTop=c,fabric.util.getElementOffset=function(t){var e,i,r=t&&t.ownerDocument,n={left:0,top:0},s={left:0,top:0},o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!r)return s;for(var a in o)s[o[a]]+=parseInt(h(t,a),10)||0;return e=r.documentElement,void 0!==t.getBoundingClientRect&&(n=t.getBoundingClientRect()),i=c(t),{left:n.left+i.left-(e.clientLeft||0)+s.left,top:n.top+i.top-(e.clientTop||0)+s.top}},fabric.util.getElementStyle=h,fabric.util.getNodeCanvas=function(t){var e=fabric.jsdomImplForWrapper(t);return e._canvas||e._image},fabric.util.cleanUpJsdomNode=function(t){if(fabric.isLikelyNode){var e=fabric.jsdomImplForWrapper(t);e&&(e._image=null,e._canvas=null,e._currentSrc=null,e._attributes=null,e._classList=null)}}}(),function(){function h(){}fabric.util.request=function(t,e){e||(e={});var i,r,n=e.method?e.method.toUpperCase():"GET",s=e.onComplete||function(){},o=new fabric.window.XMLHttpRequest,a=e.body||e.parameters;return o.onreadystatechange=function(){4===o.readyState&&(s(o),o.onreadystatechange=h)},"GET"===n&&(a=null,"string"==typeof e.parameters&&(i=t,r=e.parameters,t=i+(/\?/.test(i)?"&":"?")+r)),o.open(n,t,!0),"POST"!==n&&"PUT"!==n||o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.send(a),o}}(),fabric.log=function(){},fabric.warn=function(){},"undefined"!=typeof console&&["log","warn"].forEach(function(t){void 0!==console[t]&&"function"==typeof console[t].apply&&(fabric[t]=function(){return console[t].apply(console,arguments)})}),function(){function e(){return!1}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(t){return fabric.window.setTimeout(t,1e3/60)},i=fabric.window.cancelAnimationFrame||fabric.window.clearTimeout;function b(){return t.apply(fabric.window,arguments)}fabric.util.animate=function(m){b(function(t){m||(m={});var o,a=t||+new Date,h=m.duration||500,c=a+h,l=m.onChange||e,u=m.abort||e,f=m.onComplete||e,d=m.easing||function(t,e,i,r){return-i*Math.cos(t/r*(Math.PI/2))+i+e},g="startValue"in m?m.startValue:0,p="endValue"in m?m.endValue:100,v=m.byValue||p-g;m.onStart&&m.onStart(),function t(e){if(u())f(p,1,1);else{o=e||+new Date;var i=c<o?h:o-a,r=i/h,n=d(i,g,v,h),s=Math.abs((n-g)/v);l(n,s,r),c<o?m.onComplete&&m.onComplete():b(t)}}(a)})},fabric.util.requestAnimFrame=b,fabric.util.cancelAnimFrame=function(){return i.apply(fabric.window,arguments)}}(),fabric.util.animateColor=function(t,e,i,c){var r=new fabric.Color(t).getSource(),n=new fabric.Color(e).getSource();c=c||{},fabric.util.animate(fabric.util.object.extend(c,{duration:i||500,startValue:r,endValue:n,byValue:n,easing:function(t,e,i,r){var n,s,o,a,h=c.colorEasing?c.colorEasing(t,r):1-Math.cos(t/r*(Math.PI/2));return n=e,s=i,o=h,a="rgba("+parseInt(n[0]+o*(s[0]-n[0]),10)+","+parseInt(n[1]+o*(s[1]-n[1]),10)+","+parseInt(n[2]+o*(s[2]-n[2]),10),a+=","+(n&&s?parseFloat(n[3]+o*(s[3]-n[3])):1),a+=")"}}))},function(){function o(t,e,i,r){return t<Math.abs(e)?(t=e,r=i/4):r=0===e&&0===t?i/(2*Math.PI)*Math.asin(1):i/(2*Math.PI)*Math.asin(e/t),{a:t,c:e,p:i,s:r}}function a(t,e,i){return t.a*Math.pow(2,10*(e-=1))*Math.sin((e*i-t.s)*(2*Math.PI)/t.p)}function n(t,e,i,r){return i-s(r-t,0,i,r)+e}function s(t,e,i,r){return(t/=r)<1/2.75?i*(7.5625*t*t)+e:t<2/2.75?i*(7.5625*(t-=1.5/2.75)*t+.75)+e:t<2.5/2.75?i*(7.5625*(t-=2.25/2.75)*t+.9375)+e:i*(7.5625*(t-=2.625/2.75)*t+.984375)+e}fabric.util.ease={easeInQuad:function(t,e,i,r){return i*(t/=r)*t+e},easeOutQuad:function(t,e,i,r){return-i*(t/=r)*(t-2)+e},easeInOutQuad:function(t,e,i,r){return(t/=r/2)<1?i/2*t*t+e:-i/2*(--t*(t-2)-1)+e},easeInCubic:function(t,e,i,r){return i*(t/=r)*t*t+e},easeOutCubic:function(t,e,i,r){return i*((t=t/r-1)*t*t+1)+e},easeInOutCubic:function(t,e,i,r){return(t/=r/2)<1?i/2*t*t*t+e:i/2*((t-=2)*t*t+2)+e},easeInQuart:function(t,e,i,r){return i*(t/=r)*t*t*t+e},easeOutQuart:function(t,e,i,r){return-i*((t=t/r-1)*t*t*t-1)+e},easeInOutQuart:function(t,e,i,r){return(t/=r/2)<1?i/2*t*t*t*t+e:-i/2*((t-=2)*t*t*t-2)+e},easeInQuint:function(t,e,i,r){return i*(t/=r)*t*t*t*t+e},easeOutQuint:function(t,e,i,r){return i*((t=t/r-1)*t*t*t*t+1)+e},easeInOutQuint:function(t,e,i,r){return(t/=r/2)<1?i/2*t*t*t*t*t+e:i/2*((t-=2)*t*t*t*t+2)+e},easeInSine:function(t,e,i,r){return-i*Math.cos(t/r*(Math.PI/2))+i+e},easeOutSine:function(t,e,i,r){return i*Math.sin(t/r*(Math.PI/2))+e},easeInOutSine:function(t,e,i,r){return-i/2*(Math.cos(Math.PI*t/r)-1)+e},easeInExpo:function(t,e,i,r){return 0===t?e:i*Math.pow(2,10*(t/r-1))+e},easeOutExpo:function(t,e,i,r){return t===r?e+i:i*(1-Math.pow(2,-10*t/r))+e},easeInOutExpo:function(t,e,i,r){return 0===t?e:t===r?e+i:(t/=r/2)<1?i/2*Math.pow(2,10*(t-1))+e:i/2*(2-Math.pow(2,-10*--t))+e},easeInCirc:function(t,e,i,r){return-i*(Math.sqrt(1-(t/=r)*t)-1)+e},easeOutCirc:function(t,e,i,r){return i*Math.sqrt(1-(t=t/r-1)*t)+e},easeInOutCirc:function(t,e,i,r){return(t/=r/2)<1?-i/2*(Math.sqrt(1-t*t)-1)+e:i/2*(Math.sqrt(1-(t-=2)*t)+1)+e},easeInElastic:function(t,e,i,r){var n=0;return 0===t?e:1==(t/=r)?e+i:(n||(n=.3*r),-a(o(i,i,n,1.70158),t,r)+e)},easeOutElastic:function(t,e,i,r){var n=0;if(0===t)return e;if(1==(t/=r))return e+i;n||(n=.3*r);var s=o(i,i,n,1.70158);return s.a*Math.pow(2,-10*t)*Math.sin((t*r-s.s)*(2*Math.PI)/s.p)+s.c+e},easeInOutElastic:function(t,e,i,r){var n=0;if(0===t)return e;if(2==(t/=r/2))return e+i;n||(n=r*(.3*1.5));var s=o(i,i,n,1.70158);return t<1?-.5*a(s,t,r)+e:s.a*Math.pow(2,-10*(t-=1))*Math.sin((t*r-s.s)*(2*Math.PI)/s.p)*.5+s.c+e},easeInBack:function(t,e,i,r,n){return void 0===n&&(n=1.70158),i*(t/=r)*t*((n+1)*t-n)+e},easeOutBack:function(t,e,i,r,n){return void 0===n&&(n=1.70158),i*((t=t/r-1)*t*((n+1)*t+n)+1)+e},easeInOutBack:function(t,e,i,r,n){return void 0===n&&(n=1.70158),(t/=r/2)<1?i/2*(t*t*((1+(n*=1.525))*t-n))+e:i/2*((t-=2)*t*((1+(n*=1.525))*t+n)+2)+e},easeInBounce:n,easeOutBounce:s,easeInOutBounce:function(t,e,i,r){return t<r/2?.5*n(2*t,0,i,r)+e:.5*s(2*t-r,0,i,r)+.5*i+e}}}(),function(t){"use strict";var C=t.fabric||(t.fabric={}),g=C.util.object.extend,f=C.util.object.clone,p=C.util.toFixed,S=C.util.parseUnit,h=C.util.multiplyTransformMatrices,v={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","letter-spacing":"charSpacing","paint-order":"paintFirst","stroke-dasharray":"strokeDashArray","stroke-dashoffset":"strokeDashOffset","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"textAnchor",opacity:"opacity","clip-path":"clipPath","clip-rule":"clipRule","vector-effect":"strokeUniform"},m={stroke:"strokeOpacity",fill:"fillOpacity"};function b(t,e,i,r){var n,s="[object Array]"===Object.prototype.toString.call(e);if("fill"!==t&&"stroke"!==t||"none"!==e)if("vector-effect"===t)e="non-scaling-stroke"===e;else if("strokeDashArray"===t)e="none"===e?null:e.replace(/,/g," ").split(/\s+/).map(parseFloat);else if("transformMatrix"===t)e=i&&i.transformMatrix?h(i.transformMatrix,C.parseTransformAttribute(e)):C.parseTransformAttribute(e);else if("visible"===t)e="none"!==e&&"hidden"!==e,i&&!1===i.visible&&(e=!1);else if("opacity"===t)e=parseFloat(e),i&&void 0!==i.opacity&&(e*=i.opacity);else if("textAnchor"===t)e="start"===e?"left":"end"===e?"right":"center";else if("charSpacing"===t)n=S(e,r)/r*1e3;else if("paintFirst"===t){var o=e.indexOf("fill"),a=e.indexOf("stroke");e="fill";-1<o&&-1<a&&a<o?e="stroke":-1===o&&-1<a&&(e="stroke")}else{if("href"===t||"xlink:href"===t)return e;n=s?e.map(S):S(e,r)}else e="";return!s&&isNaN(n)?e:n}function e(t){return new RegExp("^("+t.join("|")+")\\b","i")}function _(t,e){var i,r,n,s,o=[];for(n=0,s=e.length;n<s;n++)i=e[n],r=t.getElementsByTagName(i),o=o.concat(Array.prototype.slice.call(r));return o}function y(t,e){var i,r=!0;return(i=n(t,e.pop()))&&e.length&&(r=function(t,e){var i,r=!0;for(;t.parentNode&&1===t.parentNode.nodeType&&e.length;)r&&(i=e.pop()),t=t.parentNode,r=n(t,i);return 0===e.length}(t,e)),i&&r&&0===e.length}function n(t,e){var i,r,n=t.nodeName,s=t.getAttribute("class"),o=t.getAttribute("id");if(i=new RegExp("^"+n,"i"),e=e.replace(i,""),o&&e.length&&(i=new RegExp("#"+o+"(?![a-zA-Z\\-]+)","i"),e=e.replace(i,"")),s&&e.length)for(r=(s=s.split(" ")).length;r--;)i=new RegExp("\\."+s[r]+"(?![a-zA-Z\\-]+)","i"),e=e.replace(i,"");return 0===e.length}function x(t,e){var i;if(t.getElementById&&(i=t.getElementById(e)),i)return i;var r,n,s,o=t.getElementsByTagName("*");for(n=0,s=o.length;n<s;n++)if(e===(r=o[n]).getAttribute("id"))return r}C.svgValidTagNamesRegEx=e(["path","circle","polygon","polyline","ellipse","rect","line","image","text"]),C.svgViewBoxElementsRegEx=e(["symbol","image","marker","pattern","view","svg"]),C.svgInvalidAncestorsRegEx=e(["pattern","defs","symbol","metadata","clipPath","mask","desc"]),C.svgValidParentsRegEx=e(["symbol","g","a","svg","clipPath","defs"]),C.cssRules={},C.gradientDefs={},C.clipPaths={},C.parseTransformAttribute=function(){function b(t,e,i){t[i]=Math.tan(C.util.degreesToRadians(e[0]))}var _=C.iMatrix,t=C.reNum,e="(?:\\s+,?\\s*|,\\s*)",y="(?:"+("(?:(matrix)\\s*\\(\\s*("+t+")"+e+"("+t+")"+e+"("+t+")"+e+"("+t+")"+e+"("+t+")"+e+"("+t+")\\s*\\))")+"|"+("(?:(translate)\\s*\\(\\s*("+t+")(?:"+e+"("+t+"))?\\s*\\))")+"|"+("(?:(scale)\\s*\\(\\s*("+t+")(?:"+e+"("+t+"))?\\s*\\))")+"|"+("(?:(rotate)\\s*\\(\\s*("+t+")(?:"+e+"("+t+")"+e+"("+t+"))?\\s*\\))")+"|"+("(?:(skewX)\\s*\\(\\s*("+t+")\\s*\\))")+"|"+("(?:(skewY)\\s*\\(\\s*("+t+")\\s*\\))")+")",i=new RegExp("^\\s*(?:"+("(?:"+y+"(?:"+e+"*"+y+")*)")+"?)\\s*$"),r=new RegExp(y,"g");return function(t){var v=_.concat(),m=[];if(!t||t&&!i.test(t))return v;t.replace(r,function(t){var e,i,r,n,s,o,a,h,c,l,u,f,d=new RegExp(y).exec(t).filter(function(t){return!!t}),g=d[1],p=d.slice(2).map(parseFloat);switch(g){case"translate":f=p,(u=v)[4]=f[0],2===f.length&&(u[5]=f[1]);break;case"rotate":p[0]=C.util.degreesToRadians(p[0]),s=v,o=p,a=C.util.cos(o[0]),h=C.util.sin(o[0]),l=c=0,3===o.length&&(c=o[1],l=o[2]),s[0]=a,s[1]=h,s[2]=-h,s[3]=a,s[4]=c-(a*c-h*l),s[5]=l-(h*c+a*l);break;case"scale":e=v,r=(i=p)[0],n=2===i.length?i[1]:i[0],e[0]=r,e[3]=n;break;case"skewX":b(v,p,2);break;case"skewY":b(v,p,1);break;case"matrix":v=p}m.push(v.concat()),v=_.concat()});for(var e=m[0];1<m.length;)m.shift(),e=C.util.multiplyTransformMatrices(e,m[0]);return e}}();var T=new RegExp("^\\s*("+C.reNum+"+)\\s*,?\\s*("+C.reNum+"+)\\s*,?\\s*("+C.reNum+"+)\\s*,?\\s*("+C.reNum+"+)\\s*$");function w(t){var e,i,r,n,s,o,a=t.getAttribute("viewBox"),h=1,c=1,l=t.getAttribute("width"),u=t.getAttribute("height"),f=t.getAttribute("x")||0,d=t.getAttribute("y")||0,g=t.getAttribute("preserveAspectRatio")||"",p=!a||!C.svgViewBoxElementsRegEx.test(t.nodeName)||!(a=a.match(T)),v=!l||!u||"100%"===l||"100%"===u,m=p&&v,b={},_="",y=0,x=0;if(b.width=0,b.height=0,b.toBeParsed=m)return b;if(p)return b.width=S(l),b.height=S(u),b;if(e=-parseFloat(a[1]),i=-parseFloat(a[2]),r=parseFloat(a[3]),n=parseFloat(a[4]),v?(b.width=r,b.height=n):(b.width=S(l),b.height=S(u),h=b.width/r,c=b.height/n),"none"!==(g=C.util.parsePreserveAspectRatioAttribute(g)).alignX&&("meet"===g.meetOrSlice&&(c=h=c<h?c:h),"slice"===g.meetOrSlice&&(c=h=c<h?h:c),y=b.width-r*h,x=b.height-n*h,"Mid"===g.alignX&&(y/=2),"Mid"===g.alignY&&(x/=2),"Min"===g.alignX&&(y=0),"Min"===g.alignY&&(x=0)),1===h&&1===c&&0===e&&0===i&&0===f&&0===d)return b;if((f||d)&&(_=" translate("+S(f)+" "+S(d)+") "),s=_+" matrix("+h+" 0 0 "+c+" "+(e*h+y)+" "+(i*c+x)+") ",b.viewboxTransform=C.parseTransformAttribute(s),"svg"===t.nodeName){for(o=t.ownerDocument.createElement("g");t.firstChild;)o.appendChild(t.firstChild);t.appendChild(o)}else s=(o=t).getAttribute("transform")+s;return o.setAttribute("transform",s),b}function s(t,e){var i="xlink:href",r=x(t,e.getAttribute(i).substr(1));if(r&&r.getAttribute(i)&&s(t,r),["gradientTransform","x1","x2","y1","y2","gradientUnits","cx","cy","r","fx","fy"].forEach(function(t){e.hasAttribute(t)||e.setAttribute(t,r.getAttribute(t))}),!e.children.length)for(var n=r.cloneNode(!0);n.firstChild;)e.appendChild(n.firstChild);e.removeAttribute(i)}C.parseSVGDocument=function(t,i,e,r){if(t){!function(t){for(var e=_(t,["use","svg:use"]),i=0;e.length&&i<e.length;){var r,n,s,o,a=e[i],h=(a.getAttribute("xlink:href")||a.getAttribute("href")).substr(1),c=a.getAttribute("x")||0,l=a.getAttribute("y")||0,u=x(t,h).cloneNode(!0),f=(u.getAttribute("transform")||"")+" translate("+c+", "+l+")",d=e.length;if(w(u),/^svg$/i.test(u.nodeName)){var g=u.ownerDocument.createElement("g");for(n=0,o=(s=u.attributes).length;n<o;n++)r=s.item(n),g.setAttribute(r.nodeName,r.nodeValue);for(;u.firstChild;)g.appendChild(u.firstChild);u=g}for(n=0,o=(s=a.attributes).length;n<o;n++)"x"!==(r=s.item(n)).nodeName&&"y"!==r.nodeName&&"xlink:href"!==r.nodeName&&"href"!==r.nodeName&&("transform"===r.nodeName?f=r.nodeValue+" "+f:u.setAttribute(r.nodeName,r.nodeValue));u.setAttribute("transform",f),u.setAttribute("instantiated_by_use","1"),u.removeAttribute("id"),a.parentNode.replaceChild(u,a),e.length===d&&i++}}(t);var n,s,o=C.Object.__uid++,a=w(t),h=C.util.toArray(t.getElementsByTagName("*"));if(a.crossOrigin=r&&r.crossOrigin,a.svgUid=o,0===h.length&&C.isLikelyNode){var c=[];for(n=0,s=(h=t.selectNodes('//*[name(.)!="svg"]')).length;n<s;n++)c[n]=h[n];h=c}var l=h.filter(function(t){return w(t),C.svgValidTagNamesRegEx.test(t.nodeName.replace("svg:",""))&&!function(t,e){for(;t&&(t=t.parentNode);)if(t.nodeName&&e.test(t.nodeName.replace("svg:",""))&&!t.getAttribute("instantiated_by_use"))return!0;return!1}(t,C.svgInvalidAncestorsRegEx)});if(!l||l&&!l.length)i&&i([],{});else{var u={};h.filter(function(t){return"clipPath"===t.nodeName.replace("svg:","")}).forEach(function(t){var e=t.getAttribute("id");u[e]=C.util.toArray(t.getElementsByTagName("*")).filter(function(t){return C.svgValidTagNamesRegEx.test(t.nodeName.replace("svg:",""))})}),C.gradientDefs[o]=C.getGradientDefs(t),C.cssRules[o]=C.getCSSRules(t),C.clipPaths[o]=u,C.parseElements(l,function(t,e){i&&(i(t,a,e,h),delete C.gradientDefs[o],delete C.cssRules[o],delete C.clipPaths[o])},f(a),e,r)}}};var c=new RegExp("(normal|italic)?\\s*(normal|small-caps)?\\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*("+C.reNum+"(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|"+C.reNum+"))?\\s+(.*)");g(C,{parseFontDeclaration:function(t,e){var i=t.match(c);if(i){var r=i[1],n=i[3],s=i[4],o=i[5],a=i[6];r&&(e.fontStyle=r),n&&(e.fontWeight=isNaN(parseFloat(n))?n:parseFloat(n)),s&&(e.fontSize=S(s)),a&&(e.fontFamily=a),o&&(e.lineHeight="normal"===o?1:o)}},getGradientDefs:function(t){var e,i=_(t,["linearGradient","radialGradient","svg:linearGradient","svg:radialGradient"]),r=0,n={};for(r=i.length;r--;)(e=i[r]).getAttribute("xlink:href")&&s(t,e),n[e.getAttribute("id")]=e;return n},parseAttributes:function(i,t,e){if(i){var r,n,s,o={};void 0===e&&(e=i.getAttribute("svgUid")),i.parentNode&&C.svgValidParentsRegEx.test(i.parentNode.nodeName)&&(o=C.parseAttributes(i.parentNode,t,e));var a=t.reduce(function(t,e){return(r=i.getAttribute(e))&&(t[e]=r),t},{});a=g(a,g(function(t,e){var i={};for(var r in C.cssRules[e])if(y(t,r.split(" ")))for(var n in C.cssRules[e][r])i[n]=C.cssRules[e][r][n];return i}(i,e),C.parseStyleAttribute(i))),n=s=o.fontSize||C.Text.DEFAULT_SVG_FONT_SIZE,a["font-size"]&&(a["font-size"]=n=S(a["font-size"],s));var h,c,l,u={};for(var f in a)c=b(h=(l=f)in v?v[l]:l,a[f],o,n),u[h]=c;u&&u.font&&C.parseFontDeclaration(u.font,u);var d=g(o,u);return C.svgValidParentsRegEx.test(i.nodeName)?d:function(t){for(var e in m)if(void 0!==t[m[e]]&&""!==t[e]){if(void 0===t[e]){if(!C.Object.prototype[e])continue;t[e]=C.Object.prototype[e]}if(0!==t[e].indexOf("url(")){var i=new C.Color(t[e]);t[e]=i.setAlpha(p(i.getAlpha()*t[m[e]],2)).toRgba()}}return t}(d)}},parseElements:function(t,e,i,r,n){new C.ElementsParser(t,e,i,r,n).parse()},parseStyleAttribute:function(t){var i,r,n,e={},s=t.getAttribute("style");return s&&("string"==typeof s?(i=e,s.replace(/;\s*$/,"").split(";").forEach(function(t){var e=t.split(":");r=e[0].trim().toLowerCase(),n=e[1].trim(),i[r]=n})):function(t,e){var i,r;for(var n in t)void 0!==t[n]&&(i=n.toLowerCase(),r=t[n],e[i]=r)}(s,e)),e},parsePointsAttribute:function(t){if(!t)return null;var e,i,r=[];for(e=0,i=(t=(t=t.replace(/,/g," ").trim()).split(/\s+/)).length;e<i;e+=2)r.push({x:parseFloat(t[e]),y:parseFloat(t[e+1])});return r},getCSSRules:function(t){var a,h,e=t.getElementsByTagName("style"),c={};for(a=0,h=e.length;a<h;a++){var i=e[a].textContent||e[a].text;""!==(i=i.replace(/\/\*[\s\S]*?\*\//g,"")).trim()&&i.match(/[^{]*\{[\s\S]*?\}/g).map(function(t){return t.trim()}).forEach(function(t){var e=t.match(/([\s\S]*?)\s*\{([^}]*)\}/),i={},r=e[2].trim().replace(/;$/,"").split(/\s*;\s*/);for(a=0,h=r.length;a<h;a++){var n=r[a].split(/\s*:\s*/),s=n[0],o=n[1];i[s]=o}(t=e[1]).split(",").forEach(function(t){""!==(t=t.replace(/^svg/i,"").trim())&&(c[t]?C.util.object.extend(c[t],i):c[t]=C.util.object.clone(i))})})}return c},loadSVGFromURL:function(t,n,i,r){t=t.replace(/^\n\s*/,"").trim(),new C.util.request(t,{method:"get",onComplete:function(t){var e=t.responseXML;e&&!e.documentElement&&C.window.ActiveXObject&&t.responseText&&((e=new ActiveXObject("Microsoft.XMLDOM")).async="false",e.loadXML(t.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,"")));if(!e||!e.documentElement)return n&&n(null),!1;C.parseSVGDocument(e.documentElement,function(t,e,i,r){n&&n(t,e,i,r)},i,r)}})},loadSVGFromString:function(t,n,e,i){var r;if(t=t.trim(),void 0!==C.window.DOMParser){var s=new C.window.DOMParser;s&&s.parseFromString&&(r=s.parseFromString(t,"text/xml"))}else C.window.ActiveXObject&&((r=new ActiveXObject("Microsoft.XMLDOM")).async="false",r.loadXML(t.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,"")));C.parseSVGDocument(r.documentElement,function(t,e,i,r){n(t,e,i,r)},e,i)}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(t,e,i,r,n){this.elements=t,this.callback=e,this.options=i,this.reviver=r,this.svgUid=i&&i.svgUid||0,this.parsingOptions=n,this.regexUrl=/^url\(['"]?#([^'"]+)['"]?\)/g},function(t){t.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},t.createObjects=function(){var i=this;this.elements.forEach(function(t,e){t.setAttribute("svgUid",i.svgUid),i.createObject(t,e)})},t.findTag=function(t){return fabric[fabric.util.string.capitalize(t.tagName.replace("svg:",""))]},t.createObject=function(t,e){var i=this.findTag(t);if(i&&i.fromElement)try{i.fromElement(t,this.createCallback(e,t),this.options)}catch(t){fabric.log(t)}else this.checkIfDone()},t.createCallback=function(i,r){var n=this;return function(t){var e;n.resolveGradient(t,"fill"),n.resolveGradient(t,"stroke"),t instanceof fabric.Image&&t._originalElement&&(e=t.parsePreserveAspectRatioAttribute(r)),t._removeTransformMatrix(e),n.resolveClipPath(t),n.reviver&&n.reviver(r,t),n.instances[i]=t,n.checkIfDone()}},t.extractPropertyDefinition=function(t,e,i){var r=t[e];if(/^url\(/.test(r)){var n=this.regexUrl.exec(r)[1];return this.regexUrl.lastIndex=0,fabric[i][this.svgUid][n]}},t.resolveGradient=function(t,e){var i=this.extractPropertyDefinition(t,e,"gradientDefs");i&&t.set(e,fabric.Gradient.fromElement(i,t))},t.createClipPathCallback=function(t,e){return function(t){t._removeTransformMatrix(),t.fillRule=t.clipRule,e.push(t)}},t.resolveClipPath=function(t){var e,i,r,n,s=this.extractPropertyDefinition(t,"clipPath","clipPaths");if(s){r=[],i=fabric.util.invertTransform(t.calcTransformMatrix());for(var o=0;o<s.length;o++)e=s[o],this.findTag(e).fromElement(e,this.createClipPathCallback(t,r),this.options);s=1===r.length?r[0]:new fabric.Group(r),n=fabric.util.multiplyTransformMatrices(i,s.calcTransformMatrix());var a=fabric.util.qrDecompose(n);s.flipX=!1,s.flipY=!1,s.set("scaleX",a.scaleX),s.set("scaleY",a.scaleY),s.angle=a.angle,s.skewX=a.skewX,s.skewY=0,s.setPositionByOrigin({x:a.translateX,y:a.translateY},"center","center"),t.clipPath=s}},t.checkIfDone=function(){0==--this.numElements&&(this.instances=this.instances.filter(function(t){return null!=t}),this.callback(this.instances,this.elements))}}(fabric.ElementsParser.prototype),function(t){"use strict";var e=t.fabric||(t.fabric={});function i(t,e){this.x=t,this.y=e}e.Point?e.warn("fabric.Point is already defined"):(e.Point=i).prototype={type:"point",constructor:i,add:function(t){return new i(this.x+t.x,this.y+t.y)},addEquals:function(t){return this.x+=t.x,this.y+=t.y,this},scalarAdd:function(t){return new i(this.x+t,this.y+t)},scalarAddEquals:function(t){return this.x+=t,this.y+=t,this},subtract:function(t){return new i(this.x-t.x,this.y-t.y)},subtractEquals:function(t){return this.x-=t.x,this.y-=t.y,this},scalarSubtract:function(t){return new i(this.x-t,this.y-t)},scalarSubtractEquals:function(t){return this.x-=t,this.y-=t,this},multiply:function(t){return new i(this.x*t,this.y*t)},multiplyEquals:function(t){return this.x*=t,this.y*=t,this},divide:function(t){return new i(this.x/t,this.y/t)},divideEquals:function(t){return this.x/=t,this.y/=t,this},eq:function(t){return this.x===t.x&&this.y===t.y},lt:function(t){return this.x<t.x&&this.y<t.y},lte:function(t){return this.x<=t.x&&this.y<=t.y},gt:function(t){return this.x>t.x&&this.y>t.y},gte:function(t){return this.x>=t.x&&this.y>=t.y},lerp:function(t,e){return void 0===e&&(e=.5),e=Math.max(Math.min(1,e),0),new i(this.x+(t.x-this.x)*e,this.y+(t.y-this.y)*e)},distanceFrom:function(t){var e=this.x-t.x,i=this.y-t.y;return Math.sqrt(e*e+i*i)},midPointFrom:function(t){return this.lerp(t)},min:function(t){return new i(Math.min(this.x,t.x),Math.min(this.y,t.y))},max:function(t){return new i(Math.max(this.x,t.x),Math.max(this.y,t.y))},toString:function(){return this.x+","+this.y},setXY:function(t,e){return this.x=t,this.y=e,this},setX:function(t){return this.x=t,this},setY:function(t){return this.y=t,this},setFromPoint:function(t){return this.x=t.x,this.y=t.y,this},swap:function(t){var e=this.x,i=this.y;this.x=t.x,this.y=t.y,t.x=e,t.y=i},clone:function(){return new i(this.x,this.y)}}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var f=t.fabric||(t.fabric={});function d(t){this.status=t,this.points=[]}f.Intersection?f.warn("fabric.Intersection is already defined"):(f.Intersection=d,f.Intersection.prototype={constructor:d,appendPoint:function(t){return this.points.push(t),this},appendPoints:function(t){return this.points=this.points.concat(t),this}},f.Intersection.intersectLineLine=function(t,e,i,r){var n,s=(r.x-i.x)*(t.y-i.y)-(r.y-i.y)*(t.x-i.x),o=(e.x-t.x)*(t.y-i.y)-(e.y-t.y)*(t.x-i.x),a=(r.y-i.y)*(e.x-t.x)-(r.x-i.x)*(e.y-t.y);if(0!==a){var h=s/a,c=o/a;0<=h&&h<=1&&0<=c&&c<=1?(n=new d("Intersection")).appendPoint(new f.Point(t.x+h*(e.x-t.x),t.y+h*(e.y-t.y))):n=new d}else n=new d(0===s||0===o?"Coincident":"Parallel");return n},f.Intersection.intersectLinePolygon=function(t,e,i){var r,n,s,o,a=new d,h=i.length;for(o=0;o<h;o++)r=i[o],n=i[(o+1)%h],s=d.intersectLineLine(t,e,r,n),a.appendPoints(s.points);return 0<a.points.length&&(a.status="Intersection"),a},f.Intersection.intersectPolygonPolygon=function(t,e){var i,r=new d,n=t.length;for(i=0;i<n;i++){var s=t[i],o=t[(i+1)%n],a=d.intersectLinePolygon(s,o,e);r.appendPoints(a.points)}return 0<r.points.length&&(r.status="Intersection"),r},f.Intersection.intersectPolygonRectangle=function(t,e,i){var r=e.min(i),n=e.max(i),s=new f.Point(n.x,r.y),o=new f.Point(r.x,n.y),a=d.intersectLinePolygon(r,s,t),h=d.intersectLinePolygon(s,n,t),c=d.intersectLinePolygon(n,o,t),l=d.intersectLinePolygon(o,r,t),u=new d;return u.appendPoints(a.points),u.appendPoints(h.points),u.appendPoints(c.points),u.appendPoints(l.points),0<u.points.length&&(u.status="Intersection"),u})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var c=t.fabric||(t.fabric={});function l(t){t?this._tryParsingColor(t):this.setSource([0,0,0,1])}function u(t,e,i){return i<0&&(i+=1),1<i&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}c.Color?c.warn("fabric.Color is already defined."):(c.Color=l,c.Color.prototype={_tryParsingColor:function(t){var e;t in l.colorNameMap&&(t=l.colorNameMap[t]),"transparent"===t&&(e=[255,255,255,0]),e||(e=l.sourceFromHex(t)),e||(e=l.sourceFromRgb(t)),e||(e=l.sourceFromHsl(t)),e||(e=[0,0,0,1]),e&&this.setSource(e)},_rgbToHsl:function(t,e,i){t/=255,e/=255,i/=255;var r,n,s,o=c.util.array.max([t,e,i]),a=c.util.array.min([t,e,i]);if(s=(o+a)/2,o===a)r=n=0;else{var h=o-a;switch(n=.5<s?h/(2-o-a):h/(o+a),o){case t:r=(e-i)/h+(e<i?6:0);break;case e:r=(i-t)/h+2;break;case i:r=(t-e)/h+4}r/=6}return[Math.round(360*r),Math.round(100*n),Math.round(100*s)]},getSource:function(){return this._source},setSource:function(t){this._source=t},toRgb:function(){var t=this.getSource();return"rgb("+t[0]+","+t[1]+","+t[2]+")"},toRgba:function(){var t=this.getSource();return"rgba("+t[0]+","+t[1]+","+t[2]+","+t[3]+")"},toHsl:function(){var t=this.getSource(),e=this._rgbToHsl(t[0],t[1],t[2]);return"hsl("+e[0]+","+e[1]+"%,"+e[2]+"%)"},toHsla:function(){var t=this.getSource(),e=this._rgbToHsl(t[0],t[1],t[2]);return"hsla("+e[0]+","+e[1]+"%,"+e[2]+"%,"+t[3]+")"},toHex:function(){var t,e,i,r=this.getSource();return t=1===(t=r[0].toString(16)).length?"0"+t:t,e=1===(e=r[1].toString(16)).length?"0"+e:e,i=1===(i=r[2].toString(16)).length?"0"+i:i,t.toUpperCase()+e.toUpperCase()+i.toUpperCase()},toHexa:function(){var t,e=this.getSource();return t=1===(t=(t=Math.round(255*e[3])).toString(16)).length?"0"+t:t,this.toHex()+t.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(t){var e=this.getSource();return e[3]=t,this.setSource(e),this},toGrayscale:function(){var t=this.getSource(),e=parseInt((.3*t[0]+.59*t[1]+.11*t[2]).toFixed(0),10),i=t[3];return this.setSource([e,e,e,i]),this},toBlackWhite:function(t){var e=this.getSource(),i=(.3*e[0]+.59*e[1]+.11*e[2]).toFixed(0),r=e[3];return t=t||127,i=Number(i)<Number(t)?0:255,this.setSource([i,i,i,r]),this},overlayWith:function(t){t instanceof l||(t=new l(t));var e,i=[],r=this.getAlpha(),n=this.getSource(),s=t.getSource();for(e=0;e<3;e++)i.push(Math.round(.5*n[e]+.5*s[e]));return i[3]=r,this.setSource(i),this}},c.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*)?\)$/i,c.Color.reHSLa=/^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i,c.Color.reHex=/^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i,c.Color.colorNameMap={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aqua:"#00FFFF",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blue:"#0000FF",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgrey:"#A9A9A9",darkgreen:"#006400",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",fuchsia:"#FF00FF",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgray:"#D3D3D3",lightgrey:"#D3D3D3",lightgreen:"#90EE90",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",lime:"#00FF00",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",maroon:"#800000",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",navy:"#000080",oldlace:"#FDF5E6",olive:"#808000",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",purple:"#800080",rebeccapurple:"#663399",red:"#FF0000",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",silver:"#C0C0C0",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",teal:"#008080",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",white:"#FFFFFF",whitesmoke:"#F5F5F5",yellow:"#FFFF00",yellowgreen:"#9ACD32"},c.Color.fromRgb=function(t){return l.fromSource(l.sourceFromRgb(t))},c.Color.sourceFromRgb=function(t){var e=t.match(l.reRGBa);if(e){var i=parseInt(e[1],10)/(/%$/.test(e[1])?100:1)*(/%$/.test(e[1])?255:1),r=parseInt(e[2],10)/(/%$/.test(e[2])?100:1)*(/%$/.test(e[2])?255:1),n=parseInt(e[3],10)/(/%$/.test(e[3])?100:1)*(/%$/.test(e[3])?255:1);return[parseInt(i,10),parseInt(r,10),parseInt(n,10),e[4]?parseFloat(e[4]):1]}},c.Color.fromRgba=l.fromRgb,c.Color.fromHsl=function(t){return l.fromSource(l.sourceFromHsl(t))},c.Color.sourceFromHsl=function(t){var e=t.match(l.reHSLa);if(e){var i,r,n,s=(parseFloat(e[1])%360+360)%360/360,o=parseFloat(e[2])/(/%$/.test(e[2])?100:1),a=parseFloat(e[3])/(/%$/.test(e[3])?100:1);if(0===o)i=r=n=a;else{var h=a<=.5?a*(o+1):a+o-a*o,c=2*a-h;i=u(c,h,s+1/3),r=u(c,h,s),n=u(c,h,s-1/3)}return[Math.round(255*i),Math.round(255*r),Math.round(255*n),e[4]?parseFloat(e[4]):1]}},c.Color.fromHsla=l.fromHsl,c.Color.fromHex=function(t){return l.fromSource(l.sourceFromHex(t))},c.Color.sourceFromHex=function(t){if(t.match(l.reHex)){var e=t.slice(t.indexOf("#")+1),i=3===e.length||4===e.length,r=8===e.length||4===e.length,n=i?e.charAt(0)+e.charAt(0):e.substring(0,2),s=i?e.charAt(1)+e.charAt(1):e.substring(2,4),o=i?e.charAt(2)+e.charAt(2):e.substring(4,6),a=r?i?e.charAt(3)+e.charAt(3):e.substring(6,8):"FF";return[parseInt(n,16),parseInt(s,16),parseInt(o,16),parseFloat((parseInt(a,16)/255).toFixed(2))]}},c.Color.fromSource=function(t){var e=new l;return e.setSource(t),e})}("undefined"!=typeof exports?exports:this),function(){function d(t){var e,i,r,n,s=t.getAttribute("style"),o=t.getAttribute("offset")||0;if(o=(o=parseFloat(o)/(/%$/.test(o)?100:1))<0?0:1<o?1:o,s){var a=s.split(/\s*;\s*/);for(""===a[a.length-1]&&a.pop(),n=a.length;n--;){var h=a[n].split(/\s*:\s*/),c=h[0].trim(),l=h[1].trim();"stop-color"===c?e=l:"stop-opacity"===c&&(r=l)}}return e||(e=t.getAttribute("stop-color")||"rgb(0,0,0)"),r||(r=t.getAttribute("stop-opacity")),i=(e=new fabric.Color(e)).getAlpha(),r=isNaN(parseFloat(r))?1:parseFloat(r),r*=i,{offset:o,color:e.toRgb(),opacity:r}}var v=fabric.util.object.clone;function g(t,e,i){var r,n=0,s=1,o="";for(var a in e)"Infinity"===e[a]?e[a]=1:"-Infinity"===e[a]&&(e[a]=0),r=parseFloat(e[a],10),s="string"==typeof e[a]&&/^(\d+\.\d+)%|(\d+)%$/.test(e[a])?.01:1,"x1"===a||"x2"===a||"r2"===a?(s*="objectBoundingBox"===i?t.width:1,n="objectBoundingBox"===i&&t.left||0):"y1"!==a&&"y2"!==a||(s*="objectBoundingBox"===i?t.height:1,n="objectBoundingBox"===i&&t.top||0),e[a]=r*s+n;if("ellipse"===t.type&&null!==e.r2&&"objectBoundingBox"===i&&t.rx!==t.ry){var h=t.ry/t.rx;o=" scale(1, "+h+")",e.y1&&(e.y1/=h),e.y2&&(e.y2/=h)}return o}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(t){t||(t={});var e={};this.id=fabric.Object.__uid++,this.type=t.type||"linear",e={x1:t.coords.x1||0,y1:t.coords.y1||0,x2:t.coords.x2||0,y2:t.coords.y2||0},"radial"===this.type&&(e.r1=t.coords.r1||0,e.r2=t.coords.r2||0),this.coords=e,this.colorStops=t.colorStops.slice(),t.gradientTransform&&(this.gradientTransform=t.gradientTransform),this.offsetX=t.offsetX||this.offsetX,this.offsetY=t.offsetY||this.offsetY},addColorStop:function(t){for(var e in t){var i=new fabric.Color(t[e]);this.colorStops.push({offset:parseFloat(e),color:i.toRgb(),opacity:i.getAlpha()})}return this},toObject:function(t){var e={type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY,gradientTransform:this.gradientTransform?this.gradientTransform.concat():this.gradientTransform};return fabric.util.populateWithProperties(this,e,t),e},toSVG:function(t,e){var i,r,n,s,o=v(this.coords,!0),a=(e=e||{},v(this.colorStops,!0)),h=o.r1>o.r2,c=this.gradientTransform?this.gradientTransform.concat():fabric.iMatrix.concat(),l=t.width/2-this.offsetX,u=t.height/2-this.offsetY,f=!!e.additionalTransform;if(a.sort(function(t,e){return t.offset-e.offset}),"path"===t.type&&(l-=t.pathOffset.x,u-=t.pathOffset.y),c[4]-=l,c[5]-=u,s='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',s+=' gradientTransform="'+(f?e.additionalTransform+" ":"")+fabric.util.matrixToSVG(c)+'" ',"linear"===this.type?n=["<linearGradient ",s,' x1="',o.x1,'" y1="',o.y1,'" x2="',o.x2,'" y2="',o.y2,'">\n']:"radial"===this.type&&(n=["<radialGradient ",s,' cx="',h?o.x1:o.x2,'" cy="',h?o.y1:o.y2,'" r="',h?o.r1:o.r2,'" fx="',h?o.x2:o.x1,'" fy="',h?o.y2:o.y1,'">\n']),"radial"===this.type){if(h)for((a=a.concat()).reverse(),i=0,r=a.length;i<r;i++)a[i].offset=1-a[i].offset;var d=Math.min(o.r1,o.r2);if(0<d){var g=d/Math.max(o.r1,o.r2);for(i=0,r=a.length;i<r;i++)a[i].offset+=g*(1-a[i].offset)}}for(i=0,r=a.length;i<r;i++){var p=a[i];n.push("<stop ",'offset="',100*p.offset+"%",'" style="stop-color:',p.color,void 0!==p.opacity?";stop-opacity: "+p.opacity:";",'"/>\n')}return n.push("linear"===this.type?"</linearGradient>\n":"</radialGradient>\n"),n.join("")},toLive:function(t){var e,i,r,n=fabric.util.object.clone(this.coords);if(this.type){for("linear"===this.type?e=t.createLinearGradient(n.x1,n.y1,n.x2,n.y2):"radial"===this.type&&(e=t.createRadialGradient(n.x1,n.y1,n.r1,n.x2,n.y2,n.r2)),i=0,r=this.colorStops.length;i<r;i++){var s=this.colorStops[i].color,o=this.colorStops[i].opacity,a=this.colorStops[i].offset;void 0!==o&&(s=new fabric.Color(s).setAlpha(o).toRgba()),e.addColorStop(a,s)}return e}}}),fabric.util.object.extend(fabric.Gradient,{fromElement:function(t,e){var i,r,n,s,o,a,h=t.getElementsByTagName("stop"),c=t.getAttribute("gradientUnits")||"objectBoundingBox",l=t.getAttribute("gradientTransform"),u=[];for("linear"===(i="linearGradient"===t.nodeName||"LINEARGRADIENT"===t.nodeName?"linear":"radial")?r={x1:(a=t).getAttribute("x1")||0,y1:a.getAttribute("y1")||0,x2:a.getAttribute("x2")||"100%",y2:a.getAttribute("y2")||0}:"radial"===i&&(r={x1:(o=t).getAttribute("fx")||o.getAttribute("cx")||"50%",y1:o.getAttribute("fy")||o.getAttribute("cy")||"50%",r1:0,x2:o.getAttribute("cx")||"50%",y2:o.getAttribute("cy")||"50%",r2:o.getAttribute("r")||"50%"}),s=h.length;s--;)u.push(d(h[s]));n=g(e,r,c);var f=new fabric.Gradient({type:i,coords:r,colorStops:u,offsetX:-e.left,offsetY:-e.top});return(l||""!==n)&&(f.gradientTransform=fabric.parseTransformAttribute((l||"")+n)),f},forObject:function(t,e){return e||(e={}),g(t,e.coords,"userSpaceOnUse"),new fabric.Gradient(e)}})}(),function(){"use strict";var n=fabric.util.toFixed;fabric.Pattern=fabric.util.createClass({repeat:"repeat",offsetX:0,offsetY:0,crossOrigin:"",patternTransform:null,initialize:function(t,e){if(t||(t={}),this.id=fabric.Object.__uid++,this.setOptions(t),!t.source||t.source&&"string"!=typeof t.source)e&&e(this);else if(void 0!==fabric.util.getFunctionBody(t.source))this.source=new Function(fabric.util.getFunctionBody(t.source)),e&&e(this);else{var i=this;this.source=fabric.util.createImage(),fabric.util.loadImage(t.source,function(t){i.source=t,e&&e(i)},null,this.crossOrigin)}},toObject:function(t){var e,i,r=fabric.Object.NUM_FRACTION_DIGITS;return"function"==typeof this.source?e=String(this.source):"string"==typeof this.source.src?e=this.source.src:"object"==typeof this.source&&this.source.toDataURL&&(e=this.source.toDataURL()),i={type:"pattern",source:e,repeat:this.repeat,crossOrigin:this.crossOrigin,offsetX:n(this.offsetX,r),offsetY:n(this.offsetY,r),patternTransform:this.patternTransform?this.patternTransform.concat():null},fabric.util.populateWithProperties(this,i,t),i},toSVG:function(t){var e="function"==typeof this.source?this.source():this.source,i=e.width/t.width,r=e.height/t.height,n=this.offsetX/t.width,s=this.offsetY/t.height,o="";return"repeat-x"!==this.repeat&&"no-repeat"!==this.repeat||(r=1,s&&(r+=Math.abs(s))),"repeat-y"!==this.repeat&&"no-repeat"!==this.repeat||(i=1,n&&(i+=Math.abs(n))),e.src?o=e.src:e.toDataURL&&(o=e.toDataURL()),'<pattern id="SVGID_'+this.id+'" x="'+n+'" y="'+s+'" width="'+i+'" height="'+r+'">\n<image x="0" y="0" width="'+e.width+'" height="'+e.height+'" xlink:href="'+o+'"></image>\n</pattern>\n'},setOptions:function(t){for(var e in t)this[e]=t[e]},toLive:function(t){var e="function"==typeof this.source?this.source():this.source;if(!e)return"";if(void 0!==e.src){if(!e.complete)return"";if(0===e.naturalWidth||0===e.naturalHeight)return""}return t.createPattern(e,this.repeat)}})}(),function(t){"use strict";var o=t.fabric||(t.fabric={}),a=o.util.toFixed;o.Shadow?o.warn("fabric.Shadow is already defined."):(o.Shadow=o.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,nonScaling:!1,initialize:function(t){for(var e in"string"==typeof t&&(t=this._parseShadow(t)),t)this[e]=t[e];this.id=o.Object.__uid++},_parseShadow:function(t){var e=t.trim(),i=o.Shadow.reOffsetsAndBlur.exec(e)||[];return{color:(e.replace(o.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)").trim(),offsetX:parseInt(i[1],10)||0,offsetY:parseInt(i[2],10)||0,blur:parseInt(i[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(t){var e=40,i=40,r=o.Object.NUM_FRACTION_DIGITS,n=o.util.rotateVector({x:this.offsetX,y:this.offsetY},o.util.degreesToRadians(-t.angle)),s=new o.Color(this.color);return t.width&&t.height&&(e=100*a((Math.abs(n.x)+this.blur)/t.width,r)+20,i=100*a((Math.abs(n.y)+this.blur)/t.height,r)+20),t.flipX&&(n.x*=-1),t.flipY&&(n.y*=-1),'<filter id="SVGID_'+this.id+'" y="-'+i+'%" height="'+(100+2*i)+'%" x="-'+e+'%" width="'+(100+2*e)+'%" >\n\t<feGaussianBlur in="SourceAlpha" stdDeviation="'+a(this.blur?this.blur/2:0,r)+'"></feGaussianBlur>\n\t<feOffset dx="'+a(n.x,r)+'" dy="'+a(n.y,r)+'" result="oBlur" ></feOffset>\n\t<feFlood flood-color="'+s.toRgb()+'" flood-opacity="'+s.getAlpha()+'"/>\n\t<feComposite in2="oBlur" operator="in" />\n\t<feMerge>\n\t\t<feMergeNode></feMergeNode>\n\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n\t</feMerge>\n</filter>\n'},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY,affectStroke:this.affectStroke,nonScaling:this.nonScaling};var e={},i=o.Shadow.prototype;return["color","blur","offsetX","offsetY","affectStroke","nonScaling"].forEach(function(t){this[t]!==i[t]&&(e[t]=this[t])},this),e}}),o.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/)}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)fabric.warn("fabric.StaticCanvas is already defined.");else{var n=fabric.util.object.extend,t=fabric.util.getElementOffset,c=fabric.util.removeFromArray,a=fabric.util.toFixed,s=fabric.util.transformPoint,o=fabric.util.invertTransform,i=fabric.util.getNodeCanvas,r=fabric.util.createCanvasElement,e=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass(fabric.CommonMethods,{initialize:function(t,e){e||(e={}),this.renderAndResetBound=this.renderAndReset.bind(this),this.requestRenderAllBound=this.requestRenderAll.bind(this),this._initStatic(t,e)},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!1,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:fabric.iMatrix.concat(),backgroundVpt:!0,overlayVpt:!0,onBeforeScaleRotate:function(){},enableRetinaScaling:!0,vptCoords:{},skipOffscreen:!0,clipPath:void 0,_initStatic:function(t,e){var i=this.requestRenderAllBound;this._objects=[],this._createLowerCanvas(t),this._initOptions(e),this._setImageSmoothing(),this.interactive||this._initRetinaScaling(),e.overlayImage&&this.setOverlayImage(e.overlayImage,i),e.backgroundImage&&this.setBackgroundImage(e.backgroundImage,i),e.backgroundColor&&this.setBackgroundColor(e.backgroundColor,i),e.overlayColor&&this.setOverlayColor(e.overlayColor,i),this.calcOffset()},_isRetinaScaling:function(){return 1!==fabric.devicePixelRatio&&this.enableRetinaScaling},getRetinaScaling:function(){return this._isRetinaScaling()?fabric.devicePixelRatio:1},_initRetinaScaling:function(){this._isRetinaScaling()&&(this.lowerCanvasEl.setAttribute("width",this.width*fabric.devicePixelRatio),this.lowerCanvasEl.setAttribute("height",this.height*fabric.devicePixelRatio),this.contextContainer.scale(fabric.devicePixelRatio,fabric.devicePixelRatio))},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(t,e,i){return this.__setBgOverlayImage("overlayImage",t,e,i)},setBackgroundImage:function(t,e,i){return this.__setBgOverlayImage("backgroundImage",t,e,i)},setOverlayColor:function(t,e){return this.__setBgOverlayColor("overlayColor",t,e)},setBackgroundColor:function(t,e){return this.__setBgOverlayColor("backgroundColor",t,e)},_setImageSmoothing:function(){var t=this.getContext();t.imageSmoothingEnabled=t.imageSmoothingEnabled||t.webkitImageSmoothingEnabled||t.mozImageSmoothingEnabled||t.msImageSmoothingEnabled||t.oImageSmoothingEnabled,t.imageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(i,t,r,n){return"string"==typeof t?fabric.util.loadImage(t,function(t){if(t){var e=new fabric.Image(t,n);(this[i]=e).canvas=this}r&&r(t)},this,n&&n.crossOrigin):(n&&t.setOptions(n),(this[i]=t)&&(t.canvas=this),r&&r(t)),this},__setBgOverlayColor:function(t,e,i){return this[t]=e,this._initGradient(e,t),this._initPattern(e,t,i),this},_createCanvasElement:function(){var t=r();if(!t)throw e;if(t.style||(t.style={}),void 0===t.getContext)throw e;return t},_initOptions:function(t){var e=this.lowerCanvasEl;this._setOptions(t),this.width=this.width||parseInt(e.width,10)||0,this.height=this.height||parseInt(e.height,10)||0,this.lowerCanvasEl.style&&(e.width=this.width,e.height=this.height,e.style.width=this.width+"px",e.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(t){t&&t.getContext?this.lowerCanvasEl=t:this.lowerCanvasEl=fabric.util.getById(t)||this._createCanvasElement(),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(t,e){return this.setDimensions({width:t},e)},setHeight:function(t,e){return this.setDimensions({height:t},e)},setDimensions:function(t,e){var i;for(var r in e=e||{},t)i=t[r],e.cssOnly||(this._setBackstoreDimension(r,t[r]),i+="px",this.hasLostContext=!0),e.backstoreOnly||this._setCssDimension(r,i);return this._isCurrentlyDrawing&&this.freeDrawingBrush&&this.freeDrawingBrush._setBrushStyles(),this._initRetinaScaling(),this._setImageSmoothing(),this.calcOffset(),e.cssOnly||this.requestRenderAll(),this},_setBackstoreDimension:function(t,e){return this.lowerCanvasEl[t]=e,this.upperCanvasEl&&(this.upperCanvasEl[t]=e),this.cacheCanvasEl&&(this.cacheCanvasEl[t]=e),this[t]=e,this},_setCssDimension:function(t,e){return this.lowerCanvasEl.style[t]=e,this.upperCanvasEl&&(this.upperCanvasEl.style[t]=e),this.wrapperEl&&(this.wrapperEl.style[t]=e),this},getZoom:function(){return this.viewportTransform[0]},setViewportTransform:function(t){var e,i,r,n=this._activeObject;for(this.viewportTransform=t,i=0,r=this._objects.length;i<r;i++)(e=this._objects[i]).group||e.setCoords(!1,!0);return n&&"activeSelection"===n.type&&n.setCoords(!1,!0),this.calcViewportBoundaries(),this.renderOnAddRemove&&this.requestRenderAll(),this},zoomToPoint:function(t,e){var i=t,r=this.viewportTransform.slice(0);t=s(t,o(this.viewportTransform)),r[0]=e,r[3]=e;var n=s(t,r);return r[4]+=i.x-n.x,r[5]+=i.y-n.y,this.setViewportTransform(r)},setZoom:function(t){return this.zoomToPoint(new fabric.Point(0,0),t),this},absolutePan:function(t){var e=this.viewportTransform.slice(0);return e[4]=-t.x,e[5]=-t.y,this.setViewportTransform(e)},relativePan:function(t){return this.absolutePan(new fabric.Point(-t.x-this.viewportTransform[4],-t.y-this.viewportTransform[5]))},getElement:function(){return this.lowerCanvasEl},_onObjectAdded:function(t){this.stateful&&t.setupState(),t._set("canvas",this),t.setCoords(),this.fire("object:added",{target:t}),t.fire("added")},_onObjectRemoved:function(t){this.fire("object:removed",{target:t}),t.fire("removed"),delete t.canvas},clearContext:function(t){return t.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.backgroundImage=null,this.overlayImage=null,this.backgroundColor="",this.overlayColor="",this._hasITextHandlers&&(this.off("mouse:up",this._mouseUpITextHandler),this._iTextInstances=null,this._hasITextHandlers=!1),this.clearContext(this.contextContainer),this.fire("canvas:cleared"),this.renderOnAddRemove&&this.requestRenderAll(),this},renderAll:function(){var t=this.contextContainer;return this.renderCanvas(t,this._objects),this},renderAndReset:function(){this.isRendering=0,this.renderAll()},requestRenderAll:function(){return this.isRendering||(this.isRendering=fabric.util.requestAnimFrame(this.renderAndResetBound)),this},calcViewportBoundaries:function(){var t={},e=this.width,i=this.height,r=o(this.viewportTransform);return t.tl=s({x:0,y:0},r),t.br=s({x:e,y:i},r),t.tr=new fabric.Point(t.br.x,t.tl.y),t.bl=new fabric.Point(t.tl.x,t.br.y),this.vptCoords=t},cancelRequestedRender:function(){this.isRendering&&(fabric.util.cancelAnimFrame(this.isRendering),this.isRendering=0)},renderCanvas:function(t,e){var i=this.viewportTransform,r=this.clipPath;this.cancelRequestedRender(),this.calcViewportBoundaries(),this.clearContext(t),this.fire("before:render",{ctx:t}),this.clipTo&&fabric.util.clipContext(this,t),this._renderBackground(t),t.save(),t.transform(i[0],i[1],i[2],i[3],i[4],i[5]),this._renderObjects(t,e),t.restore(),!this.controlsAboveOverlay&&this.interactive&&this.drawControls(t),this.clipTo&&t.restore(),r&&(r.canvas=this,r.shouldCache(),r._transformDone=!0,r.renderCache({forClipping:!0}),this.drawClipPathOnCanvas(t)),this._renderOverlay(t),this.controlsAboveOverlay&&this.interactive&&this.drawControls(t),this.fire("after:render",{ctx:t})},drawClipPathOnCanvas:function(t){var e=this.viewportTransform,i=this.clipPath;t.save(),t.transform(e[0],e[1],e[2],e[3],e[4],e[5]),t.globalCompositeOperation="destination-in",i.transform(t),t.scale(1/i.zoomX,1/i.zoomY),t.drawImage(i._cacheCanvas,-i.cacheTranslationX,-i.cacheTranslationY),t.restore()},_renderObjects:function(t,e){var i,r;for(i=0,r=e.length;i<r;++i)e[i]&&e[i].render(t)},_renderBackgroundOrOverlay:function(t,e){var i=this[e+"Color"],r=this[e+"Image"],n=this.viewportTransform,s=this[e+"Vpt"];if(i||r){if(i){t.save(),t.beginPath(),t.moveTo(0,0),t.lineTo(this.width,0),t.lineTo(this.width,this.height),t.lineTo(0,this.height),t.closePath(),t.fillStyle=i.toLive?i.toLive(t,this):i,s&&t.transform(n[0],n[1],n[2],n[3],n[4]+(i.offsetX||0),n[5]+(i.offsetY||0));var o=i.gradientTransform||i.patternTransform;o&&t.transform(o[0],o[1],o[2],o[3],o[4],o[5]),t.fill(),t.restore()}r&&(t.save(),s&&t.transform(n[0],n[1],n[2],n[3],n[4],n[5]),r.render(t),t.restore())}},_renderBackground:function(t){this._renderBackgroundOrOverlay(t,"background")},_renderOverlay:function(t){this._renderBackgroundOrOverlay(t,"overlay")},getCenter:function(){return{top:this.height/2,left:this.width/2}},centerObjectH:function(t){return this._centerObject(t,new fabric.Point(this.getCenter().left,t.getCenterPoint().y))},centerObjectV:function(t){return this._centerObject(t,new fabric.Point(t.getCenterPoint().x,this.getCenter().top))},centerObject:function(t){var e=this.getCenter();return this._centerObject(t,new fabric.Point(e.left,e.top))},viewportCenterObject:function(t){var e=this.getVpCenter();return this._centerObject(t,e)},viewportCenterObjectH:function(t){var e=this.getVpCenter();return this._centerObject(t,new fabric.Point(e.x,t.getCenterPoint().y)),this},viewportCenterObjectV:function(t){var e=this.getVpCenter();return this._centerObject(t,new fabric.Point(t.getCenterPoint().x,e.y))},getVpCenter:function(){var t=this.getCenter(),e=o(this.viewportTransform);return s({x:t.left,y:t.top},e)},_centerObject:function(t,e){return t.setPositionByOrigin(e,"center","center"),t.setCoords(),this.renderOnAddRemove&&this.requestRenderAll(),this},toDatalessJSON:function(t){return this.toDatalessObject(t)},toObject:function(t){return this._toObjectMethod("toObject",t)},toDatalessObject:function(t){return this._toObjectMethod("toDatalessObject",t)},_toObjectMethod:function(t,e){var i=this.clipPath,r={version:fabric.version,objects:this._toObjects(t,e)};return i&&(r.clipPath=this._toObject(this.clipPath,t,e)),n(r,this.__serializeBgOverlay(t,e)),fabric.util.populateWithProperties(this,r,e),r},_toObjects:function(e,i){return this._objects.filter(function(t){return!t.excludeFromExport}).map(function(t){return this._toObject(t,e,i)},this)},_toObject:function(t,e,i){var r;this.includeDefaultValues||(r=t.includeDefaultValues,t.includeDefaultValues=!1);var n=t[e](i);return this.includeDefaultValues||(t.includeDefaultValues=r),n},__serializeBgOverlay:function(t,e){var i={},r=this.backgroundImage,n=this.overlayImage;return this.backgroundColor&&(i.background=this.backgroundColor.toObject?this.backgroundColor.toObject(e):this.backgroundColor),this.overlayColor&&(i.overlay=this.overlayColor.toObject?this.overlayColor.toObject(e):this.overlayColor),r&&!r.excludeFromExport&&(i.backgroundImage=this._toObject(r,t,e)),n&&!n.excludeFromExport&&(i.overlayImage=this._toObject(n,t,e)),i},svgViewportTransformation:!0,toSVG:function(t,e){t||(t={}),t.reviver=e;var i=[];return this._setSVGPreamble(i,t),this._setSVGHeader(i,t),this.clipPath&&i.push('<g clip-path="url(#'+this.clipPath.clipPathId+')" >\n'),this._setSVGBgOverlayColor(i,"background"),this._setSVGBgOverlayImage(i,"backgroundImage",e),this._setSVGObjects(i,e),this.clipPath&&i.push("</g>\n"),this._setSVGBgOverlayColor(i,"overlay"),this._setSVGBgOverlayImage(i,"overlayImage",e),i.push("</svg>"),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('<?xml version="1.0" encoding="',e.encoding||"UTF-8",'" standalone="no" ?>\n','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ','"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')},_setSVGHeader:function(t,e){var i,r=e.width||this.width,n=e.height||this.height,s='viewBox="0 0 '+this.width+" "+this.height+'" ',o=fabric.Object.NUM_FRACTION_DIGITS;e.viewBox?s='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,s='viewBox="'+a(-i[4]/i[0],o)+" "+a(-i[5]/i[3],o)+" "+a(this.width/i[0],o)+" "+a(this.height/i[3],o)+'" '),t.push("<svg ",'xmlns="http://www.w3.org/2000/svg" ','xmlns:xlink="http://www.w3.org/1999/xlink" ','version="1.1" ','width="',r,'" ','height="',n,'" ',s,'xml:space="preserve">\n',"<desc>Created with Fabric.js ",fabric.version,"</desc>\n","<defs>\n",this.createSVGFontFacesMarkup(),this.createSVGRefElementsMarkup(),this.createSVGClipPathMarkup(e),"</defs>\n")},createSVGClipPathMarkup:function(t){var e=this.clipPath;return e?(e.clipPathId="CLIPPATH_"+fabric.Object.__uid++,'<clipPath id="'+e.clipPathId+'" >\n'+this.clipPath.toClipPathSVG(t.reviver)+"</clipPath>\n"):""},createSVGRefElementsMarkup:function(){var s=this;return["background","overlay"].map(function(t){var e=s[t+"Color"];if(e&&e.toLive){var i=s[t+"Vpt"],r=s.viewportTransform,n={width:s.width/(i?r[0]:1),height:s.height/(i?r[3]:1)};return e.toSVG(n,{additionalTransform:i?fabric.util.matrixToSVG(r):""})}}).join("")},createSVGFontFacesMarkup:function(){var t,e,i,r,n,s,o,a,h="",c={},l=fabric.fontPaths,u=this._objects;for(o=0,a=u.length;o<a;o++)if(e=(t=u[o]).fontFamily,-1!==t.type.indexOf("text")&&!c[e]&&l[e]&&(c[e]=!0,t.styles))for(n in i=t.styles)for(s in r=i[n])!c[e=r[s].fontFamily]&&l[e]&&(c[e]=!0);for(var f in c)h+=["\t\t@font-face {\n","\t\t\tfont-family: '",f,"';\n","\t\t\tsrc: url('",l[f],"');\n","\t\t}\n"].join("");return h&&(h=['\t<style type="text/css">',"<![CDATA[\n",h,"]]>","</style>\n"].join("")),h},_setSVGObjects:function(t,e){var i,r,n,s=this._objects;for(r=0,n=s.length;r<n;r++)(i=s[r]).excludeFromExport||this._setSVGObject(t,i,e)},_setSVGObject:function(t,e,i){t.push(e.toSVG(i))},_setSVGBgOverlayImage:function(t,e,i){this[e]&&!this[e].excludeFromExport&&this[e].toSVG&&t.push(this[e].toSVG(i))},_setSVGBgOverlayColor:function(t,e){var i=this[e+"Color"],r=this.viewportTransform,n=this.width,s=this.height;if(i)if(i.toLive){var o=i.repeat,a=fabric.util.invertTransform(r),h=this[e+"Vpt"]?fabric.util.matrixToSVG(a):"";t.push('<rect transform="'+h+" translate(",n/2,",",s/2,')"',' x="',i.offsetX-n/2,'" y="',i.offsetY-s/2,'" ','width="',"repeat-y"===o||"no-repeat"===o?i.source.width:n,'" height="',"repeat-x"===o||"no-repeat"===o?i.source.height:s,'" fill="url(#SVGID_'+i.id+')"',"></rect>\n")}else t.push('<rect x="0" y="0" width="100%" height="100%" ','fill="',i,'"',"></rect>\n")},sendToBack:function(t){if(!t)return this;var e,i,r,n=this._activeObject;if(t===n&&"activeSelection"===t.type)for(e=(r=n._objects).length;e--;)i=r[e],c(this._objects,i),this._objects.unshift(i);else c(this._objects,t),this._objects.unshift(t);return this.renderOnAddRemove&&this.requestRenderAll(),this},bringToFront:function(t){if(!t)return this;var e,i,r,n=this._activeObject;if(t===n&&"activeSelection"===t.type)for(r=n._objects,e=0;e<r.length;e++)i=r[e],c(this._objects,i),this._objects.push(i);else c(this._objects,t),this._objects.push(t);return this.renderOnAddRemove&&this.requestRenderAll(),this},sendBackwards:function(t,e){if(!t)return this;var i,r,n,s,o,a=this._activeObject,h=0;if(t===a&&"activeSelection"===t.type)for(o=a._objects,i=0;i<o.length;i++)r=o[i],0+h<(n=this._objects.indexOf(r))&&(s=n-1,c(this._objects,r),this._objects.splice(s,0,r)),h++;else 0!==(n=this._objects.indexOf(t))&&(s=this._findNewLowerIndex(t,n,e),c(this._objects,t),this._objects.splice(s,0,t));return this.renderOnAddRemove&&this.requestRenderAll(),this},_findNewLowerIndex:function(t,e,i){var r,n;if(i)for(n=(r=e)-1;0<=n;--n){if(t.intersectsWithObject(this._objects[n])||t.isContainedWithinObject(this._objects[n])||this._objects[n].isContainedWithinObject(t)){r=n;break}}else r=e-1;return r},bringForward:function(t,e){if(!t)return this;var i,r,n,s,o,a=this._activeObject,h=0;if(t===a&&"activeSelection"===t.type)for(i=(o=a._objects).length;i--;)r=o[i],(n=this._objects.indexOf(r))<this._objects.length-1-h&&(s=n+1,c(this._objects,r),this._objects.splice(s,0,r)),h++;else(n=this._objects.indexOf(t))!==this._objects.length-1&&(s=this._findNewUpperIndex(t,n,e),c(this._objects,t),this._objects.splice(s,0,t));return this.renderOnAddRemove&&this.requestRenderAll(),this},_findNewUpperIndex:function(t,e,i){var r,n,s;if(i)for(n=(r=e)+1,s=this._objects.length;n<s;++n){if(t.intersectsWithObject(this._objects[n])||t.isContainedWithinObject(this._objects[n])||this._objects[n].isContainedWithinObject(t)){r=n;break}}else r=e+1;return r},moveTo:function(t,e){return c(this._objects,t),this._objects.splice(e,0,t),this.renderOnAddRemove&&this.requestRenderAll()},dispose:function(){return this.isRendering&&(fabric.util.cancelAnimFrame(this.isRendering),this.isRendering=0),this.forEachObject(function(t){t.dispose&&t.dispose()}),this._objects=[],this.backgroundImage&&this.backgroundImage.dispose&&this.backgroundImage.dispose(),this.backgroundImage=null,this.overlayImage&&this.overlayImage.dispose&&this.overlayImage.dispose(),this.overlayImage=null,this._iTextInstances=null,this.contextContainer=null,fabric.util.cleanUpJsdomNode(this.lowerCanvasEl),this.lowerCanvasEl=void 0,this},toString:function(){return"#<fabric.Canvas ("+this.complexity()+"): { objects: "+this._objects.length+" }>"}}),n(fabric.StaticCanvas.prototype,fabric.Observable),n(fabric.StaticCanvas.prototype,fabric.Collection),n(fabric.StaticCanvas.prototype,fabric.DataURLExporter),n(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(t){var e=r();if(!e||!e.getContext)return null;var i=e.getContext("2d");if(!i)return null;switch(t){case"setLineDash":return void 0!==i.setLineDash;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject,fabric.isLikelyNode&&(fabric.StaticCanvas.prototype.createPNGStream=function(){var t=i(this.lowerCanvasEl);return t&&t.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(t){var e=i(this.lowerCanvasEl);return e&&e.createJPEGStream(t)})}}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",strokeMiterLimit:10,strokeDashArray:null,setShadow:function(t){return this.shadow=new fabric.Shadow(t),this},_setBrushStyles:function(){var t=this.canvas.contextTop;t.strokeStyle=this.color,t.lineWidth=this.width,t.lineCap=this.strokeLineCap,t.miterLimit=this.strokeMiterLimit,t.lineJoin=this.strokeLineJoin,fabric.StaticCanvas.supports("setLineDash")&&t.setLineDash(this.strokeDashArray||[])},_saveAndTransform:function(t){var e=this.canvas.viewportTransform;t.save(),t.transform(e[0],e[1],e[2],e[3],e[4],e[5])},_setShadow:function(){if(this.shadow){var t=this.canvas.contextTop,e=this.canvas.getZoom();t.shadowColor=this.shadow.color,t.shadowBlur=this.shadow.blur*e,t.shadowOffsetX=this.shadow.offsetX*e,t.shadowOffsetY=this.shadow.offsetY*e}},needsFullRender:function(){return new fabric.Color(this.color).getAlpha()<1||!!this.shadow},_resetShadow:function(){var t=this.canvas.contextTop;t.shadowColor="",t.shadowBlur=t.shadowOffsetX=t.shadowOffsetY=0}}),fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{decimate:.4,initialize:function(t){this.canvas=t,this._points=[]},_drawSegment:function(t,e,i){var r=e.midPointFrom(i);return t.quadraticCurveTo(e.x,e.y,r.x,r.y),r},onMouseDown:function(t){this._prepareForDrawing(t),this._captureDrawingPath(t),this._render()},onMouseMove:function(t){if(this._captureDrawingPath(t)&&1<this._points.length)if(this.needsFullRender())this.canvas.clearContext(this.canvas.contextTop),this._render();else{var e=this._points,i=e.length,r=this.canvas.contextTop;this._saveAndTransform(r),this.oldEnd&&(r.beginPath(),r.moveTo(this.oldEnd.x,this.oldEnd.y)),this.oldEnd=this._drawSegment(r,e[i-2],e[i-1],!0),r.stroke(),r.restore()}},onMouseUp:function(){this.oldEnd=void 0,this._finalizeAndAddPath()},_prepareForDrawing:function(t){var e=new fabric.Point(t.x,t.y);this._reset(),this._addPoint(e),this.canvas.contextTop.moveTo(e.x,e.y)},_addPoint:function(t){return!(1<this._points.length&&t.eq(this._points[this._points.length-1])||(this._points.push(t),0))},_reset:function(){this._points=[],this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(t){var e=new fabric.Point(t.x,t.y);return this._addPoint(e)},_render:function(){var t,e,i=this.canvas.contextTop,r=this._points[0],n=this._points[1];if(this._saveAndTransform(i),i.beginPath(),2===this._points.length&&r.x===n.x&&r.y===n.y){var s=this.width/1e3;r=new fabric.Point(r.x,r.y),n=new fabric.Point(n.x,n.y),r.x-=s,n.x+=s}for(i.moveTo(r.x,r.y),t=1,e=this._points.length;t<e;t++)this._drawSegment(i,r,n),r=this._points[t],n=this._points[t+1];i.lineTo(r.x,r.y),i.stroke(),i.restore()},convertPointsToSVGPath:function(t){var e,i=[],r=this.width/1e3,n=new fabric.Point(t[0].x,t[0].y),s=new fabric.Point(t[1].x,t[1].y),o=t.length,a=1,h=0,c=2<o;for(c&&(a=t[2].x<s.x?-1:t[2].x===s.x?0:1,h=t[2].y<s.y?-1:t[2].y===s.y?0:1),i.push("M ",n.x-a*r," ",n.y-h*r," "),e=1;e<o;e++){if(!n.eq(s)){var l=n.midPointFrom(s);i.push("Q ",n.x," ",n.y," ",l.x," ",l.y," ")}n=t[e],e+1<t.length&&(s=t[e+1])}return c&&(a=n.x>t[e-2].x?1:n.x===t[e-2].x?0:-1,h=n.y>t[e-2].y?1:n.y===t[e-2].y?0:-1),i.push("L ",n.x+a*r," ",n.y+h*r),i},createPath:function(t){var e=new fabric.Path(t,{fill:null,stroke:this.color,strokeWidth:this.width,strokeLineCap:this.strokeLineCap,strokeMiterLimit:this.strokeMiterLimit,strokeLineJoin:this.strokeLineJoin,strokeDashArray:this.strokeDashArray});return this.shadow&&(this.shadow.affectStroke=!0,e.setShadow(this.shadow)),e},decimatePoints:function(t,e){if(t.length<=2)return t;var i,r=this.canvas.getZoom(),n=Math.pow(e/r,2),s=t.length-1,o=t[0],a=[o];for(i=1;i<s;i++)n<=Math.pow(o.x-t[i].x,2)+Math.pow(o.y-t[i].y,2)&&(o=t[i],a.push(o));return 1===a.length&&a.push(new fabric.Point(a[0].x,a[0].y)),a},_finalizeAndAddPath:function(){this.canvas.contextTop.closePath(),this.decimate&&(this._points=this.decimatePoints(this._points,this.decimate));var t=this.convertPointsToSVGPath(this._points).join("");if("M 0 0 Q 0 0 0 0 L 0 0"!==t){var e=this.createPath(t);this.canvas.clearContext(this.canvas.contextTop),this.canvas.add(e),this.canvas.renderAll(),e.setCoords(),this._resetShadow(),this.canvas.fire("path:created",{path:e})}else this.canvas.requestRenderAll()}}),fabric.CircleBrush=fabric.util.createClass(fabric.BaseBrush,{width:10,initialize:function(t){this.canvas=t,this.points=[]},drawDot:function(t){var e=this.addPoint(t),i=this.canvas.contextTop;this._saveAndTransform(i),this.dot(i,e),i.restore()},dot:function(t,e){t.fillStyle=e.fill,t.beginPath(),t.arc(e.x,e.y,e.radius,0,2*Math.PI,!1),t.closePath(),t.fill()},onMouseDown:function(t){this.points.length=0,this.canvas.clearContext(this.canvas.contextTop),this._setShadow(),this.drawDot(t)},_render:function(){var t,e,i=this.canvas.contextTop,r=this.points;for(this._saveAndTransform(i),t=0,e=r.length;t<e;t++)this.dot(i,r[t]);i.restore()},onMouseMove:function(t){this.needsFullRender()?(this.canvas.clearContext(this.canvas.contextTop),this.addPoint(t),this._render()):this.drawDot(t)},onMouseUp:function(){var t,e,i=this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove=!1;var r=[];for(t=0,e=this.points.length;t<e;t++){var n=this.points[t],s=new fabric.Circle({radius:n.radius,left:n.x,top:n.y,originX:"center",originY:"center",fill:n.fill});this.shadow&&s.setShadow(this.shadow),r.push(s)}var o=new fabric.Group(r);o.canvas=this.canvas,this.canvas.add(o),this.canvas.fire("path:created",{path:o}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=i,this.canvas.requestRenderAll()},addPoint:function(t){var e=new fabric.Point(t.x,t.y),i=fabric.util.getRandomInt(Math.max(0,this.width-20),this.width+20)/2,r=new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0,100)/100).toRgba();return e.radius=i,e.fill=r,this.points.push(e),e}}),fabric.SprayBrush=fabric.util.createClass(fabric.BaseBrush,{width:10,density:20,dotWidth:1,dotWidthVariance:1,randomOpacity:!1,optimizeOverlapping:!0,initialize:function(t){this.canvas=t,this.sprayChunks=[]},onMouseDown:function(t){this.sprayChunks.length=0,this.canvas.clearContext(this.canvas.contextTop),this._setShadow(),this.addSprayChunk(t),this.render(this.sprayChunkPoints)},onMouseMove:function(t){this.addSprayChunk(t),this.render(this.sprayChunkPoints)},onMouseUp:function(){var t=this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove=!1;for(var e=[],i=0,r=this.sprayChunks.length;i<r;i++)for(var n=this.sprayChunks[i],s=0,o=n.length;s<o;s++){var a=new fabric.Rect({width:n[s].width,height:n[s].width,left:n[s].x+1,top:n[s].y+1,originX:"center",originY:"center",fill:this.color});e.push(a)}this.optimizeOverlapping&&(e=this._getOptimizedRects(e));var h=new fabric.Group(e);this.shadow&&h.setShadow(this.shadow),this.canvas.add(h),this.canvas.fire("path:created",{path:h}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=t,this.canvas.requestRenderAll()},_getOptimizedRects:function(t){var e,i,r,n={};for(i=0,r=t.length;i<r;i++)n[e=t[i].left+""+t[i].top]||(n[e]=t[i]);var s=[];for(e in n)s.push(n[e]);return s},render:function(t){var e,i,r=this.canvas.contextTop;for(r.fillStyle=this.color,this._saveAndTransform(r),e=0,i=t.length;e<i;e++){var n=t[e];void 0!==n.opacity&&(r.globalAlpha=n.opacity),r.fillRect(n.x,n.y,n.width,n.width)}r.restore()},_render:function(){var t,e,i=this.canvas.contextTop;for(i.fillStyle=this.color,this._saveAndTransform(i),t=0,e=this.sprayChunks.length;t<e;t++)this.render(this.sprayChunks[t]);i.restore()},addSprayChunk:function(t){this.sprayChunkPoints=[];var e,i,r,n,s=this.width/2;for(n=0;n<this.density;n++){e=fabric.util.getRandomInt(t.x-s,t.x+s),i=fabric.util.getRandomInt(t.y-s,t.y+s),r=this.dotWidthVariance?fabric.util.getRandomInt(Math.max(1,this.dotWidth-this.dotWidthVariance),this.dotWidth+this.dotWidthVariance):this.dotWidth;var o=new fabric.Point(e,i);o.width=r,this.randomOpacity&&(o.opacity=fabric.util.getRandomInt(0,100)/100),this.sprayChunkPoints.push(o)}this.sprayChunks.push(this.sprayChunkPoints)}}),fabric.PatternBrush=fabric.util.createClass(fabric.PencilBrush,{getPatternSrc:function(){var t=fabric.util.createCanvasElement(),e=t.getContext("2d");return t.width=t.height=25,e.fillStyle=this.color,e.beginPath(),e.arc(10,10,10,0,2*Math.PI,!1),e.closePath(),e.fill(),t},getPatternSrcFunction:function(){return String(this.getPatternSrc).replace("this.color",'"'+this.color+'"')},getPattern:function(){return this.canvas.contextTop.createPattern(this.source||this.getPatternSrc(),"repeat")},_setBrushStyles:function(){this.callSuper("_setBrushStyles"),this.canvas.contextTop.strokeStyle=this.getPattern()},createPath:function(t){var e=this.callSuper("createPath",t),i=e._getLeftTopCoords().scalarAdd(e.strokeWidth/2);return e.stroke=new fabric.Pattern({source:this.source||this.getPatternSrcFunction(),offsetX:-i.x,offsetY:-i.y}),e}}),function(){var h=fabric.util.getPointer,a=fabric.util.degreesToRadians,d=fabric.util.radiansToDegrees,g=Math.atan2,c=Math.abs,l=fabric.StaticCanvas.supports("setLineDash");for(var t in fabric.Canvas=fabric.util.createClass(fabric.StaticCanvas,{initialize:function(t,e){e||(e={}),this.renderAndResetBound=this.renderAndReset.bind(this),this.requestRenderAllBound=this.requestRenderAll.bind(this),this._initStatic(t,e),this._initInteractive(),this._createCacheCanvas()},uniScaleTransform:!1,uniScaleKey:"shiftKey",centeredScaling:!1,centeredRotation:!1,centeredKey:"altKey",altActionKey:"shiftKey",interactive:!0,selection:!0,selectionKey:"shiftKey",altSelectionKey:null,selectionColor:"rgba(100, 100, 255, 0.3)",selectionDashArray:[],selectionBorderColor:"rgba(255, 255, 255, 0.3)",selectionLineWidth:1,selectionFullyContained:!1,hoverCursor:"move",moveCursor:"move",defaultCursor:"default",freeDrawingCursor:"crosshair",rotationCursor:"crosshair",notAllowedCursor:"not-allowed",containerClass:"canvas-container",perPixelTargetFind:!1,targetFindTolerance:0,skipTargetFind:!1,isDrawingMode:!1,preserveObjectStacking:!1,snapAngle:0,snapThreshold:null,stopContextMenu:!1,fireRightClick:!1,fireMiddleClick:!1,_initInteractive:function(){this._currentTransform=null,this._groupSelector=null,this._initWrapperElement(),this._createUpperCanvas(),this._initEventListeners(),this._initRetinaScaling(),this.freeDrawingBrush=fabric.PencilBrush&&new fabric.PencilBrush(this),this.calcOffset()},_chooseObjectsToRender:function(){var t,e,i,r=this.getActiveObjects();if(0<r.length&&!this.preserveObjectStacking){e=[],i=[];for(var n=0,s=this._objects.length;n<s;n++)t=this._objects[n],-1===r.indexOf(t)?e.push(t):i.push(t);1<r.length&&(this._activeObject._objects=i),e.push.apply(e,i)}else e=this._objects;return e},renderAll:function(){!this.contextTopDirty||this._groupSelector||this.isDrawingMode||(this.clearContext(this.contextTop),this.contextTopDirty=!1),this.hasLostContext&&this.renderTopLayer(this.contextTop);var t=this.contextContainer;return this.renderCanvas(t,this._chooseObjectsToRender()),this},renderTopLayer:function(t){t.save(),this.isDrawingMode&&this._isCurrentlyDrawing&&(this.freeDrawingBrush&&this.freeDrawingBrush._render(),this.contextTopDirty=!0),this.selection&&this._groupSelector&&(this._drawSelection(t),this.contextTopDirty=!0),t.restore()},renderTop:function(){var t=this.contextTop;return this.clearContext(t),this.renderTopLayer(t),this.fire("after:render"),this},_resetCurrentTransform:function(){var t=this._currentTransform;t.target.set({scaleX:t.original.scaleX,scaleY:t.original.scaleY,skewX:t.original.skewX,skewY:t.original.skewY,left:t.original.left,top:t.original.top}),this._shouldCenterTransform(t.target)?("center"!==t.originX&&("right"===t.originX?t.mouseXSign=-1:t.mouseXSign=1),"center"!==t.originY&&("bottom"===t.originY?t.mouseYSign=-1:t.mouseYSign=1),t.originX="center",t.originY="center"):(t.originX=t.original.originX,t.originY=t.original.originY)},containsPoint:function(t,e,i){var r,n=i||this.getPointer(t,!0);return r=e.group&&e.group===this._activeObject&&"activeSelection"===e.group.type?this._normalizePointer(e.group,n):{x:n.x,y:n.y},e.containsPoint(r)||e._findTargetCorner(n)},_normalizePointer:function(t,e){var i=t.calcTransformMatrix(),r=fabric.util.invertTransform(i),n=this.restorePointerVpt(e);return fabric.util.transformPoint(n,r)},isTargetTransparent:function(t,e,i){if(t.shouldCache()&&t._cacheCanvas&&t!==this._activeObject){var r=this._normalizePointer(t,{x:e,y:i}),n=Math.max(t.cacheTranslationX+r.x*t.zoomX,0),s=Math.max(t.cacheTranslationY+r.y*t.zoomY,0);return fabric.util.isTransparent(t._cacheContext,Math.round(n),Math.round(s),this.targetFindTolerance)}var o=this.contextCache,a=t.selectionBackgroundColor,h=this.viewportTransform;return t.selectionBackgroundColor="",this.clearContext(o),o.save(),o.transform(h[0],h[1],h[2],h[3],h[4],h[5]),t.render(o),o.restore(),t===this._activeObject&&t._renderControls(o,{hasBorders:!1,transparentCorners:!1},{hasBorders:!1}),t.selectionBackgroundColor=a,fabric.util.isTransparent(o,e,i,this.targetFindTolerance)},_isSelectionKeyPressed:function(e){return"[object Array]"===Object.prototype.toString.call(this.selectionKey)?!!this.selectionKey.find(function(t){return!0===e[t]}):e[this.selectionKey]},_shouldClearSelection:function(t,e){var i=this.getActiveObjects(),r=this._activeObject;return!e||e&&r&&1<i.length&&-1===i.indexOf(e)&&r!==e&&!this._isSelectionKeyPressed(t)||e&&!e.evented||e&&!e.selectable&&r&&r!==e},_shouldCenterTransform:function(t){if(t){var e,i=this._currentTransform;return"scale"===i.action||"scaleX"===i.action||"scaleY"===i.action?e=this.centeredScaling||t.centeredScaling:"rotate"===i.action&&(e=this.centeredRotation||t.centeredRotation),e?!i.altKey:i.altKey}},_getOriginFromCorner:function(t,e){var i={x:t.originX,y:t.originY};return"ml"===e||"tl"===e||"bl"===e?i.x="right":"mr"!==e&&"tr"!==e&&"br"!==e||(i.x="left"),"tl"===e||"mt"===e||"tr"===e?i.y="bottom":"bl"!==e&&"mb"!==e&&"br"!==e||(i.y="top"),i},_getActionFromCorner:function(t,e,i){if(!e||!t)return"drag";switch(e){case"mtr":return"rotate";case"ml":case"mr":return i[this.altActionKey]?"skewY":"scaleX";case"mt":case"mb":return i[this.altActionKey]?"skewX":"scaleY";default:return"scale"}},_setupCurrentTransform:function(t,e,i){if(e){var r=this.getPointer(t),n=e._findTargetCorner(this.getPointer(t,!0)),s=this._getActionFromCorner(i,n,t,e),o=this._getOriginFromCorner(e,n);this._currentTransform={target:e,action:s,corner:n,scaleX:e.scaleX,scaleY:e.scaleY,skewX:e.skewX,skewY:e.skewY,offsetX:r.x-e.left,offsetY:r.y-e.top,originX:o.x,originY:o.y,ex:r.x,ey:r.y,lastX:r.x,lastY:r.y,theta:a(e.angle),width:e.width*e.scaleX,mouseXSign:1,mouseYSign:1,shiftKey:t.shiftKey,altKey:t[this.centeredKey],original:fabric.util.saveObjectTransform(e)},this._currentTransform.original.originX=o.x,this._currentTransform.original.originY=o.y,this._resetCurrentTransform(),this._beforeTransform(t)}},_translateObject:function(t,e){var i=this._currentTransform,r=i.target,n=t-i.offsetX,s=e-i.offsetY,o=!r.get("lockMovementX")&&r.left!==n,a=!r.get("lockMovementY")&&r.top!==s;return o&&r.set("left",n),a&&r.set("top",s),o||a},_changeSkewTransformOrigin:function(t,e,i){var r="originX",n={0:"center"},s=e.target.skewX,o="left",a="right",h="mt"===e.corner||"ml"===e.corner?1:-1,c=1;t=0<t?1:-1,"y"===i&&(s=e.target.skewY,o="top",a="bottom",r="originY"),n[-1]=o,n[1]=a,e.target.flipX&&(c*=-1),e.target.flipY&&(c*=-1),0===s?(e.skewSign=-h*t*c,e[r]=n[-t]):(s=0<s?1:-1,e.skewSign=s,e[r]=n[s*h*c])},_skewObject:function(t,e,i){var r,n=this._currentTransform,s=n.target,o=s.get("lockSkewingX"),a=s.get("lockSkewingY");if(o&&"x"===i||a&&"y"===i)return!1;var h,c,l=s.getCenterPoint(),u=s.toLocalPoint(new fabric.Point(t,e),"center","center")[i],f=s.toLocalPoint(new fabric.Point(n.lastX,n.lastY),"center","center")[i],d=s._getTransformedDimensions();return this._changeSkewTransformOrigin(u-f,n,i),h=s.toLocalPoint(new fabric.Point(t,e),n.originX,n.originY)[i],c=s.translateToOriginPoint(l,n.originX,n.originY),r=this._setObjectSkew(h,n,i,d),n.lastX=t,n.lastY=e,s.setPositionByOrigin(c,n.originX,n.originY),r},_setObjectSkew:function(t,e,i,r){var n,s,o,a,h,c,l,u,f,d,g=e.target,p=e.skewSign;return"x"===i?(h="y",c="Y",l="X",f=0,d=g.skewY):(h="x",c="X",l="Y",f=g.skewX,d=0),a=g._getTransformedDimensions(f,d),(u=2*Math.abs(t)-a[i])<=2?n=0:(n=p*Math.atan(u/g["scale"+l]/(a[h]/g["scale"+c])),n=fabric.util.radiansToDegrees(n)),s=g["skew"+l]!==n,g.set("skew"+l,n),0!==g["skew"+c]&&(o=g._getTransformedDimensions(),n=r[h]/o[h]*g["scale"+c],g.set("scale"+c,n)),s},_scaleObject:function(t,e,i){var r=this._currentTransform,n=r.target,s=n.lockScalingX,o=n.lockScalingY,a=n.lockScalingFlip;if(s&&o)return!1;var h,c=n.translateToOriginPoint(n.getCenterPoint(),r.originX,r.originY),l=n.toLocalPoint(new fabric.Point(t,e),r.originX,r.originY),u=n._getTransformedDimensions();return this._setLocalMouse(l,r),h=this._setObjectScale(l,r,s,o,i,a,u),n.setPositionByOrigin(c,r.originX,r.originY),h},_setObjectScale:function(t,e,i,r,n,s,o){var a=e.target,h=!1,c=!1,l=!1,u=t.x*a.scaleX/o.x,f=t.y*a.scaleY/o.y,d=a.scaleX!==u,g=a.scaleY!==f;if(e.newScaleX=u,e.newScaleY=f,"x"===n&&a instanceof fabric.Textbox){var p=a.width*(t.x/o.x);return p>=a.getMinWidth()&&(l=p!==a.width,a.set("width",p),l)}return s&&u<=0&&u<a.scaleX&&(h=!0,t.x=0),s&&f<=0&&f<a.scaleY&&(c=!0,t.y=0),"equally"!==n||i||r?n?"x"!==n||a.get("lockUniScaling")?"y"!==n||a.get("lockUniScaling")||c||r||a.set("scaleY",f)&&(l=g):h||i||a.set("scaleX",u)&&(l=d):(h||i||a.set("scaleX",u)&&(l=l||d),c||r||a.set("scaleY",f)&&(l=l||g)):l=this._scaleObjectEqually(t,a,e,o),h||c||this._flipObject(e,n),l},_scaleObjectEqually:function(t,e,i,r){var n,s,o,a=t.y+t.x,h=r.y*i.original.scaleY/e.scaleY+r.x*i.original.scaleX/e.scaleX,c=t.x<0?-1:1,l=t.y<0?-1:1;return s=c*Math.abs(i.original.scaleX*a/h),o=l*Math.abs(i.original.scaleY*a/h),n=s!==e.scaleX||o!==e.scaleY,e.set("scaleX",s),e.set("scaleY",o),n},_flipObject:function(t,e){t.newScaleX<0&&"y"!==e&&("left"===t.originX?t.originX="right":"right"===t.originX&&(t.originX="left")),t.newScaleY<0&&"x"!==e&&("top"===t.originY?t.originY="bottom":"bottom"===t.originY&&(t.originY="top"))},_setLocalMouse:function(t,e){var i=e.target,r=this.getZoom(),n=i.padding/r;"right"===e.originX?t.x*=-1:"center"===e.originX&&(t.x*=2*e.mouseXSign,t.x<0&&(e.mouseXSign=-e.mouseXSign)),"bottom"===e.originY?t.y*=-1:"center"===e.originY&&(t.y*=2*e.mouseYSign,t.y<0&&(e.mouseYSign=-e.mouseYSign)),c(t.x)>n?t.x<0?t.x+=n:t.x-=n:t.x=0,c(t.y)>n?t.y<0?t.y+=n:t.y-=n:t.y=0},_rotateObject:function(t,e){var i=this._currentTransform,r=i.target,n=r.translateToOriginPoint(r.getCenterPoint(),i.originX,i.originY);if(r.lockRotation)return!1;var s=g(i.ey-n.y,i.ex-n.x),o=g(e-n.y,t-n.x),a=d(o-s+i.theta),h=!0;if(0<r.snapAngle){var c=r.snapAngle,l=r.snapThreshold||c,u=Math.ceil(a/c)*c,f=Math.floor(a/c)*c;Math.abs(a-f)<l?a=f:Math.abs(a-u)<l&&(a=u)}return a<0&&(a=360+a),a%=360,r.angle===a?h=!1:(r.angle=a,r.setPositionByOrigin(n,i.originX,i.originY)),h},setCursor:function(t){this.upperCanvasEl.style.cursor=t},_drawSelection:function(t){var e=this._groupSelector,i=e.left,r=e.top,n=c(i),s=c(r);if(this.selectionColor&&(t.fillStyle=this.selectionColor,t.fillRect(e.ex-(0<i?0:-i),e.ey-(0<r?0:-r),n,s)),this.selectionLineWidth&&this.selectionBorderColor)if(t.lineWidth=this.selectionLineWidth,t.strokeStyle=this.selectionBorderColor,1<this.selectionDashArray.length&&!l){var o=e.ex+.5-(0<i?0:n),a=e.ey+.5-(0<r?0:s);t.beginPath(),fabric.util.drawDashedLine(t,o,a,o+n,a,this.selectionDashArray),fabric.util.drawDashedLine(t,o,a+s-1,o+n,a+s-1,this.selectionDashArray),fabric.util.drawDashedLine(t,o,a,o,a+s,this.selectionDashArray),fabric.util.drawDashedLine(t,o+n-1,a,o+n-1,a+s,this.selectionDashArray),t.closePath(),t.stroke()}else fabric.Object.prototype._setLineDash.call(this,t,this.selectionDashArray),t.strokeRect(e.ex+.5-(0<i?0:n),e.ey+.5-(0<r?0:s),n,s)},findTarget:function(t,e){if(!this.skipTargetFind){var i,r,n=this.getPointer(t,!0),s=this._activeObject,o=this.getActiveObjects();if(this.targets=[],1<o.length&&!e&&s===this._searchPossibleTargets([s],n))return s;if(1===o.length&&s._findTargetCorner(n))return s;if(1===o.length&&s===this._searchPossibleTargets([s],n)){if(!this.preserveObjectStacking)return s;i=s,r=this.targets,this.targets=[]}var a=this._searchPossibleTargets(this._objects,n);return t[this.altSelectionKey]&&a&&i&&a!==i&&(a=i,this.targets=r),a}},_checkTarget:function(t,e,i){if(e&&e.visible&&e.evented&&this.containsPoint(null,e,t)){if(!this.perPixelTargetFind&&!e.perPixelTargetFind||e.isEditing)return!0;if(!this.isTargetTransparent(e,i.x,i.y))return!0}},_searchPossibleTargets:function(t,e){for(var i,r,n=t.length;n--;){var s=t[n],o=s.group&&"activeSelection"!==s.group.type?this._normalizePointer(s.group,e):e;if(this._checkTarget(o,s,e)){(i=t[n]).subTargetCheck&&i instanceof fabric.Group&&(r=this._searchPossibleTargets(i._objects,e))&&this.targets.push(r);break}}return i},restorePointerVpt:function(t){return fabric.util.transformPoint(t,fabric.util.invertTransform(this.viewportTransform))},getPointer:function(t,e){if(this._absolutePointer&&!e)return this._absolutePointer;if(this._pointer&&e)return this._pointer;var i,r=h(t),n=this.upperCanvasEl,s=n.getBoundingClientRect(),o=s.width||0,a=s.height||0;return o&&a||("top"in s&&"bottom"in s&&(a=Math.abs(s.top-s.bottom)),"right"in s&&"left"in s&&(o=Math.abs(s.right-s.left))),this.calcOffset(),r.x=r.x-this._offset.left,r.y=r.y-this._offset.top,e||(r=this.restorePointerVpt(r)),i=0===o||0===a?{width:1,height:1}:{width:n.width/o,height:n.height/a},{x:r.x*i.width,y:r.y*i.height}},_createUpperCanvas:function(){var t=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl?this.upperCanvasEl.className="":this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+t),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{class:this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.width+"px",height:this.height+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(t){var e=this.width||t.width,i=this.height||t.height;fabric.util.setStyle(t,{position:"absolute",width:e+"px",height:i+"px",left:0,top:0,"touch-action":this.allowTouchScrolling?"manipulation":"none"}),t.width=e,t.height=i,fabric.util.makeElementUnselectable(t)},_copyCanvasStyle:function(t,e){e.style.cssText=t.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},getActiveObject:function(){return this._activeObject},getActiveObjects:function(){var t=this._activeObject;return t?"activeSelection"===t.type&&t._objects?t._objects.slice(0):[t]:[]},_onObjectRemoved:function(t){t===this._activeObject&&(this.fire("before:selection:cleared",{target:t}),this._discardActiveObject(),this.fire("selection:cleared",{target:t}),t.fire("deselected")),this._hoveredTarget===t&&(this._hoveredTarget=null),this.callSuper("_onObjectRemoved",t)},_fireSelectionEvents:function(e,t){var i=!1,r=this.getActiveObjects(),n=[],s=[],o={e:t};e.forEach(function(t){-1===r.indexOf(t)&&(i=!0,t.fire("deselected",o),s.push(t))}),r.forEach(function(t){-1===e.indexOf(t)&&(i=!0,t.fire("selected",o),n.push(t))}),0<e.length&&0<r.length?(o.selected=n,o.deselected=s,o.updated=n[0]||s[0],o.target=this._activeObject,i&&this.fire("selection:updated",o)):0<r.length?(1===r.length&&(o.target=n[0],this.fire("object:selected",o)),o.selected=n,o.target=this._activeObject,this.fire("selection:created",o)):0<e.length&&(o.deselected=s,this.fire("selection:cleared",o))},setActiveObject:function(t,e){var i=this.getActiveObjects();return this._setActiveObject(t,e),this._fireSelectionEvents(i,e),this},_setActiveObject:function(t,e){return this._activeObject!==t&&(!!this._discardActiveObject(e,t)&&(!t.onSelect({e:e})&&(this._activeObject=t,!0)))},_discardActiveObject:function(t,e){var i=this._activeObject;if(i){if(i.onDeselect({e:t,object:e}))return!1;this._activeObject=null}return!0},discardActiveObject:function(t){var e=this.getActiveObjects(),i=this.getActiveObject();return e.length&&this.fire("before:selection:cleared",{target:i,e:t}),this._discardActiveObject(t),this._fireSelectionEvents(e,t),this},dispose:function(){var t=this.wrapperEl;return this.removeListeners(),t.removeChild(this.upperCanvasEl),t.removeChild(this.lowerCanvasEl),this.contextCache=null,this.contextTop=null,["upperCanvasEl","cacheCanvasEl"].forEach(function(t){fabric.util.cleanUpJsdomNode(this[t]),this[t]=void 0}.bind(this)),t.parentNode&&t.parentNode.replaceChild(this.lowerCanvasEl,this.wrapperEl),delete this.wrapperEl,fabric.StaticCanvas.prototype.dispose.call(this),this},clear:function(){return this.discardActiveObject(),this.clearContext(this.contextTop),this.callSuper("clear")},drawControls:function(t){var e=this._activeObject;e&&e._renderControls(t)},_toObject:function(t,e,i){var r=this._realizeGroupTransformOnObject(t),n=this.callSuper("_toObject",t,e,i);return this._unwindGroupTransformOnObject(t,r),n},_realizeGroupTransformOnObject:function(e){if(e.group&&"activeSelection"===e.group.type&&this._activeObject===e.group){var i={};return["angle","flipX","flipY","left","scaleX","scaleY","skewX","skewY","top"].forEach(function(t){i[t]=e[t]}),this._activeObject.realizeTransform(e),i}return null},_unwindGroupTransformOnObject:function(t,e){e&&t.set(e)},_setSVGObject:function(t,e,i){var r=this._realizeGroupTransformOnObject(e);this.callSuper("_setSVGObject",t,e,i),this._unwindGroupTransformOnObject(e,r)},setViewportTransform:function(t){this.renderOnAddRemove&&this._activeObject&&this._activeObject.isEditing&&this._activeObject.clearContextTop(),fabric.StaticCanvas.prototype.setViewportTransform.call(this,t)}}),fabric.StaticCanvas)"prototype"!==t&&(fabric.Canvas[t]=fabric.StaticCanvas[t]);fabric.isTouchSupported&&(fabric.Canvas.prototype._setCursorFromEvent=function(){})}(),function(){var n={mt:0,tr:1,mr:2,br:3,mb:4,bl:5,ml:6,tl:7},s=fabric.util.addListener,o=fabric.util.removeListener,a={passive:!1};function h(t,e){return"which"in t?t.which===e:t.button===e-1}fabric.util.object.extend(fabric.Canvas.prototype,{cursorMap:["n-resize","ne-resize","e-resize","se-resize","s-resize","sw-resize","w-resize","nw-resize"],_initEventListeners:function(){this.removeListeners(),this._bindEvents(),this.addOrRemove(s,"add")},addOrRemove:function(t,e){var i=this.upperCanvasEl,r=this.enablePointerEvents?"pointer":"mouse";t(fabric.window,"resize",this._onResize),t(i,r+"down",this._onMouseDown),t(i,r+"move",this._onMouseMove,a),t(i,r+"out",this._onMouseOut),t(i,r+"enter",this._onMouseEnter),t(i,"wheel",this._onMouseWheel),t(i,"contextmenu",this._onContextMenu),t(i,"dblclick",this._onDoubleClick),t(i,"touchstart",this._onMouseDown,a),t(i,"touchmove",this._onMouseMove,a),t(i,"dragover",this._onDragOver),t(i,"dragenter",this._onDragEnter),t(i,"dragleave",this._onDragLeave),t(i,"drop",this._onDrop),"undefined"!=typeof eventjs&&e in eventjs&&(eventjs[e](i,"gesture",this._onGesture),eventjs[e](i,"drag",this._onDrag),eventjs[e](i,"orientation",this._onOrientationChange),eventjs[e](i,"shake",this._onShake),eventjs[e](i,"longpress",this._onLongPress))},removeListeners:function(){this.addOrRemove(o,"remove");var t=this.enablePointerEvents?"pointer":"mouse";o(fabric.document,t+"up",this._onMouseUp),o(fabric.document,"touchend",this._onMouseUp,a),o(fabric.document,t+"move",this._onMouseMove,a),o(fabric.document,"touchmove",this._onMouseMove,a)},_bindEvents:function(){this.eventsBound||(this._onMouseDown=this._onMouseDown.bind(this),this._onMouseMove=this._onMouseMove.bind(this),this._onMouseUp=this._onMouseUp.bind(this),this._onResize=this._onResize.bind(this),this._onGesture=this._onGesture.bind(this),this._onDrag=this._onDrag.bind(this),this._onShake=this._onShake.bind(this),this._onLongPress=this._onLongPress.bind(this),this._onOrientationChange=this._onOrientationChange.bind(this),this._onMouseWheel=this._onMouseWheel.bind(this),this._onMouseOut=this._onMouseOut.bind(this),this._onMouseEnter=this._onMouseEnter.bind(this),this._onContextMenu=this._onContextMenu.bind(this),this._onDoubleClick=this._onDoubleClick.bind(this),this._onDragOver=this._onDragOver.bind(this),this._onDragEnter=this._simpleEventHandler.bind(this,"dragenter"),this._onDragLeave=this._simpleEventHandler.bind(this,"dragleave"),this._onDrop=this._simpleEventHandler.bind(this,"drop"),this.eventsBound=!0)},_onGesture:function(t,e){this.__onTransformGesture&&this.__onTransformGesture(t,e)},_onDrag:function(t,e){this.__onDrag&&this.__onDrag(t,e)},_onMouseWheel:function(t){this.__onMouseWheel(t)},_onMouseOut:function(t){var e=this._hoveredTarget;this.fire("mouse:out",{target:e,e:t}),this._hoveredTarget=null,e&&e.fire("mouseout",{e:t}),this._iTextInstances&&this._iTextInstances.forEach(function(t){t.isEditing&&t.hiddenTextarea.focus()})},_onMouseEnter:function(t){this.currentTransform||this.findTarget(t)||(this.fire("mouse:over",{target:null,e:t}),this._hoveredTarget=null)},_onOrientationChange:function(t,e){this.__onOrientationChange&&this.__onOrientationChange(t,e)},_onShake:function(t,e){this.__onShake&&this.__onShake(t,e)},_onLongPress:function(t,e){this.__onLongPress&&this.__onLongPress(t,e)},_onDragOver:function(t){t.preventDefault();var e=this._simpleEventHandler("dragover",t);this._fireEnterLeaveEvents(e,t)},_onContextMenu:function(t){return this.stopContextMenu&&(t.stopPropagation(),t.preventDefault()),!1},_onDoubleClick:function(t){this._cacheTransformEventData(t),this._handleEvent(t,"dblclick"),this._resetTransformEventData(t)},_onMouseDown:function(t){this.__onMouseDown(t),this._resetTransformEventData(),s(fabric.document,"touchend",this._onMouseUp,a),s(fabric.document,"touchmove",this._onMouseMove,a);var e=this.upperCanvasEl,i=this.enablePointerEvents?"pointer":"mouse";o(e,i+"move",this._onMouseMove,a),o(e,"touchmove",this._onMouseMove,a),"touchstart"===t.type?o(e,i+"down",this._onMouseDown):(s(fabric.document,i+"up",this._onMouseUp),s(fabric.document,i+"move",this._onMouseMove,a))},_onMouseUp:function(t){this.__onMouseUp(t),this._resetTransformEventData();var e=this.upperCanvasEl,i=this.enablePointerEvents?"pointer":"mouse";if(o(fabric.document,i+"up",this._onMouseUp),o(fabric.document,"touchend",this._onMouseUp,a),o(fabric.document,i+"move",this._onMouseMove,a),o(fabric.document,"touchmove",this._onMouseMove,a),s(e,i+"move",this._onMouseMove,a),s(e,"touchmove",this._onMouseMove,a),"touchend"===t.type){var r=this;setTimeout(function(){s(r.upperCanvasEl,i+"down",r._onMouseDown)},400)}},_onMouseMove:function(t){!this.allowTouchScrolling&&t.preventDefault&&t.preventDefault(),this.__onMouseMove(t)},_onResize:function(){this.calcOffset()},_shouldRender:function(t){var e=this._activeObject;return!!(!!e!=!!t||e&&t&&e!==t)||(e&&e.isEditing,!1)},__onMouseUp:function(t){var e,i=this._currentTransform,r=this._groupSelector,n=!1,s=!r||0===r.left&&0===r.top;if(this._cacheTransformEventData(t),e=this._target,this._handleEvent(t,"up:before"),!h(t,3))return h(t,2)?(this.fireMiddleClick&&this._handleEvent(t,"up",2,s),void this._resetTransformEventData()):void(this.isDrawingMode&&this._isCurrentlyDrawing?this._onMouseUpInDrawingMode(t):(i&&(this._finalizeCurrentTransform(t),n=i.actionPerformed),s||(this._maybeGroupObjects(t),n||(n=this._shouldRender(e))),e&&(e.isMoving=!1),this._setCursorFromEvent(t,e),this._handleEvent(t,"up",1,s),this._groupSelector=null,this._currentTransform=null,e&&(e.__corner=0),n?this.requestRenderAll():s||this.renderTop()));this.fireRightClick&&this._handleEvent(t,"up",3,s)},_simpleEventHandler:function(t,e){var i=this.findTarget(e),r=this.targets,n={e:e,target:i,subTargets:r};if(this.fire(t,n),i&&i.fire(t,n),!r)return i;for(var s=0;s<r.length;s++)r[s].fire(t,n);return i},_handleEvent:function(t,e,i,r){var n=this._target,s=this.targets||[],o={e:t,target:n,subTargets:s,button:i||1,isClick:r||!1,pointer:this._pointer,absolutePointer:this._absolutePointer,transform:this._currentTransform};this.fire("mouse:"+e,o),n&&n.fire("mouse"+e,o);for(var a=0;a<s.length;a++)s[a].fire("mouse"+e,o)},_finalizeCurrentTransform:function(t){var e,i=this._currentTransform,r=i.target,n={e:t,target:r,transform:i};r._scaling&&(r._scaling=!1),r.setCoords(),(i.actionPerformed||this.stateful&&r.hasStateChanged())&&(i.actionPerformed&&(e=this._addEventOptions(n,i),this._fire(e,n)),this._fire("modified",n))},_addEventOptions:function(t,e){var i,r;switch(e.action){case"scaleX":i="scaled",r="x";break;case"scaleY":i="scaled",r="y";break;case"skewX":i="skewed",r="x";break;case"skewY":i="skewed",r="y";break;case"scale":i="scaled",r="equally";break;case"rotate":i="rotated";break;case"drag":i="moved"}return t.by=r,i},_onMouseDownInDrawingMode:function(t){this._isCurrentlyDrawing=!0,this.getActiveObject()&&this.discardActiveObject(t).requestRenderAll(),this.clipTo&&fabric.util.clipContext(this,this.contextTop);var e=this.getPointer(t);this.freeDrawingBrush.onMouseDown(e,{e:t,pointer:e}),this._handleEvent(t,"down")},_onMouseMoveInDrawingMode:function(t){if(this._isCurrentlyDrawing){var e=this.getPointer(t);this.freeDrawingBrush.onMouseMove(e,{e:t,pointer:e})}this.setCursor(this.freeDrawingCursor),this._handleEvent(t,"move")},_onMouseUpInDrawingMode:function(t){this._isCurrentlyDrawing=!1,this.clipTo&&this.contextTop.restore();var e=this.getPointer(t);this.freeDrawingBrush.onMouseUp({e:t,pointer:e}),this._handleEvent(t,"up")},__onMouseDown:function(t){this._cacheTransformEventData(t),this._handleEvent(t,"down:before");var e=this._target;if(h(t,3))this.fireRightClick&&this._handleEvent(t,"down",3);else if(h(t,2))this.fireMiddleClick&&this._handleEvent(t,"down",2);else if(this.isDrawingMode)this._onMouseDownInDrawingMode(t);else if(!this._currentTransform){var i=this._pointer;this._previousPointer=i;var r=this._shouldRender(e),n=this._shouldGroup(t,e);if(this._shouldClearSelection(t,e)?this.discardActiveObject(t):n&&(this._handleGrouping(t,e),e=this._activeObject),!this.selection||e&&(e.selectable||e.isEditing||e===this._activeObject)||(this._groupSelector={ex:i.x,ey:i.y,top:0,left:0}),e){var s=e===this._activeObject;e.selectable&&this.setActiveObject(e,t),e!==this._activeObject||!e.__corner&&n||this._setupCurrentTransform(t,e,s)}this._handleEvent(t,"down"),(r||n)&&this.requestRenderAll()}},_resetTransformEventData:function(){this._target=null,this._pointer=null,this._absolutePointer=null},_cacheTransformEventData:function(t){this._resetTransformEventData(),this._pointer=this.getPointer(t,!0),this._absolutePointer=this.restorePointerVpt(this._pointer),this._target=this._currentTransform?this._currentTransform.target:this.findTarget(t)||null},_beforeTransform:function(t){var e=this._currentTransform;this.stateful&&e.target.saveState(),this.fire("before:transform",{e:t,transform:e}),e.corner&&this.onBeforeScaleRotate(e.target)},__onMouseMove:function(t){var e,i;if(this._handleEvent(t,"move:before"),this._cacheTransformEventData(t),this.isDrawingMode)this._onMouseMoveInDrawingMode(t);else if(!(void 0!==t.touches&&1<t.touches.length)){var r=this._groupSelector;r?(i=this._pointer,r.left=i.x-r.ex,r.top=i.y-r.ey,this.renderTop()):this._currentTransform?this._transformObject(t):(e=this.findTarget(t)||null,this._setCursorFromEvent(t,e),this._fireOverOutEvents(e,t)),this._handleEvent(t,"move"),this._resetTransformEventData()}},_fireOverOutEvents:function(t,e){this.fireSyntheticInOutEvents(t,e,{targetName:"_hoveredTarget",canvasEvtOut:"mouse:out",evtOut:"mouseout",canvasEvtIn:"mouse:over",evtIn:"mouseover"})},_fireEnterLeaveEvents:function(t,e){this.fireSyntheticInOutEvents(t,e,{targetName:"_draggedoverTarget",evtOut:"dragleave",evtIn:"dragenter"})},fireSyntheticInOutEvents:function(t,e,i){var r,n,s,o=this[i.targetName],a=o!==t,h=i.canvasEvtIn,c=i.canvasEvtOut;a&&(r={e:e,target:t,previousTarget:o},n={e:e,target:o,nextTarget:t},this[i.targetName]=t),s=t&&a,o&&a&&(c&&this.fire(c,n),o.fire(i.evtOut,n)),s&&(h&&this.fire(h,r),t.fire(i.evtIn,r))},__onMouseWheel:function(t){this._cacheTransformEventData(t),this._handleEvent(t,"wheel"),this._resetTransformEventData()},_transformObject:function(t){var e=this.getPointer(t),i=this._currentTransform;i.reset=!1,i.target.isMoving=!0,i.shiftKey=t.shiftKey,i.altKey=t[this.centeredKey],this._beforeScaleTransform(t,i),this._performTransformAction(t,i,e),i.actionPerformed&&this.requestRenderAll()},_performTransformAction:function(t,e,i){var r=i.x,n=i.y,s=e.action,o=!1,a={target:e.target,e:t,transform:e,pointer:i};"rotate"===s?(o=this._rotateObject(r,n))&&this._fire("rotating",a):"scale"===s?(o=this._onScale(t,e,r,n))&&this._fire("scaling",a):"scaleX"===s?(o=this._scaleObject(r,n,"x"))&&this._fire("scaling",a):"scaleY"===s?(o=this._scaleObject(r,n,"y"))&&this._fire("scaling",a):"skewX"===s?(o=this._skewObject(r,n,"x"))&&this._fire("skewing",a):"skewY"===s?(o=this._skewObject(r,n,"y"))&&this._fire("skewing",a):(o=this._translateObject(r,n))&&(this._fire("moving",a),this.setCursor(a.target.moveCursor||this.moveCursor)),e.actionPerformed=e.actionPerformed||o},_fire:function(t,e){this.fire("object:"+t,e),e.target.fire(t,e)},_beforeScaleTransform:function(t,e){if("scale"===e.action||"scaleX"===e.action||"scaleY"===e.action){var i=this._shouldCenterTransform(e.target);(i&&("center"!==e.originX||"center"!==e.originY)||!i&&"center"===e.originX&&"center"===e.originY)&&(this._resetCurrentTransform(),e.reset=!0)}},_onScale:function(t,e,i,r){return this._isUniscalePossible(t,e.target)?(e.currentAction="scale",this._scaleObject(i,r)):(e.reset||"scale"!==e.currentAction||this._resetCurrentTransform(),e.currentAction="scaleEqually",this._scaleObject(i,r,"equally"))},_isUniscalePossible:function(t,e){return(t[this.uniScaleKey]||this.uniScaleTransform)&&!e.get("lockUniScaling")},_setCursorFromEvent:function(t,e){if(!e)return this.setCursor(this.defaultCursor),!1;var i=e.hoverCursor||this.hoverCursor,r=this._activeObject&&"activeSelection"===this._activeObject.type?this._activeObject:null,n=(!r||!r.contains(e))&&e._findTargetCorner(this.getPointer(t,!0));n?this.setCursor(this.getCornerCursor(n,e,t)):this.setCursor(i)},getCornerCursor:function(t,e,i){return this.actionIsDisabled(t,e,i)?this.notAllowedCursor:t in n?this._getRotatedCornerCursor(t,e,i):"mtr"===t&&e.hasRotatingPoint?this.rotationCursor:this.defaultCursor},actionIsDisabled:function(t,e,i){return"mt"===t||"mb"===t?i[this.altActionKey]?e.lockSkewingX:e.lockScalingY:"ml"===t||"mr"===t?i[this.altActionKey]?e.lockSkewingY:e.lockScalingX:"mtr"===t?e.lockRotation:this._isUniscalePossible(i,e)?e.lockScalingX&&e.lockScalingY:e.lockScalingX||e.lockScalingY},_getRotatedCornerCursor:function(t,e,i){var r=Math.round(e.angle%360/45);return r<0&&(r+=8),r+=n[t],i[this.altActionKey]&&n[t]%2==0&&(r+=2),r%=8,this.cursorMap[r]}})}(),function(){var f=Math.min,d=Math.max;fabric.util.object.extend(fabric.Canvas.prototype,{_shouldGroup:function(t,e){var i=this._activeObject;return i&&this._isSelectionKeyPressed(t)&&e&&e.selectable&&this.selection&&(i!==e||"activeSelection"===i.type)&&!e.onSelect({e:t})},_handleGrouping:function(t,e){var i=this._activeObject;i.__corner||(e!==i||(e=this.findTarget(t,!0))&&e.selectable)&&(i&&"activeSelection"===i.type?this._updateActiveSelection(e,t):this._createActiveSelection(e,t))},_updateActiveSelection:function(t,e){var i=this._activeObject,r=i._objects.slice(0);i.contains(t)?(i.removeWithUpdate(t),this._hoveredTarget=t,1===i.size()&&this._setActiveObject(i.item(0),e)):(i.addWithUpdate(t),this._hoveredTarget=i),this._fireSelectionEvents(r,e)},_createActiveSelection:function(t,e){var i=this.getActiveObjects(),r=this._createGroup(t);this._hoveredTarget=r,this._setActiveObject(r,e),this._fireSelectionEvents(i,e)},_createGroup:function(t){var e=this._objects,i=e.indexOf(this._activeObject)<e.indexOf(t)?[this._activeObject,t]:[t,this._activeObject];return this._activeObject.isEditing&&this._activeObject.exitEditing(),new fabric.ActiveSelection(i,{canvas:this})},_groupSelectedObjects:function(t){var e,i=this._collectObjects(t);1===i.length?this.setActiveObject(i[0],t):1<i.length&&(e=new fabric.ActiveSelection(i.reverse(),{canvas:this}),this.setActiveObject(e,t))},_collectObjects:function(e){for(var t,i=[],r=this._groupSelector.ex,n=this._groupSelector.ey,s=r+this._groupSelector.left,o=n+this._groupSelector.top,a=new fabric.Point(f(r,s),f(n,o)),h=new fabric.Point(d(r,s),d(n,o)),c=!this.selectionFullyContained,l=r===s&&n===o,u=this._objects.length;u--&&!((t=this._objects[u])&&t.selectable&&t.visible&&(c&&t.intersectsWithRect(a,h)||t.isContainedWithinRect(a,h)||c&&t.containsPoint(a)||c&&t.containsPoint(h))&&(i.push(t),l)););return 1<i.length&&(i=i.filter(function(t){return!t.onSelect({e:e})})),i},_maybeGroupObjects:function(t){this.selection&&this._groupSelector&&this._groupSelectedObjects(t),this.setCursor(this.defaultCursor),this._groupSelector=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(t){t||(t={});var e=t.format||"png",i=t.quality||1,r=(t.multiplier||1)*(t.enableRetinaScaling?this.getRetinaScaling():1),n=this.toCanvasElement(r,t);return fabric.util.toDataURL(n,e,i)},toCanvasElement:function(t,e){t=t||1;var i=((e=e||{}).width||this.width)*t,r=(e.height||this.height)*t,n=this.getZoom(),s=this.width,o=this.height,a=n*t,h=this.viewportTransform,c=(h[4]-(e.left||0))*t,l=(h[5]-(e.top||0))*t,u=this.interactive,f=[a,0,0,a,c,l],d=this.enableRetinaScaling,g=fabric.util.createCanvasElement(),p=this.contextTop;return g.width=i,g.height=r,this.contextTop=null,this.enableRetinaScaling=!1,this.interactive=!1,this.viewportTransform=f,this.width=i,this.height=r,this.calcViewportBoundaries(),this.renderCanvas(g.getContext("2d"),this._objects),this.viewportTransform=h,this.width=s,this.height=o,this.calcViewportBoundaries(),this.interactive=u,this.enableRetinaScaling=d,this.contextTop=p,g}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(t,e,i){return this.loadFromJSON(t,e,i)},loadFromJSON:function(t,i,e){if(t){var r="string"==typeof t?JSON.parse(t):fabric.util.object.clone(t),n=this,s=r.clipPath,o=this.renderOnAddRemove;return this.renderOnAddRemove=!1,delete r.clipPath,this._enlivenObjects(r.objects,function(e){n.clear(),n._setBgOverlay(r,function(){s?n._enlivenObjects([s],function(t){n.clipPath=t[0],n.__setupCanvas.call(n,r,e,o,i)}):n.__setupCanvas.call(n,r,e,o,i)})},e),this}},__setupCanvas:function(t,e,i,r){var n=this;e.forEach(function(t,e){n.insertAt(t,e)}),this.renderOnAddRemove=i,delete t.objects,delete t.backgroundImage,delete t.overlayImage,delete t.background,delete t.overlay,this._setOptions(t),this.renderAll(),r&&r()},_setBgOverlay:function(t,e){var i={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(t.backgroundImage||t.overlayImage||t.background||t.overlay){var r=function(){i.backgroundImage&&i.overlayImage&&i.backgroundColor&&i.overlayColor&&e&&e()};this.__setBgOverlay("backgroundImage",t.backgroundImage,i,r),this.__setBgOverlay("overlayImage",t.overlayImage,i,r),this.__setBgOverlay("backgroundColor",t.background,i,r),this.__setBgOverlay("overlayColor",t.overlay,i,r)}else e&&e()},__setBgOverlay:function(e,t,i,r){var n=this;if(!t)return i[e]=!0,void(r&&r());"backgroundImage"===e||"overlayImage"===e?fabric.util.enlivenObjects([t],function(t){n[e]=t[0],i[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){i[e]=!0,r&&r()})},_enlivenObjects:function(t,e,i){t&&0!==t.length?fabric.util.enlivenObjects(t,function(t){e&&e(t)},null,i):e&&e([])},_toDataURL:function(e,i){this.clone(function(t){i(t.toDataURL(e))})},_toDataURLWithMultiplier:function(e,i,r){this.clone(function(t){r(t.toDataURLWithMultiplier(e,i))})},clone:function(e,t){var i=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(i,function(){e&&e(t)})})},cloneWithoutData:function(t){var e=fabric.util.createCanvasElement();e.width=this.width,e.height=this.height;var i=new fabric.Canvas(e);i.clipTo=this.clipTo,this.backgroundImage?(i.setBackgroundImage(this.backgroundImage.src,function(){i.renderAll(),t&&t(i)}),i.backgroundImageOpacity=this.backgroundImageOpacity,i.backgroundImageStretch=this.backgroundImageStretch):t&&t(i)}}),function(t){"use strict";var x=t.fabric||(t.fabric={}),e=x.util.object.extend,o=x.util.object.clone,r=x.util.toFixed,i=x.util.string.capitalize,a=x.util.degreesToRadians,n=x.StaticCanvas.supports("setLineDash"),s=!x.isLikelyNode;x.Object||(x.Object=x.util.createClass(x.CommonMethods,{type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,skewX:0,skewY:0,cornerSize:13,transparentCorners:!0,hoverCursor:null,moveCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",borderDashArray:null,cornerColor:"rgba(102,153,255,0.5)",cornerStrokeColor:null,cornerStyle:"rect",cornerDashArray:null,centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"nonzero",globalCompositeOperation:"source-over",backgroundColor:"",selectionBackgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeDashOffset:0,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:4,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:0,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockSkewingX:!1,lockSkewingY:!1,lockScalingFlip:!1,excludeFromExport:!1,objectCaching:s,statefullCache:!1,noScaleCache:!0,strokeUniform:!1,dirty:!0,__corner:0,paintFirst:"fill",stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor skewX skewY fillRule paintFirst clipPath strokeUniform".split(" "),cacheProperties:"fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath".split(" "),clipPath:void 0,inverted:!1,absolutePositioned:!1,initialize:function(t){t&&this.setOptions(t)},_createCacheCanvas:function(){this._cacheProperties={},this._cacheCanvas=x.util.createCanvasElement(),this._cacheContext=this._cacheCanvas.getContext("2d"),this._updateCacheCanvas(),this.dirty=!0},_limitCacheSize:function(t){var e=x.perfLimitSizeTotal,i=t.width,r=t.height,n=x.maxCacheSideLimit,s=x.minCacheSideLimit;if(i<=n&&r<=n&&i*r<=e)return i<s&&(t.width=s),r<s&&(t.height=s),t;var o=i/r,a=x.util.limitDimsByArea(o,e),h=x.util.capValue,c=h(s,a.x,n),l=h(s,a.y,n);return c<i&&(t.zoomX/=i/c,t.width=c,t.capped=!0),l<r&&(t.zoomY/=r/l,t.height=l,t.capped=!0),t},_getCacheCanvasDimensions:function(){var t=this.getTotalObjectScaling(),e=this._getTransformedDimensions(0,0),i=e.x*t.scaleX/this.scaleX,r=e.y*t.scaleY/this.scaleY;return{width:i+2,height:r+2,zoomX:t.scaleX,zoomY:t.scaleY,x:i,y:r}},_updateCacheCanvas:function(){var t=this.canvas;if(this.noScaleCache&&t&&t._currentTransform){var e=t._currentTransform.target,i=t._currentTransform.action;if(this===e&&i.slice&&"scale"===i.slice(0,5))return!1}var r,n,s=this._cacheCanvas,o=this._limitCacheSize(this._getCacheCanvasDimensions()),a=x.minCacheSideLimit,h=o.width,c=o.height,l=o.zoomX,u=o.zoomY,f=h!==this.cacheWidth||c!==this.cacheHeight,d=this.zoomX!==l||this.zoomY!==u,g=f||d,p=0,v=0,m=!1;if(f){var b=this._cacheCanvas.width,_=this._cacheCanvas.height,y=b<h||_<c;m=y||(h<.9*b||c<.9*_)&&a<b&&a<_,y&&!o.capped&&(a<h||a<c)&&(p=.1*h,v=.1*c)}return!!g&&(m?(s.width=Math.ceil(h+p),s.height=Math.ceil(c+v)):(this._cacheContext.setTransform(1,0,0,1,0,0),this._cacheContext.clearRect(0,0,s.width,s.height)),r=o.x/2,n=o.y/2,this.cacheTranslationX=Math.round(s.width/2-r)+r,this.cacheTranslationY=Math.round(s.height/2-n)+n,this.cacheWidth=h,this.cacheHeight=c,this._cacheContext.translate(this.cacheTranslationX,this.cacheTranslationY),this._cacheContext.scale(l,u),this.zoomX=l,this.zoomY=u,!0)},setOptions:function(t){this._setOptions(t),this._initGradient(t.fill,"fill"),this._initGradient(t.stroke,"stroke"),this._initClipping(t),this._initPattern(t.fill,"fill"),this._initPattern(t.stroke,"stroke")},transform:function(t){var e;e=this.group&&!this.group._transformDone?this.calcTransformMatrix():this.calcOwnMatrix(),t.transform(e[0],e[1],e[2],e[3],e[4],e[5])},toObject:function(t){var e=x.Object.NUM_FRACTION_DIGITS,i={type:this.type,version:x.version,originX:this.originX,originY:this.originY,left:r(this.left,e),top:r(this.top,e),width:r(this.width,e),height:r(this.height,e),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,e),strokeDashArray:this.strokeDashArray?this.strokeDashArray.concat():this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeDashOffset:this.strokeDashOffset,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,e),scaleX:r(this.scaleX,e),scaleY:r(this.scaleY,e),angle:r(this.angle,e),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,e),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor,fillRule:this.fillRule,paintFirst:this.paintFirst,globalCompositeOperation:this.globalCompositeOperation,transformMatrix:this.transformMatrix?this.transformMatrix.concat():null,skewX:r(this.skewX,e),skewY:r(this.skewY,e)};return this.clipPath&&(i.clipPath=this.clipPath.toObject(t),i.clipPath.inverted=this.clipPath.inverted,i.clipPath.absolutePositioned=this.clipPath.absolutePositioned),x.util.populateWithProperties(this,i,t),this.includeDefaultValues||(i=this._removeDefaultValues(i)),i},toDatalessObject:function(t){return this.toObject(t)},_removeDefaultValues:function(e){var i=x.util.getKlass(e.type).prototype;return i.stateProperties.forEach(function(t){"left"!==t&&"top"!==t&&(e[t]===i[t]&&delete e[t],"[object Array]"===Object.prototype.toString.call(e[t])&&"[object Array]"===Object.prototype.toString.call(i[t])&&0===e[t].length&&0===i[t].length&&delete e[t])}),e},toString:function(){return"#<fabric."+i(this.type)+">"},getObjectScaling:function(){var t=this.scaleX,e=this.scaleY;if(this.group){var i=this.group.getObjectScaling();t*=i.scaleX,e*=i.scaleY}return{scaleX:t,scaleY:e}},getTotalObjectScaling:function(){var t=this.getObjectScaling(),e=t.scaleX,i=t.scaleY;if(this.canvas){var r=this.canvas.getZoom(),n=this.canvas.getRetinaScaling();e*=r*n,i*=r*n}return{scaleX:e,scaleY:i}},getObjectOpacity:function(){var t=this.opacity;return this.group&&(t*=this.group.getObjectOpacity()),t},_set:function(t,e){var i="scaleX"===t||"scaleY"===t,r=this[t]!==e,n=!1;return i&&(e=this._constrainScale(e)),"scaleX"===t&&e<0?(this.flipX=!this.flipX,e*=-1):"scaleY"===t&&e<0?(this.flipY=!this.flipY,e*=-1):"shadow"!==t||!e||e instanceof x.Shadow?"dirty"===t&&this.group&&this.group.set("dirty",e):e=new x.Shadow(e),this[t]=e,r&&(n=this.group&&this.group.isOnACache(),-1<this.cacheProperties.indexOf(t)?(this.dirty=!0,n&&this.group.set("dirty",!0)):n&&-1<this.stateProperties.indexOf(t)&&this.group.set("dirty",!0)),this},setOnGroup:function(){},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:x.iMatrix.concat()},isNotVisible:function(){return 0===this.opacity||0===this.width&&0===this.height&&0===this.strokeWidth||!this.visible},render:function(t){this.isNotVisible()||this.canvas&&this.canvas.skipOffscreen&&!this.group&&!this.isOnScreen()||(t.save(),this._setupCompositeOperation(t),this.drawSelectionBackground(t),this.transform(t),this._setOpacity(t),this._setShadow(t,this),this.transformMatrix&&t.transform.apply(t,this.transformMatrix),this.clipTo&&x.util.clipContext(this,t),this.shouldCache()?(this.renderCache(),this.drawCacheOnCanvas(t)):(this._removeCacheCanvas(),this.dirty=!1,this.drawObject(t),this.objectCaching&&this.statefullCache&&this.saveState({propertySet:"cacheProperties"})),this.clipTo&&t.restore(),t.restore())},renderCache:function(t){t=t||{},this._cacheCanvas||this._createCacheCanvas(),this.isCacheDirty()&&(this.statefullCache&&this.saveState({propertySet:"cacheProperties"}),this.drawObject(this._cacheContext,t.forClipping),this.dirty=!1)},_removeCacheCanvas:function(){this._cacheCanvas=null,this.cacheWidth=0,this.cacheHeight=0},hasStroke:function(){return this.stroke&&"transparent"!==this.stroke&&0!==this.strokeWidth},hasFill:function(){return this.fill&&"transparent"!==this.fill},needsItsOwnCache:function(){return!("stroke"!==this.paintFirst||!this.hasFill()||!this.hasStroke()||"object"!=typeof this.shadow)||!!this.clipPath},shouldCache:function(){return this.ownCaching=this.needsItsOwnCache()||this.objectCaching&&(!this.group||!this.group.isOnACache()),this.ownCaching},willDrawShadow:function(){return!!this.shadow&&(0!==this.shadow.offsetX||0!==this.shadow.offsetY)},drawClipPathOnCache:function(t){var e=this.clipPath;if(t.save(),e.inverted?t.globalCompositeOperation="destination-out":t.globalCompositeOperation="destination-in",e.absolutePositioned){var i=x.util.invertTransform(this.calcTransformMatrix());t.transform(i[0],i[1],i[2],i[3],i[4],i[5])}e.transform(t),t.scale(1/e.zoomX,1/e.zoomY),t.drawImage(e._cacheCanvas,-e.cacheTranslationX,-e.cacheTranslationY),t.restore()},drawObject:function(t,e){var i=this.fill,r=this.stroke;e?(this.fill="black",this.stroke="",this._setClippingProperties(t)):(this._renderBackground(t),this._setStrokeStyles(t,this),this._setFillStyles(t,this)),this._render(t),this._drawClipPath(t),this.fill=i,this.stroke=r},_drawClipPath:function(t){var e=this.clipPath;e&&(e.canvas=this.canvas,e.shouldCache(),e._transformDone=!0,e.renderCache({forClipping:!0}),this.drawClipPathOnCache(t))},drawCacheOnCanvas:function(t){t.scale(1/this.zoomX,1/this.zoomY),t.drawImage(this._cacheCanvas,-this.cacheTranslationX,-this.cacheTranslationY)},isCacheDirty:function(t){if(this.isNotVisible())return!1;if(this._cacheCanvas&&!t&&this._updateCacheCanvas())return!0;if(this.dirty||this.clipPath&&this.clipPath.absolutePositioned||this.statefullCache&&this.hasStateChanged("cacheProperties")){if(this._cacheCanvas&&!t){var e=this.cacheWidth/this.zoomX,i=this.cacheHeight/this.zoomY;this._cacheContext.clearRect(-e/2,-i/2,e,i)}return!0}return!1},_renderBackground:function(t){if(this.backgroundColor){var e=this._getNonTransformedDimensions();t.fillStyle=this.backgroundColor,t.fillRect(-e.x/2,-e.y/2,e.x,e.y),this._removeShadow(t)}},_setOpacity:function(t){this.group&&!this.group._transformDone?t.globalAlpha=this.getObjectOpacity():t.globalAlpha*=this.opacity},_setStrokeStyles:function(t,e){e.stroke&&(t.lineWidth=e.strokeWidth,t.lineCap=e.strokeLineCap,t.lineDashOffset=e.strokeDashOffset,t.lineJoin=e.strokeLineJoin,t.miterLimit=e.strokeMiterLimit,t.strokeStyle=e.stroke.toLive?e.stroke.toLive(t,this):e.stroke)},_setFillStyles:function(t,e){e.fill&&(t.fillStyle=e.fill.toLive?e.fill.toLive(t,this):e.fill)},_setClippingProperties:function(t){t.globalAlpha=1,t.strokeStyle="transparent",t.fillStyle="#000000"},_setLineDash:function(e,t,i){t&&(1&t.length&&t.push.apply(t,t),n?e.setLineDash(t):i&&i(e),this.strokeUniform&&e.setLineDash(e.getLineDash().map(function(t){return t*e.lineWidth})))},_renderControls:function(t,e){var i,r,n,s=this.getViewportTransform(),o=this.calcTransformMatrix();r=void 0!==(e=e||{}).hasBorders?e.hasBorders:this.hasBorders,n=void 0!==e.hasControls?e.hasControls:this.hasControls,o=x.util.multiplyTransformMatrices(s,o),i=x.util.qrDecompose(o),t.save(),t.translate(i.translateX,i.translateY),t.lineWidth=1*this.borderScaleFactor,this.group||(t.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1),e.forActiveSelection?(t.rotate(a(i.angle)),r&&this.drawBordersInGroup(t,i,e)):(t.rotate(a(this.angle)),r&&this.drawBorders(t,e)),n&&this.drawControls(t,e),t.restore()},_setShadow:function(t){if(this.shadow){var e,i=this.shadow,r=this.canvas,n=r&&r.viewportTransform[0]||1,s=r&&r.viewportTransform[3]||1;e=i.nonScaling?{scaleX:1,scaleY:1}:this.getObjectScaling(),r&&r._isRetinaScaling()&&(n*=x.devicePixelRatio,s*=x.devicePixelRatio),t.shadowColor=i.color,t.shadowBlur=i.blur*x.browserShadowBlurConstant*(n+s)*(e.scaleX+e.scaleY)/4,t.shadowOffsetX=i.offsetX*n*e.scaleX,t.shadowOffsetY=i.offsetY*s*e.scaleY}},_removeShadow:function(t){this.shadow&&(t.shadowColor="",t.shadowBlur=t.shadowOffsetX=t.shadowOffsetY=0)},_applyPatternGradientTransform:function(t,e){if(!e||!e.toLive)return{offsetX:0,offsetY:0};var i=e.gradientTransform||e.patternTransform,r=-this.width/2+e.offsetX||0,n=-this.height/2+e.offsetY||0;return t.translate(r,n),i&&t.transform(i[0],i[1],i[2],i[3],i[4],i[5]),{offsetX:r,offsetY:n}},_renderPaintInOrder:function(t){"stroke"===this.paintFirst?(this._renderStroke(t),this._renderFill(t)):(this._renderFill(t),this._renderStroke(t))},_render:function(){},_renderFill:function(t){this.fill&&(t.save(),this._applyPatternGradientTransform(t,this.fill),"evenodd"===this.fillRule?t.fill("evenodd"):t.fill(),t.restore())},_renderStroke:function(t){this.stroke&&0!==this.strokeWidth&&(this.shadow&&!this.shadow.affectStroke&&this._removeShadow(t),t.save(),this.strokeUniform&&t.scale(1/this.scaleX,1/this.scaleY),this._setLineDash(t,this.strokeDashArray,this._renderDashedStroke),this._applyPatternGradientTransform(t,this.stroke),t.stroke(),t.restore())},_findCenterFromElement:function(){return{x:this.left+this.width/2,y:this.top+this.height/2}},_assignTransformMatrixProps:function(){if(this.transformMatrix){var t=x.util.qrDecompose(this.transformMatrix);this.flipX=!1,this.flipY=!1,this.set("scaleX",t.scaleX),this.set("scaleY",t.scaleY),this.angle=t.angle,this.skewX=t.skewX,this.skewY=0}},_removeTransformMatrix:function(t){var e=this._findCenterFromElement();this.transformMatrix&&(this._assignTransformMatrixProps(),e=x.util.transformPoint(e,this.transformMatrix)),this.transformMatrix=null,t&&(this.scaleX*=t.scaleX,this.scaleY*=t.scaleY,this.cropX=t.cropX,this.cropY=t.cropY,e.x+=t.offsetLeft,e.y+=t.offsetTop,this.width=t.width,this.height=t.height),this.setPositionByOrigin(e,"center","center")},clone:function(t,e){var i=this.toObject(e);this.constructor.fromObject?this.constructor.fromObject(i,t):x.Object._fromObject("Object",i,t)},cloneAsImage:function(t,e){var i=this.toCanvasElement(e);return t&&t(new x.Image(i)),this},toCanvasElement:function(t){t||(t={});var e=x.util,i=e.saveObjectTransform(this),r=this.shadow,n=Math.abs,s=(t.multiplier||1)*(t.enableRetinaScaling?x.devicePixelRatio:1);t.withoutTransform&&e.resetObjectTransform(this),t.withoutShadow&&(this.shadow=null);var o,a,h=x.util.createCanvasElement(),c=this.getBoundingRect(!0,!0),l=this.shadow,u={x:0,y:0};l&&(a=l.blur,o=l.nonScaling?{scaleX:1,scaleY:1}:this.getObjectScaling(),u.x=2*Math.round(n(l.offsetX)+a)*n(o.scaleX),u.y=2*Math.round(n(l.offsetY)+a)*n(o.scaleY)),h.width=c.width+u.x,h.height=c.height+u.y,h.width+=h.width%2?2-h.width%2:0,h.height+=h.height%2?2-h.height%2:0;var f=new x.StaticCanvas(h,{enableRetinaScaling:!1,renderOnAddRemove:!1,skipOffscreen:!1});"jpeg"===t.format&&(f.backgroundColor="#fff"),this.setPositionByOrigin(new x.Point(f.width/2,f.height/2),"center","center");var d=this.canvas;f.add(this);var g=f.toCanvasElement(s||1,t);return this.shadow=r,this.canvas=d,this.set(i).setCoords(),f._objects=[],f.dispose(),f=null,g},toDataURL:function(t){return t||(t={}),x.util.toDataURL(this.toCanvasElement(t),t.format||"png",t.quality||1)},isType:function(t){return this.type===t},complexity:function(){return 1},toJSON:function(t){return this.toObject(t)},setGradient:function(t,e){e||(e={});var i={colorStops:[]};return i.type=e.type||(e.r1||e.r2?"radial":"linear"),i.coords={x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2},(e.r1||e.r2)&&(i.coords.r1=e.r1,i.coords.r2=e.r2),i.gradientTransform=e.gradientTransform,x.Gradient.prototype.addColorStop.call(i,e.colorStops),this.set(t,x.Gradient.forObject(this,i))},setPatternFill:function(t,e){return this.set("fill",new x.Pattern(t,e))},setShadow:function(t){return this.set("shadow",t?new x.Shadow(t):null)},setColor:function(t){return this.set("fill",t),this},rotate:function(t){var e=("center"!==this.originX||"center"!==this.originY)&&this.centeredRotation;return e&&this._setOriginToCenter(),this.set("angle",t),e&&this._resetOrigin(),this},centerH:function(){return this.canvas&&this.canvas.centerObjectH(this),this},viewportCenterH:function(){return this.canvas&&this.canvas.viewportCenterObjectH(this),this},centerV:function(){return this.canvas&&this.canvas.centerObjectV(this),this},viewportCenterV:function(){return this.canvas&&this.canvas.viewportCenterObjectV(this),this},center:function(){return this.canvas&&this.canvas.centerObject(this),this},viewportCenter:function(){return this.canvas&&this.canvas.viewportCenterObject(this),this},getLocalPointer:function(t,e){e=e||this.canvas.getPointer(t);var i=new x.Point(e.x,e.y),r=this._getLeftTopCoords();return this.angle&&(i=x.util.rotatePoint(i,r,a(-this.angle))),{x:i.x-r.x,y:i.y-r.y}},_setupCompositeOperation:function(t){this.globalCompositeOperation&&(t.globalCompositeOperation=this.globalCompositeOperation)}}),x.util.createAccessors&&x.util.createAccessors(x.Object),e(x.Object.prototype,x.Observable),x.Object.NUM_FRACTION_DIGITS=2,x.Object._fromObject=function(t,i,r,n){var s=x[t];i=o(i,!0),x.util.enlivenPatterns([i.fill,i.stroke],function(t){void 0!==t[0]&&(i.fill=t[0]),void 0!==t[1]&&(i.stroke=t[1]),x.util.enlivenObjects([i.clipPath],function(t){i.clipPath=t[0];var e=n?new s(i[n],i):new s(i);r&&r(e)})})},x.Object.__uid=0)}("undefined"!=typeof exports?exports:this),function(){var a=fabric.util.degreesToRadians,l={left:-.5,center:0,right:.5},u={top:-.5,center:0,bottom:.5};fabric.util.object.extend(fabric.Object.prototype,{translateToGivenOrigin:function(t,e,i,r,n){var s,o,a,h=t.x,c=t.y;return"string"==typeof e?e=l[e]:e-=.5,"string"==typeof r?r=l[r]:r-=.5,"string"==typeof i?i=u[i]:i-=.5,"string"==typeof n?n=u[n]:n-=.5,o=n-i,((s=r-e)||o)&&(a=this._getTransformedDimensions(),h=t.x+s*a.x,c=t.y+o*a.y),new fabric.Point(h,c)},translateToCenterPoint:function(t,e,i){var r=this.translateToGivenOrigin(t,e,i,"center","center");return this.angle?fabric.util.rotatePoint(r,t,a(this.angle)):r},translateToOriginPoint:function(t,e,i){var r=this.translateToGivenOrigin(t,"center","center",e,i);return this.angle?fabric.util.rotatePoint(r,t,a(this.angle)):r},getCenterPoint:function(){var t=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(t,this.originX,this.originY)},getPointByOrigin:function(t,e){var i=this.getCenterPoint();return this.translateToOriginPoint(i,t,e)},toLocalPoint:function(t,e,i){var r,n,s=this.getCenterPoint();return r=void 0!==e&&void 0!==i?this.translateToGivenOrigin(s,"center","center",e,i):new fabric.Point(this.left,this.top),n=new fabric.Point(t.x,t.y),this.angle&&(n=fabric.util.rotatePoint(n,s,-a(this.angle))),n.subtractEquals(r)},setPositionByOrigin:function(t,e,i){var r=this.translateToCenterPoint(t,e,i),n=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",n.x),this.set("top",n.y)},adjustPosition:function(t){var e,i,r=a(this.angle),n=this.getScaledWidth(),s=fabric.util.cos(r)*n,o=fabric.util.sin(r)*n;e="string"==typeof this.originX?l[this.originX]:this.originX-.5,i="string"==typeof t?l[t]:t-.5,this.left+=s*(i-e),this.top+=o*(i-e),this.setCoords(),this.originX=t},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var t=this.getCenterPoint();this.originX="center",this.originY="center",this.left=t.x,this.top=t.y},_resetOrigin:function(){var t=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=t.x,this.top=t.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","top")}})}(),function(){var k=fabric.util.degreesToRadians,P=fabric.util.multiplyTransformMatrices,D=fabric.util.transformPoint;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,aCoords:null,ownMatrixCache:null,matrixCache:null,getCoords:function(t,e){this.oCoords||this.setCoords();var i,r=t?this.aCoords:this.oCoords;return i=e?this.calcCoords(t):r,[new fabric.Point(i.tl.x,i.tl.y),new fabric.Point(i.tr.x,i.tr.y),new fabric.Point(i.br.x,i.br.y),new fabric.Point(i.bl.x,i.bl.y)]},intersectsWithRect:function(t,e,i,r){var n=this.getCoords(i,r);return"Intersection"===fabric.Intersection.intersectPolygonRectangle(n,t,e).status},intersectsWithObject:function(t,e,i){return"Intersection"===fabric.Intersection.intersectPolygonPolygon(this.getCoords(e,i),t.getCoords(e,i)).status||t.isContainedWithinObject(this,e,i)||this.isContainedWithinObject(t,e,i)},isContainedWithinObject:function(t,e,i){for(var r=this.getCoords(e,i),n=0,s=t._getImageLines(i?t.calcCoords(e):e?t.aCoords:t.oCoords);n<4;n++)if(!t.containsPoint(r[n],s))return!1;return!0},isContainedWithinRect:function(t,e,i,r){var n=this.getBoundingRect(i,r);return n.left>=t.x&&n.left+n.width<=e.x&&n.top>=t.y&&n.top+n.height<=e.y},containsPoint:function(t,e,i,r){e=e||this._getImageLines(r?this.calcCoords(i):i?this.aCoords:this.oCoords);var n=this._findCrossPoints(t,e);return 0!==n&&n%2==1},isOnScreen:function(t){if(!this.canvas)return!1;for(var e,i=this.canvas.vptCoords.tl,r=this.canvas.vptCoords.br,n=this.getCoords(!0,t),s=0;s<4;s++)if((e=n[s]).x<=r.x&&e.x>=i.x&&e.y<=r.y&&e.y>=i.y)return!0;return!!this.intersectsWithRect(i,r,!0,t)||this._containsCenterOfCanvas(i,r,t)},_containsCenterOfCanvas:function(t,e,i){var r={x:(t.x+e.x)/2,y:(t.y+e.y)/2};return!!this.containsPoint(r,null,!0,i)},isPartiallyOnScreen:function(t){if(!this.canvas)return!1;var e=this.canvas.vptCoords.tl,i=this.canvas.vptCoords.br;return!!this.intersectsWithRect(e,i,!0,t)||this._containsCenterOfCanvas(e,i,t)},_getImageLines:function(t){return{topline:{o:t.tl,d:t.tr},rightline:{o:t.tr,d:t.br},bottomline:{o:t.br,d:t.bl},leftline:{o:t.bl,d:t.tl}}},_findCrossPoints:function(t,e){var i,r,n,s=0;for(var o in e)if(!((n=e[o]).o.y<t.y&&n.d.y<t.y||n.o.y>=t.y&&n.d.y>=t.y||(n.o.x===n.d.x&&n.o.x>=t.x?r=n.o.x:(0,i=(n.d.y-n.o.y)/(n.d.x-n.o.x),r=-(t.y-0*t.x-(n.o.y-i*n.o.x))/(0-i)),r>=t.x&&(s+=1),2!==s)))break;return s},getBoundingRect:function(t,e){var i=this.getCoords(t,e);return fabric.util.makeBoundingBoxFromPoints(i)},getScaledWidth:function(){return this._getTransformedDimensions().x},getScaledHeight:function(){return this._getTransformedDimensions().y},_constrainScale:function(t){return Math.abs(t)<this.minScaleLimit?t<0?-this.minScaleLimit:this.minScaleLimit:0===t?1e-4:t},scale:function(t){return this._set("scaleX",t),this._set("scaleY",t),this.setCoords()},scaleToWidth:function(t,e){var i=this.getBoundingRect(e).width/this.getScaledWidth();return this.scale(t/this.width/i)},scaleToHeight:function(t,e){var i=this.getBoundingRect(e).height/this.getScaledHeight();return this.scale(t/this.height/i)},calcCoords:function(t){var e=this._calcRotateMatrix(),i=this._calcTranslateMatrix(),r=P(i,e),n=this.getViewportTransform(),s=t?r:P(n,r),o=this._getTransformedDimensions(),a=o.x/2,h=o.y/2,c=D({x:-a,y:-h},s),l=D({x:a,y:-h},s),u=D({x:-a,y:h},s),f=D({x:a,y:h},s);if(!t){var d=this.padding,g=k(this.angle),p=fabric.util.cos(g),v=fabric.util.sin(g),m=p*d,b=v*d,_=m+b,y=m-b;d&&(c.x-=y,c.y-=_,l.x+=_,l.y-=y,u.x-=_,u.y+=y,f.x+=y,f.y+=_);var x=new fabric.Point((c.x+u.x)/2,(c.y+u.y)/2),C=new fabric.Point((l.x+c.x)/2,(l.y+c.y)/2),S=new fabric.Point((f.x+l.x)/2,(f.y+l.y)/2),T=new fabric.Point((f.x+u.x)/2,(f.y+u.y)/2),w=new fabric.Point(C.x+v*this.rotatingPointOffset,C.y-p*this.rotatingPointOffset)}var O={tl:c,tr:l,br:f,bl:u};return t||(O.ml=x,O.mt=C,O.mr=S,O.mb=T,O.mtr=w),O},setCoords:function(t,e){return this.oCoords=this.calcCoords(t),e||(this.aCoords=this.calcCoords(!0)),t||this._setCornerCoords&&this._setCornerCoords(),this},_calcRotateMatrix:function(){if(this.angle){var t=k(this.angle),e=fabric.util.cos(t),i=fabric.util.sin(t);return[e,i,-i,e,0,0]}return fabric.iMatrix.concat()},_calcTranslateMatrix:function(){var t=this.getCenterPoint();return[1,0,0,1,t.x,t.y]},transformMatrixKey:function(t){var e="_",i="";return!t&&this.group&&(i=this.group.transformMatrixKey(t)+e),i+this.top+e+this.left+e+this.scaleX+e+this.scaleY+e+this.skewX+e+this.skewY+e+this.angle+e+this.originX+e+this.originY+e+this.width+e+this.height+e+this.strokeWidth+this.flipX+this.flipY},calcTransformMatrix:function(t){if(t)return this.calcOwnMatrix();var e=this.transformMatrixKey(),i=this.matrixCache||(this.matrixCache={});if(i.key===e)return i.value;var r=this.calcOwnMatrix();return this.group&&(r=P(this.group.calcTransformMatrix(),r)),i.key=e,i.value=r},calcOwnMatrix:function(){var t=this.transformMatrixKey(!0),e=this.ownMatrixCache||(this.ownMatrixCache={});if(e.key===t)return e.value;var i,r=this._calcTranslateMatrix(),n=this._calcDimensionsTransformMatrix(this.skewX,this.skewY,!0);return this.angle&&(i=this._calcRotateMatrix(),r=P(r,i)),r=P(r,n),e.key=t,e.value=r},_calcDimensionsTransformMatrix:function(t,e,i){var r,n=[this.scaleX*(i&&this.flipX?-1:1),0,0,this.scaleY*(i&&this.flipY?-1:1),0,0];return t&&(r=[1,0,Math.tan(k(t)),1],n=P(n,r,!0)),e&&(r=[1,Math.tan(k(e)),0,1],n=P(n,r,!0)),n},_getNonTransformedDimensions:function(){var t=this.strokeWidth;return{x:this.width+t,y:this.height+t}},_getTransformedDimensions:function(t,e){void 0===t&&(t=this.skewX),void 0===e&&(e=this.skewY);var i,r,n=this._getNonTransformedDimensions(),s=0===t&&0===e;if(this.strokeUniform?(i=this.width,r=this.height):(i=n.x,r=n.y),s)return this._finalizeDimensions(i*this.scaleX,r*this.scaleY);var o,a,h=[{x:-(i/=2),y:-(r/=2)},{x:i,y:-r},{x:-i,y:r},{x:i,y:r}],c=this._calcDimensionsTransformMatrix(t,e,!1);for(o=0;o<h.length;o++)h[o]=fabric.util.transformPoint(h[o],c);return a=fabric.util.makeBoundingBoxFromPoints(h),this._finalizeDimensions(a.width,a.height)},_finalizeDimensions:function(t,e){return this.strokeUniform?{x:t+this.strokeWidth,y:e+this.strokeWidth}:{x:t,y:e}},_calculateCurrentDimensions:function(){var t=this.getViewportTransform(),e=this._getTransformedDimensions();return fabric.util.transformPoint(e,t,!0).scalarAdd(2*this.padding)}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(t){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,t):this.canvas.sendBackwards(this,t),this},bringForward:function(t){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,t):this.canvas.bringForward(this,t),this},moveTo:function(t){return this.group&&"activeSelection"!==this.group.type?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,t):this.canvas.moveTo(this,t),this}}),function(){function f(t,e){if(e){if(e.toLive)return t+": url(#SVGID_"+e.id+"); ";var i=new fabric.Color(e),r=t+": "+i.toRgb()+"; ",n=i.getAlpha();return 1!==n&&(r+=t+"-opacity: "+n.toString()+"; "),r}return t+": none; "}var i=fabric.util.toFixed;fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(t){var e=this.fillRule?this.fillRule:"nonzero",i=this.strokeWidth?this.strokeWidth:"0",r=this.strokeDashArray?this.strokeDashArray.join(" "):"none",n=this.strokeDashOffset?this.strokeDashOffset:"0",s=this.strokeLineCap?this.strokeLineCap:"butt",o=this.strokeLineJoin?this.strokeLineJoin:"miter",a=this.strokeMiterLimit?this.strokeMiterLimit:"4",h=void 0!==this.opacity?this.opacity:"1",c=this.visible?"":" visibility: hidden;",l=t?"":this.getSvgFilter(),u=f("fill",this.fill);return[f("stroke",this.stroke),"stroke-width: ",i,"; ","stroke-dasharray: ",r,"; ","stroke-linecap: ",s,"; ","stroke-dashoffset: ",n,"; ","stroke-linejoin: ",o,"; ","stroke-miterlimit: ",a,"; ",u,"fill-rule: ",e,"; ","opacity: ",h,";",l,c].join("")},getSvgSpanStyles:function(t,e){var i="; ",r=t.fontFamily?"font-family: "+(-1===t.fontFamily.indexOf("'")&&-1===t.fontFamily.indexOf('"')?"'"+t.fontFamily+"'":t.fontFamily)+i:"",n=t.strokeWidth?"stroke-width: "+t.strokeWidth+i:"",s=(r=r,t.fontSize?"font-size: "+t.fontSize+"px"+i:""),o=t.fontStyle?"font-style: "+t.fontStyle+i:"",a=t.fontWeight?"font-weight: "+t.fontWeight+i:"",h=t.fill?f("fill",t.fill):"",c=t.stroke?f("stroke",t.stroke):"",l=this.getSvgTextDecoration(t);return l&&(l="text-decoration: "+l+i),[c,n,r,s,o,a,l,h,t.deltaY?"baseline-shift: "+-t.deltaY+"; ":"",e?"white-space: pre; ":""].join("")},getSvgTextDecoration:function(t){return"overline"in t||"underline"in t||"linethrough"in t?(t.overline?"overline ":"")+(t.underline?"underline ":"")+(t.linethrough?"line-through ":""):""},getSvgFilter:function(){return this.shadow?"filter: url(#SVGID_"+this.shadow.id+");":""},getSvgCommons:function(){return[this.id?'id="'+this.id+'" ':"",this.clipPath?'clip-path="url(#'+this.clipPath.clipPathId+')" ':""].join("")},getSvgTransform:function(t,e){var i=t?this.calcTransformMatrix():this.calcOwnMatrix();return'transform="'+fabric.util.matrixToSVG(i)+(e||"")+this.getSvgTransformMatrix()+'" '},getSvgTransformMatrix:function(){return this.transformMatrix?" "+fabric.util.matrixToSVG(this.transformMatrix):""},_setSVGBg:function(t){if(this.backgroundColor){var e=fabric.Object.NUM_FRACTION_DIGITS;t.push("\t\t<rect ",this._getFillAttributes(this.backgroundColor),' x="',i(-this.width/2,e),'" y="',i(-this.height/2,e),'" width="',i(this.width,e),'" height="',i(this.height,e),'"></rect>\n')}},toSVG:function(t){return this._createBaseSVGMarkup(this._toSVG(t),{reviver:t})},toClipPathSVG:function(t){return"\t"+this._createBaseClipPathSVGMarkup(this._toSVG(t),{reviver:t})},_createBaseClipPathSVGMarkup:function(t,e){var i=(e=e||{}).reviver,r=e.additionalTransform||"",n=[this.getSvgTransform(!0,r),this.getSvgCommons()].join(""),s=t.indexOf("COMMON_PARTS");return t[s]=n,i?i(t.join("")):t.join("")},_createBaseSVGMarkup:function(t,e){var i,r,n=(e=e||{}).noStyle,s=e.reviver,o=n?"":'style="'+this.getSvgStyles()+'" ',a=e.withShadow?'style="'+this.getSvgFilter()+'" ':"",h=this.clipPath,c=this.strokeUniform?'vector-effect="non-scaling-stroke" ':"",l=h&&h.absolutePositioned,u=this.stroke,f=this.fill,d=this.shadow,g=[],p=t.indexOf("COMMON_PARTS"),v=e.additionalTransform;return h&&(h.clipPathId="CLIPPATH_"+fabric.Object.__uid++,r='<clipPath id="'+h.clipPathId+'" >\n'+h.toClipPathSVG(s)+"</clipPath>\n"),l&&g.push("<g ",a,this.getSvgCommons()," >\n"),g.push("<g ",this.getSvgTransform(!1),l?"":a+this.getSvgCommons()," >\n"),i=[o,c,n?"":this.addPaintOrder()," ",v?'transform="'+v+'" ':""].join(""),t[p]=i,f&&f.toLive&&g.push(f.toSVG(this)),u&&u.toLive&&g.push(u.toSVG(this)),d&&g.push(d.toSVG(this)),h&&g.push(r),g.push(t.join("")),g.push("</g>\n"),l&&g.push("</g>\n"),s?s(g.join("")):g.join("")},addPaintOrder:function(){return"fill"!==this.paintFirst?' paint-order="'+this.paintFirst+'" ':""}})}(),function(){var n=fabric.util.object.extend,r="stateProperties";function s(e,t,i){var r={};i.forEach(function(t){r[t]=e[t]}),n(e[t],r,!0)}fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(t){var e="_"+(t=t||r);return Object.keys(this[e]).length<this[t].length||!function t(e,i,r){if(e===i)return!0;if(Array.isArray(e)){if(!Array.isArray(i)||e.length!==i.length)return!1;for(var n=0,s=e.length;n<s;n++)if(!t(e[n],i[n]))return!1;return!0}if(e&&"object"==typeof e){var o,a=Object.keys(e);if(!i||"object"!=typeof i||!r&&a.length!==Object.keys(i).length)return!1;for(n=0,s=a.length;n<s;n++)if("canvas"!==(o=a[n])&&!t(e[o],i[o]))return!1;return!0}}(this[e],this,!0)},saveState:function(t){var e=t&&t.propertySet||r,i="_"+e;return this[i]?(s(this,i,this[e]),t&&t.stateProperties&&s(this,i,t.stateProperties),this):this.setupState(t)},setupState:function(t){var e=(t=t||{}).propertySet||r;return this["_"+(t.propertySet=e)]={},this.saveState(t),this}})}(),function(){var h=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(t){if(!this.hasControls||this.group||!this.canvas||this.canvas._activeObject!==this)return!1;var e,i,r=t.x,n=t.y;for(var s in this.__corner=0,this.oCoords)if(this.isControlVisible(s)&&("mtr"!==s||this.hasRotatingPoint)&&(!this.get("lockUniScaling")||"mt"!==s&&"mr"!==s&&"mb"!==s&&"ml"!==s)&&(i=this._getImageLines(this.oCoords[s].corner),0!==(e=this._findCrossPoints({x:r,y:n},i))&&e%2==1))return this.__corner=s;return!1},_setCornerCoords:function(){var t,e,i=this.oCoords,r=h(45-this.angle),n=.707106*this.cornerSize,s=n*fabric.util.cos(r),o=n*fabric.util.sin(r);for(var a in i)t=i[a].x,e=i[a].y,i[a].corner={tl:{x:t-o,y:e-s},tr:{x:t+s,y:e-o},bl:{x:t-s,y:e+o},br:{x:t+o,y:e+s}}},drawSelectionBackground:function(t){if(!this.selectionBackgroundColor||this.canvas&&!this.canvas.interactive||this.canvas&&this.canvas._activeObject!==this)return this;t.save();var e=this.getCenterPoint(),i=this._calculateCurrentDimensions(),r=this.canvas.viewportTransform;return t.translate(e.x,e.y),t.scale(1/r[0],1/r[3]),t.rotate(h(this.angle)),t.fillStyle=this.selectionBackgroundColor,t.fillRect(-i.x/2,-i.y/2,i.x,i.y),t.restore(),this},drawBorders:function(t,e){e=e||{};var i=this._calculateCurrentDimensions(),r=1/this.borderScaleFactor,n=i.x+r,s=i.y+r,o=void 0!==e.hasRotatingPoint?e.hasRotatingPoint:this.hasRotatingPoint,a=void 0!==e.hasControls?e.hasControls:this.hasControls,h=void 0!==e.rotatingPointOffset?e.rotatingPointOffset:this.rotatingPointOffset;if(t.save(),t.strokeStyle=e.borderColor||this.borderColor,this._setLineDash(t,e.borderDashArray||this.borderDashArray,null),t.strokeRect(-n/2,-s/2,n,s),o&&this.isControlVisible("mtr")&&a){var c=-s/2;t.beginPath(),t.moveTo(0,c),t.lineTo(0,c-h),t.stroke()}return t.restore(),this},drawBordersInGroup:function(t,e,i){i=i||{};var r=this._getNonTransformedDimensions(),n=fabric.util.customTransformMatrix(e.scaleX,e.scaleY,e.skewX),s=fabric.util.transformPoint(r,n),o=1/this.borderScaleFactor,a=s.x+o,h=s.y+o;return t.save(),this._setLineDash(t,i.borderDashArray||this.borderDashArray,null),t.strokeStyle=i.borderColor||this.borderColor,t.strokeRect(-a/2,-h/2,a,h),t.restore(),this},drawControls:function(t,e){e=e||{};var i=this._calculateCurrentDimensions(),r=i.x,n=i.y,s=e.cornerSize||this.cornerSize,o=-(r+s)/2,a=-(n+s)/2,h=void 0!==e.transparentCorners?e.transparentCorners:this.transparentCorners,c=void 0!==e.hasRotatingPoint?e.hasRotatingPoint:this.hasRotatingPoint,l=h?"stroke":"fill";return t.save(),t.strokeStyle=t.fillStyle=e.cornerColor||this.cornerColor,this.transparentCorners||(t.strokeStyle=e.cornerStrokeColor||this.cornerStrokeColor),this._setLineDash(t,e.cornerDashArray||this.cornerDashArray,null),this._drawControl("tl",t,l,o,a,e),this._drawControl("tr",t,l,o+r,a,e),this._drawControl("bl",t,l,o,a+n,e),this._drawControl("br",t,l,o+r,a+n,e),this.get("lockUniScaling")||(this._drawControl("mt",t,l,o+r/2,a,e),this._drawControl("mb",t,l,o+r/2,a+n,e),this._drawControl("mr",t,l,o+r,a+n/2,e),this._drawControl("ml",t,l,o,a+n/2,e)),c&&this._drawControl("mtr",t,l,o+r/2,a-this.rotatingPointOffset,e),t.restore(),this},_drawControl:function(t,e,i,r,n,s){if(s=s||{},this.isControlVisible(t)){var o=this.cornerSize,a=!this.transparentCorners&&this.cornerStrokeColor;switch(s.cornerStyle||this.cornerStyle){case"circle":e.beginPath(),e.arc(r+o/2,n+o/2,o/2,0,2*Math.PI,!1),e[i](),a&&e.stroke();break;default:this.transparentCorners||e.clearRect(r,n,o,o),e[i+"Rect"](r,n,o,o),a&&e.strokeRect(r,n,o,o)}}},isControlVisible:function(t){return this._getControlsVisibility()[t]},setControlVisible:function(t,e){return this._getControlsVisibility()[t]=e,this},setControlsVisibility:function(t){for(var e in t||(t={}),t)this.setControlVisible(e,t[e]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility},onDeselect:function(){},onSelect:function(){}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(e,t){var i=function(){},r=(t=t||{}).onComplete||i,n=t.onChange||i,s=this;return fabric.util.animate({startValue:e.left,endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(t){e.set("left",t),s.requestRenderAll(),n()},onComplete:function(){e.setCoords(),r()}}),this},fxCenterObjectV:function(e,t){var i=function(){},r=(t=t||{}).onComplete||i,n=t.onChange||i,s=this;return fabric.util.animate({startValue:e.top,endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(t){e.set("top",t),s.requestRenderAll(),n()},onComplete:function(){e.setCoords(),r()}}),this},fxRemove:function(e,t){var i=function(){},r=(t=t||{}).onComplete||i,n=t.onChange||i,s=this;return fabric.util.animate({startValue:e.opacity,endValue:0,duration:this.FX_DURATION,onChange:function(t){e.set("opacity",t),s.requestRenderAll(),n()},onComplete:function(){s.remove(e),r()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&"object"==typeof arguments[0]){var t,e,i=[];for(t in arguments[0])i.push(t);for(var r=0,n=i.length;r<n;r++)t=i[r],e=r!==n-1,this._animate(t,arguments[0][t],arguments[1],e)}else this._animate.apply(this,arguments);return this},_animate:function(r,t,n,s){var o,a=this;t=t.toString(),n=n?fabric.util.object.clone(n):{},~r.indexOf(".")&&(o=r.split("."));var e=o?this.get(o[0])[o[1]]:this.get(r);"from"in n||(n.from=e),t=~t.indexOf("=")?e+parseFloat(t.replace("=","")):parseFloat(t),fabric.util.animate({startValue:n.from,endValue:t,byValue:n.by,easing:n.easing,duration:n.duration,abort:n.abort&&function(){return n.abort.call(a)},onChange:function(t,e,i){o?a[o[0]][o[1]]=t:a.set(r,t),s||n.onChange&&n.onChange(t,e,i)},onComplete:function(t,e,i){s||(a.setCoords(),n.onComplete&&n.onComplete(t,e,i))}})}}),function(t){"use strict";var s=t.fabric||(t.fabric={}),o=s.util.object.extend,r=s.util.object.clone,i={x1:1,x2:1,y1:1,y2:1},n=s.StaticCanvas.supports("setLineDash");function e(t,e){var i=t.origin,r=t.axis1,n=t.axis2,s=t.dimension,o=e.nearest,a=e.center,h=e.farthest;return function(){switch(this.get(i)){case o:return Math.min(this.get(r),this.get(n));case a:return Math.min(this.get(r),this.get(n))+.5*this.get(s);case h:return Math.max(this.get(r),this.get(n))}}}s.Line?s.warn("fabric.Line is already defined"):(s.Line=s.util.createClass(s.Object,{type:"line",x1:0,y1:0,x2:0,y2:0,cacheProperties:s.Object.prototype.cacheProperties.concat("x1","x2","y1","y2"),initialize:function(t,e){t||(t=[0,0,0,0]),this.callSuper("initialize",e),this.set("x1",t[0]),this.set("y1",t[1]),this.set("x2",t[2]),this.set("y2",t[3]),this._setWidthHeight(e)},_setWidthHeight:function(t){t||(t={}),this.width=Math.abs(this.x2-this.x1),this.height=Math.abs(this.y2-this.y1),this.left="left"in t?t.left:this._getLeftToOriginX(),this.top="top"in t?t.top:this._getTopToOriginY()},_set:function(t,e){return this.callSuper("_set",t,e),void 0!==i[t]&&this._setWidthHeight(),this},_getLeftToOriginX:e({origin:"originX",axis1:"x1",axis2:"x2",dimension:"width"},{nearest:"left",center:"center",farthest:"right"}),_getTopToOriginY:e({origin:"originY",axis1:"y1",axis2:"y2",dimension:"height"},{nearest:"top",center:"center",farthest:"bottom"}),_render:function(t){if(t.beginPath(),!this.strokeDashArray||this.strokeDashArray&&n){var e=this.calcLinePoints();t.moveTo(e.x1,e.y1),t.lineTo(e.x2,e.y2)}t.lineWidth=this.strokeWidth;var i=t.strokeStyle;t.strokeStyle=this.stroke||t.fillStyle,this.stroke&&this._renderStroke(t),t.strokeStyle=i},_renderDashedStroke:function(t){var e=this.calcLinePoints();t.beginPath(),s.util.drawDashedLine(t,e.x1,e.y1,e.x2,e.y2,this.strokeDashArray),t.closePath()},_findCenterFromElement:function(){return{x:(this.x1+this.x2)/2,y:(this.y1+this.y2)/2}},toObject:function(t){return o(this.callSuper("toObject",t),this.calcLinePoints())},_getNonTransformedDimensions:function(){var t=this.callSuper("_getNonTransformedDimensions");return"butt"===this.strokeLineCap&&(0===this.width&&(t.y-=this.strokeWidth),0===this.height&&(t.x-=this.strokeWidth)),t},calcLinePoints:function(){var t=this.x1<=this.x2?-1:1,e=this.y1<=this.y2?-1:1,i=t*this.width*.5,r=e*this.height*.5;return{x1:i,x2:t*this.width*-.5,y1:r,y2:e*this.height*-.5}},_toSVG:function(){var t=this.calcLinePoints();return["<line ","COMMON_PARTS",'x1="',t.x1,'" y1="',t.y1,'" x2="',t.x2,'" y2="',t.y2,'" />\n']}}),s.Line.ATTRIBUTE_NAMES=s.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),s.Line.fromElement=function(t,e,i){i=i||{};var r=s.parseAttributes(t,s.Line.ATTRIBUTE_NAMES),n=[r.x1||0,r.y1||0,r.x2||0,r.y2||0];e(new s.Line(n,o(r,i)))},s.Line.fromObject=function(t,e){var i=r(t,!0);i.points=[t.x1,t.y1,t.x2,t.y2],s.Object._fromObject("Line",i,function(t){delete t.points,e&&e(t)},"points")})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var a=t.fabric||(t.fabric={}),h=Math.PI;a.Circle?a.warn("fabric.Circle is already defined."):(a.Circle=a.util.createClass(a.Object,{type:"circle",radius:0,startAngle:0,endAngle:2*h,cacheProperties:a.Object.prototype.cacheProperties.concat("radius","startAngle","endAngle"),_set:function(t,e){return this.callSuper("_set",t,e),"radius"===t&&this.setRadius(e),this},toObject:function(t){return this.callSuper("toObject",["radius","startAngle","endAngle"].concat(t))},_toSVG:function(){var t,e=(this.endAngle-this.startAngle)%(2*h);if(0===e)t=["<circle ","COMMON_PARTS",'cx="0" cy="0" ','r="',this.radius,'" />\n'];else{var i=a.util.cos(this.startAngle)*this.radius,r=a.util.sin(this.startAngle)*this.radius,n=a.util.cos(this.endAngle)*this.radius,s=a.util.sin(this.endAngle)*this.radius,o=h<e?"1":"0";t=['<path d="M '+i+" "+r," A "+this.radius+" "+this.radius," 0 ",+o+" 1"," "+n+" "+s,'" ',"COMMON_PARTS"," />\n"]}return t},_render:function(t){t.beginPath(),t.arc(0,0,this.radius,this.startAngle,this.endAngle,!1),this._renderPaintInOrder(t)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(t){return this.radius=t,this.set("width",2*t).set("height",2*t)}}),a.Circle.ATTRIBUTE_NAMES=a.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),a.Circle.fromElement=function(t,e){var i,r=a.parseAttributes(t,a.Circle.ATTRIBUTE_NAMES);if(!("radius"in(i=r)&&0<=i.radius))throw new Error("value of `r` attribute is required and can not be negative");r.left=(r.left||0)-r.radius,r.top=(r.top||0)-r.radius,e(new a.Circle(r))},a.Circle.fromObject=function(t,e){return a.Object._fromObject("Circle",t,e)})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var r=t.fabric||(t.fabric={});r.Triangle?r.warn("fabric.Triangle is already defined"):(r.Triangle=r.util.createClass(r.Object,{type:"triangle",width:100,height:100,_render:function(t){var e=this.width/2,i=this.height/2;t.beginPath(),t.moveTo(-e,i),t.lineTo(0,-i),t.lineTo(e,i),t.closePath(),this._renderPaintInOrder(t)},_renderDashedStroke:function(t){var e=this.width/2,i=this.height/2;t.beginPath(),r.util.drawDashedLine(t,-e,i,0,-i,this.strokeDashArray),r.util.drawDashedLine(t,0,-i,e,i,this.strokeDashArray),r.util.drawDashedLine(t,e,i,-e,i,this.strokeDashArray),t.closePath()},_toSVG:function(){var t=this.width/2,e=this.height/2;return["<polygon ","COMMON_PARTS",'points="',[-t+" "+e,"0 "+-e,t+" "+e].join(","),'" />']}}),r.Triangle.fromObject=function(t,e){return r.Object._fromObject("Triangle",t,e)})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var r=t.fabric||(t.fabric={}),e=2*Math.PI;r.Ellipse?r.warn("fabric.Ellipse is already defined."):(r.Ellipse=r.util.createClass(r.Object,{type:"ellipse",rx:0,ry:0,cacheProperties:r.Object.prototype.cacheProperties.concat("rx","ry"),initialize:function(t){this.callSuper("initialize",t),this.set("rx",t&&t.rx||0),this.set("ry",t&&t.ry||0)},_set:function(t,e){switch(this.callSuper("_set",t,e),t){case"rx":this.rx=e,this.set("width",2*e);break;case"ry":this.ry=e,this.set("height",2*e)}return this},getRx:function(){return this.get("rx")*this.get("scaleX")},getRy:function(){return this.get("ry")*this.get("scaleY")},toObject:function(t){return this.callSuper("toObject",["rx","ry"].concat(t))},_toSVG:function(){return["<ellipse ","COMMON_PARTS",'cx="0" cy="0" ','rx="',this.rx,'" ry="',this.ry,'" />\n']},_render:function(t){t.beginPath(),t.save(),t.transform(1,0,0,this.ry/this.rx,0,0),t.arc(0,0,this.rx,0,e,!1),t.restore(),this._renderPaintInOrder(t)}}),r.Ellipse.ATTRIBUTE_NAMES=r.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),r.Ellipse.fromElement=function(t,e){var i=r.parseAttributes(t,r.Ellipse.ATTRIBUTE_NAMES);i.left=(i.left||0)-i.rx,i.top=(i.top||0)-i.ry,e(new r.Ellipse(i))},r.Ellipse.fromObject=function(t,e){return r.Object._fromObject("Ellipse",t,e)})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var s=t.fabric||(t.fabric={}),o=s.util.object.extend;s.Rect?s.warn("fabric.Rect is already defined"):(s.Rect=s.util.createClass(s.Object,{stateProperties:s.Object.prototype.stateProperties.concat("rx","ry"),type:"rect",rx:0,ry:0,cacheProperties:s.Object.prototype.cacheProperties.concat("rx","ry"),initialize:function(t){this.callSuper("initialize",t),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(t){var e=this.rx?Math.min(this.rx,this.width/2):0,i=this.ry?Math.min(this.ry,this.height/2):0,r=this.width,n=this.height,s=-this.width/2,o=-this.height/2,a=0!==e||0!==i,h=.4477152502;t.beginPath(),t.moveTo(s+e,o),t.lineTo(s+r-e,o),a&&t.bezierCurveTo(s+r-h*e,o,s+r,o+h*i,s+r,o+i),t.lineTo(s+r,o+n-i),a&&t.bezierCurveTo(s+r,o+n-h*i,s+r-h*e,o+n,s+r-e,o+n),t.lineTo(s+e,o+n),a&&t.bezierCurveTo(s+h*e,o+n,s,o+n-h*i,s,o+n-i),t.lineTo(s,o+i),a&&t.bezierCurveTo(s,o+h*i,s+h*e,o,s+e,o),t.closePath(),this._renderPaintInOrder(t)},_renderDashedStroke:function(t){var e=-this.width/2,i=-this.height/2,r=this.width,n=this.height;t.beginPath(),s.util.drawDashedLine(t,e,i,e+r,i,this.strokeDashArray),s.util.drawDashedLine(t,e+r,i,e+r,i+n,this.strokeDashArray),s.util.drawDashedLine(t,e+r,i+n,e,i+n,this.strokeDashArray),s.util.drawDashedLine(t,e,i+n,e,i,this.strokeDashArray),t.closePath()},toObject:function(t){return this.callSuper("toObject",["rx","ry"].concat(t))},_toSVG:function(){return["<rect ","COMMON_PARTS",'x="',-this.width/2,'" y="',-this.height/2,'" rx="',this.rx,'" ry="',this.ry,'" width="',this.width,'" height="',this.height,'" />\n']}}),s.Rect.ATTRIBUTE_NAMES=s.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),s.Rect.fromElement=function(t,e,i){if(!t)return e(null);i=i||{};var r=s.parseAttributes(t,s.Rect.ATTRIBUTE_NAMES);r.left=r.left||0,r.top=r.top||0,r.height=r.height||0,r.width=r.width||0;var n=new s.Rect(o(i?s.util.object.clone(i):{},r));n.visible=n.visible&&0<n.width&&0<n.height,e(n)},s.Rect.fromObject=function(t,e){return s.Object._fromObject("Rect",t,e)})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var o=t.fabric||(t.fabric={}),a=o.util.object.extend,r=o.util.array.min,n=o.util.array.max,h=o.util.toFixed;o.Polyline?o.warn("fabric.Polyline is already defined"):(o.Polyline=o.util.createClass(o.Object,{type:"polyline",points:null,cacheProperties:o.Object.prototype.cacheProperties.concat("points"),initialize:function(t,e){e=e||{},this.points=t||[],this.callSuper("initialize",e),this._setPositionDimensions(e)},_setPositionDimensions:function(t){var e,i=this._calcDimensions(t);this.width=i.width,this.height=i.height,t.fromSVG||(e=this.translateToGivenOrigin({x:i.left-this.strokeWidth/2,y:i.top-this.strokeWidth/2},"left","top",this.originX,this.originY)),void 0===t.left&&(this.left=t.fromSVG?i.left:e.x),void 0===t.top&&(this.top=t.fromSVG?i.top:e.y),this.pathOffset={x:i.left+this.width/2,y:i.top+this.height/2}},_calcDimensions:function(){var t=this.points,e=r(t,"x")||0,i=r(t,"y")||0;return{left:e,top:i,width:(n(t,"x")||0)-e,height:(n(t,"y")||0)-i}},toObject:function(t){return a(this.callSuper("toObject",t),{points:this.points.concat()})},_toSVG:function(){for(var t=[],e=this.pathOffset.x,i=this.pathOffset.y,r=o.Object.NUM_FRACTION_DIGITS,n=0,s=this.points.length;n<s;n++)t.push(h(this.points[n].x-e,r),",",h(this.points[n].y-i,r)," ");return["<"+this.type+" ","COMMON_PARTS",'points="',t.join(""),'" />\n']},commonRender:function(t){var e,i=this.points.length,r=this.pathOffset.x,n=this.pathOffset.y;if(!i||isNaN(this.points[i-1].y))return!1;t.beginPath(),t.moveTo(this.points[0].x-r,this.points[0].y-n);for(var s=0;s<i;s++)e=this.points[s],t.lineTo(e.x-r,e.y-n);return!0},_render:function(t){this.commonRender(t)&&this._renderPaintInOrder(t)},_renderDashedStroke:function(t){var e,i;t.beginPath();for(var r=0,n=this.points.length;r<n;r++)e=this.points[r],i=this.points[r+1]||e,o.util.drawDashedLine(t,e.x,e.y,i.x,i.y,this.strokeDashArray)},complexity:function(){return this.get("points").length}}),o.Polyline.ATTRIBUTE_NAMES=o.SHARED_ATTRIBUTES.concat(),o.Polyline.fromElementGenerator=function(s){return function(t,e,i){if(!t)return e(null);i||(i={});var r=o.parsePointsAttribute(t.getAttribute("points")),n=o.parseAttributes(t,o[s].ATTRIBUTE_NAMES);n.fromSVG=!0,e(new o[s](r,a(n,i)))}},o.Polyline.fromElement=o.Polyline.fromElementGenerator("Polyline"),o.Polyline.fromObject=function(t,e){return o.Object._fromObject("Polyline",t,e,"points")})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var i=t.fabric||(t.fabric={});i.Polygon?i.warn("fabric.Polygon is already defined"):(i.Polygon=i.util.createClass(i.Polyline,{type:"polygon",_render:function(t){this.commonRender(t)&&(t.closePath(),this._renderPaintInOrder(t))},_renderDashedStroke:function(t){this.callSuper("_renderDashedStroke",t),t.closePath()}}),i.Polygon.ATTRIBUTE_NAMES=i.SHARED_ATTRIBUTES.concat(),i.Polygon.fromElement=i.Polyline.fromElementGenerator("Polygon"),i.Polygon.fromObject=function(t,e){return i.Object._fromObject("Polygon",t,e,"points")})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var m=t.fabric||(t.fabric={}),b=m.util.array.min,_=m.util.array.max,n=m.util.object.extend,r=Object.prototype.toString,p=m.util.drawArc,e=m.util.toFixed,y={m:2,l:2,h:1,v:1,c:6,s:4,q:4,t:2,a:7},x={m:"l",M:"L"};m.Path?m.warn("fabric.Path is already defined"):(m.Path=m.util.createClass(m.Object,{type:"path",path:null,cacheProperties:m.Object.prototype.cacheProperties.concat("path","fillRule"),stateProperties:m.Object.prototype.stateProperties.concat("path"),initialize:function(t,e){e=e||{},this.callSuper("initialize",e),t||(t=[]);var i="[object Array]"===r.call(t);this.path=i?t:t.match&&t.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi),this.path&&(i||(this.path=this._parsePath()),m.Polyline.prototype._setPositionDimensions.call(this,e))},_renderPathCommands:function(t){var e,i,r,n=null,s=0,o=0,a=0,h=0,c=0,l=0,u=-this.pathOffset.x,f=-this.pathOffset.y;t.beginPath();for(var d=0,g=this.path.length;d<g;++d){switch((e=this.path[d])[0]){case"l":a+=e[1],h+=e[2],t.lineTo(a+u,h+f);break;case"L":a=e[1],h=e[2],t.lineTo(a+u,h+f);break;case"h":a+=e[1],t.lineTo(a+u,h+f);break;case"H":a=e[1],t.lineTo(a+u,h+f);break;case"v":h+=e[1],t.lineTo(a+u,h+f);break;case"V":h=e[1],t.lineTo(a+u,h+f);break;case"m":s=a+=e[1],o=h+=e[2],t.moveTo(a+u,h+f);break;case"M":s=a=e[1],o=h=e[2],t.moveTo(a+u,h+f);break;case"c":i=a+e[5],r=h+e[6],c=a+e[3],l=h+e[4],t.bezierCurveTo(a+e[1]+u,h+e[2]+f,c+u,l+f,i+u,r+f),a=i,h=r;break;case"C":a=e[5],h=e[6],c=e[3],l=e[4],t.bezierCurveTo(e[1]+u,e[2]+f,c+u,l+f,a+u,h+f);break;case"s":i=a+e[3],r=h+e[4],null===n[0].match(/[CcSs]/)?(c=a,l=h):(c=2*a-c,l=2*h-l),t.bezierCurveTo(c+u,l+f,a+e[1]+u,h+e[2]+f,i+u,r+f),c=a+e[1],l=h+e[2],a=i,h=r;break;case"S":i=e[3],r=e[4],null===n[0].match(/[CcSs]/)?(c=a,l=h):(c=2*a-c,l=2*h-l),t.bezierCurveTo(c+u,l+f,e[1]+u,e[2]+f,i+u,r+f),a=i,h=r,c=e[1],l=e[2];break;case"q":i=a+e[3],r=h+e[4],c=a+e[1],l=h+e[2],t.quadraticCurveTo(c+u,l+f,i+u,r+f),a=i,h=r;break;case"Q":i=e[3],r=e[4],t.quadraticCurveTo(e[1]+u,e[2]+f,i+u,r+f),a=i,h=r,c=e[1],l=e[2];break;case"t":i=a+e[1],r=h+e[2],null===n[0].match(/[QqTt]/)?(c=a,l=h):(c=2*a-c,l=2*h-l),t.quadraticCurveTo(c+u,l+f,i+u,r+f),a=i,h=r;break;case"T":i=e[1],r=e[2],null===n[0].match(/[QqTt]/)?(c=a,l=h):(c=2*a-c,l=2*h-l),t.quadraticCurveTo(c+u,l+f,i+u,r+f),a=i,h=r;break;case"a":p(t,a+u,h+f,[e[1],e[2],e[3],e[4],e[5],e[6]+a+u,e[7]+h+f]),a+=e[6],h+=e[7];break;case"A":p(t,a+u,h+f,[e[1],e[2],e[3],e[4],e[5],e[6]+u,e[7]+f]),a=e[6],h=e[7];break;case"z":case"Z":a=s,h=o,t.closePath()}n=e}},_render:function(t){this._renderPathCommands(t),this._renderPaintInOrder(t)},toString:function(){return"#<fabric.Path ("+this.complexity()+'): { "top": '+this.top+', "left": '+this.left+" }>"},toObject:function(t){return n(this.callSuper("toObject",t),{path:this.path.map(function(t){return t.slice()})})},toDatalessObject:function(t){var e=this.toObject(["sourcePath"].concat(t));return e.sourcePath&&delete e.path,e},_toSVG:function(){return["<path ","COMMON_PARTS",'d="',this.path.map(function(t){return t.join(" ")}).join(" "),'" stroke-linecap="round" ',"/>\n"]},_getOffsetTransform:function(){var t=m.Object.NUM_FRACTION_DIGITS;return" translate("+e(-this.pathOffset.x,t)+", "+e(-this.pathOffset.y,t)+")"},toClipPathSVG:function(t){var e=this._getOffsetTransform();return"\t"+this._createBaseClipPathSVGMarkup(this._toSVG(),{reviver:t,additionalTransform:e})},toSVG:function(t){var e=this._getOffsetTransform();return this._createBaseSVGMarkup(this._toSVG(),{reviver:t,additionalTransform:e})},complexity:function(){return this.path.length},_parsePath:function(){for(var t,e,i,r,n,s=[],o=[],a=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,h=0,c=this.path.length;h<c;h++){for(r=(t=this.path[h]).slice(1).trim(),o.length=0;i=a.exec(r);)o.push(i[0]);n=[t.charAt(0)];for(var l=0,u=o.length;l<u;l++)e=parseFloat(o[l]),isNaN(e)||n.push(e);var f=n[0],d=y[f.toLowerCase()],g=x[f]||f;if(n.length-1>d)for(var p=1,v=n.length;p<v;p+=d)s.push([f].concat(n.slice(p,p+d))),f=g;else s.push(n)}return s},_calcDimensions:function(){for(var t,e,i,r,n=[],s=[],o=null,a=0,h=0,c=0,l=0,u=0,f=0,d=0,g=this.path.length;d<g;++d){switch((t=this.path[d])[0]){case"l":c+=t[1],l+=t[2],r=[];break;case"L":c=t[1],l=t[2],r=[];break;case"h":c+=t[1],r=[];break;case"H":c=t[1],r=[];break;case"v":l+=t[1],r=[];break;case"V":l=t[1],r=[];break;case"m":a=c+=t[1],h=l+=t[2],r=[];break;case"M":a=c=t[1],h=l=t[2],r=[];break;case"c":e=c+t[5],i=l+t[6],u=c+t[3],f=l+t[4],r=m.util.getBoundsOfCurve(c,l,c+t[1],l+t[2],u,f,e,i),c=e,l=i;break;case"C":u=t[3],f=t[4],r=m.util.getBoundsOfCurve(c,l,t[1],t[2],u,f,t[5],t[6]),c=t[5],l=t[6];break;case"s":e=c+t[3],i=l+t[4],null===o[0].match(/[CcSs]/)?(u=c,f=l):(u=2*c-u,f=2*l-f),r=m.util.getBoundsOfCurve(c,l,u,f,c+t[1],l+t[2],e,i),u=c+t[1],f=l+t[2],c=e,l=i;break;case"S":e=t[3],i=t[4],null===o[0].match(/[CcSs]/)?(u=c,f=l):(u=2*c-u,f=2*l-f),r=m.util.getBoundsOfCurve(c,l,u,f,t[1],t[2],e,i),c=e,l=i,u=t[1],f=t[2];break;case"q":e=c+t[3],i=l+t[4],u=c+t[1],f=l+t[2],r=m.util.getBoundsOfCurve(c,l,u,f,u,f,e,i),c=e,l=i;break;case"Q":u=t[1],f=t[2],r=m.util.getBoundsOfCurve(c,l,u,f,u,f,t[3],t[4]),c=t[3],l=t[4];break;case"t":e=c+t[1],i=l+t[2],null===o[0].match(/[QqTt]/)?(u=c,f=l):(u=2*c-u,f=2*l-f),r=m.util.getBoundsOfCurve(c,l,u,f,u,f,e,i),c=e,l=i;break;case"T":e=t[1],i=t[2],null===o[0].match(/[QqTt]/)?(u=c,f=l):(u=2*c-u,f=2*l-f),r=m.util.getBoundsOfCurve(c,l,u,f,u,f,e,i),c=e,l=i;break;case"a":r=m.util.getBoundsOfArc(c,l,t[1],t[2],t[3],t[4],t[5],t[6]+c,t[7]+l),c+=t[6],l+=t[7];break;case"A":r=m.util.getBoundsOfArc(c,l,t[1],t[2],t[3],t[4],t[5],t[6],t[7]),c=t[6],l=t[7];break;case"z":case"Z":c=a,l=h}o=t,r.forEach(function(t){n.push(t.x),s.push(t.y)}),n.push(c),s.push(l)}var p=b(n)||0,v=b(s)||0;return{left:p,top:v,width:(_(n)||0)-p,height:(_(s)||0)-v}}}),m.Path.fromObject=function(i,r){if("string"==typeof i.sourcePath){var t=i.sourcePath;m.loadSVGFromURL(t,function(t){var e=t[0];e.setOptions(i),r&&r(e)})}else m.Object._fromObject("Path",i,r,"path")},m.Path.ATTRIBUTE_NAMES=m.SHARED_ATTRIBUTES.concat(["d"]),m.Path.fromElement=function(t,e,i){var r=m.parseAttributes(t,m.Path.ATTRIBUTE_NAMES);r.fromSVG=!0,e(new m.Path(r.d,n(r,i)))})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var c=t.fabric||(t.fabric={}),l=c.util.array.min,u=c.util.array.max;c.Group||(c.Group=c.util.createClass(c.Object,c.Collection,{type:"group",strokeWidth:0,subTargetCheck:!1,cacheProperties:[],useSetOnGroup:!1,initialize:function(t,e,i){e=e||{},this._objects=[],i&&this.callSuper("initialize",e),this._objects=t||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;if(i)this._updateObjectsACoords();else{var n=e&&e.centerPoint;void 0!==e.originX&&(this.originX=e.originX),void 0!==e.originY&&(this.originY=e.originY),n||this._calcBounds(),this._updateObjectsCoords(n),delete e.centerPoint,this.callSuper("initialize",e)}this.setCoords()},_updateObjectsACoords:function(){for(var t=this._objects.length;t--;)this._objects[t].setCoords(!0,!0)},_updateObjectsCoords:function(t){t=t||this.getCenterPoint();for(var e=this._objects.length;e--;)this._updateObjectCoords(this._objects[e],t)},_updateObjectCoords:function(t,e){var i=t.left,r=t.top;t.set({left:i-e.x,top:r-e.y}),t.group=this,t.setCoords(!0,!0)},toString:function(){return"#<fabric.Group: ("+this.complexity()+")>"},addWithUpdate:function(t){return this._restoreObjectsState(),c.util.resetObjectTransform(this),t&&(this._objects.push(t),t.group=this,t._set("canvas",this.canvas)),this._calcBounds(),this._updateObjectsCoords(),this.setCoords(),this.dirty=!0,this},removeWithUpdate:function(t){return this._restoreObjectsState(),c.util.resetObjectTransform(this),this.remove(t),this._calcBounds(),this._updateObjectsCoords(),this.setCoords(),this.dirty=!0,this},_onObjectAdded:function(t){this.dirty=!0,t.group=this,t._set("canvas",this.canvas)},_onObjectRemoved:function(t){this.dirty=!0,delete t.group},_set:function(t,e){var i=this._objects.length;if(this.useSetOnGroup)for(;i--;)this._objects[i].setOnGroup(t,e);if("canvas"===t)for(;i--;)this._objects[i]._set(t,e);c.Object.prototype._set.call(this,t,e)},toObject:function(r){var n=this.includeDefaultValues,t=this._objects.map(function(t){var e=t.includeDefaultValues;t.includeDefaultValues=n;var i=t.toObject(r);return t.includeDefaultValues=e,i}),e=c.Object.prototype.toObject.call(this,r);return e.objects=t,e},toDatalessObject:function(r){var t,e=this.sourcePath;if(e)t=e;else{var n=this.includeDefaultValues;t=this._objects.map(function(t){var e=t.includeDefaultValues;t.includeDefaultValues=n;var i=t.toDatalessObject(r);return t.includeDefaultValues=e,i})}var i=c.Object.prototype.toDatalessObject.call(this,r);return i.objects=t,i},render:function(t){this._transformDone=!0,this.callSuper("render",t),this._transformDone=!1},shouldCache:function(){var t=c.Object.prototype.shouldCache.call(this);if(t)for(var e=0,i=this._objects.length;e<i;e++)if(this._objects[e].willDrawShadow())return this.ownCaching=!1;return t},willDrawShadow:function(){if(this.shadow)return c.Object.prototype.willDrawShadow.call(this);for(var t=0,e=this._objects.length;t<e;t++)if(this._objects[t].willDrawShadow())return!0;return!1},isOnACache:function(){return this.ownCaching||this.group&&this.group.isOnACache()},drawObject:function(t){for(var e=0,i=this._objects.length;e<i;e++)this._objects[e].render(t);this._drawClipPath(t)},isCacheDirty:function(t){if(this.callSuper("isCacheDirty",t))return!0;if(!this.statefullCache)return!1;for(var e=0,i=this._objects.length;e<i;e++)if(this._objects[e].isCacheDirty(!0)){if(this._cacheCanvas){var r=this.cacheWidth/this.zoomX,n=this.cacheHeight/this.zoomY;this._cacheContext.clearRect(-r/2,-n/2,r,n)}return!0}return!1},_restoreObjectsState:function(){return this._objects.forEach(this._restoreObjectState,this),this},realizeTransform:function(t){var e=t.calcTransformMatrix(),i=c.util.qrDecompose(e),r=new c.Point(i.translateX,i.translateY);return t.flipX=!1,t.flipY=!1,t.set("scaleX",i.scaleX),t.set("scaleY",i.scaleY),t.skewX=i.skewX,t.skewY=i.skewY,t.angle=i.angle,t.setPositionByOrigin(r,"center","center"),t},_restoreObjectState:function(t){return this.realizeTransform(t),t.setCoords(),delete t.group,this},destroy:function(){return this._objects.forEach(function(t){t.set("dirty",!0)}),this._restoreObjectsState()},toActiveSelection:function(){if(this.canvas){var t=this._objects,e=this.canvas;this._objects=[];var i=this.toObject();delete i.objects;var r=new c.ActiveSelection([]);return r.set(i),r.type="activeSelection",e.remove(this),t.forEach(function(t){t.group=r,t.dirty=!0,e.add(t)}),r.canvas=e,r._objects=t,(e._activeObject=r).setCoords(),r}},ungroupOnCanvas:function(){return this._restoreObjectsState()},setObjectsCoords:function(){return this.forEachObject(function(t){t.setCoords(!0,!0)}),this},_calcBounds:function(t){for(var e,i,r,n=[],s=[],o=["tr","br","bl","tl"],a=0,h=this._objects.length,c=o.length;a<h;++a)for((e=this._objects[a]).setCoords(!0),r=0;r<c;r++)i=o[r],n.push(e.oCoords[i].x),s.push(e.oCoords[i].y);this._getBounds(n,s,t)},_getBounds:function(t,e,i){var r=new c.Point(l(t),l(e)),n=new c.Point(u(t),u(e)),s=r.y||0,o=r.x||0,a=n.x-r.x||0,h=n.y-r.y||0;this.width=a,this.height=h,i||this.setPositionByOrigin({x:o,y:s},"left","top")},_toSVG:function(t){for(var e=["<g ","COMMON_PARTS"," >\n"],i=0,r=this._objects.length;i<r;i++)e.push("\t\t",this._objects[i].toSVG(t));return e.push("</g>\n"),e},getSvgStyles:function(){var t=void 0!==this.opacity&&1!==this.opacity?"opacity: "+this.opacity+";":"",e=this.visible?"":" visibility: hidden;";return[t,this.getSvgFilter(),e].join("")},toClipPathSVG:function(t){for(var e=[],i=0,r=this._objects.length;i<r;i++)e.push("\t",this._objects[i].toClipPathSVG(t));return this._createBaseClipPathSVGMarkup(e,{reviver:t})}}),c.Group.fromObject=function(r,n){c.util.enlivenObjects(r.objects,function(i){c.util.enlivenObjects([r.clipPath],function(t){var e=c.util.object.clone(r,!0);e.clipPath=t[0],delete e.objects,n&&n(new c.Group(i,e,!0))})})})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var n=t.fabric||(t.fabric={});n.ActiveSelection||(n.ActiveSelection=n.util.createClass(n.Group,{type:"activeSelection",initialize:function(t,e){e=e||{},this._objects=t||[];for(var i=this._objects.length;i--;)this._objects[i].group=this;e.originX&&(this.originX=e.originX),e.originY&&(this.originY=e.originY),this._calcBounds(),this._updateObjectsCoords(),n.Object.prototype.initialize.call(this,e),this.setCoords()},toGroup:function(){var t=this._objects.concat();this._objects=[];var e=n.Object.prototype.toObject.call(this),i=new n.Group([]);if(delete e.type,i.set(e),t.forEach(function(t){t.canvas.remove(t),t.group=i}),i._objects=t,!this.canvas)return i;var r=this.canvas;return r.add(i),(r._activeObject=i).setCoords(),i},onDeselect:function(){return this.destroy(),!1},toString:function(){return"#<fabric.ActiveSelection: ("+this.complexity()+")>"},shouldCache:function(){return!1},isOnACache:function(){return!1},_renderControls:function(t,e,i){t.save(),t.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,this.callSuper("_renderControls",t,e),void 0===(i=i||{}).hasControls&&(i.hasControls=!1),void 0===i.hasRotatingPoint&&(i.hasRotatingPoint=!1),i.forActiveSelection=!0;for(var r=0,n=this._objects.length;r<n;r++)this._objects[r]._renderControls(t,i);t.restore()}}),n.ActiveSelection.fromObject=function(e,i){n.util.enlivenObjects(e.objects,function(t){delete e.objects,i&&i(new n.ActiveSelection(t,e,!0))})})}("undefined"!=typeof exports?exports:this),function(t){"use strict";var n=fabric.util.object.extend;t.fabric||(t.fabric={}),t.fabric.Image?fabric.warn("fabric.Image is already defined."):(fabric.Image=fabric.util.createClass(fabric.Object,{type:"image",crossOrigin:"",strokeWidth:0,srcFromAttribute:!1,_lastScaleX:1,_lastScaleY:1,_filterScalingX:1,_filterScalingY:1,minimumScaleTrigger:.5,stateProperties:fabric.Object.prototype.stateProperties.concat("cropX","cropY"),cacheKey:"",cropX:0,cropY:0,initialize:function(t,e){e||(e={}),this.filters=[],this.cacheKey="texture"+fabric.Object.__uid++,this.callSuper("initialize",e),this._initElement(t,e)},getElement:function(){return this._element||{}},setElement:function(t,e){return this.removeTexture(this.cacheKey),this.removeTexture(this.cacheKey+"_filtered"),this._element=t,this._originalElement=t,this._initConfig(e),0!==this.filters.length&&this.applyFilters(),this.resizeFilter&&this.applyResizeFilters(),this},removeTexture:function(t){var e=fabric.filterBackend;e&&e.evictCachesForKey&&e.evictCachesForKey(t)},dispose:function(){this.removeTexture(this.cacheKey),this.removeTexture(this.cacheKey+"_filtered"),this._cacheContext=void 0,["_originalElement","_element","_filteredEl","_cacheCanvas"].forEach(function(t){fabric.util.cleanUpJsdomNode(this[t]),this[t]=void 0}.bind(this))},setCrossOrigin:function(t){return this.crossOrigin=t,this._element.crossOrigin=t,this},getOriginalSize:function(){var t=this.getElement();return{width:t.naturalWidth||t.width,height:t.naturalHeight||t.height}},_stroke:function(t){if(this.stroke&&0!==this.strokeWidth){var e=this.width/2,i=this.height/2;t.beginPath(),t.moveTo(-e,-i),t.lineTo(e,-i),t.lineTo(e,i),t.lineTo(-e,i),t.lineTo(-e,-i),t.closePath()}},_renderDashedStroke:function(t){var e=-this.width/2,i=-this.height/2,r=this.width,n=this.height;t.save(),this._setStrokeStyles(t,this),t.beginPath(),fabric.util.drawDashedLine(t,e,i,e+r,i,this.strokeDashArray),fabric.util.drawDashedLine(t,e+r,i,e+r,i+n,this.strokeDashArray),fabric.util.drawDashedLine(t,e+r,i+n,e,i+n,this.strokeDashArray),fabric.util.drawDashedLine(t,e,i+n,e,i,this.strokeDashArray),t.closePath(),t.restore()},toObject:function(t){var e=[];this.filters.forEach(function(t){t&&e.push(t.toObject())});var i=n(this.callSuper("toObject",["crossOrigin","cropX","cropY"].concat(t)),{src:this.getSrc(),filters:e});return this.resizeFilter&&(i.resizeFilter=this.resizeFilter.toObject()),i},hasCrop:function(){return this.cropX||this.cropY||this.width<this._element.width||this.height<this._element.height},_toSVG:function(){var t,e=[],i=[],r=-this.width/2,n=-this.height/2,s="";if(this.hasCrop()){var o=fabric.Object.__uid++;e.push('<clipPath id="imageCrop_'+o+'">\n','\t<rect x="'+r+'" y="'+n+'" width="'+this.width+'" height="'+this.height+'" />\n',"</clipPath>\n"),s=' clip-path="url(#imageCrop_'+o+')" '}if(i.push("\t<image ","COMMON_PARTS",'xlink:href="',this.getSvgSrc(!0),'" x="',r-this.cropX,'" y="',n-this.cropY,'" width="',this._element.width||this._element.naturalWidth,'" height="',this._element.height||this._element.height,'"',s,"></image>\n"),this.stroke||this.strokeDashArray){var a=this.fill;this.fill=null,t=["\t<rect ",'x="',r,'" y="',n,'" width="',this.width,'" height="',this.height,'" style="',this.getSvgStyles(),'"/>\n'],this.fill=a}return e="fill"!==this.paintFirst?e.concat(t,i):e.concat(i,t)},getSrc:function(t){var e=t?this._element:this._originalElement;return e?e.toDataURL?e.toDataURL():this.srcFromAttribute?e.getAttribute("src"):e.src:this.src||""},setSrc:function(t,e,i){return fabric.util.loadImage(t,function(t){this.setElement(t,i),this._setWidthHeight(),e&&e(this)},this,i&&i.crossOrigin),this},toString:function(){return'#<fabric.Image: { src: "'+this.getSrc()+'" }>'},applyResizeFilters:function(){var t=this.resizeFilter,e=this.minimumScaleTrigger,i=this.getTotalObjectScaling(),r=i.scaleX,n=i.scaleY,s=this._filteredEl||this._originalElement;if(this.group&&this.set("dirty",!0),!t||e<r&&e<n)return this._element=s,this._filterScalingX=1,this._filterScalingY=1,this._lastScaleX=r,void(this._lastScaleY=n);fabric.filterBackend||(fabric.filterBackend=fabric.initFilterBackend());var o=fabric.util.createCanvasElement(),a=this._filteredEl?this.cacheKey+"_filtered":this.cacheKey,h=s.width,c=s.height;o.width=h,o.height=c,this._element=o,this._lastScaleX=t.scaleX=r,this._lastScaleY=t.scaleY=n,fabric.filterBackend.applyFilters([t],s,h,c,this._element,a),this._filterScalingX=o.width/this._originalElement.width,this._filterScalingY=o.height/this._originalElement.height},applyFilters:function(t){if(t=(t=t||this.filters||[]).filter(function(t){return t&&!t.isNeutralState()}),this.set("dirty",!0),this.removeTexture(this.cacheKey+"_filtered"),0===t.length)return this._element=this._originalElement,this._filteredEl=null,this._filterScalingX=1,this._filterScalingY=1,this;var e=this._originalElement,i=e.naturalWidth||e.width,r=e.naturalHeight||e.height;if(this._element===this._originalElement){var n=fabric.util.createCanvasElement();n.width=i,n.height=r,this._element=n,this._filteredEl=n}else this._element=this._filteredEl,this._filteredEl.getContext("2d").clearRect(0,0,i,r),this._lastScaleX=1,this._lastScaleY=1;return fabric.filterBackend||(fabric.filterBackend=fabric.initFilterBackend()),fabric.filterBackend.applyFilters(t,this._originalElement,i,r,this._element,this.cacheKey),this._originalElement.width===this._element.width&&this._originalElement.height===this._element.height||(this._filterScalingX=this._element.width/this._originalElement.width,this._filterScalingY=this._element.height/this._originalElement.height),this},_render:function(t){!0!==this.isMoving&&this.resizeFilter&&this._needsResize()&&this.applyResizeFilters(),this._stroke(t),this._renderPaintInOrder(t)},shouldCache:function(){return this.needsItsOwnCache()},_renderFill:function(t){var e=this._element,i=this.width,r=this.height,n=Math.min(e.naturalWidth||e.width,i*this._filterScalingX),s=Math.min(e.naturalHeight||e.height,r*this._filterScalingY),o=-i/2,a=-r/2,h=Math.max(0,this.cropX*this._filterScalingX),c=Math.max(0,this.cropY*this._filterScalingY);e&&t.drawImage(e,h,c,n,s,o,a,i,r)},_needsResize:function(){var t=this.getTotalObjectScaling();return t.scaleX!==this._lastScaleX||t.scaleY!==this._lastScaleY},_resetWidthHeight:function(){this.set(this.getOriginalSize())},_initElement:function(t,e){this.setElement(fabric.util.getById(t),e),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(t){t||(t={}),this.setOptions(t),this._setWidthHeight(t),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(t,e){t&&t.length?fabric.util.enlivenObjects(t,function(t){e&&e(t)},"fabric.Image.filters"):e&&e()},_setWidthHeight:function(t){t||(t={});var e=this.getElement();this.width=t.width||e.naturalWidth||e.width||0,this.height=t.height||e.naturalHeight||e.height||0},parsePreserveAspectRatioAttribute:function(){var t,e=fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio||""),i=this._element.width,r=this._element.height,n=1,s=1,o=0,a=0,h=0,c=0,l=this.width,u=this.height,f={width:l,height:u};return!e||"none"===e.alignX&&"none"===e.alignY?(n=l/i,s=u/r):("meet"===e.meetOrSlice&&(t=(l-i*(n=s=fabric.util.findScaleToFit(this._element,f)))/2,"Min"===e.alignX&&(o=-t),"Max"===e.alignX&&(o=t),t=(u-r*s)/2,"Min"===e.alignY&&(a=-t),"Max"===e.alignY&&(a=t)),"slice"===e.meetOrSlice&&(t=i-l/(n=s=fabric.util.findScaleToCover(this._element,f)),"Mid"===e.alignX&&(h=t/2),"Max"===e.alignX&&(h=t),t=r-u/s,"Mid"===e.alignY&&(c=t/2),"Max"===e.alignY&&(c=t),i=l/n,r=u/s)),{width:i,height:r,scaleX:n,scaleY:s,offsetLeft:o,offsetTop:a,cropX:h,cropY:c}}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(t,r){var n=fabric.util.object.clone(t);fabric.util.loadImage(n.src,function(i,t){t?r&&r(null,t):fabric.Image.prototype._initFilters.call(n,n.filters,function(t){n.filters=t||[],fabric.Image.prototype._initFilters.call(n,[n.resizeFilter],function(t){n.resizeFilter=t[0],fabric.util.enlivenObjects([n.clipPath],function(t){n.clipPath=t[0];var e=new fabric.Image(i,n);r(e)})})})},null,n.crossOrigin)},fabric.Image.fromURL=function(t,e,i){fabric.util.loadImage(t,function(t){e&&e(new fabric.Image(t,i))},null,i&&i.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height preserveAspectRatio xlink:href crossOrigin".split(" ")),fabric.Image.fromElement=function(t,e,i){var r=fabric.parseAttributes(t,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(r["xlink:href"],e,n(i?fabric.util.object.clone(i):{},r))})}("undefined"!=typeof exports?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var t=this.angle%360;return 0<t?90*Math.round((t-1)/90):90*Math.round(t/90)},straighten:function(){return this.rotate(this._getAngleValueForStraighten()),this},fxStraighten:function(t){var e=function(){},i=(t=t||{}).onComplete||e,r=t.onChange||e,n=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(t){n.rotate(t),r()},onComplete:function(){n.setCoords(),i()}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(t){return t.straighten(),this.requestRenderAll(),this},fxStraightenObject:function(t){return t.fxStraighten({onChange:this.requestRenderAllBound}),this}}),function(){"use strict";function t(t){t&&t.tileSize&&(this.tileSize=t.tileSize),this.setupGLContext(this.tileSize,this.tileSize),this.captureGPUInfo()}fabric.isWebglSupported=function(t){if(fabric.isLikelyNode)return!1;t=t||fabric.WebglFilterBackend.prototype.tileSize;var e,i,r,n=document.createElement("canvas"),s=n.getContext("webgl")||n.getContext("experimental-webgl"),o=!1;if(s){fabric.maxTextureSize=s.getParameter(s.MAX_TEXTURE_SIZE),o=fabric.maxTextureSize>=t;for(var a=["highp","mediump","lowp"],h=0;h<3;h++)if(void 0,i="precision "+a[h]+" float;\nvoid main(){}",r=(e=s).createShader(e.FRAGMENT_SHADER),e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS)){fabric.webGlPrecision=a[h];break}}return this.isSupported=o},(fabric.WebglFilterBackend=t).prototype={tileSize:2048,resources:{},setupGLContext:function(t,e){this.dispose(),this.createWebGLCanvas(t,e),this.aPosition=new Float32Array([0,0,0,1,1,0,1,1]),this.chooseFastestCopyGLTo2DMethod(t,e)},chooseFastestCopyGLTo2DMethod:function(t,e){var i,r=void 0!==window.performance;try{new ImageData(1,1),i=!0}catch(t){i=!1}var n="undefined"!=typeof ArrayBuffer,s="undefined"!=typeof Uint8ClampedArray;if(r&&i&&n&&s){var o=fabric.util.createCanvasElement(),a=new ArrayBuffer(t*e*4);if(fabric.forceGLPutImageData)return this.imageBuffer=a,void(this.copyGLTo2D=copyGLTo2DPutImageData);var h,c,l={imageBuffer:a,destinationWidth:t,destinationHeight:e,targetCanvas:o};o.width=t,o.height=e,h=window.performance.now(),copyGLTo2DDrawImage.call(l,this.gl,l),c=window.performance.now()-h,h=window.performance.now(),copyGLTo2DPutImageData.call(l,this.gl,l),window.performance.now()-h<c?(this.imageBuffer=a,this.copyGLTo2D=copyGLTo2DPutImageData):this.copyGLTo2D=copyGLTo2DDrawImage}},createWebGLCanvas:function(t,e){var i=fabric.util.createCanvasElement();i.width=t,i.height=e;var r={alpha:!0,premultipliedAlpha:!1,depth:!1,stencil:!1,antialias:!1},n=i.getContext("webgl",r);n||(n=i.getContext("experimental-webgl",r)),n&&(n.clearColor(0,0,0,0),this.canvas=i,this.gl=n)},applyFilters:function(t,e,i,r,n,s){var o,a=this.gl;s&&(o=this.getCachedTexture(s,e));var h={originalWidth:e.width||e.originalWidth,originalHeight:e.height||e.originalHeight,sourceWidth:i,sourceHeight:r,destinationWidth:i,destinationHeight:r,context:a,sourceTexture:this.createTexture(a,i,r,!o&&e),targetTexture:this.createTexture(a,i,r),originalTexture:o||this.createTexture(a,i,r,!o&&e),passes:t.length,webgl:!0,aPosition:this.aPosition,programCache:this.programCache,pass:0,filterBackend:this,targetCanvas:n},c=a.createFramebuffer();return a.bindFramebuffer(a.FRAMEBUFFER,c),t.forEach(function(t){t&&t.applyTo(h)}),resizeCanvasIfNeeded(h),this.copyGLTo2D(a,h),a.bindTexture(a.TEXTURE_2D,null),a.deleteTexture(h.sourceTexture),a.deleteTexture(h.targetTexture),a.deleteFramebuffer(c),n.getContext("2d").setTransform(1,0,0,1,0,0),h},dispose:function(){this.canvas&&(this.canvas=null,this.gl=null),this.clearWebGLCaches()},clearWebGLCaches:function(){this.programCache={},this.textureCache={}},createTexture:function(t,e,i,r){var n=t.createTexture();return t.bindTexture(t.TEXTURE_2D,n),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),r?t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,r):t.texImage2D(t.TEXTURE_2D,0,t.RGBA,e,i,0,t.RGBA,t.UNSIGNED_BYTE,null),n},getCachedTexture:function(t,e){if(this.textureCache[t])return this.textureCache[t];var i=this.createTexture(this.gl,e.width,e.height,e);return this.textureCache[t]=i},evictCachesForKey:function(t){this.textureCache[t]&&(this.gl.deleteTexture(this.textureCache[t]),delete this.textureCache[t])},copyGLTo2D:copyGLTo2DDrawImage,captureGPUInfo:function(){if(this.gpuInfo)return this.gpuInfo;var t=this.gl,e={renderer:"",vendor:""};if(!t)return e;var i=t.getExtension("WEBGL_debug_renderer_info");if(i){var r=t.getParameter(i.UNMASKED_RENDERER_WEBGL),n=t.getParameter(i.UNMASKED_VENDOR_WEBGL);r&&(e.renderer=r.toLowerCase()),n&&(e.vendor=n.toLowerCase())}return this.gpuInfo=e}}}(),function(){"use strict";var t=function(){};function e(){}(fabric.Canvas2dFilterBackend=e).prototype={evictCachesForKey:t,dispose:t,clearWebGLCaches:t,resources:{},applyFilters:function(t,e,i,r,n){var s=n.getContext("2d");s.drawImage(e,0,0,i,r);var o={sourceWidth:i,sourceHeight:r,imageData:s.getImageData(0,0,i,r),originalEl:e,originalImageData:s.getImageData(0,0,i,r),canvasEl:n,ctx:s,filterBackend:this};return t.forEach(function(t){t.applyTo(o)}),o.imageData.width===i&&o.imageData.height===r||(n.width=o.imageData.width,n.height=o.imageData.height),s.putImageData(o.imageData,0,0),o}}}(),fabric.Image=fabric.Image||{},fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",vertexSource:"attribute vec2 aPosition;\nvarying vec2 vTexCoord;\nvoid main() {\nvTexCoord = aPosition;\ngl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n}",fragmentSource:"precision highp float;\nvarying vec2 vTexCoord;\nuniform sampler2D uTexture;\nvoid main() {\ngl_FragColor = texture2D(uTexture, vTexCoord);\n}",initialize:function(t){t&&this.setOptions(t)},setOptions:function(t){for(var e in t)this[e]=t[e]},createProgram:function(t,e,i){e=e||this.fragmentSource,i=i||this.vertexSource,"highp"!==fabric.webGlPrecision&&(e=e.replace(/precision highp float/g,"precision "+fabric.webGlPrecision+" float"));var r=t.createShader(t.VERTEX_SHADER);if(t.shaderSource(r,i),t.compileShader(r),!t.getShaderParameter(r,t.COMPILE_STATUS))throw new Error("Vertex shader compile error for "+this.type+": "+t.getShaderInfoLog(r));var n=t.createShader(t.FRAGMENT_SHADER);if(t.shaderSource(n,e),t.compileShader(n),!t.getShaderParameter(n,t.COMPILE_STATUS))throw new Error("Fragment shader compile error for "+this.type+": "+t.getShaderInfoLog(n));var s=t.createProgram();if(t.attachShader(s,r),t.attachShader(s,n),t.linkProgram(s),!t.getProgramParameter(s,t.LINK_STATUS))throw new Error('Shader link error for "${this.type}" '+t.getProgramInfoLog(s));var o=this.getAttributeLocations(t,s),a=this.getUniformLocations(t,s)||{};return a.uStepW=t.getUniformLocation(s,"uStepW"),a.uStepH=t.getUniformLocation(s,"uStepH"),{program:s,attributeLocations:o,uniformLocations:a}},getAttributeLocations:function(t,e){return{aPosition:t.getAttribLocation(e,"aPosition")}},getUniformLocations:function(){return{}},sendAttributeData:function(t,e,i){var r=e.aPosition,n=t.createBuffer();t.bindBuffer(t.ARRAY_BUFFER,n),t.enableVertexAttribArray(r),t.vertexAttribPointer(r,2,t.FLOAT,!1,0,0),t.bufferData(t.ARRAY_BUFFER,i,t.STATIC_DRAW)},_setupFrameBuffer:function(t){var e,i,r=t.context;1<t.passes?(e=t.destinationWidth,i=t.destinationHeight,t.sourceWidth===e&&t.sourceHeight===i||(r.deleteTexture(t.targetTexture),t.targetTexture=t.filterBackend.createTexture(r,e,i)),r.framebufferTexture2D(r.FRAMEBUFFER,r.COLOR_ATTACHMENT0,r.TEXTURE_2D,t.targetTexture,0)):(r.bindFramebuffer(r.FRAMEBUFFER,null),r.finish())},_swapTextures:function(t){t.passes--,t.pass++;var e=t.targetTexture;t.targetTexture=t.sourceTexture,t.sourceTexture=e},isNeutralState:function(){var t=this.mainParameter,e=fabric.Image.filters[this.type].prototype;if(t){if(Array.isArray(e[t])){for(var i=e[t].length;i--;)if(this[t][i]!==e[t][i])return!1;return!0}return e[t]===this[t]}return!1},applyTo:function(t){t.webgl?(this._setupFrameBuffer(t),this.applyToWebGL(t),this._swapTextures(t)):this.applyTo2d(t)},retrieveShader:function(t){return t.programCache.hasOwnProperty(this.type)||(t.programCache[this.type]=this.createProgram(t.context)),t.programCache[this.type]},applyToWebGL:function(t){var e=t.context,i=this.retrieveShader(t);0===t.pass&&t.originalTexture?e.bindTexture(e.TEXTURE_2D,t.originalTexture):e.bindTexture(e.TEXTURE_2D,t.sourceTexture),e.useProgram(i.program),this.sendAttributeData(e,i.attributeLocations,t.aPosition),e.uniform1f(i.uniformLocations.uStepW,1/t.sourceWidth),e.uniform1f(i.uniformLocations.uStepH,1/t.sourceHeight),this.sendUniformData(e,i.uniformLocations),e.viewport(0,0,t.destinationWidth,t.destinationHeight),e.drawArrays(e.TRIANGLE_STRIP,0,4)},bindAdditionalTexture:function(t,e,i){t.activeTexture(i),t.bindTexture(t.TEXTURE_2D,e),t.activeTexture(t.TEXTURE0)},unbindAdditionalTexture:function(t,e){t.activeTexture(e),t.bindTexture(t.TEXTURE_2D,null),t.activeTexture(t.TEXTURE0)},getMainParameter:function(){return this[this.mainParameter]},setMainParameter:function(t){this[this.mainParameter]=t},sendUniformData:function(){},createHelpLayer:function(t){if(!t.helpLayer){var e=document.createElement("canvas");e.width=t.sourceWidth,e.height=t.sourceHeight,t.helpLayer=e}},toObject:function(){var t={type:this.type},e=this.mainParameter;return e&&(t[e]=this[e]),t},toJSON:function(){return this.toObject()}}),fabric.Image.filters.BaseFilter.fromObject=function(t,e){var i=new fabric.Image.filters[t.type](t);return e&&e(i),i},function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.ColorMatrix=r(i.BaseFilter,{type:"ColorMatrix",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nvarying vec2 vTexCoord;\nuniform mat4 uColorMatrix;\nuniform vec4 uConstants;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\ncolor *= uColorMatrix;\ncolor += uConstants;\ngl_FragColor = color;\n}",matrix:[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0],mainParameter:"matrix",colorsOnly:!0,initialize:function(t){this.callSuper("initialize",t),this.matrix=this.matrix.slice(0)},applyTo2d:function(t){var e,i,r,n,s,o=t.imageData.data,a=o.length,h=this.matrix,c=this.colorsOnly;for(s=0;s<a;s+=4)e=o[s],i=o[s+1],r=o[s+2],c?(o[s]=e*h[0]+i*h[1]+r*h[2]+255*h[4],o[s+1]=e*h[5]+i*h[6]+r*h[7]+255*h[9],o[s+2]=e*h[10]+i*h[11]+r*h[12]+255*h[14]):(n=o[s+3],o[s]=e*h[0]+i*h[1]+r*h[2]+n*h[3]+255*h[4],o[s+1]=e*h[5]+i*h[6]+r*h[7]+n*h[8]+255*h[9],o[s+2]=e*h[10]+i*h[11]+r*h[12]+n*h[13]+255*h[14],o[s+3]=e*h[15]+i*h[16]+r*h[17]+n*h[18]+255*h[19])},getUniformLocations:function(t,e){return{uColorMatrix:t.getUniformLocation(e,"uColorMatrix"),uConstants:t.getUniformLocation(e,"uConstants")}},sendUniformData:function(t,e){var i=this.matrix,r=[i[0],i[1],i[2],i[3],i[5],i[6],i[7],i[8],i[10],i[11],i[12],i[13],i[15],i[16],i[17],i[18]],n=[i[4],i[9],i[14],i[19]];t.uniformMatrix4fv(e.uColorMatrix,!1,r),t.uniform4fv(e.uConstants,n)}}),e.Image.filters.ColorMatrix.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Brightness=r(i.BaseFilter,{type:"Brightness",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uBrightness;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\ncolor.rgb += uBrightness;\ngl_FragColor = color;\n}",brightness:0,mainParameter:"brightness",applyTo2d:function(t){if(0!==this.brightness){var e,i=t.imageData.data,r=i.length,n=Math.round(255*this.brightness);for(e=0;e<r;e+=4)i[e]=i[e]+n,i[e+1]=i[e+1]+n,i[e+2]=i[e+2]+n}},getUniformLocations:function(t,e){return{uBrightness:t.getUniformLocation(e,"uBrightness")}},sendUniformData:function(t,e){t.uniform1f(e.uBrightness,this.brightness)}}),e.Image.filters.Brightness.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.Image.filters,n=e.util.createClass;r.Convolute=n(r.BaseFilter,{type:"Convolute",opaque:!1,matrix:[0,0,0,0,1,0,0,0,0],fragmentSource:{Convolute_3_1:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[9];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 0);\nfor (float h = 0.0; h < 3.0; h+=1.0) {\nfor (float w = 0.0; w < 3.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 1), uStepH * (h - 1));\ncolor += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 3.0 + w)];\n}\n}\ngl_FragColor = color;\n}",Convolute_3_0:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[9];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 1);\nfor (float h = 0.0; h < 3.0; h+=1.0) {\nfor (float w = 0.0; w < 3.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 1.0), uStepH * (h - 1.0));\ncolor.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 3.0 + w)];\n}\n}\nfloat alpha = texture2D(uTexture, vTexCoord).a;\ngl_FragColor = color;\ngl_FragColor.a = alpha;\n}",Convolute_5_1:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[25];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 0);\nfor (float h = 0.0; h < 5.0; h+=1.0) {\nfor (float w = 0.0; w < 5.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\ncolor += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 5.0 + w)];\n}\n}\ngl_FragColor = color;\n}",Convolute_5_0:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[25];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 1);\nfor (float h = 0.0; h < 5.0; h+=1.0) {\nfor (float w = 0.0; w < 5.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\ncolor.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 5.0 + w)];\n}\n}\nfloat alpha = texture2D(uTexture, vTexCoord).a;\ngl_FragColor = color;\ngl_FragColor.a = alpha;\n}",Convolute_7_1:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[49];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 0);\nfor (float h = 0.0; h < 7.0; h+=1.0) {\nfor (float w = 0.0; w < 7.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\ncolor += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 7.0 + w)];\n}\n}\ngl_FragColor = color;\n}",Convolute_7_0:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[49];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 1);\nfor (float h = 0.0; h < 7.0; h+=1.0) {\nfor (float w = 0.0; w < 7.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\ncolor.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 7.0 + w)];\n}\n}\nfloat alpha = texture2D(uTexture, vTexCoord).a;\ngl_FragColor = color;\ngl_FragColor.a = alpha;\n}",Convolute_9_1:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[81];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 0);\nfor (float h = 0.0; h < 9.0; h+=1.0) {\nfor (float w = 0.0; w < 9.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\ncolor += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 9.0 + w)];\n}\n}\ngl_FragColor = color;\n}",Convolute_9_0:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uMatrix[81];\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = vec4(0, 0, 0, 1);\nfor (float h = 0.0; h < 9.0; h+=1.0) {\nfor (float w = 0.0; w < 9.0; w+=1.0) {\nvec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\ncolor.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 9.0 + w)];\n}\n}\nfloat alpha = texture2D(uTexture, vTexCoord).a;\ngl_FragColor = color;\ngl_FragColor.a = alpha;\n}"},retrieveShader:function(t){var e=Math.sqrt(this.matrix.length),i=this.type+"_"+e+"_"+(this.opaque?1:0),r=this.fragmentSource[i];return t.programCache.hasOwnProperty(i)||(t.programCache[i]=this.createProgram(t.context,r)),t.programCache[i]},applyTo2d:function(t){var e,i,r,n,s,o,a,h,c,l,u,f,d,g=t.imageData,p=g.data,v=this.matrix,m=Math.round(Math.sqrt(v.length)),b=Math.floor(m/2),_=g.width,y=g.height,x=t.ctx.createImageData(_,y),C=x.data,S=this.opaque?1:0;for(u=0;u<y;u++)for(l=0;l<_;l++){for(s=4*(u*_+l),d=n=r=i=e=0;d<m;d++)for(f=0;f<m;f++)o=l+f-b,(a=u+d-b)<0||y<a||o<0||_<o||(h=4*(a*_+o),c=v[d*m+f],e+=p[h]*c,i+=p[h+1]*c,r+=p[h+2]*c,S||(n+=p[h+3]*c));C[s]=e,C[s+1]=i,C[s+2]=r,C[s+3]=S?p[s+3]:n}t.imageData=x},getUniformLocations:function(t,e){return{uMatrix:t.getUniformLocation(e,"uMatrix"),uOpaque:t.getUniformLocation(e,"uOpaque"),uHalfSize:t.getUniformLocation(e,"uHalfSize"),uSize:t.getUniformLocation(e,"uSize")}},sendUniformData:function(t,e){t.uniform1fv(e.uMatrix,this.matrix)},toObject:function(){return i(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),e.Image.filters.Convolute.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Grayscale=r(i.BaseFilter,{type:"Grayscale",fragmentSource:{average:"precision highp float;\nuniform sampler2D uTexture;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nfloat average = (color.r + color.b + color.g) / 3.0;\ngl_FragColor = vec4(average, average, average, color.a);\n}",lightness:"precision highp float;\nuniform sampler2D uTexture;\nuniform int uMode;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 col = texture2D(uTexture, vTexCoord);\nfloat average = (max(max(col.r, col.g),col.b) + min(min(col.r, col.g),col.b)) / 2.0;\ngl_FragColor = vec4(average, average, average, col.a);\n}",luminosity:"precision highp float;\nuniform sampler2D uTexture;\nuniform int uMode;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 col = texture2D(uTexture, vTexCoord);\nfloat average = 0.21 * col.r + 0.72 * col.g + 0.07 * col.b;\ngl_FragColor = vec4(average, average, average, col.a);\n}"},mode:"average",mainParameter:"mode",applyTo2d:function(t){var e,i,r=t.imageData.data,n=r.length,s=this.mode;for(e=0;e<n;e+=4)"average"===s?i=(r[e]+r[e+1]+r[e+2])/3:"lightness"===s?i=(Math.min(r[e],r[e+1],r[e+2])+Math.max(r[e],r[e+1],r[e+2]))/2:"luminosity"===s&&(i=.21*r[e]+.72*r[e+1]+.07*r[e+2]),r[e]=i,r[e+1]=i,r[e+2]=i},retrieveShader:function(t){var e=this.type+"_"+this.mode;if(!t.programCache.hasOwnProperty(e)){var i=this.fragmentSource[this.mode];t.programCache[e]=this.createProgram(t.context,i)}return t.programCache[e]},getUniformLocations:function(t,e){return{uMode:t.getUniformLocation(e,"uMode")}},sendUniformData:function(t,e){t.uniform1i(e.uMode,1)},isNeutralState:function(){return!1}}),e.Image.filters.Grayscale.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Invert=r(i.BaseFilter,{type:"Invert",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform int uInvert;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nif (uInvert == 1) {\ngl_FragColor = vec4(1.0 - color.r,1.0 -color.g,1.0 -color.b,color.a);\n} else {\ngl_FragColor = color;\n}\n}",invert:!0,mainParameter:"invert",applyTo2d:function(t){var e,i=t.imageData.data,r=i.length;for(e=0;e<r;e+=4)i[e]=255-i[e],i[e+1]=255-i[e+1],i[e+2]=255-i[e+2]},isNeutralState:function(){return!this.invert},getUniformLocations:function(t,e){return{uInvert:t.getUniformLocation(e,"uInvert")}},sendUniformData:function(t,e){t.uniform1i(e.uInvert,this.invert)}}),e.Image.filters.Invert.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.object.extend,r=e.Image.filters,n=e.util.createClass;r.Noise=n(r.BaseFilter,{type:"Noise",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uStepH;\nuniform float uNoise;\nuniform float uSeed;\nvarying vec2 vTexCoord;\nfloat rand(vec2 co, float seed, float vScale) {\nreturn fract(sin(dot(co.xy * vScale ,vec2(12.9898 , 78.233))) * 43758.5453 * (seed + 0.01) / 2.0);\n}\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\ncolor.rgb += (0.5 - rand(vTexCoord, uSeed, 0.1 / uStepH)) * uNoise;\ngl_FragColor = color;\n}",mainParameter:"noise",noise:0,applyTo2d:function(t){if(0!==this.noise){var e,i,r=t.imageData.data,n=r.length,s=this.noise;for(e=0,n=r.length;e<n;e+=4)i=(.5-Math.random())*s,r[e]+=i,r[e+1]+=i,r[e+2]+=i}},getUniformLocations:function(t,e){return{uNoise:t.getUniformLocation(e,"uNoise"),uSeed:t.getUniformLocation(e,"uSeed")}},sendUniformData:function(t,e){t.uniform1f(e.uNoise,this.noise/255),t.uniform1f(e.uSeed,Math.random())},toObject:function(){return i(this.callSuper("toObject"),{noise:this.noise})}}),e.Image.filters.Noise.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Pixelate=r(i.BaseFilter,{type:"Pixelate",blocksize:4,mainParameter:"blocksize",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uBlocksize;\nuniform float uStepW;\nuniform float uStepH;\nvarying vec2 vTexCoord;\nvoid main() {\nfloat blockW = uBlocksize * uStepW;\nfloat blockH = uBlocksize * uStepW;\nint posX = int(vTexCoord.x / blockW);\nint posY = int(vTexCoord.y / blockH);\nfloat fposX = float(posX);\nfloat fposY = float(posY);\nvec2 squareCoords = vec2(fposX * blockW, fposY * blockH);\nvec4 color = texture2D(uTexture, squareCoords);\ngl_FragColor = color;\n}",applyTo2d:function(t){var e,i,r,n,s,o,a,h,c,l,u,f=t.imageData,d=f.data,g=f.height,p=f.width;for(i=0;i<g;i+=this.blocksize)for(r=0;r<p;r+=this.blocksize)for(n=d[e=4*i*p+4*r],s=d[e+1],o=d[e+2],a=d[e+3],l=Math.min(i+this.blocksize,g),u=Math.min(r+this.blocksize,p),h=i;h<l;h++)for(c=r;c<u;c++)d[e=4*h*p+4*c]=n,d[e+1]=s,d[e+2]=o,d[e+3]=a},isNeutralState:function(){return 1===this.blocksize},getUniformLocations:function(t,e){return{uBlocksize:t.getUniformLocation(e,"uBlocksize"),uStepW:t.getUniformLocation(e,"uStepW"),uStepH:t.getUniformLocation(e,"uStepH")}},sendUniformData:function(t,e){t.uniform1f(e.uBlocksize,this.blocksize)}}),e.Image.filters.Pixelate.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var l=t.fabric||(t.fabric={}),e=l.util.object.extend,i=l.Image.filters,r=l.util.createClass;i.RemoveColor=r(i.BaseFilter,{type:"RemoveColor",color:"#FFFFFF",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform vec4 uLow;\nuniform vec4 uHigh;\nvarying vec2 vTexCoord;\nvoid main() {\ngl_FragColor = texture2D(uTexture, vTexCoord);\nif(all(greaterThan(gl_FragColor.rgb,uLow.rgb)) && all(greaterThan(uHigh.rgb,gl_FragColor.rgb))) {\ngl_FragColor.a = 0.0;\n}\n}",distance:.02,useAlpha:!1,applyTo2d:function(t){var e,i,r,n,s=t.imageData.data,o=255*this.distance,a=new l.Color(this.color).getSource(),h=[a[0]-o,a[1]-o,a[2]-o],c=[a[0]+o,a[1]+o,a[2]+o];for(e=0;e<s.length;e+=4)i=s[e],r=s[e+1],n=s[e+2],h[0]<i&&h[1]<r&&h[2]<n&&i<c[0]&&r<c[1]&&n<c[2]&&(s[e+3]=0)},getUniformLocations:function(t,e){return{uLow:t.getUniformLocation(e,"uLow"),uHigh:t.getUniformLocation(e,"uHigh")}},sendUniformData:function(t,e){var i=new l.Color(this.color).getSource(),r=parseFloat(this.distance),n=[0+i[0]/255-r,0+i[1]/255-r,0+i[2]/255-r,1],s=[i[0]/255+r,i[1]/255+r,i[2]/255+r,1];t.uniform4fv(e.uLow,n),t.uniform4fv(e.uHigh,s)},toObject:function(){return e(this.callSuper("toObject"),{color:this.color,distance:this.distance})}}),l.Image.filters.RemoveColor.fromObject=l.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass,n={Brownie:[.5997,.34553,-.27082,0,.186,-.0377,.86095,.15059,0,-.1449,.24113,-.07441,.44972,0,-.02965,0,0,0,1,0],Vintage:[.62793,.32021,-.03965,0,.03784,.02578,.64411,.03259,0,.02926,.0466,-.08512,.52416,0,.02023,0,0,0,1,0],Kodachrome:[1.12855,-.39673,-.03992,0,.24991,-.16404,1.08352,-.05498,0,.09698,-.16786,-.56034,1.60148,0,.13972,0,0,0,1,0],Technicolor:[1.91252,-.85453,-.09155,0,.04624,-.30878,1.76589,-.10601,0,-.27589,-.2311,-.75018,1.84759,0,.12137,0,0,0,1,0],Polaroid:[1.438,-.062,-.062,0,0,-.122,1.378,-.122,0,0,-.016,-.016,1.483,0,0,0,0,0,1,0],Sepia:[.393,.769,.189,0,0,.349,.686,.168,0,0,.272,.534,.131,0,0,0,0,0,1,0],BlackWhite:[1.5,1.5,1.5,0,-1,1.5,1.5,1.5,0,-1,1.5,1.5,1.5,0,-1,0,0,0,1,0]};for(var s in n)i[s]=r(i.ColorMatrix,{type:s,matrix:n[s],mainParameter:!1,colorsOnly:!0}),e.Image.filters[s].fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var f=t.fabric,e=f.Image.filters,i=f.util.createClass;e.BlendColor=i(e.BaseFilter,{type:"BlendColor",color:"#F95C63",mode:"multiply",alpha:1,fragmentSource:{multiply:"gl_FragColor.rgb *= uColor.rgb;\n",screen:"gl_FragColor.rgb = 1.0 - (1.0 - gl_FragColor.rgb) * (1.0 - uColor.rgb);\n",add:"gl_FragColor.rgb += uColor.rgb;\n",diff:"gl_FragColor.rgb = abs(gl_FragColor.rgb - uColor.rgb);\n",subtract:"gl_FragColor.rgb -= uColor.rgb;\n",lighten:"gl_FragColor.rgb = max(gl_FragColor.rgb, uColor.rgb);\n",darken:"gl_FragColor.rgb = min(gl_FragColor.rgb, uColor.rgb);\n",exclusion:"gl_FragColor.rgb += uColor.rgb - 2.0 * (uColor.rgb * gl_FragColor.rgb);\n",overlay:"if (uColor.r < 0.5) {\ngl_FragColor.r *= 2.0 * uColor.r;\n} else {\ngl_FragColor.r = 1.0 - 2.0 * (1.0 - gl_FragColor.r) * (1.0 - uColor.r);\n}\nif (uColor.g < 0.5) {\ngl_FragColor.g *= 2.0 * uColor.g;\n} else {\ngl_FragColor.g = 1.0 - 2.0 * (1.0 - gl_FragColor.g) * (1.0 - uColor.g);\n}\nif (uColor.b < 0.5) {\ngl_FragColor.b *= 2.0 * uColor.b;\n} else {\ngl_FragColor.b = 1.0 - 2.0 * (1.0 - gl_FragColor.b) * (1.0 - uColor.b);\n}\n",tint:"gl_FragColor.rgb *= (1.0 - uColor.a);\ngl_FragColor.rgb += uColor.rgb;\n"},buildSource:function(t){return"precision highp float;\nuniform sampler2D uTexture;\nuniform vec4 uColor;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\ngl_FragColor = color;\nif (color.a > 0.0) {\n"+this.fragmentSource[t]+"}\n}"},retrieveShader:function(t){var e,i=this.type+"_"+this.mode;return t.programCache.hasOwnProperty(i)||(e=this.buildSource(this.mode),t.programCache[i]=this.createProgram(t.context,e)),t.programCache[i]},applyTo2d:function(t){var e,i,r,n,s,o,a,h=t.imageData.data,c=h.length,l=1-this.alpha;e=(a=new f.Color(this.color).getSource())[0]*this.alpha,i=a[1]*this.alpha,r=a[2]*this.alpha;for(var u=0;u<c;u+=4)switch(n=h[u],s=h[u+1],o=h[u+2],this.mode){case"multiply":h[u]=n*e/255,h[u+1]=s*i/255,h[u+2]=o*r/255;break;case"screen":h[u]=255-(255-n)*(255-e)/255,h[u+1]=255-(255-s)*(255-i)/255,h[u+2]=255-(255-o)*(255-r)/255;break;case"add":h[u]=n+e,h[u+1]=s+i,h[u+2]=o+r;break;case"diff":case"difference":h[u]=Math.abs(n-e),h[u+1]=Math.abs(s-i),h[u+2]=Math.abs(o-r);break;case"subtract":h[u]=n-e,h[u+1]=s-i,h[u+2]=o-r;break;case"darken":h[u]=Math.min(n,e),h[u+1]=Math.min(s,i),h[u+2]=Math.min(o,r);break;case"lighten":h[u]=Math.max(n,e),h[u+1]=Math.max(s,i),h[u+2]=Math.max(o,r);break;case"overlay":h[u]=e<128?2*n*e/255:255-2*(255-n)*(255-e)/255,h[u+1]=i<128?2*s*i/255:255-2*(255-s)*(255-i)/255,h[u+2]=r<128?2*o*r/255:255-2*(255-o)*(255-r)/255;break;case"exclusion":h[u]=e+n-2*e*n/255,h[u+1]=i+s-2*i*s/255,h[u+2]=r+o-2*r*o/255;break;case"tint":h[u]=e+n*l,h[u+1]=i+s*l,h[u+2]=r+o*l}},getUniformLocations:function(t,e){return{uColor:t.getUniformLocation(e,"uColor")}},sendUniformData:function(t,e){var i=new f.Color(this.color).getSource();i[0]=this.alpha*i[0]/255,i[1]=this.alpha*i[1]/255,i[2]=this.alpha*i[2]/255,i[3]=this.alpha,t.uniform4fv(e.uColor,i)},toObject:function(){return{type:this.type,color:this.color,mode:this.mode,alpha:this.alpha}}}),f.Image.filters.BlendColor.fromObject=f.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var y=t.fabric,e=y.Image.filters,i=y.util.createClass;e.BlendImage=i(e.BaseFilter,{type:"BlendImage",image:null,mode:"multiply",alpha:1,vertexSource:"attribute vec2 aPosition;\nvarying vec2 vTexCoord;\nvarying vec2 vTexCoord2;\nuniform mat3 uTransformMatrix;\nvoid main() {\nvTexCoord = aPosition;\nvTexCoord2 = (uTransformMatrix * vec3(aPosition, 1.0)).xy;\ngl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n}",fragmentSource:{multiply:"precision highp float;\nuniform sampler2D uTexture;\nuniform sampler2D uImage;\nuniform vec4 uColor;\nvarying vec2 vTexCoord;\nvarying vec2 vTexCoord2;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nvec4 color2 = texture2D(uImage, vTexCoord2);\ncolor.rgba *= color2.rgba;\ngl_FragColor = color;\n}",mask:"precision highp float;\nuniform sampler2D uTexture;\nuniform sampler2D uImage;\nuniform vec4 uColor;\nvarying vec2 vTexCoord;\nvarying vec2 vTexCoord2;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nvec4 color2 = texture2D(uImage, vTexCoord2);\ncolor.a = color2.a;\ngl_FragColor = color;\n}"},retrieveShader:function(t){var e=this.type+"_"+this.mode,i=this.fragmentSource[this.mode];return t.programCache.hasOwnProperty(e)||(t.programCache[e]=this.createProgram(t.context,i)),t.programCache[e]},applyToWebGL:function(t){var e=t.context,i=this.createTexture(t.filterBackend,this.image);this.bindAdditionalTexture(e,i,e.TEXTURE1),this.callSuper("applyToWebGL",t),this.unbindAdditionalTexture(e,e.TEXTURE1)},createTexture:function(t,e){return t.getCachedTexture(e.cacheKey,e._element)},calculateMatrix:function(){var t=this.image,e=t._element.width,i=t._element.height;return[1/t.scaleX,0,0,0,1/t.scaleY,0,-t.left/e,-t.top/i,1]},applyTo2d:function(t){var e,i,r,n,s,o,a,h,c,l,u,f=t.imageData,d=t.filterBackend.resources,g=f.data,p=g.length,v=f.width,m=f.height,b=this.image;d.blendImage||(d.blendImage=y.util.createCanvasElement()),l=(c=d.blendImage).getContext("2d"),c.width!==v||c.height!==m?(c.width=v,c.height=m):l.clearRect(0,0,v,m),l.setTransform(b.scaleX,0,0,b.scaleY,b.left,b.top),l.drawImage(b._element,0,0,v,m),u=l.getImageData(0,0,v,m).data;for(var _=0;_<p;_+=4)switch(s=g[_],o=g[_+1],a=g[_+2],h=g[_+3],e=u[_],i=u[_+1],r=u[_+2],n=u[_+3],this.mode){case"multiply":g[_]=s*e/255,g[_+1]=o*i/255,g[_+2]=a*r/255,g[_+3]=h*n/255;break;case"mask":g[_+3]=n}},getUniformLocations:function(t,e){return{uTransformMatrix:t.getUniformLocation(e,"uTransformMatrix"),uImage:t.getUniformLocation(e,"uImage")}},sendUniformData:function(t,e){var i=this.calculateMatrix();t.uniform1i(e.uImage,1),t.uniformMatrix3fv(e.uTransformMatrix,!1,i)},toObject:function(){return{type:this.type,image:this.image&&this.image.toObject(),mode:this.mode,alpha:this.alpha}}}),y.Image.filters.BlendImage.fromObject=function(i,r){y.Image.fromObject(i.image,function(t){var e=y.util.object.clone(i);e.image=t,r(new y.Image.filters.BlendImage(e))})}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var m=t.fabric||(t.fabric={}),j=Math.pow,A=Math.floor,M=Math.sqrt,F=Math.abs,c=Math.round,r=Math.sin,I=Math.ceil,e=m.Image.filters,i=m.util.createClass;e.Resize=i(e.BaseFilter,{type:"Resize",resizeType:"hermite",scaleX:1,scaleY:1,lanczosLobes:3,getUniformLocations:function(t,e){return{uDelta:t.getUniformLocation(e,"uDelta"),uTaps:t.getUniformLocation(e,"uTaps")}},sendUniformData:function(t,e){t.uniform2fv(e.uDelta,this.horizontal?[1/this.width,0]:[0,1/this.height]),t.uniform1fv(e.uTaps,this.taps)},retrieveShader:function(t){var e=this.getFilterWindow(),i=this.type+"_"+e;if(!t.programCache.hasOwnProperty(i)){var r=this.generateShader(e);t.programCache[i]=this.createProgram(t.context,r)}return t.programCache[i]},getFilterWindow:function(){var t=this.tempScale;return Math.ceil(this.lanczosLobes/t)},getTaps:function(){for(var t=this.lanczosCreate(this.lanczosLobes),e=this.tempScale,i=this.getFilterWindow(),r=new Array(i),n=1;n<=i;n++)r[n-1]=t(n*e);return r},generateShader:function(t){for(var e=new Array(t),i=this.fragmentSourceTOP,r=1;r<=t;r++)e[r-1]=r+".0 * uDelta";return i+="uniform float uTaps["+t+"];\n",i+="void main() {\n",i+="  vec4 color = texture2D(uTexture, vTexCoord);\n",i+="  float sum = 1.0;\n",e.forEach(function(t,e){i+="  color += texture2D(uTexture, vTexCoord + "+t+") * uTaps["+e+"];\n",i+="  color += texture2D(uTexture, vTexCoord - "+t+") * uTaps["+e+"];\n",i+="  sum += 2.0 * uTaps["+e+"];\n"}),i+="  gl_FragColor = color / sum;\n",i+="}"},fragmentSourceTOP:"precision highp float;\nuniform sampler2D uTexture;\nuniform vec2 uDelta;\nvarying vec2 vTexCoord;\n",applyTo:function(t){t.webgl?(t.passes++,this.width=t.sourceWidth,this.horizontal=!0,this.dW=Math.round(this.width*this.scaleX),this.dH=t.sourceHeight,this.tempScale=this.dW/this.width,this.taps=this.getTaps(),t.destinationWidth=this.dW,this._setupFrameBuffer(t),this.applyToWebGL(t),this._swapTextures(t),t.sourceWidth=t.destinationWidth,this.height=t.sourceHeight,this.horizontal=!1,this.dH=Math.round(this.height*this.scaleY),this.tempScale=this.dH/this.height,this.taps=this.getTaps(),t.destinationHeight=this.dH,this._setupFrameBuffer(t),this.applyToWebGL(t),this._swapTextures(t),t.sourceHeight=t.destinationHeight):this.applyTo2d(t)},isNeutralState:function(){return 1===this.scaleX&&1===this.scaleY},lanczosCreate:function(i){return function(t){if(i<=t||t<=-i)return 0;if(t<1.1920929e-7&&-1.1920929e-7<t)return 1;var e=(t*=Math.PI)/i;return r(t)/t*r(e)/e}},applyTo2d:function(t){var e=t.imageData,i=this.scaleX,r=this.scaleY;this.rcpScaleX=1/i,this.rcpScaleY=1/r;var n,s=e.width,o=e.height,a=c(s*i),h=c(o*r);"sliceHack"===this.resizeType?n=this.sliceByTwo(t,s,o,a,h):"hermite"===this.resizeType?n=this.hermiteFastResize(t,s,o,a,h):"bilinear"===this.resizeType?n=this.bilinearFiltering(t,s,o,a,h):"lanczos"===this.resizeType&&(n=this.lanczosResize(t,s,o,a,h)),t.imageData=n},sliceByTwo:function(t,e,i,r,n){var s,o,a=t.imageData,h=!1,c=!1,l=.5*e,u=.5*i,f=m.filterBackend.resources,d=0,g=0,p=e,v=0;for(f.sliceByTwo||(f.sliceByTwo=document.createElement("canvas")),((s=f.sliceByTwo).width<1.5*e||s.height<i)&&(s.width=1.5*e,s.height=i),(o=s.getContext("2d")).clearRect(0,0,1.5*e,i),o.putImageData(a,0,0),r=A(r),n=A(n);!h||!c;)i=u,r<A(.5*(e=l))?l=A(.5*l):(l=r,h=!0),n<A(.5*u)?u=A(.5*u):(u=n,c=!0),o.drawImage(s,d,g,e,i,p,v,l,u),d=p,g=v,v+=u;return o.getImageData(d,g,r,n)},lanczosResize:function(t,g,p,v,m){var b=t.imageData.data,_=t.ctx.createImageData(v,m),y=_.data,x=this.lanczosCreate(this.lanczosLobes),C=this.rcpScaleX,S=this.rcpScaleY,T=2/this.rcpScaleX,w=2/this.rcpScaleY,O=I(C*this.lanczosLobes/2),k=I(S*this.lanczosLobes/2),P={},D={},E={};return function t(e){var i,r,n,s,o,a,h,c,l,u,f;for(D.x=(e+.5)*C,E.x=A(D.x),i=0;i<m;i++){for(D.y=(i+.5)*S,E.y=A(D.y),l=c=h=a=o=0,r=E.x-O;r<=E.x+O;r++)if(!(r<0||g<=r)){u=A(1e3*F(r-D.x)),P[u]||(P[u]={});for(var d=E.y-k;d<=E.y+k;d++)d<0||p<=d||(f=A(1e3*F(d-D.y)),P[u][f]||(P[u][f]=x(M(j(u*T,2)+j(f*w,2))/1e3)),0<(n=P[u][f])&&(o+=n,a+=n*b[s=4*(d*g+r)],h+=n*b[s+1],c+=n*b[s+2],l+=n*b[s+3]))}y[s=4*(i*v+e)]=a/o,y[s+1]=h/o,y[s+2]=c/o,y[s+3]=l/o}return++e<v?t(e):_}(0)},bilinearFiltering:function(t,e,i,r,n){var s,o,a,h,c,l,u,f,d,g=0,p=this.rcpScaleX,v=this.rcpScaleY,m=4*(e-1),b=t.imageData.data,_=t.ctx.createImageData(r,n),y=_.data;for(a=0;a<n;a++)for(h=0;h<r;h++)for(c=p*h-(s=A(p*h)),l=v*a-(o=A(v*a)),d=4*(o*e+s),u=0;u<4;u++)f=b[d+u]*(1-c)*(1-l)+b[d+4+u]*c*(1-l)+b[d+m+u]*l*(1-c)+b[d+m+4+u]*c*l,y[g++]=f;return _},hermiteFastResize:function(t,e,i,r,n){for(var s=this.rcpScaleX,o=this.rcpScaleY,a=I(s/2),h=I(o/2),c=t.imageData.data,l=t.ctx.createImageData(r,n),u=l.data,f=0;f<n;f++)for(var d=0;d<r;d++){for(var g=4*(d+f*r),p=0,v=0,m=0,b=0,_=0,y=0,x=0,C=(f+.5)*o,S=A(f*o);S<(f+1)*o;S++)for(var T=F(C-(S+.5))/h,w=(d+.5)*s,O=T*T,k=A(d*s);k<(d+1)*s;k++){var P=F(w-(k+.5))/a,D=M(O+P*P);1<D&&D<-1||0<(p=2*D*D*D-3*D*D+1)&&(x+=p*c[(P=4*(k+S*e))+3],m+=p,c[P+3]<255&&(p=p*c[P+3]/250),b+=p*c[P],_+=p*c[P+1],y+=p*c[P+2],v+=p)}u[g]=b/v,u[g+1]=_/v,u[g+2]=y/v,u[g+3]=x/m}return l},toObject:function(){return{type:this.type,scaleX:this.scaleX,scaleY:this.scaleY,resizeType:this.resizeType,lanczosLobes:this.lanczosLobes}}}),m.Image.filters.Resize.fromObject=m.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Contrast=r(i.BaseFilter,{type:"Contrast",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uContrast;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nfloat contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));\ncolor.rgb = contrastF * (color.rgb - 0.5) + 0.5;\ngl_FragColor = color;\n}",contrast:0,mainParameter:"contrast",applyTo2d:function(t){if(0!==this.contrast){var e,i=t.imageData.data,r=i.length,n=Math.floor(255*this.contrast),s=259*(n+255)/(255*(259-n));for(e=0;e<r;e+=4)i[e]=s*(i[e]-128)+128,i[e+1]=s*(i[e+1]-128)+128,i[e+2]=s*(i[e+2]-128)+128}},getUniformLocations:function(t,e){return{uContrast:t.getUniformLocation(e,"uContrast")}},sendUniformData:function(t,e){t.uniform1f(e.uContrast,this.contrast)}}),e.Image.filters.Contrast.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Saturation=r(i.BaseFilter,{type:"Saturation",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform float uSaturation;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nfloat rgMax = max(color.r, color.g);\nfloat rgbMax = max(rgMax, color.b);\ncolor.r += rgbMax != color.r ? (rgbMax - color.r) * uSaturation : 0.00;\ncolor.g += rgbMax != color.g ? (rgbMax - color.g) * uSaturation : 0.00;\ncolor.b += rgbMax != color.b ? (rgbMax - color.b) * uSaturation : 0.00;\ngl_FragColor = color;\n}",saturation:0,mainParameter:"saturation",applyTo2d:function(t){if(0!==this.saturation){var e,i,r=t.imageData.data,n=r.length,s=-this.saturation;for(e=0;e<n;e+=4)i=Math.max(r[e],r[e+1],r[e+2]),r[e]+=i!==r[e]?(i-r[e])*s:0,r[e+1]+=i!==r[e+1]?(i-r[e+1])*s:0,r[e+2]+=i!==r[e+2]?(i-r[e+2])*s:0}},getUniformLocations:function(t,e){return{uSaturation:t.getUniformLocation(e,"uSaturation")}},sendUniformData:function(t,e){t.uniform1f(e.uSaturation,-this.saturation)}}),e.Image.filters.Saturation.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var g=t.fabric||(t.fabric={}),e=g.Image.filters,i=g.util.createClass;e.Blur=i(e.BaseFilter,{type:"Blur",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform vec2 uDelta;\nvarying vec2 vTexCoord;\nconst float nSamples = 15.0;\nvec3 v3offset = vec3(12.9898, 78.233, 151.7182);\nfloat random(vec3 scale) {\nreturn fract(sin(dot(gl_FragCoord.xyz, scale)) * 43758.5453);\n}\nvoid main() {\nvec4 color = vec4(0.0);\nfloat total = 0.0;\nfloat offset = random(v3offset);\nfor (float t = -nSamples; t <= nSamples; t++) {\nfloat percent = (t + offset - 0.5) / nSamples;\nfloat weight = 1.0 - abs(percent);\ncolor += texture2D(uTexture, vTexCoord + uDelta * percent) * weight;\ntotal += weight;\n}\ngl_FragColor = color / total;\n}",blur:0,mainParameter:"blur",applyTo:function(t){t.webgl?(this.aspectRatio=t.sourceWidth/t.sourceHeight,t.passes++,this._setupFrameBuffer(t),this.horizontal=!0,this.applyToWebGL(t),this._swapTextures(t),this._setupFrameBuffer(t),this.horizontal=!1,this.applyToWebGL(t),this._swapTextures(t)):this.applyTo2d(t)},applyTo2d:function(t){t.imageData=this.simpleBlur(t)},simpleBlur:function(t){var e,i,r=t.filterBackend.resources,n=t.imageData.width,s=t.imageData.height;r.blurLayer1||(r.blurLayer1=g.util.createCanvasElement(),r.blurLayer2=g.util.createCanvasElement()),e=r.blurLayer1,i=r.blurLayer2,e.width===n&&e.height===s||(i.width=e.width=n,i.height=e.height=s);var o,a,h,c,l=e.getContext("2d"),u=i.getContext("2d"),f=.06*this.blur*.5;for(l.putImageData(t.imageData,0,0),u.clearRect(0,0,n,s),c=-15;c<=15;c++)h=f*(a=c/15)*n+(o=(Math.random()-.5)/4),u.globalAlpha=1-Math.abs(a),u.drawImage(e,h,o),l.drawImage(i,0,0),u.globalAlpha=1,u.clearRect(0,0,i.width,i.height);for(c=-15;c<=15;c++)h=f*(a=c/15)*s+(o=(Math.random()-.5)/4),u.globalAlpha=1-Math.abs(a),u.drawImage(e,o,h),l.drawImage(i,0,0),u.globalAlpha=1,u.clearRect(0,0,i.width,i.height);t.ctx.drawImage(e,0,0);var d=t.ctx.getImageData(0,0,e.width,e.height);return l.globalAlpha=1,l.clearRect(0,0,e.width,e.height),d},getUniformLocations:function(t,e){return{delta:t.getUniformLocation(e,"uDelta")}},sendUniformData:function(t,e){var i=this.chooseRightDelta();t.uniform2fv(e.delta,i)},chooseRightDelta:function(){var t,e=1,i=[0,0];return this.horizontal?1<this.aspectRatio&&(e=1/this.aspectRatio):this.aspectRatio<1&&(e=this.aspectRatio),t=e*this.blur*.12,this.horizontal?i[0]=t:i[1]=t,i}}),e.Blur.fromObject=g.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.Image.filters,r=e.util.createClass;i.Gamma=r(i.BaseFilter,{type:"Gamma",fragmentSource:"precision highp float;\nuniform sampler2D uTexture;\nuniform vec3 uGamma;\nvarying vec2 vTexCoord;\nvoid main() {\nvec4 color = texture2D(uTexture, vTexCoord);\nvec3 correction = (1.0 / uGamma);\ncolor.r = pow(color.r, correction.r);\ncolor.g = pow(color.g, correction.g);\ncolor.b = pow(color.b, correction.b);\ngl_FragColor = color;\ngl_FragColor.rgb *= color.a;\n}",gamma:[1,1,1],mainParameter:"gamma",initialize:function(t){this.gamma=[1,1,1],i.BaseFilter.prototype.initialize.call(this,t)},applyTo2d:function(t){var e,i=t.imageData.data,r=this.gamma,n=i.length,s=1/r[0],o=1/r[1],a=1/r[2];for(this.rVals||(this.rVals=new Uint8Array(256),this.gVals=new Uint8Array(256),this.bVals=new Uint8Array(256)),e=0,n=256;e<n;e++)this.rVals[e]=255*Math.pow(e/255,s),this.gVals[e]=255*Math.pow(e/255,o),this.bVals[e]=255*Math.pow(e/255,a);for(e=0,n=i.length;e<n;e+=4)i[e]=this.rVals[i[e]],i[e+1]=this.gVals[i[e+1]],i[e+2]=this.bVals[i[e+2]]},getUniformLocations:function(t,e){return{uGamma:t.getUniformLocation(e,"uGamma")}},sendUniformData:function(t,e){t.uniform3fv(e.uGamma,this.gamma)}}),e.Image.filters.Gamma.fromObject=e.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var n=t.fabric||(t.fabric={}),e=n.Image.filters,i=n.util.createClass;e.Composed=i(e.BaseFilter,{type:"Composed",subFilters:[],initialize:function(t){this.callSuper("initialize",t),this.subFilters=this.subFilters.slice(0)},applyTo:function(e){e.passes+=this.subFilters.length-1,this.subFilters.forEach(function(t){t.applyTo(e)})},toObject:function(){return n.util.object.extend(this.callSuper("toObject"),{subFilters:this.subFilters.map(function(t){return t.toObject()})})},isNeutralState:function(){return!this.subFilters.some(function(t){return!t.isNeutralState()})}}),n.Image.filters.Composed.fromObject=function(t,e){var i=(t.subFilters||[]).map(function(t){return new n.Image.filters[t.type](t)}),r=new n.Image.filters.Composed({subFilters:i});return e&&e(r),r}}("undefined"!=typeof exports?exports:this),function(t){"use strict";var s=t.fabric||(t.fabric={}),e=s.Image.filters,i=s.util.createClass;e.HueRotation=i(e.ColorMatrix,{type:"HueRotation",rotation:0,mainParameter:"rotation",calculateMatrix:function(){var t=this.rotation*Math.PI,e=s.util.cos(t),i=s.util.sin(t),r=Math.sqrt(1/3)*i,n=1-e;this.matrix=[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0],this.matrix[0]=e+n/3,this.matrix[1]=1/3*n-r,this.matrix[2]=1/3*n+r,this.matrix[5]=1/3*n+r,this.matrix[6]=e+1/3*n,this.matrix[7]=1/3*n-r,this.matrix[10]=1/3*n-r,this.matrix[11]=1/3*n+r,this.matrix[12]=e+1/3*n},isNeutralState:function(t){return this.calculateMatrix(),e.BaseFilter.prototype.isNeutralState.call(this,t)},applyTo:function(t){this.calculateMatrix(),e.BaseFilter.prototype.applyTo.call(this,t)}}),s.Image.filters.HueRotation.fromObject=s.Image.filters.BaseFilter.fromObject}("undefined"!=typeof exports?exports:this),function(t){"use strict";var d=t.fabric||(t.fabric={}),g=d.util.object.clone;d.Text?d.warn("fabric.Text is already defined"):(d.Text=d.util.createClass(d.Object,{_dimensionAffectingProps:["fontSize","fontWeight","fontFamily","fontStyle","lineHeight","text","charSpacing","textAlign","styles"],_reNewline:/\r?\n/,_reSpacesAndTabs:/[ \t\r]/g,_reSpaceAndTab:/[ \t\r]/,_reWords:/\S+/g,type:"text",fontSize:40,fontWeight:"normal",fontFamily:"Times New Roman",underline:!1,overline:!1,linethrough:!1,textAlign:"left",fontStyle:"normal",lineHeight:1.16,superscript:{size:.6,baseline:-.35},subscript:{size:.6,baseline:.11},textBackgroundColor:"",stateProperties:d.Object.prototype.stateProperties.concat("fontFamily","fontWeight","fontSize","text","underline","overline","linethrough","textAlign","fontStyle","lineHeight","textBackgroundColor","charSpacing","styles"),cacheProperties:d.Object.prototype.cacheProperties.concat("fontFamily","fontWeight","fontSize","text","underline","overline","linethrough","textAlign","fontStyle","lineHeight","textBackgroundColor","charSpacing","styles"),stroke:null,shadow:null,_fontSizeFraction:.222,offsets:{underline:.1,linethrough:-.315,overline:-.88},_fontSizeMult:1.13,charSpacing:0,styles:null,_measuringContext:null,deltaY:0,_styleProperties:["stroke","strokeWidth","fill","fontFamily","fontSize","fontWeight","fontStyle","underline","overline","linethrough","deltaY","textBackgroundColor"],__charBounds:[],CACHE_FONT_SIZE:400,MIN_TEXT_WIDTH:2,initialize:function(t,e){this.styles=e&&e.styles||{},this.text=t,this.__skipDimension=!0,this.callSuper("initialize",e),this.__skipDimension=!1,this.initDimensions(),this.setCoords(),this.setupState({propertySet:"_dimensionAffectingProps"})},getMeasuringContext:function(){return d._measuringContext||(d._measuringContext=this.canvas&&this.canvas.contextCache||d.util.createCanvasElement().getContext("2d")),d._measuringContext},_splitText:function(){var t=this._splitTextIntoLines(this.text);return this.textLines=t.lines,this._textLines=t.graphemeLines,this._unwrappedTextLines=t._unwrappedLines,this._text=t.graphemeText,t},initDimensions:function(){this.__skipDimension||(this._splitText(),this._clearCache(),this.width=this.calcTextWidth()||this.cursorWidth||this.MIN_TEXT_WIDTH,-1!==this.textAlign.indexOf("justify")&&this.enlargeSpaces(),this.height=this.calcTextHeight(),this.saveState({propertySet:"_dimensionAffectingProps"}))},enlargeSpaces:function(){for(var t,e,i,r,n,s,o,a=0,h=this._textLines.length;a<h;a++)if(("justify"===this.textAlign||a!==h-1&&!this.isEndOfWrapping(a))&&(r=0,n=this._textLines[a],(e=this.getLineWidth(a))<this.width&&(o=this.textLines[a].match(this._reSpacesAndTabs)))){i=o.length,t=(this.width-e)/i;for(var c=0,l=n.length;c<=l;c++)s=this.__charBounds[a][c],this._reSpaceAndTab.test(n[c])?(s.width+=t,s.kernedWidth+=t,s.left+=r,r+=t):s.left+=r}},isEndOfWrapping:function(t){return t===this._textLines.length-1},missingNewlineOffset:function(){return 1},toString:function(){return"#<fabric.Text ("+this.complexity()+'): { "text": "'+this.text+'", "fontFamily": "'+this.fontFamily+'" }>'},_getCacheCanvasDimensions:function(){var t=this.callSuper("_getCacheCanvasDimensions"),e=this.fontSize;return t.width+=e*t.zoomX,t.height+=e*t.zoomY,t},_render:function(t){this._setTextStyles(t),this._renderTextLinesBackground(t),this._renderTextDecoration(t,"underline"),this._renderText(t),this._renderTextDecoration(t,"overline"),this._renderTextDecoration(t,"linethrough")},_renderText:function(t){"stroke"===this.paintFirst?(this._renderTextStroke(t),this._renderTextFill(t)):(this._renderTextFill(t),this._renderTextStroke(t))},_setTextStyles:function(t,e,i){t.textBaseline="alphabetic",t.font=this._getFontDeclaration(e,i)},calcTextWidth:function(){for(var t=this.getLineWidth(0),e=1,i=this._textLines.length;e<i;e++){var r=this.getLineWidth(e);t<r&&(t=r)}return t},_renderTextLine:function(t,e,i,r,n,s){this._renderChars(t,e,i,r,n,s)},_renderTextLinesBackground:function(t){if(this.textBackgroundColor||this.styleHas("textBackgroundColor")){for(var e,i,r,n,s,o,a=0,h=t.fillStyle,c=this._getLeftOffset(),l=this._getTopOffset(),u=0,f=0,d=0,g=this._textLines.length;d<g;d++)if(e=this.getHeightOfLine(d),this.textBackgroundColor||this.styleHas("textBackgroundColor",d)){r=this._textLines[d],i=this._getLineLeftOffset(d),u=f=0,n=this.getValueOfPropertyAt(d,0,"textBackgroundColor");for(var p=0,v=r.length;p<v;p++)s=this.__charBounds[d][p],(o=this.getValueOfPropertyAt(d,p,"textBackgroundColor"))!==n?((t.fillStyle=n)&&t.fillRect(c+i+u,l+a,f,e/this.lineHeight),u=s.left,f=s.width,n=o):f+=s.kernedWidth;o&&(t.fillStyle=o,t.fillRect(c+i+u,l+a,f,e/this.lineHeight)),a+=e}else a+=e;t.fillStyle=h,this._removeShadow(t)}},getFontCache:function(t){var e=t.fontFamily.toLowerCase();d.charWidthsCache[e]||(d.charWidthsCache[e]={});var i=d.charWidthsCache[e],r=t.fontStyle.toLowerCase()+"_"+(t.fontWeight+"").toLowerCase();return i[r]||(i[r]={}),i[r]},_applyCharStyles:function(t,e,i,r,n){this._setFillStyles(e,n),this._setStrokeStyles(e,n),e.font=this._getFontDeclaration(n)},_measureChar:function(t,e,i,r){var n,s,o,a,h=this.getFontCache(e),c=i+t,l=this._getFontDeclaration(e)===this._getFontDeclaration(r),u=e.fontSize/this.CACHE_FONT_SIZE;if(i&&void 0!==h[i]&&(o=h[i]),void 0!==h[t]&&(a=n=h[t]),l&&void 0!==h[c]&&(a=(s=h[c])-o),void 0===n||void 0===o||void 0===s){var f=this.getMeasuringContext();this._setTextStyles(f,e,!0)}return void 0===n&&(a=n=f.measureText(t).width,h[t]=n),void 0===o&&l&&i&&(o=f.measureText(i).width,h[i]=o),l&&void 0===s&&(s=f.measureText(c).width,a=(h[c]=s)-o),{width:n*u,kernedWidth:a*u}},getHeightOfChar:function(t,e){return this.getValueOfPropertyAt(t,e,"fontSize")},measureLine:function(t){var e=this._measureLine(t);return 0!==this.charSpacing&&(e.width-=this._getWidthOfCharSpacing()),e.width<0&&(e.width=0),e},_measureLine:function(t){var e,i,r,n,s=0,o=this._textLines[t],a=new Array(o.length);for(this.__charBounds[t]=a,e=0;e<o.length;e++)i=o[e],n=this._getGraphemeBox(i,t,e,r),s+=(a[e]=n).kernedWidth,r=i;return a[e]={left:n?n.left+n.width:0,width:0,kernedWidth:0,height:this.fontSize},{width:s,numOfSpaces:0}},_getGraphemeBox:function(t,e,i,r,n){var s,o=this.getCompleteStyleDeclaration(e,i),a=r?this.getCompleteStyleDeclaration(e,i-1):{},h=this._measureChar(t,o,r,a),c=h.kernedWidth,l=h.width;0!==this.charSpacing&&(l+=s=this._getWidthOfCharSpacing(),c+=s);var u={width:l,left:0,height:o.fontSize,kernedWidth:c,deltaY:o.deltaY};if(0<i&&!n){var f=this.__charBounds[e][i-1];u.left=f.left+f.width+h.kernedWidth-h.width}return u},getHeightOfLine:function(t){if(this.__lineHeights[t])return this.__lineHeights[t];for(var e=this._textLines[t],i=this.getHeightOfChar(t,0),r=1,n=e.length;r<n;r++)i=Math.max(this.getHeightOfChar(t,r),i);return this.__lineHeights[t]=i*this.lineHeight*this._fontSizeMult},calcTextHeight:function(){for(var t,e=0,i=0,r=this._textLines.length;i<r;i++)t=this.getHeightOfLine(i),e+=i===r-1?t/this.lineHeight:t;return e},_getLeftOffset:function(){return-this.width/2},_getTopOffset:function(){return-this.height/2},_renderTextCommon:function(t,e){t.save();for(var i=0,r=this._getLeftOffset(),n=this._getTopOffset(),s=this._applyPatternGradientTransform(t,"fillText"===e?this.fill:this.stroke),o=0,a=this._textLines.length;o<a;o++){var h=this.getHeightOfLine(o),c=h/this.lineHeight,l=this._getLineLeftOffset(o);this._renderTextLine(e,t,this._textLines[o],r+l-s.offsetX,n+i+c-s.offsetY,o),i+=h}t.restore()},_renderTextFill:function(t){(this.fill||this.styleHas("fill"))&&this._renderTextCommon(t,"fillText")},_renderTextStroke:function(t){(this.stroke&&0!==this.strokeWidth||!this.isEmptyStyles())&&(this.shadow&&!this.shadow.affectStroke&&this._removeShadow(t),t.save(),this._setLineDash(t,this.strokeDashArray),t.beginPath(),this._renderTextCommon(t,"strokeText"),t.closePath(),t.restore())},_renderChars:function(t,e,i,r,n,s){var o,a,h,c,l=this.getHeightOfLine(s),u=-1!==this.textAlign.indexOf("justify"),f="",d=0,g=!u&&0===this.charSpacing&&this.isEmptyStyles(s);if(e.save(),n-=l*this._fontSizeFraction/this.lineHeight,g)return this._renderChar(t,e,s,0,this.textLines[s],r,n,l),void e.restore();for(var p=0,v=i.length-1;p<=v;p++)c=p===v||this.charSpacing,f+=i[p],h=this.__charBounds[s][p],0===d?(r+=h.kernedWidth-h.width,d+=h.width):d+=h.kernedWidth,u&&!c&&this._reSpaceAndTab.test(i[p])&&(c=!0),c||(o=o||this.getCompleteStyleDeclaration(s,p),a=this.getCompleteStyleDeclaration(s,p+1),c=this._hasStyleChanged(o,a)),c&&(this._renderChar(t,e,s,p,f,r,n,l),f="",o=a,r+=d,d=0);e.restore()},_renderChar:function(t,e,i,r,n,s,o){var a=this._getStyleDeclaration(i,r),h=this.getCompleteStyleDeclaration(i,r),c="fillText"===t&&h.fill,l="strokeText"===t&&h.stroke&&h.strokeWidth;(l||c)&&(a&&e.save(),this._applyCharStyles(t,e,i,r,h),a&&a.textBackgroundColor&&this._removeShadow(e),a&&a.deltaY&&(o+=a.deltaY),c&&e.fillText(n,s,o),l&&e.strokeText(n,s,o),a&&e.restore())},setSuperscript:function(t,e){return this._setScript(t,e,this.superscript)},setSubscript:function(t,e){return this._setScript(t,e,this.subscript)},_setScript:function(t,e,i){var r=this.get2DCursorLocation(t,!0),n=this.getValueOfPropertyAt(r.lineIndex,r.charIndex,"fontSize"),s=this.getValueOfPropertyAt(r.lineIndex,r.charIndex,"deltaY"),o={fontSize:n*i.size,deltaY:s+n*i.baseline};return this.setSelectionStyles(o,t,e),this},_hasStyleChanged:function(t,e){return t.fill!==e.fill||t.stroke!==e.stroke||t.strokeWidth!==e.strokeWidth||t.fontSize!==e.fontSize||t.fontFamily!==e.fontFamily||t.fontWeight!==e.fontWeight||t.fontStyle!==e.fontStyle||t.deltaY!==e.deltaY},_hasStyleChangedForSvg:function(t,e){return this._hasStyleChanged(t,e)||t.overline!==e.overline||t.underline!==e.underline||t.linethrough!==e.linethrough},_getLineLeftOffset:function(t){var e=this.getLineWidth(t);return"center"===this.textAlign?(this.width-e)/2:"right"===this.textAlign?this.width-e:"justify-center"===this.textAlign&&this.isEndOfWrapping(t)?(this.width-e)/2:"justify-right"===this.textAlign&&this.isEndOfWrapping(t)?this.width-e:0},_clearCache:function(){this.__lineWidths=[],this.__lineHeights=[],this.__charBounds=[]},_shouldClearDimensionCache:function(){var t=this._forceClearCache;return t||(t=this.hasStateChanged("_dimensionAffectingProps")),t&&(this.dirty=!0,this._forceClearCache=!1),t},getLineWidth:function(t){return this.__lineWidths[t]?this.__lineWidths[t]:(e=""===this._textLines[t]?0:this.measureLine(t).width,this.__lineWidths[t]=e);var e},_getWidthOfCharSpacing:function(){return 0!==this.charSpacing?this.fontSize*this.charSpacing/1e3:0},getValueOfPropertyAt:function(t,e,i){var r=this._getStyleDeclaration(t,e);return r&&void 0!==r[i]?r[i]:this[i]},_renderTextDecoration:function(t,e){if(this[e]||this.styleHas(e)){for(var i,r,n,s,o,a,h,c,l,u,f,d,g,p,v,m,b=this._getLeftOffset(),_=this._getTopOffset(),y=this._getWidthOfCharSpacing(),x=0,C=this._textLines.length;x<C;x++)if(i=this.getHeightOfLine(x),this[e]||this.styleHas(e,x)){h=this._textLines[x],p=i/this.lineHeight,s=this._getLineLeftOffset(x),f=u=0,c=this.getValueOfPropertyAt(x,0,e),m=this.getValueOfPropertyAt(x,0,"fill"),l=_+p*(1-this._fontSizeFraction),r=this.getHeightOfChar(x,0),o=this.getValueOfPropertyAt(x,0,"deltaY");for(var S=0,T=h.length;S<T;S++)d=this.__charBounds[x][S],g=this.getValueOfPropertyAt(x,S,e),v=this.getValueOfPropertyAt(x,S,"fill"),n=this.getHeightOfChar(x,S),a=this.getValueOfPropertyAt(x,S,"deltaY"),(g!==c||v!==m||n!==r||a!==o)&&0<f?(t.fillStyle=m,c&&m&&t.fillRect(b+s+u,l+this.offsets[e]*r+o,f,this.fontSize/15),u=d.left,f=d.width,c=g,m=v,r=n,o=a):f+=d.kernedWidth;t.fillStyle=v,g&&v&&t.fillRect(b+s+u,l+this.offsets[e]*r+o,f-y,this.fontSize/15),_+=i}else _+=i;this._removeShadow(t)}},_getFontDeclaration:function(t,e){var i=t||this,r=this.fontFamily,n=-1<d.Text.genericFonts.indexOf(r.toLowerCase()),s=void 0===r||-1<r.indexOf("'")||-1<r.indexOf(",")||-1<r.indexOf('"')||n?i.fontFamily:'"'+i.fontFamily+'"';return[d.isLikelyNode?i.fontWeight:i.fontStyle,d.isLikelyNode?i.fontStyle:i.fontWeight,e?this.CACHE_FONT_SIZE+"px":i.fontSize+"px",s].join(" ")},render:function(t){this.visible&&(this.canvas&&this.canvas.skipOffscreen&&!this.group&&!this.isOnScreen()||(this._shouldClearDimensionCache()&&this.initDimensions(),this.callSuper("render",t)))},_splitTextIntoLines:function(t){for(var e=t.split(this._reNewline),i=new Array(e.length),r=["\n"],n=[],s=0;s<e.length;s++)i[s]=d.util.string.graphemeSplit(e[s]),n=n.concat(i[s],r);return n.pop(),{_unwrappedLines:i,lines:e,graphemeText:n,graphemeLines:i}},toObject:function(t){var e=["text","fontSize","fontWeight","fontFamily","fontStyle","lineHeight","underline","overline","linethrough","textAlign","textBackgroundColor","charSpacing"].concat(t),i=this.callSuper("toObject",e);return i.styles=g(this.styles,!0),i},set:function(t,e){this.callSuper("set",t,e);var i=!1;if("object"==typeof t)for(var r in t)i=i||-1!==this._dimensionAffectingProps.indexOf(r);else i=-1!==this._dimensionAffectingProps.indexOf(t);return i&&(this.initDimensions(),this.setCoords()),this},complexity:function(){return 1}}),d.Text.ATTRIBUTE_NAMES=d.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size letter-spacing text-decoration text-anchor".split(" ")),d.Text.DEFAULT_SVG_FONT_SIZE=16,d.Text.fromElement=function(t,e,i){if(!t)return e(null);var r=d.parseAttributes(t,d.Text.ATTRIBUTE_NAMES),n=r.textAnchor||"left";if((i=d.util.object.extend(i?g(i):{},r)).top=i.top||0,i.left=i.left||0,r.textDecoration){var s=r.textDecoration;-1!==s.indexOf("underline")&&(i.underline=!0),-1!==s.indexOf("overline")&&(i.overline=!0),-1!==s.indexOf("line-through")&&(i.linethrough=!0),delete i.textDecoration}"dx"in r&&(i.left+=r.dx),"dy"in r&&(i.top+=r.dy),"fontSize"in i||(i.fontSize=d.Text.DEFAULT_SVG_FONT_SIZE);var o="";"textContent"in t?o=t.textContent:"firstChild"in t&&null!==t.firstChild&&"data"in t.firstChild&&null!==t.firstChild.data&&(o=t.firstChild.data),o=o.replace(/^\s+|\s+$|\n+/g,"").replace(/\s+/g," ");var a=i.strokeWidth;i.strokeWidth=0;var h=new d.Text(o,i),c=h.getScaledHeight()/h.height,l=((h.height+h.strokeWidth)*h.lineHeight-h.height)*c,u=h.getScaledHeight()+l,f=0;"center"===n&&(f=h.getScaledWidth()/2),"right"===n&&(f=h.getScaledWidth()),h.set({left:h.left-f,top:h.top-(u-h.fontSize*(.07+h._fontSizeFraction))/h.lineHeight,strokeWidth:void 0!==a?a:1}),e(h)},d.Text.fromObject=function(t,e){return d.Object._fromObject("Text",t,e,"text")},d.Text.genericFonts=["sans-serif","serif","cursive","fantasy","monospace"],d.util.createAccessors&&d.util.createAccessors(d.Text))}("undefined"!=typeof exports?exports:this),fabric.util.object.extend(fabric.Text.prototype,{isEmptyStyles:function(t){if(!this.styles)return!0;if(void 0!==t&&!this.styles[t])return!0;var e=void 0===t?this.styles:{line:this.styles[t]};for(var i in e)for(var r in e[i])for(var n in e[i][r])return!1;return!0},styleHas:function(t,e){if(!this.styles||!t||""===t)return!1;if(void 0!==e&&!this.styles[e])return!1;var i=void 0===e?this.styles:{0:this.styles[e]};for(var r in i)for(var n in i[r])if(void 0!==i[r][n][t])return!0;return!1},cleanStyle:function(t){if(!this.styles||!t||""===t)return!1;var e,i,r=this.styles,n=0,s=!0,o=0;for(var a in r){for(var h in e=0,r[a]){var c;n++,(c=r[a][h]).hasOwnProperty(t)?(i?c[t]!==i&&(s=!1):i=c[t],c[t]===this[t]&&delete c[t]):s=!1,0!==Object.keys(c).length?e++:delete r[a][h]}0===e&&delete r[a]}for(var l=0;l<this._textLines.length;l++)o+=this._textLines[l].length;s&&n===o&&(this[t]=i,this.removeStyle(t))},removeStyle:function(t){if(this.styles&&t&&""!==t){var e,i,r,n=this.styles;for(i in n){for(r in e=n[i])delete e[r][t],0===Object.keys(e[r]).length&&delete e[r];0===Object.keys(e).length&&delete n[i]}}},_extendStyles:function(t,e){var i=this.get2DCursorLocation(t);this._getLineStyle(i.lineIndex)||this._setLineStyle(i.lineIndex),this._getStyleDeclaration(i.lineIndex,i.charIndex)||this._setStyleDeclaration(i.lineIndex,i.charIndex,{}),fabric.util.object.extend(this._getStyleDeclaration(i.lineIndex,i.charIndex),e)},get2DCursorLocation:function(t,e){void 0===t&&(t=this.selectionStart);for(var i=e?this._unwrappedTextLines:this._textLines,r=i.length,n=0;n<r;n++){if(t<=i[n].length)return{lineIndex:n,charIndex:t};t-=i[n].length+this.missingNewlineOffset(n)}return{lineIndex:n-1,charIndex:i[n-1].length<t?i[n-1].length:t}},getSelectionStyles:function(t,e,i){void 0===t&&(t=this.selectionStart||0),void 0===e&&(e=this.selectionEnd||t);for(var r=[],n=t;n<e;n++)r.push(this.getStyleAtPosition(n,i));return r},getStyleAtPosition:function(t,e){var i=this.get2DCursorLocation(t);return(e?this.getCompleteStyleDeclaration(i.lineIndex,i.charIndex):this._getStyleDeclaration(i.lineIndex,i.charIndex))||{}},setSelectionStyles:function(t,e,i){void 0===e&&(e=this.selectionStart||0),void 0===i&&(i=this.selectionEnd||e);for(var r=e;r<i;r++)this._extendStyles(r,t);return this._forceClearCache=!0,this},_getStyleDeclaration:function(t,e){var i=this.styles&&this.styles[t];return i?i[e]:null},getCompleteStyleDeclaration:function(t,e){for(var i,r=this._getStyleDeclaration(t,e)||{},n={},s=0;s<this._styleProperties.length;s++)n[i=this._styleProperties[s]]=void 0===r[i]?this[i]:r[i];return n},_setStyleDeclaration:function(t,e,i){this.styles[t][e]=i},_deleteStyleDeclaration:function(t,e){delete this.styles[t][e]},_getLineStyle:function(t){return!!this.styles[t]},_setLineStyle:function(t){this.styles[t]={}},_deleteLineStyle:function(t){delete this.styles[t]}}),function(){function n(t){t.textDecoration&&(-1<t.textDecoration.indexOf("underline")&&(t.underline=!0),-1<t.textDecoration.indexOf("line-through")&&(t.linethrough=!0),-1<t.textDecoration.indexOf("overline")&&(t.overline=!0),delete t.textDecoration)}fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,caching:!0,_reSpace:/\s|\n/,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,__widthOfSpace:[],inCompositionMode:!1,initialize:function(t,e){this.callSuper("initialize",t,e),this.initBehavior()},setSelectionStart:function(t){t=Math.max(t,0),this._updateAndFire("selectionStart",t)},setSelectionEnd:function(t){t=Math.min(t,this.text.length),this._updateAndFire("selectionEnd",t)},_updateAndFire:function(t,e){this[t]!==e&&(this._fireSelectionChanged(),this[t]=e),this._updateTextarea()},_fireSelectionChanged:function(){this.fire("selection:changed"),this.canvas&&this.canvas.fire("text:selection:changed",{target:this})},initDimensions:function(){this.isEditing&&this.initDelayedCursor(),this.clearContextTop(),this.callSuper("initDimensions")},render:function(t){this.clearContextTop(),this.callSuper("render",t),this.cursorOffsetCache={},this.renderCursorOrSelection()},_render:function(t){this.callSuper("_render",t)},clearContextTop:function(t){if(this.isEditing&&this.canvas&&this.canvas.contextTop){var e=this.canvas.contextTop,i=this.canvas.viewportTransform;e.save(),e.transform(i[0],i[1],i[2],i[3],i[4],i[5]),this.transform(e),this.transformMatrix&&e.transform.apply(e,this.transformMatrix),this._clearTextArea(e),t||e.restore()}},renderCursorOrSelection:function(){if(this.isEditing&&this.canvas&&this.canvas.contextTop){var t=this._getCursorBoundaries(),e=this.canvas.contextTop;this.clearContextTop(!0),this.selectionStart===this.selectionEnd?this.renderCursor(t,e):this.renderSelection(t,e),e.restore()}},_clearTextArea:function(t){var e=this.width+4,i=this.height+4;t.clearRect(-e/2,-i/2,e,i)},_getCursorBoundaries:function(t){void 0===t&&(t=this.selectionStart);var e=this._getLeftOffset(),i=this._getTopOffset(),r=this._getCursorBoundariesOffsets(t);return{left:e,top:i,leftOffset:r.left,topOffset:r.top}},_getCursorBoundariesOffsets:function(t){if(this.cursorOffsetCache&&"top"in this.cursorOffsetCache)return this.cursorOffsetCache;var e,i,r,n,s=0,o=0,a=this.get2DCursorLocation(t);r=a.charIndex,i=a.lineIndex;for(var h=0;h<i;h++)s+=this.getHeightOfLine(h);e=this._getLineLeftOffset(i);var c=this.__charBounds[i][r];return c&&(o=c.left),0!==this.charSpacing&&r===this._textLines[i].length&&(o-=this._getWidthOfCharSpacing()),n={top:s,left:e+(0<o?o:0)},this.cursorOffsetCache=n,this.cursorOffsetCache},renderCursor:function(t,e){var i=this.get2DCursorLocation(),r=i.lineIndex,n=0<i.charIndex?i.charIndex-1:0,s=this.getValueOfPropertyAt(r,n,"fontSize"),o=this.scaleX*this.canvas.getZoom(),a=this.cursorWidth/o,h=t.topOffset,c=this.getValueOfPropertyAt(r,n,"deltaY");h+=(1-this._fontSizeFraction)*this.getHeightOfLine(r)/this.lineHeight-s*(1-this._fontSizeFraction),this.inCompositionMode&&this.renderSelection(t,e),e.fillStyle=this.getValueOfPropertyAt(r,n,"fill"),e.globalAlpha=this.__isMousedown?1:this._currentCursorOpacity,e.fillRect(t.left+t.leftOffset-a/2,h+t.top+c,a,s)},renderSelection:function(t,e){for(var i=this.inCompositionMode?this.hiddenTextarea.selectionStart:this.selectionStart,r=this.inCompositionMode?this.hiddenTextarea.selectionEnd:this.selectionEnd,n=-1!==this.textAlign.indexOf("justify"),s=this.get2DCursorLocation(i),o=this.get2DCursorLocation(r),a=s.lineIndex,h=o.lineIndex,c=s.charIndex<0?0:s.charIndex,l=o.charIndex<0?0:o.charIndex,u=a;u<=h;u++){var f,d=this._getLineLeftOffset(u)||0,g=this.getHeightOfLine(u),p=0,v=0;if(u===a&&(p=this.__charBounds[a][c].left),a<=u&&u<h)v=n&&!this.isEndOfWrapping(u)?this.width:this.getLineWidth(u)||5;else if(u===h)if(0===l)v=this.__charBounds[h][l].left;else{var m=this._getWidthOfCharSpacing();v=this.__charBounds[h][l-1].left+this.__charBounds[h][l-1].width-m}f=g,(this.lineHeight<1||u===h&&1<this.lineHeight)&&(g/=this.lineHeight),this.inCompositionMode?(e.fillStyle=this.compositionColor||"black",e.fillRect(t.left+d+p,t.top+t.topOffset+g,v-p,1)):(e.fillStyle=this.selectionColor,e.fillRect(t.left+d+p,t.top+t.topOffset,v-p,g)),t.topOffset+=f}},getCurrentCharFontSize:function(){var t=this._getCurrentCharIndex();return this.getValueOfPropertyAt(t.l,t.c,"fontSize")},getCurrentCharColor:function(){var t=this._getCurrentCharIndex();return this.getValueOfPropertyAt(t.l,t.c,"fill")},_getCurrentCharIndex:function(){var t=this.get2DCursorLocation(this.selectionStart,!0),e=0<t.charIndex?t.charIndex-1:0;return{l:t.lineIndex,c:e}}}),fabric.IText.fromObject=function(t,e){if(n(t),t.styles)for(var i in t.styles)for(var r in t.styles[i])n(t.styles[i][r]);fabric.Object._fromObject("IText",t,e,"text")}}(),function(){var c=fabric.util.object.clone;fabric.util.object.extend(fabric.IText.prototype,{initBehavior:function(){this.initAddedHandler(),this.initRemovedHandler(),this.initCursorSelectionHandlers(),this.initDoubleClickSimulation(),this.mouseMoveHandler=this.mouseMoveHandler.bind(this)},onDeselect:function(){this.isEditing&&this.exitEditing(),this.selected=!1},initAddedHandler:function(){var e=this;this.on("added",function(){var t=e.canvas;t&&(t._hasITextHandlers||(t._hasITextHandlers=!0,e._initCanvasHandlers(t)),t._iTextInstances=t._iTextInstances||[],t._iTextInstances.push(e))})},initRemovedHandler:function(){var e=this;this.on("removed",function(){var t=e.canvas;t&&(t._iTextInstances=t._iTextInstances||[],fabric.util.removeFromArray(t._iTextInstances,e),0===t._iTextInstances.length&&(t._hasITextHandlers=!1,e._removeCanvasHandlers(t)))})},_initCanvasHandlers:function(t){t._mouseUpITextHandler=function(){t._iTextInstances&&t._iTextInstances.forEach(function(t){t.__isMousedown=!1})},t.on("mouse:up",t._mouseUpITextHandler)},_removeCanvasHandlers:function(t){t.off("mouse:up",t._mouseUpITextHandler)},_tick:function(){this._currentTickState=this._animateCursor(this,1,this.cursorDuration,"_onTickComplete")},_animateCursor:function(t,e,i,r){var n;return n={isAborted:!1,abort:function(){this.isAborted=!0}},t.animate("_currentCursorOpacity",e,{duration:i,onComplete:function(){n.isAborted||t[r]()},onChange:function(){t.canvas&&t.selectionStart===t.selectionEnd&&t.renderCursorOrSelection()},abort:function(){return n.isAborted}}),n},_onTickComplete:function(){var t=this;this._cursorTimeout1&&clearTimeout(this._cursorTimeout1),this._cursorTimeout1=setTimeout(function(){t._currentTickCompleteState=t._animateCursor(t,0,this.cursorDuration/2,"_tick")},100)},initDelayedCursor:function(t){var e=this,i=t?0:this.cursorDelay;this.abortCursorAnimation(),this._currentCursorOpacity=1,this._cursorTimeout2=setTimeout(function(){e._tick()},i)},abortCursorAnimation:function(){var t=this._currentTickState||this._currentTickCompleteState,e=this.canvas;this._currentTickState&&this._currentTickState.abort(),this._currentTickCompleteState&&this._currentTickCompleteState.abort(),clearTimeout(this._cursorTimeout1),clearTimeout(this._cursorTimeout2),this._currentCursorOpacity=0,t&&e&&e.clearContext(e.contextTop||e.contextContainer)},selectAll:function(){return this.selectionStart=0,this.selectionEnd=this._text.length,this._fireSelectionChanged(),this._updateTextarea(),this},getSelectedText:function(){return this._text.slice(this.selectionStart,this.selectionEnd).join("")},findWordBoundaryLeft:function(t){var e=0,i=t-1;if(this._reSpace.test(this._text[i]))for(;this._reSpace.test(this._text[i]);)e++,i--;for(;/\S/.test(this._text[i])&&-1<i;)e++,i--;return t-e},findWordBoundaryRight:function(t){var e=0,i=t;if(this._reSpace.test(this._text[i]))for(;this._reSpace.test(this._text[i]);)e++,i++;for(;/\S/.test(this._text[i])&&i<this.text.length;)e++,i++;return t+e},findLineBoundaryLeft:function(t){for(var e=0,i=t-1;!/\n/.test(this._text[i])&&-1<i;)e++,i--;return t-e},findLineBoundaryRight:function(t){for(var e=0,i=t;!/\n/.test(this._text[i])&&i<this.text.length;)e++,i++;return t+e},searchWordBoundary:function(t,e){for(var i=this._reSpace.test(this.text.charAt(t))?t-1:t,r=this.text.charAt(i),n=/[ \n\.,;!\?\-]/;!n.test(r)&&0<i&&i<this.text.length;)i+=e,r=this.text.charAt(i);return n.test(r)&&"\n"!==r&&(i+=1===e?0:1),i},selectWord:function(t){t=t||this.selectionStart;var e=this.searchWordBoundary(t,-1),i=this.searchWordBoundary(t,1);this.selectionStart=e,this.selectionEnd=i,this._fireSelectionChanged(),this._updateTextarea(),this.renderCursorOrSelection()},selectLine:function(t){t=t||this.selectionStart;var e=this.findLineBoundaryLeft(t),i=this.findLineBoundaryRight(t);return this.selectionStart=e,this.selectionEnd=i,this._fireSelectionChanged(),this._updateTextarea(),this},enterEditing:function(t){if(!this.isEditing&&this.editable)return this.canvas&&(this.canvas.calcOffset(),this.exitEditingOnOthers(this.canvas)),this.isEditing=!0,this.initHiddenTextarea(t),this.hiddenTextarea.focus(),this.hiddenTextarea.value=this.text,this._updateTextarea(),this._saveEditingProps(),this._setEditingProps(),this._textBeforeEdit=this.text,this._tick(),this.fire("editing:entered"),this._fireSelectionChanged(),this.canvas&&(this.canvas.fire("text:editing:entered",{target:this}),this.initMouseMoveHandler(),this.canvas.requestRenderAll()),this},exitEditingOnOthers:function(t){t._iTextInstances&&t._iTextInstances.forEach(function(t){t.selected=!1,t.isEditing&&t.exitEditing()})},initMouseMoveHandler:function(){this.canvas.on("mouse:move",this.mouseMoveHandler)},mouseMoveHandler:function(t){if(this.__isMousedown&&this.isEditing){var e=this.getSelectionStartFromPointer(t.e),i=this.selectionStart,r=this.selectionEnd;(e===this.__selectionStartOnMouseDown&&i!==r||i!==e&&r!==e)&&(e>this.__selectionStartOnMouseDown?(this.selectionStart=this.__selectionStartOnMouseDown,this.selectionEnd=e):(this.selectionStart=e,this.selectionEnd=this.__selectionStartOnMouseDown),this.selectionStart===i&&this.selectionEnd===r||(this.restartCursorIfNeeded(),this._fireSelectionChanged(),this._updateTextarea(),this.renderCursorOrSelection()))}},_setEditingProps:function(){this.hoverCursor="text",this.canvas&&(this.canvas.defaultCursor=this.canvas.moveCursor="text"),this.borderColor=this.editingBorderColor,this.hasControls=this.selectable=!1,this.lockMovementX=this.lockMovementY=!0},fromStringToGraphemeSelection:function(t,e,i){var r=i.slice(0,t),n=fabric.util.string.graphemeSplit(r).length;if(t===e)return{selectionStart:n,selectionEnd:n};var s=i.slice(t,e);return{selectionStart:n,selectionEnd:n+fabric.util.string.graphemeSplit(s).length}},fromGraphemeToStringSelection:function(t,e,i){var r=i.slice(0,t).join("").length;return t===e?{selectionStart:r,selectionEnd:r}:{selectionStart:r,selectionEnd:r+i.slice(t,e).join("").length}},_updateTextarea:function(){if(this.cursorOffsetCache={},this.hiddenTextarea){if(!this.inCompositionMode){var t=this.fromGraphemeToStringSelection(this.selectionStart,this.selectionEnd,this._text);this.hiddenTextarea.selectionStart=t.selectionStart,this.hiddenTextarea.selectionEnd=t.selectionEnd}this.updateTextareaPosition()}},updateFromTextArea:function(){if(this.hiddenTextarea){this.cursorOffsetCache={},this.text=this.hiddenTextarea.value,this._shouldClearDimensionCache()&&(this.initDimensions(),this.setCoords());var t=this.fromStringToGraphemeSelection(this.hiddenTextarea.selectionStart,this.hiddenTextarea.selectionEnd,this.hiddenTextarea.value);this.selectionEnd=this.selectionStart=t.selectionEnd,this.inCompositionMode||(this.selectionStart=t.selectionStart),this.updateTextareaPosition()}},updateTextareaPosition:function(){if(this.selectionStart===this.selectionEnd){var t=this._calcTextareaPosition();this.hiddenTextarea.style.left=t.left,this.hiddenTextarea.style.top=t.top}},_calcTextareaPosition:function(){if(!this.canvas)return{x:1,y:1};var t=this.inCompositionMode?this.compositionStart:this.selectionStart,e=this._getCursorBoundaries(t),i=this.get2DCursorLocation(t),r=i.lineIndex,n=i.charIndex,s=this.getValueOfPropertyAt(r,n,"fontSize")*this.lineHeight,o=e.leftOffset,a=this.calcTransformMatrix(),h={x:e.left+o,y:e.top+e.topOffset+s},c=this.canvas.upperCanvasEl,l=c.width,u=c.height,f=l-s,d=u-s,g=c.clientWidth/l,p=c.clientHeight/u;return h=fabric.util.transformPoint(h,a),(h=fabric.util.transformPoint(h,this.canvas.viewportTransform)).x*=g,h.y*=p,h.x<0&&(h.x=0),h.x>f&&(h.x=f),h.y<0&&(h.y=0),h.y>d&&(h.y=d),h.x+=this.canvas._offset.left,h.y+=this.canvas._offset.top,{left:h.x+"px",top:h.y+"px",fontSize:s+"px",charHeight:s}},_saveEditingProps:function(){this._savedProps={hasControls:this.hasControls,borderColor:this.borderColor,lockMovementX:this.lockMovementX,lockMovementY:this.lockMovementY,hoverCursor:this.hoverCursor,selectable:this.selectable,defaultCursor:this.canvas&&this.canvas.defaultCursor,moveCursor:this.canvas&&this.canvas.moveCursor}},_restoreEditingProps:function(){this._savedProps&&(this.hoverCursor=this._savedProps.hoverCursor,this.hasControls=this._savedProps.hasControls,this.borderColor=this._savedProps.borderColor,this.selectable=this._savedProps.selectable,this.lockMovementX=this._savedProps.lockMovementX,this.lockMovementY=this._savedProps.lockMovementY,this.canvas&&(this.canvas.defaultCursor=this._savedProps.defaultCursor,this.canvas.moveCursor=this._savedProps.moveCursor))},exitEditing:function(){var t=this._textBeforeEdit!==this.text;return this.selected=!1,this.isEditing=!1,this.selectionEnd=this.selectionStart,this.hiddenTextarea&&(this.hiddenTextarea.blur&&this.hiddenTextarea.blur(),this.canvas&&this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea),this.hiddenTextarea=null),this.abortCursorAnimation(),this._restoreEditingProps(),this._currentCursorOpacity=0,this._shouldClearDimensionCache()&&(this.initDimensions(),this.setCoords()),this.fire("editing:exited"),t&&this.fire("modified"),this.canvas&&(this.canvas.off("mouse:move",this.mouseMoveHandler),this.canvas.fire("text:editing:exited",{target:this}),t&&this.canvas.fire("object:modified",{target:this})),this},_removeExtraneousStyles:function(){for(var t in this.styles)this._textLines[t]||delete this.styles[t]},removeStyleFromTo:function(t,e){var i,r,n=this.get2DCursorLocation(t,!0),s=this.get2DCursorLocation(e,!0),o=n.lineIndex,a=n.charIndex,h=s.lineIndex,c=s.charIndex;if(o!==h){if(this.styles[o])for(i=a;i<this._unwrappedTextLines[o].length;i++)delete this.styles[o][i];if(this.styles[h])for(i=c;i<this._unwrappedTextLines[h].length;i++)(r=this.styles[h][i])&&(this.styles[o]||(this.styles[o]={}),this.styles[o][a+i-c]=r);for(i=o+1;i<=h;i++)delete this.styles[i];this.shiftLineStyles(h,o-h)}else if(this.styles[o]){r=this.styles[o];var l,u,f=c-a;for(i=a;i<c;i++)delete r[i];for(u in this.styles[o])c<=(l=parseInt(u,10))&&(r[l-f]=r[u],delete r[u])}},shiftLineStyles:function(t,e){var i=c(this.styles);for(var r in this.styles){var n=parseInt(r,10);t<n&&(this.styles[n+e]=i[n],i[n-e]||delete this.styles[n])}},restartCursorIfNeeded:function(){this._currentTickState&&!this._currentTickState.isAborted&&this._currentTickCompleteState&&!this._currentTickCompleteState.isAborted||this.initDelayedCursor()},insertNewlineStyleObject:function(t,e,i,r){var n,s={},o=!1;for(var a in i||(i=1),this.shiftLineStyles(t,i),this.styles[t]&&(n=this.styles[t][0===e?e:e-1]),this.styles[t]){var h=parseInt(a,10);e<=h&&(o=!0,s[h-e]=this.styles[t][a],delete this.styles[t][a])}for(o?this.styles[t+i]=s:delete this.styles[t+i];1<i;)i--,r&&r[i]?this.styles[t+i]={0:c(r[i])}:n?this.styles[t+i]={0:c(n)}:delete this.styles[t+i];this._forceClearCache=!0},insertCharStyleObject:function(t,e,i,r){this.styles||(this.styles={});var n=this.styles[t],s=n?c(n):{};for(var o in i||(i=1),s){var a=parseInt(o,10);e<=a&&(n[a+i]=s[a],s[a-i]||delete n[a])}if(this._forceClearCache=!0,r)for(;i--;)Object.keys(r[i]).length&&(this.styles[t]||(this.styles[t]={}),this.styles[t][e+i]=c(r[i]));else if(n)for(var h=n[e?e-1:1];h&&i--;)this.styles[t][e+i]=c(h)},insertNewStyleBlock:function(t,e,i){for(var r=this.get2DCursorLocation(e,!0),n=[0],s=0,o=0;o<t.length;o++)"\n"===t[o]?n[++s]=0:n[s]++;0<n[0]&&(this.insertCharStyleObject(r.lineIndex,r.charIndex,n[0],i),i=i&&i.slice(n[0]+1)),s&&this.insertNewlineStyleObject(r.lineIndex,r.charIndex+n[0],s);for(o=1;o<s;o++)0<n[o]?this.insertCharStyleObject(r.lineIndex+o,0,n[o],i):i&&(this.styles[r.lineIndex+o][0]=i[0]),i=i&&i.slice(n[o]+1);0<n[o]&&this.insertCharStyleObject(r.lineIndex+o,0,n[o],i)},setSelectionStartEndWithShift:function(t,e,i){i<=t?(e===t?this._selectionDirection="left":"right"===this._selectionDirection&&(this._selectionDirection="left",this.selectionEnd=t),this.selectionStart=i):t<i&&i<e?"right"===this._selectionDirection?this.selectionEnd=i:this.selectionStart=i:(e===t?this._selectionDirection="right":"left"===this._selectionDirection&&(this._selectionDirection="right",this.selectionStart=e),this.selectionEnd=i)},setSelectionInBoundaries:function(){var t=this.text.length;this.selectionStart>t?this.selectionStart=t:this.selectionStart<0&&(this.selectionStart=0),this.selectionEnd>t?this.selectionEnd=t:this.selectionEnd<0&&(this.selectionEnd=0)}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+new Date,this.__lastLastClickTime=+new Date,this.__lastPointer={},this.on("mousedown",this.onMouseDown)},onMouseDown:function(t){if(this.canvas){this.__newClickTime=+new Date;var e=t.pointer;this.isTripleClick(e)&&(this.fire("tripleclick",t),this._stopEvent(t.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=e,this.__lastIsEditing=this.isEditing,this.__lastSelected=this.selected}},isTripleClick:function(t){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===t.x&&this.__lastPointer.y===t.y},_stopEvent:function(t){t.preventDefault&&t.preventDefault(),t.stopPropagation&&t.stopPropagation()},initCursorSelectionHandlers:function(){this.initMousedownHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("mousedblclick",function(t){this.selectWord(this.getSelectionStartFromPointer(t.e))}),this.on("tripleclick",function(t){this.selectLine(this.getSelectionStartFromPointer(t.e))})},_mouseDownHandler:function(t){!this.canvas||!this.editable||t.e.button&&1!==t.e.button||(this.__isMousedown=!0,this.selected&&this.setCursorByClick(t.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.selectionStart===this.selectionEnd&&this.abortCursorAnimation(),this.renderCursorOrSelection()))},_mouseDownHandlerBefore:function(t){!this.canvas||!this.editable||t.e.button&&1!==t.e.button||this===this.canvas._activeObject&&(this.selected=!0)},initMousedownHandler:function(){this.on("mousedown",this._mouseDownHandler),this.on("mousedown:before",this._mouseDownHandlerBefore)},initMouseupHandler:function(){this.on("mouseup",this.mouseUpHandler)},mouseUpHandler:function(t){if(this.__isMousedown=!1,!(!this.editable||this.group||t.transform&&t.transform.actionPerformed||t.e.button&&1!==t.e.button)){if(this.canvas){var e=this.canvas._activeObject;if(e&&e!==this)return}this.__lastSelected&&!this.__corner?(this.selected=!1,this.__lastSelected=!1,this.enterEditing(t.e),this.selectionStart===this.selectionEnd?this.initDelayedCursor(!0):this.renderCursorOrSelection()):this.selected=!0}},setCursorByClick:function(t){var e=this.getSelectionStartFromPointer(t),i=this.selectionStart,r=this.selectionEnd;t.shiftKey?this.setSelectionStartEndWithShift(i,r,e):(this.selectionStart=e,this.selectionEnd=e),this.isEditing&&(this._fireSelectionChanged(),this._updateTextarea())},getSelectionStartFromPointer:function(t){for(var e=this.getLocalPointer(t),i=0,r=0,n=0,s=0,o=0,a=0,h=this._textLines.length;a<h&&n<=e.y;a++)n+=this.getHeightOfLine(a)*this.scaleY,0<(o=a)&&(s+=this._textLines[a-1].length+this.missingNewlineOffset(a-1));r=this._getLineLeftOffset(o)*this.scaleX;for(var c=0,l=this._textLines[o].length;c<l&&(i=r,(r+=this.__charBounds[o][c].kernedWidth*this.scaleX)<=e.x);c++)s++;return this._getNewSelectionStartFromOffset(e,i,r,s,l)},_getNewSelectionStartFromOffset:function(t,e,i,r,n){var s=t.x-e,o=i-t.x,a=r+(s<o||o<0?0:1);return this.flipX&&(a=n-a),a>this._text.length&&(a=this._text.length),a}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.setAttribute("autocorrect","off"),this.hiddenTextarea.setAttribute("autocomplete","off"),this.hiddenTextarea.setAttribute("spellcheck","false"),this.hiddenTextarea.setAttribute("data-fabric-hiddentextarea",""),this.hiddenTextarea.setAttribute("wrap","off");var t=this._calcTextareaPosition();this.hiddenTextarea.style.cssText="position: absolute; top: "+t.top+"; left: "+t.left+"; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px; paddingーtop: "+t.fontSize+";",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keyup",this.onKeyUp.bind(this)),fabric.util.addListener(this.hiddenTextarea,"input",this.onInput.bind(this)),fabric.util.addListener(this.hiddenTextarea,"copy",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"cut",this.copy.bind(this)),fabric.util.addListener(this.hiddenTextarea,"paste",this.paste.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionstart",this.onCompositionStart.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionupdate",this.onCompositionUpdate.bind(this)),fabric.util.addListener(this.hiddenTextarea,"compositionend",this.onCompositionEnd.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},keysMap:{9:"exitEditing",27:"exitEditing",33:"moveCursorUp",34:"moveCursorDown",35:"moveCursorRight",36:"moveCursorLeft",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown"},ctrlKeysMapUp:{67:"copy",88:"cut"},ctrlKeysMapDown:{65:"selectAll"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(t){if(this.isEditing&&!this.inCompositionMode){if(t.keyCode in this.keysMap)this[this.keysMap[t.keyCode]](t);else{if(!(t.keyCode in this.ctrlKeysMapDown&&(t.ctrlKey||t.metaKey)))return;this[this.ctrlKeysMapDown[t.keyCode]](t)}t.stopImmediatePropagation(),t.preventDefault(),33<=t.keyCode&&t.keyCode<=40?(this.clearContextTop(),this.renderCursorOrSelection()):this.canvas&&this.canvas.requestRenderAll()}},onKeyUp:function(t){!this.isEditing||this._copyDone||this.inCompositionMode?this._copyDone=!1:t.keyCode in this.ctrlKeysMapUp&&(t.ctrlKey||t.metaKey)&&(this[this.ctrlKeysMapUp[t.keyCode]](t),t.stopImmediatePropagation(),t.preventDefault(),this.canvas&&this.canvas.requestRenderAll())},onInput:function(t){var e=this.fromPaste;if(this.fromPaste=!1,t&&t.stopPropagation(),this.isEditing){var i,r,n=this._splitTextIntoLines(this.hiddenTextarea.value).graphemeText,s=this._text.length,o=n.length,a=o-s;if(""===this.hiddenTextarea.value)return this.styles={},this.updateFromTextArea(),this.fire("changed"),void(this.canvas&&(this.canvas.fire("text:changed",{target:this}),this.canvas.requestRenderAll()));var h=this.fromStringToGraphemeSelection(this.hiddenTextarea.selectionStart,this.hiddenTextarea.selectionEnd,this.hiddenTextarea.value),c=this.selectionStart>h.selectionStart;this.selectionStart!==this.selectionEnd?(i=this._text.slice(this.selectionStart,this.selectionEnd),a+=this.selectionEnd-this.selectionStart):o<s&&(i=c?this._text.slice(this.selectionEnd+a,this.selectionEnd):this._text.slice(this.selectionStart,this.selectionStart-a)),r=n.slice(h.selectionEnd-a,h.selectionEnd),i&&i.length&&(this.selectionStart!==this.selectionEnd?this.removeStyleFromTo(this.selectionStart,this.selectionEnd):c?this.removeStyleFromTo(this.selectionEnd-i.length,this.selectionEnd):this.removeStyleFromTo(this.selectionEnd,this.selectionEnd+i.length)),r.length&&(e&&r.join("")===fabric.copiedText&&!fabric.disableStyleCopyPaste?this.insertNewStyleBlock(r,this.selectionStart,fabric.copiedTextStyle):this.insertNewStyleBlock(r,this.selectionStart)),this.updateFromTextArea(),this.fire("changed"),this.canvas&&(this.canvas.fire("text:changed",{target:this}),this.canvas.requestRenderAll())}},onCompositionStart:function(){this.inCompositionMode=!0},onCompositionEnd:function(){this.inCompositionMode=!1},onCompositionUpdate:function(t){this.compositionStart=t.target.selectionStart,this.compositionEnd=t.target.selectionEnd,this.updateTextareaPosition()},copy:function(){this.selectionStart!==this.selectionEnd&&(fabric.copiedText=this.getSelectedText(),fabric.disableStyleCopyPaste?fabric.copiedTextStyle=null:fabric.copiedTextStyle=this.getSelectionStyles(this.selectionStart,this.selectionEnd,!0),this._copyDone=!0)},paste:function(){this.fromPaste=!0},_getClipboardData:function(t){return t&&t.clipboardData||fabric.window.clipboardData},_getWidthBeforeCursor:function(t,e){var i,r=this._getLineLeftOffset(t);return 0<e&&(r+=(i=this.__charBounds[t][e-1]).left+i.width),r},getDownCursorOffset:function(t,e){var i=this._getSelectionForOffset(t,e),r=this.get2DCursorLocation(i),n=r.lineIndex;if(n===this._textLines.length-1||t.metaKey||34===t.keyCode)return this._text.length-i;var s=r.charIndex,o=this._getWidthBeforeCursor(n,s),a=this._getIndexOnLine(n+1,o);return this._textLines[n].slice(s).length+a+1+this.missingNewlineOffset(n)},_getSelectionForOffset:function(t,e){return t.shiftKey&&this.selectionStart!==this.selectionEnd&&e?this.selectionEnd:this.selectionStart},getUpCursorOffset:function(t,e){var i=this._getSelectionForOffset(t,e),r=this.get2DCursorLocation(i),n=r.lineIndex;if(0===n||t.metaKey||33===t.keyCode)return-i;var s=r.charIndex,o=this._getWidthBeforeCursor(n,s),a=this._getIndexOnLine(n-1,o),h=this._textLines[n].slice(0,s),c=this.missingNewlineOffset(n-1);return-this._textLines[n-1].length+a-h.length+(1-c)},_getIndexOnLine:function(t,e){for(var i,r,n=this._textLines[t],s=this._getLineLeftOffset(t),o=0,a=0,h=n.length;a<h;a++)if(e<(s+=i=this.__charBounds[t][a].width)){r=!0;var c=s-i,l=s,u=Math.abs(c-e);o=Math.abs(l-e)<u?a:a-1;break}return r||(o=n.length-1),o},moveCursorDown:function(t){this.selectionStart>=this._text.length&&this.selectionEnd>=this._text.length||this._moveCursorUpOrDown("Down",t)},moveCursorUp:function(t){0===this.selectionStart&&0===this.selectionEnd||this._moveCursorUpOrDown("Up",t)},_moveCursorUpOrDown:function(t,e){var i=this["get"+t+"CursorOffset"](e,"right"===this._selectionDirection);e.shiftKey?this.moveCursorWithShift(i):this.moveCursorWithoutShift(i),0!==i&&(this.setSelectionInBoundaries(),this.abortCursorAnimation(),this._currentCursorOpacity=1,this.initDelayedCursor(),this._fireSelectionChanged(),this._updateTextarea())},moveCursorWithShift:function(t){var e="left"===this._selectionDirection?this.selectionStart+t:this.selectionEnd+t;return this.setSelectionStartEndWithShift(this.selectionStart,this.selectionEnd,e),0!==t},moveCursorWithoutShift:function(t){return t<0?(this.selectionStart+=t,this.selectionEnd=this.selectionStart):(this.selectionEnd+=t,this.selectionStart=this.selectionEnd),0!==t},moveCursorLeft:function(t){0===this.selectionStart&&0===this.selectionEnd||this._moveCursorLeftOrRight("Left",t)},_move:function(t,e,i){var r;if(t.altKey)r=this["findWordBoundary"+i](this[e]);else{if(!t.metaKey&&35!==t.keyCode&&36!==t.keyCode)return this[e]+="Left"===i?-1:1,!0;r=this["findLineBoundary"+i](this[e])}if(void 0!==typeof r&&this[e]!==r)return this[e]=r,!0},_moveLeft:function(t,e){return this._move(t,e,"Left")},_moveRight:function(t,e){return this._move(t,e,"Right")},moveCursorLeftWithoutShift:function(t){var e=!0;return this._selectionDirection="left",this.selectionEnd===this.selectionStart&&0!==this.selectionStart&&(e=this._moveLeft(t,"selectionStart")),this.selectionEnd=this.selectionStart,e},moveCursorLeftWithShift:function(t){return"right"===this._selectionDirection&&this.selectionStart!==this.selectionEnd?this._moveLeft(t,"selectionEnd"):0!==this.selectionStart?(this._selectionDirection="left",this._moveLeft(t,"selectionStart")):void 0},moveCursorRight:function(t){this.selectionStart>=this._text.length&&this.selectionEnd>=this._text.length||this._moveCursorLeftOrRight("Right",t)},_moveCursorLeftOrRight:function(t,e){var i="moveCursor"+t+"With";this._currentCursorOpacity=1,e.shiftKey?i+="Shift":i+="outShift",this[i](e)&&(this.abortCursorAnimation(),this.initDelayedCursor(),this._fireSelectionChanged(),this._updateTextarea())},moveCursorRightWithShift:function(t){return"left"===this._selectionDirection&&this.selectionStart!==this.selectionEnd?this._moveRight(t,"selectionStart"):this.selectionEnd!==this._text.length?(this._selectionDirection="right",this._moveRight(t,"selectionEnd")):void 0},moveCursorRightWithoutShift:function(t){var e=!0;return this._selectionDirection="right",this.selectionStart===this.selectionEnd?(e=this._moveRight(t,"selectionStart"),this.selectionEnd=this.selectionStart):this.selectionStart=this.selectionEnd,e},removeChars:function(t,e){void 0===e&&(e=t+1),this.removeStyleFromTo(t,e),this._text.splice(t,e-t),this.text=this._text.join(""),this.set("dirty",!0),this._shouldClearDimensionCache()&&(this.initDimensions(),this.setCoords()),this._removeExtraneousStyles()},insertChars:function(t,e,i,r){void 0===r&&(r=i),i<r&&this.removeStyleFromTo(i,r);var n=fabric.util.string.graphemeSplit(t);this.insertNewStyleBlock(n,i,e),this._text=[].concat(this._text.slice(0,i),n,this._text.slice(r)),this.text=this._text.join(""),this.set("dirty",!0),this._shouldClearDimensionCache()&&(this.initDimensions(),this.setCoords()),this._removeExtraneousStyles()}}),function(){var l=fabric.util.toFixed,u=/  +/g;fabric.util.object.extend(fabric.Text.prototype,{_toSVG:function(){var t=this._getSVGLeftTopOffsets(),e=this._getSVGTextAndBg(t.textTop,t.textLeft);return this._wrapSVGTextAndBg(e)},toSVG:function(t){return this._createBaseSVGMarkup(this._toSVG(),{reviver:t,noStyle:!0,withShadow:!0})},_getSVGLeftTopOffsets:function(){return{textLeft:-this.width/2,textTop:-this.height/2,lineTop:this.getHeightOfLine(0)}},_wrapSVGTextAndBg:function(t){var e=this.getSvgTextDecoration(this);return[t.textBgRects.join(""),'\t\t<text xml:space="preserve" ',this.fontFamily?'font-family="'+this.fontFamily.replace(/"/g,"'")+'" ':"",this.fontSize?'font-size="'+this.fontSize+'" ':"",this.fontStyle?'font-style="'+this.fontStyle+'" ':"",this.fontWeight?'font-weight="'+this.fontWeight+'" ':"",e?'text-decoration="'+e+'" ':"",'style="',this.getSvgStyles(!0),'"',this.addPaintOrder()," >",t.textSpans.join(""),"</text>\n"]},_getSVGTextAndBg:function(t,e){var i,r=[],n=[],s=t;this._setSVGBg(n);for(var o=0,a=this._textLines.length;o<a;o++)i=this._getLineLeftOffset(o),(this.textBackgroundColor||this.styleHas("textBackgroundColor",o))&&this._setSVGTextLineBg(n,o,e+i,s),this._setSVGTextLineText(r,o,e+i,s),s+=this.getHeightOfLine(o);return{textSpans:r,textBgRects:n}},_createTextCharSpan:function(t,e,i,r){var n=t!==t.trim()||t.match(u),s=this.getSvgSpanStyles(e,n),o=s?'style="'+s+'"':"",a=e.deltaY,h="",c=fabric.Object.NUM_FRACTION_DIGITS;return a&&(h=' dy="'+l(a,c)+'" '),['<tspan x="',l(i,c),'" y="',l(r,c),'" ',h,o,">",fabric.util.string.escapeXml(t),"</tspan>"].join("")},_setSVGTextLineText:function(t,e,i,r){var n,s,o,a,h,c=this.getHeightOfLine(e),l=-1!==this.textAlign.indexOf("justify"),u="",f=0,d=this._textLines[e];r+=c*(1-this._fontSizeFraction)/this.lineHeight;for(var g=0,p=d.length-1;g<=p;g++)h=g===p||this.charSpacing,u+=d[g],o=this.__charBounds[e][g],0===f?(i+=o.kernedWidth-o.width,f+=o.width):f+=o.kernedWidth,l&&!h&&this._reSpaceAndTab.test(d[g])&&(h=!0),h||(n=n||this.getCompleteStyleDeclaration(e,g),s=this.getCompleteStyleDeclaration(e,g+1),h=this._hasStyleChangedForSvg(n,s)),h&&(a=this._getStyleDeclaration(e,g)||{},t.push(this._createTextCharSpan(u,a,i,r)),u="",n=s,i+=f,f=0)},_pushTextBgRect:function(t,e,i,r,n,s){var o=fabric.Object.NUM_FRACTION_DIGITS;t.push("\t\t<rect ",this._getFillAttributes(e),' x="',l(i,o),'" y="',l(r,o),'" width="',l(n,o),'" height="',l(s,o),'"></rect>\n')},_setSVGTextLineBg:function(t,e,i,r){for(var n,s,o=this._textLines[e],a=this.getHeightOfLine(e)/this.lineHeight,h=0,c=0,l=this.getValueOfPropertyAt(e,0,"textBackgroundColor"),u=0,f=o.length;u<f;u++)n=this.__charBounds[e][u],(s=this.getValueOfPropertyAt(e,u,"textBackgroundColor"))!==l?(l&&this._pushTextBgRect(t,l,i+c,r,h,a),c=n.left,h=n.width,l=s):h+=n.kernedWidth;s&&this._pushTextBgRect(t,s,i+c,r,h,a)},_getFillAttributes:function(t){var e=t&&"string"==typeof t?new fabric.Color(t):"";return e&&e.getSource()&&1!==e.getAlpha()?'opacity="'+e.getAlpha()+'" fill="'+e.setAlpha(1).toRgb()+'"':'fill="'+t+'"'},_getSVGLineTopOffset:function(t){for(var e,i=0,r=0;r<t;r++)i+=this.getHeightOfLine(r);return e=this.getHeightOfLine(r),{lineTop:i,offset:(this._fontSizeMult-this._fontSizeFraction)*e/(this.lineHeight*this._fontSizeMult)}},getSvgStyles:function(t){return fabric.Object.prototype.getSvgStyles.call(this,t)+" white-space: pre;"}})}(),function(t){"use strict";var b=t.fabric||(t.fabric={});b.Textbox=b.util.createClass(b.IText,b.Observable,{type:"textbox",minWidth:20,dynamicMinWidth:2,__cachedLines:null,lockScalingFlip:!0,noScaleCache:!1,_dimensionAffectingProps:b.Text.prototype._dimensionAffectingProps.concat("width"),_wordJoiners:/[ \t\r]/,splitByGrapheme:!1,initDimensions:function(){this.__skipDimension||(this.isEditing&&this.initDelayedCursor(),this.clearContextTop(),this._clearCache(),this.dynamicMinWidth=0,this._styleMap=this._generateStyleMap(this._splitText()),this.dynamicMinWidth>this.width&&this._set("width",this.dynamicMinWidth),-1!==this.textAlign.indexOf("justify")&&this.enlargeSpaces(),this.height=this.calcTextHeight(),this.saveState({propertySet:"_dimensionAffectingProps"}))},_generateStyleMap:function(t){for(var e=0,i=0,r=0,n={},s=0;s<t.graphemeLines.length;s++)"\n"===t.graphemeText[r]&&0<s?(i=0,r++,e++):!this.splitByGrapheme&&this._reSpaceAndTab.test(t.graphemeText[r])&&0<s&&(i++,r++),n[s]={line:e,offset:i},r+=t.graphemeLines[s].length,i+=t.graphemeLines[s].length;return n},styleHas:function(t,e){if(this._styleMap&&!this.isWrapping){var i=this._styleMap[e];i&&(e=i.line)}return b.Text.prototype.styleHas.call(this,t,e)},isEmptyStyles:function(t){var e,i,r=0,n=!1,s=this._styleMap[t],o=this._styleMap[t+1];for(var a in s&&(t=s.line,r=s.offset),o&&(n=o.line===t,e=o.offset),i=void 0===t?this.styles:{line:this.styles[t]})for(var h in i[a])if(r<=h&&(!n||h<e))for(var c in i[a][h])return!1;return!0},_getStyleDeclaration:function(t,e){if(this._styleMap&&!this.isWrapping){var i=this._styleMap[t];if(!i)return null;t=i.line,e=i.offset+e}return this.callSuper("_getStyleDeclaration",t,e)},_setStyleDeclaration:function(t,e,i){var r=this._styleMap[t];t=r.line,e=r.offset+e,this.styles[t][e]=i},_deleteStyleDeclaration:function(t,e){var i=this._styleMap[t];t=i.line,e=i.offset+e,delete this.styles[t][e]},_getLineStyle:function(t){var e=this._styleMap[t];return!!this.styles[e.line]},_setLineStyle:function(t){var e=this._styleMap[t];this.styles[e.line]={}},_wrapText:function(t,e){var i,r=[];for(this.isWrapping=!0,i=0;i<t.length;i++)r=r.concat(this._wrapLine(t[i],i,e));return this.isWrapping=!1,r},_measureWord:function(t,e,i){var r,n=0;i=i||0;for(var s=0,o=t.length;s<o;s++){n+=this._getGraphemeBox(t[s],e,s+i,r,!0).kernedWidth,r=t[s]}return n},_wrapLine:function(t,e,i,r){var n=0,s=this.splitByGrapheme,o=[],a=[],h=s?b.util.string.graphemeSplit(t):t.split(this._wordJoiners),c="",l=0,u=s?"":" ",f=0,d=0,g=0,p=!0,v=s?0:this._getWidthOfCharSpacing();r=r||0;0===h.length&&h.push([]),i-=r;for(var m=0;m<h.length;m++)c=s?h[m]:b.util.string.graphemeSplit(h[m]),f=this._measureWord(c,e,l),l+=c.length,i<=(n+=d+f-v)&&!p?(o.push(a),a=[],n=f,p=!0):n+=v,p||s||a.push(u),a=a.concat(c),d=this._measureWord([u],e,l),l++,p=!1,g<f&&(g=f);return m&&o.push(a),g+r>this.dynamicMinWidth&&(this.dynamicMinWidth=g-v+r),o},isEndOfWrapping:function(t){return!this._styleMap[t+1]||this._styleMap[t+1].line!==this._styleMap[t].line},missingNewlineOffset:function(t){return this.splitByGrapheme?this.isEndOfWrapping(t)?1:0:1},_splitTextIntoLines:function(t){for(var e=b.Text.prototype._splitTextIntoLines.call(this,t),i=this._wrapText(e.lines,this.width),r=new Array(i.length),n=0;n<i.length;n++)r[n]=i[n].join("");return e.lines=r,e.graphemeLines=i,e},getMinWidth:function(){return Math.max(this.minWidth,this.dynamicMinWidth)},_removeExtraneousStyles:function(){var t={};for(var e in this._styleMap)this._textLines[e]&&(t[this._styleMap[e].line]=1);for(var e in this.styles)t[e]||delete this.styles[e]},toObject:function(t){return this.callSuper("toObject",["minWidth","splitByGrapheme"].concat(t))}}),b.Textbox.fromObject=function(t,e){return b.Object._fromObject("Textbox",t,e,"text")}}("undefined"!=typeof exports?exports:this);
\ No newline at end of file
diff --git a/templates/impress.cs/js/impress.js b/templates/impress.cs/js/impress.js
new file mode 100644
index 0000000000000000000000000000000000000000..05cd6e0ef890561c70a6a744ae501064fd824790
--- /dev/null
+++ b/templates/impress.cs/js/impress.js
@@ -0,0 +1,4321 @@
+// This file was automatically generated from files in src/ directory.
+
+/*! Licensed under MIT License - http://github.com/impress/impress.js */
+/**
+ * impress.js
+ *
+ * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
+ * in modern browsers and inspired by the idea behind prezi.com.
+ *
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2018 Henrik Ingo (@henrikingo)
+ *
+ * Released under the MIT License.
+ *
+ * ------------------------------------------------
+ *  author:  Bartek Szopka, Henrik Ingo
+ *  version: 1.0.0
+ *  url:     http://impress.js.org
+ *  source:  http://github.com/impress/impress.js/
+ */
+
+// You are one of those who like to know how things work inside?
+// Let me show you the cogs that make impress.js run...
+( function( document, window ) {
+    "use strict";
+    var lib;
+
+    // HELPER FUNCTIONS
+
+    // `pfx` is a function that takes a standard CSS property name as a parameter
+    // and returns it's prefixed version valid for current browser it runs in.
+    // The code is heavily inspired by Modernizr http://www.modernizr.com/
+    var pfx = ( function() {
+
+        var style = document.createElement( "dummy" ).style,
+            prefixes = "Webkit Moz O ms Khtml".split( " " ),
+            memory = {};
+
+        return function( prop ) {
+            if ( typeof memory[ prop ] === "undefined" ) {
+
+                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+                    props   = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
+
+                memory[ prop ] = null;
+                for ( var i in props ) {
+                    if ( style[ props[ i ] ] !== undefined ) {
+                        memory[ prop ] = props[ i ];
+                        break;
+                    }
+                }
+
+            }
+
+            return memory[ prop ];
+        };
+
+    } )();
+
+    var validateOrder = function( order, fallback ) {
+        var validChars = "xyz";
+        var returnStr = "";
+        if ( typeof order === "string" ) {
+            for ( var i in order.split( "" ) ) {
+                if ( validChars.indexOf( order[ i ] ) >= 0 ) {
+                    returnStr += order[ i ];
+
+                    // Each of x,y,z can be used only once.
+                    validChars = validChars.split( order[ i ] ).join( "" );
+                }
+            }
+        }
+        if ( returnStr ) {
+            return returnStr;
+        } else if ( fallback !== undefined ) {
+            return fallback;
+        } else {
+            return "xyz";
+        }
+    };
+
+    // `css` function applies the styles given in `props` object to the element
+    // given as `el`. It runs all property names through `pfx` function to make
+    // sure proper prefixed version of the property is used.
+    var css = function( el, props ) {
+        var key, pkey;
+        for ( key in props ) {
+            if ( props.hasOwnProperty( key ) ) {
+                pkey = pfx( key );
+                if ( pkey !== null ) {
+                    el.style[ pkey ] = props[ key ];
+                }
+            }
+        }
+        return el;
+    };
+
+    // `translate` builds a translate transform string for given data.
+    var translate = function( t ) {
+        return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
+    };
+
+    // `rotate` builds a rotate transform string for given data.
+    // By default the rotations are in X Y Z order that can be reverted by passing `true`
+    // as second parameter.
+    var rotate = function( r, revert ) {
+        var order = r.order ? r.order : "xyz";
+        var css = "";
+        var axes = order.split( "" );
+        if ( revert ) {
+            axes = axes.reverse();
+        }
+
+        for ( var i = 0; i < axes.length; i++ ) {
+            css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)";
+        }
+        return css;
+    };
+
+    // `scale` builds a scale transform string for given data.
+    var scale = function( s ) {
+        return " scale(" + s + ") ";
+    };
+
+    // `computeWindowScale` counts the scale factor between window size and size
+    // defined for the presentation in the config.
+    var computeWindowScale = function( config ) {
+        var hScale = window.innerHeight / config.height,
+            wScale = window.innerWidth / config.width,
+            scale = hScale > wScale ? wScale : hScale;
+
+        if ( config.maxScale && scale > config.maxScale ) {
+            scale = config.maxScale;
+        }
+
+        if ( config.minScale && scale < config.minScale ) {
+            scale = config.minScale;
+        }
+
+        return scale;
+    };
+
+    // CHECK SUPPORT
+    var body = document.body;
+    var impressSupported =
+
+                          // Browser should support CSS 3D transtorms
+                           ( pfx( "perspective" ) !== null ) &&
+
+                          // And `classList` and `dataset` APIs
+                           ( body.classList ) &&
+                           ( body.dataset );
+
+    if ( !impressSupported ) {
+
+        // We can't be sure that `classList` is supported
+        body.className += " impress-not-supported ";
+    }
+
+    // GLOBALS AND DEFAULTS
+
+    // This is where the root elements of all impress.js instances will be kept.
+    // Yes, this means you can have more than one instance on a page, but I'm not
+    // sure if it makes any sense in practice ;)
+    var roots = {};
+
+    var preInitPlugins = [];
+    var preStepLeavePlugins = [];
+
+    // Some default config values.
+    var defaults = {
+        width: 1024,
+        height: 768,
+        maxScale: 1,
+        minScale: 0,
+
+        perspective: 1000,
+
+        transitionDuration: 1000
+    };
+
+    // It's just an empty function ... and a useless comment.
+    var empty = function() { return false; };
+
+    // IMPRESS.JS API
+
+    // And that's where interesting things will start to happen.
+    // It's the core `impress` function that returns the impress.js API
+    // for a presentation based on the element with given id ("impress"
+    // by default).
+    var impress = window.impress = function( rootId ) {
+
+        // If impress.js is not supported by the browser return a dummy API
+        // it may not be a perfect solution but we return early and avoid
+        // running code that may use features not implemented in the browser.
+        if ( !impressSupported ) {
+            return {
+                init: empty,
+                goto: empty,
+                prev: empty,
+                next: empty,
+                swipe: empty,
+                tear: empty,
+                pastAllSteps: empty,
+                last: empty,
+                lib: {}
+            };
+        }
+
+        rootId = rootId || "impress";
+
+        // If given root is already initialized just return the API
+        if ( roots[ "impress-root-" + rootId ] ) {
+            return roots[ "impress-root-" + rootId ];
+        }
+
+        // The gc library depends on being initialized before we do any changes to DOM.
+        lib = initLibraries( rootId );
+
+        body.classList.remove( "impress-not-supported" );
+        body.classList.add( "impress-supported" );
+
+        // Data of all presentation steps
+        var stepsData = {};
+
+        // Element of currently active step
+        var activeStep = null;
+
+        // Current state (position, rotation and scale) of the presentation
+        var currentState = null;
+
+        // Array of step elements
+        var steps = null;
+
+        // Configuration options
+        var config = null;
+
+        // Scale factor of the browser window
+        var windowScale = null;
+
+        // Root presentation elements
+        var root = lib.util.byId( rootId );
+        var canvas = document.createElement( "div" );
+
+        var initialized = false;
+
+        // STEP EVENTS
+        //
+        // There are currently two step events triggered by impress.js
+        // `impress:stepenter` is triggered when the step is shown on the
+        // screen (the transition from the previous one is finished) and
+        // `impress:stepleave` is triggered when the step is left (the
+        // transition to next step just starts).
+
+        // Reference to last entered step
+        var lastEntered = null;
+
+        // `onStepEnter` is called whenever the step element is entered
+        // but the event is triggered only if the step is different than
+        // last entered step.
+        // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as
+        // after screen resize. In this case - more precisely, in any case - we trigger a
+        // `impress:steprefresh` event.
+        var onStepEnter = function( step ) {
+            if ( lastEntered !== step ) {
+                lib.util.triggerEvent( step, "impress:stepenter" );
+                lastEntered = step;
+            }
+            lib.util.triggerEvent( step, "impress:steprefresh" );
+        };
+
+        // `onStepLeave` is called whenever the currentStep element is left
+        // but the event is triggered only if the currentStep is the same as
+        // lastEntered step.
+        var onStepLeave = function( currentStep, nextStep ) {
+            if ( lastEntered === currentStep ) {
+                lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } );
+                lastEntered = null;
+            }
+        };
+
+        // `initStep` initializes given step element by reading data from its
+        // data attributes and setting correct styles.
+        var initStep = function( el, idx ) {
+            var data = el.dataset,
+                step = {
+                    translate: {
+                        x: lib.util.toNumber( data.x ),
+                        y: lib.util.toNumber( data.y ),
+                        z: lib.util.toNumber( data.z )
+                    },
+                    rotate: {
+                        x: lib.util.toNumber( data.rotateX ),
+                        y: lib.util.toNumber( data.rotateY ),
+                        z: lib.util.toNumber( data.rotateZ || data.rotate ),
+                        order: validateOrder( data.rotateOrder )
+                    },
+                    scale: lib.util.toNumber( data.scale ),
+                    transitionDuration: lib.util.toNumber(
+                        data.transitionDuration, config.transitionDuration
+                    ),
+                    el: el
+                };
+
+            if ( !el.id ) {
+                el.id = "step-" + ( idx + 1 );
+            }
+
+            stepsData[ "impress-" + el.id ] = step;
+
+            if (!el.classList.contains("notransform") &&
+                !el.classList.contains("inner")) {
+                css( el, {
+                    position: "absolute",
+                    transform: "translate(-50%,-50%)" +
+                               translate( step.translate ) +
+                               rotate( step.rotate ) +
+                               scale( step.scale ),
+                    transformStyle: "preserve-3d"
+                } );
+            }
+        };
+
+        // Initialize all steps.
+        // Read the data-* attributes, store in internal stepsData, and render with CSS.
+        var initAllSteps = function() {
+            steps = lib.util.$$( ".step", root );
+            steps.forEach( initStep );
+        };
+
+        var pastAllSteps =  function() {
+            steps = lib.util.$$( ".step", root );
+            steps.forEach( el => el.classList.remove( "future" ) );
+            steps.forEach( el => el.classList.add( "past" ) );
+        };
+
+        var last = function() {
+            pastAllSteps()
+            steps = lib.util.$$( ".step", root );
+            goto(steps[steps.length - 1])
+        }
+
+        // `init` API function that initializes (and runs) the presentation.
+        var init = function() {
+            if ( initialized ) { return; }
+            execPreInitPlugins( root );
+
+            // First we set up the viewport for mobile devices.
+            // For some reason iPad goes nuts when it is not done properly.
+            var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" );
+            meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
+            if ( meta.parentNode !== document.head ) {
+                meta.name = "viewport";
+                document.head.appendChild( meta );
+            }
+
+            // Initialize configuration object
+            var rootData = root.dataset;
+            config = {
+                width: lib.util.toNumber( rootData.width, defaults.width ),
+                height: lib.util.toNumber( rootData.height, defaults.height ),
+                maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),
+                minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),
+                perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),
+                transitionDuration: lib.util.toNumber(
+                    rootData.transitionDuration, defaults.transitionDuration
+                )
+            };
+
+            windowScale = computeWindowScale( config );
+
+            // Wrap steps with "canvas" element
+            lib.util.arrayify( root.childNodes ).forEach( function( el ) {
+                canvas.appendChild( el );
+            } );
+            root.appendChild( canvas );
+
+            // Set initial styles
+            document.documentElement.style.height = "100%";
+
+            css( body, {
+                height: "100%",
+                overflow: "hidden"
+            } );
+
+            var rootStyles = {
+                position: "absolute",
+                transformOrigin: "top left",
+                transition: "all 0s ease-in-out",
+                transformStyle: "preserve-3d"
+            };
+
+            css( root, rootStyles );
+            css( root, {
+                top: "50%",
+                left: "50%",
+                perspective: ( config.perspective / windowScale ) + "px",
+                transform: scale( windowScale )
+            } );
+            css( canvas, rootStyles );
+
+            body.classList.remove( "impress-disabled" );
+            body.classList.add( "impress-enabled" );
+
+            // Get and init steps
+            initAllSteps();
+
+            // Set a default initial state of the canvas
+            currentState = {
+                translate: { x: 0, y: 0, z: 0 },
+                rotate:    { x: 0, y: 0, z: 0, order: "xyz" },
+                scale:     1
+            };
+
+            initialized = true;
+
+            lib.util.triggerEvent( root, "impress:init",
+                                   { api: roots[ "impress-root-" + rootId ] } );
+        };
+
+        // `getStep` is a helper function that returns a step element defined by parameter.
+        // If a number is given, step with index given by the number is returned, if a string
+        // is given step element with such id is returned, if DOM element is given it is returned
+        // if it is a correct step element.
+        var getStep = function( step ) {
+            if ( typeof step === "number" ) {
+                step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
+            } else if ( typeof step === "string" ) {
+                step = lib.util.byId( step );
+            }
+            return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
+        };
+
+        // Used to reset timeout for `impress:stepenter` event
+        var stepEnterTimeout = null;
+
+        // `goto` API function that moves to step given as `el` parameter (by index, id or element).
+        // `duration` optionally given as second parameter, is the transition duration in css.
+        // `reason` is the string "next", "prev" or "goto" (default) and will be made available to
+        // preStepLeave plugins.
+        // `origEvent` may contain event that caused the call to goto, such as a key press event
+        var goto = function( el, duration, reason, origEvent ) {
+            reason = reason || "goto";
+            origEvent = origEvent || null;
+
+            if ( !initialized ) {
+                return false;
+            }
+
+            // Re-execute initAllSteps for each transition. This allows to edit step attributes
+            // dynamically, such as change their coordinates, or even remove or add steps, and have
+            // that change apply when goto() is called.
+            initAllSteps();
+
+            if ( !( el = getStep( el ) ) ) {
+                return false;
+            }
+
+            // Sometimes it's possible to trigger focus on first link with some keyboard action.
+            // Browser in such a case tries to scroll the page to make this element visible
+            // (even that body overflow is set to hidden) and it breaks our careful positioning.
+            //
+            // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
+            // whenever slide is selected
+            //
+            // If you are reading this and know any better way to handle it, I'll be glad to hear
+            // about it!
+            window.scrollTo( 0, 0 );
+
+            var step = stepsData[ "impress-" + el.id ];
+            duration = ( duration !== undefined ? duration : step.transitionDuration );
+
+            // If we are in fact moving to another step, start with executing the registered
+            // preStepLeave plugins.
+            if ( activeStep && activeStep !== el ) {
+                var event = { target: activeStep, detail: {} };
+                event.detail.next = el;
+                event.detail.transitionDuration = duration;
+                event.detail.reason = reason;
+                if ( origEvent ) {
+                    event.origEvent = origEvent;
+                }
+
+                if ( execPreStepLeavePlugins( event ) === false ) {
+
+                    // PreStepLeave plugins are allowed to abort the transition altogether, by
+                    // returning false.
+                    // see stop and substep plugins for an example of doing just that
+                    return false;
+                }
+
+                // Plugins are allowed to change the detail values
+                el = event.detail.next;
+                step = stepsData[ "impress-" + el.id ];
+                duration = event.detail.transitionDuration;
+            }
+
+            if ( activeStep ) {
+                activeStep.classList.remove( "active" );
+                body.classList.remove( "impress-on-" + activeStep.id );
+            }
+            el.classList.add( "active" );
+
+            body.classList.add( "impress-on-" + el.id );
+
+            // Compute target state of the canvas based on given step
+            var target = {
+                rotate: {
+                    x: -step.rotate.x,
+                    y: -step.rotate.y,
+                    z: -step.rotate.z,
+                    order: step.rotate.order
+                },
+                translate: {
+                    x: -step.translate.x,
+                    y: -step.translate.y,
+                    z: -step.translate.z
+                },
+                scale: 1 / step.scale
+            };
+
+            // Check if the transition is zooming in or not.
+            //
+            // This information is used to alter the transition style:
+            // when we are zooming in - we start with move and rotate transition
+            // and the scaling is delayed, but when we are zooming out we start
+            // with scaling down and move and rotation are delayed.
+            var zoomin = target.scale >= currentState.scale;
+
+            duration = lib.util.toNumber( duration, config.transitionDuration );
+            var delay = ( duration / 2 );
+
+            // If the same step is re-selected, force computing window scaling,
+            // because it is likely to be caused by window resize
+            if ( el === activeStep ) {
+                windowScale = computeWindowScale( config );
+            }
+
+            var targetScale = target.scale * windowScale;
+
+            // Trigger leave of currently active element (if it's not the same step again)
+            if ( activeStep && activeStep !== el ) {
+                onStepLeave( activeStep, el );
+            }
+
+            // Now we alter transforms of `root` and `canvas` to trigger transitions.
+            //
+            // And here is why there are two elements: `root` and `canvas` - they are
+            // being animated separately:
+            // `root` is used for scaling and `canvas` for translate and rotations.
+            // Transitions on them are triggered with different delays (to make
+            // visually nice and "natural" looking transitions), so we need to know
+            // that both of them are finished.
+            css( root, {
+
+                // To keep the perspective look similar for different scales
+                // we need to "scale" the perspective, too
+                // For IE 11 support we must specify perspective independent
+                // of transform.
+                perspective: ( config.perspective / targetScale ) + "px",
+                transform: scale( targetScale ),
+                transitionDuration: duration + "ms",
+                transitionDelay: ( zoomin ? delay : 0 ) + "ms"
+            } );
+
+            css( canvas, {
+                transform: rotate( target.rotate, true ) + translate( target.translate ),
+                transitionDuration: duration + "ms",
+                transitionDelay: ( zoomin ? 0 : delay ) + "ms"
+            } );
+
+            // Here is a tricky part...
+            //
+            // If there is no change in scale or no change in rotation and translation, it means
+            // there was actually no delay - because there was no transition on `root` or `canvas`
+            // elements. We want to trigger `impress:stepenter` event in the correct moment, so
+            // here we compare the current and target values to check if delay should be taken into
+            // account.
+            //
+            // I know that this `if` statement looks scary, but it's pretty simple when you know
+            // what is going on - it's simply comparing all the values.
+            if ( currentState.scale === target.scale ||
+                ( currentState.rotate.x === target.rotate.x &&
+                  currentState.rotate.y === target.rotate.y &&
+                  currentState.rotate.z === target.rotate.z &&
+                  currentState.translate.x === target.translate.x &&
+                  currentState.translate.y === target.translate.y &&
+                  currentState.translate.z === target.translate.z ) ) {
+                delay = 0;
+            }
+
+            // Store current state
+            currentState = target;
+            activeStep = el;
+
+            // And here is where we trigger `impress:stepenter` event.
+            // We simply set up a timeout to fire it taking transition duration (and possible delay)
+            // into account.
+            //
+            // I really wanted to make it in more elegant way. The `transitionend` event seemed to
+            // be the best way to do it, but the fact that I'm using transitions on two separate
+            // elements and that the `transitionend` event is only triggered when there was a
+            // transition (change in the values) caused some bugs and made the code really
+            // complicated, cause I had to handle all the conditions separately. And it still
+            // needed a `setTimeout` fallback for the situations when there is no transition at all.
+            // So I decided that I'd rather make the code simpler than use shiny new
+            // `transitionend`.
+            //
+            // If you want learn something interesting and see how it was done with `transitionend`
+            // go back to version 0.5.2 of impress.js:
+            // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
+            window.clearTimeout( stepEnterTimeout );
+            stepEnterTimeout = window.setTimeout( function() {
+                onStepEnter( activeStep );
+            }, duration + delay );
+
+            return el;
+        };
+
+        // `prev` API function goes to previous step (in document order)
+        // `event` is optional, may contain the event that caused the need to call prev()
+        var prev = function( origEvent ) {
+            var prev = steps.indexOf( activeStep ) - 1;
+            prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
+
+            return goto( prev, undefined, "prev", origEvent );
+        };
+
+        // `next` API function goes to next step (in document order)
+        // `event` is optional, may contain the event that caused the need to call next()
+        var next = function( origEvent ) {
+            var next = steps.indexOf( activeStep ) + 1;
+            next = next < steps.length ? steps[ next ] : steps[ 0 ];
+
+            return goto( next, undefined, "next", origEvent );
+        };
+
+        // Swipe for touch devices by @and3rson.
+        // Below we extend the api to control the animation between the currently
+        // active step and a presumed next/prev step. See touch plugin for
+        // an example of using this api.
+
+        // Helper function
+        var interpolate = function( a, b, k ) {
+            return a + ( b - a ) * k;
+        };
+
+        // Animate a swipe.
+        //
+        // Pct is a value between -1.0 and +1.0, designating the current length
+        // of the swipe.
+        //
+        // If pct is negative, swipe towards the next() step, if positive,
+        // towards the prev() step.
+        //
+        // Note that pre-stepleave plugins such as goto can mess with what is a
+        // next() and prev() step, so we need to trigger the pre-stepleave event
+        // here, even if a swipe doesn't guarantee that the transition will
+        // actually happen.
+        //
+        // Calling swipe(), with any value of pct, won't in itself cause a
+        // transition to happen, this is just to animate the swipe. Once the
+        // transition is committed - such as at a touchend event - caller is
+        // responsible for also calling prev()/next() as appropriate.
+        //
+        // Note: For now, this function is made available to be used by the swipe plugin (which
+        // is the UI counterpart to this). It is a semi-internal API and intentionally not
+        // documented in DOCUMENTATION.md.
+        var swipe = function( pct ) {
+            if ( Math.abs( pct ) > 1 ) {
+                return;
+            }
+
+            // Prepare & execute the preStepLeave event
+            var event = { target: activeStep, detail: {} };
+            event.detail.swipe = pct;
+
+            // Will be ignored within swipe animation, but just in case a plugin wants to read this,
+            // humor them
+            event.detail.transitionDuration = config.transitionDuration;
+            var idx; // Needed by jshint
+            if ( pct < 0 ) {
+                idx = steps.indexOf( activeStep ) + 1;
+                event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
+                event.detail.reason = "next";
+            } else if ( pct > 0 ) {
+                idx = steps.indexOf( activeStep ) - 1;
+                event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
+                event.detail.reason = "prev";
+            } else {
+
+                // No move
+                return;
+            }
+            if ( execPreStepLeavePlugins( event ) === false ) {
+
+                // If a preStepLeave plugin wants to abort the transition, don't animate a swipe
+                // For stop, this is probably ok. For substep, the plugin it self might want to do
+                // some animation, but that's not the current implementation.
+                return false;
+            }
+            var nextElement = event.detail.next;
+
+            var nextStep = stepsData[ "impress-" + nextElement.id ];
+
+            // If the same step is re-selected, force computing window scaling,
+            var nextScale = nextStep.scale * windowScale;
+            var k = Math.abs( pct );
+
+            var interpolatedStep = {
+                translate: {
+                    x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
+                    y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
+                    z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
+                },
+                rotate: {
+                    x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
+                    y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
+                    z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
+
+                    // Unfortunately there's a discontinuity if rotation order changes. Nothing I
+                    // can do about it?
+                    order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
+                },
+                scale: interpolate( currentState.scale * windowScale, nextScale, k )
+            };
+
+            css( root, {
+
+                // To keep the perspective look similar for different scales
+                // we need to 'scale' the perspective, too
+                perspective: config.perspective / interpolatedStep.scale + "px",
+                transform: scale( interpolatedStep.scale ),
+                transitionDuration: "0ms",
+                transitionDelay: "0ms"
+            } );
+
+            css( canvas, {
+                transform: rotate( interpolatedStep.rotate, true ) +
+                           translate( interpolatedStep.translate ),
+                transitionDuration: "0ms",
+                transitionDelay: "0ms"
+            } );
+        };
+
+        // Teardown impress
+        // Resets the DOM to the state it was before impress().init() was called.
+        // (If you called impress(rootId).init() for multiple different rootId's, then you must
+        // also call tear() once for each of them.)
+        var tear = function() {
+            lib.gc.teardown();
+            delete roots[ "impress-root-" + rootId ];
+        };
+
+        // Adding some useful classes to step elements.
+        //
+        // All the steps that have not been shown yet are given `future` class.
+        // When the step is entered the `future` class is removed and the `present`
+        // class is given. When the step is left `present` class is replaced with
+        // `past` class.
+        //
+        // So every step element is always in one of three possible states:
+        // `future`, `present` and `past`.
+        //
+        // There classes can be used in CSS to style different types of steps.
+        // For example the `present` class can be used to trigger some custom
+        // animations when step is shown.
+        lib.gc.addEventListener( root, "impress:init", function() {
+
+            // STEP CLASSES
+            steps.forEach( function( step ) {
+                step.classList.add( "future" );
+            } );
+
+            lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
+                event.target.classList.remove( "past" );
+                event.target.classList.remove( "future" );
+                event.target.classList.add( "present" );
+            }, false );
+
+            lib.gc.addEventListener( root, "impress:stepleave", function( event ) {
+                event.target.classList.remove( "present" );
+                event.target.classList.add( "past" );
+            }, false );
+
+        }, false );
+
+        // Adding hash change support.
+        lib.gc.addEventListener( root, "impress:init", function() {
+
+            // Last hash detected
+            var lastHash = "";
+
+            // `#/step-id` is used instead of `#step-id` to prevent default browser
+            // scrolling to element in hash.
+            //
+            // And it has to be set after animation finishes, because in Chrome it
+            // makes transtion laggy.
+            // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
+            lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
+                window.location.hash = lastHash = "#/" + event.target.id;
+            }, false );
+
+            lib.gc.addEventListener( window, "hashchange", function() {
+
+                // When the step is entered hash in the location is updated
+                // (just few lines above from here), so the hash change is
+                // triggered and we would call `goto` again on the same element.
+                //
+                // To avoid this we store last entered hash and compare.
+                if ( window.location.hash !== lastHash ) {
+                    goto( lib.util.getElementFromHash() );
+                }
+            }, false );
+
+            // START
+            // by selecting step defined in url or first step of the presentation
+            goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );
+        }, false );
+
+        body.classList.add( "impress-disabled" );
+
+        // Store and return API for given impress.js root element
+        return ( roots[ "impress-root-" + rootId ] = {
+            init: init,
+            goto: goto,
+            next: next,
+            prev: prev,
+            swipe: swipe,
+            tear: tear,
+            pastAllSteps: pastAllSteps,
+            last: last,
+            lib: lib
+        } );
+
+    };
+
+    // Flag that can be used in JS to check if browser have passed the support test
+    impress.supported = impressSupported;
+
+    // ADD and INIT LIBRARIES
+    // Library factories are defined in src/lib/*.js, and register themselves by calling
+    // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment
+    // the API with library functions when client calls impress(rootId).
+    // See src/lib/README.md for clearer example.
+    // (Advanced usage: For different values of rootId, a different instance of the libaries are
+    // generated, in case they need to hold different state for different root elements.)
+    var libraryFactories = {};
+    impress.addLibraryFactory = function( obj ) {
+        for ( var libname in obj ) {
+            if ( obj.hasOwnProperty( libname ) ) {
+                libraryFactories[ libname ] = obj[ libname ];
+            }
+        }
+    };
+
+    // Call each library factory, and return the lib object that is added to the api.
+    var initLibraries = function( rootId ) { //jshint ignore:line
+        var lib = {};
+        for ( var libname in libraryFactories ) {
+            if ( libraryFactories.hasOwnProperty( libname ) ) {
+                if ( lib[ libname ] !== undefined ) {
+                    throw "impress.js ERROR: Two libraries both tried to use libname: " +  libname;
+                }
+                lib[ libname ] = libraryFactories[ libname ]( rootId );
+            }
+        }
+        return lib;
+    };
+
+    // `addPreInitPlugin` allows plugins to register a function that should
+    // be run (synchronously) at the beginning of init, before
+    // impress().init() itself executes.
+    impress.addPreInitPlugin = function( plugin, weight ) {
+        weight = parseInt( weight ) || 10;
+        if ( weight <= 0 ) {
+            throw "addPreInitPlugin: weight must be a positive integer";
+        }
+
+        if ( preInitPlugins[ weight ] === undefined ) {
+            preInitPlugins[ weight ] = [];
+        }
+        preInitPlugins[ weight ].push( plugin );
+    };
+
+    // Called at beginning of init, to execute all pre-init plugins.
+    var execPreInitPlugins = function( root ) { //jshint ignore:line
+        for ( var i = 0; i < preInitPlugins.length; i++ ) {
+            var thisLevel = preInitPlugins[ i ];
+            if ( thisLevel !== undefined ) {
+                for ( var j = 0; j < thisLevel.length; j++ ) {
+                    thisLevel[ j ]( root );
+                }
+            }
+        }
+    };
+
+    // `addPreStepLeavePlugin` allows plugins to register a function that should
+    // be run (synchronously) at the beginning of goto()
+    impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line
+        weight = parseInt( weight ) || 10;
+        if ( weight <= 0 ) {
+            throw "addPreStepLeavePlugin: weight must be a positive integer";
+        }
+
+        if ( preStepLeavePlugins[ weight ] === undefined ) {
+            preStepLeavePlugins[ weight ] = [];
+        }
+        preStepLeavePlugins[ weight ].push( plugin );
+    };
+
+    // Called at beginning of goto(), to execute all preStepLeave plugins.
+    var execPreStepLeavePlugins = function( event ) { //jshint ignore:line
+        for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {
+            var thisLevel = preStepLeavePlugins[ i ];
+            if ( thisLevel !== undefined ) {
+                for ( var j = 0; j < thisLevel.length; j++ ) {
+                    if ( thisLevel[ j ]( event ) === false ) {
+
+                        // If a plugin returns false, the stepleave event (and related transition)
+                        // is aborted
+                        return false;
+                    }
+                }
+            }
+        }
+    };
+
+} )( document, window );
+
+// THAT'S ALL FOLKS!
+//
+// Thanks for reading it all.
+// Or thanks for scrolling down and reading the last part.
+//
+// I've learnt a lot when building impress.js and I hope this code and comments
+// will help somebody learn at least some part of it.
+
+/**
+ * Garbage collection utility
+ *
+ * This library allows plugins to add elements and event listeners they add to the DOM. The user
+ * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
+ * the document is in the state it was before calling `impress().init()`.
+ *
+ * In addition to just adding elements and event listeners to the garbage collector, plugins
+ * can also register callback functions to do arbitrary cleanup upon teardown.
+ *
+ * Henrik Ingo (c) 2016
+ * MIT License
+ */
+
+( function( document, window ) {
+    "use strict";
+    var roots = [];
+    var rootsCount = 0;
+    var startingState = { roots: [] };
+
+    var libraryFactory = function( rootId ) {
+        if ( roots[ rootId ] ) {
+            return roots[ rootId ];
+        }
+
+        // Per root global variables (instance variables?)
+        var elementList = [];
+        var eventListenerList = [];
+        var callbackList = [];
+
+        recordStartingState( rootId );
+
+        // LIBRARY FUNCTIONS
+        // Definitions of the library functions we return as an object at the end
+
+        // `pushElement` adds a DOM element to the gc stack
+        var pushElement = function( element ) {
+            elementList.push( element );
+        };
+
+        // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
+        var appendChild = function( parent, element ) {
+            parent.appendChild( element );
+            pushElement( element );
+        };
+
+        // `pushEventListener` adds an event listener to the gc stack
+        var pushEventListener = function( target, type, listenerFunction ) {
+            eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
+        };
+
+        // `addEventListener` combines DOM addEventListener with gc.pushEventListener
+        var addEventListener = function( target, type, listenerFunction ) {
+            target.addEventListener( type, listenerFunction );
+            pushEventListener( target, type, listenerFunction );
+        };
+
+        // `pushCallback` If the above utilities are not enough, plugins can add their own callback
+        // function to do arbitrary things.
+        var pushCallback = function( callback ) {
+            callbackList.push( callback );
+        };
+        pushCallback( function( rootId ) { resetStartingState( rootId ); } );
+
+        // `teardown` will
+        // - execute all callbacks in LIFO order
+        // - call `removeChild` on all DOM elements in LIFO order
+        // - call `removeEventListener` on all event listeners in LIFO order
+        // The goal of a teardown is to return to the same state that the DOM was before
+        // `impress().init()` was called.
+        var teardown = function() {
+
+            // Execute the callbacks in LIFO order
+            var i; // Needed by jshint
+            for ( i = callbackList.length - 1; i >= 0; i-- ) {
+                callbackList[ i ]( rootId );
+            }
+            callbackList = [];
+            for ( i = 0; i < elementList.length; i++ ) {
+                elementList[ i ].parentElement.removeChild( elementList[ i ] );
+            }
+            elementList = [];
+            for ( i = 0; i < eventListenerList.length; i++ ) {
+                var target   = eventListenerList[ i ].target;
+                var type     = eventListenerList[ i ].type;
+                var listener = eventListenerList[ i ].listener;
+                target.removeEventListener( type, listener );
+            }
+        };
+
+        var lib = {
+            pushElement: pushElement,
+            appendChild: appendChild,
+            pushEventListener: pushEventListener,
+            addEventListener: addEventListener,
+            pushCallback: pushCallback,
+            teardown: teardown
+        };
+        roots[ rootId ] = lib;
+        rootsCount++;
+        return lib;
+    };
+
+    // Let impress core know about the existence of this library
+    window.impress.addLibraryFactory( { gc: libraryFactory } );
+
+    // CORE INIT
+    // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
+    // For the purposes of teardown(), we can use this as an opportunity to save the state
+    // of a few things in the DOM in their virgin state, before impress().init() did anything.
+    // Note: These could also be recorded by the code in impress.js core as these values
+    // are changed, but in an effort to not deviate too much from upstream, I'm adding
+    // them here rather than the core itself.
+    var recordStartingState = function( rootId ) {
+        startingState.roots[ rootId ] = {};
+        startingState.roots[ rootId ].steps = [];
+
+        // Record whether the steps have an id or not
+        var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
+        for ( var i = 0; i < steps.length; i++ ) {
+            var el = steps[ i ];
+            startingState.roots[ rootId ].steps.push( {
+                el: el,
+                id: el.getAttribute( "id" )
+            } );
+        }
+
+        // In the rare case of multiple roots, the following is changed on first init() and
+        // reset at last tear().
+        if ( rootsCount === 0 ) {
+            startingState.body = {};
+
+            // It is customary for authors to set body.class="impress-not-supported" as a starting
+            // value, which can then be removed by impress().init(). But it is not required.
+            // Remember whether it was there or not.
+            if ( document.body.classList.contains( "impress-not-supported" ) ) {
+                startingState.body.impressNotSupported = true;
+            } else {
+                startingState.body.impressNotSupported = false;
+            }
+
+            // If there's a <meta name="viewport"> element, its contents will be overwritten by init
+            var metas = document.head.querySelectorAll( "meta" );
+            for ( i = 0; i < metas.length; i++ ) {
+                var m = metas[ i ];
+                if ( m.name === "viewport" ) {
+                    startingState.meta = m.content;
+                }
+            }
+        }
+    };
+
+    // CORE TEARDOWN
+    var resetStartingState = function( rootId ) {
+
+        // Reset body element
+        document.body.classList.remove( "impress-enabled" );
+        document.body.classList.remove( "impress-disabled" );
+
+        var root = document.getElementById( rootId );
+        var activeId = root.querySelector( ".active" ).id;
+        document.body.classList.remove( "impress-on-" + activeId );
+
+        document.documentElement.style.height = "";
+        document.body.style.height = "";
+        document.body.style.overflow = "";
+
+        // Remove style values from the root and step elements
+        // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
+        // values. A more sophisticated implementation could keep track of original values and then
+        // reset those.
+        var steps = root.querySelectorAll( ".step" );
+        for ( var i = 0; i < steps.length; i++ ) {
+            steps[ i ].classList.remove( "future" );
+            steps[ i ].classList.remove( "past" );
+            steps[ i ].classList.remove( "present" );
+            steps[ i ].classList.remove( "active" );
+            steps[ i ].style.position = "";
+            steps[ i ].style.transform = "";
+            steps[ i ].style[ "transform-style" ] = "";
+        }
+        root.style.position = "";
+        root.style[ "transform-origin" ] = "";
+        root.style.transition = "";
+        root.style[ "transform-style" ] = "";
+        root.style.top = "";
+        root.style.left = "";
+        root.style.transform = "";
+
+        // Reset id of steps ("step-1" id's are auto generated)
+        steps = startingState.roots[ rootId ].steps;
+        var step;
+        while ( step = steps.pop() ) {
+            if ( step.id === null ) {
+                step.el.removeAttribute( "id" );
+            } else {
+                step.el.setAttribute( "id", step.id );
+            }
+        }
+        delete startingState.roots[ rootId ];
+
+        // Move step div elements away from canvas, then delete canvas
+        // Note: There's an implicit assumption here that the canvas div is the only child element
+        // of the root div. If there would be something else, it's gonna be lost.
+        var canvas = root.firstChild;
+        var canvasHTML = canvas.innerHTML;
+        root.innerHTML = canvasHTML;
+
+        if ( roots[ rootId ] !== undefined ) {
+            delete roots[ rootId ];
+            rootsCount--;
+        }
+        if ( rootsCount === 0 ) {
+
+            // In the rare case that more than one impress root elements were initialized, these
+            // are only reset when all are uninitialized.
+            document.body.classList.remove( "impress-supported" );
+            if ( startingState.body.impressNotSupported ) {
+                document.body.classList.add( "impress-not-supported" );
+            }
+
+            // We need to remove or reset the meta element inserted by impress.js
+            var metas = document.head.querySelectorAll( "meta" );
+            for ( i = 0; i < metas.length; i++ ) {
+                var m = metas[ i ];
+                if ( m.name === "viewport" ) {
+                    if ( startingState.meta !== undefined ) {
+                        m.content = startingState.meta;
+                    } else {
+                        m.parentElement.removeChild( m );
+                    }
+                }
+            }
+        }
+
+    };
+
+} )( document, window );
+
+/**
+ * Common utility functions
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz)
+ * Henrik Ingo (c) 2016
+ * MIT License
+ */
+
+( function( document, window ) {
+    "use strict";
+    var roots = [];
+
+    var libraryFactory = function( rootId ) {
+        if ( roots[ rootId ] ) {
+            return roots[ rootId ];
+        }
+
+        // `$` returns first element for given CSS `selector` in the `context` of
+        // the given element or whole document.
+        var $ = function( selector, context ) {
+            context = context || document;
+            return context.querySelector( selector );
+        };
+
+        // `$$` return an array of elements for given CSS `selector` in the `context` of
+        // the given element or whole document.
+        var $$ = function( selector, context ) {
+            context = context || document;
+            return arrayify( context.querySelectorAll( selector ) );
+        };
+
+        // `arrayify` takes an array-like object and turns it into real Array
+        // to make all the Array.prototype goodness available.
+        var arrayify = function( a ) {
+            return [].slice.call( a );
+        };
+
+        // `byId` returns element with given `id` - you probably have guessed that ;)
+        var byId = function( id ) {
+            return document.getElementById( id );
+        };
+
+        // `getElementFromHash` returns an element located by id from hash part of
+        // window location.
+        var getElementFromHash = function() {
+
+            // Get id from url # by removing `#` or `#/` from the beginning,
+            // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
+            return byId( window.location.hash.replace( /^#\/?/, "" ) );
+        };
+
+        // Throttling function calls, by Remy Sharp
+        // http://remysharp.com/2010/07/21/throttling-function-calls/
+        var throttle = function( fn, delay ) {
+            var timer = null;
+            return function() {
+                var context = this, args = arguments;
+                window.clearTimeout( timer );
+                timer = window.setTimeout( function() {
+                    fn.apply( context, args );
+                }, delay );
+            };
+        };
+
+        // `toNumber` takes a value given as `numeric` parameter and tries to turn
+        // it into a number. If it is not possible it returns 0 (or other value
+        // given as `fallback`).
+        var toNumber = function( numeric, fallback ) {
+            return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
+        };
+
+        // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
+        // and triggers it on element given as `el`.
+        var triggerEvent = function( el, eventName, detail ) {
+            var event = document.createEvent( "CustomEvent" );
+            event.initCustomEvent( eventName, true, true, detail );
+            el.dispatchEvent( event );
+        };
+
+        var lib = {
+            $: $,
+            $$: $$,
+            arrayify: arrayify,
+            byId: byId,
+            getElementFromHash: getElementFromHash,
+            throttle: throttle,
+            toNumber: toNumber,
+            triggerEvent: triggerEvent
+        };
+        roots[ rootId ] = lib;
+        return lib;
+    };
+
+    // Let impress core know about the existence of this library
+    window.impress.addLibraryFactory( { util: libraryFactory } );
+
+} )( document, window );
+
+/**
+ * Autoplay plugin - Automatically advance slideshow after N seconds
+ *
+ * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
+ * Released under the MIT license.
+ */
+/* global clearTimeout, setTimeout, document */
+
+( function( document ) {
+    "use strict";
+
+    var autoplayDefault = 0;
+    var currentStepTimeout = 0;
+    var api = null;
+    var timeoutHandle = null;
+    var root = null;
+    var util;
+
+    // On impress:init, check whether there is a default setting, as well as
+    // handle step-1.
+    document.addEventListener( "impress:init", function( event ) {
+        util = event.detail.api.lib.util;
+
+        // Getting API from event data instead of global impress().init().
+        // You don't even need to know what is the id of the root element
+        // or anything. `impress:init` event data gives you everything you
+        // need to control the presentation that was just initialized.
+        api = event.detail.api;
+        root = event.target;
+
+        // Element attributes starting with "data-", become available under
+        // element.dataset. In addition hyphenized words become camelCased.
+        var data = root.dataset;
+
+        if ( data.autoplay ) {
+            autoplayDefault = util.toNumber( data.autoplay, 0 );
+        }
+
+        var toolbar = document.querySelector( "#impress-toolbar" );
+        if ( toolbar ) {
+            addToolbarButton( toolbar );
+        }
+
+        api.lib.gc.pushCallback( function() {
+            clearTimeout( timeoutHandle );
+        } );
+
+        // Note that right after impress:init event, also impress:stepenter is
+        // triggered for the first slide, so that's where code flow continues.
+    }, false );
+
+    document.addEventListener( "impress:autoplay:pause", function( event ) {
+        status = "paused";
+        reloadTimeout( event );
+    }, false );
+
+    document.addEventListener( "impress:autoplay:play", function( event ) {
+        status = "playing";
+        reloadTimeout( event );
+    }, false );
+
+    // If default autoplay time was defined in the presentation root, or
+    // in this step, set timeout.
+    var reloadTimeout = function( event ) {
+        var step = event.target;
+        currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
+        if ( status === "paused" ) {
+            setAutoplayTimeout( 0 );
+        } else {
+            setAutoplayTimeout( currentStepTimeout );
+        }
+    };
+
+    document.addEventListener( "impress:stepenter", function( event ) {
+        reloadTimeout( event );
+    }, false );
+
+    document.addEventListener( "impress:substep:enter", function( event ) {
+        reloadTimeout( event );
+    }, false );
+
+    /**
+     * Set timeout after which we move to next() step.
+     */
+    var setAutoplayTimeout = function( timeout ) {
+        if ( timeoutHandle ) {
+            clearTimeout( timeoutHandle );
+        }
+
+        if ( timeout > 0 ) {
+            timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
+        }
+        setButtonText();
+    };
+
+    /*** Toolbar plugin integration *******************************************/
+    var status = "not clicked";
+    var toolbarButton = null;
+
+    var makeDomElement = function( html ) {
+        var tempDiv = document.createElement( "div" );
+        tempDiv.innerHTML = html;
+        return tempDiv.firstChild;
+    };
+
+    var toggleStatus = function() {
+        if ( currentStepTimeout > 0 && status !== "paused" ) {
+            status = "paused";
+        } else {
+            status = "playing";
+        }
+    };
+
+    var getButtonText = function() {
+        if ( currentStepTimeout > 0 && status !== "paused" ) {
+            return "||"; // Pause
+        } else {
+            return "&#9654;"; // Play
+        }
+    };
+
+    var setButtonText = function() {
+        if ( toolbarButton ) {
+
+            // Keep button size the same even if label content is changing
+            var buttonWidth = toolbarButton.offsetWidth;
+            var buttonHeight = toolbarButton.offsetHeight;
+            toolbarButton.innerHTML = getButtonText();
+            if ( !toolbarButton.style.width ) {
+                toolbarButton.style.width = buttonWidth + "px";
+            }
+            if ( !toolbarButton.style.height ) {
+                toolbarButton.style.height = buttonHeight + "px";
+            }
+        }
+    };
+
+    var addToolbarButton = function( toolbar ) {
+        var html = '<button id="impress-autoplay-playpause" ' + // jshint ignore:line
+                   'title="Autoplay" class="impress-autoplay">' + // jshint ignore:line
+                   getButtonText() + "</button>"; // jshint ignore:line
+        toolbarButton = makeDomElement( html );
+        toolbarButton.addEventListener( "click", function() {
+            toggleStatus();
+            if ( status === "playing" ) {
+                if ( autoplayDefault === 0 ) {
+                    autoplayDefault = 7;
+                }
+                if ( currentStepTimeout === 0 ) {
+                    currentStepTimeout = autoplayDefault;
+                }
+                setAutoplayTimeout( currentStepTimeout );
+            } else if ( status === "paused" ) {
+                setAutoplayTimeout( 0 );
+            }
+        } );
+
+        util.triggerEvent( toolbar, "impress:toolbar:appendChild",
+                      { group: 10, element: toolbarButton } );
+    };
+
+} )( document );
+
+/**
+ * Blackout plugin
+ *
+ * Press b or . to hide all slides, and b or . again to show them.
+ * Also navigating to a different slide will show them again (impress:stepleave).
+ *
+ * Copyright 2014 @Strikeskids
+ * Released under the MIT license.
+ */
+/* global document */
+
+( function( document ) {
+    "use strict";
+
+    var canvas = null;
+    var blackedOut = false;
+    var util = null;
+    var root = null;
+    var api = null;
+
+    // While waiting for a shared library of utilities, copying these 2 from main impress.js
+    var css = function( el, props ) {
+        var key, pkey;
+        for ( key in props ) {
+            if ( props.hasOwnProperty( key ) ) {
+                pkey = pfx( key );
+                if ( pkey !== null ) {
+                    el.style[ pkey ] = props[ key ];
+                }
+            }
+        }
+        return el;
+    };
+
+    var pfx = ( function() {
+
+        var style = document.createElement( "dummy" ).style,
+            prefixes = "Webkit Moz O ms Khtml".split( " " ),
+            memory = {};
+
+        return function( prop ) {
+            if ( typeof memory[ prop ] === "undefined" ) {
+
+                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+                    props   = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
+
+                memory[ prop ] = null;
+                for ( var i in props ) {
+                    if ( style[ props[ i ] ] !== undefined ) {
+                        memory[ prop ] = props[ i ];
+                        break;
+                    }
+                }
+
+            }
+
+            return memory[ prop ];
+        };
+
+    } )();
+
+    var removeBlackout = function() {
+        if ( blackedOut ) {
+            css( canvas, {
+                display: "block"
+            } );
+            blackedOut = false;
+            util.triggerEvent( root, "impress:autoplay:play", {} );
+        }
+    };
+
+    var blackout = function() {
+        if ( blackedOut ) {
+            removeBlackout();
+        } else {
+            css( canvas, {
+                display: ( blackedOut = !blackedOut ) ? "none" : "block"
+            } );
+            blackedOut = true;
+            util.triggerEvent( root, "impress:autoplay:pause", {} );
+        }
+    };
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( "impress:init", function( event ) {
+        api = event.detail.api;
+        util = api.lib.util;
+        root = event.target;
+        canvas = root.firstElementChild;
+        var gc = api.lib.gc;
+        var util = api.lib.util;
+
+        gc.addEventListener( document, "keydown", function( event ) {
+
+            // Accept b or . -> . is sent by presentation remote controllers
+            if ( event.keyCode === 66 || event.keyCode === 190 ) {
+                event.preventDefault();
+                if ( !blackedOut ) {
+                    blackout();
+                } else {
+                    removeBlackout();
+                }
+            }
+        }, false );
+
+        gc.addEventListener( document, "keyup", function( event ) {
+
+            // Accept b or . -> . is sent by presentation remote controllers
+            if ( event.keyCode === 66 || event.keyCode === 190 ) {
+                event.preventDefault();
+            }
+        }, false );
+
+    }, false );
+
+    document.addEventListener( "impress:stepleave", function() {
+        removeBlackout();
+    }, false );
+
+} )( document );
+
+
+/**
+ * Extras Plugin
+ *
+ * This plugin performs initialization (like calling mermaid.initialize())
+ * for the extras/ plugins if they are loaded into a presentation.
+ *
+ * See README.md for details.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global markdown, hljs, mermaid, impress, document, window */
+
+( function( document, window ) {
+    "use strict";
+
+    var preInit = function() {
+        if ( window.markdown ) {
+
+            // Unlike the other extras, Markdown.js doesn't by default do anything in
+            // particular. We do it ourselves here.
+            // In addition, we use "-----" as a delimiter for new slide.
+
+            // Query all .markdown elements and translate to HTML
+            var markdownDivs = document.querySelectorAll( ".markdown" );
+            for ( var idx = 0; idx < markdownDivs.length; idx++ ) {
+              var element = markdownDivs[ idx ];
+
+              var slides = element.textContent.split( /^-----$/m );
+              var i = slides.length - 1;
+              element.innerHTML = markdown.toHTML( slides[ i ] );
+
+              // If there's an id, unset it for last, and all other, elements,
+              // and then set it for the first.
+              var id = null;
+              if ( element.id ) {
+                id = element.id;
+                element.id = "";
+              }
+              i--;
+              while ( i >= 0 ) {
+                var newElement = element.cloneNode( false );
+                newElement.innerHTML = markdown.toHTML( slides[ i ] );
+                element.parentNode.insertBefore( newElement, element );
+                element = newElement;
+                i--;
+              }
+              if ( id !== null ) {
+                element.id = id;
+              }
+            }
+        } // Markdown
+
+        if ( window.hljs ) {
+            hljs.initHighlightingOnLoad();
+        }
+
+        if ( window.mermaid ) {
+            mermaid.initialize( { startOnLoad:true } );
+        }
+    };
+
+    // Register the plugin to be called in pre-init phase
+    // Note: Markdown.js should run early/first, because it creates new div elements.
+    // So add this with a lower-than-default weight.
+    impress.addPreInitPlugin( preInit, 1 );
+
+} )( document, window );
+
+
+/**
+ * Form support
+ *
+ * Functionality to better support use of input, textarea, button... elements in a presentation.
+ *
+ * This plugin does two things:
+ *
+ * Set stopPropagation on any element that might take text input. This allows users to type, for
+ * example, the letter 'P' into a form field, without causing the presenter console to spring up.
+ *
+ * On impress:stepleave, de-focus any potentially active
+ * element. This is to prevent the focus from being left in a form element that is no longer visible
+ * in the window, and user therefore typing garbage into the form.
+ *
+ * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
+ * in particular the navigation plugin, unfortunately must fully take control of the tab key,
+ * otherwise a user could cause the browser to scroll to a link or button that's not on the current
+ * step. However, it could be possible to allow tab navigation between form elements, as long as
+ * they are on the active step. This is a topic for further study.
+ *
+ * Copyright 2016 Henrik Ingo
+ * MIT License
+ */
+/* global document */
+( function( document ) {
+    "use strict";
+    var root;
+    var api;
+
+    document.addEventListener( "impress:init", function( event ) {
+        root = event.target;
+        api = event.detail.api;
+        var gc = api.lib.gc;
+
+        var selectors = [ "input", "textarea", "select", "[contenteditable=true]" ];
+        for ( var selector of selectors ) {
+            var elements = document.querySelectorAll( selector );
+            if ( !elements ) {
+                continue;
+            }
+
+            for ( var i = 0; i < elements.length; i++ ) {
+                var e = elements[ i ];
+                gc.addEventListener( e, "keydown", function( event ) {
+                    event.stopPropagation();
+                } );
+                gc.addEventListener( e, "keyup", function( event ) {
+                    event.stopPropagation();
+                } );
+            }
+        }
+    }, false );
+
+    document.addEventListener( "impress:stepleave", function() {
+        document.activeElement.blur();
+    }, false );
+
+} )( document );
+
+
+/**
+ * Fullscreen plugin
+ *
+ * Press F5 to enter fullscreen and ESC to exit fullscreen mode.
+ *
+ * Copyright 2019 @giflw
+ * Released under the MIT license.
+ */
+/* global document */
+
+( function( document ) {
+    "use strict";
+
+    function enterFullscreen() {
+        var elem = document.documentElement;
+        if ( !document.fullscreenElement ) {
+            elem.requestFullscreen();
+        }
+    }
+
+    function exitFullscreen() {
+        if ( document.fullscreenElement ) {
+            document.exitFullscreen();
+        }
+    }
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( "impress:init", function( event ) {
+        var api = event.detail.api;
+        var root = event.target;
+        var gc = api.lib.gc;
+        var util = api.lib.util;
+
+        gc.addEventListener( document, "keydown", function( event ) {
+
+            // 116 (F5) is sent by presentation remote controllers
+            if ( event.code === "F5" ) {
+                event.preventDefault();
+                enterFullscreen();
+                util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" );
+            }
+
+            // 27 (Escape) is sent by presentation remote controllers
+            if ( event.key === "Escape" || event.key === "F5" ) {
+                event.preventDefault();
+                exitFullscreen();
+                util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" );
+            }
+        }, false );
+
+        util.triggerEvent( document, "impress:help:add",
+            { command: "F5 / ESC", text: "Fullscreen: Enter / Exit", row: 200 } );
+
+    }, false );
+
+} )( document );
+
+
+/**
+ * Goto Plugin
+ *
+ * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
+ * and will alter the destination where to transition next.
+ *
+ * Example:
+ *
+ *         <!-- When leaving this step, go directly to "step-5" -->
+ *         <div class="step" data-goto="step-5">
+ *
+ *         <!-- When leaving this step with next(), go directly to "step-5", instead of next step.
+ *              If moving backwards to previous step - e.g. prev() instead of next() -
+ *              then go to "step-1". -->
+ *         <div class="step" data-goto-next="step-5" data-goto-prev="step-1">
+ *
+ *        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear
+ *             navigation. -->
+ *        <div class="step"
+ *             data-goto-key-list="ArrowUp ArrowDown ArrowRight ArrowLeft"
+ *             data-goto-next-list="step-4 step-3 step-2 step-5">
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
+ * of what strings to use for each key.
+ *
+ * Copyright 2016-2017 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document, impress */
+
+( function( document, window ) {
+    "use strict";
+    var lib;
+
+    document.addEventListener( "impress:init", function( event ) {
+        lib = event.detail.api.lib;
+    }, false );
+
+    var isNumber = function( numeric ) {
+        return !isNaN( numeric );
+    };
+
+    var goto = function( event ) {
+        if ( ( !event ) || ( !event.target ) ) {
+            return;
+        }
+
+        var data = event.target.dataset;
+        var steps = document.querySelectorAll( ".step" );
+
+        // Data-goto-key-list="" & data-goto-next-list="" //////////////////////////////////////////
+        if ( data.gotoKeyList !== undefined &&
+             data.gotoNextList !== undefined &&
+             event.origEvent !== undefined &&
+             event.origEvent.key !== undefined ) {
+            var keylist = data.gotoKeyList.split( " " );
+            var nextlist = data.gotoNextList.split( " " );
+
+            if ( keylist.length !== nextlist.length ) {
+                window.console.log(
+                    "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
+                );
+                window.console.log( keylist );
+                window.console.log( nextlist );
+
+                // Don't return, allow the other categories to work despite this error
+            } else {
+                var index = keylist.indexOf( event.origEvent.key );
+                if ( index >= 0 ) {
+                    var next = nextlist[ index ];
+                    if ( isNumber( next ) ) {
+                        event.detail.next = steps[ next ];
+
+                        // If the new next element has its own transitionDuration, we're responsible
+                        // for setting that on the event as well
+                        event.detail.transitionDuration = lib.util.toNumber(
+                            event.detail.next.dataset.transitionDuration,
+                            event.detail.transitionDuration
+                        );
+                        return;
+                    } else {
+                        var newTarget = document.getElementById( next );
+                        if ( newTarget && newTarget.classList.contains( "step" ) ) {
+                            event.detail.next = newTarget;
+                            event.detail.transitionDuration = lib.util.toNumber(
+                                event.detail.next.dataset.transitionDuration,
+                                event.detail.transitionDuration
+                            );
+                            return;
+                        } else {
+                            window.console.log( "impress goto plugin: " + next +
+                                                " is not a step in this impress presentation." );
+                        }
+                    }
+                }
+            }
+        }
+
+        // Data-goto-next="" & data-goto-prev="" ///////////////////////////////////////////////////
+
+        // Handle event.target data-goto-next attribute
+        if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) {
+            event.detail.next = steps[ data.gotoNext ];
+
+            // If the new next element has its own transitionDuration, we're responsible for setting
+            // that on the event as well
+            event.detail.transitionDuration = lib.util.toNumber(
+                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+            );
+            return;
+        }
+        if ( data.gotoNext && event.detail.reason === "next" ) {
+            var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line
+            if ( newTarget && newTarget.classList.contains( "step" ) ) {
+                event.detail.next = newTarget;
+                event.detail.transitionDuration = lib.util.toNumber(
+                    event.detail.next.dataset.transitionDuration,
+                    event.detail.transitionDuration
+                );
+                return;
+            } else {
+                window.console.log( "impress goto plugin: " + data.gotoNext +
+                                    " is not a step in this impress presentation." );
+            }
+        }
+
+        // Handle event.target data-goto-prev attribute
+        if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) {
+            event.detail.next = steps[ data.gotoPrev ];
+            event.detail.transitionDuration = lib.util.toNumber(
+                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+            );
+            return;
+        }
+        if ( data.gotoPrev && event.detail.reason === "prev" ) {
+            var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line
+            if ( newTarget && newTarget.classList.contains( "step" ) ) {
+                event.detail.next = newTarget;
+                event.detail.transitionDuration = lib.util.toNumber(
+                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+                );
+                return;
+            } else {
+                window.console.log( "impress goto plugin: " + data.gotoPrev +
+                                    " is not a step in this impress presentation." );
+            }
+        }
+
+        // Data-goto="" ///////////////////////////////////////////////////////////////////////////
+
+        // Handle event.target data-goto attribute
+        if ( isNumber( data.goto ) ) {
+            event.detail.next = steps[ data.goto ];
+            event.detail.transitionDuration = lib.util.toNumber(
+                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+            );
+            return;
+        }
+        if ( data.goto ) {
+            var newTarget = document.getElementById( data.goto ); // jshint ignore:line
+            if ( newTarget && newTarget.classList.contains( "step" ) ) {
+                event.detail.next = newTarget;
+                event.detail.transitionDuration = lib.util.toNumber(
+                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+                );
+                return;
+            } else {
+                window.console.log( "impress goto plugin: " + data.goto +
+                                    " is not a step in this impress presentation." );
+            }
+        }
+    };
+
+    // Register the plugin to be called in pre-stepleave phase
+    impress.addPreStepLeavePlugin( goto );
+
+} )( document, window );
+
+
+/**
+ * Help popup plugin
+ *
+ * Example:
+ *
+ *     <!-- Show a help popup at start, or if user presses "H" -->
+ *     <div id="impress-help"></div>
+ *
+ * For developers:
+ *
+ * Typical use for this plugin, is for plugins that support some keypress, to add a line
+ * to the help popup produced by this plugin. For example "P: Presenter console".
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document */
+
+( function( document, window ) {
+    "use strict";
+    var rows = [];
+    var timeoutHandle;
+
+    var triggerEvent = function( el, eventName, detail ) {
+        var event = document.createEvent( "CustomEvent" );
+        event.initCustomEvent( eventName, true, true, detail );
+        el.dispatchEvent( event );
+    };
+
+    var renderHelpDiv = function() {
+        var helpDiv = document.getElementById( "impress-help" );
+        if ( helpDiv ) {
+            var html = [];
+            for ( var row in rows ) {
+                for ( var arrayItem in row ) {
+                    html.push( rows[ row ][ arrayItem ] );
+                }
+            }
+            if ( html ) {
+                helpDiv.innerHTML = "<table>\n" + html.join( "\n" ) + "</table>\n";
+            }
+        }
+    };
+
+    var toggleHelp = function() {
+        var helpDiv = document.getElementById( "impress-help" );
+        if ( !helpDiv ) {
+            return;
+        }
+
+        if ( helpDiv.style.display === "block" ) {
+            helpDiv.style.display = "none";
+        } else {
+            helpDiv.style.display = "block";
+            window.clearTimeout( timeoutHandle );
+        }
+    };
+
+    document.addEventListener( "keyup", function( event ) {
+
+        if ( event.keyCode === 72 || event.keyCode === 191 ) { // "h" || "?"
+            event.preventDefault();
+            toggleHelp();
+        }
+    }, false );
+
+    // API
+    // Other plugins can add help texts, typically if they support an action on a keypress.
+    /**
+     * Add a help text to the help popup.
+     *
+     * :param: e.detail.command  Example: "H"
+     * :param: e.detail.text     Example: "Show this help."
+     * :param: e.detail.row      Row index from 0 to 9 where to place this help text. Example: 0
+     */
+    document.addEventListener( "impress:help:add", function( e ) {
+
+        // The idea is for the sender of the event to supply a unique row index, used for sorting.
+        // But just in case two plugins would ever use the same row index, we wrap each row into
+        // its own array. If there are more than one entry for the same index, they are shown in
+        // first come, first serve ordering.
+        var rowIndex = e.detail.row;
+        if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
+            rows[ rowIndex ] = [];
+        }
+        rows[ e.detail.row ].push( "<tr><td><strong>" + e.detail.command + "</strong></td><td>" +
+                                   e.detail.text + "</td></tr>" );
+        renderHelpDiv();
+    } );
+
+    document.addEventListener( "impress:init", function( e ) {
+        renderHelpDiv();
+
+        // At start, show the help for 7 seconds.
+        var helpDiv = document.getElementById( "impress-help" );
+        if ( helpDiv ) {
+            helpDiv.style.display = "block";
+            timeoutHandle = window.setTimeout( function() {
+                var helpDiv = document.getElementById( "impress-help" );
+                helpDiv.style.display = "none";
+            }, 7000 );
+
+            // Regster callback to empty the help div on teardown
+            var api = e.detail.api;
+            api.lib.gc.pushCallback( function() {
+                window.clearTimeout( timeoutHandle );
+                helpDiv.style.display = "";
+                helpDiv.innerHTML = "";
+                rows = [];
+            } );
+        }
+
+        // Use our own API to register the help text for "h"
+        triggerEvent( document, "impress:help:add",
+                      { command: "H", text: "Show this help", row: 0 } );
+    } );
+
+} )( document, window );
+
+
+/**
+ * Adds a presenter console to impress.js
+ *
+ * MIT Licensed, see license.txt.
+ *
+ * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
+ *
+ * version: 1.3-dev
+ *
+ */
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global navigator, top, setInterval, clearInterval, document, window */
+
+( function( document, window ) {
+    'use strict';
+
+    // TODO: Move this to src/lib/util.js
+    var triggerEvent = function( el, eventName, detail ) {
+        var event = document.createEvent( 'CustomEvent' );
+        event.initCustomEvent( eventName, true, true, detail );
+        el.dispatchEvent( event );
+    };
+
+    // Create Language object depending on browsers language setting
+    var lang;
+    switch ( navigator.language ) {
+    case 'de':
+        lang = {
+            'noNotes': '<div class="noNotes">Keine Notizen hierzu</div>',
+            'restart': 'Neustart',
+            'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',
+            'prev': 'zurück',
+            'next': 'weiter',
+            'loading': 'initalisiere',
+            'ready': 'Bereit',
+            'moving': 'in Bewegung',
+            'useAMPM': false
+        };
+        break;
+    case 'en': // jshint ignore:line
+    default : // jshint ignore:line
+        lang = {
+            'noNotes': '<div class="noNotes">No notes for this step</div>',
+            'restart': 'Restart',
+            'clickToOpen': 'Click to open speaker console',
+            'prev': 'Prev',
+            'next': 'Next',
+            'loading': 'Loading',
+            'ready': 'Ready',
+            'moving': 'Moving',
+            'useAMPM': false
+        };
+        break;
+    }
+
+    // Settings to set iframe in speaker console
+    const preViewDefaultFactor = 0.7;
+    const preViewMinimumFactor = 0.5;
+    const preViewGap    = 4;
+
+    // This is the default template for the speaker console window
+    const consoleTemplate = '<!DOCTYPE html>' +
+        '<html id="impressconsole"><head>' +
+
+          // Order is important: If user provides a cssFile, those will win, because they're later
+          '{{cssStyle}}' +
+          '{{cssLink}}' +
+        '</head><body>' +
+        '<div id="console">' +
+          '<div id="views">' +
+            '<iframe id="slideView" scrolling="no"></iframe>' +
+            '<iframe id="preView" scrolling="no"></iframe>' +
+            '<div id="blocker"></div>' +
+          '</div>' +
+          '<div id="notes"></div>' +
+        '</div>' +
+        '<div id="controls"> ' +
+          '<div id="prev"><a  href="#" onclick="impress().prev(); return false;" />' +
+            '{{prev}}</a></div>' +
+          '<div id="next"><a  href="#" onclick="impress().next(); return false;" />' +
+            '{{next}}</a></div>' +
+          '<div id="clock">--:--</div>' +
+          '<div id="timer" onclick="timerReset()">00m 00s</div>' +
+          '<div id="status">{{loading}}</div>' +
+        '</div>' +
+        '</body></html>';
+
+    // Default css location
+    var cssFileOldDefault = 'css/impressConsole.css';
+    var cssFile = undefined; // jshint ignore:line
+
+    // Css for styling iframs on the console
+    var cssFileIframeOldDefault = 'css/iframe.css';
+    var cssFileIframe = undefined; // jshint ignore:line
+
+    // All console windows, so that you can call impressConsole() repeatedly.
+    var allConsoles = {};
+
+    // Zero padding helper function:
+    var zeroPad = function( i ) {
+        return ( i < 10 ? '0' : '' ) + i;
+    };
+
+    // The console object
+    var impressConsole = window.impressConsole = function( rootId ) {
+
+        rootId = rootId || 'impress';
+
+        if ( allConsoles[ rootId ] ) {
+            return allConsoles[ rootId ];
+        }
+
+        // Root presentation elements
+        var root = document.getElementById( rootId );
+
+        var consoleWindow = null;
+
+        var nextStep = function() {
+            var classes = '';
+            var nextElement = document.querySelector( '.active' );
+
+            // Return to parents as long as there is no next sibling
+            while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
+                nextElement = nextElement.parentNode;
+            }
+            nextElement = nextElement.nextElementSibling;
+            while ( nextElement ) {
+                classes = nextElement.attributes[ 'class' ];
+                if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {
+                    consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;
+                    return nextElement;
+                }
+
+                if ( nextElement.firstElementChild ) { // First go into deep
+                    nextElement = nextElement.firstElementChild;
+                } else {
+
+                    // Go to next sibling or through parents until there is a next sibling
+                    while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
+                        nextElement = nextElement.parentNode;
+                    }
+                    nextElement = nextElement.nextElementSibling;
+                }
+            }
+
+            // No next element. Pick the first
+            consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;
+            return document.querySelector( '.step' );
+        };
+
+        // Sync the notes to the step
+        var onStepLeave = function() {
+            if ( consoleWindow ) {
+
+                // Set notes to next steps notes.
+                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
+                if ( newNotes ) {
+                    newNotes = newNotes.innerHTML;
+                } else {
+                    newNotes = lang.noNotes;
+                }
+                consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;
+
+                // Set the views
+                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
+                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
+                var preSrc = baseURL + '#' + nextStep().id;
+                var slideView = consoleWindow.document.getElementById( 'slideView' );
+
+                // Setting them when they are already set causes glithes in Firefox, so check first:
+                if ( slideView.src !== slideSrc ) {
+                    slideView.src = slideSrc;
+                }
+                var preView = consoleWindow.document.getElementById( 'preView' );
+                if ( preView.src !== preSrc ) {
+                    preView.src = preSrc;
+                }
+
+                consoleWindow.document.getElementById( 'status' ).innerHTML =
+                    '<span class="moving">' + lang.moving + '</span>';
+            }
+        };
+
+        // Sync the previews to the step
+        var onStepEnter = function() {
+            if ( consoleWindow ) {
+
+                // We do everything here again, because if you stopped the previos step to
+                // early, the onstepleave trigger is not called for that step, so
+                // we need this to sync things.
+                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
+                if ( newNotes ) {
+                    newNotes = newNotes.innerHTML;
+                } else {
+                    newNotes = lang.noNotes;
+                }
+                var notes = consoleWindow.document.getElementById( 'notes' );
+                notes.innerHTML = newNotes;
+                notes.scrollTop = 0;
+
+                // Set the views
+                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
+                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
+                var preSrc = baseURL + '#' + nextStep().id;
+                var slideView = consoleWindow.document.getElementById( 'slideView' );
+
+                // Setting them when they are already set causes glithes in Firefox, so check first:
+                if ( slideView.src !== slideSrc ) {
+                    slideView.src = slideSrc;
+                }
+                var preView = consoleWindow.document.getElementById( 'preView' );
+                if ( preView.src !== preSrc ) {
+                    preView.src = preSrc;
+                }
+
+                consoleWindow.document.getElementById( 'status' ).innerHTML =
+                    '<span  class="ready">' + lang.ready + '</span>';
+            }
+        };
+
+        // Sync substeps
+        var onSubstep = function( event ) {
+            if ( consoleWindow ) {
+                if ( event.detail.reason === 'next' ) {
+                    onSubstepShow();
+                }
+                if ( event.detail.reason === 'prev' ) {
+                    onSubstepHide();
+                }
+            }
+        };
+
+        var onSubstepShow = function() {
+            var slideView = consoleWindow.document.getElementById( 'slideView' );
+            triggerEventInView( slideView, 'impress:substep:show' );
+        };
+
+        var onSubstepHide = function() {
+            var slideView = consoleWindow.document.getElementById( 'slideView' );
+            triggerEventInView( slideView, 'impress:substep:hide' );
+        };
+
+        var triggerEventInView = function( frame, eventName, detail ) {
+
+            // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't
+            // work. This does work on Firefox, and should work if viewing the presentation on a
+            // http:// URL on Chrome.
+            var event = frame.contentDocument.createEvent( 'CustomEvent' );
+            event.initCustomEvent( eventName, true, true, detail );
+            frame.contentDocument.dispatchEvent( event );
+        };
+
+        var spaceHandler = function() {
+            var notes = consoleWindow.document.getElementById( 'notes' );
+            if ( notes.scrollTopMax - notes.scrollTop > 20 ) {
+               notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;
+            } else {
+               window.impress().next();
+            }
+        };
+
+        var timerReset = function() {
+            consoleWindow.timerStart = new Date();
+        };
+
+        // Show a clock
+        var clockTick = function() {
+            var now = new Date();
+            var hours = now.getHours();
+            var minutes = now.getMinutes();
+            var seconds = now.getSeconds();
+            var ampm = '';
+
+            if ( lang.useAMPM ) {
+                ampm = ( hours < 12 ) ? 'AM' : 'PM';
+                hours = ( hours > 12 ) ? hours - 12 : hours;
+                hours = ( hours === 0 ) ? 12 : hours;
+            }
+
+            // Clock
+            var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +
+                           ' ' + ampm;
+            consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;
+
+            // Timer
+            seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );
+            minutes = Math.floor( seconds / 60 );
+            seconds = Math.floor( seconds % 60 );
+            consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =
+                zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';
+
+            if ( !consoleWindow.initialized ) {
+
+                // Nudge the slide windows after load, or they will scrolled wrong on Firefox.
+                consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );
+                consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );
+                consoleWindow.initialized = true;
+            }
+        };
+
+        var registerKeyEvent = function( keyCodes, handler, window ) {
+            if ( window === undefined ) {
+                window = consoleWindow;
+            }
+
+            // Prevent default keydown action when one of supported key is pressed
+            window.document.addEventListener( 'keydown', function( event ) {
+                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
+                     keyCodes.indexOf( event.keyCode ) !== -1 ) {
+                    event.preventDefault();
+                }
+            }, false );
+
+            // Trigger impress action on keyup
+            window.document.addEventListener( 'keyup', function( event ) {
+                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
+                     keyCodes.indexOf( event.keyCode ) !== -1 ) {
+                        handler();
+                        event.preventDefault();
+                }
+            }, false );
+        };
+
+        var consoleOnLoad = function() {
+                var slideView = consoleWindow.document.getElementById( 'slideView' );
+                var preView = consoleWindow.document.getElementById( 'preView' );
+
+                // Firefox:
+                slideView.contentDocument.body.classList.add( 'impress-console' );
+                preView.contentDocument.body.classList.add( 'impress-console' );
+                if ( cssFileIframe !== undefined ) {
+                    slideView.contentDocument.head.insertAdjacentHTML(
+                        'beforeend',
+                        '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
+                    );
+                    preView.contentDocument.head.insertAdjacentHTML(
+                        'beforeend',
+                        '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
+                    );
+                }
+
+                // Chrome:
+                slideView.addEventListener( 'load', function() {
+                        slideView.contentDocument.body.classList.add( 'impress-console' );
+                        if ( cssFileIframe !== undefined ) {
+                            slideView.contentDocument.head.insertAdjacentHTML(
+                                'beforeend',
+                                '<link rel="stylesheet" type="text/css" href="' +
+                                    cssFileIframe + '">'
+                            );
+                        }
+                } );
+                preView.addEventListener( 'load', function() {
+                        preView.contentDocument.body.classList.add( 'impress-console' );
+                        if ( cssFileIframe !== undefined ) {
+                            preView.contentDocument.head.insertAdjacentHTML(
+                                'beforeend',
+                                '<link rel="stylesheet" type="text/css" href="' +
+                                    cssFileIframe + '">' );
+                        }
+                } );
+        };
+
+        var open = function() {
+            if ( top.isconsoleWindow ) {
+                return;
+            }
+
+            if ( consoleWindow && !consoleWindow.closed ) {
+                consoleWindow.focus();
+            } else {
+                consoleWindow = window.open( '', 'impressConsole' );
+
+                // If opening failes this may be because the browser prevents this from
+                // not (or less) interactive JavaScript...
+                if ( consoleWindow == null ) {
+
+                    // ... so I add a button to klick.
+                    // workaround on firefox
+                    var message = document.createElement( 'div' );
+                    message.id = 'impress-console-button';
+                    message.style.position = 'fixed';
+                    message.style.left = 0;
+                    message.style.top = 0;
+                    message.style.right = 0;
+                    message.style.bottom = 0;
+                    message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
+                    var clickStr = 'var x = document.getElementById(\'impress-console-button\');' +
+                                     'x.parentNode.removeChild(x);' +
+                                     'var r = document.getElementById(\'' + rootId + '\');' +
+                                     'impress(\'' + rootId +
+                                     '\').lib.util.triggerEvent(r, \'impress:console:open\', {})';
+                    var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';
+                    message.innerHTML = '<button style="' + styleStr + '" ' +
+                                                 'onclick="' + clickStr + '">' +
+                                        lang.clickToOpen +
+                                        '</button>';
+                    document.body.appendChild( message );
+                    return;
+                }
+
+                var cssLink = '';
+                if ( cssFile !== undefined ) {
+                    cssLink = '<link rel="stylesheet" type="text/css" media="screen" href="' +
+                              cssFile + '">';
+                }
+
+                // This sets the window location to the main window location, so css can be loaded:
+                consoleWindow.document.open();
+
+                // Write the template:
+                consoleWindow.document.write(
+
+                    // CssStyleStr is lots of inline <style></style> defined at the end of this file
+                    consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )
+                                   .replace( '{{cssLink}}', cssLink )
+                                   .replace( /{{.*?}}/gi, function( x ) {
+                                       return lang[ x.substring( 2, x.length - 2 ) ]; }
+                                   )
+                );
+                consoleWindow.document.title = 'Speaker Console (' + document.title + ')';
+                consoleWindow.impress = window.impress;
+
+                // We set this flag so we can detect it later, to prevent infinite popups.
+                consoleWindow.isconsoleWindow = true;
+
+                // Set the onload function:
+                consoleWindow.onload = consoleOnLoad;
+
+                // Add clock tick
+                consoleWindow.timerStart = new Date();
+                consoleWindow.timerReset = timerReset;
+                consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );
+
+                // Keyboard navigation handlers
+                // 33: pg up, 37: left, 38: up
+                registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );
+
+                // 34: pg down, 39: right, 40: down
+                registerKeyEvent( [ 34, 39, 40 ], window.impress().next );
+
+                // 32: space
+                registerKeyEvent( [ 32 ], spaceHandler );
+
+                // 82: R
+                registerKeyEvent( [ 82 ], timerReset );
+
+                // Cleanup
+                consoleWindow.onbeforeunload = function() {
+
+                    // I don't know why onunload doesn't work here.
+                    clearInterval( consoleWindow.clockInterval );
+                };
+
+                // It will need a little nudge on Firefox, but only after loading:
+                onStepEnter();
+                consoleWindow.initialized = false;
+                consoleWindow.document.close();
+
+                //Catch any window resize to pass size on
+                window.onresize = resize;
+                consoleWindow.onresize = resize;
+
+                return consoleWindow;
+            }
+        };
+
+        var resize = function() {
+            var slideView = consoleWindow.document.getElementById( 'slideView' );
+            var preView = consoleWindow.document.getElementById( 'preView' );
+
+            // Get ratio of presentation
+            var ratio = window.innerHeight / window.innerWidth;
+
+            // Get size available for views
+            var views = consoleWindow.document.getElementById( 'views' );
+
+            // SlideView may have a border or some padding:
+            // asuming same border width on both direktions
+            var delta = slideView.offsetWidth - slideView.clientWidth;
+
+            // Set views
+            var slideViewWidth = ( views.clientWidth - delta );
+            var slideViewHeight = Math.floor( slideViewWidth * ratio );
+
+            var preViewTop = slideViewHeight + preViewGap;
+
+            var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );
+            var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );
+
+            // Shrink preview to fit into space available
+            if ( views.clientHeight - delta < preViewTop + preViewHeight ) {
+                preViewHeight = views.clientHeight - delta - preViewTop;
+                preViewWidth = Math.floor( preViewHeight / ratio );
+            }
+
+            // If preview is not high enough forget ratios!
+            if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {
+                slideViewWidth = ( views.clientWidth - delta );
+                slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /
+                                             ( 1 + preViewMinimumFactor ) );
+
+                preViewTop = slideViewHeight + preViewGap;
+
+                preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );
+                preViewHeight = views.clientHeight - delta - preViewTop;
+            }
+
+            // Set the calculated into styles
+            slideView.style.width = slideViewWidth + 'px';
+            slideView.style.height = slideViewHeight + 'px';
+
+            preView.style.top = preViewTop + 'px';
+
+            preView.style.width = preViewWidth + 'px';
+            preView.style.height = preViewHeight + 'px';
+        };
+
+        var _init = function( cssConsole, cssIframe ) {
+            if ( cssConsole !== undefined ) {
+                cssFile = cssConsole;
+            }
+
+            // You can also specify the css in the presentation root div:
+            // <div id="impress" data-console-css=..." data-console-css-iframe="...">
+            else if ( root.dataset.consoleCss !== undefined ) {
+                cssFile = root.dataset.consoleCss;
+            }
+
+            if ( cssIframe !== undefined ) {
+                cssFileIframe = cssIframe;
+            } else if ( root.dataset.consoleCssIframe !== undefined ) {
+                cssFileIframe = root.dataset.consoleCssIframe;
+            }
+
+            // Register the event
+            root.addEventListener( 'impress:stepleave', onStepLeave );
+            root.addEventListener( 'impress:stepenter', onStepEnter );
+            root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );
+            root.addEventListener( 'impress:substep:show', onSubstepShow );
+            root.addEventListener( 'impress:substep:hide', onSubstepHide );
+
+            //When the window closes, clean up after ourselves.
+            window.onunload = function() {
+                if ( consoleWindow && !consoleWindow.closed ) {
+                    consoleWindow.close();
+                }
+            };
+
+            //Open speaker console when they press 'p'
+            registerKeyEvent( [ 80 ], open, window );
+
+            //Btw, you can also launch console automatically:
+            //<div id="impress" data-console-autolaunch="true">
+            if ( root.dataset.consoleAutolaunch === 'true' ) {
+                open();
+            }
+        };
+
+        var init = function( cssConsole, cssIframe ) {
+            if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
+                 ( cssIframe === undefined  || cssIframe === cssFileIframeOldDefault ) ) {
+                window.console.log( 'impressConsole().init() is deprecated. ' +
+                                   'impressConsole is now initialized automatically when you ' +
+                                   'call impress().init().' );
+            }
+            _init( cssConsole, cssIframe );
+        };
+
+        // New API for impress.js plugins is based on using events
+        root.addEventListener( 'impress:console:open', function() {
+            open();
+        } );
+
+        /**
+         * Register a key code to an event handler
+         *
+         * :param: event.detail.keyCodes    List of key codes
+         * :param: event.detail.handler     A function registered as the event handler
+         * :param: event.detail.window      The console window to register the keycode in
+         */
+        root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
+            registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
+        } );
+
+        // Return the object
+        allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
+                               registerKeyEvent: registerKeyEvent, _init: _init };
+        return allConsoles[ rootId ];
+
+    };
+
+    // This initializes impressConsole automatically when initializing impress itself
+    document.addEventListener( 'impress:init', function( event ) {
+
+        // Note: impressConsole wants the id string, not the DOM element directly
+        impressConsole( event.target.id )._init();
+
+        // Add 'P' to the help popup
+        triggerEvent( document, 'impress:help:add',
+                        { command: 'P', text: 'Presenter console', row: 10 } );
+    } );
+
+    // Returns a string to be used inline as a css <style> element in the console window.
+    // Apologies for length, but hiding it here at the end to keep it away from rest of the code.
+    var cssStyleStr = function() {
+        return `<style>
+            #impressconsole body {
+                background-color: rgb(255, 255, 255);
+                padding: 0;
+                margin: 0;
+                font-family: verdana, arial, sans-serif;
+                font-size: 2vw;
+            }
+
+            #impressconsole div#console {
+                position: absolute;
+                top: 0.5vw;
+                left: 0.5vw;
+                right: 0.5vw;
+                bottom: 3vw;
+                margin: 0;
+            }
+
+            #impressconsole div#views, #impressconsole div#notes {
+                position: absolute;
+                top: 0;
+                bottom: 0;
+            }
+
+            #impressconsole div#views {
+                left: 0;
+                right: 50vw;
+                overflow: hidden;
+            }
+
+            #impressconsole div#blocker {
+                position: absolute;
+                right: 0;
+                bottom: 0;
+            }
+
+            #impressconsole div#notes {
+                left: 50vw;
+                right: 0;
+                overflow-x: hidden;
+                overflow-y: auto;
+                padding: 0.3ex;
+                background-color: rgb(255, 255, 255);
+                border: solid 1px rgb(120, 120, 120);
+            }
+
+            #impressconsole div#notes .noNotes {
+                color: rgb(200, 200, 200);
+            }
+
+            #impressconsole div#notes p {
+                margin-top: 0;
+            }
+
+            #impressconsole iframe {
+                position: absolute;
+                margin: 0;
+                padding: 0;
+                left: 0;
+                border: solid 1px rgb(120, 120, 120);
+            }
+
+            #impressconsole iframe#slideView {
+                top: 0;
+                width: 49vw;
+                height: 49vh;
+            }
+
+            #impressconsole iframe#preView {
+                opacity: 0.7;
+                top: 50vh;
+                width: 30vw;
+                height: 30vh;
+            }
+
+            #impressconsole div#controls {
+                margin: 0;
+                position: absolute;
+                bottom: 0.25vw;
+                left: 0.5vw;
+                right: 0.5vw;
+                height: 2.5vw;
+                background-color: rgb(255, 255, 255);
+                background-color: rgba(255, 255, 255, 0.6);
+            }
+
+            #impressconsole div#prev, div#next {
+            }
+
+            #impressconsole div#prev a, #impressconsole div#next a {
+                display: block;
+                border: solid 1px rgb(70, 70, 70);
+                border-radius: 0.5vw;
+                font-size: 1.5vw;
+                padding: 0.25vw;
+                text-decoration: none;
+                background-color: rgb(220, 220, 220);
+                color: rgb(0, 0, 0);
+            }
+
+            #impressconsole div#prev a:hover, #impressconsole div#next a:hover {
+                background-color: rgb(245, 245, 245);
+            }
+
+            #impressconsole div#prev {
+                float: left;
+            }
+
+            #impressconsole div#next {
+                float: right;
+            }
+
+            #impressconsole div#status {
+                margin-left: 2em;
+                margin-right: 2em;
+                text-align: center;
+                float: right;
+            }
+
+            #impressconsole div#clock {
+                margin-left: 2em;
+                margin-right: 2em;
+                text-align: center;
+                float: left;
+            }
+
+            #impressconsole div#timer {
+                margin-left: 2em;
+                margin-right: 2em;
+                text-align: center;
+                float: left;
+            }
+
+            #impressconsole span.moving {
+                color: rgb(255, 0, 0);
+            }
+
+            #impressconsole span.ready {
+                color: rgb(0, 128, 0);
+            }
+        </style>`;
+    };
+
+} )( document, window );
+
+/**
+ * Media Plugin
+ *
+ * This plugin will do the following things:
+ *
+ *  - Add a special class when playing (body.impress-media-video-playing
+ *    and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused
+ *    and body.impress-media-audio-paused) (removing them when ending).
+ *    This can be useful for example for darkening the background or fading out other elements
+ *    while a video is playing.
+ *    Only media at the current step are taken into account. All classes are removed when leaving
+ *    a step.
+ *
+ *  - Introduce the following new data attributes:
+ *
+ *    - data-media-autoplay="true": Autostart media when entering its step.
+ *    - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its
+ *      step.
+ *    - data-media-autopause="true": Pause media but keep current time when leaving its step.
+ *
+ *    When these attributes are added to a step they are inherited by all media on this step.
+ *    Of course this setting can be overwritten by adding different attributes to inidvidual
+ *    media.
+ *
+ *    The same rule applies when this attributes is added to the root element. Settings can be
+ *    overwritten for individual steps and media.
+ *
+ *    Examples:
+ *    - data-media-autostart="true" data-media-autostop="true": start media on enter, stop on
+ *      leave, restart from beginning when re-entering the step.
+ *
+ *    - data-media-autostart="true" data-media-autopause="true": start media on enter, pause on
+ *      leave, resume on re-enter
+ *
+ *    - data-media-autostart="true" data-media-autostop="true" data-media-autopause="true": start
+ *      media on enter, stop on leave (stop overwrites pause).
+ *
+ *    - data-media-autostart="true" data-media-autopause="false": let media start automatically
+ *      when entering a step and let it play when leaving the step.
+ *
+ *    - <div id="impress" data-media-autostart="true"> ... <div class="step"
+ *      data-media-autostart="false">
+ *      All media is startet automatically on all steps except the one that has the
+ *      data-media-autostart="false" attribute.
+ *
+ *  - Pro tip: Use <audio onended="impress().next()"> or <video onended="impress().next()"> to
+ *    proceed to the next step automatically, when the end of the media is reached.
+ *
+ *
+ * Copyright 2018 Holger Teichert (@complanar)
+ * Released under the MIT license.
+ */
+/* global window, document */
+
+( function( document, window ) {
+    "use strict";
+    var root, api, gc, attributeTracker;
+
+    attributeTracker = [];
+
+    // Function names
+    var enhanceMediaNodes,
+        enhanceMedia,
+        removeMediaClasses,
+        onStepenterDetectImpressConsole,
+        onStepenter,
+        onStepleave,
+        onPlay,
+        onPause,
+        onEnded,
+        getMediaAttribute,
+        teardown;
+
+    document.addEventListener( "impress:init", function( event ) {
+        root = event.target;
+        api = event.detail.api;
+        gc = api.lib.gc;
+
+        enhanceMedia();
+
+        gc.pushCallback( teardown );
+    }, false );
+
+    teardown = function() {
+        var el, i;
+        removeMediaClasses();
+        for ( i = 0; i < attributeTracker.length; i += 1 ) {
+            el = attributeTracker[ i ];
+            el.node.removeAttribute( el.attr );
+        }
+        attributeTracker = [];
+    };
+
+    getMediaAttribute = function( attributeName, nodes ) {
+        var attrName, attrValue, i, node;
+        attrName = "data-media-" + attributeName;
+
+        // Look for attributes in all nodes
+        for ( i = 0; i < nodes.length; i += 1 ) {
+            node = nodes[ i ];
+
+            // First test, if the attribute exists, because some browsers may return
+            // an empty string for non-existing attributes - specs are not clear at that point
+            if ( node.hasAttribute( attrName ) ) {
+
+                // Attribute found, return their parsed boolean value, empty strings count as true
+                // to enable empty value booleans (common in html5 but not allowed in well formed
+                // xml).
+                attrValue = node.getAttribute( attrName );
+                if ( attrValue === "" || attrValue === "true" ) {
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+
+            // No attribute found at current node, proceed with next round
+        }
+
+        // Last resort: no attribute found - return undefined to distiguish from false
+        return undefined;
+    };
+
+    onPlay = function( event ) {
+        var type = event.target.nodeName.toLowerCase();
+        document.body.classList.add( "impress-media-" + type + "-playing" );
+        document.body.classList.remove( "impress-media-" + type + "-paused" );
+    };
+
+    onPause = function( event ) {
+        var type = event.target.nodeName.toLowerCase();
+        document.body.classList.add( "impress-media-" + type + "-paused" );
+        document.body.classList.remove( "impress-media-" + type + "-playing" );
+    };
+
+    onEnded = function( event ) {
+        var type = event.target.nodeName.toLowerCase();
+        document.body.classList.remove( "impress-media-" + type + "-playing" );
+        document.body.classList.remove( "impress-media-" + type + "-paused" );
+    };
+
+    removeMediaClasses = function() {
+        var type, types;
+        types = [ "video", "audio" ];
+        for ( type in types ) {
+            document.body.classList.remove( "impress-media-" + types[ type ] + "-playing" );
+            document.body.classList.remove( "impress-media-" + types[ type ] + "-paused" );
+        }
+    };
+
+    enhanceMediaNodes = function() {
+        var i, id, media, mediaElement, type;
+
+        media = root.querySelectorAll( "audio, video" );
+        for ( i = 0; i < media.length; i += 1 ) {
+            type = media[ i ].nodeName.toLowerCase();
+
+            // Set an id to identify each media node - used e.g. for cross references by
+            // the consoleMedia plugin
+            mediaElement = media[ i ];
+            id = mediaElement.getAttribute( "id" );
+            if ( id === undefined || id === null ) {
+                mediaElement.setAttribute( "id", "media-" + type + "-" + i );
+                attributeTracker.push( { "node": mediaElement, "attr": "id" } );
+            }
+            gc.addEventListener( mediaElement, "play", onPlay );
+            gc.addEventListener( mediaElement, "playing", onPlay );
+            gc.addEventListener( mediaElement, "pause", onPause );
+            gc.addEventListener( mediaElement, "ended", onEnded );
+        }
+    };
+
+    enhanceMedia = function() {
+        var steps, stepElement, i;
+        enhanceMediaNodes();
+        steps = document.getElementsByClassName( "step" );
+        for ( i = 0; i < steps.length; i += 1 ) {
+            stepElement = steps[ i ];
+
+            gc.addEventListener( stepElement, "impress:stepenter", onStepenter );
+            gc.addEventListener( stepElement, "impress:stepleave", onStepleave );
+        }
+    };
+
+    onStepenterDetectImpressConsole = function() {
+        return {
+            "preview": ( window.frameElement !== null && window.frameElement.id === "preView" ),
+            "slideView": ( window.frameElement !== null && window.frameElement.id === "slideView" )
+        };
+    };
+
+    onStepenter = function( event ) {
+        var stepElement, media, mediaElement, i, onConsole, autoplay;
+        if ( ( !event ) || ( !event.target ) ) {
+            return;
+        }
+
+        stepElement = event.target;
+        removeMediaClasses();
+
+        media = stepElement.querySelectorAll( "audio, video" );
+        for ( i = 0; i < media.length; i += 1 ) {
+            mediaElement = media[ i ];
+
+            // Autoplay when (maybe inherited) autoplay setting is true,
+            // but only if not on preview of the next step in impressConsole
+            onConsole = onStepenterDetectImpressConsole();
+            autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
+            if ( autoplay && !onConsole.preview ) {
+                if ( onConsole.slideView ) {
+                    mediaElement.muted = true;
+                }
+                mediaElement.play();
+            }
+        }
+    };
+
+    onStepleave = function( event ) {
+        var stepElement, media, i, mediaElement, autoplay, autopause, autostop;
+        if ( ( !event || !event.target ) ) {
+            return;
+        }
+
+        stepElement = event.target;
+        media = event.target.querySelectorAll( "audio, video" );
+        for ( i = 0; i < media.length; i += 1 ) {
+            mediaElement = media[ i ];
+
+            autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
+            autopause = getMediaAttribute( "autopause", [ mediaElement, stepElement, root ] );
+            autostop = getMediaAttribute( "autostop",  [ mediaElement, stepElement, root ] );
+
+            // If both autostop and autopause are undefined, set it to the value of autoplay
+            if ( autostop === undefined && autopause === undefined ) {
+                autostop = autoplay;
+            }
+
+            if ( autopause || autostop ) {
+                mediaElement.pause();
+                if ( autostop ) {
+                    mediaElement.currentTime = 0;
+                }
+            }
+        }
+        removeMediaClasses();
+    };
+
+} )( document, window );
+
+/**
+ * Mobile devices support
+ *
+ * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile
+ * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.
+ *
+ * Note: This plugin does not take into account possible redirections done with skip, goto etc
+ * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least
+ * be correct.
+ *
+ * Adapted to a plugin from a submission by @Kzeni:
+ * https://github.com/impress/impress.js/issues/333
+ */
+/* global document, navigator */
+( function( document ) {
+    "use strict";
+
+    var getNextStep = function( el ) {
+        var steps = document.querySelectorAll( ".step" );
+        for ( var i = 0; i < steps.length; i++ ) {
+            if ( steps[ i ] === el ) {
+                if ( i + 1 < steps.length ) {
+                    return steps[ i + 1 ];
+                } else {
+                    return steps[ 0 ];
+                }
+            }
+        }
+    };
+    var getPrevStep = function( el ) {
+        var steps = document.querySelectorAll( ".step" );
+        for ( var i = steps.length - 1; i >= 0; i-- ) {
+            if ( steps[ i ] === el ) {
+                if ( i - 1 >= 0 ) {
+                    return steps[ i - 1 ];
+                } else {
+                    return steps[ steps.length - 1 ];
+                }
+            }
+        }
+    };
+
+    // Detect mobile browsers & add CSS class as appropriate.
+    document.addEventListener( "impress:init", function( event ) {
+        var body = document.body;
+        if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+                 navigator.userAgent
+             ) ) {
+            body.classList.add( "impress-mobile" );
+        }
+
+        // Unset all this on teardown
+        var api = event.detail.api;
+        api.lib.gc.pushCallback( function() {
+            document.body.classList.remove( "impress-mobile" );
+            var prev = document.getElementsByClassName( "prev" )[ 0 ];
+            var next = document.getElementsByClassName( "next" )[ 0 ];
+            if ( typeof prev !== "undefined" ) {
+                prev.classList.remove( "prev" );
+            }
+            if ( typeof next !== "undefined" ) {
+                next.classList.remove( "next" );
+            }
+        } );
+    } );
+
+    // Add prev and next classes to the siblings of the newly entered active step element
+    // Remove prev and next classes from their current step elements
+    // Note: As an exception we break namespacing rules, as these are useful general purpose
+    // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,
+    // based on plugin name.)
+    document.addEventListener( "impress:stepenter", function( event ) {
+	      var oldprev = document.getElementsByClassName( "prev" )[ 0 ];
+	      var oldnext = document.getElementsByClassName( "next" )[ 0 ];
+
+	      var prev = getPrevStep( event.target );
+	      prev.classList.add( "prev" );
+	      var next = getNextStep( event.target );
+	      next.classList.add( "next" );
+
+	      if ( typeof oldprev !== "undefined" ) {
+		      oldprev.classList.remove( "prev" );
+              }
+	      if ( typeof oldnext !== "undefined" ) {
+		      oldnext.classList.remove( "next" );
+              }
+    } );
+} )( document );
+
+
+/**
+ * Mouse timeout plugin
+ *
+ * After 3 seconds of mouse inactivity, add the css class
+ * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the
+ * class.
+ *
+ * The use case for this plugin is to use CSS to hide elements from the screen
+ * and only make them visible when the mouse is moved. Examples where this
+ * might be used are: the toolbar from the toolbar plugin, and the mouse cursor
+ * itself.
+ *
+ * Example CSS:
+ *
+ *     body.impress-mouse-timeout {
+ *         cursor: none;
+ *     }
+ *     body.impress-mouse-timeout div#impress-toolbar {
+ *         display: none;
+ *     }
+ *
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document */
+( function( document, window ) {
+    "use strict";
+    var timeout = 3;
+    var timeoutHandle;
+
+    var hide = function() {
+
+        // Mouse is now inactive
+        document.body.classList.add( "impress-mouse-timeout" );
+    };
+
+    var show = function() {
+        if ( timeoutHandle ) {
+            window.clearTimeout( timeoutHandle );
+        }
+
+        // Mouse is now active
+        document.body.classList.remove( "impress-mouse-timeout" );
+
+        // Then set new timeout after which it is considered inactive again
+        timeoutHandle = window.setTimeout( hide, timeout * 1000 );
+    };
+
+    document.addEventListener( "impress:init", function( event ) {
+        var api = event.detail.api;
+        var gc = api.lib.gc;
+        gc.addEventListener( document, "mousemove", show );
+        gc.addEventListener( document, "click", show );
+        gc.addEventListener( document, "touch", show );
+
+        // Set first timeout
+        show();
+
+        // Unset all this on teardown
+        gc.pushCallback( function() {
+            window.clearTimeout( timeoutHandle );
+            document.body.classList.remove( "impress-mouse-timeout" );
+        } );
+    }, false );
+
+} )( document, window );
+
+/**
+ * Navigation events plugin
+ *
+ * As you can see this part is separate from the impress.js core code.
+ * It's because these navigation actions only need what impress.js provides with
+ * its simple API.
+ *
+ * This plugin is what we call an _init plugin_. It's a simple kind of
+ * impress.js plugin. When loaded, it starts listening to the `impress:init`
+ * event. That event listener initializes the plugin functionality - in this
+ * case we listen to some keypress and mouse events. The only dependencies on
+ * core impress.js functionality is the `impress:init` method, as well as using
+ * the public api `next(), prev(),` etc when keys are pressed.
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz)
+ * Released under the MIT license.
+ * ------------------------------------------------
+ *  author:  Bartek Szopka
+ *  version: 0.5.3
+ *  url:     http://bartaz.github.com/impress.js/
+ *  source:  http://github.com/bartaz/impress.js/
+ *
+ */
+/* global document */
+( function( document ) {
+    "use strict";
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( "impress:init", function( event ) {
+
+        // Getting API from event data.
+        // So you don't event need to know what is the id of the root element
+        // or anything. `impress:init` event data gives you everything you
+        // need to control the presentation that was just initialized.
+        var api = event.detail.api;
+        var gc = api.lib.gc;
+        var util = api.lib.util;
+
+        // Supported keys are:
+        // [space] - quite common in presentation software to move forward
+        // [up] [right] / [down] [left] - again common and natural addition,
+        // [pgdown] / [pgup] - often triggered by remote controllers,
+        // [tab] - this one is quite controversial, but the reason it ended up on
+        //   this list is quite an interesting story... Remember that strange part
+        //   in the impress.js code where window is scrolled to 0,0 on every presentation
+        //   step, because sometimes browser scrolls viewport because of the focused element?
+        //   Well, the [tab] key by default navigates around focusable elements, so clicking
+        //   it very often caused scrolling to focused element and breaking impress.js
+        //   positioning. I didn't want to just prevent this default action, so I used [tab]
+        //   as another way to moving to next step... And yes, I know that for the sake of
+        //   consistency I should add [shift+tab] as opposite action...
+        var isNavigationEvent = function( event ) {
+
+            // Don't trigger navigation for example when user returns to browser window with ALT+TAB
+            if ( event.altKey || event.ctrlKey || event.metaKey ) {
+                return false;
+            }
+
+            // In the case of TAB, we force step navigation always, overriding the browser
+            // navigation between input elements, buttons and links.
+            // 79/o is a hack to mark all steps as past
+            // 69/e jumps to last slide
+            if ( event.keyCode === 9 ||
+                 event.keyCode == 79 ||
+                 event.keyCode == 69) {
+                return true;
+            }
+
+            // With the sole exception of TAB, we also ignore keys pressed if shift is down.
+            if ( event.shiftKey ) {
+                return false;
+            }
+
+            if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||
+                 ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {
+                return true;
+            }
+        };
+
+        // KEYBOARD NAVIGATION HANDLERS
+
+        // Prevent default keydown action when one of supported key is pressed.
+        gc.addEventListener( document, "keydown", function( event ) {
+            if ( isNavigationEvent( event ) ) {
+                event.preventDefault();
+            }
+        }, false );
+
+        // Trigger impress action (next or prev) on keyup.
+        gc.addEventListener( document, "keyup", function( event ) {
+            if ( isNavigationEvent( event ) ) {
+                if ( event.shiftKey ) {
+                    switch ( event.keyCode ) {
+                        case 9: // Shift+tab
+                            api.prev();
+                            break;
+                    }
+                } else {
+                    switch ( event.keyCode ) {
+                        case 33: // Pg up
+                        case 37: // Left
+                        case 38: // Up
+                                 api.prev( event );
+                                 break;
+                        case 9:  // Tab
+                        case 32: // Space
+                        case 34: // Pg down
+                        case 39: // Right
+                        case 40: // Down
+                                 api.next( event );
+                                 break;
+                        case 79: // o
+                                 api.pastAllSteps( event );
+                                 break;
+                        case 69: // e
+                                 api.last( event );
+                                 break;
+                    }
+                }
+                event.preventDefault();
+            }
+        }, false );
+
+        // Delegated handler for clicking on the links to presentation steps
+        gc.addEventListener( document, "click", function( event ) {
+
+            // Event delegation with "bubbling"
+            // check if event target (or any of its parents is a link)
+            var target = event.target;
+            try {
+                while ( ( target.tagName !== "A" ) &&
+                        ( target !== document.documentElement ) ) {
+                    target = target.parentNode;
+                }
+
+                if ( target.tagName === "A" ) {
+                    var href = target.getAttribute( "href" );
+
+                    // If it's a link to presentation step, target this step
+                    if ( href && href[ 0 ] === "#" ) {
+                        target = document.getElementById( href.slice( 1 ) );
+                    }
+                }
+
+                if ( api.goto( target ) ) {
+                    event.stopImmediatePropagation();
+                    event.preventDefault();
+                }
+            }
+            catch ( err ) {
+
+                // For example, when clicking on the button to launch speaker console, the button
+                // is immediately deleted from the DOM. In this case target is a DOM element when
+                // we get it, but turns out to be null if you try to actually do anything with it.
+                if ( err instanceof TypeError &&
+                     err.message === "target is null" ) {
+                    return;
+                }
+                throw err;
+            }
+        }, false );
+
+        // Delegated handler for clicking on step elements
+        gc.addEventListener( document, "click", function( event ) {
+            var target = event.target;
+            try {
+
+                // Find closest step element that is not active
+                while ( !( target.classList.contains( "step" ) &&
+                        !target.classList.contains( "active" ) ) &&
+                        ( target !== document.documentElement ) ) {
+                    target = target.parentNode;
+                }
+
+                if ( api.goto( target ) ) {
+                    event.preventDefault();
+                }
+            }
+            catch ( err ) {
+
+                // For example, when clicking on the button to launch speaker console, the button
+                // is immediately deleted from the DOM. In this case target is a DOM element when
+                // we get it, but turns out to be null if you try to actually do anything with it.
+                if ( err instanceof TypeError &&
+                     err.message === "target is null" ) {
+                    return;
+                }
+                throw err;
+            }
+        }, false );
+
+        // Add a line to the help popup
+        util.triggerEvent( document, "impress:help:add", { command: "Left &amp; Right",
+                                                           text: "Previous &amp; Next step",
+                                                           row: 1 } );
+
+    }, false );
+
+} )( document );
+
+
+/**
+ * Navigation UI plugin
+ *
+ * This plugin provides UI elements "back", "forward" and a list to select
+ * a specific slide number.
+ *
+ * The navigation controls are added to the toolbar plugin via DOM events. User must enable the
+ * toolbar in a presentation to have them visible.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global document */
+
+( function( document ) {
+    'use strict';
+    var toolbar;
+    var api;
+    var root;
+    var steps;
+    var hideSteps = [];
+    var prev;
+    var select;
+    var next;
+
+    var triggerEvent = function( el, eventName, detail ) {
+        var event = document.createEvent( 'CustomEvent' );
+        event.initCustomEvent( eventName, true, true, detail );
+        el.dispatchEvent( event );
+    };
+
+    var makeDomElement = function( html ) {
+        var tempDiv = document.createElement( 'div' );
+        tempDiv.innerHTML = html;
+        return tempDiv.firstChild;
+    };
+
+    var selectOptionsHtml = function() {
+        var options = '';
+        for ( var i = 0; i < steps.length; i++ ) {
+
+            // Omit steps that are listed as hidden from select widget
+            if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {
+                options = options + '<option value="' + steps[ i ].id + '">' + // jshint ignore:line
+                                    steps[ i ].id + '</option>' + '\n'; // jshint ignore:line
+            }
+        }
+        return options;
+    };
+
+    var addNavigationControls = function( event ) {
+        api = event.detail.api;
+        var gc = api.lib.gc;
+        root = event.target;
+        steps = root.querySelectorAll( '.step' );
+
+        var prevHtml   = '<button id="impress-navigation-ui-prev" title="Previous" ' +
+                         'class="impress-navigation-ui">&lt;</button>';
+        var selectHtml = '<select id="impress-navigation-ui-select" title="Go to" ' +
+                         'class="impress-navigation-ui">' + '\n' +
+                           selectOptionsHtml() +
+                           '</select>';
+        var nextHtml   = '<button id="impress-navigation-ui-next" title="Next" ' +
+                         'class="impress-navigation-ui">&gt;</button>';
+
+        prev = makeDomElement( prevHtml );
+        prev.addEventListener( 'click',
+            function() {
+                api.prev();
+        } );
+        select = makeDomElement( selectHtml );
+        select.addEventListener( 'change',
+            function( event ) {
+                api.goto( event.target.value );
+        } );
+        gc.addEventListener( root, 'impress:steprefresh', function( event ) {
+
+            // As impress.js core now allows to dynamically edit the steps, including adding,
+            // removing, and reordering steps, we need to requery and redraw the select list on
+            // every stepenter event.
+            steps = root.querySelectorAll( '.step' );
+            select.innerHTML = '\n' + selectOptionsHtml();
+
+            // Make sure the list always shows the step we're actually on, even if it wasn't
+            // selected from the list
+            select.value = event.target.id;
+        } );
+        next = makeDomElement( nextHtml );
+        next.addEventListener( 'click',
+            function() {
+                api.next();
+        } );
+
+        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );
+        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );
+        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );
+
+    };
+
+    // API for not listing given step in the select widget.
+    // For example, if you set class="skip" on some element, you may not want it to show up in the
+    // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted
+    // must be specifically added with this API call.
+    document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {
+        hideSteps.push( event.target );
+        if ( select ) {
+            select.innerHTML = selectOptionsHtml();
+        }
+    }, false );
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( 'impress:init', function( event ) {
+        toolbar = document.querySelector( '#impress-toolbar' );
+        if ( toolbar ) {
+            addNavigationControls( event );
+        }
+    }, false );
+
+} )( document );
+
+
+/* global document */
+( function( document ) {
+    "use strict";
+    var root;
+    var stepids = [];
+
+    // Get stepids from the steps under impress root
+    var getSteps = function() {
+        stepids = [];
+        var steps = root.querySelectorAll( ".step" );
+        for ( var i = 0; i < steps.length; i++ )
+        {
+          stepids[ i + 1 ] = steps[ i ].id;
+        }
+        };
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( "impress:init", function( event ) {
+            root = event.target;
+        getSteps();
+        var gc = event.detail.api.lib.gc;
+        gc.pushCallback( function() {
+            stepids = [];
+            if ( progressbar ) {
+                progressbar.style.width = "";
+                        }
+            if ( progress ) {
+                progress.innerHTML = "";
+                        }
+        } );
+    } );
+
+    var progressbar = document.querySelector( "div.impress-progressbar div" );
+    var progress = document.querySelector( "div.impress-progress" );
+
+    if ( null !== progressbar || null !== progress ) {
+        document.addEventListener( "impress:stepleave", function( event ) {
+            updateProgressbar( event.detail.next.id );
+        } );
+
+        document.addEventListener( "impress:steprefresh", function( event ) {
+            getSteps();
+            updateProgressbar( event.target.id );
+        } );
+
+    }
+
+    function updateProgressbar( slideId ) {
+        var slideNumber = stepids.indexOf( slideId );
+        if ( null !== progressbar ) {
+                        var width = 100 / ( stepids.length - 1 ) * ( slideNumber );
+            progressbar.style.width = width.toFixed( 2 ) + "%";
+        }
+        if ( null !== progress ) {
+            progress.innerHTML = slideNumber + "/" + ( stepids.length - 1 );
+        }
+    }
+} )( document );
+
+/**
+ * Relative Positioning Plugin
+ *
+ * This plugin provides support for defining the coordinates of a step relative
+ * to the previous step. This is often more convenient when creating presentations,
+ * since as you add, remove or move steps, you may not need to edit the positions
+ * as much as is the case with the absolute coordinates supported by impress.js
+ * core.
+ *
+ * Example:
+ *
+ *         <!-- Position step 1000 px to the right and 500 px up from the previous step. -->
+ *         <div class="step" data-rel-x="1000" data-rel-y="500">
+ *
+ * Following html attributes are supported for step elements:
+ *
+ *     data-rel-x
+ *     data-rel-y
+ *     data-rel-z
+ *
+ * These values are also inherited from the previous step. This makes it easy to
+ * create a boring presentation where each slide shifts for example 1000px down
+ * from the previous.
+ *
+ * In addition to plain numbers, which are pixel values, it is also possible to
+ * define relative positions as a multiple of screen height and width, using
+ * a unit of "h" and "w", respectively, appended to the number.
+ *
+ * Example:
+ *
+ *        <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
+ *
+ * This plugin is a *pre-init plugin*. It is called synchronously from impress.js
+ * core at the beginning of `impress().init()`. This allows it to process its own
+ * data attributes first, and possibly alter the data-x, data-y and data-z attributes
+ * that will then be processed by `impress().init()`.
+ *
+ * (Another name for this kind of plugin might be called a *filter plugin*, but
+ * *pre-init plugin* is more generic, as a plugin might do whatever it wants in
+ * the pre-init stage.)
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+    "use strict";
+
+    var startingState = {};
+
+    /**
+     * Copied from core impress.js. We currently lack a library mechanism to
+     * to share utility functions like this.
+     */
+    var toNumber = function( numeric, fallback ) {
+        return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
+    };
+
+    /**
+     * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
+     *
+     * Returns the computed value in pixels with w/h postfix removed.
+     */
+    var toNumberAdvanced = function( numeric, fallback ) {
+        if ( typeof numeric !== "string" ) {
+            return toNumber( numeric, fallback );
+        }
+        var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
+        if ( ratio == null ) {
+            return toNumber( numeric, fallback );
+        } else {
+            var value = parseFloat( ratio[ 1 ] );
+            var multiplier = ratio[ 2 ] === "w" ? window.innerWidth : window.innerHeight;
+            return value * multiplier;
+        }
+    };
+
+    var computeRelativePositions = function( el, prev ) {
+        var data = el.dataset;
+
+        if ( !prev ) {
+
+            // For the first step, inherit these defaults
+            prev = { x:0, y:0, z:0, relative: { x:0, y:0, z:0 } };
+        }
+
+        if ( data.relTo ) {
+
+            var ref = document.getElementById( data.relTo );
+            if ( ref ) {
+
+                // Test, if it is a previous step that already has some assigned position data
+                if ( el.compareDocumentPosition( ref ) & Node.DOCUMENT_POSITION_PRECEDING ) {
+                    prev.x = toNumber( ref.getAttribute( "data-x" ) );
+                    prev.y = toNumber( ref.getAttribute( "data-y" ) );
+                    prev.z = toNumber( ref.getAttribute( "data-z" ) );
+                    prev.relative = {};
+                } else {
+                    window.console.error(
+                        "impress.js rel plugin: Step \"" + data.relTo + "\" is not defined " +
+                        "*before* the current step. Referencing is limited to previously defined " +
+                        "steps. Please check your markup. Ignoring data-rel-to attribute of " +
+                        "this step. Have a look at the documentation for how to create relative " +
+                        "positioning to later shown steps with the help of the goto plugin."
+                    );
+                }
+            } else {
+
+                // Step not found
+                window.console.warn(
+                    "impress.js rel plugin: \"" + data.relTo + "\" is not a valid step in this " +
+                    "impress.js presentation. Please check your markup. Ignoring data-rel-to " +
+                    "attribute of this step."
+                );
+            }
+        }
+
+        var step = {
+                x: toNumber( data.x, prev.x ),
+                y: toNumber( data.y, prev.y ),
+                z: toNumber( data.z, prev.z ),
+                relative: {
+                    x: toNumberAdvanced( data.relX, prev.relative.x ),
+                    y: toNumberAdvanced( data.relY, prev.relative.y ),
+                    z: toNumberAdvanced( data.relZ, prev.relative.z )
+                }
+            };
+
+        // Relative position is ignored/zero if absolute is given.
+        // Note that this also has the effect of resetting any inherited relative values.
+        if ( data.x !== undefined ) {
+            step.relative.x = 0;
+        }
+        if ( data.y !== undefined ) {
+            step.relative.y = 0;
+        }
+        if ( data.z !== undefined ) {
+            step.relative.z = 0;
+        }
+
+        // Apply relative position to absolute position, if non-zero
+        // Note that at this point, the relative values contain a number value of pixels.
+        step.x = step.x + step.relative.x;
+        step.y = step.y + step.relative.y;
+        step.z = step.z + step.relative.z;
+
+        return step;
+    };
+
+    var rel = function( root ) {
+        var steps = root.querySelectorAll( ".step" );
+        var prev;
+        startingState[ root.id ] = [];
+        for ( var i = 0; i < steps.length; i++ ) {
+            var el = steps[ i ];
+            startingState[ root.id ].push( {
+                el: el,
+                x: el.getAttribute( "data-x" ),
+                y: el.getAttribute( "data-y" ),
+                z: el.getAttribute( "data-z" ),
+                relX: el.getAttribute( "data-rel-x" ),
+                relY: el.getAttribute( "data-rel-y" ),
+                relZ: el.getAttribute( "data-rel-z" )
+            } );
+            var step = computeRelativePositions( el, prev );
+
+            // Apply relative position (if non-zero)
+            el.setAttribute( "data-x", step.x );
+            el.setAttribute( "data-y", step.y );
+            el.setAttribute( "data-z", step.z );
+            prev = step;
+        }
+    };
+
+    // Register the plugin to be called in pre-init phase
+    window.impress.addPreInitPlugin( rel );
+
+    // Register teardown callback to reset the data.x, .y, .z values.
+    document.addEventListener( "impress:init", function( event ) {
+        var root = event.target;
+        event.detail.api.lib.gc.pushCallback( function() {
+            var steps = startingState[ root.id ];
+            var step;
+            while ( step = steps.pop() ) {
+
+                // Reset x/y/z in cases where this plugin has changed it.
+                if ( step.relX !== null ) {
+                    if ( step.x === null ) {
+                        step.el.removeAttribute( "data-x" );
+                    } else {
+                        step.el.setAttribute( "data-x", step.x );
+                    }
+                }
+                if ( step.relY !== null ) {
+                    if ( step.y === null ) {
+                        step.el.removeAttribute( "data-y" );
+                    } else {
+                        step.el.setAttribute( "data-y", step.y );
+                    }
+                }
+                if ( step.relZ !== null ) {
+                    if ( step.z === null ) {
+                        step.el.removeAttribute( "data-z" );
+                    } else {
+                        step.el.setAttribute( "data-z", step.z );
+                    }
+                }
+            }
+            delete startingState[ root.id ];
+        } );
+    }, false );
+} )( document, window );
+
+
+/**
+ * Resize plugin
+ *
+ * Rescale the presentation after a window resize.
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz)
+ * Released under the MIT license.
+ * ------------------------------------------------
+ *  author:  Bartek Szopka
+ *  version: 0.5.3
+ *  url:     http://bartaz.github.com/impress.js/
+ *  source:  http://github.com/bartaz/impress.js/
+ *
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+    "use strict";
+
+    // Wait for impress.js to be initialized
+    document.addEventListener( "impress:init", function( event ) {
+        var api = event.detail.api;
+
+        // Rescale presentation when window is resized
+        api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() {
+
+            // Force going to active step again, to trigger rescaling
+            api.goto( document.querySelector( ".step.active" ), 500 );
+        }, 250 ), false );
+    }, false );
+
+} )( document, window );
+
+
+/**
+ * Skip Plugin
+ *
+ * Example:
+ *
+ *    <!-- This slide is disabled in presentations, when moving with next()
+ *         and prev() commands, but you can still move directly to it, for
+ *         example with a url (anything using goto()). -->
+ *         <div class="step skip">
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+    "use strict";
+    var util;
+
+    document.addEventListener( "impress:init", function( event ) {
+        util = event.detail.api.lib.util;
+    }, false );
+
+    var getNextStep = function( el ) {
+        var steps = document.querySelectorAll( ".step" );
+        for ( var i = 0; i < steps.length; i++ ) {
+            if ( steps[ i ] === el ) {
+                if ( i + 1 < steps.length ) {
+                    return steps[ i + 1 ];
+                } else {
+                    return steps[ 0 ];
+                }
+            }
+        }
+    };
+    var getPrevStep = function( el ) {
+        var steps = document.querySelectorAll( ".step" );
+        for ( var i = steps.length - 1; i >= 0; i-- ) {
+            if ( steps[ i ] === el ) {
+                if ( i - 1 >= 0 ) {
+                    return steps[ i - 1 ];
+                } else {
+                    return steps[ steps.length - 1 ];
+                }
+            }
+        }
+    };
+
+    var skip = function( event ) {
+        if ( ( !event ) || ( !event.target ) ) {
+            return;
+        }
+
+        if ( event.detail.next.classList.contains( "skip" ) ) {
+            if ( event.detail.reason === "next" ) {
+
+                // Go to the next next step instead
+                event.detail.next = getNextStep( event.detail.next );
+
+                // Recursively call this plugin again, until there's a step not to skip
+                skip( event );
+            } else if ( event.detail.reason === "prev" ) {
+
+                // Go to the previous previous step instead
+                event.detail.next = getPrevStep( event.detail.next );
+                skip( event );
+            }
+
+            // If the new next element has its own transitionDuration, we're responsible for setting
+            // that on the event as well
+            event.detail.transitionDuration = util.toNumber(
+                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+            );
+        }
+    };
+
+    // Register the plugin to be called in pre-stepleave phase
+    // The weight makes this plugin run early. This is a good thing, because this plugin calls
+    // itself recursively.
+    window.impress.addPreStepLeavePlugin( skip, 1 );
+
+} )( document, window );
+
+
+/**
+ * Stop Plugin
+ *
+ * Example:
+ *
+ *        <!-- Stop at this slide.
+ *             (For example, when used on the last slide, this prevents the
+ *             presentation from wrapping back to the beginning.) -->
+ *        <div class="step stop">
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global document, window */
+( function( document, window ) {
+    "use strict";
+
+    var stop = function( event ) {
+        if ( ( !event ) || ( !event.target ) ) {
+            return;
+        }
+
+        if ( event.target.classList.contains( "stop" ) ) {
+            if ( event.detail.reason === "next" ) {
+                return false;
+            }
+        }
+    };
+
+    // Register the plugin to be called in pre-stepleave phase
+    // The weight makes this plugin run fairly early.
+    window.impress.addPreStepLeavePlugin( stop, 2 );
+
+} )( document, window );
+
+
+/**
+ * Substep Plugin
+ *
+ * Copyright 2017 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document, window */
+
+( function( document, window ) {
+    "use strict";
+
+    // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
+    var triggerEvent = function( el, eventName, detail ) {
+        var event = document.createEvent( "CustomEvent" );
+        event.initCustomEvent( eventName, true, true, detail );
+        el.dispatchEvent( event );
+    };
+
+    var activeStep = null;
+    document.addEventListener( "impress:stepenter", function( event ) {
+        activeStep = event.target;
+    }, false );
+
+    var substep = function( event ) {
+        if ( ( !event ) || ( !event.target ) ) {
+            return;
+        }
+
+        var step = event.target;
+        var el; // Needed by jshint
+        if ( event.detail.reason === "next" ) {
+            el = showSubstepIfAny( step );
+            if ( el ) {
+
+                // Send a message to others, that we aborted a stepleave event.
+                triggerEvent( step, "impress:substep:stepleaveaborted",
+                              { reason: "next", substep: el } );
+
+                // Autoplay uses this for reloading itself
+                triggerEvent( step, "impress:substep:enter",
+                              { reason: "next", substep: el } );
+
+                // Returning false aborts the stepleave event
+                return false;
+            }
+        }
+        if ( event.detail.reason === "prev" ) {
+            el = hideSubstepIfAny( step );
+            if ( el ) {
+                triggerEvent( step, "impress:substep:stepleaveaborted",
+                              { reason: "prev", substep: el } );
+
+                triggerEvent( step, "impress:substep:leave",
+                              { reason: "prev", substep: el } );
+
+                return false;
+            }
+        }
+    };
+
+    var showSubstepIfAny = function( step ) {
+        var substeps = step.querySelectorAll( ".substep" );
+        var visible = step.querySelectorAll( ".substep-visible" );
+        if ( substeps.length > 0 ) {
+            return showSubstep( substeps, visible );
+        }
+    };
+
+    var showSubstep = function( substeps, visible ) {
+        if ( visible.length < substeps.length ) {
+            for ( var i = 0; i < substeps.length; i++ ) {
+                substeps[ i ].classList.remove( "substep-active" );
+            }
+            var el = substeps[ visible.length ];
+            el.classList.add( "substep-visible" );
+            el.classList.add( "substep-active" );
+            return el;
+        }
+    };
+
+    var hideSubstepIfAny = function( step ) {
+        var substeps = step.querySelectorAll( ".substep" );
+        var visible = step.querySelectorAll( ".substep-visible" );
+        if ( substeps.length > 0 ) {
+            return hideSubstep( visible );
+        }
+    };
+
+    var hideSubstep = function( visible ) {
+        if ( visible.length > 0 ) {
+            var current = -1;
+            for ( var i = 0; i < visible.length; i++ ) {
+                if ( visible[ i ].classList.contains( "substep-active" ) ) {
+                    current = i;
+                }
+                visible[ i ].classList.remove( "substep-active" );
+            }
+            if ( current > 0 ) {
+                visible[ current - 1 ].classList.add( "substep-active" );
+            }
+            var el = visible[ visible.length - 1 ];
+            el.classList.remove( "substep-visible" );
+            return el;
+        }
+    };
+
+    // Register the plugin to be called in pre-stepleave phase.
+    // The weight makes this plugin run before other preStepLeave plugins.
+    window.impress.addPreStepLeavePlugin( substep, 1 );
+
+    // When entering a step, in particular when re-entering, make sure that all substeps are hidden
+    // at first
+    document.addEventListener( "impress:stepenter", function( event ) {
+        var step = event.target;
+        var visible = step.querySelectorAll( ".substep-visible" );
+        for ( var i = 0; i < visible.length; i++ ) {
+            visible[ i ].classList.remove( "substep-visible" );
+        }
+    }, false );
+
+    // API for others to reveal/hide next substep ////////////////////////////////////////////////
+    document.addEventListener( "impress:substep:show", function() {
+        showSubstepIfAny( activeStep );
+    }, false );
+
+    document.addEventListener( "impress:substep:hide", function() {
+        hideSubstepIfAny( activeStep );
+    }, false );
+
+} )( document, window );
+
+/**
+ * Support for swipe and tap on touch devices
+ *
+ * This plugin implements navigation for plugin devices, via swiping left/right,
+ * or tapping on the left/right edges of the screen.
+ *
+ *
+ *
+ * Copyright 2015: Andrew Dunai (@and3rson)
+ * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
+ *
+ * MIT License
+ */
+/* global document, window */
+( function( document, window ) {
+    "use strict";
+
+    // Touch handler to detect swiping left and right based on window size.
+    // If the difference in X change is bigger than 1/20 of the screen width,
+    // we simply call an appropriate API function to complete the transition.
+    var startX = 0;
+    var lastX = 0;
+    var lastDX = 0;
+    var threshold = window.innerWidth / 20;
+
+    document.addEventListener( "touchstart", function( event ) {
+        lastX = startX = event.touches[ 0 ].clientX;
+    } );
+
+    document.addEventListener( "touchmove", function( event ) {
+         var x = event.touches[ 0 ].clientX;
+         var diff = x - startX;
+
+         // To be used in touchend
+         lastDX = lastX - x;
+         lastX = x;
+
+         window.impress().swipe( diff / window.innerWidth );
+     } );
+
+     document.addEventListener( "touchend", function() {
+         var totalDiff = lastX - startX;
+         if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {
+             if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {
+                 window.impress().prev();
+             } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {
+                 window.impress().next();
+             }
+         } else if ( Math.abs( lastDX ) > threshold ) {
+             if ( lastDX < -threshold ) {
+                 window.impress().prev();
+             } else if ( lastDX > threshold ) {
+                 window.impress().next();
+             }
+         } else {
+
+             // No movement - move (back) to the current slide
+             window.impress().goto( document.querySelector( "#impress .step.active" ) );
+         }
+     } );
+
+     document.addEventListener( "touchcancel", function() {
+
+             // Move (back) to the current slide
+             window.impress().goto( document.querySelector( "#impress .step.active" ) );
+     } );
+
+} )( document, window );
+
+/**
+ * Toolbar plugin
+ *
+ * This plugin provides a generic graphical toolbar. Other plugins that
+ * want to expose a button or other widget, can add those to this toolbar.
+ *
+ * Using a single consolidated toolbar for all GUI widgets makes it easier
+ * to position and style the toolbar rather than having to do that for lots
+ * of different divs.
+ *
+ *
+ * *** For presentation authors: *****************************************
+ *
+ * To add/activate the toolbar in your presentation, add this div:
+ *
+ *     <div id="impress-toolbar"></div>
+ *
+ * Styling the toolbar is left to presentation author. Here's an example CSS:
+ *
+ *    .impress-enabled div#impress-toolbar {
+ *        position: fixed;
+ *        right: 1px;
+ *        bottom: 1px;
+ *        opacity: 0.6;
+ *    }
+ *    .impress-enabled div#impress-toolbar > span {
+ *        margin-right: 10px;
+ *    }
+ *
+ * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide
+ * the toolbar from sight, and only make it visible when mouse is moved.
+ *
+ *    body.impress-mouse-timeout div#impress-toolbar {
+ *        display: none;
+ *    }
+ *
+ *
+ * *** For plugin authors **********************************************
+ *
+ * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`
+ * or `impress:toolbar:insertBefore` events as appropriate. The detail object
+ * should contain following parameters:
+ *
+ *    { group : 1,                       // integer. Widgets with the same group are grouped inside
+ *                                       // the same <span> element.
+ *      html : "<button>Click</button>", // The html to add.
+ *      callback : "mycallback",         // Toolbar plugin will trigger event
+ *                                       // `impress:toolbar:added:mycallback` when done.
+ *      before: element }                // The reference element for an insertBefore() call.
+ *
+ * You should also listen to the `impress:toolbar:added:mycallback` event. At
+ * this point you can find the new widget in the DOM, and for example add an
+ * event listener to it.
+ *
+ * You are free to use any integer for the group. It's ok to leave gaps. It's
+ * ok to co-locate with widgets for another plugin, if you think they belong
+ * together.
+ *
+ * See navigation-ui for an example.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+
+/* global document */
+
+( function( document ) {
+    "use strict";
+    var toolbar = document.getElementById( "impress-toolbar" );
+    var groups = [];
+
+    /**
+     * Get the span element that is a child of toolbar, identified by index.
+     *
+     * If span element doesn't exist yet, it is created.
+     *
+     * Note: Because of Run-to-completion, this is not a race condition.
+     * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion
+     *
+     * :param: index   Method will return the element <span id="impress-toolbar-group-{index}">
+     */
+    var getGroupElement = function( index ) {
+        var id = "impress-toolbar-group-" + index;
+        if ( !groups[ index ] ) {
+            groups[ index ] = document.createElement( "span" );
+            groups[ index ].id = id;
+            var nextIndex = getNextGroupIndex( index );
+            if ( nextIndex === undefined ) {
+                toolbar.appendChild( groups[ index ] );
+            } else {
+                toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );
+            }
+        }
+        return groups[ index ];
+    };
+
+    /**
+     * Get the span element from groups[] that is immediately after given index.
+     *
+     * This can be used to find the reference node for an insertBefore() call.
+     * If no element exists at a larger index, returns undefined. (In this case,
+     * you'd use appendChild() instead.)
+     *
+     * Note that index needn't itself exist in groups[].
+     */
+    var getNextGroupIndex = function( index ) {
+        var i = index + 1;
+        while ( !groups[ i ] && i < groups.length ) {
+            i++;
+        }
+        if ( i < groups.length ) {
+            return i;
+        }
+    };
+
+    // API
+    // Other plugins can add and remove buttons by sending them as events.
+    // In return, toolbar plugin will trigger events when button was added.
+    if ( toolbar ) {
+        /**
+         * Append a widget inside toolbar span element identified by given group index.
+         *
+         * :param: e.detail.group    integer specifying the span element where widget will be placed
+         * :param: e.detail.element  a dom element to add to the toolbar
+         */
+        toolbar.addEventListener( "impress:toolbar:appendChild", function( e ) {
+            var group = getGroupElement( e.detail.group );
+            group.appendChild( e.detail.element );
+        } );
+
+        /**
+         * Add a widget to toolbar using insertBefore() DOM method.
+         *
+         * :param: e.detail.before   the reference dom element, before which new element is added
+         * :param: e.detail.element  a dom element to add to the toolbar
+         */
+        toolbar.addEventListener( "impress:toolbar:insertBefore", function( e ) {
+            toolbar.insertBefore( e.detail.element, e.detail.before );
+        } );
+
+        /**
+         * Remove the widget in e.detail.remove.
+         */
+        toolbar.addEventListener( "impress:toolbar:removeWidget", function( e ) {
+            toolbar.removeChild( e.detail.remove );
+        } );
+
+        document.addEventListener( "impress:init", function( event ) {
+            var api = event.detail.api;
+            api.lib.gc.pushCallback( function() {
+                toolbar.innerHTML = "";
+                groups = [];
+            } );
+        } );
+    } // If toolbar
+
+} )( document );
diff --git a/templates/impress.cs/js/slide-drawing.js b/templates/impress.cs/js/slide-drawing.js
new file mode 100644
index 0000000000000000000000000000000000000000..911c72af701edcc3125e92cca32dfede45b19002
--- /dev/null
+++ b/templates/impress.cs/js/slide-drawing.js
@@ -0,0 +1,91 @@
+function showInline(eleId) {
+    document.getElementById(eleId).style.display = "inline-block"
+}
+
+function hideEle(eleId) {
+    document.getElementById(eleId).style.display = "none"
+}
+
+function raiseCanvas() {
+    const lc = document.querySelector("#fabric-canvas-div .lower-canvas")
+    lc.style["z-index"] = 98
+    const uc = document.querySelector("#fabric-canvas-div .upper-canvas")
+    uc.style["z-index"] = 99
+}
+
+function lowerCanvas() {
+    const lc = document.querySelector("#fabric-canvas-div .lower-canvas")
+    lc.style["z-index"] = -101
+    const uc = document.querySelector("#fabric-canvas-div .upper-canvas")
+    uc.style["z-index"] = -100
+}
+
+function showPanel() {
+    showInline("fabric-controls")
+    showInline("fabric-hide")
+    hideEle("fabric-show")
+    raiseCanvas()
+}
+
+function hidePanel() {
+    hideEle("fabric-controls")
+    hideEle("fabric-hide")
+    showInline("fabric-show")
+    lowerCanvas()
+}
+
+
+function slide_drawing_init(paneId) {
+    const overpane = document.getElementById(paneId)
+    overpane.innerHTML = `
+        <div id="fabric-panel">
+            <div class="fabric-subpanel" id="fabric-show-hide">
+                <div class="fabric-button" id="fabric-show"> </div>
+                <div class="fabric-button" id="fabric-hide"> </div>
+            </div>
+            <div class="fabric-subpanel" id="fabric-controls">
+                <div class="fabric-button" id="fabric-sel"> </div>
+                <div class="fabric-button" id="fabric-red"> </div>
+                <div class="fabric-button" id="fabric-blue"> </div>
+                <div class="fabric-button" id="fabric-black"> </div>
+                <div class="fabric-button" id="fabric-delete"> </div>
+                <div class="fabric-button" id="fabric-clear"> </div>
+            </div>
+        </div>
+        <div id="fabric-canvas-div">
+            <canvas id="fabric-canvas" width="2000" height="800"></canvas>
+        </div>
+    `;
+
+    const canvas = new fabric.Canvas("fabric-canvas");
+
+    hidePanel()
+
+    document.getElementById("fabric-show").onclick = showPanel
+    document.getElementById("fabric-hide").onclick = function() {
+        hidePanel()
+        canvas.clear()
+    }
+
+    document.getElementById("fabric-sel").onclick = function () {
+        canvas.isDrawingMode = false
+    }
+
+    new Array("red", "blue", "black").forEach(col => {
+        document.getElementById("fabric-" + col).onclick = function () {
+            canvas.isDrawingMode = true
+            canvas.freeDrawingBrush.color = col
+            canvas.freeDrawingBrush.width = 3
+        }
+    })
+
+    document.getElementById("fabric-delete").onclick = function () {
+        canvas.getActiveObjects().forEach(o => {
+            canvas.remove(o)
+        });
+    }
+
+    document.getElementById("fabric-clear").onclick = function () {
+        canvas.clear()
+    }
+}
diff --git a/templates/impress.cs/slides.html b/templates/impress.cs/slides.html
new file mode 100644
index 0000000000000000000000000000000000000000..0d9033a7a1be60eb83a92f8fadc6acff7a5b0095
--- /dev/null
+++ b/templates/impress.cs/slides.html
@@ -0,0 +1,100 @@
+---
+---
+
+{% if jekyll %}
+  {% assign headers   = site.headers %}
+  {% assign slides    = site.slides %}
+  {% assign style_css = 'style.css' %}
+{% else %}
+  {% capture style_css %}{{ name }}.css{% endcapture %}
+{% endif %}
+
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=1024">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <title>{{ headers['title'] }} | by {{ headers['author'] }}</title>
+
+    <meta name="generator" content="{{ headers['generator'] }}">
+    <meta name="author"    content="{{ headers['author'] }}">
+
+    <link rel="stylesheet" href="css/style.css" />
+    <link rel="stylesheet" href="{{ headers['css'] }}">
+
+    <link rel="apple-touch-icon" href="apple-touch-icon.png" >
+</head>
+
+<body class="impress-not-supported" onload="initialisePage()">
+
+<nav>
+    <div id="start-presentation" onclick="startPresentation()">
+        Start presentation
+    </div>
+    <div class='toc'>
+        <h3 id="toc-header">Table of Contents</h3>
+        <ul id="toc-list"> </ul>
+    </div>
+</nav>
+<div id="drawing-pane"> </div>
+<div id="impress">
+
+<!-- note: assumes no header (breaking slides w/ SLIDE directive) -->
+{% for slide in slides %}
+    <div class='step {{ slide.classes }}' {{ slide.data_attributes }}>
+      {{ slide.content }}
+    </div>
+{% endfor %}
+
+
+</div>
+
+<script src="js/impress.js"></script>
+
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+  tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}
+});
+</script>
+<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+
+<script src="js/fabric.js"></script>
+
+<script src="js/slide-drawing.js"></script>
+<script>
+    function startPresentation() {
+        impress().init()
+        slide_drawing_init("drawing-pane")
+        document.getElementsByTagName("nav")[0].style.display = "none"
+    }
+
+    function createTOC() {
+        const h3s = document.getElementsByTagName("h3")
+        const toc = document.getElementById("toc-list")
+
+        for (heading of h3s) {
+            const id = heading.id
+
+            if (id == "toc-header")
+                continue
+
+            const li = document.createElement("li")
+            const a = document.createElement("a")
+            const text = document.createTextNode(heading.textContent)
+
+            a.href = "#" + id
+            a.appendChild(text)
+            li.appendChild(a)
+            toc.appendChild(li)
+        }
+    }
+
+    function initialisePage() {
+        console.log("Creating TOC")
+        createTOC()
+    }
+</script>
+
+</body>
+</html>