@@ -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,16 @@ 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 ( canvas instanceof HTMLCanvasElement ) {
1286
+ canvas . width = canvas . height = 0 ;
1287
+ }
1288
+ }
1289
+ cache . clear ( ) ;
1290
+ }
1291
+ this . _cachedBitmapsMap . clear ( ) ;
1292
+
1278
1293
if ( this . imageLayer ) {
1279
1294
this . imageLayer . endLayout ( ) ;
1280
1295
}
@@ -1316,7 +1331,8 @@ class CanvasGraphics {
1316
1331
tmpCanvas = this . cachedCanvases . getCanvas (
1317
1332
tmpCanvasId ,
1318
1333
newWidth ,
1319
- newHeight
1334
+ newHeight ,
1335
+ /* trackTransform */ false
1320
1336
) ;
1321
1337
tmpCtx = tmpCanvas . context ;
1322
1338
tmpCtx . clearRect ( 0 , 0 , newWidth , newHeight ) ;
@@ -1349,19 +1365,60 @@ class CanvasGraphics {
1349
1365
height = img . height ;
1350
1366
const fillColor = this . current . fillColor ;
1351
1367
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 ) ;
1368
+ const objToCanvas = ctx . mozCurrentTransform ;
1369
+
1370
+ let cache , cacheKey , scaled , maskCanvas ;
1371
+ if ( ( img . bitmap || img . data ) && img . count >= 2 ) {
1372
+ const mainKey = img . bitmap || img . data . buffer ;
1373
+ // We're reusing the same image several times, so we can cache it.
1374
+ // In case we've a pattern fill we just keep the scaled version of
1375
+ // the image.
1376
+ // Only the scaling part matters, the translation part is just used
1377
+ // to compute offsets.
1378
+ // TODO: handle the case of a pattern fill if it's possible.
1379
+ const withoutTranslation = objToCanvas . slice ( 0 , 4 ) ;
1380
+ cacheKey = isPatternFill
1381
+ ? withoutTranslation
1382
+ : [ withoutTranslation , fillColor ] ;
1383
+ cacheKey = JSON . stringify ( cacheKey ) ;
1384
+
1385
+ cache = this . _cachedBitmapsMap . get ( mainKey ) ;
1386
+ if ( ! cache ) {
1387
+ cache = new Map ( ) ;
1388
+ this . _cachedBitmapsMap . set ( mainKey , cache ) ;
1389
+ }
1390
+ const cachedImage = cache . get ( cacheKey ) ;
1391
+ if ( cachedImage && ! isPatternFill ) {
1392
+ const offsetX = Math . round (
1393
+ Math . min ( objToCanvas [ 0 ] , objToCanvas [ 2 ] ) + objToCanvas [ 4 ]
1394
+ ) ;
1395
+ const offsetY = Math . round (
1396
+ Math . min ( objToCanvas [ 1 ] , objToCanvas [ 3 ] ) + objToCanvas [ 5 ]
1397
+ ) ;
1398
+ return {
1399
+ canvas : cachedImage ,
1400
+ offsetX,
1401
+ offsetY,
1402
+ } ;
1403
+ }
1404
+ scaled = cachedImage ;
1405
+ }
1406
+
1407
+ if ( ! scaled ) {
1408
+ maskCanvas = this . cachedCanvases . getCanvas (
1409
+ "maskCanvas" ,
1410
+ width ,
1411
+ height ,
1412
+ /* trackTransform */ false
1413
+ ) ;
1414
+ putBinaryImageMask ( maskCanvas . context , img ) ;
1415
+ }
1359
1416
1360
1417
// Create the mask canvas at the size it will be drawn at and also set
1361
1418
// its transform to match the current transform so if there are any
1362
1419
// patterns applied they will be applied relative to the correct
1363
1420
// transform.
1364
- const objToCanvas = ctx . mozCurrentTransform ;
1421
+
1365
1422
let maskToCanvas = Util . transform ( objToCanvas , [
1366
1423
1 / width ,
1367
1424
0 ,
@@ -1380,29 +1437,41 @@ class CanvasGraphics {
1380
1437
"fillCanvas" ,
1381
1438
drawnWidth ,
1382
1439
drawnHeight ,
1383
- true
1440
+ /* trackTransform */ true
1384
1441
) ;
1385
1442
const fillCtx = fillCanvas . context ;
1443
+
1386
1444
// The offset will be the top-left cordinate mask.
1445
+ // If objToCanvas is [a,b,c,d,e,f] then:
1446
+ // - offsetX = min(a, c) + e
1447
+ // - offsetY = min(b, d) + f
1387
1448
const offsetX = Math . min ( cord1 [ 0 ] , cord2 [ 0 ] ) ;
1388
1449
const offsetY = Math . min ( cord1 [ 1 ] , cord2 [ 1 ] ) ;
1389
1450
fillCtx . translate ( - offsetX , - offsetY ) ;
1390
1451
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
- ) ;
1452
+
1453
+ if ( ! scaled ) {
1454
+ // Pre-scale if needed to improve image smoothing.
1455
+ scaled = this . _scaleImage (
1456
+ maskCanvas . canvas ,
1457
+ fillCtx . mozCurrentTransformInverse
1458
+ ) ;
1459
+ scaled = scaled . img ;
1460
+ if ( cache && isPatternFill ) {
1461
+ cache . set ( cacheKey , scaled ) ;
1462
+ }
1463
+ }
1464
+
1396
1465
fillCtx . imageSmoothingEnabled = getImageSmoothingEnabled (
1397
1466
fillCtx . mozCurrentTransform ,
1398
1467
img . interpolate
1399
1468
) ;
1400
1469
fillCtx . drawImage (
1401
- scaled . img ,
1470
+ scaled ,
1402
1471
0 ,
1403
1472
0 ,
1404
- scaled . img . width ,
1405
- scaled . img . height ,
1473
+ scaled . width ,
1474
+ scaled . height ,
1406
1475
0 ,
1407
1476
0 ,
1408
1477
width ,
@@ -1424,6 +1493,13 @@ class CanvasGraphics {
1424
1493
1425
1494
fillCtx . fillRect ( 0 , 0 , width , height ) ;
1426
1495
1496
+ if ( cache && ! isPatternFill ) {
1497
+ // The fill canvas is put in the cache associated to the mask image
1498
+ // so we must remove from the cached canvas: it mustn't be used again.
1499
+ this . cachedCanvases . delete ( "fillCanvas" ) ;
1500
+ cache . set ( cacheKey , fillCanvas . canvas ) ;
1501
+ }
1502
+
1427
1503
// Round the offsets to avoid drawing fractional pixels.
1428
1504
return {
1429
1505
canvas : fillCanvas . canvas ,
@@ -1555,7 +1631,7 @@ class CanvasGraphics {
1555
1631
cacheId ,
1556
1632
drawnWidth ,
1557
1633
drawnHeight ,
1558
- true
1634
+ /* trackTransform */ true
1559
1635
) ;
1560
1636
this . suspendedCtx = this . ctx ;
1561
1637
this . ctx = scratchCanvas . context ;
@@ -2097,7 +2173,8 @@ class CanvasGraphics {
2097
2173
const { context : ctx } = this . cachedCanvases . getCanvas (
2098
2174
"isFontSubpixelAAEnabled" ,
2099
2175
10 ,
2100
- 10
2176
+ 10 ,
2177
+ /* trackTransform */ false
2101
2178
) ;
2102
2179
ctx . scale ( 1.5 , 1 ) ;
2103
2180
ctx . fillText ( "I" , 0 , 10 ) ;
@@ -2606,7 +2683,7 @@ class CanvasGraphics {
2606
2683
cacheId ,
2607
2684
drawnWidth ,
2608
2685
drawnHeight ,
2609
- true
2686
+ /* trackTransform */ true
2610
2687
) ;
2611
2688
const groupCtx = scratchCanvas . context ;
2612
2689
@@ -2767,7 +2844,9 @@ class CanvasGraphics {
2767
2844
return ;
2768
2845
}
2769
2846
2847
+ const count = img . count ;
2770
2848
img = this . getObject ( img . data , img ) ;
2849
+ img . count = count ;
2771
2850
2772
2851
const ctx = this . ctx ;
2773
2852
const width = img . width ,
@@ -2853,7 +2932,8 @@ class CanvasGraphics {
2853
2932
const maskCanvas = this . cachedCanvases . getCanvas (
2854
2933
"maskCanvas" ,
2855
2934
width ,
2856
- height
2935
+ height ,
2936
+ /* trackTransform */ false
2857
2937
) ;
2858
2938
const maskCtx = maskCanvas . context ;
2859
2939
maskCtx . save ( ) ;
@@ -2944,7 +3024,8 @@ class CanvasGraphics {
2944
3024
const tmpCanvas = this . cachedCanvases . getCanvas (
2945
3025
"inlineImage" ,
2946
3026
width ,
2947
- height
3027
+ height ,
3028
+ /* trackTransform */ false
2948
3029
) ;
2949
3030
const tmpCtx = tmpCanvas . context ;
2950
3031
putBinaryImageData ( tmpCtx , imgData , this . current . transferMaps ) ;
@@ -2990,7 +3071,12 @@ class CanvasGraphics {
2990
3071
const w = imgData . width ;
2991
3072
const h = imgData . height ;
2992
3073
2993
- const tmpCanvas = this . cachedCanvases . getCanvas ( "inlineImage" , w , h ) ;
3074
+ const tmpCanvas = this . cachedCanvases . getCanvas (
3075
+ "inlineImage" ,
3076
+ w ,
3077
+ h ,
3078
+ /* trackTransform */ false
3079
+ ) ;
2994
3080
const tmpCtx = tmpCanvas . context ;
2995
3081
putBinaryImageData ( tmpCtx , imgData , this . current . transferMaps ) ;
2996
3082
0 commit comments