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()
  }
}