LCOV - code coverage report
Current view: top level - src/engraving/dom - textbase.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 59.1 % 1920 1134
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) 2021 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 <cmath>
      24              : #include <stack>
      25              : 
      26              : #include "draw/fontmetrics.h"
      27              : 
      28              : #include "iengravingfont.h"
      29              : 
      30              : #include "rendering/score/textlayout.h"
      31              : #include "rendering/iscorerenderer.h"
      32              : 
      33              : #include "style/textstyle.h"
      34              : 
      35              : #include "rw/xmlreader.h"
      36              : #include "rw/xmlwriter.h"
      37              : 
      38              : #include "types/symnames.h"
      39              : #include "types/typesconv.h"
      40              : 
      41              : #ifndef ENGRAVING_NO_ACCESSIBILITY
      42              : #include "accessibility/accessibleitem.h"
      43              : #endif
      44              : 
      45              : #include "anchors.h"
      46              : #include "box.h"
      47              : #include "harppedaldiagram.h"
      48              : #include "instrumentname.h"
      49              : #include "measure.h"
      50              : #include "mscore.h"
      51              : #include "page.h"
      52              : #include "score.h"
      53              : 
      54              : #include "../editing/textedit.h"
      55              : 
      56              : #include "log.h"
      57              : 
      58              : using namespace mu;
      59              : using namespace muse::draw;
      60              : using namespace mu::engraving;
      61              : using namespace mu::engraving::rendering::score;
      62              : 
      63              : namespace mu::engraving {
      64              : static const char* FALLBACK_SYMBOL_FONT = "Bravura";
      65              : static const char* FALLBACK_SYMBOLTEXT_FONT = "Bravura Text";
      66              : 
      67              : //---------------------------------------------------------
      68              : //   isSorted
      69              : /// return true if (r1,c1) is at or before (r2,c2)
      70              : //---------------------------------------------------------
      71              : 
      72        33461 : bool TextBase::isSorted(size_t r1, size_t c1, size_t r2, size_t c2)
      73              : {
      74        33461 :     if (r1 < r2) {
      75           72 :         return true;
      76              :     }
      77              : 
      78        33389 :     if ((r1 == r2) && (c1 <= c2)) {
      79        33389 :         return true;
      80              :     }
      81              : 
      82            0 :     return false;
      83              : }
      84              : 
      85              : //---------------------------------------------------------
      86              : //   swap
      87              : /// swap (r1,c1) with (r2,c2)
      88              : //---------------------------------------------------------
      89              : 
      90            0 : void TextBase::swap(size_t& r1, size_t& c1, size_t& r2, size_t& c2)
      91              : {
      92            0 :     std::swap(r1, r2);
      93            0 :     std::swap(c1, c2);
      94            0 : }
      95              : 
      96              : //---------------------------------------------------------
      97              : //   sort
      98              : /// swap (r1,c1) with (r2,c2) if they are not sorted
      99              : //---------------------------------------------------------
     100              : 
     101        33461 : void TextBase::sort(size_t& r1, size_t& c1, size_t& r2, size_t& c2)
     102              : {
     103        33461 :     if (!isSorted(r1, c1, r2, c2)) {
     104            0 :         swap(r1, c1, r2, c2);
     105              :     }
     106        33461 : }
     107              : 
     108              : const String TextBase::UNDEFINED_FONT_FAMILY = String(u"Undefined");
     109              : const double TextBase::UNDEFINED_FONT_SIZE = -1.0;
     110              : 
     111              : //---------------------------------------------------------
     112              : //   operator==
     113              : //---------------------------------------------------------
     114              : 
     115       150934 : bool CharFormat::operator==(const CharFormat& cf) const
     116              : {
     117       150934 :     return cf.style() == style()
     118       150709 :            && cf.valign() == valign()
     119       150707 :            && cf.fontSize() == fontSize()
     120       452577 :            && cf.fontFamily() == fontFamily();
     121              : }
     122              : 
     123              : //---------------------------------------------------------
     124              : //   operator=
     125              : //---------------------------------------------------------
     126              : 
     127       183272 : CharFormat& CharFormat::operator=(const CharFormat& cf)
     128              : {
     129       183272 :     setStyle(cf.style());
     130       183272 :     setValign(cf.valign());
     131       183272 :     setFontSize(cf.fontSize());
     132       183272 :     setFontFamily(cf.fontFamily());
     133              : 
     134       183272 :     return *this;
     135              : }
     136              : 
     137              : //---------------------------------------------------------
     138              : //   clearSelection
     139              : //---------------------------------------------------------
     140              : 
     141       443068 : void TextCursor::clearSelection()
     142              : {
     143       443068 :     m_selectLine   = m_row;
     144       443068 :     m_selectColumn = m_column;
     145       443068 : }
     146              : 
     147            4 : void TextCursor::startEdit()
     148              : {
     149            4 :     setRow(0);
     150            4 :     setColumn(0);
     151            4 :     clearSelection();
     152            4 :     m_editing = true;
     153            4 :     m_visible = true;
     154            4 : }
     155              : 
     156            4 : void TextCursor::endEdit()
     157              : {
     158            4 :     setRow(0);
     159            4 :     setColumn(0);
     160            4 :     clearSelection();
     161            4 :     m_editing = false;
     162            4 :     m_visible = false;
     163            4 : }
     164              : 
     165              : //---------------------------------------------------------
     166              : //   init
     167              : //---------------------------------------------------------
     168              : 
     169        28330 : void TextCursor::init()
     170              : {
     171        28330 :     PropertyValue family = m_text->propertyDefault(Pid::FONT_FACE);
     172        28330 :     m_format.setFontFamily(family.value<String>());
     173              : 
     174        28330 :     PropertyValue size = m_text->propertyDefault(Pid::FONT_SIZE);
     175        28330 :     m_format.setFontSize(size.toReal());
     176              : 
     177        28330 :     PropertyValue style = m_text->propertyDefault(Pid::FONT_STYLE);
     178        28330 :     m_format.setStyle(static_cast<FontStyle>(style.toInt()));
     179              : 
     180        28330 :     PropertyValue verticalAlign = m_text->propertyDefault(Pid::TEXT_SCRIPT_ALIGN);
     181        28330 :     m_format.setValign(static_cast<VerticalAlignment>(verticalAlign.toInt()));
     182        28330 : }
     183              : 
     184            0 : std::pair<size_t, size_t> TextCursor::positionToLocalCoord(int position) const
     185              : {
     186            0 :     const TextBase::LayoutData* ldata = m_text->ldata();
     187            0 :     IF_ASSERT_FAILED(ldata) {
     188            0 :         return { muse::nidx, muse::nidx };
     189              :     }
     190              : 
     191            0 :     int currentPosition = 0;
     192            0 :     for (size_t i = 0; i < ldata->blocks.size(); ++i) {
     193            0 :         const TextBlock& t = ldata->blocks.at(i);
     194            0 :         for (size_t j = 0; j < t.columns(); ++j) {
     195            0 :             if (currentPosition == position) {
     196            0 :                 return { i, j };
     197              :             }
     198              : 
     199            0 :             currentPosition++;
     200              :         }
     201              :     }
     202              : 
     203            0 :     return { muse::nidx, muse::nidx };
     204              : }
     205              : 
     206            1 : int TextCursor::currentPosition() const
     207              : {
     208            1 :     return position(static_cast<int>(row()), static_cast<int>(column()));
     209              : }
     210              : 
     211            0 : TextCursor::Range TextCursor::selectionRange() const
     212              : {
     213            0 :     int cursorPosition = currentPosition();
     214            0 :     int selectionPosition = position(static_cast<int>(selectLine()), static_cast<int>(selectColumn()));
     215              : 
     216            0 :     if (cursorPosition > selectionPosition) {
     217            0 :         return range(selectionPosition, cursorPosition);
     218              :     } else {
     219            0 :         return range(cursorPosition, selectionPosition);
     220              :     }
     221              : }
     222              : 
     223              : //---------------------------------------------------------
     224              : //   columns
     225              : //---------------------------------------------------------
     226              : 
     227            2 : size_t TextCursor::columns() const
     228              : {
     229            2 :     return m_text->ldata()->textBlock(static_cast<int>(m_row)).columns();
     230              : }
     231              : 
     232              : //---------------------------------------------------------
     233              : //   currentCharacter
     234              : //---------------------------------------------------------
     235              : 
     236           18 : Char TextCursor::currentCharacter() const
     237              : {
     238           18 :     const TextBase::LayoutData* ldata = m_text->ldata();
     239           18 :     IF_ASSERT_FAILED(ldata) {
     240            0 :         return Char();
     241              :     }
     242              : 
     243           18 :     const TextBlock& t = ldata->blocks.at(row());
     244           18 :     String s = t.text(static_cast<int>(column()), 1, m_text);
     245           18 :     s = TextBase::unEscape(s);
     246           18 :     if (s.isEmpty()) {
     247            0 :         return Char();
     248              :     }
     249           18 :     return s.at(0);
     250           18 : }
     251              : 
     252              : //---------------------------------------------------------
     253              : //   updateCursorFormat
     254              : //---------------------------------------------------------
     255              : 
     256            6 : void TextCursor::updateCursorFormat()
     257              : {
     258            6 :     const TextBase::LayoutData* ldata = m_text->ldata();
     259            6 :     IF_ASSERT_FAILED(ldata) {
     260            0 :         return;
     261              :     }
     262            6 :     const TextBlock& block = ldata->blocks.at(m_row);
     263              : 
     264            6 :     size_t col = hasSelection() ? selectColumn() : column();
     265              :     // Get format at the LEFT of the cursor position
     266            6 :     const CharFormat* format = block.formatAt(std::max(static_cast<int>(col) - 1, 0));
     267            6 :     if (!format) {
     268            0 :         init();
     269              :     } else {
     270            6 :         setFormat(*format);
     271              :     }
     272              : }
     273              : 
     274              : //---------------------------------------------------------
     275              : //   cursorRect
     276              : //---------------------------------------------------------
     277              : 
     278            0 : RectF TextCursor::cursorRect() const
     279              : {
     280            0 :     const TextBlock& tline       = curLine();
     281            0 :     const TextFragment* fragment = tline.fragment(static_cast<int>(column()));
     282              : 
     283            0 :     Font _font = fragment ? fragment->font(m_text) : m_text->font();
     284            0 :     if (fragment) {
     285              :         // Ensure the cursor height matches that of the associated text font
     286            0 :         TextLayout::substituteMusicFont(_font, fragment->calculatedFontSize(m_text));
     287              :     }
     288            0 :     const FontMetrics fm(_font);
     289              : 
     290            0 :     double fontCapHeight = fm.capHeight();
     291            0 :     double fontAscent = fm.ascent();
     292              : 
     293              :     // Bravura Text returns small values for its cap heights
     294            0 :     double cursorCapHeight = fontCapHeight > 0 && _font.family().id() != u"Bravura Text"
     295            0 :                              ? std::min(fontCapHeight, fontAscent) : fontAscent;
     296            0 :     double cursorDescent = cursorCapHeight * .3;
     297              : 
     298            0 :     double h = cursorCapHeight + 2 * cursorDescent; // symmetrical cursor
     299            0 :     double w = std::max(2.0, cursorCapHeight / 16.0);
     300            0 :     double x = tline.xpos(column(), m_text);
     301            0 :     double y = tline.y() + cursorDescent - h;
     302            0 :     return RectF(x - w / 2, y, w, h);
     303            0 : }
     304              : 
     305            0 : RectF TextCursor::cursorCanvasRect() const
     306              : {
     307            0 :     return cursorRect().translated(m_text->canvasPos());
     308              : }
     309              : 
     310              : //---------------------------------------------------------
     311              : //   curLine
     312              : //    return the current text line in edit mode
     313              : //---------------------------------------------------------
     314              : 
     315            0 : const TextBlock& TextCursor::curLine() const
     316              : {
     317            0 :     const TextBase::LayoutData* ldata = m_text->ldata();
     318            0 :     IF_ASSERT_FAILED(ldata) {
     319            0 :         static TextBlock dummy;
     320            0 :         return dummy;
     321              :     }
     322              : 
     323            0 :     return ldata->blocks.at(m_row);
     324              : }
     325              : 
     326        31771 : TextBlock& TextCursor::curLine()
     327              : {
     328        31771 :     TextBase::LayoutData* ldata = m_text->mutldata();
     329        31771 :     return ldata->blocks[m_row];
     330              : }
     331              : 
     332              : //---------------------------------------------------------
     333              : //   changeSelectionFormat
     334              : //---------------------------------------------------------
     335              : 
     336        31758 : void TextCursor::changeSelectionFormat(FormatId id, const FormatValue& val)
     337              : {
     338        31758 :     TextBase::LayoutData* ldata = m_text->mutldata();
     339              : 
     340        31758 :     size_t r1 = selectLine();
     341        31758 :     size_t r2 = row();
     342        31758 :     size_t c1 = selectColumn();
     343        31758 :     size_t c2 = column();
     344              : 
     345        31758 :     TextBase::sort(r1, c1, r2, c2);
     346              : 
     347        63590 :     for (size_t row = 0; row < ldata->blocks.size(); ++row) {
     348        31832 :         TextBlock& t = ldata->blocks[row];
     349        31832 :         if (row < r1) {
     350            0 :             continue;
     351              :         }
     352        31832 :         if (row > r2) {
     353            0 :             break;
     354              :         }
     355        31832 :         if (row == r1 && r1 == r2) {
     356        31690 :             t.changeFormat(id, val, static_cast<int>(c1), static_cast<int>(c2 - c1));
     357          142 :         } else if (row == r1) {
     358           68 :             t.changeFormat(id, val, static_cast<int>(c1), static_cast<int>(t.columns() - c1));
     359           74 :         } else if (row == r2) {
     360           68 :             t.changeFormat(id, val, 0, static_cast<int>(c2));
     361              :         } else {
     362            6 :             t.changeFormat(id, val, 0, static_cast<int>(t.columns()));
     363              :         }
     364              :     }
     365              : 
     366        31758 :     m_text->renderer()->layoutText1(m_text);
     367        31758 : }
     368              : 
     369         1705 : const CharFormat TextCursor::selectedFragmentsFormat() const
     370              : {
     371         1705 :     if (!m_text || (!hasSelection() && editing())) {
     372            2 :         return m_format;
     373              :     }
     374              : 
     375         1703 :     const TextBase::LayoutData* ldata = m_text->ldata();
     376         1703 :     IF_ASSERT_FAILED(ldata) {
     377            0 :         return CharFormat();
     378              :     }
     379              : 
     380         1703 :     if (ldata->blocks.empty()) {
     381            0 :         return m_format;
     382              :     }
     383              : 
     384         1703 :     size_t startRow = hasSelection() ? m_selectLine : 0;
     385         1703 :     size_t endRow = hasSelection() ? m_row : ldata->blocks.size() - 1;
     386         1703 :     size_t selectionStartCol = hasSelection() ? m_selectColumn : 0;
     387         1703 :     size_t selectionEndCol = hasSelection() ? m_column : 1; // corrected below at `endColumn
     388              : 
     389         1703 :     TextBase::sort(startRow, selectionStartCol, endRow, selectionEndCol);
     390              : 
     391         1703 :     const TextFragment* tf = ldata->textBlock(static_cast<int>(startRow)).fragment(static_cast<int>(selectionStartCol));
     392         1703 :     CharFormat resultFormat = tf ? tf->format : CharFormat();
     393              : 
     394         1703 :     bool allBlocksEmpty = true;
     395         3410 :     for (size_t row = startRow; row <= endRow; ++row) {
     396         1707 :         const TextBlock& block = ldata->blocks.at(row);
     397              : 
     398         1707 :         if (block.fragments().empty()) {
     399            0 :             continue;
     400              :         }
     401         1707 :         allBlocksEmpty = false;
     402              : 
     403         1707 :         const size_t startColumn = (row == startRow) ? selectionStartCol : 0;
     404         1707 :         const size_t endColumn = (row == endRow && hasSelection()) ? selectionEndCol : block.columns();
     405              : 
     406         1707 :         size_t column = 0;
     407              : 
     408         1707 :         bool isSingleFragment = block.fragments().size() == 1;
     409              : 
     410         3442 :         for (const TextFragment& fragment : block.fragments()) {
     411         1736 :             const size_t fragCols = fragment.columns();
     412              : 
     413         1736 :             if (!isSingleFragment) {
     414           38 :                 if (column + fragCols <= startColumn) {
     415            1 :                     column += fragCols;
     416            1 :                     continue;
     417              :                 }
     418           37 :                 if (column > 0 && column >= endColumn) {
     419            1 :                     break;
     420              :                 }
     421              :             }
     422              : 
     423              :             // proper bitwise 'and' to ensure Bold/Italic/Underline/Strike only true if true for all fragments
     424         1734 :             resultFormat.setStyle(static_cast<FontStyle>(static_cast<int>(resultFormat.style())
     425         1734 :                                                          & static_cast<int>(fragment.format.style())));
     426              : 
     427         1734 :             if (resultFormat.fontFamily() == "ScoreText") {
     428           10 :                 resultFormat.setFontFamily(fragment.format.fontFamily());
     429              :             }
     430         1734 :             if (fragment.format.fontFamily() != "ScoreText" && resultFormat.fontFamily() != fragment.format.fontFamily()) {
     431            0 :                 resultFormat.setFontFamily(TextBase::UNDEFINED_FONT_FAMILY);
     432              :             }
     433              : 
     434         1734 :             if (resultFormat.fontSize() != fragment.format.fontSize()) {
     435            0 :                 resultFormat.setFontSize(TextBase::UNDEFINED_FONT_SIZE);
     436              :             }
     437              : 
     438         1734 :             if (resultFormat.valign() != fragment.format.valign()) {
     439            0 :                 resultFormat.setValign(VerticalAlignment::AlignUndefined);
     440              :             }
     441              : 
     442         1734 :             column += fragCols;
     443              :         }
     444              :     }
     445              : 
     446         1703 :     if (!allBlocksEmpty) {
     447         1703 :         return resultFormat;
     448              :     }
     449              : 
     450            0 :     CharFormat defaultFormat;
     451            0 :     defaultFormat.setStyle(FontStyle(m_text->propertyDefault(Pid::FONT_STYLE).value<int>()));
     452            0 :     defaultFormat.setFontFamily(m_text->propertyDefault(Pid::FONT_FACE).value<String>());
     453            0 :     defaultFormat.setFontSize(m_text->propertyDefault(Pid::FONT_SIZE).toDouble());
     454            0 :     defaultFormat.setValign(VerticalAlignment(m_text->propertyDefault(Pid::TEXT_SCRIPT_ALIGN).toInt()));
     455              : 
     456            0 :     return defaultFormat;
     457         1703 : }
     458              : 
     459              : //---------------------------------------------------------
     460              : //   PointF
     461              : //---------------------------------------------------------
     462              : 
     463       265462 : void TextCursor::setFormat(FormatId id, FormatValue val)
     464              : {
     465       265462 :     if (!hasSelection()) {
     466       265462 :         if (!editing()) {
     467       265454 :             m_text->selectAll();
     468            8 :         } else if (format()->formatValue(id) == val) {
     469            6 :             return;
     470              :         }
     471              :     }
     472       265456 :     format()->setFormatValue(id, val);
     473       265456 :     if (hasSelection()) {
     474        31758 :         changeSelectionFormat(id, val);
     475        31758 :         text()->setTextInvalid();
     476              :     }
     477       265456 :     if (!editing()) {
     478       265454 :         clearSelection();
     479              :     }
     480              : }
     481              : 
     482              : //---------------------------------------------------------
     483              : //   movePosition
     484              : //---------------------------------------------------------
     485              : 
     486            6 : bool TextCursor::movePosition(TextCursor::MoveOperation op, TextCursor::MoveMode mode, int count)
     487              : {
     488           12 :     for (int i = 0; i < count; i++) {
     489            6 :         switch (op) {
     490            0 :         case TextCursor::MoveOperation::Left:
     491            0 :             if (hasSelection() && mode == TextCursor::MoveMode::MoveAnchor) {
     492            0 :                 size_t r1 = m_selectLine;
     493            0 :                 size_t r2 = m_row;
     494            0 :                 size_t c1 = m_selectColumn;
     495            0 :                 size_t c2 = m_column;
     496              : 
     497            0 :                 TextBase::sort(r1, c1, r2, c2);
     498            0 :                 clearSelection();
     499            0 :                 m_row    = r1;
     500            0 :                 m_column = c1;
     501            0 :             } else if (m_column == 0) {
     502            0 :                 if (m_row == 0) {
     503            0 :                     return false;
     504              :                 }
     505            0 :                 --m_row;
     506            0 :                 m_column = curLine().columns();
     507              :             } else {
     508            0 :                 --m_column;
     509              :             }
     510            0 :             break;
     511              : 
     512            0 :         case TextCursor::MoveOperation::Right:
     513            0 :             if (hasSelection() && mode == TextCursor::MoveMode::MoveAnchor) {
     514            0 :                 size_t r1 = m_selectLine;
     515            0 :                 size_t r2 = m_row;
     516            0 :                 size_t c1 = m_selectColumn;
     517            0 :                 size_t c2 = m_column;
     518              : 
     519            0 :                 TextBase::sort(r1, c1, r2, c2);
     520            0 :                 clearSelection();
     521            0 :                 m_row    = r2;
     522            0 :                 m_column = c2;
     523            0 :             } else if (column() >= curLine().columns()) {
     524            0 :                 if (m_row >= m_text->ldata()->rows() - 1) {
     525            0 :                     return false;
     526              :                 }
     527            0 :                 ++m_row;
     528            0 :                 m_column = 0;
     529              :             } else {
     530            0 :                 ++m_column;
     531              :             }
     532            0 :             break;
     533              : 
     534            0 :         case TextCursor::MoveOperation::Up:
     535            0 :             if (m_row == 0) {
     536            0 :                 return false;
     537              :             }
     538            0 :             --m_row;
     539            0 :             if (m_column > curLine().columns()) {
     540            0 :                 m_column = curLine().columns();
     541              :             }
     542              : 
     543            0 :             break;
     544              : 
     545            0 :         case TextCursor::MoveOperation::Down:
     546            0 :             if (m_row >= m_text->ldata()->rows() - 1) {
     547            0 :                 return false;
     548              :             }
     549            0 :             ++m_row;
     550            0 :             if (m_column > curLine().columns()) {
     551            0 :                 m_column = curLine().columns();
     552              :             }
     553              : 
     554            0 :             break;
     555              : 
     556            1 :         case TextCursor::MoveOperation::Start:
     557            1 :             m_row    = 0;
     558            1 :             m_column = 0;
     559              : 
     560            1 :             break;
     561              : 
     562            2 :         case TextCursor::MoveOperation::End:
     563            2 :             m_row    = m_text->ldata()->rows() - 1;
     564            2 :             m_column = curLine().columns();
     565              : 
     566            2 :             break;
     567              : 
     568            0 :         case TextCursor::MoveOperation::StartOfLine:
     569            0 :             m_column = 0;
     570              : 
     571            0 :             break;
     572              : 
     573            0 :         case TextCursor::MoveOperation::EndOfLine:
     574            0 :             m_column = curLine().columns();
     575              : 
     576            0 :             break;
     577              : 
     578            1 :         case TextCursor::MoveOperation::WordLeft:
     579            1 :             if (m_column > 0) {
     580            1 :                 --m_column;
     581            1 :                 while (m_column > 0 && currentCharacter().isSpace()) {
     582            0 :                     --m_column;
     583              :                 }
     584            5 :                 while (m_column > 0 && !currentCharacter().isSpace()) {
     585            4 :                     --m_column;
     586              :                 }
     587            1 :                 if (currentCharacter().isSpace()) {
     588            1 :                     ++m_column;
     589              :                 }
     590              :             }
     591            1 :             break;
     592              : 
     593            2 :         case TextCursor::MoveOperation::NextWord: {
     594            2 :             size_t cols =  columns();
     595            2 :             if (m_column < cols) {
     596            2 :                 ++m_column;
     597           10 :                 while (m_column < cols && !currentCharacter().isSpace()) {
     598            8 :                     ++m_column;
     599              :                 }
     600            3 :                 while (m_column < cols && currentCharacter().isSpace()) {
     601            1 :                     ++m_column;
     602              :                 }
     603              :             }
     604              :         }
     605            2 :         break;
     606              : 
     607            0 :         default:
     608            0 :             LOGD("Text::movePosition: not implemented");
     609            0 :             return false;
     610              :         }
     611            6 :         if (mode == TextCursor::MoveMode::MoveAnchor) {
     612            3 :             clearSelection();
     613              :         }
     614              :     }
     615              : 
     616            6 :     updateCursorFormat();
     617            6 :     m_text->score()->addRefresh(m_text->canvasBoundingRect());
     618              : 
     619            6 :     return true;
     620              : }
     621              : 
     622              : //---------------------------------------------------------
     623              : //   doubleClickSelect
     624              : //---------------------------------------------------------
     625              : 
     626            0 : void TextCursor::selectWord()
     627              : {
     628            0 :     clearSelection();
     629              : 
     630              :     // if clicked on a space, select surrounding spaces
     631              :     // otherwise select surround non-spaces
     632            0 :     const bool selectSpaces = currentCharacter().isSpace();
     633              : 
     634              :     //handle double-clicking inside a word
     635            0 :     size_t startPosition = m_column;
     636              : 
     637            0 :     while (m_column > 0 && currentCharacter().isSpace() == selectSpaces) {
     638            0 :         --m_column;
     639              :     }
     640              : 
     641            0 :     if (currentCharacter().isSpace() != selectSpaces) {
     642            0 :         ++m_column;
     643              :     }
     644              : 
     645            0 :     m_selectColumn = m_column;
     646              : 
     647            0 :     m_column = startPosition;
     648            0 :     while (m_column < curLine().columns() && currentCharacter().isSpace() == selectSpaces) {
     649            0 :         ++m_column;
     650              :     }
     651              : 
     652            0 :     updateCursorFormat();
     653            0 :     m_text->score()->addRefresh(m_text->canvasBoundingRect());
     654            0 : }
     655              : 
     656              : //---------------------------------------------------------
     657              : //   set
     658              : //---------------------------------------------------------
     659              : 
     660            0 : bool TextCursor::set(const PointF& p, TextCursor::MoveMode mode)
     661              : {
     662            0 :     PointF pt  = p - m_text->canvasPos();
     663            0 :     if (!m_text->ldata()->bbox().contains(pt)) {
     664            0 :         return false;
     665              :     }
     666              : 
     667              : //      if (_text->_layout.empty())
     668              : //            _text->_layout.append(TextBlock());
     669            0 :     m_row = 0;
     670              : 
     671            0 :     const TextBase::LayoutData* ldata = m_text->ldata();
     672            0 :     IF_ASSERT_FAILED(ldata) {
     673            0 :         return false;
     674              :     }
     675              : 
     676            0 :     for (size_t row = 0; row < ldata->blocks.size(); ++row) {
     677            0 :         const TextBlock& l = ldata->blocks.at(row);
     678            0 :         if (l.y() > pt.y()) {
     679            0 :             m_row = row;
     680            0 :             break;
     681              :         }
     682              :     }
     683            0 :     m_column = curLine().column(pt.x(), m_text);
     684              : 
     685            0 :     m_text->score()->setUpdateAll();
     686            0 :     if (mode == TextCursor::MoveMode::MoveAnchor) {
     687            0 :         clearSelection();
     688              :     }
     689            0 :     updateCursorFormat();
     690              : 
     691            0 :     return true;
     692              : }
     693              : 
     694              : //---------------------------------------------------------
     695              : //   selectedText
     696              : //    return current selection
     697              : //---------------------------------------------------------
     698              : 
     699            0 : String TextCursor::selectedText(bool withFormat) const
     700              : {
     701            0 :     size_t r1 = selectLine();
     702            0 :     size_t r2 = m_row;
     703            0 :     size_t c1 = selectColumn();
     704            0 :     size_t c2 = column();
     705            0 :     TextBase::sort(r1, c1, r2, c2);
     706            0 :     return extractText(static_cast<int>(r1), static_cast<int>(c1), static_cast<int>(r2), static_cast<int>(c2), withFormat);
     707              : }
     708              : 
     709              : //---------------------------------------------------------
     710              : //   extractText
     711              : //    return text between (r1,c1) and (r2,c2).
     712              : //---------------------------------------------------------
     713              : 
     714            0 : String TextCursor::extractText(int r1, int c1, int r2, int c2, bool withFormat) const
     715              : {
     716            0 :     const TextBase::LayoutData* ldata = m_text->ldata();
     717            0 :     IF_ASSERT_FAILED(ldata) {
     718            0 :         return String();
     719              :     }
     720              : 
     721            0 :     assert(TextBase::isSorted(r1, c1, r2, c2));
     722            0 :     const std::vector<TextBlock>& tb = ldata->blocks;
     723              : 
     724            0 :     if (r1 == r2) {
     725            0 :         return tb.at(r1).text(c1, c2 - c1, m_text, withFormat);
     726              :     }
     727              : 
     728            0 :     String str = tb.at(r1).text(c1, -1, m_text, withFormat) + u"\n";
     729              : 
     730            0 :     for (int r = r1 + 1; r < r2; ++r) {
     731            0 :         str += tb.at(r).text(0, -1, m_text, withFormat) + u"\n";
     732              :     }
     733              : 
     734            0 :     str += tb.at(r2).text(0, c2, m_text, withFormat);
     735            0 :     return str;
     736            0 : }
     737              : 
     738            0 : TextCursor::Range TextCursor::range(int start, int end) const
     739              : {
     740            0 :     const TextBase::LayoutData* ldata = m_text->ldata();
     741            0 :     IF_ASSERT_FAILED(ldata) {
     742            0 :         return Range();
     743              :     }
     744              : 
     745            0 :     String result;
     746            0 :     int pos = 0;
     747            0 :     for (size_t i = 0; i < ldata->blocks.size(); ++i) {
     748            0 :         const TextBlock& t = ldata->blocks.at(i);
     749              : 
     750            0 :         for (size_t j = 0; j < t.columns(); ++j) {
     751            0 :             if (pos > end) {
     752            0 :                 return { start, end, result };
     753              :             }
     754              : 
     755            0 :             if (start < pos) {
     756            0 :                 result += t.text(static_cast<int>(j), 1, m_text);
     757              :             }
     758              : 
     759            0 :             pos++;
     760              :         }
     761              :     }
     762              : 
     763            0 :     return { start, end, result };
     764            0 : }
     765              : 
     766            1 : int TextCursor::position(int row, int column) const
     767              : {
     768            1 :     const TextBase::LayoutData* ldata = m_text->ldata();
     769            1 :     IF_ASSERT_FAILED(ldata) {
     770            0 :         return 0;
     771              :     }
     772              : 
     773            1 :     int result = 0;
     774              : 
     775            1 :     for (int i = 0; i < row; ++i) {
     776            0 :         const TextBlock& t = ldata->blocks.at(i);
     777            0 :         result += static_cast<int>(t.columns());
     778              :     }
     779              : 
     780            1 :     result += column;
     781              : 
     782            1 :     return result;
     783              : }
     784              : 
     785              : //---------------------------------------------------------
     786              : //   TextFragment
     787              : //---------------------------------------------------------
     788        32584 : TextFragment::TextFragment(const String& s)
     789              : {
     790        32584 :     text = s;
     791        32584 : }
     792              : 
     793        32431 : TextFragment::TextFragment(TextCursor* cursor, const String& s)
     794        32431 :     : TextFragment(s)
     795              : {
     796        32431 :     format = *cursor->format();
     797        32431 : }
     798              : 
     799        50776 : TextFragment::TextFragment(const TextFragment& f)
     800              : {
     801        50776 :     text = f.text;
     802        50776 :     format = f.format;
     803        50776 :     pos = f.pos;
     804        50776 : }
     805              : 
     806          552 : TextFragment& TextFragment::operator =(const TextFragment& f)
     807              : {
     808          552 :     text = f.text;
     809          552 :     format = f.format;
     810          552 :     pos = f.pos;
     811          552 :     return *this;
     812              : }
     813              : 
     814              : //---------------------------------------------------------
     815              : //   split
     816              : //---------------------------------------------------------
     817              : 
     818            2 : TextFragment TextFragment::split(int column)
     819              : {
     820            2 :     size_t idx = 0;
     821            2 :     int col = 0;
     822            2 :     TextFragment f;
     823            2 :     f.format = format;
     824              : 
     825            6 :     for (size_t i = 0; i < text.size(); ++i) {
     826            4 :         const Char& c = text.at(i);
     827            4 :         if (col == column) {
     828            0 :             if (idx) {
     829            0 :                 if (idx < text.size()) {
     830            0 :                     f.text = text.mid(idx);
     831            0 :                     text   = text.left(idx);
     832              :                 }
     833              :             }
     834            0 :             return f;
     835              :         }
     836            4 :         ++idx;
     837            4 :         if (c.isHighSurrogate()) {
     838            0 :             continue;
     839              :         }
     840            4 :         ++col;
     841              :     }
     842            2 :     return f;
     843            0 : }
     844              : 
     845              : //---------------------------------------------------------
     846              : //   columns
     847              : //---------------------------------------------------------
     848              : 
     849        33929 : int TextFragment::columns() const
     850              : {
     851        33929 :     int col = 0;
     852       200582 :     for (size_t i = 0; i < text.size(); ++i) {
     853       166653 :         if (text.at(i).isHighSurrogate()) {
     854           96 :             continue;
     855              :         }
     856       166557 :         ++col;
     857              :     }
     858        33929 :     return col;
     859              : }
     860              : 
     861              : //---------------------------------------------------------
     862              : //   operator ==
     863              : //---------------------------------------------------------
     864              : 
     865            0 : bool TextFragment::operator ==(const TextFragment& f) const
     866              : {
     867            0 :     return format == f.format && text == f.text;
     868              : }
     869              : 
     870              : //---------------------------------------------------------
     871              : //   font
     872              : //---------------------------------------------------------
     873              : 
     874       189404 : Font TextFragment::font(const TextBase* t) const
     875              : {
     876       189404 :     Font font;
     877              : 
     878       189404 :     double m = calculatedFontSize(t);
     879              : 
     880       189404 :     String family;
     881       189404 :     Font::Type fontType = Font::Type::Unknown;
     882       189404 :     if (format.fontFamily() == "ScoreText") {
     883         7255 :         if (t->hasSymbolScale()) {
     884         3466 :             std::string fontName = engravingFonts()->fontByName(t->style().styleSt(Sid::musicalSymbolFont).toStdString())->family();
     885         3466 :             family = String::fromStdString(fontName);
     886         3466 :             fontType = Font::Type::MusicSymbol;
     887              : 
     888         3466 :             m = StyleDef::DEFAULT_SMUFL_POINT_SIZE();
     889         3466 :             m *= t->getProperty(Pid::MUSICAL_SYMBOLS_SCALE).toDouble();
     890         3466 :             if (t->sizeIsSpatiumDependent()) {
     891         3466 :                 m *= t->spatiumScaling();
     892              :             }
     893              : 
     894         3466 :             if (t->style().styleB(Sid::dynamicsOverrideFont)) {
     895            0 :                 std::string fontName2 = engravingFonts()->fontByName(t->style().styleSt(Sid::dynamicsFont).toStdString())->family();
     896            0 :                 family = String::fromStdString(fontName2);
     897            0 :             }
     898              : 
     899         3466 :             m *= t->mag();
     900         7255 :         } else if (t->hasSymbolSize()) {
     901         3789 :             family = t->style().styleSt(Sid::musicalTextFont);
     902         3789 :             fontType = Font::Type::MusicSymbolText;
     903         3789 :             m = t->getProperty(Pid::MUSIC_SYMBOL_SIZE).toDouble();
     904         3789 :             if (t->sizeIsSpatiumDependent()) {
     905         3789 :                 m *= t->spatiumScaling();
     906              :             }
     907         3789 :             m *= t->mag();
     908              :         }
     909              :         // check if all symbols are available
     910         7255 :         font.setFamily(family, fontType);
     911         7255 :         font.setNoFontMerging(true);
     912         7255 :         FontMetrics fm(font);
     913              : 
     914         7255 :         resolveFallback(fontType, fm, family);
     915         7255 :     } else {
     916       182149 :         family = format.fontFamily();
     917       182149 :         fontType = Font::Type::Unknown;
     918       182149 :         font.setBold(format.bold());
     919       182149 :         font.setItalic(format.italic());
     920       182149 :         font.setUnderline(format.underline());
     921       182149 :         font.setStrike(format.strike());
     922              :     }
     923              : 
     924       189404 :     font.setFamily(family, fontType);
     925       189404 :     assert(m > 0.0);
     926              : 
     927       189404 :     font.setPointSizeF(m);
     928       189404 :     return font;
     929       189404 : }
     930              : 
     931              : //---------------------------------------------------------
     932              : //   calculatedFontSize
     933              : //---------------------------------------------------------
     934              : 
     935       189534 : double TextFragment::calculatedFontSize(const TextBase* t) const
     936              : {
     937       189534 :     double size = format.fontSize();
     938              : 
     939       189534 :     if (t->sizeIsSpatiumDependent()) {
     940       154525 :         size *= t->spatiumScaling();
     941              :     }
     942       189534 :     if (format.valign() != VerticalAlignment::AlignNormal) {
     943            8 :         size *= SUBSCRIPT_SIZE;
     944              :     }
     945              : 
     946       189534 :     assert(size > 0.0);
     947       189534 :     return size * t->mag();
     948              : }
     949              : 
     950         7255 : void TextFragment::resolveFallback(muse::draw::Font::Type fontType, const muse::draw::FontMetrics& fm,
     951              :                                    String& family) const
     952              : {
     953         7255 :     std::vector<char32_t> missingChars;
     954        16994 :     for (size_t i = 0; i < text.size(); ++i) {
     955         9739 :         const Char& c = text.at(i);
     956         9739 :         if (c.isHighSurrogate()) {
     957            0 :             if (i + 1 == text.size()) {
     958            0 :                 ASSERT_X("bad string");
     959              :             }
     960            0 :             const Char& c2 = text.at(i + 1);
     961            0 :             ++i;
     962            0 :             char32_t v = Char::surrogateToUcs4(c, c2);
     963            0 :             if (!fm.inFont(v)) {
     964            0 :                 missingChars.push_back(v);
     965              :             }
     966              :         } else {
     967         9739 :             if (!fm.inFont(c.unicode())) {
     968         1331 :                 missingChars.push_back(c.unicode());
     969              :             }
     970              :         }
     971              :     }
     972              : 
     973         7255 :     static String fallbackSymbolFontFamily = String::fromUtf8(FALLBACK_SYMBOL_FONT);
     974         7255 :     static String fallbackSymbolTextFontFamily = String::fromUtf8(FALLBACK_SYMBOLTEXT_FONT);
     975         7255 :     static FontMetrics fallbackSymbolFM(Font(fallbackSymbolFontFamily, Font::Type::MusicSymbol));
     976         7255 :     static FontMetrics fallbackSymbolTextFM(Font(fallbackSymbolTextFontFamily, Font::Type::MusicSymbolText));
     977              : 
     978         7255 :     if (fontType == Font::Type::MusicSymbol) {
     979         3466 :         for (char32_t missingChar : missingChars) {
     980          233 :             if (fallbackSymbolFM.inFont(missingChar)) {
     981          233 :                 family = fallbackSymbolFontFamily;
     982          233 :                 return;
     983              :             }
     984              :         }
     985              :     } else {
     986         3857 :         for (char32_t missingChar : missingChars) {
     987         1068 :             if (fallbackSymbolTextFM.inFont(missingChar)) {
     988         1000 :                 family = fallbackSymbolTextFontFamily;
     989         1000 :                 return;
     990              :             }
     991              :         }
     992              :     }
     993         7255 : }
     994              : 
     995              : //---------------------------------------------------------
     996              : 
     997              : //   fragmentsWithoutEmpty
     998              : //---------------------------------------------------------
     999              : 
    1000            0 : std::list<TextFragment> TextBlock::fragmentsWithoutEmpty()
    1001              : {
    1002            0 :     std::list<TextFragment> list;
    1003            0 :     for (const auto& x : m_fragments) {
    1004            0 :         if (!x.text.isEmpty()) {
    1005            0 :             list.push_back(x);
    1006              :         }
    1007              :     }
    1008              : 
    1009            0 :     return list;
    1010            0 : }
    1011              : 
    1012              : //---------------------------------------------------------
    1013              : //   xpos
    1014              : //---------------------------------------------------------
    1015              : 
    1016          630 : double TextBlock::xpos(size_t column, const TextBase* t) const
    1017              : {
    1018          630 :     size_t col = 0;
    1019          642 :     for (const TextFragment& f : m_fragments) {
    1020          640 :         if (column == col) {
    1021          628 :             return f.pos.x();
    1022              :         }
    1023          355 :         FontMetrics fm(f.font(t));
    1024          355 :         size_t idx = 0;
    1025         2938 :         for (size_t i = 0; i < f.text.size(); ++i) {
    1026         2926 :             ++idx;
    1027         2926 :             if (f.text.at(i).isHighSurrogate()) {
    1028            4 :                 continue;
    1029              :             }
    1030         2922 :             ++col;
    1031         2922 :             if (column == col) {
    1032          343 :                 return f.pos.x() + fm.width(f.text.left(idx));
    1033              :             }
    1034              :         }
    1035          355 :     }
    1036            2 :     return m_shape.bbox().x();
    1037              : }
    1038              : 
    1039              : //---------------------------------------------------------
    1040              : //   fragment
    1041              : //---------------------------------------------------------
    1042              : 
    1043         1714 : const TextFragment* TextBlock::fragment(int column) const
    1044              : {
    1045         1714 :     if (m_fragments.empty()) {
    1046            1 :         return nullptr;
    1047              :     }
    1048         1713 :     int col = 0;
    1049         1713 :     auto f = m_fragments.begin();
    1050         1714 :     for (; f != m_fragments.end(); ++f) {
    1051         1739 :         for (size_t i = 0; i < f->text.size(); ++i) {
    1052         1738 :             if (f->text.at(i).isHighSurrogate()) {
    1053            0 :                 continue;
    1054              :             }
    1055         1738 :             if (column == col) {
    1056         1713 :                 return &*f;
    1057              :             }
    1058           25 :             ++col;
    1059              :         }
    1060              :     }
    1061            0 :     if (column == col) {
    1062            0 :         return &*(std::prev(f));
    1063              :     }
    1064            0 :     return 0;
    1065              : }
    1066              : 
    1067              : //---------------------------------------------------------
    1068              : //   formatAt
    1069              : //---------------------------------------------------------
    1070              : 
    1071           11 : const CharFormat* TextBlock::formatAt(int column) const
    1072              : {
    1073           11 :     const TextFragment* f = fragment(column);
    1074           11 :     if (f) {
    1075           10 :         return &(f->format);
    1076              :     }
    1077            1 :     return 0;
    1078              : }
    1079              : 
    1080              : //---------------------------------------------------------
    1081              : //   boundingRect
    1082              : //---------------------------------------------------------
    1083              : 
    1084            0 : RectF TextBlock::boundingRect(int col1, int col2, const TextBase* t) const
    1085              : {
    1086            0 :     double x1 = xpos(col1, t);
    1087            0 :     double x2 = xpos(col2, t);
    1088            0 :     const RectF& bbox = m_shape.bbox();
    1089            0 :     return RectF(x1, bbox.y(), x2 - x1, bbox.height());
    1090              : }
    1091              : 
    1092              : //---------------------------------------------------------
    1093              : //   columns
    1094              : //---------------------------------------------------------
    1095              : 
    1096        33546 : size_t TextBlock::columns() const
    1097              : {
    1098        33546 :     size_t col = 0;
    1099        67150 :     for (const TextFragment& f : m_fragments) {
    1100       199454 :         for (size_t i = 0; i < f.text.size(); ++i) {
    1101       165850 :             if (!f.text.at(i).isHighSurrogate()) {
    1102       165754 :                 ++col;
    1103              :             }
    1104              :         }
    1105              :     }
    1106        33546 :     return col;
    1107              : }
    1108              : 
    1109              : //---------------------------------------------------------
    1110              : //   column
    1111              : //    Return nearest column for position x. X is in
    1112              : //    Text coordinate system
    1113              : //---------------------------------------------------------
    1114              : 
    1115            0 : int TextBlock::column(double x, TextBase* t) const
    1116              : {
    1117            0 :     int col = 0;
    1118            0 :     for (const TextFragment& f : m_fragments) {
    1119            0 :         int idx = 0;
    1120            0 :         if (x <= f.pos.x()) {
    1121            0 :             return col;
    1122              :         }
    1123            0 :         double px = 0.0;
    1124            0 :         for (size_t i = 0; i < f.text.size(); ++i) {
    1125            0 :             ++idx;
    1126            0 :             if (f.text.at(i).isHighSurrogate()) {
    1127            0 :                 continue;
    1128              :             }
    1129            0 :             FontMetrics fm(f.font(t));
    1130            0 :             double xo = fm.width(f.text.left(idx));
    1131            0 :             if (x <= f.pos.x() + px + (xo - px) * .5) {
    1132            0 :                 return col;
    1133              :             }
    1134            0 :             ++col;
    1135            0 :             px = xo;
    1136            0 :         }
    1137              :     }
    1138            0 :     return static_cast<int>(this->columns());
    1139              : }
    1140              : 
    1141              : //---------------------------------------------------------
    1142              : //   insert
    1143              : //---------------------------------------------------------
    1144              : 
    1145       177603 : void TextBlock::insert(TextCursor* cursor, const String& s)
    1146              : {
    1147              :     int rcol, ridx;
    1148       177603 :     removeEmptyFragment();   // since we are going to write text, we don't need an empty fragment to hold format info. if such exists, delete it
    1149       177603 :     auto i = fragment(static_cast<int>(cursor->column()), &rcol, &ridx);
    1150       177603 :     if (i != m_fragments.end()) {
    1151            2 :         if (!(i->format == *cursor->format())) {
    1152            2 :             if (rcol == 0) {
    1153            2 :                 m_fragments.insert(i, TextFragment(cursor, s));
    1154              :             } else {
    1155            0 :                 TextFragment f2 = i->split(rcol);
    1156            0 :                 i = m_fragments.insert(std::next(i), TextFragment(cursor, s));
    1157            0 :                 m_fragments.insert(std::next(i), f2);
    1158            0 :             }
    1159              :         } else {
    1160            0 :             i->text.insert(ridx, s);
    1161              :         }
    1162              :     } else {
    1163       177601 :         if (!m_fragments.empty() && m_fragments.back().format == *cursor->format()) {
    1164       145478 :             m_fragments.back().text.append(s);
    1165              :         } else {
    1166        32123 :             m_fragments.push_back(TextFragment(cursor, s));
    1167              :         }
    1168              :     }
    1169       177603 : }
    1170              : 
    1171              : //---------------------------------------------------------
    1172              : //
    1173              : //   insertEmptyFragmentIfNeeded
    1174              : //   used to insert an empty TextFragment in TextBlocks that have none
    1175              : //   that way, the formatting information (most importantly the font size) of the line is preserved
    1176              : //
    1177              : //---------------------------------------------------------
    1178              : 
    1179          306 : void TextBlock::insertEmptyFragmentIfNeeded(TextCursor* cursor)
    1180              : {
    1181          306 :     if (m_fragments.size() == 0 || m_fragments.front().text.isEmpty()) {
    1182          306 :         m_fragments.insert(m_fragments.begin(), TextFragment(cursor, u""));
    1183              :     }
    1184          306 : }
    1185              : 
    1186              : //---------------------------------------------------------
    1187              : //   removeEmptyFragment
    1188              : //---------------------------------------------------------
    1189              : 
    1190       177603 : void TextBlock::removeEmptyFragment()
    1191              : {
    1192       177603 :     if (m_fragments.size() > 0 && m_fragments.front().text.isEmpty()) {
    1193          213 :         m_fragments.pop_back();
    1194              :     }
    1195       177603 : }
    1196              : 
    1197              : //---------------------------------------------------------
    1198              : //   fragment
    1199              : //    inputs:
    1200              : //      column is the column relative to the start of the TextBlock.
    1201              : //    outputs:
    1202              : //      rcol will be the column relative to the start of the TextFragment that the input column is in.
    1203              : //      ridx will be the QChar index into TextFragment's text String relative to the start of that TextFragment.
    1204              : //
    1205              : //---------------------------------------------------------
    1206              : 
    1207       177603 : std::list<TextFragment>::iterator TextBlock::fragment(int column, int* rcol, int* ridx)
    1208              : {
    1209       177603 :     int col = 0;
    1210       330061 :     for (auto it = m_fragments.begin(); it != m_fragments.end(); ++it) {
    1211       152460 :         *rcol = 0;
    1212       152460 :         *ridx = 0;
    1213      1332698 :         for (size_t i = 0; i < it->text.size(); ++i) {
    1214      1180240 :             if (col == column) {
    1215            2 :                 return it;
    1216              :             }
    1217      1180238 :             ++*ridx;
    1218      1180238 :             if (it->text.at(i).isHighSurrogate()) {
    1219          936 :                 continue;
    1220              :             }
    1221      1179302 :             ++col;
    1222      1179302 :             ++*rcol;
    1223              :         }
    1224              :     }
    1225       177601 :     return m_fragments.end();
    1226              : }
    1227              : 
    1228              : //---------------------------------------------------------
    1229              : //   remove
    1230              : //---------------------------------------------------------
    1231              : 
    1232            0 : String TextBlock::remove(int column, TextCursor* cursor)
    1233              : {
    1234            0 :     int col = 0;
    1235            0 :     String s;
    1236            0 :     for (auto it = m_fragments.begin(); it != m_fragments.end(); ++it) {
    1237            0 :         size_t idx = 0;
    1238              : 
    1239            0 :         for (size_t i = 0; i < it->text.size(); ++i) {
    1240            0 :             if (col == column) {
    1241            0 :                 if (it->text.at(i).isSurrogate()) {
    1242            0 :                     s = it->text.mid(idx, 2);
    1243            0 :                     it->text.remove(idx, 2);
    1244              :                 } else {
    1245            0 :                     s = it->text.mid(idx, 1);
    1246            0 :                     it->text.remove(idx, 1);
    1247              :                 }
    1248            0 :                 if (it->text.isEmpty()) {
    1249            0 :                     m_fragments.erase(it);
    1250              :                 }
    1251            0 :                 simplify();
    1252            0 :                 insertEmptyFragmentIfNeeded(cursor);         // without this, cursorRect can't calculate the y position of the cursor correctly
    1253            0 :                 return s;
    1254              :             }
    1255            0 :             ++idx;
    1256            0 :             if (it->text.at(i).isHighSurrogate()) {
    1257            0 :                 continue;
    1258              :             }
    1259            0 :             ++col;
    1260              :         }
    1261              :     }
    1262            0 :     insertEmptyFragmentIfNeeded(cursor);   // without this, cursorRect can't calculate the y position of the cursor correctly
    1263            0 :     return s;
    1264              : //      LOGD("TextBlock::remove: column %d not found", column);
    1265            0 : }
    1266              : 
    1267              : //---------------------------------------------------------
    1268              : //   simplify
    1269              : //---------------------------------------------------------
    1270              : 
    1271            0 : void TextBlock::simplify()
    1272              : {
    1273            0 :     if (m_fragments.size() < 2) {
    1274            0 :         return;
    1275              :     }
    1276            0 :     auto i = m_fragments.begin();
    1277            0 :     TextFragment* f = &*i;
    1278            0 :     ++i;
    1279            0 :     for (; i != m_fragments.end(); ++i) {
    1280            0 :         while (i != m_fragments.end() && (i->format == f->format)) {
    1281            0 :             f->text.append(i->text);
    1282            0 :             i = m_fragments.erase(i);
    1283              :         }
    1284            0 :         if (i == m_fragments.end()) {
    1285            0 :             break;
    1286              :         }
    1287            0 :         f = &*i;
    1288              :     }
    1289              : }
    1290              : 
    1291              : //---------------------------------------------------------
    1292              : //   remove
    1293              : //---------------------------------------------------------
    1294              : 
    1295            0 : String TextBlock::remove(int start, int n, TextCursor* cursor)
    1296              : {
    1297            0 :     if (n == 0) {
    1298            0 :         return String();
    1299              :     }
    1300            0 :     int col = 0;
    1301            0 :     String s;
    1302            0 :     for (auto i = m_fragments.begin(); i != m_fragments.end();) {
    1303            0 :         bool inc = true;
    1304            0 :         for (size_t idx = 0; idx < i->text.size();) {
    1305            0 :             Char c = i->text.at(idx);
    1306            0 :             if (col == start) {
    1307            0 :                 if (c.isHighSurrogate()) {
    1308            0 :                     s += c;
    1309            0 :                     i->text.remove(idx, 1);
    1310            0 :                     c = i->text.at(idx);
    1311              :                 }
    1312            0 :                 s += c;
    1313            0 :                 i->text.remove(idx, 1);
    1314            0 :                 if (i->text.isEmpty() && (m_fragments.size() > 1)) {
    1315            0 :                     i = m_fragments.erase(i);
    1316            0 :                     inc = false;
    1317              :                 }
    1318            0 :                 --n;
    1319            0 :                 if (n == 0) {
    1320            0 :                     insertEmptyFragmentIfNeeded(cursor);           // without this, cursorRect can't calculate the y position of the cursor correctly
    1321            0 :                     return s;
    1322              :                 }
    1323            0 :                 continue;
    1324              :             }
    1325            0 :             ++idx;
    1326            0 :             if (c.isHighSurrogate()) {
    1327            0 :                 continue;
    1328              :             }
    1329            0 :             ++col;
    1330              :         }
    1331            0 :         if (inc) {
    1332            0 :             ++i;
    1333              :         }
    1334              :     }
    1335            0 :     insertEmptyFragmentIfNeeded(cursor);   // without this, cursorRect can't calculate the y position of the cursor correctly
    1336            0 :     return s;
    1337            0 : }
    1338              : 
    1339              : //---------------------------------------------------------
    1340              : //   changeFormat
    1341              : //---------------------------------------------------------
    1342              : 
    1343        31832 : void TextBlock::changeFormat(FormatId id, const FormatValue& data, int start, int n)
    1344              : {
    1345        31832 :     int col = 0;
    1346        63695 :     for (auto i = m_fragments.begin(); i != m_fragments.end(); ++i) {
    1347        31867 :         int columns = i->columns();
    1348        31867 :         if (start + n <= col) {
    1349            4 :             if (columns == 0) {
    1350              :                 // still apply the format change. Otherwise we have deviating formats for e. g. empty lines
    1351              :                 // otherwise we have Issue #19571
    1352            4 :                 i->changeFormat(id, data);
    1353              :             }
    1354            4 :             break;
    1355              :         }
    1356        31863 :         if (start >= col + columns) {
    1357            0 :             col += i->columns();
    1358            0 :             continue;
    1359              :         }
    1360        31863 :         int endCol = col + columns;
    1361              : 
    1362        31863 :         if ((start <= col) && (start < endCol) && ((start + n) < endCol)) {
    1363              :             // left
    1364            0 :             TextFragment f = i->split(start + n - col);
    1365            0 :             i->changeFormat(id, data);
    1366            0 :             i = m_fragments.insert(std::next(i), f);
    1367        31863 :         } else if (start > col && ((start + n) < endCol)) {
    1368              :             // middle
    1369            0 :             TextFragment lf = i->split(start + n - col);
    1370            0 :             TextFragment mf = i->split(start - col);
    1371            0 :             mf.changeFormat(id, data);
    1372            0 :             i = m_fragments.insert(std::next(i), mf);
    1373            0 :             i = m_fragments.insert(std::next(i), lf);
    1374        31863 :         } else if (start > col) {
    1375              :             // right
    1376            0 :             TextFragment f = i->split(start - col);
    1377            0 :             f.changeFormat(id, data);
    1378            0 :             i = m_fragments.insert(std::next(i), f);
    1379            0 :         } else {
    1380        31863 :             if (id == FormatId::FontFamily && i->format.fontFamily() == "ScoreText") {
    1381              :                 void(0);// do nothing, we need to leave that as is
    1382              :             } else {
    1383              :                 // complete fragment
    1384        31852 :                 i->changeFormat(id, data);
    1385              :             }
    1386              :         }
    1387        31863 :         col = endCol;
    1388              :     }
    1389        31832 : }
    1390              : 
    1391              : //---------------------------------------------------------
    1392              : //   formatValue
    1393              : //---------------------------------------------------------
    1394            8 : FormatValue CharFormat::formatValue(FormatId id) const
    1395              : {
    1396            8 :     switch (id) {
    1397            2 :     case FormatId::Bold: return bold();
    1398            2 :     case FormatId::Italic: return italic();
    1399            2 :     case FormatId::Underline: return underline();
    1400            2 :     case FormatId::Strike: return strike();
    1401            0 :     case FormatId::Valign: return static_cast<int>(valign());
    1402            0 :     case FormatId::FontSize: return fontSize();
    1403            0 :     case FormatId::FontFamily: return fontFamily();
    1404              :     }
    1405              : 
    1406            0 :     return FormatValue();
    1407              : }
    1408              : 
    1409              : //---------------------------------------------------------
    1410              : //   setFormatValue
    1411              : //---------------------------------------------------------
    1412              : 
    1413       297312 : void CharFormat::setFormatValue(FormatId id, const FormatValue& val)
    1414              : {
    1415       297312 :     switch (id) {
    1416        49548 :     case FormatId::Bold:
    1417        49548 :         setBold(std::get<bool>(val));
    1418        49548 :         break;
    1419        49541 :     case FormatId::Italic:
    1420        49541 :         setItalic(std::get<bool>(val));
    1421        49541 :         break;
    1422        49540 :     case FormatId::Underline:
    1423        49540 :         setUnderline(std::get<bool>(val));
    1424        49540 :         break;
    1425        49526 :     case FormatId::Strike:
    1426        49526 :         setStrike(std::get<bool>(val));
    1427        49526 :         break;
    1428            0 :     case FormatId::Valign:
    1429            0 :         m_valign = static_cast<VerticalAlignment>(std::get<int>(val));
    1430            0 :         break;
    1431        49645 :     case FormatId::FontSize:
    1432        49645 :         m_fontSize = std::get<double>(val);
    1433        49645 :         break;
    1434        49512 :     case FormatId::FontFamily:
    1435        49512 :         m_fontFamily = std::get<String>(val);
    1436        49512 :         break;
    1437              :     }
    1438       297312 : }
    1439              : 
    1440              : //---------------------------------------------------------
    1441              : //   changeFormat
    1442              : //---------------------------------------------------------
    1443              : 
    1444        31856 : void TextFragment::changeFormat(FormatId id, const FormatValue& data)
    1445              : {
    1446        31856 :     format.setFormatValue(id, data);
    1447        31856 : }
    1448              : 
    1449              : //---------------------------------------------------------
    1450              : //   split
    1451              : //---------------------------------------------------------
    1452              : 
    1453            0 : TextBlock TextBlock::split(int column, TextCursor* cursor)
    1454              : {
    1455            0 :     TextBlock tl;
    1456              : 
    1457            0 :     int col = 0;
    1458            0 :     for (auto it = m_fragments.begin(); it != m_fragments.end(); ++it) {
    1459            0 :         size_t idx = 0;
    1460            0 :         for (size_t i = 0; i < it->text.size(); ++i) {
    1461            0 :             if (col == column) {
    1462            0 :                 if (idx) {
    1463            0 :                     if (idx < it->text.size()) {
    1464            0 :                         TextFragment tf(it->text.mid(idx));
    1465            0 :                         tf.format = it->format;
    1466            0 :                         tl.m_fragments.push_back(tf);
    1467            0 :                         it->text = it->text.left(idx);
    1468            0 :                         ++it;
    1469            0 :                     }
    1470              :                 }
    1471            0 :                 for (; it != m_fragments.end(); it = m_fragments.erase(it)) {
    1472            0 :                     tl.m_fragments.push_back(*it);
    1473              :                 }
    1474              : 
    1475            0 :                 if (m_fragments.size() == 0) {
    1476            0 :                     insertEmptyFragmentIfNeeded(cursor);
    1477              :                 }
    1478            0 :                 return tl;
    1479              :             }
    1480            0 :             ++idx;
    1481            0 :             if (it->text.at(i).isHighSurrogate()) {
    1482            0 :                 continue;
    1483              :             }
    1484            0 :             ++col;
    1485              :         }
    1486              :     }
    1487              : 
    1488            0 :     TextFragment tf(u"");
    1489            0 :     if (m_fragments.size() > 0) {
    1490            0 :         tf.format = m_fragments.back().format;
    1491            0 :     } else if (m_fragments.size() == 0) {
    1492            0 :         insertEmptyFragmentIfNeeded(cursor);
    1493              :     }
    1494              : 
    1495            0 :     tl.m_fragments.push_back(tf);
    1496            0 :     return tl;
    1497            0 : }
    1498              : 
    1499           66 : static String toSymbolXml(Char c, const TextBase* t)
    1500              : {
    1501           66 :     static std::shared_ptr<IEngravingFontsProvider> provider = muse::modularity::globalIoc()->resolve<IEngravingFontsProvider>("engraving");
    1502              : 
    1503           66 :     SymId symId = t->score()->engravingFont()->fromCode(c.unicode());
    1504           66 :     if (symId == SymId::noSym) {
    1505            0 :         symId = provider->fallbackFont()->fromCode(c.unicode());
    1506              :     }
    1507           66 :     return u"<sym>" + String::fromAscii(SymNames::nameForSymId(symId).ascii()) + u"</sym>";
    1508              : }
    1509              : 
    1510              : //---------------------------------------------------------
    1511              : //   text
    1512              : //    extract text, symbols are marked with <sym>xxx</sym>
    1513              : //---------------------------------------------------------
    1514              : 
    1515           18 : String TextBlock::text(int col1, int len, const TextBase* t, bool withFormat) const
    1516              : {
    1517           18 :     String s;
    1518           18 :     int col = 0;
    1519              :     double size;
    1520           18 :     String family;
    1521           54 :     for (const auto& f : m_fragments) {
    1522           36 :         if (f.text.isEmpty()) {
    1523            0 :             continue;
    1524              :         }
    1525           36 :         if (withFormat) {
    1526            0 :             s += TextBase::getHtmlStartTag(f.format.fontSize(), size, f.format.fontFamily(), family, f.format.style(), f.format.valign());
    1527              :         }
    1528              : 
    1529          234 :         for (size_t i = 0; i < f.text.size(); ++i) {
    1530          198 :             Char c = f.text.at(i);
    1531          198 :             if (col >= col1 && (len < 0 || ((col - col1) < len))) {
    1532           18 :                 if (f.format.fontFamily() == "ScoreText" && withFormat) {
    1533            0 :                     s += toSymbolXml(c, t);
    1534              :                 } else {
    1535           18 :                     s += String::toXmlEscaped(c.unicode());
    1536              :                 }
    1537              :             }
    1538          198 :             if (!c.isHighSurrogate()) {
    1539          198 :                 ++col;
    1540              :             }
    1541              :         }
    1542           36 :         if (withFormat) {
    1543            0 :             s += TextBase::getHtmlEndTag(f.format.style(), f.format.valign());
    1544              :         }
    1545              :     }
    1546           18 :     return s;
    1547           18 : }
    1548              : 
    1549              : //---------------------------------------------------------
    1550              : //   Text
    1551              : //---------------------------------------------------------
    1552              : 
    1553        28330 : TextBase::TextBase(const ElementType& type, EngravingObject* parent, TextStyleType tid, ElementFlags f)
    1554        28330 :     : EngravingItem(type, parent, f | ElementFlag::MOVABLE)
    1555              : {
    1556        28330 :     m_textLineSpacing        = 1.0;
    1557        28330 :     m_textStyleType          = tid;
    1558        28330 :     m_bgColor                = Color::transparent;
    1559        28330 :     m_frameColor             = Color::BLACK;
    1560        28330 :     m_align                  = { AlignH::LEFT, AlignV::TOP };
    1561        28330 :     m_frameType              = FrameType::NO_FRAME;
    1562        28330 :     m_frameWidth             = 0.1_sp;
    1563        28330 :     m_paddingWidth           = 0.2_sp;
    1564        28330 :     m_frameRound             = 0_sp;
    1565              : 
    1566        28330 :     m_cursor                 = new TextCursor(this);
    1567        28330 :     m_cursor->init();
    1568        28330 : }
    1569              : 
    1570          100 : TextBase::TextBase(const ElementType& type, EngravingObject* parent, ElementFlags f)
    1571          100 :     : TextBase(type, parent, TextStyleType::DEFAULT, f)
    1572              : {
    1573          100 : }
    1574              : 
    1575         2388 : TextBase::TextBase(const TextBase& st)
    1576         2388 :     : EngravingItem(st)
    1577              : {
    1578         2388 :     m_cursor                      = new TextCursor(this);
    1579         2388 :     m_cursor->setFormat(*(st.cursor()->format()));
    1580         2388 :     m_text                        = st.m_text;
    1581         2388 :     m_textInvalid                  = st.m_textInvalid;
    1582         2388 :     m_layoutToParentWidth         = st.m_layoutToParentWidth;
    1583         2388 :     m_hexState                     = -1;
    1584              : 
    1585         2388 :     m_textStyleType               = st.m_textStyleType;
    1586         2388 :     m_textLineSpacing             = st.m_textLineSpacing;
    1587         2388 :     m_bgColor                     = st.m_bgColor;
    1588         2388 :     m_frameColor                  = st.m_frameColor;
    1589         2388 :     m_align                       = st.m_align;
    1590         2388 :     m_frameType                   = st.m_frameType;
    1591         2388 :     m_frameWidth                  = st.m_frameWidth;
    1592         2388 :     m_paddingWidth                = st.m_paddingWidth;
    1593         2388 :     m_frameRound                  = st.m_frameRound;
    1594         2388 :     m_position                    = st.m_position;
    1595         2388 :     m_symbolSize                  = st.m_symbolSize;
    1596         2388 :     m_symbolScale                 = st.m_symbolScale;
    1597              : 
    1598         2388 :     m_voiceAssignment = st.m_voiceAssignment;
    1599         2388 :     m_direction = st.m_direction;
    1600         2388 :     m_centerBetweenStaves = st.m_centerBetweenStaves;
    1601         2388 :     m_anchorToEndOfPrevious = st.m_anchorToEndOfPrevious;
    1602              : 
    1603         2388 :     size_t n = m_elementStyle->size() + TEXT_STYLE_SIZE;
    1604         2388 :     delete[] m_propertyFlagsList;
    1605         2388 :     m_propertyFlagsList = new PropertyFlags[n];
    1606        45784 :     for (size_t i = 0; i < n; ++i) {
    1607        43396 :         m_propertyFlagsList[i] = st.m_propertyFlagsList[i];
    1608              :     }
    1609         2388 :     m_links = 0;
    1610         2388 : }
    1611              : 
    1612        29311 : TextBase::~TextBase()
    1613              : {
    1614        29311 :     delete m_cursor;
    1615        29311 : }
    1616              : 
    1617              : //---------------------------------------------------------
    1618              : //   textColor
    1619              : //---------------------------------------------------------
    1620              : 
    1621            0 : Color TextBase::textColor(const rendering::PaintOptions& opt) const
    1622              : {
    1623            0 :     return curColor(opt);
    1624              : }
    1625              : 
    1626              : //---------------------------------------------------------
    1627              : //   insert
    1628              : //    insert character
    1629              : //---------------------------------------------------------
    1630              : 
    1631       177598 : void TextBase::insert(TextCursor* cursor, char32_t code, LayoutData* ldata) const
    1632              : {
    1633       177598 :     if (cursor->row() >= ldata->blocks.size()) {
    1634        30635 :         ldata->blocks.push_back(TextBlock());
    1635              :     }
    1636       177598 :     if (code == '\t') {
    1637            0 :         code = ' ';
    1638              :     }
    1639              : 
    1640       177598 :     if (cursor->row() < ldata->blocks.size()) {
    1641       177598 :         ldata->blocks[cursor->row()].insert(cursor, String::fromUcs4(code));
    1642              :     }
    1643              : 
    1644       177598 :     cursor->setColumn(cursor->column() + 1);
    1645       177598 :     cursor->clearSelection();
    1646       177598 : }
    1647              : 
    1648              : //---------------------------------------------------------
    1649              : //   parseStringProperty
    1650              : //---------------------------------------------------------
    1651              : 
    1652          811 : static String parseStringProperty(const String& s)
    1653              : {
    1654          811 :     String rs;
    1655         4099 :     for (size_t i = 0; i < s.size(); ++i) {
    1656         4099 :         Char c = s.at(i);
    1657         4099 :         if (c == '"') {
    1658          811 :             break;
    1659              :         }
    1660         3288 :         rs += c;
    1661              :     }
    1662          811 :     return rs;
    1663            0 : }
    1664              : 
    1665              : //---------------------------------------------------------
    1666              : //   parseNumProperty
    1667              : //---------------------------------------------------------
    1668              : 
    1669          567 : static double parseNumProperty(const String& s)
    1670              : {
    1671          567 :     return parseStringProperty(s).toDouble();
    1672              : }
    1673              : 
    1674              : //---------------------------------------------------------
    1675              : //   createLayout
    1676              : //    create layout from text
    1677              : //---------------------------------------------------------
    1678        12944 : void TextBase::createBlocks()
    1679              : {
    1680        12944 :     createBlocks(mutldata());
    1681        12944 : }
    1682              : 
    1683        37827 : void TextBase::createBlocks(LayoutData* ldata) const
    1684              : {
    1685              :     // reset all previous formatting information
    1686        37827 :     ldata->blocks.clear();
    1687        37827 :     TextCursor cursor = *m_cursor;
    1688        37827 :     cursor.setRow(0);
    1689        37827 :     cursor.setColumn(0);
    1690              : 
    1691        37827 :     int state = 0;
    1692        37827 :     String token;
    1693        37827 :     String sym;
    1694        37827 :     bool symState = false;
    1695       312856 :     for (size_t i = 0; i < m_text.size(); i++) {
    1696       275029 :         const Char& c = m_text.at(i);
    1697       275029 :         if (state == 0) {
    1698       224570 :             if (c == '<') {
    1699         8386 :                 state = 1;
    1700         8386 :                 token.clear();
    1701       216184 :             } else if (c == '&') {
    1702           72 :                 state = 2;
    1703           72 :                 token.clear();
    1704       216112 :             } else if (c == '\n') {
    1705          294 :                 if (ldata->rows() <= cursor.row()) {
    1706           12 :                     ldata->blocks.push_back(TextBlock());
    1707              :                 }
    1708              : 
    1709          294 :                 if (cursor.row() < ldata->rows()) {
    1710          294 :                     if (ldata->blocks.at(cursor.row()).fragments().size() == 0) {
    1711           12 :                         ldata->blocks[cursor.row()].insertEmptyFragmentIfNeeded(&cursor);           // used to preserve the Font size of the line (font info is held in TextFragments, see PR #5881)
    1712              :                     }
    1713              : 
    1714          294 :                     ldata->blocks[cursor.row()].setEol(true);
    1715              :                 }
    1716              : 
    1717          294 :                 cursor.setRow(cursor.row() + 1);
    1718          294 :                 cursor.setColumn(0);
    1719          294 :                 if (ldata->rows() <= cursor.row()) {
    1720          294 :                     ldata->blocks.push_back(TextBlock());
    1721              :                 }
    1722              : 
    1723          294 :                 if (cursor.row() < ldata->rows()) {
    1724          294 :                     if (ldata->blocks.at(cursor.row()).fragments().size() == 0) {
    1725          294 :                         ldata->blocks[cursor.row()].insertEmptyFragmentIfNeeded(&cursor); // an empty fragment may be needed on either side of the newline
    1726              :                     }
    1727              :                 }
    1728              :             } else {
    1729       215818 :                 if (symState) {
    1730        41558 :                     sym += c;
    1731              :                 } else {
    1732       174260 :                     if (c.isHighSurrogate()) {
    1733          161 :                         i++;
    1734          161 :                         assert(i < m_text.size());
    1735          161 :                         insert(&cursor, Char::surrogateToUcs4(c, m_text.at(i)), ldata);
    1736              :                     } else {
    1737       174099 :                         insert(&cursor, c.unicode(), ldata);
    1738              :                     }
    1739              :                 }
    1740              :             }
    1741        50459 :         } else if (state == 1) {
    1742        50172 :             if (c == '>') {
    1743         8386 :                 state = 0;
    1744         8386 :                 const_cast<TextBase*>(this)->prepareFormat(token, cursor);
    1745         8386 :                 if (token == "sym") {
    1746         3270 :                     symState = true;
    1747         3270 :                     sym.clear();
    1748         5116 :                 } else if (token == "/sym") {
    1749         3266 :                     symState = false;
    1750         3266 :                     SymId id = SymNames::symIdByName(sym);
    1751         3266 :                     if (id != SymId::noSym) {
    1752         3266 :                         CharFormat fmt = *cursor.format(); // save format
    1753              : 
    1754         3266 :                         const std::shared_ptr<IEngravingFont> scoreFont = score()->engravingFont();
    1755         3266 :                         char32_t code = static_cast<char32_t>(' ');
    1756         3266 :                         if (id != SymId::space) {
    1757         3239 :                             code = scoreFont->isValid(id) ? scoreFont->symCode(id) : score()->engravingFonts()->fallbackFont()->symCode(id);
    1758              :                         }
    1759         3266 :                         cursor.format()->setFontFamily(u"ScoreText");
    1760         3266 :                         insert(&cursor, code, ldata);
    1761         3266 :                         cursor.setFormat(fmt); // restore format
    1762         3266 :                     } else {
    1763            0 :                         LOGD("unknown symbol <%s>", muPrintable(sym));
    1764              :                     }
    1765              :                 }
    1766              :             } else {
    1767        41786 :                 token += c;
    1768              :             }
    1769          287 :         } else if (state == 2) {
    1770          287 :             if (c == ';') {
    1771           72 :                 state = 0;
    1772           72 :                 if (token == "lt") {
    1773            9 :                     insert(&cursor, '<', ldata);
    1774           63 :                 } else if (token == "gt") {
    1775           13 :                     insert(&cursor, '>', ldata);
    1776           50 :                 } else if (token == "amp") {
    1777           29 :                     insert(&cursor, '&', ldata);
    1778           21 :                 } else if (token == "quot") {
    1779           21 :                     insert(&cursor, '"', ldata);
    1780              :                 } else {
    1781              :                     // TODO insert(&cursor, SymNames::symIdByName(token));
    1782              :                 }
    1783              :             } else {
    1784          215 :                 token += c;
    1785              :             }
    1786              :         }
    1787              :     }
    1788        37827 :     if (ldata->blocks.empty()) {
    1789         7180 :         ldata->blocks.push_back(TextBlock());
    1790              :     }
    1791        37827 :     ldata->layoutInvalid = false;
    1792        37827 : }
    1793              : 
    1794              : //---------------------------------------------------------
    1795              : //   prepareFormat - used when reading from XML and when pasting from clipboard
    1796              : //---------------------------------------------------------
    1797         8386 : bool TextBase::prepareFormat(const String& token, CharFormat& format)
    1798              : {
    1799         8386 :     if (token == "b") {
    1800          367 :         format.setBold(true);
    1801          367 :         return true;
    1802         8019 :     } else if (token == "/b") {
    1803          367 :         format.setBold(false);
    1804         7652 :     } else if (token == "i") {
    1805           50 :         format.setItalic(true);
    1806           50 :         return true;
    1807         7602 :     } else if (token == "/i") {
    1808           50 :         format.setItalic(false);
    1809         7552 :     } else if (token == "u") {
    1810            7 :         format.setUnderline(true);
    1811            7 :         return true;
    1812         7545 :     } else if (token == "/u") {
    1813            7 :         format.setUnderline(false);
    1814         7538 :     } else if (token == "s") {
    1815            2 :         format.setStrike(true);
    1816            2 :         return true;
    1817         7536 :     } else if (token == "/s") {
    1818            2 :         format.setStrike(false);
    1819         7534 :     } else if (token == "sub") {
    1820            0 :         format.setValign(VerticalAlignment::AlignSubScript);
    1821         7534 :     } else if (token == "/sub") {
    1822            0 :         format.setValign(VerticalAlignment::AlignNormal);
    1823         7534 :     } else if (token == "sup") {
    1824            2 :         format.setValign(VerticalAlignment::AlignSuperScript);
    1825         7532 :     } else if (token == "/sup") {
    1826            2 :         format.setValign(VerticalAlignment::AlignNormal);
    1827         7530 :     } else if (token.startsWith(u"font ")) {
    1828          811 :         String remainder = token.mid(5);
    1829          811 :         if (remainder.startsWith(u"size=\"")) {
    1830          567 :             format.setFontSize(parseNumProperty(remainder.mid(6)));
    1831          567 :             return true;
    1832          244 :         } else if (remainder.startsWith(u"face=\"")) {
    1833          244 :             String face = parseStringProperty(remainder.mid(6));
    1834          244 :             face = unEscape(face);
    1835          244 :             format.setFontFamily(face);
    1836          244 :             return true;
    1837          244 :         } else {
    1838            0 :             LOGD("cannot parse html property <%s> in text <%s>", muPrintable(token), muPrintable(m_text));
    1839              :         }
    1840          811 :     }
    1841         7149 :     return false;
    1842              : }
    1843              : 
    1844              : //---------------------------------------------------------
    1845              : //   prepareFormat - used when reading from XML
    1846              : //---------------------------------------------------------
    1847         8386 : void TextBase::prepareFormat(const String& token, TextCursor& cursor)
    1848              : {
    1849         8386 :     if (prepareFormat(token, *cursor.format()) && cursor.format()->fontFamily() != propertyDefault(Pid::FONT_FACE).value<String>()) {
    1850          229 :         setPropertyFlags(Pid::FONT_FACE, PropertyFlags::UNSTYLED);
    1851              :     }
    1852         8386 : }
    1853              : 
    1854              : //---------------------------------------------------------
    1855              : //   layoutFrame
    1856              : //---------------------------------------------------------
    1857              : 
    1858            0 : void TextBase::layoutFrame()
    1859              : {
    1860            0 :     layoutFrame(mutldata());
    1861            0 : }
    1862              : 
    1863          144 : void TextBase::layoutFrame(LayoutData* ldata) const
    1864              : {
    1865              : //      if (empty()) {    // or bbox.width() <= 1.0
    1866          144 :     if (ldata->bbox().width() <= 1.0 || ldata->bbox().height() < 1.0) {      // or bbox.width() <= 1.0
    1867              :         // this does not work for Harmony:
    1868            0 :         FontMetrics fm(font());
    1869            0 :         double ch = fm.ascent();
    1870            0 :         double cw = fm.width('n');
    1871            0 :         ldata->frame = RectF(0.0, -ch, cw, ch);
    1872            0 :     } else {
    1873          144 :         ldata->frame = ldata->bbox();
    1874              :     }
    1875              : 
    1876          144 :     if (rectangle()) {
    1877              :         // make sure width >= height, if only one row (basically: make square for single characters)
    1878          134 :         if (ldata->frame.height() > ldata->frame.width() && ldata->rows() == 1) {
    1879           74 :             double w = ldata->frame.height() - ldata->frame.width();
    1880           74 :             ldata->frame.adjust(-w * .5, 0.0, w * .5, 0.0);
    1881              :         }
    1882           10 :     } else if (circle()) {
    1883           10 :         if (ldata->frame.width() > ldata->frame.height()) {
    1884            2 :             ldata->frame.setTop(ldata->frame.y() + (ldata->frame.width() - ldata->frame.height()) * -.5);
    1885            2 :             ldata->frame.setHeight(ldata->frame.width());
    1886              :         } else {
    1887            8 :             ldata->frame.setLeft(ldata->frame.x() + (ldata->frame.height() - ldata->frame.width()) * -.5);
    1888            8 :             ldata->frame.setWidth(ldata->frame.height());
    1889              :         }
    1890              :     }
    1891          144 :     double _spatium = spatium();
    1892          144 :     double w = (paddingWidth() + frameWidth() * .5f).val() * _spatium;
    1893          144 :     ldata->frame.adjust(-w, -w, w, w);
    1894          144 :     w = 0.5 * frameWidth().val() * _spatium;
    1895          144 :     ldata->setBbox(ldata->frame.adjusted(-w, -w, w, w));
    1896          144 : }
    1897              : 
    1898              : //---------------------------------------------------------
    1899              : //   lineSpacing
    1900              : //---------------------------------------------------------
    1901              : 
    1902         2525 : double TextBase::lineSpacing() const
    1903              : {
    1904         2525 :     return fontMetrics().lineSpacing();
    1905              : }
    1906              : 
    1907              : //---------------------------------------------------------
    1908              : //   lineHeight
    1909              : //---------------------------------------------------------
    1910              : 
    1911            0 : double TextBase::lineHeight() const
    1912              : {
    1913            0 :     return fontMetrics().height();
    1914              : }
    1915              : 
    1916              : //---------------------------------------------------------
    1917              : //   baseLine
    1918              : //---------------------------------------------------------
    1919              : 
    1920            0 : double TextBase::baseLine() const
    1921              : {
    1922            0 :     return fontMetrics().ascent();
    1923              : }
    1924              : 
    1925        27623 : FontStyle TextBase::fontStyle() const
    1926              : {
    1927        27623 :     return m_cursor->format()->style();
    1928              : }
    1929              : 
    1930         7256 : String TextBase::family() const
    1931              : {
    1932         7256 :     return m_cursor->format()->fontFamily();
    1933              : }
    1934              : 
    1935        24866 : double TextBase::size() const
    1936              : {
    1937        24866 :     return m_cursor->format()->fontSize();
    1938              : }
    1939              : 
    1940        44226 : void TextBase::setFontStyle(const FontStyle& val)
    1941              : {
    1942        44226 :     m_cursor->setFormat(FormatId::Bold, val & FontStyle::Bold);
    1943        44226 :     m_cursor->setFormat(FormatId::Italic, val & FontStyle::Italic);
    1944        44226 :     m_cursor->setFormat(FormatId::Underline, val & FontStyle::Underline);
    1945        44226 :     m_cursor->setFormat(FormatId::Strike, val & FontStyle::Strike);
    1946        44226 : }
    1947              : 
    1948        44221 : void TextBase::setFamily(const String& val)
    1949              : {
    1950        44221 :     m_cursor->setFormat(FormatId::FontFamily, val);
    1951        44221 : }
    1952              : 
    1953        44330 : void TextBase::setSize(const double& val)
    1954              : {
    1955        44330 :     m_cursor->setFormat(FormatId::FontSize, val);
    1956        44330 : }
    1957              : 
    1958              : //---------------------------------------------------------
    1959              : //   XmlNesting
    1960              : //---------------------------------------------------------
    1961              : 
    1962              : class XmlNesting : public std::stack<String>
    1963              : {
    1964              :     OBJECT_ALLOCATOR(engraving, XmlNesting)
    1965              : 
    1966              :     String* _s;
    1967              : 
    1968              : public:
    1969        39385 :     XmlNesting(String* s) { _s = s; }
    1970          186 :     void pushToken(const String& t)
    1971              :     {
    1972          186 :         *_s += u"<";
    1973          186 :         *_s += t;
    1974          186 :         *_s += u">";
    1975          186 :         push(t);
    1976          186 :     }
    1977              : 
    1978          161 :     void pushB() { pushToken(u"b"); }
    1979           13 :     void pushI() { pushToken(u"i"); }
    1980           11 :     void pushU() { pushToken(u"u"); }
    1981            1 :     void pushS() { pushToken(u"s"); }
    1982              : 
    1983          186 :     String popToken()
    1984              :     {
    1985          186 :         String s = top();
    1986          186 :         pop();
    1987          186 :         *_s += u"</";
    1988          186 :         *_s += s;
    1989          186 :         *_s += u">";
    1990          186 :         return s;
    1991            0 :     }
    1992              : 
    1993          157 :     void popToken(const char* t)
    1994              :     {
    1995          157 :         StringList ps;
    1996              :         for (;;) {
    1997          157 :             String s = popToken();
    1998          157 :             if (s == t) {
    1999          157 :                 break;
    2000              :             }
    2001            0 :             ps << s;
    2002          157 :         }
    2003          157 :         for (const String& s : ps) {
    2004            0 :             pushToken(s);
    2005              :         }
    2006          157 :     }
    2007              : 
    2008          150 :     void popB() { popToken("b"); }
    2009            1 :     void popI() { popToken("i"); }
    2010            6 :     void popU() { popToken("u"); }
    2011            0 :     void popS() { popToken("s"); }
    2012              : };
    2013              : 
    2014              : //---------------------------------------------------------
    2015              : //   genText
    2016              : //---------------------------------------------------------
    2017        39385 : String TextBase::genText(const LayoutData* ldata) const
    2018              : {
    2019        39385 :     if (!ldata) {
    2020            0 :         return String();
    2021              :     }
    2022              : 
    2023        39385 :     String text;
    2024        39385 :     XmlNesting xmlNesting(&text);
    2025              : 
    2026        39385 :     CharFormat fmt;
    2027        39385 :     fmt.setFontFamily(propertyDefault(Pid::FONT_FACE).value<String>());
    2028        39385 :     fmt.setFontSize(propertyDefault(Pid::FONT_SIZE).toReal());
    2029        39385 :     fmt.setStyle(static_cast<FontStyle>(propertyDefault(Pid::FONT_STYLE).toInt()));
    2030              : 
    2031              :     // Prepare the initial style tags (if any).
    2032              :     // We only need those if there is any fragment in the blocks with a different style than the default one.
    2033        39385 :     bool bold_      = false;
    2034        39385 :     bool italic_    = false;
    2035        39385 :     bool underline_ = false;
    2036        39385 :     bool strike_    = false;
    2037        76247 :     for (const TextBlock& block : ldata->blocks) {
    2038        73999 :         for (const TextFragment& f : block.fragments()) {
    2039        37137 :             if (!f.format.bold() && fmt.bold()) {
    2040            0 :                 bold_ = true;
    2041              :             }
    2042        37137 :             if (!f.format.italic() && fmt.italic()) {
    2043            1 :                 italic_ = true;
    2044              :             }
    2045        37137 :             if (!f.format.underline() && fmt.underline()) {
    2046            0 :                 underline_ = true;
    2047              :             }
    2048        37137 :             if (!f.format.strike() && fmt.strike()) {
    2049            0 :                 strike_ = true;
    2050              :             }
    2051              :         }
    2052              :     }
    2053        39385 :     if (bold_) {
    2054            0 :         xmlNesting.pushB();
    2055              :     }
    2056        39385 :     if (italic_) {
    2057            1 :         xmlNesting.pushI();
    2058              :     }
    2059        39385 :     if (underline_) {
    2060            0 :         xmlNesting.pushU();
    2061              :     }
    2062        39385 :     if (strike_) {
    2063            0 :         xmlNesting.pushS();
    2064              :     }
    2065              : 
    2066              :     // And here for the actual formatting of the text.
    2067        76247 :     for (const TextBlock& block : ldata->blocks) {
    2068        73999 :         for (const TextFragment& f : block.fragments()) {
    2069              :             // don't skip, empty text fragments hold information for empty lines
    2070              :             //    if (f.text.isEmpty())                   // skip empty fragments, not to
    2071              :             //        continue;                           // insert extra HTML formatting
    2072              : 
    2073              :             // Push or Pop XML tags according to the current format changes
    2074        37137 :             const CharFormat& format = f.format;
    2075        37137 :             if (fmt.bold() != format.bold()) {
    2076          311 :                 if (format.bold()) {
    2077          161 :                     xmlNesting.pushB();
    2078              :                 } else {
    2079          150 :                     xmlNesting.popB();
    2080              :                 }
    2081              :             }
    2082        37137 :             if (fmt.italic() != format.italic()) {
    2083           13 :                 if (format.italic()) {
    2084           12 :                     xmlNesting.pushI();
    2085              :                 } else {
    2086            1 :                     xmlNesting.popI();
    2087              :                 }
    2088              :             }
    2089        37137 :             if (fmt.underline() != format.underline()) {
    2090           17 :                 if (format.underline()) {
    2091           11 :                     xmlNesting.pushU();
    2092              :                 } else {
    2093            6 :                     xmlNesting.popU();
    2094              :                 }
    2095              :             }
    2096        37137 :             if (fmt.strike() != format.strike()) {
    2097            1 :                 if (format.strike()) {
    2098            1 :                     xmlNesting.pushS();
    2099              :                 } else {
    2100            0 :                     xmlNesting.popS();
    2101              :                 }
    2102              :             }
    2103              : 
    2104        37137 :             if (format.fontSize() != fmt.fontSize()) {
    2105          295 :                 text += String(u"<font size=\"%1\"/>").arg(format.fontSize());
    2106              :             }
    2107        37137 :             if (format.fontFamily() != "ScoreText" && format.fontFamily() != fmt.fontFamily()) {
    2108            3 :                 text += String(u"<font face=\"%1\"/>").arg(TextBase::escape(format.fontFamily()));
    2109              :             }
    2110              : 
    2111        37137 :             VerticalAlignment va = format.valign();
    2112        37137 :             VerticalAlignment cva = fmt.valign();
    2113        37137 :             if (cva != va) {
    2114            0 :                 switch (va) {
    2115            0 :                 case VerticalAlignment::AlignNormal:
    2116            0 :                     xmlNesting.popToken(cva == VerticalAlignment::AlignSuperScript ? "sup" : "sub");
    2117            0 :                     break;
    2118            0 :                 case VerticalAlignment::AlignSuperScript:
    2119            0 :                     xmlNesting.pushToken(u"sup");
    2120            0 :                     break;
    2121            0 :                 case VerticalAlignment::AlignSubScript:
    2122            0 :                     xmlNesting.pushToken(u"sub");
    2123            0 :                     break;
    2124            0 :                 case VerticalAlignment::AlignUndefined:
    2125            0 :                     break;
    2126              :                 }
    2127              :             }
    2128        37137 :             if (format.fontFamily() == u"ScoreText") {
    2129          102 :                 for (size_t i = 0; i < f.text.size(); ++i) {
    2130           66 :                     text += toSymbolXml(f.text.at(i), this);
    2131              :                 }
    2132              :             } else {
    2133        37101 :                 text += XmlWriter::xmlString(f.text);
    2134              :             }
    2135        37137 :             fmt = format;
    2136              :         }
    2137        36862 :         if (block.eol()) {
    2138           88 :             text += Char::LineFeed;
    2139              :         }
    2140              :     }
    2141        39414 :     while (!xmlNesting.empty()) {
    2142           29 :         xmlNesting.popToken();
    2143              :     }
    2144              : 
    2145        39385 :     return text;
    2146        39385 : }
    2147              : 
    2148        21200 : void TextBase::genText()
    2149              : {
    2150        21200 :     m_text = genText(ldata());
    2151        21200 :     m_textInvalid = false;
    2152        21200 : }
    2153              : 
    2154              : //---------------------------------------------------------
    2155              : //   selectAll
    2156              : //---------------------------------------------------------
    2157              : 
    2158       265454 : void TextBase::selectAll()
    2159              : {
    2160       265454 :     const LayoutData* ldata = this->ldata();
    2161       265454 :     if (!ldata || ldata->blocks.empty()) {
    2162       233690 :         return;
    2163              :     }
    2164              : 
    2165        31764 :     cursor()->setSelectColumn(0);
    2166        31764 :     cursor()->setSelectLine(0);
    2167        31764 :     cursor()->setRow(ldata->rows() - 1);
    2168        31764 :     cursor()->setColumn(cursor()->curLine().columns());
    2169              : }
    2170              : 
    2171            0 : void TextBase::select(SelectTextType type)
    2172              : {
    2173            0 :     switch (type) {
    2174            0 :     case SelectTextType::Word:
    2175            0 :         cursor()->selectWord();
    2176            0 :         break;
    2177            0 :     case SelectTextType::All:
    2178            0 :         selectAll();
    2179            0 :         break;
    2180              :     }
    2181            0 : }
    2182              : 
    2183              : //---------------------------------------------------------
    2184              : //   pageRectangle
    2185              : //---------------------------------------------------------
    2186              : 
    2187            0 : RectF TextBase::pageRectangle() const
    2188              : {
    2189            0 :     if (ownershipParent() && (ownershipParent()->isHBox() || ownershipParent()->isVBox() || ownershipParent()->isTBox())) {
    2190            0 :         Box* box = toBox(ownershipParent());
    2191            0 :         RectF r = box->pageBoundingRect();
    2192            0 :         double x = r.x() + box->leftMargin() * DPMM;
    2193            0 :         double y = r.y() + box->topMargin() * DPMM;
    2194            0 :         double h = r.height() - (box->topMargin() + box->bottomMargin()) * DPMM;
    2195            0 :         double w = r.width() - (box->leftMargin() + box->rightMargin()) * DPMM;
    2196              : 
    2197              :         // SizeF ps = _doc->pageSize();
    2198              :         // return RectF(x, y, ps.width(), ps.height());
    2199              : 
    2200            0 :         return RectF(x, y, w, h);
    2201              :     }
    2202            0 :     if (ownershipParent() && ownershipParent()->isPage()) {
    2203            0 :         Page* box  = toPage(ownershipParent());
    2204            0 :         RectF r = box->pageBoundingRect();
    2205            0 :         double x = r.x() + box->lm();
    2206            0 :         double y = r.y() + box->tm();
    2207            0 :         double h = r.height() - box->tm() - box->bm();
    2208            0 :         double w = r.width() - box->lm() - box->rm();
    2209            0 :         return RectF(x, y, w, h);
    2210              :     }
    2211            0 :     return pageBoundingRect();
    2212              : }
    2213              : 
    2214              : //---------------------------------------------------------
    2215              : //   dragTo
    2216              : //---------------------------------------------------------
    2217              : 
    2218            0 : void TextBase::dragTo(EditData& ed)
    2219              : {
    2220            0 :     cursor()->set(ed.pos, TextCursor::MoveMode::KeepAnchor);
    2221            0 :     score()->setUpdateAll();
    2222            0 :     score()->update();
    2223            0 : }
    2224              : 
    2225              : //---------------------------------------------------------
    2226              : //   dragAnchorLines
    2227              : //---------------------------------------------------------
    2228              : 
    2229            0 : std::vector<LineF> TextBase::dragAnchorLines() const
    2230              : {
    2231            0 :     std::vector<LineF> result(genericDragAnchorLines());
    2232              : 
    2233            0 :     if (layoutToParentWidth() && !result.empty()) {
    2234            0 :         LineF& line = result[0];
    2235            0 :         line.setP2(line.p2() + ldata()->bbox().topLeft());
    2236              :     }
    2237              : 
    2238            0 :     return result;
    2239            0 : }
    2240              : 
    2241              : //---------------------------------------------------------
    2242              : //   mousePress
    2243              : //    set text cursor
    2244              : //---------------------------------------------------------
    2245              : 
    2246            0 : bool TextBase::mousePress(EditData& ed)
    2247              : {
    2248            0 :     bool shift = ed.modifiers & ShiftModifier;
    2249            0 :     if (!cursor()->set(ed.startMove, shift ? TextCursor::MoveMode::KeepAnchor : TextCursor::MoveMode::MoveAnchor)) {
    2250            0 :         return false;
    2251              :     }
    2252              : 
    2253            0 :     score()->setUpdateAll();
    2254            0 :     return true;
    2255              : }
    2256              : 
    2257              : //---------------------------------------------------------
    2258              : //   acceptDrop
    2259              : //---------------------------------------------------------
    2260              : 
    2261            0 : bool TextBase::acceptDrop(EditData& data) const
    2262              : {
    2263              :     // do not accept the drop if this text element is not being edited
    2264            0 :     ElementEditDataPtr eed = data.getData(this);
    2265            0 :     if (!eed || eed->type() != EditDataType::TextEditData) {
    2266            0 :         return false;
    2267              :     }
    2268            0 :     ElementType type = data.dropElement->type();
    2269            0 :     return type == ElementType::SYMBOL || type == ElementType::FSYMBOL;
    2270            0 : }
    2271              : 
    2272              : //--------------------------------------------------------
    2273              : //   setXmlText
    2274              : //---------------------------------------------------------
    2275              : 
    2276        30881 : void TextBase::setXmlText(const String& s)
    2277              : {
    2278        30881 :     m_text = s;
    2279        30881 :     m_textInvalid = false;
    2280        30881 :     mutldata()->blocks.clear();
    2281        30881 :     mutldata()->layoutInvalid = true;
    2282        30881 : }
    2283              : 
    2284         4374 : void TextBase::checkCustomFormatting(const String& s)
    2285              : {
    2286         4374 :     if (s.contains(u"<font size")) {
    2287           56 :         setPropertyFlags(Pid::FONT_SIZE, PropertyFlags::UNSTYLED);
    2288              :     }
    2289         4374 :     if (s.contains(u"<b>") || s.contains(u"<i>") || s.contains(u"<u>") || s.contains(u"<s>")) {
    2290           84 :         setPropertyFlags(Pid::FONT_STYLE, PropertyFlags::UNSTYLED);
    2291              :     }
    2292         4374 : }
    2293              : 
    2294            5 : void TextBase::resetFormatting()
    2295              : {
    2296              :     // reset any formatting properties that can be changed per-character (doesn't change existing text)
    2297            5 :     cursor()->format()->setFontFamily(propertyDefault(Pid::FONT_FACE).value<String>());
    2298            5 :     cursor()->format()->setFontSize(propertyDefault(Pid::FONT_SIZE).toReal());
    2299            5 :     cursor()->format()->setStyle(static_cast<FontStyle>(propertyDefault(Pid::FONT_STYLE).toInt()));
    2300            5 :     cursor()->format()->setValign(VerticalAlignment::AlignNormal);
    2301            5 : }
    2302              : 
    2303              : //---------------------------------------------------------
    2304              : //   fragmentList
    2305              : //---------------------------------------------------------
    2306              : 
    2307              : /*
    2308              :  Return the text as a single list of TextFragment
    2309              :  Used by the MusicXML formatted export to avoid parsing the xml text format
    2310              :  */
    2311              : 
    2312          751 : std::list<TextFragment> TextBase::fragmentList() const
    2313              : {
    2314          751 :     std::list<TextFragment> res;
    2315              : 
    2316          751 :     const TextBase* text = this;
    2317          751 :     std::unique_ptr<TextBase> tmpText;
    2318          751 :     const LayoutData* ldata = this->ldata();
    2319          751 :     if (!ldata || ldata->layoutInvalid) {
    2320              :         // Create temporary text object to avoid side effects
    2321              :         // of createLayout() call.
    2322            0 :         tmpText.reset(toTextBase(this->clone()));
    2323            0 :         tmpText->createBlocks();
    2324            0 :         text = tmpText.get();
    2325              :     }
    2326              : 
    2327          751 :     const LayoutData* tmlldata = text->ldata();
    2328         1510 :     for (const TextBlock& block : tmlldata->blocks) {
    2329         1630 :         for (const TextFragment& f : block.fragments()) {
    2330              :             /* TODO TBD
    2331              :             if (f.text.empty())                     // skip empty fragments, not to
    2332              :                   continue;                           // insert extra HTML formatting
    2333              :              */
    2334          871 :             res.push_back(f);
    2335          871 :             if (block.eol()) {
    2336              :                 // simply append a newline
    2337            8 :                 res.back().text += u"\n";
    2338              :             }
    2339              :         }
    2340              :     }
    2341          751 :     return res;
    2342          751 : }
    2343              : 
    2344              : //---------------------------------------------------------
    2345              : //   plainText
    2346              : //    return plain text with symbols
    2347              : //---------------------------------------------------------
    2348              : 
    2349         6154 : String TextBase::plainText() const
    2350              : {
    2351         6154 :     String s;
    2352              : 
    2353         6154 :     const TextBase* text = this;
    2354         6154 :     std::unique_ptr<TextBase> tmpText;
    2355         6154 :     const LayoutData* ldata = this->ldata();
    2356         6154 :     if (!ldata || ldata->layoutInvalid) {
    2357              :         // Create temporary text object to avoid side effects
    2358              :         // of createLayout() call.
    2359         1859 :         tmpText.reset(toTextBase(this->clone()));
    2360         1859 :         tmpText->createBlocks();
    2361         1859 :         text = tmpText.get();
    2362              :     }
    2363              : 
    2364         6154 :     const LayoutData* tmlldata = text->ldata();
    2365        12339 :     for (const TextBlock& block : tmlldata->blocks) {
    2366        12714 :         for (const TextFragment& f : block.fragments()) {
    2367         6529 :             s += f.text;
    2368              :         }
    2369         6185 :         if (block.eol()) {
    2370           31 :             s += Char::LineFeed;
    2371              :         }
    2372              :     }
    2373         6154 :     return s;
    2374         6154 : }
    2375              : 
    2376              : //---------------------------------------------------------
    2377              : //   xmlText
    2378              : //---------------------------------------------------------
    2379              : 
    2380        97741 : String TextBase::xmlText() const
    2381              : {
    2382        97741 :     if (!m_textInvalid) {
    2383        79556 :         return m_text;
    2384              :     }
    2385              : 
    2386        18185 :     String text = genText(ldata());
    2387        18185 :     return text;
    2388        18185 : }
    2389              : 
    2390              : //---------------------------------------------------------
    2391              : //   unEscape
    2392              : //---------------------------------------------------------
    2393              : 
    2394          508 : String TextBase::unEscape(String s)
    2395              : {
    2396          508 :     s.replace(u"&lt;", u"<");
    2397          508 :     s.replace(u"&gt;", u">");
    2398          508 :     s.replace(u"&amp;", u"&");
    2399          508 :     s.replace(u"&apos;", u"'");
    2400          508 :     s.replace(u"&quot;", u"\"");
    2401          508 :     return s;
    2402              : }
    2403              : 
    2404              : //---------------------------------------------------------
    2405              : //   escape
    2406              : //---------------------------------------------------------
    2407              : 
    2408            3 : String TextBase::escape(String s)
    2409              : {
    2410            3 :     s.replace(u"<", u"&lt;");
    2411            3 :     s.replace(u">", u"&gt;");
    2412            3 :     s.replace(u"&", u"&amp;");
    2413            3 :     s.replace(u"'", u"&apos;");
    2414            3 :     s.replace(u"\"", u"&quot;");
    2415            3 :     return s;
    2416              : }
    2417              : 
    2418              : //---------------------------------------------------------
    2419              : //   accessibleInfo
    2420              : //---------------------------------------------------------
    2421              : 
    2422            0 : String TextBase::accessibleInfo() const
    2423              : {
    2424            0 :     String rez;
    2425            0 :     switch (textStyleType()) {
    2426            0 :     case TextStyleType::TITLE:
    2427              :     case TextStyleType::SUBTITLE:
    2428              :     case TextStyleType::COMPOSER:
    2429              :     case TextStyleType::LYRICIST:
    2430              :     case TextStyleType::TRANSLATOR:
    2431              :     case TextStyleType::MEASURE_NUMBER:
    2432              :     case TextStyleType::MMREST_RANGE:
    2433            0 :         rez = translatedSubtypeUserName();
    2434            0 :         break;
    2435            0 :     default:
    2436            0 :         rez = EngravingItem::accessibleInfo();
    2437            0 :         break;
    2438              :     }
    2439            0 :     String s = plainText().simplified();
    2440            0 :     if (s.size() > 20) {
    2441            0 :         s.truncate(20);
    2442            0 :         s += u"…";
    2443              :     }
    2444            0 :     return String(u"%1: %2").arg(rez, s);
    2445            0 : }
    2446              : 
    2447              : //---------------------------------------------------------
    2448              : //   screenReaderInfo
    2449              : //---------------------------------------------------------
    2450              : 
    2451            0 : String TextBase::screenReaderInfo() const
    2452              : {
    2453            0 :     String rez;
    2454              : 
    2455            0 :     switch (textStyleType()) {
    2456            0 :     case TextStyleType::TITLE:
    2457              :     case TextStyleType::SUBTITLE:
    2458              :     case TextStyleType::COMPOSER:
    2459              :     case TextStyleType::LYRICIST:
    2460              :     case TextStyleType::TRANSLATOR:
    2461              :     case TextStyleType::MEASURE_NUMBER:
    2462              :     case TextStyleType::MMREST_RANGE:
    2463            0 :         rez = translatedSubtypeUserName();
    2464            0 :         break;
    2465            0 :     default:
    2466            0 :         rez = EngravingItem::accessibleInfo();
    2467            0 :         break;
    2468              :     }
    2469            0 :     String s = plainText().simplified();
    2470            0 :     return String(u"%1: %2").arg(rez, s);
    2471            0 : }
    2472              : 
    2473              : //---------------------------------------------------------
    2474              : //   subtype
    2475              : //---------------------------------------------------------
    2476              : 
    2477            0 : int TextBase::subtype() const
    2478              : {
    2479            0 :     return int(textStyleType());
    2480              : }
    2481              : 
    2482              : //---------------------------------------------------------
    2483              : //   subtypeUserName
    2484              : //---------------------------------------------------------
    2485              : 
    2486            0 : TranslatableString TextBase::subtypeUserName() const
    2487              : {
    2488            0 :     return score() ? score()->getTextStyleUserName(textStyleType()) : TConv::userName(textStyleType());
    2489              : }
    2490              : 
    2491              : //---------------------------------------------------------
    2492              : //   validateText
    2493              : //    check if s is a valid musescore xml text string
    2494              : //    - simple bugs are automatically adjusted
    2495              : //   return true if text is valid or could be fixed
    2496              : //  (this is incomplete/experimental)
    2497              : //---------------------------------------------------------
    2498              : 
    2499            0 : bool TextBase::validateText(String& s)
    2500              : {
    2501            0 :     String d;
    2502            0 :     for (size_t i = 0; i < s.size(); ++i) {
    2503            0 :         Char c = s.at(i);
    2504            0 :         if (c == u'&') {
    2505            0 :             const char16_t* ok[] { u"amp;", u"lt;", u"gt;", u"quot;" };
    2506            0 :             String t = s.mid(i + 1);
    2507            0 :             bool found = false;
    2508            0 :             for (auto k : ok) {
    2509            0 :                 if (t.startsWith(k)) {
    2510            0 :                     d.append(c);
    2511            0 :                     d.append(k);
    2512            0 :                     i += int(std::u16string_view(k).size());
    2513            0 :                     found = true;
    2514            0 :                     break;
    2515              :                 }
    2516              :             }
    2517            0 :             if (!found) {
    2518            0 :                 d.append(u"&amp;");
    2519              :             }
    2520            0 :         } else if (c == u'<') {
    2521            0 :             const char16_t* ok[] { u"b>", u"/b>", u"i>", u"/i>", u"u>", u"/u", u"s>", u"/s>", u"font ", u"/font>", u"sym>", u"/sym>",
    2522              :                                    u"sub>", u"/sub>", u"sup>", u"/sup>" };
    2523            0 :             String t = s.mid(i + 1);
    2524            0 :             bool found = false;
    2525            0 :             for (auto k : ok) {
    2526            0 :                 if (t.startsWith(k)) {
    2527            0 :                     d.append(c);
    2528            0 :                     d.append(k);
    2529            0 :                     i += int(std::u16string_view(k).size());
    2530            0 :                     found = true;
    2531            0 :                     break;
    2532              :                 }
    2533              :             }
    2534            0 :             if (!found) {
    2535            0 :                 d.append(u"&lt;");
    2536              :             }
    2537            0 :         } else {
    2538            0 :             d.append(c);
    2539              :         }
    2540              :     }
    2541              : 
    2542            0 :     String ss = u"<data>" + d + u"</data>\n";
    2543            0 :     muse::ByteArray ba = ss.toUtf8();
    2544            0 :     XmlReader xml(ba);
    2545            0 :     while (xml.readNextStartElement()) {
    2546              :         // LOGD("  token %d <%s>", int(xml.tokenType()), muPrintable(xml.name().toString()));
    2547              :     }
    2548            0 :     if (xml.error() == XmlReader::NoError) {
    2549            0 :         s = d;
    2550            0 :         return true;
    2551              :     }
    2552            0 :     LOGD() << "xml error at byte offset " << xml.byteOffset()
    2553            0 :            << ": " << xml.errorString();
    2554            0 :     LOGD() << "text: |" << ss << "|";
    2555            0 :     return false;
    2556            0 : }
    2557              : 
    2558              : //---------------------------------------------------------
    2559              : //   font
    2560              : //---------------------------------------------------------
    2561              : 
    2562        27173 : Font TextBase::font() const
    2563              : {
    2564        27173 :     double m = size();
    2565        27173 :     if (sizeIsSpatiumDependent()) {
    2566        27103 :         m *= spatium() / defaultSpatium();
    2567              :     }
    2568        27173 :     Font f(family(), Font::Type::Unknown);
    2569        27173 :     f.setPointSizeF(m);
    2570        27173 :     f.setBold(bold());
    2571        27173 :     f.setItalic(italic());
    2572        27173 :     if (underline()) {
    2573            0 :         f.setUnderline(underline());
    2574              :     }
    2575        27173 :     if (strike()) {
    2576            0 :         f.setStrike(strike());
    2577              :     }
    2578              : 
    2579        27173 :     return f;
    2580            0 : }
    2581              : 
    2582              : //---------------------------------------------------------
    2583              : //   fontMetrics
    2584              : //---------------------------------------------------------
    2585              : 
    2586         6612 : FontMetrics TextBase::fontMetrics() const
    2587              : {
    2588         6612 :     return FontMetrics(font());
    2589              : }
    2590              : 
    2591            2 : bool TextBase::isPropertyLinkedToMaster(Pid id) const
    2592              : {
    2593            2 :     if (propertyGroup(id) == PropertyGroup::TEXT) {
    2594            2 :         return isTextLinkedToMaster();
    2595              :     }
    2596              : 
    2597            0 :     return EngravingItem::isPropertyLinkedToMaster(id);
    2598              : }
    2599              : 
    2600            0 : bool TextBase::isUnlinkedFromMaster() const
    2601              : {
    2602            0 :     EngravingItem* parent = ownershipParentItem();
    2603            0 :     if (parent && parent->isUnlinkedFromMaster()) {
    2604            0 :         return true;
    2605              :     }
    2606              : 
    2607            0 :     return !getProperty(Pid::TEXT_LINKED_TO_MASTER).toBool() || EngravingItem::isUnlinkedFromMaster();
    2608              : }
    2609              : 
    2610              : //---------------------------------------------------------
    2611              : //   getProperty
    2612              : //---------------------------------------------------------
    2613              : 
    2614        72094 : PropertyValue TextBase::getProperty(Pid propertyId) const
    2615              : {
    2616        72094 :     switch (propertyId) {
    2617         4919 :     case Pid::TEXT_STYLE:
    2618         4919 :         return textStyleType();
    2619          576 :     case Pid::FONT_FACE:
    2620          576 :         return m_cursor->selectedFragmentsFormat().fontFamily();
    2621          560 :     case Pid::FONT_SIZE:
    2622          560 :         return m_cursor->selectedFragmentsFormat().fontSize();
    2623          567 :     case Pid::FONT_STYLE:
    2624          567 :         return static_cast<int>(m_cursor->selectedFragmentsFormat().style());
    2625          576 :     case Pid::TEXT_LINE_SPACING:
    2626          576 :         return textLineSpacing();
    2627          493 :     case Pid::FRAME_TYPE:
    2628          493 :         return static_cast<int>(frameType());
    2629          475 :     case Pid::FRAME_WIDTH:
    2630          475 :         return frameWidth();
    2631          475 :     case Pid::FRAME_PADDING:
    2632          475 :         return paddingWidth();
    2633          475 :     case Pid::FRAME_ROUND:
    2634          475 :         return frameRound();
    2635          475 :     case Pid::FRAME_FG_COLOR:
    2636          475 :         return PropertyValue::fromValue(frameColor());
    2637          475 :     case Pid::FRAME_BG_COLOR:
    2638          475 :         return PropertyValue::fromValue(bgColor());
    2639          710 :     case Pid::ALIGN:
    2640          710 :         return PropertyValue::fromValue(align());
    2641         2130 :     case Pid::POSITION:
    2642         2130 :         return PropertyValue::fromValue(position());
    2643            2 :     case Pid::TEXT_SCRIPT_ALIGN:
    2644            2 :         return static_cast<int>(m_cursor->selectedFragmentsFormat().valign());
    2645          794 :     case Pid::TEXT:
    2646          794 :         return xmlText();
    2647         4326 :     case Pid::TEXT_LINKED_TO_MASTER:
    2648         4326 :         return isTextLinkedToMaster();
    2649         1883 :     case Pid::DIRECTION:
    2650         1883 :         return direction();
    2651         2763 :     case Pid::CENTER_BETWEEN_STAVES:
    2652         2763 :         return centerBetweenStaves();
    2653         2383 :     case Pid::VOICE_ASSIGNMENT:
    2654         2383 :         return voiceAssignment();
    2655         4264 :     case Pid::MUSIC_SYMBOL_SIZE:
    2656         4264 :         return symbolSize();
    2657         3947 :     case Pid::MUSICAL_SYMBOLS_SCALE:
    2658         3947 :         return symbolScale();
    2659        38826 :     default:
    2660        38826 :         return EngravingItem::getProperty(propertyId);
    2661              :     }
    2662              : }
    2663              : 
    2664              : //---------------------------------------------------------
    2665              : //   setProperty
    2666              : //---------------------------------------------------------
    2667              : 
    2668       714116 : bool TextBase::setProperty(Pid pid, const PropertyValue& v)
    2669              : {
    2670       714116 :     if (m_textInvalid && ldata() && ldata()->isValid()) {
    2671        15875 :         genText();
    2672              :     }
    2673              : 
    2674       714116 :     bool rv = true;
    2675       714116 :     switch (pid) {
    2676        42058 :     case Pid::COLOR:
    2677        42058 :         if (color() == frameColor()) {
    2678        42058 :             setFrameColor(v.value<Color>());
    2679              :         }
    2680        42058 :         EngravingItem::setProperty(pid, v);
    2681        42058 :         break;
    2682         2882 :     case Pid::TEXT_STYLE:
    2683         2882 :         initTextStyleType(v.value<TextStyleType>());
    2684         2882 :         break;
    2685        42928 :     case Pid::FONT_FACE:
    2686        42928 :         setFamily(v.value<String>());
    2687        42928 :         break;
    2688        42975 :     case Pid::FONT_SIZE:
    2689        42975 :         setSize(v.toReal());
    2690        42975 :         break;
    2691        42921 :     case Pid::FONT_STYLE:
    2692        42921 :         setFontStyle(FontStyle(v.toInt()));
    2693        42921 :         break;
    2694        42039 :     case Pid::TEXT_LINE_SPACING:
    2695        42039 :         setTextLineSpacing(v.toReal());
    2696        42039 :         break;
    2697        42044 :     case Pid::FRAME_TYPE:
    2698        42044 :         setFrameType(FrameType(v.toInt()));
    2699        42044 :         break;
    2700        42039 :     case Pid::FRAME_WIDTH:
    2701        42039 :         setFrameWidth(v.value<Spatium>());
    2702        42039 :         break;
    2703        42039 :     case Pid::FRAME_PADDING:
    2704        42039 :         setPaddingWidth(v.value<Spatium>());
    2705        42039 :         break;
    2706        42039 :     case Pid::FRAME_ROUND:
    2707        42039 :         setFrameRound(v.value<Spatium>());
    2708        42039 :         break;
    2709        42036 :     case Pid::FRAME_FG_COLOR:
    2710        42036 :         setFrameColor(v.value<Color>());
    2711        42036 :         break;
    2712        42039 :     case Pid::FRAME_BG_COLOR:
    2713        42039 :         setBgColor(v.value<Color>());
    2714        42039 :         break;
    2715           77 :     case Pid::TEXT:
    2716           77 :         setXmlText(v.value<String>());
    2717           77 :         break;
    2718        42538 :     case Pid::ALIGN:
    2719        42538 :         setAlign(v.value<Align>());
    2720        42538 :         break;
    2721        42650 :     case Pid::POSITION:
    2722        42650 :         setPosition(v.value<AlignH>());
    2723        42650 :         break;
    2724            0 :     case Pid::TEXT_SCRIPT_ALIGN:
    2725            0 :         m_cursor->setFormat(FormatId::Valign, v.toInt());
    2726            0 :         break;
    2727            0 :     case Pid::TEXT_LINKED_TO_MASTER:
    2728            0 :         if (isTextLinkedToMaster() == v.toBool()) {
    2729            0 :             break;
    2730              :         }
    2731            0 :         if (!isTextLinkedToMaster()) {
    2732            0 :             relinkPropertiesToMaster(PropertyGroup::TEXT);
    2733              :         }
    2734            0 :         setTextLinkedToMaster(v.toBool());
    2735            0 :         break;
    2736          147 :     case Pid::DIRECTION:
    2737          147 :         setDirection(v.value<DirectionV>());
    2738          147 :         break;
    2739           52 :     case Pid::CENTER_BETWEEN_STAVES:
    2740           52 :         setCenterBetweenStaves(v.value<AutoOnOff>());
    2741           52 :         break;
    2742           13 :     case Pid::VOICE_ASSIGNMENT:
    2743           13 :         setVoiceAssignment(v.value<VoiceAssignment>());
    2744           13 :         break;
    2745        44179 :     case Pid::MUSIC_SYMBOL_SIZE:
    2746        44179 :         setSymbolSize(v.toDouble());
    2747        44179 :         break;
    2748        42044 :     case Pid::MUSICAL_SYMBOLS_SCALE:
    2749        42044 :         setSymbolScale(v.toDouble());
    2750        42044 :         break;
    2751        74377 :     default:
    2752        74377 :         rv = EngravingItem::setProperty(pid, v);
    2753        74377 :         break;
    2754              :     }
    2755              : 
    2756       714116 :     triggerLayout();
    2757              : 
    2758       714116 :     return rv;
    2759              : }
    2760              : 
    2761              : //---------------------------------------------------------
    2762              : //   propertyDefault
    2763              : //---------------------------------------------------------
    2764              : 
    2765       292783 : PropertyValue TextBase::propertyDefault(Pid id) const
    2766              : {
    2767       292783 :     if (id == Pid::Z) {
    2768         4642 :         return EngravingItem::propertyDefault(id);
    2769              :     }
    2770              : 
    2771       288141 :     if (composition()) {
    2772         5537 :         PropertyValue v = ownershipParent()->propertyDefault(id);
    2773         5537 :         if (v.isValid()) {
    2774         5537 :             return v;
    2775              :         }
    2776         5537 :     }
    2777              : 
    2778       282604 :     Sid sid = getPropertyStyle(id);
    2779       282604 :     if (sid != Sid::NOSTYLE) {
    2780       217036 :         return styleValue(id, sid);
    2781              :     }
    2782              : 
    2783        65568 :     switch (id) {
    2784          233 :     case Pid::TEXT_STYLE:
    2785          233 :         return TextStyleType::DEFAULT;
    2786           51 :     case Pid::TEXT:
    2787          102 :         return String();
    2788        28330 :     case Pid::TEXT_SCRIPT_ALIGN:
    2789        28330 :         return static_cast<int>(VerticalAlignment::AlignNormal);
    2790         4326 :     case Pid::TEXT_LINKED_TO_MASTER:
    2791         4326 :         return true;
    2792          601 :     case Pid::DIRECTION:
    2793          601 :         return DirectionV::AUTO;
    2794         1044 :     case Pid::CENTER_BETWEEN_STAVES:
    2795         2088 :         return AutoOnOff::AUTO;
    2796          601 :     case Pid::VOICE_ASSIGNMENT:
    2797         1202 :         return VoiceAssignment::ALL_VOICE_IN_INSTRUMENT;
    2798        30382 :     default:
    2799       513820 :         for (const auto& p : *textStyle(TextStyleType::DEFAULT)) {
    2800       483642 :             if (p.pid == id) {
    2801          204 :                 return styleValue(id, p.sid);
    2802              :             }
    2803              :         }
    2804              :     }
    2805              : 
    2806        30178 :     return EngravingItem::propertyDefault(id);
    2807              : }
    2808              : 
    2809              : //---------------------------------------------------------
    2810              : //   getPropertyFlagsIdx
    2811              : //---------------------------------------------------------
    2812              : 
    2813       190201 : int TextBase::getPropertyFlagsIdx(Pid id) const
    2814              : {
    2815       190201 :     int i = 0;
    2816       626498 :     for (const StyledProperty& p : *m_elementStyle) {
    2817       453986 :         if (p.pid == id) {
    2818        17689 :             return i;
    2819              :         }
    2820       436297 :         ++i;
    2821              :     }
    2822      1953739 :     for (const auto& p : *textStyle(textStyleType())) {
    2823      1889926 :         if (p.pid == id) {
    2824       108699 :             return i;
    2825              :         }
    2826      1781227 :         ++i;
    2827              :     }
    2828        63813 :     return -1;
    2829              : }
    2830              : 
    2831              : //---------------------------------------------------------
    2832              : //   offsetSid
    2833              : //---------------------------------------------------------
    2834              : 
    2835        73763 : Sid TextBase::defaultPosSid() const
    2836              : {
    2837        73763 :     const OffsetSids offsets = textStyle(textStyleType())->offsetSids;
    2838       147526 :     return placeAbove() ? offsets.above : offsets.below;
    2839              : }
    2840              : 
    2841        76276 : PointF TextBase::defaultPos() const
    2842              : {
    2843        76276 :     if (parent()->isTextLineBaseSegment()) {
    2844         2513 :         return PointF();
    2845              :     }
    2846              : 
    2847        73763 :     return EngravingItem::defaultPos();
    2848              : }
    2849              : 
    2850              : //---------------------------------------------------------
    2851              : //   getHtmlStartTag - helper function for extractText with withFormat = true
    2852              : //---------------------------------------------------------
    2853            0 : String TextBase::getHtmlStartTag(double newSize, double& curSize, const String& newFamily, String& curFamily, FontStyle style,
    2854              :                                  VerticalAlignment vAlign)
    2855              : {
    2856            0 :     String s;
    2857            0 :     if (std::fabs(newSize - curSize) > 0.1) {
    2858            0 :         curSize = newSize;
    2859            0 :         s += String(u"<font size=\"%1\"/>").arg(newSize);
    2860              :     }
    2861            0 :     if (newFamily != curFamily && newFamily != "ScoreText") {
    2862            0 :         curFamily = newFamily;
    2863            0 :         s += String(u"<font face=\"%1\"/>").arg(newFamily);
    2864              :     }
    2865            0 :     if (style & FontStyle::Bold) {
    2866            0 :         s += u"<b>";
    2867              :     }
    2868            0 :     if (style & FontStyle::Italic) {
    2869            0 :         s += u"<i>";
    2870              :     }
    2871            0 :     if (style & FontStyle::Underline) {
    2872            0 :         s += u"<u>";
    2873              :     }
    2874            0 :     if (style & mu::engraving::FontStyle::Strike) {
    2875            0 :         s += u"<s>";
    2876              :     }
    2877            0 :     if (vAlign == VerticalAlignment::AlignSubScript) {
    2878            0 :         s += u"<sub>";
    2879            0 :     } else if (vAlign == VerticalAlignment::AlignSuperScript) {
    2880            0 :         s += u"<sup>";
    2881              :     }
    2882            0 :     return s;
    2883            0 : }
    2884              : 
    2885              : //---------------------------------------------------------
    2886              : //   getHtmlEndTag - helper function for extractText with withFormat = true
    2887              : //---------------------------------------------------------
    2888            0 : String TextBase::getHtmlEndTag(FontStyle style, VerticalAlignment vAlign)
    2889              : {
    2890            0 :     String s;
    2891            0 :     if (vAlign == VerticalAlignment::AlignSubScript) {
    2892            0 :         s += u"</sub>";
    2893            0 :     } else if (vAlign == VerticalAlignment::AlignSuperScript) {
    2894            0 :         s += u"</sup>";
    2895              :     }
    2896            0 :     if (style & FontStyle::Strike) {
    2897            0 :         s += u"</s>";
    2898              :     }
    2899            0 :     if (style & FontStyle::Underline) {
    2900            0 :         s += u"</u>";
    2901              :     }
    2902            0 :     if (style & FontStyle::Italic) {
    2903            0 :         s += u"</i>";
    2904              :     }
    2905            0 :     if (style & FontStyle::Bold) {
    2906            0 :         s += u"</b>";
    2907              :     }
    2908            0 :     return s;
    2909            0 : }
    2910              : 
    2911              : #ifndef ENGRAVING_NO_ACCESSIBILITY
    2912            0 : AccessibleItemPtr TextBase::createAccessible()
    2913              : {
    2914            0 :     return std::make_shared<AccessibleItem>(this, AccessibleItem::EditableText);
    2915              : }
    2916              : 
    2917              : #endif
    2918              : 
    2919            0 : void TextBase::notifyAboutTextCursorChanged()
    2920              : {
    2921              : #ifndef ENGRAVING_NO_ACCESSIBILITY
    2922              :     using namespace muse::accessibility;
    2923            0 :     if (accessible()) {
    2924            0 :         accessible()->accessiblePropertyChanged().send(IAccessible::Property::TextCursor, muse::Val());
    2925              :     }
    2926              : #endif
    2927            0 : }
    2928              : 
    2929            1 : void TextBase::notifyAboutTextInserted(int startPosition, int endPosition, const String& text)
    2930              : {
    2931              : #ifndef ENGRAVING_NO_ACCESSIBILITY
    2932              :     using namespace muse::accessibility;
    2933            1 :     if (accessible()) {
    2934            0 :         auto range = IAccessible::TextRange(startPosition, endPosition, text);
    2935            0 :         accessible()->accessiblePropertyChanged().send(IAccessible::Property::TextInsert, muse::Val::fromQVariant(range.toMap()));
    2936            0 :     }
    2937              : #else
    2938              :     UNUSED(startPosition);
    2939              :     UNUSED(endPosition);
    2940              :     UNUSED(text);
    2941              : #endif
    2942            1 : }
    2943              : 
    2944            0 : void TextBase::notifyAboutTextRemoved(int startPosition, int endPosition, const String& text)
    2945              : {
    2946              : #ifndef ENGRAVING_NO_ACCESSIBILITY
    2947              :     using namespace muse::accessibility;
    2948            0 :     if (accessible()) {
    2949            0 :         auto range = IAccessible::TextRange(startPosition, endPosition, text);
    2950            0 :         accessible()->accessiblePropertyChanged().send(IAccessible::Property::TextRemove, muse::Val::fromQVariant(range.toMap()));
    2951            0 :     }
    2952              : #else
    2953              :     UNUSED(startPosition);
    2954              :     UNUSED(endPosition);
    2955              :     UNUSED(text);
    2956              : #endif
    2957            0 : }
    2958              : 
    2959              : //---------------------------------------------------------
    2960              : //   getPropertyStyle
    2961              : //---------------------------------------------------------
    2962              : 
    2963       295713 : Sid TextBase::getPropertyStyle(Pid id) const
    2964              : {
    2965       500188 :     for (const StyledProperty& p : *m_elementStyle) {
    2966       206035 :         if (p.pid == id) {
    2967         1560 :             return p.sid;
    2968              :         }
    2969              :     }
    2970      1902139 :     for (const auto& p : *textStyle(textStyleType())) {
    2971      1835081 :         if (p.pid == id) {
    2972       227095 :             return p.sid;
    2973              :         }
    2974              :     }
    2975        67058 :     return Sid::NOSTYLE;
    2976              : }
    2977              : 
    2978              : //---------------------------------------------------------
    2979              : //   styleChanged
    2980              : //---------------------------------------------------------
    2981              : 
    2982          659 : void TextBase::styleChanged()
    2983              : {
    2984          659 :     if (!styledProperties()) {
    2985            0 :         LOGD("no styled properties");
    2986            0 :         return;
    2987              :     }
    2988          659 :     int i = 0;
    2989         1977 :     for (const StyledProperty& spp : *m_elementStyle) {
    2990         1318 :         PropertyFlags f = m_propertyFlagsList[i];
    2991         1318 :         if (f == PropertyFlags::STYLED) {
    2992         1317 :             setProperty(spp.pid, styleValue(spp.pid, getPropertyStyle(spp.pid)));
    2993              :         }
    2994         1318 :         ++i;
    2995              :     }
    2996        11203 :     for (const auto& spp : *textStyle(textStyleType())) {
    2997        10544 :         PropertyFlags f = m_propertyFlagsList[i];
    2998        10544 :         if (f == PropertyFlags::STYLED) {
    2999        10512 :             setProperty(spp.pid, styleValue(spp.pid, getPropertyStyle(spp.pid)));
    3000              :         }
    3001        10544 :         ++i;
    3002              :     }
    3003              : }
    3004              : 
    3005              : //---------------------------------------------------------
    3006              : //   initElementStyle
    3007              : //---------------------------------------------------------
    3008              : 
    3009        36351 : void TextBase::initElementStyle(const ElementStyle* ss)
    3010              : {
    3011        36351 :     m_elementStyle = ss;
    3012        36351 :     size_t n      = ss->size() + TEXT_STYLE_SIZE;
    3013              : 
    3014        36351 :     delete[] m_propertyFlagsList;
    3015        36351 :     m_propertyFlagsList = new PropertyFlags[n];
    3016       659030 :     for (size_t i = 0; i < n; ++i) {
    3017       622679 :         m_propertyFlagsList[i] = PropertyFlags::STYLED;
    3018              :     }
    3019        77414 :     for (const StyledProperty& p : *m_elementStyle) {
    3020        41063 :         setProperty(p.pid, styleValue(p.pid, p.sid));
    3021              :     }
    3022       617967 :     for (const auto& p : *textStyle(textStyleType())) {
    3023       581616 :         setProperty(p.pid, styleValue(p.pid, p.sid));
    3024              :     }
    3025        36351 : }
    3026              : 
    3027              : //---------------------------------------------------------
    3028              : //   initTid
    3029              : //---------------------------------------------------------
    3030              : 
    3031          473 : void TextBase::initTextStyleType(TextStyleType tid, bool preserveDifferent)
    3032              : {
    3033          473 :     if (!preserveDifferent) {
    3034            0 :         initTextStyleType(tid);
    3035            0 :         return;
    3036              :     }
    3037              : 
    3038          473 :     const LayoutData* ldata = this->ldata();
    3039          473 :     if (!ldata || ldata->layoutInvalid) {
    3040          473 :         createBlocks();
    3041              :     }
    3042              : 
    3043              :     // Before setting the new style - check if any fragments contain custom formatting. If they do, preserve
    3044              :     // the old values for face, size, and style...
    3045          473 :     const bool hadCustomFragments = hasCustomFormatting();
    3046              : 
    3047          473 :     setTextStyleType(tid);
    3048              : 
    3049         8041 :     for (const auto& p : *textStyle(tid)) {
    3050         7568 :         const bool isFragmentStyle = p.pid == Pid::FONT_FACE || p.pid == Pid::FONT_SIZE || p.pid == Pid::FONT_STYLE;
    3051         7568 :         if (isFragmentStyle && hadCustomFragments) {
    3052           66 :             continue;
    3053              :         }
    3054         7502 :         if (getProperty(p.pid) == propertyDefault(p.pid)) {
    3055         7494 :             setProperty(p.pid, styleValue(p.pid, p.sid));
    3056              :         }
    3057              :     }
    3058              : }
    3059              : 
    3060         4558 : void TextBase::initTextStyleType(TextStyleType tid)
    3061              : {
    3062        72928 :     auto getTextPID = [&](Pid p) -> Pid {
    3063              :         static const std::vector<std::pair<Pid, Pid> > TEXT_LINE_PID_MAP = { { Pid::FONT_FACE, Pid::BEGIN_FONT_FACE },
    3064              :             { Pid::FONT_SIZE, Pid::BEGIN_FONT_SIZE },
    3065              :             { Pid::FONT_STYLE, Pid::BEGIN_FONT_STYLE },
    3066              :             { Pid::ALIGN, Pid::BEGIN_TEXT_ALIGN },
    3067              :             { Pid::POSITION, Pid::BEGIN_TEXT_POSITION },
    3068        72954 :         };
    3069              : 
    3070        72928 :         const bool isTextLine = parent()->isTextLineBaseSegment();
    3071       369214 :         for (const auto& pidPair : TEXT_LINE_PID_MAP) {
    3072       319060 :             const Pid textPid = pidPair.first;
    3073       319060 :             const Pid textLinePid = pidPair.second;
    3074              : 
    3075       319060 :             if (p == textLinePid || p == textPid) {
    3076        22774 :                 return isTextLine ? textLinePid : textPid;
    3077              :             }
    3078              :         }
    3079              : 
    3080        50154 :         return p;
    3081         4558 :     };
    3082              : 
    3083         4558 :     setTextStyleType(tid);
    3084        77486 :     for (const auto& p : *textStyle(tid)) {
    3085        72928 :         setProperty(getTextPID(p.pid), styleValue(p.pid, p.sid));
    3086              :     }
    3087         4558 : }
    3088              : 
    3089            0 : RectF TextBase::drag(EditData& ed)
    3090              : {
    3091            0 :     RectF result = EngravingItem::drag(ed);
    3092              : 
    3093            0 :     MoveElementAnchors::moveElementAnchorsOnDrag(this, ed);
    3094              : 
    3095            0 :     return result;
    3096              : }
    3097              : 
    3098            0 : void TextBase::endDrag(EditData& ed)
    3099              : {
    3100            0 :     if (m_cursor->editing()) {
    3101            0 :         return;
    3102              :     }
    3103            0 :     EngravingItem::endDrag(ed);
    3104              : }
    3105              : 
    3106              : //---------------------------------------------------------
    3107              : //   editCut
    3108              : //---------------------------------------------------------
    3109              : 
    3110            0 : void TextBase::editCut(EditData& ed)
    3111              : {
    3112            0 :     TextEditData* ted = static_cast<TextEditData*>(ed.getData(this).get());
    3113            0 :     String s = cursor()->selectedText(true);
    3114              : 
    3115            0 :     if (!s.isEmpty()) {
    3116            0 :         ted->selectedText = cursor()->selectedText(true);
    3117            0 :         ed.curGrip = Grip::START;
    3118            0 :         ed.key     = Key_Delete;
    3119            0 :         ed.s       = String();
    3120            0 :         edit(ed);
    3121              :     }
    3122            0 : }
    3123              : 
    3124              : //---------------------------------------------------------
    3125              : //   editCopy
    3126              : //---------------------------------------------------------
    3127              : 
    3128            0 : void TextBase::editCopy(EditData& ed)
    3129              : {
    3130              :     //
    3131              :     // store selection as rich and plain text
    3132              :     //
    3133            0 :     TextEditData* ted = static_cast<TextEditData*>(ed.getData(this).get());
    3134            0 :     ted->selectedText = cursor()->selectedText(true);
    3135            0 :     ted->selectedPlainText = cursor()->selectedText(false);
    3136            0 : }
    3137              : 
    3138            0 : bool TextBase::nudge(const EditData& ed)
    3139              : {
    3140            0 :     bool ctrlMod = ed.modifiers & ControlModifier;
    3141            0 :     double step = spatium() * (ctrlMod ? MScore::nudgeStep10 : MScore::nudgeStep);
    3142            0 :     PointF addOffset = PointF();
    3143            0 :     switch (ed.key) {
    3144            0 :     case Key_Up:
    3145            0 :         addOffset = PointF(0.0, -step);
    3146            0 :         break;
    3147            0 :     case Key_Down:
    3148            0 :         addOffset = PointF(0.0, step);
    3149            0 :         break;
    3150            0 :     case Key_Left:
    3151            0 :         addOffset = PointF(-step, 0.0);
    3152            0 :         break;
    3153            0 :     case Key_Right:
    3154            0 :         addOffset = PointF(step, 0.0);
    3155            0 :         break;
    3156            0 :     default:
    3157            0 :         return false;
    3158              :     }
    3159            0 :     undoChangeProperty(Pid::OFFSET, offset() + addOffset, PropertyFlags::UNSTYLED);
    3160            0 :     return true;
    3161              : }
    3162              : 
    3163              : //---------------------------------------------------------
    3164              : //   hasCustomFormatting
    3165              : //---------------------------------------------------------
    3166              : 
    3167          473 : bool TextBase::hasCustomFormatting() const
    3168              : {
    3169          473 :     const LayoutData* ldata = this->ldata();
    3170          473 :     IF_ASSERT_FAILED(ldata) {
    3171            0 :         return false;
    3172              :     }
    3173              : 
    3174          473 :     CharFormat fmt;
    3175          473 :     fmt.setFontFamily(family());
    3176          473 :     fmt.setFontSize(size());
    3177          473 :     fmt.setStyle(fontStyle());
    3178          473 :     fmt.setValign(VerticalAlignment::AlignNormal);
    3179              : 
    3180          924 :     for (const TextBlock& block : ldata->blocks) {
    3181          939 :         for (const TextFragment& f : block.fragments()) {
    3182          488 :             if (f.text.isEmpty()) {                         // skip empty fragments, not to
    3183            0 :                 continue;                                   // insert extra HTML formatting
    3184              :             }
    3185          488 :             const CharFormat& format = f.format;
    3186          488 :             if (fmt.style() != format.style()) {
    3187           22 :                 return true;
    3188              :             }
    3189          487 :             if (format.fontSize() != fmt.fontSize()) {
    3190            0 :                 return true;
    3191              :             }
    3192          487 :             if (format.fontFamily() != fmt.fontFamily()) {
    3193           21 :                 return true;
    3194              :             }
    3195              : 
    3196          466 :             VerticalAlignment va = format.valign();
    3197          466 :             VerticalAlignment cva = fmt.valign();
    3198          466 :             if (cva != va) {
    3199            0 :                 return true;
    3200              :             }
    3201              :         }
    3202              :     }
    3203          451 :     return false;
    3204          473 : }
    3205              : 
    3206              : //---------------------------------------------------------
    3207              : //   stripText
    3208              : //    remove some custom text formatting and return
    3209              : //    result as xml string
    3210              : //---------------------------------------------------------
    3211              : 
    3212            0 : String TextBase::stripText(bool removeStyle, bool removeSize, bool removeFace) const
    3213              : {
    3214            0 :     const LayoutData* ldata = this->ldata();
    3215            0 :     IF_ASSERT_FAILED(ldata) {
    3216            0 :         return String();
    3217              :     }
    3218              : 
    3219            0 :     String _txt;
    3220            0 :     bool bold_      = false;
    3221            0 :     bool italic_    = false;
    3222            0 :     bool underline_ = false;
    3223            0 :     bool strike_    = false;
    3224              : 
    3225            0 :     for (const TextBlock& block : ldata->blocks) {
    3226            0 :         for (const TextFragment& f : block.fragments()) {
    3227            0 :             if (!f.format.bold() && bold()) {
    3228            0 :                 bold_ = true;
    3229              :             }
    3230            0 :             if (!f.format.italic() && italic()) {
    3231            0 :                 italic_ = true;
    3232              :             }
    3233            0 :             if (!f.format.underline() && underline()) {
    3234            0 :                 underline_ = true;
    3235              :             }
    3236            0 :             if (!f.format.strike() && strike()) {
    3237            0 :                 strike_ = true;
    3238              :             }
    3239              :         }
    3240              :     }
    3241            0 :     CharFormat fmt;
    3242            0 :     fmt.setFontFamily(family());
    3243            0 :     fmt.setFontSize(size());
    3244            0 :     fmt.setStyle(fontStyle());
    3245            0 :     fmt.setValign(VerticalAlignment::AlignNormal);
    3246              : 
    3247            0 :     XmlNesting xmlNesting(&_txt);
    3248            0 :     if (!removeStyle) {
    3249            0 :         if (bold_) {
    3250            0 :             xmlNesting.pushB();
    3251              :         }
    3252            0 :         if (italic_) {
    3253            0 :             xmlNesting.pushI();
    3254              :         }
    3255            0 :         if (underline_) {
    3256            0 :             xmlNesting.pushU();
    3257              :         }
    3258            0 :         if (strike_) {
    3259            0 :             xmlNesting.pushS();
    3260              :         }
    3261              :     }
    3262              : 
    3263            0 :     for (const TextBlock& block : ldata->blocks) {
    3264            0 :         for (const TextFragment& f : block.fragments()) {
    3265            0 :             if (f.text.isEmpty()) {                         // skip empty fragments, not to
    3266            0 :                 continue;                                   // insert extra HTML formatting
    3267              :             }
    3268            0 :             const CharFormat& format = f.format;
    3269            0 :             if (!removeStyle) {
    3270            0 :                 if (fmt.bold() != format.bold()) {
    3271            0 :                     if (format.bold()) {
    3272            0 :                         xmlNesting.pushB();
    3273              :                     } else {
    3274            0 :                         xmlNesting.popB();
    3275              :                     }
    3276              :                 }
    3277            0 :                 if (fmt.italic() != format.italic()) {
    3278            0 :                     if (format.italic()) {
    3279            0 :                         xmlNesting.pushI();
    3280              :                     } else {
    3281            0 :                         xmlNesting.popI();
    3282              :                     }
    3283              :                 }
    3284            0 :                 if (fmt.underline() != format.underline()) {
    3285            0 :                     if (format.underline()) {
    3286            0 :                         xmlNesting.pushU();
    3287              :                     } else {
    3288            0 :                         xmlNesting.popU();
    3289              :                     }
    3290              :                 }
    3291            0 :                 if (fmt.strike() != format.strike()) {
    3292            0 :                     if (format.strike()) {
    3293            0 :                         xmlNesting.pushS();
    3294              :                     } else {
    3295            0 :                         xmlNesting.popS();
    3296              :                     }
    3297              :                 }
    3298              :             }
    3299              : 
    3300            0 :             if (!removeSize && (format.fontSize() != fmt.fontSize())) {
    3301            0 :                 _txt += String(u"<font size=\"%1\"/>").arg(format.fontSize());
    3302              :             }
    3303            0 :             if (!removeFace && (format.fontFamily() != fmt.fontFamily())) {
    3304            0 :                 _txt += String(u"<font face=\"%1\"/>").arg(TextBase::escape(format.fontFamily()));
    3305              :             }
    3306              : 
    3307            0 :             VerticalAlignment va = format.valign();
    3308            0 :             VerticalAlignment cva = fmt.valign();
    3309            0 :             if (cva != va) {
    3310            0 :                 switch (va) {
    3311            0 :                 case VerticalAlignment::AlignNormal:
    3312            0 :                     xmlNesting.popToken(cva == VerticalAlignment::AlignSuperScript ? "sup" : "sub");
    3313            0 :                     break;
    3314            0 :                 case VerticalAlignment::AlignSuperScript:
    3315            0 :                     xmlNesting.pushToken(u"sup");
    3316            0 :                     break;
    3317            0 :                 case VerticalAlignment::AlignSubScript:
    3318            0 :                     xmlNesting.pushToken(u"sub");
    3319            0 :                     break;
    3320            0 :                 case VerticalAlignment::AlignUndefined:
    3321            0 :                     break;
    3322              :                 }
    3323              :             }
    3324            0 :             _txt += XmlWriter::xmlString(f.text);
    3325            0 :             fmt = format;
    3326              :         }
    3327            0 :         if (block.eol()) {
    3328            0 :             _txt += Char::LineFeed;
    3329              :         }
    3330              :     }
    3331            0 :     while (!xmlNesting.empty()) {
    3332            0 :         xmlNesting.popToken();
    3333              :     }
    3334            0 :     return _txt;
    3335            0 : }
    3336              : 
    3337              : //---------------------------------------------------------
    3338              : //   undoChangeProperty
    3339              : //---------------------------------------------------------
    3340              : 
    3341          262 : void TextBase::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
    3342              : {
    3343          262 :     if (ps == PropertyFlags::STYLED && v == propertyDefault(id)) {
    3344              :         // this is a reset
    3345              :         // remove some custom formatting
    3346           99 :         if (id == Pid::FONT_STYLE) {
    3347            0 :             undoChangeProperty(Pid::TEXT, stripText(true, false, false), propertyFlags(id));
    3348           99 :         } else if (id == Pid::FONT_SIZE) {
    3349            0 :             undoChangeProperty(Pid::TEXT, stripText(false, true, false), propertyFlags(id));
    3350           99 :         } else if (id == Pid::FONT_FACE) {
    3351            0 :             undoChangeProperty(Pid::TEXT, stripText(false, false, true), propertyFlags(id));
    3352              :         }
    3353              :     }
    3354              : 
    3355              :     static const PropertyIdSet CHARACTER_SPECIFIC_PROPERTIES {
    3356              :         Pid::FONT_STYLE,
    3357              :         Pid::FONT_FACE,
    3358              :         Pid::FONT_SIZE,
    3359              :         Pid::TEXT_SCRIPT_ALIGN
    3360          270 :     };
    3361              : 
    3362          262 :     if (!muse::contains(CHARACTER_SPECIFIC_PROPERTIES, id)) {
    3363          256 :         EngravingItem::undoChangeProperty(id, v, ps);
    3364          256 :         return;
    3365              :     }
    3366              : 
    3367            6 :     score()->undo(new ChangeTextProperties(m_cursor, id, v, ps));
    3368              : 
    3369            6 :     if (m_cursor->editing()) {
    3370              :         // If we're in edit mode, changes will be propagated later when
    3371              :         // propagating the Pid::TEXT property, so don't propagate now.
    3372            0 :         return;
    3373              :     }
    3374              : 
    3375            6 :     const std::list<EngravingObject*> linkedObjects = linkListForPropertyPropagation();
    3376           12 :     for (EngravingObject* linkedObject : linkedObjects) {
    3377            6 :         TextBase* linkedText = toTextBase(linkedObject);
    3378            6 :         if (linkedText == this) {
    3379            6 :             continue;
    3380              :         }
    3381              : 
    3382            0 :         Score* linkedScore = linkedText->score();
    3383            0 :         TextCursor* linkedCursor = linkedText->cursor();
    3384            0 :         PropertyPropagation propertyPropagate = propertyPropagation(linkedText, id);
    3385              : 
    3386            0 :         switch (propertyPropagate) {
    3387            0 :         case PropertyPropagation::PROPAGATE:
    3388            0 :             linkedScore->undo(new ChangeTextProperties(linkedCursor, id, v, ps));
    3389            0 :             break;
    3390            0 :         case PropertyPropagation::UNLINK:
    3391            0 :             unlinkPropertyFromMaster(id);
    3392            0 :             break;
    3393            0 :         default:
    3394            0 :             break;
    3395              :         }
    3396              :     }
    3397              : 
    3398            6 :     for (EngravingItem* originItem : originItems()) {
    3399              :         // This is a shared item: propagate to all origin items
    3400            0 :         score()->undo(new ChangeTextProperties(toTextBase(originItem)->cursor(), id, v, ps));
    3401              :     }
    3402            6 :     if (EngravingItem* sharedEl = sharedItem(); sharedEl && sharedEl->originItems().front() == this) {
    3403              :         // This is the first origin item of the shared item: propagate to shared item
    3404            0 :         score()->undo(new ChangeTextProperties(toTextBase(sharedEl)->cursor(), id, v, ps));
    3405              :     }
    3406            6 : }
    3407              : 
    3408        11044 : bool mu::engraving::TextBase::hasSymbolScale() const
    3409              : {
    3410        11044 :     bool hasSymbolScale = isDynamic()
    3411         8475 :                           || isStringTunings()
    3412         8475 :                           || isPlayTechAnnotation()
    3413         8406 :                           || (isHarpPedalDiagram() && toHarpPedalDiagram(this)->isDiagram())
    3414         8379 :                           || (parent() && parent()->isOttavaSegment())
    3415         7890 :                           || (parent() && parent()->isTuplet())
    3416        19519 :                           || (parent() && parent()->isPedalSegment());
    3417              : 
    3418        11044 :     return hasSymbolScale;
    3419              : }
    3420              : 
    3421       161780 : double TextBase::spatiumScaling() const
    3422              : {
    3423              :     double spatiumScaling;
    3424              : 
    3425       161780 :     if (isInstrumentName()) {
    3426       102501 :         spatiumScaling = toInstrumentName(this)->largestStaffSpatium() / defaultSpatium();
    3427              :     } else {
    3428        59279 :         spatiumScaling = spatium() / defaultSpatium();
    3429              :     }
    3430              : 
    3431       161780 :     return spatiumScaling;
    3432              : }
    3433              : }
        

Generated by: LCOV version 2.0-1