Skip to content

Commit 9b73386

Browse files
committed
Make TeX rendering use the standard Markdown notation
1 parent d05adae commit 9b73386

File tree

2 files changed

+81
-40
lines changed

2 files changed

+81
-40
lines changed

core/src/main/kotlin/dev/fishies/coho/Utilities.kt

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,10 @@ import org.apache.batik.dom.GenericDOMImplementation
1111
import org.apache.batik.svggen.SVGGeneratorContext
1212
import org.apache.batik.svggen.SVGGraphics2D
1313
import org.apache.commons.text.StringEscapeUtils
14-
import org.scilab.forge.jlatexmath.TeXConstants
15-
import org.scilab.forge.jlatexmath.TeXFormula
16-
import org.scilab.forge.jlatexmath.TeXIcon
14+
import org.scilab.forge.jlatexmath.*
1715
import org.w3c.dom.DOMImplementation
1816
import org.w3c.dom.Document
1917
import java.awt.Dimension
20-
import java.awt.Insets
2118
import java.io.StringWriter
2219
import java.nio.file.Path
2320
import kotlin.io.path.absolute
@@ -270,25 +267,60 @@ fun parseMarkdownFrontmatter(srcText: String): Pair<Map<String?, Any?>?, String>
270267
return frontmatter to srcText.substring(nextSeparator + 4)
271268
}
272269

273-
fun renderTeX(string: String): String {
274-
val formula = TeXFormula(string)
275-
val icon: TeXIcon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, 20f)
276-
icon.setForeground(java.awt.Color.WHITE)
270+
enum class TeXStyle(val intStyle: Int) {
271+
/**
272+
* The large versions of big operators are used and limits are placed under and over
273+
* these operators (default). Symbols are rendered in the largest size.
274+
*/
275+
Display(TeXConstants.STYLE_DISPLAY),
277276

278-
val domImpl: DOMImplementation = GenericDOMImplementation.getDOMImplementation()
279-
val document: Document = domImpl.createDocument(null, "svg", null)
280-
val svgGenerator = SVGGraphics2D(SVGGeneratorContext.createDefault(document).apply {
281-
graphicContextDefaults = SVGGeneratorContext.GraphicContextDefaults().apply {
282-
paint = java.awt.Color.WHITE
283-
}
284-
}, true)
277+
/**
278+
* The small versions of big operators are used and limits are attached to
279+
* these operators as scripts (default). The same size as in the display style
280+
* is used to render symbols.
281+
*/
282+
Text(TeXConstants.STYLE_TEXT),
283+
284+
/**
285+
* The same as the text style, but symbols are rendered in a smaller size.
286+
*/
287+
Script(TeXConstants.STYLE_SCRIPT),
288+
289+
/**
290+
* The same as the script style, but symbols are rendered in a smaller size.
291+
*/
292+
SmallScript(TeXConstants.STYLE_SCRIPT_SCRIPT),
293+
}
294+
295+
fun renderTeX(string: String, style: TeXStyle = TeXStyle.Display): String? {
296+
try {
297+
val formula = TeXFormula(string)
298+
val icon: TeXIcon = formula.createTeXIcon(style.intStyle, 20f)
299+
icon.setForeground(java.awt.Color.WHITE)
285300

286-
svgGenerator.setSVGCanvasSize(Dimension(icon.iconWidth, icon.iconHeight))
287-
icon.paintIcon(null, svgGenerator, 0, 0)
301+
val domImpl: DOMImplementation = GenericDOMImplementation.getDOMImplementation()
302+
val document: Document = domImpl.createDocument(null, "svg", null)
303+
val svgGenerator = SVGGraphics2D(SVGGeneratorContext.createDefault(document).apply {
304+
graphicContextDefaults = SVGGeneratorContext.GraphicContextDefaults().apply {
305+
paint = java.awt.Color.WHITE
306+
}
307+
}, true)
308+
309+
svgGenerator.setSVGCanvasSize(Dimension(icon.iconWidth, icon.iconHeight))
310+
icon.paintIcon(null, svgGenerator, 0, 0)
288311

289-
val writer = StringWriter()
290-
svgGenerator.stream(writer)
291-
return writer.toString().replace(
292-
"<svg", """<svg data-formula="${string.escapeXml()}" class="latex"""""
293-
)
312+
val writer = StringWriter()
313+
svgGenerator.stream(writer)
314+
return writer.toString().replace(
315+
"<svg", """<svg data-formula="${string.escapeXml()}" class="latex"""""
316+
)
317+
} catch (e: JMathTeXException) {
318+
val lineCount = string.lines().size
319+
if (lineCount == 1) {
320+
err("TeX formula $string failed to parse: ${e.message}")
321+
} else {
322+
err("TeX formula\n${string.prependIndent(" ")}\nfailed to parse: ${e.message}")
323+
}
324+
return null
325+
}
294326
}

core/src/main/kotlin/dev/fishies/coho/markdown/MarkdownFile.kt

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
package dev.fishies.coho.markdown
22

33
import dev.fishies.coho.*
4-
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle
54
import org.intellij.markdown.*
65
import org.intellij.markdown.ast.ASTNode
76
import org.intellij.markdown.ast.getTextInNode
87
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
8+
import org.intellij.markdown.flavours.gfm.GFMElementTypes
99
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
1010
import org.intellij.markdown.html.*
1111
import org.intellij.markdown.parser.LinkMap
1212
import org.intellij.markdown.parser.MarkdownParser
13-
import org.scilab.forge.jlatexmath.LaTeXAtom
14-
import org.scilab.forge.jlatexmath.TeXFormulaParser
15-
import org.scilab.forge.jlatexmath.TeXParser
1613
import java.nio.file.Path
1714
import java.util.regex.Pattern.compile
1815
import kotlin.io.path.*
@@ -78,26 +75,36 @@ private val highlightedCodeFenceProvider = object : GeneratingProvider {
7875
state = 1
7976
}
8077
}
78+
visitor.consumeHtml("<pre class=\"codeblock\">")
79+
visitor.consumeTagOpen(node, "code", *attributes.toTypedArray())
80+
val highlighted = language?.run { content.toString().highlight(this) } ?: content.toString().escapeHtml()
81+
visitor.consumeHtml(highlighted)
8182

82-
if (language == "latex") {
83-
visitor.consumeHtml(renderTeX(content.toString()))
84-
} else {
85-
visitor.consumeHtml("<pre class=\"codeblock\">")
83+
if (state == 0) {
8684
visitor.consumeTagOpen(node, "code", *attributes.toTypedArray())
87-
val highlighted = language?.run { content.toString().highlight(this) } ?: content.toString().escapeHtml()
88-
visitor.consumeHtml(highlighted)
85+
}
86+
if (lastChildWasContent) {
87+
visitor.consumeHtml("\n")
88+
}
89+
visitor.consumeHtml("</code></pre>")
90+
}
91+
}
8992

90-
if (state == 0) {
91-
visitor.consumeTagOpen(node, "code", *attributes.toTypedArray())
92-
}
93-
if (lastChildWasContent) {
94-
visitor.consumeHtml("\n")
95-
}
96-
visitor.consumeHtml("</code></pre>")
93+
private fun createMathProvider(inline: Boolean) = object : GeneratingProvider {
94+
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
95+
val nodes = node.children.subList(1, node.children.size - 1)
96+
val output = nodes.joinToString(separator = "") { it.getTextInNode(text) }.trim()
97+
renderTeX(output, if (inline) TeXStyle.Text else TeXStyle.Display)?.let {
98+
visitor.consumeHtml("<span class=\"latex\">")
99+
visitor.consumeHtml(it)
100+
visitor.consumeHtml("</span>")
97101
}
98102
}
99103
}
100104

105+
private val blockMathProvider by lazy { createMathProvider(inline = false) }
106+
private val inlineMathProvider by lazy { createMathProvider(inline = true) }
107+
101108
/**
102109
* Hacked together Markdown flavor descriptor to highlight codeblocks with Prism4j.
103110
*/
@@ -108,7 +115,9 @@ open class SyntaxHighlightedGFMFlavourDescriptor(
108115
val base = super.createHtmlGeneratingProviders(linkMap, baseURI)
109116
return base + mapOf(
110117
MarkdownElementTypes.CODE_SPAN to highlightedCodeSpanProvider,
111-
MarkdownElementTypes.CODE_FENCE to highlightedCodeFenceProvider
118+
MarkdownElementTypes.CODE_FENCE to highlightedCodeFenceProvider,
119+
GFMElementTypes.BLOCK_MATH to blockMathProvider,
120+
GFMElementTypes.INLINE_MATH to inlineMathProvider,
112121
)
113122
}
114123
}

0 commit comments

Comments
 (0)