Render maths with respect to `data-mx-maths`
(https://github.com/matrix-org/matrix-doc/pull/2191) Firstly, this implements a commonmark-java plugin which is solely used to parse LaTeX input in the composer box, so that they can be rendered into `<span data-mx-maths=...>fallback</span>` and `<div data-mx-maths=...>fallback</div>` for inline and display maths respectively in the sent message. Secondly, received messages of this form are pre-processed by a simple regex into a form which markwon (which performs the rendering) expects.
This commit is contained in:
parent
78b870c558
commit
20821fbe80
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths;
|
||||
|
||||
import org.commonmark.node.CustomBlock;
|
||||
|
||||
public class DisplayMaths extends CustomBlock {
|
||||
public enum DisplayDelimiter {
|
||||
DOUBLE_DOLLAR,
|
||||
SQUARE_BRACKET_ESCAPED
|
||||
};
|
||||
|
||||
private DisplayDelimiter delimiter;
|
||||
|
||||
public DisplayMaths(DisplayDelimiter delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
import org.commonmark.node.Delimited;
|
||||
|
||||
public class InlineMaths extends CustomNode implements Delimited {
|
||||
public enum InlineDelimiter {
|
||||
SINGLE_DOLLAR,
|
||||
ROUND_BRACKET_ESCAPED
|
||||
};
|
||||
|
||||
private InlineDelimiter delimiter;
|
||||
|
||||
public InlineMaths(InlineDelimiter delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOpeningDelimiter() {
|
||||
switch (delimiter) {
|
||||
case SINGLE_DOLLAR:
|
||||
return "$";
|
||||
case ROUND_BRACKET_ESCAPED:
|
||||
return "\\(";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClosingDelimiter() {
|
||||
switch (delimiter) {
|
||||
case SINGLE_DOLLAR:
|
||||
return "$";
|
||||
case ROUND_BRACKET_ESCAPED:
|
||||
return "\\)";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths;
|
||||
|
||||
import org.commonmark.Extension;
|
||||
import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor;
|
||||
import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.NodeRenderer;
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererContext;
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererFactory;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
public class MathsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
|
||||
|
||||
private MathsExtension() {
|
||||
}
|
||||
|
||||
public static Extension create() {
|
||||
return new MathsExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extend(Parser.Builder parserBuilder) {
|
||||
parserBuilder.customDelimiterProcessor(new DollarMathsDelimiterProcessor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extend(HtmlRenderer.Builder rendererBuilder) {
|
||||
rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() {
|
||||
@Override
|
||||
public NodeRenderer create(HtmlNodeRendererContext context) {
|
||||
return new MathsHtmlNodeRenderer(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths.internal;
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths;
|
||||
import org.commonmark.ext.maths.InlineMaths;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
||||
import org.commonmark.parser.delimiter.DelimiterRun;
|
||||
|
||||
public class DollarMathsDelimiterProcessor implements DelimiterProcessor {
|
||||
@Override
|
||||
public char getOpeningCharacter() {
|
||||
return '$';
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getClosingCharacter() {
|
||||
return '$';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinLength() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
|
||||
if (opener.length() == 1 && closer.length() == 1)
|
||||
return 1; // inline
|
||||
else if (opener.length() == 2 && closer.length() == 2)
|
||||
return 2; // display
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Text opener, Text closer, int delimiterUse) {
|
||||
Node maths = delimiterUse == 1 ? new InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) :
|
||||
new DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR);
|
||||
|
||||
Node tmp = opener.getNext();
|
||||
while (tmp != null && tmp != closer) {
|
||||
Node next = tmp.getNext();
|
||||
maths.appendChild(tmp);
|
||||
tmp = next;
|
||||
}
|
||||
|
||||
opener.insertAfter(maths);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths.internal;
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Text;
|
||||
import org.commonmark.renderer.html.HtmlNodeRendererContext;
|
||||
import org.commonmark.renderer.html.HtmlWriter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class MathsHtmlNodeRenderer extends MathsNodeRenderer {
|
||||
private final HtmlNodeRendererContext context;
|
||||
private final HtmlWriter html;
|
||||
|
||||
public MathsHtmlNodeRenderer(HtmlNodeRendererContext context) {
|
||||
this.context = context;
|
||||
this.html = context.getWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Node node) {
|
||||
boolean display = node.getClass() == DisplayMaths.class;
|
||||
Node contents = node.getFirstChild(); // should be the only child
|
||||
String latex = ((Text) contents).getLiteral();
|
||||
Map<String, String> attributes = context.extendAttributes(node, display ? "div" : "span", Collections.<String, String>singletonMap("data-mx-maths",
|
||||
latex));
|
||||
html.tag(display ? "div" : "span", attributes);
|
||||
html.tag("code");
|
||||
context.render(contents);
|
||||
html.tag("/code");
|
||||
html.tag(display ? "/div" : "/span");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.commonmark.ext.maths.internal;
|
||||
|
||||
import org.commonmark.ext.maths.DisplayMaths;
|
||||
import org.commonmark.ext.maths.InlineMaths;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.renderer.NodeRenderer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class MathsNodeRenderer implements NodeRenderer {
|
||||
@Override
|
||||
public Set<Class<? extends Node>> getNodeTypes() {
|
||||
final Set<Class<? extends Node>> types = new HashSet<Class<? extends Node>>();
|
||||
types.add(InlineMaths.class);
|
||||
types.add(DisplayMaths.class);
|
||||
return types;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.commonmark.Extension
|
||||
import org.commonmark.ext.maths.MathsExtension
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.matrix.android.sdk.api.session.file.FileService
|
||||
|
@ -104,6 +106,7 @@ internal abstract class RoomModule {
|
|||
|
||||
@Module
|
||||
companion object {
|
||||
private val extensions : List<Extension> = listOf(MathsExtension.create())
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@SessionScope
|
||||
|
@ -121,7 +124,7 @@ internal abstract class RoomModule {
|
|||
@Provides
|
||||
@JvmStatic
|
||||
fun providesParser(): Parser {
|
||||
return Parser.builder().build()
|
||||
return Parser.builder().extensions(extensions).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -129,6 +132,7 @@ internal abstract class RoomModule {
|
|||
fun providesHtmlRenderer(): HtmlRenderer {
|
||||
return HtmlRenderer
|
||||
.builder()
|
||||
.extensions(extensions)
|
||||
.softbreak("<br />")
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ internal class MarkdownParser @Inject constructor(
|
|||
private val textPillsUtils: TextPillsUtils
|
||||
) {
|
||||
|
||||
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex()
|
||||
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~$]".toRegex()
|
||||
|
||||
fun parse(text: CharSequence): TextContent {
|
||||
val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString()
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
|||
import android.text.Spannable
|
||||
import androidx.core.text.toSpannable
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonPlugin
|
||||
import io.noties.markwon.ext.latex.JLatexMathPlugin
|
||||
|
@ -41,6 +42,13 @@ class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfi
|
|||
|
||||
private val markwon = Markwon.builder(context)
|
||||
.usePlugin(HtmlPlugin.create(htmlConfigure))
|
||||
.usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex
|
||||
override fun processMarkdown(markdown: String): String {
|
||||
return markdown
|
||||
.replace(Regex("""<span\s+data-mx-maths="([^"]*)">.*?</span>""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" }
|
||||
.replace(Regex("""<div\s+data-mx-maths="([^"]*)">.*?</div>""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" }
|
||||
}
|
||||
})
|
||||
.usePlugin(MarkwonInlineParserPlugin.create())
|
||||
.usePlugin(JLatexMathPlugin.create(44F) { builder ->
|
||||
builder.inlinesEnabled(true)
|
||||
|
|
Loading…
Reference in New Issue