This replaces MathJax inline markup in an <svg> element with SVG-rendered MathJax.
See test0.html and test1.html for usage examples.
| /* | |
| * SVG_MathJax | |
| * | |
| * Copyright 2014 Jason M. Sachs | |
| * Based loosely on an approach outlined by Martin Clark | |
| * in http://stackoverflow.com/a/21923030/44330 | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| * | |
| * Modifications: John H. Cole, 2024 | |
| * - Updated to work with MathJax v3. | |
| * - keep the entire SVG, not just the g nodes. | |
| * - Reworked scale, width, and height calculations. | |
| * - Added MutationObserver to detect when SVGs are added to the DOM. | |
| * - Implemented polling to ensure all SVGs are injected before calling MathJax. | |
| * - Some refactoring to improve readability. | |
| * | |
| * https://gist.github.com/jhcole/0fcefdc1c0a931dcc27407074bfc87b2 | |
| * | |
| */ | |
| (function() { | |
| // Apply a function to elements of an array x | |
| function forEach(x, f) { | |
| var n = x.length; | |
| for (var i = 0; i < n; ++i) { | |
| f(x[i]); | |
| } | |
| } | |
| // Find all the SVG text elements that are delimited by | |
| // \( \) or $ $ MathJax delimiters | |
| // (with optional whitespace before/after) | |
| function findSVGMathJax(f, context) { | |
| const re = /^\s*([LlRrCc]?)(\\\(.*\\\)|\$.*\$)\s*$/; | |
| context = context || document; | |
| context.querySelectorAll('svg').forEach(svg => { | |
| svg.querySelectorAll('text').forEach(text => { | |
| const match = text.textContent.match(re); | |
| if (match) { | |
| const justification = match[1]; | |
| const mathmarkup = match[2].replace(/^\$(.*)\$$/,'\\($1\\)'); | |
| f(text, justification, mathmarkup); | |
| } | |
| }); | |
| }); | |
| } | |
| function _install(options) { | |
| const items = []; | |
| // Move the raw MathJax items to a temporary element | |
| MathJax.startup.promise.then(() => { | |
| const mathbucket = document.createElement('div'); | |
| mathbucket.setAttribute('id', 'mathjax_svg_bucket'); | |
| document.body.appendChild(mathbucket); | |
| findSVGMathJax(function(text, justification, mathmarkup) { | |
| const div = document.createElement('div'); | |
| mathbucket.appendChild(div); | |
| div.appendChild(document.createTextNode(mathmarkup)); | |
| items.push({text: text, div: div, align: justification}); | |
| }); | |
| MathJax.typesetPromise().then(() => { | |
| forEach(items, function(item) { | |
| const svgdest = item.text; | |
| const x0 = +item.text.getAttribute('x'); | |
| const y0 = +item.text.getAttribute('y'); | |
| const svgmath = item.div.getElementsByClassName('MathJax')[0].getElementsByTagName('svg')[0]; | |
| const justification = item.align; | |
| let fontsize = svgdest.getAttribute('font-size'); | |
| // If the font-size attribute is not found, look for it in the style attribute | |
| if (!fontsize) { | |
| fontsize = window.getComputedStyle(svgdest).fontSize; | |
| } | |
| const scale = options.scale*parseFloat(fontsize); | |
| let x1; | |
| const svgmathinfo = { | |
| width: svgmath.getBoundingClientRect().width, | |
| height: svgmath.getBoundingClientRect().height | |
| }; | |
| switch (justification.toUpperCase()) { | |
| case 'L': x1 = 0; break; | |
| case 'R': x1 = -svgmathinfo.width * scale; break; | |
| case 'C': // default to center | |
| default: x1 = -svgmathinfo.width * 0.5 * scale; break; | |
| } | |
| const y1 = -svgmathinfo.height * scale; | |
| svgmath.setAttribute('transform', 'translate(' + x0 + ' ' + y0 + ')' | |
| +' translate(' + x1 + ' ' + y1 + ')' | |
| +' scale(' + scale + ')' | |
| ); | |
| if (options.escape_clip) { | |
| svgdest.parentNode.removeAttribute('clip-path'); | |
| } | |
| svgdest.parentNode.replaceChild(svgmath, svgdest); | |
| }); | |
| // Remove the temporary items | |
| mathbucket.parentNode.removeChild(mathbucket); | |
| }).catch(err => { | |
| console.error('MathJax processing error:', err); | |
| }); | |
| }); | |
| } | |
| class F { | |
| constructor() { | |
| this.scale = 0.09; | |
| this.escape_clip = false; | |
| } | |
| install() { | |
| _install(this); | |
| } | |
| } | |
| window.Svg_MathJax = new F(); | |
| // Create a MutationObserver to detect when SVGs are added to the DOM | |
| var observer = new MutationObserver(function(mutations) { | |
| // Check if all SVGs are injected | |
| console.log('DOM Mutated: Checking for pending SVG injection'); | |
| if (document.querySelectorAll("img.svg-injectable").length === 0) { | |
| observer.disconnect(); | |
| console.log('Finished injecting SVGs, applying MathJax'); | |
| window.Svg_MathJax.install(); | |
| } | |
| }); | |
| // Ensure the DOM is fully loaded before setting up the observer | |
| document.addEventListener('DOMContentLoaded', function() { | |
| if (document.querySelectorAll("img.svg-injectable").length === 0) { | |
| console.log('No pending SVGs to inject, applying MathJax'); | |
| window.Svg_MathJax.install(); | |
| } else { | |
| // Start observing the document for added nodes | |
| console.log('Starting observer'); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| } | |
| }); | |
| })(); |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script> | |
| window.MathJax = { | |
| options: { | |
| ignoreHtmlClass: 'tex2jax_ignore', | |
| } | |
| }; | |
| </script> | |
| <script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script> | |
| <script defer="defer" src="svg_mathjax.js"></script> | |
| </head> | |
| <body> | |
| <div style="font-size: 12pt;"> | |
| <p> | |
| Outside an SVG, MathJax converts math automatically (e.g., \( a^2 + b^2 = c^2 \)). | |
| Inside an SVG, MathJax also converts math automatically, but the positioning is | |
| off. To address this, the "tex2jax_ignore" class is added to the SVGs parent div | |
| to prevent MathJax from processing the math within it. Then the "svg_mathjax.js" | |
| script: | |
| </p> | |
| <ol> | |
| <li>locates every math text in an SVG</li> | |
| <li>moves them to a temporary div,</li> | |
| <li>lets MathJax convert them to SVGs,</li> | |
| <li>calculates positioning adjustments,</li> | |
| <li>and inserts the generated SVGs in place of their text elements.</li> | |
| </ol> | |
| </div> | |
| <div class="tex2jax_ignore"> | |
| <svg width="300" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg"> | |
| <rect width="100%" height="100%" fill="grey" /> | |
| <text x="150" y="30" font-size="12pt">\( a^2 + b^2 = c^2 \)</text> | |
| <text x="150" y="60" font-size="12pt">L\( a^2 + b^2 = c^2 \)</text> | |
| <text x="150" y="90" font-size="12pt">R\( a^2 + b^2 = c^2 \)</text> | |
| </svg> | |
| </div> | |
| </body> | |
| </html> |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script> | |
| window.MathJax = { | |
| options: { | |
| ignoreHtmlClass: 'tex2jax_ignore', | |
| } | |
| }; | |
| </script> | |
| <script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script> | |
| <script defer="defer" src="svg_mathjax.js"></script> | |
| </head> | |
| <body> | |
| <div class="tex2jax_ignore"> | |
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="600"> | |
| <defs id="defs_block"> | |
| <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252"> | |
| <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" /> | |
| </filter> | |
| </defs> | |
| <title>blockdiag</title> | |
| <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="46" /> | |
| <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" /> | |
| <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" /> | |
| <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="126" /> | |
| <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="126" /> | |
| <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" /> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="100" y="65">plain text</text> | |
| <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" /> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="320" y="65">\( e^{-sT} \)</text> | |
| <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" /> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="512" y="70">R\( \frac{3}{s^2+1} \)</text> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="470" y="95">aligned at the right</text> | |
| <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="120" /> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="512" y="150">L\( H(s) \)</text> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="470" y="175">aligned at the left</text> | |
| <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="120" /> | |
| <text fill="rgb(0,0,0)" font-family="sansserif" font-size="11" font-style="normal" font-weight="normal" x="704" y="150">$ K_p + \left( 1 + {1 \over T s} \right) $</text> | |
| <path d="M 192 60 L 248 60" fill="none" stroke="rgb(0,0,0)" /> | |
| <polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" /> | |
| <path d="M 384 60 L 440 60" fill="none" stroke="rgb(0,0,0)" /> | |
| <polygon fill="rgb(0,0,0)" points="447,60 440,56 440,64 447,60" stroke="rgb(0,0,0)" /> | |
| <path d="M 384 60 L 416 60" fill="none" stroke="rgb(0,0,0)" /> | |
| <path d="M 416 60 L 416 140" fill="none" stroke="rgb(0,0,0)" /> | |
| <path d="M 416 140 L 440 140" fill="none" stroke="rgb(0,0,0)" /> | |
| <polygon fill="rgb(0,0,0)" points="447,140 440,136 440,144 447,140" stroke="rgb(0,0,0)" /> | |
| <path d="M 576 140 L 632 140" fill="none" stroke="rgb(0,0,0)" /> | |
| <polygon fill="rgb(0,0,0)" points="639,140 632,136 632,144 639,140" stroke="rgb(0,0,0)" /> | |
| <path d="M 768 140 L 784 140" fill="none" stroke="rgb(0,0,0)" /> | |
| <path d="M 784 140 L 784 25" fill="none" stroke="rgb(0,0,0)" /> | |
| <path d="M 320 25 L 784 25" fill="none" stroke="rgb(0,0,0)" /> | |
| <path d="M 320 25 L 320 32" fill="none" stroke="rgb(0,0,0)" /> | |
| <polygon fill="rgb(0,0,0)" points="320,39 316,32 324,32 320,39" stroke="rgb(0,0,0)" /> | |
| </svg> | |
| </div> | |
| </body> | |
| </html> |