renderer/svg/stroker/QuadraticSVGStroker.js

import * as StrokeComponent from '../../../model/StrokeComponent'
import { computeLinksPoints, computeMiddlePoint, computeAxeAngle } from '../../QuadraticUtils'

/**
 * Get info
 * @return {StrokerInfo} Information about this stroker
 */
export function getInfo () {
  return {
    type: 'svg',
    name: 'quadratic'
  }
}

function renderArc (context, center, radius) {
  const svgPath = [
    `M ${center.x},${center.y}`,
    `m ${-radius},0`,
    `a ${radius},${radius} 0 1 0 ${radius * 2},0`,
    `a ${radius},${radius} 0 1 0 ${-(radius * 2)},0`
  ].join(' ')
  return svgPath
}

function renderLine (context, begin, end, width) {
  const linkPoints1 = computeLinksPoints(begin, computeAxeAngle(begin, end), width)
  const linkPoints2 = computeLinksPoints(end, computeAxeAngle(begin, end), width)

  const svgPath = [
    `M ${linkPoints1[0].x},${linkPoints1[0].y}`,
    `L ${linkPoints2[0].x},${linkPoints2[0].y}`,
    `L ${linkPoints2[1].x},${linkPoints2[1].y}`,
    `L ${linkPoints1[1].x},${linkPoints1[1].y}`
  ].join(' ')
  return svgPath
}

function renderFinal (context, begin, end, width) {
  const ARCSPLIT = 6
  const angle = computeAxeAngle(begin, end)
  const linkPoints = computeLinksPoints(end, angle, width)

  const parts = [`M ${linkPoints[0].x},${linkPoints[0].y}`]
  for (let i = 1; i <= ARCSPLIT; i++) {
    const newAngle = angle - (i * (Math.PI / ARCSPLIT))
    parts.push(`L ${end.x - (end.p * width * Math.sin(newAngle))},${end.y + (end.p * width * Math.cos(newAngle))}`)
  }
  const svgPath = parts.join(' ')
  return svgPath
}

function renderQuadratic (context, begin, end, ctrl, width) {
  const linkPoints1 = computeLinksPoints(begin, computeAxeAngle(begin, ctrl), width)
  const linkPoints2 = computeLinksPoints(end, computeAxeAngle(ctrl, end), width)
  const linkPoints3 = computeLinksPoints(ctrl, computeAxeAngle(begin, end), width)

  const svgPath = [
    `M ${linkPoints1[0].x},${linkPoints1[0].y}`,
    `Q ${linkPoints3[0].x},${linkPoints3[0].y} ${linkPoints2[0].x},${linkPoints2[0].y}`,
    `L ${linkPoints2[1].x},${linkPoints2[1].y}`,
    `Q ${linkPoints3[1].x},${linkPoints3[1].y} ${linkPoints1[1].x},${linkPoints1[1].y}`
  ].join(' ')
  return svgPath
}

const buildSVGPath = (context, stroke) => {
  const length = stroke.x.length
  const width = stroke.width
  const firstPoint = StrokeComponent.getPointByIndex(stroke, 0)
  const nbquadratics = length - 2

  const parts = []
  if (length < 3) {
    parts.push(renderArc(context, firstPoint, width * 0.6))
  } else {
    parts.push(renderArc(context, firstPoint, width * firstPoint.p))
    parts.push(renderLine(context, firstPoint, computeMiddlePoint(firstPoint, StrokeComponent.getPointByIndex(stroke, 1)), width))

    for (let i = 0; i < nbquadratics; i++) {
      parts.push(renderQuadratic(context, computeMiddlePoint(StrokeComponent.getPointByIndex(stroke, i), StrokeComponent.getPointByIndex(stroke, i + 1)), computeMiddlePoint(StrokeComponent.getPointByIndex(stroke, i + 1), StrokeComponent.getPointByIndex(stroke, i + 2)), StrokeComponent.getPointByIndex(stroke, i + 1), width))
    }
    parts.push(renderLine(context, computeMiddlePoint(StrokeComponent.getPointByIndex(stroke, length - 2), StrokeComponent.getPointByIndex(stroke, length - 1)), StrokeComponent.getPointByIndex(stroke, length - 1), width))
    parts.push(renderFinal(context, StrokeComponent.getPointByIndex(stroke, length - 2), StrokeComponent.getPointByIndex(stroke, length - 1), width))
  }
  return parts.join(' ')
}

/**
 * Draw a stroke on a svg tag, using quadratics
 * @param {Object} context Current rendering context
 * @param {Stroke} stroke Current stroke to be drawn
 */
export function drawStroke (context, stroke) {
  const svgPath = buildSVGPath(context, stroke)

  context
    .attr('color', stroke.color)
    .style('fill', stroke.color)
    .style('stroke', 'transparent')
    .classed('pending-stroke', true)
    .attr('d', `${svgPath}Z`)
}

export function drawErasingStroke (context, stroke) {
  stroke.width = 20
  const svgPath = buildSVGPath(context, stroke)

  context
    .style('fill', 'grey')
    .style('stroke', 'transparent')
    .style('opacity', '0.2')
    .style('shadowBlur', '5')
    .classed('erasing-stroke', true)
    .attr('d', `${svgPath}Z`)
}