index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { createVNode as _createVNode } from "vue";
  4. import { computed, defineComponent, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue';
  5. import { getStyleStr, getPixelRatio, rotateWatermark, reRendering } from './utils';
  6. import { arrayType, objectType, someType, withInstall } from '../_util/type';
  7. import { useMutationObserver } from '../_util/hooks/_vueuse/useMutationObserver';
  8. import { initDefaultProps } from '../_util/props-util';
  9. import { useToken } from '../theme/internal';
  10. /**
  11. * Base size of the canvas, 1 for parallel layout and 2 for alternate layout
  12. * Only alternate layout is currently supported
  13. */
  14. const BaseSize = 2;
  15. const FontGap = 3;
  16. export const watermarkProps = () => ({
  17. zIndex: Number,
  18. rotate: Number,
  19. width: Number,
  20. height: Number,
  21. image: String,
  22. content: someType([String, Array]),
  23. font: objectType(),
  24. rootClassName: String,
  25. gap: arrayType(),
  26. offset: arrayType()
  27. });
  28. const Watermark = defineComponent({
  29. name: 'AWatermark',
  30. inheritAttrs: false,
  31. props: initDefaultProps(watermarkProps(), {
  32. zIndex: 9,
  33. rotate: -22,
  34. font: {},
  35. gap: [100, 100]
  36. }),
  37. setup(props, _ref) {
  38. let {
  39. slots,
  40. attrs
  41. } = _ref;
  42. const [, token] = useToken();
  43. const containerRef = shallowRef();
  44. const watermarkRef = shallowRef();
  45. const stopObservation = shallowRef(false);
  46. const gapX = computed(() => {
  47. var _a, _b;
  48. return (_b = (_a = props.gap) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 100;
  49. });
  50. const gapY = computed(() => {
  51. var _a, _b;
  52. return (_b = (_a = props.gap) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 100;
  53. });
  54. const gapXCenter = computed(() => gapX.value / 2);
  55. const gapYCenter = computed(() => gapY.value / 2);
  56. const offsetLeft = computed(() => {
  57. var _a, _b;
  58. return (_b = (_a = props.offset) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : gapXCenter.value;
  59. });
  60. const offsetTop = computed(() => {
  61. var _a, _b;
  62. return (_b = (_a = props.offset) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : gapYCenter.value;
  63. });
  64. const fontSize = computed(() => {
  65. var _a, _b;
  66. return (_b = (_a = props.font) === null || _a === void 0 ? void 0 : _a.fontSize) !== null && _b !== void 0 ? _b : token.value.fontSizeLG;
  67. });
  68. const fontWeight = computed(() => {
  69. var _a, _b;
  70. return (_b = (_a = props.font) === null || _a === void 0 ? void 0 : _a.fontWeight) !== null && _b !== void 0 ? _b : 'normal';
  71. });
  72. const fontStyle = computed(() => {
  73. var _a, _b;
  74. return (_b = (_a = props.font) === null || _a === void 0 ? void 0 : _a.fontStyle) !== null && _b !== void 0 ? _b : 'normal';
  75. });
  76. const fontFamily = computed(() => {
  77. var _a, _b;
  78. return (_b = (_a = props.font) === null || _a === void 0 ? void 0 : _a.fontFamily) !== null && _b !== void 0 ? _b : 'sans-serif';
  79. });
  80. const color = computed(() => {
  81. var _a, _b;
  82. return (_b = (_a = props.font) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : token.value.colorFill;
  83. });
  84. const markStyle = computed(() => {
  85. var _a;
  86. const markStyle = {
  87. zIndex: (_a = props.zIndex) !== null && _a !== void 0 ? _a : 9,
  88. position: 'absolute',
  89. left: 0,
  90. top: 0,
  91. width: '100%',
  92. height: '100%',
  93. pointerEvents: 'none',
  94. backgroundRepeat: 'repeat'
  95. };
  96. /** Calculate the style of the offset */
  97. let positionLeft = offsetLeft.value - gapXCenter.value;
  98. let positionTop = offsetTop.value - gapYCenter.value;
  99. if (positionLeft > 0) {
  100. markStyle.left = `${positionLeft}px`;
  101. markStyle.width = `calc(100% - ${positionLeft}px)`;
  102. positionLeft = 0;
  103. }
  104. if (positionTop > 0) {
  105. markStyle.top = `${positionTop}px`;
  106. markStyle.height = `calc(100% - ${positionTop}px)`;
  107. positionTop = 0;
  108. }
  109. markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`;
  110. return markStyle;
  111. });
  112. const destroyWatermark = () => {
  113. if (watermarkRef.value) {
  114. watermarkRef.value.remove();
  115. watermarkRef.value = undefined;
  116. }
  117. };
  118. const appendWatermark = (base64Url, markWidth) => {
  119. var _a;
  120. if (containerRef.value && watermarkRef.value) {
  121. stopObservation.value = true;
  122. watermarkRef.value.setAttribute('style', getStyleStr(_extends(_extends({}, markStyle.value), {
  123. backgroundImage: `url('${base64Url}')`,
  124. backgroundSize: `${(gapX.value + markWidth) * BaseSize}px`
  125. })));
  126. (_a = containerRef.value) === null || _a === void 0 ? void 0 : _a.append(watermarkRef.value);
  127. // Delayed execution
  128. setTimeout(() => {
  129. stopObservation.value = false;
  130. });
  131. }
  132. };
  133. /**
  134. * Get the width and height of the watermark. The default values are as follows
  135. * Image: [120, 64]; Content: It's calculated by content;
  136. */
  137. const getMarkSize = ctx => {
  138. let defaultWidth = 120;
  139. let defaultHeight = 64;
  140. const content = props.content;
  141. const image = props.image;
  142. const width = props.width;
  143. const height = props.height;
  144. if (!image && ctx.measureText) {
  145. ctx.font = `${Number(fontSize.value)}px ${fontFamily.value}`;
  146. const contents = Array.isArray(content) ? content : [content];
  147. const widths = contents.map(item => ctx.measureText(item).width);
  148. defaultWidth = Math.ceil(Math.max(...widths));
  149. defaultHeight = Number(fontSize.value) * contents.length + (contents.length - 1) * FontGap;
  150. }
  151. return [width !== null && width !== void 0 ? width : defaultWidth, height !== null && height !== void 0 ? height : defaultHeight];
  152. };
  153. const fillTexts = (ctx, drawX, drawY, drawWidth, drawHeight) => {
  154. const ratio = getPixelRatio();
  155. const content = props.content;
  156. const mergedFontSize = Number(fontSize.value) * ratio;
  157. ctx.font = `${fontStyle.value} normal ${fontWeight.value} ${mergedFontSize}px/${drawHeight}px ${fontFamily.value}`;
  158. ctx.fillStyle = color.value;
  159. ctx.textAlign = 'center';
  160. ctx.textBaseline = 'top';
  161. ctx.translate(drawWidth / 2, 0);
  162. const contents = Array.isArray(content) ? content : [content];
  163. contents === null || contents === void 0 ? void 0 : contents.forEach((item, index) => {
  164. ctx.fillText(item !== null && item !== void 0 ? item : '', drawX, drawY + index * (mergedFontSize + FontGap * ratio));
  165. });
  166. };
  167. const renderWatermark = () => {
  168. var _a;
  169. const canvas = document.createElement('canvas');
  170. const ctx = canvas.getContext('2d');
  171. const image = props.image;
  172. const rotate = (_a = props.rotate) !== null && _a !== void 0 ? _a : -22;
  173. if (ctx) {
  174. if (!watermarkRef.value) {
  175. watermarkRef.value = document.createElement('div');
  176. }
  177. const ratio = getPixelRatio();
  178. const [markWidth, markHeight] = getMarkSize(ctx);
  179. const canvasWidth = (gapX.value + markWidth) * ratio;
  180. const canvasHeight = (gapY.value + markHeight) * ratio;
  181. canvas.setAttribute('width', `${canvasWidth * BaseSize}px`);
  182. canvas.setAttribute('height', `${canvasHeight * BaseSize}px`);
  183. const drawX = gapX.value * ratio / 2;
  184. const drawY = gapY.value * ratio / 2;
  185. const drawWidth = markWidth * ratio;
  186. const drawHeight = markHeight * ratio;
  187. const rotateX = (drawWidth + gapX.value * ratio) / 2;
  188. const rotateY = (drawHeight + gapY.value * ratio) / 2;
  189. /** Alternate drawing parameters */
  190. const alternateDrawX = drawX + canvasWidth;
  191. const alternateDrawY = drawY + canvasHeight;
  192. const alternateRotateX = rotateX + canvasWidth;
  193. const alternateRotateY = rotateY + canvasHeight;
  194. ctx.save();
  195. rotateWatermark(ctx, rotateX, rotateY, rotate);
  196. if (image) {
  197. const img = new Image();
  198. img.onload = () => {
  199. ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
  200. /** Draw interleaved pictures after rotation */
  201. ctx.restore();
  202. rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate);
  203. ctx.drawImage(img, alternateDrawX, alternateDrawY, drawWidth, drawHeight);
  204. appendWatermark(canvas.toDataURL(), markWidth);
  205. };
  206. img.crossOrigin = 'anonymous';
  207. img.referrerPolicy = 'no-referrer';
  208. img.src = image;
  209. } else {
  210. fillTexts(ctx, drawX, drawY, drawWidth, drawHeight);
  211. /** Fill the interleaved text after rotation */
  212. ctx.restore();
  213. rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate);
  214. fillTexts(ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight);
  215. appendWatermark(canvas.toDataURL(), markWidth);
  216. }
  217. }
  218. };
  219. onMounted(() => {
  220. renderWatermark();
  221. });
  222. watch(() => [props, token.value.colorFill, token.value.fontSizeLG], () => {
  223. renderWatermark();
  224. }, {
  225. deep: true,
  226. flush: 'post'
  227. });
  228. onBeforeUnmount(() => {
  229. destroyWatermark();
  230. });
  231. const onMutate = mutations => {
  232. if (stopObservation.value) {
  233. return;
  234. }
  235. mutations.forEach(mutation => {
  236. if (reRendering(mutation, watermarkRef.value)) {
  237. destroyWatermark();
  238. renderWatermark();
  239. }
  240. });
  241. };
  242. useMutationObserver(containerRef, onMutate, {
  243. attributes: true,
  244. subtree: true,
  245. childList: true,
  246. attributeFilter: ['style', 'class']
  247. });
  248. return () => {
  249. var _a;
  250. return _createVNode("div", _objectSpread(_objectSpread({}, attrs), {}, {
  251. "ref": containerRef,
  252. "class": [attrs.class, props.rootClassName],
  253. "style": [{
  254. position: 'relative'
  255. }, attrs.style]
  256. }), [(_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots)]);
  257. };
  258. }
  259. });
  260. export default withInstall(Watermark);