diff --git a/src/attributes.js b/src/attributes.js index f091efbf65a..56963b46e5d 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -1,4 +1,4 @@ -function attributes(init = false) { +function Attributes(init = false) { this.list = {}; if (init) { for (let key in init) { @@ -9,7 +9,7 @@ function attributes(init = false) { } /** Adds an attribute */ -attributes.prototype.add = function(name, value) { +Attributes.prototype.add = function(name, value) { if (typeof this.list[name] === "undefined") { this.list[name] = []; } @@ -19,20 +19,20 @@ attributes.prototype.add = function(name, value) { }; /** Sets an attribute, overwriting existing value */ -attributes.prototype.set = function(name, value) { +Attributes.prototype.set = function(name, value) { this.list[name] = [value]; return this; }; /** Retrieves an attribute */ -attributes.prototype.get = function(name) { +Attributes.prototype.get = function(name) { if (typeof this.list[name] === "undefined") return false; else return this.list[name].join(" "); }; /** Returns SVG code for attributes */ -attributes.prototype.render = function() { +Attributes.prototype.render = function() { let svg = ""; for (let key in this.list) { svg += ` ${key}="${this.list[key].join(" ")}"`; @@ -43,7 +43,7 @@ attributes.prototype.render = function() { /** Returns SVG code for attributes with a fiven prefix * typically used for data-text*/ -attributes.prototype.renderIfPrefixIs = function(prefix = "") { +Attributes.prototype.renderIfPrefixIs = function(prefix = "") { let svg = ""; let prefixLen = prefix.length; for (let key in this.list) { @@ -56,11 +56,11 @@ attributes.prototype.renderIfPrefixIs = function(prefix = "") { }; /** Returns a deep copy of this */ -attributes.prototype.clone = function() { - let clone = new attributes(); +Attributes.prototype.clone = function() { + let clone = new Attributes(); clone.list = JSON.parse(JSON.stringify(this.list)); return clone; }; -export default attributes; +export default Attributes; diff --git a/src/hooks.js b/src/hooks.js index 72ee0d81544..2edd8daaf68 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -1,9 +1,9 @@ -export default function hooks() { +function Hooks() { this._hooks = {}; this.all = ["preRenderSvg", "postRenderSvg", "insertText"]; } -hooks.prototype.list = function(hook) { +Hooks.prototype.list = function(hook) { if (typeof this._hooks[hook] === "undefined") { return false; } @@ -11,9 +11,11 @@ hooks.prototype.list = function(hook) { return this._hooks[hook]; }; -hooks.prototype.attach = function(hook, obj) { +Hooks.prototype.attach = function(hook, obj) { if (typeof this._hooks[hook] === "undefined") return; for (let func of this._hooks[hook]) { obj.pre(hook, func); } }; + +export default Hooks; diff --git a/src/index.js b/src/index.js index 49ad352ae37..cf3a963b435 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,17 @@ -import pattern from "./pattern"; -import point from "./point"; -import path from "./path"; -import snippet from "./snippet"; +import Pattern from "./pattern"; +import Point from "./point"; +import Path from "./path"; +import Snippet from "./snippet"; import * as utils from "./utils"; import { version } from "../package.json"; export default { version: version, - pattern, - point, - path, - snippet, + Pattern, + Point, + Path, + Snippet, utils, patterns: {}, plugins: {} diff --git a/src/option.js b/src/option.js index cd71d7a9ccc..8ffb704f44b 100644 --- a/src/option.js +++ b/src/option.js @@ -1,4 +1,4 @@ -function option(config) { +function Option(config) { this.id = config.id; this.config = config; this.val = config.val; @@ -6,4 +6,4 @@ function option(config) { return this; } -export default option; +export default Option; diff --git a/src/part.js b/src/part.js index d6d9adcae03..7f057b1f838 100644 --- a/src/part.js +++ b/src/part.js @@ -1,13 +1,14 @@ import { macroName } from "./utils"; -import point from "./point"; -import path from "./path"; -import snippet from "./snippet"; -import attributes from "./attributes"; +import Point from "./point"; +import Path from "./path"; +import Snippet from "./snippet"; +import Attributes from "./attributes"; import * as hooklib from "hooks"; -import { round, units } from "./utils"; +import { units } from "./utils"; +import { round } from "./round"; -function part() { - this.attributes = new attributes(); +function Part() { + this.attributes = new Attributes(); this.points = {}; this.paths = {}; this.snippets = {}; @@ -17,13 +18,13 @@ function part() { this.width = false; this.height = false; this.render = true; - this.points.origin = new point(0, 0); + this.points.origin = new Point(0, 0); for (let k in hooklib) this[k] = hooklib[k]; // Constructors so macros can create objects - this.point = point; - this.path = path; - this.snippet = snippet; + this.Point = Point; + this.Path = Path; + this.Snippet = Snippet; // Expose round method to plugins this.round = round; @@ -31,7 +32,7 @@ function part() { return this; } -part.prototype.macroRunner = function(args) { +Part.prototype.macroRunner = function(args) { let self = this; let data = args; let method = function(key, data) { @@ -47,23 +48,23 @@ part.prototype.macroRunner = function(args) { }; /** Returns an unused ID */ -part.prototype.getUid = function() { +Part.prototype.getUid = function() { this.freeId += 1; return "" + this.freeId; }; /** Returns a value formatted for units provided in settings */ -part.prototype.units = function(value) { +Part.prototype.units = function(value) { return units(value, this.context.settings.units); }; /** Calculates the part's bounding box and sets it */ -part.prototype.boundary = function() { +Part.prototype.boundary = function() { if (this.topLeft) return this; // Cached - let topLeft = new point(Infinity, Infinity); - let bottomRight = new point(-Infinity, -Infinity); + let topLeft = new Point(Infinity, Infinity); + let bottomRight = new Point(-Infinity, -Infinity); for (let key in this.paths) { let path = this.paths[key].boundary(); if (path.render) { @@ -76,8 +77,8 @@ part.prototype.boundary = function() { } } // Add 10mm margin - this.topLeft = new point(topLeft.x - 10, topLeft.y - 10); - this.bottomRight = new point(bottomRight.x + 10, bottomRight.y + 10); + this.topLeft = new Point(topLeft.x - 10, topLeft.y - 10); + this.bottomRight = new Point(bottomRight.x + 10, bottomRight.y + 10); this.width = this.bottomRight.x - this.topLeft.x; this.height = this.bottomRight.y - this.topLeft.y; @@ -85,7 +86,7 @@ part.prototype.boundary = function() { }; /** Stacks part so that its top left corner is in (0,0) */ -part.prototype.stack = function() { +Part.prototype.stack = function() { if (this.topLeft.x === 0 && this.topLeft.y === 0) return this; this.boundary().attr( @@ -97,7 +98,7 @@ part.prototype.stack = function() { }; /** Adds an attribute. This is here to make this call chainable in assignment */ -part.prototype.attr = function(name, value, overwrite = false) { +Part.prototype.attr = function(name, value, overwrite = false) { if (overwrite) this.attributes.set(name, value); else this.attributes.add(name, value); @@ -105,7 +106,7 @@ part.prototype.attr = function(name, value, overwrite = false) { }; /** Copies point/path/snippet data from part orig into this */ -part.prototype.copy = function(orig) { +Part.prototype.copy = function(orig) { for (let type of ["points", "paths", "snippets"]) { for (let i in orig[type]) { if (typeof this[type][i] === "undefined") { @@ -117,4 +118,4 @@ part.prototype.copy = function(orig) { return this; }; -export default part; +export default Part; diff --git a/src/path.js b/src/path.js index 4e329cf79b4..8b1298401ac 100644 --- a/src/path.js +++ b/src/path.js @@ -1,46 +1,45 @@ -import attributes from "./attributes"; -import point from "./point"; +import Attributes from "./attributes"; +import Point from "./point"; import Bezier from "bezier-js"; -import { pathOffset, pathLength } from "./utils"; -function path() { +function Path() { this.render = true; this.topLeft = false; this.bottomRight = false; - this.attributes = new attributes(); + this.attributes = new Attributes(); this.ops = []; } /** Adds a move operation to Point to */ -path.prototype.move = function(to) { +Path.prototype.move = function(to) { this.ops.push({ type: "move", to }); return this; }; /** Adds a line operation to Point to */ -path.prototype.line = function(to) { +Path.prototype.line = function(to) { this.ops.push({ type: "line", to }); return this; }; /** Adds a line operation to Point to */ -path.prototype.curve = function(cp1, cp2, to) { +Path.prototype.curve = function(cp1, cp2, to) { this.ops.push({ type: "curve", cp1, cp2, to }); return this; }; /** Adds a close operation */ -path.prototype.close = function() { +Path.prototype.close = function() { this.ops.push({ type: "close" }); return this; }; /** Adds an attribute. This is here to make this call chainable in assignment */ -path.prototype.attr = function(name, value, overwrite = false) { +Path.prototype.attr = function(name, value, overwrite = false) { if (overwrite) this.attributes.set(name, value); else this.attributes.add(name, value); @@ -48,7 +47,7 @@ path.prototype.attr = function(name, value, overwrite = false) { }; /** Returns SVG pathstring for this path */ -path.prototype.asPathstring = function() { +Path.prototype.asPathstring = function() { let d = ""; for (let op of this.ops) { switch (op.type) { @@ -76,12 +75,12 @@ path.prototype.asPathstring = function() { }; /** Returns offset of this path as a new path */ -path.prototype.offset = function(distance) { +Path.prototype.offset = function(distance) { return pathOffset(this, distance); }; /** Returns the length of this path */ -path.prototype.length = function() { +Path.prototype.length = function() { let current, start; let length = 0; for (let i in this.ops) { @@ -107,12 +106,12 @@ path.prototype.length = function() { }; /** Returns the startpoint of the path */ -path.prototype.start = function() { +Path.prototype.start = function() { return this.ops[0].to; }; /** Returns the endpoint of the path */ -path.prototype.end = function() { +Path.prototype.end = function() { let op = this.ops[this.ops.length - 1]; if (op.type === "close") return this.start(); @@ -120,12 +119,12 @@ path.prototype.end = function() { }; /** Finds the bounding box of a path */ -path.prototype.boundary = function() { +Path.prototype.boundary = function() { if (this.topLeft) return this; // Cached let current; - let topLeft = new point(Infinity, Infinity); - let bottomRight = new point(-Infinity, -Infinity); + let topLeft = new Point(Infinity, Infinity); + let bottomRight = new Point(-Infinity, -Infinity); for (let i in this.ops) { let op = this.ops[i]; if (op.type === "move" || op.type === "line") { @@ -155,8 +154,8 @@ path.prototype.boundary = function() { }; /** Returns a deep copy of this */ -path.prototype.clone = function() { - let clone = new path(); +Path.prototype.clone = function() { + let clone = new Path(); clone.render = this.render = true; if (this.topLeft) clone.topLeft = this.topLeft.clone(); else clone.topLeft = false; @@ -179,4 +178,77 @@ path.prototype.clone = function() { return clone; }; -export default path; +/** Offsets a path by distance */ +function pathOffset(path, distance) { + let offset = []; + let current; + let start = false; + let closed = false; + for (let i in path.ops) { + let op = path.ops[i]; + if (op.type === "line") { + offset.push(offsetLine(current, op.to, distance)); + } else if (op.type === "curve") { + let b = new Bezier( + { x: current.x, y: current.y }, + { x: op.cp1.x, y: op.cp1.y }, + { x: op.cp2.x, y: op.cp2.y }, + { x: op.to.x, y: op.to.y } + ); + for (let bezier of b.offset(distance)) { + offset.push(asPath(bezier)); + } + } else if (op.type === "close") { + // offset.push(offsetLine(current, start, distance)); + closed = true; + } + if (op.to) current = op.to; + if (!start) start = current; + } + + return joinPaths(offset, closed); +} + +/** Offsets a line by distance */ +function offsetLine(from, to, distance) { + if (from.x === to.x && from.y === to.y) { + throw "Cannot offset line that starts and ends in the same point"; + } + let angle = from.angle(to) - 90; + + return new Path() + .move(from.shift(angle, distance)) + .line(to.shift(angle, distance)); +} + +/** Converts a bezier-js instance to a path */ +function asPath(bezier) { + return new Path() + .move(new Point(bezier.points[0].x, bezier.points[0].y)) + .curve( + new Point(bezier.points[1].x, bezier.points[1].y), + new Point(bezier.points[2].x, bezier.points[2].y), + new Point(bezier.points[3].x, bezier.points[3].y) + ); +} + +/** Joins path segments together into one path */ +function joinPaths(paths, closed = false) { + let joint = new Path().move(paths[0].ops[0].to); + for (let p of paths) { + for (let op of p.ops) { + if (op.type === "curve") { + joint.curve(op.cp1, op.cp2, op.to); + } else if (op.type !== "close") { + joint.line(op.to); + } else { + throw "Close op not handled"; + } + } + } + if (closed) joint.close(); + + return joint; +} + +export default Path; diff --git a/src/pattern.js b/src/pattern.js index 351d1f3f137..83b3d6250f7 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -1,27 +1,27 @@ -import attributes from "./attributes"; +import Attributes from "./attributes"; import { macroName } from "./utils"; -import part from "./part"; -import point from "./point"; -import path from "./path"; -import snippet from "./snippet"; -import svg from "./svg"; -import hooks from "./hooks"; +import Part from "./part"; +import Point from "./point"; +import Path from "./path"; +import Snippet from "./snippet"; +import Svg from "./svg"; +import Hooks from "./hooks"; import pack from "bin-pack"; -import store from "./store"; +import Store from "./store"; -export default function pattern(config = false) { +export default function Pattern(config = false) { // width and height properties this.width = false; this.height = false; - // Svg and hooks instance - this.svg = new svg(this); - this.hooks = new hooks(); + // Hooks and Svg instance + this.hooks = new Hooks(); + this.svg = new Svg(this); // Data containers this.settings = {}; this.options = {}; - this.store = new store(); + this.store = new Store(); this.parts = {}; // Merge config with defaults @@ -39,10 +39,10 @@ export default function pattern(config = false) { } // Constructors - this.part = part; - this.point = point; - this.path = path; - this.snippet = snippet; + this.Part = Part; + this.Point = Point; + this.Path = Path; + this.Snippet = Snippet; // Context object to inject in part prototype this.context = { @@ -52,20 +52,20 @@ export default function pattern(config = false) { options: this.options, store: this.store }; - this.part.prototype.context = this.context; - this.part.prototype.macros = {}; + this.Part.prototype.context = this.context; + this.Part.prototype.macros = {}; } /** * @throws Will throw an error when called */ -pattern.prototype.draft = function() { +Pattern.prototype.draft = function() { throw Error( "You have to implement the draft() method in your Pattern instance." ); }; -pattern.prototype.render = function() { +Pattern.prototype.render = function() { this.hooks.attach("preRenderSvg", this.svg); this.hooks.attach("postRenderSvg", this.svg); @@ -74,21 +74,21 @@ pattern.prototype.render = function() { return this.pack().svg.render(this); }; -pattern.prototype.on = function(hook, method) { +Pattern.prototype.on = function(hook, method) { if (typeof this.hooks._hooks[hook] === "undefined") { this.hooks._hooks[hook] = []; } this.hooks._hooks[hook].push(method); }; -pattern.prototype.with = function(plugin) { +Pattern.prototype.with = function(plugin) { if (plugin.hooks) this.loadPluginHooks(plugin); if (plugin.macros) this.loadPluginMacros(plugin); return this; }; -pattern.prototype.loadPluginHooks = function(plugin) { +Pattern.prototype.loadPluginHooks = function(plugin) { for (let hook of this.hooks.all) { if (typeof plugin.hooks[hook] === "function") { this.on(hook, plugin.hooks[hook]); @@ -96,7 +96,7 @@ pattern.prototype.loadPluginHooks = function(plugin) { } }; -pattern.prototype.loadPluginMacros = function(plugin) { +Pattern.prototype.loadPluginMacros = function(plugin) { for (let macro in plugin.macros) { if (typeof plugin.macros[macro] === "function") { this.macro(macro, plugin.macros[macro]); @@ -104,12 +104,12 @@ pattern.prototype.loadPluginMacros = function(plugin) { } }; -pattern.prototype.macro = function(key, method) { - this.part.prototype[macroName(key)] = method; +Pattern.prototype.macro = function(key, method) { + this.Part.prototype[macroName(key)] = method; }; /** Packs parts in a 2D space and sets pattern size */ -pattern.prototype.pack = function() { +Pattern.prototype.pack = function() { let bins = []; for (let key in this.parts) { let part = this.parts[key]; diff --git a/src/point.js b/src/point.js index e58799790a2..9aff6346064 100644 --- a/src/point.js +++ b/src/point.js @@ -1,24 +1,24 @@ -import attributes from "./attributes"; -import { round } from "./utils"; +import Attributes from "./attributes"; +import { round } from "./round"; -function point(x, y) { +function Point(x, y) { this.x = round(x); this.y = round(y); - this.attributes = new attributes(); + this.attributes = new Attributes(); } /** Radians to degrees */ -point.prototype.rad2deg = function(radians) { +Point.prototype.rad2deg = function(radians) { return radians * 57.29577951308232; }; /** Degrees to radians */ -point.prototype.deg2rad = function(degrees) { +Point.prototype.deg2rad = function(degrees) { return degrees / 57.29577951308232; }; /** Adds an attribute. This is here to make this call chainable in assignment */ -point.prototype.attr = function(name, value, overwrite = false) { +Point.prototype.attr = function(name, value, overwrite = false) { if (overwrite) this.attributes.set(name, value); else this.attributes.add(name, value); @@ -26,7 +26,7 @@ point.prototype.attr = function(name, value, overwrite = false) { }; /** Returns the distance between this point and that point */ -point.prototype.dist = function(that) { +Point.prototype.dist = function(that) { let dx = this.x - that.x; let dy = this.y - that.y; @@ -34,22 +34,22 @@ point.prototype.dist = function(that) { }; /** Returns slope of a line made by this point and that point */ -point.prototype.slope = function(that) { +Point.prototype.slope = function(that) { return (that.y - this.y) / (that.x - this.x); }; /** Returns the x-delta between this point and that point */ -point.prototype.dx = function(that) { +Point.prototype.dx = function(that) { return that.x - this.x; }; /** Returns the y-delta between this point and that point */ -point.prototype.dy = function(that) { +Point.prototype.dy = function(that) { return that.y - this.y; }; /** Returns the angle between this point and that point */ -point.prototype.angle = function(that) { +Point.prototype.angle = function(that) { let rad = Math.atan2(-1 * this.dy(that), this.dx(that)); while (rad < 0) rad += 2 * Math.PI; @@ -57,37 +57,37 @@ point.prototype.angle = function(that) { }; /** Rotate this point deg around that point */ -point.prototype.rotate = function(deg, that) { +Point.prototype.rotate = function(deg, that) { 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); + return new Point(x, y); }; /** returns an identical copy of this point */ -point.prototype.copy = function() { - return new point(this.x, this.y); +Point.prototype.copy = function() { + return new Point(this.x, this.y); }; /** checks whether this point is equal to that point */ -point.prototype.equals = function(that) { +Point.prototype.equals = function(that) { return this.x === that.x && this.y === that.y ? true : false; }; /** Mirrors this point around X value of that point */ -point.prototype.flipX = function(that) { - return new point(that.x + this.dx(that), that.y); +Point.prototype.flipX = function(that) { + return new Point(that.x + this.dx(that), that.y); }; /** Mirrors this point around Y value of that point */ -point.prototype.flipY = function(that) { - return new point(that.x, that.y + this.dy(that)); +Point.prototype.flipY = function(that) { + return new Point(that.x, that.y + this.dy(that)); }; /** Shifts this point distance in the deg direction */ -point.prototype.shift = function(deg, distance) { +Point.prototype.shift = function(deg, distance) { let p = this.copy(); p.x += distance; @@ -95,26 +95,26 @@ point.prototype.shift = function(deg, distance) { }; /** Shifts this point distance in the direction of that point */ -point.prototype.shiftTowards = function(that, distance) { +Point.prototype.shiftTowards = function(that, distance) { return this.shift(this.angle(that), distance); }; /** Shifts this point fraction of the distance towards that point */ -point.prototype.shiftFractionTowards = function(that, fraction) { +Point.prototype.shiftFractionTowards = function(that, fraction) { return this.shiftTowards(that, this.dist(that) * fraction); }; /** Shifts this point distance beyond that point */ -point.prototype.shiftOutwards = function(that, distance) { +Point.prototype.shiftOutwards = function(that, distance) { return this.shiftTowards(that, this.dist(that) + distance); }; /** Returns a deep copy of this */ -point.prototype.clone = function() { - let clone = new point(this.x, this.y); +Point.prototype.clone = function() { + let clone = new Point(this.x, this.y); clone.attributes = this.attributes.clone(); return clone; }; -export default point; +export default Point; diff --git a/src/round.js b/src/round.js new file mode 100644 index 00000000000..9ac66e44161 --- /dev/null +++ b/src/round.js @@ -0,0 +1,4 @@ +/** Rounds a value to 2 decimals */ +export function round(value) { + return Math.round(value * 1e2) / 1e2; +} diff --git a/src/snippet.js b/src/snippet.js index 2e94324d702..046d779378c 100644 --- a/src/snippet.js +++ b/src/snippet.js @@ -1,20 +1,20 @@ -import attributes from "./attributes"; +import Attributes from "./attributes"; -function snippet(def, anchor, description = "") { +function Snippet(def, anchor, description = "") { this.def = def; this.anchor = anchor; this.description = description; - this.attributes = new attributes(); + this.attributes = new Attributes(); return this; } /** Returns a deep copy of this */ -snippet.prototype.clone = function() { - let clone = new snippet(this.def, this.anchor.clone(), this.description); +Snippet.prototype.clone = function() { + let clone = new Snippet(this.def, this.anchor.clone(), this.description); clone.attributes = this.attributes.clone(); return clone; }; -export default snippet; +export default Snippet; diff --git a/src/store.js b/src/store.js index bb86c5a08e5..a75f7fe22ec 100644 --- a/src/store.js +++ b/src/store.js @@ -1,20 +1,20 @@ -function store() { +function Store() { this.data = new Map(); } /** Sets a value under index key */ -store.prototype.set = function(key, value) { +Store.prototype.set = function(key, value) { this.data.set(key, value); }; /** Sets a value under index key */ -store.prototype.setIfUnset = function(key, value) { +Store.prototype.setIfUnset = function(key, value) { if (!this.data.has(key)) this.data.set(key, value); }; /** Gets a value under index key */ -store.prototype.get = function(key) { +Store.prototype.get = function(key) { return this.data.get(key); }; -export default store; +export default Store; diff --git a/src/svg.js b/src/svg.js index d53762bbe81..be68e25b53d 100644 --- a/src/svg.js +++ b/src/svg.js @@ -1,10 +1,9 @@ -import attributes from "./attributes"; +import Attributes from "./attributes"; import * as hooklib from "hooks"; -import hooks from "./hooks"; import { version } from "../package.json"; -function svg(pattern) { +function Svg(pattern) { this.openGroups = []; this.freeId = 0; this.body = ""; @@ -15,7 +14,7 @@ function svg(pattern) { this.defs = ""; this.pattern = pattern; // Needed to expose pattern to hooks this.prefix = ''; - this.attributes = new attributes(); + this.attributes = new Attributes(); this.attributes.add("xmlns", "http://www.w3.org/2000/svg"); this.attributes.add("xmlns:svg", "http://www.w3.org/2000/svg"); this.attributes.add("xmlns:xlink", "http://www.w3.org/1999/xlink"); @@ -24,22 +23,22 @@ function svg(pattern) { "http://freesewing.org/namespaces/freesewing" ); this.attributes.add("freesewing", version); - this.hooks = hooks.all; + this.hooks = pattern.hooks.all; for (let k in hooklib) this[k] = hooklib[k]; for (let k in this.hooks) this.hook(k, this[k]); } /** Method to attach preRenderSvg hooks on */ -svg.prototype.preRenderSvg = function() {}; +Svg.prototype.preRenderSvg = function() {}; /** Method to attach postRenderSvg hooks on */ -svg.prototype.postRenderSvg = function() {}; +Svg.prototype.postRenderSvg = function() {}; /** Method to attach insertText hooks on */ -svg.prototype.insertText = function() {}; +Svg.prototype.insertText = function() {}; /** Renders a draft object as SVG */ -svg.prototype.render = function(pattern) { +Svg.prototype.render = function(pattern) { this.preRenderSvg(); this.attributes.add("width", pattern.width + "mm"); this.attributes.add("height", pattern.height + "mm"); @@ -67,7 +66,7 @@ svg.prototype.render = function(pattern) { }; /** Returns SVG code for the opening SVG tag */ -svg.prototype.renderSvgTag = function(pattern) { +Svg.prototype.renderSvgTag = function(pattern) { let svg = "" ); }; /** Returns SVG code for a Part object */ -svg.prototype.renderPart = function(part) { +Svg.prototype.renderPart = function(part) { let svg = ""; for (let key in part.paths) { let path = part.paths[key]; @@ -137,7 +136,7 @@ svg.prototype.renderPart = function(part) { }; /** Returns SVG code for a Point object */ -svg.prototype.renderPoint = function(point) { +Svg.prototype.renderPoint = function(point) { let svg = ""; if (point.attributes.get("data-text")) svg += this.renderText(point); @@ -145,7 +144,7 @@ svg.prototype.renderPoint = function(point) { }; /** Returns SVG code for a Path object */ -svg.prototype.renderPath = function(path) { +Svg.prototype.renderPath = function(path) { if (!path.attributes.get("id")) path.attributes.add("id", this.getUid()); path.attributes.add("d", path.asPathstring()); @@ -154,7 +153,7 @@ svg.prototype.renderPath = function(path) { )}`; }; -svg.prototype.renderPathText = function(path) { +Svg.prototype.renderPathText = function(path) { let text = path.attributes.get("data-text"); if (!text) return false; let attributes = path.attributes.renderIfPrefixIs("data-text-"); @@ -175,7 +174,7 @@ svg.prototype.renderPathText = function(path) { return svg; }; -svg.prototype.renderText = function(point) { +Svg.prototype.renderText = function(point) { let text = point.attributes.get("data-text"); if (!text) return false; @@ -194,7 +193,7 @@ svg.prototype.renderText = function(point) { }; /** Returns SVG code for a snippet */ -svg.prototype.renderSnippet = function(snippet) { +Svg.prototype.renderSnippet = function(snippet) { let svg = this.nl(); svg += ``; @@ -207,7 +206,7 @@ svg.prototype.renderSnippet = function(snippet) { }; /** Returns SVG code to open a group */ -svg.prototype.openGroup = function(id, attributes = false) { +Svg.prototype.openGroup = function(id, attributes = false) { let svg = this.nl() + this.nl(); svg += ``; svg += this.nl(); @@ -221,19 +220,19 @@ svg.prototype.openGroup = function(id, attributes = false) { }; /** Returns SVG code to close a group */ -svg.prototype.closeGroup = function() { +Svg.prototype.closeGroup = function() { this.outdent(); return `${this.nl()}${this.nl()}`; }; /** Returns a linebreak + identation */ -svg.prototype.nl = function() { +Svg.prototype.nl = function() { return "\n" + this.tab(); }; /** Returns indentation */ -svg.prototype.tab = function() { +Svg.prototype.tab = function() { let space = ""; for (let i = 0; i < this.tabs; i++) { space += " "; @@ -243,20 +242,20 @@ svg.prototype.tab = function() { }; /** Increases indentation by 1 */ -svg.prototype.indent = function() { +Svg.prototype.indent = function() { this.tabs += 1; }; /** Decreases indentation by 1 */ -svg.prototype.outdent = function() { +Svg.prototype.outdent = function() { this.tabs -= 1; }; /** Returns an unused ID */ -svg.prototype.getUid = function() { +Svg.prototype.getUid = function() { this.freeId += 1; return "" + this.freeId; }; -export default svg; +export default Svg; diff --git a/src/utils.js b/src/utils.js index 6bcb7e525e5..63a25da15e7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,7 @@ -import point from "./point"; -import path from "./path"; +import Point from "./point"; +import Path from "./path"; import Bezier from "bezier-js"; +import { round } from "./round"; /** Returns internal hook name for a macro */ export function macroName(name) { @@ -14,10 +15,10 @@ export function beamsCross(a1, a2, b1, b2) { if (slopeA === slopeB) return false; // Parallel lines if (a1.x === a2.x) - return new point(a1.x, slopeB * a1.x + (b1.y - slopeB * b1.x)); + return new Point(a1.x, slopeB * a1.x + (b1.y - slopeB * b1.x)); // Vertical line A else if (b1.x === b2.x) - return new point(b1.x, slopeA * b1.x + (a1.y - slopeA * a1.x)); + return new Point(b1.x, slopeA * b1.x + (a1.y - slopeA * a1.x)); // Vertical line B else { // Swap points if line A or B goes from right to left @@ -39,7 +40,7 @@ export function beamsCross(a1, a2, b1, b2) { let x = (iB - iA) / (slopeA - slopeB); let y = slopeA * x + iA; - return new point(x, y); + return new Point(x, y); } } @@ -59,8 +60,8 @@ export function linesCross(a1, a2, b1, b2) { /** Find where an (endless) line crosses a certain Y-value */ export function beamCrossesY(from, to, y) { if (from.y === to.y) return false; // Horizontal line - let left = new point(-10, y); - let right = new point(10, y); + let left = new Point(-10, y); + let right = new Point(10, y); return beamsCross(from, to, left, right); } @@ -78,94 +79,16 @@ export function shorthand(part) { paths: part.paths || {}, snippets: part.snippets || {}, macro: part.macroRunner(), - point: part.point, - path: part.path, - snippet: part.snippet, + Point: part.Point, + Path: part.Path, + Snippet: part.Snippet, final, paperless }; } -/** Offsets a path by distance */ -export function pathOffset(path, distance) { - let offset = []; - let current; - let start = false; - let closed = false; - for (let i in path.ops) { - let op = path.ops[i]; - if (op.type === "line") { - offset.push(offsetLine(current, op.to, distance)); - } else if (op.type === "curve") { - let b = new Bezier( - { x: current.x, y: current.y }, - { x: op.cp1.x, y: op.cp1.y }, - { x: op.cp2.x, y: op.cp2.y }, - { x: op.to.x, y: op.to.y } - ); - for (let bezier of b.offset(distance)) { - offset.push(asPath(bezier)); - } - } else if (op.type === "close") { - // offset.push(offsetLine(current, start, distance)); - closed = true; - } - if (op.to) current = op.to; - if (!start) start = current; - } - - return joinPaths(offset, closed); -} - -/** Offsets a line by distance */ -export function offsetLine(from, to, distance) { - if (from.x === to.x && from.y === to.y) { - throw "Cannot offset line that starts and ends in the same point"; - } - let angle = from.angle(to) - 90; - - return new path() - .move(from.shift(angle, distance)) - .line(to.shift(angle, distance)); -} - -/** Converts a bezier-js instance to a path */ -export function asPath(bezier) { - return new path() - .move(new point(bezier.points[0].x, bezier.points[0].y)) - .curve( - new point(bezier.points[1].x, bezier.points[1].y), - new point(bezier.points[2].x, bezier.points[2].y), - new point(bezier.points[3].x, bezier.points[3].y) - ); -} - -/** Joins path segments together into one path */ -export function joinPaths(paths, closed = false) { - let joint = new path().move(paths[0].ops[0].to); - for (let p of paths) { - for (let op of p.ops) { - if (op.type === "curve") { - joint.curve(op.cp1, op.cp2, op.to); - } else if (op.type !== "close") { - joint.line(op.to); - } else { - throw "Close op not handled"; - } - } - } - if (closed) joint.close(); - - return joint; -} - /** Convert value in mm to cm or imperial units */ export function units(value, to = "metric") { if (to === "imperial") return round(value / 25.4) + '"'; else return round(value / 10) + "cm"; } - -/** Rounds a value to 2 decimals */ -export function round(value) { - return Math.round(value * 1e2) / 1e2; -}