MusicSheetDrawer.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import {EngravingRules} from "./EngravingRules";
  2. import {ITextMeasurer} from "../Interfaces/ITextMeasurer";
  3. import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
  4. import {BoundingBox} from "./BoundingBox";
  5. import {GraphicalLayers, OutlineAndFillStyleEnum} from "./DrawingEnums";
  6. import {DrawingParameters} from "./DrawingParameters";
  7. import {GraphicalLine} from "./GraphicalLine";
  8. import {RectangleF2D} from "../../Common/DataObjects/RectangleF2D";
  9. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  10. import {GraphicalRectangle} from "./GraphicalRectangle";
  11. import {GraphicalLabel} from "./GraphicalLabel";
  12. import {Label} from "../Label";
  13. import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
  14. import {ArgumentOutOfRangeException} from "../Exceptions";
  15. import {SelectionStartSymbol} from "./SelectionStartSymbol";
  16. import {SelectionEndSymbol} from "./SelectionEndSymbol";
  17. import {MusicSystem} from "./MusicSystem";
  18. import {GraphicalMeasure} from "./GraphicalMeasure";
  19. import {StaffLine} from "./StaffLine";
  20. import {SystemLine} from "./SystemLine";
  21. import {MusicSymbol} from "./MusicSymbol";
  22. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  23. import {Instrument} from "../Instrument";
  24. import {MusicSymbolDrawingStyle, PhonicScoreModes} from "./DrawingMode";
  25. import {GraphicalObject} from "./GraphicalObject";
  26. import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
  27. import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
  28. import { VexFlowInstrumentBracket } from "./VexFlow";
  29. // import { FontStyles } from "../../Common/Enums/FontStyles";
  30. export class LabelRenderSpecs {
  31. public BitmapWidth: number;
  32. public BitmapHeight: number;
  33. public FontHeightInPixel: number;
  34. public ScreenPosition: PointF2D;
  35. }
  36. /**
  37. * Draw a [[GraphicalMusicSheet]] (through the .drawSheet method)
  38. *
  39. * The drawing is implemented with a top-down approach, starting from a music sheet, going through pages, systems, staffs...
  40. * ... and ending in notes, beams, accidentals and other symbols.
  41. * It's worth to say, that this class just draws the symbols and graphical elements, using the positions that have been computed before.
  42. * But in any case, some of these previous positioning algorithms need the sizes of the concrete symbols (NoteHeads, sharps, flats, keys...).
  43. * Therefore, there are some static functions on the 'Bounding Boxes' section used to compute these symbol boxes at the
  44. * beginning for the later use in positioning algorithms.
  45. *
  46. * This class also includes the resizing and positioning of the symbols due to user interaction like zooming or panning.
  47. */
  48. export abstract class MusicSheetDrawer {
  49. public drawingParameters: DrawingParameters;
  50. public splitScreenLineColor: number;
  51. public midiPlaybackAvailable: boolean;
  52. public drawableBoundingBoxElement: string = "None"; // process.env.DRAW_BOUNDING_BOX_ELEMENT;
  53. public skyLineVisible: boolean = false;
  54. public bottomLineVisible: boolean = false;
  55. protected rules: EngravingRules;
  56. protected graphicalMusicSheet: GraphicalMusicSheet;
  57. protected textMeasurer: ITextMeasurer;
  58. private phonicScoreMode: PhonicScoreModes = PhonicScoreModes.Manual;
  59. constructor(textMeasurer: ITextMeasurer,
  60. drawingParameters: DrawingParameters) {
  61. this.textMeasurer = textMeasurer;
  62. this.splitScreenLineColor = -1;
  63. this.drawingParameters = drawingParameters;
  64. this.rules = drawingParameters.Rules;
  65. }
  66. public set Mode(value: PhonicScoreModes) {
  67. this.phonicScoreMode = value;
  68. }
  69. public drawSheet(graphicalMusicSheet: GraphicalMusicSheet): void {
  70. this.graphicalMusicSheet = graphicalMusicSheet;
  71. this.rules = graphicalMusicSheet.ParentMusicSheet.Rules;
  72. this.drawSplitScreenLine();
  73. if (this.drawingParameters.drawCursors) {
  74. for (const line of graphicalMusicSheet.Cursors) {
  75. if (!line) {
  76. // TODO GraphicalMusicSheet.calculateCursorLineAtTimestamp() can return undefined.
  77. // why does this happen in the VexFlowMusicSheetDrawer_Test? (it("draws cursor..."))
  78. continue;
  79. }
  80. const psi: BoundingBox = new BoundingBox(line);
  81. psi.AbsolutePosition = line.Start;
  82. psi.BorderBottom = line.End.y - line.Start.y;
  83. psi.BorderRight = line.Width / 2.0;
  84. psi.BorderLeft = -line.Width / 2.0;
  85. if (this.isVisible(psi)) {
  86. this.drawLineAsVerticalRectangle(line, <number>GraphicalLayers.Cursor);
  87. }
  88. }
  89. }
  90. // Draw the vertical ScrollIndicator
  91. if (this.drawingParameters.drawScrollIndicator) {
  92. this.drawScrollIndicator();
  93. }
  94. // Draw the pages
  95. const pagesToDraw: number = Math.min(this.graphicalMusicSheet.MusicPages.length, this.rules.MaxPageToDrawNumber);
  96. for (let i: number = 0; i < pagesToDraw; i ++) {
  97. const page: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[i];
  98. this.drawPage(page);
  99. }
  100. }
  101. public drawLineAsHorizontalRectangle(line: GraphicalLine, layer: number): void {
  102. let rectangle: RectangleF2D = new RectangleF2D(line.Start.x, line.End.y - line.Width / 2, line.End.x - line.Start.x, line.Width);
  103. rectangle = this.applyScreenTransformationForRect(rectangle);
  104. this.renderRectangle(rectangle, layer, line.styleId, line.colorHex);
  105. }
  106. public drawLineAsVerticalRectangle(line: GraphicalLine, layer: number): void {
  107. const lineStart: PointF2D = line.Start;
  108. const lineWidth: number = line.Width;
  109. let rectangle: RectangleF2D = new RectangleF2D(lineStart.x - lineWidth / 2, lineStart.y, lineWidth, line.End.y - lineStart.y);
  110. rectangle = this.applyScreenTransformationForRect(rectangle);
  111. this.renderRectangle(rectangle, layer, line.styleId);
  112. }
  113. public drawLineAsHorizontalRectangleWithOffset(line: GraphicalLine, offset: PointF2D, layer: number): void {
  114. const start: PointF2D = new PointF2D(line.Start.x + offset.x, line.Start.y + offset.y);
  115. const end: PointF2D = new PointF2D(line.End.x + offset.x, line.End.y + offset.y);
  116. const width: number = line.Width;
  117. let rectangle: RectangleF2D = new RectangleF2D(start.x, end.y - width / 2, end.x - start.x, width);
  118. rectangle = this.applyScreenTransformationForRect(rectangle);
  119. this.renderRectangle(rectangle, layer, line.styleId);
  120. }
  121. public drawLineAsVerticalRectangleWithOffset(line: GraphicalLine, offset: PointF2D, layer: number): void {
  122. const start: PointF2D = new PointF2D(line.Start.x + offset.x, line.Start.y + offset.y);
  123. const end: PointF2D = new PointF2D(line.End.x + offset.x, line.End.y + offset.y);
  124. const width: number = line.Width;
  125. let rectangle: RectangleF2D = new RectangleF2D(start.x, start.y, width, end.y - start.y);
  126. rectangle = this.applyScreenTransformationForRect(rectangle);
  127. this.renderRectangle(rectangle, layer, line.styleId);
  128. }
  129. public drawRectangle(rect: GraphicalRectangle, layer: number): void {
  130. const psi: BoundingBox = rect.PositionAndShape;
  131. let rectangle: RectangleF2D = new RectangleF2D(psi.AbsolutePosition.x, psi.AbsolutePosition.y, psi.BorderRight, psi.BorderBottom);
  132. rectangle = this.applyScreenTransformationForRect(rectangle);
  133. this.renderRectangle(rectangle, layer, <number>rect.style);
  134. }
  135. public abstract calculatePixelDistance(unitDistance: number): number;
  136. public drawLabel(graphicalLabel: GraphicalLabel, layer: number): Node {
  137. if (!this.isVisible(graphicalLabel.PositionAndShape)) {
  138. return undefined;
  139. }
  140. const label: Label = graphicalLabel.Label;
  141. if (label.text.trim() === "") {
  142. return undefined;
  143. }
  144. const calcResults: LabelRenderSpecs = this.calculateLabel(graphicalLabel);
  145. return this.renderLabel(graphicalLabel, layer, calcResults);
  146. }
  147. protected calculateLabel(graphicalLabel: GraphicalLabel): LabelRenderSpecs {
  148. const result: LabelRenderSpecs = new LabelRenderSpecs();
  149. const label: Label = graphicalLabel.Label;
  150. result.ScreenPosition = this.applyScreenTransformation(graphicalLabel.PositionAndShape.AbsolutePosition);
  151. result.FontHeightInPixel = this.calculatePixelDistance(label.fontHeight);
  152. const widthInPixel: number = this.calculatePixelDistance(graphicalLabel.PositionAndShape.Size.width);
  153. result.BitmapWidth = Math.ceil(widthInPixel);
  154. result.BitmapHeight = Math.ceil(result.FontHeightInPixel * (0.2 + graphicalLabel.TextLines.length));
  155. switch (label.textAlignment) {
  156. // Adjust the OSMD-calculated positions to rendering coordinates
  157. // These have to match the Border settings in GraphicalLabel.setLabelPositionAndShapeBorders()
  158. // TODO isn't this a Vexflow-specific transformation that should be in VexflowMusicSheetDrawer?
  159. case TextAlignmentEnum.LeftTop:
  160. break;
  161. case TextAlignmentEnum.LeftCenter:
  162. result.ScreenPosition.y -= result.BitmapHeight / 2;
  163. break;
  164. case TextAlignmentEnum.LeftBottom:
  165. result.ScreenPosition.y -= result.BitmapHeight;
  166. break;
  167. case TextAlignmentEnum.CenterTop:
  168. result.ScreenPosition.x -= result.BitmapWidth / 2;
  169. break;
  170. case TextAlignmentEnum.CenterCenter:
  171. result.ScreenPosition.x -= result.BitmapWidth / 2;
  172. result.ScreenPosition.y -= result.BitmapHeight / 2;
  173. break;
  174. case TextAlignmentEnum.CenterBottom:
  175. result.ScreenPosition.x -= result.BitmapWidth / 2;
  176. result.ScreenPosition.y -= result.BitmapHeight;
  177. break;
  178. case TextAlignmentEnum.RightTop:
  179. result.ScreenPosition.x -= result.BitmapWidth;
  180. break;
  181. case TextAlignmentEnum.RightCenter:
  182. result.ScreenPosition.x -= result.BitmapWidth;
  183. result.ScreenPosition.y -= result.BitmapHeight / 2;
  184. break;
  185. case TextAlignmentEnum.RightBottom:
  186. result.ScreenPosition.x -= result.BitmapWidth;
  187. result.ScreenPosition.y -= result.BitmapHeight;
  188. break;
  189. default:
  190. throw new ArgumentOutOfRangeException("");
  191. }
  192. return result;
  193. }
  194. protected abstract applyScreenTransformation(point: PointF2D): PointF2D;
  195. protected applyScreenTransformations(points: PointF2D[]): PointF2D[] {
  196. const transformedPoints: PointF2D[] = [];
  197. for (const point of points) {
  198. transformedPoints.push(this.applyScreenTransformation(point));
  199. }
  200. return transformedPoints;
  201. }
  202. protected abstract applyScreenTransformationForRect(rectangle: RectangleF2D): RectangleF2D;
  203. protected drawSplitScreenLine(): void {
  204. // empty
  205. }
  206. protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string = undefined, alpha: number = 1): Node {
  207. throw new Error("not implemented");
  208. }
  209. protected drawScrollIndicator(): void {
  210. // empty
  211. }
  212. protected drawSelectionStartSymbol(symbol: SelectionStartSymbol): void {
  213. // empty
  214. }
  215. protected drawSelectionEndSymbol(symbol: SelectionEndSymbol): void {
  216. // empty
  217. }
  218. protected renderLabel(graphicalLabel: GraphicalLabel, layer: GraphicalLayers, specs: LabelRenderSpecs): Node {
  219. throw new Error("not implemented");
  220. }
  221. protected renderSystemToScreen(system: MusicSystem, systemBoundingBoxInPixels: RectangleF2D,
  222. absBoundingRectWithMargin: RectangleF2D): void {
  223. // empty
  224. }
  225. protected abstract drawMeasure(measure: GraphicalMeasure): void;
  226. protected drawSkyLine(staffLine: StaffLine): void {
  227. // empty
  228. }
  229. protected drawBottomLine(staffLine: StaffLine): void {
  230. // empty
  231. }
  232. protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {
  233. // empty
  234. }
  235. protected drawGroupBracket(bracket: GraphicalObject, system: MusicSystem): void {
  236. // empty
  237. }
  238. protected isVisible(psi: BoundingBox): boolean {
  239. return true;
  240. }
  241. protected drawMusicSystem(system: MusicSystem): void {
  242. const absBoundingRectWithMargin: RectangleF2D = this.getSystemAbsBoundingRect(system);
  243. const systemBoundingBoxInPixels: RectangleF2D = this.getSytemBoundingBoxInPixels(absBoundingRectWithMargin);
  244. this.drawMusicSystemComponents(system, systemBoundingBoxInPixels, absBoundingRectWithMargin);
  245. }
  246. protected getSytemBoundingBoxInPixels(absBoundingRectWithMargin: RectangleF2D): RectangleF2D {
  247. const systemBoundingBoxInPixels: RectangleF2D = this.applyScreenTransformationForRect(absBoundingRectWithMargin);
  248. systemBoundingBoxInPixels.x = Math.round(systemBoundingBoxInPixels.x);
  249. systemBoundingBoxInPixels.y = Math.round(systemBoundingBoxInPixels.y);
  250. return systemBoundingBoxInPixels;
  251. }
  252. protected getSystemAbsBoundingRect(system: MusicSystem): RectangleF2D {
  253. const relBoundingRect: RectangleF2D = system.PositionAndShape.BoundingRectangle;
  254. const absBoundingRectWithMargin: RectangleF2D = new RectangleF2D(
  255. system.PositionAndShape.AbsolutePosition.x + system.PositionAndShape.BorderLeft - 1,
  256. system.PositionAndShape.AbsolutePosition.y + system.PositionAndShape.BorderTop - 1,
  257. (relBoundingRect.width + 6), (relBoundingRect.height + 2)
  258. );
  259. return absBoundingRectWithMargin;
  260. }
  261. protected drawMusicSystemComponents(musicSystem: MusicSystem, systemBoundingBoxInPixels: RectangleF2D,
  262. absBoundingRectWithMargin: RectangleF2D): void {
  263. const selectStartSymb: SelectionStartSymbol = this.graphicalMusicSheet.SelectionStartSymbol;
  264. const selectEndSymb: SelectionEndSymbol = this.graphicalMusicSheet.SelectionEndSymbol;
  265. if (this.drawingParameters.drawSelectionStartSymbol) {
  266. if (selectStartSymb !== undefined && this.isVisible(selectStartSymb.PositionAndShape)) {
  267. this.drawSelectionStartSymbol(selectStartSymb);
  268. }
  269. }
  270. if (this.drawingParameters.drawSelectionEndSymbol) {
  271. if (selectEndSymb !== undefined && this.isVisible(selectEndSymb.PositionAndShape)) {
  272. this.drawSelectionEndSymbol(selectEndSymb);
  273. }
  274. }
  275. for (const staffLine of musicSystem.StaffLines) {
  276. this.drawStaffLine(staffLine);
  277. if (this.rules.RenderLyrics) {
  278. // draw lyric dashes
  279. if (staffLine.LyricsDashes.length > 0) {
  280. this.drawDashes(staffLine.LyricsDashes);
  281. }
  282. // draw lyric lines (e.g. LyricExtends: "dich,___")
  283. if (staffLine.LyricLines.length > 0) {
  284. this.drawLyricLines(staffLine.LyricLines, staffLine);
  285. }
  286. }
  287. }
  288. for (const systemLine of musicSystem.SystemLines) {
  289. this.drawSystemLineObject(systemLine);
  290. }
  291. if (this.rules.RenderSystemLabelsAfterFirstPage ||
  292. musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0]) {
  293. for (const label of musicSystem.Labels) {
  294. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  295. }
  296. }
  297. const instruments: Instrument[] = this.graphicalMusicSheet.ParentMusicSheet.Instruments;
  298. const instrumentsVisible: number = instruments.filter((instrument) => instrument.Visible).length;
  299. for (const bracket of musicSystem.InstrumentBrackets) {
  300. this.drawInstrumentBrace(bracket, musicSystem);
  301. }
  302. if (instruments.length > 0) {
  303. // TODO instead of this check we could save what instruments are in the group bracket,
  304. // and only draw it if all these instruments are visible.
  305. // Currently the instruments/stafflines aren't saved in the bracket however.
  306. if (instrumentsVisible > 1) {
  307. for (const bracket of musicSystem.GroupBrackets) {
  308. this.drawGroupBracket(bracket, musicSystem);
  309. }
  310. } else {
  311. for (const bracket of musicSystem.GroupBrackets) {
  312. (bracket as VexFlowInstrumentBracket).Visible = false; //.setType(VF.StaveConnector.type.NONE);
  313. }
  314. }
  315. }
  316. if (!this.leadSheet) {
  317. for (const measureNumberLabel of musicSystem.MeasureNumberLabels) {
  318. measureNumberLabel.SVGNode = this.drawLabel(measureNumberLabel, <number>GraphicalLayers.Notes);
  319. }
  320. }
  321. for (const staffLine of musicSystem.StaffLines) {
  322. this.drawStaffLineSymbols(staffLine);
  323. }
  324. if (this.drawingParameters.drawMarkedAreas) {
  325. this.drawMarkedAreas(musicSystem);
  326. }
  327. }
  328. protected activateSystemRendering(systemId: number, absBoundingRect: RectangleF2D,
  329. systemBoundingBoxInPixels: RectangleF2D, createNewImage: boolean): boolean {
  330. return true;
  331. }
  332. protected drawSystemLineObject(systemLine: SystemLine): void {
  333. // empty
  334. }
  335. protected drawStaffLine(staffLine: StaffLine): void {
  336. for (const measure of staffLine.Measures) {
  337. this.drawMeasure(measure);
  338. if (measure.parentSourceMeasure) {
  339. measure.parentSourceMeasure.WasRendered = true; // simplification
  340. // parentSourceMeasure can be undefined for implicit measure (e.g. time signature change at end of staffline)
  341. }
  342. }
  343. if (this.rules.RenderLyrics) {
  344. if (staffLine.LyricsDashes.length > 0) {
  345. this.drawDashes(staffLine.LyricsDashes);
  346. }
  347. }
  348. this.drawOctaveShifts(staffLine);
  349. this.drawPedals(staffLine);
  350. this.drawWavyLines(staffLine);
  351. this.drawExpressions(staffLine);
  352. if (this.skyLineVisible) {
  353. this.drawSkyLine(staffLine);
  354. }
  355. if (this.bottomLineVisible) {
  356. this.drawBottomLine(staffLine);
  357. }
  358. }
  359. protected drawLyricLines(lyricLines: GraphicalLine[], staffLine: StaffLine): void {
  360. staffLine.LyricLines.forEach(lyricLine => {
  361. // TODO maybe we should put this in the calculation (MusicSheetCalculator.calculateLyricExtend)
  362. // then we can also remove staffLine argument
  363. // but same addition doesn't work in calculateLyricExtend, because y-spacing happens after lyrics positioning
  364. lyricLine.Start.y += staffLine.PositionAndShape.AbsolutePosition.y;
  365. lyricLine.End.y += staffLine.PositionAndShape.AbsolutePosition.y;
  366. lyricLine.Start.x += staffLine.PositionAndShape.AbsolutePosition.x;
  367. lyricLine.End.x += staffLine.PositionAndShape.AbsolutePosition.x;
  368. this.drawGraphicalLine(lyricLine, this.rules.LyricUnderscoreLineWidth, lyricLine.colorHex);
  369. });
  370. }
  371. protected drawExpressions(staffline: StaffLine): void {
  372. // implemented by subclass (VexFlowMusicSheetDrawer)
  373. }
  374. protected drawGraphicalLine(graphicalLine: GraphicalLine, lineWidth: number, colorOrStyle: string = "black"): Node {
  375. /* TODO similar checks as in drawLabel
  376. if (!this.isVisible(new BoundingBox(graphicalLine.Start,)) {
  377. return;
  378. }
  379. */
  380. return this.drawLine(graphicalLine.Start, graphicalLine.End, colorOrStyle, lineWidth);
  381. }
  382. protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number): Node {
  383. // implemented by subclass (VexFlowMusicSheetDrawer)
  384. return undefined;
  385. }
  386. /**
  387. * Draw all dashes to the canvas
  388. * @param lyricsDashes Array of lyric dashes to be drawn
  389. * @param layer Number of the layer that the lyrics should be drawn in
  390. */
  391. protected drawDashes(lyricsDashes: GraphicalLabel[]): void {
  392. lyricsDashes.forEach(dash => dash.SVGNode = this.drawLabel(dash, <number>GraphicalLayers.Notes));
  393. }
  394. // protected drawSlur(slur: GraphicalSlur, abs: PointF2D): void {
  395. //
  396. // }
  397. protected drawOctaveShifts(staffLine: StaffLine): void {
  398. return;
  399. }
  400. protected abstract drawPedals(staffLine: StaffLine): void;
  401. protected abstract drawWavyLines(staffLine: StaffLine): void;
  402. protected drawStaffLines(staffLine: StaffLine): void {
  403. if (staffLine.StaffLines) {
  404. const position: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
  405. for (let i: number = 0; i < 5; i++) {
  406. this.drawLineAsHorizontalRectangleWithOffset(staffLine.StaffLines[i], position, <number>GraphicalLayers.Notes);
  407. }
  408. }
  409. }
  410. // protected drawEnding(ending: GraphicalRepetitionEnding, absolutePosition: PointF2D): void {
  411. // if (undefined !== ending.Left)
  412. // drawLineAsVerticalRectangle(ending.Left, absolutePosition, <number>GraphicalLayers.Notes);
  413. // this.drawLineAsHorizontalRectangle(ending.Top, absolutePosition, <number>GraphicalLayers.Notes);
  414. // if (undefined !== ending.Right)
  415. // drawLineAsVerticalRectangle(ending.Right, absolutePosition, <number>GraphicalLayers.Notes);
  416. // this.drawLabel(ending.Label, <number>GraphicalLayers.Notes);
  417. // }
  418. /**
  419. * Draws an instantaneous dynamic expression (p, pp, f, ff, ...) to the canvas
  420. * @param instantaneousDynamic GraphicalInstantaneousDynamicExpression to be drawn
  421. */
  422. protected abstract drawInstantaneousDynamic(instantaneousDynamic: GraphicalInstantaneousDynamicExpression): void;
  423. /**
  424. * Draws a continuous dynamic expression (wedges) to the canvas
  425. * @param expression GraphicalContinuousDynamicExpression to be drawn
  426. */
  427. protected abstract drawContinuousDynamic(expression: GraphicalContinuousDynamicExpression): void;
  428. protected drawSymbol(symbol: MusicSymbol, symbolStyle: MusicSymbolDrawingStyle, position: PointF2D,
  429. scalingFactor: number = 1, layer: number = <number>GraphicalLayers.Notes): void {
  430. //empty
  431. }
  432. protected get leadSheet(): boolean {
  433. return this.graphicalMusicSheet.LeadSheet;
  434. }
  435. protected set leadSheet(value: boolean) {
  436. this.graphicalMusicSheet.LeadSheet = value;
  437. }
  438. protected drawPage(page: GraphicalMusicPage): void {
  439. if (!this.isVisible(page.PositionAndShape)) {
  440. return;
  441. }
  442. for (const system of page.MusicSystems) {
  443. if (this.isVisible(system.PositionAndShape)) {
  444. this.drawMusicSystem(system);
  445. }
  446. }
  447. if (page === page.Parent.MusicPages[0]) {
  448. for (const label of page.Labels) {
  449. label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
  450. }
  451. }
  452. // Draw bounding boxes for debug purposes. This has to be at the end because only
  453. // then all the calculations and recalculations are done
  454. if (this.drawableBoundingBoxElement) {
  455. this.drawBoundingBoxes(page.PositionAndShape, 0, this.drawableBoundingBoxElement);
  456. }
  457. }
  458. /**
  459. * Draw bounding boxes aroung GraphicalObjects
  460. * @param startBox Bounding Box that is used as a staring point to recursively go through all child elements
  461. * @param layer Layer to draw to
  462. * @param type Type of element to show bounding boxes for as string.
  463. */
  464. private drawBoundingBoxes(startBox: BoundingBox, layer: number = 0, type: string = "all"): void {
  465. const dataObjectString: string = (startBox.DataObject.constructor as any).name; // only works with non-minified build or sourcemap
  466. let typeMatch: boolean = false;
  467. if (type === "all") {
  468. typeMatch = true;
  469. } else {
  470. /*TODO: This seems to cause a circular reference and causes compilation to fail. */
  471. // if (type === "VexFlowStaffEntry") {
  472. // typeMatch = startBox.DataObject instanceof VexFlowStaffEntry; // circular dependencies with audio player? creates error
  473. // } else if (type === "VexFlowMeasure") {
  474. // typeMatch = startBox.DataObject instanceof VexFlowMeasure;
  475. // } else if (type === "VexFlowGraphicalNote") {
  476. // typeMatch = startBox.DataObject instanceof VexFlowGraphicalNote;
  477. // } else if (type === "VexFlowVoiceEntry") {
  478. // typeMatch = startBox.DataObject instanceof VexFlowVoiceEntry;
  479. // } else if (type === "GraphicalLabel") {
  480. // typeMatch = startBox.DataObject instanceof GraphicalLabel;
  481. // } else if (type === "VexFlowStaffLine") {
  482. // typeMatch = startBox.DataObject instanceof VexFlowStaffLine;
  483. // } else if (type === "SystemLine") {
  484. // typeMatch = startBox.DataObject instanceof SystemLine;
  485. // } else if (type === "StaffLineActivitySymbol") {
  486. // typeMatch = startBox.DataObject instanceof StaffLineActivitySymbol;
  487. // } else if (type === "VexFlowContinuousDynamicExpression") {
  488. // typeMatch = startBox.DataObject instanceof VexFlowContinuousDynamicExpression;
  489. // }
  490. // else if (type === "MusicSystem") {
  491. // typeMatch = startBox.DataObject instanceof MusicSystem;
  492. // } else if (type === "GraphicalMusicPage") {
  493. // typeMatch = startBox.DataObject instanceof GraphicalMusicPage;
  494. // }
  495. }
  496. if (typeMatch || dataObjectString === type) {
  497. this.drawBoundingBox(startBox, undefined, true, dataObjectString, layer);
  498. }
  499. layer++;
  500. startBox.ChildElements.forEach(bb => this.drawBoundingBoxes(bb, layer, type));
  501. }
  502. public drawBoundingBox(bbox: BoundingBox,
  503. color: string = undefined, drawCross: boolean = false, labelText: string = undefined, layer: number = 0
  504. ): Node {
  505. let tmpRect: RectangleF2D = new RectangleF2D(bbox.AbsolutePosition.x + bbox.BorderMarginLeft,
  506. bbox.AbsolutePosition.y + bbox.BorderMarginTop,
  507. bbox.BorderMarginRight - bbox.BorderMarginLeft,
  508. bbox.BorderMarginBottom - bbox.BorderMarginTop);
  509. if (drawCross) {
  510. this.drawLineAsHorizontalRectangle(new GraphicalLine(
  511. new PointF2D(bbox.AbsolutePosition.x - 1, bbox.AbsolutePosition.y),
  512. new PointF2D(bbox.AbsolutePosition.x + 1, bbox.AbsolutePosition.y),
  513. 0.1,
  514. OutlineAndFillStyleEnum.BaseWritingColor,
  515. color),
  516. layer - 1);
  517. this.drawLineAsVerticalRectangle(new GraphicalLine(
  518. new PointF2D(bbox.AbsolutePosition.x, bbox.AbsolutePosition.y - 1),
  519. new PointF2D(bbox.AbsolutePosition.x, bbox.AbsolutePosition.y + 1),
  520. 0.1,
  521. OutlineAndFillStyleEnum.BaseWritingColor,
  522. color),
  523. layer - 1);
  524. }
  525. tmpRect = this.applyScreenTransformationForRect(tmpRect);
  526. const rectNode: Node = this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, color, 0.5);
  527. if (labelText) {
  528. const label: Label = new Label(labelText);
  529. const specs: LabelRenderSpecs = new LabelRenderSpecs();
  530. specs.BitmapWidth = tmpRect.width;
  531. specs.BitmapHeight = tmpRect.height;
  532. specs.FontHeightInPixel = tmpRect.height;
  533. specs.ScreenPosition = new PointF2D(tmpRect.x, tmpRect.y + 12);
  534. this.renderLabel(new GraphicalLabel(label, 0.8, TextAlignmentEnum.CenterCenter, this.rules),
  535. layer, specs);
  536. // theoretically we should return the nodes from renderLabel here as well, so they can also be removed later
  537. }
  538. return rectNode;
  539. }
  540. private drawMarkedAreas(system: MusicSystem): void {
  541. for (const markedArea of system.GraphicalMarkedAreas) {
  542. if (markedArea) {
  543. if (markedArea.systemRectangle) {
  544. this.drawRectangle(markedArea.systemRectangle, <number>GraphicalLayers.Background);
  545. }
  546. if (markedArea.settings) {
  547. markedArea.settings.SVGNode = this.drawLabel(markedArea.settings, <number>GraphicalLayers.Comment);
  548. }
  549. if (markedArea.labelRectangle) {
  550. this.drawRectangle(markedArea.labelRectangle, <number>GraphicalLayers.Background);
  551. }
  552. if (markedArea.label) {
  553. markedArea.label.SVGNode = this.drawLabel(markedArea.label, <number>GraphicalLayers.Comment);
  554. }
  555. }
  556. }
  557. }
  558. private drawStaffLineSymbols(staffLine: StaffLine): void {
  559. const parentInst: Instrument = staffLine.ParentStaff.ParentInstrument;
  560. const absX: number = staffLine.PositionAndShape.AbsolutePosition.x;
  561. const absY: number = staffLine.PositionAndShape.AbsolutePosition.y + 2;
  562. const borderRight: number = staffLine.PositionAndShape.BorderRight;
  563. if (parentInst.highlight && this.drawingParameters.drawHighlights) {
  564. this.drawLineAsHorizontalRectangle(
  565. new GraphicalLine(
  566. new PointF2D(absX, absY),
  567. new PointF2D(absX + borderRight, absY),
  568. 4,
  569. OutlineAndFillStyleEnum.Highlighted
  570. ),
  571. <number>GraphicalLayers.Highlight
  572. );
  573. }
  574. let style: MusicSymbolDrawingStyle = MusicSymbolDrawingStyle.Disabled;
  575. let symbol: MusicSymbol = MusicSymbol.PLAY;
  576. let drawSymbols: boolean = this.drawingParameters.drawActivitySymbols;
  577. switch (this.phonicScoreMode) {
  578. case PhonicScoreModes.Midi:
  579. symbol = MusicSymbol.PLAY;
  580. if (this.midiPlaybackAvailable && staffLine.ParentStaff.audible) {
  581. style = MusicSymbolDrawingStyle.PlaybackSymbols;
  582. }
  583. break;
  584. case PhonicScoreModes.Following:
  585. symbol = MusicSymbol.MIC;
  586. if (staffLine.ParentStaff.following) {
  587. style = MusicSymbolDrawingStyle.FollowSymbols;
  588. }
  589. break;
  590. default:
  591. drawSymbols = false;
  592. break;
  593. }
  594. if (drawSymbols) {
  595. const p: PointF2D = new PointF2D(absX + borderRight + 2, absY);
  596. this.drawSymbol(symbol, style, p);
  597. }
  598. if (this.drawingParameters.drawErrors) {
  599. for (const measure of staffLine.Measures) {
  600. const measurePSI: BoundingBox = measure.PositionAndShape;
  601. const absXPSI: number = measurePSI.AbsolutePosition.x;
  602. const absYPSI: number = measurePSI.AbsolutePosition.y + 2;
  603. if (measure.hasError && this.graphicalMusicSheet.ParentMusicSheet.DrawErroneousMeasures) {
  604. this.drawLineAsHorizontalRectangle(
  605. new GraphicalLine(
  606. new PointF2D(absXPSI, absYPSI),
  607. new PointF2D(absXPSI + measurePSI.BorderRight, absYPSI),
  608. 4,
  609. OutlineAndFillStyleEnum.ErrorUnderlay
  610. ),
  611. <number>GraphicalLayers.MeasureError
  612. );
  613. }
  614. }
  615. }
  616. }
  617. }