From 9c9743361f3cc66dae9ca4f22c2fede1c28cfdb5 Mon Sep 17 00:00:00 2001 From: Islam Date: Sat, 26 Feb 2022 07:30:44 +0100 Subject: [PATCH 1/2] Adding Tex Inline support for the issue #386 --- .../commands/mathcommands/TeXCommand.java | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java index 468fe7fda2..4b6b33f3fe 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.interactions.components.Button; import net.dv8tion.jda.api.interactions.components.ButtonStyle; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.scilab.forge.jlatexmath.ParseException; import org.scilab.forge.jlatexmath.TeXConstants; import org.scilab.forge.jlatexmath.TeXFormula; @@ -21,6 +22,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 @@ -34,6 +37,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"); @@ -44,8 +52,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); @@ -57,6 +64,9 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { 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(); @@ -92,6 +102,35 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { .queue(); } + /** + * + * 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("One dollar sign is invalid, wrap whole expression with it. "); + } + 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; + } + @Override public void onButtonClick(@NotNull final ButtonClickEvent event, @NotNull final List args) { From 8cf0a9ce9d797a40058100e8e5d09c4640689af0 Mon Sep 17 00:00:00 2001 From: Islam Date: Mon, 28 Feb 2022 11:02:18 +0100 Subject: [PATCH 2/2] Refactoring TexCommand --- .../commands/mathcommands/TeXCommand.java | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java index 4b6b33f3fe..229029aa6c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/TeXCommand.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.api.interactions.components.Button; import net.dv8tion.jda.api.interactions.components.ButtonStyle; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.scilab.forge.jlatexmath.ParseException; import org.scilab.forge.jlatexmath.TeXConstants; import org.scilab.forge.jlatexmath.TeXFormula; @@ -63,6 +62,7 @@ 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); @@ -72,49 +72,76 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { 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("One dollar sign is invalid, wrap whole expression with it. "); + 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) {