TheRightGuy

finishing touches

Feb 28th, 2022 (edited)
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 6.86 KB | None | 0 0
  1. package org.togetherjava.tjbot.commands.mathcommands;
  2.  
  3. import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
  4. import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
  5. import net.dv8tion.jda.api.interactions.commands.OptionType;
  6. import net.dv8tion.jda.api.interactions.components.Button;
  7. import net.dv8tion.jda.api.interactions.components.ButtonStyle;
  8. import org.jetbrains.annotations.NotNull;
  9. import org.scilab.forge.jlatexmath.ParseException;
  10. import org.scilab.forge.jlatexmath.TeXConstants;
  11. import org.scilab.forge.jlatexmath.TeXFormula;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import org.togetherjava.tjbot.commands.SlashCommandAdapter;
  15. import org.togetherjava.tjbot.commands.SlashCommandVisibility;
  16.  
  17. import javax.imageio.ImageIO;
  18. import java.awt.*;
  19. import java.awt.image.BufferedImage;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.IOException;
  22. import java.util.List;
  23. import java.util.Objects;
  24. import java.util.regex.Matcher;
  25. import java.util.regex.Pattern;
  26.  
  27. /**
  28.  * Implementation of a tex command which takes a string and renders an image corresponding to the
  29.  * mathematical expression in that string.
  30.  * <p>
  31.  * The implemented command is {@code /tex}. This has a single option called {@code latex} which is a
  32.  * string. If it is invalid latex or there is an error in rendering the image, it displays an error
  33.  * message.
  34.  */
  35.  
  36. public class TeXCommand extends SlashCommandAdapter {
  37.  
  38.     private static final String LATEX_OPTION = "latex";
  39.     // Matches regions between two dollars, like '$foo$'.
  40.     private static final String MATH_REGION = "(\\$[^$]+\\$)";
  41.     private static final String TEXT_REGION = "([^$]+)";
  42.     private static final Pattern INLINE_LATEX_REPLACEMENT =
  43.             Pattern.compile(MATH_REGION + "|" + TEXT_REGION);
  44.     private static final String RENDERING_ERROR = "There was an error generating the image";
  45.     private static final float DEFAULT_IMAGE_SIZE = 40F;
  46.     private static final Color BACKGROUND_COLOR = Color.decode("#36393F");
  47.     private static final Color FOREGROUND_COLOR = Color.decode("#FFFFFF");
  48.     private static final Logger logger = LoggerFactory.getLogger(TeXCommand.class);
  49.  
  50.     /**
  51.      * Creates a new Instance.
  52.      */
  53.     public TeXCommand() {
  54.         super("tex", "Renders LaTeX, also supports inline $-regions like 'see this $frac[x}{2}$'.",
  55.                 SlashCommandVisibility.GUILD);
  56.         getData().addOption(OptionType.STRING, LATEX_OPTION,
  57.                 "The latex which is rendered as an image", true);
  58.     }
  59.  
  60.     @Override
  61.     public void onSlashCommand(@NotNull final SlashCommandEvent event) {
  62.         String latex = Objects.requireNonNull(event.getOption(LATEX_OPTION)).getAsString();
  63.         String userID = (Objects.requireNonNull(event.getMember()).getId());
  64.         TeXFormula formula;
  65.  
  66.         try {
  67.             if (latex.contains("$")) {
  68.                 latex = convertInlineLatexToFull(latex);
  69.             }
  70.             formula = new TeXFormula(latex);
  71.         } catch (ParseException e) {
  72.             event.reply("That is an invalid latex: " + e.getMessage()).setEphemeral(true).queue();
  73.             return;
  74.         }
  75.  
  76.         event.deferReply().queue();
  77.  
  78.         try {
  79.             Image image = renderImage(formula);
  80.             sendImage(event, userID, image);
  81.         } catch (IOException e) {
  82.             event.getHook().editOriginal(RENDERING_ERROR).queue();
  83.             logger.warn(
  84.                     "Unable to render latex, could not convert the image into an attachable form. Formula was {}",
  85.                     latex, e);
  86.  
  87.         } catch (IllegalStateException e) {
  88.             event.getHook().editOriginal(RENDERING_ERROR).queue();
  89.  
  90.             logger.warn(
  91.                     "Unable to render latex, image does not have an accessible width or height. Formula was {}",
  92.                     latex, e);
  93.         }
  94.     }
  95.  
  96.     private @NotNull Image renderImage(@NotNull TeXFormula formula) {
  97.         Image image = formula.createBufferedImage(TeXConstants.STYLE_DISPLAY, DEFAULT_IMAGE_SIZE,
  98.                 FOREGROUND_COLOR, BACKGROUND_COLOR);
  99.  
  100.         if (image.getWidth(null) == -1 || image.getHeight(null) == -1) {
  101.             throw new IllegalStateException("Image has no height or width");
  102.         }
  103.         return image;
  104.     }
  105.  
  106.     private void sendImage(@NotNull SlashCommandEvent event, @NotNull String userID,
  107.                            @NotNull Image image) throws IOException {
  108.  
  109.         ByteArrayOutputStream renderedTextImageStream = getRenderedTextImageStream(image);
  110.         event.getHook()
  111.                 .editOriginal(renderedTextImageStream.toByteArray(), "tex.png")
  112.                 .setActionRow(Button.of(ButtonStyle.DANGER, generateComponentId(userID), "Delete"))
  113.                 .queue();
  114.     }
  115.  
  116.     @NotNull
  117.     private ByteArrayOutputStream getRenderedTextImageStream(@NotNull Image image)
  118.             throws IOException {
  119.  
  120.         BufferedImage renderedTextImage = new BufferedImage(image.getWidth(null),
  121.                 image.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR);
  122.  
  123.         renderedTextImage.getGraphics().drawImage(image, 0, 0, null);
  124.         ByteArrayOutputStream renderedTextImageStream = new ByteArrayOutputStream();
  125.  
  126.         ImageIO.write(renderedTextImage, "png", renderedTextImageStream);
  127.  
  128.         return renderedTextImageStream;
  129.     }
  130.  
  131.     /**
  132.      * Converts inline latex like: {@code hello $\frac{x}{2}$ world} to full latex
  133.      * {@code \text{hello}\frac{x}{2}\text{ world}}.
  134.      */
  135.     @NotNull
  136.     private String convertInlineLatexToFull(@NotNull String latex) {
  137.         if (isInvalidInlineFormat(latex)) {
  138.             throw new ParseException(
  139.                     "The amount of $-symbols must be divisible by two. Did you forget to close an expression? ");
  140.         }
  141.  
  142.         Matcher matcher = INLINE_LATEX_REPLACEMENT.matcher(latex);
  143.         StringBuilder sb = new StringBuilder();
  144.  
  145.         while (matcher.find()) {
  146.             boolean isInsideMathRegion = matcher.group(1) != null;
  147.             if (isInsideMathRegion) {
  148.                 sb.append(matcher.group(1).replace("$", ""));
  149.             } else {
  150.                 sb.append("\\text{").append(matcher.group(2)).append("}");
  151.             }
  152.         }
  153.  
  154.         return sb.toString();
  155.     }
  156.  
  157.     private boolean isInvalidInlineFormat(String latex) {
  158.         return latex.chars().filter(ch -> ch == '$').count() % 2 == 1;
  159.     }
  160.  
  161.     @Override
  162.     public void onButtonClick(@NotNull final ButtonClickEvent event,
  163.                               @NotNull final List<String> args) {
  164.         if (!args.get(0).equals(Objects.requireNonNull(event.getMember()).getId())) {
  165.             event.reply("You are not the person who executed the command, you cannot do that")
  166.                     .setEphemeral(true)
  167.                     .queue();
  168.             return;
  169.         }
  170.         event.getMessage().delete().queue();
  171.     }
  172. }
  173.  
Add Comment
Please, Sign In to add comment