MusicSystemBuilder.ts 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. import {GraphicalMeasure} from "./GraphicalMeasure";
  2. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  3. import {EngravingRules} from "./EngravingRules";
  4. import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
  5. import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
  6. import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
  7. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  8. import {MusicSystem} from "./MusicSystem";
  9. import {BoundingBox} from "./BoundingBox";
  10. import {Staff} from "../VoiceData/Staff";
  11. import {Instrument} from "../Instrument";
  12. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  13. import {StaffLine} from "./StaffLine";
  14. import {GraphicalLine} from "./GraphicalLine";
  15. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  16. import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
  17. import {SystemLinesEnum} from "./SystemLinesEnum";
  18. import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
  19. import {MusicSheetCalculator} from "./MusicSheetCalculator";
  20. import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
  21. import {CollectionUtil} from "../../Util/CollectionUtil";
  22. import {SystemLinePosition} from "./SystemLinePosition";
  23. export class MusicSystemBuilder {
  24. private measureList: GraphicalMeasure[][];
  25. private graphicalMusicSheet: GraphicalMusicSheet;
  26. private currentSystemParams: SystemBuildParameters;
  27. private numberOfVisibleStaffLines: number;
  28. private rules: EngravingRules;
  29. private measureListIndex: number;
  30. private musicSystems: MusicSystem[] = [];
  31. /**
  32. * Does the mapping from the currently visible staves to the global staff-list of the music sheet.
  33. */
  34. private visibleStaffIndices: number[];
  35. private activeRhythm: RhythmInstruction[];
  36. private activeKeys: KeyInstruction[];
  37. private activeClefs: ClefInstruction[];
  38. private globalSystemIndex: number = 0;
  39. private leadSheet: boolean = false;
  40. public initialize(
  41. graphicalMusicSheet: GraphicalMusicSheet, measureList: GraphicalMeasure[][], numberOfStaffLines: number): void {
  42. this.leadSheet = graphicalMusicSheet.LeadSheet;
  43. this.graphicalMusicSheet = graphicalMusicSheet;
  44. this.rules = this.graphicalMusicSheet.ParentMusicSheet.rules;
  45. this.measureList = measureList;
  46. this.numberOfVisibleStaffLines = numberOfStaffLines;
  47. this.activeRhythm = new Array(this.numberOfVisibleStaffLines);
  48. this.activeKeys = new Array(this.numberOfVisibleStaffLines);
  49. this.activeClefs = new Array(this.numberOfVisibleStaffLines);
  50. this.initializeActiveInstructions(this.measureList[0]);
  51. }
  52. public buildMusicSystems(): MusicSystem[] {
  53. let previousMeasureEndsSystem: boolean = false;
  54. const systemMaxWidth: number = this.getFullPageSystemWidth();
  55. this.measureListIndex = 0;
  56. this.currentSystemParams = new SystemBuildParameters();
  57. // the first System - create also its Labels
  58. this.currentSystemParams.currentSystem = this.initMusicSystem();
  59. let numberOfMeasures: number = 0;
  60. for (let idx: number = 0, len: number = this.measureList.length; idx < len; ++idx) {
  61. if (this.measureList[idx].length > 0) {
  62. numberOfMeasures++;
  63. }
  64. }
  65. // go through measures and add to system until system gets too long -> finish system and start next system [line break, new system].
  66. while (this.measureListIndex < numberOfMeasures) {
  67. const graphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex];
  68. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  69. graphicalMeasures[idx].resetLayout();
  70. }
  71. const sourceMeasure: SourceMeasure = graphicalMeasures[0].parentSourceMeasure;
  72. const sourceMeasureEndsSystem: boolean = sourceMeasure.BreakSystemAfter;
  73. const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
  74. const isFirstSourceMeasure: boolean = sourceMeasure === this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  75. let currentMeasureBeginInstructionsWidth: number = this.rules.MeasureLeftMargin;
  76. let currentMeasureEndInstructionsWidth: number = 0;
  77. // calculate the current Measure Width:
  78. // The width of a measure is build up from
  79. // 1. the begin instructions (clef, Key, Rhythm),
  80. // 2. the staff entries (= notes) and
  81. // 3. the end instructions (actually only clefs)
  82. const measureStartLine: SystemLinesEnum = this.getMeasureStartLine();
  83. currentMeasureBeginInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureStartLine, isSystemStartMeasure);
  84. if (!this.leadSheet) {
  85. currentMeasureBeginInstructionsWidth += this.addBeginInstructions(graphicalMeasures, isSystemStartMeasure, isFirstSourceMeasure);
  86. currentMeasureEndInstructionsWidth += this.addEndInstructions(graphicalMeasures);
  87. }
  88. let currentMeasureVarWidth: number = 0;
  89. for (let i: number = 0; i < this.numberOfVisibleStaffLines; i++) {
  90. currentMeasureVarWidth = Math.max(currentMeasureVarWidth, graphicalMeasures[i].minimumStaffEntriesWidth);
  91. }
  92. // take into account the LineWidth after each Measure
  93. const measureEndLine: SystemLinesEnum = this.getMeasureEndLine();
  94. currentMeasureEndInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureEndLine, isSystemStartMeasure);
  95. let nextMeasureBeginInstructionWidth: number = this.rules.MeasureLeftMargin;
  96. // Check if there are key or rhythm change instructions within the next measure:
  97. if (this.measureListIndex + 1 < this.measureList.length) {
  98. const nextGraphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex + 1];
  99. const nextSourceMeasure: SourceMeasure = nextGraphicalMeasures[0].parentSourceMeasure;
  100. if (nextSourceMeasure.hasBeginInstructions()) {
  101. nextMeasureBeginInstructionWidth += this.addBeginInstructions(nextGraphicalMeasures, false, false);
  102. }
  103. }
  104. const totalMeasureWidth: number = currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth + currentMeasureVarWidth;
  105. const measureFitsInSystem: boolean = this.currentSystemParams.currentWidth + totalMeasureWidth + nextMeasureBeginInstructionWidth < systemMaxWidth;
  106. if (isSystemStartMeasure || measureFitsInSystem) {
  107. this.addMeasureToSystem(
  108. graphicalMeasures, measureStartLine, measureEndLine, totalMeasureWidth,
  109. currentMeasureBeginInstructionsWidth, currentMeasureVarWidth, currentMeasureEndInstructionsWidth
  110. );
  111. this.updateActiveClefs(sourceMeasure, graphicalMeasures);
  112. this.measureListIndex++;
  113. } else {
  114. // finalize current system and prepare a new one
  115. this.finalizeCurrentAndCreateNewSystem(graphicalMeasures, previousMeasureEndsSystem);
  116. // don't increase measure index to check this measure now again
  117. }
  118. previousMeasureEndsSystem = sourceMeasureEndsSystem;
  119. }
  120. this.finalizeCurrentAndCreateNewSystem(this.measureList[this.measureList.length - 1], true);
  121. return this.musicSystems;
  122. }
  123. /**
  124. * calculates the y positions of the staff lines within a system and
  125. * furthermore the y positions of the systems themselves.
  126. */
  127. public calculateSystemYLayout(): void {
  128. for (const musicSystem of this.musicSystems) {
  129. this.optimizeDistanceBetweenStaffLines(musicSystem);
  130. }
  131. // set y positions of systems using the previous system and a fixed distance.
  132. this.calculateMusicSystemsRelativePositions();
  133. }
  134. /**
  135. * Set the Width of the staff-Measures of one source measure.
  136. * @param graphicalMeasures
  137. * @param width
  138. * @param beginInstrWidth
  139. * @param endInstrWidth
  140. */
  141. private setMeasureWidth(graphicalMeasures: GraphicalMeasure[], width: number, beginInstrWidth: number, endInstrWidth: number): void {
  142. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  143. const measure: GraphicalMeasure = graphicalMeasures[idx];
  144. measure.setWidth(width);
  145. if (beginInstrWidth > 0) {
  146. measure.beginInstructionsWidth = beginInstrWidth;
  147. }
  148. if (endInstrWidth > 0) {
  149. measure.endInstructionsWidth = endInstrWidth;
  150. }
  151. }
  152. }
  153. /**
  154. * When the actual source measure doesn't fit any more, this method finalizes the current system and
  155. * opens up a new empty system, where the actual measure will be added in the next iteration.
  156. * @param measures
  157. * @param isPartEndingSystem
  158. */
  159. private finalizeCurrentAndCreateNewSystem(measures: GraphicalMeasure[], isPartEndingSystem: boolean = false): void {
  160. this.adaptRepetitionLineWithIfNeeded();
  161. if (!isPartEndingSystem) {
  162. this.checkAndCreateExtraInstructionMeasure(measures);
  163. }
  164. this.stretchMusicSystem(isPartEndingSystem);
  165. this.currentSystemParams = new SystemBuildParameters();
  166. if (this.measureListIndex < this.measureList.length) {
  167. this.currentSystemParams.currentSystem = this.initMusicSystem();
  168. }
  169. }
  170. /**
  171. * If a line repetition is ending and a new line repetition is starting at the end of the system,
  172. * the double repetition line has to be split into two: one at the currently ending system and
  173. * one at the next system.
  174. * (this should be refactored at some point to not use a combined end/start line but always separated lines)
  175. */
  176. private adaptRepetitionLineWithIfNeeded(): void {
  177. const systemMeasures: MeasureBuildParameters[] = this.currentSystemParams.systemMeasures;
  178. if (systemMeasures.length >= 1) {
  179. const measures: GraphicalMeasure[] =
  180. this.currentSystemParams.currentSystem.GraphicalMeasures[this.currentSystemParams.currentSystem.GraphicalMeasures.length - 1];
  181. const measureParams: MeasureBuildParameters = systemMeasures[systemMeasures.length - 1];
  182. let diff: number = 0.0;
  183. if (measureParams.endLine === SystemLinesEnum.DotsBoldBoldDots) {
  184. measureParams.endLine = SystemLinesEnum.DotsThinBold;
  185. diff = measures[0].getLineWidth(SystemLinesEnum.DotsBoldBoldDots) / 2 - measures[0].getLineWidth(SystemLinesEnum.DotsThinBold);
  186. }
  187. this.currentSystemParams.currentSystemFixWidth -= diff;
  188. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  189. const measure: GraphicalMeasure = measures[idx];
  190. measure.endInstructionsWidth -= diff;
  191. }
  192. }
  193. }
  194. private addMeasureToSystem(
  195. graphicalMeasures: GraphicalMeasure[], measureStartLine: SystemLinesEnum, measureEndLine: SystemLinesEnum,
  196. totalMeasureWidth: number, currentMeasureBeginInstructionsWidth: number, currentVarWidth: number, currentMeasureEndInstructionsWidth: number
  197. ): void {
  198. this.currentSystemParams.systemMeasures.push({beginLine: measureStartLine, endLine: measureEndLine});
  199. this.setMeasureWidth(
  200. graphicalMeasures, totalMeasureWidth, currentMeasureBeginInstructionsWidth, currentMeasureEndInstructionsWidth
  201. );
  202. this.addStaveMeasuresToSystem(graphicalMeasures);
  203. this.currentSystemParams.currentWidth += totalMeasureWidth;
  204. this.currentSystemParams.currentSystemFixWidth += currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth;
  205. this.currentSystemParams.currentSystemVarWidth += currentVarWidth;
  206. this.currentSystemParams.systemMeasureIndex++;
  207. }
  208. /**
  209. * Initialize a new [[MusicSystem]].
  210. * @returns {MusicSystem}
  211. */
  212. private initMusicSystem(): MusicSystem {
  213. const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.globalSystemIndex++);
  214. this.musicSystems.push(musicSystem);
  215. this.layoutSystemStaves(musicSystem);
  216. musicSystem.createMusicSystemLabel(
  217. this.rules.InstrumentLabelTextHeight,
  218. this.rules.SystemLabelsRightMargin,
  219. this.rules.LabelMarginBorderFactor,
  220. this.musicSystems.length === 1
  221. );
  222. return musicSystem;
  223. }
  224. /**
  225. * Get the width the system should have for a given page width.
  226. * @returns {number}
  227. */
  228. private getFullPageSystemWidth(): number {
  229. return this.graphicalMusicSheet.ParentMusicSheet.pageWidth - this.rules.PageLeftMargin
  230. - this.rules.PageRightMargin - this.rules.SystemLeftMargin - this.rules.SystemRightMargin;
  231. }
  232. private layoutSystemStaves(musicSystem: MusicSystem): void {
  233. const systemWidth: number = this.getFullPageSystemWidth();
  234. const boundingBox: BoundingBox = musicSystem.PositionAndShape;
  235. boundingBox.BorderLeft = 0.0;
  236. boundingBox.BorderRight = systemWidth;
  237. boundingBox.BorderTop = 0.0;
  238. const staffList: Staff[] = [];
  239. const instruments: Instrument[] = this.graphicalMusicSheet.ParentMusicSheet.Instruments;
  240. for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
  241. const instrument: Instrument = instruments[idx];
  242. if (instrument.Voices.length === 0 || !instrument.Visible) {
  243. continue;
  244. }
  245. for (let idx2: number = 0, len2: number = instrument.Staves.length; idx2 < len2; ++idx2) {
  246. const staff: Staff = instrument.Staves[idx2];
  247. staffList.push(staff);
  248. }
  249. }
  250. let multiLyrics: boolean = false;
  251. if (this.leadSheet) {
  252. for (let idx: number = 0, len: number = staffList.length; idx < len; ++idx) {
  253. const staff: Staff = staffList[idx];
  254. if (staff.ParentInstrument.LyricVersesNumbers.length > 1) {
  255. multiLyrics = true;
  256. break;
  257. }
  258. }
  259. }
  260. let yOffsetSum: number = 0;
  261. for (let i: number = 0; i < staffList.length; i++) {
  262. this.addStaffLineToMusicSystem(musicSystem, yOffsetSum, staffList[i]);
  263. yOffsetSum += this.rules.StaffHeight;
  264. if (i + 1 < staffList.length) {
  265. let yOffset: number = 0;
  266. if (this.leadSheet && !multiLyrics) {
  267. yOffset = 2.5;
  268. } else {
  269. if (staffList[i].ParentInstrument === staffList[i + 1].ParentInstrument) {
  270. yOffset = this.rules.BetweenStaffDistance;
  271. } else {
  272. yOffset = this.rules.StaffDistance;
  273. }
  274. }
  275. yOffsetSum += yOffset;
  276. }
  277. }
  278. boundingBox.BorderBottom = yOffsetSum;
  279. }
  280. /**
  281. * Calculate the [[StaffLine]](s) needed for a [[MusicSystem]].
  282. * @param musicSystem
  283. * @param relativeYPosition
  284. * @param staff
  285. */
  286. private addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
  287. if (musicSystem !== undefined) {
  288. const staffLine: StaffLine = MusicSheetCalculator.symbolFactory.createStaffLine(musicSystem, staff);
  289. musicSystem.StaffLines.push(staffLine);
  290. const boundingBox: BoundingBox = staffLine.PositionAndShape;
  291. const relativePosition: PointF2D = new PointF2D();
  292. if (musicSystem === this.musicSystems[0] &&
  293. !EngravingRules.Rules.CompactMode) {
  294. relativePosition.x = this.rules.FirstSystemMargin;
  295. boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
  296. } else {
  297. relativePosition.x = 0.0;
  298. boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
  299. }
  300. relativePosition.y = relativeYPosition;
  301. boundingBox.RelativePosition = relativePosition;
  302. boundingBox.BorderLeft = 0.0;
  303. boundingBox.BorderTop = 0.0;
  304. boundingBox.BorderBottom = this.rules.StaffHeight;
  305. for (let i: number = 0; i < 5; i++) {
  306. const start: PointF2D = new PointF2D();
  307. start.x = 0.0;
  308. start.y = i * this.rules.StaffHeight / 4;
  309. const end: PointF2D = new PointF2D();
  310. end.x = staffLine.PositionAndShape.Size.width;
  311. end.y = i * this.rules.StaffHeight / 4;
  312. if (this.leadSheet) {
  313. start.y = end.y = 0;
  314. }
  315. staffLine.StaffLines[i] = new GraphicalLine(start, end, this.rules.StaffLineWidth);
  316. }
  317. }
  318. }
  319. /**
  320. * Initialize the active Instructions from the first [[SourceMeasure]] of first [[SourceMusicPart]].
  321. * @param measureList
  322. */
  323. private initializeActiveInstructions(measureList: GraphicalMeasure[]): void {
  324. const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  325. if (firstSourceMeasure !== undefined) {
  326. this.visibleStaffIndices = this.graphicalMusicSheet.getVisibleStavesIndicesFromSourceMeasure(measureList);
  327. for (let i: number = 0, len: number = this.visibleStaffIndices.length; i < len; i++) {
  328. const staffIndex: number = this.visibleStaffIndices[i];
  329. const graphicalMeasure: GraphicalMeasure = this.graphicalMusicSheet
  330. .getGraphicalMeasureFromSourceMeasureAndIndex(firstSourceMeasure, staffIndex);
  331. this.activeClefs[i] = <ClefInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[0];
  332. let keyInstruction: KeyInstruction = KeyInstruction.copy(
  333. <KeyInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[1]);
  334. keyInstruction = this.transposeKeyInstruction(keyInstruction, graphicalMeasure);
  335. this.activeKeys[i] = keyInstruction;
  336. this.activeRhythm[i] = <RhythmInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[2];
  337. }
  338. }
  339. }
  340. private transposeKeyInstruction(keyInstruction: KeyInstruction, graphicalMeasure: GraphicalMeasure): KeyInstruction {
  341. if (this.graphicalMusicSheet.ParentMusicSheet.Transpose !== 0
  342. && graphicalMeasure.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion
  343. && MusicSheetCalculator.transposeCalculator !== undefined
  344. ) {
  345. MusicSheetCalculator.transposeCalculator.transposeKey(
  346. keyInstruction,
  347. this.graphicalMusicSheet.ParentMusicSheet.Transpose
  348. );
  349. }
  350. return keyInstruction;
  351. }
  352. /**
  353. * Calculate the width needed for Instructions (Key, Clef, Rhythm, Repetition) for the measure.
  354. * @param measures
  355. * @param isSystemFirstMeasure
  356. * @param isFirstSourceMeasure
  357. * @returns {number}
  358. */
  359. private addBeginInstructions(measures: GraphicalMeasure[], isSystemFirstMeasure: boolean, isFirstSourceMeasure: boolean): number {
  360. const measureCount: number = measures.length;
  361. if (measureCount === 0) {
  362. return 0;
  363. }
  364. let totalBeginInstructionLengthX: number = 0.0;
  365. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  366. for (let idx: number = 0; idx < measureCount; ++idx) {
  367. const measure: GraphicalMeasure = measures[idx];
  368. const staffIndex: number = this.visibleStaffIndices[idx];
  369. const beginInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[staffIndex];
  370. const beginInstructionLengthX: number = this.AddInstructionsAtMeasureBegin(
  371. beginInstructionsStaffEntry, measure,
  372. idx, isFirstSourceMeasure,
  373. isSystemFirstMeasure
  374. );
  375. totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
  376. }
  377. return totalBeginInstructionLengthX;
  378. }
  379. /**
  380. * Calculates the width needed for Instructions (Clef, Repetition) for the measure.
  381. * @param measures
  382. * @returns {number}
  383. */
  384. private addEndInstructions(measures: GraphicalMeasure[]): number {
  385. const measureCount: number = measures.length;
  386. if (measureCount === 0) {
  387. return 0;
  388. }
  389. let totalEndInstructionLengthX: number = 0.5;
  390. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  391. for (let idx: number = 0; idx < measureCount; idx++) {
  392. const measure: GraphicalMeasure = measures[idx];
  393. const staffIndex: number = this.visibleStaffIndices[idx];
  394. const endInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
  395. const endInstructionLengthX: number = this.addInstructionsAtMeasureEnd(endInstructionsStaffEntry, measure);
  396. totalEndInstructionLengthX = Math.max(totalEndInstructionLengthX, endInstructionLengthX);
  397. }
  398. return totalEndInstructionLengthX;
  399. }
  400. private AddInstructionsAtMeasureBegin(firstEntry: SourceStaffEntry, measure: GraphicalMeasure,
  401. visibleStaffIdx: number, isFirstSourceMeasure: boolean, isSystemStartMeasure: boolean): number {
  402. let instructionsLengthX: number = 0;
  403. let currentClef: ClefInstruction = undefined;
  404. let currentKey: KeyInstruction = undefined;
  405. let currentRhythm: RhythmInstruction = undefined;
  406. if (firstEntry !== undefined) {
  407. for (let idx: number = 0, len: number = firstEntry.Instructions.length; idx < len; ++idx) {
  408. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  409. if (abstractNotationInstruction instanceof ClefInstruction) {
  410. currentClef = <ClefInstruction>abstractNotationInstruction;
  411. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  412. currentKey = <KeyInstruction>abstractNotationInstruction;
  413. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  414. currentRhythm = <RhythmInstruction>abstractNotationInstruction;
  415. }
  416. }
  417. }
  418. if (isSystemStartMeasure) {
  419. if (currentClef === undefined) {
  420. currentClef = this.activeClefs[visibleStaffIdx];
  421. }
  422. if (currentKey === undefined) {
  423. currentKey = this.activeKeys[visibleStaffIdx];
  424. }
  425. if (isFirstSourceMeasure && currentRhythm === undefined) {
  426. currentRhythm = this.activeRhythm[visibleStaffIdx];
  427. }
  428. }
  429. let clefAdded: boolean = false;
  430. let keyAdded: boolean = false;
  431. let rhythmAdded: boolean = false;
  432. if (currentClef !== undefined) {
  433. measure.addClefAtBegin(currentClef);
  434. clefAdded = true;
  435. } else {
  436. currentClef = this.activeClefs[visibleStaffIdx];
  437. }
  438. if (currentKey !== undefined) {
  439. currentKey = this.transposeKeyInstruction(currentKey, measure);
  440. const previousKey: KeyInstruction = isSystemStartMeasure ? undefined : this.activeKeys[visibleStaffIdx];
  441. measure.addKeyAtBegin(currentKey, previousKey, currentClef);
  442. keyAdded = true;
  443. }
  444. if (currentRhythm !== undefined && currentRhythm.PrintObject) {
  445. measure.addRhythmAtBegin(currentRhythm);
  446. rhythmAdded = true;
  447. }
  448. if (clefAdded || keyAdded || rhythmAdded) {
  449. instructionsLengthX += measure.beginInstructionsWidth;
  450. if (rhythmAdded) {
  451. instructionsLengthX += this.rules.RhythmRightMargin;
  452. }
  453. }
  454. return instructionsLengthX;
  455. }
  456. private addInstructionsAtMeasureEnd(lastEntry: SourceStaffEntry, measure: GraphicalMeasure): number {
  457. if (lastEntry === undefined || lastEntry.Instructions === undefined || lastEntry.Instructions.length === 0) {
  458. return 0;
  459. }
  460. for (let idx: number = 0, len: number = lastEntry.Instructions.length; idx < len; ++idx) {
  461. const abstractNotationInstruction: AbstractNotationInstruction = lastEntry.Instructions[idx];
  462. if (abstractNotationInstruction instanceof ClefInstruction) {
  463. const activeClef: ClefInstruction = <ClefInstruction>abstractNotationInstruction;
  464. measure.addClefAtEnd(activeClef);
  465. }
  466. }
  467. return this.rules.MeasureRightMargin + measure.endInstructionsWidth;
  468. }
  469. /**
  470. * Track down and update the active ClefInstruction in Measure's StaffEntries.
  471. * This has to be done after the measure is added to a system
  472. * (otherwise already the check if the measure fits to the system would update the active clefs..)
  473. * @param measure
  474. * @param graphicalMeasures
  475. */
  476. private updateActiveClefs(measure: SourceMeasure, graphicalMeasures: GraphicalMeasure[]): void {
  477. for (let visStaffIdx: number = 0, len: number = graphicalMeasures.length; visStaffIdx < len; visStaffIdx++) {
  478. const staffIndex: number = this.visibleStaffIndices[visStaffIdx];
  479. const firstEntry: SourceStaffEntry = measure.FirstInstructionsStaffEntries[staffIndex];
  480. if (firstEntry !== undefined) {
  481. for (let idx: number = 0, len2: number = firstEntry.Instructions.length; idx < len2; ++idx) {
  482. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  483. if (abstractNotationInstruction instanceof ClefInstruction) {
  484. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  485. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  486. this.activeKeys[visStaffIdx] = <KeyInstruction>abstractNotationInstruction;
  487. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  488. this.activeRhythm[visStaffIdx] = <RhythmInstruction>abstractNotationInstruction;
  489. }
  490. }
  491. }
  492. const entries: SourceStaffEntry[] = measure.getEntriesPerStaff(staffIndex);
  493. for (let idx: number = 0, len2: number = entries.length; idx < len2; ++idx) {
  494. const staffEntry: SourceStaffEntry = entries[idx];
  495. if (staffEntry.Instructions !== undefined) {
  496. for (let idx2: number = 0, len3: number = staffEntry.Instructions.length; idx2 < len3; ++idx2) {
  497. const abstractNotationInstruction: AbstractNotationInstruction = staffEntry.Instructions[idx2];
  498. if (abstractNotationInstruction instanceof ClefInstruction) {
  499. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  500. }
  501. }
  502. }
  503. }
  504. const lastEntry: SourceStaffEntry = measure.LastInstructionsStaffEntries[staffIndex];
  505. if (lastEntry !== undefined) {
  506. const instructions: AbstractNotationInstruction[] = lastEntry.Instructions;
  507. for (let idx: number = 0, len3: number = instructions.length; idx < len3; ++idx) {
  508. const abstractNotationInstruction: AbstractNotationInstruction = instructions[idx];
  509. if (abstractNotationInstruction instanceof ClefInstruction) {
  510. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  511. }
  512. }
  513. }
  514. }
  515. }
  516. /**
  517. * Check if an extra Instruction [[Measure]] is needed.
  518. * @param measures
  519. */
  520. private checkAndCreateExtraInstructionMeasure(measures: GraphicalMeasure[]): void {
  521. const firstStaffEntries: SourceStaffEntry[] = measures[0].parentSourceMeasure.FirstInstructionsStaffEntries;
  522. const visibleInstructionEntries: SourceStaffEntry[] = [];
  523. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  524. const measure: GraphicalMeasure = measures[idx];
  525. visibleInstructionEntries.push(firstStaffEntries[measure.ParentStaff.idInMusicSheet]);
  526. }
  527. let maxMeasureWidth: number = 0;
  528. for (let visStaffIdx: number = 0, len: number = visibleInstructionEntries.length; visStaffIdx < len; ++visStaffIdx) {
  529. const sse: SourceStaffEntry = visibleInstructionEntries[visStaffIdx];
  530. if (sse === undefined) {
  531. continue;
  532. }
  533. const instructions: AbstractNotationInstruction[] = sse.Instructions;
  534. let keyInstruction: KeyInstruction = undefined;
  535. let rhythmInstruction: RhythmInstruction = undefined;
  536. for (let idx2: number = 0, len2: number = instructions.length; idx2 < len2; ++idx2) {
  537. const instruction: AbstractNotationInstruction = instructions[idx2];
  538. if (instruction instanceof KeyInstruction && (<KeyInstruction>instruction).Key !== this.activeKeys[visStaffIdx].Key) {
  539. keyInstruction = <KeyInstruction>instruction;
  540. }
  541. if (instruction instanceof RhythmInstruction && (<RhythmInstruction>instruction) !== this.activeRhythm[visStaffIdx]) {
  542. rhythmInstruction = <RhythmInstruction>instruction;
  543. }
  544. }
  545. if (keyInstruction !== undefined || rhythmInstruction !== undefined) {
  546. const measureWidth: number = this.addExtraInstructionMeasure(visStaffIdx, keyInstruction, rhythmInstruction);
  547. maxMeasureWidth = Math.max(maxMeasureWidth, measureWidth);
  548. }
  549. }
  550. if (maxMeasureWidth > 0) {
  551. this.currentSystemParams.systemMeasures.push({
  552. beginLine: SystemLinesEnum.None,
  553. endLine: SystemLinesEnum.None,
  554. });
  555. this.currentSystemParams.currentWidth += maxMeasureWidth;
  556. this.currentSystemParams.currentSystemFixWidth += maxMeasureWidth;
  557. }
  558. }
  559. private addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
  560. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  561. const measures: GraphicalMeasure[] = [];
  562. const measure: GraphicalMeasure = MusicSheetCalculator.symbolFactory.createExtraGraphicalMeasure(currentSystem.StaffLines[visStaffIdx]);
  563. measures.push(measure);
  564. if (keyInstruction !== undefined) {
  565. measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
  566. }
  567. if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
  568. measure.addRhythmAtBegin(rhythmInstruction);
  569. }
  570. measure.PositionAndShape.BorderLeft = 0.0;
  571. measure.PositionAndShape.BorderTop = 0.0;
  572. measure.PositionAndShape.BorderBottom = this.rules.StaffHeight;
  573. const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
  574. measure.PositionAndShape.BorderRight = width;
  575. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  576. return width;
  577. }
  578. /**
  579. * Add all current vertical Measures to currentSystem.
  580. * @param graphicalMeasures
  581. */
  582. private addStaveMeasuresToSystem(graphicalMeasures: GraphicalMeasure[]): void {
  583. if (graphicalMeasures[0] !== undefined) {
  584. const gmeasures: GraphicalMeasure[] = [];
  585. for (let i: number = 0; i < graphicalMeasures.length; i++) {
  586. gmeasures.push(graphicalMeasures[i]);
  587. }
  588. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  589. for (let visStaffIdx: number = 0; visStaffIdx < this.numberOfVisibleStaffLines; visStaffIdx++) {
  590. const measure: GraphicalMeasure = gmeasures[visStaffIdx];
  591. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  592. measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
  593. }
  594. currentSystem.AddGraphicalMeasures(gmeasures);
  595. }
  596. }
  597. /**
  598. * Return the width of the corresponding [[SystemLine]] and set the corresponding [[SystemLineEnum]].
  599. * @returns {SystemLinesEnum}
  600. */
  601. private getMeasureStartLine(): SystemLinesEnum {
  602. const thisMeasureBeginsLineRep: boolean = this.thisMeasureBeginsLineRepetition();
  603. if (thisMeasureBeginsLineRep) {
  604. const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
  605. const isGlobalFirstMeasure: boolean = this.measureListIndex === 0;
  606. if (this.previousMeasureEndsLineRepetition() && !isSystemStartMeasure) {
  607. return SystemLinesEnum.DotsBoldBoldDots;
  608. }
  609. if (!isGlobalFirstMeasure) {
  610. return SystemLinesEnum.BoldThinDots;
  611. }
  612. }
  613. return SystemLinesEnum.None;
  614. }
  615. private getMeasureEndLine(): SystemLinesEnum {
  616. let sourceMeasure: SourceMeasure = undefined;
  617. try {
  618. sourceMeasure = this.measureList[this.measureListIndex][0].parentSourceMeasure;
  619. } finally {
  620. // do nothing
  621. }
  622. if (this.nextMeasureBeginsLineRepetition() && this.thisMeasureEndsLineRepetition()) {
  623. return SystemLinesEnum.DotsBoldBoldDots;
  624. }
  625. if (this.thisMeasureEndsLineRepetition()) {
  626. return SystemLinesEnum.DotsThinBold;
  627. }
  628. // always end piece with final barline: not a good idea. user should be able to override final barline.
  629. // also, selecting range of measures to draw would always end with final barline, even if extract is from the middle of the piece
  630. // this was probably done before we parsed the barline type from XML.
  631. /*if (this.measureListIndex === this.measureList.length - 1 || this.measureList[this.measureListIndex][0].parentSourceMeasure.endsPiece) {
  632. return SystemLinesEnum.ThinBold;
  633. }*/
  634. if (this.nextMeasureHasKeyInstructionChange() || this.thisMeasureEndsWordRepetition() || this.nextMeasureBeginsWordRepetition()) {
  635. return SystemLinesEnum.DoubleThin;
  636. }
  637. if (!sourceMeasure) {
  638. return SystemLinesEnum.SingleThin;
  639. }
  640. if (sourceMeasure.endingBarStyleEnum !== undefined) {
  641. return sourceMeasure.endingBarStyleEnum;
  642. }
  643. // TODO: print an error message if the default fallback is used.
  644. return SystemLinesEnum.SingleThin;
  645. }
  646. /**
  647. * Return the width of the corresponding [[SystemLine]] and sets the corresponding [[SystemLineEnum]].
  648. * @param measure
  649. * @param systemLineEnum
  650. * @param isSystemStartMeasure
  651. * @returns {number}
  652. */
  653. private getLineWidth(measure: GraphicalMeasure, systemLineEnum: SystemLinesEnum, isSystemStartMeasure: boolean): number {
  654. let width: number = measure.getLineWidth(systemLineEnum);
  655. if (systemLineEnum === SystemLinesEnum.DotsBoldBoldDots) {
  656. width /= 2;
  657. }
  658. if (isSystemStartMeasure && systemLineEnum === SystemLinesEnum.BoldThinDots) {
  659. width += this.rules.DistanceBetweenLastInstructionAndRepetitionBarline;
  660. }
  661. return width;
  662. }
  663. private previousMeasureEndsLineRepetition(): boolean {
  664. if (this.measureListIndex === 0) {
  665. return false;
  666. }
  667. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex - 1].length; idx < len; ++idx) {
  668. const measure: GraphicalMeasure = this.measureList[this.measureListIndex - 1][idx];
  669. if (measure.endsWithLineRepetition()) {
  670. return true;
  671. }
  672. }
  673. return false;
  674. }
  675. /**
  676. * Check if at this [[Measure]] starts a [[Repetition]].
  677. * @returns {boolean}
  678. */
  679. private thisMeasureBeginsLineRepetition(): boolean {
  680. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  681. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  682. if (measure.beginsWithLineRepetition()) {
  683. return true;
  684. }
  685. }
  686. return false;
  687. }
  688. /**
  689. * Check if a [[Repetition]] starts at the next [[Measure]].
  690. * @returns {boolean}
  691. */
  692. private nextMeasureBeginsLineRepetition(): boolean {
  693. const nextMeasureIndex: number = this.measureListIndex + 1;
  694. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length
  695. || !this.measureList[nextMeasureIndex]) {
  696. return false;
  697. }
  698. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  699. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  700. if (measure.beginsWithLineRepetition()) {
  701. return true;
  702. }
  703. }
  704. return false;
  705. }
  706. /**
  707. * Check if this [[Measure]] is a [[Repetition]] ending.
  708. * @returns {boolean}
  709. */
  710. private thisMeasureEndsLineRepetition(): boolean {
  711. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  712. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  713. if (measure.endsWithLineRepetition()) {
  714. return true;
  715. }
  716. }
  717. return false;
  718. }
  719. /**
  720. * Check if a [[Repetition]] starts at the next [[Measure]].
  721. * @returns {boolean}
  722. */
  723. private nextMeasureBeginsWordRepetition(): boolean {
  724. const nextMeasureIndex: number = this.measureListIndex + 1;
  725. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length ||
  726. nextMeasureIndex > this.measureList.length - 1) {
  727. return false;
  728. }
  729. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  730. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  731. if (measure.beginsWithWordRepetition()) {
  732. return true;
  733. }
  734. }
  735. return false;
  736. }
  737. /**
  738. * Check if this [[Measure]] is a [[Repetition]] ending.
  739. * @returns {boolean}
  740. */
  741. private thisMeasureEndsWordRepetition(): boolean {
  742. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  743. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  744. if (measure.endsWithWordRepetition()) {
  745. return true;
  746. }
  747. }
  748. return false;
  749. }
  750. /**
  751. * Check if the next [[Measure]] has a [[KeyInstruction]] change.
  752. * @returns {boolean}
  753. */
  754. private nextMeasureHasKeyInstructionChange(): boolean {
  755. return this.getNextMeasureKeyInstruction() !== undefined;
  756. }
  757. private getNextMeasureKeyInstruction(): KeyInstruction {
  758. if (this.measureListIndex < this.measureList.length - 1) {
  759. for (let visIndex: number = 0; visIndex < this.measureList[this.measureListIndex].length; visIndex++) {
  760. const sourceMeasure: SourceMeasure = this.measureList[this.measureListIndex + 1][visIndex].parentSourceMeasure;
  761. if (sourceMeasure === undefined) {
  762. return undefined;
  763. }
  764. return sourceMeasure.getKeyInstruction(this.visibleStaffIndices[visIndex]);
  765. }
  766. }
  767. return undefined;
  768. }
  769. /**
  770. * Calculate the X ScalingFactor in order to strech the whole System.
  771. * @param systemFixWidth
  772. * @param systemVarWidth
  773. * @returns {number}
  774. */
  775. private calculateXScalingFactor(systemFixWidth: number, systemVarWidth: number): number {
  776. if (Math.abs(systemVarWidth - 0) < 0.00001 || Math.abs(systemFixWidth - 0) < 0.00001) {
  777. return 1.0;
  778. }
  779. let systemEndX: number;
  780. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  781. systemEndX = currentSystem.StaffLines[0].PositionAndShape.Size.width;
  782. const scalingFactor: number = (systemEndX - systemFixWidth) / systemVarWidth;
  783. return scalingFactor;
  784. }
  785. /**
  786. * Stretch the whole System so that no white space is left at the end.
  787. * @param isPartEndingSystem
  788. */
  789. private stretchMusicSystem(isPartEndingSystem: boolean): void {
  790. let scalingFactor: number = this.calculateXScalingFactor(
  791. this.currentSystemParams.currentSystemFixWidth, this.currentSystemParams.currentSystemVarWidth
  792. );
  793. if (isPartEndingSystem) {
  794. scalingFactor = Math.min(scalingFactor, this.rules.LastSystemMaxScalingFactor);
  795. }
  796. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  797. for (let visStaffIdx: number = 0, len: number = currentSystem.StaffLines.length; visStaffIdx < len; ++visStaffIdx) {
  798. const staffLine: StaffLine = currentSystem.StaffLines[visStaffIdx];
  799. let currentXPosition: number = 0.0;
  800. for (let measureIndex: number = 0; measureIndex < staffLine.Measures.length; measureIndex++) {
  801. const measure: GraphicalMeasure = staffLine.Measures[measureIndex];
  802. measure.setPositionInStaffline(currentXPosition);
  803. measure.setWidth(measure.beginInstructionsWidth + measure.minimumStaffEntriesWidth * scalingFactor + measure.endInstructionsWidth);
  804. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  805. const startLine: SystemLinesEnum = this.currentSystemParams.systemMeasures[measureIndex].beginLine;
  806. const lineWidth: number = measure.getLineWidth(SystemLinesEnum.BoldThinDots);
  807. switch (startLine) {
  808. case SystemLinesEnum.BoldThinDots:
  809. let xPosition: number = currentXPosition;
  810. if (measureIndex === 0) {
  811. xPosition = currentXPosition + measure.beginInstructionsWidth - lineWidth;
  812. }
  813. currentSystem.createVerticalLineForMeasure(xPosition, lineWidth, startLine, SystemLinePosition.MeasureBegin, measureIndex, measure);
  814. break;
  815. default:
  816. }
  817. }
  818. measure.staffEntriesScaleFactor = scalingFactor;
  819. measure.layoutSymbols();
  820. const nextMeasureHasRepStartLine: boolean = measureIndex + 1 < this.currentSystemParams.systemMeasures.length
  821. && this.currentSystemParams.systemMeasures[measureIndex + 1].beginLine === SystemLinesEnum.BoldThinDots;
  822. if (!nextMeasureHasRepStartLine) {
  823. let endLine: SystemLinesEnum = SystemLinesEnum.SingleThin;
  824. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  825. endLine = this.currentSystemParams.systemMeasures[measureIndex].endLine;
  826. }
  827. const lineWidth: number = measure.getLineWidth(endLine);
  828. let xPos: number = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight - lineWidth;
  829. if (endLine === SystemLinesEnum.DotsBoldBoldDots) {
  830. xPos -= lineWidth / 2;
  831. }
  832. currentSystem.createVerticalLineForMeasure(xPos, lineWidth, endLine, SystemLinePosition.MeasureEnd, measureIndex, measure);
  833. }
  834. currentXPosition = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight;
  835. }
  836. }
  837. if (isPartEndingSystem) {
  838. this.decreaseMusicSystemBorders();
  839. }
  840. }
  841. /**
  842. * If the last [[MusicSystem]] doesn't need stretching, then this method decreases the System's Width,
  843. * the [[StaffLine]]'s Width and the 5 [[StaffLine]]s length.
  844. */
  845. private decreaseMusicSystemBorders(): void {
  846. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  847. const bb: BoundingBox = CollectionUtil.last(currentSystem.StaffLines[0].Measures).PositionAndShape;
  848. const width: number = bb.RelativePosition.x + bb.Size.width;
  849. for (let idx: number = 0, len: number = currentSystem.StaffLines.length; idx < len; ++idx) {
  850. const staffLine: StaffLine = currentSystem.StaffLines[idx];
  851. staffLine.PositionAndShape.BorderRight = width;
  852. for (let idx2: number = 0, len2: number = staffLine.StaffLines.length; idx2 < len2; ++idx2) {
  853. const graphicalLine: GraphicalLine = staffLine.StaffLines[idx2];
  854. graphicalLine.End = new PointF2D(width, graphicalLine.End.y);
  855. }
  856. }
  857. currentSystem.PositionAndShape.BorderRight = width + this.currentSystemParams.maxLabelLength + this.rules.SystemLabelsRightMargin;
  858. }
  859. /**
  860. * This method checks the distances between two System's StaffLines and if needed, shifts the lower down.
  861. * @param musicSystem
  862. */
  863. private optimizeDistanceBetweenStaffLines(musicSystem: MusicSystem): void {
  864. // don't perform any y-spacing in case of a StaffEntryLink (in both StaffLines)
  865. if (!musicSystem.checkStaffEntriesForStaffEntryLink()) {
  866. for (let i: number = 0; i < musicSystem.StaffLines.length - 1; i++) {
  867. const upperBottomLine: number = musicSystem.StaffLines[i].SkyBottomLineCalculator.getBottomLineMax();
  868. // TODO: Lower skyline should add to offset when there are items above the line. Currently no test
  869. // file available
  870. // const lowerSkyLine: number = Math.min(...musicSystem.StaffLines[i + 1].SkyLine);
  871. if (Math.abs(upperBottomLine) > this.rules.MinimumStaffLineDistance) {
  872. // Remove staffheight from offset. As it results in huge distances
  873. const offset: number = Math.abs(upperBottomLine) + this.rules.MinimumStaffLineDistance - this.rules.StaffHeight;
  874. this.updateStaffLinesRelativePosition(musicSystem, i + 1, offset);
  875. }
  876. }
  877. }
  878. const firstStaffLine: StaffLine = musicSystem.StaffLines[0];
  879. musicSystem.PositionAndShape.BorderTop = firstStaffLine.PositionAndShape.RelativePosition.y + firstStaffLine.PositionAndShape.BorderTop;
  880. const lastStaffLine: StaffLine = musicSystem.StaffLines[musicSystem.StaffLines.length - 1];
  881. musicSystem.PositionAndShape.BorderBottom = lastStaffLine.PositionAndShape.RelativePosition.y + lastStaffLine.PositionAndShape.BorderBottom;
  882. }
  883. /**
  884. * This method updates the System's StaffLine's RelativePosition (starting from the given index).
  885. * @param musicSystem
  886. * @param index
  887. * @param value
  888. */
  889. private updateStaffLinesRelativePosition(musicSystem: MusicSystem, index: number, value: number): void {
  890. for (let i: number = index; i < musicSystem.StaffLines.length; i++) {
  891. musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y += value;
  892. }
  893. musicSystem.PositionAndShape.BorderBottom += value;
  894. }
  895. /**
  896. * Create a new [[GraphicalMusicPage]]
  897. * (for now only one long page is used per music sheet, as we scroll down and have no page flips)
  898. * @returns {GraphicalMusicPage}
  899. */
  900. private createMusicPage(): GraphicalMusicPage {
  901. const page: GraphicalMusicPage = new GraphicalMusicPage(this.graphicalMusicSheet);
  902. this.graphicalMusicSheet.MusicPages.push(page);
  903. page.PositionAndShape.BorderLeft = 0.0;
  904. page.PositionAndShape.BorderRight = this.graphicalMusicSheet.ParentMusicSheet.pageWidth;
  905. page.PositionAndShape.BorderTop = 0.0;
  906. page.PositionAndShape.BorderBottom = this.rules.PageHeight;
  907. page.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  908. return page;
  909. }
  910. private addSystemToPage(page: GraphicalMusicPage, system: MusicSystem): void {
  911. page.MusicSystems.push(system);
  912. system.Parent = page;
  913. }
  914. /** Calculates the relative Positions of all MusicSystems.
  915. *
  916. */
  917. private calculateMusicSystemsRelativePositions(): void {
  918. let currentPage: GraphicalMusicPage = this.createMusicPage();
  919. let currentYPosition: number = 0;
  920. // xPosition is always fixed
  921. let currentSystem: MusicSystem = this.musicSystems[0];
  922. for (let i: number = 0; i < this.musicSystems.length; i++) {
  923. currentSystem = this.musicSystems[i];
  924. if (currentPage.MusicSystems.length === 0) {
  925. // first system on the page:
  926. this.addSystemToPage(currentPage, currentSystem);
  927. if (EngravingRules.Rules.CompactMode) {
  928. currentYPosition = EngravingRules.Rules.PageTopMarginNarrow;
  929. } else {
  930. currentYPosition = EngravingRules.Rules.PageTopMargin;
  931. }
  932. // Handle Title for first System on the first page
  933. if (this.graphicalMusicSheet.MusicPages.length === 1 &&
  934. EngravingRules.Rules.RenderTitle) {
  935. currentYPosition += this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
  936. this.rules.TitleBottomDistance;
  937. }
  938. currentYPosition += -currentSystem.PositionAndShape.BorderTop;
  939. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  940. currentYPosition);
  941. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  942. currentYPosition += currentSystem.PositionAndShape.BorderBottom;
  943. } else {
  944. // if this is not the first system on the page:
  945. // find optimum distance between Systems
  946. const previousSystem: MusicSystem = this.musicSystems[i - 1];
  947. const previousStaffLineBB: BoundingBox = previousSystem.StaffLines[previousSystem.StaffLines.length - 1].PositionAndShape;
  948. const currentStaffLineBB: BoundingBox = currentSystem.StaffLines[0].PositionAndShape;
  949. let distance: number = currentStaffLineBB.RelativePosition.y + previousStaffLineBB.BorderTop -
  950. (previousStaffLineBB.RelativePosition.y + previousStaffLineBB.BorderBottom);
  951. distance = Math.max(this.rules.MinimumDistanceBetweenSystems, distance);
  952. const neededHeight: number = distance - currentSystem.PositionAndShape.BorderTop + currentSystem.PositionAndShape.BorderBottom;
  953. if (currentYPosition + neededHeight <
  954. this.rules.PageHeight - this.rules.PageBottomMargin) {
  955. // enough space on this page:
  956. this.addSystemToPage(currentPage, currentSystem);
  957. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  958. currentYPosition + distance - currentSystem.PositionAndShape.BorderTop);
  959. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  960. currentYPosition += neededHeight;
  961. } else {
  962. // new page needed:
  963. currentPage = this.createMusicPage();
  964. // re-check this system again:
  965. i -= 1;
  966. continue;
  967. }
  968. }
  969. }
  970. }
  971. }
  972. export class SystemBuildParameters {
  973. public currentSystem: MusicSystem;
  974. public systemMeasures: MeasureBuildParameters[] = [];
  975. public systemMeasureIndex: number = 0;
  976. public currentWidth: number = 0;
  977. public currentSystemFixWidth: number = 0;
  978. public currentSystemVarWidth: number = 0;
  979. public maxLabelLength: number = 0;
  980. public IsSystemStartMeasure(): boolean {
  981. return this.systemMeasureIndex === 0;
  982. }
  983. }
  984. export class MeasureBuildParameters {
  985. public beginLine: SystemLinesEnum;
  986. public endLine: SystemLinesEnum;
  987. }