Skip to content

Commit 4cae056

Browse files
committed
Fix #174
1 parent 997ed68 commit 4cae056

File tree

2 files changed

+188
-51
lines changed

2 files changed

+188
-51
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Added support for luminance masking (#174)
44
* Fixed a bug in calculating text widths when using font features (#175)
55
* Added support for path stroking and filling (#174)
6+
* Added support for groups along with affine transformation and color blending.
7+
No support for Porter-Duff composition as it is porly supported in SVG (#174)
68

79
# svglite 2.1.3
810

src/devSVG.cpp

Lines changed: 186 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class SVGDesc {
7777
std::unordered_set<unsigned int> pattern_cache;
7878
unsigned int pattern_cache_next_id;
7979

80+
std::unordered_set<unsigned int> group_cache;
81+
unsigned int group_cache_next_id;
82+
8083
SVGDesc(SvgStreamPtr stream_, bool standalone_, cpp11::list aliases_,
8184
const std::string webfonts_, const std::string& file_, cpp11::strings ids_,
8285
bool fix_text_size_, double scaling_, bool always_valid_):
@@ -98,7 +101,8 @@ class SVGDesc {
98101
is_recording_clip(false),
99102
mask_cache_next_id(0),
100103
current_mask(-1),
101-
pattern_cache_next_id(0) {
104+
pattern_cache_next_id(0),
105+
group_cache_next_id(0) {
102106
}
103107

104108
void nextFile() {
@@ -630,6 +634,7 @@ void svg_new_page(const pGEcontext gc, pDevDesc dd) {
630634
svgd->clip_cache_next_id = 0;
631635
svgd->mask_cache_next_id = 0;
632636
svgd->pattern_cache_next_id = 0;
637+
svgd->group_cache_next_id = 0;
633638

634639
if (svgd->pageno > 0) {
635640
// close existing file, create a new one, and update stream
@@ -1348,70 +1353,182 @@ inline std::string composite_operator(int op) {
13481353
std::string comp_op = "normal";
13491354
#if R_GE_version >= 15
13501355
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;
13761381
}
13771382
#endif
13781383
return comp_op;
13791384
}
13801385

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+
13831392
#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+
}
13971454
}
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);
13981475
#endif
1399-
return is_blend;
1476+
1477+
return Rf_ScalarInteger(key);
14001478
}
14011479

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+
}
14051496

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";
14071509

1510+
if (has_transform) {
1511+
(*stream) << " </g>\n";
14081512
}
1409-
return R_NilValue;
1513+
1514+
return;
14101515
}
14111516

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+
}
14131523

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+
}
14151532

14161533
void svg_stroke(SEXP path, const pGEcontext gc, pDevDesc dd) {
14171534
if (Rf_isNull(path)) {
@@ -1558,10 +1675,28 @@ SEXP svg_capabilities(SEXP capabilities) {
15581675
UNPROTECT(1);
15591676

15601677
// 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);
15621697

15631698
// 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));
15651700

15661701
// Path stroking and filling
15671702
SET_VECTOR_ELT(capabilities, R_GE_capability_paths, Rf_ScalarInteger(1));

0 commit comments

Comments
 (0)