• 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
  • §

    Entity mixin

    This mixin builds on the base and position mixins to give Canvas entity objects (Scrawl-canvas Block, Grid, Loom, Label, Picture, Shape, Wheel, etc) the ability to act as artefacts.

    Entitys differ from non-entity artefacts in that they are restricted to Cell wrappers (though no harm should come if they are included in Stack-related Groups).

    • The entity object represents a set of instructions for rendering graphical lines and shapes onto a <canvas> CanvasRenderingContext2D engine, using the Canvas API to do this
    • The engine requirements for the render - fillStyle, font, globalAlpha, globalCompositeOperation, lineCap, lineDash, lineDashOffset, lineJoin, lineWidth, miterLimit, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, strokeStyle, textAlign, textBaseline - are kept in a separate State object. Note that these attributes are initialized/updated directly on the entity object
    • Entitys can act as hotspots within a <canvas> display; we can create hover and click effects for them

    Entitys are, nevertheless, full artefact objects which can interact with other artefacts and the mouse/touch cursor in all the normal ways: positioning (pivot and path) and dimensioning (mimic) functionality; collision detection (including drag-and-drop); etc

  • §

    Imports

    import { addStrings, mergeOver, pushUnique, λnull, Ωempty } from '../helper/utilities.js';
    
    import { makeState } from '../untracked-factory/state.js';
    
    import { releaseCell, requestCell } from '../untracked-factory/cell-fragment.js';
    
    import { filterEngine } from '../helper/filter-engine.js';
    import { importDomImage } from '../asset-management/image-asset.js';
    import { currentGroup } from '../factory/canvas.js';
    
    import positionMix from './position.js';
    import deltaMix from './delta.js';
    import pivotMix from './pivot.js';
    import mimicMix from './mimic.js';
    import pathMix from './path.js';
    import hiddenElementsMix from './hidden-dom-elements.js';
    import anchorMix from './anchor.js';
    import buttonMix from './button.js';
    import filterMix from './filter.js';
  • §

    Shared constants

    import { _floor, _keys, _parse, DESTINATION_OUT, FILL, GOOD_HOST, IMG, MOUSE, NAME, PARTICLE, SOURCE_IN, SOURCE_OVER, STATE_KEYS,  UNDEF, ZERO_STR } from '../helper/shared-vars.js';
  • §

    Local constants

    const NONZERO = 'nonzero';
  • §

    Export function

    export default function (P = Ωempty) {
  • §

    Mixins

        positionMix(P);
        deltaMix(P);
        pivotMix(P);
        mimicMix(P);
        pathMix(P);
        hiddenElementsMix(P);
        anchorMix(P);
        buttonMix(P);
        filterMix(P);
  • §

    Shared attributes

        const defaultAttributes = {
  • §

    method - String value which tells the entity how it will display itself on the canvas. Available options are:

    • draw - stroke the entity outline with the entity’s strokeStyle color, gradient or pattern - including shadow
    • fill - fill the entity with the entity’s fillStyle color, gradient or pattern - including shadow
    • drawAndFill - stroke the entity’s outline, remove shadow, then fill it
    • fillAndDraw - fill the entity’s outline, remove shadow, then stroke it
    • drawThenFill - stroke the entity’s outline, then fill it (shadow applied twice)
    • fillThenDraw - fill the entity’s outline, then stroke it (shadow applied twice)
    • clip - use shape to restrict future drawing area (best used in a separate group)
    • clear - remove everything that would have been covered if the entity had performed fill (including shadow)
    • none - perform all the calculations required, but don’t perform the final stamping
            method: FILL,
  • §

    pathObject - Scrawl-canvas holds details of every type of entity’s outline in a Path2D object - used both for draw/fill operations, and for collision detection work

            pathObject: null,
  • §

    winding - String with value evenodd or nonzero (default)

    • Canvas fill (flood) drawing operations can take into account an entity’s winding choice. Two are available: the non-zero rule; and the even-odd rule
            winding: NONZERO,
  • §

    flipReverse, flipUpend - Boolean flags which determine the orientation of the entity when it stamps itself on the display.

    • a reversed entity is effectively flipped 180° around a vertical line passing through that entity’s rotation-reflection (start) point - a face looking to the right will now look to the left
    • an upended entity is effectively flipped 180° around a horizontal line passing through that entity’s rotation-reflection (start) point - a normal face will now appear upside-down
            flipReverse: false,
            flipUpend: false,
  • §

    scaleOutline - Boolean flag. When set, the entity will increase its lineWidth value proportionate to the entity’s scale value - an entity of scale = 2 will display lines twice the thickness of the same entity at scale = 1

            scaleOutline: true,
  • §

    scaleShadow - Boolean flag. When set, the entity will increase its shadowOffsetX, shadowOffsetY and shadowBlur values proportionate to the entity’s scale value - an entity of scale = 2 will display shadows twice the size of the same entity at scale = 1

            scaleShadow: false,
  • §

    lockFillStyleToEntity, lockStrokeStyleToEntity - Boolean flags.

    • When set, these flags instruct any gradient-type style (Scrawl-canvas Gradient, RadialGradient) to map their start and end coordinates to the entity’s dimensions
    • Default action is for gradient-type styles to map their coordinates to the host Cell’s dimensions
            lockFillStyleToEntity: false,
            lockStrokeStyleToEntity: false,
  • §
    Event listener functions

    Entity objects have the ability to act as ‘hotspots’ within a canvas display, reacting to mouse/pointer movements over the canvas. Four mouse-like events are supported:

    • enter - triggers when the cursor passes over the entity’s outline into its fillable area
    • down - triggers when a user presses their (left) mouse key while inside the entity’s fillable area
    • up - triggers when a user releases their (left) mouse key while inside the entity’s fillable area - this can also be used for capturing mouse click and touch tap actions
    • leave - triggers when the cursor passes over the entity’s outline away from its fillable area

    The events are regular functions.

    • this refers to the entity object, giving the functions access to any attribute and method set on it.
    • They can also use variables defined elsewhere in the script code (though these will need to be rehydrated if the entity is packet stored and resurrected - see Demo Canvas-009 tests for an example of doing this)

    Scrawl-canvas will not automatically trigger these functions:

    • They can be triggered as part of an Animation object’s function checking for hits on a Group of entitys
    • Similar checks can be included in a RenderAnimation hook function
    • Alternatively we can set up an event listener on the DOM <canvas> element, which invokes a call to cascade a mouse/touch move event down to its associated entitys - in this case the entitys will check the event themselves and trigger the appropriate function

    The functions defined in these attributes will be included in entity packet functionality, and can be safely cloned

  • §

    onEnter - define tasks to be performed for enter events

            onEnter: null,
  • §

    onLeave - define tasks to be performed for leave events

            onLeave: null,
  • §

    onDown - define tasks to be performed for down events

            onDown: null,
  • §

    onUp - define tasks to be performed for up events

            onUp: null,
  • §

    onOtherInteraction - define tasks to be performed for other interaction events

            onOtherInteraction: null,
  • §
    State object attributes

    We can treat the following attributes as if they are entity object attributes, though in fact they are stored and managed by State objects

    fillStyle and strokeStyle - color, gradient or pattern used to outline or fill a entity. Can be:

    • CSS format color String - #fff, #ffffff, rgb(255 255 255), rgb(255 255 255 / 1), rgb(255,255,255), rgba(255,255,255,1), white, etc
    • COLORNAME String
    • GRADIENTNAME String
    • RADIALGRADIENTNAME String
    • PATTERNNAME String

    globalAlpha - entity transparency - a value between 0 and 1, where 0 is completely transparent and 1 is completely opaque

    globalCompositeOperation - compositing method for applying the entity to an existing Cell (<canvas>) display. Permitted values include

    • ‘source-over’
    • ‘source-atop’
    • ‘source-in’
    • ‘source-out’
    • ‘destination-over’
    • ‘destination-atop’
    • ‘destination-in’
    • ‘destination-out’
    • ‘lighter’
    • ‘darker’
    • ‘copy’
    • ‘xor’
    • any other permitted value - be aware that different browsers may render these operations in different ways, and some options are not supported by all browsers.

    lineWidth - Number (in pixels)

    lineCap - how the ends of lines will display. Permitted values include:

    • ‘butt’
    • ‘round’
    • ‘square’

    lineJoin - how line joints will display. Permitted values include:

    • ‘miter’
    • ‘round’
    • ‘bevel’

    lineDash - an array of integer Numbers representing line and gap values (in pixels), for example [5,2,2,2] for a long-short dash pattern

    lineDashOffset - distance along the entity’s outline at which to start the line dash. Changing this value can be used to create a ‘marching ants effect

    miterLimit - affecting the ‘pointiness’ of the line join where two lines join at an acute angle

    shadowOffsetX, shadowOffsetY - horizontal and vertical offsets for a entity’s shadow, in Number pixels

    shadowBlur - the blur width for a entity’s shadow, in Number pixels

    shadowColor - the color used for an entity’s shadow effect. Can be:

    • CSS format color String - #fff, #ffffff, rgb(255 255 255), rgb(255 255 255 / 1), rgb(255,255,255), rgba(255,255,255,1), white, etc
    • COLORNAME String

    font, textAlign, textBaseline - the Canvas API standards for using fonts on a canvas are near-useless, and often lead to a sub-par display of text. The Scrawl-canvas Label and EnhancedLabel entitys use these attributes internally, but have their own set of attributes for defining the font styling used by their text.

    filter - the Canvas 2D engine supports the filter attribute on an experimental basis, thus it is not guaranteed to work in all browsers and devices. The filter attribute takes a String value (default: ‘none’) defining one or more filter functions to be applied to the entity as it is stamped on the canvas.

    • Be aware that entitys can also take a filters Array - this represents an array of Scrawl-canvas filters to be applied to the entity (or group or Cell). The two filter systems are completely separate - combine their effects at your own risk!
        };
        P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

        P.packetExclusions = pushUnique(P.packetExclusions, ['state']);
        P.packetFunctions = pushUnique(P.packetFunctions, ['onEnter', 'onLeave', 'onDown', 'onUp', 'onOtherInteraction']);
    
        P.processEntityPacketOut = function (key, value, incs) {
    
            return this.processFactoryPacketOut(key, value, incs);
        };
    
        P.processFactoryPacketOut = function (key, value, incs) {
    
            let result = true;
    
            if(!incs.includes(key) && value === this.defs[key]) result = false;
    
            return result;
        };
    
        P.finalizePacketOut = function (copy, items) {
    
            const stateCopy = _parse(this.state.saveAsPacket(items))[3];
            copy = mergeOver(copy, stateCopy);
    
            copy = this.handlePacketAnchor(copy, items);
    
            return copy;
        };
  • §

    Clone management

        P.postCloneAction = function(clone, items) {
  • §

    Shared state

            if (items.sharedState) clone.state = this.state;
    
            return clone;
        };
  • §

    Kill management

    No additional kill functionality defined here

  • §

    Get, Set, deltaSet

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

    group - returns the entity’s latest Group’s String name, not the Group object itself

        G.group = function () {
    
            return (this.group) ? this.group.name : ZERO_STR;
        };
  • §

    lockStylesToEntity - a pseudo-attribute which will set the lockFillStyleToEntity and lockStrokeStyleToEntity flags to the same Boolean value

        S.lockStylesToEntity = function (item) {
    
            this.lockFillStyleToEntity = item;
            this.lockStrokeStyleToEntity = item;
        };
  • §

    Entity get, set and deltaSet functions need to take into account the entity State object, whose attributes can be retrieved/amended directly on the entity object

        P.get = function (item) {
    
            const getter = this.getters[item];
    
            if (getter) return getter.call(this);
    
            else {
    
                const state = this.state;
                let def = this.defs[item];
                let val;
    
                if (def != null) {
    
                    val = this[item];
                    return (typeof val !== UNDEF) ? val : def;
                }
    
                def = state.defs[item];
    
                if (def != null) {
    
                    val = state[item];
                    return (typeof val !== UNDEF) ? val : def;
                }
                return null;
            }
        };
    
        P.set = function (items = Ωempty) {
    
            const keys = _keys(items),
                len = keys.length;
    
            if (len) {
    
                const setters = this.setters,
                    defs = this.defs,
                    state = this.state;
    
                const stateSetters = (state) ? state.setters : Ωempty;
                const stateDefs = (state) ? state.defs : Ωempty;
    
                let fn, i, key, val;
    
                for (i = 0; i < len; i++) {
    
                    key = keys[i];
                    val = items[key];
    
                    if (key && key !== NAME && val != null) {
    
                        if (!STATE_KEYS.includes(key)) {
    
                            fn = setters[key];
    
                            if (fn) fn.call(this, val);
                            else if (typeof defs[key] !== UNDEF) this[key] = val;
                        }
                        else {
    
                            fn = stateSetters[key];
    
                            if (fn) fn.call(state, val);
                            else if (typeof stateDefs[key] !== UNDEF) state[key] = val;
                        }
                    }
                }
            }
            return this;
        };
    
        P.setDelta = function (items = Ωempty) {
    
            const keys = _keys(items),
                len = keys.length;
    
            if (len) {
    
                const setters = this.deltaSetters,
                    defs = this.defs,
                    state = this.state;
    
                const stateSetters = (state) ? state.deltaSetters : Ωempty;
                const stateDefs = (state) ? state.defs : Ωempty;
    
                let fn, i, key, val;
    
                for (i = 0; i < len; i++) {
    
                    key = keys[i];
                    val = items[key];
    
                    if (key && key !== NAME && val != null) {
    
                        if (!STATE_KEYS.includes(key)) {
    
                            fn = setters[key];
    
                            if (fn) fn.call(this, val);
                            else if (typeof defs[key] !== UNDEF) this[key] = addStrings(this[key], val);
                        }
                        else {
    
                            fn = stateSetters[key];
    
                            if (fn) fn.call(state, val);
                            else if (typeof stateDefs[key] !== UNDEF) state[key] = addStrings(state[key], val);
                        }
                    }
                }
            }
            return this;
        };
  • §

    Prototype functions

  • §

    entityInit - internal function, called by all entity factory constructors

        P.entityInit = function (items = Ωempty) {
    
            this.modifyConstructorInputForAnchorButton(items);
    
            this.makeName(items.name);
            this.register();
            this.initializePositions();
    
            this.state = makeState(Ωempty);
    
            this.filters = [];
            this.currentFilters = [];
            this.dirtyFilters = false;
            this.dirtyFiltersCache = false;
            this.dirtyImageSubscribers = false;
    
            this.stashOutput = false;
            this.stashOutputAsAsset = false;
            this.stashedImageData = null;
            this.stashedImage = null;
    
            this.set(this.defs);
    
            if (!items.group) items.group = currentGroup;
    
            this.onEnter = λnull;
            this.onLeave = λnull;
            this.onDown = λnull;
            this.onUp = λnull;
            this.onOtherInteraction = λnull;
    
            this.set(items);
    
            this.midInitActions(items);
        };
    
        P.midInitActions = λnull;
  • §

    Display cycle functionality

    Entitys - as artefacts - take part in the Display cycle at the compile stage, when Cell wrappers trigger a compile action cascade through their associated Group objects to entity objects included in those groups.

    The main entity compile-related functions are:

    • prepareStamp - a synchronous function called at the start of the compile step where an entity will check its dirty flags and update position, dimensions and other attributes accordingly.
    • stamp - this is where the main drawing activity happens. This function calls one of two other functions: filteredStamp; or regularStamp which in turn rely on the regularStampSynchronousActions function where all the drawing magic happens.

    The stamp functionality can be triggered outside of the Display cycle, if required - for instance when compiling a Cell display setup as a static background layer, which excludes itself from the Display cycle cascade’s clear and compile steps.

  • §
    Step 1: prepare the entity for stamping

    prepareStamp - all entity objects need to check the following dirty flags and take corrective action when a flag is set.

    • The order in which flags get checked is important!
    • As part of the checking process, additional dirty flags may be set early in the function, for processing later on in the function.

    If an entity relies on another artefact as a pivot, mimic or path reference, those artefacts should be processed earlier in the compile cascade. This can be achieved by:

    • Placing the reference artefacts in a different Group, whose order attribute has been set to a lower value than this entity’s Group’s order value
    • If both entity and reference artefact are in the same Group, give this entity an order value higher than its reference.
    • If both entity and the reference artefact are in the same Group, and have the same order value, then make sure that the reference artefact is defined in code before the entity.

    Note that some entity factories need to overwrite this function to meet their particular requirements. Many of the clean functions mentioned below are defined in the position mixin.

        P.prepareStamp = function() {
  • §

    dirtyHost - set by a Cell wrapper on the entitys associated with it (via the Cell’s associated Group objects) whenever the Cell’s dimensions change. The entity sets its own dirtyDimensions flag as a result.

            if (this.dirtyHost) {
    
                this.dirtyHost = false;
                this.dirtyDimensions = true;
            }
  • §

    A number of updates (scale, dimensions, start, offset, handle) require the entity to recalculate its Path2D object - if any of them are set, then the entity sets its own dirtyPathObject flag as a result.

            if (this.dirtyScale || this.dirtyDimensions || this.dirtyStart || this.dirtyOffset || this.dirtyHandle) this.dirtyPathObject = true;
  • §

    dirtyScale - triggers cleanScale function - which in turn sets the dirtyDimensions, dirtyHandle and (if required) dirtyPositionSubscribers, dirtyMimicScale flags on the entity.

            if (this.dirtyScale) this.cleanScale();
  • §

    dirtyDimensions - triggers cleanDimensions function - which in turn sets the dirtyStart, dirtyHandle, dirtyOffset and (if required) dirtyPositionSubscribers, dirtyMimicDimensions flags on the entity.

            if (this.dirtyDimensions) this.cleanDimensions();
  • §

    dirtyLock - triggers cleanLock function - which in turn sets the dirtyStart and dirtyHandle flags on the entity.

            if (this.dirtyLock) this.cleanLock();
  • §

    dirtyStart - triggers cleanStart function - which in turn sets the dirtyStampPositions flag on the entity.

            if (this.dirtyStart) this.cleanStart();
  • §

    dirtyOffset - triggers cleanOffset function - which in turn sets the dirtyStampPositions and (if required) dirtyMimicOffset flags on the entity.

            if (this.dirtyOffset) this.cleanOffset();
  • §

    dirtyHandle - triggers cleanHandle function - which in turn sets the dirtyStampHandlePositions and (if required) dirtyMimicHandle flags on the entity.

            if (this.dirtyHandle) this.cleanHandle();
  • §

    dirtyRotation - triggers cleanRotation function - which in turn sets (if required) dirtyMimicRotation, dirtyPivotRotation, dirtyPositionSubscribers flags on the entity.

            if (this.dirtyRotation) this.cleanRotation();
  • §

    To handle situations where the entity position is currently under the influence of the mouse/touch cursor - where true, entity will set its own dirtyStampPositions, dirtyStampHandlePositions flags

            if (this.isBeingDragged || this.lockTo.includes(MOUSE) || this.lockTo.includes(PARTICLE)) {
    
                this.dirtyStampPositions = true;
                this.dirtyStampHandlePositions = true;
            }
  • §

    Invoke the cleanStampPositions and cleanStampHandlePositions functions, if needed, to update current positional data prior to the stamping operation. Both functions will set the dirtyPositionSubscribers flag if changes to positional values result from the calculations.

            if (this.dirtyStampPositions) this.cleanStampPositions();
            if (this.dirtyStampHandlePositions) this.cleanStampHandlePositions();
  • §

    If the entity’s Path2D object has been marked as dirty by the dirtyPathObject flag, rebuild it by invoking the cleanPathObject function.

            if (this.dirtyPathObject) this.cleanPathObject();
  • §

    dirtyPositionSubscribers - update any artefacts subscribed to this entity as their pivot or mimic source, if required, by invoking the updatePositionSubscribers function.

            if (this.dirtyPositionSubscribers) this.updatePositionSubscribers();
  • §

    prepareStampTabsHelper is defined in the mixin/hidden-dom-elements.js file - handles updates to anchor and button objects

            this.prepareStampTabsHelper();
        };
  • §

    The dirtyFilters flag is checked and handled by the filteredStamp function.

  • §

    cleanPathObject - this function will be overwritten by every entity Factory, to meet their individual requirements.

    • The function needs to build a Canvas API Path2D object and store it in the pathObject attribute. The Path2D object is used for both entity stamping (see below) and entity collision detection work.
        P.cleanPathObject = λnull;
  • §
    Step 2: invoke the entity’s stamp action

    stamp - this is the function invoked by Group objects as they cascade the Display cycle compile step through to their member artefacts.

        P.stamp = function (force = false, host, changes) {
    
            const filterTest = (!this.noFilters && this.filters && this.filters.length) ? true : false;
    
            if (force) {
    
                if (host && GOOD_HOST.includes(host.type)) this.currentHost = host;
    
                if (changes) this.set(changes);
    
                this.prepareStamp();
    
                if (filterTest) return this.filteredStamp(filterTest);
                else return this.regularStamp();
            }
    
            else if (this.visibility) {
  • §

    To note: checkHitIgnoreTransparency is specific to Picture entity

                if (this.checkHitIgnoreTransparency || this.stashOutput || filterTest) return this.filteredStamp(filterTest);
                else return this.regularStamp();
            }
        };
  • §

    regularStamp - this function coordinates the actions required for an entity to display its output on a Cell wrapper’s <canvas> element.

    Scrawl-canvas stamps an entity onto a Cell by moving and rotating the Cell engine’s transformation (its coordinate grid) to match the entity’s start and offset coordinates, alongside any requirements to rotate (roll) and flip (flipReverse, flipUpend) the transformation as set out by the entity object.

    • We use the Web API CanvasRenderingContext2D engine’s setTransform method to perform these actions when we invoke the Cell wrapper’s rotateDestination function.
    • We never invoke the engine’s translate, rotate or scale methods on the transformation. All positional, rotational and scaling data is kept in the entity object, and calculated as part of its prepareStamp step. This means we don’t need to keep track of the transformation’s current state, and makes the entity stamping operation more efficient.
    • Scrawl-canvas does not support skew operations on the transformation - use a more appropriately shaped entity instead.
    • Scrawl-canvas does not support non-isometric scaling (applying different scaling factors along the x and y axes) - most entitys include width and height attributes: use those instead.
    • We only use the transformation’s save and restore methods where it makes sense to do so - for instance in very limited actions where the save and restore invocations are close enough in the code base that we don’t lose sight of them (and remember to restore after the action completes). They are not used when updating the engine’s attributes to match an entity’s stamping requirements!
        P.regularStamp = function () {
    
            const dest = this.currentHost;
    
            if (dest) {
    
                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) dest.setEngine(this);
  • §

    Invoke the appropriate stamping method (below)

                this[this.method](engine);
            }
        };
  • §

    filteredStamp - handles stamping functionality for all entitys that have filter functions associated with them.

        P.filteredStamp = function(hasFilters = false) {
    
            const { dirtyFilters, currentHost, state } = this;
  • §

    Clean and sort the Entity-level filters before sending them to the filter engine for application

            if (dirtyFilters || !this.currentFilters) this.cleanFilters();
  • §

    Save current host data into a set of vars, ready for restoration after web engine completes or fails

            const {
                element: currEl,
                engine: currEng,
                currentDimensions: currDims,
            } = currentHost;
    
            const w = currDims ? currDims[0] : currEl.width,
                h = currDims ? currDims[1] : currEl.height;
    
            if (w && h) {
  • §

    Get and prepare a pool Cell for the filter operations

                const filterHost = requestCell(w, h);
    
                const {
                    element: filterEl,
                    engine: filterEng,
                } = filterHost;
    
                this.currentHost = filterHost;
  • §

    Switch off fast stamp

                const oldNoCanvasEngineUpdates = this.noCanvasEngineUpdates;
                this.noCanvasEngineUpdates = false;
  • §

    Handle GCO and GA

                let oldGCO = SOURCE_OVER;
                let oldAlpha = 1;
    
                if (state) {
    
                    oldGCO = state.globalCompositeOperation;
                    oldAlpha = state.globalAlpha;
    
                    state.globalCompositeOperation = SOURCE_OVER;
                    state.globalAlpha = 1;
                }
  • §

    Stamp the entity onto the pool Cell

                this.regularStamp();
  • §

    Restore GCO and GA

                if (state) {
    
                    state.globalCompositeOperation = oldGCO;
                    state.globalAlpha = oldAlpha;
                }
    
                if (hasFilters) {
    
                    const filters = this.currentFilters;
  • §

    If we’re using the entity as a stencil, copy the entity cell’s current display over the entity in the pool Cell

                    if (this.isStencil) {
    
                        filterEng.save();
                        filterEng.globalCompositeOperation = SOURCE_IN;
                        filterEng.globalAlpha = 1;
                        filterEng.resetTransform();
                        filterEng.drawImage(currEl, 0, 0);
                        filterEng.restore();
    
                        this.dirtyFilterIdentifier = true;
                    }
    
                    filterEng.resetTransform();
    
                    const myimage = filterEng.getImageData(0, 0, w, h);
    
                    this.preprocessFilters(filters, w, h);
    
                    const img = filterEngine.action({
                        identifier: this.filterIdentifier,
                        image: myimage,
                        filters,
                    });
    
                    if (img) {
    
                        filterEng.globalCompositeOperation = SOURCE_OVER;
                        filterEng.globalAlpha = 1;
                        filterEng.resetTransform();
                        filterEng.putImageData(img, 0, 0);
  • §

    To note: checkHitIgnoreTransparency is specific to Picture entity

                        if (this.checkHitIgnoreTransparency) this.stashedImageData = img;
                    }
                }
                currEng.save();
    
                currEng.globalAlpha = (state && state.globalAlpha) ? state.globalAlpha : 1;
                currEng.globalCompositeOperation = (state && state.globalCompositeOperation) ? state.globalCompositeOperation : SOURCE_OVER;
    
                currEng.resetTransform();
    
                currEng.drawImage(filterEl, 0, 0);
  • §

    This is also the point at which we action any requests to stash the Cell output and (optionally) create/update imageAsset objects and associated <img> elements for use elsewhere in the Scrawl-canvas ecosystem.

                if (this.stashOutput) {
    
                    this.stashOutput = false;
    
                    const [stashX, stashY, stashWidth, stashHeight] = this.getCellCoverage(filterEng.getImageData(0, 0, filterEl.width, filterEl.height));
    
                    this.stashedImageData = filterEng.getImageData(stashX, stashY, stashWidth, stashHeight);
    
                    if (this.stashOutputAsAsset) {
    
                        const stashId = this.stashOutputAsAsset.substring ? this.stashOutputAsAsset : `${this.name}-image`;
  • §

    KNOWN ISSUE - it takes time for the images to load the new dataURLs generated from canvas elements. See demo Canvas-020 for a workaround.

                        this.stashOutputAsAsset = false;
    
                        filterEl.width = stashWidth;
                        filterEl.height = stashHeight;
                        filterEng.putImageData(this.stashedImageData, 0, 0);
    
                        if (!this.stashedImage) {
    
                            const control = this.group.currentHost.getController();
    
                            if (control) {
    
                                const that = this;
    
                                const newimg = document.createElement(IMG);
                                newimg.id = stashId;
                                newimg.alt = `A cached image of the ${this.name} ${this.type} entity`;
    
                                newimg.onload = function () {
    
                                    control.canvasHold.appendChild(newimg);
                                    that.stashedImage = newimg;
                                    importDomImage(`#${stashId}`);
                                };
    
                                newimg.src = filterEl.toDataURL();
                            }
                        }
                        else this.stashedImage.src = filterEl.toDataURL();
                    }
                }
                currEng.restore();
    
                this.currentHost = currentHost;
                this.noCanvasEngineUpdates = oldNoCanvasEngineUpdates;
    
                releaseCell(filterHost);
            }
        };
  • §

    getCellCoverage - internal helper function - calculates the box start and dimensions values for the entity on its current Cell host, to help minimize work required when applying filters to the entity output. Also used when building an image when the scrawl.createImageFromEntity function is invoked.

        P.getCellCoverage = function (img) {
    
            const { width, height, data } = img;
    
            let maxX = 0,
                maxY = 0,
                minX = width,
                minY = height,
                counter = 3,
                x, y, i, iz;
    
            for (i = 0, iz = width * height; i < iz; i++) {
    
                if (data[counter]) {
    
                    y = _floor(i / width);
                    x = i - (y * width);
    
                    if (minX > x) minX = x;
                    if (maxX < x) maxX = x;
                    if (minY > y) minY = y;
                    if (maxY < y) maxY = y;
                }
                counter += 4;
            }
            if (minX < maxX && minY < maxY) return [minX, minY, maxX - minX, maxY - minY];
            else return [0, 0, width, height];
        };
  • §

    simpleStamp - an alternative to the stamp function, to get an entity to stamp its output onto a Cell.

    • Note that this is a synchronous action, thus cannot be included in a Display cycle cascade.
    • Will ignore any filters assigned to the entity
        P.simpleStamp = function (host, changes) {
    
            if (host && GOOD_HOST.includes(host.type)) {
    
                this.currentHost = host;
    
                if (changes) this.set(changes);
                this.prepareStamp();
    
                this.regularStamp();
            }
        };
  • §
    Stamp methods

    All actual drawing is achieved using the entity’s pre-calculated Path2D object.

  • §

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

        P.draw = function (engine) {
    
            engine.stroke(this.pathObject);
        };
  • §

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

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

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

        P.drawAndFill = function (engine) {
    
            const p = this.pathObject;
    
            engine.stroke(p);
            engine.fill(p, this.winding);
            this.currentHost.clearShadow();
            engine.stroke(p);
            engine.fill(p, this.winding);
        };
  • §

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

        P.fillAndDraw = function (engine) {
    
            const p = this.pathObject;
    
            engine.fill(p, this.winding);
            engine.stroke(p);
            this.currentHost.clearShadow();
            engine.fill(p, this.winding);
            engine.stroke(p);
        };
  • §

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

        P.drawThenFill = function (engine) {
    
            const p = this.pathObject;
    
            engine.stroke(p);
            engine.fill(p, this.winding);
        };
  • §

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

        P.fillThenDraw = function (engine) {
    
            const p = this.pathObject;
    
            engine.fill(p, this.winding);
            engine.stroke(p);
        };
  • §

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

        P.clip = function (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 (engine) {
    
            const gco = engine.globalCompositeOperation;
    
            engine.globalCompositeOperation = DESTINATION_OUT;
            engine.fill(this.pathObject, this.winding);
    
            engine.globalCompositeOperation = gco;
        };
  • §

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

        P.none = function () {}
    }