SCDoc (schelp) style add-on: LaTex, MathML, subsubsection, multimedia file, iframe, sub, sup, small, line height, hr

To use

  • the following tags
    • <br>
    • <p>
    • <p style='line-height:>
    • <hr>
    • <small>
    • <em>
    • <strong>
    • <sub>
    • <sup>
    • <audio control>
    • <video control>
    • <iframe>
  • and subsubsection for TOC and contents
    I modified SCDocHTMLRenderer.renderToFile as follows:
	*renderToFile {|filename, doc, root|
		var stream, streamedText, spacesInSectionTitleRemover;
		File.mkdir(filename.dirname);
		stream = File(filename, "w");
		spacesInSectionTitleRemover = { |string|
			var contentsDivIndex, toTOCend, afterTOC, founds, replaced;
			contentsDivIndex = string.find("<div class='contents'>");
			toTOCend = string[.. contentsDivIndex - 1];
			afterTOC = string[contentsDivIndex ..];
			founds = afterTOC.findAllRegexp("  (?=.*</h4>)");
			while { founds.includes(nil) } { founds.remove(nil) };
			founds = founds.asSet.asArray.sort({ |a, b| a[0] < b[0] });
			replaced = afterTOC;
			if(founds.size > 0) {
				founds.reverse.do { |idx_str|
					var foundIndex = idx_str;
					replaced = if (foundIndex > 0) {
						var lastString = replaced[foundIndex + 6 ..];
						lastString = if(lastString != nil) { lastString } { "" };
						replaced[0 .. foundIndex - 1] ++ lastString
					} {
						replaced[6 ..]
					}
				}
			} {
				replaced
			};
			toTOCend ++ replaced;
		};
		if(stream.isOpen) {
			this.renderOnStream(stream, doc, root);
			stream.close;

			stream = File(filename, "r");
			streamedText = stream.readAllString;
			stream.close;

			streamedText = streamedText
			.replace("<li class='toc2'><a href='#___", "<li class='toc2'><a href='#___")
			.replace("___</a></li>", "</a></li>")
			.replace("<h3><a class='anchor' name='___", "<h4><a class='anchor' name='___")
			.replace("___'>___", "___'>  ")
			.replace("___</a></h3>", "</a></h4>")
			.replace("&lt;p&gt;", "<p>")
			.replace("&lt;p style='line-height:", "<p style='line-height:")
			.replace("em'&gt;", "em'>")
			.replace("&lt;/p&gt;", "</p>")
			.replace("&lt;br&gt;", "<br>")
			.replace("&lt;hr&gt;", "<hr>")
			.replace("&lt;small&gt;", "<small>")
			.replace("&lt;/small&gt;", "</small>")
			.replace("&lt;em&gt;", "<em>")
			.replace("&lt;/em&gt;", "</em>")
			.replace("&lt;strong&gt;", "<strong>")
			.replace("&lt;/strong&gt;", "</strong>")
			.replace("&lt;sub&gt;", "<sub>")
			.replace("&lt;/sub&gt;", "</sub>")
			.replace("&lt;sup&gt;", "<sup>")
			.replace("&lt;/sup&gt;", "</sup>")
			.replace("&lt;audio controls autoplay&gt;", "<audio controls autoplay>")
			.replace("&lt;audio controls&gt;", "<audio controls>")
			.replace("&lt;source src=", "<source src=")
			.replace("/wav'&gt;", "/wav'>")
			.replace("/aiff'&gt;", "/aiff'>")
			.replace("/flac'&gt;", "/flac'>")
			.replace("/ogg'&gt;", "/ogg'>")
			.replace("/mpeg'&gt;", "/mpeg'>")
			.replace("/mp4'&gt;", "/mp4'>")
			.replace("/x-ms-wma'&gt;", "/x-ms-wma'>")
			.replace("&lt;/audio&gt;", "</audio>")
			.replace("&lt;video controls autoplay&gt;", "<video controls autoplay>")
			.replace("&lt;video controls&gt;", "<video controls>")
			.replace("&lt;video controls width=", "<video controls width=")
			.replace("&lt;/video&gt;", "</video>")
			.replace("&lt;iframe", "<iframe")
			.replace("&lt;/iframe&gt;", "</iframe>")
			.replace("src='<a href=\"", "src='")
			.replace("'\">http", "'>http")
			.replace("'</a> title='", " title='")
			.replace("'</a> type=", " type=")
			.replace("'&gt; <source src='", "'> <source src='")
			.replace("❪", "<span style='font-size: 0.92em'>❪")
			.replace("❫", "❫</span>")
			.replace("❨", "<span style='font-size: 0.92em'>❨")
			.replace("❩", "❩</span>")
			.replace("❲", "<small>❲")
			.replace("❳", "❳</small>")
			.replace("⌊", "	<span class='sup'>")
			.replace("⌋", "</span>")
			.replace("⌈", "<span class='sub'>")
			.replace("⌉", "</span>");

			streamedText = spacesInSectionTitleRemover.(streamedText);
			stream = File(filename, "w");
			stream << streamedText;
			stream.close
		} {
			warn("SCDoc: Could not open file % for writing".format(filename));
		};
	}

About the subsubsection, I already mentioned several times.
For example:

The tags using :: are hard coded, so it seems that one should modify cpp, hpp, l and y files to use these tags in SCDoc syntax. So I have done this in the sc file.

My question is if this could be part of the core libray or should it be a quark if I am going to share this with others. I think these tags are useful to make documentation more user friendly.

I have added LaTex and MathML. SC HelpBrowser seems to display the mathematical expression correctly. However, Chrome displays them better.

It would be ideal to be able to do these things using SCDoc syntax only. Mixing SCDoc syntax, HTML syntax, MathML syntax and LaTeX syntax is a bit confusing, but flexible!

	*renderHeader {|stream, doc, body|
		var x, cats, m, z;
		var thisIsTheMainHelpFile;
		var folder = doc.path.dirname;
		var undocumented = false;
		var displayedTitle;
		if(folder==".",{folder=""});

		// FIXME: use SCDoc.helpTargetDir relative to baseDir
		baseDir = ".";
		doc.path.occurrencesOf($/).do {
			baseDir = baseDir ++ "/..";
		};

		thisIsTheMainHelpFile = (doc.title == "Help") and: {
			(folder == "") or:
			{ (thisProcess.platform.name === \windows) and: { folder == "Help" } }
		};

		stream
		<< "<!doctype html>"
		<< "<html lang='en'>"
		<< "<head><title>";

		if(thisIsTheMainHelpFile) {
			stream << "SuperCollider " << Main.version << " Help";
		} {
			stream << doc.title << " | SuperCollider " << Main.version << " Help";
		};

		// XXX if you make changes here, make sure to also update the static HTML files
		// (Search.html, Browse.html, etc.) if necessary.
		stream
		<< "</title>\n"
		<< "<link rel='stylesheet' href='" << baseDir << "/scdoc.css' type='text/css' />\n"
		<< "<link rel='stylesheet' href='" << baseDir << "/codemirror.css' type='text/css' />\n"
		<< "<link rel='stylesheet' href='" << baseDir << "/editor.css' type='text/css' />\n"
		<< "<link rel='stylesheet' href='" << baseDir << "/frontend.css' type='text/css' />\n"
		<< "<link rel='stylesheet' href='" << baseDir << "/custom.css' type='text/css' />\n"
		<< "<meta name='viewport' content='width=device-width, initial-scale=1'>\n"
		<< "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />\n"
		<< "<script src='" << baseDir << "/lib/jquery.min.js'></script>\n"
		<< "<script src='" << baseDir << "/lib/codemirror-5.39.2.min.js' type='text/javascript'></script>\n"
		<< "<script src='" << baseDir << "/lib/codemirror-addon-simple-5.39.2.min.js' type='text/javascript'></script>\n"
		<< "<script>\n"
		<< "var helpRoot = '" << baseDir << "';\n"
		<< "var scdoc_title = '" << doc.title.escapeChar($') << "';\n"
		<< "var scdoc_sc_version = '" << Main.version << "';\n"
		<< "</script>\n"
		<< "<script src='" << baseDir << "/scdoc.js' type='text/javascript'></script>\n"
		<< "<script src='" << baseDir << "/docmap.js' type='text/javascript'></script>\n" // FIXME: remove?
		<< "<script src='" << baseDir << "/frontend.js' type='text/javascript'></script>\n"
		// QWebChannel access
		<< "<script src='qrc:///qtwebchannel/qwebchannel.js' type='text/javascript'></script>\n"
		<< "<script src='https://polyfill.io/v3/polyfill.min.js?features=es6'></script>\n"
		<< "<script id='MathJax-script' async src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'></script>\n"
		<< "</head>\n"
		<< "<body onload='fixTOC()'>\n";


		displayedTitle = if(
			thisIsTheMainHelpFile,
			{ "SuperCollider " ++ Main.version },
			{ doc.title }
		);

		stream
		<< "<div id='toc'>\n"
		<< "<div id='toctitle'>" << displayedTitle << ":</div>\n"
		<< "<span class='toc_search'>Filter: <input id='toc_search'></span>";
		this.renderTOC(stream, body);
		stream << "</div>";

		stream
		<< "<div id='menubar'></div>\n"
		<< "<div class='contents'>\n"
		<< "<div class='header'>\n";

		if(thisIsTheMainHelpFile.not) {
			stream
			<< "<div id='label'>\n"
			<< "<span id='folder'>" << folder.asString;
			if(doc.isExtension) {
				stream << " (extension)";
			};
			stream << "</span>\n";

			doc.categories !? {
				// Prevent the label from starting with "|".
				if(folder.asString.size > 0) {
					stream << " | "
				};

				stream << "<span id='categories'>"

				<< (doc.categories.collect { | path |
					// get all the components of a category path ("UGens>Generators>Deterministic")
					// we link each crumb of the breadcrumbs separately.
					var pathElems = path.split($>);

					// the href for "UGens" will be "UGens", for "Generators" "UGens>Generators", etc.
					pathElems.collect { | elem, i |
						var atag = "<a href='" ++ baseDir +/+ "Browse.html#";
						atag ++ pathElems[0..i].join(">") ++ "'>"++ elem ++"</a>"
					}.join("&#8201;&gt;&#8201;"); // &#8201; is a thin space

				}.join(" | "))

				<< "</span>\n";
			};

			stream << "</div>";
		};

		stream << "<h1>" << displayedTitle;
		if(thisIsTheMainHelpFile) {
			stream << "<span class='headerimage'><img src='" << baseDir << "/images/SC_icon.png'/></span>";
		};
		if(doc.isClassDoc and: { currentClass.notNil } and: { currentClass != Object }) {
			stream << "<span id='superclasses'>"
			<< " : "
			<< (currentClass.superclasses.collect {|c|
				"<a href=\"../Classes/"++c.name++".html\">"++c.name++"</a>"
			}.join(" : "))
			<< "</span>\n";
		};
		if(doc.isExtension) {
			stream
			<< "<div class='extension-indicator-ctr' title='This help file originates from a third-party quark or plugin for SuperCollider.'>"
			<< "<img class='extension-indicator-icon' alt='Extension' src='" << baseDir << "/images/plugin.png'>"
			<< "<span class='extension-indicator-text'>Extension</span>"
			<< "</div>";
		};
		stream
		<< "</h1>\n"
		<< "<div id='summary'>" << this.escapeSpecialChars(doc.summary) << "</div>\n"
		<< "</div>\n"
		<< "<div class='subheader'>\n";

		if(doc.isClassDoc) {
			if(currentClass.notNil) {
				m = currentClass.filenameSymbol.asString;
				stream << "<div id='filename'>Source: "
				<< "<a href='%' title='%'>".format(URI.fromLocalPath(m).asString, m)
				<< m.basename << "</a></div>";
				if(currentClass.subclasses.notNil) {
					z = false;
					stream << "<div id='subclasses'>"
					<< "Subclasses: "
					<< (currentClass.subclasses.collect(_.name).sort.collect {|c,i|
						if(i==4,{z=true;"<span id='hiddensubclasses' style='display:none;'>"},{""})
						++"<a href=\"../Classes/"++c++".html\">"++c++"</a>"
					}.join(", "));
					if(z) {
						stream << "</span><a class='subclass_toggle' href='#' onclick='javascript:showAllSubclasses(this); return false'>&hellip;&nbsp;see&nbsp;all</a>";
					};
					stream << "</div>\n";
				};
				if(currentImplClass.notNil) {
					stream << "<div class='inheritance'>Implementing class: "
					<< "<a href=\"../Classes/" << currentImplClass.name << ".html\">"
					<< currentImplClass.name << "</a></div>\n";
				};
			} {
				stream << "<div id='filename'>Location: <b>NOT INSTALLED!</b></div>\n";
			};
		};

		doc.related !? {
			stream << "<div id='related'>See also: "
			<< (doc.related.collect {|r| this.htmlForLink(r)}.join(", "))
			<< "</div>\n";
		};

		// FIXME: Remove this when conversion to new help system is done!
		if(doc.isUndocumentedClass and: {Help.respondsTo('findHelpFile')}) {
			x = Help.findHelpFile(name);
			x !? {
				stream << ("[ <a href='" ++ baseDir ++ "/OldHelpWrapper.html#"
					++x++"?"++SCDoc.helpTargetDir +/+ doc.path ++ ".html"
					++"'>old help</a> ]")
			};
		};

		stream << "</div>\n";
	}
	*renderToFile {|filename, doc, root|
		var stream, streamedText, spacesInSectionTitleRemover;
		File.mkdir(filename.dirname);
		stream = File(filename, "w");
		spacesInSectionTitleRemover = { |string|
			var contentsDivIndex, toTOCend, afterTOC, founds, replaced;
			contentsDivIndex = string.find("<div class='contents'>");
			toTOCend = string[.. contentsDivIndex - 1];
			afterTOC = string[contentsDivIndex ..];
			founds = afterTOC.findAllRegexp("  (?=.*</h4>)");
			while { founds.includes(nil) } { founds.remove(nil) };
			founds = founds.asSet.asArray.sort({ |a, b| a[0] < b[0] });
			replaced = afterTOC;
			if(founds.size > 0) {
				founds.reverse.do { |idx_str|
					var foundIndex = idx_str;
					replaced = if (foundIndex > 0) {
						var lastString = replaced[foundIndex + 6 ..];
						lastString = if(lastString != nil) { lastString } { "" };
						replaced[0 .. foundIndex - 1] ++ lastString
					} {
						replaced[6 ..]
					}
				}
			} {
				replaced
			};
			toTOCend ++ replaced;
		};
		if(stream.isOpen) {
			this.renderOnStream(stream, doc, root);
			stream.close;

			stream = File(filename, "r");
			streamedText = stream.readAllString;
			stream.close;

			streamedText = streamedText
			.replace("<li class='toc2'><a href='#___", "<li class='toc2'><a href='#___")
			.replace("___</a></li>", "</a></li>")
			.replace("<h3><a class='anchor' name='___", "<h4><a class='anchor' name='___")
			.replace("___'>___", "___'>  ")
			.replace("___</a></h3>", "</a></h4>")
			.replace("&lt;p&gt;", "<p>")
			.replace("&lt;p style='line-height:", "<p style='line-height:")
			.replace("em'&gt;", "em'>")
			.replace("&lt;/p&gt;", "</p>")
			.replace("&lt;br&gt;", "<br>")
			.replace("&lt;hr&gt;", "<hr>")
			.replace("&lt;small&gt;", "<small>")
			.replace("&lt;/small&gt;", "</small>")
			.replace("&lt;em&gt;", "<em>")
			.replace("&lt;/em&gt;", "</em>")
			.replace("&lt;strong&gt;", "<strong>")
			.replace("&lt;/strong&gt;", "</strong>")
			.replace("&lt;sub&gt;", "<sub>")
			.replace("&lt;/sub&gt;", "</sub>")
			.replace("&lt;sup&gt;", "<sup>")
			.replace("&lt;/sup&gt;", "</sup>")
			.replace("&lt;audio controls autoplay&gt;", "<audio controls autoplay>")
			.replace("&lt;audio controls&gt;", "<audio controls>")
			.replace("&lt;source src=", "<source src=")
			.replace("/wav'&gt;", "/wav'>")
			.replace("/aiff'&gt;", "/aiff'>")
			.replace("/flac'&gt;", "/flac'>")
			.replace("/ogg'&gt;", "/ogg'>")
			.replace("/mpeg'&gt;", "/mpeg'>")
			.replace("/mp4'&gt;", "/mp4'>")
			.replace("/x-ms-wma'&gt;", "/x-ms-wma'>")
			.replace("&lt;/audio&gt;", "</audio>")
			.replace("&lt;video controls autoplay&gt;", "<video controls autoplay>")
			.replace("&lt;video controls&gt;", "<video controls>")
			.replace("&lt;video controls width=", "<video controls width=")
			.replace("&lt;/video&gt;", "</video>")
			.replace("&lt;iframe", "<iframe")
			.replace("&lt;/iframe&gt;", "</iframe>")
			.replace("src='<a href=\"", "src='")
			.replace("'\">http", "'>http")
			.replace("'</a> title='", " title='")
			.replace("'</a> type=", " type=")
			.replace("'&gt; <source src='", "'> <source src='")
			// mathjax ->
			// .replace("&lt;script type='text/x-mathjax-config'&gt;", "<script type='text/x-mathjax-config'>")
			// .replace("&lt;/script&gt;", "</script>")
			// MathML ->
			.replace("&lt;annotation&gt;", "<annotation>")
			.replace("&lt;/annotation&gt;", "</annotation>")
			.replace("&lt;apply&gt;", "<apply>")
			.replace("&lt;/apply&gt;", "</apply>")
			.replace("&lt;ci&gt;", "<ci>")
			.replace("&lt;/ci&gt;", "</ci>")
			.replace("&lt;cn&gt;", "<cn>")
			.replace("&lt;/cn&gt;", "</cn>")
			.replace("&lt;condition&gt;", "<condition>")
			.replace("&lt;/condition&gt;", "</condition>")
			.replace("&lt;diff&gt;", "<diff>")
			.replace("&lt;/diff&gt;", "</diff>")
			.replace("&lt;int&gt;", "<int>")
			.replace("&lt;/int&gt;", "</int>")
			.replace("&lt;lambda&gt;", "<lambda>")
			.replace("&lt;/lambda&gt;", "</lambda>")
			.replace("&lt;limit&gt;", "<limit>")
			.replace("&lt;/limit&gt;", "</limit>")
			.replace("&lt;list&gt;", "<list>")
			.replace("&lt;/list&gt;", "</list>")
			.replace("&lt;math&gt;", "<math>")
			.replace("&lt;/math&gt;", "</math>")
			.replace("&lt;matrix&gt;", "<matrix>")
			.replace("&lt;/matrix&gt;", "</matrix>")
			.replace("&lt;mfenced&gt;", "<mfenced>")
			.replace("&lt;mfenced", "<mfenced")
			.replace("&lt;/mfenced&gt;", "</mfenced>")
			.replace("&lt;mfrac&gt;", "<mfrac>")
			.replace("&lt;/mfrac&gt;", "</mfrac>")
			.replace("&lt;mi&gt;", "<mi>")
			.replace("&lt;/mi&gt;", "</mi>")
			.replace("&lt;mn&gt;", "<mn>")
			.replace("&lt;/mn&gt;", "</mn>")
			.replace("&lt;mo&gt;", "<mo>")
			.replace("&lt;/mo&gt;", "</mo>")
			.replace("&lt;mrow&gt;", "<mrow>")
			.replace("&lt;/mrow&gt;", "</mrow>")
			.replace("&lt;msqrt&gt;", "<msqrt>")
			.replace("&lt;/msqrt&gt;", "</msqrt>")
			.replace("&lt;msub&gt;", "<msub>")
			.replace("&lt;/msub&gt;", "</msub>")
			.replace("&lt;msubsup&gt;", "<msubsup>")
			.replace("&lt;/msubsup&gt;", "</msubsup>")
			.replace("&lt;msup&gt;", "<msup>")
			.replace("&lt;/msup&gt;", "</msup>")
			.replace("&lt;mtext&gt;", "<mtext>")
			.replace("&lt;/mtext&gt;", "</mtext>")
			.replace("&lt;product&gt;", "<product>")
			.replace("&lt;/product&gt;", "</product>")
			.replace("&lt;semantics&gt;", "<semantics>")
			.replace("&lt;/semantics&gt;", "</semantics>")
			.replace("&lt;set&gt;", "<set>")
			.replace("&lt;/set&gt;", "</set>")
			.replace("&lt;sum&gt;", "<sum>")
			.replace("&lt;/sum&gt;", "</sum>")
			.replace("&lt;table&gt;", "<table>")
			.replace("&lt;/table&gt;", "</table>")
			.replace("&lt;table&gt;", "<table>")
			.replace("&lt;/table&gt;", "</table>")
			.replace("&lt;tendsto&gt;", "<tendsto>")
			.replace("&lt;/tendsto&gt;", "</tendsto>")
			.replace("&lt;vector&gt;", "<vector>")
			.replace("&lt;/vector&gt;", "</vector>")
			/*.replace(
				"&lt;math xmlns='<a href=" ++ "http://www.w3.org/1998/Math/MathML'>".quote ++ ">http://www.w3.org/1998/Math/MathML'&gt;</a>",
				"<math xmlns='http://www.w3.org/1998/Math/MathML'>")*/
			.replace("&amp;part;", "&part;")
			// personal->
			.replace(")'&gt;", ")'>")
			.replace("]'&gt;", "]'>")
			.replace("}'&gt;", "}'>")
			.replace("❪", "<span style='font-size: 0.85em'>❪")
			.replace("❫", "❫</span>")
			.replace("❨", "<span style='font-size: 0.85em'>❨")
			.replace("❩", "❩</span>")
			.replace("❲", "<span style='font-size: 0.65em'>❲")
			.replace("❳", "❳</span>");

			streamedText = spacesInSectionTitleRemover.(streamedText);
			stream = File(filename, "w");
			stream << streamedText;
			stream.close
		} {
			warn("SCDoc: Could not open file % for writing".format(filename));
		};
	}




chrome:

1 Like

Implementation details aside, it would be great to see some of these features supported in SCDocs :slight_smile:

That MathML syntax looks incredibly cumbersome, (e.g. the log_2^2 example), would there a reason to support both MathML and LaTex? Would MathJax (for LaTeX support) have to ship with SC to be used offline (maintenance, license, future proofing concerns…)?

I think the mixing of syntaxes is a bit problematic, and would need to be very well documented. It could be that only a subset is supported initially to make sure features are introduced in clear way.

Would you mind linking to your SC fork where these changes are implemented (or even open a PR to discuss)?

1 Like

One thing that’s important to remember - the HTML renderer is not the only output for SCDoc files (for example, the IDE currently has it’s own custom rendering for certain pieces of SCDoc files for use in autocomplete - the sclang Language Server / vscode plugin follows the same pattern, and may end up using a Markdown renderer for some cases).

So: anything in SCDoc files must be renderable for non-HTML outputs. I think for something like MathML, it would be reasonable to assume that this might not be supported for all renderers, but at least this would need to be tagged in a way that other renderers could skip it if it was not supported.

I don’t know how it compares to MathML, but there are markdown math syntaxes that can render “pretty” versions but are also readable in pure text as well, and are reasonable to write without learning a new syntax: Writing mathematical expressions - GitHub Docs
I wonder if this would be helpful?

3 Likes

I must say, the markdown syntax used in GitHub seems to be simpler to adopt. Appears to be a good fit for SC, but it will not be “great.” (and also for this forum, why not?)

example:

   Given a rational number $p$, its continued fraction representation is a sequence $[a_0; a_1, a_2, a_3, \ldots, a_n]$ of integers $a_i$, such that:
   p = a_0 + \cfrac{1}{a_1 + \cfrac{1}{a_2 + \cfrac{1}{a_3 + \ddots + \cfrac{1}{a_n}}}}

OUTPUT GitHub (not perfect):

Compare with Katex (looks neat):

image

1 Like

This isn’t a markdown syntax, but rather is LaTeX syntax supported by GitHub’s markdown renderer—but that should be fine because LaTeX is a widely used standard. Their renderer uses MathJax, which from a quick reading requires a node package to be installed locally, or for a server to be running remotely, which could be a roadblock (I’m not familiar with Node though…).

If this were the solution the tags (enclosing $s and ```math) should be escapable.

1 Like

I think you only need to have the MathJax.js file, I don’t think you need node or internet. At least in most browsers, let’s test on the scdoc qt window.

2 Likes

Here is my PR for this:

MathML shows mathematical expression also in teletype, while LaTeX via MathJac does not. However, I do not exactly know when teletype is needed. If you think supporting mathematical expression in teletype is not needed, I can completely remove MathML.

Currently, I am using Web Integration. With this kind of integration, there seems to be no need to ship MathJax with SC, I believe. However, we also should think about the use case without an internet connection, and then MathJax should be included in SC. Also, it would be better to include SC because the script does not need to be changed when MathJax releases a new version of the script.

Thanks for letting me know it! However, if I understand correctly, the link you provided is related to the mathematical expression on GitHub. Unfortunately, I could not find how to integrate it in SC-Doc.

I changed this to be available offline.

I have revised the mathematical expression feature in the following separate PR:

  • MathJax is shipped with SC.
  • MathML is still supported because MathJax also supports MathML.
  • In this PR, mathematical expressions can also be used in teletype using both LaTeX and MathML.
1 Like

Another example of MathJax, it is remarkably good:

https://www.w3.org/TR/2021/NOTE-audio-eq-cookbook-20210608/

2 Likes