UNPKG

scrawl-canvas

Version:

Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun

474 lines (339 loc) 14.9 kB
// # TextStyle factory // We have two use cases for this artefact // + __mutable__ - used by Label entitys as their only TextStyle; by EnhancedText entitys as their default TextStyle // + __immutable__ - set once and used by EnhancedText entitys to amend the styling of their default TextStyle as the text string requires // CSS property `direction` maps to TextStyle `direction` (string) // CSS property `font-family` maps to TextStyle `fontFamily` (string) // CSS property `font-kerning` maps to TextStyle `fontKerning` (string) // CSS property `font-size` maps to TextStyle `fontSize` (string) // CSS property `font-stretch` maps to TextStyle `fontStretch` (string) // CSS property `font-style` maps to TextStyle `fontStyle` (string) // CSS property `font-variant-caps` maps to TextStyle `fontVariantCaps` (string) // CSS property `font-weight` maps to TextStyle `fontWeight` (string) // CSS property `letter-spacing` maps to TextStyle `letterSpacing` (string) // CSS property `text-rendering` maps to TextStyle `textRendering` (string) // CSS property `word-spacing` maps to TextStyle `wordSpacing` (string) // // CSS custom property `--SC-fill-style` maps to TextStyle `fillStyle` (string) // CSS custom property `--SC-stroke-style` maps to TextStyle `strokeStyle` (string) // CSS custom property `--SC-stroke-width` maps to TextStyle `lineWidth` (number) // // CSS custom property `--SC-include-highlight` maps to TextStyle `includeHighlight` (boolean) // CSS custom property `--SC-highlight-style` maps to TextStyle `highlightStyle` (string) // // CSS custom property `--SC-include-overline` maps to TextStyle `includeOverline` (boolean) // CSS custom property `--SC-overline-offset` maps to TextStyle `overlineOffset` (number) // CSS custom property `--SC-overline-style` maps to TextStyle `overlineStyle` (string) // CSS custom property `--SC-overline-width` maps to TextStyle `overlineWidth` (number) // // CSS custom property `--SC-include-underline` maps to TextStyle `includeUnderline` (boolean) // CSS custom property `--SC-underline-gap` maps to TextStyle `underlineGap` (number) // CSS custom property `--SC-underline-offset` maps to TextStyle `underlineOffset` (number) // CSS custom property `--SC-underline-style` maps to TextStyle `underlineStyle` (string) // CSS custom property `--SC-underline-width` maps to TextStyle `underlineWidth` (number) // // CSS custom property `--SC-method` maps to TextStyle `method` (string) // CSS custom property `--SC-local-handle-x` maps to TextUnit `localHandleX` (string) // CSS custom property `--SC-local-handle-y` maps to TextUnit `localHandleY` (string) // CSS custom property `--SC-local-offset-x` maps to TextUnit `localOffsetX` (number) // CSS custom property `--SC-local-offset-y` maps to TextUnit `localOffsetY` (number) // CSS custom property `--SC-local-alignment` maps to TextUnit `localAlignment` (number) // #### Imports import { constructors } from '../core/library.js'; import { addStrings, doCreate, mergeOver, λnull, Ωempty } from '../helper/utilities.js'; import baseMix from '../mixin/base.js'; // Shared constants import { _isFinite, _keys, AUTO, BLACK, DEFAULT_FONT, FILL, LINE_DASH, LTR, NAME, NORMAL, PC, UNDEF, ZERO_STR } from '../helper/shared-vars.js'; // Local constants const DEFAULT_FONT_SIZE = '12px', FONT_STRETCH_VALS = ['ultra-condensed','extra-condensed','condensed','semi-condensed','semi-expanded','expanded','extra-expanded','ultra-expanded'], SANS_SERIF = 'sans-serif', T_TEXT_STYLE = 'TextStyle'; // #### TextStyle constructor const TextStyle = function (items = Ωempty) { this.isDefaultTextStyle = !!items.isDefaultTextStyle; if (this.isDefaultTextStyle) { this.set(this.defs); this.set(items); this.letterSpacing = `${this.letterSpaceValue}px`; this.wordSpacing = `${this.wordSpaceValue}px`; } else this.set(items, true); return this; }; // #### TextStyle prototype const P = TextStyle.prototype = doCreate(); P.type = T_TEXT_STYLE; // #### Mixins baseMix(P); // #### TextStyle attributes const defaultAttributes = { // #### Canvas state - text-related attributes used by text units direction: LTR, fontKerning: NORMAL, fontStretch: NORMAL, fontVariantCaps: NORMAL, textRendering: AUTO, // #### Canvas state - non-text-related attributes used by text units fillStyle: BLACK, lineDash: null, lineDashOffset: 0, lineWidth: 1, strokeStyle: BLACK, // #### Canvas state - text-related attributes ignored by text units /* `font` - Cannot be set. Managed by local attributes 'fontString', 'canvasFont' `textAlign` - Cannot be set. Always has the value of 'left'. `textBaseline` - Cannot be set. Always has the value of 'top'. `letterSpacing` - Can be set/deltaSet. Managed by local attribute 'letterSpaceValue' `wordSpacing` - Can be set/deltaSet. Managed by local attribute 'wordSpaceValue' */ canvasFont: DEFAULT_FONT, fontString: DEFAULT_FONT, letterSpaceValue: 0, wordSpaceValue: 0, // #### Canvas state - non-text-related attributes ignored by text units /* `globalAlpha` - Can be set/deltaSet. Handled by the entity, not here `globalCompositeOperation` - Can be set. Handled by the entity, not here `lineCap` - Cannot be set. Always has the value of 'round' `lineJoin` - Cannot be set. Always has the value of 'round' `miterLimit` - Cannot be set. Irrelevant as lineJoin/lineCap are permanently set to 'round' `filter` - Can be set. Handled by the entity, not here `imageSmoothingEnabled` - Can be set. Handled by the entity, not here `imageSmoothingQuality` - Can be set. Handled by the entity, not here Note that shadow functionality is not supported by EnhancedLabels `shadowOffsetX` - Can be set/deltaSet. Handled by the entity, not here. `shadowOffsetY` - Can be set/deltaSet. Handled by the entity, not here. `shadowBlur` - Can be set/deltaSet. Handled by the entity, not here. `shadowColor` - Can be set. Handled by the entity, not here. */ // #### Unit font-related attributes fontFamily: SANS_SERIF, fontSize: DEFAULT_FONT_SIZE, fontStyle: NORMAL, fontWeight: NORMAL, /* `fontSizeValue` - Internal attribute, not stored in defs object `lineHeight` - Handled by the entity (as `lineSpacing`), not here */ // #### Unit underline styling // Underlines go behind the text, with clear guttering between the text characters and the underline // + Supports gradients, patterns and CSS color strings // + The underline thickness is adjustable via the `underlineWidth` attribute // + The position of the underline can be determined by the `underlineOffset` attribute, which takes a unit number where `0` is the top of the font height and `1` represents the bottom of the font height // + the thickness of the gap between characters and the underline, where they overlap, is controlled by the `underlineGap` attribute, measured in pixels includeUnderline: false, underlineGap: 3, underlineOffset: 0, underlineStyle: ZERO_STR, underlineWidth: 1, // #### Unit overline styling // Overlines go over the text, with no guttering // + Supports gradients, patterns and CSS color strings // + The overline thickness is adjustable via the `overlineWidth` attribute // + The position of the overline can be determined by the `overlineOffset` attribute, which takes a unit number where `0` is the top of the font height and `1` represents the bottom of the font height includeOverline: false, overlineOffset: 0, overlineStyle: ZERO_STR, overlineWidth: 1, // #### Unit highlight styling // Highlight goes behind the text and any underline, with no guttering provided. It is the full font height. // + Supports gradients, patterns and CSS color strings highlightStyle: ZERO_STR, includeHighlight: false, // #### Unit stamp method // One from: 'fill' (default), 'draw', 'fillAndDraw', 'drawAndFill' // + Other method values ('fillThenDraw', 'drawThenFill', 'clear', 'clip', 'none') will default to 'fill' method: FILL, }; P.defs = mergeOver(P.defs, defaultAttributes); // #### Packet management P.processPacketOut = function (key, value, incs) { let result = true; switch (key) { case LINE_DASH : if (!value.length) { result = (incs.includes(LINE_DASH)) ? true : false; } break; default : if (!incs.includes(key) && value === this.defs[key]) result = false; } return result; }; P.finalizePacketOut = function (copy) { const fill = copy.fillStyle, stroke = copy.strokeStyle; if (fill && !fill.substring) copy.fillStyle = fill.name; if (stroke && !stroke.substring) copy.strokeStyle = stroke.name; // Ensure CSS-like strings are exported if (copy.letterSpacing == null && this.letterSpacing != null) copy.letterSpacing = this.letterSpacing; if (copy.wordSpacing == null && this.wordSpacing != null) copy.wordSpacing = this.wordSpacing; return copy; }; // #### Clone management // Handled by Label and EnhancedLabel entity, not by the TextStyle object // #### Kill management // Handled by Label and EnhancedLabel entity, not by the TextStyle object // #### Get, Set, deltaSet // `set` - overwrites 'mixin/entity.js' function P.set = function (items = Ωempty, override = false) { if (this.isDefaultTextStyle || override) { let i, key, val, fn; const keys = _keys(items), keysLen = keys.length; if (keysLen) { const setters = this.setters, defs = this.defs; for (i = 0; i < keysLen; i++) { key = keys[i]; val = items[key]; if (key && key != NAME && val != null) { fn = setters[key]; if (fn) fn.call(this, val); else if (typeof defs[key] != UNDEF) this[key] = val; } } } } return this; }; // `setDelta` - overwrites 'mixin/entity.js' function P.setDelta = function (items = Ωempty, override = false) { if (this.isDefaultTextStyle || override) { let i, key, val, fn; const keys = _keys(items), keysLen = keys.length; if (keysLen) { const setters = this.deltaSetters, defs = this.defs; for (i = 0; i < keysLen; i++) { key = keys[i]; val = items[key]; if (key && key != NAME && val != null) { fn = setters[key]; if (fn) fn.call(this, val); else if (typeof defs[key] != UNDEF) this[key] = addStrings(this[key], val); } } } } return this; }; const G = P.getters, S = P.setters, D = P.deltaSetters; S.fontString = function (item) { if (item != null && item.substring) this.fontString = item; }; S.fontSize = function (item) { if (item != null && item.substring) this.fontSize = item.toLowerCase(); }; S.fontStyle = function (item) { if (item != null && item.substring) this.fontStyle = item.toLowerCase(); }; S.fontVariantCaps = function (item) { if (item != null && item.substring) this.fontVariantCaps = item.toLowerCase(); }; S.fontStretch = function (item) { if (item != null && item.substring) { this.fontStretch = this.fontStretchHelper(item); } }; P.fontStretchHelper = function (item) { if (!item.substring) return NORMAL; item = item.toLowerCase(); if (FONT_STRETCH_VALS.includes(item)) return item; if (item.includes(PC)) { const val = parseFloat(item); if (_isFinite(val)) { if (val <= 50) return FONT_STRETCH_VALS[0]; else if (val <= 62.5) return FONT_STRETCH_VALS[1]; else if (val <= 75) return FONT_STRETCH_VALS[2]; else if (val <= 87.5) return FONT_STRETCH_VALS[3]; else if (val >= 200) return FONT_STRETCH_VALS[7]; else if (val >= 150) return FONT_STRETCH_VALS[6]; else if (val >= 125) return FONT_STRETCH_VALS[5]; else if (val >= 112.5) return FONT_STRETCH_VALS[4]; } } return NORMAL; } S.fontWeight = function (item) { this.fontWeight = (item.toFixed) ? `${item}` : item; }; S.direction = function (item) { if (item != null && item.substring) this.direction = item; }; S.fontKerning = function (item) { if (item != null && item.substring) this.fontKerning = item; }; G.letterSpaceValue = function () { return this.letterSpaceValue; }; G.letterSpacing = function () { return `${this.letterSpaceValue}px`; }; D.letterSpacing = function (item) { if (item != null) { if (item === NORMAL) item = 0; const val = (!item.toFixed) ? parseFloat(item) : item; if (_isFinite(val)) { this.letterSpaceValue += val; this.letterSpacing = `${this.letterSpaceValue}px`; } } }; S.letterSpacing = function (item) { if (item != null) { if (item === NORMAL) item = 0; const val = (!item.toFixed) ? parseFloat(item) : item; if (_isFinite(val)) { this.letterSpaceValue = val; this.letterSpacing = `${val}px`; } } }; G.wordSpaceValue = function () { return this.wordSpaceValue; }; G.wordSpacing = function () { return `${this.wordSpaceValue}px`; }; D.wordSpacing = function (item) { if (item != null) { const val = (!item.toFixed) ? parseFloat(item) : item; if (_isFinite(val)) { this.wordSpaceValue += val; this.wordSpacing = `${this.wordSpaceValue}px`; } } }; S.wordSpacing = function (item) { if (item != null) { const val = (!item.toFixed) ? parseFloat(item) : item; if (_isFinite(val)) { this.wordSpaceValue = val; this.wordSpacing = `${val}px`; } } }; S.textRendering = function (item) { if (item != null && item.substring) this.textRendering = item; }; G.textAlign = λnull; S.textAlign = λnull; G.textBaseline = λnull; S.textBaseline = λnull; // #### Prototype functions // No additional functionality defined // #### Factory // ``` // example needed // ``` export const makeTextStyle = function (items) { if (!items) return false; return new TextStyle(items); }; constructors.TextStyle = TextStyle;