aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js')
-rw-r--r--ext/js/data/options-util.js12
-rw-r--r--ext/js/display/pronunciation-generator.js191
-rw-r--r--ext/js/templates/anki-template-renderer.js4
3 files changed, 205 insertions, 2 deletions
diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js
index d89b3370..af81f29e 100644
--- a/ext/js/data/options-util.js
+++ b/ext/js/data/options-util.js
@@ -534,7 +534,8 @@ export class OptionsUtil {
this._updateVersion25,
this._updateVersion26,
this._updateVersion27,
- this._updateVersion28
+ this._updateVersion28,
+ this._updateVersion29
];
/* eslint-enable @typescript-eslint/unbound-method */
if (typeof targetVersion === 'number' && targetVersion < result.length) {
@@ -1209,6 +1210,15 @@ export class OptionsUtil {
}
/**
+ * - Added new handlebar for different pitch accent graph style.
+ * @type {import('options-util').UpdateFunction}
+ */
+ async _updateVersion29(options) {
+ await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v29.handlebars');
+ }
+
+
+ /**
* @param {string} url
* @returns {Promise<chrome.tabs.Tab>}
*/
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;
+}
diff --git a/ext/js/templates/anki-template-renderer.js b/ext/js/templates/anki-template-renderer.js
index 4bb56a4b..ae3e7a36 100644
--- a/ext/js/templates/anki-template-renderer.js
+++ b/ext/js/templates/anki-template-renderer.js
@@ -19,7 +19,7 @@
import {Handlebars} from '../../lib/handlebars.js';
import {createAnkiNoteData} from '../data/anki-note-data-creator.js';
import {getPronunciationsOfType, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js';
-import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from '../display/pronunciation-generator.js';
+import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationGraphJJ, createPronunciationText} from '../display/pronunciation-generator.js';
import {StructuredContentGenerator} from '../display/structured-content-generator.js';
import {CssStyleApplier} from '../dom/css-style-applier.js';
import {convertHiraganaToKatakana, convertKatakanaToHiragana, distributeFurigana, getKanaMorae, getPitchCategory, isMoraPitchHigh} from '../language/ja/japanese.js';
@@ -741,6 +741,8 @@ export class AnkiTemplateRenderer {
}
case 'graph':
return this._getPronunciationHtml(createPronunciationGraph(morae, downstepPosition));
+ case 'graph-jj':
+ return this._getPronunciationHtml(createPronunciationGraphJJ(morae, downstepPosition));
case 'position':
return this._getPronunciationHtml(createPronunciationDownstepPosition(downstepPosition));
default: