diff --git a/bg.png b/bg.png new file mode 100644 index 0000000..70b6850 Binary files /dev/null and b/bg.png differ diff --git a/demojs.html b/demojs.html index d74a573..65dee12 100644 --- a/demojs.html +++ b/demojs.html @@ -3,6 +3,11 @@ Savage + @@ -25,31 +30,31 @@ diff --git a/savage.set.js b/savage.set.js index 02b13b6..bac18a2 100644 --- a/savage.set.js +++ b/savage.set.js @@ -1,7 +1,6 @@ -(function () { +Savage.plugin(function (Savage, Element, Paper, glob) { var mmax = Math.max, - mmin = Math.min, - g = eve("savage.globals")[0]; + mmin = Math.min; // Set var Set = function (items) { @@ -71,9 +70,9 @@ } return this; }; - setproto.attr = function (name, value) { + setproto.attr = function (value) { for (var i = 0, ii = this.items.length; i < ii; i++) { - this.items[i].attr(name, value); + this.items[i].attr(value); } return this; }; @@ -190,11 +189,11 @@ }; setproto.type = "set"; // export - g.savage.set = function () { + Savage.set = function () { var set = new Set; if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)); } return set; }; -})(); \ No newline at end of file +}); \ No newline at end of file diff --git a/svg.js b/svg.js index 5bd6fb9..045aba3 100644 --- a/svg.js +++ b/svg.js @@ -11,6 +11,8 @@ var Savage = function (w, h) { return new Element(glob.doc.querySelector(w)); } } + w = w == null ? "100%" : w; + h = h == null ? "100%" : h; return new Paper(w, h); }; var glob = { @@ -38,13 +40,12 @@ var has = "hasOwnProperty", spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029", separator = new RegExp("[," + spaces + "]+"), whitespace = new RegExp("[" + spaces + "]", "g"), - commaSpaces = new RegExp("[" + spaces + "]*,[" + spaces + "*]"), + commaSpaces = new RegExp("[" + spaces + "]*,[" + spaces + "]*"), hsrg = {hs: 1, rg: 1}, p2s = /,?([a-z]),?/gi, pathCommand = new RegExp("([a-z])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), tCommand = new RegExp("([rstm])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), pathValues = new RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[" + spaces + "]*,?[" + spaces + "]*", "ig"), - radial_gradient = new RegExp("^r(?:\\(([^,]+?)[" + spaces + "]*,[" + spaces + "]*([^\\)]+?)\\))?"), idgen = 0, idprefix = "S" + (+new Date).toString(36), ID = function () { @@ -109,6 +110,24 @@ function is(o, type) { (type == "array" && Array.isArray && Array.isArray(o)) || objectToString.call(o).slice(8, -1).toLowerCase() == type; } +var preload = (function () { + function onerror() { + this.parentNode.removeChild(this); + } + return function (src, f) { + var img = glob.doc.createElement("img"), + body = glob.doc.body; + img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; + img.onload = function () { + f.call(img); + img.onload = img.onerror = null; + body.removeChild(img); + }; + img.onerror = onerror; + body.appendChild(img); + img.src = src; + }; +}()); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; @@ -425,7 +444,7 @@ Savage.Matrix = Matrix; #
  • Colour name (“red”, “green”, “cornflowerblue”, etc)
  • #
  • #••• — shortened HTML colour: (“#000”, “#fc0”, etc)
  • #
  • #•••••• — full length HTML colour: (“#000000”, “#bd2300”)
  • - #
  • rgb(•••, •••, •••) — red, green and blue channels’ values: (“rgb(200, 100, 0)”)
  • + #
  • rgb(•••, •••, •••) — red, green and blue channels values: (“rgb(200, 100, 0)”)
  • #
  • rgb(•••%, •••%, •••%) — same as above, but in %: (“rgb(100%, 175%, 0%)”)
  • #
  • hsb(•••, •••, •••) — hue, saturation and brightness values: (“hsb(0.5, 0.25, 1)”)
  • #
  • hsb(•••%, •••%, •••%) — same as above, but in %
  • @@ -438,7 +457,7 @@ Savage.Matrix = Matrix; o g (number) green, o b (number) blue o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) true if string can’t be parsed + o error (boolean) true if string cant be parsed o } \*/ Savage.getRGB = cacher(function (colour) { @@ -571,7 +590,9 @@ hsltoString = function () { return "hsl(" + [this.h, this.s, this.l] + ")"; }, rgbtoString = function () { - return this.hex; + return this.opacity == 1 || this.opacity == null ? + this.hex : + "rgba(" + [this.r, this.g, this.b, this.opacity] + ")"; }, prepareRGB = function (r, g, b) { if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) { @@ -594,9 +615,9 @@ prepareRGB = function (r, g, b) { return [r, g, b]; }, packageRGB = function (r, g, b, o) { - r *= 255; - g *= 255; - b *= 255; + r = math.round(r * 255); + g = math.round(g * 255); + b = math.round(b * 255); var rgb = { r: r, g: g, @@ -621,7 +642,7 @@ packageRGB = function (r, g, b, o) { o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, - o error (boolean) `true` if string can’t be parsed, + o error (boolean) `true` if string cant be parsed, o h (number) hue, o s (number) saturation, o v (number) value (brightness), @@ -1696,11 +1717,20 @@ function Element(el) { el.savage = id; hub[id] = this; } +function arrayFirstValue(arr) { + var res; + for (var i = 0, ii = arr.length; i < ii; i++) { + res = res || arr[i]; + if (res) { + return res; + } + } +} (function (elproto) { elproto.attr = function (params) { var node = this.node; if (is(params, "string")) { - return eve("savage.util.getattr." + params, this)[0]; + return arrayFirstValue(eve("savage.util.getattr." + params, this)); } for (var att in params) { if (params[has](att)) { @@ -1883,6 +1913,31 @@ function Element(el) { p.node.appendChild(this.node); return p; }; + elproto.marker = function (x, y, width, height, refX, refY) { + var p = make("marker", this.paper.defs); + if (x == null) { + x = this.getBBox(); + } + if (x && "x" in x) { + y = x.y; + width = x.width; + height = x.height; + refX = x.refX; + refY = x.refY; + x = x.x; + } + $(p.node, { + viewBox: [x, y, width, height].join(S), + markerWidth: width, + markerHeight: height, + orient: "auto", + refX: refX || 0, + refY: refY || 0, + id: p.id + }); + p.node.appendChild(this.node); + return p; + }; // animation function applyAttr(el, key) { var at = {}; @@ -2026,6 +2081,38 @@ function wrap(dom) { } return el; }; + proto.image = function (src, x, y, width, height) { + var el = make("image", this.node); + if (is(src, "object") && "src" in src) { + el.attr(src); + } else if (src != null) { + var set = { + "xlink:href": src, + preserveAspectRatio: "none" + }; + if (x != null && y != null) { + set.x = x; + set.y = y; + } + if (width != null && height != null) { + set.width = width; + set.height = height; + } else { + preload(src, function () { + $(el.node, { + width: this.offsetWidth, + height: this.offsetHeight + }); + }); + } + $(el.node, set); + } + return el; + }; + proto.custom = function (name) { + var el = make(name, this.node); + return el; + }; proto.ellipse = function (cx, cy, rx, ry) { var el = make("ellipse", this.node); if (is(cx, "object") && "cx" in cx) { @@ -2139,7 +2226,7 @@ function wrap(dom) { // gradients (function () { proto.gradient = function (str) { - var grad = eve("savage.util.grad.parse", null, str)[0], + var grad = arrayFirstValue(eve("savage.util.grad.parse", null, str)), el; if (grad.type.toLowerCase() == "l") { el = this.gradientLinear.apply(this, grad.params); @@ -2147,7 +2234,7 @@ function wrap(dom) { el = this.gradientRadial.apply(this, grad.params); } if (grad.type != grad.type.toLowerCase()) { - el.attr({ + $(el.node, { gradientUnits: "userSpaceOnUse" }); } @@ -2195,9 +2282,9 @@ function wrap(dom) { y2 = $(this.node, "y2") || 0; return box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); } else { - var cx = this.node.cx, - cy = this.node.cy, - r = this.node.r; + var cx = this.node.cx || .5, + cy = this.node.cy || .5, + r = this.node.r || 0; return box(cx - r, cy - r, r * 2, r * 2); } } @@ -2216,6 +2303,26 @@ function wrap(dom) { } return el; }; + proto.gradientRadial = function (cx, cy, r, fx, fy) { + var el = make("radialGradient", this.node); + el.stops = stops; + el.addStop = addStop; + el.getBBox = getBBox; + if (cx != null) { + $(el.node, { + cx: cx, + cy: cy, + r: r + }); + } + if (fx != null && fy != null) { + $(el.node, { + fx: fx, + fy: fy + }); + } + return el; + }; }()); }(Paper.prototype)); // Attributes event handlers @@ -2351,6 +2458,15 @@ eve.on("savage.util.attr.transform", function (value) { this.transform(value); eve.stop(); }); +eve.on("savage.util.attr.r", function (value) { + if (this.type == "rect") { + eve.stop(); + $(this.node, { + rx: value, + ry: value + }); + } +}); eve.on("savage.util.attr.text", function (value) { if (this.type == "text") { var i = 0, @@ -2376,15 +2492,24 @@ eve.on("savage.util.attr.text", function (value) { }); // default var availableAttributes = { - rect: {x: 0, y: 0, width: 0, height: 0, rx: 0, ry: 0}, - circle: {cx: 0, cy: 0, r: 0}, - ellipse: {cx: 0, cy: 0, rx: 0, ry: 0}, - line: {x1: 0, y1: 0, x2: 0, y2: 0}, - polyline: {points: ""}, - polygon: {points: ""}, - text: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0}, - tspan: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0}, - path: {d: ""} + rect: {x: 0, y: 0, width: 0, height: 0, rx: 0, ry: 0, "class": 0}, + circle: {cx: 0, cy: 0, r: 0, "class": 0}, + ellipse: {cx: 0, cy: 0, rx: 0, ry: 0, "class": 0}, + line: {x1: 0, y1: 0, x2: 0, y2: 0, "class": 0}, + polyline: {points: "", "class": 0}, + polygon: {points: "", "class": 0}, + text: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0, lengthAdjust: 0, "class": 0}, + tspan: {x: 0, y: 0, dx: 0, dy: 0, rotate: 0, textLength: 0, lengthAdjust: 0, "class": 0}, + textPath: {"xlink:href": 0, startOffset: 0, method: 0, spacing: 0, "class": 0}, + marker: {viewBox: 0, preserveAspectRatio: 0, refX: 0, refY: 0, markerUnits: 0, markerWidth: 0, markerHeight: 0, orient: 0, "class": 0}, + linearGradient: {x1: 0, y1: 0, x2: 0, y2: 0, gradientUnits: 0, gradientTransform: 0, spreadMethod: 0, "xlink:href": 0, "class": 0}, + radialGradient: {cx: 0, cy: 0, r: 0, fx: 0, fy: 0, gradientUnits: 0, gradientTransform: 0, spreadMethod: 0, "xlink:href": 0, "class": 0}, + stop: {offset: 0, "class": 0}, + pattern: {viewBox: 0, preserveAspectRatio: 0, x: 0, y: 0, width: 0, height: 0, patternUnits: 0, patternContentUnits: 0, patternTransform: 0, "xlink:href": 0, "class": 0}, + clipPath: {transform: 0, clipPathUnits: 0, "class": 0}, + mask: {x: 0, y: 0, width: 0, height: 0, maskUnits: 0, maskContentUnits: 0, "class": 0}, + image: {preserveAspectRatio: 0, transform: 0, x: 0, y: 0, width: 0, height: 0, "xlink:href": 0, "class": 0}, + path: {d: "", "class": 0} }; eve.on("savage.util.attr", function (value) { var att = eve.nt(); @@ -2403,6 +2528,12 @@ eve.on("savage.util.getattr.transform", function () { eve.stop(); return this.transform(); })(-1); +eve.on("savage.util.getattr.r", function () { + if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { + eve.stop(); + return $(this.node, "rx"); + } +})(-1); eve.on("savage.util.getattr.viewBox", function () { eve.stop(); var vb = $(this.node, "viewBox").split(separator); @@ -2424,16 +2555,11 @@ eve.on("savage.util.getattr", function () { if (availableAttributes[has](this.type) && availableAttributes[this.type][has](att)) { return this.node.getAttribute(att); } else { - return document.defaultView.getComputedStyle(this.node, null).getPropertyValue(style); + return glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue(style); } }); -eve.on("savage.globals", function () { - return { - savage: Savage, - element: Element, - paper: Paper, - glob: glob - }; -}); +Savage.plugin = function (f) { + f(Savage, Element, Paper, glob); +}; return Savage; }()); \ No newline at end of file diff --git a/test/test.html b/test/test.html index 410f9ad..7fb828e 100644 --- a/test/test.html +++ b/test/test.html @@ -3,19 +3,25 @@ Savage Tests - - - - - - + + + + + + +
    - - - - + + +