uview-plus 3.8.86 → 3.8.108
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.
- package/changelog.md +173 -3
- package/components/u-action-sheet/u-action-sheet.vue +21 -2
- package/components/u-barcode/u-barcode.vue +0 -2
- package/components/u-button/u-button.vue +2 -5
- package/components/u-canvas/u-canvas.vue +430 -77
- package/components/u-cell/u-cell.vue +8 -0
- package/components/u-datetime-picker/u-datetime-picker.vue +53 -4
- package/components/u-dragsort/u-dragsort.vue +55 -31
- package/components/u-icon/u-icon.vue +12 -3
- package/components/u-icon/util.js +81 -10
- package/components/u-index-item/u-index-item.vue +1 -1
- package/components/u-index-list/u-index-list.vue +1 -1
- package/components/u-novel-reader/content-normalizer.js +80 -0
- package/components/u-novel-reader/layout-engine.js +225 -0
- package/components/u-novel-reader/measure-adapter.js +69 -0
- package/components/u-novel-reader/novelReader.js +35 -0
- package/components/u-novel-reader/persistence.js +144 -0
- package/components/u-novel-reader/props.js +100 -0
- package/components/u-novel-reader/reader-catalog.vue +234 -0
- package/components/u-novel-reader/reader-content.vue +226 -0
- package/components/u-novel-reader/reader-core.js +151 -0
- package/components/u-novel-reader/reader-settings.vue +325 -0
- package/components/u-novel-reader/reader-toolbar.vue +313 -0
- package/components/u-novel-reader/theme-vars.scss +49 -0
- package/components/u-novel-reader/u-novel-reader.vue +836 -0
- package/components/u-poster/u-poster.vue +272 -222
- package/components/u-qrcode/qrcode.js +56 -49
- package/components/u-qrcode/u-qrcode.vue +159 -88
- package/components/u-scroll-list/scrollWxs.wxs +1 -1
- package/components/u-slider/u-slider.vue +1 -1
- package/components/u-swipe-action-item/index.wxs +43 -25
- package/components/u-swipe-action-item/nvue.js +9 -0
- package/components/u-swipe-action-item/other.js +32 -21
- package/components/u-swipe-action-item/props.js +5 -0
- package/components/u-swipe-action-item/swipeActionItem.js +1 -0
- package/components/u-swipe-action-item/u-swipe-action-item.vue +28 -4
- package/components/u-switch/u-switch.vue +1 -1
- package/components/u-tabbar/u-tabbar.vue +2 -1
- package/components/u-tabbar-item/u-tabbar-item.vue +64 -21
- package/components/u-tabs/u-tabs.vue +11 -8
- package/components/u-tabs-pro/u-tabs-pro.vue +212 -0
- package/components/u-text/text.js +1 -1
- package/components/u-text/u-text.vue +18 -6
- package/components/u-upload/u-upload.vue +1 -1
- package/components/u-waterfall/u-waterfall.vue +118 -65
- package/libs/config/config.js +1 -1
- package/libs/config/props.js +1 -0
- package/libs/function/index.js +66 -0
- package/libs/mixin/mixin.js +4 -50
- package/libs/root/index.js +78 -5
- package/libs/root/page.js +7 -7
- package/libs/root/root.js +73 -3
- package/libs/util/app-nvue-webview-canvas.js +486 -0
- package/libs/util/gcanvas/bridge/bridge-weex.js +0 -2
- package/libs/util/gcanvas/index.js +3 -3
- package/package.json +2 -2
- package/types/comps/swipeActionItem.d.ts +13 -0
- package/types/comps/tabs.d.ts +2 -1
- package/types/comps/text.d.ts +1 -1
- package/types/func.d.ts +23 -0
- package/types/index.d.ts +1 -0
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
/>
|
|
22
22
|
<!-- #endif -->
|
|
23
23
|
|
|
24
|
-
<!-- #ifdef APP-PLUS -->
|
|
24
|
+
<!-- #ifdef APP-PLUS || APP-HARMONY -->
|
|
25
|
+
<!-- #ifndef APP-NVUE -->
|
|
25
26
|
<canvas
|
|
26
27
|
class="u-canvas__canvas"
|
|
27
28
|
:id="canvasId"
|
|
@@ -33,15 +34,15 @@
|
|
|
33
34
|
@touchend="onTouchEnd"
|
|
34
35
|
/>
|
|
35
36
|
<!-- #endif -->
|
|
37
|
+
<!-- #endif -->
|
|
36
38
|
|
|
37
39
|
<!-- #ifdef APP-NVUE -->
|
|
38
|
-
<
|
|
40
|
+
<web-view
|
|
39
41
|
class="u-canvas__canvas"
|
|
40
|
-
ref="
|
|
42
|
+
ref="web"
|
|
43
|
+
src="/static/app-plus/up-canvas/local.html"
|
|
41
44
|
:style="{ width: actualWidth + unit, height: actualHeight + unit }"
|
|
42
|
-
@
|
|
43
|
-
@touchmove="onTouchMove"
|
|
44
|
-
@touchend="onTouchEnd"
|
|
45
|
+
@onPostMessage="onWebViewMessage"
|
|
45
46
|
/>
|
|
46
47
|
<!-- #endif -->
|
|
47
48
|
</view>
|
|
@@ -50,10 +51,9 @@
|
|
|
50
51
|
<script>
|
|
51
52
|
// #ifdef APP-NVUE
|
|
52
53
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} from '../../libs/util/gcanvas/index.js';
|
|
54
|
+
createWebViewCanvasBridge,
|
|
55
|
+
createWebViewCanvasContext
|
|
56
|
+
} from '../../libs/util/app-nvue-webview-canvas.js';
|
|
57
57
|
// #endif
|
|
58
58
|
|
|
59
59
|
export default {
|
|
@@ -125,13 +125,27 @@ export default {
|
|
|
125
125
|
this._selectorResult = null;
|
|
126
126
|
this._canvasElement = null;
|
|
127
127
|
this._imageCache = Object.create(null);
|
|
128
|
+
this._imageDataCache = Object.create(null);
|
|
128
129
|
this._isNvue = false;
|
|
130
|
+
this._initPromise = null;
|
|
131
|
+
this._webViewBridge = null;
|
|
132
|
+
this._webViewContext = null;
|
|
133
|
+
this._webViewInitSignature = null;
|
|
134
|
+
this._webViewSessionId = null;
|
|
129
135
|
},
|
|
130
136
|
mounted() {
|
|
131
137
|
this.$nextTick(() => {
|
|
132
138
|
this.initCanvas();
|
|
133
139
|
});
|
|
134
140
|
},
|
|
141
|
+
beforeUnmount() {
|
|
142
|
+
if (this._webViewBridge) {
|
|
143
|
+
this._webViewBridge.destroy();
|
|
144
|
+
}
|
|
145
|
+
this._webViewBridge = null;
|
|
146
|
+
this._webViewContext = null;
|
|
147
|
+
this.ctx = null;
|
|
148
|
+
},
|
|
135
149
|
methods: {
|
|
136
150
|
parseSize(value) {
|
|
137
151
|
if (typeof value === 'number') {
|
|
@@ -157,22 +171,29 @@ export default {
|
|
|
157
171
|
onTouchEnd(event) {
|
|
158
172
|
this.$emit('touchend', event);
|
|
159
173
|
},
|
|
174
|
+
onWebViewMessage(event) {
|
|
175
|
+
if (this._webViewBridge) {
|
|
176
|
+
this._webViewBridge.handleMessage(event);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
onWebViewTouch(message) {
|
|
180
|
+
const eventType = message.eventType;
|
|
181
|
+
if (!['touchstart', 'touchmove', 'touchend'].includes(eventType)) return;
|
|
182
|
+
this.$emit(eventType, {
|
|
183
|
+
type: eventType,
|
|
184
|
+
detail: {
|
|
185
|
+
x: message.x,
|
|
186
|
+
y: message.y
|
|
187
|
+
},
|
|
188
|
+
touches: message.touches || [],
|
|
189
|
+
changedTouches: message.changedTouches || [],
|
|
190
|
+
canvasWidth: message.canvasWidth,
|
|
191
|
+
canvasHeight: message.canvasHeight
|
|
192
|
+
});
|
|
193
|
+
},
|
|
160
194
|
async getCanvasNode(id = this.canvasId, isCanvas = true) {
|
|
161
195
|
return new Promise((resolve) => {
|
|
162
196
|
try {
|
|
163
|
-
// #ifdef APP-NVUE
|
|
164
|
-
setTimeout(() => {
|
|
165
|
-
const gcanvas = this.$refs.gcanvas;
|
|
166
|
-
if (!gcanvas) {
|
|
167
|
-
resolve(false);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
this._isNvue = true;
|
|
171
|
-
resolve(enable(gcanvas, { bridge: WeexBridge }));
|
|
172
|
-
}, 100);
|
|
173
|
-
// #endif
|
|
174
|
-
|
|
175
|
-
// #ifndef APP-NVUE
|
|
176
197
|
uni.createSelectorQuery()
|
|
177
198
|
.in(this)
|
|
178
199
|
.select(`#${id}`)
|
|
@@ -182,11 +203,18 @@ export default {
|
|
|
182
203
|
size: true
|
|
183
204
|
},
|
|
184
205
|
(res) => {
|
|
206
|
+
// #ifdef APP-PLUS || APP-HARMONY
|
|
207
|
+
resolve(res || {
|
|
208
|
+
width: this.actualWidth,
|
|
209
|
+
height: this.actualHeight
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
// #endif
|
|
213
|
+
|
|
185
214
|
resolve(res || false);
|
|
186
215
|
}
|
|
187
216
|
)
|
|
188
217
|
.exec();
|
|
189
|
-
// #endif
|
|
190
218
|
} catch (error) {
|
|
191
219
|
console.error('获取画布节点失败:', error);
|
|
192
220
|
resolve(false);
|
|
@@ -200,14 +228,12 @@ export default {
|
|
|
200
228
|
return this.ctx;
|
|
201
229
|
},
|
|
202
230
|
getCanvasContext() {
|
|
203
|
-
// #ifdef APP-
|
|
204
|
-
return
|
|
231
|
+
// #ifdef APP-NVUE
|
|
232
|
+
return this._webViewContext;
|
|
205
233
|
// #endif
|
|
206
234
|
|
|
207
|
-
// #ifdef APP-
|
|
208
|
-
return this.
|
|
209
|
-
? this._canvasElement.getContext('2d')
|
|
210
|
-
: null;
|
|
235
|
+
// #ifdef APP-PLUS || APP-HARMONY
|
|
236
|
+
return uni.createCanvasContext(this.canvasId, this);
|
|
211
237
|
// #endif
|
|
212
238
|
|
|
213
239
|
// #ifdef MP || H5
|
|
@@ -229,10 +255,32 @@ export default {
|
|
|
229
255
|
}
|
|
230
256
|
},
|
|
231
257
|
async initCanvas(force = false) {
|
|
258
|
+
if (this._initPromise) {
|
|
259
|
+
return this._initPromise;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const initPromise = this._initializeCanvas(force);
|
|
263
|
+
this._initPromise = initPromise;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
return await initPromise;
|
|
267
|
+
} finally {
|
|
268
|
+
if (this._initPromise === initPromise) {
|
|
269
|
+
this._initPromise = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
async _initializeCanvas(force = false) {
|
|
232
274
|
try {
|
|
233
275
|
if (this.useRootHeightAndWidth) {
|
|
234
276
|
await this.setNewSize();
|
|
235
277
|
}
|
|
278
|
+
// #ifdef APP-NVUE
|
|
279
|
+
if (typeof createWebViewCanvasBridge === 'function') {
|
|
280
|
+
return await this._initializeWebViewCanvas(force);
|
|
281
|
+
}
|
|
282
|
+
// #endif
|
|
283
|
+
|
|
236
284
|
if (this.ctx && !force) {
|
|
237
285
|
this.$emit('ready', {
|
|
238
286
|
width: this.actualWidth,
|
|
@@ -247,10 +295,17 @@ export default {
|
|
|
247
295
|
}
|
|
248
296
|
|
|
249
297
|
this._selectorResult = this._canvasNode;
|
|
250
|
-
this._canvasElement = this.
|
|
251
|
-
|
|
298
|
+
this._canvasElement = this._isNvue
|
|
299
|
+
? this._canvasNode
|
|
300
|
+
: (this._canvasNode.node || null);
|
|
301
|
+
const systemInfo = uni.getSystemInfoSync() || {};
|
|
302
|
+
const pixelRatio = Number(systemInfo.pixelRatio) || 1;
|
|
303
|
+
// 微信开发者工具的模拟器预览通常按高密度屏幕缩放,至少使用 2 倍 backing store。
|
|
304
|
+
this.dpr = systemInfo.platform === 'devtools'
|
|
305
|
+
? Math.max(pixelRatio, 2)
|
|
306
|
+
: pixelRatio;
|
|
252
307
|
|
|
253
|
-
// #ifdef MP
|
|
308
|
+
// #ifdef MP
|
|
254
309
|
if (this._canvasElement) {
|
|
255
310
|
this._canvasElement.width = Math.ceil(this.actualWidth * this.dpr);
|
|
256
311
|
this._canvasElement.height = Math.ceil(this.actualHeight * this.dpr);
|
|
@@ -262,7 +317,7 @@ export default {
|
|
|
262
317
|
return false;
|
|
263
318
|
}
|
|
264
319
|
|
|
265
|
-
// #ifdef MP
|
|
320
|
+
// #ifdef MP
|
|
266
321
|
if (typeof this.ctx.setTransform === 'function') {
|
|
267
322
|
this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
|
|
268
323
|
} else if (typeof this.ctx.scale === 'function') {
|
|
@@ -282,6 +337,64 @@ export default {
|
|
|
282
337
|
return false;
|
|
283
338
|
}
|
|
284
339
|
},
|
|
340
|
+
async _initializeWebViewCanvas(force = false) {
|
|
341
|
+
this._isNvue = true;
|
|
342
|
+
const systemInfo = uni.getSystemInfoSync() || {};
|
|
343
|
+
this.dpr = Math.max(1, Number(systemInfo.pixelRatio) || 1);
|
|
344
|
+
|
|
345
|
+
if (!this._webViewBridge) {
|
|
346
|
+
this._webViewBridge = createWebViewCanvasBridge({
|
|
347
|
+
getWebView: () => this.$refs.web,
|
|
348
|
+
onTouch: message => this.onWebViewTouch(message),
|
|
349
|
+
onReady: (message) => {
|
|
350
|
+
if (
|
|
351
|
+
this._webViewSessionId &&
|
|
352
|
+
message.sessionId &&
|
|
353
|
+
this._webViewSessionId !== message.sessionId
|
|
354
|
+
) {
|
|
355
|
+
this._webViewInitSignature = null;
|
|
356
|
+
}
|
|
357
|
+
this._webViewSessionId = message.sessionId || null;
|
|
358
|
+
},
|
|
359
|
+
onError: error => console.error('Canvas WebView错误:', error)
|
|
360
|
+
});
|
|
361
|
+
this._webViewContext = createWebViewCanvasContext({
|
|
362
|
+
bridge: this._webViewBridge,
|
|
363
|
+
resolveImage: source => this.resolveNvueImageSource(source),
|
|
364
|
+
measureText: (text, state) => {
|
|
365
|
+
const matched = String(state.font || '').match(/(\d+(?:\.\d+)?)px/);
|
|
366
|
+
const fontSize = matched ? Number(matched[1]) : this.fontSize;
|
|
367
|
+
return { width: this.estimateTextWidth(text, fontSize) };
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
this.ctx = this._webViewContext;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
await this._webViewBridge.ready();
|
|
374
|
+
const signature = [
|
|
375
|
+
this.actualWidth,
|
|
376
|
+
this.actualHeight,
|
|
377
|
+
this.dpr,
|
|
378
|
+
this.disableScroll ? 1 : 0
|
|
379
|
+
].join(':');
|
|
380
|
+
if (force || this._webViewInitSignature !== signature) {
|
|
381
|
+
await this._webViewBridge.request('init', {
|
|
382
|
+
width: this.actualWidth,
|
|
383
|
+
height: this.actualHeight,
|
|
384
|
+
dpr: this.dpr,
|
|
385
|
+
disableScroll: this.disableScroll
|
|
386
|
+
});
|
|
387
|
+
this._webViewInitSignature = signature;
|
|
388
|
+
this.applyFont();
|
|
389
|
+
await this.clearCanvas();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
this.$emit('ready', {
|
|
393
|
+
width: this.actualWidth,
|
|
394
|
+
height: this.actualHeight
|
|
395
|
+
});
|
|
396
|
+
return true;
|
|
397
|
+
},
|
|
285
398
|
refresh() {
|
|
286
399
|
return this.initCanvas(true);
|
|
287
400
|
},
|
|
@@ -300,7 +413,7 @@ export default {
|
|
|
300
413
|
this.setFillStyle(this.bgColor);
|
|
301
414
|
this.fill();
|
|
302
415
|
}
|
|
303
|
-
this.draw();
|
|
416
|
+
return this.draw();
|
|
304
417
|
},
|
|
305
418
|
callContext(method, ...args) {
|
|
306
419
|
if (this.ctx && typeof this.ctx[method] === 'function') {
|
|
@@ -341,12 +454,28 @@ export default {
|
|
|
341
454
|
arc(x, y, radius, startAngle, endAngle, anticlockwise = false) {
|
|
342
455
|
return this.callContext('arc', x, y, radius, startAngle, endAngle, anticlockwise);
|
|
343
456
|
},
|
|
457
|
+
arcTo(x1, y1, x2, y2, radius) {
|
|
458
|
+
return this.callContext('arcTo', x1, y1, x2, y2, radius);
|
|
459
|
+
},
|
|
344
460
|
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
|
|
345
461
|
return this.callContext('bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y);
|
|
346
462
|
},
|
|
347
463
|
quadraticCurveTo(cpx, cpy, x, y) {
|
|
348
464
|
return this.callContext('quadraticCurveTo', cpx, cpy, x, y);
|
|
349
465
|
},
|
|
466
|
+
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise = false) {
|
|
467
|
+
return this.callContext(
|
|
468
|
+
'ellipse',
|
|
469
|
+
x,
|
|
470
|
+
y,
|
|
471
|
+
radiusX,
|
|
472
|
+
radiusY,
|
|
473
|
+
rotation,
|
|
474
|
+
startAngle,
|
|
475
|
+
endAngle,
|
|
476
|
+
anticlockwise
|
|
477
|
+
);
|
|
478
|
+
},
|
|
350
479
|
clip() {
|
|
351
480
|
return this.callContext('clip');
|
|
352
481
|
},
|
|
@@ -365,6 +494,15 @@ export default {
|
|
|
365
494
|
scale(x, y) {
|
|
366
495
|
return this.callContext('scale', x, y);
|
|
367
496
|
},
|
|
497
|
+
setTransform(a, b, c, d, e, f) {
|
|
498
|
+
return this.callContext('setTransform', a, b, c, d, e, f);
|
|
499
|
+
},
|
|
500
|
+
transform(a, b, c, d, e, f) {
|
|
501
|
+
return this.callContext('transform', a, b, c, d, e, f);
|
|
502
|
+
},
|
|
503
|
+
resetTransform() {
|
|
504
|
+
return this.callContext('resetTransform');
|
|
505
|
+
},
|
|
368
506
|
setFillStyle(color) {
|
|
369
507
|
if (!this.ctx) return;
|
|
370
508
|
if (typeof this.ctx.setFillStyle === 'function') {
|
|
@@ -405,6 +543,21 @@ export default {
|
|
|
405
543
|
this.ctx.lineJoin = lineJoin;
|
|
406
544
|
}
|
|
407
545
|
},
|
|
546
|
+
setMiterLimit(miterLimit) {
|
|
547
|
+
if (!this.ctx) return;
|
|
548
|
+
if (typeof this.ctx.setMiterLimit === 'function') {
|
|
549
|
+
this.ctx.setMiterLimit(miterLimit);
|
|
550
|
+
} else {
|
|
551
|
+
this.ctx.miterLimit = miterLimit;
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
setLineDash(segments = []) {
|
|
555
|
+
return this.callContext('setLineDash', segments);
|
|
556
|
+
},
|
|
557
|
+
getLineDash() {
|
|
558
|
+
const result = this.callContext('getLineDash');
|
|
559
|
+
return Array.isArray(result) ? result : [];
|
|
560
|
+
},
|
|
408
561
|
setTextAlign(align = 'left') {
|
|
409
562
|
if (!this.ctx) return;
|
|
410
563
|
if (typeof this.ctx.setTextAlign === 'function') {
|
|
@@ -448,6 +601,14 @@ export default {
|
|
|
448
601
|
this.ctx.globalAlpha = alpha;
|
|
449
602
|
}
|
|
450
603
|
},
|
|
604
|
+
setGlobalCompositeOperation(operation) {
|
|
605
|
+
if (!this.ctx) return;
|
|
606
|
+
if (typeof this.ctx.setGlobalCompositeOperation === 'function') {
|
|
607
|
+
this.ctx.setGlobalCompositeOperation(operation);
|
|
608
|
+
} else {
|
|
609
|
+
this.ctx.globalCompositeOperation = operation;
|
|
610
|
+
}
|
|
611
|
+
},
|
|
451
612
|
setShadow(offsetX = 0, offsetY = 0, blur = 0, color = 'rgba(0,0,0,0)') {
|
|
452
613
|
if (!this.ctx) return;
|
|
453
614
|
if (typeof this.ctx.setShadow === 'function') {
|
|
@@ -479,14 +640,85 @@ export default {
|
|
|
479
640
|
fillText(text, x, y) {
|
|
480
641
|
return this.callContext('fillText', String(text), x, y);
|
|
481
642
|
},
|
|
643
|
+
strokeText(text, x, y, maxWidth) {
|
|
644
|
+
if (maxWidth === undefined) {
|
|
645
|
+
return this.callContext('strokeText', String(text), x, y);
|
|
646
|
+
}
|
|
647
|
+
return this.callContext('strokeText', String(text), x, y, maxWidth);
|
|
648
|
+
},
|
|
649
|
+
/**
|
|
650
|
+
* 按字形宽度估算文本宽度
|
|
651
|
+
* @description 当平台无法给出真实测量值时的兜底。必须区分全角/半角:
|
|
652
|
+
* 汉字、假名、全角标点约占一个字号,按半角的 0.6 估算会短 40%,
|
|
653
|
+
* 中文海报因此算出"整段都放得下"而不换行。
|
|
654
|
+
* @param {String} text 待测量文本
|
|
655
|
+
* @param {Number} [fontSize] 字号,默认取组件当前字号
|
|
656
|
+
* @returns {Number} 估算宽度(px)
|
|
657
|
+
* @author jry ijry@qq.com
|
|
658
|
+
*/
|
|
659
|
+
estimateTextWidth(text, fontSize) {
|
|
660
|
+
const size = Number(fontSize) || Number(this.fontSize) || 12;
|
|
661
|
+
const FULL_WIDTH = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︰--⦆¢-₩]/;
|
|
662
|
+
return Array.from(String(text)).reduce((width, char) => {
|
|
663
|
+
if (FULL_WIDTH.test(char)) return width + size;
|
|
664
|
+
if (/\s/.test(char)) return width + size * 0.28;
|
|
665
|
+
return width + size * 0.56;
|
|
666
|
+
}, 0);
|
|
667
|
+
},
|
|
482
668
|
measureText(text) {
|
|
483
669
|
if (this.ctx && typeof this.ctx.measureText === 'function') {
|
|
484
|
-
|
|
670
|
+
const metrics = this.ctx.measureText(String(text));
|
|
671
|
+
const width = Number(metrics && metrics.width);
|
|
672
|
+
// 鸿蒙同步测量恒为 0(真实值只走 callback),上下文未就绪时其他
|
|
673
|
+
// 平台也可能返回 0。绝不能把 0 交给调用方——换行逻辑会把它当成
|
|
674
|
+
// "宽度足够",于是整段文本挤成一行。
|
|
675
|
+
if (width > 0) return metrics;
|
|
485
676
|
}
|
|
486
677
|
return {
|
|
487
|
-
width:
|
|
678
|
+
width: this.estimateTextWidth(text)
|
|
488
679
|
};
|
|
489
680
|
},
|
|
681
|
+
measureTextAsync(text) {
|
|
682
|
+
if (this.ctx && typeof this.ctx.measureTextAsync === 'function') {
|
|
683
|
+
return this.ctx.measureTextAsync(String(text));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// #ifdef APP-HARMONY
|
|
687
|
+
// 鸿蒙的 CanvasContext.measureText 同步返回值恒为 0,真实宽度只通过
|
|
688
|
+
// callback(webview.evalJSAsync) 回传,因此必须走回调形式。
|
|
689
|
+
if (this.ctx && typeof this.ctx.measureText === 'function' && !this._harmonyMeasureUnavailable) {
|
|
690
|
+
return new Promise((resolve) => {
|
|
691
|
+
let settled = false;
|
|
692
|
+
// evalJSAsync 并非所有鸿蒙版本都提供,回调可能永远不来,
|
|
693
|
+
// 必须兜底避免上层 await 永久挂起。且一旦超时就标记为不可用:
|
|
694
|
+
// 单次换行要二分测量十几次,逐次干等会把导出拖成十几秒。
|
|
695
|
+
const timer = setTimeout(() => {
|
|
696
|
+
if (settled) return;
|
|
697
|
+
settled = true;
|
|
698
|
+
this._harmonyMeasureUnavailable = true;
|
|
699
|
+
resolve({ width: this.estimateTextWidth(text) });
|
|
700
|
+
}, 300);
|
|
701
|
+
const done = (metrics) => {
|
|
702
|
+
if (settled) return;
|
|
703
|
+
settled = true;
|
|
704
|
+
clearTimeout(timer);
|
|
705
|
+
const width = Number(metrics && metrics.width);
|
|
706
|
+
resolve({ width: width > 0 ? width : this.estimateTextWidth(text) });
|
|
707
|
+
};
|
|
708
|
+
try {
|
|
709
|
+
const sync = this.ctx.measureText(String(text), done);
|
|
710
|
+
// 若该平台其实同步返回了有效值,直接采用
|
|
711
|
+
const syncWidth = Number(sync && sync.width);
|
|
712
|
+
if (syncWidth > 0) done(sync);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
done(null);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
// #endif
|
|
719
|
+
|
|
720
|
+
return Promise.resolve(this.measureText(text));
|
|
721
|
+
},
|
|
490
722
|
createLinearGradient(x0, y0, x1, y1) {
|
|
491
723
|
if (this.ctx && typeof this.ctx.createLinearGradient === 'function') {
|
|
492
724
|
return this.ctx.createLinearGradient(x0, y0, x1, y1);
|
|
@@ -499,17 +731,75 @@ export default {
|
|
|
499
731
|
}
|
|
500
732
|
return null;
|
|
501
733
|
},
|
|
734
|
+
createPattern(image, repetition = 'repeat') {
|
|
735
|
+
if (this.ctx && typeof this.ctx.createPattern === 'function') {
|
|
736
|
+
return this.ctx.createPattern(image, repetition);
|
|
737
|
+
}
|
|
738
|
+
return null;
|
|
739
|
+
},
|
|
740
|
+
async resolveNvueImageSource(src) {
|
|
741
|
+
if (typeof src !== 'string' || src.indexOf('data:image/') === 0) {
|
|
742
|
+
return src;
|
|
743
|
+
}
|
|
744
|
+
if (this._imageDataCache[src]) {
|
|
745
|
+
return this._imageDataCache[src];
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const task = (async () => {
|
|
749
|
+
const localPath = await new Promise((resolve, reject) => {
|
|
750
|
+
uni.getImageInfo({
|
|
751
|
+
src,
|
|
752
|
+
success: res => resolve(res.path || res.tempFilePath),
|
|
753
|
+
fail: reject
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
if (!localPath) {
|
|
757
|
+
throw new Error(`Canvas图片未返回本地路径: ${src}`);
|
|
758
|
+
}
|
|
759
|
+
return this.readNvueFileAsDataURL(localPath);
|
|
760
|
+
})();
|
|
761
|
+
this._imageDataCache[src] = task;
|
|
762
|
+
try {
|
|
763
|
+
const dataUrl = await task;
|
|
764
|
+
this._imageDataCache[src] = dataUrl;
|
|
765
|
+
return dataUrl;
|
|
766
|
+
} catch (error) {
|
|
767
|
+
delete this._imageDataCache[src];
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
readNvueFileAsDataURL(path) {
|
|
772
|
+
return new Promise((resolve, reject) => {
|
|
773
|
+
// #ifdef APP-NVUE
|
|
774
|
+
if (typeof plus === 'undefined' || !plus.io) {
|
|
775
|
+
reject(new Error('HTML5+ FileReader不可用'));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
plus.io.resolveLocalFileSystemURL(path, (entry) => {
|
|
779
|
+
entry.file((file) => {
|
|
780
|
+
const reader = new plus.io.FileReader();
|
|
781
|
+
reader.onloadend = event => resolve(event.target.result);
|
|
782
|
+
reader.onerror = reject;
|
|
783
|
+
reader.readAsDataURL(file);
|
|
784
|
+
}, reject);
|
|
785
|
+
}, reject);
|
|
786
|
+
return;
|
|
787
|
+
// #endif
|
|
788
|
+
|
|
789
|
+
resolve(path);
|
|
790
|
+
});
|
|
791
|
+
},
|
|
502
792
|
loadImage(src) {
|
|
793
|
+
// #ifdef APP-NVUE
|
|
794
|
+
return this.resolveNvueImageSource(src);
|
|
795
|
+
// #endif
|
|
796
|
+
|
|
503
797
|
if (this._imageCache[src]) {
|
|
504
798
|
return Promise.resolve(this._imageCache[src]);
|
|
505
799
|
}
|
|
506
800
|
return new Promise((resolve, reject) => {
|
|
507
801
|
let image = null;
|
|
508
802
|
|
|
509
|
-
// #ifdef APP-NVUE
|
|
510
|
-
image = new GImage();
|
|
511
|
-
// #endif
|
|
512
|
-
|
|
513
803
|
// #ifdef MP
|
|
514
804
|
const canvas = this.getCanvasElement();
|
|
515
805
|
if (canvas && typeof canvas.createImage === 'function') {
|
|
@@ -539,7 +829,11 @@ export default {
|
|
|
539
829
|
if (!this.ctx || typeof this.ctx.drawImage !== 'function') {
|
|
540
830
|
return false;
|
|
541
831
|
}
|
|
542
|
-
if (typeof source !== 'string'
|
|
832
|
+
if (typeof source !== 'string') {
|
|
833
|
+
this.ctx.drawImage(source, ...args);
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
if (typeof this.ctx.setFillStyle === 'function' && !this._isNvue) {
|
|
543
837
|
this.ctx.drawImage(source, ...args);
|
|
544
838
|
return true;
|
|
545
839
|
}
|
|
@@ -560,14 +854,32 @@ export default {
|
|
|
560
854
|
return new Promise((resolve, reject) => {
|
|
561
855
|
const width = options.width || this.actualWidth;
|
|
562
856
|
const height = options.height || this.actualHeight;
|
|
857
|
+
const hasDestWidth = options.destWidth !== undefined && options.destWidth !== null;
|
|
858
|
+
const hasDestHeight = options.destHeight !== undefined && options.destHeight !== null;
|
|
859
|
+
let destWidth = hasDestWidth ? options.destWidth : width;
|
|
860
|
+
let destHeight = hasDestHeight ? options.destHeight : height;
|
|
861
|
+
|
|
862
|
+
// MP、H5、APP-PLUS 和 APP-HARMONY 保持逻辑绘制坐标,只提升默认导出像素。
|
|
863
|
+
// #ifdef MP || H5 || APP-PLUS || APP-HARMONY
|
|
864
|
+
if (!hasDestWidth) {
|
|
865
|
+
destWidth = Math.round(width * this.dpr);
|
|
866
|
+
}
|
|
867
|
+
if (!hasDestHeight) {
|
|
868
|
+
destHeight = Math.round(height * this.dpr);
|
|
869
|
+
}
|
|
870
|
+
// #endif
|
|
871
|
+
|
|
872
|
+
const defaultFileType = 'png';
|
|
563
873
|
const request = {
|
|
564
874
|
x: options.x || 0,
|
|
565
875
|
y: options.y || 0,
|
|
566
876
|
width,
|
|
567
877
|
height,
|
|
568
|
-
destWidth
|
|
569
|
-
destHeight
|
|
570
|
-
fileType: options.fileType ||
|
|
878
|
+
destWidth,
|
|
879
|
+
destHeight,
|
|
880
|
+
fileType: options.fileType === undefined || options.fileType === null
|
|
881
|
+
? defaultFileType
|
|
882
|
+
: options.fileType,
|
|
571
883
|
quality: options.quality === undefined ? 1 : options.quality
|
|
572
884
|
};
|
|
573
885
|
const success = (res) => {
|
|
@@ -630,18 +942,14 @@ export default {
|
|
|
630
942
|
|
|
631
943
|
// #ifdef APP-NVUE
|
|
632
944
|
if (this.ctx && typeof this.ctx.toTempFilePath === 'function') {
|
|
633
|
-
this.ctx.toTempFilePath(
|
|
634
|
-
request.x,
|
|
635
|
-
request.y,
|
|
636
|
-
request.width,
|
|
637
|
-
request.height,
|
|
638
|
-
request.destWidth,
|
|
639
|
-
request.destHeight,
|
|
640
|
-
request.fileType,
|
|
641
|
-
request.quality,
|
|
945
|
+
Promise.resolve(this.ctx.toTempFilePath(request)).then(
|
|
642
946
|
(res) => {
|
|
643
947
|
success(res);
|
|
644
948
|
complete(res);
|
|
949
|
+
},
|
|
950
|
+
(error) => {
|
|
951
|
+
fail(error);
|
|
952
|
+
complete(error);
|
|
645
953
|
}
|
|
646
954
|
);
|
|
647
955
|
return;
|
|
@@ -667,51 +975,96 @@ export default {
|
|
|
667
975
|
fileType,
|
|
668
976
|
quality,
|
|
669
977
|
width: this.actualWidth,
|
|
670
|
-
height: this.actualHeight
|
|
671
|
-
destWidth: this.actualWidth,
|
|
672
|
-
destHeight: this.actualHeight
|
|
978
|
+
height: this.actualHeight
|
|
673
979
|
});
|
|
674
980
|
return res.tempFilePath || res.apFilePath;
|
|
675
981
|
},
|
|
676
982
|
getImageData(options = {}) {
|
|
677
983
|
return new Promise((resolve, reject) => {
|
|
984
|
+
const success = (res) => {
|
|
985
|
+
if (typeof options.success === 'function') options.success(res);
|
|
986
|
+
resolve(res);
|
|
987
|
+
};
|
|
988
|
+
const fail = (err) => {
|
|
989
|
+
if (typeof options.fail === 'function') options.fail(err);
|
|
990
|
+
reject(err);
|
|
991
|
+
};
|
|
992
|
+
const complete = (res) => {
|
|
993
|
+
if (typeof options.complete === 'function') options.complete(res);
|
|
994
|
+
};
|
|
995
|
+
// #ifdef APP-NVUE
|
|
996
|
+
if (this.ctx && typeof this.ctx.getImageData === 'function') {
|
|
997
|
+
Promise.resolve(this.ctx.getImageData({
|
|
998
|
+
x: options.x || 0,
|
|
999
|
+
y: options.y || 0,
|
|
1000
|
+
width: options.width || this.actualWidth,
|
|
1001
|
+
height: options.height || this.actualHeight
|
|
1002
|
+
})).then((res) => {
|
|
1003
|
+
success(res);
|
|
1004
|
+
complete(res);
|
|
1005
|
+
}, (error) => {
|
|
1006
|
+
fail(error);
|
|
1007
|
+
complete(error);
|
|
1008
|
+
});
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
// #endif
|
|
1012
|
+
|
|
678
1013
|
const request = {
|
|
679
1014
|
canvasId: this.canvasId,
|
|
680
1015
|
x: options.x || 0,
|
|
681
1016
|
y: options.y || 0,
|
|
682
1017
|
width: options.width || this.actualWidth,
|
|
683
1018
|
height: options.height || this.actualHeight,
|
|
684
|
-
success
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
},
|
|
688
|
-
fail: (err) => {
|
|
689
|
-
if (typeof options.fail === 'function') options.fail(err);
|
|
690
|
-
reject(err);
|
|
691
|
-
},
|
|
692
|
-
complete: options.complete
|
|
1019
|
+
success,
|
|
1020
|
+
fail,
|
|
1021
|
+
complete
|
|
693
1022
|
};
|
|
694
1023
|
uni.canvasGetImageData(request, this);
|
|
695
1024
|
});
|
|
696
1025
|
},
|
|
697
1026
|
putImageData(options = {}) {
|
|
698
1027
|
return new Promise((resolve, reject) => {
|
|
1028
|
+
const success = (res) => {
|
|
1029
|
+
if (typeof options.success === 'function') options.success(res);
|
|
1030
|
+
resolve(res);
|
|
1031
|
+
};
|
|
1032
|
+
const fail = (err) => {
|
|
1033
|
+
if (typeof options.fail === 'function') options.fail(err);
|
|
1034
|
+
reject(err);
|
|
1035
|
+
};
|
|
1036
|
+
const complete = (res) => {
|
|
1037
|
+
if (typeof options.complete === 'function') options.complete(res);
|
|
1038
|
+
};
|
|
1039
|
+
// #ifdef APP-NVUE
|
|
1040
|
+
if (this.ctx && typeof this.ctx.putImageData === 'function') {
|
|
1041
|
+
Promise.resolve(this.ctx.putImageData({
|
|
1042
|
+
x: options.x || 0,
|
|
1043
|
+
y: options.y || 0,
|
|
1044
|
+
width: options.width || (options.data && options.data.width) || this.actualWidth,
|
|
1045
|
+
height: options.height || (options.data && options.data.height) || this.actualHeight,
|
|
1046
|
+
data: options.data
|
|
1047
|
+
})).then((res) => {
|
|
1048
|
+
success(res);
|
|
1049
|
+
complete(res);
|
|
1050
|
+
}, (error) => {
|
|
1051
|
+
fail(error);
|
|
1052
|
+
complete(error);
|
|
1053
|
+
});
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
// #endif
|
|
1057
|
+
|
|
699
1058
|
const request = {
|
|
700
1059
|
canvasId: this.canvasId,
|
|
701
1060
|
x: options.x || 0,
|
|
702
1061
|
y: options.y || 0,
|
|
703
|
-
width: options.width || this.actualWidth,
|
|
704
|
-
height: options.height || this.actualHeight,
|
|
1062
|
+
width: options.width || (options.data && options.data.width) || this.actualWidth,
|
|
1063
|
+
height: options.height || (options.data && options.data.height) || this.actualHeight,
|
|
705
1064
|
data: options.data,
|
|
706
|
-
success
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
},
|
|
710
|
-
fail: (err) => {
|
|
711
|
-
if (typeof options.fail === 'function') options.fail(err);
|
|
712
|
-
reject(err);
|
|
713
|
-
},
|
|
714
|
-
complete: options.complete
|
|
1065
|
+
success,
|
|
1066
|
+
fail,
|
|
1067
|
+
complete
|
|
715
1068
|
};
|
|
716
1069
|
uni.canvasPutImageData(request, this);
|
|
717
1070
|
});
|