From 1d52f94379730ef587de87ce3ca8f1a30ac0a44a Mon Sep 17 00:00:00 2001
From: m-edlund <me@fwegmann.com>
Date: Tue, 9 Apr 2024 10:53:00 +0200
Subject: feature: add handlebar for jidoujisho pitch graph (#773)

* feat: add handlebar for jidoujisho pitch graph

* fix: update handlebar upgrade test

---------

Co-authored-by: StefanVukovic99 <stefanvukovic44@gmail.com>
---
 ext/js/display/pronunciation-generator.js | 191 ++++++++++++++++++++++++++++++
 1 file changed, 191 insertions(+)

(limited to 'ext/js/display')

diff --git a/ext/js/display/pronunciation-generator.js b/ext/js/display/pronunciation-generator.js
index 2c03eb94..e66df23c 100644
--- a/ext/js/display/pronunciation-generator.js
+++ b/ext/js/display/pronunciation-generator.js
@@ -234,3 +234,194 @@ function createGraphCircle(svgns, className, x, y, radius) {
     node.setAttribute('r', radius);
     return node;
 }
+
+// The following Jidoujisho pitch graph code is based on code from
+// https://github.com/lrorpilla/jidoujisho licensed under the
+// GNU General Public License v3.0
+
+/**
+ * Create a pronounciation graph in the style of Jidoujisho
+ * @param {string[]} mora
+ * @param {number} downstepPosition
+ * @returns {SVGSVGElement}
+ */
+export function createPronunciationGraphJJ(mora, downstepPosition) {
+    const patt = pitchValueToPattJJ(mora.length, downstepPosition);
+
+    const positions = Math.max(mora.length, patt.length);
+    const stepWidth = 35;
+    const marginLr = 16;
+    const svgWidth = Math.max(0, ((positions - 1) * stepWidth) + (marginLr * 2));
+
+    const svgns = 'http://www.w3.org/2000/svg';
+    const svg = document.createElementNS(svgns, 'svg');
+    svg.setAttribute('xmlns', svgns);
+    svg.setAttribute('width', `${(svgWidth * (3 / 5))}px`);
+    svg.setAttribute('height', '45px');
+    svg.setAttribute('viewBox', `0 0 ${svgWidth} 75`);
+
+
+    if (mora.length <= 0) { return svg; }
+
+    for (let i = 0; i < mora.length; i++) {
+        const xCenter = marginLr + (i * stepWidth);
+        textJJ(xCenter - 11, mora[i], svgns, svg);
+    }
+
+
+    let pathType = '';
+
+    const circles = [];
+    const paths = [];
+
+    let prevCenter = [-1, -1];
+    for (let i = 0; i < patt.length; i++) {
+        const xCenter = marginLr + (i * stepWidth);
+        const accent = patt[i];
+        let yCenter = 0;
+        if (accent === 'H') {
+            yCenter = 5;
+        } else if (accent === 'L') {
+            yCenter = 30;
+        }
+        circles.push(circleJJ(xCenter, yCenter, i >= mora.length, svgns));
+
+
+        if (i > 0) {
+            if (prevCenter[1] === yCenter) {
+                pathType = 's';
+            } else if (prevCenter[1] < yCenter) {
+                pathType = 'd';
+            } else if (prevCenter[1] > yCenter) {
+                pathType = 'u';
+            }
+            paths.push(pathJJ(prevCenter[0], prevCenter[1], pathType, stepWidth, svgns));
+        }
+        prevCenter = [xCenter, yCenter];
+    }
+
+    for (const path of paths) {
+        svg.appendChild(path);
+    }
+
+    for (const circle of circles) {
+        svg.appendChild(circle);
+    }
+
+    return svg;
+}
+
+/**
+ * Get H&L pattern
+ * @param {number} numberOfMora
+ * @param {number} pitchValue
+ * @returns {string}
+ */
+function pitchValueToPattJJ(numberOfMora, pitchValue) {
+    if (numberOfMora >= 1) {
+        if (pitchValue === 0) {
+        // Heiban
+            return `L${'H'.repeat(numberOfMora)}`;
+        } else if (pitchValue === 1) {
+        // Atamadaka
+            return `H${'L'.repeat(numberOfMora)}`;
+        } else if (pitchValue >= 2) {
+            const stepdown = pitchValue - 2;
+            return `LH${'H'.repeat(stepdown)}${'L'.repeat(numberOfMora - pitchValue + 1)}`;
+        }
+    }
+    return '';
+}
+
+/**
+ * @param {number} x
+ * @param {number} y
+ * @param {boolean} o
+ * @param {string} svgns
+ * @returns {Element}
+ */
+function circleJJ(x, y, o, svgns) {
+    if (o) {
+        const node = document.createElementNS(svgns, 'circle');
+
+        node.setAttribute('r', '4');
+        node.setAttribute('cx', `${(x + 4)}`);
+        node.setAttribute('cy', `${y}`);
+        node.setAttribute('stroke', 'currentColor');
+        node.setAttribute('stroke-width', '2');
+        node.setAttribute('fill', 'none');
+
+        return node;
+    } else {
+        const node = document.createElementNS(svgns, 'circle');
+
+        node.setAttribute('r', '5');
+        node.setAttribute('cx', `${x}`);
+        node.setAttribute('cy', `${y}`);
+        node.setAttribute('style', 'opacity:1;fill:currentColor;');
+
+        return node;
+    }
+}
+
+/**
+ * @param {number} x
+ * @param {string} mora
+ * @param {string} svgns
+ * @param {SVGSVGElement} svg
+ * @returns {void}
+ */
+function textJJ(x, mora, svgns, svg) {
+    if (mora.length === 1) {
+        const path = document.createElementNS(svgns, 'text');
+        path.setAttribute('x', `${x}`);
+        path.setAttribute('y', '67.5');
+        path.setAttribute('style', 'font-size:20px;font-family:sans-serif;fill:currentColor;');
+        path.textContent = mora;
+        svg.appendChild(path);
+    } else {
+        const path1 = document.createElementNS(svgns, 'text');
+        path1.setAttribute('x', `${x - 5}`);
+        path1.setAttribute('y', '67.5');
+        path1.setAttribute('style', 'font-size:20px;font-family:sans-serif;fill:currentColor;');
+        path1.textContent = mora[0];
+        svg.appendChild(path1);
+
+
+        const path2 = document.createElementNS(svgns, 'text');
+        path2.setAttribute('x', `${x + 12}`);
+        path2.setAttribute('y', '67.5');
+        path2.setAttribute('style', 'font-size:14px;font-family:sans-serif;fill:currentColor;');
+        path2.textContent = mora[1];
+        svg.appendChild(path2);
+    }
+}
+
+/**
+ * @param {number} x
+ * @param {number} y
+ * @param {string} type
+ * @param {number} stepWidth
+ * @param {string} svgns
+ * @returns {Element}
+ */
+function pathJJ(x, y, type, stepWidth, svgns) {
+    let delta = '';
+    switch (type) {
+        case 's':
+            delta = stepWidth + ',0';
+            break;
+        case 'u':
+            delta = stepWidth + ',-25';
+            break;
+        case 'd':
+            delta = stepWidth + ',25';
+            break;
+    }
+
+    const path = document.createElementNS(svgns, 'path');
+    path.setAttribute('d', `m ${x},${y} ${delta}`);
+    path.setAttribute('style', 'fill:none;stroke:currentColor;stroke-width:1.5;');
+
+    return path;
+}
-- 
cgit v1.2.3