LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - guitarbendlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 85.8 % 500 429
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              : 
      23              : #include "tlayout.h"
      24              : #include "textlayout.h"
      25              : #include "guitarbendlayout.h"
      26              : #include "guitardivelayout.h"
      27              : 
      28              : #include "../../dom/chord.h"
      29              : #include "../../dom/guitarbend.h"
      30              : #include "../../dom/measure.h"
      31              : #include "../../dom/note.h"
      32              : #include "../../dom/score.h"
      33              : #include "../../dom/segment.h"
      34              : #include "../../dom/staff.h"
      35              : #include "../../dom/system.h"
      36              : 
      37              : using namespace mu::engraving;
      38              : using namespace muse::draw;
      39              : using namespace mu::engraving::rendering::score;
      40              : 
      41         1256 : void GuitarBendLayout::updateSegmentsAndLayout(SLine* item, LayoutContext& ctx, System* system)
      42              : {
      43         1256 :     Note* startNote = toNote(item->startElement());
      44         1256 :     Note* endNote = toNote(item->endElement());
      45         1256 :     if (!startNote || !endNote) {
      46            0 :         return;
      47              :     }
      48              : 
      49         1256 :     System* system1 = startNote->findMeasure()->system();
      50         1256 :     System* system2 = endNote->findMeasure()->system();
      51              : 
      52         1256 :     if (!system1) { // system2 may be null if the end note's system hasn't been laid out yet
      53            0 :         return;
      54              :     }
      55              : 
      56         1256 :     item->setTick(startNote->tick());
      57         1256 :     item->setTick2(endNote->tick());
      58              : 
      59         1256 :     unsigned int segmentsNeeded = system1 == system2 ? 1 : 2;
      60         1256 :     size_t segmentCount = item->spannerSegments().size();
      61         1256 :     if (segmentCount != segmentsNeeded) {
      62          790 :         item->fixupSegments(segmentsNeeded, [item]() { return item->createLineSegment(); });
      63              :     }
      64              : 
      65         1256 :     item->frontSegment()->moveToSystem(system1);
      66         1256 :     if (segmentsNeeded == 1) {
      67         1256 :         item->frontSegment()->setSpannerSegmentType(SpannerSegmentType::SINGLE);
      68              :     } else {
      69            0 :         item->frontSegment()->setSpannerSegmentType(SpannerSegmentType::BEGIN);
      70            0 :         item->backSegment()->moveToSystem(system2);
      71            0 :         item->backSegment()->setSpannerSegmentType(SpannerSegmentType::END);
      72              :     }
      73              : 
      74         2512 :     for (SpannerSegment* segment : item->spannerSegments()) {
      75         1256 :         if (!segment->system() || (system && segment->system() != system)) {
      76              :             /* this segment belongs to a system which either has already been
      77              :              * laid out or will be laid out in a later pass */
      78            0 :             continue;
      79              :         }
      80         1256 :         segment->setTrack(item->track());
      81         1256 :         TLayout::layoutLineSegment(toLineSegment(segment), ctx);
      82              :     }
      83              : 
      84         1256 :     TLayout::fillNoteShape(startNote, startNote->mutldata());
      85              : }
      86              : 
      87         1156 : void GuitarBendLayout::layoutStandardStaff(GuitarBendSegment* item, LayoutContext& ctx)
      88              : {
      89         1156 :     GuitarBend* bend = item->guitarBend();
      90              : 
      91         1156 :     if (bend->bendType() == GuitarBendType::DIP) {
      92            6 :         GuitarDiveLayout::layoutDip(item, ctx);
      93         1150 :     } else if (bend->bendType() == GuitarBendType::SCOOP) {
      94            3 :         GuitarDiveLayout::layoutScoop(item);
      95         1147 :     } else if (bend->bendType() == GuitarBendType::SLIGHT_BEND) {
      96           52 :         layoutSlightBend(item, ctx);
      97              :     } else {
      98         1095 :         layoutAngularBend(item, ctx);
      99              :     }
     100         1156 : }
     101              : 
     102         1095 : void GuitarBendLayout::layoutAngularBend(GuitarBendSegment* item, LayoutContext& ctx)
     103              : {
     104         1095 :     GuitarBend* bend = item->guitarBend();
     105              : 
     106         1095 :     computeUp(bend);
     107         1095 :     computeIsInside(bend);
     108              : 
     109         1095 :     Note* startNote = bend->startNote();
     110         1095 :     Note* endNote = bend->endNote();
     111         1095 :     if (!startNote || !endNote) {
     112            0 :         return;
     113              :     }
     114              : 
     115         2100 :     if ((bend->bendType() == GuitarBendType::PRE_BEND || bend->bendType() == GuitarBendType::PRE_DIVE)
     116         2100 :         && !startNote->parenthesisInfo()) {
     117           67 :         startNote->setGhost(true);
     118           67 :         startNote->mutldata()->reset();
     119           67 :         TLayout::layoutChord(startNote->chord(), ctx);
     120              :     }
     121              : 
     122         1095 :     System* startSystem = startNote->chord()->segment()->system();
     123         1095 :     if (!startSystem) {
     124            0 :         return;
     125              :     }
     126              : 
     127         1095 :     const bool up = bend->ldata()->up();
     128         1095 :     const int upSign = up ? -1 : 1;
     129         1095 :     const bool isInside = bend->ldata()->isInside();
     130         1095 :     const double spatium = item->spatium();
     131         1095 :     const double verticalPadding = 0.2 * spatium;
     132         1095 :     const double horizontalIndent = 0.2 * spatium;
     133         1095 :     double heightDiff = endNote->y() - startNote->y();
     134              : 
     135         1095 :     PointF startPos;
     136         1095 :     PointF endPos;
     137              : 
     138         1095 :     if (item->isSingleBeginType()) {
     139         1095 :         Chord* startChord = startNote->chord();
     140         1095 :         PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
     141         1095 :         startPos = startNotePos;
     142              : 
     143         1095 :         double xOff = (isInside ? 1 : 0.5) * startNote->width() + horizontalIndent * startNote->mag();
     144         1095 :         double yOff = upSign * verticalPadding * startNote->mag();
     145         1095 :         if (!isInside) {
     146         1074 :             yOff += upSign * 0.5 * startNote->height();
     147              :         }
     148              : 
     149         1095 :         startPos += PointF(xOff, yOff);
     150              :     } else {
     151            0 :         System* endSystem = endNote->chord()->segment()->system();
     152            0 :         if (!endSystem) {
     153            0 :             return;
     154              :         }
     155            0 :         double x = endSystem->firstNoteRestSegmentX(true);
     156            0 :         double y = endNote->y() + upSign * 0.5 * endNote->height() - 0.5 * heightDiff;
     157            0 :         startPos = PointF(x, y);
     158              :     }
     159              : 
     160         1095 :     if (item->isSingleEndType()) {
     161         1095 :         Chord* endChord = endNote->chord();
     162         1095 :         PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
     163         1095 :         endPos = endNotePos;
     164              : 
     165         1095 :         double xOff = (isInside ? 0 : 0.5) * endNote->width() - horizontalIndent * endNote->mag();
     166         1095 :         double yOff = upSign * verticalPadding * endNote->mag();
     167         1095 :         if (!isInside) {
     168         1074 :             yOff += upSign * 0.5 * endNote->height();
     169              :         }
     170              : 
     171         1095 :         endPos += PointF(xOff, yOff);
     172              :     } else {
     173            0 :         double x = startSystem->endingXForOpenEndedLines();
     174            0 :         double y = startNote->y() + upSign * 0.5 * startNote->height() + 0.5 * heightDiff;
     175            0 :         endPos = PointF(x, y);
     176              :     }
     177              : 
     178         1095 :     avoidBadStaffLineIntersection(item, startPos);
     179         1095 :     avoidBadStaffLineIntersection(item, endPos);
     180         1095 :     adjustX(item, startPos, endPos, startNote, endNote);
     181              : 
     182         1095 :     if (bend->isDive() && bend->overlappingBendOrDive()) {
     183            0 :         double yOff = (up ? -1 : 1) * spatium;
     184            0 :         startPos.setY(startPos.y() + yOff);
     185            0 :         endPos.setY(endPos.y() + yOff);
     186              :     }
     187              : 
     188         1095 :     item->setPos(startPos);
     189         1095 :     item->setPos2(endPos - startPos);
     190              : 
     191         1095 :     const double vertexHeightMin = 0.75 * spatium;
     192         1095 :     const double vertexHeightMax = 2.0 * spatium;
     193              : 
     194         1095 :     PointF relEndPoint = endPos - startPos;
     195         1095 :     double angle = -atan(relEndPoint.y() / relEndPoint.x());
     196              : 
     197         1095 :     double baseLength = relEndPoint.x();
     198         1095 :     double minLength = spatium;
     199         1095 :     double vertexHeight = vertexHeightMin + 0.1 * (baseLength - minLength);
     200         1095 :     vertexHeight = std::min(vertexHeight, vertexHeightMax);
     201              : 
     202         1095 :     PointF vertex = 0.5 * relEndPoint;
     203         1095 :     PointF vertexDispl = PointF(upSign * vertexHeight * sin(angle), upSign * vertexHeight * cos(angle));
     204         1095 :     vertex += vertexDispl;
     205              : 
     206         1095 :     PointF vertexAbsCoord = vertex + startPos;
     207         1095 :     avoidBadStaffLineIntersection(item, vertexAbsCoord);
     208         1095 :     vertex = vertexAbsCoord - startPos;
     209              : 
     210         1095 :     item->mutldata()->setVertexPoint(vertex);
     211              : 
     212         1095 :     if (item->isSingleBeginType()) {
     213         1095 :         startNote->addStartLineAttachPoint(item->pos(), bend);
     214              :     }
     215         1095 :     if (item->isSingleEndType()) {
     216         1095 :         endNote->addEndLineAttachPoint(item->pos() + item->pos2(), bend);
     217              :     }
     218              : 
     219         1095 :     PainterPath path;
     220         1095 :     path.lineTo(vertex + item->vertexPointOff());
     221         1095 :     path.lineTo(item->pos2());
     222         1095 :     item->mutldata()->setPath(path);
     223              : 
     224         1095 :     RectF r = RectF(PointF(0.0, 0.0), vertex + item->vertexPointOff());
     225         1095 :     r.unite(RectF(vertex + item->vertexPointOff(), item->pos2()));
     226         1095 :     item->setbbox(r);
     227         1095 : }
     228              : 
     229         1095 : void GuitarBendLayout::computeUp(GuitarBend* item)
     230              : {
     231              :     // NOTE: at some point, it would be good to find a way to reuse the logic of Tie::computeUp() (M.S.)
     232              : 
     233         1095 :     GuitarBend::LayoutData* layoutData = item->mutldata();
     234              : 
     235         1095 :     if (item->direction() != DirectionV::AUTO) {
     236            0 :         layoutData->setUp(item->direction() == DirectionV::UP);
     237            0 :         return;
     238              :     }
     239              : 
     240         1095 :     Measure* measure = item->score()->tick2measure(item->tick());
     241         1095 :     if (!measure) {
     242            0 :         return;
     243              :     }
     244         1095 :     if (measure->hasVoices(item->staffIdx())) {
     245            6 :         layoutData->setUp(item->track() % 2);
     246            6 :         return;
     247              :     }
     248              : 
     249         1089 :     Note* startN = item->startNote();
     250         1089 :     Chord* startChord = startN->chord();
     251         1089 :     Note* endN = item->endNote();
     252         1089 :     Chord* endChord = endN->chord();
     253              : 
     254         1089 :     if (startChord->up() != endChord->up()) {
     255          444 :         layoutData->setUp(true);
     256          444 :         return;
     257              :     }
     258              : 
     259          645 :     if (startChord->notes().size() == 1 && startChord->stem()) {
     260          320 :         layoutData->setUp(!startChord->up());
     261          320 :         return;
     262              :     }
     263              : 
     264          325 :     if (endChord->notes().size() == 1 && endChord->stem()) {
     265          124 :         layoutData->setUp(!endChord->up());
     266          124 :         return;
     267              :     }
     268              : 
     269          201 :     int bendCount = 0;
     270          201 :     int indexOfThis = 0;
     271          603 :     for (Note* note : (startChord->notes().size() > 1 ? startChord->notes() : endChord->notes())) {
     272          693 :         for (Spanner* spanner : note->spannerFor()) {
     273          291 :             if (spanner->isGuitarBend()) {
     274          291 :                 if (spanner == item) {
     275          114 :                     indexOfThis = bendCount;
     276              :                 }
     277          291 :                 ++bendCount;
     278              :             }
     279              :         }
     280              :     }
     281              : 
     282          201 :     bool up = indexOfThis >= floor(double(bendCount) / 2);
     283          201 :     layoutData->setUp(up);
     284              : }
     285              : 
     286         1095 : void GuitarBendLayout::computeIsInside(GuitarBend* item)
     287              : {
     288         1095 :     GuitarBend::LayoutData* layoutData = item->mutldata();
     289         1095 :     Note* startN = item->startNote();
     290         1095 :     Chord* startChord = startN->chord();
     291         1095 :     Note* endN = item->endNote();
     292         1095 :     Chord* endChord = endN->chord();
     293              : 
     294         1095 :     if (startChord->notes().size() <= 2 || endChord->notes().size() <= 2) {
     295         1035 :         layoutData->setIsInside(false);
     296         1035 :         return;
     297              :     }
     298              : 
     299          102 :     if (startN == startChord->upNote() || startN == startChord->downNote()
     300          102 :         || endN == endChord->upNote() || endN == endChord->downNote()) {
     301           39 :         layoutData->setIsInside(false);
     302           39 :         return;
     303              :     }
     304              : 
     305           21 :     layoutData->setIsInside(true);
     306              : }
     307              : 
     308         3285 : void GuitarBendLayout::avoidBadStaffLineIntersection(GuitarBendSegment* item, PointF& point)
     309              : {
     310         3285 :     int upSign = item->guitarBend()->ldata()->up() ? -1 : 1;
     311         3285 :     double spatium = item->spatium();
     312         3285 :     double lineDist = spatium * item->staff()->lineDistance(item->tick());
     313         3285 :     double closestStaffLineY = lineDist * round(point.y() / lineDist);
     314              : 
     315         3285 :     bool isInsideStaff = closestStaffLineY >= 0 && closestStaffLineY <= 4 * spatium;
     316         3285 :     if (isInsideStaff && item->autoplace()) {
     317         1855 :         const double minLineDist = 0.5 * item->style().styleAbsolute(Sid::staffLineWidth) + 0.15 * spatium;
     318         1855 :         double pointLineDist = point.y() - closestStaffLineY;
     319         1855 :         if (std::abs(pointLineDist) < minLineDist) {
     320          564 :             point.setY(closestStaffLineY + upSign * minLineDist);
     321              :         }
     322              :     }
     323         3285 : }
     324              : 
     325         1095 : void GuitarBendLayout::adjustX(GuitarBendSegment* item, PointF& startPos, PointF& endPos, const Note* startNote, const Note* endNote)
     326              : {
     327         1095 :     GuitarBend* bend = item->guitarBend();
     328         1095 :     GuitarBend::LayoutData* layoutData = bend->mutldata();
     329         1095 :     bool up = item->guitarBend()->ldata()->up();
     330         1095 :     Chord* startChord = startNote->chord();
     331         1095 :     Chord* endChord = endNote->chord();
     332              : 
     333         2169 :     bool adjustStart = item->isSingleBeginType() && ((startChord->stem() && startChord->up() && up)
     334         1074 :                                                      || (up && startNote != startChord->upNote())
     335         1026 :                                                      || (!up && startNote != startChord->downNote()));
     336         2175 :     bool adjustEnd = item->isSingleEndType() && ((endChord->stem() && !endChord->up() && !up)
     337         1080 :                                                  || (up && endNote != endChord->upNote())
     338         1023 :                                                  || (!up && endNote != endChord->downNote()));
     339              : 
     340         1095 :     if (!layoutData->isInside() && !adjustStart && !adjustEnd) {
     341          987 :         return;
     342              :     }
     343              : 
     344          108 :     const double vertMargin = 0.1 * item->spatium();
     345          108 :     const double padding = 0.2 * item->spatium();
     346              : 
     347          108 :     if (item->isSingleBeginType()) {
     348          216 :         Shape startChordShape = startChord->shape().translate(
     349          216 :             startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos());
     350          108 :         double pointToClear = startChordShape.rightMostEdgeAtHeight(startPos.y() - vertMargin, startPos.y() + vertMargin);
     351          108 :         pointToClear += padding;
     352          108 :         double resultingX = std::max(startPos.x(), pointToClear);
     353          108 :         startPos.setX(resultingX);
     354          108 :     }
     355              : 
     356          108 :     if (item->isSingleEndType()) {
     357          216 :         Shape endChordShape = endChord->shape().translate(
     358          216 :             endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos());
     359          108 :         double pointToClear = endChordShape.leftMostEdgeAtHeight(endPos.y() - vertMargin, endPos.y() + vertMargin);
     360          108 :         pointToClear -= padding;
     361          108 :         double resultingX = std::min(endPos.x(), pointToClear);
     362          108 :         endPos.setX(resultingX);
     363          108 :     }
     364              : }
     365              : 
     366           52 : void GuitarBendLayout::layoutSlightBend(GuitarBendSegment* item, LayoutContext&)
     367              : {
     368           52 :     GuitarBend* bend = item->guitarBend();
     369           52 :     Note* startNote = bend->startNote();
     370           52 :     assert(startNote);
     371              : 
     372           52 :     Chord* startChord = startNote->chord();
     373           52 :     PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
     374              : 
     375           52 :     double spatium = item->spatium();
     376           52 :     PointF startPos = startNotePos + PointF(startNote->width() + 0.25 * spatium, -0.25 * spatium);
     377           52 :     PointF endPos(1.25 * spatium, -1.0 * spatium);
     378           52 :     PointF curve(endPos.x(), 0.0);
     379              : 
     380           52 :     item->setPos(startPos);
     381           52 :     item->setPos2(endPos);
     382           52 :     item->mutldata()->setVertexPoint(curve);
     383              : 
     384           52 :     item->mutldata()->setBbox(RectF(PointF(), item->pos2()).normalized());
     385              : 
     386           52 :     PainterPath path;
     387           52 :     path.cubicTo(PointF(0.0, 0.0), curve + item->vertexPointOff(), item->pos2());
     388           52 :     item->mutldata()->setPath(path);
     389           52 : }
     390              : 
     391           94 : void GuitarBendLayout::checkConflictWithOtherBends(GuitarBendSegment* item)
     392              : {
     393           94 :     GuitarBend* thisBend = item->guitarBend();
     394           94 :     Note* startNote = thisBend->startNote();
     395           94 :     Chord* startChord = startNote->chord();
     396              : 
     397           94 :     GuitarBend* otherBend = nullptr;
     398          194 :     for (Note* note : startChord->notes()) {
     399          103 :         if (note->string() >= startNote->string()) {
     400           97 :             continue;
     401              :         }
     402            6 :         GuitarBend* bendFor = note->bendFor();
     403            6 :         if (bendFor) {
     404            3 :             otherBend = bendFor;
     405            3 :             break;
     406              :         }
     407              :     }
     408              : 
     409           94 :     if (!otherBend) {
     410           91 :         return;
     411              :     }
     412              : 
     413            3 :     if (otherBend->ldata()->bendDigit() != thisBend->ldata()->bendDigit()) {
     414            1 :         if (thisBend->bendType() != GuitarBendType::PRE_BEND) {
     415            1 :             GuitarBendSegment::LayoutData* ldata = item->mutldata();
     416            1 :             double spatium = item->spatium();
     417            1 :             PointF endPointMove(spatium, spatium);
     418            1 :             item->setPos2(item->pos2() + endPointMove);
     419            1 :             item->mutldata()->arrow().translate(endPointMove);
     420            1 :             ldata->setVertexPoint(ldata->vertexPoint() + PointF(endPointMove.x(), 0.0));
     421              : 
     422            1 :             GuitarBendText* text = item->bendText();
     423            1 :             text->mutldata()->setPos(text->ldata()->pos() + endPointMove + PointF(0.35 * text->width(), 0.0));
     424              :         }
     425              :     }
     426              : }
     427              : 
     428          164 : bool GuitarBendLayout::startOnEndNote(GuitarBend* bend)
     429              : {
     430          164 :     GuitarBendType bendT = bend->bendType();
     431          164 :     if (!(bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::GRACE_NOTE_BEND || bendT == GuitarBendType::PRE_DIVE)) {
     432          118 :         return false;
     433              :     }
     434              : 
     435           46 :     bool preBendsAlignToGrace = bend->style().styleB(Sid::alignPreBendAndPreDiveToGraceNote);
     436           46 :     if (!preBendsAlignToGrace && (bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::PRE_DIVE)) {
     437           18 :         return true;
     438              :     }
     439              : 
     440           28 :     if (bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::GRACE_NOTE_BEND) {
     441           28 :         Chord* startChord = bend->startNote()->chord();
     442           28 :         size_t bendsCount = 0;
     443           56 :         for (Note* note : startChord->notes()) {
     444           28 :             if (note->bendFor()) {
     445           28 :                 ++bendsCount;
     446              :             }
     447              :         }
     448              : 
     449           28 :         if (bendsCount < bend->endNote()->chord()->notes().size()) {
     450            0 :             return true;
     451              :         }
     452              :     }
     453              : 
     454           28 :     return false;
     455              : }
     456              : 
     457           94 : void GuitarBendLayout::layoutTabStaff(GuitarBendSegment* item, LayoutContext& ctx)
     458              : {
     459           94 :     if (item->guitarBend()->isDive()) {
     460            0 :         GuitarDiveLayout::layoutDiveTabStaff(item, ctx);
     461              :     } else {
     462           94 :         layoutBendTabStaff(item, ctx);
     463              :     }
     464           94 : }
     465              : 
     466           94 : void GuitarBendLayout::layoutBendTabStaff(GuitarBendSegment* item, LayoutContext& ctx)
     467              : {
     468           94 :     GuitarBend* bend = item->guitarBend();
     469           94 :     GuitarBendSegment::LayoutData* ldata = item->mutldata();
     470              : 
     471           94 :     Note* startNote = bend->startNote();
     472           94 :     Note* endNote = bend->endNote();
     473           94 :     if (!startNote || !endNote) {
     474            0 :         return;
     475              :     }
     476              : 
     477           94 :     double spatium = bend->spatium();
     478           94 :     const MStyle& style = item->style();
     479           94 :     const double verticalPad = 0.25 * spatium;
     480           94 :     const double distAboveTab = style.styleD(Sid::guitarBendHeightAboveTABStaff) * spatium * item->staff()->lineDistance(item->tick());
     481           94 :     const double arrowWidth = style.styleAbsolute(Sid::guitarBendArrowWidth);
     482           94 :     const double arrowHeight = style.styleAbsolute(Sid::guitarBendArrowHeight);
     483           94 :     const double lineWidth = item->lineWidth();
     484              : 
     485           94 :     PointF startPos = PointF(0.0, 0.0);
     486           94 :     PointF endPos = PointF(0.0, 0.0);
     487           94 :     PointF vertex = PointF(0.0, 0.0);
     488           94 :     PolygonF arrow;
     489              : 
     490           94 :     GuitarBend* prevBend = bend->findPrecedingBend();
     491           29 :     GuitarBendSegment* prevBendSeg = prevBend && !prevBend->segmentsEmpty() && !prevBend->isFullRelease()
     492          123 :                                      ? toGuitarBendSegment(prevBend->backSegment()) : nullptr;
     493           29 :     GuitarBendHoldSegment* prevHoldLine = prevBend && prevBend->holdLine() && !prevBend->holdLine()->segmentsEmpty()
     494            6 :                                           && prevBend->endNote() != bend->startNote()
     495          123 :                                           ? toGuitarBendHoldSegment(prevBend->holdLine()->backSegment()) : nullptr;
     496              : 
     497           94 :     PointF prevEndPoint = PointF(0.0, 0.0);
     498           94 :     if (prevHoldLine) {
     499            6 :         prevEndPoint = prevHoldLine->pos2() + prevHoldLine->pos();
     500           88 :     } else if (prevBendSeg) {
     501           18 :         const PolygonF& prevArrow = prevBendSeg->ldata()->arrow();
     502           18 :         if (!prevArrow.empty()) {
     503           18 :             prevEndPoint = prevArrow.front() + prevBendSeg->pos();
     504           18 :             prevEndPoint += PointF(0.0, (prevBend->isReleaseBend() ? -0.5 : 0.5) * lineWidth);
     505              :         }
     506              :     }
     507              : 
     508           94 :     if (item->isSingleBeginType() && bend->bendType() != GuitarBendType::PRE_BEND && !prevEndPoint.isNull()) {
     509           24 :         startPos = prevEndPoint;
     510              :     } else {
     511           70 :         startPos = computeStartPos(item, startNote, distAboveTab, verticalPad, arrowHeight);
     512              :     }
     513              : 
     514           94 :     endPos = computeEndPos(item, endNote, distAboveTab, verticalPad, arrowHeight, arrowWidth, startPos, prevEndPoint);
     515           94 :     endPos.setX(std::max(startPos.x(), endPos.x())); // Edge case for ultra-ultra-narrow situations: ensure bend doesn't end before its start
     516              : 
     517          103 :     vertex = bend->bendType() == GuitarBendType::PRE_BEND && !bend->angledPreBend()
     518          103 :              ? 0.5 * (startPos + endPos) : PointF(endPos.x(), startPos.y());
     519              : 
     520           94 :     arrow = bend->isReleaseBend()
     521           16 :             ? arrow << PointF(0, arrowHeight) << PointF(arrowWidth * .5, 0.0) << PointF(-arrowWidth * .5, 0.0)   // arrow-down
     522          110 :             : arrow << PointF(0, -arrowHeight) << PointF(arrowWidth * .5, 0.0) << PointF(-arrowWidth * .5, 0.0); // arrow-up
     523              : 
     524           94 :     if (bend->isReleaseBend() && !endNote->ghost()) {
     525            0 :         endNote->setGhost(true);
     526            0 :         endNote->mutldata()->reset();
     527            0 :         TLayout::layoutChord(endNote->chord(), ctx);
     528              :     }
     529              : 
     530           94 :     if (startOnEndNote(bend)) {
     531            9 :         if (!startNote->overrideBendVisibilityRules()) {
     532            9 :             startNote->setVisible(false);
     533              :         }
     534            9 :         if (!endNote->overrideBendVisibilityRules()) {
     535            9 :             endNote->setVisible(true);
     536              :         }
     537           85 :     } else if (bend->bendType() != GuitarBendType::SLIGHT_BEND
     538           85 :                && (!style.styleB(Sid::showFretOnFullBendRelease) || !bend->isFullRelease())) {
     539           62 :         if (bend->bendType() == GuitarBendType::GRACE_NOTE_BEND || bend->bendType() == GuitarBendType::PRE_BEND) {
     540           15 :             if (!startNote->overrideBendVisibilityRules()) {
     541           15 :                 startNote->setVisible(true);
     542              :             }
     543              :         }
     544           62 :         if (!endNote->overrideBendVisibilityRules()) {
     545           62 :             endNote->setVisible(false);
     546              :         }
     547              :     }
     548              : 
     549           94 :     item->setPos(startPos);
     550           94 :     item->setPos2(endPos - startPos);
     551           94 :     ldata->setVertexPoint(vertex - startPos);
     552              : 
     553           94 :     arrow.translate(item->pos2());
     554           94 :     item->mutldata()->setArrow(arrow);
     555              : 
     556           94 :     GuitarBendText* guitarBendText = item->bendText();
     557           94 :     guitarBendText->setXmlText(bend->ldata()->bendDigit());
     558           94 :     TextLayout::layoutBaseTextBase(toTextBase(guitarBendText), ctx);
     559           94 :     double verticalTextPad = 0.2 * spatium;
     560           94 :     PointF centering(-0.5 * guitarBendText->width(),
     561          188 :                      (bend->isReleaseBend() ? verticalTextPad : -(guitarBendText->height() + verticalTextPad)));
     562           94 :     guitarBendText->mutldata()->setPos(arrow.front() + centering);
     563              : 
     564           94 :     GuitarBendLayout::checkConflictWithOtherBends(item);
     565              : 
     566           94 :     PainterPath path;
     567           94 :     if (bend->angledPreBend()) {
     568            0 :         path.lineTo(ldata->vertexPoint() + item->vertexPointOff());
     569            0 :         path.lineTo(item->pos2());
     570              :     } else {
     571           94 :         path.cubicTo(PointF(0.0, 0.0), ldata->vertexPoint() + item->vertexPointOff(), item->pos2());
     572              :     }
     573           94 :     ldata->setPath(path);
     574              : 
     575           94 :     RectF r;
     576           94 :     if (muse::RealIsNull(item->pos2().x())) {
     577           13 :         r = RectF(-0.5 * lineWidth, 0.0, lineWidth, arrow.front().y());
     578              :     } else {
     579           81 :         r = RectF(PointF(), PointF(arrow[1].x(), arrow[0].y()));
     580              :     }
     581           94 :     item->mutldata()->setBbox(r.normalized());
     582           94 : }
     583              : 
     584           70 : PointF GuitarBendLayout::computeStartPos(GuitarBendSegment* item, Note* startNote, double distAboveTab, double verticalPad,
     585              :                                          double arrowHeight)
     586              : {
     587           70 :     GuitarBend* bend = item->guitarBend();
     588           70 :     Chord* startChord = startNote->chord();
     589           70 :     PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
     590              : 
     591           70 :     const double spatium = item->spatium();
     592           70 :     const double lineWidth = item->lineWidth();
     593           70 :     const double horizontalIndent = 0.25 * spatium;
     594           70 :     System* system = item->system();
     595              : 
     596           70 :     PointF startPos;
     597              : 
     598           70 :     if (startOnEndNote(bend)) {
     599            9 :         Note* endN = bend->endNote();
     600            9 :         startPos = endN->systemPos();
     601            9 :         Chord* endChord = endN->chord();
     602            9 :         bool startFromTheSide = bend->bendType() == GuitarBendType::GRACE_NOTE_BEND
     603            9 :                                 || (endChord->notes().size() > 1 && endN->string() > endChord->upString());
     604            9 :         if (startFromTheSide) {
     605            0 :             startPos += PointF(endN->width() + horizontalIndent, -0.5 * endN->height() + 0.5 * lineWidth);
     606              :         } else {
     607            9 :             startPos += PointF(0.5 * endN->width(), -0.5 * endN->height() - verticalPad);
     608              :         }
     609            9 :         return startPos;
     610              :     }
     611              : 
     612           61 :     if (bend->bendType() == GuitarBendType::PRE_BEND) {
     613            0 :         startPos = startNote->systemPos();
     614            0 :         startPos += PointF(0.5 * startNote->width(), -0.5 * startNote->height() - verticalPad);
     615            0 :         return startPos;
     616              :     }
     617              : 
     618           61 :     if (bend->isReleaseBend()) {
     619            1 :         if (item->isSingleBeginType()) {
     620            1 :             startPos.setX(startNotePos.x());
     621            1 :             startPos.setY(-distAboveTab - arrowHeight + 0.5 * lineWidth);
     622              :         } else {
     623            0 :             startPos.setX(system->firstNoteRestSegmentX(true));
     624            0 :             startPos.setY(-distAboveTab);
     625              :         }
     626            1 :         return startPos;
     627              :     }
     628              : 
     629           60 :     if (item->isSingleBeginType()) {
     630           60 :         startPos = startNotePos;
     631           60 :         startPos += PointF(startNote->width() + horizontalIndent, -0.5 * startNote->height() + 0.5 * lineWidth);
     632              :     } else {
     633            0 :         startPos.setX(system->firstNoteRestSegmentX(true));
     634            0 :         startPos.setY(0.5 * spatium);
     635              :     }
     636              : 
     637           60 :     return startPos;
     638              : }
     639              : 
     640           94 : PointF GuitarBendLayout::computeEndPos(GuitarBendSegment* item, Note* endNote, double distAboveTab, double verticalPad, double arrowHeight,
     641              :                                        double arrowWidth, const PointF& startPos, const PointF& prevEndPoint)
     642              : {
     643           94 :     GuitarBend* bend = item->guitarBend();
     644           94 :     Chord* endChord = endNote->chord();
     645           94 :     PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
     646              : 
     647           94 :     const MStyle& style = item->style();
     648           94 :     const double partialBendHeight = style.styleAbsolute(Sid::guitarBendPartialBendHeight);
     649           94 :     const double spatium = item->spatium();
     650           94 :     System* system = item->system();
     651              : 
     652           94 :     PointF endPos;
     653              : 
     654           94 :     if (bend->bendType() == GuitarBendType::PRE_BEND && !bend->angledPreBend()) {
     655            9 :         endPos = PointF(startPos.x(), -distAboveTab);        // TODO: style
     656            9 :         return endPos;
     657              :     }
     658              : 
     659           85 :     if (bend->isReleaseBend()) {
     660           16 :         if (item->isSingleEndType()) {
     661           16 :             endPos = endNotePos;
     662           16 :             endPos += PointF(0.5 * endNote->width(), 0.0);
     663           16 :             if (bend->isFullRelease()) {
     664            7 :                 endPos.setY(endPos.y() - 0.5 * endNote->height() - arrowHeight - verticalPad);
     665              :             } else {
     666            9 :                 endPos.setY(startPos.y() + partialBendHeight);
     667              :             }
     668              :         } else {
     669            0 :             endPos.setX(system->endingXForOpenEndedLines() - 0.5 * arrowWidth);
     670            0 :             endPos.setY(-arrowHeight);
     671              :         }
     672           16 :         return endPos;
     673              :     }
     674              : 
     675           69 :     if (bend->bendType() == GuitarBendType::SLIGHT_BEND) {
     676           16 :         const double slightBendWidth = 1.25 * spatium;
     677           16 :         endPos = startPos + PointF(slightBendWidth, 0.0);
     678              :     } else {
     679           53 :         if (item->isSingleEndType()) {
     680           53 :             endPos = endNotePos;
     681           53 :             endPos += PointF(0.5 * endNote->width(), 0.0);
     682              :         } else {
     683            0 :             endPos.setX(system->endingXForOpenEndedLines() - 0.5 * arrowWidth);
     684              :         }
     685              :     }
     686           69 :     endPos = PointF(endPos.x(), prevEndPoint.isNull() ? -distAboveTab : startPos.y() - partialBendHeight);             // TODO: style
     687           69 :     endPos.setX(std::max(endPos.x(), startPos.x() + 0.75 * spatium));
     688              : 
     689           69 :     return endPos;
     690              : }
     691              : 
     692            6 : void GuitarBendLayout::layoutHoldLine(GuitarBendHoldSegment* item)
     693              : {
     694            6 :     if (!item->system()) {
     695            0 :         return;
     696              :     }
     697              : 
     698            6 :     GuitarBend* startBend = item->guitarBendHold()->guitarBend();
     699            6 :     if (!startBend || startBend->segmentsEmpty()) {
     700            0 :         return;
     701              :     }
     702              : 
     703            6 :     const double spatium = item->spatium();
     704            6 :     const double lineWidth = item->lineWidth();
     705              : 
     706            6 :     PointF startPos = PointF();
     707            6 :     PointF endPos = PointF();
     708              : 
     709            6 :     Note* endNote = item->guitarBendHold()->endNote();
     710              : 
     711            6 :     Chord* endChord = endNote->chord();
     712            6 :     PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
     713              : 
     714            6 :     GuitarBendSegment* startBendSegment = nullptr;
     715            6 :     if (!startBend->isFullRelease() && !startBend->segmentsEmpty()) {
     716            6 :         startBendSegment = toGuitarBendSegment(startBend->backSegment());
     717              :     } else {
     718            0 :         return;
     719              :     }
     720            6 :     item->setStartBendSeg(startBendSegment);
     721              : 
     722            6 :     GuitarBendSegment* endBendSegment = nullptr;
     723            6 :     if (item->isSingleEndType()) {
     724            6 :         GuitarBend* bend = endNote->bendFor();
     725            6 :         if (bend && bend != startBend && !bend->segmentsEmpty()) {
     726            4 :             endBendSegment = toGuitarBendSegment(bend->frontSegment());
     727              :         }
     728              :     }
     729            6 :     item->setEndBendSeg(endBendSegment);
     730              : 
     731            6 :     if (item->isSingleBeginType()) {
     732            6 :         startPos = startBendSegment->pos() + (startBend->isDive() ? startBendSegment->pos2() : startBendSegment->ldata()->arrow().front());
     733            6 :         if (!startBend->isDive()) {
     734            6 :             double gap = item->dashLength() * lineWidth;
     735            6 :             startPos += PointF(gap, (startBend->isReleaseBend() ? -0.5 : +0.5) * lineWidth);
     736              :         }
     737              :     } else {
     738            0 :         startPos.setX(item->system()->firstNoteRestSegmentX(true));
     739            0 :         startPos.setY(item->guitarBendHold()->frontSegment()->pos().y());
     740              :     }
     741              : 
     742            6 :     if (item->isSingleEndType()) {
     743            6 :         if (endBendSegment) {
     744            4 :             GuitarBend* endBend = endBendSegment->guitarBend();
     745            4 :             if (endBend->bendType() == GuitarBendType::PRE_DIVE || endBend->bendType() == GuitarBendType::PRE_BEND) {
     746            0 :                 Note* note = startOnEndNote(endBend) ? endBend->endNote() : endBend->startNote();
     747            0 :                 endPos.setX(note->systemPos().x() + 0.5 * note->ldata()->bbox().width());
     748              :             } else {
     749            4 :                 endPos.setX(endNotePos.x());
     750              :             }
     751              :         } else {
     752            2 :             endPos.setX(endNotePos.x() + endNote->chord()->segment()->width() - 1.5 * spatium);
     753              :         }
     754              :     } else {
     755            0 :         endPos.setX(item->system()->endingXForOpenEndedLines());
     756              :     }
     757              : 
     758            6 :     const double minLen = spatium;
     759            6 :     endPos.setX(std::max(startPos.x() + minLen, endPos.x()));
     760              : 
     761            6 :     endPos.setY(startPos.y());
     762              : 
     763            6 :     item->setPos(startPos);
     764            6 :     item->setPos2(endPos - startPos);
     765              : 
     766            6 :     if (startBend->dipVibratoType() != VibratoType::NONE) {
     767            0 :         SymId symId = SymId::noSym;
     768            0 :         switch (startBend->dipVibratoType()) {
     769            0 :         case VibratoType::GUITAR_VIBRATO:
     770            0 :             symId = SymId::guitarVibratoStroke;
     771            0 :             break;
     772            0 :         case VibratoType::GUITAR_VIBRATO_WIDE:
     773            0 :             symId = SymId::guitarWideVibratoStroke;
     774            0 :             break;
     775            0 :         default:
     776            0 :             symId = SymId::noSym;
     777              :         }
     778            0 :         double lineLength = item->pos2().x();
     779            0 :         double symAdvance = item->symAdvance(symId);
     780            0 :         int symsCount = std::floor(lineLength / symAdvance);
     781            0 :         symsCount = std::max(symsCount, 1);
     782            0 :         item->mutldata()->setSymIds(SymIdList(symsCount, symId));
     783            0 :         item->mutldata()->moveY(0.5 * item->symHeight(symId));
     784            0 :         RectF bbox = item->symBbox(symId);
     785            0 :         for (size_t i = 1; i < item->ldata()->symIds().size(); ++i) {
     786            0 :             bbox.unite(bbox.translated(PointF(symAdvance, 0.0)));
     787              :         }
     788            0 :         item->mutldata()->setBbox(bbox);
     789            0 :         return;
     790              :     }
     791              : 
     792            6 :     RectF r(0.0, -0.5 * lineWidth, item->pos2().x(), lineWidth);
     793            6 :     item->mutldata()->setBbox(r);
     794              : }
        

Generated by: LCOV version 2.0-1