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(" > "); //   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'>… see 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("<p>", "<p>")
.replace("<p style='line-height:", "<p style='line-height:")
.replace("em'>", "em'>")
.replace("</p>", "</p>")
.replace("<br>", "<br>")
.replace("<hr>", "<hr>")
.replace("<small>", "<small>")
.replace("</small>", "</small>")
.replace("<em>", "<em>")
.replace("</em>", "</em>")
.replace("<strong>", "<strong>")
.replace("</strong>", "</strong>")
.replace("<sub>", "<sub>")
.replace("</sub>", "</sub>")
.replace("<sup>", "<sup>")
.replace("</sup>", "</sup>")
.replace("<audio controls autoplay>", "<audio controls autoplay>")
.replace("<audio controls>", "<audio controls>")
.replace("<source src=", "<source src=")
.replace("/wav'>", "/wav'>")
.replace("/aiff'>", "/aiff'>")
.replace("/flac'>", "/flac'>")
.replace("/ogg'>", "/ogg'>")
.replace("/mpeg'>", "/mpeg'>")
.replace("/mp4'>", "/mp4'>")
.replace("/x-ms-wma'>", "/x-ms-wma'>")
.replace("</audio>", "</audio>")
.replace("<video controls autoplay>", "<video controls autoplay>")
.replace("<video controls>", "<video controls>")
.replace("<video controls width=", "<video controls width=")
.replace("</video>", "</video>")
.replace("<iframe", "<iframe")
.replace("</iframe>", "</iframe>")
.replace("src='<a href=\"", "src='")
.replace("'\">http", "'>http")
.replace("'</a> title='", " title='")
.replace("'</a> type=", " type=")
.replace("'> <source src='", "'> <source src='")
// mathjax ->
// .replace("<script type='text/x-mathjax-config'>", "<script type='text/x-mathjax-config'>")
// .replace("</script>", "</script>")
// MathML ->
.replace("<annotation>", "<annotation>")
.replace("</annotation>", "</annotation>")
.replace("<apply>", "<apply>")
.replace("</apply>", "</apply>")
.replace("<ci>", "<ci>")
.replace("</ci>", "</ci>")
.replace("<cn>", "<cn>")
.replace("</cn>", "</cn>")
.replace("<condition>", "<condition>")
.replace("</condition>", "</condition>")
.replace("<diff>", "<diff>")
.replace("</diff>", "</diff>")
.replace("<int>", "<int>")
.replace("</int>", "</int>")
.replace("<lambda>", "<lambda>")
.replace("</lambda>", "</lambda>")
.replace("<limit>", "<limit>")
.replace("</limit>", "</limit>")
.replace("<list>", "<list>")
.replace("</list>", "</list>")
.replace("<math>", "<math>")
.replace("</math>", "</math>")
.replace("<matrix>", "<matrix>")
.replace("</matrix>", "</matrix>")
.replace("<mfenced>", "<mfenced>")
.replace("<mfenced", "<mfenced")
.replace("</mfenced>", "</mfenced>")
.replace("<mfrac>", "<mfrac>")
.replace("</mfrac>", "</mfrac>")
.replace("<mi>", "<mi>")
.replace("</mi>", "</mi>")
.replace("<mn>", "<mn>")
.replace("</mn>", "</mn>")
.replace("<mo>", "<mo>")
.replace("</mo>", "</mo>")
.replace("<mrow>", "<mrow>")
.replace("</mrow>", "</mrow>")
.replace("<msqrt>", "<msqrt>")
.replace("</msqrt>", "</msqrt>")
.replace("<msub>", "<msub>")
.replace("</msub>", "</msub>")
.replace("<msubsup>", "<msubsup>")
.replace("</msubsup>", "</msubsup>")
.replace("<msup>", "<msup>")
.replace("</msup>", "</msup>")
.replace("<mtext>", "<mtext>")
.replace("</mtext>", "</mtext>")
.replace("<product>", "<product>")
.replace("</product>", "</product>")
.replace("<semantics>", "<semantics>")
.replace("</semantics>", "</semantics>")
.replace("<set>", "<set>")
.replace("</set>", "</set>")
.replace("<sum>", "<sum>")
.replace("</sum>", "</sum>")
.replace("<table>", "<table>")
.replace("</table>", "</table>")
.replace("<table>", "<table>")
.replace("</table>", "</table>")
.replace("<tendsto>", "<tendsto>")
.replace("</tendsto>", "</tendsto>")
.replace("<vector>", "<vector>")
.replace("</vector>", "</vector>")
/*.replace(
"<math xmlns='<a href=" ++ "http://www.w3.org/1998/Math/MathML'>".quote ++ ">http://www.w3.org/1998/Math/MathML'></a>",
"<math xmlns='http://www.w3.org/1998/Math/MathML'>")*/
.replace("&part;", "∂")
// personal->
.replace(")'>", ")'>")
.replace("]'>", "]'>")
.replace("}'>", "}'>")
.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: