MusicSystemBuilder.ts 55 KB

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