chore(core): Refactor v3 code
This commit is contained in:
parent
f882a26408
commit
200cebf582
27 changed files with 3961 additions and 2633 deletions
|
@ -1,8 +1,30 @@
|
|||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for Attributes
|
||||
*
|
||||
* @constructor
|
||||
* @return {Attributes} this - The Attributes instance
|
||||
*/
|
||||
export function Attributes() {
|
||||
this.list = {}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Adds an attribute */
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Add an attribute
|
||||
*
|
||||
* @param {string} name - Name of the attribute to add
|
||||
* @param {string} value - Value of the attribute to add
|
||||
* @return {Attributes} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.add = function (name, value) {
|
||||
if (typeof this.list[name] === 'undefined') {
|
||||
this.list[name] = []
|
||||
|
@ -12,75 +34,12 @@ Attributes.prototype.add = function (name, value) {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Sets an attribute, overwriting existing value */
|
||||
Attributes.prototype.set = function (name, value) {
|
||||
this.list[name] = [value]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Sets an attribute, but only if it's not currently set */
|
||||
Attributes.prototype.setIfUnset = function (name, value) {
|
||||
if (typeof this.list[name] === 'undefined') this.list[name] = [value]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Removes an attribute */
|
||||
Attributes.prototype.remove = function (name) {
|
||||
delete this.list[name]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Retrieves an attribute */
|
||||
Attributes.prototype.get = function (name) {
|
||||
if (typeof this.list[name] === 'undefined') return false
|
||||
else return this.list[name].join(' ')
|
||||
}
|
||||
|
||||
/** Retrieves an attribute as array*/
|
||||
Attributes.prototype.getAsArray = function (name) {
|
||||
if (typeof this.list[name] === 'undefined') return false
|
||||
else return this.list[name]
|
||||
}
|
||||
|
||||
/** Returns SVG code for attributes */
|
||||
Attributes.prototype.render = function () {
|
||||
let svg = ''
|
||||
for (let key in this.list) {
|
||||
svg += ` ${key}="${this.list[key].join(' ')}"`
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns CSS code for attributes */
|
||||
Attributes.prototype.renderAsCss = function () {
|
||||
let css = ''
|
||||
for (let key in this.list) {
|
||||
css += ` ${key}:${this.list[key].join(' ')};`
|
||||
}
|
||||
|
||||
return css
|
||||
}
|
||||
|
||||
/** Returns SVG code for attributes with a fiven prefix
|
||||
* typically used for data-text*/
|
||||
Attributes.prototype.renderIfPrefixIs = function (prefix = '') {
|
||||
let svg = ''
|
||||
let prefixLen = prefix.length
|
||||
for (let key in this.list) {
|
||||
if (key.substr(0, prefixLen) === prefix) {
|
||||
svg += ` ${key.substr(prefixLen)}="${this.list[key].join(' ')}"`
|
||||
}
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns a props object for attributes with a fiven prefix
|
||||
* typically used for data-text*/
|
||||
/**
|
||||
* Return a props object for attributes with a fiven prefix (typically used for data-text)
|
||||
*
|
||||
* @param {string} prefix - The prefix to filter attributes on
|
||||
* @return {object} props - The attributes as props
|
||||
*/
|
||||
Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
|
||||
let props = {}
|
||||
let prefixLen = prefix.length
|
||||
|
@ -95,10 +54,120 @@ Attributes.prototype.asPropsIfPrefixIs = function (prefix = '') {
|
|||
return props
|
||||
}
|
||||
|
||||
/** Returns a deep copy of this */
|
||||
/**
|
||||
* Return a deep copy of this
|
||||
*
|
||||
* @return {object} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.clone = function () {
|
||||
let clone = new Attributes()
|
||||
clone.list = JSON.parse(JSON.stringify(this.list))
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an attribute
|
||||
*
|
||||
* @param {string} name - Name of the attribute to get
|
||||
* @return value - The value under name
|
||||
*/
|
||||
Attributes.prototype.get = function (name) {
|
||||
if (typeof this.list[name] === 'undefined') return false
|
||||
else return this.list[name].join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an attribute as array
|
||||
*
|
||||
* @param {string} name - Name of the attribute to set
|
||||
* @return {object} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.getAsArray = function (name) {
|
||||
if (typeof this.list[name] === 'undefined') return false
|
||||
else return this.list[name]
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an attribute
|
||||
*
|
||||
* @param {string} name - Name of the attribute to set
|
||||
* @return {object} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.remove = function (name) {
|
||||
delete this.list[name]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SVG code for attributes
|
||||
*
|
||||
* @return {string} svg - The SVG code
|
||||
*/
|
||||
Attributes.prototype.render = function () {
|
||||
let svg = ''
|
||||
for (let key in this.list) {
|
||||
svg += ` ${key}="${this.list[key].join(' ')}"`
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/**
|
||||
* Return CSS code for attributes
|
||||
*
|
||||
* @return {string} css - The CSS code
|
||||
*/
|
||||
Attributes.prototype.renderAsCss = function () {
|
||||
let css = ''
|
||||
for (let key in this.list) {
|
||||
css += ` ${key}:${this.list[key].join(' ')};`
|
||||
}
|
||||
|
||||
return css
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SVG code for attributes with a fiven prefix (typically used for data-text)
|
||||
*
|
||||
* @param {string} prefix - The prefix to filter attributes on
|
||||
* @return {string} svg - The SVG code
|
||||
*/
|
||||
Attributes.prototype.renderIfPrefixIs = function (prefix = '') {
|
||||
let svg = ''
|
||||
let prefixLen = prefix.length
|
||||
for (let key in this.list) {
|
||||
if (key.substr(0, prefixLen) === prefix) {
|
||||
svg += ` ${key.substr(prefixLen)}="${this.list[key].join(' ')}"`
|
||||
}
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute, overwriting existing value
|
||||
*
|
||||
* @param {string} name - Name of the attribute to set
|
||||
* @param {string} value - Value of the attribute to set
|
||||
* @return {Attributes} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.set = function (name, value) {
|
||||
this.list[name] = [value]
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an attribute, but only if it's not currently set
|
||||
*
|
||||
* @param {string} name - Name of the attribute to set
|
||||
* @param {string} value - Value of the attribute to set
|
||||
* @return {Attributes} this - The Attributes instance
|
||||
*/
|
||||
Attributes.prototype.setIfUnset = function (name, value) {
|
||||
if (typeof this.list[name] === 'undefined') this.list[name] = [value]
|
||||
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
export const loadDesignDefaults = () => ({
|
||||
/**
|
||||
* Return an object holding the defaults for a design configuration
|
||||
*
|
||||
* @function
|
||||
* @private
|
||||
* @return {object} defaults - The default design configuration
|
||||
*/
|
||||
export const __loadDesignDefaults = () => ({
|
||||
measurements: [],
|
||||
optionalMeasurements: [],
|
||||
options: {},
|
||||
|
@ -8,7 +15,14 @@ export const loadDesignDefaults = () => ({
|
|||
plugins: [],
|
||||
})
|
||||
|
||||
export const loadPatternDefaults = () => ({
|
||||
/**
|
||||
* Return an object holding the defaults for pattern settings
|
||||
*
|
||||
* @function
|
||||
* @private
|
||||
* @return {object} defaults - The default pattern settings
|
||||
*/
|
||||
export const __loadPatternDefaults = () => ({
|
||||
complete: true,
|
||||
idPrefix: 'fs-',
|
||||
stackPrefix: '',
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import { Pattern } from './pattern.mjs'
|
||||
import { loadDesignDefaults } from './config.mjs'
|
||||
import { __loadDesignDefaults } from './config.mjs'
|
||||
|
||||
/*
|
||||
* The Design constructor. Returns a Pattern constructor
|
||||
* So it's sort of a super-constructor
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return a Pattern constructor (it's a super-constructor)
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} config - The design configuration
|
||||
* @return {function} pattern - The pattern constructor
|
||||
*/
|
||||
export function Design(config) {
|
||||
// Initialize config with defaults
|
||||
config = { ...loadDesignDefaults(), ...config }
|
||||
config = { ...__loadDesignDefaults(), ...config }
|
||||
|
||||
// Create the pattern constructor
|
||||
const pattern = function (...sets) {
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* Returns an object holding the defaults hooks structure
|
||||
*
|
||||
* @constructor
|
||||
* @return {object} hooks - The default hooks holding structure
|
||||
*/
|
||||
export function Hooks() {
|
||||
return {
|
||||
preDraft: [],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Bezier } from 'bezier-js'
|
||||
import { Attributes } from './attributes.mjs'
|
||||
import { Design } from './design.mjs'
|
||||
import { Pattern } from './pattern.mjs'
|
||||
|
@ -7,34 +8,30 @@ import { Path } from './path.mjs'
|
|||
import { Snippet } from './snippet.mjs'
|
||||
import { Store } from './store.mjs'
|
||||
import {
|
||||
isCoord,
|
||||
capitalize,
|
||||
beamsIntersect,
|
||||
linesIntersect,
|
||||
pointOnBeam,
|
||||
pointOnLine,
|
||||
pointOnCurve,
|
||||
splitCurve,
|
||||
beamIntersectsCircle,
|
||||
beamIntersectsX,
|
||||
beamIntersectsY,
|
||||
units,
|
||||
lineIntersectsCurve,
|
||||
beamsIntersect,
|
||||
capitalize,
|
||||
circlesIntersect,
|
||||
curveEdge,
|
||||
curveIntersectsX,
|
||||
curveIntersectsY,
|
||||
curvesIntersect,
|
||||
circlesIntersect,
|
||||
beamIntersectsCircle,
|
||||
lineIntersectsCircle,
|
||||
curveEdge,
|
||||
stretchToScale,
|
||||
round,
|
||||
sampleStyle,
|
||||
deg2rad,
|
||||
rad2deg,
|
||||
pctBasedOn,
|
||||
Bezier,
|
||||
generateStackTransform,
|
||||
macroName,
|
||||
lineIntersectsCircle,
|
||||
lineIntersectsCurve,
|
||||
linesIntersect,
|
||||
pctBasedOn,
|
||||
pointOnBeam,
|
||||
pointOnCurve,
|
||||
pointOnLine,
|
||||
rad2deg,
|
||||
round,
|
||||
splitCurve,
|
||||
stretchToScale,
|
||||
units,
|
||||
} from './utils.mjs'
|
||||
import { version } from '../data.mjs'
|
||||
|
||||
|
@ -48,33 +45,31 @@ export {
|
|||
Part,
|
||||
Snippet,
|
||||
Store,
|
||||
version,
|
||||
Bezier,
|
||||
capitalize,
|
||||
beamsIntersect,
|
||||
linesIntersect,
|
||||
pointOnBeam,
|
||||
pointOnLine,
|
||||
pointOnCurve,
|
||||
splitCurve,
|
||||
// Utils
|
||||
beamIntersectsCircle,
|
||||
beamIntersectsX,
|
||||
beamIntersectsY,
|
||||
units,
|
||||
lineIntersectsCurve,
|
||||
beamsIntersect,
|
||||
capitalize,
|
||||
circlesIntersect,
|
||||
curveEdge,
|
||||
curveIntersectsX,
|
||||
curveIntersectsY,
|
||||
curvesIntersect,
|
||||
circlesIntersect,
|
||||
beamIntersectsCircle,
|
||||
lineIntersectsCircle,
|
||||
curveEdge,
|
||||
stretchToScale,
|
||||
round,
|
||||
sampleStyle,
|
||||
deg2rad,
|
||||
rad2deg,
|
||||
pctBasedOn,
|
||||
generateStackTransform,
|
||||
macroName,
|
||||
isCoord,
|
||||
version,
|
||||
lineIntersectsCircle,
|
||||
lineIntersectsCurve,
|
||||
linesIntersect,
|
||||
pctBasedOn,
|
||||
pointOnBeam,
|
||||
pointOnCurve,
|
||||
pointOnLine,
|
||||
rad2deg,
|
||||
round,
|
||||
splitCurve,
|
||||
stretchToScale,
|
||||
units,
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export function Option(config) {
|
||||
this.id = config.id
|
||||
this.config = config
|
||||
this.val = config.val
|
||||
|
||||
return this
|
||||
}
|
|
@ -1,23 +1,33 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import * as utils from './utils.mjs'
|
||||
import { Point } from './point.mjs'
|
||||
import { Path } from './path.mjs'
|
||||
import { Snippet } from './snippet.mjs'
|
||||
import { Point, pointsProxy } from './point.mjs'
|
||||
import { Path, pathsProxy } from './path.mjs'
|
||||
import { Snippet, snippetsProxy } from './snippet.mjs'
|
||||
import { Hooks } from './hooks.mjs'
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for a Part
|
||||
*
|
||||
* @constructor
|
||||
* @return {Part} this - The Part instance
|
||||
*/
|
||||
export function Part() {
|
||||
// Non-enumerable properties
|
||||
utils.addNonEnumProp(this, 'freeId', 0)
|
||||
utils.addNonEnumProp(this, 'topLeft', false)
|
||||
utils.addNonEnumProp(this, 'bottomRight', false)
|
||||
utils.addNonEnumProp(this, 'width', false)
|
||||
utils.addNonEnumProp(this, 'height', false)
|
||||
utils.addNonEnumProp(this, 'utils', utils)
|
||||
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
|
||||
utils.addNonEnumProp(this, 'Point', Point)
|
||||
utils.addNonEnumProp(this, 'Path', Path)
|
||||
utils.addNonEnumProp(this, 'Snippet', Snippet)
|
||||
utils.addNonEnumProp(this, 'hooks', new Hooks())
|
||||
utils.__addNonEnumProp(this, 'freeId', 0)
|
||||
utils.__addNonEnumProp(this, 'topLeft', false)
|
||||
utils.__addNonEnumProp(this, 'bottomRight', false)
|
||||
utils.__addNonEnumProp(this, 'width', false)
|
||||
utils.__addNonEnumProp(this, 'height', false)
|
||||
utils.__addNonEnumProp(this, 'utils', utils)
|
||||
utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
|
||||
utils.__addNonEnumProp(this, 'Point', Point)
|
||||
utils.__addNonEnumProp(this, 'Path', Path)
|
||||
utils.__addNonEnumProp(this, 'Snippet', Snippet)
|
||||
utils.__addNonEnumProp(this, 'hooks', new Hooks())
|
||||
|
||||
// Enumerable properties
|
||||
this.render = true // FIXME: Replace render with hide
|
||||
|
@ -31,56 +41,163 @@ export function Part() {
|
|||
return this
|
||||
}
|
||||
|
||||
Part.prototype.macroClosure = function () {
|
||||
let self = this
|
||||
let method = function (key, args) {
|
||||
let macro = utils.macroName(key)
|
||||
if (typeof self[macro] === 'function') self[macro](args)
|
||||
}
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
return method
|
||||
/**
|
||||
* Adds an attribute in a chainable way
|
||||
*
|
||||
* @param {string} name - Name of the attribute to add
|
||||
* @param {string} value - Value of the attribute to add
|
||||
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
|
||||
* @return {Part} this - The part instance
|
||||
*/
|
||||
Part.prototype.attr = function (name, value, overwrite = false) {
|
||||
if (overwrite) this.attributes.set(name, value)
|
||||
else this.attributes.add(name, value)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Part.prototype.runHooks = function (hookName, data = false) {
|
||||
if (data === false) data = this
|
||||
let hooks = this.hooks[hookName]
|
||||
if (hooks && hooks.length > 0) {
|
||||
for (let hook of hooks) {
|
||||
hook.method(data, hook.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns an unused ID */
|
||||
/**
|
||||
* Returns on unused ID (unused in this part)
|
||||
*
|
||||
* @param {string} prefix - An optional prefix to apply to the ID
|
||||
* @return {string} id - The id
|
||||
*/
|
||||
Part.prototype.getId = function (prefix = '') {
|
||||
this.freeId += 1
|
||||
|
||||
return prefix + this.freeId
|
||||
}
|
||||
|
||||
/** Returns a value formatted for units provided in settings */
|
||||
Part.prototype.unitsClosure = function () {
|
||||
const self = this
|
||||
const method = function (value) {
|
||||
if (typeof value !== 'number')
|
||||
self.context.store.log.warning(
|
||||
`Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)`
|
||||
)
|
||||
return utils.units(value, self.context.settings.units)
|
||||
/** Returns an object with shorthand access for pattern design */
|
||||
/**
|
||||
* Returns an object that will be passed to draft method to be destructured
|
||||
*
|
||||
* @return {object} short - The so-called shorthand object with what you might need in your draft method
|
||||
*/
|
||||
Part.prototype.shorthand = function () {
|
||||
const complete = this.context.settings?.complete ? true : false
|
||||
const paperless = this.context.settings?.paperless === true ? true : false
|
||||
const sa = this.context.settings?.complete ? this.context.settings?.sa || 0 : 0
|
||||
const shorthand = {
|
||||
part: this,
|
||||
sa,
|
||||
scale: this.context.settings?.scale,
|
||||
store: this.context.store,
|
||||
macro: this.__macroClosure(),
|
||||
units: this.__unitsClosure(),
|
||||
utils: utils,
|
||||
complete,
|
||||
paperless,
|
||||
events: this.context.events,
|
||||
log: this.context.store.log,
|
||||
addCut: this.addCut,
|
||||
removeCut: this.removeCut,
|
||||
}
|
||||
// Add top-level store methods and add a part name parameter
|
||||
const partName = this.name
|
||||
for (const [key, method] of Object.entries(this.context.store)) {
|
||||
if (typeof method === 'function')
|
||||
shorthand[key] = function (...args) {
|
||||
return method(partName, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
return method
|
||||
// We'll need this
|
||||
let self = this
|
||||
|
||||
// Wrap the Point constructor so objects can log
|
||||
shorthand.Point = function (x, y) {
|
||||
Point.apply(this, [x, y])
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Point.prototype = Object.create(Point.prototype)
|
||||
// Wrap the Path constructor so objects can log
|
||||
shorthand.Path = function () {
|
||||
Path.apply(this, [true])
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Path.prototype = Object.create(Path.prototype)
|
||||
// Wrap the Snippet constructor so objects can log
|
||||
shorthand.Snippet = function (def, anchor) {
|
||||
Snippet.apply(this, [def, anchor, true])
|
||||
Snippet.apply(this, arguments)
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Snippet.prototype = Object.create(Snippet.prototype)
|
||||
|
||||
// Proxy points, paths, snippets, measurements, options, and absoluteOptions
|
||||
shorthand.points = new Proxy(this.points || {}, pointsProxy(self.points, self.context.store.log))
|
||||
shorthand.paths = new Proxy(this.paths || {}, pathsProxy(self.paths, self.context.store.log))
|
||||
shorthand.snippets = new Proxy(
|
||||
this.snippets || {},
|
||||
snippetsProxy(self.snippets, self.context.store.log)
|
||||
)
|
||||
shorthand.measurements = new Proxy(this.context.settings.measurements || {}, {
|
||||
get: function (measurements, name) {
|
||||
if (typeof measurements[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`measurements.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (measurements, name, value) => (self.context.settings.measurements[name] = value),
|
||||
})
|
||||
shorthand.options = new Proxy(this.context.settings.options || {}, {
|
||||
get: function (options, name) {
|
||||
if (typeof options[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`options.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (options, name, value) => (self.context.settings.options[name] = value),
|
||||
})
|
||||
shorthand.absoluteOptions = new Proxy(this.context.settings.absoluteOptions || {}, {
|
||||
get: function (absoluteOptions, name) {
|
||||
if (typeof absoluteOptions[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`absoluteOptions.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (absoluteOptions, name, value) => (self.context.settings.absoluteOptions[name] = value),
|
||||
})
|
||||
|
||||
return shorthand
|
||||
}
|
||||
|
||||
/** Calculates the part's bounding box and sets it */
|
||||
Part.prototype.boundary = function () {
|
||||
/**
|
||||
* Returns a value formatted for units set in settings
|
||||
*
|
||||
* @param {float} input - The value to format
|
||||
* @return {string} result - The input formatted for the units set in settings
|
||||
*/
|
||||
Part.prototype.units = function (input) {
|
||||
return utils.units(input, this.context.settings.units)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// PRIVATE METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Calculates the part's bounding box and mutates the part to set it
|
||||
*
|
||||
* @private
|
||||
* @return {Part} this - The part instance
|
||||
*/
|
||||
Part.prototype.__boundary = function () {
|
||||
if (this.topLeft) return this // Cached
|
||||
|
||||
let topLeft = new Point(Infinity, Infinity)
|
||||
let bottomRight = new Point(-Infinity, -Infinity)
|
||||
for (let key in this.paths) {
|
||||
try {
|
||||
let path = this.paths[key].boundary()
|
||||
let path = this.paths[key].__boundary()
|
||||
if (path.render) {
|
||||
if (path.topLeft.x < topLeft.x) topLeft.x = path.topLeft.x
|
||||
if (path.topLeft.y < topLeft.y) topLeft.y = path.topLeft.y
|
||||
|
@ -120,16 +237,14 @@ Part.prototype.boundary = function () {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Adds an attribute. This is here to make this call chainable in assignment */
|
||||
Part.prototype.attr = function (name, value, overwrite = false) {
|
||||
if (overwrite) this.attributes.set(name, value)
|
||||
else this.attributes.add(name, value)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Copies point/path/snippet data from part orig into this */
|
||||
Part.prototype.inject = function (orig) {
|
||||
/**
|
||||
* Copies point/path/snippet data from part orig into this
|
||||
*
|
||||
* @private
|
||||
* @param {object} orig - The original part to inject into this
|
||||
* @return {Part} this - The part instance
|
||||
*/
|
||||
Part.prototype.__inject = function (orig) {
|
||||
const findBasePoint = (p) => {
|
||||
for (let i in orig.points) {
|
||||
if (orig.points[i] === p) return i
|
||||
|
@ -161,196 +276,37 @@ Part.prototype.inject = function (orig) {
|
|||
return this
|
||||
}
|
||||
|
||||
Part.prototype.units = function (input) {
|
||||
return utils.units(input, this.context.settings.units)
|
||||
}
|
||||
|
||||
/** Returns an object with shorthand access for pattern design */
|
||||
Part.prototype.shorthand = function () {
|
||||
const complete = this.context.settings?.complete ? true : false
|
||||
const paperless = this.context.settings?.paperless === true ? true : false
|
||||
const sa = this.context.settings?.complete ? this.context.settings?.sa || 0 : 0
|
||||
const shorthand = {
|
||||
part: this,
|
||||
sa,
|
||||
scale: this.context.settings?.scale,
|
||||
store: this.context.store,
|
||||
macro: this.macroClosure(),
|
||||
units: this.unitsClosure(),
|
||||
utils: utils,
|
||||
complete,
|
||||
paperless,
|
||||
events: this.context.events,
|
||||
log: this.context.store.log,
|
||||
addCut: this.addCut,
|
||||
removeCut: this.removeCut,
|
||||
}
|
||||
// Add top-level store methods and add a part name parameter
|
||||
const partName = this.name
|
||||
for (const [key, method] of Object.entries(this.context.store)) {
|
||||
if (typeof method === 'function')
|
||||
shorthand[key] = function (...args) {
|
||||
return method(partName, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
// We'll need this
|
||||
/**
|
||||
* Returns a closure holding the macro method
|
||||
*
|
||||
* @private
|
||||
* @return {function} method - The closured macro method
|
||||
*/
|
||||
Part.prototype.__macroClosure = function () {
|
||||
let self = this
|
||||
let method = function (key, args) {
|
||||
let macro = utils.__macroName(key)
|
||||
if (typeof self[macro] === 'function') self[macro](args)
|
||||
}
|
||||
|
||||
// Wrap the Point constructor so objects can log
|
||||
shorthand.Point = function (x, y) {
|
||||
Point.apply(this, [x, y, true])
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Point.prototype = Object.create(Point.prototype)
|
||||
// Wrap the Path constructor so objects can log
|
||||
shorthand.Path = function () {
|
||||
Path.apply(this, [true])
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Path.prototype = Object.create(Path.prototype)
|
||||
// Wrap the Snippet constructor so objects can log
|
||||
shorthand.Snippet = function (def, anchor) {
|
||||
Snippet.apply(this, [def, anchor, true])
|
||||
Snippet.apply(this, arguments)
|
||||
Object.defineProperty(this, 'log', { value: self.context.store.log })
|
||||
}
|
||||
shorthand.Snippet.prototype = Object.create(Snippet.prototype)
|
||||
|
||||
// Proxy the points object
|
||||
const pointsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (points, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Point !== true)
|
||||
self.context.store.log.warning(
|
||||
`\`points.${name}\` was set with a value that is not a \`Point\` object`
|
||||
)
|
||||
if (value.x == null || !utils.isCoord(value.x))
|
||||
self.context.store.log.warning(
|
||||
`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``
|
||||
)
|
||||
if (value.y == null || !utils.isCoord(value.y))
|
||||
self.context.store.log.warning(
|
||||
`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.store.log.warning(`Could not set \`name\` property on \`points.${name}\``)
|
||||
}
|
||||
return (self.points[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.points = new Proxy(this.points || {}, pointsProxy)
|
||||
// Proxy the paths object
|
||||
const pathsProxy = {
|
||||
get: function () {
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (paths, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Path !== true)
|
||||
self.context.store.log.warning(
|
||||
`\`paths.${name}\` was set with a value that is not a \`Path\` object`
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.store.log.warning(`Could not set \`name\` property on \`paths.${name}\``)
|
||||
}
|
||||
return (self.paths[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.paths = new Proxy(this.paths || {}, pathsProxy)
|
||||
// Proxy the snippets object
|
||||
const snippetsProxy = {
|
||||
get: function (...args) {
|
||||
return Reflect.get(...args)
|
||||
},
|
||||
set: (snippets, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Snippet !== true)
|
||||
self.context.store.log.warning(
|
||||
`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`
|
||||
)
|
||||
if (typeof value.def !== 'string')
|
||||
self.context.store.log.warning(
|
||||
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
|
||||
)
|
||||
if (value.anchor instanceof Point !== true)
|
||||
self.context.store.log.warning(
|
||||
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
self.context.store.log.warning(`Could not set \`name\` property on \`snippets.${name}\``)
|
||||
}
|
||||
return (self.snippets[name] = value)
|
||||
},
|
||||
}
|
||||
shorthand.snippets = new Proxy(this.snippets || {}, snippetsProxy)
|
||||
// Proxy the measurements object
|
||||
const measurementsProxy = {
|
||||
get: function (measurements, name) {
|
||||
if (typeof measurements[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`measurements.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (measurements, name, value) => (self.context.settings.measurements[name] = value),
|
||||
}
|
||||
shorthand.measurements = new Proxy(this.context.settings.measurements || {}, measurementsProxy)
|
||||
// Proxy the options object
|
||||
const optionsProxy = {
|
||||
get: function (options, name) {
|
||||
if (typeof options[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`options.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (options, name, value) => (self.context.settings.options[name] = value),
|
||||
}
|
||||
shorthand.options = new Proxy(this.context.settings.options || {}, optionsProxy)
|
||||
// Proxy the absoluteOptions object
|
||||
const absoluteOptionsProxy = {
|
||||
get: function (absoluteOptions, name) {
|
||||
if (typeof absoluteOptions[name] === 'undefined')
|
||||
self.context.store.log.warning(
|
||||
`Tried to access \`absoluteOptions.${name}\` but it is \`undefined\``
|
||||
)
|
||||
return Reflect.get(...arguments)
|
||||
},
|
||||
set: (absoluteOptions, name, value) => (self.context.settings.absoluteOptions[name] = value),
|
||||
}
|
||||
shorthand.absoluteOptions = new Proxy(
|
||||
this.context.settings.absoluteOptions || {},
|
||||
absoluteOptionsProxy
|
||||
)
|
||||
|
||||
return shorthand
|
||||
return method
|
||||
}
|
||||
|
||||
//Part.prototype.isEmpty = function () {
|
||||
// if (Object.keys(this.snippets).length > 0) return false
|
||||
//
|
||||
// if (Object.keys(this.paths).length > 0) {
|
||||
// for (const p in this.paths) {
|
||||
// if (this.paths[p].render && this.paths[p].length()) return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (const p in this.points) {
|
||||
// if (this.points[p].attributes.get('data-text')) return false
|
||||
// if (this.points[p].attributes.get('data-circle')) return false
|
||||
// }
|
||||
//
|
||||
// return true
|
||||
//}
|
||||
/**
|
||||
* Returns a method to format values in the units provided in settings
|
||||
*
|
||||
* @private
|
||||
* @return {function} method - The closured units method
|
||||
*/
|
||||
Part.prototype.__unitsClosure = function () {
|
||||
const self = this
|
||||
const method = function (value) {
|
||||
if (typeof value !== 'number')
|
||||
self.context.store.log.warning(
|
||||
`Calling \`units(value)\` but \`value\` is not a number (\`${typeof value}\`)`
|
||||
)
|
||||
return utils.units(value, self.context.settings.units)
|
||||
}
|
||||
|
||||
export default Part
|
||||
return method
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,230 +1,319 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import { __isCoord, rad2deg, deg2rad } from './utils.mjs'
|
||||
|
||||
export function Point(x, y, debug = false) {
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for a Point
|
||||
*
|
||||
* @constructor
|
||||
* @param {float} x - X-coordinate of the Point
|
||||
* @param {float} y - Y-coordinate of the Point
|
||||
* @return {Point} this - The Point instance
|
||||
*/
|
||||
export function Point(x, y) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.attributes = new Attributes()
|
||||
Object.defineProperty(this, 'debug', { value: debug, configurable: true })
|
||||
}
|
||||
|
||||
/** Adds the raise method for a path not created through the proxy **/
|
||||
Point.prototype.withRaise = function (raise = false) {
|
||||
if (raise) Object.defineProperty(this, 'raise', { value: raise })
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
return this
|
||||
/**
|
||||
* Returns the angle between this Point and that Point
|
||||
*
|
||||
* @param {Point} that - The Point instance to calculate the angle with
|
||||
* @return {float} angle - The angle between this Point and that Point
|
||||
*/
|
||||
Point.prototype.angle = function (that) {
|
||||
let rad = Math.atan2(-1 * this.__check().dy(that.__check()), this.dx(that))
|
||||
while (rad < 0) rad += 2 * Math.PI
|
||||
|
||||
return rad2deg(rad)
|
||||
}
|
||||
|
||||
/** Debug method to validate point data **/
|
||||
Point.prototype.check = function () {
|
||||
if (typeof this.x !== 'number') this.raise.warning('X value of `Point` is not a number')
|
||||
if (typeof this.y !== 'number') this.raise.warning('Y value of `Point` is not a number')
|
||||
}
|
||||
|
||||
/** Radians to degrees */
|
||||
Point.prototype.rad2deg = function (radians) {
|
||||
return radians * 57.29577951308232
|
||||
}
|
||||
|
||||
/** Degrees to radians */
|
||||
Point.prototype.deg2rad = function (degrees) {
|
||||
return degrees / 57.29577951308232
|
||||
}
|
||||
|
||||
/** Adds an attribute. This is here to make this call chainable in assignment */
|
||||
/**
|
||||
* Chainable way to add an attribute to the Point
|
||||
*
|
||||
* @param {string} name - Name of the attribute to add
|
||||
* @param {string} value - Value of the attribute to add
|
||||
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
|
||||
* @return {object} this - The Point instance
|
||||
*/
|
||||
Point.prototype.attr = function (name, value, overwrite = false) {
|
||||
this.check()
|
||||
if (overwrite) this.attributes.set(name, value)
|
||||
else this.attributes.add(name, value)
|
||||
|
||||
return this
|
||||
return this.__check()
|
||||
}
|
||||
|
||||
/** Returns the distance between this point and that point */
|
||||
Point.prototype.dist = function (that) {
|
||||
this.check()
|
||||
that.check()
|
||||
let dx = this.x - that.x
|
||||
let dy = this.y - that.y
|
||||
|
||||
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
|
||||
}
|
||||
|
||||
/** Returns slope of a line made by this point and that point */
|
||||
Point.prototype.slope = function (that) {
|
||||
this.check()
|
||||
that.check()
|
||||
return (that.y - this.y) / (that.x - this.x)
|
||||
}
|
||||
|
||||
/** Returns the x-delta between this point and that point */
|
||||
Point.prototype.dx = function (that) {
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
return that.x - this.x
|
||||
}
|
||||
|
||||
/** Returns the y-delta between this point and that point */
|
||||
Point.prototype.dy = function (that) {
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
return that.y - this.y
|
||||
}
|
||||
|
||||
/** Returns the angle between this point and that point */
|
||||
Point.prototype.angle = function (that) {
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
let rad = Math.atan2(-1 * this.dy(that), this.dx(that))
|
||||
while (rad < 0) rad += 2 * Math.PI
|
||||
|
||||
return this.rad2deg(rad)
|
||||
}
|
||||
|
||||
/** Rotate this point deg around that point */
|
||||
Point.prototype.rotate = function (deg, that) {
|
||||
if (typeof deg !== 'number')
|
||||
this.raise.warning('Called `Point.rotate(deg,that)` but `deg` is not a number')
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
|
||||
this.check()
|
||||
that.check()
|
||||
let radius = this.dist(that)
|
||||
let angle = this.angle(that)
|
||||
let x = that.x + radius * Math.cos(this.deg2rad(angle + deg)) * -1
|
||||
let y = that.y + radius * Math.sin(this.deg2rad(angle + deg))
|
||||
|
||||
return new Point(x, y, this.debug).withRaise(this.raise)
|
||||
}
|
||||
|
||||
/** returns an identical copy of this point */
|
||||
Point.prototype.copy = function () {
|
||||
this.check()
|
||||
|
||||
return new Point(this.x, this.y, this.debug).withRaise(this.raise)
|
||||
}
|
||||
|
||||
/** Mirrors this point around X value of that point */
|
||||
Point.prototype.flipX = function (that = false) {
|
||||
this.check()
|
||||
if (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
|
||||
that.check()
|
||||
}
|
||||
if (that === false || that.x === 0)
|
||||
return new Point(this.x * -1, this.y, this.debug).withRaise(this.raise)
|
||||
else return new Point(that.x + this.dx(that), this.y, this.debug).withRaise(this.raise)
|
||||
}
|
||||
|
||||
/** Mirrors this point around Y value of that point */
|
||||
Point.prototype.flipY = function (that = false) {
|
||||
this.check()
|
||||
if (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning('Called `Point.flipY(that)` but `that` is not a `Point` object')
|
||||
that.check()
|
||||
}
|
||||
if (that === false || that.y === 0)
|
||||
return new Point(this.x, this.y * -1, this.debug).withRaise(this.raise)
|
||||
else return new Point(this.x, that.y + this.dy(that), this.debug).withRaise(this.raise)
|
||||
}
|
||||
|
||||
/** Shifts this point distance in the deg direction */
|
||||
Point.prototype.shift = function (deg, distance) {
|
||||
this.check()
|
||||
if (typeof deg !== 'number') this.raise.warning('Called `Point.shift` but `deg` is not a number')
|
||||
if (typeof distance !== 'number')
|
||||
this.raise.warning('Called `Point.shift` but `distance` is not a number')
|
||||
let p = this.copy()
|
||||
p.x += distance
|
||||
|
||||
return p.rotate(deg, this)
|
||||
}
|
||||
|
||||
/** Shifts this point distance in the direction of that point */
|
||||
Point.prototype.shiftTowards = function (that, distance) {
|
||||
if (typeof distance !== 'number')
|
||||
this.raise.warning('Called `Point.shiftTowards` but `distance` is not a number')
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning(
|
||||
'Called `Point.shiftTowards(that, distance)` but `that` is not a `Point` object'
|
||||
)
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
return this.shift(this.angle(that), distance)
|
||||
}
|
||||
|
||||
/** Checks whether this has the same coordinates as that */
|
||||
Point.prototype.sitsOn = function (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning('Called `Point.sitsOn(that)` but `that` is not a `Point` object')
|
||||
this.check()
|
||||
that.check()
|
||||
if (this.x === that.x && this.y === that.y) return true
|
||||
else return false
|
||||
}
|
||||
|
||||
/** Checks whether this has roughly the same coordinates as that */
|
||||
Point.prototype.sitsRoughlyOn = function (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning('Called `Point.sitsRoughlyOn(that)` but `that` is not a `Point` object')
|
||||
this.check()
|
||||
that.check()
|
||||
if (Math.round(this.x) === Math.round(that.x) && Math.round(this.y) === Math.round(that.y))
|
||||
return true
|
||||
else return false
|
||||
}
|
||||
|
||||
/** Shifts this point fraction of the distance towards that point */
|
||||
Point.prototype.shiftFractionTowards = function (that, fraction) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning(
|
||||
'Called `Point.shiftFractionTowards(that, fraction)` but `that` is not a `Point` object'
|
||||
)
|
||||
if (typeof fraction !== 'number')
|
||||
this.raise.warning('Called `Point.shiftFractionTowards` but `fraction` is not a number')
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
return this.shiftTowards(that, this.dist(that) * fraction)
|
||||
}
|
||||
|
||||
/** Shifts this point distance beyond that point */
|
||||
Point.prototype.shiftOutwards = function (that, distance) {
|
||||
if (that instanceof Point !== true)
|
||||
this.raise.warning(
|
||||
'Called `Point.shiftOutwards(that, distance)` but `that` is not a `Point` object'
|
||||
)
|
||||
if (typeof distance !== 'number')
|
||||
this.raise.warning(
|
||||
'Called `Point.shiftOutwards(that, distance)` but `distance` is not a number'
|
||||
)
|
||||
this.check()
|
||||
that.check()
|
||||
|
||||
return this.shiftTowards(that, this.dist(that) + distance)
|
||||
}
|
||||
|
||||
/** Returns a deep copy of this */
|
||||
/**
|
||||
* returns an deel clone of this Point (including coordinates)
|
||||
*
|
||||
* @return {Point} clone - The cloned Point instance
|
||||
*/
|
||||
Point.prototype.clone = function () {
|
||||
this.check()
|
||||
const clone = new Point(this.x, this.y, this.debug).withRaise(this.raise)
|
||||
this.__check()
|
||||
const clone = new Point(this.x, this.y).__withLog(this.log)
|
||||
clone.attributes = this.attributes.clone()
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
/** Applies a translate transform */
|
||||
/**
|
||||
* returns an copy of this Point (coordinates only)
|
||||
*
|
||||
* @return {Point} copy - The copied Point instance
|
||||
*/
|
||||
Point.prototype.copy = function () {
|
||||
return new Point(this.__check().x, this.y).__withLog(this.log)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance between this Point and that Point
|
||||
*
|
||||
* @param {Point} that - The Point instance to calculate the distance to
|
||||
* @return {float} distance - The distance between this Point and that Point
|
||||
*/
|
||||
Point.prototype.dist = function (that) {
|
||||
const dx = this.__check().x - that.__check().x
|
||||
const dy = this.y - that.y
|
||||
|
||||
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance along the X-axis between this Point and that Point (delta X)
|
||||
*
|
||||
* @param {Point} that - The Point to which to calcuate the X delta
|
||||
* @return {float} slote - The X delta
|
||||
*/
|
||||
Point.prototype.dx = function (that) {
|
||||
return that.__check().x - this.__check().x
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance along the Y-axis between this Point and that Point (delta Y)
|
||||
*
|
||||
* @param {Point} that - The Point to which to calcuate the Y delta
|
||||
* @return {float} slote - The Y delta
|
||||
*/
|
||||
Point.prototype.dy = function (that) {
|
||||
return that.__check().y - this.__check().y
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors this Point around the X value of that Point
|
||||
*
|
||||
* @param {Point} that - The Point to flip around
|
||||
* @return {Point} flopped - The new flipped Point instance
|
||||
*/
|
||||
Point.prototype.flipX = function (that = false) {
|
||||
this.__check()
|
||||
if (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
|
||||
that.__check()
|
||||
}
|
||||
if (that === false || that.x === 0) return new Point(this.x * -1, this.y).__withLog(this.log)
|
||||
else return new Point(that.x + this.dx(that), this.y).__withLog(this.log)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors this Point around the Y value of that Point
|
||||
*
|
||||
* @param {Point} that - The Point to flip around
|
||||
* @return {Point} flipped - The new flipped Point instance
|
||||
*/
|
||||
Point.prototype.flipY = function (that = false) {
|
||||
this.__check()
|
||||
if (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning('Called `Point.flipY(that)` but `that` is not a `Point` object')
|
||||
that.__check()
|
||||
}
|
||||
if (that === false || that.y === 0) return new Point(this.x, this.y * -1).__withLog(this.log)
|
||||
else return new Point(this.x, that.y + this.dy(that)).__withLog(this.lo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate this Point deg around that Point
|
||||
*
|
||||
* @param {float} deg - The degrees to rotate
|
||||
* @param {Point} that - The Point instance to rotate around
|
||||
* @return {Point} rotated - The rotated Point instance
|
||||
*/
|
||||
Point.prototype.rotate = function (deg, that) {
|
||||
if (typeof deg !== 'number')
|
||||
this.log.warning('Called `Point.rotate(deg,that)` but `deg` is not a number')
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning('Called `Point.rotate(deg,that)` but `that` is not a `Point` object')
|
||||
const radius = this.__check().dist(that.__check())
|
||||
const angle = this.angle(that)
|
||||
const x = that.x + radius * Math.cos(deg2rad(angle + deg)) * -1
|
||||
const y = that.y + radius * Math.sin(deg2rad(angle + deg))
|
||||
|
||||
return new Point(x, y).__withLog(this.log)
|
||||
}
|
||||
|
||||
/**
|
||||
* A chainable way to add a circle at a Point
|
||||
*
|
||||
* @param {float} radius - The circle radius
|
||||
* @param {string} className - The CSS classes to apply to the circle
|
||||
* @return {Point} this - The Point instance
|
||||
*/
|
||||
Point.prototype.setCircle = function (radius = false, className = false) {
|
||||
if (radius) this.attributes.set('data-circle', radius)
|
||||
if (className) this.attributes.set('data-circle-class', className)
|
||||
|
||||
return this.__check()
|
||||
}
|
||||
|
||||
/**
|
||||
* A chainable way to add text to a Point
|
||||
*
|
||||
* @param {string} text - The text to add to the Point
|
||||
* @param {string} className - The CSS classes to apply to the text
|
||||
* @return {Point} this - The Point instance
|
||||
*/
|
||||
Point.prototype.setText = function (text = '', className = false) {
|
||||
this.attributes.set('data-text', text)
|
||||
if (className) this.attributes.set('data-text-class', className)
|
||||
|
||||
return this.__check()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts this Point distance in the deg direction
|
||||
*
|
||||
* @param {float} deg - The angle to shift towards
|
||||
* @param {float} dist - The distance to shift
|
||||
* @return {Point} shifted - The new shifted Point instance
|
||||
*/
|
||||
Point.prototype.shift = function (deg, dist) {
|
||||
if (typeof deg !== 'number') this.log.warning('Called `Point.shift` but `deg` is not a number')
|
||||
if (typeof dist !== 'number')
|
||||
this.log.warning('Called `Point.shift` but `distance` is not a number')
|
||||
let p = this.__check().copy()
|
||||
p.x += dist
|
||||
|
||||
return p.rotate(deg, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts this Point a fraction in the direction of that Point
|
||||
*
|
||||
* @param {Point} that - The Point to shift towards
|
||||
* @param {float} fraction - The fraction to shift
|
||||
* @return {Point} shifted - The new shifted Point instance
|
||||
*/
|
||||
Point.prototype.shiftFractionTowards = function (that, fraction) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning(
|
||||
'Called `Point.shiftFractionTowards(that, fraction)` but `that` is not a `Point` object'
|
||||
)
|
||||
if (typeof fraction !== 'number')
|
||||
this.log.warning('Called `Point.shiftFractionTowards` but `fraction` is not a number')
|
||||
|
||||
return this.__check().shiftTowards(that.__check(), this.dist(that) * fraction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts this Point outwards from that Point
|
||||
*
|
||||
* @param {Point} that - The Point to shift outwards from
|
||||
* @param {float} distance - The distance to shift
|
||||
* @return {Point} shifted - The new shifted Point instance
|
||||
*/
|
||||
Point.prototype.shiftOutwards = function (that, distance) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning(
|
||||
'Called `Point.shiftOutwards(that, distance)` but `that` is not a `Point` object'
|
||||
)
|
||||
if (typeof distance !== 'number')
|
||||
this.log.warning('Called `Point.shiftOutwards(that, distance)` but `distance` is not a number')
|
||||
this.__check()
|
||||
that.__check()
|
||||
|
||||
return this.__check().shiftTowards(that.__check(), this.dist(that) + distance)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts this Point distance in the direction of that Point
|
||||
*
|
||||
* @param {Point} that - The Point to short towards
|
||||
* @param {float} dist - The distance to shift
|
||||
* @return {Point} shifted - The new shifted Point instance
|
||||
*/
|
||||
Point.prototype.shiftTowards = function (that, dist) {
|
||||
if (typeof dist !== 'number')
|
||||
this.log.warning('Called `Point.shiftTowards` but `distance` is not a number')
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning(
|
||||
'Called `Point.shiftTowards(that, distance)` but `that` is not a `Point` object'
|
||||
)
|
||||
|
||||
return this.__check().shift(this.angle(that.__check()), dist)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this Point has the same coordinates as that Point
|
||||
*
|
||||
* @param {Point} that - The Point to compare coordinates with
|
||||
* @return {bool} result - True if the Points' coordinates match, false when they do not
|
||||
*/
|
||||
Point.prototype.sitsOn = function (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning('Called `Point.sitsOn(that)` but `that` is not a `Point` object')
|
||||
if (this.__check().x === that.__check().x && this.y === that.y) return true
|
||||
else return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this Point has roughtly the same coordinates as that Point
|
||||
*
|
||||
* @param {Point} that - The Point to compare coordinates with
|
||||
* @return {bool} result - True if the Points' coordinates roughty match, false when they do not
|
||||
*/
|
||||
Point.prototype.sitsRoughlyOn = function (that) {
|
||||
if (that instanceof Point !== true)
|
||||
this.log.warning('Called `Point.sitsRoughlyOn(that)` but `that` is not a `Point` object')
|
||||
if (
|
||||
Math.round(this.__check().x) === Math.round(that.__check().x) &&
|
||||
Math.round(this.y) === Math.round(that.y)
|
||||
)
|
||||
return true
|
||||
else return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns slope of a line made by this Point and that Point
|
||||
*
|
||||
* @param {Point} that - The Point that forms the line together with this Point
|
||||
* @return {float} slote - The slope of the line made by this Point and that Point
|
||||
*/
|
||||
Point.prototype.slope = function (that) {
|
||||
return (that.__check().y - this.__check().y) / (that.x - this.x)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Point instance with a translate transform applied
|
||||
*
|
||||
* @param {float} x - The X-value of the translate transform
|
||||
* @param {float} y - The Y-value of the translate transform
|
||||
* @return {Point} translated - The translated Point instance
|
||||
*/
|
||||
Point.prototype.translate = function (x, y) {
|
||||
this.check()
|
||||
this.__check()
|
||||
if (typeof x !== 'number')
|
||||
this.raise.warning('Called `Point.translate(x,y)` but `x` is not a number')
|
||||
this.log.warning('Called `Point.translate(x,y)` but `x` is not a number')
|
||||
if (typeof y !== 'number')
|
||||
this.raise.warning('Called `Point.translate(x,y)` but `y` is not a number')
|
||||
this.log.warning('Called `Point.translate(x,y)` but `y` is not a number')
|
||||
const p = this.copy()
|
||||
p.x += x
|
||||
p.y += y
|
||||
|
@ -232,18 +321,67 @@ Point.prototype.translate = function (x, y) {
|
|||
return p
|
||||
}
|
||||
|
||||
/** Chainable way to set the data-text property (and optional class) */
|
||||
Point.prototype.setText = function (text = '', className = false) {
|
||||
this.attributes.set('data-text', text)
|
||||
if (className) this.attributes.set('data-text-class', className)
|
||||
//////////////////////////////////////////////
|
||||
// PRIVATE METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Checks the Points coordinates, and raises a warning when they are invalid
|
||||
*
|
||||
* @private
|
||||
* @return {object} this - The Point instance
|
||||
*/
|
||||
Point.prototype.__check = function () {
|
||||
if (typeof this.x !== 'number') this.log.warning('X value of `Point` is not a number')
|
||||
if (typeof this.y !== 'number') this.log.warning('Y value of `Point` is not a number')
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Chainable way to set the data-circle property (and optional class) */
|
||||
Point.prototype.setCircle = function (radius = false, className = false) {
|
||||
if (radius) this.attributes.set('data-circle', radius)
|
||||
if (className) this.attributes.set('data-circle-class', className)
|
||||
/**
|
||||
* Adds a logging instance so the Point can log
|
||||
*
|
||||
* @private
|
||||
* @param {object} log - An object holding the logging methods
|
||||
* @return {object} this - The Point instance
|
||||
*/
|
||||
Point.prototype.__withLog = function (log = false) {
|
||||
if (log) Object.defineProperty(this, 'log', { value: log })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC STATIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns a ready-to-proxy that logs when things aren't exactly ok
|
||||
*
|
||||
* @private
|
||||
* @param {object} points - The points object to proxy
|
||||
* @param {object} log - The logging object
|
||||
* @return {object} proxy - The object that is ready to be proxied
|
||||
*/
|
||||
export function pointsProxy(points, log) {
|
||||
return {
|
||||
get: function (...args) {
|
||||
return Reflect.get(...args)
|
||||
},
|
||||
set: (points, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Point !== true)
|
||||
log.warning(`\`points.${name}\` was set with a value that is not a \`Point\` object`)
|
||||
if (value.x == null || !__isCoord(value.x))
|
||||
log.warning(`\`points.${name}\` was set with a \`x\` parameter that is not a \`number\``)
|
||||
if (value.y == null || !__isCoord(value.y))
|
||||
log.warning(`\`points.${name}\` was set with a \`y\` parameter that is not a \`number\``)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
log.warning(`Could not set \`name\` property on \`points.${name}\``)
|
||||
}
|
||||
return (points[name] = value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,38 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import { Point } from './point.mjs'
|
||||
|
||||
export function Snippet(def, anchor, debug = false) {
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for a Snippet
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} def - The id of the snippet in the SVG defs section
|
||||
* @param {Point} anchor - The Point to anchor this Snippet on
|
||||
* @return {Snippet} this - The Snippet instance
|
||||
*/
|
||||
export function Snippet(def, anchor) {
|
||||
this.def = def
|
||||
this.anchor = anchor
|
||||
this.attributes = new Attributes()
|
||||
Object.defineProperty(this, 'debug', { value: debug, configurable: true })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Adds the raise method for a snippet not created through the proxy **/
|
||||
Snippet.prototype.withRaise = function (raise = false) {
|
||||
if (raise) Object.defineProperty(this, 'raise', { value: raise })
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
return this
|
||||
}
|
||||
/** Adds an attribute. This is here to make this call chainable in assignment */
|
||||
/**
|
||||
* Chainable way to add an attribute
|
||||
*
|
||||
* @param {string} name - Name of the attribute to add
|
||||
* @param {string} value - Value of the attribute to add
|
||||
* @param {bool} overwrite - Whether to overwrite an existing attrubute or not
|
||||
* @return {Snippet} this - The Snippet instance
|
||||
*/
|
||||
Snippet.prototype.attr = function (name, value, overwrite = false) {
|
||||
if (overwrite) this.attributes.set(name, value)
|
||||
else this.attributes.add(name, value)
|
||||
|
@ -23,10 +40,69 @@ Snippet.prototype.attr = function (name, value, overwrite = false) {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Returns a deep copy of this */
|
||||
/**
|
||||
* Returns a deep copy of this snippet
|
||||
*
|
||||
* @return {Snippet} clone - A clone of this Snippet instance
|
||||
*/
|
||||
Snippet.prototype.clone = function () {
|
||||
let clone = new Snippet(this.def, this.anchor.clone(), this.debug).withRaise(this.raise)
|
||||
let clone = new Snippet(this.def, this.anchor.clone()).__withLog(this.log)
|
||||
clone.attributes = this.attributes.clone()
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// PRIVATE METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Adds the log method for a snippet not created through the proxy
|
||||
*
|
||||
* @private
|
||||
* @return {Snippet} this - The Snippet instance
|
||||
*/
|
||||
Snippet.prototype.__withLog = function (log = false) {
|
||||
if (log) Object.defineProperty(this, 'log', { value: log })
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC STATIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns a ready-to-proxy that logs when things aren't exactly ok
|
||||
*
|
||||
* @private
|
||||
* @param {object} snippets - The snippets object to proxy
|
||||
* @param {object} log - The logging object
|
||||
* @return {object} proxy - The object that is ready to be proxied
|
||||
*/
|
||||
export function snippetsProxy(snippets, log) {
|
||||
return {
|
||||
get: function (...args) {
|
||||
return Reflect.get(...args)
|
||||
},
|
||||
set: (snippets, name, value) => {
|
||||
// Constructor checks
|
||||
if (value instanceof Snippet !== true)
|
||||
log.warning(`\`snippets.${name}\` was set with a value that is not a \`Snippet\` object`)
|
||||
if (typeof value.def !== 'string')
|
||||
log.warning(
|
||||
`\`snippets.${name}\` was set with a \`def\` parameter that is not a \`string\``
|
||||
)
|
||||
if (value.anchor instanceof Point !== true)
|
||||
log.warning(
|
||||
`\`snippets.${name}\` was set with an \`anchor\` parameter that is not a \`Point\``
|
||||
)
|
||||
try {
|
||||
value.name = name
|
||||
} catch (err) {
|
||||
log.warning(`Could not set \`name\` property on \`snippets.${name}\``)
|
||||
}
|
||||
return (snippets[name] = value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import * as utils from './utils.mjs'
|
|||
|
||||
export function Stack(name = null) {
|
||||
// Non-enumerable properties
|
||||
utils.addNonEnumProp(this, 'freeId', 0)
|
||||
utils.addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
|
||||
utils.__addNonEnumProp(this, 'freeId', 0)
|
||||
utils.__addNonEnumProp(this, 'layout', { move: { x: 0, y: 0 } })
|
||||
|
||||
// Enumerable properties
|
||||
this.attributes = new Attributes()
|
||||
|
@ -36,33 +36,13 @@ Stack.prototype.getPartNames = function () {
|
|||
return [...this.parts].map((p) => p.name)
|
||||
}
|
||||
|
||||
/** Homes the stack so that its top left corner is in (0,0) */
|
||||
//Stack.prototype.home = function () {
|
||||
// const parts = this.getPartList()
|
||||
// if (parts.length < 1) return this
|
||||
// for (const part of this.getPartList()) {
|
||||
// part.home()
|
||||
// }
|
||||
//
|
||||
// if (parts.length === 1) {
|
||||
// this.topLeft = part.topLeft
|
||||
// this.bottomRigth = part.bottomRight
|
||||
// this.width = part.width
|
||||
// this.height = part.height
|
||||
//
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// return this.boundary()
|
||||
//}
|
||||
|
||||
/** Calculates the stack's bounding box and sets it */
|
||||
Stack.prototype.home = function () {
|
||||
if (this.topLeft) return this // Cached
|
||||
this.topLeft = new Point(Infinity, Infinity)
|
||||
this.bottomRight = new Point(-Infinity, -Infinity)
|
||||
for (const part of this.getPartList()) {
|
||||
part.boundary()
|
||||
part.__boundary()
|
||||
if (part.topLeft.x < this.topLeft.x) this.topLeft.x = part.topLeft.x
|
||||
if (part.topLeft.y < this.topLeft.y) this.topLeft.y = part.topLeft.y
|
||||
if (part.bottomRight.x > this.bottomRight.x) this.bottomRight.x = part.bottomRight.x
|
||||
|
@ -144,4 +124,24 @@ Stack.prototype.generateTransform = function (transforms) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Homes the stack so that its top left corner is in (0,0) */
|
||||
//Stack.prototype.home = function () {
|
||||
// const parts = this.getPartList()
|
||||
// if (parts.length < 1) return this
|
||||
// for (const part of this.getPartList()) {
|
||||
// part.home()
|
||||
// }
|
||||
//
|
||||
// if (parts.length === 1) {
|
||||
// this.topLeft = part.topLeft
|
||||
// this.bottomRigth = part.bottomRight
|
||||
// this.width = part.width
|
||||
// this.height = part.height
|
||||
//
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// return this.boundary()
|
||||
//}
|
||||
|
||||
export default Stack
|
||||
|
|
|
@ -2,8 +2,20 @@ import set from 'lodash.set'
|
|||
import unset from 'lodash.unset'
|
||||
import get from 'lodash.get'
|
||||
|
||||
// Don't allow setting of these top-level keys in the store
|
||||
const avoid = ['set', 'setIfUnset', 'push', 'unset', 'get', 'extend']
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for a Store
|
||||
*
|
||||
* @constructor
|
||||
* @param {Array} methods - Any methods to add to the store
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
export function Store(methods = []) {
|
||||
/*
|
||||
* Default logging methods
|
||||
|
@ -40,7 +52,16 @@ export function Store(methods = []) {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Extends the store with additional methods */
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Extend the store with additional methods
|
||||
*
|
||||
* @param {function} method - Method to add to the store (variadic)
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
Store.prototype.extend = function (...methods) {
|
||||
for (const [path, method] of methods) {
|
||||
if (avoid.indexOf(method[0]) !== -1) {
|
||||
|
@ -54,29 +75,29 @@ Store.prototype.extend = function (...methods) {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Set key at path to value */
|
||||
Store.prototype.set = function (path, value) {
|
||||
if (typeof value === 'undefined') {
|
||||
this.log.warning(`Store.set(value) on key \`${path}\`, but value is undefined`)
|
||||
/**
|
||||
* Retrieve a key from the store
|
||||
*
|
||||
* @param {string|array} path - Path to the key
|
||||
* @param {mixed} dflt - Default method to return if key is undefined
|
||||
* @return {mixed} value - The value stored under key
|
||||
*/
|
||||
Store.prototype.get = function (path, dflt) {
|
||||
const val = get(this, path, dflt)
|
||||
if (typeof val === 'undefined') {
|
||||
this.log.warning(`Store.get(key) on key \`${path}\`, which is undefined`)
|
||||
}
|
||||
set(this, path, value)
|
||||
|
||||
return this
|
||||
return val
|
||||
}
|
||||
|
||||
/** Set key at path to value, but only if it's not currently set */
|
||||
Store.prototype.setIfUnset = function (path, value) {
|
||||
if (typeof value === 'undefined') {
|
||||
this.log.warning(`Store.setIfUnset(value) on key \`${path}\`, but value is undefined`)
|
||||
}
|
||||
if (typeof get(this, path) === 'undefined') {
|
||||
return set(this, path, value)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Adds a value to an array stored under path */
|
||||
/**
|
||||
* Adds a value to an array stored under path
|
||||
*
|
||||
* @param {string|array} path - Path to the key
|
||||
* @param {mixed} values - One or more values to add (variadic)
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
Store.prototype.push = function (path, ...values) {
|
||||
const arr = get(this, path)
|
||||
if (Array.isArray(arr)) {
|
||||
|
@ -88,19 +109,49 @@ Store.prototype.push = function (path, ...values) {
|
|||
return this
|
||||
}
|
||||
|
||||
/** Remove the key at path */
|
||||
/**
|
||||
* Set key at path to value
|
||||
*
|
||||
* @param {string|array} path - Path to the key
|
||||
* @param {mixed} value - The value to set
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
Store.prototype.set = function (path, value) {
|
||||
if (typeof value === 'undefined') {
|
||||
this.log.warning(`Store.set(value) on key \`${path}\`, but value is undefined`)
|
||||
}
|
||||
set(this, path, value)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key at path to value, but only if it's not currently set
|
||||
*
|
||||
* @param {string|array} path - Path to the key
|
||||
* @param {mixed} value - The value to set
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
Store.prototype.setIfUnset = function (path, value) {
|
||||
if (typeof value === 'undefined') {
|
||||
this.log.warning(`Store.setIfUnset(value) on key \`${path}\`, but value is undefined`)
|
||||
}
|
||||
if (typeof get(this, path) === 'undefined') {
|
||||
return set(this, path, value)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the key at path
|
||||
*
|
||||
* @param {string|array} path - Path to the key
|
||||
* @param {mixed} value - The value to set
|
||||
* @return {Store} this - The Store instance
|
||||
*/
|
||||
Store.prototype.unset = function (path) {
|
||||
unset(this, path)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Retrieve a key */
|
||||
Store.prototype.get = function (path, dflt) {
|
||||
const val = get(this, path, dflt)
|
||||
if (typeof val === 'undefined') {
|
||||
this.log.warning(`Store.get(key) on key \`${path}\`, which is undefined`)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
import { Attributes } from './attributes.mjs'
|
||||
import { addNonEnumProp, round } from './utils.mjs'
|
||||
import { __addNonEnumProp, round } from './utils.mjs'
|
||||
import { version } from '../data.mjs'
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// CONSTRUCTOR //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Constructor for an Svg
|
||||
*
|
||||
* @constructor
|
||||
* @param {Patern} pattern - The Pattern object to render
|
||||
* @return {Svg} this - The Path instance
|
||||
*/
|
||||
export function Svg(pattern) {
|
||||
// Non-enumerable properties
|
||||
addNonEnumProp(this, 'openGroups', [])
|
||||
addNonEnumProp(this, 'layout', {})
|
||||
addNonEnumProp(this, 'freeId', 0)
|
||||
addNonEnumProp(this, 'body', '')
|
||||
addNonEnumProp(this, 'style', '')
|
||||
addNonEnumProp(this, 'defs', '')
|
||||
addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
|
||||
__addNonEnumProp(this, 'openGroups', [])
|
||||
__addNonEnumProp(this, 'layout', {})
|
||||
__addNonEnumProp(this, 'freeId', 0)
|
||||
__addNonEnumProp(this, 'body', '')
|
||||
__addNonEnumProp(this, 'style', '')
|
||||
__addNonEnumProp(this, 'defs', '')
|
||||
__addNonEnumProp(this, 'prefix', '<?xml version="1.0" encoding="UTF-8" standalone="no"?>')
|
||||
|
||||
// Enumerable properties
|
||||
this.pattern = pattern // Needed to expose pattern to hooks
|
||||
|
@ -23,18 +34,107 @@ export function Svg(pattern) {
|
|||
this.attributes.add('freesewing', version)
|
||||
}
|
||||
|
||||
Svg.prototype.runHooks = function (hookName, data = false) {
|
||||
if (data === false) data = this
|
||||
let hooks = this.hooks[hookName]
|
||||
if (hooks.length > 0) {
|
||||
for (let hook of hooks) {
|
||||
hook.method(data, hook.data)
|
||||
//////////////////////////////////////////////
|
||||
// PUBLIC METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Renders a drafted Pattern as SVG
|
||||
*
|
||||
* @param {Pattern} pattern - The pattern to render
|
||||
* @return {string} svg - The rendered SVG output
|
||||
*/
|
||||
Svg.prototype.render = function (pattern) {
|
||||
this.idPrefix = pattern.settings.idPrefix
|
||||
this.__runHooks('preRender')
|
||||
pattern.__runHooks('postLayout')
|
||||
if (!pattern.settings.embed) {
|
||||
this.attributes.add('width', round(pattern.width) + 'mm')
|
||||
this.attributes.add('height', round(pattern.height) + 'mm')
|
||||
}
|
||||
this.attributes.add('viewBox', `0 0 ${pattern.width} ${pattern.height}`)
|
||||
this.head = this.__renderHead()
|
||||
this.tail = this.__renderTail()
|
||||
this.svg = ''
|
||||
this.layout = {} // Reset layout
|
||||
for (let partId in pattern.parts) {
|
||||
let part = pattern.parts[partId]
|
||||
if (part.render) {
|
||||
let partSvg = this.__renderPart(part)
|
||||
this.layout[partId] = {
|
||||
svg: partSvg,
|
||||
transform: part.attributes.getAsArray('transform'),
|
||||
}
|
||||
this.svg += this.__openGroup(`${this.idPrefix}part-${partId}`, part.attributes)
|
||||
this.svg += partSvg
|
||||
this.svg += this.__closeGroup()
|
||||
}
|
||||
}
|
||||
this.svg = this.prefix + this.__renderSvgTag() + this.head + this.svg + this.tail
|
||||
this.__runHooks('postRender')
|
||||
|
||||
return this.svg
|
||||
}
|
||||
|
||||
/** Runs insertText hooks */
|
||||
Svg.prototype.insertText = function (text) {
|
||||
//////////////////////////////////////////////
|
||||
// PRIVATE METHODS //
|
||||
//////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns SVG markup to close a group
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup to open a group
|
||||
*/
|
||||
Svg.prototype.__closeGroup = function () {
|
||||
this.__outdent()
|
||||
|
||||
return `${this.__nl()}</g>${this.__nl()}<!-- end of group #${this.openGroups.pop()} -->`
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes text for SVG output
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text to escape
|
||||
* @return {string} escaped - The escaped text
|
||||
*/
|
||||
Svg.prototype.__escapeText = function (text) {
|
||||
return text.replace(/"/g, '“')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returs an unused ID
|
||||
*
|
||||
* @private
|
||||
* @return {numer} id - The next free ID
|
||||
*/
|
||||
Svg.prototype.__getId = function () {
|
||||
this.freeId += 1
|
||||
|
||||
return '' + this.freeId
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases indentation by 1
|
||||
*
|
||||
* @private
|
||||
* @return {Svg} this - The Svg instance
|
||||
*/
|
||||
Svg.prototype.__indent = function () {
|
||||
this.tabs += 1
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the insertText lifecycle hook(s)
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text to insert
|
||||
* @return {Svg} this - The Svg instance
|
||||
*/
|
||||
Svg.prototype.__insertText = function (text) {
|
||||
if (this.hooks.insertText.length > 0) {
|
||||
for (let hook of this.hooks.insertText)
|
||||
text = hook.method(this.pattern.settings.locale, text, hook.data)
|
||||
|
@ -43,198 +143,188 @@ Svg.prototype.insertText = function (text) {
|
|||
return text
|
||||
}
|
||||
|
||||
/** Renders a draft object as SVG */
|
||||
Svg.prototype.render = function (pattern) {
|
||||
this.idPrefix = pattern.settings.idPrefix
|
||||
this.runHooks('preRender')
|
||||
pattern.runHooks('postLayout')
|
||||
if (!pattern.settings.embed) {
|
||||
this.attributes.add('width', round(pattern.width) + 'mm')
|
||||
this.attributes.add('height', round(pattern.height) + 'mm')
|
||||
}
|
||||
this.attributes.add('viewBox', `0 0 ${pattern.width} ${pattern.height}`)
|
||||
this.head = this.renderHead()
|
||||
this.tail = this.renderTail()
|
||||
this.svg = ''
|
||||
this.layout = {} // Reset layout
|
||||
for (let partId in pattern.parts) {
|
||||
let part = pattern.parts[partId]
|
||||
if (part.render) {
|
||||
let partSvg = this.renderPart(part)
|
||||
this.layout[partId] = {
|
||||
svg: partSvg,
|
||||
transform: part.attributes.getAsArray('transform'),
|
||||
}
|
||||
this.svg += this.openGroup(`${this.idPrefix}part-${partId}`, part.attributes)
|
||||
this.svg += partSvg
|
||||
this.svg += this.closeGroup()
|
||||
}
|
||||
}
|
||||
this.svg = this.prefix + this.renderSvgTag() + this.head + this.svg + this.tail
|
||||
this.runHooks('postRender')
|
||||
|
||||
return this.svg
|
||||
/**
|
||||
* Returns SVG markup for a linebreak + indentation
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The Svg markup for a linebreak + indentation
|
||||
*/
|
||||
Svg.prototype.__nl = function () {
|
||||
return '\n' + this.__tab()
|
||||
}
|
||||
|
||||
/** Renders SVG head section */
|
||||
Svg.prototype.renderHead = function () {
|
||||
let svg = this.renderStyle()
|
||||
svg += this.renderScript()
|
||||
svg += this.renderDefs()
|
||||
svg += this.openGroup(this.idPrefix + 'container')
|
||||
/**
|
||||
* Decreases indentation by 1
|
||||
*
|
||||
* @private
|
||||
* @return {Svg} this - The Svg instance
|
||||
*/
|
||||
Svg.prototype.__outdent = function () {
|
||||
this.tabs -= 1
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SVG markup to open a group
|
||||
*
|
||||
* @private
|
||||
* @param {text} id - The group id
|
||||
* @param {Attributes} attributes - Any other attributes for the group
|
||||
* @return {string} svg - The SVG markup to open a group
|
||||
*/
|
||||
Svg.prototype.__openGroup = function (id, attributes = false) {
|
||||
let svg = this.__nl() + this.__nl()
|
||||
svg += `<!-- Start of group #${id} -->`
|
||||
svg += this.__nl()
|
||||
svg += `<g id="${id}"`
|
||||
if (attributes) svg += ` ${attributes.render()}`
|
||||
svg += '>'
|
||||
this.__indent()
|
||||
this.openGroups.push(id)
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Renders SVG closing section */
|
||||
Svg.prototype.renderTail = function () {
|
||||
let svg = ''
|
||||
svg += this.closeGroup()
|
||||
svg += this.nl() + '</svg>'
|
||||
|
||||
return svg
|
||||
/**
|
||||
* Returns SVG markup for a circle
|
||||
*
|
||||
* @private
|
||||
* @param {Point} point - The Point instance that holds the circle data
|
||||
* @return {string} svg - The SVG markup for the circle
|
||||
*/
|
||||
Svg.prototype.__renderCircle = function (point) {
|
||||
return `<circle cx="${round(point.x)}" cy="${round(point.y)}" r="${point.attributes.get(
|
||||
'data-circle'
|
||||
)}" ${point.attributes.renderIfPrefixIs('data-circle-')}></circle>`
|
||||
}
|
||||
|
||||
/** Returns SVG code for the opening SVG tag */
|
||||
Svg.prototype.renderSvgTag = function () {
|
||||
let svg = '<svg'
|
||||
this.indent()
|
||||
svg += this.nl() + this.attributes.render()
|
||||
this.outdent()
|
||||
svg += this.nl() + '>' + this.nl()
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code for the style block */
|
||||
Svg.prototype.renderStyle = function () {
|
||||
let svg = '<style type="text/css"> <![CDATA[ '
|
||||
this.indent()
|
||||
svg += this.nl() + this.style
|
||||
this.outdent()
|
||||
svg += this.nl() + ']]>' + this.nl() + '</style>' + this.nl()
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code for the script block */
|
||||
Svg.prototype.renderScript = function () {
|
||||
let svg = '<script type="text/javascript"> <![CDATA['
|
||||
this.indent()
|
||||
svg += this.nl() + this.script
|
||||
this.outdent()
|
||||
svg += this.nl() + ']]>' + this.nl() + '</script>' + this.nl()
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code for the defs block */
|
||||
Svg.prototype.renderDefs = function () {
|
||||
/**
|
||||
* Returns SVG markup for the defs block
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the defs block
|
||||
*/
|
||||
Svg.prototype.__renderDefs = function () {
|
||||
let svg = '<defs>'
|
||||
this.indent()
|
||||
svg += this.nl() + this.defs
|
||||
this.outdent()
|
||||
svg += this.nl() + '</defs>' + this.nl()
|
||||
this.__indent()
|
||||
svg += this.__nl() + this.defs
|
||||
this.__outdent()
|
||||
svg += this.__nl() + '</defs>' + this.__nl()
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code for a Part object */
|
||||
Svg.prototype.renderPart = function (part) {
|
||||
let svg = ''
|
||||
for (let key in part.paths) {
|
||||
let path = part.paths[key]
|
||||
if (path.render) svg += this.renderPath(path)
|
||||
}
|
||||
for (let key in part.points) {
|
||||
if (part.points[key].attributes.get('data-text')) {
|
||||
svg += this.renderText(part.points[key])
|
||||
}
|
||||
if (part.points[key].attributes.get('data-circle')) {
|
||||
svg += this.renderCircle(part.points[key])
|
||||
}
|
||||
}
|
||||
for (let key in part.snippets) {
|
||||
let snippet = part.snippets[key]
|
||||
svg += this.renderSnippet(snippet, part)
|
||||
}
|
||||
/**
|
||||
* Returns SVG markup for the head section
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the head section
|
||||
*/
|
||||
Svg.prototype.__renderHead = function () {
|
||||
let svg = this.__renderStyle()
|
||||
svg += this.__renderScript()
|
||||
svg += this.__renderDefs()
|
||||
svg += this.__openGroup(this.idPrefix + 'container')
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code for a Path object */
|
||||
Svg.prototype.renderPath = function (path) {
|
||||
if (!path.attributes.get('id')) path.attributes.add('id', this.idPrefix + this.getId())
|
||||
/**
|
||||
* Returns SVG markup for a Path object
|
||||
*
|
||||
* @private
|
||||
* @param {Path} part - The Path instance to render
|
||||
* @return {string} svg - The SVG markup for the Path object
|
||||
*/
|
||||
Svg.prototype.__renderPath = function (path) {
|
||||
if (!path.attributes.get('id')) path.attributes.add('id', this.idPrefix + this.__getId())
|
||||
path.attributes.set('d', path.asPathstring())
|
||||
|
||||
return `${this.nl()}<path ${path.attributes.render()} />${this.renderPathText(path)}`
|
||||
return `${this.__nl()}<path ${path.attributes.render()} />${this.__renderPathText(path)}`
|
||||
}
|
||||
|
||||
Svg.prototype.renderPathText = function (path) {
|
||||
/**
|
||||
* Returns SVG markup for the text on a Path object
|
||||
*
|
||||
* @private
|
||||
* @param {Path} path - The Path instance that holds the text render
|
||||
* @return {string} svg - The SVG markup for the text on a Path object
|
||||
*/
|
||||
Svg.prototype.__renderPathText = function (path) {
|
||||
let text = path.attributes.get('data-text')
|
||||
if (!text) return ''
|
||||
else this.text = this.insertText(text)
|
||||
else this.text = this.__insertText(text)
|
||||
let attributes = path.attributes.renderIfPrefixIs('data-text-')
|
||||
// Sadly aligning text along a patch can't be done in CSS only
|
||||
let offset = ''
|
||||
let align = path.attributes.get('data-text-class')
|
||||
if (align && align.indexOf('center') > -1) offset = ' startOffset="50%" '
|
||||
else if (align && align.indexOf('right') > -1) offset = ' startOffset="100%" '
|
||||
let svg = this.nl() + '<text>'
|
||||
this.indent()
|
||||
let svg = this.__nl() + '<text>'
|
||||
this.__indent()
|
||||
svg += `<textPath xlink:href="#${path.attributes.get(
|
||||
'id'
|
||||
)}" ${offset}><tspan ${attributes}>${this.escapeText(this.text)}</tspan></textPath>`
|
||||
this.outdent()
|
||||
svg += this.nl() + '</text>'
|
||||
)}" ${offset}><tspan ${attributes}>${this.__escapeText(this.text)}</tspan></textPath>`
|
||||
this.__outdent()
|
||||
svg += this.__nl() + '</text>'
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
Svg.prototype.renderText = function (point) {
|
||||
let text = point.attributes.getAsArray('data-text')
|
||||
if (text !== false) {
|
||||
let joint = ''
|
||||
for (let string of text) {
|
||||
this.text = this.insertText(string)
|
||||
joint += this.text + ' '
|
||||
}
|
||||
this.text = this.insertText(joint)
|
||||
/**
|
||||
* Returns SVG markup for a Part object
|
||||
*
|
||||
* @private
|
||||
* @param {Part} part - The Part instance to render
|
||||
* @return {string} svg - The SVG markup for the Part object
|
||||
*/
|
||||
Svg.prototype.__renderPart = function (part) {
|
||||
let svg = ''
|
||||
for (let key in part.paths) {
|
||||
let path = part.paths[key]
|
||||
if (path.render) svg += this.__renderPath(path)
|
||||
}
|
||||
point.attributes.set('data-text-x', round(point.x))
|
||||
point.attributes.set('data-text-y', round(point.y))
|
||||
let lineHeight =
|
||||
point.attributes.get('data-text-lineheight') || 6 * (this.pattern.settings.scale || 1)
|
||||
point.attributes.remove('data-text-lineheight')
|
||||
let svg = `${this.nl()}<text ${point.attributes.renderIfPrefixIs('data-text-')}>`
|
||||
this.indent()
|
||||
// Multi-line text?
|
||||
if (this.text.indexOf('\n') !== -1) {
|
||||
let lines = this.text.split('\n')
|
||||
svg += `<tspan>${lines.shift()}</tspan>`
|
||||
for (let line of lines) {
|
||||
svg += `<tspan x="${round(point.x)}" dy="${lineHeight}">${line}</tspan>`
|
||||
for (let key in part.points) {
|
||||
if (part.points[key].attributes.get('data-text')) {
|
||||
svg += this.__renderText(part.points[key])
|
||||
}
|
||||
if (part.points[key].attributes.get('data-circle')) {
|
||||
svg += this.__renderCircle(part.points[key])
|
||||
}
|
||||
} else {
|
||||
svg += `<tspan>${this.escapeText(this.text)}</tspan>`
|
||||
}
|
||||
this.outdent()
|
||||
svg += this.nl() + '</text>'
|
||||
for (let key in part.snippets) {
|
||||
let snippet = part.snippets[key]
|
||||
svg += this.__renderSnippet(snippet, part)
|
||||
}
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
Svg.prototype.escapeText = function (text) {
|
||||
return text.replace(/"/g, '“')
|
||||
/**
|
||||
* Returns SVG markup for the script block
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the script block
|
||||
*/
|
||||
Svg.prototype.__renderScript = function () {
|
||||
let svg = '<script type="text/javascript"> <![CDATA['
|
||||
this.__indent()
|
||||
svg += this.__nl() + this.script
|
||||
this.__outdent()
|
||||
svg += this.__nl() + ']]>' + this.__nl() + '</script>' + this.__nl()
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
Svg.prototype.renderCircle = function (point) {
|
||||
return `<circle cx="${round(point.x)}" cy="${round(point.y)}" r="${point.attributes.get(
|
||||
'data-circle'
|
||||
)}" ${point.attributes.renderIfPrefixIs('data-circle-')}></circle>`
|
||||
}
|
||||
|
||||
/** Returns SVG code for a snippet */
|
||||
Svg.prototype.renderSnippet = function (snippet) {
|
||||
/**
|
||||
* Returns SVG markup for a snippet
|
||||
*
|
||||
* @private
|
||||
* @param {Snippet} snippet - The Snippet instance to render
|
||||
* @return {string} svg - The SVG markup for the snippet
|
||||
*/
|
||||
Svg.prototype.__renderSnippet = function (snippet) {
|
||||
let x = round(snippet.anchor.x)
|
||||
let y = round(snippet.anchor.y)
|
||||
let scale = snippet.attributes.get('data-scale') || 1
|
||||
|
@ -248,7 +338,7 @@ Svg.prototype.renderSnippet = function (snippet) {
|
|||
if (rotate) {
|
||||
snippet.attributes.add('transform', `rotate(${rotate}, ${x}, ${y})`)
|
||||
}
|
||||
let svg = this.nl()
|
||||
let svg = this.__nl()
|
||||
svg += `<use x="${x}" y="${y}" `
|
||||
svg += `xlink:href="#${snippet.def}" ${snippet.attributes.render()}>`
|
||||
svg += '</use>'
|
||||
|
@ -256,34 +346,116 @@ Svg.prototype.renderSnippet = function (snippet) {
|
|||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code to open a group */
|
||||
Svg.prototype.openGroup = function (id, attributes = false) {
|
||||
let svg = this.nl() + this.nl()
|
||||
svg += `<!-- Start of group #${id} -->`
|
||||
svg += this.nl()
|
||||
svg += `<g id="${id}"`
|
||||
if (attributes) svg += ` ${attributes.render()}`
|
||||
svg += '>'
|
||||
this.indent()
|
||||
this.openGroups.push(id)
|
||||
/**
|
||||
* Returns SVG markup for the style block
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the style block
|
||||
*/
|
||||
Svg.prototype.__renderStyle = function () {
|
||||
let svg = '<style type="text/css"> <![CDATA[ '
|
||||
this.__indent()
|
||||
svg += this.__nl() + this.style
|
||||
this.__outdent()
|
||||
svg += this.__nl() + ']]>' + this.__nl() + '</style>' + this.__nl()
|
||||
return svg
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SVG markup for the opening SVG tag
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the SVG tag
|
||||
*/
|
||||
Svg.prototype.__renderSvgTag = function () {
|
||||
let svg = '<svg'
|
||||
this.__indent()
|
||||
svg += this.__nl() + this.attributes.render()
|
||||
this.__outdent()
|
||||
svg += this.__nl() + '>' + this.__nl()
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns SVG code to close a group */
|
||||
Svg.prototype.closeGroup = function () {
|
||||
this.outdent()
|
||||
/**
|
||||
* Returns SVG markup for the closing section
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the closing section
|
||||
*/
|
||||
Svg.prototype.__renderTail = function () {
|
||||
let svg = ''
|
||||
svg += this.__closeGroup()
|
||||
svg += this.__nl() + '</svg>'
|
||||
|
||||
return `${this.nl()}</g>${this.nl()}<!-- end of group #${this.openGroups.pop()} -->`
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns a linebreak + identation */
|
||||
Svg.prototype.nl = function () {
|
||||
return '\n' + this.tab()
|
||||
/**
|
||||
* Returns SVG markup for text
|
||||
*
|
||||
* @private
|
||||
* @param {Point} point - The Point instance that holds the text render
|
||||
* @return {string} svg - The SVG markup for text
|
||||
*/
|
||||
Svg.prototype.__renderText = function (point) {
|
||||
let text = point.attributes.getAsArray('data-text')
|
||||
if (text !== false) {
|
||||
let joint = ''
|
||||
for (let string of text) {
|
||||
this.text = this.__insertText(string)
|
||||
joint += this.text + ' '
|
||||
}
|
||||
this.text = this.__insertText(joint)
|
||||
}
|
||||
point.attributes.set('data-text-x', round(point.x))
|
||||
point.attributes.set('data-text-y', round(point.y))
|
||||
let lineHeight =
|
||||
point.attributes.get('data-text-lineheight') || 6 * (this.pattern.settings.scale || 1)
|
||||
point.attributes.remove('data-text-lineheight')
|
||||
let svg = `${this.__nl()}<text ${point.attributes.renderIfPrefixIs('data-text-')}>`
|
||||
this.__indent()
|
||||
// Multi-line text?
|
||||
if (this.text.indexOf('\n') !== -1) {
|
||||
let lines = this.text.split('\n')
|
||||
svg += `<tspan>${lines.shift()}</tspan>`
|
||||
for (let line of lines) {
|
||||
svg += `<tspan x="${round(point.x)}" dy="${lineHeight}">${line}</tspan>`
|
||||
}
|
||||
} else {
|
||||
svg += `<tspan>${this.__escapeText(this.text)}</tspan>`
|
||||
}
|
||||
this.__outdent()
|
||||
svg += this.__nl() + '</text>'
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
/** Returns indentation */
|
||||
Svg.prototype.tab = function () {
|
||||
/**
|
||||
* Runs SVG lifecycle hooks
|
||||
*
|
||||
* @private
|
||||
* @param {string} hookName - The lifecycle hook to run
|
||||
* @param {mixed} data - Any data to pass to the hook method
|
||||
* @return {string} svg - The SVG markup for the indentation
|
||||
*/
|
||||
Svg.prototype.__runHooks = function (hookName, data = false) {
|
||||
if (data === false) data = this
|
||||
let hooks = this.hooks[hookName]
|
||||
if (hooks.length > 0) {
|
||||
for (let hook of hooks) {
|
||||
hook.method(data, hook.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SVG markup for indentation
|
||||
*
|
||||
* @private
|
||||
* @return {string} svg - The SVG markup for the indentation
|
||||
*/
|
||||
Svg.prototype.__tab = function () {
|
||||
let space = ''
|
||||
for (let i = 0; i < this.tabs; i++) {
|
||||
space += ' '
|
||||
|
@ -291,20 +463,3 @@ Svg.prototype.tab = function () {
|
|||
|
||||
return space
|
||||
}
|
||||
|
||||
/** Increases indentation by 1 */
|
||||
Svg.prototype.indent = function () {
|
||||
this.tabs += 1
|
||||
}
|
||||
|
||||
/** Decreases indentation by 1 */
|
||||
Svg.prototype.outdent = function () {
|
||||
this.tabs -= 1
|
||||
}
|
||||
|
||||
/** Returns an unused ID */
|
||||
Svg.prototype.getId = function () {
|
||||
this.freeId += 1
|
||||
|
||||
return '' + this.freeId
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue