UNPKG

vxe-pc-ui

Version:
181 lines (158 loc) • 5.68 kB
import XEUtils from 'xe-utils' import { toCssUnit } from '../../ui/src/dom' import type { VxeWatermarkPropTypes, VxeWatermarkDefines } from '../../../types' interface ContentMarkOptions { rotate?: VxeWatermarkPropTypes.Rotate font: VxeWatermarkPropTypes.Font gap?: VxeWatermarkPropTypes.Gap offset?: VxeWatermarkPropTypes.Offset } let canvasEl: HTMLCanvasElement | null = null let fontEl: HTMLSpanElement | null = null const fontCacheMaps: Record<string, { width: number height: number }> = {} function getMarkCanvas () { if (!canvasEl) { canvasEl = document.createElement('canvas') canvasEl.style.position = 'absolute' canvasEl.style.top = '0' canvasEl.style.left = '0' } return canvasEl } function removeMarkElement (elem: Element | null) { if (elem) { const parentEl = elem.parentNode if (parentEl) { parentEl.removeChild(elem) } } } function calcFontWH (text: string, fontSize: number) { const fKey = `${fontSize}_${text}` if (!fontCacheMaps[fKey]) { if (!fontEl) { fontEl = document.createElement('span') } if (!fontEl.parentNode) { document.body.append(fontEl) } fontEl.textContent = text fontEl.style.fontSize = toCssUnit(fontSize) const width = fontEl.offsetWidth const height = fontEl.offsetHeight fontCacheMaps[fKey] = { width, height } } return fontCacheMaps[fKey] } function calcContentWH (contList: VxeWatermarkDefines.ContentObj[]) { let contentWidth = 0 let contentHeight = 0 contList.forEach(item => { contentWidth = Math.max(item.width, contentWidth) contentHeight = Math.max(item.height, contentHeight) }) return { contentWidth, contentHeight } } function getFontConf (item: VxeWatermarkDefines.ContentObj | VxeWatermarkDefines.ContentConf, key: keyof VxeWatermarkPropTypes.Font, opts: ContentMarkOptions) { return (item.font ? item.font[key] : '') || (opts.font ? opts.font[key] : '') } function createMarkFont (contConf: VxeWatermarkDefines.ContentConf, defaultFontSize: number | string, opts: ContentMarkOptions) { const { offset } = opts const text = XEUtils.toValueString(contConf.textContent) const fontSize = XEUtils.toNumber(getFontConf(contConf, 'fontSize', opts) || defaultFontSize) || 14 const [offsetX = 0, offsetY = 0] = offset ? ((XEUtils.isArray(offset) ? offset : [offset, offset])) : [] const { width, height } = calcFontWH(text, fontSize) return { text, fontSize, font: contConf.font, width: width + XEUtils.toNumber(offsetX), height: height + XEUtils.toNumber(offsetY) } } function drayFont (ctx: CanvasRenderingContext2D, item: VxeWatermarkDefines.ContentObj, opts: ContentMarkOptions) { const fontWeight = getFontConf(item, 'fontWeight', opts) ctx.fillStyle = `${getFontConf(item, 'color', opts) || 'rgba(0, 0, 0, 0.15)'}` ctx.font = [ getFontConf(item, 'fontStyle', opts) || 'normal', fontWeight === 'bold' || fontWeight === 'bolder' ? 'bold' : '', toCssUnit(item.fontSize), getFontConf(item, 'fontFamily', opts) || 'sans-serif' ].join(' ') } export function getContentUrl (content: VxeWatermarkPropTypes.Content, defaultFontSize: number | string, options: ContentMarkOptions) { const opts = Object.assign({}, options) const { rotate } = opts const deg = XEUtils.toNumber(rotate) const contList: VxeWatermarkDefines.ContentObj[] = (XEUtils.isArray(content) ? content : [content]).map(item => { if (item) { if ((item as any).textContent) { return createMarkFont(item as VxeWatermarkDefines.ContentConf, defaultFontSize, opts) } return createMarkFont({ textContent: `${item}` }, defaultFontSize, opts) } return createMarkFont({ textContent: '' }, defaultFontSize, opts) }) removeMarkElement(fontEl) return new Promise<string>((resolve) => { const canvasEl = getMarkCanvas() if (!canvasEl.parentNode) { document.body.append(canvasEl) } const ctx = canvasEl.getContext('2d') if (ctx && contList.length) { const { gap } = opts const gapList = gap ? ((XEUtils.isArray(gap) ? gap : [gap, gap])) : [] const gapX = XEUtils.toNumber(gapList[0]) const gapY = XEUtils.toNumber(gapList[1]) const { contentWidth } = calcContentWH(contList) const canvasWidth = contentWidth * 2 + gapX * 2 + (gapX / 2) const canvasHeight = contentWidth * 2 + gapY canvasEl.width = canvasWidth canvasEl.height = canvasHeight const drayX = (gapX / 2) const drayY = contentWidth + (gapY / 2) ctx.save() ctx.translate(drayX, drayY) ctx.rotate(deg * Math.PI / 180) ctx.translate(-drayX, -drayY) let txtOffsetY = 0 contList.forEach((item) => { drayFont(ctx, item, opts) const txtX = drayX const txtY = drayY + txtOffsetY ctx.fillText(item.text, txtX, txtY, contentWidth) txtOffsetY += item.height }) const offsetX = gapX const offsetY = gapY + contentWidth txtOffsetY = 0 contList.forEach((item) => { drayFont(ctx, item, opts) const txtX = drayX + offsetX const txtY = drayY + txtOffsetY + offsetY ctx.fillText(item.text, txtX, txtY, contentWidth) txtOffsetY += item.height }) ctx.restore() resolve(canvasEl.toDataURL()) removeMarkElement(canvasEl) } else { resolve('') removeMarkElement(canvasEl) } }) }