2014-05-22 21:35:45 +02:00
|
|
|
//=============================================================================
|
|
|
|
// MusE Score
|
|
|
|
// Linux Music Score Editor
|
|
|
|
//
|
|
|
|
// Copyright (C) 2014 Werner Schweer and others
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License version 2.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
/**
|
|
|
|
MusicXML font handling support.
|
|
|
|
*/
|
|
|
|
|
2014-12-23 12:07:26 +01:00
|
|
|
#include "libmscore/sym.h"
|
2014-05-22 21:35:45 +02:00
|
|
|
#include "libmscore/xml.h"
|
|
|
|
#include "musicxmlfonthandler.h"
|
|
|
|
|
|
|
|
namespace Ms {
|
2014-06-04 08:30:59 +02:00
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// charFormat2QString
|
|
|
|
// convert charFormat to QString for debug print
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-12-26 16:30:52 +01:00
|
|
|
#if 1
|
2014-05-22 21:35:45 +02:00
|
|
|
static QString charFormat2QString(const CharFormat& f)
|
|
|
|
{
|
|
|
|
return QString("b %1 i %2 u %3 va %4 fs %5 fam %6")
|
|
|
|
.arg(f.bold())
|
|
|
|
.arg(f.italic())
|
|
|
|
.arg(f.underline())
|
|
|
|
.arg(static_cast<int>(f.valign()))
|
|
|
|
.arg(f.fontSize())
|
|
|
|
.arg(f.fontFamily())
|
|
|
|
;
|
|
|
|
}
|
2014-12-23 12:07:26 +01:00
|
|
|
|
2014-12-26 16:30:52 +01:00
|
|
|
void dumpText(const QList<TextFragment>& list)
|
2014-12-23 12:07:26 +01:00
|
|
|
{
|
|
|
|
qDebug("MScoreTextToMXML::dumpText %d fragment(s)", list.size());
|
|
|
|
for (const TextFragment& f : list) {
|
|
|
|
QString t = "fragment";
|
|
|
|
if (f.format.type() == CharFormatType::TEXT) {
|
|
|
|
t += QString(" text '%1'").arg(f.text);
|
|
|
|
t += QString(" len %1").arg(f.text.size());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
t += " syms";
|
|
|
|
int len = 0;
|
|
|
|
for (const SymId id : f.ids) {
|
|
|
|
t += QString(" '%1'").arg(Sym::id2name(id));
|
|
|
|
QString s = QString("<sym>%1</sym>").arg(Sym::id2name(id));
|
|
|
|
len += s.size();
|
|
|
|
}
|
|
|
|
t += QString(" len %1").arg(len);
|
|
|
|
}
|
|
|
|
t += " format ";
|
|
|
|
t += charFormat2QString(f.format);
|
|
|
|
qDebug("%s", qPrintable(t));
|
|
|
|
}
|
|
|
|
}
|
2014-06-04 08:30:59 +02:00
|
|
|
#endif
|
2014-05-22 21:35:45 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// MScoreTextToMXML
|
|
|
|
// Convert rich text as generated by Text::text()
|
2014-06-17 20:36:20 +02:00
|
|
|
// into MusicXML text with or without formatting
|
2014-06-25 22:41:34 +02:00
|
|
|
// defTs: default text style (as specified in word-font or lyric-font)
|
|
|
|
// actTs: actual text style for this type of text
|
2014-05-22 21:35:45 +02:00
|
|
|
// if curfs != deffs, an initial font-size attribute is emitted
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-06-25 22:41:34 +02:00
|
|
|
MScoreTextToMXML::MScoreTextToMXML(const QString& tag, const QString& attr, const QString& t, const TextStyle& defTs, const TextStyle& actTs)
|
2014-05-22 21:35:45 +02:00
|
|
|
: attribs(attr), tagname(tag)
|
|
|
|
{
|
2014-06-29 21:36:30 +02:00
|
|
|
//qDebug("MScoreTextToMXML('%s')", qPrintable(t));
|
2014-06-25 22:41:34 +02:00
|
|
|
// handle difference between style for words / lyric and actual type
|
|
|
|
oldFormat.setFontFamily(defTs.family());
|
|
|
|
newFormat.setFontFamily(actTs.family());
|
|
|
|
oldFormat.setFontSize(defTs.size());
|
|
|
|
newFormat.setFontSize(actTs.size());
|
2014-11-08 11:05:54 +01:00
|
|
|
oldFormat.setBold(false);
|
|
|
|
newFormat.setBold(actTs.bold());
|
|
|
|
oldFormat.setItalic(false);
|
|
|
|
newFormat.setItalic(actTs.italic());
|
|
|
|
oldFormat.setUnderline(false);
|
|
|
|
newFormat.setUnderline(actTs.underline());
|
2014-05-22 21:35:45 +02:00
|
|
|
// convert text into valid xml by adding dummy start and end tags
|
|
|
|
text = "<dummy>" + t + "</dummy>";
|
|
|
|
}
|
2014-06-17 20:36:20 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// toPlainText
|
|
|
|
// convert to plain text
|
|
|
|
// naive implementation: simply remove all chars from '<' to '>'
|
|
|
|
// typically used to remove formatting info from fields read
|
|
|
|
// from MuseScore 1.3 file where they are stored as html, such as
|
|
|
|
// part name and shortName
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QString MScoreTextToMXML::toPlainText(const QString& text)
|
|
|
|
{
|
|
|
|
QString res;
|
|
|
|
bool inElem = false;
|
|
|
|
foreach(QChar ch, text) {
|
|
|
|
if (ch == '<')
|
|
|
|
inElem = true;
|
|
|
|
else if (ch == '>')
|
|
|
|
inElem = false;
|
|
|
|
else {
|
|
|
|
if (!inElem)
|
|
|
|
res += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//qDebug("MScoreTextToMXML::toPlainText('%s') res '%s'", qPrintable(text), qPrintable(res));
|
|
|
|
return res;
|
|
|
|
}
|
2014-12-23 12:07:26 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// toPlainTextPlusSymbols
|
|
|
|
// convert to plain text plus <sym>[name]</sym> encoded symbols
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QString MScoreTextToMXML::toPlainTextPlusSymbols(const QList<TextFragment>& list)
|
|
|
|
{
|
|
|
|
QString res;
|
|
|
|
for (const TextFragment& f : list) {
|
|
|
|
if (f.format.type() == CharFormatType::TEXT)
|
|
|
|
res += f.text;
|
|
|
|
else {
|
|
|
|
for (const SymId id : f.ids)
|
|
|
|
res += QString("<sym>%1</sym>").arg(Sym::id2name(id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
2014-05-22 21:35:45 +02:00
|
|
|
|
2014-12-26 16:30:52 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// plainTextPlusSymbolsSize
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static int plainTextPlusSymbolsFragmentSize(const TextFragment& f)
|
|
|
|
{
|
|
|
|
int res = 0;
|
|
|
|
if (f.format.type() == CharFormatType::TEXT)
|
|
|
|
res += f.columns();
|
|
|
|
else {
|
|
|
|
for (const SymId id : f.ids)
|
|
|
|
res += QString("<sym>%1</sym>").arg(Sym::id2name(id)).size();
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// plainTextPlusSymbolsSize
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static int plainTextPlusSymbolsListSize(const QList<TextFragment>& list)
|
|
|
|
{
|
|
|
|
int res = 0;
|
|
|
|
for (const TextFragment& f : list) {
|
|
|
|
res += plainTextPlusSymbolsFragmentSize(f);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// split
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
Split \a in into \a left, \a mid and \a right. Mid starts at \a pos and is \a len characters long.
|
|
|
|
Pos and len refer to the representation returned by toPlainTextPlusSymbols().
|
|
|
|
TODO Make sure surrogate pairs are handled correctly
|
|
|
|
Return true if OK, false on error.
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool MScoreTextToMXML::split(const QList<TextFragment>& in, const int pos, const int len,
|
|
|
|
QList<TextFragment>& left, QList<TextFragment>& mid, QList<TextFragment>& right)
|
|
|
|
{
|
|
|
|
qDebug("MScoreTextToMXML::split in size %d pos %d len %d", plainTextPlusSymbolsListSize(in), pos, len);
|
|
|
|
qDebug("-> in");
|
|
|
|
dumpText(in);
|
|
|
|
|
|
|
|
if (pos < 0 || len < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// ensure output is empty at start
|
|
|
|
left.clear();
|
|
|
|
mid.clear();
|
|
|
|
right.clear();
|
|
|
|
|
|
|
|
// set pos to begin of first fragment
|
|
|
|
int fragmentNr = 0;
|
|
|
|
TextFragment fragment;
|
|
|
|
if (fragmentNr < in.size()) fragment = in.at(fragmentNr);
|
|
|
|
QList<TextFragment>* currentDest = &left;
|
|
|
|
int currentMaxSize = pos;
|
|
|
|
|
|
|
|
// while text left
|
|
|
|
while (fragmentNr < in.size()) {
|
|
|
|
int destSize = plainTextPlusSymbolsListSize(*currentDest);
|
|
|
|
int fragSize = plainTextPlusSymbolsFragmentSize(fragment);
|
|
|
|
//qDebug("destSize %d fragSize %d maxSize %d", destSize, fragSize, currentMaxSize);
|
|
|
|
// if no room left in current destination (check applies only to left and mid)
|
|
|
|
if ((currentDest != &right && destSize >= currentMaxSize)
|
|
|
|
|| currentDest == &right) {
|
|
|
|
// move to next destination
|
|
|
|
if (currentDest == &left) {
|
|
|
|
//qDebug("no room in dest move to next dest mid");
|
|
|
|
currentDest = ∣
|
|
|
|
currentMaxSize = len;
|
|
|
|
}
|
|
|
|
else if (currentDest == &mid) {
|
|
|
|
//qDebug("no room in dest move to next dest right");
|
|
|
|
currentDest = &right;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if current fragment fits in current destination (check applies only to left and mid)
|
|
|
|
if ((currentDest != &right && destSize + fragSize <= currentMaxSize)
|
|
|
|
|| currentDest == &right) {
|
|
|
|
// add it
|
|
|
|
//qDebug("fragment fits in dest, adding %d", plainTextPlusSymbolsFragmentSize(fragment));
|
|
|
|
currentDest->append(fragment);
|
|
|
|
// move to next fragment
|
|
|
|
fragmentNr++;
|
|
|
|
//posInFragment = 0;
|
|
|
|
if (fragmentNr < in.size()) fragment = in.at(fragmentNr);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// split current fragment
|
|
|
|
//qDebug("fragment does not fit in dest, splitting");
|
|
|
|
TextFragment rightPart = fragment.split(currentMaxSize - plainTextPlusSymbolsListSize(*currentDest));
|
|
|
|
// add first part to current destination
|
|
|
|
currentDest->append(fragment);
|
|
|
|
//qDebug("adding %d", plainTextPlusSymbolsFragmentSize(fragment));
|
|
|
|
fragment = rightPart;
|
|
|
|
//qDebug("remainder: %d", plainTextPlusSymbolsFragmentSize(fragment));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug("-> left");
|
|
|
|
dumpText(left);
|
|
|
|
qDebug("-> mid");
|
|
|
|
dumpText(mid);
|
|
|
|
qDebug("-> right");
|
|
|
|
dumpText(right);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// MScoreTextToMXML
|
|
|
|
// write to xml
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MScoreTextToMXML::write(Xml& xml)
|
|
|
|
{
|
|
|
|
//qDebug("MScoreTextToMXML::write()");
|
|
|
|
QXmlStreamReader r(text);
|
|
|
|
bool firstTime = true; // write additional attributes only the first time characters are written
|
|
|
|
while (!r.atEnd()) {
|
|
|
|
// do processing
|
|
|
|
r.readNext();
|
|
|
|
if(r.isCharacters()) {
|
|
|
|
//qDebug("old %s", qPrintable(charFormat2QString(oldFormat)));
|
|
|
|
//qDebug("new %s", qPrintable(charFormat2QString(newFormat)));
|
|
|
|
QString formatAttr = updateFormat();
|
|
|
|
//qDebug("old %s", qPrintable(charFormat2QString(oldFormat)));
|
|
|
|
//qDebug("new %s", qPrintable(charFormat2QString(newFormat)));
|
|
|
|
//qDebug("Characters '%s'", qPrintable(r.text().toString()));
|
|
|
|
xml.tag(tagname + (firstTime ? attribs : "") + formatAttr, r.text().toString());
|
|
|
|
firstTime = false;
|
|
|
|
}
|
|
|
|
else if(r.isEndElement()) {
|
|
|
|
//qDebug("EndElem '%s'", qPrintable(r.name().toString()));
|
|
|
|
handleEndElement(r);
|
|
|
|
}
|
|
|
|
else if(r.isStartElement()) {
|
|
|
|
/*
|
|
|
|
qDebug("StartElem '%s'", qPrintable(r.name().toString()));
|
|
|
|
if (r.name() == "font")
|
|
|
|
qDebug(" face='%s' size='%s'",
|
|
|
|
qPrintable(r.attributes().value("face").toString()),
|
|
|
|
qPrintable(r.attributes().value("size").toString()));
|
|
|
|
*/
|
|
|
|
handleStartElement(r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (r.hasError()) {
|
|
|
|
// do error handling
|
|
|
|
qDebug("Error %s", qPrintable(r.errorString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// MScoreTextToMXML
|
|
|
|
// update newFormat for start element
|
|
|
|
//---------------------------------------------------------
|
2014-06-04 08:30:59 +02:00
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
void MScoreTextToMXML::handleStartElement(QXmlStreamReader& r)
|
|
|
|
{
|
|
|
|
if (r.name() == "b")
|
|
|
|
newFormat.setBold(true);
|
|
|
|
else if (r.name() == "i")
|
|
|
|
newFormat.setItalic(true);
|
|
|
|
else if (r.name() == "u")
|
|
|
|
newFormat.setUnderline(true);
|
|
|
|
else if (r.name() == "font" && r.attributes().hasAttribute("size"))
|
|
|
|
newFormat.setFontSize(r.attributes().value("size").toFloat());
|
|
|
|
else if (r.name() == "font" && r.attributes().hasAttribute("face"))
|
|
|
|
newFormat.setFontFamily(r.attributes().value("face").toString());
|
|
|
|
else if (r.name() == "sub")
|
|
|
|
; // ignore (not supported in MusicXML)
|
|
|
|
else if (r.name() == "sup")
|
|
|
|
; // ignore (not supported in MusicXML)
|
|
|
|
else if (r.name() == "sym")
|
|
|
|
// ignore (TODO ?)
|
|
|
|
r.skipCurrentElement();
|
|
|
|
else if (r.name() == "dummy")
|
|
|
|
; // ignore
|
|
|
|
else {
|
|
|
|
qDebug("handleStartElem '%s' unknown", qPrintable(r.name().toString()));
|
|
|
|
r.skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
2014-06-04 08:30:59 +02:00
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// MScoreTextToMXML
|
|
|
|
// update newFormat for end element
|
|
|
|
//---------------------------------------------------------
|
2014-06-04 08:30:59 +02:00
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
void MScoreTextToMXML::handleEndElement(QXmlStreamReader& r)
|
|
|
|
{
|
|
|
|
if (r.name() == "b")
|
|
|
|
newFormat.setBold(false);
|
|
|
|
else if (r.name() == "i")
|
|
|
|
newFormat.setItalic(false);
|
|
|
|
else if (r.name() == "u")
|
|
|
|
newFormat.setUnderline(false);
|
|
|
|
else if (r.name() == "font")
|
|
|
|
; // ignore
|
|
|
|
else if (r.name() == "sub")
|
|
|
|
; // ignore (not supported in MusicXML)
|
|
|
|
else if (r.name() == "sup")
|
|
|
|
; // ignore (not supported in MusicXML)
|
|
|
|
else if (r.name() == "sym")
|
|
|
|
; // ignore (TODO ?)
|
|
|
|
else if (r.name() == "dummy")
|
|
|
|
; // ignore
|
|
|
|
else {
|
|
|
|
qDebug("handleEndElem '%s' unknown", qPrintable(r.name().toString()));
|
|
|
|
r.skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
2014-06-04 08:30:59 +02:00
|
|
|
|
2014-05-22 21:35:45 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// attribute
|
|
|
|
// add one attribute if necessary
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static QString attribute(bool needed, bool value, QString trueString, QString falseString)
|
|
|
|
{
|
|
|
|
QString res;
|
|
|
|
if (needed)
|
|
|
|
res = value ? trueString : falseString;
|
|
|
|
if (res != "")
|
|
|
|
res = " " + res;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// updateFormat
|
|
|
|
// update the text format by generating attributes
|
|
|
|
// corresponding to the difference between old- and newFormat
|
|
|
|
// copy newFormat to oldFormat
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QString MScoreTextToMXML::updateFormat()
|
|
|
|
{
|
|
|
|
QString res;
|
|
|
|
res += attribute(newFormat.bold() != oldFormat.bold(), newFormat.bold(), "font-weight=\"bold\"", "font-weight=\"normal\"");
|
|
|
|
res += attribute(newFormat.italic() != oldFormat.italic(), newFormat.italic(), "font-style=\"italic\"", "font-style=\"normal\"");
|
|
|
|
res += attribute(newFormat.underline() != oldFormat.underline(), newFormat.underline(), "underline=\"1\"", "underline=\"0\"");
|
|
|
|
res += attribute(newFormat.fontFamily() != oldFormat.fontFamily(), true, QString("font-family=\"%1\"").arg(newFormat.fontFamily()), "");
|
|
|
|
bool needSize = newFormat.fontSize() < 0.99 * oldFormat.fontSize() || newFormat.fontSize() > 1.01 * oldFormat.fontSize();
|
|
|
|
res += attribute(needSize, true, QString("font-size=\"%1\"").arg(newFormat.fontSize()), "");
|
|
|
|
//qDebug("updateFormat() res '%s'", qPrintable(res));
|
|
|
|
oldFormat = newFormat;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ms
|