text.js

import { vectors }  from "./vector.js"

/** Text for use in gameify. Usually you'll access these through the gameify object.
 * @example // Use text via gameify
 * // This is the most common way
 * import { gameify } from "./gameify/gameify.js"
 * let myText = new gameify.Text("Hello, World!", 0, 0);
 * @example // Import just text
 * import { text } from "./gameify/text.js"
 * let myText = new text.Text("Hello, World!", 0, 0);
 * @global
 */
export let text = {

    /** Text style data
     * @alias gameify.TextStyle
     * @constructor
     * @arg {String} [font='sans-serif'] - The text font
     * @arg {Number} [size=24] - The text size, in pixels
     * @arg {String} [fillColor='#000f'] - The fill color
     * @arg {String} [strokeColor='#000f'] - The stroke color. (Stroke paint is disabled by default - use TextStyle.setPaint to enable stroke)
     * @arg {Number} [strokeWidth=1] - The stroke width
     */
    TextStyle: function (font = 'sans-serif', size = 24, fillColor = '#000f', strokeColor = '#000f', strokeWidth = 1) {

        /** The text font
         * @type {String}
         */
        this.font = font;

        /** The text size, in pixels
         * @type {Number}
         */
        this.size = size;

        /** The line height, relative to the font size
         * @type {Number}
         * @default 1
         */
        this.lineHeight = 1;

        /** The opacity of the text
         * @type {Number}
         * @default 1
         */
        this.opacity = 1;

        this.fill = {
            paint: true,
            color: fillColor
        }
        this.stroke = {
            paint: false,
            color: strokeColor,
            width: strokeWidth
        }

        /** Set the paint options
         * @param {String} [fill] - "fill", "stroke", "none" or "both"
         * @param {String} [fillColor] - The text fill color
         * @param {String} [strokeColor] - The text stroke color
         * @param {String} [strokeWidth] - The text stroke width
         */
        this.setPaint = (fill, fillColor, strokeColor, strokeWidth) => {
            if (fill === 'fill') {
                this.fill.paint   = true;
                this.stroke.paint = false;
            } else if (fill === 'stroke') {
                this.fill.paint   = false;
                this.stroke.paint = true;
            } else if (fill === 'both') {
                this.fill.paint   = true;
                this.stroke.paint = true;
            } else if (fill === 'none') {
                this.fill.paint   = false;
                this.stroke.paint = false;
            }

            if (fillColor)   this.fill.color   = fillColor;
            if (strokeColor) this.stroke.color = strokeColor;
            if (strokeWidth) this.stroke.width = strokeWidth;
        }

        /** Get the fill paint settings
         * @returns {Object} {paint: Boolean, color: String}
         */
        this.getFill = () => {
            return {
                paint: this.fill.paint,
                color: this.fill.color
            };
        }
        /** Get the stroke paint settings
         * @returns {Object} {paint: Boolean, color: String}
         */
        this.getStroke = () => {
            return {
                paint: this.stroke.paint,
                color: this.stroke.color
            };
        }

        /** Apply these styles to a canvas context.
         * @param {CanvasRenderingContext2D} context
         * @private
         */
        this.applyTo = (context) => {
            context.font = `${this.size}px ${this.font}`;
            context.fillStyle = this.fill.color;

            context.lineWidth = this.stroke.width;
            context.strokeStyle = this.stroke.color;
        }
    },

    /** A drawable string of text
     * @alias gameify.Text
     * @constructor
     * @example // A text label
     * let myText = new gameify.Text("Hello, World", 0, 0);
     * @arg {String} text - The text string to draw
     * @arg {Number} [x=0] - x coordinate of the top left of the text
     * @arg {Number} [y=0] - y coordinate of the top left of the text
     * @arg {gameify.TextStyle} [style] - The text style
     * @arg {Number} [size=48] - The text size, in pixels
    */
    Text: function (string, x = 0, y = 0, style, size = 48) {

        /** The string of text to draw
         * @type {String}
        */
        this.string = string;

        /** The position of the text top left corner of the text
         * @type {gameify.Vector2d}
         */
        this.position = new vectors.Vector2d(x, y);

        /** The text style
         * @type {gameify.TextStyle}
         */
        this.style = style || new text.TextStyle();

        /** The text size, in pixels
         * @type {Number}
         */
        this.size = size;

        /** Draw the Text */
        this.draw = () => {
            if (!this.context) {
                throw new Error("You need to add this text to a screen before you can draw it.");
            }

            if (typeof this.size !== 'number') {
                throw new Error("Text size must be a number");
            }
            
            this.context.font = `${this.size}px sans-serif`;

            this.context.textBaseline = 'top';
            this.style.applyTo(this.context);

            const split = String(this.string).split('\n');
            for (const lineNum in split) {
                const line = split[lineNum];
                const addedPos = (this.style?.size || this.size) * lineNum * (this.style?.lineHeight || 1);

                this.context.globalAlpha = this.style?.opacity || 1;
                if (this.style.fill.paint)   this.context.fillText  (line, this.position.x, this.position.y + addedPos);
                if (this.style.stroke.paint) this.context.strokeText(line, this.position.x, this.position.y + addedPos);
                this.context.globalAlpha = 1;
            }
        }

        /** The Canvas context to draw to
         * @private
         */
        this.context = null;

        /** The parent screen (not used directly)
         * @private
         */
        this.parent = null;

        /** Get the screen this sprite draws to
         * @returns {gameify.Screen}
         */
        this.getParent = () => {
            return this.parent;
        }

        /** Set the Canvas context to draw to. This should be called whenever a sprite is added to a Screen
         * @private
         */
        this.setContext = (context, parent) => {
            this.context = context;
            this.parent = parent;
        }
    }
}