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 : /**
24 : MusicXML font handling support.
25 : */
26 :
27 : #include "musicxmlfonthandler.h"
28 :
29 : #include "engraving/rw/xmlwriter.h"
30 :
31 : using namespace mu::engraving;
32 :
33 : namespace mu::iex::musicxml {
34 : //---------------------------------------------------------
35 : // MScoreTextToMXML
36 : //---------------------------------------------------------
37 :
38 400 : MScoreTextToMusicXml::MScoreTextToMusicXml(const String& tag, const String& attr, const CharFormat& defFmt, const String& mtf)
39 400 : : attribs(attr), tagname(tag), oldFormat(defFmt), musicalTextFont(mtf)
40 : {
41 : // set MusicXML defaults
42 400 : oldFormat.setBold(false);
43 400 : oldFormat.setItalic(false);
44 400 : oldFormat.setUnderline(false);
45 400 : }
46 :
47 : //---------------------------------------------------------
48 : // toPlainText
49 : // convert to plain text
50 : // naive implementation: simply remove all chars from '<' to '>'
51 : // typically used to remove formatting info from fields read
52 : // from MuseScore 1.3 file where they are stored as html, such as
53 : // part name and shortName
54 : //---------------------------------------------------------
55 :
56 3692 : String MScoreTextToMusicXml::toPlainText(const String& text)
57 : {
58 3692 : String res;
59 3692 : bool inElem = false;
60 41787 : for (size_t i = 0; i < text.size(); ++i) {
61 38095 : Char ch = text.at(i);
62 38095 : if (ch == u'<') {
63 1437 : inElem = true;
64 36658 : } else if (ch == u'>') {
65 1437 : inElem = false;
66 : } else {
67 35221 : if (!inElem) {
68 26772 : res += ch;
69 : }
70 : }
71 : }
72 : //LOGD("MScoreTextToMXML::toPlainText('%s') res '%s'", muPrintable(text), muPrintable(res));
73 3692 : return res;
74 0 : }
75 :
76 : //---------------------------------------------------------
77 : // toPlainTextPlusSymbols
78 : // convert to plain text plus <sym>[name]</sym> encoded symbols
79 : //---------------------------------------------------------
80 :
81 302 : String MScoreTextToMusicXml::toPlainTextPlusSymbols(const std::list<TextFragment>& list)
82 : {
83 302 : String res;
84 722 : for (const TextFragment& f : list) {
85 420 : res += f.text;
86 : }
87 302 : return res;
88 0 : }
89 :
90 : //---------------------------------------------------------
91 : // plainTextPlusSymbolsSize
92 : //---------------------------------------------------------
93 :
94 326 : static int plainTextPlusSymbolsFragmentSize(const TextFragment& f)
95 : {
96 326 : return f.columns();
97 : }
98 :
99 : //---------------------------------------------------------
100 : // plainTextPlusSymbolsSize
101 : //---------------------------------------------------------
102 :
103 214 : static int plainTextPlusSymbolsListSize(const std::list<TextFragment>& list)
104 : {
105 214 : int res = 0;
106 328 : for (const TextFragment& f : list) {
107 114 : res += plainTextPlusSymbolsFragmentSize(f);
108 : }
109 214 : return res;
110 : }
111 :
112 : //---------------------------------------------------------
113 : // split
114 : //---------------------------------------------------------
115 :
116 : /**
117 : Split \a in into \a left, \a mid and \a right. Mid starts at \a pos and is \a len characters long.
118 : Pos and len refer to the representation returned by toPlainTextPlusSymbols().
119 : TODO Make sure surrogate pairs are handled correctly
120 : Return true if OK, false on error.
121 : */
122 :
123 103 : bool MScoreTextToMusicXml::split(const std::list<TextFragment>& in, const int pos, const int len,
124 : std::list<TextFragment>& left, std::list<TextFragment>& mid, std::list<TextFragment>& right)
125 : {
126 : //LOGD("MScoreTextToMXML::split in size %d pos %d len %d", plainTextPlusSymbolsListSize(in), pos, len);
127 : //LOGD("-> in");
128 : //dumpText(in);
129 :
130 103 : if (pos < 0 || len < 0) {
131 0 : return false;
132 : }
133 :
134 : // ensure output is empty at start
135 103 : left.clear();
136 103 : mid.clear();
137 103 : right.clear();
138 :
139 : // set pos to begin of first fragment
140 103 : std::list<TextFragment>::const_iterator fragmentNr = in.begin();
141 103 : TextFragment fragment;
142 103 : if (fragmentNr != in.end()) {
143 103 : fragment = *fragmentNr;
144 : }
145 103 : std::list<TextFragment>* currentDest = &left;
146 103 : int currentMaxSize = pos;
147 :
148 : // while text left
149 315 : while (fragmentNr != in.end()) {
150 212 : int destSize = plainTextPlusSymbolsListSize(*currentDest);
151 212 : int fragSize = plainTextPlusSymbolsFragmentSize(fragment);
152 : // if no room left in current destination (check applies only to left and mid)
153 212 : if ((currentDest != &right && destSize >= currentMaxSize)
154 109 : || currentDest == &right) {
155 : // move to next destination
156 103 : if (currentDest == &left) {
157 103 : currentDest = ∣
158 103 : currentMaxSize = len;
159 0 : } else if (currentDest == &mid) {
160 0 : currentDest = &right;
161 : }
162 : }
163 : // if current fragment fits in current destination (check applies only to left and mid)
164 212 : if ((currentDest != &right && destSize + fragSize <= currentMaxSize)
165 2 : || currentDest == &right) {
166 : // add it
167 210 : currentDest->push_back(fragment);
168 : // move to next fragment
169 210 : fragmentNr++;
170 317 : if (fragmentNr != in.end()) {
171 107 : fragment = *fragmentNr;
172 : }
173 : } else {
174 : // split current fragment
175 2 : TextFragment rightPart = fragment.split(currentMaxSize - plainTextPlusSymbolsListSize(*currentDest));
176 : // add first part to current destination
177 2 : currentDest->push_back(fragment);
178 2 : fragment = rightPart;
179 2 : }
180 : }
181 :
182 : /*
183 : LOGD("-> left");
184 : dumpText(left);
185 : LOGD("-> mid");
186 : dumpText(mid);
187 : LOGD("-> right");
188 : dumpText(right);
189 : */
190 :
191 103 : return true;
192 103 : }
193 :
194 : //---------------------------------------------------------
195 : // writeTextFragments
196 : //---------------------------------------------------------
197 :
198 400 : void MScoreTextToMusicXml::writeTextFragments(const std::list<TextFragment>& fr, XmlWriter& xml)
199 : {
200 : //dumpText(fr);
201 400 : bool firstTime = true; // write additional attributes only the first time characters are written
202 814 : for (const TextFragment& f : fr) {
203 414 : newFormat = f.format;
204 414 : String formatAttr = updateFormat();
205 414 : xml.tagRaw(tagname + (firstTime ? attribs : String()) + formatAttr, f.text);
206 414 : firstTime = false;
207 414 : }
208 400 : }
209 :
210 : //---------------------------------------------------------
211 : // attribute
212 : // add one attribute if necessary
213 : //---------------------------------------------------------
214 :
215 2484 : static String attribute(bool needed, bool value, String trueString, String falseString)
216 : {
217 2484 : String res;
218 2484 : if (needed) {
219 123 : res = value ? trueString : falseString;
220 : }
221 2484 : if (!res.empty()) {
222 123 : res = u" " + res;
223 : }
224 2484 : return res;
225 0 : }
226 :
227 : //---------------------------------------------------------
228 : // updateFormat
229 : // update the text format by generating attributes
230 : // corresponding to the difference between old- and newFormat
231 : // copy newFormat to oldFormat
232 : //---------------------------------------------------------
233 :
234 414 : String MScoreTextToMusicXml::updateFormat()
235 : {
236 414 : if (newFormat.fontFamily() == "ScoreText") {
237 10 : newFormat.setFontFamily(musicalTextFont);
238 : }
239 414 : String res;
240 414 : res += attribute(newFormat.bold() != oldFormat.bold(), newFormat.bold(), u"font-weight=\"bold\"", u"font-weight=\"normal\"");
241 414 : res += attribute(newFormat.italic() != oldFormat.italic(), newFormat.italic(), u"font-style=\"italic\"", u"font-style=\"normal\"");
242 414 : res += attribute(newFormat.underline() != oldFormat.underline(), newFormat.underline(), u"underline=\"1\"", u"underline=\"0\"");
243 414 : res += attribute(newFormat.strike() != oldFormat.strike(), newFormat.strike(), u"line-through=\"1\"", u"line-though=\"0\"");
244 1242 : res += attribute(newFormat.fontFamily() != oldFormat.fontFamily(), true,
245 1242 : String(u"font-family=\"%1\"").arg(newFormat.fontFamily()), u"");
246 414 : bool needSize = newFormat.fontSize() < 0.99 * oldFormat.fontSize() || newFormat.fontSize() > 1.01 * oldFormat.fontSize();
247 414 : res += attribute(needSize, true, String(u"font-size=\"%1\"").arg(newFormat.fontSize()), u"");
248 : //LOGD("updateFormat() res '%s'", muPrintable(res));
249 414 : oldFormat = newFormat;
250 414 : return res;
251 0 : }
252 : } // namespace Ms
|