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.
Files changed (97) hide show
  1. package/README.md +13 -7
  2. package/RELEASE.md +130 -0
  3. package/dist/abcjs-basic-min.js +2 -2
  4. package/dist/abcjs-basic.js +3763 -825
  5. package/dist/abcjs-basic.js.map +1 -1
  6. package/dist/abcjs-plugin-min.js +2 -2
  7. package/dist/report-basic.html +37 -0
  8. package/dist/report-before-glyph-compress.html +37 -0
  9. package/dist/report-brown-ts-target-es5.html +37 -0
  10. package/dist/report-dev-orig-no-babel.html +37 -0
  11. package/dist/report-synth.html +37 -0
  12. package/docker-build.sh +1 -0
  13. package/glyphs.json +1 -0
  14. package/package.json +9 -9
  15. package/src/api/abc_tablatures.js +144 -0
  16. package/src/api/abc_timing_callbacks.js +49 -26
  17. package/src/api/abc_tunebook.js +10 -1
  18. package/src/api/abc_tunebook_svg.js +16 -22
  19. package/src/data/abc_tune.js +90 -25
  20. package/src/data/deline-tune.js +199 -0
  21. package/src/edit/abc_editor.js +33 -11
  22. package/src/midi/abc_midi_create.js +6 -2
  23. package/src/parse/abc_parse.js +10 -6
  24. package/src/parse/abc_parse_directive.js +19 -12
  25. package/src/parse/abc_parse_header.js +12 -12
  26. package/src/parse/abc_parse_music.js +15 -5
  27. package/src/parse/tune-builder.js +23 -30
  28. package/src/parse/wrap_lines.js +13 -36
  29. package/src/synth/abc_midi_flattener.js +44 -29
  30. package/src/synth/abc_midi_sequencer.js +52 -13
  31. package/src/synth/create-synth.js +22 -7
  32. package/src/synth/load-note.js +31 -65
  33. package/src/synth/place-note.js +59 -60
  34. package/src/synth/register-audio-context.js +4 -1
  35. package/src/synth/supports-audio.js +9 -8
  36. package/src/synth/synth-controller.js +5 -3
  37. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  38. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  39. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  40. package/src/tablatures/instruments/string-patterns.js +277 -0
  41. package/src/tablatures/instruments/string-tablature.js +56 -0
  42. package/src/tablatures/instruments/tab-note.js +282 -0
  43. package/src/tablatures/instruments/tab-notes.js +41 -0
  44. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  45. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  46. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  47. package/src/tablatures/tab-absolute-elements.js +310 -0
  48. package/src/tablatures/tab-common.js +29 -0
  49. package/src/tablatures/tab-renderer.js +243 -0
  50. package/src/tablatures/transposer.js +110 -0
  51. package/src/test/abc_parser_lint.js +62 -6
  52. package/src/write/abc_absolute_element.js +2 -2
  53. package/src/write/abc_abstract_engraver.js +9 -7
  54. package/src/write/abc_create_key_signature.js +1 -0
  55. package/src/write/abc_create_note_head.js +1 -1
  56. package/src/write/abc_engraver_controller.js +22 -9
  57. package/src/write/abc_glyphs.js +5 -2
  58. package/src/write/abc_relative_element.js +11 -3
  59. package/src/write/abc_renderer.js +5 -1
  60. package/src/write/add-chord.js +5 -2
  61. package/src/write/add-text-if.js +33 -0
  62. package/src/write/bottom-text.js +8 -29
  63. package/src/write/draw/absolute.js +12 -14
  64. package/src/write/draw/brace.js +3 -3
  65. package/src/write/draw/crescendo.js +1 -1
  66. package/src/write/draw/draw.js +3 -4
  67. package/src/write/draw/dynamics.js +8 -1
  68. package/src/write/draw/ending.js +4 -3
  69. package/src/write/draw/group-elements.js +10 -8
  70. package/src/write/draw/non-music.js +11 -6
  71. package/src/write/draw/print-line.js +24 -0
  72. package/src/write/draw/print-stem.js +12 -11
  73. package/src/write/draw/print-symbol.js +11 -10
  74. package/src/write/draw/relative.js +33 -13
  75. package/src/write/draw/selectables.js +9 -6
  76. package/src/write/draw/staff-group.js +45 -9
  77. package/src/write/draw/staff-line.js +3 -17
  78. package/src/write/draw/staff.js +15 -2
  79. package/src/write/draw/tab-line.js +40 -0
  80. package/src/write/draw/tempo.js +7 -7
  81. package/src/write/draw/text.js +11 -4
  82. package/src/write/draw/tie.js +2 -2
  83. package/src/write/draw/triplet.js +3 -3
  84. package/src/write/draw/voice.js +10 -2
  85. package/src/write/format-jazz-chord.js +15 -0
  86. package/src/write/free-text.js +20 -12
  87. package/src/write/layout/VoiceElements.js +33 -1
  88. package/src/write/layout/beam.js +2 -0
  89. package/src/write/layout/staffGroup.js +37 -2
  90. package/src/write/layout/voice.js +2 -1
  91. package/src/write/selection.js +15 -5
  92. package/src/write/separator.js +1 -1
  93. package/src/write/subtitle.js +3 -3
  94. package/src/write/svg.js +41 -14
  95. package/src/write/top-text.js +19 -25
  96. package/types/index.d.ts +1007 -39
  97. 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
+
@@ -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;
@@ -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
- renderer.paper.openGroup({klass: hash.attr['class'], fill: renderer.foregroundColor});
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
- elem = renderer.paper.closeGroup();
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
  }
@@ -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;
@@ -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;
@@ -1,4 +1,5 @@
1
- function FreeText(text, vskip, getFontAndAttr, paddingLeft, width, getTextSize) {
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
- if (text[i].center)
23
- isCentered = true;
24
- var alignment = isCentered ? 'middle' : 'start';
25
- var x = isCentered ? width / 2 : paddingLeft;
26
- this.rows.push({left: x, text: text[i].text, font: currentFont, klass: 'defined-text', anchor: alignment, absElemType: "freeText"});
27
- size = getTextSize.calc(text[i].text, currentFont, 'defined-text');
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.
@@ -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].getNextX(), currentvoices[i].getSpacingUnits(), currentvoices[i].spacingduration);
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 voicechildx = layoutVoiceElements.layoutOneItem(x,spacing, currentvoices[i], renderer.minPadding);
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 and add a little margin.
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++) {
@@ -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 containig the attribute "selectable", or stopping at the SVG.
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);
@@ -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
  }
@@ -1,11 +1,11 @@
1
- function Subtitle(spaceAbove, formatting, text, center, paddingLeft, getTextSize) {
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.svg.insertBefore(el, this.svg.firstChild);
299
+ this.prepend(el);
277
300
  else
278
- this.svg.appendChild(el);
279
- this.currentGroup = el;
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
- this.currentGroup = null;
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
  };