Skip to content
Open
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
173 changes: 173 additions & 0 deletions mathjax-demo.html
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt this works well on github - we should get this rendering nicely.

Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>xkcd-script + MathJax</title>

<style>
body {
font-family: 'xkcd-script', sans-serif;
font-size: 1.25em;
max-width: 920px;
margin: 2em auto;
padding: 0 1.5em;
line-height: 1.7;
color: #111;
background: #fff;
}

h1 { font-size: 2.2em; margin-bottom: 0.2em; }
h2 { font-size: 1.5em; margin-top: 2em; border-bottom: 1px solid #ccc; padding-bottom: 0.2em; }
p { margin: 0.8em 0; }

/* ── Editor ── */
.editor-wrap {
display: flex;
gap: 1em;
align-items: flex-start;
margin-top: 0.5em;
}
#latex-input {
flex: 1;
min-height: 220px;
font-family: monospace;
font-size: 0.78em;
padding: 0.6em;
border: 1px solid #999;
resize: vertical;
background: #fffff0;
box-sizing: border-box;
}
#latex-preview {
flex: 1;
min-height: 220px;
padding: 0.6em 1em;
border: 1px solid #ccc;
background: #fff;
box-sizing: border-box;
overflow-wrap: break-word;
}
</style>

<script>
// MathJax TeX-input config (delimiters). Must be set BEFORE xkcd-mathjax.js
// (which then merges in its startup hook) and BEFORE MathJax itself loads.
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
},
};
</script>
<script src="xkcd-script/xkcd-mathjax.js"></script>
<script id="MathJax-script" async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>

</head>
<body>

<h1>xkcd-script + MathJax</h1>
<p>MathJax CHTML with xkcd-script substituted for letter and Greek glyphs.</p>

<div class="editor-wrap">
<textarea id="latex-input" spellcheck="false">Inline vs display large operators:

$\sum_{k=1}^{n} k$ and $\int_0^1 x^2\,dx$

$$\sum_{k=1}^{n} k = \frac{n(n+1)}{2}$$

$$\int_0^\infty e^{-x}\,dx = 1$$

$$\prod_{p\;\text{prime}} \frac{1}{1-p^{-s}} = \sum_{n=1}^{\infty} \frac{1}{n^s}$$</textarea>
<div id="latex-preview"></div>
</div>

<script>
const input = document.getElementById('latex-input');
const preview = document.getElementById('latex-preview');
let renderTimer;

function renderPreview() {
MathJax.typesetClear([preview]);
preview.textContent = input.value;
MathJax.typesetPromise([preview]).then(() => XkcdMathJax.refresh(preview));
}

input.addEventListener('input', () => {
clearTimeout(renderTimer);
renderTimer = setTimeout(renderPreview, 350);
});

// Initial preview render fires once MathJax + xkcd-mathjax finish setup.
XkcdMathJax.ready.then(renderPreview);

// On resize the library already re-places overlays on the static formulae;
// we additionally re-render the live preview so its layout stays in sync.
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(renderPreview, 150);
});
</script>

<h2>Large operators: inline vs display</h2>

<p>Inline: $\sum_{k=1}^{n} k$ and $\int_0^1 x^2\,dx$ and $\prod_{k=1}^{n} k$</p>

<p>Display:
$$\sum_{k=1}^{n} k = \frac{n(n+1)}{2}$$
$$\int_0^\infty e^{-x}\,dx = 1 \qquad \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$
$$\prod_{p\;\text{prime}} \frac{1}{1-p^{-s}} = \sum_{n=1}^{\infty} \frac{1}{n^s}$$
</p>

<h2>Algebra</h2>

<p>Quadratic formula:
$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$</p>

<p>Pythagorean theorem: $a^2 + b^2 = c^2$</p>

<p>Binomial theorem:
$$\sum_{k=0}^{n} \binom{n}{k} x^k y^{n-k} = (x + y)^n$$</p>

<h2>Calculus</h2>

<p>Definition of derivative:
$$f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$$</p>

<p>Gaussian integral:
$$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$$</p>

<p>Product rule: $\dfrac{d}{dx}\bigl[f(x)\,g(x)\bigr] = f'(x)\,g(x) + f(x)\,g'(x)$</p>

<h2>Greek letters</h2>

<p>Euler's identity: $e^{i\pi} + 1 = 0$</p>

<p>$\alpha,\ \beta,\ \gamma,\ \delta,\ \varepsilon,\ \zeta,\ \eta,\
\theta,\ \iota,\ \kappa,\ \lambda,\ \mu,\ \nu,\ \xi,\ \pi,\
\rho,\ \sigma,\ \tau,\ \upsilon,\ \phi,\ \chi,\ \psi,\ \omega$</p>

<p>Fourier transform:
$$\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x)\,e^{-2\pi i x \xi}\,dx$$</p>

<h2>Physics</h2>

<p>Einstein: $E = mc^2$</p>

<p>Schrödinger equation:
$$i\hbar\frac{\partial\psi}{\partial t} = \hat{H}\psi$$</p>

<p>Maxwell in free space:
$$\nabla \cdot \mathbf{E} = 0 \qquad \nabla \times \mathbf{E} = -\frac{\partial \mathbf{B}}{\partial t}$$</p>


<p>Deeply nested sqrt fractions:
$$\sqrt{b^2 - 4ac}
\sqrt{\frac{b^2}{x}}{2a}
\sqrt{\frac{b^2}{\frac{b^2}{x}}}{2a}
\sqrt{\frac{b^2}{\frac{b^2}{\frac{b^2}{x}}}}{2a}
\sqrt{\frac{b^2}{\frac{b^2}{\frac{b^2}{\frac{b^2}{4ac}}}}}{2a}$$
</p>
</body>
</html>
1 change: 1 addition & 0 deletions run_mathjax.sh
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard coded paths need fixing

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docker run --rm -v /media/important/github/jupyter/xkcd-font:/work fontbuilder-test python3 /work/xkcd-script/generator/gen_mathjax_font.py
132 changes: 132 additions & 0 deletions sqrt-builder.html
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to ship this.

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sqrt line builder</title>
<style>
body { font-family: monospace; padding: 20px; background: #f0f0f0; }
#output svg { display: block; background: white; margin-top: 12px; border: 1px solid #ccc; }
label { display: block; margin: 10px 0 4px; }
input[type=range] { width: 600px; vertical-align: middle; }
#info { margin-top: 8px; color: #555; font-size: 0.9em; }
</style>
</head>
<body>
<h2>sqrt line builder</h2>

<label><input type="checkbox" id="surdCheck" checked> Include surd (&radic;)</label>
<label>
Target width:
<input type="range" id="widthSlider" min="128" max="2000" value="300">
<span id="widthLabel">300</span> pt
</label>
<div id="info"></div>
<div id="output"></div>

<script>
// All coordinates are in the source SVG's pt space (viewBox "0 0 170 61").
// Cut points derived from pixel-profile analysis of sqrt.png (400x143px → 0.425pt/px).
const SRC_W = 170, SRC_H = 61;
const CUT_A = 57.5; // end of surd cap / start of tile (flat zone)
const CUT_B = 59.0; // start of right cap (still in flat zone, fixes the step)
const TILE_W = CUT_B - CUT_A; // 42.5 pt — the stretchable flat-bar tile
const RIGHT_CAP_W = SRC_W - CUT_B; // 85 pt

const PATH_D = `m 410.6448,596.25455 c -52.01622,-1.52077 -90.0699,-7.17037 -96.13414,-14.11479 -5.06429,-6.95363 -20.44144,-47.81367 -32.84636,-91.70123 -12.40492,-43.88756 -36.13187,-122.67228 -52.61034,-174.52264 -15.47851,-51.85958 -36.11344,-120.67237 -45.40792,-152.58808 -9.28526,-30.91575 -20.52437,-56.81329 -23.52424,-56.78565 -3.99983,0.0369 -17.7873,23.16488 -30.52875,51.28348 C 95.267178,231.14504 70.535454,260.37417 54.416347,247.52215 36.278897,232.68864 140.33939,21.72084 165.33833,21.490482 c 12.99945,-0.119786 25.22008,23.768624 49.91935,99.544258 48.37094,148.5606 70.01505,218.36413 102.04573,330.07373 l 28.93867,101.73767 400.02909,1.31406 c 221.01827,0.96351 522.03313,1.18988 670.02683,-0.17384 147.9937,-1.36371 271.0069,-0.49716 273.0345,2.48429 3.0275,2.97223 3.1104,11.97184 -0.8065,21.00832 -4.8707,14.04548 -64.8498,16.59825 -597.8548,18.5096 -326.00457,1.00395 -632.01001,1.82361 -680.0264,0.26598 z`;

// Caps extend this far into the tile region so their clipped edges
// land on flat-bar content rather than at a path outline boundary.
// The tile is drawn behind; caps (opaque black) paint over any tile
// bleed in their regions — black on black, no visible change.
const OVERLAP = 2;

const slider = document.getElementById('widthSlider');
const surdCheck = document.getElementById('surdCheck');
const widthLabel = document.getElementById('widthLabel');
const info = document.getElementById('info');
const output = document.getElementById('output');

surdCheck.addEventListener('change', () => {
const minW = surdCheck.checked
? Math.ceil(CUT_A + RIGHT_CAP_W)
: Math.ceil(2 * RIGHT_CAP_W);
slider.min = minW;
if (+slider.value < minW) slider.value = minW;
redraw();
});
slider.addEventListener('input', redraw);

function f(n) { return n.toFixed(3); }

function redraw() {
const W = +slider.value;
widthLabel.textContent = W;
const surd = surdCheck.checked;
const leftCapW = surd ? CUT_A : RIGHT_CAP_W;

const M = Math.max(0, W - leftCapW - RIGHT_CAP_W);
const sx = M > 0 ? M / TILE_W : 1;
const tileTx = leftCapW - CUT_A * sx;
const rightTx = W - RIGHT_CAP_W - CUT_B;

// Below this threshold the scaled tile is too thin to rasterize cleanly —
// skip it and let the extended left cap bridge the gap instead.
const TILE_THRESHOLD = 4;
const useTile = M >= TILE_THRESHOLD;

// When tiling: tight sub-clip on the tile, caps extend OVERLAP into it.
// When not tiling: left cap extends all the way across the gap (+ OVERLAP
// into the right cap), so no tile clip is needed.
const leftClipW = useTile ? leftCapW + OVERLAP : leftCapW + M + OVERLAP;

const clipMid = useTile ? `<clipPath id="cm">
<rect x="${f(leftCapW)}" y="0" width="${f(M)}" height="${SRC_H}"/>
</clipPath>` : '';

const clipLeft = `<clipPath id="cl">
<rect x="0" y="0" width="${f(leftClipW)}" height="${SRC_H}"/>
</clipPath>`;

const clipRight = `<clipPath id="cr">
<rect x="${f(W - RIGHT_CAP_W - OVERLAP)}" y="0" width="${f(RIGHT_CAP_W + OVERLAP)}" height="${SRC_H}"/>
</clipPath>`;

const mid = useTile ? `<g clip-path="url(#cm)">
<g transform="translate(${f(tileTx)},0) scale(${f(sx)},1)"><use href="#src"/></g>
</g>` : '';

// Left cap drawn on top of tile — covers the tile's left clip seam.
const leftCap = surd
? `<g clip-path="url(#cl)"><use href="#src"/></g>`
: `<g clip-path="url(#cl)">
<g transform="translate(${f(SRC_W)},0) scale(-1,1)"><use href="#src"/></g>
</g>`;

// Right cap drawn on top of tile — covers the tile's right clip seam.
const right = `<g clip-path="url(#cr)">
<g transform="translate(${f(rightTx)},0)"><use href="#src"/></g>
</g>`;

// Render order: tile (back) → left cap → right cap (front).
output.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"
width="${W}pt" height="${SRC_H}pt" viewBox="0 0 ${W} ${SRC_H}">
<defs>
<g id="src" transform="translate(0,61) scale(0.1,-0.1)">
<path d="${PATH_D}" fill="black"/>
</g>
${clipMid}
${clipLeft}
${clipRight}
</defs>
${mid}
${leftCap}
${right}
</svg>`;

info.textContent = `left: ${f(leftCapW)}pt | tile: ${f(M)}pt (${f(sx)}×) | right: ${f(RIGHT_CAP_W)}pt | total: ${W}pt`;
}

redraw();
</script>
</body>
</html>
Loading
Loading