From e5f1c82b1eb3daf5fbc973cca80f966db954fcd6 Mon Sep 17 00:00:00 2001 From: mkwiser Date: Wed, 3 Sep 2014 00:22:02 +0800 Subject: [PATCH] add path constructor --- demo/js/render.js | 32 ++++++- src/render/Layer.js | 28 ++++++ src/render/Painter.js | 26 ++++++ src/render/render.js | 28 ++++-- src/render/shape/Font.js | 6 +- src/render/shape/Path.js | 61 +++++++++++- src/render/shape/Rect.js | 4 +- src/render/shape/support.js | 3 +- src/render/util/computeBoundingBox.js | 129 +++++++++++++++++--------- src/render/util/isInsidePolygon.js | 47 ++++------ src/render/util/pathAdjust.js | 49 ++++++++++ src/ttf/util/glyf2path.js | 47 ++++++---- 12 files changed, 346 insertions(+), 114 deletions(-) create mode 100644 src/render/util/pathAdjust.js diff --git a/demo/js/render.js b/demo/js/render.js index f7780d4..2fd72a9 100644 --- a/demo/js/render.js +++ b/demo/js/render.js @@ -13,6 +13,9 @@ define( var shape_baidu = require('./shape-baidu'); var shape_bdjk = require('./shape-bdjk'); var glyfAdjust = require('ttf/util/glyfAdjust'); + var glyf2path = require('ttf/util/glyf2path'); + var lang = require('common/lang'); + var currentRender; var entry = { @@ -66,16 +69,35 @@ define( level: 10 }); - shape_baidu = glyfAdjust(shape_baidu, 100 / 512); - shape_baidu.x = 50; - shape_baidu.y = 50; - font.addShape('font', shape_baidu); - shape_bdjk = glyfAdjust(shape_bdjk, 100 / 512); shape_bdjk.x = 444; shape_bdjk.y = 255; font.addShape('font', shape_bdjk); + var pathPanel = currentRender.addLayer('path', { + level: 11 + }); + + var pathArray = glyf2path(shape_baidu, { + scale: 120 / 512 + }); + var computeBoundingBox = require('render/util/computeBoundingBox'); + var pathAdjust = require('render/util/pathAdjust'); + + pathArray.forEach(function(path) { + var shape = {}; + shape.points = path; + shape.bound = computeBoundingBox.computePath(path); + + shape.x = 200; + shape.y = 200; + shape.width = shape.bound.width; + shape.height = shape.bound.height; + + pathPanel.addShape('path', shape); + }); + + currentRender.refresh(); // cover.removeShape(0); diff --git a/src/render/Layer.js b/src/render/Layer.js index 564926a..8d57d95 100644 --- a/src/render/Layer.js +++ b/src/render/Layer.js @@ -135,6 +135,9 @@ define( else if(typeof shape === 'number') { return this.shapes[shape]; } + else if(typeof shape === 'object') { + return shape; + } }, /** @@ -212,6 +215,31 @@ define( } }, + /** + * 移动到指定的偏移 + * @param {number} x 偏移 + * @param {number} y 偏移 + * @param {shape} shape对象 + */ + move: function(x, y, shape) { + shape = this.getShape(shape); + var shapes = []; + if(shape) { + shapes.push(shape); + } + else { + shapes = this.shapes; + } + + shapes.forEach(function(shape) { + var _shape = shape['_shape']; + _shape.x = _shape.x + x; + _shape.y= _shape.y + y; + }); + + return this; + }, + /** * 注销本对象 */ diff --git a/src/render/Painter.js b/src/render/Painter.js index 28b012f..7cab0f0 100644 --- a/src/render/Painter.js +++ b/src/render/Painter.js @@ -136,6 +136,9 @@ define( else if(typeof layerId === 'number') { return this.layers[layerId]; } + else if(typeof layerId === 'object') { + return layerId; + } }, /** @@ -217,6 +220,29 @@ define( } }, + /** + * 移动到指定的偏移 + * @param {number} x 偏移 + * @param {number} y 偏移 + * @param {Layer} layerId对象 + */ + move: function(x, y, layerId) { + var layer = this.getLayer(layerId); + var layers = []; + if(layer) { + layers.push(layer); + } + else { + layers = this.layers; + } + + layers.forEach(function(layer) { + layer.move(x, y); + }); + + return this; + }, + /** * 注销本对象 */ diff --git a/src/render/render.js b/src/render/render.js index 64d49a7..b3b3ea9 100644 --- a/src/render/render.js +++ b/src/render/render.js @@ -61,36 +61,44 @@ define( me.camera.scale *= ratio; me.painter.refresh(); - console.log(me.camera.scale); me.camera.ratio = 1; }); this.capture.on('dragstart', function(e) { var shape = me.painter.getShapeIn(e); if(shape) { - var _shape = shape['_shape']; - _shape._x = _shape.x; - _shape._y= _shape.y; + me.selectedShape = shape; } - me.selectedShape = shape; + me.camera.mx = e.x; + me.camera.my = e.y; }); this.capture.on('drag', function(e) { var shape = me.selectedShape; if(shape) { - var _shape = shape['_shape']; - _shape.x = _shape._x + e.deltaX; - _shape.y = _shape._y + e.deltaY; - me.painter.getLayer(shape.layerId).refresh(); + me.painter.getLayer(shape.layerId) + .move(e.x - me.camera.mx, e.y - me.camera.my, shape) + .refresh(); } + else { + me.painter.move(e.x - me.camera.mx, e.y - me.camera.my) + .refresh(); + } + me.camera.mx = e.x; + me.camera.my = e.y; }); this.capture.on('dragend', function(e) { var shape = me.selectedShape; if(shape) { - me.painter.getLayer(shape.layerId).refresh(); + me.painter.getLayer(shape.layerId) + .move(e.x - me.camera.mx, e.y - me.camera.my, shape) + .refresh(); me.selectedShape = null; } + else { + me.painter.refresh(); + } }); } diff --git a/src/render/shape/Font.js b/src/render/shape/Font.js index 5936fa4..dbaa5b2 100644 --- a/src/render/shape/Font.js +++ b/src/render/shape/Font.js @@ -45,6 +45,8 @@ define( _shape.xMin = shape.xMin * scale; _shape.yMin = shape.yMin * scale; + shape.width = shape.width * scale; + shape.height = shape.height * scale; }, /** @@ -72,9 +74,9 @@ define( */ isIn: function(shape, x, y) { return x <= shape.x + shape.width - && x >= shape.x - shape.width + && x >= shape.x && y <= shape.y + shape.height - && y >= shape.y - shape.height; + && y >= shape.y; }, /** diff --git a/src/render/shape/Path.js b/src/render/shape/Path.js index 1c3354f..324f92d 100644 --- a/src/render/shape/Path.js +++ b/src/render/shape/Path.js @@ -9,10 +9,25 @@ define( function(require) { + + var isInsidePolygon = require('../util/isInsidePolygon'); + var proto = { type: 'path', + + /** + * 对形状进行缩放平移调整 + * + * @param {Object} shape shape对象 + * @param {Object} camera camera对象 + * @return {Object} shape对象 + */ + adjust: function(shape, camera) { + + }, + /** * 获取shape的矩形区域 * @@ -37,7 +52,24 @@ define( * @param {boolean} 是否 */ isIn: function(shape, x, y) { - return Math.pow(shape.x - x, 2) + Math.pow(shape.y - y, 2) <= Math.pow(shape.r, 2); + x = x - shape.bound.x; + y = y - shape.bound.y; + if( + x <= shape.x + shape.width + && x >= shape.x + && y <= shape.y + shape.height + && y >= shape.y + ) { + var points = []; + shape.points.forEach(function(point) { + if(point.c == 'Q') { + points.push(point.p1); + } + points.push(point.p); + }); + return isInsidePolygon(points, x, y); + } + return false; }, /** @@ -47,7 +79,32 @@ define( * @param {Object} shape shape数据 */ draw: function(ctx, shape) { - + var x = shape.x || 0; + var y = shape.y || 0; + + ctx.translate(x, y); + var i = -1; + var points = shape.points; + var l = points.length; + var point; + while (++i < l) { + point = points[i]; + switch (point.c) { + case 'M': + ctx.moveTo(point.p.x, point.p.y); + break; + case 'L': + ctx.lineTo(point.p.x, point.p.y); + break; + case 'Q': + ctx.quadraticCurveTo(point.p1.x, point.p1.y, point.p.x, point.p.y); + break; + case 'Z': + ctx.lineTo(point.p.x, point.p.y); + break; + } + } + ctx.translate(-x, -y); } }; diff --git a/src/render/shape/Rect.js b/src/render/shape/Rect.js index a0a0854..7b8ac3a 100644 --- a/src/render/shape/Rect.js +++ b/src/render/shape/Rect.js @@ -39,9 +39,9 @@ define( */ isIn: function(shape, x, y) { return x <= shape.x + shape.width - && x >= shape.x - shape.width + && x >= shape.x && y <= shape.y + shape.height - && y >= shape.y - shape.height; + && y >= shape.y; }, /** diff --git a/src/render/shape/support.js b/src/render/shape/support.js index 8eb430c..c3418b4 100644 --- a/src/render/shape/support.js +++ b/src/render/shape/support.js @@ -12,7 +12,8 @@ define( var support = { circle: require('./Circle'), rect: require('./Rect'), - font: require('./Font') + font: require('./Font'), + path: require('./Path') }; return support; } diff --git a/src/render/util/computeBoundingBox.js b/src/render/util/computeBoundingBox.js index e988e76..fd6b884 100644 --- a/src/render/util/computeBoundingBox.js +++ b/src/render/util/computeBoundingBox.js @@ -10,96 +10,135 @@ * https://github.com/ecomfe/zrender/blob/master/src/tool/computeBoundingBox.js */ - define( function(require) { - /** * 计算包围盒 */ - function computeBoundingBox(points, min, max) { + function computeBoundingBox(points) { if (points.length === 0) { return; } - var left = points[0][0]; - var right = points[0][0]; - var top = points[0][1]; - var bottom = points[0][1]; - + var left = points[0].x; + var right = points[0].x; + var top = points[0].y; + var bottom = points[0].y; + for (var i = 1; i < points.length; i++) { var p = points[i]; - if (p[0] < left) { - left = p[0]; + if (p.x < left) { + left = p.x; } - if (p[0] > right) { - right = p[0]; + if (p.x > right) { + right = p.x; } - if (p[1] < top) { - top = p[1]; + if (p.y < top) { + top = p.y; } - if (p[1] > bottom) { - bottom = p[1]; + if (p.y > bottom) { + bottom = p.y; } } - - min[0] = left; - min[1] = top; - max[0] = right; - max[1] = bottom; + return { + x: left, + y: top, + width: right - left, + height: bottom - top + } } /** * 计算二阶贝塞尔曲线的包围盒 * http://pissang.net/blog/?p=91 */ - function computeQuadraticBezierBoundingBox(p0, p1, p2, min, max) { + function computeQuadraticBezierBoundingBox(p0, p1, p2) { // Find extremities, where derivative in x dim or y dim is zero - var tmp = (p0[0] + p2[0] - 2 * p1[0]); + var tmp = (p0.x + p2.x - 2 * p1.x); // p1 is center of p0 and p2 in x dim var t1; if (tmp === 0) { t1 = 0.5; } else { - t1 = (p0[0] - p1[0]) / tmp; + t1 = (p0.x - p1.x) / tmp; } - tmp = (p0[1] + p2[1] - 2 * p1[1]); + tmp = (p0.y + p2.y - 2 * p1.y); // p1 is center of p0 and p2 in y dim var t2; if (tmp === 0) { t2 = 0.5; } else { - t2 = (p0[1] - p1[1]) / tmp; + t2 = (p0.y - p1.y) / tmp; } t1 = Math.max(Math.min(t1, 1), 0); t2 = Math.max(Math.min(t2, 1), 0); - var ct1 = 1-t1; - var ct2 = 1-t2; + var ct1 = 1 - t1; + var ct2 = 1 - t2; - var x1 = ct1 * ct1 * p0[0] - + 2 * ct1 * t1 * p1[0] - + t1 * t1 * p2[0]; - var y1 = ct1 * ct1 * p0[1] - + 2 * ct1 * t1 * p1[1] - + t1 * t1 * p2[1]; + var x1 = ct1 * ct1 * p0.x + 2 * ct1 * t1 * p1.x + t1 * t1 * p2.x; + var y1 = ct1 * ct1 * p0.y + 2 * ct1 * t1 * p1.y + t1 * t1 * p2.y; - var x2 = ct2 * ct2 * p0[0] - + 2 * ct2 * t2 * p1[0] - + t2 * t2 * p2[0]; - var y2 = ct2 * ct2 * p0[1] - + 2 * ct2 * t2 * p1[1] - + t2 * t2 * p2[1]; + var x2 = ct2 * ct2 * p0.x + 2 * ct2 * t2 * p1.x + t2 * t2 * p2.x; + var y2 = ct2 * ct2 * p0.y + 2 * ct2 * t2 * p1.y + t2 * t2 * p2.y; - return computeBoundingBox( - [p0.slice(), p2.slice(), [x1, y1], [x2, y2]], - min, max - ); + return computeBoundingBox([p0, p2, { + x: x1, + y: y1 + }, + { + x: x2, + y: y2 + }]); + } + + /** + * 计算曲线包围盒 + * + * @private + * @param {path} path对象 + * + * @return {Object} x,y,width,height + */ + function computePathBoundingBox(path) { + var l = path.length; + var xMax = xMin = yMax = yMin = 0; + var i = -1; + var points = []; + while (++i < l) { + var point = path[i]; + switch (point.c) { + case 'M': + case 'L': + points.push(point.p); + break; + case 'Q': + var p0 = path[i - 1].p; + var bound = computeQuadraticBezierBoundingBox(p0, point.p1, point.p); + points.push(bound); + points.push({ + x: bound.x, + y: bound.y + bound.height + }); + points.push({ + x: bound.x + bound.width, + y: bound.y + }); + points.push({ + x: bound.x + bound.width, + y: bound.y + bound.height + }); + break; + } + } + return computeBoundingBox(points); } return { - quadraticBezier: computeQuadraticBezierBoundingBox + computeBounding: computeBoundingBox, + quadraticBezier: computeQuadraticBezierBoundingBox, + computePath: computePathBoundingBox }; } ); diff --git a/src/render/util/isInsidePolygon.js b/src/render/util/isInsidePolygon.js index d2ab0af..3bc7efc 100644 --- a/src/render/util/isInsidePolygon.js +++ b/src/render/util/isInsidePolygon.js @@ -17,7 +17,7 @@ define( * 多边形包含判断 * 警告:下面这段代码会很难看,建议跳过~ */ - function _isInsidePolygon(pointList, x, y) { + function isInsidePolygon(pointList, x, y) { /** * 射线判别法 * 如果一个点在多边形内部,任意角度做射线肯定会与多边形要么有一个交点,要么有与多边形边界线重叠 @@ -34,7 +34,7 @@ define( for (i = 0; i < N; ++i) { // 是否在顶点上 - if (polygon[i][0] == x && polygon[i][1] == y ) { + if (polygon[i].x == x && polygon[i].y == y ) { redo = false; inside = true; break; @@ -45,14 +45,14 @@ define( redo = false; inside = false; for (i = 0,j = N - 1; i < N; j = i++) { - if ((polygon[i][1] < y && y < polygon[j][1]) - || (polygon[j][1] < y && y < polygon[i][1]) + if ((polygon[i].y < y && y < polygon[j].y) + || (polygon[j].y < y && y < polygon[i].y) ) { - if (x <= polygon[i][0] || x <= polygon[j][0]) { - v = (y - polygon[i][1]) - * (polygon[j][0] - polygon[i][0]) - / (polygon[j][1] - polygon[i][1]) - + polygon[i][0]; + if (x <= polygon[i].x || x <= polygon[j].x) { + v = (y - polygon[i].y) + * (polygon[j].x - polygon[i].x) + / (polygon[j].y - polygon[i].y) + + polygon[i].x; if (x < v) { // 在线的左侧 inside = !inside; } @@ -62,17 +62,17 @@ define( } } } - else if (y == polygon[i][1]) { - if (x < polygon[i][0]) { // 交点在顶点上 - polygon[i][1] > polygon[j][1] ? --y : ++y; + else if (y == polygon[i].y) { + if (x < polygon[i].x) { // 交点在顶点上 + polygon[i].y > polygon[j].y ? --y : ++y; //redo = true; break; } } - else if (polygon[i][1] == polygon[j][1] // 在水平的边界线上 - && y == polygon[i][1] - && ((polygon[i][0] < x && x < polygon[j][0]) - || (polygon[j][0] < x && x < polygon[i][0])) + else if (polygon[i].y == polygon[j].y // 在水平的边界线上 + && y == polygon[i].y + && ((polygon[i].x < x && x < polygon[j].x) + || (polygon[j].x < x && x < polygon[i].x)) ) { inside = true; break; @@ -82,21 +82,6 @@ define( return inside; } - /** - * 判断点是否在折线内部 - * - * @param {Object} path path对象 - * @param {Object} p 点对象 - * @return {boolean} 是否 - */ - function isInsidePolygon(pointList, p) { - var list = []; - pointList.forEach(function(item) { - list.push([item.x, item.y]); - }); - return _isInsidePolygon(list, p.x, p.y); - } - return isInsidePolygon; } ); diff --git a/src/render/util/pathAdjust.js b/src/render/util/pathAdjust.js new file mode 100644 index 0000000..0baaea6 --- /dev/null +++ b/src/render/util/pathAdjust.js @@ -0,0 +1,49 @@ +/** + * @file pathAdjust.js + * @author mengke01 + * @date + * @description + * 路径调整 + */ + + +define( + function(require) { + + /** + * 对path坐标进行调整 + * + * @param {Object} path path数据结构 + * @param {number} scale 缩放比例 + * @param {number} offsetX x偏移 + * @param {number} offsetY y偏移 + * @return {number} path数据结构 + */ + function pathAdjust(path, scale, offsetX, offsetY) { + var scale = scale || 1; + var x = offsetX || 0; + var y = offsetY || 0; + var l = path.length; + var i = -1; + 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; + } + } + return path; + } + + return pathAdjust; + } +); diff --git a/src/ttf/util/glyf2path.js b/src/ttf/util/glyf2path.js index ce1c056..dea2d63 100644 --- a/src/ttf/util/glyf2path.js +++ b/src/ttf/util/glyf2path.js @@ -44,18 +44,22 @@ var glyfAdjust = require('ttf/util/glyfAdjust'); var commandQueue = []; var curBezier = null; + var currentPoint = null; + var prevPoint = null; + var nextPoint = null; + // 处理glyf坐标 for ( var endPts = glyf.endPtsOfContours[i]; currentPts < endPts + 1; currentPts++) { - var currentPoint = coordinates[currentPts]; - var prevPoint = (currentPts === startPts) + currentPoint = coordinates[currentPts]; + prevPoint = (currentPts === startPts) ? coordinates[endPts] : coordinates[currentPts - 1]; - var nextPoint = (currentPts === endPts) + nextPoint = (currentPts === endPts) ? coordinates[startPts] : coordinates[currentPts + 1]; - if (currentPoint == undefined) { + if (!currentPoint) { continue; } @@ -70,7 +74,7 @@ var glyfAdjust = require('ttf/util/glyfAdjust'); } }); } - // 起始点不在曲线上 + // 起始点不在曲线上,则中间点为bezier曲线的起始点 else { var midPoint = { @@ -96,15 +100,11 @@ var glyfAdjust = require('ttf/util/glyfAdjust'); // 直线 if ( - currentPoint != undefined + currentPoint && currentPoint.isOnCurve - && prevPoint != undefined + && prevPoint && prevPoint.isOnCurve ) { - if(curBezier) { - curBezier.p = prevPoint; - curBezier = null; - } commandQueue.push({ c: 'L', p: { @@ -113,11 +113,26 @@ var glyfAdjust = require('ttf/util/glyfAdjust'); } }); } + // 当前在曲线上,并且上一个不在曲线上 + // 则当前为bezier终点 + else if ( + currentPoint.isOnCurve + && prevPoint + && !prevPoint.isOnCurve + ) { + if(curBezier) { + curBezier.p = { + x: currentPoint.x, + y: currentPoint.y + }; + curBezier = null; + } + } // 当前点不在曲线上,并且上一个点不在曲线上 - // 贝塞尔曲线的连续情况 + // 贝塞尔曲线的连续情况,需要求中间点为终点和起点 else if ( !currentPoint.isOnCurve - && prevPoint != undefined + && prevPoint && !prevPoint.isOnCurve ) { @@ -153,13 +168,13 @@ var glyfAdjust = require('ttf/util/glyfAdjust'); } } - // 处理最后一个点 + // 最后一个点不在曲线上 if ( !currentPoint.isOnCurve - && coordinates[startPts] != undefined + && coordinates[startPts] ) { - // 轮廓起始点在曲线上 + // 轮廓起始点在曲线上,则起始点为bezier曲线终点 if (coordinates[startPts].isOnCurve) { if(curBezier) { curBezier.p = {