• Jump To … +
    ./source/core/animationloop.js ./source/core/component.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/userInteraction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/coordinate.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter.js ./source/factory/fontAttributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/imageAsset.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle.js ./source/factory/particleForce.js ./source/factory/particleHistory.js ./source/factory/particleSpring.js ./source/factory/particleWorld.js ./source/factory/pattern.js ./source/factory/phrase.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/quaternion.js ./source/factory/radialGradient.js ./source/factory/rectangle.js ./source/factory/renderAnimation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/spriteAsset.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/state.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstackedElement.js ./source/factory/vector.js ./source/factory/videoAsset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset.js ./source/mixin/assetConsumer.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/delta.js ./source/mixin/displayShape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shapeBasic.js ./source/mixin/shapeCurve.js ./source/mixin/shapePathCalculation.js ./source/mixin/styles.js ./source/mixin/tween.js ./source/worker/filter-string.js ./source/worker/filter.js
  • ¶

    FontAttributes factory

    FontAttribute objects are used exclusively by Phrase entitys. They hold data about the Phrase entity’s current font

    • The constructor uses naming functionality (from the base mixin), but doesn’t actually store FontAttribute instances in the library
    • Instead they are referenced directly from each Phrase instance.
    • FontAttribute instances get cloned as part of the Phrase stamp functionality (because of subsequent ‘update’ invocations on them).
    • If we stored FA instances in the library we’d risk running out of memory, or slowing up code speed, as the cloned instances are pretty temporary and get thrown away whenever a new Phrase.set invocation (involving font attributes) happens.

    The Phrase entity includes functionality to allow the getting and setting of FontAttribute attributes directly on the entity instance.

    Note that <canvas> context engines will attempt to display variable fonts, but the added functionality of those fonts is, for the most part, ignored. Scrawl-canvas makes no overt attempts to overcome this limitation.

  • ¶

    Demos:

    • All Phrase-related demos use FontAttribute objects in the background. Developers should never need to deal with them directly
  • ¶

    Imports

    import { constructors } from '../core/library.js';
    import { mergeOver, xt } from '../core/utilities.js';
    
    import { requestCell, releaseCell } from './cell.js';
    
    import baseMix from '../mixin/base.js';
  • ¶

    FontAttributes constructor

    const FontAttributes = function (items = {}) {
    
        this.makeName(items.name);
        this.set(this.defs);
        this.set(items);
    
        return this;
    };
  • ¶

    FontAttributes prototype

    let P = FontAttributes.prototype = Object.create(Object.prototype);
    P.type = 'FontAttributes';
    P.lib = 'fontattribute';
  • ¶

    Mixins

    P = baseMix(P);
  • ¶

    FontAttributes attributes

    • Attributes defined in the base mixin: name.
    let defaultAttributes = {
  • ¶

    font - pseudo-attribute String which gets broken down into its component parts.

    • Once the breakdown completes the font string gets reassembled and then passed through a <canvas> element’s context engine to determine the actual font String that will be used by the engine. This value gets stored in the temperedFontString attribute.
  • ¶

    font-style - saved in the style String attribute - acceptable values are: normal, italic and oblique. Note that browser handling of oblique (sloped rather than explicitly italic) fonts by their respective canvas context engines is, at best, idiosyncratic.

    • To explicitly use an oblique font design (often called ‘slanted’ or ‘sloped’), reference the font face directly in the family or font strings.
        style: 'normal',
  • ¶

    font-variant - saved in the variant String attribute - the standard indicates that canvas context engine should only recognise normal and small-caps values. Do not use other possibilities (font-variant-caps, font-variant-numeric, font-variant-ligatures, font-variant-east-asian, font-variant-alternates) in font strings; scrawl-canvas will remove and/or ignore them when it parses the font string.

        variant: 'normal',
  • ¶

    font-weight - saved in the weight String attribute - acceptable values are: normal, bold, lighter, bolder; or a number (100. 200, 300, … 900). Bold is generally the equivalent of 700, and normal is 400; lighter/bolder are values relative to the <canvas> element’s computed font weight. Note that browser handling of font weight requirements by their respective canvas context engines is not entirely standards compliant - for instance Safari browsers will generally ignore weight assertions in font strings.

    • To explicitly use a light, bold or heavy font design, reference the font face directly in the family or font strings.
        weight: 'normal',
  • ¶

    font-stretch - saved in the stretch String attribute acceptable values are: normal, semi-condensed, condensed, extra-condensed, ultra-condensed, semi-expanded, expanded, extra-expanded, ultra-expanded. Browser support for these values by the <canvas> element’s context engine is, for the most part, non-existant.

    • To explicitly use a condensed or stretched font design, reference the font face directly in the family or font strings.
        stretch: 'normal',
  • ¶

    font-size - broken into two parts and saved in the sizeValue Number and sizeMetric String, each of which can be set separately. The W3C HTML Canvas 2D Context Recommendation states: “with the ‘font-size’ component converted to CSS pixels”. However, Scrawl-canvas makes every effort to respect and interpret non-px-based font-size requests.

    • Note that these pixel values are representative of the <canvas> element’s intrinsic drawing area dimensions, not of any such measure modified by CSS dimension rules (width, height, scale, skew, etc) applied to the canvas.

    Length values relative to some intrinsic propertly of the font - Scrawl-canvas will not attempt to interpret the following, instead treating them as some fixed proportion of the <canvas> element’s computed font size:

    • 1em is the <canvas> element’s computed font size
    • 1rem is the document root (<html>) element’s computed font size
    • 1lh is the <canvas> element’s computed font size multiplied by the entity’s line height value
    • 1rlh is the document root (<html>) element’s computed font size multiplied by the entity’s line height value
    • 1ex is ≈ ‘0.5em’
    • 1cap, 1ch, 1ic are all ≈ ‘1em’

    Length percentage values, with calculation based on the <canvas> element’s computed font size:

    • 120% = ‘1.2em’

    Non-numerical values (in the Fonts standards, ‘absolute-size’ and ‘relative-size’ keywords) will calculate a size based on the <canvas> element’s computed font size:

    • xx-small = 60% of the computed font size
    • x-small = 75% of the computed font size
    • small = 89% of the computed font size
    • medium = 100% of the computed font size
    • large = 120% of the computed font size
    • x-large = 150% of the computed font size
    • xx-large = 200% of the computed font size
    • xxx-large = 300% of the computed font size
    • smaller = 80% of the computed font size
    • larger = 130% of the computed font size

    Length values relative to the browser’s viewport (window) dimensions:

    • 1vw and 1vh represent 1% of the viewport’s width and height, respectively
    • 1vmax - is 1% of the larger viewport dimension
    • 1vmin - is 1% of the smaller viewport dimension
    • Scrawl-canvas treats 1vi as ≈ 1vw, and 1vb as ≈ 1vh

    Absolute length values convert as follow:

    • 1in (inch) = 96px
    • 1cm (centimeter) = 37.80px
    • 1mm (millimeter) = 3.78px
    • 1Q (quarter mm) = 0.95px
    • 1pc (pica) = 16px
    • 1pt (point) = 1.33px
    • 1px (pixel) = 1px

    Note: we break down the size attribute into two components: sizeValue and sizeMetric. Line height values are ignored and, when present in a font string, may break the code!

        sizeValue: 12,
        sizeMetric: 'px',
  • ¶

    font-family - any part of the font string that comes after the above declarations.

    • It is generally a good idea to include one of the preset defaults - serif, sans-serif, monospace, cursive, fantasy, system-ui, math, emoji, fangsong - at the end of the font or family string, to act as a fallback default as other fonts load.
    • Scrawl-canvas will attempt to redraw the display when fonts complete their (asynchronous) upload, but this may fail and need to be triggered manually.
        family: 'sans-serif',
    
    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • ¶

    Packet management

    No additional packet functionality required

  • ¶

    Clone management

    No additional clone functionality required

  • ¶

    Kill management

    No additional kill functionality required

  • ¶

    Get, Set, deltaSet

    let G = P.getters,
        S = P.setters,
        D = P.deltaSetters;
  • ¶

    size - pseudo-attribute

    • getter returns a CSS fontSize String
    G.size = function () {
    
        return (this.sizeValue) ? `${this.sizeValue}${this.sizeMetric}` : this.sizeMetric;
    };
  • ¶
    • setter breaks the fontSize String into sizeValue and sizeMetric values
    S.size = function (item) {
    
        if (xt(item)) {
    
            let res, 
                size = 0, 
                metric = 'medium';
    
            if (item.indexOf('xx-small') >= 0) metric = 'xx-small';
            else if (item.indexOf('x-small') >= 0) metric = 'x-small';
            else if (item.indexOf('smaller') >= 0) metric = 'smaller';
            else if (item.indexOf('small') >= 0) metric = 'small';
            else if (item.indexOf('medium') >= 0) metric = 'medium';
            else if (item.indexOf('xxx-large') >= 0) metric = 'xxx-large';
            else if (item.indexOf('xx-large') >= 0) metric = 'xx-large';
            else if (item.indexOf('x-large') >= 0) metric = 'x-large';
            else if (item.indexOf('larger') >= 0) metric = 'larger';
            else if (item.indexOf('large') >= 0) metric = 'large';
    
            else {
                size = 12;
                metric = 'px'
            }
    
            let full, val, suffix;
    
            let r = item.match(/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i);
    
            if (Array.isArray(r)) {
    
                [full, val, suffix] = r;
    
                if (val && suffix && val != '.') {
    
                    size = val;
                    metric = suffix;
                }
            }
            else {
    
                r = item.match(/\/(\d+\.\d+|\d+|\.\d+)(rem|em|rlh|lh|ex|cap|ch|ic|%|vw|vh|vmax|vmin|vi|vb|in|cm|mm|Q|pc|pt|px)?/i);
    
                if (Array.isArray(r)) {
    
                    [full, val, suffix] = r;
    
                    if (val && suffix && val != '.') {
    
                        size = val;
                        metric = suffix;
                    }
                }
            }
            if (size !== this.sizeValue) {
    
                this.sizeValue = size;
                this.dirtyFont = true;
            }
            if (metric !== this.sizeMetric) {
    
                this.sizeMetric = metric;
                this.dirtyFont = true;
            }
        }
    };
    
    S.sizeValue = function (item) {
    
        if (xt(item) && item !== this.sizeValue) {
    
            this.sizeValue = item;
            this.dirtyFont = true;
        }
    }
    
    S.sizeMetric = function (item) {
    
        if (xt(item) && item !== this.sizeMetric) {
    
            this.sizeMetric = item;
            this.dirtyFont = true;
        }
    }
  • ¶

    font - pseudo-attribute which calls various functions to break down the font String into its constituent parts

    S.font = function (item) {
    
        if (xt(item)) {
    
            S.style.call(this, item);
            S.variant.call(this, item);
            S.weight.call(this, item);
            S.stretch.call(this, item);
            S.size.call(this, item);
            S.family.call(this, item);
        }
    };
  • ¶

    style

    S.style = function (item) {
    
        if (xt(item)) {
    
            let v = 'normal';
    
            v = (item.indexOf('italic') >= 0) ? 'italic' : v;
            v = (item.indexOf('oblique') >= 0) ? 'oblique' : v;
    
            if (v !== this.style) {
    
                this.style = v;
                this.dirtyFont = true;
            }
        }
    };
  • ¶

    variant

    S.variant = function (item) {
    
        if (xt(item)) {
    
            let v = 'normal';
    
            v = (item.indexOf('small-caps') >= 0) ? 'small-caps' : v;
    
            if (v !== this.variant) {
    
                this.variant = v;
                this.dirtyFont = true;
            }
        }
    };
  • ¶

    weight

    S.weight = function (item) {
    
        if (xt(item)) {
    
            let v = 'normal';
  • ¶

    Handling direct entry of numbers

            if (item.toFixed) v = item;
            else {
    
                v = (item.indexOf('bold') >= 0) ? 'bold' : v;
                v = (item.indexOf('lighter') >= 0) ? 'lighter' : v;
                v = (item.indexOf('bolder') >= 0) ? 'bolder' : v;
  • ¶

    Putting spaces around the number should help identify it as a Weight value within the font string the string

                v = (item.indexOf(' 100 ') >= 0) ? '100' : v;
                v = (item.indexOf(' 200 ') >= 0) ? '200' : v;
                v = (item.indexOf(' 300 ') >= 0) ? '300' : v;
                v = (item.indexOf(' 400 ') >= 0) ? '400' : v;
                v = (item.indexOf(' 500 ') >= 0) ? '500' : v;
                v = (item.indexOf(' 600 ') >= 0) ? '600' : v;
                v = (item.indexOf(' 700 ') >= 0) ? '700' : v;
                v = (item.indexOf(' 800 ') >= 0) ? '800' : v;
                v = (item.indexOf(' 900 ') >= 0) ? '900' : v;
  • ¶

    Also need to capture instances where a number value has been directly set with no other font attributes around it

                v = (/^\d00$/.test(item)) ? item : v;
            }
    
            if (v !== this.weight) {
    
                this.weight = v;
                this.dirtyFont = true;
            }
        }
    };
  • ¶

    stretch

    S.stretch = function (item) {
    
        if (xt(item)) {
    
            let v = 'normal';
    
            v = (item.indexOf('semi-condensed') >= 0) ? 'semi-condensed' : v;
            v = (item.indexOf('condensed') >= 0) ? 'condensed' : v;
            v = (item.indexOf('extra-condensed') >= 0) ? 'extra-condensed' : v;
            v = (item.indexOf('ultra-condensed') >= 0) ? 'ultra-condensed' : v;
            v = (item.indexOf('semi-expanded') >= 0) ? 'semi-expanded' : v;
            v = (item.indexOf('expanded') >= 0) ? 'expanded' : v;
            v = (item.indexOf('extra-expanded') >= 0) ? 'extra-expanded' : v;
            v = (item.indexOf('ultra-expanded') >= 0) ? 'ultra-expanded' : v;
    
            if (v !== this.stretch) {
    
                this.stretch = v;
                this.dirtyFont = true;
            }
        }
    };
  • ¶

    family

    P.rfsTestArray1 = ['italic','oblique','small-caps','normal','bold','lighter','bolder','ultra-condensed','extra-condensed','semi-condensed','condensed','ultra-expanded','extra-expanded','semi-expanded','expanded','xx-small','x-small','small','medium','xxx-large','xx-large','x-large','large'];
    P.rfsTestArray2 = ['0','1','2','3','4','5','6','7','8','9'];
    S.family = function (item) {
    
        if (xt(item)) {
    
            let v = 'sans-serif';
    
            let itemArray = item.split(' '),
                len = itemArray.length;
    
            if (len === 1) v = item;
    
            let counter = 0,
                flag = true;
    
            while (flag) {
    
                if (counter === len) flag = false;
                else {
    
                    let el = itemArray[counter];
    
                    if (!el.length) counter++;
                    else if (this.rfsTestArray1.indexOf(el) >= 0) counter++;
                    else if (this.rfsTestArray2.indexOf(el[0]) >= 0) counter++;
                    else flag = false;
                }
            }
            if (counter < len) v = itemArray.slice(counter).join(' ');
    
            if (v !== this.family) {
    
                this.family = v;
                this.dirtyFont = true;
            }
        }
    };
  • ¶

    Prototype functions

    P.getFontString = function() {
    
        if (!this.dirtyFont && this.temperedFontString) return this.temperedFontString;
        else return this.buildFont();
    }
    
    P.updateMetadata = function (scale, lineHeight, host) {
    
        if (scale && scale.toFixed && scale > 0 && scale !== this.scale) {
    
            this.scale = scale;
            this.dirtyFont = true;
        }
    
        if (lineHeight && lineHeight.toFixed && lineHeight > 0 && lineHeight !== this.lineHeight) {
    
            this.lineHeight = lineHeight;
            this.dirtyFont = true;
        }
        
        let currentHost = (this.host && this.host.type && this.host.type === 'Cell') ? this.host.name : '';
        if (host && host.type && host.type === 'Cell' && host.name !== currentHost) {
    
            this.host = host;
            this.dirtyFont = true;
        }
    };
    
    P.calculateSize = function () {
    
        if (this.host) {
    
            let {scale, lineHeight, host, sizeValue, sizeMetric} = this;
    
            let gcfs = host.getComputedFontSizes(),
                parentSize, rootSize, viewportWidth, viewportHeight;
    
            if (!gcfs) {
    
                if (['in', 'cm', 'mm', 'Q', 'pc', 'pt', 'px'].indexOf(sizeMetric) < 0) {
    
                    this.dirtyFont = true;
                    return '12px';
                }
            }
            else {
    
                [parentSize, rootSize, viewportWidth, viewportHeight] = host.getComputedFontSizes();
            }
    
            if (isNaN(sizeValue)) sizeValue = 12;
    
            let res = parentSize;
    
            switch (sizeMetric) {
    
                case 'rem' :
                    res = rootSize * sizeValue;
                    break;
    
                case 'em' :
                    res = parentSize * sizeValue;
                    break;
    
                case 'rlh' :
                    res = (rootSize * lineHeight) * sizeValue;
                    break;
    
                case 'lh' :
                    res = (parentSize * lineHeight) * sizeValue;
                    break;
    
                case 'ex' :
                    res = (parentSize / 2) * sizeValue;
                    break;
    
                case 'cap' :
                    res = parentSize * sizeValue;
                    break;
    
                case 'ch' :
                    res = parentSize * sizeValue;
                    break;
    
                case 'ic' :
                    res = parentSize * sizeValue;
                    break;
    
                case '%' :
                    res = (parentSize / 100) * sizeValue;
                    break;
    
                case 'vw' :
                    res = (viewportWidth / 100) * sizeValue;
                    break;
    
                case 'vh' :
                    res = (viewportHeight / 100) * sizeValue;
                    break;
    
                case 'vmax' :
                    res = (Math.max(viewportWidth, viewportHeight) / 100) * sizeValue;
                    break;
    
                case 'vmin' :
                    res = (Math.min(viewportWidth, viewportHeight) / 100) * sizeValue;
                    break;
    
                case 'vi' :
                    res = (viewportWidth / 100) * sizeValue;
                    break;
    
                case 'vb' :
                    res = (viewportHeight / 100) * sizeValue;
                    break;
    
                case 'in' :
                    res = 96 * sizeValue;
                    break;
    
                case 'cm' :
                    res = 37.8 * sizeValue;
                    break;
    
                case 'mm' :
                    res = 3.78 * sizeValue;
                    break;
    
                case 'Q' :
                    res = 0.95 * sizeValue;
                    break;
    
                case 'pc' :
                    res = 16 * sizeValue;
                    break;
    
                case 'pt' :
                    res = 1.33 * sizeValue;
                    break;
    
                case 'px' :
                    res = sizeValue;
                    break;
    
                case 'xx-small' :
                    res = 0.6 * parentSize;
                    break;
    
                case 'x-small' :
                    res = 0.75 * parentSize;
                    break;
    
                case 'smaller' :
                    res = 0.8 * parentSize;
                    break;
    
                case 'small' :
                    res = 0.89 * parentSize;
                    break;
    
                case 'xxx-large' :
                    res = 3 * parentSize;
                    break;
    
                case 'xx-large' :
                    res = 2 * parentSize;
                    break;
    
                case 'x-large' :
                    res = 1.5 * parentSize;
                    break;
    
                case 'larger' :
                    res = 1.3 * parentSize;
                    break;
    
                case 'large' :
                    res = 1.2 * parentSize;
                    break;
            }
            return `${res * scale}px`;
        }
        return '12px';
    }
  • ¶

    buildFont - internal function

    • Takes into account a scaling factor
    P.buildFont = function () {
    
        this.dirtyFont = false;
    
        let font = ''
    
        if (this.style !== 'normal') font += `${this.style} `;
        if (this.variant !== 'normal') font += `${this.variant} `;
        if (this.weight !== 'normal') font += `${this.weight} `;
        if (this.stretch !== 'normal') font += `${this.stretch} `;
    
        font += `${this.calculateSize()} `;
    
        font += `${this.family}`;
  • ¶

    Temper the font string. Submit it to a canvas context engine to see what it makes of it

        let myCell = requestCell();
        myCell.engine.font = font;
        font = myCell.engine.font;
        releaseCell(myCell);
    
        this.temperedFontString = font;
    
        return font;
    };
  • ¶

    update - sets items, then calls buildFont

    P.update = function (items) {
    
        if (items) this.set(items);
        return this.getFontString();
    };
  • ¶

    Factory

    const makeFontAttributes = function (items) {
        return new FontAttributes(items);
    };
    
    constructors.FontAttributes = FontAttributes;
  • ¶

    Exports

    export {
        makeFontAttributes,
    };