VexFlowMusicSheetDrawer.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. import Vex from "vexflow";
  2. import { LabelRenderSpecs, MusicSheetDrawer } from "../MusicSheetDrawer";
  3. import VF = Vex.Flow;
  4. import { RectangleF2D } from "../../../Common/DataObjects/RectangleF2D";
  5. import { VexFlowMeasure } from "./VexFlowMeasure";
  6. import { PointF2D } from "../../../Common/DataObjects/PointF2D";
  7. import { GraphicalLabel } from "../GraphicalLabel";
  8. import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer";
  9. import { MusicSystem } from "../MusicSystem";
  10. import { GraphicalObject } from "../GraphicalObject";
  11. import { GraphicalLayers } from "../DrawingEnums";
  12. import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
  13. import { VexFlowBackend } from "./VexFlowBackend";
  14. import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
  15. import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
  16. import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
  17. import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
  18. import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
  19. import { VexFlowStaffLine } from "./VexFlowStaffLine";
  20. import { StaffLine } from "../StaffLine";
  21. import { GraphicalSlur } from "../GraphicalSlur";
  22. import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
  23. import { GraphicalInstantaneousTempoExpression } from "../GraphicalInstantaneousTempoExpression";
  24. import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneousDynamicExpression";
  25. import log from "loglevel";
  26. import { GraphicalContinuousDynamicExpression } from "../GraphicalContinuousDynamicExpression";
  27. import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
  28. import { DrawingParameters } from "../DrawingParameters";
  29. import { GraphicalMusicPage } from "../GraphicalMusicPage";
  30. import { GraphicalMusicSheet } from "../GraphicalMusicSheet";
  31. import { GraphicalUnknownExpression } from "../GraphicalUnknownExpression";
  32. import { VexFlowPedal } from "./VexFlowPedal";
  33. import { VexflowVibratoBracket } from "./VexflowVibratoBracket";
  34. /**
  35. * This is a global constant which denotes the height in pixels of the space between two lines of the stave
  36. * (when zoom = 1.0)
  37. * @type number
  38. */
  39. export const unitInPixels: number = 10;
  40. export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
  41. private backend: VexFlowBackend;
  42. private backends: VexFlowBackend[] = [];
  43. private zoom: number = 1.0;
  44. public get Zoom(): number {
  45. return this.zoom;
  46. }
  47. private pageIdx: number = 0; // this is a bad solution, should use MusicPage.PageNumber instead.
  48. constructor(drawingParameters: DrawingParameters = new DrawingParameters()) {
  49. super(new VexFlowTextMeasurer(drawingParameters.Rules), drawingParameters);
  50. }
  51. public get Backends(): VexFlowBackend[] {
  52. return this.backends;
  53. }
  54. protected initializeBackendForPage(page: GraphicalMusicPage): void {
  55. this.backend = this.backends[page.PageNumber - 1];
  56. }
  57. public drawSheet(graphicalMusicSheet: GraphicalMusicSheet): void {
  58. // vexflow 3.x: change default font
  59. if (this.rules.DefaultVexFlowNoteFont === "gonville") {
  60. (Vex.Flow as any).DEFAULT_FONT_STACK = [(Vex.Flow as any).Fonts?.Gonville, (Vex.Flow as any).Fonts?.Bravura, (Vex.Flow as any).Fonts?.Custom];
  61. } // else keep new vexflow default Bravura (more cursive, bold).
  62. // sizing defaults in Vexflow
  63. (Vex.Flow as any).STAVE_LINE_THICKNESS = this.rules.StaffLineWidth * unitInPixels;
  64. (Vex.Flow as any).STEM_WIDTH = this.rules.StemWidth * unitInPixels;
  65. // sets scale/size of notes/rest notes:
  66. (Vex.Flow as any).DEFAULT_NOTATION_FONT_SCALE = this.rules.VexFlowDefaultNotationFontScale; // default 39
  67. (Vex.Flow as any).DEFAULT_TAB_FONT_SCALE = this.rules.VexFlowDefaultTabFontScale; // default 39 // TODO doesn't seem to do anything
  68. this.pageIdx = 0;
  69. for (const graphicalMusicPage of graphicalMusicSheet.MusicPages) {
  70. if (graphicalMusicPage.PageNumber > this.rules.MaxPageToDrawNumber) {
  71. break;
  72. }
  73. const backend: VexFlowBackend = this.backends[this.pageIdx];
  74. backend.graphicalMusicPage = graphicalMusicPage;
  75. backend.scale(this.zoom);
  76. //backend.resize(graphicalMusicSheet.ParentMusicSheet.pageWidth * unitInPixels * this.zoom,
  77. // EngravingRules.Rules.PageHeight * unitInPixels * this.zoom);
  78. this.pageIdx += 1;
  79. }
  80. this.pageIdx = 0;
  81. this.backend = this.backends[0];
  82. super.drawSheet(graphicalMusicSheet);
  83. }
  84. protected drawPage(page: GraphicalMusicPage): void {
  85. if (!page) {
  86. return;
  87. }
  88. this.backend = this.backends[page.PageNumber - 1]; // TODO we may need to set this in a couple of other places. this.pageIdx is a bad solution
  89. super.drawPage(page);
  90. this.pageIdx += 1;
  91. }
  92. public clear(): void {
  93. for (const backend of this.backends) {
  94. backend.clear();
  95. }
  96. }
  97. public setZoom(zoom: number): void {
  98. this.zoom = zoom;
  99. }
  100. /**
  101. * Converts a distance from unit to pixel space.
  102. * @param unitDistance the distance in units
  103. * @returns {number} the distance in pixels
  104. */
  105. public calculatePixelDistance(unitDistance: number): number {
  106. return unitDistance * unitInPixels;
  107. }
  108. protected drawStaffLine(staffLine: StaffLine): void {
  109. const stafflineNode: Node = this.backend.getContext().openGroup();
  110. if (stafflineNode) {
  111. (stafflineNode as SVGGElement).classList.add("staffline");
  112. }
  113. super.drawStaffLine(staffLine);
  114. const absolutePos: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
  115. if (this.rules.RenderSlurs) {
  116. this.drawSlurs(staffLine as VexFlowStaffLine, absolutePos);
  117. }
  118. this.backend.getContext().closeGroup();
  119. }
  120. private drawSlurs(vfstaffLine: VexFlowStaffLine, absolutePos: PointF2D): void {
  121. for (const graphicalSlur of vfstaffLine.GraphicalSlurs) {
  122. // don't draw crossed slurs, as their curve calculation is not implemented yet:
  123. if (graphicalSlur.slur.isCrossed()) {
  124. continue;
  125. }
  126. this.drawSlur(graphicalSlur, absolutePos);
  127. }
  128. }
  129. private drawSlur(graphicalSlur: GraphicalSlur, abs: PointF2D): void {
  130. const curvePointsInPixels: PointF2D[] = [];
  131. // 1) create inner or original curve:
  132. const p1: PointF2D = new PointF2D(graphicalSlur.bezierStartPt.x + abs.x, graphicalSlur.bezierStartPt.y + abs.y);
  133. const p2: PointF2D = new PointF2D(graphicalSlur.bezierStartControlPt.x + abs.x, graphicalSlur.bezierStartControlPt.y + abs.y);
  134. const p3: PointF2D = new PointF2D(graphicalSlur.bezierEndControlPt.x + abs.x, graphicalSlur.bezierEndControlPt.y + abs.y);
  135. const p4: PointF2D = new PointF2D(graphicalSlur.bezierEndPt.x + abs.x, graphicalSlur.bezierEndPt.y + abs.y);
  136. // put screen transformed points into array
  137. curvePointsInPixels.push(this.applyScreenTransformation(p1));
  138. curvePointsInPixels.push(this.applyScreenTransformation(p2));
  139. curvePointsInPixels.push(this.applyScreenTransformation(p3));
  140. curvePointsInPixels.push(this.applyScreenTransformation(p4));
  141. //DEBUG: Render control points
  142. /*
  143. for (const point of curvePointsInPixels) {
  144. const pointRect: RectangleF2D = new RectangleF2D(point.x - 2, point.y - 2, 4, 4);
  145. this.backend.renderRectangle(pointRect, 3, "#000000", 1);
  146. }*/
  147. // 2) create second outer curve to create a thickness for the curve:
  148. if (graphicalSlur.placement === PlacementEnum.Above) {
  149. p1.y -= 0.05;
  150. p2.y -= 0.3;
  151. p3.y -= 0.3;
  152. p4.y -= 0.05;
  153. } else {
  154. p1.y += 0.05;
  155. p2.y += 0.3;
  156. p3.y += 0.3;
  157. p4.y += 0.05;
  158. }
  159. // put screen transformed points into array
  160. curvePointsInPixels.push(this.applyScreenTransformation(p1));
  161. curvePointsInPixels.push(this.applyScreenTransformation(p2));
  162. curvePointsInPixels.push(this.applyScreenTransformation(p3));
  163. curvePointsInPixels.push(this.applyScreenTransformation(p4));
  164. graphicalSlur.SVGElement = this.backend.renderCurve(curvePointsInPixels);
  165. }
  166. protected drawMeasure(measure: VexFlowMeasure): void {
  167. measure.setAbsoluteCoordinates(
  168. measure.PositionAndShape.AbsolutePosition.x * unitInPixels,
  169. measure.PositionAndShape.AbsolutePosition.y * unitInPixels
  170. );
  171. const context: Vex.IRenderContext = this.backend.getContext();
  172. try {
  173. measure.draw(context);
  174. // Vexflow errors can happen here. If we don't catch errors, rendering will stop after this measure.
  175. } catch (ex) {
  176. log.warn("VexFlowMusicSheetDrawer.drawMeasure", ex);
  177. }
  178. // Draw the StaffEntries
  179. for (const staffEntry of measure.staffEntries) {
  180. this.drawStaffEntry(staffEntry);
  181. }
  182. }
  183. // private drawPixel(coord: PointF2D): void {
  184. // coord = this.applyScreenTransformation(coord);
  185. // const ctx: any = this.backend.getContext();
  186. // const oldStyle: string = ctx.fillStyle;
  187. // ctx.fillStyle = "#00FF00FF";
  188. // ctx.fillRect( coord.x, coord.y, 2, 2 );
  189. // ctx.fillStyle = oldStyle;
  190. // }
  191. /** Draws a line in the current backend. Only usable while pages are drawn sequentially, because backend reference is updated in that process.
  192. * To add your own lines after rendering, use DrawOverlayLine.
  193. */
  194. protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 0.2): Node {
  195. // TODO maybe the backend should be given as an argument here as well, otherwise this can't be used after rendering of multiple pages is done.
  196. start = this.applyScreenTransformation(start);
  197. stop = this.applyScreenTransformation(stop);
  198. /*if (!this.backend) {
  199. this.backend = this.backends[0];
  200. }*/
  201. return this.backend.renderLine(start, stop, color, lineWidth * unitInPixels);
  202. }
  203. /** Lets a user/developer draw an overlay line on the score. Use this instead of drawLine, which is for OSMD internally only.
  204. * The MusicPage has to be specified, because each page and Vexflow backend has its own relative coordinates.
  205. * (the AbsolutePosition of a GraphicalNote is relative to its backend)
  206. * To get a MusicPage, use GraphicalNote.ParentMusicPage.
  207. */
  208. public DrawOverlayLine(start: PointF2D, stop: PointF2D, musicPage: GraphicalMusicPage,
  209. color: string = "#FF0000FF", lineWidth: number = 0.2): Node {
  210. if (!musicPage.PageNumber || musicPage.PageNumber > this.backends.length || musicPage.PageNumber < 1) {
  211. console.log("VexFlowMusicSheetDrawer.drawOverlayLine: invalid page number / music page number doesn't correspond to an existing backend.");
  212. return;
  213. }
  214. const musicPageIndex: number = musicPage.PageNumber - 1;
  215. const backendToUse: VexFlowBackend = this.backends[musicPageIndex];
  216. start = this.applyScreenTransformation(start);
  217. stop = this.applyScreenTransformation(stop);
  218. return backendToUse.renderLine(start, stop, color, lineWidth * unitInPixels);
  219. }
  220. protected drawSkyLine(staffline: StaffLine): void {
  221. const startPosition: PointF2D = staffline.PositionAndShape.AbsolutePosition;
  222. const width: number = staffline.PositionAndShape.Size.width;
  223. this.drawSampledLine(staffline.SkyLine, startPosition, width);
  224. }
  225. protected drawBottomLine(staffline: StaffLine): void {
  226. const startPosition: PointF2D = new PointF2D(staffline.PositionAndShape.AbsolutePosition.x,
  227. staffline.PositionAndShape.AbsolutePosition.y);
  228. const width: number = staffline.PositionAndShape.Size.width;
  229. this.drawSampledLine(staffline.BottomLine, startPosition, width, "#0000FFFF");
  230. }
  231. /**
  232. * Draw a line with a width and start point in a chosen color (used for skyline/bottom line debugging) from
  233. * a simple array
  234. * @param line numeric array. 0 marks the base line. Direction given by sign. Dimensions in units
  235. * @param startPosition Start position in units
  236. * @param width Max line width in units
  237. * @param color Color to paint in. Default is red
  238. */
  239. private drawSampledLine(line: number[], startPosition: PointF2D, width: number, color: string = "#FF0000FF"): void {
  240. const indices: number[] = [];
  241. let currentValue: number = 0;
  242. //Loops through bottom line, grabs all indices that don't equal the previously grabbed index
  243. //Starting with 0 (gets index of all line changes)
  244. for (let i: number = 0; i < line.length; i++) {
  245. if (line[i] !== currentValue) {
  246. indices.push(i);
  247. currentValue = line[i];
  248. }
  249. }
  250. const absolute: PointF2D = startPosition;
  251. if (indices.length > 0) {
  252. const samplingUnit: number = this.rules.SamplingUnit;
  253. let horizontalStart: PointF2D = new PointF2D(absolute.x, absolute.y);
  254. let horizontalEnd: PointF2D = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y);
  255. this.drawLine(horizontalStart, horizontalEnd, color);
  256. let verticalStart: PointF2D;
  257. let verticalEnd: PointF2D;
  258. if (line[0] >= 0) {
  259. verticalStart = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y);
  260. verticalEnd = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y + line[indices[0]]);
  261. this.drawLine(verticalStart, verticalEnd, color);
  262. }
  263. for (let i: number = 1; i < indices.length; i++) {
  264. horizontalStart = new PointF2D(indices[i - 1] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  265. horizontalEnd = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  266. this.drawLine(horizontalStart, horizontalEnd, color);
  267. verticalStart = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i - 1]]);
  268. verticalEnd = new PointF2D(indices[i] / samplingUnit + absolute.x, absolute.y + line[indices[i]]);
  269. this.drawLine(verticalStart, verticalEnd, color);
  270. }
  271. if (indices[indices.length - 1] < line.length) {
  272. horizontalStart = new PointF2D(indices[indices.length - 1] / samplingUnit + absolute.x, absolute.y + line[indices[indices.length - 1]]);
  273. horizontalEnd = new PointF2D(absolute.x + width, absolute.y + line[indices[indices.length - 1]]);
  274. this.drawLine(horizontalStart, horizontalEnd, color);
  275. } else {
  276. horizontalStart = new PointF2D(indices[indices.length - 1] / samplingUnit + absolute.x, absolute.y);
  277. horizontalEnd = new PointF2D(absolute.x + width, absolute.y);
  278. this.drawLine(horizontalStart, horizontalEnd, color);
  279. }
  280. } else {
  281. // Flat line
  282. const start: PointF2D = new PointF2D(absolute.x, absolute.y);
  283. const end: PointF2D = new PointF2D(absolute.x + width, absolute.y);
  284. this.drawLine(start, end, color);
  285. }
  286. }
  287. private drawStaffEntry(staffEntry: GraphicalStaffEntry): void {
  288. if (staffEntry.FingeringEntries.length > 0) {
  289. for (const fingeringEntry of staffEntry.FingeringEntries) {
  290. fingeringEntry.SVGNode = this.drawLabel(fingeringEntry, GraphicalLayers.Notes);
  291. }
  292. }
  293. // Draw ChordSymbols
  294. if (staffEntry.graphicalChordContainers !== undefined && staffEntry.graphicalChordContainers.length > 0) {
  295. for (const graphicalChordContainer of staffEntry.graphicalChordContainers) {
  296. const label: GraphicalLabel = graphicalChordContainer.GraphicalLabel;
  297. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  298. }
  299. }
  300. if (this.rules.RenderLyrics) {
  301. if (staffEntry.LyricsEntries.length > 0) {
  302. this.drawLyrics(staffEntry.LyricsEntries, <number>GraphicalLayers.Notes);
  303. }
  304. }
  305. }
  306. /**
  307. * Draw all lyrics to the canvas
  308. * @param lyricEntries Array of lyric entries to be drawn
  309. * @param layer Number of the layer that the lyrics should be drawn in
  310. */
  311. private drawLyrics(lyricEntries: GraphicalLyricEntry[], layer: number): void {
  312. lyricEntries.forEach(lyricsEntry => {
  313. const label: GraphicalLabel = lyricsEntry.GraphicalLabel;
  314. label.SVGNode = this.drawLabel(label, layer);
  315. });
  316. }
  317. protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {
  318. // Draw InstrumentBrackets at beginning of line
  319. const vexBrace: VexFlowInstrumentBrace = (brace as VexFlowInstrumentBrace);
  320. vexBrace.draw(this.backend.getContext());
  321. }
  322. protected drawGroupBracket(bracket: GraphicalObject, system: MusicSystem): void {
  323. // Draw InstrumentBrackets at beginning of line
  324. const vexBrace: VexFlowInstrumentBracket = (bracket as VexFlowInstrumentBracket);
  325. vexBrace.draw(this.backend.getContext());
  326. }
  327. protected drawOctaveShifts(staffLine: StaffLine): void {
  328. for (const graphicalOctaveShift of staffLine.OctaveShifts) {
  329. if (graphicalOctaveShift) {
  330. const vexFlowOctaveShift: VexFlowOctaveShift = graphicalOctaveShift as VexFlowOctaveShift;
  331. const ctx: Vex.IRenderContext = this.backend.getContext();
  332. const textBracket: VF.TextBracket = vexFlowOctaveShift.getTextBracket();
  333. textBracket.setContext(ctx);
  334. try {
  335. textBracket.draw();
  336. } catch (ex) {
  337. log.warn(ex);
  338. }
  339. }
  340. }
  341. }
  342. protected drawPedals(staffLine: StaffLine): void {
  343. for (const graphicalPedal of staffLine.Pedals) {
  344. if (graphicalPedal) {
  345. const vexFlowPedal: VexFlowPedal = graphicalPedal as VexFlowPedal;
  346. const ctx: Vex.IRenderContext = this.backend.getContext();
  347. const pedalMarking: Vex.Flow.PedalMarking = vexFlowPedal.getPedalMarking();
  348. pedalMarking.setContext(ctx);
  349. pedalMarking.draw();
  350. }
  351. }
  352. }
  353. protected drawWavyLines(staffLine: StaffLine): void {
  354. for (const graphicalWavyLine of staffLine.WavyLines) {
  355. if (graphicalWavyLine) {
  356. const vexFlowVibratoBracket: VexflowVibratoBracket = graphicalWavyLine as VexflowVibratoBracket;
  357. const ctx: Vex.IRenderContext = this.backend.getContext();
  358. const vfVibratoBracket: Vex.Flow.VibratoBracket = vexFlowVibratoBracket.getVibratoBracket();
  359. (vfVibratoBracket as any).setContext(ctx);
  360. vfVibratoBracket.draw();
  361. }
  362. }
  363. }
  364. protected drawExpressions(staffline: StaffLine): void {
  365. // Draw all Expressions
  366. for (const abstractGraphicalExpression of staffline.AbstractExpressions) {
  367. // Draw InstantaniousDynamics
  368. if (abstractGraphicalExpression instanceof GraphicalInstantaneousDynamicExpression) {
  369. this.drawInstantaneousDynamic((abstractGraphicalExpression as VexFlowInstantaneousDynamicExpression));
  370. // Draw InstantaniousTempo
  371. } else if (abstractGraphicalExpression instanceof GraphicalInstantaneousTempoExpression) {
  372. const label: GraphicalLabel = (abstractGraphicalExpression as GraphicalInstantaneousTempoExpression).GraphicalLabel;
  373. label.SVGNode = this.drawLabel(label, GraphicalLayers.Notes);
  374. // Draw ContinuousDynamics
  375. } else if (abstractGraphicalExpression instanceof GraphicalContinuousDynamicExpression) {
  376. this.drawContinuousDynamic((abstractGraphicalExpression as VexFlowContinuousDynamicExpression));
  377. // Draw ContinuousTempo
  378. // } else if (abstractGraphicalExpression instanceof GraphicalContinuousTempoExpression) {
  379. // this.drawLabel((abstractGraphicalExpression as GraphicalContinuousTempoExpression).GraphicalLabel, GraphicalLayers.Notes);
  380. // // Draw Mood
  381. // } else if (abstractGraphicalExpression instanceof GraphicalMoodExpression) {
  382. // GraphicalMoodExpression; graphicalMood = (GraphicalMoodExpression); abstractGraphicalExpression;
  383. // drawLabel(graphicalMood.GetGraphicalLabel, <number>GraphicalLayers.Notes);
  384. // Draw Unknown
  385. } else if (abstractGraphicalExpression instanceof GraphicalUnknownExpression) {
  386. const label: GraphicalLabel = abstractGraphicalExpression.Label;
  387. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  388. } else {
  389. log.warn("Unkown type of expression!");
  390. }
  391. }
  392. }
  393. protected drawInstantaneousDynamic(instantaneousDynamic: GraphicalInstantaneousDynamicExpression): void {
  394. const label: GraphicalLabel = (instantaneousDynamic as VexFlowInstantaneousDynamicExpression).Label;
  395. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  396. }
  397. protected drawContinuousDynamic(graphicalExpression: VexFlowContinuousDynamicExpression): void {
  398. if (graphicalExpression.IsVerbal) {
  399. const label: GraphicalLabel = graphicalExpression.Label;
  400. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  401. } else {
  402. for (const line of graphicalExpression.Lines) {
  403. const start: PointF2D = new PointF2D(graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.x + line.Start.x,
  404. graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.y + line.Start.y);
  405. const end: PointF2D = new PointF2D(graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.x + line.End.x,
  406. graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.y + line.End.y);
  407. this.drawLine(start, end, "black", line.Width);
  408. }
  409. }
  410. }
  411. /**
  412. * Renders a Label to the screen (e.g. Title, composer..)
  413. * @param graphicalLabel holds the label string, the text height in units and the font parameters
  414. * @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.
  415. * @param bitmapWidth Not needed for now.
  416. * @param bitmapHeight Not needed for now.
  417. * @param heightInPixel the height of the text in screen coordinates
  418. * @param screenPosition the position of the lower left corner of the text in screen coordinates
  419. */
  420. protected renderLabel(graphicalLabel: GraphicalLabel, layer: GraphicalLayers, specs: LabelRenderSpecs): Node {
  421. return this._renderLabel(graphicalLabel, specs);
  422. }
  423. private _renderLabel(graphicalLabel: GraphicalLabel, specs: LabelRenderSpecs): Node {
  424. if (!graphicalLabel.Label.print) {
  425. return undefined;
  426. }
  427. const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
  428. const { font } = graphicalLabel.Label;
  429. let color: string;
  430. if (this.rules.ColoringEnabled) {
  431. color = graphicalLabel.Label.colorDefault;
  432. if (graphicalLabel.Label.color) {
  433. color = graphicalLabel.Label.color.toString();
  434. }
  435. if (!color) {
  436. color = this.rules.DefaultColorLabel;
  437. }
  438. }
  439. let { fontStyle, fontFamily } = graphicalLabel.Label;
  440. if (!fontStyle) {
  441. fontStyle = this.rules.DefaultFontStyle;
  442. }
  443. if (!fontFamily) {
  444. fontFamily = this.rules.DefaultFontFamily;
  445. }
  446. let node: Node;
  447. for (let i: number = 0; i < graphicalLabel.TextLines?.length; i++) {
  448. const currLine: {text: string, xOffset: number, width: number} = graphicalLabel.TextLines[i];
  449. const xOffsetInPixel: number = this.calculatePixelDistance(currLine.xOffset);
  450. const linePosition: PointF2D = new PointF2D(specs.ScreenPosition.x + xOffsetInPixel, specs.ScreenPosition.y);
  451. const newNode: Node =
  452. this.backend.renderText(height, fontStyle, font, currLine.text, specs.FontHeightInPixel, linePosition, color, graphicalLabel.Label.fontFamily);
  453. if (!node) {
  454. node = newNode;
  455. } else {
  456. node.appendChild(newNode);
  457. }
  458. specs.ScreenPosition.y = specs.ScreenPosition.y + specs.FontHeightInPixel;
  459. if (graphicalLabel.TextLines.length > 1) {
  460. specs.ScreenPosition.y += this.rules.SpacingBetweenTextLines;
  461. }
  462. }
  463. // font currently unused, replaced by fontFamily
  464. return node; // this will be a merge conflict with annotations, refactor there to handle node array instead of single node
  465. }
  466. /**
  467. * Renders a rectangle with the given style to the screen.
  468. * It is given in screen coordinates.
  469. * @param rectangle the rect in screen coordinates
  470. * @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.
  471. * @param styleId the style id
  472. * @param alpha alpha value between 0 and 1
  473. */
  474. protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string, alpha: number): Node {
  475. return this.backend.renderRectangle(rectangle, styleId, colorHex, alpha);
  476. }
  477. /**
  478. * Converts a point from unit to pixel space.
  479. * @param point
  480. * @returns {PointF2D}
  481. */
  482. protected applyScreenTransformation(point: PointF2D): PointF2D {
  483. return new PointF2D(point.x * unitInPixels, point.y * unitInPixels);
  484. }
  485. /**
  486. * Converts a rectangle from unit to pixel space.
  487. * @param rectangle
  488. * @returns {RectangleF2D}
  489. */
  490. protected applyScreenTransformationForRect(rectangle: RectangleF2D): RectangleF2D {
  491. return new RectangleF2D(rectangle.x * unitInPixels, rectangle.y * unitInPixels, rectangle.width * unitInPixels, rectangle.height * unitInPixels);
  492. }
  493. }