import Attributes from "./attributes"; import { version } from "../package.json"; function Svg(pattern) { this.openGroups = []; this.layout = {}; this.freeId = 0; this.body = ""; this.style = ""; this.script = ""; this.defs = ""; this.pattern = pattern; // Needed to expose pattern to hooks this.prefix = ''; 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"); this.attributes.add("xml:lang", pattern.settings.locale); this.attributes.add( "xmlns:freesewing", "http://freesewing.org/namespaces/freesewing" ); 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); } } }; /** Runs insertText hooks */ 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); } return text; }; /** Debug method, exposes debug hook */ Svg.prototype.debug = function() {}; /** Renders a draft object as SVG */ Svg.prototype.render = function(pattern) { this.idPrefix = pattern.settings.idPrefix; this.runHooks("preRender"); if (!pattern.settings.embed) { this.attributes.add("width", pattern.width + "mm"); this.attributes.add("height", 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 && pattern.needs(partId)) { 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; }; /** Renders SVG head section */ Svg.prototype.renderHead = function() { let svg = this.renderStyle(); svg += this.renderScript(); svg += this.renderDefs(); svg += this.openGroup(this.idPrefix + "container"); return svg; }; /** Renders SVG closing section */ Svg.prototype.renderTail = function() { let svg = ""; svg += this.closeGroup(); svg += this.nl() + ""; return svg; }; /** Returns SVG code for the opening SVG tag */ Svg.prototype.renderSvgTag = function() { let svg = "" + this.nl(); return svg; }; /** Returns SVG code for the style block */ Svg.prototype.renderStyle = function() { let svg = '" + this.nl(); return svg; }; /** Returns SVG code for the script block */ Svg.prototype.renderScript = function() { let svg = '" + this.nl(); return svg; }; /** Returns SVG code for the defs block */ Svg.prototype.renderDefs = function() { let svg = ""; this.indent(); svg += this.nl() + this.defs; this.outdent(); svg += this.nl() + "" + 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); } 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()); path.attributes.set("d", path.asPathstring()); return `${this.nl()}${this.renderPathText( path )}`; }; Svg.prototype.renderPathText = function(path) { let text = path.attributes.get("data-text"); if (!text) return ""; 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() + ""; this.indent(); svg += `${this.text}`; this.outdent(); svg += this.nl() + ""; 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); } point.attributes.set("data-text-x", point.x); point.attributes.set("data-text-y", point.y); let lineHeight = point.attributes.get("data-text-lineheight") || 12; point.attributes.remove("data-text-lineheight"); let svg = `${this.nl()}`; this.indent(); // Multi-line text? if (this.text.indexOf("\n") !== -1) { let lines = this.text.split("\n"); svg += `${lines.shift()}`; for (let line of lines) { svg += `${line}`; } } else { svg += `${this.text}`; } this.outdent(); svg += this.nl() + ""; return svg; }; Svg.prototype.renderCircle = function(point) { return ``; }; /** Returns SVG code for a snippet */ Svg.prototype.renderSnippet = function(snippet, part) { let x = snippet.anchor.x; let y = snippet.anchor.y; let scale = snippet.attributes.get("data-scale"); if (scale) { snippet.attributes.add("transform", `translate(${x}, ${y})`); snippet.attributes.add("transform", `scale(${scale})`); snippet.attributes.add("transform", `translate(${x * -1}, ${y * -1})`); } let rotate = snippet.attributes.get("data-rotate"); if (rotate) { snippet.attributes.add("transform", `rotate(${rotate}, ${x}, ${y})`); } let svg = this.nl(); svg += ``; svg += ""; return svg; }; /** Returns SVG code to open a group */ Svg.prototype.openGroup = function(id, attributes = false) { let svg = this.nl() + this.nl(); svg += ``; svg += this.nl(); svg += `${this.nl()}`; }; /** Returns a linebreak + identation */ Svg.prototype.nl = function() { return "\n" + this.tab(); }; /** Returns indentation */ Svg.prototype.tab = function() { let space = ""; for (let i = 0; i < this.tabs; i++) { space += " "; } 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; }; export default Svg;