LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - headerfooterlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 67.7 % 322 218
Test Date: 2026-11-05 04:07:02

            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) 2026 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              : #include "headerfooterlayout.h"
      24              : 
      25              : #include "dom/factory.h"
      26              : #include "dom/masterscore.h"
      27              : #include "dom/mscore.h"
      28              : #include "dom/page.h"
      29              : #include "dom/text.h"
      30              : 
      31              : #include "ifileinfoprovider.h"
      32              : #include "tlayout.h"
      33              : 
      34              : using namespace mu::engraving;
      35              : using namespace mu::engraving::rendering::score;
      36              : 
      37         4131 : void HeaderFooterLayout::layoutHeaderFooter(LayoutContext& ctx, Page* page)
      38              : {
      39         4131 :     const bool isPageMode = ctx.conf().isMode(LayoutMode::PAGE) || ctx.conf().isMode(LayoutMode::FLOAT);
      40         4131 :     if (!isPageMode) {
      41            0 :         return;
      42              :     }
      43              : 
      44         4131 :     const int n = page->getDisplayPageNumber();
      45              : 
      46         4131 :     if (mustShowHeadersForPage(ctx, page)) {
      47          147 :         const bool odd = (n & 1) || !ctx.conf().styleB(Sid::headerOddEven);
      48          147 :         createUpdateHeaderText(ctx, page, 0, ctx.conf().styleSt(odd ? Sid::oddHeaderL : Sid::evenHeaderL));
      49          147 :         createUpdateHeaderText(ctx, page, 1, ctx.conf().styleSt(odd ? Sid::oddHeaderC : Sid::evenHeaderC));
      50          147 :         createUpdateHeaderText(ctx, page, 2, ctx.conf().styleSt(odd ? Sid::oddHeaderR : Sid::evenHeaderR));
      51              :     } else {
      52        15936 :         for (int area = 0; area < MAX_HEADERS; ++area) {
      53        11952 :             removeHeaderText(page, area);
      54              :         }
      55              :     }
      56              : 
      57         4131 :     if (mustShowFootersForPage(ctx, page)) {
      58         4126 :         const bool odd = (n & 1) || !ctx.conf().styleB(Sid::footerOddEven);
      59         4126 :         createUpdateFooterText(ctx, page, 0, ctx.conf().styleSt(odd ? Sid::oddFooterL : Sid::evenFooterL));
      60         4126 :         createUpdateFooterText(ctx, page, 1, ctx.conf().styleSt(odd ? Sid::oddFooterC : Sid::evenFooterC));
      61         4126 :         createUpdateFooterText(ctx, page, 2, ctx.conf().styleSt(odd ? Sid::oddFooterR : Sid::evenFooterR));
      62              :     } else {
      63           20 :         for (int area = 0; area < MAX_FOOTERS; ++area) {
      64           15 :             removeFooterText(page, area);
      65              :         }
      66              :     }
      67              : }
      68              : 
      69          441 : void HeaderFooterLayout::createUpdateHeaderText(LayoutContext& ctx, Page* page, int area, const String& s)
      70              : {
      71          441 :     if (s.empty()) {
      72          270 :         removeHeaderText(page, area);
      73          270 :         return;
      74              :     }
      75              : 
      76          171 :     Text* text = page->headerText(area);
      77          171 :     if (!text) {
      78          145 :         text = Factory::createText(page, TextStyleType::HEADER);
      79          145 :         text->setFlag(ElementFlag::MOVABLE, false);
      80          145 :         text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
      81          145 :         text->setLayoutToParentWidth(true);
      82          145 :         page->setHeaderText(area, text);
      83              : 
      84          145 :         switch (area) {
      85           96 :         case 0:
      86           96 :             text->setAlign({ AlignH::LEFT, AlignV::TOP });
      87           96 :             text->setPosition(AlignH::LEFT);
      88           96 :             break;
      89            6 :         case 1:
      90            6 :             text->setAlign({ AlignH::HCENTER, AlignV::TOP });
      91            6 :             text->setPosition(AlignH::HCENTER);
      92            6 :             break;
      93           43 :         case 2:
      94           43 :             text->setAlign({ AlignH::RIGHT, AlignV::TOP });
      95           43 :             text->setPosition(AlignH::RIGHT);
      96           43 :             break;
      97            0 :         default:
      98            0 :             break;
      99              :         }
     100              :     }
     101              : 
     102          171 :     if (!updateHeaderFooterText(ctx, page, text, s)) {
     103           19 :         removeHeaderText(page, area);
     104              :     }
     105              : }
     106              : 
     107        12378 : void HeaderFooterLayout::createUpdateFooterText(LayoutContext& ctx, Page* page, int area, const String& s)
     108              : {
     109        12378 :     if (s.empty()) {
     110         7261 :         removeFooterText(page, area);
     111         7261 :         return;
     112              :     }
     113              : 
     114         5117 :     Text* text = page->footerText(area);
     115         5117 :     if (!text) {
     116         5117 :         text = Factory::createText(page, TextStyleType::FOOTER);
     117         5117 :         text->setFlag(ElementFlag::MOVABLE, false);
     118         5117 :         text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
     119         5117 :         text->setLayoutToParentWidth(true);
     120         5117 :         page->setFooterText(area, text);
     121              : 
     122         5117 :         switch (area) {
     123           27 :         case 0:
     124           27 :             text->setAlign({ AlignH::LEFT, AlignV::BOTTOM });
     125           27 :             text->setPosition(AlignH::LEFT);
     126           27 :             break;
     127         4126 :         case 1:
     128         4126 :             text->setAlign({ AlignH::HCENTER, AlignV::BOTTOM });
     129         4126 :             text->setPosition(AlignH::HCENTER);
     130         4126 :             break;
     131          964 :         case 2:
     132          964 :             text->setAlign({ AlignH::RIGHT, AlignV::BOTTOM });
     133          964 :             text->setPosition(AlignH::RIGHT);
     134          964 :             break;
     135            0 :         default:
     136            0 :             break;
     137              :         }
     138              :     }
     139              : 
     140         5117 :     if (!updateHeaderFooterText(ctx, page, text, s)) {
     141         5029 :         removeFooterText(page, area);
     142              :     }
     143              : }
     144              : 
     145         5288 : bool HeaderFooterLayout::updateHeaderFooterText(LayoutContext& ctx, Page* page, Text* text, const String& s)
     146              : {
     147              :     // Hack: we can't use toXmlEscaped on the entire string because this would erase any manual XML
     148              :     // formatting, but we do want to be able to use a plain '&' in favour of XML character entities ...
     149              :     // ... also, opening less-than characters are escaped if they are followed by a space or a digit,
     150              :     // to avoid confusion with XML tags, but not if they are followed by a letter
     151         5288 :     String escaped;
     152        30589 :     for (size_t i = 0, n = s.size(); i < n; ++i) {
     153        25301 :         const Char c = s.at(i);
     154        25301 :         if (c == '&') {
     155            0 :             escaped += u"&amp;";
     156            0 :             continue;
     157        25301 :         } else if (c == '<' && (i + 1 < n) && (s.at(i + 1).isSpace() || s.at(i + 1).isDigit())) {
     158            0 :             escaped += u"&lt;";
     159            0 :             continue;
     160              :         }
     161        25301 :         escaped += c;
     162              :     }
     163              : 
     164              :     // first formatting pass - apply TextStyleType formatting and any manual XML formatting
     165         5288 :     text->setXmlText(escaped);
     166         5288 :     text->createBlocks();
     167              : 
     168              :     // second formatting pass - replace macros and apply their unique formatting (if any)
     169         5288 :     int emptyBlocks = 0;
     170         5288 :     std::vector<TextBlock> newBlocks;
     171        10612 :     for (const TextBlock& oldBlock : text->ldata()->blocks) {
     172         5324 :         Text* dummyText = Factory::createText(ctx.dom().dummyParent(), text->textStyleType());
     173        10648 :         dummyText->mutldata()->blocks = { replaceTextMacros(ctx, page, oldBlock) };
     174         5324 :         dummyText->genText();
     175         5324 :         dummyText->createBlocks();
     176         5324 :         if (dummyText->xmlText().isEmpty()) {
     177         5084 :             emptyBlocks++; // count empty blocks to remove them later if there is only 1 empty block
     178              :         }
     179         5324 :         newBlocks.insert(newBlocks.end(), dummyText->ldata()->blocks.cbegin(), dummyText->ldata()->blocks.cend());
     180         5324 :         delete dummyText;
     181              :     }
     182              : 
     183         5288 :     if (newBlocks.empty() || (newBlocks.size() == 1 && emptyBlocks == 1)) {
     184              :         // If there is no block, or the only block is empty, tell the caller that the text should be removed.
     185              :         // This way, empty lines are preserved if and only if there also is some text anywhere in the header/footer.
     186         5048 :         return false;
     187              :     }
     188              : 
     189          240 :     text->mutldata()->blocks = newBlocks;
     190          240 :     TLayout::layoutText(text, text->mutldata());
     191              : 
     192          240 :     return true;
     193        10612 : }
     194              : 
     195        12241 : void HeaderFooterLayout::removeHeaderText(Page* page, int area)
     196              : {
     197        12241 :     if (Text* t = page->headerText(area)) {
     198           19 :         page->setHeaderText(area, nullptr);
     199           19 :         delete t;
     200              :     }
     201        12241 : }
     202              : 
     203        12305 : void HeaderFooterLayout::removeFooterText(Page* page, int area)
     204              : {
     205        12305 :     if (Text* t = page->footerText(area)) {
     206         5029 :         page->setFooterText(area, nullptr);
     207         5029 :         delete t;
     208              :     }
     209        12305 : }
     210              : 
     211              : //---------------------------------------------------------
     212              : //   replaceTextMacros
     213              : //   (keep in sync with toolTipHeaderFooter in EditStyle::EditStyle())
     214              : //    $p          - page number, except on first page
     215              : //    $P          - page number, on all pages
     216              : //    $N          - page number, if there is more than one
     217              : //    $n          - number of pages
     218              : //    $i          - part name, except on first page
     219              : //    $I          - part name, on all pages
     220              : //    $f          - file name
     221              : //    $F          - file path+name
     222              : //    $d          - current date
     223              : //    $D          - creation date
     224              : //    $m          - last modification time
     225              : //    $M          - last modification date
     226              : //    $C          - copyright, on first page only
     227              : //    $c          - copyright, on all pages
     228              : //    $v          - MuseScore version the score was last saved with
     229              : //    $r          - MuseScore revision the score was last saved with
     230              : //    $$          - the $ sign itself
     231              : //    $:tag:      - any metadata tag
     232              : //
     233              : //       tags always defined (see Score::init())):
     234              : //       copyright
     235              : //       creationDate
     236              : //       movementNumber
     237              : //       movementTitle
     238              : //       platform
     239              : //       source
     240              : //       workNumber
     241              : //       workTitle
     242              : //---------------------------------------------------------
     243              : 
     244         5324 : TextBlock HeaderFooterLayout::replaceTextMacros(LayoutContext& ctx, const Page* page, const TextBlock& tb)
     245              : {
     246         5324 :     std::list<TextFragment> newFragments;
     247        10648 :     for (const TextFragment& tf: tb.fragments()) {
     248         5324 :         const CharFormat defaultFormat = tf.format;
     249         5324 :         const String& s = tf.text;
     250              : 
     251              :         // if this is the first fragment, or the current fragment has a different format, we need to start a new fragment
     252         5324 :         if (newFragments.size() == 0 || newFragments.back().format != defaultFormat) {
     253         5324 :             newFragments.emplace_back(TextFragment());
     254         5324 :             newFragments.back().format = defaultFormat;
     255              :         }
     256              : 
     257        10898 :         for (size_t i = 0, n = s.size(); i < n; ++i) {
     258         5574 :             Char c = s.at(i);
     259         5574 :             if (c == '$' && (i < (n - 1))) {
     260         5286 :                 Char nc = s.at(i + 1);
     261         5286 :                 switch (nc.toAscii()) {
     262         1131 :                 case 'p': // not on first page of the project nor page number 1
     263         1131 :                     if (!page->pageNumber() || page->getDisplayPageNumber() <= 1) {
     264          970 :                         break;
     265              :                     }
     266              :                     [[fallthrough]];
     267              :                 case 'N': // on page 1 only if there are multiple pages
     268          177 :                     if ((static_cast<int>(page->score()->npages()) + page->score()->pageNumberOffset()) <= 1) {
     269            6 :                         ctx.mutState().setMustRecomputeHeadersFooters(true);
     270            6 :                         break;
     271              :                     }
     272              :                     [[fallthrough]];
     273              :                 case 'P': // on all pages
     274              :                 {
     275          184 :                     const int no = page->getDisplayPageNumber();
     276          184 :                     if (no > 0) {
     277          184 :                         const String pageNumberString = String::number(no);
     278          184 :                         const CharFormat pageNumberFormat = formatForMacro(ctx, String('$' + nc));
     279          184 :                         appendFormattedString(newFragments, pageNumberString, defaultFormat, pageNumberFormat);
     280          184 :                     }
     281              :                 }
     282          184 :                 break;
     283            0 :                 case 'n':
     284              :                 {
     285            0 :                     ctx.mutState().setMustRecomputeHeadersFooters(true);
     286            0 :                     const int no = static_cast<int>(page->score()->npages()) + page->score()->pageNumberOffset();
     287            0 :                     const String numberOfPagesString = String::number(no);
     288            0 :                     const CharFormat pageNumberFormat = formatForMacro(ctx, String('$' + nc));
     289            0 :                     appendFormattedString(newFragments, numberOfPagesString, defaultFormat, pageNumberFormat);
     290            0 :                 }
     291            0 :                 break;
     292            0 :                 case 'i': // not on first page
     293            0 :                     if (!page->pageNumber()) {
     294            0 :                         break;
     295              :                     }
     296              :                     [[fallthrough]];
     297              :                 case 'I':
     298            0 :                     newFragments.back().text += page->score()->metaTag(u"partName");
     299            0 :                     break;
     300            0 :                 case 'f':
     301            0 :                     newFragments.back().text += page->score()->masterScore()->fileInfo()->fileName(false).toString();
     302            0 :                     break;
     303            0 :                 case 'F':
     304            0 :                     newFragments.back().text += page->score()->masterScore()->fileInfo()->path().toString();
     305            0 :                     break;
     306            0 :                 case 'd':
     307            0 :                     newFragments.back().text += muse::Date::currentDate().toString(muse::DateFormat::ISODate);
     308            0 :                     break;
     309            0 :                 case 'D':
     310              :                 {
     311            0 :                     String creationDate = page->score()->metaTag(u"creationDate");
     312            0 :                     if (creationDate.isEmpty()) {
     313            0 :                         newFragments.back().text += page->score()->masterScore()->fileInfo()->birthTime().date().toString(
     314            0 :                             muse::DateFormat::ISODate);
     315              :                     } else {
     316            0 :                         newFragments.back().text += muse::Date::fromStringISOFormat(creationDate).toString(
     317            0 :                             muse::DateFormat::ISODate);
     318              :                     }
     319            0 :                 }
     320            0 :                 break;
     321            0 :                 case 'm': {
     322            0 :                     IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo();
     323            0 :                     if (fileInfo->isNewlyCreated()) {
     324            0 :                         newFragments.back().text += String(u"HH:mm:ss");
     325              :                     } else {
     326            0 :                         newFragments.back().text += fileInfo->lastModified().time().toString(muse::DateFormat::ISODate);
     327              :                     }
     328            0 :                 }
     329            0 :                 break;
     330            0 :                 case 'M': {
     331            0 :                     IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo();
     332            0 :                     if (fileInfo->isNewlyCreated()) {
     333            0 :                         newFragments.back().text += String(u"YYYY-MM-DD");
     334              :                     } else {
     335            0 :                         newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate);
     336              :                     }
     337            0 :                 }
     338            0 :                 break;
     339         3071 :                 case 'C': // only on first page
     340         3071 :                     if (page->pageNumber()) {
     341          130 :                         break;
     342              :                     }
     343              :                     [[fallthrough]];
     344              :                 case 'c':
     345              :                 {
     346         2941 :                     const String copyrightString = page->score()->metaTag(u"copyright");
     347         2941 :                     const CharFormat copyrightFormat = formatForMacro(ctx, String('$' + nc));
     348         2941 :                     appendFormattedString(newFragments, copyrightString, defaultFormat, copyrightFormat);
     349         2941 :                 }
     350         2941 :                 break;
     351            0 :                 case 'v':
     352            0 :                     if (page->score()->dirty()) {
     353            0 :                         newFragments.back().text += application()->version().toString();
     354              :                     } else {
     355            0 :                         newFragments.back().text += page->score()->mscoreVersion();
     356              :                     }
     357            0 :                     break;
     358            0 :                 case 'r':
     359            0 :                     if (page->score()->dirty()) {
     360            0 :                         newFragments.back().text += application()->revision();
     361              :                     } else {
     362            0 :                         int rev = page->score()->mscoreRevision();
     363            0 :                         if (rev > 0 && rev <= 5709) { // MuseScore 1.x and earlier used decimal numbers, referring to a commit on SourceForge.net, the largest being 5709
     364            0 :                             newFragments.back().text += String::number(rev, 10);
     365            0 :                         } else if (rev > 0xffffff) { // MuseScore 2.0 and later use a >= 7-digit hex SHA, referring to a commit on GitHub.com
     366            0 :                             newFragments.back().text += String::number(rev, 16);
     367              :                         } else { // unknown, like in a self-built development version, or in a very old version of MuseScore before the revision number was tracked
     368            0 :                             newFragments.back().text += String::number(rev, 10); // or "" or "Unknown"?
     369              :                         }
     370              :                     }
     371            0 :                     break;
     372            0 :                 case '$':
     373            0 :                     newFragments.back().text += '$';
     374            0 :                     break;
     375         1055 :                 case ':':
     376              :                 {
     377         1055 :                     String tag;
     378         1055 :                     size_t k = i + 2;
     379        10550 :                     for (; k < n; ++k) {
     380        10550 :                         if (s.at(k) == u':') {
     381         1055 :                             break;
     382              :                         }
     383         9495 :                         tag += s.at(k);
     384              :                     }
     385         1055 :                     if (k != n) {      // found ':' ?
     386         1055 :                         if (tag == u"copyright") {
     387         1055 :                             const String copyrightString = page->score()->metaTag(tag);
     388         1055 :                             const CharFormat copyrightFormat = formatForMacro(ctx, String(u"$:" + tag + u":"));
     389         1055 :                             appendFormattedString(newFragments, copyrightString, defaultFormat, copyrightFormat);
     390         1055 :                         } else {
     391            0 :                             newFragments.back().text += page->score()->metaTag(tag);
     392              :                         }
     393         1055 :                         i = k - 1;
     394              :                     }
     395         1055 :                 }
     396         1055 :                 break;
     397            0 :                 default:
     398            0 :                     newFragments.back().text += '$';
     399            0 :                     newFragments.back().text += nc;
     400            0 :                     break;
     401              :                 }
     402         5286 :                 ++i;
     403              :             } else {
     404          288 :                 newFragments.back().text += c;
     405              :             }
     406              :         }
     407         5324 :     }
     408              : 
     409         5324 :     TextBlock newBlock;
     410         5324 :     newBlock.fragments() = newFragments;
     411         5324 :     return newBlock;
     412         5324 : }
     413              : 
     414         4180 : CharFormat HeaderFooterLayout::formatForMacro(const LayoutContext& ctx, const String& macro)
     415              : {
     416         4180 :     CharFormat format;
     417         4180 :     if (macro == "$c" || macro == "$C" || macro == "$:copyright:") {
     418         3996 :         format.setStyle(ctx.conf().styleV(Sid::copyrightFontStyle).value<FontStyle>());
     419         3996 :         format.setFontSize(ctx.conf().styleD(Sid::copyrightFontSize));
     420         3996 :         format.setFontFamily(ctx.conf().styleSt(Sid::copyrightFontFace));
     421          184 :     } else if (macro == "$p" || macro == "$P" || macro == "$n" || macro == "$N") {
     422          184 :         format.setStyle(ctx.conf().styleV(Sid::pageNumberFontStyle).value<FontStyle>());
     423          184 :         format.setFontSize(ctx.conf().styleD(Sid::pageNumberFontSize));
     424          184 :         format.setFontFamily(ctx.conf().styleSt(Sid::pageNumberFontFace));
     425              :     }
     426         4180 :     return format;
     427            0 : }
     428              : 
     429         4180 : void HeaderFooterLayout::appendFormattedString(std::list<TextFragment>& fragments, const String& string, const CharFormat& defaultFormat,
     430              :                                                const CharFormat& newFormat)
     431              : {
     432              :     // If the default format equals the format for this macro, we don't need to create a new fragment...
     433         4180 :     if (defaultFormat == newFormat) {
     434         4027 :         fragments.back().text += string;
     435         4027 :         return;
     436              :     }
     437          153 :     TextFragment newFragment(string);
     438          153 :     newFragment.format = newFormat;
     439          153 :     fragments.emplace_back(newFragment);
     440          153 :     fragments.emplace_back(TextFragment());    // Start next fragment
     441          153 :     fragments.back().format = defaultFormat;   // reset to default for next fragment
     442          153 : }
     443              : 
     444         4125 : double HeaderFooterLayout::headerExtension(const LayoutContext& ctx, const Page* page)
     445              : {
     446         4125 :     const bool isPageMode = ctx.conf().isMode(LayoutMode::PAGE) || ctx.conf().isMode(LayoutMode::FLOAT);
     447         4125 :     if (!isPageMode) {
     448            0 :         return 0.0;
     449              :     }
     450              : 
     451         4125 :     if (mustShowHeadersForPage(ctx, page)) {
     452          141 :         double maxHeight = 0.0;
     453          564 :         for (int area = 0; area < MAX_HEADERS; ++area) {
     454          423 :             if (Text* text = page->headerText(area)) {
     455          140 :                 double h = text->height();
     456          140 :                 if (h > maxHeight) {
     457          136 :                     maxHeight = h;
     458              :                 }
     459              :             }
     460              :         }
     461          141 :         const double offset = ctx.conf().styleV(Sid::headerOffset).value<PointF>().y() * DPMM;
     462          141 :         return std::max(0.0, maxHeight - offset);
     463              :     }
     464              : 
     465         3984 :     return 0.0;
     466              : }
     467              : 
     468         4125 : double HeaderFooterLayout::footerExtension(const LayoutContext& ctx, const Page* page)
     469              : {
     470         4125 :     const bool isPageMode = ctx.conf().isMode(LayoutMode::PAGE) || ctx.conf().isMode(LayoutMode::FLOAT);
     471         4125 :     if (!isPageMode) {
     472            0 :         return 0.0;
     473              :     }
     474              : 
     475         4125 :     if (mustShowFootersForPage(ctx, page)) {
     476         4120 :         double maxHeight = 0.0;
     477        16480 :         for (int area = 0; area < MAX_FOOTERS; ++area) {
     478        12360 :             if (Text* text = page->footerText(area)) {
     479           88 :                 double h = text->height();
     480           88 :                 if (h > maxHeight) {
     481           80 :                     maxHeight = h;
     482              :                 }
     483              :             }
     484              :         }
     485         4120 :         const double offset = ctx.conf().styleV(Sid::footerOffset).value<PointF>().y() * DPMM;
     486         4120 :         return std::max(0.0, maxHeight - offset);
     487              :     }
     488            5 :     return 0.0;
     489              : }
     490              : 
     491            0 : bool HeaderFooterLayout::containsTimestampMacros(const String& text)
     492              : {
     493            0 :     return text.contains(u"$d")
     494            0 :            || text.contains(u"$D")
     495            0 :            || text.contains(u"$m")
     496            0 :            || text.contains(u"$M");
     497              : }
     498              : 
     499            0 : bool HeaderFooterLayout::scoreHasTimestampHeadersFooters(const Score* score)
     500              : {
     501            0 :     const MStyle& style = score->style();
     502              : 
     503            0 :     if (style.styleB(Sid::showHeader)) {
     504            0 :         if (containsTimestampMacros(style.styleSt(Sid::oddHeaderL))
     505            0 :             || containsTimestampMacros(style.styleSt(Sid::oddHeaderC))
     506            0 :             || containsTimestampMacros(style.styleSt(Sid::oddHeaderR))
     507            0 :             || containsTimestampMacros(style.styleSt(Sid::evenHeaderL))
     508            0 :             || containsTimestampMacros(style.styleSt(Sid::evenHeaderC))
     509            0 :             || containsTimestampMacros(style.styleSt(Sid::evenHeaderR))) {
     510            0 :             return true;
     511              :         }
     512              :     }
     513              : 
     514            0 :     if (style.styleB(Sid::showFooter)) {
     515            0 :         if (containsTimestampMacros(style.styleSt(Sid::oddFooterL))
     516            0 :             || containsTimestampMacros(style.styleSt(Sid::oddFooterC))
     517            0 :             || containsTimestampMacros(style.styleSt(Sid::oddFooterR))
     518            0 :             || containsTimestampMacros(style.styleSt(Sid::evenFooterL))
     519            0 :             || containsTimestampMacros(style.styleSt(Sid::evenFooterC))
     520            0 :             || containsTimestampMacros(style.styleSt(Sid::evenFooterR))) {
     521            0 :             return true;
     522              :         }
     523              :     }
     524              : 
     525            0 :     return false;
     526              : }
     527              : 
     528         8256 : bool HeaderFooterLayout::mustShowHeadersForPage(const LayoutContext& ctx, const Page* page)
     529              : {
     530         8256 :     return ctx.conf().styleB(Sid::showHeader)
     531        14308 :            && ((page->pageNumber() && page->getDisplayPageNumber() != 1)
     532        14308 :                || ctx.conf().styleB(Sid::headerFirstPage));
     533              : }
     534              : 
     535         8256 : bool HeaderFooterLayout::mustShowFootersForPage(const LayoutContext& ctx, const Page* page)
     536              : {
     537         8256 :     return ctx.conf().styleB(Sid::showFooter)
     538        16178 :            && ((page->pageNumber() && page->getDisplayPageNumber() != 1)
     539        16178 :                || ctx.conf().styleB(Sid::footerFirstPage));
     540              : }
        

Generated by: LCOV version 2.0-1