diff --git a/include/vrv/options.h b/include/vrv/options.h index c81516b128..7bfb7f0d9f 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -714,6 +714,7 @@ class Options { OptionDbl m_ledgerLineThickness; OptionDbl m_ledgerLineExtension; OptionIntMap m_lyricElision; + OptionDbl m_lyricHeightFactor; OptionDbl m_lyricLineThickness; OptionBool m_lyricNoStartHyphen; OptionDbl m_lyricSize; diff --git a/include/vrv/syl.h b/include/vrv/syl.h index 47d0f654a2..23f4d62970 100644 --- a/include/vrv/syl.h +++ b/include/vrv/syl.h @@ -118,7 +118,9 @@ class Syl : public LayerElement, * The verse number with multiple verses * Value is 1 by default, set in PrepareLyrics */ - int m_drawingVerse; + int m_drawingVerseN; + /** The verse place (below by default) */ + data_STAFFREL m_drawingVersePlace; /** * A pointer to the next syllable of the word. diff --git a/include/vrv/verse.h b/include/vrv/verse.h index c67711eb1b..514e4a2edb 100644 --- a/include/vrv/verse.h +++ b/include/vrv/verse.h @@ -20,7 +20,12 @@ class Syl; // Verse //---------------------------------------------------------------------------- -class Verse : public LayerElement, public AttColor, public AttLang, public AttNInteger, public AttTypography { +class Verse : public LayerElement, + public AttColor, + public AttLang, + public AttNInteger, + public AttPlacementRelStaff, + public AttTypography { public: /** * @name Constructors, destructors, and other standard methods diff --git a/include/vrv/verticalaligner.h b/include/vrv/verticalaligner.h index d5ce55c864..8b09af337c 100644 --- a/include/vrv/verticalaligner.h +++ b/include/vrv/verticalaligner.h @@ -195,9 +195,12 @@ class StaffAlignment : public Object { * The position is calculated from the bottom. */ ///@{ - void AddVerseN(int verseN); + void AddVerseN(int verseN, data_STAFFREL place); int GetVerseCount(bool collapse) const; - int GetVersePosition(int verseN, bool collapse) const; + int GetVerseCountAbove(bool collapse) const; + int GetVerseCountBelow(bool collapse) const; + int GetVersePositionAbove(int verseN, bool collapse) const; + int GetVersePositionBelow(int verseN, bool collapse) const; ///@} /** @@ -402,9 +405,10 @@ class StaffAlignment : public Object { */ int m_yRel; /** - * Stores the verse@n of the staves attached to the aligner + * Stores the verse@n of the staves attached to the aligner (above and below) */ - std::set m_verseNs; + std::set m_verseAboveNs; + std::set m_verseBelowNs; /** * @name values for storing the overflow and overlap diff --git a/include/vrv/view.h b/include/vrv/view.h index 56cdedebee..a07a9742d2 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -624,7 +624,7 @@ class View { std::u32string IntToTimeSigFigures(unsigned short number); std::u32string IntToSmuflFigures(unsigned short number, int offset); int NestedTuplets(Object *object); - int GetSylYRel(int verseN, Staff *staff); + int GetSylYRel(int verseN, Staff *staff, data_STAFFREL place); int GetFYRel(F *f, Staff *staff); ///@} diff --git a/src/adjustfloatingpositionerfunctor.cpp b/src/adjustfloatingpositionerfunctor.cpp index 969ee0fc86..2afc2983b6 100644 --- a/src/adjustfloatingpositionerfunctor.cpp +++ b/src/adjustfloatingpositionerfunctor.cpp @@ -34,20 +34,34 @@ FunctorCode AdjustFloatingPositionersFunctor::VisitStaffAlignment(StaffAlignment staffAlignment->SortPositioners(); - const bool verseCollapse = m_doc->GetOptions()->m_lyricVerseCollapse.GetValue(); if (m_classId == SYL) { + const bool verseCollapse = m_doc->GetOptions()->m_lyricVerseCollapse.GetValue(); if (staffAlignment->GetVerseCount(verseCollapse) > 0) { FontInfo *lyricFont = m_doc->GetDrawingLyricFont(staffAlignment->GetStaff()->m_drawingStaffSize); - int descender = m_doc->GetTextGlyphDescender(L'q', lyricFont, false); - int height = m_doc->GetTextGlyphHeight(L'I', lyricFont, false); - int margin = m_doc->GetBottomMargin(SYL) * drawingUnit; - int minMargin = std::max((int)(m_doc->GetOptions()->m_lyricTopMinMargin.GetValue() * drawingUnit), - staffAlignment->GetOverflowBelow()); - staffAlignment->SetOverflowBelow( - minMargin + staffAlignment->GetVerseCount(verseCollapse) * (height - descender + margin)); - // For now just clear the overflowBelow, which avoids the overlap to be calculated. We could also keep them - // and check if they are some lyrics in order to know if the overlap needs to be calculated or not. - staffAlignment->ClearBBoxesBelow(); + int verseHeight = m_doc->GetTextGlyphHeight(L'I', lyricFont, false) + - m_doc->GetTextGlyphDescender(L'q', lyricFont, false); + verseHeight *= m_doc->GetOptions()->m_lyricHeightFactor.GetValue(); + if (staffAlignment->GetVerseCountAbove(verseCollapse)) { + int margin = m_doc->GetTopMargin(SYL) * drawingUnit; + int minMargin = std::max((int)(m_doc->GetOptions()->m_lyricTopMinMargin.GetValue() * drawingUnit), + staffAlignment->GetOverflowAbove()); + staffAlignment->SetOverflowAbove( + minMargin + staffAlignment->GetVerseCountAbove(verseCollapse) * (verseHeight + margin)); + // For now just clear the overflowBelow, which avoids the overlap to be calculated. We could also keep + // them and check if they are some lyrics in order to know if the overlap needs to be calculated or not. + staffAlignment->ClearBBoxesAbove(); + } + if (staffAlignment->GetVerseCountBelow(verseCollapse)) { + int margin = m_doc->GetBottomMargin(SYL) * drawingUnit; + int minMargin = std::max((int)(m_doc->GetOptions()->m_lyricTopMinMargin.GetValue() * drawingUnit), + staffAlignment->GetOverflowBelow()); + staffAlignment->SetOverflowBelow( + minMargin + staffAlignment->GetVerseCountBelow(verseCollapse) * (verseHeight + margin)); + // For now just clear the overflowBelow, which avoids the overlap to be calculated. We could also keep + // them and check if there are some lyrics in order to know if the overlap needs to be calculated or + // not. + staffAlignment->ClearBBoxesBelow(); + } } return FUNCTOR_SIBLINGS; } diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index 25aa777e5f..80c012544a 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -640,9 +640,9 @@ FunctorCode AlignVerticallyFunctor::VisitStaff(Staff *staff) std::vector::const_iterator verseIterator = std::find_if( staff->m_timeSpanningElements.begin(), staff->m_timeSpanningElements.end(), ObjectComparison(VERSE)); if (verseIterator != staff->m_timeSpanningElements.end()) { - Verse *v = vrv_cast(*verseIterator); - assert(v); - alignment->AddVerseN(v->GetN()); + Verse *verse = vrv_cast(*verseIterator); + assert(verse); + alignment->AddVerseN(verse->GetN(), verse->GetPlace()); } // add verse number to alignment in case there are spanning SYL elements but there is no verse number already - this @@ -653,9 +653,13 @@ FunctorCode AlignVerticallyFunctor::VisitStaff(Staff *staff) Verse *verse = vrv_cast((*sylIterator)->GetFirstAncestor(VERSE)); if (verse) { const int verseNumber = verse->GetN(); + const data_STAFFREL versePlace = verse->GetPlace(); const bool verseCollapse = m_doc->GetOptions()->m_lyricVerseCollapse.GetValue(); - if (!alignment->GetVersePosition(verseNumber, verseCollapse)) { - alignment->AddVerseN(verseNumber); + if ((versePlace == STAFFREL_above) && !alignment->GetVersePositionAbove(verseNumber, verseCollapse)) { + alignment->AddVerseN(verseNumber, verse->GetPlace()); + } + if ((versePlace != STAFFREL_above) && !alignment->GetVersePositionBelow(verseNumber, verseCollapse)) { + alignment->AddVerseN(verseNumber, verse->GetPlace()); } } } @@ -685,7 +689,7 @@ FunctorCode AlignVerticallyFunctor::VisitSyllable(Syllable *syllable) StaffAlignment *alignment = m_systemAligner->GetStaffAlignmentForStaffN(m_staffN); if (!alignment) return FUNCTOR_CONTINUE; // Current limitation of only one syl (verse n) by syllable - alignment->AddVerseN(1); + alignment->AddVerseN(1, STAFFREL_below); return FUNCTOR_CONTINUE; } @@ -720,7 +724,7 @@ FunctorCode AlignVerticallyFunctor::VisitVerse(Verse *verse) if (!alignment) return FUNCTOR_CONTINUE; // Add the number count - alignment->AddVerseN(verse->GetN()); + alignment->AddVerseN(verse->GetN(), verse->GetPlace()); return FUNCTOR_CONTINUE; } diff --git a/src/iomei.cpp b/src/iomei.cpp index a6a4fe2b25..9ed20dc3a3 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2870,6 +2870,7 @@ void MEIOutput::WriteVerse(pugi::xml_node currentNode, Verse *verse) verse->WriteColor(currentNode); verse->WriteLang(currentNode); verse->WriteNInteger(currentNode); + verse->WritePlacementRelStaff(currentNode); verse->WriteTypography(currentNode); } @@ -7163,6 +7164,7 @@ bool MEIInput::ReadVerse(Object *parent, pugi::xml_node verse) vrvVerse->ReadColor(verse); vrvVerse->ReadLang(verse); vrvVerse->ReadNInteger(verse); + vrvVerse->ReadPlacementRelStaff(verse); vrvVerse->ReadTypography(verse); parent->AddChild(vrvVerse); diff --git a/src/options.cpp b/src/options.cpp index 1fcbb2c101..6caefe7cd7 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1371,6 +1371,10 @@ Options::Options() m_lyricElision.Init(ELISION_regular, &Option::s_elision); this->Register(&m_lyricElision, "lyricElision", &m_generalLayout); + m_lyricHeightFactor.SetInfo("Lyric height factor", "The lyric verse line height factor"); + m_lyricHeightFactor.Init(1.0, 1.0, 20.0); + this->Register(&m_lyricHeightFactor, "lyricHeightFactor", &m_generalLayout); + m_lyricLineThickness.SetInfo("Lyric line thickness", "The lyric extender line thickness"); m_lyricLineThickness.Init(0.25, 0.10, 0.50); this->Register(&m_lyricLineThickness, "lyricLineThickness", &m_generalLayout); diff --git a/src/preparedatafunctor.cpp b/src/preparedatafunctor.cpp index 49484e8b06..636bfecf39 100644 --- a/src/preparedatafunctor.cpp +++ b/src/preparedatafunctor.cpp @@ -1041,7 +1041,8 @@ FunctorCode PrepareLyricsFunctor::VisitSyl(Syl *syl) { Verse *verse = vrv_cast(syl->GetFirstAncestor(VERSE, MAX_NOTE_DEPTH)); if (verse) { - syl->m_drawingVerse = std::max(verse->GetN(), 1); + syl->m_drawingVerseN = std::max(verse->GetN(), 1); + syl->m_drawingVersePlace = verse->GetPlace(); } syl->SetStart(vrv_cast(syl->GetFirstAncestor(NOTE, MAX_NOTE_DEPTH))); diff --git a/src/syl.cpp b/src/syl.cpp index 1e777512da..03c35caf6f 100644 --- a/src/syl.cpp +++ b/src/syl.cpp @@ -59,7 +59,8 @@ void Syl::Reset() this->ResetTypography(); this->ResetSylLog(); - m_drawingVerse = 1; + m_drawingVerseN = 1; + m_drawingVersePlace = STAFFREL_below; m_nextWordSyl = NULL; } diff --git a/src/verse.cpp b/src/verse.cpp index 66013ee926..cba1f9d2e9 100644 --- a/src/verse.cpp +++ b/src/verse.cpp @@ -38,6 +38,7 @@ Verse::Verse() : LayerElement(VERSE, "verse-"), AttColor(), AttLang(), AttNInteg this->RegisterAttClass(ATT_COLOR); this->RegisterAttClass(ATT_LANG); this->RegisterAttClass(ATT_NINTEGER); + this->RegisterAttClass(ATT_PLACEMENTRELSTAFF); this->RegisterAttClass(ATT_TYPOGRAPHY); this->Reset(); @@ -51,6 +52,7 @@ void Verse::Reset() this->ResetColor(); this->ResetLang(); this->ResetNInteger(); + this->ResetPlacementRelStaff(); this->ResetTypography(); m_drawingLabelAbbr = NULL; diff --git a/src/verticalaligner.cpp b/src/verticalaligner.cpp index 2093b5d993..86cf658fc8 100644 --- a/src/verticalaligner.cpp +++ b/src/verticalaligner.cpp @@ -314,7 +314,8 @@ FunctorCode SystemAligner::AcceptEnd(ConstFunctor &functor) const StaffAlignment::StaffAlignment() : Object(STAFF_ALIGNMENT) { m_yRel = 0; - m_verseNs.clear(); + m_verseAboveNs.clear(); + m_verseBelowNs.clear(); m_staff = NULL; m_floatingPositionersSorted = true; @@ -436,39 +437,73 @@ void StaffAlignment::SetRequestedSpaceBelow(int space) } } -void StaffAlignment::AddVerseN(int verseN) +void StaffAlignment::AddVerseN(int verseN, data_STAFFREL place) { // if 0, then assume 1; verseN = std::max(verseN, 1); - m_verseNs.insert(verseN); + (place == STAFFREL_above) ? m_verseAboveNs.insert(verseN) : m_verseBelowNs.insert(verseN); } int StaffAlignment::GetVerseCount(bool collapse) const { - if (m_verseNs.empty()) { + return (this->GetVerseCountAbove(collapse) + this->GetVerseCountBelow(collapse)); +} + +int StaffAlignment::GetVerseCountAbove(bool collapse) const +{ + if (m_verseAboveNs.empty()) { + return 0; + } + else if (collapse) { + return (int)m_verseAboveNs.size(); + } + else { + return (*m_verseAboveNs.rbegin()); + } +} + +int StaffAlignment::GetVerseCountBelow(bool collapse) const +{ + if (m_verseBelowNs.empty()) { return 0; } else if (collapse) { - return (int)m_verseNs.size(); + return (int)m_verseBelowNs.size(); + } + else { + return (*m_verseBelowNs.rbegin()); + } +} + +int StaffAlignment::GetVersePositionAbove(int verseN, bool collapse) const +{ + if (m_verseAboveNs.empty()) { + // Syl in neumatic notation - since verse count will be 0, position is -1 + return -1; + } + else if (collapse) { + auto it = std::find(m_verseAboveNs.begin(), m_verseAboveNs.end(), verseN); + int pos = (int)std::distance(m_verseAboveNs.begin(), it); + return pos; } else { - return *m_verseNs.rbegin(); + return verseN - 1; } } -int StaffAlignment::GetVersePosition(int verseN, bool collapse) const +int StaffAlignment::GetVersePositionBelow(int verseN, bool collapse) const { - if (m_verseNs.empty()) { + if (m_verseBelowNs.empty()) { // Syl in neumatic notation - since verse count will be 0, position is -1 return -1; } else if (collapse) { - auto it = std::find(m_verseNs.rbegin(), m_verseNs.rend(), verseN); - int pos = (int)std::distance(m_verseNs.rbegin(), it); + auto it = std::find(m_verseBelowNs.rbegin(), m_verseBelowNs.rend(), verseN); + int pos = (int)std::distance(m_verseBelowNs.rbegin(), it); return pos; } else { - return (*m_verseNs.rbegin()) - verseN; + return (*m_verseBelowNs.rbegin()) - verseN; } } diff --git a/src/view_control.cpp b/src/view_control.cpp index 538acc581c..b4d0736edb 100644 --- a/src/view_control.cpp +++ b/src/view_control.cpp @@ -1245,7 +1245,7 @@ void View::DrawSylConnector( assert(syl->GetStart() && syl->GetEnd()); if (!syl->GetStart() || !syl->GetEnd()) return; - const int y = staff->GetDrawingY() + this->GetSylYRel(syl->m_drawingVerse, staff); + const int y = staff->GetDrawingY() + this->GetSylYRel(syl->m_drawingVerseN, staff, syl->m_drawingVersePlace); // Invalid bounding boxes might occur for empty syllables without text child if (!syl->HasContentHorizontalBB()) return; diff --git a/src/view_element.cpp b/src/view_element.cpp index cad7563ea2..e96180b769 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -1753,7 +1753,7 @@ void View::DrawSyl(DeviceContext *dc, LayerElement *element, Layer *layer, Staff } if (!m_doc->IsFacs() && !m_doc->IsTranscription() && !m_doc->IsNeumeLines()) { - syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerse, staff)); + syl->SetDrawingYRel(this->GetSylYRel(syl->m_drawingVerseN, staff, syl->m_drawingVersePlace)); } dc->StartGraphic(syl, "", syl->GetID()); @@ -1871,7 +1871,7 @@ void View::DrawVerse(DeviceContext *dc, LayerElement *element, Layer *layer, Sta TextDrawingParams params; params.m_x = verse->GetDrawingX() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize); - params.m_y = staff->GetDrawingY() + this->GetSylYRel(std::max(1, verse->GetN()), staff); + params.m_y = staff->GetDrawingY() + this->GetSylYRel(std::max(1, verse->GetN()), staff, verse->GetPlace()); params.m_pointSize = labelTxt.GetPointSize(); dc->SetBrush(m_currentColor, AxSOLID); @@ -2098,22 +2098,34 @@ int View::GetFYRel(F *f, Staff *staff) return y; } -int View::GetSylYRel(int verseN, Staff *staff) +int View::GetSylYRel(int verseN, Staff *staff, data_STAFFREL place) { assert(staff); + StaffAlignment *alignment = staff->GetAlignment(); + if (!alignment) return 0; + const bool verseCollapse = m_options->m_lyricVerseCollapse.GetValue(); int y = 0; - StaffAlignment *alignment = staff->GetAlignment(); - if (alignment) { - FontInfo *lyricFont = m_doc->GetDrawingLyricFont(staff->m_drawingStaffSize); - int descender = -m_doc->GetTextGlyphDescender(L'q', lyricFont, false); - int height = m_doc->GetTextGlyphHeight(L'I', lyricFont, false); - int margin = m_doc->GetBottomMargin(SYL) * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + FontInfo *lyricFont = m_doc->GetDrawingLyricFont(staff->m_drawingStaffSize); + const int descender = m_doc->GetTextGlyphDescender(L'q', lyricFont, false); + const int height = m_doc->GetTextGlyphHeight(L'I', lyricFont, false); + + int verseHeight = height - descender; + verseHeight *= m_doc->GetOptions()->m_lyricHeightFactor.GetValue(); + int margin = m_doc->GetBottomMargin(SYL) * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + + // above the staff + if (place == STAFFREL_above) { + y = alignment->GetOverflowAbove() + - (alignment->GetVersePositionAbove(verseN, verseCollapse)) * (verseHeight + margin) - (height); + } + else { y = -alignment->GetStaffHeight() - alignment->GetOverflowBelow() - + alignment->GetVersePosition(verseN, verseCollapse) * (height + descender + margin) + (descender); + + alignment->GetVersePositionBelow(verseN, verseCollapse) * (verseHeight + margin) + verseHeight - height; } + return y; }