Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/visual/render-abc-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@ Default: undefined

Add a tablature-style staff below the standard music output. See [the tablature documentation](./tablature.md) for details.

## useDefsForGlyphs
Default: false

When this option is set each glyph path will be defined only once. The singleton glyph path is added to a `<defs>` group on first use. Each instance of the glyph is a `<use>` element with its own attributes.

```svg
<svg>
<defs>
<path id="abcjs-noteheads.quarter" d="[...]" />
</defs>
<g>
<use data-name="noteheads.quarter"
xlink:href="#abcjs-noteheads.quarter"
x="100" y="200" />
<use data-name="noteheads.quarter"
xlink:href="#abcjs-noteheads.quarter"
x="120" y="200" />
</g>
</svg>
```

This option produces a more compact SVG dom in the brower, requiring less path data to be parsed. If the SVG element is serialized to a file, the file size will also be smaller due to glyph path data not being duplicated.

There should be no visual difference in the rendering of the SVG. If other dom handling relies on the structure providing a path element for each glyph the resulting dom may not be compatible.

::: tip NOTE
Used in combination with the `oneLinePerSvg` option there will be a `<defs>` block in only one of the resulting SVG staff line elements. The assumption is that the individual SVG elements will be part of the same container dom, so the `xlink:href` attribute will still be a valid dom element id.
:::


## viewportHorizontal
Default: false

Expand Down
12 changes: 9 additions & 3 deletions src/write/creation/glyphs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/write/draw/relative.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function drawRelativeElement(renderer, params, bartop) {
}

function scaleExistingElem(paper, elem, scaleX, scaleY, x, y) {
paper.setAttributeOnElement(elem, { style: "transform:scale(" + scaleX + "," + scaleY + ");transform-origin:" + x + "px " + y + "px;" });
paper.setAttributeOnElement(elem, { style: "transform:scale(" + scaleX + "," + scaleY + ");transform-origin:" + x.toFixed(3) + "px " + y.toFixed(3) + "px;" });
}

module.exports = drawRelativeElement;
8 changes: 7 additions & 1 deletion src/write/engraver-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var EngraverController = function (paper, params) {
this.addSelectListener(params.clickListener);

this.renderer = new Renderer(paper);
this.renderer.paper.useDefsForGlyphs = params.useDefsForGlyphs
this.renderer.setPaddingOverride(params);
if (params.showDebug)
this.renderer.showDebug = params.showDebug;
Expand Down Expand Up @@ -326,6 +327,7 @@ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
if (responsive === 'resize')
output.style.paddingBottom = ''
var style = source.querySelector("style")
var defs = source.querySelector("defs")
var width = responsive === 'resize' ? source.viewBox.baseVal.width : source.getAttribute("width")
var sections = output.querySelectorAll("svg > g") // each section is a line, or the top matter or the bottom matter, or text that has been inserted.
var nextTop = 0 // There are often gaps between the elements for spacing, so the actual top and height needs to be inferred.
Expand All @@ -342,6 +344,9 @@ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
divStyles += "height:" + (height * scale) + "px;"
wrapper.setAttribute("style", divStyles)
var svg = duplicateSvg(source)
// NOTE: the defs element is only needed once in the dom
// as each SVG line can reference ids from another svg
if (defs && i == 0) svg.appendChild(defs)
var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1)
svg.setAttribute("aria-label", fullTitle)
if (responsive !== 'resize')
Expand All @@ -350,7 +355,7 @@ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
svg.style.position = ''
// TODO-PER: Hack! Not sure why this is needed.
var viewBoxHeight = renderer.firefox112 ? height+1 : height
svg.setAttribute("viewBox", "0 " + nextTop + " " + width + " " + viewBoxHeight)
svg.setAttribute("viewBox", "0 " + nextTop.toFixed(3) + " " + width.toFixed(3) + " " + viewBoxHeight.toFixed(3))
svg.appendChild(style.cloneNode(true))
var titleEl = document.createElement("title")
titleEl.innerText = fullTitle
Expand All @@ -372,6 +377,7 @@ function splitSvgIntoLines(renderer, output, title, responsive, scale) {
function duplicateSvg(source) {
var svgNS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(svgNS, "svg");
svg.setAttribute('xmlns', svgNS)
for (var i = 0; i < source.attributes.length; i++) {
var attr = source.attributes[i];
if (attr.name !== "height" && attr.name != "aria-label")
Expand Down
35 changes: 34 additions & 1 deletion src/write/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/*global module */

var svgNS = "http://www.w3.org/2000/svg";
var xlinkNS = "http://www.w3.org/1999/xlink";

function Svg(wrapper) {
this.svg = createSvg();
Expand Down Expand Up @@ -32,7 +33,7 @@ Svg.prototype.setTitle = function (title) {

Svg.prototype.setResponsiveWidth = function (w, h) {
// this technique is from: http://thenewcode.com/744/Make-SVG-Responsive, thx to https://github.com/iantresman
this.svg.setAttribute("viewBox", "0 0 " + w + " " + h);
this.svg.setAttribute("viewBox", "0 0 " + w.toFixed(3) + " " + h.toFixed(3));
this.svg.setAttribute("preserveAspectRatio", "xMinYMin meet");
this.svg.removeAttribute("height");
this.svg.removeAttribute("width");
Expand Down Expand Up @@ -343,6 +344,38 @@ Svg.prototype.closeGroup = function () {
return g;
};

Svg.prototype.use = function (attr) {
if (!attr.hasOwnProperty('id')) return; // caller must provide an id
if (!attr.hasOwnProperty('path')) return; // caller must provide a path
var defs = document.querySelector("defs");
if (!defs) {
defs = document.createElementNS(svgNS, "defs");
this.svg.appendChild(defs);
}
var use = document.createElementNS(svgNS, "use");
var path = document.getElementById(attr.id);
if (!path) {
// first use of glyph - create its path element
path = document.createElementNS(svgNS, "path");
path.setAttributeNS(null, 'd', attr.path);
path.setAttributeNS(null, 'id', attr.id);
defs.append(path);
}
for (var key in attr) {
if (key === 'path') {
// ignore it here. it's already been added to the defs path element.
} else if (key === 'id') {
// link this <use> element to the corresponding glyph path def by id
use.setAttributeNS(xlinkNS, 'xlink:href', '#' + attr[key]);
} else if (key === 'klass') {
use.setAttributeNS(null, "class", attr[key]);
} else if (attr[key] !== undefined)
use.setAttributeNS(null, key, attr[key]);
}
this.append(use);
return use;
};

Svg.prototype.path = function (attr) {
var el = document.createElementNS(svgNS, "path");
for (var key in attr) {
Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ declare module 'abcjs' {
tablature?: Array<Tablature>;
textboxpadding?: number;
timeBasedLayout?: { minPadding?:number, minWidth?:number, align?: 'left'|'center'};
useDefsForGlyphs?: boolean;
viewportHorizontal?: boolean;
viewportVertical?: boolean;
visualTranspose?: number;
Expand Down