diff --git a/packages/core/src/attributes.mjs b/packages/core/src/attributes.mjs
index f04a1211b67..211c7b28fe6 100644
--- a/packages/core/src/attributes.mjs
+++ b/packages/core/src/attributes.mjs
@@ -35,7 +35,7 @@ Attributes.prototype.add = function (name, value) {
}
/**
- * Return a props object for attributes with a fiven prefix (typically used for data-text)
+ * Return a props object for attributes with a given prefix (typically used for data-text)
*
* @param {string} prefix - The prefix to filter attributes on
* @return {object} props - The attributes as props
diff --git a/packages/core/src/defs.mjs b/packages/core/src/defs.mjs
new file mode 100644
index 00000000000..e12ef530029
--- /dev/null
+++ b/packages/core/src/defs.mjs
@@ -0,0 +1,93 @@
+//////////////////////////////////////////////
+// CONSTRUCTOR //
+//////////////////////////////////////////////
+
+/**
+ * Constructor for Defs
+ *
+ * @constructor
+ * @return {Defs} this - The Defs instance
+ */
+export function Defs() {
+ this.list = {}
+
+ return this
+}
+
+//////////////////////////////////////////////
+// PUBLIC METHODS //
+//////////////////////////////////////////////
+
+/**
+ * Return a deep copy of this
+ *
+ * @return {object} this - The Defs instance
+ */
+Defs.prototype.clone = function () {
+ let clone = new Defs()
+ clone.list = JSON.parse(JSON.stringify(this.list))
+
+ return clone
+}
+
+/**
+ * Retrieve a def
+ *
+ * @param {string} name - Name of the def to get
+ * @return value - The value under name
+ */
+Defs.prototype.get = function (name) {
+ if (typeof this.list[name] === 'undefined') return false
+ else return this.list[name]
+}
+
+/**
+ * Remove a def
+ *
+ * @param {string} name - Name of the def to set
+ * @return {object} this - The Defs instance
+ */
+Defs.prototype.remove = function (name) {
+ delete this.list[name]
+
+ return this
+}
+
+/**
+ * Return SVG code for Defs
+ *
+ * @return {string} svg - The SVG code
+ */
+Defs.prototype.render = function () {
+ let svg = ''
+ for (let key in this.list) {
+ svg += ` ${key}="${this.list[key]}"`
+ }
+
+ return svg
+}
+
+/**
+ * Set a def, overwriting existing value
+ *
+ * @param {string} name - Name of the def to set
+ * @param {string} value - Value of the def to set
+ * @return {Defs} this - The Defs instance
+ */
+Defs.prototype.set = function (name, value) {
+ this.list[name] = value
+
+ return this
+}
+
+/**
+ * Sets a def, but only if it's not currently set
+ *
+ * @param {string} name - Name of the def to set
+ * @param {string} value - Value of the def to set
+ * @return {Defs} this - The Defs instance
+ */
+Defs.prototype.setIfUnset = function (name, value) {
+ if (typeof this.list[name] === 'undefined') this.list[name] = value
+ return this
+}
diff --git a/packages/core/src/svg.mjs b/packages/core/src/svg.mjs
index d77412ce6ac..0dd58f963ea 100644
--- a/packages/core/src/svg.mjs
+++ b/packages/core/src/svg.mjs
@@ -1,4 +1,5 @@
import { Attributes } from './attributes.mjs'
+import { Defs } from './defs.mjs'
import { __addNonEnumProp, round } from './utils.mjs'
import { version } from '../data.mjs'
@@ -31,7 +32,7 @@ export function Svg(pattern) {
this.layout = {}
this.body = ''
this.style = ''
- this.defs = ''
+ this.defs = new Defs()
}
//////////////////////////////////////////////
@@ -215,7 +216,7 @@ Svg.prototype.__renderCircle = function (point) {
Svg.prototype.__renderDefs = function () {
let svg = ''
this.__indent()
- svg += this.__nl() + this.defs
+ svg += this.__nl() + this.defs.render()
this.__outdent()
svg += this.__nl() + '' + this.__nl()
diff --git a/packages/core/tests/defs.test.mjs b/packages/core/tests/defs.test.mjs
new file mode 100644
index 00000000000..f603c600f80
--- /dev/null
+++ b/packages/core/tests/defs.test.mjs
@@ -0,0 +1,38 @@
+import chai from 'chai'
+import { Defs } from '../src/defs.mjs'
+
+const expect = chai.expect
+
+describe('Defs', () => {
+ let defs = new Defs()
+
+ it('Should set a def', () => {
+ defs.set('test', 'passed')
+ expect(defs.get('test')).to.equal('passed')
+ })
+
+ it('Should remove a def', () => {
+ defs.remove('test')
+ expect(defs.get('test')).to.equal(false)
+ })
+
+ it('Should only set an unset def', () => {
+ defs.setIfUnset('test', 'passed')
+ expect(defs.get('test')).to.equal('passed')
+ defs.setIfUnset('test', 'failed')
+ expect(defs.get('test')).to.equal('passed')
+ })
+
+ it('Should return false when getting an unset def', () => {
+ expect(defs.get('doNotTest')).to.equal(false)
+ })
+
+ it('Should render defs correctly', () => {
+ console.log(defs.render())
+ expect(defs.render()).to.equal(' test="passed"')
+ })
+
+ it('Should be able to clone itself', () => {
+ expect(defs.clone().get('test')).to.equal('passed')
+ })
+})
diff --git a/packages/core/tests/svg.test.mjs b/packages/core/tests/svg.test.mjs
index 0306e5767f5..664f6f86434 100644
--- a/packages/core/tests/svg.test.mjs
+++ b/packages/core/tests/svg.test.mjs
@@ -2,6 +2,7 @@ import chai from 'chai'
import chaiString from 'chai-string'
import { Svg } from '../src/svg.mjs'
import { Design, Attributes } from '../src/index.mjs'
+import { Defs } from '../src/defs.mjs'
import { version } from '../data.mjs'
import render from './fixtures/render.mjs'
@@ -42,7 +43,7 @@ describe('Svg', () => {
expect(svg.freeId).to.equal(0)
expect(svg.body).to.equal('')
expect(svg.style).to.equal('')
- expect(svg.defs).to.equal('')
+ expect(svg.defs).to.be.an.instanceof(Defs)
expect(svg.prefix).to.equal('')
expect(svg.attributes.get('xmlns')).to.equal('http://www.w3.org/2000/svg')
expect(svg.attributes.get('xmlns:svg')).to.equal('http://www.w3.org/2000/svg')
diff --git a/plugins/plugin-annotations/src/buttons.mjs b/plugins/plugin-annotations/src/buttons.mjs
index e49f78c36c4..cf870fbdd4d 100644
--- a/plugins/plugin-annotations/src/buttons.mjs
+++ b/plugins/plugin-annotations/src/buttons.mjs
@@ -1,6 +1,8 @@
-const defs = [
- // button
- `
+// Export defs
+export const buttonsDefs = [
+ {
+ name: 'button',
+ def: `
`,
- // buttonhole
- `
+ },
+ {
+ name: 'buttonhole',
+ def: `
-
+`,
+ },
+ {
+ name: 'buttonhole-start',
+ def: `
-
+`,
+ },
+ {
+ name: 'buttonhole-end',
+ def: `
`,
- // snaps
- `
+ },
+ {
+ name: 'snap-stud-grad',
+ def: `
-
+`,
+ },
+ {
+ name: 'snap-stud',
+ def: `
-
+`,
+ },
+ {
+ name: 'snap-socket',
+ def: `
`,
+ },
]
-
-// Export hooks
-export const buttonsHooks = {
- preRender: [
- function (svg) {
- for (const def of defs) {
- if (svg.defs.indexOf(def) === -1) svg.defs += def
- }
- },
- ],
-}
diff --git a/plugins/plugin-annotations/src/cutonfold.mjs b/plugins/plugin-annotations/src/cutonfold.mjs
index 3cc29d6a1c1..17f2ca08c77 100644
--- a/plugins/plugin-annotations/src/cutonfold.mjs
+++ b/plugins/plugin-annotations/src/cutonfold.mjs
@@ -1,20 +1,21 @@
-const markers = `
+// Export defs
+export const cutonfoldDefs = [
+ {
+ name: 'cutonfoldFrom',
+ def: `
-
+`,
+ },
+ {
+ name: 'cutonfoldTo',
+ def: `
-
-`
+`,
+ },
+]
-// Export hooks
-export const cutonfoldHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(markers) === -1) svg.defs += markers
- },
- ],
-}
// Export macros
export const cutonfoldMacros = {
cutonfold: function (so, { points, paths, Path, complete, store, scale }) {
diff --git a/plugins/plugin-annotations/src/dimensions.mjs b/plugins/plugin-annotations/src/dimensions.mjs
index 52e777fc371..a4facf35b46 100644
--- a/plugins/plugin-annotations/src/dimensions.mjs
+++ b/plugins/plugin-annotations/src/dimensions.mjs
@@ -1,11 +1,21 @@
-const markers = `
+// Export defs
+export const dimensionsDefs = [
+ {
+ name: 'dimensionFrom',
+ def: `
-
+`,
+ },
+ {
+ name: 'dimensionTo',
+ def: `
-
-`
+`,
+ },
+]
+
const prefix = '__paperless'
function drawDimension(from, to, so, { Path, units }) {
@@ -70,14 +80,7 @@ function lleader(so, type, props, id) {
return point
}
-// Export hooks and macros
-export const dimensionsHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(markers) === -1) svg.defs += markers
- },
- ],
-}
+// Export macros
export const dimensionsMacros = {
// horizontal
hd: function (so, props) {
diff --git a/plugins/plugin-annotations/src/grainline.mjs b/plugins/plugin-annotations/src/grainline.mjs
index 92c90800ecf..be4c57024ee 100644
--- a/plugins/plugin-annotations/src/grainline.mjs
+++ b/plugins/plugin-annotations/src/grainline.mjs
@@ -1,21 +1,24 @@
-const markers = `
+// Export defs
+export const grainlineDefs = [
+ {
+ name: 'grainlineFrom',
+ def: `
-
+`,
+ },
+ {
+ name: 'grainlineTo',
+ def: `
-`
+`,
+ },
+]
const dflts = { text: 'grainline' }
-// Export hooks and macros
-export const grainlineHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(markers) === -1) svg.defs += markers
- },
- ],
-}
+// Export macros
export const grainlineMacros = {
grainline: function (so = {}, { points, paths, Path, complete, store }) {
if (so === false) {
diff --git a/plugins/plugin-annotations/src/index.mjs b/plugins/plugin-annotations/src/index.mjs
index 11cf575af5f..96be11fde67 100644
--- a/plugins/plugin-annotations/src/index.mjs
+++ b/plugins/plugin-annotations/src/index.mjs
@@ -1,8 +1,8 @@
import { name, version } from '../data.mjs'
-// Hooks only
-import { buttonsHooks } from './buttons.mjs'
-import { logoHooks } from './logo.mjs'
-import { notchesHooks } from './notches.mjs'
+// Defs only
+import { buttonsDefs } from './buttons.mjs'
+import { logoDefs } from './logo.mjs'
+import { notchesDefs } from './notches.mjs'
// Macros only
import { bannerMacros } from './banner.mjs'
import { bannerboxMacros } from './bannerbox.mjs'
@@ -11,26 +11,36 @@ import { crossboxMacros } from './crossbox.mjs'
import { cutlistStores, cutlistHooks } from './cutlist.mjs'
import { scaleboxMacros } from './scalebox.mjs'
import { titleMacros } from './title.mjs'
-// Hooks and Macros
-import { cutonfoldMacros, cutonfoldHooks } from './cutonfold.mjs'
-import { dimensionsMacros, dimensionsHooks } from './dimensions.mjs'
-import { grainlineMacros, grainlineHooks } from './grainline.mjs'
-import { pleatMacros, pleatHooks } from './pleat.mjs'
-import { sewtogetherMacros, sewtogetherHooks } from './sewtogether.mjs'
+// Defs and Macros
+import { cutonfoldMacros, cutonfoldDefs } from './cutonfold.mjs'
+import { dimensionsMacros, dimensionsDefs } from './dimensions.mjs'
+import { grainlineMacros, grainlineDefs } from './grainline.mjs'
+import { pleatMacros, pleatDefs } from './pleat.mjs'
+import { sewtogetherMacros, sewtogetherDefs } from './sewtogether.mjs'
export const plugin = {
name,
version,
hooks: {
preRender: [
- ...buttonsHooks.preRender,
- ...logoHooks.preRender,
- ...notchesHooks.preRender,
- ...cutonfoldHooks.preRender,
- ...dimensionsHooks.preRender,
- ...grainlineHooks.preRender,
- ...pleatHooks.preRender,
- ...sewtogetherHooks.preRender,
+ function (svg) {
+ const defs = [
+ ...buttonsDefs,
+ ...cutonfoldDefs,
+ ...dimensionsDefs,
+ ...grainlineDefs,
+ ...logoDefs,
+ ...notchesDefs,
+ ...pleatDefs,
+ ...sewtogetherDefs,
+ ]
+ for (const def of defs) {
+ svg.defs.setIfUnset(
+ def.name,
+ typeof def.def === 'function' ? def.def(svg.pattern.settings[0].scale) : def.def
+ )
+ }
+ },
],
prePartDraft: [...cutlistHooks.prePartDraft],
},
diff --git a/plugins/plugin-annotations/src/logo.mjs b/plugins/plugin-annotations/src/logo.mjs
index 92639ea4fe2..40013a0dd41 100644
--- a/plugins/plugin-annotations/src/logo.mjs
+++ b/plugins/plugin-annotations/src/logo.mjs
@@ -1,11 +1,8 @@
-const logo = (scale) =>
- ``
-
-// Export hooks
-export const logoHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf('id="logo"') === -1) svg.defs += logo(svg.pattern.settings[0].scale)
- },
- ],
-}
+// Export defs
+export const logoDefs = [
+ {
+ name: 'logo',
+ def: (scale) =>
+ ``,
+ },
+]
diff --git a/plugins/plugin-annotations/src/notches.mjs b/plugins/plugin-annotations/src/notches.mjs
index 77764b38aa8..eccb5ed6fb7 100644
--- a/plugins/plugin-annotations/src/notches.mjs
+++ b/plugins/plugin-annotations/src/notches.mjs
@@ -1,18 +1,19 @@
-const markers = `
+// Export hooks
+export const notchesDefs = [
+ {
+ name: 'notch',
+ def: `
-
+`,
+ },
+ {
+ name: 'bnotch',
+ def: `
-`
-
-// Export hooks
-export const notchesHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(`id="notch"`) === -1) svg.defs += markers
- },
- ],
-}
+`,
+ },
+]
diff --git a/plugins/plugin-annotations/src/pleat.mjs b/plugins/plugin-annotations/src/pleat.mjs
index 0e37b1d85c0..3bc0f59c9ac 100644
--- a/plugins/plugin-annotations/src/pleat.mjs
+++ b/plugins/plugin-annotations/src/pleat.mjs
@@ -1,17 +1,14 @@
-const markers = `
+// Export defs
+export const pleatDefs = [
+ {
+ name: 'notch',
+ def: `
-`
-
-// Export hooks
-export const pleatHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(markers) === -1) svg.defs += markers
- },
- ],
-}
+`,
+ },
+]
// Export macros
export const pleatMacros = {
diff --git a/plugins/plugin-annotations/src/sewtogether.mjs b/plugins/plugin-annotations/src/sewtogether.mjs
index 16d2a4aa3f7..6884865e6a7 100644
--- a/plugins/plugin-annotations/src/sewtogether.mjs
+++ b/plugins/plugin-annotations/src/sewtogether.mjs
@@ -1,23 +1,27 @@
-const markers = `
+// Export defs
+export const sewtogetherDefs = [
+ {
+ name: 'sewTogetherStart',
+ def: `
-
+`,
+ },
+ {
+ name: 'sewTogetherEnd',
+ def: `
-
+`,
+ },
+ {
+ name: 'sewTogetherCross',
+ def: `
-
-`
-
-// Export hooks
-export const sewtogetherHooks = {
- preRender: [
- function (svg) {
- if (svg.defs.indexOf(markers) === -1) svg.defs += markers
- },
- ],
-}
+`,
+ },
+]
// Export macros
export const sewtogetherMacros = {
diff --git a/plugins/plugin-annotations/tests/buttons.test.mjs b/plugins/plugin-annotations/tests/buttons.test.mjs
index ea2b28a4db3..3539cc90058 100644
--- a/plugins/plugin-annotations/tests/buttons.test.mjs
+++ b/plugins/plugin-annotations/tests/buttons.test.mjs
@@ -11,7 +11,7 @@ pattern.draft().render()
describe('Buttons Plugin Test', () => {
for (const snippet of ['button', 'buttonhole', 'snap-stud', 'snap-socket']) {
it(`Should add the ${snippet} snippet to defs`, () => {
- expect(pattern.svg.defs.indexOf(``)).to.not.equal(-1)
+ expect(pattern.svg.defs.get(snippet)).to.not.equal(false)
})
}
diff --git a/plugins/plugin-annotations/tests/logo.test.mjs b/plugins/plugin-annotations/tests/logo.test.mjs
index 440a8f3bc9d..3e0c6bc5c09 100644
--- a/plugins/plugin-annotations/tests/logo.test.mjs
+++ b/plugins/plugin-annotations/tests/logo.test.mjs
@@ -9,8 +9,6 @@ describe('Logo Plugin Tests', () => {
const Pattern = new Design()
const pattern = new Pattern().use(annotationsPlugin)
pattern.draft().render()
- expect(pattern.svg.defs).to.contain(
- ' {
- it(`Should add the snippets to defs`, () => {
- expect(pattern.svg.defs).to.contain('')
+ it(`Should add the bnotch to defs`, () => {
+ expect(pattern.svg.defs.get('bnotch')).to.not.equal(false)
})
-
- it(`Should add the notches snippet to defs`, () => {
- expect(pattern.svg.defs.indexOf(``)).to.not.equal(-1)
+ it(`Should add the notch to defs`, () => {
+ expect(pattern.svg.defs.get('notch')).to.not.equal(false)
})
-
it('Draws a notch on an anchor point', () => {
const part = {
name: 'test',
diff --git a/plugins/plugin-theme/src/index.mjs b/plugins/plugin-theme/src/index.mjs
index 0456c45a52d..dd7940ed568 100644
--- a/plugins/plugin-theme/src/index.mjs
+++ b/plugins/plugin-theme/src/index.mjs
@@ -37,8 +37,8 @@ export const plugin = {
}
if (paperless) {
svg.pattern.settings[0].units === 'imperial'
- ? (svg.defs += grid.imperial)
- : (svg.defs += grid.metric)
+ ? svg.defs.setIfUnset('grid', grid.imperial)
+ : svg.defs.setIfUnset('grid', grid.metric)
const parts = svg.pattern.parts[svg.pattern.activeSet]
const skipGrid = data.skipGrid || []
for (const key in parts) {
@@ -48,9 +48,10 @@ export const plugin = {
let anchor = new Point(0, 0)
if (typeof points.gridAnchor !== 'undefined') anchor = part.points.gridAnchor
else if (typeof points.anchor !== 'undefined') anchor = part.points.anchor
- svg.defs += ``
- svg.defs += ''
+ svg.defs.setIfUnset(
+ 'grid_' + key,
+ `'`
+ )
paths[getId()] = new Path()
.move(part.topLeft)
.line(new Point(part.topLeft.x, part.bottomRight.y))
diff --git a/sites/shared/components/workbench/draft/defs.mjs b/sites/shared/components/workbench/draft/defs.mjs
index 07cb9ab5173..92b25ee379a 100644
--- a/sites/shared/components/workbench/draft/defs.mjs
+++ b/sites/shared/components/workbench/draft/defs.mjs
@@ -49,7 +49,7 @@ const grids = {
}
export const Defs = (props) => {
- let defs = props.svg.defs
+ let defs = props.svg.defs.render()
if (props.settings[0].paperless) {
defs += grids[props.settings[0].units || 'metric']
for (let p in props.parts[0]) {