Skip to content

Commit

Permalink
Copyright and page number text styles
Browse files Browse the repository at this point in the history
  • Loading branch information
mathesoncalum committed Jul 17, 2024
1 parent f457467 commit 92abe16
Show file tree
Hide file tree
Showing 70 changed files with 412 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/engraving/api/v1/apitypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ enum class Tid {
INSTRUMENT_CHANGE = int(mu::engraving::TextStyleType::INSTRUMENT_CHANGE),
HEADER = int(mu::engraving::TextStyleType::HEADER),
FOOTER = int(mu::engraving::TextStyleType::FOOTER),
COPYRIGHT = int(mu::engraving::TextStyleType::COPYRIGHT),
PAGE_NUMBER = int(mu::engraving::TextStyleType::PAGE_NUMBER)
};
Q_ENUM_NS(Tid);

Expand Down
148 changes: 114 additions & 34 deletions src/engraving/dom/page.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,30 @@ void Page::appendSystem(System* s)

Text* Page::layoutHeaderFooter(int area, const String& ss) const
{
String s = replaceTextMacros(ss);
if (s.isEmpty()) {
bool isHeader = area < MAX_HEADERS;

TextBlock tb = replaceTextMacros(isHeader, ss);
if (tb.fragmentsWithoutEmpty().empty()) {
return nullptr;
}

//! NOTE: Keep in sync with replaceTextMacros
std::wregex copyrightSearch(LR"(\$[cC])");
std::wregex pageNumberSearch(LR"(\$[pPnN])");
bool containsCopyright = ss.contains(copyrightSearch);
bool containsPageNumber = ss.contains(pageNumberSearch);

// Slight hack - we'll use copyright/page number styling if the string contains copyright or page number
// macros (hack because any non-copyright text in the same block will also adopt these style values)
TextStyleType style = containsCopyright ? TextStyleType::COPYRIGHT
: (containsPageNumber ? TextStyleType::PAGE_NUMBER
: (isHeader ? TextStyleType::HEADER : TextStyleType::FOOTER));

Text* text;
if (area < MAX_HEADERS) {
if (isHeader) {
text = score()->headerText(area);
if (!text) {
text = Factory::createText((Page*)this, TextStyleType::HEADER);
text = Factory::createText((Page*)this, style);
text->setFlag(ElementFlag::MOVABLE, false);
text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
text->setLayoutToParentWidth(true);
Expand All @@ -108,14 +122,15 @@ Text* Page::layoutHeaderFooter(int area, const String& ss) const
} else {
text = score()->footerText(area - MAX_HEADERS); // because they are 3 4 5
if (!text) {
text = Factory::createText((Page*)this, TextStyleType::FOOTER);
text = Factory::createText((Page*)this, style);
text->setFlag(ElementFlag::MOVABLE, false);
text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
text->setLayoutToParentWidth(true);
score()->setFooterText(text, area - MAX_HEADERS);
}
}
text->setParent((Page*)this);

Align align = { AlignH::LEFT, AlignV::TOP };
switch (area) {
case 0: align = { AlignH::LEFT, AlignV::TOP };
Expand All @@ -132,8 +147,14 @@ Text* Page::layoutHeaderFooter(int area, const String& ss) const
break;
}
text->setAlign(align);
text->setXmlText(s);

// Generates text from ldata, ensures newlines are formatted properly...
text->mutldata()->blocks = { tb };
text->genText();
text->createBlocks();

renderer()->layoutItem(text);

return text;
}

Expand Down Expand Up @@ -326,10 +347,21 @@ void Page::doRebuildBspTree()
// workTitle
//---------------------------------------------------------

String Page::replaceTextMacros(const String& s) const
TextBlock Page::replaceTextMacros(bool isHeader, const String& s) const
{
String d;
// If the string in question consists solely of a "styled macro" (i.e. page number or copyright), we can set the default
// format to the associated styling. We'll use this later to prevent the creation of unneccessary fragments...
CharFormat defaultFormat = formatForMacro(s);
if (defaultFormat == CharFormat()) {
// The string isn't just a styled macro, use header/footer styling...
defaultFormat.setStyle(style().styleV(isHeader ? Sid::headerFontStyle : Sid::footerFontStyle).value<FontStyle>());
defaultFormat.setFontSize(style().styleD(isHeader ? Sid::headerFontSize : Sid::footerFontSize));
defaultFormat.setFontFamily(style().styleSt(isHeader ? Sid::headerFontFace : Sid::footerFontFace));
}

std::list<TextFragment> fragments(1);
for (size_t i = 0, n = s.size(); i < n; ++i) {
fragments.back().format = defaultFormat;
Char c = s.at(i);
if (c == '$' && (i < (n - 1))) {
Char nc = s.at(i + 1);
Expand All @@ -348,52 +380,66 @@ String Page::replaceTextMacros(const String& s) const
{
int no = static_cast<int>(m_no) + 1 + score()->pageNumberOffset();
if (no > 0) {
d += String::number(no);
const String pageNumberString = String::number(no);
const CharFormat pageNumberFormat = formatForMacro(String('$' + nc));
// If the default format equals the format for this macro, we don't need to create a new fragment...
if (defaultFormat == pageNumberFormat) {
fragments.back().text += pageNumberString;
break;
}
TextFragment pageNumberFragment(pageNumberString);
pageNumberFragment.format = pageNumberFormat;
fragments.emplace_back(pageNumberFragment);
fragments.emplace_back(TextFragment()); // Start next fragment
}
}
break;
case 'n':
d += String::number(score()->npages() + score()->pageNumberOffset());
fragments.back().text += String::number(score()->npages() + score()->pageNumberOffset());
break;
case 'i': // not on first page
if (!m_no) {
break;
}
// FALLTHROUGH
case 'I':
d += score()->metaTag(u"partName").toXmlEscaped();
fragments.back().text += score()->metaTag(u"partName").toXmlEscaped();
break;
case 'f':
d += masterScore()->fileInfo()->fileName(false).toString().toXmlEscaped();
fragments.back().text += masterScore()->fileInfo()->fileName(false).toString().toXmlEscaped();
break;
case 'F':
d += masterScore()->fileInfo()->path().toString().toXmlEscaped();
fragments.back().text += masterScore()->fileInfo()->path().toString().toXmlEscaped();
break;
case 'd':
d += muse::Date::currentDate().toString(muse::DateFormat::ISODate);
fragments.back().text += muse::Date::currentDate().toString(muse::DateFormat::ISODate);
break;
case 'D':
{
String creationDate = score()->metaTag(u"creationDate");
if (creationDate.isEmpty()) {
d += masterScore()->fileInfo()->birthTime().date().toString(muse::DateFormat::ISODate);
fragments.back().text += masterScore()->fileInfo()->birthTime().date().toString(
muse::DateFormat::ISODate);
} else {
d += muse::Date::fromStringISOFormat(creationDate).toString(muse::DateFormat::ISODate);
fragments.back().text += muse::Date::fromStringISOFormat(creationDate).toString(
muse::DateFormat::ISODate);
}
}
break;
case 'm':
if (score()->dirty() || !masterScore()->saved()) {
d += muse::Time::currentTime().toString(muse::DateFormat::ISODate);
fragments.back().text += muse::Time::currentTime().toString(muse::DateFormat::ISODate);
} else {
d += masterScore()->fileInfo()->lastModified().time().toString(muse::DateFormat::ISODate);
fragments.back().text += masterScore()->fileInfo()->lastModified().time().toString(
muse::DateFormat::ISODate);
}
break;
case 'M':
if (score()->dirty() || !masterScore()->saved()) {
d += muse::Date::currentDate().toString(muse::DateFormat::ISODate);
fragments.back().text += muse::Date::currentDate().toString(muse::DateFormat::ISODate);
} else {
d += masterScore()->fileInfo()->lastModified().date().toString(muse::DateFormat::ISODate);
fragments.back().text += masterScore()->fileInfo()->lastModified().date().toString(
muse::DateFormat::ISODate);
}
break;
case 'C': // only on first page
Expand All @@ -402,29 +448,41 @@ String Page::replaceTextMacros(const String& s) const
}
// FALLTHROUGH
case 'c':
d += score()->metaTag(u"copyright").toXmlEscaped();
break;
{
const String copyrightString = score()->metaTag(u"copyright").toXmlEscaped();
const CharFormat copyrightFormat = formatForMacro(String('$' + nc));
// If the default format equals the format for this macro, we don't need to create a new fragment...
if (defaultFormat == copyrightFormat) {
fragments.back().text += copyrightString;
break;
}
TextFragment copyrightFragment(copyrightString);
copyrightFragment.format = copyrightFormat;
fragments.emplace_back(copyrightFragment);
fragments.emplace_back(TextFragment()); // Start next fragment
}
break;
case 'v':
if (score()->dirty()) {
d += score()->appVersion();
fragments.back().text += score()->appVersion();
} else {
d += score()->mscoreVersion();
fragments.back().text += score()->mscoreVersion();
}
break;
case 'r':
if (score()->dirty()) {
d += revision;
fragments.back().text += revision;
} else {
int rev = score()->mscoreRevision();
if (rev > 99999) { // MuseScore 1.3 is decimal 5702, 2.0 and later uses a 7-digit hex SHA
d += String::number(rev, 16);
fragments.back().text += String::number(rev, 16);
} else {
d += String::number(rev, 10);
fragments.back().text += String::number(rev, 10);
}
}
break;
case '$':
d += '$';
fragments.back().text += '$';
break;
case ':':
{
Expand All @@ -437,24 +495,46 @@ String Page::replaceTextMacros(const String& s) const
tag += s.at(k);
}
if (k != n) { // found ':' ?
d += score()->metaTag(tag).toXmlEscaped();
fragments.back().text += score()->metaTag(tag).toXmlEscaped();
i = k - 1;
}
}
break;
default:
d += '$';
d += nc;
fragments.back().text += '$';
fragments.back().text += nc;
break;
}
++i;
} else if (c == '&') {
d += u"&amp;";
fragments.back().text += u"&amp;";
} else {
d += c;
fragments.back().text += c;
}
}
return d;

TextBlock tb;
tb.fragments() = fragments;
return tb;
}

//---------------------------------------------------------
// formatForMacro
//---------------------------------------------------------

const CharFormat Page::formatForMacro(const String& s) const
{
CharFormat format;
if (s == "$c" || s == "$C") {
format.setStyle(style().styleV(Sid::copyrightFontStyle).value<FontStyle>());
format.setFontSize(style().styleD(Sid::copyrightFontSize));
format.setFontFamily(style().styleSt(Sid::copyrightFontFace));
} else if (s == "$p" || s == "$P" || s == "$n" || s == "$N") {
format.setStyle(style().styleV(Sid::pageNumberFontStyle).value<FontStyle>());
format.setFontSize(style().styleD(Sid::pageNumberFontSize));
format.setFontFamily(style().styleSt(Sid::pageNumberFontFace));
}
return format;
}

//---------------------------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion src/engraving/dom/page.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "engravingitem.h"
#include "bsp.h"
#include "text.h"

namespace mu::engraving {
class RootItem;
Expand Down Expand Up @@ -93,7 +94,8 @@ class Page final : public EngravingItem
Page(RootItem* parent);

void doRebuildBspTree();
String replaceTextMacros(const String&) const;
TextBlock replaceTextMacros(bool isHeader, const String&) const;
const CharFormat formatForMacro(const String&) const;

std::vector<System*> m_systems;
page_idx_t m_no = 0; // page number
Expand Down
3 changes: 2 additions & 1 deletion src/engraving/dom/textbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ class TextBase : public EngravingItem
DirectionV direction() const { return m_direction; }
void setCenterBetweenStaves(AutoOnOff v) { m_centerBetweenStaves = v; }
AutoOnOff centerBetweenStaves() const { return m_centerBetweenStaves; }
void genText();

protected:
TextBase(const ElementType& type, EngravingItem* parent = 0, TextStyleType tid = TextStyleType::DEFAULT,
Expand All @@ -507,7 +508,7 @@ class TextBase : public EngravingItem
void drawSelection(muse::draw::Painter*, const RectF&) const;
void insert(TextCursor*, char32_t code, LayoutData* ldata) const;
String genText(const LayoutData* ldata) const;
void genText();

virtual int getPropertyFlagsIdx(Pid id) const override;
String stripText(bool, bool, bool) const;
Sid offsetSid() const;
Expand Down
50 changes: 50 additions & 0 deletions src/engraving/rw/compat/compatutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
#include "dom/playtechannotation.h"
#include "dom/capo.h"

#include "engraving/style/textstyle.h"

#include "types/string.h"

#include "log.h"
Expand Down Expand Up @@ -98,6 +100,9 @@ void CompatUtils::doCompatibilityConversions(MasterScore* masterScore)
addMissingInitKeyForTransposingInstrument(masterScore);
resetFramesExclusionFromParts(masterScore);
}
if (masterScore->mscVersion() < 440) {
mapHeaderFooterStyles(masterScore);
}
}

void CompatUtils::replaceStaffTextWithPlayTechniqueAnnotation(MasterScore* score)
Expand Down Expand Up @@ -668,3 +673,48 @@ void CompatUtils::resetFramesExclusionFromParts(MasterScore* masterScore)
}
}
}

void CompatUtils::mapHeaderFooterStyles(MasterScore* score)
{
// Copyright and page numbers used header/footer styling before 4.4 - after 4.4 these have their own styles. To ensure nothing
// changes visually when loading a pre-4.4 score for the first time, we must search the header/footer strings for copyright/page
// number macros and set the "defaults" for copyright/page number styles based on where the macros were inserted...
const auto doMap = [score](const TextStyleType type, const std::vector<Sid> headerFooterStringSids) {
const TextStyle* headerFooterTextStyle = textStyle(type);
const TextStyle* copyrightTextStyle = textStyle(TextStyleType::COPYRIGHT);
const TextStyle* pageNumberTextStyle = textStyle(TextStyleType::PAGE_NUMBER);

//! NOTE: Keep in sync with Page::replaceTextMacros
const std::wregex copyrightSearch(LR"(\$[cC])");
const std::wregex pageNumberSearch(LR"(\$[pPnN])");

bool haveMappedCopyright = false;
bool haveMappedPageNumber = false;
for (const Sid sid : headerFooterStringSids) {
const String s = score->style().styleSt(sid);
if (!haveMappedCopyright && s.contains(copyrightSearch)) {
for (size_t i = 0; i < TEXT_STYLE_SIZE; ++i) {
const Sid headerFooterSid = headerFooterTextStyle->at(i).sid;
const PropertyValue pv = score->style().styleV(headerFooterSid);
const Sid copyrightSid = copyrightTextStyle->at(i).sid;
score->style().set(copyrightSid, pv);
}
haveMappedCopyright = true;
}
if (!haveMappedPageNumber && s.contains(pageNumberSearch)) {
for (size_t i = 0; i < TEXT_STYLE_SIZE; ++i) {
const Sid headerFooterSid = headerFooterTextStyle->at(i).sid;
const Sid pageNumberSid = pageNumberTextStyle->at(i).sid;
const PropertyValue pv = score->style().styleV(headerFooterSid);
score->style().set(pageNumberSid, pv);
}
haveMappedPageNumber = true;
}
}
};

doMap(TextStyleType::HEADER, { Sid::oddHeaderL, Sid::oddHeaderC, Sid::oddHeaderR,
Sid::evenHeaderL, Sid::evenHeaderC, Sid::evenHeaderR });
doMap(TextStyleType::FOOTER, { Sid::oddFooterL, Sid::oddFooterC, Sid::oddFooterR,
Sid::evenFooterL, Sid::evenFooterC, Sid::evenFooterR });
}
1 change: 1 addition & 0 deletions src/engraving/rw/compat/compatutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CompatUtils
static void replaceStaffTextWithCapo(MasterScore* masterScore);
static void addMissingInitKeyForTransposingInstrument(MasterScore* score);
static void resetFramesExclusionFromParts(MasterScore* masterScore);
static void mapHeaderFooterStyles(MasterScore* masterScore);
};
}
#endif // MU_ENGRAVING_COMPATUTILS_H
Loading

0 comments on commit 92abe16

Please sign in to comment.