diff --git a/.gitignore b/.gitignore index 572c354..8c61013 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -builds/ \ No newline at end of file +builds/ +package-lock.json diff --git a/sources/index.html b/sources/index.html index 44462b4..b8a4235 100644 --- a/sources/index.html +++ b/sources/index.html @@ -9,6 +9,7 @@ + @@ -31,6 +32,8 @@ document.addEventListener('mousedown', function(e){ dotgrid.mouse_down(e); }, false); document.addEventListener('mousemove', function(e){ dotgrid.mouse_move(e); }, false); document.addEventListener('mouseup', function(e){ dotgrid.mouse_up(e);}, false); + document.addEventListener('copy', function(e){ dotgrid.copy(e);}, false); + document.addEventListener('paste', function(e){ dotgrid.paste(e);}, false); diff --git a/sources/scripts/dotgrid.js b/sources/scripts/dotgrid.js index 0192c6e..4f502a8 100644 --- a/sources/scripts/dotgrid.js +++ b/sources/scripts/dotgrid.js @@ -44,6 +44,7 @@ function Dotgrid(width,height,grid_x,grid_y,block_x,block_y,thickness = 3,lineca this.guide = new Guide(); this.render = new Render(); + this.serializer = new Serializer(); this.path = document.createElementNS("http://www.w3.org/2000/svg", "path"); this.segments = []; @@ -479,6 +480,47 @@ function Dotgrid(width,height,grid_x,grid_y,block_x,block_y,thickness = 3,lineca }); } + this.copy = function(e) + { + if(this.segments.length == 0){ return; } + this.scale = 1 + this.width = 300 + this.height = 300 + this.draw() + var svg = this.svg_el.outerHTML + + e.clipboardData.items.add(JSON.stringify({ dotgrid: this.serializer.serialize() }), "text/plain"); + + e.clipboardData.items.add(svg, "text/html"); + e.clipboardData.items.add(svg, "text/svg+xml"); + + // Right now, the following doesn't work and breaks "text/plain". + // This seems to be a bug in Chromium as others around the web complain, too. + /* + e.clipboardData.items.add(new File([new Blob([svg], { type: "image/svg+xml" } )], "image.svg")); + e.clipboardData.items.add(new File([new Blob([Uint8Array.from(dotgrid.render.buffer()).buffer], { type: "image/png" } ) ], "image.png")); + */ + + e.preventDefault(); + } + + this.paste = function(e) + { + var data = e.clipboardData.getData("text/plain"); + try { + data = JSON.parse(data.trim()).dotgrid; + if (!data) throw null; + } catch (err) { + // Not a dotgrid JSON. + return; + } + + this.serializer.deserialize(data); + + this.resize(); + this.draw(); + } + // Normalizers this.position_in_grid = function(pos) diff --git a/sources/scripts/path_arc.js b/sources/scripts/path_arc.js index 457c33b..fffe79b 100644 --- a/sources/scripts/path_arc.js +++ b/sources/scripts/path_arc.js @@ -1,5 +1,6 @@ function Path_Arc(from,to,orientation,end) { + this.__serialized_name__ = "Path_Arc"; this.name = "arc"; this.from = from; diff --git a/sources/scripts/path_bezier.js b/sources/scripts/path_bezier.js index 08c49b9..81546b2 100644 --- a/sources/scripts/path_bezier.js +++ b/sources/scripts/path_bezier.js @@ -1,7 +1,8 @@ function Path_Bezier(from,to,end) { + this.__serialized_name__ = "Path_Bezier"; this.name = "bezier"; - + this.from = from; this.to = to; this.end = end; diff --git a/sources/scripts/path_close.js b/sources/scripts/path_close.js index ca0d8ef..ad73414 100644 --- a/sources/scripts/path_close.js +++ b/sources/scripts/path_close.js @@ -1,7 +1,8 @@ function Path_Close() { + this.__type_ = "Path_Close"; this.name = "close"; - + this.to_segment = function(prev) { return "Z "; diff --git a/sources/scripts/path_line.js b/sources/scripts/path_line.js index 97b62f6..8abc52e 100644 --- a/sources/scripts/path_line.js +++ b/sources/scripts/path_line.js @@ -1,7 +1,8 @@ function Path_Line(from,to,end = null) { + this.__serialized_name__ = "Path_Line"; this.name = "line"; - + this.from = from; this.to = to; this.end = end; diff --git a/sources/scripts/pos.js b/sources/scripts/pos.js index e8b20e0..dfaaa79 100644 --- a/sources/scripts/pos.js +++ b/sources/scripts/pos.js @@ -1,5 +1,6 @@ function Pos(x,y) { + this.__serialized_name__ = "."; this.x = x; this.y = y; @@ -39,4 +40,8 @@ function Pos(x,y) } function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } -} \ No newline at end of file +} + +// This is ugly, but Pos.__serialized_name__ == "."; +// Let's keep the character count low. +window["."] = Pos; \ No newline at end of file diff --git a/sources/scripts/serializer.js b/sources/scripts/serializer.js new file mode 100644 index 0000000..db441b1 --- /dev/null +++ b/sources/scripts/serializer.js @@ -0,0 +1,91 @@ +function Serializer() +{ + var __data_segments__ = 0; + var __data_thickness__ = 1; + var __data_linecap__ = 2; + var __data_color__ = 3; + var __data_mirror_index__ = 4; + var __data_fill__ = 5; + + this.serialize = function() + { + // Store the data in an array. + // This keeps away the property names, which just clutter up everything. + var data = [ + [], + dotgrid.thickness, + dotgrid.linecap, + dotgrid.color, + dotgrid.mirror_index, + dotgrid.fill + ]; + + for (var id in dotgrid.segments) { + data[__data_segments__][id] = this.serialize_segment(dotgrid.segments[id]); + } + + return data; + } + + this.deserialize = function(data) + { + if (data[__data_segments__]) { + for (var id in data[__data_segments__]) { + data[__data_segments__][id] = this.deserialize_segment(data[__data_segments__][id]); + } + } + + var d = (index, fallback) => index < data.length ? data[index] : fallback; + + dotgrid.segments = d(__data_segments__, []); + dotgrid.thickness = d(__data_thickness__, 10); + dotgrid.linecap = d(__data_linecap__, "square"); + dotgrid.color = d(__data_color__, "#000000"); + dotgrid.mirror_index = d(__data_mirror_index__, 0); + dotgrid.fill = d(__data_fill__, false); + } + + this.serialize_segment = function(s) { + // Return falsy values (null, 0, false, "", ...) directly. + if (!s) return s; + + var data = [";"]; + // Get rid of non-serializable stuff (i.e. functions). + s = JSON.parse(JSON.stringify(s)); + // Store everything in arrays instead of objects, saving characters. + for (var id in s) { + // Skip the non-serialzied path name. + if (s.__serialized_name__ && id === "name") + continue; + + var prop = s[id]; + + if (typeof(prop) === "object") { + prop = this.serialize_segment(prop); + } + + data.push(prop); + } + return data; + } + + this.deserialize_segment = function(data) { + var name = data.splice(0, 2)[1]; + + // Unserialize anything that's serialized. + for (var id in data) { + var prop = data[id]; + + if (prop && typeof(prop) === "object" && prop.length && prop[0] === ";") { + prop = this.deserialize_segment(prop); + } + + data[id] = prop; + } + + var s = {}; + window[name].apply(s, data); + return s; + } + +} \ No newline at end of file