const signs = [
['\u002d', 'hyphen-minus'],
['\u2010', 'hyphen'],
['\u2011', 'non-breaking hyphen'],
['\u2012', 'figure dash'],
['\u2013', 'en dash'],
['\u2014', 'em dash',],
['\u2015', 'horizontal bar'],
['\u2043', 'hyphen bullet'],
['\u2212', 'minus'],
['\u2e3a', 'two-em dash'],
['\u2e3b', 'three-em dash'],
];
const sentences = [
{
text: s => `value is ${s}1`,
expect: "minus",
good: '\u2212',
okay: '\u002d',
},
{
text: s => `Y equals 69X${s}420`,
expect: "minus",
good: '\u2212',
okay: '\u002d',
},
{
text: s => `document 69X${s}420`,
expect: "dash",
good: '\u2012',
},
{
text: s => `evaluate C${s}3PO and R2${s}D2 for fitness for combat`,
good: '\u2010\u2011',
okay: '\u002d',
},
{
text: s => `evaluate C${s}3PO and R2${s}D2 for PO = R2 = 5`,
expect: "minus",
good: '\u2212',
okay: '\u002d',
},
{
text: s => `values are: ${s} 1 ${s} 2 ${s} 3`,
good: '\u2043\u2015',
},
{
text: s => `the nVidia${s}3dfx rivalry`,
good: '\u2013',
},
{
text: s => `there were none${s}3dfx excluded`,
good: '\u2014',
okay: '\u2013\u2e3a\u2e3b',
},
];
const html = (pieces, ...values) =>
String.raw({ raw: pieces }, ...values.map(
s => s.replaceAll(
/[<"'&]/gu,
c => `&#${c.codePointAt(0)};`)));
const table = document.createElement('table');
table.insertAdjacentHTML('afterbegin', html`
<caption>
Legend:
<span class="good">optimal</span>,
<span class="okay">acceptable</span>,
<span class="bad">wrong</span>,
<span class="ref">not applicable</span>.<br>
<label>Voice: <select></select></label>
<button type="button" class="refvoices">refresh</button>
</caption>
`);
const voiceMap = new WeakMap();
const voiceSelect = table.querySelector('select');
const refreshVoices = () => {
voiceSelect.innerHTML = '';
for (const voice of speechSynthesis.getVoices()) {
voiceSelect.insertAdjacentHTML('beforeend', html`
<option value="${voice.voiceURI}">
${voice.name}<br>
<small><${voice.voiceURI}></small>
</option>
`);
voiceMap.set(
voiceSelect.querySelector('option:last-child'),
voice
);
}
if (voiceSelect.querySelector('option'))
return;
voiceSelect.insertAdjacentHTML('beforeend', `
<option disabled>none available, try refreshing</option>
`);
};
table.querySelector('button.refvoices').addEventListener('click', ev => {
refreshVoices();
});
refreshVoices();
document.addEventListener('click', ev => {
const button = ev.target.closest('button.speak');
if (!button) return;
const { sentence } = button.dataset;
const msg = new SpeechSynthesisUtterance();
msg.voice = voiceMap.get(voiceSelect.selectedOptions[0]);
msg.volume = 1; // 0 to 1
msg.lang = 'en-US';
msg.text = sentence;
speechSynthesis.speak(msg);
});
const tr = document.createElement('tr');
tr.insertAdjacentHTML('afterbegin', html`
<th scope="col">phrase</th>
<th scope="col">reference</th>
`);
for (const [sign, name] of signs) {
tr.insertAdjacentHTML('beforeend', html`
<th scope="col">U+${
sign.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')
} (${sign}) ${name}</th>
`);
}
table.appendChild(tr);
for (const { text, expect, good, okay } of sentences) {
const tr = document.createElement('tr');
tr.insertAdjacentHTML('beforeend', html`
<th scope="row">
${text(expect ? `[${expect}]` : good.charAt(0))}
<td>
<button type="button"
data-sentence="${text(` ${expect ?? ""} `)}"
class="ref speak">play</button>
`);
for (const [sign, name] of signs) {
tr.insertAdjacentHTML('beforeend', html`
<td>
<button type="button"
data-sentence="${text(sign)}"
class="${
(good ?? '').indexOf(sign) !== -1 ? 'good' :
(okay ?? '').indexOf(sign) !== -1 ? 'okay' :
'bad'
} speak">play</button>
`);
}
table.appendChild(tr);
}
document.body.appendChild(table);
body { line-height: 200%; }
.bad { background: maroon; color: white; }
.good { background: green; color: white; }
.okay { background: olive; color: white; }
.ref { background: gray; color: white; }
table { border-collapse: collapse; }
td, th { border: 1px solid gray; padding: 0.2em; }
th[scope="row"], th[scope="col"]:first-child { text-align: left; }
th[scope="col"] { vertical-align: bottom; }
th[scope="col"]:not(:first-child) { vertical-align: top; writing-mode: vertical-lr; transform: rotate(180deg); text-align: right; text-wrap: nowrap; }
td { text-align: center; }
+character to make it as narrow as the-should work as well. Not so: the stem widths would be different.-10is very clearly narrower than the+76. Also, in most commonly used fonts, the digits are 'tabular lining', that is, they are all equal width, and the+and minus are as well. The number of signs and the number of digits above and below are the same, so they should align. No need for monospaced text at all.