const SelfFontGap = 3
const getMarkSize = ({ props, ctx, state }) => {
const { content } = props
const { fontSize, fontFamily } = state.font
let defaultWidth = 120
let defaultHeight = 64
if (!props.image && ctx.measureText) {
ctx.font = `${Number(fontSize)}px ${fontFamily}`
const contents = Array.isArray(content) ? content : [content]
const widths = contents.map((item) => ctx.measureText(item!).width)
defaultWidth = Math.ceil(Math.max(...widths))
defaultHeight = Number(fontSize) * contents.length + (contents.length - 1) * SelfFontGap
}
return [Math.max(props.width, defaultWidth), Math.max(props.height, defaultHeight)]
}
const selfDrawText = ({ props, state, ctx, canvasWidth, canvasHeight, ratios }) => {
const { rotate, content } = props
const { fontSize, color, fontStyle, fontWeight, fontFamily } = state.font
const devicePixelRatio = window.devicePixelRatio || 1
const mergedFontSize = Number(fontSize) * devicePixelRatio
const drawHeight = canvasHeight / devicePixelRatio
ctx.font = `${fontStyle} normal ${fontWeight} ${mergedFontSize}px/${drawHeight}px ${fontFamily}`
ctx.fillStyle = color
ctx.textAlign = 'center'
ctx.textBaseline = 'bottom'
ratios.forEach((ratio) => {
ctx.save()
ctx.translate(canvasWidth / ratio, canvasHeight / ratio)
ctx.rotate((Math.PI / 180) * rotate)
const contents = Array.isArray(content) ? content : [content]
const fontHeight = mergedFontSize + SelfFontGap * devicePixelRatio
const startHeight = (canvasHeight - contents.length * fontHeight) / 2 - canvasHeight / 2
contents?.forEach((item, index) => {
ctx.fillText(item || '', 0, startHeight + (index + 1) * fontHeight)
})
ctx.restore()
})
}
const imageLoad = (props, state, ctx: CanvasRenderingContext2D, result, canvas: HTMLCanvasElement): Promise<any> => {
const { image, width, height, rotate, interlaced } = props
const img = new Image()
return new Promise((resolve) => {
img.onload = () => {
const ratios = interlaced ? [4, 4 / 3] : [2]
ratios.forEach((ratio) => {
ctx.save()
ctx.translate(result.size.width / ratio, result.size.height / ratio)
ctx.rotate((Math.PI / 180) * rotate)
ctx.drawImage(img, -width / 2, -height / 2, width, height)
ctx.restore()
})
resolve({
base64: canvas.toDataURL(),
...result
})
}
img.onerror = () => {
const propsTmp = props.content
? props
: {
...props,
content: 'Invalid data'
}
if (interlaced) {
selfDrawText({
props,
ctx,
state,
canvasWidth: result.size.width,
canvasHeight: result.size.height,
ratios: [4, 4 / 3]
})
} else {
selfDrawText({
props: propsTmp,
ctx,
state,
canvasWidth: result.size.width,
canvasHeight: result.size.height,
ratios: [2]
})
}
resolve({
base64: canvas.toDataURL(),
...result
})
}
img.crossOrigin = 'anonymous'
img.referrerPolicy = 'no-referrer'
img.src = image!
})
}
export const useWatermarkBg = function (props, state) {
const { gap, image, interlaced } = props
const [gapX, gapY] = gap
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
const devicePixelRatio = window.devicePixelRatio || 1
const [markWidth, markHeight] = getMarkSize({ props, state, ctx })
const singleWidth = (gapX + markWidth) * devicePixelRatio
const singleHeight = (gapY + markHeight) * devicePixelRatio
const canvasWidth = interlaced ? singleWidth * 2 : singleWidth
const canvasHeight = interlaced ? singleHeight * 2 : singleHeight
canvas.width = canvasWidth
canvas.height = canvasHeight
const result = {
size: {
width: canvasWidth,
height: canvasHeight
},
styleSize: {
width: canvasWidth / devicePixelRatio,
height: canvasHeight / devicePixelRatio
}
}
if (image) {
return imageLoad(props, state, ctx, result, canvas)
} else {
if (interlaced) {
selfDrawText({ props, state, ctx, canvasWidth, canvasHeight, ratios: [4, 4 / 3] })
} else {
selfDrawText({ props, state, ctx, canvasWidth, canvasHeight, ratios: [2] })
}
}
return new Promise((resolve) =>
resolve({
base64: canvas.toDataURL(),
...result
})
)
}
export const updateWatermark = (watermarkBg, props) => {
const { base64, styleSize } = watermarkBg
const watermarkDiv = document.createElement('div')
watermarkDiv.style.position = 'absolute'
watermarkDiv.style.inset = '0'
watermarkDiv.style.backgroundImage = `url(${base64})`
watermarkDiv.style.backgroundSize = `${styleSize.width}px ${styleSize.height}px`
watermarkDiv.style.backgroundRepeat = 'repeat'
watermarkDiv.style.backgroundPosition = `${props.offset[0] || 20}px ${props?.offset[1] || 20}px`
watermarkDiv.style.width = '100%'
watermarkDiv.style.height = '100%'
watermarkDiv.style.pointerEvents = 'none'
watermarkDiv.style.zIndex = `${props.zIndex}`
return watermarkDiv
}
async function setSlotDivStyle({ vm, props }) {
const parentRef = vm.$refs.parentRef
if (!parentRef) {
return
}
for (let child of parentRef.children) {
child.style.position = 'relative'
child.style.zIndex = `${props.zIndex + 1}`
}
}
async function createWatermark({ vm, state, props }) {
const parentRef = vm.$refs.parentRef
if (!parentRef) {
return
}
const bgValue = await useWatermarkBg(props, state)
if (state.watermarkDiv) {
state.watermarkDiv.remove()
}
state.watermarkDiv = updateWatermark(bgValue, props)
parentRef.appendChild(state.watermarkDiv)
}
export const reRenderWatermark =
({ state }) =>
() => {
state.flag++
}
export const mounted =
({ vm, state, props }) =>
() => {
setSlotDivStyle({ vm, props })
createWatermark({ vm, state, props })
state.observerInstance = new MutationObserver((records: Array<MutationRecord>) => {
for (const record of records) {
for (const dom of Array.from(record.removedNodes)) {
if (dom === state.watermarkDiv) {
state.flag++
return
}
}
if (record.type === 'attributes' && record.target === state.watermarkDiv) {
state.flag++
return
}
}
})
const parentRef = vm.$refs.parentRef
if (parentRef) {
state.observerInstance.observe(parentRef, {
childList: true,
attributes: true,
subtree: true
})
if (props.customClass) {
parentRef.classList.add(props.customClass)
}
}
}
export const unmounted =
({ state }) =>
() => {
if (state.observerInstance) {
state.observerInstance.disconnect()
}
state.watermarkDiv = null
}
export const watchProps =
({ state, vm, props }) =>
() =>
createWatermark({ state, vm, props })