// Vex Flow
// Mohit Muthanna <mohit@muthanna.com>
//
// Copyright Mohit Muthanna 2010
//
// Requires a glyph font to be loaded and Vex.Flow.Font to be set.
/**
* A quick and dirty static glyph renderer. Renders glyphs from the default
* font defined in Vex.Flow.Font.
*
* @param {!Object} ctx The canvas context.
* @param {number} x_pos X coordinate.
* @param {number} y_pos Y coordinate.
* @param {number} point The point size to use.
* @param {string} val The glyph code in Vex.Flow.Font.
* @param {boolean} nocache If set, disables caching of font outline.
*/
Vex.Flow.renderGlyph = function(ctx, x_pos, y_pos, point, val, nocache) {
var scale = point * 72 / (Vex.Flow.Font.resolution * 100);
var metrics = Vex.Flow.Glyph.loadMetrics(Vex.Flow.Font, val, !nocache);
Vex.Flow.Glyph.renderOutline(ctx, metrics.outline, scale, x_pos, y_pos);
}
/**
* @constructor
*/
Vex.Flow.Glyph = function(code, point, options) {
this.code = code;
this.point = point;
this.context = null;
this.options = {
cache: true,
font: Vex.Flow.Font
};
this.width = null;
this.metrics = null;
this.x_shift = 0;
this.y_shift = 0;
if (options) this.setOptions(options); else this.reset();
}
Vex.Flow.Glyph.prototype.setOptions = function(options) {
Vex.Merge(this.options, options);
this.reset();
}
Vex.Flow.Glyph.prototype.setStave = function(stave) {
this.stave = stave; return this; }
Vex.Flow.Glyph.prototype.setXShift = function(x_shift) {
this.x_shift = x_shift; return this; }
Vex.Flow.Glyph.prototype.setYShift = function(y_shift) {
this.y_shift = y_shift; return this; }
Vex.Flow.Glyph.prototype.setContext = function(context) {
this.context = context; return this; }
Vex.Flow.Glyph.prototype.getContext = function(context) {
return this.context; }
Vex.Flow.Glyph.prototype.reset = function() {
this.metrics = Vex.Flow.Glyph.loadMetrics(this.options.font, this.code,
this.options.cache);
this.scale = this.point * 72 / (this.options.font.resolution * 100);
}
Vex.Flow.Glyph.prototype.getMetrics = function() {
if (!this.metrics) throw new Vex.RuntimeError("BadGlyph", "Glyph " +
this.code + " is not initialized.");
return {
x_min: this.metrics.x_min * this.scale,
x_max: this.metrics.x_max * this.scale,
width: (this.metrics.x_max - this.metrics.x_min) * this.scale
};
}
Vex.Flow.Glyph.prototype.render = function(ctx, x_pos, y_pos) {
if (!this.metrics) throw new Vex.RuntimeError("BadGlyph", "Glyph " +
this.code + " is not initialized.");
var outline = this.metrics.outline;
var scale = this.scale;
var outlineLength = outline.length;
Vex.Flow.Glyph.renderOutline(ctx, outline, scale, x_pos, y_pos);
}
Vex.Flow.Glyph.prototype.renderToStave = function(x) {
if (!this.metrics) throw new Vex.RuntimeError("BadGlyph", "Glyph " +
this.code + " is not initialized.");
if (!this.stave) throw new Vex.RuntimeError("GlyphError", "No valid stave");
if (!this.context) throw new Vex.RERR("GlyphError", "No valid context");
var outline = this.metrics.outline;
var scale = this.scale;
var outlineLength = outline.length;
Vex.Flow.Glyph.renderOutline(this.context, outline, scale,
x + this.x_shift, this.stave.getYForGlyphs() + this.y_shift);
}
/* Static methods used to implement loading / unloading of glyphs */
Vex.Flow.Glyph.loadMetrics = function(font, code, cache) {
var glyph = font.glyphs[code];
if (!glyph) throw new Vex.RuntimeError("BadGlyph", "Glyph " + code +
" does not exist in font.");
var x_min = glyph["x_min"];
var x_max = glyph["x_max"];
var outline;
if (glyph["o"]) {
if (cache) {
if (glyph.cached_outline) {
outline = glyph.cached_outline;
} else {
outline = glyph["o"].split(' ');
glyph.cached_outline = outline;
}
} else {
if (glyph.cached_outline) delete glyph.cached_outline;
outline = glyph["o"].split(' ');
}
return {
x_min: x_min,
x_max: x_max,
outline: outline
};
} else {
throw new Vex.RuntimeError("BadGlyph", "Glyph " + this.code +
" has no outline defined.");
}
}
Vex.Flow.Glyph.renderOutline = function(ctx, outline, scale, x_pos, y_pos) {
var outlineLength = outline.length;
ctx.beginPath();
ctx.moveTo(x_pos, y_pos);
for (var i = 0; i < outlineLength; ) {
var action = outline[i++];
switch(action) {
case 'm':
ctx.moveTo(x_pos + outline[i++] * scale,
y_pos + outline[i++] * -scale);
break;
case 'l':
ctx.lineTo(x_pos + outline[i++] * scale,
y_pos + outline[i++] * -scale);
break;
case 'q':
var cpx = x_pos + outline[i++] * scale;
var cpy = y_pos + outline[i++] * -scale;
ctx.quadraticCurveTo(
x_pos + outline[i++] * scale,
y_pos + outline[i++] * -scale, cpx, cpy);
break;
case 'b':
var x = x_pos + outline[i++] * scale;
var y = y_pos + outline[i++] * -scale;
ctx.bezierCurveTo(
x_pos + outline[i++] * scale, y_pos + outline[i++] * -scale,
x_pos + outline[i++] * scale, y_pos + outline[i++] * -scale,
x, y);
break;
}
}
ctx.fill();
}