diff --git a/src/shape/svg/svg_import.js b/src/shape/svg/svg_import.js index b0ed21c138..f82eb09052 100644 --- a/src/shape/svg/svg_import.js +++ b/src/shape/svg/svg_import.js @@ -711,11 +711,20 @@ export function SVGImportAddon(p5, fn, lifecycles) { ); this.tStack.current.scaleSelf(scaleX, scaleY); } else { - const scale = Math.min(w / vb.width, h / vb.height); // default: xMidYMid meet + const scale = Math.min(w / vb.width, h / vb.height); + + // Default SVG alignment is xMidYMid meet. + const alignment = preserveAspectRatio?.trim().split(/\s+/)[0] || "xMidYMid"; + + const alignX = alignment.startsWith("xMin") ? 0 + : alignment.startsWith("xMax") ? 1 : 0.5; + + const alignY = alignment.endsWith("YMin") ? 0 + : alignment.endsWith("YMax") ? 1 : 0.5; this.tStack.current.translateSelf( - (w - vb.width * scale) / 2 - vb.x * scale, - (h - vb.height * scale) / 2 - vb.y * scale + (w - vb.width * scale) * alignX - vb.x * scale, + (h - vb.height * scale) * alignY - vb.y * scale ); this.tStack.current.scaleSelf(scale, scale); } diff --git a/test/unit/assets/svg/defs-symbol-preserve-aspect-ratio-meet.svg b/test/unit/assets/svg/defs-symbol-preserve-aspect-ratio-meet.svg new file mode 100644 index 0000000000..1fcc6fb932 --- /dev/null +++ b/test/unit/assets/svg/defs-symbol-preserve-aspect-ratio-meet.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/unit/svg/svg_import.js b/test/unit/svg/svg_import.js index d4acf1d594..59cf8810a3 100644 --- a/test/unit/svg/svg_import.js +++ b/test/unit/svg/svg_import.js @@ -1151,6 +1151,95 @@ suite(' and elements', function () { assert.closeTo(node.state.transform.d, 1, 0.001); }); + test('use respects preserveAspectRatio=xMinYMin meet', function () { + const record = createSVG(` + + + + + + + + + `); + + const scope = firstChild(record); + const node = scope.children[0]; + + assert.closeTo(node.state.transform.a, 1, 0.001); + assert.closeTo(node.state.transform.d, 1, 0.001); + assert.closeTo(node.state.transform.e, 0, 0.001); + assert.closeTo(node.state.transform.f, 0, 0.001); + }); + + const meetAlignments = [ + ['xMinYMin', 0, 0], + ['xMidYMin', 0.5, 0], + ['xMaxYMin', 1, 0], + ['xMinYMid', 0, 0.5], + ['xMidYMid', 0.5, 0.5], + ['xMaxYMid', 1, 0.5], + ['xMinYMax', 0, 1], + ['xMidYMax', 0.5, 1], + ['xMaxYMax', 1, 1] + ]; + + for (const [alignment, alignX, alignY] of meetAlignments) { + for (const [width, height] of [[200, 100], [100, 200]]) { + test(`use respects ${alignment} meet in ${width}x${height}`, function () { + const record = createSVG(` + + + + + + + + + `); + + const node = firstChild(record).children[0]; + const scale = Math.min(width / 100, height / 100); + + assert.closeTo(node.state.transform.a, scale, 0.001); + assert.closeTo(node.state.transform.d, scale, 0.001); + assert.closeTo( + node.state.transform.e, + (width - 100 * scale) * alignX, + 0.001 + ); + assert.closeTo( + node.state.transform.f, + (height - 100 * scale) * alignY, + 0.001 + ); + }); + } + } + + test('use meet alignment accounts for viewBox origin', function () { + const record = createSVG(` + + + + + + + + + `); + + const node = firstChild(record).children[0]; + + assert.closeTo(node.state.transform.a, 1, 0.001); + assert.closeTo(node.state.transform.d, 1, 0.001); + assert.closeTo(node.state.transform.e, 90, 0.001); + assert.closeTo(node.state.transform.f, -20, 0.001); + }); + test('use with preserveAspectRatio=none accounts for viewBox origin', function () { const record = createSVG(` diff --git a/test/unit/visual/cases/svg.js b/test/unit/visual/cases/svg.js index 4a8d615a6b..754da3fac9 100644 --- a/test/unit/visual/cases/svg.js +++ b/test/unit/visual/cases/svg.js @@ -723,6 +723,16 @@ visualSuite('svg', function () { await screenshot(); }); + visualTest('symbol preserveAspectRatio meet alignments', async (p, screenshot) => { + setupDefault(p); + const shape = await loadFixture( + p, + 'defs-symbol-preserve-aspect-ratio-meet' + ); + p.shape(shape); + await screenshot(); + }); + visualTest('x and y', async (p, screenshot) => { setupDefault(p); const shape = await loadFixture(p, 'defs-use-xy'); diff --git a/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/000.png b/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/000.png new file mode 100644 index 0000000000..4e233500a2 Binary files /dev/null and b/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/000.png differ diff --git a/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/metadata.json b/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/svg/SVG Import - defs and use/symbol preserveAspectRatio meet alignments/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file