Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match synthesized selectable counter markers to the context list #504

Merged
merged 4 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions css/elements.css
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ emu-alg ol.nested-lots ol {
list-style-type: lower-roman;
}

emu-alg [aria-hidden=true] {
font-size: 0;
}

emu-eqn {
display: block;
margin-left: 4em;
Expand Down
130 changes: 110 additions & 20 deletions js/listNumbers.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,120 @@
'use strict';
let decimalBullet = Array.from({ length: 100 }, (a, i) => '' + (i + 1));
let alphaBullet = Array.from({ length: 26 }, (a, i) => String.fromCharCode('a'.charCodeAt(0) + i));

// prettier-ignore
let romanBullet = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi', 'xvii', 'xviii', 'xix', 'xx', 'xxi', 'xxii', 'xxiii', 'xxiv', 'xxv'];
// prettier-ignore
let bullets = [decimalBullet, alphaBullet, romanBullet, decimalBullet, alphaBullet, romanBullet];
// Manually prefix algorithm step list items with hidden counter representations
// corresponding with their markers so they get selected and copied with content.
// We read list-style-type to avoid divergence with the style sheet, but
// for efficiency assume that all lists at the same nesting depth use the same
// style (except for those associated with replacement steps).
// We also precompute some initial items for each supported style type.
// https://w3c.github.io/csswg-drafts/css-counter-styles/

function addStepNumberText(ol, parentIndex) {
for (let i = 0; i < ol.children.length; ++i) {
let child = ol.children[i];
let index = parentIndex.concat([i]);
let applicable = bullets[Math.min(index.length - 1, 5)];
let span = document.createElement('span');
span.textContent = (applicable[i] || '?') + '. ';
span.style.fontSize = '0';
span.setAttribute('aria-hidden', 'true');
child.prepend(span);
let sublist = child.querySelector('ol');
if (sublist != null) {
addStepNumberText(sublist, index);
const lowerLetters = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode('a'.charCodeAt(0) + i)
);
// Implement the lower-alpha 'alphabetic' algorithm,
// adjusting for indexing from 0 rather than 1.
// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-alphabetic
// https://w3c.github.io/csswg-drafts/css-counter-styles/#alphabetic-system
const lowerAlphaTextForIndex = i => {
let S = '';
for (const N = lowerLetters.length; i >= 0; i--) {
S = lowerLetters[i % N] + S;
i = Math.floor(i / N);
}
return S;
};

const weightedLowerRomanSymbols = Object.entries({
m: 1000,
cm: 900,
d: 500,
cd: 400,
c: 100,
xc: 90,
l: 50,
xl: 40,
x: 10,
ix: 9,
v: 5,
iv: 4,
i: 1,
});
// Implement the lower-roman 'additive' algorithm,
// adjusting for indexing from 0 rather than 1.
// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-numeric
// https://w3c.github.io/csswg-drafts/css-counter-styles/#additive-system
const lowerRomanTextForIndex = i => {
let value = i + 1;
let S = '';
for (const [symbol, weight] of weightedLowerRomanSymbols) {
if (!value) break;
if (weight > value) continue;
const reps = Math.floor(value / weight);
S += symbol.repeat(reps);
value -= weight * reps;
}
return S;
};

// Memoize pure index-to-text functions with an exposed cache for fast retrieval.
const makeCounter = (pureGetTextForIndex, precomputeCount = 30) => {
const cache = Array.from({ length: precomputeCount }, (_, i) => pureGetTextForIndex(i));
const getTextForIndex = i => {
if (i >= cache.length) cache[i] = pureGetTextForIndex(i);
return cache[i];
};
return { getTextForIndex, cache };
};

const counterByStyle = {
__proto__: null,
decimal: makeCounter(i => String(i + 1)),
'lower-alpha': makeCounter(lowerAlphaTextForIndex),
'upper-alpha': makeCounter(i => lowerAlphaTextForIndex(i).toUpperCase()),
'lower-roman': makeCounter(lowerRomanTextForIndex),
'upper-roman': makeCounter(i => lowerRomanTextForIndex(i).toUpperCase()),
};
const fallbackCounter = makeCounter(() => '?');
const counterByDepth = [];

function addStepNumberText(
ol,
depth = 0,
special = [...ol.classList].some(c => c.startsWith('nested-'))
) {
let counter = !special && counterByDepth[depth];
if (!counter) {
const counterStyle = getComputedStyle(ol)['list-style-type'];
counter = counterByStyle[counterStyle];
if (!counter) {
console.warn('unsupported list-style-type', {
ol,
counterStyle,
id: ol.closest('[id]')?.getAttribute('id'),
});
counterByStyle[counterStyle] = fallbackCounter;
counter = fallbackCounter;
}
if (!special) {
counterByDepth[depth] = counter;
}
}
const { cache, getTextForIndex } = counter;
let i = (Number(ol.getAttribute('start')) || 1) - 1;
for (const li of ol.children) {
const marker = document.createElement('span');
marker.textContent = `${i < cache.length ? cache[i] : getTextForIndex(i)}. `;
marker.setAttribute('aria-hidden', 'true');
li.prepend(marker);
for (const sublist of li.querySelectorAll(':scope > ol')) {
addStepNumberText(sublist, depth + 1, special);
}
i++;
}
}

document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('emu-alg > ol').forEach(ol => {
addStepNumberText(ol, []);
addStepNumberText(ol);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=yZyB5dGK" defer=""></script><link rel="canonical" href="../#sec-intro"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-intro"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=yZyB5dGK" defer=""></script><link rel="canonical" href="../#sec-second"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-second"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=yZyB5dGK" defer=""></script><link rel="canonical" href="../#sec-third"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-third"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down