{"version":3,"file":"watermark.mjs","sources":["../../../../../../packages/components/watermark/src/watermark.vue"],"sourcesContent":["<template>\n  <div ref=\"containerRef\" :style=\"[style]\">\n    <slot></slot>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n  computed,\n  onBeforeUnmount,\n  onMounted,\n  ref,\n  shallowRef,\n  watch,\n} from 'vue'\nimport { useMutationObserver } from '@vueuse/core'\nimport { watermarkProps } from './watermark'\nimport { getPixelRatio, getStyleStr, reRendering } from './utils'\nimport useClips, { FontGap } from './useClips'\nimport type { WatermarkProps, WatermarkFontType } from './watermark'\nimport type { CSSProperties } from 'vue'\n\ndefineOptions({\n  name: 'Watermark',\n})\n\nconst style: CSSProperties = {\n  position: 'relative',\n}\n\nconst props = defineProps(watermarkProps)\n\nconst color = computed(() => (props.font as WatermarkFontType)?.color ?? 'rgba(0,0,0,.15)')\nconst fontSize = computed(() => (props.font as WatermarkFontType)?.fontSize ?? 16)\nconst fontWeight = computed(() => (props.font as WatermarkFontType)?.fontWeight ?? 'normal')\nconst fontStyle = computed(() => (props.font as WatermarkFontType)?.fontStyle ?? 'normal')\nconst fontFamily = computed(() => (props.font as WatermarkFontType)?.fontFamily ?? 'sans-serif')\nconst textAlign = computed(() => (props.font as WatermarkFontType)?.textAlign ?? 'center')\nconst textBaseline = computed(() => (props.font as WatermarkFontType)?.textBaseline ?? 'hanging')\n\nconst gapX = computed(() => props.gap[0])\nconst gapY = computed(() => props.gap[1])\nconst gapXCenter = computed(() => gapX.value / 2)\nconst gapYCenter = computed(() => gapY.value / 2)\nconst offsetLeft = computed(() => props.offset?.[0] ?? gapXCenter.value)\nconst offsetTop = computed(() => props.offset?.[1] ?? gapYCenter.value)\n\nconst getMarkStyle = () => {\n  const markStyle: CSSProperties = {\n    zIndex: props.zIndex,\n    position: 'absolute',\n    left: 0,\n    top: 0,\n    width: '100%',\n    height: '100%',\n    pointerEvents: 'none',\n    backgroundRepeat: 'repeat',\n  }\n\n  /** Calculate the style of the offset */\n  let positionLeft = offsetLeft.value - gapXCenter.value\n  let positionTop = offsetTop.value - gapYCenter.value\n  if (positionLeft > 0) {\n    markStyle.left = `${positionLeft}px`\n    markStyle.width = `calc(100% - ${positionLeft}px)`\n    positionLeft = 0\n  }\n  if (positionTop > 0) {\n    markStyle.top = `${positionTop}px`\n    markStyle.height = `calc(100% - ${positionTop}px)`\n    positionTop = 0\n  }\n  markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`\n\n  return markStyle\n}\n\nconst containerRef = shallowRef<HTMLDivElement | null>(null)\nconst watermarkRef = shallowRef<HTMLDivElement>()\nconst stopObservation = ref(false)\n\nconst destroyWatermark = () => {\n  if (watermarkRef.value) {\n    watermarkRef.value.remove()\n    watermarkRef.value = undefined\n  }\n}\nconst appendWatermark = (base64Url: string, markWidth: number) => {\n  if (containerRef.value && watermarkRef.value) {\n    stopObservation.value = true\n    watermarkRef.value.setAttribute(\n      'style',\n      getStyleStr({\n        ...getMarkStyle(),\n        backgroundImage: `url('${base64Url}')`,\n        backgroundSize: `${Math.floor(markWidth)}px`,\n      }),\n    )\n    containerRef.value?.append(watermarkRef.value)\n    // Delayed execution\n    setTimeout(() => {\n      stopObservation.value = false\n    })\n  }\n}\n\n/**\n * Get the width and height of the watermark. The default values are as follows\n * Image: [120, 64]; Content: It's calculated by content;\n */\nconst getMarkSize = (ctx: CanvasRenderingContext2D) => {\n  let defaultWidth = 120\n  let defaultHeight = 64\n  const image = props.image\n  const content = props.content\n  const width = props.width\n  const height = props.height\n  if (!image && ctx.measureText) {\n    ctx.font = `${Number(fontSize.value)}px ${fontFamily.value}`\n    const contents = Array.isArray(content) ? content : [content]\n    const sizes = contents.map(item => {\n      const metrics = ctx.measureText(item!)\n\n      return [\n        metrics.width,\n        // Using `actualBoundingBoxAscent` to be compatible with lower version browsers (eg: Firefox < 116)\n        metrics.fontBoundingBoxAscent !== undefined\n          ? metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent\n          : metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,\n      ]\n    })\n    defaultWidth = Math.ceil(Math.max(...sizes.map(size => size[0])))\n    defaultHeight =\n      Math.ceil(Math.max(...sizes.map(size => size[1]))) * contents.length +\n      (contents.length - 1) * FontGap\n  }\n  return [width ?? defaultWidth, height ?? defaultHeight] as const\n}\n\nconst getClips = useClips()\n\nconst renderWatermark = () => {\n  const canvas = document.createElement('canvas')\n  const ctx = canvas.getContext('2d')\n  const image = props.image\n  const content = props.content\n  const rotate = props.rotate\n\n  if (ctx) {\n    if (!watermarkRef.value) {\n      watermarkRef.value = document.createElement('div')\n    }\n\n    const ratio = getPixelRatio()\n    const [markWidth, markHeight] = getMarkSize(ctx)\n\n    const drawCanvas = (\n      drawContent?: NonNullable<WatermarkProps['content']> | HTMLImageElement,\n    ) => {\n      const [textClips, clipWidth] = getClips(\n        drawContent || '',\n        rotate,\n        ratio,\n        markWidth,\n        markHeight,\n        {\n          color: color.value,\n          fontSize: fontSize.value,\n          fontStyle: fontStyle.value,\n          fontWeight: fontWeight.value,\n          fontFamily: fontFamily.value,\n          textAlign: textAlign.value,\n          textBaseline: textBaseline.value,\n        },\n        gapX.value,\n        gapY.value,\n      )\n\n      appendWatermark(textClips, clipWidth)\n    }\n\n    if (image) {\n      const img = new Image()\n      img.onload = () => {\n        drawCanvas(img)\n      }\n      img.onerror = () => {\n        drawCanvas(content)\n      }\n      img.crossOrigin = 'anonymous'\n      img.referrerPolicy = 'no-referrer'\n      img.src = image\n    } else {\n      drawCanvas(content)\n    }\n  }\n}\n\nonMounted(() => {\n  renderWatermark()\n})\n\nwatch(\n  () => props,\n  () => {\n    renderWatermark()\n  },\n  {\n    deep: true,\n    flush: 'post',\n  },\n)\n\nonBeforeUnmount(() => {\n  destroyWatermark()\n})\n\nconst onMutate = (mutations: MutationRecord[]) => {\n  if (stopObservation.value) {\n    return\n  }\n  mutations.forEach(mutation => {\n    if (reRendering(mutation, watermarkRef.value)) {\n      destroyWatermark()\n      renderWatermark()\n    }\n  })\n}\n\nuseMutationObserver(containerRef, onMutate, {\n  attributes: true,\n  subtree: true,\n  childList: true,\n})\n</script>\n"],"names":[],"mappings":";;;;;;oCAsBc;AAAA,EACZ,MAAM;AACR;;;;;;AAEA,UAAM,QAAuB;AAAA,MAC3B,UAAU;AAAA;AAKZ,UAAM,QAAQ,SAAS,MAAO;AAC9B,UAAM;AACN,4CAAyC,oBAA0C;AACnF;AACA,UAAM;AACN,UAAM;AACN,yBAAqB,eAAgB,YAAkC;AAEvE;AACA,UAAM,sBAAsB,MAAM;AAClC,UAAM;AACN,oDAAgD;AAChD;AACA,UAAM,YAAY,SAAS,MAAM;AAEjC,UAAM;AACJ;AAAiC;AACjB;AACJ,cACJ;AAAA,aACD;AAAA;AACE;AACC;AACO;AACG,MACpB;AAGA,UAAI,eAAe;AACnB,UAAI;AACJ,yBAAmB;AACjB;AACA,0BAAkB,eAAe;AACjC;AAAe;AAEjB,UAAI;AACF;AACA;AACA,sBAAc;AAAA,MAChB;AACA,qCAA+B;AAE/B;AAAO;AAGT;AACA,UAAM,eAAe;AACrB;AAEA;AACE;AACE;AACA;AAAqB;AACvB;AAEF,6BAAyB;AACvB;AACE;AACA,6DAEc;AAAA;AACM,UAChB;AAAyB,0BACT,cAAc;AAAS,QACzC;AAEF;AAEA;AACE;AAAwB,QAC1B;AAAC;AACH;AAOF;AACE;AACA;AACA;AACA,sBAAgB;AAChB;AACA;AACA,UAAI;AACF,sBAAc;AACd;AACA,mCAA2B;AACzB;AAEA;AAAO;AACG,YAER;AAE8C,UAChD;AAAA,QACF;AACA;AACA;AAE0B;AAE5B;AAAsD;AAGxD;AAEA;AACE;AACA;AACA;AACA;AACA,qBAAe;AAEf,UAAI;AACF,0BAAkB;AAChB,+BAAqB;AAA4B;AAGnD;AACA,0BAAkB;AAElB;AAGE,gBAAM,YAAY,sBAChB,mBACA;AAIA,yBACe;AAAA,sBACH,SAAS;AAAA,uBACR;AAAU;AACE,YACvB;AAAuB,YACvB;AAAqB,YACrB;AAA2B,UAC7B;AAKF,qCAA2B;AAAS,QACtC;AAEA;AACE;AACA;AACE;AAAc,UAChB;AACA,8BAAoB;AAClB;AAAkB;AAEpB,4BAAkB;AAClB;AACA;AAAU,QACZ;AACE,4BAAkB;AAAA;AACpB;AACF;AAGF,cAAU;AACR;AAAgB;AAGlB;AAGI;AAAgB;AAElB,YACQ;AAAA;AACC;AAIX;AACE;AAAiB;AAGnB;AACE,UAAI,gBAAgB,OAAO;AACzB;AAAA;AAEF;AACE,wBAAgB;AACd;AACA;AAAgB;AAClB;AACD;AAGH;AAA4C;AAC9B;AACH;AACE,KACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}