204 lines
7.6 KiB
JavaScript
204 lines
7.6 KiB
JavaScript
|
"use strict";
|
||
|
"use client";
|
||
|
|
||
|
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
||
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.default = void 0;
|
||
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
||
|
var _react = _interopRequireWildcard(require("react"));
|
||
|
var _mutateObserver = require("@rc-component/mutate-observer");
|
||
|
var _classnames = _interopRequireDefault(require("classnames"));
|
||
|
var _internal = require("../theme/internal");
|
||
|
var _context = _interopRequireDefault(require("./context"));
|
||
|
var _useClips = _interopRequireWildcard(require("./useClips"));
|
||
|
var _useRafDebounce = _interopRequireDefault(require("./useRafDebounce"));
|
||
|
var _useWatermark = _interopRequireDefault(require("./useWatermark"));
|
||
|
var _utils = require("./utils");
|
||
|
/**
|
||
|
* Only return `next` when size changed.
|
||
|
* This is only used for elements compare, not a shallow equal!
|
||
|
*/
|
||
|
function getSizeDiff(prev, next) {
|
||
|
return prev.size === next.size ? prev : next;
|
||
|
}
|
||
|
const Watermark = props => {
|
||
|
var _a, _b;
|
||
|
const {
|
||
|
/**
|
||
|
* The antd content layer zIndex is basically below 10
|
||
|
* https://github.com/ant-design/ant-design/blob/6192403b2ce517c017f9e58a32d58774921c10cd/components/style/themes/default.less#L335
|
||
|
*/
|
||
|
zIndex = 9,
|
||
|
rotate = -22,
|
||
|
width,
|
||
|
height,
|
||
|
image,
|
||
|
content,
|
||
|
font = {},
|
||
|
style,
|
||
|
className,
|
||
|
rootClassName,
|
||
|
gap = [100, 100],
|
||
|
offset,
|
||
|
children
|
||
|
} = props;
|
||
|
const [, token] = (0, _internal.useToken)();
|
||
|
const {
|
||
|
color = token.colorFill,
|
||
|
fontSize = token.fontSizeLG,
|
||
|
fontWeight = 'normal',
|
||
|
fontStyle = 'normal',
|
||
|
fontFamily = 'sans-serif'
|
||
|
} = font;
|
||
|
const [gapX, gapY] = gap;
|
||
|
const gapXCenter = gapX / 2;
|
||
|
const gapYCenter = gapY / 2;
|
||
|
const offsetLeft = (_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : gapXCenter;
|
||
|
const offsetTop = (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : gapYCenter;
|
||
|
const markStyle = _react.default.useMemo(() => {
|
||
|
const mergedStyle = {
|
||
|
zIndex,
|
||
|
position: 'absolute',
|
||
|
left: 0,
|
||
|
top: 0,
|
||
|
width: '100%',
|
||
|
height: '100%',
|
||
|
pointerEvents: 'none',
|
||
|
backgroundRepeat: 'repeat'
|
||
|
};
|
||
|
/** Calculate the style of the offset */
|
||
|
let positionLeft = offsetLeft - gapXCenter;
|
||
|
let positionTop = offsetTop - gapYCenter;
|
||
|
if (positionLeft > 0) {
|
||
|
mergedStyle.left = `${positionLeft}px`;
|
||
|
mergedStyle.width = `calc(100% - ${positionLeft}px)`;
|
||
|
positionLeft = 0;
|
||
|
}
|
||
|
if (positionTop > 0) {
|
||
|
mergedStyle.top = `${positionTop}px`;
|
||
|
mergedStyle.height = `calc(100% - ${positionTop}px)`;
|
||
|
positionTop = 0;
|
||
|
}
|
||
|
mergedStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`;
|
||
|
return mergedStyle;
|
||
|
}, [zIndex, offsetLeft, gapXCenter, offsetTop, gapYCenter]);
|
||
|
const [container, setContainer] = _react.default.useState();
|
||
|
// Used for nest case like Modal, Drawer
|
||
|
const [subElements, setSubElements] = _react.default.useState(new Set());
|
||
|
// Nest elements should also support watermark
|
||
|
const targetElements = _react.default.useMemo(() => {
|
||
|
const list = container ? [container] : [];
|
||
|
return [].concat(list, (0, _toConsumableArray2.default)(Array.from(subElements)));
|
||
|
}, [container, subElements]);
|
||
|
// ============================ Content =============================
|
||
|
/**
|
||
|
* Get the width and height of the watermark. The default values are as follows
|
||
|
* Image: [120, 64]; Content: It's calculated by content;
|
||
|
*/
|
||
|
const getMarkSize = ctx => {
|
||
|
let defaultWidth = 120;
|
||
|
let defaultHeight = 64;
|
||
|
if (!image && ctx.measureText) {
|
||
|
ctx.font = `${Number(fontSize)}px ${fontFamily}`;
|
||
|
const contents = Array.isArray(content) ? content : [content];
|
||
|
const sizes = contents.map(item => {
|
||
|
const metrics = ctx.measureText(item);
|
||
|
return [metrics.width, metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent];
|
||
|
});
|
||
|
defaultWidth = Math.ceil(Math.max.apply(Math, (0, _toConsumableArray2.default)(sizes.map(size => size[0]))));
|
||
|
defaultHeight = Math.ceil(Math.max.apply(Math, (0, _toConsumableArray2.default)(sizes.map(size => size[1])))) * contents.length + (contents.length - 1) * _useClips.FontGap;
|
||
|
}
|
||
|
return [width !== null && width !== void 0 ? width : defaultWidth, height !== null && height !== void 0 ? height : defaultHeight];
|
||
|
};
|
||
|
const getClips = (0, _useClips.default)();
|
||
|
const [watermarkInfo, setWatermarkInfo] = _react.default.useState(null);
|
||
|
// Generate new Watermark content
|
||
|
const renderWatermark = () => {
|
||
|
const canvas = document.createElement('canvas');
|
||
|
const ctx = canvas.getContext('2d');
|
||
|
if (ctx) {
|
||
|
const ratio = (0, _utils.getPixelRatio)();
|
||
|
const [markWidth, markHeight] = getMarkSize(ctx);
|
||
|
const drawCanvas = drawContent => {
|
||
|
const [nextClips, clipWidth] = getClips(drawContent || '', rotate, ratio, markWidth, markHeight, {
|
||
|
color,
|
||
|
fontSize,
|
||
|
fontStyle,
|
||
|
fontWeight,
|
||
|
fontFamily
|
||
|
}, gapX, gapY);
|
||
|
setWatermarkInfo([nextClips, clipWidth]);
|
||
|
};
|
||
|
if (image) {
|
||
|
const img = new Image();
|
||
|
img.onload = () => {
|
||
|
drawCanvas(img);
|
||
|
};
|
||
|
img.onerror = () => {
|
||
|
drawCanvas(content);
|
||
|
};
|
||
|
img.crossOrigin = 'anonymous';
|
||
|
img.referrerPolicy = 'no-referrer';
|
||
|
img.src = image;
|
||
|
} else {
|
||
|
drawCanvas(content);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
const syncWatermark = (0, _useRafDebounce.default)(renderWatermark);
|
||
|
// ============================= Effect =============================
|
||
|
// Append watermark to the container
|
||
|
const [appendWatermark, removeWatermark, isWatermarkEle] = (0, _useWatermark.default)(markStyle);
|
||
|
(0, _react.useEffect)(() => {
|
||
|
if (watermarkInfo) {
|
||
|
targetElements.forEach(holder => {
|
||
|
appendWatermark(watermarkInfo[0], watermarkInfo[1], holder);
|
||
|
});
|
||
|
}
|
||
|
}, [watermarkInfo, targetElements]);
|
||
|
// ============================ Observe =============================
|
||
|
const onMutate = mutations => {
|
||
|
mutations.forEach(mutation => {
|
||
|
if ((0, _utils.reRendering)(mutation, isWatermarkEle)) {
|
||
|
syncWatermark();
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
(0, _mutateObserver.useMutateObserver)(targetElements, onMutate);
|
||
|
(0, _react.useEffect)(syncWatermark, [rotate, zIndex, width, height, image, content, color, fontSize, fontWeight, fontStyle, fontFamily, gapX, gapY, offsetLeft, offsetTop]);
|
||
|
// ============================ Context =============================
|
||
|
const watermarkContext = _react.default.useMemo(() => ({
|
||
|
add: ele => {
|
||
|
setSubElements(prev => {
|
||
|
const clone = new Set(prev);
|
||
|
clone.add(ele);
|
||
|
return getSizeDiff(prev, clone);
|
||
|
});
|
||
|
},
|
||
|
remove: ele => {
|
||
|
removeWatermark(ele);
|
||
|
setSubElements(prev => {
|
||
|
const clone = new Set(prev);
|
||
|
clone.delete(ele);
|
||
|
return getSizeDiff(prev, clone);
|
||
|
});
|
||
|
}
|
||
|
}), []);
|
||
|
// ============================= Render =============================
|
||
|
return /*#__PURE__*/_react.default.createElement("div", {
|
||
|
ref: setContainer,
|
||
|
className: (0, _classnames.default)(className, rootClassName),
|
||
|
style: Object.assign({
|
||
|
position: 'relative'
|
||
|
}, style)
|
||
|
}, /*#__PURE__*/_react.default.createElement(_context.default.Provider, {
|
||
|
value: watermarkContext
|
||
|
}, children));
|
||
|
};
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
Watermark.displayName = 'Watermark';
|
||
|
}
|
||
|
var _default = exports.default = Watermark;
|