LCOV - code coverage report
Current view: top level - src/engraving/rw/read206 - read206.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 65.7 % 2392 1572
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 "read206.h"
      24              : 
      25              : #include <cmath>
      26              : 
      27              : #include "global/defer.h"
      28              : 
      29              : #include "compat/pageformat.h"
      30              : 
      31              : #include "engravingerrors.h"
      32              : 
      33              : #include "iengravingfont.h"
      34              : 
      35              : #include "rw/compat/compatutils.h"
      36              : 
      37              : #include "style/style.h"
      38              : #include "style/textstyle.h"
      39              : 
      40              : #include "types/typesconv.h"
      41              : #include "types/symnames.h"
      42              : 
      43              : #include "dom/accidental.h"
      44              : #include "dom/ambitus.h"
      45              : #include "dom/arpeggio.h"
      46              : #include "dom/articulation.h"
      47              : #include "dom/barline.h"
      48              : #include "dom/beam.h"
      49              : #include "dom/bend.h"
      50              : #include "dom/box.h"
      51              : #include "dom/breath.h"
      52              : #include "dom/chord.h"
      53              : #include "dom/chordline.h"
      54              : #include "dom/drumset.h"
      55              : #include "dom/dynamic.h"
      56              : #include "dom/excerpt.h"
      57              : #include "dom/factory.h"
      58              : #include "dom/fermata.h"
      59              : #include "dom/fingering.h"
      60              : #include "dom/glissando.h"
      61              : #include "dom/hairpin.h"
      62              : #include "dom/hook.h"
      63              : #include "dom/image.h"
      64              : #include "dom/keysig.h"
      65              : #include "dom/linkedobjects.h"
      66              : #include "dom/lyrics.h"
      67              : #include "dom/marker.h"
      68              : #include "dom/masterscore.h"
      69              : #include "dom/measure.h"
      70              : #include "dom/measurenumber.h"
      71              : #include "dom/measurerepeat.h"
      72              : #include "dom/mmrest.h"
      73              : #include "dom/note.h"
      74              : #include "dom/noteline.h"
      75              : #include "dom/ornament.h"
      76              : #include "dom/ottava.h"
      77              : #include "dom/page.h"
      78              : #include "dom/part.h"
      79              : #include "dom/pedal.h"
      80              : #include "dom/rehearsalmark.h"
      81              : #include "dom/rest.h"
      82              : #include "dom/score.h"
      83              : #include "dom/sig.h"
      84              : #include "dom/slur.h"
      85              : #include "dom/spacer.h"
      86              : #include "dom/staff.h"
      87              : #include "dom/staff.h"
      88              : #include "dom/stafftext.h"
      89              : #include "dom/stem.h"
      90              : #include "dom/stemslash.h"
      91              : #include "dom/system.h"
      92              : #include "dom/systemdivider.h"
      93              : #include "dom/systemtext.h"
      94              : #include "dom/tempotext.h"
      95              : #include "dom/textline.h"
      96              : #include "dom/tie.h"
      97              : #include "dom/timesig.h"
      98              : #include "dom/trill.h"
      99              : #include "dom/tremolotwochord.h"
     100              : #include "dom/tremolosinglechord.h"
     101              : #include "dom/tuplet.h"
     102              : #include "dom/utils.h"
     103              : #include "dom/volta.h"
     104              : 
     105              : #include "../../editing/editstaffbrackets.h"
     106              : #include "../../editing/transpose.h"
     107              : 
     108              : #include "../compat/readchordlisthook.h"
     109              : #include "../compat/readstyle.h"
     110              : 
     111              : #include "engraving/rw/read114/read114.h"
     112              : #include "engraving/rw/read400/tread.h"
     113              : 
     114              : #include "log.h"
     115              : 
     116              : using namespace mu;
     117              : using namespace muse;
     118              : using namespace mu::engraving;
     119              : using namespace mu::engraving::rw;
     120              : using namespace mu::engraving::read400;
     121              : using namespace mu::engraving::read206;
     122              : using namespace mu::engraving::compat;
     123              : 
     124              : static void readText206(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem* be);
     125              : 
     126           26 : static int readSigEvent(SigEvent* sigEvent, XmlReader& e, int fileDivision)
     127              : {
     128           26 :     int tick  = e.intAttribute("tick", 0);
     129           26 :     tick      = tick * Constants::DIVISION / fileDivision;
     130              : 
     131           26 :     int numerator = 1;
     132           26 :     int denominator = 1;
     133           26 :     int denominator2 = -1;
     134           26 :     int numerator2   = -1;
     135              : 
     136           78 :     while (e.readNextStartElement()) {
     137           52 :         const AsciiStringView tag(e.name());
     138           52 :         if (tag == "nom") {
     139           26 :             numerator = e.readInt();
     140           26 :         } else if (tag == "denom") {
     141           26 :             denominator = e.readInt();
     142            0 :         } else if (tag == "nom2") {
     143            0 :             numerator2 = e.readInt();
     144            0 :         } else if (tag == "denom2") {
     145            0 :             denominator2 = e.readInt();
     146              :         } else {
     147            0 :             e.unknown();
     148              :         }
     149              :     }
     150           26 :     if ((numerator2 == -1) || (denominator2 == -1)) {
     151           26 :         numerator2   = numerator;
     152           26 :         denominator2 = denominator;
     153              :     }
     154              : 
     155           26 :     sigEvent->setTimesig(TimeSigFrac(numerator, denominator));
     156           26 :     sigEvent->setNominal(TimeSigFrac(numerator2, denominator2));
     157           26 :     return tick;
     158              : }
     159              : 
     160           26 : void Read206::readTimeSigMap(TimeSigMap* map, XmlReader& e, read400::ReadContext& ctx)
     161              : {
     162           52 :     while (e.readNextStartElement()) {
     163           26 :         const AsciiStringView tag(e.name());
     164           26 :         if (tag == "sig") {
     165           26 :             SigEvent t;
     166           26 :             int tick = readSigEvent(&t, e, ctx.fileDivision());
     167           26 :             (*map)[tick] = t;
     168              :         } else {
     169            0 :             e.unknown();
     170              :         }
     171              :     }
     172           26 :     map->normalize();
     173           26 : }
     174              : 
     175              : //---------------------------------------------------------
     176              : //   excessTextStyles206
     177              : //    The first map has the name of the style as the string
     178              : //    The second map has the mapping of each Sid that the style identifies
     179              : //    to the default value for that sid.
     180              : //---------------------------------------------------------
     181              : 
     182              : static std::map<String, std::map<Sid, PropertyValue> > excessTextStyles206;
     183              : 
     184          107 : void Read206::readTextStyle206(MStyle* style, XmlReader& e, ReadContext& ctx, std::map<String, std::map<Sid, PropertyValue> >& excessStyles)
     185              : {
     186          107 :     String family = u"FreeSerif";
     187          107 :     double size = 10;
     188          107 :     bool sizeIsSpatiumDependent = false;
     189          107 :     FontStyle fontStyle = FontStyle::Normal;
     190          107 :     Align align = { AlignH::LEFT, AlignV::TOP };
     191          107 :     PointF offset;
     192          107 :     OffsetType offsetType = OffsetType::SPATIUM;
     193              : 
     194          107 :     FrameType frameType = FrameType::NO_FRAME;
     195          107 :     Spatium paddingWidth(0.0);
     196          107 :     Spatium frameWidth(0.0);
     197          107 :     Color foregroundColor = Color::BLACK;
     198          107 :     Color backgroundColor = Color::transparent;
     199              : 
     200          107 :     PlacementV placement = PlacementV::ABOVE;
     201          107 :     bool placementValid = false;
     202              : 
     203          107 :     String name = e.attribute("name");
     204          107 :     Color frameColor = Color::BLACK;
     205              : 
     206          107 :     bool systemFlag = false;
     207          107 :     double lineWidth = -1.0;
     208              : 
     209          942 :     while (e.readNextStartElement()) {
     210          835 :         const AsciiStringView tag(e.name());
     211              : 
     212          835 :         if (tag == "name") {
     213          106 :             name = e.readText();
     214          729 :         } else if (tag == "family") {
     215          107 :             family = e.readText();
     216          622 :         } else if (tag == "size") {
     217          107 :             size = e.readDouble();
     218          515 :         } else if (tag == "bold") {
     219           13 :             if (e.readInt()) {
     220           13 :                 fontStyle = fontStyle + FontStyle::Bold;
     221              :             }
     222          502 :         } else if (tag == "italic") {
     223           16 :             if (e.readInt()) {
     224           16 :                 fontStyle = fontStyle + FontStyle::Italic;
     225              :             }
     226          486 :         } else if (tag == "underline") {
     227            0 :             if (e.readInt()) {
     228            0 :                 fontStyle = fontStyle + FontStyle::Underline;
     229              :             }
     230              : #if 0 // should not happen, but won't harm either
     231              :         } else if (tag == "strike") {
     232              :             if (e.readInt()) {
     233              :                 fontStyle = fontStyle + FontStyle::Strike;
     234              :             }
     235              : #endif
     236          486 :         } else if (tag == "align") {      // obsolete
     237            0 :             e.skipCurrentElement();
     238          486 :         } else if (tag == "anchor") {     // obsolete
     239            0 :             e.skipCurrentElement();
     240          486 :         } else if (tag == "halign") {
     241          107 :             align.horizontal = TConv::fromXml(e.readAsciiText(), AlignH::LEFT);
     242          379 :         } else if (tag == "valign") {
     243          107 :             align.vertical = TConv::fromXml(e.readAsciiText(), AlignV::TOP);
     244          272 :         } else if (tag == "xoffset") {
     245           41 :             double xo = e.readDouble();
     246           41 :             if (offsetType == OffsetType::ABS) {
     247            0 :                 xo /= INCH;
     248              :             }
     249           41 :             offset.setX(xo);
     250          231 :         } else if (tag == "yoffset") {
     251           41 :             double yo = e.readDouble();
     252           41 :             if (offsetType == OffsetType::ABS) {
     253            0 :                 yo /= INCH;
     254              :             }
     255           41 :             offset.setY(yo);
     256          190 :         } else if (tag == "rxoffset" || tag == "ryoffset") {         // obsolete
     257            0 :             e.readDouble();
     258          190 :         } else if (tag == "offsetType") {
     259          107 :             const String& val(e.readText());
     260          107 :             OffsetType ot = OffsetType::ABS;
     261          107 :             if (val == "spatium" || val == "1") {
     262           83 :                 ot = OffsetType::SPATIUM;
     263              :             }
     264          107 :             if (ot != offsetType) {
     265           24 :                 offsetType = ot;
     266           24 :                 if (ot == OffsetType::ABS) {
     267           24 :                     offset /= INCH;            // convert spatium -> inch
     268              :                 } else {
     269            0 :                     offset *= INCH;            // convert inch -> spatium
     270              :                 }
     271              :             }
     272          190 :         } else if (tag == "sizeIsSpatiumDependent" || tag == "spatiumSizeDependent") {
     273           58 :             sizeIsSpatiumDependent = e.readInt();
     274           25 :         } else if (tag == "frameWidth") {   // obsolete
     275            0 :             frameType = FrameType::RECTANGLE;
     276            0 :             /*frameWidthMM =*/ e.readDouble();
     277           25 :         } else if (tag == "frameWidthS") {
     278            3 :             frameType = FrameType::RECTANGLE;
     279            3 :             frameWidth = Spatium(e.readDouble());
     280           22 :         } else if (tag == "frame") {
     281            0 :             frameType = e.readInt() ? FrameType::RECTANGLE : FrameType::NO_FRAME;
     282           22 :         } else if (tag == "paddingWidth") {          // obsolete
     283              :             /*paddingWidthMM =*/
     284            0 :             e.readDouble();
     285           22 :         } else if (tag == "paddingWidthS") {
     286            3 :             paddingWidth = Spatium(e.readDouble());
     287           19 :         } else if (tag == "frameRound") {
     288            3 :             e.readDouble();
     289           16 :         } else if (tag == "frameColor") {
     290            3 :             frameColor = e.readColor();
     291           13 :         } else if (tag == "foregroundColor") {
     292            0 :             foregroundColor = e.readColor();
     293           13 :         } else if (tag == "backgroundColor") {
     294            0 :             backgroundColor = e.readColor();
     295           13 :         } else if (tag == "circle") {
     296            1 :             frameType = e.readInt() ? FrameType::CIRCLE : FrameType::NO_FRAME;
     297           12 :         } else if (tag == "systemFlag") {
     298           12 :             systemFlag = e.readInt();
     299            0 :         } else if (tag == "placement") {
     300            0 :             String value(e.readText());
     301            0 :             if (value == "above") {
     302            0 :                 placement = PlacementV::ABOVE;
     303            0 :             } else if (value == "below") {
     304            0 :                 placement = PlacementV::BELOW;
     305              :             }
     306            0 :             placementValid = true;
     307            0 :         } else if (tag == "lineWidth") {
     308            0 :             lineWidth = e.readDouble();
     309              :         } else {
     310            0 :             e.unknown();
     311              :         }
     312              :     }
     313          107 :     if (family == "MuseJazz") {
     314           42 :         family = u"MuseJazz Text";
     315              :     }
     316              : 
     317              :     struct StyleTable {
     318              :         const char* name;
     319              :         TextStyleType ss;
     320          107 :     } styleTable[] = {
     321              :         { "",                        TextStyleType::DEFAULT },
     322              :         { "Title",                   TextStyleType::TITLE },
     323              :         { "Subtitle",                TextStyleType::SUBTITLE },
     324              :         { "Composer",                TextStyleType::COMPOSER },
     325              :         { "Lyricist",                TextStyleType::LYRICIST },
     326              :         { "Lyrics Odd Lines",        TextStyleType::LYRICS_ODD },
     327              :         { "Lyrics Even Lines",       TextStyleType::LYRICS_EVEN },
     328              :         { "Fingering",               TextStyleType::FINGERING },
     329              :         { "LH Guitar Fingering",     TextStyleType::LH_GUITAR_FINGERING },
     330              :         { "RH Guitar Fingering",     TextStyleType::RH_GUITAR_FINGERING },
     331              :         { "String Number",           TextStyleType::STRING_NUMBER },
     332              :         { "Instrument Name (Long)",  TextStyleType::INSTRUMENT_LONG },
     333              :         { "Instrument Name (Short)", TextStyleType::INSTRUMENT_SHORT },
     334              :         { "Instrument Name (Part)",  TextStyleType::INSTRUMENT_EXCERPT },
     335              :         { "Dynamics",                TextStyleType::DYNAMICS },
     336              :         { "Technique",               TextStyleType::IGNORED_TYPES },
     337              :         { "Tempo",                   TextStyleType::TEMPO },
     338              :         { "Metronome",               TextStyleType::METRONOME },
     339              :         { "Measure Number",          TextStyleType::MEASURE_NUMBER },
     340              :         { "Translator",              TextStyleType::TRANSLATOR },
     341              :         { "Tuplet",                  TextStyleType::TUPLET },
     342              :         { "System",                  TextStyleType::SYSTEM },
     343              :         { "Staff",                   TextStyleType::STAFF },
     344              :         { "Chord Symbol",            TextStyleType::HARMONY_A },
     345              :         { "Rehearsal Mark",          TextStyleType::REHEARSAL_MARK },
     346              :         { "Repeat Text Left",        TextStyleType::REPEAT_LEFT },
     347              :         { "Repeat Text Right",       TextStyleType::REPEAT_RIGHT },
     348              :         { "Frame",                   TextStyleType::FRAME },
     349              :         { "Text Line",               TextStyleType::TEXTLINE },
     350              :         { "Glissando",               TextStyleType::GLISSANDO },
     351              :         { "Ottava",                  TextStyleType::OTTAVA },
     352              :         { "Pedal",                   TextStyleType::PEDAL },
     353              :         { "Hairpin",                 TextStyleType::HAIRPIN },
     354              :         { "Bend",                    TextStyleType::BEND },
     355              :         { "Header",                  TextStyleType::HEADER },
     356              :         { "Footer",                  TextStyleType::FOOTER },
     357              :         { "Instrument Change",       TextStyleType::INSTRUMENT_CHANGE },
     358              :         { "Repeat Text",             TextStyleType::IGNORED_TYPES, },         // Repeat Text style no longer exists
     359              :         { "Figured Bass",            TextStyleType::IGNORED_TYPES, },         // F.B. data are in style properties
     360              :         { "Volta",                   TextStyleType::VOLTA },
     361              :     };
     362          107 :     TextStyleType ss = TextStyleType::TEXT_TYPES;
     363         2792 :     for (const auto& i : styleTable) {
     364         2762 :         if (name == i.name) {
     365           77 :             ss = i.ss;
     366           77 :             break;
     367              :         }
     368              :     }
     369              : 
     370          107 :     if (ss == TextStyleType::IGNORED_TYPES) {
     371            4 :         return;
     372              :     }
     373              : 
     374          103 :     if (ss == TextStyleType::DEFAULT) {
     375              :         // This could be false in older files but newer files must have this true
     376            1 :         sizeIsSpatiumDependent = true;
     377              :     }
     378              : 
     379          103 :     bool isExcessStyle = false;
     380          103 :     if (ss == TextStyleType::TEXT_TYPES) {
     381           30 :         ss = ctx.addUserTextStyle(name);
     382           30 :         if (ss == TextStyleType::TEXT_TYPES) {
     383            0 :             LOGD("unhandled substyle <%s>", muPrintable(name));
     384            0 :             isExcessStyle = true;
     385              :         } else {
     386           30 :             int idx = int(ss) - int(TextStyleType::USER1);
     387           30 :             if ((int(ss) < int(TextStyleType::USER1)) || (int(ss) > int(TextStyleType::USER12))) {
     388            0 :                 LOGD("User style index %d outside of range.", idx);
     389            0 :                 return;
     390              :             }
     391           30 :             Sid sid[] = { Sid::user1Name, Sid::user2Name, Sid::user3Name, Sid::user4Name, Sid::user5Name, Sid::user6Name,
     392              :                           Sid::user7Name, Sid::user8Name, Sid::user9Name, Sid::user10Name, Sid::user11Name, Sid::user12Name };
     393           30 :             style->set(sid[idx], name);
     394              :         }
     395              :     }
     396              : 
     397          103 :     std::map<Sid, PropertyValue> excessPairs;
     398              :     const TextStyle* ts;
     399          103 :     if (isExcessStyle) {
     400            0 :         ts = textStyle(TextStyleType::USER1);
     401              :     } else {
     402          103 :         ts = textStyle(ss);
     403              :     }
     404         1751 :     for (const auto& i : *ts) {
     405         1648 :         if (ctx.shouldSkipProperty(i.pid)) {
     406            0 :             continue;
     407              :         }
     408              : 
     409         1648 :         PropertyValue value;
     410         1648 :         if (i.sid == Sid::NOSTYLE) {
     411            0 :             break;
     412              :         }
     413         1648 :         switch (i.pid) {
     414            0 :         case Pid::TEXT_STYLE:
     415            0 :             value = ss;
     416            0 :             break;
     417          103 :         case Pid::BEGIN_FONT_FACE:
     418              :         case Pid::CONTINUE_FONT_FACE:
     419              :         case Pid::END_FONT_FACE:
     420              :         case Pid::FONT_FACE:
     421          103 :             value = family;
     422          103 :             break;
     423          103 :         case Pid::BEGIN_FONT_SIZE:
     424              :         case Pid::CONTINUE_FONT_SIZE:
     425              :         case Pid::END_FONT_SIZE:
     426              :         case Pid::FONT_SIZE:
     427          103 :             value = size;
     428          103 :             break;
     429          103 :         case Pid::BEGIN_FONT_STYLE:
     430              :         case Pid::CONTINUE_FONT_STYLE:
     431              :         case Pid::END_FONT_STYLE:
     432              :         case Pid::FONT_STYLE:
     433          103 :             value = int(fontStyle);
     434          103 :             break;
     435          103 :         case Pid::FRAME_TYPE:
     436          103 :             value = int(frameType);
     437          103 :             break;
     438          103 :         case Pid::FRAME_WIDTH:
     439          103 :             value = frameWidth;
     440          103 :             break;
     441          103 :         case Pid::FRAME_PADDING:
     442          103 :             value = paddingWidth;
     443          103 :             break;
     444          103 :         case Pid::FRAME_FG_COLOR:
     445          103 :             value = PropertyValue::fromValue(frameColor);
     446          103 :             break;
     447          103 :         case Pid::FRAME_BG_COLOR:
     448          103 :             value = PropertyValue::fromValue(backgroundColor);
     449          103 :             break;
     450          103 :         case Pid::SIZE_SPATIUM_DEPENDENT:
     451          103 :             value = sizeIsSpatiumDependent;
     452          103 :             break;
     453          103 :         case Pid::BEGIN_TEXT_ALIGN:
     454              :         case Pid::CONTINUE_TEXT_ALIGN:
     455              :         case Pid::END_TEXT_ALIGN:
     456              :         case Pid::ALIGN:
     457          103 :             value = PropertyValue::fromValue(align);
     458          103 :             break;
     459           91 :         case Pid::POSITION:
     460           91 :             value = PropertyValue::fromValue(align.horizontal);
     461           91 :             break;
     462            0 :         case Pid::SYSTEM_FLAG:
     463            0 :             value = systemFlag;
     464            0 :             break;
     465            0 :         case Pid::BEGIN_HOOK_HEIGHT:
     466              :         case Pid::END_HOOK_HEIGHT:
     467            0 :             value = PropertyValue();
     468            0 :             break;
     469            0 :         case Pid::PLACEMENT:
     470            0 :             if (placementValid) {
     471            0 :                 value = placement;
     472              :             }
     473            0 :             break;
     474            0 :         case Pid::LINE_WIDTH:
     475            0 :             if (lineWidth != -1.0) {
     476            0 :                 value = lineWidth;
     477              :             }
     478            0 :             break;
     479          527 :         default:
     480              : //                        LOGD("unhandled property <%s>%d", propertyName(i.pid), int (i.pid));
     481          527 :             break;
     482              :         }
     483         1648 :         if (value.isValid()) {
     484         1121 :             if (isExcessStyle) {
     485            0 :                 excessPairs[i.sid] = value;
     486              :             } else {
     487         1121 :                 style->set(i.sid, value);
     488              :             }
     489              :         }
     490              : //            else
     491              : //                  LOGD("invalid style value <%s> pid<%s>", MStyle::valueName(i.sid), propertyName(i.pid));
     492         1648 :     }
     493              : 
     494          103 :     if (isExcessStyle && excessPairs.size() > 0) {
     495            0 :         excessStyles[name] = excessPairs;
     496              :     }
     497          111 : }
     498              : 
     499          142 : void Read206::readAccidental206(Accidental* a, XmlReader& e, ReadContext& ctx)
     500              : {
     501          360 :     while (e.readNextStartElement()) {
     502          218 :         const AsciiStringView tag(e.name());
     503          218 :         if (tag == "bracket") {
     504           21 :             int i = e.readInt();
     505           21 :             if (i == 0 || i == 1) {
     506           21 :                 a->setBracket(AccidentalBracket(i));
     507              :             }
     508          197 :         } else if (tag == "subtype") {
     509          142 :             String text = e.readText();
     510              :             static const std::map<String, AccidentalType> accMap = {
     511            0 :                 { u"none",               AccidentalType::NONE },
     512            0 :                 { u"sharp",              AccidentalType::SHARP },
     513            0 :                 { u"flat",               AccidentalType::FLAT },
     514            0 :                 { u"natural",            AccidentalType::NATURAL },
     515            0 :                 { u"double sharp",       AccidentalType::SHARP2 },
     516            0 :                 { u"double flat",        AccidentalType::FLAT2 },
     517            0 :                 { u"flat-slash",         AccidentalType::FLAT_SLASH },
     518            0 :                 { u"flat-slash2",        AccidentalType::FLAT_SLASH2 },
     519            0 :                 { u"mirrored-flat2",     AccidentalType::MIRRORED_FLAT2 },
     520            0 :                 { u"mirrored-flat",      AccidentalType::MIRRORED_FLAT },
     521            0 :                 { u"sharp-slash",        AccidentalType::SHARP_SLASH },
     522            0 :                 { u"sharp-slash2",       AccidentalType::SHARP_SLASH2 },
     523            0 :                 { u"sharp-slash3",       AccidentalType::SHARP_SLASH3 },
     524            0 :                 { u"sharp-slash4",       AccidentalType::SHARP_SLASH4 },
     525            0 :                 { u"sharp arrow up",     AccidentalType::SHARP_ARROW_UP },
     526            0 :                 { u"sharp arrow down",   AccidentalType::SHARP_ARROW_DOWN },
     527            0 :                 { u"flat arrow up",      AccidentalType::FLAT_ARROW_UP },
     528            0 :                 { u"flat arrow down",    AccidentalType::FLAT_ARROW_DOWN },
     529            0 :                 { u"natural arrow up",   AccidentalType::NATURAL_ARROW_UP },
     530            0 :                 { u"natural arrow down", AccidentalType::NATURAL_ARROW_DOWN },
     531            0 :                 { u"sori",               AccidentalType::SORI },
     532            0 :                 { u"koron",              AccidentalType::KORON }
     533          166 :             };
     534          142 :             auto it = accMap.find(text);
     535          142 :             if (it == accMap.end()) {
     536            0 :                 LOGD("invalid type %s", muPrintable(text));
     537            0 :                 a->setAccidentalType(AccidentalType::NONE);
     538              :             } else {
     539          142 :                 a->setAccidentalType(it->second);
     540              :             }
     541          197 :         } else if (tag == "role") {
     542           43 :             AccidentalRole r = AccidentalRole(e.readInt());
     543           43 :             if (r == AccidentalRole::AUTO || r == AccidentalRole::USER) {
     544           43 :                 a->setRole(r);
     545              :             }
     546           12 :         } else if (tag == "small") {
     547            0 :             a->setSmall(e.readInt());
     548           12 :         } else if (TRead::readItemProperties(a, e, ctx)) {
     549              :         } else {
     550            0 :             e.unknown();
     551              :         }
     552              :     }
     553          143 : }
     554              : 
     555            0 : VoiceAssignment Read206::readDynamicRange(int i)
     556              : {
     557            0 :     switch (i) {
     558            0 :     case 0:
     559            0 :         return VoiceAssignment::ALL_VOICE_IN_STAFF;
     560            0 :     case 1:
     561              :     case 2:
     562            0 :         break;
     563              :     }
     564              : 
     565            0 :     return VoiceAssignment::ALL_VOICE_IN_INSTRUMENT;
     566              : }
     567              : 
     568          253 : NoteHeadGroup Read206::convertHeadGroup(int i)
     569              : {
     570              :     NoteHeadGroup val;
     571          253 :     switch (i) {
     572           89 :     case 1:
     573           89 :         val = NoteHeadGroup::HEAD_CROSS;
     574           89 :         break;
     575           28 :     case 2:
     576           28 :         val = NoteHeadGroup::HEAD_DIAMOND;
     577           28 :         break;
     578           14 :     case 3:
     579           14 :         val = NoteHeadGroup::HEAD_TRIANGLE_DOWN;
     580           14 :         break;
     581            9 :     case 4:
     582            9 :         val = NoteHeadGroup::HEAD_MI;
     583            9 :         break;
     584           14 :     case 5:
     585           14 :         val = NoteHeadGroup::HEAD_SLASH;
     586           14 :         break;
     587           18 :     case 6:
     588           18 :         val = NoteHeadGroup::HEAD_XCIRCLE;
     589           18 :         break;
     590            7 :     case 7:
     591            7 :         val = NoteHeadGroup::HEAD_DO;
     592            7 :         break;
     593            7 :     case 8:
     594            7 :         val = NoteHeadGroup::HEAD_RE;
     595            7 :         break;
     596            7 :     case 9:
     597            7 :         val = NoteHeadGroup::HEAD_FA;
     598            7 :         break;
     599            7 :     case 10:
     600            7 :         val = NoteHeadGroup::HEAD_LA;
     601            7 :         break;
     602            7 :     case 11:
     603            7 :         val = NoteHeadGroup::HEAD_TI;
     604            7 :         break;
     605            7 :     case 12:
     606            7 :         val = NoteHeadGroup::HEAD_SOL;
     607            7 :         break;
     608            8 :     case 13:
     609            8 :         val = NoteHeadGroup::HEAD_BREVIS_ALT;
     610            8 :         break;
     611           31 :     case 0:
     612              :     default:
     613           31 :         val = NoteHeadGroup::HEAD_NORMAL;
     614              :     }
     615          253 :     return val;
     616              : }
     617              : 
     618           43 : static NoteHeadType convertHeadType(int i)
     619              : {
     620              :     NoteHeadType val;
     621           43 :     switch (i) {
     622           14 :     case 0:
     623           14 :         val = NoteHeadType::HEAD_WHOLE;
     624           14 :         break;
     625           15 :     case 1:
     626           15 :         val = NoteHeadType::HEAD_HALF;
     627           15 :         break;
     628            0 :     case 2:
     629            0 :         val = NoteHeadType::HEAD_QUARTER;
     630            0 :         break;
     631           14 :     case 3:
     632           14 :         val = NoteHeadType::HEAD_BREVIS;
     633           14 :         break;
     634            0 :     default:
     635            0 :         val = NoteHeadType::HEAD_AUTO;
     636              :     }
     637           43 :     return val;
     638              : }
     639              : 
     640              : //---------------------------------------------------------
     641              : //   ArticulationNames
     642              : //---------------------------------------------------------
     643              : 
     644              : static struct ArticulationNames {
     645              :     SymId id;
     646              :     AsciiStringView name;
     647              : } articulationNames[] = {
     648              :     { SymId::fermataAbove,              "fermata",                   },
     649              :     { SymId::fermataShortAbove,         "shortfermata",              },
     650              :     { SymId::fermataLongAbove,          "longfermata",               },
     651              :     { SymId::fermataVeryLongAbove,      "verylongfermata",           },
     652              :     { SymId::articAccentAbove,          "sforzato",                  },
     653              :     { SymId::articStaccatoAbove,        "staccato",                  },
     654              :     { SymId::articStaccatissimoAbove,   "staccatissimo",             },
     655              :     { SymId::articTenutoAbove,          "tenuto",                    },
     656              :     { SymId::articTenutoStaccatoAbove,  "portato",                   },
     657              :     { SymId::articMarcatoAbove,         "marcato",                   },
     658              :     { SymId::guitarFadeIn,              "fadein",                    },
     659              :     { SymId::guitarFadeOut,             "fadeout",                   },
     660              :     { SymId::guitarVolumeSwell,         "volumeswell",               },
     661              :     { SymId::wiggleSawtooth,            "wigglesawtooth",            },
     662              :     { SymId::wiggleSawtoothWide,        "wigglesawtoothwide",        },
     663              :     { SymId::wiggleVibratoLargeFaster,  "wigglevibratolargefaster",  },
     664              :     { SymId::wiggleVibratoLargeSlowest, "wigglevibratolargeslowest", },
     665              :     { SymId::brassMuteOpen,             "ouvert",                    },
     666              :     { SymId::brassMuteClosed,           "plusstop",                  },
     667              :     { SymId::stringsUpBow,              "upbow",                     },
     668              :     { SymId::stringsDownBow,            "downbow",                   },
     669              :     { SymId::ornamentTurnInverted,      "reverseturn",               },
     670              :     { SymId::ornamentTurn,              "turn",                      },
     671              :     { SymId::ornamentTrill,             "trill",                     },
     672              :     { SymId::ornamentShortTrill,        "prall",                     },
     673              :     { SymId::ornamentMordent,           "mordent",                   },
     674              :     { SymId::ornamentTremblement,       "prallprall",                },
     675              :     { SymId::ornamentPrallMordent,      "prallmordent",              },
     676              :     { SymId::ornamentUpPrall,           "upprall",                   },
     677              :     { SymId::ornamentUpMordent,         "upmordent",                 },
     678              :     { SymId::ornamentDownMordent,       "downmordent",               },
     679              :     { SymId::ornamentPrallDown,         "pralldown",                 },
     680              :     { SymId::ornamentPrallUp,           "prallup",                   },
     681              :     { SymId::ornamentLinePrall,         "lineprall",                 },
     682              :     { SymId::ornamentPrecompSlide,      "schleifer",                 },
     683              :     { SymId::pluckedSnapPizzicatoAbove, "snappizzicato",             },
     684              :     { SymId::stringsThumbPosition,      "thumb",                     },
     685              :     { SymId::luteFingeringRHThumb,      "lutefingeringthumb",        },
     686              :     { SymId::luteFingeringRHFirst,      "lutefingering1st",          },
     687              :     { SymId::luteFingeringRHSecond,     "lutefingering2nd",          },
     688              :     { SymId::luteFingeringRHThird,      "lutefingering3rd",          },
     689              : 
     690              :     { SymId::ornamentPrecompMordentUpperPrefix, "downprall" },
     691              :     { SymId::ornamentPrecompMordentUpperPrefix, "ornamentDownPrall" },
     692              : };
     693              : 
     694          249 : SymId Read206::articulationNames2SymId206(const AsciiStringView& s)
     695              : {
     696         4300 :     for (auto i : articulationNames) {
     697         4286 :         if (i.name == s) {
     698          235 :             return i.id;
     699              :         }
     700              :     }
     701           14 :     return SymId::noSym;
     702              : }
     703              : 
     704              : //---------------------------------------------------------
     705              : //   readDrumset
     706              : //---------------------------------------------------------
     707              : 
     708           68 : static void readDrumset206(Drumset* ds, XmlReader& e)
     709              : {
     710           68 :     int pitch = e.intAttribute("pitch", -1);
     711           68 :     if (!pitchIsValid(pitch)) {
     712            0 :         LOGD("load drumset: invalid pitch %d", pitch);
     713            0 :         return;
     714              :     }
     715          415 :     while (e.readNextStartElement()) {
     716          347 :         const AsciiStringView tag(e.name());
     717          347 :         if (tag == "head") {
     718           68 :             ds->drum(pitch).notehead = Read206::convertHeadGroup(e.readInt());
     719          279 :         } else if (tag == "variants") {
     720            0 :             while (e.readNextStartElement()) {
     721            0 :                 const AsciiStringView tagv(e.name());
     722            0 :                 if (tagv == "variant") {
     723            0 :                     DrumInstrumentVariant div;
     724            0 :                     div.pitch = e.attribute("pitch").toInt();
     725            0 :                     while (e.readNextStartElement()) {
     726            0 :                         const AsciiStringView taga(e.name());
     727            0 :                         if (taga == "articulation") {
     728            0 :                             AsciiStringView oldArticulationName = e.readAsciiText();
     729            0 :                             SymId oldId = Read206::articulationNames2SymId206(oldArticulationName);
     730            0 :                             div.articulationName = Articulation::symId2ArticulationName(oldId);
     731            0 :                         } else if (taga == "tremolo") {
     732            0 :                             div.tremolo = TConv::fromXml(e.readAsciiText(), TremoloType::INVALID_TREMOLO);
     733              :                         }
     734              :                     }
     735            0 :                     ds->drum(pitch).addVariant(div);
     736            0 :                 }
     737              :             }
     738          279 :         } else if (ds->readDrumProperties(e, pitch)) {
     739              :         } else {
     740            0 :             e.unknown();
     741              :         }
     742              :     }
     743              : }
     744              : 
     745              : //---------------------------------------------------------
     746              : //   readInstrument
     747              : //---------------------------------------------------------
     748              : 
     749           55 : static void readInstrument206(Instrument* i, Part* p, XmlReader& e, ReadContext& ctx)
     750              : {
     751           55 :     int bank    = 0;
     752           55 :     char volume  = 100;
     753           55 :     char pan     = 60;
     754           55 :     char chorus  = 30;
     755           55 :     char reverb  = 30;
     756           55 :     bool customDrumset = false;
     757           55 :     i->clearChannels();         // remove default channel
     758          957 :     while (e.readNextStartElement()) {
     759          902 :         const AsciiStringView tag(e.name());
     760          902 :         if (tag == "Drum") {
     761              :             // if we see one of this tags, a custom drumset will
     762              :             // be created
     763           68 :             if (!i->drumset()) {
     764            0 :                 i->setDrumset(new Drumset(*smDrumset));
     765              :             }
     766           68 :             if (!customDrumset) {
     767            4 :                 i->drumset()->clear();
     768            4 :                 customDrumset = true;
     769              :             }
     770           68 :             readDrumset206(i->drumset(), e);
     771          834 :         } else if (read400::TRead::readProperties(i, e, ctx, p, &customDrumset)) {
     772              :         } else {
     773            0 :             e.unknown();
     774              :         }
     775              :     }
     776              : 
     777           55 :     i->updateInstrumentId();
     778              : 
     779              :     // Read single-note dynamics from template
     780           55 :     i->setSingleNoteDynamicsFromTemplate();
     781              : 
     782           55 :     if (i->channel().empty()) {        // for backward compatibility
     783            0 :         InstrChannel* a = new InstrChannel;
     784            0 :         a->setName(String::fromUtf8(InstrChannel::DEFAULT_NAME));
     785            0 :         a->setProgram(i->recognizeMidiProgram());
     786            0 :         a->setBank(bank);
     787            0 :         a->setVolume(volume);
     788            0 :         a->setPan(pan);
     789            0 :         a->setReverb(reverb);
     790            0 :         a->setChorus(chorus);
     791            0 :         i->appendChannel(a);
     792           55 :     } else if (i->channel(0)->program() < 0) {
     793            0 :         i->channel(0)->setProgram(i->recognizeMidiProgram());
     794              :     }
     795           55 :     if (i->useDrumset()) {
     796            4 :         if (i->channel()[0]->bank() == 0) {
     797            0 :             i->channel()[0]->setBank(128);
     798              :         }
     799              :     }
     800           55 : }
     801              : 
     802              : //---------------------------------------------------------
     803              : //   readStaff
     804              : //---------------------------------------------------------
     805              : 
     806           60 : static void readStaff(Staff* staff, XmlReader& e, ReadContext& ctx)
     807              : {
     808          161 :     while (e.readNextStartElement()) {
     809          101 :         const AsciiStringView tag(e.name());
     810          101 :         if (tag == "type") {        // obsolete
     811            0 :             int staffTypeIdx = e.readInt();
     812            0 :             LOGD("obsolete: Staff::read staffTypeIdx %d", staffTypeIdx);
     813          101 :         } else if (tag == "StaffType") {
     814           60 :             StaffType st;
     815           60 :             TRead::read(&st, e, ctx);
     816           60 :             staff->setStaffType(Fraction(0, 1), st);
     817          101 :         } else if (tag == "defaultClef") {           // sets both default transposing and concert clef
     818            9 :             ClefType ct = TConv::fromXml(e.readAsciiText(), ClefType::G);
     819            9 :             staff->setDefaultClefType(ClefTypeList(ct, ct));
     820           32 :         } else if (tag == "defaultConcertClef") {
     821            0 :             staff->setDefaultClefType(ClefTypeList(TConv::fromXml(e.readAsciiText(), ClefType::G),
     822            0 :                                                    staff->defaultClefType().transposingClef));
     823           32 :         } else if (tag == "defaultTransposingClef") {
     824            0 :             staff->setDefaultClefType(ClefTypeList(staff->defaultClefType().concertClef,
     825            0 :                                                    TConv::fromXml(e.readAsciiText(), ClefType::G)));
     826           32 :         } else if (tag == "small") {
     827            0 :             staff->staffType(Fraction(0, 1))->setSmall(e.readInt());
     828           32 :         } else if (tag == "invisible") {
     829            0 :             staff->staffType(Fraction(0, 1))->setInvisible(e.readInt());
     830           32 :         } else if (tag == "neverHide") {
     831            0 :             bool v = e.readInt();
     832            0 :             if (v) {
     833            0 :                 staff->setHideWhenEmpty(AutoOnOff::OFF);
     834              :             }
     835           32 :         } else if (tag == "showIfSystemEmpty") {
     836            0 :             staff->setShowIfEntireSystemEmpty(e.readInt());
     837           32 :         } else if (tag == "hideSystemBarLine") {
     838            0 :             staff->setHideSystemBarLine(e.readInt());
     839           32 :         } else if (tag == "keylist") {
     840            0 :             read400::TRead::read(staff->keyList(), e, ctx);
     841           32 :         } else if (tag == "bracket") {
     842           24 :             int col = e.intAttribute("col", -1);
     843           24 :             if (col == -1) {
     844           24 :                 col = static_cast<int>(ctx.score()->brackets(staff->idx()).size());
     845              :             }
     846           24 :             EditStaffBrackets::setBracketType(ctx.score(), staff->idx(), col, BracketType(e.intAttribute("type", -1)));
     847           24 :             EditStaffBrackets::setBracketSpan(ctx.score(), staff->idx(), col, e.intAttribute("span", 0));
     848           24 :             EditStaffBrackets::setBracketVisible(ctx.score(), staff->idx(), col, static_cast<bool>(e.intAttribute("visible", 1)));
     849           24 :             e.readNext();
     850            8 :         } else if (tag == "barLineSpan") {
     851            2 :             staff->setBarLineFrom(e.intAttribute("from", 0));
     852            2 :             staff->setBarLineTo(e.intAttribute("to", 0));
     853            2 :             const int barLineSpan = e.readInt();
     854            2 :             if (barLineSpan > 0) {
     855            1 :                 ctx.setStaffBarLineSpan(staff->idx(), static_cast<size_t>(barLineSpan - 1));
     856              :             } else {
     857            1 :                 if (barLineSpan < 0) {
     858            0 :                     LOGW() << "barLineSpan is negative: " << barLineSpan;
     859              :                 }
     860            1 :                 ctx.setStaffBarLineSpan(staff->idx(), 0);
     861              :             }
     862            6 :         } else if (tag == "distOffset") {
     863            0 :             staff->setUserDist(Spatium(e.readDouble()));
     864            6 :         } else if (tag == "mag") {
     865              :             /*_userMag =*/
     866            0 :             e.readDouble(0.1, 10.0);
     867            6 :         } else if (tag == "linkedTo") {
     868            6 :             int v = e.readInt() - 1;
     869            6 :             Staff* st = staff->score()->masterScore()->staff(v);
     870            6 :             if (staff->links()) {
     871            0 :                 LOGD("Staff::readProperties: multiple <linkedTo> tags");
     872            0 :                 if (!st || staff->isLinked(st)) {     // maybe we don't need actually to relink...
     873            0 :                     continue;
     874              :                 }
     875              :                 // not using unlink() here as it may delete _links
     876              :                 // a pointer to which is stored also in XmlReader.
     877            0 :                 staff->links()->remove(staff);
     878            0 :                 staff->setLinks(nullptr);
     879              :             }
     880            6 :             if (st && st != staff) {
     881            6 :                 staff->linkTo(st);
     882            0 :             } else if (!staff->score()->isMaster() && !st) {
     883              :                 // if it is a master score it is OK not to find
     884              :                 // a staff which is going after the current one.
     885            0 :                 LOGD("staff %d not found in parent", v);
     886              :             }
     887            0 :         } else if (tag == "color") {
     888            0 :             staff->staffType(Fraction(0, 1))->setColor(e.readColor());
     889            0 :         } else if (tag == "transposeDiatonic") {
     890            0 :             ctx.setTransposeDiatonic(static_cast<int8_t>(e.readInt()));
     891            0 :         } else if (tag == "transposeChromatic") {
     892            0 :             ctx.setTransposeChromatic(static_cast<int8_t>(e.readInt()));
     893              :         } else {
     894            0 :             e.unknown();
     895              :         }
     896              :     }
     897           60 : }
     898              : 
     899              : //---------------------------------------------------------
     900              : //   readPart
     901              : //---------------------------------------------------------
     902              : 
     903           55 : void Read206::readPart206(Part* part, XmlReader& e, ReadContext& ctx)
     904              : {
     905          226 :     while (e.readNextStartElement()) {
     906          171 :         const AsciiStringView tag(e.name());
     907          171 :         if (tag == "Instrument") {
     908           55 :             Instrument* i = part->instrument();
     909           55 :             readInstrument206(i, part, e, ctx);
     910           55 :             Drumset* ds = i->drumset();
     911           55 :             Staff* s = part->staff(0);
     912           55 :             int lld = s ? std::round(s->lineDistance(Fraction(0, 1))) : 1;
     913           55 :             if (ds && s && lld > 1) {
     914            0 :                 for (int j = 0; j < DRUM_INSTRUMENTS; ++j) {
     915            0 :                     ds->drum(j).line /= lld;
     916              :                 }
     917              :             }
     918          116 :         } else if (tag == "Staff") {
     919           60 :             Staff* staff = Factory::createStaff(part);
     920           60 :             ctx.appendStaff(staff);
     921           60 :             readStaff(staff, e, ctx);
     922           56 :         } else if (tag == "name") {
     923            0 :             part->instrument()->setLongName(e.readText());
     924           56 :         } else if (tag == "shortName") {
     925            0 :             part->instrument()->setShortName(e.readText());
     926           56 :         } else if (tag == "show") {
     927            1 :             part->setShow(e.readInt());
     928              :         } else {
     929           55 :             e.unknown();
     930              :         }
     931              :     }
     932           55 : }
     933              : 
     934              : //---------------------------------------------------------
     935              : //   readAmbitus
     936              : //---------------------------------------------------------
     937              : 
     938            2 : static void readAmbitus(Ambitus* ambitus, XmlReader& e, ReadContext& ctx)
     939              : {
     940           15 :     while (e.readNextStartElement()) {
     941           13 :         const AsciiStringView tag(e.name());
     942           13 :         if (tag == "head") {
     943            1 :             ambitus->setNoteHeadGroup(Read206::convertHeadGroup(e.readInt()));
     944           12 :         } else if (tag == "headType") {
     945            1 :             ambitus->setNoteHeadType(convertHeadType(e.readInt()));
     946           11 :         } else if (read400::TRead::readProperties(ambitus, e, ctx)) {
     947              :         } else {
     948            0 :             e.unknown();
     949              :         }
     950              :     }
     951            2 : }
     952              : 
     953              : //---------------------------------------------------------
     954              : //   readNote
     955              : //---------------------------------------------------------
     956              : 
     957          986 : static void readNote206(Note* note, XmlReader& e, ReadContext& ctx)
     958              : {
     959          986 :     note->setTpc1(Tpc::TPC_INVALID);
     960          986 :     note->setTpc2(Tpc::TPC_INVALID);
     961              : 
     962         3629 :     while (e.readNextStartElement()) {
     963         2643 :         const AsciiStringView tag(e.name());
     964         2643 :         if (tag == "Accidental") {
     965          141 :             Accidental* a = Factory::createAccidental(note);
     966          141 :             a->setTrack(note->track());
     967          141 :             Read206::readAccidental206(a, e, ctx);
     968          141 :             note->add(a);
     969         2502 :         } else if (tag == "head") {
     970           99 :             int i = e.readInt();
     971           99 :             NoteHeadGroup val = Read206::convertHeadGroup(i);
     972           99 :             note->setHeadGroup(val);
     973         2403 :         } else if (tag == "headType") {
     974           42 :             int i = e.readInt();
     975           42 :             NoteHeadType val = convertHeadType(i);
     976           42 :             note->setHeadType(val);
     977         2361 :         } else if (Read206::readNoteProperties206(note, e, ctx)) {
     978              :         } else {
     979          362 :             e.unknown();
     980              :         }
     981              :     }
     982              :     // ensure sane values:
     983          986 :     note->setPitch(clampPitch(note->pitch()));
     984              : 
     985          986 :     if (!tpcIsValid(note->tpc1()) && !tpcIsValid(note->tpc2())) {
     986            0 :         Key key = (note->staff() && note->chord()) ? note->staff()->key(note->chord()->tick()) : Key::C;
     987            0 :         int tpc = pitch2tpc(note->pitch(), key, Prefer::NEAREST);
     988            0 :         if (note->concertPitch()) {
     989            0 :             note->setTpc1(tpc);
     990              :         } else {
     991            0 :             note->setTpc2(tpc);
     992              :         }
     993              :     }
     994          986 :     if (!(tpcIsValid(note->tpc1()) && tpcIsValid(note->tpc2()))) {
     995            0 :         Fraction tick = note->chord() ? note->chord()->tick() : Fraction(-1, 1);
     996            0 :         Interval v = note->staff() ? note->staff()->transpose(tick) : Interval();
     997            0 :         if (tpcIsValid(note->tpc1())) {
     998            0 :             v.flip();
     999            0 :             if (v.isZero()) {
    1000            0 :                 note->setTpc2(note->tpc1());
    1001              :             } else {
    1002            0 :                 note->setTpc2(Transpose::transposeTpc(note->tpc1(), v, true));
    1003              :             }
    1004              :         } else {
    1005            0 :             if (v.isZero()) {
    1006            0 :                 note->setTpc1(note->tpc2());
    1007              :             } else {
    1008            0 :                 note->setTpc1(Transpose::transposeTpc(note->tpc2(), v, true));
    1009              :             }
    1010              :         }
    1011              :     }
    1012          986 : }
    1013              : 
    1014              : //---------------------------------------------------------
    1015              : //   adjustPlacement
    1016              : //---------------------------------------------------------
    1017              : 
    1018           18 : static void adjustPlacement(EngravingItem* e)
    1019              : {
    1020           18 :     if (!e || !e->staff()) {
    1021            2 :         return;
    1022              :     }
    1023              : 
    1024              :     // element to use to determine placement
    1025              :     // for spanners, choose first segment
    1026              :     EngravingItem* ee;
    1027              :     Spanner* spanner;
    1028           16 :     if (e->isSpanner()) {
    1029            7 :         spanner = toSpanner(e);
    1030            7 :         if (spanner->spannerSegments().empty()) {
    1031            7 :             return;
    1032              :         }
    1033            0 :         ee = spanner->spannerSegments().front();
    1034            0 :         if (!ee) {
    1035            0 :             return;
    1036              :         }
    1037              :     } else {
    1038            9 :         spanner = nullptr;
    1039            9 :         ee = e;
    1040              :     }
    1041              : 
    1042              :     // determine placement based on offset
    1043              :     // anything below staff will be set to below
    1044            9 :     double staffHeight = e->staff()->staffHeight();
    1045            9 :     double threshold = staffHeight;
    1046            9 :     double offsetAdjust = 0.0;
    1047            9 :     PlacementV defaultPlacement = e->propertyDefault(Pid::PLACEMENT).value<PlacementV>();
    1048              :     PlacementV newPlacement;
    1049              :     // most offsets will be recorded as relative to top staff line
    1050              :     // exceptions are styled offsets on elements with default placement below
    1051              :     double normalize;
    1052            9 :     if (defaultPlacement == PlacementV::BELOW && ee->offset().isNull()) {
    1053            0 :         normalize = staffHeight;
    1054              :     } else {
    1055            9 :         normalize = 0.0;
    1056              :     }
    1057            9 :     double ypos = ee->offset().y() + normalize;
    1058            9 :     if (ypos >= threshold) {
    1059            0 :         newPlacement = PlacementV::BELOW;
    1060            0 :         offsetAdjust -= staffHeight;
    1061              :     } else {
    1062            9 :         newPlacement = PlacementV::ABOVE;
    1063              :     }
    1064              : 
    1065              :     // set placement
    1066            9 :     e->setProperty(Pid::PLACEMENT, newPlacement);
    1067            9 :     if (newPlacement != defaultPlacement) {
    1068            0 :         e->setPropertyFlags(Pid::PLACEMENT, PropertyFlags::UNSTYLED);
    1069              :     }
    1070              : 
    1071              :     // adjust offset
    1072            9 :     if (spanner) {
    1073              :         // adjust segments individually
    1074            0 :         for (auto a : spanner->spannerSegments()) {
    1075              :             // spanner segments share the placement setting of the spanner
    1076              :             // just adjust offset
    1077            0 :             if (defaultPlacement == PlacementV::BELOW && a->offset().isNull()) {
    1078            0 :                 normalize = staffHeight;
    1079              :             } else {
    1080            0 :                 normalize = 0.0;
    1081              :             }
    1082            0 :             double yp = a->offset().y() + normalize;
    1083            0 :             a->ryoffset() += normalize + offsetAdjust;
    1084              : 
    1085              :             // if any segments are offset to opposite side of staff from placement,
    1086              :             // or if they are within staff,
    1087              :             // disable autoplace
    1088              :             bool disableAutoplace;
    1089            0 :             if (yp + a->height() <= 0.0) {
    1090            0 :                 disableAutoplace = (newPlacement == PlacementV::BELOW);
    1091            0 :             } else if (yp > staffHeight) {
    1092            0 :                 disableAutoplace = (newPlacement == PlacementV::ABOVE);
    1093              :             } else {
    1094            0 :                 disableAutoplace = true;
    1095              :             }
    1096            0 :             if (disableAutoplace) {
    1097            0 :                 a->setAutoplace(false);
    1098              :             }
    1099              :             // needed for https://musescore.org/en/node/281312
    1100              :             // ideally we would rebase and calculate new offset
    1101              :             // but this may not be possible
    1102              :             // since original offset is relative to system
    1103            0 :             a->rxoffset() = 0;
    1104              :         }
    1105              :     } else {
    1106            9 :         e->ryoffset() += normalize + offsetAdjust;
    1107              :         // if within staff, disable autoplace
    1108            9 :         if (ypos + e->height() > 0.0 && ypos <= staffHeight) {
    1109            0 :             e->setAutoplace(false);
    1110              :         }
    1111              :     }
    1112              : }
    1113              : 
    1114         3272 : bool Read206::readNoteProperties206(Note* note, XmlReader& e, ReadContext& ctx)
    1115              : {
    1116         3272 :     const AsciiStringView tag(e.name());
    1117              : 
    1118         3272 :     if (tag == "pitch") {
    1119         1425 :         note->setPitch(e.readInt());
    1120         1847 :     } else if (tag == "tpc") {
    1121         1425 :         const int tpc = e.readInt();
    1122         1425 :         note->setTpc1(tpc);
    1123         1425 :         note->setTpc2(tpc);
    1124          422 :     } else if (tag == "track") {          // for performance
    1125           43 :         note->setTrack(e.readInt());
    1126          379 :     } else if (tag == "Accidental") {
    1127            0 :         Accidental* a = Factory::createAccidental(note);
    1128            0 :         a->setTrack(note->track());
    1129            0 :         read400::TRead::read(a, e, ctx);
    1130            0 :         note->add(a);
    1131          379 :     } else if (tag == "Tie") {
    1132            0 :         Tie* tie = Factory::createTie(note);
    1133            0 :         tie->setTrack(note->track());
    1134            0 :         readTie206(e, ctx, tie);
    1135            0 :         tie->setStartNote(note);
    1136            0 :         note->setTieFor(tie);
    1137          379 :     } else if (tag == "tpc2") {
    1138            0 :         note->setTpc2(e.readInt());
    1139          379 :     } else if (tag == "small") {
    1140            0 :         note->setSmall(e.readInt());
    1141          379 :     } else if (tag == "mirror") {
    1142            0 :         read400::TRead::readProperty(note, e, ctx, Pid::MIRROR_HEAD);
    1143          379 :     } else if (tag == "dotPosition") {
    1144            0 :         read400::TRead::readProperty(note, e, ctx, Pid::DOT_POSITION);
    1145          379 :     } else if (tag == "fixed") {
    1146            0 :         note->setFixed(e.readBool());
    1147          379 :     } else if (tag == "fixedLine") {
    1148            0 :         note->setFixedLine(e.readInt());
    1149          379 :     } else if (tag == "head") {
    1150            0 :         read400::TRead::readProperty(note, e, ctx, Pid::HEAD_GROUP);
    1151          379 :     } else if (tag == "velocity") {
    1152              :         // TODO: convert to MU4
    1153            0 :         e.skipCurrentElement();
    1154          379 :     } else if (tag == "play") {
    1155            0 :         note->setPlay(e.readInt());
    1156          379 :     } else if (tag == "tuning") {
    1157            0 :         note->setTuning(e.readDouble());
    1158          379 :     } else if (tag == "fret") {
    1159            0 :         note->setFret(e.readInt());
    1160          379 :     } else if (tag == "string") {
    1161            0 :         note->setString(e.readInt());
    1162          379 :     } else if (tag == "ghost") {
    1163            0 :         note->setGhost(e.readInt());
    1164          379 :     } else if (tag == "headType") {
    1165            0 :         read400::TRead::readProperty(note, e, ctx, Pid::HEAD_TYPE);
    1166          379 :     } else if (tag == "veloType") {
    1167            0 :         read400::TRead::readProperty(note, e, ctx, Pid::VELO_TYPE);
    1168          379 :     } else if (tag == "line") {
    1169            0 :         note->setLine(e.readInt());
    1170          379 :     } else if (tag == "Fingering") {
    1171            4 :         Fingering* f = Factory::createFingering(note);
    1172            4 :         f->setTrack(note->track());
    1173            4 :         readText206(e, ctx, f, note);
    1174            4 :         note->add(f);
    1175          375 :     } else if (tag == "Symbol") {
    1176            0 :         Symbol* s = new Symbol(note);
    1177            0 :         s->setTrack(note->track());
    1178            0 :         read400::TRead::read(s, e, ctx);
    1179            0 :         if (s->sym() == SymId::noteheadParenthesisLeft) {
    1180            0 :             note->setParenthesesMode(note->rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT);
    1181            0 :             s->deleteLater();
    1182            0 :         } else if (s->sym() == SymId::noteheadParenthesisRight) {
    1183            0 :             note->setParenthesesMode(note->leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT);
    1184            0 :             s->deleteLater();
    1185              :         } else {
    1186            0 :             note->add(s);
    1187              :         }
    1188          375 :     } else if (tag == "Image") {
    1189            0 :         if (MScore::noImages) {
    1190            0 :             e.skipCurrentElement();
    1191              :         } else {
    1192            0 :             Image* image = new Image(note);
    1193            0 :             image->setTrack(note->track());
    1194            0 :             read400::TRead::read(image, e, ctx);
    1195            0 :             note->add(image);
    1196              :         }
    1197          375 :     } else if (tag == "Bend") {
    1198            4 :         Bend* b = Factory::createBend(note);
    1199            4 :         b->setTrack(note->track());
    1200            4 :         read400::TRead::read(b, e, ctx);
    1201            4 :         note->add(b);
    1202          371 :     } else if (tag == "NoteDot") {
    1203            0 :         NoteDot* dot = Factory::createNoteDot(note);
    1204            0 :         read400::TRead::read(dot, e, ctx);
    1205            0 :         note->add(dot);
    1206          371 :     } else if (tag == "Events") {
    1207            0 :         note->playEvents().clear();        // remove default event
    1208            0 :         while (e.readNextStartElement()) {
    1209            0 :             const AsciiStringView etag(e.name());
    1210            0 :             if (etag == "Event") {
    1211            0 :                 NoteEvent ne;
    1212            0 :                 read400::TRead::read(&ne, e, ctx);
    1213            0 :                 note->playEvents().push_back(ne);
    1214              :             } else {
    1215            0 :                 e.unknown();
    1216              :             }
    1217              :         }
    1218            0 :         if (Chord* ch = note->chord()) {
    1219            0 :             ch->setPlayEventType(PlayEventType::User);
    1220              :         }
    1221          371 :     } else if (tag == "endSpanner") {
    1222            0 :         int id = e.intAttribute("id");
    1223            0 :         Spanner* sp = ctx.findSpanner(id);
    1224            0 :         if (sp) {
    1225            0 :             sp->setEndElement(note);
    1226            0 :             if (sp->isTie()) {
    1227            0 :                 note->setTieBack(toTie(sp));
    1228              :             } else {
    1229            0 :                 bool isNoteAnchoredTextLine = sp->isNoteLine() && toNoteLine(sp)->enforceMinLength();
    1230            0 :                 if ((sp->isGlissando() || isNoteAnchoredTextLine) && note->ownershipParent() && note->ownershipParent()->isChord()) {
    1231            0 :                     toChord(note->ownershipParent())->setEndsNoteAnchoredLine(true);
    1232              :                 }
    1233            0 :                 note->addSpannerBack(sp);
    1234              :             }
    1235            0 :             ctx.removeSpanner(sp);
    1236              :         } else {
    1237              :             // End of a spanner whose start element will appear later;
    1238              :             // may happen for cross-staff spanner from a lower to a higher staff
    1239              :             // (for instance a glissando from bass to treble staff of piano).
    1240              :             // Create a place-holder spanner with end data
    1241              :             // (a TextLine is used only because both Spanner or SLine are abstract,
    1242              :             // the actual class does not matter, as long as it is derived from Spanner)
    1243            0 :             int id1 = e.intAttribute("id", -1);
    1244            0 :             Staff* staff = note->staff();
    1245            0 :             if (id1 != -1
    1246            0 :                 &&            // DISABLE if pasting into a staff with linked staves
    1247              :                               // because the glissando is not properly cloned into the linked staves
    1248            0 :                 staff && (!ctx.pasteMode() || !staff->links() || staff->links()->empty())) {
    1249            0 :                 Spanner* placeholder = new TextLine(ctx.dummy());
    1250            0 :                 placeholder->setEndElement(note);
    1251            0 :                 placeholder->setTrack2(note->track());
    1252            0 :                 placeholder->setTick(Fraction(0, 1));
    1253            0 :                 placeholder->setTick2(ctx.tick());
    1254            0 :                 ctx.addSpanner(id1, placeholder);
    1255              :             }
    1256              :         }
    1257            0 :         e.readNext();
    1258          371 :     } else if (tag == "TextLine"
    1259          371 :                || tag == "Glissando") {
    1260            0 :         Spanner* sp = toSpanner(Factory::createItemByName(tag, ctx.dummy()));
    1261              :         // check this is not a lower-to-higher cross-staff spanner we already got
    1262            0 :         int id = e.intAttribute("id");
    1263            0 :         Spanner* placeholder = ctx.findSpanner(id);
    1264            0 :         if (placeholder && placeholder->endElement()) {
    1265              :             // if it is, fill end data from place-holder
    1266            0 :             sp->setEndElement(placeholder->endElement());
    1267            0 :             sp->setTrack2(placeholder->track2());
    1268            0 :             sp->setTick(ctx.tick());                                // make sure tick2 will be correct
    1269            0 :             sp->setTick2(placeholder->tick2());
    1270            0 :             toNote(placeholder->endElement())->addSpannerBack(sp);
    1271              :             // remove no longer needed place-holder before reading the new spanner,
    1272              :             // as reading it also adds it to XML reader list of spanners,
    1273              :             // which would overwrite the place-holder
    1274            0 :             ctx.removeSpanner(placeholder);
    1275            0 :             delete placeholder;
    1276              :         }
    1277            0 :         sp->setTrack(note->track());
    1278            0 :         read400::TRead::readItem(sp, e, ctx);
    1279            0 :         Staff* staff = note->staff();
    1280              :         // DISABLE pasting of glissandi into staves with other linked staves
    1281              :         // because the glissando is not properly cloned into the linked staves
    1282            0 :         if (ctx.pasteMode() && staff && staff->links() && !staff->links()->empty()) {
    1283            0 :             ctx.removeSpanner(sp);          // read() added the element to the XMLReader: remove it
    1284            0 :             delete sp;
    1285              :         } else {
    1286            0 :             sp->setStartElement(note);
    1287            0 :             sp->setTick(ctx.tick());
    1288            0 :             note->addSpannerFor(sp);
    1289            0 :             sp->setOwnershipParent(note);
    1290            0 :             adjustPlacement(sp);
    1291              :         }
    1292          371 :     } else if (tag == "offset") {
    1293            0 :         TRead::readItemProperties(note, e, ctx);
    1294          371 :     } else if (TRead::readItemProperties(note, e, ctx)) {
    1295              :     } else {
    1296          362 :         return false;
    1297              :     }
    1298         2910 :     return true;
    1299              : }
    1300              : 
    1301              : //---------------------------------------------------------
    1302              : //   ReadStyleName206
    1303              : //    Retrieve the content of the "style" tag from the
    1304              : //    String with the content of the whole text tag
    1305              : //---------------------------------------------------------
    1306              : 
    1307          127 : static String ReadStyleName206(String xmlTag)
    1308              : {
    1309          127 :     size_t beginIdx = xmlTag.indexOf(u"<style>");
    1310          127 :     if (beginIdx == muse::nidx) {
    1311           20 :         return String();
    1312              :     }
    1313          107 :     beginIdx += String(u"<style>").size();
    1314              : 
    1315          107 :     size_t endIdx = xmlTag.indexOf(u"</style>");
    1316          107 :     IF_ASSERT_FAILED(endIdx > 0) {
    1317            0 :         return String();
    1318              :     }
    1319              : 
    1320          107 :     String s = xmlTag.mid(beginIdx, (endIdx - beginIdx));
    1321          107 :     return s;
    1322          107 : }
    1323              : 
    1324              : //---------------------------------------------------------
    1325              : //   readTextPropertyStyle206
    1326              : //    This reads only the 'style' tag, so that it can be read
    1327              : //    before setting anything else.
    1328              : //---------------------------------------------------------
    1329              : 
    1330          116 : static bool readTextPropertyStyle206(String xmlTag, ReadContext& ctx, TextBase* t, EngravingItem* be)
    1331              : {
    1332          116 :     String s = ReadStyleName206(xmlTag);
    1333              : 
    1334          116 :     if (s.isEmpty()) {
    1335           16 :         return false;
    1336              :     }
    1337              : 
    1338          100 :     if (!be->isTuplet()) {        // Hack
    1339           73 :         if (excessTextStyles206.find(s) != excessTextStyles206.end()) {
    1340              :             // Init the text with a style that can't be stored as a user style
    1341              :             // due to the limit on the number of user styles possible.
    1342              :             // Use User-1, since it has all the possible user style pids
    1343            0 :             t->initTextStyleType(TextStyleType::DEFAULT);
    1344            0 :             std::map<Sid, PropertyValue> styleVals = excessTextStyles206[s];
    1345            0 :             for (const auto& p : *textStyle(TextStyleType::USER1)) {
    1346            0 :                 if (t->getProperty(p.pid) == t->propertyDefault(p.pid) && styleVals.find(p.sid) != styleVals.end()) {
    1347            0 :                     t->setProperty(p.pid, styleVals[p.sid]);
    1348              :                 }
    1349              :             }
    1350            0 :         } else {
    1351              :             TextStyleType ss;
    1352           73 :             ss = ctx.lookupUserTextStyle(s);
    1353           73 :             if (ss == TextStyleType::TEXT_TYPES) {
    1354           45 :                 muse::ByteArray ba = s.toAscii();
    1355           45 :                 ss = TConv::fromXml(ba.constChar(), TextStyleType::DEFAULT);
    1356           45 :             }
    1357           73 :             if (ss != TextStyleType::TEXT_TYPES) {
    1358           73 :                 t->initTextStyleType(ss);
    1359              :             }
    1360              :         }
    1361              :     }
    1362              : 
    1363          100 :     return true;
    1364          116 : }
    1365              : 
    1366              : //---------------------------------------------------------
    1367              : //   readTextProperties206
    1368              : //---------------------------------------------------------
    1369              : 
    1370          292 : static bool readTextProperties206(XmlReader& e, ReadContext& ctx, TextBase* t)
    1371              : {
    1372          292 :     const AsciiStringView tag(e.name());
    1373          292 :     if (tag == "style") {
    1374          100 :         e.skipCurrentElement();     // read in readTextPropertyStyle206
    1375          192 :     } else if (tag == "foregroundColor") { // same as "color" ?
    1376            0 :         e.skipCurrentElement();
    1377          192 :     } else if (tag == "frame") {
    1378           16 :         t->setFrameType(e.readBool() ? FrameType::RECTANGLE : FrameType::NO_FRAME);
    1379           16 :         t->setPropertyFlags(Pid::FRAME_TYPE, PropertyFlags::UNSTYLED);
    1380          176 :     } else if (tag == "frameRound") {
    1381            0 :         read400::TRead::readProperty(t, e, ctx, Pid::FRAME_ROUND);
    1382          176 :     } else if (tag == "circle") {
    1383            0 :         if (e.readBool()) {
    1384            0 :             t->setFrameType(FrameType::CIRCLE);
    1385              :         } else {
    1386            0 :             if (t->circle()) {
    1387            0 :                 t->setFrameType(FrameType::RECTANGLE);
    1388              :             }
    1389              :         }
    1390            0 :         t->setPropertyFlags(Pid::FRAME_TYPE, PropertyFlags::UNSTYLED);
    1391          176 :     } else if (tag == "paddingWidthS") {
    1392            0 :         read400::TRead::readProperty(t, e, ctx, Pid::FRAME_PADDING);
    1393          176 :     } else if (tag == "frameWidthS") {
    1394            0 :         read400::TRead::readProperty(t, e, ctx, Pid::FRAME_WIDTH);
    1395          176 :     } else if (tag == "frameColor") {
    1396            0 :         read400::TRead::readProperty(t, e, ctx, Pid::FRAME_FG_COLOR);
    1397          176 :     } else if (tag == "backgroundColor") {
    1398            0 :         read400::TRead::readProperty(t, e, ctx, Pid::FRAME_BG_COLOR);
    1399          176 :     } else if (tag == "halign") {
    1400            0 :         Align align = t->align();
    1401            0 :         align.horizontal = TConv::fromXml(e.readAsciiText(), AlignH::LEFT);
    1402            0 :         t->setAlign(align);
    1403            0 :         t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
    1404            0 :         t->setPosition(align.horizontal);
    1405            0 :         t->setPropertyFlags(Pid::POSITION, PropertyFlags::UNSTYLED);
    1406          176 :     } else if (tag == "valign") {
    1407            0 :         Align align = t->align();
    1408            0 :         align.vertical = TConv::fromXml(e.readAsciiText(), AlignV::TOP);
    1409            0 :         t->setAlign(align);
    1410            0 :         t->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
    1411          176 :     } else if (tag == "pos") {
    1412            7 :         read400::TRead::readProperty(t, e, ctx, Pid::OFFSET);
    1413            7 :         if (t->align() == AlignV::TOP) {
    1414            2 :             t->ryoffset() += .5 * ctx.spatium();           // HACK: bbox is different in 2.x
    1415              :         }
    1416            7 :         adjustPlacement(t);
    1417          169 :     } else if (!read400::TRead::readTextProperties(t, e, ctx)) {
    1418           52 :         return false;
    1419              :     }
    1420          240 :     return true;
    1421              : }
    1422              : 
    1423              : //---------------------------------------------------------
    1424              : //   TextReaderContext206
    1425              : //    For 2.x files, the style tag could be in a different
    1426              : //    position with respect to 3.x files. Since seek
    1427              : //    position is not reliable for readline in QIODevices (for
    1428              : //    example because of non-single-byte characters in at least
    1429              : //    one of the fields; some two-byte characters are counted as
    1430              : //    two single-byte characters and thus the reading could
    1431              : //    start at the wrong position), a copy of the text tag
    1432              : //    is created and read in a separate XmlReader, while
    1433              : //    the text style is extracted from a String containing
    1434              : //    the whole text xml tag.
    1435              : //    TextReaderContext206 takes care of this process
    1436              : //---------------------------------------------------------
    1437              : 
    1438              : class TextReaderContext206
    1439              : {
    1440              :     XmlReader& origReader;
    1441              :     XmlReader tagReader;
    1442              :     String xmlTag;
    1443              : 
    1444              : public:
    1445          116 :     TextReaderContext206(XmlReader& e)
    1446          116 :         : origReader(e)
    1447              :     {
    1448              :         // Create a new xml document containing only the (text) xml chunk
    1449          116 :         String name = String::fromAscii(origReader.name().ascii());
    1450          116 :         String prefix = u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<" + name + u">";
    1451          116 :         xmlTag = origReader.readXml();
    1452          116 :         xmlTag.prepend(prefix);
    1453          116 :         xmlTag.append(u"</" + name + u">\n");
    1454          116 :         muse::ByteArray data = xmlTag.toUtf8();
    1455          116 :         tagReader.setData(data);  // Add the xml data to the XmlReader
    1456              :         // the additional lines are needed to output the correct line number
    1457              :         // of the original file in case of error
    1458          116 :         int64_t additionalOffset = origReader.byteOffset() - prefix.toStdString().size();     // Subtracting the 2 new lines that will be added
    1459          116 :         tagReader.setByteOffsetAdjustment(additionalOffset);
    1460          116 :         copyProperties(origReader, tagReader);
    1461          116 :         tagReader.readNextStartElement();     // read up to the first "name" tag
    1462          116 :     }
    1463              : 
    1464              :     // Disable copying the TextReaderContext206
    1465              :     TextReaderContext206(const TextReaderContext206&) = delete;
    1466              :     TextReaderContext206& operator=(const TextReaderContext206&) = delete;
    1467              : 
    1468          116 :     ~TextReaderContext206()
    1469              :     {
    1470              :         // Ensure to copy back the potentially changed properties
    1471              :         // to the original XmlReader before destruction
    1472          116 :         copyProperties(tagReader, origReader);
    1473          116 :     }
    1474              : 
    1475          823 :     XmlReader& reader() { return tagReader; }
    1476          127 :     const String& tag() { return xmlTag; }
    1477              : private:
    1478              :     void copyProperties(XmlReader& original, XmlReader& derived);
    1479              : };
    1480              : 
    1481              : //---------------------------------------------------------
    1482              : //   copyProperties
    1483              : //    Copy some of the XmlReader properties of an original
    1484              : //    XmlReader object to a "derived" XmlReader object.
    1485              : //    This is used for example when using the derived XmlReader
    1486              : //    to read the element properties of a 2.x text, or to
    1487              : //    update the base XmlReader object (for example, its
    1488              : //    link list) after reading the properties of a 2.x text
    1489              : //---------------------------------------------------------
    1490              : 
    1491          232 : void TextReaderContext206::copyProperties(XmlReader& original, XmlReader& derived)
    1492              : {
    1493          232 :     derived.setDocName(original.docName());
    1494          232 : }
    1495              : 
    1496              : //---------------------------------------------------------
    1497              : //   convertMScoreFontMarkerSymbols206
    1498              : //---------------------------------------------------------
    1499              : 
    1500           12 : static String convertMScoreFontMarkerSymbols206(const String& xmlText)
    1501              : {
    1502           12 :     String s = xmlText;
    1503           12 :     s.replace(u"\ue177", u"<sym>segno</sym>");
    1504           12 :     s.replace(u"\ue178", u"<sym>coda</sym>");
    1505           12 :     s.replace(u"\ue179", u"<sym>codaSquare</sym>");
    1506           12 :     return s;
    1507            0 : }
    1508              : 
    1509              : //---------------------------------------------------------
    1510              : //   readText206
    1511              : //---------------------------------------------------------
    1512              : 
    1513           91 : static void readText206(XmlReader& e, ReadContext& ctx, TextBase* t, EngravingItem* be)
    1514              : {
    1515           91 :     TextReaderContext206 tctx(e);
    1516           91 :     readTextPropertyStyle206(tctx.tag(), ctx, t, be);
    1517          423 :     while (tctx.reader().readNextStartElement()) {
    1518          241 :         if (!readTextProperties206(tctx.reader(), ctx, t)) {
    1519           48 :             tctx.reader().unknown();
    1520              :         }
    1521              :     }
    1522           91 :     CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
    1523           91 : }
    1524              : 
    1525              : //---------------------------------------------------------
    1526              : //   read
    1527              : //---------------------------------------------------------
    1528              : 
    1529            1 : static void readTempoText(TempoText* t, XmlReader& e, ReadContext& ctx)
    1530              : {
    1531            1 :     TextReaderContext206 tctx(e);
    1532            1 :     readTextPropertyStyle206(tctx.tag(), ctx, t, t);
    1533            4 :     while (tctx.reader().readNextStartElement()) {
    1534            3 :         const AsciiStringView tag(tctx.reader().name());
    1535            3 :         if (tag == "tempo") {
    1536            1 :             t->setTempo(tctx.reader().readDouble());
    1537            2 :         } else if (tag == "followText") {
    1538            1 :             t->setFollowText(tctx.reader().readInt());
    1539            1 :         } else if (!readTextProperties206(tctx.reader(), ctx, t)) {
    1540            0 :             tctx.reader().unknown();
    1541              :         }
    1542              :     }
    1543              :     // check sanity
    1544            1 :     if (t->xmlText().isEmpty()) {
    1545            0 :         t->setXmlText(String(u"<sym>metNoteQuarterUp</sym> = %1").arg(int(lrint(t->tempo().toBPM().val))));
    1546            0 :         t->setVisible(false);
    1547              :     } else {
    1548            1 :         t->setXmlText(t->xmlText().replace(u"<sym>unicode", u"<sym>met"));
    1549              :     }
    1550              : 
    1551            1 :     CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
    1552              : 
    1553            1 :     t->resetProperty(Pid::MUSIC_SYMBOL_SIZE);
    1554            1 : }
    1555              : 
    1556              : //---------------------------------------------------------
    1557              : //   readMarker
    1558              : //---------------------------------------------------------
    1559              : 
    1560           12 : static void readMarker(Marker* m, XmlReader& e, ReadContext& ctx)
    1561              : {
    1562           12 :     TextReaderContext206 tctx(e);
    1563           12 :     readTextPropertyStyle206(tctx.tag(), ctx, m, m);
    1564           12 :     MarkerType mt = MarkerType::SEGNO;
    1565              : 
    1566           48 :     while (tctx.reader().readNextStartElement()) {
    1567           36 :         const AsciiStringView tag(tctx.reader().name());
    1568           36 :         if (tag == "label") {
    1569           12 :             AsciiStringView s(tctx.reader().readAsciiText());
    1570           12 :             m->setLabel(String::fromAscii(s.ascii()));
    1571           12 :             mt = TConv::fromXml(s, MarkerType::USER);
    1572           24 :         } else if (!readTextProperties206(tctx.reader(), ctx, m)) {
    1573            0 :             tctx.reader().unknown();
    1574              :         }
    1575              :     }
    1576           12 :     m->setXmlText(convertMScoreFontMarkerSymbols206(m->xmlText()));
    1577           12 :     m->setMarkerType(mt);
    1578              : 
    1579           12 :     CompatUtils::migrateOffsetPre302(m, ctx.mscVersion());
    1580           12 : }
    1581              : 
    1582              : //---------------------------------------------------------
    1583              : //   readDynamic
    1584              : //---------------------------------------------------------
    1585              : 
    1586            1 : static void readDynamic(Dynamic* d, XmlReader& e, ReadContext& ctx)
    1587              : {
    1588            1 :     TextReaderContext206 tctx(e);
    1589            1 :     readTextPropertyStyle206(tctx.tag(), ctx, d, d);
    1590            3 :     while (tctx.reader().readNextStartElement()) {
    1591            2 :         const AsciiStringView tag = tctx.reader().name();
    1592            2 :         if (tag == "subtype") {
    1593            1 :             d->setDynamicType(tctx.reader().readText());
    1594            1 :         } else if (tag == "velocity") {
    1595            0 :             d->setVelocity(tctx.reader().readInt());
    1596            1 :         } else if (tag == "dynType") {
    1597            0 :             d->setVoiceAssignment(Read206::readDynamicRange(tctx.reader().readInt()));
    1598            1 :         } else if (tag == "size") {
    1599            0 :             e.skipCurrentElement();
    1600            1 :         } else if (!readTextProperties206(tctx.reader(), ctx, d)) {
    1601            0 :             tctx.reader().unknown();
    1602              :         }
    1603              :     }
    1604            1 :     d->setSize(10.0);
    1605            1 :     if (d->xmlText().contains(u"<sym>") && !d->xmlText().contains(u"<font")) {
    1606            1 :         d->setAlign(Align(AlignH::HCENTER, AlignV::BASELINE));
    1607              :     }
    1608            1 :     CompatUtils::migrateOffsetPre302(d, ctx.mscVersion());
    1609            1 : }
    1610              : 
    1611              : //---------------------------------------------------------
    1612              : //   readTuplet
    1613              : //---------------------------------------------------------
    1614              : 
    1615           11 : static void readTuplet206(Tuplet* tuplet, XmlReader& e, ReadContext& ctx)
    1616              : {
    1617           64 :     while (e.readNextStartElement()) {
    1618           53 :         const AsciiStringView tag(e.name());
    1619           53 :         if (tag == "Number") {
    1620           11 :             Text* _number = Factory::createText(tuplet);
    1621           11 :             _number->setComposition(true);
    1622           11 :             tuplet->setNumber(_number);
    1623              :             // _number reads property defaults from parent tuplet as "composition" is set:
    1624           11 :             tuplet->resetNumberProperty();
    1625           11 :             readText206(e, ctx, _number, tuplet);
    1626           11 :             _number->setVisible(tuplet->visible());           //?? override saved property
    1627           11 :             _number->setColor(tuplet->color());
    1628           11 :             _number->setTrack(tuplet->track());
    1629              :             // move property flags from _number
    1630           66 :             for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN, Pid::SIZE_SPATIUM_DEPENDENT }) {
    1631           55 :                 tuplet->setPropertyFlags(p, _number->propertyFlags(p));
    1632              :             }
    1633           42 :         } else if (!Read206::readTupletProperties206(e, ctx, tuplet)) {
    1634            0 :             e.unknown();
    1635              :         }
    1636              :     }
    1637           11 :     Fraction r = (tuplet->ratio() == Fraction(1, 1)) ? tuplet->ratio() : tuplet->ratio().reduced();
    1638           11 :     Fraction f(r.denominator(), tuplet->baseLen().fraction().denominator());
    1639           11 :     tuplet->setTicks(f.reduced());
    1640           11 : }
    1641              : 
    1642              : //---------------------------------------------------------
    1643              : //   readLyrics
    1644              : //---------------------------------------------------------
    1645              : 
    1646            1 : static void readLyrics(Lyrics* lyrics, XmlReader& e, ReadContext& ctx)
    1647              : {
    1648            1 :     int iEndTick = 0;               // used for backward compatibility
    1649            1 :     Text* _verseNumber = 0;
    1650              : 
    1651            2 :     while (e.readNextStartElement()) {
    1652            1 :         const AsciiStringView tag(e.name());
    1653            1 :         if (tag == "endTick") {
    1654              :             // store <endTick> tag value until a <ticks> tag has been read
    1655              :             // which positions this lyrics element in the score
    1656            0 :             iEndTick = e.readInt();
    1657            1 :         } else if (tag == "Number") {
    1658            0 :             _verseNumber = Factory::createText(lyrics);
    1659            0 :             readText206(e, ctx, _verseNumber, lyrics);
    1660            0 :             _verseNumber->setOwnershipParent(lyrics);
    1661            1 :         } else if (tag == "style") {
    1662            0 :             e.readText();          // ignore style
    1663            1 :         } else if (!read400::TRead::readProperties(lyrics, e, ctx)) {
    1664            0 :             e.unknown();
    1665              :         }
    1666              :     }
    1667              : 
    1668              :     // if any endTick, make it relative to current tick
    1669            1 :     if (iEndTick) {
    1670            0 :         lyrics->setTicks(Fraction::fromTicks(iEndTick) - ctx.tick());
    1671              :     }
    1672            1 :     if (_verseNumber) {
    1673              :         // TODO: add text to main text
    1674            0 :         delete _verseNumber;
    1675              :     }
    1676            1 :     lyrics->setAutoplace(true);
    1677              : 
    1678            1 :     if (!lyrics->offset().isNull() && !ctx.pasteMode()) {
    1679              :         // fix offset for pre-3.1 scores
    1680              :         // 2.x and earlier: y offset was relative to staff; x offset was relative to center of notehead
    1681            0 :         lyrics->rxoffset() -= lyrics->symWidth(SymId::noteheadBlack) * 0.5;
    1682              :         //lyrics->ryoffset() -= lyrics->placeBelow() && lyrics->staff() ? lyrics->staff()->height() : 0.0;
    1683              :         // temporarily set placement to above, since the original offset is relative to top of staff
    1684              :         // depend on adjustPlacement() to change the placement if appropriate
    1685            0 :         lyrics->setPlacement(PlacementV::ABOVE);
    1686            0 :         adjustPlacement(lyrics);
    1687              :     }
    1688            1 :     CompatUtils::migrateOffsetPre302(lyrics, ctx.mscVersion());
    1689            1 : }
    1690              : 
    1691          742 : bool Read206::readDurationProperties206(XmlReader& e, ReadContext& ctx, DurationElement* de)
    1692              : {
    1693          742 :     if (e.name() == "Tuplet") {
    1694          136 :         int i = e.readInt();
    1695          136 :         Tuplet* t = ctx.findTuplet(i);
    1696          136 :         if (!t) {
    1697            0 :             LOGD("readDurationProperties206(): Tuplet id %d not found", i);
    1698              :         }
    1699          136 :         if (t) {
    1700          136 :             de->setTuplet(t);
    1701          136 :             if (!ctx.undoStackActive()) {         // HACK, also added in Undo::AddElement()
    1702          136 :                 t->add(de);
    1703              :             }
    1704              :         }
    1705          136 :         return true;
    1706          606 :     } else if (TRead::readItemProperties(de, e, ctx)) {
    1707           73 :         return true;
    1708              :     }
    1709          533 :     return false;
    1710              : }
    1711              : 
    1712          116 : bool Read206::readTupletProperties206(XmlReader& e, ReadContext& ctx, Tuplet* de)
    1713              : {
    1714          116 :     const AsciiStringView tag(e.name());
    1715              : 
    1716          116 :     if (read400::TRead::readStyledProperty(de, tag, e, ctx)) {
    1717          102 :     } else if (tag == "normalNotes") {
    1718           27 :         de->setProperty(Pid::NORMAL_NOTES, e.readInt());
    1719           75 :     } else if (tag == "actualNotes") {
    1720           27 :         de->setProperty(Pid::ACTUAL_NOTES, e.readInt());
    1721           48 :     } else if (tag == "p1") {
    1722            0 :         de->setProperty(Pid::P1, PropertyValue::fromValue(e.readPoint() * ctx.spatium()));
    1723           48 :     } else if (tag == "p2") {
    1724            0 :         de->setProperty(Pid::P2, PropertyValue::fromValue(e.readPoint() * ctx.spatium()));
    1725           48 :     } else if (tag == "baseNote") {
    1726           27 :         de->setBaseLen(TDuration(TConv::fromXml(e.readAsciiText(), DurationType::V_INVALID)));
    1727           21 :     } else if (tag == "Number") {
    1728           16 :         Text* _number = Factory::createText(de);
    1729           16 :         de->setNumber(_number);
    1730           16 :         _number->setComposition(true);
    1731              : //            _number->setSubStyleId(SubStyleId::TUPLET);
    1732              : //            initSubStyle(SubStyleId::TUPLET);   // hack: initialize number
    1733           80 :         for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN }) {
    1734           64 :             _number->resetProperty(p);
    1735              :         }
    1736           16 :         readText206(e, ctx, _number, de);
    1737           16 :         _number->setVisible(de->visible());         //?? override saved property
    1738           16 :         _number->setTrack(de->track());
    1739              :         // move property flags from _number
    1740           80 :         for (auto p : { Pid::FONT_FACE, Pid::FONT_SIZE, Pid::FONT_STYLE, Pid::ALIGN }) {
    1741           64 :             de->setPropertyFlags(p, _number->propertyFlags(p));
    1742              :         }
    1743            5 :     } else if (!readDurationProperties206(e, ctx, de)) {
    1744            0 :         return false;
    1745              :     }
    1746          116 :     return true;
    1747              : }
    1748              : 
    1749         3220 : bool Read206::readChordRestProperties206(XmlReader& e, ReadContext& ctx, ChordRest* ch)
    1750              : {
    1751         3220 :     const AsciiStringView tag(e.name());
    1752              : 
    1753         3220 :     if (tag == "durationType") {
    1754         1945 :         ch->setDurationType(TConv::fromXml(e.readAsciiText(), DurationType::V_QUARTER));
    1755         1945 :         if (ch->actualDurationType().type() != DurationType::V_MEASURE) {
    1756         1436 :             if (ctx.mscVersion() < 112 && (ch->isRest())
    1757            0 :                 &&            // for backward compatibility, convert V_WHOLE rests to V_MEASURE
    1758              :                               // if long enough to fill a measure.
    1759              :                               // OTOH, freshly created (un-initialized) rests have numerator == 0 (< 4/4)
    1760              :                               // (see Fraction() constructor in fraction.h; this happens for instance
    1761              :                               // when pasting selection from clipboard): they should not be converted
    1762            0 :                 ch->ticks().numerator() != 0
    1763         1436 :                 &&            // rest durations are initialized to full measure duration when
    1764              :                               // created upon reading the <Rest> tag (see Measure::read() )
    1765              :                               // so a V_WHOLE rest in a measure of 4/4 or less => V_MEASURE
    1766         1436 :                 (ch->actualDurationType() == DurationType::V_WHOLE && ch->ticks() <= Fraction(4, 4))) {
    1767              :                 // old pre 2.0 scores: convert
    1768            0 :                 ch->setDurationType(DurationType::V_MEASURE);
    1769              :             } else {    // not from old score: set duration fraction from duration type
    1770         1436 :                 ch->setTicks(ch->actualDurationType().fraction());
    1771              :             }
    1772              :         }
    1773         1275 :     } else if (tag == "BeamMode") {
    1774              :         // 206 used begin32/begin64 for beam mode
    1775              :         // 410 uses begin16/begin32
    1776            2 :         auto txt = e.readAsciiText();
    1777            2 :         if (txt == "begin64") {
    1778            0 :             txt = "begin32";
    1779            2 :         } else if (txt == "begin32") {
    1780            0 :             txt = "begin16";
    1781              :         }
    1782            2 :         ch->setBeamMode(TConv::fromXml(txt, BeamMode::AUTO));
    1783         1273 :     } else if (tag == "Articulation") {
    1784          215 :         EngravingItem* el = readArticulation(ch, e, ctx);
    1785          215 :         if (el->isFermata()) {
    1786           31 :             ch->segment()->add(el);
    1787              :         } else {
    1788          184 :             ch->add(el);
    1789              :         }
    1790         1058 :     } else if (tag == "leadingSpace" || tag == "trailingSpace") {
    1791            0 :         LOGD("ChordRest: %s obsolete", tag.ascii());
    1792            0 :         e.skipCurrentElement();
    1793         1058 :     } else if (tag == "Beam") {
    1794           98 :         int id = e.readInt();
    1795           98 :         Beam* beam = ctx.findBeam(id);
    1796           98 :         if (beam) {
    1797           98 :             beam->add(ch);              // also calls ch->setBeam(beam)
    1798              :         } else {
    1799            0 :             LOGD("Beam id %d not found", id);
    1800              :         }
    1801          960 :     } else if (tag == "small") {
    1802            0 :         ch->setSmall(e.readInt());
    1803          960 :     } else if (tag == "duration") {
    1804          183 :         ch->setTicks(e.readFraction());
    1805          777 :     } else if (tag == "ticklen") {      // obsolete (version < 1.12)
    1806            0 :         int mticks = ctx.compatTimeSigMap()->timesig(ctx.tick()).timesig().ticks();
    1807            0 :         int i = e.readInt();
    1808            0 :         if (i == 0) {
    1809            0 :             i = mticks;
    1810              :         }
    1811            0 :         if ((ch->isRest()) && (mticks == i)) {
    1812            0 :             ch->setDurationType(DurationType::V_MEASURE);
    1813            0 :             ch->setTicks(Fraction::fromTicks(i));
    1814              :         } else {
    1815            0 :             Fraction f = Fraction::fromTicks(i);
    1816            0 :             ch->setTicks(f);
    1817            0 :             ch->setDurationType(TDuration(f));
    1818              :         }
    1819          777 :     } else if (tag == "dots") {
    1820            8 :         ch->setDots(e.readInt());
    1821          769 :     } else if (tag == "move") {
    1822            0 :         ch->setStaffMove(e.readInt());
    1823          769 :     } else if (tag == "Slur") {
    1824           32 :         int id = e.intAttribute("id");
    1825           32 :         if (id == 0) {
    1826           32 :             id = e.intAttribute("number");                        // obsolete
    1827              :         }
    1828           32 :         Spanner* spanner = ctx.findSpanner(id);
    1829           32 :         String atype(e.attribute("type"));
    1830              : 
    1831           32 :         if (!spanner) {
    1832            0 :             if (atype == "stop") {
    1833            0 :                 SpannerValues sv;
    1834            0 :                 sv.spannerId = id;
    1835            0 :                 sv.track2    = ch->track();
    1836            0 :                 sv.tick2     = ctx.tick();
    1837            0 :                 ctx.addSpannerValues(sv);
    1838            0 :             } else if (atype == "start") {
    1839            0 :                 LOGD("spanner: start without spanner");
    1840              :             }
    1841              :         } else {
    1842           32 :             if (atype == "start") {
    1843           16 :                 if (spanner->ticks() > Fraction(0, 1) && spanner->tick() == Fraction(-1, 1)) {       // stop has been read first
    1844            3 :                     spanner->setTicks(spanner->ticks() - ctx.tick() - Fraction::eps());
    1845              :                 }
    1846           16 :                 spanner->setTick(ctx.tick());
    1847           16 :                 spanner->setTrack(ch->track());
    1848           16 :                 if (spanner->isSlur()) {
    1849           16 :                     spanner->setStartElement(ch);
    1850              :                 }
    1851           16 :                 if (ctx.pasteMode()) {
    1852            0 :                     for (EngravingObject* el : spanner->linkList()) {
    1853            0 :                         if (el == spanner) {
    1854            0 :                             continue;
    1855              :                         }
    1856            0 :                         Spanner* ls = toSpanner(el);
    1857            0 :                         ls->setTick(spanner->tick());
    1858            0 :                         for (EngravingObject* ee : ch->linkList()) {
    1859            0 :                             ChordRest* cr = toChordRest(ee);
    1860            0 :                             if (cr->staffIdx() == ls->staffIdx()) {
    1861            0 :                                 ls->setTrack(cr->track());
    1862            0 :                                 if (ls->isSlur()) {
    1863            0 :                                     ls->setStartElement(cr);
    1864              :                                 }
    1865            0 :                                 break;
    1866              :                             }
    1867            0 :                         }
    1868            0 :                     }
    1869              :                 }
    1870           16 :             } else if (atype == "stop") {
    1871           16 :                 spanner->setTick2(ctx.tick());
    1872           16 :                 spanner->setTrack2(ch->track());
    1873           16 :                 if (spanner->isSlur()) {
    1874           16 :                     spanner->setEndElement(ch);
    1875              :                 }
    1876           16 :                 ChordRest* start = toChordRest(spanner->startElement());
    1877           16 :                 if (start) {
    1878           13 :                     spanner->setTrack(start->track());
    1879              :                 }
    1880           16 :                 if (ctx.pasteMode()) {
    1881            0 :                     for (EngravingObject* el : spanner->linkList()) {
    1882            0 :                         if (el == spanner) {
    1883            0 :                             continue;
    1884              :                         }
    1885            0 :                         Spanner* ls = toSpanner(el);
    1886            0 :                         ls->setTick2(spanner->tick2());
    1887            0 :                         for (EngravingObject* ee : ch->linkList()) {
    1888            0 :                             ChordRest* cr = toChordRest(ee);
    1889            0 :                             if (cr->staffIdx() == ls->staffIdx()) {
    1890            0 :                                 ls->setTrack2(cr->track());
    1891            0 :                                 if (ls->isSlur()) {
    1892            0 :                                     ls->setEndElement(cr);
    1893              :                                 }
    1894            0 :                                 break;
    1895              :                             }
    1896            0 :                         }
    1897            0 :                     }
    1898              :                 }
    1899              :             } else {
    1900            0 :                 LOGD("readChordRestProperties206(): unknown Slur type <%s>", muPrintable(atype));
    1901              :             }
    1902              :         }
    1903           32 :         e.readNext();
    1904          769 :     } else if (tag == "Lyrics") {
    1905            0 :         Lyrics* l = Factory::createLyrics(ch);
    1906            0 :         l->setTrack(ctx.track());
    1907            0 :         readLyrics(l, e, ctx);
    1908            0 :         ch->add(l);
    1909          737 :     } else if (tag == "pos") {
    1910            0 :         PointF pt = e.readPoint();
    1911            0 :         ch->setOffset(pt * ch->spatium());
    1912          737 :     } else if (ch->isRest() && tag == "Image") {
    1913            0 :         if (MScore::noImages) {
    1914            0 :             e.skipCurrentElement();
    1915              :         } else {
    1916            0 :             Image* image = new Image(ch);
    1917            0 :             image->setTrack(ctx.track());
    1918            0 :             read400::TRead::read(image, e, ctx);
    1919            0 :             ch->add(image);
    1920              :         }
    1921          737 :     } else if (!readDurationProperties206(e, ctx, ch)) {
    1922          533 :         return false;
    1923              :     }
    1924         2687 :     return true;
    1925              : }
    1926              : 
    1927         2324 : bool Read206::readChordProperties206(XmlReader& e, ReadContext& ctx, Chord* ch)
    1928              : {
    1929         2324 :     const AsciiStringView tag(e.name());
    1930              : 
    1931         2324 :     if (tag == "Note") {
    1932            0 :         Note* note = Factory::createNote(ch);
    1933              :         // the note needs to know the properties of the track it belongs to
    1934            0 :         note->setTrack(ch->track());
    1935            0 :         readNote206(note, e, ctx);
    1936            0 :         ch->add(note);
    1937         2324 :     } else if (readChordRestProperties206(e, ctx, ch)) {
    1938          455 :     } else if (tag == "Stem") {
    1939            1 :         Stem* s = Factory::createStem(ch);
    1940            1 :         read400::TRead::read(s, e, ctx);
    1941            1 :         ch->add(s);
    1942          454 :     } else if (tag == "Hook") {
    1943            0 :         Hook* hook = new Hook(ch);
    1944            0 :         read400::TRead::read(hook, e, ctx);
    1945            0 :         ch->add(hook);
    1946          454 :     } else if (tag == "appoggiatura") {
    1947            0 :         ch->setNoteType(NoteType::APPOGGIATURA);
    1948            0 :         e.readNext();
    1949          454 :     } else if (tag == "acciaccatura") {
    1950            0 :         ch->setNoteType(NoteType::ACCIACCATURA);
    1951            0 :         e.readNext();
    1952          454 :     } else if (tag == "grace4") {
    1953            0 :         ch->setNoteType(NoteType::GRACE4);
    1954            0 :         e.readNext();
    1955          454 :     } else if (tag == "grace16") {
    1956            0 :         ch->setNoteType(NoteType::GRACE16);
    1957            0 :         e.readNext();
    1958          454 :     } else if (tag == "grace32") {
    1959            0 :         ch->setNoteType(NoteType::GRACE32);
    1960            0 :         e.readNext();
    1961          454 :     } else if (tag == "grace8after") {
    1962            0 :         ch->setNoteType(NoteType::GRACE8_AFTER);
    1963            0 :         e.readNext();
    1964          454 :     } else if (tag == "grace16after") {
    1965            0 :         ch->setNoteType(NoteType::GRACE16_AFTER);
    1966            0 :         e.readNext();
    1967          454 :     } else if (tag == "grace32after") {
    1968            0 :         ch->setNoteType(NoteType::GRACE32_AFTER);
    1969            0 :         e.readNext();
    1970          454 :     } else if (tag == "StemSlash") {
    1971            0 :         StemSlash* ss = Factory::createStemSlash(ch);
    1972            0 :         read400::TRead::read(ss, e, ctx);
    1973            0 :         ch->add(ss);
    1974          454 :     } else if (read400::TRead::readProperty(ch, tag, e, ctx, Pid::STEM_DIRECTION)) {
    1975          362 :     } else if (tag == "noStem") {
    1976            0 :         ch->setNoStem(e.readInt());
    1977          362 :     } else if (tag == "Arpeggio") {
    1978            0 :         Arpeggio* arpeggio = Factory::createArpeggio(ch);
    1979            0 :         arpeggio->setTrack(ch->track());
    1980            0 :         read400::TRead::read(arpeggio, e, ctx);
    1981            0 :         ch->add(arpeggio);
    1982              :     }
    1983              :     // old glissando format, chord-to-chord, attached to its final chord
    1984          362 :     else if (tag == "Glissando") {
    1985              :         // the measure we are reading is not inserted in the score yet
    1986              :         // as well as, possibly, the glissando intended initial chord;
    1987              :         // then we cannot fully link the glissando right now;
    1988              :         // temporarily attach the glissando to its final note as a back spanner;
    1989              :         // after the whole score is read, Score::connectTies() will look for
    1990              :         // the suitable initial note
    1991            0 :         Note* finalNote = ch->upNote();
    1992            0 :         Glissando* gliss = Factory::createGlissando(ctx.dummy());
    1993            0 :         read400::TRead::read(gliss, e, ctx);
    1994            0 :         gliss->setStartElement(nullptr);
    1995            0 :         gliss->setEndElement(nullptr);
    1996              :         // in TAB, use straight line with no text
    1997            0 :         if (ctx.staff(static_cast<int>(ctx.track()) >> 2)->isTabStaff(ch->tick())) {
    1998            0 :             gliss->setGlissandoType(GlissandoType::STRAIGHT);
    1999            0 :             gliss->setShowText(false);
    2000              :         }
    2001            0 :         finalNote->addSpannerBack(gliss);
    2002          362 :     } else if (tag == "Tremolo") {
    2003            0 :         read400::TRead::TremoloCompat tcompat;
    2004            0 :         tcompat.parent = ch;
    2005            0 :         read400::TRead::read(tcompat, e, ctx);
    2006            0 :         if (tcompat.two) {
    2007            0 :             tcompat.two->setOwnershipParent(ch);
    2008            0 :             tcompat.two->setDurationType(ch->durationType());
    2009            0 :             ch->setTremoloTwoChord(tcompat.two, false);
    2010            0 :         } else if (tcompat.single) {
    2011            0 :             tcompat.single->setOwnershipParent(ch);
    2012            0 :             tcompat.single->setDurationType(ch->durationType());
    2013            0 :             ch->setTremoloSingleChord(tcompat.single);
    2014              :         } else {
    2015            0 :             UNREACHABLE;
    2016              :         }
    2017          362 :     } else if (tag == "tickOffset") {     // obsolete
    2018          362 :     } else if (tag == "ChordLine") {
    2019            0 :         ChordLine* cl = Factory::createChordLine(ch);
    2020            0 :         read400::TRead::read(cl, e, ctx);
    2021            0 :         cl->setOffset(0.0, 0.0);
    2022            0 :         ch->add(cl);
    2023              :     } else {
    2024          362 :         return false;
    2025              :     }
    2026         1962 :     return true;
    2027              : }
    2028              : 
    2029              : //---------------------------------------------------------
    2030              : //   convertDoubleArticulations
    2031              : //    Replace double articulations with proper SMuFL
    2032              : //    symbols which were not available for use prior to 3.0
    2033              : //---------------------------------------------------------
    2034              : 
    2035          906 : static void convertDoubleArticulations(Chord* chord)
    2036              : {
    2037          906 :     std::vector<Articulation*> pairableArticulations;
    2038         1090 :     for (Articulation* a : chord->articulations()) {
    2039          342 :         if (a->isStaccato() || a->isTenuto()
    2040          342 :             || a->isAccent() || a->isMarcato()) {
    2041           76 :             pairableArticulations.push_back(a);
    2042              :         }
    2043              :     }
    2044          906 :     if (pairableArticulations.size() != 2) {
    2045              :         // Do not replace triple articulation if this happens
    2046          878 :         return;
    2047              :     }
    2048              : 
    2049           28 :     SymId newSymId = SymId::noSym;
    2050           56 :     for (int i = 0; i < 2; ++i) {
    2051           56 :         if (newSymId != SymId::noSym) {
    2052           28 :             break;
    2053              :         }
    2054           28 :         Articulation* ai = pairableArticulations[i];
    2055           28 :         Articulation* aj = pairableArticulations[(i == 0) ? 1 : 0];
    2056           28 :         if (ai->isStaccato()) {
    2057           18 :             if (aj->isAccent()) {
    2058            6 :                 newSymId = SymId::articAccentStaccatoAbove;
    2059           12 :             } else if (aj->isMarcato()) {
    2060            6 :                 newSymId = SymId::articMarcatoStaccatoAbove;
    2061            6 :             } else if (aj->isTenuto()) {
    2062            6 :                 newSymId = SymId::articTenutoStaccatoAbove;
    2063              :             }
    2064           10 :         } else if (ai->isTenuto()) {
    2065           10 :             if (aj->isAccent()) {
    2066            4 :                 newSymId = SymId::articTenutoAccentAbove;
    2067            6 :             } else if (aj->isMarcato()) {
    2068            6 :                 newSymId = SymId::articMarcatoTenutoAbove;
    2069              :             }
    2070              :         }
    2071              :     }
    2072              : 
    2073           28 :     if (newSymId != SymId::noSym) {
    2074              :         // We reuse old articulation and change symbol ID
    2075              :         // rather than constructing a new articulation
    2076              :         // in order to preserve its other properties.
    2077           28 :         Articulation* newArtic = pairableArticulations[0];
    2078           84 :         for (Articulation* a : pairableArticulations) {
    2079           56 :             chord->remove(a);
    2080           56 :             if (a != newArtic) {
    2081           28 :                 delete a;
    2082              :             }
    2083              :         }
    2084              : 
    2085           28 :         ArticulationAnchor anchor = newArtic->anchor();
    2086           28 :         newArtic->setSymId(newSymId);
    2087           28 :         newArtic->setAnchor(anchor);
    2088           28 :         chord->add(newArtic);
    2089              :     }
    2090          906 : }
    2091              : 
    2092              : //---------------------------------------------------------
    2093              : //   fixTies
    2094              : //---------------------------------------------------------
    2095              : 
    2096          906 : static void fixTies(Chord* chord)
    2097              : {
    2098          906 :     std::vector<Note*> notes;
    2099         1892 :     for (Note* note : chord->notes()) {
    2100          986 :         Tie* tie = note->tieBack();
    2101          986 :         if (tie && tie->startNote()->pitch() != note->pitch()) {
    2102            0 :             notes.push_back(tie->startNote());
    2103              :         }
    2104              :     }
    2105          906 :     for (Note* note : notes) {
    2106            0 :         Note* endNote = chord->findNote(note->pitch());
    2107            0 :         Note* oldNote = note->tieFor()->endNote();
    2108            0 :         if (oldNote) {
    2109            0 :             oldNote->setTieBack(nullptr);
    2110              :         }
    2111            0 :         note->tieFor()->setEndNote(endNote);
    2112              :     }
    2113          906 : }
    2114              : 
    2115              : //---------------------------------------------------------
    2116              : //   readChord
    2117              : //---------------------------------------------------------
    2118              : 
    2119          906 : static void readChord(Chord* chord, XmlReader& e, ReadContext& ctx)
    2120              : {
    2121         3522 :     while (e.readNextStartElement()) {
    2122         2616 :         const AsciiStringView tag(e.name());
    2123         2616 :         if (tag == "Note") {
    2124          986 :             Note* note = Factory::createNote(chord);
    2125              :             // the note needs to know the properties of the track it belongs to
    2126          986 :             note->setTrack(chord->track());
    2127          986 :             readNote206(note, e, ctx);
    2128          986 :             chord->add(note);
    2129         1630 :         } else if (tag == "Stem") {
    2130            0 :             Stem* stem = Factory::createStem(chord);
    2131            0 :             while (e.readNextStartElement()) {
    2132            0 :                 const AsciiStringView t(e.name());
    2133            0 :                 if (t == "subtype") {              // obsolete
    2134            0 :                     e.skipCurrentElement();
    2135            0 :                 } else if (!read400::TRead::readProperties(stem, e, ctx)) {
    2136            0 :                     e.unknown();
    2137              :                 }
    2138              :             }
    2139            0 :             chord->add(stem);
    2140         1630 :         } else if (tag == "Lyrics") {
    2141            1 :             Lyrics* lyrics = Factory::createLyrics(chord);
    2142            1 :             lyrics->setTrack(ctx.track());
    2143            1 :             readLyrics(lyrics, e, ctx);
    2144            1 :             chord->add(lyrics);
    2145         1629 :         } else if (Read206::readChordProperties206(e, ctx, chord)) {
    2146              :         } else {
    2147          362 :             e.unknown();
    2148              :         }
    2149              :     }
    2150          906 :     convertDoubleArticulations(chord);
    2151          906 :     fixTies(chord);
    2152          906 : }
    2153              : 
    2154              : //---------------------------------------------------------
    2155              : //   readRest
    2156              : //---------------------------------------------------------
    2157              : 
    2158          239 : static void readRest(Rest* rest, XmlReader& e, ReadContext& ctx)
    2159              : {
    2160          985 :     while (e.readNextStartElement()) {
    2161          507 :         if (!Read206::readChordRestProperties206(e, ctx, rest)) {
    2162           78 :             e.unknown();
    2163              :         }
    2164              :     }
    2165          239 : }
    2166              : 
    2167              : //---------------------------------------------------------
    2168              : //   readTextLineProperties
    2169              : //---------------------------------------------------------
    2170              : 
    2171           18 : static bool readTextLineProperties(XmlReader& e, ReadContext& ctx, TextLineBase* tl)
    2172              : {
    2173           18 :     const AsciiStringView tag(e.name());
    2174              : 
    2175           18 :     if (tag == "beginText") {
    2176            5 :         Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
    2177            5 :         readText206(e, ctx, text, tl);
    2178            5 :         tl->setBeginText(text->xmlText());
    2179            5 :         tl->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
    2180            5 :         delete text;
    2181           13 :     } else if (tag == "continueText") {
    2182            3 :         Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
    2183            3 :         readText206(e, ctx, text, tl);
    2184            3 :         tl->setContinueText(text->xmlText());
    2185            3 :         tl->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
    2186            3 :         delete text;
    2187           10 :     } else if (tag == "endText") {
    2188            0 :         Text* text = Factory::createText(ctx.dummy(), TextStyleType::DEFAULT, false);
    2189            0 :         readText206(e, ctx, text, tl);
    2190            0 :         tl->setEndText(text->xmlText());
    2191            0 :         tl->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
    2192            0 :         delete text;
    2193           10 :     } else if (tag == "beginHook") {
    2194            0 :         tl->setBeginHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
    2195           10 :     } else if (tag == "endHook") {
    2196            3 :         tl->setEndHookType(e.readBool() ? HookType::HOOK_90 : HookType::NONE);
    2197            7 :     } else if (tag == "beginHookType") {
    2198            0 :         tl->setBeginHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
    2199            7 :     } else if (tag == "endHookType") {
    2200            0 :         tl->setEndHookType(e.readInt() == 0 ? HookType::HOOK_90 : HookType::HOOK_45);
    2201            7 :     } else if (!read400::TRead::readProperties(tl, e, ctx)) {
    2202            2 :         return false;
    2203              :     }
    2204              : 
    2205           16 :     return true;
    2206              : }
    2207              : 
    2208              : //---------------------------------------------------------
    2209              : //   readVolta206
    2210              : //---------------------------------------------------------
    2211              : 
    2212            1 : static void readVolta206(XmlReader& e, ReadContext& ctx, Volta* volta)
    2213              : {
    2214            4 :     while (e.readNextStartElement()) {
    2215            3 :         const AsciiStringView tag(e.name());
    2216            3 :         if (tag == "endings") {
    2217            1 :             String s = e.readText();
    2218            1 :             StringList sl = s.split(u',', muse::SkipEmptyParts);
    2219            1 :             volta->endings().clear();
    2220            2 :             for (const String& l : sl) {
    2221            1 :                 int i = l.simplified().toInt();
    2222            1 :                 volta->endings().push_back(i);
    2223              :             }
    2224            3 :         } else if (tag == "lineWidth") {
    2225            0 :             volta->setLineWidth(Spatium(e.readDouble()));
    2226            2 :         } else if (!readTextLineProperties(e, ctx, volta)) {
    2227            0 :             e.unknown();
    2228              :         }
    2229              :     }
    2230            1 :     CompatUtils::resetHookHeightSign(volta);
    2231            1 :     adjustPlacement(volta);
    2232            1 :     CompatUtils::migrateOffsetPre302(volta, ctx.mscVersion());
    2233            1 : }
    2234              : 
    2235              : //---------------------------------------------------------
    2236              : //   readPedal
    2237              : //---------------------------------------------------------
    2238              : 
    2239            1 : static void readPedal(XmlReader& e, ReadContext& ctx, Pedal* pedal)
    2240              : {
    2241            1 :     bool beginTextTag = false;
    2242            1 :     bool continueTextTag = false;
    2243            1 :     bool endTextTag = false;
    2244              : 
    2245            6 :     while (e.readNextStartElement()) {
    2246            5 :         const AsciiStringView tag(e.name());
    2247            5 :         if (readTextLineProperties(e, ctx, pedal)) {
    2248            5 :             beginTextTag = beginTextTag || tag == "beginText";
    2249            5 :             continueTextTag = continueTextTag || tag == "continueText";
    2250            5 :             endTextTag = endTextTag || tag == "endText";
    2251              :         } else {
    2252            0 :             e.unknown();
    2253              :         }
    2254              :     }
    2255              : 
    2256              :     // Set to the 206 defaults if no value was specified;
    2257              :     // or follow the new style setting if the specified value matches it
    2258            1 :     if (!beginTextTag) {
    2259            0 :         pedal->setBeginText(String());
    2260            0 :         pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
    2261            1 :     } else if (pedal->beginText() == pedal->propertyDefault(Pid::BEGIN_TEXT).value<String>()) {
    2262            1 :         pedal->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::STYLED);
    2263              :     }
    2264            1 :     if (!continueTextTag) {
    2265            0 :         pedal->setContinueText(String());
    2266            0 :         pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
    2267            1 :     } else if (pedal->continueText() == pedal->propertyDefault(Pid::CONTINUE_TEXT).value<String>()) {
    2268            1 :         pedal->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::STYLED);
    2269              :     }
    2270            1 :     if (!endTextTag) {
    2271            1 :         pedal->setEndText(String());
    2272            1 :         pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
    2273            0 :     } else if (pedal->endText() == pedal->propertyDefault(Pid::END_TEXT).value<String>()) {
    2274            0 :         pedal->setPropertyFlags(Pid::END_TEXT, PropertyFlags::STYLED);
    2275              :     }
    2276              : 
    2277            1 :     CompatUtils::resetHookHeightSign(pedal);
    2278            1 :     adjustPlacement(pedal);
    2279            1 :     CompatUtils::migrateOffsetPre302(pedal, ctx.mscVersion());
    2280            1 : }
    2281              : 
    2282              : //---------------------------------------------------------
    2283              : //   readOttava
    2284              : //---------------------------------------------------------
    2285              : 
    2286            1 : static void readOttava(XmlReader& e, ReadContext& ctx, Ottava* ottava)
    2287              : {
    2288            2 :     while (e.readNextStartElement()) {
    2289            1 :         const AsciiStringView tag(e.name());
    2290            1 :         if (tag == "subtype") {
    2291            1 :             String s = e.readText();
    2292              :             bool ok;
    2293            1 :             int idx = s.toInt(&ok);
    2294            1 :             if (!ok) {
    2295            1 :                 idx = 0;            // OttavaType::OTTAVA_8VA;
    2296            1 :                 int i = 0;
    2297            2 :                 for (auto p :  { u"8va", u"8vb", u"15ma", u"15mb", u"22ma", u"22mb" }) {
    2298            2 :                     if (p == s) {
    2299            1 :                         idx = i;
    2300            1 :                         break;
    2301              :                     }
    2302            1 :                     ++i;
    2303              :                 }
    2304              :             }
    2305            1 :             ottava->setOttavaType(OttavaType(idx));
    2306            1 :         } else if (tag == "numbersOnly") {
    2307            0 :             ottava->setNumbersOnly(e.readBool());
    2308            0 :         } else if (!readTextLineProperties(e, ctx, ottava)) {
    2309            0 :             e.unknown();
    2310              :         }
    2311              :     }
    2312            1 :     ottava->styleChanged();
    2313            1 :     CompatUtils::resetHookHeightSign(ottava);
    2314            1 :     adjustPlacement(ottava);
    2315            1 :     CompatUtils::migrateOffsetPre302(ottava, ctx.mscVersion());
    2316            1 : }
    2317              : 
    2318            3 : void Read206::readHairpin206(XmlReader& e, ReadContext& ctx, Hairpin* h)
    2319              : {
    2320            3 :     bool useText = false;
    2321           13 :     while (e.readNextStartElement()) {
    2322           10 :         const AsciiStringView tag(e.name());
    2323           10 :         if (tag == "subtype") {
    2324            2 :             h->setHairpinType(HairpinType(e.readInt()));
    2325            8 :         } else if (tag == "lineWidth") {
    2326            0 :             h->setLineWidth(Spatium(e.readDouble()));
    2327              :             // lineWidthStyle = PropertyFlags::UNSTYLED;
    2328            8 :         } else if (tag == "hairpinHeight") {
    2329            0 :             h->setHairpinHeight(Spatium(e.readDouble()));
    2330              :             // hairpinHeightStyle = PropertyFlags::UNSTYLED;
    2331            8 :         } else if (tag == "hairpinContHeight") {
    2332            0 :             h->setHairpinContHeight(Spatium(e.readDouble()));
    2333              :             // hairpinContHeightStyle = PropertyFlags::UNSTYLED;
    2334            8 :         } else if (tag == "hairpinCircledTip") {
    2335            0 :             h->setHairpinCircledTip(e.readInt());
    2336            8 :         } else if (tag == "veloChange") {
    2337            0 :             h->setVeloChange(e.readInt());
    2338            8 :         } else if (tag == "dynType") {
    2339            0 :             h->setVoiceAssignment(readDynamicRange(e.readInt()));
    2340            8 :         } else if (tag == "useTextLine") {        // < 206
    2341            0 :             e.readInt();
    2342            0 :             if (h->hairpinType() == HairpinType::CRESC_HAIRPIN) {
    2343            0 :                 h->setHairpinType(HairpinType::CRESC_LINE);
    2344            0 :             } else if (h->hairpinType() == HairpinType::DIM_HAIRPIN) {
    2345            0 :                 h->setHairpinType(HairpinType::DIM_LINE);
    2346              :             }
    2347            0 :             useText = true;
    2348            8 :         } else if (!readTextLineProperties(e, ctx, h)) {
    2349            2 :             e.unknown();
    2350              :         }
    2351              :     }
    2352            3 :     if (!useText) {
    2353            3 :         h->setBeginText(u"");
    2354            3 :         h->setContinueText(u"");
    2355            3 :         h->setEndText(u"");
    2356              :     }
    2357            3 :     CompatUtils::resetHookHeightSign(h);
    2358            3 :     adjustPlacement(h);
    2359            3 :     CompatUtils::migrateOffsetPre302(h, ctx.mscVersion());
    2360            3 : }
    2361              : 
    2362            0 : void Read206::readTrill206(XmlReader& e, ReadContext& ctx, Trill* t)
    2363              : {
    2364            0 :     while (e.readNextStartElement()) {
    2365            0 :         const AsciiStringView tag(e.name());
    2366            0 :         if (tag == "subtype") {
    2367            0 :             t->setTrillType(TConv::fromXml(e.readAsciiText(), TrillType::TRILL_LINE));
    2368            0 :         } else if (tag == "Accidental") {
    2369            0 :             Accidental* _accidental = Factory::createAccidental(t);
    2370            0 :             readAccidental206(_accidental, e, ctx);
    2371            0 :             t->setAccidental(_accidental);
    2372            0 :             if (t->ornament()) {
    2373            0 :                 t->ornament()->setTrillOldCompatAccidental(_accidental);
    2374              :             }
    2375            0 :         } else if (tag == "ornamentStyle") {
    2376            0 :             read400::TRead::readProperty(t, e, ctx, Pid::ORNAMENT_STYLE);
    2377            0 :         } else if (tag == "play") {
    2378            0 :             t->setPlaySpanner(e.readBool());
    2379            0 :         } else if (!TRead::readProperties(static_cast<SLine*>(t), e, ctx)) {
    2380            0 :             e.unknown();
    2381              :         }
    2382              :     }
    2383            0 :     adjustPlacement(t);
    2384            0 :     CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
    2385            0 : }
    2386              : 
    2387            1 : void Read206::readTextLine206(XmlReader& e, ReadContext& ctx, TextLineBase* tlb)
    2388              : {
    2389            5 :     while (e.readNextStartElement()) {
    2390            3 :         if (!readTextLineProperties(e, ctx, tlb)) {
    2391            0 :             e.unknown();
    2392              :         }
    2393              :     }
    2394            1 :     CompatUtils::resetHookHeightSign(tlb);
    2395            1 :     adjustPlacement(tlb);
    2396            1 :     CompatUtils::migrateOffsetPre302(tlb, ctx.mscVersion());
    2397            1 : }
    2398              : 
    2399              : //---------------------------------------------------------
    2400              : //   setFermataPlacement
    2401              : //    set fermata placement from old ArticulationAnchor
    2402              : //    for backwards compatibility
    2403              : //---------------------------------------------------------
    2404              : 
    2405           18 : static void setFermataPlacement(EngravingItem* el, ArticulationAnchor anchor, DirectionV direction)
    2406              : {
    2407           18 :     if (direction == DirectionV::UP) {
    2408            6 :         el->setPlacement(PlacementV::ABOVE);
    2409           12 :     } else if (direction == DirectionV::DOWN) {
    2410           12 :         el->setPlacement(PlacementV::BELOW);
    2411              :     } else {
    2412            0 :         switch (anchor) {
    2413            0 :         case ArticulationAnchor::TOP:
    2414            0 :             el->setPlacement(PlacementV::ABOVE);
    2415            0 :             break;
    2416              : 
    2417            0 :         case ArticulationAnchor::BOTTOM:
    2418            0 :             el->setPlacement(PlacementV::BELOW);
    2419            0 :             break;
    2420              : 
    2421            0 :         case ArticulationAnchor::AUTO:
    2422            0 :             break;
    2423            0 :         default:
    2424            0 :             break;
    2425              :         }
    2426              :     }
    2427           18 : }
    2428              : 
    2429          252 : EngravingItem* Read206::readArticulation(EngravingItem* parent, XmlReader& e, ReadContext& ctx)
    2430              : {
    2431          252 :     EngravingItem* el = nullptr;
    2432          252 :     SymId sym = SymId::fermataAbove;            // default -- backward compatibility (no type = ufermata in 1.2)
    2433          252 :     ArticulationAnchor anchor  = ArticulationAnchor::TOP;
    2434          252 :     DirectionV direction = DirectionV::AUTO;
    2435          252 :     track_idx_t track = parent->track();
    2436          252 :     double timeStretch = 0.0;
    2437          252 :     bool useDefaultPlacement = true;
    2438              : 
    2439           98 :     auto readProperties = [](EngravingItem* el, XmlReader& e, ReadContext& ctx)
    2440              :     {
    2441           98 :         if (el->isFermata()) {
    2442            4 :             return read400::TRead::readProperties(toFermata(el), e, ctx);
    2443           94 :         } else if (el->isArticulationFamily()) {
    2444           94 :             return read400::TRead::readProperties(dynamic_cast<Articulation*>(el), e, ctx);
    2445              :         }
    2446            0 :         UNREACHABLE;
    2447              :         return false;
    2448              :     };
    2449              : 
    2450          665 :     while (e.readNextStartElement()) {
    2451          413 :         const AsciiStringView tag(e.name());
    2452          413 :         if (tag == "subtype") {
    2453          249 :             String s = e.readText();
    2454          249 :             if (s.at(0).isDigit()) {
    2455            0 :                 int oldType = s.toInt();
    2456            0 :                 sym = articulationNames[oldType].id;
    2457              :             } else {
    2458          249 :                 muse::ByteArray ba = s.toAscii();
    2459          249 :                 sym = articulationNames2SymId206(ba.constChar());
    2460          249 :                 if (sym == SymId::noSym) {
    2461              :                     struct {
    2462              :                         const char* name;
    2463              :                         bool up;
    2464              :                         SymId id;
    2465           14 :                     } al[] =
    2466              :                     {
    2467              :                         { "fadein",                    true,  SymId::guitarFadeIn },
    2468              :                         { "fadeout",                   true,  SymId::guitarFadeOut },
    2469              :                         { "volumeswell",               true,  SymId::guitarVolumeSwell },
    2470              :                         { "wigglesawtooth",            true,  SymId::wiggleSawtooth },
    2471              :                         { "wigglesawtoothwide",        true,  SymId::wiggleSawtoothWide },
    2472              :                         { "wigglevibratolargefaster",  true,  SymId::wiggleVibratoLargeFaster },
    2473              :                         { "wigglevibratolargeslowest", true,  SymId::wiggleVibratoLargeSlowest },
    2474              :                         { "umarcato",                  true,  SymId::articMarcatoAbove },
    2475              :                         { "dmarcato",                  false, SymId::articMarcatoBelow },
    2476              :                         { "ufermata",                  true,  SymId::fermataAbove },
    2477              :                         { "dfermata",                  false, SymId::fermataBelow },
    2478              :                         { "ushortfermata",             true,  SymId::fermataShortAbove },
    2479              :                         { "dshortfermata",             false, SymId::fermataShortBelow },
    2480              :                         { "ulongfermata",              true,  SymId::fermataLongAbove },
    2481              :                         { "dlongfermata",              false, SymId::fermataLongBelow },
    2482              :                         { "uverylongfermata",          true,  SymId::fermataVeryLongAbove },
    2483              :                         { "dverylongfermata",          false, SymId::fermataVeryLongBelow },
    2484              : 
    2485              :                         // watch out, bug in 1.2 uportato and dportato are reversed
    2486              :                         { "dportato",                  true,  SymId::articTenutoStaccatoAbove },
    2487              :                         { "uportato",                  false, SymId::articTenutoStaccatoBelow },
    2488              :                         { "ustaccatissimo",            true,  SymId::articStaccatissimoAbove },
    2489              :                         { "dstaccatissimo",            false, SymId::articStaccatissimoBelow }
    2490              :                     };
    2491              :                     int i;
    2492           14 :                     int n = sizeof(al) / sizeof(*al);
    2493          215 :                     for (i = 0; i < n; ++i) {
    2494          214 :                         if (s == al[i].name) {
    2495           13 :                             sym       = al[i].id;
    2496           13 :                             bool up   = al[i].up;
    2497           13 :                             direction = up ? DirectionV::UP : DirectionV::DOWN;
    2498           13 :                             if ((direction == DirectionV::DOWN) != (track & 1)) {
    2499            7 :                                 useDefaultPlacement = false;
    2500              :                             }
    2501           13 :                             break;
    2502              :                         }
    2503              :                     }
    2504           14 :                     if (i == n) {
    2505            1 :                         sym = SymNames::symIdByName(s);
    2506            1 :                         if (sym == SymId::noSym) {
    2507            1 :                             LOGD("Articulation: unknown type <%s>", muPrintable(s));
    2508              :                         }
    2509              :                     }
    2510              :                 }
    2511          249 :             }
    2512          249 :             switch (sym) {
    2513           38 :             case SymId::fermataAbove:
    2514              :             case SymId::fermataBelow:
    2515              :             case SymId::fermataShortAbove:
    2516              :             case SymId::fermataShortBelow:
    2517              :             case SymId::fermataLongAbove:
    2518              :             case SymId::fermataLongBelow:
    2519              :             case SymId::fermataVeryLongAbove:
    2520              :             case SymId::fermataVeryLongBelow: {
    2521           38 :                 Fermata* fe = Factory::createFermata(ctx.dummy());
    2522           38 :                 fe->setSymIdAndTimeStretch(sym);
    2523           38 :                 el = fe;
    2524           38 :             } break;
    2525          211 :             default:
    2526          211 :                 Articulation* ar = Factory::createArticulation(ctx.dummy());
    2527          211 :                 ar->setSymId(sym);
    2528          211 :                 ar->setDirection(direction);
    2529          211 :                 el = ar;
    2530          211 :                 break;
    2531              :             }
    2532          413 :         } else if (tag == "anchor") {
    2533           22 :             useDefaultPlacement = false;
    2534           22 :             if (!el || el->isFermata()) {
    2535            0 :                 anchor = CompatUtils::translateToNewArticulationAnchor(e.readInt());
    2536              :             } else {
    2537           22 :                 readProperties(el, e, ctx);
    2538              :             }
    2539          142 :         } else if (tag == "direction") {
    2540           64 :             useDefaultPlacement = false;
    2541           64 :             if (!el || el->isFermata()) {
    2542           64 :                 direction = TConv::fromXml(e.readAsciiText(), DirectionV::AUTO);
    2543              :             } else {
    2544            0 :                 readProperties(el, e, ctx);
    2545              :             }
    2546           78 :         } else if (tag == "timeStretch") {
    2547            2 :             timeStretch = e.readDouble();
    2548              :         } else {
    2549           76 :             if (!el) {
    2550            0 :                 LOGD("not handled <%s>", tag.ascii());
    2551              :             }
    2552           76 :             if (!el || !readProperties(el, e, ctx)) {
    2553           56 :                 e.unknown();
    2554              :             }
    2555              :         }
    2556              :     }
    2557              :     // Special case for "no type" = ufermata, with missing subtype tag
    2558          252 :     if (!el) {
    2559            3 :         Fermata* f = Factory::createFermata(ctx.dummy());
    2560            3 :         f->setSymIdAndTimeStretch(sym);
    2561            3 :         el = f;
    2562              :     }
    2563          252 :     if (el->isFermata()) {
    2564           41 :         if (!RealIsNull(timeStretch)) {
    2565            2 :             el->setProperty(Pid::TIME_STRETCH, timeStretch);
    2566              :         }
    2567           41 :         if (useDefaultPlacement) {
    2568           23 :             el->setPlacement(track & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
    2569              :         } else {
    2570           18 :             setFermataPlacement(el, anchor, direction);
    2571              :         }
    2572              :     }
    2573          252 :     el->setTrack(track);
    2574          252 :     return el;
    2575              : }
    2576              : 
    2577              : //---------------------------------------------------------
    2578              : //   readSlurTieProperties
    2579              : //---------------------------------------------------------
    2580              : 
    2581            0 : static bool readSlurTieProperties(XmlReader& e, ReadContext& ctx, SlurTie* st)
    2582              : {
    2583            0 :     const AsciiStringView tag(e.name());
    2584              : 
    2585            0 :     if (read400::TRead::readProperty(st, tag, e, ctx, Pid::SLUR_DIRECTION)) {
    2586            0 :     } else if (tag == "lineType") {
    2587            0 :         st->setStyleType(static_cast<SlurStyleType>(e.readInt()));
    2588            0 :     } else if (tag == "SlurSegment") {
    2589            0 :         SlurTieSegment* s = st->newSlurTieSegment();
    2590            0 :         st->add(s);
    2591            0 :         read400::TRead::read(s, e, ctx);
    2592            0 :     } else if (!TRead::readItemProperties(st, e, ctx)) {
    2593            0 :         return false;
    2594              :     }
    2595            0 :     return true;
    2596              : }
    2597              : 
    2598           16 : void Read206::readSlur206(XmlReader& e, ReadContext& ctx, Slur* s)
    2599              : {
    2600           16 :     s->setTrack(ctx.track());        // set staff
    2601           16 :     ctx.addSpanner(e.intAttribute("id"), s);
    2602           16 :     while (e.readNextStartElement()) {
    2603            0 :         const AsciiStringView tag(e.name());
    2604            0 :         if (tag == "track2") {
    2605            0 :             s->setTrack2(e.readInt());
    2606            0 :         } else if (tag == "startTrack") {       // obsolete
    2607            0 :             s->setTrack(e.readInt());
    2608            0 :         } else if (tag == "endTrack") {         // obsolete
    2609            0 :             e.readInt();
    2610            0 :         } else if (!readSlurTieProperties(e, ctx, s)) {
    2611            0 :             e.unknown();
    2612              :         }
    2613              :     }
    2614           16 :     if (s->track2() == muse::nidx) {
    2615           16 :         s->setTrack2(s->track());
    2616              :     }
    2617           16 : }
    2618              : 
    2619            0 : void Read206::readTie206(XmlReader& e, ReadContext& ctx, Tie* t)
    2620              : {
    2621            0 :     ctx.addSpanner(e.intAttribute("id"), t);
    2622            0 :     while (e.readNextStartElement()) {
    2623            0 :         if (readSlurTieProperties(e, ctx, t)) {
    2624              :         } else {
    2625            0 :             e.unknown();
    2626              :         }
    2627              :     }
    2628            0 :     if (ctx.mscVersion() <= 114 && t->spannerSegments().size() == 1) {
    2629              :         // ignore manual adjustments to single-segment ties in older scores
    2630            0 :         TieSegment* ss = t->frontSegment();
    2631            0 :         PointF zeroP;
    2632            0 :         ss->ups(Grip::START).off     = zeroP;
    2633            0 :         ss->ups(Grip::BEZIER1).off   = zeroP;
    2634            0 :         ss->ups(Grip::BEZIER2).off   = zeroP;
    2635            0 :         ss->ups(Grip::END).off       = zeroP;
    2636            0 :         ss->setOffset(zeroP);
    2637            0 :         ss->setUserOff2(zeroP);
    2638              :     }
    2639            0 : }
    2640              : 
    2641              : //---------------------------------------------------------
    2642              : //   readMeasure
    2643              : //---------------------------------------------------------
    2644              : 
    2645          441 : static void readMeasure206(Measure* m, int staffIdx, XmlReader& e, ReadContext& ctx)
    2646              : {
    2647          441 :     Segment* segment = 0;
    2648              : 
    2649          441 :     std::vector<Chord*> graceNotes;
    2650          441 :     ctx.tuplets().clear();
    2651          441 :     ctx.setTrack(staffIdx * VOICES);
    2652              : 
    2653          441 :     m->createStaves(staffIdx);
    2654              : 
    2655              :     // tick is obsolete
    2656          441 :     if (e.hasAttribute("tick")) {
    2657            0 :         ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.intAttribute("tick"))));
    2658              :     }
    2659              : 
    2660              :     bool irregular;
    2661          441 :     if (e.hasAttribute("len")) {
    2662           10 :         StringList sl = e.attribute("len").split(u'/');
    2663           10 :         if (sl.size() == 2) {
    2664           10 :             m->setTicks(Fraction(sl.at(0).toInt(), sl.at(1).toInt()));
    2665              :         } else {
    2666            0 :             LOGD("illegal measure size <%s>", muPrintable(e.attribute("len")));
    2667              :         }
    2668           10 :         irregular = true;
    2669           10 :         ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
    2670           10 :         ctx.compatTimeSigMap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
    2671           10 :     } else {
    2672          431 :         irregular = false;
    2673              :     }
    2674              : 
    2675          441 :     Staff* staff = ctx.staff(staffIdx);
    2676          441 :     Fraction timeStretch(staff->timeStretch(m->tick()));
    2677              : 
    2678              :     // keep track of tick of previous element
    2679              :     // this allows markings that need to apply to previous element to do so
    2680              :     // even though we may have already advanced to next tick position
    2681          441 :     Fraction lastTick = ctx.tick();
    2682              : 
    2683         1906 :     while (e.readNextStartElement()) {
    2684         1465 :         const AsciiStringView tag(e.name());
    2685              : 
    2686         1465 :         if (tag == "move") {
    2687            0 :             ctx.setTick(e.readFraction() + m->tick());
    2688         1465 :         } else if (tag == "tick") {
    2689            8 :             ctx.setTick(Fraction::fromTicks(ctx.fileDivision(e.readInt())));
    2690            8 :             lastTick = ctx.tick();
    2691         1457 :         } else if (tag == "BarLine") {
    2692           64 :             Fermata* fermataAbove = nullptr;
    2693           64 :             Fermata* fermataBelow = nullptr;
    2694           64 :             BarLine* bl = Factory::createBarLine(ctx.dummy());
    2695           64 :             bl->setTrack(ctx.track());
    2696          202 :             while (e.readNextStartElement()) {
    2697          138 :                 const AsciiStringView t(e.name());
    2698          138 :                 if (t == "subtype") {
    2699           64 :                     bl->setBarLineType(TConv::fromXml(e.readAsciiText(), BarLineType::NORMAL));
    2700           74 :                 } else if (t == "customSubtype") {                          // obsolete
    2701            0 :                     e.readInt();
    2702           74 :                 } else if (t == "span") {
    2703           64 :                     const int span = e.readInt();
    2704           64 :                     if (span > 0) {
    2705           64 :                         ctx.setBarLineSpan(bl, static_cast<size_t>(span - 1));
    2706              :                     } else {
    2707            0 :                         if (span < 0) {
    2708            0 :                             LOGW() << "bar line span is negative: " << span;
    2709              :                         }
    2710              : 
    2711            0 :                         ctx.setBarLineSpan(bl, 0);
    2712              :                     }
    2713           10 :                 } else if (t == "spanFromOffset") {
    2714            0 :                     bl->setSpanFrom(e.readInt());
    2715           10 :                 } else if (t == "spanToOffset") {
    2716            0 :                     bl->setSpanTo(e.readInt());
    2717           10 :                 } else if (t == "Articulation") {
    2718            0 :                     EngravingItem* el = Read206::readArticulation(bl, e, ctx);
    2719            0 :                     if (el->isFermata()) {
    2720            0 :                         if (el->placement() == PlacementV::ABOVE) {
    2721            0 :                             fermataAbove = toFermata(el);
    2722              :                         } else {
    2723            0 :                             fermataBelow = toFermata(el);
    2724            0 :                             const std::optional<size_t> barLineSpan = ctx.getBarLineSpan(bl);
    2725            0 :                             if (barLineSpan) {
    2726            0 :                                 fermataBelow->setTrack(staff2track(bl->staffIdx() + *barLineSpan));
    2727              :                             } else {
    2728            0 :                                 fermataBelow->setTrack(staff2track(bl->staffIdx() + ctx.getStaffBarLineSpan(staffIdx)));
    2729              :                             }
    2730              :                         }
    2731              :                     } else {
    2732            0 :                         bl->add(el);
    2733              :                     }
    2734           10 :                 } else if (!TRead::readItemProperties(bl, e, ctx)) {
    2735           10 :                     e.unknown();
    2736              :                 }
    2737              :             }
    2738              :             //
    2739              :             //  StartRepeatBarLine: always at the beginning tick of a measure, always BarLineType::START_REPEAT
    2740              :             //  BarLine:            in the middle of a measure, has no semantic
    2741              :             //  EndBarLine:         at the end tick of a measure
    2742              :             //  BeginBarLine:       first segment of a measure
    2743              : 
    2744              :             SegmentType st;
    2745           64 :             if ((ctx.tick() != m->tick()) && (ctx.tick() != m->endTick())) {
    2746            0 :                 st = SegmentType::BarLine;
    2747           64 :             } else if (bl->barLineType() == BarLineType::START_REPEAT && ctx.tick() == m->tick()) {
    2748            0 :                 st = SegmentType::StartRepeatBarLine;
    2749           64 :             } else if (ctx.tick() == m->tick() && segment == 0) {
    2750            0 :                 st = SegmentType::BeginBarLine;
    2751              :             } else {
    2752           64 :                 st = SegmentType::EndBarLine;
    2753              :             }
    2754           64 :             segment = m->getSegment(st, ctx.tick());
    2755           64 :             segment->add(bl);
    2756           64 :             if (fermataAbove) {
    2757            0 :                 segment->add(fermataAbove);
    2758              :             }
    2759           64 :             if (fermataBelow) {
    2760            0 :                 segment->add(fermataBelow);
    2761              :             }
    2762         1393 :         } else if (tag == "Chord") {
    2763          906 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    2764          906 :             Chord* chord = Factory::createChord(segment);
    2765          906 :             chord->setTrack(ctx.track());
    2766          906 :             readChord(chord, e, ctx);
    2767          906 :             if (chord->noteType() != NoteType::NORMAL) {
    2768            0 :                 graceNotes.push_back(chord);
    2769              :             } else {
    2770          906 :                 segment->add(chord);
    2771          906 :                 for (size_t i = 0; i < graceNotes.size(); ++i) {
    2772            0 :                     Chord* gc = graceNotes[i];
    2773            0 :                     gc->setGraceIndex(i);
    2774            0 :                     chord->add(gc);
    2775              :                 }
    2776          906 :                 graceNotes.clear();
    2777          906 :                 Fraction crticks = chord->actualTicks();
    2778          906 :                 lastTick         = ctx.tick();
    2779          906 :                 ctx.incTick(crticks);
    2780              :             }
    2781          487 :         } else if (tag == "Rest") {
    2782          241 :             if (m->isMMRest()) {
    2783            4 :                 segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    2784            4 :                 MMRest* mmr = Factory::createMMRest(segment);
    2785            4 :                 mmr->setTrack(ctx.track());
    2786            4 :                 read400::TRead::read(mmr, e, ctx);
    2787            4 :                 mmr->setTicks(m->ticks());
    2788            4 :                 segment->add(mmr);
    2789            4 :                 lastTick = ctx.tick();
    2790            4 :                 ctx.incTick(mmr->actualTicks());
    2791              :             } else {
    2792          237 :                 segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    2793          237 :                 Rest* rest = Factory::createRest(segment);
    2794          237 :                 rest->setDurationType(DurationType::V_MEASURE);
    2795          237 :                 rest->setTicks(m->timesig() / timeStretch);
    2796          237 :                 rest->setTrack(ctx.track());
    2797          237 :                 readRest(rest, e, ctx);
    2798          237 :                 segment->add(rest);
    2799              : 
    2800          237 :                 if (!rest->ticks().isValid()) {    // hack
    2801            0 :                     rest->setTicks(m->timesig() / timeStretch);
    2802              :                 }
    2803              : 
    2804          237 :                 lastTick = ctx.tick();
    2805          237 :                 ctx.incTick(rest->actualTicks());
    2806              :             }
    2807          246 :         } else if (tag == "Breath") {
    2808            3 :             Breath* breath = Factory::createBreath(ctx.dummy());
    2809            3 :             breath->setTrack(ctx.track());
    2810            3 :             breath->setPlacement(PlacementV::ABOVE);
    2811            3 :             Fraction tick = ctx.tick();
    2812            3 :             read400::TRead::read(breath, e, ctx);
    2813              :             // older scores placed the breath segment right after the chord to which it applies
    2814              :             // rather than before the next chordrest segment with an element for the staff
    2815              :             // result would be layout too far left if there are other segments due to notes in other staves
    2816              :             // we need to find tick of chord to which this applies, and add its duration
    2817            3 :             Fraction prevTick;
    2818            3 :             if (ctx.tick() < tick) {
    2819            0 :                 prevTick = ctx.tick();            // use our own tick if we explicitly reset to earlier position
    2820              :             } else {
    2821            3 :                 prevTick = lastTick;            // otherwise use tick of previous tick/chord/rest tag
    2822              :             }
    2823              :             // find segment
    2824            3 :             Segment* prev = m->findSegment(SegmentType::ChordRest, prevTick);
    2825            3 :             if (prev) {
    2826              :                 // find chordrest
    2827            3 :                 ChordRest* lastCR = toChordRest(prev->element(ctx.track()));
    2828            3 :                 if (lastCR) {
    2829            3 :                     tick = prevTick + lastCR->actualTicks();
    2830              :                 }
    2831              :             }
    2832            3 :             segment = m->getSegment(SegmentType::Breath, tick);
    2833            3 :             segment->add(breath);
    2834          243 :         } else if (tag == "endSpanner") {
    2835            6 :             int id = e.attribute("id").toInt();
    2836            6 :             Spanner* spanner = ctx.findSpanner(id);
    2837            6 :             if (spanner) {
    2838            6 :                 spanner->setTicks(ctx.tick() - spanner->tick());
    2839              :                 // if (spanner->track2() == -1)
    2840              :                 // the absence of a track tag [?] means the
    2841              :                 // track is the same as the beginning of the slur
    2842            6 :                 if (spanner->track2() == muse::nidx) {
    2843            0 :                     spanner->setTrack2(spanner->track() ? spanner->track() : ctx.track());
    2844              :                 }
    2845              :             } else {
    2846              :                 // remember "endSpanner" values
    2847            0 :                 SpannerValues sv;
    2848            0 :                 sv.spannerId = id;
    2849            0 :                 sv.track2    = ctx.track();
    2850            0 :                 sv.tick2     = ctx.tick();
    2851            0 :                 ctx.addSpannerValues(sv);
    2852              :             }
    2853            6 :             e.readNext();
    2854          237 :         } else if (tag == "Slur") {
    2855            0 :             Slur* sl = Factory::createSlur(ctx.dummy());
    2856            0 :             sl->setTick(ctx.tick());
    2857            0 :             Read206::readSlur206(e, ctx, sl);
    2858              :             //
    2859              :             // check if we already saw "endSpanner"
    2860              :             //
    2861            0 :             int id = ctx.spannerId(sl);
    2862            0 :             const SpannerValues* sv = ctx.spannerValues(id);
    2863            0 :             if (sv) {
    2864            0 :                 sl->setTick2(sv->tick2);
    2865            0 :                 sl->setTrack2(sv->track2);
    2866              :             }
    2867            0 :             ctx.addSpanner(sl);
    2868          237 :         } else if (tag == "HairPin"
    2869          235 :                    || tag == "Pedal"
    2870          234 :                    || tag == "Ottava"
    2871          233 :                    || tag == "Trill"
    2872          233 :                    || tag == "TextLine"
    2873          472 :                    || tag == "Volta") {
    2874            6 :             Spanner* sp = toSpanner(Factory::createItemByName(tag, ctx.dummy()));
    2875            6 :             sp->setTrack(ctx.track());
    2876            6 :             sp->setTrack2(sp->track());
    2877            6 :             sp->setTick(ctx.tick());
    2878            6 :             sp->eraseSpannerSegments();
    2879            6 :             ctx.addSpanner(e.intAttribute("id", -1), sp);
    2880              : 
    2881            6 :             if (tag == "Volta") {
    2882            1 :                 readVolta206(e, ctx, toVolta(sp));
    2883            5 :             } else if (tag == "Pedal") {
    2884            1 :                 readPedal(e, ctx, toPedal(sp));
    2885            4 :             } else if (tag == "Ottava") {
    2886            1 :                 readOttava(e, ctx, toOttava(sp));
    2887            3 :             } else if (tag == "HairPin") {
    2888            2 :                 Read206::readHairpin206(e, ctx, toHairpin(sp));
    2889            1 :             } else if (tag == "Trill") {
    2890            0 :                 Ornament* ornament = Factory::createOrnament(m->score()->dummy());
    2891            0 :                 toTrill(sp)->setOrnament(ornament);
    2892            0 :                 Read206::readTrill206(e, ctx, toTrill(sp));
    2893              :             } else {
    2894            1 :                 Read206::readTextLine206(e, ctx, toTextLineBase(sp));
    2895              :             }
    2896            6 :             ctx.addSpanner(sp);
    2897              :             //
    2898              :             // check if we already saw "endSpanner"
    2899              :             //
    2900            6 :             int id = ctx.spannerId(sp);
    2901            6 :             const SpannerValues* sv = ctx.spannerValues(id);
    2902            6 :             if (sv) {
    2903            0 :                 sp->setTicks(sv->tick2 - sp->tick());
    2904            0 :                 sp->setTrack2(sv->track2);
    2905              :             }
    2906          231 :         } else if (tag == "RepeatMeasure") {
    2907            2 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    2908            2 :             MeasureRepeat* rm = Factory::createMeasureRepeat(segment);
    2909            2 :             rm->setTrack(ctx.track());
    2910            2 :             readRest(rm, e, ctx);
    2911            2 :             rm->setNumMeasures(1);
    2912            2 :             m->setMeasureRepeatCount(1, staffIdx);
    2913            2 :             segment->add(rm);
    2914            2 :             lastTick = ctx.tick();
    2915            2 :             ctx.incTick(m->ticks());
    2916          229 :         } else if (tag == "Clef") {
    2917           29 :             Clef* clef = Factory::createClef(ctx.dummy());
    2918           29 :             clef->setTrack(ctx.track());
    2919           29 :             read400::TRead::read(clef, e, ctx);
    2920           29 :             clef->setGenerated(false);
    2921           29 :             if (ctx.tick().isZero()) {
    2922           11 :                 if (ctx.staff(staffIdx)->clef(Fraction(0, 1)) != clef->clefType()) {
    2923            5 :                     ctx.staff(staffIdx)->setDefaultClefType(clef->clefType());
    2924              :                 }
    2925           11 :                 delete clef;
    2926           11 :                 continue;
    2927              :             }
    2928              :             // there may be more than one clef segment for same tick position
    2929           18 :             if (!segment) {
    2930              :                 // this is the first segment of measure
    2931            1 :                 segment = m->getSegment(SegmentType::Clef, ctx.tick());
    2932              :             } else {
    2933           17 :                 bool firstSegment = false;
    2934              :                 // the first clef may be missing and is added later in layout
    2935           17 :                 for (Segment* s = m->segments().first(); s && s->tick() == ctx.tick(); s = s->next()) {
    2936            0 :                     if (s->segmentType() == SegmentType::Clef
    2937              :                         // hack: there may be other segment types which should
    2938              :                         // generate a clef at current position
    2939            0 :                         || s->segmentType() == SegmentType::StartRepeatBarLine
    2940              :                         ) {
    2941            0 :                         firstSegment = true;
    2942            0 :                         break;
    2943              :                     }
    2944              :                 }
    2945           17 :                 if (firstSegment) {
    2946            0 :                     Segment* ns = 0;
    2947            0 :                     if (segment->next()) {
    2948            0 :                         ns = segment->next();
    2949            0 :                         while (ns && ns->tick() < ctx.tick()) {
    2950            0 :                             ns = ns->next();
    2951              :                         }
    2952              :                     }
    2953            0 :                     segment = 0;
    2954            0 :                     for (Segment* s = ns; s && s->tick() == ctx.tick(); s = s->next()) {
    2955            0 :                         if (s->segmentType() == SegmentType::Clef) {
    2956            0 :                             segment = s;
    2957            0 :                             break;
    2958              :                         }
    2959              :                     }
    2960            0 :                     if (!segment) {
    2961            0 :                         segment = Factory::createSegment(m, SegmentType::Clef, ctx.tick() - m->tick());
    2962            0 :                         m->segments().insert(segment, ns);
    2963              :                     }
    2964              :                 } else {
    2965              :                     // this is the first clef: move to left
    2966           17 :                     segment = m->getSegment(SegmentType::Clef, ctx.tick());
    2967              :                 }
    2968              :             }
    2969           18 :             if (ctx.tick() != m->tick()) {
    2970           17 :                 clef->setSmall(true);                 // TODO: layout does this ?
    2971              :             }
    2972              : 
    2973              :             // Clef segments are sorted on layout now.  Previously, clef barline position could be out of sync with segment placement.
    2974           18 :             if (ctx.tick() != Fraction(0, 1) && ctx.tick() == m->tick() && !(m->prevMeasure() && m->prevMeasure()->repeatEnd())) {
    2975            1 :                 clef->setClefToBarlinePosition(ClefToBarlinePosition::AFTER);
    2976              :             }
    2977              : 
    2978           18 :             segment->add(clef);
    2979          200 :         } else if (tag == "TimeSig") {
    2980           62 :             TimeSig* ts = Factory::createTimeSig(ctx.dummy());
    2981           62 :             ts->setTrack(ctx.track());
    2982           62 :             read400::TRead::read(ts, e, ctx);
    2983              :             // if time sig not at beginning of measure => courtesy time sig
    2984           62 :             Fraction currTick = ctx.tick();
    2985           62 :             bool courtesySig = (currTick > m->tick());
    2986           62 :             if (courtesySig) {
    2987              :                 // if courtesy sig., just add it without map processing
    2988            0 :                 segment = m->getSegment(SegmentType::TimeSigAnnounce, currTick);
    2989            0 :                 segment->add(ts);
    2990              :             } else {
    2991              :                 // if 'real' time sig., do full process
    2992           62 :                 segment = m->getSegment(SegmentType::TimeSig, currTick);
    2993           62 :                 segment->add(ts);
    2994              : 
    2995           62 :                 timeStretch = ts->stretch().reduced();
    2996           62 :                 m->setTimesig(ts->sig() / timeStretch);
    2997              : 
    2998           62 :                 if (irregular) {
    2999            9 :                     ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
    3000            9 :                     ctx.compatTimeSigMap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
    3001              :                 } else {
    3002           53 :                     m->setTicks(m->timesig());
    3003           53 :                     ctx.compatTimeSigMap()->add(m->tick().ticks(), SigEvent(m->timesig()));
    3004              :                 }
    3005              :             }
    3006          138 :         } else if (tag == "KeySig") {
    3007           19 :             KeySig* ks = Factory::createKeySig(ctx.dummy());
    3008           19 :             ks->setTrack(ctx.track());
    3009           19 :             read400::TRead::read(ks, e, ctx);
    3010           19 :             Fraction curTick = ctx.tick();
    3011              :             // if key sig not at beginning of measure => courtesy key sig
    3012           19 :             bool courtesySig = (curTick == m->endTick());
    3013           19 :             segment = m->getSegment(courtesySig ? SegmentType::KeySigAnnounce : SegmentType::KeySig, curTick);
    3014           19 :             segment->add(ks);
    3015           19 :             staff->setKey(curTick, ks->keySigEvent());
    3016          119 :         } else if (tag == "Text" || tag == "StaffText") {
    3017              :             // MuseScore 3 has different types for system text and
    3018              :             // staff text while MuseScore 2 didn't.
    3019              :             // We need to decide first which one we should create.
    3020           11 :             TextReaderContext206 tctx(e);
    3021           11 :             String styleName = ReadStyleName206(tctx.tag());
    3022              :             StaffTextBase* t;
    3023           18 :             if (styleName == "System" || styleName == "Tempo"
    3024            6 :                 || styleName == "Marker" || styleName == "Jump"
    3025           18 :                 || styleName == "Volta") {    // TODO: is it possible to get it from style?
    3026            5 :                 t = Factory::createSystemText(ctx.dummy());
    3027              :             } else {
    3028            6 :                 t = Factory::createStaffText(ctx.dummy());
    3029              :             }
    3030           11 :             t->setTrack(ctx.track());
    3031           11 :             readTextPropertyStyle206(tctx.tag(), ctx, t, t);
    3032           47 :             while (tctx.reader().readNextStartElement()) {
    3033           25 :                 if (!readTextProperties206(tctx.reader(), ctx, t)) {
    3034            4 :                     tctx.reader().unknown();
    3035              :                 }
    3036              :             }
    3037           11 :             CompatUtils::migrateOffsetPre302(t, ctx.mscVersion());
    3038           11 :             if (t->empty()) {
    3039            4 :                 if (t->links()) {
    3040            0 :                     if (t->links()->size() == 1) {
    3041            0 :                         delete t;
    3042              :                     }
    3043              :                 }
    3044              :             } else {
    3045            7 :                 segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3046            7 :                 segment->add(t);
    3047              :             }
    3048           11 :         }
    3049              :         //----------------------------------------------------
    3050              :         // Annotation
    3051          108 :         else if (tag == "Dynamic") {
    3052            1 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3053            1 :             Dynamic* dyn = Factory::createDynamic(segment);
    3054            1 :             dyn->setTrack(ctx.track());
    3055            1 :             readDynamic(dyn, e, ctx);
    3056            1 :             segment->add(dyn);
    3057          107 :         } else if (tag == "RehearsalMark") {
    3058            1 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3059            1 :             RehearsalMark* el = Factory::createRehearsalMark(segment);
    3060            1 :             el->setTrack(ctx.track());
    3061            1 :             readText206(e, ctx, el, el);
    3062            1 :             segment->add(el);
    3063          106 :         } else if (tag == "Harmony"
    3064          103 :                    || tag == "FretDiagram"
    3065          103 :                    || tag == "TremoloBar"
    3066           99 :                    || tag == "Symbol"
    3067           99 :                    || tag == "InstrumentChange"
    3068           98 :                    || tag == "StaffState"
    3069          209 :                    || tag == "FiguredBass"
    3070              :                    ) {
    3071            9 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3072            9 :             EngravingItem* el = Factory::createItemByName(tag, segment);
    3073              :             // hack - needed because tick tags are unreliable in 1.3 scores
    3074              :             // for symbols attached to anything but a measure
    3075            9 :             el->setTrack(ctx.track());
    3076            9 :             read400::TRead::readItem(el, e, ctx);
    3077            9 :             if (el->staff() && (el->isHarmony() || el->isFretDiagram() || el->isInstrumentChange())) {
    3078            4 :                 adjustPlacement(el);
    3079              :             }
    3080            9 :             CompatUtils::migrateOffsetPre302(el, ctx.mscVersion());
    3081              : 
    3082            9 :             segment->add(el);
    3083           97 :         } else if (tag == "Tempo") {
    3084            1 :             segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3085            1 :             TempoText* tt = Factory::createTempoText(segment);
    3086              :             // hack - needed because tick tags are unreliable in 1.3 scores
    3087              :             // for symbols attached to anything but a measure
    3088            1 :             tt->setTrack(ctx.track());
    3089            1 :             readTempoText(tt, e, ctx);
    3090            1 :             segment->add(tt);
    3091           96 :         } else if (tag == "Marker" || tag == "Jump") {
    3092           24 :             EngravingItem* el = Factory::createItemByName(tag, ctx.dummy());
    3093           24 :             el->setTrack(ctx.track());
    3094           24 :             if (tag == "Marker") {
    3095           12 :                 Marker* ma = toMarker(el);
    3096           12 :                 readMarker(ma, e, ctx);
    3097           12 :                 EngravingItem* markerEl = toEngravingItem(ma);
    3098           12 :                 m->add(markerEl);
    3099              :             } else {
    3100           12 :                 read400::TRead::readItem(el, e, ctx);
    3101           12 :                 m->add(el);
    3102              :             }
    3103           72 :         } else if (tag == "Image") {
    3104            0 :             if (MScore::noImages) {
    3105            0 :                 e.skipCurrentElement();
    3106              :             } else {
    3107            0 :                 segment = m->getSegment(SegmentType::ChordRest, ctx.tick());
    3108            0 :                 EngravingItem* el = Factory::createItemByName(tag, segment);
    3109            0 :                 el->setTrack(ctx.track());
    3110            0 :                 read400::TRead::readItem(el, e, ctx);
    3111            0 :                 segment->add(el);
    3112              :             }
    3113           72 :         } else if (tag == "stretch") {
    3114              :             // Ignore measure stretch pre 4.0
    3115            4 :             e.skipCurrentElement();
    3116           68 :         } else if (tag == "noOffset") {
    3117            0 :             m->setMeasureNumberOffset(e.readInt());
    3118           68 :         } else if (tag == "measureNumberMode") {
    3119            0 :             m->setMeasureNumberMode(MeasureNumberMode(e.readInt()));
    3120           68 :         } else if (tag == "irregular") {
    3121            0 :             m->setExcludeFromNumbering(e.readBool());
    3122           68 :         } else if (tag == "breakMultiMeasureRest") {
    3123            0 :             m->setBreakMultiMeasureRest(e.readBool());
    3124           68 :         } else if (tag == "sysInitBarLineType") {
    3125            0 :             segment = m->getSegment(SegmentType::BeginBarLine, m->tick());
    3126            0 :             BarLine* barLine = Factory::createBarLine(segment);
    3127            0 :             barLine->setTrack(ctx.track());
    3128            0 :             barLine->setBarLineType(TConv::fromXml(e.readAsciiText(), BarLineType::NORMAL));
    3129            0 :             segment->add(barLine);
    3130           68 :         } else if (tag == "Tuplet") {
    3131           11 :             int tupletId = e.attribute("id").toInt();
    3132           11 :             Tuplet* tuplet = Factory::createTuplet(m);
    3133           11 :             tuplet->setTrack(ctx.track());
    3134           11 :             tuplet->setTick(ctx.tick());
    3135           11 :             readTuplet206(tuplet, e, ctx);
    3136           11 :             ctx.addTuplet(tupletId, tuplet);
    3137           57 :         } else if (tag == "startRepeat") {
    3138            3 :             m->setRepeatStart(true);
    3139            3 :             e.readNext();
    3140           54 :         } else if (tag == "endRepeat") {
    3141            3 :             m->setRepeatCount(e.readInt());
    3142            3 :             m->setRepeatEnd(true);
    3143           51 :         } else if (tag == "vspacer" || tag == "vspacerDown") {
    3144            0 :             if (!m->vspacerDown(staffIdx)) {
    3145            0 :                 Spacer* spacer = Factory::createSpacer(m);
    3146            0 :                 spacer->setSpacerType(SpacerType::DOWN);
    3147            0 :                 spacer->setTrack(staffIdx * VOICES);
    3148            0 :                 m->add(spacer);
    3149              :             }
    3150            0 :             m->vspacerDown(staffIdx)->setGap(Spatium(e.readDouble()));
    3151           51 :         } else if (tag == "vspacer" || tag == "vspacerUp") {
    3152            0 :             if (!m->vspacerUp(staffIdx)) {
    3153            0 :                 Spacer* spacer = Factory::createSpacer(m);
    3154            0 :                 spacer->setSpacerType(SpacerType::UP);
    3155            0 :                 spacer->setTrack(staffIdx * VOICES);
    3156            0 :                 m->add(spacer);
    3157              :             }
    3158            0 :             m->vspacerUp(staffIdx)->setGap(Spatium(e.readDouble()));
    3159           51 :         } else if (tag == "visible") {
    3160            0 :             m->setStaffVisible(staffIdx, e.readInt());
    3161           51 :         } else if (tag == "slashStyle") {
    3162            0 :             m->setStaffStemless(staffIdx, e.readInt());
    3163           51 :         } else if (tag == "Beam") {
    3164            2 :             int beamId = e.attribute("id").toInt();
    3165            2 :             Beam* beam = Factory::createBeam(ctx.score());
    3166            2 :             beam->setTrack(ctx.track());
    3167            2 :             read400::TRead::read(beam, e, ctx);
    3168            2 :             ctx.addBeam(beamId, beam);
    3169           49 :         } else if (tag == "Segment") {
    3170            0 :             if (segment) {
    3171            0 :                 read400::TRead::read(segment, e, ctx);
    3172              :             } else {
    3173            0 :                 e.unknown();
    3174              :             }
    3175           49 :         } else if (tag == "MeasureNumber") {
    3176            0 :             MeasureNumber* noText = new MeasureNumber(m);
    3177            0 :             readText206(e, ctx, noText, m);
    3178            0 :             noText->setTrack(ctx.track());
    3179            0 :             m->setMeasureNumber(noText->staffIdx(), noText);
    3180           49 :         } else if (tag == "SystemDivider") {
    3181            0 :             SystemDivider* sd = new SystemDivider(ctx.dummy());
    3182            0 :             read400::TRead::read(sd, e, ctx);
    3183            0 :             m->add(sd);
    3184           49 :         } else if (tag == "Ambitus") {
    3185            2 :             segment = m->getSegment(SegmentType::Ambitus, ctx.tick());
    3186            2 :             Ambitus* range = Factory::createAmbitus(segment);
    3187            2 :             readAmbitus(range, e, ctx);
    3188            2 :             range->setTrack(trackZeroVoice(ctx.track()));
    3189            2 :             segment->add(range);
    3190           47 :         } else if (tag == "multiMeasureRest") {
    3191            4 :             m->setMMRestCount(e.readInt());
    3192              :             // set tick to previous measure
    3193            4 :             m->setTick(ctx.lastMeasure()->tick());
    3194            4 :             ctx.setTick(ctx.lastMeasure()->tick());
    3195           43 :         } else if (read400::TRead::readProperties(static_cast<MeasureBase*>(m), e, ctx)) {
    3196              :         } else {
    3197            0 :             e.unknown();
    3198              :         }
    3199              :     }
    3200          441 :     ctx.checkTuplets();
    3201          441 :     m->connectTremolo();
    3202          441 : }
    3203              : 
    3204              : //---------------------------------------------------------
    3205              : //   readBox
    3206              : //---------------------------------------------------------
    3207              : 
    3208           17 : static void readBox(Box* b, XmlReader& e, ReadContext& ctx)
    3209              : {
    3210           17 :     b->setAutoSizeEnabled(false); // didn't exist in Mu2
    3211              : 
    3212           17 :     b->setBoxHeight(0_sp); // override default set in constructor
    3213           17 :     b->setBoxWidth(0_sp);
    3214              : 
    3215          100 :     while (e.readNextStartElement()) {
    3216           83 :         const AsciiStringView tag(e.name());
    3217           83 :         if (tag == "HBox") {
    3218            0 :             HBox* hb = Factory::createHBox(b->score());
    3219            0 :             read400::TRead::read(hb, e, ctx);
    3220            0 :             b->add(hb);
    3221           83 :         } else if (tag == "VBox") {
    3222            0 :             VBox* vb = Factory::createVBox(b->score());
    3223            0 :             read400::TRead::read(vb, e, ctx);
    3224            0 :             b->add(vb);
    3225           83 :         } else if (tag == "Text") {
    3226              :             Text* t;
    3227           51 :             if (b->isTBox()) {
    3228            0 :                 t = toTBox(b)->text();
    3229            0 :                 readText206(e, ctx, t, t);
    3230              :             } else {
    3231           51 :                 t = Factory::createText(b);
    3232           51 :                 readText206(e, ctx, t, t);
    3233           51 :                 if (t->empty()) {
    3234            0 :                     LOGD("read empty text");
    3235              :                 } else {
    3236           51 :                     b->add(t);
    3237              :                 }
    3238              :             }
    3239           32 :         } else if (!read400::TRead::readBoxProperties(b, e, ctx)) {
    3240           10 :             e.unknown();
    3241              :         }
    3242              :     }
    3243           17 : }
    3244              : 
    3245              : //---------------------------------------------------------
    3246              : //   readStaffContent
    3247              : //---------------------------------------------------------
    3248              : 
    3249           60 : static void readStaffContent206(Score* score, XmlReader& e, ReadContext& ctx)
    3250              : {
    3251           60 :     int staff = e.intAttribute("id", 1) - 1;
    3252           60 :     ctx.setTick(Fraction(0, 1));
    3253           60 :     ctx.setTrack(staff * VOICES);
    3254           60 :     Box* lastReadBox = nullptr;
    3255           60 :     bool readMeasureLast = false;
    3256              : 
    3257           60 :     if (staff == 0) {
    3258          411 :         while (e.readNextStartElement()) {
    3259          372 :             const AsciiStringView tag(e.name());
    3260              : 
    3261          372 :             if (tag == "Measure") {
    3262          351 :                 if (lastReadBox) {
    3263           15 :                     lastReadBox->setBottomGap(lastReadBox->bottomGap() + lastReadBox->propertyDefault(Pid::BOTTOM_GAP).value<Spatium>());
    3264           15 :                     lastReadBox = nullptr;
    3265              :                 }
    3266          351 :                 readMeasureLast = true;
    3267              : 
    3268          351 :                 Measure* measure = nullptr;
    3269          351 :                 measure = Factory::createMeasure(score);
    3270          351 :                 measure->setTick(ctx.tick());
    3271              :                 //
    3272              :                 // inherit timesig from previous measure
    3273              :                 //
    3274          351 :                 Measure* m = ctx.lastMeasure();         // measure->prevMeasure();
    3275          351 :                 Fraction f(m ? m->timesig() : Fraction(4, 4));
    3276          351 :                 measure->setTicks(f);
    3277          351 :                 measure->setTimesig(f);
    3278              : 
    3279          351 :                 readMeasure206(measure, staff, e, ctx);
    3280          351 :                 measure->checkMeasure(staff);
    3281          351 :                 if (!measure->isMMRest()) {
    3282          347 :                     score->measures()->append(measure);
    3283          347 :                     if (m && m->mmRest()) {
    3284            4 :                         m->mmRest()->setNext(measure);
    3285              :                     }
    3286          347 :                     ctx.setLastMeasure(measure);
    3287          347 :                     ctx.setTick(measure->endTick());
    3288              :                 } else {
    3289              :                     // this is a multi measure rest
    3290              :                     // always preceded by the first measure it replaces
    3291            4 :                     Measure* lm = ctx.lastMeasure();
    3292              : 
    3293            4 :                     if (lm) {
    3294            4 :                         lm->setMMRest(measure);
    3295            4 :                         measure->setTick(lm->tick());
    3296            4 :                         measure->setPrev(lm->prev());
    3297              :                     }
    3298              :                 }
    3299           21 :             } else if (tag == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
    3300           17 :                 Box* b = toBox(Factory::createItemByName(tag, score->dummy()));
    3301           17 :                 readBox(b, e, ctx);
    3302           17 :                 b->setTick(ctx.tick());
    3303           17 :                 score->measures()->append(b);
    3304              : 
    3305              :                 // If it's the first box, and comes before any measures, reset to
    3306              :                 // 301 default.
    3307           17 :                 if (!readMeasureLast && !lastReadBox) {
    3308           16 :                     b->setTopGap(b->propertyDefault(Pid::TOP_GAP).value<Spatium>());
    3309           16 :                     b->setPropertyFlags(Pid::TOP_GAP, PropertyFlags::STYLED);
    3310            1 :                 } else if (readMeasureLast) {
    3311            0 :                     b->setTopGap(b->topGap() + b->propertyDefault(Pid::TOP_GAP).value<Spatium>());
    3312              :                 }
    3313              : 
    3314           17 :                 lastReadBox = b;
    3315           17 :                 readMeasureLast = false;
    3316            4 :             } else if (tag == "tick") {
    3317            4 :                 ctx.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
    3318              :             } else {
    3319            0 :                 e.unknown();
    3320              :             }
    3321              :         }
    3322              :     } else {
    3323           21 :         Measure* measure = score->firstMeasure();
    3324          111 :         while (e.readNextStartElement()) {
    3325           90 :             const AsciiStringView tag(e.name());
    3326              : 
    3327           90 :             if (tag == "Measure") {
    3328           90 :                 if (measure == 0) {
    3329            0 :                     LOGD("Score::readStaff(): missing measure!");
    3330            0 :                     measure = Factory::createMeasure(score);
    3331            0 :                     measure->setTick(ctx.tick());
    3332            0 :                     score->measures()->append(measure);
    3333              :                 }
    3334           90 :                 ctx.setTick(measure->tick());
    3335           90 :                 readMeasure206(measure, staff, e, ctx);
    3336           90 :                 measure->checkMeasure(staff);
    3337           90 :                 if (measure->isMMRest()) {
    3338            0 :                     measure = ctx.lastMeasure()->nextMeasure();
    3339              :                 } else {
    3340           90 :                     ctx.setLastMeasure(measure);
    3341           90 :                     if (measure->mmRest()) {
    3342            0 :                         measure = measure->mmRest();
    3343              :                     } else {
    3344           90 :                         measure = measure->nextMeasure();
    3345              :                     }
    3346              :                 }
    3347            0 :             } else if (tag == "tick") {
    3348            0 :                 ctx.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
    3349              :             } else {
    3350            0 :                 e.unknown();
    3351              :             }
    3352              :         }
    3353              :     }
    3354           60 : }
    3355              : 
    3356              : //---------------------------------------------------------
    3357              : //   readStyle
    3358              : //---------------------------------------------------------
    3359              : 
    3360           39 : static void readStyle206(MStyle* style, XmlReader& e, ReadContext& ctx, ReadChordListHook& readChordListHook)
    3361              : {
    3362           39 :     excessTextStyles206.clear();
    3363          292 :     while (e.readNextStartElement()) {
    3364          253 :         AsciiStringView tag = e.name();
    3365          253 :         if (tag == "TextStyle") {
    3366          107 :             Read206::readTextStyle206(style, e, ctx, excessTextStyles206);
    3367          146 :         } else if (tag == "Spatium") {
    3368           39 :             style->set(Sid::spatium, e.readDouble() * DPMM);
    3369          107 :         } else if (tag == "page-layout") {
    3370           27 :             readPageFormat206(style, e);
    3371           80 :         } else if (tag == "displayInConcertPitch") {
    3372            0 :             style->set(Sid::concertPitch, bool(e.readInt()));
    3373           80 :         } else if (tag == "pedalY") {
    3374            0 :             double y = e.readDouble();
    3375            0 :             style->set(Sid::pedalPosBelow, PointF(0.0, y));
    3376           80 :         } else if (tag == "lyricsDistance") {
    3377            0 :             double y = e.readDouble();
    3378            0 :             style->set(Sid::lyricsPosBelow, PointF(0.0, y));
    3379           80 :         } else if (tag == "lyricsMinBottomDistance") {
    3380              :             // no longer meaningful since it is now measured from skyline rather than staff
    3381              :             //style->set(Sid::lyricsMinBottomDistance, PointF(0.0, y));
    3382            2 :             e.skipCurrentElement();
    3383           78 :         } else if (tag == "ottavaHook") {
    3384            1 :             double y = std::abs(e.readDouble());
    3385            1 :             style->set(Sid::ottavaHookAbove, y);
    3386            1 :             style->set(Sid::ottavaHookBelow, -y);
    3387           77 :         } else if (tag == "endBarDistance") {
    3388            0 :             double d = e.readDouble();
    3389            0 :             d += style->value(Sid::barWidth).toReal();
    3390            0 :             d += style->value(Sid::endBarWidth).toReal();
    3391            0 :             style->set(Sid::endBarDistance, d);
    3392           77 :         } else if (tag == "ChordList") {
    3393            0 :             readChordListHook.read(e);
    3394           77 :         } else if (tag == "harmonyY") {
    3395            0 :             double val = -e.readDouble();
    3396            0 :             if (val > 0.0) {
    3397            0 :                 style->set(Sid::harmonyPlacement, PlacementV::BELOW);
    3398            0 :                 style->set(Sid::chordSymbolAPosBelow,  PointF(.0, val));
    3399              :             } else {
    3400            0 :                 style->set(Sid::harmonyPlacement, PlacementV::ABOVE);
    3401            0 :                 style->set(Sid::chordSymbolAPosBelow,  PointF(.0, val));
    3402              :             }
    3403           77 :         } else if (tag == "useStandardNoteNames") {     // These settings were collapsed into one enum in 4.6
    3404            0 :             if (e.readBool()) {
    3405            0 :                 style->set(Sid::chordSymbolSpelling, NoteSpellingType::STANDARD);
    3406              :             }
    3407           77 :         } else if (tag == "useGermanNoteNames") {
    3408            0 :             if (e.readBool()) {
    3409            0 :                 style->set(Sid::chordSymbolSpelling, NoteSpellingType::GERMAN);
    3410              :             }
    3411           77 :         } else if (tag == "useFullGermanNoteNames") {
    3412            0 :             if (e.readBool()) {
    3413            0 :                 style->set(Sid::chordSymbolSpelling, NoteSpellingType::GERMAN_PURE);
    3414              :             }
    3415           77 :         } else if (tag == "useSolfeggioNoteNames") {
    3416            0 :             if (e.readBool()) {
    3417            0 :                 style->set(Sid::chordSymbolSpelling, NoteSpellingType::SOLFEGGIO);
    3418              :             }
    3419           77 :         } else if (tag == "useFrenchNoteNames") {
    3420            0 :             if (e.readBool()) {
    3421            0 :                 style->set(Sid::chordSymbolSpelling, NoteSpellingType::FRENCH);
    3422              :             }
    3423              :         } else {
    3424           77 :             if (!ReadStyleHook::readStyleProperties(style, e)) {
    3425            1 :                 e.skipCurrentElement();
    3426              :             }
    3427              :         }
    3428              :     }
    3429              : 
    3430           39 :     readChordListHook.validate();
    3431           39 : }
    3432              : 
    3433           39 : bool Read206::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx)
    3434              : {
    3435          844 :     while (e.readNextStartElement()) {
    3436          805 :         ctx.setTrack(muse::nidx);
    3437          805 :         const AsciiStringView tag(e.name());
    3438          805 :         if (tag == "Staff") {
    3439           60 :             readStaffContent206(score, e, ctx);
    3440          745 :         } else if (tag == "siglist") {
    3441            0 :             readTimeSigMap(ctx.compatTimeSigMap(), e, ctx);
    3442          745 :         } else if (tag == "Omr") {
    3443            0 :             e.skipCurrentElement();
    3444          745 :         } else if (tag == "Audio") {
    3445            0 :             e.skipCurrentElement();
    3446          745 :         } else if (tag == "showOmr") {
    3447            0 :             e.skipCurrentElement();
    3448          745 :         } else if (tag == "playMode") {
    3449            0 :             e.skipCurrentElement();
    3450          745 :         } else if (tag == "LayerTag") {
    3451            5 :             e.skipCurrentElement();
    3452          740 :         } else if (tag == "Layer") {
    3453            0 :             e.skipCurrentElement();
    3454          740 :         } else if (tag == "currentLayer") {
    3455            5 :             e.skipCurrentElement();
    3456          735 :         } else if (tag == "Synthesizer") {
    3457           25 :             score->synthesizerState().read(e);
    3458          710 :         } else if (tag == "page-offset") {
    3459            0 :             score->setPageNumberOffset(e.readInt());
    3460          710 :         } else if (tag == "Division") {
    3461           39 :             score->setFileDivision(e.readInt());
    3462          671 :         } else if (tag == "showInvisible") {
    3463           39 :             score->setShowInvisible(e.readInt());
    3464          632 :         } else if (tag == "showUnprintable") {
    3465           39 :             score->setShowUnprintable(e.readInt());
    3466          593 :         } else if (tag == "showFrames") {
    3467           39 :             score->setShowFrames(e.readInt());
    3468          554 :         } else if (tag == "showMargins") {
    3469           39 :             score->setShowPageborders(e.readInt());
    3470          515 :         } else if (tag == "Style") {
    3471           39 :             double sp = score->style().value(Sid::spatium).toReal();
    3472           39 :             ReadChordListHook clhook(score);
    3473           39 :             readStyle206(&score->style(), e, ctx, clhook);
    3474           39 :             if (score->style().styleSt(Sid::musicalTextFont) == "MuseJazz") {
    3475            1 :                 score->style().set(Sid::musicalTextFont, "MuseJazz Text");
    3476              :             }
    3477           39 :             if (ctx.overrideSpatium()) {
    3478            0 :                 ctx.setOriginalSpatium(score->style().spatium());
    3479            0 :                 score->style().set(Sid::spatium, sp);
    3480              :             }
    3481           39 :             score->setEngravingFont(score->engravingFonts()->fontByName(score->style().styleSt(Sid::musicalSymbolFont).toStdString()));
    3482          515 :         } else if (tag == "copyright" || tag == "rights") {
    3483            0 :             Text* text = Factory::createText(score->dummy(), TextStyleType::DEFAULT, false);
    3484            0 :             readText206(e, ctx, text, text);
    3485            0 :             score->setMetaTag(u"copyright", text->xmlText());
    3486            0 :             delete text;
    3487          476 :         } else if (tag == "movement-number") {
    3488            0 :             score->setMetaTag(u"movementNumber", e.readText());
    3489          476 :         } else if (tag == "movement-title") {
    3490            0 :             score->setMetaTag(u"movementTitle", e.readText());
    3491          476 :         } else if (tag == "work-number") {
    3492            0 :             score->setMetaTag(u"workNumber", e.readText());
    3493          476 :         } else if (tag == "work-title") {
    3494            0 :             score->setMetaTag(u"workTitle", e.readText());
    3495          476 :         } else if (tag == "source") {
    3496            0 :             score->setMetaTag(u"source", e.readText());
    3497          476 :         } else if (tag == "metaTag") {
    3498          381 :             String name = e.attribute("name");
    3499          381 :             score->setMetaTag(name, e.readText());
    3500          476 :         } else if (tag == "Part") {
    3501           55 :             Part* part = new Part(score);
    3502           55 :             Read206::readPart206(part, e, ctx);
    3503           55 :             score->appendPart(part);
    3504           40 :         } else if ((tag == "HairPin")     // TODO: do this elements exist here?
    3505           40 :                    || (tag == "Ottava")
    3506           40 :                    || (tag == "TextLine")
    3507           40 :                    || (tag == "Volta")
    3508           40 :                    || (tag == "Trill")
    3509           40 :                    || (tag == "Slur")
    3510           80 :                    || (tag == "Pedal")) {
    3511            0 :             Spanner* s = toSpanner(Factory::createItemByName(tag, score->dummy()));
    3512            0 :             if (tag == "HairPin") {
    3513            0 :                 readHairpin206(e, ctx, toHairpin(s));
    3514            0 :             } else if (tag == "Ottava") {
    3515            0 :                 readOttava(e, ctx, toOttava(s));
    3516            0 :             } else if (tag == "TextLine") {
    3517            0 :                 Read206::readTextLine206(e, ctx, toTextLine(s));
    3518            0 :             } else if (tag == "Volta") {
    3519            0 :                 readVolta206(e, ctx, toVolta(s));
    3520            0 :             } else if (tag == "Trill") {
    3521            0 :                 Read206::readTrill206(e, ctx, toTrill(s));
    3522            0 :             } else if (tag == "Slur") {
    3523            0 :                 readSlur206(e, ctx, toSlur(s));
    3524              :             } else {
    3525            0 :                 assert(tag == "Pedal");
    3526            0 :                 readPedal(e, ctx, toPedal(s));
    3527              :             }
    3528            0 :             score->addSpanner(s);
    3529           40 :         } else if (tag == "Score") {            // recursion
    3530            6 :             ctx.tracks().clear();
    3531            6 :             ctx.clearUserTextStyles();
    3532            6 :             MasterScore* m = score->masterScore();
    3533            6 :             Score* s = m->createScore();
    3534            6 :             ReadStyleHook::setupDefaultStyle(s);
    3535            6 :             Excerpt* ex = new Excerpt(m);
    3536              : 
    3537            6 :             ex->setExcerptScore(s);
    3538            6 :             ctx.setLastMeasure(nullptr);
    3539            6 :             ReadContext exCtx(s);
    3540              : 
    3541            6 :             s->setIsOpen(true);
    3542            6 :             readScoreTag(s, e, exCtx);
    3543              : 
    3544            6 :             ex->setTracksMapping(ctx.tracks());
    3545            6 :             m->addExcerpt(ex);
    3546           40 :         } else if (tag == "PageList") {
    3547           28 :             e.skipCurrentElement();
    3548            6 :         } else if (tag == "name") {
    3549            6 :             String n = e.readText();
    3550            6 :             if (!score->isMaster()) {                 //ignore the name if it's not a child score
    3551            6 :                 score->excerpt()->setName(n, /*saveAndNotify=*/ false);
    3552              :             }
    3553            6 :         } else if (tag == "layoutMode") {
    3554            0 :             if (ctx.forcePageMode()) {
    3555            0 :                 e.skipCurrentElement();
    3556            0 :                 continue;
    3557              :             }
    3558            0 :             String s = e.readText();
    3559            0 :             if (s == "line") {
    3560            0 :                 score->setLayoutMode(LayoutMode::LINE);
    3561            0 :             } else if (s == "system") {
    3562            0 :                 score->setLayoutMode(LayoutMode::SYSTEM);
    3563              :             } else {
    3564            0 :                 LOGD("layoutMode: %s", muPrintable(s));
    3565              :             }
    3566            0 :         } else {
    3567            0 :             e.unknown();
    3568              :         }
    3569              :     }
    3570           39 :     if (e.error() != muse::XmlStreamReader::NoError) {
    3571            0 :         LOGE() << muse::String(u"XML read error at byte offset %1: %2").arg(e.byteOffset())
    3572            0 :             .arg(muse::String::fromAscii(e.name().ascii()));
    3573            0 :         return false;
    3574              :     }
    3575              : 
    3576           39 :     score->setFileDivision(Constants::DIVISION);
    3577              : 
    3578           39 :     score->updateTicksAndTimeSigMap();
    3579           39 :     if (score->isMaster()) {
    3580              :         // While reading the score, some elements might use `score->repeatList()` (which is incorrect
    3581              :         // anyway, because the repeatList will be incomplete because the score is incomplete, but some
    3582              :         // elements still do it).
    3583              :         // `score->repeatList()` calls `_repeatList->update()`; the repeat list then thinks that it is
    3584              :         // up-to-date from that point. But we weren't finished reading the score, so the score will still
    3585              :         // change. We need to tell the repeat list about that, so that it will be updated next time
    3586              :         // someone uses it.
    3587           33 :         static_cast<MasterScore*>(score)->invalidateRepeatList();
    3588              :     }
    3589           39 :     score->connectTies();
    3590           39 :     score->undoRemoveStaleTieJumpPoints(false);
    3591              : 
    3592           94 :     for (Part* p : score->parts()) {
    3593           55 :         p->updateHarmonyChannels(false);
    3594              :     }
    3595              : 
    3596           39 :     if (score->isMaster()) {
    3597           33 :         MasterScore* ms = static_cast<MasterScore*>(score);
    3598           33 :         ms->rebuildMidiMapping();
    3599           33 :         ms->updateChannel();
    3600              :     }
    3601              : 
    3602           39 :     if (score->isMaster()) {
    3603           33 :         CompatUtils::assignInitialPartToExcerpts(score->masterScore()->excerpts());
    3604              :     }
    3605              : 
    3606           39 :     return true;
    3607              : }
    3608              : 
    3609           33 : Ret Read206::readScoreFile(Score* score, XmlReader& e, ReadInOutData* out)
    3610              : {
    3611           33 :     ReadContext ctx(score);
    3612           33 :     if (out) {
    3613           33 :         if (out->overriddenSpatium.has_value()) {
    3614            0 :             ctx.setSpatium(out->overriddenSpatium.value());
    3615            0 :             ctx.setOverrideSpatium(true);
    3616              :         }
    3617              : 
    3618           33 :         ctx.setPropertiesToSkip(out->propertiesToSkip);
    3619           33 :         ctx.setForcePageMode(out->forcePageMode);
    3620              :     }
    3621           33 :     DEFER {
    3622           33 :         if (out) {
    3623           33 :             out->settingsCompat = std::move(ctx.settingCompat());
    3624              :         }
    3625           33 :     };
    3626              : 
    3627          112 :     while (e.readNextStartElement()) {
    3628           79 :         const AsciiStringView tag(e.name());
    3629           79 :         if (tag == "programVersion") {
    3630           23 :             score->setMscoreVersion(e.readText());
    3631           56 :         } else if (tag == "programRevision") {
    3632           23 :             score->setMscoreRevision(e.readInt(nullptr, 16));
    3633           33 :         } else if (tag == "Score") {
    3634           33 :             if (!readScoreTag(score, e, ctx)) {
    3635            0 :                 if (e.error() == muse::XmlStreamReader::CustomError) {
    3636            0 :                     return make_ret(Err::FileCriticallyCorrupted, e.errorString());
    3637              :                 }
    3638            0 :                 return make_ret(Err::FileBadFormat, e.errorString());
    3639              :             }
    3640              : 
    3641           33 :             if (ctx.overrideSpatium() && out) {
    3642            0 :                 out->originalSpatium = ctx.originalSpatium();
    3643              :             }
    3644            0 :         } else if (tag == "Revision") {
    3645            0 :             e.skipCurrentElement();
    3646              :         }
    3647              :     }
    3648              : 
    3649           87 :     for (Staff* s : score->staves()) {
    3650           54 :         s->updateOttava();
    3651              :     }
    3652              : 
    3653              :     // convert int bar line span to bool
    3654              : 
    3655              :     // also sets Pid::BARLINE_SPAN to non-generated and affected bar lines
    3656           33 :     read114::Read114::setBarLineSpanToStaves(score, ctx);
    3657              : 
    3658              :     // set local overrides to bar lines
    3659           33 :     constexpr auto st = SegmentType::BarLineTypes;
    3660           33 :     const size_t numStaves = score->nstaves();
    3661          298 :     for (Segment* s = score->firstSegment(st); s; s = s->next1(st)) {
    3662              :         // optional local bar line span override of this segment
    3663          265 :         std::optional<size_t> barLineSpan = std::nullopt;
    3664          620 :         for (staff_idx_t staffIdx = 0; staffIdx < numStaves; ++staffIdx) {
    3665          355 :             const size_t maxSpan = numStaves - staffIdx - 1;
    3666          355 :             const track_idx_t trackIdx = staff2track(staffIdx);
    3667          355 :             EngravingItem* el = s->element(trackIdx);
    3668          355 :             BarLine* barLine = el && el->isBarLine() ? toBarLine(el) : nullptr;
    3669          355 :             if (barLine) {
    3670           55 :                 if (const std::optional<size_t> span = ctx.getBarLineSpan(barLine)) {
    3671           55 :                     if (*span > maxSpan) {
    3672            0 :                         LOGW() << "invalid bar line span " << *span << " (max " << maxSpan << ")";
    3673            0 :                         barLineSpan = maxSpan;
    3674              :                     } else {
    3675           55 :                         barLineSpan = std::max(barLineSpan.value_or(0), *span);
    3676              :                     }
    3677              :                 }
    3678              :             }
    3679              : 
    3680          355 :             if (!barLineSpan) {
    3681              :                 // we don't have a local override. Keep the value from staff
    3682          288 :                 continue;
    3683              :             }
    3684              : 
    3685           67 :             const bool shouldBarLineSpan = *barLineSpan != 0;
    3686           67 :             const bool staffBarLineSpan = score->staff(staffIdx)->barLineSpan();
    3687           67 :             if (shouldBarLineSpan != staffBarLineSpan) {
    3688              :                 // bar line should have local override
    3689           13 :                 if (!barLine) {
    3690              :                     // clone previous bar line. This is safe because we always have a previous barline when
    3691              :                     // barLineSpan != std::nullopt because it's either the one that was read or the one cloned
    3692              :                     // in the previous iteration
    3693            6 :                     el = s->element(staff2track(staffIdx - 1));
    3694            6 :                     if (el && el->isBarLine()) {
    3695            6 :                         barLine = toBarLine(el)->clone();
    3696            6 :                         barLine->setTrack(trackIdx);
    3697            6 :                         s->add(barLine);
    3698              :                     }
    3699              :                 }
    3700              : 
    3701           13 :                 if (barLine) {
    3702           13 :                     barLine->setSpanStaff(shouldBarLineSpan);
    3703              :                 }
    3704              :             }
    3705           67 :             if (*barLineSpan == 0) {
    3706              :                 // we're done applying the local override
    3707           55 :                 barLineSpan.reset();
    3708              :             } else {
    3709           12 :                 --(*barLineSpan);
    3710              :             }
    3711              :         }
    3712              :     }
    3713              : 
    3714           33 :     return make_ok();
    3715           33 : }
    3716              : 
    3717            0 : bool Read206::pasteStaff(XmlReader&, Segment*, staff_idx_t, Fraction)
    3718              : {
    3719            0 :     UNREACHABLE;
    3720              :     return false;
    3721              : }
    3722              : 
    3723            0 : void Read206::pasteSymbols(XmlReader&, ChordRest*)
    3724              : {
    3725            0 :     UNREACHABLE;
    3726              : }
    3727              : 
    3728            0 : void Read206::readTremoloCompat(compat::TremoloCompat*, XmlReader&)
    3729              : {
    3730            0 :     UNREACHABLE;
    3731              : }
    3732              : 
    3733            0 : void Read206::doReadItem(EngravingItem*, XmlReader&)
    3734              : {
    3735            0 :     UNREACHABLE;
    3736              : }
        

Generated by: LCOV version 2.0-1