mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
* feat(components): [watermark] add fontGap property * docs(components): [watermark] add fontGap option * docs(components): [watermark] add version annotation to fontGap property
165 lines
4.5 KiB
TypeScript
165 lines
4.5 KiB
TypeScript
import { isArray } from '@element-plus/utils'
|
|
|
|
import type { WatermarkProps } from './watermark'
|
|
|
|
// [alignRatio, spaceRatio]
|
|
const TEXT_ALIGN_RATIO_MAP = {
|
|
left: [0, 0.5],
|
|
start: [0, 0.5],
|
|
center: [0.5, 0],
|
|
right: [1, -0.5],
|
|
end: [1, -0.5],
|
|
} as const
|
|
|
|
function prepareCanvas(
|
|
width: number,
|
|
height: number,
|
|
ratio = 1
|
|
): [
|
|
ctx: CanvasRenderingContext2D,
|
|
canvas: HTMLCanvasElement,
|
|
realWidth: number,
|
|
realHeight: number,
|
|
] {
|
|
const canvas = document.createElement('canvas')
|
|
const ctx = canvas.getContext('2d')!
|
|
const realWidth = width * ratio
|
|
const realHeight = height * ratio
|
|
canvas.setAttribute('width', `${realWidth}px`)
|
|
canvas.setAttribute('height', `${realHeight}px`)
|
|
ctx.save()
|
|
|
|
return [ctx, canvas, realWidth, realHeight]
|
|
}
|
|
|
|
/**
|
|
* Get the clips of text content.
|
|
* This is a lazy hook function since SSR no need this
|
|
*/
|
|
export default function useClips() {
|
|
// Get single clips
|
|
function getClips(
|
|
content: NonNullable<WatermarkProps['content']> | HTMLImageElement,
|
|
rotate: number,
|
|
ratio: number,
|
|
width: number,
|
|
height: number,
|
|
font: Required<NonNullable<WatermarkProps['font']>>,
|
|
gapX: number,
|
|
gapY: number,
|
|
space: number
|
|
): [dataURL: string, finalWidth: number, finalHeight: number] {
|
|
// ================= Text / Image =================
|
|
const [ctx, canvas, contentWidth, contentHeight] = prepareCanvas(
|
|
width,
|
|
height,
|
|
ratio
|
|
)
|
|
|
|
if (content instanceof HTMLImageElement) {
|
|
// Image
|
|
ctx.drawImage(content, 0, 0, contentWidth, contentHeight)
|
|
} else {
|
|
// Text
|
|
const {
|
|
color,
|
|
fontSize,
|
|
fontStyle,
|
|
fontWeight,
|
|
fontFamily,
|
|
textAlign,
|
|
textBaseline,
|
|
} = font
|
|
const mergedFontSize = Number(fontSize) * ratio
|
|
|
|
ctx.font = `${fontStyle} normal ${fontWeight} ${mergedFontSize}px/${height}px ${fontFamily}`
|
|
ctx.fillStyle = color
|
|
ctx.textAlign = textAlign
|
|
ctx.textBaseline = textBaseline
|
|
const contents = isArray(content) ? content : [content]
|
|
contents?.forEach((item, index) => {
|
|
const [alignRatio, spaceRatio] = TEXT_ALIGN_RATIO_MAP[textAlign]
|
|
ctx.fillText(
|
|
item ?? '',
|
|
contentWidth * alignRatio + space * spaceRatio,
|
|
index * (mergedFontSize + font.fontGap * ratio)
|
|
)
|
|
})
|
|
}
|
|
|
|
// ==================== Rotate ====================
|
|
const angle = (Math.PI / 180) * Number(rotate)
|
|
const maxSize = Math.max(width, height)
|
|
const [rCtx, rCanvas, realMaxSize] = prepareCanvas(maxSize, maxSize, ratio)
|
|
|
|
// Copy from `ctx` and rotate
|
|
rCtx.translate(realMaxSize / 2, realMaxSize / 2)
|
|
rCtx.rotate(angle)
|
|
if (contentWidth > 0 && contentHeight > 0) {
|
|
rCtx.drawImage(canvas, -contentWidth / 2, -contentHeight / 2)
|
|
}
|
|
|
|
// Get boundary of rotated text
|
|
function getRotatePos(x: number, y: number) {
|
|
const targetX = x * Math.cos(angle) - y * Math.sin(angle)
|
|
const targetY = x * Math.sin(angle) + y * Math.cos(angle)
|
|
return [targetX, targetY]
|
|
}
|
|
|
|
let left = 0
|
|
let right = 0
|
|
let top = 0
|
|
let bottom = 0
|
|
|
|
const halfWidth = contentWidth / 2
|
|
const halfHeight = contentHeight / 2
|
|
const points = [
|
|
[0 - halfWidth, 0 - halfHeight],
|
|
[0 + halfWidth, 0 - halfHeight],
|
|
[0 + halfWidth, 0 + halfHeight],
|
|
[0 - halfWidth, 0 + halfHeight],
|
|
]
|
|
points.forEach(([x, y]) => {
|
|
const [targetX, targetY] = getRotatePos(x, y)
|
|
left = Math.min(left, targetX)
|
|
right = Math.max(right, targetX)
|
|
top = Math.min(top, targetY)
|
|
bottom = Math.max(bottom, targetY)
|
|
})
|
|
|
|
const cutLeft = left + realMaxSize / 2
|
|
const cutTop = top + realMaxSize / 2
|
|
const cutWidth = right - left
|
|
const cutHeight = bottom - top
|
|
|
|
// ================ Fill Alternate ================
|
|
const realGapX = gapX * ratio
|
|
const realGapY = gapY * ratio
|
|
const filledWidth = (cutWidth + realGapX) * 2
|
|
const filledHeight = cutHeight + realGapY
|
|
|
|
const [fCtx, fCanvas] = prepareCanvas(filledWidth, filledHeight)
|
|
|
|
function drawImg(targetX = 0, targetY = 0) {
|
|
fCtx.drawImage(
|
|
rCanvas,
|
|
cutLeft,
|
|
cutTop,
|
|
cutWidth,
|
|
cutHeight,
|
|
targetX,
|
|
targetY,
|
|
cutWidth,
|
|
cutHeight
|
|
)
|
|
}
|
|
drawImg()
|
|
drawImg(cutWidth + realGapX, -cutHeight / 2 - realGapY / 2)
|
|
drawImg(cutWidth + realGapX, +cutHeight / 2 + realGapY / 2)
|
|
|
|
return [fCanvas.toDataURL(), filledWidth / ratio, filledHeight / ratio]
|
|
}
|
|
|
|
return getClips
|
|
}
|