| Author | DaveJarvis <email> |
|---|---|
| Date | 2020-09-13 14:22:20 GMT-0700 |
| Commit | 8171250a1c10e9e1f564d9395a4f239c04bc5f41 |
| Parent | e4db02b |
| Delta | 205 lines added, 8 lines removed, 197-line increase |
| * See SVGReplacedElementFactory for details. | ||
| */ | ||
| -svg { | ||
| +svg, tex { | ||
| /* Ensure the formulas can be inlined with text. */ | ||
| display: inline-block; |
| 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() ); | ||
| } | ||
| 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(); | ||
| } | ||
| +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 ) { | ||
| + } | ||
| +} | ||
| +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; | ||
| + } | ||
| +} | ||
| +package com.scrivenvar.processors.markdown; | ||
| + | ||
| +import com.vladsch.flexmark.ast.DelimitedNodeImpl; | ||
| + | ||
| +public class TeXNode extends DelimitedNodeImpl { | ||
| + | ||
| + public TeXNode() { | ||
| + } | ||
| + | ||
| +} | ||
| +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" ); | ||
| + } | ||
| +} | ||