kfb-view 3.3.7 → 3.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,26 @@ import {
8
8
  pointInOtherPoint,
9
9
  isPointInMatrix,
10
10
  } from '../../util/calculate';
11
- import {Point, Rect} from '../../plugin/openseadragon/openseadragon';
11
+
12
+ const MOVE_BODY = 4;
13
+ const HANDLE_EDGE_TOP = 5;
14
+ const HANDLE_EDGE_RIGHT = 6;
15
+ const HANDLE_EDGE_BOTTOM = 7;
16
+ const HANDLE_EDGE_LEFT = 8;
17
+ const HANDLE_HIT = 28;
18
+ const CORNER_LEN = 20;
19
+ const EDGE_HANDLE_LEN = 14;
20
+ const TOOLBAR_HEIGHT = 44;
21
+ const TOOLBAR_MIN_WIDTH = 520;
22
+ const TOOLBAR_GAP = 5;
23
+ const MIN_SIZE = 40;
24
+ const STYLE_ID = 'kfb-tailoring-styles';
25
+
26
+ const ASPECT_PRESETS = {
27
+ '16:9': 16 / 9,
28
+ '4:3': 4 / 3,
29
+ '1:1': 1,
30
+ };
12
31
 
13
32
  /**
14
33
  * 用来裁剪/截图
@@ -16,7 +35,6 @@ import {Point, Rect} from '../../plugin/openseadragon/openseadragon';
16
35
  */
17
36
  export class Tailoring extends ViewerCommon {
18
37
  /**
19
- * 初始化视图测量工具
20
38
  * @param {Object} viewer
21
39
  * @param {Object} canvas
22
40
  * @param {Object} cache
@@ -29,7 +47,10 @@ export class Tailoring extends ViewerCommon {
29
47
  this.tailoringPoints = [];
30
48
  this.movePointIndex = -1;
31
49
  this.color = '#FFF';
32
- this.contents = [];
50
+ this.aspectMode = 'custom';
51
+ this.customRatio = 1;
52
+ this.isUpdatingInputs = false;
53
+ this.toolbarEl = null;
33
54
  this.options = {
34
55
  suffix: 'jpeg',
35
56
  quality: 0.92,
@@ -37,6 +58,132 @@ export class Tailoring extends ViewerCommon {
37
58
  shape: true,
38
59
  ...this.options,
39
60
  };
61
+ this.ensureStyles();
62
+ }
63
+
64
+ ensureStyles() {
65
+ let style = document.getElementById(STYLE_ID);
66
+ if (!style) {
67
+ style = document.createElement('style');
68
+ style.id = STYLE_ID;
69
+ document.head.appendChild(style);
70
+ }
71
+ style.textContent = `
72
+ .kfb-tailoring-toolbar {
73
+ position: absolute;
74
+ z-index: 20;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ gap: 16px;
79
+ height: ${TOOLBAR_HEIGHT}px;
80
+ padding: 0 12px;
81
+ box-sizing: border-box;
82
+ background: rgba(18, 18, 18, 0.92);
83
+ border: 1px solid rgba(255, 255, 255, 0.12);
84
+ border-radius: 8px;
85
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
86
+ pointer-events: auto;
87
+ user-select: none;
88
+ }
89
+ .kfb-tailoring-toolbar__main {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 12px;
93
+ min-width: 0;
94
+ flex: 1;
95
+ overflow: hidden;
96
+ }
97
+ .kfb-tailoring-toolbar__ratios {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 4px;
101
+ flex-shrink: 0;
102
+ }
103
+ .kfb-tailoring-toolbar__ratios button {
104
+ height: 28px;
105
+ padding: 0 10px;
106
+ border: 1px solid rgba(255, 255, 255, 0.18);
107
+ border-radius: 14px;
108
+ background: rgba(255, 255, 255, 0.06);
109
+ color: rgba(255, 255, 255, 0.85);
110
+ font-size: 12px;
111
+ cursor: pointer;
112
+ transition: all 0.15s ease;
113
+ white-space: nowrap;
114
+ }
115
+ .kfb-tailoring-toolbar__ratios button:hover {
116
+ background: rgba(255, 255, 255, 0.12);
117
+ border-color: rgba(255, 255, 255, 0.28);
118
+ }
119
+ .kfb-tailoring-toolbar__ratios button.is-active {
120
+ background: #01d0b0;
121
+ border-color: #01d0b0;
122
+ color: #fff;
123
+ }
124
+ .kfb-tailoring-toolbar__custom {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 6px;
128
+ color: rgba(255, 255, 255, 0.7);
129
+ font-size: 12px;
130
+ flex-shrink: 0;
131
+ }
132
+ .kfb-tailoring-toolbar__custom input {
133
+ width: 62px;
134
+ height: 28px;
135
+ padding: 0 6px;
136
+ border: 1px solid rgba(255, 255, 255, 0.18);
137
+ border-radius: 6px;
138
+ background: rgba(0, 0, 0, 0.35);
139
+ color: #fff;
140
+ font-size: 12px;
141
+ text-align: center;
142
+ outline: none;
143
+ }
144
+ .kfb-tailoring-toolbar__custom input:focus {
145
+ border-color: #01d0b0;
146
+ }
147
+ .kfb-tailoring-toolbar__actions {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 8px;
151
+ flex-shrink: 0;
152
+ margin-left: auto;
153
+ padding-left: 8px;
154
+ }
155
+ .kfb-tailoring-toolbar__actions button {
156
+ width: 32px;
157
+ height: 32px;
158
+ border: none;
159
+ border-radius: 6px;
160
+ cursor: pointer;
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ transition: background 0.15s ease;
165
+ }
166
+ .kfb-tailoring-toolbar__cancel {
167
+ background: rgba(255, 255, 255, 0.08);
168
+ color: #fff;
169
+ }
170
+ .kfb-tailoring-toolbar__cancel:hover {
171
+ background: rgba(255, 80, 80, 0.35);
172
+ }
173
+ .kfb-tailoring-toolbar__confirm {
174
+ background: rgba(1, 208, 176, 0.2);
175
+ color: #01d0b0;
176
+ }
177
+ .kfb-tailoring-toolbar__confirm:hover {
178
+ background: rgba(1, 208, 176, 0.45);
179
+ color: #fff;
180
+ }
181
+ .kfb-tailoring-toolbar__actions svg {
182
+ width: 16px;
183
+ height: 16px;
184
+ fill: currentColor;
185
+ }
186
+ `;
40
187
  }
41
188
 
42
189
  init({
@@ -45,180 +192,555 @@ export class Tailoring extends ViewerCommon {
45
192
  left,
46
193
  top,
47
194
  color,
48
- contents = [],
49
195
  shape = true,
50
196
  now = false,
197
+ aspectMode = 'custom',
51
198
  }) {
52
199
  this.options.shape = shape;
53
200
  this.isInTailoring = true;
54
201
  this.movePointIndex = -1;
202
+ this.lastPoint = undefined;
203
+ this.color = color || '#FFF';
204
+ this.customRatio = width / height || 1;
205
+ this.aspectMode = aspectMode in ASPECT_PRESETS || aspectMode === 'custom' ?
206
+ aspectMode : 'custom';
55
207
  const pointList = getRectPoint({x: left, y: top},
56
208
  {x: left + width, y: top + height});
57
209
  this.tailoringPoints = [
58
210
  pointList[0],
59
211
  pointList[2],
60
212
  pointList[6],
61
- pointList[8]];
213
+ pointList[8],
214
+ ];
62
215
  if (now) {
63
216
  this.emitScreenShot();
64
217
  this.stopTailoring();
65
218
  } else {
66
- this.color = color || '#FFF';
67
- this.contents = contents?.length ?
68
- contents :
69
- [];
219
+ this.canvas.style.pointerEvents = 'none';
220
+ this.createToolbar();
70
221
  this.change();
71
222
  }
72
223
  }
73
224
 
225
+ getBounds() {
226
+ const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
227
+ const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
228
+ const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
229
+ const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
230
+ return {
231
+ minX,
232
+ minY,
233
+ maxX,
234
+ maxY,
235
+ width: maxX - minX,
236
+ height: maxY - minY,
237
+ centerX: (minX + maxX) / 2,
238
+ centerY: (minY + maxY) / 2,
239
+ };
240
+ }
241
+
242
+ setBounds(left, top, width, height) {
243
+ const pointList = getRectPoint(
244
+ {x: left, y: top},
245
+ {x: left + width, y: top + height},
246
+ );
247
+ this.tailoringPoints = [
248
+ pointList[0],
249
+ pointList[2],
250
+ pointList[6],
251
+ pointList[8],
252
+ ];
253
+ }
254
+
255
+ getAspectRatioValue() {
256
+ if (this.aspectMode === 'custom') {
257
+ return this.customRatio || 1;
258
+ }
259
+ return ASPECT_PRESETS[this.aspectMode];
260
+ }
261
+
262
+ clampBounds() {
263
+ const bounds = this.getBounds();
264
+ let {minX, minY, width, height} = bounds;
265
+ if (width < MIN_SIZE) {
266
+ width = MIN_SIZE;
267
+ }
268
+ if (height < MIN_SIZE) {
269
+ height = MIN_SIZE;
270
+ }
271
+ if (minX < 0) {
272
+ minX = 0;
273
+ }
274
+ if (minY < 0) {
275
+ minY = 0;
276
+ }
277
+ if (minX + width > this.canvas.width) {
278
+ minX = this.canvas.width - width;
279
+ }
280
+ if (minY + height > this.canvas.height) {
281
+ minY = this.canvas.height - height;
282
+ }
283
+ if (minX < 0) {
284
+ width += minX;
285
+ minX = 0;
286
+ }
287
+ if (minY < 0) {
288
+ height += minY;
289
+ minY = 0;
290
+ }
291
+ this.setBounds(minX, minY, width, height);
292
+ }
293
+
294
+ applyAspectMode(mode) {
295
+ this.aspectMode = mode;
296
+ if (mode === 'custom') {
297
+ const bounds = this.getBounds();
298
+ this.customRatio = bounds.width / bounds.height || 1;
299
+ } else {
300
+ const ratio = ASPECT_PRESETS[mode];
301
+ const bounds = this.getBounds();
302
+ let width = bounds.width;
303
+ let height = width / ratio;
304
+ if (height > this.canvas.height) {
305
+ height = this.canvas.height;
306
+ width = height * ratio;
307
+ }
308
+ if (width > this.canvas.width) {
309
+ width = this.canvas.width;
310
+ height = width / ratio;
311
+ }
312
+ const left = bounds.centerX - width / 2;
313
+ const top = bounds.centerY - height / 2;
314
+ this.setBounds(left, top, width, height);
315
+ this.clampBounds();
316
+ }
317
+ this.updateToolbarState();
318
+ this.change();
319
+ }
320
+
321
+ applyCustomSize(nextWidth, nextHeight, source = 'width') {
322
+ const bounds = this.getBounds();
323
+ let width = bounds.width;
324
+ let height = bounds.height;
325
+ if (this.aspectMode === 'custom') {
326
+ if (source === 'width') {
327
+ width = Math.max(MIN_SIZE, Math.round(nextWidth));
328
+ } else {
329
+ height = Math.max(MIN_SIZE, Math.round(nextHeight));
330
+ }
331
+ } else {
332
+ const ratio = ASPECT_PRESETS[this.aspectMode];
333
+ if (source === 'width') {
334
+ width = Math.max(MIN_SIZE, Math.round(nextWidth));
335
+ height = Math.round(width / ratio);
336
+ } else {
337
+ height = Math.max(MIN_SIZE, Math.round(nextHeight));
338
+ width = Math.round(height * ratio);
339
+ }
340
+ }
341
+ const left = bounds.centerX - width / 2;
342
+ const top = bounds.centerY - height / 2;
343
+ this.setBounds(left, top, width, height);
344
+ this.clampBounds();
345
+ if (this.aspectMode === 'custom') {
346
+ const nextBounds = this.getBounds();
347
+ this.customRatio = nextBounds.width / nextBounds.height || 1;
348
+ }
349
+ this.updateToolbarState();
350
+ this.change();
351
+ }
352
+
353
+ createToolbar() {
354
+ this.destroyToolbar();
355
+ const toolbar = document.createElement('div');
356
+ toolbar.className = 'kfb-tailoring-toolbar';
357
+ toolbar.innerHTML = `
358
+ <div class="kfb-tailoring-toolbar__main">
359
+ <div class="kfb-tailoring-toolbar__ratios">
360
+ <button type="button" data-ratio="16:9">16:9</button>
361
+ <button type="button" data-ratio="4:3">4:3</button>
362
+ <button type="button" data-ratio="1:1">1:1</button>
363
+ <button type="button" data-ratio="custom">${this.translate('custom')}</button>
364
+ </div>
365
+ <div class="kfb-tailoring-toolbar__custom">
366
+ <input type="number" class="kfb-tailoring-width" min="${MIN_SIZE}" step="1" />
367
+ <span>×</span>
368
+ <input type="number" class="kfb-tailoring-height" min="${MIN_SIZE}" step="1" />
369
+ </div>
370
+ </div>
371
+ <div class="kfb-tailoring-toolbar__actions">
372
+ <button type="button" class="kfb-tailoring-toolbar__cancel" title="${this.translate('cancel')}">
373
+ <svg viewBox="0 0 1024 1024"><path d="M768 307.552 716.448 256 512 460.448 307.552 256 256 307.552 460.448 512 256 716.448 307.552 768 512 563.552 716.448 768 768 716.448 563.552 512 768 307.552z"/></svg>
374
+ </button>
375
+ <button type="button" class="kfb-tailoring-toolbar__confirm" title="${this.translate('confirm')}">
376
+ <svg viewBox="0 0 1024 1024"><path d="M836.267 311.467 806.4 281.6c-8.533-8.533-21.333-8.533-29.866 0L435.2 622.933c-4.267 4.267-4.267 4.267-8.534 0L264.533 460.8c-8.533-8.533-21.333-8.533-29.866 0l-29.867 29.867c-8.533 8.533-8.533 21.333 0 29.866l217.6 217.6c4.267 4.267 12.8 4.267 17.067 0l396.8-396.8c8.533-8.533 8.533-21.333 0-29.866z"/></svg>
377
+ </button>
378
+ </div>
379
+ `;
380
+ this.kv.$el.appendChild(toolbar);
381
+ this.toolbarEl = toolbar;
382
+
383
+ toolbar.querySelectorAll('[data-ratio]').forEach((btn) => {
384
+ btn.addEventListener('click', (e) => {
385
+ e.stopPropagation();
386
+ this.applyAspectMode(btn.dataset.ratio);
387
+ });
388
+ });
389
+ toolbar.querySelector('.kfb-tailoring-toolbar__cancel')
390
+ .addEventListener('click', (e) => {
391
+ e.stopPropagation();
392
+ this.stopTailoring();
393
+ });
394
+ toolbar.querySelector('.kfb-tailoring-toolbar__confirm')
395
+ .addEventListener('click', (e) => {
396
+ e.stopPropagation();
397
+ this.emitScreenShot();
398
+ this.stopTailoring();
399
+ });
400
+
401
+ const widthInput = toolbar.querySelector('.kfb-tailoring-width');
402
+ const heightInput = toolbar.querySelector('.kfb-tailoring-height');
403
+ widthInput.addEventListener('change', () => {
404
+ if (this.isUpdatingInputs) return;
405
+ this.applyCustomSize(Number(widthInput.value), 0, 'width');
406
+ });
407
+ heightInput.addEventListener('change', () => {
408
+ if (this.isUpdatingInputs) return;
409
+ this.applyCustomSize(0, Number(heightInput.value), 'height');
410
+ });
411
+ widthInput.addEventListener('keydown', (e) => e.stopPropagation());
412
+ heightInput.addEventListener('keydown', (e) => e.stopPropagation());
413
+
414
+ this.updateToolbarState();
415
+ this.updateToolbarPosition();
416
+ }
417
+
418
+ destroyToolbar() {
419
+ if (this.toolbarEl) {
420
+ this.toolbarEl.remove();
421
+ this.toolbarEl = null;
422
+ }
423
+ }
424
+
425
+ updateToolbarState() {
426
+ if (!this.toolbarEl) return;
427
+ const bounds = this.getBounds();
428
+ this.toolbarEl.querySelectorAll('[data-ratio]').forEach((btn) => {
429
+ btn.classList.toggle('is-active', btn.dataset.ratio === this.aspectMode);
430
+ });
431
+ this.isUpdatingInputs = true;
432
+ const widthInput = this.toolbarEl.querySelector('.kfb-tailoring-width');
433
+ const heightInput = this.toolbarEl.querySelector('.kfb-tailoring-height');
434
+ widthInput.value = Math.round(bounds.width);
435
+ heightInput.value = Math.round(bounds.height);
436
+ this.isUpdatingInputs = false;
437
+ }
438
+
439
+ getSizeLabelBounds() {
440
+ const {minX, minY, width, height} = this.getBounds();
441
+ const text = `${Math.round(width)} × ${Math.round(height)}`;
442
+ const ctx = this.canvas.getContext('2d');
443
+ ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif';
444
+ const padX = 8;
445
+ const labelH = 22;
446
+ const labelW = ctx.measureText(text).width + padX * 2;
447
+ let labelX = minX;
448
+ let labelY = minY - labelH - 5;
449
+ if (labelY < 2) {
450
+ labelY = minY + 5;
451
+ }
452
+ return {
453
+ x: labelX,
454
+ y: labelY,
455
+ width: labelW,
456
+ height: labelH,
457
+ top: labelY,
458
+ bottom: labelY + labelH,
459
+ };
460
+ }
461
+
462
+ updateToolbarPosition() {
463
+ if (!this.toolbarEl) return;
464
+ const {minX, minY, maxY} = this.getBounds();
465
+ const toolbarWidth = TOOLBAR_MIN_WIDTH;
466
+ let left = minX;
467
+ let top = maxY + TOOLBAR_GAP;
468
+ if (left + toolbarWidth > this.canvas.width - 4) {
469
+ left = Math.max(4, this.canvas.width - toolbarWidth - 4);
470
+ }
471
+ if (left < 4) {
472
+ left = 4;
473
+ }
474
+ const labelBounds = this.getSizeLabelBounds();
475
+ const belowTop = maxY + TOOLBAR_GAP;
476
+ const aboveTop = labelBounds.top - TOOLBAR_HEIGHT - TOOLBAR_GAP;
477
+ if (belowTop + TOOLBAR_HEIGHT > this.canvas.height - 4) {
478
+ if (aboveTop >= 4) {
479
+ top = aboveTop;
480
+ } else {
481
+ top = Math.max(4, aboveTop);
482
+ }
483
+ } else {
484
+ top = belowTop;
485
+ }
486
+ this.toolbarEl.style.left = `${left}px`;
487
+ this.toolbarEl.style.top = `${top}px`;
488
+ this.toolbarEl.style.width = `${toolbarWidth}px`;
489
+ }
490
+
74
491
  emitTailoringRegion() {
492
+ const bounds = this.getBounds();
75
493
  this.mitt.$emit(EVENT_TAILORING_REGION, this.viewerElementToImageRectangle({
76
- x: this.tailoringPoints[0].x, y: this.tailoringPoints[0].y,
77
- width: this.tailoringPoints[3].x - this.tailoringPoints[0].x,
78
- height: this.tailoringPoints[3].y - this.tailoringPoints[0].y,
494
+ x: bounds.minX,
495
+ y: bounds.minY,
496
+ width: bounds.width,
497
+ height: bounds.height,
79
498
  }));
80
499
  }
81
500
 
82
- drawRect() {
83
- const ctx = this.canvas.getContext('2d');
84
- ctx.beginPath();
501
+ drawDimOverlay(ctx) {
502
+ const {minX, minY, maxX, maxY} = this.getBounds();
503
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.42)';
504
+ ctx.fillRect(0, 0, this.canvas.width, minY);
505
+ ctx.fillRect(0, minY, minX, maxY - minY);
506
+ ctx.fillRect(maxX, minY, this.canvas.width - maxX, maxY - minY);
507
+ ctx.fillRect(0, maxY, this.canvas.width, this.canvas.height - maxY);
508
+ }
509
+
510
+ drawSelectionFrame(ctx) {
511
+ const {minX, minY, width, height} = this.getBounds();
85
512
  ctx.strokeStyle = this.color;
86
- ctx.lineWidth = 1;
87
- ctx.rect(this.tailoringPoints[0].x, this.tailoringPoints[0].y,
88
- this.tailoringPoints[3].x - this.tailoringPoints[0].x,
89
- this.tailoringPoints[3].y - this.tailoringPoints[0].y);
90
- ctx.stroke();
513
+ ctx.lineWidth = 2;
514
+ ctx.strokeRect(minX + 0.5, minY + 0.5, width - 1, height - 1);
515
+ }
516
+
517
+ drawCornerHandles(ctx) {
518
+ const {minX, minY, maxX, maxY} = this.getBounds();
519
+ const corners = [
520
+ {x: minX, y: minY},
521
+ {x: maxX, y: minY},
522
+ {x: minX, y: maxY},
523
+ {x: maxX, y: maxY},
524
+ ];
525
+ const len = Math.min(
526
+ CORNER_LEN,
527
+ (maxX - minX) / 3,
528
+ (maxY - minY) / 3,
529
+ );
530
+ ctx.strokeStyle = this.color;
531
+ ctx.lineWidth = 3;
532
+ ctx.lineCap = 'square';
533
+ corners.forEach((corner, index) => {
534
+ ctx.beginPath();
535
+ if (index === 0) {
536
+ ctx.moveTo(corner.x, corner.y + len);
537
+ ctx.lineTo(corner.x, corner.y);
538
+ ctx.lineTo(corner.x + len, corner.y);
539
+ } else if (index === 1) {
540
+ ctx.moveTo(corner.x - len, corner.y);
541
+ ctx.lineTo(corner.x, corner.y);
542
+ ctx.lineTo(corner.x, corner.y + len);
543
+ } else if (index === 2) {
544
+ ctx.moveTo(corner.x, corner.y - len);
545
+ ctx.lineTo(corner.x, corner.y);
546
+ ctx.lineTo(corner.x + len, corner.y);
547
+ } else {
548
+ ctx.moveTo(corner.x - len, corner.y);
549
+ ctx.lineTo(corner.x, corner.y);
550
+ ctx.lineTo(corner.x, corner.y - len);
551
+ }
552
+ ctx.stroke();
553
+ });
554
+ }
555
+
556
+ drawSizeLabel(ctx) {
557
+ const labelBounds = this.getSizeLabelBounds();
558
+ const {width, height} = this.getBounds();
559
+ const text = `${Math.round(width)} × ${Math.round(height)}`;
560
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.88)';
561
+ ctx.fillRect(labelBounds.x, labelBounds.y, labelBounds.width, labelBounds.height);
562
+ ctx.fillStyle = '#fff';
563
+ ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif';
564
+ ctx.textBaseline = 'middle';
565
+ ctx.fillText(text, labelBounds.x + 8, labelBounds.y + labelBounds.height / 2 + 0.5);
91
566
  }
92
567
 
93
568
  drawTailoring() {
94
- const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
95
- const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
96
- const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
97
- const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
98
- const leftTop = {x: minX, y: minY};
99
- const rightTop = {x: maxX, y: minY};
100
- const leftBottom = {x: minX, y: maxY};
101
- const rightBottom = {x: maxX, y: maxY};
102
- const distX = maxX - minX > 30 ? 30 : maxX - minX;
103
- const distY = maxY - minY > 30 ? 30 : maxY - minY;
104
- // const deg = this.viewport.getRotation();
105
569
  const ctx = this.canvas.getContext('2d');
106
- ctx.beginPath();
107
- ctx.strokeStyle = this.color;
108
- ctx.lineWidth = 4;
109
- ctx.moveTo(leftTop.x, leftTop.y);
110
- ctx.lineTo(leftTop.x + distX, leftTop.y);
111
- ctx.moveTo(leftTop.x, leftTop.y);
112
- ctx.lineTo(leftTop.x, leftTop.y + distY);
113
-
114
- ctx.moveTo(rightTop.x, rightTop.y);
115
- ctx.lineTo(rightTop.x - distX, rightTop.y);
116
- ctx.moveTo(rightTop.x, rightTop.y);
117
- ctx.lineTo(rightTop.x, rightTop.y + distY);
118
-
119
- ctx.moveTo(leftBottom.x, leftBottom.y);
120
- ctx.lineTo(leftBottom.x + distX, leftBottom.y);
121
- ctx.moveTo(leftBottom.x, leftBottom.y);
122
- ctx.lineTo(leftBottom.x, leftBottom.y - distY);
123
-
124
- ctx.moveTo(rightBottom.x, rightBottom.y);
125
- ctx.lineTo(rightBottom.x - distX, rightBottom.y);
126
- ctx.moveTo(rightBottom.x, rightBottom.y);
127
- ctx.lineTo(rightBottom.x, rightBottom.y - distY);
128
- ctx.stroke();
129
- ctx.beginPath();
570
+ this.drawDimOverlay(ctx);
571
+ this.drawSelectionFrame(ctx);
572
+ this.drawCornerHandles(ctx);
573
+ this.drawEdgeHandles(ctx);
574
+ this.drawSizeLabel(ctx);
575
+ }
576
+
577
+ getEdgeMidpoints() {
578
+ const {minX, minY, maxX, maxY, centerX, centerY} = this.getBounds();
579
+ return [
580
+ {x: centerX, y: minY},
581
+ {x: maxX, y: centerY},
582
+ {x: centerX, y: maxY},
583
+ {x: minX, y: centerY},
584
+ ];
585
+ }
586
+
587
+ drawEdgeHandles(ctx) {
588
+ const edges = this.getEdgeMidpoints();
589
+ const halfLen = Math.min(
590
+ EDGE_HANDLE_LEN / 2,
591
+ this.getBounds().width / 4,
592
+ this.getBounds().height / 4,
593
+ );
130
594
  ctx.fillStyle = this.color;
131
- ctx.font = '18px Arial';
132
- const width = (rightTop.x - leftTop.x).toFixed(2) / 1;
133
- const height = (rightBottom.y - rightTop.y).toFixed(2) / 1;
134
- ctx.fillText(this.contents?.[2] ?
135
- this.contents[2](width, height) :
136
- (`${this.translate('area_size')}${width}*${height},${this.translate('drag_resize')}`), leftTop.x, leftTop.y - 50);
137
- ctx.fillText(this.contents?.[0] ?? `${this.translate('complete_screenshot')}`, leftTop.x, leftTop.y - 30);
138
- ctx.fillText(this.contents?.[1] ?? `${this.translate('cancel_screenshot')}`, leftTop.x, leftTop.y - 10);
139
- ctx.stroke();
140
- ctx.beginPath();
141
- ctx.fillStyle = '#000000';
142
- let areaInterval = 4;
143
- let areaDist = 100;
144
- if (rightTop.x + areaDist + areaInterval > this.canvas.width) {
145
- areaDist = -100;
146
- areaInterval = -4;
147
- }
148
- ctx.rect(rightTop.x + areaInterval, rightTop.y, areaDist, 40);
149
- ctx.fill();
150
- ctx.save();
151
- ctx.beginPath();
152
- ctx.translate(rightTop.x + areaInterval + areaDist / 2, rightTop.y - 5);
153
- ctx.fillStyle = '#ffffff';
154
- ctx.drawConfirm();
155
- ctx.restore();
156
- ctx.save();
157
- ctx.beginPath();
158
- if (areaDist < 0) {
159
- ctx.translate(rightTop.x + areaDist + areaInterval, rightTop.y - 5);
160
- } else {
161
- ctx.translate(rightTop.x + areaInterval, rightTop.y - 5);
595
+ edges.forEach((point, index) => {
596
+ if (index === 0 || index === 2) {
597
+ ctx.fillRect(point.x - halfLen, point.y - 2, halfLen * 2, 4);
598
+ } else {
599
+ ctx.fillRect(point.x - 2, point.y - halfLen, 4, halfLen * 2);
600
+ }
601
+ });
602
+ }
603
+
604
+ getCornerIndex({x, y}) {
605
+ return this.tailoringPoints.findIndex(
606
+ (point) => pointInOtherPoint(point, {x, y}, HANDLE_HIT),
607
+ );
608
+ }
609
+
610
+ getEdgeIndex({x, y}) {
611
+ return this.getEdgeMidpoints().findIndex(
612
+ (point) => pointInOtherPoint(point, {x, y}, HANDLE_HIT),
613
+ );
614
+ }
615
+
616
+ getHandleIndex({x, y}) {
617
+ const corner = this.getCornerIndex({x, y});
618
+ if (corner !== -1) return corner;
619
+ const edge = this.getEdgeIndex({x, y});
620
+ if (edge !== -1) return HANDLE_EDGE_TOP + edge;
621
+ return -1;
622
+ }
623
+
624
+ getHandleCursor(index) {
625
+ const cursors = {
626
+ 0: 'nwse-resize',
627
+ 1: 'nesw-resize',
628
+ 2: 'nesw-resize',
629
+ 3: 'nwse-resize',
630
+ [HANDLE_EDGE_TOP]: 'ns-resize',
631
+ [HANDLE_EDGE_RIGHT]: 'ew-resize',
632
+ [HANDLE_EDGE_BOTTOM]: 'ns-resize',
633
+ [HANDLE_EDGE_LEFT]: 'ew-resize',
634
+ };
635
+ return cursors[index] || 'default';
636
+ }
637
+
638
+ isPointOnToolbar({x, y}) {
639
+ if (!this.toolbarEl) return false;
640
+ const rect = this.toolbarEl.getBoundingClientRect();
641
+ const canvasRect = this.canvas.getBoundingClientRect();
642
+ const localX = rect.left - canvasRect.left;
643
+ const localY = rect.top - canvasRect.top;
644
+ return x >= localX && x <= localX + rect.width &&
645
+ y >= localY && y <= localY + rect.height;
646
+ }
647
+
648
+ isMouseOnToolbar(e) {
649
+ if (!this.toolbarEl) return false;
650
+ const rect = this.toolbarEl.getBoundingClientRect();
651
+ return e.clientX >= rect.left && e.clientX <= rect.right &&
652
+ e.clientY >= rect.top && e.clientY <= rect.bottom;
653
+ }
654
+
655
+ setViewerCursor(cursor) {
656
+ const targets = [
657
+ this.kv.$el,
658
+ this.viewer.container,
659
+ this.viewer.canvas,
660
+ document.getElementById('openseadragon-zrender'),
661
+ ];
662
+ const applyCursor = (el) => {
663
+ if (!el) return;
664
+ el.style.cursor = cursor;
665
+ el.querySelectorAll('canvas').forEach((canvas) => {
666
+ canvas.style.cursor = cursor;
667
+ });
668
+ };
669
+ targets.forEach(applyCursor);
670
+ if (this.canvas) {
671
+ this.canvas.style.cursor = cursor;
162
672
  }
163
- ctx.fillStyle = '#ffffff';
164
- ctx.drawCancel();
165
- ctx.restore();
166
673
  }
167
674
 
168
- /**
169
- * 监听canvas按下事件
170
- * @param {Object} e
171
- * @param {number} e.x
172
- * @param {number} e.y
173
- */
675
+ getCursor({x, y}) {
676
+ if (this.movePointIndex >= 0 && this.movePointIndex <= HANDLE_EDGE_LEFT) {
677
+ return this.getHandleCursor(this.movePointIndex);
678
+ }
679
+ if (this.movePointIndex === MOVE_BODY) {
680
+ return 'grabbing';
681
+ }
682
+ const handle = this.getHandleIndex({x, y});
683
+ if (handle !== -1) {
684
+ return this.getHandleCursor(handle);
685
+ }
686
+ if (this.isPointInMatrix({x, y})) {
687
+ return 'grab';
688
+ }
689
+ return 'default';
690
+ }
691
+
692
+ updateCursor(position) {
693
+ if (!this.isInTailoring) return;
694
+ const {x, y} = position;
695
+ if (this.isPointOnToolbar({x, y})) {
696
+ this.setViewerCursor('default');
697
+ return;
698
+ }
699
+ this.setViewerCursor(this.getCursor({x, y}));
700
+ }
701
+
702
+ onCanvasMove(e) {
703
+ if (!this.isInTailoring) return;
704
+ if (this.isMouseOnToolbar(e)) {
705
+ this.setViewerCursor('default');
706
+ return;
707
+ }
708
+ const rect = this.viewer.canvas.getBoundingClientRect();
709
+ const x = e.clientX - rect.left;
710
+ const y = e.clientY - rect.top;
711
+ this.setViewerCursor(this.getCursor({x, y}));
712
+ }
713
+
174
714
  onCanvasPress({x, y}) {
175
- this.movePointIndex = this.tailoringPoints.findIndex(
176
- (point) => pointInOtherPoint(point, {x, y}, 30));
177
- const rightTopPoint = this.tailoringPoints[1];
178
-
179
- let areaInterval = 4;
180
- let areaDist = 100;
181
- let cancelArea = new Rect(rightTopPoint.x + 14, rightTopPoint.y, 30, 40);
182
- let confirmArea = new Rect(rightTopPoint.x + 64, rightTopPoint.y, 30, 40);
183
- if (rightTopPoint.x + areaDist + areaInterval > this.canvas.width) {
184
- cancelArea = new Rect(rightTopPoint.x - 94, rightTopPoint.y, 30, 40);
185
- confirmArea = new Rect(rightTopPoint.x - 44, rightTopPoint.y, 30, 40);
186
- }
187
- const point = new Point(x, y);
188
- if (cancelArea.containsPoint(point)) {
189
- this.stopTailoring();
715
+ if (this.isPointOnToolbar({x, y})) {
716
+ return;
190
717
  }
191
- if (confirmArea.containsPoint(point)) {
192
- this.emitScreenShot();
193
- this.stopTailoring();
718
+ this.movePointIndex = this.getHandleIndex({x, y});
719
+ if (this.movePointIndex === -1 && this.isPointInMatrix({x, y})) {
720
+ this.movePointIndex = MOVE_BODY;
721
+ }
722
+ this.lastPoint = {x, y};
723
+ if (this.movePointIndex === MOVE_BODY) {
724
+ this.setViewerCursor('grabbing');
725
+ } else if (this.movePointIndex > -1) {
726
+ this.setViewerCursor(this.getHandleCursor(this.movePointIndex));
194
727
  }
195
728
  }
196
729
 
197
- /**
198
- * 监听鼠标释放事件
199
- * @param {Object} e
200
- * @param {number} e.x
201
- * @param {number} e.y
202
- */
203
730
  onCanvasRelease({x, y}) {
731
+ if (this.movePointIndex !== -1 && this.movePointIndex !== MOVE_BODY) {
732
+ const bounds = this.getBounds();
733
+ this.customRatio = bounds.width / bounds.height || 1;
734
+ }
204
735
  this.movePointIndex = -1;
205
736
  this.lastPoint = undefined;
737
+ if (x !== undefined && y !== undefined) {
738
+ this.updateCursor({x, y});
739
+ }
206
740
  }
207
741
 
208
- /**
209
- * 监听鼠标拖动事件
210
- * @param {Object} e
211
- * @param {number} e.x
212
- * @param {number} e.y
213
- */
214
- onCanvasDrag({x, y}) {
215
- if (this.movePointIndex === -1) return;
216
- if (!this.lastPoint) {
217
- this.lastPoint = {x, y};
218
- }
219
- const distX = (x - this.lastPoint.x);
220
- const distY = (y - this.lastPoint.y);
221
- switch (this.movePointIndex) {
742
+ resizeCornerFree(index, distX, distY) {
743
+ switch (index) {
222
744
  case 0:
223
745
  this.tailoringPoints[0] = {
224
746
  x: this.tailoringPoints[0].x + distX,
@@ -275,32 +797,174 @@ export class Tailoring extends ViewerCommon {
275
797
  y: this.tailoringPoints[3].y + distY,
276
798
  };
277
799
  break;
800
+ default:
801
+ break;
278
802
  }
279
- this.tailoringPoints.forEach((i) => {
280
- if (i.x < 0) {
281
- i.x = 0;
803
+ this.clampBounds();
804
+ }
805
+
806
+ resizeCornerLocked(index, distX, distY) {
807
+ const ratio = this.getAspectRatioValue();
808
+ const bounds = this.getBounds();
809
+ const dominant = Math.abs(distX) >= Math.abs(distY) ? distX : distY * ratio;
810
+ let {minX, minY, maxX, maxY} = bounds;
811
+ let width = bounds.width;
812
+ let height = bounds.height;
813
+
814
+ switch (index) {
815
+ case 0:
816
+ width = Math.max(MIN_SIZE, width - dominant);
817
+ height = width / ratio;
818
+ minX = maxX - width;
819
+ minY = maxY - height;
820
+ break;
821
+ case 1:
822
+ width = Math.max(MIN_SIZE, width + dominant);
823
+ height = width / ratio;
824
+ minY = maxY - height;
825
+ maxX = minX + width;
826
+ break;
827
+ case 2:
828
+ width = Math.max(MIN_SIZE, width - dominant);
829
+ height = width / ratio;
830
+ minX = maxX - width;
831
+ maxY = minY + height;
832
+ break;
833
+ case 3:
834
+ width = Math.max(MIN_SIZE, width + dominant);
835
+ height = width / ratio;
836
+ maxX = minX + width;
837
+ maxY = minY + height;
838
+ break;
839
+ default:
840
+ return;
841
+ }
842
+ this.setBounds(minX, minY, width, height);
843
+ this.clampBounds();
844
+ }
845
+
846
+ resizeEdgeFree(index, distX, distY) {
847
+ const bounds = this.getBounds();
848
+ switch (index) {
849
+ case HANDLE_EDGE_TOP: {
850
+ let minY = bounds.minY + distY;
851
+ let height = bounds.maxY - minY;
852
+ if (height < MIN_SIZE) {
853
+ minY = bounds.maxY - MIN_SIZE;
854
+ height = MIN_SIZE;
855
+ }
856
+ this.setBounds(bounds.minX, minY, bounds.width, height);
857
+ break;
282
858
  }
283
- if (i.y < 0) {
284
- i.y = 0;
859
+ case HANDLE_EDGE_BOTTOM: {
860
+ const height = Math.max(MIN_SIZE, bounds.height + distY);
861
+ this.setBounds(bounds.minX, bounds.minY, bounds.width, height);
862
+ break;
285
863
  }
286
- if (i.x > this.canvas.width) {
287
- i.x = this.canvas.width;
864
+ case HANDLE_EDGE_LEFT: {
865
+ let minX = bounds.minX + distX;
866
+ let width = bounds.maxX - minX;
867
+ if (width < MIN_SIZE) {
868
+ minX = bounds.maxX - MIN_SIZE;
869
+ width = MIN_SIZE;
870
+ }
871
+ this.setBounds(minX, bounds.minY, width, bounds.height);
872
+ break;
288
873
  }
289
- if (i.y > this.canvas.height) {
290
- i.y = this.canvas.height;
874
+ case HANDLE_EDGE_RIGHT: {
875
+ const width = Math.max(MIN_SIZE, bounds.width + distX);
876
+ this.setBounds(bounds.minX, bounds.minY, width, bounds.height);
877
+ break;
291
878
  }
292
- });
879
+ default:
880
+ break;
881
+ }
882
+ this.clampBounds();
883
+ }
884
+
885
+ resizeEdgeLocked(index, distX, distY) {
886
+ const ratio = this.getAspectRatioValue();
887
+ const bounds = this.getBounds();
888
+ switch (index) {
889
+ case HANDLE_EDGE_TOP: {
890
+ const height = Math.max(MIN_SIZE, bounds.height - distY);
891
+ const width = height * ratio;
892
+ const left = bounds.centerX - width / 2;
893
+ const top = bounds.maxY - height;
894
+ this.setBounds(left, top, width, height);
895
+ break;
896
+ }
897
+ case HANDLE_EDGE_BOTTOM: {
898
+ const height = Math.max(MIN_SIZE, bounds.height + distY);
899
+ const width = height * ratio;
900
+ const left = bounds.centerX - width / 2;
901
+ this.setBounds(left, bounds.minY, width, height);
902
+ break;
903
+ }
904
+ case HANDLE_EDGE_LEFT: {
905
+ const width = Math.max(MIN_SIZE, bounds.width - distX);
906
+ const height = width / ratio;
907
+ const top = bounds.centerY - height / 2;
908
+ const left = bounds.maxX - width;
909
+ this.setBounds(left, top, width, height);
910
+ break;
911
+ }
912
+ case HANDLE_EDGE_RIGHT: {
913
+ const width = Math.max(MIN_SIZE, bounds.width + distX);
914
+ const height = width / ratio;
915
+ const top = bounds.centerY - height / 2;
916
+ this.setBounds(bounds.minX, top, width, height);
917
+ break;
918
+ }
919
+ default:
920
+ break;
921
+ }
922
+ this.clampBounds();
923
+ }
924
+
925
+ moveBody(distX, distY) {
926
+ const bounds = this.getBounds();
927
+ let left = bounds.minX + distX;
928
+ let top = bounds.minY + distY;
929
+ left = Math.max(0, Math.min(left, this.canvas.width - bounds.width));
930
+ top = Math.max(0, Math.min(top, this.canvas.height - bounds.height));
931
+ this.setBounds(left, top, bounds.width, bounds.height);
932
+ }
933
+
934
+ onCanvasDrag({x, y}) {
935
+ if (this.movePointIndex === -1) return;
936
+ if (!this.lastPoint) {
937
+ this.lastPoint = {x, y};
938
+ return;
939
+ }
940
+ const distX = x - this.lastPoint.x;
941
+ const distY = y - this.lastPoint.y;
942
+ if (this.movePointIndex === MOVE_BODY) {
943
+ this.moveBody(distX, distY);
944
+ } else if (this.movePointIndex >= HANDLE_EDGE_TOP) {
945
+ if (this.aspectMode === 'custom') {
946
+ this.resizeEdgeFree(this.movePointIndex, distX, distY);
947
+ const bounds = this.getBounds();
948
+ this.customRatio = bounds.width / bounds.height || 1;
949
+ } else {
950
+ this.resizeEdgeLocked(this.movePointIndex, distX, distY);
951
+ }
952
+ } else if (this.aspectMode === 'custom') {
953
+ this.resizeCornerFree(this.movePointIndex, distX, distY);
954
+ const bounds = this.getBounds();
955
+ this.customRatio = bounds.width / bounds.height || 1;
956
+ } else {
957
+ this.resizeCornerLocked(this.movePointIndex, distX, distY);
958
+ }
293
959
  this.clearCanvas();
294
- this.drawRect();
295
960
  this.drawTailoring();
961
+ this.updateToolbarState();
962
+ this.updateToolbarPosition();
296
963
  this.lastPoint = {x, y};
964
+ this.updateCursor({x, y});
297
965
  this.emitTailoringRegion();
298
966
  }
299
967
 
300
- /**
301
- * 监听鼠标按钮事件
302
- * @param {Object} e
303
- */
304
968
  onCanvasKey(e) {
305
969
  const {originalEvent} = e;
306
970
  if (originalEvent.key === 'Escape') {
@@ -309,49 +973,45 @@ export class Tailoring extends ViewerCommon {
309
973
  }
310
974
 
311
975
  onCanvasDblClick(position) {
976
+ if (this.isPointOnToolbar(position)) {
977
+ return;
978
+ }
312
979
  if (this.tailoringPoints.length > 0 && this.isPointInMatrix(position)) {
313
980
  this.emitScreenShot();
981
+ this.stopTailoring();
982
+ } else {
983
+ this.stopTailoring();
314
984
  }
315
- this.stopTailoring();
316
985
  }
317
986
 
318
987
  emitScreenShot() {
319
988
  this.emitTailoringRegion();
989
+ const bounds = this.getBounds();
320
990
  this.mitt.$emit(EVENT_TAILORING_SCREENSHOT, {
321
991
  base64: this.getScreenCanvasImage(),
322
992
  region: this.viewerElementToImageRectangle({
323
- x: this.tailoringPoints[0].x, y: this.tailoringPoints[0].y,
324
- width: this.tailoringPoints[3].x - this.tailoringPoints[0].x,
325
- height: this.tailoringPoints[3].y - this.tailoringPoints[0].y,
993
+ x: bounds.minX,
994
+ y: bounds.minY,
995
+ width: bounds.width,
996
+ height: bounds.height,
326
997
  }),
327
998
  });
328
999
  }
329
1000
 
330
- /**
331
- * 判断鼠标在区域内
332
- * @param {Object} point 鼠标位置
333
- * @param {number} point.x 鼠标x位置
334
- * @param {number} point.y 鼠标y位置
335
- * @return {boolean}
336
- */
337
1001
  isPointInMatrix(point) {
338
- const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
339
- const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
340
- const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
341
- const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
342
- const point1 = {x: minX, y: minY};
343
- const point2 = {x: maxX, y: minY};
344
- const point3 = {x: minX, y: maxY};
345
- const point4 = {x: maxX, y: maxY};
346
- return isPointInMatrix(point1, point2, point3, point4, point);
1002
+ const {minX, minY, maxX, maxY} = this.getBounds();
1003
+ return isPointInMatrix(
1004
+ {x: minX, y: minY},
1005
+ {x: maxX, y: minY},
1006
+ {x: minX, y: maxY},
1007
+ {x: maxX, y: maxY},
1008
+ point,
1009
+ );
347
1010
  }
348
1011
 
349
1012
  getScreenCanvasImage() {
350
1013
  const canvas = this.viewer.drawer.canvas;
351
- const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
352
- const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
353
- const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
354
- const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
1014
+ const {minX, minY, maxX, maxY} = this.getBounds();
355
1015
  const startPoint = {x: minX, y: minY};
356
1016
  const endPoint = {x: maxX, y: maxY};
357
1017
  const ctx = canvas.getContext('2d');
@@ -361,6 +1021,8 @@ export class Tailoring extends ViewerCommon {
361
1021
  const radioY = canvasHeight / height;
362
1022
  const disWidth = Math.abs(endPoint.x - startPoint.x) * radioX;
363
1023
  const disHeight = Math.abs(endPoint.y - startPoint.y) * radioY;
1024
+ const shapeSrcW = Math.abs(endPoint.x - startPoint.x);
1025
+ const shapeSrcH = Math.abs(endPoint.y - startPoint.y);
364
1026
  if (disWidth.toFixed(5) / 1 === 0 || disHeight.toFixed(5) / 1 === 0) {
365
1027
  return;
366
1028
  }
@@ -385,9 +1047,8 @@ export class Tailoring extends ViewerCommon {
385
1047
  copyCtx.putImageData(imageData, 0, 0, 0, 0, disWidth, disHeight);
386
1048
  const shapeCanvas = this.kv.shape?.canvas;
387
1049
  if (shapeCanvas && this.options.shape) {
388
- copyCtx.drawImage(shapeCanvas, startPoint.x * radioX,
389
- startPoint.y * radioY, disWidth, disHeight, 0, 0, disWidth,
390
- disHeight);
1050
+ copyCtx.drawImage(shapeCanvas, startPoint.x, startPoint.y, shapeSrcW,
1051
+ shapeSrcH, 0, 0, disWidth, disHeight);
391
1052
  }
392
1053
  if (copyCanvas.width > this.options.resolution) {
393
1054
  const maxCanvas = document.createElement('canvas');
@@ -401,10 +1062,9 @@ export class Tailoring extends ViewerCommon {
401
1062
  0, 0, maxCanvas.width, maxCanvas.height);
402
1063
  return maxCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
403
1064
  this.options.quality);
404
- } else {
405
- return copyCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
406
- this.options.quality);
407
1065
  }
1066
+ return copyCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
1067
+ this.options.quality);
408
1068
  } catch (e) {
409
1069
  console.error(e);
410
1070
  }
@@ -412,17 +1072,23 @@ export class Tailoring extends ViewerCommon {
412
1072
 
413
1073
  stopTailoring() {
414
1074
  this.clearCanvas();
1075
+ this.destroyToolbar();
415
1076
  this.tailoringPoints = [];
416
1077
  this.isInTailoring = false;
417
1078
  this.movePointIndex = -1;
418
1079
  this.lastPoint = undefined;
1080
+ if (this.canvas) {
1081
+ this.canvas.style.pointerEvents = '';
1082
+ }
1083
+ this.setViewerCursor('default');
419
1084
  }
420
1085
 
421
1086
  change() {
422
1087
  if (!this.isInTailoring) return;
423
1088
  this.clearCanvas();
424
1089
  this.emitTailoringRegion();
425
- this.drawRect();
426
1090
  this.drawTailoring();
1091
+ this.updateToolbarState();
1092
+ this.updateToolbarPosition();
427
1093
  }
428
1094
  }