VexFlowMusicSheetDrawer.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import Vex = require("vexflow");
  2. import {MusicSheetDrawer} from "../MusicSheetDrawer";
  3. import {RectangleF2D} from "../../../Common/DataObjects/RectangleF2D";
  4. import {VexFlowMeasure} from "./VexFlowMeasure";
  5. import {PointF2D} from "../../../Common/DataObjects/PointF2D";
  6. import {GraphicalLabel} from "../GraphicalLabel";
  7. import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
  8. import {MusicSystem} from "../MusicSystem";
  9. import {GraphicalObject} from "../GraphicalObject";
  10. import {GraphicalLayers} from "../DrawingEnums";
  11. import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
  12. import {VexFlowBackend} from "./VexFlowBackend";
  13. import {VexFlowOctaveShift} from "./VexFlowOctaveShift";
  14. import {VexFlowInstantaniousDynamicExpression} from "./VexFlowInstantaniousDynamicExpression";
  15. import {VexFlowInstrumentBracket} from "./VexFlowInstrumentBracket";
  16. import {VexFlowInstrumentBrace} from "./VexFlowInstrumentBrace";
  17. import {GraphicalLyricEntry} from "../GraphicalLyricEntry";
  18. import {StaffLine} from "../StaffLine";
  19. import {EngravingRules} from "../EngravingRules";
  20. /**
  21. * This is a global constant which denotes the height in pixels of the space between two lines of the stave
  22. * (when zoom = 1.0)
  23. * @type number
  24. */
  25. export const unitInPixels: number = 10;
  26. export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
  27. private backend: VexFlowBackend;
  28. private zoom: number = 1.0;
  29. constructor(element: HTMLElement,
  30. backend: VexFlowBackend,
  31. isPreviewImageDrawer: boolean = false) {
  32. super(new VexFlowTextMeasurer(), isPreviewImageDrawer);
  33. this.backend = backend;
  34. }
  35. public clear(): void {
  36. this.backend.clear();
  37. }
  38. /**
  39. * Zoom the rendering areas
  40. * @param k is the zoom factor
  41. */
  42. public scale(k: number): void {
  43. this.zoom = k;
  44. this.backend.scale(this.zoom);
  45. }
  46. /**
  47. * Resize the rendering areas
  48. * @param x
  49. * @param y
  50. */
  51. public resize(x: number, y: number): void {
  52. this.backend.resize(x, y);
  53. }
  54. public translate(x: number, y: number): void {
  55. this.backend.translate(x, y);
  56. }
  57. /**
  58. * Converts a distance from unit to pixel space.
  59. * @param unitDistance the distance in units
  60. * @returns {number} the distance in pixels
  61. */
  62. public calculatePixelDistance(unitDistance: number): number {
  63. return unitDistance * unitInPixels;
  64. }
  65. protected drawMeasure(measure: VexFlowMeasure): void {
  66. measure.setAbsoluteCoordinates(
  67. measure.PositionAndShape.AbsolutePosition.x * unitInPixels,
  68. measure.PositionAndShape.AbsolutePosition.y * unitInPixels
  69. );
  70. measure.draw(this.backend.getContext());
  71. // Draw the StaffEntries
  72. for (const staffEntry of measure.staffEntries) {
  73. this.drawStaffEntry(staffEntry);
  74. }
  75. }
  76. // private drawPixel(coord: PointF2D): void {
  77. // coord = this.applyScreenTransformation(coord);
  78. // const ctx: any = this.backend.getContext();
  79. // const oldStyle: string = ctx.fillStyle;
  80. // ctx.fillStyle = "#00FF00FF";
  81. // ctx.fillRect( coord.x, coord.y, 2, 2 );
  82. // ctx.fillStyle = oldStyle;
  83. // }
  84. public drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 0.2): void {
  85. start = this.applyScreenTransformation(start);
  86. stop = this.applyScreenTransformation(stop);
  87. this.backend.renderLine(start, stop, color, lineWidth * unitInPixels);
  88. }
  89. protected drawSkyLine(staffline: StaffLine): void {
  90. const startPosition: PointF2D = staffline.PositionAndShape.AbsolutePosition;
  91. const width: number = staffline.PositionAndShape.Size.width;
  92. this.drawSampledLine(staffline.SkyLine, startPosition, width);
  93. }
  94. protected drawBottomLine(staffline: StaffLine): void {
  95. const startPosition: PointF2D = new PointF2D(staffline.PositionAndShape.AbsolutePosition.x,
  96. staffline.PositionAndShape.AbsolutePosition.y);
  97. const width: number = staffline.PositionAndShape.Size.width;
  98. this.drawSampledLine(staffline.BottomLine, startPosition, width, "#0000FFFF");
  99. }
  100. /**
  101. * Draw a line with a width and start point in a chosen color (used for skyline/bottom line debugging) from
  102. * a simple array
  103. * @param line numeric array. 0 marks the base line. Direction given by sign. Dimensions in units
  104. * @param startPosition Start position in units
  105. * @param width Max line width in units
  106. * @param color Color to paint in. Default is red
  107. */
  108. private drawSampledLine(line: number[], startPosition: PointF2D, width: number, color: string = "#FF0000FF"): void {
  109. const indices: number[] = [];
  110. let currentValue: number = 0;
  111. for (let i: number = 0; i < line.length; i++) {
  112. if (line[i] !== currentValue) {
  113. indices.push(i);
  114. currentValue = line[i];
  115. }
  116. }
  117. const absolute: PointF2D = startPosition;
  118. if (indices.length > 0) {
  119. const samplingUnit: number = EngravingRules.Rules.SamplingUnit;
  120. let horizontalStart: PointF2D = new PointF2D(absolute.x, absolute.y);
  121. let horizontalEnd: PointF2D = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y);
  122. this.drawLine(horizontalStart, horizontalEnd, color);
  123. let verticalStart: PointF2D;
  124. let verticalEnd: PointF2D;
  125. if (line[0] >= 0) {
  126. verticalStart = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y);
  127. verticalEnd = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y + line[indices[0]]);
  128. this.drawLine(verticalStart, verticalEnd, color);
  129. }
  130. for (let i: number = 1; i < indices.length; i++) {
  131. horizontalStart = new PointF2D(indices[i - 1] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  132. horizontalEnd = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  133. this.drawLine(horizontalStart, horizontalEnd, color);
  134. verticalStart = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  135. verticalEnd = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i]]);
  136. this.drawLine(verticalStart, verticalEnd, color);
  137. }
  138. if (indices[indices.length - 1] < line.length) {
  139. horizontalStart = new PointF2D(indices[indices.length - 1] / samplingUnit + absolute.x, absolute.y + line[indices[indices.length - 1]]);
  140. horizontalEnd = new PointF2D(absolute.x + width, absolute.y + line[indices[indices.length - 1]]);
  141. this.drawLine(horizontalStart, horizontalEnd, color);
  142. } else {
  143. horizontalStart = new PointF2D(indices[indices.length - 1] / samplingUnit + absolute.x, absolute.y);
  144. horizontalEnd = new PointF2D(absolute.x + width, absolute.y);
  145. this.drawLine(horizontalStart, horizontalEnd, color);
  146. }
  147. } else {
  148. // Flat line
  149. const start: PointF2D = new PointF2D(absolute.x, absolute.y);
  150. const end: PointF2D = new PointF2D(absolute.x + width, absolute.y);
  151. this.drawLine(start, end, color);
  152. }
  153. }
  154. private drawStaffEntry(staffEntry: GraphicalStaffEntry): void {
  155. // Draw ChordSymbol
  156. if (staffEntry.graphicalChordContainer !== undefined) {
  157. this.drawLabel(staffEntry.graphicalChordContainer.GetGraphicalLabel, <number>GraphicalLayers.Notes);
  158. }
  159. if (staffEntry.LyricsEntries.length > 0) {
  160. this.drawLyrics(staffEntry.LyricsEntries, <number>GraphicalLayers.Notes);
  161. }
  162. }
  163. /**
  164. * Draw all lyrics to the canvas
  165. * @param lyricEntries Array of lyric entries to be drawn
  166. * @param layer Number of the layer that the lyrics should be drawn in
  167. */
  168. private drawLyrics(lyricEntries: GraphicalLyricEntry[], layer: number): void {
  169. lyricEntries.forEach(lyricsEntry => this.drawLabel(lyricsEntry.GraphicalLabel, layer));
  170. }
  171. protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {
  172. // Draw InstrumentBrackets at beginning of line
  173. const vexBrace: VexFlowInstrumentBrace = (brace as VexFlowInstrumentBrace);
  174. vexBrace.draw(this.backend.getContext());
  175. }
  176. protected drawGroupBracket(bracket: GraphicalObject, system: MusicSystem): void {
  177. // Draw InstrumentBrackets at beginning of line
  178. const vexBrace: VexFlowInstrumentBracket = (bracket as VexFlowInstrumentBracket);
  179. vexBrace.draw(this.backend.getContext());
  180. }
  181. protected drawOctaveShifts(staffLine: StaffLine): void {
  182. for (const graphicalOctaveShift of staffLine.OctaveShifts) {
  183. if (graphicalOctaveShift) {
  184. const ctx: Vex.Flow.RenderContext = this.backend.getContext();
  185. const textBracket: Vex.Flow.TextBracket = (graphicalOctaveShift as VexFlowOctaveShift).getTextBracket();
  186. textBracket.setContext(ctx);
  187. textBracket.draw();
  188. }
  189. }
  190. }
  191. protected drawInstantaniousDynamic(staffline: StaffLine): void {
  192. for (const m of staffline.Measures as VexFlowMeasure[]) {
  193. for (const idx of m.instantaniousDynamics as VexFlowInstantaniousDynamicExpression[]) {
  194. this.drawLabel(idx.Label, <number>GraphicalLayers.Notes);
  195. }
  196. }
  197. }
  198. /**
  199. * Renders a Label to the screen (e.g. Title, composer..)
  200. * @param graphicalLabel holds the label string, the text height in units and the font parameters
  201. * @param layer is the current rendering layer. There are many layers on top of each other to which can be rendered. Not needed for now.
  202. * @param bitmapWidth Not needed for now.
  203. * @param bitmapHeight Not needed for now.
  204. * @param heightInPixel the height of the text in screen coordinates
  205. * @param screenPosition the position of the lower left corner of the text in screen coordinates
  206. */
  207. protected renderLabel(graphicalLabel: GraphicalLabel, layer: number, bitmapWidth: number,
  208. bitmapHeight: number, heightInPixel: number, screenPosition: PointF2D): void {
  209. const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
  210. const { fontStyle, font, text } = graphicalLabel.Label;
  211. this.backend.renderText(height, fontStyle, font, text, heightInPixel, screenPosition);
  212. }
  213. /**
  214. * Renders a rectangle with the given style to the screen.
  215. * It is given in screen coordinates.
  216. * @param rectangle the rect in screen coordinates
  217. * @param layer is the current rendering layer. There are many layers on top of each other to which can be rendered. Not needed for now.
  218. * @param styleId the style id
  219. * @param alpha alpha value between 0 and 1
  220. */
  221. protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, alpha: number): void {
  222. this.backend.renderRectangle(rectangle, styleId, alpha);
  223. }
  224. /**
  225. * Converts a point from unit to pixel space.
  226. * @param point
  227. * @returns {PointF2D}
  228. */
  229. protected applyScreenTransformation(point: PointF2D): PointF2D {
  230. return new PointF2D(point.x * unitInPixels, point.y * unitInPixels);
  231. }
  232. /**
  233. * Converts a rectangle from unit to pixel space.
  234. * @param rectangle
  235. * @returns {RectangleF2D}
  236. */
  237. protected applyScreenTransformationForRect(rectangle: RectangleF2D): RectangleF2D {
  238. return new RectangleF2D(rectangle.x * unitInPixels, rectangle.y * unitInPixels, rectangle.width * unitInPixels, rectangle.height * unitInPixels);
  239. }
  240. }