Move Japanese utility functions out of display-generator.js

This commit is contained in:
toasted-nutbread 2020-03-28 10:47:02 -04:00
parent cbc7e2646d
commit 0d80fcdf86
3 changed files with 86 additions and 33 deletions

View File

@ -305,7 +305,7 @@ class DisplayGenerator {
createPitch(details) { createPitch(details) {
const {expressions, reading, position, tags} = details; const {expressions, reading, position, tags} = details;
const morae = DisplayGenerator._jpGetKanaMorae(reading); const morae = jp.getKanaMorae(reading);
const node = this._templateHandler.instantiate('term-pitch-accent'); const node = this._templateHandler.instantiate('term-pitch-accent');
@ -324,8 +324,8 @@ class DisplayGenerator {
n = node.querySelector('.term-pitch-accent-characters'); n = node.querySelector('.term-pitch-accent-characters');
for (let i = 0, ii = morae.length; i < ii; ++i) { for (let i = 0, ii = morae.length; i < ii; ++i) {
const mora = morae[i]; const mora = morae[i];
const highPitch = DisplayGenerator._jpIsMoraPitchHigh(i, position); const highPitch = jp.isMoraPitchHigh(i, position);
const highPitchNext = DisplayGenerator._jpIsMoraPitchHigh(i + 1, position); const highPitchNext = jp.isMoraPitchHigh(i + 1, position);
const n1 = this._templateHandler.instantiate('term-pitch-accent-character'); const n1 = this._templateHandler.instantiate('term-pitch-accent-character');
const n2 = n1.querySelector('.term-pitch-accent-character-inner'); const n2 = n1.querySelector('.term-pitch-accent-character-inner');
@ -358,8 +358,8 @@ class DisplayGenerator {
const pathPoints = []; const pathPoints = [];
for (let i = 0; i < ii; ++i) { for (let i = 0; i < ii; ++i) {
const highPitch = DisplayGenerator._jpIsMoraPitchHigh(i, position); const highPitch = jp.isMoraPitchHigh(i, position);
const highPitchNext = DisplayGenerator._jpIsMoraPitchHigh(i + 1, position); const highPitchNext = jp.isMoraPitchHigh(i + 1, position);
const graphic = (highPitch && !highPitchNext ? '#term-pitch-accent-graph-dot-downstep' : '#term-pitch-accent-graph-dot'); const graphic = (highPitch && !highPitchNext ? '#term-pitch-accent-graph-dot-downstep' : '#term-pitch-accent-graph-dot');
const x = `${i * 50 + 25}`; const x = `${i * 50 + 25}`;
const y = highPitch ? '25' : '75'; const y = highPitch ? '25' : '75';
@ -376,7 +376,7 @@ class DisplayGenerator {
pathPoints.splice(0, ii - 1); pathPoints.splice(0, ii - 1);
{ {
const highPitch = DisplayGenerator._jpIsMoraPitchHigh(ii, position); const highPitch = jp.isMoraPitchHigh(ii, position);
const x = `${ii * 50 + 25}`; const x = `${ii * 50 + 25}`;
const y = highPitch ? '25' : '75'; const y = highPitch ? '25' : '75';
const use = document.createElementNS(svgns, 'use'); const use = document.createElementNS(svgns, 'use');
@ -532,30 +532,4 @@ class DisplayGenerator {
return true; return true;
} }
static _jpGetKanaMorae(text) {
// This function splits Japanese kana reading into its individual mora
// components. It is assumed that the text is well-formed.
const smallKanaSet = DisplayGenerator._smallKanaSet;
const morae = [];
let i;
for (const c of text) {
if (smallKanaSet.has(c) && (i = morae.length) > 0) {
morae[i - 1] += c;
} else {
morae.push(c);
} }
}
return morae;
}
static _jpCreateSmallKanaSet() {
return new Set(Array.from('ぁぃぅぇぉゃゅょゎァィゥェォャュョヮ'));
}
static _jpIsMoraPitchHigh(moraIndex, pitchAccentPosition) {
return pitchAccentPosition === 0 ? (moraIndex > 0) : (moraIndex < pitchAccentPosition);
}
}
DisplayGenerator._smallKanaSet = DisplayGenerator._jpCreateSmallKanaSet();

View File

@ -64,6 +64,8 @@ const jp = (() => {
[0xffe0, 0xffee] // Currency markers [0xffe0, 0xffee] // Currency markers
]; ];
const SMALL_KANA_SET = new Set(Array.from('ぁぃぅぇぉゃゅょゎァィゥェォャュョヮ'));
// Character code testing functions // Character code testing functions
@ -112,6 +114,26 @@ const jp = (() => {
} }
// Mora functions
function isMoraPitchHigh(moraIndex, pitchAccentPosition) {
return pitchAccentPosition === 0 ? (moraIndex > 0) : (moraIndex < pitchAccentPosition);
}
function getKanaMorae(text) {
const morae = [];
let i;
for (const c of text) {
if (SMALL_KANA_SET.has(c) && (i = morae.length) > 0) {
morae[i - 1] += c;
} else {
morae.push(c);
}
}
return morae;
}
// Exports // Exports
return { return {
@ -119,6 +141,8 @@ const jp = (() => {
isCodePointKana, isCodePointKana,
isCodePointJapanese, isCodePointJapanese,
isStringEntirelyKana, isStringEntirelyKana,
isStringPartiallyJapanese isStringPartiallyJapanese,
isMoraPitchHigh,
getKanaMorae
}; };
})(); })();

View File

@ -392,6 +392,59 @@ function testDistributeFuriganaInflected() {
} }
} }
function testIsMoraPitchHigh() {
const data = [
[[0, 0], false],
[[1, 0], true],
[[2, 0], true],
[[3, 0], true],
[[0, 1], true],
[[1, 1], false],
[[2, 1], false],
[[3, 1], false],
[[0, 2], true],
[[1, 2], true],
[[2, 2], false],
[[3, 2], false],
[[0, 3], true],
[[1, 3], true],
[[2, 3], true],
[[3, 3], false],
[[0, 4], true],
[[1, 4], true],
[[2, 4], true],
[[3, 4], true]
];
for (const [[moraIndex, pitchAccentPosition], expected] of data) {
const actual = jp.isMoraPitchHigh(moraIndex, pitchAccentPosition);
assert.strictEqual(actual, expected);
}
}
function testGetKanaMorae() {
const data = [
['かこ', ['か', 'こ']],
['かっこ', ['か', 'っ', 'こ']],
['カコ', ['カ', 'コ']],
['カッコ', ['カ', 'ッ', 'コ']],
['コート', ['コ', 'ー', 'ト']],
['ちゃんと', ['ちゃ', 'ん', 'と']],
['とうきょう', ['と', 'う', 'きょ', 'う']],
['ぎゅう', ['ぎゅ', 'う']],
['ディスコ', ['ディ', 'ス', 'コ']]
];
for (const [text, expected] of data) {
const actual = jp.getKanaMorae(text);
vm.assert.deepStrictEqual(actual, expected);
}
}
function main() { function main() {
testIsCodePointKanji(); testIsCodePointKanji();
@ -408,6 +461,8 @@ function main() {
testConvertAlphabeticToKana(); testConvertAlphabeticToKana();
testDistributeFurigana(); testDistributeFurigana();
testDistributeFuriganaInflected(); testDistributeFuriganaInflected();
testIsMoraPitchHigh();
testGetKanaMorae();
} }