• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Label factory

    TODO - document purpose and description

  • §

    Imports

    import { constructors } from '../core/library.js';
    
    import { doCreate, mergeOver, λnull, Ωempty } from '../helper/utilities.js';
    
    import { makeState } from '../untracked-factory/state.js';
    import { makeTextStyle } from '../untracked-factory/text-style.js';
    import { currentGroup } from '../factory/canvas.js';
    
    import { releaseCell, requestCell } from '../untracked-factory/cell-fragment.js';
    
    import baseMix from '../mixin/base.js';
    import entityMix from '../mixin/entity.js';
    import textMix from '../mixin/text.js';
  • §

    Shared constants

    import { _abs, _ceil, _isFinite, ALPHABETIC, BLACK, BOTTOM, CENTER, DESTINATION_OUT, END, ENTITY, HANGING, IDEOGRAPHIC, LEFT, LTR, MIDDLE, MOUSE, PARTICLE, RIGHT, ROUND, SOURCE_OVER, START, T_LABEL, TOP, ZERO_STR } from '../helper/shared-vars.js';
  • §

    Local constants (none defined)

  • §

    Label constructor

    const Label = function (items = Ωempty) {
    
        this.entityInit(items);
        return this;
    };
  • §

    Label prototype

    const P = Label.prototype = doCreate();
    P.type = T_LABEL;
    P.lib = ENTITY;
    P.isArtefact = true;
    P.isAsset = false;
  • §

    Mixins

    baseMix(P);
    entityMix(P);
    textMix(P);
  • §

    Label attributes

    const defaultAttributes = {
  • §

    text - string.

        text: ZERO_STR,
    
        showBoundingBox: false,
        boundingBoxStyle: BLACK,
        boundingBoxLineWidth: 1,
        boundingBoxLineDash: null,
        boundingBoxLineDashOffset: 0,
    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

    No additional packet management functionality required

  • §

    Clone management

    No additional clone functionality required

  • §

    Kill management

    No additional kill functionality required

  • §

    Get, Set, deltaSet

    const G = P.getters,
        S = P.setters,
        D = P.deltaSetters;
  • §

    Note that dimensions (width, height) cannot be set on labels as the entity’s dimensional values will depend entirely on the font, text and scale attributes

    G.width = function () {
    
        return this.currentDimensions[0];
    };
    S.width = λnull;
    D.width = λnull;
    
    G.height = function () {
    
        return this.currentDimensions[1];
    };
    S.height = λnull;
    D.height = λnull;
    
    G.dimensions = function () {
    
        return [...this.currentDimensions];
    };
    S.dimensions = λnull;
    D.dimensions = λnull;
  • §

    Prototype functions

  • §

    entityInit - overwrites the mixin/entity.js function

    P.entityInit = function (items = Ωempty) {
    
        this.modifyConstructorInputForAnchorButton(items);
    
        this.makeName(items.name);
        this.register();
        this.initializePositions();
    
        this.state = makeState(Ωempty);
    
        this.defaultTextStyle = makeTextStyle({
            name: `${this.name}_default-textstyle`,
            isDefaultTextStyle: true,
        });
    
        this.filters = [];
        this.currentFilters = [];
        this.dirtyFilters = false;
        this.dirtyFiltersCache = false;
        this.dirtyImageSubscribers = false;
    
        this.accessibleTextHold = null;
        this.accessibleTextHoldAttached = null;
    
        this.set(this.defs);
    
        if (!items.group) items.group = currentGroup;
    
        this.onEnter = λnull;
        this.onLeave = λnull;
        this.onDown = λnull;
        this.onUp = λnull;
    
        this.currentFontIsLoaded = false;
        this.updateUsingFontParts = false;
        this.updateUsingFontString = false;
        this.usingViewportFontSizing = true;
        this.letterSpaceValue = 0;
        this.wordSpaceValue = 0;
    
        this.alphabeticBaseline = 0;
        this.hangingBaseline = 0;
        this.ideographicBaseline = 0;
        this.fontVerticalOffset = 0;
    
        this.delta = {};
    
        this.set(items);
    
        this.midInitActions(items);
    
        this.dirtyFont = true;
        this.currentFontIsLoaded = false;
    };
  • §

    measureFont - gather font metadata (uses getFontMetadata from text mixin)

    P.measureFont = function () {
    
        const { defaultTextStyle, currentScale, dimensions } = this;
        const { fontFamily, fontSizeValue, letterSpaceValue, wordSpaceValue } = defaultTextStyle;
    
        defaultTextStyle.letterSpacing = `${letterSpaceValue * currentScale}px`;
        defaultTextStyle.wordSpacing = `${wordSpaceValue * currentScale}px`;
    
        const mycell = requestCell();
        const engine = mycell.engine;
    
        engine.font = defaultTextStyle.canvasFont;
        engine.fontKerning = defaultTextStyle.fontKerning;
        engine.fontStretch = defaultTextStyle.fontStretch;
        engine.fontVariantCaps = defaultTextStyle.fontVariantCaps;
        engine.textRendering = defaultTextStyle.textRendering;
        engine.letterSpacing = defaultTextStyle.letterSpacing;
        engine.wordSpacing = defaultTextStyle.wordSpacing;
        engine.direction = defaultTextStyle.direction;
        engine.textAlign = LEFT;
        engine.textBaseline = TOP;
    
        const metrics = engine.measureText(this.text);
    
        releaseCell(mycell);
    
        const { actualBoundingBoxLeft, actualBoundingBoxRight } = metrics;
    
        const meta = this.getFontMetadata(fontFamily);
    
        const ratio = fontSizeValue / 100;
  • §

    If spacing is active, compute width manually (fallback)

        let width = _ceil(_abs(actualBoundingBoxLeft) + _abs(actualBoundingBoxRight));
    
        if ((letterSpaceValue !== 0 || wordSpaceValue !== 0) && this.text) {
    
            const cell2 = requestCell();
            const ctx = cell2.engine;
    
            ctx.font = defaultTextStyle.canvasFont;
            ctx.textAlign = LEFT;
            ctx.textBaseline = TOP;
    
            let total = 0;
    
            const t = this.text;
    
            for (let i = 0; i < t.length; i++) {
    
                const ch = t[i];
                total += ctx.measureText(ch).width;
                if (i < t.length - 1) total += (letterSpaceValue + (ch === ' ' ? wordSpaceValue : 0)) * currentScale;
            }
            width = _ceil(total);
            releaseCell(cell2);
        }
    
        if (dimensions) {
    
            dimensions[0] = width;
            dimensions[1] = meta.height * ratio * currentScale;
        }
    
        const offset = meta.verticalOffset * ratio;
    
        this.alphabeticBaseline = ((meta.alphabeticBaseline * ratio) + offset) * currentScale;
        this.hangingBaseline = ((meta.hangingBaseline * ratio) + offset) * currentScale;
        this.ideographicBaseline = ((meta.ideographicBaseline * ratio) + offset) * currentScale;
        this.fontVerticalOffset = offset;
    
        this.dirtyPathObject = true;
        this.dirtyDimensions = true;
    };
  • §

    Clean functions

    cleanPathObject - calculate the Label entity’s Path2D object

    P.cleanPathObject = function () {
    
        this.dirtyPathObject = false;
    
        const p = this.pathObject = new Path2D();
    
        const handle = this.currentHandle,
            dims = this.currentDimensions;
    
        const [x, y] = handle;
        const [w, h] = dims;
    
        p.rect(-x, -y, w, h);
    };
  • §

    cleanDimensions - calculate the entity’s currentDimensions Array

    P.cleanDimensions = function () {
    
        this.dirtyDimensions = false;
    
        const dims = this.dimensions,
            curDims = this.currentDimensions;
    
        const [oldW, oldH] = curDims;
    
        curDims[0] = dims[0];
        curDims[1] = dims[1];
    
        this.dirtyStart = true;
        this.dirtyHandle = true;
        this.dirtyOffset = true;
    
        if (oldW !== curDims[0] || oldH !== curDims[1]) this.dirtyPositionSubscribers = true;
    
        if (this.mimicked && this.mimicked.length) this.dirtyMimicDimensions = true;
    
        this.dirtyFilterIdentifier = true;
    };
    
    P.cleanHandle = function () {
    
        this.dirtyHandle = false;
    
        const { handle, currentHandle, currentDimensions, mimicked, defaultTextStyle, alphabeticBaseline, hangingBaseline, ideographicBaseline } = this;
    
        const [hx, hy] = handle;
        const [dx, dy] = currentDimensions;
        const direction = defaultTextStyle.direction || LTR;
  • §

    horizontal

        if (hx.toFixed) currentHandle[0] = hx;
        else if (hx === LEFT) currentHandle[0] = 0;
        else if (hx === RIGHT) currentHandle[0] = dx;
        else if (hx === CENTER) currentHandle[0] = dx / 2;
        else if (hx === START) currentHandle[0] = (direction === LTR) ? 0 : dx;
        else if (hx === END) currentHandle[0] = (direction === LTR) ? dx : 0;
        else if (!_isFinite(parseFloat(hx))) currentHandle[0] = 0;
        else currentHandle[0] = (parseFloat(hx) / 100) * dx;
  • §

    vertical

        if (hy.toFixed) currentHandle[1] = hy;
        else if (hy === TOP) currentHandle[1] = 0;
        else if (hy === BOTTOM) currentHandle[1] = dy;
        else if (hy === CENTER) currentHandle[1] = dy / 2;
        else if (hy === MIDDLE) currentHandle[1] = dy / 2;
        else if (hy === HANGING) currentHandle[1] = (_isFinite(hangingBaseline)) ? hangingBaseline : 0;
        else if (hy === ALPHABETIC) currentHandle[1] = (_isFinite(alphabeticBaseline)) ? alphabeticBaseline : 0;
        else if (hy === IDEOGRAPHIC) currentHandle[1] = (_isFinite(ideographicBaseline)) ? ideographicBaseline : 0;
        else if (!_isFinite(parseFloat(hy))) currentHandle[1] = 0;
        else currentHandle[1] = (parseFloat(hy) / 100) * dy;
    
        this.dirtyFilterIdentifier = true;
        this.dirtyStampHandlePositions = true;
    
        if (mimicked && mimicked.length) this.dirtyMimicHandle = true;
    };
  • §

    Display cycle functions

    P.prepareStamp = function() {
    
        if (this.dirtyHost) this.dirtyHost = false;
    
        if (this.dirtyScale || this.dirtyDimensions || this.dirtyStart || this.dirtyOffset || this.dirtyHandle) this.dirtyPathObject = true;
    
        if (this.dirtyScale) this.cleanScale();
        if (this.dirtyText) this.updateAccessibleTextHold();
        if (this.dirtyFont) this.cleanFont();
        if (this.dirtyDimensions) this.cleanDimensions();
        if (this.dirtyLock) this.cleanLock();
        if (this.dirtyStart) this.cleanStart();
        if (this.dirtyOffset) this.cleanOffset();
        if (this.dirtyHandle) this.cleanHandle();
        if (this.dirtyRotation) this.cleanRotation();
    
        if (this.isBeingDragged || this.lockTo.includes(MOUSE) || this.lockTo.includes(PARTICLE)) {
    
            this.dirtyStampPositions = true;
            this.dirtyStampHandlePositions = true;
        }
    
        if (this.dirtyStampPositions) this.cleanStampPositions();
        if (this.dirtyStampHandlePositions) this.cleanStampHandlePositions();
        if (this.dirtyPathObject) this.cleanPathObject();
        if (this.dirtyPositionSubscribers) this.updatePositionSubscribers();
    
        this.prepareStampTabsHelper();
    };
  • §
    Stamp methods

    regularStamp - overwrites mixin/entity.js function.

    • If decide to pass host instead of host.engine to method functions for all entitys, then this may be a temporary fix
    P.regularStamp = function () {
    
        const dest = this.currentHost,
            textStyle = this.defaultTextStyle;
    
        if (dest && textStyle) {
    
            const engine = dest.engine;
            const [x, y] = this.currentStampPosition;
  • §

    Get the Cell wrapper to perform required transformations on its <canvas> element’s 2D engine

            dest.rotateDestination(engine, x, y, this);
  • §

    Get the Cell wrapper to update its 2D engine’s attributes to match the entity’s requirements

            if (!this.noCanvasEngineUpdates) {
    
                this.state.set(this.defaultTextStyle);
                dest.setEngine(this);
            }
    
            this.setImageSmoothing(dest.engine);
  • §

    Invoke the appropriate stamping method (below)

            this[textStyle.method](dest);
        }
    };
  • §

    stampPositioningHelper - internal helper function

    P.stampPositioningHelper = function () {
    
        const { currentHandle, currentScale, text, fontVerticalOffset } = this;
        const x = -currentHandle[0],
            y = -currentHandle[1] + fontVerticalOffset * currentScale;
    
        return [text, x, y];
    }
  • §

    underlineEngine - internal helper function

    P.underlineEngine = function (host, pos) {
  • §

    Setup constants

        const {
            currentDimensions,
            currentScale,
            currentStampPosition,
            defaultTextStyle,
            fontVerticalOffset,
        } = this;
    
        const {
            underlineGap,
            underlineOffset,
            underlineStyle,
            underlineWidth,
        } = defaultTextStyle;
    
        const [, x, y] = pos;
        const [localWidth, localHeight] = currentDimensions;
    
        const underlineStartY = y + (underlineOffset * localHeight) - fontVerticalOffset * currentScale;
        const underlineDepth = underlineWidth * currentScale;
  • §

    Setup the cell parts

        const { element, engine } = host;
    
        const mycell = requestCell(element.width, element.height);
    
        const {
            element: underlineElement,
            engine: underlineEngine,
        } = mycell;
    
        mycell.rotateDestination(underlineEngine, ...currentStampPosition, this);
  • §

    Setup the underline context

        underlineEngine.fillStyle = BLACK;
        underlineEngine.strokeStyle = BLACK;
        underlineEngine.font = defaultTextStyle.canvasFont;
        underlineEngine.fontKerning = defaultTextStyle.fontKerning;
        underlineEngine.fontStretch = defaultTextStyle.fontStretch;
        underlineEngine.fontVariantCaps = defaultTextStyle.fontVariantCaps;
        underlineEngine.textRendering = defaultTextStyle.textRendering;
        underlineEngine.letterSpacing = defaultTextStyle.letterSpacing;
        underlineEngine.lineCap = ROUND;
        underlineEngine.lineJoin = ROUND;
        underlineEngine.wordSpacing = defaultTextStyle.wordSpacing;
        underlineEngine.direction = defaultTextStyle.direction;
        underlineEngine.textAlign = LEFT;
        underlineEngine.textBaseline = TOP;
        underlineEngine.lineWidth = (underlineGap * 2) * currentScale;
    
        this.setImageSmoothing(underlineEngine);
  • §

    Underlines can take their own styling, or use the fillStyle set on the Label entity

        const uStyle = this.getStyle(underlineStyle, 'fillStyle', mycell);
  • §

    Generate the underline

        underlineEngine.strokeText(...pos);
        underlineEngine.fillText(...pos);
    
        underlineEngine.globalCompositeOperation = 'source-out';
        underlineEngine.fillStyle = uStyle;
    
        underlineEngine.fillRect(x, underlineStartY, localWidth, underlineDepth);
  • §

    Copy the underline over to the real cell

        engine.save();
        engine.resetTransform();
    
        this.setImageSmoothing(engine);
    
        engine.drawImage(underlineElement, 0, 0);
        engine.restore();
  • §

    Release the temporary cell

        releaseCell(mycell);
    };
  • §

    drawBoundingBox - internal helper function called by method functions

    P.drawBoundingBox = function (host) {
    
        if (this.pathObject) {
    
            const uStroke = this.getStyle(this.boundingBoxStyle, 'fillStyle', host);
            const engine = host.engine;
    
            engine.save();
            engine.strokeStyle = uStroke;
            engine.lineWidth = this.boundingBoxLineWidth;
            engine.setLineDash(this.boundingBoxLineDash || []);
            engine.lineDashOffset = this.boundingBoxLineDashOffset || 0;
            engine.globalCompositeOperation = SOURCE_OVER;
            engine.globalAlpha = 1;
            engine.shadowOffsetX = 0;
            engine.shadowOffsetY = 0;
            engine.shadowBlur = 0;
    
            this.setImageSmoothing(engine);
    
            engine.stroke(this.pathObject);
            engine.restore();
        }
    };
  • §

    draw - stroke the entity outline with the entity’s strokeStyle color, gradient or pattern - including shadow

    P.draw = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
    
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.strokeText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    fill - fill the entity with the entity’s fillStyle color, gradient or pattern - including shadow

    P.fill = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.fillText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    drawAndFill - stamp the entity stroke, then fill, then remove shadow and repeat

    P.drawAndFill = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.strokeText(...pos);
            engine.fillText(...pos);
            this.currentHost.clearShadow();
            engine.strokeText(...pos);
            engine.fillText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    drawAndFill - stamp the entity fill, then stroke, then remove shadow and repeat

    P.fillAndDraw = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.fillText(...pos);
            engine.strokeText(...pos);
            this.currentHost.clearShadow();
            engine.fillText(...pos);
            engine.strokeText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    drawThenFill - stroke the entity’s outline, then fill it (shadow applied twice)

    P.drawThenFill = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.strokeText(...pos);
            engine.fillText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    fillThenDraw - fill the entity’s outline, then stroke it (shadow applied twice)

    P.fillThenDraw = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const pos = this.stampPositioningHelper();
    
            if (this.defaultTextStyle && this.defaultTextStyle.includeUnderline) this.underlineEngine(host, pos);
    
            engine.fillText(...pos);
            engine.strokeText(...pos);
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    clip - restrict drawing activities to the entity’s enclosed area

    P.clip = function (host) {
    
        const engine = host.engine;
        engine.clip(this.pathObject, this.winding);
     };
  • §

    clear - remove everything that would have been covered if the entity had performed fill (including shadow)

    P.clear = function (host) {
    
        if (this.currentFontIsLoaded) {
    
            const engine = host.engine;
            const gco = engine.globalCompositeOperation;
            const pos = this.stampPositioningHelper();
    
            engine.globalCompositeOperation = DESTINATION_OUT;
            engine.fillText(...pos);
            engine.globalCompositeOperation = gco;
    
            if (this.showBoundingBox) this.drawBoundingBox(host);
        }
    };
  • §

    none - perform all the calculations required, but don’t perform the final stamping

    P.none = λnull;
  • §

    Factory

    scrawl.makeLabel({
    
        name: 'mylabel-fill',
    
    }).clone({
    
        name: 'mylabel-draw',
    });
    
    export const makeLabel = function (items) {
    
        if (!items) return false;
        return new Label(items);
    };
    
    constructors.Label = Label;