@@ -90,16 +90,30 @@ void Page::appendSystem(System* s)
9090
9191Text* Page::layoutHeaderFooter (int area, const String& ss) const
9292{
93- String s = replaceTextMacros (ss);
94- if (s.isEmpty ()) {
93+ bool isHeader = area < MAX_HEADERS;
94+
95+ TextBlock tb = replaceTextMacros (isHeader, ss);
96+ if (tb.fragmentsWithoutEmpty ().empty ()) {
9597 return nullptr ;
9698 }
9799
100+ // ! NOTE: Keep in sync with replaceTextMacros
101+ std::wregex copyrightSearch (LR"( \$[cC])" );
102+ std::wregex pageNumberSearch (LR"( \$[pPnN])" );
103+ bool containsCopyright = ss.contains (copyrightSearch);
104+ bool containsPageNumber = ss.contains (pageNumberSearch);
105+
106+ // Slight hack - we'll use copyright/page number styling if the string contains copyright or page number
107+ // macros (hack because any non-copyright text in the same block will also adopt these style values)
108+ TextStyleType style = containsCopyright ? TextStyleType::COPYRIGHT
109+ : (containsPageNumber ? TextStyleType::PAGE_NUMBER
110+ : (isHeader ? TextStyleType::HEADER : TextStyleType::FOOTER));
111+
98112 Text* text;
99- if (area < MAX_HEADERS ) {
113+ if (isHeader ) {
100114 text = score ()->headerText (area);
101115 if (!text) {
102- text = Factory::createText ((Page*)this , TextStyleType::HEADER );
116+ text = Factory::createText ((Page*)this , style );
103117 text->setFlag (ElementFlag::MOVABLE, false );
104118 text->setFlag (ElementFlag::GENERATED, true ); // set to disable editing
105119 text->setLayoutToParentWidth (true );
@@ -108,14 +122,15 @@ Text* Page::layoutHeaderFooter(int area, const String& ss) const
108122 } else {
109123 text = score ()->footerText (area - MAX_HEADERS); // because they are 3 4 5
110124 if (!text) {
111- text = Factory::createText ((Page*)this , TextStyleType::FOOTER );
125+ text = Factory::createText ((Page*)this , style );
112126 text->setFlag (ElementFlag::MOVABLE, false );
113127 text->setFlag (ElementFlag::GENERATED, true ); // set to disable editing
114128 text->setLayoutToParentWidth (true );
115129 score ()->setFooterText (text, area - MAX_HEADERS);
116130 }
117131 }
118132 text->setParent ((Page*)this );
133+
119134 Align align = { AlignH::LEFT, AlignV::TOP };
120135 switch (area) {
121136 case 0 : align = { AlignH::LEFT, AlignV::TOP };
@@ -132,8 +147,14 @@ Text* Page::layoutHeaderFooter(int area, const String& ss) const
132147 break ;
133148 }
134149 text->setAlign (align);
135- text->setXmlText (s);
150+
151+ // Generates text from ldata, ensures newlines are formatted properly...
152+ text->mutldata ()->blocks = { tb };
153+ text->genText ();
154+ text->createBlocks ();
155+
136156 renderer ()->layoutItem (text);
157+
137158 return text;
138159}
139160
@@ -326,10 +347,21 @@ void Page::doRebuildBspTree()
326347// workTitle
327348// ---------------------------------------------------------
328349
329- String Page::replaceTextMacros (const String& s) const
350+ TextBlock Page::replaceTextMacros (bool isHeader, const String& s) const
330351{
331- String d;
352+ // If the string in question consists solely of a "styled macro" (i.e. page number or copyright), we can set the default
353+ // format to the associated styling. We'll use this later to prevent the creation of unneccessary fragments...
354+ CharFormat defaultFormat = formatForMacro (s);
355+ if (defaultFormat == CharFormat ()) {
356+ // The string isn't just a styled macro, use header/footer styling...
357+ defaultFormat.setStyle (style ().styleV (isHeader ? Sid::headerFontStyle : Sid::footerFontStyle).value <FontStyle>());
358+ defaultFormat.setFontSize (style ().styleD (isHeader ? Sid::headerFontSize : Sid::footerFontSize));
359+ defaultFormat.setFontFamily (style ().styleSt (isHeader ? Sid::headerFontFace : Sid::footerFontFace));
360+ }
361+
362+ std::list<TextFragment> fragments (1 );
332363 for (size_t i = 0 , n = s.size (); i < n; ++i) {
364+ fragments.back ().format = defaultFormat;
333365 Char c = s.at (i);
334366 if (c == ' $' && (i < (n - 1 ))) {
335367 Char nc = s.at (i + 1 );
@@ -348,52 +380,66 @@ String Page::replaceTextMacros(const String& s) const
348380 {
349381 int no = static_cast <int >(m_no) + 1 + score ()->pageNumberOffset ();
350382 if (no > 0 ) {
351- d += String::number (no);
383+ const String pageNumberString = String::number (no);
384+ const CharFormat pageNumberFormat = formatForMacro (String (' $' + nc));
385+ // If the default format equals the format for this macro, we don't need to create a new fragment...
386+ if (defaultFormat == pageNumberFormat) {
387+ fragments.back ().text += pageNumberString;
388+ break ;
389+ }
390+ TextFragment pageNumberFragment (pageNumberString);
391+ pageNumberFragment.format = pageNumberFormat;
392+ fragments.emplace_back (pageNumberFragment);
393+ fragments.emplace_back (TextFragment ()); // Start next fragment
352394 }
353395 }
354396 break ;
355397 case ' n' :
356- d += String::number (score ()->npages () + score ()->pageNumberOffset ());
398+ fragments. back (). text += String::number (score ()->npages () + score ()->pageNumberOffset ());
357399 break ;
358400 case ' i' : // not on first page
359401 if (!m_no) {
360402 break ;
361403 }
362404 // FALLTHROUGH
363405 case ' I' :
364- d += score ()->metaTag (u" partName" ).toXmlEscaped ();
406+ fragments. back (). text += score ()->metaTag (u" partName" ).toXmlEscaped ();
365407 break ;
366408 case ' f' :
367- d += masterScore ()->fileInfo ()->fileName (false ).toString ().toXmlEscaped ();
409+ fragments. back (). text += masterScore ()->fileInfo ()->fileName (false ).toString ().toXmlEscaped ();
368410 break ;
369411 case ' F' :
370- d += masterScore ()->fileInfo ()->path ().toString ().toXmlEscaped ();
412+ fragments. back (). text += masterScore ()->fileInfo ()->path ().toString ().toXmlEscaped ();
371413 break ;
372414 case ' d' :
373- d += muse::Date::currentDate ().toString (muse::DateFormat::ISODate);
415+ fragments. back (). text += muse::Date::currentDate ().toString (muse::DateFormat::ISODate);
374416 break ;
375417 case ' D' :
376418 {
377419 String creationDate = score ()->metaTag (u" creationDate" );
378420 if (creationDate.isEmpty ()) {
379- d += masterScore ()->fileInfo ()->birthTime ().date ().toString (muse::DateFormat::ISODate);
421+ fragments.back ().text += masterScore ()->fileInfo ()->birthTime ().date ().toString (
422+ muse::DateFormat::ISODate);
380423 } else {
381- d += muse::Date::fromStringISOFormat (creationDate).toString (muse::DateFormat::ISODate);
424+ fragments.back ().text += muse::Date::fromStringISOFormat (creationDate).toString (
425+ muse::DateFormat::ISODate);
382426 }
383427 }
384428 break ;
385429 case ' m' :
386430 if (score ()->dirty () || !masterScore ()->saved ()) {
387- d += muse::Time::currentTime ().toString (muse::DateFormat::ISODate);
431+ fragments. back (). text += muse::Time::currentTime ().toString (muse::DateFormat::ISODate);
388432 } else {
389- d += masterScore ()->fileInfo ()->lastModified ().time ().toString (muse::DateFormat::ISODate);
433+ fragments.back ().text += masterScore ()->fileInfo ()->lastModified ().time ().toString (
434+ muse::DateFormat::ISODate);
390435 }
391436 break ;
392437 case ' M' :
393438 if (score ()->dirty () || !masterScore ()->saved ()) {
394- d += muse::Date::currentDate ().toString (muse::DateFormat::ISODate);
439+ fragments. back (). text += muse::Date::currentDate ().toString (muse::DateFormat::ISODate);
395440 } else {
396- d += masterScore ()->fileInfo ()->lastModified ().date ().toString (muse::DateFormat::ISODate);
441+ fragments.back ().text += masterScore ()->fileInfo ()->lastModified ().date ().toString (
442+ muse::DateFormat::ISODate);
397443 }
398444 break ;
399445 case ' C' : // only on first page
@@ -402,29 +448,41 @@ String Page::replaceTextMacros(const String& s) const
402448 }
403449 // FALLTHROUGH
404450 case ' c' :
405- d += score ()->metaTag (u" copyright" ).toXmlEscaped ();
406- break ;
451+ {
452+ const String copyrightString = score ()->metaTag (u" copyright" ).toXmlEscaped ();
453+ const CharFormat copyrightFormat = formatForMacro (String (' $' + nc));
454+ // If the default format equals the format for this macro, we don't need to create a new fragment...
455+ if (defaultFormat == copyrightFormat) {
456+ fragments.back ().text += copyrightString;
457+ break ;
458+ }
459+ TextFragment copyrightFragment (copyrightString);
460+ copyrightFragment.format = copyrightFormat;
461+ fragments.emplace_back (copyrightFragment);
462+ fragments.emplace_back (TextFragment ()); // Start next fragment
463+ }
464+ break ;
407465 case ' v' :
408466 if (score ()->dirty ()) {
409- d += score ()->appVersion ();
467+ fragments. back (). text += score ()->appVersion ();
410468 } else {
411- d += score ()->mscoreVersion ();
469+ fragments. back (). text += score ()->mscoreVersion ();
412470 }
413471 break ;
414472 case ' r' :
415473 if (score ()->dirty ()) {
416- d += revision;
474+ fragments. back (). text += revision;
417475 } else {
418476 int rev = score ()->mscoreRevision ();
419477 if (rev > 99999 ) { // MuseScore 1.3 is decimal 5702, 2.0 and later uses a 7-digit hex SHA
420- d += String::number (rev, 16 );
478+ fragments. back (). text += String::number (rev, 16 );
421479 } else {
422- d += String::number (rev, 10 );
480+ fragments. back (). text += String::number (rev, 10 );
423481 }
424482 }
425483 break ;
426484 case ' $' :
427- d += ' $' ;
485+ fragments. back (). text += ' $' ;
428486 break ;
429487 case ' :' :
430488 {
@@ -437,24 +495,46 @@ String Page::replaceTextMacros(const String& s) const
437495 tag += s.at (k);
438496 }
439497 if (k != n) { // found ':' ?
440- d += score ()->metaTag (tag).toXmlEscaped ();
498+ fragments. back (). text += score ()->metaTag (tag).toXmlEscaped ();
441499 i = k - 1 ;
442500 }
443501 }
444502 break ;
445503 default :
446- d += ' $' ;
447- d += nc;
504+ fragments. back (). text += ' $' ;
505+ fragments. back (). text += nc;
448506 break ;
449507 }
450508 ++i;
451509 } else if (c == ' &' ) {
452- d += u" &" ;
510+ fragments. back (). text += u" &" ;
453511 } else {
454- d += c;
512+ fragments. back (). text += c;
455513 }
456514 }
457- return d;
515+
516+ TextBlock tb;
517+ tb.fragments () = fragments;
518+ return tb;
519+ }
520+
521+ // ---------------------------------------------------------
522+ // formatForMacro
523+ // ---------------------------------------------------------
524+
525+ const CharFormat Page::formatForMacro (const String& s) const
526+ {
527+ CharFormat format;
528+ if (s == " $c" || s == " $C" ) {
529+ format.setStyle (style ().styleV (Sid::copyrightFontStyle).value <FontStyle>());
530+ format.setFontSize (style ().styleD (Sid::copyrightFontSize));
531+ format.setFontFamily (style ().styleSt (Sid::copyrightFontFace));
532+ } else if (s == " $p" || s == " $P" || s == " $n" || s == " $N" ) {
533+ format.setStyle (style ().styleV (Sid::pageNumberFontStyle).value <FontStyle>());
534+ format.setFontSize (style ().styleD (Sid::pageNumberFontSize));
535+ format.setFontFamily (style ().styleSt (Sid::pageNumberFontFace));
536+ }
537+ return format;
458538}
459539
460540// ---------------------------------------------------------
0 commit comments