Forráskód Böngészése

feat(Overlays): drawLine() etc returns removable svg node, add backend.removeNode() (#970)

squash merge pr #970

methods returning node(s):
drawLine
drawRectangle
drawBoundingBox
drawLabel (returns node array, a node for each line / text)

makes this possible:
const node = osmd.drawer.DrawOverlayLine({x: 32,y: 20},{x: 34,y: 20},osmd.graphic.MusicPages[0]);
osmd.drawer.backend.removeNode(node);

* drawBoundingBox as standalone method (#961)

* renderRectangle returns removable node (svg), add svgbackend.removeNode()

makes this possible:
const node = osmd.drawer.drawBoundingBox(osmd.graphic.MeasureList[0][0].PositionAndShape, "#b5f3f5", false, undefined, undefined)
osmd.drawer.backend.removeNode(node)
Simon 4 éve
szülő
commit
dc9c66a83a

+ 14 - 11
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -143,13 +143,13 @@ export abstract class MusicSheetDrawer {
         throw new Error("not implemented");
     }
 
-    public drawLabel(graphicalLabel: GraphicalLabel, layer: number): void {
+    public drawLabel(graphicalLabel: GraphicalLabel, layer: number): Node[] {
         if (!this.isVisible(graphicalLabel.PositionAndShape)) {
-            return;
+            return [];
         }
         const label: Label = graphicalLabel.Label;
         if (label.text.trim() === "") {
-            return;
+            return [];
         }
         const screenPosition: PointF2D = this.applyScreenTransformation(graphicalLabel.PositionAndShape.AbsolutePosition);
         const fontHeightInPixel: number = this.calculatePixelDistance(label.fontHeight);
@@ -195,7 +195,7 @@ export abstract class MusicSheetDrawer {
                 throw new ArgumentOutOfRangeException("");
         }
 
-        this.renderLabel(graphicalLabel, layer, bitmapWidth, bitmapHeight, fontHeightInPixel, screenPosition);
+        return this.renderLabel(graphicalLabel, layer, bitmapWidth, bitmapHeight, fontHeightInPixel, screenPosition);
     }
 
     protected applyScreenTransformation(point: PointF2D): PointF2D {
@@ -218,7 +218,7 @@ export abstract class MusicSheetDrawer {
         // empty
     }
 
-    protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string = undefined, alpha: number = 1): void {
+    protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string = undefined, alpha: number = 1): Node {
         throw new Error("not implemented");
     }
 
@@ -235,7 +235,7 @@ export abstract class MusicSheetDrawer {
     }
 
     protected renderLabel(graphicalLabel: GraphicalLabel, layer: number, bitmapWidth: number,
-                          bitmapHeight: number, heightInPixel: number, screenPosition: PointF2D): void {
+                          bitmapHeight: number, heightInPixel: number, screenPosition: PointF2D): Node[] {
         throw new Error("not implemented");
     }
 
@@ -399,17 +399,18 @@ export abstract class MusicSheetDrawer {
         // implemented by subclass (VexFlowMusicSheetDrawer)
     }
 
-    protected drawGraphicalLine(graphicalLine: GraphicalLine, lineWidth: number, colorOrStyle: string = "black"): void {
+    protected drawGraphicalLine(graphicalLine: GraphicalLine, lineWidth: number, colorOrStyle: string = "black"): Node {
         /* TODO similar checks as in drawLabel
         if (!this.isVisible(new BoundingBox(graphicalLine.Start,)) {
             return;
         }
         */
-        this.drawLine(graphicalLine.Start, graphicalLine.End, colorOrStyle, lineWidth);
+        return this.drawLine(graphicalLine.Start, graphicalLine.End, colorOrStyle, lineWidth);
     }
 
-    protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number): void {
+    protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number): Node {
         // implemented by subclass (VexFlowMusicSheetDrawer)
+        return undefined;
     }
 
     /**
@@ -539,7 +540,7 @@ export abstract class MusicSheetDrawer {
 
     public drawBoundingBox(bbox: BoundingBox,
         color: string = undefined, drawCross: boolean = false, labelText: string = undefined, layer: number = 0
-    ): void {
+    ): Node {
         let tmpRect: RectangleF2D = new RectangleF2D(bbox.AbsolutePosition.x + bbox.BorderMarginLeft,
             bbox.AbsolutePosition.y + bbox.BorderMarginTop,
             bbox.BorderMarginRight - bbox.BorderMarginLeft,
@@ -563,12 +564,14 @@ export abstract class MusicSheetDrawer {
         }
 
         tmpRect = this.applyScreenTransformationForRect(tmpRect);
-        this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, color, 0.5);
+        const rectNode: Node = this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, color, 0.5);
         if (labelText) {
             const label: Label = new Label(labelText);
             this.renderLabel(new GraphicalLabel(label, 0.8, TextAlignmentEnum.CenterCenter, this.rules),
                 layer, tmpRect.width, tmpRect.height, tmpRect.height, new PointF2D(tmpRect.x, tmpRect.y + 12));
+            // theoretically we should return the nodes from renderLabel here as well, so they can also be removed later
         }
+        return rectNode;
     }
 
     private drawMarkedAreas(system: MusicSystem): void {

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

@@ -96,7 +96,7 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
     }
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
                       heightInPixel: number, screenPosition: PointF2D,
-                      color: string = undefined, fontFamily: string = undefined): void  {
+                      color: string = undefined, fontFamily: string = undefined): Node  {
         const old: string = this.CanvasRenderingCtx.font;
         this.CanvasRenderingCtx.save();
         this.CanvasRenderingCtx.font = VexFlowConverter.font(
@@ -111,8 +111,9 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.CanvasRenderingCtx.fillText(text, screenPosition.x, screenPosition.y + heightInPixel);
         this.CanvasRenderingCtx.restore();
         this.CanvasRenderingCtx.font = old;
+        return undefined; // can't return svg dom node
     }
-    public renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number = 1): void {
+    public renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number = 1): Node {
         const old: string | CanvasGradient | CanvasPattern = this.CanvasRenderingCtx.fillStyle;
         if (colorHex) {
             this.CanvasRenderingCtx.fillStyle = colorHex;
@@ -123,9 +124,10 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.ctx.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
         this.CanvasRenderingCtx.fillStyle = old;
         this.CanvasRenderingCtx.globalAlpha = 1;
+        return undefined; // can't return dom node like with SVG
     }
 
-    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number= 2): void {
+    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number= 2): Node {
         const oldStyle: string | CanvasGradient | CanvasPattern = this.CanvasRenderingCtx.strokeStyle;
         this.CanvasRenderingCtx.strokeStyle = color;
         this.CanvasRenderingCtx.beginPath();
@@ -133,6 +135,7 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.CanvasRenderingCtx.lineTo(stop.x, stop.y);
         this.CanvasRenderingCtx.stroke();
         this.CanvasRenderingCtx.strokeStyle = oldStyle;
+        return undefined; // can't return svg dom node
     }
 
     public renderCurve(points: PointF2D[]): void {

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

@@ -8,6 +8,7 @@ import {RectangleF2D} from "../../../Common/DataObjects/RectangleF2D";
 import {PointF2D} from "../../../Common/DataObjects/PointF2D";
 import {BackendType} from "../../../OpenSheetMusicDisplay/OSMDOptions";
 import {EngravingRules} from "../EngravingRules";
+import log from "loglevel";
 
 export class SvgVexFlowBackend extends VexFlowBackend {
 
@@ -53,6 +54,22 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         return this.ctx.svg;
     }
 
+    removeNode(node: Node): boolean {
+        const svg: SVGElement = this.ctx?.svg;
+        if (!svg) {
+            return false;
+        }
+        // unfortunately there's no method svg.hasChild(node). traversing all nodes seems inefficient.
+        try {
+            svg.removeChild(node);
+        } catch (ex) {
+            // log.error("SvgVexFlowBackend.removeNode: error:"); // unnecessary, stacktrace is in exception
+            log.error(ex);
+            return false;
+        }
+        return true;
+    }
+
     public clear(): void {
         if (!this.ctx) {
             return;
@@ -86,8 +103,9 @@ export class SvgVexFlowBackend extends VexFlowBackend {
     }
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
                       heightInPixel: number, screenPosition: PointF2D,
-                      color: string = undefined, fontFamily: string = undefined): void {
+                      color: string = undefined, fontFamily: string = undefined): Node {
         this.ctx.save();
+        const node: Node = this.ctx.openGroup();
 
         if (color) {
             this.ctx.attributes.fill = color;
@@ -122,10 +140,13 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         this.ctx.attributes["font-style"] = fontStyleVexflow;
         this.ctx.state["font-style"] = fontStyleVexflow;
         this.ctx.fillText(text, screenPosition.x, screenPosition.y + heightInPixel);
+        this.ctx.closeGroup();
         this.ctx.restore();
+        return node;
     }
-    public renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number = 1): void {
+    public renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number = 1): Node {
         this.ctx.save();
+        const node: Node = this.ctx.openGroup();
         if (colorHex) {
             this.ctx.attributes.fill = colorHex;
         } else {
@@ -135,10 +156,13 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         this.ctx.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
         this.ctx.restore();
         this.ctx.attributes["fill-opacity"] = 1;
+        this.ctx.closeGroup();
+        return node;
     }
 
-    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2): void {
+    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2): Node {
         this.ctx.save();
+        const node: Node = this.ctx.openGroup();
         this.ctx.beginPath();
         this.ctx.moveTo(start.x, start.y);
         this.ctx.lineTo(stop.x, stop.y);
@@ -151,7 +175,9 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         this.ctx.lineWidth = lineWidth;
 
         this.ctx.stroke();
+        this.ctx.closeGroup();
         this.ctx.restore();
+        return node;
     }
 
     public renderCurve(points: PointF2D[]): void {

+ 3 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts

@@ -90,7 +90,7 @@ public abstract getContext(): Vex.IRenderContext;
   public abstract translate(x: number, y: number): void;
   public abstract renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
                              heightInPixel: number, screenPosition: PointF2D,
-                             color?: string, fontFamily?: string): void;
+                             color?: string, fontFamily?: string): Node;
   /**
    * Renders a rectangle with the given style to the screen.
    * It is given in screen coordinates.
@@ -99,9 +99,9 @@ public abstract getContext(): Vex.IRenderContext;
    * @param styleId the style id
    * @param alpha alpha value between 0 and 1
    */
-  public abstract renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number): void;
+  public abstract renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number): Node;
 
-  public abstract renderLine(start: PointF2D, stop: PointF2D, color: string, lineWidth: number): void;
+  public abstract renderLine(start: PointF2D, stop: PointF2D, color: string, lineWidth: number): Node;
 
   public abstract renderCurve(points: PointF2D[]): void;
 

+ 13 - 9
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -192,14 +192,14 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
     /** Draws a line in the current backend. Only usable while pages are drawn sequentially, because backend reference is updated in that process.
      *  To add your own lines after rendering, use DrawOverlayLine.
      */
-    protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 0.2): void {
+    protected drawLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 0.2): Node {
         // TODO maybe the backend should be given as an argument here as well, otherwise this can't be used after rendering of multiple pages is done.
         start = this.applyScreenTransformation(start);
         stop = this.applyScreenTransformation(stop);
         /*if (!this.backend) {
             this.backend = this.backends[0];
         }*/
-        this.backend.renderLine(start, stop, color, lineWidth * unitInPixels);
+        return this.backend.renderLine(start, stop, color, lineWidth * unitInPixels);
     }
 
     /** Lets a user/developer draw an overlay line on the score. Use this instead of drawLine, which is for OSMD internally only.
@@ -208,7 +208,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
      *  To get a MusicPage, use GraphicalNote.ParentMusicPage.
      */
     public DrawOverlayLine(start: PointF2D, stop: PointF2D, musicPage: GraphicalMusicPage,
-                           color: string = "#FF0000FF", lineWidth: number = 0.2): void {
+                           color: string = "#FF0000FF", lineWidth: number = 0.2): Node {
         if (!musicPage.PageNumber || musicPage.PageNumber > this.backends.length || musicPage.PageNumber < 1) {
             console.log("VexFlowMusicSheetDrawer.drawOverlayLine: invalid page number / music page number doesn't correspond to an existing backend.");
             return;
@@ -218,7 +218,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
         start = this.applyScreenTransformation(start);
         stop = this.applyScreenTransformation(stop);
-        backendToUse.renderLine(start, stop, color, lineWidth * unitInPixels);
+        return backendToUse.renderLine(start, stop, color, lineWidth * unitInPixels);
     }
 
     protected drawSkyLine(staffline: StaffLine): void {
@@ -403,10 +403,11 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
      * @param screenPosition the position of the lower left corner of the text in screen coordinates
      */
     protected renderLabel(graphicalLabel: GraphicalLabel, layer: number, bitmapWidth: number,
-                          bitmapHeight: number, fontHeightInPixel: number, screenPosition: PointF2D): void {
+                          bitmapHeight: number, fontHeightInPixel: number, screenPosition: PointF2D): Node[] {
         if (!graphicalLabel.Label.print) {
-            return;
+            return [];
         }
+        const nodes: Node[] = [];
         const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
         const { font } = graphicalLabel.Label;
         let color: string;
@@ -428,13 +429,16 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             const currLine: {text: string, xOffset: number, width: number} = graphicalLabel.TextLines[i];
             const xOffsetInPixel: number = this.calculatePixelDistance(currLine.xOffset);
             const linePosition: PointF2D = new PointF2D(screenPosition.x + xOffsetInPixel, screenPosition.y);
-            this.backend.renderText(height, fontStyle, font, currLine.text, fontHeightInPixel, linePosition, color, graphicalLabel.Label.fontFamily);
+            nodes.push(
+                this.backend.renderText(height, fontStyle, font, currLine.text, fontHeightInPixel, linePosition, color, graphicalLabel.Label.fontFamily)
+            );
             screenPosition.y = screenPosition.y + fontHeightInPixel;
             if (graphicalLabel.TextLines.length > 1) {
              screenPosition.y += this.rules.SpacingBetweenTextLines;
             }
         }
         // font currently unused, replaced by fontFamily
+        return nodes;
     }
 
     /**
@@ -445,8 +449,8 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
      * @param styleId the style id
      * @param alpha alpha value between 0 and 1
      */
-    protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string, alpha: number): void {
-        this.backend.renderRectangle(rectangle, styleId, colorHex, alpha);
+    protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, colorHex: string, alpha: number): Node {
+        return this.backend.renderRectangle(rectangle, styleId, colorHex, alpha);
     }
 
     /**