MusicSystem.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import {StaffLine} from "./StaffLine";
  2. import {Instrument} from "../Instrument";
  3. import {BoundingBox} from "./BoundingBox";
  4. import {Fraction} from "../../Common/DataObjects/Fraction";
  5. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  6. import {InstrumentalGroup} from "../InstrumentalGroup";
  7. import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
  8. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  9. import {GraphicalLabel} from "./GraphicalLabel";
  10. import {GraphicalMeasure} from "./GraphicalMeasure";
  11. import {GraphicalObject} from "./GraphicalObject";
  12. import {EngravingRules} from "./EngravingRules";
  13. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  14. import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
  15. import {SystemLinesEnum} from "./SystemLinesEnum";
  16. import Dictionary from "typescript-collections/dist/lib/Dictionary";
  17. import {GraphicalComment} from "./GraphicalComment";
  18. import {GraphicalMarkedArea} from "./GraphicalMarkedArea";
  19. import {SystemLine} from "./SystemLine";
  20. import {SystemLinePosition} from "./SystemLinePosition";
  21. import {Staff} from "../VoiceData/Staff";
  22. import { Label } from "../Label";
  23. /**
  24. * A MusicSystem contains the [[StaffLine]]s for all instruments, until a line break
  25. */
  26. export abstract class MusicSystem extends GraphicalObject {
  27. public needsToBeRedrawn: boolean = true;
  28. public rules: EngravingRules;
  29. protected parent: GraphicalMusicPage;
  30. protected id: number;
  31. protected staffLines: StaffLine[] = [];
  32. protected graphicalMeasures: GraphicalMeasure[][] = [];
  33. /** Dictionary of (Instruments and) labels.
  34. * note that the key needs to be unique, GraphicalLabel is not unique yet.
  35. * That is why the labels are labels.values() and not labels.keys().
  36. */
  37. protected labels: Dictionary<Instrument, GraphicalLabel> = new Dictionary<Instrument, GraphicalLabel>();
  38. protected measureNumberLabels: GraphicalLabel[] = [];
  39. protected maxLabelLength: number;
  40. protected objectsToRedraw: [Object[], Object][] = [];
  41. protected instrumentBrackets: GraphicalObject[] = [];
  42. protected groupBrackets: GraphicalObject[] = [];
  43. protected graphicalMarkedAreas: GraphicalMarkedArea[] = [];
  44. protected graphicalComments: GraphicalComment[] = [];
  45. protected systemLines: SystemLine[] = [];
  46. public breaksPage: boolean = false;
  47. constructor(id: number) {
  48. super();
  49. this.id = id;
  50. this.boundingBox = new BoundingBox(this);
  51. this.maxLabelLength = 0.0;
  52. }
  53. public get Parent(): GraphicalMusicPage {
  54. return this.parent;
  55. }
  56. public set Parent(value: GraphicalMusicPage) {
  57. // remove from old page
  58. if (this.parent) {
  59. const index: number = this.parent.MusicSystems.indexOf(this, 0);
  60. if (index > -1) {
  61. this.parent.MusicSystems.splice(index, 1);
  62. }
  63. }
  64. this.parent = value;
  65. this.boundingBox.Parent = value.PositionAndShape;
  66. }
  67. public get NextSystem(): MusicSystem {
  68. const idxInParent: number = this.Parent.MusicSystems.indexOf(this);
  69. return idxInParent !== this.Parent.MusicSystems.length ? this.Parent.MusicSystems[idxInParent + 1] : undefined;
  70. }
  71. public get StaffLines(): StaffLine[] {
  72. return this.staffLines;
  73. }
  74. public get GraphicalMeasures(): GraphicalMeasure[][] {
  75. return this.graphicalMeasures;
  76. }
  77. public get MeasureNumberLabels(): GraphicalLabel[] {
  78. return this.measureNumberLabels;
  79. }
  80. public get Labels(): GraphicalLabel[] {
  81. return this.labels.values();
  82. }
  83. public get ObjectsToRedraw(): [Object[], Object][] {
  84. return this.objectsToRedraw;
  85. }
  86. public get InstrumentBrackets(): GraphicalObject[] {
  87. return this.instrumentBrackets;
  88. }
  89. public get GroupBrackets(): GraphicalObject[] {
  90. return this.groupBrackets;
  91. }
  92. public get GraphicalMarkedAreas(): GraphicalMarkedArea[] {
  93. return this.graphicalMarkedAreas;
  94. }
  95. public get GraphicalComments(): GraphicalComment[] {
  96. return this.graphicalComments;
  97. }
  98. public get SystemLines(): SystemLine[] {
  99. return this.systemLines;
  100. }
  101. public get Id(): number {
  102. return this.id;
  103. }
  104. /**
  105. * Create the left vertical Line connecting all staves of the [[MusicSystem]].
  106. * @param lineWidth
  107. * @param systemLabelsRightMargin
  108. */
  109. public createSystemLeftLine(lineWidth: number, systemLabelsRightMargin: number, isFirstSystem: boolean): void {
  110. let xPosition: number = -lineWidth / 2;
  111. if (isFirstSystem) {
  112. xPosition = this.maxLabelLength + systemLabelsRightMargin - lineWidth / 2;
  113. }
  114. const top: GraphicalMeasure = this.staffLines[0].Measures[0];
  115. let bottom: GraphicalMeasure = undefined;
  116. if (this.staffLines.length > 1) {
  117. bottom = this.staffLines[this.staffLines.length - 1].Measures[0];
  118. }
  119. const leftSystemLine: SystemLine = this.createSystemLine(xPosition, lineWidth, SystemLinesEnum.SingleThin,
  120. SystemLinePosition.MeasureBegin, this, top, bottom);
  121. this.SystemLines.push(leftSystemLine);
  122. leftSystemLine.PositionAndShape.RelativePosition = new PointF2D(xPosition, 0);
  123. leftSystemLine.PositionAndShape.BorderLeft = 0;
  124. leftSystemLine.PositionAndShape.BorderRight = lineWidth;
  125. leftSystemLine.PositionAndShape.BorderTop = 0;
  126. leftSystemLine.PositionAndShape.BorderBottom = this.boundingBox.Size.height;
  127. this.createLinesForSystemLine(leftSystemLine);
  128. }
  129. /**
  130. * Create the vertical Lines after the End of all [[StaffLine]]'s Measures
  131. * @param xPosition
  132. * @param lineWidth
  133. * @param lineType
  134. * @param linePosition indicates if the line belongs to start or end of measure
  135. * @param measureIndex the measure index within the staffline
  136. * @param measure
  137. */
  138. public createVerticalLineForMeasure(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
  139. measureIndex: number, measure: GraphicalMeasure): void {
  140. //return; // TODO check why there's a bold line here for the double barline sample
  141. const staffLine: StaffLine = measure.ParentStaffLine;
  142. const staffLineRelative: PointF2D = new PointF2D(staffLine.PositionAndShape.RelativePosition.x,
  143. staffLine.PositionAndShape.RelativePosition.y);
  144. const staves: Staff[] = staffLine.ParentStaff.ParentInstrument.Staves;
  145. if (staffLine.ParentStaff === staves[0]) {
  146. let bottomMeasure: GraphicalMeasure = undefined;
  147. if (staves.length > 1) {
  148. bottomMeasure = this.getBottomStaffLine(staffLine).Measures[measureIndex];
  149. }
  150. const singleVerticalLineAfterMeasure: SystemLine = this.createSystemLine(xPosition, lineWidth, lineType,
  151. linePosition, this, measure, bottomMeasure);
  152. const systemXPosition: number = staffLineRelative.x + xPosition;
  153. singleVerticalLineAfterMeasure.PositionAndShape.RelativePosition = new PointF2D(systemXPosition, 0);
  154. singleVerticalLineAfterMeasure.PositionAndShape.BorderLeft = 0;
  155. singleVerticalLineAfterMeasure.PositionAndShape.BorderRight = lineWidth;
  156. this.SystemLines.push(singleVerticalLineAfterMeasure);
  157. }
  158. }
  159. /**
  160. * Set the y-Positions of all the system lines in the system and creates the graphical Lines and dots within.
  161. * @param rules
  162. */
  163. public setYPositionsToVerticalLineObjectsAndCreateLines(rules: EngravingRules): void {
  164. // empty
  165. }
  166. public calculateBorders(rules: EngravingRules): void {
  167. // empty
  168. }
  169. public alignBeginInstructions(): void {
  170. // empty
  171. }
  172. public GetLeftBorderAbsoluteXPosition(): number {
  173. return this.StaffLines[0].PositionAndShape.AbsolutePosition.x + this.StaffLines[0].Measures[0].beginInstructionsWidth;
  174. }
  175. public GetRightBorderAbsoluteXPosition(): number {
  176. return this.StaffLines[0].PositionAndShape.AbsolutePosition.x + this.StaffLines[0].StaffLines[0].End.x;
  177. }
  178. public AddGraphicalMeasures(graphicalMeasures: GraphicalMeasure[]): void {
  179. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  180. const graphicalMeasure: GraphicalMeasure = graphicalMeasures[idx];
  181. graphicalMeasure.ParentMusicSystem = this;
  182. }
  183. this.graphicalMeasures.push(graphicalMeasures);
  184. }
  185. public GetSystemsFirstTimeStamp(): Fraction {
  186. return this.graphicalMeasures[0][0].parentSourceMeasure.AbsoluteTimestamp;
  187. }
  188. public GetSystemsLastTimeStamp(): Fraction {
  189. const m: SourceMeasure = this.graphicalMeasures[this.graphicalMeasures.length - 1][0].parentSourceMeasure;
  190. return Fraction.plus(m.AbsoluteTimestamp, m.Duration);
  191. }
  192. /**
  193. * Create an InstrumentBracket for each multiStave Instrument.
  194. * @param instruments
  195. * @param staffHeight
  196. */
  197. public createInstrumentBrackets(instruments: Instrument[], staffHeight: number): void {
  198. for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
  199. const instrument: Instrument = instruments[idx];
  200. if (instrument.Staves.length > 1) {
  201. let firstStaffLine: StaffLine = undefined, lastStaffLine: StaffLine = undefined;
  202. for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
  203. const staffLine: StaffLine = this.staffLines[idx2];
  204. if (staffLine.ParentStaff === instrument.Staves[0]) {
  205. firstStaffLine = staffLine;
  206. }
  207. if (staffLine.ParentStaff === instrument.Staves[instrument.Staves.length - 1]) {
  208. lastStaffLine = staffLine;
  209. }
  210. }
  211. if (firstStaffLine && lastStaffLine) {
  212. this.createInstrumentBracket(firstStaffLine, lastStaffLine);
  213. }
  214. }
  215. }
  216. }
  217. /**
  218. * Create a GroupBracket for an [[InstrumentalGroup]].
  219. * @param instrumentGroups
  220. * @param staffHeight
  221. * @param recursionDepth
  222. */
  223. public createGroupBrackets(instrumentGroups: InstrumentalGroup[], staffHeight: number, recursionDepth: number): void {
  224. for (let idx: number = 0, len: number = instrumentGroups.length; idx < len; ++idx) {
  225. const instrumentGroup: InstrumentalGroup = instrumentGroups[idx];
  226. if (instrumentGroup.InstrumentalGroups.length < 1) {
  227. continue;
  228. }
  229. const instrument1: Instrument = this.findFirstVisibleInstrumentInInstrumentalGroup(instrumentGroup);
  230. const instrument2: Instrument = this.findLastVisibleInstrumentInInstrumentalGroup(instrumentGroup);
  231. if (instrument1 === undefined || instrument2 === undefined) {
  232. continue;
  233. }
  234. let firstStaffLine: StaffLine = undefined;
  235. let lastStaffLine: StaffLine = undefined;
  236. for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
  237. const staffLine: StaffLine = this.staffLines[idx2];
  238. if (staffLine.ParentStaff === instrument1.Staves[0]) {
  239. firstStaffLine = staffLine;
  240. }
  241. if (staffLine.ParentStaff === instrument2.Staves[0]) {
  242. lastStaffLine = staffLine;
  243. }
  244. }
  245. if (firstStaffLine && lastStaffLine) {
  246. this.createGroupBracket(firstStaffLine, lastStaffLine, recursionDepth);
  247. }
  248. if (instrumentGroup.InstrumentalGroups.length < 1) {
  249. continue;
  250. }
  251. this.createGroupBrackets(instrumentGroup.InstrumentalGroups, staffHeight, recursionDepth + 1);
  252. }
  253. }
  254. /**
  255. * Create the Instrument's Labels (only for the first [[MusicSystem]] of the first MusicPage).
  256. * @param instrumentLabelTextHeight
  257. * @param systemLabelsRightMargin
  258. * @param labelMarginBorderFactor
  259. */
  260. public createMusicSystemLabel( instrumentLabelTextHeight: number, systemLabelsRightMargin: number,
  261. labelMarginBorderFactor: number, isFirstSystem: boolean = false): void {
  262. for (let idx: number = 0, len: number = this.staffLines.length; idx < len; ++idx) {
  263. const instrument: Instrument = this.staffLines[idx].ParentStaff.ParentInstrument;
  264. let instrNameLabel: Label;
  265. if (isFirstSystem) {
  266. instrNameLabel = instrument.NameLabel;
  267. if (!this.rules.RenderPartNames) {
  268. instrNameLabel = new Label("", instrument.NameLabel.textAlignment, instrument.NameLabel.font);
  269. systemLabelsRightMargin = 0; // might affect lyricist/tempo placement. but without this there's still some extra x-spacing.
  270. }
  271. } else {
  272. if (!this.rules.RenderPartAbbreviations
  273. // don't render part abbreviations if there's only one instrument/part (could be an option in the future)
  274. || this.staffLines.length === 1
  275. || !instrument.PartAbbreviation
  276. || instrument.PartAbbreviation === "") {
  277. return;
  278. }
  279. const labelText: string = instrument.PartAbbreviation;
  280. // const labelText: string = instrument.NameLabel.text[0] + ".";
  281. instrNameLabel = new Label(labelText, instrument.NameLabel.textAlignment, instrument.NameLabel.font);
  282. }
  283. const graphicalLabel: GraphicalLabel = new GraphicalLabel(
  284. instrNameLabel, instrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.rules, this.boundingBox
  285. );
  286. graphicalLabel.setLabelPositionAndShapeBorders();
  287. this.labels.setValue(instrument, graphicalLabel);
  288. // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
  289. // Y-Position will be calculated after the y-Spacing
  290. // graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  291. }
  292. // calculate maxLabelLength (needed for X-Spacing)
  293. this.maxLabelLength = 0.0;
  294. const labels: GraphicalLabel[] = this.labels.values();
  295. for (let idx: number = 0, len: number = labels.length; idx < len; ++idx) {
  296. const label: GraphicalLabel = labels[idx];
  297. if (label.PositionAndShape.Size.width > this.maxLabelLength) {
  298. this.maxLabelLength = label.PositionAndShape.Size.width;
  299. }
  300. }
  301. this.updateMusicSystemStaffLineXPosition(systemLabelsRightMargin);
  302. }
  303. /**
  304. * Set the Y-Positions for the MusicSystem's Labels.
  305. */
  306. public setMusicSystemLabelsYPosition(): void {
  307. this.labels.forEach((key: Instrument, value: GraphicalLabel): void => {
  308. let ypositionSum: number = 0;
  309. let staffCounter: number = 0;
  310. for (let i: number = 0; i < this.staffLines.length; i++) {
  311. if (this.staffLines[i].ParentStaff.ParentInstrument === key) {
  312. for (let j: number = i; j < this.staffLines.length; j++) {
  313. const staffLine: StaffLine = this.staffLines[j];
  314. if (staffLine.ParentStaff.ParentInstrument !== key) {
  315. break;
  316. }
  317. ypositionSum += staffLine.PositionAndShape.RelativePosition.y;
  318. staffCounter++;
  319. }
  320. break;
  321. }
  322. }
  323. if (staffCounter > 0) {
  324. value.PositionAndShape.RelativePosition = new PointF2D(0.0, ypositionSum / staffCounter + 2.0);
  325. }
  326. });
  327. }
  328. /**
  329. * Check if two "adjacent" StaffLines have BOTH a StaffEntry with a StaffEntryLink.
  330. * This is needed for the y-spacing algorithm.
  331. * @returns {boolean}
  332. */
  333. public checkStaffEntriesForStaffEntryLink(): boolean {
  334. let first: boolean = false;
  335. let second: boolean = false;
  336. for (let i: number = 0; i < this.staffLines.length - 1; i++) {
  337. for (let idx: number = 0, len: number = this.staffLines[i].Measures.length; idx < len; ++idx) {
  338. const measure: GraphicalMeasure = this.staffLines[i].Measures[idx];
  339. for (let idx2: number = 0, len2: number = measure.staffEntries.length; idx2 < len2; ++idx2) {
  340. const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
  341. if (staffEntry.sourceStaffEntry.Link) {
  342. first = true;
  343. }
  344. }
  345. }
  346. for (let idx: number = 0, len: number = this.staffLines[i + 1].Measures.length; idx < len; ++idx) {
  347. const measure: GraphicalMeasure = this.staffLines[i + 1].Measures[idx];
  348. for (let idx2: number = 0, len2: number = measure.staffEntries.length; idx2 < len2; ++idx2) {
  349. const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
  350. if (staffEntry.sourceStaffEntry.Link) {
  351. second = true;
  352. }
  353. }
  354. }
  355. }
  356. if (first && second) {
  357. return true;
  358. }
  359. return false;
  360. }
  361. public getBottomStaffLine(topStaffLine: StaffLine): StaffLine {
  362. const staves: Staff[] = topStaffLine.ParentStaff.ParentInstrument.Staves;
  363. const last: Staff = staves[staves.length - 1];
  364. for (const line of topStaffLine.ParentMusicSystem.staffLines) {
  365. if (line.ParentStaff === last) {
  366. return line;
  367. }
  368. }
  369. return undefined;
  370. }
  371. /**
  372. * Here the system line is generated, which acts as the container of graphical lines and dots that will be finally rendered.
  373. * It holds al the logical parameters of the system line.
  374. * @param xPosition The x position within the system
  375. * @param lineWidth The total x width
  376. * @param lineType The line type enum
  377. * @param linePosition indicates if the line belongs to start or end of measure
  378. * @param musicSystem
  379. * @param topMeasure
  380. * @param bottomMeasure
  381. */
  382. protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
  383. musicSystem: MusicSystem, topMeasure: GraphicalMeasure, bottomMeasure: GraphicalMeasure = undefined): SystemLine {
  384. throw new Error("not implemented");
  385. }
  386. /**
  387. * Create all the graphical lines and dots needed to render a system line (e.g. bold-thin-dots..).
  388. * @param systemLine
  389. */
  390. protected createLinesForSystemLine(systemLine: SystemLine): void {
  391. //Empty
  392. }
  393. /**
  394. * Calculates the summed x-width of a possibly given Instrument Brace and/or Group Bracket(s).
  395. * @returns {number} the x-width
  396. */
  397. protected calcBracketsWidth(): number {
  398. let width: number = 0;
  399. for (let idx: number = 0, len: number = this.GroupBrackets.length; idx < len; ++idx) {
  400. const groupBracket: GraphicalObject = this.GroupBrackets[idx];
  401. width = Math.max(width, groupBracket.PositionAndShape.Size.width);
  402. }
  403. for (let idx2: number = 0, len2: number = this.InstrumentBrackets.length; idx2 < len2; ++idx2) {
  404. const instrumentBracket: GraphicalObject = this.InstrumentBrackets[idx2];
  405. width = Math.max(width, instrumentBracket.PositionAndShape.Size.width);
  406. }
  407. return width;
  408. }
  409. protected createInstrumentBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine): void {
  410. // no impl here
  411. }
  412. protected createGroupBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine, recursionDepth: number): void {
  413. // no impl here
  414. }
  415. private findFirstVisibleInstrumentInInstrumentalGroup(instrumentalGroup: InstrumentalGroup): Instrument {
  416. for (let idx: number = 0, len: number = instrumentalGroup.InstrumentalGroups.length; idx < len; ++idx) {
  417. const groupOrInstrument: InstrumentalGroup = instrumentalGroup.InstrumentalGroups[idx];
  418. if (groupOrInstrument instanceof Instrument) {
  419. if ((<Instrument>groupOrInstrument).Visible === true) {
  420. return <Instrument>groupOrInstrument;
  421. }
  422. continue;
  423. }
  424. return this.findFirstVisibleInstrumentInInstrumentalGroup(groupOrInstrument);
  425. }
  426. return undefined;
  427. }
  428. private findLastVisibleInstrumentInInstrumentalGroup(instrumentalGroup: InstrumentalGroup): Instrument {
  429. let groupOrInstrument: InstrumentalGroup;
  430. for (let i: number = instrumentalGroup.InstrumentalGroups.length - 1; i >= 0; i--) {
  431. groupOrInstrument = instrumentalGroup.InstrumentalGroups[i];
  432. if (groupOrInstrument instanceof Instrument) {
  433. if ((<Instrument>groupOrInstrument).Visible === true) {
  434. return <Instrument>groupOrInstrument;
  435. }
  436. continue;
  437. }
  438. return this.findLastVisibleInstrumentInInstrumentalGroup(groupOrInstrument);
  439. }
  440. return undefined;
  441. }
  442. /**
  443. * Update the xPosition of the [[MusicSystem]]'s [[StaffLine]]'s due to [[Label]] positioning.
  444. * @param systemLabelsRightMargin
  445. */
  446. private updateMusicSystemStaffLineXPosition(systemLabelsRightMargin: number): void {
  447. for (let idx: number = 0, len: number = this.StaffLines.length; idx < len; ++idx) {
  448. const staffLine: StaffLine = this.StaffLines[idx];
  449. const relative: PointF2D = staffLine.PositionAndShape.RelativePosition;
  450. relative.x = this.maxLabelLength + systemLabelsRightMargin;
  451. staffLine.PositionAndShape.RelativePosition = relative;
  452. staffLine.PositionAndShape.BorderRight = this.boundingBox.Size.width - this.maxLabelLength - systemLabelsRightMargin;
  453. for (let i: number = 0; i < staffLine.StaffLines.length; i++) {
  454. const lineEnd: PointF2D = new PointF2D(staffLine.PositionAndShape.Size.width, staffLine.StaffLines[i].End.y);
  455. staffLine.StaffLines[i].End = lineEnd;
  456. }
  457. }
  458. }
  459. }