MuseScore/mscore/musicxmlfonthandler.cpp

404 lines
15 KiB
C++
Raw Normal View History

//=============================================================================
// 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"
#include "libmscore/xml.h"
#include "musicxmlfonthandler.h"
namespace Ms {
//---------------------------------------------------------
// charFormat2QString
// convert charFormat to QString for debug print
//---------------------------------------------------------
#if 1
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
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));
}
}
#endif
//---------------------------------------------------------
// MScoreTextToMXML
// Convert rich text as generated by Text::text()
// 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
// 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)
: attribs(attr), tagname(tag)
{
//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());
oldFormat.setBold(false);
newFormat.setBold(actTs.bold());
oldFormat.setItalic(false);
newFormat.setItalic(actTs.italic());
oldFormat.setUnderline(false);
newFormat.setUnderline(actTs.underline());
// convert text into valid xml by adding dummy start and end tags
text = "<dummy>" + t + "</dummy>";
}
//---------------------------------------------------------
// 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;
}
//---------------------------------------------------------
// 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 = &mid;
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;
}
//---------------------------------------------------------
// 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
//---------------------------------------------------------
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();
}
}
//---------------------------------------------------------
// MScoreTextToMXML
// update newFormat for end element
//---------------------------------------------------------
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();
}
}
//---------------------------------------------------------
// 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