text: (function(root, factory) {
      if (typeof define === 'function' && define.amd) {
          define(['jquery'], factory);
      } else {
          factory(root.jQuery);
      }
  })(this, function($) {
      var has_VML, has_canvas, create_canvas_for, add_shape_to, clear_canvas, shape_from_area,
          canvas_style, hex_to_decimal, css3color, is_image_loaded, options_from_area;
  
      has_canvas = !!document.createElement('canvas').getContext;
  
  
      // VML: more complex
      has_VML = (function() {
          var a = document.createElement('div');
          a.innerHTML = '<v:shape id="vml_flag1" adj="1" />';
          var b = a.firstChild;
          b.style.behavior = "url(#default#VML)";
          return b ? typeof b.adj == "object": true;
      })();
  
      if(!(has_canvas || has_VML)) {
          $.fn.maphilight = function() { return this; };
          return;
      }
  
      if(has_canvas) {
          hex_to_decimal = function(hex) {
              return Math.max(0, Math.min(parseInt(hex, 16), 255));
          };
          css3color = function(color, opacity) {
              return 'rgba('+hex_to_decimal(color.substr(0,2))+','+hex_to_decimal(color.substr(2,2))+','+hex_to_decimal(color.substr(4,2))+','+opacity+')';
          };
          create_canvas_for = function(img) {
              var c = $('<canvas style="width:'+$(img).width()+'px;height:'+$(img).height()+'px;"></canvas>').get(0);
              c.getContext("2d").clearRect(0, 0, $(img).width(), $(img).height());
              return c;
          };
          var draw_shape = function(context, shape, coords, x_shift, y_shift) {
              x_shift = x_shift || 0;
              y_shift = y_shift || 0;
  
              context.beginPath();
              if(shape == 'rect') {
                  // x, y, width, height
                  context.rect(coords[0] + x_shift, coords[1] + y_shift, coords[2] - coords[0], coords[3] - coords[1]);
              } else if(shape == 'poly') {
                  context.moveTo(coords[0] + x_shift, coords[1] + y_shift);
                  for(i=2; i < coords.length; i+=2) {
                      context.lineTo(coords[i] + x_shift, coords[i+1] + y_shift);
                  }
              } else if(shape == 'circ') {
                  // x, y, radius, startAngle, endAngle, anticlockwise
                  context.arc(coords[0] + x_shift, coords[1] + y_shift, coords[2], 0, Math.PI * 2, false);
              }
              context.closePath();
          };
          add_shape_to = function(canvas, shape, coords, options, name) {
              var i, context = canvas.getContext('2d');
  
              // Because I don't want to worry about setting things back to a base state
  
              // Shadow has to happen first, since it's on the bottom, and it does some clip /
              // fill operations which would interfere with what comes next.
              if(options.shadow) {
                  context.save();
                  if(options.shadowPosition == "inside") {
                      // Cause the following stroke to only apply to the inside of the path
                      draw_shape(context, shape, coords);
                      context.clip();
                  }
  
                  // Redraw the shape shifted off the canvas massively so we can cast a shadow
                  // onto the canvas without having to worry about the stroke or fill (which
                  // cannot have 0 opacity or width, since they're what cast the shadow).
                  var x_shift = canvas.width * 100;
                  var y_shift = canvas.height * 100;
                  draw_shape(context, shape, coords, x_shift, y_shift);
  
                  context.shadowOffsetX = options.shadowX - x_shift;
                  context.shadowOffsetY = options.shadowY - y_shift;
                  context.shadowBlur = options.shadowRadius;
                  context.shadowColor = css3color(options.shadowColor, options.shadowOpacity);
  
                  // Now, work out where to cast the shadow from! It looks better if it's cast
                  // from a fill when it's an outside shadow or a stroke when it's an interior
                  // shadow. Allow the user to override this if they need to.
                  var shadowFrom = options.shadowFrom;
                  if (!shadowFrom) {
                      if (options.shadowPosition == 'outside') {
                          shadowFrom = 'fill';
                      } else {
                          shadowFrom = 'stroke';
                      }
                  }
                  if (shadowFrom == 'stroke') {
                      context.strokeStyle = "rgba(0,0,0,1)";
                      context.stroke();
                  } else if (shadowFrom == 'fill') {
                      context.fillStyle = "rgba(0,0,0,1)";
                      context.fill();
                  }
                  context.restore();
  
                  // and now we clean up
                  if(options.shadowPosition == "outside") {
                      context.save();
                      // Clear out the center
                      draw_shape(context, shape, coords);
                      context.globalCompositeOperation = "destination-out";
                      context.fillStyle = "rgba(0,0,0,1);";
                      context.fill();
                      context.restore();
                  }
              }
  
              context.save();
  
              draw_shape(context, shape, coords);
  
              // fill has to come after shadow, otherwise the shadow will be drawn over the fill,
              // which mostly looks weird when the shadow has a high opacity
              if(options.fill) {
                  context.fillStyle = css3color(options.fillColor, options.fillOpacity);
                  context.fill();
              }
              // Likewise, stroke has to come at the very end, or it'll wind up under bits of the
              // shadow or the shadow-background if it's present.
              if(options.stroke) {
                  context.strokeStyle = css3color(options.strokeColor, options.strokeOpacity);
                  context.lineWidth = options.strokeWidth;
                  context.stroke();
              }
  
              context.restore();
  
              if(options.fade) {
                  $(canvas).css('opacity', 0).animate({opacity: 1}, 100);
              }
          };
          clear_canvas = function(canvas) {
              canvas.getContext('2d').clearRect(0, 0, canvas.width,canvas.height);
          };
      } else {   // ie executes this code
          create_canvas_for = function(img) {
              return $('<var style="zoom:1;overflow:hidden;display:block;width:'+img.width+'px;height:'+img.height+'px;"></var>').get(0);
          };
          add_shape_to = function(canvas, shape, coords, options, name) {
              var fill, stroke, opacity, e;
              for (var i in coords) { coords[i] = parseInt(coords[i], 10); }
              fill = '<v:fill color="#'+options.fillColor+'" opacity="'+(options.fill ? options.fillOpacity : 0)+'" />';
              stroke = (options.stroke ? 'strokeweight="'+options.strokeWidth+'" stroked="t" strokecolor="#'+options.strokeColor+'"' : 'stroked="f"');
              opacity = '<v:stroke opacity="'+options.strokeOpacity+'"/>';
              if(shape == 'rect') {
                  e = $('<v:rect name="'+name+'" filled="t" '+stroke+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+coords[0]+'px;top:'+coords[1]+'px;width:'+(coords[2] - coords[0])+'px;height:'+(coords[3] - coords[1])+'px;"></v:rect>');
              } else if(shape == 'poly') {
                  e = $('<v:shape name="'+name+'" filled="t" '+stroke+' coordorigin="0,0" coordsize="'+canvas.width+','+canvas.height+'" path="m '+coords[0]+','+coords[1]+' l '+coords.join(',')+' x e" style="zoom:1;margin:0;padding:0;display:block;position:absolute;top:0px;left:0px;width:'+canvas.width+'px;height:'+canvas.height+'px;"></v:shape>');
              } else if(shape == 'circ') {
                  e = $('<v:oval name="'+name+'" filled="t" '+stroke+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+(coords[0] - coords[2])+'px;top:'+(coords[1] - coords[2])+'px;width:'+(coords[2]*2)+'px;height:'+(coords[2]*2)+'px;"></v:oval>');
              }
              e.get(0).innerHTML = fill+opacity;
              $(canvas).append(e);
          };
          clear_canvas = function(canvas) {
              // jquery1.8 + ie7
              var $html = $("<div>" + canvas.innerHTML + "</div>");
              $html.children('[name=highlighted]').remove();
              canvas.innerHTML = $html.html();
          };
      }
  
      shape_from_area = function(area) {
          var i, coords = area.getAttribute('coords').split(',');
          for (i=0; i < coords.length; i++) { coords[i] = parseFloat(coords[i]); }
          return [area.getAttribute('shape').toLowerCase().substr(0,4), coords];
      };
  
      options_from_area = function(area, options) {
          var $area = $(area);
          return $.extend({}, options, $.metadata ? $area.metadata() : false, $area.data('maphilight'));
      };
  
      is_image_loaded = function(img) {
          if(!img.complete) { return false; } // IE
          if(typeof img.naturalWidth != "undefined" && img.naturalWidth === 0) { return false; } // Others
          return true;
      };
  
      canvas_style = {
          position: 'absolute',
          left: 0,
          top: 0,
          padding: 0,
          border: 0
      };
  
      var ie_hax_done = false;
      $.fn.maphilight = function(opts) {
          opts = $.extend({}, $.fn.maphilight.defaults, opts);
  
          if(!has_canvas && !ie_hax_done) {
              $(window).ready(function() {
                  document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
                  var style = document.createStyleSheet();
                  var shapes = ['shape','rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group','textbox'];
                  $.each(shapes,
                      function() {
                          style.addRule('v\\:' + this, "behavior: url(#default#VML); antialias:true");
                      }
                  );
              });
              ie_hax_done = true;
          }
  
          return this.each(function() {
              var img, wrap, options, map, canvas, canvas_always, highlighted_shape, usemap;
              img = $(this);
              $(img).attr('data-wfstyle',$(img).attr('style') || '');
  
              if(!is_image_loaded(this)) {
                  // If the image isn't fully loaded, this won't work right.  Try again later.
                  return window.setTimeout(function() {
                      img.maphilight(opts);
                  }, 200);
              }
  
              options = $.extend({}, opts, $.metadata ? img.metadata() : false, img.data('maphilight'));
  
              // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr.
              // So use raw getAttribute instead.
              usemap = img.get(0).getAttribute('usemap');
  
              if (!usemap) {
                  return;
              }
  
              map = $('map[name="'+usemap.substr(1)+'"]');
  
              if(!(img.is('img,input[type="image"]') && usemap && map.length > 0)) {
                  return;
              }
  
              if(img.hasClass('maphilighted')) {
                  // We're redrawing an old map, probably to pick up changes to the options.
                  // Just clear out all the old stuff.
                  var wrapper = img.parent();
                  img.insertBefore(wrapper);
                  wrapper.remove();
                  $(map).unbind('.maphilight');
              }
              wrap = $('<div class="maphl"></div>').css({
                  display:'block',
                  backgroundImage:'url("'+this.src+'")',
                  backgroundSize:'contain',
                  position: img.css('position') === 'static' ? 'relative' : img.css('position'),
                  padding:0,
                  width:this.width,
                  height:this.height
              });
              img.css({
                  margin: 0
              })
  
              wrap.attr('style', (wrap.attr('style') + img.attr('data-wfstyle')))
  
              if(options.wrapClass) {
                  if(options.wrapClass === true) {
                      wrap.addClass($(this).attr('class'));
                  } else {
                      wrap.addClass(options.wrapClass);
                  }
              }
              // Firefox has a bug that prevents tabbing into the image map if
              // we set opacity of the image to 0, but very nearly 0 works!
              img.before(wrap).css('opacity', 0.0000000001).css(canvas_style).remove();
              if(has_VML) { img.css('filter', 'Alpha(opacity=0)'); }
              wrap.append(img);
  
              canvas = create_canvas_for(this);
              $(canvas).css(canvas_style);
              canvas.height = this.height;
              canvas.width = this.width;
  
              $(map).bind('alwaysOn.maphilight', function() {
                  // Check for areas with alwaysOn set. These are added to a *second* canvas,
                  // which will get around flickering during fading.
                  if(canvas_always) {
                      clear_canvas(canvas_always);
                  }
                  if(!has_canvas) {
                      $(canvas).empty();
                  }
                  $(map).find('area[coords]').each(function() {
                      var shape, area_options;
                      area_options = options_from_area(this, options);
                      if(area_options.alwaysOn) {
                          if(!canvas_always && has_canvas) {
                              canvas_always = create_canvas_for(img[0]);
                              $(canvas_always).css(canvas_style);
                              canvas_always.width = img[0].width;
                              canvas_always.height = img[0].height;
                              img.before(canvas_always);
                          }
                          area_options.fade = area_options.alwaysOnFade; // alwaysOn shouldn't fade in initially
                          shape = shape_from_area(this);
                          if (has_canvas) {
                              add_shape_to(canvas_always, shape[0], shape[1], area_options, "");
                          } else {
                              add_shape_to(canvas, shape[0], shape[1], area_options, "");
                          }
                      }
                  });
              }).trigger('alwaysOn.maphilight')
                  .bind('mouseover.maphilight focusin.maphilight', function(e) {
                      var shape, area_options, area = e.target;
                      area_options = options_from_area(area, options);
                      if(!area_options.neverOn && !area_options.alwaysOn) {
                          shape = shape_from_area(area);
                          add_shape_to(canvas, shape[0], shape[1], area_options, "highlighted");
                          if(area_options.groupBy) {
                              var areas;
                              // two ways groupBy might work; attribute and selector
                              if(/^[a-zA-Z][\-a-zA-Z]+$/.test(area_options.groupBy)) {
                                  areas = map.find('area['+area_options.groupBy+'="'+$(area).attr(area_options.groupBy)+'"]');
                              } else {
                                  areas = map.find(area_options.groupBy);
                              }
                              var first = area;
                              areas.each(function() {
                                  if(this != first) {
                                      var subarea_options = options_from_area(this, options);
                                      if(!subarea_options.neverOn && !subarea_options.alwaysOn) {
                                          var shape = shape_from_area(this);
                                          add_shape_to(canvas, shape[0], shape[1], subarea_options, "highlighted");
                                      }
                                  }
                              });
                          }
                          // workaround for IE7, IE8 not rendering the final rectangle in a group
                          if(!has_canvas) {
                              $(canvas).append('<v:rect></v:rect>');
                          }
                      }
                  }).bind('mouseout.maphilight focusout.maphilight', function(e) { clear_canvas(canvas); });
  
              img.before(canvas); // if we put this after, the mouseover events wouldn't fire.
  
              img.addClass('maphilighted');
          });
      };
      $.fn.maphilight.defaults = {
          fill: true,
          fillColor: '000000',
          fillOpacity: 0.2,
          stroke: true,
          strokeColor: 'ff0000',
          strokeOpacity: 1,
          strokeWidth: 1,
          fade: true,
          alwaysOn: false,
          neverOn: false,
          groupBy: false,
          wrapClass: true,
          // plenty of shadow:
          shadow: false,
          shadowX: 0,
          shadowY: 0,
          shadowRadius: 6,
          shadowColor: '000000',
          shadowOpacity: 0.8,
          shadowPosition: 'outside',
          shadowFrom: false
      };
  });
  