renderer/canvas/CanvasRenderer.js

import { rendererLogger as logger } from '../../configuration/LoggerConfig'
import { drawStroke } from './symbols/StrokeSymbolCanvasRenderer'
import { drawTextSymbol, TextSymbols } from './symbols/TextSymbolCanvasRenderer'
import { drawShapeSymbol, ShapeSymbols } from './symbols/ShapeSymbolCanvasRenderer'
import * as InkModel from '../../model/InkModel'

/**
 * Renderer info
 * @typedef {Object} RendererInfo
 * @property {String} type Renderer type.
 * @property {String} apiVersion Supported api version.
 */

/**
 * Default renderer
 * @typedef {Object} Renderer
 * @property {function} getInfo Get some information about this renderer
 * @property {function} attach Populate the DOM element to create rendering area.
 * @property {function} detach Remove rendering area from the DOM element.
 * @property {function} resize Explicitly resize the rendering area.
 * @property {function} drawCurrentStroke Draw the model currentStroke.
 * @property {function} drawModel Draw the model defaultSymbols and recognizedSymbols.
 */

/**
 * Get info
 * @return {RendererInfo} Information about this renderer
 */
export function getInfo () {
  return {
    type: 'canvas'
  }
}

function getPixelRatio (canvas) {
  if (canvas) {
    const context = canvas.getContext('2d')
    // we are using a browser object
    // eslint-disable-next-line no-undef
    const devicePixelRatio = window.devicePixelRatio || 1
    const backingStoreRatio = context.webkitBackingStorePixelRatio ||
        context.mozBackingStorePixelRatio ||
        context.msBackingStorePixelRatio ||
        context.oBackingStorePixelRatio ||
        context.backingStorePixelRatio || 1
    return devicePixelRatio / backingStoreRatio
  }
  return 1
}

function detectPixelRatio (element) {
  // we are using a browser object
  // eslint-disable-next-line no-undef
  const tempCanvas = document.createElement('canvas')
  const canvasRatio = getPixelRatio(tempCanvas)
  // document.removeChild(tempCanvas);
  return canvasRatio
}

function createCanvas (element, type) {
  // eslint-disable-next-line no-undef
  const browserDocument = document
  const canvas = browserDocument.createElement('canvas')
  canvas.classList.add(type)
  canvas.classList.add('ms-canvas')
  element.appendChild(canvas)
  logger.debug('canvas created', canvas)
  return canvas
}

function resizeContent (context) {
  const elements = [context.renderingCanvas, context.capturingCanvas]
  elements.forEach((canvas) => {
    const domElement = canvas.parentNode
    const width = domElement.clientWidth < context.minWidth ? context.minWidth : domElement.clientWidth
    const height = domElement.clientHeight < context.minHeight ? context.minHeight : domElement.clientHeight
    /* eslint-disable no-param-reassign */
    canvas.width = width * context.pixelRatio
    canvas.height = height * context.pixelRatio
    canvas.style.width = `${width}px`
    canvas.style.height = `${height}px`
    /* eslint-enable no-param-reassign */
    canvas.getContext('2d').scale(context.pixelRatio, context.pixelRatio)
    logger.debug('canvas size changed', canvas)
  })
  return context
}

/**
 * Attach the renderer to the DOM element
 * @param {Element} element DOM element to attach the rendering elements
 * @param {Number} [minHeight=0] Minimal height of the editor
 * @param {Number} [minWidth=0] Minimal width of the editor
 * @return {Object} The renderer context to give as parameter when a draw model will be call
 */
export function attach (element, minHeight = 0, minWidth = 0) {
  logger.debug('attach renderer', element)
  const pixelRatio = detectPixelRatio(element)

  const renderingCanvas = createCanvas(element, 'ms-rendering-canvas')
  const capturingCanvas = createCanvas(element, 'ms-capture-canvas')

  const context = {
    pixelRatio,
    minHeight,
    minWidth,
    renderingCanvas,
    renderingCanvasContext: renderingCanvas.getContext('2d'),
    capturingCanvas,
    capturingCanvasContext: capturingCanvas.getContext('2d')
  }

  return resizeContent(context)
}

/**
 * Detach the renderer from the DOM element
 * @param {Element} element DOM element to attach the rendering elements
 * @param {Object} context Current rendering context
 */
export function detach (element, context) {
  logger.debug('detach renderer', element)
  element.removeChild(context.renderingCanvas)
  element.removeChild(context.capturingCanvas)
}

/**
 * Update the rendering context size
 * @param {Object} context Current rendering context
 * @param {Model} model Current model
 * @param {Stroker} stroker Current stroker
 * @return {Model}
 */
export function resize (context, model, stroker) {
  return this.drawModel(resizeContent(context), model, stroker)
}

function drawSymbol (context, symbol, stroker) {
  const type = symbol.elementType ? symbol.elementType : symbol.type
  logger.trace(`attempting to draw ${type} symbol`)
  if (type === 'stroke') {
    drawStroke(context, symbol, stroker)
  } else if (TextSymbols[type]) {
    drawTextSymbol(context, symbol)
  } else if (ShapeSymbols[type]) {
    drawShapeSymbol(context, symbol)
  } else {
    logger.warn(`impossible to draw ${type} symbol`)
  }
}

/**
 * Draw the current stroke from the model
 * @param {Object} context Current rendering context
 * @param {Model} model Current model
 * @param {Stroker} stroker Current stroker
 * @return {Model}
 */
export function drawCurrentStroke (context, model, stroker) {
  // Render the current stroke
  context.capturingCanvasContext.clearRect(0, 0, context.capturingCanvas.width, context.capturingCanvas.height)
  logger.trace('drawing current stroke ', model.currentStroke)
  drawStroke(context.capturingCanvasContext, model.currentStroke, stroker)
  return model
}

/**
 * Draw all symbols contained into the model
 * @param {Object} context Current rendering context
 * @param {Model} model Current model
 * @param {Stroker} stroker Current stroker
 * @return {Model}
 */
export function drawModel (context, model, stroker) {
  context.renderingCanvasContext.clearRect(0, 0, context.renderingCanvas.width, context.renderingCanvas.height)
  // Displaying the default symbols and pending strokes
  const symbols = [...model.defaultSymbols]
  // Displaying the recognition symbols or raw strokes
  if (model.recognizedSymbols) {
    symbols.push(...model.recognizedSymbols)
    symbols.push(...InkModel.extractPendingStrokes(model))
  } else {
    symbols.push(...model.rawStrokes)
  }
  symbols.forEach(symbol => drawSymbol(context.renderingCanvasContext, symbol, stroker))
  context.capturingCanvasContext.clearRect(0, 0, context.capturingCanvas.width, context.capturingCanvas.height)
  return model
}