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.
Files changed (47) hide show
  1. package/README.md +88 -1
  2. package/dist/index.js +400 -0
  3. package/dist/index.min.js +1 -0
  4. package/package.json +22 -10
  5. package/.editorconfig +0 -16
  6. package/.eslintignore +0 -7
  7. package/.eslintrc.cjs +0 -46
  8. package/.github/workflows/deploy.yml +0 -29
  9. package/.github/workflows/npm-publish.yml +0 -32
  10. package/.husky/commit-msg +0 -4
  11. package/.husky/pre-commit +0 -4
  12. package/.prettierrc +0 -10
  13. package/CHANGELOG.md +0 -18
  14. package/babel.config.cjs +0 -23
  15. package/changelog-option.cjs +0 -87
  16. package/commitlint.config.cjs +0 -26
  17. package/docs/.vitepress/config.ts +0 -103
  18. package/docs/.vitepress/locales/zh-CN.ts +0 -51
  19. package/docs/.vitepress/theme/index.ts +0 -12
  20. package/docs/config/blind-decode.md +0 -33
  21. package/docs/config/blind.md +0 -19
  22. package/docs/config/index.md +0 -178
  23. package/docs/guide/blind-watermark.md +0 -267
  24. package/docs/guide/getting-started.md +0 -73
  25. package/docs/guide/watermark.md +0 -213
  26. package/docs/guide/what-is-this.md +0 -19
  27. package/docs/index.md +0 -33
  28. package/docs/public/favicons/apple-touch-icon.png +0 -0
  29. package/docs/public/favicons/favicon-64x64.png +0 -0
  30. package/docs/public/hero-image.png +0 -0
  31. package/docs/public/logo.png +0 -0
  32. package/docs/zh/config/blind-decode.md +0 -33
  33. package/docs/zh/config/blind.md +0 -19
  34. package/docs/zh/config/index.md +0 -178
  35. package/docs/zh/guide/blink-watermark.md +0 -288
  36. package/docs/zh/guide/getting-started.md +0 -48
  37. package/docs/zh/guide/watermark.md +0 -213
  38. package/docs/zh/guide/what-is-this.md +0 -19
  39. package/docs/zh/index.md +0 -33
  40. package/rollup.config.mjs +0 -40
  41. package/src/blind.ts +0 -43
  42. package/src/index.ts +0 -7
  43. package/src/types/index.ts +0 -69
  44. package/src/utils/index.ts +0 -63
  45. package/src/watermark.ts +0 -300
  46. package/tools/terser.js +0 -19
  47. package/tsconfig.json +0 -15
package/README.md CHANGED
@@ -1 +1,88 @@
1
- # watermark-js-plus
1
+ <p align="center">
2
+ <a href="https://zhensherlock.github.io/watermark-js-plus/" target="_blank" rel="noopener noreferrer">
3
+ <img width="300" src="https://zhensherlock.github.io/watermark-js-plus/hero-image.png" alt="watermark logo">
4
+ </a>
5
+ </p>
6
+ <p align="center">
7
+ <a href="https://npmjs.com/package/pinia"><img src="https://badgen.net/npm/v/pinia" alt="npm package"></a>
8
+ <a href="https://github.com/vuejs/pinia/actions/workflows/test.yml?query=branch%3Av2"><img src="https://github.com/vuejs/pinia/workflows/test/badge.svg?branch=v2" alt="build status"></a>
9
+ <a href="https://codecov.io/github/vuejs/pinia"><img src="https://badgen.net/codecov/c/github/vuejs/pinia/v2" alt="code coverage"></a>
10
+ </p>
11
+
12
+ # Watermark
13
+
14
+ > This is a *canvas-based* watermark for browser.
15
+
16
+ - 🛠️ Rich Features
17
+ - 🔑 Fully Typed APIs
18
+ - 📦️ Extremely light
19
+
20
+ Watermark works with both Vue 2 , Vue 3 And React.
21
+
22
+ # Translations
23
+
24
+ * [中文文档](README_zh.md)
25
+
26
+ ## Installing
27
+
28
+ ```bash
29
+ # or pnpm or yarn
30
+ npm install watermark-js-plus
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Watermark
36
+
37
+ ```ts
38
+ import { Watermark } from 'watermark-js-plus'
39
+
40
+ const watermark = new Watermark({
41
+ content: 'hello my watermark',
42
+ width: 200,
43
+ height: 200,
44
+ onSuccess: () => {
45
+ // success callback
46
+ }
47
+ })
48
+
49
+ watermark.create()
50
+ ```
51
+
52
+ ### Blind Watermark
53
+
54
+ ```ts
55
+ import { BlindWatermark } from 'watermark-js-plus'
56
+
57
+ const watermark = new BlindWatermark({
58
+ content: 'hello my watermark',
59
+ width: 200,
60
+ height: 200,
61
+ onSuccess: () => {
62
+ // success callback
63
+ }
64
+ })
65
+
66
+ watermark.create()
67
+ ```
68
+
69
+ ### Decode Blind Watermark
70
+
71
+ ```js
72
+ import { BlindWatermark } from 'watermark-js-plus'
73
+
74
+ BlindWatermark.decode({
75
+ url: uploadFile.url, // image url
76
+ onSuccess: (imageBase64) => {
77
+ // success callback
78
+ }
79
+ })
80
+ ```
81
+
82
+ ## Documentation
83
+
84
+ To learn more, check [its documentation](https://zhensherlock.github.io/watermark-js-plus).
85
+
86
+ ## License
87
+
88
+ [MIT](LICENSE).
package/dist/index.js ADDED
@@ -0,0 +1,400 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.watermark = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ const convertImage = (canvas) => {
8
+ return canvas.toDataURL('image/png', 1);
9
+ };
10
+ const isFunction = (value) => {
11
+ return typeof value === 'function';
12
+ };
13
+ const createSVGElement = (tagName, attrs = {}, namespaceURI = 'http://www.w3.org/2000/svg') => {
14
+ const element = document.createElementNS(namespaceURI, tagName);
15
+ for (const attr in attrs) {
16
+ element.setAttribute(attr, attrs[attr]);
17
+ }
18
+ return element;
19
+ };
20
+ const getMultiLineData = (ctx, text, maxWidth) => {
21
+ const result = [];
22
+ let str = '';
23
+ for (let i = 0, len = text.length; i < len; i++) {
24
+ str += text.charAt(i);
25
+ if (ctx.measureText(str).width > maxWidth) {
26
+ result.push(str.substring(0, str.length - 1));
27
+ str = '';
28
+ i--;
29
+ }
30
+ }
31
+ result.push(str);
32
+ return result;
33
+ };
34
+ const createCustomContentSVG = (ctx, options) => {
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
+ const convertSVGToImage = (svg) => {
60
+ const richContent = svg.outerHTML.replace(/\n/g, '').replace(/\t/g, '').replace(/#/g, '%23');
61
+ return `data:image/svg+xml;charset=utf-8,${richContent}`;
62
+ };
63
+
64
+ exports.ContentTypeEnum = void 0;
65
+ (function (ContentTypeEnum) {
66
+ ContentTypeEnum["text"] = "text";
67
+ ContentTypeEnum["image"] = "image";
68
+ ContentTypeEnum["multiLineText"] = "multi-line-text";
69
+ ContentTypeEnum["richText"] = "rich-text";
70
+ })(exports.ContentTypeEnum || (exports.ContentTypeEnum = {}));
71
+ exports.TextAlignEnum = void 0;
72
+ (function (TextAlignEnum) {
73
+ TextAlignEnum["center"] = "center";
74
+ TextAlignEnum["left"] = "left";
75
+ TextAlignEnum["right"] = "right";
76
+ })(exports.TextAlignEnum || (exports.TextAlignEnum = {}));
77
+ exports.TextBaselineEnum = void 0;
78
+ (function (TextBaselineEnum) {
79
+ TextBaselineEnum["top"] = "top";
80
+ TextBaselineEnum["bottom"] = "bottom";
81
+ TextBaselineEnum["middle"] = "middle";
82
+ })(exports.TextBaselineEnum || (exports.TextBaselineEnum = {}));
83
+ exports.CreateWatermarkModeEnum = void 0;
84
+ (function (CreateWatermarkModeEnum) {
85
+ CreateWatermarkModeEnum["default"] = "default";
86
+ CreateWatermarkModeEnum["blind"] = "blind";
87
+ })(exports.CreateWatermarkModeEnum || (exports.CreateWatermarkModeEnum = {}));
88
+ exports.DecodeBlindWatermarkModeEnum = void 0;
89
+ (function (DecodeBlindWatermarkModeEnum) {
90
+ DecodeBlindWatermarkModeEnum["canvas"] = "canvas";
91
+ DecodeBlindWatermarkModeEnum["html"] = "html";
92
+ DecodeBlindWatermarkModeEnum["svg"] = "svg";
93
+ })(exports.DecodeBlindWatermarkModeEnum || (exports.DecodeBlindWatermarkModeEnum = {}));
94
+
95
+ class Watermark {
96
+ options;
97
+ parentElement = document.body;
98
+ observer;
99
+ parentObserve;
100
+ watermarkDom;
101
+ constructor(props = {}) {
102
+ this.options = Object.assign({
103
+ width: 300,
104
+ height: 300,
105
+ rotate: 45,
106
+ contentType: exports.ContentTypeEnum.text,
107
+ content: 'hello watermark-js-plus',
108
+ imageWidth: 0,
109
+ imageHeight: 0,
110
+ lineHeight: 30,
111
+ zIndex: 10000,
112
+ backgroundPosition: '0 0, 0 0',
113
+ fontSize: 20,
114
+ fontFamily: 'sans-serif',
115
+ textAlign: exports.TextAlignEnum.center,
116
+ textBaseline: exports.TextBaselineEnum.middle,
117
+ fontColor: '#000',
118
+ globalAlpha: 0.5,
119
+ fontWeight: 'normal',
120
+ mode: exports.CreateWatermarkModeEnum.default,
121
+ mutationObserve: true,
122
+ unique: true,
123
+ parent: 'body',
124
+ onSuccess: () => { },
125
+ onBeforeDestroy: () => { },
126
+ onDestroyed: () => { }
127
+ }, props);
128
+ if (this.options?.rotate) {
129
+ this.options.rotate = (360 - this.options.rotate % 360) * (Math.PI / 180);
130
+ }
131
+ this.changeParentElement(this.options.parent);
132
+ }
133
+ static createCanvas(width, height) {
134
+ const ratio = window.devicePixelRatio || 1;
135
+ const canvas = document.createElement('canvas');
136
+ canvas.width = width * ratio; // 实际渲染像素
137
+ canvas.height = height * ratio; // 实际渲染像素
138
+ canvas.style.width = `${width}px`; // 控制显示大小
139
+ canvas.style.height = `${height}px`; // 控制显示大小
140
+ canvas.getContext('2d')?.setTransform(ratio, 0, 0, ratio, 0, 0);
141
+ return canvas;
142
+ }
143
+ changeParentElement(parent) {
144
+ if (typeof parent === 'string') {
145
+ const parentElement = document.querySelector(parent);
146
+ parentElement && (this.parentElement = parentElement);
147
+ }
148
+ else {
149
+ this.parentElement = parent;
150
+ }
151
+ }
152
+ async create() {
153
+ if (!this.validateUnique()) {
154
+ return;
155
+ }
156
+ if (!this.validateContent()) {
157
+ return;
158
+ }
159
+ const canvas = await this.draw();
160
+ const image = convertImage(canvas);
161
+ this.watermarkDom = document.createElement('div');
162
+ const watermarkInnerDom = document.createElement('div');
163
+ this.watermarkDom.__WATERMARK__ = 'watermark';
164
+ this.watermarkDom.__WATERMARK__INSTANCE__ = this;
165
+ this.watermarkDom.style.cssText = `
166
+ z-index: ${this.options.zIndex};
167
+ position: relative;
168
+ `;
169
+ watermarkInnerDom.style.cssText = `
170
+ position: fixed;
171
+ z-index: ${this.options.zIndex};
172
+ pointer-events: none;
173
+ top: 0;
174
+ bottom: 0;
175
+ left: 0;
176
+ right: 0;
177
+ background-image: url(${image});
178
+ background-repeat: repeat;
179
+ background-size: ${this.options.width}px ${this.options.height}px;
180
+ background-position: ${this.options.backgroundPosition};
181
+ -webkit-print-color-adjust: exact;
182
+ `;
183
+ this.watermarkDom.append(watermarkInnerDom);
184
+ this.parentElement.appendChild(this.watermarkDom);
185
+ if (this.options.mutationObserve) {
186
+ this.bindMutationObserve();
187
+ }
188
+ this.options.onSuccess?.();
189
+ }
190
+ destroy() {
191
+ this.options.onBeforeDestroy?.();
192
+ this.observer?.disconnect();
193
+ this.parentObserve?.disconnect();
194
+ this.watermarkDom?.remove();
195
+ this.options.onDestroyed?.();
196
+ }
197
+ validateUnique() {
198
+ let result = true;
199
+ if (this.options.unique) {
200
+ this.parentElement.childNodes.forEach(node => {
201
+ if (!result) {
202
+ return;
203
+ }
204
+ if (Object.hasOwnProperty.call(node, '__WATERMARK__')) {
205
+ result = false;
206
+ // throw new Error('duplicate watermark error')
207
+ }
208
+ });
209
+ }
210
+ return result;
211
+ }
212
+ validateContent() {
213
+ switch (this.options.contentType) {
214
+ case exports.ContentTypeEnum.image:
215
+ return Object.hasOwnProperty.call(this.options, 'image');
216
+ case exports.ContentTypeEnum.multiLineText:
217
+ case exports.ContentTypeEnum.richText:
218
+ case exports.ContentTypeEnum.text:
219
+ return this.options.content.length > 0;
220
+ }
221
+ return false;
222
+ }
223
+ draw() {
224
+ const canvas = Watermark.createCanvas(this.options.width, this.options.height);
225
+ // document.body?.appendChild(canvas)
226
+ const ctx = canvas.getContext('2d');
227
+ if (ctx === null) {
228
+ throw new Error('get context error');
229
+ }
230
+ ctx.font = `${this.options.fontWeight} ${this.options.fontSize}px ${this.options.fontFamily}`;
231
+ ctx.textAlign = this.options.textAlign;
232
+ ctx.textBaseline = this.options.textBaseline;
233
+ ctx.fillStyle = this.options.fontColor;
234
+ ctx.globalAlpha = this.options.globalAlpha;
235
+ return new Promise((resolve) => {
236
+ switch (this.options.contentType) {
237
+ case exports.ContentTypeEnum.text:
238
+ this.drawText(ctx, resolve);
239
+ break;
240
+ case exports.ContentTypeEnum.image:
241
+ this.drawImage(ctx, resolve);
242
+ break;
243
+ case exports.ContentTypeEnum.multiLineText:
244
+ this.drawMultiLineText(ctx, resolve);
245
+ break;
246
+ case exports.ContentTypeEnum.richText:
247
+ this.drawRichText(ctx, resolve);
248
+ break;
249
+ }
250
+ });
251
+ }
252
+ drawText(ctx, resolve) {
253
+ ctx.translate(this.options.width / 2, this.options.height / 2);
254
+ ctx.rotate(this.options.rotate);
255
+ ctx.fillText(this.options.content, 0, 0);
256
+ resolve(ctx.canvas);
257
+ }
258
+ drawImage(ctx, resolve) {
259
+ const image = new Image();
260
+ image.setAttribute('crossOrigin', 'Anonymous');
261
+ image.src = this.options.image;
262
+ image.onload = () => {
263
+ ctx.translate(this.options.width / 2, this.options.height / 2);
264
+ ctx.rotate(this.options.rotate);
265
+ const { width: imageWidth, height: imageHeight } = this.getImageRect(image);
266
+ ctx.drawImage(image, 0 - imageWidth / 2, 0 - imageHeight / 2, imageWidth, imageHeight);
267
+ resolve(ctx.canvas);
268
+ };
269
+ }
270
+ drawMultiLineText(ctx, resolve) {
271
+ // image.width = this.options.width
272
+ // image.height = this.options.height
273
+ // const element = createCustomContentSvg(context, this.options)
274
+ // image.src = convertSVGToImage(element)
275
+ // image.onload = () => {
276
+ // context.translate(this.options.width / 2, this.options.height / 2)
277
+ // context.rotate(this.options.rotate)
278
+ // context.drawImage(
279
+ // image,
280
+ // -this.options.width / 2,
281
+ // -this.options.height / 2,
282
+ // context.canvas.width,
283
+ // context.canvas.height
284
+ // )
285
+ // resolve(canvas)
286
+ // }
287
+ const lines = getMultiLineData(ctx, this.options.content, this.options.width);
288
+ ctx.translate(this.options.width / 2, this.options.height / 2);
289
+ ctx.rotate(this.options.rotate);
290
+ const yOffsetValue = (lines.length - 1) * this.options.lineHeight / 2;
291
+ lines.forEach((txt, index) => {
292
+ ctx.fillText(txt, 0, this.options.lineHeight * index - yOffsetValue);
293
+ });
294
+ resolve(ctx.canvas);
295
+ }
296
+ drawRichText(ctx, resolve) {
297
+ const image = new Image();
298
+ image.width = this.options.width;
299
+ image.height = this.options.height;
300
+ const element = createCustomContentSVG(ctx, this.options);
301
+ image.src = convertSVGToImage(element);
302
+ image.onload = () => {
303
+ ctx.translate(this.options.width / 2, this.options.height / 2);
304
+ ctx.rotate(this.options.rotate);
305
+ ctx.drawImage(image, -this.options.width / 2, -this.options.height / 2, ctx.canvas.width, ctx.canvas.height);
306
+ resolve(ctx.canvas);
307
+ };
308
+ }
309
+ getImageRect(image) {
310
+ const rect = { width: this.options.imageWidth, height: this.options.imageHeight };
311
+ switch (true) {
312
+ case rect.width !== 0 && rect.height === 0:
313
+ rect.height = rect.width * image.height / image.width;
314
+ break;
315
+ case rect.width === 0 && rect.height !== 0:
316
+ rect.width = rect.height * image.width / image.height;
317
+ break;
318
+ case rect.width === 0 && rect.height === 0:
319
+ rect.width = image.width;
320
+ rect.height = image.height;
321
+ break;
322
+ }
323
+ return rect;
324
+ }
325
+ bindMutationObserve() {
326
+ if (!this.watermarkDom) {
327
+ return;
328
+ }
329
+ this.observer = new MutationObserver((mutationsList) => {
330
+ if (mutationsList.length > 0) {
331
+ this.destroy();
332
+ this.create();
333
+ }
334
+ });
335
+ this.observer.observe(this.watermarkDom, {
336
+ attributes: true,
337
+ childList: true,
338
+ subtree: true,
339
+ characterData: true // 节点内容或节点文本的变动。
340
+ });
341
+ this.parentObserve = new MutationObserver((mutationsList) => {
342
+ mutationsList.forEach(item => {
343
+ if (item?.target === this.watermarkDom || item?.removedNodes?.[0] === this.watermarkDom) {
344
+ this.destroy();
345
+ this.create();
346
+ }
347
+ });
348
+ });
349
+ this.parentObserve.observe(this.parentElement, {
350
+ attributes: true,
351
+ childList: true,
352
+ subtree: true,
353
+ characterData: true // 节点内容或节点文本的变动。
354
+ });
355
+ }
356
+ }
357
+
358
+ class BlindWatermark extends Watermark {
359
+ constructor(props = {}) {
360
+ props.globalAlpha = 0.005;
361
+ props.mode = exports.CreateWatermarkModeEnum.blind;
362
+ super(props);
363
+ }
364
+ static decode(props) {
365
+ const options = Object.assign({
366
+ url: '',
367
+ fillColor: '#000',
368
+ compositeOperation: 'color-burn',
369
+ mode: exports.DecodeBlindWatermarkModeEnum.canvas
370
+ }, props);
371
+ if (!options.url) {
372
+ return;
373
+ }
374
+ if (options.mode === exports.DecodeBlindWatermarkModeEnum.canvas) {
375
+ const img = new Image();
376
+ img.src = options.url;
377
+ img.onload = () => {
378
+ const { width, height } = img;
379
+ const canvas = Watermark.createCanvas(width, height);
380
+ const ctx = canvas.getContext('2d');
381
+ if (ctx === null) {
382
+ throw new Error('get context error');
383
+ }
384
+ ctx.drawImage(img, 0, 0, width, height);
385
+ ctx.globalCompositeOperation = options.compositeOperation;
386
+ ctx.fillStyle = options.fillColor;
387
+ ctx.fillRect(0, 0, width, height);
388
+ const resultImage = convertImage(canvas);
389
+ if (options.onSuccess && isFunction(options.onSuccess)) {
390
+ options.onSuccess?.(resultImage);
391
+ }
392
+ };
393
+ }
394
+ }
395
+ }
396
+
397
+ exports.BlindWatermark = BlindWatermark;
398
+ exports.Watermark = Watermark;
399
+
400
+ }));
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).watermark={})}(this,(function(t){"use strict";const e=t=>t.toDataURL("image/png",1),n=(t,e={},n="http://www.w3.org/2000/svg")=>{const i=document.createElementNS(n,t);for(const t in e)i.setAttribute(t,e[t]);return i};var i,o,s,a,r;t.ContentTypeEnum=void 0,(i=t.ContentTypeEnum||(t.ContentTypeEnum={})).text="text",i.image="image",i.multiLineText="multi-line-text",i.richText="rich-text",t.TextAlignEnum=void 0,(o=t.TextAlignEnum||(t.TextAlignEnum={})).center="center",o.left="left",o.right="right",t.TextBaselineEnum=void 0,(s=t.TextBaselineEnum||(t.TextBaselineEnum={})).top="top",s.bottom="bottom",s.middle="middle",t.CreateWatermarkModeEnum=void 0,(a=t.CreateWatermarkModeEnum||(t.CreateWatermarkModeEnum={})).default="default",a.blind="blind",t.DecodeBlindWatermarkModeEnum=void 0,(r=t.DecodeBlindWatermarkModeEnum||(t.DecodeBlindWatermarkModeEnum={})).canvas="canvas",r.html="html",r.svg="svg";class h{options;parentElement=document.body;observer;parentObserve;watermarkDom;constructor(e={}){this.options=Object.assign({width:300,height:300,rotate:45,contentType:t.ContentTypeEnum.text,content:"hello watermark-js-plus",imageWidth:0,imageHeight:0,lineHeight:30,zIndex:1e4,backgroundPosition:"0 0, 0 0",fontSize:20,fontFamily:"sans-serif",textAlign:t.TextAlignEnum.center,textBaseline:t.TextBaselineEnum.middle,fontColor:"#000",globalAlpha:.5,fontWeight:"normal",mode:t.CreateWatermarkModeEnum.default,mutationObserve:!0,unique:!0,parent:"body",onSuccess:()=>{},onBeforeDestroy:()=>{},onDestroyed:()=>{}},e),this.options?.rotate&&(this.options.rotate=(360-this.options.rotate%360)*(Math.PI/180)),this.changeParentElement(this.options.parent)}static createCanvas(t,e){const n=window.devicePixelRatio||1,i=document.createElement("canvas");return i.width=t*n,i.height=e*n,i.style.width=`${t}px`,i.style.height=`${e}px`,i.getContext("2d")?.setTransform(n,0,0,n,0,0),i}changeParentElement(t){if("string"==typeof t){const e=document.querySelector(t);e&&(this.parentElement=e)}else this.parentElement=t}async create(){if(!this.validateUnique())return;if(!this.validateContent())return;const t=await this.draw(),n=e(t);this.watermarkDom=document.createElement("div");const i=document.createElement("div");this.watermarkDom.__WATERMARK__="watermark",this.watermarkDom.__WATERMARK__INSTANCE__=this,this.watermarkDom.style.cssText=`\n z-index: ${this.options.zIndex};\n position: relative;\n `,i.style.cssText=`\n position: fixed;\n z-index: ${this.options.zIndex};\n pointer-events: none;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n background-image: url(${n});\n background-repeat: repeat;\n background-size: ${this.options.width}px ${this.options.height}px;\n background-position: ${this.options.backgroundPosition};\n -webkit-print-color-adjust: exact;\n `,this.watermarkDom.append(i),this.parentElement.appendChild(this.watermarkDom),this.options.mutationObserve&&this.bindMutationObserve(),this.options.onSuccess?.()}destroy(){this.options.onBeforeDestroy?.(),this.observer?.disconnect(),this.parentObserve?.disconnect(),this.watermarkDom?.remove(),this.options.onDestroyed?.()}validateUnique(){let t=!0;return this.options.unique&&this.parentElement.childNodes.forEach((e=>{t&&Object.hasOwnProperty.call(e,"__WATERMARK__")&&(t=!1)})),t}validateContent(){switch(this.options.contentType){case t.ContentTypeEnum.image:return Object.hasOwnProperty.call(this.options,"image");case t.ContentTypeEnum.multiLineText:case t.ContentTypeEnum.richText:case t.ContentTypeEnum.text:return this.options.content.length>0}return!1}draw(){const e=h.createCanvas(this.options.width,this.options.height).getContext("2d");if(null===e)throw new Error("get context error");return e.font=`${this.options.fontWeight} ${this.options.fontSize}px ${this.options.fontFamily}`,e.textAlign=this.options.textAlign,e.textBaseline=this.options.textBaseline,e.fillStyle=this.options.fontColor,e.globalAlpha=this.options.globalAlpha,new Promise((n=>{switch(this.options.contentType){case t.ContentTypeEnum.text:this.drawText(e,n);break;case t.ContentTypeEnum.image:this.drawImage(e,n);break;case t.ContentTypeEnum.multiLineText:this.drawMultiLineText(e,n);break;case t.ContentTypeEnum.richText:this.drawRichText(e,n)}}))}drawText(t,e){t.translate(this.options.width/2,this.options.height/2),t.rotate(this.options.rotate),t.fillText(this.options.content,0,0),e(t.canvas)}drawImage(t,e){const n=new Image;n.setAttribute("crossOrigin","Anonymous"),n.src=this.options.image,n.onload=()=>{t.translate(this.options.width/2,this.options.height/2),t.rotate(this.options.rotate);const{width:i,height:o}=this.getImageRect(n);t.drawImage(n,0-i/2,0-o/2,i,o),e(t.canvas)}}drawMultiLineText(t,e){const n=((t,e,n)=>{const i=[];let o="";for(let s=0,a=e.length;s<a;s++)o+=e.charAt(s),t.measureText(o).width>n&&(i.push(o.substring(0,o.length-1)),o="",s--);return i.push(o),i})(t,this.options.content,this.options.width);t.translate(this.options.width/2,this.options.height/2),t.rotate(this.options.rotate);const i=(n.length-1)*this.options.lineHeight/2;n.forEach(((e,n)=>{t.fillText(e,0,this.options.lineHeight*n-i)})),e(t.canvas)}drawRichText(t,e){const i=new Image;i.width=this.options.width,i.height=this.options.height;const o=((t,e)=>{const i=n("svg",{xmlns:"http://www.w3.org/2000/svg"}),o=n("foreignObject",{width:e.width.toString(),height:e.height.toString()}),s=document.createElement("div");return s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.cssText=`\n text-align: center;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n font: ${t.font};\n color: ${e.fontColor};\n `,s.innerHTML=e.content,o.appendChild(s),i.appendChild(o),i})(t,this.options);i.src=`data:image/svg+xml;charset=utf-8,${o.outerHTML.replace(/\n/g,"").replace(/\t/g,"").replace(/#/g,"%23")}`,i.onload=()=>{t.translate(this.options.width/2,this.options.height/2),t.rotate(this.options.rotate),t.drawImage(i,-this.options.width/2,-this.options.height/2,t.canvas.width,t.canvas.height),e(t.canvas)}}getImageRect(t){const e={width:this.options.imageWidth,height:this.options.imageHeight};switch(!0){case 0!==e.width&&0===e.height:e.height=e.width*t.height/t.width;break;case 0===e.width&&0!==e.height:e.width=e.height*t.width/t.height;break;case 0===e.width&&0===e.height:e.width=t.width,e.height=t.height}return e}bindMutationObserve(){this.watermarkDom&&(this.observer=new MutationObserver((t=>{t.length>0&&(this.destroy(),this.create())})),this.observer.observe(this.watermarkDom,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),this.parentObserve=new MutationObserver((t=>{t.forEach((t=>{t?.target!==this.watermarkDom&&t?.removedNodes?.[0]!==this.watermarkDom||(this.destroy(),this.create())}))})),this.parentObserve.observe(this.parentElement,{attributes:!0,childList:!0,subtree:!0,characterData:!0}))}}t.BlindWatermark=class extends h{constructor(e={}){e.globalAlpha=.005,e.mode=t.CreateWatermarkModeEnum.blind,super(e)}static decode(n){const i=Object.assign({url:"",fillColor:"#000",compositeOperation:"color-burn",mode:t.DecodeBlindWatermarkModeEnum.canvas},n);if(i.url&&i.mode===t.DecodeBlindWatermarkModeEnum.canvas){const t=new Image;t.src=i.url,t.onload=()=>{const{width:n,height:o}=t,s=h.createCanvas(n,o),a=s.getContext("2d");if(null===a)throw new Error("get context error");a.drawImage(t,0,0,n,o),a.globalCompositeOperation=i.compositeOperation,a.fillStyle=i.fillColor,a.fillRect(0,0,n,o);const r=e(s);i.onSuccess&&"function"==typeof i.onSuccess&&i.onSuccess?.(r)}}}},t.Watermark=h}));
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "watermark-js-plus",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "watermark for the browser",
5
- "main": "lib/watermark.js",
6
5
  "type": "module",
7
6
  "scripts": {
8
7
  "test": "echo \"Error: no test specified\" && exit 1",
9
8
  "prepare": "husky install",
10
9
  "lint": "npx eslint \"src/*.{ts,js}\"",
11
10
  "dev": "concurrently \"npm run src:dev\" \"npm run docs:dev\"",
12
- "src:dev": "rollup -c --watch",
13
- "build": "rollup -c --environment NODE_ENV:production && rollup -c",
11
+ "src:dev": "rollup -c --watch --bundleConfigAsCjs",
12
+ "build": "rollup -c --bundleConfigAsCjs --environment NODE_ENV:production",
14
13
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 -n changelog-option.cjs",
15
14
  "docs:dev": "vitepress dev docs",
16
15
  "docs:build": "vitepress build docs",
@@ -37,6 +36,19 @@
37
36
  "防删除水印",
38
37
  "解密水印"
39
38
  ],
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.js",
42
+ "require": "./dist/index.js",
43
+ "import": "./dist/index.js"
44
+ }
45
+ },
46
+ "main": "dist/index.js",
47
+ "module": "dist/index.js",
48
+ "types": "dist/index.js",
49
+ "files": [
50
+ "dist"
51
+ ],
40
52
  "devDependencies": {
41
53
  "@babel/core": "^7.20.5",
42
54
  "@babel/eslint-parser": "^7.19.1",
@@ -46,14 +58,14 @@
46
58
  "@commitlint/config-conventional": "^17.3.0",
47
59
  "@element-plus/icons-vue": "^2.0.10",
48
60
  "@rollup/plugin-babel": "^6.0.3",
49
- "@rollup/plugin-commonjs": "^23.0.3",
61
+ "@rollup/plugin-commonjs": "^23.0.4",
50
62
  "@rollup/plugin-eslint": "^9.0.1",
51
63
  "@rollup/plugin-node-resolve": "^15.0.1",
52
64
  "@rollup/plugin-replace": "^5.0.1",
53
- "@rollup/plugin-terser": "^0.1.0",
65
+ "@rollup/plugin-terser": "^0.2.0",
54
66
  "@rollup/plugin-typescript": "^10.0.1",
55
67
  "@types/markdown-it": "^12.2.3",
56
- "@typescript-eslint/parser": "^5.45.0",
68
+ "@typescript-eslint/parser": "^5.45.1",
57
69
  "@vue/theme": "^1.3.0",
58
70
  "concurrently": "^7.6.0",
59
71
  "conventional-changelog-angular": "^5.0.13",
@@ -67,12 +79,12 @@
67
79
  "husky": "^8.0.2",
68
80
  "lint-staged": "^13.1.0",
69
81
  "markdown-it": "^13.0.1",
70
- "rollup": "^3.5.1",
82
+ "rollup": "^3.7.0",
71
83
  "rollup-plugin-filesize": "^9.1.2",
72
84
  "rollup-plugin-your-function": "^0.4.3",
73
85
  "terser": "^5.16.1",
74
- "typescript": "^4.9.3",
86
+ "typescript": "^4.9.4",
75
87
  "unplugin-element-plus": "^0.4.1",
76
- "vitepress": "^1.0.0-alpha.29"
88
+ "vitepress": "^1.0.0-alpha.30"
77
89
  }
78
90
  }
package/.editorconfig DELETED
@@ -1,16 +0,0 @@
1
- # http://editorconfig.org
2
- root = true
3
-
4
- [*]
5
- indent_style = space
6
- indent_size = 2
7
- end_of_line = lf
8
- charset = utf-8
9
- trim_trailing_whitespace = true
10
- insert_final_newline = true
11
-
12
- [*.md]
13
- trim_trailing_whitespace = false
14
-
15
- [Makefile]
16
- indent_style = tab
package/.eslintignore DELETED
@@ -1,7 +0,0 @@
1
- */build/*.js
2
- */public
3
- dist
4
- .husky/
5
- lib
6
- */node_modules
7
- package-lock.json
package/.eslintrc.cjs DELETED
@@ -1,46 +0,0 @@
1
- module.exports = {
2
- parser: '@typescript-eslint/parser', // 解析器,默认使用Espree
3
- env: { // 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
4
- browser: true, // 运行在浏览器
5
- es2021: true, // 支持es2021
6
- es6: true,
7
- },
8
- extends: [ // 使用的外部代码格式化配置文件
9
- 'semistandard',
10
- 'plugin:import/recommended'
11
- ],
12
- plugins: [
13
- 'import'
14
- ],
15
- parserOptions: {
16
- ecmaVersion: 12, // ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持es5
17
- sourceType: 'module',
18
- },
19
- globals: { // 脚本在执行期间访问的额外的全局变量
20
- describe: true,
21
- it: true,
22
- after: true,
23
- before: true,
24
- afterEach: true,
25
- beforeEach: true,
26
- __BUILD__: true,
27
- __DEV__: true,
28
- __SIT__: true,
29
- __UAT__: true,
30
- __PROD__: true
31
- },
32
- rules: {
33
- // 启用的规则及其各自的错误级别。0(off) 1(warning) 2(error)
34
- 'no-console': 0,
35
- semi: [1, 'never'],
36
- quotes: [1, 'single'],
37
- 'no-unused-vars': 0,
38
- },
39
- settings: {
40
- 'import/resolver': {
41
- node: {
42
- extensions: ['.js', '.jsx', '.ts', '.tsx']
43
- }
44
- }
45
- },
46
- };
@@ -1,29 +0,0 @@
1
- name: deploy
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- deploy:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v3
13
- with:
14
- fetch-depth: 0
15
- - uses: actions/setup-node@v3
16
- with:
17
- node-version: 16
18
- cache: npm
19
- - run: npm ci
20
-
21
- - name: Build
22
- run: yarn docs:build
23
-
24
- - name: Deploy
25
- uses: peaceiris/actions-gh-pages@v3
26
- with:
27
- github_token: ${{ secrets.GITHUB_TOKEN }}
28
- publish_dir: docs/.vitepress/dist
29
- # cname: example.com # if wanna deploy to custom domain