vxe-pc-ui
Version:
A vue based PC component library
181 lines (158 loc) • 5.68 kB
text/typescript
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)
}
})
}