forked from 0xfe/vexflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrenderer.ts
166 lines (146 loc) · 5.2 KB
/
renderer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// MIT License
import { CanvasContext } from './canvascontext';
import { RenderContext } from './rendercontext';
import { SVGContext } from './svgcontext';
import { isRenderContext } from './typeguard';
import { RuntimeError } from './util';
import { isHTMLCanvas, isHTMLDiv } from './web';
// A ContextBuilder is either Renderer.getSVGContext or Renderer.getCanvasContext.
export type ContextBuilder = typeof Renderer.getSVGContext | typeof Renderer.getCanvasContext;
export enum RendererBackends {
CANVAS = 1,
SVG = 2,
}
// End of line types
export enum RendererLineEndType {
NONE = 1, // No leg
UP = 2, // Upward leg
DOWN = 3, // Downward leg
}
/**
* Support Canvas & SVG rendering contexts.
*/
export class Renderer {
static Backends = RendererBackends;
static LineEndType = RendererLineEndType;
// Used by vexflow_test_helpers.ts
// Should this be private?
// Can we do this in a cleaner way?
static lastContext?: RenderContext = undefined;
static buildContext(
elementId: string | HTMLCanvasElement | HTMLDivElement,
backend: number,
width: number,
height: number,
background: string = '#FFF'
): RenderContext {
const renderer = new Renderer(elementId, backend);
if (width && height) {
renderer.resize(width, height);
}
const ctx = renderer.getContext();
ctx.setBackgroundFillStyle(background);
Renderer.lastContext = ctx;
return ctx;
}
static getCanvasContext(elementId: string, width: number, height: number, background?: string): RenderContext {
return Renderer.buildContext(elementId, Renderer.Backends.CANVAS, width, height, background);
}
static getSVGContext(elementId: string, width: number, height: number, background?: string): RenderContext {
return Renderer.buildContext(elementId, Renderer.Backends.SVG, width, height, background);
}
// 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: RenderContext,
fromX: number,
fromY: number,
toX: number,
toY: number,
dashPattern: number[]
): void {
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();
}
protected ctx: RenderContext;
/**
* @param canvasId can be:
* - a string element ID (of a canvas or div element)
* - a canvas element
* - a div element, which will contain the SVG output
* @param backend Renderer.Backends.CANVAS or Renderer.Backends.SVG
*/
constructor(context: RenderContext);
constructor(canvas: string | HTMLCanvasElement | HTMLDivElement, backend: number);
constructor(arg0: string | HTMLCanvasElement | HTMLDivElement | RenderContext, arg1?: number) {
if (isRenderContext(arg0)) {
// The user has provided what looks like a RenderContext, let's just use it.
this.ctx = arg0;
} else {
if (arg1 === undefined) {
// The backend must be specified if the render context isn't directly provided.
throw new RuntimeError('InvalidArgument', 'Missing backend argument');
}
const backend: number = arg1;
let element: HTMLElement;
if (typeof arg0 == 'string') {
const maybeElement = document.getElementById(arg0);
if (!maybeElement) {
throw new RuntimeError('BadElementId', `Can't find element with ID "${maybeElement}"`);
}
element = maybeElement;
} else {
element = arg0 as HTMLElement;
}
// Verify backend and create context
if (backend === Renderer.Backends.CANVAS) {
if (!isHTMLCanvas(element)) {
throw new RuntimeError('BadElement', 'CANVAS context requires an HTMLCanvasElement.');
}
const context = element.getContext('2d', { willReadFrequently: true });
if (!context) {
throw new RuntimeError('BadElement', "Can't get canvas context");
}
this.ctx = new CanvasContext(context);
} else if (backend === Renderer.Backends.SVG) {
if (!isHTMLDiv(element)) {
throw new RuntimeError('BadElement', 'SVG context requires an HTMLDivElement.');
}
this.ctx = new SVGContext(element);
} else {
throw new RuntimeError('InvalidBackend', `No support for backend: ${backend}`);
}
}
}
resize(width: number, height: number): this {
this.ctx.resize(width, height);
return this;
}
getContext(): RenderContext {
return this.ctx;
}
}