<!DOCTYPE html>
<html ng-app="roundingApp">

  <head>
    <script data-require="angular.js@*" data-semver="1.3.1" src="//code.angularjs.org/1.3.1/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="rounding.js"></script>
    <script src="script.js"></script>
  </head>

  <body ng-controller="RoundingCtrl">
    <div rounding-test-case="M0 0 L 100 0 L100 100 L 0 100 Z"></div>
    <div rounding-test-case="M0 0 L 100 0 L100 100 L 0 100"></div>
    <div rounding-test-case="M0 0 L 100 0 L100 50 L 0 50 Z"></div>
    <div rounding-test-case="M0 0 L 100 0 L50 100 Z"></div>
    <div rounding-test-case="M0 0 L 100 0 L100 100"></div>
    
    <div rounding-test-case="M0 0 L 20 100 L 40 0 L 60 100 L 80 0 L 100 100"></div>
    
    <div rounding-test-case="M 0 0 C 25 0 50 50 100 50 L 0 100 L 25 50 Z"></div>
    
    <div rounding-test-case="M 0 0 L 100 0 L 100 100 M 25 25 L 75 25 L 75 75"></div>
  </body>
</html>
var roundingApp = angular.module('roundingApp', []);

roundingApp.directive('roundingTestCase', function() {
  return {
    restrict: "A",
    scope: {
      "roundingTestCase": "@"
    },
    replace: true,
    template: ''
      +'<div class="test-case-container">'
      +'  <svg width="120" height="120" viewBox="-10 -10 120 120">'
      +'    <path ng-attr-d="{{ roundedPath }}" fill="none" stroke="red" stroke-width="6" />'
      +'    <path ng-attr-d="{{ roundingTestCase }}" fill="none" stroke="black" stroke-width="1" />'
      +'  </svg>'
      +'  <input type="range" min="0" max="1" step="0.01" ng-model="config.rounding" />'
      +'  <div><label><input type="checkbox" ng-model="config.fractional" /> Fractional</label></div>'
      +'</div>',
    controller: function($scope){
      var origPath = $scope.roundingTestCase;
      
      $scope.config = {
        rounding: .1,
        fractional: false
      };
      
      $scope.$watch("config", function() {
        $scope.roundedPath = roundPathCorners(
          origPath, 
          $scope.config.fractional
            ? $scope.config.rounding
            : $scope.config.rounding * 100,
          $scope.config.fractional
        );
      }, true);
    }
  };
});

roundingApp.directive('myCustomer', function() {
  return {
    template: 'Foobat12'
  };
})

roundingApp.controller(
  "RoundingCtrl",
  function RoundingCtrl($scope) {
    $scope.foo = 10;
  }
)
/* Styles go here */

.test-case-container {
  display: inline-block;
  border: 2px solid gray;
  padding: .5em;
  margin: 1em;
  display: inline-block;
  text-align: center;
}

.test-case-container svg {
  display: block;
}
/*****************************************************************************
*                                                                            *
*  SVG Path Rounding Function                                                *
*  Copyright (C) 2014 Yona Appletree                                         *
*                                                                            *
*  Licensed under the Apache License, Version 2.0 (the "License");           *
*  you may not use this file except in compliance with the License.          *
*  You may obtain a copy of the License at                                   *
*                                                                            *
*      http://www.apache.org/licenses/LICENSE-2.0                            *
*                                                                            *
*  Unless required by applicable law or agreed to in writing, software       *
*  distributed under the License is distributed on an "AS IS" BASIS,         *
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
*  See the License for the specific language governing permissions and       *
*  limitations under the License.                                            *
*                                                                            *
*****************************************************************************/

/**
 * SVG Path rounding function. Takes an input path string and outputs a path
 * string where all line-line corners have been rounded. Only supports absolute
 * commands at the moment.
 * 
 * @param pathString The SVG input path
 * @param radius The amount to round the corners, either a value in the SVG 
 *               coordinate space, or, if useFractionalRadius is true, a value
 *               from 0 to 1.
 * @param useFractionalRadius If true, the curve radius is expressed as a
 *               fraction of the distance between the point being curved and
 *               the previous and next points.
 * @returns A new SVG path string with the rounding
 */
function roundPathCorners(pathString, radius, useFractionalRadius) {
  function moveTowardsLength(movingPoint, targetPoint, amount) {
    var width = (targetPoint.x - movingPoint.x);
    var height = (targetPoint.y - movingPoint.y);
    
    var distance = Math.sqrt(width*width + height*height);
    
    return moveTowardsFractional(movingPoint, targetPoint, Math.min(1, amount / distance));
  }
  function moveTowardsFractional(movingPoint, targetPoint, fraction) {
    return {
      x: movingPoint.x + (targetPoint.x - movingPoint.x)*fraction,
      y: movingPoint.y + (targetPoint.y - movingPoint.y)*fraction
    };
  }
  
  // Adjusts the ending position of a command
  function adjustCommand(cmd, newPoint) {
    if (cmd.length > 2) {
      cmd[cmd.length - 2] = newPoint.x;
      cmd[cmd.length - 1] = newPoint.y;
    }
  }
  
  // Gives an {x, y} object for a command's ending position
  function pointForCommand(cmd) {
    return {
      x: parseFloat(cmd[cmd.length - 2]),
      y: parseFloat(cmd[cmd.length - 1]),
    };
  }
  
  // Split apart the path, handing concatonated letters and numbers
  var pathParts = pathString
    .split(/[,\s]/)
    .reduce(function(parts, part){
      var match = part.match("([a-zA-Z])(.+)");
      if (match) {
        parts.push(match[1]);
        parts.push(match[2]);
      } else {
        parts.push(part);
      }
      
      return parts;
    }, []);
  
  // Group the commands with their arguments for easier handling
  var commands = pathParts.reduce(function(commands, part) {
    if (parseFloat(part) == part && commands.length) {
      commands[commands.length - 1].push(part);
    } else {
      commands.push([part]);
    }
    
    return commands;
  }, []);
  
  // The resulting commands, also grouped
  var resultCommands = [];
  
  if (commands.length > 1) {
    var startPoint = pointForCommand(commands[0]);
    
    // Handle the close path case with a "virtual" closing line
    var virtualCloseLine = null;
    if (commands[commands.length - 1][0] == "Z" && commands[0].length > 2) {
      virtualCloseLine = ["L", startPoint.x, startPoint.y];
      commands[commands.length - 1] = virtualCloseLine;
    }
    
    // We always use the first command (but it may be mutated)
    resultCommands.push(commands[0]);
    
    for (var cmdIndex=1; cmdIndex < commands.length; cmdIndex++) {
      var prevCmd = resultCommands[resultCommands.length - 1];
      
      var curCmd = commands[cmdIndex];
      
      // Handle closing case
      var nextCmd = (curCmd == virtualCloseLine)
        ? commands[1]
        : commands[cmdIndex + 1];
      
      // Nasty logic to decide if this path is a candidite.
      if (nextCmd && prevCmd && (prevCmd.length > 2) && curCmd[0] == "L" && nextCmd.length > 2 && nextCmd[0] == "L") {
        // Calc the points we're dealing with
        var prevPoint = pointForCommand(prevCmd);
        var curPoint = pointForCommand(curCmd);
        var nextPoint = pointForCommand(nextCmd);
        
        // The start and end of the cuve are just our point moved towards the previous and next points, respectivly
        var curveStart, curveEnd;
        
        if (useFractionalRadius) {
          curveStart = moveTowardsFractional(curPoint, prevCmd.origPoint || prevPoint, radius);
          curveEnd = moveTowardsFractional(curPoint, nextCmd.origPoint || nextPoint, radius);
        } else {
          curveStart = moveTowardsLength(curPoint, prevPoint, radius);
          curveEnd = moveTowardsLength(curPoint, nextPoint, radius);
        }
        
        // Adjust the current command and add it
        adjustCommand(curCmd, curveStart);
        curCmd.origPoint = curPoint;
        resultCommands.push(curCmd);
        
        // The curve control points are halfway between the start/end of the curve and
        // the original point
        var startControl = moveTowardsFractional(curveStart, curPoint, .5);
        var endControl = moveTowardsFractional(curPoint, curveEnd, .5);
  
        // Create the curve 
        var curveCmd = ["C", startControl.x, startControl.y, endControl.x, endControl.y, curveEnd.x, curveEnd.y];
        // Save the original point for fractional calculations
        curveCmd.origPoint = curPoint;
        resultCommands.push(curveCmd);
      } else {
        // Pass through commands that don't qualify
        resultCommands.push(curCmd);
      }
    }
    
    // Fix up the starting point and restore the close path if the path was orignally closed
    if (virtualCloseLine) {
      var newStartPoint = pointForCommand(resultCommands[resultCommands.length-1]);
      resultCommands.push(["Z"]);
      adjustCommand(resultCommands[0], newStartPoint);
    }
  } else {
    resultCommands = commands;
  }
  
  return resultCommands.reduce(function(str, c){ return str + c.join(" ") + " "; }, "");
}