diff options
-rw-r--r-- | ext/data/templates/anki-field-templates-upgrade-v29.handlebars | 13 | ||||
-rw-r--r-- | ext/data/templates/default-anki-field-templates.handlebars | 4 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 12 | ||||
-rw-r--r-- | ext/js/display/pronunciation-generator.js | 191 | ||||
-rw-r--r-- | ext/js/templates/anki-template-renderer.js | 4 | ||||
-rw-r--r-- | ext/settings.html | 4 | ||||
-rw-r--r-- | test/options-util.test.js | 2 |
7 files changed, 227 insertions, 3 deletions
diff --git a/ext/data/templates/anki-field-templates-upgrade-v29.handlebars b/ext/data/templates/anki-field-templates-upgrade-v29.handlebars new file mode 100644 index 00000000..e19d66c4 --- /dev/null +++ b/ext/data/templates/anki-field-templates-upgrade-v29.handlebars @@ -0,0 +1,13 @@ +{{<<<<<<<}} +{{#*inline "pitch-accent-graphs"}} + {{~> pitch-accent-list format='graph'~}} +{{/inline}} +{{=======}} +{{#*inline "pitch-accent-graphs"}} + {{~> pitch-accent-list format='graph'~}} +{{/inline}} + +{{#*inline "pitch-accent-graphs-jj"}} + {{~> pitch-accent-list format='graph-jj'~}} +{{/inline}} +{{>>>>>>>}}
\ No newline at end of file diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index 0495af34..46d5c889 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -228,6 +228,10 @@ {{~> pitch-accent-list format='graph'~}} {{/inline}} +{{#*inline "pitch-accent-graphs-jj"}} + {{~> pitch-accent-list format='graph-jj'~}} +{{/inline}} + {{#*inline "pitch-accent-positions"}} {{~> pitch-accent-list format='position'~}} {{/inline}} 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: diff --git a/ext/settings.html b/ext/settings.html index 441e26df..773d7cb2 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -2979,6 +2979,10 @@ <td>List of pitch accent graphs for the term.</td> </tr> <tr> + <td><code class="anki-field-marker">{pitch-accent-graphs-jj}</code></td> + <td>List of pitch accent graphs for the term (styled after Jidoujisho).</td> + </tr> + <tr> <td><code class="anki-field-marker">{pitch-accent-positions}</code></td> <td>List of accent downstep positions for the term as a number.</td> </tr> diff --git a/test/options-util.test.js b/test/options-util.test.js index be881d15..ebee0765 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -599,7 +599,7 @@ function createOptionsUpdatedTestData1() { } ], profileCurrent: 0, - version: 28, + version: 29, global: { database: { prefixWildcardsSupported: false |