ng-zorro-antd-water-mark.mjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. import { DOCUMENT } from '@angular/common';
  2. import * as i0 from '@angular/core';
  3. import { inject, numberAttribute, Input, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
  4. /**
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  7. */
  8. /**
  9. * Use of this source code is governed by an MIT-style license that can be
  10. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  11. */
  12. /** Returns the ratio of the device's physical pixel resolution to the css pixel resolution */
  13. function getPixelRatio() {
  14. return window.devicePixelRatio || 1;
  15. }
  16. function toLowercaseSeparator(key) {
  17. return key.replace(/([A-Z])/g, '-$1').toLowerCase();
  18. }
  19. function getStyleStr(style) {
  20. const keys = Object.keys(style);
  21. const styleCss = keys.map((key) => `${toLowercaseSeparator(key)}: ${style[key]};`);
  22. return styleCss.join(' ');
  23. }
  24. /** Whether to re-render the watermark */
  25. function reRendering(mutation, watermarkElement) {
  26. let flag = false;
  27. // Whether to delete the watermark node
  28. if (mutation.removedNodes.length) {
  29. flag = Array.from(mutation.removedNodes).some(node => node === watermarkElement);
  30. }
  31. // Whether the watermark dom property value has been modified
  32. if (mutation.type === 'attributes' && mutation.target === watermarkElement) {
  33. flag = true;
  34. }
  35. return flag;
  36. }
  37. /** Rotate with the watermark as the center point */
  38. function rotateWatermark(ctx, rotateX, rotateY, rotate) {
  39. ctx.translate(rotateX, rotateY);
  40. ctx.rotate((Math.PI / 180) * Number(rotate));
  41. ctx.translate(-rotateX, -rotateY);
  42. }
  43. /**
  44. * Use of this source code is governed by an MIT-style license that can be
  45. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  46. */
  47. /**
  48. * Base size of the canvas, 1 for parallel layout and 2 for alternate layout
  49. * Only alternate layout is currently supported
  50. */
  51. const BaseSize = 2;
  52. const FontGap = 3;
  53. class NzWaterMarkComponent {
  54. el;
  55. cdr;
  56. nzWidth = 120;
  57. nzHeight = 64;
  58. nzRotate = -22;
  59. nzZIndex = 9;
  60. nzImage = '';
  61. nzContent = '';
  62. nzFont = {};
  63. nzGap = [100, 100];
  64. nzOffset = [this.nzGap[0] / 2, this.nzGap[1] / 2];
  65. document = inject(DOCUMENT);
  66. waterMarkElement = this.document.createElement('div');
  67. stopObservation = false;
  68. observer = new MutationObserver(mutations => {
  69. if (this.stopObservation) {
  70. return;
  71. }
  72. mutations.forEach(mutation => {
  73. if (reRendering(mutation, this.waterMarkElement)) {
  74. this.destroyWatermark();
  75. this.renderWatermark();
  76. }
  77. });
  78. });
  79. constructor(el, cdr) {
  80. this.el = el;
  81. this.cdr = cdr;
  82. }
  83. ngOnInit() {
  84. this.observer.observe(this.el.nativeElement, {
  85. subtree: true,
  86. childList: true,
  87. attributeFilter: ['style', 'class']
  88. });
  89. }
  90. ngAfterViewInit() {
  91. this.renderWatermark();
  92. }
  93. ngOnChanges(changes) {
  94. const { nzRotate, nzZIndex, nzWidth, nzHeight, nzImage, nzContent, nzFont, gapX, gapY, offsetLeft, offsetTop } = changes;
  95. if (nzRotate ||
  96. nzZIndex ||
  97. nzWidth ||
  98. nzHeight ||
  99. nzImage ||
  100. nzContent ||
  101. nzFont ||
  102. gapX ||
  103. gapY ||
  104. offsetLeft ||
  105. offsetTop) {
  106. this.renderWatermark();
  107. }
  108. }
  109. getFont() {
  110. const font = {
  111. color: 'rgba(0,0,0,.15)',
  112. fontSize: 16,
  113. fontWeight: 'normal',
  114. fontFamily: 'sans-serif',
  115. fontStyle: 'normal'
  116. };
  117. this.nzFont = { ...font, ...this.nzFont };
  118. this.cdr.markForCheck();
  119. }
  120. getMarkStyle() {
  121. const markStyle = {
  122. zIndex: this.nzZIndex,
  123. position: 'absolute',
  124. left: 0,
  125. top: 0,
  126. width: '100%',
  127. height: '100%',
  128. pointerEvents: 'none',
  129. backgroundRepeat: 'repeat',
  130. visibility: 'visible'
  131. };
  132. /** Calculate the style of the nzOffset */
  133. let positionLeft = (this.nzOffset?.[0] ?? this.nzGap[0] / 2) - this.nzGap[0] / 2;
  134. let positionTop = (this.nzOffset?.[1] ?? this.nzGap[1] / 2) - this.nzGap[1] / 2;
  135. if (positionLeft > 0) {
  136. markStyle.left = `${positionLeft}px`;
  137. markStyle.width = `calc(100% - ${positionLeft}px)`;
  138. positionLeft = 0;
  139. }
  140. if (positionTop > 0) {
  141. markStyle.top = `${positionTop}px`;
  142. markStyle.height = `calc(100% - ${positionTop}px)`;
  143. positionTop = 0;
  144. }
  145. markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`;
  146. return markStyle;
  147. }
  148. destroyWatermark() {
  149. if (this.waterMarkElement) {
  150. this.waterMarkElement.remove();
  151. }
  152. }
  153. appendWatermark(base64Url, markWidth) {
  154. this.stopObservation = true;
  155. this.waterMarkElement.setAttribute('style', getStyleStr({
  156. ...this.getMarkStyle(),
  157. backgroundImage: `url('${base64Url}')`,
  158. backgroundSize: `${(this.nzGap[0] + markWidth) * BaseSize}px`
  159. }));
  160. this.el.nativeElement.append(this.waterMarkElement);
  161. this.cdr.markForCheck();
  162. // Delayed execution
  163. setTimeout(() => {
  164. this.stopObservation = false;
  165. this.cdr.markForCheck();
  166. });
  167. }
  168. getMarkSize(ctx) {
  169. let defaultWidth = 120;
  170. let defaultHeight = 64;
  171. if (!this.nzImage && ctx.measureText) {
  172. ctx.font = `${Number(this.nzFont.fontSize)}px ${this.nzFont.fontFamily}`;
  173. const contents = Array.isArray(this.nzContent) ? this.nzContent : [this.nzContent];
  174. const widths = contents.map(item => ctx.measureText(item).width);
  175. defaultWidth = Math.ceil(Math.max(...widths));
  176. defaultHeight = Number(this.nzFont.fontSize) * contents.length + (contents.length - 1) * FontGap;
  177. }
  178. return [this.nzWidth ?? defaultWidth, this.nzHeight ?? defaultHeight];
  179. }
  180. fillTexts(ctx, drawX, drawY, drawWidth, drawHeight) {
  181. const ratio = getPixelRatio();
  182. const mergedFontSize = Number(this.nzFont.fontSize) * ratio;
  183. ctx.font = `${this.nzFont.fontStyle} normal ${this.nzFont.fontWeight} ${mergedFontSize}px/${drawHeight}px ${this.nzFont.fontFamily}`;
  184. if (this.nzFont.color)
  185. ctx.fillStyle = this.nzFont.color;
  186. ctx.textAlign = 'center';
  187. ctx.textBaseline = 'top';
  188. ctx.translate(drawWidth / 2, 0);
  189. const contents = Array.isArray(this.nzContent) ? this.nzContent : [this.nzContent];
  190. contents?.forEach((item, index) => {
  191. ctx.fillText(item ?? '', drawX, drawY + index * (mergedFontSize + FontGap * ratio));
  192. });
  193. }
  194. drawText(canvas, ctx, drawX, drawY, drawWidth, drawHeight, alternateRotateX, alternateRotateY, alternateDrawX, alternateDrawY, markWidth) {
  195. this.fillTexts(ctx, drawX, drawY, drawWidth, drawHeight);
  196. /** Fill the interleaved text after rotation */
  197. ctx.restore();
  198. rotateWatermark(ctx, alternateRotateX, alternateRotateY, this.nzRotate);
  199. this.fillTexts(ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight);
  200. this.appendWatermark(canvas.toDataURL(), markWidth);
  201. }
  202. renderWatermark() {
  203. if (!this.nzContent && !this.nzImage) {
  204. return;
  205. }
  206. const canvas = this.document.createElement('canvas');
  207. const ctx = canvas.getContext('2d');
  208. if (ctx) {
  209. if (!this.waterMarkElement) {
  210. this.waterMarkElement = this.document.createElement('div');
  211. }
  212. this.getFont();
  213. const ratio = getPixelRatio();
  214. const [markWidth, markHeight] = this.getMarkSize(ctx);
  215. const canvasWidth = (this.nzGap[0] + markWidth) * ratio;
  216. const canvasHeight = (this.nzGap[1] + markHeight) * ratio;
  217. canvas.setAttribute('width', `${canvasWidth * BaseSize}px`);
  218. canvas.setAttribute('height', `${canvasHeight * BaseSize}px`);
  219. const drawX = (this.nzGap[0] * ratio) / 2;
  220. const drawY = (this.nzGap[1] * ratio) / 2;
  221. const drawWidth = markWidth * ratio;
  222. const drawHeight = markHeight * ratio;
  223. const rotateX = (drawWidth + this.nzGap[0] * ratio) / 2;
  224. const rotateY = (drawHeight + this.nzGap[1] * ratio) / 2;
  225. /** Alternate drawing parameters */
  226. const alternateDrawX = drawX + canvasWidth;
  227. const alternateDrawY = drawY + canvasHeight;
  228. const alternateRotateX = rotateX + canvasWidth;
  229. const alternateRotateY = rotateY + canvasHeight;
  230. ctx.save();
  231. rotateWatermark(ctx, rotateX, rotateY, this.nzRotate);
  232. if (this.nzImage) {
  233. const img = new Image();
  234. const onLoad = () => {
  235. cleanup();
  236. ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
  237. /** Draw interleaved pictures after rotation */
  238. ctx.restore();
  239. rotateWatermark(ctx, alternateRotateX, alternateRotateY, this.nzRotate);
  240. ctx.drawImage(img, alternateDrawX, alternateDrawY, drawWidth, drawHeight);
  241. this.appendWatermark(canvas.toDataURL(), markWidth);
  242. };
  243. const onError = () => {
  244. cleanup();
  245. this.drawText(canvas, ctx, drawX, drawY, drawWidth, drawHeight, alternateRotateX, alternateRotateY, alternateDrawX, alternateDrawY, markWidth);
  246. };
  247. const cleanup = () => {
  248. img.removeEventListener('load', onLoad);
  249. img.removeEventListener('error', onError);
  250. };
  251. img.addEventListener('load', onLoad);
  252. img.addEventListener('error', onError);
  253. img.crossOrigin = 'anonymous';
  254. img.referrerPolicy = 'no-referrer';
  255. img.src = this.nzImage;
  256. }
  257. else {
  258. this.drawText(canvas, ctx, drawX, drawY, drawWidth, drawHeight, alternateRotateX, alternateRotateY, alternateDrawX, alternateDrawY, markWidth);
  259. }
  260. }
  261. }
  262. ngOnDestroy() {
  263. this.observer.disconnect();
  264. }
  265. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
  266. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.2.2", type: NzWaterMarkComponent, isStandalone: true, selector: "nz-water-mark", inputs: { nzWidth: ["nzWidth", "nzWidth", numberAttribute], nzHeight: ["nzHeight", "nzHeight", numberAttribute], nzRotate: ["nzRotate", "nzRotate", numberAttribute], nzZIndex: ["nzZIndex", "nzZIndex", numberAttribute], nzImage: "nzImage", nzContent: "nzContent", nzFont: "nzFont", nzGap: "nzGap", nzOffset: "nzOffset" }, host: { classAttribute: "ant-water-mark" }, exportAs: ["NzWaterMark"], usesOnChanges: true, ngImport: i0, template: ` <ng-content></ng-content> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
  267. }
  268. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkComponent, decorators: [{
  269. type: Component,
  270. args: [{
  271. selector: 'nz-water-mark',
  272. exportAs: 'NzWaterMark',
  273. changeDetection: ChangeDetectionStrategy.OnPush,
  274. template: ` <ng-content></ng-content> `,
  275. host: {
  276. class: 'ant-water-mark'
  277. }
  278. }]
  279. }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { nzWidth: [{
  280. type: Input,
  281. args: [{ transform: numberAttribute }]
  282. }], nzHeight: [{
  283. type: Input,
  284. args: [{ transform: numberAttribute }]
  285. }], nzRotate: [{
  286. type: Input,
  287. args: [{ transform: numberAttribute }]
  288. }], nzZIndex: [{
  289. type: Input,
  290. args: [{ transform: numberAttribute }]
  291. }], nzImage: [{
  292. type: Input
  293. }], nzContent: [{
  294. type: Input
  295. }], nzFont: [{
  296. type: Input
  297. }], nzGap: [{
  298. type: Input
  299. }], nzOffset: [{
  300. type: Input
  301. }] } });
  302. /**
  303. * Use of this source code is governed by an MIT-style license that can be
  304. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  305. */
  306. class NzWaterMarkModule {
  307. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  308. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkModule, imports: [NzWaterMarkComponent], exports: [NzWaterMarkComponent] });
  309. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkModule });
  310. }
  311. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: NzWaterMarkModule, decorators: [{
  312. type: NgModule,
  313. args: [{
  314. exports: [NzWaterMarkComponent],
  315. imports: [NzWaterMarkComponent]
  316. }]
  317. }] });
  318. /**
  319. * Use of this source code is governed by an MIT-style license that can be
  320. * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
  321. */
  322. /**
  323. * Generated bundle index. Do not edit.
  324. */
  325. export { NzWaterMarkComponent, NzWaterMarkModule };
  326. //# sourceMappingURL=ng-zorro-antd-water-mark.mjs.map