src/VexFlowPatch/src/renderer.js
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
//
// ## Description
// Support for different rendering contexts: Canvas, Raphael
import { CanvasContext } from './canvascontext';
import { RaphaelContext } from './raphaelcontext';
import { SVGContext } from './svgcontext';
import { Vex } from './vex';
let lastContext = null;
export class Renderer {
static get Backends() {
return {
CANVAS: 1,
RAPHAEL: 2,
SVG: 3,
VML: 4,
};
}
// End of line types
static get LineEndType() {
return {
NONE: 1, // No leg
UP: 2, // Upward leg
DOWN: 3, // Downward leg
};
}
// Set this to true if you're using VexFlow inside a runtime
// that does not allow modifiying canvas objects. There is a small
// performance degradation due to the extra indirection.
static get USE_CANVAS_PROXY() {
return false;
}
static get lastContext() {
return lastContext;
}
static set lastContext(ctx) {
lastContext = ctx;
}
static buildContext(elementId, backend, width, height, background) {
const renderer = new Renderer(elementId, backend);
if (width && height) {
renderer.resize(width, height);
}
if (!background) background = '#FFF';
const ctx = renderer.getContext();
ctx.setBackgroundFillStyle(background);
Renderer.lastContext = ctx;
return ctx;
}
static getCanvasContext(elementId, width, height, background) {
return Renderer.buildContext(elementId, Renderer.Backends.CANVAS, width, height, background);
}
static getRaphaelContext(elementId, width, height, background) {
return Renderer.buildContext(elementId, Renderer.Backends.RAPHAEL, width, height, background);
}
static getSVGContext(elementId, width, height, background) {
return Renderer.buildContext(elementId, Renderer.Backends.SVG, width, height, background);
}
static bolsterCanvasContext(ctx) {
if (Renderer.USE_CANVAS_PROXY) {
return new CanvasContext(ctx);
}
const methodNames = [
'clear', 'setFont', 'setRawFont', 'setFillStyle', 'setBackgroundFillStyle',
'setStrokeStyle', 'setShadowColor', 'setShadowBlur', 'setLineWidth',
'setLineCap', 'setLineDash', 'openGroup', 'closeGroup', 'getGroup',
];
ctx.vexFlowCanvasContext = ctx;
methodNames.forEach(methodName => {
ctx[methodName] = ctx[methodName] || CanvasContext.prototype[methodName];
});
return ctx;
}
// Draw a dashed line (horizontal, vertical or diagonal
// dashPattern = [3,3] draws a 3 pixel dash followed by a three pixel space.
// setting the second number to 0 draws a solid line.
static drawDashedLine(context, fromX, fromY, toX, toY, dashPattern) {
context.beginPath();
const dx = toX - fromX;
const dy = toY - fromY;
const angle = Math.atan2(dy, dx);
let x = fromX;
let y = fromY;
context.moveTo(fromX, fromY);
let idx = 0;
let draw = true;
while (!((dx < 0 ? x <= toX : x >= toX) && (dy < 0 ? y <= toY : y >= toY))) {
const dashLength = dashPattern[idx++ % dashPattern.length];
const nx = x + (Math.cos(angle) * dashLength);
x = dx < 0 ? Math.max(toX, nx) : Math.min(toX, nx);
const ny = y + (Math.sin(angle) * dashLength);
y = dy < 0 ? Math.max(toY, ny) : Math.min(toY, ny);
if (draw) {
context.lineTo(x, y);
} else {
context.moveTo(x, y);
}
draw = !draw;
}
context.closePath();
context.stroke();
}
constructor(elementId, backend) {
this.elementId = elementId;
if (!this.elementId) {
throw new Vex.RERR('BadArgument', 'Invalid id for renderer.');
}
this.element = document.getElementById(elementId);
if (!this.element) this.element = elementId;
// Verify backend and create context
this.ctx = null;
this.paper = null;
this.backend = backend;
if (this.backend === Renderer.Backends.CANVAS) {
// Create context.
if (!this.element.getContext) {
throw new Vex.RERR('BadElement', `Can't get canvas context from element: ${elementId}`);
}
// VexFlowPatch: add willReadFrequently attribute for getImageData() performance
this.ctx = Renderer.bolsterCanvasContext(this.element.getContext('2d', {willReadFrequently: true}));
} else if (this.backend === Renderer.Backends.RAPHAEL) {
this.ctx = new RaphaelContext(this.element);
} else if (this.backend === Renderer.Backends.SVG) {
this.ctx = new SVGContext(this.element);
} else {
throw new Vex.RERR('InvalidBackend', `No support for backend: ${this.backend}`);
}
}
resize(width, height) {
if (this.backend === Renderer.Backends.CANVAS) {
if (!this.element.getContext) {
throw new Vex.RERR(
'BadElement', `Can't get canvas context from element: ${this.elementId}`
);
}
[width, height] = CanvasContext.SanitizeCanvasDims(width, height);
const devicePixelRatio = window.devicePixelRatio || 1;
this.element.width = width * devicePixelRatio;
this.element.height = height * devicePixelRatio;
this.element.style.width = width + 'px';
this.element.style.height = height + 'px';
// VexFlowPatch: add willReadFrequently attribute for getImageData() performance
this.ctx = Renderer.bolsterCanvasContext(this.element.getContext('2d', {willReadFrequently: true}));
this.ctx.scale(devicePixelRatio, devicePixelRatio);
} else {
this.ctx.resize(width, height);
}
return this;
}
getContext() { return this.ctx; }
}