diff --git a/demo/bezierCubic2Q2.html b/demo/bezierCubic2Q2.html new file mode 100644 index 0000000..c9e4a4e --- /dev/null +++ b/demo/bezierCubic2Q2.html @@ -0,0 +1,54 @@ + + + + + 三次贝塞尔转二次 + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/demo/js/bezierCubic2Q2.js b/demo/js/bezierCubic2Q2.js new file mode 100644 index 0000000..231170f --- /dev/null +++ b/demo/js/bezierCubic2Q2.js @@ -0,0 +1,92 @@ +/** + * @file quadraticBezier.js + * @author mengke01 + * @date + * @description + * 三次贝塞尔转二次 + */ + +define( + function(require) { + + var bezierCubic2Q2 = require('math/bezierCubic2Q2'); + + var entry = { + + /** + * 初始化 + */ + init: function() { + var canvas = $('#canvas').get(0); + var ctx = canvas.getContext('2d'); + var width = canvas.offsetWidth; + var height = canvas.offsetHeight; + + var points =[{"x":167,"y":449},{"x":233,"y":331},{"x":431,"y":332},{"x":481,"y":417}]; + + $(points).each(function(index, item) { + $('[data-index="'+index+'"]').css({ + left: points[index].x, + top: points[index].y + }) + }); + + var point; + + $('.point').on('mousedown', function(e) { + point = $(this); + }); + + $(document.body).on('mousemove', onMove); + $(document.body).on('mouseup', function(e) { + onMove.call(this, e); + point = null; + }); + + function onMove(e) { + var px = e.pageX; + var py = e.pageY; + if(point) { + point.css({ + left: px, + top: py + }); + var p = points[+point.attr('data-index')]; + p.x = px; + p.y = py; + draw(); + } + } + + function draw() { + + + ctx.clearRect(0, 0, width, height); + //绘制2次贝塞尔曲线 + ctx.beginPath(); + ctx.strokeStyle='red'; + + ctx.moveTo(points[0].x, points[0].y); + ctx.bezierCurveTo(points[1].x,points[1].y,points[2].x,points[2].y,points[3].x,points[3].y); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle='blue'; + bezierCubic2Q2.apply(null, points).forEach(function(bezier) { + ctx.moveTo(bezier[0].x, bezier[0].y); + ctx.quadraticCurveTo(bezier[1].x, bezier[1].y, bezier[2].x, bezier[2].y); + }); + + ctx.stroke(); + + } + + draw(); + } + }; + + entry.init(); + + return entry; + } +); \ No newline at end of file diff --git a/src/math/bezierCubic2Q2.js b/src/math/bezierCubic2Q2.js index 3d6abb7..3efd836 100644 --- a/src/math/bezierCubic2Q2.js +++ b/src/math/bezierCubic2Q2.js @@ -14,6 +14,15 @@ define( function(require) { + + function toQuad(p1, c1, c2, p2) { + // Quad control point is (3*c2 - p2 + 3*c1 - p1)/4 + var x = (3 * c2.x - p2.x + 3 * c1.x - p1.x) / 4; + var y = (3 * c2.y - p2.y + 3 * c1.y - p1.y) / 4; + return [p1, {x: x, y: y}, p2]; + } + + /** * 三次贝塞尔转二次贝塞尔 * @@ -24,8 +33,48 @@ define( * @return {Array} 二次贝塞尔控制点 */ function bezierCubic2Q2(p1, c1, c2, p2) { - - return []; + + var mx = p2.x - 3 * c2.x + 3 * c1.x - p1.x; + var my = p2.y - 3 * c2.y + 3 * c1.y - p1.y; + + // control points near + if (mx * mx + my * my <= 4) { + return [ toQuad(p1, c1, c2, p2) ]; + } + + // Split to 2 qubic beziers by midpoints + // (p2 + 3*c2 + 3*c1 + p1)/8 + var mp = { + x: (p2.x + 3 * c2.x + 3 * c1.x + p1.x) / 8, + y: (p2.y + 3 * c2.y + 3 * c1.y + p1.y) / 8 + }; + + return [ + toQuad( + p1, + { + x: (p1.x + c1.x) / 2, + y: (p1.y + c1.y) / 2 + }, + { + x: (p1.x + 2 * c1.x + c2.x) / 4, + y: (p1.y + 2 * c1.y + c2.y) / 4 + }, + mp + ), + toQuad( + mp, + { + x: (p2.x + c1.x + 2 * c2.x) / 4, + y: (p2.y + c1.y + 2 * c2.y) / 4 + }, + { + x: (p2.x + c2.x) / 2, + y: (p2.y + c2.y) / 2 + }, + p2 + ) + ]; } return bezierCubic2Q2; diff --git a/src/ttf/util/svg2contours.js b/src/ttf/util/svg2contours.js index 0869c1b..5fdd8f7 100644 --- a/src/ttf/util/svg2contours.js +++ b/src/ttf/util/svg2contours.js @@ -10,6 +10,53 @@ define( function(require) { + var bezierCubic2Q2 = require('math/bezierCubic2Q2'); + + function cubic2Points(cubicList, contour) { + // 三次贝塞尔转二次 + var q2List = []; + cubicList.forEach(function(c) { + q2List = q2List.concat(bezierCubic2Q2(c[0], c[1], c[2], c[3])); + }); + + var q2, prevq2; + for (var i = 0, l = q2List.length; i < l; i++) { + q2 = q2List[i]; + if (i == 0) { + contour.push({ + x: q2[1].x, + y: q2[1].y + }); + contour.push({ + x: q2[2].x, + y: q2[2].y, + onCurve: true + }); + } + else { + prevq2 = q2List[i - 1]; + // 检查是否存在切线点 + if (prevq2[1].x + q2[1].x == 2 * q2[0].x && prevq2[1].y + q2[1].y == 2 * q2[0].y) { + contour.pop(); + } + contour.push({ + x: q2[1].x, + y: q2[1].y + }); + } + } + + contour.push({ + x: q2[2].x, + y: q2[2].y, + onCurve: true + }); + + return contour; + } + + + /** * svg path 转 contours * @@ -65,7 +112,7 @@ define( segments.push({cmd:'Z'}); // 解析segments - var contours = [], contour = [], prevX = 0, prevY = 0; + var contours = [], contour = [], prevX = 0, prevY = 0, prevc1; for (var i = 0, l = segments.length;i < l; i++) { segment = segments[i]; @@ -147,14 +194,79 @@ define( y: prevY, onCurve: true }); + } // 三次贝塞尔 else if (segment.cmd === 'C') { - throw 'not support cubic bezier'; + + // 这里可能会连续绘制,最后一个是终点 + var q = 0, ql = segment.args.length - 2; + var cubicList = []; + + for (; q < ql ; q += 4) { + var cubic = []; + var c1 = { + x: prevX + segment.args[q], + y: prevY + segment.args[q + 1] + }; + var c2 = { + x: prevX + segment.args[q + 2], + y: prevY + segment.args[q + 3] + }; + + // 计算中间点 + if (q == 0) { + cubic.push({x: prevX, y: prevY}); + cubic.push(c1); + cubic.push(c2); + } + else { + var prevC2 = cubicList[cubicList.length - 1][2]; + var p1 = { + x: (prevC2.x + c1.x) / 2, + y: (prevC2.y + c1.y) / 2 + } + cubicList[cubicList.length - 1][3] = p1; + + cubic.push(p1); + cubic.push(c1); + cubic.push(c2); + } + + cubicList.push(cubic); + } + + prevX += segment.args[ql]; + prevY += segment.args[ql + 1]; + + cubicList[cubicList.length - 1].push({x: prevX, y: prevY}); + cubic2Points(cubicList, contour); + prevc1 = cubicList[cubicList.length - 1][2]; } // 三次贝塞尔平滑 else if (segment.cmd === 'S') { - throw 'not support cubic bezier'; + + // 这里需要移除上一个曲线的终点 + var p1 = contour.pop(); + var c1 = { + x: 2 * p1.x - prevc1.x, + y: 2 * p1.y - prevc1.y + }; + var c2 = { + x: prevX + segment.args[0], + y: prevX + segment.args[1] + }; + + prevX += segment.args[2]; + prevY += segment.args[3]; + + var p2 = { + x: prevX, + y: prevY + }; + + cubic2Points([[p1, c1, c2, p2]], contour); + prevc1 = c2; } // 圆弧 else if (segment.cmd === 'A') {