import { PointF2D } from "../../Common/DataObjects/PointF2D" ; import { GraphicalNote } from "./GraphicalNote" ; import { GraphicalCurve } from "./GraphicalCurve" ; import { Slur } from "../VoiceData/Expressions/ContinuousExpressions/Slur" ; import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression" ; import { EngravingRules } from "./EngravingRules" ; import { StaffLine } from "./StaffLine" ; import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator" ; import { Matrix2D } from "../../Common/DataObjects/Matrix2D" ; import { LinkedVoice } from "../VoiceData/LinkedVoice" ; import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry" ; import { GraphicalStaffEntry } from "./GraphicalStaffEntry" ; import { Fraction } from "../../Common/DataObjects/Fraction" ; import { StemDirectionType } from "../VoiceData/VoiceEntry" ; export class GraphicalSlur extends GraphicalCurve { constructor ( slur , rules ) { super ( ) ; this . slur = slur ; this . rules = rules ; } public slur : Slur ; public staffEntries : GraphicalStaffEntry [ ] = [ ] ; public placement : PlacementEnum ; public graceStart : boolean ; public graceEnd : boolean ; private rules : EngravingRules ; public SVGElement : Node ; param x param y public static Compare ( x , y ) { if ( x . staffEntries . length < 1 ) { return - 1 ; } else if ( y . staffEntries . length < 1 ) { return 1 ; } const xTimestampSpan = Fraction . minus ( x . staffEntries [ x . staffEntries . length - 1 ] . getAbsoluteTimestamp ( ) , x . staffEntries [ 0 ] . getAbsoluteTimestamp ( ) ) ; const yTimestampSpan = Fraction . minus ( y . staffEntries [ y . staffEntries . length - 1 ] . getAbsoluteTimestamp ( ) , y . staffEntries [ 0 ] . getAbsoluteTimestamp ( ) ) ; if ( xTimestampSpan . RealValue > yTimestampSpan . RealValue ) { return 1 ; } if ( yTimestampSpan . RealValue > xTimestampSpan . RealValue ) { return - 1 ; } return 0 ; } param rules public calculateCurve ( rules ) { const startStaffEntry = this . staffEntries [ 0 ] ; const endStaffEntry = this . staffEntries [ this . staffEntries . length - 1 ] ; let slurStartNote = startStaffEntry . findGraphicalNoteFromNote ( this . slur . StartNote ) ; if ( ! slurStartNote && this . graceStart ) { slurStartNote = startStaffEntry . findGraphicalNoteFromGraceNote ( this . slur . StartNote ) ; } if ( ! slurStartNote ) { slurStartNote = startStaffEntry . findEndTieGraphicalNoteFromNoteWithStartingSlur ( this . slur . StartNote , this . slur ) ; } let slurEndNote = endStaffEntry . findGraphicalNoteFromNote ( this . slur . EndNote ) ; if ( ! slurEndNote && this . graceEnd ) { slurEndNote = endStaffEntry . findGraphicalNoteFromGraceNote ( this . slur . EndNote ) ; } const staffLine = startStaffEntry . parentMeasure . ParentStaffLine ; const skyBottomLineCalculator = staffLine . SkyBottomLineCalculator ; this . calculatePlacement ( skyBottomLineCalculator , staffLine ) ; const startEndPoints = this . calculateStartAndEnd ( slurStartNote , slurEndNote , staffLine , rules , skyBottomLineCalculator ) ; const startX = startEndPoints . startX ; const endX = startEndPoints . endX ; let startY = startEndPoints . startY ; let endY = startEndPoints . endY ; const minAngle = rules . SlurTangentMinAngle ; const maxAngle = rules . SlurTangentMaxAngle ; let points ; if ( this . placement === PlacementEnum . Above ) { startY -= rules . SlurNoteHeadYOffset ; endY -= rules . SlurNoteHeadYOffset ; const startUpperRight = new PointF2D ( this . staffEntries [ 0 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ 0 ] . PositionAndShape . RelativePosition . x , startY ) ; if ( slurStartNote ) { startUpperRight . x += this . staffEntries [ 0 ] . PositionAndShape . BorderRight ; } else { startUpperRight . x = this . staffEntries [ 0 ] . parentMeasure . beginInstructionsWidth ; } if ( this . graceStart ) { startUpperRight . x += endStaffEntry . PositionAndShape . RelativePosition . x ; } const endUpperLeft = new PointF2D ( this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ this . staffEntries . length - 1 ] . PositionAndShape . RelativePosition . x , endY ) ; if ( slurEndNote ) { endUpperLeft . x += this . staffEntries [ this . staffEntries . length - 1 ] . PositionAndShape . BorderLeft ; } else { endUpperLeft . x = this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . Size . width ; } if ( this . graceEnd ) { endUpperLeft . x += endStaffEntry . staffEntryParent . PositionAndShape . RelativePosition . x ; } points = this . calculateTopPoints ( startUpperRight , endUpperLeft , staffLine , skyBottomLineCalculator ) ; if ( points . length === 0 ) { const pointF = new PointF2D ( ( endUpperLeft . x - startUpperRight . x ) / 2 + startUpperRight . x , ( endUpperLeft . y - startUpperRight . y ) / 2 + startUpperRight . y ) ; points . push ( pointF ) ; } const startEndLineAngleRadians = ( Math . atan ( ( endY - startY ) / ( endX - startX ) ) ) ; const start2 = new PointF2D ( 0 , 0 ) ; let end2 = new PointF2D ( endX - startX , - ( endY - startY ) ) ; const rotationMatrix = Matrix2D . getRotationMatrix ( startEndLineAngleRadians ) ; const transposeMatrix = rotationMatrix . getTransposeMatrix ( ) ; end2 = rotationMatrix . vectorMultiplication ( end2 ) ; const transformedPoints = this . calculateTranslatedAndRotatedPointListAbove ( points , startX , startY , rotationMatrix ) ; const startLineSlope = this . calculateMaxLeftSlope ( transformedPoints , start2 , end2 ) ; const endLineSlope = this . calculateMaxRightSlope ( transformedPoints , start2 , end2 ) ; const startLineD = start2 . y - start2 . x * startLineSlope ; const endLineD = end2 . y - end2 . x * endLineSlope ; const intersectionPoint = new PointF2D ( ) ; let sameSlope = false ; if ( Math . abs ( Math . abs ( startLineSlope ) - Math . abs ( endLineSlope ) ) < 0.0001 ) { intersectionPoint . x = end2 . x / 2 ; intersectionPoint . y = 0 ; sameSlope = true ; } else { intersectionPoint . x = ( endLineD - startLineD ) / ( startLineSlope - endLineSlope ) ; intersectionPoint . y = startLineSlope * intersectionPoint . x + startLineD ; } const heightWidthRatio = this . calculateHeightWidthRatio ( end2 . x , transformedPoints ) ; const startYOffset = 0 ; const endYOffset = 0 ; let startAngle = minAngle ; let endAngle = - minAngle ; if ( ! sameSlope ) { const result = this . calculateAngles ( minAngle , startLineSlope , endLineSlope , maxAngle ) ; startAngle = result . startAngle ; endAngle = result . endAngle ; } const controlPoints = this . calculateControlPoints ( end2 . x , startAngle , endAngle , transformedPoints , heightWidthRatio , startY , endY ) ; let startControlPoint = controlPoints . startControlPoint ; let endControlPoint = controlPoints . endControlPoint ; startControlPoint = transposeMatrix . vectorMultiplication ( startControlPoint ) ; startControlPoint . x += startX ; startControlPoint . y = - startControlPoint . y + startY ; endControlPoint = transposeMatrix . vectorMultiplication ( endControlPoint ) ; endControlPoint . x += startX ; endControlPoint . y = - endControlPoint . y + startY ; this . bezierStartPt = new PointF2D ( startX , startY - startYOffset ) ; this . bezierStartControlPt = new PointF2D ( startControlPoint . x , startControlPoint . y - startYOffset ) ; this . bezierEndControlPt = new PointF2D ( endControlPoint . x , endControlPoint . y - endYOffset ) ; this . bezierEndPt = new PointF2D ( endX , endY - endYOffset ) ; const length = staffLine . SkyLine . length ; const startIndex = skyBottomLineCalculator . getLeftIndexForPointX ( this . bezierStartPt . x , length ) ; const endIndex = skyBottomLineCalculator . getLeftIndexForPointX ( this . bezierEndPt . x , length ) ; const distance = this . bezierEndPt . x - this . bezierStartPt . x ; const samplingUnit = skyBottomLineCalculator . SamplingUnit ; for ( let i = startIndex ; i < endIndex ; i ++ ) { const diff = i / samplingUnit - this . bezierStartPt . x ; const curvePoint = this . calculateCurvePointAtIndex ( Math . abs ( diff ) / distance ) ; let index = skyBottomLineCalculator . getLeftIndexForPointX ( curvePoint . x , length ) ; if ( index >= startIndex ) { staffLine . SkyLine [ index ] = Math . min ( staffLine . SkyLine [ index ] , curvePoint . y ) ; } index ++ ; if ( index < length ) { staffLine . SkyLine [ index ] = Math . min ( staffLine . SkyLine [ index ] , curvePoint . y ) ; } } } else { startY += rules . SlurNoteHeadYOffset ; endY += rules . SlurNoteHeadYOffset ; const startLowerRight = new PointF2D ( this . staffEntries [ 0 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ 0 ] . PositionAndShape . RelativePosition . x , startY ) ; if ( slurStartNote ) { startLowerRight . x += this . staffEntries [ 0 ] . PositionAndShape . BorderRight ; } else { startLowerRight . x = this . staffEntries [ 0 ] . parentMeasure . beginInstructionsWidth ; } if ( this . graceStart ) { startLowerRight . x += endStaffEntry . PositionAndShape . RelativePosition . x ; } const endLowerLeft = new PointF2D ( this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ this . staffEntries . length - 1 ] . PositionAndShape . RelativePosition . x , endY ) ; if ( slurEndNote ) { endLowerLeft . x += this . staffEntries [ this . staffEntries . length - 1 ] . PositionAndShape . BorderLeft ; } else { endLowerLeft . x = this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . RelativePosition . x + this . staffEntries [ this . staffEntries . length - 1 ] . parentMeasure . PositionAndShape . Size . width ; } if ( this . graceEnd ) { endLowerLeft . x += endStaffEntry . staffEntryParent . PositionAndShape . RelativePosition . x ; } points = this . calculateBottomPoints ( startLowerRight , endLowerLeft , staffLine , skyBottomLineCalculator ) ; if ( points . length === 0 ) { const pointF = new PointF2D ( ( endLowerLeft . x - startLowerRight . x ) / 2 + startLowerRight . x , ( endLowerLeft . y - startLowerRight . y ) / 2 + startLowerRight . y ) ; points . push ( pointF ) ; } const startEndLineAngleRadians = Math . atan ( ( endY - startY ) / ( endX - startX ) ) ; const start2 = new PointF2D ( 0 , 0 ) ; let end2 = new PointF2D ( endX - startX , endY - startY ) ; const rotationMatrix = Matrix2D . getRotationMatrix ( - startEndLineAngleRadians ) ; const transposeMatrix = rotationMatrix . getTransposeMatrix ( ) ; end2 = rotationMatrix . vectorMultiplication ( end2 ) ; const transformedPoints = this . calculateTranslatedAndRotatedPointListBelow ( points , startX , startY , rotationMatrix ) ; const startLineSlope = this . calculateMaxLeftSlope ( transformedPoints , start2 , end2 ) ; const endLineSlope = this . calculateMaxRightSlope ( transformedPoints , start2 , end2 ) ; const startLineD = start2 . y - start2 . x * startLineSlope ; const endLineD = end2 . y - end2 . x * endLineSlope ; const intersectionPoint = new PointF2D ( ) ; let sameSlope = false ; if ( Math . abs ( Math . abs ( startLineSlope ) - Math . abs ( endLineSlope ) ) < 0.0001 ) { intersectionPoint . x = end2 . x / 2 ; intersectionPoint . y = 0 ; sameSlope = true ; } else { intersectionPoint . x = ( endLineD - startLineD ) / ( startLineSlope - endLineSlope ) ; intersectionPoint . y = startLineSlope * intersectionPoint . x + startLineD ; } const heightWidthRatio = this . calculateHeightWidthRatio ( end2 . x , transformedPoints ) ; const startYOffset = 0 ; const endYOffset = 0 ; let startAngle = minAngle ; let endAngle = - minAngle ; if ( ! sameSlope ) { const result = this . calculateAngles ( minAngle , startLineSlope , endLineSlope , maxAngle ) ; startAngle = result . startAngle ; endAngle = result . endAngle ; } const controlPoints = this . calculateControlPoints ( end2 . x , startAngle , endAngle , transformedPoints , heightWidthRatio , startY , endY ) ; let startControlPoint = controlPoints . startControlPoint ; let endControlPoint = controlPoints . endControlPoint ; startControlPoint = transposeMatrix . vectorMultiplication ( startControlPoint ) ; startControlPoint . x += startX ; startControlPoint . y += startY ; endControlPoint = transposeMatrix . vectorMultiplication ( endControlPoint ) ; endControlPoint . x += startX ; endControlPoint . y += startY ; this . bezierStartPt = new PointF2D ( startX , startY + startYOffset ) ; this . bezierStartControlPt = new PointF2D ( startControlPoint . x , startControlPoint . y + startYOffset ) ; this . bezierEndControlPt = new PointF2D ( endControlPoint . x , endControlPoint . y + endYOffset ) ; this . bezierEndPt = new PointF2D ( endX , endY + endYOffset ) ; const length = staffLine . BottomLine . length ; const startIndex = skyBottomLineCalculator . getLeftIndexForPointX ( this . bezierStartPt . x , length ) ; const endIndex = skyBottomLineCalculator . getLeftIndexForPointX ( this . bezierEndPt . x , length ) ; const distance = this . bezierEndPt . x - this . bezierStartPt . x ; const samplingUnit = skyBottomLineCalculator . SamplingUnit ; for ( let i = startIndex ; i < endIndex ; i ++ ) { const diff = i / samplingUnit - this . bezierStartPt . x ; const curvePoint = this . calculateCurvePointAtIndex ( Math . abs ( diff ) / distance ) ; let index = skyBottomLineCalculator . getLeftIndexForPointX ( curvePoint . x , length ) ; if ( index >= startIndex ) { staffLine . BottomLine [ index ] = Math . max ( staffLine . BottomLine [ index ] , curvePoint . y ) ; } index ++ ; if ( index < length ) { staffLine . BottomLine [ index ] = Math . max ( staffLine . BottomLine [ index ] , curvePoint . y ) ; } } } } param slurStartNote param slurEndNote param staffLine param startX param startY param endX param endY param rules param skyBottomLineCalculator private calculateStartAndEnd ( slurStartNote , slurEndNote , staffLine , rules , skyBottomLineCalculator ) { let startX = 0 ; let startY = 0 ; let endX = 0 ; let endY = 0 ; if ( slurStartNote ) { startX = slurStartNote . PositionAndShape . RelativePosition . x + slurStartNote . parentVoiceEntry . parentStaffEntry . PositionAndShape . RelativePosition . x + slurStartNote . parentVoiceEntry . parentStaffEntry . parentMeasure . PositionAndShape . RelativePosition . x ; if ( this . graceStart ) { startX += slurStartNote . parentVoiceEntry . parentStaffEntry . staffEntryParent . PositionAndShape . RelativePosition . x ; } const slurStartVE = slurStartNote . parentVoiceEntry ; if ( this . placement === PlacementEnum . Above ) { startY = slurStartVE . PositionAndShape . RelativePosition . y + slurStartVE . PositionAndShape . BorderTop ; } else { startY = slurStartVE . PositionAndShape . RelativePosition . y + slurStartVE . PositionAndShape . BorderBottom ; } if ( slurStartVE . parentVoiceEntry . StemDirection === StemDirectionType . Down && this . placement === PlacementEnum . Below ) { startX -= 0.5 ; } if ( slurStartVE . parentVoiceEntry . StemDirection === StemDirectionType . Up && this . placement === PlacementEnum . Above ) { startX += 0.5 ; } } else { startX = 0 ; } if ( slurEndNote ) { endX = slurEndNote . PositionAndShape . RelativePosition . x + slurEndNote . parentVoiceEntry . parentStaffEntry . PositionAndShape . RelativePosition . x + slurEndNote . parentVoiceEntry . parentStaffEntry . parentMeasure . PositionAndShape . RelativePosition . x ; if ( this . graceEnd ) { endX += slurEndNote . parentVoiceEntry . parentStaffEntry . staffEntryParent . PositionAndShape . RelativePosition . x ; } const slurEndVE = slurEndNote . parentVoiceEntry ; if ( this . placement === PlacementEnum . Above ) { endY = slurEndVE . PositionAndShape . RelativePosition . y + slurEndVE . PositionAndShape . BorderTop ; } else { endY = slurEndVE . PositionAndShape . RelativePosition . y + slurEndVE . PositionAndShape . BorderBottom ; } if ( slurEndVE . parentVoiceEntry . StemDirection === StemDirectionType . Down && this . placement === PlacementEnum . Below ) { endX -= 0.5 ; } if ( slurEndVE . parentVoiceEntry . StemDirection === StemDirectionType . Up && this . placement === PlacementEnum . Above ) { endX += 0.5 ; } } else { endX = staffLine . PositionAndShape . Size . width ; } if ( ! slurStartNote && ! slurEndNote ) { startY = - 1.5 ; endY = - 1.5 ; } if ( ! slurStartNote ) { if ( this . placement === PlacementEnum . Above ) { startY = endY - 1 ; } else { startY = endY + 1 ; } } if ( ! slurEndNote ) { if ( this . placement === PlacementEnum . Above ) { endY = startY - 1 ; } else { endY = startY + 1 ; } } if ( this . slur . startNoteHasMoreStartingSlurs ( ) && this . slur . isSlurLonger ( ) ) { if ( this . placement === PlacementEnum . Above ) { startY -= rules . SlursStartingAtSameStaffEntryYOffset ; } else { startY += rules . SlursStartingAtSameStaffEntryYOffset ; } } if ( this . slur . endNoteHasMoreEndingSlurs ( ) && this . slur . isSlurLonger ( ) ) { if ( this . placement === PlacementEnum . Above ) { endY -= rules . SlursStartingAtSameStaffEntryYOffset ; } else { endY += rules . SlursStartingAtSameStaffEntryYOffset ; } } if ( this . placement === PlacementEnum . Above ) { startY = Math . min ( startY , 1.5 ) ; endY = Math . min ( endY , 1.5 ) ; } else { startY = Math . max ( startY , staffLine . StaffHeight - 1.5 ) ; endY = Math . max ( endY , staffLine . StaffHeight - 1.5 ) ; } return { startX , startY , endX , endY } ; } param skyBottomLineCalculator param staffLine private calculatePlacement ( skyBottomLineCalculator , staffLine ) { if ( this . rules . SlurPlacementFromXML ) { this . placement = this . slur . PlacementXml ; return ; } for ( let idx = 0 , len = this . staffEntries . length ; idx < len ; ++ idx ) { const graphicalStaffEntry = this . staffEntries [ idx ] ; if ( graphicalStaffEntry . parentMeasure . hasMultipleVoices ( ) ) { if ( this . slur . StartNote . ParentVoiceEntry . ParentVoice instanceof LinkedVoice || this . slur . EndNote . ParentVoiceEntry . ParentVoice instanceof LinkedVoice ) { this . placement = PlacementEnum . Below ; } else { this . placement = PlacementEnum . Above ; } return ; } } for ( let idx = 0 , len = this . staffEntries . length ; idx < len ; ++ idx ) { const graphicalStaffEntry = this . staffEntries [ idx ] ; if ( graphicalStaffEntry . LyricsEntries . length > 0 ) { this . placement = PlacementEnum . Above ; return ; } } const startStaffEntry = this . staffEntries [ 0 ] ; const endStaffEntry = this . staffEntries [ this . staffEntries . length - 1 ] ; const startStemDirection = startStaffEntry . graphicalVoiceEntries [ 0 ] . parentVoiceEntry . StemDirection ; const endStemDirection = endStaffEntry . graphicalVoiceEntries [ 0 ] . parentVoiceEntry . StemDirection ; if ( startStemDirection === endStemDirection ) { this . placement = ( startStemDirection === StemDirectionType . Up ) ? PlacementEnum . Below : PlacementEnum . Above ; } else { let sX = startStaffEntry . PositionAndShape . BorderLeft + startStaffEntry . PositionAndShape . RelativePosition . x + startStaffEntry . parentMeasure . PositionAndShape . RelativePosition . x ; let eX = endStaffEntry . PositionAndShape . BorderRight + endStaffEntry . PositionAndShape . RelativePosition . x + endStaffEntry . parentMeasure . PositionAndShape . RelativePosition . x ; if ( this . graceStart ) { sX += endStaffEntry . PositionAndShape . RelativePosition . x ; } if ( this . graceEnd ) { eX += endStaffEntry . staffEntryParent . PositionAndShape . RelativePosition . x ; } const minAbove = skyBottomLineCalculator . getSkyLineMinInRange ( sX , eX ) * - 1 ; const maxBelow = skyBottomLineCalculator . getBottomLineMaxInRange ( sX , eX ) - staffLine . StaffHeight ; if ( maxBelow > minAbove ) { this . placement = PlacementEnum . Above ; } else { this . placement = PlacementEnum . Below ; } } } param start param end param staffLine param skyBottomLineCalculator private calculateTopPoints ( start , end , staffLine , skyBottomLineCalculator ) { const points = [ ] ; let startIndex = skyBottomLineCalculator . getRightIndexForPointX ( start . x , staffLine . SkyLine . length ) ; let endIndex = skyBottomLineCalculator . getLeftIndexForPointX ( end . x , staffLine . SkyLine . length ) ; if ( startIndex < 0 ) { startIndex = 0 ; } if ( endIndex >= staffLine . SkyLine . length ) { endIndex = staffLine . SkyLine . length - 1 ; } for ( let i = startIndex ; i < endIndex ; i ++ ) { const skylineValue = staffLine . SkyLine [ i ] ; if ( skylineValue !== 0 ) { const point = new PointF2D ( ( 0.5 + i ) / skyBottomLineCalculator . SamplingUnit , skylineValue ) ; points . push ( point ) ; } } return points ; } param start param end param staffLine param skyBottomLineCalculator private calculateBottomPoints ( start , end , staffLine , skyBottomLineCalculator ) { const points = [ ] ; let startIndex = skyBottomLineCalculator . getRightIndexForPointX ( start . x , staffLine . BottomLine . length ) ; let endIndex = skyBottomLineCalculator . getLeftIndexForPointX ( end . x , staffLine . BottomLine . length ) ; if ( startIndex < 0 ) { startIndex = 0 ; } if ( endIndex >= staffLine . BottomLine . length ) { endIndex = staffLine . BottomLine . length - 1 ; } for ( let i = startIndex ; i < endIndex ; i ++ ) { const bottomLineValue = staffLine . BottomLine [ i ] ; if ( bottomLineValue !== 0 ) { const point = new PointF2D ( ( 0.5 + i ) / skyBottomLineCalculator . SamplingUnit , bottomLineValue ) ; points . push ( point ) ; } } return points ; } param points param start param end private calculateMaxLeftSlope ( points , start , end ) { let slope = - Number . MAX_VALUE ; const x = start . x ; const y = start . y ; for ( let i = 0 ; i < points . length ; i ++ ) { if ( Math . abs ( points [ i ] . y - Number . MAX_VALUE ) < 0.0001 || Math . abs ( points [ i ] . y - ( - Number . MAX_VALUE ) ) < 0.0001 ) { continue ; } slope = Math . max ( slope , ( points [ i ] . y - y ) / ( points [ i ] . x - x ) ) ; } slope = Math . max ( slope , Math . abs ( end . y - y ) / ( end . x - x ) ) ; slope = Math . min ( slope , 5.6713 ) ; return slope ; } param points param start param end private calculateMaxRightSlope ( points , start , end ) { let slope = Number . MAX_VALUE ; const x = end . x ; const y = end . y ; for ( let i = 0 ; i < points . length ; i ++ ) { if ( Math . abs ( points [ i ] . y - Number . MAX_VALUE ) < 0.0001 || Math . abs ( points [ i ] . y - ( - Number . MAX_VALUE ) ) < 0.0001 ) { continue ; } slope = Math . min ( slope , ( y - points [ i ] . y ) / ( x - points [ i ] . x ) ) ; } slope = Math . min ( slope , ( y - start . y ) / ( x - start . x ) ) ; slope = Math . max ( slope , - 5.6713 ) ; return slope ; } param points private getPointListMaxY ( points ) { let max = - Number . MAX_VALUE ; for ( let idx = 0 , len = points . length ; idx < len ; ++ idx ) { const point = points [ idx ] ; if ( Math . abs ( point . y - ( - Number . MAX_VALUE ) ) < 0.0001 || Math . abs ( point . y - Number . MAX_VALUE ) < 0.0001 ) { continue ; } max = Math . max ( max , point . y ) ; } return max ; } param points param startX param startY param rotationMatrix private calculateTranslatedAndRotatedPointListAbove ( points , startX , startY , rotationMatrix ) { const transformedPoints = [ ] ; for ( let i = 0 ; i < points . length ; i ++ ) { if ( Math . abs ( points [ i ] . y - Number . MAX_VALUE ) < 0.0001 || Math . abs ( points [ i ] . y - ( - Number . MAX_VALUE ) ) < 0.0001 ) { continue ; } let point = new PointF2D ( points [ i ] . x - startX , - ( points [ i ] . y - startY ) ) ; point = rotationMatrix . vectorMultiplication ( point ) ; transformedPoints . push ( point ) ; } return transformedPoints ; } param points param startX param startY param rotationMatrix private calculateTranslatedAndRotatedPointListBelow ( points , startX , startY , rotationMatrix ) { const transformedPoints = [ ] ; for ( let i = 0 ; i < points . length ; i ++ ) { if ( Math . abs ( points [ i ] . y - Number . MAX_VALUE ) < 0.0001 || Math . abs ( points [ i ] . y - ( - Number . MAX_VALUE ) ) < 0.0001 ) { continue ; } let point = new PointF2D ( points [ i ] . x - startX , points [ i ] . y - startY ) ; point = rotationMatrix . vectorMultiplication ( point ) ; transformedPoints . push ( point ) ; } return transformedPoints ; } param endX param points private calculateHeightWidthRatio ( endX , points ) { if ( points . length === 0 ) { return 0 ; } const max = Math . max ( 0 , this . getPointListMaxY ( points ) ) ; return max / endX ; } param endX param startAngle param endAngle param points private calculateControlPoints ( endX , startAngle , endAngle , points , heightWidthRatio , startY , endY ) { let heightFactor = this . rules . SlurHeightFactor ; let widthFlattenFactor = 1 ; const cutoffAngle = this . rules . SlurHeightFlattenLongSlursCutoffAngle ; const cutoffWidth = this . rules . SlurHeightFlattenLongSlursCutoffWidth ; if ( startAngle > cutoffAngle && endX > cutoffWidth ) { widthFlattenFactor += endX / 70 * this . rules . SlurHeightFlattenLongSlursFactorByWidth ; widthFlattenFactor *= 1 + ( startAngle / 30 * this . rules . SlurHeightFlattenLongSlursFactorByAngle ) ; heightFactor /= widthFlattenFactor ; } const factorStart = Math . min ( 0.5 , Math . max ( 0.1 , 1.7 * startAngle / 80 * heightFactor * Math . pow ( Math . max ( heightWidthRatio , 0.05 ) , 0.4 ) ) ) ; const factorEnd = Math . min ( 0.5 , Math . max ( 0.1 , 1.7 * ( - endAngle ) / 80 * heightFactor * Math . pow ( Math . max ( heightWidthRatio , 0.05 ) , 0.4 ) ) ) ; const startControlPoint = new PointF2D ( ) ; startControlPoint . x = endX * factorStart * Math . cos ( startAngle * GraphicalSlur . degreesToRadiansFactor ) ; startControlPoint . y = endX * factorStart * Math . sin ( startAngle * GraphicalSlur . degreesToRadiansFactor ) ; const endControlPoint = new PointF2D ( ) ; endControlPoint . x = endX - ( endX * factorEnd * Math . cos ( endAngle * GraphicalSlur . degreesToRadiansFactor ) ) ; endControlPoint . y = - ( endX * factorEnd * Math . sin ( endAngle * GraphicalSlur . degreesToRadiansFactor ) ) ; let controlPointYDiff = startControlPoint . y - endControlPoint . y ; while ( this . rules . SlurMaximumYControlPointDistance && Math . abs ( controlPointYDiff ) > this . rules . SlurMaximumYControlPointDistance ) { if ( controlPointYDiff < 0 ) { startControlPoint . y += 1 ; endControlPoint . y -= 1 ; } else { startControlPoint . y -= 1 ; endControlPoint . y += 1 ; } controlPointYDiff = startControlPoint . y - endControlPoint . y ; } return { startControlPoint : startControlPoint , endControlPoint : endControlPoint } ; } param leftAngle param rightAngle param startLineSlope param endLineSlope param maxAngle private calculateAngles ( minAngle , startLineSlope , endLineSlope , maxAngle ) { const angle = 20 ; let calculatedStartAngle = Math . atan ( startLineSlope ) / GraphicalSlur . degreesToRadiansFactor ; if ( startLineSlope > 0 ) { calculatedStartAngle += angle ; } else { calculatedStartAngle -= angle ; } let calculatedEndAngle = Math . atan ( endLineSlope ) / GraphicalSlur . degreesToRadiansFactor ; if ( endLineSlope < 0 ) { calculatedEndAngle -= angle ; } else { calculatedEndAngle += angle ; } const leftAngle = Math . min ( Math . max ( minAngle , calculatedStartAngle ) , maxAngle ) ; const rightAngle = Math . max ( Math . min ( - minAngle , calculatedEndAngle ) , - maxAngle ) ; return { "startAngle" : leftAngle , "endAngle" : rightAngle } ; } private static degreesToRadiansFactor : number = Math . PI / 180 ; }