- Jump To … +
  ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document-root-elements.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/shared-vars.js ./source/core/snippets.js ./source/core/system-flags.js ./source/core/user-interaction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/array-pool.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell-fragment.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/drag-zone.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter-engine-bluenoise-data.js ./source/factory/filter-engine.js ./source/factory/filter.js ./source/factory/font-attributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/image-asset.js ./source/factory/keyboard-zone.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise-asset.js ./source/factory/observe-update.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle-force.js ./source/factory/particle-history.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.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/radial-gradient.js ./source/factory/raw-asset.js ./source/factory/reaction-diffusion-asset.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/sprite-asset.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/unstacked-element.js ./source/factory/vector.js ./source/factory/video-asset.js ./source/factory/wheel.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/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/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/shape-path-calculation.js ./source/mixin/styles.js ./source/mixin/tween.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 { doCreate, mergeOver, xt, Ωempty } from '../core/utilities.js';

      import { releaseCell, requestCell } from './cell-fragment.js';

      import baseMix from '../mixin/base.js';

      import { _isArray, _max, _min, _PC, _Q, BOLD, BOLDER, CAP, CH, CM, CONDENSED, DEFAULT_SIZE, EM, EX, EXPANDED, EXTRA_CONDENSED, EXTRA_EXPANDED, FONT_ATTRIBUTE, IC, IN, ITALIC, LARGE, LARGER, LH, LIGHTER, MEDIUM, MM, NORMAL, OBLIQUE, PC, PT, PX, REM, RFS_ARRAY_1, RFS_ARRAY_2, RLH, SANS_SERIF, SEMI_CONDENSED, SEMI_EXPANDED, SIZE_SUFFIX, SMALL, SMALL_CAPS, SMALLER, SPACE, STOP, T_CELL, T_FONT_ATTRIBUTES, ULTRA_CONDENSED, ULTRA_EXPANDED, VB, VH, VI, VMAX, VMIN, VW, X_LARGE, X_SMALL, XX_LARGE, XX_SMALL, XXX_LARGE, ZERO_STR } from '../core/shared-vars.js';

- §

  #### FontAttributes constructor

      const FontAttributes = function (items = Ωempty) {

          this.makeName(items.name);
          this.set(this.defs);
          this.set(items);

          return this;
      };

- §

  #### FontAttributes prototype

      const P = FontAttributes.prototype = doCreate();
      P.type = T_FONT_ATTRIBUTES;
      P.lib = FONT_ATTRIBUTE;

- §

  #### Mixins

      baseMix(P);

- §

  #### FontAttributes attributes

  - Attributes defined in the base mixin: name.

      const 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 clone functionality required P.packetExclusions = pushUnique(P.packetExclusions, [‘font’]);

- §

  #### Clone management

  No additional clone functionality required

- §

  #### Kill management

  No additional kill functionality required

- §

  #### Get, Set, deltaSet

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

- §

  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 size = 0,
                  metric = MEDIUM;

              if (item.includes(XX_SMALL)) metric = XX_SMALL;
              else if (item.includes(X_SMALL)) metric = X_SMALL;
              else if (item.includes(SMALLER)) metric = SMALLER;
              else if (item.includes(SMALL)) metric = SMALL;
              else if (item.includes(MEDIUM)) metric = MEDIUM;
              else if (item.includes(XXX_LARGE)) metric = XXX_LARGE;
              else if (item.includes(XX_LARGE)) metric = XX_LARGE;
              else if (item.includes(X_LARGE)) metric = X_LARGE;
              else if (item.includes(LARGER)) metric = LARGER;
              else if (item.includes(LARGE)) metric = LARGE;

              else {
                  size = 12;
                  metric = PX
              }

              let 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 (_isArray(r)) {

                  [, val, suffix] = r;

                  if (val && suffix && val != STOP) {

                      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 (_isArray(r)) {

                      [, val, suffix] = r;

                      if (val && suffix && val != STOP) {

                          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.includes(ITALIC)) ? ITALIC : v;
              v = (item.includes(OBLIQUE)) ? 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.includes(SMALL_CAPS)) ? 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.includes(BOLD)) ? BOLD : v;
                  v = (item.includes(LIGHTER)) ? LIGHTER : v;
                  v = (item.includes(BOLDER)) ? BOLDER : v;

- §

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

                  v = (item.includes(' 100 ')) ? '100' : v;
                  v = (item.includes(' 200 ')) ? '200' : v;
                  v = (item.includes(' 300 ')) ? '300' : v;
                  v = (item.includes(' 400 ')) ? '400' : v;
                  v = (item.includes(' 500 ')) ? '500' : v;
                  v = (item.includes(' 600 ')) ? '600' : v;
                  v = (item.includes(' 700 ')) ? '700' : v;
                  v = (item.includes(' 800 ')) ? '800' : v;
                  v = (item.includes(' 900 ')) ? '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.includes(CONDENSED)) ? CONDENSED : v;
              v = (item.includes(SEMI_CONDENSED)) ? SEMI_CONDENSED : v;
              v = (item.includes(EXTRA_CONDENSED)) ? EXTRA_CONDENSED : v;
              v = (item.includes(ULTRA_CONDENSED)) ? ULTRA_CONDENSED : v;
              v = (item.includes(EXPANDED)) ? EXPANDED : v;
              v = (item.includes(SEMI_EXPANDED)) ? SEMI_EXPANDED : v;
              v = (item.includes(EXTRA_EXPANDED)) ? EXTRA_EXPANDED : v;
              v = (item.includes(ULTRA_EXPANDED)) ? ULTRA_EXPANDED : v;

              if (v != this.stretch) {

                  this.stretch = v;
                  this.dirtyFont = true;
              }
          }
      };

- §

  family

      S.family = function (item) {

          if (xt(item)) {

              let v = SANS_SERIF;

              const itemArray = item.split(SPACE),
                  len = itemArray.length;

              if (len === 1) v = item;

              let counter = 0,
                  flag = true;

              while (flag) {

                  if (counter === len) flag = false;
                  else {

                      const el = itemArray[counter];

                      if (!el.length) counter++;
                      else if (RFS_ARRAY_1.includes(el)) counter++;
                      else if (RFS_ARRAY_2.includes(el[0])) counter++;
                      else flag = false;
                  }
              }
              if (counter < len) v = itemArray.slice(counter).join(SPACE);

              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;
          }

          const currentHost = (this.host && this.host.type && this.host.type == T_CELL) ? this.host.name : ZERO_STR;
          if (host && host.type && host.type == T_CELL && host.name !== currentHost) {

              this.host = host;
              this.dirtyFont = true;
          }
      };

      P.calculateSize = function () {

          if (this.host) {

              const {scale, lineHeight, host, sizeMetric} = this;

              const gcfs = host.getComputedFontSizes();

              let parentSize, rootSize, viewportWidth, viewportHeight,
                  sizeValue = this.sizeValue;

              if (!gcfs) {

                  if (!SIZE_SUFFIX.includes(sizeMetric)) {

                      this.dirtyFont = true;
                      return DEFAULT_SIZE;
                  }
              }
              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 PC :
                      res = (parentSize / 100) * sizeValue;
                      break;

                  case VW :
                      res = (viewportWidth / 100) * sizeValue;
                      break;

                  case VH :
                      res = (viewportHeight / 100) * sizeValue;
                      break;

                  case VMAX :
                      res = (_max(viewportWidth, viewportHeight) / 100) * sizeValue;
                      break;

                  case VMIN :
                      res = (_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 `${12 * this.scale}px`;
      }

- §

  `buildFont` - internal function

  - Takes into account a scaling factor

      P.buildFont = function () {

          this.dirtyFont = false;

          let font = ZERO_STR;

          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

          const 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

      export const makeFontAttributes = function (items) {

          if (!items) return false;
          return new FontAttributes(items);
      };

      constructors.FontAttributes = FontAttributes;