Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Add node renderer for TeX nodes

AuthorDaveJarvis <email>
Date2020-09-13 14:22:20 GMT-0700
Commit8171250a1c10e9e1f564d9395a4f239c04bc5f41
Parente4db02b
Delta205 lines added, 8 lines removed, 197-line increase
src/main/resources/com/scrivenvar/preview/webview.css
* See SVGReplacedElementFactory for details.
*/
-svg {
+svg, tex {
/* Ensure the formulas can be inlined with text. */
display: inline-block;
src/main/java/com/scrivenvar/processors/markdown/ImageLinkExtension.java
import com.scrivenvar.service.Options;
import com.vladsch.flexmark.ast.Image;
-import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.IndependentLinkResolverFactory;
import com.vladsch.flexmark.html.LinkResolver;
import static com.scrivenvar.StatusBarNotifier.alert;
import static com.scrivenvar.util.ProtocolResolver.getProtocol;
+import static com.vladsch.flexmark.html.HtmlRenderer.Builder;
+import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
import static java.lang.String.format;
/**
* Responsible for ensuring that images can be rendered relative to a path.
* This allows images to be located virtually anywhere.
*/
-public class ImageLinkExtension implements HtmlRenderer.HtmlRendererExtension {
+public class ImageLinkExtension implements HtmlRendererExtension {
/**
* Used for image directory preferences.
@Override
- public void extend(
- final HtmlRenderer.Builder rendererBuilder,
- @NotNull final String rendererType ) {
- rendererBuilder.linkResolverFactory( new Factory() );
+ public void extend( @NotNull final Builder builder,
+ @NotNull final String rendererType ) {
+ builder.linkResolverFactory( new Factory() );
}
src/main/java/com/scrivenvar/processors/markdown/MarkdownProcessor.java
extensions.add( ImageLinkExtension.create( path ) );
extensions.add( BlockExtension.create() );
+ extensions.add( TeXExtension.create() );
// TODO: https://github.com/FAlthausen/Vollkorn-Typeface/issues/38
// TODO: Uncomment when Vollkorn ligatures are fixed.
// extensions.add( LigatureExtension.create() );
mRenderer = HtmlRenderer.builder().extensions( extensions ).build();
- mParser = Parser.builder().extensions( extensions ).build();
+ mParser = Parser.builder()
+ .extensions( extensions )
+ .build();
}
src/main/java/com/scrivenvar/processors/markdown/TeXExtension.java
+package com.scrivenvar.processors.markdown;
+
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.util.data.MutableDataHolder;
+import org.jetbrains.annotations.NotNull;
+
+import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
+import static com.vladsch.flexmark.parser.Parser.ParserExtension;
+
+/**
+ * Responsible for wrapping delimited TeX code in Markdown into an XML element
+ * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes
+ * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer
+ * is responsible for converting the TeX code for display. This avoids inserting
+ * SVG code into the Markdown document, which the parser would then have to
+ * iterate---a <em>very</em> wasteful operation that impacts front-end
+ * performance.
+ */
+public class TeXExtension implements ParserExtension, HtmlRendererExtension {
+ /**
+ * Creates an extension capable of handling delimited TeX code in Markdown.
+ *
+ * @return The new {@link TeXExtension}, never {@code null}.
+ */
+ public static TeXExtension create() {
+ return new TeXExtension();
+ }
+
+ /**
+ * Force using the {@link #create()} method for consistency.
+ */
+ private TeXExtension() {
+ }
+
+ /**
+ * Adds the TeX extension for HTML document export types.
+ *
+ * @param builder The document builder.
+ * @param rendererType Indicates the document type to be built.
+ */
+ @Override
+ public void extend( @NotNull final HtmlRenderer.Builder builder,
+ @NotNull final String rendererType ) {
+ if( "HTML".equalsIgnoreCase( rendererType ) ) {
+ builder.nodeRendererFactory( new TeXNodeRenderer.Factory() );
+ }
+ }
+
+ @Override
+ public void extend( final Parser.Builder builder ) {
+ builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() );
+ }
+
+ @Override
+ public void rendererOptions( @NotNull final MutableDataHolder options ) {
+ }
+
+ @Override
+ public void parserOptions( final MutableDataHolder options ) {
+ }
+}
src/main/java/com/scrivenvar/processors/markdown/TeXInlineDelimiterProcessor.java
+package com.scrivenvar.processors.markdown;
+
+import com.vladsch.flexmark.parser.InlineParser;
+import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
+import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor;
+import com.vladsch.flexmark.parser.delimiter.DelimiterRun;
+import com.vladsch.flexmark.util.ast.Node;
+
+public class TeXInlineDelimiterProcessor implements DelimiterProcessor {
+
+ @Override
+ public void process( final Delimiter opener, final Delimiter closer,
+ final int delimitersUsed ) {
+ final var node = new TeXNode();
+ opener.moveNodesBetweenDelimitersTo(node, closer);
+ }
+
+ @Override
+ public char getOpeningCharacter() {
+ return '$';
+ }
+
+ @Override
+ public char getClosingCharacter() {
+ return '$';
+ }
+
+ @Override
+ public int getMinLength() {
+ return 1;
+ }
+
+ /**
+ * Allow for $ or $$.
+ *
+ * @param opener One or more opening delimiter characters.
+ * @param closer One or more closing delimiter characters.
+ * @return The number of delimiters to use to determine whether a valid
+ * opening delimiter expression is found.
+ */
+ @Override
+ public int getDelimiterUse(
+ final DelimiterRun opener, final DelimiterRun closer ) {
+ return 1;
+ }
+
+ @Override
+ public boolean canBeOpener( final String before,
+ final String after,
+ final boolean leftFlanking,
+ final boolean rightFlanking,
+ final boolean beforeIsPunctuation,
+ final boolean afterIsPunctuation,
+ final boolean beforeIsWhitespace,
+ final boolean afterIsWhiteSpace ) {
+ return leftFlanking;
+ }
+
+ @Override
+ public boolean canBeCloser( final String before,
+ final String after,
+ final boolean leftFlanking,
+ final boolean rightFlanking,
+ final boolean beforeIsPunctuation,
+ final boolean afterIsPunctuation,
+ final boolean beforeIsWhitespace,
+ final boolean afterIsWhiteSpace ) {
+ return rightFlanking;
+ }
+
+ @Override
+ public Node unmatchedDelimiterNode(
+ final InlineParser inlineParser, final DelimiterRun delimiter ) {
+ return null;
+ }
+
+ @Override
+ public boolean skipNonOpenerCloser() {
+ return false;
+ }
+}
src/main/java/com/scrivenvar/processors/markdown/TeXNode.java
+package com.scrivenvar.processors.markdown;
+
+import com.vladsch.flexmark.ast.DelimitedNodeImpl;
+
+public class TeXNode extends DelimitedNodeImpl {
+
+ public TeXNode() {
+ }
+
+}
src/main/java/com/scrivenvar/processors/markdown/TeXNodeRenderer.java
+package com.scrivenvar.processors.markdown;
+
+import com.vladsch.flexmark.html.HtmlWriter;
+import com.vladsch.flexmark.html.renderer.NodeRenderer;
+import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
+import com.vladsch.flexmark.util.data.DataHolder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class TeXNodeRenderer implements NodeRenderer {
+
+ public static class Factory implements NodeRendererFactory {
+ @NotNull
+ @Override
+ public NodeRenderer apply( @NotNull DataHolder options ) {
+ return new TeXNodeRenderer();
+ }
+ }
+
+ @Override
+ public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
+ final Set<NodeRenderingHandler<?>> set = new HashSet<>();
+ set.add( new NodeRenderingHandler<>(
+ TeXNode.class, this::render ) );
+
+ return set;
+ }
+
+ private void render( final TeXNode node,
+ final NodeRendererContext context,
+ final HtmlWriter html ) {
+ html.tag( "tex" );
+ html.raw( node.getText() );
+ html.closeTag( "tex" );
+ }
+}