@@ -364,6 +364,10 @@ class CachedCanvases {
364
364
return canvasEntry ;
365
365
}
366
366
367
+ delete ( id ) {
368
+ delete this . cache [ id ] ;
369
+ }
370
+
367
371
clear ( ) {
368
372
for ( const id in this . cache ) {
369
373
const canvasEntry = this . cache [ id ] ;
@@ -1121,6 +1125,7 @@ class CanvasGraphics {
1121
1125
}
1122
1126
this . _cachedScaleForStroking = null ;
1123
1127
this . _cachedGetSinglePixelWidth = null ;
1128
+ this . _cachedBitmapsMap = new Map ( ) ;
1124
1129
}
1125
1130
1126
1131
getObject ( data , fallback = null ) {
@@ -1156,7 +1161,7 @@ class CanvasGraphics {
1156
1161
"transparent" ,
1157
1162
width ,
1158
1163
height ,
1159
- true
1164
+ /* trackTransform */ true
1160
1165
) ;
1161
1166
this . compositeCtx = this . ctx ;
1162
1167
this . transparentCanvas = transparentCanvas . canvas ;
@@ -1275,6 +1280,19 @@ class CanvasGraphics {
1275
1280
this . cachedCanvases . clear ( ) ;
1276
1281
this . cachedPatterns . clear ( ) ;
1277
1282
1283
+ for ( const cache of this . _cachedBitmapsMap . values ( ) ) {
1284
+ for ( const canvas of cache . values ( ) ) {
1285
+ if (
1286
+ typeof HTMLCanvasElement !== "undefined" &&
1287
+ canvas instanceof HTMLCanvasElement
1288
+ ) {
1289
+ canvas . width = canvas . height = 0 ;
1290
+ }
1291
+ }
1292
+ cache . clear ( ) ;
1293
+ }
1294
+ this . _cachedBitmapsMap . clear ( ) ;
1295
+
1278
1296
if ( this . imageLayer ) {
1279
1297
this . imageLayer . endLayout ( ) ;
1280
1298
}
@@ -1316,7 +1334,8 @@ class CanvasGraphics {
1316
1334
tmpCanvas = this . cachedCanvases . getCanvas (
1317
1335
tmpCanvasId ,
1318
1336
newWidth ,
1319
- newHeight
1337
+ newHeight ,
1338
+ /* trackTransform */ false
1320
1339
) ;
1321
1340
tmpCtx = tmpCanvas . context ;
1322
1341
tmpCtx . clearRect ( 0 , 0 , newWidth , newHeight ) ;
@@ -1345,24 +1364,65 @@ class CanvasGraphics {
1345
1364
1346
1365
_createMaskCanvas ( img ) {
1347
1366
const ctx = this . ctx ;
1348
- const width = img . width ,
1349
- height = img . height ;
1367
+ const { width, height } = img ;
1350
1368
const fillColor = this . current . fillColor ;
1351
1369
const isPatternFill = this . current . patternFill ;
1352
- const maskCanvas = this . cachedCanvases . getCanvas (
1353
- "maskCanvas" ,
1354
- width ,
1355
- height
1356
- ) ;
1357
- const maskCtx = maskCanvas . context ;
1358
- putBinaryImageMask ( maskCtx , img ) ;
1370
+ const currentTransform = ctx . mozCurrentTransform ;
1371
+
1372
+ let cache , cacheKey , scaled , maskCanvas ;
1373
+ if ( ( img . bitmap || img . data ) && img . count > 1 ) {
1374
+ const mainKey = img . bitmap || img . data . buffer ;
1375
+ // We're reusing the same image several times, so we can cache it.
1376
+ // In case we've a pattern fill we just keep the scaled version of
1377
+ // the image.
1378
+ // Only the scaling part matters, the translation part is just used
1379
+ // to compute offsets.
1380
+ // TODO: handle the case of a pattern fill if it's possible.
1381
+ const withoutTranslation = currentTransform . slice ( 0 , 4 ) ;
1382
+ cacheKey = JSON . stringify (
1383
+ isPatternFill ? withoutTranslation : [ withoutTranslation , fillColor ]
1384
+ ) ;
1385
+
1386
+ cache = this . _cachedBitmapsMap . get ( mainKey ) ;
1387
+ if ( ! cache ) {
1388
+ cache = new Map ( ) ;
1389
+ this . _cachedBitmapsMap . set ( mainKey , cache ) ;
1390
+ }
1391
+ const cachedImage = cache . get ( cacheKey ) ;
1392
+ if ( cachedImage && ! isPatternFill ) {
1393
+ const offsetX = Math . round (
1394
+ Math . min ( currentTransform [ 0 ] , currentTransform [ 2 ] ) +
1395
+ currentTransform [ 4 ]
1396
+ ) ;
1397
+ const offsetY = Math . round (
1398
+ Math . min ( currentTransform [ 1 ] , currentTransform [ 3 ] ) +
1399
+ currentTransform [ 5 ]
1400
+ ) ;
1401
+ return {
1402
+ canvas : cachedImage ,
1403
+ offsetX,
1404
+ offsetY,
1405
+ } ;
1406
+ }
1407
+ scaled = cachedImage ;
1408
+ }
1409
+
1410
+ if ( ! scaled ) {
1411
+ maskCanvas = this . cachedCanvases . getCanvas (
1412
+ "maskCanvas" ,
1413
+ width ,
1414
+ height ,
1415
+ /* trackTransform */ false
1416
+ ) ;
1417
+ putBinaryImageMask ( maskCanvas . context , img ) ;
1418
+ }
1359
1419
1360
1420
// Create the mask canvas at the size it will be drawn at and also set
1361
1421
// its transform to match the current transform so if there are any
1362
1422
// patterns applied they will be applied relative to the correct
1363
1423
// transform.
1364
- const objToCanvas = ctx . mozCurrentTransform ;
1365
- let maskToCanvas = Util . transform ( objToCanvas , [
1424
+
1425
+ let maskToCanvas = Util . transform ( currentTransform , [
1366
1426
1 / width ,
1367
1427
0 ,
1368
1428
0 ,
@@ -1380,29 +1440,41 @@ class CanvasGraphics {
1380
1440
"fillCanvas" ,
1381
1441
drawnWidth ,
1382
1442
drawnHeight ,
1383
- true
1443
+ /* trackTransform */ true
1384
1444
) ;
1385
1445
const fillCtx = fillCanvas . context ;
1446
+
1386
1447
// The offset will be the top-left cordinate mask.
1448
+ // If objToCanvas is [a,b,c,d,e,f] then:
1449
+ // - offsetX = min(a, c) + e
1450
+ // - offsetY = min(b, d) + f
1387
1451
const offsetX = Math . min ( cord1 [ 0 ] , cord2 [ 0 ] ) ;
1388
1452
const offsetY = Math . min ( cord1 [ 1 ] , cord2 [ 1 ] ) ;
1389
1453
fillCtx . translate ( - offsetX , - offsetY ) ;
1390
1454
fillCtx . transform . apply ( fillCtx , maskToCanvas ) ;
1391
- // Pre-scale if needed to improve image smoothing.
1392
- const scaled = this . _scaleImage (
1393
- maskCanvas . canvas ,
1394
- fillCtx . mozCurrentTransformInverse
1395
- ) ;
1455
+
1456
+ if ( ! scaled ) {
1457
+ // Pre-scale if needed to improve image smoothing.
1458
+ scaled = this . _scaleImage (
1459
+ maskCanvas . canvas ,
1460
+ fillCtx . mozCurrentTransformInverse
1461
+ ) ;
1462
+ scaled = scaled . img ;
1463
+ if ( cache && isPatternFill ) {
1464
+ cache . set ( cacheKey , scaled ) ;
1465
+ }
1466
+ }
1467
+
1396
1468
fillCtx . imageSmoothingEnabled = getImageSmoothingEnabled (
1397
1469
fillCtx . mozCurrentTransform ,
1398
1470
img . interpolate
1399
1471
) ;
1400
1472
fillCtx . drawImage (
1401
- scaled . img ,
1473
+ scaled ,
1402
1474
0 ,
1403
1475
0 ,
1404
- scaled . img . width ,
1405
- scaled . img . height ,
1476
+ scaled . width ,
1477
+ scaled . height ,
1406
1478
0 ,
1407
1479
0 ,
1408
1480
width ,
@@ -1424,6 +1496,13 @@ class CanvasGraphics {
1424
1496
1425
1497
fillCtx . fillRect ( 0 , 0 , width , height ) ;
1426
1498
1499
+ if ( cache && ! isPatternFill ) {
1500
+ // The fill canvas is put in the cache associated to the mask image
1501
+ // so we must remove from the cached canvas: it mustn't be used again.
1502
+ this . cachedCanvases . delete ( "fillCanvas" ) ;
1503
+ cache . set ( cacheKey , fillCanvas . canvas ) ;
1504
+ }
1505
+
1427
1506
// Round the offsets to avoid drawing fractional pixels.
1428
1507
return {
1429
1508
canvas : fillCanvas . canvas ,
@@ -1555,7 +1634,7 @@ class CanvasGraphics {
1555
1634
cacheId ,
1556
1635
drawnWidth ,
1557
1636
drawnHeight ,
1558
- true
1637
+ /* trackTransform */ true
1559
1638
) ;
1560
1639
this . suspendedCtx = this . ctx ;
1561
1640
this . ctx = scratchCanvas . context ;
@@ -2097,7 +2176,8 @@ class CanvasGraphics {
2097
2176
const { context : ctx } = this . cachedCanvases . getCanvas (
2098
2177
"isFontSubpixelAAEnabled" ,
2099
2178
10 ,
2100
- 10
2179
+ 10 ,
2180
+ /* trackTransform */ false
2101
2181
) ;
2102
2182
ctx . scale ( 1.5 , 1 ) ;
2103
2183
ctx . fillText ( "I" , 0 , 10 ) ;
@@ -2606,7 +2686,7 @@ class CanvasGraphics {
2606
2686
cacheId ,
2607
2687
drawnWidth ,
2608
2688
drawnHeight ,
2609
- true
2689
+ /* trackTransform */ true
2610
2690
) ;
2611
2691
const groupCtx = scratchCanvas . context ;
2612
2692
@@ -2768,7 +2848,9 @@ class CanvasGraphics {
2768
2848
return ;
2769
2849
}
2770
2850
2851
+ const count = img . count ;
2771
2852
img = this . getObject ( img . data , img ) ;
2853
+ img . count = count ;
2772
2854
2773
2855
const ctx = this . ctx ;
2774
2856
const width = img . width ,
@@ -2854,7 +2936,8 @@ class CanvasGraphics {
2854
2936
const maskCanvas = this . cachedCanvases . getCanvas (
2855
2937
"maskCanvas" ,
2856
2938
width ,
2857
- height
2939
+ height ,
2940
+ /* trackTransform */ false
2858
2941
) ;
2859
2942
const maskCtx = maskCanvas . context ;
2860
2943
maskCtx . save ( ) ;
@@ -2945,7 +3028,8 @@ class CanvasGraphics {
2945
3028
const tmpCanvas = this . cachedCanvases . getCanvas (
2946
3029
"inlineImage" ,
2947
3030
width ,
2948
- height
3031
+ height ,
3032
+ /* trackTransform */ false
2949
3033
) ;
2950
3034
const tmpCtx = tmpCanvas . context ;
2951
3035
putBinaryImageData ( tmpCtx , imgData , this . current . transferMaps ) ;
@@ -2991,7 +3075,12 @@ class CanvasGraphics {
2991
3075
const w = imgData . width ;
2992
3076
const h = imgData . height ;
2993
3077
2994
- const tmpCanvas = this . cachedCanvases . getCanvas ( "inlineImage" , w , h ) ;
3078
+ const tmpCanvas = this . cachedCanvases . getCanvas (
3079
+ "inlineImage" ,
3080
+ w ,
3081
+ h ,
3082
+ /* trackTransform */ false
3083
+ ) ;
2995
3084
const tmpCtx = tmpCanvas . context ;
2996
3085
putBinaryImageData ( tmpCtx , imgData , this . current . transferMaps ) ;
2997
3086
0 commit comments