213 lines
6.8 KiB
JavaScript
213 lines
6.8 KiB
JavaScript
import { h, defineComponent, ref, watchEffect } from 'vue';
|
|
import { onFontsReady } from 'vooks';
|
|
import { useConfig, useTheme } from "../../_mixins/index.mjs";
|
|
import { isBrowser, warnOnce } from "../../_utils/index.mjs";
|
|
import { watermarkLight } from "../styles/index.mjs";
|
|
import style from "./styles/index.cssr.mjs";
|
|
function getRatio(context) {
|
|
if (!context) {
|
|
return 1;
|
|
}
|
|
const backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1;
|
|
return (window.devicePixelRatio || 1) / backingStore;
|
|
}
|
|
export const watermarkProps = Object.assign(Object.assign({}, useTheme.props), {
|
|
debug: Boolean,
|
|
cross: Boolean,
|
|
fullscreen: Boolean,
|
|
width: {
|
|
type: Number,
|
|
default: 32
|
|
},
|
|
height: {
|
|
type: Number,
|
|
default: 32
|
|
},
|
|
zIndex: {
|
|
type: Number,
|
|
default: 10
|
|
},
|
|
xGap: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
yGap: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
yOffset: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
xOffset: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
rotate: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
image: String,
|
|
imageOpacity: {
|
|
type: Number,
|
|
default: 1
|
|
},
|
|
imageHeight: Number,
|
|
imageWidth: Number,
|
|
content: String,
|
|
selectable: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
fontSize: {
|
|
type: Number,
|
|
default: 14
|
|
},
|
|
fontFamily: String,
|
|
fontStyle: {
|
|
type: String,
|
|
default: 'normal'
|
|
},
|
|
fontVariant: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
fontWeight: {
|
|
type: Number,
|
|
default: 400
|
|
},
|
|
fontColor: {
|
|
type: String,
|
|
default: 'rgba(128, 128, 128, .3)'
|
|
},
|
|
fontStretch: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
lineHeight: {
|
|
type: Number,
|
|
default: 14
|
|
},
|
|
globalRotate: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
});
|
|
export default defineComponent({
|
|
name: 'Watermark',
|
|
props: watermarkProps,
|
|
setup(props, {
|
|
slots
|
|
}) {
|
|
const {
|
|
mergedClsPrefixRef
|
|
} = useConfig(props);
|
|
const themeRef = useTheme('Watermark', '-watermark', style, watermarkLight, props, mergedClsPrefixRef);
|
|
const base64UrlRef = ref('');
|
|
const canvas = isBrowser ? document.createElement('canvas') : null;
|
|
const ctx = canvas ? canvas.getContext('2d') : null;
|
|
const fontsReadyRef = ref(false);
|
|
onFontsReady(() => fontsReadyRef.value = true);
|
|
watchEffect(() => {
|
|
if (!canvas) return;
|
|
void fontsReadyRef.value;
|
|
const ratio = getRatio(ctx);
|
|
const {
|
|
xGap,
|
|
yGap,
|
|
width,
|
|
height,
|
|
yOffset,
|
|
xOffset,
|
|
rotate,
|
|
image,
|
|
content,
|
|
fontColor,
|
|
fontStyle,
|
|
fontVariant,
|
|
fontStretch,
|
|
fontWeight,
|
|
fontFamily,
|
|
fontSize,
|
|
lineHeight,
|
|
debug
|
|
} = props;
|
|
const canvasWidth = (xGap + width) * ratio;
|
|
const canvasHeight = (yGap + height) * ratio;
|
|
const canvasOffsetLeft = xOffset * ratio;
|
|
const canvasOffsetTop = yOffset * ratio;
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
if (ctx) {
|
|
ctx.translate(0, 0);
|
|
const markWidth = width * ratio;
|
|
const markHeight = height * ratio;
|
|
if (debug) {
|
|
ctx.strokeStyle = 'grey';
|
|
ctx.strokeRect(0, 0, markWidth, markHeight);
|
|
}
|
|
ctx.rotate(rotate * (Math.PI / 180));
|
|
if (image) {
|
|
const img = new Image();
|
|
img.crossOrigin = 'anonymous';
|
|
img.referrerPolicy = 'no-referrer';
|
|
img.src = image;
|
|
img.onload = () => {
|
|
ctx.globalAlpha = props.imageOpacity;
|
|
const {
|
|
imageWidth,
|
|
imageHeight
|
|
} = props;
|
|
ctx.drawImage(img, canvasOffsetLeft, canvasOffsetTop, (props.imageWidth || (imageHeight ? img.width * imageHeight / img.height : img.width)) * ratio, (props.imageHeight || (imageWidth ? img.height * imageWidth / img.width : img.height)) * ratio);
|
|
base64UrlRef.value = canvas.toDataURL();
|
|
};
|
|
} else if (content) {
|
|
if (debug) {
|
|
ctx.strokeStyle = 'green';
|
|
ctx.strokeRect(0, 0, markWidth, markHeight);
|
|
}
|
|
ctx.font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontStretch} ${fontSize * ratio}px/${lineHeight * ratio}px ${fontFamily || themeRef.value.self.fontFamily}`;
|
|
ctx.fillStyle = fontColor;
|
|
ctx.fillText(content, canvasOffsetLeft, canvasOffsetTop + lineHeight * ratio);
|
|
base64UrlRef.value = canvas.toDataURL();
|
|
} else if (!content) {
|
|
// For example, you are using the input box to customize the watermark
|
|
// content, but after clearing the input box, the content is empty,
|
|
// and the canvas content is empty. Clear canvas when content is empty
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
base64UrlRef.value = canvas.toDataURL();
|
|
}
|
|
} else {
|
|
warnOnce('watermark', 'Canvas is not supported in the browser.');
|
|
}
|
|
});
|
|
return () => {
|
|
var _a;
|
|
const {
|
|
globalRotate,
|
|
fullscreen,
|
|
zIndex
|
|
} = props;
|
|
const mergedClsPrefix = mergedClsPrefixRef.value;
|
|
const isFullScreenGlobalRotate = globalRotate !== 0 && fullscreen;
|
|
const rotatedImageOffset = 'max(142vh, 142vw)';
|
|
const watermarkNode = h("div", {
|
|
class: [`${mergedClsPrefix}-watermark`, globalRotate !== 0 && `${mergedClsPrefix}-watermark--global-rotate`, fullscreen && `${mergedClsPrefix}-watermark--fullscreen`],
|
|
style: {
|
|
transform: globalRotate ? `translateX(-50%) translateY(-50%) rotate(${globalRotate}deg)` : undefined,
|
|
zIndex: isFullScreenGlobalRotate ? undefined : zIndex,
|
|
backgroundSize: `${props.xGap + props.width}px`,
|
|
backgroundPosition: globalRotate === 0 ? props.cross ? `${props.width / 2}px ${props.height / 2}px, 0 0` : '' : props.cross ? `calc(${rotatedImageOffset} + ${props.width / 2}px) calc(${rotatedImageOffset} + ${props.height / 2}px), ${rotatedImageOffset} ${rotatedImageOffset}` : rotatedImageOffset,
|
|
backgroundImage: props.cross ? `url(${base64UrlRef.value}), url(${base64UrlRef.value})` : `url(${base64UrlRef.value})`
|
|
}
|
|
});
|
|
if (props.fullscreen && !globalRotate) return watermarkNode;
|
|
return h("div", {
|
|
class: [`${mergedClsPrefix}-watermark-container`, globalRotate !== 0 && `${mergedClsPrefix}-watermark-container--global-rotate`, fullscreen && `${mergedClsPrefix}-watermark-container--fullscreen`, props.selectable && `${mergedClsPrefix}-watermark-container--selectable`],
|
|
style: {
|
|
zIndex: isFullScreenGlobalRotate ? zIndex : undefined
|
|
}
|
|
}, (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots), watermarkNode);
|
|
};
|
|
}
|
|
}); |