LCOV - code coverage report
Current view: top level - src/engraving/infrastructure - shape.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 60.8 % 314 191
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2023 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include <cfloat>
      23              : 
      24              : #include "shape.h"
      25              : 
      26              : #include "draw/painter.h"
      27              : 
      28              : #include "dom/engravingitem.h"
      29              : 
      30              : #include "log.h"
      31              : 
      32              : using namespace mu;
      33              : using namespace muse;
      34              : using namespace muse::draw;
      35              : using namespace mu::engraving;
      36              : 
      37          196 : Shape::Shape(const std::vector<RectF>& rects, const EngravingItem* p)
      38              : {
      39          196 :     m_type = Type::Composite;
      40          196 :     m_elements.reserve(rects.size());
      41          894 :     for (const RectF& rect : rects) {
      42          698 :         m_elements.emplace_back(ShapeElement(rect, p));
      43              :     }
      44          196 : }
      45              : 
      46              : //---------------------------------------------------------
      47              : //   addHorizontalSpacing
      48              : //    This methods creates "walls". They are represented by
      49              : //    rectangles of zero height, and it is assumed that rectangles
      50              : //    of zero height vertically collide with everything. Use this
      51              : //    method ONLY when you want to create space that cannot tuck
      52              : //    above/below other elements of the staff.
      53              : //---------------------------------------------------------
      54              : 
      55        21890 : void Shape::addHorizontalSpacing(EngravingItem* item, double leftEdge, double rightEdge)
      56              : {
      57        21890 :     constexpr double eps = 100 * std::numeric_limits<double>::epsilon();
      58        21890 :     if (leftEdge == rightEdge) { // HACK zero-width shapes collide with everything currently.
      59         2171 :         rightEdge += eps;
      60              :     }
      61        21890 :     add(RectF(leftEdge, 0, rightEdge - leftEdge, 0), item);
      62        21890 : }
      63              : 
      64              : //---------------------------------------------------------
      65              : //   translate
      66              : //---------------------------------------------------------
      67              : 
      68      2894290 : Shape& Shape::translate(const PointF& pt)
      69              : {
      70      6886614 :     for (RectF& r : m_elements) {
      71      3992324 :         r.translate(pt);
      72              :     }
      73      2894290 :     invalidateBBox();
      74      2894290 :     return *this;
      75              : }
      76              : 
      77        13700 : void Shape::translateX(double xo)
      78              : {
      79        60985 :     for (RectF& r : m_elements) {
      80        47285 :         r.setLeft(r.left() + xo);
      81        47285 :         r.setRight(r.right() + xo);
      82              :     }
      83        13700 :     invalidateBBox();
      84        13700 : }
      85              : 
      86        92555 : void Shape::translateY(double yo)
      87              : {
      88       232055 :     for (RectF& r : m_elements) {
      89       139500 :         r.setTop(r.top() + yo);
      90       139500 :         r.setBottom(r.bottom() + yo);
      91              :     }
      92        92555 :     invalidateBBox();
      93        92555 : }
      94              : 
      95              : //---------------------------------------------------------
      96              : //   translated
      97              : //---------------------------------------------------------
      98              : 
      99       180288 : Shape Shape::translated(const PointF& pt) const
     100              : {
     101       180288 :     Shape s;
     102       180288 :     s.m_elements.reserve(m_elements.size());
     103       526558 :     for (const ShapeElement& r : m_elements) {
     104       346270 :         s.add(r.translated(pt), r.item(), r.ignoreForLayout());
     105              :     }
     106       180288 :     return s;
     107            0 : }
     108              : 
     109            0 : Shape& Shape::scale(const SizeF& mag)
     110              : {
     111            0 :     for (RectF& r : m_elements) {
     112            0 :         r.scale(mag);
     113              :     }
     114            0 :     invalidateBBox();
     115            0 :     return *this;
     116              : }
     117              : 
     118        43520 : Shape Shape::scaled(const SizeF& mag) const
     119              : {
     120        43520 :     Shape s;
     121        43520 :     s.m_elements.reserve(m_elements.size());
     122       179120 :     for (const ShapeElement& r : m_elements) {
     123       135600 :         s.add(r.scaled(mag), r.item(), r.ignoreForLayout());
     124              :     }
     125        43520 :     return s;
     126            0 : }
     127              : 
     128         2530 : Shape& Shape::adjust(double xp1, double yp1, double xp2, double yp2)
     129              : {
     130        21603 :     for (ShapeElement& element : m_elements) {
     131        19073 :         element.adjust(xp1, yp1, xp2, yp2);
     132              :     }
     133         2530 :     return *this;
     134              : }
     135              : 
     136          514 : Shape Shape::adjusted(double xp1, double yp1, double xp2, double yp2) const
     137              : {
     138          514 :     Shape s;
     139          514 :     s.m_elements.reserve(m_elements.size());
     140         1388 :     for (const ShapeElement& element : m_elements) {
     141          874 :         s.add(element.adjusted(xp1, yp1, xp2, yp2));
     142              :     }
     143              : 
     144          514 :     return s;
     145            0 : }
     146              : 
     147          999 : Shape& Shape::pad(double p)
     148              : {
     149         1998 :     for (ShapeElement& el : m_elements) {
     150          999 :         el.pad(p);
     151              :     }
     152          999 :     return *this;
     153              : }
     154              : 
     155            0 : Shape Shape::padded(double p) const
     156              : {
     157            0 :     Shape s;
     158            0 :     s.m_elements.reserve(m_elements.size());
     159            0 :     for (const ShapeElement& el : m_elements) {
     160            0 :         s.add(el.padded(p));
     161              :     }
     162            0 :     return s;
     163            0 : }
     164              : 
     165            0 : mu::engraving::Shape& mu::engraving::Shape::transform(const muse::draw::Transform& t)
     166              : {
     167            0 :     for (ShapeElement& el : m_elements) {
     168            0 :         bool ignoreForLayout = el.ignoreForLayout();
     169            0 :         const EngravingItem* item = el.item();
     170            0 :         RectF rect = t.map(el);
     171            0 :         el = ShapeElement(rect, item, ignoreForLayout);
     172              :     }
     173              : 
     174            0 :     return *this;
     175              : }
     176              : 
     177            0 : Shape Shape::transformed(const muse::draw::Transform& t)
     178              : {
     179            0 :     Shape s(*this);
     180            0 :     return s.transform(t);
     181            0 : }
     182              : 
     183      8346949 : void Shape::invalidateBBox()
     184              : {
     185      8346949 :     m_bbox = RectF();
     186      8346949 : }
     187              : 
     188      3003584 : const RectF& Shape::bbox() const
     189              : {
     190      3003584 :     if (m_elements.size() == 0) {
     191              :         static const RectF _dummy;
     192       335032 :         return _dummy;
     193      2668552 :     } else if (m_elements.size() == 1) {
     194      2596165 :         return m_elements.at(0);
     195              :     } else {
     196        72387 :         if (m_bbox.isNull()) {
     197       240726 :             for (const ShapeElement& e : m_elements) {
     198       211920 :                 m_bbox.unite(e);
     199              :             }
     200              :         }
     201        72387 :         return m_bbox;
     202              :     }
     203              : }
     204              : 
     205          468 : std::optional<ShapeElement> Shape::find_if(const std::function<bool(const ShapeElement&)>& func) const
     206              : {
     207          468 :     auto it = std::find_if(m_elements.begin(), m_elements.end(), func);
     208          468 :     if (it == m_elements.end()) {
     209            0 :         return std::nullopt;
     210              :     }
     211          468 :     return std::make_optional(*it);
     212              : }
     213              : 
     214        28299 : std::optional<ShapeElement> Shape::find_first(ElementType type) const
     215              : {
     216        28583 :     for (const ShapeElement& i : m_elements) {
     217        27726 :         if (i.item() && i.item()->type() == type) {
     218        27442 :             return std::make_optional(i);
     219              :         }
     220              :     }
     221              : 
     222          857 :     return std::nullopt;
     223              : }
     224              : 
     225          857 : std::optional<ShapeElement> Shape::get_first() const
     226              : {
     227          857 :     if (m_elements.empty()) {
     228          719 :         return std::nullopt;
     229              :     }
     230          138 :     return std::make_optional(m_elements.at(0));
     231              : }
     232              : 
     233              : //-------------------------------------------------------------------
     234              : //   minVerticalDistance
     235              : //    a is located below this shape.
     236              : //    Calculates the minimum distance between two shapes.
     237              : //-------------------------------------------------------------------
     238              : 
     239        28099 : double Shape::minVerticalDistance(const Shape& a, double minHorizontalClearance) const
     240              : {
     241        28099 :     if (empty() || a.empty()) {
     242          315 :         return 0.0;
     243              :     }
     244              : 
     245        27784 :     double dist = -DBL_MAX; // min real
     246       481463 :     for (const RectF& r2 : a.m_elements) {
     247       453679 :         if (r2.height() <= 0.0) {
     248         1493 :             continue;
     249              :         }
     250       452186 :         double bx1 = r2.left();
     251       452186 :         double bx2 = r2.right();
     252      7813301 :         for (const RectF& r1 : m_elements) {
     253      7361115 :             if (r1.height() <= 0.0) {
     254          507 :                 continue;
     255              :             }
     256      7360608 :             double ax1 = r1.left();
     257      7360608 :             double ax2 = r1.right();
     258      7360608 :             if (mu::engraving::intersects(ax1, ax2, bx1, bx2, minHorizontalClearance)) {
     259       644003 :                 dist = std::max(dist, r1.bottom() - r2.top());
     260              :             }
     261              :         }
     262              :     }
     263        27784 :     return dist;
     264              : }
     265              : 
     266              : //-------------------------------------------------------------------
     267              : //   verticalClearance
     268              : //    a is located below this shape.
     269              : //    Claculates the amount of clearance between the two shapes.
     270              : //    If there is an overlap, returns a negative value corresponging
     271              : //    to the amount of overlap.
     272              : //-------------------------------------------------------------------
     273              : 
     274         2087 : double Shape::verticalClearance(const Shape& a, double minHorizontalDistance) const
     275              : {
     276         2087 :     if (empty() || a.empty()) {
     277          126 :         return 0.0;
     278              :     }
     279              : 
     280         1961 :     double dist = DBL_MAX; // max real
     281        10992 :     for (const RectF& r2 : a.m_elements) {
     282         9031 :         if (r2.height() <= 0.0) {
     283           26 :             continue;
     284              :         }
     285         9005 :         double bx1 = r2.left();
     286         9005 :         double bx2 = r2.right();
     287        36996 :         for (const RectF& r1 : m_elements) {
     288        27991 :             if (r1.height() <= 0.0) {
     289            8 :                 continue;
     290              :             }
     291        27983 :             double ax1 = r1.left();
     292        27983 :             double ax2 = r1.right();
     293        27983 :             if (mu::engraving::intersects(ax1, ax2, bx1, bx2, minHorizontalDistance)) {
     294         5965 :                 dist = std::min(dist, r2.top() - r1.bottom());
     295              :             }
     296              :         }
     297              :     }
     298         1961 :     return dist;
     299              : }
     300              : 
     301              : //----------------------------------------------------------------
     302              : // clearsVertically()
     303              : // a is located below this shape
     304              : // returns true if, within the horizontal width of both shapes,
     305              : // all parts of this shape are above all parts of a
     306              : //----------------------------------------------------------------
     307        32277 : bool Shape::clearsVertically(const Shape& a) const
     308              : {
     309       286221 :     for (const RectF& r1 : a.m_elements) {
     310       717966 :         for (const RectF& r2 : m_elements) {
     311       464022 :             if (mu::engraving::intersects(r1.left(), r1.right(), r2.left(), r2.right())) {
     312        31904 :                 if (std::min(r1.top(), r1.bottom()) <= std::max(r2.top(), r2.bottom())) {
     313         1961 :                     return false;
     314              :                 }
     315              :             }
     316              :         }
     317              :     }
     318        30316 :     return true;
     319              : }
     320              : 
     321              : //---------------------------------------------------------
     322              : //   left
     323              : //    compute left border
     324              : //---------------------------------------------------------
     325              : 
     326       199580 : double Shape::left() const
     327              : {
     328       199580 :     double dist = DBL_MAX;
     329       507326 :     for (const ShapeElement& r : m_elements) {
     330              :         // TURBO-HACK: the only purpose of this is to ignore fingering for spacing the first CR segment after the header.
     331       307746 :         if (!RealIsNull(r.height()) && !(r.item() && r.item()->isFingering()) && r.left() < dist) {
     332       235948 :             dist = r.left();
     333              :         }
     334              :     }
     335       199580 :     return dist;
     336              : }
     337              : 
     338              : //---------------------------------------------------------
     339              : //   right
     340              : //    compute right border
     341              : //---------------------------------------------------------
     342              : 
     343       499600 : double Shape::right() const
     344              : {
     345       499600 :     double dist = -DBL_MAX;
     346      1152347 :     for (const RectF& r : m_elements) {
     347       652747 :         if (r.right() > dist) {
     348       538139 :             dist = r.right();
     349              :         }
     350              :     }
     351       499600 :     return dist;
     352              : }
     353              : 
     354              : //---------------------------------------------------------
     355              : //   top
     356              : //---------------------------------------------------------
     357              : 
     358        37655 : double Shape::top() const
     359              : {
     360        37655 :     double dist = DBL_MAX;
     361       254718 :     for (const RectF& r : m_elements) {
     362       217063 :         if (r.top() < dist) {
     363       102542 :             dist = r.top();
     364              :         }
     365              :     }
     366        37655 :     return dist;
     367              : }
     368              : 
     369              : //---------------------------------------------------------
     370              : //   bottom
     371              : //---------------------------------------------------------
     372              : 
     373        51485 : double Shape::bottom() const
     374              : {
     375        51485 :     double dist = -DBL_MAX;
     376       566267 :     for (const RectF& r : m_elements) {
     377       514782 :         if (r.bottom() > dist) {
     378        94682 :             dist = r.bottom();
     379              :         }
     380              :     }
     381        51485 :     return dist;
     382              : }
     383              : 
     384            0 : double Shape::width() const
     385              : {
     386            0 :     double left = DBL_MAX;
     387            0 :     double right = -DBL_MAX;
     388            0 :     for (const RectF& r : m_elements) {
     389            0 :         left = std::min(left, r.left());
     390            0 :         right = std::max(right, r.right());
     391              :     }
     392              : 
     393            0 :     return right - left;
     394              : }
     395              : 
     396            0 : double Shape::height() const
     397              : {
     398            0 :     double top = DBL_MAX;
     399            0 :     double bottom = -DBL_MAX;
     400            0 :     for (const RectF& r : m_elements) {
     401            0 :         top = std::min(top, r.top());
     402            0 :         bottom = std::max(bottom, r.bottom());
     403              :     }
     404              : 
     405            0 :     return bottom - top;
     406              : }
     407              : 
     408            0 : double Shape::topAtX(double x) const
     409              : {
     410            0 :     double localTop = DBL_MAX;
     411            0 :     for (const ShapeElement& el : m_elements) {
     412            0 :         if (el.left() < x && el.right() > x) {
     413            0 :             localTop = std::min(localTop, el.top());
     414              :         }
     415              :     }
     416              : 
     417            0 :     return localTop != DBL_MAX ? localTop : top();
     418              : }
     419              : 
     420            0 : double Shape::bottomAtX(double x) const
     421              : {
     422            0 :     double localBottom = -DBL_MAX;
     423            0 :     for (const ShapeElement& el : m_elements) {
     424            0 :         if (el.left() < x && el.right() > x) {
     425            0 :             localBottom = std::max(localBottom, el.bottom());
     426              :         }
     427              :     }
     428              : 
     429            0 :     return localBottom != DBL_MAX ? localBottom : bottom();
     430              : }
     431              : 
     432         1452 : double Shape::rightMostEdgeAtHeight(double yAbove, double yBelow) const
     433              : {
     434         1452 :     double edge = -DBL_MAX;
     435         7090 :     for (const ShapeElement& sh : m_elements) {
     436         5638 :         if (sh.bottom() > yAbove && sh.top() < yBelow) {
     437         2757 :             edge = std::max(edge, sh.right());
     438              :         }
     439              :     }
     440              : 
     441         1452 :     return edge;
     442              : }
     443              : 
     444         1421 : double Shape::leftMostEdgeAtHeight(double yAbove, double yBelow) const
     445              : {
     446         1421 :     double edge = DBL_MAX;
     447         6509 :     for (const ShapeElement& sh : m_elements) {
     448         5088 :         if (sh.bottom() > yAbove && sh.top() < yBelow) {
     449         2303 :             edge = std::min(edge, sh.left());
     450              :         }
     451              :     }
     452              : 
     453         1421 :     return edge;
     454              : }
     455              : 
     456            0 : double Shape::leftMostEdgeAtTop() const
     457              : {
     458            0 :     double edge = DBL_MAX;
     459            0 :     const double shapeTop = top();
     460            0 :     for (const ShapeElement& sh : m_elements) {
     461            0 :         if (muse::RealIsEqual(sh.top(), shapeTop)) {
     462            0 :             edge = std::min(edge, sh.left());
     463              :         }
     464              :     }
     465              : 
     466            0 :     return edge;
     467              : }
     468              : 
     469              : //---------------------------------------------------------
     470              : //   topDistance
     471              : //    p is on top of shape
     472              : //    returns negative values if there is an overlap
     473              : //---------------------------------------------------------
     474              : 
     475            0 : double Shape::topDistance(const PointF& p) const
     476              : {
     477            0 :     double dist = DBL_MAX;
     478            0 :     for (const RectF& r : m_elements) {
     479            0 :         if (p.x() >= r.left() && p.x() < r.right()) {
     480            0 :             dist = std::min(dist, r.top() - p.y());
     481              :         }
     482              :     }
     483            0 :     return dist;
     484              : }
     485              : 
     486              : //---------------------------------------------------------
     487              : //   bottomDistance
     488              : //    p is below the shape
     489              : //    returns negative values if there is an overlap
     490              : //---------------------------------------------------------
     491              : 
     492            0 : double Shape::bottomDistance(const PointF& p) const
     493              : {
     494            0 :     double dist = DBL_MAX;
     495            0 :     for (const RectF& r : m_elements) {
     496            0 :         if (p.x() >= r.left() && p.x() < r.right()) {
     497            0 :             dist = std::min(dist, p.y() - r.bottom());
     498              :         }
     499              :     }
     500            0 :     return dist;
     501              : }
     502              : 
     503      1909825 : void Shape::setBBox(const RectF& r, const EngravingItem* p)
     504              : {
     505      1909825 :     IF_ASSERT_FAILED(type() == Type::Fixed) {
     506            0 :         return;
     507              :     }
     508              : 
     509      1909825 :     if (m_elements.empty()) {
     510      1909825 :         m_elements.push_back(ShapeElement(r, p));
     511              :     } else {
     512            0 :         m_elements[0] = ShapeElement(r, p);
     513              :     }
     514              : }
     515              : 
     516            0 : void Shape::addBBox(const RectF& r)
     517              : {
     518              : //    IF_ASSERT_FAILED(type() == Type::Fixed) {
     519              : //        return;
     520              : //    }
     521              : 
     522            0 :     if (m_elements.empty()) {
     523            0 :         m_elements.push_back(RectF());
     524              :     }
     525              : 
     526            0 :     m_elements[0].unite(r);
     527            0 : }
     528              : 
     529              : //---------------------------------------------------------
     530              : //   add
     531              : //---------------------------------------------------------
     532              : 
     533      2247897 : void Shape::add(const Shape& s)
     534              : {
     535      2247897 :     m_type = Type::Composite;
     536      2247897 :     m_elements.insert(m_elements.end(), s.m_elements.begin(), s.m_elements.end());
     537      2247897 :     invalidateBBox();
     538      2247897 : }
     539              : 
     540      2403888 : void Shape::add(const ShapeElement& shapeEl)
     541              : {
     542      2403888 :     m_type = Type::Composite;
     543      2403888 :     m_elements.push_back(shapeEl);
     544      2403888 :     invalidateBBox();
     545      2403888 : }
     546              : 
     547              : //---------------------------------------------------------
     548              : //   remove
     549              : //---------------------------------------------------------
     550              : 
     551            0 : void Shape::remove(const RectF& r)
     552              : {
     553            0 :     for (auto i = m_elements.begin(); i != m_elements.end(); ++i) {
     554            0 :         if (*i == r) {
     555            0 :             m_elements.erase(i);
     556            0 :             return;
     557              :         }
     558              :     }
     559              : 
     560            0 :     ASSERT_X("Shape::remove: RectF not found in Shape");
     561              : 
     562              :     invalidateBBox();
     563              : }
     564              : 
     565            0 : void Shape::remove(const Shape& s)
     566              : {
     567            0 :     for (const RectF& r : s.m_elements) {
     568            0 :         remove(r);
     569              :     }
     570              : 
     571            0 :     invalidateBBox();
     572            0 : }
     573              : 
     574            0 : std::vector<RectF> Shape::toRects() const
     575              : {
     576            0 :     std::vector<RectF> rects;
     577            0 :     rects.reserve(m_elements.size());
     578              : 
     579            0 :     for (const RectF& shapeEl : m_elements) {
     580            0 :         rects.push_back(shapeEl);
     581              :     }
     582              : 
     583            0 :     return rects;
     584            0 : }
     585              : 
     586          549 : void Shape::removeInvisibles()
     587              : {
     588          549 :     muse::remove_if(m_elements, [](ShapeElement& shapeElement) {
     589         1529 :         return !shapeElement.item() || !shapeElement.item()->visible();
     590              :     });
     591          549 :     invalidateBBox();
     592          549 : }
     593              : 
     594          637 : void Shape::removeTypes(const std::set<ElementType>& types)
     595              : {
     596          637 :     muse::remove_if(m_elements, [&types](ShapeElement& shapeElement) {
     597         1850 :         return shapeElement.item() && muse::contains(types, shapeElement.item()->type());
     598              :     });
     599          637 :     invalidateBBox();
     600          637 : }
     601              : 
     602              : //---------------------------------------------------------
     603              : //   contains
     604              : //---------------------------------------------------------
     605              : 
     606            0 : bool Shape::contains(const PointF& p) const
     607              : {
     608            0 :     for (const RectF& r : m_elements) {
     609            0 :         if (r.contains(p)) {
     610            0 :             return true;
     611              :         }
     612              :     }
     613            0 :     return false;
     614              : }
     615              : 
     616              : //---------------------------------------------------------
     617              : //   intersects
     618              : //---------------------------------------------------------
     619              : 
     620       607319 : bool Shape::intersects(const RectF& rr) const
     621              : {
     622      1071021 :     for (const RectF& r : m_elements) {
     623       805994 :         if (r.intersects(rr)) {
     624       342292 :             return true;
     625              :         }
     626              :     }
     627       265027 :     return false;
     628              : }
     629              : 
     630              : //---------------------------------------------------------
     631              : //   intersects
     632              : //---------------------------------------------------------
     633              : 
     634       521629 : bool Shape::intersects(const Shape& other) const
     635              : {
     636       754259 :     for (const RectF& r : other.m_elements) {
     637       571913 :         if (intersects(r)) {
     638       339283 :             return true;
     639              :         }
     640              :     }
     641       182346 :     return false;
     642              : }
     643              : 
     644            0 : void Shape::paint(Painter& painter) const
     645              : {
     646            0 :     for (const RectF& r : m_elements) {
     647            0 :         painter.drawRect(r);
     648              :     }
     649            0 : }
     650              : 
     651            0 : void mu::engraving::dump(const ShapeElement& se, std::stringstream& ss)
     652              : {
     653            0 :     const RectF* r = &se;
     654            0 :     ss << "item: " << (se.item() ? se.item()->typeName() : "no") << " rect: ";
     655            0 :     muse::dump(*r, ss);
     656            0 : }
     657              : 
     658            0 : void mu::engraving::dump(const Shape& sh, std::stringstream& ss)
     659              : {
     660            0 :     ss << "Shape size: " << sh.size() << "\n";
     661            0 :     for (const ShapeElement& r : sh.elements()) {
     662            0 :         ss << "  ";
     663            0 :         dump(r, ss);
     664            0 :         ss << "\n";
     665              :     }
     666            0 : }
     667              : 
     668            0 : std::string mu::engraving::dump(const Shape& sh)
     669              : {
     670            0 :     std::stringstream ss;
     671            0 :     dump(sh, ss);
     672            0 :     return ss.str();
     673            0 : }
        

Generated by: LCOV version 2.0-1