Skip to content

Commit de4a670

Browse files
committed
Render maths with respect to data-mx-maths
(matrix-org/matrix-spec-proposals#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.
1 parent 13fce9b commit de4a670

File tree

9 files changed

+303
-2
lines changed

9 files changed

+303
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths;
18+
19+
import org.commonmark.node.CustomBlock;
20+
21+
public class DisplayMaths extends CustomBlock {
22+
public enum DisplayDelimiter {
23+
DOUBLE_DOLLAR,
24+
SQUARE_BRACKET_ESCAPED
25+
};
26+
27+
private DisplayDelimiter delimiter;
28+
29+
public DisplayMaths(DisplayDelimiter delimiter) {
30+
this.delimiter = delimiter;
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths;
18+
19+
import org.commonmark.node.CustomNode;
20+
import org.commonmark.node.Delimited;
21+
22+
public class InlineMaths extends CustomNode implements Delimited {
23+
public enum InlineDelimiter {
24+
SINGLE_DOLLAR,
25+
ROUND_BRACKET_ESCAPED
26+
};
27+
28+
private InlineDelimiter delimiter;
29+
30+
public InlineMaths(InlineDelimiter delimiter) {
31+
this.delimiter = delimiter;
32+
}
33+
34+
@Override
35+
public String getOpeningDelimiter() {
36+
switch (delimiter) {
37+
case SINGLE_DOLLAR:
38+
return "$";
39+
case ROUND_BRACKET_ESCAPED:
40+
return "\\(";
41+
}
42+
return null;
43+
}
44+
45+
@Override
46+
public String getClosingDelimiter() {
47+
switch (delimiter) {
48+
case SINGLE_DOLLAR:
49+
return "$";
50+
case ROUND_BRACKET_ESCAPED:
51+
return "\\)";
52+
}
53+
return null;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths;
18+
19+
import org.commonmark.Extension;
20+
import org.commonmark.ext.maths.internal.DollarMathsDelimiterProcessor;
21+
import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer;
22+
import org.commonmark.parser.Parser;
23+
import org.commonmark.renderer.NodeRenderer;
24+
import org.commonmark.renderer.html.HtmlNodeRendererContext;
25+
import org.commonmark.renderer.html.HtmlNodeRendererFactory;
26+
import org.commonmark.renderer.html.HtmlRenderer;
27+
28+
public class MathsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension {
29+
30+
private MathsExtension() {
31+
}
32+
33+
public static Extension create() {
34+
return new MathsExtension();
35+
}
36+
37+
@Override
38+
public void extend(Parser.Builder parserBuilder) {
39+
parserBuilder.customDelimiterProcessor(new DollarMathsDelimiterProcessor());
40+
}
41+
42+
@Override
43+
public void extend(HtmlRenderer.Builder rendererBuilder) {
44+
rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() {
45+
@Override
46+
public NodeRenderer create(HtmlNodeRendererContext context) {
47+
return new MathsHtmlNodeRenderer(context);
48+
}
49+
});
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths.internal;
18+
19+
import org.commonmark.ext.maths.DisplayMaths;
20+
import org.commonmark.ext.maths.InlineMaths;
21+
import org.commonmark.node.Node;
22+
import org.commonmark.node.Text;
23+
import org.commonmark.parser.delimiter.DelimiterProcessor;
24+
import org.commonmark.parser.delimiter.DelimiterRun;
25+
26+
public class DollarMathsDelimiterProcessor implements DelimiterProcessor {
27+
@Override
28+
public char getOpeningCharacter() {
29+
return '$';
30+
}
31+
32+
@Override
33+
public char getClosingCharacter() {
34+
return '$';
35+
}
36+
37+
@Override
38+
public int getMinLength() {
39+
return 1;
40+
}
41+
42+
@Override
43+
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
44+
if (opener.length() == 1 && closer.length() == 1)
45+
return 1; // inline
46+
else if (opener.length() == 2 && closer.length() == 2)
47+
return 2; // display
48+
else
49+
return 0;
50+
}
51+
52+
@Override
53+
public void process(Text opener, Text closer, int delimiterUse) {
54+
Node maths = delimiterUse == 1 ? new InlineMaths(InlineMaths.InlineDelimiter.SINGLE_DOLLAR) :
55+
new DisplayMaths(DisplayMaths.DisplayDelimiter.DOUBLE_DOLLAR);
56+
57+
Node tmp = opener.getNext();
58+
while (tmp != null && tmp != closer) {
59+
Node next = tmp.getNext();
60+
maths.appendChild(tmp);
61+
tmp = next;
62+
}
63+
64+
opener.insertAfter(maths);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths.internal;
18+
19+
import org.commonmark.ext.maths.DisplayMaths;
20+
import org.commonmark.node.Node;
21+
import org.commonmark.node.Text;
22+
import org.commonmark.renderer.html.HtmlNodeRendererContext;
23+
import org.commonmark.renderer.html.HtmlWriter;
24+
25+
import java.util.Collections;
26+
import java.util.Map;
27+
28+
public class MathsHtmlNodeRenderer extends MathsNodeRenderer {
29+
private final HtmlNodeRendererContext context;
30+
private final HtmlWriter html;
31+
32+
public MathsHtmlNodeRenderer(HtmlNodeRendererContext context) {
33+
this.context = context;
34+
this.html = context.getWriter();
35+
}
36+
37+
@Override
38+
public void render(Node node) {
39+
boolean display = node.getClass() == DisplayMaths.class;
40+
Node contents = node.getFirstChild(); // should be the only child
41+
String latex = ((Text) contents).getLiteral();
42+
Map<String, String> attributes = context.extendAttributes(node, display ? "div" : "span", Collections.<String, String>singletonMap("data-mx-maths",
43+
latex));
44+
html.tag(display ? "div" : "span", attributes);
45+
html.tag("code");
46+
context.render(contents);
47+
html.tag("/code");
48+
html.tag(display ? "/div" : "/span");
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2020 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.commonmark.ext.maths.internal;
18+
19+
import org.commonmark.ext.maths.DisplayMaths;
20+
import org.commonmark.ext.maths.InlineMaths;
21+
import org.commonmark.node.Node;
22+
import org.commonmark.renderer.NodeRenderer;
23+
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
27+
abstract class MathsNodeRenderer implements NodeRenderer {
28+
@Override
29+
public Set<Class<? extends Node>> getNodeTypes() {
30+
final Set<Class<? extends Node>> types = new HashSet<Class<? extends Node>>();
31+
types.add(InlineMaths.class);
32+
types.add(DisplayMaths.class);
33+
return types;
34+
}
35+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.session.room
1919
import dagger.Binds
2020
import dagger.Module
2121
import dagger.Provides
22+
import org.commonmark.Extension
23+
import org.commonmark.ext.maths.MathsExtension
2224
import org.commonmark.parser.Parser
2325
import org.commonmark.renderer.html.HtmlRenderer
2426
import org.matrix.android.sdk.api.session.file.FileService
@@ -83,6 +85,7 @@ internal abstract class RoomModule {
8385

8486
@Module
8587
companion object {
88+
private val extensions : List<Extension> = listOf(MathsExtension.create())
8689
@Provides
8790
@JvmStatic
8891
@SessionScope
@@ -93,14 +96,15 @@ internal abstract class RoomModule {
9396
@Provides
9497
@JvmStatic
9598
fun providesParser(): Parser {
96-
return Parser.builder().build()
99+
return Parser.builder().extensions(extensions).build()
97100
}
98101

99102
@Provides
100103
@JvmStatic
101104
fun providesHtmlRenderer(): HtmlRenderer {
102105
return HtmlRenderer
103106
.builder()
107+
.extensions(extensions)
104108
.build()
105109
}
106110
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParser.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal class MarkdownParser @Inject constructor(
3030
private val htmlRenderer: HtmlRenderer
3131
) {
3232

33-
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex()
33+
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~$]".toRegex()
3434

3535
fun parse(text: String): TextContent {
3636
// If no special char are detected, just return plain text

vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import im.vector.app.core.di.ActiveSessionHolder
2121
import im.vector.app.core.glide.GlideApp
2222
import im.vector.app.core.resources.ColorProvider
2323
import im.vector.app.features.home.AvatarRenderer
24+
import io.noties.markwon.AbstractMarkwonPlugin
2425
import io.noties.markwon.Markwon
2526
import io.noties.markwon.MarkwonPlugin
2627
import io.noties.markwon.ext.latex.JLatexMathPlugin
@@ -39,6 +40,13 @@ class EventHtmlRenderer @Inject constructor(context: Context,
3940

4041
private val markwon = Markwon.builder(context)
4142
.usePlugin(HtmlPlugin.create(htmlConfigure))
43+
.usePlugin(object : AbstractMarkwonPlugin() { // Markwon expects maths to be in a specific format: https://noties.io/Markwon/docs/v4/ext-latex
44+
override fun processMarkdown(markdown: String): String {
45+
return markdown
46+
.replace(Regex("""<span\s+data-mx-maths="([^"]*)">.*?</span>""")) { matchResult -> "$$" + matchResult.groupValues[1] + "$$" }
47+
.replace(Regex("""<div\s+data-mx-maths="([^"]*)">.*?</div>""")) { matchResult -> "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" }
48+
}
49+
})
4250
.usePlugin(MarkwonInlineParserPlugin.create())
4351
.usePlugin(JLatexMathPlugin.create(44F) { builder ->
4452
builder.inlinesEnabled(true)

0 commit comments

Comments
 (0)