From c6ac1246c271c66733366086f467e381c3fd65a8 Mon Sep 17 00:00:00 2001 From: wout Date: Sun, 16 Dec 2012 16:15:47 +0100 Subject: [PATCH] Implemented core library --- Rakefile | 36 +++++++----- src/circle.js | 32 +++++++++++ src/clip_path.js | 14 +++++ src/container.js | 138 ++++++++++++++++++++++++++++++++++++++++++++++ src/defs.js | 17 ++++++ src/dispatcher.js | 17 ++++++ src/document.js | 16 ++++++ src/draggable.js | 46 ++++++++++++++++ src/element.js | 87 +++++++++++++++++++++++++++++ src/ellipse.js | 30 ++++++++++ src/group.js | 16 ++++++ src/image.js | 17 ++++++ src/nested.js | 12 ++++ src/object.js | 11 ++++ src/path.js | 13 +++++ src/rect.js | 7 +++ src/shape.js | 51 +++++++++++++++++ src/svg.js | 11 ++++ 18 files changed, 558 insertions(+), 13 deletions(-) create mode 100644 src/circle.js create mode 100644 src/ellipse.js create mode 100644 src/path.js create mode 100644 src/rect.js diff --git a/Rakefile b/Rakefile index 7eac669..f9ef0ff 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ SVGJS_VERSION = '0.1' -DEFAULT_MODULES = %w[ svg ] +DEFAULT_MODULES = %w[ svg container dispatcher draggable object element document defs group nested clip_path shape rect circle ellipse path image ] KILO = 1024 # how many bytes in a "kilobyte" @@ -8,6 +8,7 @@ task :default => :dist # module-aware file task class BuildTask < Rake::FileTask + def modules prerequisites.map {|f| File.basename(f, '.js') } end @@ -28,24 +29,33 @@ class BuildTask < Rake::FileTask end def first_line - File.open(name, 'r') {|f| f.gets } + File.open(name, 'r') { |f| f.gets } end + end BuildTask.define_task 'dist/svg.js' => DEFAULT_MODULES.map {|m| "src/#{ m }.js" } do |task| mkdir_p 'dist', :verbose => false - File.open(task.name, 'w') do |svgjs| - svgjs.puts "/* svg.js %s - %s - svgjs.com/license */" % - [version_string, task.modules.join(' ')] - - task.prerequisites.each do |src| - # bring in source files one by one, but without copyright info - copyright = true - File.open(src).each_line do |line| - copyright = false if copyright and line !~ %r{^(/|\s*$)} - svgjs.puts line unless copyright - end + + svgjs = '' + + task.prerequisites.each do |src| + # bring in source files one by one, but without copyright info + copyright = true + File.open(src).each_line do |line| + copyright = false if copyright and line !~ %r{^(/|\s*$)} + svgjs << " #{ line }" unless copyright end + svgjs << "\n\n" + end + + File.open(task.name, 'w') do |file| + file.puts "/* svg.js %s - %s - svgjs.com/license */" % [version_string, task.modules.join(' ')] + + file.puts '(function() {' + file.puts "\n" + file.puts svgjs + file.puts '}).call(this);' end end diff --git a/src/circle.js b/src/circle.js new file mode 100644 index 0000000..7d1fbee --- /dev/null +++ b/src/circle.js @@ -0,0 +1,32 @@ + +SVG.Circle = function Circle() { + this.constructor.call(this, SVG.createElement('circle')); +}; + +// inherit from SVG.Shape +SVG.Circle.prototype = new SVG.Shape(); + +// custom move function +SVG.Circle.prototype.move = function(x, y) { + this.attributes.x = x; + this.attributes.y = y; + this._center(); + + return this; +}; + +// custom size function +SVG.Circle.prototype.size = function(w, h) { + this.setAttribute('r', w / 2); + this._center(); + + return this; +}; + +// private: center +SVG.Circle.prototype._center = function() { + var r = this.attributes.r || 0; + + this.setAttribute('cx', (this.attributes.x || 0) + r); + this.setAttribute('cy', (this.attributes.y || 0) + r); +}; \ No newline at end of file diff --git a/src/clip_path.js b/src/clip_path.js index e69de29..2eece49 100644 --- a/src/clip_path.js +++ b/src/clip_path.js @@ -0,0 +1,14 @@ +// initialize id sequence +var clipID = 0; + +SVG.ClipPath = function ClipPath() { + this.constructor.call(this, SVG.createElement('clipPath')); + this.id = '_' + (clipID++); + this.setAttribute('id', this.id); +}; + +// inherit from SVG.Element +SVG.ClipPath.prototype = new SVG.Element(); + +// include the container object +SVG.ClipPath.include(SVG.Container); \ No newline at end of file diff --git a/src/container.js b/src/container.js index e69de29..339e554 100644 --- a/src/container.js +++ b/src/container.js @@ -0,0 +1,138 @@ + +SVG.Container = { + + add: function(e) { + return this.addAt(e); + }, + + addAt: function(e, i) { + if (!this.contains(e)) { + i = i == null ? this.children().length : i; + this.children().splice(i, 0, e); + this.svgElement.insertBefore(e.svgElement, this.svgElement.childNodes[i + 1]); + e.parent = this; + } + + return this; + }, + + contains: function(e) { + return Array.prototype.indexOf.call(this.children(), e) >= 0; + }, + + children: function() { + return this._children || []; + }, + + sendBack: function(e) { + var i = this.children().indexOf(e); + if (i !== -1) + return this.remove(e).addAt(e, i - 1); + }, + + bringForward: function(e) { + var i = this.children().indexOf(e); + if (i !== -1) + return this.remove(e).addAt(e, i + 1); + }, + + bringToFront: function(e) { + if (this.contains(e)) + this.remove(e).add(e); + + return this; + }, + + sendToBottom: function(e) { + if (this.contains(e)) + this.remove(e).addAt(e, 0); + + return this; + }, + + remove: function(e) { + return this.removeAt(this.children().indexOf(e)); + }, + + removeAt: function(i) { + if (0 <= i && i < this.children().length) { + var e = this.children()[i]; + this.children().splice(i, 1); + this.svgElement.removeChild(e.svgElement); + e.parent = null; + } + + return this; + }, + + defs: function() { + if (this._defs == null) { + this._defs = new SVG.Defs(); + this.add(this._defs); + } + + return this._defs; + }, + + group: function() { + var e = new SVG.Group(); + this.add(e); + + return e; + }, + + svg: function(v) { + return this.place(new SVG.Nested(), v); + }, + + rect: function(v) { + return this.place(new SVG.Rect(), v); + }, + + circle: function(v) { + return this.place(new SVG.Circle(), { + x: v != null ? v.x : void 0, + y: v != null ? v.y : void 0, + width: (v != null ? (v.width || v.r || v.radius) : void 0), + height: null + }); + }, + + ellipse: function(v) { + return this.place(new SVG.Ellipse(), { + x: v != null ? v.x : void 0, + y: v != null ? v.y : void 0, + width: (v != null ? (v.width || v.rx || v.radiusX) : void 0), + height: (v != null ? (v.height || v.ry || v.radiusY) : void 0) + }); + }, + + path: function(v) { + return this.place(new SVG.Path(), v); + }, + + image: function(v) { + return this.place(new SVG.Image(), v); + }, + + place: function(e, v) { + if (v != null) { + if (v.x != null && v.y != null) + e.move(v.x, v.y); + + if (v.width != null && v.height != null) + e.size(v.width, v.height); + + if (v.data != null) + e.data(v.data); + + if (v.src != null) + e.load(v.src); + } + + this.add(e); + + return e; + } + +}; \ No newline at end of file diff --git a/src/defs.js b/src/defs.js index e69de29..4661a54 100644 --- a/src/defs.js +++ b/src/defs.js @@ -0,0 +1,17 @@ +SVG.Defs = function Defs() { + this.constructor.call(this, SVG.createElement('defs')); +}; + +// inherit from SVG.Element +SVG.Defs.prototype = new SVG.Element(); + +// define clippath +SVG.Defs.prototype.clipPath = function() { + var e = new SVG.ClipPath(); + this.add(e); + + return e; +}; + +// include the container object +SVG.Defs.include(SVG.Container); \ No newline at end of file diff --git a/src/dispatcher.js b/src/dispatcher.js index e69de29..2765652 100644 --- a/src/dispatcher.js +++ b/src/dispatcher.js @@ -0,0 +1,17 @@ +SVG.Dispatcher = { + + on: function(events, listener) { + events = events.split(' '); + + for (var i = 0, l = events.length; i < l; i++) + this.svgElement.addEventListener(events[i], listener) + }, + + off: function(events, listener) { + events = events.split(' '); + + for (var i = 0, l = events.length; i < l; i++) + this.svgElement.removeEventListener(events[i], listener) + } + +}; \ No newline at end of file diff --git a/src/document.js b/src/document.js index e69de29..a510c44 100644 --- a/src/document.js +++ b/src/document.js @@ -0,0 +1,16 @@ + +SVG.Document = function Document(c) { + this.constructor.call(this, SVG.createElement('svg')); + + this.setAttribute('xmlns', SVG.namespace); + this.setAttribute('version', '1.1'); + this.setAttribute('xlink', 'http://www.w3.org/1999/xlink', SVG.namespace); + + document.getElementById(c).appendChild(this.svgElement); +}; + +// inherit from SVG.Element +SVG.Document.prototype = new SVG.Element(); + +// include the container object +SVG.Document.include(SVG.Container); \ No newline at end of file diff --git a/src/draggable.js b/src/draggable.js index e69de29..b0deac7 100644 --- a/src/draggable.js +++ b/src/draggable.js @@ -0,0 +1,46 @@ + +var bind = function(fn, me) { + return function() { return fn.apply(me, arguments); }; +}; + +SVG.Draggable = function Draggable(e) { + this._windowMouseUp = bind(this._windowMouseUp, this); + this._windowMouseMove = bind(this._windowMouseMove, this); + this.draggable = bind(this.draggable, this); + this.element = e; + this.element.draggable = this.draggable; +}; + +SVG.Draggable.prototype.draggable = function() { + var self = this; + this.element.on('mousedown', function(e) { + self.startDragEvent = e; + self.startDragPosition = { + x: self.element.attributes.x || 0, + y: self.element.attributes.y || 0 + }; + window.addEventListener('mousemove', self._windowMouseMove); + window.addEventListener('mouseup', self._windowMouseUp); + return typeof self.element.dragstart === 'function' ? self.element.dragstart(e) : void 0; + }); + return this.element; +}; + +SVG.Draggable.prototype._windowMouseMove = function(e) { + if (this.startDragEvent != null) { + var d = { + x: e.pageX - this.startDragEvent.pageX, + y: e.pageY - this.startDragEvent.pageY + }; + this.element.move(this.startDragPosition.x + d.x, this.startDragPosition.y + d.y); + return typeof this.element.dragmove === 'function' ? this.element.dragmove(d, e) : void 0; + } +}; + +SVG.Draggable.prototype._windowMouseUp = function(e) { + this.startDragEvent = null; + this.startDragPosition = null; + window.removeEventListener('mousemove', this._windowMouseMove); + window.removeEventListener('mouseup', this._windowMouseUp); + return typeof this.element.dragend === 'function' ? this.element.dragend(e) : void 0; +}; \ No newline at end of file diff --git a/src/element.js b/src/element.js index e69de29..d38e323 100644 --- a/src/element.js +++ b/src/element.js @@ -0,0 +1,87 @@ + +SVG.Element = function Element(svgElement) { + this.svgElement = svgElement; + this.attributes = {}; +}; + +//-D // inherit from SVG.Object +//-D SVG.Element.prototype = new SVG.Object(); + +// move element to given x and y values +SVG.Element.prototype.move = function(x, y) { + this.setAttribute('x', x); + this.setAttribute('y', y); + + return this; +}; + +// set element opacity +SVG.Element.prototype.opacity = function(o) { + return this.setAttribute('opacity', Math.max(0, Math.min(1, o))); +}; + +// set element size to given width and height +SVG.Element.prototype.size = function(w, h) { + this.setAttribute('width', w); + this.setAttribute('height', h); + + return this; +}; + +// clip element using another element +SVG.Element.prototype.clip = function(block) { + var p = this.parentSVG().defs().clipPath(); + block(p); + + return this.clipTo(p); +}; + +// distribute clipping path to svg element +SVG.Element.prototype.clipTo = function(p) { + return this.setAttribute('clip-path', 'url(#' + p.id + ')'); +}; + +// remove element +SVG.Element.prototype.destroy = function() { + return this.parent != null ? this.parent.remove(this) : void 0; +}; + +// get parent document +SVG.Element.prototype.parentDoc = function() { + return this._findParent(SVG.Document); +}; + +// get parent svg wrapper +SVG.Element.prototype.parentSVG = function() { + return this._findParent(SVG.Nested) || this.parentDoc(); +}; + +// set svg element attribute +SVG.Element.prototype.setAttribute = function(a, v, ns) { + this.attributes[a] = v; + + if (ns != null) + this.svgElement.setAttributeNS(ns, a, v); + else + this.svgElement.setAttribute(a, v); + + return this; +}; + +// get bounding box +SVG.Element.prototype.getBBox = function() { + return this.svgElement.getBBox(); +}; + +// private: find svg parent +SVG.Element.prototype._findParent = function(pt) { + var e = this; + + while (e != null && !(e instanceof pt)) + e = e.parent; + + return e; +}; + +// include the dispatcher object +SVG.Element.include(SVG.Dispatcher); \ No newline at end of file diff --git a/src/ellipse.js b/src/ellipse.js new file mode 100644 index 0000000..9e03277 --- /dev/null +++ b/src/ellipse.js @@ -0,0 +1,30 @@ + +SVG.Ellipse = function Ellipse() { + this.constructor.call(this, SVG.createElement('ellipse')); +}; + +// inherit from SVG.Shape +SVG.Ellipse.prototype = new SVG.Shape(); + +// custom move function +SVG.Ellipse.prototype.move = function(x, y) { + this.attributes.x = x; + this.attributes.y = y; + this._center(); + + return this; +}; + +// custom size function +SVG.Ellipse.prototype.size = function(w, h) { + this.setAttribute('rx', w / 2); + this.setAttribute('ry', h / 2); + this._center(); + + return this; +}; + +SVG.Ellipse.prototype._center = function() { + this.setAttribute('cx', (this.attributes.x || 0) + (this.attributes.rx || 0)); + this.setAttribute('cy', (this.attributes.y || 0) + (this.attributes.ry || 0)); +}; \ No newline at end of file diff --git a/src/group.js b/src/group.js index e69de29..e46035f 100644 --- a/src/group.js +++ b/src/group.js @@ -0,0 +1,16 @@ + +SVG.Group = function Group() { + this.constructor.call(this, SVG.createElement("g")); +}; + +// inherit from SVG.Element +SVG.Group.prototype = new SVG.Element(); + +// group rotation +SVG.Group.prototype.rotate = function(d) { + this.setAttribute('transform', 'rotate(' + d + ')'); + return this; +}; + +// include the container object +SVG.Group.include(SVG.Container); \ No newline at end of file diff --git a/src/image.js b/src/image.js index e69de29..ff70aee 100644 --- a/src/image.js +++ b/src/image.js @@ -0,0 +1,17 @@ + +SVG.Image = function Image() { + this.drag = new SVG.Draggable(this); + this.constructor.call(this, SVG.createElement('image')); +}; + +// inherit from SVG.Element +SVG.Image.prototype = new SVG.Element(); + +// (re)load image +SVG.Image.prototype.load = function(url) { + this.setAttribute('href', url, SVG.xlink); + return this; +}; + +// include the container object +SVG.Image.include(SVG.Container); \ No newline at end of file diff --git a/src/nested.js b/src/nested.js index e69de29..6245ca6 100644 --- a/src/nested.js +++ b/src/nested.js @@ -0,0 +1,12 @@ + +SVG.Nested = function Nested() { + this.drag = new SVG.Draggable(this); + this.constructor.call(this, SVG.createElement('svg')); + this.setAttribute('overflow', 'visible'); +}; + +// inherit from SVG.Element +SVG.Nested.prototype = new SVG.Element(); + +// include the container object +SVG.Nested.include(SVG.Container); \ No newline at end of file diff --git a/src/object.js b/src/object.js index e69de29..ecf43ee 100644 --- a/src/object.js +++ b/src/object.js @@ -0,0 +1,11 @@ + +Object.prototype.include = function(module) { + + for (var key in module) + this.prototype[key] = module[key]; + + if (module.included != null) + module.included.apply(this); + + return this; +}; \ No newline at end of file diff --git a/src/path.js b/src/path.js new file mode 100644 index 0000000..c064a91 --- /dev/null +++ b/src/path.js @@ -0,0 +1,13 @@ + +SVG.Path = function Path() { + this.constructor.call(this, SVG.createElement('path')); +}; + +// inherit from SVG.Shape +SVG.Path.prototype = new SVG.Shape(); + +// set path data +SVG.Path.prototype.data = function(d) { + this.setAttribute('d', d); + return this; +}; \ No newline at end of file diff --git a/src/rect.js b/src/rect.js new file mode 100644 index 0000000..b08d68e --- /dev/null +++ b/src/rect.js @@ -0,0 +1,7 @@ + +SVG.Rect = function Rect() { + this.constructor.call(this, SVG.createElement('rect')); +}; + +// inherit from SVG.Shape +SVG.Rect.prototype = new SVG.Shape(); \ No newline at end of file diff --git a/src/shape.js b/src/shape.js index e69de29..8295f1c 100644 --- a/src/shape.js +++ b/src/shape.js @@ -0,0 +1,51 @@ + +SVG.Shape = function Shape(element) { + this.drag = new SVG.Draggable(this); + this.constructor.call(this, element); +}; + +// inherit from SVG.Element +SVG.Shape.prototype = new SVG.Element(); + +// set fill color and opacity +SVG.Shape.prototype.fill = function(fill) { + this._formatColor(fill); + + if (fill.color != null) + this.setAttribute('fill', fill.color); + + if (fill.opacity != null) + this.setAttribute('fill-opacity', fill.opacity); + + return this; +}; + +// set stroke color and opacity +SVG.Shape.prototype.stroke = function(stroke) { + this._formatColor(stroke); + + if (stroke.color != null) + this.setAttribute('stroke', stroke.color); + + if (stroke.width != null) + this.setAttribute('stroke-width', stroke.width); + + if (stroke.opacity != null) + this.setAttribute('stroke-opacity', stroke.opacity); + + if (this.attributes['fill-opacity'] == null) + this.fill({ opacity: 0 }); + + return this; +}; + +// ensure correct color string +SVG.Shape.prototype._formatColor = function(obj) { + if (typeof obj.color === 'number') { + obj.color = '' + obj.color.toString(16); + while (obj.color.length < 6) + obj.color = '0' + obj.color; + + return obj.color = '#' + obj.color; + } +}; \ No newline at end of file diff --git a/src/svg.js b/src/svg.js index e69de29..4f17c92 100644 --- a/src/svg.js +++ b/src/svg.js @@ -0,0 +1,11 @@ + +var SVG = { + namespace: "http://www.w3.org/2000/svg", + xlink: "http://www.w3.org/1999/xlink", + + createElement: function(e) { + return document.createElementNS(this.namespace, e); + } +}; + +this.SVG = SVG; \ No newline at end of file -- 2.39.5