staff_data/packages/mindmap/src/plugins/Watermark.ts

210 lines
5.0 KiB
TypeScript
Raw Normal View History

2025-01-13 07:52:51 +08:00
import { Text, G } from '@svgdotjs/svg.js'
import { degToRad, camelCaseToHyphen } from '../utils'
import merge from 'deepmerge'
import MindMap from '..'
interface WatermarkOptions {
mindMap: MindMap
}
interface WatermarkConfig {
text?: string | number
lineSpacing?: number
textSpacing?: number
angle?: number
textStyle?: Record<string, any>
belowNode?: boolean
onlyExport?: boolean
}
interface TextStyle {
[key: string]: any
}
// 水印插件
class Watermark {
static instanceName: string = 'watermark'
private mindMap: any
private lineSpacing: number
private textSpacing: number
private angle: number
private text: string
private textStyle: TextStyle
private watermarkDraw: G | null
private isInExport: boolean
private maxLong: number
constructor(opt: WatermarkOptions = {} as WatermarkOptions) {
this.mindMap = opt.mindMap
this.lineSpacing = 0
this.textSpacing = 0
this.angle = 0
this.text = ''
this.textStyle = {}
this.watermarkDraw = null
this.isInExport = false
this.maxLong = this.getMaxLong()
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
this.bindEvent()
}
private getMaxLong(): number {
return Math.sqrt(
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
)
}
private bindEvent(): void {
this.onResize = this.onResize.bind(this)
this.mindMap.on('resize', this.onResize)
}
private unBindEvent(): void {
this.mindMap.off('resize', this.onResize)
}
private onResize(): void {
this.maxLong = this.getMaxLong()
this.draw()
}
private createContainer(): void {
if (this.watermarkDraw) return
this.watermarkDraw = new G()
.css({
pointerEvents: 'none',
userSelect: 'none'
})
.addClass('smm-water-mark-container')
this.updateLayer()
}
private updateLayer(): void {
if (!this.watermarkDraw) return
const { belowNode } = this.mindMap.opt.watermarkConfig
if (belowNode) {
this.watermarkDraw.insertBefore(this.mindMap.draw)
} else {
this.mindMap.svg.add(this.watermarkDraw)
}
}
private removeContainer(): void {
if (!this.watermarkDraw) {
return
}
this.watermarkDraw.remove()
this.watermarkDraw = null
}
private hasWatermark(): boolean {
return !!this.text.trim()
}
private handleConfig({ text, lineSpacing, textSpacing, angle, textStyle }: WatermarkConfig): void {
this.text = text === undefined ? '' : String(text).trim()
this.lineSpacing =
typeof lineSpacing === 'number' && lineSpacing > 0 ? lineSpacing : 100
this.textSpacing =
typeof textSpacing === 'number' && textSpacing > 0 ? textSpacing : 100
this.angle =
typeof angle === 'number' && angle >= 0 && angle <= 90 ? angle : 30
this.textStyle = Object.assign(this.textStyle, textStyle || {})
}
private clear(): void {
if (this.watermarkDraw) this.watermarkDraw.clear()
}
private draw(): void {
this.clear()
const { onlyExport } = this.mindMap.opt.watermarkConfig
if (onlyExport && !this.isInExport) return
if (!this.hasWatermark()) {
this.removeContainer()
return
}
this.createContainer()
let x = 0
while (x < this.mindMap.width) {
this.drawText(x)
x += this.lineSpacing / Math.sin(degToRad(this.angle))
}
let yOffset =
this.lineSpacing / Math.cos(degToRad(this.angle)) || this.lineSpacing
let y = yOffset
while (y < this.mindMap.height) {
this.drawText(0, y)
y += yOffset
}
}
private drawText(x: number, y?: number): void {
let long = Math.min(
this.maxLong,
(this.mindMap.width - x) / Math.cos(degToRad(this.angle))
)
let g = new G()
let bbox = null
let bboxWidth = 0
let textHeight = -1
while (bboxWidth < long) {
let text = new Text().text(this.text)
g.add(text)
text.transform({
translateX: bboxWidth
})
this.setTextStyle(text)
bbox = g.bbox()
if (textHeight === -1) {
textHeight = bbox.height
}
bboxWidth = bbox.width + this.textSpacing
}
let params = {
rotate: this.angle,
origin: 'top left',
translateX: x,
translateY: textHeight
}
if (y !== undefined) {
params.translateY = y + textHeight
}
g.transform(params)
this.watermarkDraw?.add(g)
}
private setTextStyle(text: Text): void {
Object.keys(this.textStyle).forEach(item => {
let value = this.textStyle[item]
if (item === 'color') {
text.fill(value)
} else {
text.css(camelCaseToHyphen(item) as CSSStyleName, value)
}
})
}
public updateWatermark(config: WatermarkConfig): void {
this.mindMap.opt.watermarkConfig = merge(
this.mindMap.opt.watermarkConfig,
config
)
this.updateLayer()
this.handleConfig(config)
this.draw()
}
public beforePluginRemove(): void {
this.unBindEvent()
this.removeContainer()
}
public beforePluginDestroy(): void {
this.unBindEvent()
this.removeContainer()
}
}
export default Watermark