At this point, here's the complete all-in-one .ly template. Much cleaner and without risks to mess the code, for the user. In addition, it has not to be patched: just open and use the generated .svg file. Hope it could be included in the snippets repo. Thanks all for your collaboration!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \version "2.19.45" JSSVGSlurTuner = #(define-void-function (body) (string?) (let* ((mod (resolve-module '(scm framework-svg))) (svg-end (module-ref mod 'svg-end #f))) (if (procedure? svg-end) (module-define! mod 'svg-end (lambda () (string-join (list "<script type=\"text/javascript\"><![CDATA[" body "]]></script>" (svg-end)) "\n")))))) JSSVGSlurTunerScript = #" pixelsX = document.querySelector('svg').getAttribute('width').replace('mm', '') * 96 / 25.4 pixelsY = document.querySelector('svg').getAttribute('height').replace('mm', '') * 96 / 25.4 scaleX = document.querySelector('svg').getAttribute('viewBox').split(' ')[2] / pixelsX scaleY = document.querySelector('svg').getAttribute('viewBox').split(' ')[3] / pixelsY var slurId = 0 var currCp = null function setCpsOnPath(path, x1, y1, x2, y2, x3, y3, x4, y4) { path.setAttribute('d', 'M ' + x1 + ',' + y1 + ' ' + 'C ' + x2 + ',' + y2 + ' ' + x3 + ',' + y3 + ' ' + x4 + ',' + y4) } function initSlur(node) { //already initialized if (node.hasAttribute('id')) return cpsCounter = 1 lineCounter = 1 for (var child = node.firstChild; child !== null; child = child.nextSibling) { if (child.nodeName == 'g') { for (var child2 = child.firstChild; child2 !== null; child2 = child2.nextSibling) { //console.log(child2.nodeName) if (child2.nodeName == 'circle') { if (!node.hasAttribute('cp' + cpsCounter + 'x')) { //TODO Ugly parsering, replace with a proper and safer one node.setAttribute('cp' + cpsCounter + 'x', child2.getAttribute('transform').replace('translate(', '').split(',')[0]) node.setAttribute('cp' + cpsCounter + 'y', child2.getAttribute('transform').split(',')[1].trim().replace(')', '')) child2.setAttribute('id', slurId + '_cp_' + cpsCounter) child2.setAttribute('onmousedown', 'selectCp(this)') } cpsCounter++ } if (child2.nodeName == 'line') { child2.setAttribute('id', slurId + '_line_' + lineCounter) lineCounter++ } } } //remove 'transform' attribute and set abs coords if (child.nodeName == 'path') { if (child.hasAttribute('transform')) child.removeAttribute('transform') setCpsOnPath(child, node.getAttribute('cp1x'), node.getAttribute('cp1y'), node.getAttribute('cp2x'), node.getAttribute('cp2y'), node.getAttribute('cp3x'), node.getAttribute('cp3y'), node.getAttribute('cp4x'), node.getAttribute('cp4y')) child.setAttribute('id', slurId + '_path') child.setAttribute('fill', 'none') } } node.setAttribute('id', slurId) coords = document.createElementNS('http://www.w3.org/2000/svg', 'text'); coords.setAttribute('transform', 'translate(' + node.getAttribute('cp1x') + ',' + node.getAttribute('cp1y') + ')') coords.setAttribute('font-size', '2') coords.setAttribute('class', 'lilySlurCoords') coords.setAttribute('id', slurId + '_coords') node.appendChild(coords) slurId++ } function selectCp(cp) { if (!cp.hasAttribute('id')) return if (!detectLeftButton(event)) { event.preventDefault() showCoords(cp.getAttribute('id').split('_')[0]) return } cp.setAttribute('color', 'cyan') currCp = cp } function unselectCp() { if (currCp) currCp.setAttribute('color', 'rgb(255, 127, 0)') currCp = null } function moveCp() { if (!currCp) return currCp.setAttribute('transform', 'translate(' + event.pageX * scaleX + ',' + event.pageY * scaleY + ')') //get the associated path assocSlurId = currCp.getAttribute('id').split('_')[0] path = document.getElementById(assocSlurId + '_path') xs = [] ys = [] for (i = 0; i < 4; i++) { //TODO ugly parser again! xs[i] = document.getElementById(assocSlurId + '_cp_' + (i + 1)).getAttribute('transform').replace('translate(', '').split(',')[0] ys[i] = document.getElementById(assocSlurId + '_cp_' + (i + 1)).getAttribute('transform').split(',')[1].trim().replace(')', '') } for (i = 0; i < 3; i++) { document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('transform', 'translate(' + xs[i] + ',' + ys[i] + ')') document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('x2', xs[i + 1] - xs[i]) document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('y2', ys[i + 1] - ys[i]) } setCpsOnPath(path, xs[0], ys[0], xs[1], ys[1], xs[2], ys[2], xs[3], ys[3]) } function showCoords(assocSlurId) { xsOrig = [] xsNew = [] ysOrig = [] ysNew = [] coordsToDisplay = '\\\\shape #\\'(' for (q = 0; q < 4; q++) { xsOrig[q] = document.getElementById(assocSlurId).getAttribute('cp' + (q + 1) + 'x') xsNew[q] = document.getElementById(assocSlurId + '_cp_' + (q + 1)).getAttribute('transform').replace('translate(', '').split(',')[0] } for (q = 0; q < 4; q++) { ysOrig[q] = document.getElementById(assocSlurId).getAttribute('cp' + (q + 1) + 'y') ysNew[q] = document.getElementById(assocSlurId + '_cp_' + (q + 1)).getAttribute('transform').split(',')[1].trim().replace(')', '') } for (q = 0; q < 4; q++) { diffX = (xsNew[q] - xsOrig[q]).toFixed(3) diffY = (ysOrig[q] - ysNew[q]).toFixed(3) coordsToDisplay += '(' + (+diffX) + ' . ' + (+diffY) + ') ' } coordsToDisplay = coordsToDisplay.substring(0, coordsToDisplay.length -1) coordsToDisplay += ') ' + document.getElementById(assocSlurId).getAttribute('slurtype') alert(coordsToDisplay) } function detectLeftButton(evt) { evt = evt || window.event; if ('buttons' in evt) { return evt.buttons == 1; } var button = evt.which || evt.button; return button == 1; } window.oncontextmenu = (e) => { e.preventDefault(); } var as = document.querySelectorAll('a') //Remove all 'a' tags for (var i = 0; i < as.length; i++) { as[i].replaceWith(...as[i].childNodes) } slurs = document.querySelectorAll('svg .lilySlur') for (i = 0; i < slurs.length; i++) { initSlur(slurs[i]) } document.addEventListener('mouseup', unselectCp) document.addEventListener('mousemove', moveCp) " addJSSVGSlurTuner = \JSSVGSlurTuner \JSSVGSlurTunerScript addControlPoints = #(grob-transformer 'stencil (lambda (grob orig) (define (draw-control-point pt) #{ \markup \translate $pt \with-color #'(1 .7 .7) \draw-circle #0.6 #0 ##t #}) (define (draw-control-segment pt1 pt2) (let ((delta (cons (- (car pt2) (car pt1)) (- (cdr pt2) (cdr pt1))))) #{ \markup \translate $pt1 \with-color #'(1 .5 0) \draw-line $delta #})) (let* ((pts (ly:grob-property grob 'control-points)) (dots (map (lambda (pt) (grob-interpret-markup grob (draw-control-point pt))) pts)) (lines (map (lambda (pt1 pt2) (grob-interpret-markup grob (draw-control-segment pt1 pt2))) pts (cdr pts)))) (ly:stencil-add (apply ly:stencil-add lines) (apply ly:stencil-add dots) orig)))) showControlPoints = { \override PhrasingSlur.stencil = #addControlPoints \override PhrasingSlur.output-attributes = #'((class . "lilySlur")(slurtype . "PhrasingSlur")) \override Slur.stencil = #addControlPoints \override Slur.output-attributes = #'((class . "lilySlur")(slurtype . "Slur")) \override Tie.stencil = #addControlPoints \override Tie.output-attributes = #'((class . "lilySlur")(slurtype . "Tie")) } %% How it works: %% %% 1) Produce SVG output file(s) ( compile with -dbackend=svg ) %% 2) Open the SVG file(s) with any viewer (it works with Firefox and Chrome browsers too) %% 3) Modify slurs/ties with the mouse by moving their control points %% 4) Right click on one of the control points of a modified slur/tie, %% and copy and paste the expression generated by the script to the .ly file, just before the corresponding slur/tie. Recompile. \score { { \showControlPoints g4_\( a' b'2~ | b'2( e''8 d'') c'4\) a'\( c'\) d' e' } } \addJSSVGSlurTuner %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% On Sun, Dec 15, 2019 at 3:54 AM Étienne Beaulé <beauleetien...@gmail.com> wrote: > I wouldn't worry so much about overriding that define, as svg-end can > only be used at that one spot or the output file would be corrupted. > > A patch that included a proper override was provided 9 years but > ignored. I'll get the relevant parts of the patch up-to-date as it is > proving potential. > > Thanks for your solution; I was discussing with OP on IRC the > potential ways to bodge the feature, but came up a bit short on both > your suggestions, Aaron. Also I didn't know about those undocumented > features! > > Étienne >