MusicSheetDrawer.ts 28 KB

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