LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - stemlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 91.4 % 267 244
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 "stemlayout.h"
      24              : #include "dom/chord.h"
      25              : #include "dom/hook.h"
      26              : #include "dom/note.h"
      27              : #include "dom/rest.h"
      28              : #include "dom/score.h"
      29              : #include "dom/staff.h"
      30              : #include "dom/tremolosinglechord.h"
      31              : #include "dom/tremolotwochord.h"
      32              : 
      33              : using namespace mu::engraving;
      34              : using namespace mu::engraving::rendering::score;
      35              : 
      36              : //-----------------------------------------------------------------------------
      37              : //   defaultStemLength
      38              : ///   Get the default stem length for this chord
      39              : ///   all internal calculation is done in quarter spaces
      40              : ///   using integers to eliminate all possibilities for rounding errors
      41              : //-----------------------------------------------------------------------------
      42       144130 : double StemLayout::calcDefaultStemLength(Chord* item, const LayoutContext& ctx)
      43              : {
      44              :     // returns default length even if the chord doesn't have a stem
      45       144130 :     const MStyle& style = ctx.conf().style();
      46       144130 :     const Chord::LayoutData* ldata = item->ldata();
      47       144130 :     const Staff* staff = item->staff();
      48              : 
      49       144130 :     double spatium = item->spatium();
      50       144130 :     double lineDistance = (staff ? staff->lineDistance(item->tick()) : 1.0);
      51              : 
      52       144130 :     const StaffType* staffType = staff ? staff->staffTypeForElement(item) : nullptr;
      53       144130 :     const StaffType* tab = (staffType && staffType->isTabStaff()) ? staffType : nullptr;
      54              : 
      55       144130 :     bool isBesideTabStaff = tab && !tab->stemless() && !tab->stemThrough();
      56       144130 :     if (isBesideTabStaff) {
      57          420 :         return tabStemLength(item, tab) * spatium;
      58              :     }
      59              : 
      60       143710 :     int defaultStemLength = style.styleD(Sid::stemLength) * 4;
      61       143710 :     defaultStemLength += stemLengthBeamAddition(item, ctx);
      62       143710 :     if (tab) {
      63          656 :         defaultStemLength *= 1.5;
      64              :     }
      65              :     // extraHeight represents the extra vertical distance between notehead and stem start
      66              :     // eg. slashed noteheads etc
      67       143710 :     double extraHeight = (ldata->up ? item->upNote()->stemUpSE().y() : item->downNote()->stemDownNW().y()) / item->intrinsicMag()
      68       143710 :                          / spatium;
      69       143710 :     int shortestStem = style.styleB(Sid::useWideBeams) ? 12 : (style.styleD(Sid::shortestStem) + std::abs(extraHeight)) * 4;
      70       143710 :     int quarterSpacesPerLine = std::floor(lineDistance * 2);
      71       143710 :     int chordHeight = (item->downLine() - item->upLine()) * quarterSpacesPerLine; // convert to quarter spaces
      72       143710 :     int stemLength = defaultStemLength;
      73              : 
      74       143710 :     int minStemLengthQuarterSpaces = calcMinStemLength(item, ctx);
      75              : 
      76       143710 :     int staffLineCount = staff ? staff->lines(item->tick()) : 5;
      77       143710 :     int shortStemStart = style.styleI(Sid::shortStemStartLocation) * quarterSpacesPerLine + 1;
      78       143710 :     bool useWideBeams = style.styleB(Sid::useWideBeams);
      79       143710 :     int beamCount = calcBeamCount(item);
      80       287420 :     int middleLine = minStaffOverlap(ldata->up, staffLineCount,
      81       143710 :                                      beamCount, !!item->hook(), useWideBeams ? 4 : 3,
      82       143710 :                                      useWideBeams, !(item->isGrace() || item->isSmall()));
      83       143710 :     int idealStemLength = defaultStemLength;
      84              : 
      85       143710 :     if (ldata->up) {
      86        75683 :         int stemEndPosition = item->upLine() * quarterSpacesPerLine - defaultStemLength;
      87        75683 :         double stemEndPositionMag = (double)item->upLine() * quarterSpacesPerLine - (defaultStemLength * item->intrinsicMag());
      88              : 
      89        75683 :         if (stemEndPositionMag <= -shortStemStart) {
      90        32377 :             int reduction = maxReduction(item, ctx, std::abs((int)floor(stemEndPositionMag) + shortStemStart));
      91        32377 :             idealStemLength = std::max(idealStemLength - reduction, shortestStem);
      92        43306 :         } else if (stemEndPosition > middleLine) {
      93              :             // this case will be taken care of below; even if we were to adjust here we'd have
      94              :             // to adjust again later if the line spacing != 1.0 or if _relativeMag != 1.0
      95              :         } else {
      96        40636 :             idealStemLength -= stemOpticalAdjustment(item, stemEndPosition);
      97        40636 :             idealStemLength = std::max(idealStemLength, shortestStem);
      98              :         }
      99        75683 :         stemLength = std::max(idealStemLength, minStemLengthQuarterSpaces);
     100              :     } else {
     101        68027 :         int stemEndPosition = item->downLine() * quarterSpacesPerLine + defaultStemLength;
     102        68027 :         double stemEndPositionMag = (double)item->downLine() * quarterSpacesPerLine + (defaultStemLength * item->intrinsicMag());
     103              : 
     104        68027 :         int downShortStemStart = (staffLineCount - 1) * (2 * quarterSpacesPerLine) + shortStemStart;
     105        68027 :         if (stemEndPositionMag >= downShortStemStart) {
     106        46365 :             int reduction = maxReduction(item, ctx, std::abs((int)ceil(stemEndPositionMag) - downShortStemStart));
     107        46365 :             idealStemLength = std::max(idealStemLength - reduction, shortestStem);
     108        21662 :         } else if (stemEndPosition < middleLine) {
     109              :             // this case will be taken care of below; even if we were to adjust here we'd have
     110              :             // to adjust again later if the line spacing != 1.0 or if _relativeMag != 1.0
     111              :         } else {
     112        19688 :             idealStemLength -= stemOpticalAdjustment(item, stemEndPosition);
     113        19688 :             idealStemLength = std::max(idealStemLength, shortestStem);
     114              :         }
     115              : 
     116        68027 :         stemLength = std::max(idealStemLength, minStemLengthQuarterSpaces);
     117              :     }
     118       143710 :     if (beamCount == 4 && !item->hook()) {
     119         1377 :         stemLength = calc4BeamsException(item, stemLength);
     120              :     }
     121              : 
     122       143710 :     double finalStemLength = (chordHeight / 4.0 * spatium) + ((stemLength / 4.0 * spatium) * item->intrinsicMag());
     123       143710 :     double extraLength = 0.;
     124       143710 :     Note* startNote = ldata->up ? item->downNote() : item->upNote();
     125       143710 :     if (!startNote->fixed()) {
     126              :         // when the chord's magnitude is < 1, the stem length with mag can find itself below the middle line.
     127              :         // in those cases, we have to add the extra amount to it to bring it to a minimum.
     128       143386 :         double upValue = ldata->up ? -1. : 1.;
     129       143386 :         double stemStart = startNote->ldata()->pos().y();
     130       143386 :         double stemEndMag = stemStart + (finalStemLength * upValue);
     131       143386 :         double topLine = 0.0;
     132       143386 :         lineDistance *= spatium;
     133       143386 :         double bottomLine = lineDistance * (staffLineCount - 1.0);
     134       143386 :         double target = 0.0;
     135       143386 :         double midLine = middleLine / 4.0 * lineDistance;
     136       143386 :         if (muse::RealIsEqualOrMore(lineDistance / spatium, 1.0)) {
     137              :             // need to extend to middle line, or to opposite line if staff is < 2sp tall
     138       143386 :             if (bottomLine < 2 * spatium) {
     139          322 :                 target = ldata->up ? topLine : bottomLine;
     140              :             } else {
     141       143064 :                 double twoSpIn = ldata->up ? bottomLine - (2 * spatium) : topLine + (2 * spatium);
     142       143064 :                 target = muse::RealIsEqual(lineDistance / spatium, 1.0) ? midLine : twoSpIn;
     143              :             }
     144              :         } else {
     145              :             // need to extend to second line in staff, or to opposite line if staff has < 3 lines
     146            0 :             if (staffLineCount < 3) {
     147            0 :                 target = ldata->up ? topLine : bottomLine;
     148              :             } else {
     149            0 :                 target = ldata->up ? bottomLine - (2 * lineDistance) : topLine + (2 * lineDistance);
     150              :             }
     151              :         }
     152       143386 :         extraLength = 0.0;
     153       143386 :         if (ldata->up && stemEndMag > target) {
     154         2849 :             extraLength = stemEndMag - target;
     155       140537 :         } else if (!ldata->up && stemEndMag < target) {
     156         1889 :             extraLength = target - stemEndMag;
     157              :         }
     158              :     }
     159       143710 :     return finalStemLength + extraLength;
     160              : }
     161              : 
     162       161718 : int StemLayout::minStaffOverlap(bool up, int staffLines, int beamCount, bool hasHook, double beamSpacing, bool useWideBeams,
     163              :                                 bool isFullSize)
     164              : {
     165       161718 :     int beamOverlap = 8;
     166       161718 :     if (isFullSize) {
     167       157079 :         if (beamCount == 3 && !hasHook) {
     168         6738 :             beamOverlap = 12;
     169       150341 :         } else if (beamCount >= 4 && !hasHook) {
     170         1819 :             beamOverlap = (beamCount - 4) * beamSpacing + (useWideBeams ? 16 : 14);
     171              :         }
     172              :     }
     173              : 
     174       161718 :     int staffLineOffset = isFullSize ? 1 : 4;
     175       161718 :     int staffOverlap = std::min(beamOverlap, (staffLines - staffLineOffset) * 4);
     176       161718 :     if (!up) {
     177        77321 :         return staffOverlap;
     178              :     }
     179        84397 :     return (staffLines - 1) * 4 - staffOverlap;
     180              : }
     181              : 
     182       132057 : double StemLayout::stemPosX(const ChordRest* item)
     183              : {
     184       132057 :     return item->isChord() ? stemPosX(toChord(item)) : stemPosX(toRest(item));
     185              : }
     186              : 
     187       318597 : double StemLayout::stemPosX(const Chord* item)
     188              : {
     189       318597 :     const StaffType* staffType = item->staffType();
     190       318597 :     if (!staffType || !staffType->isTabStaff()) {
     191       318184 :         bool up = item->ldata()->up;
     192       318184 :         const Note* refNote = up ? item->upNote() : item->downNote();
     193       318184 :         PointF stemAttach = up ? refNote->stemUpSE() : refNote->stemDownNW();
     194       318184 :         if (stemAttach.isNull()) {
     195         2196 :             return up ? item->noteHeadWidth() : 0.0;
     196              :         }
     197       315988 :         double noteWidthOffset = up ? (refNote->headBodyWidth() - item->noteHeadWidth()) : 0.0;
     198       315988 :         return stemAttach.x() - noteWidthOffset;
     199              :     }
     200              : 
     201          413 :     const Note* refNote = item->ldata()->up ? item->upNote() : item->downNote();
     202          413 :     double xPos = refNote->pos().x() + 0.5 * refNote->width();
     203          413 :     if (item->isGraceBendEnd()) {
     204            4 :         GraceNotesGroup& graceBefore = item->graceNotesBefore();
     205            4 :         Chord* grace = graceBefore.empty() ? nullptr : graceBefore.front();
     206            4 :         if (grace) {
     207            4 :             xPos += grace->pos().x();
     208              :         }
     209              :     }
     210          413 :     return xPos;
     211              : }
     212              : 
     213            0 : double StemLayout::stemPosX(const Rest* item)
     214              : {
     215            0 :     if (item->ldata()->up) {
     216            0 :         return item->ldata()->bbox().right();
     217              :     }
     218              : 
     219            0 :     return item->ldata()->bbox().left();
     220              : }
     221              : 
     222            0 : PointF StemLayout::stemPos(const ChordRest* item)
     223              : {
     224            0 :     return item->isChord() ? stemPos(toChord(item)) : stemPos(toRest(item));
     225              : }
     226              : 
     227          265 : PointF StemLayout::stemPos(const Chord* item)
     228              : {
     229          265 :     const Staff* staff = item->staff();
     230          265 :     const StaffType* staffType = staff ? staff->staffTypeForElement(item) : nullptr;
     231          265 :     if (staffType && staffType->isTabStaff()) {
     232            0 :         return item->pagePos() + rendering::score::StemLayout::tabStemPos(item, staffType) * item->spatium();
     233              :     }
     234              : 
     235          265 :     if (item->ldata()->up) {
     236          123 :         const Note* downNote = item->downNote();
     237          123 :         double nhw = item->notes().size() == 1 ? downNote->bboxRightPos() : item->noteHeadWidth();
     238          123 :         return item->pagePos() + PointF(nhw, downNote->pos().y());
     239              :     }
     240              : 
     241          142 :     return item->pagePos() + PointF(0.0, item->upNote()->pos().y());
     242              : }
     243              : 
     244            0 : PointF StemLayout::stemPos(const Rest* item)
     245              : {
     246            0 :     return item->pagePos();
     247              : }
     248              : 
     249              : //---------------------------------------------------------
     250              : //   tabRestStemPosY / chordStemPos / chordStemPosBeam / chordStemLength
     251              : //
     252              : //    computes the stem data for the given chord, according to TAB settings
     253              : //    NOTE: unit: spatium, position: relative to chord (DIFFERENT from Chord own functions)
     254              : //
     255              : //   tabRestStemPosY
     256              : //          returns the vertical position of stem start point
     257              : //---------------------------------------------------------
     258              : 
     259           95 : double StemLayout::tabRestStemPosY(const ChordRest* item, const StaffType* st)
     260              : {
     261           95 :     if (st->stemThrough()) {            // does not make sense for "stems through staves" setting; just return top line vert. position
     262            0 :         return 0.0;
     263              :     }
     264              : 
     265              :     // if stems beside staff, position are fixed, but take into account delta for half notes
     266              :     double delta                                 // displacement for half note stems (if used)
     267              :         =   // if half notes have not a short stem OR not a half note => 0
     268          170 :           (st->minimStyle() != TablatureMinimStyle::SHORTER || item->durationType().type() != DurationType::V_HALF)
     269          170 :           ? 0.0
     270              :           :           // if stem is up, displace of half stem length down (positive)
     271              :           // if stem is down, displace of half stem length up (negative)
     272           10 :           (item->up()
     273           10 :            ? -STAFFTYPE_TAB_DEFAULTSTEMLEN_UP : STAFFTYPE_TAB_DEFAULTSTEMLEN_DN) * 0.5;
     274              :     // if fret marks above lines and chordRest is up, move half a line distance up
     275           95 :     if (!st->onLines() && item->up()) {
     276            0 :         delta -= st->lineDistance().val() * 0.5;
     277              :     }
     278              :     double y
     279           95 :         = (item->up() ? STAFFTYPE_TAB_DEFAULTSTEMPOSY_UP : (st->lines() - 1) * st->lineDistance().val() + STAFFTYPE_TAB_DEFAULTSTEMPOSY_DN)
     280           95 :           + delta;
     281           95 :     return y;
     282              : }
     283              : 
     284              : //---------------------------------------------------------
     285              : //   tabStemLength
     286              : //          return length of stem
     287              : //---------------------------------------------------------
     288              : 
     289          420 : double StemLayout::tabStemLength(const Chord* item, const StaffType* st)
     290              : {
     291              :     double stemLen;
     292              :     // if stems are through staff, length should be computed by relevant chord algorithm;
     293              :     // here, just return default length (= 1 'octave' = 3.5 line spaces)
     294          420 :     if (st->stemThrough()) {
     295            0 :         return STAFFTYPE_TAB_DEFAULTSTEMLEN_THRU * st->lineDistance().val();
     296              :     }
     297              :     // if stems beside staff, length is fixed, but take into account shorter half note stems
     298              :     else {
     299          420 :         bool shrt = (st->minimStyle() == TablatureMinimStyle::SHORTER) && (item->durationType().type() == DurationType::V_HALF);
     300          420 :         stemLen = (st->stemsDown() ? STAFFTYPE_TAB_DEFAULTSTEMLEN_DN : STAFFTYPE_TAB_DEFAULTSTEMLEN_UP)
     301          420 :                   * (shrt ? STAFFTYPE_TAB_SHORTSTEMRATIO : 1.0) * (item->style().styleB(Sid::useWideBeams) ? 1.25 : 1.0);
     302              :     }
     303              :     // scale length by scale of parent chord, but relative to scale of context staff
     304          420 :     return stemLen * item->mag() / item->staff()->staffMag(item->tick());
     305              : }
     306              : 
     307              : //---------------------------------------------------------
     308              : //   tabStemPos
     309              : //    return position of note at other side of beam
     310              : //---------------------------------------------------------
     311              : 
     312          115 : PointF StemLayout::tabStemPos(const Chord* item, const StaffType* st)
     313              : {
     314          115 :     double y = 0.0;
     315          115 :     if (st->stemThrough()) {
     316              :         // if stems are through staff, stem goes from farthest note string
     317           20 :         y = (item->up() ? item->downString() : item->upString()) * st->lineDistance().val();
     318              :     } else {
     319              :         // if stems are beside staff, stem start point has a fixed vertical position,
     320              :         // according to TAB parameters and stem up/down
     321           95 :         y = tabRestStemPosY(item, st);
     322              :     }
     323          115 :     return PointF(0.5 * item->score()->noteHeadWidth() * item->mag() / item->spatium(), y);
     324              : }
     325              : 
     326       143710 : int StemLayout::stemLengthBeamAddition(const Chord* item, const LayoutContext& ctx)
     327              : {
     328       143710 :     if (item->hook()) {
     329         3489 :         return 0;
     330              :     }
     331       140221 :     const MStyle& style = ctx.conf().style();
     332       140221 :     int beamCount = calcBeamCount(item);
     333       140221 :     switch (beamCount) {
     334       132616 :     case 0:
     335              :     case 1:
     336              :     case 2:
     337       132616 :         return 0;
     338         6054 :     case 3:
     339         6054 :         return 2;
     340         1551 :     default:
     341         1551 :         return (beamCount - 3) * (style.styleB(Sid::useWideBeams) ? 4 : 3);
     342              :     }
     343              : }
     344              : 
     345        78742 : int StemLayout::maxReduction(const Chord* item, const LayoutContext& ctx, int extensionOutsideStaff)
     346              : {
     347        78742 :     const MStyle& style = ctx.conf().style();
     348        78742 :     if (!style.styleB(Sid::shortenStem)) {
     349            0 :         return 0;
     350              :     }
     351              :     // [extensionOutsideStaff][beamCount]
     352              :     static const int maxReductions[4][5] = {
     353              :         //1sp 1.5sp 2sp 2.5sp >=3sp -- extensionOutsideStaff
     354              :         { 1, 2, 3, 4, 4 }, // 0 beams
     355              :         { 0, 1, 2, 3, 3 }, // 1 beam
     356              :         { 0, 1, 1, 1, 1 }, // 2 beams
     357              :         { 0, 0, 0, 1, 1 }, // 3 beams
     358              :     };
     359        78742 :     int beamCount = 0;
     360        78742 :     if (!item->hook()) {
     361        76412 :         beamCount = calcBeamCount(item);
     362              :     }
     363        78742 :     bool hasTradHook = item->hook() && !style.styleB(Sid::useStraightNoteFlags);
     364        78742 :     if (item->hook() && !hasTradHook) {
     365            0 :         beamCount = std::min(beamCount, 2); // the straight glyphs extend outwards after 2 beams
     366              :     }
     367        78742 :     if (beamCount >= 4) {
     368         1102 :         return 0;
     369              :     }
     370        77640 :     int extensionHalfSpaces = floor(extensionOutsideStaff / 2.0);
     371        77640 :     extensionHalfSpaces = std::min(extensionHalfSpaces, 4);
     372        77640 :     int reduction = maxReductions[beamCount][extensionHalfSpaces];
     373        77640 :     if (item->intrinsicMag() < 1) {
     374              :         // there is an exception for grace-sized stems with hooks.
     375              :         // reducing by the full amount puts the hooks too low. Limit reduction to 0.5sp
     376         1995 :         if (hasTradHook) {
     377          146 :             reduction = std::min(reduction, 1);
     378              :         }
     379              :     } else {
     380              :         // there are a few exceptions for normal-sized (non-grace) beams
     381        75645 :         if (beamCount == 1 && extensionHalfSpaces < 2) {
     382              :             // 1) if the extension is less than 1sp above or below the staff, they've been adjusted
     383              :             //    already to play nicely with staff lines. Reduce by 1sp.
     384        16341 :             reduction = 2;
     385        59304 :         } else if (beamCount == 3 && extensionHalfSpaces == 3) {
     386              :             // 2) if there are three beams and it extends 1.5sp above or below the staff, we need to
     387              :             //    *extend* the stem rather than reduce it.
     388          429 :             reduction = 0;
     389              :         }
     390        75645 :         if (hasTradHook) {
     391         2184 :             reduction = std::min(reduction, 1);
     392        73461 :         } else if (item->hook() && item->beams() > 2) {
     393            0 :             reduction += 1;
     394              :         }
     395              :     }
     396        77640 :     return reduction;
     397              : }
     398              : 
     399        60324 : int StemLayout::stemOpticalAdjustment(const Chord* item, int stemEndPosition)
     400              : {
     401        60324 :     if (item->hook() && !item->beam()) {
     402         1126 :         return 0;
     403              :     }
     404        59198 :     int beamCount = calcBeamCount(item);
     405        59198 :     if (beamCount == 0 || beamCount > 2) {
     406        35805 :         return 0;
     407              :     }
     408        23393 :     bool isOnEvenLine = fmod(stemEndPosition + 4, 4) == 2;
     409        23393 :     if (isOnEvenLine) {
     410        14332 :         return 1;
     411              :     }
     412         9061 :     return 0;
     413              : }
     414              : 
     415       143710 : int StemLayout::calcMinStemLength(Chord* item, const LayoutContext& ctx)
     416              : {
     417       143710 :     int minStemLength = 0; // in quarter spaces
     418       143710 :     double spatium = item->spatium();
     419       143710 :     const MStyle& style = ctx.conf().style();
     420       143710 :     const Chord::LayoutData* ldata = item->ldata();
     421       143710 :     const Staff* staff = item->staff();
     422              : 
     423       143710 :     if (item->tremoloSingleChord()) {
     424              :         // buzz roll's height is actually half of the visual height,
     425              :         // so we need to multiply it by 2 to get the actual height
     426          751 :         int buzzRollMultiplier = item->tremoloSingleChord()->isBuzzRoll() ? 2 : 1;
     427          751 :         minStemLength += ceil(item->tremoloSingleChord()->minHeight() / item->intrinsicMag() * 4.0 * buzzRollMultiplier);
     428          751 :         int outSidePadding = style.styleAbsolute(Sid::tremoloOutSidePadding) / spatium * 4.0;
     429          751 :         int noteSidePadding = style.styleAbsolute(Sid::tremoloNoteSidePadding) / spatium * 4.0;
     430              : 
     431          751 :         int outsideStaffOffset = 0;
     432          751 :         if (!staff->isTabStaff(item->tick())) {
     433          747 :             Note* lineNote = ldata->up ? item->upNote() : item->downNote();
     434          747 :             if (lineNote->line() == INVALID_LINE) {
     435            0 :                 lineNote->updateLine();
     436              :             }
     437              : 
     438          747 :             int line = lineNote->line();
     439          747 :             line *= 2; // convert to quarter spaces
     440              : 
     441          747 :             if (!ldata->up && line < -2) {
     442            0 :                 outsideStaffOffset = -line;
     443          747 :             } else if (ldata->up && line > staff->lines(item->tick()) * 4) {
     444            0 :                 outsideStaffOffset = line - (staff->lines(item->tick()) * 4) + 4;
     445              :             }
     446              :         }
     447          751 :         minStemLength += (outSidePadding + std::max(noteSidePadding, outsideStaffOffset));
     448              : 
     449          751 :         if (item->hook()) {
     450            4 :             bool straightFlags = style.styleB(Sid::useStraightNoteFlags);
     451            4 :             double smuflAnchor = item->hook()->smuflAnchor().y() * (ldata->up ? 1 : -1);
     452            4 :             int hookOffset = floor((item->hook()->height() / item->intrinsicMag() + smuflAnchor) / spatium * 4) - (straightFlags ? 0 : 2);
     453              :             // some fonts have hooks that extend very far down (making the height of the hook very large)
     454              :             // so we constrain to a reasonable maximum for hook length
     455            4 :             hookOffset = std::min(hookOffset, 11);
     456              :             // TODO: when the SMuFL metadata includes a cutout for flags, replace this with that metadata
     457              :             // https://github.com/w3c/smufl/issues/203
     458            4 :             int cutout = ldata->up ? 5 : 7;
     459            4 :             if (straightFlags) {
     460              :                 // don't need cutout for straight flags (they are similar to beams)
     461            0 :                 cutout = 0;
     462            4 :             } else if (item->beams() >= 2) {
     463              :                 // beams greater than two extend outwards and thus don't factor into the cutout
     464            0 :                 cutout -= 2;
     465              :             }
     466              : 
     467            4 :             minStemLength += hookOffset - cutout;
     468              : 
     469              :             // hooks with trems inside them no longer ceil (snap) to nearest 0.5sp.
     470              :             // if we want to add that back in, here is the place to do it:
     471              :             // minStemLength = ceil(minStemLength / 2.0) * 2;
     472              :         }
     473              :     }
     474       143710 :     if (item->beam() || item->tremoloTwoChord()) {
     475        69364 :         int beamCount = calcBeamCount(item);
     476              :         static const int minInnerStemLengths[4] = { 10, 9, 8, 7 };
     477        69364 :         int innerStemLength = minInnerStemLengths[std::min(beamCount, 3)];
     478        69364 :         int beamsHeight = beamCount * (style.styleB(Sid::useWideBeams) ? 4 : 3) - 1;
     479        69364 :         int newMinStemLength = std::max(minStemLength, innerStemLength);
     480        69364 :         newMinStemLength += beamsHeight;
     481              :         // for 4+ beams, there are a few situations where we need to lengthen the stem by 1
     482        69364 :         int noteLine = item->line();
     483        69364 :         int staffLines = staff->lines(item->tick());
     484        69364 :         bool noteInStaff = (ldata->up && noteLine > 0) || (!ldata->up && noteLine < (staffLines - 1) * 2);
     485        69364 :         if (beamCount >= 4 && noteInStaff) {
     486         1525 :             newMinStemLength++;
     487              :         }
     488        69364 :         minStemLength = std::max(minStemLength, newMinStemLength);
     489              :     }
     490       143710 :     return minStemLength;
     491              : }
     492              : 
     493         1377 : int StemLayout::calc4BeamsException(const Chord* item, int stemLength)
     494              : {
     495         1377 :     const Chord::LayoutData* ldata = item->ldata();
     496         1377 :     int difference = 0;
     497         1377 :     int staffLines = (item->staff()->lines(item->tick()) - 1) * 2;
     498         1377 :     if (ldata->up && item->upNote()->line() > staffLines) {
     499          133 :         difference = item->upNote()->line() - staffLines;
     500         1244 :     } else if (!ldata->up && item->downNote()->line() < 0) {
     501          190 :         difference = std::abs(item->downNote()->line());
     502              :     }
     503         1377 :     switch (difference) {
     504           60 :     case 2:
     505           60 :         return std::max(stemLength, 21);
     506           90 :     case 3:
     507              :     case 4:
     508           90 :         return std::max(stemLength, 23);
     509         1227 :     default:
     510         1227 :         return stemLength;
     511              :     }
     512              : }
     513              : 
     514       488905 : int StemLayout::calcBeamCount(const Chord* item)
     515              : {
     516       488905 :     const TremoloTwoChord* trem = item->tremoloTwoChord();
     517       488905 :     return (trem ? trem->lines() : 0) + (item->beam() ? item->beams() : 0);
     518              : }
        

Generated by: LCOV version 2.0-1