MusicSystemBuilder.ts 54 KB

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