Text.js

/**
 * Model for drawing text.<br/>
 * This element extends {@link Linen.Model} and inherits all of it's methods.
 */
Linen.Text = class extends Linen.Model {

    /**
     * 
     * @param {Linen} Linen - The instance of Linen.
     */
    constructor(Linen) {
        super(Linen);
        this.wrap = false;
        this.text = '';
    }

    /**
     * set whether to wrap text or not.
     * @param {boolean} bool
     * @returns {self} self
     */
    setWrap(bool = true) {
        return this.setProp('wrap', bool);
    }

    /**
     * Set the text that will be rendered.
     * @param {string} text - the text to be rendered
     * @return {self} self
     */
    setText(text) {
        return this.setProp('text', text);
    }

    /**
     * Set the font family that the text will be rendered in.
     * @param {string} fontFamily - font family that the text will be rendered in.
     * @return {self} self
     */
    setFontFamily(fontFamily) {
        return this.setSetting('fontFamily', fontFamily);
    }

    /**
     * Set the font size that the text will be rendered in.
     * @param {*} fontSize - font size in pixels, points (pt), or inches (in)
     * @return {self} self
     */
    setFontSize(fontSize) {
        return this.setSetting('fontSize', this.translateToPx(fontSize));
    }

    /**
     * Set the font to be Bold
     * @param {boolean} bool
     * @returns {self} self
     */
    setBold(bool = true) {
        return this.setSetting('bold', bool);
    }

    /**
     * Set the font to be Italic
     * @param {boolean} bool
     * @returns {self} self
     */
    setItalic(bool = true) {
        return this.setSetting('italic', bool);
    }
    
    setTextLineHeight(textLineHeight){
        return this.setSetting('textLineHeight', textLineHeight);
    }

    /**
     * Render the Text on the Linen.Canvas object
     * Splits the text into lines and sends it to private methods for wrapping.
     * @access private
     */
    render() {
        super.render();
        var lines = this.text.split("\n");
        var x = this.x();
        var y = this.y();

        this.setFont();
        if (this.wrap) {
            lines = this.wrapLines(lines);
        } else if (this.width() > 0) {
            this.fitTextWidth(lines);
        }

        if (this.height() > 0) {
            this.fitTextHeight(lines);
        }

        lines.map(line => {
            this.writeLine(line, x, y);
            y += this.textHeight(line);
        });
        
        return this;
    }

    /* Private Functions */

    /**
     * Private method to fit lines within width dimension.
     * @access private
     * @param {array} lines - array of lines
     * @return {boolean} will return true when complete.
     */
    fitTextWidth(lines) {
        var fit = true;
        if (this.settings.fontSize <= 1) {
            return true;
        }
        var w = this.width();
        lines.map(line => {
            if (this.textWidth(line) > w) {
                fit = false;
            }
        });

        if (!fit) {
            this.settings.fontSize -= 1;
            this.setFont();
            this.fitTextWidth(lines);
        } else {
            return true;
        }
    }

    /**
     * Private method to fit lines within height dimension.
     * @access private
     * @param {array} lines - array of lines
     * @return {boolean} will return true when complete.
     */
    fitTextHeight(lines) {
        var fit = true;
        if (this.settings.fontSize <= 1) {
            return true;
        }

        var h = 0;
        lines.map(line => {
            h += this.textHeight(line);
        });
        if (h > this.height()) {
            fit = false;
        }

        if (!fit) {
            this.settings.fontSize -= 1;
            this.setFont();
            this.fitTextHeight(lines);
        } else {
            return true;
        }
    }

    /**
     * Private method to get the calculated width of a given string.
     * @access private
     * @param {string} text - The text to calculate the width of
     * @return {number} px
     */
    textWidth(text) {
        return this.context().measureText(text).width;
    }

    /**
     * Private method to get the calculated height of a given string.
     * @access private
     * @param {string} text - The text to calculate the height of
     * @return {number} px
     */
    textHeight(text) {
        return this.settings.fontSize * this.context().textLineHeight;
    }

    /**
     * Private method to write a single line of text after breaks and wraps are calculated.
     * Context should be set before running this method.
     * @access private
     * @param {string} line - The text to render
     * @param {number} x - The x position of the textbox
     * @param {number} y - The y position of the textbox
     */
    writeLine(line, x, y) {
        var w = super.width();
        var h = super.height();
        var tw = this.textWidth(line);

        switch (this.context().textAlign) {
            case 'center':
                x += Math.round(w / 2);
                break;
            case 'right':
                x += w;
                break;
        }

        if (this.fill) {
            this.context().fillText(line, x, y);
        }
        if (this.stroke) {
            this.context().strokeText(line, x, y);
        }
    }

    /**
     * Private method used to calculate wrapping.
     * @access private
     * @param {array} lines - array of lines.
     * @return {array} lines after processing.
     */
    wrapLines(lines) {
        var newLines = [];
        const w = this.width();
        lines.map(line => {
            var newLine = '';
            line.split(' ').map(word => {
                if (this.textWidth(newLine + word + ' ') > w) {
                    newLines.push(newLine);
                    newLine = word + ' ';
                } else {
                    newLine += word + ' ';
                }
            });
            newLines.push(newLine);
        });
        return newLines;
    }
};