abcjs-rails 1.11 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/abcjs/api/abc_animation.js +41 -1
  3. data/app/assets/javascripts/abcjs/api/abc_tunebook.js +5 -0
  4. data/app/assets/javascripts/abcjs/data/abc_tune.js +4 -3
  5. data/app/assets/javascripts/abcjs/edit/abc_editor.js +10 -0
  6. data/app/assets/javascripts/abcjs/parse/abc_parse.js +120 -19
  7. data/app/assets/javascripts/abcjs/parse/abc_parse_directive.js +456 -115
  8. data/app/assets/javascripts/abcjs/raphael.js +2 -2
  9. data/app/assets/javascripts/abcjs/write/abc_absolute_element.js +111 -4
  10. data/app/assets/javascripts/abcjs/write/abc_abstract_engraver.js +899 -0
  11. data/app/assets/javascripts/abcjs/write/abc_beam_element.js +263 -37
  12. data/app/assets/javascripts/abcjs/write/abc_create_clef.js +76 -0
  13. data/app/assets/javascripts/abcjs/write/abc_create_key_signature.js +41 -0
  14. data/app/assets/javascripts/abcjs/write/abc_create_time_signature.js +51 -0
  15. data/app/assets/javascripts/abcjs/write/{abc_cresendo_element.js → abc_crescendo_element.js} +32 -1
  16. data/app/assets/javascripts/abcjs/write/abc_decoration.js +321 -0
  17. data/app/assets/javascripts/abcjs/write/abc_dynamic_decoration.js +22 -1
  18. data/app/assets/javascripts/abcjs/write/abc_ending_element.js +31 -1
  19. data/app/assets/javascripts/abcjs/write/abc_engraver_controller.js +359 -0
  20. data/app/assets/javascripts/abcjs/write/abc_glyphs.js +119 -9
  21. data/app/assets/javascripts/abcjs/write/abc_layout.js +2 -2
  22. data/app/assets/javascripts/abcjs/write/abc_relative_element.js +106 -8
  23. data/app/assets/javascripts/abcjs/write/abc_renderer.js +754 -0
  24. data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +249 -9
  25. data/app/assets/javascripts/abcjs/write/abc_tempo_element.js +104 -0
  26. data/app/assets/javascripts/abcjs/write/abc_tie_element.js +69 -22
  27. data/app/assets/javascripts/abcjs/write/abc_triplet_element.js +77 -10
  28. data/app/assets/javascripts/abcjs/write/abc_voice_element.js +100 -8
  29. data/app/assets/javascripts/abcjs/write/abc_write.js +64 -68
  30. data/lib/abcjs-rails/version.rb +1 -1
  31. metadata +12 -4
@@ -22,48 +22,94 @@ if (!window.ABCJS)
22
22
  if (!window.ABCJS.write)
23
23
  window.ABCJS.write = {};
24
24
 
25
- ABCJS.write.TieElem = function(anchor1, anchor2, above, forceandshift) {
25
+ ABCJS.write.TieElem = function(anchor1, anchor2, above, forceandshift, isTie) {
26
26
  this.anchor1 = anchor1; // must have a .x and a .pitch, and a .parent property or be null (means starts at the "beginning" of the line - after keysig)
27
27
  this.anchor2 = anchor2; // must have a .x and a .pitch property or be null (means ends at the end of the line)
28
28
  this.above = above; // true if the arc curves above
29
29
  this.force = forceandshift; // force the arc curve, regardless of beaming if true
30
- // move by +7 "up" by -7 if "down"
30
+ this.isTie = isTie;
31
31
  };
32
32
 
33
+ <<<<<<< HEAD
34
+ ABCJS.write.TieElem.prototype.setEndAnchor = function(anchor2) {
35
+ this.anchor2 = anchor2; // must have a .x and a .pitch property or be null (means ends at the end of the line)
36
+ };
37
+ =======
33
38
  ABCJS.write.TieElem.prototype.draw = function (renderer, linestartx, lineendx) {
34
39
  var startpitch;
35
40
  var endpitch;
41
+ >>>>>>> origin/master
36
42
 
37
- if (this.startlimitelem) {
38
- linestartx = this.startlimitelem.x+this.startlimitelem.w;
39
- }
43
+ // If we encounter a repeat sign, then we don't want to extend either a tie or a slur past it, so these are called to be a limit.
44
+ ABCJS.write.TieElem.prototype.setStartX = function(startLimitElem) {
45
+ this.startLimitX = startLimitElem;
46
+ };
40
47
 
41
- if (this.endlimitelem) {
42
- lineendx = this.endlimitelem.x;
48
+ ABCJS.write.TieElem.prototype.setEndX = function(endLimitElem) {
49
+ this.endLimitX = endLimitElem;
50
+ };
51
+
52
+ ABCJS.write.TieElem.prototype.setUpperAndLowerElements = function(positionY) {
53
+ // Doesn't depend on the highest and lowest, so there's nothing to do here.
54
+ };
55
+
56
+ ABCJS.write.TieElem.prototype.layout = function (lineStartX, lineEndX) {
57
+ function getPitch(anchor, isAbove, isTie) {
58
+ if (isTie) {
59
+ // Always go to the note
60
+ return anchor.pitch;
61
+ }
62
+ if (isAbove && anchor.highestVert !== undefined)
63
+ return anchor.highestVert;
64
+ return anchor.pitch;
43
65
  }
66
+ // We now have all of the input variables set, so we can figure out the start and ending x,y coordinates, and finalize the direction of the arc.
67
+
44
68
  // PER: We might have to override the natural slur direction if the first and last notes are not in the
45
- // save direction. We always put the slur up in this case. The one case that works out wrong is that we always
69
+ // same direction. We always put the slur up in this case. The one case that works out wrong is that we always
46
70
  // want the slur to be up when the last note is stem down. We can tell the stem direction if the top is
47
71
  // equal to the pitch: if so, there is no stem above it.
48
- if (!this.force && this.anchor2 && this.anchor2.pitch === this.anchor2.top)
72
+ if (!this.force && this.anchor2 && this.anchor2.pitch === this.anchor2.highestVert) // TODO-PER: this is a fragile way to detect that there is no stem going up on this note.
49
73
  this.above = true;
50
74
 
51
- if (this.anchor1) {
52
- linestartx = this.anchor1.x;
53
- startpitch = this.above ? this.anchor1.highestVert : this.anchor1.pitch;
54
- if (!this.anchor2) {
55
- endpitch = this.above ? this.anchor1.highestVert : this.anchor1.pitch;
56
- }
57
- }
75
+ if (this.anchor1)
76
+ this.startX = this.anchor1.x; // The normal case where there is a starting element to attach to.
77
+ else if (this.startLimitX)
78
+ this.startX = this.startLimitX.x+this.startLimitX.w; // if there is no start element, but there is a repeat mark before the start of the line.
79
+ else
80
+ this.startX = lineStartX; // There is no element and no repeat mark: extend to the beginning of the line.
58
81
 
59
- if (this.anchor2) {
60
- lineendx = this.anchor2.x;
61
- endpitch = this.above ? this.anchor2.highestVert : this.anchor2.pitch;
62
- if (!this.anchor1) {
63
- startpitch = this.above ? this.anchor2.highestVert : this.anchor2.pitch;
64
- }
82
+ if (this.anchor2)
83
+ this.endX = this.anchor2.x; // The normal case where there is a starting element to attach to.
84
+ else if (this.endLimitX)
85
+ this.endX = this.endLimitX.x; // if there is no start element, but there is a repeat mark before the start of the line.
86
+ else
87
+ this.endX = lineEndX; // There is no element and no repeat mark: extend to the beginning of the line.
88
+
89
+ // For the pitches, if one of the anchors is present, both of the pitches are that anchor. If both are present, then we use both. If neither is present, we use the top of the staff.
90
+ if (this.anchor1 && this.anchor2) {
91
+ this.startY = getPitch(this.anchor1, this.above, this.isTie);
92
+ this.endY = getPitch(this.anchor2, this.above, this.isTie);
93
+ } else if (this.anchor1) {
94
+ this.startY = getPitch(this.anchor1, this.above, this.isTie);
95
+ this.endY = getPitch(this.anchor1, this.above, this.isTie);
96
+ } else if (this.anchor2) {
97
+ this.startY = getPitch(this.anchor2, this.above, this.isTie);
98
+ this.endY = getPitch(this.anchor2, this.above, this.isTie);
99
+ } else {
100
+ // This is the case where the slur covers the entire line.
101
+ // TODO-PER: figure out where the real top and bottom of the line are.
102
+ this.startY = this.above ? 14 : 0;
103
+ this.endY = this.above ? 14 : 0;
65
104
  }
105
+ };
106
+
107
+ ABCJS.write.TieElem.prototype.draw = function (renderer, linestartx, lineendx) {
108
+ this.layout(linestartx, lineendx);
66
109
 
110
+ <<<<<<< HEAD
111
+ renderer.drawArc(this.startX, this.endX, this.startY, this.endY, this.above);
112
+ =======
67
113
  // if (this.anchor1 && this.anchor2) {
68
114
  // if ((!this.force && this.anchor1.parent.beam && this.anchor2.parent.beam &&
69
115
  // this.anchor1.parent.beam.asc===this.anchor2.parent.beam.asc) ||
@@ -79,5 +125,6 @@ ABCJS.write.TieElem.prototype.draw = function (renderer, linestartx, lineendx) {
79
125
 
80
126
  // renderer.debugMsgLow(linestartx, debugMsg);
81
127
  renderer.drawArc(linestartx, lineendx, startpitch, endpitch, this.above);
128
+ >>>>>>> origin/master
82
129
 
83
130
  };
@@ -22,13 +22,63 @@ if (!window.ABCJS)
22
22
  if (!window.ABCJS.write)
23
23
  window.ABCJS.write = {};
24
24
 
25
- ABCJS.write.TripletElem = function(number, anchor1, anchor2, above) {
26
- this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after keysig)
27
- this.anchor2 = anchor2; // must have a .x property or be null (means ends at the end of the line)
28
- this.above = above;
29
- this.number = number;
30
- };
25
+ (function() {
26
+ "use strict";
27
+
28
+ <<<<<<< HEAD
29
+ ABCJS.write.TripletElem = function(number, anchor1) {
30
+ this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after keysig)
31
+ this.number = number;
32
+ };
33
+
34
+ ABCJS.write.TripletElem.prototype.setCloseAnchor = function(anchor2) {
35
+ this.anchor2 = anchor2;
36
+ };
37
+
38
+ ABCJS.write.TripletElem.prototype.setUpperAndLowerElements = function(/*positionY*/) {
39
+ };
40
+
41
+ ABCJS.write.TripletElem.prototype.draw = function(renderer) {
42
+ // TODO end and beginning of line (PER: P.S. I'm not sure this can happen: I think the parser will always specify both the start and end points.)
43
+ if (this.anchor1 && this.anchor2) {
44
+ var xTextPos;
45
+ var yTextPos;
46
+ var hasBeam = this.anchor1.parent.beam && this.anchor1.parent.beam === this.anchor2.parent.beam;
47
+
48
+ if (hasBeam) {
49
+ // If there is a beam then we don't need to draw anything except the text. The beam could either be above or below.
50
+ var beam = this.anchor1.parent.beam;
51
+ var left = beam.isAbove() ? this.anchor1.x + this.anchor1.w : this.anchor1.x;
52
+ xTextPos = beam.xAtMidpoint(left, this.anchor2.x);
53
+ yTextPos = beam.heightAtMidpoint(left, this.anchor2.x);
54
+ yTextPos += beam.isAbove() ? 4 : -4; // This creates some space between the beam and the number.
55
+ } else {
56
+ // If there isn't a beam, then we need to draw the bracket and the text. The bracket is always above.
57
+ // The bracket is never lower than the 'a' line, but is 4 pitches above the first and last notes. If there is
58
+ // a tall note in the middle, the bracket is horizontal and above the highest note.
59
+ var startNote = Math.max(this.anchor1.parent.top, 9) + 4;
60
+ var endNote = Math.max(this.anchor2.parent.top, 9) + 4;
61
+ // TODO-PER: Do the case where the middle note is really high.
62
+ xTextPos = this.anchor1.x + (this.anchor2.x + this.anchor2.w - this.anchor1.x) / 2;
63
+ yTextPos = startNote + (endNote - startNote) / 2;
64
+ drawBracket(renderer, this.anchor1.x, startNote, this.anchor2.x + this.anchor2.w, endNote);
65
+ }
31
66
 
67
+ renderer.renderText(xTextPos, renderer.calcY(yTextPos), "" + this.number, 'tripletfont', "triplet", "middle", true);
68
+ }
69
+ };
70
+
71
+ function drawLine(renderer, l, t, r, b) {
72
+ var pathString = ABCJS.write.sprintf("M %f %f L %f %f",
73
+ l, t, r, b);
74
+ renderer.printPath({path: pathString, stroke: "#000000", 'class': renderer.addClasses('triplet')});
75
+ }
76
+
77
+ function drawBracket(renderer, x1, y1, x2, y2) {
78
+ y1 = renderer.calcY(y1);
79
+ y2 = renderer.calcY(y2);
80
+ var bracketHeight = 5;
81
+ =======
32
82
  ABCJS.write.TripletElem.prototype.draw = function (renderer, linestartx, lineendx) {
33
83
  // TODO end and beginning of line
34
84
  if (this.anchor1 && this.anchor2) {
@@ -55,9 +105,7 @@ ABCJS.write.TripletElem.prototype.draw = function (renderer, linestartx, lineend
55
105
  xsum += this.anchor2.w;
56
106
  }
57
107
 
58
-
59
- renderer.printText(xsum/2, ypos+ydelta, this.number, "middle", 'triplet').attr({"font-size":"10px", 'font-style': 'italic' });
60
-
108
+ renderer.renderText(xsum/2, renderer.calcY(ypos+ydelta), this.number, 'annotationfont', "middle", "triplet"); // TODO-PER: There doesn't seem to be a tripletfont defined.
61
109
  }
62
110
  };
63
111
 
@@ -76,10 +124,29 @@ ABCJS.write.TripletElem.prototype.drawLine = function (renderer, y) {
76
124
  pathString = ABCJS.write.sprintf("M %f %f L %f %f",
77
125
  linestartx, y, (linestartx+lineendx)/2-5, y);
78
126
  renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
127
+ >>>>>>> origin/master
79
128
 
129
+ // Draw vertical lines at the beginning and end
130
+ drawLine(renderer, x1, y1, x1, y1 + bracketHeight);
131
+ drawLine(renderer, x2, y2, x2, y2 + bracketHeight);
80
132
 
133
+ <<<<<<< HEAD
134
+ // figure out midpoints to draw the broken line.
135
+ var midX = x1 + (x2-x1)/2;
136
+ var midY = y1 + (y2-y1)/2;
137
+ var gapWidth = 8;
138
+ var slope = (y2 - y1) / (x2 - x1);
139
+ var leftEndX = midX - gapWidth;
140
+ var leftEndY = y1 + (leftEndX - x1) * slope;
141
+ drawLine(renderer, x1, y1, leftEndX, leftEndY);
142
+ var rightStartX = midX + gapWidth;
143
+ var rightStartY = y1 + (rightStartX - x1) * slope;
144
+ drawLine(renderer, rightStartX, rightStartY, x2, y2);
145
+ }
146
+ })();
147
+ =======
81
148
  pathString = ABCJS.write.sprintf("M %f %f L %f %f",
82
149
  (linestartx+lineendx)/2+5, y, lineendx, y);
83
150
  renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
151
+ >>>>>>> origin/master
84
152
 
85
- };
@@ -30,6 +30,22 @@ ABCJS.write.VoiceElement = function(voicenumber, voicetotal) {
30
30
  this.duplicate = false;
31
31
  this.voicenumber = voicenumber; //number of the voice on a given stave (not staffgroup)
32
32
  this.voicetotal = voicetotal;
33
+ this.bottom = 7;
34
+ this.top = 7;
35
+ this.specialY = {
36
+ tempoHeightAbove: 0,
37
+ partHeightAbove: 0,
38
+ volumeHeightAbove: 0,
39
+ dynamicHeightAbove: 0,
40
+ endingHeightAbove: 0,
41
+ chordHeightAbove: 0,
42
+ lyricHeightAbove: 0,
43
+
44
+ lyricHeightBelow: 0,
45
+ chordHeightBelow: 0,
46
+ volumeHeightBelow: 0,
47
+ dynamicHeightBelow: 0
48
+ };
33
49
  };
34
50
 
35
51
  ABCJS.write.VoiceElement.prototype.addChild = function (child) {
@@ -45,6 +61,55 @@ ABCJS.write.VoiceElement.prototype.addChild = function (child) {
45
61
  }
46
62
  }
47
63
  this.children[this.children.length] = child;
64
+ this.setRange(child);
65
+ };
66
+
67
+ ABCJS.write.VoiceElement.prototype.setLimit = function(member, child) {
68
+ // Sometimes we get an absolute element in here and sometimes we get some type of relative element.
69
+ // If there is a "specialY" element, then assume it is an absolute element. If that doesn't exist, look for the
70
+ // same members at the top level, because that's where they are in relative elements.
71
+ var specialY = child.specialY;
72
+ if (!specialY) specialY = child;
73
+ if (!specialY[member]) return;
74
+ if (!this.specialY[member])
75
+ this.specialY[member] = specialY[member];
76
+ else
77
+ this.specialY[member] = Math.max(this.specialY[member], specialY[member]);
78
+ };
79
+
80
+ ABCJS.write.VoiceElement.prototype.adjustRange = function(child) {
81
+ if (child.bottom !== undefined)
82
+ this.bottom = Math.min(this.bottom, child.bottom);
83
+ if (child.top !== undefined)
84
+ this.top = Math.max(this.top, child.top);
85
+ };
86
+
87
+ ABCJS.write.VoiceElement.prototype.setRange = function(child) {
88
+ this.adjustRange(child);
89
+ this.setLimit('tempoHeightAbove', child);
90
+ this.setLimit('partHeightAbove', child);
91
+ this.setLimit('volumeHeightAbove', child);
92
+ this.setLimit('dynamicHeightAbove', child);
93
+ this.setLimit('endingHeightAbove', child);
94
+ this.setLimit('chordHeightAbove', child);
95
+ this.setLimit('lyricHeightAbove', child);
96
+ this.setLimit('lyricHeightBelow', child);
97
+ this.setLimit('chordHeightBelow', child);
98
+ this.setLimit('volumeHeightBelow', child);
99
+ this.setLimit('dynamicHeightBelow', child);
100
+ };
101
+
102
+ ABCJS.write.VoiceElement.prototype.setUpperAndLowerElements = function(positionY) {
103
+ var i;
104
+ for (i = 0; i < this.children.length; i++) {
105
+ var abselem = this.children[i];
106
+ abselem.setUpperAndLowerElements(positionY);
107
+ }
108
+ for (i = 0; i < this.otherchildren.length; i++) {
109
+ var abselem = this.otherchildren[i];
110
+ if (typeof abselem !== 'string')
111
+ abselem.setUpperAndLowerElements(positionY);
112
+ }
48
113
  };
49
114
 
50
115
  ABCJS.write.VoiceElement.prototype.addOther = function (child) {
@@ -52,6 +117,7 @@ ABCJS.write.VoiceElement.prototype.addOther = function (child) {
52
117
  this.beams.push(child);
53
118
  } else {
54
119
  this.otherchildren.push(child);
120
+ this.setRange(child);
55
121
  }
56
122
  };
57
123
 
@@ -73,7 +139,9 @@ ABCJS.write.VoiceElement.prototype.getDurationIndex = function () {
73
139
 
74
140
  // number of spacing units expected for next positioning
75
141
  ABCJS.write.VoiceElement.prototype.getSpacingUnits = function () {
76
- return (this.minx<this.nextx) ? Math.sqrt(this.spacingduration*8) : 0; // we haven't used any spacing units if we end up using minx
142
+ return Math.sqrt(this.spacingduration*8);
143
+ // TODO-PER: On short lines, this would never trigger, so the spacing was wrong. I just changed this line empirically, though, so I don't know if there are other ramifications.
144
+ //return (this.minx<this.nextx) ? Math.sqrt(this.spacingduration*8) : 0; // we haven't used any spacing units if we end up using minx
77
145
  };
78
146
 
79
147
  //
@@ -84,7 +152,7 @@ ABCJS.write.VoiceElement.prototype.getNextX = function () {
84
152
  ABCJS.write.VoiceElement.prototype.beginLayout = function (startx) {
85
153
  this.i=0;
86
154
  this.durationindex=0;
87
- this.ii=this.children.length;
155
+ //this.ii=this.children.length;
88
156
  this.startx=startx;
89
157
  this.minx=startx; // furthest left to where negatively positioned elements are allowed to go
90
158
  this.nextx=startx; // x position where the next element of this voice should be placed assuming no other voices and no fixed width constraints
@@ -102,18 +170,18 @@ ABCJS.write.VoiceElement.prototype.layoutOneItem = function (x, spacing) {
102
170
  if (er<child.getExtraWidth()) { // shift right by needed amount
103
171
  x+=child.getExtraWidth()-er;
104
172
  }
105
- child.x=x; // place child at x
173
+ child.setX(x);
106
174
 
107
175
  this.spacingduration = child.duration;
108
176
  //update minx
109
177
  this.minx = x+child.getMinWidth(); // add necessary layout space
110
- if (this.i!==this.ii-1) this.minx+=child.minspacing; // add minimumspacing except on last elem
178
+ if (this.i!==this.children.length-1) this.minx+=child.minspacing; // add minimumspacing except on last elem
111
179
 
112
180
  this.updateNextX(x, spacing);
113
181
 
114
182
  // contribute to staff y position
115
- this.staff.highest = Math.max(child.top,this.staff.highest);
116
- this.staff.lowest = Math.min(child.bottom,this.staff.lowest);
183
+ //this.staff.top = Math.max(child.top,this.staff.top);
184
+ //this.staff.bottom = Math.min(child.bottom,this.staff.bottom);
117
185
 
118
186
  return x; // where we end up having placed the child
119
187
  };
@@ -126,13 +194,22 @@ ABCJS.write.VoiceElement.prototype.updateNextX = function (x, spacing) {
126
194
  ABCJS.write.VoiceElement.prototype.shiftRight = function (dx) {
127
195
  var child = this.children[this.i];
128
196
  if (!child) return;
129
- child.x+=dx;
197
+ child.setX(child.x+dx);
130
198
  this.minx+=dx;
131
199
  this.nextx+=dx;
132
200
  };
133
201
 
134
202
  ABCJS.write.VoiceElement.prototype.draw = function (renderer, bartop) {
135
203
  var width = this.w-1;
204
+ <<<<<<< HEAD
205
+ renderer.staffbottom = this.staff.bottom;
206
+ //this.barbottom = renderer.calcY(2);
207
+
208
+ renderer.measureNumber = null;
209
+ if (this.header) { // print voice name
210
+ var textpitch = 14 - (this.voicenumber+1)*(12/(this.voicetotal+1));
211
+ renderer.renderText(renderer.padding.left, renderer.calcY(textpitch), this.header, 'voicefont', 'staff-extra voice-name', 'start');
212
+ =======
136
213
  renderer.y = this.staff.y;
137
214
  renderer.staffbottom = this.staff.bottom;
138
215
  this.barbottom = renderer.calcY(2);
@@ -142,7 +219,8 @@ ABCJS.write.VoiceElement.prototype.draw = function (renderer, bartop) {
142
219
  var textpitch = 12 - (this.voicenumber+1)*(12/(this.voicetotal+1));
143
220
  var headerX = (this.startx-renderer.paddingleft)/2+renderer.paddingleft;
144
221
  headerX = headerX*renderer.scale;
145
- renderer.paper.text(headerX, renderer.calcY(textpitch)*renderer.scale, this.header).attr({"font-size":12*renderer.scale, "font-family":"serif", 'font-weight':'bold', 'class': renderer.addClasses('staff-extra voice-name')}); // code duplicated above
222
+ renderer.renderText(headerX, renderer.calcY(textpitch), this.header, 'voicefont', 'staff-extra voice-name');
223
+ >>>>>>> origin/master
146
224
  }
147
225
 
148
226
  for (var i=0, ii=this.children.length; i<ii; i++) {
@@ -175,3 +253,17 @@ ABCJS.write.VoiceElement.prototype.draw = function (renderer, bartop) {
175
253
  });
176
254
 
177
255
  };
256
+
257
+ ABCJS.write.VoiceElement.prototype.layoutBeams = function() {
258
+ for (var i = 0; i < this.beams.length; i++) {
259
+ if (this.beams[i].layout) {
260
+ this.beams[i].layout();
261
+ // The above will change the top and bottom of the abselem children, so see if we need to expand our range.
262
+ for (var j = 0; j < this.beams[i].elems.length; j++) {
263
+ this.adjustRange(this.beams[i].elems[j]);
264
+ }
265
+ }
266
+ }
267
+ this.staff.top = Math.max(this.staff.top, this.top);
268
+ this.staff.bottom = Math.min(this.staff.bottom, this.bottom);
269
+ };
@@ -54,6 +54,7 @@ ABCJS.write.Printer = function(paper, params) {
54
54
  this.usingSvg = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? true : false); // Same test Raphael uses
55
55
  if (this.usingSvg && params.add_classes)
56
56
  Raphael._availableAttrs['class'] = "";
57
+ Raphael._availableAttrs['text-decoration'] = "";
57
58
  };
58
59
 
59
60
  ABCJS.write.Printer.prototype.addClasses = function (c) {
@@ -190,15 +191,6 @@ ABCJS.write.Printer.prototype.printStem = function (x, dx, y1, y2) {
190
191
  }
191
192
  };
192
193
 
193
- ABCJS.write.Printer.prototype.printText = function (x, offset, text, anchor, extraClass) {
194
- anchor = anchor || "start";
195
- var ret = this.paper.text(x*this.scale, this.calcY(offset)*this.scale, text).attr({"text-anchor":anchor, "font-size":12*this.scale, 'class': this.addClasses(extraClass)});
196
- // if (this.scale!==1) {
197
- // ret.scale(this.scale, this.scale, 0, 0);
198
- // }
199
- return ret;
200
- };
201
-
202
194
  // assumes this.y is set appropriately
203
195
  // if symbol is a multichar string without a . (as in scripts.staccato) 1 symbol per char is assumed
204
196
  // not scaled if not in printgroup
@@ -215,7 +207,7 @@ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex,
215
207
  elemset.push(el);
216
208
  dx+=this.glyphs.getSymbolWidth(symbol.charAt(i));
217
209
  } else {
218
- this.debugMsg(x,"no symbol:" +symbol);
210
+ this.renderText(x, this.y, "no symbol:" +symbol, "debugfont", 'debug-msg', 'start');
219
211
  }
220
212
  }
221
213
  if (this.scale!==1) {
@@ -234,7 +226,7 @@ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex,
234
226
  }
235
227
  return el;
236
228
  } else
237
- this.debugMsg(x,"no symbol:" +symbol);
229
+ this.renderText(x, this.y, "no symbol:" +symbol, "debugfont", 'debug-msg', 'start');
238
230
  }
239
231
  return null;
240
232
  }
@@ -281,18 +273,16 @@ ABCJS.write.Printer.prototype.drawArc = function(x1, x2, pitch1, pitch2, above)
281
273
  return ret;
282
274
  };
283
275
 
284
- ABCJS.write.Printer.prototype.debugMsg = function(x, msg) {
285
- return this.paper.text(x, this.y, msg).scale(this.scale, this.scale, 0, 0).attr({'class': this.addClasses('debug-msg')});
286
- };
287
-
288
- ABCJS.write.Printer.prototype.debugMsgLow = function(x, msg) {
289
- return this.paper.text(x, this.calcY(this.layouter.minY-7), msg).attr({"font-family":"serif", "font-size":12, "text-anchor":"begin", 'class': this.addClasses('debug-msg')}).scale(this.scale, this.scale, 0, 0);
290
- };
291
-
292
- ABCJS.write.Printer.prototype.printLyrics = function(x, msg) {
293
- var el = this.paper.text(x, this.calcY(this.layouter.minY-7), msg).attr({"font-family":"Times New Roman", "font-weight":'bold', "font-size":14, "text-anchor":"begin", 'class': this.addClasses('lyrics')}).scale(this.scale, this.scale, 0, 0);
294
- el[0].setAttribute("class", "abc-lyric");
295
- return el;
276
+ ABCJS.write.Printer.prototype.renderText = function(x, y, text, type, klass, anchor) {
277
+ var font = this.abctune.formatting[type];
278
+ if (!font)
279
+ font = { face: "Arial", size: 12, decoration: "underline", style: "normal", weight: "normal" };
280
+ var attr = {"font-size": font.size*this.scale,
281
+ "font-family": font.face, 'font-weight': font.weight, 'text-decoration': font.decoration,
282
+ 'class': this.addClasses(klass) };
283
+ if (anchor)
284
+ attr["text-anchor"] = anchor;
285
+ return this.paper.text(x, y*this.scale, text).attr(attr);
296
286
  };
297
287
 
298
288
  ABCJS.write.Printer.prototype.calcY = function(ofs) {
@@ -328,9 +318,8 @@ ABCJS.write.Printer.prototype.printABC = function(abctunes) {
328
318
  };
329
319
 
330
320
  ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y, printer, x) {
331
- var fontStyle = {"text-anchor":"start", 'font-size':12*printer.scale, 'font-weight':'bold', 'class': this.addClasses('tempo')};
332
321
  if (tempo.preString) {
333
- var text = paper.text(x*printer.scale, y*printer.scale + 20*printer.scale, tempo.preString).attr(fontStyle);
322
+ var text = this.renderText(x, this.y+20, tempo.preString, 'tempofont', 'tempo',"start");
334
323
  x += (text.getBBox().width + 20*printer.scale);
335
324
  }
336
325
  if (tempo.duration) {
@@ -365,11 +354,11 @@ ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y,
365
354
  abselem.x = x*(1/printer.scale); // TODO-PER: For some reason it scales this element twice, so just compensate.
366
355
  abselem.draw(printer, null);
367
356
  x += (abselem.w + 5*printer.scale);
368
- text = paper.text(x, y*printer.scale + 20*printer.scale, "= " + tempo.bpm).attr(fontStyle);
357
+ text = this.renderText(x, this.y+20, "= " + tempo.bpm, 'tempofont', 'tempo',"start");
369
358
  x += text.getBBox().width + 10*printer.scale;
370
359
  }
371
360
  if (tempo.postString) {
372
- paper.text(x, y*printer.scale + 20*printer.scale, tempo.postString).attr(fontStyle);
361
+ this.renderText(x, this.y+20, tempo.postString, 'tempofont', 'tempo',"start");
373
362
  }
374
363
  y += 15*printer.scale;
375
364
  return y;
@@ -377,6 +366,7 @@ ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y,
377
366
 
378
367
  ABCJS.write.Printer.prototype.printTune = function (abctune) {
379
368
  this.lineNumber = null;
369
+ this.abctune = abctune;
380
370
  this.measureNumber = null;
381
371
  this.layouter = new ABCJS.write.Layout(this.glyphs, abctune.formatting.bagpipes);
382
372
  this.layouter.printer = this; // TODO-PER: this is a hack to get access, but it tightens the coupling.
@@ -395,8 +385,7 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
395
385
  }
396
386
  else
397
387
  this.y+=this.paddingtop;
398
- // FIXED BELOW, NEEDS CHECKING if (abctune.formatting.stretchlast) { this.paper.text(200, this.y, "Format: stretchlast"); this.y += 20; }
399
- if (abctune.formatting.staffwidth) {
388
+ if (abctune.formatting.staffwidth) {
400
389
  this.width=abctune.formatting.staffwidth;
401
390
  } else {
402
391
  this.width=this.staffwidth;
@@ -404,26 +393,34 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
404
393
  this.width+=this.paddingleft;
405
394
  if (abctune.formatting.scale) { this.scale=abctune.formatting.scale; }
406
395
  if (abctune.metaText.title)
407
- this.paper.text(this.width*this.scale/2, this.y, abctune.metaText.title).attr({"font-size":20*this.scale, "font-family":"serif", 'class': this.addClasses('title meta-top')});
396
+ this.renderText(this.width/2, this.y, abctune.metaText.title, 'titlefont', 'title meta-top');
408
397
  this.y+=20*this.scale;
409
398
  if (abctune.lines[0] && abctune.lines[0].subtitle) {
410
399
  this.printSubtitleLine(abctune.lines[0]);
411
400
  this.y+=20*this.scale;
412
401
  }
413
402
  if (abctune.metaText.rhythm) {
414
- this.paper.text(this.paddingleft, this.y, abctune.metaText.rhythm).attr({"text-anchor":"start","font-style":"italic","font-family":"serif", "font-size":12*this.scale, 'class': this.addClasses('meta-top')});
415
- !(abctune.metaText.author || abctune.metaText.origin || abctune.metaText.composer) && (this.y+=15*this.scale);
403
+ this.renderText(this.paddingleft, this.y, abctune.metaText.rhythm, 'infofont', 'meta-top', "start");
404
+ if (!abctune.metaText.author && !abctune.metaText.origin && !abctune.metaText.composer)
405
+ this.y+=15*this.scale;
416
406
  }
417
407
  var composerLine = "";
418
408
  if (abctune.metaText.composer) composerLine += abctune.metaText.composer;
419
409
  if (abctune.metaText.origin) composerLine += ' (' + abctune.metaText.origin + ')';
420
- if (composerLine.length > 0) {this.paper.text(this.width*this.scale, this.y, composerLine).attr({"text-anchor":"end","font-style":"italic","font-family":"serif", "font-size":12*this.scale, 'class': this.addClasses('meta-top')});this.y+=15;}
421
- if (abctune.metaText.author) {this.paper.text(this.width*this.scale, this.y, abctune.metaText.author).attr({"text-anchor":"end","font-style":"italic","font-family":"serif", "font-size":12*this.scale, 'class': this.addClasses('meta-top')}); this.y+=15;}
410
+ if (composerLine.length > 0) {
411
+ this.renderText(this.width, this.y, composerLine, 'composerfont', 'meta-top', "end");
412
+ this.y+=15;
413
+ }
414
+ if (abctune.metaText.author) {
415
+ this.renderText(this.width, this.y, abctune.metaText.author, 'composerfont', 'meta-top', "end");
416
+ this.y+=15;
417
+ }
422
418
  if (abctune.metaText.tempo && !abctune.metaText.tempo.suppress) {
423
419
  this.y = this.printTempo(abctune.metaText.tempo, this.paper, this.layouter, this.y, this, 50, -1);
424
420
  this.y += 20*this.scale;
425
421
  }
426
422
  this.staffgroups = [];
423
+ var text2;
427
424
  var maxwidth = this.width;
428
425
  for(var line=0; line<abctune.lines.length; line++) {
429
426
  this.lineNumber = line;
@@ -436,51 +433,50 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
436
433
  this.y+=20*this.scale; //hardcoded
437
434
  } else if (abcline.text) {
438
435
  if (typeof abcline.text === 'string')
439
- this.paper.text(100, this.y, "TEXT: " + abcline.text).attr({'class': this.addClasses('defined-text')});
440
- else {
441
- var str = "";
442
- for (var i = 0; i < abcline.text.length; i++) {
443
- str += " FONT " + abcline.text[i].text;
444
- }
445
- this.paper.text(100, this.y, "TEXT: " + str).attr({'class': this.addClasses('defined-text')});
446
- }
447
- this.y+=20*this.scale; //hardcoded
448
- }
436
+ text2 = this.renderText(this.paddingleft, this.y, abcline.text, "textfont", 'defined-text', "start");
437
+ else {
438
+ var str = "";
439
+ for (var i = 0; i < abcline.text.length; i++) {
440
+ str += " FONT " + abcline.text[i].text;
441
+ }
442
+ text2 = this.renderText(this.paddingleft, this.y, str, "textfont", 'defined-text', "start");
443
+ }
444
+ this.y += text2.getBBox().height + 10 * this.scale;
445
+ }
449
446
  }
450
447
  this.lineNumber = null;
451
448
  this.measureNumber = null;
449
+ if (abctune.metaText.partOrder) {
450
+ text2 = this.renderText(this.paddingleft, this.y, "Part Order: " + abctune.metaText.partOrder, 'partsfont', 'meta-bottom');
451
+ this.y += text2.getBBox().height + 10 * this.scale;
452
+ }
452
453
  var extraText = "";
453
- var text2;
454
- var height;
455
- if (abctune.metaText.partOrder) extraText += "Part Order: " + abctune.metaText.partOrder + "\n";
456
454
  if (abctune.metaText.unalignedWords) {
457
- for (var j = 0; j < abctune.metaText.unalignedWords.length; j++) {
458
- if (typeof abctune.metaText.unalignedWords[j] === 'string')
459
- extraText += abctune.metaText.unalignedWords[j] + "\n";
460
- else {
461
- for (var k = 0; k < abctune.metaText.unalignedWords[j].length; k++) {
462
- extraText += " FONT " + abctune.metaText.unalignedWords[j][k].text;
463
- }
464
- extraText += "\n";
465
- }
466
- }
467
- text2 = this.paper.text(this.paddingleft*this.scale+50*this.scale, this.y*this.scale+25*this.scale, extraText).attr({"text-anchor":"start", "font-family":"serif", "font-size":17*this.scale, 'class': this.addClasses('meta-bottom')});
468
- height = text2.getBBox().height + 17*this.scale;
469
- text2.translate(0,height/2);
470
- this.y+=height;
455
+ for (var j = 0; j < abctune.metaText.unalignedWords.length; j++) {
456
+ if (typeof abctune.metaText.unalignedWords[j] === 'string')
457
+ extraText += abctune.metaText.unalignedWords[j] + "\n";
458
+ else {
459
+ for (var k = 0; k < abctune.metaText.unalignedWords[j].length; k++) {
460
+ extraText += " FONT " + abctune.metaText.unalignedWords[j][k].text;
461
+ }
462
+ extraText += "\n";
463
+ }
464
+ }
465
+ text2 = this.renderText(this.paddingleft + 50, this.y, extraText, 'wordsfont', 'meta-bottom', "start");
466
+ this.y += text2.getBBox().height + 10 * this.scale;
471
467
  extraText = "";
472
- }
468
+ }
473
469
  if (abctune.metaText.book) extraText += "Book: " + abctune.metaText.book + "\n";
474
470
  if (abctune.metaText.source) extraText += "Source: " + abctune.metaText.source + "\n";
475
471
  if (abctune.metaText.discography) extraText += "Discography: " + abctune.metaText.discography + "\n";
476
472
  if (abctune.metaText.notes) extraText += "Notes: " + abctune.metaText.notes + "\n";
477
473
  if (abctune.metaText.transcription) extraText += "Transcription: " + abctune.metaText.transcription + "\n";
478
474
  if (abctune.metaText.history) extraText += "History: " + abctune.metaText.history + "\n";
479
- text2 = this.paper.text(this.paddingleft, this.y*this.scale+25*this.scale, extraText).attr({"text-anchor":"start", "font-family":"serif", "font-size":17*this.scale, 'class': this.addClasses('meta-bottom')});
480
- height = text2.getBBox().height;
481
- if (!height) height = 25*this.scale; // TODO-PER: Hack! Don't know why Raphael chokes on this sometimes and returns NaN. Perhaps only when printing to PDF? Possibly if the SVG is hidden?
482
- text2.translate(0,height/2);
483
- this.y+=25*this.scale+height*this.scale;
475
+ if (abctune.metaText['abc-copyright']) extraText += "Copyright: " + abctune.metaText['abc-copyright'] + "\n";
476
+ if (abctune.metaText['abc-creator']) extraText += "Creator: " + abctune.metaText['abc-creator'] + "\n";
477
+ if (abctune.metaText['abc-edited-by']) extraText += "Edited By: " + abctune.metaText['abc-edited-by'] + "\n";
478
+ text2 = this.renderText(this.paddingleft, this.y, extraText, 'historyfont', 'meta-bottom', "start");
479
+ this.y += text2.getBBox().height + 10 * this.scale;
484
480
  var sizetoset = {w: (maxwidth+this.paddingright)*this.scale,h: (this.y+this.paddingbottom)*this.scale};
485
481
  this.paper.setSize(sizetoset.w,sizetoset.h);
486
482
  // Correct for IE problem in calculating height
@@ -493,7 +489,7 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
493
489
  };
494
490
 
495
491
  ABCJS.write.Printer.prototype.printSubtitleLine = function(abcline) {
496
- this.paper.text(this.width/2, this.y, abcline.subtitle).attr({"font-size":16, 'class': 'text meta-top'}).scale(this.scale, this.scale, 0,0);
492
+ this.renderText(this.width/2, this.y, abcline.subtitle, "subtitlefont", 'text meta-top');
497
493
  };
498
494
 
499
495
  function centerWholeRests(voices) {