Skip to content

Commit 799a36c

Browse files
committed
More support for aspect ratio
1 parent 9132262 commit 799a36c

5 files changed

Lines changed: 88 additions & 26 deletions

File tree

examples/graphics/source/examples/Svg.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ class SvgDemo : public yup::Component
6565
return;
6666

6767
for (const auto& svgFile : files)
68+
{
69+
// if (svgFile.getFileName() == "preserveAspectRatio.svg")
6870
svgFiles.add (svgFile);
71+
}
6972

7073
svgFiles.sort();
7174
}

modules/yup_graphics/drawables/yup_Drawable.cpp

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,10 @@ void Drawable::paint (Graphics& g, const Rectangle<float>& targetArea, Fitting f
305305
const auto savedState = g.saveState();
306306

307307
auto finalBounds = data.viewBox.isEmpty() ? data.bounds : data.viewBox;
308-
auto finalTransform = calculateTransformForTarget (finalBounds, targetArea, fitting, justification);
308+
auto finalTransform = calculateTransformForTarget (finalBounds,
309+
targetArea,
310+
data.rootHasPreserveAspectRatio ? data.rootPreserveAspectRatioFitting : fitting,
311+
data.rootHasPreserveAspectRatio ? data.rootPreserveAspectRatioJustification : justification);
309312

310313
if (! finalTransform.isIdentity())
311314
g.addTransform (finalTransform);
@@ -385,22 +388,40 @@ void Drawable::paintElement (Graphics& g, const SVGData& data, const SVGElement&
385388
}
386389
}
387390

391+
const auto setViewportClip = [&g] (const Rectangle<float>& viewportBounds)
392+
{
393+
Path viewportClip;
394+
viewportClip.addRectangle (viewportBounds);
395+
auto clipTransform = g.getTransform().translated (g.getDrawingArea().getTopLeft());
396+
auto transformedViewportClip = viewportClip.transformed (clipTransform);
397+
398+
const auto savedClipTransform = g.getTransform();
399+
g.setTransform (AffineTransform::identity());
400+
g.setClipPath (transformedViewportClip);
401+
g.setTransform (savedClipTransform);
402+
};
403+
388404
if (element.viewBox && (element.viewportBounds || element.viewportSize))
389405
{
390-
auto viewport = element.viewportBounds
391-
? *element.viewportBounds
406+
auto viewport = element.viewportBounds != std::nullopt
407+
? Rectangle<float> (0.0f, 0.0f, element.viewportBounds->getWidth(), element.viewportBounds->getHeight())
392408
: Rectangle<float> (0.0f, 0.0f, element.viewportSize->getWidth(), element.viewportSize->getHeight());
409+
410+
auto viewportTransform = calculateTransformForTarget (*element.viewBox, viewport, element.preserveAspectRatioFitting, element.preserveAspectRatioJustification);
393411
if (element.tagName == "svg" && element.viewportBounds)
394-
g.setClipPath (viewport);
412+
{
413+
setViewportClip (*element.viewportBounds);
414+
viewportTransform = viewportTransform.followedBy (AffineTransform::translation (element.viewportBounds->getX(), element.viewportBounds->getY()));
415+
}
395416

396-
auto viewBoxTransform = calculateTransformForTarget (*element.viewBox, viewport, element.preserveAspectRatioFitting, element.preserveAspectRatioJustification);
397-
if (! viewBoxTransform.isIdentity())
398-
g.addTransform (viewBoxTransform);
417+
if (! viewportTransform.isIdentity())
418+
g.setTransform (viewportTransform.followedBy (g.getTransform()));
399419
}
400420
else if (element.tagName == "svg" && element.viewportBounds)
401421
{
402-
g.setClipPath (*element.viewportBounds);
403-
g.addTransform (AffineTransform::translation (element.viewportBounds->getX(), element.viewportBounds->getY()));
422+
setViewportClip (*element.viewportBounds);
423+
auto viewportTransform = AffineTransform::translation (element.viewportBounds->getX(), element.viewportBounds->getY());
424+
g.setTransform (viewportTransform.followedBy (g.getTransform()));
404425
}
405426

406427
bool hasClipping = false;
@@ -1489,8 +1510,8 @@ AffineTransform Drawable::calculateTransformForTarget (const Rectangle<float>& s
14891510
offsetY += targetArea.getHeight() - scaledHeight;
14901511

14911512
return AffineTransform::translation (-sourceBounds.getX(), -sourceBounds.getY())
1492-
.scaled (scaleX, scaleY)
1493-
.translated (offsetX, offsetY);
1513+
.followedBy (AffineTransform::scaling (scaleX, scaleY))
1514+
.followedBy (AffineTransform::translation (offsetX, offsetY));
14941515
}
14951516

14961517
//==============================================================================

modules/yup_graphics/svg/yup_SVGDocument.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ struct SVGData
5757
bool rootHasStroke = false;
5858
std::optional<Color> rootFillColor;
5959
std::optional<Color> rootStrokeColor;
60+
bool rootHasPreserveAspectRatio = false;
61+
Fitting rootPreserveAspectRatioFitting = Fitting::scaleToFit;
62+
Justification rootPreserveAspectRatioJustification = Justification::center;
6063
};
6164

6265
//==============================================================================

modules/yup_graphics/svg/yup_SVGParser.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ bool SVGParser::parseDocument (std::unique_ptr<XmlElement> svgRoot)
107107
auto height = svgRoot->getFloatAttribute ("height");
108108
data.size.setHeight (height == 0.0f ? data.viewBox.getHeight() : height);
109109

110+
if (auto preserveAspectRatio = svgRoot->getStringAttribute ("preserveAspectRatio"); preserveAspectRatio.isNotEmpty())
111+
{
112+
data.rootHasPreserveAspectRatio = true;
113+
data.rootPreserveAspectRatioFitting = parsePreserveAspectRatio (preserveAspectRatio);
114+
data.rootPreserveAspectRatioJustification = parseAspectRatioAlignment (preserveAspectRatio);
115+
}
116+
110117
YUP_DRAWABLE_LOG ("Parse complete - viewBox: " << data.viewBox.toString() << " size: " << data.size.getWidth() << "x" << data.size.getHeight());
111118

112119
std::function<void (const XmlElement&)> collectStyleElements = [&] (const XmlElement& xml)

tests/yup_graphics/yup_SVGParser.cpp

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,26 +1688,52 @@ TEST (SVGParserTests, ParseFontSizeWithUnits)
16881688

16891689
TEST (SVGParserTests, ParsePreserveAspectRatioNone)
16901690
{
1691-
Drawable d;
1692-
EXPECT_TRUE (d.parseSVG ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\"></svg>"));
1691+
auto doc = SVGParser::parse ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\"></svg>");
1692+
ASSERT_NE (doc, nullptr);
1693+
doc->visit ([] (const SVGData& data)
1694+
{
1695+
EXPECT_TRUE (data.rootHasPreserveAspectRatio);
1696+
EXPECT_EQ (Fitting::fill, data.rootPreserveAspectRatioFitting);
1697+
});
16931698
}
16941699

16951700
TEST (SVGParserTests, ParsePreserveAspectRatioXMidYMid)
16961701
{
1697-
Drawable d;
1698-
EXPECT_TRUE (d.parseSVG ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid meet\"></svg>"));
1702+
auto doc = SVGParser::parse ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid meet\"></svg>");
1703+
ASSERT_NE (doc, nullptr);
1704+
doc->visit ([] (const SVGData& data)
1705+
{
1706+
EXPECT_TRUE (data.rootHasPreserveAspectRatio);
1707+
EXPECT_EQ (Fitting::scaleToFit, data.rootPreserveAspectRatioFitting);
1708+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::horizontalCenter));
1709+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::verticalCenter));
1710+
});
16991711
}
17001712

17011713
TEST (SVGParserTests, ParsePreserveAspectRatioXMinYMin)
17021714
{
1703-
Drawable d;
1704-
EXPECT_TRUE (d.parseSVG ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMinYMin meet\"></svg>"));
1715+
auto doc = SVGParser::parse ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMinYMin meet\"></svg>");
1716+
ASSERT_NE (doc, nullptr);
1717+
doc->visit ([] (const SVGData& data)
1718+
{
1719+
EXPECT_TRUE (data.rootHasPreserveAspectRatio);
1720+
EXPECT_EQ (Fitting::scaleToFit, data.rootPreserveAspectRatioFitting);
1721+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::left));
1722+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::top));
1723+
});
17051724
}
17061725

17071726
TEST (SVGParserTests, ParsePreserveAspectRatioXMaxYMax)
17081727
{
1709-
Drawable d;
1710-
EXPECT_TRUE (d.parseSVG ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMaxYMax slice\"></svg>"));
1728+
auto doc = SVGParser::parse ("<svg viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMaxYMax slice\"></svg>");
1729+
ASSERT_NE (doc, nullptr);
1730+
doc->visit ([] (const SVGData& data)
1731+
{
1732+
EXPECT_TRUE (data.rootHasPreserveAspectRatio);
1733+
EXPECT_EQ (Fitting::scaleToFill, data.rootPreserveAspectRatioFitting);
1734+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::right));
1735+
EXPECT_TRUE (data.rootPreserveAspectRatioJustification.testFlags (Justification::bottom));
1736+
});
17111737
}
17121738

17131739
TEST (SVGParserTests, ParsePreserveAspectRatioWithInternalEntities)
@@ -1736,23 +1762,25 @@ TEST (SVGParserTests, ParsePreserveAspectRatioWithInternalEntities)
17361762

17371763
ASSERT_NE (doc, nullptr);
17381764

1739-
bool foundSmileCircle = false;
1765+
int nestedSmileCircles = 0;
17401766
doc->visit ([&] (const SVGData& data)
17411767
{
1742-
std::function<void (const SVGElement&)> visitElement = [&] (const SVGElement& element)
1768+
std::function<void (const SVGElement&, bool)> visitElement = [&] (const SVGElement& element, bool insideNestedSVG)
17431769
{
1744-
if (element.tagName == "circle")
1745-
foundSmileCircle = true;
1770+
const auto isNestedSVG = insideNestedSVG || (element.tagName == "svg" && element.viewBox.has_value());
1771+
1772+
if (isNestedSVG && element.tagName == "circle")
1773+
++nestedSmileCircles;
17461774

17471775
for (const auto& child : element.children)
1748-
visitElement (*child);
1776+
visitElement (*child, isNestedSVG);
17491777
};
17501778

17511779
for (const auto& element : data.elements)
1752-
visitElement (*element);
1780+
visitElement (*element, false);
17531781
});
17541782

1755-
EXPECT_TRUE (foundSmileCircle);
1783+
EXPECT_EQ (1, nestedSmileCircles);
17561784
}
17571785

17581786
TEST (SVGParserTests, ParsePreserveAspectRatioOnSymbol)

0 commit comments

Comments
 (0)