vue3-electronic-signature
Version:
一个基于Vue3的电子签名组件库,支持手写签名、触摸绘制和多种导出格式
1,554 lines • 55.3 kB
JavaScript
var lt = Object.defineProperty;
var ht = (r, e, a) => e in r ? lt(r, e, { enumerable: !0, configurable: !0, writable: !0, value: a }) : r[e] = a;
var M = (r, e, a) => (ht(r, typeof e != "symbol" ? e + "" : e, a), a);
import { defineComponent as ct, ref as W, computed as R, watch as se, nextTick as oe, onMounted as ut, onUnmounted as dt, openBlock as $, createElementBlock as z, normalizeStyle as ie, createElementVNode as b, toDisplayString as re, createCommentVNode as le } from "vue";
function _e(r, e) {
return Math.sqrt(
Math.pow(e.x - r.x, 2) + Math.pow(e.y - r.y, 2)
);
}
function mt(r, e) {
return Math.atan2(e.y - r.y, e.x - r.x);
}
function pt(r, e, a, s) {
const o = e || r, i = a || r, c = 0.2, h = mt(o, i) * (s ? 1 : -1), l = _e(o, i) * c;
return {
x: r.x + Math.cos(h) * l,
y: r.y + Math.sin(h) * l,
time: r.time
};
}
function gt(r, e, a) {
if (!a.pressure.enabled)
return a.strokeWidth;
const s = _e(r, e), o = e.time - r.time, i = o > 0 ? s / o : 0, c = Math.max(0.1, Math.min(1, 1 - i * 0.01)), { min: h, max: l } = a.pressure;
return h + (l - h) * c;
}
function jt(r, e, a) {
if (e.length < 2)
return;
if (r.strokeStyle = a.strokeColor, r.lineCap = "round", r.lineJoin = "round", !a.smoothing || e.length < 3) {
r.beginPath(), r.lineWidth = a.strokeWidth, r.moveTo(e[0].x, e[0].y);
for (let o = 1; o < e.length; o++)
r.lineTo(e[o].x, e[o].y);
r.stroke();
return;
}
r.beginPath(), r.moveTo(e[0].x, e[0].y);
for (let o = 1; o < e.length - 1; o++) {
const i = e[o], c = e[o + 1];
a.pressure.enabled ? r.lineWidth = gt(e[o - 1], i, a) : r.lineWidth = a.strokeWidth;
const h = pt(i, e[o - 1], c);
r.quadraticCurveTo(h.x, h.y, i.x, i.y);
}
const s = e[e.length - 1];
r.lineTo(s.x, s.y), r.stroke();
}
function ft(r) {
const { canvasSize: e, paths: a } = r;
let s = `<svg width="${e.width}" height="${e.height}" xmlns="http://www.w3.org/2000/svg">`;
return a.forEach((o) => {
if (o.points.length < 2)
return;
let i = `M ${o.points[0].x} ${o.points[0].y}`;
for (let c = 1; c < o.points.length; c++)
i += ` L ${o.points[c].x} ${o.points[c].y}`;
s += `<path d="${i}" stroke="${o.strokeColor}" stroke-width="${o.strokeWidth}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`;
}), s += "</svg>", s;
}
function yt(r, e, a = { format: "png" }) {
const { format: s, quality: o = 0.9, size: i, backgroundColor: c } = a;
if (s === "svg")
return ft(e);
const h = document.createElement("canvas"), l = h.getContext("2d");
if (i) {
h.width = i.width, h.height = i.height;
const u = i.width / r.width, g = i.height / r.height;
l.scale(u, g);
} else
h.width = r.width, h.height = r.height;
switch (c && c !== "transparent" && (l.fillStyle = c, l.fillRect(0, 0, h.width, h.height)), l.drawImage(r, 0, 0), s) {
case "jpeg":
return h.toDataURL("image/jpeg", o);
case "base64":
return h.toDataURL("image/png").split(",")[1];
case "png":
default:
return h.toDataURL("image/png");
}
}
function vt(r, e) {
return new Promise((a, s) => {
const o = new Image();
o.onload = () => {
const i = r.getContext("2d");
i.clearRect(0, 0, r.width, r.height), i.drawImage(o, 0, 0, r.width, r.height), a();
}, o.onerror = s, o.src = e;
});
}
function N(r) {
return r.paths.length === 0 || r.paths.every((e) => e.points.length === 0);
}
function j(r, e) {
return {
paths: [],
canvasSize: { width: r, height: e },
timestamp: Date.now(),
isEmpty: !0
};
}
function B(r) {
return JSON.parse(JSON.stringify(r));
}
class xt {
constructor(e) {
M(this, "canvas");
M(this, "ctx");
M(this, "replayData", null);
M(this, "state", "idle");
M(this, "currentTime", 0);
M(this, "speed", 1);
M(this, "animationId", null);
M(this, "startTimestamp", 0);
M(this, "pausedTime", 0);
M(this, "options", {});
// 事件回调
M(this, "eventCallbacks", /* @__PURE__ */ new Map());
// 性能优化相关
M(this, "offscreenCanvas", null);
M(this, "offscreenCtx", null);
M(this, "lastFrameImageBitmap", null);
M(this, "renderThrottle", 0);
M(this, "isRendering", !1);
// 确定性随机数生成器(解决毛笔闪烁问题)
M(this, "seededRandom");
this.canvas = e, this.ctx = e.getContext("2d"), this.initializeOffscreenCanvas(), this.seededRandom = this.createSeededRandom();
}
/**
* 创建确定性随机数生成器(解决毛笔等笔迹的闪烁问题)
* 基于简单的线性同余生成器(LCG)算法
*/
createSeededRandom() {
return (e) => {
const o = Math.pow(2, 32);
return (1664525 * e + 1013904223) % o / o;
};
}
/**
* 初始化离屏画布用于性能优化
*/
initializeOffscreenCanvas() {
try {
if (typeof OffscreenCanvas < "u" ? (this.offscreenCanvas = new OffscreenCanvas(this.canvas.width, this.canvas.height), this.offscreenCtx = this.offscreenCanvas.getContext("2d")) : (this.offscreenCanvas = document.createElement("canvas"), this.offscreenCanvas.width = this.canvas.width, this.offscreenCanvas.height = this.canvas.height, this.offscreenCtx = this.offscreenCanvas.getContext("2d")), this.offscreenCtx) {
this.offscreenCtx.imageSmoothingEnabled = !0, this.offscreenCtx.imageSmoothingQuality = "high";
try {
this.offscreenCtx.textRenderingOptimization = "optimizeSpeed";
} catch {
}
}
} catch (e) {
console.warn("Failed to initialize optimized offscreen canvas:", e), this.offscreenCanvas = document.createElement("canvas"), this.offscreenCanvas.width = this.canvas.width, this.offscreenCanvas.height = this.canvas.height, this.offscreenCtx = this.offscreenCanvas.getContext("2d");
}
}
/**
* 设置回放数据
*/
setReplayData(e, a = {}) {
console.log("设置回放数据:", e), console.log("回放选项:", a), this.replayData = e, this.options = { ...a }, this.speed = a.speed || e.speed || 1, this.currentTime = a.startTime || 0, this.state = "idle", this.resetOptimizationState(), console.log("回放数据设置完成,路径数量:", e.paths.length), console.log("总时长:", e.totalDuration);
}
/**
* 重置优化状态
*/
resetOptimizationState() {
this.offscreenCanvas && (this.offscreenCanvas.width = this.canvas.width, this.offscreenCanvas.height = this.canvas.height);
}
/**
* 开始播放
*/
play() {
if (console.log("播放方法调用,回放数据:", this.replayData), console.log("当前状态:", this.state), !this.replayData) {
console.error("没有回放数据,无法播放");
return;
}
if (this.replayData.totalDuration <= 0) {
console.error("回放数据总时长为0,无法播放");
return;
}
if (this.replayData.paths.length === 0) {
console.error("回放数据没有路径,无法播放");
return;
}
if (this.state === "playing") {
console.log("已在播放中,忽略");
return;
}
this.state === "paused" ? (console.log("从暂停状态恢复播放"), this.state = "playing", this.startTimestamp = performance.now() - this.pausedTime, this.emit("replay-resume")) : (console.log("开始新的播放"), this.state = "playing", this.startTimestamp = performance.now(), this.pausedTime = 0, this.currentTime = this.options.startTime || 0, this.clearCanvas(), this.emit("replay-start")), this.animate();
}
/**
* 暂停播放
*/
pause() {
this.state === "playing" && (this.state = "paused", this.pausedTime = performance.now() - this.startTimestamp, this.animationId && (cancelAnimationFrame(this.animationId), this.animationId = null), this.emit("replay-pause"));
}
/**
* 停止播放
*/
stop() {
this.state = "stopped", this.currentTime = 0, this.pausedTime = 0, this.animationId && (cancelAnimationFrame(this.animationId), this.animationId = null), this.clearCanvas(), this.emit("replay-stop");
}
/**
* 跳转到指定时间
*/
seek(e) {
if (!this.replayData)
return;
const a = this.options.endTime || this.replayData.totalDuration;
this.currentTime = Math.max(0, Math.min(e, a)), this.state === "playing" ? this.startTimestamp = performance.now() - this.currentTime / this.speed : this.pausedTime = this.currentTime / this.speed, this.renderFrame(this.currentTime);
}
/**
* 设置播放速度
*/
setSpeed(e) {
const a = this.state === "playing";
a && this.pause(), this.speed = Math.max(0.1, Math.min(5, e)), this.emit("replay-speed-change", this.speed), a && this.play();
}
/**
* 获取当前状态
*/
getState() {
return this.state;
}
/**
* 获取当前时间
*/
getCurrentTime() {
return this.currentTime;
}
/**
* 获取总时长
*/
getTotalDuration() {
var e;
return ((e = this.replayData) == null ? void 0 : e.totalDuration) || 0;
}
/**
* 获取当前进度(0-1)
*/
getProgress() {
const e = this.getTotalDuration();
return e > 0 ? this.currentTime / e : 0;
}
/**
* 动画循环
*/
animate() {
if (this.state !== "playing" || !this.replayData)
return;
const e = performance.now();
this.currentTime = (e - this.startTimestamp) * this.speed;
const a = this.options.endTime || this.replayData.totalDuration;
if (this.currentTime >= a) {
this.currentTime = a, this.state = "completed", this.renderFrame(this.currentTime), this.emit("replay-complete"), this.options.loop && setTimeout(() => {
this.currentTime = this.options.startTime || 0, this.play();
}, 500);
return;
}
this.renderFrame(this.currentTime), this.emit("replay-progress", this.getProgress(), this.currentTime), this.animationId = requestAnimationFrame(() => this.animate());
}
/**
* 渲染指定时间的帧 - 高性能优化版本
*/
renderFrame(e) {
if (!this.replayData || !this.offscreenCanvas || !this.offscreenCtx)
return;
const a = performance.now();
if (!(a - this.renderThrottle < 16) && (this.renderThrottle = a, !this.isRendering)) {
this.isRendering = !0;
try {
this.renderToOffscreenCanvas(e), this.transferToMainCanvasSync();
} finally {
this.isRendering = !1;
}
}
}
/**
* 高效地将离屏画布内容传输到主画布(同步版本)
*/
transferToMainCanvasSync() {
if (this.offscreenCanvas)
try {
this.ctx.imageSmoothingEnabled = !1, this.ctx.globalCompositeOperation = "copy", this.ctx.drawImage(this.offscreenCanvas, 0, 0), this.ctx.globalCompositeOperation = "source-over", this.ctx.imageSmoothingEnabled = !0;
} catch {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.ctx.drawImage(this.offscreenCanvas, 0, 0);
}
}
/**
* 在离屏画布上渲染完整帧
*/
renderToOffscreenCanvas(e) {
if (!this.replayData || !this.offscreenCtx)
return;
const a = this.offscreenCanvas, s = this.offscreenCtx;
s.globalCompositeOperation = "copy", s.fillStyle = "transparent", s.fillRect(0, 0, a.width, a.height), s.globalCompositeOperation = "source-over";
let o = !1;
for (let i = 0; i < this.replayData.paths.length; i++) {
const c = this.replayData.paths[i], h = c.startTime || 0, l = c.endTime || h + (c.duration || 0);
if (e < h)
break;
if (e >= l) {
this.drawCompletePathToOffscreen(c), !o && Math.abs(e - l) < 32 && this.emit("replay-path-end", i, c);
continue;
}
o = !0;
const u = Math.max(0, Math.min(1, (e - h) / Math.max(l - h, 1)));
u > 0 && Math.abs(e - h) < 32 && this.emit("replay-path-start", i, c), this.drawPartialPathToOffscreen(c, u);
break;
}
}
/**
* 获取指定时间内的所有点
*/
getPointsUpToTime(e, a, s) {
const o = [];
for (let i = 0; i < e.length; i++) {
const c = e[i], h = a + (c.relativeTime || i * 50);
if (h <= s)
o.push(c);
else {
if (i > 0) {
const l = e[i - 1], u = a + (l.relativeTime || (i - 1) * 50);
if (u <= s) {
const g = (s - u) / (h - u), v = {
x: l.x + (c.x - l.x) * g,
y: l.y + (c.y - l.y) * g,
time: s,
pressure: l.pressure ? l.pressure + (c.pressure || l.pressure - l.pressure) * g : c.pressure
};
o.push(v);
}
}
break;
}
}
return o;
}
/**
* 根据笔迹样式计算动态线宽(与录制时一致)
*/
calculateDynamicStrokeWidth(e, a, s, o) {
switch (s) {
case "pen":
return 1;
case "brush":
if (a) {
const p = Math.sqrt(Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)), x = Math.max(1, (e.time || 0) - (a.time || 0)), C = p / x, I = Math.max(0.1, Math.min(3, 100 / Math.max(C, 1))), E = e.pressure || 0.5, k = Math.floor(e.x * 1e3 + e.y * 1e3 + (e.time || 0)), S = 0.8 + this.seededRandom(k) * 0.4;
return Math.max(1, Math.min(20, o * I * (0.3 + E * 1.4) * S));
}
return o;
case "marker":
return 12;
case "pencil":
const i = e.pressure || 0.5, c = Math.floor(e.x * 1e3 + e.y * 1e3 + (e.time || 0)), h = 0.9 + this.seededRandom(c + 1) * 0.2;
return o * (0.7 + i * 0.6) * h;
case "ballpoint":
const l = e.pressure || 0.5;
return o * (0.8 + l * 0.4);
case "elegant":
const u = e.pressure || 0.5;
let g = 1;
if (a) {
const p = Math.sqrt(Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)), x = Math.max(1, (e.time || 0) - (a.time || 0)), C = p / x;
g = Math.max(0.3, Math.min(2, 50 / Math.max(C, 1)));
}
const v = u * g;
return o * (0.4 + v * 1.6);
default:
return o;
}
}
/**
* 根据笔迹样式绘制线段(与录制时完全一致)
*/
drawStyledStrokeForReplay(e, a, s, o) {
if (!(e.length < 2))
switch (this.ctx.strokeStyle = s, a) {
case "pen":
if (this.ctx.lineWidth = 1, this.ctx.lineCap = "butt", this.ctx.lineJoin = "miter", this.ctx.beginPath(), this.ctx.moveTo(e[0].x, e[0].y), e.length >= 3) {
for (let i = 1; i < e.length - 1; i++) {
const c = this.getControlPoint(e[i], e[i - 1], e[i + 1]);
this.ctx.quadraticCurveTo(c.x, c.y, e[i].x, e[i].y);
}
this.ctx.lineTo(e[e.length - 1].x, e[e.length - 1].y);
} else
for (let i = 1; i < e.length; i++)
this.ctx.lineTo(e[i].x, e[i].y);
this.ctx.stroke();
break;
case "brush":
this.ctx.lineCap = "round", this.ctx.lineJoin = "round";
for (let i = 1; i < e.length; i++) {
const c = e[i], h = e[i - 1], l = this.calculateDynamicStrokeWidth(c, h, a, o);
this.ctx.lineWidth = l, this.ctx.beginPath(), this.ctx.moveTo(h.x, h.y), this.ctx.lineTo(c.x, c.y), this.ctx.stroke();
const u = Math.floor(c.x * 100 + c.y * 100 + i);
l > 8 && this.seededRandom(u) > 0.6 && (this.ctx.globalAlpha = 0.2, this.ctx.beginPath(), this.ctx.arc(c.x, c.y, l * 0.3, 0, Math.PI * 2), this.ctx.fill(), this.ctx.globalAlpha = 1);
}
break;
case "marker":
this.ctx.globalAlpha = 0.7, this.ctx.lineWidth = 12, this.ctx.lineCap = "square", this.ctx.lineJoin = "bevel", this.ctx.beginPath(), this.ctx.moveTo(e[0].x, e[0].y);
for (let i = 1; i < e.length; i++)
this.ctx.lineTo(e[i].x, e[i].y);
this.ctx.stroke(), this.ctx.globalAlpha = 0.3, this.ctx.lineWidth = 16, this.ctx.beginPath(), this.ctx.moveTo(e[0].x, e[0].y);
for (let i = 1; i < e.length; i++)
this.ctx.lineTo(e[i].x, e[i].y);
this.ctx.stroke(), this.ctx.globalAlpha = 1;
break;
case "pencil":
this.ctx.lineCap = "round", this.ctx.lineJoin = "round";
for (let i = 1; i < e.length; i++) {
const c = e[i], h = e[i - 1], l = this.calculateDynamicStrokeWidth(c, h, a, o);
this.ctx.lineWidth = l, this.ctx.globalAlpha = 0.8, this.ctx.beginPath(), this.ctx.moveTo(h.x, h.y), this.ctx.lineTo(c.x, c.y), this.ctx.stroke();
for (let g = 0; g < 3; g++) {
const v = Math.floor(c.x * 10 + c.y * 10 + i * 10 + g);
if (this.seededRandom(v) > 0.5) {
this.ctx.globalAlpha = 0.2, this.ctx.lineWidth = l * 0.3;
const p = (this.seededRandom(v + 1) - 0.5) * 2, x = (this.seededRandom(v + 2) - 0.5) * 2;
this.ctx.beginPath(), this.ctx.moveTo(h.x + p, h.y + x), this.ctx.lineTo(c.x + p, c.y + x), this.ctx.stroke();
}
}
const u = Math.floor(c.x * 5 + c.y * 5 + i * 5);
if (this.seededRandom(u) > 0.8) {
this.ctx.globalAlpha = 0.4;
for (let g = 0; g < 5; g++) {
const v = u + g * 10;
this.ctx.beginPath(), this.ctx.arc(
c.x + (this.seededRandom(v + 1) - 0.5) * 3,
c.y + (this.seededRandom(v + 2) - 0.5) * 3,
this.seededRandom(v + 3) * 0.8,
0,
Math.PI * 2
), this.ctx.fill();
}
}
}
this.ctx.globalAlpha = 1;
break;
case "ballpoint":
this.ctx.lineCap = "round", this.ctx.lineJoin = "round";
for (let i = 1; i < e.length; i++) {
const c = e[i], h = e[i - 1], l = this.calculateDynamicStrokeWidth(c, h, a, o);
if (this.ctx.lineWidth = l, this.ctx.globalAlpha = 1, this.ctx.beginPath(), i < e.length - 1) {
const u = e[i + 1], g = this.getControlPoint(c, h, u);
this.ctx.moveTo(h.x, h.y), this.ctx.quadraticCurveTo(g.x, g.y, c.x, c.y);
} else
this.ctx.moveTo(h.x, h.y), this.ctx.lineTo(c.x, c.y);
this.ctx.stroke();
}
this.ctx.globalAlpha = 1;
break;
case "elegant":
this.drawElegantStroke(e, s, o);
break;
}
}
/**
* 绘制优雅笔迹 - 基于Fabric.js和Vue3-Signature-Pad的速度压力感应技术
*/
drawElegantStroke(e, a, s) {
if (e.length < 2)
return;
this.ctx.strokeStyle = a, this.ctx.fillStyle = a, this.ctx.lineCap = "round", this.ctx.lineJoin = "round", this.ctx.globalCompositeOperation = "source-over";
const o = this.preprocessPointsForVelocity(e, s);
this.drawVelocityBasedPath(o);
}
/**
* 预处理点数据,计算速度和动态线宽 - 基于Vue3-Signature-Pad算法
*/
preprocessPointsForVelocity(e, a) {
const s = [];
for (let o = 0; o < e.length; o++) {
const i = e[o];
let c = 0, h = a;
if (o > 0) {
const u = e[o - 1], g = Math.sqrt(
Math.pow(i.x - u.x, 2) + Math.pow(i.y - u.y, 2)
), v = Math.max(1, (i.time || 0) - (u.time || 0));
c = g / v;
const p = i.pressure || 0.5, x = Math.max(0.1, Math.min(5, 150 / Math.max(c, 1)));
h = a * (0.2 + p * x * 2);
}
let l = h;
if (o > 0) {
const u = s[o - 1].smoothedWidth;
l = u + (h - u) * 0.5;
}
s.push({
...i,
velocity: c,
dynamicWidth: h,
smoothedWidth: Math.max(0.3, Math.min(a * 5, l))
// 扩大变化范围
});
}
return s;
}
/**
* 基于速度的路径绘制 - 使用Fabric.js的平滑算法
*/
drawVelocityBasedPath(e) {
if (!(e.length < 2))
for (let a = 1; a < e.length; a++) {
const s = e[a], o = e[a - 1];
this.drawVelocitySegment(o, s, o.smoothedWidth, s.smoothedWidth);
}
}
/**
* 绘制基于速度的单个线段 - 实现由粗到细的渐变
*/
drawVelocitySegment(e, a, s, o) {
const i = Math.sqrt(
Math.pow(a.x - e.x, 2) + Math.pow(a.y - e.y, 2)
), c = Math.max(2, Math.min(10, Math.floor(i / 3)));
this.ctx.beginPath();
const h = [];
for (let l = 0; l <= c; l++) {
const u = l / c, g = this.smoothStep(u), v = e.x + (a.x - e.x) * g, p = e.y + (a.y - e.y) * g, x = s + (o - s) * g, C = a.x - e.x, I = a.y - e.y, E = Math.sqrt(C * C + I * I);
if (E > 0) {
const k = -I / E * x / 2, S = C / E * x / 2;
l === 0 ? this.ctx.moveTo(v + k, p + S) : this.ctx.lineTo(v + k, p + S), h.push({ x: v - k, y: p - S });
}
}
for (let l = h.length - 1; l >= 0; l--)
this.ctx.lineTo(h[l].x, h[l].y);
this.ctx.closePath(), this.ctx.fill();
}
// 删除连接点函数 - 基于用户反馈,不需要黑色圆圈连接点
/**
* 平滑插值函数 - 基于Paper.js的平滑算法
*/
smoothStep(e) {
return e * e * (3 - 2 * e);
}
/**
* 获取控制点(用于贝塞尔曲线平滑) - 与signature.ts中的实现一致
*/
getControlPoint(e, a, s) {
const i = {
length: Math.sqrt(Math.pow(s.x - a.x, 2) + Math.pow(s.y - a.y, 2)),
angle: Math.atan2(s.y - a.y, s.x - a.x)
}, c = i.angle + Math.PI, h = i.length * 0.2;
return {
x: e.x + Math.cos(c) * h,
y: e.y + Math.sin(c) * h,
time: e.time || 0
};
}
/**
* 清除画布
*/
clearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
/**
* 注册事件监听器
*/
on(e, a) {
this.eventCallbacks.has(e) || this.eventCallbacks.set(e, []), this.eventCallbacks.get(e).push(a);
}
/**
* 移除事件监听器
*/
off(e, a) {
if (this.eventCallbacks.has(e))
if (a) {
const s = this.eventCallbacks.get(e), o = s.indexOf(a);
o > -1 && s.splice(o, 1);
} else
this.eventCallbacks.delete(e);
}
/**
* 触发事件
*/
emit(e, ...a) {
const s = this.eventCallbacks.get(e);
s && s.forEach((o) => o(...a));
}
/**
* 在离屏画布上绘制完整路径
*/
drawCompletePathToOffscreen(e) {
if (!this.offscreenCtx || e.points.length < 2)
return;
const a = this.ctx;
this.ctx = this.offscreenCtx;
const s = e.penStyle || this.options.penStyle || "pen";
this.drawStyledStrokeForReplay(e.points, s, e.strokeColor, e.strokeWidth), this.ctx = a;
}
/**
* 在离屏画布上绘制部分路径
*/
drawPartialPathToOffscreen(e, a) {
if (!this.offscreenCtx || e.points.length < 2)
return;
const s = e.startTime || 0, o = e.duration || 0, i = s + o * a, c = this.getPointsUpToTime(e.points, s, i);
if (c.length < 2)
return;
const h = this.ctx;
this.ctx = this.offscreenCtx;
const l = e.penStyle || this.options.penStyle || "pen";
this.drawStyledStrokeForReplay(c, l, e.strokeColor, e.strokeWidth), this.ctx = h;
}
/**
* 销毁控制器
*/
destroy() {
this.stop(), this.eventCallbacks.clear(), this.replayData = null, this.lastFrameImageBitmap && (this.lastFrameImageBitmap.close(), this.lastFrameImageBitmap = null);
}
}
function Ct(r) {
const e = r.paths.map((l) => {
const u = l.points.map((v, p) => {
var C;
let x;
if (v.time && l.points[0].time)
x = v.time - l.points[0].time;
else if (p === 0)
x = 0;
else {
const I = l.points[p - 1], k = Math.sqrt(
Math.pow(v.x - I.x, 2) + Math.pow(v.y - I.y, 2)
) / 100 * 1e3;
x = (((C = u[p - 1]) == null ? void 0 : C.relativeTime) || 0) + Math.max(k, 16);
}
return {
...v,
relativeTime: x
};
}), g = u.length > 0 ? u[u.length - 1].relativeTime : 0;
return {
...l,
points: u,
duration: g
};
}), a = [];
for (let l = 0; l < e.length; l++) {
const u = e[l];
let g;
if (l === 0)
g = 0;
else {
const x = a[l - 1], C = wt(
r.paths[l - 1].points,
r.paths[l].points
);
g = x.endTime + C;
}
const v = g + u.duration, p = {
...u,
startTime: g,
endTime: v
};
console.log(`路径 ${l}: 开始时间=${g}, 结束时间=${v}, 持续时间=${u.duration}`), a.push(p);
}
const s = a.length > 0 ? a[a.length - 1].endTime : 0;
console.log("回放数据生成完成:"), console.log("- 路径数量:", a.length), console.log("- 总时长:", s), console.log("- 路径详情:", a.map((l) => ({
startTime: l.startTime,
endTime: l.endTime,
duration: l.duration,
pointCount: l.points.length
})));
const o = a.reduce((l, u) => l + bt(u.points), 0), i = s > 0 ? o / (s / 1e3) : 0, c = a.slice(1).map((l, u) => {
const g = a[u];
return l.startTime - g.endTime;
}), h = c.length > 0 ? c.reduce((l, u) => l + u, 0) / c.length : 0;
return {
paths: a,
totalDuration: s,
speed: 1,
metadata: {
deviceType: Tt(r),
averageSpeed: i,
totalDistance: o,
averagePauseTime: h
}
};
}
function wt(r, e) {
if (r.length === 0 || e.length === 0)
return 200;
const a = r[r.length - 1], s = e[0];
if (a.time && s.time)
return Math.max(s.time - a.time, 50);
const o = Math.sqrt(
Math.pow(s.x - a.x, 2) + Math.pow(s.y - a.y, 2)
);
return Math.min(Math.max(o * 2, 100), 1e3);
}
function Tt(r) {
const e = r.paths.reduce((i, c) => i + c.points.length, 0), a = r.paths.length;
if (e === 0)
return "touch";
const s = e / a;
return s > 20 ? "touch" : s < 10 ? "mouse" : r.paths.some(
(i) => i.points.some((c) => c.pressure !== void 0)
) ? "pen" : "touch";
}
function bt(r) {
let e = 0;
for (let a = 1; a < r.length; a++) {
const s = r[a].x - r[a - 1].x, o = r[a].y - r[a - 1].y;
e += Math.sqrt(s * s + o * o);
}
return e;
}
const Fe = {
ballpoint: {
name: "适中笔迹",
description: "连续流畅,简洁专业,商务首选",
strokeWidth: 2,
smoothing: !0,
pressure: {
enabled: !0,
min: 1.5,
max: 2.5
},
lineCap: "round",
lineJoin: "round",
recommendedColor: "#374151"
},
elegant: {
name: "优雅笔迹",
description: "智能渐变,由粗到细,速度感应",
strokeWidth: 3,
smoothing: !0,
pressure: {
enabled: !0,
min: 0.3,
max: 15
},
lineCap: "round",
lineJoin: "round",
recommendedColor: "#374151"
},
brush: {
name: "毛笔",
description: "传统书法,自然变化,文化韵味",
strokeWidth: 8,
smoothing: !0,
pressure: {
enabled: !0,
min: 1,
max: 20
},
lineCap: "round",
lineJoin: "round",
recommendedColor: "#2c3e50"
},
pencil: {
name: "铅笔",
description: "稳定连续,简洁清晰,自然书写",
strokeWidth: 3,
smoothing: !0,
pressure: {
enabled: !0,
min: 2,
max: 5
},
lineCap: "round",
lineJoin: "round",
recommendedColor: "#666666"
},
pen: {
name: "钢笔",
description: "极细锐利,精准线条,技术绘图",
strokeWidth: 1,
smoothing: !0,
pressure: {
enabled: !0,
min: 1,
max: 1
},
lineCap: "round",
lineJoin: "round",
recommendedColor: "#000080"
},
marker: {
name: "马克笔",
description: "超粗荧光,醒目标记,重点突出",
strokeWidth: 12,
smoothing: !1,
pressure: {
enabled: !1,
min: 10,
max: 14
},
lineCap: "square",
lineJoin: "bevel",
recommendedColor: "#ff6b35"
}
};
function kt(r) {
return Fe[r];
}
function Gt() {
return Object.entries(Fe).map(([r, e]) => ({
key: r,
config: e
}));
}
function Mt(r, e) {
const a = kt(r);
return {
strokeWidth: a.strokeWidth,
smoothing: a.smoothing,
pressure: a.pressure,
lineCap: a.lineCap,
lineJoin: a.lineJoin,
strokeColor: e || a.recommendedColor || "#000000"
};
}
const St = ["width", "height"], Pt = {
key: 1,
class: "signature-toolbar"
}, Dt = ["disabled"], Wt = ["disabled"], Rt = ["disabled"], It = {
key: 2,
class: "replay-controls"
}, Et = { class: "replay-buttons" }, Ot = ["disabled"], _t = { key: 0 }, Ft = { key: 1 }, At = ["disabled"], Jt = { class: "replay-progress" }, qt = ["max", "value", "disabled"], $t = { class: "time-display" }, zt = { class: "replay-speed" }, Bt = 16, Yt = /* @__PURE__ */ ct({
__name: "ElectronicSignature",
props: {
showToolbar: { type: Boolean, default: !1 },
width: { default: "100%" },
height: { default: 300 },
penStyle: { default: "ballpoint" },
strokeColor: { default: "#000000" },
strokeWidth: { default: 2 },
backgroundColor: { default: "transparent" },
disabled: { type: Boolean, default: !1 },
placeholder: { default: "请在此处签名" },
smoothing: { type: Boolean, default: !0 },
pressureSensitive: { type: Boolean, default: !1 },
minStrokeWidth: { default: 1 },
maxStrokeWidth: { default: 4 },
borderStyle: { default: "1px solid #ddd" },
borderRadius: { default: "4px" },
replayMode: { type: Boolean },
realTimeMode: { type: Boolean, default: !0 },
replayData: {},
replayOptions: {}
},
emits: ["signature-start", "signature-drawing", "signature-end", "signature-clear", "signature-undo", "signature-redo", "replay-start", "replay-progress", "replay-pause", "replay-resume", "replay-stop", "replay-complete", "replay-path-start", "replay-path-end", "replay-speed-change"],
setup(r, { expose: e, emit: a }) {
const s = r, o = a, i = W(), c = W(!1), h = W(null), l = W(j(0, 0)), u = W([]), g = W(-1), v = W(0), p = W(null), x = W(!1), C = W("idle"), I = W(0), E = W(0), k = R(() => typeof s.width == "number" ? s.width : 800), S = R(() => typeof s.height == "number" ? s.height : 300), Je = R(() => ({
position: "relative",
display: "inline-block",
width: typeof s.width == "string" ? s.width : `${s.width}px`,
height: typeof s.height == "string" ? s.height : `${s.height}px`
})), qe = R(() => ({
border: s.borderStyle,
borderRadius: s.borderRadius,
backgroundColor: s.backgroundColor,
cursor: s.disabled ? "not-allowed" : "crosshair",
display: "block",
width: "100%",
height: "100%"
})), $e = R(() => ({
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
color: "#999",
fontSize: "14px",
pointerEvents: "none",
userSelect: "none"
})), ze = R(() => x.value ? !1 : s.placeholder && N(l.value)), he = R(() => g.value > 0), ce = R(() => g.value < u.value.length - 1), ue = R(() => x.value && p.value), O = R(() => !ue.value && !s.disabled), Be = R(() => {
var t;
return ue.value && ((t = s.replayOptions) == null ? void 0 : t.showControls) !== !1;
}), D = R(() => {
if (s.penStyle) {
const t = Mt(s.penStyle, s.strokeColor);
return {
strokeColor: t.strokeColor,
strokeWidth: s.strokeWidth || t.strokeWidth,
smoothing: s.smoothing !== void 0 ? s.smoothing : t.smoothing,
pressure: {
enabled: s.pressureSensitive !== void 0 ? s.pressureSensitive : t.pressure.enabled,
min: s.minStrokeWidth || t.pressure.min,
max: s.maxStrokeWidth || t.pressure.max
},
lineCap: t.lineCap,
lineJoin: t.lineJoin
};
}
return {
strokeColor: s.strokeColor || "#000000",
strokeWidth: s.strokeWidth || 2,
smoothing: s.smoothing !== void 0 ? s.smoothing : !0,
pressure: {
enabled: s.pressureSensitive || !1,
min: s.minStrokeWidth || 1,
max: s.maxStrokeWidth || 4
},
lineCap: "round",
lineJoin: "round"
};
}), Y = () => {
var t;
return ((t = i.value) == null ? void 0 : t.getContext("2d")) || null;
}, V = (t, n) => {
const m = i.value, f = m.getBoundingClientRect(), d = m.width / f.width, y = m.height / f.height;
return {
x: (t - f.left) * d,
y: (n - f.top) * y,
time: Date.now()
};
}, de = (t) => {
if (!O.value)
return;
c.value = !0;
const n = performance.now(), m = { ...t, time: n };
h.value = {
points: [m],
strokeColor: s.strokeColor,
strokeWidth: s.strokeWidth,
penStyle: s.penStyle,
// 保存笔迹样式
startTime: n,
endTime: n,
duration: 0
}, o("signature-start");
}, G = (t, n, m, f) => {
switch (m) {
case "pen":
return 1;
case "brush":
if (n) {
const _ = Math.sqrt(Math.pow(t.x - n.x, 2) + Math.pow(t.y - n.y, 2)), F = Math.max(1, (t.time || 0) - (n.time || 0)), A = _ / F, q = Math.max(0.1, Math.min(3, 100 / Math.max(A, 1))), H = t.pressure || 0.5;
return Math.max(1, Math.min(20, f * q * (0.3 + H * 1.4)));
}
return f;
case "marker":
return 12;
case "pencil":
const d = t.pressure || 0.5;
return f * (0.7 + d * 0.6);
case "ballpoint":
const y = t.pressure || 0.5;
return f * (0.8 + y * 0.4);
case "elegant":
const w = t.pressure || 0.5;
let P = 1;
if (n) {
const _ = Math.sqrt(Math.pow(t.x - n.x, 2) + Math.pow(t.y - n.y, 2)), F = Math.max(1, (t.time || 0) - (n.time || 0)), A = _ / F;
P = Math.max(0.3, Math.min(2, 50 / Math.max(A, 1)));
}
const T = w * P;
return f * (0.4 + T * 1.6);
default:
return f;
}
}, X = (t, n, m) => {
var f;
if (!(n.length < 2))
switch (t.strokeStyle = ((f = h.value) == null ? void 0 : f.strokeColor) || D.value.strokeColor, t.lineCap = D.value.lineCap || "round", t.lineJoin = D.value.lineJoin || "round", m) {
case "pen":
if (t.lineWidth = 1, t.lineCap = "butt", t.lineJoin = "miter", t.beginPath(), t.moveTo(n[0].x, n[0].y), n.length >= 3) {
for (let d = 1; d < n.length - 1; d++) {
const y = me(n[d], n[d - 1], n[d + 1]);
t.quadraticCurveTo(y.x, y.y, n[d].x, n[d].y);
}
t.lineTo(n[n.length - 1].x, n[n.length - 1].y);
} else
for (let d = 1; d < n.length; d++)
t.lineTo(n[d].x, n[d].y);
t.stroke();
break;
case "brush":
t.lineCap = "round", t.lineJoin = "round";
for (let d = 1; d < n.length; d++) {
const y = n[d], w = n[d - 1], P = G(y, w, m, D.value.strokeWidth), T = t.createLinearGradient(w.x, w.y, y.x, y.y);
T.addColorStop(0, t.strokeStyle), T.addColorStop(1, t.strokeStyle), t.lineWidth = P, t.beginPath(), t.moveTo(w.x, w.y), t.lineTo(y.x, y.y), t.stroke();
}
break;
case "marker":
t.globalAlpha = 0.7, t.lineWidth = 12, t.lineCap = "square", t.lineJoin = "bevel", t.beginPath(), t.moveTo(n[0].x, n[0].y);
for (let d = 1; d < n.length; d++)
t.lineTo(n[d].x, n[d].y);
t.stroke(), t.globalAlpha = 0.3, t.lineWidth = 16, t.beginPath(), t.moveTo(n[0].x, n[0].y);
for (let d = 1; d < n.length; d++)
t.lineTo(n[d].x, n[d].y);
t.stroke(), t.globalAlpha = 1;
break;
case "pencil":
t.lineCap = "round", t.lineJoin = "round";
for (let d = 1; d < n.length; d++) {
const y = n[d], w = n[d - 1], P = G(y, w, m, D.value.strokeWidth);
t.lineWidth = P, t.globalAlpha = 0.8, t.beginPath(), t.moveTo(w.x, w.y), t.lineTo(y.x, y.y), t.stroke();
}
t.globalAlpha = 1;
break;
case "ballpoint":
t.lineCap = "round", t.lineJoin = "round";
for (let d = 1; d < n.length; d++) {
const y = n[d], w = n[d - 1], P = G(y, w, m, D.value.strokeWidth);
if (t.lineWidth = P, t.globalAlpha = 1, t.beginPath(), D.value.smoothing && d < n.length - 1) {
const T = n[d + 1], _ = me(y, w, T);
t.moveTo(w.x, w.y), t.quadraticCurveTo(_.x, _.y, y.x, y.y);
} else
t.moveTo(w.x, w.y), t.lineTo(y.x, y.y);
t.stroke();
}
t.globalAlpha = 1;
break;
case "elegant":
Ue(t, n);
break;
}
}, Ye = () => {
if (!h.value || h.value.points.length < 2)
return;
const t = Y();
if (!t)
return;
const n = h.value.penStyle || s.penStyle || "pen";
if (n === "brush") {
Xe();
return;
}
if (n === "ballpoint") {
Ve();
return;
}
t.clearRect(0, 0, k.value, S.value);
for (const m of l.value.paths)
m !== h.value && Q(t, m);
h.value.points.length >= 2 && Q(t, h.value);
}, Xe = () => {
if (!h.value || h.value.points.length < 2)
return;
const t = Y();
if (!t)
return;
const n = h.value.points;
if (n.length >= 2) {
const f = n.slice(-3);
f.length >= 2 && X(t, f, "brush");
}
}, Ve = () => {
if (!h.value || h.value.points.length < 2)
return;
const t = Y();
if (!t)
return;
const n = h.value.points;
if (n.length >= 2) {
const f = n.slice(-3);
f.length >= 2 && X(t, f, "ballpoint");
}
}, Le = () => {
if (!h.value || h.value.points.length < 2)
return;
const t = Y();
if (!t)
return;
const n = h.value.points, m = n.length, f = s.penStyle || "pen";
if (m === 2)
X(t, n, f);
else if (m >= 3) {
const d = n.slice(-3);
X(t, d, f);
}
}, Ue = (t, n) => {
var f, d;
if (n.length < 2)
return;
t.strokeStyle = ((f = h.value) == null ? void 0 : f.strokeColor) || D.value.strokeColor, t.fillStyle = ((d = h.value) == null ? void 0 : d.strokeColor) || D.value.strokeColor, t.lineCap = "round", t.lineJoin = "round", t.globalCompositeOperation = "source-over";
const m = He(n, D.value.strokeWidth);
Ne(t, m);
}, He = (t, n) => {
const m = [];
for (let f = 0; f < t.length; f++) {
const d = t[f];
let y = 0, w = n;
if (f > 0) {
const T = t[f - 1], _ = Math.sqrt(
Math.pow(d.x - T.x, 2) + Math.pow(d.y - T.y, 2)
), F = Math.max(1, (d.time || 0) - (T.time || 0));
y = _ / F;
const A = d.pressure || 0.5, q = Math.max(0.1, Math.min(5, 150 / Math.max(y, 1)));
w = n * (0.2 + A * q * 2);
}
let P = w;
if (f > 0) {
const T = m[f - 1].smoothedWidth;
P = T + (w - T) * 0.5;
}
m.push({
...d,
velocity: y,
dynamicWidth: w,
smoothedWidth: Math.max(0.3, Math.min(n * 5, P))
// 扩大变化范围
});
}
return m;
}, Ne = (t, n) => {
if (!(n.length < 2))
for (let m = 1; m < n.length; m++) {
const f = n[m], d = n[m - 1];
je(t, d, f, d.smoothedWidth, f.smoothedWidth);
}
}, je = (t, n, m, f, d) => {
const y = Math.sqrt(
Math.pow(m.x - n.x, 2) + Math.pow(m.y - n.y, 2)
), w = Math.max(2, Math.min(10, Math.floor(y / 3)));
t.beginPath();
const P = [];
for (let T = 0; T <= w; T++) {
const _ = T / w, F = Ge(_), A = n.x + (m.x - n.x) * F, q = n.y + (m.y - n.y) * F, H = f + (d - f) * F, Z = m.x - n.x, ee = m.y - n.y, te = Math.sqrt(Z * Z + ee * ee);
if (te > 0) {
const ne = -ee / te * H / 2, ae = Z / te * H / 2;
T === 0 ? t.moveTo(A + ne, q + ae) : t.lineTo(A + ne, q + ae), P.push({ x: A - ne, y: q - ae });
}
}
for (let T = P.length - 1; T >= 0; T--)
t.lineTo(P[T].x, P[T].y);
t.closePath(), t.fill();
}, Ge = (t) => t * t * (3 - 2 * t), me = (t, n, m) => {
const d = {
length: Math.sqrt(Math.pow(m.x - n.x, 2) + Math.pow(m.y - n.y, 2)),
angle: Math.atan2(m.y - n.y, m.x - n.x)
}, y = d.angle + Math.PI, w = d.length * 0.2;
return {
x: t.x + Math.cos(y) * w,
y: t.y + Math.sin(y) * w,
time: t.time || 0
};
}, Q = (t, n) => {
if (n.points.length < 2)
return;
const m = n.penStyle || s.penStyle || "pen", f = h.value;
h.value = n, X(t, n.points, m), h.value = f;
}, pe = (t) => {
if (!c.value || !h.value || !O.value)
return;
const n = performance.now();
if (n - v.value < Bt)
return;
v.value = n;
const m = { ...t, time: n };
h.value.points.push(m), h.value.startTime && (h.value.endTime = n, h.value.duration = n - h.value.startTime), s.realTimeMode ? Ye() : Le(), ve(), o("signature-drawing", l.value);
}, ge = () => {
if (!(!c.value || !h.value)) {
if (c.value = !1, h.value.points.length > 0) {
const t = h.value.points[h.value.points.length - 1];
t.time && h.value.startTime && (h.value.endTime = t.time, h.value.duration = t.time - h.value.startTime);
}
l.value.paths.push(h.value), l.value.isEmpty = !1, l.value.timestamp = Date.now(), L(), J(), h.value = null, o("signature-end", l.value);
}
}, Qe = (t) => {
t.preventDefault();
const n = V(t.clientX, t.clientY);
de(n);
}, Ke = (t) => {
if (t.preventDefault(), !c.value)
return;
const n = V(t.clientX, t.clientY);
pe(n);
}, fe = (t) => {
t.preventDefault(), ge();
}, Ze = (t) => {
if (t.preventDefault(), t.touches.length !== 1)
return;
const n = t.touches[0], m = V(n.clientX, n.clientY);
de(m);
}, et = (t) => {
if (t.preventDefault(), t.touches.length !== 1 || !c.value)
return;
const n = t.touches[0], m = V(n.clientX, n.clientY);
pe(m);
}, ye = (t) => {
t.preventDefault(), ge();
}, ve = () => {
l.value.canvasSize = {
width: k.value,
height: S.value
}, l.value.isEmpty = N(l.value);
}, L = () => {
u.value = u.value.slice(0, g.value + 1), u.value.push(B(l.value)), g.value = u.value.length - 1;
const t = 50;
u.value.length > t && (u.value = u.value.slice(-t), g.value = u.value.length - 1);
}, J = () => {
const t = Y();
t && (t.clearRect(0, 0, k.value, S.value), s.backgroundColor && s.backgroundColor !== "transparent" && (t.fillStyle = s.backgroundColor, t.fillRect(0, 0, k.value, S.value)), l.value.paths.forEach((n) => {
n.points.length > 0 && Q(t, n);
}));
}, U = () => {
if (console.log("初始化回放控制器"), console.log("canvas引用是否存在:", !!i.value), !i.value) {
console.error("canvas引用不存在,无法初始化回放控制器");
return;
}
p.value && (console.log("销毁现有回放控制器"), p.value.destroy()), console.log("创建新的回放控制器"), p.value = new xt(i.value), console.log("回放控制器创建成功:", !!p.value), p.value.on("replay-start", () => {
C.value = "playing", o("replay-start");
}), p.value.on("replay-progress", (t, n) => {
I.value = t, E.value = n, o("replay-progress", t, n);
}), p.value.on("replay-pause", () => {
C.value = "paused", o("replay-pause");
}), p.value.on("replay-resume", () => {
C.value = "playing", o("replay-resume");
}), p.value.on("replay-stop", () => {
C.value = "stopped", o("replay-stop");
}), p.value.on("replay-complete", () => {
C.value = "completed", o("replay-complete");
}), p.value.on("replay-path-start", (t, n) => {
o("replay-path-start", t, n);
}), p.value.on("replay-path-end", (t, n) => {
o("replay-path-end", t, n);
}), p.value.on("replay-speed-change", (t) => {
o("replay-speed-change", t);
});
}, xe = (t, n) => {
if (p.value || U(), p.value) {
x.value = !0;
const m = {
...n,
drawOptions: D.value,
penStyle: s.penStyle
};
p.value.setReplayData(t, m), console.log("startReplay调用,自动播放:", n == null ? void 0 : n.autoPlay), (n == null ? void 0 : n.autoPlay) === !0 && p.value.play();
}
}, Ce = (t) => {
x.value = t, !t && p.value && (p.value.stop(), J());
}, tt = () => N(l.value) ? null : Ct(l.value), we = () => {
console.log("play方法被调用"), console.log("回放控制器是否存在:", !!p.value), p.value || (console.log("回放控制器不存在,尝试初始化"), U()), p.value ? (console.log("调用回放控制器的play方法"), p.value.play()) : console.error("回放控制器初始化失败,无法播放");
}, Te = () => {
var t;
(t = p.value) == null || t.pause();
}, be = () => {
var t;
(t = p.value) == null || t.stop();
}, ke = (t) => {
var n;
(n = p.value) == null || n.seek(t);
}, Me = (t) => {
var n;
(n = p.value) == null || n.setSpeed(t);
}, nt = () => {
var t;
return ((t = p.value) == null ? void 0 : t.getState()) || "idle";
}, at = () => {
var t;
return ((t = p.value) == null ? void 0 : t.getCurrentTime()) || 0;
}, K = () => {
var t;
return ((t = p.value) == null ? void 0 : t.getTotalDuration()) || 0;
}, st = () => {
var t;
return ((t = p.value) == null ? void 0 : t.getProgress()) || 0;
}, Se = (t) => {
const n = Math.floor(t / 1e3), m = Math.floor(n / 60), f = n % 60;
return `${m}:${f.toString().padStart(2, "0")}`;
}, Pe = () => {
O.value && (l.value = j(k.value, S.value), J(), L(), o("signature-clear"));
}, De = () => {
!he.value || !O.value || (g.value--, l.value = B(u.value[g.value]), J(), o("signature-undo", l.value));
}, We = () => {
!ce.value || !O.value || (g.value++, l.value = B(u.value[g.value]), J(), o("signature-redo", l.value));
}, Re = (t) => {
const n = i.value;
return yt(n, l.value, t);
}, Ie = () => N(l.value), Ee = async (t) => {
if (!O.value)
return;
const n = i.value;
await vt(n, t), l.value = j(k.value, S.value), l.value.isEmpty = !1, L();
}, ot = () => B(l.value), it = (t) => {
O.value && (l.value = B(t), J(), L());
}, Oe = (t, n) => {
const m = t || k.value, f = n || S.value, d = Re({ format: "png" });
oe(() => {
const y = i.value;
y.width = m, y.height = f, Ie() || Ee(d), ve();
});
}, rt = () => {
const t = i.value;
t.width = k.value, t.height = S.value, l.value = j(k.value, S.value), u.value = [B(l.value)], g.value = 0, J();
};
return se([() => s.width, () => s.height], () => {
oe(() => {
i.value && Oe();
});
}), se(() => s.replayMode, (t) => {
t !== void 0 && Ce(t);
}), se(() => s.replayData, (t) => {
if (console.log("watch监听到回放数据变化:", t), console.log("当前回放模式:", s.replayMode), console.log("回放控制器是否存在:", !!p.value), t && s.replayMode)
if (p.value || (console.log("回放控制器未初始化,先初始化"), U()), p.value) {
console.log("开始设置回放数据到控制器");
const n = {
...s.replayOptions,
drawOptions: D.value,
penStyle: s.penStyle
};
p.value.setReplayData(t, n), console.log("回放数据已更新:", t);
} else
console.error("回放控制器初始化失败");
else
t || console.log("回放数据为空,跳过设置"), s.replayMode || console.log("不在回放模式,跳过设置");
}, { immediate: !0 }), ut(() => {
oe(() => {
rt(), U(), s.replayMode && s.replayData && xe(s.replayData, s.replayOptions);
});
}), dt(() => {
p.value && (p.value.destroy(), p.value = null);
}), e({
clear: Pe,
undo: De,
redo: We,
save: Re,
isEmpty: Ie,
fromDataURL: Ee,
getSignatureData: ot,
setSignatureData: it,
resize: Oe,
// 回放相关方法
startReplay: xe,
getReplayData: tt,
setReplayMode: Ce,
play: we,
pause: Te,
stop: be,
seek: ke,
setSpeed: Me,
getState: nt,
getCurrentTime: at,
getTotalDuration: K,
getProgress: st
}), (t, n) => ($(), z("div", {
class: "electronic-signature",
style: ie(Je.value)
}, [
b("canvas", {
ref_key: "canvasRef",
ref: i,
width: k.value,
height: S.value,
style: ie(qe.value),
onMousedown: Qe,
onMousemove: Ke,
onMouseup: fe,
onMouseleave: fe,
onTouchstart: Ze,
onTouchmove: et,
onTouchend: ye,
onTouchcancel: ye
}, null, 44, St),
ze.value ? ($(), z("div", {
key: 0,
class: "signature-placeholder",
style: ie($e.value)
}, re(t.placeholder), 5)) : le("", !0),
t.showToolbar ? ($(), z("div", Pt, [
b("button", {
onClick: Pe,
disabled: !O.value
}, "清除", 8, Dt),
b("button", {
onClick: De,
disabled: !O.value || !he.value
}, "撤销", 8, Wt),
b("button", {
onClick: We,
disabled: !O.value || !ce.value
}, "重做", 8, Rt)
])) : le("", !0),
Be.value ? ($(), z("div", It, [
b("div", Et, [
b("button", {
onClick: n[0] || (n[0] = (m) => C.value === "playing" ? Te() : we()),
disabled: C.value === "idle",
class: "replay-btn play-pause-btn"
}, [
C.value === "playing" ? ($(), z("span", _t, "⏸️")) : ($(), z("span", Ft, "▶️"))
], 8, Ot),
b("button", {
onClick: n[1] || (n[1] = (m) => be()),
disabled: C.value === "idle",
class: "replay-btn stop-btn"
}, " ⏹️ ", 8, At)
]),
b("div", Jt, [
b("input", {
type: "range",
min: "0",
max: K(),
value: E.value,
onInput: n[2] || (n[2] = (m) => ke(Number(m.target.value))),
class: "progress-slider",
disabled: C.value === "idle"
}, null, 40, qt),
b("div", $t, [
b("span", null, re(Se(E.value)), 1),
n[4] || (n[4] = b("span", null, "/", -1)),
b("span", null, re(Se(K())), 1)
])
]),
b("div", zt, [
n[6] || (n[6] = b("label", null, "速度:", -1)),
b("select", {
onChange: n[3] || (n[3] = (m) => Me(Number(m.target.value))),
class: "speed-select"
}, n[5] || (n[5] = [
b("option", { value: "0.5" }, "0.5x", -1),
b("option", {
value: "1",
selected: ""
}, "1x", -1),
b("option", { value: "1.5" }, "1.5x", -1),
b("option", { value: "2" }, "2x", -1)
]), 32)
])
])) : le("", !0)
], 4));
}
});
const Xt = (r, e) => {
const a = r.__vccOpts || r;
for (const [s, o] of e)
a[s] = o;
return a;
}, Ae = /* @__PURE__ */ Xt(Yt, [["__scopeId", "data-v-259e6ad4"]]);
function Vt() {
return window.devicePixelRatio || 1;
}
function Qt(r) {
const e = r.getContext("2d"), a = Vt(), s = r.clientWidth, o = r.clientHeight;
return r.width = s * a, r.height = o * a, e.scale(a, a), r.style.width = s + "px", r.style.height = o + "px", e;
}
function Lt(r) {
if (r.paths.length === 0)
return {
minX: 0,
minY: 0,
maxX: 0,
maxY: 0,
width: 0,
height: 0
};
let e = 1 / 0, a = 1 / 0, s = -1 / 0, o = -1 / 0;
return r.paths.forEach((i) => {
i.points.forEach((c) => {
e = Math.min(e, c.x), a = Math.min(a, c.y), s = Math.max(s, c.x), o = Math.max(o, c.y);
});
}), {
minX: e,
minY: a,
maxX: s,
maxY: o,
width: s - e,
height: o - a
};
}
function Kt(r, e, a = 10) {
const s = Lt(e);
if (s.width === 0 || s.height === 0) {
const l = document.createElement("canvas");
return l.width = 1, l.height = 1, l;
}
const o = document.createElement("canvas"), i = o.getContext("2d"), c = s.width + a * 2, h = s.height + a * 2;
return o.width = c, o.height = h, i.drawImage(
r,
s.minX - a,
s.minY - a,
c,
h,
0,
0,
c,
h
), o;
}
function Zt(r, e, a, s = !0) {
const o = document.createElement("canvas"), i = o.getContext("2d");
let c = e, h = a;
if (s) {
const l = r.width / r.height, u = e / a;
l > u ? h = e / l : c = a * l;
}
return o.width = c, o.height = h, i.imageSmoothingEnabled = !0, i.imageSmoothingQuality = "high", i.drawImage(r, 0, 0, c, h), o;
}
function en(r, e, a = {}) {
const {
fontSize: s = 12,
fontFamily: o = "Arial",
color: i = "#999",
opacity: c = 0.5,
position: h = "bottom-right"
} = a, l = document.createElement("canvas"), u = l.getContext("2d");
l.width = r.width, l.height = r.height, u.drawImage(r, 0, 0), u.font = `${s}px ${o}`, u.fillStyle = i, u.globalAlpha = c;
const v = u.measureText(e).width, p = s;
let x, C;
switch (h) {
case "top-left":
x = 10, C = p + 10;
break;
case "top-right":
x = r.width - v - 10, C = p + 10;
break;
case "bottom-left":
x = 10, C = r.height - 10;
break;
case "bottom-right":
x = r.width - v - 10, C = r.height - 10;
break;
case "center":
x = (r.width - v) / 2, C = (r.height + p) / 2;
break;
default:
x = r.width - v - 10, C = r.height - 10;
}
return u.fillText(e, x, C), u.globalAlpha = 1, l;
}
function tn(r) {
const e = document.createElement("canvas"), a = e.getContext("2d");
e.width = r.width, e.height = r.height, a.drawImage(r, 0, 0);
const s = a.getImageData(0, 0, r.width, r.height), o = s.data;
for (let i = 0; i < o.length; i += 4) {
const c = o[i] * 0.299 + o[i + 1] * 0.587 + o[i + 2] * 0.114;
o[i] = c, o[i + 1] = c, o[i + 2] = c;
}
return a.putImageData(s, 0, 0), e;
}
const Ut = (r) => {
r.component("ElectronicSignature", Ae);
}, nn = {
install: Ut,
ElectronicSignature: Ae
}, an = "1.0.0";
export {
Ae as ElectronicSignature,
Fe as PEN_STYLE_CONFIGS,
xt as SignatureReplayController,
en as addWatermark,
gt as calculateStrokeWidth,
B as cloneSignatureData,
tn as convertToGrayscale,
Mt as createDrawOptionsFromPenStyle,
j as createEmptySignatureData,
Ct as createReplayData,
Kt as cropSignature,
nn as default,
jt as drawSmoothPath,
yt as exportSignature,
Gt as getAllPenStyles,
mt as getAngle,
pt as getControlPoint,
Vt as getDevicePixelRatio,
_e as getDistance,
kt as getPenStyleConfig,
Lt as getSignatureBounds,
N as isSignatureEmpty,
vt as loadImageToCanvas,
Zt as resizeSignature,
Qt as setupHighDPICanvas,
ft as signatureToSVG,
an as version
};