MeasureSizeCalculator.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import Vex = require("vexflow");
  2. /* TODO
  3. * Take into account StaveModifiers
  4. * Take into account Ties and Slurs
  5. */
  6. /* Measure Size Calculator
  7. * Given a stave, voices and a formatter, calculates
  8. * through VexFlow the size of a measure.
  9. * !!! before using this, call the methods
  10. * !!! joinVoices and preCalculateMinTotalWidth
  11. * !!! of the formatter!
  12. *
  13. * Usage:
  14. * let stave: Vex.Flow.Stave = ...;
  15. * let formatter = new Vex.Flow.Formatter()
  16. * let voices: Vex.Flor.Voice[] = ...;
  17. * formatter.preCalculateMinTotalWidth(voices);
  18. * let calc = new MeasureSizeCalculator(stave, voices, formatter);
  19. * calc.???
  20. */
  21. export class MeasureSizeCalculator {
  22. private stave: Vex.Flow.Stave;
  23. private voices: Vex.Flow.Voice[];
  24. private formatter: any;
  25. private offsetLeft: number;
  26. private offsetRight: number;
  27. private voicesWidth: number;
  28. private topBorder: number;
  29. private bottomBorder: number;
  30. constructor(
  31. stave: Vex.Flow.Stave,
  32. voices: Vex.Flow.Voice[],
  33. formatter: Vex.Flow.Formatter
  34. ) {
  35. this.stave = stave;
  36. this.voices = voices;
  37. this.formatter = formatter;
  38. // the stave must be initialized with width, x, y 0
  39. // the voices must be already joined and (pre)formatted
  40. if (!formatter.hasMinTotalWidth) {
  41. throw "Must first call Formatter.preCalculateMinTotalWidth " +
  42. "with all the voices in the measure (vertical)";
  43. }
  44. this.format();
  45. }
  46. public getWidth(): number {
  47. // begin_modifiers + voices + end_modifiers
  48. return this.offsetLeft + this.voicesWidth + this.offsetRight;
  49. // = stave.end_x - stave.x
  50. }
  51. public getHeight(): number {
  52. // FIXME this formula does not take into account
  53. // other things like staves and ties!
  54. return this.stave.getSpacingBetweenLines()
  55. * (this.topBorder - this.bottomBorder);
  56. }
  57. // The following methods return a number
  58. // where 0 is the upper line of the stave.
  59. public getTopBorder(): number {
  60. return this.topBorder;
  61. }
  62. public getBottomBorder(): number {
  63. return this.bottomBorder;
  64. }
  65. private format(): void {
  66. let stave: Vex.Flow.Stave = this.stave;
  67. let voices: Vex.Flow.Voice[] = this.voices;
  68. let voicesBoundingBox: Vex.Flow.BoundingBox;
  69. let bb: Vex.Flow.BoundingBox;
  70. // Compute widths
  71. this.voicesWidth = this.formatter.minTotalWidth;
  72. stave.setWidth(this.voicesWidth);
  73. stave.format();
  74. this.offsetLeft = stave.getNoteStartX() - stave.x;
  75. this.offsetRight = stave.end_x - stave.getWidth() - stave.start_x;
  76. // Compute heights
  77. // Height is:
  78. //// height of StaveModifiers + BoundingBox of notes + height of NoteMod's
  79. for (let i: number = 0; i < this.voices.length; i ++) {
  80. voices[i].setStave(stave);
  81. bb = voices[i].getBoundingBox();
  82. if (voicesBoundingBox === undefined) {
  83. voicesBoundingBox = bb;
  84. } else {
  85. voicesBoundingBox = voicesBoundingBox.mergeWith(bb);
  86. }
  87. }
  88. // TODO voicesBoundingBox.getW() should be similar to this.voicesWidth?
  89. //console.log("this.width", this.voicesWidth);
  90. //console.log("voicesBB", voicesBoundingBox.getW());
  91. //this.height = voicesBoundingBox.getH(); FIXME
  92. // Consider clefs
  93. let clefs: Vex.Flow.Clef[] = stave.getModifiers(
  94. Vex.Flow.StaveModifier.Position.LEFT,
  95. Vex.Flow.Clef.category
  96. );
  97. for (let clef of clefs) {
  98. voicesBoundingBox = voicesBoundingBox.mergeWith(
  99. MeasureSizeCalculator.getClefBoundingBox(clef)
  100. );
  101. }
  102. this.topBorder = Math.min(
  103. 0,
  104. Math.floor(stave.getLineForY(voicesBoundingBox.getY()))
  105. );
  106. this.bottomBorder = Math.max(
  107. stave.getNumLines(),
  108. Math.ceil(stave.getLineForY(voicesBoundingBox.getY() + voicesBoundingBox.getH()))
  109. );
  110. }
  111. public static getClefBoundingBox(clef: Vex.Flow.Clef): Vex.Flow.BoundingBox {
  112. let clef2: any = clef;
  113. clef2.placeGlyphOnLine(clef2.glyph, clef2.stave, clef2.clef.line);
  114. let glyph: any = clef.glyph;
  115. let x_pos: number = clef.x + glyph.x_shift;
  116. let y_pos: number = clef.stave.getYForGlyphs() + glyph.y_shift;
  117. let scale: number = glyph.scale;
  118. let outline: any[] = glyph.metrics.outline;
  119. let xmin: number = 0, xmax: number = 0, ymin: number = 0, ymax: number = 0;
  120. function update(i: number): void {
  121. let x: number = outline[i + 1];
  122. let y: number = outline[i + 2];
  123. xmin = Math.min(xmin, x);
  124. xmax = Math.max(xmax, x);
  125. ymin = Math.min(ymin, y);
  126. ymax = Math.max(ymax, y);
  127. }
  128. for (let i = 0, len = outline.length; i < len; i += 3) {
  129. console.log(i, outline[i]);
  130. switch (<string> outline[i]) {
  131. case "m": update(i); break;
  132. case "l": update(i); break;
  133. case "q": i += 2; update(i); break;
  134. case "b": i += 4; update(i); break;
  135. default: break;
  136. }
  137. }
  138. return new Vex.Flow.BoundingBox(
  139. x_pos + xmin * scale,
  140. y_pos - ymin * scale,
  141. (xmax - xmin) * scale,
  142. (ymin - ymax) * scale
  143. );
  144. }
  145. }