vxe-pc-ui
Version:
A vue based PC component library
177 lines (154 loc) • 5.67 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 calcCanvasWH (contentWidth: number, opts: ContentMarkOptions) {
const { gap } = opts
const [gapX = 0, gapY = 0] = gap ? ((XEUtils.isArray(gap) ? gap : [gap, gap])) : []
const canvasWidth = contentWidth + XEUtils.toNumber(gapX)
const canvasHeight = contentWidth + XEUtils.toNumber(gapY)
return {
canvasWidth,
canvasHeight
}
}
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 { contentWidth, contentHeight } = calcContentWH(contList)
const { canvasWidth, canvasHeight } = calcCanvasWH(contentWidth, opts)
canvasEl.width = canvasWidth
canvasEl.height = canvasHeight
const x = (canvasWidth - contentWidth) / 2
const y = (canvasHeight - contentHeight) / 2
const drayX = x + (contentWidth / 2)
const drayY = y + (contentHeight / 2)
ctx.save()
ctx.translate(drayX, drayY)
ctx.rotate(deg * Math.PI / 180)
ctx.translate(-drayX, -drayY)
let offsetHeight = 0
contList.forEach((item) => {
const align = getFontConf(item, 'align', opts)
drayFont(ctx, item, opts)
ctx.fillText(item.text, x + (align === 'center' ? (contentWidth - item.width) / 2 : 0), y + (contentHeight + contentHeight) / 2 + offsetHeight, contentWidth)
offsetHeight += item.height
})
ctx.restore()
resolve(canvasEl.toDataURL())
removeMarkElement(canvasEl)
} else {
resolve('')
removeMarkElement(canvasEl)
}
})
}