@react-native-ohos/clipboard 1.16.3-rc.2 → 1.16.4-beta.1

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.
@@ -3,7 +3,7 @@
3
3
  "types": "",
4
4
  "devDependencies": {},
5
5
  "name": "@react-native-ohos/clipboard",
6
- "version": "1.16.3-rc.2",
6
+ "version": "1.16.4-beta.1",
7
7
  "description": "",
8
8
  "main": "index.ets",
9
9
  "dependencies": {
@@ -33,6 +33,8 @@ import url from '@ohos.url';
33
33
  const TAG = "RNCClipboardTurboModule"
34
34
  const prefixPNG = "data:image/png;base64,"
35
35
  const prefixJPG = "data:image/jpg;base64,"
36
+ const imageMimePrefix = "image/"
37
+ const defaultImageMimeType = "image/png"
36
38
  const PERMISSIONS: Array<Permissions> = [
37
39
  'ohos.permission.READ_PASTEBOARD'
38
40
  ]
@@ -181,7 +183,14 @@ export class RNCClipboardTurboModule extends TurboModule {
181
183
 
182
184
  systemPasteboard.getData().then((pasteData) => {
183
185
  let pixMap = pasteData.getPrimaryPixelMap();
184
- //packer方式
186
+ if (!pixMap) {
187
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG,pixMap is null or undefined");
188
+ reject({
189
+ code: 1,
190
+ message: "Clipboard does not contain an image"
191
+ });
192
+ return;
193
+ }
185
194
  const imagePackerApi = image.createImagePacker();
186
195
  let packOpt: image.PackingOption = {
187
196
  format: "image/png",
@@ -196,11 +205,12 @@ export class RNCClipboardTurboModule extends TurboModule {
196
205
  }).catch((err) => {
197
206
  logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG,failed to packing");
198
207
  reject(err)
199
- })
200
- imagePackerApi.release().then(() => {
201
- logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG, releasing image packaging");
202
- }).catch((error: BusinessError) => {
203
- logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG, releasing image packaging error");
208
+ }).finally(() => {
209
+ imagePackerApi.release().then(() => {
210
+ logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG, releasing image packaging");
211
+ }).catch((error: BusinessError) => {
212
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG, releasing image packaging error");
213
+ })
204
214
  })
205
215
 
206
216
  }).catch((err) => {
@@ -213,6 +223,12 @@ export class RNCClipboardTurboModule extends TurboModule {
213
223
  message: "User refuses authorization"
214
224
  })
215
225
  }
226
+ }).catch((err) => {
227
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImagePNG,failed to request permission");
228
+ reject({
229
+ code: 2,
230
+ message: "Failed to request permission"
231
+ })
216
232
  });
217
233
  });
218
234
  }
@@ -227,8 +243,14 @@ export class RNCClipboardTurboModule extends TurboModule {
227
243
 
228
244
  systemPasteboard.getData().then((pasteData) => {
229
245
  let pixMap = pasteData.getPrimaryPixelMap();
230
-
231
- //packer方式
246
+ if (!pixMap) {
247
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG,pixMap is null or undefined");
248
+ reject({
249
+ code: 1,
250
+ message: "Clipboard does not contain an image"
251
+ });
252
+ return;
253
+ }
232
254
  const imagePackerApi = image.createImagePacker();
233
255
  let packOpt: image.PackingOption = {
234
256
  format: "image/jpeg",
@@ -243,11 +265,12 @@ export class RNCClipboardTurboModule extends TurboModule {
243
265
  }).catch((err) => {
244
266
  logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG,failed to packing");
245
267
  reject(err)
246
- })
247
- imagePackerApi.release().then(() => {
248
- logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG, releasing image packaging");
249
- }).catch((error: BusinessError) => {
250
- logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG, releasing image packaging error");
268
+ }).finally(() => {
269
+ imagePackerApi.release().then(() => {
270
+ logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG, releasing image packaging");
271
+ }).catch((error: BusinessError) => {
272
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG, releasing image packaging error");
273
+ })
251
274
  })
252
275
 
253
276
  }).catch((err) => {
@@ -260,6 +283,12 @@ export class RNCClipboardTurboModule extends TurboModule {
260
283
  message: "User refuses authorization"
261
284
  })
262
285
  }
286
+ }).catch((err) => {
287
+ logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call getImageJPG,failed to request permission");
288
+ reject({
289
+ code: 2,
290
+ message: "Failed to request permission"
291
+ })
263
292
  });
264
293
  });
265
294
  }
@@ -274,6 +303,10 @@ export class RNCClipboardTurboModule extends TurboModule {
274
303
  let imageSource: image.ImageSource = image.createImageSource(arrayBuffer);
275
304
  logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call setImage100");
276
305
 
306
+ // 获取图片格式信息
307
+ let imageFormat = this.getImageFormatFromArrayBuffer(arrayBuffer);
308
+ logger.debug(TAG, `[RNOH]:RNCClipboardTurboModule call setImage, detected format: ${imageFormat}`);
309
+
277
310
  imageSource.getImageInfo().then(value => {
278
311
  let hValue = Math.round(value.size.height);
279
312
  let wValue = Math.round(value.size.width);
@@ -293,11 +326,17 @@ export class RNCClipboardTurboModule extends TurboModule {
293
326
 
294
327
  let systemPasteboard = pasteboard.getSystemPasteboard();
295
328
  systemPasteboard.getData().then((pasteData) => {
329
+ // 使用 MIMETYPE_PIXELMAP 创建记录,确保兼容性
296
330
  let record = pasteboard.createRecord(pasteboard.MIMETYPE_PIXELMAP, iconPixelMap)
331
+ // 在 record 的 data 中保存原始格式信息,供 getImage 读取
332
+ if (!record.data) {
333
+ record.data = {};
334
+ }
335
+ record.data[imageFormat] = arrayBuffer;
297
336
  pasteData.addRecord(record)
298
337
 
299
338
  systemPasteboard.setData(pasteData).then((data: void) => {
300
- logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call setImage305,successed in setting pasteData");
339
+ logger.debug(TAG, `[RNOH]:RNCClipboardTurboModule call setImage305,successed in setting pasteData with format: ${imageFormat}`);
301
340
  }).catch((err) => {
302
341
  logger.error(TAG, "[RNOH]:RNCClipboardTurboModule call setImage305,failed in setting pasteData");
303
342
  })
@@ -309,13 +348,252 @@ export class RNCClipboardTurboModule extends TurboModule {
309
348
 
310
349
  }
311
350
 
351
+ // 从 ArrayBuffer 中检测图片格式
352
+ private getImageFormatFromArrayBuffer(buffer: ArrayBuffer): string {
353
+ let uint8Array = new Uint8Array(buffer);
354
+
355
+ // 检查 JPEG (FF D8 FF)
356
+ if (uint8Array.length >= 3 &&
357
+ uint8Array[0] === 0xFF &&
358
+ uint8Array[1] === 0xD8 &&
359
+ uint8Array[2] === 0xFF) {
360
+ return "image/jpeg";
361
+ }
362
+
363
+ // 检查 PNG (89 50 4E 47 0D 0A 1A 0A)
364
+ if (uint8Array.length >= 8 &&
365
+ uint8Array[0] === 0x89 &&
366
+ uint8Array[1] === 0x50 &&
367
+ uint8Array[2] === 0x4E &&
368
+ uint8Array[3] === 0x47 &&
369
+ uint8Array[4] === 0x0D &&
370
+ uint8Array[5] === 0x0A &&
371
+ uint8Array[6] === 0x1A &&
372
+ uint8Array[7] === 0x0A) {
373
+ return "image/png";
374
+ }
375
+
376
+ // 检查 WEBP (52 49 46 46 ... 57 45 42 50)
377
+ if (uint8Array.length >= 12 &&
378
+ uint8Array[0] === 0x52 &&
379
+ uint8Array[1] === 0x49 &&
380
+ uint8Array[2] === 0x46 &&
381
+ uint8Array[3] === 0x46 &&
382
+ uint8Array[8] === 0x57 &&
383
+ uint8Array[9] === 0x45 &&
384
+ uint8Array[10] === 0x42 &&
385
+ uint8Array[11] === 0x50) {
386
+ return "image/webp";
387
+ }
388
+
389
+ // 检查 GIF (47 49 46 38)
390
+ if (uint8Array.length >= 4 &&
391
+ uint8Array[0] === 0x47 &&
392
+ uint8Array[1] === 0x49 &&
393
+ uint8Array[2] === 0x46 &&
394
+ uint8Array[3] === 0x38) {
395
+ return "image/gif";
396
+ }
397
+
398
+ // 检查 BMP (42 4D)
399
+ if (uint8Array.length >= 2 &&
400
+ uint8Array[0] === 0x42 &&
401
+ uint8Array[1] === 0x4D) {
402
+ return "image/bmp";
403
+ }
404
+
405
+ // 默认返回 PNG
406
+ logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule getImageFormatFromArrayBuffer, unknown format, defaulting to image/png");
407
+ return "image/png";
408
+ }
409
+
312
410
  getImage(): Promise<string> {
313
411
  return new Promise<string>((resolve, reject) => {
314
412
  logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImage");
315
- resolve("demo-todo")
413
+ this.requestPermission().then(async res => {
414
+ if (!res) {
415
+ reject({
416
+ code: 0,
417
+ message: "User refuses authorization"
418
+ })
419
+ return
420
+ }
421
+
422
+ try {
423
+ let systemPasteboard = pasteboard.getSystemPasteboard();
424
+ let pasteData = await systemPasteboard.getData();
425
+
426
+ // 检查剪贴板中是否有 JPEG 或 PNG 格式的数据
427
+ // 如果有,使用与 getImageJPG/getImagePNG 相同的逻辑:通过 getPrimaryPixelMap 重新打包
428
+ let imageFormat = this.getImageFormatFromPasteData(pasteData);
429
+ if (imageFormat) {
430
+ logger.debug(TAG, `[RNOH]:RNCClipboardTurboModule getImage, detected format: ${imageFormat}, using getImageJPG/getImagePNG logic`);
431
+ let pixelMapObj = pasteData.getPrimaryPixelMap();
432
+ if (pixelMapObj) {
433
+ // 使用与 getImageJPG/getImagePNG 相同的参数重新打包
434
+ let imageDataUri = await this.packPixelMapAsDataUri(pixelMapObj, imageFormat);
435
+ resolve(imageDataUri);
436
+ return;
437
+ }
438
+ }
439
+
440
+ let imageDataUri = await this.getImageDataUriFromPasteData(pasteData);
441
+ resolve(imageDataUri)
442
+ } catch (err) {
443
+ logger.error(TAG, `[RNOH]:RNCClipboardTurboModule call getImage,failed to get image. Cause:${err.message}`);
444
+ reject(err)
445
+ }
446
+ }).catch((err) => {
447
+ logger.error(TAG, `[RNOH]:RNCClipboardTurboModule call getImage,failed to request permission. Cause:${err.message}`);
448
+ reject(err)
449
+ })
316
450
  });
317
451
  }
318
452
 
453
+ // 从剪贴板数据中检测图片格式
454
+ private getImageFormatFromPasteData(pasteData: pasteboard.PasteData): string | null {
455
+ let count = pasteData.getRecordCount();
456
+ for (let index = 0; index < count; index++) {
457
+ let record = pasteData.getRecord(index);
458
+ // 检查 record.mimeType
459
+ let mimeType = record.mimeType?.toLowerCase() || "";
460
+ if (mimeType === "image/jpeg" || mimeType === "image/jpg") {
461
+ return "image/jpeg";
462
+ }
463
+ if (mimeType === "image/png") {
464
+ return "image/png";
465
+ }
466
+ // 检查 record.data 中的 mimeType
467
+ if (record.data) {
468
+ for (let mt of Object.keys(record.data)) {
469
+ let lowerMt = mt.toLowerCase();
470
+ if (lowerMt === "image/jpeg" || lowerMt === "image/jpg") {
471
+ return "image/jpeg";
472
+ }
473
+ if (lowerMt === "image/png") {
474
+ return "image/png";
475
+ }
476
+ }
477
+ }
478
+ }
479
+ return null;
480
+ }
481
+
482
+ private async getImageDataUriFromPasteData(pasteData: pasteboard.PasteData): Promise<string> {
483
+ let count = pasteData.getRecordCount();
484
+ for (let index = 0; index < count; index++) {
485
+ let record = pasteData.getRecord(index);
486
+ let dataUri = await this.getImageDataUriFromRecord(record);
487
+ if (dataUri.length > 0) {
488
+ return dataUri
489
+ }
490
+ }
491
+
492
+ try {
493
+ let pixelMapObj = pasteData.getPrimaryPixelMap();
494
+ if (pixelMapObj) {
495
+ return await this.packPixelMapAsDataUri(pixelMapObj, defaultImageMimeType)
496
+ }
497
+ } catch (err) {
498
+ logger.debug(TAG, `[RNOH]:RNCClipboardTurboModule getImage,no primary PixelMap. Cause:${err.message}`);
499
+ }
500
+
501
+ return ""
502
+ }
503
+
504
+ private async getImageDataUriFromRecord(record: pasteboard.PasteDataRecord): Promise<string> {
505
+ if (this.isImageMimeType(record.mimeType)) {
506
+ let dataUri = await this.getImageDataUriByMimeType(record, record.mimeType);
507
+ if (dataUri.length > 0) {
508
+ return dataUri
509
+ }
510
+ }
511
+
512
+ if (record.data) {
513
+ let customMimeTypes = Object.keys(record.data);
514
+ for (let index = 0; index < customMimeTypes.length; index++) {
515
+ let mimeType = customMimeTypes[index];
516
+ if (this.isImageMimeType(mimeType)) {
517
+ return this.createDataUri(mimeType, record.data[mimeType])
518
+ }
519
+ }
520
+ }
521
+
522
+ if (record.pixelMap) {
523
+ let mimeType = this.isImageMimeType(record.mimeType) ? record.mimeType : defaultImageMimeType;
524
+ return await this.packPixelMapAsDataUri(record.pixelMap, mimeType)
525
+ }
526
+
527
+ return ""
528
+ }
529
+
530
+ private async getImageDataUriByMimeType(record: pasteboard.PasteDataRecord, mimeType: string): Promise<string> {
531
+ if (record.data && record.data[mimeType]) {
532
+ return this.createDataUri(mimeType, record.data[mimeType])
533
+ }
534
+
535
+ try {
536
+ let value = await record.getData(mimeType);
537
+ if (value instanceof ArrayBuffer) {
538
+ return this.createDataUri(mimeType, value)
539
+ }
540
+
541
+ if (this.isPixelMap(value)) {
542
+ return await this.packPixelMapAsDataUri(value as image.PixelMap, mimeType)
543
+ }
544
+ } catch (err) {
545
+ logger.debug(TAG, `[RNOH]:RNCClipboardTurboModule getImage,failed to read mimeType:${mimeType}. Cause:${err.message}`);
546
+ }
547
+
548
+ return ""
549
+ }
550
+
551
+ private async packPixelMapAsDataUri(pixelMapObj: image.PixelMap, mimeType: string): Promise<string> {
552
+ const imagePackerApi = image.createImagePacker();
553
+ let packingMimeType = this.getPackingMimeType(mimeType);
554
+ let packOpt: image.PackingOption = {
555
+ format: packingMimeType,
556
+ quality: 96
557
+ }
558
+
559
+ try {
560
+ let data = await imagePackerApi.packing(pixelMapObj, packOpt);
561
+ return this.createDataUri(packingMimeType, data)
562
+ } finally {
563
+ imagePackerApi.release().then(() => {
564
+ logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call getImage, releasing image packaging");
565
+ }).catch((error: BusinessError) => {
566
+ logger.error(TAG, `[RNOH]:RNCClipboardTurboModule call getImage, releasing image packaging error. Cause:${error.message}`);
567
+ })
568
+ }
569
+ }
570
+
571
+ private createDataUri(mimeType: string, data: ArrayBuffer): string {
572
+ let uint8Array = new Uint8Array(data);
573
+ let base64Helper = new util.Base64Helper();
574
+ let base64Str = base64Helper.encodeToStringSync(uint8Array, util.Type.BASIC);
575
+ return `data:${mimeType};base64,${base64Str}`
576
+ }
577
+
578
+ private getPackingMimeType(mimeType: string): string {
579
+ let normalizedMimeType = mimeType.toLowerCase();
580
+ if (normalizedMimeType == "image/jpeg" || normalizedMimeType == "image/jpg") {
581
+ return "image/jpeg"
582
+ }
583
+ if (normalizedMimeType == "image/webp") {
584
+ return "image/webp"
585
+ }
586
+ return defaultImageMimeType
587
+ }
588
+
589
+ private isImageMimeType(mimeType: string): boolean {
590
+ return mimeType != undefined && mimeType.length > 0 && mimeType.toLowerCase().indexOf(imageMimePrefix) == 0
591
+ }
592
+
593
+ private isPixelMap(value: pasteboard.ValueType): boolean {
594
+ return value != undefined && typeof value == "object" && "getImageInfo" in value
595
+ }
596
+
319
597
  setStrings(content: string[]) {
320
598
  logger.debug(TAG, "[RNOH]:RNCClipboardTurboModule call setStrings fun");
321
599
  let systemPasteboard = pasteboard.getSystemPasteboard();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-ohos/clipboard",
3
- "version": "1.16.3-rc.2",
3
+ "version": "1.16.4-beta.1",
4
4
  "description": "React Native Clipboard API",
5
5
  "harmony": {
6
6
  "alias": "@react-native-clipboard/clipboard"