Home Manual Reference Source

src/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();
  }
}