private void MoveStemTips() { float staffOriginY = Chords[0].Voice.Staff.Metrics.OriginY; QuaverBeam quaverBeam = null; foreach (Beam beam in Beams) { quaverBeam = beam as QuaverBeam; if (quaverBeam != null) { break; } } Debug.Assert(quaverBeam != null); float tanAlpha = (quaverBeam.RightTopY - quaverBeam.LeftTopY) / (quaverBeam.RightX - quaverBeam.LeftX); foreach (ChordSymbol chord in Chords) { ChordMetrics chordMetrics = ((ChordMetrics)chord.Metrics); StemMetrics stemMetrics = chordMetrics.StemMetrics; // a clone Debug.Assert(chord.Stem.Direction == _stemDirection); // just to be sure. float stemTipDeltaY = ((stemMetrics.OriginX - this._left) * tanAlpha); float stemTipY = quaverBeam.LeftTopY + stemTipDeltaY; chordMetrics.MoveOuterStemTip(stemTipY, _stemDirection); // dont just move the clone! Moves the auxilliaries too. } }
/// <summary> /// This function moves the lowerChord to the left or right in order to avoid collisions with /// the noteheads of the upper chord. /// The positions of noteheads and ledgerlines relative to their own stem is never changed. /// The positions of accidentals are adjusted in both chords after the lower chord has moved. /// Stem lengths, and the positions of flags and beamBlocks, are adjusted later in FinalizeBeamBlocks(). /// /// The stems are currently at the standard x-positions for stems up and down at the same MsPosition. /// Their lengths have been changed if necessary, for crossing parts. So collisions can be checked /// reliably in this function. /// There are 7 possible horizontal positions for the stem of the lower chord: /// 1. the standard position (aligned as when the lower chord is well below the upper chord) /// 2. hairline left of upper noteheads /// 3. aligned with upper stem /// (top notehead of the bottom chord is half a space below the bottom notehead of the upper chord) /// 4. thin hairline right of upper stem /// (top notehead of the bottom chord is at the same height as the bottom notehead of the upper chord) /// 5. thick hairline right of upper stem /// (top notehead of the bottom chord is above the bottom notehead of the upper chord) /// If both upper and lower chords have sideways shifted noteheads, and there are no notehead collisions: /// 6. thick hairline right of right-side note head on upper stem /// else /// 7. a head width left of its original position /// The position selected, is the first of these which can be applied without causing any collisions. /// The principle is that the total width of all the noteheads should be minimized. /// Accidentals are rearranged (top to bottom, to the left of the combined chord) once the /// noteheads and ledgerlines have been given their final positions. /// </summary> private void AdjustLowerChordXPosition(ChordSymbol upperChord, ChordSymbol lowerChord) { Debug.Assert(upperChord.MsPosition == lowerChord.MsPosition); if (!(upperChord is CautionaryChordSymbol)) { Debug.Assert(upperChord.Stem.Direction == VerticalDir.up); } if (!(lowerChord is CautionaryChordSymbol)) { Debug.Assert(lowerChord.Stem.Direction == VerticalDir.down); } List <HeadMetrics> upperChordHeadMetrics = upperChord.ChordMetrics.HeadsMetrics; // a clone List <HeadMetrics> lowerChordHeadMetrics = lowerChord.ChordMetrics.HeadsMetrics; // a clone StemMetrics lowerChordStemMetrics = lowerChord.ChordMetrics.StemMetrics; // a clone float deltaX = LowerChordDeltaX(upperChordHeadMetrics, lowerChordHeadMetrics, lowerChordStemMetrics); if (deltaX != 0) { lowerChord.ChordMetrics.Move(deltaX, 0F); // move the whole chord, including accidentals } // adjust the positions of accidentals in both chords lowerChord.AdjustAccidentalsX(upperChord); }
/// <summary> /// Notehead metrics.Left and metrics.Right include horizontal padding, /// so head overlaps cannot be checked using the standard Metrics.Overlaps function. /// </summary> public bool OverlapsStem(StemMetrics stemMetrics) { // See the above constructor. Sorry, I didnt want to save the value in every Head! float thisHorizontalPadding = this._fontHeight * 0.04F; float thisRealLeft = _left + thisHorizontalPadding; float thisRealRight = _right - thisHorizontalPadding; bool verticalOverlap = this.Bottom >= stemMetrics.Top && this.Top <= stemMetrics.Bottom; bool horizontalOverlap = thisRealRight >= stemMetrics.Left && thisRealLeft <= stemMetrics.Right; return(verticalOverlap && horizontalOverlap); }
private bool NoHeadStemCollisions(float deltaX, List <HeadMetrics> upperHMList, StemMetrics lowerStemMetrics) { // N.B. Notehead metrics.Left and metrics.Right include horizontal padding, // so this function does not use the standard Metrics.Overlaps functions. bool noHeadStemCollisions = true; if (lowerStemMetrics != null) { lowerStemMetrics.Move(deltaX, 0F); foreach (HeadMetrics upperHM in upperHMList) { if (upperHM.OverlapsStem(lowerStemMetrics)) { noHeadStemCollisions = false; break; } } lowerStemMetrics.Move(-deltaX, 0F); // its a clone of the real stemMetrics, but move it back anyway. } return(noHeadStemCollisions); }
/// <summary> /// Returns the amount by which to move the lower of two synchronous /// chords to avoid notehead and stem collisions between them. /// </summary> private float LowerChordDeltaX(List <HeadMetrics> upperHM, List <HeadMetrics> lowerHM, StemMetrics lowerChordStemMetrics) { float deltaX = 0F; float iota = 0.001F; // tolerance for float comparisons float stemThickness = SVGSystem.Score.PageFormat.StafflineStemStrokeWidth; float upperHeadRightStemX = upperHM[upperHM.Count - 1].RightStemX; float lowerHeadLeftStemX = lowerHM[0].LeftStemX; float verticalChordOverlap = upperHM[upperHM.Count - 1].Top - lowerHM[0].Top + Gap; // position 1: if there is no vertical overlap between the chords, deltaX is 0 if (verticalChordOverlap > 0F) { // position 2: hairline left of upper noteheads float testDeltaX = -(stemThickness * 2F); if (NoNoteheadCollisions(testDeltaX + iota, upperHM, lowerHM)) { deltaX = testDeltaX; } if (deltaX == 0F) { // (nearly) aligned with upper stem testDeltaX = upperHeadRightStemX - lowerHeadLeftStemX; if ((upperHM[upperHM.Count - 1].Bottom > lowerHM[0].Top && upperHM[upperHM.Count - 1].Top < lowerHM[0].Top) && NoNoteheadCollisions(testDeltaX + iota, upperHM, lowerHM)) { // position 3: The lowest note of the upper chord is a half space above the // highest notehead in the lower chord, so the stems should align exactly. deltaX = testDeltaX - stemThickness; // Strange, but effective! } if (deltaX == 0) { if (lowerHM[0].OriginY > ((upperHM[upperHM.Count - 1].Top + upperHM[upperHM.Count - 1].OriginY) / 2) && NoNoteheadCollisions(testDeltaX + iota, upperHM, lowerHM)) { // position 4: The lowest note of the upper chord is at the same height as // highest notehead in the lower chord, so the stems should be slightly separated. deltaX = testDeltaX + (stemThickness * 0.6F); // Strange, but effective! } } if (deltaX == 0F) { testDeltaX += (stemThickness * 1.8F); if (NoNoteheadCollisions(testDeltaX + iota, upperHM, lowerHM)) { // position 5: The lowest note of the upper chord is below the highest notehead // in the lower chord, so the stems should be separated slightly more. if (NoHeadStemCollisions(testDeltaX + iota, upperHM, lowerChordStemMetrics)) // stem metrics can be null... { deltaX = testDeltaX; } } } } if (deltaX == 0F && HasSidewaysShiftedNotehead(upperHM) && HasSidewaysShiftedNotehead(lowerHM)) { // position 6: align the lower stem a hairline right of a right-shifted notehead on the upper chord testDeltaX = ((upperHeadRightStemX - lowerHeadLeftStemX) * 2) + (stemThickness * 1.8F); if (NoNoteheadCollisions(testDeltaX + iota, upperHM, lowerHM)) { deltaX = testDeltaX; } } if (deltaX == 0F) { // position 7: align lower stem a head width left of its original position deltaX = lowerHeadLeftStemX - lowerHM[0].RightStemX; } } return(deltaX); }
private void SetStemAndFlags(ChordSymbol chord, List<HeadMetrics> topDownHeadsMetrics, float stemThickness) { DurationClass durationClass = chord.DurationClass; _flagsBlockMetrics = null; if(chord.BeamBlock == null && (durationClass == DurationClass.quaver || durationClass == DurationClass.semiquaver || durationClass == DurationClass.threeFlags || durationClass == DurationClass.fourFlags || durationClass == DurationClass.fiveFlags)) { _flagsBlockMetrics = GetFlagsBlockMetrics(topDownHeadsMetrics, durationClass, chord.FontHeight, chord.Stem.Direction, stemThickness); } if(durationClass == DurationClass.minim || durationClass == DurationClass.crotchet || durationClass == DurationClass.quaver || durationClass == DurationClass.semiquaver || durationClass == DurationClass.threeFlags || durationClass == DurationClass.fourFlags || durationClass == DurationClass.fiveFlags) { _stemMetrics = NewStemMetrics(topDownHeadsMetrics, chord, _flagsBlockMetrics, stemThickness); } }
private StemMetrics NewStemMetrics( List<HeadMetrics> topDownHeadsMetrics, VerticalDir stemDirection, float fontHeight, Metrics flagsBlockMetrics, BeamBlock beamBlock, float strokeWidth) { HeadMetrics outerNotehead = FindOuterNotehead(topDownHeadsMetrics, stemDirection); HeadMetrics innerNotehead = FindInnerNotehead(topDownHeadsMetrics, stemDirection); string noteheadID = outerNotehead.ID_Type; NoteheadStemPositions_px nspPX = CLichtFontMetrics.ClichtNoteheadStemPositionsDictPX[noteheadID]; float outerNoteheadAlignmentY = (outerNotehead.Bottom + outerNotehead.Top) / 2F; float innerNoteheadAlignmentY = (innerNotehead.Bottom + innerNotehead.Top) / 2F; float delta = _gap * 0.1F; float octave = (_gap * 3.5F) + delta; // a little more than 1 octave float sixth = (_gap * 2.5F) + delta; // a little more than 1 sixth float top = 0F; float bottom = 0F; float x = 0F; if(stemDirection == VerticalDir.up) { x = outerNotehead.RightStemX - (strokeWidth / 2); bottom = outerNoteheadAlignmentY + (nspPX.RightStemY_px * fontHeight); if(beamBlock != null) { top = beamBlock.DefaultStemTipY; } else { if(flagsBlockMetrics != null) { top = flagsBlockMetrics.Top; } else top = innerNoteheadAlignmentY - octave; if(top > (_gap * 2)) { top = (_gap * 2) - delta; } } } else // stem is down { x = outerNotehead.LeftStemX + (strokeWidth / 2); top = outerNoteheadAlignmentY + (nspPX.LeftStemY_px * fontHeight); if(beamBlock != null) { bottom = beamBlock.DefaultStemTipY; } else { if(flagsBlockMetrics != null) { bottom = flagsBlockMetrics.Bottom; } else bottom = innerNoteheadAlignmentY + octave; if(bottom < (_gap * 2)) { bottom = (_gap * 2) + delta; } } } StemMetrics stemMetrics = new StemMetrics(top, x, bottom, strokeWidth, stemDirection); return stemMetrics; }
/// <summary> /// The accidental is at the correct height (accidental.OriginY == head.OriginY), /// but it has not yet been added to this.MetricsList, or used to set this chord's Boundary. /// The accidental is now moved left, such that it does not overlap noteheads, stem, ledgerlines or accidentals. /// It is added to this.MetricsList after this function returns. /// </summary> private void MoveAccidentalLeft(AccidentalMetrics accidentalMetrics, List<HeadMetrics> topDownHeadsMetrics, StemMetrics stemMetrics, LedgerlineBlockMetrics upperLedgerlineBlockMetrics, LedgerlineBlockMetrics lowerLedgerlineBlockMetrics, List<AccidentalMetrics> existingAccidentalsMetrics) { #region move left of ledgerline block if(upperLedgerlineBlockMetrics != null) { MoveAccidentalLeftOfLedgerlineBlock(accidentalMetrics, upperLedgerlineBlockMetrics); } if(lowerLedgerlineBlockMetrics != null) { MoveAccidentalLeftOfLedgerlineBlock(accidentalMetrics, lowerLedgerlineBlockMetrics); } #endregion #region move left of noteheads float topRange = accidentalMetrics.OriginY - (_gap * 1.51F); float bottomRange = accidentalMetrics.OriginY + (_gap * 1.51F); foreach(HeadMetrics head in topDownHeadsMetrics) { if(head.OriginY > topRange && head.OriginY < bottomRange && head.Overlaps(accidentalMetrics)) { float extraHorizontalSpace = 0; if(accidentalMetrics.ID_Type == "b") extraHorizontalSpace = accidentalMetrics.FontHeight * -0.03F; accidentalMetrics.Move(head.Left - extraHorizontalSpace - accidentalMetrics.Right, 0); } } #endregion #region move left of stem (can be in another chord) if(stemMetrics != null) { // Note that the length of the stem is ignored here. float maxRight = stemMetrics.Left - stemMetrics.StrokeWidth; if(maxRight < accidentalMetrics.Right) accidentalMetrics.Move(maxRight - accidentalMetrics.Right, 0F); } #endregion #region move accidental left of existing accidentals MoveLeftOfExistingAccidentals(existingAccidentalsMetrics, 0, accidentalMetrics); #endregion }
/// <summary> /// Notehead metrics.Left and metrics.Right include horizontal padding, /// so head overlaps cannot be checked using the standard Metrics.Overlaps function. /// </summary> public bool OverlapsStem(StemMetrics stemMetrics) { // See the above constructor. Sorry, I didnt want to save the value in every Head! float thisHorizontalPadding = this._fontHeight * 0.04F; float thisRealLeft = _left + thisHorizontalPadding; float thisRealRight = _right - thisHorizontalPadding; bool verticalOverlap = this.Bottom >= stemMetrics.Top && this.Top <= stemMetrics.Bottom; bool horizontalOverlap = thisRealRight >= stemMetrics.Left && thisRealLeft <= stemMetrics.Right; return verticalOverlap && horizontalOverlap; }