@@ -77,6 +77,9 @@ class SVGDesc {
77
77
std::unordered_set<unsigned int > pattern_cache;
78
78
unsigned int pattern_cache_next_id;
79
79
80
+ std::unordered_set<unsigned int > group_cache;
81
+ unsigned int group_cache_next_id;
82
+
80
83
SVGDesc (SvgStreamPtr stream_, bool standalone_, cpp11::list aliases_,
81
84
const std::string webfonts_, const std::string& file_, cpp11::strings ids_,
82
85
bool fix_text_size_, double scaling_, bool always_valid_):
@@ -98,7 +101,8 @@ class SVGDesc {
98
101
is_recording_clip (false ),
99
102
mask_cache_next_id (0 ),
100
103
current_mask (-1 ),
101
- pattern_cache_next_id (0 ) {
104
+ pattern_cache_next_id (0 ),
105
+ group_cache_next_id (0 ) {
102
106
}
103
107
104
108
void nextFile () {
@@ -630,6 +634,7 @@ void svg_new_page(const pGEcontext gc, pDevDesc dd) {
630
634
svgd->clip_cache_next_id = 0 ;
631
635
svgd->mask_cache_next_id = 0 ;
632
636
svgd->pattern_cache_next_id = 0 ;
637
+ svgd->group_cache_next_id = 0 ;
633
638
634
639
if (svgd->pageno > 0 ) {
635
640
// close existing file, create a new one, and update stream
@@ -1348,70 +1353,182 @@ inline std::string composite_operator(int op) {
1348
1353
std::string comp_op = " normal" ;
1349
1354
#if R_GE_version >= 15
1350
1355
switch (op) {
1351
- case R_GE_compositeClear :
1352
- case R_GE_compositeSource :
1353
- case R_GE_compositeDest :
1354
- case R_GE_compositeDestOver :
1355
- case R_GE_compositeDestIn :
1356
- case R_GE_compositeDestOut :
1357
- case R_GE_compositeDestAtop: cpp11::warning ( " Unsupported composition operator. Fallowing back to `over` " );
1358
- case R_GE_compositeOver: comp_op = " normal " ; break ;
1359
- case R_GE_compositeIn: comp_op = " in " ; break ;
1360
- case R_GE_compositeOut : comp_op = " out " ; break ;
1361
- case R_GE_compositeAtop : comp_op = " atop " ; break ;
1362
- case R_GE_compositeXor : comp_op = " xor " ; break ;
1363
- case R_GE_compositeAdd: comp_op = " plus-lighter" ; break ;
1364
- case R_GE_compositeSaturate: comp_op = " saturation" ; break ;
1365
- case R_GE_compositeMultiply: comp_op = " multiply" ; break ;
1366
- case R_GE_compositeScreen: comp_op = " screen" ; break ;
1367
- case R_GE_compositeOverlay: comp_op = " overlay" ; break ;
1368
- case R_GE_compositeDarken: comp_op = " darken" ; break ;
1369
- case R_GE_compositeLighten: comp_op = " lighten" ; break ;
1370
- case R_GE_compositeColorDodge: comp_op = " color-dodge" ; break ;
1371
- case R_GE_compositeColorBurn: comp_op = " color-burn" ; break ;
1372
- case R_GE_compositeHardLight: comp_op = " hard-light" ; break ;
1373
- case R_GE_compositeSoftLight: comp_op = " soft-light" ; break ;
1374
- case R_GE_compositeDifference: comp_op = " difference" ; break ;
1375
- case R_GE_compositeExclusion: comp_op = " exclusion" ; break ;
1356
+ case R_GE_compositeDestOver :
1357
+ case R_GE_compositeDestIn :
1358
+ case R_GE_compositeDestOut :
1359
+ case R_GE_compositeIn :
1360
+ case R_GE_compositeOut :
1361
+ case R_GE_compositeAtop :
1362
+ case R_GE_compositeXor:
1363
+ case R_GE_compositeSource:
1364
+ case R_GE_compositeDestAtop: cpp11::warning ( " Unsupported composition operator. Fallowing back to `over` " ) ;
1365
+ case R_GE_compositeOver : comp_op = " normal " ; break ;
1366
+ case R_GE_compositeDest : comp_op = " destination " ; break ; // We can fake this as a blend mode
1367
+ case R_GE_compositeClear : comp_op = " clear " ; break ; // We can fake this as a blend mode
1368
+ case R_GE_compositeAdd: comp_op = " plus-lighter" ; break ;
1369
+ case R_GE_compositeSaturate: comp_op = " saturation" ; break ;
1370
+ case R_GE_compositeMultiply: comp_op = " multiply" ; break ;
1371
+ case R_GE_compositeScreen: comp_op = " screen" ; break ;
1372
+ case R_GE_compositeOverlay: comp_op = " overlay" ; break ;
1373
+ case R_GE_compositeDarken: comp_op = " darken" ; break ;
1374
+ case R_GE_compositeLighten: comp_op = " lighten" ; break ;
1375
+ case R_GE_compositeColorDodge: comp_op = " color-dodge" ; break ;
1376
+ case R_GE_compositeColorBurn: comp_op = " color-burn" ; break ;
1377
+ case R_GE_compositeHardLight: comp_op = " hard-light" ; break ;
1378
+ case R_GE_compositeSoftLight: comp_op = " soft-light" ; break ;
1379
+ case R_GE_compositeDifference: comp_op = " difference" ; break ;
1380
+ case R_GE_compositeExclusion: comp_op = " exclusion" ; break ;
1376
1381
}
1377
1382
#endif
1378
1383
return comp_op;
1379
1384
}
1380
1385
1381
- inline bool composite_is_blend (int op) {
1382
- bool is_blend = true ;
1386
+ SEXP svg_define_group (SEXP source, int op, SEXP destination, pDevDesc dd) {
1387
+ SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific ;
1388
+ SvgStreamPtr stream = svgd->stream ;
1389
+
1390
+ int key = -1 ;
1391
+
1383
1392
#if R_GE_version >= 15
1384
- switch (op) {
1385
- case R_GE_compositeClear:
1386
- case R_GE_compositeSource:
1387
- case R_GE_compositeDest:
1388
- case R_GE_compositeDestOver:
1389
- case R_GE_compositeDestIn:
1390
- case R_GE_compositeDestOut:
1391
- case R_GE_compositeDestAtop:
1392
- case R_GE_compositeIn:
1393
- case R_GE_compositeOut:
1394
- case R_GE_compositeAtop:
1395
- case R_GE_compositeXor: is_blend = false ; break ;
1396
- default : is_blend = true ;
1393
+ key = svgd->group_cache_next_id ;
1394
+ svgd->group_cache_next_id ++;
1395
+
1396
+ // Cache current clipping and break out of clipping group
1397
+ bool was_clipping = svgd->is_clipping ;
1398
+ std::string old_clipid = svgd->clipid ;
1399
+ double clipx0 = svgd->clipx0 ;
1400
+ double clipx1 = svgd->clipx1 ;
1401
+ double clipy0 = svgd->clipy0 ;
1402
+ double clipy1 = svgd->clipy1 ;
1403
+ int temp_mask = svgd->current_mask ;
1404
+ svgd->current_mask = -1 ;
1405
+ if (was_clipping) {
1406
+ (*stream) << " </g>\n " ;
1407
+ }
1408
+ svgd->set_clipping (false );
1409
+
1410
+ (*stream) << " <defs>\n " ;
1411
+
1412
+ if (op == R_GE_compositeClear) {
1413
+ source = R_NilValue;
1414
+ destination = R_NilValue;
1415
+ op = R_GE_compositeOver;
1416
+ } else if (op == R_GE_compositeDest) {
1417
+ source = R_NilValue;
1418
+ op = R_GE_compositeOver;
1419
+ }
1420
+
1421
+ bool is_simple = op == R_GE_compositeOver;
1422
+
1423
+ std::string blend_op = composite_operator (op);
1424
+ (*stream) << " <g id='group-" << key << (is_simple ? " '" : " ' style='isolation:isolate;'" ) << " >\n " ;
1425
+
1426
+ if (destination != R_NilValue) {
1427
+ SEXP R_fcall = PROTECT (Rf_lang1 (destination));
1428
+ Rf_eval (R_fcall, R_GlobalEnv);
1429
+ UNPROTECT (1 );
1430
+
1431
+ // Clipping may have happened above. End it before terminating mask
1432
+ if (svgd->is_clipping ) {
1433
+ (*stream) << " </g>\n " ;
1434
+ }
1435
+ svgd->set_clipping (false );
1436
+ }
1437
+
1438
+ if (source != R_NilValue) {
1439
+ if (!is_simple) {
1440
+ (*stream) << " <g style='mix-blend-mode:" << blend_op << " ;'>\n " ;
1441
+ }
1442
+ SEXP R_fcall1 = PROTECT (Rf_lang1 (source));
1443
+ Rf_eval (R_fcall1, R_GlobalEnv);
1444
+ UNPROTECT (1 );
1445
+ // Clipping may have happened above. End it before terminating mask
1446
+ if (svgd->is_clipping ) {
1447
+ (*stream) << " </g>\n " ;
1448
+ }
1449
+ svgd->set_clipping (false );
1450
+
1451
+ if (!is_simple) {
1452
+ (*stream) << " </g>\n " ;
1453
+ }
1397
1454
}
1455
+
1456
+ (*stream) << " </g>\n " ;
1457
+
1458
+ (*stream) << " </defs>\n " ;
1459
+
1460
+ // Resume old clipping if it was happening
1461
+ if (was_clipping) {
1462
+ (*stream) << " <g" ;
1463
+ svgd->clipid = old_clipid;
1464
+ svgd->clipx0 = clipx0;
1465
+ svgd->clipx1 = clipx1;
1466
+ svgd->clipy0 = clipy0;
1467
+ svgd->clipy1 = clipy1;
1468
+ write_attr_clip (stream, svgd->clipid );
1469
+ (*stream) << " >\n " ;
1470
+ svgd->set_clipping (true );
1471
+ }
1472
+ svgd->current_mask = temp_mask;
1473
+
1474
+ svgd->group_cache .insert (key);
1398
1475
#endif
1399
- return is_blend;
1476
+
1477
+ return Rf_ScalarInteger (key);
1400
1478
}
1401
1479
1402
- SEXP svg_define_group (SEXP source, int op, SEXP destination, pDevDesc dd) {
1403
- if (composite_is_blend (op)) {
1404
- std::string blend_op = composite_operator (op);
1480
+ void svg_use_group (SEXP ref, SEXP trans, pDevDesc dd) {
1481
+ SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific ;
1482
+ SvgStreamPtr stream = svgd->stream ;
1483
+ if (Rf_isNull (ref)) {
1484
+ return ;
1485
+ }
1486
+ int key = INTEGER (ref)[0 ];
1487
+ if (key < 0 ) {
1488
+ cpp11::warning (" Unknown group, %i" , key);
1489
+ return ;
1490
+ }
1491
+ auto it = svgd->group_cache .find (key);
1492
+ if (it == svgd->group_cache .end ()) {
1493
+ cpp11::warning (" Unknown group, %i" , key);
1494
+ return ;
1495
+ }
1405
1496
1406
- } else {
1497
+ bool has_transform = trans != R_NilValue;
1498
+ if (has_transform) {
1499
+ (*stream) << " <g style='transform:matrix(" <<
1500
+ REAL (trans)[0 ] << " ," <<
1501
+ REAL (trans)[3 ] << " ," <<
1502
+ REAL (trans)[1 ] << " ," <<
1503
+ REAL (trans)[4 ] << " ," <<
1504
+ REAL (trans)[2 ] << " ," <<
1505
+ REAL (trans)[5 ] << " );'>\n " ;
1506
+ }
1507
+
1508
+ (*stream) << " <use href='#group-" << key << " ' />\n " ;
1407
1509
1510
+ if (has_transform) {
1511
+ (*stream) << " </g>\n " ;
1408
1512
}
1409
- return R_NilValue;
1513
+
1514
+ return ;
1410
1515
}
1411
1516
1412
- void svg_use_group (SEXP ref, SEXP trans, pDevDesc dd) {}
1517
+ void svg_release_group (SEXP ref, pDevDesc dd) {
1518
+ SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific ;
1519
+ if (Rf_isNull (ref)) {
1520
+ svgd->group_cache .clear ();
1521
+ return ;
1522
+ }
1413
1523
1414
- void svg_release_group (SEXP ref, pDevDesc dd) {}
1524
+ unsigned int key = INTEGER (ref)[0 ];
1525
+
1526
+ auto it = svgd->group_cache .find (key);
1527
+ // Check if path exists
1528
+ if (it != svgd->group_cache .end ()) {
1529
+ svgd->group_cache .erase (it);
1530
+ }
1531
+ }
1415
1532
1416
1533
void svg_stroke (SEXP path, const pGEcontext gc, pDevDesc dd) {
1417
1534
if (Rf_isNull (path)) {
@@ -1558,10 +1675,28 @@ SEXP svg_capabilities(SEXP capabilities) {
1558
1675
UNPROTECT (1 );
1559
1676
1560
1677
// Group composition
1561
- SET_VECTOR_ELT (capabilities, R_GE_capability_compositing, Rf_ScalarInteger (0 ));
1678
+ SEXP compositing = PROTECT (Rf_allocVector (INTSXP, 16 ));
1679
+ INTEGER (compositing)[0 ] = R_GE_compositeMultiply;
1680
+ INTEGER (compositing)[1 ] = R_GE_compositeScreen;
1681
+ INTEGER (compositing)[2 ] = R_GE_compositeOverlay;
1682
+ INTEGER (compositing)[3 ] = R_GE_compositeDarken;
1683
+ INTEGER (compositing)[4 ] = R_GE_compositeLighten;
1684
+ INTEGER (compositing)[5 ] = R_GE_compositeColorDodge;
1685
+ INTEGER (compositing)[6 ] = R_GE_compositeColorBurn;
1686
+ INTEGER (compositing)[7 ] = R_GE_compositeHardLight;
1687
+ INTEGER (compositing)[8 ] = R_GE_compositeSoftLight;
1688
+ INTEGER (compositing)[9 ] = R_GE_compositeDifference;
1689
+ INTEGER (compositing)[10 ] = R_GE_compositeExclusion;
1690
+ INTEGER (compositing)[11 ] = R_GE_compositeAdd;
1691
+ INTEGER (compositing)[12 ] = R_GE_compositeSaturate;
1692
+ INTEGER (compositing)[13 ] = R_GE_compositeOver;
1693
+ INTEGER (compositing)[14 ] = R_GE_compositeClear;
1694
+ INTEGER (compositing)[15 ] = R_GE_compositeDest;
1695
+ SET_VECTOR_ELT (capabilities, R_GE_capability_compositing, compositing);
1696
+ UNPROTECT (1 );
1562
1697
1563
1698
// Group transformation
1564
- SET_VECTOR_ELT (capabilities, R_GE_capability_transformations, Rf_ScalarInteger (0 ));
1699
+ SET_VECTOR_ELT (capabilities, R_GE_capability_transformations, Rf_ScalarInteger (1 ));
1565
1700
1566
1701
// Path stroking and filling
1567
1702
SET_VECTOR_ELT (capabilities, R_GE_capability_paths, Rf_ScalarInteger (1 ));
0 commit comments