abcjs-rails 1.8 → 1.11

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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/javascripts/abcjs/api/abc_animation.js +224 -0
  4. data/app/assets/javascripts/abcjs/api/abc_tunebook.js +158 -154
  5. data/app/assets/javascripts/abcjs/data/abc_tune.js +35 -1
  6. data/app/assets/javascripts/abcjs/edit/abc_editor.js +18 -17
  7. data/app/assets/javascripts/abcjs/parse/abc_parse.js +23 -6
  8. data/app/assets/javascripts/abcjs/parse/abc_parse_header.js +5 -1
  9. data/app/assets/javascripts/abcjs/{write/raphael.js → raphael.js} +2562 -266
  10. data/app/assets/javascripts/abcjs/write/abc_absolute_element.js +163 -0
  11. data/app/assets/javascripts/abcjs/write/abc_beam_element.js +162 -0
  12. data/app/assets/javascripts/abcjs/write/abc_cresendo_element.js +46 -0
  13. data/app/assets/javascripts/abcjs/write/abc_dynamic_decoration.js +36 -0
  14. data/app/assets/javascripts/abcjs/write/abc_ending_element.js +53 -0
  15. data/app/assets/javascripts/abcjs/write/abc_glyphs.js +6 -3
  16. data/app/assets/javascripts/abcjs/write/abc_layout.js +84 -29
  17. data/app/assets/javascripts/abcjs/write/abc_relative_element.js +72 -0
  18. data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +225 -0
  19. data/app/assets/javascripts/abcjs/write/abc_tie_element.js +83 -0
  20. data/app/assets/javascripts/abcjs/write/abc_triplet_element.js +85 -0
  21. data/app/assets/javascripts/abcjs/write/abc_voice_element.js +177 -0
  22. data/app/assets/javascripts/abcjs/write/abc_write.js +65 -28
  23. data/lib/abcjs-rails/version.rb +1 -1
  24. metadata +24 -14
  25. data/app/assets/javascripts/abcjs/write/abc_graphelements.js +0 -790
@@ -0,0 +1,83 @@
1
+ // abc_tie_element.js: Definition of the TieElement class.
2
+ // Copyright (C) 2010,2014 Gregory Dyke (gregdyke at gmail dot com) and Paul Rosen
3
+ //
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+ //
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+ //
14
+ // You should have received a copy of the GNU General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ /*globals ABCJS */
18
+
19
+ if (!window.ABCJS)
20
+ window.ABCJS = {};
21
+
22
+ if (!window.ABCJS.write)
23
+ window.ABCJS.write = {};
24
+
25
+ ABCJS.write.TieElem = function(anchor1, anchor2, above, forceandshift) {
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
+ this.anchor2 = anchor2; // must have a .x and a .pitch property or be null (means ends at the end of the line)
28
+ this.above = above; // true if the arc curves above
29
+ this.force = forceandshift; // force the arc curve, regardless of beaming if true
30
+ // move by +7 "up" by -7 if "down"
31
+ };
32
+
33
+ ABCJS.write.TieElem.prototype.draw = function (renderer, linestartx, lineendx) {
34
+ var startpitch;
35
+ var endpitch;
36
+
37
+ if (this.startlimitelem) {
38
+ linestartx = this.startlimitelem.x+this.startlimitelem.w;
39
+ }
40
+
41
+ if (this.endlimitelem) {
42
+ lineendx = this.endlimitelem.x;
43
+ }
44
+ // 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
46
+ // want the slur to be up when the last note is stem down. We can tell the stem direction if the top is
47
+ // equal to the pitch: if so, there is no stem above it.
48
+ if (!this.force && this.anchor2 && this.anchor2.pitch === this.anchor2.top)
49
+ this.above = true;
50
+
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
+ }
58
+
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
+ }
65
+ }
66
+
67
+ // if (this.anchor1 && this.anchor2) {
68
+ // if ((!this.force && this.anchor1.parent.beam && this.anchor2.parent.beam &&
69
+ // this.anchor1.parent.beam.asc===this.anchor2.parent.beam.asc) ||
70
+ // ((this.force==="up") || this.force==="down") && this.anchor1.parent.beam && this.anchor2.parent.beam && this.anchor1.parent.beam===this.anchor2.parent.beam) {
71
+ // this.above = !this.anchor1.parent.beam.asc;
72
+ // preservebeamdir = true;
73
+ // }
74
+ // }
75
+
76
+ // var pitchshift = 0;
77
+ // if (this.force==="up" && !preservebeamdir) pitchshift = 7;
78
+ // if (this.force==="down" && !preservebeamdir) pitchshift = -7;
79
+
80
+ // renderer.debugMsgLow(linestartx, debugMsg);
81
+ renderer.drawArc(linestartx, lineendx, startpitch, endpitch, this.above);
82
+
83
+ };
@@ -0,0 +1,85 @@
1
+ // abc_triplet_element.js: Definition of the TripletElem class.
2
+ // Copyright (C) 2010,2014 Gregory Dyke (gregdyke at gmail dot com) and Paul Rosen
3
+ //
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+ //
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+ //
14
+ // You should have received a copy of the GNU General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ /*globals ABCJS */
18
+
19
+ if (!window.ABCJS)
20
+ window.ABCJS = {};
21
+
22
+ if (!window.ABCJS.write)
23
+ window.ABCJS.write = {};
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
+ };
31
+
32
+ ABCJS.write.TripletElem.prototype.draw = function (renderer, linestartx, lineendx) {
33
+ // TODO end and beginning of line
34
+ if (this.anchor1 && this.anchor2) {
35
+ var ypos = this.above?16:-1; // PER: Just bumped this up from 14 to make (3z2B2B2 (3B2B2z2 succeed. There's probably a better way.
36
+
37
+ if (this.anchor1.parent.beam &&
38
+ this.anchor1.parent.beam===this.anchor2.parent.beam) {
39
+ var beam = this.anchor1.parent.beam;
40
+ this.above = beam.asc;
41
+ ypos = beam.pos;
42
+ } else {
43
+ this.drawLine(renderer,renderer.calcY(ypos));
44
+ }
45
+ var xsum = this.anchor1.x+this.anchor2.x;
46
+ var ydelta = 0;
47
+ if (beam) {
48
+ if (this.above) {
49
+ xsum += (this.anchor2.w + this.anchor1.w);
50
+ ydelta = 4;
51
+ } else {
52
+ ydelta = -4;
53
+ }
54
+ } else {
55
+ xsum += this.anchor2.w;
56
+ }
57
+
58
+
59
+ renderer.printText(xsum/2, ypos+ydelta, this.number, "middle", 'triplet').attr({"font-size":"10px", 'font-style': 'italic' });
60
+
61
+ }
62
+ };
63
+
64
+ ABCJS.write.TripletElem.prototype.drawLine = function (renderer, y) {
65
+ var pathString;
66
+ var linestartx = this.anchor1.x;
67
+ pathString = ABCJS.write.sprintf("M %f %f L %f %f",
68
+ linestartx, y, linestartx, y+5);
69
+ renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
70
+
71
+ var lineendx = this.anchor2.x+this.anchor2.w;
72
+ pathString = ABCJS.write.sprintf("M %f %f L %f %f",
73
+ lineendx, y, lineendx, y+5);
74
+ renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
75
+
76
+ pathString = ABCJS.write.sprintf("M %f %f L %f %f",
77
+ linestartx, y, (linestartx+lineendx)/2-5, y);
78
+ renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
79
+
80
+
81
+ pathString = ABCJS.write.sprintf("M %f %f L %f %f",
82
+ (linestartx+lineendx)/2+5, y, lineendx, y);
83
+ renderer.printPath({path:pathString, stroke:"#000000", 'class': renderer.addClasses('triplet')});
84
+
85
+ };
@@ -0,0 +1,177 @@
1
+ // abc_voice_element.js: Definition of the VoiceElement class.
2
+ // Copyright (C) 2010,2014 Gregory Dyke (gregdyke at gmail dot com) and Paul Rosen
3
+ //
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+ //
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+ //
14
+ // You should have received a copy of the GNU General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ /*globals ABCJS */
18
+
19
+ if (!window.ABCJS)
20
+ window.ABCJS = {};
21
+
22
+ if (!window.ABCJS.write)
23
+ window.ABCJS.write = {};
24
+
25
+ ABCJS.write.VoiceElement = function(voicenumber, voicetotal) {
26
+ this.children = [];
27
+ this.beams = [];
28
+ this.otherchildren = []; // ties, slurs, triplets
29
+ this.w = 0;
30
+ this.duplicate = false;
31
+ this.voicenumber = voicenumber; //number of the voice on a given stave (not staffgroup)
32
+ this.voicetotal = voicetotal;
33
+ };
34
+
35
+ ABCJS.write.VoiceElement.prototype.addChild = function (child) {
36
+ if (child.type === 'bar') {
37
+ var firstItem = true;
38
+ for (var i = 0; firstItem && i < this.children.length; i++) {
39
+ if (this.children[i].type !== "staff-extra")
40
+ firstItem = false;
41
+ }
42
+ if (!firstItem) {
43
+ this.beams.push("bar");
44
+ this.otherchildren.push("bar");
45
+ }
46
+ }
47
+ this.children[this.children.length] = child;
48
+ };
49
+
50
+ ABCJS.write.VoiceElement.prototype.addOther = function (child) {
51
+ if (child instanceof ABCJS.write.BeamElem) {
52
+ this.beams.push(child);
53
+ } else {
54
+ this.otherchildren.push(child);
55
+ }
56
+ };
57
+
58
+ ABCJS.write.VoiceElement.prototype.updateIndices = function () {
59
+ if (!this.layoutEnded()) {
60
+ this.durationindex += this.children[this.i].duration;
61
+ if (this.children[this.i].duration===0) this.durationindex = Math.round(this.durationindex*64)/64; // everytime we meet a barline, do rounding to nearest 64th
62
+ this.i++;
63
+ }
64
+ };
65
+
66
+ ABCJS.write.VoiceElement.prototype.layoutEnded = function () {
67
+ return (this.i>=this.children.length);
68
+ };
69
+
70
+ ABCJS.write.VoiceElement.prototype.getDurationIndex = function () {
71
+ return this.durationindex - (this.children[this.i] && (this.children[this.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
72
+ };
73
+
74
+ // number of spacing units expected for next positioning
75
+ 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
77
+ };
78
+
79
+ //
80
+ ABCJS.write.VoiceElement.prototype.getNextX = function () {
81
+ return Math.max(this.minx, this.nextx);
82
+ };
83
+
84
+ ABCJS.write.VoiceElement.prototype.beginLayout = function (startx) {
85
+ this.i=0;
86
+ this.durationindex=0;
87
+ this.ii=this.children.length;
88
+ this.startx=startx;
89
+ this.minx=startx; // furthest left to where negatively positioned elements are allowed to go
90
+ this.nextx=startx; // x position where the next element of this voice should be placed assuming no other voices and no fixed width constraints
91
+ this.spacingduration=0; // duration left to be laid out in current iteration (omitting additional spacing due to other aspects, such as bars, dots, sharps and flats)
92
+ };
93
+
94
+ // Try to layout the element at index this.i
95
+ // x - position to try to layout the element at
96
+ // spacing - base spacing
97
+ // can't call this function more than once per iteration
98
+ ABCJS.write.VoiceElement.prototype.layoutOneItem = function (x, spacing) {
99
+ var child = this.children[this.i];
100
+ if (!child) return 0;
101
+ var er = x - this.minx; // available extrawidth to the left
102
+ if (er<child.getExtraWidth()) { // shift right by needed amount
103
+ x+=child.getExtraWidth()-er;
104
+ }
105
+ child.x=x; // place child at x
106
+
107
+ this.spacingduration = child.duration;
108
+ //update minx
109
+ 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
111
+
112
+ this.updateNextX(x, spacing);
113
+
114
+ // 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);
117
+
118
+ return x; // where we end up having placed the child
119
+ };
120
+
121
+ // call when spacingduration has been updated
122
+ ABCJS.write.VoiceElement.prototype.updateNextX = function (x, spacing) {
123
+ this.nextx= x + (spacing*Math.sqrt(this.spacingduration*8));
124
+ };
125
+
126
+ ABCJS.write.VoiceElement.prototype.shiftRight = function (dx) {
127
+ var child = this.children[this.i];
128
+ if (!child) return;
129
+ child.x+=dx;
130
+ this.minx+=dx;
131
+ this.nextx+=dx;
132
+ };
133
+
134
+ ABCJS.write.VoiceElement.prototype.draw = function (renderer, bartop) {
135
+ var width = this.w-1;
136
+ renderer.y = this.staff.y;
137
+ renderer.staffbottom = this.staff.bottom;
138
+ this.barbottom = renderer.calcY(2);
139
+
140
+ renderer.measureNumber = null;
141
+ if (this.header) { // print voice name
142
+ var textpitch = 12 - (this.voicenumber+1)*(12/(this.voicetotal+1));
143
+ var headerX = (this.startx-renderer.paddingleft)/2+renderer.paddingleft;
144
+ 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
146
+ }
147
+
148
+ for (var i=0, ii=this.children.length; i<ii; i++) {
149
+ var child = this.children[i];
150
+ var justInitializedMeasureNumber = false;
151
+ if (child.type !== 'staff-extra' && renderer.measureNumber === null) {
152
+ renderer.measureNumber = 0;
153
+ justInitializedMeasureNumber = true;
154
+ }
155
+ child.draw(renderer, (this.barto || i===ii-1)?bartop:0);
156
+ if (child.type === 'bar' && !justInitializedMeasureNumber)
157
+ renderer.measureNumber++;
158
+ }
159
+
160
+ renderer.measureNumber = 0;
161
+ window.ABCJS.parse.each(this.beams, function(beam) {
162
+ if (beam === 'bar')
163
+ renderer.measureNumber++;
164
+ else
165
+ beam.draw(renderer); // beams must be drawn first for proper printing of triplets, slurs and ties.
166
+ });
167
+
168
+ renderer.measureNumber = 0;
169
+ var self = this;
170
+ window.ABCJS.parse.each(this.otherchildren, function(child) {
171
+ if (child === 'bar')
172
+ renderer.measureNumber++;
173
+ else
174
+ child.draw(renderer,self.startx+10,width);
175
+ });
176
+
177
+ };
@@ -15,7 +15,7 @@
15
15
  // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
17
 
18
- /*global window, ABCJS, Math */
18
+ /*global window, ABCJS, Math, Raphael */
19
19
 
20
20
  if (!window.ABCJS)
21
21
  window.ABCJS = {};
@@ -50,6 +50,18 @@ ABCJS.write.Printer = function(paper, params) {
50
50
  this.paddingright = params.paddingright || 50;
51
51
  this.paddingleft = params.paddingleft || 15;
52
52
  this.editable = params.editable || false;
53
+ // HACK-PER: Raphael doesn't support setting the class of an element, so this adds that support. This doesn't work on IE8 or less, though.
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
+ if (this.usingSvg && params.add_classes)
56
+ Raphael._availableAttrs['class'] = "";
57
+ };
58
+
59
+ ABCJS.write.Printer.prototype.addClasses = function (c) {
60
+ var ret = [];
61
+ if (c.length > 0) ret.push(c);
62
+ if (this.lineNumber !== null) ret.push("l"+this.lineNumber);
63
+ if (this.measureNumber !== null) ret.push("m"+this.measureNumber);
64
+ return ret.join(' ');
53
65
  };
54
66
 
55
67
  // notify all listeners that a graphical element has been selected
@@ -125,10 +137,10 @@ ABCJS.write.Printer.prototype.addPath = function (path) {
125
137
  }
126
138
  };
127
139
 
128
- ABCJS.write.Printer.prototype.endGroup = function () {
140
+ ABCJS.write.Printer.prototype.endGroup = function (klass) {
129
141
  this.ingroup = false;
130
142
  if (this.path.length===0) return null;
131
- var ret = this.paper.path().attr({path:this.path, stroke:"none", fill:"#000000"});
143
+ var ret = this.paper.path().attr({path:this.path, stroke:"none", fill:"#000000", 'class': this.addClasses(klass)});
132
144
  if (this.scale!==1) {
133
145
  ret.scale(this.scale, this.scale, 0, 0);
134
146
  }
@@ -146,7 +158,7 @@ ABCJS.write.Printer.prototype.printStaveLine = function (x1,x2, pitch) {
146
158
  var y = this.calcY(pitch);
147
159
  var pathString = ABCJS.write.sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y-dy, x2, y-dy,
148
160
  x2, y+dy, x1, y+dy);
149
- var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:fill}).toBack();
161
+ var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:fill, 'class': this.addClasses('staff')}).toBack();
150
162
  if (this.scale!==1) {
151
163
  ret.scale(this.scale, this.scale, 0, 0);
152
164
  }
@@ -170,7 +182,7 @@ ABCJS.write.Printer.prototype.printStem = function (x, dx, y1, y2) {
170
182
  if (!isIE && this.ingroup) {
171
183
  this.addPath(pathArray);
172
184
  } else {
173
- var ret = this.paper.path().attr({path:pathArray, stroke:"none", fill:fill}).toBack();
185
+ var ret = this.paper.path().attr({path:pathArray, stroke:"none", fill:fill, 'class': this.addClasses('stem')}).toBack();
174
186
  if (this.scale!==1) {
175
187
  ret.scale(this.scale, this.scale, 0, 0);
176
188
  }
@@ -178,9 +190,9 @@ ABCJS.write.Printer.prototype.printStem = function (x, dx, y1, y2) {
178
190
  }
179
191
  };
180
192
 
181
- ABCJS.write.Printer.prototype.printText = function (x, offset, text, anchor) {
193
+ ABCJS.write.Printer.prototype.printText = function (x, offset, text, anchor, extraClass) {
182
194
  anchor = anchor || "start";
183
- var ret = this.paper.text(x*this.scale, this.calcY(offset)*this.scale, text).attr({"text-anchor":anchor, "font-size":12*this.scale});
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)});
184
196
  // if (this.scale!==1) {
185
197
  // ret.scale(this.scale, this.scale, 0, 0);
186
198
  // }
@@ -190,7 +202,7 @@ ABCJS.write.Printer.prototype.printText = function (x, offset, text, anchor) {
190
202
  // assumes this.y is set appropriately
191
203
  // if symbol is a multichar string without a . (as in scripts.staccato) 1 symbol per char is assumed
192
204
  // not scaled if not in printgroup
193
- ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex, scaley) {
205
+ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex, scaley, klass) {
194
206
  var el;
195
207
  if (!symbol) return null;
196
208
  if (symbol.length>0 && symbol.indexOf(".")<0) {
@@ -198,7 +210,7 @@ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex,
198
210
  var dx =0;
199
211
  for (var i=0; i<symbol.length; i++) {
200
212
  var ycorr = this.glyphs.getYCorr(symbol.charAt(i));
201
- el = this.glyphs.printSymbol(x+dx, this.calcY(offset+ycorr), symbol.charAt(i), this.paper);
213
+ el = this.glyphs.printSymbol(x+dx, this.calcY(offset+ycorr), symbol.charAt(i), this.paper, klass);
202
214
  if (el) {
203
215
  elemset.push(el);
204
216
  dx+=this.glyphs.getSymbolWidth(symbol.charAt(i));
@@ -215,7 +227,7 @@ ABCJS.write.Printer.prototype.printSymbol = function(x, offset, symbol, scalex,
215
227
  if (this.ingroup) {
216
228
  this.addPath(this.glyphs.getPathForSymbol(x, this.calcY(offset+ycorr), symbol, scalex, scaley));
217
229
  } else {
218
- el = this.glyphs.printSymbol(x, this.calcY(offset+ycorr), symbol, this.paper);
230
+ el = this.glyphs.printSymbol(x, this.calcY(offset+ycorr), symbol, this.paper, klass);
219
231
  if (el) {
220
232
  if (this.scale!==1) {
221
233
  el.scale(this.scale, this.scale, 0, 0);
@@ -262,7 +274,7 @@ ABCJS.write.Printer.prototype.drawArc = function(x1, x2, pitch1, pitch2, above)
262
274
  var pathString = ABCJS.write.sprintf("M %f %f C %f %f %f %f %f %f C %f %f %f %f %f %f z", x1, y1,
263
275
  controlx1, controly1, controlx2, controly2, x2, y2,
264
276
  controlx2-thickness*uy, controly2+thickness*ux, controlx1-thickness*uy, controly1+thickness*ux, x1, y1);
265
- var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:"#000000"});
277
+ var ret = this.paper.path().attr({path:pathString, stroke:"none", fill:"#000000", 'class': this.addClasses('slur')});
266
278
  if (this.scale!==1) {
267
279
  ret.scale(this.scale, this.scale, 0, 0);
268
280
  }
@@ -270,15 +282,15 @@ ABCJS.write.Printer.prototype.drawArc = function(x1, x2, pitch1, pitch2, above)
270
282
  };
271
283
 
272
284
  ABCJS.write.Printer.prototype.debugMsg = function(x, msg) {
273
- return this.paper.text(x, this.y, msg).scale(this.scale, this.scale, 0, 0);
285
+ return this.paper.text(x, this.y, msg).scale(this.scale, this.scale, 0, 0).attr({'class': this.addClasses('debug-msg')});
274
286
  };
275
287
 
276
288
  ABCJS.write.Printer.prototype.debugMsgLow = function(x, msg) {
277
- return this.paper.text(x, this.calcY(this.layouter.minY-7), msg).attr({"font-family":"serif", "font-size":12, "text-anchor":"begin"}).scale(this.scale, this.scale, 0, 0);
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);
278
290
  };
279
291
 
280
292
  ABCJS.write.Printer.prototype.printLyrics = function(x, msg) {
281
- 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"}).scale(this.scale, this.scale, 0, 0);
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);
282
294
  el[0].setAttribute("class", "abc-lyric");
283
295
  return el;
284
296
  };
@@ -316,7 +328,7 @@ ABCJS.write.Printer.prototype.printABC = function(abctunes) {
316
328
  };
317
329
 
318
330
  ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y, printer, x) {
319
- var fontStyle = {"text-anchor":"start", 'font-size':12*printer.scale, 'font-weight':'bold'};
331
+ var fontStyle = {"text-anchor":"start", 'font-size':12*printer.scale, 'font-weight':'bold', 'class': this.addClasses('tempo')};
320
332
  if (tempo.preString) {
321
333
  var text = paper.text(x*printer.scale, y*printer.scale + 20*printer.scale, tempo.preString).attr(fontStyle);
322
334
  x += (text.getBBox().width + 20*printer.scale);
@@ -325,7 +337,7 @@ ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y,
325
337
  var temposcale = 0.75*printer.scale;
326
338
  var tempopitch = 14.5;
327
339
  var duration = tempo.duration[0]; // TODO when multiple durations
328
- var abselem = new ABCJS.write.AbsoluteElement(tempo, duration, 1);
340
+ var abselem = new ABCJS.write.AbsoluteElement(tempo, duration, 1, 'tempo');
329
341
  var durlog = Math.floor(Math.log(duration) / Math.log(2));
330
342
  var dot = 0;
331
343
  for (var tot = Math.pow(2, durlog), inc = tot / 2; tot < duration; dot++, tot += inc, inc /= 2);
@@ -364,6 +376,8 @@ ABCJS.write.Printer.prototype.printTempo = function (tempo, paper, layouter, y,
364
376
  };
365
377
 
366
378
  ABCJS.write.Printer.prototype.printTune = function (abctune) {
379
+ this.lineNumber = null;
380
+ this.measureNumber = null;
367
381
  this.layouter = new ABCJS.write.Layout(this.glyphs, abctune.formatting.bagpipes);
368
382
  this.layouter.printer = this; // TODO-PER: this is a hack to get access, but it tightens the coupling.
369
383
  if (abctune.media === 'print') {
@@ -390,28 +404,29 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
390
404
  this.width+=this.paddingleft;
391
405
  if (abctune.formatting.scale) { this.scale=abctune.formatting.scale; }
392
406
  if (abctune.metaText.title)
393
- this.paper.text(this.width*this.scale/2, this.y, abctune.metaText.title).attr({"font-size":20*this.scale, "font-family":"serif"});
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')});
394
408
  this.y+=20*this.scale;
395
409
  if (abctune.lines[0] && abctune.lines[0].subtitle) {
396
410
  this.printSubtitleLine(abctune.lines[0]);
397
411
  this.y+=20*this.scale;
398
412
  }
399
413
  if (abctune.metaText.rhythm) {
400
- 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});
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')});
401
415
  !(abctune.metaText.author || abctune.metaText.origin || abctune.metaText.composer) && (this.y+=15*this.scale);
402
416
  }
403
417
  var composerLine = "";
404
418
  if (abctune.metaText.composer) composerLine += abctune.metaText.composer;
405
419
  if (abctune.metaText.origin) composerLine += ' (' + abctune.metaText.origin + ')';
406
- 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});this.y+=15;}
407
- 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}); this.y+=15;}
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;}
408
422
  if (abctune.metaText.tempo && !abctune.metaText.tempo.suppress) {
409
- this.y = this.printTempo(abctune.metaText.tempo, this.paper, this.layouter, this.y, this, 50);
423
+ this.y = this.printTempo(abctune.metaText.tempo, this.paper, this.layouter, this.y, this, 50, -1);
410
424
  this.y += 20*this.scale;
411
425
  }
412
426
  this.staffgroups = [];
413
427
  var maxwidth = this.width;
414
428
  for(var line=0; line<abctune.lines.length; line++) {
429
+ this.lineNumber = line;
415
430
  var abcline = abctune.lines[line];
416
431
  if (abcline.staff) {
417
432
  staffgroup = this.printStaffLine(abctune, abcline, line);
@@ -421,18 +436,20 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
421
436
  this.y+=20*this.scale; //hardcoded
422
437
  } else if (abcline.text) {
423
438
  if (typeof abcline.text === 'string')
424
- this.paper.text(100, this.y, "TEXT: " + abcline.text);
439
+ this.paper.text(100, this.y, "TEXT: " + abcline.text).attr({'class': this.addClasses('defined-text')});
425
440
  else {
426
441
  var str = "";
427
442
  for (var i = 0; i < abcline.text.length; i++) {
428
443
  str += " FONT " + abcline.text[i].text;
429
444
  }
430
- this.paper.text(100, this.y, "TEXT: " + str);
445
+ this.paper.text(100, this.y, "TEXT: " + str).attr({'class': this.addClasses('defined-text')});
431
446
  }
432
447
  this.y+=20*this.scale; //hardcoded
433
448
  }
434
449
  }
435
- var extraText = "";
450
+ this.lineNumber = null;
451
+ this.measureNumber = null;
452
+ var extraText = "";
436
453
  var text2;
437
454
  var height;
438
455
  if (abctune.metaText.partOrder) extraText += "Part Order: " + abctune.metaText.partOrder + "\n";
@@ -447,7 +464,7 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
447
464
  extraText += "\n";
448
465
  }
449
466
  }
450
- 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});
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')});
451
468
  height = text2.getBBox().height + 17*this.scale;
452
469
  text2.translate(0,height/2);
453
470
  this.y+=height;
@@ -459,7 +476,7 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
459
476
  if (abctune.metaText.notes) extraText += "Notes: " + abctune.metaText.notes + "\n";
460
477
  if (abctune.metaText.transcription) extraText += "Transcription: " + abctune.metaText.transcription + "\n";
461
478
  if (abctune.metaText.history) extraText += "History: " + abctune.metaText.history + "\n";
462
- 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});
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')});
463
480
  height = text2.getBBox().height;
464
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?
465
482
  text2.translate(0,height/2);
@@ -476,9 +493,28 @@ ABCJS.write.Printer.prototype.printTune = function (abctune) {
476
493
  };
477
494
 
478
495
  ABCJS.write.Printer.prototype.printSubtitleLine = function(abcline) {
479
- this.paper.text(this.width/2, this.y, abcline.subtitle).attr({"font-size":16}).scale(this.scale, this.scale, 0,0);
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);
480
497
  };
481
498
 
499
+ function centerWholeRests(voices) {
500
+ // whole rests are a special case: if they are by themselves in a measure, then they should be centered.
501
+ // (If they are not by themselves, that is probably a user error, but we'll just center it between the two items to either side of it.)
502
+ for (var i = 0; i < voices.length; i++) {
503
+ var voice = voices[i];
504
+ // Look through all of the elements except for the first and last. If the whole note appears there then there isn't anything to center it between anyway.
505
+ for (var j = 1; j < voice.children.length-1; j++) {
506
+ var absElem = voice.children[j];
507
+ if (absElem.abcelem.rest && absElem.abcelem.rest.type === 'whole') {
508
+ var before = voice.children[j-1];
509
+ var after = voice.children[j+1];
510
+ var midpoint = (after.x - before.x) / 2 + before.x;
511
+ absElem.x = midpoint - absElem.w / 2;
512
+
513
+ }
514
+ }
515
+ }
516
+ }
517
+
482
518
  ABCJS.write.Printer.prototype.printStaffLine = function (abctune, abcline, line) {
483
519
  var staffgroup = this.layouter.printABCLine(abcline.staff);
484
520
  var newspace = this.space;
@@ -494,9 +530,10 @@ ABCJS.write.Printer.prototype.printStaffLine = function (abctune, abcline, line)
494
530
  }
495
531
  }
496
532
  }
533
+ centerWholeRests(staffgroup.voices);
497
534
  staffgroup.draw(this, this.y);
498
535
  this.staffgroups[this.staffgroups.length] = staffgroup;
499
536
  this.y = staffgroup.y + staffgroup.height;
500
537
  this.y += ABCJS.write.spacing.STAVEHEIGHT * 0.2;
501
538
  return staffgroup;
502
- }
539
+ };