abcjs 6.2.3 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/RELEASE.md +84 -1
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +1775 -1034
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/index.js +3 -0
- package/package.json +1 -1
- package/plugin.js +1 -1
- package/src/api/abc_tablatures.js +48 -13
- package/src/api/tune-metrics.js +18 -0
- package/src/data/abc_tune.js +13 -2
- package/src/edit/abc_editarea.js +4 -1
- package/src/parse/abc_parse.js +2 -0
- package/src/parse/abc_parse_directive.js +23 -16
- package/src/parse/abc_parse_header.js +22 -19
- package/src/parse/abc_tokenizer.js +72 -7
- package/src/parse/tune-builder.js +60 -1
- package/src/synth/abc_midi_flattener.js +40 -462
- package/src/synth/abc_midi_sequencer.js +25 -10
- package/src/synth/chord-track.js +562 -0
- package/src/synth/create-note-map.js +2 -1
- package/src/synth/create-synth.js +91 -42
- package/src/synth/synth-controller.js +6 -2
- package/src/tablatures/instruments/string-patterns.js +11 -0
- package/src/tablatures/instruments/{violin/violin-patterns.js → tab-string-patterns.js} +6 -6
- package/src/tablatures/instruments/{violin/tab-violin.js → tab-string.js} +13 -11
- package/src/tablatures/tab-absolute-elements.js +16 -7
- package/src/tablatures/tab-renderer.js +22 -9
- package/src/test/abc_parser_lint.js +14 -12
- package/src/write/creation/abstract-engraver.js +6 -2
- package/src/write/creation/add-chord.js +102 -82
- package/src/write/creation/add-text-if.js +2 -2
- package/src/write/creation/decoration.js +16 -9
- package/src/write/creation/elements/bottom-text.js +62 -47
- package/src/write/creation/elements/rich-text.js +51 -0
- package/src/write/creation/elements/tie-element.js +23 -0
- package/src/write/creation/elements/top-text.js +37 -11
- package/src/write/creation/glyphs.js +1 -1
- package/src/write/draw/absolute.js +4 -1
- package/src/write/draw/draw.js +13 -4
- package/src/write/draw/non-music.js +3 -1
- package/src/write/draw/relative.js +1 -1
- package/src/write/draw/tempo.js +1 -1
- package/src/write/draw/text.js +10 -0
- package/src/write/engraver-controller.js +62 -17
- package/src/write/helpers/classes.js +1 -1
- package/src/write/helpers/get-font-and-attr.js +8 -1
- package/src/write/helpers/get-text-size.js +8 -1
- package/src/write/interactive/create-analysis.js +50 -0
- package/src/write/interactive/find-selectable-element.js +24 -0
- package/src/write/interactive/selection.js +5 -45
- package/src/write/layout/layout-in-grid.js +83 -0
- package/src/write/layout/layout.js +29 -24
- package/src/write/layout/set-upper-and-lower-elements.js +2 -0
- package/src/write/layout/staff-group.js +2 -2
- package/src/write/layout/voice-elements.js +1 -1
- package/src/write/layout/voice.js +1 -1
- package/src/write/renderer.js +3 -0
- package/src/write/svg.js +30 -0
- package/temp.txt +3 -0
- package/types/index.d.ts +142 -38
- package/version.js +1 -1
- package/.github/workflows/tests.yml +0 -29
- package/abc2xml_239/abc2xml.html +0 -769
- package/abc2xml_239/abc2xml.py +0 -2248
- package/abc2xml_239/abc2xml_changelog.html +0 -124
- package/abc2xml_239/lazy-river.abc +0 -26
- package/abc2xml_239/lazy-river.xml +0 -3698
- package/abc2xml_239/mean-to-me.abc +0 -22
- package/abc2xml_239/mean-to-me.xml +0 -2954
- package/abc2xml_239/pyparsing.py +0 -3672
- package/abc2xml_239/pyparsing.pyc +0 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +0 -23
- package/src/tablatures/instruments/guitar/tab-guitar.js +0 -48
package/src/write/draw/draw.js
CHANGED
|
@@ -6,7 +6,10 @@ var Selectables = require('./selectables');
|
|
|
6
6
|
|
|
7
7
|
function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset) {
|
|
8
8
|
var selectables = new Selectables(renderer.paper, selectTypes, tuneNumber);
|
|
9
|
-
|
|
9
|
+
var groupClasses = {}
|
|
10
|
+
if (classes.shouldAddClasses)
|
|
11
|
+
groupClasses.klass = "abcjs-meta-top"
|
|
12
|
+
renderer.paper.openGroup(groupClasses)
|
|
10
13
|
renderer.moveY(renderer.padding.top);
|
|
11
14
|
nonMusic(renderer, abcTune.topText, selectables);
|
|
12
15
|
renderer.paper.closeGroup()
|
|
@@ -16,7 +19,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
16
19
|
classes.incrLine();
|
|
17
20
|
var abcLine = abcTune.lines[line];
|
|
18
21
|
if (abcLine.staff) {
|
|
19
|
-
|
|
22
|
+
if (classes.shouldAddClasses)
|
|
23
|
+
groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber
|
|
24
|
+
renderer.paper.openGroup(groupClasses)
|
|
20
25
|
if (abcLine.vskip) {
|
|
21
26
|
renderer.moveY(abcLine.vskip);
|
|
22
27
|
}
|
|
@@ -27,7 +32,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
27
32
|
staffgroups.push(staffgroup);
|
|
28
33
|
renderer.paper.closeGroup()
|
|
29
34
|
} else if (abcLine.nonMusic) {
|
|
30
|
-
|
|
35
|
+
if (classes.shouldAddClasses)
|
|
36
|
+
groupClasses.klass = "abcjs-non-music"
|
|
37
|
+
renderer.paper.openGroup(groupClasses)
|
|
31
38
|
nonMusic(renderer, abcLine.nonMusic, selectables);
|
|
32
39
|
renderer.paper.closeGroup()
|
|
33
40
|
}
|
|
@@ -35,7 +42,9 @@ function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, se
|
|
|
35
42
|
|
|
36
43
|
classes.reset();
|
|
37
44
|
if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) {
|
|
38
|
-
|
|
45
|
+
if (classes.shouldAddClasses)
|
|
46
|
+
groupClasses.klass = "abcjs-meta-bottom"
|
|
47
|
+
renderer.paper.openGroup(groupClasses)
|
|
39
48
|
renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be?
|
|
40
49
|
nonMusic(renderer, abcTune.bottomText, selectables);
|
|
41
50
|
renderer.paper.closeGroup()
|
|
@@ -8,12 +8,14 @@ function nonMusic(renderer, obj, selectables) {
|
|
|
8
8
|
renderer.absolutemoveY(row.absmove);
|
|
9
9
|
} else if (row.move) {
|
|
10
10
|
renderer.moveY(row.move);
|
|
11
|
-
} else if (row.text) {
|
|
11
|
+
} else if (row.text || row.phrases) {
|
|
12
12
|
var x = row.left ? row.left : 0;
|
|
13
13
|
var el = renderText(renderer, {
|
|
14
14
|
x: x,
|
|
15
15
|
y: renderer.y,
|
|
16
16
|
text: row.text,
|
|
17
|
+
phrases: row.phrases,
|
|
18
|
+
'dominant-baseline': row['dominant-baseline'],
|
|
17
19
|
type: row.font,
|
|
18
20
|
klass: row.klass,
|
|
19
21
|
name: row.name,
|
|
@@ -27,7 +27,7 @@ function drawRelativeElement(renderer, params, bartop) {
|
|
|
27
27
|
case "tabNumber":
|
|
28
28
|
var hAnchor = "middle";
|
|
29
29
|
var tabFont = "tabnumberfont";
|
|
30
|
-
var tabClass = 'tab-number';
|
|
30
|
+
var tabClass = 'abcjs-tab-number';
|
|
31
31
|
if (params.isGrace) {
|
|
32
32
|
tabFont = "tabgracefont";
|
|
33
33
|
y += 2.5;
|
package/src/write/draw/tempo.js
CHANGED
|
@@ -16,7 +16,7 @@ function drawTempo(renderer, params) {
|
|
|
16
16
|
var text;
|
|
17
17
|
var size;
|
|
18
18
|
if (params.tempo.preString) {
|
|
19
|
-
text = renderText(renderer, { x: x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true,
|
|
19
|
+
text = renderText(renderer, { x: x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "pre" }, true);
|
|
20
20
|
size = renderer.controller.getTextSize.calc(params.tempo.preString, 'tempofont', 'tempo', text);
|
|
21
21
|
var preWidth = size.width;
|
|
22
22
|
var charWidth = preWidth / params.tempo.preString.length; // Just get some average number to increase the spacing.
|
package/src/write/draw/text.js
CHANGED
|
@@ -2,6 +2,14 @@ var roundNumber = require("./round-number");
|
|
|
2
2
|
|
|
3
3
|
function renderText(renderer, params, alreadyInGroup) {
|
|
4
4
|
var y = params.y;
|
|
5
|
+
|
|
6
|
+
// TODO-PER: Probably need to merge the regular text and rich text better. At the least, rich text loses the font box.
|
|
7
|
+
if (params.phrases) {
|
|
8
|
+
//richTextLine = function (phrases, x, y, klass, anchor, target)
|
|
9
|
+
var elem = renderer.paper.richTextLine(params.phrases, params.x, params.y, params.klass, params.anchor);
|
|
10
|
+
return elem;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
if (params.lane) {
|
|
6
14
|
var laneMargin = params.dim.font.size * 0.25;
|
|
7
15
|
y += (params.dim.font.size + laneMargin) * params.lane;
|
|
@@ -15,6 +23,8 @@ function renderText(renderer, params, alreadyInGroup) {
|
|
|
15
23
|
hash = renderer.controller.getFontAndAttr.calc(params.type, params.klass);
|
|
16
24
|
if (params.anchor)
|
|
17
25
|
hash.attr["text-anchor"] = params.anchor;
|
|
26
|
+
if (params['dominant-baseline'])
|
|
27
|
+
hash.attr["dominant-baseline"] = params['dominant-baseline'];
|
|
18
28
|
hash.attr.x = params.x;
|
|
19
29
|
hash.attr.y = y;
|
|
20
30
|
if (!params.centerVertically)
|
|
@@ -17,6 +17,7 @@ var GetFontAndAttr = require('./helpers/get-font-and-attr');
|
|
|
17
17
|
var GetTextSize = require('./helpers/get-text-size');
|
|
18
18
|
var draw = require('./draw/draw');
|
|
19
19
|
var tablatures = require('../api/abc_tablatures');
|
|
20
|
+
var findSelectableElement = require('./interactive/find-selectable-element');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @class
|
|
@@ -32,6 +33,7 @@ var tablatures = require('../api/abc_tablatures');
|
|
|
32
33
|
*/
|
|
33
34
|
var EngraverController = function (paper, params) {
|
|
34
35
|
params = params || {};
|
|
36
|
+
this.findSelectableElement = findSelectableElement;
|
|
35
37
|
this.oneSvgPerLine = params.oneSvgPerLine;
|
|
36
38
|
this.selectionColor = params.selectionColor;
|
|
37
39
|
this.dragColor = params.dragColor ? params.dragColor : params.selectionColor;
|
|
@@ -40,6 +42,7 @@ var EngraverController = function (paper, params) {
|
|
|
40
42
|
this.responsive = params.responsive;
|
|
41
43
|
this.space = 3 * spacing.SPACE;
|
|
42
44
|
this.initialClef = params.initialClef;
|
|
45
|
+
this.timeBasedLayout = params.timeBasedLayout;
|
|
43
46
|
this.expandToWidest = !!params.expandToWidest;
|
|
44
47
|
this.scale = params.scale ? parseFloat(params.scale) : 0;
|
|
45
48
|
this.classes = new Classes({ shouldAddClasses: params.add_classes });
|
|
@@ -65,6 +68,8 @@ var EngraverController = function (paper, params) {
|
|
|
65
68
|
this.renderer.showDebug = params.showDebug;
|
|
66
69
|
if (params.jazzchords)
|
|
67
70
|
this.jazzchords = params.jazzchords;
|
|
71
|
+
if (params.accentAbove)
|
|
72
|
+
this.accentAbove = params.accentAbove;
|
|
68
73
|
if (params.germanAlphabet)
|
|
69
74
|
this.germanAlphabet = params.germanAlphabet;
|
|
70
75
|
if (params.lineThickness)
|
|
@@ -123,12 +128,13 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
|
|
|
123
128
|
this.reset();
|
|
124
129
|
this.getFontAndAttr = new GetFontAndAttr(abcTune.formatting, this.classes);
|
|
125
130
|
this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper);
|
|
131
|
+
var origJazzChords = this.jazzchords
|
|
126
132
|
|
|
127
133
|
this.setupTune(abcTune, 0);
|
|
128
134
|
this.constructTuneElements(abcTune);
|
|
129
135
|
// layout() sets the x-coordinate of the abcTune element here:
|
|
130
136
|
// abcTune.lines[0].staffGroup.voices[0].children[0].x
|
|
131
|
-
layout(this.renderer, abcTune, 0, this.space);
|
|
137
|
+
layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout);
|
|
132
138
|
|
|
133
139
|
var ret = [];
|
|
134
140
|
var section;
|
|
@@ -170,6 +176,7 @@ EngraverController.prototype.getMeasureWidths = function (abcTune) {
|
|
|
170
176
|
} else
|
|
171
177
|
needNewSection = true;
|
|
172
178
|
}
|
|
179
|
+
this.jazzchords = origJazzChords
|
|
173
180
|
return ret;
|
|
174
181
|
};
|
|
175
182
|
|
|
@@ -178,6 +185,8 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
|
|
|
178
185
|
|
|
179
186
|
if (abcTune.formatting.jazzchords !== undefined)
|
|
180
187
|
this.jazzchords = abcTune.formatting.jazzchords;
|
|
188
|
+
if (abcTune.formatting.accentAbove !== undefined)
|
|
189
|
+
this.accentAbove = abcTune.formatting.accentAbove;
|
|
181
190
|
|
|
182
191
|
this.renderer.newTune(abcTune);
|
|
183
192
|
this.engraver = new AbstractEngraver(this.getTextSize, tuneNumber, {
|
|
@@ -187,6 +196,8 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
|
|
|
187
196
|
percmap: abcTune.formatting.percmap,
|
|
188
197
|
initialClef: this.initialClef,
|
|
189
198
|
jazzchords: this.jazzchords,
|
|
199
|
+
timeBasedLayout: this.timeBasedLayout,
|
|
200
|
+
accentAbove: this.accentAbove,
|
|
190
201
|
germanAlphabet: this.germanAlphabet
|
|
191
202
|
});
|
|
192
203
|
this.engraver.setStemHeight(this.renderer.spacing.stemHeight);
|
|
@@ -206,7 +217,7 @@ EngraverController.prototype.setupTune = function (abcTune, tuneNumber) {
|
|
|
206
217
|
};
|
|
207
218
|
|
|
208
219
|
EngraverController.prototype.constructTuneElements = function (abcTune) {
|
|
209
|
-
abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
|
|
220
|
+
abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
|
|
210
221
|
|
|
211
222
|
// Generate the raw staff line data
|
|
212
223
|
var i;
|
|
@@ -233,43 +244,77 @@ EngraverController.prototype.constructTuneElements = function (abcTune) {
|
|
|
233
244
|
abcLine.nonMusic = new Separator(abcLine.separator.spaceAbove, abcLine.separator.lineLength, abcLine.separator.spaceBelow);
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
|
-
abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.getTextSize);
|
|
247
|
+
abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
|
|
237
248
|
};
|
|
238
249
|
|
|
239
250
|
EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOffset) {
|
|
240
|
-
var scale = this.setupTune(abcTune, tuneNumber);
|
|
241
251
|
|
|
252
|
+
var origJazzChords = this.jazzchords
|
|
253
|
+
var scale = this.setupTune(abcTune, tuneNumber);
|
|
254
|
+
|
|
242
255
|
// Create all of the element objects that will appear on the page.
|
|
243
256
|
this.constructTuneElements(abcTune);
|
|
244
|
-
|
|
257
|
+
|
|
258
|
+
//Set the top text now that we know the width
|
|
259
|
+
|
|
245
260
|
// Do all the positioning, both horizontally and vertically
|
|
246
|
-
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest);
|
|
247
|
-
|
|
261
|
+
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout);
|
|
262
|
+
|
|
248
263
|
//Set the top text now that we know the width
|
|
249
|
-
if (this.expandToWidest && maxWidth > this.width+1) {
|
|
250
|
-
|
|
264
|
+
if (this.expandToWidest && maxWidth > this.width + 1) {
|
|
265
|
+
|
|
266
|
+
abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, maxWidth, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize);
|
|
267
|
+
|
|
268
|
+
if ((abcTune.lines)&&(abcTune.lines.length > 0)){
|
|
269
|
+
var nlines = abcTune.lines.length;
|
|
270
|
+
|
|
271
|
+
for (var i=0;i<nlines;++i){
|
|
272
|
+
var entry = abcTune.lines[i];
|
|
273
|
+
if (entry.nonMusic){
|
|
274
|
+
if ((entry.nonMusic.rows) && (entry.nonMusic.rows.length > 0)){
|
|
275
|
+
var nRows = entry.nonMusic.rows.length;
|
|
276
|
+
for (var j=0;j<nRows;++j){
|
|
277
|
+
var thisRow = entry.nonMusic.rows[j];
|
|
278
|
+
// Recenter the element if it's a subtitle or centered text
|
|
279
|
+
if (thisRow.left){
|
|
280
|
+
if (entry.subtitle){
|
|
281
|
+
thisRow.left = (maxWidth/2) + this.renderer.padding.left;
|
|
282
|
+
} else {
|
|
283
|
+
if ((entry.text)&&(entry.text.length>0)){
|
|
284
|
+
if (entry.text[0].center){
|
|
285
|
+
thisRow.left = (maxWidth/2) + this.renderer.padding.left;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
251
295
|
}
|
|
252
296
|
|
|
253
297
|
// Deal with tablature for staff
|
|
254
298
|
if (abcTune.tablatures) {
|
|
255
|
-
|
|
299
|
+
tablatures.layoutTablatures(this.renderer, abcTune);
|
|
256
300
|
}
|
|
257
|
-
|
|
301
|
+
|
|
258
302
|
// Do all the writing to the SVG
|
|
259
303
|
var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset);
|
|
260
304
|
this.staffgroups = ret.staffgroups;
|
|
261
305
|
this.selectables = ret.selectables;
|
|
262
|
-
|
|
263
306
|
if (this.oneSvgPerLine) {
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
var div = this.renderer.paper.svg.parentNode;
|
|
308
|
+
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale);
|
|
266
309
|
} else {
|
|
267
|
-
|
|
310
|
+
this.svgs = [this.renderer.paper.svg];
|
|
268
311
|
}
|
|
269
312
|
setupSelection(this, this.svgs);
|
|
313
|
+
|
|
314
|
+
this.jazzchords = origJazzChords
|
|
270
315
|
};
|
|
271
316
|
|
|
272
|
-
function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
317
|
+
function splitSvgIntoLines(renderer, output, title, responsive, scale) {
|
|
273
318
|
// Each line is a top level <g> in the svg. To split it into separate
|
|
274
319
|
// svgs iterate through each of those and put them in a new svg. Since
|
|
275
320
|
// they are placed absolutely, the viewBox needs to be manipulated to
|
|
@@ -294,7 +339,7 @@ function splitSvgIntoLines(renderer, output, title, responsive) {
|
|
|
294
339
|
var wrapper = document.createElement("div");
|
|
295
340
|
var divStyles = "overflow: hidden;"
|
|
296
341
|
if (responsive !== 'resize')
|
|
297
|
-
divStyles += "height:" + height + "px;"
|
|
342
|
+
divStyles += "height:" + (height * scale) + "px;"
|
|
298
343
|
wrapper.setAttribute("style", divStyles)
|
|
299
344
|
var svg = duplicateSvg(source)
|
|
300
345
|
var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1)
|
|
@@ -79,7 +79,7 @@ Classes.prototype.generate = function (c) {
|
|
|
79
79
|
return "";
|
|
80
80
|
var ret = [];
|
|
81
81
|
if (c && c.length > 0) ret.push(c);
|
|
82
|
-
if (c === "tab-number") // TODO-PER-HACK! straighten out the tablature
|
|
82
|
+
if (c === "abcjs-tab-number") // TODO-PER-HACK! straighten out the tablature
|
|
83
83
|
return ret.join(' ')
|
|
84
84
|
if (c === "text instrument-name")
|
|
85
85
|
return "abcjs-text abcjs-instrument-name"
|
|
@@ -14,6 +14,13 @@ GetFontAndAttr.prototype.updateFonts = function (fontOverrides) {
|
|
|
14
14
|
this.formatting.vocalfont = fontOverrides.vocalfont;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
GetFontAndAttr.prototype.getFamily = function (type) {
|
|
18
|
+
if (type[0] === '"' && type[type.length-1] === '"') {
|
|
19
|
+
return type.substring(1, type.length-1)
|
|
20
|
+
}
|
|
21
|
+
return type
|
|
22
|
+
};
|
|
23
|
+
|
|
17
24
|
GetFontAndAttr.prototype.calc = function (type, klass) {
|
|
18
25
|
var font;
|
|
19
26
|
if (typeof type === 'string') {
|
|
@@ -30,7 +37,7 @@ GetFontAndAttr.prototype.calc = function (type, klass) {
|
|
|
30
37
|
|
|
31
38
|
var attr = {
|
|
32
39
|
"font-size": font.size, 'font-style': font.style,
|
|
33
|
-
"font-family": font.face, 'font-weight': font.weight, 'text-decoration': font.decoration,
|
|
40
|
+
"font-family": this.getFamily(font.face), 'font-weight': font.weight, 'text-decoration': font.decoration,
|
|
34
41
|
'class': this.classes.generate(klass)
|
|
35
42
|
};
|
|
36
43
|
return { font: font, attr: attr };
|
|
@@ -11,6 +11,13 @@ GetTextSize.prototype.attr = function (type, klass) {
|
|
|
11
11
|
return this.getFontAndAttr.calc(type, klass);
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
GetTextSize.prototype.getFamily = function (type) {
|
|
15
|
+
if (type[0] === '"' && type[type.length-1] === '"') {
|
|
16
|
+
return type.substring(1, type.length-1)
|
|
17
|
+
}
|
|
18
|
+
return type
|
|
19
|
+
};
|
|
20
|
+
|
|
14
21
|
GetTextSize.prototype.calc = function (text, type, klass, el) {
|
|
15
22
|
var hash;
|
|
16
23
|
// This can be passed in either a string or a font. If it is a string it names one of the standard fonts.
|
|
@@ -28,7 +35,7 @@ GetTextSize.prototype.calc = function (text, type, klass, el) {
|
|
|
28
35
|
attr: {
|
|
29
36
|
"font-size": type.size,
|
|
30
37
|
"font-style": type.style,
|
|
31
|
-
"font-family": type.face,
|
|
38
|
+
"font-family": this.getFamily(type.face),
|
|
32
39
|
"font-weight": type.weight,
|
|
33
40
|
"text-decoration": type.decoration,
|
|
34
41
|
"class": this.getFontAndAttr.classes.generate(klass)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function findNumber(klass, match, target, name) {
|
|
2
|
+
if (klass.indexOf(match) === 0) {
|
|
3
|
+
var value = klass.replace(match, '');
|
|
4
|
+
var num = parseInt(value, 10);
|
|
5
|
+
if ('' + num === value)
|
|
6
|
+
target[name] = num;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createAnalysis(target, ev) {
|
|
11
|
+
var classes = [];
|
|
12
|
+
if (target.absEl.elemset) {
|
|
13
|
+
var classObj = {};
|
|
14
|
+
for (var j = 0; j < target.absEl.elemset.length; j++) {
|
|
15
|
+
var es = target.absEl.elemset[j];
|
|
16
|
+
if (es) {
|
|
17
|
+
var klass = es.getAttribute("class").split(' ');
|
|
18
|
+
for (var k = 0; k < klass.length; k++)
|
|
19
|
+
classObj[klass[k]] = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (var kk = 0; kk < Object.keys(classObj).length; kk++)
|
|
23
|
+
classes.push(Object.keys(classObj)[kk]);
|
|
24
|
+
}
|
|
25
|
+
var analysis = {};
|
|
26
|
+
for (var ii = 0; ii < classes.length; ii++) {
|
|
27
|
+
findNumber(classes[ii], "abcjs-v", analysis, "voice");
|
|
28
|
+
findNumber(classes[ii], "abcjs-l", analysis, "line");
|
|
29
|
+
findNumber(classes[ii], "abcjs-m", analysis, "measure");
|
|
30
|
+
}
|
|
31
|
+
if (target.staffPos)
|
|
32
|
+
analysis.staffPos = target.staffPos;
|
|
33
|
+
var closest = ev.target;
|
|
34
|
+
while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
|
|
35
|
+
closest = closest.parentNode;
|
|
36
|
+
var parent = ev.target;
|
|
37
|
+
while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
|
|
38
|
+
parent = parent.parentNode;
|
|
39
|
+
if (parent && parent.dataset) {
|
|
40
|
+
analysis.name = parent.dataset.name;
|
|
41
|
+
analysis.clickedName = closest.dataset.name;
|
|
42
|
+
analysis.parentClasses = parent.classList;
|
|
43
|
+
}
|
|
44
|
+
if (closest && closest.classList)
|
|
45
|
+
analysis.clickedClasses = closest.classList;
|
|
46
|
+
analysis.selectableElement = target.svgEl;
|
|
47
|
+
return {classes: classes, analysis: analysis}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = createAnalysis;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var createAnalysis = require('./create-analysis');
|
|
2
|
+
|
|
3
|
+
function findSelectableElement(event) {
|
|
4
|
+
var selectable = event
|
|
5
|
+
while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) {
|
|
6
|
+
selectable = selectable.parentNode
|
|
7
|
+
}
|
|
8
|
+
if (selectable && selectable.attributes && selectable.attributes.selectable) {
|
|
9
|
+
var index = selectable.attributes['data-index'].nodeValue
|
|
10
|
+
if (index) {
|
|
11
|
+
index = parseInt(index, 10)
|
|
12
|
+
if (index >= 0 && index < this.selectables.length) {
|
|
13
|
+
var element = this.selectables[index]
|
|
14
|
+
var ret = createAnalysis(element, event)
|
|
15
|
+
ret.index = index
|
|
16
|
+
ret.element = element
|
|
17
|
+
return ret
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = findSelectableElement;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var spacing = require('../helpers/spacing');
|
|
2
|
+
var createAnalysis = require('./create-analysis');
|
|
2
3
|
|
|
3
4
|
function setupSelection(engraver, svgs) {
|
|
4
5
|
engraver.rangeHighlight = rangeHighlight;
|
|
@@ -339,58 +340,17 @@ function setSelection(dragIndex) {
|
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
|
|
342
344
|
function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
|
|
343
|
-
var
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for (var j = 0; j < target.absEl.elemset.length; j++) {
|
|
347
|
-
var es = target.absEl.elemset[j];
|
|
348
|
-
if (es) {
|
|
349
|
-
var klass = es.getAttribute("class").split(' ');
|
|
350
|
-
for (var k = 0; k < klass.length; k++)
|
|
351
|
-
classObj[klass[k]] = true;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
for (var kk = 0; kk < Object.keys(classObj).length; kk++)
|
|
355
|
-
classes.push(Object.keys(classObj)[kk]);
|
|
356
|
-
}
|
|
357
|
-
var analysis = {};
|
|
358
|
-
for (var ii = 0; ii < classes.length; ii++) {
|
|
359
|
-
findNumber(classes[ii], "abcjs-v", analysis, "voice");
|
|
360
|
-
findNumber(classes[ii], "abcjs-l", analysis, "line");
|
|
361
|
-
findNumber(classes[ii], "abcjs-m", analysis, "measure");
|
|
362
|
-
}
|
|
363
|
-
if (target.staffPos)
|
|
364
|
-
analysis.staffPos = target.staffPos;
|
|
365
|
-
var closest = ev.target;
|
|
366
|
-
while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
|
|
367
|
-
closest = closest.parentNode;
|
|
368
|
-
var parent = ev.target;
|
|
369
|
-
while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
|
|
370
|
-
parent = parent.parentNode;
|
|
371
|
-
if (parent && parent.dataset) {
|
|
372
|
-
analysis.name = parent.dataset.name;
|
|
373
|
-
analysis.clickedName = closest.dataset.name;
|
|
374
|
-
analysis.parentClasses = parent.classList;
|
|
375
|
-
}
|
|
376
|
-
if (closest && closest.classList)
|
|
377
|
-
analysis.clickedClasses = closest.classList;
|
|
378
|
-
analysis.selectableElement = target.svgEl;
|
|
345
|
+
var ret = createAnalysis(target, ev)
|
|
346
|
+
var classes = ret.classes
|
|
347
|
+
var analysis = ret.analysis
|
|
379
348
|
|
|
380
349
|
for (var i = 0; i < this.listeners.length; i++) {
|
|
381
350
|
this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, { step: dragStep, max: dragMax, index: dragIndex, setSelection: setSelection.bind(this) }, ev);
|
|
382
351
|
}
|
|
383
352
|
}
|
|
384
353
|
|
|
385
|
-
function findNumber(klass, match, target, name) {
|
|
386
|
-
if (klass.indexOf(match) === 0) {
|
|
387
|
-
var value = klass.replace(match, '');
|
|
388
|
-
var num = parseInt(value, 10);
|
|
389
|
-
if ('' + num === value)
|
|
390
|
-
target[name] = num;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
354
|
function clearSelection() {
|
|
395
355
|
for (var i = 0; i < this.selected.length; i++) {
|
|
396
356
|
this.selected[i].unhighlight(undefined, this.renderer.foregroundColor);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
|
|
2
|
+
|
|
3
|
+
function layoutInGrid(renderer, staffGroup, timeBasedLayout) {
|
|
4
|
+
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
5
|
+
var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding)
|
|
6
|
+
var totalDuration = ret.totalDuration
|
|
7
|
+
var minSpacing = ret.minSpacing
|
|
8
|
+
var totalWidth = minSpacing * totalDuration
|
|
9
|
+
if (timeBasedLayout.minWidth)
|
|
10
|
+
totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth)
|
|
11
|
+
var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding/2 : 2 // If the padding isn't specified still give it some
|
|
12
|
+
|
|
13
|
+
staffGroup.startx = leftEdge
|
|
14
|
+
staffGroup.w = totalWidth + leftEdge
|
|
15
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
16
|
+
var voice = staffGroup.voices[i]
|
|
17
|
+
voice.startx = leftEdge
|
|
18
|
+
voice.w = totalWidth + leftEdge
|
|
19
|
+
|
|
20
|
+
var x = leftEdge
|
|
21
|
+
var afterFixedLeft = false
|
|
22
|
+
var durationUnit = 0
|
|
23
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
24
|
+
var child = voice.children[j]
|
|
25
|
+
if (!afterFixedLeft) {
|
|
26
|
+
if (child.duration !== 0) {
|
|
27
|
+
// We got to the first music element on the line
|
|
28
|
+
afterFixedLeft = true
|
|
29
|
+
durationUnit = (totalWidth + leftEdge - x) / totalDuration
|
|
30
|
+
staffGroup.gridStart = x
|
|
31
|
+
} else {
|
|
32
|
+
// We are still doing the preliminary stuff - clef, time sig, etc.
|
|
33
|
+
child.x = x
|
|
34
|
+
x += child.w + child.minspacing
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (afterFixedLeft) {
|
|
38
|
+
if (timeBasedLayout.align === 'center')
|
|
39
|
+
child.x = x + (child.duration * durationUnit) / 2 - child.w / 2
|
|
40
|
+
else {
|
|
41
|
+
// left align with padding - but no padding for barlines, they should be right aligned.
|
|
42
|
+
// TODO-PER: it looks better to move bar lines one pixel to right. Not sure why.
|
|
43
|
+
if (child.duration === 0) {
|
|
44
|
+
child.x = x + 1 - child.w
|
|
45
|
+
} else {
|
|
46
|
+
// child.extraw has the width of the accidentals - push the note to the right to take that into consideration. It will be 0 if there is nothing to the left.
|
|
47
|
+
child.x = x + leftAlignPadding - child.extraw
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
x += child.duration * durationUnit
|
|
51
|
+
}
|
|
52
|
+
for (var k = 0; k < child.children.length; k++) {
|
|
53
|
+
var grandchild = child.children[k]
|
|
54
|
+
// some elements don't have a dx - Tempo, for instance
|
|
55
|
+
var dx = grandchild.dx ? grandchild.dx : 0
|
|
56
|
+
grandchild.x = child.x + dx
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
staffGroup.gridEnd = x
|
|
60
|
+
}
|
|
61
|
+
return totalWidth
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getTotalDuration(staffGroup, timeBasedLayout) {
|
|
65
|
+
var maxSpacing = 0
|
|
66
|
+
var maxCount = 0
|
|
67
|
+
for (var i = 0; i < staffGroup.voices.length; i++) {
|
|
68
|
+
var count = 0
|
|
69
|
+
var voice = staffGroup.voices[i]
|
|
70
|
+
for (var j = 0; j < voice.children.length; j++) {
|
|
71
|
+
var element = voice.children[j]
|
|
72
|
+
count += element.duration
|
|
73
|
+
if (element.duration) {
|
|
74
|
+
var width = (element.w+timeBasedLayout) / element.duration
|
|
75
|
+
maxSpacing = Math.max(maxSpacing, width)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
maxCount = Math.max(maxCount, count)
|
|
79
|
+
}
|
|
80
|
+
return { totalDuration: maxCount, minSpacing: maxSpacing}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = layoutInGrid;
|
|
@@ -2,8 +2,12 @@ var layoutVoice = require('./voice');
|
|
|
2
2
|
var setUpperAndLowerElements = require('./set-upper-and-lower-elements');
|
|
3
3
|
var layoutStaffGroup = require('./staff-group');
|
|
4
4
|
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
|
|
5
|
+
var layoutInGrid = require('./layout-in-grid');
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// This sets the "x" attribute on all the children in abctune.lines
|
|
8
|
+
// It also sets the "w" and "startx" attributes on "voices"
|
|
9
|
+
// It also sets the "w" and "startx" attributes on "voices.children"
|
|
10
|
+
var layout = function (renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
|
|
7
11
|
var i;
|
|
8
12
|
var abcLine;
|
|
9
13
|
// Adjust the x-coordinates to their absolute positions
|
|
@@ -12,7 +16,11 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
|
|
|
12
16
|
abcLine = abctune.lines[i];
|
|
13
17
|
if (abcLine.staff) {
|
|
14
18
|
// console.log("=== line", i)
|
|
15
|
-
var thisWidth
|
|
19
|
+
var thisWidth;
|
|
20
|
+
if (timeBasedLayout !== undefined)
|
|
21
|
+
thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);
|
|
22
|
+
else
|
|
23
|
+
thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
|
|
16
24
|
// console.log(thisWidth, maxWidth)
|
|
17
25
|
if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness
|
|
18
26
|
maxWidth = thisWidth
|
|
@@ -46,39 +54,36 @@ var layout = function (renderer, abctune, width, space, expandToWidest) {
|
|
|
46
54
|
var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) {
|
|
47
55
|
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
|
|
48
56
|
var newspace = space;
|
|
57
|
+
//dumpGroup("before", staffGroup)
|
|
49
58
|
for (var it = 0; it < 8; it++) { // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.)
|
|
50
59
|
// console.log("iteration", it)
|
|
51
|
-
|
|
52
|
-
var ret = layoutStaffGroup(newspace, renderer, debug, staffGroup, leftEdge);
|
|
53
|
-
// dumpGroup("after",staffGroup)
|
|
60
|
+
var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
|
|
54
61
|
newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
|
|
55
62
|
if (debug)
|
|
56
63
|
console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
|
|
57
64
|
if (newspace === null) break;
|
|
58
65
|
}
|
|
66
|
+
//dumpGroup("after",staffGroup)
|
|
59
67
|
centerWholeRests(staffGroup.voices);
|
|
60
68
|
return staffGroup.w - leftEdge
|
|
61
69
|
};
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// }
|
|
80
|
-
// console.log(label,output)
|
|
81
|
-
// }
|
|
71
|
+
function replacer(key, value) {
|
|
72
|
+
// Filtering out properties
|
|
73
|
+
if (key === 'parent') {
|
|
74
|
+
return 'parent';
|
|
75
|
+
}
|
|
76
|
+
if (key === 'beam') {
|
|
77
|
+
return 'beam';
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function dumpGroup(label, staffGroup) {
|
|
83
|
+
console.log("=================== " + label + " =========================")
|
|
84
|
+
console.log(staffGroup)
|
|
85
|
+
console.log(JSON.stringify(staffGroup, replacer, "\t"))
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
|
|
84
89
|
if (isLastLine) {
|
|
@@ -87,6 +87,8 @@ var setUpperAndLowerElements = function (renderer, staffGroup) {
|
|
|
87
87
|
if (addedSpace > 0)
|
|
88
88
|
staff.top += addedSpace;
|
|
89
89
|
}
|
|
90
|
+
staff.top += renderer.spacing.staffTopMargin / spacing.STEP
|
|
91
|
+
|
|
90
92
|
lastStaffBottom = 2 - staff.bottom; // the staff starts at position 2 and the bottom variable is negative. Therefore to find out how large the bottom is, we reverse the sign of the bottom, and add the 2 in.
|
|
91
93
|
|
|
92
94
|
// Now we need a little margin on the top, so we'll just throw that in.
|