aboutsummaryrefslogtreecommitdiff
path: root/ext/js/display/pronunciation-generator.js
diff options
context:
space:
mode:
authorm-edlund <me@fwegmann.com>2024-04-09 10:53:00 +0200
committerGitHub <noreply@github.com>2024-04-09 08:53:00 +0000
commit1d52f94379730ef587de87ce3ca8f1a30ac0a44a (patch)
treeb812a0aed52e415184e058d5f38e1f234ddaef76 /ext/js/display/pronunciation-generator.js
parent11e58d616cffbadc5a0c6ab72e5fc803f8c4e70b (diff)
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>
Diffstat (limited to 'ext/js/display/pronunciation-generator.js')
-rw-r--r--ext/js/display/pronunciation-generator.js191
1 files changed, 191 insertions, 0 deletions
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;
+}