watermark-js-plus 0.0.2 → 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 (45) 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 -18
  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 -103
  16. package/docs/.vitepress/locales/zh-CN.ts +0 -51
  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/favicons/apple-touch-icon.png +0 -0
  27. package/docs/public/favicons/favicon-64x64.png +0 -0
  28. package/docs/public/hero-image.png +0 -0
  29. package/docs/public/logo.png +0 -0
  30. package/docs/zh/config/blind-decode.md +0 -33
  31. package/docs/zh/config/blind.md +0 -19
  32. package/docs/zh/config/index.md +0 -178
  33. package/docs/zh/guide/blink-watermark.md +0 -288
  34. package/docs/zh/guide/getting-started.md +0 -48
  35. package/docs/zh/guide/watermark.md +0 -213
  36. package/docs/zh/guide/what-is-this.md +0 -19
  37. package/docs/zh/index.md +0 -33
  38. package/rollup.config.mjs +0 -40
  39. package/src/blind.ts +0 -43
  40. package/src/index.ts +0 -7
  41. package/src/types/index.ts +0 -69
  42. package/src/utils/index.ts +0 -63
  43. package/src/watermark.ts +0 -300
  44. package/tools/terser.js +0 -19
  45. 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
- 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
- }
@@ -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
- }