Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render oblique ligatures with curve shapes #3962

Merged
merged 8 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/vrv/bboxdevicecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions include/vrv/devicecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions include/vrv/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -148,6 +150,7 @@ class Option {
static const std::map<int, std::string> s_fontFallback;
static const std::map<int, std::string> s_footer;
static const std::map<int, std::string> s_header;
static const std::map<int, std::string> s_ligatureOblique;
static const std::map<int, std::string> s_multiRestStyle;
static const std::map<int, std::string> s_pedalStyle;
static const std::map<int, std::string> s_systemDivider;
Expand Down Expand Up @@ -845,6 +848,7 @@ class Options {

OptionIntMap m_durationEquivalence;
OptionBool m_ligatureAsBracket;
OptionIntMap m_ligatureOblique;
OptionBool m_mensuralScoreUp;
OptionBool m_mensuralResponsiveView;
OptionBool m_mensuralToCmn;
Expand Down
1 change: 1 addition & 0 deletions include/vrv/svgdevicecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion include/vrv/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/bboxdevicecontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const std::map<int, std::string> Option::s_footer
const std::map<int, std::string> Option::s_header
= { { HEADER_none, "none" }, { HEADER_auto, "auto" }, { HEADER_encoded, "encoded" } };

const std::map<int, std::string> Option::s_ligatureOblique
= { { LIGATURE_OBL_auto, "auto" }, { LIGATURE_OBL_straight, "straight" }, { LIGATURE_OBL_curved, "curved" } };

const std::map<int, std::string> Option::s_multiRestStyle = { { MULTIRESTSTYLE_auto, "auto" },
{ MULTIRESTSTYLE_default, "default" }, { MULTIRESTSTYLE_block, "block" }, { MULTIRESTSTYLE_symbols, "symbols" } };

Expand Down Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions src/svgdevicecontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
80 changes: 58 additions & 22 deletions src/view_mensural.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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
Expand Down Expand Up @@ -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];
Expand All @@ -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;
}
}

Expand Down
Loading