diff options
Diffstat (limited to 'ext/js/display')
-rw-r--r-- | ext/js/display/display-generator.js | 98 | ||||
-rw-r--r-- | ext/js/display/pronunciation-generator.js | 145 |
2 files changed, 150 insertions, 93 deletions
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index d7ae3bd9..15c5787d 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -18,6 +18,7 @@ /* global * DictionaryDataUtil * HtmlTemplateCollection + * PronunciationGenerator * StructuredContentGenerator */ @@ -28,7 +29,7 @@ class DisplayGenerator { this._hotkeyHelpController = hotkeyHelpController; this._templates = null; this._structuredContentGenerator = new StructuredContentGenerator(this._mediaLoader, document); - this._termPitchAccentStaticTemplateIsSetup = false; + this._pronunciationGenerator = new PronunciationGenerator(japaneseUtil); } async prepare() { @@ -45,13 +46,6 @@ class DisplayGenerator { } } - preparePitchAccents() { - if (this._termPitchAccentStaticTemplateIsSetup) { return; } - this._termPitchAccentStaticTemplateIsSetup = true; - const t = this._templates.instantiate('pitch-accent-static'); - document.head.appendChild(t); - } - createTermEntry(dictionaryEntry) { const node = this._templates.instantiate('term-entry'); @@ -439,8 +433,6 @@ class DisplayGenerator { } _createPitches(details) { - this.preparePitchAccents(); - const {dictionary, pitches} = details; const node = this._templates.instantiate('pitch-accent-group'); @@ -471,9 +463,6 @@ class DisplayGenerator { const {reading, position, nasalPositions, devoicePositions, tags, exclusiveTerms, exclusiveReadings} = details; const morae = jp.getKanaMorae(reading); - const nasalPositionsSet = nasalPositions.length > 0 ? new Set(nasalPositions) : null; - const devoicePositionsSet = devoicePositions.length > 0 ? new Set(devoicePositions) : null; - const node = this._templates.instantiate('pitch-accent'); node.dataset.pitchAccentPosition = `${position}`; @@ -491,46 +480,10 @@ class DisplayGenerator { this._createPitchAccentDisambiguations(n, exclusiveTerms, exclusiveReadings); n = node.querySelector('.pitch-accent-characters'); - for (let i = 0, ii = morae.length; i < ii; ++i) { - const i1 = i + 1; - const mora = morae[i]; - const highPitch = jp.isMoraPitchHigh(i, position); - const highPitchNext = jp.isMoraPitchHigh(i1, position); - const nasal = nasalPositionsSet !== null && nasalPositionsSet.has(i1); - const devoice = devoicePositionsSet !== null && devoicePositionsSet.has(i1); - - const n1 = document.createElement('span'); - n1.className = 'pitch-accent-character'; - - const n2 = document.createElement('span'); - n2.className = 'pitch-accent-character-inner'; - - n1.appendChild(n2); - - n1.dataset.position = `${i}`; - n1.dataset.pitch = highPitch ? 'high' : 'low'; - n1.dataset.pitchNext = highPitchNext ? 'high' : 'low'; - this._setTextContent(n2, mora, 'ja'); - - if (devoice) { - n1.dataset.devoice = 'true'; - const n3 = document.createElement('span'); - n3.className = 'pitch-accent-character-devoice-indicator'; - n1.appendChild(n3); - } - if (nasal) { - n1.dataset.nasal = 'true'; - const n3 = document.createElement('span'); - n3.className = 'pitch-accent-character-nasal-indicator'; - n1.appendChild(n3); - } - - n.appendChild(n1); - } + n.lang = 'ja'; + n.appendChild(this._pronunciationGenerator.createPitchAccentHtml(morae, position, nasalPositions, devoicePositions)); - if (morae.length > 0) { - this._populatePitchGraph(node.querySelector('.pitch-accent-graph'), position, morae); - } + node.querySelector('.pitch-accent-details').appendChild(this._pronunciationGenerator.createPitchGraph(morae, position)); return node; } @@ -556,47 +509,6 @@ class DisplayGenerator { container.dataset.readingCount = `${exclusiveReadings.length}`; } - _populatePitchGraph(svg, position, morae) { - const jp = this._japaneseUtil; - const svgns = svg.getAttribute('xmlns'); - const ii = morae.length; - svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`); - - const pathPoints = []; - for (let i = 0; i < ii; ++i) { - const highPitch = jp.isMoraPitchHigh(i, position); - const highPitchNext = jp.isMoraPitchHigh(i + 1, position); - const graphic = (highPitch && !highPitchNext ? '#pitch-accent-graph-dot-downstep' : '#pitch-accent-graph-dot'); - const x = `${i * 50 + 25}`; - const y = highPitch ? '25' : '75'; - const use = document.createElementNS(svgns, 'use'); - use.setAttribute('href', graphic); - use.setAttribute('x', x); - use.setAttribute('y', y); - svg.appendChild(use); - pathPoints.push(`${x} ${y}`); - } - - let path = svg.querySelector('.pitch-accent-graph-line'); - path.setAttribute('d', `M${pathPoints.join(' L')}`); - - pathPoints.splice(0, ii - 1); - { - const highPitch = jp.isMoraPitchHigh(ii, position); - const x = `${ii * 50 + 25}`; - const y = highPitch ? '25' : '75'; - const use = document.createElementNS(svgns, 'use'); - use.setAttribute('href', '#pitch-accent-graph-triangle'); - use.setAttribute('x', x); - use.setAttribute('y', y); - svg.appendChild(use); - pathPoints.push(`${x} ${y}`); - } - - path = svg.querySelector('.pitch-accent-graph-line-tail'); - path.setAttribute('d', `M${pathPoints.join(' L')}`); - } - _createFrequencyGroup(details, kanji) { const {dictionary, frequencies} = details; diff --git a/ext/js/display/pronunciation-generator.js b/ext/js/display/pronunciation-generator.js new file mode 100644 index 00000000..eb5eb035 --- /dev/null +++ b/ext/js/display/pronunciation-generator.js @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +class PronunciationGenerator { + constructor(japaneseUtil) { + this._japaneseUtil = japaneseUtil; + } + + createPitchAccentHtml(morae, downstepPosition, nasalPositions, devoicePositions) { + const jp = this._japaneseUtil; + const nasalPositionsSet = nasalPositions.length > 0 ? new Set(nasalPositions) : null; + const devoicePositionsSet = devoicePositions.length > 0 ? new Set(devoicePositions) : null; + const fragment = document.createDocumentFragment(); + for (let i = 0, ii = morae.length; i < ii; ++i) { + const i1 = i + 1; + const mora = morae[i]; + const highPitch = jp.isMoraPitchHigh(i, downstepPosition); + const highPitchNext = jp.isMoraPitchHigh(i1, downstepPosition); + const nasal = nasalPositionsSet !== null && nasalPositionsSet.has(i1); + const devoice = devoicePositionsSet !== null && devoicePositionsSet.has(i1); + + const n1 = document.createElement('span'); + n1.className = 'pitch-accent-character'; + + const n2 = document.createElement('span'); + n2.className = 'pitch-accent-character-inner'; + + n1.appendChild(n2); + + n1.dataset.position = `${i}`; + n1.dataset.pitch = highPitch ? 'high' : 'low'; + n1.dataset.pitchNext = highPitchNext ? 'high' : 'low'; + n2.textContent = mora; + + if (devoice) { + n1.dataset.devoice = 'true'; + const n3 = document.createElement('span'); + n3.className = 'pitch-accent-character-devoice-indicator'; + n1.appendChild(n3); + } + if (nasal) { + n1.dataset.nasal = 'true'; + const n3 = document.createElement('span'); + n3.className = 'pitch-accent-character-nasal-indicator'; + n1.appendChild(n3); + } + + fragment.appendChild(n1); + } + return fragment; + } + + createPitchGraph(morae, downstepPosition) { + const jp = this._japaneseUtil; + const ii = morae.length; + + const svgns = 'http://www.w3.org/2000/svg'; + const svg = document.createElementNS(svgns, 'svg'); + svg.setAttribute('xmlns', svgns); + svg.setAttribute('class', 'pitch-accent-graph'); + svg.setAttribute('focusable', 'false'); + svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`); + + if (ii <= 0) { return svg; } + + const path1 = document.createElementNS(svgns, 'path'); + svg.appendChild(path1); + + const path2 = document.createElementNS(svgns, 'path'); + svg.appendChild(path2); + + const pathPoints = []; + for (let i = 0; i < ii; ++i) { + const highPitch = jp.isMoraPitchHigh(i, downstepPosition); + const highPitchNext = jp.isMoraPitchHigh(i + 1, downstepPosition); + const x = i * 50 + 25; + const y = highPitch ? 25 : 75; + if (highPitch && !highPitchNext) { + this._addGraphDotDownstep(svg, svgns, x, y); + } else { + this._addGraphDot(svg, svgns, x, y); + } + pathPoints.push(`${x} ${y}`); + } + + path1.setAttribute('class', 'pitch-accent-graph-line'); + path1.setAttribute('d', `M${pathPoints.join(' L')}`); + + pathPoints.splice(0, ii - 1); + { + const highPitch = jp.isMoraPitchHigh(ii, downstepPosition); + const x = ii * 50 + 25; + const y = highPitch ? 25 : 75; + this._addGraphTriangle(svg, svgns, x, y); + pathPoints.push(`${x} ${y}`); + } + + path2.setAttribute('class', 'pitch-accent-graph-line-tail'); + path2.setAttribute('d', `M${pathPoints.join(' L')}`); + + return svg; + } + + // Private + + _addGraphDot(container, svgns, x, y) { + container.appendChild(this._createGraphCircle(svgns, 'pitch-accent-graph-dot', x, y, '15')); + } + + _addGraphDotDownstep(container, svgns, x, y) { + container.appendChild(this._createGraphCircle(svgns, 'pitch-accent-graph-dot-downstep1', x, y, '15')); + container.appendChild(this._createGraphCircle(svgns, 'pitch-accent-graph-dot-downstep2', x, y, '5')); + } + + _addGraphTriangle(container, svgns, x, y) { + const node = document.createElementNS(svgns, 'path'); + node.setAttribute('class', 'pitch-accent-graph-triangle'); + node.setAttribute('d', 'M0 13 L15 -13 L-15 -13 Z'); + node.setAttribute('transform', `translate(${x},${y})`); + container.appendChild(node); + } + + _createGraphCircle(svgns, className, x, y, radius) { + const node = document.createElementNS(svgns, 'circle'); + node.setAttribute('class', className); + node.setAttribute('cx', `${x}`); + node.setAttribute('cy', `${y}`); + node.setAttribute('r', radius); + return node; + } +} |