LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - parenthesislayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 87.9 % 346 304
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) 2025 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              : 
      23              : #include "dom/chord.h"
      24              : #include "dom/harmony.h"
      25              : #include "dom/ledgerline.h"
      26              : #include "dom/measure.h"
      27              : #include "dom/note.h"
      28              : #include "dom/parenthesis.h"
      29              : #include "dom/score.h"
      30              : #include "dom/segment.h"
      31              : #include "dom/slurtie.h"
      32              : #include "dom/staff.h"
      33              : #include "dom/timesig.h"
      34              : #include "horizontalspacing.h"
      35              : #include "parenthesislayout.h"
      36              : 
      37              : using namespace mu::engraving;
      38              : using namespace mu::engraving::rendering::score;
      39              : 
      40              : static constexpr Spatium MAX_MID_THICKNESS = 0.2_sp;
      41              : 
      42         3626 : void ParenthesisLayout::layoutParentheses(const EngravingItem* parent, const LayoutContext& ctx)
      43              : {
      44              :     // Layout parentheses surrounding an engraving item. Handle padding and placement
      45         3626 :     Parenthesis* leftParen = parent->leftParen();
      46         3626 :     Parenthesis* rightParen = parent->rightParen();
      47              : 
      48         3626 :     if (!(leftParen || rightParen)) {
      49         3556 :         return;
      50              :     }
      51           70 :     if (leftParen) {
      52           45 :         layoutParenthesis(leftParen, leftParen->mutldata(), ctx);
      53              :     }
      54              : 
      55           70 :     if (rightParen) {
      56           45 :         layoutParenthesis(rightParen, rightParen->mutldata(), ctx);
      57              :     }
      58              : 
      59           70 :     bool itemAddToSkyline = parent->autoplace() && !parent->ldata()->isSkipDraw();
      60           70 :     Shape dummyItemShape = getParentShape(parent);
      61              : 
      62           70 :     layoutParentheses(leftParen, rightParen, dummyItemShape, itemAddToSkyline, ctx);
      63           70 : }
      64              : 
      65        50677 : void ParenthesisLayout::layoutChordParentheses(const Chord* chord, const LayoutContext& ctx)
      66              : {
      67        50896 :     for (const NoteParenthesisInfo* parenNotesInfo : chord->noteParentheses()) {
      68          219 :         Parenthesis* leftParen = parenNotesInfo->leftParen();
      69          219 :         Parenthesis* rightParen = parenNotesInfo->rightParen();
      70              : 
      71          219 :         if (!(leftParen || rightParen)) {
      72            0 :             return;
      73              :         }
      74          219 :         if (leftParen) {
      75          219 :             layoutParenthesis(leftParen, leftParen->mutldata(), ctx);
      76              :         }
      77              : 
      78          219 :         if (rightParen) {
      79          219 :             layoutParenthesis(rightParen, rightParen->mutldata(), ctx);
      80              :         }
      81              : 
      82          219 :         bool itemAddToSkyline = chord->autoplace() && !chord->ldata()->isSkipDraw();
      83          219 :         Shape dummyItemShape = getParentShape(chord);
      84              : 
      85          219 :         layoutParentheses(leftParen, rightParen, dummyItemShape, itemAddToSkyline, ctx);
      86          219 :     }
      87              : }
      88              : 
      89          289 : void ParenthesisLayout::layoutParentheses(Parenthesis* leftParen, Parenthesis* rightParen, Shape& dummyItemShape, bool itemAddToSkyline,
      90              :                                           const LayoutContext& ctx)
      91              : {
      92              :     UNUSED(ctx);
      93          289 :     if (!leftParen || !rightParen) {
      94              :         // 1 parenthesis
      95           50 :         Parenthesis* paren = leftParen ? leftParen : rightParen;
      96           50 :         Parenthesis::LayoutData* ldata = paren->mutldata();
      97           50 :         const bool leftBracket = paren->direction() == DirectionH::LEFT;
      98           50 :         double minDist = 0.0;
      99           50 :         if (!leftBracket && itemAddToSkyline) {
     100              :             // Space against existing item shape
     101           25 :             minDist = HorizontalSpacing::minHorizontalDistance(dummyItemShape, paren->shape().translated(
     102              :                                                                    ldata->pos()), paren->spatium());
     103           25 :         } else if (itemAddToSkyline) {
     104              :             // Space following item shape against this
     105           25 :             minDist = -HorizontalSpacing::minHorizontalDistance(paren->shape().translated(
     106              :                                                                     ldata->pos()), dummyItemShape, paren->spatium());
     107              :         }
     108           50 :         paren->mutldata()->moveX(minDist);
     109              : 
     110           50 :         return;
     111              :     }
     112              : 
     113              :     // 2 parentheses
     114          239 :     assert(leftParen && rightParen);
     115              : 
     116          239 :     if (!itemAddToSkyline) {
     117            0 :         return;
     118              :     }
     119              : 
     120          239 :     Parenthesis::LayoutData* leftLdata = leftParen->mutldata();
     121          239 :     Parenthesis::LayoutData* rightLdata = rightParen->mutldata();
     122              : 
     123          239 :     const double itemLeftX = dummyItemShape.bbox().x();
     124          239 :     const double parentWidth = dummyItemShape.bbox().width();
     125          239 :     const double itemRightX = itemLeftX + parentWidth;
     126              : 
     127          239 :     const double leftParenPadding = HorizontalSpacing::minHorizontalDistance(leftParen->shape().translated(leftLdata->pos()),
     128              :                                                                              dummyItemShape, leftParen->spatium());
     129          239 :     leftLdata->moveX(-leftParenPadding);
     130          239 :     dummyItemShape.add(leftParen->shape().translate(leftLdata->pos() + leftParen->staffOffset()));
     131              : 
     132          239 :     const double rightParenPadding = HorizontalSpacing::minHorizontalDistance(dummyItemShape, rightParen->shape().translated(
     133              :                                                                                   rightLdata->pos()), rightParen->spatium());
     134          239 :     rightLdata->moveX(rightParenPadding);
     135              : 
     136              :     // If the right parenthesis has been padded against the left parenthesis, this means the parenthesis -> parenthesis padding distance
     137              :     // is larger than the width of the item the parentheses surrounds. In this case, the result is visually unbalanced.  Move both parens
     138              :     // to the left (relative to the segment) in order to centre the item: (b  ) -> ( b )
     139          239 :     const double itemWidth = parentWidth;
     140          239 :     double parenPadding = computeInternalParenthesisPadding(leftParen, rightParen);
     141              : 
     142          239 :     if (itemWidth >= parenPadding) {
     143          238 :         return;
     144              :     }
     145              : 
     146              :     // Move parentheses to place item in the middle
     147            1 :     const double leftParenX = leftLdata->pos().x() + leftParen->ldata()->bbox().x() + leftParen->ldata()->midPointThickness;
     148            1 :     const double rightParenX = rightLdata->pos().x() + rightParen->ldata()->bbox().x() + rightParen->width()
     149            1 :                                - rightParen->ldata()->midPointThickness;
     150              : 
     151            1 :     const double leftParenToItem = itemLeftX - leftParenX;
     152            1 :     const double itemToRightParen = rightParenX - itemRightX;
     153            1 :     const double parenToItemDist = (leftParenToItem + itemToRightParen) / 2;
     154              : 
     155            1 :     leftLdata->moveX(-(parenToItemDist - leftParenToItem));
     156            1 :     rightLdata->moveX(-(itemToRightParen - parenToItemDist));
     157              : }
     158              : 
     159          528 : void ParenthesisLayout::layoutParenthesis(Parenthesis* item, Parenthesis::LayoutData* ldata, const LayoutContext& ctx)
     160              : {
     161          528 :     setLayoutValues(item, ldata, ctx);
     162              : 
     163          528 :     if (ldata->symId == SymId::noSym) {
     164          528 :         createPathAndShape(item, ldata);
     165              :     } else {
     166            0 :         createSmuflShape(item, ldata);
     167              :     }
     168          528 : }
     169              : 
     170        72047 : bool ParenthesisLayout::isInternalParenPadding(const EngravingItem* item1, const EngravingItem* item2)
     171              : {
     172        72047 :     const Chord* chord1 = toChord(item1->findAncestor(ElementType::CHORD));
     173        72047 :     const Chord* chord2 = toChord(item2->findAncestor(ElementType::CHORD));
     174        72047 :     const EngravingObject* parent1 = item1->ownershipParent();
     175        72047 :     const EngravingObject* parent2 = item2->ownershipParent();
     176              : 
     177       113063 :     bool internalPadding = (item1->isParenthesis() && parent1 == item2)
     178        70844 :                            || (item2->isParenthesis() && parent2 == item1)
     179        70146 :                            || parent1 == parent2
     180       144094 :                            || (chord1 && chord2 && chord1 == chord2);
     181              : 
     182        72047 :     return internalPadding;
     183              : }
     184              : 
     185        61067 : double ParenthesisLayout::computeExternalParenthesisPadding(const EngravingItem* item1, const EngravingItem* item2)
     186              : {
     187        61067 :     EngravingItem* parent = item1->isParenthesis() ? item1->ownershipParentItem() : item2->ownershipParentItem();
     188              : 
     189        61067 :     ElementType type1 = item1->type();
     190        61067 :     ElementType type2 = item2->type();
     191              : 
     192        61067 :     const ParenPaddingTable& paddingTable = parent->score()->paddingTables().parenthesisPaddingTableFor(parent);
     193              : 
     194        61067 :     double padding = paddingTable.padding(type1, type2);
     195              : 
     196        61067 :     double scaling = (item1->mag() + item2->mag()) / 2;
     197        61067 :     padding *= scaling;
     198        61067 :     return padding;
     199              : }
     200              : 
     201        72047 : double ParenthesisLayout::computeParenthesisPadding(const EngravingItem* item1, const EngravingItem* item2)
     202              : {
     203        72047 :     if (isInternalParenPadding(item1, item2)) {
     204        10980 :         return computeInternalParenthesisPadding(item1, item2);
     205              :     }
     206              : 
     207        61067 :     return computeExternalParenthesisPadding(item1, item2);
     208              : }
     209              : 
     210        11219 : double ParenthesisLayout::computeInternalParenthesisPadding(const EngravingItem* item1, const EngravingItem* item2)
     211              : {
     212        11219 :     bool parenFirst = item1->isParenthesis();
     213        11219 :     const EngravingItem* other = item1->isParenthesis() ? item2 : item1;
     214        11219 :     const double spatium = item1->style().spatium();
     215        11219 :     double padding = 0.0;
     216              : 
     217        11219 :     switch (other->type()) {
     218         2988 :     case ElementType::NOTE:
     219         2988 :         if (item1->staffType() && item1->staffType()->isTabStaff()) {
     220          468 :             padding = item1->style().styleS(Sid::tabFretPadding).val() * spatium;
     221              :         } else {
     222         2520 :             padding = 0.3 * spatium;
     223              :         }
     224         2988 :         break;
     225          828 :     case ElementType::ARTICULATION:
     226              :     case ElementType::ACCIDENTAL:
     227              :     case ElementType::STEM:
     228          828 :         padding = 0.3 * spatium;
     229          828 :         break;
     230           18 :     case ElementType::NOTEDOT:
     231           18 :         padding = 0.15 * spatium;
     232           18 :         break;
     233          744 :     case ElementType::LEDGER_LINE:
     234          744 :         padding = 0.3 * spatium;
     235          744 :         break;
     236            0 :     case ElementType::HOOK:
     237            0 :         padding = 0.3 * spatium;
     238            0 :         break;
     239          566 :     case ElementType::KEYSIG:
     240          566 :         padding = (parenFirst ? 0.35 : 0.25) * spatium;
     241          566 :         break;
     242          542 :     case ElementType::TIMESIG:
     243          542 :         padding = (parenFirst ? 0.2 : 0.25) * spatium;
     244          542 :         break;
     245          729 :     case ElementType::CLEF:
     246          729 :         padding = (parenFirst ? 0.2 : 0.25) * spatium;
     247          729 :         break;
     248           64 :     case ElementType::HARMONY:
     249           64 :         padding = 0.2 * spatium;
     250           64 :         break;
     251         4700 :     case ElementType::PARENTHESIS:
     252         4700 :         padding = spatium;
     253         4700 :         break;
     254           40 :     default:
     255           40 :         padding = 0.1 * spatium;
     256           40 :         break;
     257              :     }
     258              : 
     259        11219 :     double scaling = (item1->mag() + item2->mag()) / 2;
     260        11219 :     padding *= scaling;
     261        11219 :     return padding;
     262              : }
     263              : 
     264          592 : void ParenthesisLayout::createPathAndShape(Parenthesis* item, Parenthesis::LayoutData* ldata)
     265              : {
     266          592 :     const double spatium = item->spatium();
     267          592 :     const bool leftBracket = item->direction() == DirectionH::LEFT;
     268              : 
     269          592 :     const double startY = ldata->startY;
     270          592 :     const double height = ldata->height;
     271          592 :     const double xPos = ldata->pos().x();
     272          592 :     const double mag = ldata->mag();
     273          592 :     const double maxMidPointThickness = MAX_MID_THICKNESS.toAbsolute(spatium);
     274          592 :     const double midPointThickness = std::min(ldata->midPointThickness.value(), maxMidPointThickness);
     275              : 
     276          592 :     const double heightInSpatium = height / spatium;
     277          592 :     const double shoulderYOffset = 0.2 * height;
     278              : 
     279          592 :     if (std::isinf(height)) {
     280            0 :         LOGE() << "Error: parenthesis height is infinite";
     281            0 :         return;
     282              :     }
     283              : 
     284              :     // Control width of parentheses. We don't want tall parens to be too wide, nor do we want parens at a small scale to lose their curve too much
     285          592 :     double shoulderX = !muse::RealIsNull(ldata->shoulderWidth()) ? ldata->shoulderWidth() : 0.2
     286          520 :                        * std::pow(height, 0.95) * std::pow(mag, 0.1);
     287              : 
     288          592 :     const double minShoulderX = 0.25 * spatium * ldata->intrinsicMag();
     289          592 :     shoulderX = std::max(shoulderX, minShoulderX);
     290              : 
     291          592 :     PointF start = PointF(xPos, startY);
     292          592 :     const PointF end = PointF(xPos, start.y() + height);
     293          592 :     const PointF endNormalised = end - start;
     294              : 
     295          592 :     const int direction = leftBracket ? -1 : 1;
     296          592 :     const double shoulderForX = direction * shoulderX + midPointThickness * direction;
     297          592 :     const double shoulderBackX = direction * shoulderX + midPointThickness * direction * -1;
     298              : 
     299          592 :     const PointF bezier1for = PointF(shoulderForX, shoulderYOffset);
     300          592 :     const PointF bezier2for = PointF(shoulderForX, endNormalised.y() - shoulderYOffset);
     301          592 :     const PointF bezier1back = PointF(shoulderBackX, endNormalised.y() - shoulderYOffset);
     302          592 :     const PointF bezier2back = PointF(shoulderBackX, shoulderYOffset);
     303              : 
     304          592 :     PainterPath path = PainterPath();
     305          592 :     path.moveTo(PointF());
     306          592 :     path.cubicTo(bezier1for, bezier2for, endNormalised);
     307          592 :     path.cubicTo(bezier1back, bezier2back, PointF());
     308              : 
     309          592 :     ldata->path = path;
     310              : 
     311              :     // Fill shape
     312          592 :     Shape shape(Shape::Type::Composite);
     313              : 
     314          592 :     PointF startPoint = PointF();
     315          592 :     double midThickness = 2 * midPointThickness;
     316          592 :     int nbShapes = round(2.0 * heightInSpatium);
     317          592 :     nbShapes = std::clamp(nbShapes, 3, 10);
     318          592 :     PointF bezier1mid = bezier1for - PointF(midPointThickness * direction, 0.0);
     319          592 :     PointF bezier2mid = bezier2for - PointF(midPointThickness * direction, 0.0);
     320          592 :     const CubicBezier b(startPoint, bezier1mid, bezier2mid, endNormalised);
     321         3044 :     for (int i = 1; i <= nbShapes; i++) {
     322         2452 :         double percent = pow(sin(0.5 * M_PI * (double(i) / double(nbShapes))), 2);
     323         2452 :         const PointF point = b.pointAtPercent(percent);
     324         2452 :         RectF re = RectF(startPoint, point).normalized();
     325         2452 :         double approxThicknessAtPercent = (1 - 2 * std::abs(0.5 - percent)) * midThickness;
     326         2452 :         if (re.width() < approxThicknessAtPercent) {
     327          672 :             double adjust = (approxThicknessAtPercent - re.width()) * .5;
     328          672 :             re.adjust(-adjust, 0.0, adjust, 0.0);
     329              :         }
     330         2452 :         shape.add(re, item);
     331         2452 :         startPoint = point;
     332              :     }
     333              : 
     334          592 :     ldata->setShape(shape);
     335              : 
     336          592 :     item->setPos(start);
     337          592 : }
     338              : 
     339            0 : void ParenthesisLayout::createSmuflShape(Parenthesis* item, Parenthesis::LayoutData* ldata)
     340              : {
     341            0 :     Shape bbox = Shape(item->symBbox(ldata->symId), item);
     342            0 :     double scale = item->ldata()->symScale;
     343            0 :     bbox.scale(SizeF(scale, scale));
     344            0 :     ldata->setShape(bbox);
     345            0 : }
     346              : 
     347          528 : void ParenthesisLayout::setLayoutValues(Parenthesis* item, Parenthesis::LayoutData* ldata, const LayoutContext& ctx)
     348              : {
     349          528 :     if (!item->ownershipParent()) {
     350            0 :         return;
     351              :     }
     352              : 
     353          528 :     ldata->setPos(PointF());
     354          528 :     ldata->reset();
     355          528 :     ldata->path.reset();
     356          528 :     ldata->symId.reset();
     357              : 
     358              :     // Set ldata values based on parent
     359          528 :     switch (item->ownershipParent()->type()) {
     360          438 :     case ElementType::CHORD:
     361          438 :         setChordValues(item, ldata);
     362          438 :         break;
     363              :     // height & startY are set in MeasureLayout for clef, timesig and keysig
     364              :     // TODO: create generic way of matching height & startY between parentheses on different items
     365           39 :     case ElementType::CLEF:
     366           39 :         setClefValues(item, ldata);
     367           39 :         break;
     368           33 :     case ElementType::TIMESIG:
     369           33 :         setTimeSigValues(item, ldata, ctx);
     370           33 :         break;
     371           10 :     case ElementType::KEYSIG:
     372           10 :         break;
     373            8 :     case ElementType::HARMONY:
     374            8 :         setHarmonyValues(item, ldata, ctx);
     375            8 :         break;
     376            0 :     default:
     377            0 :         setDefaultValues(item, ldata);
     378            0 :         break;
     379              :     }
     380              : }
     381              : 
     382           39 : void ParenthesisLayout::setClefValues(Parenthesis* item, Parenthesis::LayoutData* ldata)
     383              : {
     384           39 :     const EngravingItem* parent = item->ownershipParentItem();
     385           39 :     const Fraction tick = parent->tick();
     386           39 :     const Measure* measure = item->findMeasure();
     387              : 
     388           39 :     double offset = -parent->pos().y();
     389              : 
     390           39 :     if (!measure || tick != measure->endTick()) {
     391           19 :         ldata->startY.mut_value() += offset;
     392           19 :         return;
     393              :     }
     394              : 
     395           20 :     const double spatium = item->spatium();
     396           20 :     const Staff* staff = parent->staff();
     397           20 :     const Fraction tickPrev = tick - Fraction::eps();
     398           20 :     const StaffType* st = staff->staffType(tick);
     399           20 :     const StaffType* stPrev = !tickPrev.negative() ? item->staff()->staffType(tickPrev) : nullptr;
     400              : 
     401           20 :     offset += (st->yoffset().val() - (stPrev ? stPrev->yoffset().val() : 0)) * spatium;
     402           20 :     ldata->startY.mut_value() += offset;
     403              : }
     404              : 
     405           33 : void ParenthesisLayout::setTimeSigValues(Parenthesis* item, Parenthesis::LayoutData* ldata, const LayoutContext& ctx)
     406              : {
     407           33 :     const TimeSig* parentTs = toTimeSig(item->ownershipParent());
     408           33 :     if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() == TimeSigPlacement::NORMAL) {
     409           33 :         return;
     410              :     }
     411              : 
     412            0 :     double yOffset = -parentTs->pos().y();
     413            0 :     ldata->startY.mut_value() += yOffset;
     414              : }
     415              : 
     416          438 : void ParenthesisLayout::setChordValues(Parenthesis* item, Parenthesis::LayoutData* ldata)
     417              : {
     418          438 :     Chord* chord = toChord(item->ownershipParent());
     419              : 
     420          438 :     ldata->setMag(chord->mag());
     421          438 :     ldata->intrinsicMag = chord->intrinsicMag();    // Scaling information not linked to staff size. item->spatium is NOT scaled by this
     422              : 
     423          438 :     Shape notesShape;
     424              : 
     425          438 :     const NoteParenthesisInfo* parenInfo = chord->findNoteParenthesisInfo(item);
     426          438 :     IF_ASSERT_FAILED(parenInfo) {
     427            0 :         LOGD() << "No parenthesis info found for this chord";
     428            0 :         return;
     429              :     }
     430              : 
     431          438 :     const std::vector<Note*>& notes = parenInfo->notes();
     432              : 
     433          438 :     assert(!notes.empty());
     434              : 
     435          986 :     for (const Note* note : notes) {
     436          548 :         notesShape.add(getNoteShape(note, item).translated(note->pos()));
     437              :     }
     438              : 
     439          438 :     const StaffType* st = chord->staffType();
     440          438 :     const bool isTab = st->isTabStaff();
     441              : 
     442          438 :     auto calculateMidPointThickness = [item, ldata, isTab]() -> double {
     443          438 :         const double sp = item->spatium();
     444          438 :         const double sp0 = item->defaultSpatium();
     445              : 
     446          438 :         IF_ASSERT_FAILED(sp > 0.0 && sp0 > 0.0) {
     447            0 :             LOGE() << "spatium or default spatium is 0";
     448            0 :             return 0.0;
     449              :         }
     450              : 
     451          438 :         const double exponent = isTab ? 0.36 : 0.33;
     452          438 :         const double exponentComplement = 1.0 - exponent;
     453              : 
     454          438 :         const double normalizedHeight = ldata->height / sp;
     455              : 
     456              :         // Scale factor that keeps the normalized formula compatible with the
     457              :         // original formula at the default spatium.
     458          438 :         const double scaleNormalization = sp / std::pow(sp0, exponentComplement);
     459          438 :         const double normalizedThickness = std::pow(normalizedHeight, exponent)
     460          438 :                                            * scaleNormalization
     461          438 :                                            * ldata->intrinsicMag();
     462              : 
     463          438 :         return normalizedThickness;
     464          438 :     };
     465              : 
     466          438 :     if (isTab) {
     467           26 :         ldata->startY = notesShape.top();
     468           26 :         ldata->height = notesShape.bbox().height();
     469              :     } else {
     470          412 :         ldata->startY = notesShape.top() - 0.25 * item->spatium() * ldata->intrinsicMag();
     471          412 :         ldata->height = notesShape.bbox().height() + 0.5 * item->spatium() * ldata->intrinsicMag();
     472              :     }
     473          438 :     ldata->midPointThickness.set_value(calculateMidPointThickness());
     474          438 :     ldata->endPointThickness.set_value(0.05);
     475          438 : }
     476              : 
     477            8 : void ParenthesisLayout::setHarmonyValues(Parenthesis* item, Parenthesis::LayoutData* ldata, const LayoutContext& ctx)
     478              : {
     479            8 :     const double spatium = item->spatium();
     480            8 :     Harmony* parent = toHarmony(item->ownershipParent());
     481            8 :     RectF bbox = parent->ldata()->bbox();
     482              : 
     483            8 :     double endPointThickness = 0.03;
     484            8 :     double extension = 0.1 * spatium * (parent->size() / 10.0);
     485            8 :     double bottom = parent->baseLine() + extension;
     486              : 
     487            8 :     double defaultCapHeight = muse::draw::FontMetrics::capHeight(parent->font());
     488              : 
     489            8 :     double topCapHeight = DBL_MAX;
     490            8 :     const TextSegment* rootTextSeg = nullptr;
     491            8 :     if (!parent->ldata()->renderItemList.value().empty()) {
     492           48 :         for (const HarmonyRenderItem* renderItem : parent->ldata()->renderItemList.value()) {
     493           40 :             if (const TextSegment* ts = dynamic_cast<const TextSegment*>(renderItem)) {
     494           40 :                 topCapHeight = std::min(topCapHeight, ts->pos().y() - ts->capHeight());
     495              : 
     496           40 :                 rootTextSeg = !rootTextSeg ? ts : rootTextSeg;
     497              :             }
     498              :         }
     499              :     } else {
     500            0 :         topCapHeight = defaultCapHeight;
     501              :     }
     502              : 
     503            8 :     double top = topCapHeight - extension;
     504            8 :     double height = bottom - top;
     505              : 
     506            8 :     if (ctx.conf().styleB(Sid::harmonyParenUseSmuflSym)) {
     507            0 :         ldata->symId = item->direction() == DirectionH::LEFT ? SymId::csymParensLeftTall : SymId::csymParensRightTall;
     508            0 :         double symHeight = item->symHeight(ldata->symId);
     509            0 :         ldata->symScale = height / symHeight;
     510              : 
     511            0 :         return;
     512              :     }
     513              : 
     514            8 :     double rootCapHeight = rootTextSeg ? rootTextSeg->capHeight() : defaultCapHeight;
     515            8 :     double scale = (height - 2 * extension) / rootCapHeight;
     516              : 
     517            8 :     ldata->setMag(parent->mag());
     518            8 :     ldata->startY = top;
     519            8 :     ldata->height = height;
     520              :     static constexpr double HEIGHT_TO_WIDTH_RATIO = 20;
     521            8 :     ldata->midPointThickness.set_value(ldata->height / HEIGHT_TO_WIDTH_RATIO * ldata->mag() * 1 / std::sqrt(scale));
     522            8 :     ldata->endPointThickness.set_value(endPointThickness);
     523              : 
     524            8 :     double shoulder = 0.2 * ldata->height * std::pow(ldata->mag(), 0.1) * 1 / std::sqrt(scale);
     525            8 :     ldata->shoulderWidth = shoulder;
     526              : 
     527            8 :     const double PADDING = spatium * 0.2;
     528            8 :     ldata->setPosX(item->direction() == DirectionH::RIGHT ? bbox.right() + PADDING : bbox.left() - PADDING);
     529              : }
     530              : 
     531            0 : void ParenthesisLayout::setDefaultValues(Parenthesis* item, Parenthesis::LayoutData* ldata)
     532              : {
     533            0 :     const double spatium = item->spatium();
     534            0 :     EngravingItem* parent = item->layoutParent();
     535            0 :     RectF bbox = parent->ldata()->bbox();
     536              : 
     537            0 :     ldata->setMag(parent->mag());
     538            0 :     ldata->startY = bbox.top() - 0.25 * spatium;
     539            0 :     ldata->height = bbox.height() + 0.5 * spatium;
     540            0 :     ldata->midPointThickness.set_value(ldata->height / 60 * ldata->mag());  // 0.1sp for a height of 6sp
     541            0 :     const double PADDING = spatium * 0.2;
     542            0 :     ldata->setPosX(item->direction() == DirectionH::RIGHT ? bbox.right() + PADDING : bbox.left() - PADDING);
     543            0 : }
     544              : 
     545          289 : Shape ParenthesisLayout::getParentShape(const EngravingItem* parent)
     546              : {
     547          289 :     Shape parentShape = parent->shape();
     548              : 
     549          289 :     bool isChord = parent->isChord();
     550              : 
     551          289 :     parentShape.remove_if([isChord](ShapeElement& s) {
     552         6364 :         return !s.item() || s.item()->isParenthesis()
     553          863 :                || (s.item()->isLaissezVibSegment() && isChord)
     554          863 :                || (s.item()->isHook() && isChord)
     555          859 :                || (s.item()->isStem() && isChord)
     556          747 :                || (s.item()->isArpeggio() && isChord)
     557         6364 :                || (s.item()->isChordBracket() && isChord);
     558              :     });
     559              : 
     560          289 :     return parentShape;
     561            0 : }
     562              : 
     563          548 : Shape ParenthesisLayout::getNoteShape(const Note* note, Parenthesis* paren)
     564              : {
     565          548 :     IF_ASSERT_FAILED(note && paren) {
     566            0 :         return Shape();
     567              :     }
     568          548 :     Shape noteShape = note->shape();
     569          548 :     noteShape.remove_if([paren](ShapeElement& s) {
     570          812 :         return s.item() == paren || s.item()->isBend() || s.item()->isParenthesis() || s.item()->isAccidental() || s.item()->isNoteDot() || s.item()->isLaissezVibSegment();
     571              :     });
     572              : 
     573          548 :     return noteShape;
     574          548 : }
        

Generated by: LCOV version 2.0-1