Skip to content

Adding Tex Inline support for the issue #386 #397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Implementation of a tex command which takes a string and renders an image corresponding to the
Expand All @@ -34,6 +36,11 @@
public class TeXCommand extends SlashCommandAdapter {

private static final String LATEX_OPTION = "latex";
// Matches regions between two dollars, like '$foo$'.
private static final String MATH_REGION = "(\\$[^$]+\\$)";
private static final String TEXT_REGION = "([^$]+)";
private static final Pattern INLINE_LATEX_REPLACEMENT =
Pattern.compile(MATH_REGION + "|" + TEXT_REGION);
private static final String RENDERING_ERROR = "There was an error generating the image";
private static final float DEFAULT_IMAGE_SIZE = 40F;
private static final Color BACKGROUND_COLOR = Color.decode("#36393F");
Expand All @@ -44,8 +51,7 @@ public class TeXCommand extends SlashCommandAdapter {
* Creates a new Instance.
*/
public TeXCommand() {
super("tex",
"This command accepts a latex expression and generates an image corresponding to it.",
super("tex", "Renders LaTeX, also supports inline $-regions like 'see this $frac[x}{2}$'.",
SlashCommandVisibility.GUILD);
getData().addOption(OptionType.STRING, LATEX_OPTION,
"The latex which is rendered as an image", true);
Expand All @@ -56,42 +62,102 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) {
String latex = Objects.requireNonNull(event.getOption(LATEX_OPTION)).getAsString();
String userID = (Objects.requireNonNull(event.getMember()).getId());
TeXFormula formula;

try {
if (latex.contains("$")) {
latex = convertInlineLatexToFull(latex);
}
formula = new TeXFormula(latex);
} catch (ParseException e) {
event.reply("That is an invalid latex: " + e.getMessage()).setEphemeral(true).queue();
return;
}

event.deferReply().queue();
Image image = formula.createBufferedImage(TeXConstants.STYLE_DISPLAY, DEFAULT_IMAGE_SIZE,
FOREGROUND_COLOR, BACKGROUND_COLOR);
if (image.getWidth(null) == -1 || image.getHeight(null) == -1) {
event.getHook().setEphemeral(true).editOriginal(RENDERING_ERROR).queue();
logger.warn(
"Unable to render latex, image does not have an accessible width or height. Formula was {}",
latex);
return;
}
BufferedImage renderedTextImage = new BufferedImage(image.getWidth(null),
image.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR);
renderedTextImage.getGraphics().drawImage(image, 0, 0, null);
ByteArrayOutputStream renderedTextImageStream = new ByteArrayOutputStream();

try {
ImageIO.write(renderedTextImage, "png", renderedTextImageStream);
Image image = renderImage(formula);
sendImage(event, userID, image);
} catch (IOException e) {
event.getHook().setEphemeral(true).editOriginal(RENDERING_ERROR).queue();
event.getHook().editOriginal(RENDERING_ERROR).queue();
logger.warn(
"Unable to render latex, could not convert the image into an attachable form. Formula was {}",
latex, e);
return;

} catch (IllegalStateException e) {
event.getHook().editOriginal(RENDERING_ERROR).queue();

logger.warn(
"Unable to render latex, image does not have an accessible width or height. Formula was {}",
latex, e);
}
}

private @NotNull Image renderImage(@NotNull TeXFormula formula) {
Image image = formula.createBufferedImage(TeXConstants.STYLE_DISPLAY, DEFAULT_IMAGE_SIZE,
FOREGROUND_COLOR, BACKGROUND_COLOR);

if (image.getWidth(null) == -1 || image.getHeight(null) == -1) {
throw new IllegalStateException("Image has no height or width");
}
return image;
}

private void sendImage(@NotNull SlashCommandEvent event, @NotNull String userID,
@NotNull Image image) throws IOException {

ByteArrayOutputStream renderedTextImageStream = getRenderedTextImageStream(image);
event.getHook()
.editOriginal(renderedTextImageStream.toByteArray(), "tex.png")
.setActionRow(Button.of(ButtonStyle.DANGER, generateComponentId(userID), "Delete"))
.queue();
}

@NotNull
private ByteArrayOutputStream getRenderedTextImageStream(@NotNull Image image)
throws IOException {

BufferedImage renderedTextImage = new BufferedImage(image.getWidth(null),
image.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR);

renderedTextImage.getGraphics().drawImage(image, 0, 0, null);
ByteArrayOutputStream renderedTextImageStream = new ByteArrayOutputStream();

ImageIO.write(renderedTextImage, "png", renderedTextImageStream);

return renderedTextImageStream;
}

/**
* Converts inline latex like: {@code hello $\frac{x}{2}$ world} to full latex
* {@code \text{hello}\frac{x}{2}\text{ world}}.
*/
@NotNull
private String convertInlineLatexToFull(@NotNull String latex) {
if (isInvalidInlineFormat(latex)) {
throw new ParseException(
"The amount of $-symbols must be divisible by two. Did you forget to close an expression? ");
}

Matcher matcher = INLINE_LATEX_REPLACEMENT.matcher(latex);
StringBuilder sb = new StringBuilder();

while (matcher.find()) {
boolean isInsideMathRegion = matcher.group(1) != null;
if (isInsideMathRegion) {
sb.append(matcher.group(1).replace("$", ""));
} else {
sb.append("\\text{").append(matcher.group(2)).append("}");
}
}

return sb.toString();
}

private boolean isInvalidInlineFormat(String latex) {
return latex.chars().filter(ch -> ch == '$').count() % 2 == 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.
We should escape the possible escaped \$ characters

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this and agreed that we can ignore it for now.

So unless you have a cheap and easy suggestion to adjust the code to support it, we can also go without it for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that.
Didn't know it was discussed.

But, wouldn't (?<!\\)\$ be a good regex for it? It would only match unescaped dollar signs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know it was discussed.

No worries, not your fault. It was probably in Discord and not here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, wouldn't (?<!\)$ be a good regex for it? It would only match unescaped dollar signs

Good idea. Does Javas regex engine support that? @IslamSakrak could you try that out?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also add a really simple unit test to test it too.

Yes, totally correct. We still have a separate GH issue open for adding tests to /tex, so I didnt want to bloat up this PR with more tasks than necessary (poor @IslamSakrak )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think that, as soon as this is merged, someone should tackle the unit tests for /tex. It would benefit a lot from it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct.
I'm gonna mention this point on that issue, so it doesn't fall into the forgotten realm when those tests get addressed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind testing this also, I have written some tests already. Also, first time writing tests so might help me get better at it

Copy link
Member

@Zabuzard Zabuzard Feb 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, feel free to either create a second PR or also do it directly in this, thanks. (Assign yourself to the GH issue then though)

}

@Override
public void onButtonClick(@NotNull final ButtonClickEvent event,
@NotNull final List<String> args) {
Expand Down