210 lines
5.0 KiB
TypeScript
210 lines
5.0 KiB
TypeScript
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 |