import { EngravingRules } from "./EngravingRules" ; import { ITextMeasurer } from "../Interfaces/ITextMeasurer" ; import { GraphicalMusicSheet } from "./GraphicalMusicSheet" ; import { BoundingBox } from "./BoundingBox" ; import { GraphicalLayers , OutlineAndFillStyleEnum } from "./DrawingEnums" ; import { DrawingParameters } from "./DrawingParameters" ; import { GraphicalLine } from "./GraphicalLine" ; import { RectangleF2D } from "../../Common/DataObjects/RectangleF2D" ; import { PointF2D } from "../../Common/DataObjects/PointF2D" ; import { GraphicalRectangle } from "./GraphicalRectangle" ; import { GraphicalLabel } from "./GraphicalLabel" ; import { Label } from "../Label" ; import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment" ; import { ArgumentOutOfRangeException } from "../Exceptions" ; import { SelectionStartSymbol } from "./SelectionStartSymbol" ; import { SelectionEndSymbol } from "./SelectionEndSymbol" ; import { MusicSystem } from "./MusicSystem" ; import { GraphicalMeasure } from "./GraphicalMeasure" ; import { StaffLine } from "./StaffLine" ; import { SystemLine } from "./SystemLine" ; import { MusicSymbol } from "./MusicSymbol" ; import { GraphicalMusicPage } from "./GraphicalMusicPage" ; import { Instrument } from "../Instrument" ; import { MusicSymbolDrawingStyle , PhonicScoreModes } from "./DrawingMode" ; import { GraphicalObject } from "./GraphicalObject" ; import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression" ; import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression" ; import { VexFlowContinuousDynamicExpression , VexFlowGraphicalNote , VexFlowInstrumentBracket , VexFlowMeasure , VexFlowStaffEntry , VexFlowStaffLine , VexFlowVoiceEntry } from "./VexFlow" ; import { StaffLineActivitySymbol } from "./StaffLineActivitySymbol" ; /**
 * Draw a [[GraphicalMusicSheet]] (through the .drawSheet method)
 *
 * The drawing is implemented with a top-down approach, starting from a music sheet, going through pages, systems, staffs...
 * ... and ending in notes, beams, accidentals and other symbols.
 * It's worth to say, that this class just draws the symbols and graphical elements, using the positions that have been computed before.
 * But in any case, some of these previous positioning algorithms need the sizes of the concrete symbols (NoteHeads, sharps, flats, keys...).
 * Therefore, there are some static functions on the 'Bounding Boxes' section used to compute these symbol boxes at the
 * beginning for the later use in positioning algorithms.
 *
 * This class also includes the resizing and positioning of the symbols due to user interaction like zooming or panning.
 */ export abstract class MusicSheetDrawer { public drawingParameters : DrawingParameters ; public splitScreenLineColor : number ; public midiPlaybackAvailable : boolean ; public drawableBoundingBoxElement : string = "None" ; public skyLineVisible : boolean = false ; public bottomLineVisible : boolean = false ; protected rules : EngravingRules ; protected graphicalMusicSheet : GraphicalMusicSheet ; protected textMeasurer : ITextMeasurer ; private phonicScoreMode : PhonicScoreModes = PhonicScoreModes . Manual ; constructor ( textMeasurer , drawingParameters ) { this . textMeasurer = textMeasurer ; this . splitScreenLineColor = - 1 ; this . drawingParameters = drawingParameters ; this . rules = drawingParameters . Rules ; } public set Mode ( value ) { this . phonicScoreMode = value ; } public drawSheet ( graphicalMusicSheet ) { this . graphicalMusicSheet = graphicalMusicSheet ; this . rules = graphicalMusicSheet . ParentMusicSheet . Rules ; this . drawSplitScreenLine ( ) ; if ( this . drawingParameters . drawCursors ) { for ( const line of graphicalMusicSheet . Cursors ) { if ( ! line ) { continue ; } const psi = new BoundingBox ( line ) ; psi . AbsolutePosition = line . Start ; psi . BorderBottom = line . End . y - line . Start . y ; psi . BorderRight = line . Width / 2.0 ; psi . BorderLeft = - line . Width / 2.0 ; if ( this . isVisible ( psi ) ) { this . drawLineAsVerticalRectangle ( line , < number > GraphicalLayers . Cursor ) ; } } } if ( this . drawingParameters . drawScrollIndicator ) { this . drawScrollIndicator ( ) ; } const pagesToDraw = Math . min ( this . graphicalMusicSheet . MusicPages . length , this . rules . MaxPageToDrawNumber ) ; for ( let i = 0 ; i < pagesToDraw ; i ++ ) { const page = this . graphicalMusicSheet . MusicPages [ i ] ; this . drawPage ( page ) ; } } public drawLineAsHorizontalRectangle ( line , layer ) { let rectangle = new RectangleF2D ( line . Start . x , line . End . y - line . Width / 2 , line . End . x - line . Start . x , line . Width ) ; rectangle = this . applyScreenTransformationForRect ( rectangle ) ; this . renderRectangle ( rectangle , layer , line . styleId , line . colorHex ) ; } public drawLineAsVerticalRectangle ( line , layer ) { const lineStart = line . Start ; const lineWidth = line . Width ; let rectangle = new RectangleF2D ( lineStart . x - lineWidth / 2 , lineStart . y , lineWidth , line . End . y - lineStart . y ) ; rectangle = this . applyScreenTransformationForRect ( rectangle ) ; this . renderRectangle ( rectangle , layer , line . styleId ) ; } public drawLineAsHorizontalRectangleWithOffset ( line , offset , layer ) { const start = new PointF2D ( line . Start . x + offset . x , line . Start . y + offset . y ) ; const end = new PointF2D ( line . End . x + offset . x , line . End . y + offset . y ) ; const width = line . Width ; let rectangle = new RectangleF2D ( start . x , end . y - width / 2 , end . x - start . x , width ) ; rectangle = this . applyScreenTransformationForRect ( rectangle ) ; this . renderRectangle ( rectangle , layer , line . styleId ) ; } public drawLineAsVerticalRectangleWithOffset ( line , offset , layer ) { const start = new PointF2D ( line . Start . x + offset . x , line . Start . y + offset . y ) ; const end = new PointF2D ( line . End . x + offset . x , line . End . y + offset . y ) ; const width = line . Width ; let rectangle = new RectangleF2D ( start . x , start . y , width , end . y - start . y ) ; rectangle = this . applyScreenTransformationForRect ( rectangle ) ; this . renderRectangle ( rectangle , layer , line . styleId ) ; } public drawRectangle ( rect , layer ) { const psi = rect . PositionAndShape ; let rectangle = new RectangleF2D ( psi . AbsolutePosition . x , psi . AbsolutePosition . y , psi . BorderRight , psi . BorderBottom ) ; rectangle = this . applyScreenTransformationForRect ( rectangle ) ; this . renderRectangle ( rectangle , layer , < number > rect . style ) ; } public calculatePixelDistance ( unitDistance ) { throw new Error ( "not implemented" ) ; } public drawLabel ( graphicalLabel , layer ) { if ( ! this . isVisible ( graphicalLabel . PositionAndShape ) ) { return undefined ; } const label = graphicalLabel . Label ; if ( label . text . trim ( ) === "" ) { return undefined ; } const screenPosition = this . applyScreenTransformation ( graphicalLabel . PositionAndShape . AbsolutePosition ) ; const fontHeightInPixel = this . calculatePixelDistance ( label . fontHeight ) ; const widthInPixel = this . calculatePixelDistance ( graphicalLabel . PositionAndShape . Size . width ) ; const bitmapWidth = Math . ceil ( widthInPixel ) ; const bitmapHeight = Math . ceil ( fontHeightInPixel * ( 0.2 + graphicalLabel . TextLines . length ) ) ; switch ( label . textAlignment ) { case TextAlignmentEnum . LeftTop : break ; case TextAlignmentEnum . LeftCenter : screenPosition . y -= bitmapHeight / 2 ; break ; case TextAlignmentEnum . LeftBottom : screenPosition . y -= bitmapHeight ; break ; case TextAlignmentEnum . CenterTop : screenPosition . x -= bitmapWidth / 2 ; break ; case TextAlignmentEnum . CenterCenter : screenPosition . x -= bitmapWidth / 2 ; screenPosition . y -= bitmapHeight / 2 ; break ; case TextAlignmentEnum . CenterBottom : screenPosition . x -= bitmapWidth / 2 ; screenPosition . y -= bitmapHeight ; break ; case TextAlignmentEnum . RightTop : screenPosition . x -= bitmapWidth ; break ; case TextAlignmentEnum . RightCenter : screenPosition . x -= bitmapWidth ; screenPosition . y -= bitmapHeight / 2 ; break ; case TextAlignmentEnum . RightBottom : screenPosition . x -= bitmapWidth ; screenPosition . y -= bitmapHeight ; break ; default : throw new ArgumentOutOfRangeException ( "" ) ; } return this . renderLabel ( graphicalLabel , layer , bitmapWidth , bitmapHeight , fontHeightInPixel , screenPosition ) ; } protected applyScreenTransformation ( point ) { throw new Error ( "not implemented" ) ; } protected applyScreenTransformations ( points ) { const transformedPoints = [ ] ; for ( const point of points ) { transformedPoints . push ( this . applyScreenTransformation ( point ) ) ; } return transformedPoints ; } protected applyScreenTransformationForRect ( rectangle ) { throw new Error ( "not implemented" ) ; } protected drawSplitScreenLine ( ) { } protected renderRectangle ( rectangle , layer , styleId , colorHex = undefined , alpha = 1 ) { throw new Error ( "not implemented" ) ; } protected drawScrollIndicator ( ) { } protected drawSelectionStartSymbol ( symbol ) { } protected drawSelectionEndSymbol ( symbol ) { } protected renderLabel ( graphicalLabel , layer , bitmapWidth , bitmapHeight , heightInPixel , screenPosition ) { throw new Error ( "not implemented" ) ; } protected renderSystemToScreen ( system , systemBoundingBoxInPixels , absBoundingRectWithMargin ) { } protected drawMeasure ( measure ) { throw new Error ( "not implemented" ) ; } protected drawSkyLine ( staffLine ) { } protected drawBottomLine ( staffLine ) { } protected drawInstrumentBrace ( brace , system ) { } protected drawGroupBracket ( bracket , system ) { } protected isVisible ( psi ) { return true ; } protected drawMusicSystem ( system ) { const absBoundingRectWithMargin = this . getSystemAbsBoundingRect ( system ) ; const systemBoundingBoxInPixels = this . getSytemBoundingBoxInPixels ( absBoundingRectWithMargin ) ; this . drawMusicSystemComponents ( system , systemBoundingBoxInPixels , absBoundingRectWithMargin ) ; } protected getSytemBoundingBoxInPixels ( absBoundingRectWithMargin ) { const systemBoundingBoxInPixels = this . applyScreenTransformationForRect ( absBoundingRectWithMargin ) ; systemBoundingBoxInPixels . x = Math . round ( systemBoundingBoxInPixels . x ) ; systemBoundingBoxInPixels . y = Math . round ( systemBoundingBoxInPixels . y ) ; return systemBoundingBoxInPixels ; } protected getSystemAbsBoundingRect ( system ) { const relBoundingRect = system . PositionAndShape . BoundingRectangle ; const absBoundingRectWithMargin = new RectangleF2D ( system . PositionAndShape . AbsolutePosition . x + system . PositionAndShape . BorderLeft - 1 , system . PositionAndShape . AbsolutePosition . y + system . PositionAndShape . BorderTop - 1 , ( relBoundingRect . width + 6 ) , ( relBoundingRect . height + 2 ) ) ; return absBoundingRectWithMargin ; } protected drawMusicSystemComponents ( musicSystem , systemBoundingBoxInPixels , absBoundingRectWithMargin ) { const selectStartSymb = this . graphicalMusicSheet . SelectionStartSymbol ; const selectEndSymb = this . graphicalMusicSheet . SelectionEndSymbol ; if ( this . drawingParameters . drawSelectionStartSymbol ) { if ( selectStartSymb !== undefined && this . isVisible ( selectStartSymb . PositionAndShape ) ) { this . drawSelectionStartSymbol ( selectStartSymb ) ; } } if ( this . drawingParameters . drawSelectionEndSymbol ) { if ( selectEndSymb !== undefined && this . isVisible ( selectEndSymb . PositionAndShape ) ) { this . drawSelectionEndSymbol ( selectEndSymb ) ; } } for ( const staffLine of musicSystem . StaffLines ) { this . drawStaffLine ( staffLine ) ; if ( this . rules . RenderLyrics ) { if ( staffLine . LyricsDashes . length > 0 ) { this . drawDashes ( staffLine . LyricsDashes ) ; } if ( staffLine . LyricLines . length > 0 ) { this . drawLyricLines ( staffLine . LyricLines , staffLine ) ; } } } for ( const systemLine of musicSystem . SystemLines ) { this . drawSystemLineObject ( systemLine ) ; } if ( musicSystem . Parent === musicSystem . Parent . Parent . MusicPages [ 0 ] ) { for ( const label of musicSystem . Labels ) { label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } } const instruments = this . graphicalMusicSheet . ParentMusicSheet . Instruments ; const instrumentsVisible = instruments . filter ( ( instrument ) => instrument . Visible ) . length ; for ( const bracket of musicSystem . InstrumentBrackets ) { this . drawInstrumentBrace ( bracket , musicSystem ) ; } if ( instruments . length > 0 ) { if ( instrumentsVisible > 1 ) { for ( const bracket of musicSystem . GroupBrackets ) { this . drawGroupBracket ( bracket , musicSystem ) ; } } else { for ( const bracket of musicSystem . GroupBrackets ) { ( bracket as VexFlowInstrumentBracket ) . Visible = false ; } } } if ( ! this . leadSheet ) { for ( const measureNumberLabel of musicSystem . MeasureNumberLabels ) { measureNumberLabel . SVGNode = this . drawLabel ( measureNumberLabel , < number > GraphicalLayers . Notes ) ; } } for ( const staffLine of musicSystem . StaffLines ) { this . drawStaffLineSymbols ( staffLine ) ; } if ( this . drawingParameters . drawMarkedAreas ) { this . drawMarkedAreas ( musicSystem ) ; } if ( this . drawingParameters . drawComments ) { this . drawComment ( musicSystem ) ; } } protected activateSystemRendering ( systemId , absBoundingRect , systemBoundingBoxInPixels , createNewImage ) { return true ; } protected drawSystemLineObject ( systemLine ) { } protected drawStaffLine ( staffLine ) { for ( const measure of staffLine . Measures ) { this . drawMeasure ( measure ) ; } if ( this . rules . RenderLyrics ) { if ( staffLine . LyricsDashes . length > 0 ) { this . drawDashes ( staffLine . LyricsDashes ) ; } } this . drawOctaveShifts ( staffLine ) ; this . drawExpressions ( staffLine ) ; if ( this . skyLineVisible ) { this . drawSkyLine ( staffLine ) ; } if ( this . bottomLineVisible ) { this . drawBottomLine ( staffLine ) ; } } protected drawLyricLines ( lyricLines , staffLine ) { staffLine . LyricLines . forEach ( lyricLine => { lyricLine . Start . y += staffLine . PositionAndShape . AbsolutePosition . y ; lyricLine . End . y += staffLine . PositionAndShape . AbsolutePosition . y ; lyricLine . Start . x += staffLine . PositionAndShape . AbsolutePosition . x ; lyricLine . End . x += staffLine . PositionAndShape . AbsolutePosition . x ; this . drawGraphicalLine ( lyricLine , this . rules . LyricUnderscoreLineWidth ) ; } ) ; } protected drawExpressions ( staffline ) { } protected drawGraphicalLine ( graphicalLine , lineWidth , colorOrStyle = "black" ) { return this . drawLine ( graphicalLine . Start , graphicalLine . End , colorOrStyle , lineWidth ) ; } protected drawLine ( start , stop , color = "#FF0000FF" , lineWidth ) { return undefined ; } param lyricsDashes param layer protected drawDashes ( lyricsDashes ) { lyricsDashes . forEach ( dash => dash . SVGNode = this . drawLabel ( dash , < number > GraphicalLayers . Notes ) ) ; } protected drawOctaveShifts ( staffLine ) { return ; } protected drawStaffLines ( staffLine ) { if ( staffLine . StaffLines ) { const position = staffLine . PositionAndShape . AbsolutePosition ; for ( let i = 0 ; i < 5 ; i ++ ) { this . drawLineAsHorizontalRectangleWithOffset ( staffLine . StaffLines [ i ] , position , < number > GraphicalLayers . Notes ) ; } } } param instantaneousDynamic protected drawInstantaneousDynamic ( instantaneousDynamic ) { throw new Error ( "not implemented" ) ; } param expression protected drawContinuousDynamic ( expression ) { throw new Error ( "not implemented" ) ; } protected drawSymbol ( symbol , symbolStyle , position , scalingFactor = 1 , layer = < number > GraphicalLayers . Notes ) { } protected get leadSheet ( ) : boolean { return this . graphicalMusicSheet . LeadSheet ; } protected set leadSheet ( value ) { this . graphicalMusicSheet . LeadSheet = value ; } protected drawPage ( page ) { if ( ! this . isVisible ( page . PositionAndShape ) ) { return ; } for ( const system of page . MusicSystems ) { if ( this . isVisible ( system . PositionAndShape ) ) { this . drawMusicSystem ( system ) ; } } if ( page === page . Parent . MusicPages [ 0 ] ) { for ( const label of page . Labels ) { label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } } if ( this . drawableBoundingBoxElement ) { this . drawBoundingBoxes ( page . PositionAndShape , 0 , this . drawableBoundingBoxElement ) ; } } param startBox param layer param type private drawBoundingBoxes ( startBox , layer = 0 , type = "all" ) { const dataObjectString = ( startBox . DataObject . constructor as any ) . name ; let typeMatch = false ; if ( type === "all" ) { typeMatch = true ; } else { if ( type === "VexFlowStaffEntry" ) { typeMatch = startBox . DataObject instanceof VexFlowStaffEntry ; } else if ( type === "VexFlowMeasure" ) { typeMatch = startBox . DataObject instanceof VexFlowMeasure ; } else if ( type === "VexFlowGraphicalNote" ) { typeMatch = startBox . DataObject instanceof VexFlowGraphicalNote ; } else if ( type === "VexFlowVoiceEntry" ) { typeMatch = startBox . DataObject instanceof VexFlowVoiceEntry ; } else if ( type === "GraphicalLabel" ) { typeMatch = startBox . DataObject instanceof GraphicalLabel ; } else if ( type === "VexFlowStaffLine" ) { typeMatch = startBox . DataObject instanceof VexFlowStaffLine ; } else if ( type === "SystemLine" ) { typeMatch = startBox . DataObject instanceof SystemLine ; } else if ( type === "StaffLineActivitySymbol" ) { typeMatch = startBox . DataObject instanceof StaffLineActivitySymbol ; } else if ( type === "VexFlowContinuousDynamicExpression" ) { typeMatch = startBox . DataObject instanceof VexFlowContinuousDynamicExpression ; } } if ( typeMatch || dataObjectString === type ) { this . drawBoundingBox ( startBox , undefined , true , dataObjectString , layer ) ; } layer ++ ; startBox . ChildElements . forEach ( bb => this . drawBoundingBoxes ( bb , layer , type ) ) ; } public drawBoundingBox ( bbox , color = undefined , drawCross = false , labelText = undefined , layer = 0 ) { let tmpRect = new RectangleF2D ( bbox . AbsolutePosition . x + bbox . BorderMarginLeft , bbox . AbsolutePosition . y + bbox . BorderMarginTop , bbox . BorderMarginRight - bbox . BorderMarginLeft , bbox . BorderMarginBottom - bbox . BorderMarginTop ) ; if ( drawCross ) { this . drawLineAsHorizontalRectangle ( new GraphicalLine ( new PointF2D ( bbox . AbsolutePosition . x - 1 , bbox . AbsolutePosition . y ) , new PointF2D ( bbox . AbsolutePosition . x + 1 , bbox . AbsolutePosition . y ) , 0.1 , OutlineAndFillStyleEnum . BaseWritingColor , color ) , layer - 1 ) ; this . drawLineAsVerticalRectangle ( new GraphicalLine ( new PointF2D ( bbox . AbsolutePosition . x , bbox . AbsolutePosition . y - 1 ) , new PointF2D ( bbox . AbsolutePosition . x , bbox . AbsolutePosition . y + 1 ) , 0.1 , OutlineAndFillStyleEnum . BaseWritingColor , color ) , layer - 1 ) ; } tmpRect = this . applyScreenTransformationForRect ( tmpRect ) ; const rectNode = this . renderRectangle ( tmpRect , < number > GraphicalLayers . Background , layer , color , 0.5 ) ; if ( labelText ) { const label = new Label ( labelText ) ; this . renderLabel ( new GraphicalLabel ( label , 0.8 , TextAlignmentEnum . CenterCenter , this . rules ) , layer , tmpRect . width , tmpRect . height , tmpRect . height , new PointF2D ( tmpRect . x , tmpRect . y + 12 ) ) ; } return rectNode ; } private drawMarkedAreas ( system ) { for ( const markedArea of system . GraphicalMarkedAreas ) { if ( markedArea ) { if ( markedArea . systemRectangle ) { this . drawRectangle ( markedArea . systemRectangle , < number > GraphicalLayers . Background ) ; } if ( markedArea . settings ) { markedArea . settings . SVGNode = this . drawLabel ( markedArea . settings , < number > GraphicalLayers . Comment ) ; } if ( markedArea . labelRectangle ) { this . drawRectangle ( markedArea . labelRectangle , < number > GraphicalLayers . Background ) ; } if ( markedArea . label ) { markedArea . label . SVGNode = this . drawLabel ( markedArea . label , < number > GraphicalLayers . Comment ) ; } } } } private drawComment ( system ) { for ( const comment of system . GraphicalComments ) { if ( comment ) { if ( comment . settings ) { comment . settings . SVGNode = this . drawLabel ( comment . settings , < number > GraphicalLayers . Comment ) ; } if ( comment . label ) { comment . label . SVGNode = this . drawLabel ( comment . label , < number > GraphicalLayers . Comment ) ; } } } } private drawStaffLineSymbols ( staffLine ) { const parentInst = staffLine . ParentStaff . ParentInstrument ; const absX = staffLine . PositionAndShape . AbsolutePosition . x ; const absY = staffLine . PositionAndShape . AbsolutePosition . y + 2 ; const borderRight = staffLine . PositionAndShape . BorderRight ; if ( parentInst . highlight && this . drawingParameters . drawHighlights ) { this . drawLineAsHorizontalRectangle ( new GraphicalLine ( new PointF2D ( absX , absY ) , new PointF2D ( absX + borderRight , absY ) , 4 , OutlineAndFillStyleEnum . Highlighted ) , < number > GraphicalLayers . Highlight ) ; } let style = MusicSymbolDrawingStyle . Disabled ; let symbol = MusicSymbol . PLAY ; let drawSymbols = this . drawingParameters . drawActivitySymbols ; switch ( this . phonicScoreMode ) { case PhonicScoreModes . Midi : symbol = MusicSymbol . PLAY ; if ( this . midiPlaybackAvailable && staffLine . ParentStaff . audible ) { style = MusicSymbolDrawingStyle . PlaybackSymbols ; } break ; case PhonicScoreModes . Following : symbol = MusicSymbol . MIC ; if ( staffLine . ParentStaff . following ) { style = MusicSymbolDrawingStyle . FollowSymbols ; } break ; default : drawSymbols = false ; break ; } if ( drawSymbols ) { const p = new PointF2D ( absX + borderRight + 2 , absY ) ; this . drawSymbol ( symbol , style , p ) ; } if ( this . drawingParameters . drawErrors ) { for ( const measure of staffLine . Measures ) { const measurePSI = measure . PositionAndShape ; const absXPSI = measurePSI . AbsolutePosition . x ; const absYPSI = measurePSI . AbsolutePosition . y + 2 ; if ( measure . hasError && this . graphicalMusicSheet . ParentMusicSheet . DrawErroneousMeasures ) { this . drawLineAsHorizontalRectangle ( new GraphicalLine ( new PointF2D ( absXPSI , absYPSI ) , new PointF2D ( absXPSI + measurePSI . BorderRight , absYPSI ) , 4 , OutlineAndFillStyleEnum . ErrorUnderlay ) , < number > GraphicalLayers . MeasureError ) ; } } } } }