draw.js 8.28 KB
import { getLinearColor, getTextX, toPx } from './tools'

const drawRadiusRect = ({ x, y, w, h, r }, { ctx }) => {
  const minSize = Math.min(w, h)
  if (r > minSize / 2) {
    r = minSize / 2
  }
  ctx.beginPath()
  ctx.moveTo(x + r, y)
  ctx.arcTo(x + w, y, x + w, y + h, r)
  ctx.arcTo(x + w, y + h, x, y + h, r)
  ctx.arcTo(x, y + h, x, y, r)
  ctx.arcTo(x, y, x + w, y, r)
  ctx.closePath()
}

const drawRadiusGroupRect = ({ x, y, w, h, g }, { ctx }) => {
  const [
    borderTopLeftRadius,
    borderTopRightRadius,
    borderBottomRightRadius,
    borderBottomLeftRadius
  ] = g
  ctx.beginPath()
  ctx.arc(
    x + w - borderBottomRightRadius,
    y + h - borderBottomRightRadius,
    borderBottomRightRadius,
    0,
    Math.PI * 0.5
  )
  ctx.lineTo(x + borderBottomLeftRadius, y + h)
  ctx.arc(
    x + borderBottomLeftRadius,
    y + h - borderBottomLeftRadius,
    borderBottomLeftRadius,
    Math.PI * 0.5,
    Math.PI
  )
  ctx.lineTo(x, y + borderTopLeftRadius)
  ctx.arc(
    x + borderTopLeftRadius,
    y + borderTopLeftRadius,
    borderTopLeftRadius,
    Math.PI,
    Math.PI * 1.5
  )
  ctx.lineTo(x + w - borderTopRightRadius, y)
  ctx.arc(
    x + w - borderTopRightRadius,
    y + borderTopRightRadius,
    borderTopRightRadius,
    Math.PI * 1.5,
    Math.PI * 2
  )
  ctx.lineTo(x + w, y + h - borderBottomRightRadius)
  ctx.closePath()
}

const getTextWidth = (text, drawOptions) => {
  const { ctx } = drawOptions
  let texts = []
  if (Object.prototype.toString.call(text) === '[object Object]') {
    texts.push(text)
  } else {
    texts = text
  }
  let width = 0
  texts.forEach(
    ({
      fontSize,
      text: textStr,
      fontStyle = 'normal',
      fontWeight = 'normal',
      fontFamily = 'sans-serif',
      marginLeft = 0,
      marginRight = 0
    }) => {
      ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`
      width += ctx.measureText(textStr).width + marginLeft + marginRight
    }
  )
  return width
}

const drawSingleText = (drawData, drawOptions) => {
  const {
    x = 0,
    y = 0,
    text,
    color,
    width,
    fontSize = 28,
    baseLine = 'top',
    textAlign = 'left',
    opacity = 1,
    textDecoration = 'none',
    lineNum = 1,
    lineHeight = 0,
    fontWeight = 'normal',
    fontStyle = 'normal',
    fontFamily = 'sans-serif'
  } = drawData
  const { ctx } = drawOptions
  ctx.save()
  ctx.beginPath()
  ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`
  ctx.globalAlpha = opacity
  ctx.fillStyle = color
  ctx.textBaseline = baseLine
  ctx.textAlign = textAlign
  let textWidth = ctx.measureText(text).width
  const textArr = []

  if (textWidth > width) {
    let fillText = ''
    let line = 1
    for (let i = 0; i <= text.length - 1; i++) {
      fillText += text[i]
      const nextText = i < text.length - 1 ? fillText + text[i + 1] : fillText
      const restWidth = width - ctx.measureText(nextText).width

      if (restWidth < 0) {
        if (line === lineNum) {
          if (restWidth + ctx.measureText(text[i + 1]).width > ctx.measureText('...').width) {
            fillText = `${fillText}...`
          } else {
            fillText = `${fillText.substr(0, fillText.length - 1)}...`
          }
          textArr.push(fillText)
          break
        } else {
          textArr.push(fillText)
          line++
          fillText = ''
        }
      } else if (i === text.length - 1) {
        textArr.push(fillText)
      }
    }
    textWidth = width
  } else {
    textArr.push(text)
  }

  textArr.forEach((item, index) =>
    ctx.fillText(item, getTextX(textAlign, x, width), y + (lineHeight || fontSize) * index)
  )
  ctx.restore()

  if (textDecoration !== 'none') {
    let lineY = y
    if (textDecoration === 'line-through') {
      lineY = y
    }
    ctx.save()
    ctx.moveTo(x, lineY)
    ctx.lineTo(x + textWidth, lineY)
    ctx.strokeStyle = color
    ctx.stroke()
    ctx.restore()
  }
  return textWidth
}

export function drawText(params, drawOptions) {
  const { x = 0, y = 0, text, baseLine } = params
  if (Object.prototype.toString.call(text) === '[object Array]') {
    const preText = { x, y, baseLine }
    text.forEach(item => {
      preText.x += item.marginLeft || 0
      const textWidth = drawSingleText(
        Object.assign(item, { ...preText, y: y + (item.marginTop || 0) }),
        drawOptions
      )
      preText.x += textWidth + (item.marginRight || 0)
    })
  } else {
    drawSingleText(params, drawOptions)
  }
}

export function drawLine(drawData, drawOptions) {
  const { startX, startY, endX, endY, color, width } = drawData
  const { ctx } = drawOptions
  if (!width) {
    return
  }
  ctx.save()
  ctx.beginPath()
  ctx.strokeStyle = color
  ctx.lineWidth = width
  ctx.moveTo(startX, startY)
  ctx.lineTo(endX, endY)
  ctx.stroke()
  ctx.closePath()
  ctx.restore()
}

export function drawBlock(data, drawOptions) {
  const {
    x,
    y,
    text,
    width = 0,
    height,
    opacity = 1,
    paddingLeft = 0,
    paddingRight = 0,
    borderWidth,
    backgroundColor,
    borderColor,
    borderRadius = 0,
    borderRadiusGroup = null
  } = data || {}
  const { ctx } = drawOptions
  ctx.save()
  ctx.globalAlpha = opacity

  let blockWidth = 0
  let textX = 0
  let textY = 0

  if (text) {
    const textWidth = getTextWidth(typeof text.text === 'string' ? text : text.text, drawOptions)
    blockWidth = textWidth > width ? textWidth : width
    blockWidth += paddingLeft + paddingLeft

    const { textAlign = 'left' } = text
    textY = y
    textX = x + paddingLeft

    if (textAlign === 'center') {
      textX = blockWidth / 2 + x
    } else if (textAlign === 'right') {
      textX = x + blockWidth - paddingRight
    }
    drawText(Object.assign(text, { x: textX, y: textY }), drawOptions)
  } else {
    blockWidth = width
  }

  if (backgroundColor) {
    const grd = getLinearColor(ctx, backgroundColor, x, y, blockWidth, height)
    ctx.fillStyle = grd

    if (borderRadius > 0) {
      const drawData = {
        x,
        y,
        w: blockWidth,
        h: height,
        r: borderRadius
      }
      drawRadiusRect(drawData, drawOptions)
      ctx.fill()
    } else if (borderRadiusGroup) {
      const drawData = {
        x,
        y,
        w: blockWidth,
        h: height,
        g: borderRadiusGroup
      }
      drawRadiusGroupRect(drawData, drawOptions)
      ctx.fill()
    } else {
      ctx.fillRect(x, y, blockWidth, height)
    }
  }

  if (borderWidth && borderRadius > 0) {
    ctx.strokeStyle = borderColor
    ctx.lineWidth = borderWidth
    if (borderRadius > 0) {
      const drawData = {
        x,
        y,
        w: blockWidth,
        h: height,
        r: borderRadius
      }
      drawRadiusRect(drawData, drawOptions)
      ctx.stroke()
    } else {
      ctx.strokeRect(x, y, blockWidth, height)
    }
  }
  ctx.restore()
}

export const drawImage = (data, drawOptions) =>
  new Promise(resolve => {
    const { canvas, ctx } = drawOptions
    const {
      x,
      y,
      w,
      h,
      sx,
      sy,
      sw,
      sh,
      imgPath,
      borderRadius = 0,
      borderWidth = 0,
      borderColor,
      borderRadiusGroup = null
    } = data

    ctx.save()
    if (borderRadius > 0) {
      drawRadiusRect(
        {
          x,
          y,
          w,
          h,
          r: borderRadius
        },
        drawOptions
      )
      ctx.clip()
      ctx.fill()
      const img = canvas.createImage()
      img.src = imgPath
      img.onload = () => {
        ctx.drawImage(img, toPx(sx), toPx(sy), toPx(sw), toPx(sh), x, y, w, h)
        if (borderWidth > 0) {
          ctx.strokeStyle = borderColor
          ctx.lineWidth = borderWidth
          ctx.stroke()
        }
        resolve()
        ctx.restore()
      }
    } else if (borderRadiusGroup) {
      drawRadiusGroupRect(
        {
          x,
          y,
          w,
          h,
          g: borderRadiusGroup
        },
        drawOptions
      )
      ctx.clip()
      ctx.fill()
      const img = canvas.createImage()
      img.src = imgPath
      img.onload = () => {
        ctx.drawImage(img, toPx(sx), toPx(sy), toPx(sw), toPx(sh), x, y, w, h)
        resolve()
        ctx.restore()
      }
    } else {
      const img = canvas.createImage()
      img.src = imgPath
      img.onload = () => {
        ctx.drawImage(img, toPx(sx), toPx(sy), toPx(sw), toPx(sh), x, y, w, h)
        resolve()
        ctx.restore()
      }
    }
  })