index.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. "use strict";
  2. "use client";
  3. var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
  4. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
  5. Object.defineProperty(exports, "__esModule", {
  6. value: true
  7. });
  8. exports.default = void 0;
  9. var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
  10. var _react = _interopRequireWildcard(require("react"));
  11. var _mutateObserver = require("@rc-component/mutate-observer");
  12. var _classnames = _interopRequireDefault(require("classnames"));
  13. var _useEvent = _interopRequireDefault(require("rc-util/lib/hooks/useEvent"));
  14. var _toList = _interopRequireDefault(require("../_util/toList"));
  15. var _internal = require("../theme/internal");
  16. var _context = _interopRequireDefault(require("./context"));
  17. var _useClips = _interopRequireWildcard(require("./useClips"));
  18. var _useRafDebounce = _interopRequireDefault(require("./useRafDebounce"));
  19. var _useSingletonCache = _interopRequireDefault(require("./useSingletonCache"));
  20. var _useWatermark = _interopRequireDefault(require("./useWatermark"));
  21. var _utils = require("./utils");
  22. /**
  23. * Only return `next` when size changed.
  24. * This is only used for elements compare, not a shallow equal!
  25. */
  26. function getSizeDiff(prev, next) {
  27. return prev.size === next.size ? prev : next;
  28. }
  29. const DEFAULT_GAP_X = 100;
  30. const DEFAULT_GAP_Y = 100;
  31. const fixedStyle = {
  32. position: 'relative',
  33. overflow: 'hidden'
  34. };
  35. const Watermark = props => {
  36. var _a, _b;
  37. const {
  38. /**
  39. * The antd content layer zIndex is basically below 10
  40. * https://github.com/ant-design/ant-design/blob/6192403b2ce517c017f9e58a32d58774921c10cd/components/style/themes/default.less#L335
  41. */
  42. zIndex = 9,
  43. rotate = -22,
  44. width,
  45. height,
  46. image,
  47. content,
  48. font = {},
  49. style,
  50. className,
  51. rootClassName,
  52. gap = [DEFAULT_GAP_X, DEFAULT_GAP_Y],
  53. offset,
  54. children,
  55. inherit = true
  56. } = props;
  57. const mergedStyle = Object.assign(Object.assign({}, fixedStyle), style);
  58. const [, token] = (0, _internal.useToken)();
  59. const {
  60. color = token.colorFill,
  61. fontSize = token.fontSizeLG,
  62. fontWeight = 'normal',
  63. fontStyle = 'normal',
  64. fontFamily = 'sans-serif',
  65. textAlign = 'center'
  66. } = font;
  67. const [gapX = DEFAULT_GAP_X, gapY = DEFAULT_GAP_Y] = gap;
  68. const gapXCenter = gapX / 2;
  69. const gapYCenter = gapY / 2;
  70. const offsetLeft = (_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : gapXCenter;
  71. const offsetTop = (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : gapYCenter;
  72. const markStyle = _react.default.useMemo(() => {
  73. const mergedMarkStyle = {
  74. zIndex,
  75. position: 'absolute',
  76. left: 0,
  77. top: 0,
  78. width: '100%',
  79. height: '100%',
  80. pointerEvents: 'none',
  81. backgroundRepeat: 'repeat'
  82. };
  83. /** Calculate the style of the offset */
  84. let positionLeft = offsetLeft - gapXCenter;
  85. let positionTop = offsetTop - gapYCenter;
  86. if (positionLeft > 0) {
  87. mergedMarkStyle.left = `${positionLeft}px`;
  88. mergedMarkStyle.width = `calc(100% - ${positionLeft}px)`;
  89. positionLeft = 0;
  90. }
  91. if (positionTop > 0) {
  92. mergedMarkStyle.top = `${positionTop}px`;
  93. mergedMarkStyle.height = `calc(100% - ${positionTop}px)`;
  94. positionTop = 0;
  95. }
  96. mergedMarkStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`;
  97. return mergedMarkStyle;
  98. }, [zIndex, offsetLeft, gapXCenter, offsetTop, gapYCenter]);
  99. const [container, setContainer] = _react.default.useState();
  100. // Used for nest case like Modal, Drawer
  101. const [subElements, setSubElements] = _react.default.useState(() => new Set());
  102. // Nest elements should also support watermark
  103. const targetElements = _react.default.useMemo(() => {
  104. const list = container ? [container] : [];
  105. return [].concat(list, (0, _toConsumableArray2.default)(Array.from(subElements)));
  106. }, [container, subElements]);
  107. // ============================ Content =============================
  108. /**
  109. * Get the width and height of the watermark. The default values are as follows
  110. * Image: [120, 64]; Content: It's calculated by content;
  111. */
  112. const getMarkSize = ctx => {
  113. let defaultWidth = 120;
  114. let defaultHeight = 64;
  115. if (!image && ctx.measureText) {
  116. ctx.font = `${Number(fontSize)}px ${fontFamily}`;
  117. const contents = (0, _toList.default)(content);
  118. const sizes = contents.map(item => {
  119. const metrics = ctx.measureText(item);
  120. return [metrics.width, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent];
  121. });
  122. defaultWidth = Math.ceil(Math.max.apply(Math, (0, _toConsumableArray2.default)(sizes.map(size => size[0]))));
  123. defaultHeight = Math.ceil(Math.max.apply(Math, (0, _toConsumableArray2.default)(sizes.map(size => size[1])))) * contents.length + (contents.length - 1) * _useClips.FontGap;
  124. }
  125. return [width !== null && width !== void 0 ? width : defaultWidth, height !== null && height !== void 0 ? height : defaultHeight];
  126. };
  127. const getClips = (0, _useClips.default)();
  128. const getClipsCache = (0, _useSingletonCache.default)();
  129. const [watermarkInfo, setWatermarkInfo] = _react.default.useState(null);
  130. // Generate new Watermark content
  131. const renderWatermark = () => {
  132. const canvas = document.createElement('canvas');
  133. const ctx = canvas.getContext('2d');
  134. if (ctx) {
  135. const ratio = (0, _utils.getPixelRatio)();
  136. const [markWidth, markHeight] = getMarkSize(ctx);
  137. const drawCanvas = drawContent => {
  138. const params = [drawContent || '', rotate, ratio, markWidth, markHeight, {
  139. color,
  140. fontSize,
  141. fontStyle,
  142. fontWeight,
  143. fontFamily,
  144. textAlign
  145. }, gapX, gapY];
  146. const [nextClips, clipWidth] = getClipsCache(params, () => getClips.apply(void 0, params));
  147. setWatermarkInfo([nextClips, clipWidth]);
  148. };
  149. if (image) {
  150. const img = new Image();
  151. img.onload = () => {
  152. drawCanvas(img);
  153. };
  154. img.onerror = () => {
  155. drawCanvas(content);
  156. };
  157. img.crossOrigin = 'anonymous';
  158. img.referrerPolicy = 'no-referrer';
  159. img.src = image;
  160. } else {
  161. drawCanvas(content);
  162. }
  163. }
  164. };
  165. const syncWatermark = (0, _useRafDebounce.default)(renderWatermark);
  166. // ============================= Effect =============================
  167. // Append watermark to the container
  168. const [appendWatermark, removeWatermark, isWatermarkEle] = (0, _useWatermark.default)(markStyle);
  169. (0, _react.useEffect)(() => {
  170. if (watermarkInfo) {
  171. targetElements.forEach(holder => {
  172. appendWatermark(watermarkInfo[0], watermarkInfo[1], holder);
  173. });
  174. }
  175. }, [watermarkInfo, targetElements]);
  176. // ============================ Observe =============================
  177. const onMutate = (0, _useEvent.default)(mutations => {
  178. mutations.forEach(mutation => {
  179. if ((0, _utils.reRendering)(mutation, isWatermarkEle)) {
  180. syncWatermark();
  181. } else if (mutation.target === container && mutation.attributeName === 'style') {
  182. // We've only force container not modify.
  183. // Not consider nest case.
  184. const keyStyles = Object.keys(fixedStyle);
  185. for (let i = 0; i < keyStyles.length; i += 1) {
  186. const key = keyStyles[i];
  187. const oriValue = mergedStyle[key];
  188. const currentValue = container.style[key];
  189. if (oriValue && oriValue !== currentValue) {
  190. container.style[key] = oriValue;
  191. }
  192. }
  193. }
  194. });
  195. });
  196. (0, _mutateObserver.useMutateObserver)(targetElements, onMutate);
  197. (0, _react.useEffect)(syncWatermark, [rotate, zIndex, width, height, image, content, color, fontSize, fontWeight, fontStyle, fontFamily, textAlign, gapX, gapY, offsetLeft, offsetTop]);
  198. // ============================ Context =============================
  199. const watermarkContext = _react.default.useMemo(() => ({
  200. add: ele => {
  201. setSubElements(prev => {
  202. const clone = new Set(prev);
  203. clone.add(ele);
  204. return getSizeDiff(prev, clone);
  205. });
  206. },
  207. remove: ele => {
  208. removeWatermark(ele);
  209. setSubElements(prev => {
  210. const clone = new Set(prev);
  211. clone.delete(ele);
  212. return getSizeDiff(prev, clone);
  213. });
  214. }
  215. }), []);
  216. // ============================= Render =============================
  217. const childNode = inherit ? (/*#__PURE__*/_react.default.createElement(_context.default.Provider, {
  218. value: watermarkContext
  219. }, children)) : children;
  220. return /*#__PURE__*/_react.default.createElement("div", {
  221. ref: setContainer,
  222. className: (0, _classnames.default)(className, rootClassName),
  223. style: mergedStyle
  224. }, childNode);
  225. };
  226. if (process.env.NODE_ENV !== 'production') {
  227. Watermark.displayName = 'Watermark';
  228. }
  229. var _default = exports.default = Watermark;