EffEPi / fabric.curvedText

Allows you to create curved text - extension to fabric.js
43 stars 41 forks source link

Rendering issue #9

Closed fahadnabbasi closed 10 years ago

fahadnabbasi commented 10 years ago

Hi,

I noticed an issue in this great tool and don't know what to do, any help would be appreciated

The _render is method is called twice and hence the response is very slow in IE10/11 but not on all systems.

How can we improve the performance of it? It is very slow on IE10 and 11 for most of the machines on some it works fine. However, this one works fine http://jsfiddle.net/NHs8t/ on these browsers but don't know what the difference is

I would really appreciate your help..

Fahad

EffEPi commented 10 years ago

Can you please create a jfiddle that shows the problem that you are having ?

fahadnabbasi commented 10 years ago

Here is the fiddle.

http://jsfiddle.net/fahadnabbasi/1z13Lv0a/

If you look in console, I have added some debugging statements so that you know _render is called more then once for a single curve text and in IE10 and IE11 64 bit machines, it is working very very slow.

fahadnabbasi commented 10 years ago

I have also added some more effects, they are not perfect but still they can give you a good start to modify the parameters to get good results and enhance the library for more text effects instead of just curved text.

These are the effects myEditor.currentFontShape = 'STRAIGHT'; //No effect, normal text myEditor.currentFontShape = 'curved'; myEditor.currentFontShape = 'arc'; myEditor.currentFontShape = 'smallToLarge'; myEditor.currentFontShape = 'largeToSmallTop'; myEditor.currentFontShape = 'largeToSmallBottom'; myEditor.currentFontShape = 'bulge';

This is how you call it var textSample = new fabric.CurvedText($('#my_text').val(), { left: canvas.width / 2, top: canvas.height / 2, fontFamily: myEditor.currentFont, fontSize: 20, angle: 0, range: myEditor.currentFontRange, smallFont: myEditor.currentFontSmall, largeFont: myEditor.currentFontLarge, radius: myEditor.currentFontRadius, spacing: myEditor.currentFontSpacing, effect: myEditor.currentFontShape, fill: myEditor.currentFontColor, scaleX: 1, scaleY: 1, fontWeight: '', textAlign: 'center', originX: 'left',

            hasRotatingPoint: true,
            centerTransform: true
        });

and here is the complete code for curvedText.js

(function(global) {

"use strict";

var fabric = global.fabric || (global.fabric = {}),
extend = fabric.util.object.extend,
    clone = fabric.util.object.clone;

if (fabric.CurvedText) {
    fabric.warn('fabric.CurvedText is already defined');
    return;
}
var stateProperties = fabric.Text.prototype.stateProperties.concat();
stateProperties.push(
        'radius',
        'spacing',
        'effect',
        'range',
        'reverse',
        'largeFont',
        'smallFont'
);
var _dimensionAffectingProps = fabric.Text.prototype._dimensionAffectingProps;
_dimensionAffectingProps['radius']          = true;
_dimensionAffectingProps['effect']          = true;
_dimensionAffectingProps['spacing']         = true;
_dimensionAffectingProps['width']           = true;
_dimensionAffectingProps['height']          = true;
_dimensionAffectingProps['range']           = true;
_dimensionAffectingProps['reverse']         = true;
_dimensionAffectingProps['fontSize']        = true;
_dimensionAffectingProps['fill']            = true;
_dimensionAffectingProps['shadow']          = true;
_dimensionAffectingProps['largeFont']           = true;
_dimensionAffectingProps['smallFont']           = true;

var delegatedProperties = fabric.Group.prototype.delegatedProperties;
delegatedProperties['backgroundColor']      = true;
delegatedProperties['textBackgroundColor']  = true;
delegatedProperties['textDecoration']       = true;
delegatedProperties['stroke']               = true;
delegatedProperties['strokeWidth']          = true;
delegatedProperties['shadow']           = true;

/**
 * Group class
 * @class fabric.CurvedText
 * @extends fabric.Text
 * @mixes fabric.Collection
 */
fabric.CurvedText = fabric.util.createClass(fabric.Text, fabric.Collection, /** @lends fabric.CurvedText.prototype */ {
    /**
     * Type of an object
     * @type String
     * @default
     */
    //type: 'curvedText',
    type: 'curvedText',
    /**
     * The radius of the curved Text
     * @type Number
     * @default 50
     */
    radius: 180,

    range: 5,

    smallFont: 20,

    largeFont: 50,

    effect:'curved',

    /**
     * Spacing between the letters
     * @type fabricNumber
     * @default 20
     */
    spacing: 15,

// letters: null,

    /**
     * Reversing the radius (position of the original point)
     * @type Boolead
     * @default false
     */
    reverse: false,

    /**
     * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})
     * as well as for history (undo/redo) purposes
     * @type Array
     */
    stateProperties:      stateProperties,

    /**
     * Properties that are delegated to group objects when reading/writing
     * @param {Object} delegatedProperties
    */
    delegatedProperties: delegatedProperties,

    /**
     * Properties which when set cause object to change dimensions
     * @type Object
     * @private
    */
    _dimensionAffectingProps: _dimensionAffectingProps,

    /**
     * Added complexity
     */
    complexity: function() {
        this.callSuper('complexity');
    },

    initialize: function(text, options) {
        options || (options = {});
        this.letters = new fabric.Group([], {selectable: false, padding: 0});
        this.__skipDimension = true;
        this.setOptions(options);
        this.__skipDimension = false;
        this.callSuper('initialize', options);
        this.setText(text);
    },
    setText: function(text) {
        console.log('called method');
        if (this.letters) {
            while (text.length !== 0 && this.letters.size() >= text.length) {
                this.letters.remove(this.letters.item(this.letters.size() - 1));
            }
            for (var i = 0; i < text.length; i++) {
                //I need to pass the options from the main options
                if (this.letters.item(i) === undefined) {
                    this.letters.add(new fabric.Text(text[i]));

                } else {
                    this.letters.item(i).setText(text[i]);
                }
                console.log('index='+i);

            }

        }
        //this.opts.text = text;
        //this._setFontStyles();
        //this._render();
        this.set('text', text);
        console.log('after called supper');
    },
    setStyle: function(activeObj, style, value) {

        this.callSuper('setStyle', activeObj, style, value);

    },
    _render: function(ctx) {
        if (this.letters) {
            var curAngle = 0,
                angleRadians = 0,
                align = 0;
            // Text align
            /*if(this.width!=0)
             {
             this.set('width',this.width);
             this.set('height',this.height);
             }  */
            if (this.get('textAlign') === 'center' || this.get('textAlign') === 'justify') {
                align = (this.spacing / 2) * (this.text.length - 1);
            } else if (this.get('textAlign') === 'right') {
                align = (this.spacing) * (this.text.length - 1);
            }

            var width=0;
            for (var i = 0, len = this.text.length; i < len; i++) {
                // Find coords of each letters (radians : angle*(Math.PI / 180)
                console.log('called letter ->'+i)
                var multiplier = this.reverse ? 1 : -1;
                curAngle = (multiplier * -i * parseInt(this.spacing, 10)) + (multiplier * align);
                angleRadians = curAngle * (Math.PI / 180);

                for (var key in this.delegatedProperties) {
                    this.letters.item(i).set(key, this.get(key));
                }
                this.letters.item(i).set('left', (width));
                this.letters.item(i).set('top', (0));
                this.letters.item(i).setAngle(0);
                this.letters.item(i).set('padding', 0);
                if(this.effect=='curved')
                {
                    this.letters.item(i).set('top', (multiplier * Math.cos(angleRadians) * this.radius));
                    this.letters.item(i).set('left', (multiplier * -Math.sin(angleRadians) * this.radius));

                    this.letters.item(i).setAngle(curAngle);
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);

                }
                else if(this.effect=='arc')
                {
                    this.letters.item(i).set('top', (multiplier * Math.cos(angleRadians) * this.radius));
                    this.letters.item(i).set('left', (multiplier * -Math.sin(angleRadians) * this.radius));

                    //this.letters.item(i).setAngle(curAngle);
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);
                }
                else if(this.effect=='STRAIGHT')
                {
                    //var newfont=(i*5)+15;
                    //this.letters.item(i).set('fontSize',(newfont));
                    this.letters.item(i).set('left', (width));
                    this.letters.item(i).set('top', (0));
                    this.letters.item(i).setAngle(0);
                    width+=this.letters.item(i).get('width');
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set({
                        borderColor: 'red',
                        cornerColor: 'green',
                        cornerSize: 6,
                        transparentCorners: false
                    });
                    this.letters.item(i).set('selectable',false);
                }
                else if(this.effect=='smallToLarge')
                {
                    var small = parseInt(this.smallFont);
                    var large = parseInt(this.largeFont);
                    //var small = 20;
                    //var large = 75;
                    var difference = large-small;
                    var center = Math.ceil(this.text.length/2);
                    var step = difference / (this.text.length );
                    var newfont=small + (i*step);

                    //var newfont=(i*this.smallFont)+15;

                    this.letters.item(i).set('fontSize',(newfont));

                    this.letters.item(i).set('left', (width));
                    width+=this.letters.item(i).get('width');
                    //this.letters.item(i).set('padding', 0);
                    /*this.letters.item(i).set({
                        borderColor: 'red',
                        cornerColor: 'green',
                        cornerSize: 6,
                        transparentCorners: false
                    });*/
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);
                    this.letters.item(i).set('top', -1* this.letters.item(i).get('fontSize')+i);
                    //this.letters.width=width;
                    //this.letters.height=this.letters.item(i).get('height');

                }
                else if(this.effect=='largeToSmallTop')
                {
                    var small = parseInt(this.largeFont);
                    var large = parseInt(this.smallFont);
                    //var small = 20;
                    //var large = 75;
                    var difference = large-small;
                    var center = Math.ceil(this.text.length/2);
                    var step = difference / (this.text.length );
                    var newfont=small + (i*step);
                    //var newfont=((this.text.length-i)*this.smallFont)+12;
                    this.letters.item(i).set('fontSize',(newfont));
                    this.letters.item(i).set('left', (width));
                    width+=this.letters.item(i).get('width');
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set({
                        borderColor: 'red',
                        cornerColor: 'green',
                        cornerSize: 6,
                        transparentCorners: false
                    });
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);
                    this.letters.item(i).top =-1* this.letters.item(i).get('fontSize')+(i/this.text.length);

                }
                else if(this.effect=='largeToSmallBottom')
                {
                    var small = parseInt(this.largeFont);
                    var large = parseInt(this.smallFont);
                    //var small = 20;
                    //var large = 75;
                    var difference = large-small;
                    var center = Math.ceil(this.text.length/2);
                    var step = difference / (this.text.length );
                    var newfont=small + (i*step);
                    //var newfont=((this.text.length-i)*this.smallFont)+12;
                    this.letters.item(i).set('fontSize',(newfont));
                    this.letters.item(i).set('left', (width));
                    width+=this.letters.item(i).get('width');
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set({
                        borderColor: 'red',
                        cornerColor: 'green',
                        cornerSize: 6,
                        transparentCorners: false
                    });
                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);
                    //this.letters.item(i).top =-1* this.letters.item(i).get('fontSize')+newfont-((this.text.length-i))-((this.text.length-i));
                    this.letters.item(i).top =-1* this.letters.item(i).get('fontSize')-i;
                }
                else if(this.effect=='bulge')
                {

                    var small = parseInt(this.smallFont);
                    var large = parseInt(this.largeFont);
                    //var small = 20;
                    //var large = 75;
                    var difference = large-small;
                    var center = Math.ceil(this.text.length/2);
                    var step = difference / (this.text.length - center);
                    if(i<center)
                        var newfont=small + (i*step);
                    else
                        var newfont=large - ((i-center+1)*step);
                    this.letters.item(i).set('fontSize',(newfont));

                    this.letters.item(i).set('left', (width));
                    width+=this.letters.item(i).get('width');

                    this.letters.item(i).set('padding', 0);
                    this.letters.item(i).set('selectable',false);

                    this.letters.item(i).set('top',-1*this.letters.item(i).get('height')/2);

                }

            }

            // Update group coords
            if(this.effect=='curved')
            {
                for (var i = 0, len = this.text.length; i < len; i++) {
                    this.letters.item(i).set('top',(this.letters.item(i).get('top')+15-i))

                }
            }
            this.letters._calcBounds();
            this.letters._updateObjectsCoords();
            this.letters.saveCoords();

// this.letters.render(ctx);

            this.width = this.letters.width;
            this.height = this.letters.height;
            console.log('End rendering')

        }
    },

    render: function(ctx, noTransform) {
        // do not render if object is not visible

        if (!this.visible) return;
        if (!this.letters) return;

        ctx.save();
        this.transform(ctx);

        var groupScaleFactor = Math.max(this.scaleX, this.scaleY);

        this.clipTo && fabric.util.clipContext(this, ctx);

        //The array is now sorted in order of highest first, so start from end.
        for (var i = 0, len = this.letters.size(); i < len; i++) {

            var object = this.letters.item(i),
                originalScaleFactor = object.borderScaleFactor,
                originalHasRotatingPoint = object.hasRotatingPoint;

            // do not render if object is not visible
            if (!object.visible) continue;

            object.borderScaleFactor = groupScaleFactor;
            object.hasRotatingPoint = false;

            object.render(ctx);

            object.borderScaleFactor = originalScaleFactor;
            object.hasRotatingPoint = originalHasRotatingPoint;
        }
        this.clipTo && ctx.restore();

        if (!noTransform && this.active) {
            this.drawBorders(ctx);
            this.drawControls(ctx);
        }
        ctx.restore();
        this.setCoords();

        /*console.log('called2');
        console.log(this.letters._objects)
        var height=this.height;
            $.each(this.letters._objects, function(i,item){

                console.log("top="+item.top+" height="+item.height);
                item.height=height;item.top=0;

            }); */
    },
    /**
     * @private
     */
    _set: function(key, value) {

        this.callSuper('_set', key, value);
        if (this.letters) {
        //Properties are delegated with the object is rendered

// if (key in this.delegatedProperties) { // var i = this.letters.size(); // while (i--) { // this.letters.item(i).set(key, value); // } // } if (key in this._dimensionAffectingProps) { this._initDimensions(); this.setCoords(); } }

    },
    toObject: function(propertiesToInclude) {
        var object = extend(this.callSuper('toObject', propertiesToInclude), {
            radius: this.radius,
            spacing: this.spacing,
            reverse: this.reverse,
            effect: this.effect,
            range: this.range,
            smallFont: this.smallFont,
            largeFont: this.largeFont
            //              letters: this.letters   //No need to pass this, the letters are recreated on the fly every time when initiated
        });
        if (!this.includeDefaultValues) {
            this._removeDefaultValues(object);
        }
        return object;
    },
    /**
     * Returns string represenation of a group
     * @return {String}
     */
    toString: function() {
        return '#<fabric.CurvedText (' + this.complexity() + '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '", "radius": "' + this.radius + '", "spacing": "' + this.spacing + '", "reverse": "' + this.reverse + '" }>';
    },
    /* _TO_SVG_START_ */
    /**
     * Returns svg representation of an instance
     * @param {Function} [reviver] Method for further parsing of svg representation.
     * @return {String} svg representation of an instance
     */
    toSVG: function(reviver) {
        var markup = [
            '<g ',
            'transform="', this.getSvgTransform(),
            '">'
        ];
        if (this.letters) {
            for (var i = 0, len = this.letters.size(); i < len; i++) {
                markup.push(this.letters.item(i).toSVG(reviver));
            }
        }
        markup.push('</g>');
        return reviver ? reviver(markup.join('')) : markup.join('');
    }
    /* _TO_SVG_END_ */
});

/**
 * Returns {@link fabric.CurvedText} instance from an object representation
 * @static
 * @memberOf fabric.CurvedText
 * @param {Object} object Object to create a group from
 * @param {Object} [options] Options object
 * @return {fabric.CurvedText} An instance of fabric.CurvedText
 */
fabric.CurvedText.fromObject = function(object) {
    return new fabric.CurvedText(object.text, clone(object));
};

fabric.util.createAccessors(fabric.CurvedText);

/**
 * Indicates that instances of this type are async
 * @static
 * @memberOf fabric.CurvedText
 * @type Boolean
 * @default
 */
fabric.CurvedText.async = false;

})(typeof exports !== 'undefined' ? exports : this);

fahadnabbasi commented 10 years ago

I have found the issue and it is at _render function where you read all the delegated properties per character. If we stick to basic properties, it becomes a lot faster

EffEPi commented 10 years ago

Seems like that the _render it is called twice, but it is coming from fabric itself. @kangax (https://github.com/kangax/fabric.js) might have a better insight on this issue.