diff --git a/src/editor/Editor.js b/src/editor/Editor.js index b4e054d..c827b3f 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -10,11 +10,10 @@ define( function(require) { var lang = require('common/lang'); - var selectShape = require('render/util/selectShape'); var computeBoundingBox = require('graphics/computeBoundingBox'); var pathAdjust = require('render/util/pathAdjust'); var glyf2path = require('ttf/util/glyf2path'); - var ShapeGroup = require('./ShapeGroup'); + var editorMode = require('./mode/editorMode'); /** * 初始化 @@ -31,89 +30,69 @@ define( render.camera.center.x = e.x; render.camera.center.y = e.y; render.camera.scale *= ratio; - render.painter.refresh(); render.camera.ratio = 1; }); render.capture.on('down', function(e) { - - var result = render.getLayer('cover').getShapeIn(e); - - if(result) { - if (me.currentGroup) { - me.currentPoint = lang.clone(result[0]); - me.currentGroup.beginTransform(me.currentPoint); - } - } - else { - - if (me.currentGroup) { - me.currentGroup.dispose(); - me.currentGroup = null; - } - - result = render.getLayer('font').getShapeIn(e); - - if(result) { - var shape = result[0]; - if (result.length > 1) { - shape = selectShape(result); - } - me.currentGroup = new ShapeGroup(shape, render); - } - } - render.camera.startx = e.x; render.camera.starty = e.y; render.camera.x = e.x; render.camera.y = e.y; + render.camera.event = e; + + me.mode.down && me.mode.down.call(me, e); }); render.capture.on('drag', function(e) { + render.camera.mx = e.x - render.camera.x; + render.camera.my = e.y - render.camera.y; + render.camera.x = e.x; + render.camera.y = e.y; + render.camera.event = e; - if(me.currentGroup) { - - var mx = render.camera.x; - var my = render.camera.y; - - render.camera.x = e.x; - render.camera.y = e.y; - render.camera.event = e; - - if (me.currentPoint) { - me.currentGroup.transform(me.currentPoint, render.camera); - } - else { - me.currentGroup.move(e.x - mx, e.y - my); - } - } + me.mode.drag && me.mode.drag.call(me, e); }); render.capture.on('dragend', function(e) { - if (me.currentGroup) { - if (me.currentPoint) { - me.currentGroup.finishTransform(me.currentPoint); - me.currentPoint = null; - } - } - + render.camera.x = e.x; + render.camera.y = e.y; + render.camera.event = e; + + me.mode.dragend && me.mode.dragend.call(me, e); }); + + render.capture.on('dblclick', function(e) { + me.mode.end.call(me, e); + if(me.mode === editorMode.bound) { + me.mode = editorMode.point; + + } + else if(me.mode === editorMode.point){ + me.mode = editorMode.bound; + } + me.mode.begin.call(me, e); + }); + } function initLayer() { this.render.addLayer('cover', { level: 30, stroke: true, - strokeColor: 'green' + fill: false, + strokeColor: 'green', + fillColor: 'white', }); this.render.addLayer('font', { - level: 20 + level: 20, + strokeColor: 'red' }); this.render.addLayer('axis', { level: 10, - stroke: true + stroke: true, + fill: false }); } @@ -160,7 +139,7 @@ define( return shape; }); - this.shapes = shapes; + this.font = font; // 渲染形状 @@ -168,12 +147,19 @@ define( var fontLayer = this.render.painter.getLayer('font'); - this.shapes.forEach(function(shape) { + shapes.forEach(function(shape) { fontLayer.addShape('path', shape); }); this.render.refresh(); + if (this.mode) { + this.mode.end.call(this); + } + + this.mode = editorMode.bound; + this.mode.begin.call(this); + return this; }; diff --git a/src/editor/ShapeGroup.js b/src/editor/group/ShapeGroup.js similarity index 99% rename from src/editor/ShapeGroup.js rename to src/editor/group/ShapeGroup.js index 1cba45c..88a1e9c 100644 --- a/src/editor/ShapeGroup.js +++ b/src/editor/group/ShapeGroup.js @@ -3,13 +3,13 @@ * @author mengke01 * @date * @description - * 字体编辑控制器 + * 形状编辑组 */ define( function(require) { - var adjustShape = require('./util/adjustShape'); + var adjustShape = require('../util/adjustShape'); var lang = require('common/lang'); /** diff --git a/src/editor/mode/bound.js b/src/editor/mode/bound.js new file mode 100644 index 0000000..ebdc0c3 --- /dev/null +++ b/src/editor/mode/bound.js @@ -0,0 +1,121 @@ +/** + * @file bound.js + * @author mengke01 + * @date + * @description + * 轮廓模式处理事件 + */ + + +define( + function(require) { + + var ShapeGroup = require('../group/ShapeGroup'); + var lang = require('common/lang'); + var selectShape = require('render/util/selectShape'); + + var POS_CUSOR = require('./cursor'); + + var boundMode = { + + name: 'bound', + + /** + * 按下事件 + */ + down: function(e) { + var render = this.render; + var result = render.getLayer('cover').getShapeIn(e); + if(result) { + if (this.currentGroup) { + this.currentPoint = lang.clone(result[0]); + this.currentGroup.beginTransform(this.currentPoint); + } + } + else { + + if (this.currentGroup) { + this.currentGroup.dispose(); + this.currentGroup = null; + } + + result = render.getLayer('font').getShapeIn(e); + + if(result) { + var shape = result[0]; + if (result.length > 1) { + shape = selectShape(result); + } + this.currentGroup = new ShapeGroup(shape, render); + } + } + }, + + /** + * 拖动事件 + */ + drag: function(e) { + var render = this.render; + var camera = render.camera; + if(this.currentGroup) { + if (this.currentPoint) { + this.currentGroup.transform(this.currentPoint, camera); + } + else { + this.currentGroup.move(camera.mx, camera.my); + } + } + }, + + /** + * 拖动结束事件 + */ + dragend: function(e) { + if (this.currentGroup) { + if (this.currentPoint) { + this.currentGroup.finishTransform(this.currentPoint); + this.currentPoint = null; + } + } + }, + + /** + * 开始模式 + */ + begin: function() { + var me = this; + var coverLayer = me.render.getLayer('cover'); + // 注册鼠标样式 + me.render.capture.on('move', me.__moveEvent = function (e) { + var shapes = coverLayer.getShapeIn(e); + if(shapes) { + me.render.setCursor(POS_CUSOR[shapes[0].pos] || 'default'); + } + else { + me.render.setCursor('default'); + } + }); + }, + + /** + * 结束模式 + */ + end: function() { + + if (this.currentGroup) { + if (this.currentPoint) { + this.currentGroup.finishTransform(this.currentPoint); + this.currentPoint = null; + } + this.currentGroup.dispose(); + this.currentGroup = null; + } + + this.render.capture.un('move', this.__moveEvent); + this.render.setCursor('default'); + } + }; + + return boundMode; + } +); diff --git a/src/editor/mode/cursor.js b/src/editor/mode/cursor.js new file mode 100644 index 0000000..f8df50a --- /dev/null +++ b/src/editor/mode/cursor.js @@ -0,0 +1,24 @@ +/** + * @file cursor.js + * @author mengke01 + * @date + * @description + * 光标集合 + */ + + +define( + function(require) { + // 不同位置的光标集合 + return { + 1: 'nw-resize', + 2: 'ne-resize', + 3: 'se-resize', + 4: 'sw-resize', + 5: 's-resize', + 6: 'e-resize', + 7: 'n-resize', + 8: 'w-resize' + };; + } +); diff --git a/src/editor/mode/editorMode.js b/src/editor/mode/editorMode.js new file mode 100644 index 0000000..834f94d --- /dev/null +++ b/src/editor/mode/editorMode.js @@ -0,0 +1,17 @@ +/** + * @file editorMode.js + * @author mengke01 + * @date + * @description + * 编辑器模式集合 + */ + + +define( + function(require) { + return { + 'bound': require('./bound'), + 'point': require('./point') + }; + } +); diff --git a/src/editor/mode/point.js b/src/editor/mode/point.js new file mode 100644 index 0000000..b1adf46 --- /dev/null +++ b/src/editor/mode/point.js @@ -0,0 +1,150 @@ +/** + * @file point.js + * @author mengke01 + * @date + * @description + * 点编辑模式 + */ + + +define( + function(require) { + + var pathIterator = require('render/util/pathIterator'); + var computeBoundingBox = require('graphics/computeBoundingBox'); + var pathAdjust = require('render/util/pathAdjust'); + + var pointMode = { + name: 'point', + + /** + * 按下事件 + */ + down: function(e) { + var render = this.render; + var result = render.getLayer('cover').getShapeIn(e); + + if(result) { + this.currentPoint = result[0]; + } + }, + + /** + * 拖动事件 + */ + drag: function(e) { + var render = this.render; + var camera = render.camera; + if(this.currentPoint) { + + this.currentPoint.x += camera.mx; + this.currentPoint.y += camera.my; + + this.currentPoint._point.x += camera.mx; + this.currentPoint._point.y += camera.my; + + render.getLayer('cover').refresh(); + render.getLayer('font').refresh(); + + this.modifiedShape[this.currentPoint._shape] = true; + } + }, + + /** + * 拖动结束事件 + */ + dragend: function(e) { + this.currentPoint = null; + }, + + begin: function() { + + var controls = []; + var shapes = this.render.getLayer('font').shapes; + + shapes.forEach(function(shape) { + pathIterator(shape.points, function(c, i, p0, p1, p2) { + if(c == 'M' || c == 'L') { + controls.push({ + type: 'point', + x: shape.x + p0.x, + y: shape.y + p0.y, + _point: p0, + _shape: shape.id + }); + } + else if (c == 'Q') { + controls.push({ + type: 'cpoint', + x: shape.x + p1.x, + y: shape.y + p1.y, + _point: p1, + _shape: shape.id + }); + controls.push({ + type: 'point', + x: shape.x + p2.x, + y: shape.y + p2.y, + _point: p2, + _shape: shape.id + }); + } + }); + }); + + var coverLayer = this.render.getLayer('cover'); + coverLayer.options.fill = true; + + controls.forEach(function(shape){ + coverLayer.addShape(shape); + }); + coverLayer.refresh(); + + var me = this; + // 注册鼠标样式 + me.render.capture.on('move', me.__moveEvent = function (e) { + var shape = coverLayer.getShapeIn(e); + if(shape) { + me.render.setCursor('pointer'); + } + else { + me.render.setCursor('default'); + } + }); + + this.modifiedShape = {}; + }, + + end: function() { + + // 重新调整shape大小和位置 + var shapes = Object.keys(this.modifiedShape); + if(shapes.length) { + var fontLayer = this.render.getLayer('font'); + shapes.forEach(function(shapeId) { + var shape = fontLayer.getShape(shapeId); + var pathBox = computeBoundingBox.computePathBox(shape.points); + shape.width = pathBox.width; + shape.height = pathBox.height; + shape.x = shape.x + pathBox.x; + shape.y = shape.y + pathBox.y; + shape.points = pathAdjust(shape.points, 1, -pathBox.x, -pathBox.y); + }); + fontLayer.refresh(); + } + + this.modifiedShape = null; + + var coverLayer = this.render.getLayer('cover'); + coverLayer.options.fill = false; + coverLayer.clearShapes(); + coverLayer.refresh(); + + this.render.capture.un('move', this.__moveEvent); + this.render.setCursor('default'); + } + }; + + return pointMode; + } +); diff --git a/src/editor/util/adjustShape.js b/src/editor/util/adjustShape.js index 4981455..bf83cf0 100644 --- a/src/editor/util/adjustShape.js +++ b/src/editor/util/adjustShape.js @@ -10,14 +10,12 @@ define( function(require) { + var pathIterator = require('render/util/pathIterator'); + /** * 根据相对值,调整shape大小 */ function adjustShape(shape, matrix) { - var i = -1; - var l = shape.points.length; - var point; - var scaleX = matrix[2]; var scaleY = matrix[3]; var offsetX = 0; @@ -31,22 +29,18 @@ define( offsetY = -shape.height; } - while (++i < l) { - var point = shape.points[i]; - switch (point.c) { - case 'M': - case 'L': - point.p.x = scaleX * (point.p.x + offsetX); - point.p.y = scaleY * (point.p.y + offsetY); - break; - case 'Q': - point.p.x = scaleX * (point.p.x + offsetX); - point.p.y = scaleY * (point.p.y + offsetY); - point.p1.x = scaleX * (point.p1.x + offsetX); - point.p1.y = scaleY * (point.p1.y + offsetY); - break; + pathIterator(shape.points, function(c, i, p0, p1, p2) { + if (c == 'Q') { + p1.x = scaleX * (p1.x + offsetX); + p1.y = scaleY * (p1.y + offsetY); + p2.x = scaleX * (p2.x + offsetX); + p2.y = scaleY * (p2.y + offsetY); } - } + else { + p0.x = scaleX * (p0.x + offsetX); + p0.y = scaleY * (p0.y + offsetY); + } + }); shape.x = matrix[0]; shape.y = matrix[1]; diff --git a/src/graphics/computeBoundingBox.js b/src/graphics/computeBoundingBox.js index fd6b884..5f64340 100644 --- a/src/graphics/computeBoundingBox.js +++ b/src/graphics/computeBoundingBox.js @@ -103,7 +103,6 @@ define( */ function computePathBoundingBox(path) { var l = path.length; - var xMax = xMin = yMax = yMin = 0; var i = -1; var points = []; while (++i < l) { @@ -111,6 +110,7 @@ define( switch (point.c) { case 'M': case 'L': + case 'Z': points.push(point.p); break; case 'Q': @@ -135,10 +135,41 @@ define( return computeBoundingBox(points); } + + /** + * 计算曲线点边界 + * + * @private + * @param {path} path对象 + * + * @return {Object} x,y,width,height + */ + function computePathBox(path) { + var l = path.length; + var i = -1; + var points = []; + while (++i < l) { + var point = path[i]; + switch (point.c) { + case 'M': + case 'L': + case 'Z': + points.push(point.p); + break; + case 'Q': + points.push(point.p1); + points.push(point.p); + break; + } + } + return computeBoundingBox(points); + } + return { computeBounding: computeBoundingBox, quadraticBezier: computeQuadraticBezierBoundingBox, - computePath: computePathBoundingBox + computePath: computePathBoundingBox, + computePathBox: computePathBox, }; } ); diff --git a/src/render/Layer.js b/src/render/Layer.js index f74d1e5..07e0fea 100644 --- a/src/render/Layer.js +++ b/src/render/Layer.js @@ -33,17 +33,21 @@ define( * 设置canvas的绘制样式 */ function setContextStyle(context, options) { + context.fillStyle = options.fillColor || 'black'; + context.strokeStyle = options.strokeColor || 'black'; + context.strokeWidth = options.strokeWidth || 1; + } - if(options.fillColor) { - context.fillStyle = options.fillColor; + /** + * 绘制图形 + */ + function draw(context, options) { + if(false !== options.stroke) { + context.stroke(); } - if(options.strokeColor) { - context.strokeStyle = options.strokeColor; - } - - if(options.strokeWidth) { - context.strokeWidth = options.strokeWidth || 1; + if(false !== options.fill) { + context.fill(); } } @@ -68,7 +72,12 @@ define( */ function Layer(context, options) { this.context = context; - this.options = options || {}; + + this.options = lang.extend({ + stroke: true, + fill: true + }, options); + this.painter = this.options.painter; this.id = this.options.id || guid('layer'); this.level = this.options.level; @@ -86,15 +95,13 @@ define( */ refresh: function() { var support = this.painter.support; - - this.context.clearRect(0, 0, this.painter.width, this.painter.height); - - - setContextStyle(this.context, this.options); - - this.context.beginPath(); var context = this.context; + var options = this.options; var camera = this.painter.camera; + + context.clearRect(0, 0, this.painter.width, this.painter.height); + setContextStyle(context, options); + context.beginPath(); this.shapes.forEach(function(shape) { var drawer = support[shape.type]; @@ -103,17 +110,28 @@ define( if(camera.ratio != 1) { drawer.adjust(shape, camera); } - drawer.draw(context, shape); + + if(shape.style) { + // 绘制之前shape + draw(context, options); + + // 绘制当前shape + context.beginPath(); + setContextStyle(context, shape.style); + drawer.draw(context, shape); + draw(context, options); + + // 重置 + setContextStyle(context, options); + context.beginPath(); + } + else { + drawer.draw(context, shape); + } } }); - - if(this.options.stroke) { - this.context.stroke(); - } - else { - this.context.fill(); - } + draw(context, options); return this; }, diff --git a/src/render/render.js b/src/render/render.js index fa7baf2..5870df6 100644 --- a/src/render/render.js +++ b/src/render/render.js @@ -53,7 +53,7 @@ define( function Render(main, options) { this.main = main; - this.options = options || {}; + this.options = lang.extend({}, options); this.id = guid(); if(!this.main) { @@ -63,6 +63,16 @@ define( init.call(this); } + /** + * 设置鼠标样式 + * + * @param {string} name 名字 + * @return {Render} 本对象 + */ + Render.prototype.setCursor = function(name) { + this.main.style.cursor = name || 'default'; + }; + /** * 刷新render * diff --git a/src/render/shape/CirclePoint.js b/src/render/shape/CirclePoint.js index b304520..facf32a 100644 --- a/src/render/shape/CirclePoint.js +++ b/src/render/shape/CirclePoint.js @@ -10,17 +10,12 @@ define( function(require) { - var POINT_SIZE = 6; + var POINT_SIZE = 2; var proto = { type: 'cpoint', - // 调整大小 - adjust: function(shape, camera) { - return shape; - }, - /** * 获取shape的矩形区域 * @@ -45,7 +40,7 @@ define( * @param {boolean} 是否 */ isIn: function(shape, x, y) { - return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(POINT_SIZE, 2); + return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(POINT_SIZE * 2, 2); }, /** diff --git a/src/render/shape/DashedRect.js b/src/render/shape/DashedRect.js index 06494df..8f240e3 100644 --- a/src/render/shape/DashedRect.js +++ b/src/render/shape/DashedRect.js @@ -10,6 +10,8 @@ define( function(require) { + var dashedLineTo = require('../util/dashedLineTo'); + var proto = { type: 'dashedrect', @@ -50,11 +52,10 @@ define( draw: function(ctx, shape) { var w = shape.width; var h = shape.height; - ctx.moveTo(shape.x, shape.y); - ctx.lineTo(shape.x + w, shape.y); - ctx.lineTo(shape.x + w, shape.y + h); - ctx.lineTo(shape.x, shape.y + h); - ctx.lineTo(shape.x, shape.y); + dashedLineTo(ctx, shape.x, shape.y, shape.x + w, shape.y); + dashedLineTo(ctx, shape.x + w, shape.y, shape.x + w, shape.y + h); + dashedLineTo(ctx, shape.x + w, shape.y + h, shape.x, shape.y + h); + dashedLineTo(ctx, shape.x, shape.y + h, shape.x, shape.y); } }; diff --git a/src/render/shape/Point.js b/src/render/shape/Point.js index 1ccf5cc..f2aba4a 100644 --- a/src/render/shape/Point.js +++ b/src/render/shape/Point.js @@ -10,16 +10,11 @@ define( function(require) { - var POINT_SIZE = 8; // 控制点的大小 + var POINT_SIZE = 6; // 控制点的大小 var proto = { type: 'point', - - // 调整大小 - adjust: function(shape, camera) { - return shape; - }, /** * 获取shape的矩形区域 @@ -45,8 +40,8 @@ define( * @param {boolean} 是否 */ isIn: function(shape, x, y) { - var w = POINT_SIZE / 2; - var h = POINT_SIZE / 2; + var w = POINT_SIZE; + var h = POINT_SIZE; return x <= shape.x + w && x >= shape.x - w && y <= shape.y + h diff --git a/src/render/shape/support.js b/src/render/shape/support.js index 5659c03..9e4aaf8 100644 --- a/src/render/shape/support.js +++ b/src/render/shape/support.js @@ -14,7 +14,7 @@ define( cpoint: require('./CirclePoint'), rect: require('./Rect'), dashedrect: require('./DashedRect'), - point: require('./point'), + point: require('./Point'), font: require('./Font'), path: require('./Path') }; diff --git a/src/render/util/dashedLineTo.js b/src/render/util/dashedLineTo.js new file mode 100644 index 0000000..ad99503 --- /dev/null +++ b/src/render/util/dashedLineTo.js @@ -0,0 +1,55 @@ +/** + * @file dashedLineTo.js + * @author mengke01 + * @date + * @description + * 绘制虚线段 + * + * modify from: + * zrender/src/shape/util + */ + + +define( + function(require) { + + /** + * 绘制虚线段 + * + * @param {Context2D} ctx canvascontext + * @param {number} x1 x1坐标 + * @param {number} y1 y1坐标 + * @param {number} x2 x2坐标 + * @param {number} y2 y2坐标 + * @param {?number} dashLength 虚线长度 + */ + function dashedLineTo(ctx, x1, y1, x2, y2, dashLength) { + + dashLength = typeof dashLength != 'number' + ? 2 + : dashLength; + + var dx = x2 - x1; + var dy = y2 - y1; + var numDashes = Math.floor( + Math.sqrt(dx * dx + dy * dy) / dashLength + ); + dx = dx / numDashes; + dy = dy / numDashes; + var flag = true; + for (var i = 0; i < numDashes; ++i) { + if (flag) { + ctx.moveTo(x1, y1); + } else { + ctx.lineTo(x1, y1); + } + flag = !flag; + x1 += dx; + y1 += dy; + } + ctx.lineTo(x2, y2); + } + + return dashedLineTo; + } +); diff --git a/src/render/util/pathAdjust.js b/src/render/util/pathAdjust.js index 9d7a138..19eb761 100644 --- a/src/render/util/pathAdjust.js +++ b/src/render/util/pathAdjust.js @@ -10,6 +10,8 @@ define( function(require) { + var pathIterator = require('./pathIterator'); + /** * 对path坐标进行调整 * @@ -23,43 +25,34 @@ define( var scale = scale || 1; var x = offsetX || 0; var y = offsetY || 0; - var l = path.length; - var i = -1; if(scale == 1) { - while (++i < l) { - var point = path[i]; - switch (point.c) { - case 'M': - case 'L': - point.p.x = (point.p.x + x); - point.p.y = (point.p.y + y); - break; - case 'Q': - point.p.x = (point.p.x + x); - point.p.y = (point.p.y + y); - point.p1.x = (point.p1.x + x); - point.p1.y = (point.p1.y + y); - break; + pathIterator(path, function(c, i, p0, p1, p2) { + if (c == 'Q') { + p1.x = p1.x + x; + p1.y = p1.y + y; + p2.x = p2.x + x; + p2.y = p2.y + y; } - } + else { + p0.x = p0.x + x; + p0.y = p0.y + y; + } + }); } else { - while (++i < l) { - var point = path[i]; - switch (point.c) { - case 'M': - case 'L': - point.p.x = scale * (point.p.x + x); - point.p.y = scale * (point.p.y + y); - break; - case 'Q': - point.p.x = scale * (point.p.x + x); - point.p.y = scale * (point.p.y + y); - point.p1.x = scale * (point.p1.x + x); - point.p1.y = scale * (point.p1.y + y); - break; + + pathIterator(path, function(c, i, p0, p1, p2) { + if (c == 'Q') { + p1.x = scale * (p1.x + x); + p1.y = scale * (p1.y + y); + p2.x = scale * (p2.x + x); + p2.y = scale * (p2.y + y); } - } + else { + p0.x = scale * (p0.x + x); + p0.y = scale * (p0.y + y); + } + }); } return path; } diff --git a/src/render/util/pathIterator.js b/src/render/util/pathIterator.js new file mode 100644 index 0000000..6e07117 --- /dev/null +++ b/src/render/util/pathIterator.js @@ -0,0 +1,44 @@ +/** + * @file pathIterator.js + * @author mengke01 + * @date + * @description + * path遍历 + */ + + +define( + function(require) { + + /** + * 路径迭代器 + * + * @param {Array} path 路径点集合 + * @param {Function} iterator 迭代器,参数:command, points, index + * @return {Array} path 路径点集合 + */ + function pathIterator(path, iterator) { + var i = -1; + var l = path.length; + var prev, point; + while (++i < l) { + point = path[i]; + switch (point.c) { + case 'M': + case 'L': + case 'Z': + iterator && iterator(point.c, i, point.p); + prev = point.p; + break; + case 'Q': + iterator && iterator('Q', i, prev, point.p1, point.p); + prev = point.p; + break; + } + } + return path; + } + + return pathIterator; + } +);