diff --git a/include/vrv/bboxdevicecontext.h b/include/vrv/bboxdevicecontext.h index 9f0b369ba4..80315b7e39 100644 --- a/include/vrv/bboxdevicecontext.h +++ b/include/vrv/bboxdevicecontext.h @@ -72,6 +72,7 @@ class BBoxDeviceContext : public DeviceContext { void DrawQuadBezierPath(Point bezier[3]) override; void DrawCubicBezierPath(Point bezier[4]) override; void DrawCubicBezierPathFilled(Point bezier1[4], Point bezier2[4]) override; + void DrawBentParallelogramFilled(Point side[4], int height) override; void DrawCircle(int x, int y, int radius) override; void DrawEllipse(int x, int y, int width, int height) override; void DrawEllipticArc(int x, int y, int width, int height, double start, double end) override; diff --git a/include/vrv/devicecontext.h b/include/vrv/devicecontext.h index 565e32fb82..e5f14a47b6 100644 --- a/include/vrv/devicecontext.h +++ b/include/vrv/devicecontext.h @@ -193,6 +193,7 @@ class DeviceContext { virtual void DrawQuadBezierPath(Point bezier[3]) = 0; virtual void DrawCubicBezierPath(Point bezier[4]) = 0; virtual void DrawCubicBezierPathFilled(Point bezier1[4], Point bezier2[4]) = 0; + virtual void DrawBentParallelogramFilled(Point side[4], int height) = 0; virtual void DrawCircle(int x, int y, int radius) = 0; virtual void DrawEllipse(int x, int y, int width, int height) = 0; virtual void DrawEllipticArc(int x, int y, int width, int height, double start, double end) = 0; diff --git a/include/vrv/options.h b/include/vrv/options.h index 5961c57ea3..f545882b35 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -75,6 +75,8 @@ enum option_FOOTER { FOOTER_none = 0, FOOTER_auto, FOOTER_encoded, FOOTER_always enum option_HEADER { HEADER_none = 0, HEADER_auto, HEADER_encoded }; +enum option_LIGATURE_OBL { LIGATURE_OBL_auto = 0, LIGATURE_OBL_straight, LIGATURE_OBL_curved }; + enum option_MULTIRESTSTYLE { MULTIRESTSTYLE_auto = 0, MULTIRESTSTYLE_default, @@ -148,6 +150,7 @@ class Option { static const std::map s_fontFallback; static const std::map s_footer; static const std::map s_header; + static const std::map s_ligatureOblique; static const std::map s_multiRestStyle; static const std::map s_pedalStyle; static const std::map s_systemDivider; @@ -845,6 +848,7 @@ class Options { OptionIntMap m_durationEquivalence; OptionBool m_ligatureAsBracket; + OptionIntMap m_ligatureOblique; OptionBool m_mensuralScoreUp; OptionBool m_mensuralResponsiveView; OptionBool m_mensuralToCmn; diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index 80746b5fba..c8ca9dff7f 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -78,6 +78,7 @@ class SvgDeviceContext : public DeviceContext { void DrawQuadBezierPath(Point bezier[3]) override; void DrawCubicBezierPath(Point bezier[4]) override; void DrawCubicBezierPathFilled(Point bezier1[4], Point bezier2[4]) override; + void DrawBentParallelogramFilled(Point side[4], int height) override; void DrawCircle(int x, int y, int radius) override; void DrawEllipse(int x, int y, int width, int height) override; void DrawEllipticArc(int x, int y, int width, int height, double start, double end) override; diff --git a/include/vrv/view.h b/include/vrv/view.h index f73e89492d..d8c9b17b23 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -641,7 +641,7 @@ class View { void CalcBrevisPoints( Note *note, Staff *staff, Point *topLeft, Point *bottomRight, int sides[4], int shape, bool isMensuralBlack); void CalcObliquePoints(Note *note1, Note *note2, Staff *staff, Point points[4], int sides[4], int shape, - bool isMensuralBlack, bool firstHalf); + bool isMensuralBlack, bool firstHalf, bool straight); /** * Internal methods for drawing a BeamSegment diff --git a/src/bboxdevicecontext.cpp b/src/bboxdevicecontext.cpp index 3a444bd647..5de7b21c38 100644 --- a/src/bboxdevicecontext.cpp +++ b/src/bboxdevicecontext.cpp @@ -169,6 +169,11 @@ void BBoxDeviceContext::DrawCubicBezierPathFilled(Point bezier1[4], Point bezier this->UpdateBB(pos.x, pos.y, pos.x + width, pos.y + height); } +void BBoxDeviceContext::DrawBentParallelogramFilled(Point side[4], int height) +{ + this->UpdateBB(side[0].x, side[0].y, side[3].x, side[3].y + height); +} + void BBoxDeviceContext::DrawCircle(int x, int y, int radius) { this->DrawEllipse(x - radius, y - radius, 2 * radius, 2 * radius); diff --git a/src/options.cpp b/src/options.cpp index dedfe749d8..0745085504 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -44,6 +44,9 @@ const std::map Option::s_footer const std::map Option::s_header = { { HEADER_none, "none" }, { HEADER_auto, "auto" }, { HEADER_encoded, "encoded" } }; +const std::map Option::s_ligatureOblique + = { { LIGATURE_OBL_auto, "auto" }, { LIGATURE_OBL_straight, "straight" }, { LIGATURE_OBL_curved, "curved" } }; + const std::map Option::s_multiRestStyle = { { MULTIRESTSTYLE_auto, "auto" }, { MULTIRESTSTYLE_default, "default" }, { MULTIRESTSTYLE_block, "block" }, { MULTIRESTSTYLE_symbols, "symbols" } }; @@ -1832,6 +1835,10 @@ Options::Options() m_ligatureAsBracket.Init(false); this->Register(&m_ligatureAsBracket, "ligatureAsBracket", &m_mensural); + m_ligatureOblique.SetInfo("Ligature oblique", "Ligature oblique shape"); + m_ligatureOblique.Init(LIGATURE_OBL_auto, &Option::s_ligatureOblique); + this->Register(&m_ligatureOblique, "ligatureOblique", &m_mensural); + m_mensuralResponsiveView.SetInfo( "Mensural reduced view", "Convert mensural content to a more responsive view reduced to the seleceted markup"); m_mensuralResponsiveView.Init(false); diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index bfb70d389e..c88b1585d5 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -693,6 +693,19 @@ void SvgDeviceContext::DrawCubicBezierPathFilled(Point bezier1[4], Point bezier2 pathChild.append_attribute("stroke-width") = m_penStack.top().GetWidth(); } +void SvgDeviceContext::DrawBentParallelogramFilled(Point side[4], int height) +{ + pugi::xml_node pathChild = AddChild("path"); + pathChild.append_attribute("d") = StringFormat("M%d,%d C%d,%d %d,%d %d,%d L%d,%d C%d,%d %d,%d %d,%d Z", side[0].x, + side[0].y, side[1].x, side[1].y, side[2].x, side[2].y, side[3].x, side[3].y, side[3].x, side[3].y + height, + side[2].x, side[2].y + height, side[1].x, side[1].y + height, side[0].x, side[0].y + height) + .c_str(); + pathChild.append_attribute("stroke") = this->GetColor(m_penStack.top().GetColor()).c_str(); + pathChild.append_attribute("stroke-linecap") = "round"; + pathChild.append_attribute("stroke-linejoin") = "round"; + pathChild.append_attribute("stroke-width") = m_penStack.top().GetWidth(); +} + void SvgDeviceContext::DrawCircle(int x, int y, int radius) { this->DrawEllipse(x - radius, y - radius, 2 * radius, 2 * radius); diff --git a/src/view_mensural.cpp b/src/view_mensural.cpp index 2adc1cea7b..c4b5bf28d6 100644 --- a/src/view_mensural.cpp +++ b/src/view_mensural.cpp @@ -350,11 +350,17 @@ void View::DrawLigatureNote(DeviceContext *dc, LayerElement *element, Layer *lay bool oblique = ((shape & LIGATURE_OBLIQUE) || (prevShape & LIGATURE_OBLIQUE)); bool obliqueEnd = (prevShape & LIGATURE_OBLIQUE); bool stackedEnd = (shape & LIGATURE_STACKED); - int stemWidth = m_doc->GetDrawingStemWidth(staff->m_drawingStaffSize); int strokeWidth = 2.8 * stemWidth; /** end code duplicated */ + bool straight = true; + switch (m_doc->GetOptions()->m_ligatureOblique.GetValue()) { + case LIGATURE_OBL_auto: straight = !isMensuralBlack; break; + case LIGATURE_OBL_straight: straight = true; break; + case LIGATURE_OBL_curved: straight = false; break; + } + Point points[4]; Point *topLeft = &points[0]; Point *bottomLeft = &points[1]; @@ -372,24 +378,52 @@ void View::DrawLigatureNote(DeviceContext *dc, LayerElement *element, Layer *lay // First half of the oblique - checking the nextNote is there just in case, but is should if ((shape & LIGATURE_OBLIQUE) && nextNote) { // return; - CalcObliquePoints(note, nextNote, staff, points, sides, shape, isMensuralBlack, true); + CalcObliquePoints(note, nextNote, staff, points, sides, shape, isMensuralBlack, true, straight); } // Second half of the oblique - checking the prevNote is there just in case, but is should else if ((prevShape & LIGATURE_OBLIQUE) && prevNote) { - CalcObliquePoints(prevNote, note, staff, points, sides, prevShape, isMensuralBlack, false); + CalcObliquePoints(prevNote, note, staff, points, sides, prevShape, isMensuralBlack, false, straight); } else { assert(false); } } - if (!fillNotehead) { - // double the bases of rectangles - this->DrawObliquePolygon(dc, topLeft->x, topLeft->y, topRight->x, topRight->y, -strokeWidth); - this->DrawObliquePolygon(dc, bottomLeft->x, bottomLeft->y, bottomRight->x, bottomRight->y, strokeWidth); + // Oblique polygons + if (straight) { + if (!fillNotehead) { + this->DrawObliquePolygon(dc, topLeft->x, topLeft->y, topRight->x, topRight->y, -strokeWidth); + this->DrawObliquePolygon(dc, bottomLeft->x, bottomLeft->y, bottomRight->x, bottomRight->y, strokeWidth); + } + else { + this->DrawObliquePolygon(dc, topLeft->x, topLeft->y, topRight->x, topRight->y, bottomLeft->y - topLeft->y); + } } + // Bent parallelograms else { - this->DrawObliquePolygon(dc, topLeft->x, topLeft->y, topRight->x, topRight->y, bottomLeft->y - topLeft->y); + const int thickness = topLeft->y - bottomLeft->y; + // The curved side points (two ends and two control points) + Point curvedSide[4]; + curvedSide[0] = ToDeviceContext(*topLeft); + curvedSide[3] = ToDeviceContext(*topRight); + // + const int width = (curvedSide[3].x - curvedSide[0].x); + const int height = (curvedSide[3].y - curvedSide[0].y); + curvedSide[1] = curvedSide[3]; + curvedSide[1].x -= (width * 0.7); + curvedSide[1].y -= (height * 0.7) + (height * 0.07); + curvedSide[2] = curvedSide[3]; + curvedSide[2].x -= (width * 0.3); + curvedSide[2].y -= (height * 0.3) + (height * 0.07); + + if (!fillNotehead) { + dc->DrawBentParallelogramFilled(curvedSide, strokeWidth); + for (Point &point : curvedSide) point.y += thickness - strokeWidth; + dc->DrawBentParallelogramFilled(curvedSide, strokeWidth); + } + else { + dc->DrawBentParallelogramFilled(curvedSide, thickness); + } } // Do not draw a left connector with obliques @@ -617,13 +651,17 @@ void View::CalcBrevisPoints( } void View::CalcObliquePoints(Note *note1, Note *note2, Staff *staff, Point points[4], int sides[4], int shape, - bool isMensuralBlack, bool firstHalf) + bool isMensuralBlack, bool firstHalf, bool straight) { assert(note1); assert(note2); assert(staff); const int stemWidth = m_doc->GetDrawingStemWidth(staff->m_drawingStaffSize); + const int noteDiff = note1->PitchDifferenceTo(note2); + + // Adjustment for end points according to the note diff + const int yAdjust = noteDiff * stemWidth / 5; Point *topLeft = &points[0]; Point *bottomLeft = &points[1]; @@ -648,35 +686,33 @@ void View::CalcObliquePoints(Note *note1, Note *note2, Staff *staff, Point point sides[3] = sides2[3]; // With oblique it is best visually to move them up / down - more with (white) ligatures with serif - double adjustmentFactor = (isMensuralBlack) ? 0.5 : 1.8; + // double adjustmentFactor = (isMensuralBlack) ? 2.5 : 1.8; double slope = 0.0; if (bottomRight->x != bottomLeft->x) slope = (double)(bottomRight->y - bottomLeft->y) / (double)(bottomRight->x - bottomLeft->x); - int adjustment = (int)(slope * stemWidth) * adjustmentFactor; - topLeft->y -= adjustment; - bottomLeft->y -= adjustment; - topRight->y += adjustment; - bottomRight->y += adjustment; - - slope = 0.0; - // recalculate slope after adjustment - if (bottomRight->x != bottomLeft->x) - slope = (double)(bottomRight->y - bottomLeft->y) / (double)(bottomRight->x - bottomLeft->x); + int length = (bottomRight->x - bottomLeft->x) / 2; + if (!straight) slope *= 0.85; if (firstHalf) { - // make sure there are some pixels of overlap - length += 10; + // make sure there is one pixel of overlap + length += 1; bottomRight->x = bottomLeft->x + length; topRight->x = bottomRight->x; bottomRight->y = bottomLeft->y + (int)(length * slope); topRight->y = topLeft->y + (int)(length * slope); + // + topLeft->y += yAdjust; + bottomLeft->y += yAdjust; } else { bottomLeft->x = bottomLeft->x + length; topLeft->x = bottomLeft->x; bottomLeft->y = bottomLeft->y + (int)(length * slope); topLeft->y = topLeft->y + (int)(length * slope); + // + topRight->y -= yAdjust; + bottomRight->y -= yAdjust; } }