import { h } from '@ui-arch/frame'; import type { WatermarkProps } from './types.js'; /*** 取令牌实色:canvas 不认 var(--ua-*),先解析成计算值,解析不到再退回中性灰 ***/ function tokenColor(expr: string): string { const name = /^var\((--[\w-]+)\)$/.exec(expr.trim())?.[1]; // 形如 var(--ua-color-text-muted) if (!name) return expr; // 已经是实际色值,直接用 const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); return v || 'rgb(128 128 128)'; // 兜底:中性灰,避免透明水印 } /*** 生成平铺单元:一块 gap 见方的画布,居中画一行旋转文字,靠 background-repeat 铺满 ***/ function tile(props: WatermarkProps): string { const gap = props.gap ?? 140; // 平铺单元尺寸 const size = props.fontSize ?? 14; // 字号 const ratio = window.devicePixelRatio || 1; // 高分屏按 DPR 放大,避免糊 const cv = document.createElement('canvas'); cv.width = gap * ratio; cv.height = gap * ratio; const ctx = cv.getContext('2d'); if (!ctx) return ''; // 卫语句:环境不支持 canvas 时不出图 ctx.scale(ratio, ratio); ctx.translate(gap / 2, gap / 2); ctx.rotate(((props.rotate ?? -20) * Math.PI) / 180); ctx.font = `${size}px system-ui, sans-serif`; ctx.fillStyle = tokenColor(props.color ?? 'var(--ua-color-text-muted)'); ctx.globalAlpha = props.opacity ?? 0.12; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; props.text.split('\n').forEach((line, i, arr) => { ctx.fillText(line, 0, (i - (arr.length - 1) / 2) * (size + 4)); // 多行按行距垂直居中 }); return cv.toDataURL('image/png'); } /*** 水印:内容容器 + 覆盖层,覆盖层用 repeat 背景平铺,pointer-events:none 不挡交互 ***/ export function watermark(props: WatermarkProps, ...children: unknown[]): HTMLElement { const img = tile(props); const layer = h('div', { class: 'ua-watermark-layer', 'aria-hidden': 'true' }) as HTMLElement; if (img) layer.style.backgroundImage = `url(${img})`; // 画布不可用时降级为无水印,不影响内容 return h('div', { class: 'ua-watermark' }, h('div', { class: 'ua-watermark-body' }, ...children), layer) as HTMLElement; }