Просмотр исходного кода

Merge branch 'fix/entangled-engraving-rules' of https://github.com/dnlfrst/opensheetmusicdisplay into dnlfrst-fix/entangled-engraving-rules

remove static instance in EngravingRules (EngravingRules.Rules)
manually correct a lot of uses of the newly non-static EngravingRules
Remove static EngravingRules usage from VexflowConverter
add EngravingRules argument to constructors for GraphicalLabel, CanvasVexFlowBackend, etc

manual merge of PR #647 with many additions and changes
fix #647, #559

tested with npm test, visual regression tests, manually by setting options like defaultColorNotehead

# Conflicts:
#	src/MusicalScore/Graphical/GraphicalVoiceEntry.ts
#	src/MusicalScore/Graphical/MusicSheetCalculator.ts
#	src/MusicalScore/Graphical/MusicSystem.ts
#	src/MusicalScore/Graphical/MusicSystemBuilder.ts
#	src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
#	src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
#	src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
sschmid 5 лет назад
Родитель
Сommit
c1d79798e3
42 измененных файлов с 414 добавлено и 272 удалено
  1. 2 1
      src/MusicalScore/Graphical/AbstractGraphicalExpression.ts
  2. 1 1
      src/MusicalScore/Graphical/BoundingBox.ts
  3. 17 8
      src/MusicalScore/Graphical/DrawingParameters.ts
  4. 8 12
      src/MusicalScore/Graphical/EngravingRules.ts
  5. 7 2
      src/MusicalScore/Graphical/GraphicalChordSymbolContainer.ts
  6. 5 2
      src/MusicalScore/Graphical/GraphicalLabel.ts
  7. 3 3
      src/MusicalScore/Graphical/GraphicalLyricEntry.ts
  8. 2 3
      src/MusicalScore/Graphical/GraphicalMusicSheet.ts
  9. 4 0
      src/MusicalScore/Graphical/GraphicalVoiceEntry.ts
  10. 48 42
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  11. 4 4
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  12. 4 3
      src/MusicalScore/Graphical/MusicSystem.ts
  13. 7 7
      src/MusicalScore/Graphical/MusicSystemBuilder.ts
  14. 2 2
      src/MusicalScore/Graphical/SkyBottomLineCalculator.ts
  15. 2 3
      src/MusicalScore/Graphical/StaffLine.ts
  16. 2 1
      src/MusicalScore/Graphical/VexFlow/AlignmentManager.ts
  17. 9 3
      src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts
  18. 8 3
      src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts
  19. 2 1
      src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts
  20. 1 0
      src/MusicalScore/Graphical/VexFlow/VexFlowContinuousDynamicExpression.ts
  21. 8 6
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  22. 9 6
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts
  23. 1 0
      src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts
  24. 31 20
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  25. 17 15
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  26. 6 7
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  27. 2 2
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts
  28. 1 2
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  29. 3 4
      src/MusicalScore/Graphical/VexFlow/VexFlowTabMeasure.ts
  30. 5 2
      src/MusicalScore/Graphical/VexFlow/VexFlowTextMeasurer.ts
  31. 13 14
      src/MusicalScore/Graphical/VexFlow/VexFlowVoiceEntry.ts
  32. 2 1
      src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts
  33. 23 21
      src/MusicalScore/MusicSheet.ts
  34. 6 2
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  35. 1 2
      src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts
  36. 9 2
      src/MusicalScore/VoiceData/SourceMeasure.ts
  37. 4 2
      src/OpenSheetMusicDisplay/Cursor.ts
  38. 57 51
      src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
  39. 63 1
      test/Common/OSMD/OSMD_Test.ts
  40. 8 5
      test/MusicalScore/Graphical/VexFlow/VexFlowMeasure_Test.ts
  41. 4 4
      test/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer_Test.ts
  42. 3 2
      test/MusicalScore/ScoreCalculation/MusicSheetCalculator_Test.ts

+ 2 - 1
src/MusicalScore/Graphical/AbstractGraphicalExpression.ts

@@ -11,7 +11,7 @@ export abstract class AbstractGraphicalExpression extends GraphicalObject {
     /** Internal cache of read expression */
     protected expression: AbstractExpression;
     /** EngravingRules for positioning */
-    protected rules: EngravingRules = EngravingRules.Rules;
+    protected rules: EngravingRules;
 
     constructor(parentStaffline: StaffLine, expression: AbstractExpression) {
         super();
@@ -19,6 +19,7 @@ export abstract class AbstractGraphicalExpression extends GraphicalObject {
         this.boundingBox = new BoundingBox(this, parentStaffline.PositionAndShape);
         this.parentStaffLine = parentStaffline;
         this.parentStaffLine.AbstractExpressions.push(this);
+        this.rules = parentStaffline.ParentMusicSystem.rules;
     }
 
     /** Graphical label of the expression if available */

+ 1 - 1
src/MusicalScore/Graphical/BoundingBox.ts

@@ -377,7 +377,7 @@ export class BoundingBox {
         for (let idx: number = 0, len: number = this.ChildElements.length; idx < len; ++idx) {
             const childElement: BoundingBox = this.ChildElements[idx];
             minTop = Math.min(minTop, childElement.relativePosition.y + childElement.borderTop);
-            if (!EngravingRules.Rules.FixStafflineBoundingBox || !(childElement.dataObject instanceof StaffLineActivitySymbol)) {
+            if (!EngravingRules.FixStafflineBoundingBox || !(childElement.dataObject instanceof StaffLineActivitySymbol)) {
                 maxBottom = Math.max(maxBottom, childElement.relativePosition.y + childElement.borderBottom);
                 // TODO there's a problem with the bottom bounding box of many stafflines, often caused by StaffLineActivitySymbol,
                 // often leading to the page SVG canvas being unnecessarily long in y-direction. This seems to be remedied by this workaround.

+ 17 - 8
src/MusicalScore/Graphical/DrawingParameters.ts

@@ -20,6 +20,7 @@ export enum DrawingParametersEnum {
 export class DrawingParameters {
     /** will set other settings if changed with set method */
     private drawingParametersEnum: DrawingParametersEnum;
+    private rules: EngravingRules = new EngravingRules();
     public drawHighlights: boolean;
     public drawErrors: boolean;
     public drawSelectionStartSymbol: boolean;
@@ -87,7 +88,7 @@ export class DrawingParameters {
         this.drawCredits = true;
         this.DrawPartNames = true;
         this.drawHiddenNotes = true;
-        EngravingRules.Rules.CompactMode = false;
+        this.rules.CompactMode = false;
     }
 
     public setForDefault(): void {
@@ -110,7 +111,7 @@ export class DrawingParameters {
 
     public setForCompactMode(): void {
         this.setForDefault();
-        EngravingRules.Rules.CompactMode = true;
+        this.rules.CompactMode = true;
         this.DrawCredits = false; // sets DrawComposer, DrawTitle, DrawLyricist to false
         // this.DrawPartNames = true; // unnecessary
         this.drawHiddenNotes = false;
@@ -152,7 +153,7 @@ export class DrawingParameters {
     /** Enable or disable drawing the Title of the piece. If disabled, will disable drawing Subtitle as well. */
     public set DrawTitle(value: boolean) {
         this.drawTitle = value;
-        EngravingRules.Rules.RenderTitle = value;
+        this.rules.RenderTitle = value;
         if (!value) { // don't draw subtitle if title isn't drawn
             this.DrawSubtitle = false;
         }
@@ -165,7 +166,7 @@ export class DrawingParameters {
     /** Enable or disable drawing the Subtitle of the piece. If enabled, will enable drawing Title as well. */
     public set DrawSubtitle(value: boolean) {
         this.drawSubtitle = value;
-        EngravingRules.Rules.RenderSubtitle = value;
+        this.rules.RenderSubtitle = value;
         if (value) {
             this.DrawTitle = true; // if subtitle is drawn, title needs to be drawn as well
         }
@@ -178,7 +179,7 @@ export class DrawingParameters {
     /** Enable or disable drawing a label for the Composer of the piece. */
     public set DrawComposer(value: boolean) {
         this.drawComposer = value;
-        EngravingRules.Rules.RenderComposer = value;
+        this.rules.RenderComposer = value;
     }
 
     public get DrawLyricist(): boolean {
@@ -187,7 +188,7 @@ export class DrawingParameters {
 
     public set DrawLyricist(value: boolean) {
         this.drawLyricist = value;
-        EngravingRules.Rules.RenderLyricist = value;
+        this.rules.RenderLyricist = value;
     }
 
     public get DrawPartNames(): boolean {
@@ -196,7 +197,7 @@ export class DrawingParameters {
 
     public set DrawPartNames(value: boolean) {
         this.drawPartNames = value;
-        EngravingRules.Rules.RenderPartNames = value;
+        this.rules.RenderPartNames = value;
     }
 
     public get FingeringPosition(): PlacementEnum {
@@ -205,6 +206,14 @@ export class DrawingParameters {
 
     public set FingeringPosition(value: PlacementEnum) {
         this.fingeringPosition = value;
-        EngravingRules.Rules.FingeringPosition = value;
+        this.rules.FingeringPosition = value;
+    }
+
+    public get Rules(): EngravingRules {
+        return this.rules;
+    }
+
+    public set Rules(value: EngravingRules) {
+        this.rules = value;
     }
 }

+ 8 - 12
src/MusicalScore/Graphical/EngravingRules.ts

@@ -9,7 +9,6 @@ import { Dictionary } from "typescript-collections";
 import { NoteEnum } from "../..";
 
 export class EngravingRules {
-    private static rules: EngravingRules;
     /** A unit of distance. 1.0 is the distance between lines of a stave for OSMD, which is 10 pixels in Vexflow. */
     private static unit: number = 1.0;
     private samplingUnit: number;
@@ -218,7 +217,7 @@ export class EngravingRules {
     private pageBackgroundColor: string; // vexflow-color-string (#FFFFFF). Default undefined/transparent.
     private renderSingleHorizontalStaffline: boolean;
 
-    private fixStafflineBoundingBox: boolean; // TODO temporary workaround
+    private static fixStafflineBoundingBox: boolean; // TODO temporary workaround
 
     constructor() {
         // global variables
@@ -443,7 +442,7 @@ export class EngravingRules {
         this.fingeringPosition = PlacementEnum.Left; // easier to get bounding box, and safer for vertical layout
         this.fingeringInsideStafflines = false;
 
-        this.fixStafflineBoundingBox = false; // TODO temporary workaround
+        EngravingRules.FixStafflineBoundingBox = false; // TODO temporary workaround
 
         this.pageFormat = PageFormat.UndefinedPageFormat; // default: undefined / 'infinite' height page, using the canvas'/container's width and height
         this.pageBackgroundColor = undefined; // default: transparent. half-transparent white: #FFFFFF88"
@@ -460,10 +459,14 @@ export class EngravingRules {
         } catch (ex) {
             log.info("EngravingRules()", ex);
         }
+    }
 
+    // these two need to be static so that we can avoid passing EngravingRules to BoundingBox in lots of code
+    public static get FixStafflineBoundingBox(): boolean {
+        return EngravingRules.fixStafflineBoundingBox;
     }
-    public static get Rules(): EngravingRules {
-        return EngravingRules.rules !== undefined ? EngravingRules.rules : (EngravingRules.rules = new EngravingRules());
+    public static set FixStafflineBoundingBox(value: boolean) {
+        EngravingRules.fixStafflineBoundingBox = value;
     }
     public get SamplingUnit(): number {
         return this.samplingUnit;
@@ -1558,13 +1561,6 @@ export class EngravingRules {
     public set FingeringInsideStafflines(value: boolean) {
         this.fingeringInsideStafflines = value;
     }
-    public set FixStafflineBoundingBox(value: boolean) { // TODO temporary workaround
-        this.fixStafflineBoundingBox = value;
-    }
-    public get FixStafflineBoundingBox(): boolean {
-        return this.fixStafflineBoundingBox;
-    }
-
     public get PageFormat(): PageFormat {
         return this.pageFormat;
     }

+ 7 - 2
src/MusicalScore/Graphical/GraphicalChordSymbolContainer.ts

@@ -5,15 +5,20 @@ import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
 import {BoundingBox} from "./BoundingBox";
 import {GraphicalObject} from "./GraphicalObject";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
+import {EngravingRules} from "./EngravingRules";
 
 export class GraphicalChordSymbolContainer extends GraphicalObject {
     private chordSymbolContainer: ChordSymbolContainer;
     private graphicalLabel: GraphicalLabel;
-    constructor(chordSymbolContainer: ChordSymbolContainer, parent: BoundingBox, textHeight: number, transposeHalftones: number) {
+    private rules: EngravingRules;
+
+    constructor(chordSymbolContainer: ChordSymbolContainer, parent: BoundingBox, textHeight: number,
+                transposeHalftones: number, rules: EngravingRules) {
         super();
         this.chordSymbolContainer = chordSymbolContainer;
         this.boundingBox = new BoundingBox(this, parent);
         this.calculateLabel(textHeight, transposeHalftones);
+        this.rules = rules;
     }
     public get GetChordSymbolContainer(): ChordSymbolContainer {
         return this.chordSymbolContainer;
@@ -23,7 +28,7 @@ export class GraphicalChordSymbolContainer extends GraphicalObject {
     }
     private calculateLabel(textHeight: number, transposeHalftones: number): void {
         const text: string = ChordSymbolContainer.calculateChordText(this.chordSymbolContainer, transposeHalftones);
-        this.graphicalLabel = new GraphicalLabel(new Label(text), textHeight, TextAlignmentEnum.CenterBottom, this.boundingBox);
+        this.graphicalLabel = new GraphicalLabel(new Label(text), textHeight, TextAlignmentEnum.CenterBottom, this.rules, this.boundingBox);
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
     }
 }

+ 5 - 2
src/MusicalScore/Graphical/GraphicalLabel.ts

@@ -10,6 +10,7 @@ import { MusicSheetCalculator } from "./MusicSheetCalculator";
  */
 export class GraphicalLabel extends Clickable {
     private label: Label;
+    private rules: EngravingRules;
 
     /**
      * Creates a new GraphicalLabel from a Label
@@ -18,12 +19,14 @@ export class GraphicalLabel extends Clickable {
      * @param alignment Alignement like left, right, top, ...
      * @param parent Parent Bounding Box where the label is attached to
      */
-    constructor(label: Label, textHeight: number, alignment: TextAlignmentEnum, parent: BoundingBox = undefined) {
+    constructor(label: Label, textHeight: number, alignment: TextAlignmentEnum, rules: EngravingRules,
+                parent: BoundingBox = undefined, ) {
         super();
         this.label = label;
         this.boundingBox = new BoundingBox(this, parent);
         this.label.fontHeight = textHeight;
         this.label.textAlignment = alignment;
+        this.rules = rules;
     }
 
     public get Label(): Label {
@@ -41,7 +44,7 @@ export class GraphicalLabel extends Clickable {
         if (this.Label.text.trim() === "") {
             return;
         }
-        const labelMarginBorderFactor: number = EngravingRules.Rules.LabelMarginBorderFactor;
+        const labelMarginBorderFactor: number = this.rules?.LabelMarginBorderFactor ?? 0.1;
 
         const widthToHeightRatio: number =
             MusicSheetCalculator.TextMeasurer.computeTextWidthToHeightRatio(this.Label.text, this.Label.font, this.Label.fontStyle);

+ 3 - 3
src/MusicalScore/Graphical/GraphicalLyricEntry.ts

@@ -4,7 +4,6 @@ import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
 import {Label} from "../Label";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
-import { EngravingRules } from "./EngravingRules";
 import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
 
 /**
@@ -19,7 +18,7 @@ export class GraphicalLyricEntry {
     constructor(lyricsEntry: LyricsEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricsHeight: number, staffHeight: number) {
         this.lyricsEntry = lyricsEntry;
         this.graphicalStaffEntry = graphicalStaffEntry;
-        const lyricsTextAlignment: TextAlignmentEnum = EngravingRules.Rules.LyricsAlignmentStandard;
+        const lyricsTextAlignment: TextAlignmentEnum = graphicalStaffEntry.parentMeasure.parentSourceMeasure.Rules.LyricsAlignmentStandard;
         // for small notes with long text, use center alignment
         // TODO use this, fix center+left alignment combination spacing
         if (lyricsEntry.Text.length >= 4
@@ -31,7 +30,8 @@ export class GraphicalLyricEntry {
             new Label(lyricsEntry.Text),
             lyricsHeight,
             lyricsTextAlignment,
-            graphicalStaffEntry.PositionAndShape
+            this.graphicalStaffEntry.parentMeasure.parentSourceMeasure.Rules,
+            graphicalStaffEntry.PositionAndShape,
         );
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0, staffHeight);
         if (lyricsTextAlignment === TextAlignmentEnum.LeftBottom) {

+ 2 - 3
src/MusicalScore/Graphical/GraphicalMusicSheet.ts

@@ -23,7 +23,6 @@ import {CollectionUtil} from "../../Util/CollectionUtil";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
 import {SelectionEndSymbol} from "./SelectionEndSymbol";
 import {OutlineAndFillStyleEnum} from "./DrawingEnums";
-import {EngravingRules} from "./EngravingRules";
 
 /**
  * The graphical counterpart of a [[MusicSheet]]
@@ -165,7 +164,7 @@ export class GraphicalMusicSheet {
      */
     public static transformRelativeToAbsolutePosition(graphicalMusicSheet: GraphicalMusicSheet): void {
         for (let i: number = 0; i < graphicalMusicSheet.MusicPages.length; i++) {
-            const pageAbsolute: PointF2D = graphicalMusicSheet.MusicPages[i].setMusicPageAbsolutePosition(i, graphicalMusicSheet.ParentMusicSheet.rules);
+            const pageAbsolute: PointF2D = graphicalMusicSheet.MusicPages[i].setMusicPageAbsolutePosition(i, graphicalMusicSheet.ParentMusicSheet.Rules);
             const page: GraphicalMusicPage = graphicalMusicSheet.MusicPages[i];
             page.PositionAndShape.calculateAbsolutePositionsRecursive(pageAbsolute.x, pageAbsolute.y);
         }
@@ -463,7 +462,7 @@ export class GraphicalMusicSheet {
     public getLastGraphicalMeasureFromIndex(staffIndex: number, lastRendered: boolean = true): GraphicalMeasure {
         let measureIndex: number = this.measureList.length - 1;
         if (lastRendered) {
-            measureIndex = EngravingRules.Rules.MaxMeasureToDrawIndex;
+            measureIndex = this.musicSheet.Rules.MaxMeasureToDrawIndex;
         }
         return this.measureList[measureIndex][staffIndex];
     }

+ 4 - 0
src/MusicalScore/Graphical/GraphicalVoiceEntry.ts

@@ -4,6 +4,7 @@ import { BoundingBox } from "./BoundingBox";
 import { GraphicalNote } from "./GraphicalNote";
 import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
 import { OctaveEnum } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { EngravingRules } from ".";
 
 /**
  * The graphical counterpart of a [[VoiceEntry]].
@@ -15,6 +16,8 @@ export class GraphicalVoiceEntry extends GraphicalObject {
         this.parentStaffEntry = parentStaffEntry;
         this.PositionAndShape = new BoundingBox(this, parentStaffEntry ? parentStaffEntry.PositionAndShape : undefined, true);
         this.notes = [];
+        this.rules = parentStaffEntry ?
+                        parentStaffEntry.parentMeasure.parentSourceMeasure.Rules : new EngravingRules();
     }
 
     public parentVoiceEntry: VoiceEntry;
@@ -22,6 +25,7 @@ export class GraphicalVoiceEntry extends GraphicalObject {
     public notes: GraphicalNote[];
     /** Contains octave shifts affecting this voice entry, caused by octave brackets. */
     public octaveShiftValue: OctaveEnum;
+    protected rules: EngravingRules;
 
     /** Sort this entry's notes by pitch.
      * Notes need to be sorted for Vexflow StaveNote creation.

+ 48 - 42
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -108,7 +108,7 @@ export abstract class MusicSheetCalculator {
 
     public initialize(graphicalMusicSheet: GraphicalMusicSheet): void {
         this.graphicalMusicSheet = graphicalMusicSheet;
-        this.rules = graphicalMusicSheet.ParentMusicSheet.rules;
+        this.rules = graphicalMusicSheet.ParentMusicSheet.Rules;
         this.prepareGraphicalMusicSheet();
         //this.calculate();
     }
@@ -350,7 +350,7 @@ export abstract class MusicSheetCalculator {
     private calculateSingleMeasureNumberPlacement(measure: GraphicalMeasure, staffLine: StaffLine, musicSystem: MusicSystem): void {
         const labelNumber: string = measure.MeasureNumber.toString();
         const graphicalLabel: GraphicalLabel = new GraphicalLabel(new Label(labelNumber), this.rules.MeasureNumberLabelHeight,
-                                                                  TextAlignmentEnum.LeftBottom);
+                                                                  TextAlignmentEnum.LeftBottom, this.rules);
 
         const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
 
@@ -581,15 +581,15 @@ export abstract class MusicSheetCalculator {
         if (allMeasures === undefined) {
             return;
         }
-        if (EngravingRules.Rules.MinMeasureToDrawIndex > allMeasures.length - 1) {
+        if (this.rules.MinMeasureToDrawIndex > allMeasures.length - 1) {
             log.debug("minimum measure to draw index out of range. resetting min measure index to limit.");
-            EngravingRules.Rules.MinMeasureToDrawIndex = allMeasures.length - 1;
+            this.rules.MinMeasureToDrawIndex = allMeasures.length - 1;
         }
 
         // visible 2D-MeasureList
         const visibleMeasureList: GraphicalMeasure[][] = [];
-        for (let idx: number = EngravingRules.Rules.MinMeasureToDrawIndex, len: number = allMeasures.length;
-            idx < len && idx <= EngravingRules.Rules.MaxMeasureToDrawIndex; ++idx) {
+        for (let idx: number = this.rules.MinMeasureToDrawIndex, len: number = allMeasures.length;
+            idx < len && idx <= this.rules.MaxMeasureToDrawIndex; ++idx) {
             const graphicalMeasures: GraphicalMeasure[] = allMeasures[idx];
             const visiblegraphicalMeasures: GraphicalMeasure[] = [];
             for (let idx2: number = 0, len2: number = graphicalMeasures.length; idx2 < len2; ++idx2) {
@@ -598,7 +598,7 @@ export abstract class MusicSheetCalculator {
                 if (graphicalMeasure.isVisible()) {
                     visiblegraphicalMeasures.push(graphicalMeasure);
 
-                    if (EngravingRules.Rules.ColoringEnabled) {
+                    if (this.rules.ColoringEnabled) {
                         // (re-)color notes
                         for (const staffEntry of graphicalMeasure.staffEntries) {
                             for (const gve of staffEntry.graphicalVoiceEntries) {
@@ -641,7 +641,7 @@ export abstract class MusicSheetCalculator {
             this.optimizeRestPlacement();
             // possible Displacement of RestNotes
             this.calculateStaffEntryArticulationMarks();
-            if (EngravingRules.Rules.RenderSlurs) { // technically we should separate slurs and ties, but shouldn't be relevant for now
+            if (this.rules.RenderSlurs) { // technically we should separate slurs and ties, but shouldn't be relevant for now
                 // calculate Ties
                 this.calculateTieCurves();
             }
@@ -653,14 +653,14 @@ export abstract class MusicSheetCalculator {
         this.calculateTupletNumbers();
 
         // calculate MeasureNumbers
-        if (EngravingRules.Rules.RenderMeasureNumbers) {
+        if (this.rules.RenderMeasureNumbers) {
             for (let idx: number = 0, len: number = this.musicSystems.length; idx < len; ++idx) {
                 const musicSystem: MusicSystem = this.musicSystems[idx];
                 this.calculateMeasureNumberPlacement(musicSystem);
             }
         }
         // calculate Slurs
-        if (!this.leadSheet && EngravingRules.Rules.RenderSlurs) {
+        if (!this.leadSheet && this.rules.RenderSlurs) {
             this.calculateSlurs();
         }
         // calculate StaffEntry Ornaments
@@ -1237,7 +1237,7 @@ export abstract class MusicSheetCalculator {
         label.fontHeight = fontHeight;
 
         // TODO_RR: TextHeight from first Entry
-        const graphLabel: GraphicalLabel = new GraphicalLabel(label, fontHeight, label.textAlignment, staffLine.PositionAndShape);
+        const graphLabel: GraphicalLabel = new GraphicalLabel(label, fontHeight, label.textAlignment, this.rules, staffLine.PositionAndShape);
         graphLabel.Label.fontStyle = style;
         const marginFactor: number = 1.1;
 
@@ -1291,7 +1291,7 @@ export abstract class MusicSheetCalculator {
 
         if (multiTempoExpression.ContinuousTempo || multiTempoExpression.InstantaneousTempo) {
             // TempoExpressions always on the first visible System's StaffLine // TODO is it though?
-            if (EngravingRules.Rules.MinMeasureToDrawIndex > 0) {
+            if (this.rules.MinMeasureToDrawIndex > 0) {
                 return; // assuming that the tempo is always in measure 1 (idx 0), adding the expression causes issues when we don't draw measure 1
             }
             let staffLine: StaffLine = measures[0].ParentStaffLine;
@@ -1332,7 +1332,7 @@ export abstract class MusicSheetCalculator {
                         const lastInstruction: AbstractGraphicalInstruction = firstInstructionEntry.GraphicalInstructions.last();
                         relative.x = lastInstruction.PositionAndShape.RelativePosition.x;
                     }
-                    if (EngravingRules.Rules.CompactMode) {
+                    if (this.rules.CompactMode) {
                         relative.x = staffLine.PositionAndShape.RelativePosition.x +
                             staffLine.Measures[0].PositionAndShape.RelativePosition.x;
                     }
@@ -1342,7 +1342,7 @@ export abstract class MusicSheetCalculator {
             // const addAtLastList: GraphicalObject[] = [];
             for (const entry of multiTempoExpression.EntriesList) {
                 let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
-                if (EngravingRules.Rules.CompactMode) {
+                if (this.rules.CompactMode) {
                     textAlignment = TextAlignmentEnum.LeftBottom;
                 }
                 const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
@@ -1350,7 +1350,7 @@ export abstract class MusicSheetCalculator {
                                                                        entry.label,
                                                                        multiTempoExpression.getFontstyleOfFirstEntry(),
                                                                        entry.Expression.Placement,
-                                                                       EngravingRules.Rules.UnknownTextHeight,
+                                                                       this.rules.UnknownTextHeight,
                                                                        textAlignment);
 
                 if (entry.Expression instanceof InstantaneousTempoExpression) {
@@ -1452,7 +1452,7 @@ export abstract class MusicSheetCalculator {
                                octaveShiftValue: OctaveEnum, linkedNotes: Note[] = undefined,
                                sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
         if (voiceEntry.StemDirectionXml !== StemDirectionType.Undefined &&
-            EngravingRules.Rules.SetWantedStemDirectionByXml &&
+            this.rules.SetWantedStemDirectionByXml &&
             voiceEntry.StemDirectionXml !== undefined) {
                 voiceEntry.WantedStemDirection = voiceEntry.StemDirectionXml;
         } else {
@@ -1556,16 +1556,16 @@ export abstract class MusicSheetCalculator {
         for (const instrument of this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
             if (instrument.Voices.length > 0 && instrument.Voices[0].Visible) {
                 let renderedLabel: Label = instrument.NameLabel;
-                if (!EngravingRules.Rules.RenderPartNames) {
+                if (!this.rules.RenderPartNames) {
                     renderedLabel = new Label("", renderedLabel.textAlignment, renderedLabel.font);
                 }
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                    renderedLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentEnum.LeftCenter);
+                    renderedLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.rules);
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 maxLabelLength = Math.max(maxLabelLength, graphicalLabel.PositionAndShape.MarginSize.width);
             }
         }
-        if (!EngravingRules.Rules.RenderPartNames) {
+        if (!this.rules.RenderPartNames) {
             return 0;
         }
         return maxLabelLength;
@@ -1573,37 +1573,40 @@ export abstract class MusicSheetCalculator {
 
     protected calculateSheetLabelBoundingBoxes(): void {
         const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
-        const defaultColorTitle: string = EngravingRules.Rules.DefaultColorTitle; // can be undefined => black
-        if (musicSheet.Title !== undefined && EngravingRules.Rules.RenderTitle) {
-            const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom);
+        const defaultColorTitle: string = this.rules.DefaultColorTitle; // can be undefined => black
+        if (musicSheet.Title !== undefined && this.rules.RenderTitle) {
+            const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom, this.rules);
             title.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Title = title;
             title.setLabelPositionAndShapeBorders();
-        } else if (!EngravingRules.Rules.RenderTitle) {
+        } else if (!this.rules.RenderTitle) {
             this.graphicalMusicSheet.Title = undefined; // clear label if rendering it was disabled after last render
         }
-        if (musicSheet.Subtitle !== undefined && EngravingRules.Rules.RenderSubtitle) {
-            const subtitle: GraphicalLabel = new GraphicalLabel(musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter);
+        if (musicSheet.Subtitle !== undefined && this.rules.RenderSubtitle) {
+            const subtitle: GraphicalLabel = new GraphicalLabel(
+                musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter, this.rules);
             subtitle.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Subtitle = subtitle;
             subtitle.setLabelPositionAndShapeBorders();
-        } else if (!EngravingRules.Rules.RenderSubtitle) {
+        } else if (!this.rules.RenderSubtitle) {
             this.graphicalMusicSheet.Subtitle = undefined;
         }
-        if (musicSheet.Composer !== undefined && EngravingRules.Rules.RenderComposer) {
-            const composer: GraphicalLabel = new GraphicalLabel(musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter);
+        if (musicSheet.Composer !== undefined && this.rules.RenderComposer) {
+            const composer: GraphicalLabel = new GraphicalLabel(
+                musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter, this.rules);
             composer.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Composer = composer;
             composer.setLabelPositionAndShapeBorders();
-        } else if (!EngravingRules.Rules.RenderComposer) {
+        } else if (!this.rules.RenderComposer) {
             this.graphicalMusicSheet.Composer = undefined;
         }
-        if (musicSheet.Lyricist !== undefined && EngravingRules.Rules.RenderLyricist) {
-            const lyricist: GraphicalLabel = new GraphicalLabel(musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter);
+        if (musicSheet.Lyricist !== undefined && this.rules.RenderLyricist) {
+            const lyricist: GraphicalLabel = new GraphicalLabel(
+                musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter, this.rules);
             lyricist.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Lyricist = lyricist;
             lyricist.setLabelPositionAndShapeBorders();
-        } else if (!EngravingRules.Rules.RenderLyricist) {
+        } else if (!this.rules.RenderLyricist) {
             this.graphicalMusicSheet.Lyricist = undefined;
         }
     }
@@ -1725,7 +1728,7 @@ export abstract class MusicSheetCalculator {
     }
 
     protected calculatePageLabels(page: GraphicalMusicPage): void {
-        if (EngravingRules.Rules.RenderSingleHorizontalStaffline) {
+        if (this.rules.RenderSingleHorizontalStaffline) {
             page.PositionAndShape.BorderRight = page.PositionAndShape.Size.width;
             page.PositionAndShape.calculateBoundingBox();
             this.graphicalMusicSheet.ParentMusicSheet.pageWidth = page.PositionAndShape.Size.width;
@@ -2045,7 +2048,7 @@ export abstract class MusicSheetCalculator {
         }
         // if there are no staffEntries in this measure, create a rest for the whole measure:
         // check OSMDOptions.fillEmptyMeasuresWithWholeRest
-        if (EngravingRules.Rules.FillEmptyMeasuresWithWholeRest >= 1) { // fill measures with no notes given with whole rests, visible (1) or invisible (2)
+        if (this.rules.FillEmptyMeasuresWithWholeRest >= 1) { // fill measures with no notes given with whole rests, visible (1) or invisible (2)
             if (measure.staffEntries.length === 0) {
                 const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(
                     new VerticalSourceStaffEntryContainer(measure.parentSourceMeasure,
@@ -2054,7 +2057,7 @@ export abstract class MusicSheetCalculator {
                     staff);
                 const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
                 const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined);
-                note.PrintObject = EngravingRules.Rules.FillEmptyMeasuresWithWholeRest === FillEmptyMeasuresWithWholeRests.YesVisible;
+                note.PrintObject = this.rules.FillEmptyMeasuresWithWholeRest === FillEmptyMeasuresWithWholeRests.YesVisible;
                   // don't display whole rest that wasn't given in XML, only for layout/voice completion
                 voiceEntry.Notes.push(note);
                 const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
@@ -2446,7 +2449,8 @@ export abstract class MusicSheetCalculator {
      * @param {number} y
      */
     private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
-        const dash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
+        const dash: GraphicalLabel = new GraphicalLabel(
+            new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
         dash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(dash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2560,7 +2564,8 @@ export abstract class MusicSheetCalculator {
      * @returns {number}
      */
     private calculateRightAndLeftDashesForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): number {
-        const leftDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
+        const leftDash: GraphicalLabel = new GraphicalLabel(
+            new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
         leftDash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(leftDash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2569,7 +2574,8 @@ export abstract class MusicSheetCalculator {
         leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
         const leftDashRelative: PointF2D = new PointF2D(startX, y);
         leftDash.PositionAndShape.RelativePosition = leftDashRelative;
-        const rightDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
+        const rightDash: GraphicalLabel = new GraphicalLabel(
+            new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
         rightDash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(rightDash);
         rightDash.PositionAndShape.Parent = staffLine.PositionAndShape;
@@ -2579,8 +2585,8 @@ export abstract class MusicSheetCalculator {
     }
 
     private calculateDynamicExpressions(): void {
-        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, EngravingRules.Rules.MaxMeasureToDrawIndex);
-        const minIndex: number = Math.min(EngravingRules.Rules.MinMeasureToDrawIndex, this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length);
+        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, this.rules.MaxMeasureToDrawIndex);
+        const minIndex: number = Math.min(this.rules.MinMeasureToDrawIndex, this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length);
         for (let i: number = minIndex; i <= maxIndex; i++) {
             const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
             for (let j: number = 0; j < sourceMeasure.StaffLinkedExpressions.length; j++) {
@@ -2661,8 +2667,8 @@ export abstract class MusicSheetCalculator {
     }
 
     private calculateTempoExpressions(): void {
-        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, EngravingRules.Rules.MaxMeasureToDrawIndex);
-        const minIndex: number = EngravingRules.Rules.MinMeasureToDrawIndex;
+        const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, this.rules.MaxMeasureToDrawIndex);
+        const minIndex: number = this.rules.MinMeasureToDrawIndex;
         for (let i: number = minIndex; i <= maxIndex; i++) {
             const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
             for (let j: number = 0; j < sourceMeasure.TempoExpressions.length; j++) {

+ 4 - 4
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -295,7 +295,7 @@ export abstract class MusicSheetDrawer {
         for (const staffLine of musicSystem.StaffLines) {
             this.drawStaffLine(staffLine);
 
-            if (EngravingRules.Rules.RenderLyrics) {
+            if (this.rules.RenderLyrics) {
                 // draw lyric dashes
                 if (staffLine.LyricsDashes.length > 0) {
                     this.drawDashes(staffLine.LyricsDashes);
@@ -351,7 +351,7 @@ export abstract class MusicSheetDrawer {
             this.drawMeasure(measure);
         }
 
-        if (EngravingRules.Rules.RenderLyrics) {
+        if (this.rules.RenderLyrics) {
             if (staffLine.LyricsDashes.length > 0) {
                 this.drawDashes(staffLine.LyricsDashes);
             }
@@ -379,7 +379,7 @@ export abstract class MusicSheetDrawer {
             lyricLine.End.y += staffLine.PositionAndShape.AbsolutePosition.y;
             lyricLine.Start.x += staffLine.PositionAndShape.AbsolutePosition.x;
             lyricLine.End.x += staffLine.PositionAndShape.AbsolutePosition.x;
-            this.drawGraphicalLine(lyricLine, EngravingRules.Rules.LyricUnderscoreLineWidth);
+            this.drawGraphicalLine(lyricLine, this.rules.LyricUnderscoreLineWidth);
         });
     }
 
@@ -515,7 +515,7 @@ export abstract class MusicSheetDrawer {
 
             tmpRect = this.applyScreenTransformationForRect(tmpRect);
             this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, 0.5);
-            this.renderLabel(new GraphicalLabel(new Label(dataObjectString), 0.8, TextAlignmentEnum.CenterCenter),
+            this.renderLabel(new GraphicalLabel(new Label(dataObjectString), 0.8, TextAlignmentEnum.CenterCenter, this.rules),
                              layer, tmpRect.width, tmpRect.height, tmpRect.height, new PointF2D(tmpRect.x, tmpRect.y + 12));
         }
         layer++;

+ 4 - 3
src/MusicalScore/Graphical/MusicSystem.ts

@@ -26,6 +26,7 @@ import { Label } from "../Label";
  */
 export abstract class MusicSystem extends GraphicalObject {
     public needsToBeRedrawn: boolean = true;
+    public rules: EngravingRules;
     protected parent: GraphicalMusicPage;
     protected id: number;
     protected staffLines: StaffLine[] = [];
@@ -292,12 +293,12 @@ export abstract class MusicSystem extends GraphicalObject {
             let instrNameLabel: Label;
             if (isFirstSystem) {
                 instrNameLabel = instrument.NameLabel;
-                if (!EngravingRules.Rules.RenderPartNames) {
+                if (!this.rules.RenderPartNames) {
                     instrNameLabel = new Label("", instrument.NameLabel.textAlignment, instrument.NameLabel.font);
                     systemLabelsRightMargin = 0; // might affect lyricist/tempo placement. but without this there's still some extra x-spacing.
                 }
             } else {
-                if (!EngravingRules.Rules.RenderPartAbbreviations
+                if (!this.rules.RenderPartAbbreviations
                     // don't render part abbreviations if there's only one instrument/part (could be an option in the future)
                     || this.staffLines.length === 1
                     || !instrument.PartAbbreviation
@@ -309,7 +310,7 @@ export abstract class MusicSystem extends GraphicalObject {
                 instrNameLabel = new Label(labelText, instrument.NameLabel.textAlignment, instrument.NameLabel.font);
             }
             const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                instrNameLabel, instrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.boundingBox
+                instrNameLabel, instrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.rules, this.boundingBox
             );
             graphicalLabel.setLabelPositionAndShapeBorders();
             this.labels.setValue(instrument, graphicalLabel);

+ 7 - 7
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -44,7 +44,7 @@ export class MusicSystemBuilder {
         graphicalMusicSheet: GraphicalMusicSheet, measureList: GraphicalMeasure[][], numberOfStaffLines: number): void {
         this.leadSheet = graphicalMusicSheet.LeadSheet;
         this.graphicalMusicSheet = graphicalMusicSheet;
-        this.rules = this.graphicalMusicSheet.ParentMusicSheet.rules;
+        this.rules = this.graphicalMusicSheet.ParentMusicSheet.Rules;
         this.measureList = measureList;
         this.numberOfVisibleStaffLines = numberOfStaffLines;
         this.activeRhythm = new Array(this.numberOfVisibleStaffLines);
@@ -228,7 +228,7 @@ export class MusicSystemBuilder {
      * @returns {MusicSystem}
      */
     private initMusicSystem(): MusicSystem {
-        const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.globalSystemIndex++);
+        const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.globalSystemIndex++, this.rules);
         this.musicSystems.push(musicSystem);
         this.layoutSystemStaves(musicSystem);
         musicSystem.createMusicSystemLabel(
@@ -311,7 +311,7 @@ export class MusicSystemBuilder {
             const boundingBox: BoundingBox = staffLine.PositionAndShape;
             const relativePosition: PointF2D = new PointF2D();
             if (musicSystem === this.musicSystems[0] &&
-                !EngravingRules.Rules.CompactMode) {
+                !this.rules.CompactMode) {
                 relativePosition.x = this.rules.FirstSystemMargin;
                 boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
             } else {
@@ -980,15 +980,15 @@ export class MusicSystemBuilder {
             if (currentPage.MusicSystems.length === 0) {
                 // first system on the page:
                 this.addSystemToPage(currentPage, currentSystem);
-                if (EngravingRules.Rules.CompactMode) {
-                    currentYPosition = EngravingRules.Rules.PageTopMarginNarrow;
+                if (this.rules.CompactMode) {
+                    currentYPosition = this.rules.PageTopMarginNarrow;
                 } else {
-                    currentYPosition = EngravingRules.Rules.PageTopMargin;
+                    currentYPosition = this.rules.PageTopMargin;
                 }
 
                 // Handle Title for first System on the first page
                 if (this.graphicalMusicSheet.MusicPages.length === 1 &&
-                    EngravingRules.Rules.RenderTitle) {
+                    this.rules.RenderTitle) {
                     currentYPosition +=   this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
                                             this.rules.TitleBottomDistance;
                 }

+ 2 - 2
src/MusicalScore/Graphical/SkyBottomLineCalculator.ts

@@ -30,7 +30,7 @@ export class SkyBottomLineCalculator {
      */
     constructor(staffLineParent: StaffLine) {
         this.mStaffLineParent = staffLineParent;
-        this.mRules = EngravingRules.Rules;
+        this.mRules = staffLineParent.ParentMusicSystem.rules;
     }
 
     /**
@@ -43,7 +43,7 @@ export class SkyBottomLineCalculator {
         this.mBottomLine = [];
 
         // Create a temporary canvas outside the DOM to draw the measure in.
-        const tmpCanvas: any = new CanvasVexFlowBackend();
+        const tmpCanvas: any = new CanvasVexFlowBackend(this.StaffLineParent.ParentMusicSystem.rules);
         // search through all Measures
         for (const measure of this.StaffLineParent.Measures as VexFlowMeasure[]) {
             // must calculate first AbsolutePositions

+ 2 - 3
src/MusicalScore/Graphical/StaffLine.ts

@@ -13,7 +13,6 @@ import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
 import { GraphicalOctaveShift } from "./GraphicalOctaveShift";
 import { GraphicalSlur } from "./GraphicalSlur";
 import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
-import { EngravingRules } from "./EngravingRules";
 
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
@@ -41,9 +40,9 @@ export abstract class StaffLine extends GraphicalObject {
         this.parentStaff = parentStaff;
         this.boundingBox = new BoundingBox(this, parentSystem.PositionAndShape);
         this.skyBottomLine = new SkyBottomLineCalculator(this);
-        this.staffHeight = EngravingRules.Rules.StaffHeight;
+        this.staffHeight = this.parentMusicSystem.rules.StaffHeight;
         if (this.parentStaff.isTab) {
-            this.staffHeight = EngravingRules.Rules.TabStaffHeight;
+            this.staffHeight = this.parentMusicSystem.rules.TabStaffHeight;
         }
     }
 

+ 2 - 1
src/MusicalScore/Graphical/VexFlow/AlignmentManager.ts

@@ -7,10 +7,11 @@ import { EngravingRules } from "../EngravingRules";
 
 export class AlignmentManager {
     private parentStaffline: StaffLine;
-    private rules: EngravingRules = EngravingRules.Rules;
+    private rules: EngravingRules;
 
     constructor(staffline: StaffLine) {
         this.parentStaffline = staffline;
+        this.rules = this.parentStaffline.ParentMusicSystem.rules;
     }
 
     public alignDynamicExpressions(): void {

+ 9 - 3
src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts

@@ -11,6 +11,11 @@ import {EngravingRules} from "../EngravingRules";
 import {GraphicalMusicPage} from "../GraphicalMusicPage";
 
 export class CanvasVexFlowBackend extends VexFlowBackend {
+    constructor(rules: EngravingRules) {
+        super();
+        this.rules = rules;
+    }
+
     public getVexflowBackendType(): Vex.Flow.Renderer.Backends {
         return Vex.Flow.Renderer.Backends.CANVAS;
     }
@@ -61,10 +66,10 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         (<any>this.ctx).clearRect(0, 0, (<any>this.canvas).width, (<any>this.canvas).height);
 
         // set background color if not transparent
-        if (EngravingRules.Rules.PageBackgroundColor !== undefined) {
+        if (this.rules.PageBackgroundColor !== undefined) {
             this.ctx.save();
             // note that this will hide the cursor
-            this.ctx.setFillStyle(EngravingRules.Rules.PageBackgroundColor);
+            this.ctx.setFillStyle(this.rules.PageBackgroundColor);
             this.ctx.fillRect(0, 0, (this.canvas as any).width, (this.canvas as any).height);
             this.ctx.restore();
         }
@@ -84,7 +89,8 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.CanvasRenderingCtx.font = VexFlowConverter.font(
             fontHeight,
             fontStyle,
-            font
+            font,
+            this.rules
         );
         this.CanvasRenderingCtx.fillStyle = color;
         this.CanvasRenderingCtx.strokeStyle = color;

+ 8 - 3
src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts

@@ -13,6 +13,11 @@ export class SvgVexFlowBackend extends VexFlowBackend {
 
     private ctx: Vex.Flow.SVGContext;
 
+    constructor(rules: EngravingRules) {
+        super();
+        this.rules = rules;
+    }
+
     public getVexflowBackendType(): Vex.Flow.Renderer.Backends {
         return Vex.Flow.Renderer.Backends.SVG;
     }
@@ -52,10 +57,10 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         }
 
         // set background color if not transparent
-        if (EngravingRules.Rules.PageBackgroundColor !== undefined) {
+        if (this.rules.PageBackgroundColor !== undefined) {
             this.ctx.save();
             // note that this will hide the cursor
-            this.ctx.setFillStyle(EngravingRules.Rules.PageBackgroundColor);
+            this.ctx.setFillStyle(this.rules.PageBackgroundColor);
 
             this.ctx.fillRect(0, 0, this.canvas.offsetWidth, this.canvas.offsetHeight);
             this.ctx.restore();
@@ -77,7 +82,7 @@ export class SvgVexFlowBackend extends VexFlowBackend {
             this.ctx.attributes.fill = color;
             this.ctx.attributes.stroke = color;
         }
-        this.ctx.setFont(EngravingRules.Rules.DefaultFontFamily, fontHeight, VexFlowConverter.fontStyle(fontStyle));
+        this.ctx.setFont(this.rules.DefaultFontFamily, fontHeight, VexFlowConverter.fontStyle(fontStyle));
         // font size is set by VexFlow in `pt`. This overwrites the font so it's set to px instead
         this.ctx.attributes["font-size"] = `${fontHeight}px`;
         this.ctx.state["font-size"] = `${fontHeight}px`;

+ 2 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts

@@ -3,7 +3,7 @@ import {FontStyles} from "../../../Common/Enums/FontStyles";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {RectangleF2D} from "../../../Common/DataObjects/RectangleF2D";
 import {PointF2D} from "../../../Common/DataObjects/PointF2D";
-import {GraphicalMusicPage} from "..";
+import {GraphicalMusicPage, EngravingRules} from "..";
 import {BackendType} from "../../../OpenSheetMusicDisplay";
 
 export class VexFlowBackends {
@@ -18,6 +18,7 @@ export abstract class VexFlowBackend {
 
   /** The GraphicalMusicPage the backend is drawing from. Each backend only renders one GraphicalMusicPage, to which the coordinates are relative. */
   public graphicalMusicPage: GraphicalMusicPage;
+  protected rules: EngravingRules;
 
   public abstract initialize(container: HTMLElement): void;
 

+ 1 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowContinuousDynamicExpression.ts

@@ -16,6 +16,7 @@ export class VexFlowContinuousDynamicExpression extends GraphicalContinuousDynam
             this.label = new GraphicalLabel(new Label(continuousDynamic.Label),
                                             textHeight ? textHeight : this.rules.ContinuousDynamicTextHeight,
                                             TextAlignmentEnum.LeftCenter,
+                                            this.rules,
                                             this.PositionAndShape);
 
             this.label.Label.fontStyle = FontStyles.Italic;

+ 8 - 6
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -168,6 +168,7 @@ export class VexFlowConverter {
         } */
         // VexFlow needs the notes ordered vertically in the other direction:
         const notes: GraphicalNote[] = gve.notes.reverse();
+        const rules: EngravingRules = gve.parentStaffEntry.parentMeasure.parentSourceMeasure.Rules;
 
         const baseNote: GraphicalNote = notes[0];
         let keys: string[] = [];
@@ -203,7 +204,7 @@ export class VexFlowConverter {
                     // https://github.com/0xfe/vexflow/issues/579 The author reports that he needs to add some negative x shift
                     // if the measure has no modifiers.
                     alignCenter = true;
-                    xShift = EngravingRules.Rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
+                    xShift = rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
                     // affects VexFlowStaffEntry.calculateXPosition()
                 }
                 break;
@@ -260,8 +261,8 @@ export class VexFlowConverter {
             vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
         }
 
-        if (EngravingRules.Rules.ColoringEnabled) {
-            const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
+        if (rules.ColoringEnabled) {
+            const defaultColorStem: string = rules.DefaultColorStem;
             let stemColor: string = gve.parentVoiceEntry.StemColor;
             if (!stemColor && defaultColorStem) {
                 stemColor = defaultColorStem;
@@ -271,7 +272,7 @@ export class VexFlowConverter {
             if (stemColor) {
                 gve.parentVoiceEntry.StemColor = stemColor;
                 vfnote.setStemStyle(stemStyle);
-                if (vfnote.flag && EngravingRules.Rules.ColorFlags) {
+                if (vfnote.flag && rules.ColorFlags) {
                     vfnote.setFlagStyle(stemStyle);
                 }
             }
@@ -705,10 +706,11 @@ export class VexFlowConverter {
      * @param font
      * @returns {string}
      */
-    public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular, font: Fonts = Fonts.TimesNewRoman): string {
+    public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular,
+                       font: Fonts = Fonts.TimesNewRoman, rules: EngravingRules): string {
         let style: string = "normal";
         let weight: string = "normal";
-        const family: string = "'" + EngravingRules.Rules.DefaultFontFamily + "'"; // default "'Times New Roman'"
+        const family: string = "'" + rules.DefaultFontFamily + "'"; // default "'Times New Roman'"
 
         switch (fontStyle) {
             case FontStyles.Bold:

+ 9 - 6
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -36,8 +36,8 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      * @param systemIndex
      * @returns {VexFlowMusicSystem}
      */
-    public createMusicSystem(systemIndex: number): MusicSystem {
-        return new VexFlowMusicSystem(systemIndex);
+    public createMusicSystem(systemIndex: number, rules: EngravingRules): MusicSystem {
+        return new VexFlowMusicSystem(systemIndex, rules);
     }
 
     /**
@@ -166,16 +166,19 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      * @param transposeHalftones
      */
     public createChordSymbols(sourceStaffEntry: SourceStaffEntry, graphicalStaffEntry: GraphicalStaffEntry, transposeHalftones: number): void {
+        const rules: EngravingRules = graphicalStaffEntry.parentMeasure.parentSourceMeasure.Rules;
         let xShift: number = 0;
-        const chordSymbolSpacing: number = EngravingRules.Rules.ChordSymbolXSpacing;
+        const chordSymbolSpacing: number = rules.ChordSymbolXSpacing;
         for (const chordSymbolContainer of sourceStaffEntry.ChordContainers) {
             const graphicalChordSymbolContainer: GraphicalChordSymbolContainer =
               new GraphicalChordSymbolContainer(chordSymbolContainer,
                                                 graphicalStaffEntry.PositionAndShape,
-                                                EngravingRules.Rules.ChordSymbolTextHeight,
-                                                transposeHalftones);
+                                                rules.ChordSymbolTextHeight,
+                                                transposeHalftones,
+                                                graphicalStaffEntry.parentMeasure.parentSourceMeasure.Rules
+                                                );
             const graphicalLabel: GraphicalLabel = graphicalChordSymbolContainer.GetGraphicalLabel;
-            graphicalLabel.PositionAndShape.RelativePosition.y -= EngravingRules.Rules.ChordSymbolYOffset;
+            graphicalLabel.PositionAndShape.RelativePosition.y -= rules.ChordSymbolYOffset;
             graphicalLabel.PositionAndShape.RelativePosition.x += xShift;
             // TODO check for available space until next staffEntry or chord symbol (x direction)
             graphicalLabel.setLabelPositionAndShapeBorders();

+ 1 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts

@@ -14,6 +14,7 @@ export class VexFlowInstantaneousDynamicExpression extends GraphicalInstantaneou
         this.label = new GraphicalLabel(new Label(this.Expression),
                                         this.rules.ContinuousDynamicTextHeight,
                                         TextAlignmentEnum.CenterCenter,
+                                        this.rules,
                                         this.PositionAndShape);
 
         this.label.Label.fontStyle = FontStyles.BoldItalic;

+ 31 - 20
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -27,7 +27,7 @@ import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Voice} from "../../VoiceData/Voice";
 import {LinkedVoice} from "../../VoiceData/LinkedVoice";
-import {EngravingRules} from "../EngravingRules";
+import { EngravingRules } from "../EngravingRules";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {TechnicalInstruction, TechnicalInstructionType} from "../../VoiceData/Instructions/TechnicalInstruction";
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
@@ -39,6 +39,18 @@ export class VexFlowMeasure extends GraphicalMeasure {
     constructor(staff: Staff, sourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) {
         super(staff, sourceMeasure, staffLine);
         this.minimumStaffEntriesWidth = -1;
+
+        /*
+         * There is no case in which `staffLine === undefined && sourceMeasure === undefined` holds.
+         * Hence, it is not necessary to specify an `else` case.
+         * One can verify this through a usage search for this constructor.
+         */
+        if (staffLine) {
+            this.rules = staffLine.ParentMusicSystem.rules;
+        } else if (sourceMeasure) {
+            this.rules = sourceMeasure.Rules;
+        }
+
         this.resetLayout();
     }
 
@@ -67,7 +79,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
     /** Intermediate object to construct tuplets */
     protected tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
     /** VexFlow Tuplets */
-    protected vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
+    private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
+    // The engraving rules of OSMD.
+    public rules: EngravingRules;
 
     // Sets the absolute coordinates of the VFStave on the canvas
     public setAbsoluteCoordinates(x: number, y: number): void {
@@ -99,9 +113,6 @@ export class VexFlowMeasure extends GraphicalMeasure {
         }
         // the correct bar types seem to be set later
 
-        //if (nextMeasureIndex >= EngravingRules.Rules.MinMeasureToDrawIndex && nextMeasureIndex <= EngravingRules.Rules.MaxMeasureToDrawIndex) {
-        //this.stave.setBegBarType(Vex.Flow.Barline.type.NONE);
-
         this.updateInstructionWidth();
     }
 
@@ -620,7 +631,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                         if (entry.parentVoiceEntry.IsGrace) {
                             isGraceBeam = true;
                         }
-                        if (entry.parentVoiceEntry.StemColor && EngravingRules.Rules.ColoringEnabled) {
+                        if (entry.parentVoiceEntry.StemColor && this.rules.ColoringEnabled) {
                             stemColors.push(entry.parentVoiceEntry.StemColor);
                         }
                     }
@@ -631,7 +642,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                             (<any>vfBeam).render_options.beam_width = 3;
                             (<any>vfBeam).render_options.partial_beam_length = 4;
                         }
-                        if (stemColors.length >= 2 && EngravingRules.Rules.ColorBeams) {
+                        if (stemColors.length >= 2 && this.rules.ColorBeams) {
                             beamColor = stemColors[0];
                             for (const stemColor of stemColors) {
                                 if (stemColor !== beamColor) {
@@ -648,13 +659,13 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 }
             }
         }
-        if (EngravingRules.Rules.AutoBeamNotes) {
+        if (this.rules.AutoBeamNotes) {
             this.autoBeamNotes(beamedNotes); // try to autobeam notes except those that are already beamed (beamedNotes).
         }
     }
 
     /** Automatically creates beams for notes except beamedNotes, using Vexflow's Beam.generateBeams().
-     *  Takes options from EngravingRules.Rules.AutoBeamOptions.
+     *  Takes options from this.rules.AutoBeamOptions.
      * @param beamedNotes notes that will not be autobeamed (usually because they are already beamed)
      */
     private autoBeamNotes(beamedNotes: StemmableNote[]): void {
@@ -723,7 +734,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                             //   however, taking the note length as fraction is tricky because of tuplets.
                             //   a quarter in a triplet has length < quarter, but quarter note head, which Vexflow can't beam.
                                 note.ParentVoiceEntry.IsGrace ||
-                                note.isRest() && !EngravingRules.Rules.AutoBeamOptions.beam_rests) {
+                                note.isRest() && !this.rules.AutoBeamOptions.beam_rests) {
                                 tupletContainsUnbeamableNote = true;
                                 break;
                             }
@@ -766,7 +777,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         }
 
         // create options for generateBeams
-        const autoBeamOptions: AutoBeamOptions = EngravingRules.Rules.AutoBeamOptions;
+        const autoBeamOptions: AutoBeamOptions = this.rules.AutoBeamOptions;
         const generateBeamOptions: any = {
             beam_middle_only: autoBeamOptions.beam_middle_rests_only,
             beam_rests: autoBeamOptions.beam_rests,
@@ -812,14 +823,14 @@ export class VexFlowMeasure extends GraphicalMeasure {
                       const tuplet: Tuplet = tupletBuilder[0];
                       const notesOccupied: number = tuplet.Notes[0][0].NormalNotes;
                       const bracketed: boolean = tuplet.Bracket ||
-                        (tuplet.TupletLabelNumber === 3 && EngravingRules.Rules.TripletsBracketed) ||
-                        (tuplet.TupletLabelNumber !== 3 && EngravingRules.Rules.TupletsBracketed);
+                        (tuplet.TupletLabelNumber === 3 && this.rules.TripletsBracketed) ||
+                        (tuplet.TupletLabelNumber !== 3 && this.rules.TupletsBracketed);
                       vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
                                                           {
                                                             bracketed: bracketed,
                                                             notes_occupied: notesOccupied,
                                                             num_notes: tuplet.TupletLabelNumber, //, location: -1, ratioed: true
-                                                            ratioed: EngravingRules.Rules.TupletsRatioed,
+                                                            ratioed: this.rules.TupletsRatioed,
                                                           }));
                     } else {
                         log.debug("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
@@ -937,7 +948,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 }
 
                 // add fingering
-                if (voiceEntry.parentVoiceEntry && EngravingRules.Rules.RenderFingerings) {
+                if (voiceEntry.parentVoiceEntry && this.rules.RenderFingerings) {
                     this.createFingerings(voiceEntry);
                 }
 
@@ -949,12 +960,12 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     if (voiceEntry.notes && voiceEntry.notes.length > 1) {
                         const type: Vex.Flow.Stroke.Type = VexFlowConverter.StrokeTypeFromArpeggioType(arpeggio.type);
                         const stroke: Vex.Flow.Stroke = new Vex.Flow.Stroke(type, {
-                            all_voices: EngravingRules.Rules.ArpeggiosGoAcrossVoices
+                            all_voices: this.rules.ArpeggiosGoAcrossVoices
                             // default: false. This causes arpeggios to always go across all voices, which is often unwanted.
                             // also, this can cause infinite height of stroke, see #546
                         });
                         //if (arpeggio.notes.length === vexFlowVoiceEntry.notes.length) { // different workaround for endless y bug
-                        if (EngravingRules.Rules.RenderArpeggios) {
+                        if (this.rules.RenderArpeggios) {
                             vexFlowVoiceEntry.vfStaveNote.addStroke(0, stroke);
                         }
                     } else {
@@ -1051,7 +1062,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 continue;
             }
             fingeringIndex++; // 0 for first fingering
-            let fingeringPosition: PlacementEnum = EngravingRules.Rules.FingeringPosition;
+            let fingeringPosition: PlacementEnum = this.rules.FingeringPosition;
             if (fingeringInstruction.placement !== PlacementEnum.NotYetDefined) {
                 fingeringPosition = fingeringInstruction.placement;
             }
@@ -1089,13 +1100,13 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 const offsetYSign: number = fingeringPosition === PlacementEnum.Above ? -1 : 1; // minus y is up
                 const ordering: number = fingeringPosition === PlacementEnum.Above ? fingeringIndex :
                     fingeringsCount - 1 - fingeringIndex; // reverse order for fingerings below staff
-                if (EngravingRules.Rules.FingeringInsideStafflines && fingeringsCount > 1) { // y-shift for single fingering is ok
+                if (this.rules.FingeringInsideStafflines && fingeringsCount > 1) { // y-shift for single fingering is ok
                     // experimental, bounding boxes wrong for fretFinger above/below, better would be creating Labels
                     // set y-shift. vexflow fretfinger simply places directly above/below note
                     const perFingeringShift: number = fretFinger.getWidth() / 2;
                     const shiftCount: number = fingeringsCount * 2.5;
                     (<any>fretFinger).setOffsetY(offsetYSign * (ordering + shiftCount) * perFingeringShift);
-                } else if (!EngravingRules.Rules.FingeringInsideStafflines) { // use StringNumber for placement above/below stafflines
+                } else if (!this.rules.FingeringInsideStafflines) { // use StringNumber for placement above/below stafflines
                     const stringNumber: Vex.Flow.StringNumber = new Vex.Flow.StringNumber(fingeringInstruction.value);
                     (<any>stringNumber).radius = 0; // hack to remove the circle around the number
                     stringNumber.setPosition(modifierPosition);

+ 17 - 15
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -38,7 +38,6 @@ import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
 // import { VexFlowStaffLine } from "./VexFlowStaffLine";
 // import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 */
-import { EngravingRules } from "../EngravingRules";
 import { PointF2D } from "../../../Common/DataObjects/PointF2D";
 import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
 import { GraphicalSlur } from "../GraphicalSlur";
@@ -48,15 +47,17 @@ import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicEx
 import { InstantaneousTempoExpression } from "../../VoiceData/Expressions";
 import { AlignRestOption } from "../../../OpenSheetMusicDisplay";
 import { VexFlowStaffLine } from "./VexFlowStaffLine";
+import { EngravingRules } from "..";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
   private dashSpace: number;
 
-  constructor() {
+  constructor(rules: EngravingRules) {
     super();
+    this.rules = rules;
     MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
-    MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+    MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(this.rules);
   }
 
   protected clearRecreatedObjects(): void {
@@ -150,11 +151,11 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
       for (const measure of measures) {
         // determine whether to align rests
-        if (EngravingRules.Rules.AlignRests === AlignRestOption.Never) {
+        if (this.rules.AlignRests === AlignRestOption.Never) {
           (measure as VexFlowMeasure).formatVoices = formatVoicesDefault;
-        } else if (EngravingRules.Rules.AlignRests === AlignRestOption.Always) {
+        } else if (this.rules.AlignRests === AlignRestOption.Always) {
           (measure as VexFlowMeasure).formatVoices = formatVoicesAlignRests;
-        } else if (EngravingRules.Rules.AlignRests === AlignRestOption.Auto) {
+        } else if (this.rules.AlignRests === AlignRestOption.Auto) {
           let alignRests: boolean = false;
           for (const staffEntry of measure.staffEntries) {
             let collidableVoiceEntries: number = 0;
@@ -247,18 +248,18 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           const lyricsEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[j];
           // const lyricsEntryText = lyricsEntry.LyricsEntry.Text; // for easier debugging
           const lyricAlignment: TextAlignmentEnum = lyricsEntry.GraphicalLabel.Label.textAlignment;
-          let minLyricsSpacing: number = EngravingRules.Rules.HorizontalBetweenLyricsDistance;
+          let minLyricsSpacing: number = this.rules.HorizontalBetweenLyricsDistance;
           // for quarter note in Vexflow, where spacing is halfed for each smaller note duration.
 
           let lyricOverlapAllowedIntoNextMeasure: number =
-            EngravingRules.Rules.LyricOverlapAllowedIntoNextMeasure;
+            this.rules.LyricOverlapAllowedIntoNextMeasure;
           // TODO allow more overlap if there are no lyrics in next measure
 
           // spacing for multi-syllable words
           if (lyricsEntry.ParentLyricWord) {
             if (lyricsEntry.LyricsEntry.SyllableIndex > 0) { // syllables after first
               // give a little more spacing for dash between syllables
-              minLyricsSpacing = EngravingRules.Rules.BetweenSyllableMinimumDistance;
+              minLyricsSpacing = this.rules.BetweenSyllableMinimumDistance;
               if (TextAlignment.IsCenterAligned(lyricsEntry.GraphicalLabel.Label.textAlignment)) {
                 minLyricsSpacing += 1.0; // TODO check for previous lyric alignment too. though center is not standard
                 // without this, there's not enough space for dashes between long syllables on eigth notes
@@ -370,6 +371,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   }
 
   protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
+    (measure as VexFlowMeasure).rules = this.rules;
     (measure as VexFlowMeasure).graphicalMeasureCreatedCalculations();
   }
 
@@ -474,7 +476,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   }
 
   protected calculateDynamicExpressionsForMultiExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
-    if (measureIndex < EngravingRules.Rules.MinMeasureToDrawIndex || measureIndex > EngravingRules.Rules.MaxMeasureToDrawIndex) {
+    if (measureIndex < this.rules.MinMeasureToDrawIndex || measureIndex > this.rules.MaxMeasureToDrawIndex) {
       return;
       // we do already use the min/max in MusicSheetCalculator.calculateDynamicsExpressions,
       // but this may be necessary for StaffLinkedExpressions, not tested.
@@ -537,16 +539,16 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           //duration: metronomeExpression.beatUnit
           duration: "q"
       },
-      EngravingRules.Rules.MetronomeMarkYShift * unitInPixels);
+      this.rules.MetronomeMarkYShift * unitInPixels);
        // -50, -30), 0); //needs Vexflow PR
        //.setShiftX(-50);
 
     (<any>vfStave.getModifiers()[vfStave.getModifiers().length - 1]).setShiftX(
-      EngravingRules.Rules.MetronomeMarkXShift * unitInPixels
+      this.rules.MetronomeMarkXShift * unitInPixels
     );
     // TODO calculate bounding box of metronome mark instead of hacking skyline to fix lyricist collision
     const skyline: number[] = this.graphicalMusicSheet.MeasureList[0][0].ParentStaffLine.SkyLine;
-    skyline[0] = Math.min(skyline[0], -4.5 + EngravingRules.Rules.MetronomeMarkYShift);
+    skyline[0] = Math.min(skyline[0], -4.5 + this.rules.MetronomeMarkYShift);
     // somehow this is called repeatedly in Clementi, so skyline[0] = Math.min instead of -=
   }
 
@@ -564,8 +566,8 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     const startTimeStamp: Fraction = octaveShift.ParentStartMultiExpression.Timestamp;
     const endTimeStamp: Fraction = octaveShift.ParentEndMultiExpression.Timestamp;
 
-    const minMeasureToDrawIndex: number = EngravingRules.Rules.MinMeasureToDrawIndex;
-    const maxMeasureToDrawIndex: number = EngravingRules.Rules.MaxMeasureToDrawIndex;
+    const minMeasureToDrawIndex: number = this.rules.MinMeasureToDrawIndex;
+    const maxMeasureToDrawIndex: number = this.rules.MaxMeasureToDrawIndex;
 
     let startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
     if (startStaffLine === undefined) { // fix for rendering range set. all of these can probably done cleaner.

+ 6 - 7
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -17,7 +17,6 @@ import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
 import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
 import { VexFlowStaffLine } from "./VexFlowStaffLine";
 import { StaffLine } from "../StaffLine";
-import { EngravingRules } from "../EngravingRules";
 import { GraphicalSlur } from "../GraphicalSlur";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { GraphicalInstantaneousTempoExpression } from "../GraphicalInstantaneousTempoExpression";
@@ -43,7 +42,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
     private pageIdx: number = 0; // this is a bad solution, should use MusicPage.PageNumber instead.
 
     constructor(drawingParameters: DrawingParameters = new DrawingParameters()) {
-        super(new VexFlowTextMeasurer(), drawingParameters);
+        super(new VexFlowTextMeasurer(drawingParameters.Rules), drawingParameters);
     }
 
     public get Backends(): VexFlowBackend[] {
@@ -95,7 +94,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
     protected drawStaffLine(staffLine: StaffLine): void {
         super.drawStaffLine(staffLine);
         const absolutePos: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
-        if (EngravingRules.Rules.RenderSlurs) {
+        if (this.rules.RenderSlurs) {
             this.drawSlurs(staffLine as VexFlowStaffLine, absolutePos);
         }
     }
@@ -233,7 +232,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
         const absolute: PointF2D = startPosition;
         if (indices.length > 0) {
-            const samplingUnit: number = EngravingRules.Rules.SamplingUnit;
+            const samplingUnit: number = this.rules.SamplingUnit;
 
             let horizontalStart: PointF2D = new PointF2D(absolute.x, absolute.y);
             let horizontalEnd: PointF2D = new PointF2D(indices[0] / samplingUnit + absolute.x, absolute.y);
@@ -284,7 +283,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
                 this.drawLabel(graphicalChordContainer.GetGraphicalLabel, <number>GraphicalLayers.Notes);
             }
         }
-        if (EngravingRules.Rules.RenderLyrics) {
+        if (this.rules.RenderLyrics) {
             if (staffEntry.LyricsEntries.length > 0) {
                 this.drawLyrics(staffEntry.LyricsEntries, <number>GraphicalLayers.Notes);
             }
@@ -401,10 +400,10 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
         const { fontStyle, font, text } = graphicalLabel.Label;
         let color: string;
-        if (EngravingRules.Rules.ColoringEnabled) {
+        if (this.rules.ColoringEnabled) {
             color = graphicalLabel.Label.colorDefault;
             if (!color) {
-                color = EngravingRules.Rules.DefaultColorLabel;
+                color = this.rules.DefaultColorLabel;
             }
         }
         this.backend.renderText(height, fontStyle, font, text, heightInPixel, screenPosition, color);

+ 2 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts

@@ -13,9 +13,9 @@ import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
 import { SkyBottomLineCalculator } from "../SkyBottomLineCalculator";
 
 export class VexFlowMusicSystem extends MusicSystem {
-    constructor(id: number) {
+    constructor(id: number, rules: EngravingRules) {
         super(id);
-
+        this.rules = rules;
     }
 
     public calculateBorders(rules: EngravingRules): void {

+ 1 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -5,7 +5,6 @@ import { SourceStaffEntry } from "../../VoiceData/SourceStaffEntry";
 import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 import { Note } from "../../VoiceData/Note";
-import { EngravingRules } from "../EngravingRules";
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
@@ -46,7 +45,7 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
                     // whole rest: length = measure length. (4/4 in a 4/4 time signature, 3/4 in a 3/4 time signature, 1/4 in a 1/4 time signature, etc.)
                     // see Note.isWholeRest(), which is currently not safe
                     this.PositionAndShape.RelativePosition.x +=
-                        EngravingRules.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
+                        this.parentMeasure.parentSourceMeasure.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
                     gve.PositionAndShape.BorderLeft = -0.7;
                     gve.PositionAndShape.BorderRight = 0.7;
                 }

+ 3 - 4
src/MusicalScore/Graphical/VexFlow/VexFlowTabMeasure.ts

@@ -7,7 +7,6 @@ import { VexFlowConverter } from "./VexFlowConverter";
 import { StaffLine } from "../StaffLine";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
-import { EngravingRules } from "../EngravingRules";
 import { Arpeggio } from "../../VoiceData/Arpeggio";
 import { Voice } from "../../VoiceData/Voice";
 import * as log from "loglevel";
@@ -80,7 +79,7 @@ export class VexFlowTabMeasure extends VexFlowMeasure {
                 }
 
                 // add fingering
-                if (voiceEntry.parentVoiceEntry && EngravingRules.Rules.RenderFingerings) {
+                if (voiceEntry.parentVoiceEntry && this.rules.RenderFingerings) {
                     this.createFingerings(voiceEntry);
                 }
 
@@ -92,12 +91,12 @@ export class VexFlowTabMeasure extends VexFlowMeasure {
                     if (voiceEntry.notes && voiceEntry.notes.length > 1) {
                         const type: Vex.Flow.Stroke.Type = VexFlowConverter.StrokeTypeFromArpeggioType(arpeggio.type);
                         const stroke: Vex.Flow.Stroke = new Vex.Flow.Stroke(type, {
-                            all_voices: EngravingRules.Rules.ArpeggiosGoAcrossVoices
+                            all_voices: this.rules.ArpeggiosGoAcrossVoices
                             // default: false. This causes arpeggios to always go across all voices, which is often unwanted.
                             // also, this can cause infinite height of stroke, see #546
                         });
                         //if (arpeggio.notes.length === vexFlowVoiceEntry.notes.length) { // different workaround for endless y bug
-                        if (EngravingRules.Rules.RenderArpeggios) {
+                        if (this.rules.RenderArpeggios) {
                             vexFlowVoiceEntry.vfStaveNote.addStroke(0, stroke);
                         }
                     } else {

+ 5 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowTextMeasurer.ts

@@ -2,19 +2,22 @@ import {ITextMeasurer} from "../../Interfaces/ITextMeasurer";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {FontStyles} from "../../../Common/Enums/FontStyles";
 import {VexFlowConverter} from "./VexFlowConverter";
+import { EngravingRules } from "../EngravingRules";
 /**
  * Created by Matthias on 21.06.2016.
  */
 
 export class VexFlowTextMeasurer implements ITextMeasurer {
-    constructor() {
+    constructor(rules: EngravingRules) {
         const canvas: HTMLCanvasElement = document.createElement("canvas");
         this.context = canvas.getContext("2d");
+        this.rules = rules;
     }
     // The context of a canvas used internally to compute font sizes
     private context: CanvasRenderingContext2D;
     public fontSize: number = 20;
     public fontSizeStandard: number = this.fontSize;
+    private rules: EngravingRules;
 
     /**
      *
@@ -24,7 +27,7 @@ export class VexFlowTextMeasurer implements ITextMeasurer {
      * @returns {number}
      */
     public computeTextWidthToHeightRatio(text: string, font: Fonts, style: FontStyles, fontSize: number = this.fontSize): number {
-        this.context.font = VexFlowConverter.font(fontSize, style, font);
+        this.context.font = VexFlowConverter.font(fontSize, style, font, this.rules);
         return this.context.measureText(text).width / fontSize;
     }
 

+ 13 - 14
src/MusicalScore/Graphical/VexFlow/VexFlowVoiceEntry.ts

@@ -3,7 +3,6 @@ import { VoiceEntry } from "../../VoiceData/VoiceEntry";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
 import { unitInPixels } from "./VexFlowMusicSheetDrawer";
-import { EngravingRules } from "../EngravingRules";
 import { GraphicalNote } from "..";
 import { NoteEnum } from "../../../Common/DataObjects/Pitch";
 import { Note } from "../../VoiceData/Note";
@@ -44,9 +43,9 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
      * See VexFlowConverter.StaveNote()
      */
     public color(): void {
-        const defaultColorNotehead: string = EngravingRules.Rules.DefaultColorNotehead;
-        const defaultColorRest: string = EngravingRules.Rules.DefaultColorRest;
-        const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
+        const defaultColorNotehead: string = this.rules.DefaultColorNotehead;
+        const defaultColorRest: string = this.rules.DefaultColorRest;
+        const defaultColorStem: string = this.rules.DefaultColorStem;
         const transparentColor: string = "#00000000"; // transparent color in vexflow
         let noteheadColor: string; // if null: no noteheadcolor to set (stays black)
         let sourceNoteNoteheadColor: string;
@@ -58,13 +57,13 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
             sourceNoteNoteheadColor = note.sourceNote.NoteheadColor;
             noteheadColor = sourceNoteNoteheadColor;
             // Switch between XML colors and automatic coloring
-            if (EngravingRules.Rules.ColoringMode === ColoringModes.AutoColoring ||
-                EngravingRules.Rules.ColoringMode === ColoringModes.CustomColorSet) {
+            if (this.rules.ColoringMode === ColoringModes.AutoColoring ||
+                this.rules.ColoringMode === ColoringModes.CustomColorSet) {
                 if (note.sourceNote.isRest()) {
-                    noteheadColor = EngravingRules.Rules.ColoringSetCurrent.getValue(-1);
+                    noteheadColor = this.rules.ColoringSetCurrent.getValue(-1);
                 } else {
                     const fundamentalNote: NoteEnum = note.sourceNote.Pitch.FundamentalNote;
-                    noteheadColor = EngravingRules.Rules.ColoringSetCurrent.getValue(fundamentalNote);
+                    noteheadColor = this.rules.ColoringSetCurrent.getValue(fundamentalNote);
                 }
             }
             if (!note.sourceNote.PrintObject) {
@@ -73,7 +72,7 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
                 || noteheadColor === "#000000" // questionable, because you might want to set specific notes to black,
                                                // but unfortunately some programs export everything explicitly as black
                 ) {
-                noteheadColor = EngravingRules.Rules.DefaultColorNotehead;
+                noteheadColor = this.rules.DefaultColorNotehead;
             }
 
             // DEBUG runtime coloring test
@@ -85,7 +84,7 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
                     ", in measure #" + measureNumber);
             }*/
 
-            if (!sourceNoteNoteheadColor && EngravingRules.Rules.ColoringMode === ColoringModes.XML && note.sourceNote.PrintObject) {
+            if (!sourceNoteNoteheadColor && this.rules.ColoringMode === ColoringModes.XML && note.sourceNote.PrintObject) {
                 if (!note.sourceNote.isRest() && defaultColorNotehead) {
                     noteheadColor = defaultColorNotehead;
                 } else if (note.sourceNote.isRest() && defaultColorRest) {
@@ -99,7 +98,7 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
             }
 
             // color notebeam if all noteheads have same color and stem coloring enabled
-            if (EngravingRules.Rules.ColoringEnabled && note.sourceNote.NoteBeam && EngravingRules.Rules.ColorBeams) {
+            if (this.rules.ColoringEnabled && note.sourceNote.NoteBeam && this.rules.ColorBeams) {
                 const beamNotes: Note[] = note.sourceNote.NoteBeam.Notes;
                 let colorBeam: boolean = true;
                 for (let j: number = 0; j < beamNotes.length; j++) {
@@ -126,13 +125,13 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
 
         // color stems
         let stemColor: string = defaultColorStem; // reset to black/default when coloring was disabled. maybe needed elsewhere too
-        if (EngravingRules.Rules.ColoringEnabled) {
+        if (this.rules.ColoringEnabled) {
             stemColor = this.parentVoiceEntry.StemColor; // TODO: once coloringSetCustom gets stem color, respect it
             if (!stemColor
                 || stemColor === "#000000") { // see above, noteheadColor === "#000000"
                 stemColor = defaultColorStem;
             }
-            if (EngravingRules.Rules.ColorStemsLikeNoteheads && noteheadColor) {
+            if (this.rules.ColorStemsLikeNoteheads && noteheadColor) {
                 // condition could be even more fine-grained by only recoloring if there was no custom StemColor set. will be more complex though
                 stemColor = noteheadColor;
             }
@@ -154,7 +153,7 @@ export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
                 this.parentVoiceEntry.StemColor = stemColor;
             }
             vfStaveNote.setStemStyle(stemStyle);
-            if (vfStaveNote.flag && vfStaveNote.setFlagStyle && EngravingRules.Rules.ColorFlags) {
+            if (vfStaveNote.flag && vfStaveNote.setFlagStyle && this.rules.ColorFlags) {
                 vfStaveNote.setFlagStyle(stemStyle);
             }
         }

+ 2 - 1
src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts

@@ -14,10 +14,11 @@ import {GraphicalMeasure} from "../Graphical/GraphicalMeasure";
 import { TechnicalInstruction } from "../VoiceData/Instructions/TechnicalInstruction";
 import { GraphicalVoiceEntry } from "../Graphical/GraphicalVoiceEntry";
 import { VoiceEntry } from "../VoiceData/VoiceEntry";
+import { EngravingRules } from "../Graphical/EngravingRules";
 
 export interface IGraphicalSymbolFactory {
 
-    createMusicSystem(systemIndex: number): MusicSystem;
+    createMusicSystem(systemIndex: number, rules: EngravingRules): MusicSystem;
 
     createStaffLine(parentSystem: MusicSystem, parentStaff: Staff): StaffLine;
 

+ 23 - 21
src/MusicalScore/MusicSheet.ts

@@ -32,7 +32,6 @@ export class PlaybackSettings {
  */
 export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet>*/ {
     constructor() {
-        this.rules = EngravingRules.Rules;
         this.playbackSettings = new PlaybackSettings();
         // FIXME?
         // initialize SheetPlaybackSetting with default values
@@ -47,7 +46,6 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
 
     public userStartTempoInBPM: number;
     public pageWidth: number;
-    public rules: EngravingRules;
 
     private idString: string = "random idString, not initialized";
     private sourceMeasures: SourceMeasure[] = [];
@@ -77,7 +75,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
     private hasBeenOpenedForTheFirstTime: boolean = false;
     private currentEnrolledPosition: Fraction = new Fraction(0, 1);
     // (*) private musicSheetParameterObject: MusicSheetParameterObject = undefined;
-    private engravingRules: EngravingRules;
+    private rules: EngravingRules;
     // (*) private musicSheetParameterChangedDelegate: MusicSheetParameterChangedDelegate;
     /* Whether BPM info is present in the sheet. If it is set to false, each measure's BPM was set to a default of 120. */
     private hasBPMInfo: boolean;
@@ -121,10 +119,10 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         // this method name should remain for a while to maintain compatibility even when Instrument is renamed to Part
         return this.instruments;
     }
-     public get SheetPlaybackSetting(): PlaybackSettings {
+    public get SheetPlaybackSetting(): PlaybackSettings {
         return this.playbackSettings;
     }
-     public set SheetPlaybackSetting(value: PlaybackSettings) {
+    public set SheetPlaybackSetting(value: PlaybackSettings) {
         this.playbackSettings = value;
     }
     public get DrawErroneousMeasures(): boolean {
@@ -212,10 +210,14 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         this.lyricist = value;
     }
     public get Rules(): EngravingRules {
-       return this.engravingRules;
+        if (!this.rules) {
+            log.debug("warning: sheet.Rules was undefined. Creating new EngravingRules.");
+            this.rules = new EngravingRules();
+        }
+        return this.rules;
     }
     public set Rules(value: EngravingRules) {
-       this.engravingRules = value;
+        this.rules = value;
     }
     public get SheetErrors(): MusicSheetErrors {
         return this.musicSheetErrors;
@@ -336,17 +338,17 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         return this.sourceMeasures[this.sourceMeasures.length - 1];
     }
     public resetAllNoteStates(): void {
-       const iterator: MusicPartManagerIterator = this.MusicPartManager.getIterator();
-       while (!iterator.EndReached && iterator.CurrentVoiceEntries !== undefined) {
-           for (let idx: number = 0, len: number = iterator.CurrentVoiceEntries.length; idx < len; ++idx) {
-               const voiceEntry: VoiceEntry = iterator.CurrentVoiceEntries[idx];
-               for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
-                   const note: Note = voiceEntry.Notes[idx2];
-                   note.state = NoteState.Normal;
-               }
-           }
-           iterator.moveToNext();
-       }
+        const iterator: MusicPartManagerIterator = this.MusicPartManager.getIterator();
+        while (!iterator.EndReached && iterator.CurrentVoiceEntries !== undefined) {
+            for (let idx: number = 0, len: number = iterator.CurrentVoiceEntries.length; idx < len; ++idx) {
+                const voiceEntry: VoiceEntry = iterator.CurrentVoiceEntries[idx];
+                for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
+                    const note: Note = voiceEntry.Notes[idx2];
+                    note.state = NoteState.Normal;
+                }
+            }
+            iterator.moveToNext();
+        }
     }
     public getMusicSheetInstrumentIndex(instrument: Instrument): number {
         return this.Instruments.indexOf(instrument);
@@ -495,13 +497,13 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
     //    this.musicSheetParameterChangedDelegate = value;
     //}
     public get FullNameString(): string {
-       return this.ComposerString + " " + this.TitleString;
+        return this.ComposerString + " " + this.TitleString;
     }
     public get IdString(): string {
-       return this.idString;
+        return this.idString;
     }
     public set IdString(value: string) {
-       this.idString = value;
+        this.idString = value;
     }
     // (*)
     // public Dispose(): void {

+ 6 - 2
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -21,10 +21,11 @@ import {MusicSymbolModuleFactory} from "./MusicSymbolModuleFactory";
 import {IAfterSheetReadingModule} from "../Interfaces/IAfterSheetReadingModule";
 import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
 import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
+import { EngravingRules } from "../Graphical";
 
 export class MusicSheetReader /*implements IMusicSheetReader*/ {
 
-    constructor(afterSheetReadingModules: IAfterSheetReadingModule[] = undefined) {
+    constructor(afterSheetReadingModules: IAfterSheetReadingModule[] = undefined, rules: EngravingRules = new EngravingRules()) {
      if (afterSheetReadingModules === undefined) {
        this.afterSheetReadingModules = [];
      } else {
@@ -32,6 +33,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
      }
      this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
      this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
+     this.rules = rules;
     }
 
     private repetitionInstructionReader: RepetitionInstructionReader;
@@ -42,6 +44,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
     private currentMeasure: SourceMeasure;
     private previousMeasure: SourceMeasure;
     private currentFraction: Fraction;
+    public rules: EngravingRules;
 
     public get CompleteNumberOfStaves(): number {
         return this.completeNumberOfStaves;
@@ -116,6 +119,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         let sourceMeasureCounter: number = 0;
         this.musicSheet = new MusicSheet();
         this.musicSheet.Path = path;
+        this.musicSheet.Rules = this.rules;
         if (root === undefined) {
             throw new MusicSheetReadingException("Undefined root element");
         }
@@ -146,7 +150,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
             if (this.currentMeasure !== undefined && this.currentMeasure.endsPiece) {
                 sourceMeasureCounter = 0;
             }
-            this.currentMeasure = new SourceMeasure(this.completeNumberOfStaves);
+            this.currentMeasure = new SourceMeasure(this.completeNumberOfStaves, this.musicSheet.Rules);
             for (const instrumentReader of instrumentReaders) {
                 try {
                     couldReadMeasure = couldReadMeasure && instrumentReader.readNextXmlMeasure(this.currentMeasure, this.currentFraction, guitarPro);

+ 1 - 2
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -16,7 +16,6 @@ import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {TextAlignmentEnum} from "../../../Common/Enums/TextAlignment";
 import {ITextTranslation} from "../../Interfaces/ITextTranslation";
 import * as log from "loglevel";
-import { EngravingRules } from "../../Graphical/EngravingRules";
 
 export class ExpressionReader {
     private musicSheet: MusicSheet;
@@ -544,7 +543,7 @@ export class ExpressionReader {
             }
         }
         let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
-        if (EngravingRules.Rules.CompactMode) {
+        if (this.musicSheet.Rules.CompactMode) {
             textAlignment = TextAlignmentEnum.LeftBottom;
         }
         const unknownExpression: UnknownExpression = new UnknownExpression(

+ 9 - 2
src/MusicalScore/VoiceData/SourceMeasure.ts

@@ -11,7 +11,7 @@ import {MultiTempoExpression} from "./Expressions/MultiTempoExpression";
 import {KeyInstruction} from "./Instructions/KeyInstruction";
 import {AbstractNotationInstruction} from "./Instructions/AbstractNotationInstruction";
 import {Repetition} from "../MusicSource/Repetition";
-import {GraphicalMeasure, SystemLinesEnum} from "../Graphical";
+import {GraphicalMeasure, SystemLinesEnum, EngravingRules} from "../Graphical";
 //import {BaseIdClass} from "../../Util/BaseIdClass"; // SourceMeasure originally extended BaseIdClass, but ids weren't used.
 
 /**
@@ -24,7 +24,7 @@ export class SourceMeasure {
      * so that existing objects can be referred to by staff index.
      * @param completeNumberOfStaves
      */
-    constructor(completeNumberOfStaves: number) {
+    constructor(completeNumberOfStaves: number, rules: EngravingRules) {
         this.completeNumberOfStaves = completeNumberOfStaves;
         this.implicitMeasure = false;
         this.breakSystemAfter = false;
@@ -33,6 +33,7 @@ export class SourceMeasure {
         this.endingBarStyleEnum = SystemLinesEnum.SingleThin;
         this.firstInstructionsStaffEntries = new Array(completeNumberOfStaves);
         this.lastInstructionsStaffEntries = new Array(completeNumberOfStaves);
+        this.rules = rules;
         this.TempoInBPM = 0;
         for (let i: number = 0; i < completeNumberOfStaves; i++) {
             this.graphicalMeasureErrors.push(false);
@@ -69,6 +70,7 @@ export class SourceMeasure {
     private lastInstructionsStaffEntries: SourceStaffEntry[];
     private firstRepetitionInstructions: RepetitionInstruction[] = [];
     private lastRepetitionInstructions: RepetitionInstruction[] = [];
+    private rules: EngravingRules;
     private tempoInBPM: number;
     private verticalMeasureList: GraphicalMeasure[]; // useful, see GraphicalMusicSheet.GetGraphicalFromSourceStaffEntry
 
@@ -171,6 +173,10 @@ export class SourceMeasure {
         return undefined;
     }
 
+    public get Rules(): EngravingRules {
+        return this.rules;
+    }
+
     public get VerticalMeasureList(): GraphicalMeasure[] {
         return this.verticalMeasureList;
     }
@@ -186,6 +192,7 @@ export class SourceMeasure {
     public set TempoInBPM(value: number) {
         this.tempoInBPM = value;
     }
+
     /**
      * Check at the given timestamp if a VerticalContainer exists, if not creates a new, timestamp-ordered one,
      * and at the given index, if a [[SourceStaffEntry]] exists, and if not, creates a new one.

+ 4 - 2
src/OpenSheetMusicDisplay/Cursor.ts

@@ -17,6 +17,7 @@ export class Cursor {
   constructor(container: HTMLElement, openSheetMusicDisplay: OpenSheetMusicDisplay) {
     this.container = container;
     this.openSheetMusicDisplay = openSheetMusicDisplay;
+    this.rules = this.openSheetMusicDisplay.EngravingRules;
     const curs: HTMLElement = document.createElement("img");
     curs.style.position = "absolute";
     curs.style.zIndex = "-1";
@@ -26,6 +27,7 @@ export class Cursor {
 
   private container: HTMLElement;
   private openSheetMusicDisplay: OpenSheetMusicDisplay;
+  private rules: EngravingRules;
   private manager: MusicPartManager;
   protected iterator: MusicPartManagerIterator;
   private graphic: GraphicalMusicSheet;
@@ -58,9 +60,9 @@ export class Cursor {
 
     // set selection start, so that when there's MinMeasureToDraw set, the cursor starts there right away instead of at measure 1
     const lastSheetMeasureIndex: number = this.openSheetMusicDisplay.Sheet.SourceMeasures.length - 1; // last measure in data model
-    let startMeasureIndex: number = EngravingRules.Rules.MinMeasureToDrawIndex;
+    let startMeasureIndex: number = this.rules.MinMeasureToDrawIndex;
     startMeasureIndex = Math.min(startMeasureIndex, lastSheetMeasureIndex);
-    let endMeasureIndex: number = EngravingRules.Rules.MaxMeasureToDrawIndex;
+    let endMeasureIndex: number = this.rules.MaxMeasureToDrawIndex;
     endMeasureIndex = Math.min(endMeasureIndex, lastSheetMeasureIndex);
 
     if (this.openSheetMusicDisplay.Sheet && this.openSheetMusicDisplay.Sheet.SourceMeasures.length > startMeasureIndex) {

+ 57 - 51
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -75,6 +75,7 @@ export class OpenSheetMusicDisplay {
     private drawBottomLine: boolean;
     private graphic: GraphicalMusicSheet;
     private drawingParameters: DrawingParameters;
+    private rules: EngravingRules;
     private autoResizeEnabled: boolean;
     private resizeHandlerAttached: boolean;
     private followCursor: boolean;
@@ -148,7 +149,7 @@ export class OpenSheetMusicDisplay {
             return Promise.reject(new Error("OpenSheetMusicDisplay: Document is not a valid 'partwise' MusicXML"));
         }
         const score: IXmlElement = new IXmlElement(scorePartwiseElement);
-        const reader: MusicSheetReader = new MusicSheetReader();
+        const reader: MusicSheetReader = new MusicSheetReader(undefined, this.rules);
         this.sheet = reader.createMusicSheet(score, "Untitled Score");
         if (this.sheet === undefined) {
             // error loading sheet, probably already logged, do nothing
@@ -166,7 +167,7 @@ export class OpenSheetMusicDisplay {
      * (Re-)creates the graphic sheet from the music sheet
      */
     public updateGraphic(): void {
-        const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
+        const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator(this.rules);
         this.graphic = new GraphicalMusicSheet(this.sheet, calc);
         if (this.drawingParameters.drawCursors && this.cursor) {
             this.cursor.init(this.sheet.MusicPartManager, this.graphic);
@@ -188,7 +189,7 @@ export class OpenSheetMusicDisplay {
 
         // Set page width
         let width: number = this.container.offsetWidth;
-        if (EngravingRules.Rules.RenderSingleHorizontalStaffline) {
+        if (this.rules.RenderSingleHorizontalStaffline) {
             width = 32767; // set safe maximum (browser limit), will be reduced later
             // reduced later in MusicSheetCalculator.calculatePageLabels (sets sheet.pageWidth to page.PositionAndShape.Size.width before labels)
             // rough calculation:
@@ -197,12 +198,12 @@ export class OpenSheetMusicDisplay {
         // log.debug("[OSMD] render width: " + width);
 
         this.sheet.pageWidth = width / this.zoom / 10.0;
-        if (EngravingRules.Rules.PageFormat && !EngravingRules.Rules.PageFormat.IsUndefined) {
-            EngravingRules.Rules.PageHeight = this.sheet.pageWidth / EngravingRules.Rules.PageFormat.aspectRatio;
-            log.debug("[OSMD] PageHeight: " + EngravingRules.Rules.PageHeight);
+        if (this.rules.PageFormat && !this.rules.PageFormat.IsUndefined) {
+            this.rules.PageHeight = this.sheet.pageWidth / this.rules.PageFormat.aspectRatio;
+            log.debug("[OSMD] PageHeight: " + this.rules.PageHeight);
         } else {
-            log.debug("[OSMD] endless/undefined pageformat, id: " + EngravingRules.Rules.PageFormat.idString);
-            EngravingRules.Rules.PageHeight = 100001; // infinite page height // TODO maybe Number.MAX_VALUE or Math.pow(10, 20)?
+            log.debug("[OSMD] endless/undefined pageformat, id: " + this.rules.PageFormat.idString);
+            this.rules.PageHeight = 100001; // infinite page height // TODO maybe Number.MAX_VALUE or Math.pow(10, 20)?
         }
 
         // Before introducing the following optimization (maybe irrelevant), tests
@@ -255,6 +256,7 @@ export class OpenSheetMusicDisplay {
         }
 
         // Create the drawer
+        this.drawingParameters.Rules = this.rules;
         this.drawer = new VexFlowMusicSheetDrawer(this.drawingParameters); // note that here the drawer.drawableBoundingBoxElement is lost. now saved in OSMD.
         this.drawer.drawableBoundingBoxElement = this.DrawBoundingBox;
         this.drawer.bottomLineVisible = this.drawBottomLine;
@@ -262,7 +264,7 @@ export class OpenSheetMusicDisplay {
 
         // Set page width
         let width: number = this.container.offsetWidth;
-        if (EngravingRules.Rules.RenderSingleHorizontalStaffline) {
+        if (this.rules.RenderSingleHorizontalStaffline) {
             width = this.graphic.MusicPages[0].PositionAndShape.Size.width * 10 * this.zoom;
             // this.container.style.width = width + "px";
             // console.log("width: " + width)
@@ -281,8 +283,8 @@ export class OpenSheetMusicDisplay {
                 console.log("[OSMD] Warning: width of " + width + sizeWarningPartTwo);
                 width = canvasDimensionsLimit;
             }
-            if (EngravingRules.Rules.PageFormat && !EngravingRules.Rules.PageFormat.IsUndefined) {
-                height = width / EngravingRules.Rules.PageFormat.aspectRatio;
+            if (this.rules.PageFormat && !this.rules.PageFormat.IsUndefined) {
+                height = width / this.rules.PageFormat.aspectRatio;
                 // console.log("pageformat given. height: " + page.PositionAndShape.Size.height);
             } else {
                 height = (page.PositionAndShape.Size.height + 15) * this.zoom * 10.0;
@@ -295,7 +297,7 @@ export class OpenSheetMusicDisplay {
             }
 
             backend.resize(width, height);
-            backend.clear(); // set bgcolor if defined (EngravingRules.Rules.PageBackgroundColor, see OSMDOptions)
+            backend.clear(); // set bgcolor if defined (this.rules.PageBackgroundColor, see OSMDOptions)
             this.drawer.Backends.push(backend);
         }
     }
@@ -316,8 +318,12 @@ export class OpenSheetMusicDisplay {
      *  For example, setOptions({autoResize: false}) will disable autoResize even during runtime.
      */
     public setOptions(options: IOSMDOptions): void {
+        if (!this.rules) {
+            this.rules = new EngravingRules();
+        }
         if (!this.drawingParameters) {
             this.drawingParameters = new DrawingParameters();
+            this.drawingParameters.Rules = this.rules;
         }
         if (options === undefined || options === null) {
             log.warn("warning: osmd.setOptions() called without an options parameter, has no effect."
@@ -341,14 +347,14 @@ export class OpenSheetMusicDisplay {
 
         // individual drawing parameters options
         if (options.autoBeam !== undefined) { // only change an option if it was given in options, otherwise it will be undefined
-            EngravingRules.Rules.AutoBeamNotes = options.autoBeam;
+            this.rules.AutoBeamNotes = options.autoBeam;
         }
         const autoBeamOptions: AutoBeamOptions = options.autoBeamOptions;
         if (autoBeamOptions) {
             if (autoBeamOptions.maintain_stem_directions === undefined) {
                 autoBeamOptions.maintain_stem_directions = false;
             }
-            EngravingRules.Rules.AutoBeamOptions = autoBeamOptions;
+            this.rules.AutoBeamOptions = autoBeamOptions;
             if (autoBeamOptions.groups && autoBeamOptions.groups.length) {
                 for (const fraction of autoBeamOptions.groups) {
                     if (fraction.length !== 2) {
@@ -359,16 +365,16 @@ export class OpenSheetMusicDisplay {
         }
 
         if (options.alignRests !== undefined) {
-            EngravingRules.Rules.AlignRests = options.alignRests;
+            this.rules.AlignRests = options.alignRests;
         }
         if (options.coloringMode !== undefined) {
             this.setColoringMode(options);
         }
         if (options.coloringEnabled !== undefined) {
-            EngravingRules.Rules.ColoringEnabled = options.coloringEnabled;
+            this.rules.ColoringEnabled = options.coloringEnabled;
         }
         if (options.colorStemsLikeNoteheads !== undefined) {
-            EngravingRules.Rules.ColorStemsLikeNoteheads = options.colorStemsLikeNoteheads;
+            this.rules.ColorStemsLikeNoteheads = options.colorStemsLikeNoteheads;
         }
         if (options.disableCursor) {
             this.drawingParameters.drawCursors = false;
@@ -398,70 +404,70 @@ export class OpenSheetMusicDisplay {
             this.drawingParameters.DrawPartNames = options.drawPartNames; // indirectly writes to EngravingRules
         }
         if (options.drawPartAbbreviations !== undefined) {
-            EngravingRules.Rules.RenderPartAbbreviations = options.drawPartAbbreviations;
+            this.rules.RenderPartAbbreviations = options.drawPartAbbreviations;
         }
         if (options.drawFingerings === false) {
-            EngravingRules.Rules.RenderFingerings = false;
+            this.rules.RenderFingerings = false;
         }
         if (options.drawMeasureNumbers !== undefined) {
-            EngravingRules.Rules.RenderMeasureNumbers = options.drawMeasureNumbers;
+            this.rules.RenderMeasureNumbers = options.drawMeasureNumbers;
         }
         if (options.drawLyrics !== undefined) {
-            EngravingRules.Rules.RenderLyrics = options.drawLyrics;
+            this.rules.RenderLyrics = options.drawLyrics;
         }
         if (options.drawSlurs !== undefined) {
-            EngravingRules.Rules.RenderSlurs = options.drawSlurs;
+            this.rules.RenderSlurs = options.drawSlurs;
         }
         if (options.measureNumberInterval !== undefined) {
-            EngravingRules.Rules.MeasureNumberLabelOffset = options.measureNumberInterval;
+            this.rules.MeasureNumberLabelOffset = options.measureNumberInterval;
         }
         if (options.fingeringPosition !== undefined) {
-            EngravingRules.Rules.FingeringPosition = AbstractExpression.PlacementEnumFromString(options.fingeringPosition);
+            this.rules.FingeringPosition = AbstractExpression.PlacementEnumFromString(options.fingeringPosition);
         }
         if (options.fingeringInsideStafflines !== undefined) {
-            EngravingRules.Rules.FingeringInsideStafflines = options.fingeringInsideStafflines;
+            this.rules.FingeringInsideStafflines = options.fingeringInsideStafflines;
         }
         if (options.fillEmptyMeasuresWithWholeRest !== undefined) {
-            EngravingRules.Rules.FillEmptyMeasuresWithWholeRest = options.fillEmptyMeasuresWithWholeRest;
+            this.rules.FillEmptyMeasuresWithWholeRest = options.fillEmptyMeasuresWithWholeRest;
         }
         if (options.followCursor !== undefined) {
             this.FollowCursor = options.followCursor;
         }
         if (options.setWantedStemDirectionByXml !== undefined) {
-            EngravingRules.Rules.SetWantedStemDirectionByXml = options.setWantedStemDirectionByXml;
+            this.rules.SetWantedStemDirectionByXml = options.setWantedStemDirectionByXml;
         }
         if (options.defaultColorNotehead) {
-            EngravingRules.Rules.DefaultColorNotehead = options.defaultColorNotehead;
+            this.rules.DefaultColorNotehead = options.defaultColorNotehead;
         }
         if (options.defaultColorRest) {
-            EngravingRules.Rules.DefaultColorRest = options.defaultColorRest;
+            this.rules.DefaultColorRest = options.defaultColorRest;
         }
         if (options.defaultColorStem) {
-            EngravingRules.Rules.DefaultColorStem = options.defaultColorStem;
+            this.rules.DefaultColorStem = options.defaultColorStem;
         }
         if (options.defaultColorLabel) {
-            EngravingRules.Rules.DefaultColorLabel = options.defaultColorLabel;
+            this.rules.DefaultColorLabel = options.defaultColorLabel;
         }
         if (options.defaultColorTitle) {
-            EngravingRules.Rules.DefaultColorTitle = options.defaultColorTitle;
+            this.rules.DefaultColorTitle = options.defaultColorTitle;
         }
         if (options.defaultFontFamily) {
-            EngravingRules.Rules.DefaultFontFamily = options.defaultFontFamily; // default "Times New Roman", also used if font family not found
+            this.rules.DefaultFontFamily = options.defaultFontFamily; // default "Times New Roman", also used if font family not found
         }
         if (options.drawUpToMeasureNumber) {
-            EngravingRules.Rules.MaxMeasureToDrawIndex = options.drawUpToMeasureNumber - 1;
+            this.rules.MaxMeasureToDrawIndex = options.drawUpToMeasureNumber - 1;
         }
         if (options.drawFromMeasureNumber) {
-            EngravingRules.Rules.MinMeasureToDrawIndex = options.drawFromMeasureNumber - 1;
+            this.rules.MinMeasureToDrawIndex = options.drawFromMeasureNumber - 1;
         }
         if (options.tupletsRatioed) {
-            EngravingRules.Rules.TupletsRatioed = true;
+            this.rules.TupletsRatioed = true;
         }
         if (options.tupletsBracketed) {
-            EngravingRules.Rules.TupletsBracketed = true;
+            this.rules.TupletsBracketed = true;
         }
         if (options.tripletsBracketed) {
-            EngravingRules.Rules.TripletsBracketed = true;
+            this.rules.TripletsBracketed = true;
         }
         if (options.autoResize) {
             if (!this.resizeHandlerAttached) {
@@ -473,19 +479,19 @@ export class OpenSheetMusicDisplay {
             // we could remove the window EventListener here, but not necessary.
         }
         if (options.pageFormat !== undefined) { // only change this option if it was given, see above
-            EngravingRules.Rules.PageFormat = OpenSheetMusicDisplay.StringToPageFormat(options.pageFormat);
+            this.rules.PageFormat = OpenSheetMusicDisplay.StringToPageFormat(options.pageFormat);
         }
         if (options.pageBackgroundColor !== undefined) {
-            EngravingRules.Rules.PageBackgroundColor = options.pageBackgroundColor;
+            this.rules.PageBackgroundColor = options.pageBackgroundColor;
         }
         if (options.renderSingleHorizontalStaffline !== undefined) {
-            EngravingRules.Rules.RenderSingleHorizontalStaffline = options.renderSingleHorizontalStaffline;
+            this.rules.RenderSingleHorizontalStaffline = options.renderSingleHorizontalStaffline;
         }
     }
 
     public setColoringMode(options: IOSMDOptions): void {
         if (options.coloringMode === ColoringModes.XML) {
-            EngravingRules.Rules.ColoringMode = ColoringModes.XML;
+            this.rules.ColoringMode = ColoringModes.XML;
             return;
         }
         const noteIndices: NoteEnum[] = [NoteEnum.C, NoteEnum.D, NoteEnum.E, NoteEnum.F, NoteEnum.G, NoteEnum.A, NoteEnum.B, -1];
@@ -516,9 +522,9 @@ export class OpenSheetMusicDisplay {
             coloringSetCurrent.setValue(noteIndices[i], colorSetString[i]);
         }
         coloringSetCurrent.setValue(-1, colorSetString[7]);
-        EngravingRules.Rules.ColoringSetCurrent = coloringSetCurrent;
+        this.rules.ColoringSetCurrent = coloringSetCurrent;
 
-        EngravingRules.Rules.ColoringMode = options.coloringMode;
+        this.rules.ColoringMode = options.coloringMode;
     }
 
     /**
@@ -664,9 +670,9 @@ export class OpenSheetMusicDisplay {
     public createBackend(type: BackendType, page: GraphicalMusicPage): VexFlowBackend {
         let backend: VexFlowBackend;
         if (type === undefined || type === BackendType.SVG) {
-            backend = new SvgVexFlowBackend();
+            backend = new SvgVexFlowBackend(this.rules);
         } else {
-            backend = new CanvasVexFlowBackend();
+            backend = new CanvasVexFlowBackend(this.rules);
         }
         backend.graphicalMusicPage = page; // the page the backend renders on. needed to identify DOM element to extract image/SVG
         backend.initialize(this.container);
@@ -716,14 +722,14 @@ export class OpenSheetMusicDisplay {
     /** Sets page format by string. Alternative to setOptions({pageFormat: PageFormatStandards.Endless}) for example. */
     public setPageFormat(formatId: string): void {
         const newPageFormat: PageFormat = OpenSheetMusicDisplay.StringToPageFormat(formatId);
-        this.needBackendUpdate = !(newPageFormat.Equals(EngravingRules.Rules.PageFormat));
-        EngravingRules.Rules.PageFormat = newPageFormat;
+        this.needBackendUpdate = !(newPageFormat.Equals(this.rules.PageFormat));
+        this.rules.PageFormat = newPageFormat;
     }
 
     public setCustomPageFormat(width: number, height: number): void {
         if (width > 0 && height > 0) {
             const f: PageFormat = new PageFormat(width, height);
-            EngravingRules.Rules.PageFormat = f;
+            this.rules.PageFormat = f;
         }
     }
 
@@ -747,7 +753,7 @@ export class OpenSheetMusicDisplay {
 
         let pageWidth: number = 210;
         let pageHeight: number = 297;
-        const engravingRulesPageFormat: PageFormat = EngravingRules.Rules.PageFormat;
+        const engravingRulesPageFormat: PageFormat = this.rules.PageFormat;
         if (engravingRulesPageFormat && !engravingRulesPageFormat.IsUndefined) {
             pageWidth = engravingRulesPageFormat.width;
             pageHeight = engravingRulesPageFormat.height;
@@ -837,7 +843,7 @@ export class OpenSheetMusicDisplay {
         return this.drawingParameters;
     }
     public get EngravingRules(): EngravingRules { // custom getter, useful for engraving parameter setting in Demo
-        return EngravingRules.Rules;
+        return this.rules;
     }
     /** Returns the version of OSMD this object is built from (the version you are using). */
     public get Version(): string {

+ 63 - 1
test/Common/OSMD/OSMD_Test.ts

@@ -1,7 +1,8 @@
 import chai = require("chai");
 import { OpenSheetMusicDisplay } from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 import { TestUtils } from "../../Util/TestUtils";
-import { VoiceEntry, Instrument, Note, Staff, Voice, GraphicalStaffEntry, GraphicalNote, Fraction, Pitch, AccidentalEnum } from "../../../src";
+import { VoiceEntry, Instrument, Note, Staff, Voice, GraphicalStaffEntry, GraphicalNote,
+            Fraction, Pitch, AccidentalEnum, DrawingParametersEnum, IOSMDOptions } from "../../../src";
 
 describe("OpenSheetMusicDisplay Main Export", () => {
     let container1: HTMLElement;
@@ -21,6 +22,67 @@ describe("OpenSheetMusicDisplay Main Export", () => {
         done();
     });
 
+    it("multiple instances", () => {
+        const musicSheetFragmentContainer: HTMLElement = TestUtils.getDivElement(document);
+        const fullMusicSheetContainer: HTMLElement = TestUtils.getDivElement(document);
+
+        const musicSheetFragmentOptions: IOSMDOptions = {
+            drawComposer: false,
+            drawCredits: false,
+            drawFingerings: false,
+            drawHiddenNotes: false,
+            drawLyricist: false,
+            drawPartAbbreviations: false,
+            drawPartNames: false,
+            drawSubtitle: false,
+            drawTitle: false,
+            drawUpToMeasureNumber: 1,
+            drawingParameters: DrawingParametersEnum.compact
+        };
+        const fullMusicSheetOptions: IOSMDOptions = {
+            drawUpToMeasureNumber: 10
+        };
+
+        const musicSheetFragment: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(
+            musicSheetFragmentContainer,
+            musicSheetFragmentOptions
+        );
+        const fullMusicSheet: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(fullMusicSheetContainer, fullMusicSheetOptions);
+
+        const musicSheet: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
+        const musicSheetXML: string = new XMLSerializer().serializeToString(musicSheet);
+
+        return musicSheetFragment.load(musicSheetXML)
+                            .then(() => {
+                                musicSheetFragment.render();
+
+                                return fullMusicSheet.load(musicSheetXML);
+                            })
+                            .then(() => {
+                                fullMusicSheet.render();
+
+                                // Verify that the music sheet fragment has its options set correctly.
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderComposer).to.equal(musicSheetFragmentOptions.drawComposer);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderFingerings).to.equal(musicSheetFragmentOptions.drawFingerings);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderLyricist).to.equal(musicSheetFragmentOptions.drawLyricist);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderPartAbbreviations).to.equal(musicSheetFragmentOptions.drawPartAbbreviations);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderPartNames).to.equal(musicSheetFragmentOptions.drawPartNames);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderSubtitle).to.equal(musicSheetFragmentOptions.drawSubtitle);
+                                chai.expect(musicSheetFragment.Sheet.Rules.RenderTitle).to.equal(musicSheetFragmentOptions.drawTitle);
+                                chai.expect(musicSheetFragment.Sheet.Rules.MaxMeasureToDrawIndex).to.equal(musicSheetFragmentOptions.drawUpToMeasureNumber - 1);
+
+                                // Verify that the full music sheet has its options set correctly.
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderComposer).to.not.equal(musicSheetFragmentOptions.drawComposer);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderFingerings).to.not.equal(musicSheetFragmentOptions.drawFingerings);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderLyricist).to.not.equal(musicSheetFragmentOptions.drawLyricist);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderPartAbbreviations).to.not.equal(musicSheetFragmentOptions.drawPartAbbreviations);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderPartNames).to.not.equal(musicSheetFragmentOptions.drawPartNames);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderSubtitle).to.not.equal(musicSheetFragmentOptions.drawSubtitle);
+                                chai.expect(fullMusicSheet.Sheet.Rules.RenderTitle).to.not.equal(musicSheetFragmentOptions.drawTitle);
+                                chai.expect(fullMusicSheet.Sheet.Rules.MaxMeasureToDrawIndex).to.equal(fullMusicSheetOptions.drawUpToMeasureNumber - 1);
+                            });
+    });
+
     it("load MXL from string", (done: MochaDone) => {
         const mxl: string = TestUtils.getMXL("Mozart_Clarinet_Quintet_Excerpt.mxl");
         const div: HTMLElement = TestUtils.getDivElement(document);

+ 8 - 5
test/MusicalScore/Graphical/VexFlow/VexFlowMeasure_Test.ts

@@ -8,6 +8,7 @@ import {SourceMeasure} from "../../../../src/MusicalScore/VoiceData/SourceMeasur
 import {SourceStaffEntry} from "../../../../src/MusicalScore/VoiceData/SourceStaffEntry";
 import {GraphicalMeasure} from "../../../../src/MusicalScore/Graphical/GraphicalMeasure";
 import {MusicSheetCalculator} from "../../../../src/MusicalScore/Graphical/MusicSheetCalculator";
+import { EngravingRules } from "../../../../src/MusicalScore/Graphical/EngravingRules";
 
 /* tslint:disable:no-unused-expression */
 describe("VexFlow Measure", () => {
@@ -18,8 +19,8 @@ describe("VexFlow Measure", () => {
       chai.expect(score).to.not.be.undefined;
       const partwise: Element = TestUtils.getPartWiseElement(score);
       chai.expect(partwise).to.not.be.undefined;
-      const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator();
       const reader: MusicSheetReader = new MusicSheetReader();
+      const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator(reader.rules);
       const sheet: MusicSheet = reader.createMusicSheet(new IXmlElement(partwise), path);
       const gms: GraphicalMusicSheet = new GraphicalMusicSheet(sheet, calc);
       console.log(gms);
@@ -28,9 +29,10 @@ describe("VexFlow Measure", () => {
 
    it.skip("Simple Measure", (done: MochaDone) => {
       const sheet: MusicSheet = new MusicSheet();
-      const measure: SourceMeasure = new SourceMeasure(1);
+      sheet.Rules = new EngravingRules();
+      const measure: SourceMeasure = new SourceMeasure(1, sheet.Rules);
       sheet.addMeasure(measure);
-      const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
+      const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator(sheet.Rules);
       const gms: GraphicalMusicSheet = new GraphicalMusicSheet(sheet, calc);
       chai.expect(gms.MeasureList.length).to.equal(1);
       chai.expect(gms.MeasureList[0].length).to.equal(1);
@@ -41,10 +43,11 @@ describe("VexFlow Measure", () => {
 
    it.skip("Empty Measure", (done: MochaDone) => {
       const sheet: MusicSheet = new MusicSheet();
-      const measure: SourceMeasure = new SourceMeasure(1);
+      sheet.Rules = new EngravingRules();
+      const measure: SourceMeasure = new SourceMeasure(1, sheet.Rules);
       measure.FirstInstructionsStaffEntries[0] = new SourceStaffEntry(undefined, undefined);
       sheet.addMeasure(measure);
-      const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
+      const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator(sheet.Rules);
       const gms: GraphicalMusicSheet = new GraphicalMusicSheet(sheet, calc);
       chai.expect(gms.MeasureList.length).to.equal(1);
       chai.expect(gms.MeasureList[0].length).to.equal(0);

+ 4 - 4
test/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer_Test.ts

@@ -19,14 +19,14 @@ describe("VexFlow Music Sheet Drawer", () => {
         chai.expect(score).to.not.be.undefined;
         const partwise: Element = TestUtils.getPartWiseElement(score);
         chai.expect(partwise).to.not.be.undefined;
-        const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator();
         const reader: MusicSheetReader = new MusicSheetReader();
+        const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator(reader.rules);
         const sheet: MusicSheet = reader.createMusicSheet(new IXmlElement(partwise), "** missing path **");
         const gms: GraphicalMusicSheet = new GraphicalMusicSheet(sheet, calc);
 
         // Create the canvas in the document:
         const canvas: HTMLCanvasElement = document.createElement("canvas");
-        const backend: VexFlowBackend = new CanvasVexFlowBackend();
+        const backend: VexFlowBackend = new CanvasVexFlowBackend(sheet.Rules);
         backend.initialize(canvas);
         const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer();
         drawer.Backends.push(backend);
@@ -39,15 +39,15 @@ describe("VexFlow Music Sheet Drawer", () => {
         chai.expect(score).to.not.be.undefined;
         const partwise: Element = TestUtils.getPartWiseElement(score);
         chai.expect(partwise).to.not.be.undefined;
-        const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator();
         const reader: MusicSheetReader = new MusicSheetReader();
+        const calc: VexFlowMusicSheetCalculator = new VexFlowMusicSheetCalculator(reader.rules);
         const sheet: MusicSheet = reader.createMusicSheet(new IXmlElement(partwise), "** missing path **");
         const gms: GraphicalMusicSheet = new GraphicalMusicSheet(sheet, calc);
         gms.Cursors.push(gms.calculateCursorLineAtTimestamp(new Fraction(0, 4), OutlineAndFillStyleEnum.PlaybackCursor));
 
         // Create the canvas in the document:
         const canvas: HTMLCanvasElement = document.createElement("canvas");
-        const backend: VexFlowBackend = new CanvasVexFlowBackend();
+        const backend: VexFlowBackend = new CanvasVexFlowBackend(sheet.Rules);
         backend.initialize(canvas);
         const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer(new DrawingParameters());
         drawer.Backends.push(backend);

+ 3 - 2
test/MusicalScore/ScoreCalculation/MusicSheetCalculator_Test.ts

@@ -6,18 +6,19 @@ import {VexFlowMusicSheetCalculator} from "../../../src/MusicalScore/Graphical/V
 import {GraphicalMusicSheet} from "../../../src/MusicalScore/Graphical/GraphicalMusicSheet";
 import {VexFlowTextMeasurer} from "../../../src/MusicalScore/Graphical/VexFlow/VexFlowTextMeasurer";
 import {TestUtils} from "../../Util/TestUtils";
+import {EngravingRules} from "../../../src";
 
 /* tslint:disable:no-unused-expression */
 describe("Music Sheet Calculator", () => {
     const filename: string = "MuzioClementi_SonatinaOpus36No1_Part1.xml";
     const reader: MusicSheetReader = new MusicSheetReader();
-    const calculator: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
+    const calculator: MusicSheetCalculator = new VexFlowMusicSheetCalculator(reader.rules);
     let score: IXmlElement;
     let sheet: MusicSheet;
 
     it("calculates music sheet", (done: MochaDone) => {
         this.timeout = 10000;
-        MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+        MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(new EngravingRules());
         // Load the XML file
         const xml: Document = TestUtils.getScore(filename);
         chai.expect(xml).to.not.be.undefined;