diff --git a/build.js b/build.js index cbe29ff0..05d96bd6 100644 --- a/build.js +++ b/build.js @@ -147,6 +147,7 @@ var filesToInclude = [ ifSpecifiedInclude('interaction', 'src/mixins/canvas_events.mixin.js'), 'src/mixins/canvas_animation.mixin.js', + 'src/mixins/canvas_dataurl_exporter.mixin.js', ifSpecifiedInclude('serialization', 'src/mixins/canvas_serialization.mixin.js'), ifSpecifiedInclude('gestures', 'src/mixins/canvas_gestures.mixin.js'), @@ -185,6 +186,7 @@ var filesToInclude = [ ifSpecifiedInclude('image_filters', 'src/filters/tint_filter.class.js'), ifSpecifiedInclude('text', 'src/shapes/text.class.js'), + ifSpecifiedInclude('cufon', 'src/shapes/text.cufon.js'), ifSpecifiedInclude('node', 'src/node.js') ]; diff --git a/dist/all.js b/dist/all.js index 6bfdc810..557964ed 100644 --- a/dist/all.js +++ b/dist/all.js @@ -5424,7 +5424,7 @@ fabric.util.string = { this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2); } - for (var i = 0; i < this.colorStops.length; i++) { + for (var i = 0, len = this.colorStops.length; i < len; i++) { var color = this.colorStops[i].color, opacity = this.colorStops[i].opacity, offset = this.colorStops[i].offset; @@ -6412,6 +6412,7 @@ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { extend(fabric.StaticCanvas.prototype, fabric.Observable); extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { @@ -6931,7 +6932,7 @@ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { canvasToDrawOn.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop); } - if (this.controlsAboveOverlay) { + if (this.controlsAboveOverlay && this.interactive) { this.drawControls(canvasToDrawOn); } @@ -6987,167 +6988,6 @@ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { return this; }, - /** - * Draws objects' controls (borders/controls) - * @param {Object} ctx context to render controls on - */ - drawControls: function(ctx) { - var activeGroup = this.getActiveGroup(); - if (activeGroup) { - ctx.save(); - fabric.Group.prototype.transform.call(activeGroup, ctx); - activeGroup.drawBorders(ctx).drawControls(ctx); - ctx.restore(); - } - else { - for (var i = 0, len = this._objects.length; i < len; ++i) { - if (!this._objects[i] || !this._objects[i].active) continue; - - ctx.save(); - fabric.Object.prototype.transform.call(this._objects[i], ctx); - this._objects[i].drawBorders(ctx).drawControls(ctx); - ctx.restore(); - - this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; - } - } - }, - - /** - * Exports canvas element to a dataurl image. - * @param {Object} options - * - * `format` the format of the output image. Either "jpeg" or "png". - * `quality` quality level (0..1) - * `multiplier` multiplier to scale by {Number} - * - * @return {String} - */ - toDataURL: function (options) { - options || (options = { }); - - var format = options.format || 'png', - quality = options.quality || 1, - multiplier = options.multiplier || 1; - - if (multiplier !== 1) { - return this.__toDataURLWithMultiplier(format, quality, multiplier); - } - else { - return this.__toDataURL(format, quality); - } - }, - - /** - * @private - */ - __toDataURL: function(format, quality) { - this.renderAll(true); - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl; - var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) - ? canvasEl.toDataURL('image/' + format, quality) - : canvasEl.toDataURL('image/' + format); - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - return data; - }, - - /** - * @private - */ - __toDataURLWithMultiplier: function(format, quality, multiplier) { - - var origWidth = this.getWidth(), - origHeight = this.getHeight(), - scaledWidth = origWidth * multiplier, - scaledHeight = origHeight * multiplier, - activeObject = this.getActiveObject(), - activeGroup = this.getActiveGroup(), - - ctx = this.contextTop || this.contextContainer; - - this.setWidth(scaledWidth).setHeight(scaledHeight); - ctx.scale(multiplier, multiplier); - - if (activeGroup) { - // not removing group due to complications with restoring it with correct state afterwords - this._tempRemoveBordersControlsFromGroup(activeGroup); - } - else if (activeObject && this.deactivateAll) { - this.deactivateAll(); - } - - // restoring width, height for `renderAll` to draw - // background properly (while context is scaled) - this.width = origWidth; - this.height = origHeight; - - this.renderAll(true); - - var data = this.__toDataURL(format, quality); - - ctx.scale(1 / multiplier, 1 / multiplier); - this.setWidth(origWidth).setHeight(origHeight); - - if (activeGroup) { - this._restoreBordersControlsOnGroup(activeGroup); - } - else if (activeObject && this.setActiveObject) { - this.setActiveObject(activeObject); - } - - this.contextTop && this.clearContext(this.contextTop); - this.renderAll(); - - return data; - }, - - /** - * Exports canvas element to a dataurl image (allowing to change image size via multiplier). - * @deprecated since 1.0.13 - * @param {String} format (png|jpeg) - * @param {Number} multiplier - * @param {Number} quality (0..1) - * @return {String} - */ - toDataURLWithMultiplier: function (format, multiplier, quality) { - return this.toDataURL({ - format: format, - multiplier: multiplier, - quality: quality - }); - }, - - /** - * @private - */ - _tempRemoveBordersControlsFromGroup: function(group) { - group.origHasControls = group.hasControls; - group.origBorderColor = group.borderColor; - - group.hasControls = true; - group.borderColor = 'rgba(0,0,0,0)'; - - group.forEachObject(function(o) { - o.origBorderColor = o.borderColor; - o.borderColor = 'rgba(0,0,0,0)'; - }); - }, - - /** - * @private - */ - _restoreBordersControlsOnGroup: function(group) { - group.hideControls = group.origHideControls; - group.borderColor = group.origBorderColor; - - group.forEachObject(function(o) { - o.borderColor = o.origBorderColor; - delete o.origBorderColor; - }); - }, - /** * Returns coordinates of a center of canvas. * Returned value is an object with top and left properties @@ -7488,21 +7328,6 @@ fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { removeListener(fabric.window, 'resize', this._onResize); } return this; - }, - - /** - * @private - * @param {HTMLImageElement} imgEl - */ - _resizeImageToFit: function (imgEl) { - - var imageWidth = imgEl.width || imgEl.offsetWidth, - widthScaleFactor = this.getWidth() / imageWidth; - - // scale image down so that it has original dimensions when printed in large resolution - if (imageWidth) { - imgEl.width = imageWidth * widthScaleFactor; - } } }); @@ -9228,6 +9053,32 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.fire('selection:cleared'); } return this; + }, + + /** + * Draws objects' controls (borders/controls) + * @param {Object} ctx context to render controls on + */ + drawControls: function(ctx) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + ctx.save(); + fabric.Group.prototype.transform.call(activeGroup, ctx); + activeGroup.drawBorders(ctx).drawControls(ctx); + ctx.restore(); + } + else { + for (var i = 0, len = this._objects.length; i < len; ++i) { + if (!this._objects[i] || !this._objects[i].active) continue; + + ctx.save(); + fabric.Object.prototype.transform.call(this._objects[i], ctx); + this._objects[i].drawBorders(ctx).drawControls(ctx); + ctx.restore(); + + this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i]; + } + } } }; @@ -9806,6 +9657,145 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }); +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Exports canvas element to a dataurl image. + * @param {Object} options + * + * `format` the format of the output image. Either "jpeg" or "png". + * `quality` quality level (0..1) + * `multiplier` multiplier to scale by {Number} + * + * @return {String} + */ + toDataURL: function (options) { + options || (options = { }); + + var format = options.format || 'png', + quality = options.quality || 1, + multiplier = options.multiplier || 1; + + if (multiplier !== 1) { + return this.__toDataURLWithMultiplier(format, quality, multiplier); + } + else { + return this.__toDataURL(format, quality); + } + }, + + /** + * @private + */ + __toDataURL: function(format, quality) { + this.renderAll(true); + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl; + var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) + ? canvasEl.toDataURL('image/' + format, quality) + : canvasEl.toDataURL('image/' + format); + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + return data; + }, + + /** + * @private + */ + __toDataURLWithMultiplier: function(format, quality, multiplier) { + + var origWidth = this.getWidth(), + origHeight = this.getHeight(), + scaledWidth = origWidth * multiplier, + scaledHeight = origHeight * multiplier, + activeObject = this.getActiveObject(), + activeGroup = this.getActiveGroup(), + + ctx = this.contextTop || this.contextContainer; + + this.setWidth(scaledWidth).setHeight(scaledHeight); + ctx.scale(multiplier, multiplier); + + if (activeGroup) { + // not removing group due to complications with restoring it with correct state afterwords + this._tempRemoveBordersControlsFromGroup(activeGroup); + } + else if (activeObject && this.deactivateAll) { + this.deactivateAll(); + } + + // restoring width, height for `renderAll` to draw + // background properly (while context is scaled) + this.width = origWidth; + this.height = origHeight; + + this.renderAll(true); + + var data = this.__toDataURL(format, quality); + + ctx.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + + if (activeGroup) { + this._restoreBordersControlsOnGroup(activeGroup); + } + else if (activeObject && this.setActiveObject) { + this.setActiveObject(activeObject); + } + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + + return data; + }, + + /** + * Exports canvas element to a dataurl image (allowing to change image size via multiplier). + * @deprecated since 1.0.13 + * @param {String} format (png|jpeg) + * @param {Number} multiplier + * @param {Number} quality (0..1) + * @return {String} + */ + toDataURLWithMultiplier: function (format, multiplier, quality) { + return this.toDataURL({ + format: format, + multiplier: multiplier, + quality: quality + }); + }, + + /** + * @private + */ + _tempRemoveBordersControlsFromGroup: function(group) { + group.origHasControls = group.hasControls; + group.origBorderColor = group.borderColor; + + group.hasControls = true; + group.borderColor = 'rgba(0,0,0,0)'; + + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = 'rgba(0,0,0,0)'; + }); + }, + + /** + * @private + */ + _restoreBordersControlsOnGroup: function(group) { + group.hideControls = group.origHideControls; + group.borderColor = group.origBorderColor; + + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + } +}); + + fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { /** @@ -16833,57 +16823,6 @@ fabric.Image.filters.Tint.fromObject = function(object) { } }, - /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ - _renderViaCufon: function(ctx) { - var o = Cufon.textOptions || (Cufon.textOptions = { }); - - // export options to be used by cufon.js - o.left = this.left; - o.top = this.top; - o.context = ctx; - o.color = this.fill; - - var el = this._initDummyElementForCufon(); - - // set "cursor" to top/left corner - this.transform(ctx); - - // draw text - Cufon.replaceElement(el, { - engine: 'canvas', - separate: 'none', - fontFamily: this.fontFamily, - fontWeight: this.fontWeight, - textDecoration: this.textDecoration, - textShadow: this.textShadow, - textAlign: this.textAlign, - fontStyle: this.fontStyle, - lineHeight: this.lineHeight, - stroke: this.stroke, - strokeWidth: this.strokeWidth, - backgroundColor: this.backgroundColor, - textBackgroundColor: this.textBackgroundColor - }); - - // update width, height - this.width = o.width; - this.height = o.height; - - this._totalLineHeight = o.totalLineHeight; - this._fontAscent = o.fontAscent; - this._boundaries = o.boundaries; - this._shadowOffsets = o.shadowOffsets; - this._shadows = o.shadows || [ ]; - - el = null; - - // need to set coords _after_ the width/height was retreived from Cufon - this.setCoords(); - }, - /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on @@ -17289,32 +17228,6 @@ fabric.Image.filters.Tint.fromObject = function(object) { ].join(' '); }, - /** - * @private - */ - _initDummyElementForCufon: function() { - var el = fabric.document.createElement('pre'), - container = fabric.document.createElement('div'); - - // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent - container.appendChild(el); - - if (typeof G_vmlCanvasManager === 'undefined') { - el.innerHTML = this.text; - } - else { - // IE 7 & 8 drop newlines and white space on text nodes - // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html - // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp - el.innerText = this.text.replace(/\r?\n/gi, '\r'); - } - - el.style.fontSize = this.fontSize + 'px'; - el.style.letterSpacing = 'normal'; - - return el; - }, - /** * Renders text instance on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on @@ -17627,6 +17540,87 @@ fabric.Image.filters.Tint.fromObject = function(object) { })(typeof exports !== 'undefined' ? exports : this); +/** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ +fabric.util.object.extend(fabric.Text.prototype, { + _renderViaCufon: function(ctx) { + + var o = Cufon.textOptions || (Cufon.textOptions = { }); + + // export options to be used by cufon.js + o.left = this.left; + o.top = this.top; + o.context = ctx; + o.color = this.fill; + + var el = this._initDummyElementForCufon(); + + // set "cursor" to top/left corner + this.transform(ctx); + + // draw text + Cufon.replaceElement(el, { + engine: 'canvas', + separate: 'none', + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, + textDecoration: this.textDecoration, + textShadow: this.textShadow, + textAlign: this.textAlign, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + stroke: this.stroke, + strokeWidth: this.strokeWidth, + backgroundColor: this.backgroundColor, + textBackgroundColor: this.textBackgroundColor + }); + + // update width, height + this.width = o.width; + this.height = o.height; + + this._totalLineHeight = o.totalLineHeight; + this._fontAscent = o.fontAscent; + this._boundaries = o.boundaries; + this._shadowOffsets = o.shadowOffsets; + this._shadows = o.shadows || [ ]; + + el = null; + + // need to set coords _after_ the width/height was retreived from Cufon + this.setCoords(); + }, + + /** + * @private + */ + _initDummyElementForCufon: function() { + var el = fabric.document.createElement('pre'), + container = fabric.document.createElement('div'); + + // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent + container.appendChild(el); + + if (typeof G_vmlCanvasManager === 'undefined') { + el.innerHTML = this.text; + } + else { + // IE 7 & 8 drop newlines and white space on text nodes + // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html + // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp + el.innerText = this.text.replace(/\r?\n/gi, '\r'); + } + + el.style.fontSize = this.fontSize + 'px'; + el.style.letterSpacing = 'normal'; + + return el; + } +}); + + (function() { if (typeof document !== 'undefined' && typeof window !== 'undefined') { diff --git a/dist/all.min.js b/dist/all.min.js index 2c00d0da..cbd78c93 100644 --- a/dist/all.min.js +++ b/dist/all.min.js @@ -1,6 +1,6 @@ /* build: `node build.js modules=ALL exclude=gestures` *//*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.1.21"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom("
"),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined";var Cufon=function(){function r(e){var t=this.face=e.face;this.glyphs=e.glyphs,this.w=e.w,this.baseSize=parseInt(t["units-per-em"],10),this.family=t["font-family"].toLowerCase(),this.weight=t["font-weight"],this.style=t["font-style"]||"normal",this.viewBox=function(){var e=t.bbox.split(/\s+/),n={minX:parseInt(e[0],10),minY:parseInt(e[1],10),maxX:parseInt(e[2],10),maxY:parseInt(e[3],10)};return n.width=n.maxX-n.minX,n.height=n.maxY-n.minY,n.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},n}(),this.ascent=-parseInt(t.ascent,10),this.descent=-parseInt(t.descent,10),this.height=-this.ascent+this.descent}function i(){var e={},t={oblique:"italic",italic:"oblique"};this.add=function(t){(e[t.style]||(e[t.style]={}))[t.weight]=t},this.get=function(n,r){var i=e[n]||e[t[n]]||e.normal||e.italic||e.oblique;if(!i)return null;r={normal:400,bold:700}[r]||parseInt(r,10);if(i[r])return i[r];var s={1:1,99:0}[r%100],o=[],u,a;s===undefined&&(s=r>400),r==500&&(r=400);for(var f in i){f=parseInt(f,10);if(!u||fa)a=f;o.push(f)}return ra&&(r=a),o.sort(function(e,t){return(s?e>r&&t>r?e-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)},toGrayscale:function(){return this.forEachObject(function(e){e.toGrayscale()})}},function(){function n(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e}function r(e,t){return Math.floor(Math.random()*(t-e+1))+e}function s(e){return e*i}function o(e){return e/i}function u(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)}function a(e,t){return parseFloat(Number(e).toFixed(t))}function f(){return!1}function l(e){e||(e={});var t=+(new Date),n=e.duration||500,r=t+n,i,s=e.onChange||function(){},o=e.abort||function(){return!1},u=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},a="startValue"in e?e.startValue:0,f="endValue"in e?e.endValue:100,l=e.byValue||f-a;e.onStart&&e.onStart(),function c(){i=+(new Date);var f=i>r?n:i-t;s(u(f,a,l,n));if(i>r||o()){e.onComplete&&e.onComplete();return}h(c)}()}function p(e){return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(e))]}function d(e,t,n){if(e){var r=fabric.util.createImage();r.onload=function(){t&&t.call(n,r),r=r.onload=null},r.src=e}else t&&t.call(n,e)}function v(e,t){function n(){++i===s&&t&&t(r)}var r=[],i=0,s=e.length;e.forEach(function(e,t){if(!e.type)return;var i=fabric.util.getKlass(e.type);i.async?i.fromObject(e,function(e,i){i||(r[t]=e),n()}):(r[t]=i.fromObject(e),n())})}function m(e,t,n){var r;return e.length>1?r=new fabric.PathGroup(e,t):r=e[0],typeof n!="undefined"&&r.setSourcePath(n),r}function g(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;rr)r+=u[p++%h],r>l&&(r=l),n[d?"lineTo":"moveTo"](r,0),d=!d;n.restore()}function b(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e}function w(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")}function E(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}}function S(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()}function x(e,t){var n=[[e[0],e[2],e[4]],[e[1],e[3],e[5]],[0,0,1]],r=[[t[0],t[2],t[4]],[t[1],t[3],t[5]],[0,0,1]],i=[];for(var s=0;s<3;s++){i[s]=[];for(var o=0;o<3;o++){var u=0;for(var a=0;a<3;a++)u+=n[s][a]*r[a][o];i[s][o]=u}}return[i[0][0],i[1][0],i[0][1],i[1][1],i[0][2],i[1][2]]}function T(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]}function N(e,t,n,r){var i=r[0],s=r[1],o=r[2],u=r[3],a=r[4],f=r[5],l=r[6],c=O(f,l,i,s,u,a,o,t,n);for(var h=0;h =r&&(r=e[n][t]);else while(n--)e[n]>=r&&(r=e[n]);return r}function r(e,t){if(!e||e.length===0)return undefined;var n=e.length-1,r=t?e[n][t]:e[n];if(t)while(n--)e[n][t]>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n