From adcf2a8ef210ba2e69358635ccb78b18e7d3bb03 Mon Sep 17 00:00:00 2001 From: subhranshudash13-dotcom Date: Thu, 29 Oct 2026 14:58:28 +0530 Subject: [PATCH 1/2] Fix SVG currentColor inheritance from active p5 styles (#9246) --- src/shape/svg/svg_export.js | 16 ++- src/shape/svg/svg_import.js | 9 +- test/unit/svg/svg_e2e.js | 277 ++++++++++++++++++++++++++++++++++++ test/unit/svg/svg_export.js | 123 ++++++++++++++++ test/unit/svg/svg_import.js | 157 ++++++++++++++++++++ 5 files changed, 574 insertions(+), 8 deletions(-) create mode 100644 test/unit/svg/svg_e2e.js diff --git a/src/shape/svg/svg_export.js b/src/shape/svg/svg_export.js index 27f851c157..5f9bb20a19 100644 --- a/src/shape/svg/svg_export.js +++ b/src/shape/svg/svg_export.js @@ -287,12 +287,16 @@ export function SVGExportAddon(p5, fn, lifecycles) { this._currentOpacity = 1; return 'none'; } + if (color === 'currentColor' || (typeof color === 'string' && color.toLowerCase() === 'currentcolor')) { + this._currentOpacity = 1; + return 'currentColor'; + } const [, , , alpha] = color._getRGBA([255, 255, 255, 255]); this._currentOpacity = alpha / 255; - return color.toString('#rrggbb'); - } + return color.toString('#rrggbb'); + } _applyStyle(el) { const state = this.currentState; @@ -941,14 +945,18 @@ export function SVGExportAddon(p5, fn, lifecycles) { p.applyMatrix(m.a, m.b, m.c, m.d, m.e, m.f); } - if (state.fill) { + if (state.fill === 'currentColor' || (typeof state.fill === 'string' && state.fill.toLowerCase() === 'currentcolor')) { + // Inherit active p5 fill: do not call fill() or noFill() + } else if (state.fill) { const [r, g, b, a] = state.fill._getRGBA([255, 255, 255, 255]); p.fill(r, g, b, a); } else { p.noFill(); } - if (state.stroke) { + if (state.stroke === 'currentColor' || (typeof state.stroke === 'string' && state.stroke.toLowerCase() === 'currentcolor')) { + // Inherit active p5 stroke: do not call stroke() or noStroke() + } else if (state.stroke) { const [r, g, b, a] = state.stroke._getRGBA([255, 255, 255, 255]); p.stroke(r, g, b, a); } else { diff --git a/src/shape/svg/svg_import.js b/src/shape/svg/svg_import.js index f82eb09052..dec73d9bf5 100644 --- a/src/shape/svg/svg_import.js +++ b/src/shape/svg/svg_import.js @@ -330,7 +330,7 @@ class RenderContext { this.strokeOpacity = 1; this.visibility = "visible"; this.display = "inline"; - this.color = "rgb(0, 0, 0)"; + this.color = null; //todo future properties like blendMode, etc. } } @@ -396,9 +396,10 @@ export function SVGImportAddon(p5, fn, lifecycles) { } let parsedColor = colorStr.trim(); if (parsedColor.toLowerCase() === "currentcolor") { - parsedColor = context.color || "rgb(0, 0, 0)"; - if (parsedColor.toLowerCase() === "currentcolor") { - parsedColor = "rgb(0, 0, 0)"; + if (context.color) { + parsedColor = context.color; + } else { + return "currentColor"; } } try { diff --git a/test/unit/svg/svg_e2e.js b/test/unit/svg/svg_e2e.js new file mode 100644 index 0000000000..271340b516 --- /dev/null +++ b/test/unit/svg/svg_e2e.js @@ -0,0 +1,277 @@ +import p5 from '../../../src/app.js'; + +suite('SVG currentColor End-to-End Rendering', function () { + let myp5; + + beforeEach(function () { + return new Promise((resolve) => { + myp5 = new p5((p) => { + p.setup = () => { + resolve(); + }; + }); + }); + }); + + afterEach(function () { + if (myp5) { + myp5.remove(); + myp5 = null; + } + }); + + test('reproduction: fill="currentColor" inherits active p5 fill(255, 0, 0) and renders red', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.noStroke(); + p.fill(255, 0, 0); + p.shape(svgIcon); + + const pixel = p.get(50, 50); + assert.strictEqual(pixel[0], 255, 'red channel must be 255'); + assert.strictEqual(pixel[1], 0, 'green channel must be 0'); + assert.strictEqual(pixel[2], 0, 'blue channel must be 0'); + assert.strictEqual(pixel[3], 255, 'alpha channel must be 255'); + }); + + test('stroke="currentColor" inherits active p5 stroke(0, 0, 255) and renders blue', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.noFill(); + p.stroke(0, 0, 255); + p.shape(svgIcon); + + const pixel = p.get(50, 50); + assert.strictEqual(pixel[0], 0, 'red channel must be 0'); + assert.strictEqual(pixel[1], 0, 'green channel must be 0'); + assert.strictEqual(pixel[2], 255, 'blue channel must be 255'); + assert.strictEqual(pixel[3], 255, 'alpha channel must be 255'); + }); + + test('fill="currentColor" respects active p5 noFill()', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(120, 130, 140); + + const svgIcon = p.createSVG(` + + + + `); + + p.noFill(); + p.noStroke(); + p.shape(svgIcon); + + // Canvas background should remain untouched because noFill was active + const pixel = p.get(50, 50); + assert.strictEqual(pixel[0], 120); + assert.strictEqual(pixel[1], 130); + assert.strictEqual(pixel[2], 140); + }); + + test('independent styles: fill="currentColor" with explicit stroke in SVG', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.fill(255, 0, 0); + p.stroke(0, 0, 255); // active p5 stroke is blue, but SVG specifies green stroke + p.shape(svgIcon); + + // Center should have inherited active red fill + const fillPixel = p.get(50, 50); + assert.strictEqual(fillPixel[0], 255); + assert.strictEqual(fillPixel[1], 0); + assert.strictEqual(fillPixel[2], 0); + + // Stroke edge at (50, 10) should have explicit green SVG stroke + const strokePixel = p.get(50, 10); + assert.strictEqual(strokePixel[0], 0); + assert.strictEqual(strokePixel[1], 255); + assert.strictEqual(strokePixel[2], 0); + }); + + test('consecutive shapes: state does not leak between shapes', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + + `); + + p.noStroke(); + p.fill(255, 0, 0); + p.shape(svgIcon); + + // Left rect must be explicit blue + const leftPixel = p.get(25, 50); + assert.strictEqual(leftPixel[0], 0); + assert.strictEqual(leftPixel[1], 0); + assert.strictEqual(leftPixel[2], 255); + + // Right rect must be active p5 red + const rightPixel = p.get(75, 50); + assert.strictEqual(rightPixel[0], 255); + assert.strictEqual(rightPixel[1], 0); + assert.strictEqual(rightPixel[2], 0); + }); + + test('explicit color attribute takes precedence over active p5 fill', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + // Active fill in p5 is red, but SVG has explicit color="rgb(0, 255, 0)" + p.noStroke(); + p.fill(255, 0, 0); + p.shape(svgIcon); + + const pixel = p.get(50, 50); + assert.strictEqual(pixel[0], 0, 'red channel must be 0'); + assert.strictEqual(pixel[1], 255, 'green channel must be 255'); + assert.strictEqual(pixel[2], 0, 'blue channel must be 0'); + }); + + test('stroke="currentColor" respects active p5 noStroke()', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(100, 150, 200); + + const svgIcon = p.createSVG(` + + + + `); + + p.noFill(); + p.noStroke(); + p.shape(svgIcon); + + const pixel = p.get(50, 50); + assert.strictEqual(pixel[0], 100); + assert.strictEqual(pixel[1], 150); + assert.strictEqual(pixel[2], 200); + }); + + test('re-exporting imported SVG with currentColor traverses AST and preserves attribute in getSVG', function () { + const p = myp5; + const svgIcon = p.createSVG(` + + + + `); + + // Delete sourceSVG to guarantee getSVG() traverses and serializes the AST rather than returning cached markup + delete svgIcon.sourceSVG; + + const exportedSVG = p.getSVG(svgIcon); + assert.include(exportedSVG, 'fill="currentColor"'); + assert.include(exportedSVG, 'stroke="currentColor"'); + }); + + test('inverse mixed styles: explicit SVG fill with stroke="currentColor"', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.fill(255, 0, 0); // active p5 fill is red, but SVG has explicit green fill + p.stroke(0, 0, 255); // active p5 stroke is blue, which stroke="currentColor" should inherit + p.shape(svgIcon); + + // Center should retain explicit green SVG fill + const fillPixel = p.get(50, 50); + assert.strictEqual(fillPixel[0], 0); + assert.strictEqual(fillPixel[1], 255); + assert.strictEqual(fillPixel[2], 0); + + // Stroke edge at (50, 10) should have inherited active blue p5 stroke + const strokePixel = p.get(50, 10); + assert.strictEqual(strokePixel[0], 0); + assert.strictEqual(strokePixel[1], 0); + assert.strictEqual(strokePixel[2], 255); + }); + + test('loadSVG() path: loads SVG asynchronously and inherits active p5 styles', async function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgUrl = 'data:image/svg+xml;utf8,' + encodeURIComponent(` + + + + `); + + const loadedShape = await p.loadSVG(svgUrl); + p.fill(255, 0, 0); + p.stroke(0, 0, 255); + p.shape(loadedShape); + + // Center should be active red + const fillPixel = p.get(50, 50); + assert.strictEqual(fillPixel[0], 255); + assert.strictEqual(fillPixel[1], 0); + assert.strictEqual(fillPixel[2], 0); + + // Stroke at (50, 10) should be active blue + const strokePixel = p.get(50, 10); + assert.strictEqual(strokePixel[0], 0); + assert.strictEqual(strokePixel[1], 0); + assert.strictEqual(strokePixel[2], 255); + }); + + test('full buildShape recording exports concrete hex colors', function () { + const p = myp5; + p.createCanvas(100, 100); + + p.fill(255, 0, 0); + p.stroke(0, 0, 255); + const recorded = p.buildShape(() => { + p.rect(10, 10, 50, 50); + }); + + const exportedSVG = p.getSVG(recorded); + assert.include(exportedSVG, 'fill="#ff0000"'); + assert.include(exportedSVG, 'stroke="#0000ff"'); + }); +}); + diff --git a/test/unit/svg/svg_export.js b/test/unit/svg/svg_export.js index 9aa099a1c8..b41ee6f2f7 100644 --- a/test/unit/svg/svg_export.js +++ b/test/unit/svg/svg_export.js @@ -1047,3 +1047,126 @@ suite('buildSVG — serialization', function () { assert.include(svgStr, 'height="600"'); }); }); + +suite('currentColor export and playback', function () { + test('colorToSVG converts currentColor to "currentColor" string', function () { + const pInst = createPInst(); + const visitor = createVisitor(pInst); + + assert.strictEqual(visitor.colorToSVG('currentColor'), 'currentColor'); + assert.strictEqual(visitor.colorToSVG('currentcolor'), 'currentColor'); + }); + + test('_applyStyle sets currentColor attribute on SVG elements', function () { + const pInst = createPInst(); + const visitor = createVisitor(pInst); + visitor.currentState = { + fill: 'currentColor', + stroke: 'currentColor', + strokeWeight: 2, + transform: new DOMMatrix() + }; + + const el = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + visitor._applyStyle(el); + + assert.strictEqual(el.getAttribute('fill'), 'currentColor'); + assert.strictEqual(el.getAttribute('stroke'), 'currentColor'); + assert.strictEqual(el.getAttribute('stroke-width'), '2'); + }); + + test('CanvasReplay applyState skips fill and stroke when set to currentColor', function () { + const pInst = createPInst(); + let fillCalls = 0; + let noFillCalls = 0; + let strokeCalls = 0; + let noStrokeCalls = 0; + + pInst.fill = () => { fillCalls++; }; + pInst.noFill = () => { noFillCalls++; }; + pInst.stroke = () => { strokeCalls++; }; + pInst.noStroke = () => { noStrokeCalls++; }; + + const mockRecord = { + children: [ + { + type: 'shape', + shape: { accept() {} }, + state: { + fill: 'currentColor', + stroke: 'currentColor' + } + } + ] + }; + + pInst.shape(mockRecord); + + assert.strictEqual(fillCalls, 0, 'fill() must not be called for currentColor'); + assert.strictEqual(noFillCalls, 0, 'noFill() must not be called for currentColor'); + assert.strictEqual(strokeCalls, 0, 'stroke() must not be called for currentColor'); + assert.strictEqual(noStrokeCalls, 0, 'noStroke() must not be called for currentColor'); + }); + + test('CanvasReplay applyState applies concrete colors and handles null colors', function () { + const pInst = createPInst(); + const fillCalls = []; + let noFillCalls = 0; + const strokeCalls = []; + let noStrokeCalls = 0; + + pInst.fill = (...args) => { fillCalls.push(args); }; + pInst.noFill = () => { noFillCalls++; }; + pInst.stroke = (...args) => { strokeCalls.push(args); }; + pInst.noStroke = () => { noStrokeCalls++; }; + + const mockRecord = { + children: [ + { + type: 'shape', + shape: { accept() {} }, + state: { + fill: createMockColor(255, 0, 0, 255), + stroke: null + } + }, + { + type: 'shape', + shape: { accept() {} }, + state: { + fill: null, + stroke: createMockColor(0, 0, 255, 255) + } + } + ] + }; + + pInst.shape(mockRecord); + + assert.strictEqual(fillCalls.length, 1); + assert.deepEqual(fillCalls[0], [255, 0, 0, 255]); + assert.strictEqual(noStrokeCalls, 1); + + assert.strictEqual(noFillCalls, 1); + assert.strictEqual(strokeCalls.length, 1); + assert.deepEqual(strokeCalls[0], [0, 0, 255, 255]); + }); + + test('recorded shapes with active p5 styles export concrete colors', function () { + const pInst = createPInst(); + pInst._renderer.strokeCap = () => 'butt'; + pInst._renderer.states.fillColor = createMockColor(255, 100, 50, 255, '#ff6432'); + pInst._renderer.states.strokeColor = createMockColor(10, 20, 30, 255, '#0a141e'); + + const state = pInst._svgCaptureState(); + const visitor = createVisitor(pInst); + visitor.currentState = state; + + const el = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + visitor._applyStyle(el); + + assert.strictEqual(el.getAttribute('fill'), '#ff6432'); + assert.strictEqual(el.getAttribute('stroke'), '#0a141e'); + }); +}); + diff --git a/test/unit/svg/svg_import.js b/test/unit/svg/svg_import.js index 59cf8810a3..3ebcaa3834 100644 --- a/test/unit/svg/svg_import.js +++ b/test/unit/svg/svg_import.js @@ -1314,3 +1314,160 @@ suite('Path parser — T and S fallback (no preceding matching command)', functi assert.closeTo(bv[0].y, 0, 0.001); }); }); + +suite('currentColor inheritance during import', function () { + test('fill="currentColor" without ancestor color retains "currentColor" marker', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.strictEqual(node.state.fill, 'currentColor'); + }); + + test('stroke="currentColor" without ancestor color retains "currentColor" marker', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.strictEqual(node.state.stroke, 'currentColor'); + }); + + test('fill="currentColor" in inline style retains "currentColor" marker', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.strictEqual(node.state.fill, 'currentColor'); + }); + + test('fill="currentColor" resolves to ancestor color attribute when present', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.isNotNull(node.state.fill); + assert.notStrictEqual(node.state.fill, 'currentColor'); + assert.strictEqual(node.state.fill._src, '#e74c3c'); + }); + + test('stroke="currentColor" resolves to nested ancestor color override', function () { + const record = createSVG(` + + + + + + `); + // Scope -> Scope -> ShapeNode + const gScope = record.children[0]; + const node = gScope.children[0]; + assert.isNotNull(node.state.stroke); + assert.notStrictEqual(node.state.stroke, 'currentColor'); + assert.strictEqual(node.state.stroke._src, '#3498db'); + }); + + test('unspecified fill defaults to black, unspecified stroke defaults to null', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.isNotNull(node.state.fill); + assert.notStrictEqual(node.state.fill, 'currentColor'); + assert.strictEqual(node.state.fill._src, 'rgb(0, 0, 0)'); + assert.isNull(node.state.stroke); + }); + + test('explicit fill and stroke overrides are preserved', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.strictEqual(node.state.fill._src, 'blue'); + assert.strictEqual(node.state.stroke._src, 'red'); + }); + + test('color attribute on the same element resolves currentColor', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.isNotNull(node.state.fill); + assert.notStrictEqual(node.state.fill, 'currentColor'); + assert.strictEqual(node.state.fill._src, 'blue'); + }); + + test('color in inline style on the same element resolves currentColor', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.isNotNull(node.state.fill); + assert.notStrictEqual(node.state.fill, 'currentColor'); + assert.strictEqual(node.state.fill._src, 'green'); + }); + + test('child element color overrides parent color for currentColor', function () { + const record = createSVG(` + + + + `); + const node = firstChild(record); + assert.strictEqual(node.state.fill._src, 'blue'); + }); + + test('deeply nested groups inherit ancestor color without redeclaring', function () { + const record = createSVG(` + + + + + + + + `); + const outerGroup = record.children[0]; + const innerGroup = outerGroup.children[0]; + const node = innerGroup.children[0]; + assert.strictEqual(node.state.fill._src, 'purple'); + }); + + test('independent fill and stroke markers on the same shape', function () { + const record = createSVG(` + + + + + + `); + const circle = record.children[0]; + const rect = record.children[1]; + const line = record.children[2]; + + assert.strictEqual(circle.state.fill, 'currentColor'); + assert.strictEqual(circle.state.stroke._src, 'black'); + + assert.strictEqual(rect.state.fill._src, 'yellow'); + assert.strictEqual(rect.state.stroke, 'currentColor'); + + assert.strictEqual(line.state.fill, 'currentColor'); + assert.strictEqual(line.state.stroke, 'currentColor'); + }); +}); + From 88c0f04bb0c55f053915b83fde4cf0bfe4ca761c Mon Sep 17 00:00:00 2001 From: subhranshudash13-dotcom Date: Thu, 5 Nov 2026 13:40:13 +0530 Subject: [PATCH 2/2] fix(svg): respect opacity attributes when using currentColor --- src/shape/svg/svg_export.js | 14 +++++++--- src/shape/svg/svg_import.js | 4 +-- test/unit/svg/svg_e2e.js | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/shape/svg/svg_export.js b/src/shape/svg/svg_export.js index 5f9bb20a19..f9f81a8058 100644 --- a/src/shape/svg/svg_export.js +++ b/src/shape/svg/svg_export.js @@ -307,11 +307,11 @@ export function SVGExportAddon(p5, fn, lifecycles) { this._currentOpacity = 1; const fill = this.colorToSVG(state.fill); - const fillOpacity = this._currentOpacity; + const fillOpacity = fill === 'currentColor' && state.fillOpacity != null ? state.fillOpacity : this._currentOpacity; this._currentOpacity = 1; const stroke = this.colorToSVG(state.stroke); - const strokeOpacity = this._currentOpacity; + const strokeOpacity = stroke === 'currentColor' && state.strokeOpacity != null ? state.strokeOpacity : this._currentOpacity; el.setAttribute('fill', fill); el.setAttribute('stroke', stroke); @@ -946,7 +946,10 @@ export function SVGExportAddon(p5, fn, lifecycles) { } if (state.fill === 'currentColor' || (typeof state.fill === 'string' && state.fill.toLowerCase() === 'currentcolor')) { - // Inherit active p5 fill: do not call fill() or noFill() + if (state.fillOpacity != null && state.fillOpacity < 1 && p._renderer.states.fillColor) { + const [r, g, b, a] = p._renderer.states.fillColor._getRGBA([255, 255, 255, 255]); + p.fill(r, g, b, a * state.fillOpacity); + } } else if (state.fill) { const [r, g, b, a] = state.fill._getRGBA([255, 255, 255, 255]); p.fill(r, g, b, a); @@ -955,7 +958,10 @@ export function SVGExportAddon(p5, fn, lifecycles) { } if (state.stroke === 'currentColor' || (typeof state.stroke === 'string' && state.stroke.toLowerCase() === 'currentcolor')) { - // Inherit active p5 stroke: do not call stroke() or noStroke() + if (state.strokeOpacity != null && state.strokeOpacity < 1 && p._renderer.states.strokeColor) { + const [r, g, b, a] = p._renderer.states.strokeColor._getRGBA([255, 255, 255, 255]); + p.stroke(r, g, b, a * state.strokeOpacity); + } } else if (state.stroke) { const [r, g, b, a] = state.stroke._getRGBA([255, 255, 255, 255]); p.stroke(r, g, b, a); diff --git a/src/shape/svg/svg_import.js b/src/shape/svg/svg_import.js index 367a803cd9..f1a2a50c2a 100644 --- a/src/shape/svg/svg_import.js +++ b/src/shape/svg/svg_import.js @@ -426,8 +426,8 @@ export function SVGImportAddon(p5, fn, lifecycles) { strokeWeight: context.strokeWidth, strokeCap: context.strokeCap, renderContext: context.clone(), - fillOpacity: context.fillOpacity, - strokeOpacity: context.strokeOpacity, + fillOpacity: context.opacity * context.fillOpacity, + strokeOpacity: context.opacity * context.strokeOpacity, }; } diff --git a/test/unit/svg/svg_e2e.js b/test/unit/svg/svg_e2e.js index 271340b516..87c377e616 100644 --- a/test/unit/svg/svg_e2e.js +++ b/test/unit/svg/svg_e2e.js @@ -273,5 +273,56 @@ suite('SVG currentColor End-to-End Rendering', function () { assert.include(exportedSVG, 'fill="#ff0000"'); assert.include(exportedSVG, 'stroke="#0000ff"'); }); + + test('currentColor with fill-opacity, stroke-opacity, and opacity applies transparency in rendering and export', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.fill(0, 0, 0); + p.noStroke(); + p.shape(svgIcon); + + const fillPixel = p.get(50, 50); + assert.closeTo(fillPixel[0], 128, 5); + assert.closeTo(fillPixel[1], 128, 5); + assert.closeTo(fillPixel[2], 128, 5); + + delete svgIcon.sourceSVG; + const exportedSVG = p.getSVG(svgIcon); + assert.include(exportedSVG, 'fill="currentColor"'); + assert.include(exportedSVG, 'fill-opacity="0.5000"'); + }); + + test('currentColor stroke with stroke-opacity applies transparency in rendering and export', function () { + const p = myp5; + p.createCanvas(100, 100); + p.background(255, 255, 255); + + const svgIcon = p.createSVG(` + + + + `); + + p.stroke(0, 0, 0); + p.shape(svgIcon); + + const strokePixel = p.get(50, 50); + assert.closeTo(strokePixel[0], 128, 5); + assert.closeTo(strokePixel[1], 128, 5); + assert.closeTo(strokePixel[2], 128, 5); + + delete svgIcon.sourceSVG; + const exportedSVG = p.getSVG(svgIcon); + assert.include(exportedSVG, 'stroke="currentColor"'); + assert.include(exportedSVG, 'stroke-opacity="0.5000"'); + }); });