# renderer/canvas/symbols/ShapeSymbolCanvasRenderer.js

    import { rendererLogger as logger } from '../../../configuration/LoggerConfig'

    /**
     * @type {{table: String, shape: String, recognizedShape: String, ellipse: String, line: String}}
     */
    export const ShapeSymbols = {
      table: 'table',
      shape: 'shape',
      recognizedShape: 'recognizedShape',
      ellipse: 'ellipse',
      line: 'line'
    }

    function phi (angle) {
      let returnedAngle = ((angle + Math.PI) % (Math.PI * 2)) - Math.PI
      if (returnedAngle < -Math.PI) {
        returnedAngle += Math.PI * 2
      }
      return returnedAngle
    }

    function drawEllipseArc (context, centerPoint, maxRadius, minRadius, orientation, startAngle, sweepAngle) {
      const angleStep = 0.02 // angle delta between interpolated

      let z1 = Math.cos(orientation)
      let z3 = Math.sin(orientation)
      let z2 = z1
      let z4 = z3
      z1 *= maxRadius
      z2 *= minRadius
      z3 *= maxRadius
      z4 *= minRadius

      const n = Math.floor(Math.abs(sweepAngle) / angleStep)

      const boundariesPoints = []

      context.save()
      try {
        context.beginPath()

        for (let i = 0; i <= n; i++) {
          const angle = startAngle + ((i / n) * sweepAngle) // points on the arc, in radian
          const alpha = Math.atan2(Math.sin(angle) / minRadius, Math.cos(angle) / maxRadius)

          const cosAlpha = Math.cos(alpha)
          const sinAlpha = Math.sin(alpha)

          // current point
          const x = (centerPoint.x + (z1 * cosAlpha)) - (z4 * sinAlpha)
          const y = (centerPoint.y + (z2 * sinAlpha)) + (z3 * cosAlpha)
          if (i === 0) {
            context.moveTo(x, y)
          } else {
            context.lineTo(x, y)
          }

          if (i === 0 || i === n) {
            boundariesPoints.push({ x, y })
          }
        }

        context.stroke()
      } finally {
        context.restore()
      }

      return boundariesPoints
    }

    function drawArrowHead (context, headPoint, angle, length) {
      const alpha = phi(angle + (Math.PI * (7 / 8)))
      const beta = phi(angle - (Math.PI * (7 / 8)))

      const contextReference = context
      contextReference.save()
      try {
        contextReference.fillStyle = contextReference.strokeStyle

        contextReference.moveTo(headPoint.x, headPoint.y)
        contextReference.beginPath()
        contextReference.lineTo(headPoint.x + (length * Math.cos(alpha)), headPoint.y + (length * Math.sin(alpha)))
        contextReference.lineTo(headPoint.x + (length * Math.cos(beta)), headPoint.y + (length * Math.sin(beta)))
        contextReference.lineTo(headPoint.x, headPoint.y)
        contextReference.fill()
      } finally {
        contextReference.restore()
      }
    }

    function drawShapeEllipse (context, shapeEllipse) {
      const points = drawEllipseArc(
        context,
        shapeEllipse.center,
        shapeEllipse.maxRadius,
        shapeEllipse.minRadius,
        shapeEllipse.orientation,
        shapeEllipse.startAngle,
        shapeEllipse.sweepAngle)

      if (shapeEllipse.beginDecoration && shapeEllipse.beginDecoration === 'ARROW_HEAD') {
        drawArrowHead(context, points[0], shapeEllipse.beginTangentAngle, 12.0)
      }
      if (shapeEllipse.endDecoration && shapeEllipse.endDecoration === 'ARROW_HEAD') {
        drawArrowHead(context, points[1], shapeEllipse.endTangentAngle, 12.0)
      }
    }

    /**
     * Draw a line
     * @param {Object} context Current rendering context
     * @param {{x: Number, y: Number}} p1 Origin point
     * @param {{x: Number, y: Number}} p2 Destination point
     */
    export function drawLine (context, p1, p2) {
      context.save()
      try {
        context.beginPath()
        context.moveTo(p1.x, p1.y)
        context.lineTo(p2.x, p2.y)
        context.stroke()
      } finally {
        context.restore()
      }
    }

    function drawShapeLine (context, shapeLine) {
      drawLine(context, shapeLine.firstPoint, shapeLine.lastPoint)
      if (shapeLine.beginDecoration === 'ARROW_HEAD') {
        drawArrowHead(context, shapeLine.firstPoint, shapeLine.beginTangentAngle, 12.0)
      }
      if (shapeLine.endDecoration === 'ARROW_HEAD') {
        drawArrowHead(context, shapeLine.lastPoint, shapeLine.endTangentAngle, 12.0)
      }
    }

    /**
     * Draw a shape symbol
     * @param {Object} context Current rendering context
     * @param {Object} symbol Symbol to draw
     */
    export function drawShapeSymbol (context, symbol) {
      logger.debug(`draw ${symbol.type} symbol`)
      const contextReference = context
      contextReference.save()
      try {
        contextReference.lineWidth = symbol.width
        contextReference.strokeStyle = symbol.color

        if (symbol.elementType) {
          switch (symbol.elementType) {
            case ShapeSymbols.shape:
              drawShapeSymbol(contextReference, symbol.candidates[symbol.selectedCandidateIndex])
              break
            case ShapeSymbols.table:
              symbol.lines.forEach(line => drawShapeSymbol(contextReference, line))
              break
            case ShapeSymbols.line:
              drawLine(contextReference, symbol.data.p1, symbol.data.p2)
              break
            default:
              logger.error(`${symbol.elementType} not implemented`)
              break
          }
        } else {
          switch (symbol.type) {
            case ShapeSymbols.ellipse:
              drawShapeEllipse(contextReference, symbol)
              break
            case ShapeSymbols.line:
              drawShapeLine(contextReference, symbol)
              break
            case ShapeSymbols.recognizedShape:
              symbol.primitives.forEach(primitive => drawShapeSymbol(contextReference, primitive))
              break
            default:
              logger.error(`${symbol.type} not implemented`)
              break
          }
        }
      } finally {
        contextReference.restore()
      }
    }