LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - harmonylayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 80.2 % 592 475
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "harmonylayout.h"
      24              : 
      25              : #include "draw/fontmetrics.h"
      26              : 
      27              : #include "dom/factory.h"
      28              : #include "dom/fret.h"
      29              : #include "dom/harmony.h"
      30              : #include "dom/staff.h"
      31              : 
      32              : #include "parenthesislayout.h"
      33              : #include "textlayout.h"
      34              : #include "tlayout.h"
      35              : 
      36              : using namespace muse::draw;
      37              : using namespace mu::engraving;
      38              : using namespace mu::engraving::rendering::score;
      39              : 
      40         3654 : void HarmonyLayout::layoutHarmony(Harmony* item, Harmony::LayoutData* ldata,
      41              :                                   const LayoutContext& ctx)
      42              : {
      43         3654 :     if (!item->ownershipParent()) {
      44            0 :         ldata->setPos(0.0, 0.0);
      45            0 :         const_cast<Harmony*>(item)->setOffset(0.0, 0.0);
      46              :     }
      47              : 
      48         3654 :     if (!item->cursor()->editing()) {
      49         3654 :         render(item, ldata, ctx);
      50              :     }
      51              : 
      52         3654 :     if (ldata->layoutInvalid) {
      53         1158 :         item->createBlocks(ldata);
      54              :     }
      55              : 
      56         3654 :     if (ldata->blocks.empty()) {
      57            0 :         ldata->blocks.push_back(TextBlock());
      58              :     }
      59              : 
      60         3654 :     PointF positionPoint = calculateBoundingRect(item, ldata, ctx);
      61              : 
      62         3654 :     if (item->isPolychord()) {
      63           62 :         for (LineF& line : ldata->polychordDividerLines.mut_value()) {
      64           32 :             line.setP1(PointF(ldata->bbox().left(), line.y1()));
      65           32 :             line.setP2(PointF(ldata->bbox().right(), line.y2()));
      66              :         }
      67              :     }
      68              : 
      69         3654 :     if (item->hasFrame()) {
      70            0 :         item->layoutFrame(ldata);
      71              :     }
      72              : 
      73         3654 :     ldata->setPos(positionPoint);
      74              : 
      75         3654 :     if (!item->cursor()->editing() && !ldata->renderItemList.value().empty()) {
      76         3560 :         ParenthesisLayout::layoutParentheses(item, ctx);
      77              :     }
      78         3654 : }
      79              : 
      80         3654 : PointF HarmonyLayout::calculateBoundingRect(const Harmony* item, Harmony::LayoutData* ldata, const LayoutContext& ctx)
      81              : {
      82         3654 :     const double ypos = (item->placeBelow() && item->staff()) ? item->staff()->staffHeight(item->tick()) : 0.0;
      83         7308 :     const FretDiagram* fd = (item->ownershipParent() && item->ownershipParent()->isFretDiagram())
      84         7308 :                             ? toFretDiagram(item->ownershipParent())
      85         3654 :                             : nullptr;
      86         3654 :     const bool alignToFretDiagram = fd && fd->visible();
      87              : 
      88         3654 :     const double standardNoteWidth = item->symWidth(SymId::noteheadBlack);
      89              : 
      90         3654 :     double newPosX = 0.0;
      91         3654 :     double newPosY = 0.0;
      92              : 
      93         3654 :     if (item->ldata()->renderItemList().empty()) {
      94           94 :         TextLayout::layoutBaseTextBase1(item, ldata);
      95              : 
      96           94 :         if (alignToFretDiagram) {
      97           42 :             newPosY = ldata->pos().y();
      98              :         } else {
      99           52 :             newPosY = ypos - ((item->align() == AlignV::BOTTOM) ? -ldata->bbox().height() : 0.0);
     100              :         }
     101              :     } else {
     102         3560 :         layoutModifierParentheses(item, ctx);
     103         3560 :         RectF bb;
     104         3560 :         RectF hAlignBox;
     105        14306 :         for (HarmonyRenderItem* renderItem : item->ldata()->renderItemList()) {
     106        10746 :             RectF tsBbox = renderItem->tightBoundingRect().translated(renderItem->x(), renderItem->y());
     107        10746 :             bb.unite(tsBbox);
     108              : 
     109        10746 :             if (renderItem->align()) {
     110        10746 :                 hAlignBox.unite(tsBbox);
     111              :             }
     112              :         }
     113              : 
     114         3560 :         double xx = 0.0;
     115         3560 :         if (alignToFretDiagram) {
     116          718 :             switch (ctx.conf().styleV(Sid::chordAlignmentToFretboard).value<AlignH>()) {
     117            0 :             case AlignH::LEFT:
     118              :             case AlignH::JUSTIFY:
     119            0 :                 xx = -hAlignBox.left();
     120            0 :                 break;
     121          718 :             case AlignH::HCENTER:
     122          718 :                 xx = -(hAlignBox.center().x());
     123          718 :                 break;
     124            0 :             case AlignH::RIGHT:
     125            0 :                 xx = -hAlignBox.right();
     126            0 :                 break;
     127              :             }
     128              :         } else {
     129         2842 :             switch (item->position()) {
     130         1659 :             case AlignH::LEFT:
     131              :             case AlignH::JUSTIFY:
     132         1659 :                 xx = -hAlignBox.left();
     133         1659 :                 break;
     134         1183 :             case AlignH::HCENTER:
     135         1183 :                 xx = -(hAlignBox.center().x());
     136         1183 :                 break;
     137            0 :             case AlignH::RIGHT:
     138            0 :                 xx = -hAlignBox.right();
     139            0 :                 break;
     140              :             }
     141              :         }
     142              : 
     143         3560 :         double yy = -bb.y();      // Align::TOP
     144         3560 :         if (item->align() == AlignV::VCENTER) {
     145            0 :             yy = -bb.y() / 2.0;
     146         3560 :         } else if (item->align() == AlignV::BASELINE) {
     147         3548 :             yy = item->baseLine();
     148           12 :         } else if (item->align() == AlignV::BOTTOM) {
     149            0 :             yy = -bb.height() - bb.y();
     150              :         }
     151              : 
     152         3560 :         if (alignToFretDiagram) {
     153          718 :             double nutLineWidth = fd->ldata()->nutLineWidth;
     154          718 :             newPosY = yy - ctx.conf().styleAbsolute(Sid::harmonyFretDist) - nutLineWidth;
     155              :         } else {
     156         2842 :             newPosY = ypos;
     157              :         }
     158              : 
     159        14306 :         for (HarmonyRenderItem* renderItem : item->ldata()->renderItemList()) {
     160        10746 :             renderItem->setOffset(PointF(xx, yy));
     161              :         }
     162              : 
     163         3560 :         ldata->polychordDividerOffset = yy;
     164              : 
     165         3560 :         ldata->setBbox(bb.translated(xx, yy));
     166         3560 :         ldata->harmonyHeight = ldata->bbox().height();
     167              :     }
     168              : 
     169         3654 :     if (alignToFretDiagram) {
     170          760 :         switch (ctx.conf().styleV(Sid::chordAlignmentToFretboard).value<AlignH>()) {
     171            0 :         case AlignH::LEFT:
     172              :         case AlignH::JUSTIFY:
     173            0 :             newPosX = 0.0;
     174            0 :             break;
     175          760 :         case AlignH::HCENTER:
     176          760 :             newPosX = 0.5 * fd->mainWidth();
     177          760 :             break;
     178            0 :         case AlignH::RIGHT:
     179            0 :             newPosX = fd->mainWidth();
     180            0 :             break;
     181              :         }
     182              :     } else {
     183         2894 :         switch (item->position()) {
     184         1659 :         case AlignH::LEFT:
     185              :         case AlignH::JUSTIFY:
     186         1659 :             newPosX = 0.0;
     187         1659 :             break;
     188         1235 :         case AlignH::HCENTER:
     189         1235 :             newPosX = standardNoteWidth * 0.5;
     190         1235 :             break;
     191            0 :         case AlignH::RIGHT:
     192            0 :             newPosX = standardNoteWidth;
     193            0 :             break;
     194              :         }
     195              :     }
     196              : 
     197         3654 :     if (fd && !fd->visible()) {
     198              :         // Translate to base position around note
     199            0 :         newPosX -= fd->pos().x();
     200              :     }
     201              : 
     202         3654 :     PointF newPos = PointF(newPosX, newPosY);
     203              : 
     204         3654 :     if (!alignToFretDiagram) {
     205         2894 :         PointF defaultPos = item->defaultPos();
     206         2894 :         newPos += defaultPos;
     207              :     }
     208              : 
     209         3654 :     return newPos;
     210              : }
     211              : 
     212         3560 : void HarmonyLayout::layoutModifierParentheses(const Harmony* item, const LayoutContext& ctx)
     213              : {
     214         3560 :     const double spatium = item->spatium();
     215         3560 :     const std::vector<HarmonyRenderItem*>& itemList = item->ldata()->renderItemList();
     216         3560 :     const double smuflParens = ctx.conf().styleB(Sid::harmonyParenUseSmuflSym);
     217              :     // Layout parentheses
     218         3560 :     std::vector<ChordSymbolParen*> openingParenStack;
     219         3560 :     double lastTextSegHeight = 0.0;
     220         3560 :     double lastTextSegTop = 0.0;
     221         3560 :     double rootRefHeight = 0.0;
     222         3560 :     double rootCapHeight = 0.0;
     223         3560 :     double parenExtension = 0.1 * spatium * (item->size() / 10.0);
     224        14306 :     for (HarmonyRenderItem* renderItem : itemList) {
     225        10746 :         if (ChordSymbolParen* curParen = dynamic_cast<ChordSymbolParen*>(renderItem)) {
     226           64 :             if (curParen->parenItem->direction() == DirectionH::LEFT) {
     227              :                 // Opening paren
     228           32 :                 openingParenStack.push_back(curParen);
     229           32 :                 if (ctx.conf().styleB(Sid::harmonyParenUseSmuflSym)) {
     230            0 :                     curParen->parenItem->mutldata()->symId = SymId::csymParensLeftTall;
     231              :                 }
     232              :             } else {
     233              :                 // Closing paren
     234           32 :                 ChordSymbolParen* openingParen = openingParenStack.empty() ? nullptr : openingParenStack.back();
     235           32 :                 if (!openingParen) {
     236            0 :                     continue;
     237              :                 }
     238           32 :                 openingParenStack.pop_back();
     239           32 :                 curParen->top = openingParen->top;
     240           32 :                 curParen->bottom = openingParen->bottom;
     241              : 
     242              :                 // Layout parenthesis pair
     243           32 :                 double startY = openingParen->top - parenExtension;
     244           32 :                 double height = (openingParen->bottom - openingParen->top) + 2 * parenExtension;
     245           32 :                 if (std::isinf(height)) {
     246            0 :                     height = lastTextSegHeight;
     247              :                 }
     248           32 :                 if (muse::RealIsEqual(DBL_MAX, startY)) {
     249            0 :                     startY = lastTextSegTop;
     250              :                 }
     251              : 
     252           32 :                 if (smuflParens) {
     253            0 :                     double symHeight = item->symHeight(SymId::csymParensRightTall);
     254            0 :                     double scale = height / symHeight;
     255            0 :                     curParen->parenItem->mutldata()->symId = SymId::csymParensRightTall;
     256            0 :                     curParen->parenItem->mutldata()->symScale = scale;
     257            0 :                     openingParen->parenItem->mutldata()->symScale = scale;
     258              : 
     259            0 :                     curParen->sety(startY + height);
     260            0 :                     openingParen->sety(startY + height);
     261              : 
     262            0 :                     ParenthesisLayout::createSmuflShape(openingParen->parenItem, openingParen->parenItem->mutldata());
     263            0 :                     ParenthesisLayout::createSmuflShape(curParen->parenItem, curParen->parenItem->mutldata());
     264              :                 } else {
     265           32 :                     const double scale = (height - 2 * parenExtension) / rootCapHeight;
     266           32 :                     const double mag = openingParen->parenItem->ldata()->mag();
     267              :                     static constexpr double HEIGHT_TO_WIDTH_RATIO = 20;
     268           32 :                     const double midPointThickness = height / HEIGHT_TO_WIDTH_RATIO * mag * 1 / std::sqrt(scale);
     269           32 :                     const double endPointThickness = 0.03;
     270           32 :                     const double shoulder = 0.2 * height * std::pow(mag, 0.1) * 1 / std::sqrt(scale);
     271              : 
     272           32 :                     openingParen->parenItem->mutldata()->startY = startY;
     273           32 :                     openingParen->sety(startY);
     274           32 :                     openingParen->parenItem->mutldata()->height = height;
     275           32 :                     openingParen->parenItem->mutldata()->midPointThickness.set_value(midPointThickness);
     276           32 :                     openingParen->parenItem->mutldata()->endPointThickness.set_value(endPointThickness);
     277           32 :                     openingParen->parenItem->mutldata()->shoulderWidth = shoulder;
     278              : 
     279           32 :                     curParen->parenItem->mutldata()->startY = startY;
     280           32 :                     curParen->sety(startY);
     281           32 :                     curParen->parenItem->mutldata()->height = height;
     282           32 :                     curParen->parenItem->mutldata()->midPointThickness.set_value(midPointThickness);
     283           32 :                     curParen->parenItem->mutldata()->endPointThickness.set_value(endPointThickness);
     284           32 :                     curParen->parenItem->mutldata()->shoulderWidth = shoulder;
     285              : 
     286           32 :                     ParenthesisLayout::createPathAndShape(openingParen->parenItem, openingParen->parenItem->mutldata());
     287           32 :                     ParenthesisLayout::createPathAndShape(curParen->parenItem, curParen->parenItem->mutldata());
     288              :                 }
     289              : 
     290           32 :                 double closingPos = openingParen->closingParenPos;
     291           32 :                 if (muse::RealIsEqual(-DBL_MAX, closingPos)) {
     292            0 :                     closingPos = openingParen->x() + openingParen->boundingRect().width();
     293              :                 }
     294           32 :                 curParen->setx(closingPos);
     295              : 
     296              :                 // Outer parens must always be the same length or longer than inner parens
     297           32 :                 for (ChordSymbolParen* outerParen : openingParenStack) {
     298            0 :                     outerParen->top = std::min(openingParen->top, outerParen->top);
     299            0 :                     outerParen->bottom = std::max(openingParen->bottom, outerParen->bottom);
     300            0 :                     outerParen->closingParenPos = std::max(openingParen->closingParenPos, outerParen->closingParenPos);
     301              :                 }
     302              :             }
     303        10682 :         } else if (TextSegment* textSeg = dynamic_cast<TextSegment*>(renderItem)) {
     304              :             // Set top paren height
     305        10682 :             lastTextSegHeight = textSeg->height();
     306        10682 :             lastTextSegTop = textSeg->tightBoundingRect().translated(textSeg->pos()).y();
     307        10682 :             if (muse::RealIsNull(rootRefHeight)) {
     308         3560 :                 rootRefHeight = textSeg->height();
     309         3560 :                 double top = textSeg->tightBoundingRect().translated(textSeg->pos()).y();
     310         3560 :                 double bottom =  textSeg->pos().y();
     311         3560 :                 double height = (bottom - top) + 2 * parenExtension;
     312              : 
     313         3560 :                 rootRefHeight = height;
     314         3560 :                 rootCapHeight = textSeg->capHeight();
     315              :             }
     316        10682 :             if (!openingParenStack.empty()) {
     317           96 :                 ChordSymbolParen* topParen = openingParenStack.back();
     318           96 :                 if (textSeg->font().type() != Font::Type::MusicSymbolText) {
     319           96 :                     topParen->top = std::min(topParen->top, textSeg->tightBoundingRect().translated(textSeg->pos()).y());
     320           96 :                     topParen->bottom = std::max(topParen->bottom, textSeg->pos().y());
     321              :                 }
     322           96 :                 topParen->closingParenPos = std::max(topParen->closingParenPos, textSeg->x() + textSeg->width());
     323           96 :                 continue;
     324           96 :             }
     325              :         }
     326              :     }
     327              : 
     328         7186 :     auto computePadding = [](HarmonyRenderItem* item1, HarmonyRenderItem* item2) {
     329         7186 :         bool item1Paren = item1->type() == HarmonyRenderItemType::PAREN;
     330         7186 :         bool item2Paren = item2->type() == HarmonyRenderItemType::PAREN;
     331              : 
     332         7186 :         double padding = 0.0;
     333         7186 :         if (item1Paren && item2Paren) {
     334            0 :             padding = std::min(item1->rightPadding(), item2->leftPadding());
     335              :         } else {
     336         7186 :             padding = std::max(item1->rightPadding(), item2->leftPadding());
     337              :         }
     338              : 
     339         7186 :         double scaling = (item1->height() + item2->height()) / 2;
     340         7186 :         padding *= scaling;
     341              : 
     342         7186 :         return padding;
     343              :     };
     344              : 
     345              :     // Create space in text for parentheses
     346              :     // TODO - STACKED C7((#11)#13)
     347              :     // C7(#11(#13))
     348         3560 :     double additionalSpace = 0.0;
     349         3560 :     bool prevParen = false;
     350              : 
     351        14306 :     for (size_t i = 0; i < itemList.size(); i++) {
     352        10746 :         HarmonyRenderItem* renderItem = itemList.at(i);
     353        10746 :         double padding = i != 0 ? computePadding(itemList.at(i - 1), renderItem) : 0.0;
     354              : 
     355        10746 :         if (ChordSymbolParen* paren = dynamic_cast<ChordSymbolParen*>(renderItem)) {
     356           64 :             if (paren->parenItem->direction() == DirectionH::LEFT) {
     357           32 :                 double heightToRefRatio = std::max(paren->height(), rootRefHeight) / rootRefHeight;
     358           32 :                 bool scaleParenWidth = !smuflParens && !prevParen && !muse::RealIsEqual(heightToRefRatio, 1.0);
     359           32 :                 const double PAREN_PADDING_SCALER = 0.6;
     360           32 :                 double scaledParenWidth = paren->parenItem->width() * PAREN_PADDING_SCALER / heightToRefRatio;
     361              : 
     362           32 :                 double parenWidth = scaleParenWidth ? scaledParenWidth : paren->parenItem->width();
     363              : 
     364           32 :                 double parenAdjust = additionalSpace + padding + (smuflParens ? 0.0 : parenWidth);
     365           32 :                 paren->movex(parenAdjust);
     366           32 :                 additionalSpace += parenWidth + padding;
     367           32 :             } else if (paren->parenItem->direction() == DirectionH::RIGHT) {
     368           32 :                 paren->movex(additionalSpace + padding);
     369           32 :                 additionalSpace += paren->parenItem->width() + padding;
     370              :             }
     371           64 :             prevParen = true;
     372        10682 :         } else if (TextSegment* ts = dynamic_cast<TextSegment*>(renderItem)) {
     373        10682 :             additionalSpace += padding;
     374        10682 :             ts->movex(additionalSpace);
     375        10682 :             prevParen = false;
     376              :         }
     377              :     }
     378         3560 : }
     379              : 
     380              : //---------------------------------------------------------
     381              : //   render
     382              : //    construct Chord Symbol
     383              : //---------------------------------------------------------
     384              : 
     385         3654 : void HarmonyLayout::render(Harmony* item, Harmony::LayoutData* ldata, const LayoutContext& ctx)
     386              : {
     387        10902 :     for (const HarmonyRenderItem* renderItem : ldata->renderItemList()) {
     388         7248 :         delete renderItem;
     389              :     }
     390         3654 :     ldata->renderItemList.mut_value().clear();
     391         3654 :     if (item->harmonyType() == HarmonyType::ROMAN) {
     392           56 :         renderRomanNumeral(item, ldata);
     393         1889 :         return;
     394              :     }
     395              : 
     396              :     // Render standard or Nashville chords
     397              : 
     398         3598 :     ChordList* chordList = item->score()->chordList();
     399              : 
     400         3598 :     ldata->fontList.mut_value().clear();
     401        19862 :     for (const ChordFont& cf : chordList->fonts) {
     402        16264 :         Font ff(item->font());
     403        16264 :         ff.setFamily(item->family(), Font::Type::Harmony);
     404              : 
     405        16264 :         double mag = item->mag() * cf.mag;
     406        16264 :         ff.setPointSizeF(ff.pointSizeF() * mag);
     407        16264 :         if (cf.musicSymbolText) {
     408         3582 :             ff.setFamily(cf.family, Font::Type::MusicSymbolText);
     409        12682 :         } else if (!(cf.family.isEmpty() || cf.family == "default")) {
     410            8 :             ff.setFamily(cf.family, Font::Type::Harmony);
     411              :         }
     412        16264 :         ldata->fontList.mut_value().push_back(ff);
     413        16264 :     }
     414         3598 :     if (ldata->fontList.mut_value().empty()) {
     415          727 :         Font ff(item->font());
     416          727 :         ff.setFamily(item->family(), Font::Type::Harmony);
     417              : 
     418          727 :         ldata->fontList.mut_value().push_back(ff);
     419          727 :     }
     420              : 
     421         3598 :     ldata->polychordDividerLines.reset();
     422         3598 :     HarmonyRenderCtx harmonyCtx;
     423              : 
     424              :     // Map of text segments and their final width
     425         3598 :     std::multimap<double, std::vector<HarmonyRenderItem*> > chordTextSegments;
     426              : 
     427         3630 :     for (size_t i = item->chords().size(); i > 0; i--) {
     428         3630 :         harmonyCtx.info = item->chords().at(i - 1);
     429         3630 :         renderSingleHarmony(item, ldata, harmonyCtx, ctx);
     430              : 
     431         3630 :         chordTextSegments.emplace(std::pair<double, std::vector<HarmonyRenderItem*> > { harmonyCtx.x(), harmonyCtx.renderItemList });
     432         3630 :         ldata->renderItemList.mut_value().insert(ldata->renderItemList.mut_value().end(),
     433              :                                                  harmonyCtx.renderItemList.begin(), harmonyCtx.renderItemList.end());
     434              : 
     435              :         // Measure divider spacing from lowest baseline and highest cap-height in segments
     436         3630 :         double rootBaseline = harmonyCtx.renderItemList.empty() ? -DBL_MAX : harmonyCtx.renderItemList.front()->y();
     437         3630 :         double bottomBaseline = -DBL_MAX;
     438        14320 :         for (const HarmonyRenderItem* renderItem : harmonyCtx.renderItemList) {
     439        10690 :             if (const TextSegment* ts = dynamic_cast<const TextSegment*>(renderItem)) {
     440        10626 :                 bottomBaseline = std::max(bottomBaseline, ts->y());
     441              :             }
     442              :         }
     443              : 
     444         3630 :         double diff = rootBaseline - bottomBaseline;
     445         3630 :         if (bottomBaseline > rootBaseline) {
     446           36 :             for (HarmonyRenderItem* renderItem : harmonyCtx.renderItemList) {
     447           30 :                 if (TextSegment* ts = dynamic_cast<TextSegment*>(renderItem)) {
     448           30 :                     ts->movey(diff);
     449              :                 }
     450              :             }
     451              :         }
     452         3630 :         if (i == item->chords().size()) {
     453              :             // Set baseline for bottom chord
     454         3598 :             ldata->baseline = -diff;
     455              :         }
     456              : 
     457         3630 :         double topCapHeight = DBL_MAX;
     458        14320 :         for (const HarmonyRenderItem* renderItem : harmonyCtx.renderItemList) {
     459        10690 :             if (const TextSegment* ts = dynamic_cast<const TextSegment*>(renderItem)) {
     460        10626 :                 topCapHeight = std::min(topCapHeight, ts->y() - ts->capHeight());
     461              :             }
     462              :         }
     463              : 
     464         3630 :         harmonyCtx.renderItemList.clear();
     465         3630 :         if (item->chords().size() == 1 || i == 1) {
     466         3598 :             break;
     467              :         }
     468              : 
     469           32 :         assert(!muse::RealIsEqual(topCapHeight, DBL_MAX));
     470              : 
     471           32 :         harmonyCtx.setx(0);
     472           32 :         harmonyCtx.sety(topCapHeight);
     473              : 
     474           32 :         double lineY = harmonyCtx.y() - item->absoluteFromSpatium(ctx.conf().style().styleS(Sid::polychordDividerSpacing))
     475           32 :                        - item->absoluteFromSpatium(ctx.conf().style().styleS(Sid::polychordDividerThickness)) / 2;
     476           32 :         LineF line = LineF(PointF(0.0, lineY), PointF(0.0, lineY));
     477           32 :         ldata->polychordDividerLines.mut_value().push_back(line);
     478              : 
     479           32 :         harmonyCtx.movey(item->absoluteFromSpatium(-ctx.conf().style().styleS(Sid::polychordDividerSpacing)) * 2.0);
     480           32 :         harmonyCtx.movey(item->absoluteFromSpatium(-ctx.conf().style().styleS(Sid::polychordDividerThickness)));
     481              :     }
     482              : 
     483              :     // Align polychords
     484              : 
     485         3598 :     if (item->align() == AlignH::LEFT) {
     486         1833 :         return;
     487              :     }
     488              : 
     489         1765 :     double longestLine = 0.0;
     490              : 
     491         3562 :     for (const auto& [width, _] : chordTextSegments) {
     492         1797 :         if (width > longestLine) {
     493         1719 :             longestLine = width;
     494              :         }
     495              :     }
     496              : 
     497         3562 :     for (auto& textSegs : chordTextSegments) {
     498         1797 :         double width = textSegs.first;
     499         1797 :         std::vector<HarmonyRenderItem*>& segs = textSegs.second;
     500              : 
     501         1797 :         double diff = longestLine - width;
     502              : 
     503         1797 :         if (muse::RealIsNull(diff)) {
     504         1765 :             continue;
     505              :         }
     506              : 
     507              :         // For centre align adjust by .5* difference, for right align adjust by full difference
     508           32 :         if (item->align() == AlignH::HCENTER) {
     509           32 :             diff *= 0.5;
     510              :         }
     511              : 
     512           88 :         for (HarmonyRenderItem* seg : segs) {
     513           56 :             seg->movex(diff);
     514              :         }
     515              :     }
     516         5431 : }
     517              : 
     518           56 : void HarmonyLayout::renderRomanNumeral(Harmony* item, Harmony::LayoutData* ldata)
     519              : {
     520           56 :     HarmonyRenderCtx harmonyCtx;
     521           56 :     if (item->chords().empty()) {
     522            0 :         return;
     523              :     }
     524           56 :     HarmonyInfo* info = item->chords().front();
     525              : 
     526           56 :     render(item, ldata, info->textName(), harmonyCtx);
     527           56 :     ldata->renderItemList.mut_value().insert(ldata->renderItemList.mut_value().end(), harmonyCtx.renderItemList.begin(),
     528              :                                              harmonyCtx.renderItemList.end());
     529           56 :     ldata->baseline = 0.0;
     530           56 : }
     531              : 
     532         3630 : void HarmonyLayout::doRenderSingleHarmony(Harmony* item, Harmony::LayoutData* ldata, HarmonyRenderCtx& harmonyCtx, int rootTpc, int bassTpc,
     533              :                                           const LayoutContext& ctx)
     534              : {
     535         3630 :     HarmonyInfo* info = harmonyCtx.info;
     536         3630 :     ChordList* chordList = info->chordList();
     537         3630 :     if (!chordList) {
     538            0 :         return;
     539              :     }
     540              : 
     541         3630 :     const MStyle& style = ctx.conf().style();
     542              : 
     543         3630 :     NoteCaseType rootCase = item->rootRenderCase(info);
     544         3630 :     NoteCaseType bassCase = item->bassRenderCase();
     545              : 
     546         3630 :     NoteSpellingType spelling = style.styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
     547              : 
     548         3630 :     const ChordDescription* cd = info->getDescription();
     549         3630 :     const bool stackModifiers = style.styleB(Sid::verticallyStackModifiers) && !item->doNotStackModifiers();
     550              : 
     551         3630 :     if (item->harmonyType() == HarmonyType::STANDARD && tpcIsValid(rootTpc)) {
     552              :         // render root
     553         3438 :         render(item, ldata, chordList->renderListRoot, harmonyCtx, ctx, rootTpc, spelling, rootCase);
     554              :         // render extension
     555         3438 :         if (cd) {
     556         3438 :             render(item, ldata, stackModifiers ? cd->renderListStacked : cd->renderList, harmonyCtx, ctx, 0);
     557              :         }
     558          192 :     } else if (item->harmonyType() == HarmonyType::NASHVILLE && tpcIsValid(rootTpc)) {
     559              :         // render function
     560          162 :         render(item, ldata, chordList->renderListFunction, harmonyCtx, ctx, rootTpc, spelling, bassCase);
     561          162 :         double adjust = chordList->nominalAdjust();
     562          162 :         harmonyCtx.movey(adjust * item->magS() * item->spatium() * .2);
     563              :         // render extension
     564          162 :         if (cd) {
     565          162 :             render(item, ldata, stackModifiers ? cd->renderListStacked : cd->renderList, harmonyCtx, ctx, 0);
     566              :         }
     567              :     } else {
     568           30 :         render(item, ldata, info->textName(), harmonyCtx);
     569              :     }
     570              : 
     571              :     // render bass
     572         3630 :     if (tpcIsValid(bassTpc)) {
     573              :         const std::vector<RenderActionPtr>& bassNoteChordList
     574           55 :             = style.styleB(Sid::chordBassNoteStagger) ? chordList->renderListBassOffset : chordList->renderListBass;
     575              : 
     576           55 :         static const std::wregex PATTERN_69 = std::wregex(L"6[,/]?9");
     577           55 :         const bool is69 = info->textName().contains(PATTERN_69);
     578           55 :         const bool hasModifierStack = stackModifiers && (info->parsedChord() ? info->parsedChord()->modifierList().size() > 1 : false);
     579              : 
     580           55 :         if (hasModifierStack || is69) {
     581            0 :             render(item, ldata, { std::make_shared<RenderActionMove>(0.05, 0.0) }, harmonyCtx, ctx,
     582              :                    bassTpc, spelling, bassCase, item->bassScale());
     583              :         }
     584           55 :         render(item, ldata, bassNoteChordList, harmonyCtx, ctx, bassTpc, spelling, bassCase, item->bassScale());
     585              :     }
     586            0 : }
     587              : 
     588         3630 : void HarmonyLayout::renderSingleHarmony(Harmony* item, Harmony::LayoutData* ldata, HarmonyRenderCtx& harmonyCtx,
     589              :                                         const LayoutContext& ctx)
     590              : {
     591         3630 :     harmonyCtx.hAlign = true;
     592         3630 :     const MStyle& style = ctx.conf().style();
     593         3630 :     HarmonyInfo* info = harmonyCtx.info;
     594              : 
     595         3630 :     int rootTpc = info->rootTpc();
     596         3630 :     int bassTpc = info->bassTpc();
     597              : 
     598         3630 :     DisplayCapoChordType displayCapo = style.styleV(Sid::displayCapoChords).value<DisplayCapoChordType>();
     599              : 
     600              :     // render single if no capo or both
     601              : 
     602         3630 :     if (displayCapo == DisplayCapoChordType::BOTH || displayCapo == DisplayCapoChordType::CONCERT) {
     603         3630 :         doRenderSingleHarmony(item, ldata, harmonyCtx, rootTpc, bassTpc, ctx);
     604              :     }
     605              : 
     606         3630 :     int capo = style.styleI(Sid::capoPosition);
     607         3630 :     if (capo == 0) {
     608         3630 :         return;
     609              :     }
     610              : 
     611            0 :     int capoRootTpc = Tpc::TPC_INVALID;
     612            0 :     int capoBassTpc = Tpc::TPC_INVALID;
     613            0 :     if (tpcIsValid(rootTpc) && capo > 0 && capo < 12) {
     614            0 :         int tpcOffset[] = { 0, 5, -2, 3, -4, 1, 6, -1, 4, -3, 2, -5 };
     615            0 :         capoRootTpc = rootTpc + tpcOffset[capo];
     616            0 :         capoBassTpc = bassTpc;
     617              : 
     618            0 :         if (tpcIsValid(capoBassTpc)) {
     619            0 :             capoBassTpc += tpcOffset[capo];
     620              :         }
     621              : 
     622              :         /*
     623              :          * For guitarists, avoid x and bb in Root or Bass,
     624              :          * and also avoid E#, B#, Cb and Fb in Root.
     625              :          */
     626            0 :         if (capoRootTpc < 8 || (tpcIsValid(capoBassTpc) && capoBassTpc < 6)) {
     627            0 :             capoRootTpc += 12;
     628            0 :             if (tpcIsValid(capoBassTpc)) {
     629            0 :                 capoBassTpc += 12;
     630              :             }
     631            0 :         } else if (capoRootTpc > 24 || (tpcIsValid(capoBassTpc) && capoBassTpc > 26)) {
     632            0 :             capoRootTpc -= 12;
     633            0 :             if (tpcIsValid(capoBassTpc)) {
     634            0 :                 capoBassTpc -= 12;
     635              :             }
     636              :         }
     637              :     }
     638              : 
     639              :     // Render parens if both
     640            0 :     if (displayCapo == DisplayCapoChordType::BOTH) {
     641            0 :         RenderActionParenPtr p = std::make_shared<RenderActionParenLeft>();
     642            0 :         renderActionParen(item, p, harmonyCtx);
     643            0 :     }
     644            0 :     if (displayCapo == DisplayCapoChordType::BOTH || displayCapo == DisplayCapoChordType::TRANSPOSED) {
     645              :         // render capo if both or capo
     646            0 :         doRenderSingleHarmony(item, ldata, harmonyCtx, capoRootTpc, capoBassTpc, ctx);
     647              :     }
     648            0 :     if (displayCapo == DisplayCapoChordType::BOTH) {
     649            0 :         RenderActionParenPtr p = std::make_shared<RenderActionParenRight>();
     650            0 :         renderActionParen(item, p, harmonyCtx);
     651            0 :     }
     652              : }
     653              : 
     654              : //---------------------------------------------------------
     655              : //   render
     656              : //---------------------------------------------------------
     657              : 
     658           86 : void HarmonyLayout::render(Harmony* item, Harmony::LayoutData* ldata, const String& s, HarmonyRenderCtx& harmonyCtx)
     659              : {
     660           86 :     if (s.isEmpty()) {
     661            0 :         return;
     662              :     }
     663              : 
     664           86 :     Font f = item->harmonyType() != HarmonyType::ROMAN ? ldata->fontList.value().front() : item->font();
     665           86 :     TextSegment* ts = new TextSegment(s, f, harmonyCtx.x(), harmonyCtx.y(), harmonyCtx.hAlign);
     666           86 :     harmonyCtx.renderItemList.push_back(ts);
     667           86 :     harmonyCtx.movex(ts->width());
     668           86 : }
     669              : 
     670            0 : void HarmonyLayout::render(Harmony* item, Harmony::LayoutData* ldata, SymId sym, HarmonyRenderCtx& harmonyCtx, const LayoutContext& ctx)
     671              : {
     672            0 :     if (sym == SymId::noSym) {
     673            0 :         return;
     674              :     }
     675              : 
     676            0 :     Font f = item->harmonyType() != HarmonyType::ROMAN ? ldata->fontList.value().front() : item->font();
     677            0 :     f.setFamily(ctx.conf().style().styleSt(Sid::musicalTextFont), Font::Type::MusicSymbolText);
     678              : 
     679            0 :     String s = ctx.conf().engravingFont()->toString(sym);
     680              : 
     681            0 :     TextSegment* ts = new TextSegment(s, f, harmonyCtx.x(), harmonyCtx.y(), harmonyCtx.hAlign);
     682            0 :     harmonyCtx.renderItemList.push_back(ts);
     683            0 :     harmonyCtx.movex(ts->width());
     684            0 : }
     685              : 
     686         7255 : void HarmonyLayout::render(Harmony* item, Harmony::LayoutData* ldata, const std::vector<RenderActionPtr>& renderList,
     687              :                            HarmonyRenderCtx& harmonyCtx, const LayoutContext& ctx,
     688              :                            int tpc,
     689              :                            NoteSpellingType noteSpelling,
     690              :                            NoteCaseType noteCase, double noteMag)
     691              : {
     692         7255 :     harmonyCtx.stack = {};
     693         7255 :     harmonyCtx.tpc = tpc;
     694         7255 :     harmonyCtx.noteSpelling = noteSpelling;
     695         7255 :     harmonyCtx.noteCase = noteCase;
     696         7255 :     harmonyCtx.scale = noteMag;
     697              : 
     698        23099 :     for (const RenderActionPtr& a : renderList) {
     699        15844 :         renderAction(item, ldata, a, harmonyCtx, ctx);
     700              :     }
     701         7255 : }
     702              : 
     703        16258 : void HarmonyLayout::renderAction(Harmony* item, Harmony::LayoutData* ldata, const RenderActionPtr& a, HarmonyRenderCtx& harmonyCtx,
     704              :                                  const LayoutContext& ctx)
     705              : {
     706        16258 :     switch (a->actionType()) {
     707         7099 :     case RenderAction::RenderActionType::SET:
     708         7099 :         renderActionSet(item, ldata, std::static_pointer_cast<RenderActionSet>(a), harmonyCtx, ctx);
     709         7099 :         break;
     710         3233 :     case RenderAction::RenderActionType::MOVE:
     711         3233 :         renderActionMove(item, std::static_pointer_cast<RenderActionMove>(a), harmonyCtx);
     712         3233 :         break;
     713           12 :     case RenderAction::RenderActionType::MOVEXHEIGHT:
     714           12 :         renderActionMoveXHeight(item, std::static_pointer_cast<RenderActionMoveXHeight>(a), harmonyCtx);
     715           12 :         break;
     716           16 :     case RenderAction::RenderActionType::PUSH:
     717           16 :         renderActionPush(harmonyCtx);
     718           16 :         break;
     719           16 :     case RenderAction::RenderActionType::POP:
     720           16 :         renderActionPop(std::static_pointer_cast<RenderActionPop>(a), harmonyCtx);
     721           16 :         break;
     722         2897 :     case RenderAction::RenderActionType::NOTE:
     723         2897 :         renderActionNote(item, ldata, harmonyCtx);
     724         2897 :         break;
     725         2897 :     case RenderAction::RenderActionType::ACCIDENTAL:
     726         2897 :         renderActionAcc(item, ldata, harmonyCtx, ctx);
     727         2897 :         break;
     728            0 :     case RenderAction::RenderActionType::STOPHALIGN:
     729            0 :         renderActionAlign(harmonyCtx);
     730            0 :         break;
     731           24 :     case RenderAction::RenderActionType::SCALE:
     732           24 :         renderActionScale(std::static_pointer_cast<RenderActionScale>(a), harmonyCtx);
     733           24 :         break;
     734           64 :     case RenderAction::RenderActionType::PAREN:
     735           64 :         renderActionParen(item, std::static_pointer_cast<RenderActionParen>(a), harmonyCtx);
     736           64 :         break;
     737            0 :     default:
     738            0 :         LOGD("unknown render action %d", static_cast<int>(a->actionType()));
     739              :     }
     740        16258 : }
     741              : 
     742           16 : void HarmonyLayout::renderActionPush(HarmonyRenderCtx& harmonyCtx)
     743              : {
     744           16 :     harmonyCtx.stack.push(harmonyCtx.pos);
     745           16 : }
     746              : 
     747           16 : void HarmonyLayout::renderActionPop(const RenderActionPopPtr& a, HarmonyRenderCtx& harmonyCtx)
     748              : {
     749           16 :     if (harmonyCtx.stack.empty()) {
     750            0 :         LOGD("RenderAction::RenderActionType::POP: stack empty");
     751            0 :         return;
     752              :     }
     753              : 
     754           16 :     PointF pt = harmonyCtx.stack.top();
     755           16 :     harmonyCtx.stack.pop();
     756           16 :     harmonyCtx.pos = PointF(a->popX() ? pt.x() : harmonyCtx.x(), a->popY() ? pt.y() : harmonyCtx.y());
     757              : }
     758              : 
     759         2897 : void HarmonyLayout::renderActionNote(Harmony* item, Harmony::LayoutData* ldata, HarmonyRenderCtx& harmonyCtx)
     760              : {
     761         2897 :     if (!tpcIsValid(harmonyCtx.tpc)) {
     762            0 :         return;
     763              :     }
     764         2897 :     const ChordList* chordList = item->score()->chordList();
     765         2897 :     const Staff* st = item->staff();
     766         2897 :     const Key key = st ? st->key(item->tick()) : Key::INVALID;
     767              : 
     768         2897 :     String c;
     769              :     AccidentalVal acc;
     770              : 
     771         2897 :     if (item->harmonyType() == HarmonyType::STANDARD) {
     772         2803 :         tpc2name(harmonyCtx.tpc, harmonyCtx.noteSpelling, harmonyCtx.noteCase, c, acc);
     773           94 :     } else if (item->harmonyType() == HarmonyType::NASHVILLE) {
     774           94 :         String accStr;
     775           94 :         tpc2Function(harmonyCtx.tpc, key, accStr, c);
     776           94 :     }
     777              : 
     778         2897 :     if (c.empty()) {
     779            0 :         return;
     780              :     }
     781              : 
     782         2897 :     String lookup = u"note" + c;
     783         2897 :     ChordSymbol cs = chordList->symbol(lookup);
     784         2897 :     if (!cs.isValid()) {
     785         2897 :         cs = chordList->symbol(c);
     786              :     }
     787         2897 :     String text = cs.isValid() ? cs.value : c;
     788         2897 :     muse::draw::Font font = cs.isValid() ? ldata->fontList.value()[cs.fontIdx] : ldata->fontList.value().front();
     789         2897 :     font.setPointSizeF(font.pointSizeF() * harmonyCtx.scale);
     790              : 
     791         2897 :     TextSegment* ts = new TextSegment(text, font, harmonyCtx.x(), harmonyCtx.y(), harmonyCtx.hAlign);
     792         2897 :     harmonyCtx.renderItemList.push_back(ts);
     793         2897 :     harmonyCtx.movex(ts->width());
     794         2897 : }
     795              : 
     796         2897 : void HarmonyLayout::renderActionAcc(Harmony* item, Harmony::LayoutData* ldata, HarmonyRenderCtx& harmonyCtx, const LayoutContext& ctx)
     797              : {
     798         2897 :     if (!tpcIsValid(harmonyCtx.tpc)) {
     799         2297 :         return;
     800              :     }
     801         2897 :     const ChordList* chordList = item->score()->chordList();
     802         2897 :     const Staff* st = item->staff();
     803         2897 :     const Key key = st ? st->key(item->tick()) : Key::INVALID;
     804              : 
     805         2897 :     String c;
     806         2897 :     String acc;
     807         2897 :     String context = u"accidental";
     808              : 
     809         2897 :     if (item->harmonyType() == HarmonyType::STANDARD) {
     810         2803 :         tpc2name(harmonyCtx.tpc, harmonyCtx.noteSpelling, harmonyCtx.noteCase, c, acc);
     811           94 :     } else if (item->harmonyType() == HarmonyType::NASHVILLE) {
     812           94 :         tpc2Function(harmonyCtx.tpc, key, acc, c);
     813              :     }
     814              : 
     815         2897 :     if (acc.empty()) {
     816         2159 :         return;
     817              :     }
     818              : 
     819              :     // Try to find token & execute renderlist
     820          738 :     ChordToken tok = chordList->token(acc, ChordTokenClass::ACCIDENTAL);
     821          738 :     if (tok.isValid()) {
     822          552 :         for (const RenderActionPtr& a : tok.renderList) {
     823          414 :             renderAction(item, ldata, a, harmonyCtx, ctx);
     824              :         }
     825          138 :         return;
     826              :     }
     827              : 
     828              :     // No valid token, find symbol
     829              : 
     830              :     // German spelling - use special symbol for accidental in TPC_B_B
     831              :     // to allow it to be rendered as either Bb or B
     832          600 :     if (harmonyCtx.tpc == Tpc::TPC_B_B && harmonyCtx.noteSpelling == NoteSpellingType::GERMAN) {
     833            0 :         context = u"german_B";
     834              :     }
     835          600 :     String lookup = context + acc;
     836          600 :     ChordSymbol cs = chordList->symbol(lookup);
     837          600 :     if (!cs.isValid()) {
     838          600 :         cs = chordList->symbol(acc);
     839              :     }
     840          600 :     String text = cs.isValid() ? cs.value : c;
     841          600 :     muse::draw::Font font = cs.isValid() ? ldata->fontList.value()[cs.fontIdx] : ldata->fontList.value().front();
     842          600 :     font.setPointSizeF(font.pointSizeF() * harmonyCtx.scale);
     843              : 
     844          600 :     TextSegment* ts = new TextSegment(text, font, harmonyCtx.x(), harmonyCtx.y(), harmonyCtx.hAlign);
     845          600 :     harmonyCtx.renderItemList.push_back(ts);
     846          600 :     harmonyCtx.movex(ts->width());
     847         7629 : }
     848              : 
     849            0 : void HarmonyLayout::renderActionAlign(HarmonyRenderCtx& harmonyCtx)
     850              : {
     851            0 :     harmonyCtx.hAlign = false;
     852            0 : }
     853              : 
     854           24 : void HarmonyLayout::renderActionScale(const RenderActionScalePtr& a, HarmonyRenderCtx& harmonyCtx)
     855              : {
     856           24 :     harmonyCtx.scale *= a->scale();
     857           24 : }
     858              : 
     859           64 : void HarmonyLayout::renderActionParen(Harmony* item, const RenderActionParenPtr& a, HarmonyRenderCtx& harmonyCtx)
     860              : {
     861           64 :     Parenthesis* p = Factory::createParenthesis(item);
     862           64 :     p->setDirection(a->direction());
     863           64 :     p->setColor(item->color());
     864           64 :     p->setFollowParentColor(true);
     865           64 :     p->setGenerated(true);
     866              : 
     867           64 :     ChordSymbolParen* parenItem = new ChordSymbolParen(p, harmonyCtx.hAlign, harmonyCtx.x(), harmonyCtx.y());
     868           64 :     harmonyCtx.renderItemList.push_back(parenItem);
     869           64 : }
     870              : 
     871         7099 : void HarmonyLayout::kernCharacters(const Harmony* item, const String& text, HarmonyRenderCtx& harmonyCtx, const LayoutContext& ctx)
     872              : {
     873         7099 :     if (harmonyCtx.renderItemList.empty()) {
     874          663 :         return;
     875              :     }
     876              :     // Character pair and distance to move the second
     877              :     // Blank strings will apply the kerning no matter the following character
     878              :     // TODO - move this information to the XML file
     879              :     static const std::map<std::pair<String, String>, double> KERNED_CHARACTERS {
     880            0 :         { { u"A", u"\uE870" }, -0.4 },  // dim
     881            0 :         { { u"A", u"\uE871" }, -0.3 },  // half-dim
     882            0 :         { { u"\uE873", u"\uE870" }, -0.4 }, // triangle - dim
     883            0 :         { { u"\uE873", u"\uE871" }, -0.3 }, // triangle - half-dim
     884            0 :         { { u"A", u"/" }, 0.1 },
     885              : 
     886            0 :         { { u"A", u"\uE18E" }, -0.15 },  // dim JAZZ
     887            0 :         { { u"A", u"\uE18F" }, -0.15 },  // half-dim JAZZ
     888            0 :         { { u"\uE18A", u"\uE18E" }, -0.15 },  // triangle - dim JAZZ
     889            0 :         { { u"\uE18A", u"\uE18F" }, -0.15 },  // triangle - half-dim JAZZ
     890              : 
     891            0 :         { { u"\u266D", u"" }, -0.15 },  // b JAZZ
     892            0 :         { { u"\u266E", u"" }, -0.15 },  // natural JAZZ
     893            0 :         { { u"\u266F", u"" }, -0.15 },  // # JAZZ
     894            0 :         { { u"\u1D12A", u"" }, -0.15 }, // ## JAZZ
     895            0 :         { { u"\u1D12B", u"" }, -0.15 }, // bb JAZZ
     896         6516 :     };
     897              : 
     898         6436 :     HarmonyRenderItem* prevSeg = harmonyCtx.renderItemList.back();
     899         6436 :     TextSegment* ts = dynamic_cast<TextSegment*>(prevSeg);
     900         6436 :     if (!ts) {
     901           32 :         return;
     902              :     }
     903              : 
     904        96060 :     for (auto& kernInfo : KERNED_CHARACTERS) {
     905        89656 :         const std::pair<String, String> kernPair = kernInfo.first;
     906        89656 :         bool endChar = ts->text().endsWith(kernPair.first);
     907        89656 :         bool startChar = text.startsWith(kernPair.second);
     908              :         // VERY DIRTY HACK ALERT
     909              :         // "Match any" should only be applied to the Jazz preset currently.
     910              :         // Remove the jazz condition when these kern values are moved to the XML files
     911        89656 :         if (kernPair.second.isEmpty() && ctx.conf().styleV(Sid::chordStyle).value<ChordStylePreset>() != ChordStylePreset::JAZZ) {
     912        32020 :             continue;
     913              :         }
     914        57636 :         bool startMatchAny = kernPair.second.isEmpty();
     915        57636 :         if ((endChar && startChar) || (endChar && startMatchAny)) {
     916            0 :             const FontMetrics fm = FontMetrics(item->font());
     917            0 :             const double scale = harmonyCtx.scale * item->mag();
     918            0 :             harmonyCtx.pos = harmonyCtx.pos + PointF(kernInfo.second, 0.0) * FontMetrics::capHeight(item->font()) * scale;
     919            0 :             break;
     920            0 :         }
     921        89656 :     }
     922            5 : }
     923              : 
     924         7099 : void HarmonyLayout::renderActionSet(Harmony* item, Harmony::LayoutData* ldata, const RenderActionSetPtr& a, HarmonyRenderCtx& harmonyCtx,
     925              :                                     const LayoutContext& ctx)
     926              : {
     927         7099 :     const ChordList* chordList = item->score()->chordList();
     928         7099 :     const ChordSymbol cs = chordList->symbol(a->text());
     929         7099 :     const String text = cs.isValid() ? cs.value : a->text();
     930         7099 :     muse::draw::Font font = cs.isValid() ? ldata->fontList.value()[cs.fontIdx] : ldata->fontList.value().front();
     931         7099 :     font.setPointSizeF(font.pointSizeF() * harmonyCtx.scale);
     932         7099 :     if (item->harmonyType() == HarmonyType::NASHVILLE) {
     933          142 :         double nmag = chordList->nominalMag();
     934          142 :         font.setPointSizeF(font.pointSizeF() * nmag);
     935              :     }
     936              : 
     937         7099 :     kernCharacters(item, text, harmonyCtx, ctx);
     938              : 
     939         7099 :     TextSegment* ts = new TextSegment(text, font, harmonyCtx.x(), harmonyCtx.y(), harmonyCtx.hAlign);
     940         7099 :     harmonyCtx.movex(ts->width());
     941              : 
     942         7099 :     if (a->renderText()) {
     943         7099 :         harmonyCtx.renderItemList.push_back(ts);
     944         7099 :         return;
     945              :     }
     946              : 
     947            0 :     delete ts;
     948        21297 : }
     949              : 
     950         3233 : void HarmonyLayout::renderActionMove(Harmony* item, const RenderActionMovePtr& a, HarmonyRenderCtx& harmonyCtx)
     951              : {
     952         3233 :     const double scale = (a->scaled() ? harmonyCtx.scale : 1.0) * item->mag();
     953         3233 :     harmonyCtx.pos = harmonyCtx.pos + a->vec() * FontMetrics::capHeight(item->font()) * scale;
     954         3233 : }
     955              : 
     956           12 : void HarmonyLayout::renderActionMoveXHeight(Harmony* item, const RenderActionMoveXHeightPtr& a, HarmonyRenderCtx& harmonyCtx)
     957              : {
     958           12 :     const int direction = a->up() ? -1 : 1;
     959           12 :     const double scale = a->scaled() ? harmonyCtx.scale : 1.0;
     960           12 :     const FontMetrics fm = FontMetrics(item->font());
     961           12 :     harmonyCtx.movey(direction * fm.xHeight() * scale);
     962           12 : }
        

Generated by: LCOV version 2.0-1