SvgVexFlowBackend.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import Vex from "vexflow";
  2. import {VexFlowBackend} from "./VexFlowBackend";
  3. import {VexFlowConverter} from "./VexFlowConverter";
  4. import {FontStyles} from "../../../Common/Enums/FontStyles";
  5. import {Fonts} from "../../../Common/Enums/Fonts";
  6. import {RectangleF2D} from "../../../Common/DataObjects/RectangleF2D";
  7. import {PointF2D} from "../../../Common/DataObjects/PointF2D";
  8. import {BackendType} from "../../../OpenSheetMusicDisplay/OSMDOptions";
  9. import {EngravingRules} from "../EngravingRules";
  10. export class SvgVexFlowBackend extends VexFlowBackend {
  11. private ctx: Vex.Flow.SVGContext;
  12. private zoom: number;
  13. constructor(rules: EngravingRules) {
  14. super();
  15. this.rules = rules;
  16. }
  17. public getVexflowBackendType(): Vex.Flow.Renderer.Backends {
  18. return Vex.Flow.Renderer.Backends.SVG;
  19. }
  20. public getOSMDBackendType(): BackendType {
  21. return BackendType.SVG;
  22. }
  23. public getCanvasSize(): number {
  24. return document.getElementById("osmdCanvasPage" + this.graphicalMusicPage.PageNumber)?.offsetHeight;
  25. }
  26. public initialize(container: HTMLElement, zoom: number): void {
  27. this.zoom = zoom;
  28. this.canvas = document.createElement("div");
  29. this.canvas.id = "osmdCanvasPage" + this.graphicalMusicPage.PageNumber;
  30. // this.canvas.id = uniqueID // TODO create unique tagName like with cursor now?
  31. this.inner = this.canvas;
  32. this.inner.style.position = "relative";
  33. this.canvas.style.zIndex = "0";
  34. container.appendChild(this.inner);
  35. this.renderer = new Vex.Flow.Renderer(this.canvas, this.getVexflowBackendType());
  36. this.ctx = <Vex.Flow.SVGContext>this.renderer.getContext();
  37. this.ctx.svg.id = "osmdSvgPage" + this.graphicalMusicPage.PageNumber;
  38. }
  39. public getContext(): Vex.Flow.SVGContext {
  40. return this.ctx;
  41. }
  42. public getSvgElement(): SVGElement {
  43. return this.ctx.svg;
  44. }
  45. public clear(): void {
  46. if (!this.ctx) {
  47. return;
  48. }
  49. //const { svg } = this.ctx; // seems to make svg static between osmd instances.
  50. const svg: SVGElement = this.ctx.svg;
  51. // removes all children from the SVG element,
  52. // effectively clearing the SVG viewport
  53. while (svg.lastChild) {
  54. svg.removeChild(svg.lastChild);
  55. }
  56. // set background color if not transparent
  57. if (this.rules.PageBackgroundColor) {
  58. this.ctx.save();
  59. // note that this will hide the cursor
  60. this.ctx.setFillStyle(this.rules.PageBackgroundColor);
  61. this.ctx.setStrokeStyle("#12345600"); // transparent
  62. this.ctx.fillRect(0, 0, this.canvas.offsetWidth / this.zoom, this.canvas.offsetHeight / this.zoom);
  63. this.ctx.restore();
  64. }
  65. }
  66. public scale(k: number): void {
  67. this.ctx.scale(k, k);
  68. }
  69. public translate(x: number, y: number): void {
  70. // TODO: implement this
  71. }
  72. public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
  73. heightInPixel: number, screenPosition: PointF2D,
  74. color: string = undefined, fontFamily: string = undefined): void {
  75. this.ctx.save();
  76. if (color) {
  77. this.ctx.attributes.fill = color;
  78. this.ctx.attributes.stroke = color;
  79. }
  80. let fontFamilyVexFlow: string = fontFamily;
  81. if (!fontFamily || fontFamily === "default") {
  82. fontFamilyVexFlow = this.rules.DefaultFontFamily;
  83. }
  84. this.ctx.setFont(fontFamilyVexFlow, fontHeight, VexFlowConverter.fontStyle(fontStyle));
  85. // font size is set by VexFlow in `pt`. This overwrites the font so it's set to px instead
  86. this.ctx.attributes["font-size"] = `${fontHeight}px`;
  87. this.ctx.state["font-size"] = `${fontHeight}px`;
  88. let fontWeightVexflow: string = "normal";
  89. let fontStyleVexflow: string = "normal";
  90. switch (fontStyle) {
  91. case FontStyles.Bold:
  92. fontWeightVexflow = "bold";
  93. break;
  94. case FontStyles.Italic:
  95. fontStyleVexflow = "italic";
  96. break;
  97. case FontStyles.BoldItalic:
  98. fontWeightVexflow = "bold";
  99. fontStyleVexflow = "italic";
  100. break;
  101. default:
  102. fontWeightVexflow = "normal";
  103. }
  104. this.ctx.attributes["font-weight"] = fontWeightVexflow;
  105. this.ctx.state["font-weight"] = fontWeightVexflow;
  106. this.ctx.attributes["font-style"] = fontStyleVexflow;
  107. this.ctx.state["font-style"] = fontStyleVexflow;
  108. this.ctx.fillText(text, screenPosition.x, screenPosition.y + heightInPixel);
  109. this.ctx.restore();
  110. }
  111. public renderRectangle(rectangle: RectangleF2D, styleId: number, alpha: number = 1): void {
  112. this.ctx.save();
  113. this.ctx.attributes.fill = VexFlowConverter.style(styleId);
  114. this.ctx.attributes["fill-opacity"] = alpha;
  115. this.ctx.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
  116. this.ctx.restore();
  117. this.ctx.attributes["fill-opacity"] = 1;
  118. }
  119. public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2): void {
  120. this.ctx.save();
  121. this.ctx.beginPath();
  122. this.ctx.moveTo(start.x, start.y);
  123. this.ctx.lineTo(stop.x, stop.y);
  124. this.ctx.attributes.stroke = color;
  125. //this.ctx.attributes.strokeStyle = color;
  126. //this.ctx.attributes["font-weight"] = "bold";
  127. //this.ctx.attributes["stroke-linecap"] = "round";
  128. this.ctx.lineWidth = lineWidth;
  129. this.ctx.stroke();
  130. this.ctx.restore();
  131. }
  132. public renderCurve(points: PointF2D[]): void {
  133. this.ctx.beginPath();
  134. this.ctx.moveTo(points[0].x, points[0].y);
  135. this.ctx.bezierCurveTo(
  136. points[1].x,
  137. points[1].y,
  138. points[2].x,
  139. points[2].y,
  140. points[3].x,
  141. points[3].y
  142. );
  143. this.ctx.lineTo(points[7].x, points[7].y);
  144. this.ctx.bezierCurveTo(
  145. points[6].x,
  146. points[6].y,
  147. points[5].x,
  148. points[5].y,
  149. points[4].x,
  150. points[4].y
  151. );
  152. this.ctx.lineTo(points[0].x, points[0].y);
  153. //this.ctx.stroke();
  154. this.ctx.closePath();
  155. this.ctx.fill();
  156. }
  157. public export(): void {
  158. // See: https://stackoverflow.com/questions/38477972/javascript-save-svg-element-to-file-on-disk
  159. // first create a clone of our svg node so we don't mess the original one
  160. const clone: SVGElement = (this.ctx.svg.cloneNode(true) as SVGElement);
  161. // create a doctype that is SVG
  162. const svgDocType: DocumentType = document.implementation.createDocumentType(
  163. "svg",
  164. "-//W3C//DTD SVG 1.1//EN",
  165. "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
  166. );
  167. // Create a new svg document
  168. const svgDoc: Document = document.implementation.createDocument("http://www.w3.org/2000/svg", "svg", svgDocType);
  169. // replace the documentElement with our clone
  170. svgDoc.replaceChild(clone, svgDoc.documentElement);
  171. // get the data
  172. const svgData: string = (new XMLSerializer()).serializeToString(svgDoc);
  173. // now you've got your svg data, the following will depend on how you want to download it
  174. // e.g yo could make a Blob of it for FileSaver.js
  175. /*
  176. var blob = new Blob([svgData.replace(/></g, '>\n\r<')]);
  177. saveAs(blob, 'myAwesomeSVG.svg');
  178. */
  179. // here I'll just make a simple a with download attribute
  180. const a: HTMLAnchorElement = document.createElement("a");
  181. a.href = "data:image/svg+xml; charset=utf8, " + encodeURIComponent(svgData.replace(/></g, ">\n\r<"));
  182. a.download = "opensheetmusicdisplay_download.svg";
  183. a.innerHTML = window.location.href + "/download";
  184. document.body.appendChild(a);
  185. }
  186. }