watermark-js-plus 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -1
- package/dist/index.js +400 -0
- package/dist/index.min.js +1 -0
- package/package.json +22 -10
- package/.editorconfig +0 -16
- package/.eslintignore +0 -7
- package/.eslintrc.cjs +0 -46
- package/.github/workflows/deploy.yml +0 -29
- package/.github/workflows/npm-publish.yml +0 -32
- package/.husky/commit-msg +0 -4
- package/.husky/pre-commit +0 -4
- package/.prettierrc +0 -10
- package/CHANGELOG.md +0 -18
- package/babel.config.cjs +0 -23
- package/changelog-option.cjs +0 -87
- package/commitlint.config.cjs +0 -26
- package/docs/.vitepress/config.ts +0 -103
- package/docs/.vitepress/locales/zh-CN.ts +0 -51
- package/docs/.vitepress/theme/index.ts +0 -12
- package/docs/config/blind-decode.md +0 -33
- package/docs/config/blind.md +0 -19
- package/docs/config/index.md +0 -178
- package/docs/guide/blind-watermark.md +0 -267
- package/docs/guide/getting-started.md +0 -73
- package/docs/guide/watermark.md +0 -213
- package/docs/guide/what-is-this.md +0 -19
- package/docs/index.md +0 -33
- package/docs/public/favicons/apple-touch-icon.png +0 -0
- package/docs/public/favicons/favicon-64x64.png +0 -0
- package/docs/public/hero-image.png +0 -0
- package/docs/public/logo.png +0 -0
- package/docs/zh/config/blind-decode.md +0 -33
- package/docs/zh/config/blind.md +0 -19
- package/docs/zh/config/index.md +0 -178
- package/docs/zh/guide/blink-watermark.md +0 -288
- package/docs/zh/guide/getting-started.md +0 -48
- package/docs/zh/guide/watermark.md +0 -213
- package/docs/zh/guide/what-is-this.md +0 -19
- package/docs/zh/index.md +0 -33
- package/rollup.config.mjs +0 -40
- package/src/blind.ts +0 -43
- package/src/index.ts +0 -7
- package/src/types/index.ts +0 -69
- package/src/utils/index.ts +0 -63
- package/src/watermark.ts +0 -300
- package/tools/terser.js +0 -19
- package/tsconfig.json +0 -15
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: doc
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# 这是什么?
|
|
6
|
-
这是一个基于 *canvas* 画布的水印库,作用于浏览器中。
|
|
7
|
-
|
|
8
|
-
## 功能
|
|
9
|
-
- 创建水印和暗水印
|
|
10
|
-
- 支持文本、多行文本、图片、富文本
|
|
11
|
-
- 支持监听DOM改动行为,可防止水印被手动删除
|
|
12
|
-
- 支持TS
|
|
13
|
-
- 丰富的配置项
|
|
14
|
-
|
|
15
|
-
## 浏览器兼容性
|
|
16
|
-
|
|
17
|
-
|  |  |  |  |  |  |
|
|
18
|
-
|:-------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------:|
|
|
19
|
-
| Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 11 ✔ |
|
package/docs/zh/index.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: home
|
|
3
|
-
sidebar: false
|
|
4
|
-
title: watermark-js-plus.js
|
|
5
|
-
titleTemplate: Watermark
|
|
6
|
-
|
|
7
|
-
hero:
|
|
8
|
-
name: watermark-js-plus
|
|
9
|
-
text: 高级的水印插件
|
|
10
|
-
tagline: 简易、强大、高性能。
|
|
11
|
-
image:
|
|
12
|
-
src: /hero-image.png
|
|
13
|
-
alt: watermark-js-plus
|
|
14
|
-
actions:
|
|
15
|
-
- theme: brand
|
|
16
|
-
text: 开始 👆
|
|
17
|
-
link: /zh/guide/getting-started
|
|
18
|
-
- theme: alt
|
|
19
|
-
text: 在 GitHub 上查看
|
|
20
|
-
link: https://github.com/zhensherlock/watermark-js-plus
|
|
21
|
-
|
|
22
|
-
features:
|
|
23
|
-
- icon: 🛠️
|
|
24
|
-
title: 丰富的功能
|
|
25
|
-
details: 支持文本、多行文本、图片、富文本和暗水印。
|
|
26
|
-
- icon: 🔑
|
|
27
|
-
title: 完全类型化的API
|
|
28
|
-
details: 灵活的 API 和完整的 TypeScript 类型。
|
|
29
|
-
- icon: 📦
|
|
30
|
-
title: 极致轻量化
|
|
31
|
-
details: 大小只有 2kb 左右,你甚至可能忘记它的存在!
|
|
32
|
-
---
|
|
33
|
-
|
package/rollup.config.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import filesize from 'rollup-plugin-filesize'
|
|
2
|
-
import babel from '@rollup/plugin-babel'
|
|
3
|
-
import resolve from '@rollup/plugin-node-resolve'
|
|
4
|
-
import commonjs from '@rollup/plugin-commonjs'
|
|
5
|
-
import eslint from '@rollup/plugin-eslint'
|
|
6
|
-
// import terser from './tools/terser.js'
|
|
7
|
-
import terser from '@rollup/plugin-terser'
|
|
8
|
-
import typescript from '@rollup/plugin-typescript'
|
|
9
|
-
|
|
10
|
-
export default {
|
|
11
|
-
input: 'src/index.ts',
|
|
12
|
-
output: [
|
|
13
|
-
{
|
|
14
|
-
file: 'dist/watermark.js',
|
|
15
|
-
format: 'umd',
|
|
16
|
-
exports: 'default',
|
|
17
|
-
name: 'watermark',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
file: 'dist/watermark.min.js',
|
|
21
|
-
format: 'umd',
|
|
22
|
-
exports: 'default',
|
|
23
|
-
name: 'watermark',
|
|
24
|
-
plugins: [terser()]
|
|
25
|
-
}
|
|
26
|
-
],
|
|
27
|
-
plugins: [
|
|
28
|
-
typescript(),
|
|
29
|
-
eslint({
|
|
30
|
-
throwOnError: true,
|
|
31
|
-
throwOnWarning: true,
|
|
32
|
-
include: ['src/**'],
|
|
33
|
-
exclude: ['node_modules/**']
|
|
34
|
-
}),
|
|
35
|
-
resolve(),
|
|
36
|
-
commonjs(),
|
|
37
|
-
filesize(),
|
|
38
|
-
babel({ babelHelpers: 'runtime', exclude: ['node_modules/**'] })
|
|
39
|
-
]
|
|
40
|
-
}
|
package/src/blind.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import Watermark from './watermark'
|
|
2
|
-
import { CreateWatermarkModeEnum, DecodeBlindWatermark, DecodeBlindWatermarkModeEnum, WatermarkOptions } from './types'
|
|
3
|
-
import { convertImage, isFunction } from './utils'
|
|
4
|
-
|
|
5
|
-
export default class BlindWatermark extends Watermark {
|
|
6
|
-
constructor (props: Partial<WatermarkOptions> = {}) {
|
|
7
|
-
props.globalAlpha = 0.005
|
|
8
|
-
props.mode = CreateWatermarkModeEnum.blind
|
|
9
|
-
super(props)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
static decode (props: Partial<DecodeBlindWatermark>) {
|
|
13
|
-
const options = Object.assign({
|
|
14
|
-
url: '',
|
|
15
|
-
fillColor: '#000',
|
|
16
|
-
compositeOperation: 'color-burn',
|
|
17
|
-
mode: DecodeBlindWatermarkModeEnum.canvas
|
|
18
|
-
}, props)
|
|
19
|
-
if (!options.url) {
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
if (options.mode === DecodeBlindWatermarkModeEnum.canvas) {
|
|
23
|
-
const img = new Image()
|
|
24
|
-
img.src = options.url
|
|
25
|
-
img.onload = () => {
|
|
26
|
-
const { width, height } = img
|
|
27
|
-
const canvas = Watermark.createCanvas(width, height)
|
|
28
|
-
const ctx = canvas.getContext('2d')
|
|
29
|
-
if (ctx === null) {
|
|
30
|
-
throw new Error('get context error')
|
|
31
|
-
}
|
|
32
|
-
ctx.drawImage(img, 0, 0, width, height)
|
|
33
|
-
ctx.globalCompositeOperation = options.compositeOperation as any
|
|
34
|
-
ctx.fillStyle = options.fillColor
|
|
35
|
-
ctx.fillRect(0, 0, width, height)
|
|
36
|
-
const resultImage = convertImage(canvas)
|
|
37
|
-
if (options.onSuccess && isFunction(options.onSuccess)) {
|
|
38
|
-
options.onSuccess?.(resultImage)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/index.ts
DELETED
package/src/types/index.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export enum ContentTypeEnum {
|
|
2
|
-
text = 'text',
|
|
3
|
-
image = 'image',
|
|
4
|
-
multiLineText = 'multi-line-text',
|
|
5
|
-
richText = 'rich-text'
|
|
6
|
-
}
|
|
7
|
-
export enum TextAlignEnum {
|
|
8
|
-
center = 'center',
|
|
9
|
-
left = 'left',
|
|
10
|
-
right = 'right'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export enum TextBaselineEnum {
|
|
14
|
-
top = 'top',
|
|
15
|
-
bottom = 'bottom',
|
|
16
|
-
middle = 'middle'
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export enum CreateWatermarkModeEnum {
|
|
20
|
-
default = 'default',
|
|
21
|
-
blind = 'blind'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export enum DecodeBlindWatermarkModeEnum {
|
|
25
|
-
canvas = 'canvas',
|
|
26
|
-
html = 'html',
|
|
27
|
-
svg = 'svg'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface WatermarkDom extends HTMLDivElement {
|
|
31
|
-
__WATERMARK__?: string;
|
|
32
|
-
__WATERMARK__INSTANCE__?: any;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface WatermarkOptions {
|
|
36
|
-
width: number;
|
|
37
|
-
height: number;
|
|
38
|
-
rotate: number;
|
|
39
|
-
contentType: ContentTypeEnum;
|
|
40
|
-
content: string;
|
|
41
|
-
image?: string;
|
|
42
|
-
imageWidth: number;
|
|
43
|
-
imageHeight: number;
|
|
44
|
-
lineHeight: number;
|
|
45
|
-
zIndex: number;
|
|
46
|
-
backgroundPosition: string;
|
|
47
|
-
fontSize: number;
|
|
48
|
-
fontFamily: string;
|
|
49
|
-
textAlign: TextAlignEnum; // 对齐方式 center | left | right
|
|
50
|
-
textBaseline: TextBaselineEnum; // 底部对齐方式 top | bottom | middle
|
|
51
|
-
fontColor: string;
|
|
52
|
-
globalAlpha: number;
|
|
53
|
-
fontWeight: string;
|
|
54
|
-
mode: CreateWatermarkModeEnum; // 模式 default | blind
|
|
55
|
-
mutationObserve: boolean;
|
|
56
|
-
unique: boolean;
|
|
57
|
-
parent: Element | string;
|
|
58
|
-
onSuccess: Function;
|
|
59
|
-
onBeforeDestroy: Function;
|
|
60
|
-
onDestroyed: Function;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface DecodeBlindWatermark {
|
|
64
|
-
url: string;
|
|
65
|
-
fillColor: string;
|
|
66
|
-
compositeOperation: string;
|
|
67
|
-
mode: DecodeBlindWatermarkModeEnum;
|
|
68
|
-
onSuccess: Function;
|
|
69
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { WatermarkOptions } from '../types'
|
|
2
|
-
|
|
3
|
-
export const convertImage = (canvas: HTMLCanvasElement): string => {
|
|
4
|
-
return canvas.toDataURL('image/png', 1)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const isFunction = (value: Function): boolean => {
|
|
8
|
-
return typeof value === 'function'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const createSVGElement = (tagName: string, attrs: {[key: string]: string} = {}, namespaceURI = 'http://www.w3.org/2000/svg'): Element => {
|
|
12
|
-
const element = document.createElementNS(namespaceURI, tagName)
|
|
13
|
-
for (const attr in attrs) {
|
|
14
|
-
element.setAttribute(attr, attrs[attr])
|
|
15
|
-
}
|
|
16
|
-
return element
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const getMultiLineData = (ctx: CanvasRenderingContext2D, text: string, maxWidth: number) => {
|
|
20
|
-
const result = []
|
|
21
|
-
let str = ''
|
|
22
|
-
for (let i = 0, len = text.length; i < len; i++) {
|
|
23
|
-
str += text.charAt(i)
|
|
24
|
-
if (ctx.measureText(str).width > maxWidth) {
|
|
25
|
-
result.push(str.substring(0, str.length - 1))
|
|
26
|
-
str = ''
|
|
27
|
-
i--
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
result.push(str)
|
|
31
|
-
return result
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const createCustomContentSVG = (ctx: CanvasRenderingContext2D, options: WatermarkOptions): Element => {
|
|
35
|
-
const svgElement = createSVGElement('svg', {
|
|
36
|
-
xmlns: 'http://www.w3.org/2000/svg'
|
|
37
|
-
})
|
|
38
|
-
const foreignObjectElement = createSVGElement('foreignObject', {
|
|
39
|
-
width: options.width.toString(),
|
|
40
|
-
height: options.height.toString()
|
|
41
|
-
})
|
|
42
|
-
const bodyElement = document.createElement('div')
|
|
43
|
-
bodyElement.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
|
44
|
-
bodyElement.style.cssText = `
|
|
45
|
-
text-align: center;
|
|
46
|
-
display: flex;
|
|
47
|
-
align-items: center;
|
|
48
|
-
justify-content: center;
|
|
49
|
-
width: 100%;
|
|
50
|
-
height: 100%;
|
|
51
|
-
font: ${ctx.font};
|
|
52
|
-
color: ${options.fontColor};
|
|
53
|
-
`
|
|
54
|
-
bodyElement.innerHTML = options.content
|
|
55
|
-
foreignObjectElement.appendChild(bodyElement)
|
|
56
|
-
svgElement.appendChild(foreignObjectElement)
|
|
57
|
-
return svgElement
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const convertSVGToImage = (svg: Element): string => {
|
|
61
|
-
const richContent = svg.outerHTML.replace(/\n/g, '').replace(/\t/g, '').replace(/#/g, '%23')
|
|
62
|
-
return `data:image/svg+xml;charset=utf-8,${richContent}`
|
|
63
|
-
}
|
package/src/watermark.ts
DELETED
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import { convertImage, convertSVGToImage, createCustomContentSVG, getMultiLineData } from './utils'
|
|
2
|
-
import {
|
|
3
|
-
ContentTypeEnum,
|
|
4
|
-
CreateWatermarkModeEnum,
|
|
5
|
-
TextAlignEnum,
|
|
6
|
-
TextBaselineEnum,
|
|
7
|
-
WatermarkDom,
|
|
8
|
-
WatermarkOptions
|
|
9
|
-
} from './types'
|
|
10
|
-
|
|
11
|
-
export default class Watermark {
|
|
12
|
-
private readonly options: WatermarkOptions
|
|
13
|
-
private parentElement: Element = document.body
|
|
14
|
-
private observer?: MutationObserver
|
|
15
|
-
private parentObserve?: MutationObserver
|
|
16
|
-
private watermarkDom?: WatermarkDom
|
|
17
|
-
|
|
18
|
-
constructor (props: Partial<WatermarkOptions> = {}) {
|
|
19
|
-
this.options = Object.assign({
|
|
20
|
-
width: 300,
|
|
21
|
-
height: 300,
|
|
22
|
-
rotate: 45,
|
|
23
|
-
contentType: ContentTypeEnum.text,
|
|
24
|
-
content: 'hello watermark-js-plus',
|
|
25
|
-
imageWidth: 0,
|
|
26
|
-
imageHeight: 0,
|
|
27
|
-
lineHeight: 30,
|
|
28
|
-
zIndex: 10000,
|
|
29
|
-
backgroundPosition: '0 0, 0 0',
|
|
30
|
-
fontSize: 20,
|
|
31
|
-
fontFamily: 'sans-serif',
|
|
32
|
-
textAlign: TextAlignEnum.center, // 对齐方式 center | left | right
|
|
33
|
-
textBaseline: TextBaselineEnum.middle, // 底部对齐方式 top | bottom | middle
|
|
34
|
-
fontColor: '#000',
|
|
35
|
-
globalAlpha: 0.5,
|
|
36
|
-
fontWeight: 'normal',
|
|
37
|
-
mode: CreateWatermarkModeEnum.default, // 模式 default | blind
|
|
38
|
-
mutationObserve: true,
|
|
39
|
-
unique: true,
|
|
40
|
-
parent: 'body',
|
|
41
|
-
onSuccess: () => {},
|
|
42
|
-
onBeforeDestroy: () => {},
|
|
43
|
-
onDestroyed: () => {}
|
|
44
|
-
}, props)
|
|
45
|
-
if (this.options?.rotate) {
|
|
46
|
-
this.options.rotate = (360 - this.options.rotate % 360) * (Math.PI / 180)
|
|
47
|
-
}
|
|
48
|
-
this.changeParentElement(this.options.parent)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static createCanvas (width: number, height: number): HTMLCanvasElement {
|
|
52
|
-
const ratio = window.devicePixelRatio || 1
|
|
53
|
-
const canvas = document.createElement('canvas')
|
|
54
|
-
canvas.width = width * ratio // 实际渲染像素
|
|
55
|
-
canvas.height = height * ratio // 实际渲染像素
|
|
56
|
-
canvas.style.width = `${width}px` // 控制显示大小
|
|
57
|
-
canvas.style.height = `${height}px` // 控制显示大小
|
|
58
|
-
canvas.getContext('2d')?.setTransform(ratio, 0, 0, ratio, 0, 0)
|
|
59
|
-
return canvas
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
changeParentElement (parent: Element | string) {
|
|
63
|
-
if (typeof parent === 'string') {
|
|
64
|
-
const parentElement = document.querySelector(parent)
|
|
65
|
-
parentElement && (this.parentElement = parentElement)
|
|
66
|
-
} else {
|
|
67
|
-
this.parentElement = parent
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async create () {
|
|
72
|
-
if (!this.validateUnique()) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!this.validateContent()) {
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const canvas = await this.draw()
|
|
81
|
-
const image = convertImage(canvas)
|
|
82
|
-
this.watermarkDom = document.createElement('div')
|
|
83
|
-
const watermarkInnerDom = document.createElement('div')
|
|
84
|
-
this.watermarkDom.__WATERMARK__ = 'watermark'
|
|
85
|
-
this.watermarkDom.__WATERMARK__INSTANCE__ = this
|
|
86
|
-
this.watermarkDom.style.cssText = `
|
|
87
|
-
z-index: ${this.options.zIndex};
|
|
88
|
-
position: relative;
|
|
89
|
-
`
|
|
90
|
-
watermarkInnerDom.style.cssText = `
|
|
91
|
-
position: fixed;
|
|
92
|
-
z-index: ${this.options.zIndex};
|
|
93
|
-
pointer-events: none;
|
|
94
|
-
top: 0;
|
|
95
|
-
bottom: 0;
|
|
96
|
-
left: 0;
|
|
97
|
-
right: 0;
|
|
98
|
-
background-image: url(${image});
|
|
99
|
-
background-repeat: repeat;
|
|
100
|
-
background-size: ${this.options.width}px ${this.options.height}px;
|
|
101
|
-
background-position: ${this.options.backgroundPosition};
|
|
102
|
-
-webkit-print-color-adjust: exact;
|
|
103
|
-
`
|
|
104
|
-
this.watermarkDom.append(watermarkInnerDom)
|
|
105
|
-
this.parentElement.appendChild(this.watermarkDom)
|
|
106
|
-
|
|
107
|
-
if (this.options.mutationObserve) {
|
|
108
|
-
this.bindMutationObserve()
|
|
109
|
-
}
|
|
110
|
-
this.options.onSuccess?.()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
destroy () {
|
|
114
|
-
this.options.onBeforeDestroy?.()
|
|
115
|
-
this.observer?.disconnect()
|
|
116
|
-
this.parentObserve?.disconnect()
|
|
117
|
-
this.watermarkDom?.remove()
|
|
118
|
-
this.options.onDestroyed?.()
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private validateUnique (): boolean {
|
|
122
|
-
let result = true
|
|
123
|
-
if (this.options.unique) {
|
|
124
|
-
this.parentElement.childNodes.forEach(node => {
|
|
125
|
-
if (!result) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
if (Object.hasOwnProperty.call(node, '__WATERMARK__')) {
|
|
129
|
-
result = false
|
|
130
|
-
// throw new Error('duplicate watermark error')
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
return result
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private validateContent (): boolean {
|
|
138
|
-
switch (this.options.contentType) {
|
|
139
|
-
case ContentTypeEnum.image:
|
|
140
|
-
return Object.hasOwnProperty.call(this.options, 'image')
|
|
141
|
-
case ContentTypeEnum.multiLineText:
|
|
142
|
-
case ContentTypeEnum.richText:
|
|
143
|
-
case ContentTypeEnum.text:
|
|
144
|
-
return this.options.content.length > 0
|
|
145
|
-
}
|
|
146
|
-
return false
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private draw (): Promise<HTMLCanvasElement> {
|
|
150
|
-
const canvas = Watermark.createCanvas(this.options.width, this.options.height)
|
|
151
|
-
// document.body?.appendChild(canvas)
|
|
152
|
-
const ctx = canvas.getContext('2d')
|
|
153
|
-
if (ctx === null) {
|
|
154
|
-
throw new Error('get context error')
|
|
155
|
-
}
|
|
156
|
-
ctx.font = `${this.options.fontWeight} ${this.options.fontSize}px ${this.options.fontFamily}`
|
|
157
|
-
ctx.textAlign = this.options.textAlign
|
|
158
|
-
ctx.textBaseline = this.options.textBaseline
|
|
159
|
-
ctx.fillStyle = this.options.fontColor
|
|
160
|
-
ctx.globalAlpha = this.options.globalAlpha
|
|
161
|
-
return new Promise((resolve) => {
|
|
162
|
-
switch (this.options.contentType) {
|
|
163
|
-
case ContentTypeEnum.text:
|
|
164
|
-
this.drawText(ctx, resolve)
|
|
165
|
-
break
|
|
166
|
-
case ContentTypeEnum.image:
|
|
167
|
-
this.drawImage(ctx, resolve)
|
|
168
|
-
break
|
|
169
|
-
case ContentTypeEnum.multiLineText:
|
|
170
|
-
this.drawMultiLineText(ctx, resolve)
|
|
171
|
-
break
|
|
172
|
-
case ContentTypeEnum.richText:
|
|
173
|
-
this.drawRichText(ctx, resolve)
|
|
174
|
-
break
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private drawText (ctx: CanvasRenderingContext2D, resolve: Function) {
|
|
180
|
-
ctx.translate(this.options.width / 2, this.options.height / 2)
|
|
181
|
-
ctx.rotate(this.options.rotate)
|
|
182
|
-
ctx.fillText(this.options.content, 0, 0)
|
|
183
|
-
resolve(ctx.canvas)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private drawImage (ctx: CanvasRenderingContext2D, resolve: Function) {
|
|
187
|
-
const image = new Image()
|
|
188
|
-
image.setAttribute('crossOrigin', 'Anonymous')
|
|
189
|
-
image.src = this.options.image as string
|
|
190
|
-
image.onload = () => {
|
|
191
|
-
ctx.translate(this.options.width / 2, this.options.height / 2)
|
|
192
|
-
ctx.rotate(this.options.rotate)
|
|
193
|
-
const { width: imageWidth, height: imageHeight } = this.getImageRect(image)
|
|
194
|
-
ctx.drawImage(
|
|
195
|
-
image,
|
|
196
|
-
0 - imageWidth / 2,
|
|
197
|
-
0 - imageHeight / 2,
|
|
198
|
-
imageWidth,
|
|
199
|
-
imageHeight
|
|
200
|
-
)
|
|
201
|
-
resolve(ctx.canvas)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private drawMultiLineText (ctx: CanvasRenderingContext2D, resolve: Function) {
|
|
206
|
-
// image.width = this.options.width
|
|
207
|
-
// image.height = this.options.height
|
|
208
|
-
// const element = createCustomContentSvg(context, this.options)
|
|
209
|
-
// image.src = convertSVGToImage(element)
|
|
210
|
-
// image.onload = () => {
|
|
211
|
-
// context.translate(this.options.width / 2, this.options.height / 2)
|
|
212
|
-
// context.rotate(this.options.rotate)
|
|
213
|
-
// context.drawImage(
|
|
214
|
-
// image,
|
|
215
|
-
// -this.options.width / 2,
|
|
216
|
-
// -this.options.height / 2,
|
|
217
|
-
// context.canvas.width,
|
|
218
|
-
// context.canvas.height
|
|
219
|
-
// )
|
|
220
|
-
// resolve(canvas)
|
|
221
|
-
// }
|
|
222
|
-
const lines = getMultiLineData(ctx, this.options.content, this.options.width)
|
|
223
|
-
ctx.translate(this.options.width / 2, this.options.height / 2)
|
|
224
|
-
ctx.rotate(this.options.rotate)
|
|
225
|
-
const yOffsetValue = (lines.length - 1) * this.options.lineHeight / 2
|
|
226
|
-
lines.forEach((txt, index) => {
|
|
227
|
-
ctx.fillText(txt, 0, this.options.lineHeight * index - yOffsetValue)
|
|
228
|
-
})
|
|
229
|
-
resolve(ctx.canvas)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private drawRichText (ctx: CanvasRenderingContext2D, resolve: Function) {
|
|
233
|
-
const image = new Image()
|
|
234
|
-
image.width = this.options.width
|
|
235
|
-
image.height = this.options.height
|
|
236
|
-
const element = createCustomContentSVG(ctx, this.options)
|
|
237
|
-
image.src = convertSVGToImage(element)
|
|
238
|
-
image.onload = () => {
|
|
239
|
-
ctx.translate(this.options.width / 2, this.options.height / 2)
|
|
240
|
-
ctx.rotate(this.options.rotate)
|
|
241
|
-
ctx.drawImage(
|
|
242
|
-
image,
|
|
243
|
-
-this.options.width / 2,
|
|
244
|
-
-this.options.height / 2,
|
|
245
|
-
ctx.canvas.width,
|
|
246
|
-
ctx.canvas.height
|
|
247
|
-
)
|
|
248
|
-
resolve(ctx.canvas)
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private getImageRect (image: HTMLImageElement) {
|
|
253
|
-
const rect = { width: this.options.imageWidth, height: this.options.imageHeight }
|
|
254
|
-
switch (true) {
|
|
255
|
-
case rect.width !== 0 && rect.height === 0:
|
|
256
|
-
rect.height = rect.width * image.height / image.width
|
|
257
|
-
break
|
|
258
|
-
case rect.width === 0 && rect.height !== 0:
|
|
259
|
-
rect.width = rect.height * image.width / image.height
|
|
260
|
-
break
|
|
261
|
-
case rect.width === 0 && rect.height === 0:
|
|
262
|
-
rect.width = image.width
|
|
263
|
-
rect.height = image.height
|
|
264
|
-
break
|
|
265
|
-
}
|
|
266
|
-
return rect
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private bindMutationObserve (): void {
|
|
270
|
-
if (!this.watermarkDom) {
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
this.observer = new MutationObserver((mutationsList: MutationRecord[]) => {
|
|
274
|
-
if (mutationsList.length > 0) {
|
|
275
|
-
this.destroy()
|
|
276
|
-
this.create()
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
this.observer.observe(this.watermarkDom, {
|
|
280
|
-
attributes: true, // 子节点的变动(指新增,删除或者更改)
|
|
281
|
-
childList: true, // 属性的变动
|
|
282
|
-
subtree: true, // 布尔值,表示是否将该观察器应用于该节点的所有后代节点。
|
|
283
|
-
characterData: true // 节点内容或节点文本的变动。
|
|
284
|
-
})
|
|
285
|
-
this.parentObserve = new MutationObserver((mutationsList: MutationRecord[]) => {
|
|
286
|
-
mutationsList.forEach(item => {
|
|
287
|
-
if (item?.target === this.watermarkDom || item?.removedNodes?.[0] === this.watermarkDom) {
|
|
288
|
-
this.destroy()
|
|
289
|
-
this.create()
|
|
290
|
-
}
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
this.parentObserve.observe(this.parentElement, {
|
|
294
|
-
attributes: true, // 子节点的变动(指新增,删除或者更改)
|
|
295
|
-
childList: true, // 属性的变动
|
|
296
|
-
subtree: true, // 布尔值,表示是否将该观察器应用于该节点的所有后代节点。
|
|
297
|
-
characterData: true // 节点内容或节点文本的变动。
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
}
|
package/tools/terser.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { minify } from 'terser'
|
|
2
|
-
import { yourFunction } from 'rollup-plugin-your-function'
|
|
3
|
-
|
|
4
|
-
const terser = () => yourFunction({
|
|
5
|
-
output: true,
|
|
6
|
-
name: 'terser',
|
|
7
|
-
fn: async (source, options) => {
|
|
8
|
-
return minify(
|
|
9
|
-
source,
|
|
10
|
-
{
|
|
11
|
-
module: (/^esm?$/).test(options.outputOptions.format),
|
|
12
|
-
toplevel: options.outputOptions.format === 'cjs',
|
|
13
|
-
sourceMap: true
|
|
14
|
-
}
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
export default terser
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "esnext",
|
|
4
|
-
"target": "esnext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"noUnusedLocals": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"jsx": "preserve",
|
|
12
|
-
"lib": ["ESNext", "DOM"]
|
|
13
|
-
},
|
|
14
|
-
"exclude": ["**/node_modules/**", "**/dist/**"]
|
|
15
|
-
}
|