forked from 0xfe/vexflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstaveconnector.js
241 lines (217 loc) · 7.2 KB
/
staveconnector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
import { Vex } from './vex';
import { Element } from './element';
import { Flow } from './tables';
import { Glyph } from './glyph';
function drawBoldDoubleLine(ctx, type, topX, topY, botY) {
if (
type !== StaveConnector.type.BOLD_DOUBLE_LEFT &&
type !== StaveConnector.type.BOLD_DOUBLE_RIGHT
) {
throw new Vex.RERR(
'InvalidConnector', 'A REPEAT_BEGIN or REPEAT_END type must be provided.'
);
}
let x_shift = 3;
let variableWidth = 3.5; // Width for avoiding anti-aliasing width issues
const thickLineOffset = 2; // For aesthetics
if (type === StaveConnector.type.BOLD_DOUBLE_RIGHT) {
x_shift = -5; // Flips the side of the thin line
variableWidth = 3;
}
// Thin line
ctx.fillRect(topX + x_shift, topY, 1, botY - topY);
// Thick line
ctx.fillRect(topX - thickLineOffset, topY, variableWidth, botY - topY);
}
export class StaveConnector extends Element {
// SINGLE_LEFT and SINGLE are the same value for compatibility
// with older versions of vexflow which didn't have right sided
// stave connectors
static get type() {
return {
SINGLE_RIGHT: 0,
SINGLE_LEFT: 1,
SINGLE: 1,
DOUBLE: 2,
BRACE: 3,
BRACKET: 4,
BOLD_DOUBLE_LEFT: 5,
BOLD_DOUBLE_RIGHT: 6,
THIN_DOUBLE: 7,
NONE: 8,
};
}
static get typeString() {
return {
singleRight: StaveConnector.type.SINGLE_RIGHT,
singleLeft: StaveConnector.type.SINGLE_LEFT,
single: StaveConnector.type.SINGLE,
double: StaveConnector.type.DOUBLE,
brace: StaveConnector.type.BRACE,
bracket: StaveConnector.type.BRACKET,
boldDoubleLeft: StaveConnector.type.BOLD_DOUBLE_LEFT,
boldDoubleRight: StaveConnector.type.BOLD_DOUBLE_RIGHT,
thinDouble: StaveConnector.type.THIN_DOUBLE,
none: StaveConnector.type.NONE,
};
}
constructor(top_stave, bottom_stave) {
super();
this.setAttribute('type', 'StaveConnector');
this.thickness = Flow.STAVE_LINE_THICKNESS;
this.width = 3;
this.top_stave = top_stave;
this.bottom_stave = bottom_stave;
this.type = StaveConnector.type.DOUBLE;
this.font = {
family: 'times',
size: 16,
weight: 'normal',
};
// 1. Offset Bold Double Left to align with offset Repeat Begin bars
// 2. Offset BRACE type not to overlap with another StaveConnector
this.x_shift = 0;
this.texts = [];
}
setType(type) {
type = typeof(type) === 'string'
? StaveConnector.typeString[type]
: type;
if (type >= StaveConnector.type.SINGLE_RIGHT && type <= StaveConnector.type.NONE) {
this.type = type;
}
return this;
}
setText(text, options) {
this.texts.push({
content: text,
options: Vex.Merge({ shift_x: 0, shift_y: 0 }, options),
});
return this;
}
setFont(font) {
Vex.Merge(this.font, font);
}
setXShift(x_shift) {
if (typeof x_shift !== 'number') {
throw Vex.RERR('InvalidType', 'x_shift must be a Number');
}
this.x_shift = x_shift;
return this;
}
draw() {
const ctx = this.checkContext();
this.setRendered();
let topY = this.top_stave.getYForLine(0);
let botY = this.bottom_stave.getYForLine(this.bottom_stave.getNumLines() - 1) +
this.thickness;
let width = this.width;
let topX = this.top_stave.getX();
const isRightSidedConnector = (
this.type === StaveConnector.type.SINGLE_RIGHT ||
this.type === StaveConnector.type.BOLD_DOUBLE_RIGHT ||
this.type === StaveConnector.type.THIN_DOUBLE
);
if (isRightSidedConnector) {
topX = this.top_stave.getX() + this.top_stave.width;
}
let attachment_height = botY - topY;
switch (this.type) {
case StaveConnector.type.SINGLE:
width = 1;
break;
case StaveConnector.type.SINGLE_LEFT:
width = 1;
break;
case StaveConnector.type.SINGLE_RIGHT:
width = 1;
break;
case StaveConnector.type.DOUBLE:
topX -= (this.width + 2);
break;
case StaveConnector.type.BRACE: {
width = 12;
// May need additional code to draw brace
const x1 = this.top_stave.getX() - 2 + this.x_shift;
const y1 = topY;
const x3 = x1;
const y3 = botY;
const x2 = x1 - width;
const y2 = y1 + attachment_height / 2.0;
const cpx1 = x2 - (0.90 * width);
const cpy1 = y1 + (0.2 * attachment_height);
const cpx2 = x1 + (1.10 * width);
const cpy2 = y2 - (0.135 * attachment_height);
const cpx3 = cpx2;
const cpy3 = y2 + (0.135 * attachment_height);
const cpx4 = cpx1;
const cpy4 = y3 - (0.2 * attachment_height);
const cpx5 = x2 - width;
const cpy5 = cpy4;
const cpx6 = x1 + (0.40 * width);
const cpy6 = y2 + (0.135 * attachment_height);
const cpx7 = cpx6;
const cpy7 = y2 - (0.135 * attachment_height);
const cpx8 = cpx5;
const cpy8 = cpy1;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x2, y2);
ctx.bezierCurveTo(cpx3, cpy3, cpx4, cpy4, x3, y3);
ctx.bezierCurveTo(cpx5, cpy5, cpx6, cpy6, x2, y2);
ctx.bezierCurveTo(cpx7, cpy7, cpx8, cpy8, x1, y1);
ctx.fill();
ctx.stroke();
break;
} case StaveConnector.type.BRACKET:
topY -= 4;
botY += 4;
attachment_height = botY - topY;
Glyph.renderGlyph(ctx, topX - 5, topY - 3, 40, 'v1b', true);
Glyph.renderGlyph(ctx, topX - 5, botY + 3, 40, 'v10', true);
topX -= (this.width + 2);
break;
case StaveConnector.type.BOLD_DOUBLE_LEFT:
drawBoldDoubleLine(ctx, this.type, topX + this.x_shift, topY, botY);
break;
case StaveConnector.type.BOLD_DOUBLE_RIGHT:
drawBoldDoubleLine(ctx, this.type, topX, topY, botY);
break;
case StaveConnector.type.THIN_DOUBLE:
width = 1;
break;
case StaveConnector.type.NONE:
break;
default:
throw new Vex.RERR(
'InvalidType', `The provided StaveConnector.type (${this.type}) is invalid`
);
}
if (
this.type !== StaveConnector.type.BRACE &&
this.type !== StaveConnector.type.BOLD_DOUBLE_LEFT &&
this.type !== StaveConnector.type.BOLD_DOUBLE_RIGHT &&
this.type !== StaveConnector.type.NONE
) {
ctx.fillRect(topX, topY, width, attachment_height);
}
// If the connector is a thin double barline, draw the paralell line
if (this.type === StaveConnector.type.THIN_DOUBLE) {
ctx.fillRect(topX - 3, topY, width, attachment_height);
}
ctx.save();
ctx.lineWidth = 2;
ctx.setFont(this.font.family, this.font.size, this.font.weight);
// Add stave connector text
for (let i = 0; i < this.texts.length; i++) {
const text = this.texts[i];
const text_width = ctx.measureText('' + text.content).width;
const x = this.top_stave.getX() - text_width - 24 + text.options.shift_x;
const y = (this.top_stave.getYForLine(0) + this.bottom_stave.getBottomLineY()) / 2 +
text.options.shift_y;
ctx.fillText('' + text.content, x, y + 4);
}
ctx.restore();
}
}