diff --git a/src/engraving/layout/layoutpage.cpp b/src/engraving/layout/layoutpage.cpp index 14c83eb1d9e8..ff0c6c2cac27 100644 --- a/src/engraving/layout/layoutpage.cpp +++ b/src/engraving/layout/layoutpage.cpp @@ -102,8 +102,11 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx) { const qreal slb = ctx.score()->styleP(Sid::staffLowerBorder); bool breakPages = ctx.score()->layoutMode() != LayoutMode::SYSTEM; - qreal ey = ctx.page->height() - ctx.page->bm(); - qreal y = 0.0; + qreal footerExtension = ctx.page->footerExtension(); + qreal headerExtension = ctx.page->headerExtension(); + qreal headerFooterPadding = ctx.score()->styleP(Sid::staffHeaderFooterPadding); + qreal endY = ctx.page->height() - ctx.page->bm(); + qreal y = 0.0; System* nextSystem = 0; int systemIdx = -1; @@ -137,7 +140,9 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx) } else { // this is the first system on page if (ctx.curSystem->vbox()) { - distance = 0.0; + // if the header exists and there is a frame, move the frame downwards + // to avoid collisions + distance = headerExtension ? headerExtension + headerFooterPadding : 0.0; } else { distance = ctx.score()->styleP(Sid::staffUpperBorder); bool fixedDistance = false; @@ -157,7 +162,12 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx) } } if (!fixedDistance) { - distance = qMax(distance, ctx.curSystem->minTop()); + qreal top = ctx.curSystem->minTop(); + // ensure it doesn't collide with header + if (headerExtension > 0.0) { + top += headerExtension + headerFooterPadding; + } + distance = qMax(distance, top); } } } @@ -202,16 +212,29 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx) Box* vbox = ctx.curSystem->vbox(); if (vbox) { dist += vbox->bottomGap(); + if (footerExtension > 0) { + dist += footerExtension; + } } else if (!ctx.prevSystem->hasFixedDownDistance()) { qreal margin = qMax(ctx.curSystem->minBottom(), ctx.curSystem->spacerDistance(false)); + // ensure it doesn't collide with footer + if (footerExtension > 0) { + margin += footerExtension + headerFooterPadding; + } dist += qMax(margin, slb); } - breakPage = (y + dist) >= ey && breakPages; + breakPage = (y + dist) >= endY && breakPages; } if (breakPage) { qreal dist = qMax(ctx.prevSystem->minBottom(), ctx.prevSystem->spacerDistance(false)); + qreal footerPadding = 0.0; + // ensure it doesn't collide with footer + if (footerExtension > 0) { + footerPadding = footerExtension + headerFooterPadding; + dist += footerPadding; + } dist = qMax(dist, slb); - layoutPage(ctx, ctx.page, ey - (y + dist)); + layoutPage(ctx, ctx.page, endY - (y + dist), footerPadding); // if we collected a system we cannot fit onto this page, // we need to collect next page in order to correctly set system positions if (collected) { @@ -307,7 +330,7 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx) // systems. //--------------------------------------------------------- -void LayoutPage::layoutPage(const LayoutContext& ctx, Page* page, qreal restHeight) +void LayoutPage::layoutPage(const LayoutContext& ctx, Page* page, qreal restHeight, qreal footerPadding) { if (restHeight < 0.0) { qDebug("restHeight < 0.0: %f\n", restHeight); @@ -349,7 +372,7 @@ void LayoutPage::layoutPage(const LayoutContext& ctx, Page* page, qreal restHeig system->move(PointF(0.0, y)); } } else if ((score->layoutMode() != LayoutMode::SYSTEM) && score->enableVerticalSpread()) { - distributeStaves(ctx, page); + distributeStaves(ctx, page, footerPadding); } // system dividers @@ -454,7 +477,7 @@ void LayoutPage::checkDivider(const LayoutContext& ctx, bool left, System* s, qr } } -void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page) +void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page, qreal footerPadding) { Score* score = ctx.score(); VerticalGapDataList vgdl; @@ -540,18 +563,18 @@ void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page) } --ngaps; - qreal spaceLeft { page->height() - page->bm() - score->styleP(Sid::staffLowerBorder) - yBottom }; + qreal spaceRemaining { page->height() - page->bm() - footerPadding - score->styleP(Sid::staffLowerBorder) - yBottom }; if (nextSpacer) { - spaceLeft -= qMax(0.0, nextSpacer->gap() - spacerOffset - score->styleP(Sid::staffLowerBorder)); + spaceRemaining -= qMax(0.0, nextSpacer->gap() - spacerOffset - score->styleP(Sid::staffLowerBorder)); } - if (spaceLeft <= 0.0) { + if (spaceRemaining <= 0.0) { return; } // Try to make the gaps equal, taking the spread factors and maximum spacing into account. static const int maxPasses { 20 }; // Saveguard to prevent endless loops. int pass { 0 }; - while (!RealIsNull(spaceLeft) && (ngaps > 0) && (++pass < maxPasses)) { + while (!RealIsNull(spaceRemaining) && (ngaps > 0) && (++pass < maxPasses)) { ngaps = 0; qreal smallest { vgdl.smallest() }; qreal nextSmallest { vgdl.smallest(smallest) }; @@ -559,8 +582,8 @@ void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page) break; } - if ((nextSmallest - smallest) * vgdl.sumStretchFactor() > spaceLeft) { - nextSmallest = smallest + spaceLeft / vgdl.sumStretchFactor(); + if ((nextSmallest - smallest) * vgdl.sumStretchFactor() > spaceRemaining) { + nextSmallest = smallest + spaceRemaining / vgdl.sumStretchFactor(); } qreal addedSpace { 0.0 }; @@ -579,30 +602,30 @@ void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page) modified.append(vgd); ++ngaps; } - if ((spaceLeft - addedSpace) <= 0.0) { + if ((spaceRemaining - addedSpace) <= 0.0) { break; } } - if ((spaceLeft - addedSpace) <= 0.0) { + if ((spaceRemaining - addedSpace) <= 0.0) { for (VerticalGapData* vgd : modified) { vgd->undoLastAddSpacing(); } ngaps = 0; } else { - spaceLeft -= addedSpace; + spaceRemaining -= addedSpace; } } // If there is still space left, distribute the space of the staves. // However, there is a limit on how much space is added per gap. const qreal maxPageFill { score->styleP(Sid::maxPageFillSpread) }; - spaceLeft = qMin(maxPageFill * vgdl.length(), spaceLeft); + spaceRemaining = qMin(maxPageFill * vgdl.length(), spaceRemaining); pass = 0; ngaps = 1; - while (!RealIsNull(spaceLeft) && !RealIsNull(maxPageFill) && (ngaps > 0) && (++pass < maxPasses)) { + while (!RealIsNull(spaceRemaining) && !RealIsNull(maxPageFill) && (ngaps > 0) && (++pass < maxPasses)) { ngaps = 0; qreal addedSpace { 0.0 }; - qreal step { spaceLeft / vgdl.sumStretchFactor() }; + qreal step { spaceRemaining / vgdl.sumStretchFactor() }; for (VerticalGapData* vgd : vgdl) { qreal res { vgd->addFillSpacing(step, maxPageFill) }; if (!RealIsNull(res)) { @@ -610,7 +633,7 @@ void LayoutPage::distributeStaves(const LayoutContext& ctx, Page* page) ++ngaps; } } - spaceLeft -= addedSpace; + spaceRemaining -= addedSpace; } QSet systems; diff --git a/src/engraving/layout/layoutpage.h b/src/engraving/layout/layoutpage.h index be19b222746b..58402939bf1e 100644 --- a/src/engraving/layout/layoutpage.h +++ b/src/engraving/layout/layoutpage.h @@ -39,9 +39,9 @@ class LayoutPage static void collectPage(const LayoutOptions& options, LayoutContext& lc); private: - static void layoutPage(const LayoutContext& ctx, Ms::Page* page, qreal restHeight); + static void layoutPage(const LayoutContext& ctx, Ms::Page* page, qreal restHeight, qreal footerPadding); static void checkDivider(const LayoutContext& ctx, bool left, Ms::System* s, qreal yOffset, bool remove = false); - static void distributeStaves(const LayoutContext& ctx, Ms::Page* page); + static void distributeStaves(const LayoutContext& ctx, Ms::Page* page, qreal footerPadding); }; } diff --git a/src/engraving/libmscore/page.cpp b/src/engraving/libmscore/page.cpp index a4d7e868dc9f..1a34da019206 100644 --- a/src/engraving/libmscore/page.cpp +++ b/src/engraving/libmscore/page.cpp @@ -169,10 +169,26 @@ void Page::draw(mu::draw::Painter* painter) const //--------------------------------------------------------- void Page::drawHeaderFooter(mu::draw::Painter* p, int area, const QString& ss) const +{ + Text* text = layoutHeaderFooter(area, ss); + if (!text) { + return; + } + p->translate(text->pos()); + text->draw(p); + p->translate(-text->pos()); + text->moveToDummy(); +} + +//--------------------------------------------------------- +// layoutHeaderFooter +//--------------------------------------------------------- + +Text* Page::layoutHeaderFooter(int area, const QString& ss) const { QString s = replaceTextMacros(ss); if (s.isEmpty()) { - return; + return nullptr; } Text* text; @@ -214,10 +230,94 @@ void Page::drawHeaderFooter(mu::draw::Painter* p, int area, const QString& ss) c text->setAlign(flags); text->setXmlText(s); text->layout(); - p->translate(text->pos()); - text->draw(p); - p->translate(-text->pos()); - text->moveToDummy(); + return text; +} + +//--------------------------------------------------------- +// headerExtension +// - how much the header extends into the page (i.e., not in the margins) +//--------------------------------------------------------- + +qreal Page::headerExtension() const +{ + if (!score()->isLayoutMode(LayoutMode::PAGE)) { + return 0.0; + } + + int n = no() + 1 + score()->pageNumberOffset(); + + QString s1, s2, s3; + + if (score()->styleB(Sid::showHeader) && (no() || score()->styleB(Sid::headerFirstPage))) { + bool odd = (n & 1) || !score()->styleB(Sid::headerOddEven); + if (odd) { + s1 = score()->styleSt(Sid::oddHeaderL); + s2 = score()->styleSt(Sid::oddHeaderC); + s3 = score()->styleSt(Sid::oddHeaderR); + } else { + s1 = score()->styleSt(Sid::evenHeaderL); + s2 = score()->styleSt(Sid::evenHeaderC); + s3 = score()->styleSt(Sid::evenHeaderR); + } + + Text* headerLeft = layoutHeaderFooter(0, s1); + Text* headerCenter = layoutHeaderFooter(1, s2); + Text* headerRight = layoutHeaderFooter(2, s3); + + qreal headerLeftHeight = headerLeft ? headerLeft->height() : 0.0; + qreal headerCenterHeight = headerCenter ? headerCenter->height() : 0.0; + qreal headerRightHeight = headerRight ? headerRight->height() : 0.0; + + qreal headerHeight = qMax(headerLeftHeight, qMax(headerCenterHeight, headerRightHeight)); + qreal headerOffset = score()->styleV(Sid::headerOffset).value().y() * DPMM; + return qMax(0.0, headerHeight - headerOffset); + } + + return 0.0; +} + +//--------------------------------------------------------- +// footerExtension +// - how much the footer extends into the page (i.e., not in the margins) +//--------------------------------------------------------- + +qreal Page::footerExtension() const +{ + if (!score()->isLayoutMode(LayoutMode::PAGE)) { + return 0.0; + } + + int n = no() + 1 + score()->pageNumberOffset(); + + QString s1, s2, s3; + + if (score()->styleB(Sid::showFooter) && (no() || score()->styleB(Sid::footerFirstPage))) { + bool odd = (n & 1) || !score()->styleB(Sid::footerOddEven); + if (odd) { + s1 = score()->styleSt(Sid::oddFooterL); + s2 = score()->styleSt(Sid::oddFooterC); + s3 = score()->styleSt(Sid::oddFooterR); + } else { + s1 = score()->styleSt(Sid::evenFooterL); + s2 = score()->styleSt(Sid::evenFooterC); + s3 = score()->styleSt(Sid::evenFooterR); + } + + Text* footerLeft = layoutHeaderFooter(3, s1); + Text* footerCenter = layoutHeaderFooter(4, s2); + Text* footerRight = layoutHeaderFooter(5, s3); + + qreal footerLeftHeight = footerLeft ? footerLeft->height() : 0.0; + qreal footerCenterHeight = footerCenter ? footerCenter->height() : 0.0; + qreal footerRightHeight = footerRight ? footerRight->height() : 0.0; + + qreal footerHeight = qMax(footerLeftHeight, qMax(footerCenterHeight, footerRightHeight)); + + qreal footerOffset = score()->styleV(Sid::footerOffset).value().y() * DPMM; + return qMax(0.0, footerHeight - footerOffset); + } + + return 0.0; } //--------------------------------------------------------- diff --git a/src/engraving/libmscore/page.h b/src/engraving/libmscore/page.h index 5a562a3d32b5..fbdf9d76e6cb 100644 --- a/src/engraving/libmscore/page.h +++ b/src/engraving/libmscore/page.h @@ -60,6 +60,7 @@ class Page final : public EngravingItem QString replaceTextMacros(const QString&) const; void drawHeaderFooter(mu::draw::Painter*, int area, const QString&) const; + Text* layoutHeaderFooter(int area, const QString& ss) const; public: @@ -87,6 +88,8 @@ class Page final : public EngravingItem qreal bm() const; qreal lm() const; qreal rm() const; + qreal headerExtension() const; + qreal footerExtension() const; void draw(mu::draw::Painter*) const override; void scanElements(void* data, void (* func)(void*, EngravingItem*), bool all=true) override; diff --git a/src/engraving/libmscore/textbase.cpp b/src/engraving/libmscore/textbase.cpp index 681c540b8739..82537b24fc46 100644 --- a/src/engraving/libmscore/textbase.cpp +++ b/src/engraving/libmscore/textbase.cpp @@ -1818,8 +1818,11 @@ static qreal parseNumProperty(const QString& s) void TextBase::createLayout() { - _layout.clear(); // deletes the text fragments so we lose all formatting information + // reset all previous formatting information + _layout.clear(); TextCursor cursor = *_cursor; + cursor.setRow(0); + cursor.setColumn(0); int state = 0; QString token; @@ -2009,6 +2012,8 @@ void TextBase::layout1() } RectF bb; qreal y = 0; + + // adjust the bounding box for the text item for (int i = 0; i < rows(); ++i) { TextBlock* t = &_layout[i]; t->layout(this); diff --git a/src/engraving/style/styledef.cpp b/src/engraving/style/styledef.cpp index 5858b1b3fd36..4638382d94e4 100644 --- a/src/engraving/style/styledef.cpp +++ b/src/engraving/style/styledef.cpp @@ -57,6 +57,7 @@ const std::array StyleDef::styleValue { Sid::staffUpperBorder, "staffUpperBorder", Spatium(7.0) }, { Sid::staffLowerBorder, "staffLowerBorder", Spatium(7.0) }, + { Sid::staffHeaderFooterPadding, "staffHeaderFooterPadding", Spatium(1.0) }, { Sid::staffDistance, "staffDistance", Spatium(6.5) }, { Sid::instrumentNameOffset, "instrumentNameOffset", Spatium(1.0) }, { Sid::akkoladeDistance, "akkoladeDistance", Spatium(6.5) }, @@ -1115,7 +1116,7 @@ const std::array StyleDef::styleValue { Sid::footerFontStyle, "footerFontStyle", int(FontStyle::Normal) }, { Sid::footerColor, "footerColor", QVariant::fromValue(draw::Color::black) }, { Sid::footerAlign, "footerAlign", QVariant::fromValue(Align::CENTER | Align::BOTTOM) }, - { Sid::footerOffset, "footerOffset", PointF(0.0, 5.0) }, + { Sid::footerOffset, "footerOffset", PointF(0.0, 0.0) }, { Sid::footerFrameType, "footerFrameType", int(FrameType::NO_FRAME) }, { Sid::footerFramePadding, "footerFramePadding", 0.2 }, { Sid::footerFrameWidth, "footerFrameWidth", 0.1 }, diff --git a/src/engraving/style/styledef.h b/src/engraving/style/styledef.h index 9e6a54768456..30dd452ddc49 100644 --- a/src/engraving/style/styledef.h +++ b/src/engraving/style/styledef.h @@ -72,6 +72,7 @@ enum class Sid { staffUpperBorder, staffLowerBorder, + staffHeaderFooterPadding, staffDistance, instrumentNameOffset, akkoladeDistance,