watermark-js-plus 0.0.1 → 0.0.3

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.
Files changed (42) hide show
  1. package/README.md +88 -1
  2. package/package.json +22 -10
  3. package/.editorconfig +0 -16
  4. package/.eslintignore +0 -7
  5. package/.eslintrc.cjs +0 -46
  6. package/.github/workflows/deploy.yml +0 -29
  7. package/.github/workflows/npm-publish.yml +0 -32
  8. package/.husky/commit-msg +0 -4
  9. package/.husky/pre-commit +0 -4
  10. package/.prettierrc +0 -10
  11. package/CHANGELOG.md +0 -9
  12. package/babel.config.cjs +0 -23
  13. package/changelog-option.cjs +0 -87
  14. package/commitlint.config.cjs +0 -26
  15. package/docs/.vitepress/config.ts +0 -91
  16. package/docs/.vitepress/locales/zh-CN.ts +0 -49
  17. package/docs/.vitepress/theme/index.ts +0 -12
  18. package/docs/config/blind-decode.md +0 -33
  19. package/docs/config/blind.md +0 -19
  20. package/docs/config/index.md +0 -178
  21. package/docs/guide/blind-watermark.md +0 -267
  22. package/docs/guide/getting-started.md +0 -73
  23. package/docs/guide/watermark.md +0 -213
  24. package/docs/guide/what-is-this.md +0 -19
  25. package/docs/index.md +0 -33
  26. package/docs/public/logo.png +0 -0
  27. package/docs/zh/config/blind-decode.md +0 -33
  28. package/docs/zh/config/blind.md +0 -19
  29. package/docs/zh/config/index.md +0 -178
  30. package/docs/zh/guide/blink-watermark.md +0 -288
  31. package/docs/zh/guide/getting-started.md +0 -48
  32. package/docs/zh/guide/watermark.md +0 -213
  33. package/docs/zh/guide/what-is-this.md +0 -19
  34. package/docs/zh/index.md +0 -33
  35. package/rollup.config.mjs +0 -40
  36. package/src/blind.ts +0 -43
  37. package/src/index.ts +0 -7
  38. package/src/types/index.ts +0 -69
  39. package/src/utils/index.ts +0 -63
  40. package/src/watermark.ts +0 -288
  41. package/tools/terser.js +0 -19
  42. package/tsconfig.json +0 -15
@@ -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
- parentElement: HTMLElement;
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
- }
@@ -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,288 +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
- options: WatermarkOptions
13
- observer?: MutationObserver
14
- parentObserve?: MutationObserver
15
- watermarkDom?: WatermarkDom
16
-
17
- constructor (props: Partial<WatermarkOptions> = {}) {
18
- this.options = Object.assign({
19
- width: 300,
20
- height: 300,
21
- rotate: 45,
22
- contentType: ContentTypeEnum.text,
23
- content: 'hello watermark-js-plus',
24
- imageWidth: 0,
25
- imageHeight: 0,
26
- lineHeight: 30,
27
- zIndex: 10000,
28
- backgroundPosition: '0 0, 0 0',
29
- fontSize: 20,
30
- fontFamily: 'sans-serif',
31
- textAlign: TextAlignEnum.center, // 对齐方式 center | left | right
32
- textBaseline: TextBaselineEnum.middle, // 底部对齐方式 top | bottom | middle
33
- fontColor: '#000',
34
- globalAlpha: 0.5,
35
- fontWeight: 'normal',
36
- mode: CreateWatermarkModeEnum.default, // 模式 default | blind
37
- mutationObserve: true,
38
- unique: true,
39
- parentElement: document.body,
40
- onSuccess: () => {},
41
- onBeforeDestroy: () => {},
42
- onDestroyed: () => {}
43
- }, props)
44
- if (this.options?.rotate) {
45
- this.options.rotate = (360 - this.options.rotate % 360) * (Math.PI / 180)
46
- }
47
- }
48
-
49
- static createCanvas (width: number, height: number): HTMLCanvasElement {
50
- const ratio = window.devicePixelRatio || 1
51
- const canvas = document.createElement('canvas')
52
- canvas.width = width * ratio // 实际渲染像素
53
- canvas.height = height * ratio // 实际渲染像素
54
- canvas.style.width = `${width}px` // 控制显示大小
55
- canvas.style.height = `${height}px` // 控制显示大小
56
- canvas.getContext('2d')?.setTransform(ratio, 0, 0, ratio, 0, 0)
57
- return canvas
58
- }
59
-
60
- async create () {
61
- if (!this.validateUnique()) {
62
- return
63
- }
64
-
65
- if (!this.validateContent()) {
66
- return
67
- }
68
-
69
- const canvas = await this.draw()
70
- const image = convertImage(canvas)
71
- this.watermarkDom = document.createElement('div')
72
- const watermarkInnerDom = document.createElement('div')
73
- this.watermarkDom.__WATERMARK__ = 'watermark'
74
- this.watermarkDom.__WATERMARK__INSTANCE__ = this
75
- this.watermarkDom.style.cssText = `
76
- z-index: ${this.options.zIndex};
77
- `
78
- watermarkInnerDom.style.cssText = `
79
- position: fixed;
80
- z-index: ${this.options.zIndex};
81
- pointer-events: none;
82
- top: 0;
83
- bottom: 0;
84
- left: 0;
85
- right: 0;
86
- background-image: url(${image});
87
- background-repeat: repeat;
88
- background-size: ${this.options.width}px ${this.options.height}px;
89
- background-position: ${this.options.backgroundPosition};
90
- -webkit-print-color-adjust: exact;
91
- `
92
- this.watermarkDom.append(watermarkInnerDom)
93
- this.options.parentElement.appendChild(this.watermarkDom)
94
-
95
- if (this.options.mutationObserve) {
96
- this.bindMutationObserve()
97
- }
98
- this.options.onSuccess?.()
99
- }
100
-
101
- destroy () {
102
- this.options.onBeforeDestroy?.()
103
- this.observer?.disconnect()
104
- this.parentObserve?.disconnect()
105
- this.watermarkDom?.remove()
106
- this.options.onDestroyed?.()
107
- }
108
-
109
- private validateUnique (): boolean {
110
- let result = true
111
- if (this.options.unique) {
112
- this.options.parentElement.childNodes.forEach(node => {
113
- if (!result) {
114
- return
115
- }
116
- if (Object.hasOwnProperty.call(node, '__WATERMARK__')) {
117
- result = false
118
- // throw new Error('duplicate watermark error')
119
- }
120
- })
121
- }
122
- return result
123
- }
124
-
125
- private validateContent (): boolean {
126
- switch (this.options.contentType) {
127
- case ContentTypeEnum.image:
128
- return Object.hasOwnProperty.call(this.options, 'image')
129
- case ContentTypeEnum.multiLineText:
130
- case ContentTypeEnum.richText:
131
- case ContentTypeEnum.text:
132
- return this.options.content.length > 0
133
- }
134
- return false
135
- }
136
-
137
- private draw (): Promise<HTMLCanvasElement> {
138
- const canvas = Watermark.createCanvas(this.options.width, this.options.height)
139
- // document.body?.appendChild(canvas)
140
- const ctx = canvas.getContext('2d')
141
- if (ctx === null) {
142
- throw new Error('get context error')
143
- }
144
- ctx.font = `${this.options.fontWeight} ${this.options.fontSize}px ${this.options.fontFamily}`
145
- ctx.textAlign = this.options.textAlign
146
- ctx.textBaseline = this.options.textBaseline
147
- ctx.fillStyle = this.options.fontColor
148
- ctx.globalAlpha = this.options.globalAlpha
149
- return new Promise((resolve) => {
150
- switch (this.options.contentType) {
151
- case ContentTypeEnum.text:
152
- this.drawText(ctx, resolve)
153
- break
154
- case ContentTypeEnum.image:
155
- this.drawImage(ctx, resolve)
156
- break
157
- case ContentTypeEnum.multiLineText:
158
- this.drawMultiLineText(ctx, resolve)
159
- break
160
- case ContentTypeEnum.richText:
161
- this.drawRichText(ctx, resolve)
162
- break
163
- }
164
- })
165
- }
166
-
167
- private drawText (ctx: CanvasRenderingContext2D, resolve: Function) {
168
- ctx.translate(this.options.width / 2, this.options.height / 2)
169
- ctx.rotate(this.options.rotate)
170
- ctx.fillText(this.options.content, 0, 0)
171
- resolve(ctx.canvas)
172
- }
173
-
174
- private drawImage (ctx: CanvasRenderingContext2D, resolve: Function) {
175
- const image = new Image()
176
- image.setAttribute('crossOrigin', 'Anonymous')
177
- image.src = this.options.image as string
178
- image.onload = () => {
179
- ctx.translate(this.options.width / 2, this.options.height / 2)
180
- ctx.rotate(this.options.rotate)
181
- const { width: imageWidth, height: imageHeight } = this.getImageRect(image)
182
- ctx.drawImage(
183
- image,
184
- 0 - imageWidth / 2,
185
- 0 - imageHeight / 2,
186
- imageWidth,
187
- imageHeight
188
- )
189
- resolve(ctx.canvas)
190
- }
191
- }
192
-
193
- private drawMultiLineText (ctx: CanvasRenderingContext2D, resolve: Function) {
194
- // image.width = this.options.width
195
- // image.height = this.options.height
196
- // const element = createCustomContentSvg(context, this.options)
197
- // image.src = convertSVGToImage(element)
198
- // image.onload = () => {
199
- // context.translate(this.options.width / 2, this.options.height / 2)
200
- // context.rotate(this.options.rotate)
201
- // context.drawImage(
202
- // image,
203
- // -this.options.width / 2,
204
- // -this.options.height / 2,
205
- // context.canvas.width,
206
- // context.canvas.height
207
- // )
208
- // resolve(canvas)
209
- // }
210
- const lines = getMultiLineData(ctx, this.options.content, this.options.width)
211
- ctx.translate(this.options.width / 2, this.options.height / 2)
212
- ctx.rotate(this.options.rotate)
213
- const yOffsetValue = (lines.length - 1) * this.options.lineHeight / 2
214
- lines.forEach((txt, index) => {
215
- ctx.fillText(txt, 0, this.options.lineHeight * index - yOffsetValue)
216
- })
217
- resolve(ctx.canvas)
218
- }
219
-
220
- private drawRichText (ctx: CanvasRenderingContext2D, resolve: Function) {
221
- const image = new Image()
222
- image.width = this.options.width
223
- image.height = this.options.height
224
- const element = createCustomContentSVG(ctx, this.options)
225
- image.src = convertSVGToImage(element)
226
- image.onload = () => {
227
- ctx.translate(this.options.width / 2, this.options.height / 2)
228
- ctx.rotate(this.options.rotate)
229
- ctx.drawImage(
230
- image,
231
- -this.options.width / 2,
232
- -this.options.height / 2,
233
- ctx.canvas.width,
234
- ctx.canvas.height
235
- )
236
- resolve(ctx.canvas)
237
- }
238
- }
239
-
240
- private getImageRect (image: HTMLImageElement) {
241
- const rect = { width: this.options.imageWidth, height: this.options.imageHeight }
242
- switch (true) {
243
- case rect.width !== 0 && rect.height === 0:
244
- rect.height = rect.width * image.height / image.width
245
- break
246
- case rect.width === 0 && rect.height !== 0:
247
- rect.width = rect.height * image.width / image.height
248
- break
249
- case rect.width === 0 && rect.height === 0:
250
- rect.width = image.width
251
- rect.height = image.height
252
- break
253
- }
254
- return rect
255
- }
256
-
257
- private bindMutationObserve (): void {
258
- if (!this.watermarkDom) {
259
- return
260
- }
261
- this.observer = new MutationObserver((mutationsList: MutationRecord[]) => {
262
- if (mutationsList.length > 0) {
263
- this.destroy()
264
- this.create()
265
- }
266
- })
267
- this.observer.observe(this.watermarkDom, {
268
- attributes: true, // 子节点的变动(指新增,删除或者更改)
269
- childList: true, // 属性的变动
270
- subtree: true, // 布尔值,表示是否将该观察器应用于该节点的所有后代节点。
271
- characterData: true // 节点内容或节点文本的变动。
272
- })
273
- this.parentObserve = new MutationObserver((mutationsList: MutationRecord[]) => {
274
- mutationsList.forEach(item => {
275
- if (item?.target === this.watermarkDom || item?.removedNodes?.[0] === this.watermarkDom) {
276
- this.destroy()
277
- this.create()
278
- }
279
- })
280
- })
281
- this.parentObserve.observe(this.options.parentElement, {
282
- attributes: true, // 子节点的变动(指新增,删除或者更改)
283
- childList: true, // 属性的变动
284
- subtree: true, // 布尔值,表示是否将该观察器应用于该节点的所有后代节点。
285
- characterData: true // 节点内容或节点文本的变动。
286
- })
287
- }
288
- }
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
- }