/* * DrawingCanvas.js 0.04 * * nanto_vi (TOYAMA Nao), 2005-10-14 * * 0.04 * - CSS Positioning バックエンドで整数でない点が与えられたとき * 線が切れることがあったのを修正。(単に入力を整数に丸めるようにしただけ) * - CSS Positioning バックエンドでのパフォーマンス低下を修正。 * * 0.03 * - CSS Positioning バックエンドのパフォーマンス向上。 * 特にx軸、y軸に平行な直線の描画に効果あり。 * - その他細かい修正。 * * 0.02 * - Safari で HTML Canvas バックエンドのとき * 描画がまったくできなかった問題を修正。 */ // SVG Tiny Backend function DrawingCanvasSVGT(parent, width, height) { if (!parent) throw new Error("No canvas parent!"); if (!width) width = parent.clientWidth; if (!height) height = parent.clientHeight; this.parent = parent; this.width = width; this.height = height; this._bgColor = "none"; this._lineWidth = 2; this._lineColor = "#000"; this._isDrawing = false; this._currentShape = null; this._points = ""; this._dummyDot = null; this._stack = []; this._stackSize = 0; var svg = document.createElementNS(this._XMLNS_SVG, "svg"); svg.setAttribute("width", width); svg.setAttribute("height", height); var rect = document.createElementNS(this._XMLNS_SVG, "rect"); rect.setAttribute("width", width); rect.setAttribute("height", height); rect.setAttribute("fill", this._bgColor); svg.appendChild(rect); var container = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); container.setAttribute("style", "width: " + width + "px; " + "height: " + height + "px; " + "vertical-align: bottom; "); container.appendChild(svg); parent.appendChild(container); this.container = container; this._svgRoot = svg; this._bg = rect; } DrawingCanvasSVGT.prototype = { _XMLNS_SVG: "http://www.w3.org/2000/svg", setBgColor: function (color) { this._bgColor = (color == "transparent") ? "none" : color; this._refresh(); }, setLineColor: function (color) { this._lineColor = color; }, setLineWidth: function (width) { this._lineWidth = Number(width) || 0; }, startLine: function (x, y) { if (this._isDrawing) this.endLine(); this._isDrawing = true; var dot = document.createElementNS(this._XMLNS_SVG, "circle"); dot.setAttribute("cx", x); dot.setAttribute("cy", y); dot.setAttribute("r", this._lineWidth / 2); this._svgRoot.appendChild(dot); dot.setAttribute("fill", this._lineColor); this._dummyDot = dot; this._points = x + "," + y; var polyline = document.createElementNS(this._XMLNS_SVG, "polyline"); polyline.setAttribute("fill", "none"); polyline.setAttribute("stroke", this._lineColor); polyline.setAttribute("stroke-width", this._lineWidth); polyline.setAttribute("stroke-linecap", "round"); polyline.setAttribute("stroke-linejoin", "round"); polyline.setAttribute("points", this._points); this._svgRoot.appendChild(polyline); this._currentShape = polyline; }, endLine: function () { if (!this._isDrawing) return; if (this._dummyDot) { this._svgRoot.removeChild(this._currentShape); this._pushStack(this._dummyDot); this._dummyDot = null; } else { this._pushStack(this._currentShape); } this._isDrawing = false; this._currentShape = null; this._points = ""; }, lineTo: function (x, y) { if (!this._isDrawing) return; if (this._dummyDot) { this._svgRoot.removeChild(this._dummyDot); this._dummyDot = null; } this._points += " " + x + "," + y; this._currentShape.setAttribute("points", this._points); }, undo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize <= 0) return false; this._svgRoot.removeChild(this._stack[--this._stackSize]); this._refresh(); return true; }, redo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize >= this._stack.length) return false; this._svgRoot.appendChild(this._stack[this._stackSize++]); this._refresh(); return true; }, clear: function () { if (this._isDrawing) this.endLine(); for (var i = this._stackSize; i--;) this._svgRoot.removeChild(this._stack[i]); this._refresh(); this._stack.length = 0; this._stackSize = 0; }, getX: function () { var box = this.container; var x = box.offsetLeft; while ((box = box.offsetParent)) x += box.offsetLeft; return x; }, getY: function () { var box = this.container; var y = box.offsetTop; while ((box = box.offsetParent)) y += box.offsetTop; return y; }, _pushStack: function (shape) { if (this._stackSize + 1 < this._stack.length) this._stack.length = this._stackSize + 1; this._stack[this._stackSize++] = shape; }, _refresh: function () { this._bg.setAttribute("fill", this._bgColor); } }; // HTML Canvas Backend function DrawingCanvasCanvas(parent, width, height) { if (!parent) throw new Error("No canvas parent!"); if (!width) width = parent.clientWidth; if (!height) height = parent.clientHeight; this.parent = parent; this.width = width; this.height = height; this._bgColor = "transparent"; this._lineWidth = 2; this._lineColor = "#000"; this._isDrawing = false; this._points = null; this._stack = []; this._stackSize = 0; var canvas = document.createElement("canvas"); // Apple の文献によると id は必須とのことなので念のために追加 // http://developer.apple.com/documentation/AppleApplications/Reference/SafariJSRef/Classes/Canvas.html#//apple_ref/js/cl/Canvas canvas.setAttribute("id", "DCCanvas" + (new Date()).getTime() + ++arguments.callee._count); canvas.setAttribute("width", width); canvas.setAttribute("height", height); var container = document.createElement("div"); container.setAttribute("style", "width: " + width + "px; " + "height: " + height + "px; " + "vertical-align: bottom; "); container.appendChild(canvas); parent.appendChild(container); var context = canvas.getContext("2d") context.lineCap = "round"; context.lineJoin = "round"; this.container = container; this._context = context; } DrawingCanvasCanvas._count = 0; DrawingCanvasCanvas.prototype = { setBgColor: function (color) { this._bgColor = color; this._refresh(); }, setLineColor: function (color) { this._lineColor = color; }, setLineWidth: function (width) { this._lineWidth = Number(width) || 0; }, startLine: function (x, y) { if (this._isDrawing) this.endLine(); this._isDrawing = true; this._context.strokeStyle = this._lineColor; this._context.fillStyle = this._lineColor; this._context.lineWidth = this._lineWidth; this._context.arc(x, y, this._lineWidth / 2, 0, 2 * Math.PI, true); this._context.fill(); this._points = [x, y]; this._currentX = x; this._currentY = y; this._currentLineColor = this._lineColor; this._currentLineWidth = this._lineWidth; }, endLine: function () { if (!this._isDrawing) return; this._isDrawing = false; this._pushStack(this._points, this._currentLineColor, this._currentLineWidth); this._points = null; }, lineTo: function (x, y) { if (!this._isDrawing) return; this._context.moveTo(this._currentX, this._currentY); this._context.lineTo(x, y); this._context.stroke(); this._points.push(x, y); this._currentX = x; this._currentY = y; }, undo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize <= 0) return false; --this._stackSize; this._refresh(); return true; }, redo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize >= this._stack.length) return false; ++this._stackSize; this._refresh(); return true; }, clear: function () { if (this._isDrawing) this.endLine(); this._stack.length = 0; this._stackSize = 0; this._refresh(); }, getX: function () { var box = this.container; var x = box.offsetLeft; while ((box = box.offsetParent)) x += box.offsetLeft; return x; }, getY: function () { var box = this.container; var y = box.offsetTop; while ((box = box.offsetParent)) y += box.offsetTop; return y; }, _pushStack: function (points, lineColor, lineWidth) { if (this._stackSize + 1 < this._stack.length) this._stack.length = this._stackSize + 1; this._stack[this._stackSize++] = { type: (points.length == 2) ? "dot" : "polyline", points: points, lineColor: lineColor, lineWidth: lineWidth }; }, _refresh: function () { if (this._bgColor == "transparent") { this._context.clearRect(0, 0, this.width, this.height); } else { this._context.fillStyle = this._bgColor; this._context.fillRect(0, 0, this.width, this.height); } for (var i = 0; i < this._stackSize; i++) { var shape = this._stack[i]; if (shape.type == "dot") { this._context.fillStyle = shape.lineColor; this._context.arc(shape.points[0], shape.points[1], shape.lineWidth / 2, 0, this._MATH_2PI, true); this._context.fill(); } else { this._context.strokeStyle = shape.lineColor; this._context.lineWidth = shape.lineWidth; this._context.moveTo(shape.points[0], shape.points[1]); for (var j = 2, n = shape.points.length; j < n; j += 2) this._context.lineTo(shape.points[j], shape.points[j + 1]); this._context.stroke(); } } } }; // CSS Positioning Backend function DrawingCanvasCSSP(parent, width, height) { if (!parent) throw new Error("No canvas parent!"); if (!width) width = parent.clientWidth; if (!height) height = parent.clientHeight; this.parent = parent; this.width = width; this.height = height; this._bgColor = "transparent"; this._lineWidth = 2; this._lineColor = "#000"; this._isDrawing = false; this._currentLayer = null; this._stack = []; this._stackSize = 0; this._dotStyle = ""; this._currentLineWidth = 0; this._dotOffset = 0; this._dotX = 0; this._dotY = 0; var container = document.createElement("div"); var containerStyle = "background-color: " + this._bgColor + "; " + "position: relative; " + "width: " + width + "px; " + "height: " + height + "px; " + "overflow: hidden; "; if (this._useCssText) container.style.cssText = containerStyle; else container.setAttribute("style", containerStyle); parent.appendChild(container); this.container = container; } DrawingCanvasCSSP.prototype = { _useCssText: document.documentElement.getAttribute("style") == document.documentElement.style, setBgColor: function (color) { this.container.style.backgroundColor = this._bgColor = color; }, setLineColor: function (color) { this._lineColor = color; }, setLineWidth: function (width) { this._lineWidth = Number(width) || 0; }, startLine: function (x, y) { if (this._isDrawing) this.endLine(); this._isDrawing = true; x = Math.round(x); y = Math.round(y); var currentLineWidth = this._lineWidth; this._dotStyle = "background-color: " + this._lineColor + "; " + "position: absolute; " + "overflow: hidden; "; this._dotOffset = Math.floor(currentLineWidth / 2); this._dotX = x - this._dotOffset; this._dotY = y - this._dotOffset; var layer = document.createElement("div"); var dot = document.createElement("div"); var dotStyle = this._dotStyle + "width: " + currentLineWidth + "px; " + "height: " + currentLineWidth + "px; " + "left: " + this._dotX + "px; " + "top: " + this._dotY + "px; "; if (this._useCssText) dot.style.cssText = dotStyle; else dot.setAttribute("style", dotStyle); layer.appendChild(dot); this.container.appendChild(layer); this._currentLineWidth = currentLineWidth; this._currentLayer = layer; }, endLine: function () { if (!this._isDrawing) return; this._isDrawing = false; this._pushStack(this._currentLayer); this._currentLineWidth = 0; this._currentLayer = null; }, lineTo: function (x, y) { if (!this._isDrawing) return; x = Math.round(x); y = Math.round(y); // ライン・ルーチン (1)線分描画のアルゴリズム - Fussy's HOMEPAGE // http://www2.starcat.ne.jp/~fussy/algo/algo1-1.htm var currentX = this._dotX; var currentY = this._dotY; var endX = x - this._dotOffset; var endY = y - this._dotOffset; var dirX = (currentX < endX) ? 1 : -1; var dirY = (currentY < endY) ? 1 : -1; var dx = (dirX == 1) ? endX - currentX : currentX - endX; var dy = (dirY == 1) ? endY - currentY : currentY - endY; var error, errorIncrement, errorCorrection; if (dx == 0 && dy == 0) return; var fragment = document.createDocumentFragment(); var dotStyleCommon = this._dotStyle; var useCssText = this._useCssText; var lineWidth = this._currentLineWidth; var lineLength = lineWidth; if (dx >= dy) { var left, dot, dotStyle; var isPositiveDir = (dirX == 1); error = -dx; errorIncrement = dy << 1; errorCorrection = dx << 1; for (var i = 0; i < dx; ++i) { error += errorIncrement; if (error >= 0) { left = isPositiveDir ? currentX - lineLength + lineWidth : currentX; dot = document.createElement("div"); dotStyle = dotStyleCommon + "width: " + lineLength + "px; " + "height: " + lineWidth + "px; " + "left: " + left + "px; " + "top: " + currentY + "px; "; if (useCssText) dot.style.cssText = dotStyle; else dot.setAttribute("style", dotStyle); fragment.appendChild(dot); currentY += dirY; error -= errorCorrection; lineLength = lineWidth; } else { ++lineLength; } // if (error >= 0) currentX += dirX; } // for (var i = 0; i < dx; ++i) left = isPositiveDir ? endX - lineLength + lineWidth : endX; dot = document.createElement("div"); dotStyle = dotStyleCommon + "width: " + lineLength + "px; " + "height: " + lineWidth + "px; " + "left: " + left + "px; " + "top: " + endY + "px; "; if (useCssText) dot.style.cssText = dotStyle; else dot.setAttribute("style", dotStyle); fragment.appendChild(dot); } else { // == if (dx < dy) var top, dot, dotStyle; var isPositiveDir = (dirY == 1); error = -dy; errorIncrement = dx << 1; errorCorrection = dy << 1; for (var i = 0; i < dy; ++i) { error += errorIncrement; if (error >= 0) { top = isPositiveDir ? currentY - lineLength + lineWidth : currentY; dot = document.createElement("div"); dotStyle = dotStyleCommon + "width: " + lineWidth + "px; " + "height: " + lineLength + "px; " + "left: " + currentX + "px; " + "top: " + top + "px; "; if (useCssText) dot.style.cssText = dotStyle; else dot.setAttribute("style", dotStyle); fragment.appendChild(dot); currentX += dirX; error -= errorCorrection; lineLength = lineWidth; } else { ++lineLength; } // if (error >= 0) currentY += dirY; } // for (var i = 0; i < dy; ++i) top = isPositiveDir ? endY - lineLength + lineWidth : endY; dot = document.createElement("div"); dotStyle = dotStyleCommon + "width: " + lineWidth + "px; " + "height: " + lineLength + "px; " + "left: " + endX + "px; " + "top: " + top + "px; "; if (useCssText) dot.style.cssText = dotStyle; else dot.setAttribute("style", dotStyle); fragment.appendChild(dot); } // if (dx >= dy) this._currentLayer.appendChild(fragment); this._dotX = endX; this._dotY = endY; }, undo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize <= 0) return false; this.container.removeChild(this._stack[--this._stackSize]); return true; }, redo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize >= this._stack.length) return false; this.container.appendChild(this._stack[this._stackSize++]); return true; }, clear: function () { if (this._isDrawing) this.endLine(); for (var i = this._stackSize; i--;) this.container.removeChild(this._stack[i]); this._stack.length = 0; this._stackSize = 0; }, _htmlIsRoot: (typeof document.compatMode == "string") && (document.compatMode == "CSS1Compat"), getX: function () { var box = this.container; var x = box.offsetLeft; while ((box = box.offsetParent)) x += box.offsetLeft; if (this._useCssText) x += (this._htmlIsRoot ? document.documentElement : document.body).clientLeft; return x; }, getY: function () { var box = this.container; var y = box.offsetTop; while ((box = box.offsetParent)) y += box.offsetTop; if (this._useCssText) y += (this._htmlIsRoot ? document.documentElement : document.body).clientTop; return y; }, _pushStack: function (layer) { if (this._stackSize + 1 < this._stack.length) this._stack.length = this._stackSize + 1; this._stack[this._stackSize++] = layer; } }; // VML Backend function DrawingCanvasVML(parent, width, height) { if (!document.namespaces) throw new Error("Not supported!"); if (!document.namespaces.v) { document.namespaces.add("v", "urn:schemas-microsoft-com:vml"); document.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);"); } if (!parent) throw new Error("No canvas parent!"); if (!width) width = parent.clientWidth; if (!height) height = parent.clientHeight; this.parent = parent; this.width = width; this.height = height; this._bgColor = "none"; this._lineWidth = 2; this._lineColor = "#000"; this._isDrawing = false; this._currentShape = null; this._dummyDot = null; this._points = ""; this._stack = []; this._stackSize = 0; var container = document.createElement("div"); container.style.cssText = "position: relative; " + "width: " + width + "px; " + "height: " + height + "px; " + "overflow: hidden; "; parent.appendChild(container); this.container = container; } DrawingCanvasVML.prototype = { setBgColor: function (color) { this.container.style.backgroundColor = this._bgColor = color; }, setLineColor: function (color) { this._lineColor = color; }, setLineWidth: function (width) { this._lineWidth = Number(width) || 0; }, startLine: function (x, y) { if (this._isDrawing) this.endLine(); this._isDrawing = true; var dot = document.createElement("v:oval"); var size = this._lineWidth dot.fillcolor = this._lineColor; dot.strokecolor = this._lineColor; //dot.stroked = false; dot.style.cssText = "position: absolute; " + "width: " + size + "px; " + "height: " + size + "px; " + "left: " + (x - size / 2) + "px; " + "top: " + (y - size / 2) + "px; "; this.container.appendChild(dot); this._dummyDot = dot; this._points = x + "," + y; var polyline = document.createElement("v:polyline"); polyline.filled = false; polyline.strokecolor = this._lineColor; polyline.strokeweight = this._lineWidth; polyline.points = this._points; var stroke = document.createElement("v:stroke"); stroke.endcap = "round"; polyline.appendChild(stroke); this.container.appendChild(polyline); this._currentShape = polyline; }, endLine: function () { if (!this._isDrawing) return; if (this._dummyDot) { this.container.removeChild(this._currentShape); this._pushStack(this._dummyDot); this._dummyDot = null; } else { this._pushStack(this._currentShape); } this._isDrawing = false; this._currentShape = null; this._points = ""; }, lineTo: function (x, y) { if (!this._isDrawing) return; if (this._dummyDot) { this.container.removeChild(this._dummyDot); this._dummyDot = null; } this._points += " " + x + "," + y; this._currentShape.points.value = this._points; }, undo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize <= 0) return false; this._stack[--this._stackSize].style.visibility = "hidden"; return true; }, redo: function () { if (this._isDrawing) this.endLine(); if (this._stackSize >= this._stack.length) return false; this._stack[this._stackSize++].style.visibility = "visible"; return true; }, clear: function () { if (this._isDrawing) this.endLine(); for (var i = this._stack.length; i--;) this.container.removeChild(this._stack[i]); this._stack.length = 0; this._stackSize = 0; }, _htmlIsRoot: (typeof document.compatMode == "string") && (document.compatMode == "CSS1Compat"), getX: function () { var box = this.container; var x = box.offsetLeft; while ((box = box.offsetParent)) x += box.offsetLeft; x += (this._htmlIsRoot ? document.documentElement : document.body).clientLeft; return x; }, getY: function () { var box = this.container; var y = box.offsetTop; while ((box = box.offsetParent)) y += box.offsetTop; y += (this._htmlIsRoot ? document.documentElement : document.body).clientTop; return y; }, _pushStack: function (shape) { var stackLength = this._stack.length; if (this._stackSize < stackLength) { for (var i = this._stackSize; i < stackLength; i++) this.container.removeChild(this._stack[i]); this._stack.length = this._stackSize + 1; } this._stack[this._stackSize++] = shape; } }; function DrawingCanvas() { var backend = "CSSP"; if (document.namespaces && document.namespaces.add) backend = "VML"; try { if (document.createElement("canvas").getContext) backend = "Canvas"; } catch (e) {} if (document.createElementNS) { if (document.implementation.hasFeature("org.w3c.svg", null)) backend = "SVGT"; if (window.opera) { var ua = navigator.userAgent; var index = ua.indexOf("Opera"); if (index >= 0 && parseInt(ua.substring(index + 6)) >= 8) backend = "SVGT"; } } switch (backend) { case "SVGT": DrawingCanvas = DrawingCanvasSVGT; break; case "Canvas": DrawingCanvas = DrawingCanvasCanvas; break; case "CSSP": DrawingCanvas = DrawingCanvasCSSP; break; case "VML": DrawingCanvas = DrawingCanvasVML; break; } DrawingCanvas.backend = backend; DrawingCanvas.prototype.constructor = DrawingCanvas; } DrawingCanvas();