LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - tupletlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 72.5 % 480 348
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 "tupletlayout.h"
      23              : 
      24              : #include "dom/chordrest.h"
      25              : #include "dom/tuplet.h"
      26              : #include "dom/text.h"
      27              : #include "dom/factory.h"
      28              : #include "dom/stafftype.h"
      29              : #include "dom/chord.h"
      30              : #include "dom/note.h"
      31              : #include "dom/stem.h"
      32              : #include "dom/system.h"
      33              : #include "dom/measure.h"
      34              : 
      35              : #include "tlayout.h"
      36              : #include "autoplace.h"
      37              : 
      38              : using namespace mu::engraving;
      39              : using namespace mu::engraving::rendering::score;
      40              : 
      41         2033 : void TupletLayout::layout(Tuplet* item, LayoutContext& ctx)
      42              : {
      43         2033 :     Tuplet::LayoutData* ldata = item->mutldata();
      44         2033 :     if (item->elements().empty()) {
      45            0 :         LOGD("Tuplet::layout(): tuplet is empty");
      46            4 :         return;
      47              :     }
      48              : 
      49         2033 :     const StaffType* stt = item->staffType();
      50         2033 :     if (stt && stt->isTabStaff() && stt->stemless()) {
      51              :         // is in a TAB without stems, skip any format: tuplets are not shown
      52            4 :         return;
      53              :     }
      54              : 
      55         2029 :     createNumber(item, ctx);
      56              : 
      57         2029 :     computeDirection(item);
      58              : 
      59         2029 :     const ChordRest* cr1 = nullptr;
      60         2029 :     const ChordRest* cr2 = nullptr;
      61         2029 :     computeStartEndCR(item, &cr1, &cr2);
      62              : 
      63         2029 :     bool hasBracket = item->calcHasBracket(cr1, cr2);
      64         2029 :     item->setHasBracket(hasBracket);
      65              : 
      66         2029 :     double mag = (cr1->mag() + cr2->mag()) / 2;
      67         2029 :     ldata->setMag(mag);
      68              : 
      69         2029 :     item->setPos(0.0, 0.0);
      70              : 
      71         2029 :     layoutBracket(item, cr1, cr2, ctx);
      72              : 
      73              :     // collect bounding box
      74         2029 :     RectF r;
      75         2029 :     if (item->number()) {
      76         1712 :         r |= item->number()->ldata()->bbox().translated(item->number()->pos());
      77         1712 :         if (item->hasBracket()) {
      78         1032 :             RectF b;
      79         1032 :             b.setCoords(item->bracketL[1].x(), item->bracketL[1].y(), item->bracketR[2].x(), item->bracketR[2].y());
      80         1032 :             r |= b;
      81              :         }
      82          317 :     } else if (item->hasBracket()) {
      83            1 :         RectF b;
      84            1 :         b.setCoords(item->bracketL[1].x(), item->bracketL[1].y(), item->bracketL[3].x(), item->bracketL[3].y());
      85            1 :         r |= b;
      86              :     }
      87         2029 :     item->setbbox(r);
      88              : 
      89         2029 :     if (!item->cross()) {
      90         1947 :         Autoplace::autoplaceMeasureElement(item, item->mutldata(), item->isUp(), /* add to skyline */ true);
      91              :     }
      92              : }
      93              : 
      94         1992 : void TupletLayout::layoutTupletAndNestedTuplets(Tuplet* t, LayoutContext& ctx)
      95              : {
      96         1992 :     const std::vector<DurationElement*> elements = t->elements();
      97         9087 :     for (auto revIter = elements.rbegin(); revIter != elements.rend(); ++revIter) {
      98         7095 :         DurationElement* d = *revIter;
      99         7095 :         if (d == t) {
     100            0 :             continue;
     101              :         }
     102              :         // if element is tuplet, layoutTuplet(that tuplet)
     103         7095 :         if (d->isTuplet()) {
     104          458 :             layoutTupletAndNestedTuplets(toTuplet(d), ctx);
     105              :         }
     106              :     }
     107              :     // layout t
     108         1992 :     layout(t, ctx);
     109         1992 : }
     110              : 
     111            0 : bool TupletLayout::isTopTuplet(ChordRest* cr)
     112              : {
     113            0 :     Tuplet* t = cr->tuplet();
     114            0 :     if (t && t->elements().front() == cr) {
     115              :         // find top level tuplet
     116            0 :         while (t->tuplet()) {
     117            0 :             t = t->tuplet();
     118              :         }
     119              :         // consider tuplet cross if anything moved within it
     120            0 :         if (t->cross()) {
     121            0 :             return false;
     122              :         } else {
     123            0 :             return true;
     124              :         }
     125              :     }
     126              : 
     127              :     // no tuplet or not first element
     128            0 :     return false;
     129              : }
     130              : 
     131        92140 : bool TupletLayout::notTopTuplet(ChordRest* cr)
     132              : {
     133        92140 :     Tuplet* t = cr->tuplet();
     134        92140 :     if (t && t->elements().front() == cr) {
     135              :         // find top level tuplet
     136         2568 :         while (t->tuplet()) {
     137          625 :             t = t->tuplet();
     138              :         }
     139              :         // consider tuplet cross if anything moved within it
     140         1943 :         if (t->cross()) {
     141           41 :             return true;
     142              :         } else {
     143         1902 :             return false;
     144              :         }
     145              :     }
     146              : 
     147              :     // no tuplet or not first element
     148        90197 :     return false;
     149              : }
     150              : 
     151         2029 : void TupletLayout::createNumber(Tuplet* item, LayoutContext& ctx)
     152              : {
     153         2029 :     if (item->numberType() == TupletNumberType::NO_TEXT) {
     154          317 :         if (item->number()) {
     155            0 :             if (item->number()->selected()) {
     156            0 :                 ctx.deselect(item->number());
     157              :             }
     158            0 :             delete item->number();
     159            0 :             item->setNumber(nullptr);
     160              :         }
     161              : 
     162          317 :         return;
     163              :     }
     164              : 
     165         1712 :     if (!item->number()) {
     166          381 :         Text* number = Factory::createText(item, TextStyleType::TUPLET);
     167          381 :         number->setComposition(true);
     168          381 :         number->setTrack(item->track());
     169          381 :         number->setVisible(item->visible());
     170          381 :         number->setColor(item->color());
     171          381 :         item->setNumber(number);
     172          381 :         item->resetNumberProperty();
     173              :     }
     174              : 
     175              :     // tuplet properties are propagated to number automatically by setProperty()
     176              :     // but we need to make sure flags are as well
     177         1712 :     item->number()->setPropertyFlags(Pid::FONT_FACE, item->propertyFlags(Pid::FONT_FACE));
     178         1712 :     item->number()->setPropertyFlags(Pid::FONT_SIZE, item->propertyFlags(Pid::FONT_SIZE));
     179         1712 :     item->number()->setPropertyFlags(Pid::FONT_STYLE, item->propertyFlags(Pid::FONT_STYLE));
     180         1712 :     item->number()->setPropertyFlags(Pid::ALIGN, item->propertyFlags(Pid::ALIGN));
     181         1712 :     item->number()->setPropertyFlags(Pid::POSITION, item->propertyFlags(Pid::POSITION));
     182              : 
     183         1712 :     const MStyle& style = ctx.conf().style();
     184              : 
     185         1712 :     String numberString = (item->numberType() == TupletNumberType::SHOW_NUMBER)
     186         3374 :                           ? String(u"%1").arg(item->ratio().numerator())
     187         3399 :                           : String(u"%1:%2").arg(item->ratio().numerator(), item->ratio().denominator());
     188         1712 :     if (style.styleB(Sid::tupletUseSymbols)) {
     189            0 :         String smuflNum;
     190            0 :         for (size_t i = 0; i < numberString.size(); ++i) {
     191            0 :             smuflNum.append(u"<sym>tuplet");
     192            0 :             smuflNum.append(numberString.at(i).unicode());
     193            0 :             smuflNum.append(u"</sym>");
     194              :         }
     195            0 :         smuflNum.replace(String(u":"), String(u"Colon"));
     196            0 :         item->number()->setXmlText(smuflNum);
     197            0 :     } else {
     198         1712 :         item->number()->setXmlText(numberString);
     199              :     }
     200              : 
     201         1712 :     item->setIsSmall(true);
     202         1746 :     for (const DurationElement* e : item->elements()) {
     203         1736 :         if ((e->isChordRest() && !toChordRest(e)->isSmall()) || (e->isTuplet() && !toTuplet(e)->isSmall())) {
     204         1702 :             item->setIsSmall(false);
     205         1702 :             break;
     206              :         }
     207              :     }
     208              : 
     209         1712 :     item->number()->mutldata()->setMag(item->isSmall() ? style.styleD(Sid::smallNoteMag) : 1.0);
     210         1712 : }
     211              : 
     212         2029 : void TupletLayout::computeDirection(Tuplet* item)
     213              : {
     214         2029 :     if (item->direction() != DirectionV::AUTO) {
     215          556 :         item->setIsUp(item->direction() == DirectionV::UP);
     216          556 :         return;
     217              :     }
     218              : 
     219         1473 :     int up = 0;
     220         7009 :     for (const DurationElement* e : item->elements()) {
     221         5536 :         if (e->isChord()) {
     222         4246 :             const Chord* c = toChord(e);
     223         4246 :             if (c->stemDirection() != DirectionV::AUTO) {
     224          150 :                 up += c->stemDirection() == DirectionV::UP ? 1000 : -1000;
     225              :             } else {
     226         4096 :                 up += c->up() ? 1 : -1;
     227              :             }
     228              :         }
     229              :     }
     230         1473 :     if (up == 0) {
     231              :         // this is a tuplet full of rests, default to up but also take voices into consideration
     232          217 :         Measure* m = item->measure();
     233          217 :         if (m && m->hasVoices(item->staffIdx())) {
     234            1 :             up = item->voice() % 2 == 0 ? 1 : -1;
     235              :         } else {
     236          216 :             up = 1;             // default up
     237              :         }
     238              :     }
     239              : 
     240         1473 :     item->setIsUp(up > 0);
     241              : }
     242              : 
     243         2029 : void TupletLayout::computeStartEndCR(Tuplet* item, const ChordRest** cr1, const ChordRest** cr2)
     244              : {
     245         2029 :     const DurationElement* startEl = item->elements().front();
     246         2131 :     while (startEl->isTuplet()) {
     247          102 :         const Tuplet* t = toTuplet(startEl);
     248          102 :         if (t->elements().empty()) {
     249            0 :             break;
     250              :         }
     251          102 :         startEl = t->elements().front();
     252              :     }
     253         2029 :     assert(startEl && startEl->isChordRest());
     254              : 
     255         2029 :     const DurationElement* endEl = item->elements().back();
     256         2072 :     while (endEl->isTuplet()) {
     257           43 :         const Tuplet* t = toTuplet(endEl);
     258           43 :         if (t->elements().empty()) {
     259            0 :             break;
     260              :         }
     261           43 :         endEl = t->elements().back();
     262              :     }
     263         2029 :     assert(endEl && endEl->isChordRest());
     264              : 
     265         2029 :     *cr1 = toChordRest(startEl);
     266         2029 :     *cr2 = toChordRest(endEl);
     267         2029 : }
     268              : 
     269         2029 : void TupletLayout::layoutBracket(Tuplet* item, const ChordRest* cr1, const ChordRest* cr2, LayoutContext& ctx)
     270              : {
     271         2029 :     const MStyle& style = ctx.conf().style();
     272         2029 :     double spatium = item->spatium();
     273              : 
     274         2029 :     double maxSlope      = style.styleD(Sid::tupletMaxSlope);
     275         2029 :     bool outOfStaff      = style.styleB(Sid::tupletOutOfStaff);
     276         2029 :     double vHeadDistance = style.styleAbsolute(Sid::tupletVHeadDistance) * item->mag();
     277         2029 :     double vStemDistance = style.styleAbsolute(Sid::tupletVStemDistance) * item->mag();
     278         2029 :     double stemLeft      = (style.styleAbsolute(Sid::tupletStemLeftDistance) - style.styleAbsolute(Sid::tupletBracketWidth) / 2)
     279         2029 :                            * cr1->mag();
     280         2029 :     double stemRight     = (style.styleAbsolute(Sid::tupletStemRightDistance) - style.styleAbsolute(Sid::tupletBracketWidth) / 2)
     281         2029 :                            * cr2->mag();
     282         2029 :     double noteLeft      = (style.styleAbsolute(Sid::tupletNoteLeftDistance) - style.styleAbsolute(Sid::tupletBracketWidth) / 2)
     283         2029 :                            * cr1->mag();
     284         2029 :     double noteRight     = (style.styleAbsolute(Sid::tupletNoteRightDistance) - style.styleAbsolute(Sid::tupletBracketWidth) / 2)
     285         2029 :                            * cr2->mag();
     286              : 
     287         2029 :     int move = 0;
     288         2029 :     if (outOfStaff && cr1->isChordRest() && cr2->isChordRest()) {
     289              :         // account for staff move when adjusting bracket to avoid staff
     290              :         // but don't attempt adjustment unless both endpoints are in same staff
     291         2006 :         if (toChordRest(cr1)->staffMove() == toChordRest(cr2)->staffMove()) {
     292         2000 :             move = toChordRest(cr1)->staffMove();
     293              :         } else {
     294            6 :             outOfStaff = false;
     295              :         }
     296              :     }
     297              : 
     298         2029 :     double l1  = style.styleAbsolute(Sid::tupletBracketHookHeight) * item->mag();
     299         2029 :     double l2l = vHeadDistance;      // left bracket vertical distance
     300         2029 :     double l2r = vHeadDistance;      // right bracket vertical distance
     301              : 
     302         2029 :     if (item->isUp()) {
     303         1195 :         vHeadDistance = -vHeadDistance;
     304              :     }
     305              : 
     306         2029 :     item->setP1(cr1->pagePos());
     307         2029 :     item->setP2(cr2->pagePos());
     308              : 
     309         2029 :     item->p1().rx() -= noteLeft;
     310         2029 :     item->p2().rx() += ctx.conf().noteHeadWidth() + noteRight;
     311         2029 :     item->p1().ry() += vHeadDistance;          // TODO: Direction ?
     312         2029 :     item->p2().ry() += vHeadDistance;
     313              : 
     314         2029 :     double xx1 = item->p1().x();   // use to center the number on the beam
     315              : 
     316         2029 :     double leftNoteEdge = 0.0; // page coordinates
     317         2029 :     double rightNoteEdge = 0.0;
     318         2029 :     if (cr1->isChord()) {
     319         1624 :         const Chord* chord1 = toChord(cr1);
     320         1624 :         leftNoteEdge = chord1->up() ? chord1->downNote()->pageBoundingRect().left() : chord1->upNote()->pageBoundingRect().left();
     321              :     }
     322         2029 :     if (cr2->isChord()) {
     323         1561 :         const Chord* chord2 = toChord(cr2);
     324         1561 :         rightNoteEdge = chord2->up() ? chord2->downNote()->pageBoundingRect().right() : chord2->upNote()->pageBoundingRect().right();
     325              :     }
     326              : 
     327         2029 :     if (item->isUp()) {
     328         1195 :         if (cr1->isChord()) {
     329          916 :             const Chord* chord1 = toChord(cr1);
     330          916 :             Stem* stem = chord1->stem();
     331          916 :             if (stem) {
     332          914 :                 xx1 = stem->pageBoundingRect().x();
     333              :             }
     334          916 :             if (chord1->up() && stem) {
     335          884 :                 item->p1().ry() = stem->pageBoundingRect().y();
     336          884 :                 l2l = vStemDistance;
     337          884 :                 item->p1().rx() = stem->pageBoundingRect().left() - stemLeft;
     338              :             } else {
     339           32 :                 item->p1().ry() = chord1->upNote()->pageBoundingRect().top();
     340           32 :                 item->p1().rx() = leftNoteEdge - noteLeft;
     341              :             }
     342              :         }
     343              : 
     344         1195 :         if (cr2->isChord()) {
     345          846 :             const Chord* chord2 = toChord(cr2);
     346          846 :             Stem* stem = chord2->stem();
     347          846 :             if (stem && chord2->up()) {
     348          816 :                 item->p2().ry() = stem->pageBoundingRect().top();
     349          816 :                 l2r = vStemDistance;
     350          816 :                 item->p2().rx() = stem->pageBoundingRect().right() + stemRight;
     351              :             } else {
     352           30 :                 item->p2().ry() = chord2->upNote()->pageBoundingRect().top();
     353           30 :                 item->p2().rx() = rightNoteEdge + noteRight;
     354              :             }
     355              :         }
     356              :         //
     357              :         // special case: one of the bracket endpoints is
     358              :         // a rest
     359              :         //
     360         1195 :         if (!cr1->isChord() && cr2->isChord()) {
     361           81 :             if (item->p2().y() < item->p1().y()) {
     362           75 :                 item->p1().setY(item->p2().y());
     363              :             } else {
     364            6 :                 item->p2().setY(item->p1().y());
     365              :             }
     366         1114 :         } else if (cr1->isChord() && !cr2->isChord()) {
     367          151 :             if (item->p1().y() < item->p2().y()) {
     368          135 :                 item->p2().setY(item->p1().y());
     369              :             } else {
     370           16 :                 item->p1().setY(item->p2().y());
     371              :             }
     372              :         }
     373              : 
     374              :         // outOfStaff
     375         1195 :         if (outOfStaff) {
     376         1182 :             double min = cr1->measure()->staffPageBoundingRect(cr1->staffIdx() + move).y();
     377         1182 :             if (min < item->p1().y()) {
     378          396 :                 item->p1().ry() = min;
     379          396 :                 l2l = vStemDistance;
     380              :             }
     381         1182 :             min = cr2->measure()->staffPageBoundingRect(cr2->staffIdx() + move).y();
     382         1182 :             if (min < item->p2().y()) {
     383          384 :                 item->p2().ry() = min;
     384          384 :                 l2r = vStemDistance;
     385              :             }
     386              :         }
     387              : 
     388              :         // check that slope is no more than max
     389         1195 :         double d = (item->p2().y() - item->p1().y()) / (item->p2().x() - item->p1().x());
     390         1195 :         if (d < -maxSlope) {
     391              :             // move p1 y up
     392            0 :             item->p1().ry() = item->p2().y() + maxSlope * (item->p2().x() - item->p1().x());
     393         1195 :         } else if (d > maxSlope) {
     394              :             // move p2 y up
     395            2 :             item->p2().ry() = item->p1().ry() + maxSlope * (item->p2().x() - item->p1().x());
     396              :         }
     397              : 
     398              :         // check for collisions
     399         1195 :         size_t n = item->elements().size();
     400         1195 :         if (n >= 3) {
     401         1108 :             d = (item->p2().y() - item->p1().y()) / (item->p2().x() - item->p1().x());
     402         3322 :             for (size_t i = 1; i < (n - 1); ++i) {
     403         2214 :                 const EngravingItem* e = item->elements().at(i);
     404         2214 :                 if (e->isChord()) {
     405         1480 :                     const Chord* chord = toChord(e);
     406         1480 :                     const Stem* stem = chord->stem();
     407         1480 :                     if (stem) {
     408         1478 :                         RectF r(chord->up() ? stem->pageBoundingRect() : chord->upNote()->pageBoundingRect());
     409         1478 :                         double y3 = r.top();
     410         1478 :                         double x3 = r.x() + r.width() * .5;
     411         1478 :                         double y0 = item->p1().y() + (x3 - item->p1().x()) * d;
     412         1478 :                         double c  = y0 - y3;
     413         1478 :                         if (c > 0) {
     414          270 :                             item->p1().ry() -= c;
     415          270 :                             item->p2().ry() -= c;
     416              :                         }
     417              :                     }
     418              :                 }
     419              :             }
     420              :         }
     421              :     } else {
     422          834 :         if (cr1->isChord()) {
     423          708 :             const Chord* chord1 = toChord(cr1);
     424          708 :             Stem* stem = chord1->stem();
     425          708 :             if (stem) {
     426          708 :                 xx1 = stem->pageBoundingRect().x();
     427              :             }
     428          708 :             if (!chord1->up() && stem) {
     429          698 :                 item->p1().ry() = stem->pageBoundingRect().bottom();
     430          698 :                 l2l = vStemDistance;
     431          698 :                 item->p1().rx() = stem->pageBoundingRect().left() - stemLeft;
     432              :             } else {
     433           10 :                 item->p1().ry() = chord1->downNote()->pageBoundingRect().bottom();
     434           10 :                 item->p1().rx() = leftNoteEdge - noteLeft;
     435              :             }
     436              :         }
     437              : 
     438          834 :         if (cr2->isChord()) {
     439          715 :             const Chord* chord2 = toChord(cr2);
     440          715 :             Stem* stem = chord2->stem();
     441          715 :             if (stem && !chord2->up()) {
     442          711 :                 item->p2().ry() = stem->pageBoundingRect().bottom();
     443          711 :                 l2r = vStemDistance;
     444          711 :                 item->p2().rx() = stem->pageBoundingRect().right() + stemRight;
     445              :             } else {
     446            4 :                 item->p2().ry() = chord2->downNote()->pageBoundingRect().bottom();
     447            4 :                 item->p2().rx() = rightNoteEdge + noteRight;
     448              :             }
     449              :         }
     450              :         //
     451              :         // special case: one of the bracket endpoints is
     452              :         // a rest
     453              :         //
     454          834 :         if (!cr1->isChord() && cr2->isChord()) {
     455           97 :             if (item->p2().y() > item->p1().y()) {
     456           97 :                 item->p1().setY(item->p2().y());
     457              :             } else {
     458            0 :                 item->p2().setY(item->p1().y());
     459              :             }
     460          737 :         } else if (cr1->isChord() && !cr2->isChord()) {
     461           90 :             if (item->p1().y() > item->p2().y()) {
     462           90 :                 item->p2().setY(item->p1().y());
     463              :             } else {
     464            0 :                 item->p1().setY(item->p2().y());
     465              :             }
     466              :         }
     467              :         // outOfStaff
     468          834 :         if (outOfStaff) {
     469          818 :             double max = cr1->measure()->staffPageBoundingRect(cr1->staffIdx() + move).bottom();
     470          818 :             if (max > item->p1().y()) {
     471           57 :                 item->p1().ry() = max;
     472           57 :                 l2l = vStemDistance;
     473              :             }
     474          818 :             max = cr2->measure()->staffPageBoundingRect(cr2->staffIdx() + move).bottom();
     475          818 :             if (max > item->p2().y()) {
     476           58 :                 item->p2().ry() = max;
     477           58 :                 l2r = vStemDistance;
     478              :             }
     479              :         }
     480              :         // check that slope is no more than max
     481          834 :         double d = (item->p2().y() - item->p1().y()) / (item->p2().x() - item->p1().x());
     482          834 :         if (d < -maxSlope) {
     483              :             // move p1 y up
     484            1 :             item->p2().ry() = item->p1().y() - maxSlope * (item->p2().x() - item->p1().x());
     485          833 :         } else if (d > maxSlope) {
     486              :             // move p2 y up
     487            1 :             item->p1().ry() = item->p2().ry() - maxSlope * (item->p2().x() - item->p1().x());
     488              :         }
     489              : 
     490              :         // check for collisions
     491          834 :         size_t n = item->elements().size();
     492          834 :         if (n >= 3) {
     493          744 :             d  = (item->p2().y() - item->p1().y()) / (item->p2().x() - item->p1().x());
     494         1737 :             for (size_t i = 1; i < (n - 1); ++i) {
     495          993 :                 EngravingItem* e = item->elements()[i];
     496          993 :                 if (e->isChord()) {
     497          766 :                     const Chord* chord = toChord(e);
     498          766 :                     const Stem* stem = chord->stem();
     499          766 :                     if (stem) {
     500          766 :                         RectF r(chord->up() ? chord->downNote()->pageBoundingRect() : stem->pageBoundingRect());
     501          766 :                         double y3 = r.bottom();
     502          766 :                         double x3 = r.x() + r.width() * .5;
     503          766 :                         double y0 = item->p1().y() + (x3 - item->p1().x()) * d;
     504          766 :                         double c  = y0 - y3;
     505          766 :                         if (c < 0) {
     506          179 :                             item->p1().ry() -= c;
     507          179 :                             item->p2().ry() -= c;
     508              :                         }
     509              :                     }
     510              :                 }
     511              :             }
     512              :         }
     513              :     }
     514              : 
     515         2029 :     if (!cr1->isChord()) {
     516          405 :         item->p1().rx() = cr1->pageBoundingRect().left() - noteLeft;
     517              :     }
     518         2029 :     if (!cr2->isChord()) {
     519          468 :         Shape shape = cr2->ldata()->shape();
     520          936 :         auto shEl = shape.find_if([](const ShapeElement& i) { return i.item() && i.item()->isChordRest(); });
     521          468 :         DO_ASSERT(shEl);
     522          468 :         item->p2().rx() = shEl->translated(cr2->pagePos()).right() + noteRight;
     523          468 :     }
     524              : 
     525         2029 :     PointF mp(item->layoutParent()->pagePos());
     526         2029 :     if (item->layoutParent()->isMeasure()) {
     527         2029 :         System* s = toMeasure(item->layoutParent())->system();
     528         2029 :         if (s) {
     529         2029 :             SysStaff* tupletStaff = s->staff(item->vStaffIdx());
     530         2029 :             mp.ry() += tupletStaff ? tupletStaff->y() : 0.0;
     531              :         }
     532              :     }
     533         2029 :     item->p1() -= mp;
     534         2029 :     item->p2() -= mp;
     535              : 
     536         2029 :     item->p1() += item->userP1();
     537         2029 :     item->p2() += item->userP2();
     538         2029 :     xx1 -= mp.x();
     539              : 
     540         2029 :     item->p1().ry() -= l2l * (item->isUp() ? 1.0 : -1.0);
     541         2029 :     item->p2().ry() -= l2r * (item->isUp() ? 1.0 : -1.0);
     542              : 
     543         2029 :     const double yOffset = item->staffOffsetY();
     544              : 
     545         2029 :     item->p1().ry() -= yOffset;
     546         2029 :     item->p2().ry() -= yOffset;
     547              : 
     548         2029 :     if (style.styleB(Sid::tupletExtendToEndOfDuration)) {
     549            0 :         extendToEndOfDuration(item, toChordRest(cr2));
     550              :     }
     551              : 
     552              :     // l2l l2r, mp, _p1, _p2 const
     553              : 
     554              :     // center number
     555         2029 :     double xNumber = 0.0;
     556         2029 :     double numberWidth = 0.0;
     557         2029 :     if (item->number()) {
     558         1712 :         Text::LayoutData* numLdata = item->number()->mutldata();
     559         1712 :         TLayout::layoutText(item->number(), numLdata);
     560         1712 :         numberWidth = numLdata->bbox().width();
     561              : 
     562         1712 :         double yNumber = item->p1().y() + (item->p2().y() - item->p1().y()) * .5 - l1 * (item->isUp() ? 1.0 : -1.0);
     563              : 
     564         1712 :         if (placeNumberOnRhythmicCenter(item, cr1, cr2, ctx)) {
     565            0 :             xNumber = computeRhythmicCenter(item, cr2);
     566         1712 :         } else if (cr1->beam() && cr2->beam() && cr1->beam() == cr2->beam() && !item->hasBracket()) {
     567              :             // for beamed tuplets, center number on beam - if they don't have a bracket
     568          634 :             const ChordRest* crr = toChordRest(cr1);
     569          634 :             if (item->isUp() == crr->up()) {
     570          634 :                 double deltax = cr2->pagePos().x() - cr1->pagePos().x();
     571          634 :                 xNumber = xx1 + deltax * .5;
     572              :             } else {
     573            0 :                 double deltax = item->p2().x() - item->p1().x();
     574            0 :                 xNumber = item->p1().x() + deltax * .5;
     575              :             }
     576              :         } else {
     577              :             // otherwise center on the bracket (TODO: make centering rules customizable?)
     578         1078 :             double deltax = item->p2().x() - item->p1().x();
     579         1078 :             xNumber = item->p1().x() + deltax * .5;
     580              :         }
     581              : 
     582         1712 :         numLdata->setPos(PointF(xNumber, yNumber) - item->ldata()->pos());
     583              :     }
     584              : 
     585         2029 :     if (item->hasBracket()) {
     586         1033 :         double slope = (item->p2().y() - item->p1().y()) / (item->p2().x() - item->p1().x());
     587         1033 :         const double numberGap = 0.35 * spatium;
     588              : 
     589         1033 :         if (item->isUp()) {
     590          652 :             if (item->number()) {
     591              :                 //set width of bracket hole
     592          652 :                 double x     = xNumber - numberWidth * .5 - numberGap;
     593          652 :                 item->p1().rx() = std::min(item->p1().x(), x - 0.5 * l1); // ensure enough space for the number
     594          652 :                 double y     = item->p1().y() + (x - item->p1().x()) * slope;
     595          652 :                 item->bracketL[0] = PointF(item->p1().x(), item->p1().y());
     596          652 :                 item->bracketL[1] = PointF(item->p1().x(), item->p1().y() - l1);
     597          652 :                 item->bracketL[2] = PointF(x,   y - l1);
     598              : 
     599              :                 //set width of bracket hole
     600          652 :                 x           = xNumber + numberWidth * .5 + numberGap;
     601          652 :                 item->p2().rx() = std::max(item->p2().x(), x + 0.5 * l1); // ensure enough space for the number
     602          652 :                 y           = item->p1().y() + (x - item->p1().x()) * slope;
     603          652 :                 item->bracketR[0] = PointF(x,   y - l1);
     604          652 :                 item->bracketR[1] = PointF(item->p2().x(), item->p2().y() - l1);
     605          652 :                 item->bracketR[2] = PointF(item->p2().x(), item->p2().y());
     606              :             } else {
     607            0 :                 item->bracketL[0] = PointF(item->p1().x(), item->p1().y());
     608            0 :                 item->bracketL[1] = PointF(item->p1().x(), item->p1().y() - l1);
     609            0 :                 item->bracketL[2] = PointF(item->p2().x(), item->p2().y() - l1);
     610            0 :                 item->bracketL[3] = PointF(item->p2().x(), item->p2().y());
     611              :             }
     612              :         } else {
     613          381 :             if (item->number()) {
     614              :                 //set width of bracket hole
     615          380 :                 double x     = xNumber - numberWidth * .5 - numberGap;
     616          380 :                 item->p1().rx() = std::min(item->p1().x(), x - 0.5 * l1); // ensure enough space for the number
     617          380 :                 double y     = item->p1().y() + (x - item->p1().x()) * slope;
     618          380 :                 item->bracketL[0] = PointF(item->p1().x(), item->p1().y());
     619          380 :                 item->bracketL[1] = PointF(item->p1().x(), item->p1().y() + l1);
     620          380 :                 item->bracketL[2] = PointF(x,   y + l1);
     621              : 
     622              :                 //set width of bracket hole
     623          380 :                 x           = xNumber + numberWidth * .5 + numberGap;
     624          380 :                 item->p2().rx() = std::max(item->p2().x(), x + 0.5 * l1);
     625          380 :                 y           = item->p1().y() + (x - item->p1().x()) * slope;
     626          380 :                 item->bracketR[0] = PointF(x,   y + l1);
     627          380 :                 item->bracketR[1] = PointF(item->p2().x(), item->p2().y() + l1);
     628          380 :                 item->bracketR[2] = PointF(item->p2().x(), item->p2().y());
     629              :             } else {
     630            1 :                 item->bracketL[0] = PointF(item->p1().x(), item->p1().y());
     631            1 :                 item->bracketL[1] = PointF(item->p1().x(), item->p1().y() + l1);
     632            1 :                 item->bracketL[2] = PointF(item->p2().x(), item->p2().y() + l1);
     633            1 :                 item->bracketL[3] = PointF(item->p2().x(), item->p2().y());
     634              :             }
     635              :         }
     636              :     }
     637         2029 : }
     638              : 
     639         1712 : bool TupletLayout::placeNumberOnRhythmicCenter(Tuplet* item, const ChordRest* cr1, const ChordRest* cr2, LayoutContext& ctx)
     640              : {
     641         1712 :     if (ctx.conf().styleB(Sid::tupletNumberRythmicCenter) && !isSymmetric(item, cr1, cr2)) {
     642            0 :         Fraction center = centerTick(item);
     643            0 :         if (cr2->tick() <= center) {
     644            0 :             return ctx.conf().styleB(Sid::tupletExtendToEndOfDuration);
     645              :         }
     646              : 
     647            0 :         return true;
     648              :     }
     649              : 
     650         1712 :     return false;
     651              : }
     652              : 
     653            0 : bool TupletLayout::isSymmetric(Tuplet* item, const ChordRest* cr1, const ChordRest* cr2)
     654              : {
     655            0 :     Fraction endTick = cr2->endTick();
     656              : 
     657            0 :     std::vector<Segment*> tupletSegments;
     658            0 :     tupletSegments.reserve(item->elements().size());
     659              : 
     660            0 :     for (Segment* segment = cr1->segment(); segment && segment->tick() < endTick; segment = segment->nextActive()) {
     661            0 :         tupletSegments.push_back(segment);
     662              :     }
     663              : 
     664            0 :     size_t segmentsCount = tupletSegments.size();
     665            0 :     for (size_t i = 0; i < segmentsCount; ++i) {
     666            0 :         size_t j = segmentsCount - 1 - i;
     667            0 :         if (j <= i) {
     668            0 :             break;
     669              :         }
     670            0 :         Segment* firstSeg = tupletSegments[i];
     671            0 :         Segment* secondSeg = tupletSegments[j];
     672            0 :         if (firstSeg->ticks() != secondSeg->ticks()) {
     673            0 :             return false;
     674              :         }
     675              :     }
     676              : 
     677            0 :     return true;
     678            0 : }
     679              : 
     680            0 : double TupletLayout::computeRhythmicCenter(Tuplet* item, const ChordRest* endChord)
     681              : {
     682            0 :     Fraction center = centerTick(item);
     683              : 
     684            0 :     staff_idx_t track = item->track();
     685            0 :     const Segment* refSeg = nullptr;
     686            0 :     const ChordRest* refCR = nullptr;
     687            0 :     for (Segment* seg = endChord->segment(); seg; seg = seg->prev(SegmentType::ChordRest)) {
     688            0 :         refSeg = seg;
     689            0 :         refCR = toChordRest(seg->element(track));
     690            0 :         if (!refCR) {
     691            0 :             continue;
     692              :         }
     693            0 :         if (seg->tick() <= center) {
     694            0 :             break;
     695              :         }
     696              :     }
     697              : 
     698            0 :     IF_ASSERT_FAILED(refSeg && refCR) {
     699            0 :         return 0.0;
     700              :     }
     701              : 
     702            0 :     if (refSeg->tick() == center) {
     703            0 :         double xRef = 0.0;
     704            0 :         if (refCR->isChord()) {
     705            0 :             const Chord* chord = toChord(refCR);
     706            0 :             bool chordUp = chord->up();
     707            0 :             bool tupletPlaceAbove = item->isUp();
     708            0 :             xRef = 0.5 * chord->upNote()->headWidth();
     709            0 :             if (chordUp == tupletPlaceAbove) {
     710              :                 // Stem-side correction
     711            0 :                 xRef += (chordUp ? 0.25 : -0.25) * chord->upNote()->headWidth();
     712              :             }
     713              :         } else {
     714            0 :             xRef = 0.5 * refCR->width();
     715              :         }
     716            0 :         return refSeg->x() + xRef;
     717              :     }
     718              : 
     719            0 :     Fraction refCRTicks = refCR->actualTicks();
     720            0 :     Fraction tickDiff = center - refCR->tick();
     721            0 :     double tickRatio = (tickDiff / refCRTicks).toDouble();
     722              : 
     723            0 :     const Segment* nextSeg = item->measure()->findSegment(SegmentType::ChordRest, refSeg->tick() + refCRTicks);
     724            0 :     double xRef = refSeg->x() + (refCR->isChord() ? toChord(refCR)->upNote()->headWidth() : refCR->width());
     725            0 :     double refWidth = nextSeg ? nextSeg->x() - xRef : refSeg->width();
     726              : 
     727            0 :     double relativeWidth = refWidth * tickRatio;
     728              : 
     729            0 :     return xRef + relativeWidth;
     730              : }
     731              : 
     732            0 : Fraction TupletLayout::centerTick(Tuplet* item)
     733              : {
     734            0 :     Fraction startTick = item->tick();
     735            0 :     Fraction baseLen = item->baseLen().ticks();
     736            0 :     Fraction ratio = item->ratio();
     737            0 :     Fraction subdivision = item->baseLen().ticks() / item->ratio();
     738            0 :     Fraction endTick = startTick + baseLen * ratio.denominator();
     739              : 
     740            0 :     Fraction centerTick = (startTick + endTick - subdivision) / 2;
     741              : 
     742            0 :     return centerTick;
     743              : }
     744              : 
     745            0 : void TupletLayout::extendToEndOfDuration(Tuplet* item, const ChordRest* endCR)
     746              : {
     747            0 :     Fraction baseDuration = item->baseLen().ticks();
     748            0 :     if (endCR->ticks() <= baseDuration) {
     749            0 :         return;
     750              :     }
     751              : 
     752            0 :     Fraction lastTupletSubdivision = endCR->endTick() - baseDuration / item->ratio();
     753            0 :     Segment* refSegment = endCR->segment();
     754            0 :     while (refSegment) {
     755            0 :         Segment* nextCRSeg = refSegment->next1(SegmentType::ChordRest);
     756            0 :         if (!nextCRSeg || nextCRSeg->tick() > lastTupletSubdivision) {
     757            0 :             break;
     758              :         }
     759            0 :         refSegment = nextCRSeg;
     760              :     }
     761              : 
     762            0 :     Fraction tickRatio = (lastTupletSubdivision - refSegment->tick()) / refSegment->ticks();
     763              : 
     764            0 :     double xResult = refSegment->pagePos().x() + refSegment->width() * tickRatio.toDouble() + item->score()->noteHeadWidth();
     765              : 
     766            0 :     const double padding = 0.6 * item->spatium();
     767              : 
     768            0 :     Segment* nextSeg = refSegment->next1WithElemsOnStaff(endCR->vStaffIdx(), ~SegmentType::TimeTick);
     769            0 :     xResult = std::min(xResult, nextSeg->pagePos().x() - padding);
     770              : 
     771            0 :     track_idx_t startTrack = staff2track(endCR->vStaffIdx());
     772            0 :     track_idx_t endTrack = startTrack + VOICES;
     773            0 :     if (nextSeg->isChordRestType()) {
     774            0 :         for (track_idx_t track = startTrack; track < endTrack; ++track) {
     775            0 :             if (ChordRest* chordRest = toChordRest(nextSeg->element(track))) {
     776            0 :                 if (Tuplet* nextTuplet = chordRest->tuplet()) {
     777            0 :                     if (nextTuplet->elements().front() == chordRest && nextTuplet->isUp() == item->isUp()) {
     778            0 :                         double xStartOfNextTuplet = nextTuplet->pagePos().x() + nextTuplet->p1().x();
     779            0 :                         xResult = std::min(xResult, xStartOfNextTuplet - padding);
     780              :                     }
     781              :                 }
     782              :             }
     783              :         }
     784              :     }
     785              : 
     786            0 :     Shape nextSegShape = nextSeg->staffShape(endCR->vStaffIdx());
     787            0 :     nextSegShape.translate(PointF(nextSeg->pagePos().x(), nextSeg->system()->staff(endCR->vStaffIdx())->y()));
     788            0 :     double yAbove = item->p2().y() - (item->isUp() ? item->style().styleAbsolute(Sid::tupletBracketHookHeight) : 0.0);
     789            0 :     double yBelow = item->p2().y() + (item->isUp() ? 0.0 : item->style().styleAbsolute(Sid::tupletBracketHookHeight));
     790            0 :     double left = nextSegShape.leftMostEdgeAtHeight(yAbove, yBelow);
     791            0 :     xResult = std::min(xResult, left - padding);
     792              : 
     793            0 :     double curPos = item->p2().x();
     794            0 :     item->p2().rx() = std::max(curPos, xResult - item->measure()->pagePos().x());
     795            0 : }
        

Generated by: LCOV version 2.0-1