abcjs 6.0.0-beta.32 → 6.0.0-beta.36
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 +13 -7
- package/RELEASE.md +130 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +3763 -825
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/dist/report-basic.html +37 -0
- package/dist/report-before-glyph-compress.html +37 -0
- package/dist/report-brown-ts-target-es5.html +37 -0
- package/dist/report-dev-orig-no-babel.html +37 -0
- package/dist/report-synth.html +37 -0
- package/docker-build.sh +1 -0
- package/glyphs.json +1 -0
- package/package.json +9 -9
- package/src/api/abc_tablatures.js +144 -0
- package/src/api/abc_timing_callbacks.js +49 -26
- package/src/api/abc_tunebook.js +10 -1
- package/src/api/abc_tunebook_svg.js +16 -22
- package/src/data/abc_tune.js +90 -25
- package/src/data/deline-tune.js +199 -0
- package/src/edit/abc_editor.js +33 -11
- package/src/midi/abc_midi_create.js +6 -2
- package/src/parse/abc_parse.js +10 -6
- package/src/parse/abc_parse_directive.js +19 -12
- package/src/parse/abc_parse_header.js +12 -12
- package/src/parse/abc_parse_music.js +15 -5
- package/src/parse/tune-builder.js +23 -30
- package/src/parse/wrap_lines.js +13 -36
- package/src/synth/abc_midi_flattener.js +44 -29
- package/src/synth/abc_midi_sequencer.js +52 -13
- package/src/synth/create-synth.js +22 -7
- package/src/synth/load-note.js +31 -65
- package/src/synth/place-note.js +59 -60
- package/src/synth/register-audio-context.js +4 -1
- package/src/synth/supports-audio.js +9 -8
- package/src/synth/synth-controller.js +5 -3
- package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
- package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
- package/src/tablatures/instruments/string-patterns.js +277 -0
- package/src/tablatures/instruments/string-tablature.js +56 -0
- package/src/tablatures/instruments/tab-note.js +282 -0
- package/src/tablatures/instruments/tab-notes.js +41 -0
- package/src/tablatures/instruments/violin/tab-violin.js +47 -0
- package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
- package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
- package/src/tablatures/tab-absolute-elements.js +310 -0
- package/src/tablatures/tab-common.js +29 -0
- package/src/tablatures/tab-renderer.js +243 -0
- package/src/tablatures/transposer.js +110 -0
- package/src/test/abc_parser_lint.js +62 -6
- package/src/write/abc_absolute_element.js +2 -2
- package/src/write/abc_abstract_engraver.js +9 -7
- package/src/write/abc_create_key_signature.js +1 -0
- package/src/write/abc_create_note_head.js +1 -1
- package/src/write/abc_engraver_controller.js +22 -9
- package/src/write/abc_glyphs.js +5 -2
- package/src/write/abc_relative_element.js +11 -3
- package/src/write/abc_renderer.js +5 -1
- package/src/write/add-chord.js +5 -2
- package/src/write/add-text-if.js +33 -0
- package/src/write/bottom-text.js +8 -29
- package/src/write/draw/absolute.js +12 -14
- package/src/write/draw/brace.js +3 -3
- package/src/write/draw/crescendo.js +1 -1
- package/src/write/draw/draw.js +3 -4
- package/src/write/draw/dynamics.js +8 -1
- package/src/write/draw/ending.js +4 -3
- package/src/write/draw/group-elements.js +10 -8
- package/src/write/draw/non-music.js +11 -6
- package/src/write/draw/print-line.js +24 -0
- package/src/write/draw/print-stem.js +12 -11
- package/src/write/draw/print-symbol.js +11 -10
- package/src/write/draw/relative.js +33 -13
- package/src/write/draw/selectables.js +9 -6
- package/src/write/draw/staff-group.js +45 -9
- package/src/write/draw/staff-line.js +3 -17
- package/src/write/draw/staff.js +15 -2
- package/src/write/draw/tab-line.js +40 -0
- package/src/write/draw/tempo.js +7 -7
- package/src/write/draw/text.js +11 -4
- package/src/write/draw/tie.js +2 -2
- package/src/write/draw/triplet.js +3 -3
- package/src/write/draw/voice.js +10 -2
- package/src/write/format-jazz-chord.js +15 -0
- package/src/write/free-text.js +20 -12
- package/src/write/layout/VoiceElements.js +33 -1
- package/src/write/layout/beam.js +2 -0
- package/src/write/layout/staffGroup.js +37 -2
- package/src/write/layout/voice.js +2 -1
- package/src/write/selection.js +15 -5
- package/src/write/separator.js +1 -1
- package/src/write/subtitle.js +3 -3
- package/src/write/svg.js +41 -14
- package/src/write/top-text.js +19 -25
- package/types/index.d.ts +1007 -39
- package/version.js +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var sprintf = require('./sprintf');
|
|
2
|
+
var roundNumber = require('./round-number');
|
|
3
|
+
var printStem = require('./print-stem');
|
|
4
|
+
|
|
5
|
+
function TabLine(renderer , klass , dx , name) {
|
|
6
|
+
this.renderer = renderer;
|
|
7
|
+
if (!dx) dx = 0.35; // default
|
|
8
|
+
this.dx = dx;
|
|
9
|
+
this.klass = klass;
|
|
10
|
+
this.name = name;
|
|
11
|
+
var fill = renderer.foregroundColor;
|
|
12
|
+
this.options = { stroke: "none", fill: fill };
|
|
13
|
+
if (name)
|
|
14
|
+
this.options['data-name'] = name;
|
|
15
|
+
if (klass)
|
|
16
|
+
this.options['class'] = klass;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
TabLine.prototype.printVertical = function (y1, y2, x) {
|
|
20
|
+
return printStem(this.renderer,
|
|
21
|
+
x,
|
|
22
|
+
this.dx,
|
|
23
|
+
y1,
|
|
24
|
+
y2,
|
|
25
|
+
this.options.klass,
|
|
26
|
+
this.options.name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
TabLine.prototype.printHorizontal = function (x1, x2, y) {
|
|
30
|
+
x1 = roundNumber(x1);
|
|
31
|
+
x2 = roundNumber(x2);
|
|
32
|
+
var y1 = roundNumber(y - this.dx);
|
|
33
|
+
var y2 = roundNumber(y + this.dx);
|
|
34
|
+
this.options.path = sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y1, x2, y1,
|
|
35
|
+
x2, y2, x1, y2);
|
|
36
|
+
return this.renderer.paper.pathToBack(this.options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = TabLine;
|
|
40
|
+
|
package/src/write/draw/tempo.js
CHANGED
|
@@ -6,17 +6,17 @@ function drawTempo(renderer, params) {
|
|
|
6
6
|
if (params.pitch === undefined)
|
|
7
7
|
window.console.error("Tempo Element y-coordinate not set.");
|
|
8
8
|
|
|
9
|
-
var tempoGroup;
|
|
9
|
+
//var tempoGroup;
|
|
10
10
|
params.tempo.el_type = "tempo";
|
|
11
11
|
// renderer.wrapInAbsElem(params.tempo, "abcjs-tempo", function () {
|
|
12
|
-
renderer.paper.openGroup({klass: renderer.controller.classes.generate("tempo")});
|
|
12
|
+
//renderer.paper.openGroup({klass: renderer.controller.classes.generate("tempo wha")});
|
|
13
13
|
// The text is aligned with extra room for descenders but numbers look like they are a little too high, so bump it a little.
|
|
14
14
|
var descenderHeight = 2;
|
|
15
15
|
var y = renderer.calcY(params.pitch) + 2;
|
|
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, "dominant-baseline": "ideographic"});
|
|
19
|
+
text = renderText(renderer, {x:x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, "dominant-baseline": "ideographic", 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.
|
|
@@ -28,18 +28,18 @@ function drawTempo(renderer, params) {
|
|
|
28
28
|
drawRelativeElement(renderer, params.note.children[i], x);
|
|
29
29
|
x += (params.note.w + 5);
|
|
30
30
|
var str = "= " + params.tempo.bpm;
|
|
31
|
-
text = renderText(renderer, {x:x, y: y, text: str, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true});
|
|
31
|
+
text = renderText(renderer, {x:x, y: y, text: str, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "beats"});
|
|
32
32
|
size = renderer.controller.getTextSize.calc(str, 'tempofont', 'tempo', text);
|
|
33
33
|
var postWidth = size.width;
|
|
34
34
|
var charWidth2 = postWidth / str.length; // Just get some average number to increase the spacing.
|
|
35
35
|
x += postWidth + charWidth2;
|
|
36
36
|
}
|
|
37
37
|
if (params.tempo.postString) {
|
|
38
|
-
renderText(renderer, {x:x, y: y, text: params.tempo.postString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true});
|
|
38
|
+
renderText(renderer, {x:x, y: y, text: params.tempo.postString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "post"}, true);
|
|
39
39
|
}
|
|
40
|
-
tempoGroup = renderer.paper.closeGroup();
|
|
40
|
+
//tempoGroup = renderer.paper.closeGroup();
|
|
41
41
|
// });
|
|
42
|
-
return [tempoGroup];
|
|
42
|
+
//return [tempoGroup];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
module.exports = drawTempo;
|
package/src/write/draw/text.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var roundNumber = require("./round-number");
|
|
2
2
|
|
|
3
|
-
function renderText(renderer, params) {
|
|
3
|
+
function renderText(renderer, params, alreadyInGroup) {
|
|
4
4
|
var y = params.y;
|
|
5
5
|
if (params.lane) {
|
|
6
6
|
var laneMargin = params.dim.font.size*0.25;
|
|
@@ -23,12 +23,16 @@ function renderText(renderer, params) {
|
|
|
23
23
|
console.log("Debug msg: " + params.text);
|
|
24
24
|
hash.attr.stroke = "#ff0000";
|
|
25
25
|
}
|
|
26
|
+
if (params.cursor) {
|
|
27
|
+
hash.attr.cursor = params.cursor;
|
|
28
|
+
}
|
|
26
29
|
|
|
27
30
|
var text = params.text.replace(/\n\n/g, "\n \n");
|
|
28
31
|
text = text.replace(/^\n/, "\xA0\n");
|
|
29
32
|
|
|
30
33
|
if (hash.font.box) {
|
|
31
|
-
|
|
34
|
+
if (!alreadyInGroup)
|
|
35
|
+
renderer.paper.openGroup({klass: hash.attr['class'], fill: renderer.foregroundColor, "data-name": params.name});
|
|
32
36
|
if (hash.attr["text-anchor"] === "end") {
|
|
33
37
|
hash.attr.x -= hash.font.padding;
|
|
34
38
|
} else if (hash.attr["text-anchor"] === "start") {
|
|
@@ -41,6 +45,8 @@ function renderText(renderer, params) {
|
|
|
41
45
|
delete hash.attr['class'];
|
|
42
46
|
hash.attr.x = roundNumber(hash.attr.x);
|
|
43
47
|
hash.attr.y = roundNumber(hash.attr.y);
|
|
48
|
+
if (params.name)
|
|
49
|
+
hash.attr["data-name"] = params.name;
|
|
44
50
|
var elem = renderer.paper.text(text, hash.attr);
|
|
45
51
|
if (hash.font.box) {
|
|
46
52
|
var size = elem.getBBox();
|
|
@@ -55,8 +61,9 @@ function renderText(renderer, params) {
|
|
|
55
61
|
if (params.centerVertically) {
|
|
56
62
|
deltaY = size.height - hash.font.padding;
|
|
57
63
|
}
|
|
58
|
-
renderer.paper.rect({ x: Math.round(params.x - delta), y: Math.round(y - deltaY), width: Math.round(size.width + hash.font.padding*2), height: Math.round(size.height + hash.font.padding*2)});
|
|
59
|
-
|
|
64
|
+
renderer.paper.rect({ "data-name": "box", x: Math.round(params.x - delta), y: Math.round(y - deltaY), width: Math.round(size.width + hash.font.padding*2), height: Math.round(size.height + hash.font.padding*2)});
|
|
65
|
+
if (!alreadyInGroup)
|
|
66
|
+
elem = renderer.paper.closeGroup();
|
|
60
67
|
}
|
|
61
68
|
return elem;
|
|
62
69
|
}
|
package/src/write/draw/tie.js
CHANGED
|
@@ -83,12 +83,12 @@ var drawArc = function(renderer, x1, x2, pitch1, pitch2, above, klass, isTie, do
|
|
|
83
83
|
klass += ' dotted';
|
|
84
84
|
var pathString2 = sprintf("M %f %f C %f %f %f %f %f %f", x1, y1,
|
|
85
85
|
controlx1, controly1, controlx2, controly2, x2, y2);
|
|
86
|
-
ret = renderer.paper.path({path:pathString2, stroke:renderer.foregroundColor, fill:"none", 'stroke-dasharray': "5 5", 'class': renderer.controller.classes.generate(klass)});
|
|
86
|
+
ret = renderer.paper.path({path:pathString2, stroke:renderer.foregroundColor, fill:"none", 'stroke-dasharray': "5 5", 'class': renderer.controller.classes.generate(klass), "data-name": isTie ? "tie" : "slur"});
|
|
87
87
|
} else {
|
|
88
88
|
var pathString = sprintf("M %f %f C %f %f %f %f %f %f C %f %f %f %f %f %f z", x1, y1,
|
|
89
89
|
controlx1, controly1, controlx2, controly2, x2, y2,
|
|
90
90
|
roundNumber(controlx2 - thickness * uy), roundNumber(controly2 + thickness * ux), roundNumber(controlx1 - thickness * uy), roundNumber(controly1 + thickness * ux), x1, y1);
|
|
91
|
-
ret = renderer.paper.path({path:pathString, stroke:"none", fill:renderer.foregroundColor, 'class': renderer.controller.classes.generate(klass)});
|
|
91
|
+
ret = renderer.paper.path({path:pathString, stroke:"none", fill:renderer.foregroundColor, 'class': renderer.controller.classes.generate(klass), "data-name": isTie ? "tie" : "slur"});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
return ret;
|
|
@@ -4,12 +4,12 @@ var printPath = require('./print-path');
|
|
|
4
4
|
var roundNumber = require("./round-number");
|
|
5
5
|
|
|
6
6
|
function drawTriplet(renderer, params, selectables) {
|
|
7
|
-
renderer.paper.openGroup({ klass: renderer.controller.classes.generate('triplet '+params.durationClass)});
|
|
7
|
+
renderer.paper.openGroup({ klass: renderer.controller.classes.generate('triplet '+params.durationClass), "data-name": "triplet"});
|
|
8
8
|
if (!params.hasBeam) {
|
|
9
9
|
drawBracket(renderer, params.anchor1.x, params.startNote, params.anchor2.x + params.anchor2.w, params.endNote);
|
|
10
10
|
}
|
|
11
11
|
// HACK: adjust the position of "3". It is too high in all cases so we fudge it by subtracting 1 here.
|
|
12
|
-
renderText(renderer, {x: params.xTextPos, y: renderer.calcY(params.yTextPos - 1), text: "" + params.number, type: 'tripletfont', anchor: "middle", centerVertically: true, noClass: true});
|
|
12
|
+
renderText(renderer, {x: params.xTextPos, y: renderer.calcY(params.yTextPos - 1), text: "" + params.number, type: 'tripletfont', anchor: "middle", centerVertically: true, noClass: true, name: ""+params.number}, true);
|
|
13
13
|
var g = renderer.paper.closeGroup();
|
|
14
14
|
selectables.wrapSvgEl({ el_type: "triplet", startChar: -1, endChar: -1 }, g);
|
|
15
15
|
return g;
|
|
@@ -40,7 +40,7 @@ function drawBracket(renderer, x1, y1, x2, y2) {
|
|
|
40
40
|
var rightStartX = midX + gapWidth;
|
|
41
41
|
var rightStartY = y1 + (rightStartX - x1) * slope;
|
|
42
42
|
pathString += drawLine( rightStartX, rightStartY, x2, y2);
|
|
43
|
-
printPath(renderer, {path: pathString, stroke: renderer.foregroundColor});
|
|
43
|
+
printPath(renderer, {path: pathString, stroke: renderer.foregroundColor, "data-name": "triplet-bracket"});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
module.exports = drawTriplet;
|
package/src/write/draw/voice.js
CHANGED
|
@@ -12,7 +12,7 @@ function drawVoice(renderer, params, bartop, selectables, staffPos) {
|
|
|
12
12
|
renderer.staffbottom = params.staff.bottom;
|
|
13
13
|
|
|
14
14
|
if (params.header) { // print voice name
|
|
15
|
-
var textEl = renderText(renderer, {x: renderer.padding.left, y: renderer.calcY(params.headerPosition), text: params.header, type: 'voicefont', klass: 'staff-extra voice-name', anchor: 'start', centerVertically: true});
|
|
15
|
+
var textEl = renderText(renderer, {x: renderer.padding.left, y: renderer.calcY(params.headerPosition), text: params.header, type: 'voicefont', klass: 'staff-extra voice-name', anchor: 'start', centerVertically: true, name: "voice-name"}, true);
|
|
16
16
|
selectables.wrapSvgEl({ el_type: "voiceName", startChar: -1, endChar: -1, text: params.header }, textEl);
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -33,6 +33,14 @@ function drawVoice(renderer, params, bartop, selectables, staffPos) {
|
|
|
33
33
|
// child.elemset = drawTempo(renderer, child);
|
|
34
34
|
// break;
|
|
35
35
|
default:
|
|
36
|
+
if (params.staff.isTabStaff) {
|
|
37
|
+
child.invisible = false;
|
|
38
|
+
if ( child.type == 'bar' ) {
|
|
39
|
+
if (child.abcelem.lastBar) {
|
|
40
|
+
bartop = params.topLine;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
36
44
|
drawAbsolute(renderer, child,(params.barto || i === params.children.length - 1) ? bartop : 0, selectables, staffPos);
|
|
37
45
|
}
|
|
38
46
|
if (child.type === 'note' || isNonSpacerRest(child))
|
|
@@ -75,7 +83,7 @@ function drawVoice(renderer, params, bartop, selectables, staffPos) {
|
|
|
75
83
|
child.elemset = drawTie(renderer, child, params.startx + 10, width, selectables);
|
|
76
84
|
break;
|
|
77
85
|
default:
|
|
78
|
-
console.log(child)
|
|
86
|
+
console.log(child);
|
|
79
87
|
drawAbsolute(renderer, child, params.startx + 10, width, selectables, staffPos);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function formatJazzChord(chordString) {
|
|
2
|
+
// This puts markers in the pieces of the chord that are read by the svg creator.
|
|
3
|
+
// After the main part of the chord (the letter, a sharp or flat, and "m") a marker is added. Before a slash a marker is added.
|
|
4
|
+
var lines = chordString.split("\n");
|
|
5
|
+
for (var i = 0; i < lines.length; i++) {
|
|
6
|
+
var chord = lines[i];
|
|
7
|
+
// If the chord isn't in a recognizable format then just skip the formatting.
|
|
8
|
+
var reg = chord.match(/^([ABCDEFG][♯♭]?)?([^\/]+)?(\/[ABCDEFG][#b]?)?/);
|
|
9
|
+
if (reg)
|
|
10
|
+
lines[i] = (reg[1]?reg[1]:'') + "\x03" + (reg[2]?reg[2]:'') + "\x03" + (reg[3]?reg[3]:'');
|
|
11
|
+
}
|
|
12
|
+
return lines.join("\n");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = formatJazzChord;
|
package/src/write/free-text.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
function FreeText(
|
|
1
|
+
function FreeText(info, vskip, getFontAndAttr, paddingLeft, width, getTextSize) {
|
|
2
|
+
var text = info.text;
|
|
2
3
|
this.rows = [];
|
|
3
4
|
var size;
|
|
4
5
|
if (vskip)
|
|
@@ -8,23 +9,30 @@ function FreeText(text, vskip, getFontAndAttr, paddingLeft, width, getTextSize)
|
|
|
8
9
|
this.rows.push({move: hash.attr['font-size'] * 2}); // move the distance of the line, plus the distance of the margin, which is also one line.
|
|
9
10
|
} else if (typeof text === 'string') {
|
|
10
11
|
this.rows.push({move: hash.attr['font-size']/2}); // TODO-PER: move down some - the y location should be the top of the text, but we output text specifying the center line.
|
|
11
|
-
this.rows.push({left: paddingLeft, text: text, font: 'textfont', klass: 'defined-text', anchor: "start", absElemType: "freeText"});
|
|
12
|
+
this.rows.push({left: paddingLeft, text: text, font: 'textfont', klass: 'defined-text', anchor: "start", startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text"});
|
|
12
13
|
size = getTextSize.calc(text, 'textfont', 'defined-text');
|
|
13
14
|
this.rows.push({move: size.height});
|
|
14
|
-
} else {
|
|
15
|
+
} else if (text) {
|
|
16
|
+
var maxHeight = 0;
|
|
17
|
+
var leftSide = paddingLeft;
|
|
15
18
|
var currentFont = 'textfont';
|
|
16
|
-
var isCentered = false; // The structure is wrong here: it requires an array to do centering, but it shouldn't have.
|
|
17
19
|
for (var i = 0; i < text.length; i++) {
|
|
18
|
-
if (text[i].font)
|
|
20
|
+
if (text[i].font) {
|
|
19
21
|
currentFont = text[i].font;
|
|
20
|
-
else
|
|
22
|
+
} else
|
|
21
23
|
currentFont = 'textfont';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
this.rows.push({left: leftSide, text: text[i].text, font: currentFont, klass: 'defined-text', anchor: 'start', startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text"});
|
|
25
|
+
size = getTextSize.calc(text[i].text, getFontAndAttr.calc(currentFont, 'defined-text').font, 'defined-text');
|
|
26
|
+
leftSide += size.width + size.height/2; // add a little padding to the right side. The height of the font is probably a close enough approximation.
|
|
27
|
+
maxHeight = Math.max(maxHeight, size.height)
|
|
28
|
+
}
|
|
29
|
+
this.rows.push({move: maxHeight});
|
|
30
|
+
} else {
|
|
31
|
+
// The structure is wrong here: it requires an array to do centering, but it shouldn't have.
|
|
32
|
+
if (info.length === 1) {
|
|
33
|
+
var x = width / 2;
|
|
34
|
+
this.rows.push({left: x, text: info[0].text, font: 'textfont', klass: 'defined-text', anchor: 'middle', startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text"});
|
|
35
|
+
size = getTextSize.calc(info[0].text, 'textfont', 'defined-text');
|
|
28
36
|
this.rows.push({move: size.height});
|
|
29
37
|
}
|
|
30
38
|
}
|
|
@@ -27,11 +27,43 @@ VoiceElement.getSpacingUnits = function (voice) {
|
|
|
27
27
|
// x - position to try to layout the element at
|
|
28
28
|
// spacing - base spacing
|
|
29
29
|
// can't call this function more than once per iteration
|
|
30
|
-
VoiceElement.layoutOneItem = function (x, spacing, voice, minPadding) {
|
|
30
|
+
VoiceElement.layoutOneItem = function (x, spacing, voice, minPadding, firstVoice) {
|
|
31
31
|
var child = voice.children[voice.i];
|
|
32
32
|
if (!child) return 0;
|
|
33
33
|
var er = x - voice.minx; // available extrawidth to the left
|
|
34
34
|
var pad = voice.durationindex + child.duration > 0 ? minPadding : 0; // only add padding to the items that aren't fixed to the left edge.
|
|
35
|
+
// See if this item overlaps the item in the first voice. If firstVoice is undefined then there's nothing to compare.
|
|
36
|
+
if (child.abcelem.el_type === "note" && !child.abcelem.rest && voice.voicenumber !== 0 && firstVoice) {
|
|
37
|
+
var firstChild = firstVoice.children[firstVoice.i];
|
|
38
|
+
// It overlaps if the either the child's top or bottom is inside the firstChild's or at least within 1
|
|
39
|
+
// A special case is if the element is on the same line then it can share a note head, if the notehead is the same
|
|
40
|
+
var overlaps = firstChild &&
|
|
41
|
+
((child.abcelem.maxpitch <= firstChild.abcelem.maxpitch+1 && child.abcelem.maxpitch >= firstChild.abcelem.minpitch-1) ||
|
|
42
|
+
(child.abcelem.minpitch <= firstChild.abcelem.maxpitch+1 && child.abcelem.minpitch >= firstChild.abcelem.minpitch-1))
|
|
43
|
+
// See if they can share a note head
|
|
44
|
+
if (overlaps && child.abcelem.minpitch === firstChild.abcelem.minpitch && child.abcelem.maxpitch === firstChild.abcelem.maxpitch &&
|
|
45
|
+
firstChild.heads && firstChild.heads.length > 0 && child.heads && child.heads.length > 0 &&
|
|
46
|
+
firstChild.heads[0].c === child.heads[0].c)
|
|
47
|
+
overlaps = false;
|
|
48
|
+
// If this note overlaps the note in the first voice and we haven't moved the note yet (this can be called multiple times)
|
|
49
|
+
if (overlaps) {
|
|
50
|
+
// I think that firstChild should always have at least one note head, but defensively make sure.
|
|
51
|
+
// There was a problem with this being called more than once so if a value is adjusted then it is saved so it is only adjusted once.
|
|
52
|
+
var firstChildNoteWidth = firstChild.heads && firstChild.heads.length > 0 ? firstChild.heads[0].realWidth : firstChild.fixed.w;
|
|
53
|
+
if (!child.adjustedWidth)
|
|
54
|
+
child.adjustedWidth = firstChildNoteWidth + child.w;
|
|
55
|
+
child.w = child.adjustedWidth
|
|
56
|
+
for (var j = 0; j < child.children.length; j++) {
|
|
57
|
+
var relativeChild = child.children[j];
|
|
58
|
+
if (relativeChild.name.indexOf("accidental") < 0) {
|
|
59
|
+
if (!relativeChild.adjustedWidth)
|
|
60
|
+
relativeChild.adjustedWidth = relativeChild.dx + firstChildNoteWidth;
|
|
61
|
+
relativeChild.dx = relativeChild.adjustedWidth
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
}
|
|
35
67
|
var extraWidth = getExtraWidth(child, pad);
|
|
36
68
|
if (er<extraWidth) { // shift right by needed amount
|
|
37
69
|
// There's an exception if a bar element is after a Part element, there is no shift.
|
package/src/write/layout/beam.js
CHANGED
|
@@ -116,6 +116,8 @@ function createStems(elems, asc, beam, dy, mainNote) {
|
|
|
116
116
|
var ovalDelta = 1 / 5;//(isGrace)?1/3:1/5;
|
|
117
117
|
var pitch = furthestHead.pitch + ((asc) ? ovalDelta : -ovalDelta);
|
|
118
118
|
var dx = asc ? furthestHead.w : 0; // down-pointing stems start on the left side of the note, up-pointing stems start on the right side, so we offset by the note width.
|
|
119
|
+
if (!isGrace)
|
|
120
|
+
dx += furthestHead.dx;
|
|
119
121
|
var x = furthestHead.x + dx; // this is now the actual x location in pixels.
|
|
120
122
|
var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x);
|
|
121
123
|
var lineWidth = (asc) ? -0.6 : 0.6;
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
var layoutVoiceElements = require('./VoiceElements');
|
|
2
2
|
|
|
3
|
+
function checkLastBarX(voices) {
|
|
4
|
+
var maxX = 0;
|
|
5
|
+
for (var i = 0; i < voices.length; i++) {
|
|
6
|
+
var curVoice = voices[i];
|
|
7
|
+
var lastChild = curVoice.children.length - 1;
|
|
8
|
+
var maxChild = curVoice.children[lastChild];
|
|
9
|
+
if (maxChild.abcelem.el_type == 'bar') {
|
|
10
|
+
var barX = maxChild.children[0].x;
|
|
11
|
+
if (barX > maxX) {
|
|
12
|
+
maxX = barX;
|
|
13
|
+
} else {
|
|
14
|
+
maxChild.children[0].x = maxX;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
var layoutStaffGroup = function(spacing, renderer, debug, staffGroup, leftEdge) {
|
|
4
21
|
var epsilon = 0.0000001; // Fudging for inexactness of floating point math.
|
|
5
22
|
var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances)
|
|
@@ -44,7 +61,7 @@ var layoutStaffGroup = function(spacing, renderer, debug, staffGroup, leftEdge)
|
|
|
44
61
|
spacingunit = 0; // number of spacingunits coming from the previously laid out element to this one
|
|
45
62
|
var spacingduration = 0;
|
|
46
63
|
for (i=0;i<currentvoices.length;i++) {
|
|
47
|
-
//console.log("greatest spacing unit", x, currentvoices[i]
|
|
64
|
+
//console.log("greatest spacing unit", x, layoutVoiceElements.getNextX(currentvoices[i]), layoutVoiceElements.getSpacingUnits(currentvoices[i]), currentvoices[i].spacingduration);
|
|
48
65
|
if (layoutVoiceElements.getNextX(currentvoices[i])>x) {
|
|
49
66
|
x=layoutVoiceElements.getNextX(currentvoices[i]);
|
|
50
67
|
spacingunit=layoutVoiceElements.getSpacingUnits(currentvoices[i]);
|
|
@@ -55,8 +72,15 @@ var layoutStaffGroup = function(spacing, renderer, debug, staffGroup, leftEdge)
|
|
|
55
72
|
minspace = Math.min(minspace,spacingunit);
|
|
56
73
|
if (debug) console.log("currentduration: ",currentduration, spacingunits, minspace);
|
|
57
74
|
|
|
75
|
+
var lastTopVoice = undefined;
|
|
58
76
|
for (i=0;i<currentvoices.length;i++) {
|
|
59
|
-
var
|
|
77
|
+
var v = currentvoices[i];
|
|
78
|
+
if (v.voicenumber === 0)
|
|
79
|
+
lastTopVoice = i;
|
|
80
|
+
var topVoice = (lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber) ? currentvoices[lastTopVoice] : undefined;
|
|
81
|
+
if (!isSameStaff(v, topVoice))
|
|
82
|
+
topVoice = undefined;
|
|
83
|
+
var voicechildx = layoutVoiceElements.layoutOneItem(x,spacing, v, renderer.minPadding, topVoice);
|
|
60
84
|
var dx = voicechildx-x;
|
|
61
85
|
if (dx>0) {
|
|
62
86
|
x = voicechildx; //update x
|
|
@@ -87,6 +111,9 @@ var layoutStaffGroup = function(spacing, renderer, debug, staffGroup, leftEdge)
|
|
|
87
111
|
spacingunit=layoutVoiceElements.getSpacingUnits(staffGroup.voices[i]);
|
|
88
112
|
}
|
|
89
113
|
}
|
|
114
|
+
|
|
115
|
+
// adjust lastBar when needed (multi staves)
|
|
116
|
+
checkLastBarX(staffGroup.voices);
|
|
90
117
|
//console.log("greatest remaining",spacingunit,x);
|
|
91
118
|
spacingunits+=spacingunit;
|
|
92
119
|
staffGroup.setWidth(x);
|
|
@@ -106,4 +133,12 @@ function getDurationIndex(element) {
|
|
|
106
133
|
return element.durationindex - (element.children[element.i] && (element.children[element.i].duration>0)?0:0.0000005); // if the ith element doesn't have a duration (is not a note), its duration index is fractionally before. This enables CLEF KEYSIG TIMESIG PART, etc. to be laid out before we get to the first note of other voices
|
|
107
134
|
}
|
|
108
135
|
|
|
136
|
+
function isSameStaff(voice1, voice2) {
|
|
137
|
+
if (!voice1 || !voice1.staff || !voice1.staff.voices || voice1.staff.voices.length === 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (!voice2 || !voice2.staff || !voice2.staff.voices || voice2.staff.voices.length === 0)
|
|
140
|
+
return false;
|
|
141
|
+
return (voice1.staff.voices[0] === voice2.staff.voices[0]);
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
module.exports = layoutStaffGroup;
|
|
@@ -51,7 +51,8 @@ function moveDecorations(beam) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function placeInLane(rightMost, relElem) {
|
|
54
|
-
// These items are centered so figure the coordinates accordingly
|
|
54
|
+
// These items are centered so figure the coordinates accordingly.
|
|
55
|
+
// The font reports some extra space so the margin is built in.
|
|
55
56
|
var xCoords = relElem.getChordDim();
|
|
56
57
|
if (xCoords) {
|
|
57
58
|
for (var i = 0; i < rightMost.length; i++) {
|
package/src/write/selection.js
CHANGED
|
@@ -5,7 +5,7 @@ function setupSelection(engraver) {
|
|
|
5
5
|
if (engraver.dragging) {
|
|
6
6
|
for (var h = 0; h < engraver.selectables.length; h++) {
|
|
7
7
|
var hist = engraver.selectables[h];
|
|
8
|
-
if (hist.selectable) {
|
|
8
|
+
if (hist.svgEl.getAttribute("selectable") === "true") {
|
|
9
9
|
hist.svgEl.setAttribute("tabindex", 0);
|
|
10
10
|
hist.svgEl.setAttribute("data-index", h);
|
|
11
11
|
hist.svgEl.addEventListener("keydown", keyboardDown.bind(engraver));
|
|
@@ -79,7 +79,7 @@ function keyboardSelection(ev) {
|
|
|
79
79
|
this.dragTarget = this.selectables[index];
|
|
80
80
|
this.dragIndex = index;
|
|
81
81
|
this.dragMechanism = "keyboard";
|
|
82
|
-
mouseUp.bind(this)();
|
|
82
|
+
mouseUp.bind(this)(ev);
|
|
83
83
|
break;
|
|
84
84
|
case 38: // arrow up
|
|
85
85
|
handled = true;
|
|
@@ -107,7 +107,7 @@ function keyboardSelection(ev) {
|
|
|
107
107
|
case 9: // tab
|
|
108
108
|
// This is losing focus - if there had been dragging, then do the callback
|
|
109
109
|
if (this.dragYStep !== 0) {
|
|
110
|
-
mouseUp.bind(this)();
|
|
110
|
+
mouseUp.bind(this)(ev);
|
|
111
111
|
}
|
|
112
112
|
break;
|
|
113
113
|
default:
|
|
@@ -186,7 +186,7 @@ function getBestMatchCoordinates(dim, ev, scale) {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function getTarget(target) {
|
|
189
|
-
// This searches up the dom for the first item
|
|
189
|
+
// This searches up the dom for the first item containing the attribute "selectable", or stopping at the SVG.
|
|
190
190
|
if (target.tagName === "svg")
|
|
191
191
|
return target;
|
|
192
192
|
|
|
@@ -284,7 +284,7 @@ function setSelection(dragIndex) {
|
|
|
284
284
|
this.dragTarget = this.selectables[dragIndex];
|
|
285
285
|
this.dragIndex = dragIndex;
|
|
286
286
|
this.dragMechanism = "keyboard";
|
|
287
|
-
mouseUp.bind(this)();
|
|
287
|
+
mouseUp.bind(this)({target: this.dragTarget.svgEl});
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -311,6 +311,16 @@ function notifySelect(target, dragStep, dragMax, dragIndex, ev) {
|
|
|
311
311
|
}
|
|
312
312
|
if (target.staffPos)
|
|
313
313
|
analysis.staffPos = target.staffPos;
|
|
314
|
+
var closest = ev.target;
|
|
315
|
+
while (!closest.dataset.name && closest.tagName.toLowerCase() !== 'svg')
|
|
316
|
+
closest = closest.parentNode;
|
|
317
|
+
var parent = ev.target;
|
|
318
|
+
while (!parent.dataset.index && parent.tagName.toLowerCase() !== 'svg')
|
|
319
|
+
parent = parent.parentNode;
|
|
320
|
+
analysis.name = parent.dataset.name;
|
|
321
|
+
analysis.clickedName = closest.dataset.name;
|
|
322
|
+
analysis.parentClasses = parent.classList;
|
|
323
|
+
analysis.clickedClasses = closest.classList;
|
|
314
324
|
|
|
315
325
|
for (var i=0; i<this.listeners.length;i++) {
|
|
316
326
|
this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, { step: dragStep, max: dragMax, index: dragIndex, setSelection: setSelection.bind(this)}, ev);
|
package/src/write/separator.js
CHANGED
|
@@ -2,7 +2,7 @@ function Separator(spaceAbove, lineLength, spaceBelow) {
|
|
|
2
2
|
this.rows = [];
|
|
3
3
|
if (spaceAbove)
|
|
4
4
|
this.rows.push({move: spaceAbove});
|
|
5
|
-
this.rows.push({ separator: lineLength });
|
|
5
|
+
this.rows.push({ separator: lineLength, absElemType: "separator" });
|
|
6
6
|
if (spaceBelow)
|
|
7
7
|
this.rows.push({move: spaceBelow});
|
|
8
8
|
}
|
package/src/write/subtitle.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
function Subtitle(spaceAbove, formatting,
|
|
1
|
+
function Subtitle(spaceAbove, formatting, info, center, paddingLeft, getTextSize) {
|
|
2
2
|
this.rows = [];
|
|
3
3
|
if (spaceAbove)
|
|
4
4
|
this.rows.push({move: spaceAbove});
|
|
5
5
|
var tAnchor = formatting.titleleft ? 'start' : 'middle';
|
|
6
6
|
var tLeft = formatting.titleleft ? paddingLeft : center;
|
|
7
|
-
this.rows.push({left: tLeft, text: text, font: 'subtitlefont', klass: 'text subtitle', anchor: tAnchor});
|
|
8
|
-
var size = getTextSize.calc(text, 'subtitlefont', 'text subtitle');
|
|
7
|
+
this.rows.push({left: tLeft, text: info.text, font: 'subtitlefont', klass: 'text subtitle', anchor: tAnchor, startChar: info.startChar, endChar: info.endChar, absElemType: "subtitle", name: "subtitle"});
|
|
8
|
+
var size = getTextSize.calc(info.text, 'subtitlefont', 'text subtitle');
|
|
9
9
|
this.rows.push({move: size.height});
|
|
10
10
|
}
|
|
11
11
|
|
package/src/write/svg.js
CHANGED
|
@@ -6,6 +6,7 @@ var svgNS = "http://www.w3.org/2000/svg";
|
|
|
6
6
|
|
|
7
7
|
function Svg(wrapper) {
|
|
8
8
|
this.svg = createSvg();
|
|
9
|
+
this.currentGroup = [];
|
|
9
10
|
wrapper.appendChild(this.svg);
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@ Svg.prototype.clear = function() {
|
|
|
13
14
|
if (this.svg) {
|
|
14
15
|
var wrapper = this.svg.parentNode;
|
|
15
16
|
this.svg = createSvg();
|
|
17
|
+
this.currentGroup = [];
|
|
16
18
|
if (wrapper) {
|
|
17
19
|
// TODO-PER: If the wrapper is not present, then the underlying div was pulled out from under this instance. It's possible that is still useful (for creating the music off page?)
|
|
18
20
|
wrapper.innerHTML = "";
|
|
@@ -135,7 +137,7 @@ Svg.prototype.rect = function(attr) {
|
|
|
135
137
|
lines.push(constructVLine(x2, y1, y2));
|
|
136
138
|
lines.push(constructVLine(x1, y2, y1));
|
|
137
139
|
|
|
138
|
-
return this.path({ path: lines.join(" "), stroke: "none"});
|
|
140
|
+
return this.path({ path: lines.join(" "), stroke: "none", "data-name": attr["data-name"] });
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
Svg.prototype.dottedLine = function(attr) {
|
|
@@ -177,10 +179,29 @@ Svg.prototype.text = function(text, attr, target) {
|
|
|
177
179
|
var lines = (""+text).split("\n");
|
|
178
180
|
for (var i = 0; i < lines.length; i++) {
|
|
179
181
|
var line = document.createElementNS(svgNS, 'tspan');
|
|
180
|
-
line.textContent = lines[i];
|
|
181
182
|
line.setAttribute("x", attr.x ? attr.x : 0);
|
|
182
183
|
if (i !== 0)
|
|
183
184
|
line.setAttribute("dy", "1.2em");
|
|
185
|
+
if (lines[i].indexOf("\x03") !== -1) {
|
|
186
|
+
var parts = lines[i].split('\x03')
|
|
187
|
+
line.textContent = parts[0];
|
|
188
|
+
if (parts[1]) {
|
|
189
|
+
var ts2 = document.createElementNS(svgNS, 'tspan');
|
|
190
|
+
ts2.setAttribute("dy", "-0.3em");
|
|
191
|
+
ts2.setAttribute("style", "font-size:0.7em");
|
|
192
|
+
ts2.textContent = parts[1];
|
|
193
|
+
line.appendChild(ts2);
|
|
194
|
+
}
|
|
195
|
+
if (parts[2]) {
|
|
196
|
+
var dist = parts[1] ? "0.4em" : "0.1em";
|
|
197
|
+
var ts3 = document.createElementNS(svgNS, 'tspan');
|
|
198
|
+
ts3.setAttribute("dy", dist);
|
|
199
|
+
ts3.setAttribute("style", "font-size:0.7em");
|
|
200
|
+
ts3.textContent = parts[2];
|
|
201
|
+
line.appendChild(ts3);
|
|
202
|
+
}
|
|
203
|
+
} else
|
|
204
|
+
line.textContent = lines[i];
|
|
184
205
|
el.appendChild(line);
|
|
185
206
|
}
|
|
186
207
|
if (target)
|
|
@@ -252,8 +273,8 @@ Svg.prototype.getTextSize = function(text, attr, el) {
|
|
|
252
273
|
size = this.guessWidth(text, attr);
|
|
253
274
|
}
|
|
254
275
|
if (removeLater) {
|
|
255
|
-
if (this.currentGroup)
|
|
256
|
-
this.currentGroup.removeChild(el);
|
|
276
|
+
if (this.currentGroup.length > 0)
|
|
277
|
+
this.currentGroup[0].removeChild(el);
|
|
257
278
|
else
|
|
258
279
|
this.svg.removeChild(el);
|
|
259
280
|
}
|
|
@@ -271,18 +292,24 @@ Svg.prototype.openGroup = function(options) {
|
|
|
271
292
|
el.setAttribute("fill", options.fill);
|
|
272
293
|
if (options.stroke)
|
|
273
294
|
el.setAttribute("stroke", options.stroke);
|
|
295
|
+
if (options['data-name'])
|
|
296
|
+
el.setAttribute("data-name", options['data-name']);
|
|
274
297
|
|
|
275
298
|
if (options.prepend)
|
|
276
|
-
this.
|
|
299
|
+
this.prepend(el);
|
|
277
300
|
else
|
|
278
|
-
this.
|
|
279
|
-
this.currentGroup
|
|
301
|
+
this.append(el);
|
|
302
|
+
this.currentGroup.unshift(el);
|
|
280
303
|
return el;
|
|
281
304
|
};
|
|
282
305
|
|
|
283
306
|
Svg.prototype.closeGroup = function() {
|
|
284
|
-
var g = this.currentGroup;
|
|
285
|
-
|
|
307
|
+
var g = this.currentGroup.shift();
|
|
308
|
+
if (g && g.children.length === 0) {
|
|
309
|
+
// If nothing was added to the group it is because all the elements were invisible. We don't need the group, then.
|
|
310
|
+
this.svg.removeChild(g);
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
286
313
|
return g;
|
|
287
314
|
};
|
|
288
315
|
|
|
@@ -292,7 +319,7 @@ Svg.prototype.path = function(attr) {
|
|
|
292
319
|
if (attr.hasOwnProperty(key)) {
|
|
293
320
|
if (key === 'path')
|
|
294
321
|
el.setAttributeNS(null, 'd', attr.path);
|
|
295
|
-
else
|
|
322
|
+
else if (attr[key] !== undefined)
|
|
296
323
|
el.setAttributeNS(null, key, attr[key]);
|
|
297
324
|
}
|
|
298
325
|
}
|
|
@@ -315,16 +342,16 @@ Svg.prototype.pathToBack = function(attr) {
|
|
|
315
342
|
};
|
|
316
343
|
|
|
317
344
|
Svg.prototype.append = function(el) {
|
|
318
|
-
if (this.currentGroup)
|
|
319
|
-
this.currentGroup.appendChild(el);
|
|
345
|
+
if (this.currentGroup.length > 0)
|
|
346
|
+
this.currentGroup[0].appendChild(el);
|
|
320
347
|
else
|
|
321
348
|
this.svg.appendChild(el);
|
|
322
349
|
};
|
|
323
350
|
|
|
324
351
|
Svg.prototype.prepend = function(el) {
|
|
325
352
|
// The entire group is prepended, so don't prepend the individual elements.
|
|
326
|
-
if (this.currentGroup)
|
|
327
|
-
this.currentGroup.appendChild(el);
|
|
353
|
+
if (this.currentGroup.length > 0)
|
|
354
|
+
this.currentGroup[0].appendChild(el);
|
|
328
355
|
else
|
|
329
356
|
this.svg.insertBefore(el, this.svg.firstChild);
|
|
330
357
|
};
|