From 4a2b824371e7e2c0f576805631f3ccf29d2c0ad3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 17 Jul 2021 10:26:20 -0400 Subject: [PATCH] Display pronunciation separation (#1833) * Create PronunciationGenerator * Update DisplayGenerator * Update templates * Move pronunciation styles to a separate file * Simplify pitch-accents-preview.html --- .eslintrc.json | 3 + ext/css/display-pronunciation.css | 119 ++++++++++++++ ext/css/display.css | 100 ------------ ext/display-templates.html | 9 +- ext/js/display/display-generator.js | 98 +----------- ext/js/display/pronunciation-generator.js | 145 ++++++++++++++++++ .../settings/pitch-accents-preview-main.js | 35 ----- ext/pitch-accents-preview.html | 24 +-- ext/popup.html | 2 + ext/search.html | 2 + 10 files changed, 283 insertions(+), 254 deletions(-) create mode 100644 ext/css/display-pronunciation.css create mode 100644 ext/js/display/pronunciation-generator.js delete mode 100644 ext/js/pages/settings/pitch-accents-preview-main.js diff --git a/.eslintrc.json b/.eslintrc.json index fbbe9925..ad06ff05 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -111,6 +111,7 @@ "files": [ "ext/js/core.js", "ext/js/data/anki-note-data-creator.js", + "ext/js/display/pronunciation-generator.js", "ext/js/display/structured-content-generator.js", "ext/js/dom/css-style-applier.js", "ext/js/language/dictionary-data-util.js", @@ -126,6 +127,7 @@ "excludedFiles": [ "ext/js/core.js", "ext/js/data/anki-note-data-creator.js", + "ext/js/display/pronunciation-generator.js", "ext/js/display/structured-content-generator.js", "ext/js/dom/css-style-applier.js", "ext/js/language/dictionary-data-util.js", @@ -158,6 +160,7 @@ "ext/js/core.js", "ext/js/yomichan.js", "ext/js/data/anki-note-data-creator.js", + "ext/js/display/pronunciation-generator.js", "ext/js/display/structured-content-generator.js", "ext/js/dom/css-style-applier.js", "ext/js/language/dictionary-data-util.js", diff --git a/ext/css/display-pronunciation.css b/ext/css/display-pronunciation.css new file mode 100644 index 00000000..4ea15baf --- /dev/null +++ b/ext/css/display-pronunciation.css @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys 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 . + */ + +:root { + --pitch-accent-annotation-color: #000000; +} +:root[data-theme=dark] { + --pitch-accent-annotation-color: #ffffff; +} + +.pitch-accent-character { + display: inline-block; + position: relative; +} +.pitch-accent-character::before { + border-color: var(--pitch-accent-annotation-color); +} +.pitch-accent-character[data-pitch='high']::before { + content: ''; + display: block; + user-select: none; + pointer-events: none; + position: absolute; + top: 0.1em; + left: 0; + right: 0; + height: 0; + border-top-width: 0.1em; + border-top-style: solid; +} +.pitch-accent-character[data-pitch='high'][data-pitch-next='low']::before { + right: -0.1em; + height: 0.4em; + border-right-width: 0.1em; + border-right-style: solid; +} +.pitch-accent-character[data-pitch='high'][data-pitch-next='low'] { + padding-right: 0.1em; + margin-right: 0.1em; +} +.pitch-accent-character-devoice-indicator { + display: block; + position: absolute; + left: 50%; + top: 50%; + width: 1.125em; + height: 1.125em; + border: calc(1.5em / var(--font-size-no-units)) dotted var(--danger-color); + border-radius: 50%; + box-sizing: border-box; + z-index: 1; + transform: translate(-50%, -50%); +} +.pitch-accent-character-nasal-indicator { + display: block; + position: absolute; + right: -0.125em; + top: 0.125em; + width: 0.375em; + height: 0.375em; + border: calc(1.5em / var(--font-size-no-units)) solid var(--danger-color); + border-radius: 50%; + box-sizing: border-box; + z-index: 1; +} + +.pitch-accent-position::before { + content: ' ['; +} +.pitch-accent-position::after { + content: ']'; +} + +.pitch-accent-graph { + display: block; + height: 1.5em; + transform: translateY(-0.875em); +} +.pitch-accent-graph-line, +.pitch-accent-graph-line-tail { + fill: none; + stroke: var(--pitch-accent-annotation-color); + stroke-width: 5; +} +.pitch-accent-graph-line-tail { + stroke-dasharray: 5 5; +} +.pitch-accent-graph-dot { + fill: var(--pitch-accent-annotation-color); + stroke: var(--pitch-accent-annotation-color); + stroke-width: 5; +} +.pitch-accent-graph-dot-downstep1 { + fill: none; + stroke: var(--pitch-accent-annotation-color); + stroke-width: 5; +} +.pitch-accent-graph-dot-downstep2 { + fill: var(--pitch-accent-annotation-color); +} +.pitch-accent-graph-triangle { + fill: none; + stroke: var(--pitch-accent-annotation-color); + stroke-width: 5; +} diff --git a/ext/css/display.css b/ext/css/display.css index cd9391fe..f2e36b56 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -132,8 +132,6 @@ --medium-border-color: #dddddd; --dark-border-color: #777777; - --pitch-accent-annotation-color: #000000; - --tag-text-color: #ffffff; --tag-border-color: transparent; --tag-default-background-color: #8a8a91; @@ -202,8 +200,6 @@ --medium-border-color: #3f3f3f; --dark-border-color: #888888; - --pitch-accent-annotation-color: #ffffff; - --tag-text-color: #f1f1f1; --tag-border-color: transparent; --tag-default-background-color: #69696e; @@ -1482,67 +1478,6 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con .pitch-accent-tag-list:not([data-count='0']) { margin-right: 0.375em; } -.pitch-accent-character { - display: inline-block; - position: relative; -} -.pitch-accent-character::before { - border-color: var(--pitch-accent-annotation-color); -} -.pitch-accent-character[data-pitch='high']::before { - content: ''; - display: block; - user-select: none; - pointer-events: none; - position: absolute; - top: 0.1em; - left: 0; - right: 0; - height: 0; - border-top-width: 0.1em; - border-top-style: solid; -} -.pitch-accent-character[data-pitch='high'][data-pitch-next='low']::before { - right: -0.1em; - height: 0.4em; - border-right-width: 0.1em; - border-right-style: solid; -} -.pitch-accent-character[data-pitch='high'][data-pitch-next='low'] { - padding-right: 0.1em; - margin-right: 0.1em; -} -.pitch-accent-character-devoice-indicator { - display: block; - position: absolute; - left: 50%; - top: 50%; - width: 1.125em; - height: 1.125em; - border: calc(1.5em / var(--font-size-no-units)) dotted var(--danger-color); - border-radius: 50%; - box-sizing: border-box; - z-index: 1; - transform: translate(-50%, -50%); -} -.pitch-accent-character-nasal-indicator { - display: block; - position: absolute; - right: -0.125em; - top: 0.125em; - width: 0.375em; - height: 0.375em; - border: calc(1.5em / var(--font-size-no-units)) solid var(--danger-color); - border-radius: 50%; - box-sizing: border-box; - z-index: 1; -} -.pitch-accent-position::before { - content: ' ['; -} -.pitch-accent-position::after { - content: ']'; -} .pitch-accent-details { display: inline-block; height: 0; @@ -1551,41 +1486,6 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con } -/* Pitch accent graph styles */ -.pitch-accent-graph { - display: block; - height: 1.5em; - transform: translateY(-0.875em); -} -.pitch-accent-graph-line, -.pitch-accent-graph-line-tail { - fill: none; - stroke: var(--pitch-accent-annotation-color); - stroke-width: 5; -} -.pitch-accent-graph-line-tail { - stroke-dasharray: 5 5; -} -#pitch-accent-graph-dot { - fill: var(--pitch-accent-annotation-color); - stroke: var(--pitch-accent-annotation-color); - stroke-width: 5; -} -#pitch-accent-graph-dot-downstep { - fill: none; - stroke: var(--pitch-accent-annotation-color); - stroke-width: 5; -} -#pitch-accent-graph-dot-downstep>circle:last-of-type { - fill: var(--pitch-accent-annotation-color); -} -#pitch-accent-graph-triangle { - fill: none; - stroke: var(--pitch-accent-annotation-color); - stroke-width: 5; -} - - /* Kanji */ .kanji-glyph-container { display: block; diff --git a/ext/display-templates.html b/ext/display-templates.html index a7a184a4..1107ead3 100644 --- a/ext/display-templates.html +++ b/ext/display-templates.html @@ -82,16 +82,9 @@ - - + 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); + n.lang = 'ja'; + n.appendChild(this._pronunciationGenerator.createPitchAccentHtml(morae, position, nasalPositions, devoicePositions)); - 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); - } - - 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 . + */ + +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; + } +} diff --git a/ext/js/pages/settings/pitch-accents-preview-main.js b/ext/js/pages/settings/pitch-accents-preview-main.js deleted file mode 100644 index d9d56727..00000000 --- a/ext/js/pages/settings/pitch-accents-preview-main.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019-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 . - */ - -/* global - * DisplayGenerator - */ - -(async () => { - try { - await yomichan.prepare(); - - const displayGenerator = new DisplayGenerator({ - japaneseUtil: null, - mediaLoader: null - }); - await displayGenerator.prepare(); - displayGenerator.preparePitchAccents(); - } catch (e) { - log.error(e); - } -})(); diff --git a/ext/pitch-accents-preview.html b/ext/pitch-accents-preview.html index 0f954e72..73b9bc8a 100644 --- a/ext/pitch-accents-preview.html +++ b/ext/pitch-accents-preview.html @@ -11,7 +11,7 @@ - + @@ -35,12 +35,13 @@ Graph - - + - - - + + + + @@ -49,18 +50,5 @@ - - - - - - - - - - - - - diff --git a/ext/popup.html b/ext/popup.html index ae5685f5..065bcfa1 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -13,6 +13,7 @@ + @@ -109,6 +110,7 @@ + diff --git a/ext/search.html b/ext/search.html index 44d3c680..beceeb18 100644 --- a/ext/search.html +++ b/ext/search.html @@ -13,6 +13,7 @@ + @@ -93,6 +94,7 @@ +